From 88429e84dac6a5fb590c6e8e39d60536edda8485 Mon Sep 17 00:00:00 2001 From: Konstantin Aladyshev Date: Sun, 18 Jul 2021 19:59:47 +0300 Subject: Add scripts for GDB launch on UEFI shell applications and drivers Signed-off-by: Konstantin Aladyshev --- scripts/efi.py | 202 +++++++++++++++++++++++++++++++++++++++++++++++++++++ scripts/run_gdb.sh | 144 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 346 insertions(+) create mode 100644 scripts/efi.py create mode 100755 scripts/run_gdb.sh diff --git a/scripts/efi.py b/scripts/efi.py new file mode 100644 index 0000000..c904bae --- /dev/null +++ b/scripts/efi.py @@ -0,0 +1,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() diff --git a/scripts/run_gdb.sh b/scripts/run_gdb.sh new file mode 100755 index 0000000..a5ba6af --- /dev/null +++ b/scripts/run_gdb.sh @@ -0,0 +1,144 @@ +#!/bin/bash + +##### Controllable parameters ##### +QEMU_SHARED_FOLDER=~/UEFI_disk +PACKAGE=UefiLessonsPkg +################################### + +function show_help { + echo "Description:" + echo " run_gdb.sh is a script that helps to debug UEFI shell applications and drivers" + echo "" + echo "Usage: run_gdb.sh [-1|-f] -m " + echo " -1 This is a first run of this configuration" + echo " (in this case before main gdb launch there would be another QEMU start that will create 'debug.log' file)" + echo " -f Load all debug symbols" + echo " (this will load all OVMF debug symbols - with this you could step inside OVMF functions)" + echo " -m UEFI module to debug" + echo " -p UEFI package to debug" + echo " (by default it is equal to PACKAGE variable in the head of the script)" + echo " -q QEMU shared directory" + echo " (by default it is equal to QEMU_SHARED_FOLDER variable in the head of the script)" + echo "" + echo "Examples:" + echo " run_gdb.sh -1 -m MyApp - create 'debug.log' file with the necessary address information for the 'MyApp'" + echo " and debug it with gdb" + echo " run_gdb.sh -1 -m MyApp -f - create 'debug.log' file with the necessary address information for the 'MyApp'" + echo " and debug it with gdb (all debug symbols are included, i.e. you can step into OVMF functions)" + echo " run_gdb.sh -m MyApp - debug 'MyApp' with gdb ('debug.log' was created in the last run, no need to remake it again)" +} + + +# A POSIX variable +OPTIND=1 # Reset in case getopts has been used previously in the shell. + +while getopts "h?1fm:q:p:" opt; do + case "$opt" in + h|\?) + show_help + exit 0 + ;; + 1) FIRST_RUN=1 + ;; + f) FULL=1 + ;; + m) TARGET=$OPTARG + ;; + q) QEMU_SHARED_FOLDER=$OPTARG + ;; + p) PACKAGE=$OPTARG + ;; + esac +done + +shift $((OPTIND-1)) + +[ "${1:-}" = "--" ] && shift + + + +if [[ ! -z "${FULL}" ]]; then + DRIVERS='' +else + DRIVERS=${TARGET} +fi + + +function test_file { + FILE_NAME=$1 + if [[ ! -f ${FILE_NAME} ]]; then + echo "Error! There is no file ${FILE_NAME}" + exit 1; + fi +} + +TARGET_INF="${PACKAGE}/${TARGET}/${TARGET}.inf" +TARGET_C="${PACKAGE}/${TARGET}/${TARGET}.c" + +OVMF="Build/OvmfX64/DEBUG_GCC5/FV/OVMF.fd" + +test_file "${TARGET_INF}" +test_file "${TARGET_C}" +test_file "${OVMF}" + +ENTRY_POINT_NAME=$(grep ENTRY_POINT ${TARGET_INF} | cut -f 2 -d "=") +if [ ${ENTRY_POINT_NAME} == "ShellCEntryLib" ]; then + ENTRY_POINT_NAME="ShellAppMain" +fi +ENTRY_POINT_LINE=$(grep -n ${ENTRY_POINT_NAME} ${TARGET_C} | cut -f 1 -d ":") + +MODULE_TYPE=$(grep MODULE_TYPE ${TARGET_INF} | cut -f 2 -d "=") +if [ ${MODULE_TYPE} == "UEFI_DRIVER" ]; then + LAUNCH_COMMAND="load fs0:${TARGET}.efi" +else + LAUNCH_COMMAND="fs0:${TARGET}.efi" +fi + +if [[ ! -z "${FIRST_RUN}" || ! -f debug.log ]]; then + touch debug.log + # If it is a first run, we need to create 'debug.log' file for addresses + TARGET_EFI="Build/${PACKAGE}/DEBUG_GCC5/X64/${TARGET}.efi" + test_file "${TARGET_EFI}" + cp ${TARGET_EFI} ${QEMU_SHARED_FOLDER} + tmux new-session \; \ + send-keys "tail -f debug.log" Enter \; \ + split-window -v \; \ + send-keys "qemu-system-x86_64 \ + -drive if=pflash,format=raw,readonly,file=${OVMF} \ + -drive format=raw,file=fat:rw:${QEMU_SHARED_FOLDER} \ + -net none \ + -nographic \ + -global isa-debugcon.iobase=0x402 \ + -debugcon file:debug.log \ + -s" C-m Enter \; \ + send-keys C-m Enter \; \ + send-keys "${LAUNCH_COMMAND}" Enter \; +fi + + +test_file "${QEMU_SHARED_FOLDER}/${TARGET}.efi" +touch debug_temp.log +tmux new-session \; \ + send-keys "gdb -ex 'source efi.py' -tui" Enter \; \ + split-window -h \; \ + send-keys "tail -f debug_temp.log" Enter \; \ + split-window -v \; \ + send-keys "qemu-system-x86_64 \ + -drive if=pflash,format=raw,readonly,file=${OVMF} \ + -drive format=raw,file=fat:rw:${QEMU_SHARED_FOLDER} \ + -net none \ + -nographic \ + -global isa-debugcon.iobase=0x402 \ + -debugcon file:debug_temp.log \ + -s" C-m Enter \; \ + select-pane -t 0 \; \ + send-keys "efi -64 ${DRIVERS}" Enter \; \ + send-keys "b ${TARGET_C}:${ENTRY_POINT_LINE}" Enter \; \ + send-keys Enter \; \ + send-keys "target remote :1234" Enter \; \ + send-keys "c" Enter \; \ + select-pane -t 2 \; \ + send-keys C-m Enter \; \ + send-keys "${LAUNCH_COMMAND}" Enter \; + + -- cgit v1.2.3-18-g5258