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/
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,3 +1,5 @@
|
|||
jest
|
||||
.vscode
|
||||
bin
|
||||
artifacts
|
||||
profile/*/symbol/main.sym
|
||||
11
Makefile
11
Makefile
|
|
@ -3,14 +3,11 @@ all: generate
|
|||
ce-lua:
|
||||
./script/build-cheatengine-scripts cheatengine profile/3.3.5a-windows-386/cheatengine
|
||||
|
||||
compile-symbols:
|
||||
./script/compile-symbols profile/3.3.5a-windows-386
|
||||
artifacts:
|
||||
./bin/bna mk 3.3.5a-windows-386
|
||||
|
||||
profile-gen:
|
||||
./bin/binana generate --profile profile/3.3.5a-windows-386
|
||||
|
||||
generate: compile-symbols profile-gen
|
||||
generate: artifacts
|
||||
|
||||
dependencies:
|
||||
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
|
||||
|
|
|
|||
8
go.mod
8
go.mod
|
|
@ -1,20 +1,18 @@
|
|||
module github.com/thunderbrewhq/binana
|
||||
|
||||
go 1.22.0
|
||||
go 1.25.5
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/pierrec/lz4/v4 v4.1.21
|
||||
github.com/spf13/cobra v1.8.1
|
||||
modernc.org/cc/v3 v3.41.0
|
||||
)
|
||||
|
||||
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/mattn/go-colorable v0.1.13 // 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/spf13/pflag v1.0.5 // 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/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/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
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/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
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/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
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.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
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/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
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
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"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 {
|
||||
|
|
@ -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) {
|
||||
batch_path := filepath.Join(profile.ArtifactsDirectory, "ida", "batch")
|
||||
if err = os.MkdirAll(batch_path, 0755); err != nil {
|
||||
return
|
||||
}
|
||||
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 {
|
||||
return
|
||||
}
|
||||
|
|
@ -73,8 +92,7 @@ func (profile *Profile) create_idac_import_batch(name string) (b *idac_import_ba
|
|||
return
|
||||
}
|
||||
|
||||
func (profile *Profile) generate_symbols_idc() (err error) {
|
||||
// symbols
|
||||
func compile_idapro_artifacts(profile *Profile, params *CompileArtifactsParams) (err error) {
|
||||
var b *idac_import_batch
|
||||
b, err = profile.create_idac_import_batch("import_symbols")
|
||||
if err != nil {
|
||||
|
|
@ -88,8 +106,8 @@ func (profile *Profile) generate_symbols_idc() (err error) {
|
|||
|
||||
name_instances := make(map[string]int)
|
||||
|
||||
for _, symbol := range profile.SymbolTable.Entries {
|
||||
name := symbol.Name
|
||||
for symbol := range profile.Symbols.Entries() {
|
||||
name := symbol.Symbol.Name
|
||||
instances := name_instances[name]
|
||||
name_instances[name] = instances + 1
|
||||
|
||||
|
|
@ -98,7 +116,7 @@ func (profile *Profile) generate_symbols_idc() (err error) {
|
|||
}
|
||||
|
||||
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.T(0)
|
||||
|
|
@ -117,10 +135,10 @@ func (profile *Profile) generate_symbols_idc() (err error) {
|
|||
b.P("static import_data_types() {")
|
||||
b.T(1)
|
||||
b.P("// Give types to data labels")
|
||||
for _, symbol := range profile.SymbolTable.Entries {
|
||||
if symbol.DataType != "" {
|
||||
quoted_data_type := strconv.Quote(symbol.DataType)
|
||||
address := fmt.Sprintf("0x%08X", symbol.StartAddress)
|
||||
for symbol := range profile.Symbols.Entries() {
|
||||
if symbol.Symbol.DataType != "" {
|
||||
quoted_data_type := strconv.Quote(symbol.Symbol.DataType)
|
||||
address := fmt.Sprintf("0x%08X", symbol.Symbol.StartAddress)
|
||||
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.P("// Import function addresses and comments")
|
||||
b.P(`msg("Importing function addresses and comments");`)
|
||||
for _, function_symbol := range profile.SymbolTable.Entries {
|
||||
if function_symbol.Kind == symfile.Function {
|
||||
address := fmt.Sprintf("0x%08X", function_symbol.StartAddress)
|
||||
for function_symbol := range profile.Symbols.Entries() {
|
||||
if function_symbol.Symbol.Kind == symbols.Function {
|
||||
address := fmt.Sprintf("0x%08X", function_symbol.Symbol.StartAddress)
|
||||
// b.P("set_func_start(%s, %s);", address, address)
|
||||
// if function_symbol.EndAddress != 0 {
|
||||
// end_address := fmt.Sprintf("0x%08X", function_symbol.EndAddress)
|
||||
// b.P("set_func_end(%s, %s);", address, end_address)
|
||||
// }
|
||||
if function_symbol.Comment != "" {
|
||||
b.P("set_func_cmt(%s, %s, 0);", address, strconv.Quote(function_symbol.Comment))
|
||||
if function_symbol.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()
|
||||
|
||||
// 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
|
||||
}
|
||||
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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/thunderbrewhq/binana/go/symfile"
|
||||
"github.com/thunderbrewhq/binana/go/symbols"
|
||||
"github.com/thunderbrewhq/binana/go/x64dbg"
|
||||
)
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ func hex_address(u uint64) string {
|
|||
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
|
||||
|
||||
is_64bit := profile.Info.Arch == "amd64"
|
||||
|
|
@ -22,30 +23,30 @@ func (profile *Profile) generate_x64dbg_database(compress bool) (err error) {
|
|||
|
||||
var dd x64dbg.Database
|
||||
|
||||
for _, entry := range profile.SymbolTable.Entries {
|
||||
relative_start_address := entry.StartAddress - base_address
|
||||
for entry := range profile.Symbols.Entries() {
|
||||
relative_start_address := entry.Symbol.StartAddress - base_address
|
||||
relative_end_address := relative_start_address
|
||||
|
||||
if entry.EndAddress != 0 {
|
||||
relative_end_address = entry.EndAddress - base_address
|
||||
if entry.Symbol.EndAddress != 0 {
|
||||
relative_end_address = entry.Symbol.EndAddress - base_address
|
||||
// for x64dbg, the end address is the last instruction.
|
||||
// for us, the end address is the address immediately after the last instruction.
|
||||
relative_end_address -= 1
|
||||
}
|
||||
|
||||
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
|
||||
var label x64dbg.Label
|
||||
label.Manual = true
|
||||
label.Address = hex_address(relative_start_address)
|
||||
label.Text = entry.Name
|
||||
label.Text = entry.Symbol.Name
|
||||
label.Module = module_name
|
||||
dd.Labels = append(dd.Labels, label)
|
||||
|
||||
if entry.Kind == symfile.Function {
|
||||
if entry.Symbol.Kind == symbols.Function {
|
||||
var fn x64dbg.Function
|
||||
fn.Manual = true
|
||||
fn.Start = hex_address(relative_start_address)
|
||||
|
|
@ -66,8 +67,11 @@ func (profile *Profile) generate_x64dbg_database(compress bool) (err error) {
|
|||
}
|
||||
|
||||
// save database
|
||||
dd_path := filepath.Join(profile.Directory, "x64dbg", filename)
|
||||
if err = x64dbg.SaveDatabase(dd_path, &dd, compress); err != nil {
|
||||
dd_path := filepath.Join(profile.ArtifactsDirectory, "x64dbg")
|
||||
if err = os.MkdirAll(dd_path, 0755); err != nil {
|
||||
return
|
||||
}
|
||||
if err = x64dbg.SaveDatabase(filepath.Join(dd_path, filename), &dd, compress); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -2,6 +2,7 @@ package profile
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
|
|
@ -98,7 +99,7 @@ loop:
|
|||
}
|
||||
|
||||
// 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
|
||||
var cc_config cc.Config
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
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"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/thunderbrewhq/binana/go/symfile"
|
||||
"github.com/thunderbrewhq/binana/go/symbols"
|
||||
)
|
||||
|
||||
type Profile struct {
|
||||
loaded bool
|
||||
Info Info
|
||||
Directory string
|
||||
SymbolTable *symfile.InMemoryTable
|
||||
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
|
||||
dir, err = os.Stat(profile_directory)
|
||||
if err != nil {
|
||||
|
|
@ -27,31 +29,23 @@ func Open(profile_directory string) (profile *Profile, err error) {
|
|||
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 {
|
||||
return
|
||||
}
|
||||
|
||||
profile.Directory = profile_directory
|
||||
|
||||
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 {
|
||||
// read symbols directory
|
||||
if err = profile.Symbols.Load(filepath.Join(profile_directory, "symbol")); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
profile.SymbolTable = new(symfile.InMemoryTable)
|
||||
profile.loaded = true
|
||||
|
||||
if err = symfile.Load(profile.SymbolTable, symbols_file); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
symbols_file.Close()
|
||||
|
||||
//
|
||||
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 (
|
||||
"fmt"
|
||||
|
|
@ -69,7 +69,7 @@ func parse_attributes(attribute_columns []string) (attributes map[string]string,
|
|||
return
|
||||
}
|
||||
|
||||
func (entry *Entry) Parse(line string) (err error) {
|
||||
func (symbol *Symbol) Parse(line string) (err error) {
|
||||
// trim extraneous whitespace
|
||||
line = strings.Trim(line, " \t")
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ func (entry *Entry) Parse(line string) (err error) {
|
|||
// get name of symbol
|
||||
name_column := columns[0]
|
||||
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)
|
||||
|
|
@ -99,10 +99,10 @@ func (entry *Entry) Parse(line string) (err error) {
|
|||
|
||||
kind_column := columns[2]
|
||||
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) {
|
||||
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
|
||||
entry.Name = name_column
|
||||
entry.StartAddress = start_address
|
||||
entry.Kind = kind
|
||||
entry.Comment = comment_text
|
||||
symbol.Name = name_column
|
||||
symbol.StartAddress = start_address
|
||||
symbol.Kind = kind
|
||||
symbol.Comment = comment_text
|
||||
|
||||
// build attributes
|
||||
if num_semantic_columns > 3 {
|
||||
|
|
@ -138,11 +138,11 @@ func (entry *Entry) Parse(line string) (err error) {
|
|||
}
|
||||
|
||||
if data_type, found := attributes["type"]; found {
|
||||
entry.DataType = data_type
|
||||
symbol.DataType = data_type
|
||||
}
|
||||
|
||||
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 {
|
||||
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