使用不同的arm-linux-ld -tText 链接地址,但是编译出来的bin文件一致

使用不同的arm-linux-ld -tText 链接地址,但是编译出来的bin文件一致,第1张

bin文件是纯二进制文件,不包含任何里链接地址,符号表等信息,它的执行地址是你烧写bin文件的地址,烧写到哪个地址处就在那里执行;

elf文件的执行地址是在文件编译链接时候确定的,烧写的地址并不是执行的地址,加载器会解析elf文件里记录的执行地址,将其从烧写地址处拷贝到执行地址处执行;

程序执行的过程: 1、写好一个程序,经过编译、链接后会生成一个可执行文件,在linux平台下是ELF(Executable Linkable Format)格式的,windows平台下是PE(Portable Executable)格式的。 2、然后你执行这个可执行文件,这个可执行文件里面的代码段、数据段和BSS段会被加载到PC或者某设备的内存中。代码段里放的就是指令,所以内存里的指令是通过执行某可执行文件加载到内存里的。 3、CPU会从代码段的起始地址,调用第一条指令,开始执行。如果没有遇到跳转指令就顺序执行:假设代码段起始地址是0x100,那么就是先执行0x100这个地址里的指令,然后再执。原因就是硬盘和内存在传输之间是有不同的地方的。计算机所有设备各部件之间的延时排列由高到低,依次为机械硬盘、固态硬盘、存储器和CPU;从数据响应速度来看,存储器明显优于硬盘。数据的读写速度与固态磁盘的读写速度相差甚远。 实际上,计算机内存和CPU缓存的功能是一样的。实际上,它是CPU和硬盘之间的一个临时存储区。CPU需要访问和处理的数据将通过这里。当计算机工作时,首先将要使用的东西从硬盘调用到内存,然后根据情况在CPU中输入各级缓存,最后由CPU调用。 同时,内存也是数据临时存储的地方。例如,可以使用文本软件输入一段文本,也可以使用绘图软件绘制。在您按下保存按钮或软件帮助您自动保存之前。

计算机区分指令和数据有以下2种方法: 1、通过不同的时间段来区分指令和数据,即在取指令阶段(或取指微程序)取出的为指令,在执行指令阶段(或相应微程序)取出的即为数据。 2、通过地址来源区分,由PC提供存储单元地址的取出的是指令,由指令地址码部分提供存储单元地址的取出的是 *** 作数。 存储器中的每段存储空间都会有一个地址,每个指令都包括一段 *** 作数和一段空间地址,cpu会根据 *** 作数去处理地址所指的数据。 一般计算机先读取存储器最开始的内容(这一部分是指令),然后加载 *** 作系统(先是LOADER)后由 *** 作系统对硬盘文件系统结构(即是数据)以判断其他数据和指令的位置。

我们从一个简单的NDK Demo开始分析。

下面从 SystemloadLibrary() 开始分析。

下面看 loadLibrary0()

参数 loader 为Android的应用类加载器,它是 PathClassLoader 类型的对象,继承自 BaseDexClassLoader 对象,下面看 BaseDexClassLoader 的 findLibrary() 方法。

下面看 DexPathList 的 findLibrary() 方法

回到 loadLibrary0() ,有了动态库的全路径名就可以装载库了,下面看 doLoad() 。

nativeLoad() 最终调用 LoadNativeLibrary() ,下面直接分析 LoadNativeLibrary() 。

对于JNI注册,这里暂不讨论,下面看 OpenNativeLibrary() 的实现。

下面看 android_dlopen_ext() 的实现

接下来就Android链接器linker的工作了。

下面从 do_dlopen() 开始分析。

find_library() 当参数translated_name不为空时,直接调用 find_libraries() ,这是装载链接的关键函数,下面看它的实现。

find_libraries() 中动态库的装载可以分为两部分

下面从 find_library_internal() 开始分析。

下面分析 load_library()

下面看另一个 load_library() 的实现

下面分析ELF文件头以及段信息的读取过程,也就是LoadTask的 read() ,它直接调用ElfReader的 Read() 方法。

动态库的装载在LoadTask的 load() 中实现。

下面看ElfReader的 Load() 方法

动态库的装载已经完成,下面看链接过程。

下面看 prelink_image()

链接主要完成符号重定位工作,下面从 link_image() 开始分析

下面以函数引用重定位为例分析 relocate() 方法

一个程序内存分配:

下图是APUE中的一个典型C内存空间分布图(虚拟内存)

例如:

int g1=0, g2=0, g3=0;

int max(int i)

