How to keep GDB backtrace fully intact when interrupts happen?

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
cardboardaardvark
Posts: 20
Joined: Mon Nov 18, 2024 10:06 pm

How to keep GDB backtrace fully intact when interrupts happen?

Post by cardboardaardvark »

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:

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
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:

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
Any info is greatly appreciated.
Octocontrabass
Member
Member
Posts: 5571
Joined: Mon Mar 25, 2013 7:01 pm

Re: How to keep GDB backtrace fully intact when interrupts happen?

Post by Octocontrabass »

cardboardaardvark wrote: Fri Nov 29, 2024 1:47 pmAt 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.
Doesn't your exception handler tell you the address of the faulting instruction?
cardboardaardvark wrote: Fri Nov 29, 2024 1:47 pmSo my question is: how do I get frame #5 to be useful?
I'd guess DWARF CFI would work best, but it looks like you're using NASM, and I don't think NASM supports CFI.

Maybe setting up a stack frame will work? But you might have to get clever about it, since GDB probably expects a stack frame that matches the i386 psABI.
cardboardaardvark wrote: Fri Nov 29, 2024 1:47 pm

Code: Select all

    mov eax, esp   ; Push us onto the stack - is this needed?
    push eax
This is needed. Function arguments are clobbered by function calls, so if you pass the CPU context by value, the CPU context will get clobbered. Passing a pointer to the CPU context means only the pointer gets clobbered.

Also, you can use "push esp" here.
cardboardaardvark wrote: Fri Nov 29, 2024 1:47 pm

Code: Select all

    mov eax, handle_interrupt
    call eax       ; A special call, preserves the 'eip' register
Calls always preserve EIP? Or are you saying a direct call ("call handle_interrupt") wouldn't work here for some reason? Usually problems with direct calls indicate you're linking or loading something wrong.

You should clear the direction flag ("cld") before this call in case an interrupt happens while the direction flag is set.

GCC and Clang are very lenient about x86 stack alignment, but you're supposed to make sure the stack is aligned before this call too.
cardboardaardvark
Posts: 20
Joined: Mon Nov 18, 2024 10:06 pm

Re: How to keep GDB backtrace fully intact when interrupts happen?

Post by cardboardaardvark »

Octocontrabass wrote: Fri Nov 29, 2024 7:56 pm Doesn't your exception handler tell you the address of the faulting instruction?
No. I print out the exception number and it's name, the error codes associated with the fault (or 0 if none are provided for the fault) and for GPF I print out the memory address that caused the exception. That's it so far. Is that the normal way this is handled? Is there a way to get GDB to show me information based on the address of an instruction?

I've got EIP in the struct that is passed to the interrupt handler. GPF will leave EIP at the faulting instruction so execution can resume if the GPF handler did something like mark a page table entry present, right? Printing the faulting instruction then would be a mater of printing out EIP, correct?
I'd guess DWARF CFI would work best, but it looks like you're using NASM, and I don't think NASM supports CFI.

Maybe setting up a stack frame will work? But you might have to get clever about it, since GDB probably expects a stack frame that matches the i386 psABI.
Can you elucidate here?
cardboardaardvark wrote: Fri Nov 29, 2024 1:47 pm

Code: Select all

    mov eax, handle_interrupt
    call eax       ; A special call, preserves the 'eip' register
Calls always preserve EIP? Or are you saying a direct call ("call handle_interrupt") wouldn't work here for some reason? Usually problems with direct calls indicate you're linking or loading something wrong.
That specific comment came from the example I used as the starting point and I didn't remove it since I wasn't sure what it was telling me. call seems to work fine.
You should clear the direction flag ("cld") before this call in case an interrupt happens while the direction flag is set.

GCC and Clang are very lenient about x86 stack alignment, but you're supposed to make sure the stack is aligned before this call too.
Alright, thanks for the info. I'm starting to get a grip on assembly but just barely.
nullplan
Member
Member
Posts: 1790
Joined: Wed Aug 30, 2017 8:24 am

Re: How to keep GDB backtrace fully intact when interrupts happen?

Post by nullplan »

cardboardaardvark wrote: Fri Nov 29, 2024 9:19 pm No. I print out the exception number and it's name, the error codes associated with the fault (or 0 if none are provided for the fault) and for GPF I print out the memory address that caused the exception. That's it so far. Is that the normal way this is handled? Is there a way to get GDB to show me information based on the address of an instruction?
Printing the return address that the CPU pushed onto the exception stack is also a good idea, since that tells you where the fault happened. You can then use debugging tools such as addr2line to find where the exception occurred in code.

