提交 4a6b53c0 编写于 作者: A aarzilli 提交者: Derek Parker

proc: build disassemblers unconditionally

Remove build tags from disassembler code, move architecture specific
functionality inside proc.Arch.
This is necessary because Delve should be able to debug corefiles
cross-platform.
上级 8ed7a840
......@@ -47,10 +47,9 @@ func (a *AMD64) PtrSize() int {
return 8
}
// MinInstructionLength returns the min lenth
// of the instruction
func (a *AMD64) MinInstructionLength() int {
return 1
// MaxInstructionLength returns the maximum lenght of an instruction.
func (a *AMD64) MaxInstructionLength() int {
return 15
}
// BreakpointInstruction returns the Breakpoint
......
......@@ -10,26 +10,40 @@ import (
"golang.org/x/arch/x86/x86asm"
)
var maxInstructionLength uint64 = 15
type archInst x86asm.Inst
func asmDecode(mem []byte, pc uint64) (*archInst, error) {
// AsmDecode decodes the assembly instruction starting at mem[0:] into asmInst.
// It assumes that the Loc and AtPC fields of asmInst have already been filled.
func (a *AMD64) AsmDecode(asmInst *AsmInstruction, mem []byte, regs Registers, memrw MemoryReadWriter, bi *BinaryInfo) error {
inst, err := x86asm.Decode(mem, 64)
if err != nil {
return nil, err
asmInst.Inst = (*amd64ArchInst)(nil)
asmInst.Size = 1
asmInst.Bytes = mem[:asmInst.Size]
return err
}
patchPCRel(pc, &inst)
r := archInst(inst)
return &r, nil
asmInst.Size = inst.Len
asmInst.Bytes = mem[:asmInst.Size]
patchPCRelAMD64(asmInst.Loc.PC, &inst)
asmInst.Inst = (*amd64ArchInst)(&inst)
asmInst.Kind = OtherInstruction
switch inst.Op {
case x86asm.CALL, x86asm.LCALL:
asmInst.Kind = CallInstruction
case x86asm.RET, x86asm.LRET:
asmInst.Kind = RetInstruction
}
asmInst.DestLoc = resolveCallArgAMD64(&inst, asmInst.Loc.PC, asmInst.AtPC, regs, memrw, bi)
return nil
}
func (inst *archInst) Size() int {
return inst.Len
func (a *AMD64) Prologues() []opcodeSeq {
return prologuesAMD64
}
// converts PC relative arguments to absolute addresses
func patchPCRel(pc uint64, inst *x86asm.Inst) {
func patchPCRelAMD64(pc uint64, inst *x86asm.Inst) {
for i := range inst.Args {
rel, isrel := inst.Args[i].(x86asm.Rel)
if isrel {
......@@ -38,10 +52,10 @@ func patchPCRel(pc uint64, inst *x86asm.Inst) {
}
}
// Text will return the assembly instructions in human readable format according to
// the flavour specified.
func (inst *AsmInstruction) Text(flavour AssemblyFlavour, bi *BinaryInfo) string {
if inst.Inst == nil {
type amd64ArchInst x86asm.Inst
func (inst *amd64ArchInst) Text(flavour AssemblyFlavour, pc uint64, symLookup func(uint64) (string, uint64)) string {
if inst == nil {
return "?"
}
......@@ -49,35 +63,27 @@ func (inst *AsmInstruction) Text(flavour AssemblyFlavour, bi *BinaryInfo) string
switch flavour {
case GNUFlavour:
text = x86asm.GNUSyntax(x86asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup)
text = x86asm.GNUSyntax(x86asm.Inst(*inst), pc, symLookup)
case GoFlavour:
text = x86asm.GoSyntax(x86asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup)
text = x86asm.GoSyntax(x86asm.Inst(*inst), pc, symLookup)
case IntelFlavour:
fallthrough
default:
text = x86asm.IntelSyntax(x86asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup)
text = x86asm.IntelSyntax(x86asm.Inst(*inst), pc, symLookup)
}
return text
}
// IsCall returns true if the instruction is a CALL or LCALL instruction.
func (inst *AsmInstruction) IsCall() bool {
if inst.Inst == nil {
return false
}
return inst.Inst.Op == x86asm.CALL || inst.Inst.Op == x86asm.LCALL
}
// IsRet returns true if the instruction is a RET or LRET instruction.
func (inst *AsmInstruction) IsRet() bool {
if inst.Inst == nil {
func (inst *amd64ArchInst) OpcodeEquals(op uint64) bool {
if inst == nil {
return false
}
return inst.Inst.Op == x86asm.RET || inst.Inst.Op == x86asm.LRET
return uint64(inst.Op) == op
}
func resolveCallArg(inst *archInst, instAddr uint64, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location {
func resolveCallArgAMD64(inst *x86asm.Inst, instAddr uint64, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location {
if inst.Op != x86asm.CALL && inst.Op != x86asm.LCALL {
return nil
}
......@@ -127,29 +133,27 @@ func resolveCallArg(inst *archInst, instAddr uint64, currentGoroutine bool, regs
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
var prologuesAMD64 []opcodeSeq
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))
var tinyStacksplit = opcodeSeq{uint64(x86asm.CMP), uint64(x86asm.JBE)}
var smallStacksplit = opcodeSeq{uint64(x86asm.LEA), uint64(x86asm.CMP), uint64(x86asm.JBE)}
var bigStacksplit = opcodeSeq{uint64(x86asm.MOV), uint64(x86asm.CMP), uint64(x86asm.JE), uint64(x86asm.LEA), uint64(x86asm.SUB), uint64(x86asm.CMP), uint64(x86asm.JBE)}
var unixGetG = opcodeSeq{uint64(x86asm.MOV)}
var windowsGetG = opcodeSeq{uint64(x86asm.MOV), uint64(x86asm.MOV)}
prologuesAMD64 = make([]opcodeSeq, 0, 2*3)
for _, getG := range []opcodeSeq{unixGetG, windowsGetG} {
for _, stacksplit := range []opcodeSeq{tinyStacksplit, smallStacksplit, bigStacksplit} {
prologue := make(opcodeSeq, 0, len(getG)+len(stacksplit))
prologue = append(prologue, getG...)
prologue = append(prologue, stacksplit...)
prologues = append(prologues, prologue)
prologuesAMD64 = append(prologuesAMD64, prologue)
}
}
}
......@@ -9,7 +9,9 @@ import (
// CPU architecture.
type Arch interface {
PtrSize() int
MinInstructionLength() int
MaxInstructionLength() int
AsmDecode(asmInst *AsmInstruction, mem []byte, regs Registers, memrw MemoryReadWriter, bi *BinaryInfo) error
Prologues() []opcodeSeq
BreakpointInstruction() []byte
BreakInstrMovesPC() bool
BreakpointSize() int
......
......@@ -47,9 +47,8 @@ func (a *ARM64) PtrSize() int {
return 8
}
// MinInstructionLength returns the min lenth
// of the instruction
func (a *ARM64) MinInstructionLength() int {
// MaxInstructionLength returns the maximum lenght of an instruction.
func (a *ARM64) MaxInstructionLength() int {
return 4
}
......
......@@ -8,60 +8,38 @@ import (
"golang.org/x/arch/arm64/arm64asm"
)
var maxInstructionLength uint64 = 4
// AsmDecode decodes the assembly instruction starting at mem[0:] into asmInst.
// It assumes that the Loc and AtPC fields of asmInst have already been filled.
func (a *ARM64) AsmDecode(asmInst *AsmInstruction, mem []byte, regs Registers, memrw MemoryReadWriter, bi *BinaryInfo) error {
asmInst.Size = 4
asmInst.Bytes = mem[:asmInst.Size]
type archInst arm64asm.Inst
func asmDecode(mem []byte, pc uint64) (*archInst, error) {
inst, err := arm64asm.Decode(mem)
if err != nil {
return nil, err
asmInst.Inst = (*arm64ArchInst)(nil)
return err
}
r := archInst(inst)
return &r, nil
}
asmInst.Inst = (*arm64ArchInst)(&inst)
asmInst.Kind = OtherInstruction
func (inst *archInst) Size() int {
return 4
}
// Text will return the assembly instructions in human readable format according to
// the flavour specified.
func (inst *AsmInstruction) Text(flavour AssemblyFlavour, bi *BinaryInfo) string {
if inst.Inst == nil {
return "?"
switch inst.Op {
case arm64asm.BL, arm64asm.BLR:
asmInst.Kind = CallInstruction
case arm64asm.RET, arm64asm.ERET:
asmInst.Kind = RetInstruction
}
var text string
asmInst.DestLoc = resolveCallArgARM64(&inst, asmInst.Loc.PC, asmInst.AtPC, regs, memrw, bi)
switch flavour {
case GNUFlavour:
text = arm64asm.GNUSyntax(arm64asm.Inst(*inst.Inst))
default:
text = arm64asm.GoSyntax(arm64asm.Inst(*inst.Inst), inst.Loc.PC, bi.symLookup, nil)
}
return text
return nil
}
// IsCall returns true if the instruction is a BL or BLR instruction.
func (inst *AsmInstruction) IsCall() bool {
if inst.Inst == nil {
return false
}
return inst.Inst.Op == arm64asm.BL || inst.Inst.Op == arm64asm.BLR
func (a *ARM64) Prologues() []opcodeSeq {
return prologuesARM64
}
// IsRet returns true if the instruction is a RET or ERET instruction.
func (inst *AsmInstruction) IsRet() bool {
if inst.Inst == nil {
return false
}
return inst.Inst.Op == arm64asm.RET || inst.Inst.Op == arm64asm.ERET
}
func resolveCallArg(inst *archInst, instAddr uint64, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location {
func resolveCallArgARM64(inst *arm64asm.Inst, instAddr uint64, currentGoroutine bool, regs Registers, mem MemoryReadWriter, bininfo *BinaryInfo) *Location {
if inst.Op != arm64asm.BL && inst.Op != arm64asm.BLR {
return nil
}
......@@ -93,25 +71,49 @@ func resolveCallArg(inst *archInst, instAddr uint64, currentGoroutine bool, regs
return &Location{PC: pc, File: file, Line: line, Fn: fn}
}
type instrseq []arm64asm.Op
// Possible stacksplit prologues are inserted by stacksplit in
// $GOROOT/src/cmd/internal/obj/arm64/obj7.go.
var prologues []instrseq
var prologuesARM64 []opcodeSeq
func init() {
var tinyStacksplit = instrseq{arm64asm.MOV, arm64asm.CMP, arm64asm.B}
var smallStacksplit = instrseq{arm64asm.SUB, arm64asm.CMP, arm64asm.B}
var bigStacksplit = instrseq{arm64asm.CMP, arm64asm.B, arm64asm.ADD, arm64asm.SUB, arm64asm.MOV, arm64asm.CMP, arm64asm.B}
var unixGetG = instrseq{arm64asm.LDR}
prologues = make([]instrseq, 0, 3)
for _, getG := range []instrseq{unixGetG} {
for _, stacksplit := range []instrseq{tinyStacksplit, smallStacksplit, bigStacksplit} {
prologue := make(instrseq, 0, len(getG)+len(stacksplit))
var tinyStacksplit = opcodeSeq{uint64(arm64asm.MOV), uint64(arm64asm.CMP), uint64(arm64asm.B)}
var smallStacksplit = opcodeSeq{uint64(arm64asm.SUB), uint64(arm64asm.CMP), uint64(arm64asm.B)}
var bigStacksplit = opcodeSeq{uint64(arm64asm.CMP), uint64(arm64asm.B), uint64(arm64asm.ADD), uint64(arm64asm.SUB), uint64(arm64asm.MOV), uint64(arm64asm.CMP), uint64(arm64asm.B)}
var unixGetG = opcodeSeq{uint64(arm64asm.LDR)}
prologuesARM64 = make([]opcodeSeq, 0, 3)
for _, getG := range []opcodeSeq{unixGetG} {
for _, stacksplit := range []opcodeSeq{tinyStacksplit, smallStacksplit, bigStacksplit} {
prologue := make(opcodeSeq, 0, len(getG)+len(stacksplit))
prologue = append(prologue, getG...)
prologue = append(prologue, stacksplit...)
prologues = append(prologues, prologue)
prologuesARM64 = append(prologuesARM64, prologue)
}
}
}
type arm64ArchInst arm64asm.Inst
func (inst *arm64ArchInst) Text(flavour AssemblyFlavour, pc uint64, symLookup func(uint64) (string, uint64)) string {
if inst == nil {
return "?"
}
var text string
switch flavour {
case GNUFlavour:
text = arm64asm.GNUSyntax(arm64asm.Inst(*inst))
default:
text = arm64asm.GoSyntax(arm64asm.Inst(*inst), pc, symLookup, nil)
}
return text
}
func (inst *arm64ArchInst) OpcodeEquals(op uint64) bool {
if inst == nil {
return false
}
return uint64(inst.Op) == op
}
......@@ -7,7 +7,32 @@ type AsmInstruction struct {
Bytes []byte
Breakpoint bool
AtPC bool
Inst *archInst
Size int
Kind AsmInstructionKind
Inst archInst
}
type AsmInstructionKind uint8
const (
OtherInstruction AsmInstructionKind = iota
CallInstruction
RetInstruction
)
func (instr *AsmInstruction) IsCall() bool {
return instr.Kind == CallInstruction
}
func (instr *AsmInstruction) IsRet() bool {
return instr.Kind == RetInstruction
}
type archInst interface {
Text(flavour AssemblyFlavour, pc uint64, symLookup func(uint64) (string, uint64)) string
OpcodeEquals(op uint64) bool
}
// AssemblyFlavour is the assembly syntax to display.
......@@ -22,6 +47,8 @@ const (
GoFlavour
)
type opcodeSeq []uint64
// firstPCAfterPrologueDisassembly returns the address of the first
// instruction after the prologue for function fn by disassembling fn and
// matching the instructions against known split-stack prologue patterns.
......@@ -40,7 +67,7 @@ func firstPCAfterPrologueDisassembly(p Process, fn *Function, sameline bool) (ui
return fn.Entry, nil
}
for _, prologue := range prologues {
for _, prologue := range p.BinInfo().Arch.Prologues() {
if len(prologue) >= len(text) {
continue
}
......@@ -58,10 +85,10 @@ func firstPCAfterPrologueDisassembly(p Process, fn *Function, sameline bool) (ui
return fn.Entry, nil
}
func checkPrologue(s []AsmInstruction, prologuePattern instrseq) bool {
func checkPrologue(s []AsmInstruction, prologuePattern opcodeSeq) bool {
line := s[0].Loc.Line
for i, op := range prologuePattern {
if s[i].Inst.Op != op || s[i].Loc.Line != line {
if !s[i].Inst.OpcodeEquals(op) || s[i].Loc.Line != line {
return false
}
}
......@@ -78,14 +105,13 @@ func Disassemble(mem MemoryReadWriter, regs Registers, breakpoints *BreakpointMa
}
func disassemble(memrw MemoryReadWriter, regs Registers, breakpoints *BreakpointMap, bi *BinaryInfo, startAddr, endAddr uint64, singleInstr bool) ([]AsmInstruction, error) {
minInstructionLength := bi.Arch.MinInstructionLength()
mem := make([]byte, int(endAddr-startAddr))
_, err := memrw.ReadMemory(mem, uintptr(startAddr))
if err != nil {
return nil, err
}
r := make([]AsmInstruction, 0, len(mem)/int(maxInstructionLength))
r := make([]AsmInstruction, 0, len(mem)/int(bi.Arch.MaxInstructionLength()))
pc := startAddr
var curpc uint64
......@@ -100,24 +126,30 @@ func disassemble(memrw MemoryReadWriter, regs Registers, breakpoints *Breakpoint
mem[i] = bp.OriginalData[i]
}
}
file, line, fn := bi.PCToLine(pc)
loc := Location{PC: pc, File: file, Line: line, Fn: fn}
inst, err := asmDecode(mem, pc)
if err == nil {
atpc := (regs != nil) && (curpc == pc)
destloc := resolveCallArg(inst, pc, atpc, regs, memrw, bi)
r = append(r, AsmInstruction{Loc: loc, DestLoc: destloc, Bytes: mem[:inst.Size()], Breakpoint: atbp, AtPC: atpc, Inst: inst})
pc += uint64(inst.Size())
mem = mem[inst.Size():]
} else {
r = append(r, AsmInstruction{Loc: loc, Bytes: mem[:minInstructionLength], Breakpoint: atbp, Inst: nil})
pc += uint64(minInstructionLength)
mem = mem[minInstructionLength:]
}
var inst AsmInstruction
inst.Loc = Location{PC: pc, File: file, Line: line, Fn: fn}
inst.Breakpoint = atbp
inst.AtPC = (regs != nil) && (curpc == pc)
bi.Arch.AsmDecode(&inst, mem, regs, memrw, bi)
r = append(r, inst)
pc += uint64(inst.Size)
mem = mem[inst.Size:]
if singleInstr {
break
}
}
return r, nil
}
// Text will return the assembly instructions in human readable format according to
// the flavour specified.
func (inst *AsmInstruction) Text(flavour AssemblyFlavour, bi *BinaryInfo) string {
return inst.Inst.Text(flavour, inst.Loc.PC, bi.symLookup)
}
......@@ -240,7 +240,7 @@ func Continue(dbp Process) error {
return err
}
pc := regs.PC()
text, err := disassemble(curthread, regs, dbp.Breakpoints(), dbp.BinInfo(), pc, pc+maxInstructionLength, true)
text, err := disassemble(curthread, regs, dbp.Breakpoints(), dbp.BinInfo(), pc, pc+uint64(dbp.BinInfo().Arch.MaxInstructionLength()), true)
if err != nil {
return err
}
......
......@@ -2483,12 +2483,6 @@ func TestStepOutDeferReturnAndDirectCall(t *testing.T) {
var maxInstructionLength uint64
func TestStepOnCallPtrInstr(t *testing.T) {
switch runtime.GOARCH {
case "amd64":
maxInstructionLength = 15
case "arm64":
maxInstructionLength = 4
}
protest.AllowRecording(t)
withTestProcess("teststepprog", t, func(p proc.Process, fixture protest.Fixture) {
setFileBreakpoint(p, t, fixture.Source, 10)
......@@ -2505,7 +2499,7 @@ func TestStepOnCallPtrInstr(t *testing.T) {
regs, err := p.CurrentThread().Registers(false)
assertNoError(err, t, "Registers()")
pc := regs.PC()
text, err := proc.Disassemble(p.CurrentThread(), regs, p.Breakpoints(), p.BinInfo(), pc, pc+maxInstructionLength)
text, err := proc.Disassemble(p.CurrentThread(), regs, p.Breakpoints(), p.BinInfo(), pc, pc+uint64(p.BinInfo().Arch.MaxInstructionLength()))
assertNoError(err, t, "Disassemble()")
if text[0].IsCall() {
found = true
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册