mirror of
https://github.com/thunderbrewhq/binana.git
synced 2026-03-22 22:00:13 +00:00
feat(binana): change tool to deposit generated files into an 'artifacts' folder that isn't retained by repository history
This commit is contained in:
parent
68f52b8efd
commit
47b08df145
44 changed files with 904 additions and 622 deletions
42
.github/workflows/push.yml
vendored
Normal file
42
.github/workflows/push.yml
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
name: Push
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- "Makefile"
|
||||||
|
- "go.mod"
|
||||||
|
- "go.sum"
|
||||||
|
- "go/**"
|
||||||
|
- "profile/**"
|
||||||
|
branches:
|
||||||
|
- artifacts
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v6
|
||||||
|
with:
|
||||||
|
go-version:
|
||||||
|
"stable" # Uses the latest stable Go version
|
||||||
|
|
||||||
|
# checkout repository
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
# build binana tool
|
||||||
|
- name: Make tool
|
||||||
|
run: make dependencies
|
||||||
|
|
||||||
|
# run binana tool
|
||||||
|
- name: Make artifacts
|
||||||
|
run: make artifacts
|
||||||
|
|
||||||
|
# upload artifacts
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: artifacts
|
||||||
|
path: artifacts/
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -1,3 +1,5 @@
|
||||||
jest
|
jest
|
||||||
.vscode
|
.vscode
|
||||||
bin
|
bin
|
||||||
|
artifacts
|
||||||
|
profile/*/symbol/main.sym
|
||||||
11
Makefile
11
Makefile
|
|
@ -3,14 +3,11 @@ all: generate
|
||||||
ce-lua:
|
ce-lua:
|
||||||
./script/build-cheatengine-scripts cheatengine profile/3.3.5a-windows-386/cheatengine
|
./script/build-cheatengine-scripts cheatengine profile/3.3.5a-windows-386/cheatengine
|
||||||
|
|
||||||
compile-symbols:
|
artifacts:
|
||||||
./script/compile-symbols profile/3.3.5a-windows-386
|
./bin/bna mk 3.3.5a-windows-386
|
||||||
|
|
||||||
profile-gen:
|
generate: artifacts
|
||||||
./bin/binana generate --profile profile/3.3.5a-windows-386
|
|
||||||
|
|
||||||
generate: compile-symbols profile-gen
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
mkdir -p bin
|
mkdir -p bin
|
||||||
go build -v -o bin/binana github.com/thunderbrewhq/binana/go/cmd/binana
|
go build -v -o bin/bna github.com/thunderbrewhq/binana/go/cmd/bna
|
||||||
|
|
|
||||||
|
|
@ -1,36 +1,36 @@
|
||||||
from ghidra.program.model.symbol.SourceType import *
|
from ghidra.program.model.symbol.SourceType import *
|
||||||
|
|
||||||
functionManager = currentProgram.getFunctionManager()
|
functionManager = currentProgram.getFunctionManager()
|
||||||
|
|
||||||
file_location = askFile("Choose a file to write to", "Go baby go!")
|
file_location = askFile("Choose a file to write to", "Go baby go!")
|
||||||
|
|
||||||
listing = currentProgram.getListing()
|
listing = currentProgram.getListing()
|
||||||
|
|
||||||
def export_function_symbols(file):
|
def export_function_symbols(file):
|
||||||
monitor.setMessage("Exporting function symbols...")
|
monitor.setMessage("Exporting function symbols...")
|
||||||
|
|
||||||
for f in functionManager.getFunctionsNoStubs(1):
|
for f in functionManager.getFunctionsNoStubs(1):
|
||||||
monitor.checkCanceled() # throws exception if canceled
|
monitor.checkCanceled() # throws exception if canceled
|
||||||
|
|
||||||
if f.isExternal() or f.isThunk():
|
if f.isExternal() or f.isThunk():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
func_name = f.getName()
|
func_name = f.getName()
|
||||||
|
|
||||||
if func_name.startswith("FUN_"):
|
if func_name.startswith("FUN_"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
func_start_address = f.getBody().getMinAddress().getOffset()
|
func_start_address = f.getBody().getMinAddress().getOffset()
|
||||||
func_end_address = f.getBody().getMaxAddress().getOffset() + 1
|
func_end_address = f.getBody().getMaxAddress().getOffset() + 1
|
||||||
|
|
||||||
line_template = "{name} {start_address:08X} f end={end_address:08X}\n"
|
line_template = "{name} {start_address:08X} f end={end_address:08X}\n"
|
||||||
|
|
||||||
func_line = line_template.format(name = func_name, start_address = func_start_address, end_address = func_end_address)
|
func_line = line_template.format(name = func_name, start_address = func_start_address, end_address = func_end_address)
|
||||||
|
|
||||||
file.write(func_line)
|
file.write(func_line)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
with open(file_location.absolutePath, "w") as file:
|
with open(file_location.absolutePath, "w") as file:
|
||||||
export_function_symbols(file)
|
export_function_symbols(file)
|
||||||
file.close()
|
file.close()
|
||||||
8
go.mod
8
go.mod
|
|
@ -1,20 +1,18 @@
|
||||||
module github.com/thunderbrewhq/binana
|
module github.com/thunderbrewhq/binana
|
||||||
|
|
||||||
go 1.22.0
|
go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/fatih/color v1.18.0
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.21
|
||||||
github.com/spf13/cobra v1.8.1
|
github.com/spf13/cobra v1.8.1
|
||||||
modernc.org/cc/v3 v3.41.0
|
modernc.org/cc/v3 v3.41.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/fatih/color v1.18.0 // indirect
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd // indirect
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
golang.org/x/sys v0.25.0 // indirect
|
golang.org/x/sys v0.25.0 // indirect
|
||||||
|
|
|
||||||
6
go.sum
6
go.sum
|
|
@ -1,14 +1,10 @@
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd h1:EVX1s+XNss9jkRW9K6XGJn2jL2lB1h5H804oKPsxOec=
|
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20240912202439-0a2b6291aafd/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
|
@ -16,8 +12,6 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
|
|
||||||
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
|
|
||||||
11
go/app/app.go
Normal file
11
go/app/app.go
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Fatal(args ...any) {
|
||||||
|
fmt.Fprintln(os.Stderr, args...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
17
go/app/cmd/execute.go
Normal file
17
go/app/cmd/execute.go
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/thunderbrewhq/binana/go/app/cmd/lint"
|
||||||
|
_ "github.com/thunderbrewhq/binana/go/app/cmd/make"
|
||||||
|
_ "github.com/thunderbrewhq/binana/go/app/cmd/tidy"
|
||||||
|
|
||||||
|
"github.com/thunderbrewhq/binana/go/app"
|
||||||
|
"github.com/thunderbrewhq/binana/go/app/cmd/root"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
err := root.RootCmd.Execute()
|
||||||
|
if err != nil {
|
||||||
|
app.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
43
go/app/cmd/lint/lint.go
Normal file
43
go/app/cmd/lint/lint.go
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/thunderbrewhq/binana/go/app"
|
||||||
|
"github.com/thunderbrewhq/binana/go/app/profile"
|
||||||
|
)
|
||||||
|
|
||||||
|
var lint_cmd = cobra.Command{
|
||||||
|
Use: "lint profile",
|
||||||
|
Short: "show warnings and coverage for a profile",
|
||||||
|
Run: lint_func,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
f := lint_cmd.Flags()
|
||||||
|
f.Bool("bounds", false, "check for bad function boundaries")
|
||||||
|
f.Bool("constructors", false, "check for outdated class constructor names")
|
||||||
|
}
|
||||||
|
|
||||||
|
func lint_func(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
cmd.Help()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
f := cmd.Flags()
|
||||||
|
var params profile.LintParams
|
||||||
|
params.Profile = args[0]
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
params.Constructors, err = f.GetBool("constructors")
|
||||||
|
if err != nil {
|
||||||
|
app.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
params.Bounds, err = f.GetBool("bounds")
|
||||||
|
if err != nil {
|
||||||
|
app.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
profile.Lint(¶ms)
|
||||||
|
}
|
||||||
32
go/app/cmd/make/make.go
Normal file
32
go/app/cmd/make/make.go
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package make
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/thunderbrewhq/binana/go/app/cmd/root"
|
||||||
|
"github.com/thunderbrewhq/binana/go/app/profile"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mk_func(cmd *cobra.Command, args []string) {
|
||||||
|
compress, err := cmd.Flags().GetBool("compress")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var params profile.MakeParams
|
||||||
|
params.Profile = args[0]
|
||||||
|
params.CompressX64dbgDatabase = compress
|
||||||
|
profile.Make(¶ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mk_cmd = cobra.Command{
|
||||||
|
Use: "mk profile",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Short: "Convert source files into various tool formats",
|
||||||
|
Run: mk_func,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
f := mk_cmd.Flags()
|
||||||
|
f.BoolP("compress", "c", true, "enable/disable compression of the x64dbg database file")
|
||||||
|
root.RootCmd.AddCommand(&mk_cmd)
|
||||||
|
}
|
||||||
8
go/app/cmd/root/root.go
Normal file
8
go/app/cmd/root/root.go
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
package root
|
||||||
|
|
||||||
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
|
var RootCmd = cobra.Command{
|
||||||
|
Use: "bna",
|
||||||
|
Short: "Binana helper tool",
|
||||||
|
}
|
||||||
25
go/app/cmd/tidy/tidy.go
Normal file
25
go/app/cmd/tidy/tidy.go
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
package tidy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/thunderbrewhq/binana/go/app/cmd/root"
|
||||||
|
"github.com/thunderbrewhq/binana/go/app/profile"
|
||||||
|
)
|
||||||
|
|
||||||
|
var tidy_cmd = cobra.Command{
|
||||||
|
Use: "tidy profile",
|
||||||
|
Short: "sort all symbol files in a profile",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
|
Run: tidy_func,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
//f := tidy_cmd.Flags()
|
||||||
|
root.RootCmd.AddCommand(&tidy_cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tidy_func(cmd *cobra.Command, args []string) {
|
||||||
|
var params profile.TidyParams
|
||||||
|
params.Profile = args[0]
|
||||||
|
profile.Tidy(¶ms)
|
||||||
|
}
|
||||||
65
go/app/profile/lint.go
Normal file
65
go/app/profile/lint.go
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
package profile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
|
"github.com/thunderbrewhq/binana/go/symbols"
|
||||||
|
)
|
||||||
|
|
||||||
|
type linter struct {
|
||||||
|
warnings uint64
|
||||||
|
named_functions_count uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (linter *linter) warn(s *symbols.TableEntry, f string, args ...any) {
|
||||||
|
linter.warnings++
|
||||||
|
color.Set(color.FgRed)
|
||||||
|
fmt.Printf(" warning: ")
|
||||||
|
color.Unset()
|
||||||
|
fmt.Printf(" in %s:%d: %s", s.Filename, s.Linenumber, s.Symbol.Name)
|
||||||
|
fmt.Printf(f, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LintParams struct {
|
||||||
|
Profile string
|
||||||
|
Constructors bool
|
||||||
|
Bounds bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Lint(params *LintParams) {
|
||||||
|
Open(params.Profile)
|
||||||
|
defer Close()
|
||||||
|
|
||||||
|
var linter linter
|
||||||
|
for entry := range Profile.Symbols.Entries() {
|
||||||
|
sn := entry.Symbol.Name
|
||||||
|
|
||||||
|
if entry.Symbol.Kind == symbols.Function {
|
||||||
|
linter.named_functions_count++
|
||||||
|
|
||||||
|
if params.Constructors {
|
||||||
|
//
|
||||||
|
b, a, found := strings.Cut(sn, "__")
|
||||||
|
if found {
|
||||||
|
if b == a {
|
||||||
|
linter.warn(entry, "this style of naming a constructor function is preferred: ClassName__constructor\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Bounds {
|
||||||
|
if entry.Symbol.EndAddress == 0 {
|
||||||
|
linter.warn(entry, "does not have an end address\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if Profile.Info.FunctionCount != 0 {
|
||||||
|
ratio := float64(linter.named_functions_count) / float64(Profile.Info.FunctionCount)
|
||||||
|
fmt.Printf("%d out of %d functions named (%f%%)\n", linter.named_functions_count, Profile.Info.FunctionCount, ratio*100.0)
|
||||||
|
fmt.Printf("%d warnings generated\n", linter.warnings)
|
||||||
|
}
|
||||||
|
}
|
||||||
20
go/app/profile/make.go
Normal file
20
go/app/profile/make.go
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
package profile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/thunderbrewhq/binana/go/app"
|
||||||
|
"github.com/thunderbrewhq/binana/go/profile"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MakeParams struct {
|
||||||
|
Profile string
|
||||||
|
profile.CompileArtifactsParams
|
||||||
|
}
|
||||||
|
|
||||||
|
func Make(params *MakeParams) {
|
||||||
|
Open(params.Profile)
|
||||||
|
defer Close()
|
||||||
|
|
||||||
|
if err := Profile.CompileArtifacts(¶ms.CompileArtifactsParams); err != nil {
|
||||||
|
app.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
go/app/profile/profile.go
Normal file
26
go/app/profile/profile.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package profile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/thunderbrewhq/binana/go/app"
|
||||||
|
"github.com/thunderbrewhq/binana/go/profile"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Profile profile.Profile
|
||||||
|
|
||||||
|
func Open(profile_name string) {
|
||||||
|
if profile_name == "" {
|
||||||
|
app.Fatal("no profile selected")
|
||||||
|
}
|
||||||
|
if err := Profile.Open(
|
||||||
|
filepath.Join("profile", profile_name),
|
||||||
|
filepath.Join("artifacts", profile_name),
|
||||||
|
); err != nil {
|
||||||
|
app.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Close() {
|
||||||
|
//Profile.Close()
|
||||||
|
}
|
||||||
61
go/app/profile/tidy.go
Normal file
61
go/app/profile/tidy.go
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package profile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/thunderbrewhq/binana/go/app"
|
||||||
|
"github.com/thunderbrewhq/binana/go/symbols"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TidyParams struct {
|
||||||
|
Profile string
|
||||||
|
}
|
||||||
|
|
||||||
|
func tidy_symbol_file(name string, params *TidyParams) (err error) {
|
||||||
|
var symbol_table symbols.Table
|
||||||
|
symbol_table.Init()
|
||||||
|
if err = symbol_table.Load(name); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var f *os.File
|
||||||
|
f, err = os.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = symbol_table.WriteTo(f); err != nil {
|
||||||
|
f.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = f.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func tidy_symbol_directory(name string, params *TidyParams) (err error) {
|
||||||
|
var contents []os.DirEntry
|
||||||
|
contents, err = os.ReadDir(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, content := range contents {
|
||||||
|
if content.IsDir() {
|
||||||
|
if err = tidy_symbol_directory(filepath.Join(name, content.Name()), params); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err = tidy_symbol_file(filepath.Join(name, content.Name()), params); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func Tidy(params *TidyParams) {
|
||||||
|
// tidy symbols
|
||||||
|
profile_symbols := filepath.Join("profile", params.Profile, "symbol")
|
||||||
|
|
||||||
|
if err := tidy_symbol_directory(profile_symbols, params); err != nil {
|
||||||
|
app.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/fatih/color"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/thunderbrewhq/binana/go/profile"
|
|
||||||
"github.com/thunderbrewhq/binana/go/symfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
type check_params struct {
|
|
||||||
ProfileName string
|
|
||||||
Constructors bool
|
|
||||||
Bounds bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func check(params *check_params) {
|
|
||||||
p, err := profile.Open(params.ProfileName)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
named_function_count := uint64(0)
|
|
||||||
|
|
||||||
warnings := uint64(0)
|
|
||||||
|
|
||||||
warn := func(s *symfile.Entry, f string, args ...any) {
|
|
||||||
warnings++
|
|
||||||
color.Set(color.FgRed)
|
|
||||||
fmt.Printf(" warning: ")
|
|
||||||
color.Unset()
|
|
||||||
fmt.Printf("in line %d: (%s): ", s.LineNumber, s.Name)
|
|
||||||
fmt.Printf(f, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range p.SymbolTable.Entries {
|
|
||||||
sn := s.Name
|
|
||||||
|
|
||||||
if s.Kind == symfile.Function {
|
|
||||||
named_function_count++
|
|
||||||
|
|
||||||
if params.Constructors {
|
|
||||||
//
|
|
||||||
b, a, found := strings.Cut(sn, "__")
|
|
||||||
if found {
|
|
||||||
if b == a {
|
|
||||||
warn(&s, "this style of naming a constructor function is preferred: ClassName__constructor\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if params.Bounds {
|
|
||||||
if s.EndAddress == 0 {
|
|
||||||
warn(&s, "does not have an end address\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Info.FunctionCount != 0 {
|
|
||||||
ratio := float64(named_function_count) / float64(p.Info.FunctionCount)
|
|
||||||
fmt.Printf("%d out of %d functions named (%f%%)\n", named_function_count, p.Info.FunctionCount, ratio*100.0)
|
|
||||||
fmt.Printf("%d warnings generated\n", warnings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func check_run(cmd *cobra.Command, args []string) {
|
|
||||||
if len(args) < 1 {
|
|
||||||
cmd.Help()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f := cmd.Flags()
|
|
||||||
var params check_params
|
|
||||||
params.ProfileName = args[0]
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
params.Constructors, err = f.GetBool("constructors")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
params.Bounds, err = f.GetBool("bounds")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
check(¶ms)
|
|
||||||
}
|
|
||||||
|
|
||||||
var check_cmd = cobra.Command{
|
|
||||||
Use: "check profile",
|
|
||||||
Short: "check a profile for correctness",
|
|
||||||
Run: check_run,
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ianlancetaylor/demangle"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/thunderbrewhq/binana/go/symfile"
|
|
||||||
)
|
|
||||||
|
|
||||||
func demangle_symbol_name(name string) (bn_name string, dm_name string, type_str string, err error) {
|
|
||||||
// var ast demangle.AST
|
|
||||||
// ast, err = demangle.ToAST(name)
|
|
||||||
// if err != nil {
|
|
||||||
// bn_name = name
|
|
||||||
// err = nil
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// var name []string
|
|
||||||
// var args []string
|
|
||||||
|
|
||||||
// ast.Traverse(func(a demangle.AST) bool {
|
|
||||||
// if fn_type, ok := a.(*demangle.FunctionType); ok {
|
|
||||||
// args = append(args, fn_)
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
var dm_err error
|
|
||||||
dm_name, dm_err = demangle.ToString(name)
|
|
||||||
if dm_err != nil {
|
|
||||||
bn_name = name
|
|
||||||
return
|
|
||||||
}
|
|
||||||
bn_name = dm_name
|
|
||||||
|
|
||||||
if strings.Contains(bn_name, "(") {
|
|
||||||
var arg string
|
|
||||||
bn_name, arg, _ = strings.Cut(bn_name, "(")
|
|
||||||
type_str = fmt.Sprintf("uint32_t func(%s", arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
bn_name = strings.ReplaceAll(bn_name, "::", "__")
|
|
||||||
bn_name = strings.ReplaceAll(bn_name, "<", "_")
|
|
||||||
bn_name = strings.ReplaceAll(bn_name, ">", "")
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func format_symbol_entry(entry *symfile.Entry, out io.Writer) {
|
|
||||||
outentry := *entry
|
|
||||||
|
|
||||||
if outentry.Name[0] == '_' && outentry.Name[1] != 'Z' {
|
|
||||||
outentry.Name = outentry.Name[1:]
|
|
||||||
}
|
|
||||||
bn_name, dm_name, _, err := demangle_symbol_name(outentry.Name)
|
|
||||||
if err == nil {
|
|
||||||
outentry.Name = bn_name
|
|
||||||
outentry.Comment = dm_name
|
|
||||||
// if outentry.DataType == "" {
|
|
||||||
// outentry.DataType = dm_type
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
out.Write([]byte(outentry.String()))
|
|
||||||
out.Write([]byte("\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func format_symbols(in io.Reader, out io.Writer) {
|
|
||||||
scanner := bufio.NewScanner(in)
|
|
||||||
for scanner.Scan() {
|
|
||||||
var entry symfile.Entry
|
|
||||||
if err := entry.Parse(scanner.Text()); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
format_symbol_entry(&entry, out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func format_symbols_run(cmd *cobra.Command, args []string) {
|
|
||||||
format_symbols(os.Stdin, os.Stdout)
|
|
||||||
}
|
|
||||||
|
|
||||||
var format_symbols_cmd = cobra.Command{
|
|
||||||
Use: "format-symbols",
|
|
||||||
Short: "format symbols from stdin",
|
|
||||||
Run: format_symbols_run,
|
|
||||||
}
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/thunderbrewhq/binana/go/profile"
|
|
||||||
)
|
|
||||||
|
|
||||||
func generate_run(cmd *cobra.Command, args []string) {
|
|
||||||
compress, err := cmd.Flags().GetBool("compress")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
game_profile_directory, err := cmd.Flags().GetString("profile")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
game_profile, err := profile.Open(game_profile_directory)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = game_profile.Generate(compress); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var generate_cmd = cobra.Command{
|
|
||||||
Use: "generate",
|
|
||||||
Short: "Convert source files into various tool formats",
|
|
||||||
Run: generate_run,
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
|
|
||||||
*/
|
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// rootCmd represents the base command when called without any subcommands
|
|
||||||
var root = cobra.Command{
|
|
||||||
Use: "binana",
|
|
||||||
Short: "Binana helper tool",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
|
||||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
|
||||||
func Execute() {
|
|
||||||
err := root.Execute()
|
|
||||||
if err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
generate_cmd.Flags().StringP("profile", "p", "3.3.5a", "the game profile")
|
|
||||||
generate_cmd.Flags().BoolP("compress", "c", true, "enable/disable compression of the x64dbg database file")
|
|
||||||
|
|
||||||
check_cmd.Flags().Bool("bounds", false, "check for bad function boundaries")
|
|
||||||
check_cmd.Flags().Bool("constructors", false, "check for outdated class constructor names")
|
|
||||||
|
|
||||||
root.AddCommand(&generate_cmd)
|
|
||||||
root.AddCommand(&format_symbols_cmd)
|
|
||||||
root.AddCommand(&x64dbg_typesort_cmd)
|
|
||||||
root.AddCommand(&check_cmd)
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/thunderbrewhq/binana/go/x64dbg"
|
|
||||||
)
|
|
||||||
|
|
||||||
var x64dbg_typesort_cmd = cobra.Command{
|
|
||||||
Use: "x64dbg-typesort [types.json file]",
|
|
||||||
Short: "sort a x64dbg types file",
|
|
||||||
Run: x64dbg_typesort_run,
|
|
||||||
}
|
|
||||||
|
|
||||||
func x64dbg_typesort_run(cmd *cobra.Command, args []string) {
|
|
||||||
types, err := x64dbg.LoadTypes(args[0])
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = x64dbg.SortTypes(types); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
encoder := json.NewEncoder(os.Stdout)
|
|
||||||
encoder.SetIndent("", " ")
|
|
||||||
if err = encoder.Encode(types); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import "github.com/thunderbrewhq/binana/go/cmd/binana/cmd"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cmd.Execute()
|
|
||||||
}
|
|
||||||
7
go/cmd/bna/main.go
Normal file
7
go/cmd/bna/main.go
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/thunderbrewhq/binana/go/app/cmd"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
||||||
35
go/profile/compile_artifacts.go
Normal file
35
go/profile/compile_artifacts.go
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
package profile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompileArtifactsParams struct {
|
||||||
|
CompressX64dbgDatabase bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (profile *Profile) CompileArtifacts(params *CompileArtifactsParams) (err error) {
|
||||||
|
if !profile.loaded {
|
||||||
|
panic(profile.loaded)
|
||||||
|
}
|
||||||
|
compilers := []struct {
|
||||||
|
OS []string
|
||||||
|
Name string
|
||||||
|
Fn func(*Profile, *CompileArtifactsParams) error
|
||||||
|
}{
|
||||||
|
{[]string{"windows", "linux", "darwin"}, "Ghidra", compile_ghidra_artifacts},
|
||||||
|
{[]string{"windows", "linux", "darwin"}, "IDA Pro", compile_idapro_artifacts},
|
||||||
|
{[]string{"windows"}, "x64dbg", compile_x64dbg_artifacts},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, compiler := range compilers {
|
||||||
|
if slices.Contains(compiler.OS, profile.Info.OS) {
|
||||||
|
fmt.Println("compiling artifacts for", compiler.Name)
|
||||||
|
if err = compiler.Fn(profile, params); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
21
go/profile/compile_ghidra_artifacts.go
Normal file
21
go/profile/compile_ghidra_artifacts.go
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
package profile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func compile_ghidra_artifacts(profile *Profile, params *CompileArtifactsParams) (err error) {
|
||||||
|
ghidra_path := filepath.Join(profile.ArtifactsDirectory, "ghidra")
|
||||||
|
if err = os.MkdirAll(ghidra_path, 0755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var symbol_file *os.File
|
||||||
|
symbol_file, err = os.Create(filepath.Join(ghidra_path, "all.sym"))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = profile.Symbols.WriteTo(symbol_file)
|
||||||
|
symbol_file.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,27 @@
|
||||||
package profile
|
package profile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/thunderbrewhq/binana/go/symfile"
|
"github.com/thunderbrewhq/binana/go/symbols"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed ida/import_all.idc
|
||||||
|
import_all_script []byte
|
||||||
|
|
||||||
|
//go:embed ida/import_data_types.idc
|
||||||
|
import_data_types_script []byte
|
||||||
|
|
||||||
|
//go:embed ida/import_functions.idc
|
||||||
|
import_functions_script []byte
|
||||||
|
|
||||||
|
//go:embed ida/import_symbols.idc
|
||||||
|
import_symbols_script []byte
|
||||||
)
|
)
|
||||||
|
|
||||||
type idac_import_batch struct {
|
type idac_import_batch struct {
|
||||||
|
|
@ -64,8 +79,12 @@ func (b *idac_import_batch) Close() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (profile *Profile) create_idac_import_batch(name string) (b *idac_import_batch, err error) {
|
func (profile *Profile) create_idac_import_batch(name string) (b *idac_import_batch, err error) {
|
||||||
|
batch_path := filepath.Join(profile.ArtifactsDirectory, "ida", "batch")
|
||||||
|
if err = os.MkdirAll(batch_path, 0755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
b = new(idac_import_batch)
|
b = new(idac_import_batch)
|
||||||
b.fd, err = os.Create(filepath.Join(profile.Directory, "ida", "batch", name+".idc"))
|
b.fd, err = os.Create(filepath.Join(batch_path, name+".idc"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -73,8 +92,7 @@ func (profile *Profile) create_idac_import_batch(name string) (b *idac_import_ba
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (profile *Profile) generate_symbols_idc() (err error) {
|
func compile_idapro_artifacts(profile *Profile, params *CompileArtifactsParams) (err error) {
|
||||||
// symbols
|
|
||||||
var b *idac_import_batch
|
var b *idac_import_batch
|
||||||
b, err = profile.create_idac_import_batch("import_symbols")
|
b, err = profile.create_idac_import_batch("import_symbols")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -88,8 +106,8 @@ func (profile *Profile) generate_symbols_idc() (err error) {
|
||||||
|
|
||||||
name_instances := make(map[string]int)
|
name_instances := make(map[string]int)
|
||||||
|
|
||||||
for _, symbol := range profile.SymbolTable.Entries {
|
for symbol := range profile.Symbols.Entries() {
|
||||||
name := symbol.Name
|
name := symbol.Symbol.Name
|
||||||
instances := name_instances[name]
|
instances := name_instances[name]
|
||||||
name_instances[name] = instances + 1
|
name_instances[name] = instances + 1
|
||||||
|
|
||||||
|
|
@ -98,7 +116,7 @@ func (profile *Profile) generate_symbols_idc() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
quoted_name := strconv.Quote(name)
|
quoted_name := strconv.Quote(name)
|
||||||
address := fmt.Sprintf("0x%08X", symbol.StartAddress)
|
address := fmt.Sprintf("0x%08X", symbol.Symbol.StartAddress)
|
||||||
b.P("set_name(%s, %s);", address, quoted_name)
|
b.P("set_name(%s, %s);", address, quoted_name)
|
||||||
}
|
}
|
||||||
b.T(0)
|
b.T(0)
|
||||||
|
|
@ -117,10 +135,10 @@ func (profile *Profile) generate_symbols_idc() (err error) {
|
||||||
b.P("static import_data_types() {")
|
b.P("static import_data_types() {")
|
||||||
b.T(1)
|
b.T(1)
|
||||||
b.P("// Give types to data labels")
|
b.P("// Give types to data labels")
|
||||||
for _, symbol := range profile.SymbolTable.Entries {
|
for symbol := range profile.Symbols.Entries() {
|
||||||
if symbol.DataType != "" {
|
if symbol.Symbol.DataType != "" {
|
||||||
quoted_data_type := strconv.Quote(symbol.DataType)
|
quoted_data_type := strconv.Quote(symbol.Symbol.DataType)
|
||||||
address := fmt.Sprintf("0x%08X", symbol.StartAddress)
|
address := fmt.Sprintf("0x%08X", symbol.Symbol.StartAddress)
|
||||||
b.P("apply_type(%s, %s);", address, quoted_data_type)
|
b.P("apply_type(%s, %s);", address, quoted_data_type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -141,16 +159,16 @@ func (profile *Profile) generate_symbols_idc() (err error) {
|
||||||
b.T(1)
|
b.T(1)
|
||||||
b.P("// Import function addresses and comments")
|
b.P("// Import function addresses and comments")
|
||||||
b.P(`msg("Importing function addresses and comments");`)
|
b.P(`msg("Importing function addresses and comments");`)
|
||||||
for _, function_symbol := range profile.SymbolTable.Entries {
|
for function_symbol := range profile.Symbols.Entries() {
|
||||||
if function_symbol.Kind == symfile.Function {
|
if function_symbol.Symbol.Kind == symbols.Function {
|
||||||
address := fmt.Sprintf("0x%08X", function_symbol.StartAddress)
|
address := fmt.Sprintf("0x%08X", function_symbol.Symbol.StartAddress)
|
||||||
// b.P("set_func_start(%s, %s);", address, address)
|
// b.P("set_func_start(%s, %s);", address, address)
|
||||||
// if function_symbol.EndAddress != 0 {
|
// if function_symbol.EndAddress != 0 {
|
||||||
// end_address := fmt.Sprintf("0x%08X", function_symbol.EndAddress)
|
// end_address := fmt.Sprintf("0x%08X", function_symbol.EndAddress)
|
||||||
// b.P("set_func_end(%s, %s);", address, end_address)
|
// b.P("set_func_end(%s, %s);", address, end_address)
|
||||||
// }
|
// }
|
||||||
if function_symbol.Comment != "" {
|
if function_symbol.Symbol.Comment != "" {
|
||||||
b.P("set_func_cmt(%s, %s, 0);", address, strconv.Quote(function_symbol.Comment))
|
b.P("set_func_cmt(%s, %s, 0);", address, strconv.Quote(function_symbol.Symbol.Comment))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -159,10 +177,23 @@ func (profile *Profile) generate_symbols_idc() (err error) {
|
||||||
|
|
||||||
b.Close()
|
b.Close()
|
||||||
|
|
||||||
return
|
// include stock scripts
|
||||||
}
|
ida_path := filepath.Join(profile.ArtifactsDirectory, "ida")
|
||||||
|
if err = os.MkdirAll(ida_path, 0755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = os.WriteFile(filepath.Join(ida_path, "import_all.idc"), import_all_script, 0755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = os.WriteFile(filepath.Join(ida_path, "import_data_types.idc"), import_data_types_script, 0755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = os.WriteFile(filepath.Join(ida_path, "import_functions.idc"), import_functions_script, 0755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = os.WriteFile(filepath.Join(ida_path, "import_symbols.idc"), import_symbols_script, 0755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (profile *Profile) CreateIDAFiles() (err error) {
|
|
||||||
err = profile.generate_symbols_idc()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
9
go/profile/compile_x64dbg_artifacts.go
Normal file
9
go/profile/compile_x64dbg_artifacts.go
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
package profile
|
||||||
|
|
||||||
|
func compile_x64dbg_artifacts(profile *Profile, params *CompileArtifactsParams) (err error) {
|
||||||
|
if err = compile_x64dbg_types(profile); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = compile_x64dbg_database(profile, params.CompressX64dbgDatabase)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -2,9 +2,10 @@ package profile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/thunderbrewhq/binana/go/symfile"
|
"github.com/thunderbrewhq/binana/go/symbols"
|
||||||
"github.com/thunderbrewhq/binana/go/x64dbg"
|
"github.com/thunderbrewhq/binana/go/x64dbg"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -12,7 +13,7 @@ func hex_address(u uint64) string {
|
||||||
return fmt.Sprintf("0x%x", u)
|
return fmt.Sprintf("0x%x", u)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (profile *Profile) generate_x64dbg_database(compress bool) (err error) {
|
func compile_x64dbg_database(profile *Profile, compress bool) (err error) {
|
||||||
// Convert symbol table into x64dbg database
|
// Convert symbol table into x64dbg database
|
||||||
|
|
||||||
is_64bit := profile.Info.Arch == "amd64"
|
is_64bit := profile.Info.Arch == "amd64"
|
||||||
|
|
@ -22,30 +23,30 @@ func (profile *Profile) generate_x64dbg_database(compress bool) (err error) {
|
||||||
|
|
||||||
var dd x64dbg.Database
|
var dd x64dbg.Database
|
||||||
|
|
||||||
for _, entry := range profile.SymbolTable.Entries {
|
for entry := range profile.Symbols.Entries() {
|
||||||
relative_start_address := entry.StartAddress - base_address
|
relative_start_address := entry.Symbol.StartAddress - base_address
|
||||||
relative_end_address := relative_start_address
|
relative_end_address := relative_start_address
|
||||||
|
|
||||||
if entry.EndAddress != 0 {
|
if entry.Symbol.EndAddress != 0 {
|
||||||
relative_end_address = entry.EndAddress - base_address
|
relative_end_address = entry.Symbol.EndAddress - base_address
|
||||||
// for x64dbg, the end address is the last instruction.
|
// for x64dbg, the end address is the last instruction.
|
||||||
// for us, the end address is the address immediately after the last instruction.
|
// for us, the end address is the address immediately after the last instruction.
|
||||||
relative_end_address -= 1
|
relative_end_address -= 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if relative_end_address < relative_start_address || relative_end_address-relative_start_address >= 50000 {
|
if relative_end_address < relative_start_address || relative_end_address-relative_start_address >= 50000 {
|
||||||
fmt.Printf("Strange symbol %s %08x %08x (offset %d)\n", entry.Name, relative_start_address, relative_end_address, relative_end_address-relative_start_address)
|
fmt.Printf("Strange symbol %s %08x %08x (offset %d)\n", entry.Symbol.Name, relative_start_address, relative_end_address, relative_end_address-relative_start_address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create label
|
// create label
|
||||||
var label x64dbg.Label
|
var label x64dbg.Label
|
||||||
label.Manual = true
|
label.Manual = true
|
||||||
label.Address = hex_address(relative_start_address)
|
label.Address = hex_address(relative_start_address)
|
||||||
label.Text = entry.Name
|
label.Text = entry.Symbol.Name
|
||||||
label.Module = module_name
|
label.Module = module_name
|
||||||
dd.Labels = append(dd.Labels, label)
|
dd.Labels = append(dd.Labels, label)
|
||||||
|
|
||||||
if entry.Kind == symfile.Function {
|
if entry.Symbol.Kind == symbols.Function {
|
||||||
var fn x64dbg.Function
|
var fn x64dbg.Function
|
||||||
fn.Manual = true
|
fn.Manual = true
|
||||||
fn.Start = hex_address(relative_start_address)
|
fn.Start = hex_address(relative_start_address)
|
||||||
|
|
@ -66,8 +67,11 @@ func (profile *Profile) generate_x64dbg_database(compress bool) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// save database
|
// save database
|
||||||
dd_path := filepath.Join(profile.Directory, "x64dbg", filename)
|
dd_path := filepath.Join(profile.ArtifactsDirectory, "x64dbg")
|
||||||
if err = x64dbg.SaveDatabase(dd_path, &dd, compress); err != nil {
|
if err = os.MkdirAll(dd_path, 0755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = x64dbg.SaveDatabase(filepath.Join(dd_path, filename), &dd, compress); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2,6 +2,7 @@ package profile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
@ -98,7 +99,7 @@ loop:
|
||||||
}
|
}
|
||||||
|
|
||||||
// parses the C headers and generates a matching x64dbg types.json file
|
// parses the C headers and generates a matching x64dbg types.json file
|
||||||
func (profile *Profile) generate_x64dbg_types() (err error) {
|
func compile_x64dbg_types(profile *Profile) (err error) {
|
||||||
// parse C headers
|
// parse C headers
|
||||||
var cc_config cc.Config
|
var cc_config cc.Config
|
||||||
cc_config.ABI, err = cc.NewABI("windows", profile.Info.Arch)
|
cc_config.ABI, err = cc.NewABI("windows", profile.Info.Arch)
|
||||||
|
|
@ -231,7 +232,11 @@ func (profile *Profile) generate_x64dbg_types() (err error) {
|
||||||
x64_types.Structs = append(x64_types.Structs, x64_struct)
|
x64_types.Structs = append(x64_types.Structs, x64_struct)
|
||||||
}
|
}
|
||||||
|
|
||||||
types_file_path := filepath.Join(profile.Directory, "x64dbg", "types.json")
|
dd_path := filepath.Join(profile.ArtifactsDirectory, "x64dbg")
|
||||||
|
if err = os.MkdirAll(dd_path, 0755); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
types_file_path := filepath.Join(dd_path, "types.json")
|
||||||
|
|
||||||
err = x64dbg.SortTypes(&x64_types)
|
err = x64dbg.SortTypes(&x64_types)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
package profile
|
|
||||||
|
|
||||||
func (profile *Profile) Generate(compress_db bool) (err error) {
|
|
||||||
if err = profile.CreateIDAFiles(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if profile.Info.OS == "windows" {
|
|
||||||
if err = profile.CreateX64dbgFiles(compress_db); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
13
go/profile/ida/import_all.idc
Normal file
13
go/profile/ida/import_all.idc
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#include <idc.idc>
|
||||||
|
#include "batch/import_symbols.idc"
|
||||||
|
#include "batch/import_data_types.idc"
|
||||||
|
#include "batch/import_functions.idc"
|
||||||
|
|
||||||
|
static main() {
|
||||||
|
// Import all
|
||||||
|
import_symbols();
|
||||||
|
import_data_types();
|
||||||
|
import_functions();
|
||||||
|
// Wait for auto-analysis to be finished
|
||||||
|
auto_wait();
|
||||||
|
}
|
||||||
6
go/profile/ida/import_data_types.idc
Normal file
6
go/profile/ida/import_data_types.idc
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#include "batch/import_data_types.idc"
|
||||||
|
|
||||||
|
static main() {
|
||||||
|
import_data_types();
|
||||||
|
auto_wait();
|
||||||
|
}
|
||||||
5
go/profile/ida/import_functions.idc
Normal file
5
go/profile/ida/import_functions.idc
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#include "batch/import_functions.idc"
|
||||||
|
|
||||||
|
static main() {
|
||||||
|
import_functions();
|
||||||
|
}
|
||||||
5
go/profile/ida/import_symbols.idc
Normal file
5
go/profile/ida/import_symbols.idc
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#include "batch/import_symbols.idc"
|
||||||
|
|
||||||
|
static main() {
|
||||||
|
import_symbols();
|
||||||
|
}
|
||||||
|
|
@ -6,16 +6,18 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/thunderbrewhq/binana/go/symfile"
|
"github.com/thunderbrewhq/binana/go/symbols"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Profile struct {
|
type Profile struct {
|
||||||
Info Info
|
loaded bool
|
||||||
Directory string
|
Info Info
|
||||||
SymbolTable *symfile.InMemoryTable
|
Directory string
|
||||||
|
ArtifactsDirectory string
|
||||||
|
Symbols symbols.Table
|
||||||
}
|
}
|
||||||
|
|
||||||
func Open(profile_directory string) (profile *Profile, err error) {
|
func (profile *Profile) Open(profile_directory, artifacts_directory string) (err error) {
|
||||||
var dir fs.FileInfo
|
var dir fs.FileInfo
|
||||||
dir, err = os.Stat(profile_directory)
|
dir, err = os.Stat(profile_directory)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -27,31 +29,23 @@ func Open(profile_directory string) (profile *Profile, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Opening profile", profile_directory)
|
fmt.Println("opening profile", profile_directory)
|
||||||
|
|
||||||
profile = new(Profile)
|
profile.Symbols.Init()
|
||||||
|
profile.Directory = profile_directory
|
||||||
|
profile.ArtifactsDirectory = artifacts_directory
|
||||||
|
|
||||||
|
// read profile meta info
|
||||||
if err = read_info(filepath.Join(profile_directory, "info.json"), &profile.Info); err != nil {
|
if err = read_info(filepath.Join(profile_directory, "info.json"), &profile.Info); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.Directory = profile_directory
|
// read symbols directory
|
||||||
|
if err = profile.Symbols.Load(filepath.Join(profile_directory, "symbol")); err != nil {
|
||||||
path_to_symbols_file := filepath.Join(profile_directory, "symbol", "main.sym")
|
|
||||||
var symbols_file *os.File
|
|
||||||
symbols_file, err = os.Open(path_to_symbols_file)
|
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.SymbolTable = new(symfile.InMemoryTable)
|
profile.loaded = true
|
||||||
|
|
||||||
if err = symfile.Load(profile.SymbolTable, symbols_file); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
symbols_file.Close()
|
|
||||||
|
|
||||||
//
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
package profile
|
|
||||||
|
|
||||||
func (profile *Profile) CreateX64dbgFiles(compress_db bool) (err error) {
|
|
||||||
if err = profile.generate_x64dbg_database(compress_db); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = profile.generate_x64dbg_types(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
8
go/symbols/error.go
Normal file
8
go/symbols/error.go
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
package symbols
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrDuplicateSymbol = fmt.Errorf("symbols: a symbol already exists at that address")
|
||||||
|
ErrLineLeftEmpty = fmt.Errorf("symbols: line was left empty")
|
||||||
|
)
|
||||||
79
go/symbols/loader.go
Normal file
79
go/symbols/loader.go
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
package symbols
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const min_columns = 3
|
||||||
|
|
||||||
|
type loader struct {
|
||||||
|
filename string
|
||||||
|
input *bufio.Reader
|
||||||
|
table *Table
|
||||||
|
line_number int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loader) read_line() (line string, err error) {
|
||||||
|
line, err = l.input.ReadString('\n')
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
line = strings.TrimRight(line, "\r\n")
|
||||||
|
l.line_number++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loader) parse_line(line string) (err error) {
|
||||||
|
var table_entry TableEntry
|
||||||
|
table_entry.Filename = l.filename
|
||||||
|
table_entry.Linenumber = l.line_number
|
||||||
|
|
||||||
|
if line == "" {
|
||||||
|
//err = fmt.Errorf("%w: %s:%d", ErrLineLeftEmpty, table_entry.Filename, table_entry.Linenumber)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = table_entry.Symbol.Parse(line); err != nil {
|
||||||
|
err = fmt.Errorf("%w: %s:%d", err, table_entry.Filename, table_entry.Linenumber)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.table.Insert(&table_entry)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func load(filename string, file io.Reader, table *Table) (err error) {
|
||||||
|
l := new(loader)
|
||||||
|
|
||||||
|
l.input = bufio.NewReader(file)
|
||||||
|
l.filename = filename
|
||||||
|
l.table = table
|
||||||
|
|
||||||
|
var (
|
||||||
|
line string
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
line, err = l.read_line()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
err = nil
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("symbols: error reading at line %d: %w", l.line_number, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = l.parse_line(line); err != nil {
|
||||||
|
err = fmt.Errorf("symbols: error parsing at line %d: %w", l.line_number, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package symfile
|
package symbols
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -69,7 +69,7 @@ func parse_attributes(attribute_columns []string) (attributes map[string]string,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (entry *Entry) Parse(line string) (err error) {
|
func (symbol *Symbol) Parse(line string) (err error) {
|
||||||
// trim extraneous whitespace
|
// trim extraneous whitespace
|
||||||
line = strings.Trim(line, " \t")
|
line = strings.Trim(line, " \t")
|
||||||
|
|
||||||
|
|
@ -89,7 +89,7 @@ func (entry *Entry) Parse(line string) (err error) {
|
||||||
// get name of symbol
|
// get name of symbol
|
||||||
name_column := columns[0]
|
name_column := columns[0]
|
||||||
if name_column == "" {
|
if name_column == "" {
|
||||||
return fmt.Errorf("symfile: (*entry).Parse: entry has invalid name '%s", name_column)
|
return fmt.Errorf("symbols: (*Symbol).Parse: entry has invalid name '%s", name_column)
|
||||||
}
|
}
|
||||||
|
|
||||||
start_address, err = strconv.ParseUint(columns[1], 16, 64)
|
start_address, err = strconv.ParseUint(columns[1], 16, 64)
|
||||||
|
|
@ -99,10 +99,10 @@ func (entry *Entry) Parse(line string) (err error) {
|
||||||
|
|
||||||
kind_column := columns[2]
|
kind_column := columns[2]
|
||||||
if len(kind_column) != 1 {
|
if len(kind_column) != 1 {
|
||||||
return fmt.Errorf("symfile: (*entry).Parse: entry has invalid kind")
|
return fmt.Errorf("symbols: (*Symbol).Parse: entry has invalid kind")
|
||||||
}
|
}
|
||||||
|
|
||||||
kind := EntryKind(kind_column[0])
|
kind := SymbolKind(kind_column[0])
|
||||||
|
|
||||||
if !slices.Contains(valid_kinds, kind) {
|
if !slices.Contains(valid_kinds, kind) {
|
||||||
return fmt.Errorf("symfile: (*entry).Parse: entry has invalid kind")
|
return fmt.Errorf("symfile: (*entry).Parse: entry has invalid kind")
|
||||||
|
|
@ -122,10 +122,10 @@ func (entry *Entry) Parse(line string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start to build entry
|
// Start to build entry
|
||||||
entry.Name = name_column
|
symbol.Name = name_column
|
||||||
entry.StartAddress = start_address
|
symbol.StartAddress = start_address
|
||||||
entry.Kind = kind
|
symbol.Kind = kind
|
||||||
entry.Comment = comment_text
|
symbol.Comment = comment_text
|
||||||
|
|
||||||
// build attributes
|
// build attributes
|
||||||
if num_semantic_columns > 3 {
|
if num_semantic_columns > 3 {
|
||||||
|
|
@ -138,11 +138,11 @@ func (entry *Entry) Parse(line string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if data_type, found := attributes["type"]; found {
|
if data_type, found := attributes["type"]; found {
|
||||||
entry.DataType = data_type
|
symbol.DataType = data_type
|
||||||
}
|
}
|
||||||
|
|
||||||
if end_address, found := attributes["end"]; found {
|
if end_address, found := attributes["end"]; found {
|
||||||
entry.EndAddress, err = strconv.ParseUint(end_address, 16, 64)
|
symbol.EndAddress, err = strconv.ParseUint(end_address, 16, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
77
go/symbols/symbol.go
Normal file
77
go/symbols/symbol.go
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
package symbols
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// What kind of Symbol is this?
|
||||||
|
type SymbolKind uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Something that can be executed
|
||||||
|
Function SymbolKind = 'f'
|
||||||
|
// Something that is read or written to
|
||||||
|
DataLabel SymbolKind = 'l'
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
valid_kinds = []SymbolKind{Function, DataLabel}
|
||||||
|
)
|
||||||
|
|
||||||
|
// An entry in the table
|
||||||
|
type Symbol struct {
|
||||||
|
// Undecorated, raw name
|
||||||
|
Name string
|
||||||
|
// Offset to the start of the function or data
|
||||||
|
StartAddress uint64
|
||||||
|
// What kind of Symbol is this?
|
||||||
|
Kind SymbolKind
|
||||||
|
// Any table entry can have a comment after a ';' column
|
||||||
|
Comment string
|
||||||
|
// Attributes
|
||||||
|
// end=AABBCCEEDD
|
||||||
|
EndAddress uint64
|
||||||
|
// type=void*
|
||||||
|
// The C syntax type of the data
|
||||||
|
DataType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Symbol) String() string {
|
||||||
|
var b strings.Builder
|
||||||
|
entry.WriteTo(&b)
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Symbol) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
var b int
|
||||||
|
b, err = fmt.Fprintf(w, "%s %08X %c", entry.Name, entry.StartAddress, entry.Kind)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n += int64(b)
|
||||||
|
if entry.EndAddress != 0 {
|
||||||
|
b, err = fmt.Fprintf(w, " end=%08X", entry.EndAddress)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n += int64(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.DataType != "" {
|
||||||
|
b, err = fmt.Fprintf(w, " type=\"%s\"", entry.DataType)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n += int64(b)
|
||||||
|
}
|
||||||
|
if entry.Comment != "" {
|
||||||
|
b, err = fmt.Fprintf(w, " ; %s", entry.Comment)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n += int64(b)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
145
go/symbols/table.go
Normal file
145
go/symbols/table.go
Normal file
|
|
@ -0,0 +1,145 @@
|
||||||
|
package symbols
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"iter"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TableEntry struct {
|
||||||
|
// the file where the symbol appears
|
||||||
|
Filename string
|
||||||
|
// the line number where the symbol appears in the file
|
||||||
|
Linenumber int
|
||||||
|
Symbol Symbol
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most tables are reasonably sized and can be kept in memory
|
||||||
|
type Table struct {
|
||||||
|
entries map[uint64]*TableEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (table *Table) Init() {
|
||||||
|
table.entries = make(map[uint64]*TableEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open opens a symbol file or a tree of symbol files.
|
||||||
|
func (table *Table) Load(name string) (err error) {
|
||||||
|
var (
|
||||||
|
root_info os.FileInfo
|
||||||
|
)
|
||||||
|
root_info, err = os.Stat(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if root_info.IsDir() {
|
||||||
|
var tree_entries []os.DirEntry
|
||||||
|
tree_entries, err = os.ReadDir(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slices.SortFunc(tree_entries, func(a, b os.DirEntry) int {
|
||||||
|
return strings.Compare(a.Name(), b.Name())
|
||||||
|
})
|
||||||
|
for _, tree_entry := range tree_entries {
|
||||||
|
tree_child_name := filepath.Join(name, tree_entry.Name())
|
||||||
|
if err = table.Load(tree_child_name); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var symbol_file *os.File
|
||||||
|
symbol_file, err = os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = table.LoadFile(name, symbol_file)
|
||||||
|
symbol_file.Close()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (table *Table) Entries() iter.Seq[*TableEntry] {
|
||||||
|
sorted_entries := make([]*TableEntry, len(table.entries))
|
||||||
|
var i int
|
||||||
|
for _, entry := range table.entries {
|
||||||
|
sorted_entries[i] = entry
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
slices.SortFunc(sorted_entries, func(a, b *TableEntry) int {
|
||||||
|
if a.Symbol.StartAddress < b.Symbol.StartAddress {
|
||||||
|
return -1
|
||||||
|
} else if a.Symbol.StartAddress == b.Symbol.StartAddress {
|
||||||
|
return 0
|
||||||
|
} else {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return func(yield func(*TableEntry) bool) {
|
||||||
|
for _, entry := range sorted_entries {
|
||||||
|
if !yield(entry) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFile reads all symbols from the input stream
|
||||||
|
func (table *Table) LoadFile(name string, file io.Reader) (err error) {
|
||||||
|
err = load(name, file, table)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (table *Table) Insert(entry *TableEntry) (err error) {
|
||||||
|
conflicting_symbol, ok := table.entries[entry.Symbol.StartAddress]
|
||||||
|
if ok {
|
||||||
|
err = fmt.Errorf(
|
||||||
|
"%w: cannot insert symbol '%s' (%s:%d) because another symbol '%s' (%s:%d) already exists at that address '%08x'",
|
||||||
|
ErrDuplicateSymbol,
|
||||||
|
entry.Symbol.Name,
|
||||||
|
entry.Filename,
|
||||||
|
entry.Linenumber,
|
||||||
|
conflicting_symbol.Symbol.Name,
|
||||||
|
conflicting_symbol.Filename,
|
||||||
|
conflicting_symbol.Linenumber,
|
||||||
|
conflicting_symbol.Symbol.StartAddress,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
table.entries[entry.Symbol.StartAddress] = entry
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (table *Table) WriteTo(w io.Writer) (n int64, err error) {
|
||||||
|
for entry := range table.Entries() {
|
||||||
|
var b int64
|
||||||
|
b, err = entry.Symbol.WriteTo(w)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n += b
|
||||||
|
_, err = w.Write([]byte("\n"))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (table *Table) Len() int {
|
||||||
|
return len(table.entries)
|
||||||
|
}
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
package symfile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const min_columns = 3
|
|
||||||
|
|
||||||
type loader struct {
|
|
||||||
input *bufio.Reader
|
|
||||||
table Table
|
|
||||||
line_number uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *loader) read_line() (line string, err error) {
|
|
||||||
l.line_number++
|
|
||||||
line, err = l.input.ReadString('\n')
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
line = strings.TrimRight(line, "\r\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *loader) parse_line(line string) (err error) {
|
|
||||||
var entry Entry
|
|
||||||
if err = entry.Parse(line); err != nil {
|
|
||||||
err = fmt.Errorf("%w: line %d", err, l.line_number)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
entry.LineNumber = l.line_number
|
|
||||||
|
|
||||||
err = l.table.Insert(&entry)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func load(text io.Reader, table Table) (err error) {
|
|
||||||
l := new(loader)
|
|
||||||
|
|
||||||
l.input = bufio.NewReader(text)
|
|
||||||
l.table = table
|
|
||||||
|
|
||||||
var (
|
|
||||||
line string
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
line, err = l.read_line()
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
err = nil
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("symfile: error reading at line %d: %w", l.line_number, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = l.parse_line(line); err != nil {
|
|
||||||
err = fmt.Errorf("symfile: error parsing at line %d: %w", l.line_number, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
package symfile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"slices"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Most tables are reasonably-sized and can be kept in memory
|
|
||||||
type InMemoryTable struct {
|
|
||||||
Entries []Entry
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *InMemoryTable) Insert(entry *Entry) (err error) {
|
|
||||||
i := sort.Search(len(t.Entries), func(i int) bool {
|
|
||||||
return t.Entries[i].StartAddress >= entry.StartAddress
|
|
||||||
})
|
|
||||||
|
|
||||||
if i < len(t.Entries) {
|
|
||||||
if t.Entries[i].StartAddress == entry.StartAddress {
|
|
||||||
err = fmt.Errorf("symfile: (*InMemoryTable).Insert() failed: duplicate entry: %s", entry.Name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Entries = slices.Insert(t.Entries, i, *entry)
|
|
||||||
} else {
|
|
||||||
t.Entries = append(t.Entries, *entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *InMemoryTable) Len() int {
|
|
||||||
return len(t.Entries)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInMemoryTable() *InMemoryTable {
|
|
||||||
return new(InMemoryTable)
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
package symfile
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// What kind of Entry is this?
|
|
||||||
type EntryKind uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Something that can be executed
|
|
||||||
Function EntryKind = 'f'
|
|
||||||
// Something that is read or written to
|
|
||||||
DataLabel EntryKind = 'l'
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
valid_kinds = []EntryKind{Function, DataLabel}
|
|
||||||
)
|
|
||||||
|
|
||||||
// An entry in the table
|
|
||||||
type Entry struct {
|
|
||||||
LineNumber uint64
|
|
||||||
// Undecorated, raw name
|
|
||||||
Name string
|
|
||||||
// Offset to the start of the function or data
|
|
||||||
StartAddress uint64
|
|
||||||
// What kind of Entry is this?
|
|
||||||
Kind EntryKind
|
|
||||||
// Any table entry can have a comment after a ';' column
|
|
||||||
Comment string
|
|
||||||
// Attributes
|
|
||||||
// end=AABBCCEEDD
|
|
||||||
EndAddress uint64
|
|
||||||
// type=void*
|
|
||||||
// The C syntax type of the data
|
|
||||||
DataType string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (entry *Entry) String() string {
|
|
||||||
var b strings.Builder
|
|
||||||
fmt.Fprintf(&b, "%s %08X %c", entry.Name, entry.StartAddress, entry.Kind)
|
|
||||||
if entry.EndAddress != 0 {
|
|
||||||
fmt.Fprintf(&b, " end=%08X", entry.EndAddress)
|
|
||||||
}
|
|
||||||
if entry.DataType != "" {
|
|
||||||
fmt.Fprintf(&b, " type=\"%s\"", entry.DataType)
|
|
||||||
}
|
|
||||||
if entry.Comment != "" {
|
|
||||||
fmt.Fprintf(&b, " ; %s", entry.Comment)
|
|
||||||
}
|
|
||||||
return b.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Table interface {
|
|
||||||
Insert(entry *Entry) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Load(table Table, text io.Reader) (err error) {
|
|
||||||
err = load(text, table)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue