Files
wehub-resource-sync c4536f7e05
CI / test (push) Failing after 1s
CI / macOS amd64 (push) Has been cancelled
CI / macOS arm64 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:31 +08:00

120 lines
2.6 KiB
Go

package goobj
import (
"regexp"
"sort"
"strings"
"sync"
"loov.dev/lensm/internal/disasm"
godisasm "loov.dev/lensm/internal/go/src/disasm"
"loov.dev/lensm/internal/go/src/objfile"
)
var _ disasm.File = (*File)(nil)
var _ disasm.Func = (*Func)(nil)
// File contains information about the object file.
type File struct {
objfile *objfile.File
disasm *godisasm.Disasm
funcs []disasm.Func
// mu guards cache and serializes Disassemble calls: disassembly
// lazily populates line-table caches inside disasm, which is not
// safe for concurrent use.
mu sync.Mutex
cache map[cacheKey]cacheEntry
}
// cacheKey includes the options: MCP callers choose the source context
// per call, and a cache keyed by function alone would silently serve
// whichever context happened to load first.
type cacheKey struct {
fn *Func
context int
}
// cacheEntry also caches failures, so an erroring function isn't
// re-disassembled on every frame.
type cacheEntry struct {
code *disasm.Code
err error
}
func (file *File) Funcs() []disasm.Func { return file.funcs }
// Function contains information about the executable.
type Func struct {
obj *File
sym objfile.Sym
sortName string
}
func (fn *Func) Name() string { return fn.sym.Name }
func (file *File) Close() error {
return file.objfile.Close()
}
func Load(path string) (*File, error) {
f, err := objfile.Open(path)
if err != nil {
return nil, err
}
dis, err := godisasm.DisasmForFile(f)
if err != nil {
_ = f.Close()
return nil, err
}
file := &File{
objfile: f,
disasm: dis,
cache: make(map[cacheKey]cacheEntry),
}
for _, sym := range dis.Syms() {
if sym.Code != 'T' && sym.Code != 't' || sym.Addr < dis.TextStart() {
continue
}
sym := &Func{
obj: file,
sym: sym,
sortName: sortingName(sym.Name),
}
file.funcs = append(file.funcs, sym)
}
sort.SliceStable(file.funcs, func(i, k int) bool {
return sortingName(file.funcs[i].Name()) < sortingName(file.funcs[k].Name())
})
return file, nil
}
func (fn *Func) Load(opts disasm.Options) (*disasm.Code, error) {
return fn.obj.LoadCode(fn, opts)
}
func (file *File) LoadCode(fn *Func, opts disasm.Options) (*disasm.Code, error) {
file.mu.Lock()
defer file.mu.Unlock()
key := cacheKey{fn: fn, context: opts.Context}
entry, ok := file.cache[key]
if !ok {
entry.code, entry.err = Disassemble(fn.obj.disasm, fn, opts)
file.cache[key] = entry
}
return entry.code, entry.err
}
var rxCodeDelimiter = regexp.MustCompile(`[ *().]+`)
func sortingName(sym string) string {
sym = strings.ToLower(sym)
return rxCodeDelimiter.ReplaceAllString(sym, " ")
}