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

255 lines
6.1 KiB
Go

package goobj
import (
"fmt"
"os"
"regexp"
"sort"
"strconv"
"strings"
"loov.dev/lensm/internal/disasm"
godisasm "loov.dev/lensm/internal/go/src/disasm"
)
var rxRefAbs = regexp.MustCompile(`\s0x[\da-fA-F]+$`)
var rxRefRel = regexp.MustCompile(`\s-?\d+\(PC\)$`)
var rxCallOrJump = regexp.MustCompile(`^(?:CALL|JMP)\s+(.+?)\(SB\)`)
// Disassemble disassembles the specified symbol.
func Disassemble(dis *godisasm.Disasm, sym *Func, opts disasm.Options) (*disasm.Code, error) {
neededLines := make(map[string]*disasm.LineSet)
file, _, _ := dis.PCLN().PCToLine(sym.sym.Addr)
needRefPCs := map[uint64]struct{}{}
code := &disasm.Code{
Name: sym.Name(),
File: file,
Arch: dis.GOARCH(),
}
var instructions []disasm.Inst
dis.DecodeSyntax(sym.sym.Addr, sym.sym.Addr+uint64(sym.sym.Size), sym.sym.Relocs,
func(pc, size uint64, file string, line int, text, nativeText, mnemonic string) {
// TODO: find a better way to calculate the jump target
var refPC uint64
var call string
if match := rxRefAbs.FindString(text); match != "" {
if target, err := strconv.ParseInt(match[3:], 16, 64); err == nil {
refPC = uint64(target)
}
} else if match := rxRefRel.FindString(text); match != "" {
// TODO: this calculation seems incorrect
if target, err := strconv.ParseInt(match[1:len(match)-4], 10, 64); err == nil {
refPC = uint64(int64(pc) + target*4)
} else {
panic(err)
}
} else if match := rxCallOrJump.FindStringSubmatch(text); len(match) > 0 {
call = match[1]
}
if refPC != 0 {
needRefPCs[refPC] = struct{}{}
}
instructions = append(instructions, disasm.Inst{
PC: pc,
Text: text,
NativeText: nativeText,
Mnemonic: mnemonic,
File: file,
Line: line,
Call: call,
RefPC: refPC,
})
if file != "" && file != "<autogenerated>" {
lineset, ok := neededLines[file]
if !ok {
lineset = &disasm.LineSet{}
neededLines[file] = lineset
}
lineset.Add(line)
}
})
pcToIndex := map[uint64]int{}
for _, ix := range instructions {
if _, ok := needRefPCs[ix.PC]; ok {
// add empty line
code.Insts = append(code.Insts, disasm.Inst{})
}
pcToIndex[ix.PC] = len(code.Insts)
code.Insts = append(code.Insts, ix)
}
type jumpInterval struct {
index int
ix *disasm.Inst
min, max uint64
}
var jumps []jumpInterval
for i := range code.Insts {
ix := &code.Insts[i]
if ix.RefPC != 0 {
target, ok := pcToIndex[ix.RefPC]
if !ok {
continue
}
ix.RefOffset = target - i
if ix.PC <= ix.RefPC {
jumps = append(jumps, jumpInterval{
index: i,
ix: ix,
min: ix.PC,
max: ix.RefPC,
})
} else {
jumps = append(jumps, jumpInterval{
index: i,
ix: ix,
min: ix.RefPC,
max: ix.PC,
})
}
}
}
sort.Slice(jumps, func(i, k int) bool {
if jumps[i].min == jumps[k].min {
return jumps[i].max > jumps[k].max
}
return jumps[i].min < jumps[k].min
})
var stackLayers []uint64
insertToStack := func(ix *disasm.Inst, max uint64) {
found := false
for k, pc := range stackLayers {
if pc == 0 {
stackLayers[k] = max
ix.RefStack = k
found = true
break
}
}
if !found {
code.MaxJump = len(stackLayers)
ix.RefStack = len(stackLayers)
stackLayers = append(stackLayers, max)
}
}
for _, jump := range jumps {
for i, pc := range stackLayers {
if pc <= jump.min {
stackLayers[i] = 0
}
}
insertToStack(jump.ix, jump.max)
}
for i := range code.Insts {
ix := &code.Insts[i]
ix.RefStack = code.MaxJump - ix.RefStack + 1
}
code.MaxJump++
// remove trailing interrupts from funcs
for len(code.Insts) > 0 &&
(strings.HasPrefix(code.Insts[len(code.Insts)-1].Text, "INT ") ||
code.Insts[len(code.Insts)-1].Text == "?") {
code.Insts = code.Insts[:len(code.Insts)-1]
}
// load sources
code.Source = LoadSources(neededLines, code.File, opts.Context)
// create a mapping from source code to disassembly
type fileLine struct {
file string
line int
}
lineRefs := map[fileLine]*disasm.LineSet{}
for i, ix := range code.Insts {
k := fileLine{file: ix.File, line: ix.Line}
n, ok := lineRefs[k]
if !ok {
n = &disasm.LineSet{}
lineRefs[k] = n
}
n.Add(i)
}
for i := range code.Source {
src := &code.Source[i]
for k := range src.Blocks {
block := &src.Blocks[k]
block.Related = make([][]disasm.LineRange, len(block.Lines))
for line := block.From; line <= block.To; line++ { // todo check: line <= block.To
if refs, ok := lineRefs[fileLine{file: src.File, line: line}]; ok {
block.Related[line-block.From] = refs.RangesZero()
}
}
}
}
return code, nil
}
var rxEnvVariable = regexp.MustCompile(`\$[a-zA-Z_]+[a-zA-Z0-9_]+\b`)
func replaceEnvironmentVariables(s string) string {
return rxEnvVariable.ReplaceAllStringFunc(s, func(env string) string {
replacement := os.Getenv(env[1:])
if replacement != "" {
return replacement
}
return env
})
}
// LoadSources loads the specified line sets.
func LoadSources(needed map[string]*disasm.LineSet, symbolFile string, context int) []disasm.Source {
var sources []disasm.Source
for file, set := range needed {
data, err := os.ReadFile(replaceEnvironmentVariables(file))
if err != nil {
// TODO: should we create a stub source block instead?
fmt.Fprintf(os.Stderr, "unable to load source from %q: %v\n", file, err)
continue
}
lines := strings.Split(string(data), "\n")
source := disasm.Source{
File: file,
}
for _, r := range set.Ranges(context) {
to := min(r.To-1, len(lines))
lineBlock := lines[r.From-1 : to]
for i, v := range lineBlock {
lineBlock[i] = strings.Replace(v, "\t", " ", -1)
}
source.Blocks = append(source.Blocks, disasm.SourceBlock{
LineRange: r,
Lines: lineBlock,
})
}
sources = append(sources, source)
}
// Sort the sources and prioritize the file where the main symbol is located.
sort.Slice(sources, func(i, j int) bool {
if sources[i].File == symbolFile {
return true
}
if sources[j].File == symbolFile {
return false
}
return sources[i].File < sources[j].File
})
return sources
}