diff --git a/pkg/dwarf/frame/entries.go b/pkg/dwarf/frame/entries.go index 1c26891a..cb3917e6 100644 --- a/pkg/dwarf/frame/entries.go +++ b/pkg/dwarf/frame/entries.go @@ -17,6 +17,7 @@ type CommonInformationEntry struct { DataAlignmentFactor int64 ReturnAddressRegister uint64 InitialInstructions []byte + staticBase uint64 } // Represents a Frame Descriptor Entry in the @@ -25,14 +26,14 @@ type FrameDescriptionEntry struct { Length uint32 CIE *CommonInformationEntry Instructions []byte - begin, end uint64 + begin, size uint64 order binary.ByteOrder } // Returns whether or not the given address is within the // bounds of this frame. func (fde *FrameDescriptionEntry) Cover(addr uint64) bool { - return (addr - fde.begin) < fde.end + return (addr - fde.begin) < fde.size } // Address of first location for this frame. @@ -42,7 +43,7 @@ func (fde *FrameDescriptionEntry) Begin() uint64 { // Address of last location for this frame. func (fde *FrameDescriptionEntry) End() uint64 { - return fde.begin + fde.end + return fde.begin + fde.size } // Set up frame for the given PC. @@ -67,20 +68,10 @@ func (err *ErrNoFDEForPC) Error() string { // Returns the Frame Description Entry for the given PC. func (fdes FrameDescriptionEntries) FDEForPC(pc uint64) (*FrameDescriptionEntry, error) { idx := sort.Search(len(fdes), func(i int) bool { - if fdes[i].Cover(pc) { - return true - } - if fdes[i].LessThan(pc) { - return false - } - return true + return fdes[i].Cover(pc) || fdes[i].Begin() >= pc }) - if idx == len(fdes) { + if idx == len(fdes) || !fdes[idx].Cover(pc) { return nil, &ErrNoFDEForPC{pc} } return fdes[idx], nil } - -func (frame *FrameDescriptionEntry) LessThan(pc uint64) bool { - return frame.End() <= pc -} diff --git a/pkg/dwarf/frame/entries_test.go b/pkg/dwarf/frame/entries_test.go index 577ad165..a608120a 100644 --- a/pkg/dwarf/frame/entries_test.go +++ b/pkg/dwarf/frame/entries_test.go @@ -8,24 +8,46 @@ import ( ) func TestFDEForPC(t *testing.T) { - fde1 := &FrameDescriptionEntry{begin: 0, end: 49} - fde2 := &FrameDescriptionEntry{begin: 50, end: 99} - fde3 := &FrameDescriptionEntry{begin: 100, end: 200} - fde4 := &FrameDescriptionEntry{begin: 201, end: 245} - frames := NewFrameIndex() - frames = append(frames, fde1) - frames = append(frames, fde2) - frames = append(frames, fde3) - frames = append(frames, fde4) + frames = append(frames, + &FrameDescriptionEntry{begin: 10, size: 40}, + &FrameDescriptionEntry{begin: 50, size: 50}, + &FrameDescriptionEntry{begin: 100, size: 100}, + &FrameDescriptionEntry{begin: 300, size: 10}) - node, err := frames.FDEForPC(35) - if err != nil { - t.Fatal(err) - } + for _, test := range []struct { + pc uint64 + fde *FrameDescriptionEntry + }{ + {0, nil}, + {9, nil}, + {10, frames[0]}, + {35, frames[0]}, + {49, frames[0]}, + {50, frames[1]}, + {75, frames[1]}, + {100, frames[2]}, + {199, frames[2]}, + {200, nil}, + {299, nil}, + {300, frames[3]}, + {309, frames[3]}, + {310, nil}, + {400, nil}} { - if node != fde1 { - t.Fatal("Got incorrect fde") + out, err := frames.FDEForPC(test.pc) + if test.fde != nil { + if err != nil { + t.Fatal(err) + } + if out != test.fde { + t.Errorf("[pc = %#x] got incorrect fde\noutput:\t%#v\nexpected:\t%#v", test.pc, out, test.fde) + } + } else { + if err == nil { + t.Errorf("[pc = %#x] expected error got fde %#v", test.pc, out) + } + } } } @@ -40,7 +62,7 @@ func BenchmarkFDEForPC(b *testing.B) { if err != nil { b.Fatal(err) } - fdes := Parse(data, binary.BigEndian) + fdes := Parse(data, binary.BigEndian, 0) 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 4fe84f6b..550f4120 100644 --- a/pkg/dwarf/frame/parser.go +++ b/pkg/dwarf/frame/parser.go @@ -13,6 +13,8 @@ import ( type parsefunc func(*parseContext) parsefunc type parseContext struct { + staticBase uint64 + buf *bytes.Buffer entries FrameDescriptionEntries common *CommonInformationEntry @@ -23,10 +25,10 @@ type parseContext struct { // 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) FrameDescriptionEntries { +func Parse(data []byte, order binary.ByteOrder, staticBase uint64) FrameDescriptionEntries { var ( buf = bytes.NewBuffer(data) - pctx = &parseContext{buf: buf, entries: NewFrameIndex()} + pctx = &parseContext{buf: buf, entries: NewFrameIndex(), staticBase: staticBase} ) for fn := parselength; buf.Len() != 0; { @@ -57,7 +59,7 @@ func parselength(ctx *parseContext) parsefunc { ctx.length -= 4 // take off the length of the CIE id / CIE pointer. if cieEntry(data) { - ctx.common = &CommonInformationEntry{Length: ctx.length} + ctx.common = &CommonInformationEntry{Length: ctx.length, staticBase: ctx.staticBase} return parseCIE } @@ -68,8 +70,8 @@ func parselength(ctx *parseContext) parsefunc { func parseFDE(ctx *parseContext) parsefunc { r := ctx.buf.Next(int(ctx.length)) - ctx.frame.begin = binary.LittleEndian.Uint64(r[:8]) - ctx.frame.end = binary.LittleEndian.Uint64(r[8:16]) + ctx.frame.begin = binary.LittleEndian.Uint64(r[:8]) + ctx.staticBase + ctx.frame.size = binary.LittleEndian.Uint64(r[8:16]) // Insert into the tree after setting address range begin // otherwise compares won't work. diff --git a/pkg/dwarf/frame/parser_test.go b/pkg/dwarf/frame/parser_test.go index 37866aad..07c7c31f 100644 --- a/pkg/dwarf/frame/parser_test.go +++ b/pkg/dwarf/frame/parser_test.go @@ -25,6 +25,6 @@ func BenchmarkParse(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - frame.Parse(data, binary.BigEndian) + frame.Parse(data, binary.BigEndian, 0) } } diff --git a/pkg/dwarf/frame/table.go b/pkg/dwarf/frame/table.go index c0699c9b..ff6621a5 100644 --- a/pkg/dwarf/frame/table.go +++ b/pkg/dwarf/frame/table.go @@ -277,7 +277,7 @@ func setloc(frame *FrameContext) { var loc uint64 binary.Read(frame.buf, frame.order, &loc) - frame.loc = loc + frame.loc = loc + frame.cie.staticBase } func offsetextended(frame *FrameContext) { diff --git a/pkg/dwarf/line/line_parser.go b/pkg/dwarf/line/line_parser.go index a3749ee6..92554ff3 100644 --- a/pkg/dwarf/line/line_parser.go +++ b/pkg/dwarf/line/line_parser.go @@ -34,6 +34,9 @@ type DebugLineInfo struct { // lastMachineCache[pc] is a state machine stopped at an address after pc lastMachineCache map[uint64]*StateMachine + + // staticBase is the address at which the executable is loaded, 0 for non-PIEs + staticBase uint64 } type FileEntry struct { @@ -46,7 +49,7 @@ type FileEntry struct { type DebugLines []*DebugLineInfo // ParseAll parses all debug_line segments found in data -func ParseAll(data []byte, logfn func(string, ...interface{})) DebugLines { +func ParseAll(data []byte, logfn func(string, ...interface{}), staticBase uint64) DebugLines { var ( lines = make(DebugLines, 0) buf = bytes.NewBuffer(data) @@ -54,7 +57,7 @@ func ParseAll(data []byte, logfn func(string, ...interface{})) DebugLines { // We have to parse multiple file name tables here. for buf.Len() > 0 { - lines = append(lines, Parse("", buf, logfn)) + lines = append(lines, Parse("", buf, logfn, staticBase)) } return lines @@ -62,9 +65,10 @@ func ParseAll(data []byte, logfn func(string, ...interface{})) DebugLines { // 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{})) *DebugLineInfo { +func Parse(compdir string, buf *bytes.Buffer, logfn func(string, ...interface{}), staticBase uint64) *DebugLineInfo { dbl := new(DebugLineInfo) dbl.Logf = logfn + dbl.staticBase = staticBase dbl.Lookup = make(map[string]*FileEntry) if compdir != "" { 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 83651624..cc358b41 100644 --- a/pkg/dwarf/line/line_parser_test.go +++ b/pkg/dwarf/line/line_parser_test.go @@ -67,7 +67,7 @@ const ( func testDebugLinePrologueParser(p string, t *testing.T) { data := grabDebugLineSection(p, t) - debugLines := ParseAll(data, nil) + debugLines := ParseAll(data, nil, 0) mainFileFound := false @@ -164,7 +164,7 @@ func BenchmarkLineParser(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - _ = ParseAll(data, nil) + _ = ParseAll(data, nil, 0) } } @@ -179,7 +179,7 @@ func loadBenchmarkData(tb testing.TB) DebugLines { tb.Fatal("Could not read test data", err) } - return ParseAll(data, nil) + return ParseAll(data, nil, 0) } func BenchmarkStateMachine(b *testing.B) { diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go index af52eb8b..30b77a85 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -105,7 +105,7 @@ func newStateMachine(dbl *DebugLineInfo, instructions []byte) *StateMachine { 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)} + 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} return sm } @@ -433,7 +433,7 @@ func setaddress(sm *StateMachine, buf *bytes.Buffer) { binary.Read(buf, binary.LittleEndian, &addr) - sm.address = addr + sm.address = addr + sm.dbl.staticBase } func definefile(sm *StateMachine, buf *bytes.Buffer) { diff --git a/pkg/dwarf/line/state_machine_test.go b/pkg/dwarf/line/state_machine_test.go index 89cb418a..af189826 100644 --- a/pkg/dwarf/line/state_machine_test.go +++ b/pkg/dwarf/line/state_machine_test.go @@ -77,7 +77,7 @@ func TestGrafana(t *testing.T) { } cuname, _ := e.Val(dwarf.AttrName).(string) - lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, t.Logf) + lineInfo := Parse(e.Val(dwarf.AttrCompDir).(string), debugLineBuffer, t.Logf, 0) sm := newStateMachine(lineInfo, lineInfo.Instructions) lnrdr, err := data.LineReader(e) diff --git a/pkg/dwarf/op/op.go b/pkg/dwarf/op/op.go index a1499135..7cc4fe3c 100644 --- a/pkg/dwarf/op/op.go +++ b/pkg/dwarf/op/op.go @@ -133,7 +133,7 @@ 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.stack = append(ctxt.stack, int64(binary.LittleEndian.Uint64(ctxt.buf.Next(8))+ctxt.StaticBase)) return nil } diff --git a/pkg/dwarf/op/regs.go b/pkg/dwarf/op/regs.go index 67c12fcf..1de5e0c4 100644 --- a/pkg/dwarf/op/regs.go +++ b/pkg/dwarf/op/regs.go @@ -6,6 +6,8 @@ import ( ) type DwarfRegisters struct { + StaticBase uint64 + CFA int64 FrameBase int64 ObjBase int64 diff --git a/pkg/dwarf/reader/reader.go b/pkg/dwarf/reader/reader.go index ed056fbf..bf17b3ef 100755 --- a/pkg/dwarf/reader/reader.go +++ b/pkg/dwarf/reader/reader.go @@ -34,7 +34,7 @@ func (reader *Reader) SeekToEntry(entry *dwarf.Entry) error { // SeekToFunctionEntry moves the reader to the function that includes the // specified program counter. -func (reader *Reader) SeekToFunction(pc uint64) (*dwarf.Entry, error) { +func (reader *Reader) SeekToFunction(pc RelAddr) (*dwarf.Entry, error) { reader.Seek(0) for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { if err != nil { @@ -55,7 +55,7 @@ func (reader *Reader) SeekToFunction(pc uint64) (*dwarf.Entry, error) { continue } - if lowpc <= pc && highpc > pc { + if lowpc <= uint64(pc) && highpc > uint64(pc) { return entry, nil } } @@ -64,7 +64,7 @@ func (reader *Reader) SeekToFunction(pc uint64) (*dwarf.Entry, error) { } // Returns the address for the named entry. -func (reader *Reader) AddrFor(name string) (uint64, error) { +func (reader *Reader) AddrFor(name string, staticBase uint64) (uint64, error) { entry, err := reader.FindEntryNamed(name, false) if err != nil { return 0, err @@ -73,7 +73,7 @@ func (reader *Reader) AddrFor(name string) (uint64, error) { if !ok { return 0, fmt.Errorf("type assertion failed") } - addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{}, instructions) + addr, _, err := op.ExecuteStackProgram(op.DwarfRegisters{StaticBase: staticBase}, instructions) if err != nil { return 0, err } @@ -380,10 +380,10 @@ type InlineStackReader struct { // InlineStack returns an InlineStackReader for the specified function and // PC address. // If pc is 0 then all inlined calls will be returned. -func InlineStack(dwarf *dwarf.Data, fnoff dwarf.Offset, pc uint64) *InlineStackReader { +func InlineStack(dwarf *dwarf.Data, fnoff dwarf.Offset, pc RelAddr) *InlineStackReader { reader := dwarf.Reader() reader.Seek(fnoff) - return &InlineStackReader{dwarf: dwarf, reader: reader, entry: nil, depth: 0, pc: pc} + return &InlineStackReader{dwarf: dwarf, reader: reader, entry: nil, depth: 0, pc: uint64(pc)} } // Next reads next inlined call in the stack, returns false if there aren't any. diff --git a/pkg/dwarf/reader/variables.go b/pkg/dwarf/reader/variables.go index ca93e721..627fd53a 100644 --- a/pkg/dwarf/reader/variables.go +++ b/pkg/dwarf/reader/variables.go @@ -6,6 +6,14 @@ import ( "debug/dwarf" ) +// RelAddr is an address relative to the static base. For normal executables +// this is just a normal memory address, for PIE it's a relative address. +type RelAddr uint64 + +func ToRelAddr(addr uint64, staticBase uint64) RelAddr { + return RelAddr(addr - staticBase) +} + // VariableReader provides a way of reading the local variables and formal // parameters of a function that are visible at the specified PC address. type VariableReader struct { @@ -22,10 +30,10 @@ type VariableReader struct { // Variables returns a VariableReader for the function or lexical block at off. // If onlyVisible is true only variables visible at pc will be returned by // the VariableReader. -func Variables(dwarf *dwarf.Data, off dwarf.Offset, pc uint64, line int, onlyVisible bool) *VariableReader { +func Variables(dwarf *dwarf.Data, off dwarf.Offset, pc RelAddr, line int, onlyVisible bool) *VariableReader { reader := dwarf.Reader() reader.Seek(off) - return &VariableReader{dwarf: dwarf, reader: reader, entry: nil, depth: 0, onlyVisible: onlyVisible, pc: pc, line: line, err: nil} + return &VariableReader{dwarf: dwarf, reader: reader, entry: nil, depth: 0, onlyVisible: onlyVisible, pc: uint64(pc), line: line, err: nil} } // Next reads the next variable entry, returns false if there aren't any. diff --git a/pkg/proc/arch.go b/pkg/proc/arch.go index e061a0bc..5d924631 100644 --- a/pkg/proc/arch.go +++ b/pkg/proc/arch.go @@ -17,7 +17,7 @@ type Arch interface { DerefTLS() bool FixFrameUnwindContext(fctxt *frame.FrameContext, pc uint64, bi *BinaryInfo) *frame.FrameContext RegSize(uint64) int - RegistersToDwarfRegisters(Registers) op.DwarfRegisters + RegistersToDwarfRegisters(regs Registers, staticBase uint64) op.DwarfRegisters GoroutineToDwarfRegisters(*G) op.DwarfRegisters } @@ -270,7 +270,7 @@ func maxAmd64DwarfRegister() int { // RegistersToDwarfRegisters converts hardware registers to the format used // by the DWARF expression interpreter. -func (a *AMD64) RegistersToDwarfRegisters(regs Registers) op.DwarfRegisters { +func (a *AMD64) RegistersToDwarfRegisters(regs Registers, staticBase uint64) op.DwarfRegisters { dregs := make([]*op.DwarfRegister, maxAmd64DwarfRegister()+1) dregs[amd64DwarfIPRegNum] = op.DwarfRegisterFromUint64(regs.PC()) @@ -292,7 +292,7 @@ func (a *AMD64) RegistersToDwarfRegisters(regs Registers) op.DwarfRegisters { } } - return op.DwarfRegisters{Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum} + return op.DwarfRegisters{StaticBase: staticBase, Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum} } // GoroutineToDwarfRegisters extract the saved DWARF registers from a parked @@ -302,5 +302,5 @@ func (a *AMD64) GoroutineToDwarfRegisters(g *G) op.DwarfRegisters { dregs[amd64DwarfIPRegNum] = op.DwarfRegisterFromUint64(g.PC) dregs[amd64DwarfSPRegNum] = op.DwarfRegisterFromUint64(g.SP) dregs[amd64DwarfBPRegNum] = op.DwarfRegisterFromUint64(g.BP) - return op.DwarfRegisters{Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum} + return op.DwarfRegisters{StaticBase: g.variable.bi.staticBase, Regs: dregs, ByteOrder: binary.LittleEndian, PCRegNum: amd64DwarfIPRegNum, SPRegNum: amd64DwarfSPRegNum, BPRegNum: amd64DwarfBPRegNum} } diff --git a/pkg/proc/bininfo.go b/pkg/proc/bininfo.go index 15ed8d63..d5d3f961 100644 --- a/pkg/proc/bininfo.go +++ b/pkg/proc/bininfo.go @@ -33,6 +33,8 @@ type BinaryInfo struct { closer io.Closer sepDebugCloser io.Closer + staticBase uint64 + // Maps package names to package paths, needed to lookup types inside DWARF info packageMap map[string]string @@ -82,11 +84,14 @@ var ErrUnsupportedWindowsArch = errors.New("unsupported architecture of windows/ // 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") +var ErrCouldNotDetermineRelocation = errors.New("could not determine the base address of a PIE") + const dwarfGoLanguage = 22 // DW_LANG_Go (from DWARF v5, section 7.12, page 231) type compileUnit struct { - Name string // univocal name for non-go compile units - LowPC, HighPC uint64 + Name string // univocal name for non-go compile units + LowPC uint64 + Ranges [][2]uint64 entry *dwarf.Entry // debug_info entry describing this compile unit isgo bool // true if this is the go compile unit @@ -288,7 +293,7 @@ func NewBinaryInfo(goos, goarch string) *BinaryInfo { // LoadBinaryInfo will load and store the information from the binary at 'path'. // It is expected this will be called in parallel with other initialization steps // so a sync.WaitGroup must be provided. -func (bi *BinaryInfo) LoadBinaryInfo(path string, wg *sync.WaitGroup) error { +func (bi *BinaryInfo) LoadBinaryInfo(path string, entryPoint uint64, wg *sync.WaitGroup) error { fi, err := os.Stat(path) if err == nil { bi.lastModified = fi.ModTime() @@ -296,13 +301,14 @@ func (bi *BinaryInfo) LoadBinaryInfo(path string, wg *sync.WaitGroup) error { switch bi.GOOS { case "linux": - return bi.LoadBinaryInfoElf(path, wg) + return bi.LoadBinaryInfoElf(path, entryPoint, wg) case "windows": - return bi.LoadBinaryInfoPE(path, wg) + return bi.LoadBinaryInfoPE(path, entryPoint, wg) case "darwin": - return bi.LoadBinaryInfoMacho(path, wg) + return bi.LoadBinaryInfoMacho(path, entryPoint, wg) } return errors.New("unsupported operating system") + return nil } // GStructOffset returns the offset of the G @@ -423,7 +429,7 @@ func (bi *BinaryInfo) LoadFromData(dwdata *dwarf.Data, debugFrameBytes, debugLin bi.dwarf = dwdata if debugFrameBytes != nil { - bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugFrameBytes)) + bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugFrameBytes), bi.staticBase) } bi.loclistInit(debugLocBytes) @@ -503,8 +509,10 @@ func (bi *BinaryInfo) loclistEntry(off int64, pc uint64) []byte { // findCompileUnit returns the compile unit containing address pc. func (bi *BinaryInfo) findCompileUnit(pc uint64) *compileUnit { for _, cu := range bi.compileUnits { - if pc >= cu.LowPC && pc < cu.HighPC { - return cu + for _, rng := range cu.Ranges { + if pc >= rng[0] && pc < rng[1] { + return cu + } } } return nil @@ -598,7 +606,7 @@ func (bi *BinaryInfo) openSeparateDebugInfo(exe *elf.File) (*os.File, *elf.File, } // LoadBinaryInfoElf specifically loads information from an ELF binary. -func (bi *BinaryInfo) LoadBinaryInfoElf(path string, wg *sync.WaitGroup) error { +func (bi *BinaryInfo) LoadBinaryInfoElf(path string, entryPoint uint64, wg *sync.WaitGroup) error { exe, err := os.OpenFile(path, 0, os.ModePerm) if err != nil { return err @@ -611,7 +619,17 @@ func (bi *BinaryInfo) LoadBinaryInfoElf(path string, wg *sync.WaitGroup) error { if elfFile.Machine != elf.EM_X86_64 { return ErrUnsupportedLinuxArch } + + if entryPoint != 0 { + bi.staticBase = entryPoint - elfFile.Entry + } else { + if elfFile.Type == elf.ET_DYN { + return ErrCouldNotDetermineRelocation + } + } + dwarfFile := elfFile + bi.dwarf, err = elfFile.DWARF() if err != nil { var sepFile *os.File @@ -660,7 +678,7 @@ func (bi *BinaryInfo) parseDebugFrameElf(exe *elf.File, wg *sync.WaitGroup) { return } - bi.frameEntries = frame.Parse(debugFrameData, frame.DwarfEndian(debugInfoData)) + bi.frameEntries = frame.Parse(debugFrameData, frame.DwarfEndian(debugInfoData), bi.staticBase) } func (bi *BinaryInfo) setGStructOffsetElf(exe *elf.File, wg *sync.WaitGroup) { @@ -703,8 +721,10 @@ func (bi *BinaryInfo) setGStructOffsetElf(exe *elf.File, wg *sync.WaitGroup) { // PE //////////////////////////////////////////////////////////////// +const _IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040 + // LoadBinaryInfoPE specifically loads information from a PE binary. -func (bi *BinaryInfo) LoadBinaryInfoPE(path string, wg *sync.WaitGroup) error { +func (bi *BinaryInfo) LoadBinaryInfoPE(path string, entryPoint uint64, wg *sync.WaitGroup) error { peFile, closer, err := openExecutablePathPE(path) if err != nil { return err @@ -718,6 +738,16 @@ func (bi *BinaryInfo) LoadBinaryInfoPE(path string, wg *sync.WaitGroup) error { return err } + //TODO(aarzilli): actually test this when Go supports PIE buildmode on Windows. + opth := peFile.OptionalHeader.(*pe.OptionalHeader64) + if entryPoint != 0 { + bi.staticBase = entryPoint - opth.ImageBase + } else { + if opth.DllCharacteristics&_IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE != 0 { + return ErrCouldNotDetermineRelocation + } + } + bi.dwarfReader = bi.dwarf.Reader() debugLineBytes, err := godwarf.GetDebugSectionPE(peFile, "line") @@ -766,7 +796,7 @@ func (bi *BinaryInfo) parseDebugFramePE(exe *pe.File, wg *sync.WaitGroup) { return } - bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes)) + bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), bi.staticBase) } // Borrowed from https://golang.org/src/cmd/internal/objfile/pe.go @@ -789,7 +819,7 @@ func findPESymbol(f *pe.File, name string) (*pe.Symbol, error) { // MACH-O //////////////////////////////////////////////////////////// // LoadBinaryInfoMacho specifically loads information from a Mach-O binary. -func (bi *BinaryInfo) LoadBinaryInfoMacho(path string, wg *sync.WaitGroup) error { +func (bi *BinaryInfo) LoadBinaryInfoMacho(path string, entryPoint uint64, wg *sync.WaitGroup) error { exe, err := macho.Open(path) if err != nil { return err @@ -844,5 +874,5 @@ func (bi *BinaryInfo) parseDebugFrameMacho(exe *macho.File, wg *sync.WaitGroup) return } - bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes)) + bi.frameEntries = frame.Parse(debugFrameBytes, frame.DwarfEndian(debugInfoBytes), bi.staticBase) } diff --git a/pkg/proc/core/core.go b/pkg/proc/core/core.go index 610f0816..60043ee0 100644 --- a/pkg/proc/core/core.go +++ b/pkg/proc/core/core.go @@ -191,7 +191,7 @@ func OpenCore(corePath, exePath string) (*Process, error) { } var wg sync.WaitGroup - err = p.bi.LoadBinaryInfo(exePath, &wg) + err = p.bi.LoadBinaryInfo(exePath, core.entryPoint, &wg) wg.Wait() if err == nil { err = p.bi.LoadError() diff --git a/pkg/proc/core/core_test.go b/pkg/proc/core/core_test.go index c8ad068a..5814085b 100644 --- a/pkg/proc/core/core_test.go +++ b/pkg/proc/core/core_test.go @@ -2,6 +2,7 @@ package core import ( "bytes" + "flag" "fmt" "go/constant" "io/ioutil" @@ -19,7 +20,14 @@ import ( "github.com/derekparker/delve/pkg/proc/test" ) +var buildMode string + func TestMain(m *testing.M) { + flag.StringVar(&buildMode, "test-buildmode", "", "selects build mode") + if buildMode != "" && buildMode != "pie" { + fmt.Fprintf(os.Stderr, "unknown build mode %q", buildMode) + os.Exit(1) + } os.Exit(test.RunTestsWithFixtures(m)) } @@ -146,7 +154,12 @@ func withCoreFile(t *testing.T, name, args string) *Process { if err != nil { t.Fatal(err) } - fix := test.BuildFixture(name, 0) + test.PathsToRemove = append(test.PathsToRemove, tempDir) + var buildFlags test.BuildFlags + if buildMode == "pie" { + buildFlags = test.BuildModePIE + } + fix := test.BuildFixture(name, buildFlags) bashCmd := fmt.Sprintf("cd %v && ulimit -c unlimited && GOTRACEBACK=crash %v %s", tempDir, fix.Path, args) exec.Command("bash", "-c", bashCmd).Run() cores, err := filepath.Glob(path.Join(tempDir, "core*")) @@ -161,11 +174,12 @@ func withCoreFile(t *testing.T, name, args string) *Process { p, err := OpenCore(corePath, fix.Path) if err != nil { + t.Errorf("ReadCore(%q) failed: %v", corePath, err) pat, err := ioutil.ReadFile("/proc/sys/kernel/core_pattern") t.Errorf("read core_pattern: %q, %v", pat, err) apport, err := ioutil.ReadFile("/var/log/apport.log") t.Errorf("read apport log: %q, %v", apport, err) - t.Fatalf("ReadCore() failed: %v", err) + t.Fatalf("previous errors") } return p } @@ -209,7 +223,7 @@ func TestCore(t *testing.T) { // Walk backward, because the current function seems to be main.main // in the actual call to panic(). for i := len(panickingStack) - 1; i >= 0; i-- { - if panickingStack[i].Current.Fn.Name == "main.main" { + if panickingStack[i].Current.Fn != nil && panickingStack[i].Current.Fn.Name == "main.main" { mainFrame = &panickingStack[i] } } diff --git a/pkg/proc/core/linux_amd64_core.go b/pkg/proc/core/linux_amd64_core.go index 5fcd1387..482a8a67 100644 --- a/pkg/proc/core/linux_amd64_core.go +++ b/pkg/proc/core/linux_amd64_core.go @@ -11,6 +11,7 @@ import ( "golang.org/x/arch/x86/x86asm" "github.com/derekparker/delve/pkg/proc" + "github.com/derekparker/delve/pkg/proc/linutil" ) // Copied from golang.org/x/sys/unix.PtraceRegs since it's not available on @@ -58,6 +59,9 @@ const NT_FILE elf.NType = 0x46494c45 // "FILE". // NT_X86_XSTATE is other registers, including AVX and such. const NT_X86_XSTATE elf.NType = 0x202 // Note type for notes containing X86 XSAVE area. +// NT_AUXV is the note type for notes containing a copy of the Auxv array +const NT_AUXV elf.NType = 0x6 + // PC returns the value of RIP. func (r *LinuxCoreRegisters) PC() uint64 { return r.Rip @@ -273,7 +277,7 @@ func readCore(corePath, exePath string) (*Core, error) { if coreFile.Type != elf.ET_CORE { return nil, fmt.Errorf("%v is not a core file", coreFile) } - if exeELF.Type != elf.ET_EXEC { + if exeELF.Type != elf.ET_EXEC && exeELF.Type != elf.ET_DYN { return nil, fmt.Errorf("%v is not an exe file", exeELF) } @@ -282,10 +286,12 @@ func readCore(corePath, exePath string) (*Core, error) { return nil, err } memory := buildMemory(coreFile, exeELF, exe, notes) + entryPoint := findEntryPoint(notes) core := &Core{ MemoryReader: memory, Threads: map[int]*Thread{}, + entryPoint: entryPoint, } var lastThread *Thread @@ -311,6 +317,8 @@ type Core struct { proc.MemoryReader Threads map[int]*Thread Pid int + + entryPoint uint64 } // Note is a note from the PT_NOTE prog. @@ -401,7 +409,7 @@ func readNote(r io.ReadSeeker) (*Note, error) { for i := 0; i < int(data.Count); i++ { entry := &LinuxNTFileEntry{} if err := binary.Read(descReader, binary.LittleEndian, entry); err != nil { - return nil, fmt.Errorf("reading NT_PRPSINFO entry %v: %v", i, err) + return nil, fmt.Errorf("reading NT_FILE entry %v: %v", i, err) } data.entries = append(data.entries, entry) } @@ -412,6 +420,8 @@ func readNote(r io.ReadSeeker) (*Note, error) { return nil, err } note.Desc = &fpregs + case NT_AUXV: + note.Desc = desc } if err := skipPadding(r, 4); err != nil { return nil, fmt.Errorf("aligning after desc: %v", err) @@ -471,6 +481,15 @@ func buildMemory(core, exeELF *elf.File, exe io.ReaderAt, notes []*Note) proc.Me return memory } +func findEntryPoint(notes []*Note) uint64 { + for _, note := range notes { + if note.Type == NT_AUXV { + return linutil.EntryPointFromAuxvAMD64(note.Desc.([]byte)) + } + } + return 0 +} + // LinuxPrPsInfo has various structures from the ELF spec and the Linux kernel. // AMD64 specific primarily because of unix.PtraceRegs, but also // because some of the fields are word sized. diff --git a/pkg/proc/dwarf_expr_test.go b/pkg/proc/dwarf_expr_test.go index 3fccd604..e3a62150 100644 --- a/pkg/proc/dwarf_expr_test.go +++ b/pkg/proc/dwarf_expr_test.go @@ -90,7 +90,7 @@ func dwarfExprCheck(t *testing.T, mem proc.MemoryReadWriter, regs op.DwarfRegist func dwarfRegisters(regs *core.Registers) op.DwarfRegisters { a := proc.AMD64Arch("linux") - dwarfRegs := a.RegistersToDwarfRegisters(regs) + dwarfRegs := a.RegistersToDwarfRegisters(regs, 0) dwarfRegs.CFA = defaultCFA dwarfRegs.FrameBase = defaultCFA return dwarfRegs diff --git a/pkg/proc/fncall.go b/pkg/proc/fncall.go index fba6daf2..b7239869 100644 --- a/pkg/proc/fncall.go +++ b/pkg/proc/fncall.go @@ -297,7 +297,7 @@ func funcCallArgFrame(fn *Function, actualArgs []*Variable, g *G, bi *BinaryInfo func funcCallArgs(fn *Function, bi *BinaryInfo, includeRet bool) (argFrameSize int64, formalArgs []funcCallArg, err error) { const CFA = 0x1000 - vrdr := reader.Variables(bi.dwarf, fn.offset, fn.Entry, int(^uint(0)>>1), false) + vrdr := reader.Variables(bi.dwarf, fn.offset, reader.ToRelAddr(fn.Entry, bi.staticBase), int(^uint(0)>>1), false) // typechecks arguments, calculates argument frame size for vrdr.Next() { diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 3ada3474..9fa7f0e3 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -82,6 +82,7 @@ import ( "github.com/derekparker/delve/pkg/logflags" "github.com/derekparker/delve/pkg/proc" + "github.com/derekparker/delve/pkg/proc/linutil" "github.com/mattn/go-isatty" "github.com/sirupsen/logrus" ) @@ -302,8 +303,16 @@ func (p *Process) Connect(conn net.Conn, path string, pid int) error { } } + var entryPoint uint64 + if auxv, err := p.conn.readAuxv(); err == nil { + // 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) + } + var wg sync.WaitGroup - err = p.bi.LoadBinaryInfo(path, &wg) + err = p.bi.LoadBinaryInfo(path, entryPoint, &wg) wg.Wait() if err == nil { err = p.bi.LoadError() diff --git a/pkg/proc/gdbserial/gdbserver_conn.go b/pkg/proc/gdbserial/gdbserver_conn.go index ec0633c6..b2dfed65 100644 --- a/pkg/proc/gdbserial/gdbserver_conn.go +++ b/pkg/proc/gdbserial/gdbserver_conn.go @@ -357,7 +357,7 @@ func (conn *gdbConn) readRegisterInfo() (err error) { } func (conn *gdbConn) readAnnex(annex string) ([]gdbRegisterInfo, error) { - tgtbuf, err := conn.qXfer("features", annex) + tgtbuf, err := conn.qXfer("features", annex, false) if err != nil { return nil, err } @@ -377,19 +377,28 @@ func (conn *gdbConn) readAnnex(annex string) ([]gdbRegisterInfo, error) { } func (conn *gdbConn) readExecFile() (string, error) { - outbuf, err := conn.qXfer("exec-file", "") + outbuf, err := conn.qXfer("exec-file", "", true) if err != nil { return "", err } return string(outbuf), nil } +func (conn *gdbConn) readAuxv() ([]byte, error) { + return conn.qXfer("auxv", "", true) +} + // qXfer executes a 'qXfer' read with the specified kind (i.e. feature, // exec-file, etc...) and annex. -func (conn *gdbConn) qXfer(kind, annex string) ([]byte, error) { +func (conn *gdbConn) qXfer(kind, annex string, binary bool) ([]byte, error) { out := []byte{} for { - buf, err := conn.exec([]byte(fmt.Sprintf("$qXfer:%s:read:%s:%x,fff", kind, annex, len(out))), "target features transfer") + cmd := []byte(fmt.Sprintf("$qXfer:%s:read:%s:%x,fff", kind, annex, len(out))) + err := conn.send(cmd) + if err != nil { + return nil, err + } + buf, err := conn.recv(cmd, "target features transfer", binary) if err != nil { return nil, err } diff --git a/pkg/proc/linutil/auxv.go b/pkg/proc/linutil/auxv.go new file mode 100644 index 00000000..786c3232 --- /dev/null +++ b/pkg/proc/linutil/auxv.go @@ -0,0 +1,39 @@ +package linutil + +import ( + "bytes" + "encoding/binary" +) + +const ( + _AT_NULL_AMD64 = 0 + _AT_ENTRY_AMD64 = 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 { + rd := bytes.NewBuffer(auxv) + + for { + var tag, val uint64 + err := binary.Read(rd, binary.LittleEndian, &tag) + if err != nil { + return 0 + } + err = binary.Read(rd, binary.LittleEndian, &val) + if err != nil { + return 0 + } + + switch tag { + case _AT_NULL_AMD64: + return 0 + case _AT_ENTRY_AMD64: + return val + } + } +} diff --git a/pkg/proc/linutil/doc.go b/pkg/proc/linutil/doc.go new file mode 100644 index 00000000..c126c672 --- /dev/null +++ b/pkg/proc/linutil/doc.go @@ -0,0 +1,4 @@ +// This package contains functions and data structures used by both the +// linux implementation of the native backend and the core backend to deal +// with structures used by the linux kernel. +package linutil diff --git a/pkg/proc/native/nonative_darwin.go b/pkg/proc/native/nonative_darwin.go index 790eca8e..f5e5d96b 100644 --- a/pkg/proc/native/nonative_darwin.go +++ b/pkg/proc/native/nonative_darwin.go @@ -75,6 +75,10 @@ func (dbp *Process) detach(kill bool) error { panic(ErrNativeBackendDisabled) } +func (dbp *Process) entryPoint() (uint64, error) { + panic(ErrNativeBackendDisabled) +} + // Blocked returns true if the thread is blocked func (t *Thread) Blocked() bool { panic(ErrNativeBackendDisabled) diff --git a/pkg/proc/native/proc.go b/pkg/proc/native/proc.go index 631d254d..1d6d3a47 100644 --- a/pkg/proc/native/proc.go +++ b/pkg/proc/native/proc.go @@ -194,9 +194,14 @@ func (dbp *Process) LoadInformation(path string) error { path = findExecutable(path, dbp.pid) + entryPoint, err := dbp.entryPoint() + if err != nil { + return err + } + wg.Add(1) go dbp.loadProcessInformation(&wg) - err := dbp.bi.LoadBinaryInfo(path, &wg) + err = dbp.bi.LoadBinaryInfo(path, entryPoint, &wg) wg.Wait() if err == nil { err = dbp.bi.LoadError() diff --git a/pkg/proc/native/proc_darwin.go b/pkg/proc/native/proc_darwin.go index d2e37155..84cf3642 100644 --- a/pkg/proc/native/proc_darwin.go +++ b/pkg/proc/native/proc_darwin.go @@ -463,3 +463,8 @@ func (dbp *Process) stop(trapthread *Thread) (err error) { func (dbp *Process) detach(kill bool) error { return PtraceDetach(dbp.pid, 0) } + +func (dbp *Process) entryPoint() (uint64, error) { + //TODO(aarzilli): implement this + return 0, nil +} diff --git a/pkg/proc/native/proc_linux.go b/pkg/proc/native/proc_linux.go index f4e1bb64..6527f3e2 100644 --- a/pkg/proc/native/proc_linux.go +++ b/pkg/proc/native/proc_linux.go @@ -19,6 +19,7 @@ import ( sys "golang.org/x/sys/unix" "github.com/derekparker/delve/pkg/proc" + "github.com/derekparker/delve/pkg/proc/linutil" "github.com/mattn/go-isatty" ) @@ -478,6 +479,15 @@ func (dbp *Process) detach(kill bool) error { return nil } +func (dbp *Process) entryPoint() (uint64, error) { + auxvbuf, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/auxv", dbp.pid)) + if err != nil { + return 0, fmt.Errorf("could not read auxiliary vector: %v", err) + } + + return linutil.EntryPointFromAuxvAMD64(auxvbuf), nil +} + func killProcess(pid int) error { return sys.Kill(pid, sys.SIGINT) } diff --git a/pkg/proc/native/proc_windows.go b/pkg/proc/native/proc_windows.go index 4208fb82..7cf7482e 100644 --- a/pkg/proc/native/proc_windows.go +++ b/pkg/proc/native/proc_windows.go @@ -20,6 +20,7 @@ import ( type OSProcessDetails struct { hProcess syscall.Handle breakThread int + entryPoint uint64 } func openExecutablePathPE(path string) (*pe.File, io.Closer, error) { @@ -271,6 +272,7 @@ func (dbp *Process) waitForDebugEvent(flags waitForDebugEventFlags) (threadID, e return 0, 0, err } } + dbp.os.entryPoint = uint64(debugInfo.BaseOfImage) dbp.os.hProcess = debugInfo.Process _, err = dbp.addThread(debugInfo.Thread, int(debugEvent.ThreadId), false, flags&waitSuspendNewThreads != 0) if err != nil { @@ -480,6 +482,10 @@ func (dbp *Process) detach(kill bool) error { return _DebugActiveProcessStop(uint32(dbp.pid)) } +func (dbp *Process) entryPoint() (uint64, error) { + return dbp.os.entryPoint, nil +} + func killProcess(pid int) error { p, err := os.FindProcess(pid) if err != nil { diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go index 5c53bf73..49843879 100644 --- a/pkg/proc/proc.go +++ b/pkg/proc/proc.go @@ -463,7 +463,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) { } } - addr, err := rdr.AddrFor("runtime.allglen") + addr, err := rdr.AddrFor("runtime.allglen", dbp.BinInfo().staticBase) if err != nil { return nil, err } @@ -475,10 +475,10 @@ func GoroutinesInfo(dbp Process) ([]*G, error) { allglen := binary.LittleEndian.Uint64(allglenBytes) rdr.Seek(0) - allgentryaddr, err := rdr.AddrFor("runtime.allgs") + allgentryaddr, err := rdr.AddrFor("runtime.allgs", dbp.BinInfo().staticBase) if err != nil { // try old name (pre Go 1.6) - allgentryaddr, err = rdr.AddrFor("runtime.allg") + allgentryaddr, err = rdr.AddrFor("runtime.allg", dbp.BinInfo().staticBase) if err != nil { return nil, err } diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index b54f57b5..f1dc01a7 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -21,6 +21,7 @@ import ( "github.com/derekparker/delve/pkg/dwarf/frame" "github.com/derekparker/delve/pkg/goversion" + "github.com/derekparker/delve/pkg/logflags" "github.com/derekparker/delve/pkg/proc" "github.com/derekparker/delve/pkg/proc/gdbserial" "github.com/derekparker/delve/pkg/proc/native" @@ -28,7 +29,7 @@ import ( ) var normalLoadConfig = proc.LoadConfig{true, 1, 64, 64, -1} -var testBackend string +var testBackend, buildMode string func init() { runtime.GOMAXPROCS(4) @@ -37,8 +38,16 @@ func init() { func TestMain(m *testing.M) { flag.StringVar(&testBackend, "backend", "", "selects backend") + flag.StringVar(&buildMode, "test-buildmode", "", "selects build mode") + var logConf string + flag.StringVar(&logConf, "log", "", "configures logging") flag.Parse() protest.DefaultTestBackend(&testBackend) + if buildMode != "" && buildMode != "pie" { + fmt.Fprintf(os.Stderr, "unknown build mode %q", buildMode) + os.Exit(1) + } + logflags.Setup(logConf != "", logConf) os.Exit(protest.RunTestsWithFixtures(m)) } @@ -47,6 +56,9 @@ func withTestProcess(name string, t testing.TB, fn func(p proc.Process, fixture } func withTestProcessArgs(name string, t testing.TB, wd string, args []string, buildFlags protest.BuildFlags, fn func(p proc.Process, fixture protest.Fixture)) { + if buildMode == "pie" { + buildFlags |= protest.BuildModePIE + } fixture := protest.BuildFixture(name, buildFlags) var p proc.Process var err error @@ -1825,7 +1837,7 @@ func TestPackageVariables(t *testing.T) { assertNoError(err, t, "PackageVariables()") failed := false for _, v := range vars { - if v.Unreadable != nil { + if v.Unreadable != nil && v.Unreadable.Error() != "no location attribute Location" { failed = true t.Logf("Unreadable variable %s: %v", v.Name, v.Unreadable) } @@ -2798,7 +2810,11 @@ func TestAttachDetach(t *testing.T) { if testBackend == "rr" { return } - fixture := protest.BuildFixture("testnextnethttp", 0) + var buildFlags protest.BuildFlags + if buildMode == "pie" { + buildFlags |= protest.BuildModePIE + } + fixture := protest.BuildFixture("testnextnethttp", buildFlags) cmd := exec.Command(fixture.Path) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr @@ -3097,6 +3113,9 @@ func TestAttachStripped(t *testing.T) { t.Log("-s does not produce stripped executables on macOS") return } + if buildMode != "" { + t.Skip("not enabled with buildmode=PIE") + } fixture := protest.BuildFixture("testnextnethttp", protest.LinkStrip) cmd := exec.Command(fixture.Path) cmd.Stdout = os.Stdout diff --git a/pkg/proc/stack.go b/pkg/proc/stack.go index c63009df..07258f3f 100644 --- a/pkg/proc/stack.go +++ b/pkg/proc/stack.go @@ -100,7 +100,7 @@ func ThreadStacktrace(thread Thread, depth int) ([]Stackframe, error) { if err != nil { return nil, err } - it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(regs), 0, nil, -1, nil) + it := newStackIterator(thread.BinInfo(), thread, thread.BinInfo().Arch.RegistersToDwarfRegisters(regs, thread.BinInfo().staticBase), 0, nil, -1, nil) return it.stacktrace(depth) } return g.Stacktrace(depth, false) @@ -117,7 +117,7 @@ func (g *G) stackIterator() (*stackIterator, error) { if err != nil { return nil, err } - return newStackIterator(g.variable.bi, g.Thread, g.variable.bi.Arch.RegistersToDwarfRegisters(regs), g.stackhi, stkbar, g.stkbarPos, g), nil + return newStackIterator(g.variable.bi, g.Thread, g.variable.bi.Arch.RegistersToDwarfRegisters(regs, g.variable.bi.staticBase), g.stackhi, stkbar, g.stkbarPos, g), nil } return newStackIterator(g.variable.bi, g.variable.mem, g.variable.bi.Arch.GoroutineToDwarfRegisters(g), g.stackhi, stkbar, g.stkbarPos, g), nil } @@ -442,7 +442,7 @@ func (it *stackIterator) appendInlineCalls(frames []Stackframe, frame Stackframe callpc-- } - irdr := reader.InlineStack(it.bi.dwarf, frame.Call.Fn.offset, callpc) + irdr := reader.InlineStack(it.bi.dwarf, frame.Call.Fn.offset, reader.ToRelAddr(callpc, it.bi.staticBase)) for irdr.Next() { entry, offset := reader.LoadAbstractOrigin(irdr.Entry(), it.dwarfReader) @@ -498,11 +498,11 @@ func (it *stackIterator) advanceRegs() (callFrameRegs op.DwarfRegisters, ret uin cfareg, err := it.executeFrameRegRule(0, framectx.CFA, 0) if cfareg == nil { it.err = fmt.Errorf("CFA becomes undefined at PC %#x", it.pc) - return op.DwarfRegisters{}, 0, 0 + return op.DwarfRegisters{StaticBase: it.bi.staticBase}, 0, 0 } it.regs.CFA = int64(cfareg.Uint64Val) - callFrameRegs = op.DwarfRegisters{ByteOrder: it.regs.ByteOrder, PCRegNum: it.regs.PCRegNum, SPRegNum: it.regs.SPRegNum, BPRegNum: it.regs.BPRegNum} + callFrameRegs = op.DwarfRegisters{StaticBase: it.bi.staticBase, ByteOrder: it.regs.ByteOrder, PCRegNum: it.regs.PCRegNum, SPRegNum: it.regs.SPRegNum, BPRegNum: it.regs.BPRegNum} // According to the standard the compiler should be responsible for emitting // rules for the RSP register so that it can then be used to calculate CFA, diff --git a/pkg/proc/test/support.go b/pkg/proc/test/support.go index 6ed14819..733bad85 100644 --- a/pkg/proc/test/support.go +++ b/pkg/proc/test/support.go @@ -40,6 +40,9 @@ type FixtureKey struct { // Fixtures is a map of fixtureKey{ Fixture.Name, buildFlags } to Fixture. var Fixtures = make(map[FixtureKey]Fixture) +// PathsToRemove is a list of files and directories to remove after running all the tests +var PathsToRemove []string + // FindFixturesDir will search for the directory holding all test fixtures // beginning with the current directory and searching up 10 directories. func FindFixturesDir() string { @@ -68,6 +71,7 @@ const ( EnableOptimization // EnableDWZCompression will enable DWZ compression of DWARF sections. EnableDWZCompression + BuildModePIE ) // BuildFixture will compile the fixture 'name' using the provided build flags. @@ -118,6 +122,9 @@ func BuildFixture(name string, flags BuildFlags) Fixture { if *EnableRace { buildFlags = append(buildFlags, "-race") } + if flags&BuildModePIE != 0 { + buildFlags = append(buildFlags, "-buildmode=pie") + } if path != "" { buildFlags = append(buildFlags, name+".go") } @@ -163,6 +170,18 @@ func RunTestsWithFixtures(m *testing.M) int { for _, f := range Fixtures { os.Remove(f.Path) } + + for _, p := range PathsToRemove { + fi, err := os.Stat(p) + if err != nil { + panic(err) + } + if fi.IsDir() { + SafeRemoveAll(p) + } else { + os.Remove(p) + } + } return status } diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go index 3a9139e0..3de30a8e 100644 --- a/pkg/proc/threads.go +++ b/pkg/proc/threads.go @@ -349,17 +349,17 @@ func removeInlinedCalls(dbp Process, pcs []uint64, topframe Stackframe) ([]uint6 return pcs, err } for _, rng := range ranges { - pcs = removePCsBetween(pcs, rng[0], rng[1]) + pcs = removePCsBetween(pcs, rng[0], rng[1], bi.staticBase) } irdr.SkipChildren() } return pcs, irdr.Err() } -func removePCsBetween(pcs []uint64, start, end uint64) []uint64 { +func removePCsBetween(pcs []uint64, start, end, staticBase uint64) []uint64 { out := pcs[:0] for _, pc := range pcs { - if pc < start || pc >= end { + if pc < start+staticBase || pc >= end+staticBase { out = append(out, pc) } } diff --git a/pkg/proc/types.go b/pkg/proc/types.go index 559501b3..1a0c3906 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -235,9 +235,13 @@ outer: if compdir != "" { cu.Name = filepath.Join(compdir, cu.Name) } - if ranges, _ := bi.dwarf.Ranges(entry); len(ranges) == 1 { - cu.LowPC = ranges[0][0] - cu.HighPC = ranges[0][1] + cu.Ranges, _ = bi.dwarf.Ranges(entry) + for i := range cu.Ranges { + cu.Ranges[i][0] += bi.staticBase + cu.Ranges[i][1] += bi.staticBase + } + if len(cu.Ranges) >= 1 { + cu.LowPC = cu.Ranges[0][0] } lineInfoOffset, _ := entry.Val(dwarf.AttrStmtList).(int64) if lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) { @@ -249,7 +253,7 @@ outer: logger.Printf(fmt, args) } } - cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn) + cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:]), logfn, bi.staticBase) } cu.producer, _ = entry.Val(dwarf.AttrProducer).(string) if cu.isgo && cu.producer != "" { @@ -352,12 +356,12 @@ outer: } } if pu != nil { - pu.variables = append(pu.variables, packageVar{n, entry.Offset, addr}) + pu.variables = append(pu.variables, packageVar{n, entry.Offset, addr + bi.staticBase}) } else { if !cu.isgo { n = "C." + n } - bi.packageVars = append(bi.packageVars, packageVar{n, entry.Offset, addr}) + bi.packageVars = append(bi.packageVars, packageVar{n, entry.Offset, addr + bi.staticBase}) } } @@ -390,8 +394,8 @@ outer: } if ranges, _ := bi.dwarf.Ranges(entry); len(ranges) == 1 { ok1 = true - lowpc = ranges[0][0] - highpc = ranges[0][1] + lowpc = ranges[0][0] + bi.staticBase + highpc = ranges[0][1] + bi.staticBase } name, ok2 := entry.Val(dwarf.AttrName).(string) var fn Function @@ -443,8 +447,8 @@ outer: callfile := cu.lineInfo.FileNames[callfileidx-1].Path cu.concreteInlinedFns = append(cu.concreteInlinedFns, inlinedFn{ Name: name, - LowPC: lowpc, - HighPC: highpc, + LowPC: lowpc + bi.staticBase, + HighPC: highpc + bi.staticBase, CallFile: callfile, CallLine: callline, Parent: &fn, @@ -540,7 +544,7 @@ func (bi *BinaryInfo) expandPackagesInType(expr ast.Expr) { func (bi *BinaryInfo) registerRuntimeTypeToDIE(entry *dwarf.Entry, ardr *reader.Reader) { if off, ok := entry.Val(godwarf.AttrGoRuntimeType).(uint64); ok { if _, ok := bi.runtimeTypeToDIE[off]; !ok { - bi.runtimeTypeToDIE[off] = runtimeTypeDIE{entry.Offset, -1} + bi.runtimeTypeToDIE[off+bi.staticBase] = runtimeTypeDIE{entry.Offset, -1} } } } diff --git a/pkg/proc/variables.go b/pkg/proc/variables.go index 6115da79..a8da5f97 100644 --- a/pkg/proc/variables.go +++ b/pkg/proc/variables.go @@ -193,7 +193,7 @@ func (err *IsNilErr) Error() string { } func globalScope(bi *BinaryInfo, mem MemoryReadWriter) *EvalScope { - return &EvalScope{Location: Location{}, Regs: op.DwarfRegisters{}, Mem: mem, Gvar: nil, BinInfo: bi, frameOffset: 0} + return &EvalScope{Location: Location{}, Regs: op.DwarfRegisters{StaticBase: bi.staticBase}, Mem: mem, Gvar: nil, BinInfo: bi, frameOffset: 0} } func (scope *EvalScope) newVariable(name string, addr uintptr, dwarfType godwarf.Type, mem MemoryReadWriter) *Variable { @@ -319,9 +319,12 @@ func newVariable(name string, addr uintptr, dwarfType godwarf.Type, bi *BinaryIn func resolveTypedef(typ godwarf.Type) godwarf.Type { for { - if tt, ok := typ.(*godwarf.TypedefType); ok { + switch tt := typ.(type) { + case *godwarf.TypedefType: typ = tt.Type - } else { + case *godwarf.QualType: + typ = tt.Type + default: return typ } } @@ -2046,7 +2049,7 @@ func (scope *EvalScope) Locals() ([]*Variable, error) { var vars []*Variable var depths []int - varReader := reader.Variables(scope.BinInfo.dwarf, scope.Fn.offset, scope.PC, scope.Line, true) + varReader := reader.Variables(scope.BinInfo.dwarf, scope.Fn.offset, reader.ToRelAddr(scope.PC, scope.BinInfo.staticBase), scope.Line, true) hasScopes := false for varReader.Next() { entry := varReader.Entry() diff --git a/pkg/terminal/command_test.go b/pkg/terminal/command_test.go index 81392648..5a17a6a3 100644 --- a/pkg/terminal/command_test.go +++ b/pkg/terminal/command_test.go @@ -24,12 +24,17 @@ import ( "github.com/derekparker/delve/service/rpccommon" ) -var testBackend string +var testBackend, buildMode string func TestMain(m *testing.M) { flag.StringVar(&testBackend, "backend", "", "selects backend") + flag.StringVar(&buildMode, "test-buildmode", "", "selects build mode") flag.Parse() test.DefaultTestBackend(&testBackend) + if buildMode != "" && buildMode != "pie" { + fmt.Fprintf(os.Stderr, "unknown build mode %q", buildMode) + os.Exit(1) + } os.Exit(test.RunTestsWithFixtures(m)) } @@ -104,6 +109,9 @@ func withTestTerminalBuildFlags(name string, t testing.TB, buildFlags test.Build t.Fatalf("couldn't start listener: %s\n", err) } defer listener.Close() + if buildMode == "pie" { + buildFlags |= test.BuildModePIE + } server := rpccommon.NewServer(&service.Config{ Listener: listener, ProcessArgs: []string{test.BuildFixture(name, buildFlags).Path}, diff --git a/scripts/make.go b/scripts/make.go index 57cba2d1..9940a7ef 100644 --- a/scripts/make.go +++ b/scripts/make.go @@ -16,7 +16,7 @@ import ( const DelveMainPackagePath = "github.com/derekparker/delve/cmd/dlv" var Verbose bool -var TestSet, TestRegex, TestBackend string +var TestSet, TestRegex, TestBackend, TestBuildMode string func NewMakeCommands() *cobra.Command { RootCommand := &cobra.Command{ @@ -62,10 +62,13 @@ func NewMakeCommands() *cobra.Command { Use the flags -s, -r and -b to specify which tests to run. Specifying nothing is equivalent to: go run scripts/make.go test -s all -b default - go run scripts/make.go test -s basic -b lldb - go run scripts/make.go test -s basic -b rr - -with lldb and rr tests only run if the relevant programs are installed.`, + go run scripts/make.go test -s basic -b lldb # if lldb-server is installed + go run scripts/make.go test -s basic -b rr # if rr is installed + + go run scripts/make.go test -s basic -m pie # only on linux + go run scripts/make.go test -s core -m pie # only on linux + go run scripts/make.go test -s +`, Run: testCmd, } test.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "Verbose tests") @@ -81,6 +84,11 @@ with lldb and rr tests only run if the relevant programs are installed.`, lldb lldb backend rr rr backend +This option can only be specified if testset is basic or a single package.`) + test.PersistentFlags().StringVarP(&TestBuildMode, "test-build-mode", "m", "", `Runs tests compiling with the specified build mode, one of either: + normal normal buildmode (default) + pie PIE buildmode + This option can only be specified if testset is basic or a single package.`) RootCommand.AddCommand(test) @@ -253,21 +261,30 @@ func testCmd(cmd *cobra.Command, args []string) { return } - if TestSet == "" && TestBackend == "" { + if TestSet == "" && TestBackend == "" && TestBuildMode == "" { if TestRegex != "" { fmt.Printf("Can not use --test-run without --test-set\n") os.Exit(1) } fmt.Println("Testing default backend") - testCmdIntl("all", "", "default") + testCmdIntl("all", "", "default", "normal") if inpath("lldb-server") { fmt.Println("\nTesting LLDB backend") - testCmdIntl("basic", "", "lldb") + testCmdIntl("basic", "", "lldb", "normal") } if inpath("rr") { fmt.Println("\nTesting RR backend") - testCmdIntl("basic", "", "rr") + testCmdIntl("basic", "", "rr", "normal") + } + if runtime.GOOS == "linux" { + fmt.Println("\nTesting PIE buildmode, default backend") + testCmdIntl("basic", "", "default", "pie") + testCmdIntl("core", "", "default", "pie") + } + if runtime.GOOS == "linux" && inpath("rr") { + fmt.Println("\nTesting PIE buildmode, RR backend") + testCmdIntl("basic", "", "rr", "pie") } return } @@ -280,10 +297,14 @@ func testCmd(cmd *cobra.Command, args []string) { TestBackend = "default" } - testCmdIntl(TestSet, TestRegex, TestBackend) + if TestBuildMode == "" { + TestBuildMode = "normal" + } + + testCmdIntl(TestSet, TestRegex, TestBackend, TestBuildMode) } -func testCmdIntl(testSet, testRegex, testBackend string) { +func testCmdIntl(testSet, testRegex, testBackend, testBuildMode string) { testPackages := testSetToPackages(testSet) if len(testPackages) == 0 { fmt.Printf("Unknown test set %q\n", testSet) @@ -304,12 +325,21 @@ func testCmdIntl(testSet, testRegex, testBackend string) { backendFlag = "-backend=" + testBackend } + buildModeFlag := "" + if testBuildMode != "" && testBuildMode != "normal" { + if testSet != "basic" && len(testPackages) != 1 { + fmt.Printf("Can not use test-buildmode with test set %q\n", testSet) + os.Exit(1) + } + buildModeFlag = "-test-buildmode=" + testBuildMode + } + if len(testPackages) > 3 { - execute("go", "test", testFlags(), buildFlags(), testPackages, backendFlag) + executeq("go", "test", testFlags(), buildFlags(), testPackages, backendFlag, buildModeFlag) } else if testRegex != "" { - execute("go", "test", testFlags(), buildFlags(), testPackages, "-run="+testRegex, backendFlag) + execute("go", "test", testFlags(), buildFlags(), testPackages, "-run="+testRegex, backendFlag, buildModeFlag) } else { - execute("go", "test", testFlags(), buildFlags(), testPackages, backendFlag) + execute("go", "test", testFlags(), buildFlags(), testPackages, backendFlag, buildModeFlag) } } @@ -334,6 +364,13 @@ func testSetToPackages(testSet string) []string { } } +func defaultBackend() string { + if runtime.GOOS == "darwin" { + return "lldb" + } + return "native" +} + func inpath(exe string) bool { path, _ := exec.LookPath(exe) return path != "" diff --git a/service/test/integration1_test.go b/service/test/integration1_test.go index 38669a6d..3bef7843 100644 --- a/service/test/integration1_test.go +++ b/service/test/integration1_test.go @@ -21,6 +21,12 @@ import ( ) func withTestClient1(name string, t *testing.T, fn func(c *rpc1.RPCClient)) { + withTestClient1Extended(name, t, func(c *rpc1.RPCClient, fixture protest.Fixture) { + fn(c) + }) +} + +func withTestClient1Extended(name string, t *testing.T, fn func(c *rpc1.RPCClient, fixture protest.Fixture)) { if testBackend == "rr" { protest.MustHaveRecordingAllowed(t) } @@ -29,9 +35,14 @@ func withTestClient1(name string, t *testing.T, fn func(c *rpc1.RPCClient)) { t.Fatalf("couldn't start listener: %s\n", err) } defer listener.Close() + var buildFlags protest.BuildFlags + if buildMode == "pie" { + buildFlags = protest.BuildModePIE + } + fixture := protest.BuildFixture(name, buildFlags) server := rpccommon.NewServer(&service.Config{ Listener: listener, - ProcessArgs: []string{protest.BuildFixture(name, 0).Path}, + ProcessArgs: []string{fixture.Path}, Backend: testBackend, }) if err := server.Run(); err != nil { @@ -42,7 +53,7 @@ func withTestClient1(name string, t *testing.T, fn func(c *rpc1.RPCClient)) { client.Detach(true) }() - fn(client) + fn(client, fixture) } func Test1RunWithInvalidPath(t *testing.T) { @@ -618,25 +629,24 @@ func Test1ClientServer_FindLocations(t *testing.T) { findLocationHelper(t, c, "main.stacktraceme", false, 1, stacktracemeAddr) }) - withTestClient1("locationsUpperCase", t, func(c *rpc1.RPCClient) { + withTestClient1Extended("locationsUpperCase", t, func(c *rpc1.RPCClient, fixture protest.Fixture) { // Upper case findLocationHelper(t, c, "locationsUpperCase.go:6", false, 1, 0) // Fully qualified path - path := protest.Fixtures[protest.FixtureKey{"locationsUpperCase", 0}].Source - findLocationHelper(t, c, path+":6", false, 1, 0) - bp, err := c.CreateBreakpoint(&api.Breakpoint{File: path, Line: 6}) + findLocationHelper(t, c, fixture.Source+":6", false, 1, 0) + bp, err := c.CreateBreakpoint(&api.Breakpoint{File: fixture.Source, Line: 6}) if err != nil { - t.Fatalf("Could not set breakpoint in %s: %v\n", path, err) + t.Fatalf("Could not set breakpoint in %s: %v\n", fixture.Source, err) } c.ClearBreakpoint(bp.ID) // Allow `/` or `\` on Windows if runtime.GOOS == "windows" { - findLocationHelper(t, c, filepath.FromSlash(path)+":6", false, 1, 0) - bp, err = c.CreateBreakpoint(&api.Breakpoint{File: filepath.FromSlash(path), Line: 6}) + findLocationHelper(t, c, filepath.FromSlash(fixture.Source)+":6", false, 1, 0) + bp, err = c.CreateBreakpoint(&api.Breakpoint{File: filepath.FromSlash(fixture.Source), Line: 6}) if err != nil { - t.Fatalf("Could not set breakpoint in %s: %v\n", filepath.FromSlash(path), err) + t.Fatalf("Could not set breakpoint in %s: %v\n", filepath.FromSlash(fixture.Source), err) } c.ClearBreakpoint(bp.ID) } @@ -648,10 +658,10 @@ func Test1ClientServer_FindLocations(t *testing.T) { shouldWrongCaseBeError = false numExpectedMatches = 1 } - findLocationHelper(t, c, strings.ToLower(path)+":6", shouldWrongCaseBeError, numExpectedMatches, 0) - bp, err = c.CreateBreakpoint(&api.Breakpoint{File: strings.ToLower(path), Line: 6}) + findLocationHelper(t, c, strings.ToLower(fixture.Source)+":6", shouldWrongCaseBeError, numExpectedMatches, 0) + bp, err = c.CreateBreakpoint(&api.Breakpoint{File: strings.ToLower(fixture.Source), Line: 6}) if (err == nil) == shouldWrongCaseBeError { - t.Fatalf("Could not set breakpoint in %s: %v\n", strings.ToLower(path), err) + t.Fatalf("Could not set breakpoint in %s: %v\n", strings.ToLower(fixture.Source), err) } c.ClearBreakpoint(bp.ID) }) diff --git a/service/test/integration2_test.go b/service/test/integration2_test.go index 4c49da79..e6c07b72 100644 --- a/service/test/integration2_test.go +++ b/service/test/integration2_test.go @@ -25,26 +25,43 @@ import ( ) var normalLoadConfig = api.LoadConfig{true, 1, 64, 64, -1} -var testBackend string +var testBackend, buildMode string func TestMain(m *testing.M) { flag.StringVar(&testBackend, "backend", "", "selects backend") + flag.StringVar(&buildMode, "test-buildmode", "", "selects build mode") var logOutput string flag.StringVar(&logOutput, "log-output", "", "configures log output") flag.Parse() protest.DefaultTestBackend(&testBackend) + if buildMode != "" && buildMode != "pie" { + fmt.Fprintf(os.Stderr, "unknown build mode %q", buildMode) + os.Exit(1) + } logflags.Setup(logOutput != "", logOutput) os.Exit(protest.RunTestsWithFixtures(m)) } func withTestClient2(name string, t *testing.T, fn func(c service.Client)) { + withTestClient2Extended(name, t, func(c service.Client, fixture protest.Fixture) { + fn(c) + }) +} + +func withTestClient2Extended(name string, t *testing.T, fn func(c service.Client, fixture protest.Fixture)) { if testBackend == "rr" { protest.MustHaveRecordingAllowed(t) } listener, clientConn := service.ListenerPipe() + defer listener.Close() + var buildFlags protest.BuildFlags + if buildMode == "pie" { + buildFlags = protest.BuildModePIE + } + fixture := protest.BuildFixture(name, buildFlags) server := rpccommon.NewServer(&service.Config{ Listener: listener, - ProcessArgs: []string{protest.BuildFixture(name, 0).Path}, + ProcessArgs: []string{fixture.Path}, Backend: testBackend, }) if err := server.Run(); err != nil { @@ -59,7 +76,7 @@ func withTestClient2(name string, t *testing.T, fn func(c service.Client)) { } }() - fn(client) + fn(client, fixture) } func TestRunWithInvalidPath(t *testing.T) { @@ -668,25 +685,24 @@ func TestClientServer_FindLocations(t *testing.T) { findLocationHelper(t, c, "main.stacktraceme", false, 1, stacktracemeAddr) }) - withTestClient2("locationsUpperCase", t, func(c service.Client) { + withTestClient2Extended("locationsUpperCase", t, func(c service.Client, fixture protest.Fixture) { // Upper case findLocationHelper(t, c, "locationsUpperCase.go:6", false, 1, 0) // Fully qualified path - path := protest.Fixtures[protest.FixtureKey{"locationsUpperCase", 0}].Source - findLocationHelper(t, c, path+":6", false, 1, 0) - bp, err := c.CreateBreakpoint(&api.Breakpoint{File: path, Line: 6}) + findLocationHelper(t, c, fixture.Source+":6", false, 1, 0) + bp, err := c.CreateBreakpoint(&api.Breakpoint{File: fixture.Source, Line: 6}) if err != nil { - t.Fatalf("Could not set breakpoint in %s: %v\n", path, err) + t.Fatalf("Could not set breakpoint in %s: %v\n", fixture.Source, err) } c.ClearBreakpoint(bp.ID) // Allow `/` or `\` on Windows if runtime.GOOS == "windows" { - findLocationHelper(t, c, filepath.FromSlash(path)+":6", false, 1, 0) - bp, err = c.CreateBreakpoint(&api.Breakpoint{File: filepath.FromSlash(path), Line: 6}) + findLocationHelper(t, c, filepath.FromSlash(fixture.Source)+":6", false, 1, 0) + bp, err = c.CreateBreakpoint(&api.Breakpoint{File: filepath.FromSlash(fixture.Source), Line: 6}) if err != nil { - t.Fatalf("Could not set breakpoint in %s: %v\n", filepath.FromSlash(path), err) + t.Fatalf("Could not set breakpoint in %s: %v\n", filepath.FromSlash(fixture.Source), err) } c.ClearBreakpoint(bp.ID) } @@ -698,10 +714,10 @@ func TestClientServer_FindLocations(t *testing.T) { shouldWrongCaseBeError = false numExpectedMatches = 1 } - findLocationHelper(t, c, strings.ToLower(path)+":6", shouldWrongCaseBeError, numExpectedMatches, 0) - bp, err = c.CreateBreakpoint(&api.Breakpoint{File: strings.ToLower(path), Line: 6}) + findLocationHelper(t, c, strings.ToLower(fixture.Source)+":6", shouldWrongCaseBeError, numExpectedMatches, 0) + bp, err = c.CreateBreakpoint(&api.Breakpoint{File: strings.ToLower(fixture.Source), Line: 6}) if (err == nil) == shouldWrongCaseBeError { - t.Fatalf("Could not set breakpoint in %s: %v\n", strings.ToLower(path), err) + t.Fatalf("Could not set breakpoint in %s: %v\n", strings.ToLower(fixture.Source), err) } c.ClearBreakpoint(bp.ID) }) @@ -1254,6 +1270,9 @@ func TestClientServer_FpRegisters(t *testing.T) { func TestClientServer_RestartBreakpointPosition(t *testing.T) { protest.AllowRecording(t) + if buildMode == "pie" { + t.Skip("not meaningful in PIE mode") + } withTestClient2("locationsprog2", t, func(c service.Client) { bpBefore, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.afunction", Line: -1, Tracepoint: true, Name: "this"}) addrBefore := bpBefore.Addr diff --git a/service/test/variables_test.go b/service/test/variables_test.go index 8f773784..d85f0da0 100644 --- a/service/test/variables_test.go +++ b/service/test/variables_test.go @@ -107,7 +107,11 @@ func setVariable(p proc.Process, symbol, value string) error { } func withTestProcess(name string, t *testing.T, fn func(p proc.Process, fixture protest.Fixture)) { - fixture := protest.BuildFixture(name, 0) + var buildFlags protest.BuildFlags + if buildMode == "pie" { + buildFlags = protest.BuildModePIE + } + fixture := protest.BuildFixture(name, buildFlags) var p proc.Process var err error var tracedir string