ARTICLE DETAIL

资讯详情

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

PRODUCT_STATE修改后Core ID读取失败:根因分析与修复实录

PRODUCT_STATE修改后Core ID读取失败:根因分析与修复实录 1. 问题描述与影响范围上个月生产线上反馈了一批整机功能测试失败日志里只打了一行奇怪的报错Unable to Get Core ID after Changing PRODUCT_STATE。当时第一反应是那个从错误信息里根本看不出是哪一层报出来的但凡是做过智能硬件或者Android系统定制的人都会明白这类错误一旦出现在产线测试环节往往意味着大批量返工甚至直接影响出货节奏。先把背景交代清楚。PRODUCT_STATE——产品状态是嵌入式设备和Android终端里常见的一个标志位用来标识当前设备处于什么样的生命周期阶段。有的平台叫ProductState有的叫FactoryState有的藏在misc分区里有的通过内核cmdline传递还有的直接用persist属性管理。不同的状态值通常对应不同的使用场景比如量产模式、用户模式、测试模式、返修模式、开发模式等等。Core ID则指设备的唯一核心标识可以理解成这台设备的“身份证号”既可能是SoC的DIE ID、CPU序列号也可能是设备出厂时写入安全存储或校准分区里的唯一编号。系统在开机后会上报这个ID用于设备认证、云端绑定、售后服务、软件授权等一系列场景。真正让人头疼的是这两者之间看似毫无关联却能通过一条长长的链路互相影响。PRODUCT_STATE被修改后系统组件在读取Core ID时直接失败这属于非常典型的“上层一个字段变动、底层一条链路断裂”的问题。这条报错可能出现在Android/Linux系统的init阶段、HAL层、供应商守护进程甚至TEE安全侧。影响范围覆盖但不限于整机无法通过工厂测试、设备无法完成云平台注册、OTA升级流程被中断、系统安全服务拒绝工作。这次遇到的案例是产测阶段修改状态后重启导致无法正常进系统我花了整晚把日志翻了个底朝天最后定位到问题出在存储分区的访问控制与驱动初始化时序上。这篇文章会把整个排查链路、根因分析和修复方案完整写一遍适合做系统集成、产测工具开发、Android BSP移植、嵌入式Linux驱动的工程师参考。新手也不需要担心看不懂我会把几个关键概念先拆开讲清楚然后再进入实操环节。2. 为什么改一个状态字段会牵动Core ID读取2.1 PRODUCT_STATE到底是什么它在那里“活”着要理解这个问题首先得搞明白PRODUCT_STATE在系统里到底扮演什么角色。从工程实现上看它不是一个普普通通的配置文件字段而是一个具有“生命周期含义”的标志位经常被存放在以下三个位置之一misc或persist分区这种存储方式最常用。系统启动早期由init或vendor层的一个服务去读取分区中的原始字节解析成枚举值供上层使用。因为misc分区本身很小读写频率低适合存这类生命周期状态。内核cmdlineBootloader在启动内核时通过cmdline参数传入比如androidboot.product_stateproduction。这种方式读取最快但修改需要改bootloader或者刷boot分区参数。安全存储或RPMB高端平台会把这个状态放进安全世界管理的存储里普通内核态不能直接改写必须通过TEE接口操作。这次复现问题的设备用的是分区存储方式Bootloader把PRODUCT_STATE写进misc分区的一个结构体里Linux内核启动后由vendor模块读取并注册到sysfs节点最终暴露成/sys/xxx/product_state这样的接口。生产测试工具修改这个状态后调用reboot命令重启系统结果就出现了标题里那个经典的报错。2.2 Core ID的获取路径比你想的要深很多刚接触这块的工程师会以为Core ID就是个简单读寄存器的活儿其实不然。在主流移动平台上Core ID通常要经过如下几个环节才能到达应用层BootROM或TEE侧初始化安全密钥设备唯一ID可能与安全启动密钥、硬件唯一密钥Hardware Unique Key绑定由TEE在安全世界生成或解封。内核驱动通过SMC/ATF调用安全侧接口比如通过ARM的SMC指令陷入EL3或者TEE请求返回芯片唯一ID。HAL层封装成标准接口上层通过hidl或者其他IPC去获取。系统服务校验状态位后使用很多系统服务在获取ID前会先检查设备状态比如判断是否处于“生产模式”以便在不同状态下返回不同的行为。这个链路里的任何一个环节被截断都会导致最终拿不到Core ID。而PRODUCT_STATE恰恰可能影响到其中多处环节——最典型的莫过于当状态值不合法时TEE侧拒绝配合执行读取操作或者驱动在初始化时发现状态异常直接跳过了Core ID相关节点的注册。这就很好地解释了为什么“改状态”和“读不到ID”会同时出现。2.3 状态位影响Core ID的三种常见方式根据我平时踩坑和看过的平台代码PRODUCT_STATE影响Core ID读取的路径主要有三种影响TEE侧接口权限状态值是安全接口能否调用的前提条件。比如状态值为“生产模式”时允许读取设备唯一ID状态切到“用户模式”后只允许读取部分掩码后的ID或者干脆拒绝。面对这种设计如果状态被修改成不在合法枚举范围内的值TEE会直接给一个NOT_ALLOWED。影响驱动初始化分支内核驱动在probe阶段读取状态值然后决定是否要创建sysfs节点、是否要注册字符设备。非法状态可能导致节点完全不存在上层一打开文件就报错。影响存储分区的挂载与校验PRODUCT_STATE变化后某些分区可能需要重新校验。比如persist分区在状态改变后触发了一次重新格式化或者签名校验失败导致存储在不合法状态下不可读写那么存储在里面的Core ID备份数据自然就读不到了。把这三条路径理清楚之后整个排查方向就变得异常清晰先看状态值本身再看节点是否存在最后看TEE和安全侧有没有拒绝行为。实话说大多数时候问题没有原理上那么深奥只是没有把链路捋顺而已。3. 完整排查流程从日志到根因3.1 第一步把报错现场完整录下来遇到这种错误第一件要做的事情不是看代码而是把现场日志完整保存下来。很多同事拿到一个报错就急着翻代码结果看了半天也没头绪。正确做法是拿到一份完整的串口日志或logcat日志以及内核的dmesg输出尤其注意要保留设备启动全过程也就是从按下电源键到系统完全启动或崩溃的所有输出。比如下面这种典型的内核日志片段就能提供非常关键的信息[ 7.342129] product_state: current state 0x07 (UNKNOWN) [ 7.342850] core_id_service: failed to read core id, err -13 [ 7.343009] core_id_service: PERMISSION DENIED, check TEE status这里最值得注意的其实是两行日志之间那几百微秒的间隔。第一行说明模块已经读到了状态值但值不在预期范围内第二行紧接着报权限不够。结合这个顺序基本可以锁定问题的触发点就是状态值不合法导致后续安全接口拒绝执行。还有一类情况dmesg里根本没有任何core_id的报错但应用层收到了异常返回值这时候要看logcat里的vendor进程日志。我的经验是要把两组日志统一时间戳后对齐看因为内核日志的时间戳和Android的logcat时间格式不一样建议先做时间同步再比对。3.2 第二步确认状态值当前长相拿到日志后第一时间确认PRODUCT_STATE的当前值。不同平台的接口不一样但常见的就是从sysfs节点或者通过属性命令读取。以我们这次遇到的设备为例adb shell cat /sys/class/product_info/product_state 0x07如果输出的是一个不在平台定义范围内的值问题就非常清晰了。平台定义合法值通常是这几个0x01: 生产模式 PRODUCTION 0x02: 用户模式 USER 0x03: 开发模式 DEVELOP 0x04: 返修模式 REPAIR而当前设备上的值是0x07明显不在合法枚举值范围内。这种状态值通常是产测工具在写入时用了错误的偏移量、写入了全0xFF或者写入后没有做校验就重启了。遇到这种情况先不要急着去改驱动第一步应该尝试把状态恢复成合法值再验证问题是否消失。注意这里要看改完之后是否还需要做同步操作。有些平台写入状态后还需要通过ioctl触发一次sync或者在文件系统层面做一些标记如果少了这步重启后读出来的内容可能是缓存的旧值或者半新半旧的数据。3.3 第三步查节点是否存在还是存在但读取拒绝状态值确认后下一步要区分“接口完全不存在”和“接口存在但读取拒绝”两种情况。这一步可以通过简单的命令验证# 查看core_id节点是否存在 adb shell ls -l /sys/class/product_info/ | grep core # 如果节点存在手动读一下 adb shell cat /sys/class/product_info/core_id如果节点不存在说明内核驱动在初始化阶段就因为状态值异常跳过了节点创建。这种情况通常要在dmesg里搜索驱动probe的日志比如grep -i core /proc/kmsg找到驱动没有走正常注册流程的原因。如果节点存在但读取时报错说明问题更深一层大概率走到了TEE或者安全侧。下一步就需要看安全侧/内核的SMC调用有没有返回错误信息。3.4 第四步从内核追踪到TEE/ATF层有些平台上TEE侧的日志需要通过cat /proc/tee_log获取或者通过vendor提供的调试工具抓取。这一步最容易被忽略因为普通内核日志往往只显示一句err -13不会告诉你安全侧到底为什么拒绝。但实际上TEE侧可能详细记录了拒绝原因比如[ERROR] TA_CORE_INFO: access not allowed in current state [ERROR] TA_CORE_INFO: state 7, expected 1-4如果抓不到TEE日志可以看一下内核代码中tEE接口调用的返回值。通常在驱动代码里会有错误码的转换逻辑比如-13表示EACCES但在某些内核版本中也可能是-EPERM。要结合具体平台代码去转化错误码的含义。从我个人的排查经验来看能够走到这一步问题的定位工作其实已经完成了大半。剩下的核心任务就是回答一个问题为什么状态值变非法后TEE或驱动不再配合读取ID以及如何让系统恢复到可用的状态。4. 修复方案与恢复路径实录4.1 恢复期望状态值最直接的修复方式就是把PRODUCT_STATE恢复成合法值。这个操作在产线环境下尤其方便因为产测工具本身就带写入功能只是写入逻辑有缺陷或者参数传错。在实际操作中可以通过下面两种方式之一来恢复第一种通过SoC厂家提供的工具重写。比如很多平台提供了fuse工具或者工厂烧录工具可以从Bootloader侧直接写入正确的状态。这种方式最为彻底因为不经过内核直接把正确值刷进misc分区。第二种在系统里手动写。以我们遇到的设备为例misc分区在Linux下可以通过dd命令直接操作# 确认misc分区路径 cat /proc/partitions # 找到misc分区对应的块设备如 /dev/block/by-name/misc # 写入结构体需要确认偏移位置 dd ifcorrect_state.bin of/dev/block/by-name/misc bs1 count4 seekOFFSET sync这里要特别提醒一下dd写misc分区之前务必确认分区结构和写入偏移。我曾经见过一位同事因为offset算错一位把一个好端端的设备写成了砖。更安全的做法是先用备份工具把原有分区内容完整保留一份再执行写入操作。操作完成后重启设备确认状态值是否被正确读取。4.2 清理异常状态缓存有些平台上PRODUCT_STATE不止存在一个地方系统可能会在persist属性里缓存一份或者在RPMB里存了一份备份。改完主存储后如果缓存还是旧值开机后依然会报错。常见的做法是清除相关属性缓存adb shell rm /data/property/xxx adb shell rm /mnt/vendor/persist/property/xxx adb reboot如果是RPMB场景则只能通过TEE提供的事务接口来更新备份值普通文件操作并不能生效。我建议在修复前先检查平台文档里是否定义了备份机制别修完主存储才发现还有一份“二主”。4.3 从驱动侧做兜底处理如果是产品已经量产、市面上存在一批状态值错误的设备光靠人工恢复不现实这时候就需要在驱动侧做兜底容错。驱动在读取到非法状态值时可以自动用默认值代替并把异常记录到日志里static int product_state_get(void) { uint32_t state read_from_misc(); if (state STATE_PRODUCTION || state STATE_REPAIR) { pr_warn(invalid product state %d, fallback to USER\n, state); state STATE_USER; } return state; }这种做法在工程上属于“防御式兜底”既保证系统能正常启动也让后续排查有日志可循。不过要注意如果状态值涉及到安全启动、签名校验等强安全场景这种兜底可能会被视为安全漏洞需要和平台安全团队确认后再实施。4.4 恢复后必须验证哪些点修复完成后不要直接宣布“搞定”。我会按下面这个清单逐项确认避免遗漏/sys/class/product_info/product_state的值是否为目标合法值。/sys/class/product_info/core_id是否能正常读出且与出厂记录一致。系统日志中是否还有Unable to Get Core ID或类似的报错。尝试调用一次设备认证或者云平台注册流程确认整条链路通畅。再做一轮开关机测试至少连续重启五次确保没有偶发问题。只有这些全部通过我才会认为修复真正完成。5. 常见问题排查速查表与避坑心得5.1 高频问题速查表下面这张表整理了我在这类问题上遇到最多的几种情况和对应的处理方向建议直接保存备用。现象可能原因排查方向处理方式状态值读取为0xFF写入时偏移错误或分区未初始化读misc分区原始内容确认结构体位置重写正确状态并重启状态值合法但core_id节点不存在驱动初始化时状态判断逻辑有bug查dmesg中驱动probe日志修改驱动状态判断逻辑兼容合法值节点存在但read返回-13TEE侧接口拒绝当前状态下访问抓TEE日志确认拒绝原因恢复合法状态或调整TEE策略修改状态后立即读ID失败状态写入未同步文件系统缓存确认写后是否执行sync/fsync补执行sync再重启修复后偶发仍读不到persist分区备份值异常检查persist属性缓存清除缓存并重启5.2 产测工具侧最容易踩的坑从这次事件追根溯源问题本质上出在产测工具写入逻辑上。常见的坑有三个第一写入时没做范围校验。工具界面允许传入任意值没有限定合法枚举范围结果操作员输入一个非法值就直接写入。解决办法很简单在工具端加上枚举校验非合法值的输入直接拦截。第二写入后没有等待保存完成就断电重启。实际上写入misc分区虽然很快但如果目标分区是emmc或ufs写缓存的存在意味着数据可能并没有真正落盘。正确做法是写完以后执行一次sync或者通过ioctl发送BLKFLSBUF确保数据完全落盘再断电。第三没有在重启前做写回验证。写入完成后立刻读一次确认写入值正确再重启。就这么简单的一个步骤能省掉后面一大串的返工。5.3 排查过程中为什么容易绕远路我在处理这个问题时最初有两个小时是在乱转原因就是被表面的错误信息带偏了。看到“Unable to Get Core ID”就只想着从Core ID那条链路往前找却忽略了真正触发点是前面的状态值判断。这其实也提醒所有做系统底层的人报错信息往往只是最终的结果根本原因可能藏在距离它几层远的地方。后来我调整策略先把日志按时间线拉出来把报错前后的每一个关键输出都标出来结果发现状态值判断是在Core ID报错前几百微秒打印出来的。这个顺序上的关系比任何代码分析都更直接。5.4 平台差异与提前预防不同平台的实现差异很大有的平台把所有状态都放在内核cmdline里改起来特别快但一旦写错连系统都起不来有的平台用AB分区做备份状态损坏后还能从另一个槽位恢复还有些平台在Bootloader阶段就做了状态校验非法状态直接拒绝启动内核。所以拿到一个陌生平台时第一步永远不是写代码而是先读平台提供的启动流程文档搞清楚状态值是谁读的、在哪里读的、读完后分发给哪些模块。我的建议是在项目开发阶段就把状态值校验做进启动流程里内核驱动起来后如果读到非法状态值用默认值兜底并打印警告日志产测工具端做好枚举校验TEE侧对非法状态值也返回明确的错误码。这三道防线只要有一道这次的事故就不会发生。尤其是驱动侧那一层看起来简单但在海量设备返修的压力面前它是最后也是最可靠的救生圈。6. 最后再分享一个小技巧这次排查过程中还有个比较实用的工具配合经验在设备上开启init.svc.vendor.debug1以及对应的log.tag级别调整可以让vendor进程在启动阶段打印出更多调试信息。这些日志在正常系统里是看不到的但产测阶段开启后几乎可以完整观察到状态值从Bootloader到HAL层的全过程传递定位效率提升非常明显。具体命令如下adb root adb shell setprop log.tag.CoreIDService V adb shell setprop init.svc.vendor.debug 1 adb shell stop adb shell start如果项目里已经有了一套完整的产测日志采集系统建议把状态写入和ID读取这两个关键动作的日志打点做得更细一些最好带上时间戳和操作者身份。这样就算出了问题也能快速回溯到是哪一批设备、哪个环节、哪个操作参数导致的状态异常而不是对着一个光秃秃的Unable to Get Core ID发愁。这玩意儿排查过一次之后你就会发现它更像是一个“连环扣”看着是A问题实际是B问题但真正要改的可能是C模块里的一行判断。把链路摸清楚后面再遇到类似问题基本上半小时之内就能定位。
返回列表