so库_so库文件 - CSDN
精华内容
参与话题
  • Android 调用so库全过程

    万次阅读 多人点赞 2017-12-05 10:41:59
    Android中有时候为了效率以及平台开发的支持,难免会用到NDK开发,那么都会产生一个so文件,通过native方法进行调用,开发和调用步骤很简单,这里就不多说了,本文主要来介绍,我们在使用so的时候总是会出现一些...

     原文地址点击打开链接


    一、前言

    Android中有时候为了效率以及平台开发库的支持,难免会用到NDK开发,那么都会产生一个so文件,通过native方法进行调用,开发和调用步骤很简单,这里就不多说了,本文主要来介绍,我们在使用so的时候总是会出现一些常见的问题,而现在插件化开发也很普遍了,有时候插件中也会包含一些so文件,需要加载,这时候也会出现一些问题。本文就来详细总结一下这些问题出现的原因,以及解决方法,主要还是通过源码来分析。

    二、涉及到的源码类

    因为本文主要通过分析源码来分析so使用的知识点和问题总结,所以涉及到了很多的源码类,这里就现提供一下:

    1、PackageManagerService.java
    +setNativeLibraryPaths:设置应用的native库路径
    +scanPackageDirtyLI:扫描包内容初始化应用信息

    2、ActivityManagerService.java
    +startProcessLocked:发送命令给Zygote进程启动一个虚拟机

    3、NativeLibraryHelper.java

    底层实现类:com_android_internal_content_NativeLibraryHelper.cpp

    +copyNativeBinariesWithOverride:释放apk中的so文件到本地目录

    +findSupportedAbi:遍历apk中的so文件结合abiList值得到应用支持的abi类型索引值

    4、LoadApk类和ApplicationLoaders类

    5、VMRuntime.java

    底层实现类:dalvik_system_VMRuntime.c

    +getInstructionSet:获取虚拟机的指令集类型

    +is64BitAbi:判断VM是否为64位

    6、Runtime.java

    底层实现类:dalvik/vm/native/java_lang_Runtime.cpp,dalvik/vm/Native.cpp

    +nativeLoad:加载so文件

    三、Android中so文件的编译平台

    Android中在进行NDK开发的时候,都知道因为机型杂而多的原因,没有一个大的标准,所以很多厂商都会采用不同型号的cpu,那么在编译so文件的时候,就需要进行交叉编译出多个cpu平台版本,现在主流的cpu架构版本:

    armeabi/armeabi-v7a:这个架构是arm类型的,主要用于Android4.0之后的,cpu值32位的

    x86/x86_64:这个架构是x86类型的,有32位和64位,占用的设备比例比较小

    arm64-v8:这个架构是arm类型,主要用于Android5.0之后,cpu是64位的

    这里可以看到,其中arm类型的是往下兼容策略,比如arm64-v8a肯定兼容armeabi/armeabi-v7a,也就是说armeabi/armeabi-v7a架构的so文件可以用在arm64-v8a的设备中的,而armeabi-v7a也是兼容armeabi的,但是因为cpu型号不同,所以arm体系和x86体系之间是不能相互兼容的。

    四、Android中so加载流程

    在Android中如果想使用so的话,首先得先加载,加载现在主要有两种方法,一种是直接System.loadLibrary方法加载工程中的libs目录下的默认so文件,这里的加载文件名是xxx,而整个so的文件名为:libxxx.so。还有一种是加载指定目录下的so文件,使用System.load方法,这里需要加载的文件名是全路径,比如:xxx/xxx/libxxx.so。

    上面的两种加载方式,在大部分场景中用到的都是第一种方式,而第二种方式用的比较多的就是在插件中加载so文件了。

    不管是第一种方式还是第二种方式,其实到最后都是调用了Runtime.java类的加载方法doLoad:


    这里会先从类加载中获取到nativeLib路径,然后在调用native方法nativeLoad(java_lang_Runtime.cpp):


    这里调用了一个核心的方法dvmLoadNativeCode(dalvik/vm/Native.cpp):


    注意:

    这里有一个检测异常的代码,而这个错误,是我们在使用插件开发加载so的时候可能会遇到的错误,比如现在我们使用DexClassLoader类去加载插件,但是因为我们为了插件能够实时更新,所以每次都会赋值新的DexClassLoader对象,但是第一次加载so文件到内存中了,这时候退出程序,但是没有真正意义上的退出,只是关闭了Activity了,这时候再次启动又会赋值新的加载器对象,那么原先so已经加载到内存中了,但是这时候是新的类加载器那么就报错了,解决办法其实很简单,主要有两种方式:

    第一种方式:在退出程序的时候采用真正意义上的退出,比如调用System.exit(0)方法,这时候进程被杀了,加载到内存的so也就被释放了,那么下次赋值新的类加载就在此加载so到内存了,

    第二种方式:就是全局定义一个static类型的类加载DexClassLoader也是可以的,因为static类型是保存在当前进程中,如果进程没有被杀就一直存在这个对象,下次进入程序的时候判断当前类加载器是否为null,如果不为null就不要赋值了,但是这个方法有一个弊端就是类加载器没有从新赋值,如果插件这时候更新了,但是还是使用之前的加载器,那么新插件将不会进行加载。

    继续往下看:


    这里主要调用了两个核心的系统方法,dlopen和dlsym,这两个方法用途还是很多的,一般是先加载so文件,然后得到指定函数的指针,最后直接调用即可,主要用于调用动态的调用so中的指定函数功能。而且这里注意到了最开始先调用so中的JNI_OnLoad函数,这个函数是so被加载之后调用的第一个方法。

    到这里我们就总结一下Android中加载so的流程:

    1、调用System.loadLibrary和System.load方法进行加载so文件

    2、通过Runtime.java类的nativeLoad方法进行最终调用,这里需要通过类加载器获取到nativeLib路径。

    3、到底层之后,就开始使用dlopen方法加载so文件,然后使用dlsym方法调用JNI_OnLoad方法,最终开始了so的执行。

    五、Android中类加载器关联so路径

    上面分析so加载过程中可以发现有一个地方,就是通过类加载器来获取到so的路径,那么Android中的主要类加载器有两个,一个是PathClassLoader和DexClassLoader,关于这两个类加载不多说了,网上资料很多可以自行查找阅读。而PathClassLoader是我们Android中默认的类加载器,也就是apk文件就是由他来加载的,我们可以通过查看源码得知,Android中加载apk的类加载可以从LoadApk.java类查找到:


    注意:

    这个类很重要的,而这个类加载器也是我们在做插件的时候,需要做一些操作,比如需要把加载插件的DexClassLoader类给添加到这个系统加载器中,就可以解决插件中组件的生命周期问题。

    看看这个类加载器在哪里赋值的:


    去看看ApplicationLoaders.java类:


    看到了,这里就是定义了PathClassLoader类了,所以我们Android中应用的默认加载器是PathClassLoader,再去看看这个类加载器的nativeLib是哪里:


    六、Android中so文件如何释放

    我们在使用System.loadLibrary加载so的时候,传递的是so文件的libxxx.so中的xxx部分,那么系统是如何找到这个so文件然后进行加载的呢?这个就要先从apk文件安装时机说起。

    我们如果还没有分析源码之前,大致能够猜想到的流程是:

    在安装apk的时候,系统解析apk文件,因为so文件肯定是存放在libs下指定平台目录中的,而apk文件本身就是一个压缩文件,所以可以进行解压,然后读取libs目录下的so文件,进行本地释放解压到指定目录,然后在加载的时候就先拼接so文件的全路径,最后在进行加载工作即可。

    通过猜想,下面就通过源码来分析一下流程,系统在安装apk的时候,是调用系统类:PackageManagerService.java类:

    主要的核心方法是scanPackageDirtyLI:


    这个方法主要通过传递的pkg变量,开始构造applicationInfo信息。我们往下面看,找到设置nativeLib信息的代码:


    这里注意有一个判断,是不是多平台架构的应用:


    所以,我们看看info.flags有没有设置这个标志,我们看到上面的pkg变量是通过解析apk文件的类PackageParser.java类中获取到的,所以可以去这个类中找这个标志位的设置。


    这里看到了,如果在AndroidManifest.xml中设置了Application中的multiArch属性值的话就有,但是我们默认都没有设置这个属性值,那么就是false,也就是说一般应用都不是多平台的。所以上面的isMultiArch方法就返回false,代码就走到了这里:


    在这里就有很多知识点了,而这里可以看到,就涉及到了so文件的释放工作了,主要是在NativeLibraryHelper类中,但是这里看到首先获取abiList值:


    通过Build.SUPPORTED_ABIS来获取到的:


    最终是通过获取系统属性:ro.product.cpu.abilist的值来得到的,我们可以使用getprop命令来查看这个属性值:


    这里获取到的值是:arm64-v8a,armeabi-v7a,armeabi,我用的是64位的cpu设备,所以可以看到他有多个cpu架构可选,而且看到这个顺序会想到,这个顺序正好是向下兼容的顺序。

    现在去看看NativeLibraryHelper类的copyNativeBinariesForSupportedAbi方法:


    这个方法中主要干了三件事:

    第一件事是获取应用所支持的arch架构类型

    第二件事是通过架构类型获取so释放的目录

    第三件事是native层中释放apk中的指定架构的so到设备目录中

    第一件事:获取应用所支持的arch架构类型

    NativeLibraryHelper类的findSupportedAbi方法,其实这个方法就是查找系统当前支持的架构型号索引值:


    看看native方法的实现:


    这里看到了,会先读取apk文件,然后遍历apk文件中的so文件,得到全路径然后在和传递进来的abiList进行比较,得到合适的索引值,其实实现逻辑很简单:abiList是:arm64-v8a,armeabi-v7a,armeabi,然后就开始比例apk中有没有这些架构平台的so文件,如果有,就直接返回abiList中的索引值即可,比如说apk中的libs结构如下:


    那么这时候返回来的索引值就是0,代表的是arm64-v8a架构的。如果apk文件中没有arm64-v8a目录的话,那么就返回1,代表的是armeabi-v7a架构的。依次类推。得到应用支持的架构索引之后就可以获取so释放到设备中的目录了。

    第二件事:获取so释放之后的目录

    这里主要通过VMRuntime.java中的getInstructionSet方法:


    这里调用了一个map结构值:


    这里的arch架构和目录对应关系,如果arch是arm64-v8a的话,那么目录就是arm64了。

    第三件事:释放apk中的so文件

    直接调用的是native层方法iterateOverNativeFiles:


    好了到这里就讲完了上面的三件事了,而这三件事做完之后,apk中的so文件就会被释放到本地设备中的指定目录中了,当然这里系统会根据abiList中的值以及apk中包含的arch类型的so来决定释放哪个目录中的so文件,比如这里通过ApplicationInfo类来打印当前应用的nativeLibraryDir值:


    打印的结果:


    看到了,因为是arm64-v8a类型的,所以目录是arm64的,而且可以看到这个应用不是多平台的。

    我们可以看到Android中是如何释放apk中的so文件到本地目录的:

    1、通过遍历apk文件中的so文件的全路径,然后和系统的abiList中的类型值进行比较,如果匹配到了就返回arch类型的索引值

    2、得到了应用所支持的arch类型之后,就开始获取创建本地释放so的目录

    3、然后开始释放so文件

    我们在PackageMangerService类中继续往下看:


    这里还要保存上面获取到应用支持的arch类型值,我们可以使用反射打印这个值:


    打印结果:


    这个值在后面应用创建VM的时候会用到。

    接着开始设置应用的nativeLib路径了:


    看看这个方法的实现:


    这里先判断是不是64位:


    通过arch类型对应的目录来判断的:


    这里如果是64位,目录就是lib,如果是32位就是lib64:


    这样就和我们上面释放so文件的目录保持一致了,所以这里的ApplicationInfo类中的lib路径就是我们上面释放so之后的路径了。

    在之前说到了类加载器中的lib路径,我们可以打印一下库路径的,这里直接使用getClassLoader得到加载器打印即可:

    这里看到Library的目录包含很多路径。

    七、Android中64位系统如何兼容32位的so

    上面分析完了,so文件的释放工作,下面继续来看一下如果一个64位系统的Android设备如何做到能够运行32位的so文件,这个就需要从应用的启动说起了,那么这个类就是ActivityManagerService.java,有一个核心的方法:startProcessLocked,这个方法就是向Zygote进程发送一个消息,为这个应用创建虚拟机开始运行程序了:


    这里在发送消息给Zygote进程,看到这里通过ApplicationInfo中的primaryCpuAbi类型告诉Zygote改创建多少位的虚拟机,我们查看系统启动文件init.rc内容:


    这里会启动一个64位的Zygote进程


    然后启动一个32位的Zygot进程

    所以这里应该就可以想明白了,原来系统启动的时候,如果是64位的系统设备,会启动两个Zygote进程用来兼容32位类型的应用,我们可以使用ps命令查看进程:


    看到了,这里果然启动了两个Zygote进程,一个64位的,一个是32位的。所以兼容功能的大致流程图应该是这样的:


    上层启动应用的时候会把应用的abi类型带过来,然后这里会根据这个类型发送给具体的Zygote进程消息,来创建虚拟机开始运行程序,这样就做到了兼容。

    八、Android插件中如何加载so文件

    有时候我们在开发插件的时候,可能会调用so文件,一般来说有两种方案:

    一种是在加载插件的时候,先把插件中的so文件释放到本地目录,然后在把目录设置到DexClassLoader类加载器的nativeLib中。

    一种在插件初始化的时候,释放插件中的so文件到本地目录,然后使用System.load方法去全路径加载so文件

    这两种方式的区别在于,第一种方式的代码逻辑放在了宿主工程中,同时so文件可以放在插件的任意目录中,然后在解压插件文件找到这个so文件释放即可。第二种方式的代码逻辑是放在了插件中,同时so文件只能放在插件的assets目录中,然后通过把插件文件设置到程序的AssetManager中,最后通过访问assets中的so文件进行释放。

    上面就全部分析完了Android中关于so加载的相关内容:

    1、so编译平台问题

    2、so加载流程分析

    3、so文件释放功能分析

    4、so文件兼容功能分析

    5、插件中so文件调用功能分析

    九、常见问题分析

    第一个问题:Could not find libxxx.so


    这个问题看上去很好理解,就是在调用加载so的方法的时候,到底层使用dlopen方法打开so文件,发现找不到这个so文件,那么这个问题产生的原因主要有两个:

    第一个是我们的确忘了在工程的libs下存放so文件了;

    第二个是我们把so文件放错目录了;

    第一个原因就不多说了,主要来看第二原因:

    有时候我们在开发项目的时候,可能会放多个架构类型的so文件,那么现在假如我的设备是arm64-v8a类型的,我的项目中有三个so文件,比如叫做AAA.so,BBB.so,CCC.so,然后我再arm64-v8a目录中放了AAA.so,BBB.so,而CCC.so忘了放了,但是会放到armeabi-v7a和armeabi目录中,那么这时候就会发生找不到CCC.so的错误,原因很简单:

    上面分析了apk中so文件的释放逻辑,系统会先遍历apk中所有so文件的全路径,然后在结合abiList的值来决定最终释放哪个目录中的so文件,那么现在系统是arm64-v8a了,而apk中的libs下也有arm64-v8a,所以这里就会把apk中的libs\arm64-v8a中的所有so文件释放解压到本地目录中,而不会在去释放armeabi/armeabi-v7a了。因为arm64-v8a中没有CCC.so文件,所以最终释放到本地目录中也是没有这个so文件的,所以加载时找不到文件了。

    解决办法:就是在使用so文件的时候,需要确定在每个架构类型目录中都要有相同的so文件即可。

    第二个问题:32-bit instead of 64-bit


    这个问题的原因主要是因为64位的Zygote进程创建的虚拟机中加载了32位的so文件,这个问题的产生原因主要有两个:

    第一个是我们把不同架构类型的so文件放错目录了,比如armeabi/armeabi-v7a的so文件放到了arm64-v8a中了

    第二个是我们在开发插件的过程中,宿主工程中有arm64-v8a目录,但是插件中加载so却是armeabi/armeabi-v7a类型的

    第一个原因就不多说了,主要是因为so放错目录了,来看一下第二个原因,我们在开发插件的时候有时候需要在插件中去加载so文件,一般都是使用System.load方式去加载全路径的so文件,那么这里就可能存在一个问题,比如宿主工程中,放了所有架构的目录,包括了64位的,因为考虑插件的大小,所以在插件中只放了armeabi-v7a目录的so文件,如果设备是64位的系统,那么这时候插件加载so文件就会报错。原因就在于上面分析的so兼容问题中说到了,因为宿主工程中包含了64位的架构arm64-v8a类型,系统的abiList中也有arm64-v8a类型,所以这时候应用的ApplicationInfo的abi就是arm64-v8a了,那么就会发送消息给Zygote64的进程,创建的也是64位的虚拟机了,而最后插件中加载so的类型是32位的armeabi-v7a,那么就会报错了,因为32位的so文件不能运行在64位的虚拟机中的。

    解决办法:宿主工程和插件工程中的so文件的架构类型保持一致,这个将会带来一个很大的问题,就是插件包会变得很大,因为宿主工程为了兼容多数机型,加入了多个类型的架构so文件,但是插件为了减小包大小,就放了指定类型的so文件,但是最终会存在这种问题,所以这个解决办法就要看项目需要了。

    还有一个类似的问题:64-bit instead of 32-bit:


    原理都是一样的,32位的虚拟机中加载了64位的so文件问题导致的。

    第三个问题:Shared library already opened


    这个问题在上面介绍so加载流程中已经介绍过了,原因主要是因为之前使用DexClassLoader加载so之后,so没有释放还在内存中,而在此启动有弄了一个新的DexClassLoader对象去加载so问题,就出错了。

    我们使用DexClassLoader类去加载插件,但是因为我们为了插件能够实时更新,所以每次都会赋值新的DexClassLoader对象,但是第一次加载so文件到内存中了,这时候退出程序,但是没有真正意义上的退出,只是关闭了Activity了,这时候再次启动又会赋值新的加载器对象,那么原先so已经加载到内存中了,但是这时候是新的类加载器那么就报错了。

    解决办法:

    第一种方式:在退出程序的时候采用真正意义上的退出,比如调用System.exit(0)方法,这时候进程被杀了,加载到内存的so也就被释放了,那么下次赋值新的类加载就在此加载so到内存了。

    第二种方式:就是全局定义一个static类型的类加载DexClassLoader也是可以的,因为static类型是保存在当前进程中,如果进程没有被杀就一直存在这个对象,下次进入程序的时候判断当前类加载器是否为null,如果不为null就不要赋值了,但是这个方法有一个弊端就是类加载器没有从新赋值,如果插件这时候更新了,但是还是使用之前的加载器,那么新插件将不会进行加载。

    十、技术概要

    本文主要介绍了Android中关于so的相关知识,主要包括so编译多架构问题,so加载流程问题,so释放问题,so系统兼容问题以及插件中加载so文件的功能解析,看完本文之后,我们需要了解到的知识点:

    1、在NDK开发时,可以指定多种架构类型编译出多种类型的so文件。

    2、so的加载流程主要是System类中的两个加载方法,最终都会调用Runtime中的nativeLoad的native方法,而这个native方法最终会调用dlopen来打开so文件,然后在调用dlsym方法调用so的JNI_OnLoad方法。

    3、关于apk文件在安装的时候释放so文件到本地目录中,主要是结合当前设备的abiList信息(这个信息主要是通过系统属性:ro.product.cpu.abilist值来获取的)和apk中不同类型架构,来决定最终释放哪个类型目录中的so文件,释放完成之后,还需要设置应用的nativeLib路径,以及应用的abi信息,因为这个abi信息在后面启动虚拟机的时候需要用到。

    4、因为现在有很多设备已经是64位系统了,但是为了兼容32位的so文件,所以这些64位系统就会在系统启动的时候创建两个Zygote进程,一个是64位的,一个是32位的,当一个应用启动的时候,需要创建虚拟机,那么这时候就会把应用的架构类型传递过去,系统会根据这个类型来交给哪个Zygote进程来处理这个应用启动事件。这样就可以做到so调用的兼容问题了。

    5、插件中加载so文件现阶段主要有两种方式,一种是先释放插件中的so文件到本地目录,然后设置DexClassLoader的nativeLib路径;还有一种方式是先释放插件中的so文件,然后调用System.load来加载全局路径的so文件。

    十一、问题总结

    本文还总结了在使用so文件的时候,会遇到的一些问题,主要是三个问题:

    1、so文件找不到问题

    这个问题一般是因为我们忘记放了so文件,或者是so文件没有放置全部,也就是没有在libs目录中所有的架构类型目录中放置。

    2、不同位数的虚拟机运行了不同位数的so文件

    这个问题一般是因为我们在libs目录中把so文件放错目录了,或者是宿主工程和插件工程中的so文件架构类型目录没有保持一致。

    3、类加载器加载so文件再次加载

    这个问题一般是因为插件开发中使用了不同的DexClassLoader去加载多次相同的so文件导致的。

    十二、知识延展

    我们在开发的过程中有时候想知道系统的位数,那么这里网上告知说有好几种方法,其实那些都是忽悠人的,特别是在使用这个api的时候:android.os.Build.CPU_ABI,我就是在项目中被这个方法坑爹了,这个方法其实不是获取系统的位数,而是获取当前应用的架构类型位数,就是我们前面分析的ApplicationInfo中的abi信息,我们可以查看一下源码:


    这里可以看到,这个字段已经被废弃了,因为他不靠谱呀,这个字段在Build类的static块中进行赋值的:


    这里会通过VMRuntime类的is64Bit方法来判断当前虚拟机的位数,来获取这个值


    这里还有两个系统属性:

    ro.product.cpu.abilist32是32位的所有arch架构类型

    ro.product.cpu.abilist64是64位的所有arch架构类型

    而这两个字段值的合集就是前面的ro.product.cpu.abilist属性值。


    而VMRuntime的is64Bit方法是native方法,实现如下:


    看到了,这里得到的是虚拟机的位数,那么就是上面的Zygote进程的位数了。那么问题就来了,假如我的设备是64位的,但是我的项目中没有arm64-v8a类型的so文件,这时候在解析apk进行释放so文件的时候,就会得知架构类型是armeabi/armeabi-v7a了,因为遍历apk文件,没有找到arm64-v8a类型的so文件,这时候应用的abi类型就是armeabi/armeabi-v7a了,这就是32位的了,就会通知32位的Zygote进程创建了一个32位的虚拟机,那么此时我的项目中通过Build.CPU_ABI得到的系统位数就是32了,那么完全不是我们想要的了。

    所以正确的获取系统位数的方法是:

    Android5.0系统之后,可以通过ro.product.cpu.abilist属性字段值来判断,如果这个字段值中包含了64的话,那么就是64位系统了

    Android5.0系统之前,需要通过ro.product.cpu.abi属性字段值来判断,不过5.0系统之前都是32位的,还没有出现64位呢。

    十三、选择适当架构类型减小包大小

    我们上面分析之后可以看到,如果想做到万无一失即,项目不报错,而且so运行效率也是非常高的话,就需要把那几个架构类型的so文件都要在项目中放一遍,那么这个问题就来了,如果so文件较大的话,apk包最终也是很大的,所以这里就需要做一次选择了。

    1、我们在开发一个项目的时候因为,整个项目的so文件结构我们可以控制,所以为了防止apk包增大,我们可以考虑只放几个架构类型的so文件,比如最好的是放armeabi类型的,因为首先现在大部分设备采用cpu型号都是arm的,少数采用x86或者是mips类型的,其次是防止了armeabi类型之后,对于armeabi-v7a和arm64-v8a就可以兼容了,不会存在报错问题。但是因为系统需要兼容所以就会出现so运行效率的问题了,最好的效率就是指定架构类型的so运行在对应架构类型的设备中。因为现在大部分的设备系统版本都是4.0以上了,所以armeabi-v7a架构类型用的比较多了,所以有时候为了效率问题,项目中只放了这个架构类型的so文件,那么像老版本的手机armeabi的话就会报错了,当然这个错误是可以接受的即可。

    2、有时候像x86和mips等少数类型架构的设备,开发程序的时候会单独出一个版本比如叫做xxx应用x86版本

    3、在开发SDK的时候,因为开发之后的SDK包是给其他app接入的,而对于接入的app,我们不能做太多的限制,所以理论上应该把所有架构类型的so都要提供,这样给需要接入的app进行选择即可,比如像百度地图SDK:


    十四、总结

    本文主要是介绍了Android中关于so的相关知识,而这些知识点都是在使用so文件中会经常用到的,同时一些问题也是我们会遇到的,这里只是做了一个总结,同时也给出了插件中加载so文件的方案已经遇到的问题解决思路等内容。


    展开全文
  • so动态和a静态的编译

    千次阅读 2018-06-09 11:32:39
    一般linux环境下的链接库分为a库和so库,分别表示静态链接库和动态链接库,其作用是把C程序编译好做成一种可执行链接文件,c主程序文件调用这些程序的函数接口是可以使用a库或so库,在主程序中只需要include含有库中...

    一般linux环境下的链接库分为a库和so库,分别表示静态链接库和动态链接库,其作用是把C程序编译好做成一种可执行链接文件,c主程序文件调用这些程序的函数接口是可以使用a库或so库,在主程序中只需要include含有库中提供的函数接口声明的头文件即可。

    一、生成a库

    首先,你需要写几个你要调用的函数的源文件,如test1.c、test2.c等。其代码如下:

    再将头文件写好(其实这里的头文件并不需要,若是编译成库的程序之间有调用关系的时候才用得到,只在调用链接库的时候编写头文件即可)。开始编译,将源文件编译成.o文件。命令行指令如下:


    然后开始生成a库,指令如下:


    注意:链接库前缀必须以lib开头

    得到libtest.a库文件后,然后将.a库链接到主程序中,写主程序main.c和头文件main.h 。

    加载a库,生成可执行文件并执行,指令如下:gcc main.c -L. -ltest -o test


    可以看到,a库连接成功,可以调用到里面的函数。

    二、生成so库

    利用上面所写过的test1.c 和test2.c生成.so库的指令如下:


    同样so库的前缀必须为lib,然后将.so库链接到主程序main.c中,这里需要注意一下,因为动态库的特性,编译器会到指定的目录去寻找动态库,目录的地址在/etc/ld.so.conf.d/ 目录里的libc.conf文件里,你可以在里面加一行地址表示你so库的位置,更改完conf文件里的内容,记得输入命令行:ldconfig。

    你还可以将so库复制到默认的目录下。这里是将so库复制到了默认目录下,生成可执行文件并运行,指令如下:

    gcc main.c -L. -ltest -o main


    静态链接库和动态链接库的区别在于,主程序在运行前,静态链接库的链接固定写入在程序中,而动态链接库则是在每次程序运行再加载链接。

    在加载动态链接库的时候,有可能会遇到加载不到的错误,原因在于系统默认加载的动态链接库路径里没有找到你的动态库,有三种解决方法:

    1.在执行gcc main.c -L. -ltest -o main 前,执行 export LD_LIBRARY_PATH=$(pwd)

    2.将你so所在的目录写到/etc/ld.so.conf文件里,然后执行ldconfig。

    3.将你的so放在/etc/ld.so.conf里的路径位置里。

    展开全文
  • so库方法的调用过程

    千次阅读 2019-09-14 23:05:42
    但程序运行时,这些地址早已经确定,那程序引用so库中的这些代码地址如何确定呢,这就是这次要整理学习的内容,即so库的在链接和执行时的加载过程。 静动态库 在聊so库之前先聊聊静态库。为了程序更加优雅...

         0. 写在前面

    So库,又动态名库,是Linux下最常见的文件之一,也是Android中最常见的文件之一,是一种ELF文件。这种so库是程序运行时,才会将这些需要的代码拷贝到对应的内存中。但程序运行时,这些地址早已经确定,那程序引用so库中的这些代码地址如何确定呢,这就是这次要整理学习的内容,即so库的在链接和执行时的加载过程。

    1. 静动态库

    在聊so库之前先聊聊静态库。为了程序更加优雅和高效,每一个程序的完成都是采用分而治之的方法,即同一个程序或者项目每个程序员都会完成不同的功能,有的功能是可复用的,而对于一些公共的可复用的功能,会使用库的形式来完成。比如我们在不同模块中多次用到了一个方法ar_public(),我们就可以将其包装到一个公共的文件里面,这样就如果其他的地方有调用就可以把引用这个公共文件从而调用这个方法,这样就有了静态库。简单来说静态库是链接的时候将库中所用到的程序拷贝进来,这样即使在执行阶段吧对应的库删掉都没有关系,因为此时对应方法的真实内容已经被拷贝过来了。但是虽然能做到方法共用,但带来最大的一个问题是如果是同一个项目的不同模块使用的话,使用的每个模块都会把这些拷贝过来,这样会使得应用的内存增大。也恰恰是由于会将静态库的这些方法拷贝进来如果静态库发生改变的话那程序需要重新编译。

    为了解决上面的问题,于是又有了一个动态库的概念。动态库,又称共享库链接的时候它只包含需要的函数引用表,只有在执行的时候那些需要的函数才能被拷贝到内存中,而且在操作系统使用的是虚拟内存,使得一份共享库驻留在内存中被多个程序使用,也同时节约了内存。

       2. 位置无关(PIC

    大家都知道,可执行文件在执行期的时候内存地址已经都确定了,而上面说的只有执行时才会确定那些函数地址拷贝到内存中,那基于这个特点大家第一想道的实现就是像那些段一样预留一个空间,但是这样做的一个最大问题就是会造成空间浪费,我们可以readelf去看下so库中的地址情况,从图一来看和data相关的地址都不是绝对地址(由于程序的起始加载地址都是从data开始,所以data相关的头如果不是绝对地址则可以认为加载的地址不固定)。

                                                                                图一

    在动态共享库中,如果库里面的代码发生改变,重新加载进来之后,我们必须保证它放到修改前的位置  ,否则我们还要为它找一个新的位置。而我们对于这个修改之后希望将动态库编译成可以在任意位置加载无需linker进行修改,这个叫做位置无关代码即PIC,也就是生成so库的-fPIC的那个PIC(这个指令就表示生成位置无关代码)。那PIC的原理是如何实现的呢,我们都知道数据段和代码段的距离在运行时是可以确定的,其中就是利用这一点来做地址定位的。如图二其实就是静态库中的一个变量,他的地址就是%rip+rel

                                                                                      图二

     3.静态分析:

    这个代码无关的特性具体是怎么实现的呢,我们先从静态的角度来分析下这些是怎么执行的。自己写一个引用一个最常见的printf函数(如图三),编译之后通过最常用的objdump –d 反编译,先看下print_banner()对应的反编译代码(如图四)

        

    图三

                                                                图四

    从main函数开始,跳转到print_baner,而print_baner里面最主要的方法是callq400400这个pc值,我们再看下601018(rip+200C12)内存的内容。

                                                                         图五

     使用GDB的看下对应的值是多少,发现这个值是0x400406<printf@plt+6>,即执行后面pushq $0x0,然后再jmpq到<printf@plt-0x10>,即pushq到,然后再jmp到,然后再退出,这样整个printf的方法就执行完了。从静态代码来看只是几次jmp和push就完成了这个在so库中调用printf的操作,的确是这样,不过是这些jmp到的方法有自己的规范和名称,这就是GOT和PLT。

                                                              图六

    1. GOTPLT

    首先我们说过这些是一个规范的有名称的,那么每一个可执行文件只要有这种so库的调用就一定会为他分配特定的存储空间。我们使用readelf看下(图7)

                                  图7

     

    关于GOT(),也叫全局偏移表,由于这个表和静态变量或者静态函数的相对地址是固定的,所以这个表的作用一个很大的作用是用来寻址。在上图中要注意的是.got的权限,是具有写权限的,也就是说这个在后面是会修改里面的值的,这个大家可以在对应的/proc下面去看下,这个地址是在data区的,关于这个是如何的写我们后面再看。

    在反汇编代码中有一个pushq $0x0的操作,这个实际上是将printf对应的GOT数组中的条目方法入栈,且printf的条目偏移地址为0x0,对应GOT条目是一个共享库符号值保留的,而这里的0x0实际上是push第四个GOT条目,即GOT[3],下面是出自计算机系统圣经的CSAPP中GOT表的截图(图中的printf就是和本文so库中的printf条目一样)。

                                                                                      图8

     第一个条目是指.dynamic段;第二个条目是指存放link_map结构的地址,动态链接器利用该地址来对符号进行解析,第三个条目是存放了指向动态链接器_dl_runtime_resolve()函数的地址,该函数用来解析共享库函数的实际符号地址,第四个条目就是printf的PLT[1]地址,也就是<printf@plt>的地址。

    下面说下PLT,在图五的反汇编中可以看到有很多的带plt的方法,这些都是plt表中对应的条目。在图五中可以看到首先进到的是<printf@plt>地址,这些汇编很简单,前面也说过这里的pushq 0x0是将GOT[3]入队,执行完<printf@plt>之后,执行的jmpq到<printf@plt-0x10>中,这里也很指令简单,只不过操作数比较复杂,先说下pushq 0x601008,这里地址就是前面说的GOT[1],即这个程序的link_map,下一条jmpq 0x601010,则是GOT[2],即_dl_runtime_resolve()函数的地址。后续控制权就交给动态链接器了,解析出printf的地址。

    对printf的解析完成之后,后面所有的对PLT条目中printf的调用都会直接跳转到printf中,而不是重新再进行这些跳转。通过watch 第一次jmp的值就可以看到,执行完成之后值以及由0x400406变化到0xFFFFFFFFF7A62800。

                              图9

    东一句西一句啰嗦了这么多,其实总结起来就是对so库的里面方法的调用:

    1. 调用函数先跑到被调用的so库中方法的PLT(printf@plt)方法里面;
    2. PLT代码做一次到GOT中地址的间接跳转;
    3. GOT条目存放了指向PLT的地址,该地址存放在push指令中;
    4. push $0x0指令将printf() GOT条目的偏移量压栈;
    5. 最后的printf() PLT指令是指向PLT-0代码的jmp指令;
    6. PLT-0的第一条指令将GOT[1]的地址压栈,GOT[1]中存放了指向printf()的link_map结构的偏移地址;
    7. PLT-0的第二条指令会跳转到GOT[2]存放的地址,该地址指向动态链接器的_dl_runtime_resolve函数,_dl_runtime_resolve函数会通过把printf()函数的符号值加到.got.plt节对应的GOT条目中,来处理重定位。
    8. 下一次再做跳转的时候PLT条目会直接跳转到函数本身

     

    在这里补充几点:1.为何引用so的方法需要先通过PLT再到GOT,这里这样做是因为这个data段的权限能读能写,而text段只有写的权限;2.这写的是so库方法的加载过程,而如果是仅是变量的话是由/lib/ld-linux.so.2填充的;3.关于_dl_runtime_resolve方法也可以去网上找下源码和实现,还有一些关于重定位相关的内容,等下次再总结分析吧,这个发生在so库之前,还有就是有的时候可以利用GOT的写权限做一些劫持的工作。

     

     

     

     

     

     

     

     

     

    https://www.cnblogs.com/cdcode/p/5551649.html

    https://blog.csdn.net/ylcangel/article/details/18145155

    https://www.jianshu.com/p/eca50b89a423

    https://docs.oracle.com/cd/E24847_01/html/E22196/chapter6-14428.html

    https://www.cnblogs.com/fellow1988/p/6158240.html

    https://blog.csdn.net/linyt/article/details/51635768

    https://blog.csdn.net/conansonic/article/details/54634142

    https://www.cnblogs.com/xingyun/archive/2011/12/10/2283149.html

    https://www.freebuf.com/articles/system/135685.html

    https://bbs.pediy.com/thread-221821.htm

    https://www.cnblogs.com/pannengzhi/p/2018-04-09-about-got-plt.html

    https://www.cnblogs.com/LittleHann/p/4244863.html

     

     

    展开全文
  • so库你应该知道的基础知识

    千次阅读 2020-04-01 16:58:54
    LLDB全称Low Level Debugger ,并不是低水平的调试器,而是轻量级的高性能调试器 每个操作系统都会为运行在该系统下的应用程序提供应用程序二进制接口(Application Binary Interface,ABI) 1. 什么是CPU架构及ABI ...

    LLDB全称Low Level Debugger ,并不是低水平的调试器,而是轻量级的高性能调试器

    每个操作系统都会为运行在该系统下的应用程序提供应用程序二进制接口(Application Binary Interface,ABI

    1. 什么是CPU架构及ABI

    Android系统目前支持以下七种不同的CPU架构:ARMv5,ARMv7 (从2010年起),x86 (从2011年起),MIPS (从2012年起),ARMv8,MIPS64和x86_64 (从2014年起),每一种都关联着一个相应的ABI。

    应用程序二进制接口(Application Binary Interface)定义了二进制文件(尤其是.so文件)如何运行在相应的系统平台上,从使用的指令集、内存对齐到可用的系统函数库。在Android系统上,每一个CPU架构对应一个ABI:armeabi,armeabi-v7a,x86,mips,arm64-v8a,mips64,x86_64。

    2. 为什么需要重点关注.so文件

    如果项目中使用到了NDK,它将会生成.so文件,因此显然你已经在关注它了。如果只是使用Java语言进行编码,你可能在想不需要关注.so文件了吧,因为Java是跨平台的。但事实上,即使你在项目中只是使用Java语言,很多情况下,你可能并没有意识到项目中依赖的函数库或者引擎库里面已经嵌入了.so文件,并依赖于不同的ABI。

     

     

    Android应用支持的ABI取决于APK中位于lib/ABI目录中的.so文件,其中ABI可能是上面说过的七种ABI中的一种。

    Native Libs Monitor这个应用可以帮助我们理解手机上安装的APK用到了哪些.so文件,以及.so文件来源于哪些函数库或者框架。当然,我们也可以自己对APP反编译来获取这些信息,不过相对麻烦一些。

    很多设备都支持多于一种的ABI,例如ARM64和x86设备也可以同时运行armeabi-v7a和armeabi的二进制包。但最好是针对特定平台提供相应平台的二进制包,这种情况下运行时就少了一个模拟层(例如x86设备上模拟arm的虚拟层),从而得到更好的性能(归功于最近的架构更新,例如硬件fpu,更多的寄存器,更好的向量化等)。

    我们可以通过Build.SUPPORTED_ABIS得到根据偏好排序的设备支持的ABI列表。但你不应该从你的应用程序中读取它,因为Android包管理器安装APK时,会自动选择APK包中为对应系统ABI预编译好的.so文件,如果在对应的lib/ABI目录中存在.so文件的话。

    3. .so文件应该放在什么地方

    我们往往很容易对.so文件应该放在或者生成到哪里感到困惑,下面是一个总结:

    Android Studio工程放在main/jniLibs/ABI目录中(当然也可以通过在build.gradle文件中的设置jniLibs.srcDir属性自己指定)
    Eclipse工程放在libs/ABI目录中(这也是ndk-build命令默认生成.so文件的目录)
    AAR压缩包中位于jni/ABI目录中(.so文件会自动包含到引用AAR压缩包的APK中)
    最终APK文件中的lib/ABI目录中
    通过PackageManager安装后,在小于Android 5.0的系统中,.so文件位于app的nativeLibraryPath目录中;在大于等于Android 5.0的系统中,.so文件位于app的nativeLibraryRootDir/CPU_ARCH目录中。

     

    4. 安装Apk时PackageManagerService选择解压so文件的策略

    在Android系统中,当我们安装Apk文件的时候,lib目录下的so文件会被解压App的原生库目录,一般来说是放到/data/data/package-name/lib目录下,而根据系统和CPU架构的不同,其拷贝策略也是不一样的,不正确地配置so文件,比如某些App使用第三方的so时,只配置了其中某一种CPU架构的so,可能会造成App在某些机型上的适配问题。

    Android版本    so拷贝策略    策略问题
    < 4.0    遍历Apk中文件,当Apk中lib目录下主abi子目录中有so文件存在时,则全部拷贝主abi子目录下的so;

    只有当主abi子目录下没有so文件的时候才会拷贝次abi子目录下的so文件。    当so放置不当时,安装Apk时会导致拷贝不全。比如Apk的lib目录下存在armeabi/libx.so,armeabi/liby.so,armeabi-v7a/libx.so这3个so文件,那么在主abi为armeabi-v7a且系统版本小于4.0的手机上,Apk安装后,按照拷贝策略,只会拷贝主abi目录下的文件即armeabi-v7a/libx.so,当加载liby.so时就会报找不到so的异常。另外如果主abi目录不存在,这个策略会遍历2次Apk,效率偏低
    4.0-4.0.3    遍历Apk中所有文件,如果符合so文件的规则,且为主abi目录或者次abi目录下的so,就解压拷贝到相应目录。    存在同名so覆盖,比如一个App的armeabi和armeabi-v7a目录下都包含同名的so,那么就会发生覆盖现象,覆盖的先后顺序根据so文件对应ZipFileR0中的hash值而定,考虑这样一个例子,假设一个Apk同时有armeabi/libx.so和armeabi-v7a/libx.so,安装到主abi为armeabi-v7a的手机上,拷贝so时根据遍历顺序,存在一种可能即armeab-v7a/libx.so优先遍历并被拷贝,随后armeabi/libx.so被遍历拷贝,覆盖了前者。本来应该加载armeabi-v7a目录下的so,结果按照这个策略拷贝了armeabi目录下的so。
    > 4.0.4    遍历Apk中文件,当遍历到有主abi目录的so时,拷贝并设置标记hasPrimaryAbi为真,以后遍历则只拷贝主abi目录下的so,当标记为假的时候,如果遍历的so的entry名包含次abi字符串,则拷贝该so。    经过实际测试,so放置不当时,安装Apk时存在so拷贝不全的情况。这个策略想解决的问题是在4.0~4.0.3系统中的so随意覆盖的问题,即如果有主abi目录的so则拷贝,如果主abi目录不存在这个so则拷贝次abi目录的so,但代码逻辑是根据ZipFileR0的遍历顺序来决定是否拷贝so,假设存在这样的Apk,lib目录下存在armeabi/libx.so, armeabi/liby.so, armeabi-v7a/libx.so这三个so文件,且hash的顺序为armeabi-v7a/libx.so在armeabi/liby.so之前,则Apk安装的时候liby.so根本不会被拷贝,因为按照拷贝策略,armeabi-v7a/libx.so会优先遍历到,由于它是主abi目录的so文件,所以标记被设置了,当遍历到armeabi/liby.so时,由于标记被设置为真,liby.so的拷贝就被忽略了,从而在加载liby.so的时候会报异常。
    64位    分别处理32位和64位abi目录的so拷贝,abi由遍历Apk结果的所有so中符合bilist列表的最靠前的序号决定,然后拷贝该abi目录下的so文件。    策略假定每个abi目录下的so都放置完全的,这是和4.0以前版本一样的处理逻辑,存在遗漏拷贝so的可能。
    5. 配置so的建议

    针对Android 系统的这些拷贝策略的问题,我们给出了一些配置so的建议:

    5.1 针对armeabi和armeabi-v7a两种ABI

    方法1:由于armeabi-v7a指令集兼容armeabi指令集,所以如果损失一些应用的性能是可以接受的,同时不希望保留库的两份拷贝,可以移除armeabi-v7a目录和其下的库文件,只保留armeabi目录;比如Apk使用第三方的so只有armeabi这一种ABI时,可以考虑去掉Apk中lib目录下armeabi-v7a目录。

    方法2:在armeabi和armeabi-v7a目录下各放入一份so。

    5.2 针对x86

    目前市面上的x86机型,为了兼容arm指令,基本都内置libhoudini模块,即二进制转码支持,该模块负责把ARM指令转换为x86指令,所以如果是出于Apk包大小的考虑,并且可以接受一些性能损失,可以选择删掉x86库目录,x86下配置的armeabi目录的so库一样可以正常加载使用。

    5.3 针对64位ABI

    如果App开发者打算支持64位,那么64位的so要放全,否则可以选择不单独编译64位的so,全部使用32位的so,64位机型默认支持32位so的加载。比如Apk使用第三方的so只有32位ABI的so,可以考虑去掉Apk中lib目录下的64位ABI子目录,保证Apk安装后正常使用。

    5. Android Studio配置abiFilters

    android {
        defaultConfig {
            ndk {
                abiFilters 'armeabi-v7a' //, 'armeabi', 'arm64-v8a', 'x86', 'x86_64', 'mips', 'mips64'
            }
        }
    }
    这句话的意思就是指定NDK需要兼容的架构,把除了armeabi-v7a以外的兼容包都过滤掉,只剩下一个armeabi-v7a的文件夹。

    即使我们没有指定其他的兼容框架,也需要一个过滤。当我们接入多个第三方库时,很可能第三方库做了多个平台的兼容。譬如fresco就做了各个平台的兼容,所以它创建了各个兼容平台的目录。因为只要出现了这个目录,系统就只会在这个目录里找.so文件而不会遍历其他的目录,所以就出现了找不到.so文件的情况。

    6. java.lang.UnsatisfiedLinkError

    该错误类型较多,以下进行分类:

    java.lang.UnsatisfiedLinkError : dlopen failed: library //dlopen打开失败
    java.lang.UnsatisfiedLinkError :findLibrary returned null //找不到library
    java.lang.UnsatisfiedLinkError : Native method not found //找不到对应函数
    java.lang.UnsatisfiedLinkError :Cannot load library: load_library //无法load library
    出现原因:

    显然出现上述崩溃的根本原因是:

    (1)so无法加载,可能是so不存在等原因

    (2)so正常加载,但是没有找到相应的函数

    针对第二个原因,显然相对来说很容易排查,而且在开发中,这样的函数调用必然会在编译时和debug模式下进行测试,所以这种原因产生的概率很小。

    那么下面主要总结几类“so无法加载”而导致上述崩溃的几种原因:

    6.1 生成的so本身缺陷

    一个简单的例子:

    crash堆栈:

    java.lang.UnsatisfiedLinkError: Cannot load library: find_library(linker.cpp:889): "/data/data/com.netease.nis.apptestunit/app_lib/libdemo.so" failed to load previously
     at java.lang.Runtime.load(Runtime.java:340)
     at java.lang.System.load(System.java:521)
     at com.netease.nis.bugrpt.ReLinker.loadLibrary(ReLinker.java:76)
     at com.example.crash.MainActivity.onCreate(MainActivity.java:272)
     at android.app.Activity.performCreate(Activity.java:5220)
     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1086)
     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2193)
     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2279)
     at android.app.ActivityThread.access$600(ActivityThread.java:142)
     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1272)
     at android.os.Handler.dispatchMessage(Handler.java:99)
     at android.os.Looper.loop(Looper.java:137)
     at android.app.ActivityThread.main(ActivityThread.java:5105)
     at java.lang.reflect.Method.invokeNative(Native Method)
     at java.lang.reflect.Method.invoke(Method.java:511)
     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
     at dalvik.system.NativeStart.main(Native Method)
    解决方法:

    查看原项目Application.mk,发现APP_STL := gnustl_shared。原方案使用的是共享库,这不一定都支持所有的机型,改用静态库gnustl_static问题解决。 
    对应的在Android Studio中需要将共享库改用静态库gnustl_static。这一类关于so编译共享库问题,需要进行检查。

    APP_STL 可用值
     
    system  系统默认
    stlport_static - 使用STLport作为静态库
    stlport_shared - 使用STLport 作为共享库
    gnustl_static  -  使用GNU libstdc++ 作为静态库
    gnustl_shared -  使用GNU libstdc++ 作为共享库
    上述例子只是一个简单的例子,可能在so编译生成时,由于没有考虑共享库的机型匹配等原因导致UnsatisfiedLinkError崩溃,其次是64位32位系统架构问题,也可能导致UnsatisfiedLinkError崩溃。

    6.2 手机设备没有空间

    在so正确生成情况下,会根据设置的支持so库框架生成对应的库。在Android系统中,当我们安装Apk文件的时候,lib目录下的so文件会被解压到App的原生库目录,一般来说是放到/data/data/package-name/lib目录下,当准备加载native层的so时,虽然在Apk中有对应的so文件,但是由于手机设备没有足够的空间加载该so,导致加载失败,产生上述崩溃。

    6.3 so配置错误

    倘若so正确生成,且手机空间充足,那么如上所述,在Android系统中,当我们安装Apk文件的时候,lib目录下的so文件会被解压到App的原生库目录,一般来说是放到/data/data/package-name/lib目录下。但是根据系统和CPU架构的不同,其拷贝策略也是不一样的。倘若不正确地配置了so文件,比如某些App使用第三方的so时,只配置了其中某一种CPU架构的so,可能会造成App在某些机型上的适配问题,产生上述崩溃。

    6.4 Android的PackageManager安装问题

    用户安装了与手机CPU架构不符的Apk安装包,或者App升级过程中因各种原因未正确释放so文件。这种问题可以使用ReLinker解决。

    使用ReLinker十分简单,使用

    ReLinker.loadLibrary(context, “mylibrary”)
    代替标准的即可。

    System.loadLibrary(“mylibrary”);
     

    展开全文
  • 在linux环境下的链接库分为静态链接库(.a库)和动态链接库(.so库),其作用是把C程序编译好做成一种可执行链接文件,主程序文件调用这些程序的函数接口是可以使用a库或so库,在主程序中只需要include含有库中提供...
  • so库

    千次阅读 2019-07-31 09:51:09
    简介 xxx 分类 armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64
  • 关于so文件你需要知道的知识

    万次阅读 2017-08-01 17:13:57
    so abi UnsatisfiedLinkError
  • ijkplayer编译so库真没那么难

    万次阅读 2018-01-22 22:53:57
    ijkplayer编译so库真没那么难 引言: 公司现在的电台项目是我第二个接触音频播放项目,Android音视频 播放很多还是使用的MediaPlayer(大中厂除外),但是如果你用过 MediaPlayer的话,很多开发者都会吐槽有多坑...
  • linux环境下生成a库和so库

    千次阅读 2018-03-22 14:50:25
    一、编译链接库(a库和so库)方法一般linux环境下的链接库分为a库和so库,分别表示静态链接库和动态链接库,其作用是把C程序编译好做成一种可执行链接文件,c主程序文件调用这些程序的函数接口是可以使用a库或so库,...
  • Android Studio制作.so库实践

    万次阅读 2016-04-28 16:01:55
    前言因为工作需要可能要用到JNI开发,本篇文章就分享一下我在这方面的实践,以前我们使用Eclipse做NDK开发,非常麻烦,需要配cygwin的编译环境,后面...JNI_Android项目中调用.so动态库Eclipse的如何通过NDK生成so库
  • 动态加载so库的实现方法与问题处理

    万次阅读 热门讨论 2017-05-26 10:09:32
    前一阵项目上要求实现App的so库动态加载功能,因为这块本来就有成熟的方案,所以一般的实现没什么难度。可是到项目测试中,才发现有不少意料之外的情况,需要一一针对处理,故此记录一下具体的解决办法,以供后来者...
  • android开发 加载so库的解析和出现的各种错误分析

    万次阅读 多人点赞 2018-09-28 16:44:42
    一.android目前有几种cpu架构?   早期的Android系统几乎只支持ARMv5的CPU架构,你知道现在它支持多少种吗?7种! ...Android系统目前支持以下七种不同的CPU架构:ARMv5,ARMv7 (从2010年起),x86 (从2011年起),...
  • Android 静态和动态的调用so库(JNI)

    万次阅读 2017-12-26 10:43:17
    静态编译不再多说,可以查看这篇文章:《Android6.0 NDK 和 .So 之间的关系》优点为什么我们需要动态加载?因为静态加载中CPU的文件夹我们可能需要兼容的话需要放在不同arm文件夹下,那么就会导致apk 包体过大,安全...
  • android开发ndk调用第三方so库

    万次阅读 2016-10-28 16:26:01
    1.0 功能概述1.0.1 概述在android开发中,java调用C/C++函数是经常遇到的,我们的android开发中使用JNI开发技术,有下面几种情况: 已经使用C/C++发了某些函数,如果再使用java封装相应的函数的话,可能会话费...
  • Android 编写so文件的方式主要有两种:一种是通过ndk-build(),此方法在Android Studio 3.0.1之后就不建议使用了, ...如何调用so库文件? 文章目录编译环境如何生成so文件?1. 通过ndk 编译生成so(已...
  • 关于第三方so库调用网上的教程看了很多,但是因为非常小白,很多细节还是不明白, 关于so库的导入已经了解了,但是Cmakelists.txt文件里面该如何编写, 以及Java和so库如何通信,调用函数还是搞不懂。 比如说我有a....
  • Android查看当前应用已经加载的so库

    千次阅读 2019-10-25 11:17:18
    源代码: private static List<String> allSOLists = new ArrayList<... * 获取全部已加载的SO库 */ private void getAllSOLoaded(){ allSOLists.clear(); // 当前应用的进程ID ...
  • 正好动态加载系列文章谈到了加载SO库的地方,我觉得这里可以顺便谈谈使用SO库时需要注意的一些问题。或许这些问题对于经常和SO库开发打交道的同学来说已经是老生长谈,但是既然要讨论一整个动态加载系列,我想还是有...
  • Android studio 多个so库配置 ffmpeg库配置 cmake编译

    千次阅读 热门讨论 2018-02-06 09:27:12
    这里要以ffmpeg的so库配置为例,ffmpeg需要jni开发环境支持,想要了解Android Studio 的jni环境配置请到这里查看。如果想要下载ffmpeg编译好的so库请到这里下载。 现在就在这篇文章配置好的环境基础上进行ffmpeg的...
  • Android源码中编译自己的so库

    千次阅读 2016-07-02 19:37:39
    有时候,我们反编译apk得到一个so库,如果直接使用这个so库的话,必须使用原来so库同样的package名字,才能用。 这样人家反编译你的apk,就知道你侵犯了人家的版权。为了达到混淆的目的,我们可以再写一个so库
1 2 3 4 5 ... 20
收藏数 335,958
精华内容 134,383
关键字:

so库