ogle/program: in stack frames, add the function name, parameters, and local variables.
A program.Var is returned for each parameter and variable, so we can use
Value to get their values. evalLocation is changed to return an error
when it can't parse a DWARF location description. Parameters and
variables we can't parse are ignored.
Fixes some error messages.
Change-Id: I0bd4263268db01cf206fdc8e1f1d55b9106035c6
Reviewed-on: https://go-review.googlesource.com/10609
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/ogle/demo/ogler/ogler_test.go b/ogle/demo/ogler/ogler_test.go
index 8bceaff..fc7c511 100644
--- a/ogle/demo/ogler/ogler_test.go
+++ b/ogle/demo/ogler/ogler_test.go
@@ -191,6 +191,37 @@
log.Fatalf("prog.Frames error: %v", err)
}
fmt.Printf("%#v\n", frames)
+ if len(frames) == 0 {
+ t.Fatalf("no stack frames returned")
+ }
+ if frames[0].Function != "main.foo" {
+ t.Errorf("function name: got %s expected main.foo", frames[0].Function)
+ }
+ if len(frames[0].Params) != 2 {
+ t.Errorf("got %d parameters, expected 2", len(frames[0].Params))
+ } else {
+ x := frames[0].Params[0]
+ y := frames[0].Params[1]
+ if x.Name != "x" {
+ x, y = y, x
+ }
+ if x.Name != "x" {
+ t.Errorf("parameter name: got %s expected x", x.Name)
+ }
+ if y.Name != "y" {
+ t.Errorf("parameter name: got %s expected y", y.Name)
+ }
+ if val, err := prog.Value(x.Var); err != nil {
+ t.Errorf("value of x: %s", err)
+ } else if val != int16(42) {
+ t.Errorf("value of x: got %T(%v) expected int16(42)", val, val)
+ }
+ if val, err := prog.Value(y.Var); err != nil {
+ t.Errorf("value of y: %s", err)
+ } else if val != float32(1.5) {
+ t.Errorf("value of y: got %T(%v) expected float32(1.5)", val, val)
+ }
+ }
varnames, err := prog.Eval(`re:main\.Z_.*`)
if err != nil {
@@ -207,25 +238,25 @@
} else {
fmt.Printf("%s = %v\n", v, val)
if seen[v] {
- log.Fatalf("Repeated variable %s\n", v)
+ log.Fatalf("repeated variable %s\n", v)
}
seen[v] = true
if len(val) != 1 {
- log.Fatalf("Should be one value for %s\n", v)
+ log.Fatalf("should be one value for %s\n", v)
}
expected, ok := expectedVars[v]
if !ok {
- log.Fatalf("Unexpected variable %s\n", v)
+ log.Fatalf("unexpected variable %s\n", v)
} else {
if !matches(expected, val[0]) {
- log.Fatalf("Expected %s = %s\n", v, expected)
+ log.Fatalf("expected %s = %s\n", v, expected)
}
}
}
}
for v, e := range expectedVars {
if !seen[v] {
- log.Fatalf("Didn't get %s = %s\n", v, e)
+ log.Fatalf("didn't get %s = %s\n", v, e)
}
}
@@ -260,7 +291,7 @@
}
}
if !ok {
- t.Errorf("Stopped at %X expected one of %X.", status.PC, pcs2)
+ t.Errorf("stopped at %X expected one of %X.", status.PC, pcs2)
}
// Check we get the expected results calling VarByName then Value
@@ -269,9 +300,9 @@
if v, err := prog.VarByName(name); err != nil {
t.Errorf("VarByName(%s): %s", name, err)
} else if val, err := prog.Value(v); err != nil {
- t.Errorf("Value for %s: %s", name, err)
+ t.Errorf("value of %s: %s", name, err)
} else if val != exp {
- t.Errorf("Value for %s: got %T(%v) want %T(%v)", name, val, val, exp, exp)
+ t.Errorf("value of %s: got %T(%v) want %T(%v)", name, val, val, exp, exp)
}
}
@@ -280,7 +311,7 @@
t.Error("VarByName for invalid name: expected error")
}
if _, err = prog.Value(program.Var{}); err == nil {
- t.Error("Value of invalid var: expected error")
+ t.Error("value of invalid var: expected error")
}
if v, err := prog.VarByName("main.Z_int16"); err != nil {
t.Error("VarByName(main.Z_int16) error:", err)
@@ -289,7 +320,7 @@
// v now has a valid type but a bad address.
_, err = prog.Value(v)
if err == nil {
- t.Error("Value of invalid location: expected error")
+ t.Error("value of invalid location: expected error")
}
}
@@ -300,9 +331,9 @@
if v, err := prog.VarByName(name); err != nil {
t.Errorf("VarByName(%s): %s", name, err)
} else if val, err := prog.Value(v); err != nil {
- t.Errorf("value for %s: %s", name, err)
+ t.Errorf("value of %s: %s", name, err)
} else if err := fn(val); err != nil {
- t.Errorf("value for %s: %s", name, err)
+ t.Errorf("value of %s: %s", name, err)
}
}
diff --git a/ogle/demo/tracee/main.go b/ogle/demo/tracee/main.go
index 9b44649..4fbf63b 100644
--- a/ogle/demo/tracee/main.go
+++ b/ogle/demo/tracee/main.go
@@ -70,7 +70,7 @@
Z_unsafe_pointer_nil unsafe.Pointer
)
-func foo() {
+func foo(x int16, y float32) {
fmt.Println(Z_bool_false, Z_bool_true)
fmt.Println(Z_int, Z_int8, Z_int16, Z_int32, Z_int64)
fmt.Println(Z_uint, Z_uint8, Z_uint16, Z_uint32, Z_uint64, Z_uintptr)
@@ -97,7 +97,7 @@
}
func bar() {
- foo()
+ foo(42, 1.5)
fmt.Print()
}
@@ -105,7 +105,7 @@
args := os.Args[1:]
expected := []string{"some", "arguments"}
if len(args) != 2 || args[0] != expected[0] || args[1] != expected[1] {
- log.Fatalf("Got command-line args %v, expected %v", args, expected)
+ log.Fatalf("got command-line args %v, expected %v", args, expected)
}
for ; ; time.Sleep(2 * time.Second) {
bar()
diff --git a/ogle/program/program.go b/ogle/program/program.go
index 34b3961..77d8b8a 100644
--- a/ogle/program/program.go
+++ b/ogle/program/program.go
@@ -154,14 +154,25 @@
PC uint64
// SP is the hardware stack pointer.
SP uint64
-
// File and Line are the source code location of the PC.
File string
Line int
+ // Function is the name of this frame's function.
+ Function string
+ // Params contains the function's parameters.
+ Params []Param
+ // Vars contains the function's local variables.
+ Vars []LocalVar
+}
- // TODO: add arguments and return values.
+// Param is a parameter of a function.
+type Param struct {
+ Name string
+ Var Var
+}
- // S is an unstructured string value used for debugging the ogle
- // library. It is not guaranteed to be in any particular format.
- S string
+// LocalVar is a local variable of a function.
+type LocalVar struct {
+ Name string
+ Var Var
}
diff --git a/ogle/program/server/dwarf.go b/ogle/program/server/dwarf.go
index ca99683..50f2220 100644
--- a/ogle/program/server/dwarf.go
+++ b/ogle/program/server/dwarf.go
@@ -5,6 +5,7 @@
package server
import (
+ "errors"
"regexp"
"golang.org/x/debug/dwarf"
@@ -51,22 +52,40 @@
return s.dwarfData.EntryForPC(pc)
}
-// TODO: signedness? Return (x int64, ok bool)??
-func evalLocation(v []uint8) int64 {
+// evalLocation parses a DWARF location description encoded in v. It works for
+// cases where the variable is stored at an offset from the Canonical Frame
+// Address. The return value is this offset.
+// TODO: a more general location-description-parsing function.
+func evalLocation(v []uint8) (int64, error) {
+ // Some DWARF constants.
+ const (
+ opConsts = 0x11
+ opPlus = 0x22
+ opCallFrameCFA = 0x9C
+ )
if len(v) == 0 {
- return 0
+ return 0, errors.New("empty location specifier")
}
- if v[0] != 0x9C { // DW_OP_call_frame_cfa
- return 0
+ if v[0] != opCallFrameCFA {
+ return 0, errors.New("unsupported location specifier")
}
if len(v) == 1 {
- return 0
+ // The location description was just DW_OP_call_frame_cfa, so the location is exactly the CFA.
+ return 0, nil
}
- v = v[1:]
- if v[0] != 0x11 { // DW_OP_consts
- return 0
+ if v[1] != opConsts {
+ return 0, errors.New("unsupported location specifier")
}
- return sleb128(v[1:])
+ offset, v, err := sleb128(v[2:])
+ if err != nil {
+ return 0, err
+ }
+ if len(v) == 1 && v[0] == opPlus {
+ // The location description was DW_OP_call_frame_cfa, DW_OP_consts <offset>, DW_OP_plus.
+ // So return the offset.
+ return offset, nil
+ }
+ return 0, errors.New("unsupported location specifier")
}
func uleb128(v []uint8) (u uint64) {
@@ -81,10 +100,14 @@
return u
}
-func sleb128(v []uint8) (s int64) {
+// sleb128 parses a signed integer encoded with sleb128 at the start of v, and
+// returns the integer and the remainder of v.
+func sleb128(v []uint8) (s int64, rest []uint8, err error) {
var shift uint
var sign int64 = -1
- for _, x := range v {
+ var i int
+ var x uint8
+ for i, x = range v {
s |= (int64(x) & 0x7F) << shift
shift += 7
sign <<= 7
@@ -95,6 +118,8 @@
break
}
}
- // Sign extend?
- return s
+ if i == len(v) {
+ return 0, nil, errors.New("truncated sleb128")
+ }
+ return s, v[i+1:], nil
}
diff --git a/ogle/program/server/server.go b/ogle/program/server/server.go
index 1a3ca3e..4afb911 100644
--- a/ogle/program/server/server.go
+++ b/ogle/program/server/server.go
@@ -587,6 +587,13 @@
if err != nil {
return err
}
+ frame := program.Frame{
+ PC: pc,
+ SP: sp,
+ File: file,
+ Line: line,
+ }
+ frame.Function, _ = entry.Val(dwarf.AttrName).(string)
r.Seek(entry.Offset)
for {
entry, err := r.Next()
@@ -596,38 +603,19 @@
if entry.Tag == 0 {
break
}
- if entry.Tag != dwarf.TagFormalParameter {
- continue
- }
- if entry.Children {
- // TODO: handle this??
- return fmt.Errorf("FormalParameter has children, expected none")
- }
-
- offset := int64(0)
- name := "arg"
- for _, f := range entry.Field {
- switch f.Attr {
- case dwarf.AttrLocation:
- offset = evalLocation(f.Val.([]uint8))
- case dwarf.AttrName:
- name = f.Val.(string)
+ // TODO: report variables we couldn't parse?
+ if entry.Tag == dwarf.TagFormalParameter {
+ if v, err := s.parseParameterOrLocal(entry, fp); err == nil {
+ frame.Params = append(frame.Params, program.Param(v))
}
}
- str, err := s.printer.SprintEntry(entry, address(fp)+address(offset))
- fmt.Fprintf(b, "%s (%d(FP)) = %s ", name, offset, str)
- if err != nil {
- fmt.Fprintf(b, "(%s) ", err)
+ if entry.Tag == dwarf.TagVariable {
+ if v, err := s.parseParameterOrLocal(entry, fp); err == nil {
+ frame.Vars = append(frame.Vars, v)
+ }
}
}
- resp.Frames = append(resp.Frames, program.Frame{
- PC: pc,
- SP: sp,
- File: file,
- Line: line,
- // TODO: delete the Frame.S field.
- S: b.String(),
- })
+ resp.Frames = append(resp.Frames, frame)
// Walk to the caller's PC and SP.
if s.topOfStack(funcEntry) {
@@ -642,6 +630,29 @@
return nil
}
+// parseParameterOrLocal parses the entry for a function parameter or local
+// variable, which are both specified the same way. fp contains the frame
+// pointer, which is used to calculate the variable location.
+func (s *Server) parseParameterOrLocal(entry *dwarf.Entry, fp uint64) (program.LocalVar, error) {
+ var v program.LocalVar
+ v.Name, _ = entry.Val(dwarf.AttrName).(string)
+ if off, err := s.dwarfData.EntryTypeOffset(entry); err != nil {
+ return v, err
+ } else {
+ v.Var.TypeID = uint64(off)
+ }
+ if i := entry.Val(dwarf.AttrLocation); i == nil {
+ return v, fmt.Errorf("missing location description")
+ } else if locationDescription, ok := i.([]uint8); !ok {
+ return v, fmt.Errorf("unsupported location description")
+ } else if offset, err := evalLocation(locationDescription); err != nil {
+ return v, err
+ } else {
+ v.Var.Address = fp + uint64(offset)
+ }
+ return v, nil
+}
+
func (s *Server) evaluateTopOfStackAddrs() error {
var (
lookup func(name string) (uint64, error)