debugger improvements

This commit is contained in:
Araq 2013-03-16 17:07:47 +01:00
commit b63f322a46
5 changed files with 131 additions and 587 deletions

View file

@ -892,6 +892,12 @@ proc getFileHeader(cfilenoext: string): PRope =
result = getCopyright(cfilenoext) result = getCopyright(cfilenoext)
addIntTypes(result) addIntTypes(result)
proc genFilenames(m: BModule): PRope =
discard cgsym(m, "dbgRegisterFilename")
result = nil
for i in 0.. <fileInfos.len:
result.appf("dbgRegisterFilename($1);$n", fileInfos[i].projPath.makeCString)
proc genMainProc(m: BModule) = proc genMainProc(m: BModule) =
const const
CommonMainBody = CommonMainBody =
@ -950,6 +956,8 @@ proc genMainProc(m: BModule) =
nimMain = PosixNimMain nimMain = PosixNimMain
otherMain = PosixCMain otherMain = PosixCMain
if gBreakpoints != nil: discard cgsym(m, "dbgRegisterBreakpoint") if gBreakpoints != nil: discard cgsym(m, "dbgRegisterBreakpoint")
if optEndb in gOptions:
gBreakpoints.app(m.genFilenames)
let initStackBottomCall = if emulatedThreadVars() or let initStackBottomCall = if emulatedThreadVars() or
platform.targetOS == osStandalone: "".toRope platform.targetOS == osStandalone: "".toRope

View file

@ -61,8 +61,8 @@ Executing Commands
Breakpoint Commands Breakpoint Commands
=================== ===================
``b``, ``setbreak`` <identifier> [fromline [toline]] [file] ``b``, ``setbreak`` [fromline [toline]] [file]
Set a new breakpoint named 'identifier' for the given file Set a new breakpoint for the given file
and line numbers. If no file is given, the current execution point's and line numbers. If no file is given, the current execution point's
filename is used. If the filename has no extension, ``.nim`` is filename is used. If the filename has no extension, ``.nim`` is
appended for your convenience. appended for your convenience.
@ -71,16 +71,16 @@ Breakpoint Commands
breakpoint contains a line number range. Some examples if it is still breakpoint contains a line number range. Some examples if it is still
unclear: unclear:
* ``b br1 12 15 thallo`` creates a breakpoint named ``br1`` that * ``b 12 15 thallo`` creates a breakpoint that
will be triggered if the instruction pointer reaches one of the will be triggered if the instruction pointer reaches one of the
lines 12-15 in the file ``thallo.nim``. lines 12-15 in the file ``thallo.nim``.
* ``b br1 12 thallo`` creates a breakpoint named ``br1`` that * ``b 12 thallo`` creates a breakpoint that
will be triggered if the instruction pointer reaches the will be triggered if the instruction pointer reaches the
line 12 in the file ``thallo.nim``. line 12 in the file ``thallo.nim``.
* ``b br1 12`` creates a breakpoint named ``br1`` that * ``b 12`` creates a breakpoint that
will be triggered if the instruction pointer reaches the will be triggered if the instruction pointer reaches the
line 12 in the current file. line 12 in the current file.
* ``b br1`` creates a breakpoint named ``br1`` that * ``b`` creates a breakpoint that
will be triggered if the instruction pointer reaches the will be triggered if the instruction pointer reaches the
current line in the current file again. current line in the current file again.

View file

@ -102,6 +102,16 @@ proc newAny(value: pointer, rawType: PNimType): TAny =
result.value = value result.value = value
result.rawType = rawType result.rawType = rawType
when defined(system.TVarSlot):
proc toAny*(x: TVarSlot): TAny {.inline.} =
## constructs a ``TAny`` object from a variable slot ``x``.
## This captures `x`'s address, so `x` can be modified with its
## ``TAny`` wrapper! The client needs to ensure that the wrapper
## **does not** live longer than `x`!
## This is provided for easier reflection capabilities of a debugger.
result.value = x.address
result.rawType = x.typ
proc toAny*[T](x: var T): TAny {.inline.} = proc toAny*[T](x: var T): TAny {.inline.} =
## constructs a ``TAny`` object from `x`. This captures `x`'s address, so ## constructs a ``TAny`` object from `x`. This captures `x`'s address, so
## `x` can be modified with its ``TAny`` wrapper! The client needs to ensure ## `x` can be modified with its ``TAny`` wrapper! The client needs to ensure

