binary hacks读数笔记(ld 链接讲解 二)

摘要:
链接基本上是完成四个方面的工作:存储分配、符号管理、库和重新定位。Ld可以标识由LinkercommandLanguage表示的链接脚本文件,以显式控制链接过程。查看ld的版本和简介:ld版本1.首先,看一个程序TinyHelloWorld。c/**小HelloWorld。c*/char*str=“HelloWorld!

这块将介绍一下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!
";

voidprint(){
    asm( "movl $13,%%edx 
	"
        "movl  %0,%%ecx 
	"
        "movl $0,%%ebx 
	"
        "movl $4,%%eax 
	"
        "int $0x80     
	"::"r"(str):"edx","ecx","ebx");
}

voidexit() {
    asm( "movl $42,%ebx 
	"
        "movl $1,%eax 
	"
        "int $0x80    
	");
}

voidnomain() {
    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**0CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .rodata       0000000e  080480d3  080480d3  000000d3  2**0CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .eh_frame     0000007c  080480e4  080480e4  000000e4  2**2CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .data         00000004  0804a000  0804a000  00001000  2**2CONTENTS, ALLOC, LOAD, DATA
  4 .comment      0000002d  00000000  00000000  00001004  2**0CONTENTS, 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**2CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 tinytext      00000052  080480f0  080480f0  000000f0  2**2CONTENTS, ALLOC, LOAD, CODE

免责声明:文章转载自《binary hacks读数笔记(ld 链接讲解 二)》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇linux 文件系统之superblockjQuery快速入门下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

Node.js如何执行cmd

最近,由于业务需求,有必要研究如何根据vscode插件的名称下载相应的插件,以解决打包插件并将其上载到服务器所导致的延迟问题。灵感是件好事。本文主要讨论Node.js如何执行cmd。除了我一开始说的,还有很多应用场景,但只有意想不到的。正如我们的经理所说,现在20%的技术基本上可以解决80%的业务问题。在这个时代,技术有点泛滥,换句话说,技术过剩。...

SqlLite 简明教程

LIMIT运算符LIMIT子句指定要返回的记录数。WHERE表达式LIMITnumberLIKE运算符LIKE操作符用于在WHERE子句的列中搜索指定的模式。SELECT可以是简单的,也可以是复合的。请注意,UNION中的SELECT语句必须具有相同数量的字段。NOT NULL约束强制字段始终包含值。SQLNULL约束NULL值缺少未知数据。SQLPRIMA...

极验验证码破解之selenium

大家好。我是星星在线,我又来了。今天,我给大家带来极性验证码的硒裂解方法。你有点兴奋吗?你们等不及了。让我们直奔主题。首先,随机找到一个特征点,检查元素,看它是否位于div元素,然后查看它后面的位置。距离已确定。以下是移动硒的大量模拟操作。我们只需要确认需要哪些接口。...

oracle 在sql中显示blob的字符串

最近在用oracle的过程中用到了对blob字段模糊查询的问题,对oracle来说,我并不是高手,找了很多的资料终于能够查出来了。以上只是自己做了个简单的处理,相信肯定有更好的方法,希望大家帮忙,但是感觉dbms_lob函数下的方法真的很好用。...

图卷积神经网络(GCN)入门

不得不专门为GCN开一个新篇章,表示其重要程度。图卷积神经网络,实际上跟CNN的作用一样,就是一个特征提取器,只不过它的对象是图数据。总地来说,图数据既要考虑节点信息,也要考虑结构信息,图卷积神经网络就可以自动化地既学习节点特征,又能学习节点与节点之间的关联信息。GCN的本质目的就是用来提取拓扑图的空间特征。理解图卷积神经网络主要有两类,一类是基于空间域或顶...

Java注解

Java注解注解实际就是一种元数据为程序元素设置元数据并且可以对程序执行没有影响。目前Java有5个元注解,他们是:Retention描述注解被保留的时间长短,有三个取值分别是:RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME如果我们想要通过反射获取注解那么应该使用Ret...