在Linux中从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 但是,它没有生成任何

我一直在从头开始为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
但是,它没有生成任何二进制文件来返回这些错误:

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_'