我正在用 C++ 构建一个简单的操作系统来了解该主题。我正在使用 Limine 引导加载程序(Limine 6.20231210.0,如引导加载程序版本请求所述)。我在主内核函数中要做的第一件事如下:
extern "C" void _start(void) {
// Ensure the bootloader actually understands our base revision (see spec) and fetch first fb
if(LIMINE_BASE_REVISION_SUPPORTED == false) {
hcf();
}
struct limine_framebuffer* framebuffer = framebuffer_request.response->framebuffers[0];
if(framebuffer_request.response == NULL || framebuffer_request.response->framebuffer_count < 1) {
hcf();
}
KERNEL::VGA::VGA_Driver vgaDriver(framebuffer);
KERNEL::TERMINAL::kernel_terminal terminal(&terfont7x14, &vgaDriver);
terminal.printf("%s %s", bootloader_info_request.response->name, bootloader_info_request.response->version);
gdt_init();
hcf();
}
加载GDT。如您所见,调用了 gdt_init() 函数(代码如下)。 vgaDriver 和终端对象是简单的对象,允许我写入屏幕(它们使用限制帧缓冲区)。另外.h内的gdt_init()声明被标记为ar extern“C”,所以它不能是关于函数名称的,编译器会给我一个错误
bits 64
align 0x10
gdt:
null_descriptor:
dw 0x0000
dw 0x0000
db 0x00
db 00000000b
db 00000000b
db 0x00
kernel_code_64:
dw 0x0000
dw 0x0000
db 0x00
db 10011010b
db 00100000b
db 0x00
kernel_data_64:
dw 0x0000
dw 0x0000
db 0x00
db 10010010b
db 00100000b
db 0x00
user_code_64:
dw 0x0000
dw 0x0000
db 0x00
db 11111010b
db 00100000b
db 0x00
user_data_64:
dw 0x0000
dw 0x0000
db 0x00
db 11110010b
db 00100000b
db 0x00
gdt_end:
gdt_ptr:
dw gdt_end - gdt - 1
dq gdt
CODE_SEG equ kernel_code_64 - gdt
DATA_SEG equ kernel_data_64 - gdt
global gdt_init
gdt_init:
lgdt [gdt_ptr]
mov rax, rsp
push DATA_SEG
push rax
pushfq
push CODE_SEG
push flush
iretq
flush:
mov ax, DATA_SEG
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
ret
我什至尝试了不同版本的 gdt_load(),如下所示:
lgdt [rdi] ; i was passing the parameters by function here
push 0x8 ; the code segment offset
lea rax, [test] ; i declare the test label later in the code, after crash point
push rax
iretq
; fails after iretq
但是代码总是在 iretq 指令处失败。我尝试使用 gdb 进行调试,但据我所知,寄存器似乎一切正常。即使在 C++ 实现中,gdt 条目始终与第一个代码示例中显示的条目类似,因此不可能是这样(除非条目本身就是问题)。 我尝试构建操作系统的平台是 x86_64,这是我的 linker.ld 文件:
/* Tell the linker that we want an x86_64 ELF64 output file */
OUTPUT_FORMAT(elf64-x86-64)
OUTPUT_ARCH(i386:x86-64)
/* We want the symbol _start to be our entry point */
ENTRY(_start)
/* Define the program headers we want so the bootloader gives us the right */
/* MMU permissions */
PHDRS
{
text PT_LOAD FLAGS((1 << 0) | (1 << 2)) ; /* Execute + Read */
rodata PT_LOAD FLAGS((1 << 2)) ; /* Read only */
data PT_LOAD FLAGS((1 << 1) | (1 << 2)) ; /* Write + Read */
dynamic PT_DYNAMIC FLAGS((1 << 1) | (1 << 2)) ; /* Dynamic PHDR for relocations */
}
SECTIONS
{
/* We wanna be placed in the topmost 2GiB of the address space, for optimisations */
/* and because that is what the Limine spec mandates. */
/* Any address in this region will do, but often 0xffffffff80000000 is chosen as */
/* that is the beginning of the region. */
. = 0xffffffff80000000;
.text : {
*(.text .text.*)
} :text
/* Move to the next memory page for .rodata */
. += CONSTANT(MAXPAGESIZE);
.rodata : {
*(.rodata .rodata.*)
} :rodata
/* Move to the next memory page for .data */
. += CONSTANT(MAXPAGESIZE);
.data : {
*(.data .data.*)
} :data
/* Dynamic section for relocations, both in its own PHDR and inside data PHDR */
.dynamic : {
*(.dynamic)
} :data :dynamic
/* NOTE: .bss needs to be the last thing mapped to :data, otherwise lots of */
/* unnecessary zeros will be written to the binary. */
/* If you need, for example, .init_array and .fini_array, those should be placed */
/* above this. */
.bss : {
*(.bss .bss.*)
*(COMMON)
} :data
/* Discard .note.* and .eh_frame since they may cause issues on some hosts. */
/DISCARD/ : {
*(.eh_frame)
*(.note .note.*)
*(.comment)
}
}
如你所见,我的代码主要基于 osdev 上的限制基础
这也是我的构建命令(替换 .c 和 .o 名称)
g++ -o build/memory.o -c -std=c++17 -Wall -Wno-missing-field-initializers -Wextra -Wno-switch-bool -fno-rtti -fno-stack-protector -fno-stack-check -fno-lto -m64 -march=x86-64 -g src/memory.cpp
nasm -f elf64 -o build/x86.o src/x86.asm
ld -o build/kernel -m elf_x86_64 -static --no-dynamic-linker -T src/linker.ld build/kernel.o build/memory.o build/ports.o build/terminal.o build/vga.o build/gdt.o build/x86.o
代码似乎能够在寄存器中加载GDTD的地址,因为我曾经尝试删除段更新并仅加载gdt并且它有效,并且我可以使用sgdt获取地址。返回的地址可以转换为有效的 GDT 结构(与汇编示例中的实现相同,但在 C++ 中)
这是 gdt_init() 函数的反汇编(直到 iretq)
│ 0xffffffff80001608 <kernel_code_64> add BYTE PTR [rax],al │
│ 0xffffffff8000160a <kernel_code_64+2> add BYTE PTR [rax],al │
│ 0xffffffff8000160c <kernel_code_64+4> add BYTE PTR [rdx+0x20],bl │
│ 0xffffffff80001612 <kernel_data_64+2> add BYTE PTR [rax],al │
│ 0xffffffff80001614 <kernel_data_64+4> add BYTE PTR [rdx+0x20],dl │
│ 0xffffffff8000161a <user_code_64+2> add BYTE PTR [rax],al │
│ 0xffffffff8000161c <user_code_64+4> add dl,bh │
│ 0xffffffff8000161e <user_code_64+6> and BYTE PTR [rax],al │
│ 0xffffffff80001620 <user_data_64> add BYTE PTR [rax],al │
│ 0xffffffff80001622 <user_data_64+2> add BYTE PTR [rax],al │
│ 0xffffffff80001624 <user_data_64+4> add dl,dh │
│ 0xffffffff80001626 <user_data_64+6> and BYTE PTR [rax],al │
│ 0xffffffff80001628 <gdt_ptr> (bad) │
│ 0xffffffff80001629 <gdt_ptr+1> add BYTE PTR [rax],al │
│ 0xffffffff8000162b <gdt_ptr+3> (bad) │
│ 0xffffffff8000162c <gdt_ptr+4> add BYTE PTR [rax-0x1],al │
│ > 0xffffffff80001632 <gdt_init> lgdt ds:0xffffffff80001628 │
│ 0xffffffff8000163a <gdt_init+8> mov rax,rsp │
│ 0xffffffff8000163d <gdt_init+11> push 0x10 │
│ 0xffffffff8000163f <gdt_init+13> push rax │
│ 0xffffffff80001640 <gdt_init+14> pushf │
│ 0xffffffff80001641 <gdt_init+15> push 0x8 │
│ 0xffffffff80001643 <gdt_init+17> push 0xffffffff8000164a │
│ 0xffffffff80001648 <gdt_init+22> iretq
这些是 iretq 指令之前的寄存器状态:
rax 0xffff800007e9ff48 -140737355579576
rbx 0x0 0
rcx 0xffff8000fd000000 -140733243719680
rdx 0x0 0
rsi 0x0 0
rdi 0xffff800007e9ffb0 -140737355579472
rbp 0xffff800007e9fff0 0xffff800007e9fff0
rsp 0xffff800007e9ff28 0xffff800007e9ff28
r8 0x0 0
r9 0x0 0
r10 0x84 132
r11 0xd 13
r12 0x0 0
r13 0x0 0
r15 0x0 0
rip 0xffffffff80001643 0xffffffff80001643 <gdt_init+17>
eflags 0x46 [ IOPL=0 ZF PF ]
cs 0x28 40
ss 0x30 48
ds 0x30 48
es 0x30 48
fs 0x30 48
gs 0x30 48
fs_base 0x0 0
gs_base 0x0 0
k_gs_base 0x0 0
cr0 0x80010011 [ PG WP ET PE ]
cr3 0x7e8f000 [ PDBR=32399 PCID=0 ]
cr4 0x20 [ PAE ]
cr8 0x0 0
efer 0xd00 [ NXE LMA LME ]
mxcsr 0x1f80 [ IM DM ZM OM UM PM ]
请注意,正如您所看到的,段寄存器已经设置为某个合理的值,这只是因为 Limine 加载了带有一些条目的默认 gdt。不过我想加载自己的。