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网站

Mediatek Account | Login

参考博客

Android 抓取 core dump_android cocedump-CSDN博客

tombstone与debuggerd相关流程 - 简书

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()

android p crash 抓堆栈流程 - 简书

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

Libevent之evconnlistener详解-CSDN博客

连接监听器evconnlistener_evconnlistener_free-CSDN博客

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)

42-带参数的信号_sigaction接收带参数的信号-CSDN博客

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会阻塞,直到管道中有空位置了才写入数据并返回。

下一篇

NE 源码流程集锦(MTK Android R 二)_mtk的ne 文件-CSDN博客

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值