【问题标题】:How to correctly use a simple linker script? Executable gets SIGKILL when run如何正确使用简单的链接器脚本?可执行文件在运行时获得 SIGKILL
【发布时间】:2011-11-03 04:31:34
【问题描述】:

我正在尝试了解更深入的链接过程和链接器脚本...查看 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,但我的情况呢?使用此链接器脚本的正确方法是什么?

【问题讨论】:

    标签: gcc linker linker-scripts


    【解决方案1】:

    我怀疑您的代码运行良好,但最终遇到了麻烦:您希望在a++ 之后会发生什么?

    mymain() 只是一个普通的 C 函数,它会尝试返回给它的调用者。

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

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


    [更新以下 cmets:]

    当我使用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 系统调用(使用内联汇编) a 作为退出代码。 (注意:我刚刚意识到您并没有确切说明您使用的是什么平台;我假设这里是 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 调用其他函数之前,例如我认为的 ctors。但是,如果我想为main 取另一个名字,我应该在哪里指定呢?而且,如果我将我的程序与ld 明确链接,我是否还必须传递 c 运行时目标文件?
    • 你想达到什么目的?您想使用普通的 C 运行时和库吗?如果是这样,您的 main 函数必须被称为 main:这就是 C 运行时所调用的。 (这与 ELF 入口点不同,它通常是 crt1.o 中的 _start。)如果您直接调用 ld,那么是的,您必须自己链接各种 C 运行时文件。如果您使用gcc,它会为您执行此操作。您可以看到它对gcc -v 的作用,但您需要知道它通过包装器collect2 调用ld(请参阅here)。
    • 我会知道是否可以构建一个可以正确运行的可执行文件(ELF 格式),而无需将其链接到 c 运行时对象文件。
    • 是的,只要您可以在没有 C 运行时代码为您做的事情的情况下生活。我已经用一个例子更新了我的答案。
    • 是的,我正在使用 Linux...我不知道为什么,但如果我重现您的步骤(使用 gdb 编译、链接和调试)gdb 会说另一件事:During startup program terminated with signal SIGKILL, Killed. 所以我可以'甚至没有开始调试。我正在使用gcc 4.4.5
    【解决方案2】:

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

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

    【讨论】:

      猜你喜欢
      • 2011-07-17
      • 2021-07-12
      • 1970-01-01
      • 2011-02-02
      • 1970-01-01
      • 1970-01-01
      • 2012-06-06
      • 1970-01-01
      • 2013-10-30
      相关资源
      最近更新 更多