怎么将文件中数据linux加载so文件进so库,避免在调用so库函数的时候去进行文件的读操作?

没有头文件怎么获知so文件里的函数接口_百度知道
没有头文件怎么获知so文件里的函数接口
答题抽奖
首次认真答题后
即可获得3次抽奖机会,100%中奖。
我不是他舅
来自电脑网络类芝麻团
我不是他舅
采纳数:214873
获赞数:1327807
参与团队:
库函数是编译到库文件里面的,库分静态库(.lib)跟动态库(.dll),静态库是需要头文件的。动态库则分两种调用方式:显式调用和隐式调用。隐式调用也需要头文件,显式调用则根据符号表查找函数入口,可以不要头文件,但是你要知道库函数的函数名和参数列表。
为你推荐:
其他类似问题
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。Android热修复升级探索——SO库修复方案
Android热修复升级探索——SO库修复方案
通常情况下,大多数人希望android下热补丁方案能够做到补丁的全方位修复,包括类修复/资源修复/so库的修复。 这里主要介绍热补丁之so库修复思路。
通常情况下,大多数人希望android下热补丁方案能够做到补丁的全方位修复,包括类修复/资源修复/so库的修复。 这里主要介绍热补丁之so库修复思路。
二、so库加载原理
Java Api提供以下两个接口加载一个so库
System.loadLibrary(String libName):传进去的参数:so库名称, 表示的so库文件,位于apk压缩文件中的libs目录,最后复制到apk安装目录下。
System.load(String pathName):传进去的参数:so库在磁盘中的完整路径, 加载一个自定义外部so库文件 。
上述两种方式加载一个so库,实际上最后都调用nativeLoad这个native方法去加载so库, 这个方法的参数fileName:so库在磁盘中的完整路径名,代码+图文的方式简述so库加载原理,下面的代码示例,stringFromJNI-& Java_com_taobao_jni_MainActivity_stringFromJNI静态注册的native方法,test-&test动态注册的native方法. 。
我们知道JNI编程中,动态注册的native方法必须实现JNI_OnLoad方法,同时实现一个JNINativeMethod[]数组, 静态注册的native方法必须是Java+类完整路径+方法名的格式。
动态注册的native方法映射通过加载so库过程中调用JNI_OnLoad方法调用完成。
静态注册的native方法映射是在该native方法第一次执行的时候才完成映射,当然前提是该so库已经load过。
三、so库热部署实时生效可行性分析
1.动态注册native方法实时生效
前面我们分析过so库的加载原理, 我们知道动态注册的native方法调用一次JNI_OnLoad方法都会重新完成一次映射, 所以我们是否只要先加载原来的so库,,然后再加载补丁so库,就能完成Java层native方法到native层patch后的新方法映射, 这样就完成动态注册native方法的patch实时修复。一张图说明:
实测发现art下这样是可以做到实时生效的,但是Dalvik下做不到实时生效,通过代码测试我们发现, 实际上Dalvik下第二次load补丁so库, 执行的仍然是原来so库的JNI_OnLoad方法, 而不是补丁so库的JNI_OnLoad方法, 所以Dalvik下做不到实时生效。 我们来简单分析下, 既然拿到的是原来so库的JNI_OnLoad方法, 那么我们首先怀疑以下两个函数是否有问题。
dlopen():返回给我们一个动态链接库的句柄dlsym(): 通过一个dlopen得到的动态连接库句柄,来查找一个symbol
首先来看下Dalvik虚拟机下面dlopen的实现, 源码在/bionic/linker/dlfcn.cpp文件, 方法调用链路:dlopen-& do_dlopen -& find_library -& find_library_internal
findloadedlibrary方法判断name表示的so库是否已经被加载过, 如果加载过直接返回之前加载so库的句柄,没有加载过, 调用load_library尝试加载so库 。
看代码注释, 也知道其实这是Dalvik虚拟机下的一个bug,这里它是通过basename去做查找, 传进来的参数name实际上是so库所在磁盘的完整路径, 比如此时修复后的so库的路径为/data/data/com.taobao.jni/files/libnative-lib.so. 但是此时是通过bname:libnative-lib.so作为key去查找, 我们知道第一次加载原来的so库System.loadLibrary("native-lib");实际上已经在solist表中存在了native-lib这个key, 所以Dalvik下面加载修复后的补丁so拿到的还是原so库文件的句柄, 所以执行的仍然是原来SO库的JNI_OnLoad方法,Art下不存在这个问题, 是因为Art下这个地方是以name作为key去查找而不是bname, 所以art下重新load一遍补丁so库, 拿到的是补丁so库的句柄, 然后执行补丁so库的JNI_OnLoad。
所以为了解决Dalvik下面的这个问题, 那么如果尝试对补丁so进行改名,比如此处补丁so库的完整路径修改之后变成/data/data/com.taobao.jni/files/libnative-lib-123333.so, 后面一串数字是当前时间戳, 确保这个bname是全局唯一的, 按照上面的分析, 在solist中查找的key已经是唯一的,所以此时可以做到Dalvik下面动态注册的native方法的实时生效。
2. 静态注册native方法实时生效
上面通过尝试对补丁so库进行重命名为全局唯一的名称可以确保第二次加载补丁so库可以做到Dalvik下和Art下动态注册方法的实时生效, 但要做到静态注册native方法的实时生效还需要更多工作。
前面我们说过静态注册native方法的映射是在native方法第一次执行的时候就完成了映射, 所以如果native方法在加载补丁so库之前已经执行过了, 那么是否这种时候这个静态注册的native方法一定得不到修复? 幸运的是, 系统JNI API提供了解注册的接口。
UnregisterNatives函数会把jclazz所在类的所有native方法都重新指向为dvmResolveNativeMethod, 所以调用UnregisterNatives之后不管是静态注册还是动态注册的native方法之前是否执行过在加载补丁so的时候都会重新去做映射。 所以我们只需要以下调用。
这里有一个难点, 因为native方法的修改是在SO库中, 所以我们的补丁工具很难检测出到底是哪个Java类需要解注册native方法。 这个问题暂且放下, 假设我们能知道哪个类需要解注册native方法, 然后load补丁so库之后,再次执行该native方法,这样看起来是可以让该native方法实时生效, 但是测试发现, 在补丁so库重命名的前提下, java层native方法可能映射到原so库的方法, 也可能映射到补丁so库的修复后的新方法。
首先静态注册的native方法之前从未执行, 首先尝试解析该方法。或者调用了unregisterJNINativeMethods解注册方法,那么该方法将指向meth-&nativeFunc = dvmResolveNativeMethod,那么真正运行该方法的时候, 实际上执行的是dvmResolveNativeMethod函数。这个函数主要完成java层native方法和native层方法的映射逻辑。
gDvm.nativeLibs是一个全局变量, 它是一个hashtable, 存放着整个虚拟机加载so库的SharedLib结构指针。 然后该变量作为参数传递给dvmHashForeach函数进行hashtable遍历。 执行findMethodInLib函数看是否找到对应的native函数指针, 如果第一个找到就直接return, 不在进行下次的查找。
这个结构很重要, 在虚拟机中大量使用到了hashtable这个数据结构, hashtable的实现源码在dalvik/vm/Hash.h和dalvik/vm/Hash.cpp文件中, 有兴趣可以自行查看源码, 这里不进行详细分析。 hashtable的遍历和插入都是在dvmHashTableLookup方法中实现, 简单说下java.hashtable和c.hashtable的异同点:
共同点: 两者实际上都是数组实现, hashtable容量如果超过默认值都会进行扩容, 都是对key进行hash计算然后跟hashtable的长度进行取模作为bucket。
不同点: Dalvik虚拟机下hashtable put/get操作实现方法,实际上实现要比java hashmap的实现要简单一些, java hashmap的put实现需要处理hash冲突的情况, 一般情况下会通过在冲突节点上新增一个链表处理冲突, 然后get实现会遍历这个链表通过equals方法比较value是否一致进行查找, davlik下hashtable的put实现上(doAdd=true)只是简单的把指针下移直到下一个空节点。 get实现(doAdd=false)首先根据hash值计算出bucket位置, 然后通过cmpFunc函数比较值是否一致, 不一致, 指针下移。 hashtable的遍历实际就是数组遍历实现。
知道了davlik下hashtable的实现原理, 那我们再来看下前面提到的: 补丁so库重命名的前提下, 为什么java层native方法可能映射到原so库的方法也可能映射到补丁so库的修复后的新方法。 一张图说明情况 :
所以我们可以得到结论: 对补丁so库进行重命名后, 如果这个补丁so库在hashtable中的位置比原so库的位置靠前, 那么这个静态注册native方法就能够得到修复, 位置如果靠后就得不到修复。
3. SO实时生效方案总结
基于上面的分析, so库的实时生效必须满足以下几点:
so库为了兼容Dalvik虚拟机下动态注册native方法的实时生效, 必须对so文件进行改名。
针对so库静态注册native方法的实时生效, 首先需要解注册静态注册的native方法, 这个也是难点, 因为我们很难知道so库中哪几个静态注册的native方法发生了变更。 假设就算我们知道如果静态注册的native方法需要解注册, 重新load补丁so库也有可能被修复也有可能不被修复。
上面对补丁so进行了第二次加载, 那么肯定是多消耗了一次本地内存, 如果补丁so库够大, 补丁so够多,那么JNI层的OOM也不是没可能。
另外一方面补丁so如果新增了一个动态注册的方法而dex中没有相应方法,直接去加载这个补丁so文件会报NoSuchMethodError异常, 具体逻辑在dvmRegisterJNIMethod中。 我们知道如果dex如果新增了一个native方法, 那么走不了热部署只能冷启动重启生效, 所以此时补丁so就不能第二次load了。 这种情况下so库的修复严重依赖于dex的修复方案。
可以看到SO库实时生效方案, 对于静态注册的native方法有一定的局限性, 不能满足一般的通用性, 所以最后我们放弃了so库的实时生效需求,转而求次实现so库修复的冷部署重启生效方案。
四、so库冷部署重启生效实现方案
为了更好的兼容通用性, 我们尝试通过冷部署重启生效的角度分析下补丁so库的修复方案。
方案1. 接口调用替换
sdk提供接口替换System默认加载so库接口
SOPatchManager.loadLibrary接口加载so库的时候优先尝试去加载sdk指定目录下的补丁so, 加载策略如下:如果存在则加载补丁so库而不会去加载安装apk安装目录下的so库。如果不存在补丁so, 那么调用System.loadLibrary去加载安装apk目录下的so库。
我们可以很清楚的看到这个方案的优缺点:
优点:不需要对不同sdk版本进行兼容, 因为所有的sdk版本都有System.loadLibrary这个接口。缺点: 调用方需要替换掉System默认加载so库接口为sdk提供的接口, 如果是已经编译混淆好的三方库的so库需要patch, 那么是很难做到接口的替换。虽然这种方案实现简单, 同时不需要对不同sdk版本区分处理,但是有一定的局限性没法修复三方包的so库同时需要强制侵入接入方接口调用, 所以来看下方案2. 反射注入。
方案2. 反射注入
前面介绍过System.loadLibrary("native-lib");加载so库的原理, 其实native-lib这个so库最终传给native方法执行的参数是so库在磁盘中的完整路径, 比如: /data/app-lib/com.taobao.jni-2/libnative-lib.so, so库会在DexPathList.nativeLibraryDirectories/nativeLibraryPathElements变量所表示的目录下去遍历搜索。sdk&23 DexPathList.findLibrary实现如下:
可以发现会遍历nativeLibraryDirectories数组, 如果找到了IoUtils.canOpenReadOnly(path)返回为true, 那么就直接返回该path, IoUtils.canOpenReadOnly(path)返回为true的前提肯定是需要path表示的so文件存在的。 那么我们可以采取类似类修复反射注入方式, 只要把我们的补丁so库的路径插入到nativeLibraryDirectories数组的最前面就能够达到加载so库的时候是补丁so库而不是原来so库的目录, 从而达到修复的目的。sdk&=23 DexPathList.findLibrary实现如下 :
sdk23以上findLibrary实现已经发生了变化, 如上所示, 那么我们只需要把补丁so库的完整路径作为参数构建一个Element对象, 然后再插入到nativeLibraryPathElements数组的最前面就好了。
优点: 可以修复三方库的so库。 同时接入方不需要像方案1一样强制侵入用户接口调用。缺点: 需要不断的对sdk进行适配, 如上sdk23为分界线, findLibrary接口实现已经发生了变化。我们知道在不管是在补丁包中还是apk中一个so库都存在多种cpu架构的so文件, 比如"armeabi","arm64-v8a", "x86"等。 加载肯定是加载其中一个so库文件的, 如何选择机型对应的so库文件将是重点所在。
五、如果正确复制补丁so库?
上面提到的一个问题, 这里不打算详细介绍。 有需要的参考文档: Android 动态链接库加载原理及 HotFix 方案介绍, 这篇文档有些观点不尽正确, 但是我也能知道虚拟机究竟选择哪个abis目录作为参数构建PathClassLoader对象, 一张图简单了解下原理:
实际上补丁so也存在类似的问题, 我们的补丁so库文件放到补丁包的libs目录下面, libs目录和.dex文件和res资源文件一起打包成一个压缩文件作为最后的补丁包, libs目录可能也包含多种abis目录。 所以我们需要选择手机最合适的primaryCpuAbi, 然后从libs目录下面选择这个primaryCpuAbi子目录插入到nativeLibraryDirectories/nativeLibraryPathElements数组中。 所以怎么选择primaryCpuAbi是关键, 来看下我们sdk具体的实现。
sdk&=21下, 直接反射拿到ApplicationInfo对象的primaryCpuAbi即可sdk&21下, 由于此时不支持64位, 所以直接把Build.CPU_ABI, Build.CPU_ABI2作为primaryCpuAbi即可 。
最后做一个简单的小结:
so文件修复方案目前更多采取的是接口调用替换方式, 需要强制侵入用户接口调用。 目前我们的so文件修复方案采取的是反射注入的方案, 重启生效, 具有更好的普遍性。
同时如果有so文件修复实时生效的需求, 也是可以做到的,只是有些限制情况, 详见以上分析。
用云栖社区APP,舒服~
【云栖快讯】诚邀你用自己的技术能力来用心回答每一个问题,通过回答传承技术知识、经验、心得,问答专家期待你加入!&&
移动测试(Mobile Testing)是为广大企业客户和移动开发者提供真机测试服务的云平台...
阿里云总监课正式启航Linux系统中“动态库”和“静态库”那点事儿 /etc/ld.so.conf
动态库的后缀为*.so
静态库的后缀为 libxxx.a
时间: 23:14:40
&&&& 阅读:766
&&&& 评论:
&&&& 收藏:0
标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&Linux系统中&动态库&和&静态库&那点事儿 /etc/ld.so.conf &动态库的后缀为*.so &静态库的后缀为 libxxx.a & ldconfig & 目录名
转载自:http://blog.chinaunix.net/uid--id-3142046.html
今天我们主要来说说Linux系统下基于动态库(.so)和静态(.a)的程序那些猫腻。在这之前,我们需要了解一下源代码到可执行程序之间到底发生了什么神奇而美妙的事情。
在Linux操作系统中,普遍使用ELF格式作为可执行程序或者程序生成过程中的中间格式。ELF(Executable and Linking Format,可执行连接格式)是UNIX系统实验室(USL)作为应用程序二进制接口(Application BinaryInterface,ABI)而开发和发布的。工具接口标准委员会(TIS)选择了正在发展中的ELF标准作为工作在32位Intel体系上不同操作系统之间可移植的二进制文件格式。本文不对ELF文件格式及其组成做太多解释,以免冲淡本文的主题,大家只要知道这么个概念就行。以后再详解Linux中的ELF格式。源代码到可执行程序的转换时需要经历如下图所示的过程:
Win9x和Win NT/2000/XP下的32位的可执行文件PE(Portable Executable:可移动的可执行文件)
l 编译是指把用高级语言编写的程序转换成相应处理器的汇编语言程序的过程。从本质上讲,编译是一个文本转换的过程。对嵌入式系统而言,一般要把用C语言编写的程序转换成处理器的汇编代码。编译过程包含了C语言的语法解析和汇编码的生成两个步骤。编译一般是逐个文件进行的,对于每一个C语言编写的文件,可能还需要进行预处理。l 汇编是从汇编语言程序生成目标系统的二进制代码(机器代码)的过程。机器代码的生成和处理器有密切的联系。相对于编译过程的语法解析,汇编的过程相对简单。这是因为对于一款特定的处理器,其汇编语言和二进制的机器代码是一一对应的。汇编过程的输入是汇编代码,这个汇编代码可能来源于编译过程的输出,也可以是直接用汇编语言书写的程序。l 连接是指将汇编生成的多段机器代码组合成一个可执行程序。一般来说,通过编译和汇编过程,每一个源文件将生成一个目标文件。连接器的作用就是将这些目标文件组合起来,组合的过程包括了代码段、数据段等部分的合并,以及添加相应的文件头。GCC是Linux下主要的程序生成工具,它除了编译器、汇编器、连接器外,还包括一些辅助工具。在下面的分析过程中我会教大家这些工具的基本使用方法,Linux的强大之处在于,对于不太懂的命令或函数,有一个很强大的&男人&时刻stand by your side,有什么不会的就去命令行终端输入:man [命令名或函数名],然后阿拉神灯就会显灵了。对于最后编译出来的可执行程序,当我们执行它的时候,操作系统又是如何反应的呢?我们先从宏观上来个总体把握,如图2所示:
作为UNIX操作系统的一种,Linux的操作系统提供了一系列的接口,这些接口被称为系统调用(System Call)。在UNIX的理念中,系统调用"提供的是机制,而不是策略"。C语言的库函数通过调用系统调用来实现,库函数对上层提供了C语言库文件的接口。在应用程序层,通过调用C语言库函数和系统调用来实现功能。一般来说,应用程序大多使用C语言库函数实现其功能,较少使用系统调用。那么最后的可执行文件到底是什么样子呢?前面已经说过,这里我们不深入分析ELF文件的格式,只是给出它的一个结构图和一些简单的说明,以方便大家理解。ELF文件格式包括三种主要的类型:可执行文件、目标文件、共享库。1.可执行文件(应用程序)可执行文件包含了代码和数据,是可以直接运行的程序。2.目标文件(*.o)可重定向文件又称为目标文件,它包含了代码和数据(这些数据是和其他重定位文件和共享的object文件一起连接时使用的)。*.o文件参与程序的连接(创建一个程序)和程序的执行(运行一个程序),它提供了一个方便有效的方法来用并行的视角看待文件的内容,这些*.o文件的活动可以反映出不同的需要。Linux下,我们可以用gcc -c编译源文件时可将其编译成*.o格式。3.共享文件(*.so)也称为动态库文件,它包含了代码和数据(这些数据是在连接时候被连接器ld和运行时动态连接器使用的)。动态连接器可能称为ld.so.1,libc.so.1或者 ld-linux.so.1。我的CentOS6.0系统中该文件为:/lib/ld-2.12.so
一个ELF文件从连接器(Linker)的角度看,是一些节的集合;从程序加载器(Loader)的角度看,它是一些段(Segments)的集合。ELF格式的程序和共享库具有相同的结构,只是段的集合和节的集合上有些不同。那么到底什么是库呢?库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行。库分静态库和动态库两种。静态库:这类库的名字一般是libxxx.a,xxx为库的名字。利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行程序不需要外部的函数库支持,因为所有使用的函数都已经被编译进去了。当然这也会成为他的缺点,因为如果静态函数库改变了,那么你的程序必须重新编译。动态库:这类库的名字一般是libxxx.M.N.so,同样的xxx为库的名字,M是库的主版本号,N是库的副版本号。当然也可以不要版本号,但名字必须有。相对于静态函数库,动态函数库在编译的时候并没有被编译进目标代码中,你的程序执行到相关函数时才调用该函数库里的相应函数,因此动态函数库所产生的可执行文件比较小。由于函数库没有被整合进你的程序,而是程序运行时动态的申请并调用,所以程序的运行环境中必须提供相应的库。动态函数库的改变并不影响你的程序,所以动态函数库的升级比较方便。linux系统有几个重要的目录存放相应的函数库,如/lib /usr/lib。当要使用静态的程序库时,连接器会找出程序所需的函数,然后将它们拷贝到执行文件,由于这种拷贝是完整的,所以一旦连接成功,静态程序库也就不再需要了。然而,对动态库而言,就不是这样。动态库会在执行程序内留下一个标记指明当程序执行时,首先必须载入这个库。由于动态库节省空间,linux下进行连接的缺省操作是首先连接动态库,也就是说,如果同时存在静态和动态库,不特别指定的话,将与动态库相连接。
OK,有了这些知识,接下来大家就可以弄明白我所做的事情是干什么了。都说例子是最好老师,我们就从例子入手。
1、静态链接库
我们先制作自己的静态链接库,然后再使用它。制作静态链接库的过程中要用到gcc和ar命令。
准备两个库的源码文件st1.c和st2.c,用它们来制作库libmytest.a,如下:
file /usr/bin/pkg-config/usr/bin/pkg-config: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
静态库文件libmytest.a已经生成,用file命令查看其属性,发现它确实是归档压缩文件。用ar -t libmytest.a可以查看一个静态库包含了那些obj文件:
接下来我们就写个测试程序来调用库libmytest.a中所提供的两个接口print1()和print2()。
看到没,静态库的编写和调用就这么简单,学会了吧。这里gcc的参数-L是告诉编译器库文件的路径是当前目录,-l是告诉编译器要使用的库的名字叫mytest。
静态库*.a文件的存在主要是为了支持较老的a.out格式的可执行文件而存在的。目前用的最多的要数动态库了。动态库的后缀为*.so。在Linux发行版中大多数的动态库基本都位于/usr/lib和/lib目录下。在开发和使用我们自己动态库之前,请容许我先落里罗嗦的跟大家唠叨唠叨Linux下和动态库相关的事儿吧。有时候当我们的应用程序无法运行时,它会提示我们说它找不到什么样的库,或者哪个库的版本又不合它胃口了等等之类的话。那么应用程序它是怎么知道需要哪些库的呢?我们前面已几个学了个很棒的命令ldd,用就是用来查看一个文件到底依赖了那些so库文件。Linux系统中动态链接库的配置文件一般在/etc/ld.so.conf文件内,它里面存放的内容是可以被Linux共享的动态联库所在的目录的名字。我的系统中,该文件的内容如下:
然后/etc/ld.so.conf.d/目录下存放了很多*.conf文件,如下:
其中每个conf文件代表了一种应用的库配置内容,以mysql为例:
如果您是和我一样装的CentOS6.0的系统,那么细心的读者可能会发现,在/etc目录下还存在一个名叫ld.so.cache的文件。从名字来看,我们知道它肯定是动态链接库的什么缓存文件。对,您说的一点没错。为了使得动态链接库可以被系统使用,当我们修改了/etc/ld.so.conf或/etc/ld.so.conf.d/目录下的任何文件,或者往那些目录下拷贝了新的动态链接库文件时,都需要运行一个很重要的命令:ldconfig,该命令位于/sbin目录下,主要的用途就是负责搜索/lib和/usr/lib,以及配置文件/etc/ld.so.conf里所列的目录下搜索可用的动态链接库文件,然后创建处动态加载程序/lib/ld-linux.so.2所需要的连接和(默认)缓存文件/etc/ld.so.cache(此文件里保存着已经排好序的动态链接库名字列表)。也就是说:当用户在某个目录下面创建或拷贝了一个动态链接库,若想使其被系统共享,可以执行一下"ldconfig & 目录名"这个命令。此命令的功能在于让ldconfig将指定目录下的动态链接库被系统共享起来,即:在缓存文件/etc/ld.so.cache中追加进指定目录下的共享库。请注意:如果此目录不在/lib,/usr/lib及/etc/ld.so.conf文件所列的目录里面,则再次单独运行ldconfig时,此目录下的动态链接库可能不被系统共享了。单独运行ldconfig时,它只会搜索/lib、/usr/lib以及在/etc/ld.so.conf文件里所列的目录,用它们来重建/etc/ld.so.cache。因此,等会儿我们自己开发的共享库就可以将其拷贝到/lib、/etc/lib目录里,又或者修改/etc/ld.so.conf文件将我们自己的库路径添加到该文件中,再执行ldconfig命令。非了老半天功夫,终于把基础打好了,猴急的您早已按耐不住激情的想动手尝试了吧!哈哈。。。OK,说整咱就开整,接下来我就带领大家一步一步来开发自己的动态库,然后教大家怎么去使用它。我们有一个头文件my_so_test.h和三个源文件test_a.c、test_b.c和test_c.c,将他们制作成一个名为libtest.so的动态链接库文件:
OK,万事俱备,只欠东风。如何将这些文件编译成一个我们所需要的so文件呢?可以分两步来完成,也可以一步到位:方法一:
1、先生成目标.o文件:
2、再生成so文件:
-shared该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件。-fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。方法二:一步到位。
至此,我们制作的动态库文件libtest.so就算大功告成了。
接下来,就是如何使用这个动态库了。动态链接库的使用有两种方法:既可以在运行时对其进行动态链接,又可以动态加载在程序中是用它们。接下来,我就这两种方法分别对其介绍。
+++动态库的使用+++
用法一:动态链接。
使用&-ltest&标记来告诉GCC驱动程序在连接阶段引用共享函数库libtest.so。&-L.&标记告诉GCC函数库可能位于当前目录。否则GNU连接器会查找标准系统函数目录。这里我们注意,ldd的输出它说我们的libtest.so它没找到。还记得我在前面动态链接库一节刚开始时的那堆唠叨么,现在你应该很明白了为什么了吧。因为我们的libtest.so既不在/etc/ld.so.cache里,又不在/lib、/usr/lib或/etc/ld.so.conf所指定的任何一个目录中。怎么办?还用我告诉你?管你用啥办法,反正我用的ldconfig `pwd`搞定的:
执行结果如下:
偶忍不住又要罗嗦一句了,相信俺,我的唠叨对大家是有好处。我为什么用这种方法呢?因为我是在给大家演示动态库的用法,完了之后我就把libtest.so给删了,然后再重构ld.so.cache,对我的系统不会任何影响。倘若我是开发一款软件,或者给自己的系统DIY一个非常有用的功能模块,那么我更倾向于将libtest.so拷贝到/lib、/usr/lib目录下,或者我还有可能在/usr/local/lib/目录下新建一文件夹xxx,将so库拷贝到那儿去,并在/etc/ld.so.conf.d/目录下新建一文件mytest.conf,内容只有一行&/usr/local/lib/xxx/libtest.so&,再执行ldconfig。如果你之前还是不明白怎么解决那个&not found&的问题,那么现在总该明白了吧。
方法二:动态加载。动态加载是非常灵活的,它依赖于一套Linux提供的标准API来完成。在源程序里,你可以很自如的运用API来加载、使用、释放so库资源。以下函数在代码中使用需要包含头文件:dlfcn.h函数原型说明const char *dlerror(void)当动态链接库操作函数执行失败时,dlerror可以返回出错信息,返回值为NULL时表示操作函数执行成功。void *dlopen(const char *filename, int flag)用于打开指定名字(filename)的动态链接库,并返回操作句柄。调用失败时,将返回NULL值,否则返回的是操作句柄。void *dlsym(void *handle, char *symbol)根据动态链接库操作句柄(handle)与符号(symbol),返回符号对应的函数的执行代码地址。由此地址,可以带参数执行相应的函数。int dlclose (void *handle)用于关闭指定句柄的动态链接库,只有当此动态链接库的使用计数为0时,才会真正被系统卸载。2.2在程序中使用动态链接库函数。
dlsym(void *handle, char *symbol)filename:如果名字不以&/&开头,则非绝对路径名,将按下列先后顺序查找该文件。
(1)用户环境变量中的LD_LIBRARY值;
(2)动态链接缓冲文件/etc/ld.so.cache
(3)目录/lib,/usr/lib
flag表示在什么时候解决未定义的符号(调用)。取值有两个:
1) RTLD_LAZY : 表明在动态链接库的函数代码执行时解决。
2) RTLD_NOW :表明在dlopen返回前就解决所有未定义的符号,一旦未解决,dlopen将返回错误。
dlsym(void *handle, char *symbol)
dlsym()的用法一般如下:
void(*add)(int x,int y); /*说明一下要调用的动态函数add */add=dlsym("xxx.so","add"); /* 打开xxx.so共享库,取add函数地址 */add(89,369); /* 带两个参数89和369调用add函数 */
看我出招:
执行结果:
使用动态链接库,源程序中要包含dlfcn.h头文件,写程序时注意dlopen等函数的正确调用,编译时要采用-rdynamic选项与-ldl选项(不然编译无法通过),以产生可调用动态链接库的执行代码。
OK,通过本文的指导、练习相信各位应该对Linux的库机制有了些许了解,最主要的是会开发使用库文件了。由于本人知识所限,文中某些观点如果不到位或理解有误的地方还请各位个人不吝赐教。标签:&&&&&&&&&&&&&&&&&&&&&&&&&&&原文:http://www.cnblogs.com/MYSQLZOUQI/p/5901765.html
教程昨日排行
&&国之画&&&& &&&&&&
&& &&&&&&&&&&&&&&
鲁ICP备号-4
打开技术之扣,分享程序人生!}

我要回帖

更多关于 so预加载 的文章

更多推荐

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

点击添加站长微信