binary hacks读数笔记(ld 链接讲解 二)
这块将介绍一下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 链接讲解 二)的更多相关文章
- binary hacks读数笔记(ld 链接讲解 一)
首先我们先看两段代码: a.c extern int shared; int main(){ int a=100; swap(&a,&shared); } b.c int shared ...
- binary hacks读数笔记(堆、栈 VMA的分布)
一.首先看一个简单的程序: #include<stdlib.h> int main() { while(1) { sleep(1000); } return 0; } gcc -stati ...
- binary hacks读数笔记(readelf基本命令)
一.首先对readelf常用的参数进行简单说明: readelf命令是Linux下的分析ELF文件的命令,这个命令在分析ELF文件格式时非常有用,下面以ELF格式可执行文件test为例详细介绍: 1. ...
- binary hacks读数笔记(共享库)
共享库从文件结构上来讲,与共享对象没什么区别.Linux下,共享库就是普通的ELF共享对象. 1.共享库命名: libname.so.x.y.z :其中最前面使用前缀lib,中间是库的名字和后缀&qu ...
- binary hacks读数笔记(装载)
1.地址空间 在linux系统中,每个进程拥有自己独立的虚拟地址空间,这个虚拟地址空间的大小是由计算机硬件决定的,具体地说,是由CPU的位数决定的.比如,32位硬件平台决定的虚拟地址空间大小:0--2 ...
- binary hacks读数笔记(nm命令)
nm命令(names):输出包含三个部分:1 符号值.默认显示十六进制,也可以指定: 2 符号类型.小写表示是本地符号,大写表示全局符号(external); 3 符号名称. 例如:nm Simple ...
- binary hacks读数笔记(objdump命令)
一.首先看一下几个常用参数的基本含义: objdump命令是Linux下的反汇编目标文件或者可执行文件的命令,它还有其他作用,下面以ELF格式可执行文件test为例详细介绍: 1.objdump -f ...
- binary hacks读数笔记(file命令与magic file)
file命令的作用是用于检验文件的类型,并打印至终端.file命令检验文件类型按以下顺序来完成: 检验文件系统(Filesystem)中支持的文件类型. 检验magic file规则. 检验文件内容的 ...
- binary hacks读数笔记(dlopen、dlsym、dlerror、dlclose)
1.dlopen是一个强大的库函数.该函数将打开一个动态库,并把它装入内存.该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的.比如 Apache Web 服务器利用这个函数在运行过程中加载 ...
随机推荐
- 多测师讲解自动化测试 _RFalert弹框._高级讲师肖sir
alert弹框定位 Open Browser file:///D:\\bao\\baoan\\alert弹框.html gc sleep 2 Handle Alert accept #点击确定 Han ...
- 多测师讲解selenium_输入性弹框定位_高级讲师肖sir
#输入性弹框from selenium import webdriverfrom time import sleepdrvier=webdriver.Chrome()url='file:///F:\d ...
- 多测师讲解pthon _函数__return_高级讲师肖sir
#函数中的返回的作用(return) 案例: #函数中的返回的作用:def fun(): #定义的一个函数 num =100 a=num/2 #print(a) #50.0 return a # pr ...
- windows.h头文件中改变光标位置的函数——SetConsoleCursorPosition
COORD 具体为 typedef struct COORD{ short X; short Y; } COORD,*PCOORD; 可以用来记录坐标. #include <iostre ...
- xuexi0.1
1.C语言通过编译器对内存进行了一定的封装.a +=4等效于a=a+4.C语言中数据类型的本质含义:表示一个内存格子的长度和解析方法.(int *)0:表示0是一个指针,这个指针指向一个int类型的数 ...
- centos8安装php7.4
一,下载php7.4 1,官方网站: https://www.php.net/ 2,下载 [root@yjweb source]# wget https://www.php.net/distribut ...
- NCEP数据资料获取 地面抬升指数
先放上数据地址:https://www.esrl.noaa.gov/psd/data/gridded/data.ncep.reanalysis.surface.html 美国国家环境预报中心(NCEP ...
- 【应用程序见解 Application Insights】使用Azure Monitor Application Insights Agent获取Azure VM中监控数据及IIS请求指标等信息
问题情形 为了使用Application Insights也可以监控Azure VM中的相关性能数据,如CPU, Memory,IIS Reuqest等信息,可以在VM中开始一个一个扩展插件: Azu ...
- MySQL历史
MySQL历史 马云生气了 去IOE活动 1979年 研发一个引擎 1996年 发布MySQL1.0 1999年 瑞典注册AB公司 2003年 MySQL 5.0版本 提供试图.存储过程 具有了一些企 ...
- log4j日志级别怎么搞
日志的级别之间的大小关系如右所示:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF Log4j建 ...