gdb_helper.py.in 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import os
  2. import re
  3. import subprocess
  4. def prompt_hook (current_prompt):
  5. return "(grub gdb) "
  6. gdb.prompt_hook = prompt_hook
  7. ##### Convenience functions #####
  8. class IsGrubLoaded (gdb.Function):
  9. """Return 1 if GRUB has been loaded in memory, otherwise 0.
  10. The hueristic used is checking if the first 4 bytes of the memory pointed
  11. to by the _start symbol are not 0. This is true for QEMU on the first run
  12. of GRUB. This may not be true on physical hardware, where memory is not
  13. necessarily cleared on soft reset. This may not also be true in QEMU on
  14. soft resets. Also this many not be true when chainloading GRUB.
  15. """
  16. def __init__ (self):
  17. super (IsGrubLoaded, self).__init__ ("is_grub_loaded")
  18. def invoke (self):
  19. return int (gdb.parse_and_eval ("*(int *) _start")) != 0
  20. is_grub_loaded = IsGrubLoaded ()
  21. class IsUserCommand (gdb.Function):
  22. """Set the second argument to true value if first argument is the name
  23. of a user-defined command.
  24. """
  25. def __init__ (self):
  26. super (IsUserCommand, self).__init__ ("is_user_command")
  27. def invoke (self, fmt, *args):
  28. name = fmt.string () % tuple(a.string () for a in args)
  29. for line in gdb.execute ("help user-defined", to_string=True).splitlines ():
  30. line_parts = line.split(' -- ', 1)
  31. if len (line_parts) > 1 and line_parts[0] == name:
  32. return True
  33. return False
  34. is_user_command = IsUserCommand ()
  35. ##### Commands #####
  36. # Loading symbols is complicated by the fact that kernel.exec is an ELF
  37. # ELF binary, but the UEFI runtime is PE32+. All the data sections of
  38. # the ELF binary are concatenated (accounting for ELF section alignment)
  39. # and put into one .data section of the PE32+ runtime image. So given
  40. # the load address of the .data PE32+ section we can determine the
  41. # addresses each ELF data section maps to. The UEFI application is
  42. # loaded into memory just as it is laid out in the file. It is not
  43. # assumed that the binary is available, but it is known that the .text
  44. # section directly precedes the .data section and that .data is EFI
  45. # page aligned. Using this, the .data offset can be found from the .text
  46. # address.
  47. class GrubLoadKernelExecSymbols (gdb.Command):
  48. """Load debugging symbols from kernel.exec given the address of the
  49. .text segment of the UEFI binary in memory."""
  50. PE_SECTION_ALIGN = 12
  51. def __init__ (self):
  52. super (GrubLoadKernelExecSymbols, self).__init__ ("dynamic_load_kernel_exec_symbols",
  53. gdb.COMMAND_USER,
  54. gdb.COMPLETE_EXPRESSION)
  55. def invoke (self, arg, from_tty):
  56. self.dont_repeat ()
  57. args = gdb.string_to_argv (arg)
  58. if len (args) != 1:
  59. raise RuntimeError ("dynamic_load_kernel_exec_symbols expects exactly one argument")
  60. sections = self.parse_objdump_sections ("kernel.exec")
  61. pe_text = args[0]
  62. text_size = [s['size'] for s in sections if s['name'] == '.text'][0]
  63. pe_data_offset = self.alignup (text_size, self.PE_SECTION_ALIGN)
  64. sym_load_cmd_parts = ["add-symbol-file", "kernel.exec", pe_text]
  65. offset = 0
  66. for section in sections:
  67. if 'DATA' in section["flags"] or section["name"] == ".bss":
  68. offset = self.alignup (offset, section["align"])
  69. sym_load_cmd_parts.extend (["-s", section["name"], "(%s+0x%x+0x%x)" % (pe_text, pe_data_offset, offset)])
  70. offset += section["size"]
  71. gdb.execute (' '.join (sym_load_cmd_parts))
  72. @staticmethod
  73. def parse_objdump_sections (filename):
  74. fields = ("idx", "name", "size", "vma", "lma", "fileoff", "align")
  75. re_section = re.compile ("^\s*" + "\s+".join(["(?P<%s>\S+)" % f for f in fields]))
  76. c = subprocess.run (["objdump", "-h", filename], text=True, capture_output=True)
  77. section_lines = c.stdout.splitlines ()[5:]
  78. sections = []
  79. for i in range (len (section_lines) >> 1):
  80. m = re_section.match (section_lines[i * 2])
  81. s = dict (m.groupdict ())
  82. for f in ("size", "vma", "lma", "fileoff"):
  83. s[f] = int (s[f], 16)
  84. s["idx"] = int (s["idx"])
  85. s["align"] = int (s["align"].split ("**", 1)[1])
  86. s["flags"] = section_lines[(i * 2) + 1].strip ().split (", ")
  87. sections.append (s)
  88. return sections
  89. @staticmethod
  90. def alignup (addr, align):
  91. pad = (addr % (1 << align)) and 1 or 0
  92. return ((addr >> align) + pad) << align
  93. dynamic_load_kernel_exec_symbols = GrubLoadKernelExecSymbols ()
  94. class GrubLoadModuleSymbols (gdb.Command):
  95. """Load module symbols at correct locations.
  96. Takes one argument which is a pointer to a grub_dl_t struct."""
  97. def __init__ (self):
  98. super (GrubLoadModuleSymbols, self).__init__ ("load_module",
  99. gdb.COMMAND_USER,
  100. gdb.COMPLETE_EXPRESSION)
  101. def invoke (self, arg, from_tty):
  102. self.dont_repeat ()
  103. args = gdb.string_to_argv (arg)
  104. self.mod = gdb.parse_and_eval (args[0])
  105. sections = self.get_section_offsets ()
  106. section_names = self.get_section_names ()
  107. sym_load_cmd_parts = ["add-symbol-file",
  108. "%s.module" % (self.mod['name'].string (),)]
  109. for idx, addr in sections:
  110. section_name = section_names[idx]
  111. if section_name == ".text":
  112. sym_load_cmd_parts.append (addr)
  113. else:
  114. sym_load_cmd_parts.extend (["-s", section_name, addr])
  115. gdb.execute (' '.join (sym_load_cmd_parts))
  116. if is_user_command.invoke (gdb.Value ("onload_%s"), self.mod['name']):
  117. gdb.execute ("onload_%s (grub_dl_t)%s" % (self.mod['name'].string (),
  118. self.mod.format_string (format='x')))
  119. def get_section_offsets (self):
  120. sections = []
  121. segment = self.mod['segment']
  122. while segment:
  123. sections.append ((int (segment['section']), segment['addr'].format_string (format='x')))
  124. segment = segment['next']
  125. return sections
  126. def get_section_names (self):
  127. re_index = re.compile ("^\s+\[\s*(\d+)\] (\S*)")
  128. names = {}
  129. modfilename = "%s.mod" % (self.mod['name'].string (),)
  130. if not os.path.exists (modfilename):
  131. raise RuntimeError ("%s not found in current directory" % (modfilename,))
  132. c = subprocess.run (["readelf", "-SW", modfilename], text=True, capture_output=True)
  133. for line in c.stdout.splitlines ()[4:]:
  134. m = re_index.match (line)
  135. if not m:
  136. continue
  137. idx, name = m.groups ()
  138. names[int (idx)] = name
  139. return names
  140. grub_load_module = GrubLoadModuleSymbols ()