旅游找什么网站好,设计企业网络方案的五个步骤,互联网营销师题库,机械设计师网课前序文章请看#xff1a; 从裸机启动开始运行一个C程序#xff08;十三#xff09; 从裸机启动开始运行一个C程序#xff08;十二#xff09; 从裸机启动开始运行一个C程序#xff08;十一#xff09; 从裸机启动开始运行一个C程序#xff08;十#xff09; 从裸机启动…前序文章请看 从裸机启动开始运行一个C程序十三 从裸机启动开始运行一个C程序十二 从裸机启动开始运行一个C程序十一 从裸机启动开始运行一个C程序十 从裸机启动开始运行一个C程序九 从裸机启动开始运行一个C程序八 从裸机启动开始运行一个C程序七 从裸机启动开始运行一个C程序六 从裸机启动开始运行一个C程序五 从裸机启动开始运行一个C程序四 从裸机启动开始运行一个C程序三 从裸机启动开始运行一个C程序二 从裸机启动开始运行一个C程序一
加载64位指令
前一节我们已经成功进入了IA-32e模式但是却意料之外地体验了一把在IA-32e模式上运行IA-32指令的兼容模式。
前面我们也看到了IA-32e架构下的硬件扩展方式比如说寄存器都是在原本基础上扩展的所以他可以通过只用低32位寄存器的方式运行IA-32的指令以此实现高度兼容。
因此这里的秘密就是在段配置的一个保留位上咱们前面讨论过段描述符的第54和55位是保留位因为在IA-32模式下不会去解析这两位。但是IA-32e模式下就利用了其中的第55位用来表示该段是32位模式还是64位模式当它为1时CPU将会用64位指令来解析。
但是前面分好的代码段我们不能动毕竟内核这里还有一段32位的代码要执行。所以我们就再分一个64位指令的段。不过64位的段有一个要求就是「强制平坦」也就是说Base配置是无效的强制按照0x0作为首地址。原因也很简单因为IA-32e模式强制要求分页所以他希望操作系统用这种更先进的方式来管理内存因而分段这里就要求强制平坦。
注意我们说的强制平坦是仅当第55位置1的情况才会强制平坦如果这一位是0那么向下兼容IA-32模式的话段基址是有效的。
64位段配置如下
; 5号段-64位代码段
; 基址0x0000上限0xfffff
mov [es:0x28], word 0x00ff ; Limit0x00ff这是低16位
mov [es:0x2a], word 0x0000 ; Base(无效)低16位
mov [es:0x2c], byte 0x0000 ; Base(无效)16~23位
mov [es:0x2d], byte 1_00_1_101_0b ; P1, DPL0, S1, Type001b, A0
mov [es:0x2e], byte 1_0_1_0_0000b ; G1, D/B0, L1(开启64位模式), AVL0, Limit的高4位是0000
mov [es:0x2f], byte 0x00 ; Base(无效)高8位这样当我们把CS设置成5号段的时候就可以执行64位指令了。为此咱们在kernel中添加一个64位指令操作一下R8寄存器来验证是否能正常执行
; 进入IA-32e模式
; 刷新cs以进入64位指令模式
jmp 00101_00_0b:ent64 0x8000 ; 注意这里平坦模式下要从0x0计算偏移量[bits 64]
ent64:mov r8, 0x12345678911hlt通过调试指令可以观察这一句的执行情况
没问题我们成功在IA-32e模式下运行了64位指令并且给64位寄存器赋了值。
到此的项目代码将会放在附件14-1中供读者参考。
改造剩余内核代码
既然我们成功进入了64位模式那么将剩下的代码改用64位编译模式就可以链接到当前的内核中这样我们就可以执行原本编写的C程序了。
C程序的源码是都不用改变的我们只需要通过调整参数让编译期按照64位的方式来编译就好了。不过有两个个地方是需要我们来管的就是asm_func.nas因为这个文件是用汇编写的所以我们需要改造成64位指令。另一个地方是进入entry函数之前有一些段和栈的配置需要改造。接下来我们一个一个来
asm_func的改造
要改造的点有4处
压栈弹栈时要匹配栈的位宽因此要改成64位寄存器。在64位模式下由于段已经强制平坦了因此不再允许用es加偏移来操作内存只能用默认的ds因此我们要把其中使用es的部分改成ds。因为64位模式强制平坦所以原本的2号段无法使用了我们得配一个新的数据段与其对齐。对应显存的地址也要改变。在32位模式下C语言规范传参方式都是通过压栈的因此我们用[rsp 12]的方法找参数。但是64位模式下由于通用寄存器数量增加为了更高效会优先采用寄存器传参的方式。对于6个寄存器以下的情况会按照rdi, rsi, rdx, rcx, r8, r9的顺序来传参当大于6个时才会采用压栈的方式。所以读参方式要改造。
MBR的段配置处加一个6号段
; 6号段-64位数据段
; 强制平坦模式基址无效上限0xffffffff
mov [es:0x30], word 0xffff ; Limit0xffff这是低16位
mov [es:0x32], word 0x0000 ; Base无效
mov [es:0x34], byte 0x0000 ; Base无效
mov [es:0x35], byte 1_00_1_001_0b ; P1, DPL0, S1, Type001b, A0
mov [es:0x36], byte 1_0_1_0_0000b ; G1, D/B0, L1, AVL0, Limit的高4位是0000
mov [es:0x37], byte 0x00 ; Base无效; 下面是gdt信息的配置暂且放在0x07f00的位置
mov ax, 0x07f0
mov es, ax
mov [es:0x00], word 55 ; 因为目前配了7个段长度为56所以limit为55
mov [es:0x02], dword 0x7e00 ; GDT配置表的首地址
; 把gdt配置进gdtr
lgdt [es:0x00]asm_func改造后的代码如下
[bits 64]
section .text global SetVMem ; 告诉链接器下面这个标签是外部可用的
SetVMem:; 现场记录push rbpmov rbp, rsp; 过程中用到的寄存器都要先记录push rbxpush rcxpush rdx; 64位模式下不允许通过es偏移所以只能设置dsmov bx, ds ; 用bx记录原本的ds用于后续恢复现场这里是因为寄存器还够用如果不够用的话就还是要压栈; 把es配成数据mov dx, 00110_00_0bmov ds, dx; 通过参数找到addr和data64位优先用寄存器传参mov rdx, rdi ; addrmov rcx, rsi ; data; 通过偏移地址来操作显存0xa0000是显存基址mov [rdx0xa0000], cl ; 由于data是1字节的所以其实只有cl是有效数据; 现场还原mov ds, bxpop rdxpop rcxpop rbxmov rsp, rbppop rbp; 回跳ret
kernel的改造
在kernel.nas中进入entry函数之前我们要做段寄存器的配置所以我们把ds、es和ss都配置为平坦模式的数据段也就是6号段代码如下
mov ax, 00110_00_0b ; 选择6号段数据段
mov ss, ax
; ds要跟ss一致
mov ds, ax
; es也初始化为数据段防止后续出问题先初始化
mov es, ax; 初始化栈
mov rax, 0x1000
mov rsp, rax ; 设置初始栈顶
mov rbp, rax ; ebp也记录初始栈顶extern Entry
call Entryhlt配置参数改造
接下来就是通过调整参数把这些.nas和.c通过64位方式编译并链接起来。
C编译参数要用-m64 -marchx86-64来生成64位的.o文件nas的编译参数要用-f elf64来生成64位.o文件。
链接时也要用-m elf_x86_64参数而且要注意一个严重问题由于现在分段是平坦模式了所以程序加载的内存地址不再是0的偏移量而是0x8000所以链接参数要做调整-Ttext0x8000。
最后objcopy时也要制定参数elf64-x86-64要注意这个指令的参数是用中划线而不是下划线跟前两个指令要区分开。
完整的kernel的makefile如下
.PHONY: all
all: kernel_final.binkernel.o: kernel.nasnasm kernel.nas -f elf64 -o kernel.oentry.o: entry.c ../libc/include/stdio.h
# 需要用-I制定头文件扫描位置x86_64-elf-gcc -c -m64 -marchx86-64 -fno-builtin -I../libc/include entry.c -o entry.o -Wall -Werror -Wextra../libc/libc.a:pushd ../libc $(MAKE) clean $(MAKE) libc.a popdkernel_final.out: kernel.o entry.o ../libc/libc.a
# 需要用-L指定静态链接库位置
# -lc表示链接libc.a
# 注意kernel.o要放在第一个x86_64-elf-ld -m elf_x86_64 -Ttext0x8000 kernel.o entry.o -L../libc -lc -o kernel_final.outkernel_final.bin: kernel_final.outx86_64-elf-objcopy -I elf64-x86-64 -S -R .eh_frame -R .comment -O binary kernel_final.out kernel_final.bin.PHONY: clean
clean:-rm -f .DS_Store-rm -f *.bin -rm -f *.o-rm -f *.out同理调整libc的配置文件如下
.PHONY: all
all: libc.afont.o: font.cx86_64-elf-gcc -c -m64 -marchx86-64 -fno-builtin font.c -o font.ostdio.o: stdio.c include/stdio.hx86_64-elf-gcc -c -m64 -marchx86-64 -fno-builtin stdio.c -o stdio.ostring.o: string.c include/string.hx86_64-elf-gcc -c -m64 -marchx86-64 -fno-builtin string.c -o string.oasm_func.o: asm_func.nasnasm asm_func.nas -f elf64 -o asm_func.olibc.a: asm_func.o stdio.o string.o font.o
# $^表示所有依赖文件
# ar是制作静态链接库的工具x86_64-elf-ar -crv --targetelf64-x86-64 libc.a $^.PHONY: clean
clean:-rm -f *.o libc.a看一眼64位改造后的成果
都改造完毕后就可以尝试运行了这是我们第一次在64位模式下运行完整的程序
完美该部分的项目源码将会放在附件14-2中供读者参考。
加一个C程序
终于我们到了邀请最终大咖登场的环节了。既然64位C语言程序已经可以正常运行那么同理我们把C代码编译成elf64格式的文件链接到Kernel中照理说就大功告成了。
因此我们先在工程中建立一个main.cpp然后在makefile中编写对应的构建命令
main.o: main.cppx86_64-elf-g -c -stdc17 -m64 -marchx86-64 -fno-builtin -I../libc/include main.cpp -o main.o -Wall -Werror -Wextra# 注意链接的时候要加上main.o
kernel_final.out: kernel.o entry.o main.o ../libc/libc.a x86_64-elf-ld -m elf_x86_64 -Ttext0x8000 kernel.o entry.o main.o -L../libc -lc -o kernel_final.out这里用来编译C代码的指令是x86_64-elf-g这里我们指定C17标准其余参与跟C语言的entry.c相同不再赘述。
然后我们在main.cpp中实现main函数但是有一点要注意因为程序实际的入口是Entry所以需要在Entry中调用main函数。不过既然已经有了这一步调用我们索性就把函数返回值打印出来代码如下
void Entry() {// 背景设置为白色SetBackground(0x0f);extern int main();int ret main();printf(main() returned by: %d, ret);
}接下来我们来实现main函数。有一点需要注意的是由于C是支持函数重载的所以参与链接的函数符号并不仅仅是函数名还包含了参数信息。这种构建方式是C语言不支持的因此我们想在entry.c中调用main.cpp中的main函数还需要对这个函数进行额外的声明告诉编译器采用原始C的方式做链接符号。
声明的方法是使用extern C关键字。需要知晓的是用C方式编译的函数不再支持重载但可以和C语言源码链接上
extern C
int main() {return 0;
}好了运行一下看看效果吧
大功告成我们实现了「从裸机启动开始运行一个C程序」的任务撒花~~
……
真的大功告成了吗哈哈当然没那么简单C不像C那么纯粹它存在很多隐含的动作只是因为目前main函数过于简单我们还没有踩任何坑而已。
因此我们不能过于激动还是要沉下心来继续进行一段修炼。 不过不用操之过急可以先享受片刻胜利的喜悦下一章我们再来看看上了C之后会碰到哪些问题。
到此的项目源码会放入附件14-3中供读者参考。
小结
这一篇我们介绍了如何在IA-32e模式中运行64位指令还介绍了如何把C语言编译成64位指令以及配套的asm_func如何改造。最后成功把C程序加入了项目中。
本篇的所有项目源码将会放在附件demo_code_14中供读者参考。