{

int m1=0,m2,m3=0, p_max;

static n1_max=0,n2_max,n3_max=0;

p_max = (int )malloc(10);

printf("打印max程序地址\n");

printf("in max: 0xx\n\n",max);

printf("打印max传入参数地址\n");

printf("in max: 0xx\n\n",&i);

printf("打印max函数中静态变量地址\n");

printf("0xx\n",&n1_max); //打印各本地变量的内存地址

printf("0xx\n",&n2_max);

printf("0xx\n\n",&n3_max);

printf("打印max函数中局部变量地址\n");

printf("0xx\n",&m1); //打印各本地变量的内存地址

printf("0xx\n",&m2);

printf("0xx\n\n",&m3);

printf("打印max函数中malloc分配地址\n");

printf("0xx\n\n",p_max); //打印各本地变量的内存地址

if(i) return 1;

else return 0;

}

int main(int argc, char argv)

{

static int s1=0, s2, s3=0;

int v1=0, v2, v3=0;

int p;

p = (int )malloc(10);

printf("打印各全局变量(已初始化)的内存地址\n");

printf("0xx\n",&g1); //打印各全局变量的内存地址

printf("0xx\n",&g2);

printf("0xx\n\n",&g3);

printf("======================\n");

printf("打印程序初始程序main地址\n");

printf("main: 0xx\n\n", main);

printf("打印主参地址\n");

printf("argv: 0xx\n\n",argv);

printf("打印各静态变量的内存地址\n");

printf("0xx\n",&s1); //打印各静态变量的内存地址

printf("0xx\n",&s2);

printf("0xx\n\n",&s3);

printf("打印各局部变量的内存地址\n");

printf("0xx\n",&v1); //打印各本地变量的内存地址

printf("0xx\n",&v2);

printf("0xx\n\n",&v3);

printf("打印malloc分配的堆地址\n");

printf("malloc: 0xx\n\n",p);

printf("======================\n");

max(v1);

printf("======================\n");

printf("打印子函数起始地址\n");

printf("max: 0xx\n\n",max);

return 0;

}

打印结果:

ELF目标文件格式的最前端是 ELF文件头(ELF Header)

包含了描述整个文件的基本属性,如ELF版本、目标机器型号、 程序入口地址

3 加载:

编译技术什么是程序的入口语句?我们刚开始学习C语言的时候,总会以一个hello world程序开始了解,并会被告知main是程序的入口函数。

那么是否有怀疑过,既然main是函数的入口,那么全局函数是如何初始化的?

实际上main并不是一个程序的入口地址。

以linux为例,一个可执行程序是ELF格式(Executable and Linkable Format)。在ELF的文件头里记录了当程序入口地址,该入口地址是内核加载该文件后的第一条指令执行的地方。

可以写个hello world,生成可执行文件,然后通过readelf -h aout查看文件头:

ELF 头: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 类别: ELF64 数据: 2 补码,小端序 (little endian) 版本: 1 (current) OS/ABI: UNIX - System V ABI 版本: 0 类型: DYN (共享目标文件) 系统架构: Advanced Micro Devices X86-64 版本: 0x1 入口点地址: 0x1040 程序头起点: 64 (bytes into file) Start of section headers: 14688 (bytes into file) 标志: 0x0 本头的大小: 64 (字节) 程序头大小: 56 (字节) Number of program headers: 11 节头大小: 64 (字节) 节头数量: 29 字符串表索引节头: 28

这里入口地址是0x1040。

让我们反编译看看:

0000000000001040 <_start>: 1040: f3 0f 1e fa endbr64 1044: 31 ed xor %ebp,%ebp 1046: 49 89 d1 mov %rdx,%r9 1049: 5e pop %rsi 104a: 48 89 e2 mov %rsp,%rdx 104d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 1051: 50 push %rax 1052: 54 push %rsp 1053: 4c 8d 05 76 01 00 00 lea 0x176(%rip),%r8 # 11d0 <__libc_csu_fini> 105a: 48 8d 0d ff 00 00 00 lea 0xff(%rip),%rcx # 1160 <__libc_csu_init> 1061: 48 8d 3d d1 00 00 00 lea 0xd1(%rip),%rdi # 1139 <main> 1068: ff 15 72 2f 00 00 callq 0x2f72(%rip) # 3fe0 <__libc_start_main@GLIBC_225> 106e: f4 hlt 106f: 90 nop

0x1040对应的是_start函数,_start调用了glibc中的__libc_strat_main__libc_strat_main最终会调用main函数。(_start和__libc_start_main的分析见《程序员的自我修改——链接、装载与库》111)

