| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Copyright 2025, Heinrich Schuchardt <xypron.glpk@gmx.de> | |
| * | |
| * dbginfodump.efi prints out the content of the EFI_DEBUG_IMAGE_INFO_TABLE. | |
| */ | |
| #include <efi_api.h> | |
| /** | |
| * BUFFER_SIZE - size of the command line input buffer | |
| */ | |
| #define BUFFER_SIZE 64 | |
| static struct efi_simple_text_output_protocol *cerr; | |
| static struct efi_simple_text_output_protocol *cout; | |
| static struct efi_simple_text_input_protocol *cin; | |
| static efi_handle_t handle; | |
| static struct efi_system_table *systable; | |
| static struct efi_boot_services *bs; | |
| static efi_guid_t guid_device_path_to_text_protocol = | |
| EFI_DEVICE_PATH_TO_TEXT_PROTOCOL_GUID; | |
| static struct efi_device_path_to_text_protocol *device_path_to_text; | |
| static const efi_guid_t dbg_info_guid = | |
| EFI_DEBUG_IMAGE_INFO_TABLE_GUID; | |
| /* EFI_DEBUG_IMAGE_INFO_NORMAL */ | |
| struct dbg_info { | |
| u32 type; | |
| struct efi_loaded_image *info; | |
| efi_handle_t handle; | |
| }; | |
| /* FI_DEBUG_IMAGE_INFO_TABLE_HEADER */ | |
| struct dbg_info_header { | |
| u32 status; | |
| u32 size; | |
| struct dbg_info **info; | |
| }; | |
| /** | |
| * print() - print string | |
| * | |
| * @string: text | |
| */ | |
| static void print(u16 *string) | |
| { | |
| cout->output_string(cout, string); | |
| } | |
| /** | |
| * error() - print error string | |
| * | |
| * @string: error text | |
| */ | |
| static void error(u16 *string) | |
| { | |
| cout->set_attribute(cout, EFI_LIGHTRED | EFI_BACKGROUND_BLACK); | |
| print(string); | |
| cout->set_attribute(cout, EFI_LIGHTBLUE | EFI_BACKGROUND_BLACK); | |
| } | |
| /** | |
| * printu() - print unsigned | |
| * | |
| * @val: value to print | |
| */ | |
| static void printu(u32 val) | |
| { | |
| u16 str[19] = u"0x"; | |
| u16 *ptr = &str[2]; | |
| u16 ch; | |
| for (ssize_t i = 8 * sizeof(u32) - 4; i >= 0; i -= 4) { | |
| ch = (val >> i & 0xf) + '0'; | |
| if (ch > '9') | |
| ch += 'a' - '9' - 1; | |
| *ptr++ = ch; | |
| } | |
| *ptr = 0; | |
| print(str); | |
| } | |
| /** | |
| * printp() - print pointer | |
| * | |
| * @p: pointer | |
| */ | |
| static void printp(void *p) | |
| { | |
| u16 str[19] = u"0x"; | |
| u16 *ptr = &str[2]; | |
| u16 ch; | |
| for (ssize_t i = 8 * sizeof(void *) - 4; i >= 0; i -= 4) { | |
| ch = ((uintptr_t)p >> i & 0xf) + '0'; | |
| if (ch > '9') | |
| ch += 'a' - '9' - 1; | |
| *ptr++ = ch; | |
| } | |
| *ptr = 0; | |
| print(str); | |
| } | |
| /** | |
| * efi_input() - read string from console | |
| * | |
| * @buffer: input buffer | |
| * @buffer_size: buffer size | |
| * Return: status code | |
| */ | |
| static efi_status_t efi_input(u16 *buffer, efi_uintn_t buffer_size) | |
| { | |
| struct efi_input_key key = {0}; | |
| efi_uintn_t index; | |
| efi_uintn_t pos = 0; | |
| u16 outbuf[2] = u" "; | |
| efi_status_t ret; | |
| /* Drain the console input */ | |
| ret = cin->reset(cin, true); | |
| *buffer = 0; | |
| for (;;) { | |
| ret = bs->wait_for_event(1, &cin->wait_for_key, &index); | |
| if (ret != EFI_SUCCESS) | |
| continue; | |
| ret = cin->read_key_stroke(cin, &key); | |
| if (ret != EFI_SUCCESS) | |
| continue; | |
| switch (key.scan_code) { | |
| case 0x17: /* Escape */ | |
| print(u"\r\nAborted\r\n"); | |
| return EFI_ABORTED; | |
| default: | |
| break; | |
| } | |
| switch (key.unicode_char) { | |
| case 0x08: /* Backspace */ | |
| if (pos) { | |
| buffer[pos--] = 0; | |
| print(u"\b \b"); | |
| } | |
| break; | |
| case 0x0a: /* Linefeed */ | |
| case 0x0d: /* Carriage return */ | |
| print(u"\r\n"); | |
| return EFI_SUCCESS; | |
| default: | |
| break; | |
| } | |
| /* Ignore surrogate codes */ | |
| if (key.unicode_char >= 0xD800 && key.unicode_char <= 0xDBFF) | |
| continue; | |
| if (key.unicode_char >= 0x20 && | |
| pos < buffer_size - 1) { | |
| *outbuf = key.unicode_char; | |
| buffer[pos++] = key.unicode_char; | |
| buffer[pos] = 0; | |
| print(outbuf); | |
| } | |
| } | |
| } | |
| /** | |
| * skip_whitespace() - skip over leading whitespace | |
| * | |
| * @pos: UTF-16 string | |
| * Return: pointer to first non-whitespace | |
| */ | |
| static u16 *skip_whitespace(u16 *pos) | |
| { | |
| for (; *pos && *pos <= 0x20; ++pos) | |
| ; | |
| return pos; | |
| } | |
| /** | |
| * starts_with() - check if @string starts with @keyword | |
| * | |
| * @string: string to search for keyword | |
| * @keyword: keyword to be searched | |
| * Return: true fi @string starts with the keyword | |
| */ | |
| static bool starts_with(u16 *string, u16 *keyword) | |
| { | |
| for (; *keyword; ++string, ++keyword) { | |
| if (*string != *keyword) | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * do_help() - print help | |
| */ | |
| static void do_help(void) | |
| { | |
| error(u"dump - print debug info table\r\n"); | |
| error(u"exit - exit the shell\r\n"); | |
| } | |
| /** | |
| * get_dbg_info_table() - get debug info table | |
| * | |
| * Return: debug info table or NULL | |
| */ | |
| static void *get_dbg_info(void) | |
| { | |
| void *dbg = NULL; | |
| efi_uintn_t i; | |
| for (i = 0; i < systable->nr_tables; ++i) { | |
| if (!memcmp(&systable->tables[i].guid, &dbg_info_guid, | |
| sizeof(efi_guid_t))) { | |
| dbg = systable->tables[i].table; | |
| break; | |
| } | |
| } | |
| return dbg; | |
| } | |
| /** | |
| * print_info() - print loaded image protocol | |
| */ | |
| static void print_info(struct efi_loaded_image *info) | |
| { | |
| print(u" Address: ["); | |
| printp(info->image_base); | |
| print(u", "); | |
| printp(info->image_base + info->image_size - 1); | |
| print(u"]\r\n"); | |
| if (device_path_to_text && info->file_path) { | |
| u16 *string; | |
| string = device_path_to_text->convert_device_path_to_text( | |
| info->file_path, true, false); | |
| if (!string) { | |
| error(u"ConvertDevicePathToText failed"); | |
| } else { | |
| print(u" File: "); | |
| print(string); | |
| } | |
| print(u"\r\n"); | |
| } | |
| } | |
| /** | |
| * do_dump() - print debug info table | |
| */ | |
| static efi_status_t do_dump(void) | |
| { | |
| struct dbg_info_header *dbg; | |
| u32 count; | |
| dbg = get_dbg_info(); | |
| if (!dbg) { | |
| error(u"Debug info table not found\r\n"); | |
| return EFI_NOT_FOUND; | |
| } | |
| if (dbg->status & 0x01) { | |
| error(u"Update in progress\r\n"); | |
| return EFI_LOAD_ERROR; | |
| } | |
| if (dbg->status & 0x02) | |
| print(u"Modified\r\n"); | |
| print(u"Number of entries: "); | |
| printu(dbg->size); | |
| print(u"\r\n"); | |
| count = dbg->size; | |
| for (u32 i = 0; count; ++i) { | |
| struct dbg_info *info = dbg->info[i]; | |
| /* | |
| * The EDK II implementation decreases the size field and | |
| * writes a NULL value when deleting an entry which is not | |
| * backed by the UEFI specification. | |
| */ | |
| if (!info) { | |
| print(u"Deleted entry\r\n"); | |
| continue; | |
| } | |
| --count; | |
| print(u"Info type "); | |
| printu(info->type); | |
| print(u"\r\n"); | |
| if (info->type != 1) | |
| continue; | |
| print_info(info->info); | |
| print(u" Handle: "); | |
| printp(info->handle); | |
| print(u"\r\n"); | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| * efi_main() - entry point of the EFI application. | |
| * | |
| * @handle: handle of the loaded image | |
| * @systab: system table | |
| * Return: status code | |
| */ | |
| efi_status_t EFIAPI efi_main(efi_handle_t image_handle, | |
| struct efi_system_table *systab) | |
| { | |
| efi_status_t ret; | |
| handle = image_handle; | |
| systable = systab; | |
| cerr = systable->std_err; | |
| cout = systable->con_out; | |
| cin = systable->con_in; | |
| bs = systable->boottime; | |
| cout->set_attribute(cout, EFI_LIGHTBLUE | EFI_BACKGROUND_BLACK); | |
| cout->clear_screen(cout); | |
| cout->set_attribute(cout, EFI_WHITE | EFI_BACKGROUND_BLACK); | |
| print(u"Debug Info Table Dump\r\n=====================\r\n\r\n"); | |
| cout->set_attribute(cout, EFI_LIGHTBLUE | EFI_BACKGROUND_BLACK); | |
| ret = bs->locate_protocol(&guid_device_path_to_text_protocol, | |
| NULL, (void **)&device_path_to_text); | |
| if (ret != EFI_SUCCESS) { | |
| error(u"No device path to text protocol\r\n"); | |
| device_path_to_text = NULL; | |
| } | |
| for (;;) { | |
| u16 command[BUFFER_SIZE]; | |
| u16 *pos; | |
| efi_uintn_t ret; | |
| print(u"=> "); | |
| ret = efi_input(command, sizeof(command)); | |
| if (ret == EFI_ABORTED) | |
| break; | |
| pos = skip_whitespace(command); | |
| if (starts_with(pos, u"exit")) | |
| break; | |
| else if (starts_with(pos, u"dump")) | |
| do_dump(); | |
| else | |
| do_help(); | |
| } | |
| cout->set_attribute(cout, EFI_LIGHTGRAY | EFI_BACKGROUND_BLACK); | |
| cout->clear_screen(cout); | |
| return EFI_SUCCESS; | |
| } |