Without CFI, GDB will expect that each base pointer points to two words, where the first is the next base pointer the second is the return address. In an interrupt handler, that is not what you get from the CPU, since the error code is in the way. You can, however, conceivably do something like this in the interrupt handler

Code: Select all

; having pushed 13 registers to the stack:
push [esp+14*4] ; push return address
push ebp
mov ebp, esp
; of course, now the address of the CPU register struct is [esp+8], not esp, so:
lea eax, [esp+8]
push eax
After calling the C handler, the two additional words can just be removed from the stack. The return value there doesn't matter, and EBP will be restored with the popa.
Carpe diem!
Octocontrabass
Member
Member
Posts: 5571
Joined: Mon Mar 25, 2013 7:01 pm

Re: How to keep GDB backtrace fully intact when interrupts happen?

Post by Octocontrabass »

Ah, I'm too slow. I wrote all this before nullplan replied.
cardboardaardvark wrote: Fri Nov 29, 2024 9:19 pmfor GPF I print out the memory address that caused the exception.
Are you talking about #GP or #PF here? The address thing only applies to #PF, but usually when someone says GPF they're talking about #GP.
cardboardaardvark wrote: Fri Nov 29, 2024 9:19 pmIs that the normal way this is handled?
Usually you want exception handlers to give you as much information as possible, so you can debug even when GDB isn't available. Attaching GDB to bare metal is a bit more difficult than attaching GDB to a virtual machine.
cardboardaardvark wrote: Fri Nov 29, 2024 9:19 pmIs there a way to get GDB to show me information based on the address of an instruction?
Yes. For example, "info line *0x1234" will tell you which line of code corresponds to the instruction at 0x1234.
cardboardaardvark wrote: Fri Nov 29, 2024 9:19 pmGPF will leave EIP at the faulting instruction so execution can resume if the GPF handler did something like mark a page table entry present, right? Printing the faulting instruction then would be a mater of printing out EIP, correct?
Yes and yes.
cardboardaardvark wrote: Fri Nov 29, 2024 9:19 pmCan you elucidate here?
Stack frames are specified in the i386 psABI. To set them up in ordinary functions, you put "push ebp/mov ebp, esp" at the beginning of every function and "leave" at the end. When you do this, EBP points to where the previous function's EBP was saved on the stack, and right next to that is the return address. This creates a linked list of return addresses, which your debugger can then display to you as a backtrace.

For exceptions, the CPU pushes its error code right after the return address, so if you use the same "push ebp/mov ebp, esp" to set up the stack frame, your debugger will see the error code where it's expecting the return address and won't be able to find the caller. You'd have to come up with some other way of adding the return address to the linked list. (For example, by copying the return address over the garbage ESP value pushed by "pusha" and then setting EBP to point to the EBP value pushed by "pusha".)

You can use DWARF Call Frame Information to tell GDB how to do a backtrace without setting up an i386 psABI stack frame. This is how GDB can do a backtrace on code compiled with "-fomit-frame-pointer".
cardboardaardvark
Posts: 20
Joined: Mon Nov 18, 2024 10:06 pm

Re: How to keep GDB backtrace fully intact when interrupts happen?

Post by cardboardaardvark »

Thank you both for the help. This is coming along pretty well. First, I added printing out EIP from the interrupt info struct to the exception handler and when I feed that to the GDB session using "info line" I get back the appropriate source code line the fault happened at.

My goal of having the GDB backtrace operate as expected is really close. My first test with calling printf() using nullptr as the value for %s in the format has very promising results:

