这块将介绍一下ld链接命令的具体使用。ld的作用:ld是GNU binutils工具集中的一个,是众多Linkers(链接器)的一种。完成的功能自然也就是链接器的基本功能:把各种目标文件和库文件链接起来,并重定向它们的数据,完成符号解析。链接其实主要就是完成四个方面的工作:storage allocation、symbol management、libraries、relocation。
ld可以识别一种Linker command Language表示的linker scriopt文件来显式的控制链接的过程。通过BFD(Binary Format Description)库,ld可以读取和操作COFF(common object file format)、ELF(executable and linking format)、a.out等各种格式的目标文件。
查看ld的版本以及简单介绍:ld -version

一、首先:看一个程序TinyHelloWorld.c

/*
*TinyHelloWorld.c
*/ char* str = "Hello World!\n"; void print(){
asm( "movl $13,%%edx \n\t"
"movl %0,%%ecx \n\t"
"movl $0,%%ebx \n\t"
"movl $4,%%eax \n\t"
"int $0x80 \n\t"
::"r"(str):"edx","ecx","ebx");
} void exit() {
asm( "movl $42,%ebx \n\t"
"movl $1,%eax \n\t"
"int $0x80 \n\t");
} void nomain() {
print();
exit();
}

1、进行 编译

gcc -c -fno-builtin TinyHelloWorld.c
其中,”-fno-builtin” 用来关闭GCC内置函数(built-in function)优化功能。
但编译出错
Error如下:
TinyHelloWorld.c: Assembler messages:
TinyHelloWorld.c:5: Error: unsupported instruction `mov'

问题原因:
在64位系统下去编译32位的目标文件,这样是非法的。

解决方案:
用”-m32”强制用32位ABI去编译,即可编译通过。

gcc -c -fno-builtin -m32 TinyHelloWorld.c

2、编译完进行链接, 链接报错

ld -static -T TinyHelloWorld.lds -e nomain -o TinyHelloWorld TinyHelloWorld.o

其中:

    “-T TinyHelloWorld.lds”是TinyHelloWorld的链接控制脚本;
    -e 是指定程序入口函数为nomain();
    -static 表示ld是静态链接的方式链接程序,而不是用默认的动态链接方式;
    -o 表示指定输出文件名为”TinyHelloWorld”

Error如下:

ld: i386 architecture of input file `TinyHelloWorld.o' is incompatible with i386:x86-64 output

