Android 深入系统完全讲解(9)

news2024/11/27 5:26:17

3 JNI 调试

开发 android 应用,主要围绕着 java 语言,可是,如果我们需要追求性能,或者需要调用之前我们已经写好的 c c++ so 库的时候,或者和硬件打交道的时候,那么我们就会接触到 JNI(java native interface)。

我们知道,java 是在其虚拟机里面运行。我们简单举个例子吧。我让你使用任何一个语言,写一段代码:打开一个文件,读取每一行,如果这一行内容是 1,我们就在窗口显示生活真美好。 想一下,是否都能写出来?

这里举的例子,简单的解释了虚拟机的动作,打开一个文件(输入),读取每一行(内容),如果这一行内容是 1(解析),我们就在窗口显示生活真美好(输出),比起这个来说,java虚拟机比这个复杂,但是基础核心的原理就是这个了。

我们本节去讲一个内容,System.loadLibrary(XXXX) 的执行过程。此过程完成将 so 库加载进来,打通 java 和 c c++本地库的桥梁,实现相互调用。(此文牵扯概念 javaVM JNI ELF动态库静态库)

在这里插入图片描述
嗯,我们这节,就是展看 loadLibrary,来看这个方法都做了哪些事情。

在这里插入图片描述
我们这里看下参数:libname 将加载的库名字,比如我们库为 libtest_jni.so 这里则写为test_jni,其余的系统会帮我们拼接。继续向下看,发现调用了 Runtime 类里面的 loadLibrary0方法,我们看下:

在这里插入图片描述
我们看到有两个参数:第一个为 Classloader,这个为我们的类加载器,我们这里的参数为VMStack.getCallingClassLoader(),于是我们看下这段代码。

在这里插入图片描述
看到这里为 native,于是它本身是使用 c 或者 c++本地语言编写的了,我们找下位置。通过搜索 getCallingClassLoader,我们找到了本地实现的地方在 dalvik_system_VMStack.cc 里面,于是我们截图,来看下。

在这里插入图片描述
这里 NATIVE_METHOD 是个宏定义

在这里插入图片描述
于是

在这里插入图片描述
会转化为这个就是 jni 编写中,需要配置的对应表,主要完成 java 和 c 语言函数对应,参数和返回值对应的关系,给了这些,虚拟机才会在 java 和 c 之间建立起来关系,知道哪个 java 函数调用的真正正确的 c 语言函数,同时 c 也是可以反向调用 java 的,更多可以百度 jni 的编写。

关于 getCallingClassLoader 这个是如何加入到系统的,就是上面的register_dalvik_system_VMStack 方法了。

我们不对这里展开了,此方法是在 runtime.cc 的 InitNativeMethods 方法里面的RegisterRuntimeNativeMethods 完成。有兴趣的可以去看看。我们继续跟踪 system.loadLibrary,这里继续看 VMStack.getCallingClassLoader()。

通过上面的展看,我们知道了这个对应的 c 方法为:VMStack_getCallingClassLoader,于是我们看到:
在这里插入图片描述

这里因为不熟悉,就不讲了。
loadLibrary0 里面主要调用的方法为:

loader.findLibrary(libraryName); 去查找是否存在此动态库,没有就报找不到异常。
然后我们调用 doLoad 去加载

doLoad 主要完成,传入设置的 librarySearchPath,然后调用本地代码 nativeLoad 方法。搜索nativeLoad,我们找到了它对应的实现,在 Runtime.c
里面

在这里插入图片描述
根据之前展开的方式,此函数为:Runtime_nativeLoad,于是我们看到:

在这里插入图片描述
在 OpenjdkJvm.cc 里面:

在这里插入图片描述
关键方法,通过拿到当前的虚拟机 vm,调用对应的 LoadNativeLibrary(java_vm_ext.cc)方法,去真正加载对应的 so。

我们来到 java_vm_ext.cc 里面,去看下 LoadNativeLibrary 真正的执行过程:

在这里插入图片描述
这里我们关系的是高亮的几个函数:OpenNativeLibrary,完成加载 so 的过程。
FindSymbol(“JNI_OnLoad”)完成找出 so 里面的 JNI_OnLoad 方法,如果有,使用
(*jni_on_load)(this, nullptr)调用,返回 so 使用的 java 版本。这个 JNI_OnLoad 就是我们加载
so 的时候,会主动触发的一个初始化方法了。在这里主要完成 java 和 c 的对应关系方法,
然后使用 RegisterNativeMethods 将此关系注册进入 vm,以便后续调用能够找到。
扩展:
用于 Android ART 虚拟机 JNI 调用的 NativeBridge 介绍,地址为:
http://www.aichengxu.com/android/1473706.htm
我们停一下,完成一个简单的测试 demo 代码,以便我们调试使用。
参考 http://blog.csdn.net/a332324956/article/details/8703286 来写一个 JNI
搭配着 eclipse 去编译出来一个 libtest_jni.so。(后续此工程会直接提供下载)

