未验证 提交 f3a191cd 编写于 作者: C chainhelen 提交者: GitHub

pkg/proc,service: support linux/386 (#1884)

Implement debugging function for 386 on linux with reference to AMD64.
There are a few remaining problems that need to be solved in another time.

1. The stacktrace of cgo are not exactly as expected.
2. Not implement `core` for now.
3. Not implement `call` for now. Can't not find `runtime·debugCallV1` or
   similar function in $GOROOT/src/runtime/asm_386.s.

Update #20
上级 e9b2da17
env:
GOFLAGS: -mod=vendor
freebsd_task:
env:
GOFLAGS: -mod=vendor
freebsd_instance:
image: freebsd-11-2-release-amd64
freebsd_instance:
image: freebsd-11-2-release-amd64
test_task:
install_script: pkg install -y go gcc git
test_script: make test
linux386_task:
container:
image: i386/ubuntu:18.04
env:
matrix:
- GOVERSION: 1.12
- GOVERSION: 1.13
- GOVERSION: 1.14
test_script:
- apt-get -y update
- apt-get -y install software-properties-common
- apt-get -y install git
- add-apt-repository ppa:longsleep/golang-backports
- apt-get -y install golang-${GOVERSION}-go
- export PATH=$PATH:/usr/lib/go-${GOVERSION}/bin
- go version
- uname -a
- make test
......@@ -4,6 +4,8 @@
#ifdef __amd64__
#define BREAKPOINT asm("int3;")
#elif __i386__
#define BREAKPOINT asm("int3;")
#elif __aarch64__
#define BREAKPOINT asm("brk 0;")
#endif
......
......@@ -5,8 +5,13 @@ import (
"io/ioutil"
"os"
"testing"
"unsafe"
)
func ptrSizeByRuntimeArch() int {
return int(unsafe.Sizeof(uintptr(0)))
}
func TestFDEForPC(t *testing.T) {
frames := NewFrameIndex()
frames = append(frames,
......@@ -62,7 +67,7 @@ func BenchmarkFDEForPC(b *testing.B) {
if err != nil {
b.Fatal(err)
}
fdes := Parse(data, binary.BigEndian, 0)
fdes := Parse(data, binary.BigEndian, 0, ptrSizeByRuntimeArch())
for i := 0; i < b.N; i++ {
// bench worst case, exhaustive search
......
......@@ -20,15 +20,16 @@ type parseContext struct {
common *CommonInformationEntry
frame *FrameDescriptionEntry
length uint32
ptrSize int
}
// Parse takes in data (a byte slice) and returns a slice of
// commonInformationEntry structures. Each commonInformationEntry
// has a slice of frameDescriptionEntry structures.
func Parse(data []byte, order binary.ByteOrder, staticBase uint64) FrameDescriptionEntries {
func Parse(data []byte, order binary.ByteOrder, staticBase uint64, ptrSize int) FrameDescriptionEntries {
var (
buf = bytes.NewBuffer(data)
pctx = &parseContext{buf: buf, entries: NewFrameIndex(), staticBase: staticBase}
pctx = &parseContext{buf: buf, entries: NewFrameIndex(), staticBase: staticBase, ptrSize: ptrSize}
)
for fn := parselength; buf.Len() != 0; {
......@@ -68,10 +69,14 @@ func parselength(ctx *parseContext) parsefunc {
}
func parseFDE(ctx *parseContext) parsefunc {
var num uint64
r := ctx.buf.Next(int(ctx.length))
ctx.frame.begin = binary.LittleEndian.Uint64(r[:8]) + ctx.staticBase
ctx.frame.size = binary.LittleEndian.Uint64(r[8:16])
reader := bytes.NewReader(r)
num, _ = util.ReadUintRaw(reader, binary.LittleEndian, ctx.ptrSize)
ctx.frame.begin = num + ctx.staticBase
num, _ = util.ReadUintRaw(reader, binary.LittleEndian, ctx.ptrSize)
ctx.frame.size = num
// Insert into the tree after setting address range begin
// otherwise compares won't work.
......@@ -80,7 +85,7 @@ func parseFDE(ctx *parseContext) parsefunc {
// The rest of this entry consists of the instructions
// so we can just grab all of the data from the buffer
// cursor to length.
ctx.frame.Instructions = r[16:]
ctx.frame.Instructions = r[2*ctx.ptrSize:]
ctx.length = 0
return parselength
......
......@@ -12,8 +12,8 @@ import (
func TestParseCIE(t *testing.T) {
ctx := &parseContext{
buf : bytes.NewBuffer([]byte{3,0,1,124,16,12,7,8,5,16,2,0,36,0,0,0,0,0,0,0,0,16,64,0,0,0,0,0}),
common: &CommonInformationEntry{Length:12},
buf: bytes.NewBuffer([]byte{3, 0, 1, 124, 16, 12, 7, 8, 5, 16, 2, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 16, 64, 0, 0, 0, 0, 0}),
common: &CommonInformationEntry{Length: 12},
length: 12,
}
_ = parseCIE(ctx)
......@@ -35,8 +35,8 @@ func TestParseCIE(t *testing.T) {
if common.ReturnAddressRegister != 16 {
t.Fatalf("Expected ReturnAddressRegister 16, but get %d", common.ReturnAddressRegister)
}
initialInstructions := []byte{12,7,8,5,16,2,0}
if !bytes.Equal(common.InitialInstructions, initialInstructions){
initialInstructions := []byte{12, 7, 8, 5, 16, 2, 0}
if !bytes.Equal(common.InitialInstructions, initialInstructions) {
t.Fatalf("Expected InitialInstructions %v, but get %v", initialInstructions, common.InitialInstructions)
}
}
......@@ -56,6 +56,6 @@ func BenchmarkParse(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
Parse(data, binary.BigEndian, 0)
Parse(data, binary.BigEndian, 0, ptrSizeByRuntimeArch())
}
}
......@@ -41,6 +41,7 @@ type DebugLineInfo struct {
// if normalizeBackslash is true all backslashes (\) will be converted into forward slashes (/)
normalizeBackslash bool
ptrSize int
}
type FileEntry struct {
......@@ -53,7 +54,7 @@ type FileEntry struct {
type DebugLines []*DebugLineInfo
// ParseAll parses all debug_line segments found in data
func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool) DebugLines {
func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) DebugLines {
var (
lines = make(DebugLines, 0)
buf = bytes.NewBuffer(data)
......@@ -61,7 +62,7 @@ func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64
// We have to parse multiple file name tables here.
for buf.Len() > 0 {
lines = append(lines, Parse("", buf, logfn, staticBase, normalizeBackslash))
lines = append(lines, Parse("", buf, logfn, staticBase, normalizeBackslash, ptrSize))
}
return lines
......@@ -69,10 +70,11 @@ func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64
// Parse parses a single debug_line segment from buf. Compdir is the
// DW_AT_comp_dir attribute of the associated compile unit.
func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool) *DebugLineInfo {
func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64, normalizeBackslash bool, ptrSize int) *DebugLineInfo {
dbl := new(DebugLineInfo)
dbl.Logf = logfn
dbl.staticBase = staticBase
dbl.ptrSize = ptrSize
dbl.Lookup = make(map[string]*FileEntry)
dbl.IncludeDirs = append(dbl.IncludeDirs, compdir)
......
......@@ -13,6 +13,7 @@ import (
"strings"
"testing"
"time"
"unsafe"
"github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/pkg/profile"
......@@ -65,10 +66,13 @@ const (
opcodeBaseGo111 uint8 = 11
)
func ptrSizeByRuntimeArch() int {
return int(unsafe.Sizeof(uintptr(0)))
}
func testDebugLinePrologueParser(p string, t *testing.T) {
data := grabDebugLineSection(p, t)
debugLines := ParseAll(data, nil, 0, true)
debugLines := ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
mainFileFound := false
for _, dbl := range debugLines {
......@@ -173,7 +177,7 @@ func BenchmarkLineParser(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ParseAll(data, nil, 0, true)
_ = ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
}
}
......@@ -188,7 +192,7 @@ func loadBenchmarkData(tb testing.TB) DebugLines {
tb.Fatal("Could not read test data", err)
}
return ParseAll(data, nil, 0, true)
return ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
}
func BenchmarkStateMachine(b *testing.B) {
......@@ -196,7 +200,7 @@ func BenchmarkStateMachine(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions)
sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions, ptrSizeByRuntimeArch())
for {
if err := sm.next(); err != nil {
......@@ -223,7 +227,7 @@ func setupTestPCToLine(t testing.TB, lineInfos DebugLines) ([]pctolineEntry, []u
entries := []pctolineEntry{}
basePCs := []uint64{}
sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions)
sm := newStateMachine(lineInfos[0], lineInfos[0].Instructions, ptrSizeByRuntimeArch())
for {
if err := sm.next(); err != nil {
break
......@@ -310,7 +314,7 @@ func TestDebugLineC(t *testing.T) {
t.Fatal("Could not read test data", err)
}
parsed := ParseAll(data, nil, 0, true)
parsed := ParseAll(data, nil, 0, true, ptrSizeByRuntimeArch())
if len(parsed) == 0 {
t.Fatal("Parser result is empty")
......
......@@ -46,6 +46,7 @@ type StateMachine struct {
lastAddress uint64
lastFile string
lastLine int
ptrSize int
}
type opcodeKind uint8
......@@ -102,13 +103,23 @@ var extendedopcodes = map[byte]opcodefn{
DW_LINE_define_file: definefile,
}
func newStateMachine(dbl *DebugLineInfo, instructions []byte) *StateMachine {
func newStateMachine(dbl *DebugLineInfo, instructions []byte, ptrSize int) *StateMachine {
opcodes := make([]opcodefn, len(standardopcodes)+1)
opcodes[0] = execExtendedOpcode
for op := range standardopcodes {
opcodes[op] = standardopcodes[op]
}
sm := &StateMachine{dbl: dbl, file: dbl.FileNames[0].Path, line: 1, buf: bytes.NewBuffer(instructions), opcodes: opcodes, isStmt: dbl.Prologue.InitialIsStmt == uint8(1), address: dbl.staticBase, lastAddress: ^uint64(0)}
sm := &StateMachine{
dbl: dbl,
file: dbl.FileNames[0].Path,
line: 1,
buf: bytes.NewBuffer(instructions),
opcodes: opcodes,
isStmt: dbl.Prologue.InitialIsStmt == uint8(1),
address: dbl.staticBase,
lastAddress: ^uint64(0),
ptrSize: ptrSize,
}
return sm
}
......@@ -121,7 +132,7 @@ func (lineInfo *DebugLineInfo) AllPCsForFileLines(f string, m map[int][]uint64)
var (
lastAddr uint64
sm = newStateMachine(lineInfo, lineInfo.Instructions)
sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
)
for {
......@@ -153,7 +164,7 @@ func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64, excludeFile stri
var (
pcs []uint64
lastaddr uint64
sm = newStateMachine(lineInfo, lineInfo.Instructions)
sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
)
for {
......@@ -189,7 +200,7 @@ func (sm *StateMachine) copy() *StateMachine {
func (lineInfo *DebugLineInfo) stateMachineForEntry(basePC uint64) (sm *StateMachine) {
sm = lineInfo.stateMachineCache[basePC]
if sm == nil {
sm = newStateMachine(lineInfo, lineInfo.Instructions)
sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
sm.PCToLine(basePC)
lineInfo.stateMachineCache[basePC] = sm
}
......@@ -219,7 +230,7 @@ func (lineInfo *DebugLineInfo) PCToLine(basePC, pc uint64) (string, int) {
func (lineInfo *DebugLineInfo) stateMachineFor(basePC, pc uint64) *StateMachine {
var sm *StateMachine
if basePC == 0 {
sm = newStateMachine(lineInfo, lineInfo.Instructions)
sm = newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
} else {
// Try to use the last state machine that we used for this function, if
// there isn't one or it's already past pc try to clone the cached state
......@@ -274,7 +285,7 @@ func (lineInfo *DebugLineInfo) LineToPC(filename string, lineno int) uint64 {
return 0
}
sm := newStateMachine(lineInfo, lineInfo.Instructions)
sm := newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
// if no instruction marked is_stmt is found fallback to the first
// instruction assigned to the filename:line.
......@@ -393,7 +404,7 @@ func (lineInfo *DebugLineInfo) FirstStmtForLine(start, end uint64) (pc uint64, f
}
func (lineInfo *DebugLineInfo) FirstFile() string {
sm := newStateMachine(lineInfo, lineInfo.Instructions)
sm := newStateMachine(lineInfo, lineInfo.Instructions, lineInfo.ptrSize)
for {
if sm.valid {
return sm.file
......@@ -530,11 +541,10 @@ func endsequence(sm *StateMachine, buf *bytes.Buffer) {
}
func setaddress(sm *StateMachine, buf *bytes.Buffer) {
//TODO: this needs to be changed to support 32bit architectures (addr must be target arch pointer sized) -- also target endianness
var addr uint64
binary.Read(buf, binary.LittleEndian, &addr)
addr, err := util.ReadUintRaw(buf, binary.LittleEndian, sm.ptrSize)
if err != nil {
panic(err)
}
sm.address = addr + sm.dbl.staticBase
}
......
......@@ -43,7 +43,6 @@ func TestGrafana(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("filepath.Join ruins this test on windows")
}
debugBytes, err := slurpGzip("_testdata/debug.grafana.debug.gz")
if err != nil {
t.Fatal(err)
......@@ -80,8 +79,8 @@ func TestGrafana(t *testing.T) {
}
cuname, _ := e.Val(dwarf.AttrName).(string)
lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, t.Logf, 0, false)
sm := newStateMachine(lineInfo, lineInfo.Instructions)
lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, t.Logf, 0, false, 8)
sm := newStateMachine(lineInfo, lineInfo.Instructions, 8)
lnrdr, err := data.LineReader(e)
if err != nil {
......@@ -137,13 +136,13 @@ func TestMultipleSequences(t *testing.T) {
const thefile = "thefile.go"
instr := bytes.NewBuffer(nil)
ptrSize := ptrSizeByRuntimeArch()
write_DW_LNE_set_address := func(addr uint64) {
instr.WriteByte(0)
util.EncodeULEB128(instr, 9) // 1 + ptr_size
instr.WriteByte(DW_LINE_set_address)
binary.Write(instr, binary.LittleEndian, addr)
util.WriteUint(instr, binary.LittleEndian, ptrSize, addr)
}
write_DW_LNS_copy := func() {
......@@ -215,6 +214,7 @@ func TestMultipleSequences(t *testing.T) {
IncludeDirs: []string{},
FileNames: []*FileEntry{&FileEntry{Path: thefile}},
Instructions: instr.Bytes(),
ptrSize: ptrSize,
}
// Test that PCToLine is correct for all three sequences
......@@ -235,7 +235,7 @@ func TestMultipleSequences(t *testing.T) {
{0x600002, 12},
{0x600004, 13},
} {
sm := newStateMachine(lines, lines.Instructions)
sm := newStateMachine(lines, lines.Instructions, lines.ptrSize)
file, curline, ok := sm.PCToLine(testCase.pc)
if !ok {
t.Fatalf("Could not find %#x", testCase.pc)
......
......@@ -17,10 +17,11 @@ type Opcode byte
type stackfn func(Opcode, *context) error
type context struct {
buf *bytes.Buffer
stack []int64
pieces []Piece
reg bool
buf *bytes.Buffer
stack []int64
pieces []Piece
reg bool
ptrSize int
DwarfRegisters
}
......@@ -36,11 +37,12 @@ type Piece struct {
// ExecuteStackProgram executes a DWARF location expression and returns
// either an address (int64), or a slice of Pieces for location expressions
// that don't evaluate to an address (such as register and composite expressions).
func ExecuteStackProgram(regs DwarfRegisters, instructions []byte) (int64, []Piece, error) {
func ExecuteStackProgram(regs DwarfRegisters, instructions []byte, ptrSize int) (int64, []Piece, error) {
ctxt := &context{
buf: bytes.NewBuffer(instructions),
stack: make([]int64, 0, 3),
DwarfRegisters: regs,
ptrSize: ptrSize,
}
for {
......@@ -133,7 +135,12 @@ func callframecfa(opcode Opcode, ctxt *context) error {
}
func addr(opcode Opcode, ctxt *context) error {
ctxt.stack = append(ctxt.stack, int64(binary.LittleEndian.Uint64(ctxt.buf.Next(8))+ctxt.StaticBase))
buf := ctxt.buf.Next(ctxt.ptrSize)
stack, err := util.ReadUintRaw(bytes.NewReader(buf), binary.LittleEndian, ctxt.ptrSize)
if err != nil {
return err
}
ctxt.stack = append(ctxt.stack, int64(stack+ctxt.StaticBase))
return nil
}
......
package op
import "testing"
import (
"testing"
"unsafe"
)
func ptrSizeByRuntimeArch() int {
return int(unsafe.Sizeof(uintptr(0)))
}
func TestExecuteStackProgram(t *testing.T) {
var (
instructions = []byte{byte(DW_OP_consts), 0x1c, byte(DW_OP_consts), 0x1c, byte(DW_OP_plus)}
expected = int64(56)
)
actual, _, err := ExecuteStackProgram(DwarfRegisters{}, instructions)
actual, _, err := ExecuteStackProgram(DwarfRegisters{}, instructions, ptrSizeByRuntimeArch())
if err != nil {
t.Fatal(err)
}
......
......@@ -64,7 +64,7 @@ func (reader *Reader) SeekToFunction(pc RelAddr) (*dwarf.Entry, error) {
}
// Returns the address for the named entry.
func (reader *Reader) AddrFor(name string, staticBase uint64) (uint64, error) {
func (reader *Reader) AddrFor(name string, staticBase uint64, ptrSize int) (uint64, error) {
entry, err := reader.FindEntryNamed(name, false)
if err != nil {
return 0, err
......@@ -73,7 +73,7 @@ func (reader *Reader) AddrFor(name string, staticBase uint64) (uint64, error) {
if !ok {
return 0, fmt.Errorf("type assertion failed")
}
addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{StaticBase: staticBase}, instructions)
addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{StaticBase: staticBase}, instructions, ptrSize)
if err != nil {
return 0, err
}
......@@ -82,7 +82,7 @@ func (reader *Reader) AddrFor(name string, staticBase uint64) (uint64, error) {
// Returns the address for the named struct member. Expects the reader to be at the parent entry
// or one of the parents children, thus does not seek to parent by itself.
func (reader *Reader) AddrForMember(member string, initialInstructions []byte) (uint64, error) {
func (reader *Reader) AddrForMember(member string, initialInstructions []byte, ptrSize int) (uint64, error) {
for {
entry, err := reader.NextMemberVariable()
if err != nil {
......@@ -99,7 +99,7 @@ func (reader *Reader) AddrForMember(member string, initialInstructions []byte) (
if !ok {
continue
}
addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{}, append(initialInstructions, instructions...))
addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{}, append(initialInstructions, instructions...), ptrSize)
return uint64(addr), err
}
}
......
......@@ -2,6 +2,8 @@ package util
import (
"bytes"
"encoding/binary"
"fmt"
"io"
)
......@@ -124,3 +126,33 @@ func ParseString(data *bytes.Buffer) (string, uint32) {
return str[:len(str)-1], uint32(len(str))
}
// ReadUintRaw reads an integer of ptrSize bytes, with the specified byte order, from reader.
func ReadUintRaw(reader io.Reader, order binary.ByteOrder, ptrSize int) (uint64, error) {
switch ptrSize {
case 4:
var n uint32
if err := binary.Read(reader, order, &n); err != nil {
return 0, err
}
return uint64(n), nil
case 8:
var n uint64
if err := binary.Read(reader, order, &n); err != nil {
return 0, err
}
return n, nil
}
return 0, fmt.Errorf("not supprted ptr size %d", ptrSize)
}
// WriteUint writes an integer of ptrSize bytes to writer, in the specified byte order.
func WriteUint(writer io.Writer, order binary.ByteOrder, ptrSize int, data uint64) error {
switch ptrSize {
case 4:
return binary.Write(writer, order, uint32(data))
case 8:
return binary.Write(writer, order, data)
}
return fmt.Errorf("not support prt size %d", ptrSize)
}
......@@ -51,7 +51,7 @@ func (a *AMD64) PtrSize() int {
return 8
}
// MaxInstructionLength returns the maximum lenght of an instruction.
// MaxInstructionLength returns the maximum length of an instruction.
func (a *AMD64) MaxInstructionLength() int {
return 15
}
......@@ -558,3 +558,9 @@ func formatX87Reg(b []byte) string {
return fmt.Sprintf("%#04x%016x\t%g", exponent, mantissa, f)
}
// InhibitStepInto returns whether StepBreakpoint can be set at pc.
// Always return false on amd64.
func (a *AMD64) InhibitStepInto(bi *BinaryInfo, pc uint64) bool {
return false
}
......@@ -5,134 +5,19 @@
package proc
import (
"encoding/binary"
"golang.org/x/arch/x86/x86asm"
)
// 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 {
asmInst.Inst = (*amd64ArchInst)(nil)
asmInst.Size = 1
asmInst.Bytes = mem[:asmInst.Size]
return err
}
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
return x86AsmDecode(asmInst, mem, regs, memrw, bi, 64)
}
func (a *AMD64) Prologues() []opcodeSeq {
return prologuesAMD64
}
// converts PC relative arguments to absolute addresses
func patchPCRelAMD64(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))
}
}
}
type amd64ArchInst x86asm.Inst
func (inst *amd64ArchInst) Text(flavour AssemblyFlavour, pc uint64, symLookup func(uint64) (string, uint64)) string {
if inst == nil {
return "?"
}
var text string
switch flavour {
case GNUFlavour:
text = x86asm.GNUSyntax(x86asm.Inst(*inst), pc, symLookup)
case GoFlavour:
text = x86asm.GoSyntax(x86asm.Inst(*inst), pc, symLookup)
case IntelFlavour:
fallthrough
default:
text = x86asm.IntelSyntax(x86asm.Inst(*inst), pc, symLookup)
}
return text
}
func (inst *amd64ArchInst) OpcodeEquals(op uint64) bool {
if inst == nil {
return false
}
return uint64(inst.Op) == op
}
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
}
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
}
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 := make([]byte, inst.MemBytes)
_, err := mem.ReadMemory(pcbytes, addr)
if err != nil {
return nil
}
pc = binary.LittleEndian.Uint64(pcbytes)
default:
return nil
}
file, line, fn := bininfo.PCToLine(pc)
if fn == nil {
return &Location{PC: pc}
}
return &Location{PC: pc, File: file, Line: line, Fn: fn}
}
// 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
......
......@@ -22,8 +22,10 @@ type Arch interface {
RegistersToDwarfRegisters(uint64, Registers) op.DwarfRegisters
AddrAndStackRegsToDwarfRegisters(uint64, uint64, uint64, uint64, uint64) op.DwarfRegisters
DwarfRegisterToString(int, *op.DwarfRegister) (string, bool, string)
InhibitStepInto(bi *BinaryInfo, pc uint64) bool
}
// crosscall2 is defined in $GOROOT/src/runtime/cgo/asm_amd64.s.
const (
crosscall2SPOffsetBad = 0x8
crosscall2SPOffsetWindows = 0x118
......
......@@ -52,7 +52,7 @@ func (a *ARM64) PtrSize() int {
return 8
}
// MaxInstructionLength returns the maximum lenght of an instruction.
// MaxInstructionLength returns the maximum length of an instruction.
func (a *ARM64) MaxInstructionLength() int {
return 4
}
......@@ -465,3 +465,9 @@ func (a *ARM64) DwarfRegisterToString(i int, reg *op.DwarfRegister) (name string
}
return name, false, fmt.Sprintf("%#x", reg.Bytes)
}
// InhibitStepInto returns whether StepBreakpoint can be set at pc.
// Always return false on arm64.
func (a *ARM64) InhibitStepInto(bi *BinaryInfo, pc uint64) bool {
return false
}
......@@ -27,6 +27,7 @@ import (
"github.com/go-delve/delve/pkg/dwarf/loclist"
"github.com/go-delve/delve/pkg/dwarf/op"
"github.com/go-delve/delve/pkg/dwarf/reader"
"github.com/go-delve/delve/pkg/dwarf/util"
"github.com/go-delve/delve/pkg/goversion"
"github.com/go-delve/delve/pkg/logflags"
"github.com/sirupsen/logrus"
......@@ -50,6 +51,9 @@ type BinaryInfo struct {
// LookupFunc maps function names to a description of the function.
LookupFunc map[string]*Function
// SymNames maps addr to a description *elf.Symbol of this addr.
SymNames map[uint64]*elf.Symbol
// Images is a list of loaded shared libraries (also known as
// shared objects on linux or DLLs on windows).
Images []*Image
......@@ -97,15 +101,6 @@ type BinaryInfo struct {
logger *logrus.Entry
}
// ErrUnsupportedLinuxArch is returned when attempting to debug a binary compiled for an unsupported architecture.
var ErrUnsupportedLinuxArch = errors.New("unsupported architecture - only linux/amd64 and linux/arm64 are supported")
// ErrUnsupportedWindowsArch is returned when attempting to debug a binary compiled for an unsupported architecture.
var ErrUnsupportedWindowsArch = errors.New("unsupported architecture of windows/386 - only windows/amd64 is supported")
// ErrUnsupportedDarwinArch is returned when attempting to debug a binary compiled for an unsupported architecture.
var ErrUnsupportedDarwinArch = errors.New("unsupported architecture - only darwin/amd64 is supported")
// ErrCouldNotDetermineRelocation is an error returned when Delve could not determine the base address of a
// position independant executable.
var ErrCouldNotDetermineRelocation = errors.New("could not determine the base address of a PIE")
......@@ -114,6 +109,61 @@ var ErrCouldNotDetermineRelocation = errors.New("could not determine the base ad
// section or find an external debug info file.
var ErrNoDebugInfoFound = errors.New("could not open debug info")
// ErrUnsupportedArch is returned when attempting to debug a binary compiled for an unsupported architecture.
type ErrUnsupportedArch struct {
os string
cpuArch CpuArch
}
type CpuArch interface {
String() string
}
func (e *ErrUnsupportedArch) Error() string {
var supportArchs []CpuArch
switch e.os {
case "linux":
for linuxArch, _ := range supportedLinuxArch {
supportArchs = append(supportArchs, linuxArch)
}
case "windows":
for windowArch, _ := range supportedWindowsArch {
supportArchs = append(supportArchs, windowArch)
}
case "darwin":
for darwinArch, _ := range supportedDarwinArch {
supportArchs = append(supportArchs, darwinArch)
}
}
errStr := "unsupported architecture of " + e.os + "/" + e.cpuArch.String()
errStr += " - only"
for _, arch := range supportArchs {
errStr += " " + e.os + "/" + arch.String() + " "
}
if len(supportArchs) == 1 {
errStr += "is supported"
} else {
errStr += "are supported"
}
return errStr
}
var supportedLinuxArch = map[elf.Machine]bool{
elf.EM_X86_64: true,
elf.EM_AARCH64: true,
elf.EM_386: true,
}
var supportedWindowsArch = map[PEMachine]bool{
IMAGE_FILE_MACHINE_AMD64: true,
}
var supportedDarwinArch = map[macho.Cpu]bool{
macho.CpuAmd64: true,
}
const dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231)
type compileUnit struct {
......@@ -260,12 +310,13 @@ func NewBinaryInfo(goos, goarch string) *BinaryInfo {
// TODO: find better way to determine proc arch (perhaps use executable file info).
switch goarch {
case "386":
r.Arch = I386Arch(goos)
case "amd64":
r.Arch = AMD64Arch(goos)
case "arm64":
r.Arch = ARM64Arch(goos)
}
return r
}
......@@ -614,7 +665,7 @@ func (bi *BinaryInfo) LoadImageFromData(dwdata *dwarf.Data, debugFrameBytes, deb
image.typeCache = make(map[dwarf.Offset]godwarf.Type)
if debugFrameBytes != nil {
bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugFrameBytes), 0)
bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugFrameBytes), 0, bi.Arch.PtrSize())
}
image.loclist = loclist.New(debugLocBytes, bi.Arch.PtrSize())
......@@ -697,7 +748,7 @@ func (bi *BinaryInfo) Location(entry reader.Entry, attr dwarf.Attr, pc uint64, r
if err != nil {
return 0, nil, "", err
}
addr, pieces, err := op.ExecuteStackProgram(regs, instr)
addr, pieces, err := op.ExecuteStackProgram(regs, instr, bi.Arch.PtrSize())
return addr, pieces, descr, err
}
......@@ -827,9 +878,9 @@ func (bi *BinaryInfo) openSeparateDebugInfo(image *Image, exe *elf.File, debugIn
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugFilePath, err.Error())
}
if elfFile.Machine != elf.EM_X86_64 && elfFile.Machine != elf.EM_AARCH64 {
if !supportedLinuxArch[elfFile.Machine] {
sepFile.Close()
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugFilePath, ErrUnsupportedLinuxArch.Error())
return nil, nil, fmt.Errorf("can't open separate debug file %q: %v", debugFilePath, &ErrUnsupportedArch{os: "linux", cpuArch: elfFile.Machine})
}
return sepFile, elfFile, nil
......@@ -875,8 +926,8 @@ func loadBinaryInfoElf(bi *BinaryInfo, image *Image, path string, addr uint64, w
if err != nil {
return err
}
if elfFile.Machine != elf.EM_X86_64 && elfFile.Machine != elf.EM_AARCH64 {
return ErrUnsupportedLinuxArch
if !supportedLinuxArch[elfFile.Machine] {
return &ErrUnsupportedArch{os: "linux", cpuArch: elfFile.Machine}
}
if image.index == 0 {
......@@ -924,9 +975,10 @@ func loadBinaryInfoElf(bi *BinaryInfo, image *Image, path string, addr uint64, w
debugLocBytes, _ := godwarf.GetDebugSectionElf(dwarfFile, "loc")
image.loclist = loclist.New(debugLocBytes, bi.Arch.PtrSize())
wg.Add(2)
wg.Add(3)
go bi.parseDebugFrameElf(image, dwarfFile, wg)
go bi.loadDebugInfoMaps(image, debugLineBytes, wg, nil)
go bi.loadSymbolName(image, elfFile, wg)
if image.index == 0 {
// determine g struct offset only when loading the executable file
wg.Add(1)
......@@ -935,6 +987,25 @@ func loadBinaryInfoElf(bi *BinaryInfo, image *Image, path string, addr uint64, w
return nil
}
// STT_FUNC is a code object, see /usr/include/elf.h for a full definition.
const STT_FUNC = 2
func (bi *BinaryInfo) loadSymbolName(image *Image, file *elf.File, wg *sync.WaitGroup) {
defer wg.Done()
if bi.SymNames == nil {
bi.SymNames = make(map[uint64]*elf.Symbol)
}
symSecs, _ := file.Symbols()
if symSecs != nil {
for _, symSec := range symSecs {
if symSec.Info == STT_FUNC { // TODO(chainhelen), need to parse others types.
s := symSec
bi.SymNames[symSec.Value+image.StaticBase] = &s
}
}
}
}
func (bi *BinaryInfo) parseDebugFrameElf(image *Image, exe *elf.File, wg *sync.WaitGroup) {
defer wg.Done()
......@@ -949,7 +1020,7 @@ func (bi *BinaryInfo) parseDebugFrameElf(image *Image, exe *elf.File, wg *sync.W
return
}
bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameData, frame.DwarfEndian(debugInfoData), image.StaticBase))
bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameData, frame.DwarfEndian(debugInfoData), image.StaticBase, bi.Arch.PtrSize()))
}
func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync.WaitGroup) {
......@@ -957,7 +1028,9 @@ func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync.
// This is a bit arcane. Essentially:
// - If the program is pure Go, it can do whatever it wants, and puts the G
// pointer at %fs-8.
// pointer at %fs-8 on 64 bit.
// - %Gs is the index of private storage in GDT on 32 bit, and puts the G
// pointer at -4(tls).
// - Otherwise, Go asks the external linker to place the G pointer by
// emitting runtime.tlsg, a TLS symbol, which is relocated to the chosen
// offset in libc's TLS block.
......@@ -974,10 +1047,6 @@ func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync.
break
}
}
if tlsg == nil {
bi.gStructOffset = ^uint64(8) + 1 // -8
return
}
var tls *elf.Prog
for _, prog := range exe.Progs {
if prog.Type == elf.PT_TLS {
......@@ -985,8 +1054,8 @@ func (bi *BinaryInfo) setGStructOffsetElf(image *Image, exe *elf.File, wg *sync.
break
}
}
if tls == nil {
bi.gStructOffset = ^uint64(8) + 1 // -8
if tlsg == nil || tls == nil {
bi.gStructOffset = ^uint64(bi.Arch.PtrSize()) + 1 //-ptrSize
return
}
......@@ -1012,8 +1081,9 @@ func loadBinaryInfoPE(bi *BinaryInfo, image *Image, path string, entryPoint uint
return err
}
image.closer = closer
if peFile.Machine != pe.IMAGE_FILE_MACHINE_AMD64 {
return ErrUnsupportedWindowsArch
cpuArch := PEMachine(peFile.Machine)
if !supportedWindowsArch[cpuArch] {
return &ErrUnsupportedArch{os: "windows", cpuArch: cpuArch}
}
image.dwarf, err = peFile.DWARF()
if err != nil {
......@@ -1078,7 +1148,7 @@ func (bi *BinaryInfo) parseDebugFramePE(image *Image, exe *pe.File, wg *sync.Wai
return
}
bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), image.StaticBase))
bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), image.StaticBase, bi.Arch.PtrSize()))
}
// Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go
......@@ -1107,8 +1177,8 @@ func loadBinaryInfoMacho(bi *BinaryInfo, image *Image, path string, entryPoint u
return err
}
image.closer = exe
if exe.Cpu != macho.CpuAmd64 {
return ErrUnsupportedDarwinArch
if !supportedDarwinArch[exe.Cpu] {
return &ErrUnsupportedArch{os: "darwin", cpuArch: exe.Cpu}
}
image.dwarf, err = exe.DWARF()
if err != nil {
......@@ -1156,7 +1226,7 @@ func (bi *BinaryInfo) parseDebugFrameMacho(image *Image, exe *macho.File, wg *sy
return
}
bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), image.StaticBase))
bi.frameEntries = bi.frameEntries.Append(frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), image.StaticBase, bi.Arch.PtrSize()))
}
// Do not call this function directly it isn't able to deal correctly with package paths
......@@ -1327,7 +1397,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(image *Image, debugLineBytes []byte, wg
logger.Printf(fmt, args)
}
}
cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn, image.StaticBase, bi.GOOS == "windows")
cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn, image.StaticBase, bi.GOOS == "windows", bi.Arch.PtrSize())
}
cu.producer, _ = entry.Val(dwarf.AttrProducer).(string)
if cu.isgo && cu.producer != "" {
......@@ -1418,7 +1488,7 @@ func (bi *BinaryInfo) loadDebugInfoMapsCompileUnit(ctxt *loadDebugInfoMapsContex
var addr uint64
if loc, ok := entry.Val(dwarf.AttrLocation).([]byte); ok {
if len(loc) == bi.Arch.PtrSize()+1 && op.Opcode(loc[0]) == op.DW_OP_addr {
addr = binary.LittleEndian.Uint64(loc[1:])
addr, _ = util.ReadUintRaw(bytes.NewReader(loc[1:]), binary.LittleEndian, bi.Arch.PtrSize())
}
}
if !cu.isgo {
......@@ -1741,6 +1811,9 @@ func (bi *BinaryInfo) symLookup(addr uint64) (string, uint64) {
}
return "", 0
}
if sym, ok := bi.SymNames[addr]; ok {
return sym.Name, addr
}
i := sort.Search(len(bi.packageVars), func(i int) bool {
return bi.packageVars[i].addr >= addr
})
......
......@@ -118,24 +118,29 @@ func readLinuxCore(corePath, exePath string) (*Process, error) {
return nil, err
}
memory := buildMemory(coreFile, exeELF, exe, notes)
entryPoint := findEntryPoint(notes)
p := &Process{
mem: memory,
Threads: map[int]*Thread{},
entryPoint: entryPoint,
breakpoints: proc.NewBreakpointMap(),
}
// TODO support 386
var bi *proc.BinaryInfo
switch machineType {
case EM_X86_64:
p.bi = proc.NewBinaryInfo("linux", "amd64")
linuxThreadsFromNotes(p, notes, machineType)
bi = proc.NewBinaryInfo("linux", "amd64")
case EM_AARCH64:
p.bi = proc.NewBinaryInfo("linux", "arm64")
linuxThreadsFromNotes(p, notes, machineType)
bi = proc.NewBinaryInfo("linux", "arm64")
default:
return nil, fmt.Errorf("unsupported machine type")
}
entryPoint := findEntryPoint(notes, bi.Arch.PtrSize())
p := &Process{
mem: memory,
Threads: map[int]*Thread{},
entryPoint: entryPoint,
bi: bi,
breakpoints: proc.NewBreakpointMap(),
}
linuxThreadsFromNotes(p, notes, machineType)
return p, nil
}
......@@ -352,10 +357,10 @@ func buildMemory(core, exeELF *elf.File, exe io.ReaderAt, notes []*Note) proc.Me
return memory
}
func findEntryPoint(notes []*Note) uint64 {
func findEntryPoint(notes []*Note, ptrSize int) uint64 {
for _, note := range notes {
if note.Type == NT_AUXV {
return linutil.EntryPointFromAuxvAMD64(note.Desc.([]byte))
return linutil.EntryPointFromAuxv(note.Desc.([]byte), ptrSize)
}
}
return 0
......
......@@ -11,6 +11,7 @@ import (
"fmt"
"go/constant"
"testing"
"unsafe"
"github.com/go-delve/delve/pkg/dwarf/dwarfbuilder"
"github.com/go-delve/delve/pkg/dwarf/godwarf"
......@@ -19,7 +20,20 @@ import (
"github.com/go-delve/delve/pkg/proc/linutil"
)
const defaultCFA = 0xc420051d00
func ptrSizeByRuntimeArch() int {
return int(unsafe.Sizeof(uintptr(0)))
}
func fakeCFA() uint64 {
ptrSize := ptrSizeByRuntimeArch()
if ptrSize == 8 {
return 0xc420051d00
}
if ptrSize == 4 {
return 0xc4251d00
}
panic(fmt.Errorf("not support ptr size %d", ptrSize))
}
func fakeBinaryInfo(t *testing.T, dwb *dwarfbuilder.Builder) (*proc.BinaryInfo, *dwarf.Data) {
abbrev, aranges, frame, info, line, pubnames, ranges, str, loc, err := dwb.Build()
......@@ -92,8 +106,8 @@ func dwarfRegisters(bi *proc.BinaryInfo, regs *linutil.AMD64Registers) op.DwarfR
a := proc.AMD64Arch("linux")
so := bi.PCToImage(regs.PC())
dwarfRegs := a.RegistersToDwarfRegisters(so.StaticBase, regs)
dwarfRegs.CFA = defaultCFA
dwarfRegs.FrameBase = defaultCFA
dwarfRegs.CFA = int64(fakeCFA())
dwarfRegs.FrameBase = int64(fakeCFA())
return dwarfRegs
}
......@@ -118,8 +132,7 @@ func TestDwarfExprRegisters(t *testing.T) {
bi, _ := fakeBinaryInfo(t, dwb)
mainfn := bi.LookupFunc["main.main"]
mem := newFakeMemory(defaultCFA, uint64(0), uint64(testCases["b"]))
mem := newFakeMemory(fakeCFA(), uint64(0), uint64(testCases["b"]))
regs := linutil.AMD64Registers{Regs: &linutil.AMD64PtraceRegs{}}
regs.Regs.Rax = uint64(testCases["a"])
regs.Regs.Rdx = uint64(testCases["c"])
......@@ -171,11 +184,11 @@ func TestDwarfExprComposite(t *testing.T) {
mainfn := bi.LookupFunc["main.main"]
mem := newFakeMemory(defaultCFA, uint64(0), uint64(0), uint16(testCases["pair.v"]), []byte(stringVal))
mem := newFakeMemory(fakeCFA(), uint64(0), uint64(0), uint16(testCases["pair.v"]), []byte(stringVal))
var regs linutil.AMD64Registers
regs.Regs = &linutil.AMD64PtraceRegs{}
regs.Regs.Rax = uint64(len(stringVal))
regs.Regs.Rdx = defaultCFA + 18
regs.Regs.Rdx = fakeCFA() + 18
regs.Regs.Rcx = uint64(testCases["pair.k"])
regs.Regs.Rbx = uint64(testCases["n"])
......@@ -211,7 +224,7 @@ func TestDwarfExprLoclist(t *testing.T) {
mainfn := bi.LookupFunc["main.main"]
mem := newFakeMemory(defaultCFA, uint16(before), uint16(after))
mem := newFakeMemory(fakeCFA(), uint16(before), uint16(after))
const PC = 0x40100
regs := linutil.AMD64Registers{Regs: &linutil.AMD64PtraceRegs{Rip: PC}}
......@@ -248,7 +261,7 @@ func TestIssue1419(t *testing.T) {
mainfn := bi.LookupFunc["main.main"]
mem := newFakeMemory(defaultCFA)
mem := newFakeMemory(fakeCFA())
scope := &proc.EvalScope{Location: proc.Location{PC: 0x40100, Fn: mainfn}, Regs: op.DwarfRegisters{}, Mem: mem, BinInfo: bi}
......
......@@ -46,7 +46,7 @@ func TestAMD64Get(t *testing.T) {
t.Fatal(err)
}
if eax != 0xDEADBEEF {
t.Fatalf("expected %#v, got %#v\n", 0xdeadbeef, eax)
t.Fatalf("expected %#v, got %#v\n", uint64(0xdeadbeef), eax)
}
// Test RAX, full 64 bits of register
......
......@@ -542,7 +542,7 @@ func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize i
err = fmt.Errorf("could not get argument location of %s: %v", argname, err)
} else {
var pieces []op.Piece
off, pieces, err = op.ExecuteStackProgram(op.DwarfRegisters{CFA: CFA, FrameBase: CFA}, locprog)
off, pieces, err = op.ExecuteStackProgram(op.DwarfRegisters{CFA: CFA, FrameBase: CFA}, locprog, bi.Arch.PtrSize())
if err != nil {
err = fmt.Errorf("unsupported location expression for argument %s: %v", argname, err)
}
......
......@@ -473,7 +473,7 @@ func (p *Process) EntryPoint() (uint64, error) {
// If we can't read the auxiliary vector it just means it's not supported
// by the OS or by the stub. If we are debugging a PIE and the entry point
// is needed proc.LoadBinaryInfo will complain about it.
entryPoint = linutil.EntryPointFromAuxvAMD64(auxv)
entryPoint = linutil.EntryPointFromAuxv(auxv, p.BinInfo().Arch.PtrSize())
}
return entryPoint, nil
}
......
......@@ -13,13 +13,13 @@ func (gcache *goroutineCache) init(bi *BinaryInfo) {
exeimage := bi.Images[0]
rdr := exeimage.DwarfReader()
gcache.allglenAddr, _ = rdr.AddrFor("runtime.allglen", exeimage.StaticBase)
gcache.allglenAddr, _ = rdr.AddrFor("runtime.allglen", exeimage.StaticBase, bi.Arch.PtrSize())
rdr.Seek(0)
gcache.allgentryAddr, err = rdr.AddrFor("runtime.allgs", exeimage.StaticBase)
gcache.allgentryAddr, err = rdr.AddrFor("runtime.allgs", exeimage.StaticBase, bi.Arch.PtrSize())
if err != nil {
// try old name (pre Go 1.6)
gcache.allgentryAddr, _ = rdr.AddrFor("runtime.allg", exeimage.StaticBase)
gcache.allgentryAddr, _ = rdr.AddrFor("runtime.allg", exeimage.StaticBase, bi.Arch.PtrSize())
}
}
......@@ -31,6 +31,7 @@ func (gcache *goroutineCache) getRuntimeAllg(bi *BinaryInfo, mem MemoryReadWrite
if err != nil {
return 0, 0, err
}
allgptr, err := readUintRaw(mem, uintptr(gcache.allgentryAddr), int64(bi.Arch.PtrSize()))
if err != nil {
return 0, 0, err
......
package proc
import (
"encoding/binary"
"fmt"
"github.com/go-delve/delve/pkg/dwarf/frame"
"github.com/go-delve/delve/pkg/dwarf/op"
"strings"
)
// I386 represents the Intel386 CPU architecture.
type I386 struct {
gStructOffset uint64
goos string
// crosscall2fn is the DIE of crosscall2, a function used by the go runtime
// to call C functions. This function in go 1.9 (and previous versions) had
// a bad frame descriptor which needs to be fixed to generate good stack
// traces.
crosscall2fn *Function
// sigreturnfn is the DIE of runtime.sigreturn, the return trampoline for
// the signal handler. See comment in FixFrameUnwindContext for a
// description of why this is needed.
sigreturnfn *Function
}
const (
i386DwarfIPRegNum uint64 = 8
i386DwarfSPRegNum uint64 = 4
i386DwarfBPRegNum uint64 = 5
)
var i386BreakInstruction = []byte{0xCC}
// I386Arch returns an initialized I386Arch
// struct.
func I386Arch(goos string) *I386 {
return &I386{
goos: goos,
}
}
// PtrSize returns the size of a pointer
// on this architecture.
func (i *I386) PtrSize() int {
return 4
}
// MaxInstructionLength returns the maximum length of an instruction.
func (i *I386) MaxInstructionLength() int {
return 15
}
// BreakpointInstruction returns the Breakpoint
// instruction for this architecture.
func (i *I386) BreakpointInstruction() []byte {
return i386BreakInstruction
}
// BreakInstrMovesPC returns whether the
// breakpoint instruction will change the value
// of PC after being executed
func (i *I386) BreakInstrMovesPC() bool {
return true
}
// BreakpointSize returns the size of the
// breakpoint instruction on this architecture.
func (i *I386) BreakpointSize() int {
return len(i386BreakInstruction)
}
// TODO, Not sure, always return false for now. Need to test on windows.
func (i *I386) DerefTLS() bool {
return false
}
// FixFrameUnwindContext adds default architecture rules to fctxt or returns
// the default frame unwind context if fctxt is nil.
func (i *I386) FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext {
if i.sigreturnfn == nil {
i.sigreturnfn = bi.LookupFunc["runtime.sigreturn"]
}
if fctxt == nil || (i.sigreturnfn != nil && pc >= i.sigreturnfn.Entry && pc < i.sigreturnfn.End) {
// When there's no frame descriptor entry use BP (the frame pointer) instead
// - return register is [bp + i.PtrSize()] (i.e. [cfa-i.PtrSize()])
// - cfa is bp + i.PtrSize()*2
// - bp is [bp] (i.e. [cfa-i.PtrSize()*2])
// - sp is cfa
// When the signal handler runs it will move the execution to the signal
// handling stack (installed using the sigaltstack system call).
// This isn't i proper stack switch: the pointer to g in TLS will still
// refer to whatever g was executing on that thread before the signal was
// received.
// Since go did not execute i stack switch the previous value of sp, pc
// and bp is not saved inside g.sched, as it normally would.
// The only way to recover is to either read sp/pc from the signal context
// parameter (the ucontext_t* parameter) or to unconditionally follow the
// frame pointer when we get to runtime.sigreturn (which is what we do
// here).
return &frame.FrameContext{
RetAddrReg: i386DwarfIPRegNum,
Regs: map[uint64]frame.DWRule{
i386DwarfIPRegNum: frame.DWRule{
Rule: frame.RuleOffset,
Offset: int64(-i.PtrSize()),
},
i386DwarfBPRegNum: frame.DWRule{
Rule: frame.RuleOffset,
Offset: int64(-2 * i.PtrSize()),
},
i386DwarfSPRegNum: frame.DWRule{
Rule: frame.RuleValOffset,
Offset: 0,
},
},
CFA: frame.DWRule{
Rule: frame.RuleCFA,
Reg: i386DwarfBPRegNum,
Offset: int64(2 * i.PtrSize()),
},
}
}
if i.crosscall2fn == nil {
i.crosscall2fn = bi.LookupFunc["crosscall2"]
}
// TODO(chainhelen), need to check whether there is a bad frame descriptor like amd64.
// crosscall2 is defined in $GOROOT/src/runtime/cgo/asm_386.s.
if i.crosscall2fn != nil && pc >= i.crosscall2fn.Entry && pc < i.crosscall2fn.End {
rule := fctxt.CFA
fctxt.CFA = rule
}
// We assume that EBP is the frame pointer and we want to keep it updated,
// so that we can use it to unwind the stack even when we encounter frames
// without descriptor entries.
// If there isn't i rule already we emit one.
if fctxt.Regs[i386DwarfBPRegNum].Rule == frame.RuleUndefined {
fctxt.Regs[i386DwarfBPRegNum] = frame.DWRule{
Rule: frame.RuleFramePointer,
Reg: i386DwarfBPRegNum,
Offset: 0,
}
}
return fctxt
}
// SwitchStack will use the current frame to determine if it's time to
// switch between the system stack and the goroutine stack or vice versa.
// Sets it.atend when the top of the stack is reached.
func (i *I386) SwitchStack(it *stackIterator, _ *op.DwarfRegisters) bool {
if it.frame.Current.Fn == nil {
return false
}
switch it.frame.Current.Fn.Name {
case "runtime.asmcgocall", "runtime.cgocallback_gofunc": // TODO(chainhelen), need to support cgo stacktraces.
return false
case "runtime.goexit", "runtime.rt0_go", "runtime.mcall":
// Look for "top of stack" functions.
it.atend = true
return true
case "runtime.mstart":
// Calls to runtime.systemstack will switch to the systemstack then:
// 1. alter the goroutine stack so that it looks like systemstack_switch
// was called
// 2. alter the system stack so that it looks like the bottom-most frame
// belongs to runtime.mstart
// If we find a runtime.mstart frame on the system stack of a goroutine
// parked on runtime.systemstack_switch we assume runtime.systemstack was
// called and continue tracing from the parked position.
if it.top || !it.systemstack || it.g == nil {
return false
}
if fn := it.bi.PCToFunc(it.g.PC); fn == nil || fn.Name != "runtime.systemstack_switch" {
return false
}
it.switchToGoroutineStack()
return true
default:
if it.systemstack && it.top && it.g != nil && strings.HasPrefix(it.frame.Current.Fn.Name, "runtime.") && it.frame.Current.Fn.Name != "runtime.fatalthrow" {
// The runtime switches to the system stack in multiple places.
// This usually happens through a call to runtime.systemstack but there
// are functions that switch to the system stack manually (for example
// runtime.morestack).
// Since we are only interested in printing the system stack for cgo
// calls we switch directly to the goroutine stack if we detect that the
// function at the top of the stack is a runtime function.
//
// The function "runtime.fatalthrow" is deliberately excluded from this
// because it can end up in the stack during a cgo call and switching to
// the goroutine stack will exclude all the C functions from the stack
// trace.
it.switchToGoroutineStack()
return true
}
return false
}
}
// RegSize returns the size (in bytes) of register regnum.
// The mapping between hardware registers and DWARF registers is specified
// in the System V ABI Intel386 Architecture Processor Supplement page 25,
// table 2.14
// https://www.uclibc.org/docs/psABI-i386.pdf
func (i *I386) RegSize(regnum uint64) int {
// XMM registers
if regnum >= 21 && regnum <= 36 {
return 16
}
// x87 registers
if regnum >= 11 && regnum <= 18 {
return 10
}
return 4
}
// The mapping between hardware registers and DWARF registers is specified
// in the System V ABI Intel386 Architecture Processor Supplement page 25,
// table 2.14
// https://www.uclibc.org/docs/psABI-i386.pdf
var i386DwarfToName = map[int]string{
0: "Eax",
1: "Ecx",
2: "Edx",
3: "Ebx",
4: "Esp",
5: "Ebp",
6: "Esi",
7: "Edi",
8: "Eip",
9: "Eflags",
11: "ST(0)",
12: "ST(1)",
13: "ST(2)",
14: "ST(3)",
15: "ST(4)",
16: "ST(5)",
17: "ST(6)",
18: "ST(7)",
21: "XMM0",
22: "XMM1",
23: "XMM2",
24: "XMM3",
25: "XMM4",
26: "XMM5",
27: "XMM6",
28: "XMM7",
40: "Es",
41: "Cs",
42: "Ss",
43: "Ds",
44: "Fs",
45: "Gs",
}
var i386NameToDwarf = func() map[string]int {
r := make(map[string]int)
for regNum, regName := range i386DwarfToName {
r[strings.ToLower(regName)] = regNum
}
r["eflags"] = 9
r["st0"] = 11
r["st1"] = 12
r["st2"] = 13
r["st3"] = 14
r["st4"] = 15
r["st5"] = 16
r["st6"] = 17
r["st7"] = 18
return r
}()
func maxI386DwarfRegister() int {
max := int(i386DwarfIPRegNum)
for i := range i386DwarfToName {
if i > max {
max = i
}
}
return max
}
func (i *I386) RegistersToDwarfRegisters(staticBase uint64, regs Registers) op.DwarfRegisters {
dregs := make([]*op.DwarfRegister, maxI386DwarfRegister()+1)
for _, reg := range regs.Slice(true) {
if dwarfReg, ok := i386NameToDwarf[strings.ToLower(reg.Name)]; ok {
dregs[dwarfReg] = reg.Reg
}
}
return op.DwarfRegisters{
StaticBase: staticBase,
Regs: dregs,
ByteOrder: binary.LittleEndian,
PCRegNum: i386DwarfIPRegNum,
SPRegNum: i386DwarfSPRegNum,
BPRegNum: i386DwarfBPRegNum,
}
}
// AddrAndStackRegsToDwarfRegisters returns DWARF registers from the passed in
// PC, SP, and BP registers in the format used by the DWARF expression interpreter.
func (i *I386) AddrAndStackRegsToDwarfRegisters(staticBase, pc, sp, bp, lr uint64) op.DwarfRegisters {
dregs := make([]*op.DwarfRegister, i386DwarfIPRegNum+1)
dregs[i386DwarfIPRegNum] = op.DwarfRegisterFromUint64(pc)
dregs[i386DwarfSPRegNum] = op.DwarfRegisterFromUint64(sp)
dregs[i386DwarfBPRegNum] = op.DwarfRegisterFromUint64(bp)
return op.DwarfRegisters{
StaticBase: staticBase,
Regs: dregs,
ByteOrder: binary.LittleEndian,
PCRegNum: i386DwarfIPRegNum,
SPRegNum: i386DwarfSPRegNum,
BPRegNum: i386DwarfBPRegNum,
}
}
func (i *I386) DwarfRegisterToString(j int, reg *op.DwarfRegister) (name string, floatingPoint bool, repr string) {
name, ok := i386DwarfToName[j]
if !ok {
name = fmt.Sprintf("unknown%d", j)
}
switch n := strings.ToLower(name); n {
case "eflags":
return name, false, eflagsDescription.Describe(reg.Uint64Val, 32)
case "tw", "fop":
return name, true, fmt.Sprintf("%#04x", reg.Uint64Val)
default:
if reg.Bytes != nil && strings.HasPrefix(name, "xmm") {
return name, true, formatSSEReg(reg.Bytes)
} else if reg.Bytes != nil && strings.HasPrefix(name, "st(") {
return name, true, formatX87Reg(reg.Bytes)
} else if reg.Bytes == nil || (reg.Bytes != nil && len(reg.Bytes) <= 8) {
return name, false, fmt.Sprintf("%#016x", reg.Uint64Val)
} else {
return name, false, fmt.Sprintf("%#x", reg.Bytes)
}
}
}
// InhibitStepInto returns whether StepBreakpoint can be set at pc.
// When cgo or pie on 386 linux, compiler will insert more instructions (ex: call __x86.get_pc_thunk.).
// StepBreakpoint shouldn't be set on __x86.get_pc_thunk and skip it.
// See comments on stacksplit in $GOROOT/src/cmd/internal/obj/x86/obj6.go for generated instructions details.
func (i *I386) InhibitStepInto(bi *BinaryInfo, pc uint64) bool {
if bi.SymNames != nil && bi.SymNames[pc] != nil &&
strings.HasPrefix(bi.SymNames[pc].Name, "__x86.get_pc_thunk.") {
return true
}
return false
}
// TODO: disassembler support should be compiled in unconditionally,
// instead of being decided by the build-target architecture, and be
// part of the Arch object instead.
package proc
import (
"golang.org/x/arch/x86/x86asm"
)
// 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 (i *I386) AsmDecode(asmInst *AsmInstruction, mem []byte, regs Registers, memrw MemoryReadWriter, bi *BinaryInfo) error {
return x86AsmDecode(asmInst, mem, regs, memrw, bi, 32)
}
func (i *I386) Prologues() []opcodeSeq {
return prologuesI386
}
// Possible stacksplit prologues are inserted by stacksplit in
// $GOROOT/src/cmd/internal/obj/x86/obj6.go.
// If 386 on linux when pie, the stacksplit prologue beigin with `call __x86.get_pc_thunk.` sometime.
var prologuesI386 []opcodeSeq
func init() {
var i386GetPcIns = opcodeSeq{uint64(x86asm.CALL)}
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), uint64(x86asm.MOV)}
prologuesI386 = make([]opcodeSeq, 0, 2*3)
for _, getPcIns := range []opcodeSeq{{}, i386GetPcIns} {
for _, getG := range []opcodeSeq{unixGetG} { // TODO(chainhelen), need to support other OSs.
for _, stacksplit := range []opcodeSeq{tinyStacksplit, smallStacksplit, bigStacksplit} {
prologue := make(opcodeSeq, 0, len(getPcIns)+len(getG)+len(stacksplit))
prologue = append(prologue, getPcIns...)
prologue = append(prologue, getG...)
prologue = append(prologue, stacksplit...)
prologuesI386 = append(prologuesI386, prologue)
}
}
}
}
......@@ -6,33 +6,34 @@ import (
)
const (
_AT_NULL_AMD64 = 0
_AT_ENTRY_AMD64 = 9
_AT_NULL = 0
_AT_ENTRY = 9
)
// EntryPointFromAuxv searches the elf auxiliary vector for the entry point
// address.
// For a description of the auxiliary vector (auxv) format see:
// System V Application Binary Interface, AMD64 Architecture Processor
// Supplement, section 3.4.3
func EntryPointFromAuxvAMD64(auxv []byte) uint64 {
// Supplement, section 3.4.3.
// System V Application Binary Interface, Intel386 Architecture Processor
// Supplement (fourth edition), section 3-28.
func EntryPointFromAuxv(auxv []byte, ptrSize int) uint64 {
rd := bytes.NewBuffer(auxv)
for {
var tag, val uint64
err := binary.Read(rd, binary.LittleEndian, &tag)
tag, err := readUintRaw(rd, binary.LittleEndian, ptrSize)
if err != nil {
return 0
}
err = binary.Read(rd, binary.LittleEndian, &val)
val, err := readUintRaw(rd, binary.LittleEndian, ptrSize)
if err != nil {
return 0
}
switch tag {
case _AT_NULL_AMD64:
case _AT_NULL:
return 0
case _AT_ENTRY_AMD64:
case _AT_ENTRY:
return val
}
}
......
......@@ -5,6 +5,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"io"
"github.com/go-delve/delve/pkg/proc"
)
......@@ -21,6 +22,25 @@ const (
_DT_DEBUG = 21 // DT_DEBUG as defined by SysV ABI specification
)
// readUintRaw reads an integer of ptrSize bytes, with the specified byte order, from reader.
func readUintRaw(reader io.Reader, order binary.ByteOrder, ptrSize int) (uint64, error) {
switch ptrSize {
case 4:
var n uint32
if err := binary.Read(reader, order, &n); err != nil {
return 0, err
}
return uint64(n), nil
case 8:
var n uint64
if err := binary.Read(reader, order, &n); err != nil {
return 0, err
}
return n, nil
}
return 0, fmt.Errorf("not supprted ptr size %d", ptrSize)
}
// dynamicSearchDebug searches for the DT_DEBUG entry in the .dynamic section
func dynamicSearchDebug(p proc.Process) (uint64, error) {
bi := p.BinInfo()
......@@ -36,10 +56,10 @@ func dynamicSearchDebug(p proc.Process) (uint64, error) {
for {
var tag, val uint64
if err := binary.Read(rd, binary.LittleEndian, &tag); err != nil {
if tag, err = readUintRaw(rd, binary.LittleEndian, p.BinInfo().Arch.PtrSize()); err != nil {
return 0, err
}
if err := binary.Read(rd, binary.LittleEndian, &val); err != nil {
if val, err = readUintRaw(rd, binary.LittleEndian, p.BinInfo().Arch.PtrSize()); err != nil {
return 0, err
}
switch tag {
......@@ -51,24 +71,13 @@ func dynamicSearchDebug(p proc.Process) (uint64, error) {
}
}
// hard-coded offsets of the fields of the r_debug and link_map structs, see
// /usr/include/elf/link.h for a full description of those structs.
const (
_R_DEBUG_MAP_OFFSET = 8
_LINK_MAP_ADDR_OFFSET = 0 // offset of link_map.l_addr field (base address shared object is loaded at)
_LINK_MAP_NAME_OFFSET = 8 // offset of link_map.l_name field (absolute file name object was found in)
_LINK_MAP_LD = 16 // offset of link_map.l_ld field (dynamic section of the shared object)
_LINK_MAP_NEXT = 24 // offset of link_map.l_next field
_LINK_MAP_PREV = 32 // offset of link_map.l_prev field
)
func readPtr(p proc.Process, addr uint64) (uint64, error) {
ptrbuf := make([]byte, p.BinInfo().Arch.PtrSize())
_, err := p.CurrentThread().ReadMemory(ptrbuf, uintptr(addr))
if err != nil {
return 0, err
}
return binary.LittleEndian.Uint64(ptrbuf), nil
return readUintRaw(bytes.NewReader(ptrbuf), binary.LittleEndian, p.BinInfo().Arch.PtrSize())
}
type linkMap struct {
......@@ -145,7 +154,11 @@ func ElfUpdateSharedObjects(p proc.Process) error {
return nil
}
r_map, err := readPtr(p, debugAddr+_R_DEBUG_MAP_OFFSET)
// Offsets of the fields of the r_debug and link_map structs,
// see /usr/include/elf/link.h for a full description of those structs.
debugMapOffset := uint64(p.BinInfo().Arch.PtrSize())
r_map, err := readPtr(p, debugAddr+debugMapOffset)
if err != nil {
return err
}
......
package linutil
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/go-delve/delve/pkg/proc"
"golang.org/x/arch/x86/x86asm"
)
// I386Registers implements the proc.Registers interface for the native/linux
// backend and core/linux backends, on I386.
type I386Registers struct {
Regs *I386PtraceRegs
Fpregs []proc.Register
Fpregset *I386Xstate
Tls uint64
}
// I386PtraceRegs is the struct used by the linux kernel to return the
// general purpose registers for I386 CPUs.
type I386PtraceRegs struct {
Ebx int32
Ecx int32
Edx int32
Esi int32
Edi int32
Ebp int32
Eax int32
Xds int32
Xes int32
Xfs int32
Xgs int32
Orig_eax int32
Eip int32
Xcs int32
Eflags int32
Esp int32
Xss int32
}
// Slice returns the registers as a list of (name, value) pairs.
func (r *I386Registers) Slice(floatingPoint bool) []proc.Register {
var regs = []struct {
k string
v int32
}{
{"Ebx", r.Regs.Ebx},
{"Ecx", r.Regs.Ecx},
{"Edx", r.Regs.Edx},
{"Esi", r.Regs.Esi},
{"Edi", r.Regs.Edi},
{"Ebp", r.Regs.Ebp},
{"Eax", r.Regs.Eax},
{"Xds", r.Regs.Xds},
{"Xes", r.Regs.Xes},
{"Xfs", r.Regs.Xfs},
{"Xgs", r.Regs.Xgs},
{"Orig_eax", r.Regs.Orig_eax},
{"Eip", r.Regs.Eip},
{"Xcs", r.Regs.Xcs},
{"Eflags", r.Regs.Eflags},
{"Esp", r.Regs.Esp},
{"Xss", r.Regs.Xss},
}
out := make([]proc.Register, 0, len(regs)+len(r.Fpregs))
for _, reg := range regs {
out = proc.AppendUint64Register(out, reg.k, uint64(uint32(reg.v)))
}
if floatingPoint {
out = append(out, r.Fpregs...)
}
return out
}
// PC returns the value of EIP register.
func (r *I386Registers) PC() uint64 {
return uint64(uint32(r.Regs.Eip))
}
// SP returns the value of ESP register.
func (r *I386Registers) SP() uint64 {
return uint64(uint32(r.Regs.Esp))
}
func (r *I386Registers) BP() uint64 {
return uint64(uint32(r.Regs.Ebp))
}
// CX returns the value of ECX register.
func (r *I386Registers) CX() uint64 {
return uint64(uint32(r.Regs.Ecx))
}
// TLS returns the address of the thread local storage memory segment.
func (r I386Registers) TLS() uint64 {
return r.Tls
}
// GAddr returns the address of the G variable if it is known, 0 and false
// otherwise.
func (r *I386Registers) GAddr() (uint64, bool) {
return 0, false
}
// Get returns the value of the n-th register (in x86asm order).
func (r *I386Registers) Get(n int) (uint64, error) {
reg := x86asm.Reg(n)
const (
mask8 = 0x000000ff
mask16 = 0x0000ffff
)
switch reg {
// 8-bit
case x86asm.AL:
return uint64(r.Regs.Eax) & mask8, nil
case x86asm.CL:
return uint64(r.Regs.Ecx) & mask8, nil
case x86asm.DL:
return uint64(r.Regs.Edx) & mask8, nil
case x86asm.BL:
return uint64(r.Regs.Ebx) & mask8, nil
case x86asm.AH:
return (uint64(r.Regs.Eax) >> 8) & mask8, nil
case x86asm.CH:
return (uint64(r.Regs.Ecx) >> 8) & mask8, nil
case x86asm.DH:
return (uint64(r.Regs.Edx) >> 8) & mask8, nil
case x86asm.BH:
return (uint64(r.Regs.Ebx) >> 8) & mask8, nil
case x86asm.SPB:
return uint64(r.Regs.Esp) & mask8, nil
case x86asm.BPB:
return uint64(r.Regs.Ebp) & mask8, nil
case x86asm.SIB:
return uint64(r.Regs.Esi) & mask8, nil
case x86asm.DIB:
return uint64(r.Regs.Edi) & mask8, nil
// 16-bit
case x86asm.AX:
return uint64(r.Regs.Eax) & mask16, nil
case x86asm.CX:
return uint64(r.Regs.Ecx) & mask16, nil
case x86asm.DX:
return uint64(r.Regs.Edx) & mask16, nil
case x86asm.BX:
return uint64(r.Regs.Ebx) & mask16, nil
case x86asm.SP:
return uint64(r.Regs.Esp) & mask16, nil
case x86asm.BP:
return uint64(r.Regs.Ebp) & mask16, nil
case x86asm.SI:
return uint64(r.Regs.Esi) & mask16, nil
case x86asm.DI:
return uint64(r.Regs.Edi) & mask16, nil
// 32-bit
case x86asm.EAX:
return uint64(uint32(r.Regs.Eax)), nil
case x86asm.ECX:
return uint64(uint32(r.Regs.Ecx)), nil
case x86asm.EDX:
return uint64(uint32(r.Regs.Edx)), nil
case x86asm.EBX:
return uint64(uint32(r.Regs.Ebx)), nil
case x86asm.ESP:
return uint64(uint32(r.Regs.Esp)), nil
case x86asm.EBP:
return uint64(uint32(r.Regs.Ebp)), nil
case x86asm.ESI:
return uint64(uint32(r.Regs.Esi)), nil
case x86asm.EDI:
return uint64(uint32(r.Regs.Edi)), nil
}
return 0, proc.ErrUnknownRegister
}
// Copy returns a copy of these registers that is guarenteed not to change.
func (r *I386Registers) Copy() proc.Registers {
var rr I386Registers
rr.Regs = &I386PtraceRegs{}
rr.Fpregset = &I386Xstate{}
*(rr.Regs) = *(r.Regs)
if r.Fpregset != nil {
*(rr.Fpregset) = *(r.Fpregset)
}
if r.Fpregs != nil {
rr.Fpregs = make([]proc.Register, len(r.Fpregs))
copy(rr.Fpregs, r.Fpregs)
}
return &rr
}
// I386PtraceFpRegs tracks user_fpregs_struct in /usr/include/x86_64-linux-gnu/sys/user.h
type I386PtraceFpRegs struct {
Cwd uint16
Swd uint16
Ftw uint16
Fop uint16
Rip uint64
Rdp uint64
Mxcsr uint32
MxcrMask uint32
StSpace [32]uint32
XmmSpace [256]byte
Padding [24]uint32
}
// I386Xstate represents amd64 XSAVE area. See Section 13.1 (and
// following) of Intel® 64 and IA-32 Architectures Software Developer’s
// Manual, Volume 1: Basic Architecture.
type I386Xstate struct {
I386PtraceFpRegs
Xsave []byte // raw xsave area
AvxState bool // contains AVX state
YmmSpace [256]byte
}
// Decode decodes an XSAVE area to a list of name/value pairs of registers.
func (xsave *I386Xstate) Decode() (regs []proc.Register) {
// x87 registers
regs = proc.AppendUint64Register(regs, "CW", uint64(xsave.Cwd))
regs = proc.AppendUint64Register(regs, "SW", uint64(xsave.Swd))
regs = proc.AppendUint64Register(regs, "TW", uint64(xsave.Ftw))
regs = proc.AppendUint64Register(regs, "FOP", uint64(xsave.Fop))
regs = proc.AppendUint64Register(regs, "FIP", uint64(xsave.Rip))
regs = proc.AppendUint64Register(regs, "FDP", uint64(xsave.Rdp))
for i := 0; i < len(xsave.StSpace); i += 4 {
var buf bytes.Buffer
binary.Write(&buf, binary.LittleEndian, uint64(xsave.StSpace[i+1])<<32|uint64(xsave.StSpace[i]))
binary.Write(&buf, binary.LittleEndian, uint16(xsave.StSpace[i+2]))
regs = proc.AppendBytesRegister(regs, fmt.Sprintf("ST(%d)", i/4), buf.Bytes())
}
// SSE registers
regs = proc.AppendUint64Register(regs, "MXCSR", uint64(xsave.Mxcsr))
regs = proc.AppendUint64Register(regs, "MXCSR_MASK", uint64(xsave.MxcrMask))
for i := 0; i < len(xsave.XmmSpace); i += 16 {
regs = proc.AppendBytesRegister(regs, fmt.Sprintf("XMM%d", i/16), xsave.XmmSpace[i:i+16])
if xsave.AvxState {
regs = proc.AppendBytesRegister(regs, fmt.Sprintf("YMM%d", i/16), xsave.YmmSpace[i:i+16])
}
}
return
}
// LinuxX86XstateRead reads a byte array containing an XSAVE area into regset.
// If readLegacy is true regset.PtraceFpRegs will be filled with the
// contents of the legacy region of the XSAVE area.
// See Section 13.1 (and following) of Intel® 64 and IA-32 Architectures
// Software Developer’s Manual, Volume 1: Basic Architecture.
func I386XstateRead(xstateargs []byte, readLegacy bool, regset *I386Xstate) error {
if _XSAVE_HEADER_START+_XSAVE_HEADER_LEN >= len(xstateargs) {
return nil
}
if readLegacy {
rdr := bytes.NewReader(xstateargs[:_XSAVE_HEADER_START])
if err := binary.Read(rdr, binary.LittleEndian, &regset.I386PtraceFpRegs); err != nil {
return err
}
}
xsaveheader := xstateargs[_XSAVE_HEADER_START : _XSAVE_HEADER_START+_XSAVE_HEADER_LEN]
xstate_bv := binary.LittleEndian.Uint64(xsaveheader[0:8])
xcomp_bv := binary.LittleEndian.Uint64(xsaveheader[8:16])
if xcomp_bv&(1<<63) != 0 {
// compact format not supported
return nil
}
if xstate_bv&(1<<2) == 0 {
// AVX state not present
return nil
}
avxstate := xstateargs[_XSAVE_EXTENDED_REGION_START:]
regset.AvxState = true
copy(regset.YmmSpace[:], avxstate[:len(regset.YmmSpace)])
return nil
}
......@@ -46,7 +46,7 @@ func TestAMD64Get(t *testing.T) {
t.Fatal(err)
}
if eax != 0xDEADBEEF {
t.Fatalf("expected %#v, got %#v\n", 0xdeadbeef, eax)
t.Fatalf("expected %#v, got %#v\n", uint64(0xdeadbeef), eax)
}
// Test RAX, full 64 bits of register
......
......@@ -545,7 +545,7 @@ func (dbp *Process) EntryPoint() (uint64, error) {
return 0, fmt.Errorf("could not read auxiliary vector: %v", err)
}
return linutil.EntryPointFromAuxvAMD64(auxvbuf), nil
return linutil.EntryPointFromAuxv(auxvbuf, dbp.bi.Arch.PtrSize()), nil
}
func killProcess(pid int) error {
......
......@@ -49,31 +49,3 @@ func PtracePeekUser(tid int, off uintptr) (uintptr, error) {
}
return val, nil
}
// ProcessVmRead calls process_vm_readv
func ProcessVmRead(tid int, addr uintptr, data []byte) (int, error) {
len_iov := uint64(len(data))
local_iov := sys.Iovec{Base: &data[0], Len: len_iov}
remote_iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(addr)), Len: len_iov}
p_local := uintptr(unsafe.Pointer(&local_iov))
p_remote := uintptr(unsafe.Pointer(&remote_iov))
n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_READV, uintptr(tid), p_local, 1, p_remote, 1, 0)
if err != syscall.Errno(0) {
return 0, err
}
return int(n), nil
}
// ProcessVmWrite calls process_vm_writev
func ProcessVmWrite(tid int, addr uintptr, data []byte) (int, error) {
len_iov := uint64(len(data))
local_iov := sys.Iovec{Base: &data[0], Len: len_iov}
remote_iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(addr)), Len: len_iov}
p_local := uintptr(unsafe.Pointer(&local_iov))
p_remote := uintptr(unsafe.Pointer(&remote_iov))
n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_WRITEV, uintptr(tid), p_local, 1, p_remote, 1, 0)
if err != syscall.Errno(0) {
return 0, err
}
return int(n), nil
}
package native
import "C"
import (
"fmt"
"syscall"
"unsafe"
sys "golang.org/x/sys/unix"
"github.com/go-delve/delve/pkg/proc/linutil"
)
// PtraceGetRegset returns floating point registers of the specified thread
// using PTRACE.
// See i386_linux_fetch_inferior_registers in gdb/i386-linux-nat.c.html
// and i386_supply_xsave in gdb/i386-tdep.c.html
// and Section 13.1 (and following) of Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 1: Basic Architecture
func PtraceGetRegset(tid int) (regset linutil.I386Xstate, err error) {
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETFPREGS, uintptr(tid), uintptr(0), uintptr(unsafe.Pointer(&regset.I386PtraceFpRegs)), 0, 0)
if err == syscall.Errno(0) || err == syscall.ENODEV {
// ignore ENODEV, it just means this CPU doesn't have X87 registers (??)
err = nil
}
var xstateargs [_X86_XSTATE_MAX_SIZE]byte
iov := sys.Iovec{Base: &xstateargs[0], Len: _X86_XSTATE_MAX_SIZE}
_, _, err = syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GETREGSET, uintptr(tid), _NT_X86_XSTATE, uintptr(unsafe.Pointer(&iov)), 0, 0)
if err != syscall.Errno(0) {
if err == syscall.ENODEV || err == syscall.EIO {
// ignore ENODEV, it just means this CPU or kernel doesn't support XSTATE, see https://github.com/go-delve/delve/issues/1022
// also ignore EIO, it means that we are running on an old kernel (pre 2.6.34) and PTRACE_GETREGSET is not implemented
err = nil
}
return
} else {
err = nil
}
regset.Xsave = xstateargs[:iov.Len]
err = linutil.I386XstateRead(regset.Xsave, false, &regset)
return
}
// PtraceGetTls return the addr of tls by PTRACE_GET_THREAD_AREA for specify thread.
// See http://man7.org/linux/man-pages/man2/ptrace.2.html for detail about PTRACE_GET_THREAD_AREA.
// struct user_desc at https://golang.org/src/runtime/sys_linux_386.s
// type UserDesc struct {
// EntryNumber uint32
// BaseAddr uint32
// Limit uint32
// Flag uint32
// }
func PtraceGetTls(gs int32, tid int) (uint32, error) {
ud := [4]uint32{}
// Gs usually is 0x33
_, _, err := syscall.Syscall6(syscall.SYS_PTRACE, sys.PTRACE_GET_THREAD_AREA, uintptr(tid), uintptr(gs>>3), uintptr(unsafe.Pointer(&ud)), 0, 0)
if err == syscall.ENODEV || err == syscall.EIO {
return 0, fmt.Errorf("%s", err)
}
return uint32(ud[1]), nil
}
// ProcessVmRead calls process_vm_readv
func ProcessVmRead(tid int, addr uintptr, data []byte) (int, error) {
len_iov := uint32(len(data))
local_iov := sys.Iovec{Base: &data[0], Len: len_iov}
remote_iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(addr)), Len: len_iov}
p_local := uintptr(unsafe.Pointer(&local_iov))
p_remote := uintptr(unsafe.Pointer(&remote_iov))
n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_READV, uintptr(tid), p_local, 1, p_remote, 1, 0)
if err != syscall.Errno(0) {
return 0, err
}
return int(n), nil
}
// ProcessVmWrite calls process_vm_writev
func ProcessVmWrite(tid int, addr uintptr, data []byte) (int, error) {
len_iov := uint32(len(data))
local_iov := sys.Iovec{Base: &data[0], Len: len_iov}
remote_iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(addr)), Len: len_iov}
p_local := uintptr(unsafe.Pointer(&local_iov))
p_remote := uintptr(unsafe.Pointer(&remote_iov))
n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_WRITEV, uintptr(tid), p_local, 1, p_remote, 1, 0)
if err != syscall.Errno(0) {
return 0, err
}
return int(n), nil
}
// +build linux,amd64 linux,arm64
package native
import (
"syscall"
"unsafe"
sys "golang.org/x/sys/unix"
)
// ProcessVmRead calls process_vm_readv
func ProcessVmRead(tid int, addr uintptr, data []byte) (int, error) {
len_iov := uint64(len(data))
local_iov := sys.Iovec{Base: &data[0], Len: len_iov}
remote_iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(addr)), Len: len_iov}
p_local := uintptr(unsafe.Pointer(&local_iov))
p_remote := uintptr(unsafe.Pointer(&remote_iov))
n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_READV, uintptr(tid), p_local, 1, p_remote, 1, 0)
if err != syscall.Errno(0) {
return 0, err
}
return int(n), nil
}
// ProcessVmWrite calls process_vm_writev
func ProcessVmWrite(tid int, addr uintptr, data []byte) (int, error) {
len_iov := uint64(len(data))
local_iov := sys.Iovec{Base: &data[0], Len: len_iov}
remote_iov := sys.Iovec{Base: (*byte)(unsafe.Pointer(addr)), Len: len_iov}
p_local := uintptr(unsafe.Pointer(&local_iov))
p_remote := uintptr(unsafe.Pointer(&remote_iov))
n, _, err := syscall.Syscall6(sys.SYS_PROCESS_VM_WRITEV, uintptr(tid), p_local, 1, p_remote, 1, 0)
if err != syscall.Errno(0) {
return 0, err
}
return int(n), nil
}
......@@ -38,4 +38,4 @@ func PtraceGetRegset(tid int) (regset linutil.AMD64Xstate, err error) {
regset.Xsave = xstateargs[:iov.Len]
err = linutil.AMD64XstateRead(regset.Xsave, false, &regset)
return
}
}
\ No newline at end of file
package native
import (
"fmt"
sys "golang.org/x/sys/unix"
"github.com/go-delve/delve/pkg/proc"
"github.com/go-delve/delve/pkg/proc/linutil"
)
// SetPC sets EIP to the value specified by 'pc'.
func (thread *Thread) SetPC(pc uint64) error {
ir, err := registers(thread, false)
if err != nil {
return err
}
r := ir.(*linutil.I386Registers)
r.Regs.Eip = int32(pc)
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.PtraceRegs)(r.Regs)) })
return err
}
// SetSP sets ESP to the value specified by 'sp'
func (thread *Thread) SetSP(sp uint64) (err error) {
var ir proc.Registers
ir, err = registers(thread, false)
if err != nil {
return err
}
r := ir.(*linutil.I386Registers)
r.Regs.Esp = int32(sp)
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.PtraceRegs)(r.Regs)) })
return
}
func (thread *Thread) SetDX(dx uint64) (err error) {
var ir proc.Registers
ir, err = registers(thread, false)
if err != nil {
return err
}
r := ir.(*linutil.I386Registers)
r.Regs.Edx = int32(dx)
thread.dbp.execPtraceFunc(func() { err = sys.PtraceSetRegs(thread.ID, (*sys.PtraceRegs)(r.Regs)) })
return
}
func registers(thread *Thread, floatingPoint bool) (proc.Registers, error) {
var (
regs linutil.I386PtraceRegs
err error
)
thread.dbp.execPtraceFunc(func() { err = sys.PtraceGetRegs(thread.ID, (*sys.PtraceRegs)(&regs)) })
if err != nil {
return nil, err
}
r := &linutil.I386Registers{&regs, nil, nil, 0}
if floatingPoint {
var fpregset linutil.I386Xstate
r.Fpregs, fpregset, err = thread.fpRegisters()
r.Fpregset = &fpregset
if err != nil {
return nil, err
}
}
thread.dbp.execPtraceFunc(func() {
tls, _ := PtraceGetTls(regs.Xgs, thread.ThreadID())
r.Tls = uint64(tls)
})
return r, nil
}
const (
_X86_XSTATE_MAX_SIZE = 2688
_NT_X86_XSTATE = 0x202
_XSAVE_HEADER_START = 512
_XSAVE_HEADER_LEN = 64
_XSAVE_EXTENDED_REGION_START = 576
_XSAVE_SSE_REGION_LEN = 416
)
func (thread *Thread) fpRegisters() (regs []proc.Register, fpregs linutil.I386Xstate, err error) {
thread.dbp.execPtraceFunc(func() { fpregs, err = PtraceGetRegset(thread.ID) })
regs = fpregs.Decode()
if err != nil {
err = fmt.Errorf("could not get floating point registers: %v", err.Error())
}
return
}
// This file is used to detect build on unsupported GOOS/GOARCH combinations.
//+build !linux,!darwin,!windows,!freebsd linux,!amd64,!arm64 darwin,!amd64 windows,!amd64 freebsd,!amd64
//+build !linux,!darwin,!windows,!freebsd linux,!amd64,!arm64,!386 darwin,!amd64 windows,!amd64 freebsd,!amd64
package your_operating_system_and_architecture_combination_is_not_supported_by_delve
package native
import (
"fmt"
"github.com/go-delve/delve/pkg/proc"
)
func (t *Thread) restoreRegisters(savedRegs proc.Registers) error {
return fmt.Errorf("restore regs not supported on i386")
}
package proc
import (
"fmt"
)
const (
IMAGE_FILE_MACHINE_UNKNOWN = 0x0
IMAGE_FILE_MACHINE_AM33 = 0x1d3
IMAGE_FILE_MACHINE_AMD64 = 0x8664
IMAGE_FILE_MACHINE_ARM = 0x1c0
IMAGE_FILE_MACHINE_ARMNT = 0x1c4
IMAGE_FILE_MACHINE_ARM64 = 0xaa64
IMAGE_FILE_MACHINE_EBC = 0xebc
IMAGE_FILE_MACHINE_I386 = 0x14c
IMAGE_FILE_MACHINE_IA64 = 0x200
IMAGE_FILE_MACHINE_M32R = 0x9041
IMAGE_FILE_MACHINE_MIPS16 = 0x266
IMAGE_FILE_MACHINE_MIPSFPU = 0x366
IMAGE_FILE_MACHINE_MIPSFPU16 = 0x466
IMAGE_FILE_MACHINE_POWERPC = 0x1f0
IMAGE_FILE_MACHINE_POWERPCFP = 0x1f1
IMAGE_FILE_MACHINE_R4000 = 0x166
IMAGE_FILE_MACHINE_SH3 = 0x1a2
IMAGE_FILE_MACHINE_SH3DSP = 0x1a3
IMAGE_FILE_MACHINE_SH4 = 0x1a6
IMAGE_FILE_MACHINE_SH5 = 0x1a8
IMAGE_FILE_MACHINE_THUMB = 0x1c2
IMAGE_FILE_MACHINE_WCEMIPSV2 = 0x169
)
type PEMachine uint16
// PEMachineString map pe machine to name, See $GOROOT/src/debug/pe/pe.go for detail
var PEMachineString = map[uint16]string{
IMAGE_FILE_MACHINE_UNKNOWN: "unknown",
IMAGE_FILE_MACHINE_AM33: "am33",
IMAGE_FILE_MACHINE_AMD64: "amd64",
IMAGE_FILE_MACHINE_ARM: "arm",
IMAGE_FILE_MACHINE_ARMNT: "armnt",
IMAGE_FILE_MACHINE_ARM64: "arm64",
IMAGE_FILE_MACHINE_EBC: "ebc",
IMAGE_FILE_MACHINE_I386: "i386",
IMAGE_FILE_MACHINE_IA64: "ia64",
IMAGE_FILE_MACHINE_M32R: "m32r",
IMAGE_FILE_MACHINE_MIPS16: "mips16",
IMAGE_FILE_MACHINE_MIPSFPU: "mipsfpu",
IMAGE_FILE_MACHINE_MIPSFPU16: "mipsfpu16",
IMAGE_FILE_MACHINE_POWERPC: "powerpc",
IMAGE_FILE_MACHINE_POWERPCFP: "powerpcfp",
IMAGE_FILE_MACHINE_R4000: "r4000",
IMAGE_FILE_MACHINE_SH3: "sh3",
IMAGE_FILE_MACHINE_SH3DSP: "sh3dsp",
IMAGE_FILE_MACHINE_SH4: "sh4",
IMAGE_FILE_MACHINE_SH5: "sh5",
IMAGE_FILE_MACHINE_THUMB: "thumb",
IMAGE_FILE_MACHINE_WCEMIPSV2: "wcemipsv2",
}
func (m PEMachine) String() string {
str, ok := PEMachineString[uint16(m)]
if ok {
return str
}
return fmt.Sprintf("unkown image file machine code %d\n", uint16(m))
}
......@@ -2,13 +2,25 @@ package proc
import (
"testing"
"unsafe"
)
func ptrSizeByRuntimeArch() int {
return int(unsafe.Sizeof(uintptr(0)))
}
func TestIssue554(t *testing.T) {
// unsigned integer overflow in proc.(*memCache).contains was
// causing it to always return true for address 0xffffffffffffffff
mem := memCache{true, 0x20, make([]byte, 100), nil}
if mem.contains(0xffffffffffffffff, 40) {
var addr uint64
switch ptrSizeByRuntimeArch() {
case 4:
addr = 0xffffffff
case 8:
addr = 0xffffffffffffffff
}
if mem.contains(uintptr(addr), 40) {
t.Fatalf("should be false")
}
}
......@@ -2073,15 +2073,17 @@ func TestUnsupportedArch(t *testing.T) {
t.Skip("test not valid for this backend")
}
switch err {
case proc.ErrUnsupportedLinuxArch, proc.ErrUnsupportedWindowsArch, proc.ErrUnsupportedDarwinArch:
// all good
case nil:
if err == nil {
p.Detach(true)
t.Fatal("Launch is expected to fail, but succeeded")
default:
t.Fatal(err)
}
if _, ok := err.(*proc.ErrUnsupportedArch); ok {
// all good
return
}
t.Fatal(err)
}
func TestIssue573(t *testing.T) {
......@@ -3261,6 +3263,10 @@ func TestCgoStacktrace(t *testing.T) {
}
}
if runtime.GOARCH == "386" {
t.Skip("cgo stacktraces not supported on i386 for now")
}
// Tests that:
// a) we correctly identify the goroutine while we are executing cgo code
// b) that we can stitch together the system stack (where cgo code
......@@ -3360,6 +3366,10 @@ func TestCgoSources(t *testing.T) {
}
}
if runtime.GOARCH == "386" {
t.Skip("cgo stacktraces not supported on i386 for now")
}
withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) {
sources := p.BinInfo().Sources
for _, needle := range []string{"main.go", "hello.c"} {
......@@ -3429,6 +3439,10 @@ func TestSystemstackOnRuntimeNewstack(t *testing.T) {
}
func TestIssue1034(t *testing.T) {
if runtime.GOARCH == "386" {
t.Skip("cgo stacktraces not supported on i386 for now")
}
// The external linker on macOS produces an abbrev for DW_TAG_subprogram
// without the "has children" flag, we should support this.
withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) {
......@@ -3446,6 +3460,10 @@ func TestIssue1034(t *testing.T) {
}
func TestIssue1008(t *testing.T) {
if runtime.GOARCH == "386" {
t.Skip("cgo stacktraces not supported on i386 for now")
}
// The external linker on macOS inserts "end of sequence" extended opcodes
// in debug_line. which we should support correctly.
withTestProcess("cgostacktest/", t, func(p *proc.Target, fixture protest.Fixture) {
......@@ -3565,6 +3583,14 @@ func TestDisassembleGlobalVars(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("On ARM64 symLookup can't look up variables due to how they are loaded, see issue #1778")
}
// On 386 linux when pie, the genered code use __x86.get_pc_thunk to ensure position-independent.
// Locate global variable by
// `CALL __x86.get_pc_thunk.ax(SB) 0xb0f7f
// LEAL 0xc0a19(AX), AX`
// dynamically.
if runtime.GOARCH == "386" && runtime.GOOS == "linux" && buildMode == "pie" {
t.Skip("On 386 linux when pie, symLookup can't look up global variables")
}
withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) {
mainfn := p.BinInfo().LookupFunc["main.main"]
regs, _ := p.CurrentThread().Registers(false)
......@@ -4115,8 +4141,8 @@ func TestReadDeferArgs(t *testing.T) {
}
func TestIssue1374(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support FunctionCall for now")
if runtime.GOARCH == "arm64" || runtime.GOARCH == "386" {
t.Skip(fmt.Errorf("%s does not support FunctionCall for now", runtime.GOARCH))
}
// Continue did not work when stopped at a breakpoint immediately after calling CallFunction.
protest.MustSupportFunctionCalls(t, testBackend)
......@@ -4337,9 +4363,10 @@ func TestCallConcurrent(t *testing.T) {
if runtime.GOOS == "freebsd" {
t.Skip("test is not valid on FreeBSD")
}
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support FunctionCall for now")
if runtime.GOARCH == "arm64" || runtime.GOARCH == "386" {
t.Skip(fmt.Sprintf("%s does not support FunctionCall for now", runtime.GOARCH))
}
protest.MustSupportFunctionCalls(t, testBackend)
withTestProcess("teststepconcurrent", t, func(p *proc.Target, fixture protest.Fixture) {
bp := setFileBreakpoint(p, t, fixture.Source, 24)
......
......@@ -112,21 +112,22 @@ func (g *G) stackIterator(opts StacktraceOptions) (*stackIterator, error) {
return nil, err
}
bi := g.variable.bi
if g.Thread != nil {
regs, err := g.Thread.Registers(true)
if err != nil {
return nil, err
}
so := g.variable.bi.PCToImage(regs.PC())
so := bi.PCToImage(regs.PC())
return newStackIterator(
g.variable.bi, g.Thread,
g.variable.bi.Arch.RegistersToDwarfRegisters(so.StaticBase, regs),
bi, g.Thread,
bi.Arch.RegistersToDwarfRegisters(so.StaticBase, regs),
g.stackhi, stkbar, g.stkbarPos, g, opts), nil
}
so := g.variable.bi.PCToImage(g.PC)
return newStackIterator(
g.variable.bi, g.variable.mem,
g.variable.bi.Arch.AddrAndStackRegsToDwarfRegisters(so.StaticBase, g.PC, g.SP, g.BP, g.LR),
bi, g.variable.mem,
bi.Arch.AddrAndStackRegsToDwarfRegisters(so.StaticBase, g.PC, g.SP, g.BP, g.LR),
g.stackhi, stkbar, g.stkbarPos, g, opts), nil
}
......@@ -438,11 +439,7 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin
// implicit.
// See also the comment in dwarf2_frame_default_init in
// $GDB_SOURCE/dwarf2-frame.c
if _, ok := it.bi.Arch.(*ARM64); ok {
callFrameRegs.AddReg(uint64(arm64DwarfSPRegNum), cfareg)
} else {
callFrameRegs.AddReg(uint64(amd64DwarfSPRegNum), cfareg)
}
callFrameRegs.AddReg(callFrameRegs.SPRegNum, cfareg)
for i, regRule := range framectx.Regs {
reg, err := it.executeFrameRegRule(i, regRule, it.regs.CFA)
......@@ -488,13 +485,13 @@ func (it *stackIterator) executeFrameRegRule(regnum uint64, rule frame.DWRule, c
case frame.RuleRegister:
return it.regs.Reg(rule.Reg), nil
case frame.RuleExpression:
v, _, err := op.ExecuteStackProgram(it.regs, rule.Expression)
v, _, err := op.ExecuteStackProgram(it.regs, rule.Expression, it.bi.Arch.PtrSize())
if err != nil {
return nil, err
}
return it.readRegisterAt(regnum, uint64(v))
case frame.RuleValExpression:
v, _, err := op.ExecuteStackProgram(it.regs, rule.Expression)
v, _, err := op.ExecuteStackProgram(it.regs, rule.Expression, it.bi.Arch.PtrSize())
if err != nil {
return nil, err
}
......
package proc
import (
"encoding/binary"
"errors"
"fmt"
"go/ast"
......@@ -209,7 +208,7 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error {
continue
}
if instr.DestLoc != nil && instr.DestLoc.Fn != nil {
if instr.DestLoc != nil {
if err := setStepIntoBreakpoint(dbp, []AsmInstruction{instr}, sameGCond); err != nil {
return err
}
......@@ -399,6 +398,11 @@ func setStepIntoBreakpoint(dbp Process, text []AsmInstruction, cond ast.Expr) er
pc := instr.DestLoc.PC
// Skip InhibitStepInto functions for different arch.
if dbp.BinInfo().Arch.InhibitStepInto(dbp.BinInfo(), pc) {
return nil
}
// We want to skip the function prologue but we should only do it if the
// destination address of the CALL instruction is the entry point of the
// function.
......@@ -427,12 +431,11 @@ func getGVariable(thread Thread) (*Variable, error) {
gaddr, hasgaddr := regs.GAddr()
if !hasgaddr {
gaddrbs := make([]byte, thread.Arch().PtrSize())
_, err := thread.ReadMemory(gaddrbs, uintptr(regs.TLS()+thread.BinInfo().GStructOffset()))
var err error
gaddr, err = readUintRaw(thread, uintptr(regs.TLS()+thread.BinInfo().GStructOffset()), int64(thread.BinInfo().Arch.PtrSize()))
if err != nil {
return nil, err
}
gaddr = binary.LittleEndian.Uint64(gaddrbs)
}
return newGVariable(thread, uintptr(gaddr), thread.Arch().DerefTLS())
......
......@@ -1488,13 +1488,12 @@ func (v *Variable) readFunctionPtr() {
// funcvalAddr reads the address of the funcval contained in a function variable.
func (v *Variable) funcvalAddr() uint64 {
val := make([]byte, v.bi.Arch.PtrSize())
_, err := v.mem.ReadMemory(val, v.Addr)
val, err := readUintRaw(v.mem, v.Addr, int64(v.bi.Arch.PtrSize()))
if err != nil {
v.Unreadable = err
return 0
}
return binary.LittleEndian.Uint64(val)
return val
}
func (v *Variable) loadMap(recurseLevel int, cfg LoadConfig) {
......
package proc
import (
"golang.org/x/arch/x86/x86asm"
)
type x86Inst x86asm.Inst
// 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 x86AsmDecode(asmInst *AsmInstruction, mem []byte, regs Registers, memrw MemoryReadWriter, bi *BinaryInfo, bit int) error {
inst, err := x86asm.Decode(mem, bit)
if err != nil {
asmInst.Inst = (*x86Inst)(nil)
asmInst.Size = 1
asmInst.Bytes = mem[:asmInst.Size]
return err
}
asmInst.Size = inst.Len
asmInst.Bytes = mem[:asmInst.Size]
patchPCRelX86(asmInst.Loc.PC, &inst)
asmInst.Inst = (*x86Inst)(&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 = resolveCallArgX86(&inst, asmInst.Loc.PC, asmInst.AtPC, regs, memrw, bi)
return nil
}
// converts PC relative arguments to absolute addresses
func patchPCRelX86(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))
}
}
}
func (inst *x86Inst) Text(flavour AssemblyFlavour, pc uint64, symLookup func(uint64) (string, uint64)) string {
if inst == nil {
return "?"
}
var text string
switch flavour {
case GNUFlavour:
text = x86asm.GNUSyntax(x86asm.Inst(*inst), pc, symLookup)
case GoFlavour:
text = x86asm.GoSyntax(x86asm.Inst(*inst), pc, symLookup)
case IntelFlavour:
fallthrough
default:
text = x86asm.IntelSyntax(x86asm.Inst(*inst), pc, symLookup)
}
return text
}
func (inst *x86Inst) OpcodeEquals(op uint64) bool {
if inst == nil {
return false
}
return uint64(inst.Op) == op
}
func resolveCallArgX86(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
}
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
}
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)
pc, err = readUintRaw(mem, addr, int64(inst.MemBytes))
if err != nil {
return nil
}
default:
return nil
}
file, line, fn := bininfo.PCToLine(pc)
if fn == nil {
return &Location{PC: pc}
}
return &Location{PC: pc, File: file, Line: line, Fn: fn}
}
......@@ -978,8 +978,8 @@ func findStarFile(name string) string {
}
func TestIssue1598(t *testing.T) {
if runtime.GOARCH == "arm64" {
t.Skip("arm64 does not support FunctionCall for now")
if runtime.GOARCH == "arm64" || runtime.GOARCH == "386" {
t.Skip(fmt.Errorf("%s does not support FunctionCall for now", runtime.GOARCH))
}
test.MustSupportFunctionCalls(t, testBackend)
withTestTerminal("issue1598", t, func(term *FakeTerminal) {
......
......@@ -127,7 +127,7 @@ func New(config *Config, processArgs []string) (*Debugger, error) {
d.log.Infof("launching process with args: %v", d.processArgs)
p, err := d.Launch(d.processArgs, d.config.WorkingDir)
if err != nil {
if err != proc.ErrNotExecutable && err != proc.ErrUnsupportedLinuxArch && err != proc.ErrUnsupportedWindowsArch && err != proc.ErrUnsupportedDarwinArch {
if _, ok := err.(*proc.ErrUnsupportedArch); !ok {
err = go11DecodeErrorCheck(err)
err = fmt.Errorf("could not launch process: %s", err)
}
......
......@@ -919,8 +919,13 @@ func Test1Disasm(t *testing.T) {
t.Fatal("PC instruction not found")
}
startinstr := getCurinstr(d3)
if runtime.GOARCH == "386" && buildMode == "pie" {
// Skip the rest of the test because on intel 386 with PIE build mode
// the compiler will insert calls to __x86.get_pc_thunk which do not have DIEs and we can't resolve.
return
}
startinstr := getCurinstr(d3)
count := 0
for {
if count > 20 {
......
......@@ -1026,8 +1026,13 @@ func TestDisasm(t *testing.T) {
t.Fatal("PC instruction not found")
}
startinstr := getCurinstr(d3)
if runtime.GOARCH == "386" && buildMode == "pie" {
// Skip the rest of the test because on intel 386 with PIE build mode
// the compiler will insert calls to __x86.get_pc_thunk which do not have DIEs and we can't resolve.
return
}
startinstr := getCurinstr(d3)
count := 0
for {
if count > 20 {
......@@ -1154,13 +1159,14 @@ func TestSkipPrologue2(t *testing.T) {
callme3 := findLocationHelper(t, c, "main.callme3", false, 1, 0)[0]
callme3Z := uint64(clientEvalVariable(t, c, "main.callme3").Addr)
ver, _ := goversion.Parse(runtime.Version())
if ver.Major < 0 || ver.AfterOrEqual(goversion.GoVer18Beta) {
if (ver.Major < 0 || ver.AfterOrEqual(goversion.GoVer18Beta)) && runtime.GOARCH != "386" {
findLocationHelper(t, c, "callme.go:19", false, 1, callme3)
} else {
// callme3 does not have local variables therefore the first line of the
// function is immediately after the prologue
// This is only true before 1.8 where frame pointer chaining introduced a
// bit of prologue even for functions without local variables
// This is only true before go1.8 or on Intel386 where frame pointer chaining
// introduced a bit of prologue even for functions without local variables
findLocationHelper(t, c, "callme.go:19", false, 1, callme3Z)
}
if callme3 == callme3Z {
......
......@@ -1001,13 +1001,11 @@ func TestPackageRenames(t *testing.T) {
// Renamed imports
{"badexpr", true, `interface {}(*go/ast.BadExpr) *{From: 1, To: 2}`, "", "interface {}", nil},
{"req", true, `interface {}(*net/http.Request) *{Method: "amethod", …`, "", "interface {}", nil},
{"amap", true, "interface {}(map[go/ast.BadExpr]net/http.Request) [{From: 2, To: 3}: *{Method: \"othermethod\", …", "", "interface {}", nil},
// Package name that doesn't match import path
{"iface3", true, `interface {}(*github.com/go-delve/delve/_fixtures/internal/dir0/renamedpackage.SomeType) *{A: true}`, "", "interface {}", nil},
// Interfaces to anonymous types
{"amap2", true, "interface {}(*map[go/ast.BadExpr]net/http.Request) *[{From: 2, To: 3}: *{Method: \"othermethod\", …", "", "interface {}", nil},
{"dir0someType", true, "interface {}(*github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeType) *{X: 3}", "", "interface {}", nil},
{"dir1someType", true, "interface {}(github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType) {X: 1, Y: 2}", "", "interface {}", nil},
{"amap3", true, "interface {}(map[github.com/go-delve/delve/_fixtures/internal/dir0/pkg.SomeType]github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType) [{X: 4}: {X: 5, Y: 6}, ]", "", "interface {}", nil},
......@@ -1022,6 +1020,16 @@ func TestPackageRenames(t *testing.T) {
{`"dir1/pkg".A`, false, "1", "", "int", nil},
}
testcases_i386 := []varTest{
{"amap", true, "interface {}(map[go/ast.BadExpr]net/http.Request) [{From: 2, To: 3}: {Method: \"othermethod\", …", "", "interface {}", nil},
{"amap2", true, "interface {}(*map[go/ast.BadExpr]net/http.Request) *[{From: 2, To: 3}: {Method: \"othermethod\", …", "", "interface {}", nil},
}
testcases_64bit := []varTest{
{"amap", true, "interface {}(map[go/ast.BadExpr]net/http.Request) [{From: 2, To: 3}: *{Method: \"othermethod\", …", "", "interface {}", nil},
{"amap2", true, "interface {}(*map[go/ast.BadExpr]net/http.Request) *[{From: 2, To: 3}: *{Method: \"othermethod\", …", "", "interface {}", nil},
}
testcases1_8 := []varTest{
// before 1.9 embedded struct fields have fieldname == type
{"astruct2", true, `interface {}(*struct { github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType; X int }) *{github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType: github.com/go-delve/delve/_fixtures/internal/dir1/pkg.SomeType {X: 1, Y: 2}, X: 10}`, "", "interface {}", nil},
......@@ -1054,6 +1062,12 @@ func TestPackageRenames(t *testing.T) {
if goversion.VersionAfterOrEqual(runtime.Version(), 1, 13) {
testPackageRenamesHelper(t, p, testcases1_13)
}
if runtime.GOARCH == "386" {
testPackageRenamesHelper(t, p, testcases_i386)
} else {
testPackageRenamesHelper(t, p, testcases_64bit)
}
})
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册