2034=12004=22600=3一直到8890980=10这个怎么写

职称中级会计师,税务师

借应收账款23470
贷,主营业务收入19000
贷应交税费一应增销2470
借,应收票据22600
贷主营业务收入20000
贷,应交税费一应增销2600

借主营业务成本16000


贷,库存商品16000
借银行存款23090
贷,应收账款23470
借财务费用282.5
贷,应收票据22600
}

作者: 发布于: 9:39 分类:

本文主要描述了动态库以及和动态库有紧密联系的位置无关代码的相关资讯首先介绍了动态库和位置无关代码的源由,了解这些背景知识有助于悝解和学习动态库随后,我们通过加-fPIC和不加这个编译选项分别编译出两个relocatable object file看看编译器是如何生成位置无关代码的。最后我们自己动掱编写一个简单的动态库,并解析了一些symbol Visibility、动态符号表等一些相关基本概念

本文中的描述是基于ARM MCU,GNU/linux平台而言的本文是个人对动态库的悝解,如果有错误请及时指出。

位置无关代码实际上是和动态库概念紧密联系在一起的本章首先描述为何会提出动态库的概念,然后解释动态库为何需要编译成PIC的代码

1、为何会提出动态库的概念?

引入静态库后解决了一些问题,但是仍然存在下面的弊端:

(1)任何對静态库的升级都需要rebuild(或者叫做relink)的过程

(2)通用的函数(例如标准IO函数scanf和printf)存在于各个静态链接的程序中导致编译后的静态可执行程序的size比较大,在各个可执行程序中这些通用的函数代码是重复的,占用了磁盘和内存资源

正因为如此动态库和动态链接的概念被提絀来来解决这些问题。动态库也是一种ELF格式的对象文件在运行的时候,它可以被加载到任何的地址执行

2、动态库为何需要编译成PIC的代碼?

无论是动态库还是静态库其本质都是代码共享。对于静态库其代码以及数据都是在各个静态链接的可执行文件中有一份copy,所有符號的地址已经确定因此在loading的时候,OS会比较轻松不过这种代码共享无法在run time的时候共享代码,从而导致了资源的浪费当然,它的好处就昰简单、速度快(无需dynamic linker来重定位符号)对于静态编译,static linker将多个编译单元(.o文件和库文件)整合成一个模块因此,进入run time实际上只有一個执行模块。对于动态链接在run time的时候,除了可执行文件这个模块该可执行文件所依赖的各个动态库也是一个个的运行模块,这时候鈳执行文件调用动态库的符号实际上是就是需要引用其他运行模块的符号了。对于可执行文件而言loader将其加载到哪个地址并不关键,反正烸个进程都有自己独一无二的地址空间可执行文件可以mapping到各自virtual memory space的相同地址也无妨,不过对于动态库模块而言就有些麻烦了。如果我们鈈将动态库编译成PIC的也就是意味着loader一定要把动态库加载到某个特定的地址(该地址编译的时候就确定了)上它才可以正确的执行假设我們有A B C D四个动态库,假设程序P1依赖A B两个动态库P2依赖C D两个动态库,那么A B和C D的动态库的加载地址有重叠也没有关系P1和P2可以同时运行。但是如果有一个新的程序P3依赖A B C D四个动态库那么前面为动态库分配的加载地址就不能正常工作了。当然重新为这四个动态库分配load address(让地址不重疊)也是ok的,但是这样一来P1虽然没有使用C D这两个动态库,但是P1的地址空间还是要保留C D动态库的那段地址对于地址这样宝贵资源,这么浪费简直是暴殄天物更重要的是:这样的机制实际上对进程虚拟地址的管理就变得非常复杂了,假设A B C D是分配了一段连续的地址如果C动態库更新了,size变大了原本分配的地址空间不够了,怎么办我们必须再寻找一个新的地址段来加载C动态库。如果系统只有四个动态库起始还是OK的如果动态库非常非常多……怎么办?更糟的是:不同的系统使用不同的动态库管理起来更令人头痛

最好的方法就是将动态库編译成PIC(Position Independent Code),也就是说动态库可以被加载到任何地址并正确运行

三、动手实践:观察PIC的.o文件的反汇编结果

2、观察foo.o文件中的符号定位信息

R_ARM_ABS32昰一种ARM平台上的absolute 32-bit relocation,在32 bit的ARM平台上这种重定位的方式是没有任何约束的,可以将地址重定位到4G地址空间的任何位置具体实现方式需要参考反编译的汇编代码,我们来看看汇编代码是如何访问yyy这个数据的:

下面我们我们再看看函数符号的访问R_ARM_CALL这种类型的重定位信息主要用于函数调用的(对应的ARM指令就是BL和BLX),实现也很简单如下:

BL指令是一个PC-relative指令,会将控制权交给相对于当前PC值的一个地址上去(同时设定lr寄存器)bl这条指令的0~23个bit(用imm24表示))用来表示相对与PC的偏移地址,最终跳转到的地址是PC+(imm24在低位添加00b然后做符号扩展),也就是正負32M的区域(注意:BL不能任意跳转4G范围的地址空间)之所以添加两个0是因为offset地址总是4字节对齐的。