Code: Select all

    #0  0x0013374f in hal::wait () at cpu/x86/interrupt.cpp:15
    #1  0x00104aea in libk::halt () at libk/terminate.cpp:18
    #2  0x001047d7 in libk::panic (format=0x1e9d14 "Got an exception: #%u %s; error: %u\n") at libk/error.cpp:23
    #3  0x001034e2 in cpu::x86::handle_interrupt (info=0x218ed8) at cpu/x86/idt.cpp:167
    #4  0xfff0003a in idt_stub_common.execute_handler () at cpu/x86/idt.asm:43
    #5  0x00218ed8 in ?? ()
    #6  0x001349e5 in _strnlen_s (str=<optimized out>, maxsize=1838) at contrib/mpaland_printf/printf.c:174
    #7  _vsnprintf (out=out@entry=0x134b7f <_out_char>, buffer=buffer@entry=0x218f8f "", maxlen=maxlen@entry=4294967295, format=<optimized out>, va=<optimized out>)
        at contrib/mpaland_printf/printf.c:798
    #8  0x00134c1e in vprintf_ (format=0x1e5468 "Crash: %s\n", va=0x218fe4 "\001") at contrib/mpaland_printf/printf.c:896
    #9  0x0010485d in libk::vprintf (format=0x1e5468 "Crash: %s\n", args=0x218fe4 "\001") at libk/logging.cpp:56
    #10 0x00104874 in libk::printf (format=0x1e5468 "Crash: %s\n") at libk/logging.cpp:47
    #11 0x00103fab in kernel::start () at kernel/start.cpp:47
    #12 0x0010201c in boot () at platform/ibmpc/crt0.asm:97
The dereference did indeed happen at contrib/mpaland_printf/printf.c:174 so that's good. That particular line is a few different operations all merged together though which is making the results a bit deceptive.

I did another experiment with '*reinterpret_cast<uint8_t *>(0xCCCCCCCC) = 5;' to generate a page fault and for that the GDB backtrace is showing the line immediately before it. I suspect this has to do with the address the CPU put on the stack to continue at being correct for continuing but not debugging. Am I barking up the right tree with that? Is correcting that a matter of doing a bit of arithmetic on the value in the stack before doing the work to setup the ebp register? And if so, that will be conditional on the specific exception that happened, right?

I had to make a trivial adjustment to the example provided

Code: Select all

; having pushed 13 registers to the stack:
push [esp+14*4] ; push return address
push ebp
mov ebp, esp
; of course, now the address of the CPU register struct is [esp+8], not esp, so:
lea eax, [esp+8]
push eax
because 13 registers and an error code are being pushed onto the stack. My new idt_stub_common is

Code: Select all

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

    ; Make the base pointer something GDB will understand. This is for
    ; 13 registers and 1 error code having already been pushed to the stack.
    mov eax, [esp + 15 * 4]
    push eax
    push ebp
    mov ebp, esp

    ; make the address for the interrupt info struct point to the
    ; right place in the stack after the GDB adjustment.
    lea eax, [esp + 8]
    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

    ; remove the GDB info
    add esp, 8

    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!
Thanks again for the help.
Octocontrabass
Member
Member
Posts: 5571
Joined: Mon Mar 25, 2013 7:01 pm

Re: How to keep GDB backtrace fully intact when interrupts happen?

Post by Octocontrabass »

cardboardaardvark wrote: Sat Nov 30, 2024 7:23 pmthe GDB backtrace is showing the line immediately before it. I suspect this has to do with the address the CPU put on the stack to continue at being correct for continuing but not debugging. Am I barking up the right tree with that?
Yep. In normal function calls, the return address points to the instruction following the call, but in a backtrace you want the function call itself. For faults, the return address points to the instruction that caused the exception, so GDB ends up reporting the caller as the instruction before the one that faulted.
cardboardaardvark wrote: Sat Nov 30, 2024 7:23 pmIs correcting that a matter of doing a bit of arithmetic on the value in the stack before doing the work to setup the ebp register?
I'm pretty sure you can add 1 to the return address to fix it.
cardboardaardvark wrote: Sat Nov 30, 2024 7:23 pmAnd if so, that will be conditional on the specific exception that happened, right?
Right. Faults and aborts would need the adjustment, but traps (and regular non-exception interrupts) wouldn't. The hardest part is figuring out whether #DB is a fault or a trap, since that depends on what caused it.
cardboardaardvark
Posts: 20
Joined: Mon Nov 18, 2024 10:06 pm

Re: How to keep GDB backtrace fully intact when interrupts happen?

Post by cardboardaardvark »

