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.
This commit is contained in:
aarzilli 2018-05-29 17:01:51 +02:00 committed by Derek Parker
parent 4e4ed02948
commit 74c98bc961
41 changed files with 467 additions and 161 deletions

@ -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
}

@ -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

@ -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.

@ -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)
}
}

@ -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) {

@ -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)

@ -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) {

@ -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) {

@ -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)

@ -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
}

@ -6,6 +6,8 @@ import (
)
type DwarfRegisters struct {
StaticBase uint64
CFA int64
FrameBase int64
ObjBase int64

@ -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.

@ -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.

@ -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}
}

@ -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)
}

@ -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()

@ -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]
}
}

@ -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.

@ -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

@ -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() {

@ -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()

@ -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
}

39
pkg/proc/linutil/auxv.go Normal file

@ -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
}
}
}

4
pkg/proc/linutil/doc.go Normal file

@ -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

@ -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)

@ -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()

@ -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
}

@ -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)
}

@ -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 {

@ -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
}

@ -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

@ -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,

@ -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
}

@ -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)
}
}

@ -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}
}
}
}

@ -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()

@ -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},

@ -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 != ""

@ -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)
})

@ -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

@ -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