提交 7c61e2a1 编写于 作者: E epipho 提交者: Derek Parker

EvalSymbol supports evaluating struct members on pointers. Fixed panic

when evaluating a nil pointer.
上级 eed50f3e
...@@ -28,6 +28,7 @@ func foobar(baz string, bar FooBar) { ...@@ -28,6 +28,7 @@ func foobar(baz string, bar FooBar) {
a6 = FooBar{Baz: 8, Bur: "word"} a6 = FooBar{Baz: 8, Bur: "word"}
a7 = &FooBar{Baz: 5, Bur: "strum"} a7 = &FooBar{Baz: 5, Bur: "strum"}
a8 = FooBar2{Bur: 10, Baz: "feh"} a8 = FooBar2{Bur: 10, Baz: "feh"}
a9 = (*FooBar)(nil)
neg = -1 neg = -1
i8 = int8(1) i8 = int8(1)
f32 = float32(1.2) f32 = float32(1.2)
...@@ -35,7 +36,7 @@ func foobar(baz string, bar FooBar) { ...@@ -35,7 +36,7 @@ func foobar(baz string, bar FooBar) {
) )
barfoo() barfoo()
fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, baz, neg, i8, f32, i32, bar) fmt.Println(a1, a2, a3, a4, a5, a6, a7, a8, a9, baz, neg, i8, f32, i32, bar)
} }
func main() { func main() {
......
...@@ -394,7 +394,7 @@ func findDwarfEntry(name string, reader *dwarf.Reader, member bool) (*dwarf.Entr ...@@ -394,7 +394,7 @@ func findDwarfEntry(name string, reader *dwarf.Reader, member bool) (*dwarf.Entr
} }
func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, reader *reader.Reader, memberName string) (*Variable, error) { func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, reader *reader.Reader, memberName string) (*Variable, error) {
parentInstr, err := instructionsForEntry(parentEntry) parentAddr, err := thread.extractVariableDataAddress(parentEntry, reader)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -406,7 +406,7 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read ...@@ -406,7 +406,7 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read
} }
// Seek reader to the type information so members can be iterated // Seek reader to the type information so members can be iterated
_, err = reader.SeekToType(parentEntry, true, false) _, err = reader.SeekToType(parentEntry, true, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -423,6 +423,11 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read ...@@ -423,6 +423,11 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read
} }
if name == memberName { if name == memberName {
// nil ptr. wait until here to throw a nil pointer error to prioritize no such member error
if parentAddr == 0 {
return nil, fmt.Errorf("%s is nil", parentName)
}
memberInstr, err := instructionsForEntry(memberEntry) memberInstr, err := instructionsForEntry(memberEntry)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -439,8 +444,11 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read ...@@ -439,8 +444,11 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read
return nil, err return nil, err
} }
app := append(parentInstr, memberInstr...) baseAddr := make([]byte, 8)
val, err := thread.extractValue(app, 0, t) binary.LittleEndian.PutUint64(baseAddr, uint64(parentAddr))
parentInstructions := append([]byte{op.DW_OP_addr}, baseAddr...)
val, err := thread.extractValue(append(parentInstructions, memberInstr...), 0, t)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -448,7 +456,7 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read ...@@ -448,7 +456,7 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read
} }
} }
return nil, fmt.Errorf("member %s not found for %s", memberName, parentName) return nil, fmt.Errorf("%s has no member %s", parentName, memberName)
} }
// Extracts the name, type, and value of a variable from a dwarf entry // Extracts the name, type, and value of a variable from a dwarf entry
...@@ -490,27 +498,70 @@ func (thread *ThreadContext) extractVariableFromEntry(entry *dwarf.Entry) (*Vari ...@@ -490,27 +498,70 @@ func (thread *ThreadContext) extractVariableFromEntry(entry *dwarf.Entry) (*Vari
return &Variable{Name: n, Type: t.String(), Value: val}, nil return &Variable{Name: n, Type: t.String(), Value: val}, nil
} }
// Extracts the value from the instructions given in the DW_AT_location entry. // Execute the stack program taking into account the current stack frame
// We execute the stack program described in the DW_OP_* instruction stream, and func (thread *ThreadContext) executeStackProgram(instructions []byte) (int64, error) {
// then grab the value from the other processes memory. regs, err := thread.Registers()
func (thread *ThreadContext) extractValue(instructions []byte, addr int64, typ interface{}) (string, error) { if err != nil {
address := addr return 0, err
}
if address == 0 { fde, err := thread.Process.FrameEntries.FDEForPC(regs.PC())
regs, err := thread.Registers() if err != nil {
return 0, err
}
fctx := fde.EstablishFrame(regs.PC())
cfaOffset := fctx.CFAOffset() + int64(regs.SP())
address, err := op.ExecuteStackProgram(cfaOffset, instructions)
if err != nil {
return 0, err
}
return address, nil
}
// Extracts the address of a variable, dereferencing any pointers
func (thread *ThreadContext) extractVariableDataAddress(entry *dwarf.Entry, reader *reader.Reader) (int64, error) {
instructions, err := instructionsForEntry(entry)
if err != nil {
return 0, err
}
address, err := thread.executeStackProgram(instructions)
if err != nil {
return 0, err
}
// dereference pointers to get down the concrete type
for typeEntry, err := reader.SeekToType(entry, true, false); typeEntry != nil; typeEntry, err = reader.SeekToType(typeEntry, true, false) {
if err != nil { if err != nil {
return "", err return 0, err
} }
fde, err := thread.Process.FrameEntries.FDEForPC(regs.PC()) if typeEntry.Tag != dwarf.TagPointerType {
break
}
ptraddress := uintptr(address)
ptr, err := thread.readMemory(ptraddress, ptrsize)
if err != nil { if err != nil {
return "", err return 0, err
} }
address = int64(binary.LittleEndian.Uint64(ptr))
}
fctx := fde.EstablishFrame(regs.PC()) return address, nil
cfaOffset := fctx.CFAOffset() + int64(regs.SP()) }
address, err = op.ExecuteStackProgram(cfaOffset, instructions) // 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 (thread *ThreadContext) extractValue(instructions []byte, addr int64, typ interface{}) (string, error) {
var err error
if addr == 0 {
addr, err = thread.executeStackProgram(instructions)
if err != nil { if err != nil {
return "", err return "", err
} }
...@@ -522,20 +573,25 @@ func (thread *ThreadContext) extractValue(instructions []byte, addr int64, typ i ...@@ -522,20 +573,25 @@ func (thread *ThreadContext) extractValue(instructions []byte, addr int64, typ i
typ = tt.Type typ = tt.Type
} }
ptraddress := uintptr(address) ptraddress := uintptr(addr)
switch t := typ.(type) { switch t := typ.(type) {
case *dwarf.PtrType: case *dwarf.PtrType:
ptr, err := thread.readMemory(ptraddress, ptrsize) ptr, err := thread.readMemory(ptraddress, ptrsize)
if err != nil { if err != nil {
return "", err return "", err
} }
val, err := thread.extractValue(nil, int64(binary.LittleEndian.Uint64(ptr)), t.Type)
intaddr := int64(binary.LittleEndian.Uint64(ptr))
if intaddr == 0 {
return fmt.Sprintf("%s nil", t.String()), nil
}
val, err := thread.extractValue(nil, intaddr, t.Type)
if err != nil { if err != nil {
return "", err return "", err
} }
retstr := fmt.Sprintf("*%s", val) return fmt.Sprintf("*%s", val), nil
return retstr, nil
case *dwarf.StructType: case *dwarf.StructType:
switch t.StructName { switch t.StructName {
case "string": case "string":
...@@ -547,7 +603,7 @@ func (thread *ThreadContext) extractValue(instructions []byte, addr int64, typ i ...@@ -547,7 +603,7 @@ func (thread *ThreadContext) extractValue(instructions []byte, addr int64, typ i
// the value of all the members of the struct. // the value of all the members of the struct.
fields := make([]string, 0, len(t.Field)) fields := make([]string, 0, len(t.Field))
for _, field := range t.Field { for _, field := range t.Field {
val, err := thread.extractValue(nil, field.ByteOffset+address, field.Type) val, err := thread.extractValue(nil, field.ByteOffset+addr, field.Type)
if err != nil { if err != nil {
return "", err return "", err
} }
......
package proctl package proctl
import ( import (
"errors"
"path/filepath" "path/filepath"
"sort" "sort"
"testing" "testing"
...@@ -10,6 +11,7 @@ type varTest struct { ...@@ -10,6 +11,7 @@ type varTest struct {
name string name string
value string value string
varType string varType string
err error
} }
func assertVariable(t *testing.T, variable *Variable, expected varTest) { func assertVariable(t *testing.T, variable *Variable, expected varTest) {
...@@ -35,26 +37,31 @@ func TestVariableEvaluation(t *testing.T) { ...@@ -35,26 +37,31 @@ func TestVariableEvaluation(t *testing.T) {
} }
testcases := []varTest{ testcases := []varTest{
{"a1", "foo", "struct string"}, {"a1", "foo", "struct string", nil},
{"a2", "6", "int"}, {"a2", "6", "int", nil},
{"a3", "7.23", "float64"}, {"a3", "7.23", "float64", nil},
{"a4", "[2]int [1 2]", "[2]int"}, {"a4", "[2]int [1 2]", "[2]int", nil},
{"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int"}, {"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int", nil},
{"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar"}, {"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar", nil},
{"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar"}, {"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar", nil},
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2"}, {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2", nil},
{"baz", "bazburzum", "struct string"}, {"a9", "*main.FooBar nil", "*main.FooBar", nil},
{"neg", "-1", "int"}, {"baz", "bazburzum", "struct string", nil},
{"i8", "1", "int8"}, {"neg", "-1", "int", nil},
{"f32", "1.2", "float32"}, {"i8", "1", "int8", nil},
{"a6.Baz", "8", "int"}, {"f32", "1.2", "float32", nil},
{"a8.Baz", "feh", "struct string"}, {"a6.Baz", "8", "int", nil},
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2"}, // reread variable after member {"a7.Baz", "5", "int", nil},
{"i32", "[2]int32 [1 2]", "[2]int32"}, {"a8.Baz", "feh", "struct string", nil},
{"a9.Baz", "nil", "int", errors.New("a9 is nil")},
{"a9.NonExistent", "nil", "int", errors.New("a9 has no member NonExistent")},
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2", nil}, // reread variable after member
{"i32", "[2]int32 [1 2]", "[2]int32", nil},
{"NonExistent", "", "", errors.New("could not find symbol value for NonExistent")},
} }
withTestProcess(executablePath, t, func(p *DebuggedProcess) { withTestProcess(executablePath, t, func(p *DebuggedProcess) {
pc, _, _ := p.GoSymTable.LineToPC(fp, 37) pc, _, _ := p.GoSymTable.LineToPC(fp, 38)
_, err := p.Break(pc) _, err := p.Break(pc)
assertNoError(err, t, "Break() returned an error") assertNoError(err, t, "Break() returned an error")
...@@ -64,8 +71,14 @@ func TestVariableEvaluation(t *testing.T) { ...@@ -64,8 +71,14 @@ func TestVariableEvaluation(t *testing.T) {
for _, tc := range testcases { for _, tc := range testcases {
variable, err := p.EvalSymbol(tc.name) variable, err := p.EvalSymbol(tc.name)
assertNoError(err, t, "EvalSymbol() returned an error") if tc.err == nil {
assertVariable(t, variable, tc) assertNoError(err, t, "EvalSymbol() returned an error")
assertVariable(t, variable, tc)
} else {
if tc.err.Error() != err.Error() {
t.Fatalf("Unexpected error. Expected %s got %s", tc.err.Error(), err.Error())
}
}
} }
}) })
} }
...@@ -79,7 +92,7 @@ func TestVariableFunctionScoping(t *testing.T) { ...@@ -79,7 +92,7 @@ func TestVariableFunctionScoping(t *testing.T) {
} }
withTestProcess(executablePath, t, func(p *DebuggedProcess) { withTestProcess(executablePath, t, func(p *DebuggedProcess) {
pc, _, _ := p.GoSymTable.LineToPC(fp, 37) pc, _, _ := p.GoSymTable.LineToPC(fp, 38)
_, err := p.Break(pc) _, err := p.Break(pc)
assertNoError(err, t, "Break() returned an error") assertNoError(err, t, "Break() returned an error")
...@@ -143,26 +156,27 @@ func TestLocalVariables(t *testing.T) { ...@@ -143,26 +156,27 @@ func TestLocalVariables(t *testing.T) {
}{ }{
{(*ThreadContext).LocalVariables, {(*ThreadContext).LocalVariables,
[]varTest{ []varTest{
{"a1", "foo", "struct string"}, {"a1", "foo", "struct string", nil},
{"a2", "6", "int"}, {"a2", "6", "int", nil},
{"a3", "7.23", "float64"}, {"a3", "7.23", "float64", nil},
{"a4", "[2]int [1 2]", "[2]int"}, {"a4", "[2]int [1 2]", "[2]int", nil},
{"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int"}, {"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int", nil},
{"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar"}, {"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar", nil},
{"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar"}, {"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar", nil},
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2"}, {"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2", nil},
{"f32", "1.2", "float32"}, {"a9", "*main.FooBar nil", "*main.FooBar", nil},
{"i32", "[2]int32 [1 2]", "[2]int32"}, {"f32", "1.2", "float32", nil},
{"i8", "1", "int8"}, {"i32", "[2]int32 [1 2]", "[2]int32", nil},
{"neg", "-1", "int"}}}, {"i8", "1", "int8", nil},
{"neg", "-1", "int", nil}}},
{(*ThreadContext).FunctionArguments, {(*ThreadContext).FunctionArguments,
[]varTest{ []varTest{
{"bar", "main.FooBar {Baz: 10, Bur: lorem}", "main.FooBar"}, {"bar", "main.FooBar {Baz: 10, Bur: lorem}", "main.FooBar", nil},
{"baz", "bazburzum", "struct string"}}}, {"baz", "bazburzum", "struct string", nil}}},
} }
withTestProcess(executablePath, t, func(p *DebuggedProcess) { withTestProcess(executablePath, t, func(p *DebuggedProcess) {
pc, _, _ := p.GoSymTable.LineToPC(fp, 37) pc, _, _ := p.GoSymTable.LineToPC(fp, 38)
_, err := p.Break(pc) _, err := p.Break(pc)
assertNoError(err, t, "Break() returned an error") assertNoError(err, t, "Break() returned an error")
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册