链接器错误设置使用内联汇编使用 LGDT 指令加载 GDT 寄存器
Linker error setting loading GDT register with LGDT instruction using Inline assembly
我正在编译我的内核原型原型(听起来很奇怪,但它真的没关系)并且在安装中我需要 link 将 ASM 文件转换为用 gcc 编译的 C 文件以获取可用作内核的可执行文件。
问题是,在实现从实模式到保护模式的交换后,我在 linking kernel.c 和 loader.asm 脚本时收到此错误:
代码:
kernel.c:(.text+0x1e1): undefined reference to `gdtr'
我将解释安装的所有过程,并将代码放在下面。
安装步骤:
1:编译汇编:
代码:
nasm -f elf32 loader.asm -o kasm.o
2:编译.c:
代码:
gcc -m32 -ffreestanding -c kernel.c -o kc.o
3:Link 两者:
代码:
ld -m elf_i386 -T linker.ld -o kernel kasm.o kc.o
完整的错误输出是:
代码:
kc.o: In function `k_enter_protected_mode':
kernel.c:(.text+0x1e1): undefined reference to `gdtr'
代码如下:
代码:
/*
*
* kernel.c - version 0.0.1
* This script is under the license of the distributed package, this license
* can be found in the package itself
* Script coded by Cristian Simón for the CKA Proyect
* ----
* License: GNU GPL v3
* Coder: Cristian Simón
* Proyect: CKA
*
*/
/* Output defines */
#define BLACK_BGROUND 0X07 /* black background */
#define WHITE_TXT 0x07 /* light gray on black text */
#define GREEN_TXT 0x02 /* light green on black text */
#define RED_TXT 0x04 /* light red on black text*/
#define CYAN_TXT 0x03 /*light cyan on black text */
#include <stddef.h>
#include <stdint.h>
#include <cpuid.h>
void k_clear_screen();
void k_sleep_3sec();
unsigned int k_printf(char *message, unsigned int line, float color);
void k_malloc(size_t sz);
void k_free(void *mem);
/* k_clear_screen : to clear the entire text screen */
void k_clear_screen()
{
char *vidmem = (char *) 0xC00B8000;
unsigned int i=0;
while(i < (80*25*2))
{
vidmem[i]=' ';
i++;
vidmem[i]=BLACK_BGROUND;
i++;
};
}
/* k_printf : the message and the line # */
unsigned int k_printf(char *message, unsigned int line, float color)
{
char *vidmem = (char *) 0xC00B8000;
unsigned int i=0;
i=(line*80*2);
while(*message!=0)
{
if(*message=='\n') /* check for a new line */
{
line++;
i=(line*80*2);
*message++;
} else {
vidmem[i]=*message;
*message++;
i++;
vidmem[i]=color;
i++;
};
};
return(1);
}
/*
* k_sleep_3sec : to make a simple delay of aprox 3 sec, since is a nasty sleep,
* duration will vary
* from system to system
*/
void k_sleep_3sec()
{
int c = 1, d = 1;
for ( c = 1 ; c <= 20000 ; c++ )
for ( d = 1 ; d <= 20000 ; d++ )
{}
}
/*
* Malloc and free functions for this kernel
* Maybe change in the future, sure
*/
static unsigned char our_memory[1024 * 1024]; /* reserve 1 MB for malloc */
static size_t next_index = 0;
int k_malloc_err;
void k_malloc(size_t sz)
{
void *mem;
if(sizeof our_memory - next_index < sz){
return NULL;
k_malloc_err = 1;
}
mem = &our_memory[next_index];
next_index += sz;
return mem;
}
void k_free(void *mem)
{
/* we cheat, and don't free anything. */
}
/* Schreduler */
/*---*/
/*
* Our schreduler is a RTC (Run to Completion)
* In the future we will add more schredulers or change the type
* but for now this is what we got
*/
int proc_number_count = 0;
void k_schreduler(char *proc_name, unsigned int proc_prior)
{
proc_number_count = proc_number_count + 1;
int proc_number = proc_number_count;
}
void k_enter_protected_mode()
{
__asm__ volatile ("cli;"
"lgdt (gdtr);"
"mov %eax, cr0;"
"or %al, 1;"
"mov cr0, %eax;"
"jmp 0x8,PModeMain;"
"PModeMain:");
}
/*main function*/
void k_main()
{
k_clear_screen();
k_printf(" Wellcome to", 0, WHITE_TXT);
k_printf(" CKA!", 1, GREEN_TXT);
k_printf("==============>", 2, WHITE_TXT);
k_printf(" CKA stands for C Kernel with Assembly", 3, WHITE_TXT);
k_printf(" Version 0.0.1, => based in the job of Debashis Barman", 4, WHITE_TXT);
k_printf(" Contact => assemblyislaw@gmail.com / blueshell@mail2tor.com", 5, WHITE_TXT);
k_printf(" or in the github repository page", 6, WHITE_TXT);
k_sleep_3sec();
k_clear_screen();
/* here start the magic */
k_printf(" !===> Starting Checkup <===!", 0, WHITE_TXT);
k_printf(" =-=-=-=-=-=-=-=-=-=-=-=-=-=-", 1, WHITE_TXT);
k_printf("[KernelInfo] Woah! No Kernel Panic for now! Well, lets fix that...", 2, CYAN_TXT);
k_printf("[Proc1] Checking for k_malloc() and k_free() kernel functions", 3, WHITE_TXT);
k_malloc(15);
if (k_malloc_err == 1){
k_printf("[F-ERROR] Unable to use k_malloc, do you have enough memory?", 4, RED_TXT);
while(1){
int error_stayer = 1;
}
} else{
k_printf("[Proc1] k_malloc and k_free found, resuming boot...", 4, GREEN_TXT);
}
k_enter_protected_mode();
k_printf("[KernelInfo] Switched to protected mode successfully", 5, CYAN_TXT);
}
这是kernel.c
代码:
ENTRY(loader)
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
/* The kernel will live at 3GB + 1MB in the virtual
address space, which will be mapped to 1MB in the
physical address space. */
. = 0xC0100000;
.text : AT(ADDR(.text) - 0xC0000000) {
*(.text)
*(.rodata*)
}
.data ALIGN (0x1000) : AT(ADDR(.data) - 0xC0000000) {
*(.data)
}
.bss : AT(ADDR(.bss) - 0xC0000000) {
_sbss = .;
*(COMMON)
*(.bss)
_ebss = .;
}
}
这是 linker.ld
代码:
global _loader ; Make entry point visible to linker.
extern k_main ; _main is defined elsewhere
; setting up the Multiboot header - see GRUB docs for details
MODULEALIGN equ 1<<0 ; align loaded modules on page boundaries
MEMINFO equ 1<<1 ; provide memory map
FLAGS equ MODULEALIGN | MEMINFO ; this is the Multiboot 'flag' field
MAGIC equ 0x1BADB002 ; 'magic number' lets bootloader find the header
CHECKSUM equ -(MAGIC + FLAGS) ; checksum required
; This is the virtual base address of kernel space. It must be used to convert virtual
; addresses into physical addresses until paging is enabled. Note that this is not
; the virtual address where the kernel image itself is loaded -- just the amount that must
; be subtracted from a virtual address to get a physical address.
KERNEL_VIRTUAL_BASE equ 0xC0000000 ; 3GB
KERNEL_PAGE_NUMBER equ (KERNEL_VIRTUAL_BASE >> 22) ; Page directory index of kernel's 4MB PTE.
section .data
align 0x1000
BootPageDirectory:
; This page directory entry identity-maps the first 4MB of the 32-bit physical address space.
; All bits are clear except the following:
; bit 7: PS The kernel page is 4MB.
; bit 1: RW The kernel page is read/write.
; bit 0: P The kernel page is present.
; This entry must be here -- otherwise the kernel will crash immediately after paging is
; enabled because it can't fetch the next instruction! It's ok to unmap this page later.
dd 0x00000083
times (KERNEL_PAGE_NUMBER - 1) dd 0 ; Pages before kernel space.
; This page directory entry defines a 4MB page containing the kernel.
dd 0x00000083
times (1024 - KERNEL_PAGE_NUMBER - 1) dd 0 ; Pages after the kernel image.
section .text
align 4
MultiBootHeader:
dd MAGIC
dd FLAGS
dd CHECKSUM
; reserve initial kernel stack space -- that's 16k.
STACKSIZE equ 0x4000
; setting up entry point for linker
loader equ (_loader - 0xC0000000)
global loader
_loader:
; NOTE: Until paging is set up, the code must be position-independent and use physical
; addresses, not virtual ones!
mov ecx, (BootPageDirectory - KERNEL_VIRTUAL_BASE)
mov cr3, ecx ; Load Page Directory Base Register.
mov ecx, cr4
or ecx, 0x00000010 ; Set PSE bit in CR4 to enable 4MB pages.
mov cr4, ecx
mov ecx, cr0
or ecx, 0x80000000 ; Set PG bit in CR0 to enable paging.
mov cr0, ecx
; Start fetching instructions in kernel space.
; Since eip at this point holds the physical address of this command (approximately 0x00100000)
; we need to do a long jump to the correct virtual address of StartInHigherHalf which is
; approximately 0xC0100000.
lea ecx, [StartInHigherHalf]
jmp ecx ; NOTE: Must be absolute jump!
StartInHigherHalf:
; Unmap the identity-mapped first 4MB of physical address space. It should not be needed
; anymore.
mov dword [BootPageDirectory], 0
invlpg [0]
; NOTE: From now on, paging should be enabled. The first 4MB of physical address space is
; mapped starting at KERNEL_VIRTUAL_BASE. Everything is linked to this address, so no more
; position-independent code or funny business with virtual-to-physical address translation
; should be necessary. We now have a higher-half kernel.
mov esp, stack+STACKSIZE ; set up the stack
push eax ; pass Multiboot magic number
; pass Multiboot info structure -- WARNING: This is a physical address and may not be
; in the first 4MB!
push ebx
call k_main ; call kernel proper
hlt ; halt machine should kernel return
section .bss
align 32
stack:
resb STACKSIZE ; reserve 16k stack on a uint64_t boundary
这是loader.asm
我试图通过将 ASM 块转换为高级 ASM 块并将 gdtr 解析为参数来解决这个问题,但我不明白最后一种方法
我该如何解决错误?
你的错误:
kc.o: In function `k_enter_protected_mode':
kernel.c:(.text+0x1e1): undefined reference to `gdtr'
由于这行汇编代码而生成:
"lgdt (gdtr);"
gdtr
是一个内存操作数,表示可以找到 GDT 记录的内存地址的标签。您没有使用该名称定义的结构。这会导致未定义的引用。
您需要创建 GDT 记录,其中包含 GDT table 的大小和长度。该记录将由 LGDT 指令加载到 GDT 寄存器中。您还没有创建 GDT table。 gdtr
应该是一个 6 字节结构,由 GDT 的长度减去 1(存储在 16 位字中)和一个 32 位线性地址组成,其中 GDTtable可以找到
与其在 C 中做你想做的事,我建议在 call k_main
之前但在设置分页之后在你的汇编代码中做这件事。
在 C 代码中完全删除 k_enter_protected_mode
函数。然后在程序集文件 loader.asm
中放置此代码以在 StartInHigherHalf
代码的开头加载新的 GDT。所以它看起来像:
StartInHigherHalf:
; Set our own GDT, can't rely GDT register being valid after bootloader
; transfers control to our entry point
lgdt [gdtr] ; Load GDT Register with GDT record
mov eax, DATA_SEG
mov ds, eax ; Reload all the data descriptors with Data selector (2nd argument)
mov es, eax
mov gs, eax
mov fs, eax
mov ss, eax
jmp CODE_SEG:.setcs
; Do the FAR JMP to next instruction to set CS with Code selector, and
; set the EIP (instruction pointer) to offset of setcs
.setcs:
唯一剩下的就是定义 GDT table。一个简单的具有必需的 NULL 描述符和平面 32 位代码和数据描述符的文件可以放置在您的 .data
部分,方法是将其更改为:
section .data
align 0x1000
BootPageDirectory:
; This page directory entry identity-maps the first 4MB of the 32-bit physical address space.
; All bits are clear except the following:
; bit 7: PS The kernel page is 4MB.
; bit 1: RW The kernel page is read/write.
; bit 0: P The kernel page is present.
; This entry must be here -- otherwise the kernel will crash immediately after paging is
; enabled because it can't fetch the next instruction! It's ok to unmap this page later.
dd 0x00000083
times (KERNEL_PAGE_NUMBER - 1) dd 0 ; Pages before kernel space.
; This page directory entry defines a 4MB page containing the kernel.
dd 0x00000083
times (1024 - KERNEL_PAGE_NUMBER - 1) dd 0 ; Pages after the kernel image.
; 32-bit GDT to replace one created by multiboot loader
; Per the multiboot specification we Can't rely on GDTR
; being valid so we need our own if we ever intend to
; reload any of the segment registers (this may be an
; issue with protected mode interrupts).
align 8
gdt_start:
dd 0 ; null descriptor
dd 0
gdt32_code:
dw 0FFFFh ; limit low
dw 0 ; base low
db 0 ; base middle
db 10011010b ; access
db 11001111b ; 32-bit size, 4kb granularity, limit 0xfffff pages
db 0 ; base high
gdt32_data:
dw 0FFFFh ; limit low (Same as code)
dw 0 ; base low
db 0 ; base middle
db 10010010b ; access
db 11001111b ; 32-bit size, 4kb granularity, limit 0xfffff pages
db 0 ; base high
end_of_gdt:
gdtr:
dw end_of_gdt - gdt_start - 1
; limit (Size of GDT - 1)
dd gdt_start ; base of GDT
CODE_SEG equ gdt32_code - gdt_start
DATA_SEG equ gdt32_data - gdt_start
我们现在已经添加了所需的 GDT 结构并创建了一个名为 gdtr
的记录,可以使用 LGDT 指令加载该记录。
因为您正在使用 OSDev as a resource, I recommend looking at the GDT tutorial 获取有关创建 GDT 的信息。英特尔手册也是极好的信息来源。
其他观察结果
您的 loader.asm
设置了一个 Multiboot header,所以很可能您正在使用 Multiboot compliant bootloader。当您使用符合多重引导的引导加载程序时,您的 CPU 将被置于 32 位保护模式 之前 它开始 运行 您的代码从 _loader
开始。您的问题表明您认为自己处于实模式,但实际上已经处于保护模式。使用 Mulitboot 加载程序,无需将 CR0 位 0 设置为值 1。它保证已经为 1(已设置)。在我上面的代码中,我在设置 GDT.
后删除了它
我正在编译我的内核原型原型(听起来很奇怪,但它真的没关系)并且在安装中我需要 link 将 ASM 文件转换为用 gcc 编译的 C 文件以获取可用作内核的可执行文件。 问题是,在实现从实模式到保护模式的交换后,我在 linking kernel.c 和 loader.asm 脚本时收到此错误:
代码:
kernel.c:(.text+0x1e1): undefined reference to `gdtr'
我将解释安装的所有过程,并将代码放在下面。 安装步骤: 1:编译汇编: 代码:
nasm -f elf32 loader.asm -o kasm.o
2:编译.c: 代码:
gcc -m32 -ffreestanding -c kernel.c -o kc.o
3:Link 两者: 代码:
ld -m elf_i386 -T linker.ld -o kernel kasm.o kc.o
完整的错误输出是: 代码:
kc.o: In function `k_enter_protected_mode':
kernel.c:(.text+0x1e1): undefined reference to `gdtr'
代码如下: 代码:
/*
*
* kernel.c - version 0.0.1
* This script is under the license of the distributed package, this license
* can be found in the package itself
* Script coded by Cristian Simón for the CKA Proyect
* ----
* License: GNU GPL v3
* Coder: Cristian Simón
* Proyect: CKA
*
*/
/* Output defines */
#define BLACK_BGROUND 0X07 /* black background */
#define WHITE_TXT 0x07 /* light gray on black text */
#define GREEN_TXT 0x02 /* light green on black text */
#define RED_TXT 0x04 /* light red on black text*/
#define CYAN_TXT 0x03 /*light cyan on black text */
#include <stddef.h>
#include <stdint.h>
#include <cpuid.h>
void k_clear_screen();
void k_sleep_3sec();
unsigned int k_printf(char *message, unsigned int line, float color);
void k_malloc(size_t sz);
void k_free(void *mem);
/* k_clear_screen : to clear the entire text screen */
void k_clear_screen()
{
char *vidmem = (char *) 0xC00B8000;
unsigned int i=0;
while(i < (80*25*2))
{
vidmem[i]=' ';
i++;
vidmem[i]=BLACK_BGROUND;
i++;
};
}
/* k_printf : the message and the line # */
unsigned int k_printf(char *message, unsigned int line, float color)
{
char *vidmem = (char *) 0xC00B8000;
unsigned int i=0;
i=(line*80*2);
while(*message!=0)
{
if(*message=='\n') /* check for a new line */
{
line++;
i=(line*80*2);
*message++;
} else {
vidmem[i]=*message;
*message++;
i++;
vidmem[i]=color;
i++;
};
};
return(1);
}
/*
* k_sleep_3sec : to make a simple delay of aprox 3 sec, since is a nasty sleep,
* duration will vary
* from system to system
*/
void k_sleep_3sec()
{
int c = 1, d = 1;
for ( c = 1 ; c <= 20000 ; c++ )
for ( d = 1 ; d <= 20000 ; d++ )
{}
}
/*
* Malloc and free functions for this kernel
* Maybe change in the future, sure
*/
static unsigned char our_memory[1024 * 1024]; /* reserve 1 MB for malloc */
static size_t next_index = 0;
int k_malloc_err;
void k_malloc(size_t sz)
{
void *mem;
if(sizeof our_memory - next_index < sz){
return NULL;
k_malloc_err = 1;
}
mem = &our_memory[next_index];
next_index += sz;
return mem;
}
void k_free(void *mem)
{
/* we cheat, and don't free anything. */
}
/* Schreduler */
/*---*/
/*
* Our schreduler is a RTC (Run to Completion)
* In the future we will add more schredulers or change the type
* but for now this is what we got
*/
int proc_number_count = 0;
void k_schreduler(char *proc_name, unsigned int proc_prior)
{
proc_number_count = proc_number_count + 1;
int proc_number = proc_number_count;
}
void k_enter_protected_mode()
{
__asm__ volatile ("cli;"
"lgdt (gdtr);"
"mov %eax, cr0;"
"or %al, 1;"
"mov cr0, %eax;"
"jmp 0x8,PModeMain;"
"PModeMain:");
}
/*main function*/
void k_main()
{
k_clear_screen();
k_printf(" Wellcome to", 0, WHITE_TXT);
k_printf(" CKA!", 1, GREEN_TXT);
k_printf("==============>", 2, WHITE_TXT);
k_printf(" CKA stands for C Kernel with Assembly", 3, WHITE_TXT);
k_printf(" Version 0.0.1, => based in the job of Debashis Barman", 4, WHITE_TXT);
k_printf(" Contact => assemblyislaw@gmail.com / blueshell@mail2tor.com", 5, WHITE_TXT);
k_printf(" or in the github repository page", 6, WHITE_TXT);
k_sleep_3sec();
k_clear_screen();
/* here start the magic */
k_printf(" !===> Starting Checkup <===!", 0, WHITE_TXT);
k_printf(" =-=-=-=-=-=-=-=-=-=-=-=-=-=-", 1, WHITE_TXT);
k_printf("[KernelInfo] Woah! No Kernel Panic for now! Well, lets fix that...", 2, CYAN_TXT);
k_printf("[Proc1] Checking for k_malloc() and k_free() kernel functions", 3, WHITE_TXT);
k_malloc(15);
if (k_malloc_err == 1){
k_printf("[F-ERROR] Unable to use k_malloc, do you have enough memory?", 4, RED_TXT);
while(1){
int error_stayer = 1;
}
} else{
k_printf("[Proc1] k_malloc and k_free found, resuming boot...", 4, GREEN_TXT);
}
k_enter_protected_mode();
k_printf("[KernelInfo] Switched to protected mode successfully", 5, CYAN_TXT);
}
这是kernel.c
代码:
ENTRY(loader)
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
/* The kernel will live at 3GB + 1MB in the virtual
address space, which will be mapped to 1MB in the
physical address space. */
. = 0xC0100000;
.text : AT(ADDR(.text) - 0xC0000000) {
*(.text)
*(.rodata*)
}
.data ALIGN (0x1000) : AT(ADDR(.data) - 0xC0000000) {
*(.data)
}
.bss : AT(ADDR(.bss) - 0xC0000000) {
_sbss = .;
*(COMMON)
*(.bss)
_ebss = .;
}
}
这是 linker.ld
代码:
global _loader ; Make entry point visible to linker.
extern k_main ; _main is defined elsewhere
; setting up the Multiboot header - see GRUB docs for details
MODULEALIGN equ 1<<0 ; align loaded modules on page boundaries
MEMINFO equ 1<<1 ; provide memory map
FLAGS equ MODULEALIGN | MEMINFO ; this is the Multiboot 'flag' field
MAGIC equ 0x1BADB002 ; 'magic number' lets bootloader find the header
CHECKSUM equ -(MAGIC + FLAGS) ; checksum required
; This is the virtual base address of kernel space. It must be used to convert virtual
; addresses into physical addresses until paging is enabled. Note that this is not
; the virtual address where the kernel image itself is loaded -- just the amount that must
; be subtracted from a virtual address to get a physical address.
KERNEL_VIRTUAL_BASE equ 0xC0000000 ; 3GB
KERNEL_PAGE_NUMBER equ (KERNEL_VIRTUAL_BASE >> 22) ; Page directory index of kernel's 4MB PTE.
section .data
align 0x1000
BootPageDirectory:
; This page directory entry identity-maps the first 4MB of the 32-bit physical address space.
; All bits are clear except the following:
; bit 7: PS The kernel page is 4MB.
; bit 1: RW The kernel page is read/write.
; bit 0: P The kernel page is present.
; This entry must be here -- otherwise the kernel will crash immediately after paging is
; enabled because it can't fetch the next instruction! It's ok to unmap this page later.
dd 0x00000083
times (KERNEL_PAGE_NUMBER - 1) dd 0 ; Pages before kernel space.
; This page directory entry defines a 4MB page containing the kernel.
dd 0x00000083
times (1024 - KERNEL_PAGE_NUMBER - 1) dd 0 ; Pages after the kernel image.
section .text
align 4
MultiBootHeader:
dd MAGIC
dd FLAGS
dd CHECKSUM
; reserve initial kernel stack space -- that's 16k.
STACKSIZE equ 0x4000
; setting up entry point for linker
loader equ (_loader - 0xC0000000)
global loader
_loader:
; NOTE: Until paging is set up, the code must be position-independent and use physical
; addresses, not virtual ones!
mov ecx, (BootPageDirectory - KERNEL_VIRTUAL_BASE)
mov cr3, ecx ; Load Page Directory Base Register.
mov ecx, cr4
or ecx, 0x00000010 ; Set PSE bit in CR4 to enable 4MB pages.
mov cr4, ecx
mov ecx, cr0
or ecx, 0x80000000 ; Set PG bit in CR0 to enable paging.
mov cr0, ecx
; Start fetching instructions in kernel space.
; Since eip at this point holds the physical address of this command (approximately 0x00100000)
; we need to do a long jump to the correct virtual address of StartInHigherHalf which is
; approximately 0xC0100000.
lea ecx, [StartInHigherHalf]
jmp ecx ; NOTE: Must be absolute jump!
StartInHigherHalf:
; Unmap the identity-mapped first 4MB of physical address space. It should not be needed
; anymore.
mov dword [BootPageDirectory], 0
invlpg [0]
; NOTE: From now on, paging should be enabled. The first 4MB of physical address space is
; mapped starting at KERNEL_VIRTUAL_BASE. Everything is linked to this address, so no more
; position-independent code or funny business with virtual-to-physical address translation
; should be necessary. We now have a higher-half kernel.
mov esp, stack+STACKSIZE ; set up the stack
push eax ; pass Multiboot magic number
; pass Multiboot info structure -- WARNING: This is a physical address and may not be
; in the first 4MB!
push ebx
call k_main ; call kernel proper
hlt ; halt machine should kernel return
section .bss
align 32
stack:
resb STACKSIZE ; reserve 16k stack on a uint64_t boundary
这是loader.asm
我试图通过将 ASM 块转换为高级 ASM 块并将 gdtr 解析为参数来解决这个问题,但我不明白最后一种方法 我该如何解决错误?
你的错误:
kc.o: In function `k_enter_protected_mode': kernel.c:(.text+0x1e1): undefined reference to `gdtr'
由于这行汇编代码而生成:
"lgdt (gdtr);"
gdtr
是一个内存操作数,表示可以找到 GDT 记录的内存地址的标签。您没有使用该名称定义的结构。这会导致未定义的引用。
您需要创建 GDT 记录,其中包含 GDT table 的大小和长度。该记录将由 LGDT 指令加载到 GDT 寄存器中。您还没有创建 GDT table。 gdtr
应该是一个 6 字节结构,由 GDT 的长度减去 1(存储在 16 位字中)和一个 32 位线性地址组成,其中 GDTtable可以找到
与其在 C 中做你想做的事,我建议在 call k_main
之前但在设置分页之后在你的汇编代码中做这件事。
在 C 代码中完全删除 k_enter_protected_mode
函数。然后在程序集文件 loader.asm
中放置此代码以在 StartInHigherHalf
代码的开头加载新的 GDT。所以它看起来像:
StartInHigherHalf:
; Set our own GDT, can't rely GDT register being valid after bootloader
; transfers control to our entry point
lgdt [gdtr] ; Load GDT Register with GDT record
mov eax, DATA_SEG
mov ds, eax ; Reload all the data descriptors with Data selector (2nd argument)
mov es, eax
mov gs, eax
mov fs, eax
mov ss, eax
jmp CODE_SEG:.setcs
; Do the FAR JMP to next instruction to set CS with Code selector, and
; set the EIP (instruction pointer) to offset of setcs
.setcs:
唯一剩下的就是定义 GDT table。一个简单的具有必需的 NULL 描述符和平面 32 位代码和数据描述符的文件可以放置在您的 .data
部分,方法是将其更改为:
section .data
align 0x1000
BootPageDirectory:
; This page directory entry identity-maps the first 4MB of the 32-bit physical address space.
; All bits are clear except the following:
; bit 7: PS The kernel page is 4MB.
; bit 1: RW The kernel page is read/write.
; bit 0: P The kernel page is present.
; This entry must be here -- otherwise the kernel will crash immediately after paging is
; enabled because it can't fetch the next instruction! It's ok to unmap this page later.
dd 0x00000083
times (KERNEL_PAGE_NUMBER - 1) dd 0 ; Pages before kernel space.
; This page directory entry defines a 4MB page containing the kernel.
dd 0x00000083
times (1024 - KERNEL_PAGE_NUMBER - 1) dd 0 ; Pages after the kernel image.
; 32-bit GDT to replace one created by multiboot loader
; Per the multiboot specification we Can't rely on GDTR
; being valid so we need our own if we ever intend to
; reload any of the segment registers (this may be an
; issue with protected mode interrupts).
align 8
gdt_start:
dd 0 ; null descriptor
dd 0
gdt32_code:
dw 0FFFFh ; limit low
dw 0 ; base low
db 0 ; base middle
db 10011010b ; access
db 11001111b ; 32-bit size, 4kb granularity, limit 0xfffff pages
db 0 ; base high
gdt32_data:
dw 0FFFFh ; limit low (Same as code)
dw 0 ; base low
db 0 ; base middle
db 10010010b ; access
db 11001111b ; 32-bit size, 4kb granularity, limit 0xfffff pages
db 0 ; base high
end_of_gdt:
gdtr:
dw end_of_gdt - gdt_start - 1
; limit (Size of GDT - 1)
dd gdt_start ; base of GDT
CODE_SEG equ gdt32_code - gdt_start
DATA_SEG equ gdt32_data - gdt_start
我们现在已经添加了所需的 GDT 结构并创建了一个名为 gdtr
的记录,可以使用 LGDT 指令加载该记录。
因为您正在使用 OSDev as a resource, I recommend looking at the GDT tutorial 获取有关创建 GDT 的信息。英特尔手册也是极好的信息来源。
其他观察结果
您的 loader.asm
设置了一个 Multiboot header,所以很可能您正在使用 Multiboot compliant bootloader。当您使用符合多重引导的引导加载程序时,您的 CPU 将被置于 32 位保护模式 之前 它开始 运行 您的代码从 _loader
开始。您的问题表明您认为自己处于实模式,但实际上已经处于保护模式。使用 Mulitboot 加载程序,无需将 CR0 位 0 设置为值 1。它保证已经为 1(已设置)。在我上面的代码中,我在设置 GDT.