Implement basic int value expressions
This commit is contained in:
parent
319f6d2e20
commit
3993cfe148
37298
_fixtures/foo_info.txt
Normal file
37298
_fixtures/foo_info.txt
Normal file
File diff suppressed because it is too large
Load Diff
BIN
_fixtures/testvariables
Executable file
BIN
_fixtures/testvariables
Executable file
Binary file not shown.
20
_fixtures/testvariables.go
Normal file
20
_fixtures/testvariables.go
Normal file
@ -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
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
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
39501
proctl/foo_info.txt
Normal file
File diff suppressed because it is too large
Load Diff
BIN
proctl/proctl.test
Executable file
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user