我正在尝试了解更深层次的链接过程和链接器脚本...查看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.o
,crti.o
和crtn.o
等其他对象文件的产品,但我的情况呢?使用此链接描述文件的正确方法是什么?
答案 0 :(得分:22)
我怀疑你的代码运行得很好,并且在最后遇到了麻烦: {/ 1>}之后你会发生什么?
a++
只是一个普通的C函数,它会尝试返回其调用者。
但是你已经将它设置为ELF入口点,它告诉ELF加载器在正确的位置加载程序段后跳转到它 - 并且它不会指望你返回。
那些“其他目标文件,如mymain()
,crt1.o
和crti.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)
}