在Linux中从C代码生成原始二进制
我一直在从头开始为x86架构实现一个简单的操作系统。我实现了引导加载程序的汇编代码,引导加载程序从磁盘加载内核并以32位模式进入。加载的内核代码是用C编写的,因此为了执行,我们的想法是从C代码生成原始二进制文件 首先,我使用了以下命令:在Linux中从C代码生成原始二进制,c,linux,gcc,x86,kernel,C,Linux,Gcc,X86,Kernel,我一直在从头开始为x86架构实现一个简单的操作系统。我实现了引导加载程序的汇编代码,引导加载程序从磁盘加载内核并以32位模式进入。加载的内核代码是用C编写的,因此为了执行,我们的想法是从C代码生成原始二进制文件 首先,我使用了以下命令: $gcc -ffreestanding -c kernel.c -o kernel.o -m32 $ld -o kernel.bin -Ttext 0x1000 kernel.o --oformat binary -m elf_i386 但是,它没有生成任何
$gcc -ffreestanding -c kernel.c -o kernel.o -m32
$ld -o kernel.bin -Ttext 0x1000 kernel.o --oformat binary -m elf_i386
但是,它没有生成任何二进制文件来返回这些错误:
kernel.o: In function 'main':
kernel.c:(.text+0xc): undefined reference to '_GLOBAL_OFFSET_TABLE_'
为了清楚起见,kernel.c代码是:
/* kernel.c */
void main ()
{
char *video_memory = (char *) 0xb8000 ;
*video_memory = 'X';
}
然后我学习了本教程:
为我自己的目标实现我自己的交叉编译器。它是为我的目的而工作的,但是使用命令ndisam进行反汇编时,我获得了以下代码:
00000000 55 push ebp
00000001 89E5 mov ebp,esp
00000003 83EC10 sub esp,byte +0x10
00000006 C745FC00800B00 mov dword [ebp-0x4],0xb8000
0000000D 8B45FC mov eax,[ebp-0x4]
00000010 C60058 mov byte [eax],0x58
00000013 90 nop
00000014 C9 leave
00000015 C3 ret
00000016 0000 add [eax],al
00000018 1400 adc al,0x0
0000001A 0000 add [eax],al
0000001C 0000 add [eax],al
0000001E 0000 add [eax],al
00000020 017A52 add [edx+0x52],edi
00000023 0001 add [ecx],al
00000025 7C08 jl 0x2f
00000027 011B add [ebx],ebx
00000029 0C04 or al,0x4
0000002B 0488 add al,0x88
0000002D 0100 add [eax],eax
0000002F 001C00 add [eax+eax],bl
00000032 0000 add [eax],al
00000034 1C00 sbb al,0x0
00000036 0000 add [eax],al
00000038 C8FFFFFF enter 0xffff,0xff
0000003C 16 push ss
0000003D 0000 add [eax],al
0000003F 0000 add [eax],al
00000041 41 inc ecx
00000042 0E push cs
00000043 088502420D05 or [ebp+0x50d4202],al
00000049 52 push edx
0000004A C50C04 lds ecx,[esp+eax]
0000004D 0400 add al,0x0
0000004F 00 db 0x00
如您所见,前9行(除了我不知道为什么插入的NOP)是我的主函数的汇编翻译。从第10行到最后,有很多代码,我不知道为什么会出现在这里
最后,我有两个问题:
1) 为什么会产生这样的代码
2) 有没有一种方法可以从C生成原始机器代码,而不需要那些无用的东西 首先有几点提示:
- 避免命名开始例程
。这是令人困惑的(对于读者和编译器来说都是如此;当您没有将main
传递给-ffreestanding
时,它正在非常明确地处理gcc
)。使用类似于main
或start
myu内核的
begin\u的其他内容
- 使用
进行编译,以了解特定编译器正在执行的操作gcc-v
- 您可能应该向编译器询问一些优化和所有警告,因此至少将
传递给-O-Wall
gcc
- 您可能希望查看生成的汇编程序代码,因此使用
获取gcc-S-O-Wall-fverbose asm kernel.c
汇编程序文件并查看它kernel.S
- 正如您所评论的,您可能希望传递
-fno异常
- 您可能需要一些和/或一些手工编写的汇编程序
- 你应该读一些关于
这闻起来像是和你的工作有关的。我的猜测是:尝试使用 (在某些Linux发行版上,它们的
gcc
可能配置了一些默认启用的-fpic
)
注:如果您想要x86 32位二进制文件,请不要忘记将
-m32
添加到gcc
。您看到的是未启用优化时生成的低效代码。编译C时,可以尝试传递-O3
。生成的代码的第一部分是典型的堆栈帧序言,然后在堆栈上为局部变量分配空间。插入优化选项当然不会生成堆栈帧序言,但是,它仍然会在RET之后生成与主函数不匹配的代码。函数之后的内容可能是异常处理信息。我没有仔细看。它实际上不是代码而是数据。您可以尝试使用GCC使用-fno异常构建,然后看看是什么happens@Olaf,虽然C没有,但GCC通常仍会在对象中创建一个.eh\u frame
部分。我通常使用链接器脚本来丢弃.eh_frame
部分和comment
部分(以及构建说明)。您使用的是什么GCC?我试过你的代码,效果很好-二进制是21字节。同时将main()重命名为_start(),以消除警告。我支持你的意见。在上一篇评论中,我还提供了一个使用OBJCOPY而不是链接器脚本的示例。链接器脚本当然是我的首选,但是给猫剥皮的方法总是不止一种。谢谢你的建议。使用-fno-pic选项,我可以直接使用gcc进行编译,而无需使用我制作的交叉编译器gcc。然而,即使传递选项-fno异常,如果我从二进制文件中反汇编,我在RET之后也会有同样无用的代码。使用@Michael Petch提出的过程,它工作得很好!也感谢you@gyro91:如果你打算长期使用玩具操作系统,我强烈建议你坚持使用交叉编译器。从长远来看,这将为你省去麻烦和悲伤。无用的代码实际上是NDISAM将数据解释为指令的数据,因为在二进制文件中,它无法正确区分代码和集中在一起的数据。@Michael Petch:事实上,我正在使用交叉编译器。我按照你的建议使用了OBJCOPY,效果很好!
kernel.c:(.text+0xc): undefined reference to '_GLOBAL_OFFSET_TABLE_'