我的内核原型有问题 (x86_64)
Problems with my prototype of kernel (x86_64)
我在学习汇编的同时试图了解内核的工作原理,并且在学习如何成功创建可引导 x86_64 内核的职责中,我遇到了一个问题:
我试图通过使用 0xB8000
中的 VGA 缓冲区成功地输出一些带有 "main.c" 中函数的文本(下面的所有文件),就像我对内核原型的 32 位版本所做的一样,但是区别在于启动文件不同。
这里的问题是,当我对 32 位版本使用完全相同的功能时,它成功地打印到屏幕上,但是当使用新文件达到长模式(multiboot.S
和 start.S
)时,这不会发生,在 qemu 中测试时屏幕变黑,几秒钟后它崩溃并显示错误消息:
warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
qemu-system-x86_64: Trying to execute code outside RAM or ROM at 0x00000000000a0000
为什么会这样? VGA 缓冲区不在 0xB8000
中,*.S 文件有问题吗?提前致谢!
我将在这里粘贴内核文件:
内核由 4 个文件组成:"main.c"、"start.S"、"multiboot.S" 和链接描述文件 "linker.ld".
这 3 个文件被链接和编译没有任何错误,文件如下: 这是 main.c(你会看到一个 "basiccolors.h",这个文件只是定义了 vga 颜色代码)
#include "basiccolors.h"
#include <stddef.h>
#include <stdint.h>
volatile uint16_t* vga_buffer = (uint16_t*)0xB8000; /* memory location of the VGA textmode buffer */
/* Columns and rows of the VGA buffer */
const int VGA_COLS = 80;
const int VGA_ROWS = 25;
/* We start displaying text in the top-left of the screen (column = 0, row = 0) */
int term_col = 0;
int term_row = 0;
uint8_t term_color = WHITE_TXT; /* This color and others are defined in basiccolors.h */
/* term_init() : This function initiates the terminal by clearing it */
void term_init()
{
/* Clear the textmode buffer */
for (int col = 0; col < VGA_COLS; col ++)
{
for (int row = 0; row < VGA_ROWS; row ++)
{
/* The VGA textmode buffer has size (VGA_COLS * VGA_ROWS) */
/* Given this, we find an index into the buffer for our character */
const size_t index = (VGA_COLS * row) + col;
/* Entries in the VGA buffer take the binary form BBBBFFFFCCCCCCCC, where: */
/* - B is the background color */
/* - F is the foreground color */
/* - C is the ASCII character */
/* Now we set the character to blank (a space character) */
vga_buffer[index] = ((uint16_t)term_color << 8) | ' ';
}
}
}
/* term_putc(char c) : This function places a single character onto the screen */
void term_putc(char c)
{
/* We don't want to display all characters, for example, the newline ones */
switch (c)
{
case '\n': /* Newline characters should return the column to 0, and increment the row */
{
term_col = 0;
term_row ++;
break;
}
default: /* Normal characters just get displayed and then increment the column */
{
/* Like before, calculate the buffer index */
const size_t index = (VGA_COLS * term_row) + term_col;
vga_buffer[index] = ((uint16_t)term_color << 8) | c;
term_col ++;
break;
}
}
/* We need to reset the column to 0, and increment the row to get to a new line */
if (term_col >= VGA_COLS)
{
term_col = 0;
term_row ++;
}
/* we get past the last row, so we need to reset both column and row to 0 in order to loop back to the top of the screen */
if (term_row >= VGA_ROWS)
{
term_col = 0;
term_row = 0;
}
}
/* term_print : prints an entire string onto the screen, remember to use the "\n" and that short of things */
void term_print(const char* str)
{
for (size_t i = 0; str[i] != '[=12=]'; i ++) /* Keep placing characters until we hit the null-terminating character ('[=12=]') */
term_putc(str[i]);
}
/* Main function of the kernel, the one that is called at the end of the loading */
void kmain(void)
{
/* Now we should initialize the interfaces */
term_init(); /* VGA basic interface, in "basicoutput.c/h" */
term_print("CKA Cobalt release [0-0-1]\n");
};
这是start.S:
.extern kmain
.section .data
.align 16
gdtr:
gdtr_limit:
.word (global_descriptor_table_end - global_descriptor_table) - 1
gdtr_pointer:
.int global_descriptor_table
.global global_descriptor_table
global_descriptor_table:
null_descriptor:
.quad 0x0000000000000000
code_descriptor:
.quad 0x0020980000000000
data_descriptor:
.quad 0x0000900000000000
global_descriptor_table_end:
.global null_segment
.set null_segment, (null_descriptor - global_descriptor_table)
.global code_segment
.set code_segment, (code_descriptor - global_descriptor_table)
.global data_segment
.set data_segment, (data_descriptor - global_descriptor_table)
multiboot_magic:
.space 4
multiboot_info:
.space 4
.section .bss
.global kernel_pagetable
.align 0x1000
kernel_pagetable:
pml4:
.space 0x1000
pdpt:
.space 0x1000
pd:
.space 0x1000
kernel_pagetable_end:
.global kernel_stack
kernel_stack:
.space 0x1000
kernel_stack_end:
.section .text
.code32
.global start
start:
cli
# store multiboot parameters in .data
mov %eax, multiboot_magic
mov %ebx, multiboot_info
# zerofill .bss
cld
mov $bss, %edi
mov $bss_end, %ecx
sub %edi, %ecx
xor %eax, %eax
rep stosb
# create pagetable for identity mapping lower 2 megabytes
# make minimal page table entries
.set pml4_entry, (pdpt + 0x03)
.set pdpt_entry, (pd + 0x03)
.set pd_entry, 0b10000011
movl $pml4_entry, pml4
movl $pdpt_entry, pdpt
movl $pd_entry, pd
# setup long mode
# load global descriptor table
lgdt (gdtr)
# enable Physical Address Extension (PAE)
mov %cr4, %eax
bts , %eax
mov %eax, %cr4
# set up page table
mov $kernel_pagetable, %eax
mov %eax, %cr3
# set up long mode
.set EFER_MSR_ADDRESS, 0xC0000080
mov $EFER_MSR_ADDRESS, %ecx
rdmsr
bts , %eax
wrmsr
# enable paging
mov %cr0, %eax
bts , %eax
mov %eax, %cr0
# long jump to set code segment
ljmp $code_segment, $longmode_start
.code64
longmode_start:
# data segment selector to all data segments
mov $data_segment, %bx
mov %bx, %ds
mov %bx, %es
mov %bx, %fs
mov %bx, %gs
# null segment selector to ss
mov $null_segment, %bx
mov %bx, %ss
# set up kernel stack
mov $kernel_stack_end, %rsp
push [=13=] # debugger backtrace stops here
# call kmain
mov multiboot_magic, %edi
mov multiboot_info, %esi
call kmain
# hang the computer
cli
hang:
hlt
jmp hang
这是multiboot.S:
.set MULTIBOOT_PAGE_ALIGN, 1 << 0
.set MULTIBOOT_MEM_INFO, 1 << 1
.set MULTIBOOT_AOUT_KLUDGE, 1 << 16
.set MULTIBOOT_MAGIC, 0x1BADB002
.set MULTIBOOT_FLAGS, MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEM_INFO | MULTIBOOT_AOUT_KLUDGE
.set MULTIBOOT_CHECKSUM, -(MULTIBOOT_MAGIC + MULTIBOOT_FLAGS)
.section .mboot
.align 4
.global multiboot_header
multiboot_header:
.int MULTIBOOT_MAGIC
.int MULTIBOOT_FLAGS
.int MULTIBOOT_CHECKSUM
.int multiboot_header
.int text
.int data_end
.int kernel_end
.int start
这是我的linker.ld:
OUTPUT_FORMAT("elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(start)
phys = 0x0100000;
SECTIONS
{
. = phys;
kernel_start = .;
.text ALIGN(4096) : AT( ADDR(.text) )
{
text = .;
*(.mboot) /* Put Multiboot header section in the beginning of .text section */
*(.text)
*(.rodata)
text_end = .;
}
.data ALIGN(4096) : AT( ADDR(.data) )
{
data = .;
*(.data)
data_end = .;
}
.bss ALIGN(4096) : AT( ADDR(.bss) )
{
bss = .;
*(.bss)
bss_end = .;
}
kernel_end = .;
}
所有这些代码都通过以下命令编译和链接:
正在编译...
x86_64-elf-gcc -ffreestanding -mcmodel=large -mno-red-zone -mno-mmx -mno-sse -mno-sse2 -c <file> -o <object-file>
和链接:
x86_64-elf-gcc -ffreestanding -T linker.ld multiboot.o start.o main.o -o kernel.bin -nostdlib -lgcc
此命令由 osdev.com 在教程 http://wiki.osdev.org/Creating_a_64-bit_kernel 中建议,所有命令均使用 gcc 交叉编译器针对 x86_64 架构进行编译和链接。
QEMU 在使用 -kernel
参数时不支持 ELF64 可执行文件。您将需要使用 GRUB2 之类的 multiboot2 兼容加载器来引导您的内核。不幸的是,您还需要将多重启动 header 更改为 multiboot2 compliant。您可以将 multiboot.S
文件替换为:
.section .mboot
.code32
.align 8
# constants for multiboot2 header:
.set MAGIC2, 0xe85250d6
.set ARCH2, 0 # i386 protected mode
.set CHECKSUM2, (-(MAGIC2 + ARCH2 + (mboot2_end - mboot2_start)) & 0xffffffff)
/* multiboot2 header */
mboot2_start:
.long MAGIC2
.long ARCH2
.long mboot2_end - mboot2_start
.long CHECKSUM2
.word 0 # type
.word 0 # flags
.long 8 # size
mboot2_end:
你像以前一样编译它。有一个问题 - 为了确保这个 header 最终不会被推到文件的前 8kb 之外,你可能需要在 link:[=16 时指定 4kb 页面=]
x86_64-elf-gcc -z max-page-size=0x1000 -ffreestanding -T linker.ld \
multiboot.o start.o main.o -o kernel.bin -nostdlib -lgcc
添加 -z max-page-size=0x1000
将最大页面大小强制为 4kb。
我在学习汇编的同时试图了解内核的工作原理,并且在学习如何成功创建可引导 x86_64 内核的职责中,我遇到了一个问题:
我试图通过使用 0xB8000
中的 VGA 缓冲区成功地输出一些带有 "main.c" 中函数的文本(下面的所有文件),就像我对内核原型的 32 位版本所做的一样,但是区别在于启动文件不同。
这里的问题是,当我对 32 位版本使用完全相同的功能时,它成功地打印到屏幕上,但是当使用新文件达到长模式(multiboot.S
和 start.S
)时,这不会发生,在 qemu 中测试时屏幕变黑,几秒钟后它崩溃并显示错误消息:
warning: TCG doesn't support requested feature: CPUID.01H:ECX.vmx [bit 5]
qemu-system-x86_64: Trying to execute code outside RAM or ROM at 0x00000000000a0000
为什么会这样? VGA 缓冲区不在 0xB8000
中,*.S 文件有问题吗?提前致谢!
我将在这里粘贴内核文件:
内核由 4 个文件组成:"main.c"、"start.S"、"multiboot.S" 和链接描述文件 "linker.ld".
这 3 个文件被链接和编译没有任何错误,文件如下: 这是 main.c(你会看到一个 "basiccolors.h",这个文件只是定义了 vga 颜色代码)
#include "basiccolors.h"
#include <stddef.h>
#include <stdint.h>
volatile uint16_t* vga_buffer = (uint16_t*)0xB8000; /* memory location of the VGA textmode buffer */
/* Columns and rows of the VGA buffer */
const int VGA_COLS = 80;
const int VGA_ROWS = 25;
/* We start displaying text in the top-left of the screen (column = 0, row = 0) */
int term_col = 0;
int term_row = 0;
uint8_t term_color = WHITE_TXT; /* This color and others are defined in basiccolors.h */
/* term_init() : This function initiates the terminal by clearing it */
void term_init()
{
/* Clear the textmode buffer */
for (int col = 0; col < VGA_COLS; col ++)
{
for (int row = 0; row < VGA_ROWS; row ++)
{
/* The VGA textmode buffer has size (VGA_COLS * VGA_ROWS) */
/* Given this, we find an index into the buffer for our character */
const size_t index = (VGA_COLS * row) + col;
/* Entries in the VGA buffer take the binary form BBBBFFFFCCCCCCCC, where: */
/* - B is the background color */
/* - F is the foreground color */
/* - C is the ASCII character */
/* Now we set the character to blank (a space character) */
vga_buffer[index] = ((uint16_t)term_color << 8) | ' ';
}
}
}
/* term_putc(char c) : This function places a single character onto the screen */
void term_putc(char c)
{
/* We don't want to display all characters, for example, the newline ones */
switch (c)
{
case '\n': /* Newline characters should return the column to 0, and increment the row */
{
term_col = 0;
term_row ++;
break;
}
default: /* Normal characters just get displayed and then increment the column */
{
/* Like before, calculate the buffer index */
const size_t index = (VGA_COLS * term_row) + term_col;
vga_buffer[index] = ((uint16_t)term_color << 8) | c;
term_col ++;
break;
}
}
/* We need to reset the column to 0, and increment the row to get to a new line */
if (term_col >= VGA_COLS)
{
term_col = 0;
term_row ++;
}
/* we get past the last row, so we need to reset both column and row to 0 in order to loop back to the top of the screen */
if (term_row >= VGA_ROWS)
{
term_col = 0;
term_row = 0;
}
}
/* term_print : prints an entire string onto the screen, remember to use the "\n" and that short of things */
void term_print(const char* str)
{
for (size_t i = 0; str[i] != '[=12=]'; i ++) /* Keep placing characters until we hit the null-terminating character ('[=12=]') */
term_putc(str[i]);
}
/* Main function of the kernel, the one that is called at the end of the loading */
void kmain(void)
{
/* Now we should initialize the interfaces */
term_init(); /* VGA basic interface, in "basicoutput.c/h" */
term_print("CKA Cobalt release [0-0-1]\n");
};
这是start.S:
.extern kmain
.section .data
.align 16
gdtr:
gdtr_limit:
.word (global_descriptor_table_end - global_descriptor_table) - 1
gdtr_pointer:
.int global_descriptor_table
.global global_descriptor_table
global_descriptor_table:
null_descriptor:
.quad 0x0000000000000000
code_descriptor:
.quad 0x0020980000000000
data_descriptor:
.quad 0x0000900000000000
global_descriptor_table_end:
.global null_segment
.set null_segment, (null_descriptor - global_descriptor_table)
.global code_segment
.set code_segment, (code_descriptor - global_descriptor_table)
.global data_segment
.set data_segment, (data_descriptor - global_descriptor_table)
multiboot_magic:
.space 4
multiboot_info:
.space 4
.section .bss
.global kernel_pagetable
.align 0x1000
kernel_pagetable:
pml4:
.space 0x1000
pdpt:
.space 0x1000
pd:
.space 0x1000
kernel_pagetable_end:
.global kernel_stack
kernel_stack:
.space 0x1000
kernel_stack_end:
.section .text
.code32
.global start
start:
cli
# store multiboot parameters in .data
mov %eax, multiboot_magic
mov %ebx, multiboot_info
# zerofill .bss
cld
mov $bss, %edi
mov $bss_end, %ecx
sub %edi, %ecx
xor %eax, %eax
rep stosb
# create pagetable for identity mapping lower 2 megabytes
# make minimal page table entries
.set pml4_entry, (pdpt + 0x03)
.set pdpt_entry, (pd + 0x03)
.set pd_entry, 0b10000011
movl $pml4_entry, pml4
movl $pdpt_entry, pdpt
movl $pd_entry, pd
# setup long mode
# load global descriptor table
lgdt (gdtr)
# enable Physical Address Extension (PAE)
mov %cr4, %eax
bts , %eax
mov %eax, %cr4
# set up page table
mov $kernel_pagetable, %eax
mov %eax, %cr3
# set up long mode
.set EFER_MSR_ADDRESS, 0xC0000080
mov $EFER_MSR_ADDRESS, %ecx
rdmsr
bts , %eax
wrmsr
# enable paging
mov %cr0, %eax
bts , %eax
mov %eax, %cr0
# long jump to set code segment
ljmp $code_segment, $longmode_start
.code64
longmode_start:
# data segment selector to all data segments
mov $data_segment, %bx
mov %bx, %ds
mov %bx, %es
mov %bx, %fs
mov %bx, %gs
# null segment selector to ss
mov $null_segment, %bx
mov %bx, %ss
# set up kernel stack
mov $kernel_stack_end, %rsp
push [=13=] # debugger backtrace stops here
# call kmain
mov multiboot_magic, %edi
mov multiboot_info, %esi
call kmain
# hang the computer
cli
hang:
hlt
jmp hang
这是multiboot.S:
.set MULTIBOOT_PAGE_ALIGN, 1 << 0
.set MULTIBOOT_MEM_INFO, 1 << 1
.set MULTIBOOT_AOUT_KLUDGE, 1 << 16
.set MULTIBOOT_MAGIC, 0x1BADB002
.set MULTIBOOT_FLAGS, MULTIBOOT_PAGE_ALIGN | MULTIBOOT_MEM_INFO | MULTIBOOT_AOUT_KLUDGE
.set MULTIBOOT_CHECKSUM, -(MULTIBOOT_MAGIC + MULTIBOOT_FLAGS)
.section .mboot
.align 4
.global multiboot_header
multiboot_header:
.int MULTIBOOT_MAGIC
.int MULTIBOOT_FLAGS
.int MULTIBOOT_CHECKSUM
.int multiboot_header
.int text
.int data_end
.int kernel_end
.int start
这是我的linker.ld:
OUTPUT_FORMAT("elf64-x86-64")
OUTPUT_ARCH(i386:x86-64)
ENTRY(start)
phys = 0x0100000;
SECTIONS
{
. = phys;
kernel_start = .;
.text ALIGN(4096) : AT( ADDR(.text) )
{
text = .;
*(.mboot) /* Put Multiboot header section in the beginning of .text section */
*(.text)
*(.rodata)
text_end = .;
}
.data ALIGN(4096) : AT( ADDR(.data) )
{
data = .;
*(.data)
data_end = .;
}
.bss ALIGN(4096) : AT( ADDR(.bss) )
{
bss = .;
*(.bss)
bss_end = .;
}
kernel_end = .;
}
所有这些代码都通过以下命令编译和链接:
正在编译...
x86_64-elf-gcc -ffreestanding -mcmodel=large -mno-red-zone -mno-mmx -mno-sse -mno-sse2 -c <file> -o <object-file>
和链接:
x86_64-elf-gcc -ffreestanding -T linker.ld multiboot.o start.o main.o -o kernel.bin -nostdlib -lgcc
此命令由 osdev.com 在教程 http://wiki.osdev.org/Creating_a_64-bit_kernel 中建议,所有命令均使用 gcc 交叉编译器针对 x86_64 架构进行编译和链接。
QEMU 在使用 -kernel
参数时不支持 ELF64 可执行文件。您将需要使用 GRUB2 之类的 multiboot2 兼容加载器来引导您的内核。不幸的是,您还需要将多重启动 header 更改为 multiboot2 compliant。您可以将 multiboot.S
文件替换为:
.section .mboot
.code32
.align 8
# constants for multiboot2 header:
.set MAGIC2, 0xe85250d6
.set ARCH2, 0 # i386 protected mode
.set CHECKSUM2, (-(MAGIC2 + ARCH2 + (mboot2_end - mboot2_start)) & 0xffffffff)
/* multiboot2 header */
mboot2_start:
.long MAGIC2
.long ARCH2
.long mboot2_end - mboot2_start
.long CHECKSUM2
.word 0 # type
.word 0 # flags
.long 8 # size
mboot2_end:
你像以前一样编译它。有一个问题 - 为了确保这个 header 最终不会被推到文件的前 8kb 之外,你可能需要在 link:[=16 时指定 4kb 页面=]
x86_64-elf-gcc -z max-page-size=0x1000 -ffreestanding -T linker.ld \
multiboot.o start.o main.o -o kernel.bin -nostdlib -lgcc
添加 -z max-page-size=0x1000
将最大页面大小强制为 4kb。