From 0eec0219f9b43046e08b3ae2db03005d22f5a45a Mon Sep 17 00:00:00 2001 From: superp00t Date: Thu, 17 Apr 2025 16:26:20 -0400 Subject: [PATCH] feat(go): add format-symbols command --- go.mod | 1 + go.sum | 2 + go/cmd/binana/cmd/format_symbols.go | 92 +++++++++++++++++ go/cmd/binana/cmd/root.go | 2 +- go/symfile/loader.go | 144 +------------------------- go/symfile/parse_entry.go | 153 ++++++++++++++++++++++++++++ go/symfile/symfile.go | 17 ++++ 7 files changed, 269 insertions(+), 142 deletions(-) create mode 100644 go/cmd/binana/cmd/format_symbols.go create mode 100644 go/symfile/parse_entry.go diff --git a/go.mod b/go.mod index 1bc8aad..d579be7 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( require ( 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/pierrec/lz4/v4 v4.1.21 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect diff --git a/go.sum b/go.sum index 0a8c95d..8beb669 100644 --- a/go.sum +++ b/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/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/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= diff --git a/go/cmd/binana/cmd/format_symbols.go b/go/cmd/binana/cmd/format_symbols.go new file mode 100644 index 0000000..8f35893 --- /dev/null +++ b/go/cmd/binana/cmd/format_symbols.go @@ -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, +} diff --git a/go/cmd/binana/cmd/root.go b/go/cmd/binana/cmd/root.go index bc1ed4d..123218b 100644 --- a/go/cmd/binana/cmd/root.go +++ b/go/cmd/binana/cmd/root.go @@ -39,6 +39,6 @@ func init() { generate.Flags().BoolP("compress", "c", true, "enable/disable compression of the x64dbg database file") rootCmd.AddCommand(generate) - + rootCmd.AddCommand(format_symbols_cmd) rootCmd.AddCommand(x64dbg_typesort) } diff --git a/go/symfile/loader.go b/go/symfile/loader.go index a388a9d..3486902 100644 --- a/go/symfile/loader.go +++ b/go/symfile/loader.go @@ -5,8 +5,6 @@ import ( "errors" "fmt" "io" - "slices" - "strconv" "strings" ) @@ -28,147 +26,11 @@ func (l *loader) read_line() (line string, err error) { 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) { - // 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 - 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: (*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 - } - } + if err = entry.Parse(line); err != nil { + err = fmt.Errorf("%w: line %d", err, l.line_number) + return } err = l.table.Insert(&entry) diff --git a/go/symfile/parse_entry.go b/go/symfile/parse_entry.go new file mode 100644 index 0000000..d59180c --- /dev/null +++ b/go/symfile/parse_entry.go @@ -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 +} diff --git a/go/symfile/symfile.go b/go/symfile/symfile.go index cf4e692..6b2fcfb 100644 --- a/go/symfile/symfile.go +++ b/go/symfile/symfile.go @@ -1,7 +1,9 @@ package symfile import ( + "fmt" "io" + "strings" ) // What kind of Entry is this? @@ -36,6 +38,21 @@ type Entry struct { 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) }