如何正确使用简单的链接脚本?运行可执行文件时出现SIGKILL。

21

我正在尝试理解深层链接过程和链接器脚本...查看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个回答

23
我猜你的代码运行得很好,但在结尾处出了问题:在a++之后,你期望发生什么? mymain()只是一个普通的C函数,它将尝试返回给它的调用者。
但你将其设置为ELF入口点,这告诉ELF加载程序段到正确位置后跳转到该函数 - 它不希望你返回。
crt1.ocrti.ocrtn.o这样的“其他目标文件”通常为C程序处理这些内容。C程序的ELF入口点不是main(),而是一个包装器,为main()设置适当的环境(例如,在堆栈或寄存器中设置argcargv参数),调用main()(并期望它可能返回),然后调用exit系统调用(使用main()的返回代码)。
[根据评论更新:] 当我使用gdb尝试你的示例时,我确实看到它在从mymain()返回时失败:在mymain上设置断点,然后逐步执行指令,我看到它执行增量,然后在函数收场部分出了问题:
$ 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

至少对于i386架构,ELF加载器在进入加载的代码之前会设置一个合理的堆栈,因此您可以将ELF入口点设置为C函数并获得合理的行为;然而,正如我上面提到的,您必须自己处理干净的进程退出。如果您没有使用C运行时,最好也不要使用依赖于C运行时的任何库。
下面是一个示例,使用您的原始链接器脚本-但是将C代码修改为将a初始化为已知值,并使用最终值作为退出代码调用exit系统调用(使用内联汇编)。 (注意:我刚意识到您没有说您使用的确切平台;我在这里假设Linux。)
$ cat main2.c
void mymain(void)
{
  int a = 42;
  a++;
  asm volatile("mov $1,%%eax; mov %0,%%ebx; int $0x80" : : "r"(a) : "%eax" );
}
$ gcc -c main2.c
$ ld -o prog2 -T my_script.lds main2.o
$ ./prog2 ; echo $?
43
$ 

感谢Matthew的回答。我说程序在启动时崩溃是因为gdb显示了这个...我知道在main之前会调用其他函数,例如构造函数。但是如果我想要另一个名称的main,我应该在哪里指定?而且,如果我使用ld显式链接我的程序,我必须传递C运行时对象文件吗? - MirkoBanchi
你想要实现什么?你想使用普通的C运行时和库吗?如果是这样,你的主函数必须被命名为main:这就是C运行时调用的函数。(这与ELF入口点不同,通常在crt1.o中为_start。)如果你直接调用ld,那么是的,你必须自己链接各种C运行时文件。如果你使用gcc,它会为你完成这个过程。你可以通过gcc -v查看它的操作,但你需要知道它通过一个包装器collect2来调用ld(参见这里)。 - Matthew Slattery
是的,只要你能够在没有C运行时代码为你完成的事情的情况下生存下去。我已经更新了我的答案,并提供了一个示例。 - Matthew Slattery
好的,我会调查一下!非常感谢你,马修。 - MirkoBanchi
@MirkoBanchi 我也遇到了同样的问题。看起来 ELF 文件生成不正确。我使用了一个小应用程序进行修复(不确定是否有更简单的方法)。更多关于我所做的事情的信息,请参见此处 - bjxt
显示剩余3条评论

2

要在Linux上运行,我们需要更改.lds文件。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接