modlegend
modlegend

Reputation: 11

If I set a register, completely unrelated memory location gets changed

I am creating a 32 bit operating system. I have enabled paging and it works fine, until the kernel_main function returns to the assembly code (which calls the kernel_main function). The assembly code is like this:

call kernel_main
jmp $

But when bochs runs the jmp $ instruction, it triple faults with a GPF. Apparently, the code section in the GDT got corrupted.

Bochs output:

interrupt(): not accessible or not code segment cs=0x0008
interrupt(): not accessible or not code segment cs=0x0008
interrupt(): not accessible or not code segment cs=0x0008
CPU is in protected mode (active)
CS.mode = 32 bit
SS.mode = 32 bit
EFER   = 0x00000000
| EAX=00000001  EBX=00007d76  ECX=00000014  EDX=00000000
| ESP=0008fff8  EBP=00090000  ESI=000e0000  EDI=0000ffac
| IOPL=0 id vip vif ac vm RF nt of df IF tf sf zf af PF cf
| SEG sltr(index|ti|rpl)     base    limit G D
|  CS:0008( 0001| 0|  0) 00000000 ffffffff 1 1
|  DS:00010 0002| 0|  0) 00000000 ffffffff 1 1
|  SS:00010 0002| 0|  0) 00000000 ffffffff 1 1
|  ES:00010 0002| 0|  0) 00000000 ffffffff 1 1
|  FS:00010 0002| 0|  0) 00000000 ffffffff 1 1
|  GS:00010 0002| 0|  0) 00000000 ffffffff 1 1
| EIP=00001021 (00001021)
| CR0=0xe0000011 CR2=0x00000000
| CR3=0x00006000 CR4=0x00000000
0008:0000000000001021 (unk. ctxt): jmp .-2  (0x00001021) ; ebfe
exception(): 3rd (13) exception with no resolution, shutdown status is 00h, resetting
bx_pc_system_c::Reset(HARDWARE) called
cpu hardware reset

I put a memory watchpoint using the bochs debugger, and found out that the code segment in the GDT gets corrupted at least once. Using the bochs debugger, I found the corrupting instruction:

mov ax, DATA_SEG

DATA_SEG being the corresponding entry in the GDT. It's value is 0x10. I want to understand, how does setting a register change a value somewhere else completely?

Thanks for the help!

Some more info I thought would help. The instruction before mov ax, DATA_SEG is jmp CODE_SEG:init_pm. init_pm is the label right before mov ax, DATA_SEG. The full code of that file is below:

[bits 16]
switch_to_pm:
    cli ; 1. disable interrupts
    lgdt [gdt_descriptor] ; 2. load the GDT descriptor
    mov eax, cr0
    or eax, 0x1 ; 3. set 32-bit mode bit in cr0
    mov cr0, eax
    jmp CODE_SEG:init_pm ; 4. far jump by using a different segment

[bits 32]
init_pm: ; we are now using 32-bit instructions
    mov ax, DATA_SEG ; 5. update the segment registers
    mov ds, ax
    mov ss, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    mov ebp, 0x90000 ; 6. update the stack right at the top of the free space
    mov esp, ebp

    call BEGIN_PM ; 7. Call a well-known label with useful code

EDIT:

GDT definition:

gdt_start: ; don't remove the labels, they're needed to compute sizes and jumps 0x7CCA
    ; the GDT starts with a null 8-byte
    dd 0x0 ; 4 byte
    dd 0x0 ; 4 byte

; GDT for code segment. base = 0x00000000, length = 0xfffff
; for flags, refer to os-dev.pdf document, page 36
gdt_code: 
    dw 0xffff    ; segment length, bits 0-15
    dw 0x0       ; segment base, bits 0-15
    db 0x0       ; segment base, bits 16-23
    db 10011010b ; flags (8 bits)
    db 11001111b ; flags (4 bits) + segment length, bits 16-19
    db 0x0       ; segment base, bits 24-31

; GDT for data segment. base and length identical to code segment
; some flags changed, again, refer to os-dev.pdf
gdt_data:
    dw 0xffff
    dw 0x0
    db 0x0
    db 10010010b
    db 11001111b
    db 0x0

gdt_end: ; 0x7CE2

; GDT descriptor
gdt_descriptor:
    dw gdt_end - gdt_start - 1 ; size (16 bit), always one less of its true size
    dd gdt_start ; address (32 bit)

; define some constants for later use
CODE_SEG equ gdt_code - gdt_start
DATA_SEG equ gdt_data - gdt_start

EDIT 2:

The link to the github is here: https://github.com/aharsi1298/devOS

Upvotes: 1

Views: 101

Answers (0)

Related Questions