├── LICENSE ├── README.md └── efi.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 artem-nefedov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UEFI OVMF symbol load script for GDB 2 | 3 | GDB extension to automate loading of debug symbols for OVMF image. 4 | Also works for custom drivers and applications, as long as symlinks to .efi and 5 | .debug files are present in working directory. 6 | 7 | Sourcing script from gdb adds new command "efi". 8 | Usage can be displayed by printing help page: 9 | ``` 10 | (gdb) help efi 11 | ``` 12 | 13 | Requires OVMF image to built with [EDK II](https://github.com/tianocore/edk2) 14 | 15 | Algorithm is based on the instruction: 16 | 17 | [How to debug OVMF with QEMU using GDB](https://github.com/tianocore/tianocore.github.io/wiki/How-to-debug-OVMF-with-QEMU-using-GDB) 18 | 19 | Steps: 20 | 21 | 1. Enter edk2 git directory. 22 | 2. Run qemu with debug console being redirected to file "debug.log": 23 | ``` 24 | $ qemu-system-i386 -s -bios Build/OvmfIa32/DEBUG_GCC5/FV/OVMF.fd -net none -debugcon file:debug.log -global isa-debugcon.iobase=0x402 25 | ``` 26 | 3. (optional) If debugging a custom app/driver, run or load it in QEMU, so the load address can be found in debug.log. 27 | 4. Run gdb and source "efi.py": 28 | ``` 29 | $ gdb -ex 'source efi.py' 30 | ``` 31 | 5. Execute "efi" or "efi -r": 32 | ``` 33 | (gdb) efi 34 | ... 35 | add symbol table from file "Build/OvmfIa32/DEBUG_GCC5/IA32/Ps2KeyboardDxe.debug" at 36 | .text_addr = 0x6c37220 37 | .data_addr = 0x6c3e480 38 | add symbol table from file "Build/OvmfIa32/DEBUG_GCC5/IA32/VirtioPciDeviceDxe.debug" at 39 | .text_addr = 0x7ca0220 40 | .data_addr = 0x7ca4160 41 | Restoring pagination 42 | (gdb) 43 | ``` 44 | -------------------------------------------------------------------------------- /efi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Load OVMF debug symbols into gdb 3 | # Author: Artem Nefedov 4 | 5 | import gdb 6 | import re 7 | import os 8 | from collections import OrderedDict 9 | 10 | 11 | def clear_symbols(): 12 | gdb.execute('file') 13 | gdb.execute('symbol-file') 14 | 15 | 16 | def remote_connect(): 17 | gdb.execute('target remote :1234') 18 | 19 | 20 | def update_addresses(base_addr, text_addr, data_addr): 21 | try: 22 | print(' Base address ' + base_addr) 23 | i_base_addr = int(base_addr, 16) 24 | except: 25 | print('Failed to locate base address') 26 | return None 27 | 28 | try: 29 | print('.text address ' + text_addr) 30 | print('.data address ' + data_addr) 31 | i_text_addr = int(text_addr, 16) 32 | i_data_addr = int(data_addr, 16) 33 | except: 34 | print("Failed to locate sections' addresses") 35 | return None 36 | 37 | return ((i_base_addr + i_text_addr), (i_base_addr + i_data_addr)) 38 | 39 | 40 | def add_symbol_file(file_name, i_text_addr, i_data_addr): 41 | gdb.execute('add-symbol-file ' + file_name + 42 | ' ' + hex(i_text_addr) + 43 | ' -s .data ' + hex(i_data_addr)) 44 | 45 | 46 | def get_pagination(): 47 | out = gdb.execute('show pagination', to_string=True) 48 | return out.split()[-1].rstrip('.') 49 | 50 | 51 | class Command_efi(gdb.Command): 52 | """ 53 | Load all debug symbols for UEFI OVMF image. Image must be build with EDK2. 54 | Requires debug.log to be located in working directory. 55 | By default, it will try to load all drivers listed in debug.log. 56 | Alternatively, you can explicitly specify a list of drives to load. 57 | 58 | Usage: efi [options] [driver_1 driver_2 ...] 59 | 60 | Options: 61 | -r - connect to remote target after execution 62 | -64 - use X64 architecture (default is IA32) 63 | """ 64 | 65 | LOG_FILE = 'debug.log' 66 | A_PATTERN = r'Loading [^ ]+ at (0x[0-9A-F]{8,}) [^ ]+ ([^ ]+)\.efi' 67 | 68 | def __init__(self): 69 | super(Command_efi, self).__init__("efi", gdb.COMMAND_OBSCURE) 70 | self.arch = 'IA32' 71 | 72 | def find_file(self, name): 73 | # look in working directory first (without subdirectories) 74 | for f in os.listdir('.'): 75 | if f == name and os.path.isfile(f): 76 | return f 77 | # if nothing is found, look in "Build" directory and subdirectories 78 | if not os.path.isdir('Build'): 79 | return None 80 | for root, dirs, files in os.walk('Build'): 81 | if name in files and self.arch in root.split(os.sep): 82 | return os.path.join(root, name) 83 | 84 | def get_addresses(self, file_name): 85 | gdb.execute('file ' + file_name) 86 | ok_arch = False 87 | file_arch = None 88 | 89 | for line in gdb.execute('info files', to_string=True).split('\n'): 90 | if ' is .text' in line: 91 | text_addr = line.split()[0] 92 | elif ' is .data' in line: 93 | data_addr = line.split()[0] 94 | elif ' file type ' in line: 95 | file_arch = line.split()[-1].rstrip('.') 96 | if file_arch == 'pei-i386' and self.arch == 'IA32': 97 | ok_arch = True 98 | elif file_arch == 'pei-x86-64' and self.arch == 'X64': 99 | ok_arch = True 100 | 101 | gdb.execute('file') 102 | 103 | if ok_arch: 104 | return (text_addr, data_addr) 105 | else: 106 | print('Bad file architecture ' + str(file_arch)) 107 | return (None, None) 108 | 109 | def get_drivers(self, drivers): 110 | print('Looking for addresses in ' + self.LOG_FILE) 111 | with open(self.LOG_FILE, 'r', errors='ignore') as f: 112 | for match in re.finditer(self.A_PATTERN, f.read()): 113 | name = match.group(2) 114 | if not drivers or name in drivers or name + '.efi' in drivers: 115 | yield (match.group(1), name + '.efi') 116 | 117 | def invoke(self, arg, from_tty): 118 | self.dont_repeat() 119 | clear_symbols() 120 | 121 | pagination = get_pagination() 122 | 123 | if pagination == 'on': 124 | print('Turning pagination off') 125 | gdb.execute('set pagination off') 126 | 127 | if arg: 128 | drivers = [d for d in arg.split() if not d.startswith('-')] 129 | if drivers: 130 | print('Using pre-defined driver list: ' + str(drivers)) 131 | if '-64' in arg.split(): 132 | self.arch = 'X64' 133 | gdb.execute('set architecture i386:x86-64:intel') 134 | else: 135 | drivers = None 136 | 137 | if not os.path.isdir('Build'): 138 | print('Directory "Build" is missing') 139 | 140 | print('With architecture ' + self.arch) 141 | 142 | files_in_log = OrderedDict() 143 | load_addresses = {} 144 | used_addresses = {} 145 | 146 | # if same file is loaded multiple times in log, use last occurence 147 | for base_addr, file_name in self.get_drivers(drivers): 148 | files_in_log[file_name] = base_addr 149 | 150 | for file_name in files_in_log: 151 | efi_file = self.find_file(file_name) 152 | 153 | if not efi_file: 154 | print('File ' + file_name + ' not found') 155 | continue 156 | 157 | debug_file = efi_file[:-3] + 'debug' 158 | 159 | if not os.path.isfile(debug_file): 160 | print('No debug file for ' + efi_file) 161 | continue 162 | 163 | print('EFI file ' + efi_file) 164 | 165 | if efi_file and debug_file: 166 | text_addr, data_addr = self.get_addresses(efi_file) 167 | 168 | if not text_addr or not data_addr: 169 | continue 170 | 171 | base_addr = files_in_log[file_name] 172 | 173 | prev_used = used_addresses.get(base_addr) 174 | 175 | if prev_used: 176 | print('WARNING: duplicate base address ' + base_addr) 177 | print('(was previously provided for ' + prev_used + ')') 178 | print('Only new file will be loaded') 179 | del load_addresses[prev_used] 180 | else: 181 | used_addresses[base_addr] = debug_file 182 | 183 | load_addresses[debug_file] = ( 184 | update_addresses(base_addr, 185 | text_addr, 186 | data_addr)) 187 | 188 | if load_addresses: 189 | for debug_file in load_addresses: 190 | add_symbol_file(debug_file, *load_addresses[debug_file]) 191 | else: 192 | print('No symbols loaded') 193 | 194 | if pagination == 'on': 195 | print('Restoring pagination') 196 | gdb.execute('set pagination on') 197 | 198 | if arg and '-r' in arg.split(): 199 | remote_connect() 200 | 201 | 202 | Command_efi() 203 | --------------------------------------------------------------------------------