feat(binana): add check command for profiles

This commit is contained in:
phaneron 2025-06-04 17:40:52 -04:00
parent b5ddafb89a
commit ebf88595d9
11 changed files with 151 additions and 39 deletions

View file

@ -0,0 +1,97 @@
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(&params)
}
var check_cmd = cobra.Command{
Use: "check profile",
Short: "check a profile for correctness",
Run: check_run,
}

View file

@ -81,12 +81,12 @@ func format_symbols(in io.Reader, out io.Writer) {
}
}
func format_symbols_func(cmd *cobra.Command, args []string) {
func format_symbols_run(cmd *cobra.Command, args []string) {
format_symbols(os.Stdin, os.Stdout)
}
var format_symbols_cmd = &cobra.Command{
var format_symbols_cmd = cobra.Command{
Use: "format-symbols",
Short: "format symbols from stdin",
Run: format_symbols_func,
Run: format_symbols_run,
}

View file

@ -8,13 +8,7 @@ import (
"github.com/thunderbrewhq/binana/go/profile"
)
var generate = &cobra.Command{
Use: "generate",
Short: "Convert source files into various tool formats",
Run: generate_func,
}
func generate_func(cmd *cobra.Command, args []string) {
func generate_run(cmd *cobra.Command, args []string) {
compress, err := cmd.Flags().GetBool("compress")
if err != nil {
panic(err)
@ -35,3 +29,9 @@ func generate_func(cmd *cobra.Command, args []string) {
os.Exit(1)
}
}
var generate_cmd = cobra.Command{
Use: "generate",
Short: "Convert source files into various tool formats",
Run: generate_run,
}

View file

@ -10,7 +10,7 @@ import (
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
var root = cobra.Command{
Use: "binana",
Short: "Binana helper tool",
}
@ -18,27 +18,21 @@ var rootCmd = &cobra.Command{
// 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 := rootCmd.Execute()
err := root.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
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")
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.binana.yaml)")
check_cmd.Flags().Bool("bounds", false, "check for bad function boundaries")
check_cmd.Flags().Bool("constructors", false, "check for outdated class constructor names")
// Cobra also supports local flags, which will only run
// when this action is called directly.
// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
generate.Flags().StringP("profile", "p", "3.3.5a", "the game profile")
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)
root.AddCommand(&generate_cmd)
root.AddCommand(&format_symbols_cmd)
root.AddCommand(&x64dbg_typesort_cmd)
root.AddCommand(&check_cmd)
}

View file

@ -8,13 +8,13 @@ import (
"github.com/thunderbrewhq/binana/go/x64dbg"
)
var x64dbg_typesort = &cobra.Command{
var x64dbg_typesort_cmd = cobra.Command{
Use: "x64dbg-typesort [types.json file]",
Short: "sort a x64dbg types file",
Run: x64dbg_typesort_func,
Run: x64dbg_typesort_run,
}
func x64dbg_typesort_func(cmd *cobra.Command, args []string) {
func x64dbg_typesort_run(cmd *cobra.Command, args []string) {
types, err := x64dbg.LoadTypes(args[0])
if err != nil {
panic(err)

View file

@ -23,17 +23,19 @@ var (
)
type info_schema struct {
OS string `json:"os"`
Arch string `json:"arch"`
ModuleName string `json:"module_name"`
ModuleBase string `json:"module_base"`
OS string `json:"os"`
Arch string `json:"arch"`
ModuleName string `json:"module_name"`
ModuleBase string `json:"module_base"`
FunctionCount uint64 `json:"function_count"`
}
type Info struct {
OS string
Arch string
ModuleName string
ModuleBase uint64
OS string
Arch string
ModuleName string
ModuleBase uint64
FunctionCount uint64
}
func read_info(filename string, info *Info) (err error) {
@ -68,5 +70,7 @@ func read_info(filename string, info *Info) (err error) {
return
}
info.FunctionCount = is.FunctionCount
return
}

View file

@ -33,7 +33,7 @@ func (profile *Profile) generate_x64dbg_database(compress bool) (err error) {
relative_end_address -= 1
}
if relative_end_address < relative_start_address || relative_end_address-relative_start_address >= 10000 {
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)
}

View file

@ -13,7 +13,7 @@ const min_columns = 3
type loader struct {
input *bufio.Reader
table Table
line_number int
line_number uint64
}
func (l *loader) read_line() (line string, err error) {
@ -32,6 +32,7 @@ func (l *loader) parse_line(line string) (err error) {
err = fmt.Errorf("%w: line %d", err, l.line_number)
return
}
entry.LineNumber = l.line_number
err = l.table.Insert(&entry)
return

View file

@ -22,6 +22,7 @@ var (
// An entry in the table
type Entry struct {
LineNumber uint64
// Undecorated, raw name
Name string
// Offset to the start of the function or data