diff --git a/dwarf/reader/reader.go b/dwarf/reader/reader.go index 95145bfe..0ad6c416 100755 --- a/dwarf/reader/reader.go +++ b/dwarf/reader/reader.go @@ -143,11 +143,7 @@ func (reader *Reader) SeekToType(entry *dwarf.Entry, resolveTypedefs bool, resol return nil, TypeNotFoundErr } -// SeekToTypeNamed moves the reader to the type specified by the name. -// If the reader is set to a struct type the NextMemberVariable call -// can be used to walk all member data. -func (reader *Reader) SeekToTypeNamed(name string) (*dwarf.Entry, error) { - // Walk the types to the base +func (reader *Reader) NextType() (*dwarf.Entry, error) { for entry, err := reader.Next(); entry != nil; entry, err = reader.Next() { if err != nil { return nil, err @@ -155,9 +151,21 @@ func (reader *Reader) SeekToTypeNamed(name string) (*dwarf.Entry, error) { switch entry.Tag { case dwarf.TagArrayType, dwarf.TagBaseType, dwarf.TagClassType, dwarf.TagStructType, dwarf.TagUnionType, dwarf.TagConstType, dwarf.TagVolatileType, dwarf.TagRestrictType, dwarf.TagEnumerationType, dwarf.TagPointerType, dwarf.TagSubroutineType, dwarf.TagTypedef, dwarf.TagUnspecifiedType: - //ok - default: - continue + return entry, nil + } + } + + return nil, nil +} + +// SeekToTypeNamed moves the reader to the type specified by the name. +// If the reader is set to a struct type the NextMemberVariable call +// can be used to walk all member data. +func (reader *Reader) SeekToTypeNamed(name string) (*dwarf.Entry, error) { + // Walk the types to the base + for entry, err := reader.NextType(); entry != nil; entry, err = reader.NextType() { + if err != nil { + return nil, err } n, ok := entry.Val(dwarf.AttrName).(string) diff --git a/proc/proc.go b/proc/proc.go index 67349f24..c07d28d0 100644 --- a/proc/proc.go +++ b/proc/proc.go @@ -656,6 +656,25 @@ func (dbp *Process) Funcs() []gosym.Func { return dbp.goSymTable.Funcs } +// Types returns list of types present in the debugged program. +func (dbp *Process) Types() ([]string, error) { + reader := dbp.DwarfReader() + types := []string{} + seen := map[string]struct{}{} + for entry, err := reader.NextType(); entry != nil; entry, err = reader.NextType() { + if err != nil { + return nil, err + } + if n, ok := entry.Val(dwarf.AttrName).(string); ok { + if _, isseen := seen[n]; !isseen { + seen[n] = struct{}{} + types = append(types, n) + } + } + } + return types, nil +} + // PCToLine converts an instruction address to a file/line/function. func (dbp *Process) PCToLine(pc uint64) (string, int, *gosym.Func) { return dbp.goSymTable.PCToLine(pc) diff --git a/service/client.go b/service/client.go index 2f0dba06..6d1ab136 100644 --- a/service/client.go +++ b/service/client.go @@ -69,6 +69,8 @@ type Client interface { ListSources(filter string) ([]string, error) // ListFunctions lists all functions in the process matching filter. ListFunctions(filter string) ([]string, error) + // ListTypes lists all types in the process matching filter. + ListTypes(filter string) ([]string, error) // ListLocals lists all local variables in scope. ListLocalVariables(scope api.EvalScope) ([]api.Variable, error) // ListFunctionArgs lists all arguments to the current function. diff --git a/service/debugger/debugger.go b/service/debugger/debugger.go index 3b2da075..0c793d0b 100644 --- a/service/debugger/debugger.go +++ b/service/debugger/debugger.go @@ -498,6 +498,30 @@ func (d *Debugger) Functions(filter string) ([]string, error) { return regexFilterFuncs(filter, d.process.Funcs()) } +func (d *Debugger) Types(filter string) ([]string, error) { + d.processMutex.Lock() + defer d.processMutex.Unlock() + + regex, err := regexp.Compile(filter) + if err != nil { + return nil, fmt.Errorf("invalid filter argument: %s", err.Error()) + } + + types, err := d.process.Types() + if err != nil { + return nil, err + } + + r := make([]string, 0, len(types)) + for _, typ := range types { + if regex.Match([]byte(typ)) { + r = append(r, typ) + } + } + + return r, nil +} + func regexFilterFuncs(filter string, allFuncs []gosym.Func) ([]string, error) { regex, err := regexp.Compile(filter) if err != nil { diff --git a/service/rpc/client.go b/service/rpc/client.go index ce5c749d..295ef44f 100644 --- a/service/rpc/client.go +++ b/service/rpc/client.go @@ -209,6 +209,12 @@ func (c *RPCClient) ListFunctions(filter string) ([]string, error) { return funcs, err } +func (c *RPCClient) ListTypes(filter string) ([]string, error) { + var types []string + err := c.call("ListTypes", filter, &types) + return types, err +} + func (c *RPCClient) ListPackageVariables(filter string) ([]api.Variable, error) { var vars []api.Variable err := c.call("ListPackageVars", filter, &vars) diff --git a/service/rpc/server.go b/service/rpc/server.go index 81022145..72012943 100644 --- a/service/rpc/server.go +++ b/service/rpc/server.go @@ -345,6 +345,15 @@ func (s *RPCServer) ListFunctions(filter string, funcs *[]string) error { return nil } +func (s *RPCServer) ListTypes(filter string, types *[]string) error { + tps, err := s.debugger.Types(filter) + if err != nil { + return err + } + *types = tps + return nil +} + func (s *RPCServer) ListGoroutines(arg interface{}, goroutines *[]*api.Goroutine) error { gs, err := s.debugger.Goroutines() if err != nil { diff --git a/service/test/integration_test.go b/service/test/integration_test.go index c5954f92..ed49a224 100644 --- a/service/test/integration_test.go +++ b/service/test/integration_test.go @@ -104,24 +104,24 @@ func TestRestart_breakpointPreservation(t *testing.T) { _, err := c.CreateBreakpoint(&api.Breakpoint{FunctionName: "main.main", Line: 1, Name: "firstbreakpoint", Tracepoint: true}) assertNoError(err, t, "CreateBreakpoint()") stateCh := c.Continue() - - state := <- stateCh + + state := <-stateCh if state.CurrentThread.Breakpoint.Name != "firstbreakpoint" || !state.CurrentThread.Breakpoint.Tracepoint { t.Fatalf("Wrong breakpoint: %#v\n", state.CurrentThread.Breakpoint) } - state = <- stateCh + state = <-stateCh if !state.Exited { t.Fatal("Did not exit after first tracepoint") } - + t.Log("Restart") c.Restart() stateCh = c.Continue() - state = <- stateCh + state = <-stateCh if state.CurrentThread.Breakpoint.Name != "firstbreakpoint" || !state.CurrentThread.Breakpoint.Tracepoint { t.Fatalf("Wrong breakpoint (after restart): %#v\n", state.CurrentThread.Breakpoint) } - state = <- stateCh + state = <-stateCh if !state.Exited { t.Fatal("Did not exit after first tracepoint (after restart)") } @@ -1089,3 +1089,29 @@ func TestIssue419(t *testing.T) { assertNoError(state.Err, t, "Continue()") }) } + +func TestTypesCommand(t *testing.T) { + withTestClient("testvariables2", t, func(c service.Client) { + state := <-c.Continue() + assertNoError(state.Err, t, "Continue()") + types, err := c.ListTypes("") + assertNoError(err, t, "ListTypes()") + + found := false + for i := range types { + if types[i] == "main.astruct" { + found = true + break + } + } + if !found { + t.Fatal("Type astruct not found in ListTypes output") + } + + types, err = c.ListTypes("main.astruct") + assertNoError(err, t, "ListTypes(\"main.astruct\")") + if len(types) <= 0 { + t.Fatal("ListTypes(\"main.astruct\") did not return anything") + } + }) +} diff --git a/terminal/command.go b/terminal/command.go index 8984fc81..bbdc9bda 100644 --- a/terminal/command.go +++ b/terminal/command.go @@ -75,6 +75,7 @@ func DebugCommands(client service.Client) *Commands { {aliases: []string{"set"}, cmdFn: g0f0(setVar), helpMsg: "Changes the value of a variable."}, {aliases: []string{"sources"}, cmdFn: filterSortAndOutput(sources), helpMsg: "Print list of source files, optionally filtered by a regexp."}, {aliases: []string{"funcs"}, cmdFn: filterSortAndOutput(funcs), helpMsg: "Print list of functions, optionally filtered by a regexp."}, + {aliases: []string{"types"}, cmdFn: filterSortAndOutput(types), helpMsg: "Print list of types, optionally filtered by a regexp."}, {aliases: []string{"args"}, cmdFn: filterSortAndOutput(g0f0filter(args)), helpMsg: "Print function arguments, optionally filtered by a regexp."}, {aliases: []string{"locals"}, cmdFn: filterSortAndOutput(g0f0filter(locals)), helpMsg: "Print function locals, optionally filtered by a regexp."}, {aliases: []string{"vars"}, cmdFn: filterSortAndOutput(vars), helpMsg: "Print package variables, optionally filtered by a regexp."}, @@ -703,6 +704,10 @@ func funcs(t *Term, filter string) ([]string, error) { return t.client.ListFunctions(filter) } +func types(t *Term, filter string) ([]string, error) { + return t.client.ListTypes(filter) +} + func args(t *Term, scope api.EvalScope, filter string) ([]string, error) { vars, err := t.client.ListFunctionArgs(scope) if err != nil {