diff --git a/_fixtures/cgotest.go b/_fixtures/cgotest.go new file mode 100644 index 0000000000..c19d8c3cc0 --- /dev/null +++ b/_fixtures/cgotest.go @@ -0,0 +1,12 @@ +package main + +/* +char* foo(void) { return "hello, world!"; } +*/ +import "C" + +import "fmt" + +func main() { + fmt.Println(C.GoString(C.foo())) +} diff --git a/dwarf/op/op.go b/dwarf/op/op.go index a53ea96fe4..82f06832c8 100644 --- a/dwarf/op/op.go +++ b/dwarf/op/op.go @@ -31,10 +31,10 @@ func ExecuteStackProgram(cfa int64, instructions []byte) (int64, error) { stack := make([]int64, 0, 3) buf := bytes.NewBuffer(instructions) - for ocfaode, err := buf.ReadByte(); err == nil; ocfaode, err = buf.ReadByte() { - fn, ok := oplut[ocfaode] + for opcode, err := buf.ReadByte(); err == nil; opcode, err = buf.ReadByte() { + fn, ok := oplut[opcode] if !ok { - return 0, fmt.Errorf("invalid instruction %#v", ocfaode) + return 0, fmt.Errorf("invalid instruction %#v", opcode) } stack, err = fn(buf, stack, cfa) @@ -51,6 +51,9 @@ func ExecuteStackProgram(cfa int64, instructions []byte) (int64, error) { } func callframecfa(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) { + if cfa == 0 { + return stack, fmt.Errorf("Could not retrieve CFA for current PC") + } return append(stack, int64(cfa)), nil } diff --git a/proc/arch.go b/proc/arch.go index 7d9a78ad55..26c0cbef02 100644 --- a/proc/arch.go +++ b/proc/arch.go @@ -3,6 +3,7 @@ package proc import "runtime" type Arch interface { + SetCurGInstructions(ver GoVersion, iscgo bool) PtrSize() int BreakpointInstruction() []byte BreakpointSize() int @@ -20,10 +21,18 @@ type AMD64 struct { } func AMD64Arch() *AMD64 { - var ( - curg []byte - breakInstr = []byte{0xCC} - ) + var breakInstr = []byte{0xCC} + + return &AMD64{ + ptrSize: 8, + breakInstruction: breakInstr, + breakInstructionLen: len(breakInstr), + hardwareBreakpointUsage: make([]bool, 4), + } +} + +func (a *AMD64) SetCurGInstructions(ver GoVersion, iscgo bool) { + var curg []byte switch runtime.GOOS { case "darwin": @@ -32,19 +41,19 @@ func AMD64Arch() *AMD64 { 0x0, 0x0, } case "linux": - curg = []byte{ - 0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf0, 0xff, 0xff, 0xff, // mov %fs:0xfffffffffffffff0,%rcx + if iscgo || ver.After(GoVersion{1, 5, 0}) { + curg = []byte{ + 0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf8, 0xff, 0xff, 0xff, // mov %fs:0xfffffffffffffff8,%rcx + } + } else { + curg = []byte{ + 0x64, 0x48, 0x8b, 0x0c, 0x25, 0xf0, 0xff, 0xff, 0xff, // mov %fs:0xfffffffffffffff0,%rcx + } } } - curg = append(curg, breakInstr[0]) + curg = append(curg, a.breakInstruction...) - return &AMD64{ - ptrSize: 8, - breakInstruction: breakInstr, - breakInstructionLen: 1, - curgInstructions: curg, - hardwareBreakpointUsage: make([]bool, 4), - } + a.curgInstructions = curg } func (a *AMD64) PtrSize() int { diff --git a/proc/proc.go b/proc/proc.go index 5df254385d..bde0a139f8 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -596,6 +596,13 @@ func initializeDebugProcess(dbp *Process, path string, attach bool) (*Process, e dbp.arch = AMD64Arch() } + ver, iscgo, err := dbp.getGoInformation() + if err != nil { + return nil, err + } + + dbp.arch.SetCurGInstructions(ver, iscgo) + return dbp, nil } @@ -674,3 +681,27 @@ func (dbp *Process) execPtraceFunc(fn func()) { dbp.ptraceChan <- fn <-dbp.ptraceDoneChan } + +func (dbp *Process) getGoInformation() (ver GoVersion, iscgo bool, err error) { + th := dbp.Threads[dbp.Pid] + + vv, err := th.EvalPackageVariable("runtime.buildVersion") + if err != nil { + err = fmt.Errorf("Could not determine version number: %v\n", err) + return + } + + ver, ok := parseVersionString(vv.Value) + if !ok { + err = fmt.Errorf("Could not parse version number: %s\n", vv.Value) + } + + cv, err := th.EvalPackageVariable("runtime.iscgo") + if err != nil { + err = fmt.Errorf("Could not determine cgo usage: %v\n", err) + return + } + iscgo = (cv.Value == "true") + + return +} diff --git a/proc/proc_test.go b/proc/proc_test.go index 617048460c..8e09ca0fef 100644 --- a/proc/proc_test.go +++ b/proc/proc_test.go @@ -592,3 +592,32 @@ func TestKill(t *testing.T) { } }) } + +func testGSupportFunc(name string, t *testing.T, p *Process, fixture protest.Fixture) { + bp, err := p.SetBreakpointByLocation("main.main") + assertNoError(err, t, name+": BreakByLocation()") + + assertNoError(p.Continue(), t, name+": Continue()") + + g, err := p.CurrentThread.GetG() + assertNoError(err, t, name+": GetG()") + + if g == nil { + t.Fatal(name + ": g was nil") + } + + t.Logf(name+": g is: %v", g) + + p.ClearBreakpoint(bp.Addr) + p.Kill() +} + +func TestGetG(t *testing.T) { + withTestProcess("testprog", t, func(p *Process, fixture protest.Fixture) { + testGSupportFunc("nocgo", t, p, fixture) + }) + + withTestProcess("cgotest", t, func(p *Process, fixture protest.Fixture) { + testGSupportFunc("cgo", t, p, fixture) + }) +} diff --git a/proc/variables.go b/proc/variables.go index 68ac8dcbae..be550a483f 100644 --- a/proc/variables.go +++ b/proc/variables.go @@ -275,6 +275,27 @@ func (thread *Thread) PackageVariables() ([]*Variable, error) { return vars, nil } +func (thread *Thread) EvalPackageVariable(name string) (*Variable, error) { + reader := thread.dbp.DwarfReader() + + for entry, err := reader.NextPackageVariable(); entry != nil; entry, err = reader.NextPackageVariable() { + if err != nil { + return nil, err + } + + n, ok := entry.Val(dwarf.AttrName).(string) + if !ok { + continue + } + + if n == name { + return thread.extractVariableFromEntry(entry) + } + } + + return nil, fmt.Errorf("could not find symbol value for %s", name) +} + func (thread *Thread) evaluateStructMember(parentEntry *dwarf.Entry, rdr *reader.Reader, memberName string) (*Variable, error) { parentAddr, err := thread.extractVariableDataAddress(parentEntry, rdr) if err != nil { @@ -387,13 +408,13 @@ func (thread *Thread) executeStackProgram(instructions []byte) (int64, error) { return 0, err } + var cfa int64 = 0 + fde, err := thread.dbp.frameEntries.FDEForPC(regs.PC()) - if err != nil { - return 0, err + if err == nil { + fctx := fde.EstablishFrame(regs.PC()) + cfa = fctx.CFAOffset() + int64(regs.SP()) } - - fctx := fde.EstablishFrame(regs.PC()) - cfa := fctx.CFAOffset() + int64(regs.SP()) address, err := op.ExecuteStackProgram(cfa, instructions) if err != nil { return 0, err diff --git a/proc/version.go b/proc/version.go new file mode 100644 index 0000000000..f373f7f9be --- /dev/null +++ b/proc/version.go @@ -0,0 +1,54 @@ +package proc + +import ( + "strconv" + "strings" +) + +type GoVersion struct { + Major int + Minor int + Rev int +} + +func parseVersionString(ver string) (GoVersion, bool) { + if ver[:2] != "go" { + return GoVersion{}, false + } + v := strings.SplitN(ver[2:], ".", 3) + if len(v) != 3 { + return GoVersion{}, false + } + + var r GoVersion + var err1, err2, err3 error + + r.Major, err1 = strconv.Atoi(v[0]) + r.Minor, err2 = strconv.Atoi(v[1]) + r.Rev, err3 = strconv.Atoi(v[2]) + if err1 != nil || err2 != nil || err3 != nil { + return GoVersion{}, false + } + + return r, true +} + +func (a *GoVersion) After(b GoVersion) bool { + if a.Major < b.Major { + return false + } else if a.Major > b.Major { + return true + } + + if a.Minor < b.Minor { + return false + } else if a.Minor > b.Minor { + return true + } + + if a.Rev < b.Rev { + return false + } + + return true +}