debugger.nim 9.9 KB

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