aboutsummaryrefslogtreecommitdiffstats
path: root/scripts/efi.py
blob: c904bae758926b863ca9916235df3affdb081ad2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/usr/bin/env python3
# Load OVMF debug symbols into gdb
# Author: Artem Nefedov

import gdb
import re
import os
from collections import OrderedDict


def clear_symbols():
    gdb.execute('file')
    gdb.execute('symbol-file')


def remote_connect():
    gdb.execute('target remote :1234')


def update_addresses(base_addr, text_addr, data_addr):
    try:
        print(' Base address ' + base_addr)
        i_base_addr = int(base_addr, 16)
    except:
        print('Failed to locate base address')
        return None

    try:
        print('.text address ' + text_addr)
        print('.data address ' + data_addr)
        i_text_addr = int(text_addr, 16)
        i_data_addr = int(data_addr, 16)
    except:
        print("Failed to locate sections' addresses")
        return None

    return ((i_base_addr + i_text_addr), (i_base_addr + i_data_addr))


def add_symbol_file(file_name, i_text_addr, i_data_addr):
    gdb.execute('add-symbol-file ' + file_name +
                ' ' + hex(i_text_addr) +
                ' -s .data ' + hex(i_data_addr))


def get_pagination():
    out = gdb.execute('show pagination', to_string=True)
    return out.split()[-1].rstrip('.')


class Command_efi(gdb.Command):
    """
    Load all debug symbols for UEFI OVMF image. Image must be build with EDK2.
    Requires debug.log to be located in working directory.
    By default, it will try to load all drivers listed in debug.log.
    Alternatively, you can explicitly specify a list of drives to load.

    Usage: efi [options] [driver_1 driver_2 ...]

    Options:
    -r  - connect to remote target after execution
    -64 - use X64 architecture (default is IA32)
    """

    LOG_FILE = 'debug.log'
    A_PATTERN = r'Loading [^ ]+ at (0x[0-9A-F]{8,}) [^ ]+ ([^ ]+)\.efi'

    def __init__(self):
        super(Command_efi, self).__init__("efi", gdb.COMMAND_OBSCURE)
        self.arch = 'IA32'

    def find_file(self, name):
        # look in working directory first (without subdirectories)
        for f in os.listdir('.'):
            if f == name and os.path.isfile(f):
                return f
        # if nothing is found, look in "Build" directory and subdirectories
        if not os.path.isdir('Build'):
            return None
        for root, dirs, files in os.walk('Build'):
            if name in files and self.arch in root.split(os.sep):
                return os.path.join(root, name)

    def get_addresses(self, file_name):
        gdb.execute('file ' + file_name)
        ok_arch = False
        file_arch = None

        for line in gdb.execute('info files', to_string=True).split('\n'):
            if ' is .text' in line:
                text_addr = line.split()[0]
            elif ' is .data' in line:
                data_addr = line.split()[0]
            elif ' file type ' in line:
                file_arch = line.split()[-1].rstrip('.')
                if file_arch == 'pei-i386' and self.arch == 'IA32':
                    ok_arch = True
                elif file_arch == 'pei-x86-64' and self.arch == 'X64':
                    ok_arch = True

        gdb.execute('file')

        if ok_arch:
            return (text_addr, data_addr)
        else:
            print('Bad file architecture ' + str(file_arch))
            return (None, None)

    def get_drivers(self, drivers):
        print('Looking for addresses in ' + self.LOG_FILE)
        with open(self.LOG_FILE, 'r', errors='ignore') as f:
            for match in re.finditer(self.A_PATTERN, f.read()):
                name = match.group(2)
                if not drivers or name in drivers or name + '.efi' in drivers:
                    yield (match.group(1), name + '.efi')

    def invoke(self, arg, from_tty):
        self.dont_repeat()
        clear_symbols()

        pagination = get_pagination()

        if pagination == 'on':
            print('Turning pagination off')
            gdb.execute('set pagination off')

        if arg:
            drivers = [d for d in arg.split() if not d.startswith('-')]
            if drivers:
                print('Using pre-defined driver list: ' + str(drivers))
            if '-64' in arg.split():
                self.arch = 'X64'
                gdb.execute('set architecture i386:x86-64:intel')
        else:
            drivers = None

        if not os.path.isdir('Build'):
            print('Directory "Build" is missing')

        print('With architecture ' + self.arch)

        files_in_log = OrderedDict()
        load_addresses = {}
        used_addresses = {}

        # if same file is loaded multiple times in log, use last occurence
        for base_addr, file_name in self.get_drivers(drivers):
            files_in_log[file_name] = base_addr

        for file_name in files_in_log:
            efi_file = self.find_file(file_name)

            if not efi_file:
                print('File ' + file_name + ' not found')
                continue

            debug_file = efi_file[:-3] + 'debug'

            if not os.path.isfile(debug_file):
                print('No debug file for ' + efi_file)
                continue

            print('EFI file ' + efi_file)

            if efi_file and debug_file:
                text_addr, data_addr = self.get_addresses(efi_file)

                if not text_addr or not data_addr:
                    continue

                base_addr = files_in_log[file_name]

                prev_used = used_addresses.get(base_addr)

                if prev_used:
                    print('WARNING: duplicate base address ' + base_addr)
                    print('(was previously provided for ' + prev_used + ')')
                    print('Only new file will be loaded')
                    del load_addresses[prev_used]
                else:
                    used_addresses[base_addr] = debug_file

                load_addresses[debug_file] = (
                    update_addresses(base_addr,
                                     text_addr,
                                     data_addr))

        if load_addresses:
            for debug_file in load_addresses:
                add_symbol_file(debug_file, *load_addresses[debug_file])
        else:
            print('No symbols loaded')

        if pagination == 'on':
            print('Restoring pagination')
            gdb.execute('set pagination on')

        if arg and '-r' in arg.split():
            remote_connect()


Command_efi()