既然知道main不是程序的真正入口,那么是否有办法在main之前或之后执行代码呢?

gcc提供了一组“进入”、“退出”函数可以首先这个功能:

用于告诉汇编程序如何进行汇编的指令,它既不控制机器的 *** 作也不被汇编成机器代码,只能为汇编程序所识别并指导汇编如何进行。

ADRL

伪指令

将相对于程序或相对于寄存器的地址载入寄存器中。

ADR

指令相似。ADRL

所加载的地址比

ADR

所加载的地址更宽,因为它可生成两个数据处理指令。

Note

汇编版本老于

ARMv6T2

的处理器的

Thumb

指令时,ADRL

是无效的。

语法

ADRL{cond}

Rd,label

其中:

cond

是一个可选的条件代码(请参阅条件执行)。

Rd

是要加载的寄存器。

label

表达式,与程序或寄存器相关。

有关详细信息,请参阅相对寄存器和程序相对的表达式。

用法

ADRL

始终汇编为两个

32

位指令。

即使使用单个指令就可完成地址访问,也会生成多余的第二个地址。

如果汇编程序无法将地址构建为两个指令,则它将生成一条错误消息,汇编将失败。

有关加载更宽范围地址的信息,请参阅LDR

伪指令(另请参阅将常数加载到寄存器)。

ADRL

可生成与位置无关的代码,因为地址与程序或寄存器有关。

如果

label

与程序有关,则其表示的地址必须要与

ADRL

伪指令在同一汇编程序区域内,请参阅AREA。

如果使用

ADRL

来为

BX

BLX

指令生成目标,则当目标中包含

Thumb

指令时,您就要自己设置地址的

Thumb

位(位

0)。

体系结构和范围

可用范围取决于所用的指令集:

ARM

±64KB

到字节或半字对齐的地址。

±256KB

字节,字对齐地址。

32

Thumb

±1MB

字节,字节、半字或字对齐地址。

16

Thumb

ADRL

不可用。

上面给出的范围是相对于位于当前指令后的、离当前指令有四个字节(在

Thumb

代码中)或两个字(在

ARM

代码中)间隔的点而言的。

ARM

32

Thumb

中,如果地址为

16

字节对齐,或与该点的相对性更高,则相对地址的范围可更大。

MOV32

伪指令

将以下项之一加载到寄存器:

一个

32

位常数值

任何地址。

MOV32

始终会生成两个

32

位指令,即一个

MOV、MOVT

对。

您可利用它加载任何

32

位常数或访问整个地址空间。

如果用

MOV32

加载地址,则所生成的代码将与位置有关。

语法

MOV32{cond}

Rd,

expr

其中:

cond

是一个可选的条件代码(请参阅条件执行)。

Rd

是要加载数据的寄存器。Rd

不可为

sp

pc。

expr

可以是下列项之一:

symbol

程序区域中的标签。

constant

任何

32

位常数。

symbol

+

constant

标签加上

32

位常数。

用法

MOV32

伪指令的主要功能有:

当单个指令中无法生成立即数时,生成文字常数。

将相对于程序的地址或外部地址载入寄存器中。

无论链接器将包含

MOV32

ELF

代码段置于何处,该地址始终有效。

Note

以这种方式加载的地址是在链接时确定的,因此代码不是位置无关的。

如果所引用的标签位于

Thumb

代码中,则

MOV32

将会设置该地址的

Thumb

位(位

0)。

体系结构

此伪指令在

ARMv6T2

ARMv7

中的

ARM

Thumb

状态下均有效。

LDR

伪指令

将以下项之一载入寄存器:

一个

32

位常数值

一个地址。

Note

本节仅介绍

LDR

指令。

有关

LDR

指令

的详细信息,请参阅

内存访问指令。

有关使用

LDR

伪指令加载常数的信息,请参阅用

LDR

Rd,

=const

加载。

语法

LDR{cond}{w}

Rt,=[expr

|

label‑expr]

其中:

cond

是一个可选的条件代码(请参阅条件执行)。

W

是可选的指令宽度说明符。

Rt

是要加载的寄存器。

以上就是关于使用不同的arm-linux-ld -tText 链接地址,但是编译出来的bin文件一致全部的内容,包括:使用不同的arm-linux-ld -tText 链接地址,但是编译出来的bin文件一致、为什么编好的程序和原始数据得事先存到存储器中,cpu才能够从地址当中加载指令、Android Native库的加载及动态链接等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9278107.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-04-26
下一篇 2023-04-26

发表评论

登录后才能评论

评论列表(0条)

保存