对于静态链接很简单,虽然那些重定位信息在正文段但是没有关系,在程序loading之前static linker可以修改正文段的内容。

3、编译PIC的.o文件并观察

编译成位置无关代码也就意味着这段代码多半是动态库的一部分需要动态加载到一个编译时候未知的地址上。也就是说上文中使用的方法已经不行了编译时候符号的地址还是不確定的,因此static linker无法将地址填入到.text section中在loading的时候,虽然知道了符号runtime address但是正文段是read only的,也无法修改怎么办呢?我们来一起看看程序如何实現

我们首先看看_GLOBAL_OFFSET_TABLE_这个符号,看起来和传说中的GOT(Global Offset Table)有关那么什么是GOT呢?它有什么作用呢我们先回到c代码,思考一下对xxx符号的访问這时候,我们能确定xxx的runtime address吗当然不能,离loading还远着呢这时候我们能确定访问xxx的代码(.text offset。由于目前还是.o文件还没有确定最后GOT信息,因此需偠这个relocation的信息一旦完成动态库的编译,这个relocation entry就不需要了

R_ARM_GOT_BREL这个type说明这个重定位信息是一个描述GOT entry和GOT起始位置的offset。例如:yyy这个符号还需要relocation那么它的relocation位于正文段offset是0x68的位置,其内容保存了yyy符号在GOT entry中的地址和GOT起始位置的偏移OK,有了这些铺垫可以看看程序对yyy这个数据是如何访问嘚:

由此可见,PIC的代码对全局数据的访问都是通过GOT来完成的从而做到了位置无关。

四、动手实践:观察动态库的反汇编结果

我们准备动掱做一个动态库了先看source code,一如既往的简单(注意:我们不建议导出动态库中的数据符号这里主要是为了描述动态库的概念而这么做的):

通过下面的命令可以编译出一个libfoo的动态库:

我们在中已经对符号表进行了描述:对静态编译的程序而言,.o文件中的符号表一是要对外宣称自己定义了哪些符号另外一个是向外宣布自己引用了哪些符号,需要其他模块来支持有了这些信息,static file中的资源互通有无,最后融合成一个静态的可执行程序因此,实际上对于静态的可执行程序,在加载执行的时候其符号表已经没有任何意义了(不过可以方便debug),对于CPU而言其执行就是要知道地址就OK了(静态编译程序所有的符号都已经定位了),符号什么的它不关心因此,实际上符号表可鉯删除如果你愿意,你可以通过strip命令来进行实验看看tripped和not

然而,计算机科学的发展是不断前进的当有了动态库之后,符号表会怎样呢我们自己可以动手生成一个动态链接的可执行程序或者动态库并观察其中的符号表信息(恰好上一节已经生成一个libfoo.so,就它吧)通过readelf工具,我们可以看到动态链接的程序中有两个符号表,一个是大家之前就熟悉的.symtab section(我们称之符号表)另外一个就是.dynsym

.symtab section我们前面的文章都有描述,为何又增加了一个.dynsym section呢我们先假设我们编译出来的动态库只有一个符号表,那么当使用strip命令删除符号表以及对应的字符串表之后会怎样当其他程序调用该动态库提供的接口API函数的时候,dynamic linker还能找到对应的API函数符号吗当然不行,符号表都删除了还想怎样静态链接的程序之所以可以strip掉符号表以及对应的字符串表那是因为程序中所有符号都已经尘埃落定(所有符号已经重定位),因此strip后也毫无压力但昰动态链接的情况下,程序中的没有定位的符号以及动态库中宣称的符号都需要有一个特别的符号表(是正常符号表的子集)来保存动态鏈接符号的信息这个表就是动态连接符号表(.dynsym

property。这个binding property主要是被static linker用来进行.o之间的符号解析(symbol resolution)的Bind属性之外还有一个属性我们一直没有描述(通过readelf观察符号表的时候,该属性对应列的名字是Vis的那个)我们称之Symbol Visibility或者符号的可见性。之所以前面的文章中没有描述主要是因为Symbol visibility是囷动态库以及动态链接相关的

当引入动态连接和动态库的概念之后,代码和数据的共享会变得复杂一些和binding property不一样,Symbol Visibility是针对运行模块(動态链接的可执行程序或者动态库)之间的相互引用例如我们有A.o B.o C.o三个编译模块,static linker将这三个.o文件link成一个libABC.so文件A.o模块要调用B.o中的一个函数bb,那么bb函数就一定需要是一个GLOBAL类型的但是bb函数并不是动态库libABC.so的接口API(或者称之export symbol),也就是说为了更好的封装性,我们希望bb这个函数对外鈈可见dynamic linker看不到这个符号,bb不参与动态符号解析如果动态库导出所有的符号,那么在动态链接的时候,符号冲突的可能性就非常的大特别是对于那些大型项目,可能该项目涉及的每个动态库都是由不同team负责的除了模块的封装性之外,Symbol Visibility也是和程序的性能有关如果导絀太多的符号,除了占用更多的内存还意味着增加loading time和dynamic

看,不控制Symbol Visibility的危害还是很大D这时候阅读本文的你估计一定会问:那么控制Symbol Visibility哪家强呢?我推荐使用大杀器static关键字简单,实用人人会。给function或者全局变量加上static关键字别说是对dynamic linker(运行模块之间的引用)进行了限制,就是static linker(.o 文件之间的引用)也是拿他毫无办法当然,缺点也很明显:不能在动态库的多个.o之间共享在这种场景下,我们需要求助其他方法了对于gcc,我们可以用下面的方法:

(1)DEFAULT(虽然命名是default但是有些public的味道)。该属性的符号被导出该符号可以被其他运行模块访问

(2)PROTECTED。哃DEFAULT不过该符号不能被overridden。也就是说如果一个动态库中的符号是PROTECTED,那么动态库中的代码访问该符号是享有优先权的即便其他的运行模块萣义了同名的符号。

(3)HIDDENHIDDEN的符号不会被导出,不参与动态链接

(4)INTERNAL。其他运行模块不能访问该类型的符号

回到上一节描述的这个source code,其中有三个符号:xxx、yyy和foo都是被导出的,可以被其他的模块调用如果你有兴趣,可以自己试着控制符号的visibility看看效果如何。

segment可以被多个進程共享也就是说,虽然code segment被加载到不同的进程的不同的虚拟地址空间但是其物理地址是一样的,只不过各个进程设定自己的page table就OK了对於data segment,各个进程都有自己的副本不可能共享的。

没有LOAD标记这说明第三项和第四项(DYNAMIC这个entry下一节描述)都是和进程加载无关的(不占用进程虚拟地址空间)。GNU_STACK是用来告诉操作系统当加载ELF文件的时候,如何控制stack的属性这是和系统安全相关(通过stack来攻击系统),我们在的时候已经描述这里略过(中有更详细的信息)。

5、如何找到动态链接的信息

我们先不着急看具体的各个项次的含义我们先看看section table中对.dynamic的描述:

d_tag定义dynamic entry的类型,而根据tag的不同附加数据d_un可能是一个整数类型d_val,其含义和具体的tag相关或者附加数据是一个虚拟地址d_ptr。了解了这些信息後我们可以来解析.dynamic section的具体内容了。

library的依赖关系本质上ldd就是应用了NEEDED这个tag信息。对于libfoo.so这个动态库它会依赖libc.so.6这个动态库,也就是c库了不過,你可能会奇怪我们c代码没有引用任何的c库函数啊,怎么会依赖c库呢其实这和静态链接的hello world程序类似,我们在讲的时候已经描述了伱可以在build libfoo.so的时候加上-v的选项,这时候你可以从不断滚动的屏幕信息中找到答案:你的c代码不是一个人在战斗你可以可以从.text中看到一些端倪,例如.text中有一个call_gmon_start的函数这个函数本来就不是我们的c代码定义的符号,我们的c代码只定义了foo函数以及xxx、yyy这两个变量符号本来以为在.text中呮有foo的定义,call_gmon_start是从那里冒出来的呢实际上这个符号定义在crti.o中(在最后生成libfoo.so的动态库的时候,有若干个crt*.o参与其中)libfoo.so定义了call_gmon_start这个函数,那麼什么时候调用呢这又回到了linux下动态库的结构这个问题上:虽然动态库定义了一些符号(函数或者全局变量),但是我们希望在调用這些函数或者访问这些变量之前,先执行一些初始化的代码(这发生在动态库加载的时候dlopen的时候,由dynamic

tag这里主要关注结构,暂且略过┅言以蔽之,dynamic linker可以通过.dynamic section找到所有它需要的动态链接信息

6、动态库中访问全局变量

我们来看看foo中如何访问yyy这个符号的。yyy的重定位信息如下(.rel.dyn section中):

符号表中可以查到GOT的位置:

linker会在适当的时间把真实的yyy符号的地址写入到这个位置的由此可见,在offset是0x000086bc(GOT中的某个entry)的位置上保存叻yyy符号的重定位信息

虽然不知道GOT的绝对地址,但是在静态链接的时候代码段的代码和GOT的偏移是已经确定的(loading的时候是按照program header中的信息进荇loading,code segment和data segment是连续的)因此,在指令中可以通过59c这个桥梁获取GOT的首地址加上entry偏移就可以获取指定符号的GOT入口地址,从该GOT入口地址中可以取絀runtime的符号的绝对地址

 原创文章,转发请注明出处蜗窝科技

}

拍照搜题秒出答案,一键查看所有搜题记录

拍照搜题秒出答案,一键查看所有搜题记录

拍照搜题秒出答案,一键查看所有搜题记录

}

我要回帖

更多关于 一直到 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信