链接器的核心是两个:
符号表
重定位表
工具:
nm file.o
objdump -h a.out
ld --verbose > defaults.lds 查看默认的链接脚本
gcc -fno-builtin -fno-builtin 用于关闭GCC内部函数功能
gcc -v -nostartfiles -o hello hello.o 不链接系统标准启动文件,而标准库文件仍然正常使用
gcc -v -nostdlib -o hello hello.o 不链接系统标准启动文件和标准库文件
ld -static 链接默认是动态链接 ,静态链接需要加上static指定
程序的入口函数是由链接器文件指定或者通过gcc 指定。
ENTRY(hello)
gcc -e
链接脚本如下:
ENTRY(hello) //入口函数不是main()了,是hello
SECTIONS
{
.text 0x00400000+SIZEOF_HEADERS: //指定程序的代码段从0x00400000开始(64位),32位及其为0x08048400开始
{
//必须加上SIZEOF_HEADERS,否者程序将会变得很大。因为代码段占用了ELF文件的文件头,导致 ELF文件的相关信息丢失
*(.text)
*(.rodata)
}
. = 0x01000000;//当然,不能随意指定数据的位置,否者容易引起 segment fault ,还是让程序自己处理
s1 = .; //指定程序中参数s1的地址在0x01000000处
. += 4; //不指定的话就是有系统分配的地址,可以不管
s2 = .; //指定程序中参数s1的地址在0x01000000+4处
.data :
{
*(.data)
}
.bss :
{
*(.bss)
}
/DISCARD/ :
{
*(*) //放弃所有目标文件中除了前面指定的段外的所有其它段
}
}//各个段之间存在对齐的问题
例子:
void printf(const char* s, int l)
{
asm (
"movl $4, %%eax\n" // _write系统函数相关
"movl $1, %%ebx\n"
"movq %0, %%rcx\n" //对于64位机器,由于指针是64位的,所以这里用movq
"movl %1, %%edx\n"
"int $0x80 \n" //通过80H进行interrupt
:
: "r"(s), "r"(l) // printf的参数
: "eax", "ebx", "ecx", "edx" // 保留列表
);
}
void exit(int code)
{
asm (
"movl $1, %%eax\n" //_exit
"movl %0, %%ebx\n"
"int $0x80 \n"
:
: "r"(code)
: "eax", "ebx"
);
}
int hello()
{
printf("D.T.Software\n", 13);
exit(0);
}
入口程序不是main()了,而是hello()函数。
编译,汇编时和普通函数是一样处理,但是当进行连接时,需要用自己编写的链接文件,上面已经给出。
同时,由于程序中用到了printf和exit函数,这个和系统中的printf和exit冲突,如下:
所以,编译时要加上 -fno-builtin 。