terminal: add ability to show disassembly instead of source (#3047)

Adds ability to show current position as disassembly instead of source
listing every time execution stops. Change default to show disassembly
after step-instruction and source listing in every other case.

Fixes #2878
This commit is contained in:
Alessandro Arzilli 2022-07-14 23:08:47 +02:00 committed by GitHub
parent 278e4d10c8
commit 1fe03e728c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 101 additions and 22 deletions

@ -9,6 +9,7 @@ import (
"path" "path"
"runtime" "runtime"
"github.com/go-delve/delve/service/api"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
@ -16,6 +17,10 @@ const (
configDir string = "dlv" configDir string = "dlv"
configDirHidden string = ".dlv" configDirHidden string = ".dlv"
configFile string = "config.yml" configFile string = "config.yml"
PositionSource = "source"
PositionDisassembly = "disassembly"
PositionDefault = "default"
) )
// SubstitutePathRule describes a rule for substitution of path to source code file. // SubstitutePathRule describes a rule for substitution of path to source code file.
@ -76,9 +81,19 @@ type Config struct {
// called (i.e. when execution stops, listCommand is used, etc) // called (i.e. when execution stops, listCommand is used, etc)
SourceListLineCount *int `yaml:"source-list-line-count,omitempty"` SourceListLineCount *int `yaml:"source-list-line-count,omitempty"`
// DebugFileDirectories is the list of directories Delve will use // DebugInfoDirectories is the list of directories Delve will use
// in order to resolve external debug info files. // in order to resolve external debug info files.
DebugInfoDirectories []string `yaml:"debug-info-directories"` DebugInfoDirectories []string `yaml:"debug-info-directories"`
// Position controls how the current position in the program is displayed.
// There are three possible values:
// - source: always show the current position in the program's source
// code.
// - disassembly: always should the current position by disassembling the
// current function.
// - default (or the empty string): use disassembly for step-instruction,
// source for everything else.
Position string `yaml:"position"`
} }
func (c *Config) GetSourceListLineCount() int { func (c *Config) GetSourceListLineCount() int {
@ -90,6 +105,20 @@ func (c *Config) GetSourceListLineCount() int {
return n return n
} }
func (c *Config) GetDisassembleFlavour() api.AssemblyFlavour {
if c == nil || c.DisassembleFlavor == nil {
return api.IntelFlavour
}
switch *c.DisassembleFlavor {
case "go":
return api.GoFlavour
case "gnu":
return api.GNUFlavour
default:
return api.IntelFlavour
}
}
// LoadConfig attempts to populate a Config object from the config.yml file. // LoadConfig attempts to populate a Config object from the config.yml file.
func LoadConfig() (*Config, error) { func LoadConfig() (*Config, error) {
err := createConfigPath() err := createConfigPath()

@ -1175,7 +1175,7 @@ func restartRecorded(t *Term, ctx callContext, args string) error {
return err return err
} }
printcontext(t, state) printcontext(t, state)
printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) printPos(t, state.CurrentThread, printPosShowArrow)
t.onStop() t.onStop()
return nil return nil
} }
@ -1328,7 +1328,7 @@ func (c *Commands) cont(t *Term, ctx callContext, args string) error {
} }
printcontext(t, state) printcontext(t, state)
} }
printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) printPos(t, state.CurrentThread, printPosShowArrow)
return nil return nil
} }
@ -1336,7 +1336,7 @@ func continueUntilCompleteNext(t *Term, state *api.DebuggerState, op string, sho
defer t.onStop() defer t.onStop()
if !state.NextInProgress { if !state.NextInProgress {
if shouldPrintFile { if shouldPrintFile {
printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) printPos(t, state.CurrentThread, printPosShowArrow)
} }
return nil return nil
} }
@ -1356,7 +1356,7 @@ func continueUntilCompleteNext(t *Term, state *api.DebuggerState, op string, sho
fallthrough fallthrough
default: default:
t.client.CancelNext() t.client.CancelNext()
printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) printPos(t, state.CurrentThread, printPosShowArrow)
return err return err
} }
} else { } else {
@ -1372,7 +1372,7 @@ func continueUntilCompleteNext(t *Term, state *api.DebuggerState, op string, sho
printcontext(t, state) printcontext(t, state)
} }
if !state.NextInProgress { if !state.NextInProgress {
printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) printPos(t, state.CurrentThread, printPosShowArrow)
return nil return nil
} }
} }
@ -1452,7 +1452,7 @@ func (c *Commands) stepInstruction(t *Term, ctx callContext, args string) error
return err return err
} }
printcontext(t, state) printcontext(t, state)
printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) printPos(t, state.CurrentThread, printPosShowArrow|printPosStepInstruction)
return nil return nil
} }
@ -2385,17 +2385,7 @@ func disassCommand(t *Term, ctx callContext, args string) error {
rest = argv[1] rest = argv[1]
} }
flavor := api.IntelFlavour flavor := t.conf.GetDisassembleFlavour()
if t.conf != nil && t.conf.DisassembleFlavor != nil {
switch *t.conf.DisassembleFlavor {
case "go":
flavor = api.GoFlavour
case "gnu":
flavor = api.GNUFlavour
default:
flavor = api.IntelFlavour
}
}
var disasm api.AsmInstructions var disasm api.AsmInstructions
var disasmErr error var disasmErr error
@ -2438,7 +2428,7 @@ func disassCommand(t *Term, ctx callContext, args string) error {
return disasmErr return disasmErr
} }
disasmPrint(disasm, t.stdout) disasmPrint(disasm, t.stdout, true)
return nil return nil
} }
@ -2678,6 +2668,26 @@ func printTracepoint(t *Term, th *api.Thread, bpname string, fn *api.Function, a
} }
} }
type printPosFlags uint8
const (
printPosShowArrow printPosFlags = 1 << iota
printPosStepInstruction
)
func printPos(t *Term, th *api.Thread, flags printPosFlags) error {
if flags&printPosStepInstruction != 0 {
if t.conf.Position == config.PositionSource {
return printfile(t, th.File, th.Line, flags&printPosShowArrow != 0)
}
return printdisass(t, th.PC)
}
if t.conf.Position == config.PositionDisassembly {
return printdisass(t, th.PC)
}
return printfile(t, th.File, th.Line, flags&printPosShowArrow != 0)
}
func printfile(t *Term, filename string, line int, showArrow bool) error { func printfile(t *Term, filename string, line int, showArrow bool) error {
if filename == "" { if filename == "" {
return nil return nil
@ -2712,6 +2722,35 @@ func printfile(t *Term, filename string, line int, showArrow bool) error {
return t.stdout.ColorizePrint(file.Name(), file, line-lineCount, line+lineCount+1, arrowLine) return t.stdout.ColorizePrint(file.Name(), file, line-lineCount, line+lineCount+1, arrowLine)
} }
func printdisass(t *Term, pc uint64) error {
disasm, err := t.client.DisassemblePC(api.EvalScope{GoroutineID: -1, Frame: 0, DeferredCall: 0}, pc, t.conf.GetDisassembleFlavour())
if err != nil {
return err
}
lineCount := t.conf.GetSourceListLineCount()
showHeader := true
for i := range disasm {
if disasm[i].AtPC {
s := i - lineCount
if s < 0 {
s = 0
}
e := i + lineCount + 1
if e > len(disasm) {
e = len(disasm)
}
showHeader = s == 0
disasm = disasm[s:e]
break
}
}
disasmPrint(disasm, t.stdout, showHeader)
return nil
}
// ExitRequestError is returned when the user // ExitRequestError is returned when the user
// exits Delve. // exits Delve.
type ExitRequestError struct{} type ExitRequestError struct{}
@ -2908,7 +2947,7 @@ func (c *Commands) rewind(t *Term, ctx callContext, args string) error {
} }
printcontext(t, state) printcontext(t, state)
} }
printfile(t, state.CurrentThread.File, state.CurrentThread.Line, true) printPos(t, state.CurrentThread, printPosShowArrow)
return nil return nil
} }

