記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.pswp.cn/web/71075.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/71075.shtml
英文地址,請注明出處:http://en.pswp.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…