在这里插入图片描述
工程目录为:这里 jni 就是需要编出来 so 的地方。我们右键 jnidemo 选择 properties,然后选择下 Builders,点击 new,创建一个编译规则。

在这里插入图片描述
编写一个调试:

这里 Location 指的是 ndk-build 脚本位置
Working Directory 指的是当前项目的 src/jni,我们要使用 ndk-build 将 jni 目录下的 android.mk执行,完成生成 so 的动作。

最后生成出来 libtest_jni.so我们在 java 工程使用下。(我们要在此基础上进行调试,所以我们使用的是自己 load,不是写在 static 语句里面)

在这里插入图片描述
完整代码,文章最后提供,可以看着代码然后阅读。
我们在 loadLibrary0 上面打断点,然后看下流程:

我们可以看到看到,这里的 loader 为 PathClassLoader.java,所以此处的 findLibrary 就是
PathClassLoader.java 文件里面的了。然后发现 PathClassLoader 继承自 BaseDexClassLoader,
于是我们关心 BaseDexClassLoader 代码。
此段代码,完成在此 app 的本地 so 库的搜索路径下,查找我们的 test_jni 动态库,找到后
path 返回此 so 的绝对路径,以使后面的 dlopen 去动态打开此库。在此处,libname 就是
/data/app/com.example.jnidemo-2/lib/arm/libtest_jni.so,这个就是我们的 jni 动态库真正的位
置了。
关于动态库 dlopen dlsym 的用法,参照 http://blog.csdn.net/edonlii/article/details/8445239 主要就是打开 so,然后找到对应函数,然后执行。
按照这个文档,去调试 so(需要下载 android 的 ndk)
http://blog.csdn.net/kaiqiangzhang001/article/details/21108857
打上断点的截图为:

在这里插入图片描述
我们这里提供一个 Android 的加载/链接器 linker 的讲解
http://blog.csdn.net/dinuliang/article/details/5509009
关于 android linker 的代码位置 bionic/linker,可以去阅读。
我们回到之前的讲解,来找下 LoadNativeLibrary 调用的 OpenNativeLibrary 方法。在
native_loader.cpp 文件内找到此文件。

这里 android 调用了 android_dlopen_ext 方法,来实现动态库的加载,返回 dlextinfo,而非
android 的,则是调用 dlopen 加载的。
我们搜索 android_dlopen_ext,发现在 /bionic/libdl/里面的/bionic/libdl/libdl.c 里面有看,是个空方法,没有实际动作,看到这里的注释,意思是我们的 dynamic linker 实现了这
个方法,我们找到 linker(手机里面的/system/bin/linker),我们在 linker 的源码里面 dlfcn.cpp
找到 android_dlopen_ext

在这里插入图片描述
但是在最终编译出来的 linker 里面是被修改成了__dl_android_dlopen_ext
找到 linker 文件里面的方法,具体的操作是:

在这里插入图片描述
关于 linker 的启动,可以参考 http://www.myexception.cn/android/1930690.html 阅读。同时
adnroid 源码也是提供了一个简单解释:
在/development/ndk/platforms/下面的 README.CRT.TXT 文件,有如下内容:

完整的我提交网盘了,可以去下载阅读。
在 bionic/linker 里面的 Android.mk 文件,发现了一段注释,可以解释__dl_android_dlopen_ext
和 android_dlopen_ext 怎么变化的。

在这里插入图片描述
生成 linker 的时候,使用了 objcopy 修改了方法名。
我们调试 linker 的代码,我们因为加载的是__dl_android_dlopen_ext ,于是我们 gdb 下断点
b __dl_android_dlopen_ext ,这样子我们打断点,运行时候会在加载动态库时候,停下来:

可以看到,断点成功。
info sharedlibrary 查看当前需要的 so。
info breakpoints 查看断点信息
bt 查看堆栈
b 方法 下断点
delete num 删除对应断点。
file XXX.so (有调试信息的库,然后我们调试,就会变成有效信息)

关于 gdb 的使用,可以参考
http://blog.csdn.net/ghostyu/article/details/8083228
关于 solib-absolute-prefix 和 solib-search-path 的区别 ,可以参考:
http://blog.csdn.net/caspiansea/article/details/16798735
我们这里看到了一个地址信息,又没有显示出来,这里为 0xaafceefa,我们想找到这个地址,
对应的代码,该如何找呢?
adb shell

