debugger.nim 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2013 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This file implements basic features for any debugger.
  10. type
  11. VarSlot* {.compilerproc, final.} = object ## a slot in a frame
  12. address*: pointer ## the variable's address
  13. typ*: PNimType ## the variable's type
  14. name*: cstring ## the variable's name; for globals this is "module.name"
  15. PExtendedFrame = ptr ExtendedFrame
  16. ExtendedFrame = object # If the debugger is enabled the compiler
  17. # provides an extended frame. Of course
  18. # only slots that are
  19. # needed are allocated and not 10_000,
  20. # except for the global data description.
  21. f: TFrame
  22. slots: array[0..10_000, VarSlot]
  23. var
  24. dbgGlobalData: ExtendedFrame # this reserves much space, but
  25. # for now it is the most practical way
  26. proc dbgRegisterGlobal(name: cstring, address: pointer,
  27. typ: PNimType) {.compilerproc.} =
  28. let i = dbgGlobalData.f.len
  29. if i >= high(dbgGlobalData.slots):
  30. #debugOut("[Warning] cannot register global ")
  31. return
  32. dbgGlobalData.slots[i].name = name
  33. dbgGlobalData.slots[i].typ = typ
  34. dbgGlobalData.slots[i].address = address
  35. inc(dbgGlobalData.f.len)
  36. proc getLocal*(frame: PFrame; slot: int): VarSlot {.inline.} =
  37. ## retrieves the meta data for the local variable at `slot`. CAUTION: An
  38. ## invalid `slot` value causes a corruption!
  39. result = cast[PExtendedFrame](frame).slots[slot]
  40. proc getGlobalLen*(): int {.inline.} =
  41. ## gets the number of registered globals.
  42. result = dbgGlobalData.f.len
  43. proc getGlobal*(slot: int): VarSlot {.inline.} =
  44. ## retrieves the meta data for the global variable at `slot`. CAUTION: An
  45. ## invalid `slot` value causes a corruption!
  46. result = dbgGlobalData.slots[slot]
  47. # ------------------- breakpoint support ------------------------------------
  48. type
  49. Breakpoint* = object ## represents a break point
  50. low*, high*: int ## range from low to high; if disabled
  51. ## both low and high are set to their negative values
  52. filename*: cstring ## the filename of the breakpoint
  53. var
  54. dbgBP: array[0..127, Breakpoint] # breakpoints
  55. dbgBPlen: int
  56. dbgBPbloom: int64 # we use a bloom filter to speed up breakpoint checking
  57. dbgFilenames*: array[0..300, cstring] ## registered filenames;
  58. ## 'nil' terminated
  59. dbgFilenameLen: int
  60. proc dbgRegisterFilename(filename: cstring) {.compilerproc.} =
  61. # XXX we could check for duplicates here for DLL support
  62. dbgFilenames[dbgFilenameLen] = filename
  63. inc dbgFilenameLen
  64. proc dbgRegisterBreakpoint(line: int,
  65. filename, name: cstring) {.compilerproc.} =
  66. let x = dbgBPlen
  67. if x >= high(dbgBP):
  68. #debugOut("[Warning] cannot register breakpoint")
  69. return
  70. inc(dbgBPlen)
  71. dbgBP[x].filename = filename
  72. dbgBP[x].low = line
  73. dbgBP[x].high = line
  74. dbgBPbloom = dbgBPbloom or line
  75. proc addBreakpoint*(filename: cstring, lo, hi: int): bool =
  76. let x = dbgBPlen
  77. if x >= high(dbgBP): return false
  78. inc(dbgBPlen)
  79. result = true
  80. dbgBP[x].filename = filename
  81. dbgBP[x].low = lo
  82. dbgBP[x].high = hi
  83. for line in lo..hi: dbgBPbloom = dbgBPbloom or line
  84. const
  85. FileSystemCaseInsensitive = defined(windows) or defined(dos) or defined(os2)
  86. proc fileMatches(c, bp: cstring): bool =
  87. # bp = breakpoint filename
  88. # c = current filename
  89. # we consider it a match if bp is a suffix of c
  90. # and the character for the suffix does not exist or
  91. # is one of: \ / :
  92. # depending on the OS case does not matter!
  93. var blen: int = bp.len
  94. var clen: int = c.len
  95. if blen > clen: return false
  96. # check for \ / :
  97. if clen-blen-1 >= 0 and c[clen-blen-1] notin {'\\', '/', ':'}:
  98. return false
  99. var i = 0
  100. while i < blen:
  101. var x = bp[i]
  102. var y = c[i+clen-blen]
  103. when FileSystemCaseInsensitive:
  104. if x >= 'A' and x <= 'Z': x = chr(ord(x) - ord('A') + ord('a'))
  105. if y >= 'A' and y <= 'Z': y = chr(ord(y) - ord('A') + ord('a'))
  106. if x != y: return false
  107. inc(i)
  108. return true
  109. proc canonFilename*(filename: cstring): cstring =
  110. ## returns 'nil' if the filename cannot be found.
  111. for i in 0 .. dbgFilenameLen-1:
  112. result = dbgFilenames[i]
  113. if fileMatches(result, filename): return result
  114. result = nil
  115. iterator listBreakpoints*(): ptr Breakpoint =
  116. ## lists all breakpoints.
  117. for i in 0..dbgBPlen-1: yield addr(dbgBP[i])
  118. proc isActive*(b: ptr Breakpoint): bool = b.low > 0
  119. proc flip*(b: ptr Breakpoint) =
  120. ## enables or disables 'b' depending on its current state.
  121. b.low = -b.low; b.high = -b.high
  122. proc checkBreakpoints*(filename: cstring, line: int): ptr Breakpoint =
  123. ## in which breakpoint (if any) we are.
  124. if (dbgBPbloom and line) != line: return nil
  125. for b in listBreakpoints():
  126. if line >= b.low and line <= b.high and filename == b.filename: return b
  127. # ------------------- watchpoint support ------------------------------------
  128. type
  129. Hash = int
  130. Watchpoint {.pure, final.} = object
  131. name: cstring
  132. address: pointer
  133. typ: PNimType
  134. oldValue: Hash
  135. var
  136. watchpoints: array[0..99, Watchpoint]
  137. watchpointsLen: int
  138. proc `!&`(h: Hash, val: int): Hash {.inline.} =
  139. result = h +% val
  140. result = result +% result shl 10
  141. result = result xor (result shr 6)
  142. proc `!$`(h: Hash): Hash {.inline.} =
  143. result = h +% h shl 3
  144. result = result xor (result shr 11)
  145. result = result +% result shl 15
  146. proc hash(data: pointer, size: int): Hash =
  147. var h: Hash = 0
  148. var p = cast[cstring](data)
  149. var i = 0
  150. var s = size
  151. while s > 0:
  152. h = h !& ord(p[i])
  153. inc(i)
  154. dec(s)
  155. result = !$h
  156. proc hashGcHeader(data: pointer): Hash =
  157. const headerSize = sizeof(int)*2
  158. result = hash(cast[pointer](cast[int](data) -% headerSize), headerSize)
  159. proc genericHashAux(dest: pointer, mt: PNimType, shallow: bool,
  160. h: Hash): Hash
  161. proc genericHashAux(dest: pointer, n: ptr TNimNode, shallow: bool,
  162. h: Hash): Hash =
  163. var d = cast[ByteAddress](dest)
  164. case n.kind
  165. of nkSlot:
  166. result = genericHashAux(cast[pointer](d +% n.offset), n.typ, shallow, h)
  167. of nkList:
  168. result = h
  169. for i in 0..n.len-1:
  170. result = result !& genericHashAux(dest, n.sons[i], shallow, result)
  171. of nkCase:
  172. result = h !& hash(cast[pointer](d +% n.offset), n.typ.size)
  173. var m = selectBranch(dest, n)
  174. if m != nil: result = genericHashAux(dest, m, shallow, result)
  175. of nkNone: sysAssert(false, "genericHashAux")
  176. proc genericHashAux(dest: pointer, mt: PNimType, shallow: bool,
  177. h: Hash): Hash =
  178. sysAssert(mt != nil, "genericHashAux 2")
  179. case mt.kind
  180. of tyString:
  181. var x = cast[PPointer](dest)[]
  182. result = h
  183. if x != nil:
  184. let s = cast[NimString](x)
  185. when defined(trackGcHeaders):
  186. result = result !& hashGcHeader(x)
  187. else:
  188. result = result !& hash(x, s.len)
  189. of tySequence:
  190. var x = cast[PPointer](dest)
  191. var dst = cast[ByteAddress](cast[PPointer](dest)[])
  192. result = h
  193. if dst != 0:
  194. when defined(trackGcHeaders):
  195. result = result !& hashGcHeader(cast[PPointer](dest)[])
  196. else:
  197. for i in 0..cast[PGenericSeq](dst).len-1:
  198. result = result !& genericHashAux(
  199. cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize),
  200. mt.base, shallow, result)
  201. of tyObject, tyTuple:
  202. # we don't need to copy m_type field for tyObject, as they are equal anyway
  203. result = genericHashAux(dest, mt.node, shallow, h)
  204. of tyArray, tyArrayConstr:
  205. let d = cast[ByteAddress](dest)
  206. result = h
  207. for i in 0..(mt.size div mt.base.size)-1:
  208. result = result !& genericHashAux(cast[pointer](d +% i*% mt.base.size),
  209. mt.base, shallow, result)
  210. of tyRef:
  211. when defined(trackGcHeaders):
  212. var s = cast[PPointer](dest)[]
  213. if s != nil:
  214. result = result !& hashGcHeader(s)
  215. else:
  216. if shallow:
  217. result = h !& hash(dest, mt.size)
  218. else:
  219. result = h
  220. var s = cast[PPointer](dest)[]
  221. if s != nil:
  222. result = result !& genericHashAux(s, mt.base, shallow, result)
  223. else:
  224. result = h !& hash(dest, mt.size) # hash raw bits
  225. proc genericHash(dest: pointer, mt: PNimType): int =
  226. result = genericHashAux(dest, mt, false, 0)
  227. proc dbgRegisterWatchpoint(address: pointer, name: cstring,
  228. typ: PNimType) {.compilerproc.} =
  229. let L = watchPointsLen
  230. for i in 0 .. pred(L):
  231. if watchPoints[i].name == name:
  232. # address may have changed:
  233. watchPoints[i].address = address
  234. return
  235. if L >= watchPoints.high:
  236. #debugOut("[Warning] cannot register watchpoint")
  237. return
  238. watchPoints[L].name = name
  239. watchPoints[L].address = address
  240. watchPoints[L].typ = typ
  241. watchPoints[L].oldValue = genericHash(address, typ)
  242. inc watchPointsLen
  243. proc dbgUnregisterWatchpoints*() =
  244. watchPointsLen = 0
  245. var
  246. dbgLineHook*: proc () {.nimcall.}
  247. ## set this variable to provide a procedure that should be called before
  248. ## each executed instruction. This should only be used by debuggers!
  249. ## Only code compiled with the ``debugger:on`` switch calls this hook.
  250. dbgWatchpointHook*: proc (watchpointName: cstring) {.nimcall.}
  251. proc checkWatchpoints =
  252. let L = watchPointsLen
  253. for i in 0 .. pred(L):
  254. let newHash = genericHash(watchPoints[i].address, watchPoints[i].typ)
  255. if newHash != watchPoints[i].oldValue:
  256. dbgWatchpointHook(watchPoints[i].name)
  257. watchPoints[i].oldValue = newHash
  258. proc endb(line: int, file: cstring) {.compilerproc, noinline.} =
  259. # This proc is called before every Nim code line!
  260. if framePtr == nil: return
  261. if dbgWatchpointHook != nil: checkWatchpoints()
  262. framePtr.line = line # this is done here for smaller code size!
  263. framePtr.filename = file
  264. if dbgLineHook != nil: dbgLineHook()
  265. include "system/endb"