-
Notifications
You must be signed in to change notification settings - Fork 0
/
nimgdb.nim
191 lines (173 loc) · 5.11 KB
/
nimgdb.nim
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#****h* debug/nimgdb
## PURPOSE
## GDB frontend with optional type translation and filtering
## SEE ALSO
#* - [docgen]( href:nimgdb.html )
#* TODO
#* - [ ] connect gdb stdout to translator
#* - [ ] connect translator to stdout
#* - [ ] add test for entitled gdb on macos
#* - [ ] add proc to entitle gdb on macos
#* - [X] connect `nimgdb`'s `stdin` to `gdb`'s `stdin`
#******
import
std/[os, sequtils, strutils, tables, locks, tempfiles, streams, options],
pkg/[platforms, procs],
pkg/prelude/[alias],
"."/[debug_mi, debug_logging]
type
GdbObj = object
path*: string
parser*: GdbMiParser
args*: seq[string]
`proc`*: Process
stdin*: Stream
stdout*: FileHandle
stderr*: FileHandle
Gdb* = ref GdbObj
#****t* nimgdb/GdbInstance
GdbInstance* = ref object
## PURPOSE
## Holds synchronization details for each GDB instance.
thread*: Thread[(seq[string], ptr GdbInstance, Handler, Handler)]
started*: Cond
stdinLock*: Lock
gdb*: Gdb
stdinChan*: Channel[string]
#******
proc `=copy`(a: var GdbObj, b: GdbObj) {.error.}
var
gdbInstances*: seq[GdbInstance]
stop = false
func initGdbSync*(sync: var GdbInstance) =
sync.started.initCond
sync.stdinLock.initLock
proc initGdb*(args: openArray[string]): Gdb =
result = Gdb()
result.path = findExe "gdb"
result.parser = newGdbMiParser()
result.args = @["-i", "mi"]
result.args.add(args)
proc initGdb*(args: openArray[string], sync: var GdbInstance): Gdb {.gcsafe.} =
result = initGdb args
proc start*(gdb: var Gdb) =
gdb.`proc` = gdb.path.startProcess(
args = gdb.args,
options = {}
)
gdb.stdin = gdb.`proc`.inputStream
gdb.stdout = gdb.`proc`.outputHandle
gdb.stderr = gdb.`proc`.errorHandle
proc start*(gdb: var Gdb, sync: var GdbInstance) {.gcsafe.} =
gdb.start
sync.started.signal
sync.gdb = gdb
proc eventLoop*(gdb: Gdb, sync: GdbInstance,
stdoutHandler, stderrHandler: Handler = nil)
{.effectsOf: [stdoutHandler, stderrHandler].} =
echo $gdb.`proc`.monitor(stdoutHandler, stderrHandler)
proc gdbThread*(args: (seq[string], ptr GdbInstance, Handler, Handler)) {.gcsafe.} =
var gdb = initGdb(args[0], args[1][])
gdb.start args[1][]
{.cast(gcsafe).}:
gdb.eventLoop args[1][], args[2], args[3]
#****f* nimgdb/write
proc write*(gdb: Gdb, cmd: string) =
## PURPOSE
## Writes `cmd` to GDB input.
echo "<- " & cmd
gdb.stdin.write cmd
gdb.stdin.flush
#******
#****f* nimgdb/run
proc run*(gdb: Gdb, all, start = false, threadGroup = none(int)): bool =
## PURPOSE
## Run the inferior.
var cmd = "-exec-run"
if all: cmd &= " --all"
elif threadGroup.isSome:
cmd &= " --thread-group " & $threadGroup.get
if start: cmd &= " --start"
cmd &= '\n'
gdb.write cmd
#******
#****f* nimgdb/exit
proc exit*(gdb: Gdb) =
## PURPOSE
## Close the GDB session.
gdb.write "-gdb-exit\n"
#******
#****f* nimgdb/preRunOrAttachCmds
func preRunOrAttachCmds*: string =
## PURPOSE
## Return commands to setup the debugger *before* it runs or attaches to a
## target.
## SEE ALSO
## Debugging with GDB: 27.3.2 Asynchronous command execution and non-stop mode
# https://sourceware.org/gdb/current/onlinedocs/gdb/Asynchronous-and-non_002dstop-modes.html#Asynchronous-and-non_002dstop-modes
result &= "-gdb-set mi-async on\n"
# https://sourceware.org/gdb/current/onlinedocs/gdb/Non_002dStop-Mode.html#Non_002dStop-Mode
result &= "-gdb-set non-stop on\n"
#******
#****f* nimgdb/newDefaultGdbOptions
func newDefaultGdbOptions*: string =
## PURPOSE
## Return default options for a GDB session.
## Can be set after running or attaching to a target.
discard
#******
#****if* nimgdb/ctrlc
proc ctrlc() {.noconv.} =
## PURPOSE
## Cntrl-C signal handler
## Closes each GDB instance.
## SEE ALSO
## `setControlCHook`
stop = true
for i in gdbInstances:
i.gdb.exit
#******
#****f* nimgdb/nimgdb
proc nimgdb*(args: seq[string],
preRunOrAttachCmds = preRunOrAttachCmds(),
dbgOpts = newDefaultGdbOptions()) =
## PURPOSE
## Instantiates a debugger session.
## Passes `args` verbatim to the debugger programm.
## Checks and sets default debugger options.
## SEE ALSO
## `preRunOrAttachCmds`
## `newDefaultGdbOptions`
initLogging()
var i = GdbInstance()
i.initGdbSync
gdbInstances.add i
alias gdbSync, gdbInstances[0]
proc p(o: string) {.nimcall.} = stdout.write o
setControlCHook(ctrlc)
createThread(gdbSync.thread, gdbThread, (args, gdbSync.addr, p, p))
gdbSync.started.wait gdbSync.stdinLock
# TODO: assert that target is not attached or running
gdbSync.gdb.write preRunOrAttachCmds
gdbSync.gdb.write dbgOpts
# discard gdbSync.gdb.run
while not stdin.endOfFile:
gdbSync.gdb.write stdin.readLine & '\n'
gdbSync.thread.joinThread()
#******
when isMainModule:
when defined test:
initLogging()
import std/[tempfiles, unittest]
# import fusion/scripting
# suite "e2e":
# setup:
# let tmp = createTempDir("debug_gdb", "test_e2e")
# teardown:
# removeDir tmp
# test "load inferior":
# check:
# false
else:
import pkg/cligen
dispatch nimgdb