proc: Replaced FunctionEntryToFirstLine with disassembly alternative
- Unlike FunctionEntryToFirstLine can skip the prologue on functions that are defined on a single line, either because they weren't formatted or because they were autogenerated - Can skip the prologue on most functions when setting a breakpoint with the filename:line syntax Fixes #396
This commit is contained in:
parent
c277b27157
commit
4116d66c8d
27
_fixtures/callme.go
Normal file
27
_fixtures/callme.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func callme(i int) {
|
||||||
|
fmt.Println("got:", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
const nBytes = 10
|
||||||
|
var zeroarr [nBytes]byte
|
||||||
|
|
||||||
|
func callme2() {
|
||||||
|
for i := 0; i < nBytes; i++ {
|
||||||
|
zeroarr[i] = '0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func callme3() {
|
||||||
|
callme2()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
callme(i)
|
||||||
|
}
|
||||||
|
callme3()
|
||||||
|
}
|
@ -31,10 +31,13 @@ func (thread *Thread) Disassemble(startPC, endPC uint64, currentGoroutine bool)
|
|||||||
r := make([]AsmInstruction, 0, len(mem)/15)
|
r := make([]AsmInstruction, 0, len(mem)/15)
|
||||||
pc := startPC
|
pc := startPC
|
||||||
|
|
||||||
regs, _ := thread.Registers()
|
|
||||||
var curpc uint64
|
var curpc uint64
|
||||||
if regs != nil {
|
var regs Registers
|
||||||
curpc = regs.PC()
|
if currentGoroutine {
|
||||||
|
regs, _ = thread.Registers()
|
||||||
|
if regs != nil {
|
||||||
|
curpc = regs.PC()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for len(mem) > 0 {
|
for len(mem) > 0 {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package proc
|
package proc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"debug/gosym"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"rsc.io/x86/x86asm"
|
"rsc.io/x86/x86asm"
|
||||||
)
|
)
|
||||||
@ -111,3 +112,50 @@ func (thread *Thread) resolveCallArg(inst *ArchInst, currentGoroutine bool, regs
|
|||||||
}
|
}
|
||||||
return &Location{PC: pc, File: file, Line: line, Fn: fn}
|
return &Location{PC: pc, File: file, Line: line, Fn: fn}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type instrseq []x86asm.Op
|
||||||
|
|
||||||
|
var windowsPrologue = instrseq{x86asm.MOV, x86asm.MOV, x86asm.LEA, x86asm.CMP, x86asm.JBE}
|
||||||
|
var windowsPrologue2 = instrseq{x86asm.MOV, x86asm.MOV, x86asm.CMP, x86asm.JBE}
|
||||||
|
var unixPrologue = instrseq{x86asm.MOV, x86asm.LEA, x86asm.CMP, x86asm.JBE}
|
||||||
|
var unixPrologue2 = instrseq{x86asm.MOV, x86asm.CMP, x86asm.JBE}
|
||||||
|
var prologues = []instrseq{windowsPrologue, windowsPrologue2, unixPrologue, unixPrologue2}
|
||||||
|
|
||||||
|
// FirstPCAfterPrologue returns the address of the first instruction after the prologue for function fn
|
||||||
|
// If sameline is set FirstPCAfterPrologue will always return an address associated with the same line as fn.Entry
|
||||||
|
func (dbp *Process) FirstPCAfterPrologue(fn *gosym.Func, sameline bool) (uint64, error) {
|
||||||
|
text, err := dbp.CurrentThread.Disassemble(fn.Entry, fn.End, false)
|
||||||
|
if err != nil {
|
||||||
|
return fn.Entry, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(text) <= 0 {
|
||||||
|
return fn.Entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prologue := range prologues {
|
||||||
|
if len(prologue) >= len(text) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if checkPrologue(text, prologue) {
|
||||||
|
r := &text[len(prologue)]
|
||||||
|
if sameline {
|
||||||
|
if r.Loc.Line != text[0].Loc.Line {
|
||||||
|
return fn.Entry, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r.Loc.PC, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn.Entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkPrologue(s []AsmInstruction, prologuePattern instrseq) bool {
|
||||||
|
for i, op := range prologuePattern {
|
||||||
|
if s[i].Inst.Op != op {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
38
proc/proc.go
38
proc/proc.go
@ -9,7 +9,6 @@ import (
|
|||||||
"go/constant"
|
"go/constant"
|
||||||
"go/token"
|
"go/token"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -161,12 +160,15 @@ func (dbp *Process) LoadInformation(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FindFileLocation returns the PC for a given file:line.
|
// FindFileLocation returns the PC for a given file:line.
|
||||||
// Assumes that `file` is normailzed to lower case and '/' on Windows.
|
// Assumes that `file` is normailzed to lower case and '/' on Windows.
|
||||||
func (dbp *Process) FindFileLocation(fileName string, lineno int) (uint64, error) {
|
func (dbp *Process) FindFileLocation(fileName string, lineno int) (uint64, error) {
|
||||||
pc, _, err := dbp.goSymTable.LineToPC(fileName, lineno)
|
pc, fn, err := dbp.goSymTable.LineToPC(fileName, lineno)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
if fn.Entry == pc {
|
||||||
|
pc, _ = dbp.FirstPCAfterPrologue(fn, true)
|
||||||
|
}
|
||||||
return pc, nil
|
return pc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +185,7 @@ func (dbp *Process) FindFunctionLocation(funcName string, firstLine bool, lineOf
|
|||||||
}
|
}
|
||||||
|
|
||||||
if firstLine {
|
if firstLine {
|
||||||
return dbp.FunctionEntryToFirstLine(origfn.Entry)
|
return dbp.FirstPCAfterPrologue(origfn, false)
|
||||||
} else if lineOffset > 0 {
|
} else if lineOffset > 0 {
|
||||||
filename, lineno, _ := dbp.goSymTable.PCToLine(origfn.Entry)
|
filename, lineno, _ := dbp.goSymTable.PCToLine(origfn.Entry)
|
||||||
breakAddr, _, err := dbp.goSymTable.LineToPC(filename, lineno+lineOffset)
|
breakAddr, _, err := dbp.goSymTable.LineToPC(filename, lineno+lineOffset)
|
||||||
@ -193,34 +195,6 @@ func (dbp *Process) FindFunctionLocation(funcName string, firstLine bool, lineOf
|
|||||||
return origfn.Entry, nil
|
return origfn.Entry, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dbp *Process) FunctionEntryToFirstLine(entry uint64) (uint64, error) {
|
|
||||||
filename, lineno, startfn := dbp.goSymTable.PCToLine(entry)
|
|
||||||
if filepath.Ext(filename) != ".go" {
|
|
||||||
return entry, nil
|
|
||||||
}
|
|
||||||
if startfn == nil {
|
|
||||||
return entry, nil
|
|
||||||
}
|
|
||||||
funcName := startfn.Name
|
|
||||||
|
|
||||||
for {
|
|
||||||
lineno++
|
|
||||||
pc, fn, _ := dbp.goSymTable.LineToPC(filename, lineno)
|
|
||||||
if fn != nil {
|
|
||||||
if fn.Name != funcName {
|
|
||||||
if strings.Contains(fn.Name, funcName) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if fn.Name == funcName {
|
|
||||||
return pc, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return entry, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentLocation returns the location of the current thread.
|
// CurrentLocation returns the location of the current thread.
|
||||||
func (dbp *Process) CurrentLocation() (*Location, error) {
|
func (dbp *Process) CurrentLocation() (*Location, error) {
|
||||||
return dbp.CurrentThread.Location()
|
return dbp.CurrentThread.Location()
|
||||||
|
@ -311,6 +311,7 @@ func testnext(program string, testcases []nextTest, initialLocation string, t *t
|
|||||||
|
|
||||||
func TestNextGeneral(t *testing.T) {
|
func TestNextGeneral(t *testing.T) {
|
||||||
testcases := []nextTest{
|
testcases := []nextTest{
|
||||||
|
{17, 19},
|
||||||
{19, 20},
|
{19, 20},
|
||||||
{20, 23},
|
{20, 23},
|
||||||
{23, 24},
|
{23, 24},
|
||||||
@ -331,6 +332,7 @@ func TestNextGeneral(t *testing.T) {
|
|||||||
|
|
||||||
func TestNextConcurrent(t *testing.T) {
|
func TestNextConcurrent(t *testing.T) {
|
||||||
testcases := []nextTest{
|
testcases := []nextTest{
|
||||||
|
{8, 9},
|
||||||
{9, 10},
|
{9, 10},
|
||||||
{10, 11},
|
{10, 11},
|
||||||
}
|
}
|
||||||
@ -371,6 +373,7 @@ func TestNextConcurrent(t *testing.T) {
|
|||||||
func TestNextConcurrentVariant2(t *testing.T) {
|
func TestNextConcurrentVariant2(t *testing.T) {
|
||||||
// Just like TestNextConcurrent but instead of removing the initial breakpoint we check that when it happens is for other goroutines
|
// Just like TestNextConcurrent but instead of removing the initial breakpoint we check that when it happens is for other goroutines
|
||||||
testcases := []nextTest{
|
testcases := []nextTest{
|
||||||
|
{8, 9},
|
||||||
{9, 10},
|
{9, 10},
|
||||||
{10, 11},
|
{10, 11},
|
||||||
}
|
}
|
||||||
@ -419,6 +422,7 @@ func TestNextConcurrentVariant2(t *testing.T) {
|
|||||||
|
|
||||||
func TestNextFunctionReturn(t *testing.T) {
|
func TestNextFunctionReturn(t *testing.T) {
|
||||||
testcases := []nextTest{
|
testcases := []nextTest{
|
||||||
|
{13, 14},
|
||||||
{14, 15},
|
{14, 15},
|
||||||
{15, 35},
|
{15, 35},
|
||||||
}
|
}
|
||||||
@ -427,6 +431,7 @@ func TestNextFunctionReturn(t *testing.T) {
|
|||||||
|
|
||||||
func TestNextFunctionReturnDefer(t *testing.T) {
|
func TestNextFunctionReturnDefer(t *testing.T) {
|
||||||
testcases := []nextTest{
|
testcases := []nextTest{
|
||||||
|
{5, 8},
|
||||||
{8, 9},
|
{8, 9},
|
||||||
{9, 10},
|
{9, 10},
|
||||||
{10, 7},
|
{10, 7},
|
||||||
@ -1539,3 +1544,10 @@ func TestIssue332_Part2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssue396(t *testing.T) {
|
||||||
|
withTestProcess("callme", t, func(p *Process, fixture protest.Fixture) {
|
||||||
|
_, err := p.FindFunctionLocation("main.init", true, -1)
|
||||||
|
assertNoError(err, t, "FindFunctionLocation()")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -268,7 +268,8 @@ func (loc *AddrLocationSpec) Find(d *Debugger, scope *proc.EvalScope, locStr str
|
|||||||
addr, _ := constant.Uint64Val(v.Value)
|
addr, _ := constant.Uint64Val(v.Value)
|
||||||
return []api.Location{{PC: addr}}, nil
|
return []api.Location{{PC: addr}}, nil
|
||||||
case reflect.Func:
|
case reflect.Func:
|
||||||
pc, err := d.process.FunctionEntryToFirstLine(uint64(v.Base))
|
_, _, fn := d.process.PCToLine(uint64(v.Base))
|
||||||
|
pc, err := d.process.FirstPCAfterPrologue(fn, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -252,6 +252,7 @@ func testnext(testcases []nextTest, initialLocation string, t *testing.T) {
|
|||||||
|
|
||||||
func TestNextGeneral(t *testing.T) {
|
func TestNextGeneral(t *testing.T) {
|
||||||
testcases := []nextTest{
|
testcases := []nextTest{
|
||||||
|
{17, 19},
|
||||||
{19, 20},
|
{19, 20},
|
||||||
{20, 23},
|
{20, 23},
|
||||||
{23, 24},
|
{23, 24},
|
||||||
@ -272,6 +273,7 @@ func TestNextGeneral(t *testing.T) {
|
|||||||
|
|
||||||
func TestNextFunctionReturn(t *testing.T) {
|
func TestNextFunctionReturn(t *testing.T) {
|
||||||
testcases := []nextTest{
|
testcases := []nextTest{
|
||||||
|
{13, 14},
|
||||||
{14, 15},
|
{14, 15},
|
||||||
{15, 35},
|
{15, 35},
|
||||||
}
|
}
|
||||||
@ -592,9 +594,10 @@ func findLocationHelper(t *testing.T, c service.Client, loc string, shouldErr bo
|
|||||||
|
|
||||||
func TestClientServer_FindLocations(t *testing.T) {
|
func TestClientServer_FindLocations(t *testing.T) {
|
||||||
withTestClient("locationsprog", t, func(c service.Client) {
|
withTestClient("locationsprog", t, func(c service.Client) {
|
||||||
someFunctionCallAddr := findLocationHelper(t, c, "locationsprog.go:27", false, 1, 0)[0]
|
someFunctionCallAddr := findLocationHelper(t, c, "locationsprog.go:26", false, 1, 0)[0]
|
||||||
findLocationHelper(t, c, "anotherFunction:1", false, 1, someFunctionCallAddr)
|
someFunctionLine1 := findLocationHelper(t, c, "locationsprog.go:27", false, 1, 0)[0]
|
||||||
findLocationHelper(t, c, "main.anotherFunction:1", false, 1, someFunctionCallAddr)
|
findLocationHelper(t, c, "anotherFunction:1", false, 1, someFunctionLine1)
|
||||||
|
findLocationHelper(t, c, "main.anotherFunction:1", false, 1, someFunctionLine1)
|
||||||
findLocationHelper(t, c, "anotherFunction", false, 1, someFunctionCallAddr)
|
findLocationHelper(t, c, "anotherFunction", false, 1, someFunctionCallAddr)
|
||||||
findLocationHelper(t, c, "main.anotherFunction", false, 1, someFunctionCallAddr)
|
findLocationHelper(t, c, "main.anotherFunction", false, 1, someFunctionCallAddr)
|
||||||
findLocationHelper(t, c, fmt.Sprintf("*0x%x", someFunctionCallAddr), false, 1, someFunctionCallAddr)
|
findLocationHelper(t, c, fmt.Sprintf("*0x%x", someFunctionCallAddr), false, 1, someFunctionCallAddr)
|
||||||
@ -603,8 +606,8 @@ func TestClientServer_FindLocations(t *testing.T) {
|
|||||||
findLocationHelper(t, c, "String", true, 0, 0)
|
findLocationHelper(t, c, "String", true, 0, 0)
|
||||||
findLocationHelper(t, c, "main.String", true, 0, 0)
|
findLocationHelper(t, c, "main.String", true, 0, 0)
|
||||||
|
|
||||||
someTypeStringFuncAddr := findLocationHelper(t, c, "locationsprog.go:15", false, 1, 0)[0]
|
someTypeStringFuncAddr := findLocationHelper(t, c, "locationsprog.go:14", false, 1, 0)[0]
|
||||||
otherTypeStringFuncAddr := findLocationHelper(t, c, "locationsprog.go:19", false, 1, 0)[0]
|
otherTypeStringFuncAddr := findLocationHelper(t, c, "locationsprog.go:18", false, 1, 0)[0]
|
||||||
findLocationHelper(t, c, "SomeType.String", false, 1, someTypeStringFuncAddr)
|
findLocationHelper(t, c, "SomeType.String", false, 1, someTypeStringFuncAddr)
|
||||||
findLocationHelper(t, c, "(*SomeType).String", false, 1, someTypeStringFuncAddr)
|
findLocationHelper(t, c, "(*SomeType).String", false, 1, someTypeStringFuncAddr)
|
||||||
findLocationHelper(t, c, "main.SomeType.String", false, 1, someTypeStringFuncAddr)
|
findLocationHelper(t, c, "main.SomeType.String", false, 1, someTypeStringFuncAddr)
|
||||||
@ -638,7 +641,7 @@ func TestClientServer_FindLocations(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
withTestClient("testnextdefer", t, func(c service.Client) {
|
withTestClient("testnextdefer", t, func(c service.Client) {
|
||||||
firstMainLine := findLocationHelper(t, c, "testnextdefer.go:8", false, 1, 0)[0]
|
firstMainLine := findLocationHelper(t, c, "testnextdefer.go:5", false, 1, 0)[0]
|
||||||
findLocationHelper(t, c, "main.main", false, 1, firstMainLine)
|
findLocationHelper(t, c, "main.main", false, 1, firstMainLine)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -691,7 +694,7 @@ func TestClientServer_FindLocationsAddr(t *testing.T) {
|
|||||||
<-c.Continue()
|
<-c.Continue()
|
||||||
|
|
||||||
afunction := findLocationHelper(t, c, "main.afunction", false, 1, 0)[0]
|
afunction := findLocationHelper(t, c, "main.afunction", false, 1, 0)[0]
|
||||||
anonfunc := findLocationHelper(t, c, "locationsprog2.go:25", false, 1, 0)[0]
|
anonfunc := findLocationHelper(t, c, "main.main.func1", false, 1, 0)[0]
|
||||||
|
|
||||||
findLocationHelper(t, c, "*fn1", false, 1, afunction)
|
findLocationHelper(t, c, "*fn1", false, 1, afunction)
|
||||||
findLocationHelper(t, c, "*fn3", false, 1, anonfunc)
|
findLocationHelper(t, c, "*fn3", false, 1, anonfunc)
|
||||||
@ -1025,3 +1028,45 @@ func TestClientServer_CondBreakpoint(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSkipPrologue(t *testing.T) {
|
||||||
|
withTestClient("locationsprog2", t, func(c service.Client) {
|
||||||
|
<-c.Continue()
|
||||||
|
|
||||||
|
afunction := findLocationHelper(t, c, "main.afunction", false, 1, 0)[0]
|
||||||
|
findLocationHelper(t, c, "*fn1", false, 1, afunction)
|
||||||
|
findLocationHelper(t, c, "locationsprog2.go:8", false, 1, afunction)
|
||||||
|
|
||||||
|
afunction0 := findLocationHelper(t, c, "main.afunction:0", false, 1, 0)[0]
|
||||||
|
|
||||||
|
if afunction == afunction0 {
|
||||||
|
t.Fatal("Skip prologue failed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkipPrologue2(t *testing.T) {
|
||||||
|
withTestClient("callme", t, func(c service.Client) {
|
||||||
|
callme := findLocationHelper(t, c, "main.callme", false, 1, 0)[0]
|
||||||
|
callmeZ := findLocationHelper(t, c, "main.callme:0", false, 1, 0)[0]
|
||||||
|
findLocationHelper(t, c, "callme.go:5", false, 1, callme)
|
||||||
|
if callme == callmeZ {
|
||||||
|
t.Fatal("Skip prologue failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
callme2 := findLocationHelper(t, c, "main.callme2", false, 1, 0)[0]
|
||||||
|
callme2Z := findLocationHelper(t, c, "main.callme2:0", false, 1, 0)[0]
|
||||||
|
findLocationHelper(t, c, "callme.go:12", false, 1, callme2)
|
||||||
|
if callme2 == callme2Z {
|
||||||
|
t.Fatal("Skip prologue failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
callme3 := findLocationHelper(t, c, "main.callme3", false, 1, 0)[0]
|
||||||
|
callme3Z := findLocationHelper(t, c, "main.callme3:0", false, 1, 0)[0]
|
||||||
|
// callme3 does not have local variables therefore the first line of the function is immediately after the prologue
|
||||||
|
findLocationHelper(t, c, "callme.go:18", false, 1, callme3Z)
|
||||||
|
if callme3 == callme3Z {
|
||||||
|
t.Fatal("Skip prologue failed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user