32位OS开发:切换保护模式后无法调用C函数kmain求助
看起来你已经成功闯过了进入保护模式的关卡,但内核调用这块卡壳了,咱们一步步拆解问题,定位根源:
1. 链接器格式错误(最核心的问题)
你当前用ld -m i386pe链接内核,这是Windows平台的PE可执行文件格式——这种格式会在二进制头部插入额外的PE结构信息,导致你的实际代码根本不是从0x100000开始的!PE头会占用一部分空间,kernel_entry.asm的代码被挤到了0x100000 + PE头大小的位置,你从0x100000调用的话,执行的是PE头里的垃圾数据,自然会直接崩溃退出。
解决方案:改用适合裸机内核的纯二进制格式链接,把链接命令替换成:
ld -m elf_i386 -Ttext 0x100000 --oformat binary kernel_entry.o kernel.o -o kernel.bin
-m elf_i386:指定针对32位x86的ELF目标文件-Ttext 0x100000:直接将代码段起始地址锁定为0x100000--oformat binary:输出纯二进制文件,无任何额外头部,确保代码精准落在你预期的内存地址上
2. 符号名与链接地址验证
虽然你的kernel_entry.asm里用了[extern _kmain](gcc默认会给C函数名加下划线,比如kmain变成_kmain,这点是对的),但可以用nm工具验证符号地址是否正确:
nm kernel.o
你应该能看到_kmain的地址是0x1000XX(相对于0x100000的偏移),确保链接后的符号位置符合预期。
3. 内核加载扇区数是否足够
你用dh=15加载15个扇区,总容量是15*512=7680字节,要确保你的kernel.bin大小不超过这个值。可以用dir kernel.bin(Windows)或ls -l kernel.bin(Linux)查看文件大小,如果超过了,把dh改成更大的数值(比如20)。
4. 保护模式调用逻辑修正
在boot_sect.asm的BEGIN_PM段里,你直接call KERNEL_OFFSET的逻辑是对的,但只有当kernel.bin开头就是kernel_entry.asm的代码时才会生效——换成上面的链接命令后,kernel.bin的第一个字节就是call _kmain指令,调用就能正确执行。
5. 无限循环测试验证
修改你的kmain函数,加上无限循环:
void kmain() { char* screen = (char*)0xb8000; screen[0] = 'X'; while(1); // 无限循环挂起程序 }
如果链接正确,调用后程序会稳定挂在这个循环里,不会退出,这就说明地址问题已经解决了。
额外小提示
Windows的type命令合并文件时,偶尔会在文件之间插入额外空字符,建议用二进制模式合并:
copy /b boot_sect.bin + kernel.bin os-image
/b参数会确保以二进制方式合并,避免额外字符干扰。
内容的提问来源于stack exchange,提问作者Kaan99__