我们关心的是 10171(进程 id),然后我们查找/proc/10171/maps
cat /proc/10171/maps ,找到 aafc 是在这个位置:
aada1000-ab1f4000 r-xp 00000000 103:08 1377 /system/lib/libart.so
于是我们 file 加载下这个 libart.so
然后重新调试,看效果:

在这里插入图片描述
看#2,是不是出来了。
我们打断点,发现 b android_dlopen_ext 和 b __dl_android_dlopen_ext 是一个位置
(bionic/linker/dlfcn.cpp line 82).所以我们实际的 android_dlopen_ext 就是
__dl_android_dlopen_ext,也就是 dlfcn.cpp 文件内容了。
我们将 bionic 放置到我们调试的 ndk-gdb --start 目录,再次调试,代码就检索出来了。

漫漫长路,我们又可以启程了,我们当前需要阅读的代码,就围绕着 android_dlopen_ext
(dlfcn.cpp)函数开展了。先开心看一个内容,这里我将编译出来的所有 so 加载进来了,
我们看到调试栈就会变成:

看到没,调用信息一目了然。
我们看下追踪这条代码线,可以找到我们的调用关系:
android::OpenNativeLibrary -->dlopen_ext–>do_dlopen–>find_library–>find_libraries.再追下去
就没完没了了,这些方法,都是有源码的,于是我们从源码去看看吧。

在这里插入图片描述
再看个 ElfReader 的 load 即可(更深层次的自行学习了),参考链接器与加载器那本书嗯,我们就讲到这里,主要就是学习如何开发 ndk,跟踪 loadlibrary 的流程,调试 so,linker
的具体含义。
我们延伸一个内容:
我们加载 nativehelper 库,这个是在手机/system/lib 下的一个核心库。我们测试下:

在这里插入图片描述
这里更详细的不看,只需要关注我们>23 之后,直接返回出错,禁止调用系统这些库。
如何使用 gdb 调试 android c 可执行文件方案呢?
其中 hello-jni 是测试代码,操作如下:
在这里插入图片描述

当没有打上断点的时候,使用 set solib-search-path 将对应的 so 加载上来,然后就可以了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/153567.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Cadence:层次化电路+信号线束

之前用Altium Designer做工程,偏向于使用层次化电路信号线束的方式画图,类似下图: 现在工作需求使用Cadence,继续采用层次化电路设计信号线束的方式,总结如下: 打开OrCAD Capture,创建一个工程…

libtorch在windows使用(c++项目中部署.pt)

狗头pytorch官网教程:Loading a TorchScript Model in C — PyTorch Tutorials 1.13.1cu117 documentation首先我已经有了个model.pt,就不需要做前面序列化为文件之类的操作,直接从在C中加载开始最后成功的版本:example-app.cpp 内…

计算机CCF-C的SCI期刊,值得投稿吗? - 易智编译EaseEditing

ccf应该说是比较权威的,哪怕是C类。 目前不少SCI期刊开始在投稿时,就建议非英语母语作者进行SCI润色后再投稿。 甚至有的杂志还将润色后的English editing certificate作为硬性上传文件指标,列在投稿系统中。 英语润色也是为了避免学术上的…

超优化文章记录之NeurIPS2022_Gradient Descent: The Ultimate Optimizer

作者:Kartik Chandra 单位:MIT 官网链接:Github 下面将首先以简单翻译文章重要内容,穿插一些讲解,并进行简单的复现实验。之后总结各种资料,如review意见等。最后讲解代码,研究具体实现。 文章…

VUE_关于Vue.use()详解

问题 相信很多人在用Vue使用别人的组件时,会用到 Vue.use() 。例如:Vue.use(VueRouter)、Vue.use(MintUI)。但是用 axios时,就不需要用 Vue.use(axios),就能直接使用。那这是为什么呐? 答案 因为 axios 没有 instal…

Arduino工程的创建和实例使用

下载安装 网址:https://www.arduino.cc/en/software,如下图,选择合适的版本下载使用 下载后直接安装就行 因为这是一个完全开源免费的软件不需要破解,只需要注意安装目录就行 可以参考https://blog.csdn.net/impossible_Jesse…

Kubernetes(k8s) 笔记总结(三)

提示:针对kubernetes的服务网络学习。 文章目录一、Kubernetes的 Service服务发现 ClusterIP方式1. Service 介绍2. Service 暴露ClusterIP的方式(集群内部访问)3. Service 暴露NodePort方式(集群外也可以访问)二、Kubernets 之 Ingress1. Ingress 介绍2. Ingress 安…

十一、51单片机之串口通信

