如何正确使用简单的链接描述文件?可执行文件在运行时获取SIGKILL

时间:2011-08-24 21:19:46

标签: gcc linker linker-scripts

我正在尝试了解更深层次的链接过程和链接器脚本...查看binutils文档我发现了一个简单的链接器脚本实现,我通过添加一些命令进行了改进:

OUTPUT_FORMAT("elf32-i386", "elf32-i386",
          "elf32-i386")
OUTPUT_ARCH(i386)

ENTRY(mymain)

SECTIONS
{
   . = 0x10000;
   .text : { *(.text) }
   . = 0x8000000;
   .data : { *(.data) }
   .bss : { *(.bss) }
}

我的程序是一个非常简单的程序:

void mymain(void)
{
  int a;
  a++;
}

现在我尝试构建一个可执行文件:

gcc -c main.c
ld -o prog -T my_script.lds main.o

但是,如果我尝试运行prog,它会在启动时收到SIGKILL。我知道当程序编译并与命令链接时:

gcc prog.c -o prog

最终的可执行文件也是crt1.ocrti.ocrtn.o等其他对象文件的产品,但我的情况呢?使用此链接描述文件的正确方法是什么?

2 个答案:

答案 0 :(得分:22)

我怀疑你的代码运行得很好,并且在最后遇到了麻烦: {/ 1>}之后你会发生什么?

a++只是一个普通的C函数,它会尝试返回其调用者。

但是你已经将它设置为ELF入口点,它告诉ELF加载器在正确的位置加载程序段后跳转到它 - 并且它不会指望你返回。

那些“其他目标文件,如mymain()crt1.ocrti.o”通常会为C程序处理这些内容。 C程序的ELF入口点不是crtn.o - 相反,它是为main()设置适当环境的包装器(例如设置main()argc堆栈或寄存器中的参数,具体取决于平台),调用argv(期望它可能返回),然后调用main()系统调用(返回代码来自exit })。


[更新以下评论:]

当我使用main()尝试您的示例时,我发现从gdb返回确实失败了:在mymain()上设置断点后,然后逐步执行说明,我看到了它执行增量,然后在函数结尾中遇到麻烦:

mymain

至少对于i386,ELF加载器在输入加载的代码之前设置一个合理的堆栈,因此可以将ELF入口点设置为C函数并获得合理的行为;但是,正如我上面提到的,你必须自己处理一个干净的流程退出。如果你没有使用C运行时,你最好不要使用任何依赖于C运行库的库。

所以这是一个例子,使用原始的链接描述文件 - 但修改了C代码以将$ gcc -g -c main.c $ ld -o prog -T my_script.lds main.o $ gdb ./prog ... (gdb) b mymain Breakpoint 1 at 0x10006: file main.c, line 4. (gdb) r Starting program: /tmp/prog Breakpoint 1, mymain () at main.c:4 4 a++; (gdb) display/i $pc 1: x/i $pc 0x10006 <mymain+6>: addl $0x1,-0x4(%ebp) (gdb) si 5 } 1: x/i $pc 0x1000a <mymain+10>: leave (gdb) si Cannot access memory at address 0x4 (gdb) si 0x00000001 in ?? () 1: x/i $pc Disabling display 1 to avoid infinite recursion. 0x1: Cannot access memory at address 0x1 (gdb) q 初始化为已知值,并调用a系统调用(使用内联汇编)最终值为exit作为退出代码。 (注意:我刚刚意识到你还没有确切地说你正在使用什么平台;我在这里假设Linux。)

a

答案 1 :(得分:2)

是的,要在linux上运行,我们需要更改.lds文件

SECTIONS
{
   . = 0x8048000;
   .text : { *(.text) 
}