提交 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) {
a6 = FooBar{Baz: 8, Bur: "word"}
a7 = &FooBar{Baz: 5, Bur: "strum"}
a8 = FooBar2{Bur: 10, Baz: "feh"}
a9 = (*FooBar)(nil)
neg = -1
i8 = int8(1)
f32 = float32(1.2)
......@@ -35,7 +36,7 @@ func foobar(baz string, bar FooBar) {
)
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() {
......
......@@ -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) {
parentInstr, err := instructionsForEntry(parentEntry)
parentAddr, err := thread.extractVariableDataAddress(parentEntry, reader)
if err != nil {
return nil, err
}
......@@ -406,7 +406,7 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read
}
// 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 {
return nil, err
}
......@@ -423,6 +423,11 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read
}
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)
if err != nil {
return nil, err
......@@ -439,8 +444,11 @@ func (thread *ThreadContext) evaluateStructMember(parentEntry *dwarf.Entry, read
return nil, err
}
app := append(parentInstr, memberInstr...)
val, err := thread.extractValue(app, 0, t)
baseAddr := make([]byte, 8)
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 {
return nil, err
}
......@@ -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
......@@ -490,27 +498,70 @@ func (thread *ThreadContext) extractVariableFromEntry(entry *dwarf.Entry) (*Vari
return &Variable{Name: n, Type: t.String(), Value: val}, 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 (thread *ThreadContext) extractValue(instructions []byte, addr int64, typ interface{}) (string, error) {
address := addr
// Execute the stack program taking into account the current stack frame
func (thread *ThreadContext) executeStackProgram(instructions []byte) (int64, error) {
regs, err := thread.Registers()
if err != nil {
return 0, err
}
if address == 0 {
regs, err := thread.Registers()
fde, err := thread.Process.FrameEntries.FDEForPC(regs.PC())
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 {
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 {
return "", err
return 0, err
}
address = int64(binary.LittleEndian.Uint64(ptr))
}
fctx := fde.EstablishFrame(regs.PC())
cfaOffset := fctx.CFAOffset() + int64(regs.SP())
return address, nil
}
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 {
return "", err
}
......@@ -522,20 +573,25 @@ func (thread *ThreadContext) extractValue(instructions []byte, addr int64, typ i
typ = tt.Type
}
ptraddress := uintptr(address)
ptraddress := uintptr(addr)
switch t := typ.(type) {
case *dwarf.PtrType:
ptr, err := thread.readMemory(ptraddress, ptrsize)
if err != nil {
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 {
return "", err
}
retstr := fmt.Sprintf("*%s", val)
return retstr, nil
return fmt.Sprintf("*%s", val), nil
case *dwarf.StructType:
switch t.StructName {
case "string":
......@@ -547,7 +603,7 @@ func (thread *ThreadContext) extractValue(instructions []byte, addr int64, typ i
// the value of all the members of the struct.
fields := make([]string, 0, len(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 {
return "", err
}
......
package proctl
import (
"errors"
"path/filepath"
"sort"
"testing"
......@@ -10,6 +11,7 @@ type varTest struct {
name string
value string
varType string
err error
}
func assertVariable(t *testing.T, variable *Variable, expected varTest) {
......@@ -35,26 +37,31 @@ func TestVariableEvaluation(t *testing.T) {
}
testcases := []varTest{
{"a1", "foo", "struct string"},
{"a2", "6", "int"},
{"a3", "7.23", "float64"},
{"a4", "[2]int [1 2]", "[2]int"},
{"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int"},
{"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar"},
{"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar"},
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2"},
{"baz", "bazburzum", "struct string"},
{"neg", "-1", "int"},
{"i8", "1", "int8"},
{"f32", "1.2", "float32"},
{"a6.Baz", "8", "int"},
{"a8.Baz", "feh", "struct string"},
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2"}, // reread variable after member
{"i32", "[2]int32 [1 2]", "[2]int32"},
{"a1", "foo", "struct string", nil},
{"a2", "6", "int", nil},
{"a3", "7.23", "float64", nil},
{"a4", "[2]int [1 2]", "[2]int", nil},
{"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int", nil},
{"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar", nil},
{"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar", nil},
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2", nil},
{"a9", "*main.FooBar nil", "*main.FooBar", nil},
{"baz", "bazburzum", "struct string", nil},
{"neg", "-1", "int", nil},
{"i8", "1", "int8", nil},
{"f32", "1.2", "float32", nil},
{"a6.Baz", "8", "int", nil},
{"a7.Baz", "5", "int", nil},
{"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) {
pc, _, _ := p.GoSymTable.LineToPC(fp, 37)
pc, _, _ := p.GoSymTable.LineToPC(fp, 38)
_, err := p.Break(pc)
assertNoError(err, t, "Break() returned an error")
......@@ -64,8 +71,14 @@ func TestVariableEvaluation(t *testing.T) {
for _, tc := range testcases {
variable, err := p.EvalSymbol(tc.name)
assertNoError(err, t, "EvalSymbol() returned an error")
assertVariable(t, variable, tc)
if tc.err == nil {
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) {
}
withTestProcess(executablePath, t, func(p *DebuggedProcess) {
pc, _, _ := p.GoSymTable.LineToPC(fp, 37)
pc, _, _ := p.GoSymTable.LineToPC(fp, 38)
_, err := p.Break(pc)
assertNoError(err, t, "Break() returned an error")
......@@ -143,26 +156,27 @@ func TestLocalVariables(t *testing.T) {
}{
{(*ThreadContext).LocalVariables,
[]varTest{
{"a1", "foo", "struct string"},
{"a2", "6", "int"},
{"a3", "7.23", "float64"},
{"a4", "[2]int [1 2]", "[2]int"},
{"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int"},
{"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar"},
{"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar"},
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2"},
{"f32", "1.2", "float32"},
{"i32", "[2]int32 [1 2]", "[2]int32"},
{"i8", "1", "int8"},
{"neg", "-1", "int"}}},
{"a1", "foo", "struct string", nil},
{"a2", "6", "int", nil},
{"a3", "7.23", "float64", nil},
{"a4", "[2]int [1 2]", "[2]int", nil},
{"a5", "len: 5 cap: 5 [1 2 3 4 5]", "struct []int", nil},
{"a6", "main.FooBar {Baz: 8, Bur: word}", "main.FooBar", nil},
{"a7", "*main.FooBar {Baz: 5, Bur: strum}", "*main.FooBar", nil},
{"a8", "main.FooBar2 {Bur: 10, Baz: feh}", "main.FooBar2", nil},
{"a9", "*main.FooBar nil", "*main.FooBar", nil},
{"f32", "1.2", "float32", nil},
{"i32", "[2]int32 [1 2]", "[2]int32", nil},
{"i8", "1", "int8", nil},
{"neg", "-1", "int", nil}}},
{(*ThreadContext).FunctionArguments,
[]varTest{
{"bar", "main.FooBar {Baz: 10, Bur: lorem}", "main.FooBar"},
{"baz", "bazburzum", "struct string"}}},
{"bar", "main.FooBar {Baz: 10, Bur: lorem}", "main.FooBar", nil},
{"baz", "bazburzum", "struct string", nil}}},
}
withTestProcess(executablePath, t, func(p *DebuggedProcess) {
pc, _, _ := p.GoSymTable.LineToPC(fp, 37)
pc, _, _ := p.GoSymTable.LineToPC(fp, 38)
_, err := p.Break(pc)
assertNoError(err, t, "Break() returned an error")
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册