NE 源码流程集锦(MTK Android R)

2023-10-15 07:10
文章标签 android 源码 流程 mtk 集锦 ne

本文主要是介绍NE 源码流程集锦(MTK Android R),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

深入分析Android native exception框架

MTK NE异常流程图

Ptrace

模块组成

Tombstoned

libdebuggerd_handler

crash_dump

进程关系

Pipe 管道

进程通讯关系

debuggerd

Android P上Java Crash、Native Crash的异常处理流程学习

Linux 信号


深入分析Android native exception框架

参考MTK网站

https://online.mediatek.com/QuickStart/fc6dd989-890d-444d-b4bf-13a93219702d

参考博客

https://blog.csdn.net/hl09083253cy/article/details/78927104?utm_source=blogxgwz0

https://www.jianshu.com/p/110ea9bd2e3f

https://www2.lauterbach.com/pdf/rtos_linux_stop.pdf

MTK NE异常流程图(Q 之前)

异常发送后,会执行到Arm_notify_die ,用户模式则执行force_sig_info 发送对应信号给用户进程,svc 模式就直接die(),重启手机。

Ptrace

https://www.cnblogs.com/tangr206/articles/3094358.html

模块组成

NE常见类型

如空指针,非法指针,程序跑飞,内存踩坏,段地址错误等

这类问题会被MMU 捕获,MMU 就会发送abort 信号给到cpu,这个时候cpu 根据信号类型执行对应的向量表函数,同时也由用户态切换到内核态,内存处理完会调用__send_signal()发送信号,debuggerd_init()里注册的函数debugger_signal_handler()会接收到信号

一般native 应用都会动态链接一些库如libc.so/libutils.so,这些库动态加载是通过linker 完成的。Kernel 将native 应用、linker 加载到应用进程空间,先跑linker,再跑应用。

linker执行期间还做了一件事:注册信号,具体的函数调用流程如下:
__linker_init() -> __linker_init_post_relocation() -> debuggerd_init()

system\core\debuggerd\tombstoned

Tombstoned :

        "tombstoned/intercept_manager.cpp",

        "tombstoned/tombstoned.cpp"

         libdebuggerd、libevent

        "tombstoned/tombstoned.rc

Debuggerd:

        Debuggerd.cpp

        libdebuggerd_client

crash_dump

       crash_dump.cpp

       libtombstoned_client

libdebuggerd_client

       client/debuggerd_client.cpp

libdebuggerd_handler (need by linker)

       handler/debuggerd_handler.cpp

libtombstoned_client

       tombstoned/tombstoned_client.cpp

libdebuggerd

        "libdebuggerd/backtrace.cpp",

        "libdebuggerd/gwp_asan.cpp",

        "libdebuggerd/open_files_list.cpp",

        "libdebuggerd/tombstone.cpp",

tombstoned

Tombstoned :

        "tombstoned/intercept_manager.cpp",

        "tombstoned/tombstoned.cpp"

         libdebuggerd、libevent

        "tombstoned/tombstoned.rc

bionic/libc/platform/bionic/reserved_signals.h

#define BIONIC_SIGNAL_DEBUGGER (__SIGRTMIN + 3)

Tombstoned

tombstoned.cpp

注册tombstone 自己的信号处理函数

tombstone 建立监听回调处理函数,当客户端发起connect ,socket accept 就会触发对应事件,执行回调。intercept_socket 用于debuggerd 向 tombstoned 请求输出 tombstone,其中backtrace 通过debuggerd 输出。

intercept_socket  与crash_socket  交互

debuggerd -b  pid      //native backtrace   

intercept_socket  与java_socket  交互

debuggerd -j pid       //java backtrace

crash_socket 用于发生NE 时,内核发送信号,程序收到信息进入其注册的异常处理信号函数,在这里面最后会通过crash_socket连接到tombstoned 打印tombstone 

