十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MTK平台AEE dump机制详解与user版本开启实操指南

MTK平台AEE dump机制详解与user版本开启实操指南 1. 问题背景与aee dump机制的价值前阵子接手一个项目Android Q的user版本在量产测试阶段出现了偶发重启。因为抓不到现场日志测试端反馈了三四次都定位不了问题。后来把版本切换到userdebug问题复现了一次一查aee_exp目录kernel panic信息和db信息都攒在那里五分钟左右就锁定了方向。这就是aee dump机制的价值。AEEAndroid Exception Engine是MTK平台的一套异常捕获引擎在系统发生kernel panic、watchdog timeout、native crash、system_server异常等关键故障时它会把现场的寄存器状态、内核日志、进程栈帧、内存快照打包成db文件统一存放在/data/aee_exp目录下。有了这份现场数据才能做后续的堆栈解析和根因定位。但问题在于Android Q/R之后user版本和userdebug版本的默认配置差异很大很多aee相关的功能开关在user版本里是被关闭的。原厂ROM一般没事但做定制项目的朋友如果没提前打开这些开关出了问题就会发现连aee_exp目录都是空的。这篇就把这件事讲透包括原理、配置开关、实操命令和踩坑记录。这篇文章适合谁看做MTK平台驱动、系统稳定性、性能优化的工程师尤其是手里有user版本量产项目的团队提前把本文收藏着出问题的时候能少走弯路。2. 为什么user版本和userdebug版本行为完全不同2.1 系统类型的本质差异Android系统从构建类型上分为user、userdebug和eng三种。eng完全开放主要用于开发调试userdebug在user基础上开放了adb root、部分调试属性方便开发人员验证功能user是最终交付版本需要满足安全性和性能要求所以默认会把调试通道都收掉。关键差异集中在这几个方面adb root权限userdebug可通过adb root获取rootuser版本即使做了adb root因为ro.debuggable0的原因shell权限仍受限。ro.build.type属性值不同这个属性直接决定了系统代码在运行时的行为分支。部分系统属性在user版本中被框架层屏蔽即使setprop也会被忽略。SELinux策略在user版本中更严格很多调试接口被直接deny。对于aee来说MTK的代码里通过Build.TYPE和几个关键property来控制aee的行为。比如在AEE相关的初始化代码中会做类似这样的检查if (Build.TYPE.equals(user)) { // 简化dump内容、关闭部分触发源 } else { // 完整dump }这就是为什么同一个异常在userdebug上能抓到完整db在user上可能什么都不留或者只留一条不完整的log。所谓“开启aee dump机制”本质上要做的事情就是绕过或者覆盖掉这些基于构建类型的默认分支让user版本也能完整地抓取异常现场。2.2 aee在Android Q/R中的架构演变Android Q/R里aee的架构也发生了调整。老版本中aee主要依赖aee_core和aee_dumpstate两个进程新版本中逐渐将部分能力整合到system_server的异常处理流程中同时属性的命名也做了梳理。这里要特别注意MTK平台不同软件分支上的属性名差异挺大的。比如有的分支用ro.aee.mode有的分支用persist.sys.aee.mode还有的在mk文件里直接配置宏。给大家列一下Android Q/R上比较常见的属性对照属性名作用user默认userdebug默认ro.build.type系统类型useruserdebugpersist.sys.aee.modeaee运行模式normal或offnormalpersist.sys.aee_extra_enable扩展dump开关falsetruedebug.aee.dump.enabledump总开关部分为falsetruepersist.sys.mtk.aee.debugaee调试级别较低较高这个表格是经验值不是绝对。真正做项目的时候一定要以你自己手里的代码为准去代码里搜属性名看默认值怎么定义的再对照操作。我见过有人按网上文档操作了一通结果属性名根本对不上白白浪费了时间。3. 开启aee dump的完整实操步骤3.1 检查当前配置状态拿到一台设备先确认基本状态这一步很重要不要跳过。用下面的命令把当前配置全摸一遍# 确认构建类型 adb shell getprop ro.build.type # 查看aee相关属性 adb shell getprop | grep -i aee # 确认aee_exp目录是否存在 adb shell ls -l /data/aee_exp # 看aee进程状态 adb shell ps -A | grep aee如果是userdebug版本下列属性一般应该是正常的。如果某个属性缺失或者值为off就需要手动打开。如果是user版本很可能所有aee相关属性都处于关闭状态。3.2 userdebug版本快速开启方式userdebug版本支持adb root操作起来相对简单adb root adb remount adb shell setprop persist.sys.aee.mode normal adb shell setprop persist.sys.aee_extra_enable true adb shell setprop debug.aee.dump.enable true adb shell setprop persist.sys.mtk.aee.debug true # 重启生效部分属性必须重启后才能生效 adb reboot为什么有些属性必须重启后才生效因为persist.*开头的属性会写入/data/property目录下的持久化文件理论上设置完后下次开机自动生效。但aee的初始化进程在system_server启动阶段就会读取这些属性来决定行为模式如果只setprop不重启aee模块已经按旧配置初始化完成了新值不会作用于当前运行实例。所以设置完之后务必重启重启后再getprop确认一遍。3.3 user版本开启方式user版本比较麻烦。由于user版本默认ro.debuggable0adb root无法直接使用所以不能简单通过setprop来修改系统属性。常见的做法有这几种方式一临时打开ro.debuggable只适合临场调试量产版本不要这样干。# 需要通过fastboot解锁后刷入boot镜像 # 修改boot.img中的ro.debuggable1 # 重新刷入后即可adb root方式二通过uboot或kernel cmdline加入aee相关开关。MTK平台在lkU-Boot阶段可以读取某些cmdline参数来配置aee行为。如果你有平台源码可以在device/mediatek/xxx/BoardConfig.mk或者lk配置里加上aee的默认开关。方式三最稳妥的方式编译期直接改配置。在编译user版本时直接把aee的默认配置改成完整模式# 修改的宏或mk文件配置 MTK_AEE_SUPPORT : yes MTK_AEE_MODE : normal MTK_AEE_EXTRA_ENABLE : yes然后重新编译user版本。这是量产阶段的正确做法避免线上出问题时无法抓取现场。编译期修改的好处在于所有配置在系统启动时就已经是完整的不依赖运行时权限也不存在SELinux拦截的问题。3.4 重启后验证是否真的生效配置完成后重启设备再验证一遍# 构建类型 adb shell getprop ro.build.type # aee属性 adb shell getprop persist.sys.aee.mode adb shell getprop persist.sys.aee_extra_enable adb shell getprop debug.aee.dump.enable # 检查aee_exp目录是否自动创建 adb shell ls -ld /data/aee_exp # 检查aee相关进程是否运行 adb shell ps -A | grep -i aee如果aee_exp目录存在说明aee框架已经初始化。如果该目录还不存在可能system_server还没走到创建目录的逻辑也可能是aee进程没有正确启动。这种情况下可以触发一次测试异常来验证下面第5章会讲具体方法。4. 核心参数背后的设计逻辑4.1 persist.sys.aee.mode的三种模式persist.sys.aee.mode是aee的核心模式开关常见的取值有off、normal、power三种。off完全不启用aee任何异常都不抓取normal标准模式异常发生时抓取基本的log和dbpower低功耗模式在normal基础上减少部分高频日志采集用于功耗敏感场景为什么这个属性设计成persist开头而不是ro开头因为persist属性允许运行时修改并持久化保存而ro.*属性开机时设定一次之后就不能再改了。aee的模式在开发阶段经常需要切换用persist设计更灵活。但在user版本中由于安全限制即使persist属性也需要root权限才能修改所以这个设计主要是方便开发调试用的。还有一个容易混淆的点有些分支上这个属性叫ro.aee.mode含义其实是一样的。如果同时存在persist和ro两个属性一般以生效的那个为准需要通过实际测试确认也不要死记硬背。4.2 dump完整度配置决定能抓到什么dump完整度决定了抓取的日志范围。aee在抓取db时会执行类似dumpsys、logcat -b all、cat /proc/last_kmsg等命令并把输出整理到db文件中。不同版本对user和userdebug定义了不同的完整度集合日志类型user默认userdebug默认last_kmsg / panic日志有限完整main loglogcat只保留最近一段全量system log有限全量events log有限全量dumpsys关键服务仅系统核心全部/data/tombstones不收集收集内存、CPU状态不收集收集有一个实际案例一台user版本设备偶发无响应打开aee后发现db里没有dumpsys信息只能看到kernel侧的数据。原因就是user模式下AEE的dumpstate收集逻辑被裁剪只执行了最低限度的命令集合。这种情况下就需要针对性地提升dump level。提升dump level的方式在MTK平台上一般通过persist.sys.aee_extra_enabletrue来打开扩展选项。打开后aee会额外执行system_server所有关键服务的dumpsys这不仅对问题定位有帮助也会延长dump时间可能从几十秒增加到几分钟。所以量产版本打开之前需要先评估是否接受这个耗时开销。4.3 触发源与频控模块aee并不是所有的异常都会一视同仁地抓取。它有触发源配置和频控模块。触发源包括kernel panic、hardware watchdog、framework watchdog、native crash、java crash等每个触发源都可以单独配置是否启用。频控模块用来抑制反复崩溃时的重复抓取。比如一个进程崩溃后重启、再崩溃、再重启如果没有频控aee会把存储空间写爆。所以在开启aee dump时要注意确认频控参数是否合理否则会遇到dump目录不断增大、系统频繁重启的问题。常见频控相关配置有persist.sys.aee.dump.count单位时间内的最大dump次数persist.sys.aee.dump.interval两次dump之间的最小间隔秒persist.sys.aee.clean.enable磁盘空间不足时是否自动清理旧db在userdebug版本中这些参数可能没有默认限制需要自行设置。在user版本中系统会根据构建类型自动应用一套保守的配置这也是我们常说的“user版本默认不开全量dump”的原因之一。5. 实操过程中遇到的坑与排查思路5.1 aee_exp目录不存在这是最常遇到的问题。开启aee dump后重启完发现/data/aee_exp目录还是不存在。排查步骤按这个顺序来确认aee相关属性已经设置成功getprop逐个确认别只看一个值。确认aee进程是否存在ps -A | grep aee如果进程都没有说明初始化就失败了。查看logcat中是否有AEE相关的报错logcat -b system | grep -i aee。确认SELinux是否阻止了aee创建目录dmesg | grep avc。我遇到过一次情况属性值被框架层强制覆盖。AEE的初始化逻辑在system_server启动时会根据Build.TYPE强制把某些属性重置为安全值导致手动setprop的值无效。这种需要阅读代码在对应的初始化逻辑里添加白名单或修改判断条件光靠命令行是没办法绕过去的。还有一种情况是/data分区满了。aee在启动时会检查/data目录的可用空间如果可用空间过小会拒绝创建aee_exp目录。排查时顺手用df -h /data确认一下不用绕弯路。5.2 user版本设置属性提示Permission denieduser版本默认adb shell下没有写系统属性的权限命令会直接报Permission deniedadb shell setprop persist.sys.aee.mode normal # 输出: setprop: failed to set property persist.sys.aee.mode to normal # 或者直接Permission denied这是正常的。因为setprop写系统属性需要root或者相应的SELinux权限。解决办法就是前面说的编译期修改或者通过临时的root调试手段。但要注意一个细节即使修改ro.debuggable1让user版本能adb root部分属性依然受SELinux限制。MTK平台在user版本中会收紧aee相关属性的访问策略即使拿到了root权限也未必能写入。这种情况下最彻底的办法还是重新编译boot image或者system image把aee的默认配置直接内嵌进去。5.3 dump文件很大导致/data空间被写满AEE完整dump的db文件尤其是开启了extra_enable的单个文件可以达到几百MB甚至超过1GB。在存储空间有限的设备上连续几次异常就会导致/data分区被写满进而引发其他问题。对策是这几个设置磁盘空间阈值在aee配置中指定可用空间低于某个值时自动清理旧db合理配置频控参数避免反复抓取定期导出并清理aee_exp目录不要让日志在设备上无限堆积我在一个项目中就吃过这个亏。开了完整dump后又遇到一个反复重启的bug一晚上aee_exp写了8个多GB第二天整机无法启动。后来我强行从user版本切回userdebug才把系统恢复过来把aee_exp清掉后重新配了频控参数才恢复正常。5.4 dump的db文件如何解析开启aee dump并抓到db之后接下来是解析问题。db文件是目录形式里面通常包含db ├── 00_xxx │ ├── BIN │ ├── LAST_KMSG │ ├── MAIN_LOG │ ├── SYSTEM_LOG │ ├── EVENT_LOG │ ├── DS_xxx │ └── ... ├── 01_yyy └── ...每个子目录代表一次异常。解析时的重点LAST_KMSGkernel panic或watchdog的原始信息从里面找PC地址和调用栈MAIN_LOG/SYSTEM_LOG上层日志看异常发生前的关键打印DS_xxxdumpsys输出看系统服务状态如果是native crash一般还会有tombstone或backtrace内核panic的问题通常用addr2line结合vmlinux解析PC地址。用户态crash用ndk-stack或者gdb分析。这些是另一个深挖的话题了这里先不做展开。6. 一些实际的工程建议写到最后结合我的经验给几点建议希望对做MTK平台项目的朋友有用。第一user版本的aee配置一定要在项目启动阶段就评估好而不是等到bug出现了再回头折腾。用户反馈问题是最难定位的没有现场dump定位成本会成倍增加。等出了问题再去加配置、重新编译、刷机一来一回就是几天的时间。第二userdebug版本平时可以开完整dump但发布前一定要检查一遍配置确认没有把冗余的调试开关带到user版本里。aee的完整dump在真实用户环境中会带来明显的性能开销和存储压力不是所有场景都需要开全量。第三开启aee后建议做一次异常注入测试确认确实能抓取db文件。常见的方法有这几种# 方法和注意事项说明 # 1. 通过sysrq触发kernel panic需要内核配置了sysrq adb shell echo c /proc/sysrq-trigger # 2. 杀掉system_server触发watchdog adb shell kill -9 $(pidof system_server)注意这两种测试方法都会导致系统重启请在测试机上操作不要在存有重要数据的设备上乱试。第四如果把aee关闭了比如persist.sys.aee.modeoff记得把关闭前产生的db文件导出避免误删导致问题现场丢失。检查一次aee_exp目录把有价值的db同步到电脑上这是花不了几分钟但能救命的工作。最后再说一点MTK平台各分支的属性名、默认行为经常有差异本文写的是Android Q/R上比较通用的实践。如果你在自己项目上操作时发现属性名对不上建议先到代码里搜一下aee相关的属性定义确认你所在分支的实际命名和含义再照方抓药。这篇FAQ就是想帮大家少走一些弯路。有问题欢迎在评论区交流尤其是不同分支上的差异我看到了会补充更新。
返回列表