Full code is uploaded at the very bottom.
I've recently been developing a small Kernel in assembly language (NASM).
I chose to do this because I believe assembly is much easier to maintain and read than C when it comes to low-level functionality.
The Kernel is for x86 computers. It currently only has functionality suitable for an old IBM Thinkpad, however I plan on adding more to it in the future.
So far, all of its functions seem to be working correctly, however I have not been able to successfully get interrupts working in user mode.
Right now the problem seems to be caused by either the TSS and/or TSS-descriptor or the routine to enter into user mode.
I apologize for unconventional methods used in code.
Here is what they look like:
The GDT, which has the TSS descriptor in it, looks like this:
Code: Select all
GDT:
dq 0
; System Reserved Segment
SYS_CODE1:
dw 0x001f
dw 0x0
db 0x0
db 0b10011010
db 0b11001111
db 0x0
SYS_DATA1:
dw 0x001f
dw 0x0
db 0x0
db 0b10010010
db 0b11001111
db 0x0
; General Memory Segment
GEN_CODE:
dw 0xdfff
dw 0x0020 ; base
db 0x00 ; base
db 0b11111010
db 0b11001111
db 0x00 ; base
GEN_DATA:
dw 0xdfff
dw 0x0020 ; base
db 0x00 ; base
db 0b11110010
db 0b11001111
db 0x00 ; base
; TSS Descriptor
tss_desc:
dq 0
tss_desc_end:
GDT_END:
gdt_desc:
dw GDT_END - GDT - 1
dd GDT
CSEG_1: equ SYS_CODE1 - GDT
DSEG_1: equ SYS_DATA1 - GDT
CSEG_G: equ GEN_CODE - GDT
DSEG_G: equ GEN_DATA - GDT
The TSS itself looks like this:
Code: Select all
; An Actual TSS
tss:
dd 0 ; previous TSS
dd 0x0010ffff ; ESP0
dd 0x10 ; SS0
;dw 0
times 0x15 dd 0
dw 0
dw tss_end - tss
dd 0
tss_end:
The routine to get into user mode looks like this:
Code: Select all
enter_usermode: ;;
mov ax, (4 * 8) | 3 ; ring 3 data with bottom 2 bits set for ring 3
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax ; SS is handled by iret
; set up the stack frame iret expects
mov eax, esp
push (4 * 8) | 3 ; data selector
push eax ; current esp
pushf ; eflags
push (3 * 8) | 3 ; code selector (ring 3 code with bottom 2 bits set for ring 3)
push 0x200000 ; instruction address to return to
iret
The function to setup the tss:
Code: Select all
set_tss_desc: ; Sets the TSS descriptor
; ecx = base
; edx = limit
; Finally got this thing working
; after hours of researching
; and thinking.
; STEPS OF SETTING THE TSS DESCRIPTOR:
; Set WORD to the first 16 bits of Limit
; Set WORD to the first 16 bits of Base
; Set BYTE to the next byte of Base
; Set BYTE to 0b10010001
; Set BYTE to final hex digit of Limit + 0000
; Set BYTE to final byte of Base
; STEPS OF RETRIEVING PORTIONS OF ADDRESSES:
; For getting LIMIT[0..15]
; Start at BIT 0 of LIMIT.
; shl limit, 0
; GET word limit
; For getting BASE[0..15]
; Start at BIT 0 of BASE.
; shl base, 0
; GET word base
; For getting BASE[16..23]
; Start at BIT 16 of BASE.
; shl base, 16
; GET byte base
; For getting LIMIT[16..19]
; Start at BIT 16 of LIMIT.
; shl limit, 16
; AL = GET byte limit
; and al, 0b11110000
; For getting BASE[24..31]
; Start at BIT 24 of BASE.
; shl base, 24
; GET byte base
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;mov [tss_desc_base], ecx
;mov [tss_desc_limit], edx
;mov ecx, tss_desc_base
;mov edx, tss_desc_limit
; EDX=LIMIT
; ECX=BASE
mov eax, tss_desc ; Load the address of the TSS descriptor
mov ecx, tss ; Load the base address of the TSS
mov edx, tss_end ; Load the limit of the TSS
sub edx, ecx
;;; ecx is base, set base to base
;;; edx is limit, set limit to ecx-end_addr
mov ebx, tss_desc
;---------------------
; LIMIT [15..0];
mov word [ebx], dx
;---------------------
;---------------------
; BASE [15..0] ;
add ebx, 2
mov word [ebx], cx
;---------------------
;---------------------
; BASE [23..16];
add ebx, 2
shr ecx, 16
mov byte [ebx], cl
;---------------------
;---------------------
; ... ;
inc ebx
mov byte [ebx], 0x89 ; 0b10001001
;---------------------
;---------------------
; LIMIT [19..16]
inc ebx
shr edx, 16
mov al, dl
;and al, 0b11110000
mov byte [ebx], al
;---------------------
;---------------------
; BASE [31..24]
inc ebx
shr ecx, 7
mov byte [ebx], cl
; ----- DONE -----
ret
I have tried printing the TSS descriptor to debug, and I have gotten some interesting results.
When I change the TSS descriptor, after jumping into userland I get a general protection fault, however, when I keep it the way it must be, it seems to continue fine, but interrupts do not work.
This is what displayed: 6C00178000890000
Routine to print the TSS Descriptor:
Code: Select all
disp_tss_desc: ; function to display the tss descriptor
mov ebx, tss_desc
mov esi, 0
.loop:
mov al, [ebx]
call byteout
inc ebx
inc esi
cmp esi, 8
jne .loop
ret
The function to set the stack portion (esp0) of the tss:
Code: Select all
tss_set_stack:
pop esi
mov eax, tss
add eax, 4
mov [eax], esp
push esi
ret
The code that sets this whole thing up:
Code: Select all
call incrow ; go onto the next row of text to print
mov eax, setup4
call strout ; strout is the print function
mov ecx, tss
mov eax, tss
mov edx, tss_end
sub edx, eax
call set_tss_desc
call incrow
mov eax, msg_tss_desc ; "TSS Descriptor: "
mov bl, 0x07
call strout_color
call disp_tss_desc
call tss_set_stack ; set esp0
mov ax, 0x28
ltr ax
call loads ; Load a sector from after the Kernel
Full code is here as needed: (included so that people who have downloaded this one can tell the difference)