鏈接器的核心是兩個:
符號表
重定位表
工具:
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 。