Implement basic int value expressions

This commit is contained in:
Derek Parker 2014-07-29 19:00:24 -05:00
parent 319f6d2e20
commit 3993cfe148
10 changed files with 77056 additions and 3 deletions

37298
_fixtures/foo_info.txt Normal file

File diff suppressed because it is too large Load Diff

BIN
_fixtures/testvariables Executable file

Binary file not shown.

@ -0,0 +1,20 @@
package main
import "fmt"
type FooBar struct {
Baz int
Bur string
}
func main() {
var (
bar = "foo"
foo = 6
sl = []int{1, 2, 3, 4, 5}
arr = [1]int{1}
baz = &FooBar{Baz: 5, Bur: "strum"}
)
fmt.Println(bar, foo, arr, sl, baz)
}

@ -35,6 +35,10 @@ type FrameContext struct {
dataAlignment int64
}
func (fctx *FrameContext) CFAOffset() int64 {
return fctx.cfa.offset
}
// Instructions used to recreate the table from the .debug_frame data.
const (
DW_CFA_nop = 0x0 // No ops

60
dwarf/op/op.go Normal file

@ -0,0 +1,60 @@
package op
import (
"bytes"
"fmt"
"github.com/derekparker/dbg/dwarf/util"
)
const (
DW_OP_call_frame_cfa = 0x9c
DW_OP_plus = 0x22
DW_OP_consts = 0x11
)
type stackfn func(*bytes.Buffer, []int64, int64) ([]int64, error)
var oplut = map[byte]stackfn{
DW_OP_call_frame_cfa: callframecfa,
DW_OP_plus: plus,
DW_OP_consts: consts,
}
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]
if !ok {
return 0, fmt.Errorf("invalid instruction %#v", ocfaode)
}
stack, err = fn(buf, stack, cfa)
if err != nil {
return 0, err
}
}
return stack[len(stack)-1], nil
}
func callframecfa(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) {
return append(stack, int64(cfa)), nil
}
func plus(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) {
var (
slen = len(stack)
digits = stack[slen-2 : slen]
st = stack[:slen-2]
)
return append(st, digits[0]+digits[1]), nil
}
func consts(buf *bytes.Buffer, stack []int64, cfa int64) ([]int64, error) {
num, _ := util.DecodeSLEB128(buf)
return append(stack, num), nil
}

18
dwarf/op/op_test.go Normal file

@ -0,0 +1,18 @@
package op
import "testing"
func TestExecuteStackProgram(t *testing.T) {
var (
instructions = []byte{DW_OP_consts, 0x1c, DW_OP_consts, 0x1c, DW_OP_plus}
expected = int64(56)
)
actual, err := ExecuteStackProgram(0, instructions)
if err != nil {
t.Fatal(err)
}
if actual != expected {
t.Fatalf("actual %d != expected %d", actual, expected)
}
}

39501
proctl/foo_info.txt Normal file

File diff suppressed because it is too large Load Diff

BIN
proctl/proctl.test Executable file

Binary file not shown.

