这块将介绍一下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. UDP协议网络Socket编程(java实现C/S通信案例)

    我的博客园:https://www.cnblogs.com/chenzhenhong/p/13825286.html 我的CSDN博客:https://blog.csdn.net/Charzous/a ...

  2. day39 Pyhton 并发编程02 后

    一.开启子进程的另一种方式 import os from multiprocessing import Process class MyProcess(Process): def __init__(s ...

  3. python接口测试之日志功能

    之前在简书中看了一篇关于日志功能的文档,供大家参考:https://www.jianshu.com/p/62f7b49b41e7 Python通过logging模块提供日志功能,所以直接导入即可 im ...

  4. 好用的C语言编程软件!工具都没有,怎么用技术改变世界呢!

    好用的C语言编程软件 1.VS(Visual Studio)   VS(Visual Studio) VS是目前最流行的windows平台应用程序的集成开发环境,由于大部分同学使用的都是Windows ...

  5. kafka-消费者测试

    1. 在窗口1创建一个producer,topic为test,broker-list为zookeeper集群ip+端口   /usr/local/kafka/bin/kafka-console-pro ...

  6. 工程化编程实战callback接口学习笔记

    一.编译并运行 help.version命令执行正常,但quit命令出错 二.Debug 从命令输入到执行过程: 源代码: 更改后: 运行结果:能正确运行quit命令 Callback接口学习成果: ...

  7. B站弹幕爬取

    B站弹幕爬取 单个视频弹幕的爬取 ​ B站弹幕都是以xml文件的形式存在的,而xml文件的请求地址是如下形式: http://comment.bilibili.com/233182992.xml ​ ...

  8. C# Timer用法及实例讲解

    摘自:http://www.cnblogs.com/xcsn/archive/2013/05/10/3070485.html 1.C# Timer用法及实例详解 http://developer.51 ...

  9. C#一行代码实现(06)跨线程获取控件值,结合BeginInvoke和EndInvoke使用,以DataGridView为例

    主要功能 跨线程获取控件值,以DataGridView为例,结合BeginInvoke和EndInvoke使用 一行代码 object cellValue = dataGridView.ExInvok ...

  10. 3号随笔,搭建web环境

    创建数据库可能会遇到的问题 数据库语法错误: 如果写错了就会报错. 搭建web项目,我采用了MVC结构搭建 模型层写数据包装,controller层写业务代码,service写数据库内容,创建之后就搭 ...