记Android12上一个原生bug引起的system_server crash

欢迎使用Markdown编辑器

一. 现象描述

近日测试上报一个几乎必现的crash,描述如下:
现象: launcher编辑状态与锁屏解锁交互时系统概率性重启
操作步骤:

  1. 进入launcher组件编辑状态
  2. 按电源键灭屏后亮屏,锁屏界面上滑解锁
  3. launcher编辑状态向右或向左滑动
  4. 重复1,2,3 步骤多次操作,观察系统重启情况

二. 初步分析

随后在提供的日志中搜索“crash”,果然发现了以下的报错信息打印:

从日志中大概能分析出3点重要的信息

  • crash 发生在system_server进程
  • 报错打印是Could not find consume time for seq=3513
  • crash大概发生在input时间在被client端处理完毕后向 inputdispatchar 发送 finsh的阶段

在这里插入图片描述
除此之外,在大概5s后,crash进一步引起了anr,根据anr的提示信息:** ANR in gesture monitor owned by pid:1045. Reason: PointerEventDispatcher0** 可进一步推断,**crash发生在 手势处理的过程中向 inputdispatchar 发送 finsh的阶段
**
在这里插入图片描述

三. 分析crash发生的原因

猜想1

根据报错的提示信息和堆栈打印,在结合代码,报错发生在frameworks/native/libs/input/InputTransport.cpp中
在这里插入图片描述

上文提到发生crash发生在 手势处理的过程中向 inputdispatchar 发送 finsh的阶段,大概流程如下

  1. client端梳理完事件代用finishInputEvent随后进一步来到图中1的位置,
  2. 根据seq在容器mConsumeTimes中尝试获取一个时间戳
  3. 向input端发送finish消息
  4. 从容器mConsumeTimes中移除该seq对应的对象

crash发生在阶段2,从容器mConsumeTime获取不到该seq对应的对象,然后报错 Could not find consume time for seq=3513,然后根据该类的其他代码得知,mConsumeTime 是一个map类型的容器,在事件分发前,会以事件的seq为key,当前时间为value添加一条记录,当时间被client端处理完毕后然后从该容器中移除对应seq的记录。
在这里插入图片描述
结合代码和报错的注释,初步猜想是对应事件的finish调用了两次引发了crash:
在这里插入图片描述
为了验证猜想,随后放开了input相关的日志,进行复现,发现发生crash时 对应seq的事件确实调用了两次finish:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

猜想是正确的,现在的问题变成了为什么status_t InputConsumer::sendFinishedSignal发生了两次,

进一步猜想

进一步阅读代码得知frameworks/base/core/jni/android_view_InputEventReceiver.cpp中会调用到 InputConsumer::sendFinishedSigna.
对于普通的touch事件来说,调用到InputConsumer::sendFinishedSigna.有两种情况:

  1. 情况1
    事件被java层的frameworks/base/core/java/android/view/InputEventReceiver.java正常处理完毕后,主动调用finishInputEvent随后再来到natvie层调用到 InputConsumer::sendFinishedSigna. 如下图
    在这里插入图片描述
  2. 情况二
    在时间分发阶段,构造了一个java层的事件对象,然后交给java层去处理,java层处理阶段出现了异常,native层的NativeInputEventReceiver捕获到了该异常,NativeInputEventReceiver会发起一次InputConsumer.sendFinishedSignal