1、通信的关键 (1)事先约定。通信之前规定好的,如通信速率,起始信号,结束信号等。 (2)通信传输的基本信息单元。 (3)信息的编码、传输、解码。 2、通信相关的概念 2.1、同步和异步 (1)同步通信要求接收端时钟频率与发送端时钟频率一致&a…

【Python爬虫项目实战】Python爬虫采集弹幕数据

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录前言一、开发工具二、环境搭建三、数据来源查询分析四、代码实现1. 发送请求2.设置编码格式3.解析数据4.保存数据总结前言 今天给大家介绍的是Python爬虫采集弹幕数…

django项目中使用logging记录日志

前提 基于已有项目https://blog.csdn.net/qq_38122800/article/details/128583379?spm1001.2014.3001.5502 1、修改settings.py 在settings.py文件中添加如下代码: 先导入模块: import os,time#配置日志cur_path os.path.dirname(os.path.realpath(__file__)) # log_pat…

Vue3——第九章(依赖注入:provide、inject)

一、Prop 逐级透传问题 通常情况下,当我们需要从父组件向子组件传递数据时,会使用 props。如果需要给深层子组件传递数据,会非常麻烦,如下图: provide 和 inject 可以帮助我们解决这一问题。一个父组件相对于其所有的…

【memcpy和memove函数的详解】

1.memcpy函数详解 模拟实现memcpy函数 2.memmove函数详解 模拟实现memmove函数 memset函数详解 memcpy函数 了解一个函数,就查询该函数的相关信息 memcpy函数在库中的声明如下: void * memcpy ( void * destination, const void * source, size_…

Node.js安装与编写

Node.js是JavaScript运行环境,是可以让JavaScript运行在服务端的开发平台。 Node.js对一些特殊用例进行优化,提供替代的API。 Node.js本质上是为文件系统、数据库之类的资源提供接口。 Node.js是单线程的,通过事件循环(event l…

05【JSP-MVC】

文章目录05【JSP-MVC】一、JSP简介1.1 JSP概述1.1.1 HTML和Servlet的弊端1.1.1 什么是JSP1.2 JSP体验:1.2.2 JSP的执行过程1.2.2 JSP和Servlet是什么关系?二、JSP的脚本元素2.1 JSP中的注释:2.2 JSP脚本表达式2.3 JSP代码片段2.4 JSP声明2.5 …

数字音频接口DAI之I2S/PCM

I2S/PCM数字音频接口概述数字音频接口DAI,即Digital Audio Interfaces,顾名思义,DAI表示在板级或板间传输数字音频信号的方式。相比于模拟接口,数字音频接口抗干扰能力更强,硬件设计简单,DAI在音频电路设计…

深度学习——循环神经网络RNN(笔记)

循环神经网络RNN:对于序列模型的神经网络 1.回顾:潜变量自回归模型 使用潜变量ht总结过去的信息 ①xt跟当前的ht和x(t-1)相关 ②ht跟ht-1和xt-1相关 ③n元语法模型中,单词xt在时间t的条件概率取决于前面n-1个单词 隐藏层和隐藏状态的区别…

【ESP32Cam项目1】:ESP32Cam人脸检测(ArduinoESP32底层、Python版opencv)

人脸检测项目效果图: 人脸检测效果视频: 暮年的主页 - 抖音 (douyin.com) 人脸检测项目目标: 大家好!近期拿到了便宜的ESP32Cam开发板,摄像头让我想起来人脸识别,于是ESP32Cam人脸检测项目由此诞生。后期还…

一文总结ACE代码框架

一、前言ACE_Engine框架是OpenAtom OpenHarmony(简称“OpenHarmony”)的UI开发框架,为开发者提供在进行应用UI开发时所必需的各种组件,以及定义这些组件的属性、样式、事件及方法,通过这些组件可以方便进行OpenHarmony…

JavaScript面试题

目录1.★★ 介绍一下JS的内置类型有哪些?2.★★★★ 介绍一下 typeof 区分类型的原理3.★★★ 介绍一下类型转换4.★★★★ 说说你对 JavaScript 的作用域的理解。什么是作用域链?5.★★ 解释下 let 和 const 的块级作用域6.★★★★ 说说你对执行上下文…

用Vue+Nodejs+Axios+express连接Sqlserver做一个动态网页demo

文章目录一、主要工具二、流程2.1. 安装Node2.2. 新建Vue工程并启动2.3. 前后端通信2.3.1. 修改前端2.3.2. 用axios来发起请求2.3.3. 创建服务端程序一、主要工具 Vue做前端页面Nodejs做服务器后端(Nodejs是一个JS的运行环境,可以让JS像其它后端语言一样…