有个愿望:让小米平板2用上64位 Android9.0。因为64位 Android9.0 的 houdini 兼容性非常好,我曾经试过把 houdini9 植入 Android_x86-9.0 r2,然后安装到小米平板2中,发现APP运行很稳定,其它小米平板2安卓系统常见的APP闪退问题基本上都没有。只是因为显卡原因,Android_x86-9.0 r2 在小米平板2上只能运行在 nomodeset 模式下,视频靠CPU解码,导致系统卡顿很厉害。
目标很美好,但对于安卓适配新兵来说,实现这个目标还是要循序渐进。
我手头有能成功编译的 lineage13.0 for latte(小米平板2的代号是 latte)源码,lineage13.0 属于安卓6,为了缩小跨度,降低难度,我决定先从 lineage14.1 的小米平板2适配入手,因为 lineage14.1 属于安卓7,相比安卓6的变化比9要小一些。
虽然适配工作还没有完成,但过程中也有不少收获,总结一下。
一、下载源码
lineage14.1 的主源码可以按?LineageOS Wiki?上的说明下载,latte 的 vendor、device、kernel 则来自 Github(?LineageOS Resources for Xiaomi Mi Pad 2?),但配置 local_mainifest.xml 时要注意:不要使用 cm-14.1 分支的 vendor、device、kernel,它的BUG太多,基于它进行适配工作量巨大,并且会出现很多看不懂的问题。建议用 cm-13.0 分支,因为它已经在 lineage13.0 for latte上适配成功,本身不存在问题,所需要做的是对 lineage14.1 调试。
二、获取日志
最初编译出来的 lineage14.1 Rom 安装到小米平板2上多半无法成功运行,需要查看日志来分析原因,但系统如果没有运行到合适的阶段是无法通过 ADB 获取日志的,怎么办?群友 ygjsz 提供了一个方法。
在 init.rc 中加入以下代码:
# Take logs even when adb is not working.
# Thanks: markakash
# Add these lines to your init.rc file:
? ??
service boot_lc_main /system/bin/logcat -f /cache/boot_lc_main.txt
? ? class main
? ? user root
? ? group root system
? ? oneshot
service boot_dmesg /system/bin/sh -c "dmesg -w > /cache/boot_dmesg.txt"
? ? class main
? ? user root
? ? group root system
? ? oneshot
on property:sys.boot_completed=1
? ? start boot_lc_main
? ? start boot_dmesg
这段代码在开机后会自动把日志记录到 cache 文件夹中。当系统启动失败后,长按电源键+音量增键,让平板进入 Recovery 模式,就能把日志拷贝出来分析。
三、处理 dlopen failed 错误
日志中常出现 dlopen failed 报错,形式为 cannot locate symbol "_ZNxxxx" referenced by "xxxxx.so",例如:
?dlopen failed: cannot locate symbol "_ZN7android13GraphicBufferC1Ejjij" ?referenced by "/system/vendor/lib/hw/hwcomposer.gmin.so"
这里需要重点搞清 symbol "_ZN7androidxxxxxx" 是什么?简单说,它是系统在编译 so 时自动生成的某个函数的别名,这个函数在源码中本来有易理解的名称,但在编译时编译器给它生成了一个 symbol,这个 symbol 具有唯一性,生成规则与函数名称、参数个数和参数类型有关,系统运行时根据它就可以定位到具体的动态库中具体的函数。
解决这个报错的第一步是要定位这个函数,但由于不知道生成规则,所以无法直接解码 symbol 去定位函数源码,但我找到了一个方法。
报错中的?xxxxx.so?一般都位于 system/vendor/lib 中,因为用的是 lineage13.0 for latte 的 vendor, lineage14.1 中找不到的那个 symbol 在 lineage13.0 一定能找到,所以可以先到 lineage13.0 中找,确定函数位于哪个源码文件中,然后再看 lineage14.1 中这个文件为什么没有这个函数。
就以 dlopen failed: cannot locate symbol "_ZN7android13GraphicBufferC1Ejjij" referenced by "/system/vendor/lib/hw/hwcomposer.gmin.so" 举例。
先看 "_ZN7android13GraphicBufferC1Ejjij" 位于 lineage13.0的哪个动态链接库中:
butterfly@Amazon:~/lineage/13.0/out/target/product/latte/system/lib$ find -type f -name '*.so' | xargs grep "_ZN7android13GraphicBufferC1Ejjij"
出现以下结果:
匹配到二进制文件 ./libstagefright_wfd.so
匹配到二进制文件 ./libshim_camera.so
匹配到二进制文件 ./libgui.so
匹配到二进制文件 ./hw/camera.gmin.so
匹配到二进制文件 ./arm/libgui.so
匹配到二进制文件 ./arm/libui.so
匹配到二进制文件 ./arm/nb/libgui.so
匹配到二进制文件 ./arm/nb/libui.so
匹配到二进制文件 ./libui.so
匹配到二进制文件 ./libwebviewchromium_plat_support.so
说明这些so动态链接文件中都有 "_ZN7android13GraphicBufferC1Ejjij",但有并不表示这个函数在这个文件中,有可能这个文件只是调用了这个函数而已,那么哪个才是被调用的文件呢?
对这些文件逐个执行下面命令:
butterfly@Amazon:~/lineage/13.0$ ~/Android/Sdk/ndk/16.1.4479499/toolchains/x86_64-4.9/prebuilt/linux-x86_64/bin/x86_64-linux-android-gcc-nm -D ~/lineage/13.0/out/target/product/latte/system/lib/libui.so | grep GraphicBuffer | sort
命令中,“GraphicBuffer”是 symbol 中一段有字面含义的文字,它一般是那个函数名称中的一部分。
出现下面结果:
00007930 T _ZN7android13GraphicBufferC1Ev
00007930 T _ZN7android13GraphicBufferC2Ev
00007a20 T _ZN7android13GraphicBufferC1Ejjij
00007a20 T _ZN7android13GraphicBufferC2Ejjij
00007b30 T _ZN7android13GraphicBuffer8initSizeEjjij
00007c00 T _ZN7android13GraphicBufferC1EjjijjP13native_handleb
......
可以看到第三行就是那个 symbol,它前面有一串十六进制数 00007a20,这表明这个 symbol 在这个so文件中对应着有实实在在的函数;如果这个 symbol 前面没有十六进制字符串,则表明这个 symbol 出现在这个文件中只是因为这个文件调用了别的库中的这个函数。通过这个方法可以找到有这个函数的so文件,在这个例子中就是 system/lib/libui.so(其实有一个方法可以减轻工作量,不用对每个so文件都检查:用 Cutter 工具打开?hwcomposer.gmin.so,查看它的依赖库,仅对是其依赖库的so文件进行分析)。
上面的命令中,x86_64-linux-android-gcc-nm 的路径是计算机中它的安装位置,我的安装在 ~/Android/Sdk/ndk/16.1.4479499/toolchains/x86_64-4.9/prebuilt/linux-x86_64/bin/ 中。
下面查这个so文件的确切函数名称和参数。对 system/lib/libui.so 执行下面这条命令(与上一条很像,但多一个 “-C”):
butterfly@Amazon:~/lineage/13.0$ ~/Android/Sdk/ndk/16.1.4479499/toolchains/x86_64-4.9/prebuilt/linux-x86_64/bin/x86_64-linux-android-gcc-nm -C -D ~/lineage/13.0/out/target/product/latte/system/lib/libui.so | grep GraphicBuffer | sort
出现下面结果:
00007930 T android::GraphicBuffer::GraphicBuffer()
00007930 T android::GraphicBuffer::GraphicBuffer()
00007a20 T android::GraphicBuffer::GraphicBuffer(unsigned int, unsigned int, int, unsigned int)
00007a20 T android::GraphicBuffer::GraphicBuffer(unsigned int, unsigned int, int, unsigned int)
00007b30 T android::GraphicBuffer::initSize(unsigned int, unsigned int, int, unsigned int)
00007c00 T android::GraphicBuffer::GraphicBuffer(unsigned int, unsigned int, int, unsigned int, unsigned int, native_handle*, bool)
00007c00 T android::GraphicBuffer::GraphicBuffer(unsigned int, unsigned int, int, unsigned int, unsigned int, native_handle*, bool)
00007d00 T android::GraphicBuffer::GraphicBuffer(ANativeWindowBuffer*, bool)
00007d00 T android::GraphicBuffer::GraphicBuffer(ANativeWindowBuffer*, bool)
00007e10 T android::GraphicBuffer::~GraphicBuffer()
00007e10 T android::GraphicBuffer::~GraphicBuffer()
......
找到 00007a20 那行,其对应的是 GraphicBuffer::GraphicBuffer(unsigned int, unsigned int, int, unsigned int),这就是那个函数的名称与参数。
然后查找函数源码。执行下面命令:
butterfly@Amazon:~/lineage/13.0$ find -type f -name '*.mk' | xargs grep "LOCAL_MODULE := libui"
出现:
./frameworks/native/libs/ui/Android.mk:LOCAL_MODULE := libui
由此可知 libui 的源码在 frameworks/native/libs/ui/ ,进到这个文件夹,运行:
butterfly@Amazon:~/lineage/13.0/frameworks/native/libs/ui$ find -type f -name '*.cpp' | xargs grep "GraphicBuffer::GraphicBuffer("
出现:
./GraphicBuffer.cpp:GraphicBuffer::GraphicBuffer()
./GraphicBuffer.cpp:GraphicBuffer::GraphicBuffer(uint32_t inWidth, uint32_t inHeight,
./GraphicBuffer.cpp:GraphicBuffer::GraphicBuffer(uint32_t inWidth, uint32_t inHeight,
./GraphicBuffer.cpp:GraphicBuffer::GraphicBuffer(ANativeWindowBuffer* buffer, bool keepOwnership)
至此,可以确定那个 symbol 对应的函数在 frameworks/native/libs/ui/GraphicBuffer.cpp 中。
打开 ~/lineage/13.0/frameworks/native/libs/ui/GraphicBuffer.cpp,可以找到 GraphicBuffer::GraphicBuffer(unsigned int, unsigned int, int, unsigned int)。打开 Lineage14.1 中同名 文件,发现尽管其中也有 GraphicBuffer::GraphicBuffer 函数,但所有的 GraphicBuffer 函数中没有参数为 (unsigned int, unsigned int, int, unsigned int) 的。
现在可以动手解决 dlopen failed 错误了:参照 lineage13.0 中函数内容,在 lineage14.1 中添加相同参数的 GraphicBuffer 函数。这个函数怎么写要具体情况具体分析,即要实现其应有的功能,又不能破坏原有的函数。
添加完,编译后, 让平板进入REC 模式,将 lineage14.1 中新生成的 out/target/product/latte/system/lib/libui.so 拷贝到平板中,再开机获取日志,可以看到那条 dlopen failed 报错不再出现了。
其它 dlopen failed 错误也可以参考这个方法解决。
但有一个类似报错折腾了我很长时间,需要用到不同的处理方法。这个报错形式是:Can't locate symbol "ufoInitPlatform" referenced by ......
这个问题折磨我很久才发现是因为系统中有两个?libskuwa.so,一个在 system/lib 中,一个在 vendor/lib 中,默认会调用 system/lib 下那个,而那个是没有 ufoInitPlatform 函数的。解决办法就是把 system/lib 下面那个?libskuwa.so?删除。
通过前面这些处理方法,我成功地实现了小米平板2的开机动画显示。
四、目前遇到的困难
适配工作还在艰难进行中,不断遇到新的问题。目前主要有三个问题不知道该怎么分析。
1.keymaster
日志中有下面报错重复出现:
3336 3336 I keystore: Found keymaster0 module Keymaster Intel MEI HAL, version 3
3336 3336 I SoftKeymaster: system/keymaster/soft_keymaster_device.cpp, Line 131: Creating device
3336 3336 D SoftKeymaster: system/keymaster/soft_keymaster_device.cpp, Line 132: Device address: 0xf6eb0000
3336 3336 I ITEEKeyMaster: intel_mei_km_open called
3336 3336 I ITEEKeyMaster: Connected to keymasterd successfully
3336 3336 I ITEEKeyMaster: get_supported_classes called
2904 3337 E ITEEKeyMasterD: Could not acquire wake lock ret = -2
3336 3336 E ITEEKeyMaster: Received error -2 on command 0 from FW
3336 3336 E ITEEKeyMaster: CMD_ID_GET_SUPPORTED_CLASSES failed
3336 3336 E keystore: Error opening keystore keymaster0 device.
3336 3336 E keystore: keystore keymaster could not be initialized; exiting
我能跟踪到报错来自 keystore.gmin.so,由于没有它的源码,无法进一步分析。
尝试用?keystore.default.so?替换 keystore.gmin.so,日志中报错消失,变成下面的内容,不确定是否解决。
3045 3045 I keystore: Found keymaster0 module Keymaster OpenSSL HAL, version 2
3045 3045 I SoftKeymaster: system/keymaster/soft_keymaster_device.cpp, Line 131: Creating device
3045 3045 D SoftKeymaster: system/keymaster/soft_keymaster_device.cpp, Line 132: Device address: 0xf6eb0000
3045 3045 I keystore: Keymaster0 module is software-only. Using SoftKeymasterDevice instead.
2.sensorhubd
报错如下,不知道该如何分析。
3335 3335 F libc : Fatal signal 11 (SIGSEGV), code 1, fault addr 0x0 in tid 3335 (sensorhubd)
3338 3338 E : debuggerd: Unable to connect to activity manager (connect failed: Connection refused)
3338 3338 F DEBUG : *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
3338 3338 F DEBUG : LineageOS Version: '14.1-20220120-UNOFFICIAL-latte'
3338 3338 F DEBUG : Build fingerprint: 'Xiaomi/latte/latte:5.1/LMY47I/V8.2.2.0.LACCNDL:user/release-keys'
3338 3338 F DEBUG : Revision: '0'
3338 3338 F DEBUG : ABI: 'x86'
3338 3338 F DEBUG : pid: 3335, tid: 3335, name: sensorhubd >>> /system/bin/sensorhubd <<<
3338 3338 F DEBUG : signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
3338 3338 F DEBUG : eax 00000001 ebx f733fdcc ecx 00000000 edx f7747371
3338 3338 F DEBUG : esi f6d1900c edi f745f010
3338 3338 F DEBUG : xcs 00000023 xds 0000002b xes 0000002b xfs 00000003 xss 0000002b
3338 3338 F DEBUG : eip f6dd92ce ebp ffb37a18 esp ffb37a18 flags 00010286
3338 3338 F DEBUG :
3338 3338 F DEBUG : backtrace:
3338 3338 F DEBUG : #00 pc 000062ce /system/lib/libcutils.so (android_atomic_inc+14)
3338 3338 F DEBUG : #01 pc 00008f6d /system/lib/libaware_client.so (_ZNK7android12SharedBuffer7acquireEv+29)
3338 3338 F DEBUG : #02 pc 00005470 /system/lib/libaware_client.so (_ZN7android7String8C1Ev+32)
3338 3338 F DEBUG : #03 pc 000074b1 /system/lib/libEGL.so
3338 3338 F DEBUG : #04 pc 0000b91b /system/bin/linker (__dl__ZN6soinfo13call_functionEPKcPFvvE+139)
3338 3338 F DEBUG : #05 pc 0000b803 /system/bin/linker (__dl__ZN6soinfo10call_arrayEPKcPPFvvEjb+323)
3338 3338 F DEBUG : #06 pc 00009423 /system/bin/linker (__dl__ZN6soinfo17call_constructorsEv+371)
3338 3338 F DEBUG : #07 pc 0000937a /system/bin/linker (__dl__ZN6soinfo17call_constructorsEv+202)
3338 3338 F DEBUG : #08 pc 0000937a /system/bin/linker (__dl__ZN6soinfo17call_constructorsEv+202)
3338 3338 F DEBUG : #09 pc 0000937a /system/bin/linker (__dl__ZN6soinfo17call_constructorsEv+202)
3338 3338 F DEBUG : #10 pc 0000937a /system/bin/linker (__dl__ZN6soinfo17call_constructorsEv+202)
3338 3338 F DEBUG : #11 pc 0000937a /system/bin/linker (__dl__ZN6soinfo17call_constructorsEv+202)
3338 3338 F DEBUG : #12 pc 00010ec2 /system/bin/linker (__dl__ZL29__linker_init_post_relocationR19KernelArgumentBlockj+7746)
3338 3338 F DEBUG : #13 pc 0000efb6 /system/bin/linker (__dl___linker_init+630)
579 3338 3338 F DEBUG : #14 pc 0001c4f3 /system/bin/linker (__dl__start+35)
将 system/bin/sensorhubd 删除后报错消失,但问题肯定是没有解决。
3.Preloading shared libraries
前面两个报错凑乎着应付过去后,日志停在下面这行,也不知道该如何分析,需要恶补一些安卓系统启动方面的知识。
3054 3054 I Zygote : Preloading shared libraries...
五、几点体会
不知道最终能否成功,但时不时总结一下很有必要。
- 小米平板2适配高版本安卓这个目标,给我提供了强烈的学习动机。前面那些摸索出来的方法和工具,以及很多不方便记录下来的小而杂的收获,都是得益于这个动机的推动。可以肯定地说,没有这个目标我无法坚持自学这么多东西,这点倒是很符合费曼学习法。
- 没有足够的知识储备,包括C++、JAVA、Android系统原理等,进行适配工作是很困难的。这些决定了不能急于求成,有强烈动机当然好,但一味急功近利地解决眼前问题,恨不得马上就见成果,是不现实的,欲速则不达,欲速往往是在走弯路。应该停下来补习补习相关知识。
- 做一件辛苦的事,冲着最终目标去当然是应该的,但设计好过程中的子目标也很重要。尤其对于系统适配这个不知道是否能成功的目标,如果最终目标没有达成,过程中也没有收获,那真是白白浪费了时间。对于这个项目,设置一些学习目标作为子目标是不错的选择。
- 做 lineage14.1 for latte 对于新手来说不是好的选择。刚开始时选择这个目标没有错,因为它对我最有吸引力,但遇到这么多不能克服的困难后,应该暂停,换一个目标。因为 lineage14.1 for latte 的 vendor 没有源码,遇到问题往往只能来回试探,效率很低,运气成分很大;Android7 相比 Android6 的变化比预想的要大,导致 lineage13.0 的代码和日志对于 14.1 来说参考性不足;小米平板2是一款比较老的设备,给它做适配的人很少,导致网上可以找到的参考资料很少。
我打算换个系统做学习样例,等知识积累足够多了,再回来继续做 lineage for latte 的适配。
希望高手大神们看到这篇文章,能给予指点,你们的一两句话也许对我就是拨云见日。拜托!
|