记一次dlopen使用问题导致Framework重启,tombstones、pmap与反汇编分析(上)
关键词:Android Framework 动态库 动态链接 Binder
1、事件起因
Android Studio一次更新后发现install App,设备就重启了,跑了一遍开机动画但不是从开机第一屏开始重启,tombstones内容查看发现是surfaceflinger
挂在libbinder.so
,那install app做了什么这个不得而知,理论上有问题应该挂的是PackageManagerService。先不管Android Studio的事情,虽然挂在Binder的库里,还是首先怀疑问题出在surfaceflinger
的Binder使用逻辑。
2、原因分析
(以下分析使用RockPi4B还原现场)
tombstone文件如下
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'rockchip/rk3399_ROCKPI4B_Android11/rk3399_ROCKPI4B_Android11:11/RQ3A.210705.001/eng.kryo.20240128.131540:userdebug/release-keys'
Revision: '0'
ABI: 'arm64'
Timestamp: 2024-01-28 23:42:32+0800
pid: 11263, tid: 11289, name: Binder:11263_2 >>> /system/bin/surfaceflinger <<<
uid: 1000
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x791c0e6cd0
x0 b4000079ce312fd8 x1 000000005f444d50 x2 000000791d20c9c0 x3 000000791d20c940
x4 0000000000000010 x5 0000000000000018 x6 b400007a7e312e50 x7 b4000079ce312fd8
x8 000000791c0e6c50 x9 0000000010000000 x10 0000000000000001 x11 0000000000000002
x12 0000000000000000 x13 0000007baff5e020 x14 0001096dce96e5c4 x15 0000000029aaaaf0
x16 0000007bafd6c420 x17 0000007bafd29e30 x18 000000791c592000 x19 000000791d20c940
x20 0000000000000010 x21 000000791d20c9c0 x22 b4000079ce312fd8 x23 000000005f444d50
x24 000000791d20d000 x25 0000000000000000 x26 ffffffff000003e8 x27 b400007a7e313014
x28 0000000000000000 x29 000000791d20c8d0
lr 0000007bafd167bc sp 000000791d20c8c0 pc 0000007bafd1685c pst 0000000080000000
backtrace:
#00 pc 000000000004985c /system/lib64/libbinder.so (android::BBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+228) (BuildId: d5e42e998e9031430bee87f595521231)
#01 pc 00000000000524a8 /system/lib64/libbinder.so (android::IPCThreadState::executeCommand(int)+1032) (BuildId: d5e42e998e9031430bee87f595521231)
#02 pc 0000000000051fec /system/lib64/libbinder.so (android::IPCThreadState::getAndExecuteCommand()+156) (BuildId: d5e42e998e9031430bee87f595521231)
#03 pc 000000000005282c /system/lib64/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+60) (BuildId: d5e42e998e9031430bee87f595521231)
#04 pc 0000000000078e10 /system/lib64/libbinder.so (android::PoolThread::threadLoop()+24) (BuildId: d5e42e998e9031430bee87f595521231)
#05 pc 000000000001567c /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+260) (BuildId: c081ab14bd4aef44c9c459d77d8c9b48)
#06 pc 0000000000014f14 /system/lib64/libutils.so (thread_data_t::trampoline(thread_data_t const*)+412) (BuildId: c081ab14bd4aef44c9c459d77d8c9b48)
#07 pc 00000000000b0c08 /apex/com.android.runtime/lib64/bionic/libc.so (__pthread_start(void*)+64) (BuildId: 0a481e8df134382e9d3effff2fce8b74)
#08 pc 00000000000505d0 /apex/com.android.runtime/lib64/bionic/libc.so (__start_thread+64) (BuildId: 0a481e8df134382e9d3effff2fce8b74)
使用addr2line工具查看代码奔溃处源码,aosp编译时会在out目录下生成symbols目录,会额外保存一份带符号的so文件方便回溯问题,把崩溃处的地址4985c
传入
aarch64-linux-android-addr2line -e out/target/product/rk3399_ROCKPI4B_Android11/symbols/system/lib64/libbinder.so 4985c
#frameworks/native/libs/binder/Binder.cpp:188
定位到Binder.cpp 188行处的源码:
status_t BBinder::transact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
data.setDataPosition(0);
status_t err = NO_ERROR;
switch (code) {
case PING_TRANSACTION:
err = pingBinder();
break;
case EXTENSION_TRANSACTION:
err = reply->writeStrongBinder(getExtension());
break;
case DEBUG_PID_TRANSACTION:
err = reply->writeInt32(getDebugPid());
break;
default:
err = onTransact(code, data, reply, flags); // 188行处
break;
}
//... ...
}
还是目前很难看出崩溃原因,tombstone给出的是SEGV_MAPERR错误,推测与访存有关,再结合objdump -S 查看libbinder.so的反汇编:
... ...
49840: 1400001a b 498a8 <_ZN7android7BBinder8transactEjRKNS_6ParcelEPS1_j@@Base+0x130>
49844: f94002c8 ldr x8, [x22]
49848: aa1603e0 mov x0, x22
4984c: 2a1703e1 mov w1, w23
49850: aa1503e2 mov x2, x21
49854: aa1303e3 mov x3, x19
49858: 2a1403e4 mov w4, w20
4985c: f9404108 ldr x8, [x8,#128]
49860: d63f0100 blr x8
... ...
可以看到4985c
处的汇编指令是通过x8寄存器偏移+128字节进行访存(寄存器相对寻址) x8内容0x791c0e6c50加128正好是出错内存地址0x791c0e6cd0,取得的内存结果再存回x8
tombstone也会把进程的pmap信息打印,由于地址空间布局随机化(ASLR)机制,可能每次运行结果的地址都不一样:
memory map (836 entries): (fault address prefixed with --->)
... ...
00000079'1afe2000-00000079'1bc05fff --- 0 c24000
00000079'1bc06000-00000079'1bc07fff rw- 0 2000
00000079'1bc08000-00000079'1bfe1fff --- 0 3da000
00000079'1bfe2000-00000079'1bfe2fff --- 0 1000
00000079'1bfe3000-00000079'1c0defff rw- 0 fc000 [anon:stack_and_tls:11290]
00000079'1c0df000-00000079'1c0dffff --- 0 1000
--->Fault address falls at 00000079'1c0e6cd0 between mapped regions
00000079'1c113000-00000079'1c591fff --- 0 47f000
00000079'1c592000-00000079'1c593fff rw- 0 2000
00000079'1c594000-00000079'1d112fff --- 0 b7f000
00000079'1d113000-00000079'1d113fff --- 0 1000
... ...
出现错误的地址是个空洞,也就是出现空指针,通过对比汇编代码和C++代码可以得知x0x3(w0w3)正好是传递了4个参数,ldr x8, [x8,#128]
准备赋值给x8寄存器onTransact
函数类型的函数指针,然后blr x8
跳转到x8执行子程序,说明没有找到onTransact这个函数地址。
以上分析也不好判断是前面这个x8寄存器本身出了问题,在运行过程中被意外修改,还是访问这块内存有毛病被修改了,但还是不要怀疑是binder库本身的问题,binder是系统核心功能,有问题早就挂在其他地方了。好消息是这个问题是必现的,我们有足够的试错机会。
通过重新搜寻surfaceflinger相关代码,发现MTK为其定制了一个库libsurface_ext.so
,扩展了一些功能,并且实现了一个名为SurfaceExtService的binder服务添加道ServiceManager里:
extern "C" void createSurfaceExtService() {
const sp<IServiceManager> sm = defaultServiceManager();
if (sm == nullptr) {
LOGE("Can't get ServiceManager");
} else {
sp<IBinder> binder = sm->checkService(String16(SERVICE_NAME));
if (binder != nullptr) {
LOGW("SurfaceExtService added");
} else {
sp<SurfaceExtService> sfext = new SurfaceExtService();
sm->addService(String16(SERVICE_NAME), sfext, false);
LOGI("SurfaceExtService add");
}
}
}
通过宏注释编译代码,去掉SurfaceExtService服务,重新编译push,再次通过Android Studio安装应用,surfaceflinger不出现奔溃。这下问题能够缩小到libsurface_ext.so
与binder
相关功能。
3、解决问题
反复看了巨久的代码,并不能找到问题,后面突发灵感,还原代码重新编译运行surfaceflinger,通过cat /proc/[pid]/maps |grep libsurface_ext
命令查看surfaceflinger的内存映射,发现libsurface_ext.so并不在内存映射中。
搜索代码发现其动态加载so的位置:
SurfaceExtServiceHelper.cpp
//
// Created by kryo on 1/28/24.
//
#include <log/log.h>
#include <dlfcn.h>
void createSurfaceExtServiceProxy() {
void* soHandle = dlopen("libsurface_ext.so", RTLD_LAZY);
if (soHandle) {
void (*createSurfaceExtPtr)();
createSurfaceExtPtr = (decltype(createSurfaceExtPtr))(dlsym(soHandle, "createSurfaceExtService"));
if (NULL == createSurfaceExtPtr) {
dlclose(soHandle);
soHandle = nullptr;
ALOGE("createSurfaceExtService not found");
} else {
createSurfaceExtPtr();
dlclose(soHandle); // ?
ALOGD("createSurfaceExtPtr()");
}
} else {
soHandle = nullptr;
ALOGE("lib_surface_ext.so not found");
}
}
代码逻辑dlopen加载libsurface_ext.so库,然后使用dlsym搜索符号createSurfaceExtService
拿到函数指针并调用(看起来和Java反射有点类似),日志createSurfaceExtPtr()也成功打印。
乍一看没有问题,但是为啥maps里面并没有libsurface_ext.so,仔细一看createSurfaceExtPtr()调用后又调用了dlclose()
,去掉这个调用,重新编译push,问题不再复现,之前绕了一大圈没找到bug,原来被这块给坑了。
回溯下之前的onTransact调用出错的问题,x8寄存器访问的内存所在的区间就是libsurface_ext.so加载的内存映射,后面dlclose又把so库卸载了,回头再看tombstone文件memory map段也没有这个so
这个库使用了binder,有重写BBinder::onTransact方法:
status_t BBinder::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t /*flags*/);
推测进行Binder调用时通过vtable(虚表指针偏移128字节)访问该库Binder对象找被重写的onTransact函数指针,再跳转到函数去执行,结果获取函数指针时访问的不存在,导致进程crash掉!
调用dumpsys
、service list
等可以触发surfaceflinger的binder调用,均能复现该问题,应该也是Android Studio安装app能复现的原因。
总结
对于代码动态添加binder功能的so库时,我们尽量保存打开的so句柄,并在所有业务结束后才能通过dlclose()关闭句柄,以防止因so库的引用计数为0时被系统从内存中卸载,且对于surfaceflinger这种永不退出的进程来说没有必要调用关闭,这个锅要甩给MTK开发。
Reference
-
《程序员的自我修养:链接、装载与库》