Skip to content

Commit 22a0fa1

Browse files
committed
boot-qemu.py: Implement '--gdb'
Signed-off-by: Nathan Chancellor <nathan@kernel.org>
1 parent 7a1363b commit 22a0fa1

1 file changed

Lines changed: 87 additions & 21 deletions

File tree

boot-qemu.py

Lines changed: 87 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# pylint: disable=invalid-name
33

44
from argparse import ArgumentParser
5+
import contextlib
56
import os
67
from pathlib import Path
78
import platform
@@ -24,6 +25,8 @@ def __init__(self):
2425
# Properties that can be adjusted by the user or class
2526
self.cmdline = []
2627
self.efi = False
28+
self.gdb = False
29+
self.gdb_bin = ''
2730
self.interactive = False
2831
self.kernel = None
2932
self.kernel_dir = None
@@ -45,7 +48,6 @@ def __init__(self):
4548
'-display', 'none',
4649
'-nodefaults',
4750
'-no-reboot',
48-
'-serial', 'mon:stdio',
4951
] # yapf: disable
5052
self._qemu_path = None
5153
self._ram = '512m'
@@ -107,6 +109,64 @@ def _prepare_initrd(self):
107109

108110
return dst
109111

112+
def _run_fg(self):
113+
# Pretty print and run QEMU command
114+
qemu_cmd = []
115+
116+
if not self.interactive:
117+
utils.check_cmd('timeout')
118+
qemu_cmd += ['timeout', '--foreground', self.timeout]
119+
120+
utils.check_cmd('stdbuf')
121+
qemu_cmd += ['stdbuf', '-eL', '-oL']
122+
123+
qemu_cmd += [self._qemu_path, *self._qemu_args]
124+
125+
print(f"$ {' '.join(shlex.quote(str(elem)) for elem in qemu_cmd)}")
126+
try:
127+
subprocess.run(qemu_cmd, check=True)
128+
except subprocess.CalledProcessError as err:
129+
if err.returncode == 124:
130+
utils.red("ERROR: QEMU timed out!")
131+
else:
132+
utils.red("ERROR: QEMU did not exit cleanly!")
133+
sys.exit(err.returncode)
134+
135+
def _run_gdb(self):
136+
qemu_cmd = [self._qemu_path, *self._qemu_args]
137+
138+
utils.check_cmd(self.gdb_bin)
139+
utils.check_cmd('lsof')
140+
141+
gdb_cmd = [
142+
self.gdb_bin,
143+
Path(self.kernel_dir, 'vmlinux'),
144+
'-ex',
145+
'target remote :1234',
146+
]
147+
148+
while True:
149+
lsof = subprocess.run(['lsof', '-i:1234'],
150+
capture_output=True,
151+
check=False)
152+
if lsof.returncode == 0:
153+
utils.die('Port 1234 is already in use, is QEMU running?')
154+
155+
utils.green('Starting QEMU with gdb connection on port 1234...')
156+
with subprocess.Popen(qemu_cmd,
157+
preexec_fn=os.setpgrp) as qemu_proc:
158+
utils.green(f"Starting {self.gdb_bin}...")
159+
with subprocess.Popen(gdb_cmd) as gdb_proc, \
160+
contextlib.suppress(KeyboardInterrupt):
161+
gdb_proc.wait()
162+
163+
utils.red('Killing QEMU...')
164+
qemu_proc.kill()
165+
166+
answer = input('Re-run QEMU + gdb [y/n] ')
167+
if answer.lower() == 'n':
168+
break
169+
110170
def run(self):
111171
# Make sure QEMU binary is configured and available
112172
if not self._qemu_arch:
@@ -157,6 +217,8 @@ def run(self):
157217
# Kernel options
158218
if self.interactive:
159219
self.cmdline.append('rdinit=/bin/sh')
220+
if self.gdb:
221+
self.cmdline.append('nokaslr')
160222
if self.cmdline:
161223
self._qemu_args += ['-append', ' '.join(self.cmdline)]
162224
self._qemu_args += ['-kernel', self.kernel]
@@ -177,27 +239,14 @@ def run(self):
177239
utils.green(f"QEMU location: \033[0m{self._qemu_path.parent}")
178240
utils.green(f"QEMU version: \033[0m{self._get_qemu_ver_string()}")
179241

180-
# Pretty print and run QEMU command
181-
qemu_cmd = []
182-
183-
if not self.interactive:
184-
utils.check_cmd('timeout')
185-
qemu_cmd += ['timeout', '--foreground', self.timeout]
242+
if self.gdb:
243+
self._qemu_args += ['-s', '-S']
186244

187-
utils.check_cmd('stdbuf')
188-
qemu_cmd += ['stdbuf', '-eL', '-oL']
245+
self._run_gdb()
246+
else:
247+
self._qemu_args += ['-serial', 'mon:stdio']
189248

190-
qemu_cmd += [qemu_path, *self._qemu_args]
191-
192-
print(f"$ {' '.join(shlex.quote(str(elem)) for elem in qemu_cmd)}")
193-
try:
194-
subprocess.run(qemu_cmd, check=True)
195-
except subprocess.CalledProcessError as err:
196-
if err.returncode == 124:
197-
utils.red("ERROR: QEMU timed out!")
198-
else:
199-
utils.red("ERROR: QEMU did not exit cleanly!")
200-
sys.exit(err.returncode)
249+
self._run_fg()
201250

202251
def supports_efi(self):
203252
return False
@@ -275,6 +324,15 @@ def parse_arguments():
275324
parser.add_argument('--efi',
276325
action='store_true',
277326
help='Boot kernel via UEFI (x86_64 only)')
327+
parser.add_argument(
328+
'-g',
329+
'--gdb',
330+
action='store_true',
331+
help="Start QEMU with '-s -S' then launch gdb on 'vmlinux'")
332+
parser.add_argument(
333+
'--gdb-bin',
334+
default='gdb-multiarch',
335+
help='gdb binary to use for debugging (default: gdb-multiarch)')
278336
parser.add_argument(
279337
'-k',
280338
'--kernel-location',
@@ -321,6 +379,10 @@ def parse_arguments():
321379
raise FileNotFoundError(
322380
f"Supplied kernel location ('{kernel_location}') does not exist!")
323381
if kernel_location.is_file():
382+
if args.gdb and kernel_location.name != 'vmlinux':
383+
raise RuntimeError(
384+
'Debugging with gdb requires a kernel build folder to locate vmlinux',
385+
)
324386
runner.kernel = kernel_location
325387
else:
326388
runner.kernel_dir = kernel_location
@@ -335,13 +397,17 @@ def parse_arguments():
335397
f"EFI boot requested on unsupported architecture ('{args.architecture}'), ignoring...",
336398
)
337399

400+
if args.gdb:
401+
runner.gdb = True
402+
runner.gdb_bin = args.gdb_bin
403+
338404
if args.no_kvm:
339405
runner.use_kvm = False
340406

341407
if args.smp:
342408
runner.smp = args.smp
343409

344-
runner.interactive = args.interactive
410+
runner.interactive = args.interactive or args.gdb
345411
runner.timeout = args.timeout
346412

347413
runner.run()

0 commit comments

Comments
 (0)