当前位置: 首页 > news >正文

Android 15 之如何快速适配 16K Page Size

在此之前,我们通过 《Android 15 上 16K Page Size 为什么是最坑》 介绍了:

  • 什么是16K Page Size
  • 为什么它对于 Android 很坑
  • 如何测试

如果你还没了解,建议先去了解下前文,然后本篇主要是提供适配的思路,因为这类适配更多工作量在于实际的执行调整和编译跟踪,而非功能上的适配,本质不复杂,但可能量大且繁琐

是的,想要适配你首选需要有 C/C++ 等 so 库的源码。

适配的开始,我们可以先粗暴的全局搜索 「4096」关键字,看看是否有将 4096 搭配 mmapsysconf 等相关的地方,因为 Android 上的 4K 这么多年已经「深入人心」,可以说不少代码都将 Android 默认为「4096」。

对于这些地方,我们可以通过类似 getpagesize() 等方式来进行调整,例如通过 ALOGV("####### %d", getpagesize()); 可以看到在 Android15 上输出的是 16384 。

接着我们可以运行项目到 Android 15 的模拟器上(如果运行不起来看前文),看 so 是否能正常被加载执行,一般情况下你可能会看到类似:

java.lang.UnsatisfiedLinkError: dlopen failed: empty/missing DT_HASH ···· (new hash type from the future?)

这类问题基本都是 so 文件没有 16K 编译对齐的原因,此时,根据你的 CMakeList 版本,可以使用 target_link_options 或者 target_link_libraries 进行调整。

例如 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")

注意 CMakeList 的版本还需要 SDK Manager 里有下载安装。

如果是 Android.mk , 则添加 LOCAL_LDFLAGS 也可以配置 16K Page :

LOCAL_LDFLAGS += -Wl,-z,max-page-size=16384

编译后,我们通过 readelf 工具,可以对比编译前后两个 so 的 elf 对齐情况,工具一般位于 /Users/guoshuyu/Library/Android/sdk/ndk/21.4.7075529/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin,通过以下命令可以输出对应参数:

./aarch64-linux-android-readelf -l /Users/guoshuyu/workspace/android/******/libs/arm64-v8a/libijkffmpeg.so

如下两种图所示:

  • 图 1 LOAD 段在 Align 栏目显示 1000 (16进制,即 4096) ,也就是还没增加 16K 对齐的状态
  • 图 2 LOAD 段在 Align 栏目显示 4000 (16进制,即 16384) ,也就是修改编译后,已经增加 16K 对齐的状态

这里我们只关心 LOAD 段,因为一般只有 LOAD 段需要加载到内存中。

所以简单情况下,你也可以通过 readelf 工具查看 so 的对齐状态

另外,如果你在低版本 NDK (低于 r27) 上使用动态链接到 C++ 标准库,那么可以也会遇到 1ibc++_shared.so 的相关报错,解决的思路还是:

  • 升级到 NDK r27
  • 将 C++ 标准库静态链接到 so 库里面,例如 -DANDROID_STL=c++_static
        externalNativeBuild {cmake {abiFilters 'armeabi-v7a', 'arm64-v8a'arguments '-DANDROID_ARM_NEON=TRUE', '-DANDROID_STL=c++_static'}}

之后,如果 so 运行过程中还存在异常问题, 可以通过地址信息来进行代码定位,例如使用常见的:addr2line

addr2line 的可执行文件位置,一般位于以下位置:

/Users/guoshuyu/Library/Android/sdk/ndk/21.4.7075529/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin

注意 ndkVersion 的版本还需要 SDK Manager 里有下载安装。

其中 ndk 版本为项目 build.gradle 下配置的 ndk 版本,例如 ndkVersion '21.4.7075529' ,之后你只需要通过执行命令输出,即可看到出现问题的文件和行数:

 ./aarch64-linux-android-addr2line -e /Users/guoshuyu/workspace/android/··········/build/intermediates/merged_native_libs/debug/out/lib/arm64-v8a/????.so 000000000007e0a0

当然,也可以增加 -f,通过 ./aarch64-linux-android-addr2line -f -e 来查看出问题的函数是什么:

例如,对于 C++ 里静态存储区域分配,内存在程序编译的时候就已经分配好,而 static 这块内存变量上,如果 so 对齐和地址存在问题,也可能会导致访问地址异常。

如果上述在使用 addr2line 输出时看到都是 ?? ,那么你可能需要修改下编译的调试支持和优化级别,例如 Application.mk 上降低优化等级为 APP_CFLAGS := -O0

当然,很多时候存在许多「玄学」的问题,例如前段时间就遇到了一个神奇的 Bug ,甚至也不知道为什么,但是又能改好它。

举个不严谨的例子,通过地址定位,可以定位到报错的地方是这个 static 的全局变量报错,但是为什么会出现 Fatal signal 11(SIGSEGV),code 2(SEGV_ACCERR) 让人费解。

如果按照正常场景来看,C 和 C++ 中的静态变量通常存储在 “data” 区域,但是对于任何未初始化或初始化为零的全局数据,都是存放在 “bss” :

正常情况下,bss 段属于静态内存分配,通常是用来存放未初始化的全局变量和未初始化的局部静态变量,所以 bss 段只是给未初始化的全局变量和未初始化的局部静态变量预留位置而已。

