summary refs log tree commit diff stats
path: root/lib/system/debugger.nim
blob: 937c0d6f0c4763c1c904d3ad3b843edf50402398 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
# Copyright (c) 2009, 2010 hut <hut@lavabit.com>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

from .fsobject import FileSystemObject as SuperClass
class File(SuperClass):
	pass
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
#
#
#            Nim's Runtime Library
#        (c) Copyright 2013 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This file implements basic features for any debugger.

type
  VarSlot* {.compilerproc, final.} = object ## a slot in a frame
    address*: pointer ## the variable's address
    typ*: PNimType    ## the variable's type
    name*: cstring    ## the variable's name; for globals this is "module.name"

  PExtendedFrame = ptr ExtendedFrame
  ExtendedFrame = object  # If the debugger is enabled the compiler
                           # provides an extended frame. Of course
                           # only slots that are
                           # needed are allocated and not 10_000,
                           # except for the global data description.
    f: TFrame
    slots: array[0..10_000, VarSlot]
{.deprecated: [TVarSlot: VarSlot, TExtendedFrame: ExtendedFrame].}

var
  dbgGlobalData: ExtendedFrame  # this reserves much space, but
                                # for now it is the most practical way

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 getLocal*(frame: PFrame; slot: int): VarSlot {.inline.} =
  ## retrieves the meta data for the local variable at `slot`. CAUTION: An
  ## invalid `slot` value causes a corruption!
  result = cast[PExtendedFrame](frame).slots[slot]

proc getGlobalLen*(): int {.inline.} =
  ## gets the number of registered globals.
  result = dbgGlobalData.f.len

proc getGlobal*(slot: int): VarSlot {.inline.} =
  ## retrieves the meta data for the global variable at `slot`. CAUTION: An
  ## invalid `slot` value causes a corruption!
  result = dbgGlobalData.slots[slot]

# ------------------- breakpoint support ------------------------------------

type
  Breakpoint* = object   ## represents a break point
    low*, high*: int     ## range from low to high; if disabled
                         ## both low and high are set to their negative values
    filename*: cstring   ## the filename of the breakpoint

var
  dbgBP: array[0..127, Breakpoint] # breakpoints
  dbgBPlen: int
  dbgBPbloom: int64  # we use a bloom filter to speed up breakpoint checking

  dbgFilenames*: array[0..300, cstring] ## registered filenames;
                                        ## 'nil' terminated
  dbgFilenameLen: int

proc dbgRegisterFilename(filename: cstring) {.compilerproc.} =
  # XXX we could check for duplicates here for DLL support
  dbgFilenames[dbgFilenameLen] = filename
  inc dbgFilenameLen

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].filename = filename
  dbgBP[x].low = line
  dbgBP[x].high = line
  dbgBPbloom = dbgBPbloom or line

proc addBreakpoint*(filename: cstring, lo, hi: int): bool =
  let x = dbgBPlen
  if x >= high(dbgBP): return false
  inc(dbgBPlen)
  result = true
  dbgBP[x].filename = filename
  dbgBP[x].low = lo
  dbgBP[x].high = hi
  for line in lo..hi: dbgBPbloom = dbgBPbloom or line

const
  FileSystemCaseInsensitive = defined(windows) or defined(dos) or defined(os2)

proc fileMatches(c, bp: cstring): bool =
  # bp = breakpoint filename
  # c = current filename
  # we consider it a match if bp is a suffix of c
  # and the character for the suffix does not exist or
  # is one of: \  /  :
  # depending on the OS case does not matter!
  var blen: int = bp.len
  var clen: int = c.len
  if blen > clen: return false
  # check for \ /  :
  if clen-blen-1 >= 0 and c[clen-blen-1] notin {'\\', '/', ':'}:
    return false
  var i = 0
  while i < blen:
    var x = bp[i]
    var y = c[i+clen-blen]
    when FileSystemCaseInsensitive:
      if x >= 'A' and x <= 'Z': x = chr(ord(x) - ord('A') + ord('a'))
      if y >= 'A' and y <= 'Z': y = chr(ord(y) - ord('A') + ord('a'))
    if x != y: return false
    inc(i)
  return true

proc canonFilename*(filename: cstring): cstring =
  ## returns 'nil' if the filename cannot be found.
  for i in 0 .. dbgFilenameLen-1:
    result = dbgFilenames[i]
    if fileMatches(result, filename): return result
  result = nil

iterator listBreakpoints*(): ptr Breakpoint =
  ## lists all breakpoints.
  for i in 0..dbgBPlen-1: yield addr(dbgBP[i])

proc isActive*(b: ptr Breakpoint): bool = b.low > 0
proc flip*(b: ptr Breakpoint) =
  ## enables or disables 'b' depending on its current state.
  b.low = -b.low; b.high = -b.high

proc checkBreakpoints*(filename: cstring, line: int): ptr Breakpoint =
  ## in which breakpoint (if any) we are.
  if (dbgBPbloom and line) != line: return nil
  for b in listBreakpoints():
    if line >= b.low and line <= b.high and filename == b.filename: return b

# ------------------- watchpoint support ------------------------------------

