mirror of
https://github.com/cirosantilli/linux-kernel-module-cheat.git
synced 2025-07-23 00:34:48 +00:00
256 lines
8.8 KiB
Python
Executable File
256 lines
8.8 KiB
Python
Executable File
#!/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']:
|
|
linux_full_system = False
|
|
if self.env['gdbserver']:
|
|
before.extend([
|
|
'-ex', 'set sysroot {}'.format(self.env['buildroot_staging_dir']),
|
|
])
|
|
elif self.env['baremetal']:
|
|
linux_full_system = False
|
|
else:
|
|
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(self.env['image_elf']), 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()
|