coredump介绍

coredump是什么?

coredump一般我们说是核心转储,就是在进程异常时的一个快照,保存了异常时的内存、寄存器、堆栈等数据。这些数据存储成一个文件,而且是一个ELF文件格式,可通过readelf读取查看。

为何需要coredump?

在android系统上,一般程序在native或者art中异常后会在data/tombstones下生成对应的tombstone文件,这个文件一般已经包含了很多native程序的重要信息,结合symbol,我们会对异常原因有个初步判断,而且一般的简单问题可以有个初步分析结论。但对于较难的问题,仅有tombstone不足以判断异常原因,尤其是需要查看一些较多分支,对象地址数据没有在tombstone中输出时,我们就需要更多的寄存器和内存信息。这就需要抓起coredump来分析。

如何抓取coredump?

1,设置要抓取进程的rlimit

#define RLIM_INFINITY 0x7fffffffUL 
......
221        rl.rlim_cur = RLIM_INFINITY;
222        rl.rlim_max = RLIM_INFINITY;
223        if (setrlimit(RLIMIT_CORE, &rl) < 0) {
           //异常打印
}

上面即设置core文件大小不限。

在android中,不同平台调用时机有区别,以某平台调用时机为例,在zygote fork进程时执行,其时序如下:

//frameworks/base/core/jni/com_android_internal_os_Zygote.cpp
487static pid_t ForkAndSpecializeCommon(JNIEnv* env, uid_t uid, gid_t gid, jintArray javaGids,
488                                     jint debug_flags, jobjectArray javaRlimits,
489                                     jlong permittedCapabilities, jlong effectiveCapabilities,
490                                     jint mount_external,
491                                     jstring java_se_info, jstring java_se_name,
492                                     bool is_system_server, jintArray fdsToClose,
493                                     jintArray fdsToIgnore,
494                                     jstring instructionSet, jstring dataDir) {
......
529  pid_t pid = fork();
531  if (pid == 0) {
......
      //这里执行java方法
669    env->CallStaticVoidMethod(gZygoteClass, gCallPostForkChildHooks, debug_flags,
670                              is_system_server, instructionSet);
671    if (env->ExceptionCheck()) {
672      RuntimeAbort(env, __LINE__, "Error calling post fork hooks.");
673    }
674  } else if (pid > 0) {
.....
}

// gCallPostForkChildHooks对应的Java方法如下
832int register_com_android_internal_os_Zygote(JNIEnv* env) {
833  gZygoteClass = MakeGlobalRefOrDie(env, FindClassOrDie(env, kZygoteClassName));
834  gCallPostForkChildHooks = GetStaticMethodIDOrDie(env, gZygoteClass, "callPostForkChildHooks",
835                                                   "(IZLjava/lang/String;)V");


callPostForkChildHooks

    postForkChild(×××)

        nativePostForkChild(); //Jni

            ZygoteHooks_nativePostForkChild(); //native方法

                EnableDebugFeatures

                    EnableDebugger()

                         setrlimit()

2,设置coredump存储路径和core文件名称

mkdir /data/core  0777  root  root

write /proc/sys/kernel/core_pattern  "data/core/%E.%p.%e"

即创建一个放置core的路径,且具有读写权限,然后设置了文件的名称,若不设置,则名称为core,可以用cat 查看,路径在可执行程序路径下。

对应某q公司,则设置了一个属性,persist.debug.trace,当其为1时,出发init执行上述两条命令:

device/q**/common/rootdir/etc/init.***.rc

on property:persist.debug.trace=1

    mkdir /data/core  0777  root  root

    write /proc/sys/kernel/core_pattern  "data/core/%E.%p.%e"

关于core文件名称的格式意义如下:

%p 出Core进程的PID

%u 出Core进程的UID

%s 造成Core的signal号

%t 出Core的时间,从1970-01-0100:00:00开始的秒数

%e 出core进程对应的可执行文件名

 

3,然后等待触发native crash

 

补充:

对于抓取fulldump话,需要增加一个步骤:adb shell echo 0x27 > /proc/pid/coredump_filter,进程重启需要重新执行

1278    snprintf(path,sizeof(path),"/proc/%d/coredump_filter",pid);
......
1283        if (is_full) {
1284            write(fd, "39", 2);    /*0x27 
1285        } else {
1286            write(fd, "35", 2);    /*0x23 
1287        }

这一步如果动态或者监控的话,可以通过hook执行。以某android厂商为例,hook了android的debuggerd进程的syscall,然后在发现进程正要处理的是SYS_tgkill信号时,就增加一个方法调用,之后在执行正常的syscall。

160extern "C" long hook_syscall(long number, ...) {
......
165    if (SYS_tgkill == number) {
166        int pid = va_arg(ap,int);
167        int tid = va_arg(ap,int);
168        int signal = va_arg(ap,int);
169        ****_native_debug_process(pid,tid);
170        ret = syscall(SYS_tgkill, pid, tid, signal);


//先在context初始化时hook了pthread_setname_np,在hook_pthread_setname_np执行时设置了syscall的hook函数hook_syscall

180extern "C" int hook_pthread_setname_np(pthread_t thread, const char* name) {
181    MILOGI("hook hook_pthread_setname_np name=%s", name);
182#if defined(__LP64__)
183  const char* sigsendersname = "debuggerd64:sig";
184#else
185  const char* sigsendersname = "debuggerd:sig";
186#endif
187    if (!strcmp(name,sigsendersname)) {
188        SoInfo si;
189        if (si.replace("syscall",(const uintptr_t)&hook_syscall)) {

android上有可能存在selinux的权限问题,可以将se关闭(adb shell setenforce 0),重启后需要重新关闭se。

对于已经启动的进程,rlimit不好更改,目前只能gdb attach到目标进程更改。

 

如何使用coredump?

coredump一般通过gdb调试,同时需要准备对应版本的symbol文件一起调试。

1,启动gdb

./prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8/bin/arm-linux-androideabi-gdb(gdb路径)

或者prebuilts/gdb/linux-x86/bin/gdb

2,加载coredump

core-file    coredump_file(coredump文件) 

 file   

3. 设置symbol路径,即当前栈中用的可执行文件的symbol位置

file ~/symbols/out/target/product/项目名称/symbols/system/bin/app_process32 (一般app的话,是app_process,注意32位和64位的区别)

set solib-search-path   symbols/system/lib/

4. 然后通过gdb命令查看,几个常用命令如下:

bt:查看对应当前线程栈
set arm force-mode thumb:汇编按照thumb命令解析
info r:查看当前帧寄存器
info thread:列出进程的所有线程
t num:切换到num线程
f num:切换到num帧
disassemble 0x9d73320c-0x30,+0x32:反汇编一段地址
x /10x 0x9d7331fc:按16进制,读取10个指定地址内存
p *(android::JavaBBinder*)0x8bb5c500:把指定地址按照指定数据类型解析数据;
set print pretty on:设置良好的阅读模式
directory ~/disk/androidO/:指定源码路径

其他命令不再列出

如何查看是否已开启coredump?

:/ # ulimit -a
-t: time(cpu-seconds)     unlimited
-f: file(blocks)          unlimited
-c: coredump(blocks)      0     //coredump大小为0,即关闭,可以通过ulimit -c size或者ulimit -c unlimited来指定size大小或者不限制大小
-d: data(KiB)             unlimited
-s: stack(KiB)            8192
-l: lockedmem(KiB)        65536
-n: nofiles(descriptors)  32768
-p: processes             21499
-i: sigpending            21499
-q: msgqueue(bytes)       819200
-e: maxnice               40
-r: maxrtprio             0
-m: resident-set(KiB)     unlimited
-v: address-space(KiB)    unlimited

 

补充一篇不错的类似文章:https://blog.csdn.net/tenfyguo/article/details/8159176

你可能感兴趣的:(coredump介绍)