SKC wrote:
As Octocontrabass said, you can't access CS directly. Use the following code to change it (it's in AT&T syntax because I don't know how the operand sizes work in Intel's syntax for these instructions):
Code: Select all
pushq 0x8 # Push the code selector. Pushes 64 bits (although the selector is 16 bits)
pushq cs_reload_lbl # Push the address of the label
retfq # Return Far - This will set RIP to cs_reload_lbl and CS to 0x8
cs_reload_lbl:
# The rest of the code here
Alright I changed the code and now load CS properly, thank you.
SKC wrote:
You can't handle exceptions and IRQs with the exact same code. Each interrupt serves a different purpose.
In my OS, I have one IRQ handler and one exception handler, both written in C (actually C++ but I don't use any C++ features except classes). These two handlers are called by small assembly handlers, one for each interrupt. The assembly handlers push all the registers and call the handlers, giving them the interrupt's number and an error code (if the exception has one). The C handlers call a thread that handles the exception/IRQ. After the C handler returns, the assembly handler pops all the registers and returns (iret).
For now, I think your exception handler could just print some exception info and then hang ('jmp $').
So I understand what you're saying, but I just don't know how to implement it. Right now I use the loop that you recommended in a previous answer.
SKC wrote:
Code: Select all
mov rbx,idt
mov rax,isr_wrapper.handler ; Assuming you use the same handler for all the interrupts. It's fine for now, but you'll need to change it.
mov rcx,48 ; rcx should be the number of entries in your IDT
.idt_loop: ; This loop sets the offset for each IDT entry
mov word [rbx],ax ; offset1
shr rax,16
mov word [rbx+6],ax ; offset2
shr rax,16
mov dword [rbx+8],eax ; offset3
add rbx,16 ; Go to the next entry
dec rcx
jne .idt_loop ; 'dec' will set ZF to 1 if the result is 0
So this should make all 48 entries in my IDT? If this is correct, what am I missing? I apologize ahead of time for my lack of knowledge.
Here is the whole main64.asm file.
Code: Select all
global long_mode_start
extern kernel_main
extern interrupt_handler
section .text
bits 64
long_mode_start:
; push 0x8 into cs
push 0x8
push cs_reload
retfq
cs_reload:
call load_idt
lidt [idt.pointer]
sti
call kernel_main
mov rax, 60
mov rdi, 2
syscall
load_idt:
mov rbx, idt
mov rax, isr_wrapper.handler
mov rcx, 48
.loop:
mov word [rbx], ax
shr rax, 16
mov word [rbx + 6], ax
shr rax, 16
mov dword [rbx + 8], eax
add rbx, 16
dec rcx
jne .loop
ret
idt:
.irq:
dw 0x0000
dw 0x8000
db 0x00
db 10101110b
dw 0x0000
dd 0x00000000
dd 0x00000000
.pointer:
dw $ - idt - 1
dq idt
isr_wrapper:
.handler:
push rax
push rcx
push rdx
push rbx
push rbp
push rsi
push rdi
push r8
push r9
push r10
push r11
push r12
push r13
push r14
push r15
cld ; C code following the System V ABI requires DF to be clear on function entry
call interrupt_handler
pop r15
pop r14
pop r13
pop r12
pop r11
pop r10
pop r9
pop r8
pop rdi
pop rsi
pop rbp
pop rbx
pop rdx
pop rcx
pop rax
iretq
SKC wrote:
Also, about your 'long_mode_start' function, I don't understand it. Where and when do you load the GDT? And why do you still set the segment registers to 0?
Besides that, you don't need to push rsp in your handler. The interrupt does it by itself. And since you'll probably push all the registers a few more times in your code, I'd recommend you create a 'pusha' macro.
My GDT is loaded in main.asm when the OS is still in protected mode. I then make a long jump in order to reach long_mode_start after switching to long mode. The segment registers were set to 0 because that is how it was explained to me in a tutorial, I have no other reason. I've taken out that part as of now. Here is the GDT code from the other file.
Code: Select all
section .rodata
gdt:
dq 0 ; null descriptor
.code: equ $ - gdt
dq 0x00209a0000000000 ; 64-bit code descriptor (exec/read)
dq 0x0009200000000000 ; 64-bit data descriptor (read/write)
align 4
dw 0
.pointer:
dw $ - gdt - 1
dq gdt
And the GDT is loaded like this in the start function:
Code: Select all
lgdt [gtd.pointer]
jmp gdt.code:long_mode_start
Octocontrabass wrote:
The Windows builds of QEMU include a GUI with a convenient menu to switch between them, but the QEMU documentation says you can use keyboard shortcuts to switch to the monitor too.
Thank you, I finally got the prompt.