delve/pkg/proc/disasm.go

182 lines
4.7 KiB
Go
Raw Normal View History

2016-02-06 06:00:48 +00:00
package proc
import (
"fmt"
"github.com/go-delve/delve/pkg/dwarf/op"
)
// AsmInstruction represents one assembly instruction.
2016-02-06 06:00:48 +00:00
type AsmInstruction struct {
Loc Location
DestLoc *Location
Bytes []byte
Breakpoint bool
AtPC bool
Size int
Kind AsmInstructionKind
Inst archInst
}
type AsmInstructionKind uint8
const (
OtherInstruction AsmInstructionKind = iota
CallInstruction
RetInstruction
JmpInstruction
HardBreakInstruction
)
// IsCall is true if instr is a call instruction.
func (instr *AsmInstruction) IsCall() bool {
return instr.Kind == CallInstruction
}
// IsRet is true if instr is a return instruction.
func (instr *AsmInstruction) IsRet() bool {
return instr.Kind == RetInstruction
}
// IsJmp is true if instr is an unconditional jump instruction.
func (instr *AsmInstruction) IsJmp() bool {
return instr.Kind == JmpInstruction
}
// IsHardBreak is true if instr is a hardcoded breakpoint instruction.
func (instr *AsmInstruction) IsHardBreak() bool {
return instr.Kind == HardBreakInstruction
}
type archInst interface {
Text(flavour AssemblyFlavour, pc uint64, symLookup func(uint64) (string, uint64)) string
OpcodeEquals(op uint64) bool
2016-02-06 06:00:48 +00:00
}
// AssemblyFlavour is the assembly syntax to display.
2016-02-06 06:00:48 +00:00
type AssemblyFlavour int
const (
// GNUFlavour will display GNU assembly syntax.
GNUFlavour AssemblyFlavour = iota
// IntelFlavour will display Intel assembly syntax.
2016-02-06 06:00:48 +00:00
IntelFlavour
// GoFlavour will display Go assembly syntax.
GoFlavour
2016-02-06 06:00:48 +00:00
)
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.
// If sameline is set firstPCAfterPrologueDisassembly will always return an
// address associated with the same line as fn.Entry
func firstPCAfterPrologueDisassembly(p Process, fn *Function, sameline bool) (uint64, error) {
proc/*: remove proc.Thread.Blocked, refactor memory access (#2206) On linux we can not read memory if the thread we use to do it is occupied doing certain system calls. The exact conditions when this happens have never been clear. This problem was worked around by using the Blocked method which recognized the most common circumstances where this would happen. However this is a hack: Blocked returning true doesn't mean that the problem will manifest and Blocked returning false doesn't necessarily mean the problem will not manifest. A side effect of this is issue #2151 where sometimes we can't read the memory of a thread and find its associated goroutine. This commit fixes this problem by always reading memory using a thread we know to be good for this, specifically the one returned by ContinueOnce. In particular the changes are as follows: 1. Remove (ProcessInternal).CurrentThread and (ProcessInternal).SetCurrentThread, the "current thread" becomes a field of Target, CurrentThread becomes a (*Target) method and (*Target).SwitchThread basically just sets a field Target. 2. The backends keep track of their own internal idea of what the current thread is, to use it to read memory, this is the thread they return from ContinueOnce as trapthread 3. The current thread in the backend and the current thread in Target only ever get synchronized in two places: when the backend creates a Target object the currentThread field of Target is initialized with the backend's current thread and when (*Target).Restart gets called (when a recording is rewound the currentThread used by Target might not exist anymore). 4. We remove the MemoryReadWriter interface embedded in Thread and instead add a Memory method to Process that returns a MemoryReadWriter. The backends will return something here that will read memory using the current thread saved by the backend. 5. The Thread.Blocked method is removed One possible problem with this change is processes that have threads with different memory maps. As far as I can determine this could happen on old versions of linux but this option was removed in linux 2.5. Fixes #2151
2020-11-09 19:28:40 +00:00
mem := p.Memory()
breakpoints := p.Breakpoints()
bi := p.BinInfo()
text, err := disassemble(mem, nil, breakpoints, bi, fn.Entry, fn.End, false)
if err != nil {
return fn.Entry, err
}
if len(text) == 0 {
return fn.Entry, nil
}
for _, prologue := range p.BinInfo().Arch.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 opcodeSeq) bool {
line := s[0].Loc.Line
for i, op := range prologuePattern {
if !s[i].Inst.OpcodeEquals(op) || s[i].Loc.Line != line {
return false
}
}
return true
}
2019-08-08 18:54:56 +00:00
// Disassemble disassembles target memory between startAddr and endAddr, marking
// the current instruction being executed in goroutine g.
2019-08-08 18:54:56 +00:00
// If currentGoroutine is set and thread is stopped at a CALL instruction Disassemble
// will evaluate the argument of the CALL instruction using the thread's registers.
// Be aware that the Bytes field of each returned instruction is a slice of a larger array of size startAddr - endAddr.
func Disassemble(mem MemoryReadWriter, regs Registers, breakpoints *BreakpointMap, bi *BinaryInfo, startAddr, endAddr uint64) ([]AsmInstruction, error) {
if startAddr > endAddr {
return nil, fmt.Errorf("start address(%x) should be less than end address(%x)", startAddr, endAddr)
}
2019-08-08 18:54:56 +00:00
return disassemble(mem, regs, breakpoints, bi, startAddr, endAddr, false)
}
2019-08-08 18:54:56 +00:00
func disassemble(memrw MemoryReadWriter, regs Registers, breakpoints *BreakpointMap, bi *BinaryInfo, startAddr, endAddr uint64, singleInstr bool) ([]AsmInstruction, error) {
var dregs *op.DwarfRegisters
if regs != nil {
dregs = bi.Arch.RegistersToDwarfRegisters(0, regs)
}
2019-08-08 18:54:56 +00:00
mem := make([]byte, int(endAddr-startAddr))
_, err := memrw.ReadMemory(mem, startAddr)
2016-02-06 06:00:48 +00:00
if err != nil {
return nil, err
}
r := make([]AsmInstruction, 0, len(mem)/bi.Arch.MaxInstructionLength())
2019-08-08 18:54:56 +00:00
pc := startAddr
2016-02-06 06:00:48 +00:00
var curpc uint64
if regs != nil {
curpc = regs.PC()
2016-02-06 06:00:48 +00:00
}
for len(mem) > 0 {
bp, atbp := breakpoints.M[pc]
2016-02-06 06:00:48 +00:00
if atbp {
copy(mem, bp.OriginalData)
2016-02-06 06:00:48 +00:00
}
file, line, fn := bi.PCToLine(pc)
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, dregs, memrw, bi)
r = append(r, inst)
pc += uint64(inst.Size)
mem = mem[inst.Size:]
if singleInstr {
break
}
2016-02-06 06:00:48 +00:00
}
return r, nil
}
// Text will return the assembly instructions in human readable format according to
// the flavour specified.
func (instr *AsmInstruction) Text(flavour AssemblyFlavour, bi *BinaryInfo) string {
return instr.Inst.Text(flavour, instr.Loc.PC, bi.symLookup)
}