run-gdb 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. #!/usr/bin/env python3
  2. import os
  3. import signal
  4. import subprocess
  5. import sys
  6. import common
  7. import lkmc.import_path
  8. from shell_helpers import LF
  9. class GdbTestcase:
  10. def __init__(
  11. self,
  12. source_path,
  13. test_script_path,
  14. cmd,
  15. verbose=False
  16. ):
  17. '''
  18. :param verbose: if True, print extra debug information to help understand
  19. why a test is not working
  20. '''
  21. self.prompt = '\(gdb\) '
  22. self.source_path = source_path
  23. import pexpect
  24. self.child = pexpect.spawn(
  25. cmd[0],
  26. cmd[1:],
  27. encoding='utf-8'
  28. )
  29. if verbose:
  30. self.child.logfile = sys.stdout
  31. self.child.setecho(False)
  32. self.child.waitnoecho()
  33. self.child.expect(self.prompt)
  34. test = lkmc.import_path.import_path(test_script_path)
  35. exception = None
  36. try:
  37. test.test(self)
  38. except AssertionError as e:
  39. exception = e
  40. self.child.sendcontrol('d')
  41. self.child.close()
  42. if exception is not None:
  43. raise 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', default='',
  77. help='Pass extra arguments to GDB, to be appended after all other arguments'
  78. )
  79. self.add_argument(
  80. '--before', default='',
  81. help='Pass extra arguments to GDB to be prepended before any of the arguments passed by this script'
  82. )
  83. self.add_argument(
  84. 'break_at', nargs='?',
  85. help='Extra options to append at the end of the emulator command line'
  86. )
  87. self.add_argument(
  88. '-k', '--kgdb', default=False,
  89. )
  90. self.add_argument(
  91. '-C', '--no-continue', default=False,
  92. help="Don't run continue after connecting"
  93. )
  94. self.add_argument(
  95. '-X', '--no-lxsymbols', default=False,
  96. )
  97. self.add_argument(
  98. '--test', default=False,
  99. help='''\
  100. Run an expect test case instead of interactive usage. For baremetal and userland,
  101. the script is a .py file next to the source code.
  102. '''
  103. )
  104. self.add_argument(
  105. '--sim', default=False,
  106. help='''Use the built-in GDB CPU simulator
  107. See: https://github.com/cirosantilli/linux-kernel-module-cheat#gdb-builtin-cpu-simulator
  108. '''
  109. )
  110. self.add_argument(
  111. '-u', '--userland',
  112. )
  113. def timed_main(self):
  114. after = self.sh.shlex_split(self.env['after'])
  115. before = self.sh.shlex_split(self.env['before'])
  116. no_continue = self.env['no_continue']
  117. if self.env['test']:
  118. no_continue = True
  119. before.extend([
  120. '-q', LF,
  121. '-nh', LF,
  122. '-ex', 'set confirm off', LF
  123. ])
  124. elif self.env['verbose']:
  125. # The output of this would affect the tests.
  126. # https://stackoverflow.com/questions/13496389/gdb-remote-protocol-how-to-analyse-packets
  127. # Also be opinionated and set remotetimeout to allow you to step debug the emulator at the same time.
  128. before.extend([
  129. '-ex', 'set debug remote 1', LF,
  130. '-ex', 'set remotetimeout 99999', LF,
  131. ])
  132. if self.env['break_at'] is not None:
  133. break_at = ['-ex', 'break {}'.format(self.env['break_at']), LF]
  134. else:
  135. break_at = []
  136. linux_full_system = (self.env['baremetal'] is None and self.env['userland'] is None)
  137. if self.env['userland']:
  138. image = self.env['image']
  139. elif self.env['baremetal']:
  140. image = self.env['image']
  141. test_script_path = os.path.splitext(self.env['source_path'])[0] + '.py'
  142. else:
  143. image = self.env['vmlinux']
  144. cmd = (
  145. [self.env['gdb_path'], LF] +
  146. before
  147. )
  148. if linux_full_system:
  149. cmd.extend(['-ex', 'add-auto-load-safe-path {}'.format(self.env['linux_build_dir']), LF])
  150. if self.env['sim']:
  151. target = 'sim'
  152. else:
  153. if self.env['kgdb']:
  154. port = self.env['extra_serial_port']
  155. else:
  156. port = self.env['gdb_port']
  157. target = 'remote localhost:{}'.format(port)
  158. cmd.extend([
  159. '-ex', 'file {}'.format(image), LF,
  160. '-ex', 'target {}'.format(target), LF,
  161. ])
  162. if not self.env['kgdb']:
  163. cmd.extend(break_at)
  164. if not no_continue:
  165. # ## lx-symbols
  166. #
  167. # ### lx-symbols after continue
  168. #
  169. # lx symbols must be run after continue.
  170. #
  171. # running it immediately after the connect on the bootloader leads to failure,
  172. # likely because kernel structure on which it depends are not yet available.
  173. #
  174. # With this setup, continue runs, and lx-symbols only runs when a break happens,
  175. # either by hitting the breakpoint, or by entering Ctrl + C.
  176. #
  177. # Sure, if the user sets a break on a raw address of the bootloader,
  178. # problems will still arise, but let's think about that some other time.
  179. #
  180. # ### lx-symbols autoload
  181. #
  182. # The lx-symbols commands gets loaded through the file vmlinux-gdb.py
  183. # which gets put on the kernel build root when python debugging scripts are enabled.
  184. cmd.extend(['-ex', 'continue', LF])
  185. if not self.env['no_lxsymbols'] and linux_full_system:
  186. cmd.extend(['-ex', 'lx-symbols {}'.format(self.env['kernel_modules_build_subdir']), LF])
  187. cmd.extend(after)
  188. if self.env['test']:
  189. self.sh.print_cmd(cmd)
  190. if not self.env['dry_run']:
  191. GdbTestcase(
  192. self.env['source_path'],
  193. test_script_path,
  194. self.sh.strip_newlines(cmd),
  195. verbose=self.env['verbose'],
  196. )
  197. else:
  198. # I would rather have cwd be out_rootfs_overlay_dir,
  199. # but then lx-symbols cannot fine the vmlinux and fails with:
  200. # vmlinux: No such file or directory.
  201. return self.sh.run_cmd(
  202. cmd,
  203. cmd_file=os.path.join(self.env['run_dir'], 'run-gdb.sh'),
  204. cwd=self.env['linux_build_dir']
  205. )
  206. if __name__ == '__main__':
  207. Main().cli()