mirror of
https://github.com/thunderbrewhq/binana.git
synced 2026-03-22 22:00:13 +00:00
475 lines
11 KiB
Go
475 lines
11 KiB
Go
|
|
package util
|
||
|
|
|
||
|
|
import (
|
||
|
|
"crypto/sha256"
|
||
|
|
"encoding/hex"
|
||
|
|
"fmt"
|
||
|
|
"maps"
|
||
|
|
"slices"
|
||
|
|
"sort"
|
||
|
|
"strconv"
|
||
|
|
|
||
|
|
"github.com/thunderbrewhq/binana/go/db"
|
||
|
|
"github.com/thunderbrewhq/binana/go/pdbconv"
|
||
|
|
)
|
||
|
|
|
||
|
|
type pdb_token_visitor struct {
|
||
|
|
tokens_database *tokens_database
|
||
|
|
|
||
|
|
// location of the base module
|
||
|
|
base_address uint64
|
||
|
|
pdb_source_id string
|
||
|
|
strings map[string]*db.Token
|
||
|
|
datatypes map[string]*db.Token
|
||
|
|
// maps a symbol to a token
|
||
|
|
symbols map[uint64]*db.Token
|
||
|
|
|
||
|
|
constants []db.Token
|
||
|
|
}
|
||
|
|
|
||
|
|
func (v *pdb_token_visitor) init(tokens_database *tokens_database, pdb_source_id string, base_address uint64) {
|
||
|
|
v.base_address = base_address
|
||
|
|
v.pdb_source_id = pdb_source_id
|
||
|
|
v.strings = make(map[string]*db.Token)
|
||
|
|
v.datatypes = make(map[string]*db.Token)
|
||
|
|
// map of address to token
|
||
|
|
v.symbols = make(map[uint64]*db.Token)
|
||
|
|
v.tokens_database = tokens_database
|
||
|
|
}
|
||
|
|
|
||
|
|
func (v *pdb_token_visitor) visit_class(class *pdbconv.Class) (err error) {
|
||
|
|
token, ok := v.datatypes[class.Name]
|
||
|
|
if !ok {
|
||
|
|
token = new(db.Token)
|
||
|
|
v.datatypes[class.Name] = token
|
||
|
|
}
|
||
|
|
|
||
|
|
// set token source to pdb
|
||
|
|
token.Source = v.pdb_source_id
|
||
|
|
// kind is debug information token
|
||
|
|
token.Kind = db.OriginalDatatypeToken
|
||
|
|
|
||
|
|
// set original name
|
||
|
|
token.Names = append(token.Names, db.TokenName{db.OriginalName, class.Name})
|
||
|
|
|
||
|
|
// set the basic type
|
||
|
|
token.Keyword = "class"
|
||
|
|
|
||
|
|
for _, member := range class.Members {
|
||
|
|
var token_member db.TokenMember
|
||
|
|
if member.Kind == "Member" {
|
||
|
|
token_member.Kind = db.FieldMember
|
||
|
|
} else if member.Kind == "Unknown" && member.Datatype == "void *" {
|
||
|
|
token_member.Kind = db.MethodMember
|
||
|
|
} else {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
token_member.Key = member.Name
|
||
|
|
token_member.Value = member.Datatype
|
||
|
|
|
||
|
|
if !slices.Contains(token.Members, token_member) {
|
||
|
|
token.Members = append(token.Members, token_member)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func (v *pdb_token_visitor) visit_datatype(datatype *pdbconv.Datatype) (err error) {
|
||
|
|
token, ok := v.datatypes[datatype.Name]
|
||
|
|
if !ok {
|
||
|
|
token = new(db.Token)
|
||
|
|
v.datatypes[datatype.Name] = token
|
||
|
|
}
|
||
|
|
|
||
|
|
// set token source to pdb
|
||
|
|
token.Source = v.pdb_source_id
|
||
|
|
// kind is debug information token
|
||
|
|
token.Kind = db.OriginalDatatypeToken
|
||
|
|
|
||
|
|
// set original name
|
||
|
|
token.Names = append(token.Names, db.TokenName{db.OriginalName, datatype.Name})
|
||
|
|
|
||
|
|
// set the basic type
|
||
|
|
if datatype.Kind == "Structure" {
|
||
|
|
token.Keyword = "struct"
|
||
|
|
} else if datatype.Kind == "Union" {
|
||
|
|
token.Keyword = "union"
|
||
|
|
} else {
|
||
|
|
err = fmt.Errorf("unhandled datatype kind '%s'", datatype.Kind)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, member := range datatype.Members {
|
||
|
|
var token_member db.TokenMember
|
||
|
|
if member.Kind == "Member" {
|
||
|
|
token_member.Kind = db.FieldMember
|
||
|
|
} else if member.Kind == "Unknown" && member.Datatype == "void *" {
|
||
|
|
token_member.Kind = db.MethodMember
|
||
|
|
} else {
|
||
|
|
continue
|
||
|
|
}
|
||
|
|
token_member.Key = member.Name
|
||
|
|
token_member.Value = member.Datatype
|
||
|
|
|
||
|
|
if !slices.Contains(token.Members, token_member) {
|
||
|
|
token.Members = append(token.Members, token_member)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func (v *pdb_token_visitor) visit_function(function *pdbconv.Function) (err error) {
|
||
|
|
var address uint64
|
||
|
|
address, err = strconv.ParseUint(function.Address, 0, 64)
|
||
|
|
if err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
address = v.base_address + address
|
||
|
|
|
||
|
|
token, ok := v.symbols[address]
|
||
|
|
if !ok {
|
||
|
|
token = new(db.Token)
|
||
|
|
v.symbols[address] = token
|
||
|
|
}
|
||
|
|
|
||
|
|
// set token source to pdb
|
||
|
|
token.Source = v.pdb_source_id
|
||
|
|
// kind is symbol information token
|
||
|
|
token.Kind = db.OriginalSymbolToken
|
||
|
|
// set address
|
||
|
|
token.Offset = fmt.Sprintf("%X", address)
|
||
|
|
// set original name
|
||
|
|
token.Names = append(token.Names, db.TokenName{db.OriginalName, function.Name})
|
||
|
|
|
||
|
|
if looks_mangled(function.Name) {
|
||
|
|
demangled, demangler_err := demangle(function.Name)
|
||
|
|
if demangler_err == nil {
|
||
|
|
token.Names = append(token.Names, db.TokenName{db.DemangledName, demangled})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// visit source files
|
||
|
|
for _, line_number := range function.LineNumbers {
|
||
|
|
if err = v.visit_string(line_number.SourceFile); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// classify stack variables as members
|
||
|
|
for _, stack_variable := range function.StackVariables {
|
||
|
|
var token_member db.TokenMember
|
||
|
|
if stack_variable.Kind == "Parameter" || stack_variable.Kind == "ObjectPointer" {
|
||
|
|
token_member.Kind = db.ParameterMember
|
||
|
|
} else if stack_variable.Kind == "Local" {
|
||
|
|
token_member.Kind = db.LocalMember
|
||
|
|
} else if stack_variable.Kind == "StaticLocal" {
|
||
|
|
token_member.Kind = db.StaticLocalMember
|
||
|
|
} else if stack_variable.Kind == "Constant" {
|
||
|
|
// these are repeated elsewhere
|
||
|
|
continue
|
||
|
|
} else {
|
||
|
|
err = fmt.Errorf("unhandled stack variable kind '%s'", stack_variable.Kind)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
token_member.Key = stack_variable.Name
|
||
|
|
token_member.Value = stack_variable.Datatype
|
||
|
|
|
||
|
|
token.Members = append(token.Members, token_member)
|
||
|
|
}
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func (v *pdb_token_visitor) visit_enum(enum *pdbconv.Enum) (err error) {
|
||
|
|
// create hash of enum's contents and use to key the datatype
|
||
|
|
h := sha256.New()
|
||
|
|
h.Write([]byte(enum.Name))
|
||
|
|
for _, member := range enum.Members {
|
||
|
|
h.Write([]byte(member.Name))
|
||
|
|
h.Write([]byte(fmt.Sprintf("%d", member.Value)))
|
||
|
|
}
|
||
|
|
name := hex.EncodeToString(h.Sum(nil))
|
||
|
|
|
||
|
|
token, ok := v.datatypes[name]
|
||
|
|
if !ok {
|
||
|
|
token = new(db.Token)
|
||
|
|
v.datatypes[name] = token
|
||
|
|
}
|
||
|
|
|
||
|
|
token.Source = v.pdb_source_id
|
||
|
|
|
||
|
|
token.Keyword = "enum"
|
||
|
|
|
||
|
|
// apply name (may be __unnamed)
|
||
|
|
token.Names = append(token.Names, db.TokenName{db.OriginalName, enum.Name})
|
||
|
|
|
||
|
|
// this is an original datatype
|
||
|
|
token.Kind = db.OriginalDatatypeToken
|
||
|
|
|
||
|
|
for _, member := range enum.Members {
|
||
|
|
var token_member db.TokenMember
|
||
|
|
token_member.Kind = db.EnumMember
|
||
|
|
token_member.Key = member.Name
|
||
|
|
token_member.Value = fmt.Sprintf("%d", member.Value)
|
||
|
|
|
||
|
|
if !slices.Contains(token.Members, token_member) {
|
||
|
|
token.Members = append(token.Members, token_member)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func (v *pdb_token_visitor) visit_string(s string) (err error) {
|
||
|
|
token, ok := v.strings[s]
|
||
|
|
if !ok {
|
||
|
|
token = new(db.Token)
|
||
|
|
v.strings[s] = token
|
||
|
|
}
|
||
|
|
|
||
|
|
// apply source
|
||
|
|
token.Source = v.pdb_source_id
|
||
|
|
|
||
|
|
// this is a string token
|
||
|
|
token.Kind = db.OriginalStringToken
|
||
|
|
|
||
|
|
// add name
|
||
|
|
var token_name db.TokenName
|
||
|
|
token_name.Kind = db.OriginalName
|
||
|
|
token_name.Name = s
|
||
|
|
token.Names = []db.TokenName{token_name}
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func (v *pdb_token_visitor) visit_source_files_table(table *pdbconv.Table) (err error) {
|
||
|
|
for _, source_file := range table.SourceFiles {
|
||
|
|
if err = v.visit_string(source_file.Name); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func (v *pdb_token_visitor) visit_constant(symbol *pdbconv.TableSymbol) (err error) {
|
||
|
|
var token db.Token
|
||
|
|
token.ID = v.tokens_database.next_token_id()
|
||
|
|
token.Source = v.pdb_source_id
|
||
|
|
token.Keyword = "const"
|
||
|
|
token.Datatype = symbol.Datatype
|
||
|
|
|
||
|
|
if symbol.Name != "" {
|
||
|
|
var name db.TokenName
|
||
|
|
name.Kind = db.OriginalName
|
||
|
|
name.Name = symbol.Name
|
||
|
|
token.Names = append(token.Names, name)
|
||
|
|
}
|
||
|
|
|
||
|
|
if symbol.Undecorated != "" {
|
||
|
|
var name db.TokenName
|
||
|
|
name.Kind = db.OriginalName
|
||
|
|
name.Name = symbol.Undecorated
|
||
|
|
token.Names = append(token.Names, name)
|
||
|
|
}
|
||
|
|
|
||
|
|
var value db.TokenMember
|
||
|
|
value.Kind = db.ConstantValueMember
|
||
|
|
value.Value = symbol.Value
|
||
|
|
|
||
|
|
token.Members = append(token.Members, value)
|
||
|
|
|
||
|
|
v.constants = append(v.constants, token)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func (v *pdb_token_visitor) visit_table_symbol(symbol *pdbconv.TableSymbol) (err error) {
|
||
|
|
if symbol.Address == "0x0" {
|
||
|
|
if symbol.Value != "" && symbol.Kind == "Constant" {
|
||
|
|
err = v.visit_constant(symbol)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// so, this does not correspond to an actual symbol.
|
||
|
|
// we can still mine it for string tokens.
|
||
|
|
if symbol.Name != "" {
|
||
|
|
if err = v.visit_string(symbol.Name); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if symbol.Undecorated != "" {
|
||
|
|
if err = v.visit_string(symbol.Name); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// this corresponds to an address
|
||
|
|
// compute the real address
|
||
|
|
var address uint64
|
||
|
|
address, err = strconv.ParseUint(symbol.Address, 0, 64)
|
||
|
|
if err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
address = v.base_address + address
|
||
|
|
|
||
|
|
token, ok := v.symbols[address]
|
||
|
|
if !ok {
|
||
|
|
token = new(db.Token)
|
||
|
|
v.symbols[address] = token
|
||
|
|
}
|
||
|
|
token.Source = v.pdb_source_id
|
||
|
|
|
||
|
|
if symbol.Datatype != "" {
|
||
|
|
token.Datatype = symbol.Datatype
|
||
|
|
}
|
||
|
|
|
||
|
|
if symbol.Kind == "FileStatic" {
|
||
|
|
token.Keyword = "static"
|
||
|
|
} else if symbol.Kind == "Global" {
|
||
|
|
token.Keyword = "global"
|
||
|
|
}
|
||
|
|
|
||
|
|
if symbol.Name != "" {
|
||
|
|
token_name := db.TokenName{db.OriginalName, symbol.Name}
|
||
|
|
if !slices.Contains(token.Names, token_name) {
|
||
|
|
token.Names = append(token.Names, token_name)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if symbol.Undecorated != "" {
|
||
|
|
undecorated := db.TokenName{db.OriginalName, symbol.Undecorated}
|
||
|
|
if !slices.Contains(token.Names, undecorated) {
|
||
|
|
token.Names = append(token.Names, undecorated)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func (v *pdb_token_visitor) visit_symbols_table(table *pdbconv.Table) (err error) {
|
||
|
|
for _, symbol := range table.Symbols {
|
||
|
|
if err = v.visit_table_symbol(&symbol); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func (v *pdb_token_visitor) visit_table(table *pdbconv.Table) (err error) {
|
||
|
|
if table.Name == "SourceFiles" {
|
||
|
|
err = v.visit_source_files_table(table)
|
||
|
|
} else if table.Name == "Symbols" {
|
||
|
|
err = v.visit_symbols_table(table)
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func (v *pdb_token_visitor) visit_typedef(typedef *pdbconv.Typedef) (err error) {
|
||
|
|
token, ok := v.datatypes[typedef.Name]
|
||
|
|
if !ok {
|
||
|
|
token = new(db.Token)
|
||
|
|
v.datatypes[typedef.Name] = token
|
||
|
|
}
|
||
|
|
|
||
|
|
token.Source = v.pdb_source_id
|
||
|
|
|
||
|
|
token.Kind = db.OriginalDatatypeToken
|
||
|
|
|
||
|
|
var token_name db.TokenName
|
||
|
|
token_name.Kind = db.OriginalName
|
||
|
|
token_name.Name = typedef.Name
|
||
|
|
|
||
|
|
if !slices.Contains(token.Names, token_name) {
|
||
|
|
token.Names = append(token.Names, token_name)
|
||
|
|
}
|
||
|
|
|
||
|
|
token.Datatype = typedef.Basetype
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func (v *pdb_token_visitor) visit_all(pdb *pdbconv.ProgramDatabase) (err error) {
|
||
|
|
for _, class := range pdb.Classes {
|
||
|
|
if err = v.visit_class(&class); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, datatype := range pdb.Datatypes {
|
||
|
|
if err = v.visit_datatype(&datatype); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, enum := range pdb.Enums {
|
||
|
|
if err = v.visit_enum(&enum); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, function := range pdb.Functions {
|
||
|
|
if err = v.visit_function(&function); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, table := range pdb.Tables {
|
||
|
|
if err = v.visit_table(&table); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for _, typedef := range pdb.Typedefs {
|
||
|
|
if err = v.visit_typedef(&typedef); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
func (v *pdb_token_visitor) write_tokens() (err error) {
|
||
|
|
datatypes := slices.Collect(maps.Keys(v.datatypes))
|
||
|
|
sort.Strings(datatypes)
|
||
|
|
symbols := slices.Collect(maps.Keys(v.symbols))
|
||
|
|
slices.SortFunc(symbols, func(a, b uint64) int {
|
||
|
|
if a < b {
|
||
|
|
return -1
|
||
|
|
} else if a == b {
|
||
|
|
return 0
|
||
|
|
}
|
||
|
|
return 1
|
||
|
|
})
|
||
|
|
strings := slices.Collect(maps.Keys(v.strings))
|
||
|
|
sort.Strings(strings)
|
||
|
|
for _, datatype := range datatypes {
|
||
|
|
if err = v.tokens_database.Write(v.datatypes[datatype]); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for _, symbol := range symbols {
|
||
|
|
if err = v.tokens_database.Write(v.symbols[symbol]); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for _, string := range strings {
|
||
|
|
if err = v.tokens_database.Write(v.strings[string]); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for _, constant := range v.constants {
|
||
|
|
if err = v.tokens_database.Write(&constant); err != nil {
|
||
|
|
return
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return
|
||
|
|
}
|