run-gdb 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. #!/usr/bin/env python3
  2. import os
  3. import signal
  4. import subprocess
  5. import sys
  6. from shell_helpers import LF
  7. import common
  8. import lkmc.import_path
  9. import thread_pool
  10. class GdbTestcase:
  11. def __init__(
  12. self,
  13. source_path,
  14. test_script_path,
  15. cmd,
  16. verbose=False
  17. ):
  18. '''
  19. :param verbose: if True, print extra debug information to help understand
  20. why a test is not working
  21. '''
  22. self.prompt = '\(gdb\) '
  23. self.source_path = source_path
  24. import pexpect
  25. self.child = pexpect.spawn(
  26. cmd[0],
  27. cmd[1:],
  28. encoding='utf-8'
  29. )
  30. if verbose:
  31. self.child.logfile = sys.stdout
  32. self.child.setecho(False)
  33. self.child.waitnoecho()
  34. self.child.expect(self.prompt)
  35. test = lkmc.import_path.import_path(test_script_path)
  36. exception = None
  37. try:
  38. test.test(self)
  39. except Exception as e:
  40. exception = e
  41. self.child.sendcontrol('d')
  42. self.child.close()
  43. self.exception = exception
  44. def before(self):
  45. return self.child.before.rstrip()
  46. def continue_to(self, lineid):
  47. line_number = self.find_line(lineid)
  48. self.sendline('tbreak {}'.format(line_number))
  49. self.sendline('continue')
  50. def get_int(self, int_id):
  51. self.sendline('printf "%d\\n", {}'.format(int_id))
  52. return int(self.before())
  53. def get_float(self, float_id):
  54. self.sendline('printf "%f\\n", {}'.format(float_id))
  55. return float(self.before())
  56. def find_line(self, lineid):
  57. '''
  58. Search for the first line that contains a comment line
  59. that ends in /* test-gdb-<lineid> */ and return the line number.
  60. '''
  61. lineend = '/* test-gdb-' + lineid + ' */'
  62. with open(self.source_path, 'r') as f:
  63. for i, line in enumerate(f):
  64. if line.rstrip().endswith(lineend):
  65. return i + 1
  66. return -1
  67. def sendline(self, line):
  68. self.child.sendline(line)
  69. self.child.expect(self.prompt)
  70. class Main(common.LkmcCliFunction):
  71. def __init__(self):
  72. super().__init__(description='''\
  73. Connect with GDB to an emulator to debug Linux itself
  74. ''')
  75. self.add_argument(
  76. '--after',
  77. default='',
  78. help='''Pass extra arguments to GDB, to be appended after all other arguments.'''
  79. )
  80. self.add_argument(
  81. '--before',
  82. default='',
  83. help='''Pass extra arguments to GDB to be prepended before any of the arguments passed by this script.'''
  84. )
  85. self.add_argument(
  86. '--continue',
  87. default=True,
  88. help='''\
  89. Run `continue` in GDB after connecting.
  90. * https://cirosantilli.com/linux-kernel-module-cheat#gdb-step-debug-early-boot
  91. * https://cirosantilli.com/linux-kernel-module-cheat#freestanding-programs
  92. * https://cirosantilli.com/linux-kernel-module-cheat#baremetal-gdb-step-debug
  93. '''
  94. )
  95. self.add_argument(
  96. '--gdbserver',
  97. default=False,
  98. help='''https://cirosantilli.com/linux-kernel-module-cheat#gdbserver'''
  99. )
  100. self.add_argument(
  101. '--kgdb',
  102. default=False,
  103. help='''https://cirosantilli.com/linux-kernel-module-cheat#kgdb'''
  104. )
  105. self.add_argument(
  106. '--lxsymbols',
  107. default=True,
  108. help='''\
  109. Use the Linux kernel lxsymbols GDB script.
  110. Only enabled by default when debugging the Linux kernel, not on userland or baremetal.
  111. * https://cirosantilli.com/linux-kernel-module-cheat#gdb-step-debug-kernel-module
  112. * https://cirosantilli.com/linux-kernel-module-cheat#bypass-lx-symbols
  113. '''
  114. )
  115. self.add_argument(
  116. '--sim',
  117. default=False,
  118. help='''\
  119. Use the built-in GDB CPU simulator.
  120. https://cirosantilli.com/linux-kernel-module-cheat#gdb-builtin-cpu-simulator
  121. '''
  122. )
  123. self.add_argument(
  124. '--test',
  125. default=False,
  126. help='''\
  127. Run an expect test case instead of interactive usage. For baremetal and userland,
  128. the script is a .py file next to the source code.
  129. '''
  130. )
  131. self.add_argument(
  132. 'break_at',
  133. nargs='?',
  134. help='''\
  135. If given, break at the given expression, e.g. `main`. You will be left there automatically
  136. by default due to --continue if this breakpoint is reached.
  137. '''
  138. )
  139. def timed_main(self):
  140. after = self.sh.shlex_split(self.env['after'])
  141. before = self.sh.shlex_split(self.env['before'])
  142. no_continue = not self.env['continue']
  143. if self.env['test']:
  144. no_continue = True
  145. before.extend([
  146. '-q', LF,
  147. '-nh', LF,
  148. '-ex', 'set confirm off', LF
  149. ])
  150. elif self.env['verbose']:
  151. # The output of this would affect the tests.
  152. # https://stackoverflow.com/questions/13496389/gdb-remote-protocol-how-to-analyse-packets
  153. # Also be opinionated and set remotetimeout to allow you to step debug the emulator at the same time.
  154. before.extend([
  155. '-ex', 'set debug remote 1', LF,
  156. '-ex', 'set remotetimeout 99999', LF,
  157. ])
  158. if self.env['break_at'] is not None:
  159. break_at = ['-ex', 'break {}'.format(self.env['break_at']), LF]
  160. else:
  161. break_at = []
  162. if self.env['userland']:
  163. linux_full_system = False
  164. if self.env['gdbserver']:
  165. before.extend([
  166. '-ex', 'set sysroot {}'.format(self.env['buildroot_staging_dir']),
  167. ])
  168. elif self.env['baremetal']:
  169. linux_full_system = False
  170. else:
  171. linux_full_system = True
  172. cmd = (
  173. [self.env['gdb_path'], LF] +
  174. before
  175. )
  176. if linux_full_system:
  177. cmd.extend(['-ex', 'add-auto-load-safe-path {}'.format(self.env['linux_build_dir']), LF])
  178. if self.env['sim']:
  179. target = 'sim'
  180. else:
  181. if self.env['gdbserver']:
  182. port = self.env['qemu_hostfwd_generic_port']
  183. elif self.env['kgdb']:
  184. port = self.env['extra_serial_port']
  185. else:
  186. port = self.env['gdb_port']
  187. target = 'remote localhost:{}'.format(port)
  188. cmd.extend([
  189. '-ex', 'file {}'.format(self.env['image_elf']), LF,
  190. '-ex', 'target {}'.format(target), LF,
  191. ])
  192. if not self.env['kgdb']:
  193. cmd.extend(break_at)
  194. if not no_continue:
  195. # ## lx-symbols
  196. #
  197. # ### lx-symbols after continue
  198. #
  199. # lx symbols must be run after continue.
  200. #
  201. # running it immediately after the connect on the bootloader leads to failure,
  202. # likely because kernel structure on which it depends are not yet available.
  203. #
  204. # With this setup, continue runs, and lx-symbols only runs when a break happens,
  205. # either by hitting the breakpoint, or by entering Ctrl + C.
  206. #
  207. # Sure, if the user sets a break on a raw address of the bootloader,
  208. # problems will still arise, but let's think about that some other time.
  209. #
  210. # ### lx-symbols autoload
  211. #
  212. # The lx-symbols commands gets loaded through the file vmlinux-gdb.py
  213. # which gets put on the kernel build root when python debugging scripts are enabled.
  214. cmd.extend(['-ex', 'continue', LF])
  215. if self.env['lxsymbols'] and linux_full_system:
  216. cmd.extend(['-ex', 'lx-symbols {}'.format(self.env['kernel_modules_build_subdir']), LF])
  217. cmd.extend(after)
  218. if self.env['test']:
  219. self.sh.print_cmd(cmd)
  220. if not self.env['dry_run']:
  221. exception = GdbTestcase(
  222. self.env['source_path'],
  223. os.path.splitext(self.env['source_path'])[0] + '.py',
  224. self.sh.strip_newlines(cmd),
  225. verbose=self.env['verbose'],
  226. ).exception
  227. if exception is None:
  228. exit_status = 0
  229. else:
  230. exit_status = 1
  231. self.log_info(thread_pool.ThreadPool.exception_traceback_string(exception))
  232. return exit_status
  233. else:
  234. # I would rather have cwd be out_rootfs_overlay_dir,
  235. # but then lx-symbols cannot fine the vmlinux and fails with:
  236. # vmlinux: No such file or directory.
  237. return self.sh.run_cmd(
  238. cmd,
  239. cmd_file=os.path.join(self.env['run_dir'], 'run-gdb.sh'),
  240. cwd=self.env['linux_build_dir']
  241. )
  242. if __name__ == '__main__':
  243. Main().cli()