
Improved prologue detection for big stackframes and added references to the function of the compiler that inserts the prologue.
184 lines
4.4 KiB
Go
184 lines
4.4 KiB
Go
package proc
|
|
|
|
import (
|
|
"debug/gosym"
|
|
"encoding/binary"
|
|
"rsc.io/x86/x86asm"
|
|
)
|
|
|
|
var maxInstructionLength uint64 = 15
|
|
|
|
type ArchInst x86asm.Inst
|
|
|
|
func asmDecode(mem []byte, pc uint64) (*ArchInst, error) {
|
|
inst, err := x86asm.Decode(mem, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
patchPCRel(pc, &inst)
|
|
r := ArchInst(inst)
|
|
return &r, nil
|
|
}
|
|
|
|
func (inst *ArchInst) Size() int {
|
|
return inst.Len
|
|
}
|
|
|
|
// converts PC relative arguments to absolute addresses
|
|
func patchPCRel(pc uint64, inst *x86asm.Inst) {
|
|
for i := range inst.Args {
|
|
rel, isrel := inst.Args[i].(x86asm.Rel)
|
|
if isrel {
|
|
inst.Args[i] = x86asm.Imm(int64(pc) + int64(rel) + int64(inst.Len))
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (inst *AsmInstruction) Text(flavour AssemblyFlavour) string {
|
|
if inst.Inst == nil {
|
|
return "?"
|
|
}
|
|
|
|
var text string
|
|
|
|
switch flavour {
|
|
case GNUFlavour:
|
|
text = x86asm.GNUSyntax(x86asm.Inst(*inst.Inst))
|
|
case IntelFlavour:
|
|
fallthrough
|
|
default:
|
|
text = x86asm.IntelSyntax(x86asm.Inst(*inst.Inst))
|
|
}
|
|
|
|
if inst.IsCall() && inst.DestLoc != nil && inst.DestLoc.Fn != nil {
|
|
text += " " + inst.DestLoc.Fn.Name
|
|
}
|
|
|
|
return text
|
|
}
|
|
|
|
func (inst *AsmInstruction) IsCall() bool {
|
|
return inst.Inst.Op == x86asm.CALL || inst.Inst.Op == x86asm.LCALL
|
|
}
|
|
|
|
func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs Registers) *Location {
|
|
if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL {
|
|
return nil
|
|
}
|
|
|
|
var pc uint64
|
|
var err error
|
|
|
|
switch arg := inst.Args[0].(type) {
|
|
case x86asm.Imm:
|
|
pc = uint64(arg)
|
|
case x86asm.Reg:
|
|
if !currentGoroutine || regs == nil {
|
|
return nil
|
|
}
|
|
pc, err = regs.Get(int(arg))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
case x86asm.Mem:
|
|
if !currentGoroutine || regs == nil {
|
|
return nil
|
|
}
|
|
if arg.Segment != 0 {
|
|
return nil
|
|
}
|
|
regs, err := thread.Registers(false)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
base, err1 := regs.Get(int(arg.Base))
|
|
index, err2 := regs.Get(int(arg.Index))
|
|
if err1 != nil || err2 != nil {
|
|
return nil
|
|
}
|
|
addr := uintptr(int64(base) + int64(index*uint64(arg.Scale)) + arg.Disp)
|
|
//TODO: should this always be 64 bits instead of inst.MemBytes?
|
|
pcbytes, err := thread.readMemory(addr, inst.MemBytes)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
pc = binary.LittleEndian.Uint64(pcbytes)
|
|
default:
|
|
return nil
|
|
}
|
|
|
|
file, line, fn := thread.dbp.PCToLine(pc)
|
|
if fn == nil {
|
|
return nil
|
|
}
|
|
return &Location{PC: pc, File: file, Line: line, Fn: fn}
|
|
}
|
|
|
|
type instrseq []x86asm.Op
|
|
|
|
// Possible stacksplit prologues are inserted by stacksplit in
|
|
// $GOROOT/src/cmd/internal/obj/x86/obj6.go.
|
|
// The stacksplit prologue will always begin with loading curg in CX, this
|
|
// instruction is added by load_g_cx in the same file and is either 1 or 2
|
|
// MOVs.
|
|
var prologues []instrseq
|
|
|
|
func init() {
|
|
var tinyStacksplit = instrseq{x86asm.CMP, x86asm.JBE}
|
|
var smallStacksplit = instrseq{x86asm.LEA, x86asm.CMP, x86asm.JBE}
|
|
var bigStacksplit = instrseq{x86asm.MOV, x86asm.CMP, x86asm.JE, x86asm.LEA, x86asm.SUB, x86asm.CMP, x86asm.JBE}
|
|
var unixGetG = instrseq{x86asm.MOV}
|
|
var windowsGetG = instrseq{x86asm.MOV, x86asm.MOV}
|
|
|
|
prologues = make([]instrseq, 0, 2*3)
|
|
for _, getG := range []instrseq{unixGetG, windowsGetG} {
|
|
for _, stacksplit := range []instrseq{tinyStacksplit, smallStacksplit, bigStacksplit} {
|
|
prologue := make(instrseq, 0, len(getG)+len(stacksplit))
|
|
prologue = append(prologue, getG...)
|
|
prologue = append(prologue, stacksplit...)
|
|
prologues = append(prologues, prologue)
|
|
}
|
|
}
|
|
}
|
|
|
|
// FirstPCAfterPrologue returns the address of the first instruction after the prologue for function fn
|
|
// If sameline is set FirstPCAfterPrologue will always return an address associated with the same line as fn.Entry
|
|
func (dbp *Process) FirstPCAfterPrologue(fn *gosym.Func, sameline bool) (uint64, error) {
|
|
text, err := dbp.CurrentThread.Disassemble(fn.Entry, fn.End, false)
|
|
if err != nil {
|
|
return fn.Entry, err
|
|
}
|
|
|
|
if len(text) <= 0 {
|
|
return fn.Entry, nil
|
|
}
|
|
|
|
for _, prologue := range prologues {
|
|
if len(prologue) >= len(text) {
|
|
continue
|
|
}
|
|
if checkPrologue(text, prologue) {
|
|
r := &text[len(prologue)]
|
|
if sameline {
|
|
if r.Loc.Line != text[0].Loc.Line {
|
|
return fn.Entry, nil
|
|
}
|
|
}
|
|
return r.Loc.PC, nil
|
|
}
|
|
}
|
|
|
|
return fn.Entry, nil
|
|
}
|
|
|
|
func checkPrologue(s []AsmInstruction, prologuePattern instrseq) bool {
|
|
line := s[0].Loc.Line
|
|
for i, op := range prologuePattern {
|
|
if s[i].Inst.Op != op || s[i].Loc.Line != line {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|