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)
|
||||
pc := startPC
|
||||
|
||||
regs, _ := thread.Registers()
|
||||
var curpc uint64
|
||||
if regs != nil {
|
||||
curpc = regs.PC()
|
||||
var regs Registers
|
||||
if currentGoroutine {
|
||||
regs, _ = thread.Registers()
|
||||
if regs != nil {
|
||||
curpc = regs.PC()
|
||||
}
|
||||
}
|
||||
|
||||
for len(mem) > 0 {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"debug/gosym"
|
||||
"encoding/binary"
|
||||
"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}
|
||||
}
|
||||
|
||||
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/token"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -161,12 +160,15 @@ func (dbp *Process) LoadInformation(path string) error {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
pc, _, err := dbp.goSymTable.LineToPC(fileName, lineno)
|
||||
pc, fn, err := dbp.goSymTable.LineToPC(fileName, lineno)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if fn.Entry == pc {
|
||||
pc, _ = dbp.FirstPCAfterPrologue(fn, true)
|
||||
}
|
||||
return pc, nil
|
||||
}
|
||||
|
||||
@ -183,7 +185,7 @@ func (dbp *Process) FindFunctionLocation(funcName string, firstLine bool, lineOf
|
||||
}
|
||||
|
||||
if firstLine {
|
||||
return dbp.FunctionEntryToFirstLine(origfn.Entry)
|
||||
return dbp.FirstPCAfterPrologue(origfn, false)
|
||||
} else if lineOffset > 0 {
|
||||
filename, lineno, _ := dbp.goSymTable.PCToLine(origfn.Entry)
|
||||
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
|
||||
}
|
||||
|
||||
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.
|
||||
func (dbp *Process) CurrentLocation() (*Location, error) {
|
||||
return dbp.CurrentThread.Location()
|
||||
|
@ -311,6 +311,7 @@ func testnext(program string, testcases []nextTest, initialLocation string, t *t
|
||||
|
||||
func TestNextGeneral(t *testing.T) {
|
||||
testcases := []nextTest{
|
||||
{17, 19},
|
||||
{19, 20},
|
||||
{20, 23},
|
||||
{23, 24},
|
||||
@ -331,6 +332,7 @@ func TestNextGeneral(t *testing.T) {
|
||||
|
||||
func TestNextConcurrent(t *testing.T) {
|
||||
testcases := []nextTest{
|
||||
{8, 9},
|
||||
{9, 10},
|
||||
{10, 11},
|
||||
}
|
||||
@ -371,6 +373,7 @@ func TestNextConcurrent(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
|
||||
testcases := []nextTest{
|
||||
{8, 9},
|
||||
{9, 10},
|
||||
{10, 11},
|
||||
}
|
||||
@ -419,6 +422,7 @@ func TestNextConcurrentVariant2(t *testing.T) {
|
||||
|
||||
func TestNextFunctionReturn(t *testing.T) {
|
||||
testcases := []nextTest{
|
||||
{13, 14},
|
||||
{14, 15},
|
||||
{15, 35},
|
||||
}
|
||||
@ -427,6 +431,7 @@ func TestNextFunctionReturn(t *testing.T) {
|
||||
|
||||
func TestNextFunctionReturnDefer(t *testing.T) {
|
||||
testcases := []nextTest{
|
||||
{5, 8},
|
||||
{8, 9},
|
||||
{9, 10},
|
||||
{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)
|
||||
return []api.Location{{PC: addr}}, nil
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -252,6 +252,7 @@ func testnext(testcases []nextTest, initialLocation string, t *testing.T) {
|
||||
|
||||
func TestNextGeneral(t *testing.T) {
|
||||
testcases := []nextTest{
|
||||
{17, 19},
|
||||
{19, 20},
|
||||
{20, 23},
|
||||
{23, 24},
|
||||
@ -272,6 +273,7 @@ func TestNextGeneral(t *testing.T) {
|
||||
|
||||
func TestNextFunctionReturn(t *testing.T) {
|
||||
testcases := []nextTest{
|
||||
{13, 14},
|
||||
{14, 15},
|
||||
{15, 35},
|
||||
}
|
||||
@ -592,9 +594,10 @@ func findLocationHelper(t *testing.T, c service.Client, loc string, shouldErr bo
|
||||
|
||||
func TestClientServer_FindLocations(t *testing.T) {
|
||||
withTestClient("locationsprog", t, func(c service.Client) {
|
||||
someFunctionCallAddr := findLocationHelper(t, c, "locationsprog.go:27", false, 1, 0)[0]
|
||||
findLocationHelper(t, c, "anotherFunction:1", false, 1, someFunctionCallAddr)
|
||||
findLocationHelper(t, c, "main.anotherFunction:1", false, 1, someFunctionCallAddr)
|
||||
someFunctionCallAddr := findLocationHelper(t, c, "locationsprog.go:26", false, 1, 0)[0]
|
||||
someFunctionLine1 := findLocationHelper(t, c, "locationsprog.go:27", false, 1, 0)[0]
|
||||
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, "main.anotherFunction", 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, "main.String", true, 0, 0)
|
||||
|
||||
someTypeStringFuncAddr := findLocationHelper(t, c, "locationsprog.go:15", false, 1, 0)[0]
|
||||
otherTypeStringFuncAddr := findLocationHelper(t, c, "locationsprog.go:19", false, 1, 0)[0]
|
||||
someTypeStringFuncAddr := findLocationHelper(t, c, "locationsprog.go:14", 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, "main.SomeType.String", false, 1, someTypeStringFuncAddr)
|
||||
@ -638,7 +641,7 @@ func TestClientServer_FindLocations(t *testing.T) {
|
||||
})
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
@ -691,7 +694,7 @@ func TestClientServer_FindLocationsAddr(t *testing.T) {
|
||||
<-c.Continue()
|
||||
|
||||
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, "*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