status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {.......bool skipCallbacks = false;for (;;) {uint32_t seq;........jobject inputEventObj;switch (inputEvent->getType()) {// 构造 MotionEvent 对象case AINPUT_EVENT_TYPE_MOTION: {if (kDebugDispatchCycle) {ALOGD("channel '%s' ~ Received motion event.", getInputChannelName().c_str());}MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {*outConsumedBatch = true;}inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);break;}........default:assert(false); // InputConsumer should prevent this from ever happeninginputEventObj = nullptr;}if (inputEventObj) {if (kDebugDispatchCycle) {ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName().c_str());}env->CallVoidMethod(receiverObj.get(),// 调用java方法,分发事件gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);if (env->ExceptionCheck()) {// 捕获到java层的异常,使得skipCallbacks = true;ALOGE("Exception dispatching input event.");skipCallbacks = true;}env->DeleteLocalRef(inputEventObj);} else {ALOGW("channel '%s' ~ Failed to obtain event object.",getInputChannelName().c_str());skipCallbacks = true;}}if (skipCallbacks) {ALOGD("NativeInputEventReceiver consumeEvents seq=%u ", seq);// 因为前面捕获到异常,skipCallbacks == ture。 调用mInputConsumer.sendFinishedSignalmInputConsumer.sendFinishedSignal(seq, false);}}
}

在上文两处中添加log,复现crash log如下:
在这里插入图片描述

可见对于seq 3513 ,正常情况和发生异常的情况都调用了一次InputConsumer.sendFinishedSignal!!!

总结一下目前的线索:在桌面手势分发seq == 3513的事件阶段出现了异常,异常被native层的NativeInputEventReceiver得知,然后发起了一次sendFinishedSignal, 同时java层在对seq ==3513事件处理结束后,主动调用了一次sendFinishedSignal,两次调用sendFinishedSignal进而引发crash。

有新的疑问涌出:

  1. 桌面手势分发seq == 3513的事件阶段出现了什么异常
  2. 为什么正常的情况和异常的情况都触发?

四. 发现端倪

在native检测到异常的分支里面添加对错误的打印,然后复现问题,发现如下打印,原来是上层发生可空指针问题
在这里插入图片描述
但,这个空指针问题怎么会使得 异常分支和正常分支的sendFnishSignal都触发呢?随后在PointerEventDispatcher这个类中发现端倪:

frameworks/base/services/core/java/com/android/server/wm/PointerEventDispatcher.java
@Overridepublic void onInputEvent(InputEvent event) {try {if (event instanceof MotionEvent&& (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {MotionEvent motionEvent = (MotionEvent) event;if (ENABLE_PER_WINDOW_INPUT_ROTATION) {final int rotation = mDisplayContent.getRotation();if (rotation != Surface.ROTATION_0) {mDisplayContent.getDisplay().getRealSize(mTmpSize);motionEvent = MotionEvent.obtain(motionEvent);motionEvent.transform(MotionEvent.createRotateMatrix(rotation, mTmpSize.x, mTmpSize.y));}}PointerEventListener[] listeners;synchronized (mListeners) {if (mListenersArray == null) {mListenersArray = new PointerEventListener[mListeners.size()];mListeners.toArray(mListenersArray);}listeners = mListenersArray;}for (int i = 0; i < listeners.length; ++i) {listeners[i].onPointerEvent(motionEvent);}}} finally {  // catch呢?????finishInputEvent(event, false);}}

如上,分发完事件后,无论是否发生异常都会在finally中调用finishInputEvent,同时虽然有try, final,但没有catch,这就意味着该段逻辑并未捕获任何类型的异常!!!!,真相大白:

  1. 为什么正常情况的sendFnishSignal会被触发? 事件处理结束后触发了 finally 执行了finishInputEvent,随后进一步触发sendFnishSignal
  2. 为什么异常情况的sendFnishSignal也会被触发? 首先事件分发阶段发生了异常,但PointerEventDispatcher并没有catch住相关的异常,随后异常被native层的NativeInputEventReceiver获知,进入了异常分支也触发了一次sendFnishSignal。

但,疑问还在继续,这似乎是google的原生的一个bug,为什么谷歌的代码这样处理?无论正常还是异常情况下 java层调用finishInputEvent 触发一次sendFnishSignal然后native层检测到异常 再触发一次 sendFnishSignal,Java层和native层的代码不像同一个人写的,都考虑到了异常情况要去触发sendFnishSignal,然后就发生了在分发阶段出现异常时同一事件就发生了两次 sendFnishSignal

这么明显的bug,谷歌应该能发现吧? 果然:
在这里插入图片描述
谷歌的修复: 发生异常时native层不再触发sendFnishSignal
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/71075.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

系统架构设计师—计算机基础篇—计算机体系结构

文章目录 计算机硬件分级存储体系目的特点 硬件组成CPU运算器控制器 主存储器 指令系统流水线 内存按字节编址磁盘阵列 计算机硬件 分级存储体系 寄存器组&#xff08;CPU&#xff09;Cache&#xff08;内存&#xff09;主存Flash&#xff08;外存/辅存&#xff09; 目的 解…

Qt基于等待条件QWaitCondition实现的任务队列模型示例

核心概念 Qt中的QWaitCondition是一个用于多线程同步的类&#xff0c;允许线程在某些条件满足时唤醒其他等待的线程。它通常与QMutex配合使用&#xff0c;协调线程之间的执行顺序&#xff0c;适用于生产者-消费者模型、任务队列调度等场景。 ​wait()&#xff1a;使当前线程进…

JAVA实战开源项目:安康旅游网站(Vue+SpringBoot) 附源码

本文项目编号 T 098 &#xff0c;文末自助获取源码 \color{red}{T098&#xff0c;文末自助获取源码} T098&#xff0c;文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…

《Qt动画编程实战:轻松实现头像旋转效果》

《Qt动画编程实战&#xff1a;轻松实现头像旋转效果》 Qt 提供了丰富的动画框架&#xff0c;可以轻松实现各种平滑的动画效果。其中&#xff0c;旋转动画是一种常见的 UI 交互方式&#xff0c;广泛应用于加载指示器、按钮动画、场景变换等。本篇文章将详细介绍如何使用 Qt 实现…

基于 MyBatis-Plus 的多租户数据隔离方案

​什么是多租户? 多租户技术(Multi-Tenancy)是一种软件架构设计,允许多个用户(通常为企业或组织)共享同一套系统或应用程序,同时确保各用户之间的数据隔离。这种技术广泛应用于 SaaS(软件即服务)平台,能够有效降低运维成本,提高资源利用率。 核心思想:在一台服务…

8 SpringBootWeb(下):登录效验、异步任务和多线程、SpringBoot中的事务管理@Transactional

文章目录 案例-登录认证1. 登录功能1.1 需求1.2 接口文档1.3 思路分析1.4 功能开发1.5 测试2. 登录校验2.1 问题分析2.2 会话技术2.2.1 会话技术介绍2.2.2 会话跟踪方案2.2.2.1 方案一 - Cookie2.2.2.2 方案二 - Session2.2.2.3 方案三 - 令牌技术2.2.3 JWT令牌(Token)2.2.3.…

mysql系列10—mysql锁

背景 mysql中锁机制核心是保证数据的一致性以及并发控制。锁机制的实现与存储引擎有关&#xff0c;本文介绍的是INNODB存储引擎的锁机制&#xff1b;其他存储引擎如myISAM和memory等仅支持表锁不支持行锁&#xff0c;不是本文关注的重点。 本文介绍mysql数据库提供的锁机制&am…

Redis7——基础篇(八)

前言&#xff1a;此篇文章系本人学习过程中记录下来的笔记&#xff0c;里面难免会有不少欠缺的地方&#xff0c;诚心期待大家多多给予指教。 基础篇&#xff1a; Redis&#xff08;一&#xff09;Redis&#xff08;二&#xff09;Redis&#xff08;三&#xff09;Redis&#x…

《国密算法开发实战:从合规落地到性能优化》

前言 随着信息技术的飞速发展,信息安全已成为全球关注的焦点。在数字化时代,数据的保密性、完整性和可用性直接关系到国家、企业和个人的利益。为了保障信息安全,密码技术作为核心支撑,发挥着至关重要的作用。国密算法,即国家密码算法,是我国自主设计和推广的一系列密码…

yolov12 部署瑞芯微 rk3588、RKNN 部署工程难度小、模型推理速度快

yolov12 部署又来了。 特别说明&#xff1a;如有侵权告知删除&#xff0c;谢谢。 完整代码&#xff1a;包括onnx转rknn和测试代码、rknn板端部署C代码&#xff1a; 【onnx转rknn和测试代码】 【rknn板端部署C代码】 1 模型训练 yolov12训练官方开源的已经非常详细了&#…

windows本地化部署Dify+Deepseek

Windows本地化部署DifyDeepseek 一、下载Docker 前往 Docker 官网 下载 Docker Desktop&#xff0c;按序安装。 1.1启用WSL 打开本机的控制面板>程序>启用或关闭 Windows 功能,勾选: Linux 的 Windows 子系统虚拟机平台&#xff08;若无该选择则勾选 Hyper-V &#…

使用Spring Boot与达梦数据库(DM)进行多数据源配置及MyBatis Plus集成

使用Spring Boot与达梦数据库(DM)进行多数据源配置及MyBatis Plus集成 在现代企业级应用开发中&#xff0c;处理多个数据源是一个常见的需求。本文将详细介绍如何使用Spring Boot结合达梦数据库&#xff08;DM&#xff09;&#xff0c;并通过MyBatis Plus来简化数据库操作&…

第二十四:5.2【搭建 pinia 环境】axios 异步调用数据

第一步安装&#xff1a;npm install pinia 第二步&#xff1a;操作src/main.ts 改变里面的值的信息&#xff1a; <div class"count"><h2>当前求和为&#xff1a;{{ sum }}</h2><select v-model.number"n">  // .number 这里是…

使用 DeepSeek 生成流程图、甘特图与思维导图:结合 Typora 和 XMind 的高效工作流

在现代工作与学习中&#xff0c;可视化工具如流程图、甘特图和思维导图能够极大地提升信息整理与表达的效率。本文将详细介绍如何使用 DeepSeek 生成 Mermaid 文本&#xff0c;结合 Typora 快速生成流程图和甘特图&#xff0c;并通过 Markdown 格式生成思维导图&#xff0c;最终…

DeepSeek 开源周:第五天 - Fire-Flyer 文件系统(3FS)

&#xff08;下面文字主要由 Grok 3 协助生成&#xff09; 概述 Deepseek 今天开源的 Fire-Flyer 文件系统&#xff08;3FS&#xff09;是一个高性能分布式文件系统&#xff0c;专门为 AI 训练和推理设计。研究表明&#xff0c;它解决了 AI 工作负载中处理海量数据的高效存储需…

【笔记】论文阅读方法(AI大模型)

1 为什么读论文 构建知识体系&#xff1a;通过Related Works快速了解该方向研究现状&#xff0c;追踪经典论文 紧跟前沿技术&#xff1a;了解领域内新技术及效果&#xff0c;快速借鉴到自身项目 培养科研逻辑&#xff1a;熟悉论文体系&#xff0c;了解如何创造新事物&#x…

【数据集】ACM数据集

ACM&#xff08;Association for Computing Machinery&#xff09;数据集是计算机科学领域常用于研究学术论文、作者关系、引文网络、推荐系统、图神经网络&#xff08;GNN&#xff09;等任务的数据集之一。该数据集通常包含学术论文、作者、研究领域以及它们之间的关系&#x…

SQL server配置ODBC数据源(本地和服务器)

本地配置 1. 控制面板中找到系统ODBC数据源&#xff08;打开控制面板直接搜&#xff09; 2. 选择“系统DSN”&#xff0c;点击“添加” 3. 选择“SQL server” 4. 名称和描述自己填&#xff0c;服务器选择本机设备名称 5. 选择ID和密码验证&#xff0c;并填写本地SQL server登…

使用 Postman 访问 Keycloak 端点

1. 引言 在本教程中&#xff0c;我们将首先快速回顾 OAuth 2.0、OpenID 和 Keycloak。然后&#xff0c;我们将了解 Keycloak REST API 以及如何在 Postman 中调用它们。 2. OAuth 2.0 OAuth 2.0 是一个授权框架&#xff0c;它允许经过身份验证的用户通过令牌向第三方授予访问…

文生图开源模型发展史(2014-2025年)

文生图开源模型的发展历程是一段充满技术革新、社区生态繁荣与商业化竞争的多维度演进史。 一、技术萌芽期&#xff08;2014-2020年&#xff09; 核心突破 2014年&#xff1a;GAN&#xff08;生成对抗网络&#xff09;诞生&#xff0c;首次实现数据驱动式图像生成&#xff0…