这里tombstoned 自己发生异常,则直接执行_exit(1)

Libevent之evconnlistener

https://blog.csdn.net/u010710458/article/details/80067676

https://blog.csdn.net/bestone0213/article/details/46729247

bind()将端口跟socket 关联起来,listen()则成为服务端进入监听,accept()则当客户端有connect 则响应。

accept()接到连接则执行客户端注册的回调。

第一个参数是event_base,也就底层在监听套接字上有新的 TCP 连接

第二个参数是accept()时执行回调,第三个参数是传递给回调的参数

回调函数中,event_new 创建一个新的event加入监听

第三个参数是事件触发类型

第四个参数是事件触发回调函数

第二个参数、第三个参数、第四个参数都是传递给crash_request_cb 的

从socket 中读取请求数据,判断dump 类型及pid

  1. 判读如果是java dump 就,通过for_anrs 创建CrashQuere,其它类型通过for_tombstone创建CrashQueue,这里指定了日志保存目录,最大日志数,及最大并行处理数。
  2. anr 保存/data/anr ,最大日志64,最大并行4,tombstone 保存/data/tombstone,最大日志32,最大并行1。
  3. 如果当前正在处理dump 达到最大并行数,则将当前请求加入CrashQueue队尾,否则执行dump。
  4. 执行dump

libdebuggerd_handler

    handler/debuggerd_handler.cpp

bionic/linker/linker_main.cpp

int sigaction(int signum, const struct sigaction *act,

struct sigaction *oldact);

signum参数指出要捕获的信号类型,act参数指定新的信号处理方式,oldact参数输出先前信号的处理方式(如果不为NULL的话)。

struct sigaction结构体介绍


struct sigaction {

void (*sa_handler)(int);//信号处理函数

void (*sa_sigaction)(int, siginfo_t *, void *);

sigset_t sa_mask;

int sa_flags;

void (*sa_restorer)(void);

}

sa_handler 是一个函数指针,其含义与 signal 函数中的信号处理函数类似。或者设置为SIG_IGN忽略信号。

sa_sigaction 则是另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。

sa_flags 成员的值包含了 SA_SIGINFO 标志时,系统将使用 sa_sigaction 函数作为信号处理函数,否则使用 sa_handler 作为信号处理函数。在某些系统中,成员 sa_handler 与 sa_sigaction 被放在联合体中,因此使用时不要同时设置。
sa_mask 成员用来指定在信号处理函数执行期间需要被屏蔽的信号,特别是当某个信号被处理时,它自身会被自动放入进程的信号掩码,因此在信号处理函数执行期间这个信号不会再度发生。

sa_flags中包含了许多标志位,一个比较重要的标志位是SA_SIGINFO,当设定了该标志位时,表示信号附带的参数可以被传递到信号处理函数中,因此,应该为sigaction结构中的sa_sigaction指定处理函数,而不应该为sa_handler指定信号处理函数,否则,设置该标志变得毫无意义。即使为sa_sigaction指定了信号处理函数,如果不设置SA_SIGINFO,信号处理函数同样不能得到信号传递过来的数据,在信号处理函数中对这些信息的访问都将导致段错误(Segmentation fault)

https://blog.csdn.net/q1007729991/article/details/53893743

Ptrace

https://www.cnblogs.com/tangr206/articles/3094358.html

debuggerd_signal_handler

创建子线程

clone返回创建进程的进程ID,出错的话返回-1;

CLONE_PARENT   创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”

 CLONE_FS           子进程与父进程共享相同的文件系统,包括root、当前目录、umask

 CLONE_FILES      子进程与父进程共享相同的文件描述符(file descriptor)表

 CLONE_NEWNS   在新的namespace启动子进程,namespace描述了进程的文件hierarchy

 CLONE_SIGHAND   子进程与父进程共享相同的信号处理(signal handler)表

 CLONE_PTRACE   若父进程被trace,子进程也被trace

 CLONE_VFORK     父进程被挂起,直至子进程释放虚拟内存资源

 CLONE_VM           子进程与父进程运行于相同的内存空间

 CLONE_PID          子进程在创建时PID与父进程一致

 CLONE_THREAD    Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群