Octocontrabass wrote: Sat Nov 30, 2024 10:22 pm I'm pretty sure you can add 1 to the return address to fix it.
Looks like this was correct. Adding 1 to the return address has caused GDB to show me the correct line that is causing a #PF.
Right. Faults and aborts would need the adjustment, but traps (and regular non-exception interrupts) wouldn't. The hardest part is figuring out whether #DB is a fault or a trap, since that depends on what caused it.
Not knowing any better, and because I could use the practice writing assembly anyway, I took the first approach that came to mind: look at the Intel reference for which interrupts are faults, traps, and aborts, and make an assembly function to add 1 to the return address for all instances of faults and aborts. The only ambiguous entry I found was interrupt #1 which is mnemonic #DB. Looking it up that is the debug exception and ..... yeah that gets complicated.

The reference I have says the two instances of fault type #DB interrupts are "Instruction fetch breakpoint" and "General detect condition (in conjunction with in-circuit emulation)"

It looks like determining instruction fetch breakpoint can be done by checking the appropriate bits set in the debug status register (DR6). And the appropriate bits in DR6 depend on bits set in the debug control register (DR7) which could be bits L0 to L3 or G0 to G3. If any of the L0 - L3 or G0 - G3 in DR7 are set then look at the same number B bit (B0 to B3) in DR6. If any of the B0 to B3 bits are set true and the corresponding L0 to L3 or G0 to G3 bit are also set to true it is an "Instruction fetch breakpoint" and needs to be considered a fault.

For "General detect condition" I can look at BD in DR6. Do I need to also check for GD being set true in DR7?

Here's the latest iteration of my IDT handling assembly including the conditional return address adjustment:

Code: Select all

%define idt_size 256

extern handle_interrupt
extern kernel_page_directory

; Add 1 to eax for interrupt numbers that correspond to
; fault and abort. Assumes ecx has the interrupt number
; stored in it.
maybe_adjust_return:
    cmp ecx, 0
    je .adjust

    cmp ecx, 4
    jle .done

    cmp ecx, 20
    jge .done

    .adjust:
    add eax, 1

    .done:
    ret

idt_stub_common:
    ; get a backup of the processor state before calling into the kernel
    pusha ; push general purpose registers
    mov eax, cr3
    push eax ; store the page directory in use when the interrupt happened
    push ds
    push es
    push fs
    push gs

    ; make sure the kernel page directory will be used when executing the ISRs
    ; assumes eax still holds the contents of cr3
    cmp eax, [kernel_page_directory]
    je .skip_set_page_directory

    mov eax, [kernel_page_directory]
    mov cr3, eax

    .skip_set_page_directory:

    ; Load the Kernel Data Segment descriptor
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    ; get the return address into a register
    mov eax, [esp + 15 * 4]
    ; get the interrupt number into a register
    mov ecx, [esp + 13 * 4]

    ; TODO Is it better to jmp into maybe_adjust_return and jmp to after it
    ; instead of using call?
    ; Advance the address of the return location for interrupt numbers that
    ; continue at the place the fault happened.
    call maybe_adjust_return

    ; put together a new frame pointer for GDB
    push eax
    push ebp
    mov ebp, esp

    ; TODO Align the stack at 16 bytes for the call to handle_interrupt

    ; make the address for the interrupt info struct point to the
    ; right place in the stack after entries were added for the
    ; frame pointer
    lea eax, [esp + 8]
    push eax

    call handle_interrupt

    ; remove the call frame info and argument to handle_interrupt
    add esp, 12

    pop gs
    pop fs
    pop es
    pop ds

    ; use the original page directory if it is different from the kernel page directory
    pop eax
    cmp eax, [kernel_page_directory]
    je .continue_return

    mov cr3, eax

    .continue_return:
    popa ; restore 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
Octocontrabass
Member
Member
Posts: 5571
Joined: Mon Mar 25, 2013 7:01 pm

Re: How to keep GDB backtrace fully intact when interrupts happen?

Post by Octocontrabass »

cardboardaardvark wrote: Mon Dec 02, 2024 10:17 pmIf any of the L0 - L3 or G0 - G3 in DR7 are set then look at the same number B bit (B0 to B3) in DR6. If any of the B0 to B3 bits are set true and the corresponding L0 to L3 or G0 to G3 bit are also set to true it is an "Instruction fetch breakpoint" and needs to be considered a fault.
That logic will also catch reads and writes. You need to check the corresponding R/W0 - R/W3 bits in DR7 to make sure it's really an instruction fetch.
cardboardaardvark wrote: Mon Dec 02, 2024 10:17 pmFor "General detect condition" I can look at BD in DR6. Do I need to also check for GD being set true in DR7?
You can't check it, it'll always be 0.
cardboardaardvark wrote: Mon Dec 02, 2024 10:17 pm

