diff --git a/go/app/cmd/add_symbol/add_symbol.go b/go/app/cmd/add_symbol/add_symbol.go new file mode 100644 index 0000000..7ec0cfa --- /dev/null +++ b/go/app/cmd/add_symbol/add_symbol.go @@ -0,0 +1,71 @@ +package add_symbol + +import ( + "bufio" + "os" + + "github.com/spf13/cobra" + "github.com/thunderbrewhq/binana/go/app" + "github.com/thunderbrewhq/binana/go/app/cmd/root" + "github.com/thunderbrewhq/binana/go/app/profile" + "github.com/thunderbrewhq/binana/go/symbols" +) + +func as_func(cmd *cobra.Command, args []string) { + f := cmd.Flags() + var ( + err error + params profile.AddSymbolParams + ) + params.Profile = args[0] + params.Group, err = f.GetString("group") + if err != nil { + return + } + // TODO: infer the group + if params.Group == "" { + app.Fatal("pass in the group (-g, --group) that will store the symbol(s)") + return + } + if len(args) < 2 { + // read normally formatted symbols from stdin + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + line := scanner.Text() + var symbol symbols.Symbol + if err = symbol.Parse(line); err != nil { + app.Fatal(err) + return + } + params.Symbols = append(params.Symbols, symbol) + } + } else { + // use args to add symbol + if len(args) < 4 { + cmd.Help() + return + } + params.Symbols = make([]symbols.Symbol, 1) + if err = params.Symbols[0].ParseCommandLine(args[1:]); err != nil { + return + } + params.Symbols[0].Comment, err = f.GetString("comment") + } + + profile.AddSymbol(¶ms) +} + +var as_cmd = cobra.Command{ + Use: "as profile [symbol_name] [address] [l|f] [type=...]", + Args: cobra.MinimumNArgs(1), + Short: "Add symbols to profile", + Long: "Adds symbol passed through command-line arguments, or add one or more normally-formatted symbols by passing them through stdin", + Run: as_func, +} + +func init() { + f := as_cmd.Flags() + f.StringP("group", "g", "", "the profile symbol group to record symbols into (e.g. 'feature1' to record into profile//symbol/feature1)") + f.StringP("comment", "m", "", "the comment to pass to the symbol") + root.RootCmd.AddCommand(&as_cmd) +} diff --git a/go/app/cmd/execute.go b/go/app/cmd/execute.go index 0e62ad9..04eb251 100644 --- a/go/app/cmd/execute.go +++ b/go/app/cmd/execute.go @@ -1,12 +1,13 @@ package cmd import ( + _ "github.com/thunderbrewhq/binana/go/app/cmd/add_symbol" _ "github.com/thunderbrewhq/binana/go/app/cmd/lint" _ "github.com/thunderbrewhq/binana/go/app/cmd/make" + "github.com/thunderbrewhq/binana/go/app/cmd/root" _ "github.com/thunderbrewhq/binana/go/app/cmd/tidy" "github.com/thunderbrewhq/binana/go/app" - "github.com/thunderbrewhq/binana/go/app/cmd/root" ) func Execute() { diff --git a/go/app/cmd/lint/lint.go b/go/app/cmd/lint/lint.go index 3e0a182..2b91334 100644 --- a/go/app/cmd/lint/lint.go +++ b/go/app/cmd/lint/lint.go @@ -3,6 +3,7 @@ package cmd import ( "github.com/spf13/cobra" "github.com/thunderbrewhq/binana/go/app" + "github.com/thunderbrewhq/binana/go/app/cmd/root" "github.com/thunderbrewhq/binana/go/app/profile" ) @@ -16,6 +17,7 @@ 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") + root.RootCmd.AddCommand(&lint_cmd) } func lint_func(cmd *cobra.Command, args []string) { diff --git a/go/app/profile/add-symbol.go b/go/app/profile/add-symbol.go new file mode 100644 index 0000000..951efcd --- /dev/null +++ b/go/app/profile/add-symbol.go @@ -0,0 +1,132 @@ +package profile + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/thunderbrewhq/binana/go/app" + "github.com/thunderbrewhq/binana/go/symbols" +) + +type AddSymbolParams struct { + Profile string + Group string + Symbols []symbols.Symbol +} + +func AddSymbol(params *AddSymbolParams) { + Open(params.Profile) + + // Detect collisions + // TODO: check if address exists within a function's range + for i, symbol := range params.Symbols { + entry, err := Profile.Symbols.Lookup(symbol.StartAddress) + if err == nil { + app.Fatal(fmt.Errorf( + "profile: cannot add symbol '%s' (--:%d), another symbol '%s' (%s:%d) already exists at the same address (%08X)", + symbol.Name, + i+1, + entry.Symbol.Name, + entry.Filename, + entry.Linenumber, + symbol.StartAddress, + )) + } + } + + // Load all symbols, and rewrite them + group_symbol_directory := filepath.Join(Profile.Directory, "symbol", params.Group) + var group_symbols symbols.Table + group_symbols.Init() + + group_symbols_file_info, err := os.Stat(group_symbol_directory) + if err != nil { + err = os.MkdirAll(group_symbol_directory, 0755) + if err != nil { + app.Fatal(err) + } + } else { + if !group_symbols_file_info.IsDir() { + app.Fatal(fmt.Errorf("profile: symbol group '%s' has invalid directory structure", params.Group)) + } + if err = group_symbols.Load(group_symbol_directory); err != nil { + app.Fatal(err) + } + } + + for i, symbol := range params.Symbols { + var entry symbols.TableEntry + entry.Filename = "--" + entry.Linenumber = i + 1 + entry.Symbol = symbol + if err := group_symbols.Insert(&entry); err != nil { + panic(err) + } + } + + label_file_name := filepath.Join(group_symbol_directory, "label.sym") + func_file_name := filepath.Join(group_symbol_directory, "func.sym") + label_file_tmp_name := label_file_name + ".tmp" + func_file_tmp_name := func_file_name + ".tmp" + label_file, err := os.Create(label_file_tmp_name) + if err != nil { + app.Fatal(err) + } + func_file, err := os.Create(func_file_tmp_name) + if err != nil { + app.Fatal(err) + } + var ( + labels, functions int + ) + +save_loop: + for entry := range group_symbols.Entries() { + switch entry.Symbol.Kind { + case symbols.DataLabel: + labels++ + if _, err = entry.Symbol.WriteTo(label_file); err != nil { + break save_loop + } + if _, err = label_file.Write([]byte("\n")); err != nil { + break save_loop + } + case symbols.Function: + functions++ + if _, err = entry.Symbol.WriteTo(func_file); err != nil { + break save_loop + } + if _, err = func_file.Write([]byte("\n")); err != nil { + break save_loop + } + default: + err = fmt.Errorf("unhandled function kind %c", entry.Symbol.Kind) + } + } + if err != nil { + label_file.Close() + func_file.Close() + os.Remove(label_file_name) + os.Remove(func_file_name) + return + } + label_file.Close() + func_file.Close() + if labels == 0 { + os.Remove(label_file_name) + os.Remove(label_file_tmp_name) + } else { + if err = os.Rename(label_file_tmp_name, label_file_name); err != nil { + return + } + } + if functions == 0 { + os.Remove(func_file_name) + os.Remove(func_file_tmp_name) + } else { + if err = os.Rename(func_file_tmp_name, func_file_name); err != nil { + return + } + } +} diff --git a/go/app/util/tidy-symbols.go b/go/app/util/tidy-symbols.go new file mode 100644 index 0000000..ae57cfb --- /dev/null +++ b/go/app/util/tidy-symbols.go @@ -0,0 +1,4 @@ +package util + +type TidySymbolFileParams struct { +} diff --git a/go/symbols/error.go b/go/symbols/error.go index db1ab86..4363536 100644 --- a/go/symbols/error.go +++ b/go/symbols/error.go @@ -3,6 +3,7 @@ package symbols import "fmt" var ( + ErrSymbolNotFound = fmt.Errorf("symbols: symbol not found") ErrDuplicateSymbol = fmt.Errorf("symbols: a symbol already exists at that address") ErrLineLeftEmpty = fmt.Errorf("symbols: line was left empty") ) diff --git a/go/symbols/parse_symbol.go b/go/symbols/parse_symbol.go index da73f7a..4b57577 100644 --- a/go/symbols/parse_symbol.go +++ b/go/symbols/parse_symbol.go @@ -32,7 +32,6 @@ func parse_attributes(attribute_columns []string) (attributes map[string]string, continue } } - key, value_start, found := strings.Cut(attribute_column, "=") if !found { err = fmt.Errorf("extraneous column: '%s'", attribute_column) @@ -134,7 +133,84 @@ func (symbol *Symbol) Parse(line string) (err error) { 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) + return fmt.Errorf("symbols: (*Symbol).Parse: error parsing attribute: %w", err) + } + + if data_type, found := attributes["type"]; found { + symbol.DataType = data_type + } + + if end_address, found := attributes["end"]; found { + symbol.EndAddress, err = strconv.ParseUint(end_address, 16, 64) + if err != nil { + return + } + } + } + + return +} + +func (symbol *Symbol) ParseCommandLine(columns []string) (err error) { + // 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("symbols: (*Symbol).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("symbols: (*Symbol).Parse: entry has invalid kind") + } + + kind := SymbolKind(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 + symbol.Name = name_column + symbol.StartAddress = start_address + symbol.Kind = kind + symbol.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("symbols: (*Symbol).Parse: error parsing attribute: %w", err) } if data_type, found := attributes["type"]; found { diff --git a/go/symbols/table.go b/go/symbols/table.go index e10936a..24e2f37 100644 --- a/go/symbols/table.go +++ b/go/symbols/table.go @@ -100,6 +100,16 @@ func (table *Table) LoadFile(name string, file io.Reader) (err error) { return } +func (table *Table) Lookup(address uint64) (entry *TableEntry, err error) { + var ok bool + entry, ok = table.entries[address] + if !ok { + err = fmt.Errorf("%w: %08X", ErrSymbolNotFound, address) + return + } + return +} + func (table *Table) Insert(entry *TableEntry) (err error) { conflicting_symbol, ok := table.entries[entry.Symbol.StartAddress] if ok {