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

发布于 2024-12-01 04:07:59 字数 757 浏览 0 评论 0原文

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

I'm trying to understand deeper linking process and linker scripts...looking at binutils doc i found a simple linker script implementation that i've improved by adding some commands:

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

ENTRY(mymain)

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

My program is a very simple program:

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

Now i tried to build an executable:

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

But if i try to run prog it receives a SIGKILL during startup. I know that when a program is compiled and linked with the command:

gcc prog.c -o prog

the final executable is the product also of other object files like crt1.o, crti.o and crtn.o but what about my case? Which is the correct way to use this linker scripts?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

浅听莫相离 2024-12-08 04:07:59

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

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

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

那些“其他目标文件,如 crt1.ocrti.ocrtn.o”通常为 C 程序处理这些内容。 C 程序的 ELF 入口点不是 main() - 相反,它是一个包装器,为 main() 设置适当的环境(例如设置 < code>argc 和 argv 参数位于堆栈或寄存器中,具体取决于平台),调用 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 系统调用(使用内联汇编),最终值 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
$ 

I suspect that your code is running just fine, and getting into trouble at the end: what do you expect to happen after the a++?

mymain() is just an ordinary C function, which will try to return to its caller.

But you've set it as the ELF entry point, which tells the ELF loader to jump to it once it has loaded the program segments in the right place - and it doesn't expect you to return.

Those "other object files like crt1.o, crti.o and crtn.o" normally handle this stuff for C programs. The ELF entry point for a C program isn't main() - instead, it's a wrapper which sets up an appropriate environment for main() (e.g. setting up the argc and argv arguments on the stack or in registers, depending on platform), calls main() (with the expectation that it may return), and then invokes the exit system call (with the return code from main()).


[Update following comments:]

When I try your example with gdb, I see that it does indeed fail on returning from mymain(): after setting a breakpoint on mymain, and then stepping through instructions, I see that it performs the increment, then gets into trouble in the function epilogue:

$ 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

For i386 at least, the ELF loader sets up a sensible stack before entering the loaded code, so you can set the ELF entry point to a C function and get reasonable behaviour; however, as I mentioned above, you have to handle a clean process exit yourself. And if you're not using the C runtime, you'd better not be using any libraries that depend on the C runtime either.

So here is an example of that, using your original linker script - but with the C code modified to initialise a to a known value, and invoke an exit system call (using inline assembly) with the final value of a as the exit code. (Note: I've just realised that you haven't said exactly what platform you're using; I'm assuming Linux here.)

$ 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
$ 
灵芸 2024-12-08 04:07:59

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

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

yes to run on linux, we need to change .lds file

SECTIONS
{
   . = 0x8048000;
   .text : { *(.text) 
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文