From 74c98bc9616ad4ee324eb4d31717b61f4a6ab8ce Mon Sep 17 00:00:00 2001 From: aarzilli Date: Tue, 29 May 2018 17:01:51 +0200 Subject: [PATCH] proc: support position independent executables (PIE) Support for position independent executables (PIE) on the native linux backend, the gdbserver backend on linux and the core backend. Also implemented in the windows native backend, but it can't be tested because go doesn't support PIE on windows yet. --- pkg/dwarf/frame/entries.go | 21 +++------ pkg/dwarf/frame/entries_test.go | 54 ++++++++++++++++------- pkg/dwarf/frame/parser.go | 12 ++--- pkg/dwarf/frame/parser_test.go | 2 +- pkg/dwarf/frame/table.go | 2 +- pkg/dwarf/line/line_parser.go | 10 +++-- pkg/dwarf/line/line_parser_test.go | 6 +-- pkg/dwarf/line/state_machine.go | 4 +- pkg/dwarf/line/state_machine_test.go | 2 +- pkg/dwarf/op/op.go | 2 +- pkg/dwarf/op/regs.go | 2 + pkg/dwarf/reader/reader.go | 12 ++--- pkg/dwarf/reader/variables.go | 12 ++++- pkg/proc/arch.go | 8 ++-- pkg/proc/bininfo.go | 60 ++++++++++++++++++------- pkg/proc/core/core.go | 2 +- pkg/proc/core/core_test.go | 20 +++++++-- pkg/proc/core/linux_amd64_core.go | 23 +++++++++- pkg/proc/dwarf_expr_test.go | 2 +- pkg/proc/fncall.go | 2 +- pkg/proc/gdbserial/gdbserver.go | 11 ++++- pkg/proc/gdbserial/gdbserver_conn.go | 17 ++++++-- pkg/proc/linutil/auxv.go | 39 +++++++++++++++++ pkg/proc/linutil/doc.go | 4 ++ pkg/proc/native/nonative_darwin.go | 4 ++ pkg/proc/native/proc.go | 7 ++- pkg/proc/native/proc_darwin.go | 5 +++ pkg/proc/native/proc_linux.go | 10 +++++ pkg/proc/native/proc_windows.go | 6 +++ pkg/proc/proc.go | 6 +-- pkg/proc/proc_test.go | 25 +++++++++-- pkg/proc/stack.go | 10 ++--- pkg/proc/test/support.go | 19 ++++++++ pkg/proc/threads.go | 6 +-- pkg/proc/types.go | 26 ++++++----- pkg/proc/variables.go | 11 +++-- pkg/terminal/command_test.go | 10 ++++- scripts/make.go | 65 ++++++++++++++++++++++------ service/test/integration1_test.go | 36 +++++++++------ service/test/integration2_test.go | 47 ++++++++++++++------ service/test/variables_test.go | 6 ++- 41 files changed, 467 insertions(+), 161 deletions(-) create mode 100644 pkg/proc/linutil/auxv.go create mode 100644 pkg/proc/linutil/doc.go 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