使用gcc直接编译为机器代码而无需链接

时间:2018-12-12 07:28:09

标签: c linux gcc x86

我想让gcc将我的C代码编译成x86-32 linux二进制代码,但是周围没有任何库。 我只想在开始处指定一个地址,并且应该假定它已被加载到那里。然后,我将手动从输出中手动构建一个elf文件并设置所有内容。

我知道如何使用NASM来做类似的事情,但是在我不想只使用汇编程序的地方,我有一些更复杂的想法。我不需要任何库,我将在嵌入式asm中使用纯syscall。我也不在乎是否会失去某些可移植性。

我尝试了一下,但是找不到一种方法。 有人不仅可以为我提供正确的设置,还可以为我提供一些有关编译和链接器参数的背景信息? 我尝试搜索gcc手册,但发现它很混乱。

1 个答案:

答案 0 :(得分:4)

  

我想让gcc将我的C代码编译成x86-32 linux二进制代码,但周围没有任何库。

这意味着您编写独立 C代码。 (当标准库可用时,您将拥有一个 hosted 环境;否则,您将拥有一个 freestanding 环境。)

编译例如foo.c转换为可执行文件foo,请确保其具有_start()函数,并使用

gcc -march=i686 -mtune=generic -m32 -ffreestanding -nostdlib -nostartfiles foo.c -o foo

GNU工具链使用_start符号的地址来编码ELF文件中可执行文件的起始地址。

This answer是x86-64的实际示例。对于x86-32(或任何其他体系结构),您需要调整SYSCALL_宏。


在评论中,OP解释说他们想要一个二进制Blob,而不是ELF可执行文件。

在这种情况下,最好告诉编译器生成一个position independent executable。例如,“ blob.c”:

void do_something(int arg)
{
    /* Do something with arg, perhaps a syscall,
       or inline assembly? */
}

void loop_something(int from, int to)
{
    int  arg;

    if (from <= to)
        for (arg = from; arg <= to; arg++)
            do_something(arg);
    else
        for (arg = from; arg <= to; arg--)
            do_something(arg);
}

void _start(void)
{
    loop_something(2, 5);
    do_something(6);
    loop_something(5, 2);
    do_something(1);
}    

我确实建议将_start以外的所有函数都声明为static,以避免任何全局偏移表(GOT)或过程链接表(PLT)引用(例如<__x86.get_pc_thunk.bx>调用)。 / p>

例如使用

将其编译为与位置无关的可执行文件
gcc -march=i686 -mtune=generic -m32 -O2 -fPIE -ffreestanding -nostdlib -nostartfiles blob.c -o blob

将其剥离,

strip --strip-all blob

并转储二进制文件的内容:

objdump -fd blob

在此输出中,有两条重要的线:

start address 0x08048120

告诉_start符号的地址,和

080480e0 <.text>:

以十六进制表示代码的偏移量。从后者减去前者(0x08048120-0x080480e0 = 0x40 = 64)以获取起始符号的偏移量。

最后,使用

将代码转储到原始二进制文件“ blob.raw”中
objcopy -O binary -j .text blob blob.raw