Android 15 上适配 16K Page Size 的填坑思路,以 IJKPlayer 为例子
The following article is from GSYTech Author 恋猫de小郭
如果没有看过前两期的,可以回顾下:
Android 15 上 16K Page Size 为什么是最坑 Android 15 之如何快速适配 16K Page Size
确定 so 是否 16K 对齐,可以通过前两篇文章里的脚本或者 readelf 工具进行判断,例如 readelf 的到 4000(16384),就认为对齐。 代码是否在 mmap / sysconf 写死了 4096(0x1000)
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
强调这个,是因为最近真的有人问我,为什么我改了 xxx 4096 ,但是没效果
NDK r27 之前,Android.mk 增加 LOCAL_LDFLAGS += -Wl,-z,max-page-size=16384 NDK r27 开始,Application.mk 配置 APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true
3.13 之前:target_link_libraries(a4ijkplayer "-Wl,-z,max-page-size=16384") 3.13 之后:target_link_options(a4ijkplayer PRIVATE "-Wl,-z,max-page-size=16384")
而今天后面要讲的,则是不正常的情况,就是你按照上面的调整之后,依然会有 SIGNAL Crash 的填坑思路,大多都是因为「陈年的代码,上古的工具链,复杂的引用」所造成的问题。
有源码 能支持编译的环境
默认 IJKPlayer 推荐使用 android-ndk-r10e ,关于环境配置的问题这里就不多赘述,毕竟这不是本篇的重点,需要的可以看:https://github.com/CarGuo/GSYVideoPlayer/blob/master/doc/BUILD_SO.md#macos-%E7%8E%AF%E5%A2%83%E4%B8%8B%E6%96%B0%E9%85%8D%E7%BD%AE%E4%BB%8B%E7%BB%8D
java.lang.UnsatisfiedLinkError: dlopen failed: empty/missing DT_HASH ···· (new hash type from the future?)
虽然 AArch64 编译器默认生成 ELF 文件部分与 64K 对齐,从而兼容所有 AArch64 机器,但如果工具出现 bug ,例如旧版本 patchelf 操作的二进制文件,或自定义编译器标志不对,就可能会导致某些二进制的部分仅与 4K 对齐。
tools/do-detect-env.sh 文件下增加你想支持的 ndk 版本:11*|12*|13*|14*|15*|16*|21*|22*|25*|26*|27*) Bad file descriptor error: invalid argument '-std=c99 :此时你需要在所有 Android.mk 下将所有的 LOCAL_CFLAGS += -std=c99 去掉,因为 GCC 早在 r18 就被移除了 Invalid NDK_TOOLCHAIN_VERSION value: 4.9 :同样是因为 GCC 已经移除,所以需要将 Application.mk 下的 NDK_TOOLCHAIN_VERSION=4.9 移除 APP_STL := stlport_static is not supported :同样在 r18 的时候,stlport 也已经被移除,所以我们需要将 Application.mk 下的 APP_STL 修改为 APP_STL := c++_static ,采用 libc++ 实现 从 r21开始 ndk-bundle/build/tools/make-standalone-toolchain.sh 中会调用 ndk-bundlel/build/tools/make-standalone-toolchain.py ,所以需要把 tools/do-compile-ffmpeg.sh 下对应的 FF_ANDROID_PLATFORM 修改为 FF_ANDROID_PLATFORM=android-21 ,同理还是有 tools/do-compile-openssl.sh
ld: error: relocation R_AARCH64_ADR_PREL_PG_HI21 cannot be used against symbol ff_cos_32; recompile with -fPIC
GNU binutils 已弃用,将在即将发布的 NDK 版本中被删除,包括 GNU assembler (as) ,如果使用 as 请转为 clang LLD 现在是默认链接器 libc++ 升级 llvm-ar 代替 ar llvm-strip 代替 strip make 升级到 4.3
ar:用于生成静态库,类似打包器 as: 汇编器,变为 .o 文件 strip: 裁剪符号,瘦身 LLD:llvm 的链接器
在 r22 之前,对于 arm64 默认的 NDK 链接器是 ld.bfd,其他架构是 ld.gold,而 r22 将默认链接器切换为 ld.lld
# 得到 1 这个 num
./aarch64-linux-android-readelf --sections -W xxx.so
# -x 1 看这个 num 下的内容
./aarch64-linux-android-readelf -x 1 xxx.so
另外,曾经还遇到过 p_offset + p_filesz > file_size 的情况,这种时候基本也是工具链的问题,当时好像也是通过 llvm-strip 代替 GNU strip 解决了问题。
详细的具体原因这里就不再深入展开,这里更多是一个提示:如果出现「玄学」的报错,可能更多来自工具链的问题。
移除 stlport、gnustl 标准库的,使用 LLVM 的 libc++,如 c++_static、c++_shared 使用较高版本 Clang/LLVM 替代 GCC/Binutils 进行编译适配 低版本下 64k(0x10000) 对齐,其实并不一定对齐
毕竟例如 IJKPlayer 使用的 stlport STL ,已经十多年没更新了,关于页面大小的计算适配,还是在 2007 年的时候;另外 GNU 的 libstdc++ 与 LLVM Clang 编译器配合也不是很好。
所以,在 16K Page Size 的适配上,只要你没有 mmap 、sysconf 等写死 4096(0x1000) 地方的代码,那么完全就可以怀疑是 NDK 对应的工具链和标准库的问题。
NDK r11 开始建议切换到 Clang NDK r12 ndk-build 命令默认使用 Clang make-standalone-toolchain.sh 脚本即将删除,需要尽快计划迁移到 make_standalone_toolchain.py,所以如果你的 ndk 已经删除了,可以在老版本中找到兼容方式: NDK r13 GCC 不再受支持,它暂时不会从 NDK 中删除 NDK r14 GCC 弃用。但未从 NDK 中删除 NDK r16 鼓励使用 libc++ 作为 C++ 标准库 NDK r17 libc++ 现在是 CMake 和独立工具链的默认 STL 删除对 ARMv5 (armeabi)、MIPS 和 MIPS64 的支持 NDK r18 GCC 已被删除,包括 GNUSTL ,包括 gabi++ 和 stlport 一起被删除。 NDK r22 GNU binutils 已弃用,将在即将发布的 NDK 版本中被删除,包括 GNU assembler (as) ,如果使用 as 请转为 clang LLD 现在是默认链接器 libc++ 升级 llvm-ar 代替 ar llvm-strip 代替 strip make 升级到 4.3 NDK r23 GNU binutils已被删除,GAS 将在下一版本中删除 对 GDB 的支持已终止 NDK r23 是最后一个支持非 Neon 的版本 NDK r24 GNU 汇编器 (GAS) 已被移除 非 Neon 设备不再受支持 NDK r27 支持 APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true
所以升级 NDK 不要一下跨度太大,因为升级的成本真的很高,很容易让人放弃,而逐步升级,直到可以运行的方式会比较合适。
二进制文件主要指 *.o 文件和 elf 执行文件,编译源代码的是 gcc,所以 Binutils 不包含 gcc。
Clang 的设计初衷是提供一个可以替代 GCC 的前端编译器,因为 GCC 的发展不符合 Apple 的节奏和需要,同时受限于License,苹果公司无法使用 LLVM 在 GCC 基础上进一步提升代码生成质量,因此苹果公司决定从头编写 C、C++、Objective-C 语言的前端 Clang,以彻底替代GCC。
那为什么会有这些东西存在?
Clang/LLVM 和 GCC/Binutils 一般是成对出现,Clang 和 GCC 主要是提供对于拓展的编译支持,而 LLVM 和 Binutils 则是提供对应的工具支持。
如果真要说,GCC 比 Clang 支持更多的传统语言和冷门架构,而新兴语言基本使用 LLVM 居多,如 Swift、Rust、Julia 和 Ruby。
STL 属于是单独开发,然后提交给 C++ 标准委员会进行审议并接纳,但它不是作为 C++ 标准的一部分开发的,也就是前面我们说到的扩展支持,所以它会有 GNU 和 LLVM 不同版本。
std::sort(container.begin(), container.end());
system(default) 系统默认的 C++运行库 gabi++_static 静态链接的方式使用 gabi++ gabi++_shared 动态链接的方式使用 gabi++ stlport_static 静态链接的方式使用 stlport 版本的 STL stlport_shared 动态链接的方式使用 stlport 版本的 STL gnustl_static 静态链接的方式使用 gnustl 版本的 STL gnustl_shared 动态链接的方式使用 gnustl 版本的 STL c++_static 以静态链接的方式使用 LLVM libc++ c++_shared 以动态链接的方式使用 LLVM libc++
system:默认最小的 C++ 运行库,这样生成的应用体积小,内存占用小,但部分功能将无法支持,只提供默认的一些固定标头。 gabi++:不支持 C++ 标准库,提供与默认运行时相同的标头,但加入了对异常处理和 RTTI 的支持。 stlport:提供 C++ 的特性支持,它是开源项目 STLPort 的一个 android 移植版本。 gnu-libstdc++:GNU 标准 C++ 运行时库,同样支持异常和 RTTI。 llvm-libc++:该版本是 LLVM libc++ 的一个移植版本,支持 C++ 的所有特性。
例如,如果 NDK 开发的 App 用到 libc++_shared.so ,那么其实整个 .so 会被打包到 APK里,用到libc++_static.a 的情况下,.a 也是被包到 App 中,在发布应用时,完整的 STL 会跟随一起发布,不依赖Android 版本内部的 STL 。
你存在有依赖使用了 STL 的闭源第三方依赖项,那么你必须使用与依赖项相同的 STL,两个不同的 STL 构建的库可能会存在冲突,例如 std::string 在 libc++ 和 gnustl 就存在差异化。
确保代码里没有 mmap 、sysconf 等写死 4096(0x1000) 地方的代码(本篇第 N 次出现)。 确保不是假对齐,例如低版本 NDK 编译下的 0x10000(65536) 64k 对齐不一定可信。 4K 可运行的情况下, 16K 在确定 so 都是地址对齐的情况下,高度怀疑 NDK 问题。 升级 NDK or 工具链合集,首选升级为 Clang/LLVM 相关。
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!