Why I can't access to Tss variable declared in assembly from this C code?

Viewed 113

Why I can't access to Tss variable in this C code Qnd How to fix this? and why I get Page Fault exception while I try to write to Tss variable from C code? in protected mode I declare gdt64 and Tss in boot32.S:

.align 16
gdt64:
    .quad 0x0000000000000000 // 0x00 NULL
    .quad 0x0020980000000000 // 0x08 KCODE64
    .quad 0x0020f80000000000
    .quad 0x0000f20000000000
TssDesc:
    .word TssLen-1
    .word 0
    .byte 0
    .byte 0x89
    .byte 0
    .byte 0
    .quad 0

gdt64_end:

.align 16

.global init_gdt64_ptr_baseaddr
.global init_gdt64_ptr
init_gdt64_ptr:
        .word gdt64_end - gdt64 - 1
init_gdt64_ptr_baseaddr:
        .quad gdt64                     # Change to QUAD from LONG


.global Tss
.global TssDesc
Tss:
    .long 0
    .quad 0xffff800000190000
    .fill 88
    .long TssLen

.equ TssLen, . - Tss

boot64.S:

.extern Tss
.extern TssDesc

    ...

    SetTss:
        lea Tss, %rax
        lea TssDesc, %rbx
        mov %ax, 2(%rbx)
        shr $16,%rax
        mov %al,4(%rbx)
        shr $8,%rax
        mov %al,7(%rbx)
        shr $8,%rax
        mov %eax,8(%rbx)
        mov $0x20,%ax
        ltr %ax
        ret

...

.global _start64h
_start64h:

    mov $KERNEL_VMA, %rax
    add %rax, init_gdt64_ptr_baseaddr

    lgdt init_gdt64_ptr(%rax)

    add %rax, %rsp

    call SetTss

But later in long mode and C code in proc.c:

extern struct TSS Tss; 

static void set_tss(struct Process *proc)
{
    Tss.rsp0 = proc->stack + STACK_SIZE;    
}

And I get Page Fault (14)exception on this line when write to Tss.rsp0.

In gdb if I try to read Tss by "p/x Tss" got

this:

Cannot access memory at address 0x20103a

Qemu output: this line of code in exception handler :

printk("[Error %d at ring %d] %d:%x %x", tf->trapno, (tf->cs & 3), tf->errorcode, read_cr2(), tf->rip);

outputs this:

[Error 14 at ring 0] 2:20103EH FFFF800000209AC8H

link to this project on github: https://github.com/JustVic/kernel_multitasking

kernel development is so hard process :(...

1 Answers

The information displayed by Qemu's printk("[Error %d at ring %d] %d:%x %x", tf->trapno, (tf->cs & 3), tf->errorcode, read_cr2(), tf->rip); indicates that the instruction at address FFFF800000209AC8H tried to write to a "not present" page at address 20103EH.

This leads to 3 possible observations:

a) the code is running in the higher half of the virtual address space ("kernel space") and tried to access something in the lower half of the virtual address space ("user space") which doesn't exist. That probably means that the linker is generating addresses for physical memory (used before the kernel set up paging) which don't make sense after paging has been set up.

b) The data was supposed to be at a lower address (0xFFF800000020103E) than the code (at 0xFFFF800000209AC8). This is unusual (normally the .data section is at a higher address than the code/.text section). We can see from your source code that you don't change sections when creating the TSS's data, so its likely (unless it's a "cut & paste omission") that you've actually got data in the code/.text section. This is relatively bad because of how CPUs work (e.g. if you have code in one part of a cache line and data in another part of the same cache line, then writes to the data look like self-modifying code to the CPU, and modern CPUs, which can have hundreds of instructions "in flight" at the same time, really don't like self modifying code).

c) The kernel's code is just above the "non-canonical hole". This is also unusual. The reason is that "64-bit" 80x86 mostly doesn't support 64-bit immediate operands in instructions (there's one special variation of mov, like mov rax, 0x0123456789ABCDEF, that can handle a 64-bit immediate and nothing else). This means that code is more efficient when "addresses known at compile time" can be squeezed into 32 bits, with either with zero extension or sign extension to 64 bits. A (negative) 32-bit number sign extended to 64-bit ends up in the range 0xFFFFFFFF80000000 to 0xFFFFFFFFFFFFFFFF, so this range of addresses is more efficient (for "addresses known at compile time") than the range starting at 0xFFFF800000000000 (which can't be represented in 32 bits).

Looking at "kernel/kernel.ld" in your source code, it looks like you've created special sections to fix the first problem (e.g. a .boottext section for code that runs before paging is set up and a .bootdata section for data that is used before paging is set up). However, the second problem ("data in a code section") implies that you're not using sections properly, so it's reasonable to infer that the first problem is also caused by not using sections properly.

In other words, it's reasonable to assume that (in your SetTss routine) the lea Tss, %rax and lea TssDesc, %rbx are using physical addresses (because these labels were created in the wrong section - namely, in the .boottext section and not the .data section), causing the next instruction (the mov %ax, 2(%rbx)) to write to the wrong address (a physical address, not a virtual address).

However; checking your code (mostly in kernel/boot64.S) shows that somewhere my assumptions are not right. The call SetTss happens while the first 1 GiB of physical memory is still identity mapped into the virtual address space so this should "work by accident" (until later when the CPU attempts to use the TSS that has vanished); and then the physical memory mapping is removed (by the movq $0x0, p4_table and invlpg 0) soon after the call SetTss already finished. That means there's additional "using wrong section/s" bug/s somewhere else.

Related