diff options
| author | Felix Morgner <felix.morgner@ost.ch> | 2026-05-04 19:40:01 +0200 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@ost.ch> | 2026-05-04 19:40:01 +0200 |
| commit | d8670d8eeb55bc0ea347cfe4a9a27640fe4e4e7e (patch) | |
| tree | 58c69126ad8b2147f4a5b68350207b811d00f1e2 /scripts | |
| parent | 78e42a1b6e0a857865be1e60f82871ac13c91bb1 (diff) | |
| download | kernel-d8670d8eeb55bc0ea347cfe4a9a27640fe4e4e7e.tar.xz kernel-d8670d8eeb55bc0ea347cfe4a9a27640fe4e4e7e.zip | |
debug: add multiboot2 information dump tool
This patch introduces a new GDB tool `dump_mb2i` that dump the
multiboot2 information provided by the bootloader. This tool can be
invoked in the GDB console. For example in vscode:
-exec dump_mb2i ((kapi::boot::information)bootstrap_information).mbi
The tool expects the address of the loader provided MB2 information as
its only argument.
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/gdb/load.py (renamed from scripts/gdb/pretty_printers.py) | 6 | ||||
| -rw-r--r-- | scripts/gdb/teachos/dump_mb2i.py | 147 |
2 files changed, 153 insertions, 0 deletions
diff --git a/scripts/gdb/pretty_printers.py b/scripts/gdb/load.py index c209328..355f6b9 100644 --- a/scripts/gdb/pretty_printers.py +++ b/scripts/gdb/load.py @@ -10,6 +10,8 @@ repo_root = os.path.dirname(os.path.dirname(script_root)) if script_root not in sys.path: sys.path.insert(0, script_root) +from teachos.dump_mb2i import DumpMB2I + components = { "kstd": "libs/kstd/gdb", "kapi": "kapi/gdb", @@ -39,3 +41,7 @@ for component, path in components.items(): gdb.write(f"Warning: GDB extension init not found: '{init_file}'\n") gdb.write("Info: Loaded TeachOS pretty printers.\n") + +DumpMB2I() + +gdb.write("Info: Loaded TeachOS tools.\n")
\ No newline at end of file diff --git a/scripts/gdb/teachos/dump_mb2i.py b/scripts/gdb/teachos/dump_mb2i.py new file mode 100644 index 0000000..ff15fde --- /dev/null +++ b/scripts/gdb/teachos/dump_mb2i.py @@ -0,0 +1,147 @@ +import gdb +import struct + + +TAG_NAMES = { + 0: "End of Tags", + 1: "Boot Command Line", + 2: "Boot Loader Name", + 3: "Boot Module", + 4: "Basic Memory Information", + 5: "BIOS Boot Device", + 6: "Memory Map", + 7: "VBE Info", + 8: "Framebuffer Info", + 9: "ELF Symbols", + 10: "APM Table", + 14: "ACPI old RSDP (1.0)", + 15: "ACPI new RSDP (2.0+)", + 21: "Image Load Base Physical Address", +} + +MEMORY_TYPES = { + 1: "Available", + 2: "Reserved", + 3: "ACPI Reclaim", + 4: "Non-volatile storage", + 5: "Bad Memory", +} + +INDENT = f"{' '*11}" + + +class DumpMB2I(gdb.Command): + """ + Dump the information provided by the Multiboot2 loader. + Usage: dump_mb2i <address_expression> + Example: dump_mb2i $rbx + dump_mb2i 0x8000 + """ + + def __init__(self): + super(DumpMB2I, self).__init__("dump_mb2i", gdb.COMMAND_USER) + + def invoke(self, arg, from_tty): + if not arg: + gdb.write( + "Error: please provide the address of the Multiboot2 information structure.\n" + ) + return + + try: + address = int(gdb.parse_and_eval(arg)) + except gdb.error as e: + gdb.write(f"Error: Invalid address expression: {e}\n") + return + + inferior = gdb.selected_inferior() + + try: + header_data = inferior.read_memory(address, 8).tobytes() + total_size, reserved = struct.unpack("<II", header_data) + except gdb.MemoryError: + gdb.write(f"Error: Cannot read memory at {address:#018x}\n") + return + + if total_size < 8 or total_size > 1024 * 1024: + gdb.write("Warning: suspicious total size ({total_size} bytes). Aborting.") + return + + gdb.write(f"+{'-'*70}+\n") + gdb.write(f"| Multiboot2 Boot Information Payload{' ':>34}|\n") + gdb.write(f"+{'-'*70}+\n") + gdb.write(f"Base Address: {address:#018x}\nTotal Size: {total_size} Bytes\n\n") + + offset = 8 + + while offset < total_size: + tag_address = address + offset + + try: + tag_header = inferior.read_memory(tag_address, 8).tobytes() + tag_type, tag_size = struct.unpack("<II", tag_header) + except gdb.MemoryError: + gdb.write( + f"Error: Cannot read memory at {tag_address:#018x} (offset {offset})\n" + ) + break + + self._print_tag(inferior, tag_address, tag_type, tag_size) + + if tag_type == 0 and tag_size == 0: + break + + offset += (tag_size + 7) & ~7 + + def _format_size(self, size): + for unit in ["Bytes", "KiB", "MiB", "GiB", "TiB", "PiB"]: + if size < 1024.0: + if unit == "Bytes": + return f"{int(size)} {unit}" + return f"{size:.2f} {unit}" + size /= 1024.0 + return f"{size:.2f} PiB" + + def _print_tag(self, inferior, tag_address, tag_type, tag_size): + name = TAG_NAMES.get(tag_type, f"Unknown Tag") + gdb.write(f"[Tag {tag_type:#04x}] {name} (size: {self._format_size(tag_size)})\n") + + if tag_size <= 8 and tag_type != 0: + return + + try: + if tag_type in (1, 2): + data = inferior.read_memory(tag_address + 8, tag_size - 8).tobytes() + text = data.split(b"\x00")[0].decode("ascii", "replace") + gdb.write(f'{INDENT}"{text}"\n') + elif tag_type == 3: + data = inferior.read_memory(tag_address + 8, tag_size - 8).tobytes() + start, end = struct.unpack_from("<II", data) + string = data[8:].split(b"\x00")[0].decode("ascii", "replace") + gdb.write( + f"{INDENT}start: {start:#010x} | end: {end:#010x} | size: {self._format_size(end - start)}\n" + ) + if string: + gdb.write(f'{INDENT}string: "{string}"\n') + elif tag_type == 6: + data = inferior.read_memory(tag_address + 8, tag_size - 8).tobytes() + entry_size, entry_version = struct.unpack_from("<II", data) + if entry_size < 24: + return + num_entries = (tag_size - 16) // entry_size + for i in range(num_entries): + entry_offset = 8 + i * entry_size + base, length, memory_type = struct.unpack_from( + "<QQI", data, entry_offset + ) + type_string = MEMORY_TYPES.get(memory_type, "Unknown") + gdb.write( + f"{INDENT}[{i:02d}] {base:#018x} - {base + length:#018x} | {self._format_size(length)} | type: {type_string}\n" + ) + elif tag_type == 21: + data = inferior.read_memory(tag_address + 8, tag_size - 8).tobytes() + base = struct.unpack("<I", data)[0] + gdb.write(f"{INDENT}address: {base:#010x}\n") + + except Exception as e: + gdb.write(f"{INDENT}<failed to decode tag: {e}>\n") |