View file

@ -1650,10 +1650,6 @@ const nimrodStackTrace = compileOption("stacktrace")
# of the code # of the code
var var
dbgLineHook*: proc () {.nimcall.}
## set this variable to provide a procedure that should be called before
## each executed instruction. This should only be used by debuggers!
## Only code compiled with the ``debugger:on`` switch calls this hook.
globalRaiseHook*: proc (e: ref E_Base): bool {.nimcall.} globalRaiseHook*: proc (e: ref E_Base): bool {.nimcall.}
## with this hook you can influence exception handling on a global level. ## with this hook you can influence exception handling on a global level.
## If not nil, every 'raise' statement ends up calling this hook. Ordinary ## If not nil, every 'raise' statement ends up calling this hook. Ordinary
@ -1691,13 +1687,14 @@ var
## continues and the program is terminated. ## continues and the program is terminated.
type type
PFrame = ptr TFrame PFrame* = ptr TFrame ## represents a runtime frame of the call stack;
TFrame {.importc, nodecl, final.} = object ## part of the debugger API.
prev: PFrame TFrame* {.importc, nodecl, final.} = object ## the frame itself
procname: CString prev*: PFrame ## previous frame; used for chaining the call stack
line: int # current line number procname*: cstring ## name of the proc that is currently executing
filename: CString line*: int ## line number of the proc that is currently executing
len: int # length of slots (when not debugging always zero) filename*: cstring ## filename of the proc that is currently executing
len*: int ## length of the inspectable slots
when not defined(JS): when not defined(JS):
{.push stack_trace:off, profiler:off.} {.push stack_trace:off, profiler:off.}

View file

