mirror of
https://github.com/thunderbrewhq/binana.git
synced 2025-12-12 09:52:28 +00:00
feat(go): add format-symbols command
This commit is contained in:
parent
b8b0b5076f
commit
0eec0219f9
7 changed files with 269 additions and 142 deletions
1
go.mod
1
go.mod
|
|
@ -9,6 +9,7 @@ require (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // 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/pierrec/lz4/v4 v4.1.21 // 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
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -5,6 +5,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
||||||
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/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/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
|
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
|
||||||
|
|
|
||||||
92
go/cmd/binana/cmd/format_symbols.go
Normal file
92
go/cmd/binana/cmd/format_symbols.go
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
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_func(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_func,
|
||||||
|
}
|
||||||
|
|
@ -39,6 +39,6 @@ func init() {
|
||||||
generate.Flags().BoolP("compress", "c", true, "enable/disable compression of the x64dbg database file")
|
generate.Flags().BoolP("compress", "c", true, "enable/disable compression of the x64dbg database file")
|
||||||
|
|
||||||
rootCmd.AddCommand(generate)
|
rootCmd.AddCommand(generate)
|
||||||
|
rootCmd.AddCommand(format_symbols_cmd)
|
||||||
rootCmd.AddCommand(x64dbg_typesort)
|
rootCmd.AddCommand(x64dbg_typesort)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -28,148 +26,12 @@ func (l *loader) read_line() (line string, err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func parse_attributes(attribute_columns []string) (attributes map[string]string, err error) {
|
|
||||||
attributes = make(map[string]string)
|
|
||||||
var (
|
|
||||||
scanning_quoted_string bool
|
|
||||||
current_key string
|
|
||||||
current_value string
|
|
||||||
)
|
|
||||||
for _, attribute_column := range attribute_columns {
|
|
||||||
if scanning_quoted_string {
|
|
||||||
current_value += " "
|
|
||||||
current_value += attribute_column
|
|
||||||
|
|
||||||
if strings.HasSuffix(attribute_column, `"`) {
|
|
||||||
scanning_quoted_string = false
|
|
||||||
attributes[current_key], err = strconv.Unquote(current_value)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
current_key = ""
|
|
||||||
current_value = ""
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
key, value_start, found := strings.Cut(attribute_column, "=")
|
|
||||||
if !found {
|
|
||||||
err = fmt.Errorf("extraneous column: '%s'", attribute_column)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
current_key = key
|
|
||||||
|
|
||||||
if strings.HasPrefix(value_start, `"`) {
|
|
||||||
current_value = value_start
|
|
||||||
if strings.HasSuffix(value_start, `"`) {
|
|
||||||
attributes[current_key], err = strconv.Unquote(value_start)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
scanning_quoted_string = true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// unquoted, we can succeed immediately
|
|
||||||
attributes[current_key] = value_start
|
|
||||||
current_value = ""
|
|
||||||
current_key = ""
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if scanning_quoted_string {
|
|
||||||
err = fmt.Errorf("line ends in the middle of a quoted attribute --> \"%s", current_value)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *loader) parse_line(line string) (err error) {
|
func (l *loader) parse_line(line string) (err error) {
|
||||||
// trim extraneous whitespace
|
|
||||||
line = strings.Trim(line, " \t")
|
|
||||||
|
|
||||||
// split into columns
|
|
||||||
columns := strings.Split(line, " ")
|
|
||||||
|
|
||||||
// validate
|
|
||||||
if len(columns) < min_columns {
|
|
||||||
// this line is discarded but not in error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
start_address uint64
|
|
||||||
comment_text string
|
|
||||||
)
|
|
||||||
|
|
||||||
// get name of symbol
|
|
||||||
name_column := columns[0]
|
|
||||||
if name_column == "" {
|
|
||||||
return fmt.Errorf("symfile: (*loader).parse_line: line %d: entry has invalid name '%s", l.line_number, name_column)
|
|
||||||
}
|
|
||||||
|
|
||||||
start_address, err = strconv.ParseUint(columns[1], 16, 64)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
kind_column := columns[2]
|
|
||||||
if len(kind_column) != 1 {
|
|
||||||
return fmt.Errorf("symfile: (*loader).parse_line: line %d: entry has invalid kind", l.line_number)
|
|
||||||
}
|
|
||||||
|
|
||||||
kind := EntryKind(kind_column[0])
|
|
||||||
|
|
||||||
if !slices.Contains(valid_kinds, kind) {
|
|
||||||
return fmt.Errorf("symfile: (*loader).parse_line: line %d: entry has invalid kind", l.line_number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// find index of comment column
|
|
||||||
index_of_comment := slices.Index(columns, ";")
|
|
||||||
|
|
||||||
var num_semantic_columns int
|
|
||||||
|
|
||||||
if index_of_comment != -1 {
|
|
||||||
num_semantic_columns = index_of_comment
|
|
||||||
comment_text_columns := columns[index_of_comment+1:]
|
|
||||||
comment_text = strings.Join(comment_text_columns, " ")
|
|
||||||
} else {
|
|
||||||
num_semantic_columns = len(columns)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start to build entry
|
|
||||||
var entry Entry
|
var entry Entry
|
||||||
entry.Name = name_column
|
if err = entry.Parse(line); err != nil {
|
||||||
entry.StartAddress = start_address
|
err = fmt.Errorf("%w: line %d", err, l.line_number)
|
||||||
entry.Kind = kind
|
|
||||||
entry.Comment = comment_text
|
|
||||||
|
|
||||||
// build attributes
|
|
||||||
if num_semantic_columns > 3 {
|
|
||||||
extra_columns := columns[3:num_semantic_columns]
|
|
||||||
|
|
||||||
var attributes map[string]string
|
|
||||||
attributes, err = parse_attributes(extra_columns)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("symfile: (*loader).parse_line: line %d: error parsing attribute: '%s'", l.line_number, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if data_type, found := attributes["type"]; found {
|
|
||||||
entry.DataType = data_type
|
|
||||||
}
|
|
||||||
|
|
||||||
if end_address, found := attributes["end"]; found {
|
|
||||||
entry.EndAddress, err = strconv.ParseUint(end_address, 16, 64)
|
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = l.table.Insert(&entry)
|
err = l.table.Insert(&entry)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
153
go/symfile/parse_entry.go
Normal file
153
go/symfile/parse_entry.go
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
package symfile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parse_attributes(attribute_columns []string) (attributes map[string]string, err error) {
|
||||||
|
attributes = make(map[string]string)
|
||||||
|
var (
|
||||||
|
scanning_quoted_string bool
|
||||||
|
current_key string
|
||||||
|
current_value string
|
||||||
|
)
|
||||||
|
for _, attribute_column := range attribute_columns {
|
||||||
|
if scanning_quoted_string {
|
||||||
|
current_value += " "
|
||||||
|
current_value += attribute_column
|
||||||
|
|
||||||
|
if strings.HasSuffix(attribute_column, `"`) {
|
||||||
|
scanning_quoted_string = false
|
||||||
|
attributes[current_key], err = strconv.Unquote(current_value)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
current_key = ""
|
||||||
|
current_value = ""
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
key, value_start, found := strings.Cut(attribute_column, "=")
|
||||||
|
if !found {
|
||||||
|
err = fmt.Errorf("extraneous column: '%s'", attribute_column)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
current_key = key
|
||||||
|
|
||||||
|
if strings.HasPrefix(value_start, `"`) {
|
||||||
|
current_value = value_start
|
||||||
|
if strings.HasSuffix(value_start, `"`) {
|
||||||
|
attributes[current_key], err = strconv.Unquote(value_start)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
scanning_quoted_string = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// unquoted, we can succeed immediately
|
||||||
|
attributes[current_key] = value_start
|
||||||
|
current_value = ""
|
||||||
|
current_key = ""
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if scanning_quoted_string {
|
||||||
|
err = fmt.Errorf("line ends in the middle of a quoted attribute --> \"%s", current_value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Parse(line string) (err error) {
|
||||||
|
// trim extraneous whitespace
|
||||||
|
line = strings.Trim(line, " \t")
|
||||||
|
|
||||||
|
// split into columns
|
||||||
|
columns := strings.Split(line, " ")
|
||||||
|
|
||||||
|
// validate
|
||||||
|
if len(columns) < min_columns {
|
||||||
|
// this line is discarded but not in error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
start_address uint64
|
||||||
|
comment_text string
|
||||||
|
)
|
||||||
|
|
||||||
|
// get name of symbol
|
||||||
|
name_column := columns[0]
|
||||||
|
if name_column == "" {
|
||||||
|
return fmt.Errorf("symfile: (*entry).Parse: entry has invalid name '%s", name_column)
|
||||||
|
}
|
||||||
|
|
||||||
|
start_address, err = strconv.ParseUint(columns[1], 16, 64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
kind_column := columns[2]
|
||||||
|
if len(kind_column) != 1 {
|
||||||
|
return fmt.Errorf("symfile: (*entry).Parse: entry has invalid kind")
|
||||||
|
}
|
||||||
|
|
||||||
|
kind := EntryKind(kind_column[0])
|
||||||
|
|
||||||
|
if !slices.Contains(valid_kinds, kind) {
|
||||||
|
return fmt.Errorf("symfile: (*entry).Parse: entry has invalid kind")
|
||||||
|
}
|
||||||
|
|
||||||
|
// find index of comment column
|
||||||
|
index_of_comment := slices.Index(columns, ";")
|
||||||
|
|
||||||
|
var num_semantic_columns int
|
||||||
|
|
||||||
|
if index_of_comment != -1 {
|
||||||
|
num_semantic_columns = index_of_comment
|
||||||
|
comment_text_columns := columns[index_of_comment+1:]
|
||||||
|
comment_text = strings.Join(comment_text_columns, " ")
|
||||||
|
} else {
|
||||||
|
num_semantic_columns = len(columns)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start to build entry
|
||||||
|
entry.Name = name_column
|
||||||
|
entry.StartAddress = start_address
|
||||||
|
entry.Kind = kind
|
||||||
|
entry.Comment = comment_text
|
||||||
|
|
||||||
|
// build attributes
|
||||||
|
if num_semantic_columns > 3 {
|
||||||
|
extra_columns := columns[3:num_semantic_columns]
|
||||||
|
|
||||||
|
var attributes map[string]string
|
||||||
|
attributes, err = parse_attributes(extra_columns)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("symfile: (*entry).Parse: error parsing attribute: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if data_type, found := attributes["type"]; found {
|
||||||
|
entry.DataType = data_type
|
||||||
|
}
|
||||||
|
|
||||||
|
if end_address, found := attributes["end"]; found {
|
||||||
|
entry.EndAddress, err = strconv.ParseUint(end_address, 16, 64)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package symfile
|
package symfile
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// What kind of Entry is this?
|
// What kind of Entry is this?
|
||||||
|
|
@ -36,6 +38,21 @@ type Entry struct {
|
||||||
DataType string
|
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 {
|
type Table interface {
|
||||||
Insert(entry *Entry) (err error)
|
Insert(entry *Entry) (err error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue