linux 除0异常,NDK开发中为什么除以0程序不崩溃?

我们都知道不管是在Java还是在C++程序中,下面这段代码都会导致程序错误:

int x = 10;

int y = x / 0;

...

复制代码

但是我今天发现了一个比较神奇的事情,把这段代码写成native方法后在Java层调用,竟然没有导致App崩溃,代码是这样子的:

#include

#include "logger.h"

#ifdef __cplusplus

extern "C" {

#endif

JNIEXPORT void JNICALL test_crash(JNIEnv *env, jobject /* this */){

int x = 10;

int y = x / 0;

LOGD("crash %d", y);

}

#ifdef __cplusplus

}

#endif

复制代码

Java层很简单,我保证没有try-catch,而且Log打印出来y的值竟然是0,这完全颠覆我三观。

怀疑人生的我把这两行关键代码放到原生的C++编译器中编译并运行,妥妥地报错:557eb3801116a23f3c1de8bde1867eb1.png

为什么在Android手机上就不会导致crash呢?于是我验证了一下是不是异常机制有什么问题,直接这样写native代码:

JNIEXPORT void JNICALL test_crash(JNIEnv *env, jobject /* this */){

// int x = 10;

// int y = x / 0;

// LOGD("crash %d", y);

throw "Crash!!!"; // 手动抛异常

}

复制代码

还好没有毁三观,App顺利地crash了,所以异常机制是正常的。

那么就说明除0操作在我的手机上没有导致异常抛出,唯一能想到的就是汇编指令不同了,毕竟电脑CPU是x86-64架构的,手机是arm的,二者指令集不同。

验证方法当然就是利用NDK工具和so文件生成汇编代码,看看是不是arm架构下根本就不用除法指令。

1、先build一下Android工程,然后找到so文件:4e5804b83a64504276222ed5a3600512.png

可以把它们暂时拷贝出来待用。

2、进入NDK工具链目录(前提是在SDK Manager中下载了NDK相关的工具),下面是Linux终端命令操作:

$ cd Android/Sdk/ndk-bundle/toolchains/

$ ls

aarch64-linux-android-4.9 arm-linux-androideabi-4.9 llvm renderscript x86-4.9 x86_64-4.9

复制代码

可以发现toolchains下面有4种架构的工具包,对于build出来的4个so文件,这里我们只测试x86-64和arm的。

3、以前者为例,于是进入相关目录,找到xxx-linux-android-objdump工具,然后生成汇编指令:

$ cd x86_64-4.9/prebuilt/linux-x86_64/bin

$ ./x86_64-linux-android-objdump -dx libnative-lib.so > test.txt

复制代码

这里我已经把刚才的so文件拷贝到工具同目录了,所以命令才能直接这么写。

4、其它架构的操作和上述几步一致,但一定要对应解析正确的so文件,不然objdump会提示错误。

5、打开刚才生成的汇编txt文本,搜索我们最开始写的那个test_crash方法,发现在x86-64架构中:

0000000000013960 :

13960:55 push %rbp

13961:48 89 e5 mov %rsp,%rbp

13964:31 c0 xor %eax,%eax

13966:c7 45 fc 0a 00 00 00 movl $0xa,-0x4(%rbp)

1396d:8b 4d fc mov -0x4(%rbp),%ecx

13970:89 45 f4 mov %eax,-0xc(%rbp)

13973:89 c8 mov %ecx,%eax

13975:99 cltd

13976:8b 4d f4 mov -0xc(%rbp),%ecx

13979:f7 f9 idiv %ecx // idiv表示有符号数除法

1397b:89 45 f8 mov %eax,-0x8(%rbp)

1397e:5d pop %rbp

1397f:c3 retq

复制代码

然鹅,在arm的汇编指令中:

0000cb1c :

cb1c:b082 subsp, #8

cb1e:200a movsr0, #10

cb20:9001 strr0, [sp, #4]

cb22:b002 addsp, #8

cb24:4770 bxlr

...

复制代码

发现没有div相关的指令,看来果然是不一样啊。

如果真是如此,那么x86-64架构的Android手机,肯定就会出现除0错误,于是我打开了一个x86-64的谷歌亲儿子模拟器,运行程序,还真崩掉了,但在arm架构的机器上,就不会崩。我手机是高通的CPU,正好也就是arm架构的。

这么说来,还真是印证了复杂指令集和精简指令集啊哈哈,你看看上面同样的代码实现,指令数却完全不同。

后来,去Goggle一查,才发现arm体系结构本身就不包含除法运算硬件,是靠函数实现的,看来是我孤陋寡闻了:

GCC ARM cortex-m0 除0问题

ARM的除法运算优化策略

高效的C编程之:除法运算

还没完,上面解释的只是arm架构,还有arm64呢(目前主流的骁龙845/855等等都是)?先看看汇编指令:

00000000000131b8 :

131b8:d10043ff subsp, sp, #0x10

131bc:52800148 movw8, #0xa // #10

131c0:52800009 movw9, #0x0 // #0

131c4:b9000fe8 strw8, [sp,#12]

131c8:b9400fe8 ldrw8, [sp,#12]

131cc:1ac90d08 sdivw8, w8, w9 // sdiv表示有符号数除法

131d0:b9000be8 strw8, [sp,#8]

131d4:910043ff addsp, sp, #0x10

131d8:d65f03c0 ret

复制代码

咦?怎么arm64架构又有除法指令(sdiv指令文档)了?那怎么除以0也不造成崩溃呢?

看了看arm官方的文档之后:f7628d79ac5299acf8f4818d06002c9d.png

可以发现,指令有一个标志位DZ,叫Divide by Zero fault enable bit,默认为0,即当除以0时不带出异常,而是返回0(这就解释了我们最开始为什么打log值是0),如果设为1,才会抛异常。

此外,在ARM 架构的运行时 ABI文档中,也看到了类似的解释,说明除0抛不抛异常,取决于函数的不同实现:69000cf1a87b31863dfc2b7042aa3708.png

哦对,要查看自己手机的CPU信息,可以用adb命令哦:

adb shell cat /proc/cpuinfo

复制代码

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值