@ -4,15 +4,18 @@ package proctl
import (
"bytes"
"debug/dwarf"
"debug/elf"
"debug/gosym"
"encoding/binary"
"fmt"
"os"
"strconv"
"syscall"
"github.com/derekparker/dbg/dwarf/frame"
"github.com/derekparker/dbg/dwarf/line"
"github.com/derekparker/dbg/dwarf/op"
)
// Struct representing a debugged process. Holds onto pid, register values,
@ -48,6 +51,12 @@ type BreakPointExistsError struct {
addr uintptr
}
type Variable struct {
Name string
Value string
Type string
}
func (bpe BreakPointExistsError) Error() string {
return fmt.Sprintf("Breakpoint exists at %s:%d at %x", bpe.file, bpe.line, bpe.addr)
}
@ -125,6 +134,55 @@ func (dbp *DebuggedProcess) Registers() (*syscall.PtraceRegs, error) {
return dbp.Regs, nil
}
// Returns the value of the named symbol.
func (dbp *DebuggedProcess) EvalSymbol(name string) (*Variable, error) {
data, err := dbp.Executable.DWARF()
if err != nil {
return nil, err
}
reader := data.Reader()
for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() {
if err != nil {
return nil, err
}
if entry.Tag == dwarf.TagVariable {
n, ok := entry.Val(dwarf.AttrName).(string)
if !ok {
continue
}
if n == name {
offset, ok := entry.Val(dwarf.AttrType).(dwarf.Offset)
if !ok {
continue
}
t, err := data.Type(offset)
if err != nil {
return nil, err
}
instructions, ok := entry.Val(dwarf.AttrLocation).([]byte)
if !ok {
continue
}
val, err := dbp.extractValue(instructions, t)
if err != nil {
return nil, err
}
return &Variable{Name: n, Type: t.String(), Value: val}, nil
}
}
}
return nil, fmt.Errorf("could not find symbol value for %s", name)
}
// Sets a breakpoint in the running process.
func (dbp *DebuggedProcess) Break(addr uintptr) (*BreakPoint, error) {
var (
@ -293,6 +351,55 @@ func (dbp *DebuggedProcess) CurrentPC() (uint64, error) {
return regs.Rip, nil
}
// Extracts the value from the instructions given in the DW_AT_location entry.
// We execute the stack program described in the DW_OP_* instruction stream, and
// then grab the value from the other processes memory.
func (dbp *DebuggedProcess) extractValue(instructions []byte, typ interface{}) (string, error) {
regs, err := dbp.Registers()
if err != nil {
return "", err
}
fde, err := dbp.FrameEntries.FDEForPC(regs.PC())
if err != nil {
return "", err
}
fctx := fde.EstablishFrame(regs.PC())
cfaOffset := fctx.CFAOffset()
off, err := op.ExecuteStackProgram(cfaOffset, instructions)
if err != nil {
return "", err
}
switch typ.(type) {
case *dwarf.IntType:
addr := uintptr(int64(regs.Rsp) + off)
val, err := dbp.readMemory(addr, 8)
if err != nil {
return "", err
}
n := binary.LittleEndian.Uint64(val)
return strconv.Itoa(int(n)), nil
}
return "", fmt.Errorf("could not find value for type %s", typ)
}
func (dbp *DebuggedProcess) readMemory(addr uintptr, size uintptr) ([]byte, error) {
buf := make([]byte, size)
_, err := syscall.PtracePeekData(dbp.Pid, addr, buf)
if err != nil {
return nil, err
}
return buf, nil
}
func (dbp *DebuggedProcess) handleResult(err error) error {
if err != nil {
return err

@ -170,8 +170,9 @@ func TestClearBreakPoint(t *testing.T) {
func TestNext(t *testing.T) {
var (
ln int
err error
ln int
err error
executablePath = "../_fixtures/testnextprog"
)
testcases := []struct {
@ -195,7 +196,7 @@ func TestNext(t *testing.T) {
t.Fatal(err)
}
helper.WithTestProcess("../_fixtures/testnextprog", t, func(p *proctl.DebuggedProcess) {
helper.WithTestProcess(executablePath, t, func(p *proctl.DebuggedProcess) {
pc, _, _ := p.GoSymTable.LineToPC(fp, testcases[0].begin)
_, err := p.Break(uintptr(pc))
assertNoError(err, t, "Break() returned an error")
@ -216,3 +217,47 @@ func TestNext(t *testing.T) {
}
})
}
func TestVariableEvaluation(t *testing.T) {
executablePath := "../_fixtures/testvariables"
fp, err := filepath.Abs(executablePath + ".go")
if err != nil {
t.Fatal(err)
}
testcases := []struct {
name string
value string
varType string
}{
{"foo", "6", "int"},
}
helper.WithTestProcess(executablePath, t, func(p *proctl.DebuggedProcess) {
pc, _, _ := p.GoSymTable.LineToPC(fp, 19)
_, err := p.Break(uintptr(pc))
assertNoError(err, t, "Break() returned an error")
err = p.Continue()
assertNoError(err, t, "Break() returned an error")
for _, tc := range testcases {
variable, err := p.EvalSymbol(tc.name)
assertNoError(err, t, "Variable() returned an error")
if variable.Name != tc.name {
t.Fatalf("Expected %s got %s\n", tc.name, variable.Name)
}
if variable.Type != tc.varType {
t.Fatalf("Expected %s got %s\n", tc.varType, variable.Type)
}
if variable.Value != tc.value {
t.Fatalf("Expected %s got %s\n", tc.value, variable.Value)
}
}
})
}