aboutsummaryrefslogtreecommitdiff
path: root/scripts/gdb
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@ost.ch>2026-05-04 19:40:01 +0200
committerFelix Morgner <felix.morgner@ost.ch>2026-05-04 19:40:01 +0200
commitd8670d8eeb55bc0ea347cfe4a9a27640fe4e4e7e (patch)
tree58c69126ad8b2147f4a5b68350207b811d00f1e2 /scripts/gdb
parent78e42a1b6e0a857865be1e60f82871ac13c91bb1 (diff)
downloadkernel-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/gdb')
-rw-r--r--scripts/gdb/load.py (renamed from scripts/gdb/pretty_printers.py)6
-rw-r--r--scripts/gdb/teachos/dump_mb2i.py147
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")