diff --git a/.cirrus.yml b/.cirrus.yml index a5727d0f..d7d0cfb3 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -1,9 +1,28 @@ -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 diff --git a/_fixtures/cgostacktest/hello.c b/_fixtures/cgostacktest/hello.c index 7e179be1..7226ffed 100644 --- a/_fixtures/cgostacktest/hello.c +++ b/_fixtures/cgostacktest/hello.c @@ -4,6 +4,8 @@ #ifdef __amd64__ #define BREAKPOINT asm("int3;") +#elif __i386__ +#define BREAKPOINT asm("int3;") #elif __aarch64__ #define BREAKPOINT asm("brk 0;") #endif diff --git a/pkg/dwarf/frame/entries_test.go b/pkg/dwarf/frame/entries_test.go index a608120a..cd919af4 100644 --- a/pkg/dwarf/frame/entries_test.go +++ b/pkg/dwarf/frame/entries_test.go @@ -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 diff --git a/pkg/dwarf/frame/parser.go b/pkg/dwarf/frame/parser.go index f0b1f920..6d12dad2 100644 --- a/pkg/dwarf/frame/parser.go +++ b/pkg/dwarf/frame/parser.go @@ -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 diff --git a/pkg/dwarf/frame/parser_test.go b/pkg/dwarf/frame/parser_test.go index d9d37673..d03ea01c 100644 --- a/pkg/dwarf/frame/parser_test.go +++ b/pkg/dwarf/frame/parser_test.go @@ -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()) } } diff --git a/pkg/dwarf/line/line_parser.go b/pkg/dwarf/line/line_parser.go index d0b9e901..d0761993 100644 --- a/pkg/dwarf/line/line_parser.go +++ b/pkg/dwarf/line/line_parser.go @@ -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) diff --git a/pkg/dwarf/line/line_parser_test.go b/pkg/dwarf/line/line_parser_test.go index 08a8a6eb..ac3c2405 100644 --- a/pkg/dwarf/line/line_parser_test.go +++ b/pkg/dwarf/line/line_parser_test.go @@ -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") diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go index cb041544..b92f8849 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -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 } diff --git a/pkg/dwarf/line/state_machine_test.go b/pkg/dwarf/line/state_machine_test.go index 3a36d183..5088c4f4 100644 --- a/pkg/dwarf/line/state_machine_test.go +++ b/pkg/dwarf/line/state_machine_test.go @@ -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) diff --git a/pkg/dwarf/op/op.go b/pkg/dwarf/op/op.go index 89af5a68..ac6bd52e 100644 --- a/pkg/dwarf/op/op.go +++ b/pkg/dwarf/op/op.go @@ -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 } diff --git a/pkg/dwarf/op/op_test.go b/pkg/dwarf/op/op_test.go index c46c8c7a..0a346261 100644 --- a/pkg/dwarf/op/op_test.go +++ b/pkg/dwarf/op/op_test.go @@ -1,13 +1,20 @@ 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) } diff --git a/pkg/dwarf/reader/reader.go b/pkg/dwarf/reader/reader.go index d59de829..f3118e9f 100644 --- a/pkg/dwarf/reader/reader.go +++ b/pkg/dwarf/reader/reader.go @@ -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 } } diff --git a/pkg/dwarf/util/util.go b/pkg/dwarf/util/util.go index 012f5308..20e4e2b9 100644 --- a/pkg/dwarf/util/util.go +++ b/pkg/dwarf/util/util.go @@ -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) +} diff --git a/pkg/proc/amd64_arch.go b/pkg/proc/amd64_arch.go index 202dc53b..92731cb4 100644 --- a/pkg/proc/amd64_arch.go +++ b/pkg/proc/amd64_arch.go @@ -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 +} diff --git a/pkg/proc/amd64_disasm.go b/pkg/proc/amd64_disasm.go index 84ac9446..67ba7083 100644 --- a/pkg/proc/amd64_disasm.go +++ b/pkg/proc/amd64_disasm.go @@ -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 diff --git a/pkg/proc/arch.go b/pkg/proc/arch.go index 28d16635..c915575d 100644 --- a/pkg/proc/arch.go +++ b/pkg/proc/arch.go @@ -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 diff --git a/pkg/proc/arm64_arch.go b/pkg/proc/arm64_arch.go index cdfd4b66..8fe7096c 100644 --- a/pkg/proc/arm64_arch.go +++ b/pkg/proc/arm64_arch.go @@ -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 +} diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 47451324..ae3da942 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -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 }) diff --git a/pkg/proc/core/linux_core.go b/pkg/proc/core/linux_core.go index e52ab0a2..03727246 100644 --- a/pkg/proc/core/linux_core.go +++ b/pkg/proc/core/linux_core.go @@ -118,24 +118,29 @@ func readLinuxCore(corePath, exePath string) (*Process, error) { return nil, err } memory := buildMemory(coreFile, exeELF, exe, notes) - entryPoint := findEntryPoint(notes) + + // TODO support 386 + var bi *proc.BinaryInfo + switch machineType { + case EM_X86_64: + bi = proc.NewBinaryInfo("linux", "amd64") + case EM_AARCH64: + 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(), } - switch machineType { - case EM_X86_64: - p.bi = proc.NewBinaryInfo("linux", "amd64") - linuxThreadsFromNotes(p, notes, machineType) - case EM_AARCH64: - p.bi = proc.NewBinaryInfo("linux", "arm64") - linuxThreadsFromNotes(p, notes, machineType) - default: - return nil, fmt.Errorf("unsupported machine type") - } + 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 diff --git a/pkg/proc/dwarf_expr_test.go b/pkg/proc/dwarf_expr_test.go index 3d8d429b..6a236e26 100644 --- a/pkg/proc/dwarf_expr_test.go +++ b/pkg/proc/dwarf_expr_test.go @@ -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} diff --git a/pkg/proc/fbsdutil/regs_test.go b/pkg/proc/fbsdutil/regs_test.go index 30773ffa..19aa7641 100644 --- a/pkg/proc/fbsdutil/regs_test.go +++ b/pkg/proc/fbsdutil/regs_test.go @@ -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 diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go index a64f32e8..9ec3a8f4 100644 --- a/pkg/proc/fncall.go +++ b/pkg/proc/fncall.go @@ -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) } diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index b0add2d8..e077e8ca 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -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 } diff --git a/pkg/proc/goroutine_cache.go b/pkg/proc/goroutine_cache.go index c4613a10..e81f4d2b 100644 --- a/pkg/proc/goroutine_cache.go +++ b/pkg/proc/goroutine_cache.go @@ -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 diff --git a/pkg/proc/i386_arch.go b/pkg/proc/i386_arch.go new file mode 100644 index 00000000..c48342d7 --- /dev/null +++ b/pkg/proc/i386_arch.go @@ -0,0 +1,369 @@ +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 +} diff --git a/pkg/proc/i386_disasm.go b/pkg/proc/i386_disasm.go new file mode 100644 index 00000000..3f560a2c --- /dev/null +++ b/pkg/proc/i386_disasm.go @@ -0,0 +1,45 @@ +// 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) + } + } + } +} diff --git a/pkg/proc/linutil/auxv.go b/pkg/proc/linutil/auxv.go index 786c3232..a19ea23b 100644 --- a/pkg/proc/linutil/auxv.go +++ b/pkg/proc/linutil/auxv.go @@ -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 } } diff --git a/pkg/proc/linutil/dynamic.go b/pkg/proc/linutil/dynamic.go index 1f70ff05..73fd34dc 100644 --- a/pkg/proc/linutil/dynamic.go +++ b/pkg/proc/linutil/dynamic.go @@ -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 } diff --git a/pkg/proc/linutil/regs_i386_arch.go b/pkg/proc/linutil/regs_i386_arch.go new file mode 100644 index 00000000..6ec04d23 --- /dev/null +++ b/pkg/proc/linutil/regs_i386_arch.go @@ -0,0 +1,286 @@ +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, ®set.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 +} diff --git a/pkg/proc/linutil/regs_test.go b/pkg/proc/linutil/regs_test.go index 7d6fb879..a9ed0fbf 100644 --- a/pkg/proc/linutil/regs_test.go +++ b/pkg/proc/linutil/regs_test.go @@ -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 diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index 75dece1e..5f2693f8 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -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 { diff --git a/pkg/proc/native/ptrace_linux.go b/pkg/proc/native/ptrace_linux.go index 849516d8..ab4f5372 100644 --- a/pkg/proc/native/ptrace_linux.go +++ b/pkg/proc/native/ptrace_linux.go @@ -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 -} diff --git a/pkg/proc/native/ptrace_linux_386.go b/pkg/proc/native/ptrace_linux_386.go new file mode 100644 index 00000000..269f95e2 --- /dev/null +++ b/pkg/proc/native/ptrace_linux_386.go @@ -0,0 +1,93 @@ +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(®set.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, ®set) + 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 +} diff --git a/pkg/proc/native/ptrace_linux_64bit.go b/pkg/proc/native/ptrace_linux_64bit.go new file mode 100644 index 00000000..9206f046 --- /dev/null +++ b/pkg/proc/native/ptrace_linux_64bit.go @@ -0,0 +1,38 @@ +// +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 +} diff --git a/pkg/proc/native/ptrace_linux_amd64.go b/pkg/proc/native/ptrace_linux_amd64.go index 089410fa..5532c76f 100644 --- a/pkg/proc/native/ptrace_linux_amd64.go +++ b/pkg/proc/native/ptrace_linux_amd64.go @@ -38,4 +38,4 @@ func PtraceGetRegset(tid int) (regset linutil.AMD64Xstate, err error) { regset.Xsave = xstateargs[:iov.Len] err = linutil.AMD64XstateRead(regset.Xsave, false, ®set) return -} +} \ No newline at end of file diff --git a/pkg/proc/native/register_linux_386.go b/pkg/proc/native/register_linux_386.go new file mode 100644 index 00000000..9eb7d162 --- /dev/null +++ b/pkg/proc/native/register_linux_386.go @@ -0,0 +1,91 @@ +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)(®s)) }) + if err != nil { + return nil, err + } + r := &linutil.I386Registers{®s, 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 +} diff --git a/pkg/proc/native/support_sentinel.go b/pkg/proc/native/support_sentinel.go index 826a021f..8dcdb5a8 100644 --- a/pkg/proc/native/support_sentinel.go +++ b/pkg/proc/native/support_sentinel.go @@ -1,5 +1,5 @@ // 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 diff --git a/pkg/proc/native/threads_linux_386.go b/pkg/proc/native/threads_linux_386.go new file mode 100644 index 00000000..02e2972f --- /dev/null +++ b/pkg/proc/native/threads_linux_386.go @@ -0,0 +1,10 @@ +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") +} diff --git a/pkg/proc/pe.go b/pkg/proc/pe.go new file mode 100644 index 00000000..21a9a27a --- /dev/null +++ b/pkg/proc/pe.go @@ -0,0 +1,66 @@ +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)) +} diff --git a/pkg/proc/proc_general_test.go b/pkg/proc/proc_general_test.go index 8af69eac..2e35469d 100644 --- a/pkg/proc/proc_general_test.go +++ b/pkg/proc/proc_general_test.go @@ -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") } } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index dd15e580..70d913c6 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -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) diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index f99d0e2d..0efb92f7 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -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 } diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go index 514a1fa2..a54b716d 100644 --- a/pkg/proc/threads.go +++ b/pkg/proc/threads.go @@ -1,7 +1,6 @@ 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()) diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 96a559fd..2a06aa3a 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -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) { diff --git a/pkg/proc/x86_disasm.go b/pkg/proc/x86_disasm.go new file mode 100644 index 00000000..a4c1ef38 --- /dev/null +++ b/pkg/proc/x86_disasm.go @@ -0,0 +1,120 @@ +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} +} diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index 384e2c13..d422ccb4 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -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) { diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 7b54a1ee..5bf9b18a 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -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) } diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go index 27e3d051..980f0cb6 100644 --- a/service/test/integration1_test.go +++ b/service/test/integration1_test.go @@ -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 { diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index 6a635de4..9cf441c1 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -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 { diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 00cf95ae..418fcf3b 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -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) + } }) }