Skip to content

Commit a90c1de

Browse files
committed
05eb5522e8fd48923c089657e9e61759bb09f34ceaba9d5713e382f19a04e0a9
1 parent 1320ae7 commit a90c1de

18 files changed

+612
-26
lines changed
Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,34 @@
1-
# Native crash 分析
1+
# Native crash介绍
22

3-
## 信号量
3+
### 信号量
44

5-
### 1.程序奔溃
5+
#### 1.程序崩溃
66

77
- 在Unix-like系统中,所有的崩溃都是编程错误或者硬件错误相关的,系统遇到不可恢复的错误时会触发崩溃机制让程序退出,如除零、段地址错误等。
88
- 异常发生时,CPU通过异常中断的方式,触发异常处理流程。不同的处理器,有不同的异常中断类型和中断处理方式。
99
- linux把这些中断处理,统一为信号量,可以注册信号量向量进行处理。
1010
- 信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号。
1111

12-
### 2.信号机制
12+
#### 2.信号机制
1313

1414
函数运行在用户态,当遇到系统调用、中断或是异常的情况时,程序会进入内核态。信号涉及到了这两种状态之间的转换。
1515

1616
![v2-792e0559464e45a92efa9e8ebb7ac5a5_1440w](https://pic3.zhimg.com/80/v2-792e0559464e45a92efa9e8ebb7ac5a5_1440w.png)
1717

1818

1919

20-
### 3. 常见类型
20+
#### 3. 常见类型
2121

2222
![img](https://pic4.zhimg.com/80/v2-fd171770eac4c565e8f6df3621c00647_1440w.png)
2323

2424

2525

26+
27+
28+
29+
2630
https://zhuanlan.zhihu.com/p/27834417
2731

28-
http://crash.163.com/index.do#news/!newsId=2
32+
http://crash.163.com/index.do#news/!newsId=2
33+
34+
https://mp.weixin.qq.com/s/g-WzYF3wWAljok1XjPoo7w
File renamed without changes.

android/jni/jni 数据类型.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# JNI 数据类型
2+
3+
### 1. 种类
4+
5+
Java中有两种类型:**基本数据类型(int、float、char等)****引用类型(类、对象、数组等)**。JNI定义了一个C/C++类型的集合,集合中每一个类型对应于Java中的每一个类型,其中,对于基本类型而言,JNI与Java之间的映射是一对一的,比如Java中的int类型直接对应于C/C++中的jint;而对引用类型的处理却是不同的,JNI把Java中的对象当作一个C指针传递到本地函数中,这个指针指向JVM中的内部数据结构,而内部数据结构在内存中的存储方式是不可见的,本地代码必须通过在JNIEnv中选择适当的JNI函数来操作JVM中的对象。比如,对于java.lang.String对应的JNI类型是jstring,本地代码只能通过GetStringUTFChars这样的JNI函数来访问字符串的内容。以下是JNI数据类型映射关系表,通过这种映射JNI就可以正确识别并转换Java数据类型。
6+
7+
8+
9+
基本数据类型转换表:
10+
11+
| Java | Native类型 | 符号属性 | 字长 |
12+
| ------- | ---------- | -------- | ---- |
13+
| boolean | jboolean | 无符号 | 8位 |
14+
| byte | jbyte | 无符号 | 8位 |
15+
| char | jchar | 无符号 | 16位 |
16+
| short | jshort | 有符号 | 16位 |
17+
| int | jint | 有符号 | 32位 |
18+
| long | jlong | 有符号 | 64位 |
19+
| float | jfloat | 有符号 | 32位 |
20+
| double | jdouble | 有符号 | 64位 |
21+
22+
23+
24+
引用数据类型的转换:
25+
26+
| Java引用类型 | Native类型 | Java引用类型 | Native类型 |
27+
| ----------------------- | ------------- | ------------ | -------------- |
28+
| **All object** | **jobject** | char[] | jcharArray |
29+
| **java.lang.class实例** | **jclass** | short[] | jshortArray |
30+
| java.lang.String实例 | jstring | int[] | jintArray |
31+
| object[] | jobjectArray | short[] | jshortArray |
32+
| boolean[] | jbooleanArray | long[] | **jlongArray** |
33+
| byte[] | jbyteArray | double[] | jdoubleArray |
34+
| java.lang.Throwable实例 | jthrowable | float[] | jfloatArray |
35+
36+
37+
38+
### 2. 字符串处理
39+
40+
在JNI开发中,jstring类型指向JVM内部的一个字符串和C字符串类型char*是不同的,前者编码格式为Unicodec(UTF-16),后者为UTF-8。因此,我们不能简单的将jstring当作一个普通的C字符串来使用,必须要使用合适的JNI函数把jstring转化为C/C++字符串,即GetStringUTFChars。该函数可以把一个jstring指针(指向JVM内部的Unicode字符序列),转化为一个UTF-8格式的C字符串。需要注意的是,如果jstring字符串中包含中文,还需要将UTF-8转化为GB2312,否则会出现中文乱码。
41+
42+
43+
44+
------
45+
46+
[]: https://blog.csdn.net/AndrExpert/article/details/72851294 "详解JNI数据类型与C/C++、Java之间的互调"
47+

android/jni/jni基础.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# jni基础
2+
3+
### 1. JNIEnv
4+
5+
首先理解一下JNIEnv,它表示了java在native层的语言环境,是一个指针,几乎所有JNI有关的函数都通过这个指针来调用,例如上面的`env->RegisterNatives(...)`
6+
7+
在native方法中,第一个亘古不变的参数env便是这个JNIEnv,它是线程内部存储,**不能多个线程共享一个JNIEnv** 。如果你的code无法直接拿到env或者要多线程使用,可以通过JavaVM来获取,最典型的使用场景就是c++层的异步callback函数。这里的示例等讲完JavaVM再给出。
8+
9+
### 2. JavaVM
10+
11+
JavaVM可以理解为c++层可以获取的Java的虚拟机。一个进程只有一个JavaVM,所有线程共享它,它是一个全局变量。
12+
他的获得时机只有2个:
13+
14+
1. 在加载动态链接库的时候,JVM会调用`JNI_OnLoad(JavaVM* jvm, void* reserved)`(如果定义了该函数)。第一个参数会传入JavaVM指针。
15+
2. 在native code中调用`JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)`
16+
17+
由于Android限制只有能一个JavaVM,所以第二种方式并不常用,因为你需要一直维护它的生命周期,不断的去调用`JNI_DestroyJavaVM()`,很容易崩溃。而既然他是一个全局变量,所以更常用的方式是在时机1的时候,把它存下来。
18+
19+
关于时机1,这里又多出来一个新的话题,就是加载动态链接库,长话短说,就是当java层执行`System.loadLibrary("NativeLib");`的时候,这个`NativeLib.so被加载`,此时如果它定义了`JNI_OnLoad(JavaVM* jvm, void* reserved)`,则该函数会被调用。

android/jni/jni引用.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# jni引用
2+
3+
### 0. 简介
4+
5+
在 Native 代码中有时候会接收 Java 传入的引用类型参数,有时候也会通过 NewObject 方法来创建一个 Java 的引用类型变量。
6+
7+
在编写 Native 代码时,要注意这个代表 Java 数据结构类型的引用在使用时会被 GC 回收的可能性。
8+
9+
Native 代码并不能直接通过引用来访问其内部的数据接口,必须要通过调用 JNI 接口来间接操作这些引用对象,就如在之前的系列文章中写的那样。并且 JNI 还提供了和 Java 相对应的引用类型,因此,我们就需要通过管理好这些引用来管理 Java 对象,避免在使用时被 GC 回收了。
10+
11+
JNI 提供了三种引用类型:
12+
13+
- 局部引用
14+
- 全局引用
15+
- 弱全局引用
16+
17+
### 1. 局部引用
18+
19+
局部引用是最常见的一种引用。绝大多数 JNI 函数创建的都是局部引用,比如:NewObject、FindClass、NewObjectArray 函数等等。
20+
21+
> 局部引用会阻止 GC 回收所引用的对象,同时,它不能在本地函数中跨函数传递,不能跨线程使用。
22+
23+
局部引用在 Native 函数返回后,所引用的对象会被 GC 自动回收,也可以通过 DeleteLocalRef 函数来手动回收。
24+
25+
在之前文章 JNI 调用时缓存字段和方法 ID,第一种方法采用的是使用时缓存,把字段 ID 通过 static 变量缓存起来。
26+
27+
如果把 FindClass 函数创建的局部引用也通过 static 变量缓存起来,那么在函数退出后,局部引用被自动释放了,static 静态变量中存储的就是一个被释放后的内存地址,成为了一个野指针,再次调用时就会引起程序崩溃了。
28+
29+
### 2. 全局引用
30+
31+
> 全局引用和局部引用一样,也会阻止它所引用的对象被回收。但是它不会在方法返回时被自动释放,必须要通过手动释放才行,而且,全局引用可以跨方法、跨线程使用。
32+
33+
全局引用只能通过 `NewGlobalRef`函数来创建,然后通过 `DeleteGlobalRef` 函数来手动释放。
34+
35+
还是上面提到的缓存字段的例子,现在就可以使用全局引用来缓存了。
36+
37+
### 3. 弱全局引用
38+
39+
> 弱全局引用有点类似于 Java 中的弱引用,它所引用的对象可以被 GC 回收,并且它也可以跨方法、跨线程使用。
40+
41+
弱引用使用 `NewWeakGlobalRef` 方法创建,使用 `DeleteWeakGlobalRef` 方法释放。
42+
43+
### 4. 合理管理引用
44+
45+
总结一些关于引用管理方面的知识点,可以减少内存的使用和避免因为对象被引用不能释放而造成的内存浪费。
46+
47+
通常来说,Native 代码大体有两种情况:
48+
49+
- 直接实现 Java 层声明的 Native 函数的代码
50+
- 用在任何场景下的工具函数
51+
52+
对于直接实现 Java 层声明的 Native 函数,不要造成全局引用和弱全局引用的累加,因为它们在函数返回后并不会自动释放。
53+
54+
对于工具类的 Native 函数,由于它的调用场合和次数是不确定的,所以要格外小心各种引用类型,避免造成累积而导致内存溢出,比如如下规则:
55+
56+
- 返回基础类型的 Native 工具函数,不能造成全局引用、弱全局引用、局部引用的积累。
57+
- 返回引用类型的 Native 工具函数,除了要返回的引用之外,也不能造成任何的全局引用、弱全局引用、局部引用的积累。

android/jni/jni静态动态注册.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# jni静态/动态注册
2+
3+
### 0x01. 静态注册
4+
5+
静态注册时,C/C++文件代码中,需要以固定的命名规则来命名导出的JNI函数:
6+
7+
```c++
8+
/*
9+
- 命名规则:
10+
Java_{package_and_classname}_{function_name}(JNI_arguments)`,用下划线代替"."
11+
12+
- 参数:
13+
JNIEnv*: JNI环境的引用
14+
jobject: "this" Java对象的引用
15+
*/
16+
17+
#include <jni.h>
18+
#include <stdio.h>
19+
JNIEXPORT void JNICALL Java_com_testing_jni_HelloWorldJNI_sayHello(JNIEnv* env, jobject thisObject) {
20+
printf("Hello World!\n");
21+
}
22+
```
23+
24+
除了固定的命名规则以外,还需要用JNIEXPORT和JNICALL来标识JNI方法。在/Android/SDK/ndk-bundle/……/include/jni.h中,可看到JNIEXPORT和JNICALL的宏定义:
25+
26+
```c++
27+
#define JNIIMPORT
28+
#define JNIEXPORT __attribute__ ((visibility ("default")))
29+
#define JNICALL
30+
```
31+
32+
JNIEXPORT 确保标识的函数被正常导出。C++中提供了elf文件的[导出表隐藏功能](http://gcc.gnu.org/wiki/Visibility),而为了JNI能找到Native方法,需要设置Visibility属性为”default“, 即确保标识函数会出现到so文件的导出表中,因此JNI接口可以找到目标方法。JNICALL 则确保函数的命名规范正确。
33+
34+
Native方法静态注册后,JNI环境可以很轻松地在so的导出表中,根据调用者的包名、类名和方法名,找到对应的方法。
35+
36+
### 0x02. 动态注册
37+
38+
除了使用静态注册这种传统方法实现JNI外,也可以使用RegisterNatives动态实现JNI。
39+
40+
JNIEnv类中提供了动态注册Native函数的方法**RegisterNatives**,这个方法可动态地将so库中的方法名,与JVM中某个类调用的Native方法名做绑定和映射,这样JNI就不需要通过函数命名规则来搜索目标函数,可直接在JNIEnv中定位目标函数。动态注册的过程,一般发生在JNI_Onload执行期间。
41+
42+
JVM在System.loadLibrary加载so文件时,会第一时间调用JNI_Onload函数,它有两个重要的作用:
43+
44+
- 指定JNI版本:告诉VM该组件使用那一个JNI版本(若未提供JNI_OnLoad()函数,VM会默认该使用最老的JNI 1.1版),如果要使用新版本的JNI,例如JNI 1.4版,则必须由JNI_OnLoad()函数返回常量JNI_VERSION_1_4来告知VM。
45+
- 初始化设定,进行各种资源的初始化操作。
46+
47+
由于JNI_Onload执行了初始化资源的操作,动态注册也一般在初始化时进行。重写JNI_Onload函数
48+
49+
```c++
50+
#include <jni.h>
51+
#include <stdio.h>
52+
#include <stdlib.h>
53+
#include <unistd.h>
54+
#include <sys/types.h>
55+
#include <sys/stat.h>
56+
#include <fcntl.h>
57+
58+
JNIEXPORT void JNICALL sayHello(JNIEnv* env, jobject thisObject) {
59+
printf("Hello World!\n");
60+
}
61+
62+
//Java和JNI函数的绑定表
63+
static JNINativeMethod gMethods[] = {
64+
{"sayHello", "()V", (void *)sayHello},
65+
};
66+
67+
//注册native方法到java中
68+
static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* gMethods, int numMethods){
69+
jclass clazz;
70+
clazz = (*env)->FindClass(env, className);
71+
if (clazz == NULL) {
72+
return JNI_FALSE;
73+
}
74+
if ((*env)->RegisterNatives(env, clazz, gMethods,numMethods) < 0){
75+
return JNI_FALSE;
76+
}
77+
return JNI_TRUE;
78+
}
79+
80+
int register_ndk_load(JNIEnv *env){
81+
return registerNativeMethods(env, "com/testing/jni/HelloWorldJNI",gMethods,sizeof(gMethods) / sizeof(gMethods[0]));
82+
}
83+
84+
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
85+
{
86+
JNIEnv* env = NULL;
87+
jint result = -1;
88+
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK){
89+
return result;
90+
}
91+
// 动态注册
92+
register_ndk_load(env);
93+
94+
// 返回jni的版本
95+
return JNI_VERSION_1_4;
96+
}
97+
```
98+
99+
在C++和Java中创建关联的是JNINativeMethod,保存了Java中调用的函数名和C++代码中函数名的映射关系,它在jni.h中定义:
100+
101+
```
102+
/*used in RegisterNatives to describe native method name, signature, and function pointer.*/
103+
typedef struct {
104+
char *name;
105+
char *signature;
106+
void *fnPtr;
107+
} JNINativeMethod;
108+
```
109+
110+
name是java中定义的native函数的名字,fnPtr是函数指针,也就是C++中java native函数的实现。signature是java native函数的签名,可以认为是参数和返回值。比如(LMyJavaClass;)V,表示函数的参数是LMyJavaClass(Java类MyJavaClass),返回值是void。对于基本类型,对应关系如下:
111+
112+
```
113+
字符 Java类型 C/C++类型
114+
V void void
115+
Z jboolean boolean
116+
I jint int
117+
J jlong long
118+
D jdouble double
119+
F jfloat float
120+
B jbyte byte
121+
C jchar char
122+
S jshort short
123+
```
124+
125+
- 数组则以”[“开始,用两个字符表示,比如int数组表示为[I,以此类推。
126+
- 如果参数是Java类,则以”L”开头,以”;”结尾,中间是用”/“隔开包及类名,例如Ljava/lang/String;,而其对应的C++函数的参数为jobject,一个例外是String类,它对应C++类型jstring。
127+
- *(DD)I* 表示2个double的参数,返回值为void
128+
129+
```
130+
static JNINativeMethod gMethods[] = {
131+
{"processDirectory", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V", (void *)android_media_MediaScanner_processDirectory},
132+
{"processFile", "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
133+
(void *)android_media_MediaScanner_processFile},
134+
{"setLocale", "(Ljava/lang/String;)V", (void *)android_media_MediaScanner_setLocale},
135+
{"extractAlbumArt", "(Ljava/io/FileDescriptor;)[B", (void *)android_media_MediaScanner_extractAlbumArt},
136+
{"native_init", "()V", (void *)android_media_MediaScanner_native_init},
137+
{"native_setup", "(DD)V", (void *)android_media_MediaScanner_native_setup},
138+
{"native_finalize", "(BDI)V", (void *)android_media_MediaScanner_native_finalize},
139+
};
140+
```
141+
142+
可见动态注册时,不仅仅会将C/C++函数与Java调用的函数名做映射,也会与具体的调用类class进行绑定。因此,调用第三方so文件时,**Java中定义的类名和package名都要严格对应**。
143+
144+
动态注册的好处是:
145+
146+
1. C/C++中函数命名自由,不必拘泥特定的命名方式;
147+
2. 效率高。传统方式下,Java类call本地函数时,通常是依靠VM去动态寻找.so中的本地函数(因此它们才需要特定规则的命名格式),而使用RegisterNatives将本地函数向VM进行登记,可以让其更有效率的找到函数;
148+
3. 运行时动态调整本地函数与Java函数值之间的映射关系,只需要多次call RegisterNatives()方法,并传入不同的映射表参数即可。

0 commit comments

Comments
 (0)