@ -1331,3 +1331,14 @@ func TestTranscript(t *testing.T) {
os.Remove(name) os.Remove(name)
}) })
} }
func TestDisassPosCmd(t *testing.T) {
withTestTerminal("testvariables2", t, func(term *FakeTerminal) {
term.MustExec("continue")
out := term.MustExec("step-instruction")
t.Logf("%q\n", out)
if !strings.Contains(out, "call $runtime.Breakpoint") && !strings.Contains(out, "CALL runtime.Breakpoint(SB)") {
t.Errorf("output doesn't look like disassembly")
}
})
}

@ -10,10 +10,10 @@ import (
"github.com/go-delve/delve/service/api" "github.com/go-delve/delve/service/api"
) )
func disasmPrint(dv api.AsmInstructions, out io.Writer) { func disasmPrint(dv api.AsmInstructions, out io.Writer, showHeader bool) {
bw := bufio.NewWriter(out) bw := bufio.NewWriter(out)
defer bw.Flush() defer bw.Flush()
if len(dv) > 0 && dv[0].Loc.Function != nil { if len(dv) > 0 && dv[0].Loc.Function != nil && showHeader {
fmt.Fprintf(bw, "TEXT %s(SB) %s\n", dv[0].Loc.Function.Name(), dv[0].Loc.File) fmt.Fprintf(bw, "TEXT %s(SB) %s\n", dv[0].Loc.Function.Name(), dv[0].Loc.File)
} }
tw := tabwriter.NewWriter(bw, 1, 8, 1, '\t', 0) tw := tabwriter.NewWriter(bw, 1, 8, 1, '\t', 0)