diff --git a/Documentation/usage/dlv.md b/Documentation/usage/dlv.md index c8435026..5f5673d8 100644 --- a/Documentation/usage/dlv.md +++ b/Documentation/usage/dlv.md @@ -36,6 +36,7 @@ Pass flags to the program you are debugging using `--`, for example: debugger Log debugger commands gdbwire Log connection to gdbserial backend lldbout Copy output from debugserver/lldb to standard output + debuglineerr Log recoverable errors reading .debug_line Defaults to "debugger" when logging is enabled with --log. --wd string Working directory for running the program. (default ".") ``` diff --git a/Documentation/usage/dlv_attach.md b/Documentation/usage/dlv_attach.md index dc2a4c95..d5c9c7e2 100644 --- a/Documentation/usage/dlv_attach.md +++ b/Documentation/usage/dlv_attach.md @@ -36,6 +36,7 @@ dlv attach pid [executable] debugger Log debugger commands gdbwire Log connection to gdbserial backend lldbout Copy output from debugserver/lldb to standard output + debuglineerr Log recoverable errors reading .debug_line Defaults to "debugger" when logging is enabled with --log. --wd string Working directory for running the program. (default ".") ``` diff --git a/Documentation/usage/dlv_connect.md b/Documentation/usage/dlv_connect.md index 2eabaf27..f4fb53df 100644 --- a/Documentation/usage/dlv_connect.md +++ b/Documentation/usage/dlv_connect.md @@ -31,6 +31,7 @@ dlv connect addr debugger Log debugger commands gdbwire Log connection to gdbserial backend lldbout Copy output from debugserver/lldb to standard output + debuglineerr Log recoverable errors reading .debug_line Defaults to "debugger" when logging is enabled with --log. --wd string Working directory for running the program. (default ".") ``` diff --git a/Documentation/usage/dlv_core.md b/Documentation/usage/dlv_core.md index 8e0d6f48..ab282b11 100644 --- a/Documentation/usage/dlv_core.md +++ b/Documentation/usage/dlv_core.md @@ -35,6 +35,7 @@ dlv core debugger Log debugger commands gdbwire Log connection to gdbserial backend lldbout Copy output from debugserver/lldb to standard output + debuglineerr Log recoverable errors reading .debug_line Defaults to "debugger" when logging is enabled with --log. --wd string Working directory for running the program. (default ".") ``` diff --git a/Documentation/usage/dlv_debug.md b/Documentation/usage/dlv_debug.md index 6c78ad4a..fd5b4405 100644 --- a/Documentation/usage/dlv_debug.md +++ b/Documentation/usage/dlv_debug.md @@ -42,6 +42,7 @@ dlv debug [package] debugger Log debugger commands gdbwire Log connection to gdbserial backend lldbout Copy output from debugserver/lldb to standard output + debuglineerr Log recoverable errors reading .debug_line Defaults to "debugger" when logging is enabled with --log. --wd string Working directory for running the program. (default ".") ``` diff --git a/Documentation/usage/dlv_exec.md b/Documentation/usage/dlv_exec.md index 22f8fd0d..b9ed0e0b 100644 --- a/Documentation/usage/dlv_exec.md +++ b/Documentation/usage/dlv_exec.md @@ -36,6 +36,7 @@ dlv exec debugger Log debugger commands gdbwire Log connection to gdbserial backend lldbout Copy output from debugserver/lldb to standard output + debuglineerr Log recoverable errors reading .debug_line Defaults to "debugger" when logging is enabled with --log. --wd string Working directory for running the program. (default ".") ``` diff --git a/Documentation/usage/dlv_replay.md b/Documentation/usage/dlv_replay.md index 8364ebe1..6e1d1eef 100644 --- a/Documentation/usage/dlv_replay.md +++ b/Documentation/usage/dlv_replay.md @@ -35,6 +35,7 @@ dlv replay [trace directory] debugger Log debugger commands gdbwire Log connection to gdbserial backend lldbout Copy output from debugserver/lldb to standard output + debuglineerr Log recoverable errors reading .debug_line Defaults to "debugger" when logging is enabled with --log. --wd string Working directory for running the program. (default ".") ``` diff --git a/Documentation/usage/dlv_run.md b/Documentation/usage/dlv_run.md index c8b3c5e5..07c4b298 100644 --- a/Documentation/usage/dlv_run.md +++ b/Documentation/usage/dlv_run.md @@ -31,6 +31,7 @@ dlv run debugger Log debugger commands gdbwire Log connection to gdbserial backend lldbout Copy output from debugserver/lldb to standard output + debuglineerr Log recoverable errors reading .debug_line Defaults to "debugger" when logging is enabled with --log. --wd string Working directory for running the program. (default ".") ``` diff --git a/Documentation/usage/dlv_test.md b/Documentation/usage/dlv_test.md index f62d10da..f0374eef 100644 --- a/Documentation/usage/dlv_test.md +++ b/Documentation/usage/dlv_test.md @@ -42,6 +42,7 @@ dlv test [package] debugger Log debugger commands gdbwire Log connection to gdbserial backend lldbout Copy output from debugserver/lldb to standard output + debuglineerr Log recoverable errors reading .debug_line Defaults to "debugger" when logging is enabled with --log. --wd string Working directory for running the program. (default ".") ``` diff --git a/Documentation/usage/dlv_trace.md b/Documentation/usage/dlv_trace.md index cfabdcf8..39b37847 100644 --- a/Documentation/usage/dlv_trace.md +++ b/Documentation/usage/dlv_trace.md @@ -44,6 +44,7 @@ dlv trace [package] regexp debugger Log debugger commands gdbwire Log connection to gdbserial backend lldbout Copy output from debugserver/lldb to standard output + debuglineerr Log recoverable errors reading .debug_line Defaults to "debugger" when logging is enabled with --log. --wd string Working directory for running the program. (default ".") ``` diff --git a/Documentation/usage/dlv_version.md b/Documentation/usage/dlv_version.md index 5e3eb6a8..11b060c1 100644 --- a/Documentation/usage/dlv_version.md +++ b/Documentation/usage/dlv_version.md @@ -31,6 +31,7 @@ dlv version debugger Log debugger commands gdbwire Log connection to gdbserial backend lldbout Copy output from debugserver/lldb to standard output + debuglineerr Log recoverable errors reading .debug_line Defaults to "debugger" when logging is enabled with --log. --wd string Working directory for running the program. (default ".") ``` diff --git a/_fixtures/testinline.go b/_fixtures/testinline.go index f8ef9207..4848b90d 100644 --- a/_fixtures/testinline.go +++ b/_fixtures/testinline.go @@ -4,7 +4,7 @@ import "fmt" func inlineThis(a int) int { z := a * a - return z + a/a + return z + a } func initialize(a, b *int) { diff --git a/cmd/dlv/cmds/commands.go b/cmd/dlv/cmds/commands.go index e9197bac..b5776ee1 100644 --- a/cmd/dlv/cmds/commands.go +++ b/cmd/dlv/cmds/commands.go @@ -93,6 +93,7 @@ func New(docCall bool) *cobra.Command { debugger Log debugger commands gdbwire Log connection to gdbserial backend lldbout Copy output from debugserver/lldb to standard output + debuglineerr Log recoverable errors reading .debug_line Defaults to "debugger" when logging is enabled with --log.`) RootCommand.PersistentFlags().BoolVarP(&Headless, "headless", "", false, "Run debug server only, in headless mode.") RootCommand.PersistentFlags().BoolVarP(&AcceptMulti, "accept-multiclient", "", false, "Allows a headless server to accept multiple client connections. Note that the server API is not reentrant and clients will have to coordinate.") diff --git a/pkg/dwarf/line/line_parser.go b/pkg/dwarf/line/line_parser.go index 27cfbcbd..d4ece88c 100644 --- a/pkg/dwarf/line/line_parser.go +++ b/pkg/dwarf/line/line_parser.go @@ -32,6 +32,9 @@ type DebugLineInfo struct { // lastMachineCache[pc] is a state machine stopped at an address after pc lastMachineCache map[uint64]*StateMachine + + // logSuppressedErrors enables logging of otherwise suppressed errors + logSuppressedErrors bool } type FileEntry struct { @@ -143,3 +146,8 @@ func readFileEntry(info *DebugLineInfo, buf *bytes.Buffer, exitOnEmptyPath bool) return entry } + +// LogSuppressedErrors enables or disables logging of suppressed errors +func (dbl *DebugLineInfo) LogSuppressedErrors(v bool) { + dbl.logSuppressedErrors = v +} diff --git a/pkg/dwarf/line/line_parser_test.go b/pkg/dwarf/line/line_parser_test.go index f4405241..d6d69cf3 100644 --- a/pkg/dwarf/line/line_parser_test.go +++ b/pkg/dwarf/line/line_parser_test.go @@ -55,10 +55,14 @@ func grabDebugLineSection(p string, t *testing.T) []byte { } const ( - lineBaseGo14 int8 = -1 - lineBaseGo18 int8 = -4 - lineRangeGo14 uint8 = 4 - lineRangeGo18 uint8 = 10 + lineBaseGo14 int8 = -1 + lineBaseGo18 int8 = -4 + lineRangeGo14 uint8 = 4 + lineRangeGo18 uint8 = 10 + versionGo14 uint16 = 2 + versionGo111 uint16 = 3 + opcodeBaseGo14 uint8 = 10 + opcodeBaseGo111 uint8 = 11 ) func testDebugLinePrologueParser(p string, t *testing.T) { @@ -70,7 +74,7 @@ func testDebugLinePrologueParser(p string, t *testing.T) { for _, dbl := range debugLines { prologue := dbl.Prologue - if prologue.Version != uint16(2) { + if prologue.Version != versionGo14 && prologue.Version != versionGo111 { t.Fatal("Version not parsed correctly", prologue.Version) } @@ -94,11 +98,11 @@ func testDebugLinePrologueParser(p string, t *testing.T) { t.Fatal("Line Range not parsed correctly", prologue.LineRange) } - if prologue.OpcodeBase != uint8(10) { + if prologue.OpcodeBase != opcodeBaseGo14 && prologue.OpcodeBase != opcodeBaseGo111 { t.Fatal("Opcode Base not parsed correctly", prologue.OpcodeBase) } - lengths := []uint8{0, 1, 1, 1, 1, 0, 0, 0, 1} + lengths := []uint8{0, 1, 1, 1, 1, 0, 0, 0, 1, 0} for i, l := range prologue.StdOpLengths { if l != lengths[i] { t.Fatal("Length not parsed correctly", l) diff --git a/pkg/dwarf/line/state_machine.go b/pkg/dwarf/line/state_machine.go index 881c115e..6192172c 100644 --- a/pkg/dwarf/line/state_machine.go +++ b/pkg/dwarf/line/state_machine.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "errors" "fmt" + "log" "github.com/derekparker/delve/pkg/dwarf/util" ) @@ -17,22 +18,25 @@ type Location struct { } type StateMachine struct { - dbl *DebugLineInfo - file string - line int - address uint64 - column uint - isStmt bool - basicBlock bool - endSeq bool - lastWasStandard bool - lastDelta int + dbl *DebugLineInfo + file string + line int + address uint64 + column uint + isStmt bool + basicBlock bool + endSeq bool + lastDelta int + prologueEnd bool + epilogueBegin bool // valid is true if the current value of the state machine is the address of // an instruction (using the terminology used by DWARF spec the current // value of the state machine should be appended to the matrix representing // the compilation unit) valid bool + lastOpcodeKind opcodeKind + started bool buf *bytes.Buffer // remaining instructions @@ -45,6 +49,14 @@ type StateMachine struct { lastLine int } +type opcodeKind uint8 + +const ( + specialOpcode opcodeKind = iota + standardOpcode + extendedOpcode +) + type opcodefn func(*StateMachine, *bytes.Buffer) // Special opcodes @@ -58,6 +70,8 @@ const ( DW_LNS_set_basic_block = 7 DW_LNS_const_add_pc = 8 DW_LNS_fixed_advance_pc = 9 + DW_LNS_prologue_end = 10 + DW_LNS_epilogue_begin = 11 ) // Extended opcodes @@ -77,6 +91,8 @@ var standardopcodes = map[byte]opcodefn{ DW_LNS_set_basic_block: setbasicblock, DW_LNS_const_add_pc: constaddpc, DW_LNS_fixed_advance_pc: fixedadvancepc, + DW_LNS_prologue_end: prologueend, + DW_LNS_epilogue_begin: epiloguebegin, } var extendedopcodes = map[byte]opcodefn{ @@ -91,7 +107,8 @@ func newStateMachine(dbl *DebugLineInfo, instructions []byte) *StateMachine { for op := range standardopcodes { opcodes[op] = standardopcodes[op] } - return &StateMachine{dbl: dbl, file: dbl.FileNames[0].Path, line: 1, buf: bytes.NewBuffer(instructions), opcodes: opcodes} + sm := &StateMachine{dbl: dbl, file: dbl.FileNames[0].Path, line: 1, buf: bytes.NewBuffer(instructions), opcodes: opcodes, isStmt: dbl.Prologue.InitialIsStmt == uint8(1)} + return sm } // Returns all PCs for a given file/line. Useful for loops where the 'for' line @@ -102,34 +119,20 @@ func (lineInfo *DebugLineInfo) AllPCsForFileLine(f string, l int) (pcs []uint64) } var ( - foundFile bool - lastAddr uint64 - sm = newStateMachine(lineInfo, lineInfo.Instructions) + lastAddr uint64 + sm = newStateMachine(lineInfo, lineInfo.Instructions) ) for { if err := sm.next(); err != nil { + if lineInfo.logSuppressedErrors { + log.Printf("AllPCsForFileLine error: %v", err) + } break } - if foundFile && sm.file != f { - return - } - if sm.line == l && sm.file == f && sm.address != lastAddr { - foundFile = true - if sm.valid { - pcs = append(pcs, sm.address) - } - // Keep going until we're on a different line. We only care about - // when a line comes back around (i.e. for loop) so get to next line, - // and try to find the line we care about again. - for { - if err := sm.next(); err != nil { - break - } - if l != sm.line { - break - } - } + if sm.line == l && sm.file == f && sm.address != lastAddr && sm.isStmt && sm.valid { + pcs = append(pcs, sm.address) + lastAddr = sm.address } } return @@ -137,7 +140,8 @@ func (lineInfo *DebugLineInfo) AllPCsForFileLine(f string, l int) (pcs []uint64) var NoSourceError = errors.New("no source available") -func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64) ([]uint64, error) { +// AllPCsBetween returns all PC addresses between begin and end (including both begin and end) that have the is_stmt flag set and do not belong to excludeFile:excludeLine +func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64, excludeFile string, excludeLine int) ([]uint64, error) { if lineInfo == nil { return nil, NoSourceError } @@ -150,6 +154,9 @@ func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64) ([]uint64, error for { if err := sm.next(); err != nil { + if lineInfo.logSuppressedErrors { + log.Printf("AllPCsBetween error: %v", err) + } break } if !sm.valid { @@ -158,7 +165,7 @@ func (lineInfo *DebugLineInfo) AllPCsBetween(begin, end uint64) ([]uint64, error if sm.address > end { break } - if sm.address >= begin && sm.address > lastaddr { + if (sm.address >= begin && sm.address > lastaddr) && sm.isStmt && ((sm.file != excludeFile) || (sm.line != excludeLine)) { lastaddr = sm.address pcs = append(pcs, sm.address) } @@ -175,6 +182,17 @@ func (sm *StateMachine) copy() *StateMachine { return &r } +func (lineInfo *DebugLineInfo) stateMachineForEntry(basePC uint64) (sm *StateMachine) { + sm = lineInfo.stateMachineCache[basePC] + if sm == nil { + sm = newStateMachine(lineInfo, lineInfo.Instructions) + sm.PCToLine(basePC) + lineInfo.stateMachineCache[basePC] = sm + } + sm = sm.copy() + return +} + // PCToLine returns the filename and line number associated with pc. // If pc isn't found inside lineInfo's table it will return the filename and // line number associated with the closest PC address preceding pc. @@ -198,13 +216,7 @@ func (lineInfo *DebugLineInfo) PCToLine(basePC, pc uint64) (string, int) { // As a last resort start from the start of the debug_line section. sm = lineInfo.lastMachineCache[basePC] if sm == nil || sm.lastAddress > pc { - sm = lineInfo.stateMachineCache[basePC] - if sm == nil { - sm = newStateMachine(lineInfo, lineInfo.Instructions) - sm.PCToLine(basePC) - lineInfo.stateMachineCache[basePC] = sm - } - sm = sm.copy() + sm = lineInfo.stateMachineForEntry(basePC) lineInfo.lastMachineCache[basePC] = sm } } @@ -216,6 +228,9 @@ func (lineInfo *DebugLineInfo) PCToLine(basePC, pc uint64) (string, int) { func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) { if !sm.started { if err := sm.next(); err != nil { + if sm.dbl.logSuppressedErrors { + log.Printf("PCToLine error: %v", err) + } return "", 0, false } } @@ -232,6 +247,9 @@ func (sm *StateMachine) PCToLine(pc uint64) (string, int, bool) { } } if err := sm.next(); err != nil { + if sm.dbl.logSuppressedErrors { + log.Printf("PCToLine error: %v", err) + } break } } @@ -252,8 +270,15 @@ func (lineInfo *DebugLineInfo) LineToPC(filename string, lineno int) uint64 { sm = newStateMachine(lineInfo, lineInfo.Instructions) ) + // if no instruction marked is_stmt is found fallback to the first + // instruction assigned to the filename:line. + var fallbackPC uint64 + for { if err := sm.next(); err != nil { + if lineInfo.logSuppressedErrors { + log.Printf("LineToPC error: %v", err) + } break } if foundFile && sm.file != filename { @@ -262,11 +287,36 @@ func (lineInfo *DebugLineInfo) LineToPC(filename string, lineno int) uint64 { if sm.line == lineno && sm.file == filename { foundFile = true if sm.valid { - return sm.address + if sm.isStmt { + return sm.address + } else if fallbackPC == 0 { + fallbackPC = sm.address + } } } } - return 0 + return fallbackPC +} + +// PrologueEndPC returns the first PC address marked as prologue_end in the half open interval [start, end) +func (lineInfo *DebugLineInfo) PrologueEndPC(start, end uint64) (pc uint64, file string, line int, ok bool) { + sm := lineInfo.stateMachineForEntry(start) + for { + if sm.valid { + if sm.address >= end { + return 0, "", 0, false + } + if sm.prologueEnd { + return sm.address, sm.file, sm.line, true + } + } + if err := sm.next(); err != nil { + if lineInfo.logSuppressedErrors { + log.Printf("PrologueEnd error: %v", err) + } + return 0, "", 0, false + } + } } func (sm *StateMachine) next() error { @@ -282,12 +332,21 @@ func (sm *StateMachine) next() error { sm.isStmt = false sm.basicBlock = false } + if sm.lastOpcodeKind == specialOpcode { + sm.basicBlock = false + sm.prologueEnd = false + sm.epilogueBegin = false + } b, err := sm.buf.ReadByte() if err != nil { return err } if int(b) < len(sm.opcodes) { - sm.lastWasStandard = b != 0 + if b == 0 { + sm.lastOpcodeKind = extendedOpcode + } else { + sm.lastOpcodeKind = standardOpcode + } sm.valid = false sm.opcodes[b](sm, sm.buf) } else if b < sm.dbl.Prologue.OpcodeBase { @@ -309,15 +368,11 @@ func execSpecialOpcode(sm *StateMachine, instr byte) { decoded = opcode - sm.dbl.Prologue.OpcodeBase ) - if sm.dbl.Prologue.InitialIsStmt == uint8(1) { - sm.isStmt = true - } - sm.lastDelta = int(sm.dbl.Prologue.LineBase + int8(decoded%sm.dbl.Prologue.LineRange)) sm.line += sm.lastDelta sm.address += uint64(decoded/sm.dbl.Prologue.LineRange) * uint64(sm.dbl.Prologue.MinInstrLength) sm.basicBlock = false - sm.lastWasStandard = false + sm.lastOpcodeKind = specialOpcode sm.valid = true } @@ -400,3 +455,11 @@ func definefile(sm *StateMachine, buf *bytes.Buffer) { entry := readFileEntry(sm.dbl, sm.buf, false) sm.definedFiles = append(sm.definedFiles, entry) } + +func prologueend(sm *StateMachine, buf *bytes.Buffer) { + sm.prologueEnd = true +} + +func epiloguebegin(sm *StateMachine, buf *bytes.Buffer) { + sm.epilogueBegin = true +} diff --git a/pkg/logflags/logflags.go b/pkg/logflags/logflags.go index 73afd0a7..5d346c3a 100644 --- a/pkg/logflags/logflags.go +++ b/pkg/logflags/logflags.go @@ -8,6 +8,8 @@ import ( var debugger = false var gdbWire = false var lldbServerOutput = false +var suppressedErrors = false +var debugLineErrors = false // GdbWire returns true if the gdbserial package should log all the packets // exchanged with the stub. @@ -26,6 +28,12 @@ func LLDBServerOutput() bool { return lldbServerOutput } +// DebugLineErrors returns true if pkg/dwarf/line should log its recoverable +// errors. +func DebugLineErrors() bool { + return debugLineErrors +} + var errLogstrWithoutLog = errors.New("--log-output specified without --log") // Setup sets debugger flags based on the contents of logstr. @@ -48,6 +56,8 @@ func Setup(log bool, logstr string) error { gdbWire = true case "lldbout": lldbServerOutput = true + case "debuglineerr": + debugLineErrors = true } } return nil diff --git a/pkg/proc/disasm_amd64.go b/pkg/proc/disasm_amd64.go index f9fcbc47..3bb4616f 100644 --- a/pkg/proc/disasm_amd64.go +++ b/pkg/proc/disasm_amd64.go @@ -136,9 +136,12 @@ func init() { } } -// FirstPCAfterPrologue returns the address of the first instruction after the prologue for function fn -// If sameline is set FirstPCAfterPrologue will always return an address associated with the same line as fn.Entry -func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error) { +// firstPCAfterPrologueDisassembly returns the address of the first +// instruction after the prologue for function fn by disassembling fn and +// matching the instructions against known split-stack prologue patterns. +// If sameline is set firstPCAfterPrologueDisassembly will always return an +// address associated with the same line as fn.Entry +func firstPCAfterPrologueDisassembly(p Process, fn *Function, sameline bool) (uint64, error) { var mem MemoryReadWriter = p.CurrentThread() breakpoints := p.Breakpoints() bi := p.BinInfo() diff --git a/pkg/proc/gdbserial/gdbserver.go b/pkg/proc/gdbserial/gdbserver.go index 64c39ac4..074f9aeb 100644 --- a/pkg/proc/gdbserial/gdbserver.go +++ b/pkg/proc/gdbserial/gdbserver.go @@ -983,6 +983,9 @@ func (p *Process) writeBreakpoint(addr uint64) (string, int, *proc.Function, []b } func (p *Process) SetBreakpoint(addr uint64, kind proc.BreakpointKind, cond ast.Expr) (*proc.Breakpoint, error) { + if p.exited { + return nil, &proc.ProcessExitedError{Pid: p.conn.pid} + } return p.breakpoints.Set(addr, kind, cond, p.writeBreakpoint) } diff --git a/pkg/proc/proc.go b/pkg/proc/proc.go index 6dae76e2..691e3428 100644 --- a/pkg/proc/proc.go +++ b/pkg/proc/proc.go @@ -328,7 +328,7 @@ func StepOut(dbp Process) error { if selg != nil { deferPCEntry := selg.DeferPC() if deferPCEntry != 0 { - _, _, deferfn := dbp.BinInfo().PCToLine(deferPCEntry) + deferfn := dbp.BinInfo().PCToFunc(deferPCEntry) deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false) if err != nil { return err @@ -565,3 +565,38 @@ func CreateUnrecoveredPanicBreakpoint(p Process, writeBreakpoint writeBreakpoint } } + +// FirstPCAfterPrologue returns the address of the first +// instruction after the prologue for function fn. +// If sameline is set FirstPCAfterPrologue will always return an +// address associated with the same line as fn.Entry. +func FirstPCAfterPrologue(p Process, fn *Function, sameline bool) (uint64, error) { + pc, _, line, ok := fn.cu.lineInfo.PrologueEndPC(fn.Entry, fn.End) + if ok { + if !sameline { + return pc, nil + } else { + _, entryLine := fn.cu.lineInfo.PCToLine(fn.Entry, fn.Entry) + if entryLine == line { + return pc, nil + } + } + } + + pc, err := firstPCAfterPrologueDisassembly(p, fn, sameline) + if err != nil { + return fn.Entry, err + } + + if pc == fn.Entry { + // Look for the first instruction with the stmt flag set, so that setting a + // breakpoint with file:line and with the function name always result on + // the same instruction being selected. + entryFile, entryLine := fn.cu.lineInfo.PCToLine(fn.Entry, fn.Entry) + if pc, _, err := p.BinInfo().LineToPC(entryFile, entryLine); err == nil { + return pc, nil + } + } + + return pc, nil +} diff --git a/pkg/proc/proc_test.go b/pkg/proc/proc_test.go index d8878a17..25af6f32 100644 --- a/pkg/proc/proc_test.go +++ b/pkg/proc/proc_test.go @@ -897,6 +897,9 @@ func stackMatch(stack []loc, locations []proc.Stackframe, skipRuntime bool) bool func TestStacktraceGoroutine(t *testing.T) { mainStack := []loc{{14, "main.stacktraceme"}, {29, "main.main"}} + if goversion.VersionAfterOrEqual(runtime.Version(), 1, 11) { + mainStack[0].line = 15 + } agoroutineStacks := [][]loc{ {{8, "main.agoroutine"}}, {{9, "main.agoroutine"}}, @@ -3618,6 +3621,7 @@ func TestInlinedStacktraceAndVariables(t *testing.T) { t.Fatalf("expected at least two locations for %s:%d (got %d: %#x)", fixture.Source, 6, len(pcs), pcs) } for _, pc := range pcs { + t.Logf("setting breakpoint at %#x\n", pc) _, err := p.SetBreakpoint(pc, proc.UserBreakpoint, nil) assertNoError(err, t, fmt.Sprintf("SetBreakpoint(%#x)", pc)) } @@ -3628,7 +3632,7 @@ func TestInlinedStacktraceAndVariables(t *testing.T) { assertNoError(err, t, "ThreadStacktrace") t.Logf("Stacktrace:\n") for i := range frames { - t.Logf("\t%s at %s:%d\n", frames[i].Call.Fn.Name, frames[i].Call.File, frames[i].Call.Line) + t.Logf("\t%s at %s:%d (%#x)\n", frames[i].Call.Fn.Name, frames[i].Call.File, frames[i].Call.Line, frames[i].Current.PC) } if err := checkFrame(frames[0], "main.inlineThis", fixture.Source, 7, true); err != nil { @@ -3655,7 +3659,7 @@ func TestInlinedStacktraceAndVariables(t *testing.T) { assertNoError(err, t, "ThreadStacktrace (2)") t.Logf("Stacktrace 2:\n") for i := range frames { - t.Logf("\t%s at %s:%d\n", frames[i].Call.Fn.Name, frames[i].Call.File, frames[i].Call.Line) + t.Logf("\t%s at %s:%d (%#x)\n", frames[i].Call.Fn.Name, frames[i].Call.File, frames[i].Call.Line, frames[i].Current.PC) } if err := checkFrame(frames[0], "main.inlineThis", fixture.Source, 7, true); err != nil { @@ -3716,8 +3720,6 @@ func TestInlineStepOver(t *testing.T) { } testseq2Args(".", []string{}, protest.EnableInlining, t, "testinline", "", []seqTest{ {contContinue, 18}, - {contNext, 18}, - {contNext, 19}, {contNext, 19}, {contNext, 20}, }) diff --git a/pkg/proc/threads.go b/pkg/proc/threads.go index 98058482..c2104fe1 100644 --- a/pkg/proc/threads.go +++ b/pkg/proc/threads.go @@ -209,7 +209,7 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error { if selg != nil { deferPCEntry := selg.DeferPC() if deferPCEntry != 0 { - _, _, deferfn := dbp.BinInfo().PCToLine(deferPCEntry) + deferfn := dbp.BinInfo().PCToFunc(deferPCEntry) var err error deferpc, err = FirstPCAfterPrologue(dbp, deferfn, false) if err != nil { @@ -231,7 +231,7 @@ func next(dbp Process, stepInto, inlinedStepOut bool) error { } // Add breakpoints on all the lines in the current function - pcs, err := topframe.Current.Fn.cu.lineInfo.AllPCsBetween(topframe.Current.Fn.Entry, topframe.Current.Fn.End-1) + pcs, err := topframe.Current.Fn.cu.lineInfo.AllPCsBetween(topframe.Current.Fn.Entry, topframe.Current.Fn.End-1, topframe.Current.File, topframe.Current.Line) if err != nil { return err } diff --git a/pkg/proc/types.go b/pkg/proc/types.go index 8de17354..cfbcd3c6 100644 --- a/pkg/proc/types.go +++ b/pkg/proc/types.go @@ -21,6 +21,7 @@ import ( "github.com/derekparker/delve/pkg/dwarf/line" "github.com/derekparker/delve/pkg/dwarf/op" "github.com/derekparker/delve/pkg/dwarf/reader" + "github.com/derekparker/delve/pkg/logflags" ) // The kind field in runtime._type is a reflect.Kind value plus @@ -217,6 +218,7 @@ func (bi *BinaryInfo) loadDebugInfoMaps(debugLineBytes []byte, wg *sync.WaitGrou lineInfoOffset, _ := entry.Val(dwarf.AttrStmtList).(int64) if lineInfoOffset >= 0 && lineInfoOffset < int64(len(debugLineBytes)) { cu.lineInfo = line.Parse(compdir, bytes.NewBuffer(debugLineBytes[lineInfoOffset:])) + cu.lineInfo.LogSuppressedErrors(logflags.DebugLineErrors()) } if producer, _ := entry.Val(dwarf.AttrProducer).(string); cu.isgo && producer != "" { semicolon := strings.Index(producer, ";")