提交 4116d66c 编写于 作者: A aarzilli 提交者: Derek Parker

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
上级 c277b271
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 {
......
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
}
......@@ -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")
}
})
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册