我们可以通过前面的 readelf 工具看一下,如下命令所示 ,通过 -s | grep _errMsg 输出,我们可以看到 _errMsg 确实存在 [23] 的 bss 的 NOBITS 位置。

./aarch64-linux-android-readelf -S /Users/guoshuyu/workspace/*****.so -s | grep _errMsg

但是从输出看,貌似也没什么问题,如果从另外的 .text.rodata 的 Address 上看, 165670 - 071fa0 = F36D0 ,也正好是 4000 的 3C 倍数 ,所以分页应该也没问题。

而通过 objdump 输出的结果看,也和没看出异常,所以问题就变得很玄学,根据猜测,可能是以下某个之一的问题:

./aarch64-linux-android-objdump -h /Users/guoshuyu/workspace/android/****.so
  • 某个过程编译的对齐有问题
  • NDK 的 gcc 优化存在一些奇怪问题, 例如 gcc 不仅观察变量如何定义,还观察了变量如何在代码中使用
  • 赋值的寻址有问题没对齐
  • 模拟器系统问题

在偶然之下,屏蔽掉某个使用 _errMsg 的 static function 的 static 声明后,它居然就正常运行了,就是偶尔删掉了某个函数的 static 声明,它突然就好了···所以我也不知道是编译的问题还是系统的问题,也许后面有空再深入研究下,不过在适配过程中,真的是「运气好」的情况下,你只需要改一下配置,编译完就可以立即使用,运气不好的情况下,真的就很「玄学」。

最后,如果你需要升级到 NDK r27,你可能还需要对代码的编排方式做一些调整,例如类似 ISO C99 and later do not support implicit function declarations 等小细节问题,因为 NDK r27 目前是 clang-r522817 的版本:

根据 r27 目前的 clang_source_info.md ,其 clang 应该会是 18.1.0 ,也就是包含 C++ 20/23/2C 等的支持,如果升级跨度较大,其实可能会是改了旧坑来新坑的节奏。

例如腾讯目前的 MMKV,在 issue#1353 就提到了兼容的评估问题,虽然对于使用者来说,可能就是几个简单配置就可以重新编译支持,但是对于平台方来说,升级环境是一个“高风险”行为,所以还需要谨慎而行。

最后,这个适配的基础还是你有源码,如果你连源码都没有,那么基本就没希望了,对于 android 环境下,基于 linux 的 4k/16k 内核是不支持混用(详细原因见前文),所以如果你没做适配,很大可能 so 是无法正常运行,不过好消息是,OEM 厂商可以不启用,坏消息是,Google 表示明年在 Google Play 上会强制要求

相关文章:

  • Spring Boot 学习(10)——固基(Idea 配置 git 访问 gitee)
  • JSON字符串介绍
  • 【深度学习图像】拼接图的切分
  • GIS技能应用(1)
  • Web前端:HTML篇(二)元素属性
  • SpringBoot缓存注解使用
  • 如何在Linux中打开core文件
  • 【手撕数据结构】拿捏单链表
  • 前后端分离项目部署,vue--nagix发布部署,.net--API发布部署。
  • TYPE-C接口PD取电快充协议芯片ECP5701:支持PD 2.0和PD 3.0(5V,9V,12V,15V,20V)
  • 【数据结构】探索排序的奥秘
  • HTML零基础自学笔记(上)-7.18
  • laravel框架基础通识-新手
  • 【计算机视觉】siamfc论文复现实现目标追踪
  • 基于 Electron+Vite+Vue3+Sass 框架搭建
  • ----------
  • 《Javascript高级程序设计 (第三版)》第五章 引用类型
  • 5、React组件事件详解
  • Angular 响应式表单之下拉框
  • Apache的80端口被占用以及访问时报错403
  • Apache的基本使用
  • Docker 1.12实践:Docker Service、Stack与分布式应用捆绑包
  • FastReport在线报表设计器工作原理
  • Golang-长连接-状态推送
  • HTTP请求重发
  • javascript从右向左截取指定位数字符的3种方法
  • Java的Interrupt与线程中断
  • js作用域和this的理解
  • MySQL数据库运维之数据恢复
  • Netty 框架总结「ChannelHandler 及 EventLoop」
  • Python - 闭包Closure
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • 测试如何在敏捷团队中工作?
  • 从@property说起(二)当我们写下@property (nonatomic, weak) id obj时,我们究竟写了什么...
  • 高程读书笔记 第六章 面向对象程序设计
  • 紧急通知:《观止-微软》请在经管柜购买!
  • 前端工程化(Gulp、Webpack)-webpack
  • 前端路由实现-history
  • 做一名精致的JavaScripter 01:JavaScript简介
  • zabbix3.2监控linux磁盘IO
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • ​​​​​​​sokit v1.3抓手机应用socket数据包: Socket是传输控制层协议,WebSocket是应用层协议。
  • #QT(一种朴素的计算器实现方法)
  • #我与Java虚拟机的故事#连载18:JAVA成长之路
  • ${factoryList }后面有空格不影响
  • $refs 、$nextTic、动态组件、name的使用
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (2024,LoRA,全量微调,低秩,强正则化,缓解遗忘,多样性)LoRA 学习更少,遗忘更少
  • (42)STM32——LCD显示屏实验笔记
  • (C语言)fgets与fputs函数详解
  • (Note)C++中的继承方式
  • (NSDate) 时间 (time )比较
  • (笔记)Kotlin——Android封装ViewBinding之二 优化
  • (笔试题)分解质因式
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上