feat(go): add format-symbols command

This commit is contained in:
phaneron 2025-04-17 16:26:20 -04:00
parent b8b0b5076f
commit 0eec0219f9
7 changed files with 269 additions and 142 deletions

View 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,
}

View file

@ -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)
}

View file

@ -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)

153
go/symfile/parse_entry.go Normal file
View 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
}

View file

@ -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)
}