当我必须设置EIP寄存器时,程序无法跳到正确的位置。我期望jmp *%ecx
使用LEA跳到内存设置EIP的正确位置,大约为0xC0100000(标签:StartInHigherHalf
)。我认为kmain
并不是必需的,因为问题是在调用之前。我还是要发布它。
我尝试使用QEMU上的-d cpu
标志对其进行调试,并且在跳转之前(被HLT阻止)说ECX未加载LEA函数。 LEA指令可能无法执行吗?为什么会发生这种情况?我该如何解决?
Boot.S:
.set ALIGN, 1<<0
.set MEMINFO, 1<<1
.set FLAGS, ALIGN | MEMINFO
.set MAGIC, 0x1BADB002
.set CHECKSUM, -(MAGIC + FLAGS)
.set KERNEL_VIRTUAL_BASE, 0xC0000000
.set KERNEL_PAGE_NUMBER, (KERNEL_VIRTUAL_BASE >> 22)
.section .multiboot
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM
.section .data
.align 0x1000
BootPageDirectory:
.quad 0x00000083
.rept KERNEL_PAGE_NUMBER - 1
.quad 0
.endr
.quad 0x00000083
.rept 0x400 - KERNEL_PAGE_NUMBER - 1
.quad 0
.endr
.set STACKSIZE, 0x4000
.global __start__
.set __start__, (setup)
setup:
mov $(BootPageDirectory - KERNEL_VIRTUAL_BASE), %ecx
mov %ecx, %cr3
mov %cr4, %ecx
or $0x00000010, %ecx
mov %ecx, %cr4
mov %cr0, %ecx
or $0x80000000, %ecx
mov %ecx, %cr0
lea StartInHigherHalf, %ecx
jmp *%ecx
StartInHigherHalf:
movl $0, (BootPageDirectory)
invlpg (0)
mov $(stack + STACKSIZE), %esp
push %eax
push %ebx
call _init
call kmain
cli
1: hlt
jmp 1
.section .bss
.align 32
.lcomm stack, STACKSIZE
.global gdtFlush
.extern gp
gdtFlush:
lgdt (gp)
mov $0x10, %eax
mov %eax, %ds
mov %eax, %es
mov %eax, %gs
mov %eax, %fs
mov %eax, %ss
ljmp $0x08, $setcs
setcs:
ret
linker.ld:
ENTRY(__start__)
OUTPUT_FORMAT(elf32-i386)
SECTIONS {
. = 0xC0100000;
.text ALIGN(0x1000) : AT(ADDR(.text) - 0xC0000000) {
*(.multiboot)
*(.text)
}
.rodata ALIGN(0x1000) : AT(ADDR(.rodata) - 0xC0000000) {
*(.rodata*)
}
.data ALIGN(0x1000) : AT(ADDR(.data) - 0xC0000000) {
*(.data)
}
.bss ALIGN(0x1000) : AT(ADDR(.bss) - 0xC0000000) {
_sbss = .;
*(COMMON)
*(.bss)
_ebss = .;
}
}
kmain.c:
#include <kernel/tty.h>
#include <kernel/log.h>
#include <kernel/stack-protector.h>
#include <kernel/gdt.h>
__attribute__ ((noreturn));
void kmain(void) {
gdtInstall();
initializeTerminal();
char c;
char *buffer = &c;
char *start = buffer;
char str[] = "Hello, kernel World!";
atomicallyLog(1, 1, str, buffer);
kprintf(start, 1);
}
答案 0 :(得分:2)
如果我们暂时假设您的代码实际到达lea StartInHigherHalf, %ecx
,并且不认为它实际执行了该指令-最明显的问题将是与分页相关。 LEA 指令恰好是在设置了分页位之后执行的第一条指令。如果分页错误,则下一条指令可能会出错。
在查看页面目录时,我注意到您使用.quad
(8字节类型)而不是.int
(4字节类型)来构建它。页面目录中的每个条目都是4个字节,而不是8个字节。这可能是导致问题的主要原因。您只需使用.fill
指令即可避免使用.rept
宏:
.fill repeat , size , value
结果,大小和值是绝对表达式。这会发出大小字节的重复副本。重复次数可以为零或更多。大小可能为零或更大,但如果大于8,则认为它的值8与其他人的汇编程序兼容。每个重复字节的内容取自一个8字节的数字。最高4字节为零。最低顺序的4个字节是按组装时在计算机上以整数的字节顺序呈现的值。重复中的每个大小字节均取自该数字的最低顺序大小字节。同样,这种奇怪的行为与其他人的汇编程序兼容。
大小和值是可选的。如果第二个逗号和值不存在,则将值假定为零。如果缺少第一个逗号和后面的标记,则假定大小为1。
您的引导页目录可以写为:
.align 0x1000
BootPageDirectory:
.int 0x00000083
.fill KERNEL_PAGE_NUMBER - 1, 4, 0
.int 0x00000083
.fill 0x400 - KERNEL_PAGE_NUMBER - 1, 4, 0
.rept
也可以工作:
.align 0x1000
BootPageDirectory:
.int 0x00000083
.rept KERNEL_PAGE_NUMBER - 1
.int 0
.endr
.int 0x00000083
.rept 0x400 - KERNEL_PAGE_NUMBER - 1
.int 0
.endr
您的代码似乎是OSDev Higher Half x86 Bare Bones内核的变体。主要的区别似乎是您从 NASM 转换为GNU汇编程序。我在OSDev Forum上写了关于此代码的主要缺陷。它的结构方式是,假设所有内容都是上半部分,则生成所有地址,而不是将上半部分的0x100000从下半部分的内容拆分为0x100000。这种设计导致以Multiboot规范未真正定义的方式使用Mulitboot。
您可以通过使用链接程序脚本将两者分开并将相应的部分放在boot.S
中来解决此问题。链接描述文件(linker.ld
)如下:
ENTRY(setup)
OUTPUT_FORMAT(elf32-i386)
KERNEL_VIRTUAL_BASE = 0xC0000000;
SECTIONS {
/* The multiboot data and code will exist in low memory
starting at 0x100000 */
. = 0x00100000;
.lowerhalf ALIGN(0x1000) : {
*(.lowerhalf.data)
*(.lowerhalf.text)
}
/* The kernel will live at 3GB + 1MB in the virtual
address space, which will be mapped to 1MB in the
physical address space. */
. += KERNEL_VIRTUAL_BASE;
.text ALIGN(0x1000) : AT(ADDR(.text) - KERNEL_VIRTUAL_BASE) {
*(.text)
}
.data ALIGN (0x1000) : AT(ADDR(.data) - KERNEL_VIRTUAL_BASE) {
*(.data)
*(.rodata*)
}
.bss ALIGN (0x1000) : AT(ADDR(.bss) - KERNEL_VIRTUAL_BASE) {
_sbss = .;
*(COMMON)
*(.bss)
_ebss = .;
}
/DISCARD/ : {
*(.eh_frame);
*(.comment*);
}
}
您的boot.S
文件可以写为:
.set ALIGN, 1<<0
.set MEMINFO, 1<<1
.set FLAGS, ALIGN | MEMINFO
.set MAGIC, 0x1BADB002
.set CHECKSUM, -(MAGIC + FLAGS)
.set KERNEL_VIRTUAL_BASE, 0xC0000000
.set KERNEL_PAGE_NUMBER, (KERNEL_VIRTUAL_BASE >> 22)
.section .lowerhalf.data,"aw",@progbits
.align 4
.long MAGIC
.long FLAGS
.long CHECKSUM
.align 0x1000
BootPageDirectory:
.int 0x00000083
.fill KERNEL_PAGE_NUMBER - 1, 4, 0
.int 0x00000083
.fill 0x400 - KERNEL_PAGE_NUMBER - 1, 4, 0
.set STACKSIZE, 0x4000
.section .lowerhalf.text,"axw",@progbits
.global setup
setup:
mov $BootPageDirectory, %ecx
mov %ecx, %cr3
mov %cr4, %ecx
or $0x00000010, %ecx
mov %ecx, %cr4
mov %cr0, %ecx
or $0x80000000, %ecx
mov %ecx, %cr0
jmp StartInHigherHalf
.section .text
StartInHigherHalf:
movl $0, (BootPageDirectory)
invlpg (0)
mov $(stack + STACKSIZE), %esp
push %eax
push %ebx
#call _init
call kmain
cli
1: hlt
jmp 1
/*
.global gdtFlush
.extern gp
gdtFlush:
lgdt (gp)
mov $0x10, %eax
mov %eax, %ds
mov %eax, %es
mov %eax, %gs
mov %eax, %fs
mov %eax, %ss
ljmp $0x08, $setcs
setcs:
ret
*/
.section .bss
.align 32
.lcomm stack, STACKSIZE
无所事事的内核(kernel.c
)看起来像:
volatile unsigned short int *const video_mem = (unsigned short int *)0xc00b8000;
void kmain(void) {
/* print MDP in upper left of display using white on magenta */
video_mem[0] = (0x57 << 8) | 'M';
video_mem[1] = (0x57 << 8) | 'D';
video_mem[2] = (0x57 << 8) | 'P';
while (1) __asm__ ("hlt");
}
您可以使用以下命令生成ELF可执行文件:
i686-elf-gcc -c -g -m32 boot.S -o boot.o
i686-elf-gcc -c -g -m32 -O3 kernel.c -o kernel.o -ffreestanding -std=gnu99 \
-mno-red-zone -fno-exceptions -Wall -Wextra
i686-elf-gcc -nostdlib -Wl,--build-id=none -T linker.ld boot.o kernel.o -lgcc -o kernel.elf
评论:
kernel.elf
可以由GRUB(在ISO文件中)加载,也可以直接由 QEMU 的-kernel
选项加载。StartInHigherHalf
简化了JMP。您仍然可以使用LEA / JMP方法,但不会获得任何好处。gdtFlush
错误地放在了.bss
部分中。需要将其移至.text
部分。我已经在代码中这样做了,但是我也对其进行了注释,以使该内核在分页中不起作用。您将需要重新添加它以使用您的代码库。 call _init
注释掉,以使此功能不起作用。您需要删除注释才能将其集成到您的代码库中。