Assembly 从x86实模式引导加载程序跳到保护模式C代码
我正在编写一个x86引导加载程序,目前正在尝试启用if-enable-protected模式,然后跳转到C文件中定义的主函数。但是我在链接由汇编代码和C代码创建的对象文件时遇到了问题 下面是my boot.asm的外观,省略了不相关的部分:Assembly 从x86实模式引导加载程序跳到保护模式C代码,assembly,gcc,x86,bootloader,osdev,Assembly,Gcc,X86,Bootloader,Osdev,我正在编写一个x86引导加载程序,目前正在尝试启用if-enable-protected模式,然后跳转到C文件中定义的主函数。但是我在链接由汇编代码和C代码创建的对象文件时遇到了问题 下面是my boot.asm的外观,省略了不相关的部分: [bits 16] [extern main] ; ... lgdt [gdt_descriptor] mov eax, cr0 or al, 0x1 mov cr0, eax jmp GDT_SELECTOR_KERNEL_CODE:protect
[bits 16]
[extern main]
; ...
lgdt [gdt_descriptor]
mov eax, cr0
or al, 0x1
mov cr0, eax
jmp GDT_SELECTOR_KERNEL_CODE:protected_mode_start
[bits 32]
protected_mode_start:
; ...
call main
我的GDT的长度和位置存储在GDT_描述符中,GDT_选择器_KERNEL_CODE是GDT中的内核代码段偏移量
我在boot.C中的C代码目前看起来就像:
int main(void)
{ return 0; }
我还编写了以下链接器脚本boot.ld:
当尝试使用nasm、gcc和ld编译和链接时,我得到:
我这里有几个问题:
为什么会出现未定义的引用错误
如果我没有在boot.c中定义一个main函数,gcc将根本不会创建一个对象文件,那么使用-ffreestanding是否可以做到这一点?
我不确定如何设置BSS和堆栈。BSS段是否应该只占用从数据段末尾到引导加载程序512字节末尾的剩余字节?在调用main之前,在哪里设置堆栈指针?
我在链接器文件中布局代码和数据段的方式是否与我的保护模式设置兼容?我对这里是什么感到困惑,因为我创建的二进制文件在16位和32位模式下运行。
对于x86_64,这种跳转可能看起来像
movabsq $main_entry, %rax
callq *%rax
我正在使用臂弓,在那里我使用相同的方法,通过函数入口地址间接调用
为了设置堆栈,将相应的节添加到链接器脚本中
.stack (NOLOAD) : ALIGN(64)
{
_stack_el1_end = .;
. = . + 0x4000;
_stack_el1 = .;
}
现在在启动代码中,您需要将_stack_el1符号加载到堆栈指针寄存器中。
注意,由于堆栈是递减的,所以堆栈指针取堆栈内存的顶部地址
BSS部分与其他部分类似,应该类似
.bss (NOLOAD) : ALIGN(16)
{
__bss_start = .
*(.bss)
*(.bss.*)
. = ALIGN(4);
__bss_end = .
}
对于x86_64,这种跳转可能看起来像
movabsq $main_entry, %rax
callq *%rax
我正在使用臂弓,在那里我使用相同的方法,通过函数入口地址间接调用
为了设置堆栈,将相应的节添加到链接器脚本中
.stack (NOLOAD) : ALIGN(64)
{
_stack_el1_end = .;
. = . + 0x4000;
_stack_el1 = .;
}
现在在启动代码中,您需要将_stack_el1符号加载到堆栈指针寄存器中。
注意,由于堆栈是递减的,所以堆栈指针取堆栈内存的顶部地址
BSS部分与其他部分类似,应该类似
.bss (NOLOAD) : ALIGN(16)
{
__bss_start = .
*(.bss)
*(.bss.*)
. = ALIGN(4);
__bss_end = .
}
为什么会出现未定义的引用错误
我不知道;但请检查编译器是否没有向可能需要extern\u main和call\u main的符号添加下划线
我不确定如何设置BSS和堆栈。BSS段是否应该只占用从数据段末尾到引导加载程序512字节末尾的剩余字节?在调用main之前,在哪里设置堆栈指针
我在链接器文件中布局代码和数据段的方式是否与我的保护模式设置兼容?我对这里是什么感到困惑,因为我创建的二进制文件在16位和32位模式下运行
用于从BIOS引导;BIOS不理解Elf可执行文件格式,只加载文件的前512字节,该字节将是Elf头,没有任何代码或数据
修复此问题的最佳方法是使用多个不同的引导加载程序第一阶段文件—一个用于MBR分区磁盘,一个用于GPT分区磁盘,一个用于PXE/网络引导,一个用于无模拟CD引导。。。用纯汇编语言编写并汇编成平面二进制。所有这些独立的引导加载程序第一阶段都可以找到并加载引导加载程序第二阶段,包括解析Elf头文件,初始化头文件描述的任何.bss部分,从Elf头文件中查找文件的入口点/main,还可能包括切换到受保护模式,这样您就不需要在链接器脚本中进行可怕的黑客攻击来处理实模式,还可能包括启用寻呼以避免另一个您尚未到达的糟糕局面
为什么会出现未定义的引用错误
我不知道;但请检查编译器是否没有向可能需要extern\u main和call\u main的符号添加下划线
我不确定如何设置BSS和堆栈。BSS段是否应该只占用从数据段末尾到引导加载程序512字节末尾的剩余字节?在调用main之前,在哪里设置堆栈指针
我在链接器文件中布局代码和数据段的方式是否与我的保护模式设置兼容?我对这里是什么感到困惑,因为我创建的二进制文件在16位和32位模式下运行
用于从BIOS引导;BIOS不理解Elf可执行文件格式,只加载文件的前512字节,该字节将是Elf头,没有任何代码或数据
修复此问题的最佳方法是使用多个不同的引导加载程序第一阶段文件—一个用于MBR分区磁盘,一个用于GPT分区磁盘,一个用于PXE/网络引导,一个用于无模拟CD引导。。。在纯组装中写入,并组装到扁平箱中
阿瑞。所有这些独立的引导加载程序第一阶段都可以找到并加载引导加载程序第二阶段,包括解析Elf头文件,初始化头文件描述的任何.bss部分,从Elf头文件中查找文件的入口点/main,还可能包括切换到受保护模式,这样您就不需要在链接器脚本中进行可怕的黑客攻击来处理实模式,还可能包括启用分页以避免另一个尚未达到的严重混乱。未定义引用的主要问题是,您生成的ELF可执行文件具有:
gcc -m32 -fno-PIC -ffreestanding -nostdinc -Os -fomit-frame-pointer \
-g -gdwarf -Wall -Werror c/boot.c -o out/boot_c.o
编译boot.c并将其链接到一个名为boot_c.o的完全链接ELF可执行文件。这也是GCC试图从中生成可执行文件时代码需要一个主函数的原因。您缺少编译为对象文件.o的-c选项。应改为:
gcc -c -m32 -fno-PIC -ffreestanding -nostdinc -Os -fomit-frame-pointer \
-g -gdwarf -Wall -Werror c/boot.c -o out/boot_c.o
至于堆栈,在调用main之前设置ESP,并将其指向存在的内存区域。请记住,堆栈将从该点向下扩展到较低的内存。未定义引用的主要问题是,您生成的ELF可执行文件具有:
gcc -m32 -fno-PIC -ffreestanding -nostdinc -Os -fomit-frame-pointer \
-g -gdwarf -Wall -Werror c/boot.c -o out/boot_c.o
编译boot.c并将其链接到一个名为boot_c.o的完全链接ELF可执行文件。这也是GCC试图从中生成可执行文件时代码需要一个主函数的原因。您缺少编译为对象文件.o的-c选项。应改为:
gcc -c -m32 -fno-PIC -ffreestanding -nostdinc -Os -fomit-frame-pointer \
-g -gdwarf -Wall -Werror c/boot.c -o out/boot_c.o
至于堆栈,在调用main之前设置ESP,并将其指向存在的内存区域。请记住,堆栈将从此点向下扩展到较低的内存。不确定x86,但对于ARM 32b或64b,我从不喜欢“调用main”。跳转到“main”是通过直接分支到函数地址来完成的,例如ldr x1,=main_条目,br x1。void main_条目是main.c文件中要调用的函数。BSS类似于:.BSS NOLOAD:ALIGN8{*.BSS*.BSS.*}。{}之间的每个“bss”都在新行上。对于堆栈,您应该在链接器脚本中保留一个特殊的部分,可能在“data”之后,并将该部分末尾的加载地址保留到堆栈指针寄存器sp或用于arm32/64的x30。没有一个答案解决了对main的未定义引用的真正罪魁祸首。这是因为您将c/boot.c编译为名为out/boot_c.o的ELF可执行文件,而不是ELF对象。GCC命令行缺少将.c文件编译为.o对象文件的-c选项。可能您正在这样做,但是一旦您有了ELF可执行文件,您将需要使用类似于objcopy-O binary out/boot.ELF out/boot的东西将其转换为二进制文件。bin@MichaelPetch:Dangit,这么简单的错误,我正在做objcopy部分,但缺少-c开关确实是问题所在。不确定x86,但对于ARM 32b或64b,我从来不喜欢“打电话给梅因”。跳转到“main”是通过直接分支到函数地址来完成的,例如ldr x1,=main_条目,br x1。void main_条目是main.c文件中要调用的函数。BSS类似于:.BSS NOLOAD:ALIGN8{*.BSS*.BSS.*}。{}之间的每个“bss”都在新行上。对于堆栈,您应该在链接器脚本中保留一个特殊的部分,可能在“data”之后,并将该部分末尾的加载地址保留到堆栈指针寄存器sp或用于arm32/64的x30。没有一个答案解决了对main的未定义引用的真正罪魁祸首。这是因为您将c/boot.c编译为名为out/boot_c.o的ELF可执行文件,而不是ELF对象。GCC命令行缺少将.c文件编译为.o对象文件的-c选项。可能您正在这样做,但是一旦您有了ELF可执行文件,您将需要使用类似于objcopy-O binary out/boot.ELF out/boot的东西将其转换为二进制文件。bin@MichaelPetch:该死,这么简单的错误,我正在做objcopy部分,但缺少-c开关确实是问题所在。