CLONE_THREAD :将子进程加入父进程线程组,否则设置新的线程组

CLONE_SIGHAND:信号处理函数跟父进程一样

如果设置了CLONE_PARENT_SETTID,内核会将子进程的线程ID写入ptid所指向的位置。如果设置了CLONE_CHILD_SETTID,那么clone()会将子线程的线程ID写入指针ctid所指向的位置。如果设置了CLONE_CHILD_CLEARTID,则会在子进程终止时将ctid所指向的内存清零。

等待子线程开始和结束

debuggerd_dispatch_pseudothread

int dup(int oldfd);等效fcntl(oldfd, F_DUPFD, 0)

Dup 用于复制oldfd 所执行的文件描述符,若成功则返回尚未使用的最小的文件描述符。新文件描述符跟oldfd 指向同一文件;

int dup2(int oldfd, int newfd);等效close(oldfd);fcntl(oldfd, F_DUPFD, newfd);

使用newfd 文件描述符指定oldfd ;若newfd 已经存在,则关闭newfd 指向文件;若newfd 跟oldfd 相等,则返回newfd,不关闭newfd 指向文件

创建读写管道

子线程中设置输出输入文件描述符;

main_tid 是发生crash 的进程,pseudothread_tid是clone 创建的线程,通过exccle 执行crash_dump。

AEE产生流程图:

crash_dump

      crash_dump.cpp

       libtombstoned_client

将信号处理函数设置为默认,信号屏蔽掩码设置为空(这样阻塞时不会丢弃信号),设置sigpipe 处理函数

让进程摆脱原会话的控制

让进程摆脱原进程组的控制

让进程摆脱原控制终端的控制

setsid函数的进程成为新的会话的领头进程

创建子线程,父进程通过pipe 读子进程,进入等待;

解析execle 传递的参数,g_target_thread 是发生异常进程,pseudothread_tid是clone 出来的线程。

获取进程名,/proc/pid/cmdline,线程名,/proc/pid/comm,/proc/self/comm

获取进程的文件句柄,/proc/pid/fd 下文件句柄

获取进程组中的线程

ptrace()系统调用函数提供了一个进程(the “tracer”)监察和控制另一个进程(the “tracee”)的方法,并且可以检查和改变“tracee”进程的内存和寄存器里的数据,它可以用来实现断点调试和系统调用跟踪。

其基本原理是: 当使用了ptrace跟踪后,所有发送给被跟踪的子进程的信号(除了SIGKILL),都会被转发给父进程,而子进程则会被阻塞,这时子进程的状态就会被系统标注为TASK_TRACED,而父进程通过waitpid(wstatus)(或者其它wait系统调用)被通知收到信号后,就可以对停止下来的子进程进行检查和修改,然后让子进程继续运行。当被跟踪后,每当系统调用信号量传来,甚至信号量会被忽略时,tracee会暂停,被跟踪的程序在进入或者退出某次系统调用的时候都会触发一个SIGTRAP信号。

PTRACE_O_TRACECLONE:被跟踪进程在下一次调用clone()时将其停止,并自动跟踪新产生的进程。这样wait_for_vm_process 可以通过PTRACE_GETEVENTMSG获取clone 产生的新进程。

新产生的进程开始执行时就已设置SIGSTOP信号,新产生的进程刚执行就收到SIGSTOP信号,wait_for_vm_process 会等待新的进程停止信号SIGSTOP,检测是否是SIGSTOP信号,并ptrace(PTRACE_CONT, child, 0, 0)让clone 的子进程继续运行;

