2011年12月16日
Android 2.3と4.0で0除算のふるまいの違い
Android 2.3までは0で割り算しても無視されていました。4.0では普通のARM Linuxと同様にSIGFPEのシグナルを発行するようになりました。どこでこの違いがあらわれるかやっと突き止めたのでここにメモしておきます。
android2.3/hardware/libhardware/modules/gralloc/framebuffer.cpp
if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1) return -errno; int refreshRate = 1000000000000000LLU / ( uint64_t( info.upper_margin + info.lower_margin + info.yres ) * ( info.left_margin + info.right_margin + info.xres ) * info.pixclock ); if (refreshRate == 0) { // bleagh, bad info from the driver refreshRate = 60*1000; // 60 Hz }
info.pixclockは0だったので、refreshRateを計算する時に0で除算していました。しかし正常に動作していました。これを元にカスタマイズしたものをAndroid 4.0で動かすと、SIGFPEのシグナルが発生してこのプロセスは強制終了させられてしましました。(KZM-A9-DualボードでAndroid 4.0(Ice Cream Sandwich)を動かす)
同じところがAndroid4.0では以下のように修正されて0で除算しないようになっていました。
android4.0/hardware/libhardware/modules/gralloc/framebuffer.cpp
if (ioctl(fd, FBIOGET_VSCREENINFO, &info) == -1)
return -errno;
uint64_t refreshQuotient =
(
uint64_t( info.upper_margin + info.lower_margin + info.yres )
* ( info.left_margin + info.right_margin + info.xres )
* info.pixclock
);
/* Beware, info.pixclock might be 0 under emulation, so avoid a
* division-by-0 here (SIGFPE on ARM) */
int refreshRate = refreshQuotient > 0 ? (int)(1000000000000000LLU / refreshQuotient) : 0;
if (refreshRate == 0) {
// bleagh, bad info from the driver
refreshRate = 60*1000; // 60 Hz
}
intの除算はgccのランタイムライブラリ libgccの中の__aeabi_uidivで行われます。(Androidで使われるARM926, ARM1136, Cortex-A8, Cortex-A9などは整数の割り算命令がありません。)
Androidではlibgccに含まれる関数群は/system/bin/linkerに含まれていることを発見しました。ただしシンボルに__dl_のprefixがつけられていました。(bionic/linker/Android.mk参照。) もちろん__aeabi_uidivもあります。そこでの0除算のハンドラは
$ arm-eabi-objdump -d android-2.3/out/target/product/kzm9d/symbols/system/bin/linker |less
b00016d4 <__dl___aeabi_idiv0>: b00016d4: e12fff1e bx lr
$ arm-eabi-objdump -d android-4.0/out/target/product/generic/symbols/system/bin/linker |less
b0001b30 <__dl___div0>: b0001b30: e92d4002 push {r1, lr} b0001b34: e3a00008 mov r0, #8 ; 0x8 b0001b38: eb0013dd bl b0006ab4 <__dl_raise+0x18> b0001b3c: e8bd8002 pop {r1, pc}
__aeabi_idiv0と__div0はaliasになっているので同じところを指しています。
2.3のほうは何もせずにすぐリターンしていて、4.0ではraiseを呼んでシグナルを発行しています。
その元になっているlibgccを見つけました。
$arm-eabi-objdump -d android-4.0/prebuilt/linux-x86/toolchain/arm-linux-androideabi-4.4.x/lib/gcc/arm-linux-androideabi/4.4.3/armv7/libgcc.a |less
Disassembly of section .text: 00000000 <__div0>: 0: e92d4002 push {r1, lr} 4: e3a00008 mov r0, #8 ; 0x8 8: ebfffffe bl 0 <raise> c: e8bd8002 pop {r1, pc}
わかりました。Android 2.3と4.0では使用しているtoolchainが異なります。そのためにlibgcc.aの内容が違っていました。
Android 2.3でのtoolchainの選択
ANDROID_EABI_TOOLCHAIN=$prebuiltdir/toolchain/arm-eabi-4.4.3/bin
Android 4.0でのtoolchainの選択
ANDROID_EABI_TOOLCHAIN=$prebuiltdir/toolchain/arm-linux-androideabi-4.4.x/bin
さらに深堀りして、gccのソースでこの部分を見つけました。
gcc-4.4.3/gcc/config/arm/lib1funcs.asm
#ifdef L_dvmd_tls FUNC_START div0 FUNC_ALIAS aeabi_idiv0 div0 FUNC_ALIAS aeabi_ldiv0 div0 RET FUNC_END aeabi_ldiv0 FUNC_END aeabi_idiv0 FUNC_END div0 #endif /* L_divmodsi_tools */ /* ------------------------------------------------------------------------ */ #ifdef L_dvmd_lnx @ GNU/Linux division-by zero handler. Used in place of L_dvmd_tls /* Constant taken from <asm/signal.h>. */ #define SIGFPE 8 ARM_FUNC_START div0 do_push {r1, lr} mov r0, #SIGFPE bl SYM(raise) __PLT__ RETLDM r1 FUNC_END div0 #endif /* L_dvmd_lnx */
2012.4.14追記
ここで書かれていことはAndroidのシステムでの話です。NDKを使って作成したダイナミックリンクライブラリではgccのランタイムライブラリ(libgcc.a)はスタティックリンクされるようになっていました。つまり動作させる環境がAndroid2.3でも4.0でも0除算のふるまいは同じです。