@ -7,187 +7,95 @@
# distribution, for details about the copyright. # distribution, for details about the copyright.
# #
# This file implements the embedded debugger that can be linked ## This file implements basic features for any debugger.
# with the application. Mostly we do not use dynamic memory here as that
# would interfere with the GC and trigger ON/OFF errors if the
# user program corrupts memory. Unfortunately, for dispaying
# variables we use the ``system.repr()`` proc which uses Nimrod
# strings and thus allocates memory from the heap. Pity, but
# I do not want to implement ``repr()`` twice.
type type
TStaticStr {.pure, final.} = object TVarSlot* {.compilerproc, final.} = object ## a slot in a frame
len: int address*: pointer ## the variable's address
data: array[0..100, char] typ*: PNimType ## the variable's type
name*: cstring ## the variable's name; for globals this is "module.name"
TDbgState = enum
dbOff, # debugger is turned off
dbStepInto, # debugger is in tracing mode
dbStepOver,
dbSkipCurrent,
dbQuiting, # debugger wants to quit
dbBreakpoints # debugger is only interested in breakpoints
TDbgBreakpoint {.final.} = object
low, high: int # range from low to high; if disabled
# both low and high are set to their negative values
# this makes the check faster and safes memory
filename: cstring
name: TStaticStr # name of breakpoint
TVarSlot {.compilerproc, final.} = object # variable slots used for debugger:
address: pointer
typ: PNimType
name: cstring # for globals this is "module.name"
PExtendedFrame = ptr TExtendedFrame PExtendedFrame = ptr TExtendedFrame
TExtendedFrame {.final.} = object # If the debugger is enabled the compiler TExtendedFrame = object # If the debugger is enabled the compiler
# provides an extended frame. Of course # provides an extended frame. Of course
# only slots that are # only slots that are
# needed are allocated and not 10_000, # needed are allocated and not 10_000,
# except for the global data description. # except for the global data description.
f: TFrame f: TFrame
slots: array[0..10_000, TVarSlot] slots: array[0..10_000, TVarSlot]
var var
dbgUser: TStaticStr # buffer for user input; first command is ``step_into``
# needs to be global cause we store the last command
# in it
dbgState: TDbgState # state of debugger
dbgBP: array[0..127, TDbgBreakpoint] # breakpoints
dbgBPlen: int
dbgBPbloom: int64 # we use a bloom filter to speed up breakpoint checking
dbgSkipToFrame: PFrame # frame to be skipped to
dbgGlobalData: TExtendedFrame # this reserves much space, but dbgGlobalData: TExtendedFrame # this reserves much space, but
# for now it is the most practical way # for now it is the most practical way
maxDisplayRecDepth: int = 5 # do not display too much data! proc dbgRegisterGlobal(name: cstring, address: pointer,
typ: PNimType) {.compilerproc.} =
let i = dbgGlobalData.f.len
if i >= high(dbgGlobalData.slots):
#debugOut("[Warning] cannot register global ")
return
dbgGlobalData.slots[i].name = name
dbgGlobalData.slots[i].typ = typ
dbgGlobalData.slots[i].address = address
inc(dbgGlobalData.f.len)
proc setLen(s: var TStaticStr, newLen=0) = proc getLocal*(frame: PFrame; slot: int): TVarSlot {.inline.} =
s.len = newLen ## retrieves the meta data for the local variable at `slot`. CAUTION: An
s.data[newLen] = '\0' ## invalid `slot` value causes a corruption!
result = cast[PExtendedFrame](frame).slots[slot]
proc add(s: var TStaticStr, c: char) = proc getGlobalLen*(): int {.inline.} =
if s.len < high(s.data)-1: ## gets the number of registered globals.
s.data[s.len] = c result = dbgGlobalData.f.len
s.data[s.len+1] = '\0'
inc s.len
proc add(s: var TStaticStr, c: cstring) = proc getGlobal*(slot: int): TVarSlot {.inline.} =
var i = 0 ## retrieves the meta data for the global variable at `slot`. CAUTION: An
while c[i] != '\0': ## invalid `slot` value causes a corruption!
add s, c[i] result = dbgGlobalData.slots[slot]
inc i
proc assign(s: var TStaticStr, c: cstring) = # ------------------- breakpoint support ------------------------------------
setLen(s)
add s, c
proc `==`(a, b: TStaticStr): bool = type
if a.len == b.len: TBreakpoint* = object ## represents a break point
for i in 0 .. a.len-1: low*, high*: int ## range from low to high; if disabled
if a.data[i] != b.data[i]: return false ## both low and high are set to their negative values
return true filename*: cstring ## the filename of the breakpoint
proc `==`(a: TStaticStr, b: cstring): bool = var
result = c_strcmp(a.data, b) == 0 dbgBP: array[0..127, TBreakpoint] # breakpoints
dbgBPlen: int
dbgBPbloom: int64 # we use a bloom filter to speed up breakpoint checking
proc findBreakpoint(name: TStaticStr): int = dbgFilenames*: array[0..300, cstring] ## registered filenames;
# returns -1 if not found ## 'nil' terminated
for i in countdown(dbgBPlen-1, 0): dbgFilenameLen: int
if name == dbgBP[i].name: return i
return -1
proc write(f: TFile, s: TStaticStr) = proc dbgRegisterFilename(filename: cstring) {.compilerproc.} =
write(f, cstring(s.data)) # XXX we could check for duplicates here for DLL support
dbgFilenames[dbgFilenameLen] = filename
inc dbgFilenameLen
proc ListBreakPoints() = proc dbgRegisterBreakpoint(line: int,
write(stdout, "*** endb| Breakpoints:\n") filename, name: cstring) {.compilerproc.} =
for i in 0 .. dbgBPlen-1: let x = dbgBPlen
write(stdout, dbgBP[i].name) if x >= high(dbgBP):
write(stdout, ": ") #debugOut("[Warning] cannot register breakpoint")
write(stdout, abs(dbgBP[i].low)) return
write(stdout, "..") inc(dbgBPlen)
write(stdout, abs(dbgBP[i].high)) dbgBP[x].filename = filename
write(stdout, dbgBP[i].filename) dbgBP[x].low = line
if dbgBP[i].low < 0: dbgBP[x].high = line
write(stdout, " [disabled]\n") dbgBPbloom = dbgBPbloom or line
else:
write(stdout, "\n")
write(stdout, "***\n")
proc openAppend(filename: cstring): TFile = proc addBreakpoint*(filename: cstring, lo, hi: int): bool =
var p: pointer = fopen(filename, "ab") let x = dbgBPlen
if p != nil: if x >= high(dbgBP): return false
result = cast[TFile](p) inc(dbgBPlen)
write(result, "----------------------------------------\n") result = true
dbgBP[x].filename = filename
proc dbgRepr(p: pointer, typ: PNimType): string = dbgBP[x].low = lo
var cl: TReprClosure dbgBP[x].high = hi
initReprClosure(cl) for line in lo..hi: dbgBPbloom = dbgBPbloom or line
cl.recDepth = maxDisplayRecDepth
# locks for the GC turned out to be a bad idea...
# inc(recGcLock)
result = ""
reprAux(result, p, typ, cl)
# dec(recGcLock)
deinitReprClosure(cl)
proc writeVariable(stream: TFile, slot: TVarSlot) =
write(stream, slot.name)
write(stream, " = ")
writeln(stream, dbgRepr(slot.address, slot.typ))
proc ListFrame(stream: TFile, f: PExtendedFrame) =
write(stream, "*** endb| Frame (")
write(stream, f.f.len)
write(stream, " slots):\n")
for i in 0 .. f.f.len-1:
writeVariable(stream, f.slots[i])
write(stream, "***\n")
proc ListVariables(stream: TFile, f: PExtendedFrame) =
write(stream, "*** endb| Frame (")
write(stream, f.f.len)
write(stream, " slots):\n")
for i in 0 .. f.f.len-1:
writeln(stream, f.slots[i].name)
write(stream, "***\n")
proc debugOut(msg: cstring) =
# the *** *** markers are for easy recognition of debugger
# output for external frontends.
write(stdout, "*** endb| ")
write(stdout, msg)
write(stdout, "***\n")
proc dbgFatal(msg: cstring) =
debugOut(msg)
dbgAborting = True # the debugger wants to abort
quit(1)
proc findVariable(frame: PExtendedFrame, varname: cstring): int =
for i in 0 .. frame.f.len - 1:
if c_strcmp(frame.slots[i].name, varname) == 0: return i
return -1
proc dbgShowCurrentProc(dbgFramePointer: PFrame) =
if dbgFramePointer != nil:
write(stdout, "*** endb| now in proc: ")
write(stdout, dbgFramePointer.procname)
write(stdout, " ***\n")
else:
write(stdout, "*** endb| (proc name not available) ***\n")
proc dbgShowExecutionPoint() =
write(stdout, "*** endb| ")
write(stdout, framePtr.filename)
write(stdout, "(")
write(stdout, framePtr.line)
write(stdout, ") ")
write(stdout, framePtr.procname)
write(stdout, " ***\n")
const const
FileSystemCaseInsensitive = defined(windows) or defined(dos) or defined(os2) FileSystemCaseInsensitive = defined(windows) or defined(dos) or defined(os2)
@ -216,337 +124,29 @@ proc fileMatches(c, bp: cstring): bool =
inc(i) inc(i)
return true return true
proc dbgBreakpointReached(line: int): int = proc canonFilename*(filename: cstring): cstring =
for i in 0..dbgBPlen-1: ## returns 'nil' if the filename cannot be found.
if line >= dbgBP[i].low and line <= dbgBP[i].high and for i in 0 .. <dbgFilenameLen:
fileMatches(framePtr.filename, dbgBP[i].filename): return i result = dbgFilenames[i]
return -1 if fileMatches(result, filename): return result
result = nil
proc scanAndAppendWord(src: cstring, a: var TStaticStr, start: int): int = iterator listBreakpoints*(): ptr TBreakpoint =
result = start ## lists all breakpoints.
# skip whitespace: for i in 0..dbgBPlen-1: yield addr(dbgBP[i])
while src[result] in {'\t', ' '}: inc(result)
while True:
case src[result]
of 'a'..'z', '0'..'9': add(a, src[result])
of '_': nil # just skip it
of 'A'..'Z': add(a, chr(ord(src[result]) - ord('A') + ord('a')))
else: break
inc(result)
proc scanWord(src: cstring, a: var TStaticStr, start: int): int = proc isActive*(b: ptr TBreakpoint): bool = b.low > 0
setlen(a) proc flip*(b: ptr TBreakpoint) =
result = scanAndAppendWord(src, a, start) ## enables or disables 'b' depending on its current state.
b.low = -b.low; b.high = -b.high
proc scanFilename(src: cstring, a: var TStaticStr, start: int): int = proc checkBreakpoints*(filename: cstring, line: int): ptr TBreakpoint =
result = start ## in which breakpoint (if any) we are.
setLen a if (dbgBPbloom and line) != line: return nil
# skip whitespace: for b in listBreakpoints():
while src[result] in {'\t', ' '}: inc(result) if line >= b.low and line <= b.high and filename == b.filename: return b
while src[result] notin {'\t', ' ', '\0'}:
add(a, src[result])
inc(result)
proc scanNumber(src: cstring, a: var int, start: int): int = # ------------------- watchpoint support ------------------------------------
result = start
a = 0
while src[result] in {'\t', ' '}: inc(result)
while true:
case src[result]
of '0'..'9': a = a * 10 + ord(src[result]) - ord('0')
of '_': nil # skip underscores (nice for long line numbers)
else: break
inc(result)
proc dbgHelp() =
debugOut("""
list of commands (see the manual for further help):
GENERAL
h, help display this help message
q, quit quit the debugger and the program
<ENTER> repeat the previous debugger command
EXECUTING
s, step single step, stepping into routine calls
n, next single step, without stepping into routine calls
f, skipcurrent continue execution until the current routine finishes
c, continue, r, run continue execution until the next breakpoint
i, ignore continue execution, ignore all breakpoints
BREAKPOINTS
b, break <name> [fromline [toline]] [file]
set a new breakpoint named 'name' for line and file
if line or file are omitted the current one is used
breakpoints display the entire breakpoint list
disable <name> disable a breakpoint
enable <name> enable a breakpoint
DATA DISPLAY
e, eval <expr> evaluate the expression <expr>
o, out <file> <expr> evaluate <expr> and write it to <file>
w, where display the current execution point
stackframe [file] display current stack frame [and write it to file]
u, up go up in the call stack
d, down go down in the call stack
bt, backtrace display the entire call stack
l, locals display available local variables
g, globals display available global variables
maxdisplay <integer> set the display's recursion maximum
""")
proc InvalidCommand() =
debugOut("[Warning] invalid command ignored (type 'h' for help) ")
proc hasExt(s: cstring): bool =
# returns true if s has a filename extension
var i = 0
while s[i] != '\0':
if s[i] == '.': return true
inc i
proc setBreakPoint(s: cstring, start: int) =
var dbgTemp: TStaticStr
var i = scanWord(s, dbgTemp, start)
if i <= start:
InvalidCommand()
return
if dbgBPlen >= high(dbgBP):
debugOut("[Warning] no breakpoint could be set; out of breakpoint space ")
return
var x = dbgBPlen
inc(dbgBPlen)
dbgBP[x].name = dbgTemp
i = scanNumber(s, dbgBP[x].low, i)
if dbgBP[x].low == 0:
# set to current line:
dbgBP[x].low = framePtr.line
i = scanNumber(s, dbgBP[x].high, i)
if dbgBP[x].high == 0: # set to low:
dbgBP[x].high = dbgBP[x].low
for line in dbgBP[x].low .. dbgBP[x].high: dbgBPbloom = dbgBPbloom or line
i = scanFilename(s, dbgTemp, i)
if dbgTemp.len != 0:
debugOut("[Warning] explicit filename for breakpoint not supported")
when false:
if not hasExt(dbgTemp.data): add(dbgTemp, ".nim")
dbgBP[x].filename = dbgTemp
dbgBP[x].filename = framePtr.filename
else: # use current filename
dbgBP[x].filename = framePtr.filename
# skip whitespace:
while s[i] in {' ', '\t'}: inc(i)
if s[i] != '\0':
dec(dbgBPLen) # remove buggy breakpoint
InvalidCommand()
proc BreakpointSetEnabled(s: cstring, start, enabled: int) =
var dbgTemp: TStaticStr
var i = scanWord(s, dbgTemp, start)
if i <= start:
InvalidCommand()
return
var x = findBreakpoint(dbgTemp)
if x < 0: debugOut("[Warning] breakpoint does not exist ")
elif enabled * dbgBP[x].low < 0: # signs are different?
dbgBP[x].low = -dbgBP[x].low
dbgBP[x].high = -dbgBP[x].high
proc dbgEvaluate(stream: TFile, s: cstring, start: int,
currFrame: PExtendedFrame) =
var dbgTemp: tstaticstr
var i = scanWord(s, dbgTemp, start)
while s[i] in {' ', '\t'}: inc(i)
var f = currFrame
if s[i] == '.':
inc(i) # skip '.'
add(dbgTemp, '.')
i = scanAndAppendWord(s, dbgTemp, i)
# search for global var:
f = addr(dbgGlobalData)
if s[i] != '\0':
debugOut("[Warning] could not parse expr ")
return
var j = findVariable(f, dbgTemp.data)
if j < 0:
debugOut("[Warning] could not find variable ")
return
writeVariable(stream, f.slots[j])
proc dbgOut(s: cstring, start: int, currFrame: PExtendedFrame) =
var dbgTemp: tstaticstr
var i = scanFilename(s, dbgTemp, start)
if dbgTemp.len == 0:
InvalidCommand()
return
var stream = openAppend(dbgTemp.data)
if stream == nil:
debugOut("[Warning] could not open or create file ")
return
dbgEvaluate(stream, s, i, currFrame)
close(stream)
proc dbgStackFrame(s: cstring, start: int, currFrame: PExtendedFrame) =
var dbgTemp: TStaticStr
var i = scanFilename(s, dbgTemp, start)
if dbgTemp.len == 0:
# just write it to stdout:
ListFrame(stdout, currFrame)
else:
var stream = openAppend(dbgTemp.data)
if stream == nil:
debugOut("[Warning] could not open or create file ")
return
ListFrame(stream, currFrame)
close(stream)
proc readLine(f: TFile, line: var TStaticStr): bool =
while True:
var c = fgetc(f)
if c < 0'i32:
if line.len > 0: break
else: return false
if c == 10'i32: break # LF
if c == 13'i32: # CR
c = fgetc(f) # is the next char LF?
if c != 10'i32: ungetc(c, f) # no, put the character back
break
add line, chr(int(c))
result = true
proc dbgWriteStackTrace(f: PFrame)
proc CommandPrompt() =
# if we return from this routine, user code executes again
var
again = True
dbgFramePtr = framePtr # for going down and up the stack
dbgDown = 0 # how often we did go down
dbgTemp: TStaticStr
while again:
write(stdout, "*** endb| >>")
let oldLen = dbgUser.len
dbgUser.len = 0
if not readLine(stdin, dbgUser): break
if dbgUser.len == 0: dbgUser.len = oldLen
# now look what we have to do:
var i = scanWord(dbgUser.data, dbgTemp, 0)
template `?`(x: expr): expr = dbgTemp == cstring(x)
if ?"s" or ?"step":
dbgState = dbStepInto
again = false
elif ?"n" or ?"next":
dbgState = dbStepOver
dbgSkipToFrame = framePtr
again = false
elif ?"f" or ?"skipcurrent":
dbgState = dbSkipCurrent
dbgSkipToFrame = framePtr.prev
again = false
elif ?"c" or ?"continue" or ?"r" or ?"run":
dbgState = dbBreakpoints
again = false
elif ?"i" or ?"ignore":
dbgState = dbOff
again = false
elif ?"h" or ?"help":
dbgHelp()
elif ?"q" or ?"quit":
dbgState = dbQuiting
dbgAborting = True
again = false
quit(1) # BUGFIX: quit with error code > 0
elif ?"e" or ?"eval":
dbgEvaluate(stdout, dbgUser.data, i, cast[PExtendedFrame](dbgFramePtr))
elif ?"o" or ?"out":
dbgOut(dbgUser.data, i, cast[PExtendedFrame](dbgFramePtr))
elif ?"stackframe":
dbgStackFrame(dbgUser.data, i, cast[PExtendedFrame](dbgFramePtr))
elif ?"w" or ?"where":
dbgShowExecutionPoint()
elif ?"l" or ?"locals":
ListVariables(stdout, cast[PExtendedFrame](dbgFramePtr))
elif ?"g" or ?"globals":
ListVariables(stdout, addr(dbgGlobalData))
elif ?"u" or ?"up":
if dbgDown <= 0:
debugOut("[Warning] cannot go up any further ")
else:
dbgFramePtr = framePtr
for j in 0 .. dbgDown-2: # BUGFIX
dbgFramePtr = dbgFramePtr.prev
dec(dbgDown)
dbgShowCurrentProc(dbgFramePtr)
elif ?"d" or ?"down":
if dbgFramePtr != nil:
inc(dbgDown)
dbgFramePtr = dbgFramePtr.prev
dbgShowCurrentProc(dbgFramePtr)
else:
debugOut("[Warning] cannot go down any further ")
elif ?"bt" or ?"backtrace":
dbgWriteStackTrace(framePtr)
elif ?"b" or ?"break":
setBreakPoint(dbgUser.data, i)
elif ?"breakpoints":
ListBreakPoints()
elif ?"disable":
BreakpointSetEnabled(dbgUser.data, i, -1)
elif ?"enable":
BreakpointSetEnabled(dbgUser.data, i, +1)
elif ?"maxdisplay":
var parsed: int
i = scanNumber(dbgUser.data, parsed, i)
if dbgUser.data[i-1] in {'0'..'9'}:
if parsed == 0: maxDisplayRecDepth = -1
else: maxDisplayRecDepth = parsed
else:
InvalidCommand()
else: InvalidCommand()
proc endbStep() =
# we get into here if an unhandled exception has been raised
# XXX: do not allow the user to run the program any further?
# XXX: BUG: the frame is lost here!
dbgShowExecutionPoint()
CommandPrompt()
proc checkForBreakpoint(line: int) =
if (dbgBPbloom and line) != line: return
let i = dbgBreakpointReached(line)
if i >= 0:
write(stdout, "*** endb| reached ")
write(stdout, dbgBP[i].name)
write(stdout, " in ")
write(stdout, framePtr.filename)
write(stdout, "(")
write(stdout, framePtr.line)
write(stdout, ") ")
write(stdout, framePtr.procname)
write(stdout, " ***\n")
CommandPrompt()
# interface to the user program:
proc dbgRegisterBreakpoint(line: int,
filename, name: cstring) {.compilerproc.} =
let x = dbgBPlen
if x >= high(dbgBP):
debugOut("[Warning] cannot register breakpoint")
return
inc(dbgBPlen)
dbgBP[x].name.assign(name)
dbgBP[x].filename = filename
dbgBP[x].low = line
dbgBP[x].high = line
dbgBPbloom = dbgBPbloom or line
proc dbgRegisterGlobal(name: cstring, address: pointer,
typ: PNimType) {.compilerproc.} =
let i = dbgGlobalData.f.len
if i >= high(dbgGlobalData.slots):
debugOut("[Warning] cannot register global ")
return
dbgGlobalData.slots[i].name = name
dbgGlobalData.slots[i].typ = typ
dbgGlobalData.slots[i].address = address
inc(dbgGlobalData.f.len)
type type
THash = int THash = int
@ -665,7 +265,7 @@ proc dbgRegisterWatchpoint(address: pointer, name: cstring,
Watchpoints[i].address = address Watchpoints[i].address = address
return return
if L >= watchPoints.high: if L >= watchPoints.high:
debugOut("[Warning] cannot register watchpoint") #debugOut("[Warning] cannot register watchpoint")
return return
Watchpoints[L].name = name Watchpoints[L].name = name
Watchpoints[L].address = address Watchpoints[L].address = address
@ -676,99 +276,28 @@ proc dbgRegisterWatchpoint(address: pointer, name: cstring,
proc dbgUnregisterWatchpoints*() = proc dbgUnregisterWatchpoints*() =
WatchpointsLen = 0 WatchpointsLen = 0
proc dbgWriteStackTrace(f: PFrame) = var
const dbgLineHook*: proc () {.nimcall.}
firstCalls = 32 ## set this variable to provide a procedure that should be called before
var ## each executed instruction. This should only be used by debuggers!
it = f ## Only code compiled with the ``debugger:on`` switch calls this hook.
i = 0
total = 0
tempFrames: array [0..127, PFrame]
# setup long head:
while it != nil and i <= high(tempFrames)-firstCalls:
tempFrames[i] = it
inc(i)
inc(total)
it = it.prev
# go up the stack to count 'total':
var b = it
while it != nil:
inc(total)
it = it.prev
var skipped = 0
if total > len(tempFrames):
# skip N
skipped = total-i-firstCalls+1
for j in 1..skipped:
if b != nil: b = b.prev
# create '...' entry:
tempFrames[i] = nil
inc(i)
# setup short tail:
while b != nil and i <= high(tempFrames):
tempFrames[i] = b
inc(i)
b = b.prev
for j in countdown(i-1, 0):
if tempFrames[j] == nil:
write(stdout, "(")
write(stdout, skipped)
write(stdout, " calls omitted) ...")
else:
write(stdout, tempFrames[j].filename)
if tempFrames[j].line > 0:
write(stdout, '(')
write(stdout, tempFrames[j].line)
write(stdout, ')')
write(stdout, ' ')
write(stdout, tempFrames[j].procname)
write(stdout, "\n")
proc strstr(s1, s2: cstring): cstring {.importc, header: "<string.h>".} dbgWatchpointHook*: proc (watchpointName: cstring) {.nimcall.}
proc interestingFilename(filename: cstring): bool =
#result = strstr(filename, "/rst.nim") == nil
result = true
proc checkWatchpoints = proc checkWatchpoints =
let L = WatchpointsLen let L = WatchpointsLen
for i in 0.. <L: for i in 0.. <L:
let newHash = genericHash(Watchpoints[i].address, Watchpoints[i].typ) let newHash = genericHash(Watchpoints[i].address, Watchpoints[i].typ)
if newHash != Watchpoints[i].oldValue: if newHash != Watchpoints[i].oldValue:
if interestingFilename(framePtr.filename): dbgWatchpointHook(Watchpoints[i].name)
dbgWriteStackTrace(framePtr)
debugOut(Watchpoints[i].name)
Watchpoints[i].oldValue = newHash Watchpoints[i].oldValue = newHash
proc endb(line: int, file: cstring) {.compilerproc.} = proc endb(line: int, file: cstring) {.compilerproc, noinline.} =
# This proc is called before every Nimrod code line! # This proc is called before every Nimrod code line!
# Thus, it must have as few parameters as possible to keep the
# code size small!
# Check if we are at an enabled breakpoint or "in the mood"
if framePtr == nil: return if framePtr == nil: return
let oldState = dbgState if dbgWatchpointHook != nil: checkWatchpoints()
checkWatchpoints()
framePtr.line = line # this is done here for smaller code size! framePtr.line = line # this is done here for smaller code size!
framePtr.filename = file framePtr.filename = file
if dbgLineHook != nil: dbgLineHook() if dbgLineHook != nil: dbgLineHook()
case oldState
of dbStepInto:
# we really want the command prompt here:
dbgShowExecutionPoint()
CommandPrompt()
of dbSkipCurrent, dbStepOver: # skip current routine
if framePtr == dbgSkipToFrame:
dbgShowExecutionPoint()
CommandPrompt()
else: # breakpoints are wanted though (I guess)
checkForBreakpoint(line)
of dbBreakpoints:
# debugger is only interested in breakpoints
checkForBreakpoint(line)
else: nil
proc initDebugger {.inline.} =
dbgState = dbStepInto
dbgUser.len = 1
dbgUser.data[0] = 's'
include "system/endb"