这时pseudothread_tid进程调用create_vm_process,会调用clone 创建子进程,被跟踪,子进程执行就发送SIGSTOP停止,这时wait_for_clone 获取到子进程pid,并让子进程继续执行,子进程执行时会再次调用clone 创建孙进程,从而获取到孙进程pid,重复上面流程。

crash_dump 通过socket connect 到tombstoned_client,tombstone_client 与socket 服务端tombstoned 通讯。

这里g_output_fd 就是这次socket 请求端(crash_dump 中fork 的子进程)发送数据文件句柄,engrave_tombstone 将dump的信息通过g_output_fd 发送给tombstoned。

连接tombstoned后,crash_dump端会通过g_output_fd将要写入的日志内容发送给tombstoned,tombstoned最终存在文件中。

这里tombstone_path 是/proc/%d/task/%d/fd/%d 文件句柄对应的链接,即g_output_fd文件句柄对应链接,这里通知aee_aed。aee_aed 就是libaed.so 中crash_mini_dump_notify 方法来获取mini dump信息。

aee_aed每次会创建同名子进程,子进程创建aee_dumpstate。

aee_aed 抓取相应日志与打包,aee_dumpstate获取/proc/$pid 下文件。

根据/proc/sys/kernel/core_pattern  启动aee_core_forwarder 获取coredump,跟aee_aed 一起打包为db.

init->aee_aed64->【(aee_aed64与父进程同名->aee_dumpstate)】

init->aee_aedv64->aee_aedv64->aee_dumpstatev

2->aee_core_forwarder   coredump

通知system_server。

进程关系

  • 在debuggerd_signal_handler 中是发生crash 的线程,gettid 为进程id,getpid 是组进程id,不同,是父子关系。

1、debuggerd_signal_handler 中clone进程pseudothread

clone(debuggerd_dispatch_pseudothread, pseudothread_stack,

          CLONE_THREAD | CLONE_SIGHAND | CLONE_VM | CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID,

getpid 与debuggerd_signal_handler 中一样,gettid 不同于进程debuggerd_signal_handler 中;但是打印的log,pid:tid 跟发生问题debuggerd_signal_handler 中一样。

2、create_vm_process 中

clone(nullptr, nullptr, CLONE_FILES, nullptr)

  • pseudothread中__fork()进程,在子进程中执行crash_dump

execle(CRASH_DUMP_PATH

在父进程中(__fork()进程)getid跟getpid一样,getppid跟debuggerd_signal_handler 中getpid 一样,在crash_dump中跟父进程中(__fork()进程)一样

  • crash_dump 调用setSid 前

调用setSid后gettid、getpid、getppid 一样

fork 创建子进程,在子进程中 gettid跟getpid 一样,getppid 为crash_dump

  • debuggerd_signal_handler 中create_vm_process与crash_dump 通过fork 创建的子进程中wait_for_vm_process(pseudothread_tid)

Pipe 管道

管道也是unix ipc的最老形式,管道有两种限制

数据自己读不能自己写

它们是半双工的。数据只能在一个方向上流动。

数据一旦被读走,便不在管道中存在,不可反复读取

它们只能在具有公共祖先的进程之间使用。通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

管道由pipe函数创建而成pipe(pipe_fd)经由参数pipe_fd返回两个文件描述符,pipe_fd[0]为读而打开,pipe_fd[1]为写而打开。pipe_fd[1]的输出是pipe_fd[0]的输入。

函数调用成功返回r/w两个文件描述符。无需open,但需手动close。规定:fd[0] → r; fd[1] → w,就像0对应标准输入,1对应标准输出一样。向管道文件读写数据其实是在读写内核缓冲区。管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。

父进程写,子进程读流程:

父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。

父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。

父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。

管道读写4中情况

如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。

如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。

如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。具体方法信号章节详细介绍。

如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。

下一篇

https://blog.csdn.net/lei7143/article/details/118244656

这篇关于NE 源码流程集锦(MTK Android R)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/216177

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动