Code: Select all

    ; TODO Is it better to jmp into maybe_adjust_return and jmp to after it
    ; instead of using call?
Why not put the code here so you don't need to jump or call?
cardboardaardvark
Posts: 20
Joined: Mon Nov 18, 2024 10:06 pm

Re: How to keep GDB backtrace fully intact when interrupts happen?

Post by cardboardaardvark »

Octocontrabass wrote: Mon Dec 02, 2024 11:46 pm Why not put the code here so you don't need to jump or call?
No reason aside from I felt like idt_stub_common is long enough already. Though since assembly operations are so small I suppose it is not unusual to have a single assembly function span several pages or more and avoiding call and even jmp winds up as a performance consideration.

It's not a thing I'm used to thinking about but the CPU does like it best when the instructions are all adjacent as it caches better, doesn't it?

Unrelated question that came up as I refactored idt_stub_common: what's a good way to make sure the stack is aligned at a 16 byte boundary before calling into an external function? I align the stack in my crt0 but that spot starts with a stack aligned on a page and there isn't much going on with the stack before I get to calling into the init and start functions for the kernel. In idt_stub_common I don't know what the stack was like before and things happening before the call to handle_interrupt could undo any static alignment immediately preceding the call.

Is it a normal practice to get the address of the stack pointer before call is used and align based on that?
Octocontrabass
Member
Member
Posts: 5571
Joined: Mon Mar 25, 2013 7:01 pm

Re: How to keep GDB backtrace fully intact when interrupts happen?

Post by Octocontrabass »

cardboardaardvark wrote: Tue Dec 03, 2024 12:53 amIt's not a thing I'm used to thinking about but the CPU does like it best when the instructions are all adjacent as it caches better, doesn't it?
Generally yes.
cardboardaardvark wrote: Tue Dec 03, 2024 12:53 amwhat's a good way to make sure the stack is aligned at a 16 byte boundary before calling into an external function?
If you don't already know the stack alignment, your only real choice is "and esp, ~0xf" to get a known stack alignment. If you're passing arguments to the function, you may need to insert padding before you push the arguments.

You can also relax the stack alignment requirement. For example, "-mpreferred-stack-boundary=2" reduces the alignment requirement to 4 bytes. This works best when combined with "-mgeneral-regs-only", which you might already be using.
cardboardaardvark
Posts: 20
Joined: Mon Nov 18, 2024 10:06 pm

Re: How to keep GDB backtrace fully intact when interrupts happen?

Post by cardboardaardvark »

Octocontrabass wrote: Tue Dec 03, 2024 2:04 pm If you don't already know the stack alignment, your only real choice is "and esp, ~0xf" to get a known stack alignment.
Oh that's cute. Chop off the least significant bits from the stack pointer and store the result back into the stack pointer. Since the x86 stack grows down that advances the stack to an aligned address. But that raises the question: how do you undo the operation?

For example, with an interrupt handler, the CPU pushes some values to the stack then gives a handler control to continue from there. You do the "and esp" to align the stack at your first opportunity but then lost the original value of esp and esp needs to be at the right value before iret is called to get out of the ISR so the CPU can pop the right content as a part of iret. RIght?

So in that case is the thing to do 1) align the stack 2) store the alignment adjustment on the stack 3) do your work, padding for function calls, etc, using the known alignment from step 1, 4) undo all the work on the stack to get back to the adjustment stored on the stack as step 2, 5) undo the alignment adjustment, 6) call iret

Am I making this too complicated?
Octocontrabass
Member
Member
Posts: 5571
Joined: Mon Mar 25, 2013 7:01 pm

Re: How to keep GDB backtrace fully intact when interrupts happen?

Post by Octocontrabass »

cardboardaardvark wrote: Tue Dec 03, 2024 2:54 pmAm I making this too complicated?
Probably. Function calls won't modify EBX, ESI, or EDI, so you can move the original possibly-misaligned stack pointer into one of those registers beforehand and move it back after.

Something like this, perhaps...

Code: Select all

lea ebx, [esp + 8]
and esp, ~0xf
sub esp, 12
push ebx
cld
call handle_interrupt
mov esp, ebx
Post Reply