forked from cirosantilli/linux-kernel-module-cheat
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun-gdb
More file actions
executable file
·258 lines (246 loc) · 8.93 KB
/
run-gdb
File metadata and controls
executable file
·258 lines (246 loc) · 8.93 KB
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
#!/usr/bin/env python3
import os
import signal
import subprocess
import sys
from shell_helpers import LF
import common
import lkmc.import_path
import thread_pool
class GdbTestcase:
def __init__(
self,
source_path,
test_script_path,
cmd,
verbose=False
):
'''
:param verbose: if True, print extra debug information to help understand
why a test is not working
'''
self.prompt = '\(gdb\) '
self.source_path = source_path
import pexpect
self.child = pexpect.spawn(
cmd[0],
cmd[1:],
encoding='utf-8'
)
if verbose:
self.child.logfile = sys.stdout
self.child.setecho(False)
self.child.waitnoecho()
self.child.expect(self.prompt)
test = lkmc.import_path.import_path(test_script_path)
exception = None
try:
test.test(self)
except Exception as e:
exception = e
self.child.sendcontrol('d')
self.child.close()
self.exception = exception
def before(self):
return self.child.before.rstrip()
def continue_to(self, lineid):
line_number = self.find_line(lineid)
self.sendline('tbreak {}'.format(line_number))
self.sendline('continue')
def get_int(self, int_id):
self.sendline('printf "%d\\n", {}'.format(int_id))
return int(self.before())
def get_float(self, float_id):
self.sendline('printf "%f\\n", {}'.format(float_id))
return float(self.before())
def find_line(self, lineid):
'''
Search for the first line that contains a comment line
that ends in /* test-gdb-<lineid> */ and return the line number.
'''
lineend = '/* test-gdb-' + lineid + ' */'
with open(self.source_path, 'r') as f:
for i, line in enumerate(f):
if line.rstrip().endswith(lineend):
return i + 1
return -1
def sendline(self, line):
self.child.sendline(line)
self.child.expect(self.prompt)
class Main(common.LkmcCliFunction):
def __init__(self):
super().__init__(description='''\
Connect with GDB to an emulator to debug Linux itself
''')
self.add_argument(
'--after',
default='',
help='''Pass extra arguments to GDB, to be appended after all other arguments.'''
)
self.add_argument(
'--before',
default='',
help='''Pass extra arguments to GDB to be prepended before any of the arguments passed by this script.'''
)
self.add_argument(
'--continue',
default=True,
help='''\
Run `continue` in GDB after connecting.
* https://cirosantilli.com/linux-kernel-module-cheat#gdb-step-debug-early-boot
* https://cirosantilli.com/linux-kernel-module-cheat#freestanding-programs
* https://cirosantilli.com/linux-kernel-module-cheat#baremetal-gdb-step-debug
'''
)
self.add_argument(
'--gdbserver',
default=False,
help='''https://cirosantilli.com/linux-kernel-module-cheat#gdbserver'''
)
self.add_argument(
'--kgdb',
default=False,
help='''https://cirosantilli.com/linux-kernel-module-cheat#kgdb'''
)
self.add_argument(
'--lxsymbols',
default=True,
help='''\
Use the Linux kernel lxsymbols GDB script.
Only enabled by default when debugging the Linux kernel, not on userland or baremetal.
* https://cirosantilli.com/linux-kernel-module-cheat#gdb-step-debug-kernel-module
* https://cirosantilli.com/linux-kernel-module-cheat#bypass-lx-symbols
'''
)
self.add_argument(
'--sim',
default=False,
help='''\
Use the built-in GDB CPU simulator.
https://cirosantilli.com/linux-kernel-module-cheat#gdb-builtin-cpu-simulator
'''
)
self.add_argument(
'--test',
default=False,
help='''\
Run an expect test case instead of interactive usage. For baremetal and userland,
the script is a .py file next to the source code.
'''
)
self.add_argument(
'break_at',
nargs='?',
help='''\
If given, break at the given expression, e.g. `main`. You will be left there automatically
by default due to --continue if this breakpoint is reached.
'''
)
def timed_main(self):
after = self.sh.shlex_split(self.env['after'])
before = self.sh.shlex_split(self.env['before'])
no_continue = not self.env['continue']
if self.env['test']:
no_continue = True
before.extend([
'-q', LF,
'-nh', LF,
'-ex', 'set confirm off', LF
])
elif self.env['verbose']:
# The output of this would affect the tests.
# https://stackoverflow.com/questions/13496389/gdb-remote-protocol-how-to-analyse-packets
# Also be opinionated and set remotetimeout to allow you to step debug the emulator at the same time.
before.extend([
'-ex', 'set debug remote 1', LF,
'-ex', 'set remotetimeout 99999', LF,
])
if self.env['break_at'] is not None:
break_at = ['-ex', 'break {}'.format(self.env['break_at']), LF]
else:
break_at = []
if self.env['userland']:
image = self.env['image']
linux_full_system = False
if self.env['gdbserver']:
before.extend([
'-ex', 'set sysroot {}'.format(self.env['buildroot_staging_dir']),
])
elif self.env['baremetal']:
image = self.env['image']
linux_full_system = False
else:
image = self.env['vmlinux']
linux_full_system = True
cmd = (
[self.env['gdb_path'], LF] +
before
)
if linux_full_system:
cmd.extend(['-ex', 'add-auto-load-safe-path {}'.format(self.env['linux_build_dir']), LF])
if self.env['sim']:
target = 'sim'
else:
if self.env['gdbserver']:
port = self.env['qemu_hostfwd_generic_port']
elif self.env['kgdb']:
port = self.env['extra_serial_port']
else:
port = self.env['gdb_port']
target = 'remote localhost:{}'.format(port)
cmd.extend([
'-ex', 'file {}'.format(image), LF,
'-ex', 'target {}'.format(target), LF,
])
if not self.env['kgdb']:
cmd.extend(break_at)
if not no_continue:
# ## lx-symbols
#
# ### lx-symbols after continue
#
# lx symbols must be run after continue.
#
# running it immediately after the connect on the bootloader leads to failure,
# likely because kernel structure on which it depends are not yet available.
#
# With this setup, continue runs, and lx-symbols only runs when a break happens,
# either by hitting the breakpoint, or by entering Ctrl + C.
#
# Sure, if the user sets a break on a raw address of the bootloader,
# problems will still arise, but let's think about that some other time.
#
# ### lx-symbols autoload
#
# The lx-symbols commands gets loaded through the file vmlinux-gdb.py
# which gets put on the kernel build root when python debugging scripts are enabled.
cmd.extend(['-ex', 'continue', LF])
if self.env['lxsymbols'] and linux_full_system:
cmd.extend(['-ex', 'lx-symbols {}'.format(self.env['kernel_modules_build_subdir']), LF])
cmd.extend(after)
if self.env['test']:
self.sh.print_cmd(cmd)
if not self.env['dry_run']:
exception = GdbTestcase(
self.env['source_path'],
os.path.splitext(self.env['source_path'])[0] + '.py',
self.sh.strip_newlines(cmd),
verbose=self.env['verbose'],
).exception
if exception is None:
exit_status = 0
else:
exit_status = 1
self.log_info(thread_pool.ThreadPool.exception_traceback_string(exception))
return exit_status
else:
# I would rather have cwd be out_rootfs_overlay_dir,
# but then lx-symbols cannot fine the vmlinux and fails with:
# vmlinux: No such file or directory.
return self.sh.run_cmd(
cmd,
cmd_file=os.path.join(self.env['run_dir'], 'run-gdb.sh'),
cwd=self.env['linux_build_dir']
)
if __name__ == '__main__':
Main().cli()