How to keep GDB backtrace fully intact when interrupts happen?
Posted: Fri Nov 29, 2024 1:47 pm
At the moment if a GPF happens inside my kernel I can get most of a backtrace out of GDB but GDB is blind to the most important detail: specifically where the GPF was caused. As far as I know this shows up every time control winds up in an interrupt handler but it really maters for something like a GPF (I don't see how it is an issue for something like a timer interrupt).
For instance, I'm trying to get ACPICA going and it's throwing a GPF internally. Here's the backtrace I get:
Everything is great at frame #6 which is inside my ACPICA glue code. That specific line is a call into AcpiFindRootPointer() which is implemented by ACPICA.
Frame #5 is rather disappointing. Frame #4 is good again: line 34 of idt.asm is the correct place where control is given back to C++ from the assembly interrupt handler.
So my question is: how do I get frame #5 to be useful? I'm not very familiar with how GDB works but it seems like GDB doesn't like execution being interrupted outside of some kind of defined points. Or one thing I am familiar with is screwing up GDB with something in the code. Perhaps something else is going on.
Considering the issue shows up around assembly I've created and I barely know what I'm doing there's a good chance I'm trashing something GDB wants to see. In case that's going on here is my nasm based idt.asm:
Any info is greatly appreciated.
For instance, I'm trying to get ACPICA going and it's throwing a GPF internally. Here's the backtrace I get:
Code: Select all
#0 0x00169a47 in hal::wait () at cpu/x86/interrupt.cpp:15
#1 0x00104abc in libk::halt () at libk/terminate.cpp:18
#2 0x001047a9 in libk::panic (format=0x22a438 "Got an exception: #%u %s; error: %u\n") at libk/error.cpp:23
#3 0x00103502 in cpu::x86::handle_interrupt (info=0x25fef8) at cpu/x86/idt.cpp:167
#4 0xfff00030 in idt_stub_common.execute_handler () at cpu/x86/idt.asm:34
#5 0x0025fef8 in ?? ()
#6 0x00171852 in AcpiOsGetRootPointer () at driver/acpi/acpica.cpp:34
#7 0x001363c2 in AcpiInitializeTables (InitialTableArray=0x0, InitialTableCount=16, AllowResize=0 '\000') at contrib/acpica/dist/source/components/tables/tbxface.c:257
#8 0x00132bc5 in driver::acpi::initialize_tables () at driver/acpi/acpi.cpp:32
#9 0x00102d69 in platform::ibmpc::init (multiboot_magic=732803074, multiboot_info=0x9500) at platform/ibmpc/init.cpp:234
#10 0x00102014 in boot () at platform/ibmpc/crt0.asm:90
Frame #5 is rather disappointing. Frame #4 is good again: line 34 of idt.asm is the correct place where control is given back to C++ from the assembly interrupt handler.
So my question is: how do I get frame #5 to be useful? I'm not very familiar with how GDB works but it seems like GDB doesn't like execution being interrupted outside of some kind of defined points. Or one thing I am familiar with is screwing up GDB with something in the code. Perhaps something else is going on.
Considering the issue shows up around assembly I've created and I barely know what I'm doing there's a good chance I'm trashing something GDB wants to see. In case that's going on here is my nasm based idt.asm:
Code: Select all
%define idt_size 256
extern handle_interrupt
extern kernel_page_directory
idt_stub_common:
; get a backup of the processor state before calling into the kernel
pusha ; push general purpose registers
mov eax, cr3 ; store the page directory in use when the interrupt happened
push eax
push ds
push es
push fs
push gs
mov ax, 0x10 ; Load the Kernel Data Segment descriptor
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov eax, esp ; Push us onto the stack - is this needed?
push eax
; make sure the kernel page directory will be used when executing
; the ISRs
mov eax, [kernel_page_directory]
mov ebx, cr3
cmp eax, ebx
je .execute_handler
mov cr3, eax
.execute_handler:
mov eax, handle_interrupt
call eax ; A special call, preserves the 'eip' register
pop eax
pop gs
pop fs
pop es
pop ds
; use the original page directory if it is different from the kernel page directory
mov eax, [kernel_page_directory]
pop ebx
cmp eax, ebx
je .continue_return
mov cr3, ebx
.continue_return:
popa ; pop general purpose registers
add esp, 8 ; Cleans up the pushed error code and ISR number
iret ; pops 5 things at once: CS, EIP, EFLAGS, SS, and ESP!
%macro idt_err_stub 1
idt_stub_%+%1:
push long %1
jmp idt_stub_common
%endmacro
%macro idt_no_err_stub 1
idt_stub_%+%1:
push long 0
push long %1
jmp idt_stub_common
%endmacro
idt_no_err_stub 0
idt_no_err_stub 1
idt_no_err_stub 2
idt_no_err_stub 3
idt_no_err_stub 4
idt_no_err_stub 5
idt_no_err_stub 6
idt_no_err_stub 7
idt_err_stub 8
idt_no_err_stub 9
idt_err_stub 10
idt_err_stub 11
idt_err_stub 12
idt_err_stub 13
idt_err_stub 14
idt_no_err_stub 15
idt_no_err_stub 16
idt_err_stub 17
idt_no_err_stub 18
idt_no_err_stub 19
idt_no_err_stub 20
idt_no_err_stub 21
idt_no_err_stub 22
idt_no_err_stub 23
idt_no_err_stub 24
idt_no_err_stub 25
idt_no_err_stub 26
idt_no_err_stub 27
idt_no_err_stub 28
idt_no_err_stub 29
idt_err_stub 30
idt_no_err_stub 31
; all the IDT entries after 31 don't have error codes
%assign stub_num 0
%rep idt_size
%if stub_num >= 32
idt_no_err_stub stub_num
%endif
%assign stub_num stub_num+1
%endrep
global idt_stub_table
idt_stub_table:
%assign i 0
%rep idt_size
dd idt_stub_%+i
%assign i i+1
%endrep