问题原因:
输入目标文件`TinyHelloWorld.o’是32位系统的,然而我们的平台是64位的(默认链接脚本位于/usr/lib/ldscripts下,x86_64平台默认链接64位可执行文件用的是elf_x86_64.x,链接32位可执行文件用的是elf32_x86_64.x),如果直接ld肯定不匹配,所以需要指定链接脚本与输入目标文件对应的。

解决方案:
链接的时候加上“-m elf_i386”,因为输入目标文件为i386平台。

ld -static -m elf_i386 -T TinyHelloWorld.lds -e nomain -o TinyHelloWorld TinyHelloWorld.o

利用objdump查看TinyHelloWorld 目标文件:

[root@tlinux /]# objdump -h TinyHelloWorld

TinyHelloWorld:     file format elf32-i386

Sections:
Idx Name Size VMA LMA File off Algn
0 .text 0000003f 08048094 08048094 00000094 2**0
CONTENTS, ALLOC, LOAD, READONLY, CODE
1 .rodata 0000000e 080480d3 080480d3 000000d3 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
2 .eh_frame 0000007c 080480e4 080480e4 000000e4 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
3 .data 00000004 0804a000 0804a000 00001000 2**2
CONTENTS, ALLOC, LOAD, DATA
4 .comment 0000002d 00000000 00000000 00001004 2**0
CONTENTS, READONLY

二、上述链接过程是用的系统默认链接脚本,可以由ld -verbose查看默认链接脚本,接下来对脚本进行简单lds连接脚本进行介绍说明:

开头:

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

OUTPUT_FORMAT 和 OUTPUT_ARCH 都是 ld 脚本的保留字命令。OUTPUT_FORMAT 说明输出二进制文件的格式。OUTPUT_ARCH 说明输出文件系统平台。

ENTRY(_start)
            ENTRY 命令的作用是,将后面括号中的符号值设置成入口地址。入口地址(entry point)的定义是这样的──进程执行的第一条用户空间的指令在进程地址空间中的地址。ld 有多种方法设置进程入口地址,通常它按以下顺序:(编号越前, 优先级越高)
                   1, ld 命令行的-e选项
                   2, 连接脚本的 ENTRY(SYMBOL) 命令
                   3, 如果定义了 start 符号, 使用 start 符号值
                   4, 如果存在 .text section, 使用 .text section 的第一字节的位置值
                   5, 使用值 0
SEARCH_DIR("/usr/i486-linux-gnu/lib32"):    设置链接时搜寻库文件目录.

接下来是一大段的 SECTIONS,对应的右大括号直到脚本的末尾。
SECTIONS 命令告诉 ld 如何把输入文件的 sections 映射到输出文件的各个 section:即是如何将输入 section 合为输出 section;如何把输出 section 放入程序地址空间 (VMA) 和进程地址空间 (LMA) 。

该命令格式如下:
SECTIONS
{
….
}

PROVIDE (__executable_start = SEGMENT_START(“text-segment”, 0x08048000)):PROVIDE 定义的变量 如果源文件中已经定义值 那么用源文件中的,如果没有定义则用脚本中定义的。并设定该变量的值为0x08048000
           

. = SEGMENT_START(“text-segment”, 0x08048000) + SIZEOF_HEADERS:这句把定位器符号置为 0x08048000 + SIZE_HEADERS(若不指定,则该符号的初始值为 0)。SIZE_HEADERS为输出文件的文件头
            .

. 是一个特殊的符号,它是定位器,一个位置指针,指向程序地址空间内的某位置(或某section内的偏移,如果它在SECTIONS命令内的某section描述内),该符号只能在SECTIONS命令内使用。

.rel.dyn        :
{
*(.rel.init)
*(.rel.text .rel.text.* .rel.gnu.linkonce.t.*)
*(.rel.fini)
*(.rel.rodata .rel.rodata.* .rel.gnu.linkonce.r.*)
*(.rel.data.rel.ro* .rel.gnu.linkonce.d.rel.ro.*)
*(.rel.data .rel.data.* .rel.gnu.linkonce.d.*)
*(.rel.tdata .rel.tdata.* .rel.gnu.linkonce.td.*)
*(.rel.tbss .rel.tbss.* .rel.gnu.linkonce.tb.*)
*(.rel.ctors)
*(.rel.dtors)
*(.rel.got)
*(.rel.bss .rel.bss.* .rel.gnu.linkonce.b.*)
*(.rel.ifunc)
}
.rel.plt :
{
*(.rel.plt)
PROVIDE_HIDDEN (__rel_iplt_start = .);
*(.rel.iplt)
PROVIDE_HIDDEN (__rel_iplt_end = .);
}

以上这些段主要用于重定位.

 .init           :
{
KEEP (*(.init))
} =0x90909090
.plt : { *(.plt) *(.iplt) }
.text :
{
*(.text.unlikely .text.*_unlikely)
*(.text .stub .text.* .gnu.linkonce.t.*)
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
} =0x90909090

.init将在下文与.fini一起介绍.

.text : 表示text段开始.
*(.text) 将所有(*符号代表任意输入文件)输入文件的.text section合并成一个.text section, 该section的地址由定位器符号的值指定, 即0x08048000.

*(.text.unlikely .text.*_unlikely)
*(.text .stub .text.* .gnu.linkonce.t.*)
/* .gnu.warning sections are handled specially by elf32.em. */
*(.gnu.warning)
.fini :
{
KEEP (*(.fini)) #

KEEP()强制连接器保留一些特定的section

ELF文件中定义了 .init 和 .fini 两个特殊的段,其中 .init 段中的代码会在main之前被执行,.fini 段中的代码会在main退出之后被执行.默认用NOP(0x90)字段进行填充.

CONSTRUCTORS 是一个保留字命令。与 c++ 内的(全局对象的)构造函数和(全局对像的)析构函数相关。

 .ctors          :
{
/* gcc uses crtbegin.o to find the start of
the constructors, so we make sure it is
first. Because this is a wildcard, it
doesn't matter if the user does not
actually link against crtbegin.o; the
linker won't look for a file to match a
wildcard. The wildcard also means that it
doesn't matter which directory crtbegin.o
is in. */
KEEP (*crtbegin.o(.ctors))
KEEP (*crtbegin?.o(.ctors))
/* We don't want to include the .ctor section from
the crtend.o file until after the sorted ctors.
The .ctor section from the crtend file contains the
end of ctors marker and it must be last */
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
}
.dtors :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*crtbegin?.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
}

对于支持任意section名的目标文件格式,比如COFF、ELF格式,GNU C++将全局构造和全局析构信息分别放入 .ctors section 和 .dtors section 内

当连接器生成的目标文件格式不支持任意section名字时,比如说ECOFF、XCOFF格式,连接器将通过名字来识别全局构造和全局析构,对于这些文件格式,连接器把与全局构造和全局析构的相关信息放入出现 CONSTRUCTORS 关键字的输出section内。

/DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) (.gnu.lto_) }
DISCARD关键字用于将指定段舍弃,不出现在输出文件中.

三、接下来,我们利用自定义链接脚本进行链接

TinyHelloWorld.lds脚本

 ENTRY(nomain)

 SECTIONS
{
. = 0x08048000 + SIZEOF_HEADERS;
tinytext : { *(.text) *(.data) *(.rodata) }
/DISCARD/ : { *(.comment) } }

利用上述链接脚本进行链接,将代码段、数据段合成tinytext段输出:

 ld -static -T TinyHelloWorld.lds -m elf_i386 -o TinyHelloWorld TinyHelloWorld.o

利用objdump命令查看是否符合我们预期:

[root@tlinux /]# objdump -h TinyHelloWorld

TinyHelloWorld:     file format elf32-i386

Sections:
Idx Name Size VMA LMA File off Algn
0 .eh_frame 0000007c 08048074 08048074 00000074 2**2
CONTENTS, ALLOC, LOAD, READONLY, DATA
1 tinytext 00000052 080480f0 080480f0 000000f0 2**2
CONTENTS, ALLOC, LOAD, CODE

binary hacks读数笔记(ld 链接讲解 二)的更多相关文章

  1. binary hacks读数笔记(ld 链接讲解 一)

    首先我们先看两段代码: a.c extern int shared; int main(){ int a=100; swap(&a,&shared); } b.c int shared ...

  2. binary hacks读数笔记(堆、栈 VMA的分布)

    一.首先看一个简单的程序: #include<stdlib.h> int main() { while(1) { sleep(1000); } return 0; } gcc -stati ...

  3. binary hacks读数笔记(readelf基本命令)

    一.首先对readelf常用的参数进行简单说明: readelf命令是Linux下的分析ELF文件的命令,这个命令在分析ELF文件格式时非常有用,下面以ELF格式可执行文件test为例详细介绍: 1. ...

  4. binary hacks读数笔记(共享库)

    共享库从文件结构上来讲,与共享对象没什么区别.Linux下,共享库就是普通的ELF共享对象. 1.共享库命名: libname.so.x.y.z :其中最前面使用前缀lib,中间是库的名字和后缀&qu ...

  5. binary hacks读数笔记(装载)

    1.地址空间 在linux系统中,每个进程拥有自己独立的虚拟地址空间,这个虚拟地址空间的大小是由计算机硬件决定的,具体地说,是由CPU的位数决定的.比如,32位硬件平台决定的虚拟地址空间大小:0--2 ...

  6. binary hacks读数笔记(nm命令)

    nm命令(names):输出包含三个部分:1 符号值.默认显示十六进制,也可以指定: 2 符号类型.小写表示是本地符号,大写表示全局符号(external); 3 符号名称. 例如:nm Simple ...

  7. binary hacks读数笔记(objdump命令)

    一.首先看一下几个常用参数的基本含义: objdump命令是Linux下的反汇编目标文件或者可执行文件的命令,它还有其他作用,下面以ELF格式可执行文件test为例详细介绍: 1.objdump -f ...

  8. binary hacks读数笔记(file命令与magic file)

    file命令的作用是用于检验文件的类型,并打印至终端.file命令检验文件类型按以下顺序来完成: 检验文件系统(Filesystem)中支持的文件类型. 检验magic file规则. 检验文件内容的 ...

  9. binary hacks读数笔记(dlopen、dlsym、dlerror、dlclose)

    1.dlopen是一个强大的库函数.该函数将打开一个动态库,并把它装入内存.该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的.比如 Apache Web 服务器利用这个函数在运行过程中加载 ...

随机推荐

  1. Markdown语法及使用方法完整手册

    欢迎使用 Markdown在线编辑器 MdEditor Markdown是一种轻量级的「标记语言」 Markdown是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容 ...

  2. pytest+allure生成接口自动化测试报告

    准备环境 1.安装pytest pip install pytest -i http://pypi.douban.com/simple pytest-ordering pytest.main([ &q ...

  3. SQL 查询当天,本月,本周的记录 sql 查询日期

    SELECT * FROM 表 WHERE CONVERT(Nvarchar, dateandtime, 111) = CONVERT(Nvarchar, GETDATE(), 111)   ORDE ...

  4. 判断是否是胖子的shell脚本

    read -p "请输入身高(m为单位): " HIGH if [[ ! "$HIGH" =~ [1].?[0-9]{,2}$ ]];then echo &qu ...

  5. R语言学习-(金融数据获取和简单的分析)

    利用R语言中的quantmod包和fBasics对股票数据的获取和简要的分析, 通过获取的数据进行典型图像绘制,使用JB正态性检验来检验是否服从于正态分布. 前提概要:quantmod 包默认是访问 ...

  6. F. Moving Points 解析(思維、離散化、BIT、前綴和)

    Codeforce 1311 F. Moving Points 解析(思維.離散化.BIT.前綴和) 今天我們來看看CF1311F 題目連結 題目 略,請直接看原題. 前言 最近寫1900的題目更容易 ...

  7. Rancher 2.5特性解读丨更简单友好的API和Dashboard

    本文来自Rancher Labs 关注我们,看K8S干货教程 作者简介 张智博,Rancher中国研发与产品总监.7年云计算领域经验,一直活跃在研发一线,经历了OpenStack到Kubernetes ...

  8. Kubernetes 使用 Ingress 实现灰度发布功能

    使用 Ingress 实现灰度发布 一.Canary 规则说明 Ingress-Nginx 是一个K8S ingress工具,支持配置 Ingress Annotations 来实现不同场景下的灰度发 ...

  9. NB-IoT技术适合在哪些场景应用

    LPWAN,Low Power Wide Area Network,低功耗广域网.名字里就有它的两个最重要的特点:低功耗.广覆盖.目前比较主流的有:NB-IoT.LoRa.Sigfox.eMTC.NB ...

  10. PuTTY SSH 使用证书免密码登录

    1.用PuTTY SSH 密钥生成工具puttygen.exe生成密钥.生成的密钥类型和位数按照默认的就OK,SSH-2 RSA,1024位生成密钥时你需要在空白区域移动鼠标,以便产生随机数据点击保存 ...