type
  Hash = int
  Watchpoint {.pure, final.} = object
    name: cstring
    address: pointer
    typ: PNimType
    oldValue: Hash
{.deprecated: [THash: Hash, TWatchpoint: Watchpoint].}

var
  watchpoints: array[0..99, Watchpoint]
  watchpointsLen: int

proc `!&`(h: Hash, val: int): Hash {.inline.} =
  result = h +% val
  result = result +% result shl 10
  result = result xor (result shr 6)

proc `!$`(h: Hash): Hash {.inline.} =
  result = h +% h shl 3
  result = result xor (result shr 11)
  result = result +% result shl 15

proc hash(data: pointer, size: int): Hash =
  var h: Hash = 0
  var p = cast[cstring](data)
  var i = 0
  var s = size
  while s > 0:
    h = h !& ord(p[i])
    inc(i)
    dec(s)
  result = !$h

proc hashGcHeader(data: pointer): Hash =
  const headerSize = sizeof(int)*2
  result = hash(cast[pointer](cast[int](data) -% headerSize), headerSize)

proc genericHashAux(dest: pointer, mt: PNimType, shallow: bool,
                    h: Hash): Hash
proc genericHashAux(dest: pointer, n: ptr TNimNode, shallow: bool,
                    h: Hash): Hash =
  var d = cast[ByteAddress](dest)
  case n.kind
  of nkSlot:
    result = genericHashAux(cast[pointer](d +% n.offset), n.typ, shallow, h)
  of nkList:
    result = h
    for i in 0..n.len-1:
      result = result !& genericHashAux(dest, n.sons[i], shallow, result)
  of nkCase:
    result = h !& hash(cast[pointer](d +% n.offset), n.typ.size)
    var m = selectBranch(dest, n)
    if m != nil: result = genericHashAux(dest, m, shallow, result)
  of nkNone: sysAssert(false, "genericHashAux")

proc genericHashAux(dest: pointer, mt: PNimType, shallow: bool,
                    h: Hash): Hash =
  sysAssert(mt != nil, "genericHashAux 2")
  case mt.kind
  of tyString:
    var x = cast[PPointer](dest)[]
    result = h
    if x != nil:
      let s = cast[NimString](x)
      when defined(trackGcHeaders):
        result = result !& hashGcHeader(x)
      else:
        result = result !& hash(x, s.len)
  of tySequence:
    var x = cast[PPointer](dest)
    var dst = cast[ByteAddress](cast[PPointer](dest)[])
    result = h
    if dst != 0:
      when defined(trackGcHeaders):
        result = result !& hashGcHeader(cast[PPointer](dest)[])
      else:
        for i in 0..cast[PGenericSeq](dst).len-1:
          result = result !& genericHashAux(
            cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize),
            mt.base, shallow, result)
  of tyObject, tyTuple:
    # we don't need to copy m_type field for tyObject, as they are equal anyway
    result = genericHashAux(dest, mt.node, shallow, h)
  of tyArray, tyArrayConstr:
    let d = cast[ByteAddress](dest)
    result = h
    for i in 0..(mt.size div mt.base.size)-1:
      result = result !& genericHashAux(cast[pointer](d +% i*% mt.base.size),
                                        mt.base, shallow, result)
  of tyRef:
    when defined(trackGcHeaders):
      var s = cast[PPointer](dest)[]
      if s != nil:
        result = result !& hashGcHeader(s)
    else:
      if shallow:
        result = h !& hash(dest, mt.size)
      else:
        result = h
        var s = cast[PPointer](dest)[]
        if s != nil:
          result = result !& genericHashAux(s, mt.base, shallow, result)
  else:
    result = h !& hash(dest, mt.size) # hash raw bits

proc genericHash(dest: pointer, mt: PNimType): int =
  result = genericHashAux(dest, mt, false, 0)

proc dbgRegisterWatchpoint(address: pointer, name: cstring,
                           typ: PNimType) {.compilerproc.} =
  let L = watchPointsLen
  for i in 0 .. pred(L):
    if watchPoints[i].name == name:
      # address may have changed:
      watchPoints[i].address = address
      return
  if L >= watchPoints.high:
    #debugOut("[Warning] cannot register watchpoint")
    return
  watchPoints[L].name = name
  watchPoints[L].address = address
  watchPoints[L].typ = typ
  watchPoints[L].oldValue = genericHash(address, typ)
  inc watchPointsLen

proc dbgUnregisterWatchpoints*() =
  watchPointsLen = 0

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.

  dbgWatchpointHook*: proc (watchpointName: cstring) {.nimcall.}

proc checkWatchpoints =
  let L = watchPointsLen
  for i in 0 .. pred(L):
    let newHash = genericHash(watchPoints[i].address, watchPoints[i].typ)
    if newHash != watchPoints[i].oldValue:
      dbgWatchpointHook(watchPoints[i].name)
      watchPoints[i].oldValue = newHash

proc endb(line: int, file: cstring) {.compilerproc, noinline.} =
  # This proc is called before every Nim code line!
  if framePtr == nil: return
  if dbgWatchpointHook != nil: checkWatchpoints()
  framePtr.line = line # this is done here for smaller code size!
  framePtr.filename = file
  if dbgLineHook != nil: dbgLineHook()

include "system/endb"