农妇山泉一亩田 发表于 2024-11-22 15:35:09

Linux CoreDump机制详解

目次
一、配景
二、coredump先容
2.1 什么是coredump
2.2 coredump作用
2.3 什么情况下触发coredump
三、如何利用coredump
3.1 方案1:设置core size和coredump文件路径方式使能coredump
3.1.1 使能步调
3.1.2 方案缺陷
3.2 方案2:命名管道方式使能coredump
3.2.1 使能步调
3.2.2 根本工作流程
3.2.3 内核设置用户空间辅助程序并执行
3.2.4 用户空间coredump辅助程序Demo   
四、coredump实现原理
4.1 根本原理
4.2 焦点代码段
4.3 代码时序
4.4 core文件格式及内容
五、Demo案例
六、风险及解决方案


一、配景

系统发生native crash时,针对内存非常访问、内存踩踏等疑难题目,由于tombstone信息量不足无法正确定位分析这类题目。
二、coredump先容

2.1 什么是coredump

当用户程序运行过程中发生非常, 程序非常退出时, 由Linux内核把程序当前的内存状态信息(运行时的内存,寄存器状态,堆栈指针,各种函数调用堆栈信息等)存储在一个core文件中, 这个过程称作coredump.
2.2 coredump作用

coredump主要应用于解决NE题目(native exception)。用户进程发生native crash时,tombstone会抓取一些简单的backtrace信息,但是对于定位一些内存访问非常、内存被踩的疑难题目来说,tombstone信息量不富足导致无法正确定位分析题目,这个时候就必要利用到coredump分析这类题目。
2.3 什么情况下触发coredump

从进程发生非常范例维度来看,当native进程发生内存越界访问、堆栈溢出、非法指针等操作时,会触发coredump
从进程吸收的信号范例来看,当native进程吸收SIGQUIT、SIGABRT、SIGSEGV、SIGTRAP等信号时,会触发coredump
三、如何利用coredump

在Android平台中默认关闭coredump功能,必要手动或代码中去打开。当检测到进程非常退出时,会在指定的路径下生成core文件(格式为elf),可以联合gdb工具调试分析,详见第五章Demo案例。

使能coredump有两种方案,第一种是设置core size和coredump文件路径,别的一种是采用命名管道方式使能coredump.
3.1 方案1:设置core size和coredump文件路径方式使能coredump

3.1.1 使能步调

1)设置core size
可以用命令方式全局设置core size,如下:
1) 检查系统 coredump 是否开启
    ulimit -c// 返回 0,则未启用
2) 打开coredump   
   ulimit -c 1024 // 设置成 1024 byte   
   或者
   ulimit -c unlimited// 设置成无限大 也为单个进程设置core size,在代码端实现,如下:
void coreSetLimit(pid_t pid, uint64_t size)
    struct rlimit64 rlim64;
    rlim64.rlim_cur = size;
    rlim64.rlim_max = size;
    int ret = prlimit64(pid, RLIMIT_CORE, &rlim64, NULL);
} 2.设置coredump生成文件的路径
// 如果不设置文件路径,core文件生成的位置默认是可执行文件所在的位置
echo "/data/corefile/core-%e-%p-%t" > /proc/sys/kernel/core_pattern 3.1.2 方案缺陷

1)假如为每个进程设置core size,必要设置setrlimit selinux权限,由于系统中的进程数量很多,为每个进程设置selinux权限不太实际,且有些进程对setrlimit selinux权限是neverallow.
2)即使进程设置core size乐成,该进程必要对coredump文件路径(/data/xxx)设置相干的selinux权限和读写权限,每个进程都去设置这些权限不太实际,也容易遗漏,且有些进程对这部门的权限是neverallow.

方案2可以绕过selinux权限,解决以上题目。
3.2 方案2:命名管道方式使能coredump

3.2.1 使能步调

1)在内核设置CONFIG_STATIC_USERMODEHELPER_PATH属性
2)用户空间实现core辅助程序core_bin
3)用户空间设置
mkdir /data/xxx/coredump 0777 root root
    chmod 0777 data/xxx/coredump 0777
    restorecon data/xxx/coredump 0777
    write /proc/sys/kernel/core_pattern "|/system/bin/core_bin %e %p" 往/proc/sys/kernel/core_pattern节点挂载一个用户空间的辅助程序core_bin,linux coredump模块会启动该用户空间辅助程序,通过命名管道的方式将数据写入管道,core辅助进程从管道中读取coredump数据,存入data/xxx/coredump目次的core文件中。
3.2.2 根本工作流程

1)进程发生crash时,内核发送非常信号,在linux coredump中处置惩罚非常信号,创建管道,通过exec方式启动用户空间的辅助程序core_bin
2)收集coredump信息写入管道,用户空间的辅助程序core_bin从管道中读取数据,写入到指定的文件
3.2.3 内核设置用户空间辅助程序并执行



[*] do_coredump
do_coredump函数主要作用:假如用户空间采用的是管道方式,则设置管道并启动用户模式辅助进程,进行coredump数据转储。
// kernel/fs/coredump.c
void do_coredump(const kernel_siginfo_t *siginfo)
{
      struct core_state core_state;
      struct core_name cn;
      struct mm_struct *mm = current->mm;
      struct linux_binfmt * binfmt;
      const struct cred *old_cred;
      struct cred *cred;
      int retval = 0;
      int ispipe;
      size_t *argv = NULL;
      int argc = 0;
      /* require nonrelative corefile path and be extra careful */
      bool need_suid_safe = false;
      bool core_dumped = false;
      static atomic_t core_dump_count = ATOMIC_INIT(0);
      struct coredump_params cprm = {
                .siginfo = siginfo,
                .regs = signal_pt_regs(),
                .limit = rlimit(RLIMIT_CORE),
                /*
               * We must use the same mm->flags while dumping core to avoid
               * inconsistency of bit flags, since this flag is not protected
               * by any locks.
               */
                .mm_flags = mm->flags,
                .vma_meta = NULL,
      };

      audit_core_dumps(siginfo->si_signo);

      binfmt = mm->binfmt;
      if (!binfmt || !binfmt->core_dump)
                goto fail;
      if (!__get_dumpable(cprm.mm_flags))
                goto fail;

      cred = prepare_creds();
      if (!cred)
                goto fail;
      /*
         * We cannot trust fsuid as being the "true" uid of the process
         * nor do we know its entire history. We only know it was tainted
         * so we dump it as root in mode 2, and only into a controlled
         * environment (pipe handler or fully qualified path).
         */
      if (__get_dumpable(cprm.mm_flags) == SUID_DUMP_ROOT) {
                /* Setuid core dump mode */
                cred->fsuid = GLOBAL_ROOT_UID;      /* Dump root private */
                need_suid_safe = true;
      }

      retval = coredump_wait(siginfo->si_signo, &core_state);
      if (retval < 0)
                goto fail_creds;

      old_cred = override_creds(cred);

      // 1. 判断是否采用管道转储
      ispipe = format_corename(&cn, &cprm, &argv, &argc);
      
      /* 2. 如果是管道转储,则设置管道并调用用户模式辅助进程;
      如果是文件转储,则打开文件并进行写入 */
      if (ispipe) {
                int argi;
                int dump_count;
                char **helper_argv;
                struct subprocess_info *sub_info;

                if (ispipe < 0) {
                        printk(KERN_WARNING "format_corename failed\n");
                        printk(KERN_WARNING "Aborting core\n");
                        goto fail_unlock;
                }

                if (cprm.limit == 1) {
                     
                        printk(KERN_WARNING
                              "Process %d(%s) has RLIMIT_CORE set to 1\n",
                              task_tgid_vnr(current), current->comm);
                        printk(KERN_WARNING "Aborting core\n");
                        goto fail_unlock;
                }
                cprm.limit = RLIM_INFINITY;

                dump_count = atomic_inc_return(&core_dump_count);
                if (core_pipe_limit && (core_pipe_limit < dump_count)) {
                        printk(KERN_WARNING "Pid %d(%s) over core_pipe_limit\n",
                               task_tgid_vnr(current), current->comm);
                        printk(KERN_WARNING "Skipping core dump\n");
                        goto fail_dropcount;
                }

                helper_argv = kmalloc_array(argc + 1, sizeof(*helper_argv),
                                          GFP_KERNEL);
                if (!helper_argv) {
                        printk(KERN_WARNING "%s failed to allocate memory\n",
                               __func__);
                        goto fail_dropcount;
                }
                for (argi = 0; argi < argc; argi++)
                        helper_argv = cn.corename + argv;
                helper_argv = NULL;

                retval = -ENOMEM;
                // 2.1 设置用户模式辅助程序
                sub_info = call_usermodehelper_setup(helper_argv,
                                                helper_argv, NULL, GFP_KERNEL,
                                                umh_pipe_setup, NULL, &cprm);
                // 2.2 内核执行用户辅助程序
                if (sub_info)
                        retval = call_usermodehelper_exec(sub_info,
                                                          UMH_WAIT_EXEC);
                kfree(helper_argv);
                if (retval) {
                        printk(KERN_INFO "Core dump to |%s pipe failed\n",
                               cn.corename);
                        goto close_fail;
                }
      } else {
            // 文件转储
         ....   
      }
      ...
      // 3. 查是否中断,如果没有中断,则写入核心转储数据
      if (!dump_interrupted()) {
                /*
               * umh disabled with CONFIG_STATIC_USERMODEHELPER_PATH="" would
               * have this set to NULL.
               */
                if (!cprm.file) {
                        pr_info("Core dump to |%s disabled\n", cn.corename);
                        goto close_fail;
                }
                if (!dump_vma_snapshot(&cprm))
                        goto close_fail;

                file_start_write(cprm.file);
                core_dumped = binfmt->core_dump(&cprm);
                /*
               * Ensures that file size is big enough to contain the current
               * file postion. This prevents gdb from complaining about
               * a truncated file if the last "write" to the file was
               * dump_skip.
               */
                if (cprm.to_skip) {
                        cprm.to_skip--;
                        dump_emit(&cprm, "", 1);
                }
                file_end_write(cprm.file);
                free_vma_snapshot(&cprm);
      }
      // 4. 进行清理工作,包括关闭文件、减少核心转储计数、释放内存、结束核心转储等
      if (ispipe && core_pipe_limit)
                wait_for_dump_helpers(cprm.file);
close_fail:
      if (cprm.file)
                filp_close(cprm.file, NULL);
fail_dropcount:
      if (ispipe)
                atomic_dec(&core_dump_count);
fail_unlock:
      kfree(argv);
      kfree(cn.corename);
      coredump_finish(core_dumped);
      revert_creds(old_cred);
fail_creds:
      put_cred(cred);
fail:
      return;
}

static void wait_for_dump_helpers(struct file *file)
{
      // 1. 获取管道的信息
      struct pipe_inode_info *pipe = file->private_data;
      // 2.锁定管道,以防止其他进程同时修改管道的状态
      pipe_lock(pipe);
      // 3. 增加管道的读者计数,并减少写者计数。这表明有一个新的读者(核心转储辅助进程)正在等待数据
      pipe->readers++;
      pipe->writers--;
      // 4. 唤醒所有在管道读等待队列上等待的进程
      wake_up_interruptible_sync(&pipe->rd_wait);
      // 5. 向所有注册了异步通知的读者发送 ​SIGIO​ 信号,通知它们有数据可读
      kill_fasync(&pipe->fasync_readers, SIGIO, POLL_IN);
      // 6. 解锁管道,允许其他进程访问管道
      pipe_unlock(pipe);

      /*
         * We actually want wait_event_freezable() but then we need
         * to clear TIF_SIGPENDING and improve dump_interrupted().
         */
         // 7. 当前进程进入可中断的等待状态,直到管道的读者计数等于1。这表明核心转储数据已经被读取完毕。
      wait_event_interruptible(pipe->rd_wait, pipe->readers == 1);

      // 8. 再次锁定管道,以进行后续的状态更新
      pipe_lock(pipe);
      // 9. 减少管道的读者计数,并增加写者计数。这表明读者已经完成了数据读取。
      pipe->readers--;
      pipe->writers++;
      // 10. 解锁管道,允许其他进程访问管道
      pipe_unlock(pipe);
}

// 设置管道
static int umh_pipe_setup(struct subprocess_info *info, struct cred *new)
{
      struct file *files;
      struct coredump_params *cp = (struct coredump_params *)info->data;
      // 1. 创建一个管道,并将管道的两个文件描述符存储在 files数组中
      int err = create_pipe_files(files, 0);
      if (err)
                return err;
      // 2. 管道的写端(​files​)设置为 ​cp->file​,以便后续的核心转储数据可以通过这个文件描述符写入
      cp->file = files;
      // 3. 将当前进程的标准输入(fd 0)替换为管道的读端(​files​)。
      // ​replace_fd​ 函数用于替换文件描述符,​fput​ 函数用于减少文件引用计数
      err = replace_fd(0, files, 0);
      fput(files);
      /* and disallow core files too */
      // 4. 设置当前进程的核心文件大小限制为1,用于防止递归核心转储
      current->signal->rlim = (struct rlimit){1, 1};

      return err;
}

[*] format_corename
format_corename函数作用:根据给定的模式字符串生成焦点转储文件的名称,并处置惩罚管道模式,代码如下:

static int format_corename(struct core_name *cn, struct coredump_params *cprm,
                           size_t **argv, int *argc)
{
      const struct cred *cred = current_cred();
      const char *pat_ptr = core_pattern;
      int ispipe = (*pat_ptr == '|');
      bool was_space = false;
      int pid_in_pattern = 0;
      int err = 0;

      cn->used = 0;
      cn->corename = NULL;
      if (expand_corename(cn, core_name_size))
                return -ENOMEM;
      cn->corename = '\0';
      // 1. 如果模式以管道符号开头,则分配内存用于存储命令行参数,并初始化参数数组
      if (ispipe) {
                int argvs = sizeof(core_pattern) / 2;
                (*argv) = kmalloc_array(argvs, sizeof(**argv), GFP_KERNEL);
                if (!(*argv))
                        return -ENOMEM;
                (*argv)[(*argc)++] = 0;
                ++pat_ptr;
                if (!(*pat_ptr))
                        return -ENOMEM;
      }

      /* Repeat as long as we have more pattern to process and more output
         space */
      while (*pat_ptr) {
                /*
               * Split on spaces before doing template expansion so that
               * %e and %E don't get split if they have spaces in them
               */
                if (ispipe) {
                        if (isspace(*pat_ptr)) {
                              if (cn->used != 0)
                                        was_space = true;
                              pat_ptr++;
                              continue;
                        } else if (was_space) {
                              was_space = false;
                              err = cn_printf(cn, "%c", '\0');
                              if (err)
                                        return err;
                              (*argv)[(*argc)++] = cn->used;
                        }
                }
                // 遍历模式字符串,根据不同的模式字符(如 ​%p​、​%u​、​%s​ 等)生成相应的文件名
                if (*pat_ptr != '%') {
                        err = cn_printf(cn, "%c", *pat_ptr++);
                } else {
                        switch (*++pat_ptr) {
                        /* single % at the end, drop that */
                        case 0:
                              goto out;
                        /* Double percent, output one percent */
                        case '%':
                              err = cn_printf(cn, "%c", '%');
                              break;
                        /* pid */
                        case 'p':
                              pid_in_pattern = 1;
                              err = cn_printf(cn, "%d",
                                              task_tgid_vnr(current));
                              break;
                        /* global pid */
                        case 'P':
                              err = cn_printf(cn, "%d",
                                              task_tgid_nr(current));
                              break;
                     ...                     
                        default:
                              break;
                        }
                        ++pat_ptr;
                }

                if (err)
                        return err;
      }
      ...
}


[*] call_usermodehelper_setup
call_usermodehelper_setup函数作用:内核设置一个用户空间辅助进程的执行环境
// kernel/kernel/umh.c
struct subprocess_info *call_usermodehelper_setup(const char *path, char **argv,
                char **envp, gfp_t gfp_mask,
                int (*init)(struct subprocess_info *info, struct cred *new),
                void (*cleanup)(struct subprocess_info *info),
                void *data)
{
      // 1. 分配内存用于存储 ​subprocess_info​ 结构体
      struct subprocess_info *sub_info;
      sub_info = kzalloc(sizeof(struct subprocess_info), gfp_mask);
      if (!sub_info)
                goto out;
      // 2. 初始化工作队列,用于执行用户空间的辅助进程
      INIT_WORK(&sub_info->work, call_usermodehelper_exec_work);

      // 3. 设置路径、参数、环境变量以及初始化和清理函数
#ifdef CONFIG_STATIC_USERMODEHELPER
      sub_info->path = CONFIG_STATIC_USERMODEHELPER_PATH;
#else
      sub_info->path = path;
#endif
      sub_info->argv = argv;
      sub_info->envp = envp;

      sub_info->cleanup = cleanup;
      sub_info->init = init;
      sub_info->data = data;
out:
      return sub_info;
}

[*] call_usermodehelper_exec
call_usermodehelper_exec函数作用:在内核空间中启动一个用户空间的进程,通常用于执行一些特定的任务,如core文件转储
int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait)
{
      // 1. 初始化了一些变量,并检查 ​sub_info->path​ 是否为空
      unsigned int state = TASK_UNINTERRUPTIBLE;
      DECLARE_COMPLETION_ONSTACK(done);
      int retval = 0;

      if (!sub_info->path) {
                call_usermodehelper_freeinfo(sub_info);
                return -EINVAL;
      }
      // 2. 对用户模式辅助进程进行加锁,并检查是否禁用了用户模式辅助进程
      helper_lock();
      if (usermodehelper_disabled) {
                retval = -EBUSY;
                goto out;
      }

      /*
         * If there is no binary for us to call, then just return and get out of
         * here.This allows us to set STATIC_USERMODEHELPER_PATH to "" and
         * disable all call_usermodehelper() calls.
         */
      if (strlen(sub_info->path) == 0)
                goto out;

      /*
         * Set the completion pointer only if there is a waiter.
         * This makes it possible to use umh_complete to free
         * the data structure in case of UMH_NO_WAIT.
         */
      sub_info->complete = (wait == UMH_NO_WAIT) ? NULL : &done;
      sub_info->wait = wait;
      // 3. 将work排队到系统未绑定工作队列中
      queue_work(system_unbound_wq, &sub_info->work);
      if (wait == UMH_NO_WAIT)      /* task has freed sub_info */
                goto unlock;

      if (wait & UMH_FREEZABLE)
                state |= TASK_FREEZABLE;

      if (wait & UMH_KILLABLE) {
                retval = wait_for_completion_state(&done, state | TASK_KILLABLE);
                if (!retval)
                        goto wait_done;

                /* umh_complete() will see NULL and free sub_info */
                if (xchg(&sub_info->complete, NULL))
                        goto unlock;

                /*
               * fallthrough; in case of -ERESTARTSYS now do uninterruptible
               * wait_for_completion_state(). Since umh_complete() shall call
               * complete() in a moment if xchg() above returned NULL, this
               * uninterruptible wait_for_completion_state() will not block
               * SIGKILL'ed processes for long.
               */
      }
      wait_for_completion_state(&done, state);

wait_done:
      retval = sub_info->retval;
out:
      call_usermodehelper_freeinfo(sub_info);
unlock:
      helper_unlock();
      return retval;
} 3.2.4 用户空间coredump辅助程序Demo   

int main(int argc, char *argv[])
{

    int result = snprintf(name, sizeof(name),
               "/data/xxx/coredump/core-%s-%s", argv, argv);

        ...

    fd = open(name, O_RDWR, 0777);

    while (numread = read(STDIN_FILENO, buf, BUF_SIZE))
    {
      if ((numread == -1) && (errno != EINTR)) {
            break;
      } else if (numread > 0) {
            ptr = buf;
            while (numwrite = write(fd, ptr, numread)) {
                if ((numwrite == -1) && (errno != EINTR)) break;
                else if (numwrite == numread) break;
                else if (numwrite > 0) {
                  ptr += numwrite;
                  numread -= numwrite;
                }
            }
            if (numwrite == -1) {
                break;
            }
      }
    }

    close(fd);
    return 0;
}
四、coredump实现原理

4.1 根本原理

用户程序发生某些错误或非常时,在Linux内核会捕获到非常,并给用户进程发送signal非常信号,进程在返回用户空间之前处置惩罚信号,调用Linux内核coredump,生成elf格式的core文件,生存到指定的路径。
https://i-blog.csdnimg.cn/direct/1e00119376254a11b4f251be28dde1bc.png
4.2 焦点代码段

调用 do_coredump 函数来生成 core文件。如下:
void do_coredump(const kernel_siginfo_t *siginfo)
{
      ......
      
      binfmt = mm->binfmt;
      if (!binfmt || !binfmt->core_dump)
                goto fail;
      if (!__get_dumpable(cprm.mm_flags))
                goto fail;
      ......

      // 1.生成core文件名称
      ispipe = format_corename(&cn, &cprm, &argv, &argc);

      ......
            
      // 2.创建core文件
      cprm.file = file_open_root(&root, cn.corename, open_flags, 0600);
      ......
      
      // 3.将进程的内存信息写入core文件
      core_dumped = binfmt->core_dump(&cprm);
       ......
} elf_core_dump 函数负责将进程的内存状态信息写入elf格式的core文件,以便后续的gdb调试和分析。如下:
// kernel_platform/msm-kernel/fs/binfmt_elf.c

static int elf_core_dump(struct coredump_params *cprm)
{
      ......

      /*
         * Collect all the non-memory information about the process for the
         * notes.This also sets up the file header.
         */
         // 1.函数填充 ELF 头部和 notes 信息
      if (!fill_note_info(&elf, e_phnum, &info, cprm))
                goto end_coredump;

      has_dumped = 1;
      // 2.计算 ELF 头部、程序头部和 notes 节的大小,并分配相应的内存
      offset += sizeof(elf);                              /* Elf header */
      offset += segs * sizeof(struct elf_phdr);      /* Program headers */

       ......

      /* Write program headers for segments dump */
      for (i = 0; i < cprm->vma_count; i++) {
                struct core_vma_metadata *meta = cprm->vma_meta + i;
                struct elf_phdr phdr;

                phdr.p_type = PT_LOAD;
                phdr.p_offset = offset;
                phdr.p_vaddr = meta->start;
                phdr.p_paddr = 0;
                phdr.p_filesz = meta->dump_size;
                phdr.p_memsz = meta->end - meta->start;
                offset += phdr.p_filesz;
                phdr.p_flags = 0;
                if (meta->flags & VM_READ)
                        phdr.p_flags |= PF_R;
                if (meta->flags & VM_WRITE)
                        phdr.p_flags |= PF_W;
                if (meta->flags & VM_EXEC)
                        phdr.p_flags |= PF_X;
                phdr.p_align = ELF_EXEC_PAGESIZE;

                if (!dump_emit(cprm, &phdr, sizeof(phdr)))
                        goto end_coredump;
      }
      // 3.写入 ELF 头部和程序头部
      if (!elf_core_write_extra_phdrs(cprm, offset))
                goto end_coredump;

         /* write out the notes section */
         // 4.写入 notes信息
      if (!write_note_info(&info, cprm))
                goto end_coredump;

      /* For cell spufs */
      // 5.写入数据段
      if (elf_coredump_extra_notes_write(cprm))
                goto end_coredump;

      /* Align to page */
      dump_skip_to(cprm, dataoff);

      for (i = 0; i < cprm->vma_count; i++) {
                struct core_vma_metadata *meta = cprm->vma_meta + i;

                if (!dump_user_range(cprm, meta->start, meta->dump_size))
                        goto end_coredump;
      }
      // 6.写入扩展编号
      if (!elf_core_write_extra_data(cprm))
                goto end_coredump;

      if (e_phnum == PN_XNUM) {
                if (!dump_emit(cprm, shdr4extnum, sizeof(*shdr4extnum)))
                        goto end_coredump;
      }

end_coredump:
      free_note_info(&info);
      kfree(shdr4extnum);
      kfree(phdr4note);
      return has_dumped;
} 4.3 代码时序

非常捕获、信号处置惩罚&生成core文件的功能逻辑的代码时序,如下:
https://i-blog.csdnimg.cn/direct/cff9d8dd671742a5b5f5c084fb89ce06.png
4.4 core文件格式及内容

coredump抓取的core文件为elf格式,可以利用gdb调试,定位分析题目。
core文件内容,如下:
ELF Header:
Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class:                           ELF64
Data:                              2's complement, little endian
Version:                           1 (current)
OS/ABI:                            UNIX - System V
ABI Version:                     0
Type:                              CORE (Core file)
Machine:                           AArch64
Version:                           0x1
Entry point address:               0x0
Start of program headers:          64 (bytes into file)
Start of section headers:          0 (bytes into file)
Flags:                           0x0
Size of this header:               64 (bytes)
Size of program headers:         56 (bytes)
Number of program headers:         138
Size of section headers:         0 (bytes)
Number of section headers:         0
Section header string table index: 0

Program Headers:
Type         Offset             VirtAddr         PhysAddr
               FileSiz            MemSiz            FlagsAlign
NOTE         0x0000000000001e70 0x0000000000000000 0x0000000000000000
               0x00000000000018a8 0x0000000000000000         0x0
LOAD         0x0000000000004000 0x000000560ca89000 0x0000000000000000
               0x0000000000000000 0x0000000000002000R      0x1000
LOAD         0x0000000000004000 0x000000560ca8b000 0x0000000000000000
               0x0000000000000000 0x0000000000003000R E    0x1000
LOAD         0x0000000000004000 0x000000560ca8e000 0x0000000000000000
               0x0000000000001000 0x0000000000001000R      0x1000
...


Displaying notes found at file offset 0x00001e70 with length 0x000018a8:
Owner                Data size         Description
CORE               0x00000188      NT_PRSTATUS (prstatus structure)
CORE               0x00000088      NT_PRPSINFO (prpsinfo structure)
CORE               0x00000080      NT_SIGINFO (siginfo_t data)
CORE               0x00000150      NT_AUXV (auxiliary vector)
CORE               0x00000f6e      NT_FILE (mapped files)
    Page size: 4096
               Start               End         Page Offset
    0x000000560ca890000x000000560ca8b0000x0000000000000000
      /system/bin/coredump-test-bin
    0x000000560ca8b0000x000000560ca8e0000x0000000000000002
      /system/bin/coredump-test-bin
...
CORE               0x00000210      NT_FPREGSET (floating point registers)
LINUX                0x00000010      NT_ARM_TLS (AArch TLS registers)
   description data: 00 10 e4 45 7e 00 00 00 00 00 00 00 00 00 00 00
LINUX                0x00000108      NT_ARM_HW_BREAK (AArch hardware breakpoint registers)
   description data: 06 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
LINUX                0x00000108      NT_ARM_HW_WATCH (AArch hardware watchpoint registers)
   description data: 04 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
LINUX                0x00000004      Unknown note type: (0x00000404)
   description data: ff ff ff ff
LINUX                0x00000010      Unknown note type: (0x00000406)
   description data: 00 00 00 00 80 ff 7f 00 00 00 00 00 80 ff 7f 00
LINUX                0x00000008      Unknown note type: (0x0000040a)
   description data: 0f 00 00 00 00 00 00 00
LINUX                0x00000008      Unknown note type: (0x00000409)
   description data: 01 00 00 00 00 00 00 00 core文件内容主要包括ELF Header、Program Headers、NOTE segment.
ELF Header:用于记录core文件的根本信息和布局。
Program Headers: 记录内存中映射文件的信息,以及segment的权限和属性。
NOTE segment:记录进程瓦解时刻的进程状态、寄存器、信号信息、辅助向量和映射文件的具体信息。通过这些信息,gdb调试工具可以重建瓦解时的内存布局,分析瓦解原因,并帮助开辟者正确定位分析题目。
五、Demo案例

1)Demo程序
进程发生非常crash后,抓取tombstone和core文件。
2)生成的tombstone文件
从抓取的tombstone文件分析,只能看出大抵的原因,无法正确定位到根本原因或哪句代码出错导致进程crash.因此,必要借助coredump,抓取core文件来正确定位分析这类题目。
Cmdline: ../../system/bin/coredump-test-bin use-after-free
pid: 11966, tid: 11966, name: coredump-test-b>>> ../../system/bin/coredump-test-bin <<<
uid: 0
...
backtrace:
      #01 pc 0000000000090088/system/lib64/libc.so (__vfprintf+10416) (BuildId: 567e41669f1cb528e72fe319cd09033b)
      #02 pc 00000000000ac06c/system/lib64/libc.so (vsnprintf+192) (BuildId: 567e41669f1cb528e72fe319cd09033b)
      #03 pc 0000000000006afc/system/lib64/liblog.so (__android_log_print+184) (BuildId: 87ba6a9314f00fab650fb8fad7913d58)
      #04 pc 00000000000010a4/system/bin/coredump-test-bin (main+80) (BuildId: c97bade065c198c12dcca74f107c513c)
      #05 pc 0000000000048768/system/lib64/libc.so (__libc_init+96) (BuildId: 567e41669f1cb
... 3)生成的core文件
打开coredump功能,抓取core文件。core文件为elf格式,可以用gdb调试。

用gdb调试Demo程序和生成的core文件,执行gdb ./coredump-test-bin ./core-coredump-test-bin-11966-1720526041命令,可以正确定位到是源文件哪一行代码出错,如下:
--->            
...            
    Program terminated with signal SIGSEGV,       Segmentation fault.            
    #00x000000000040053c in square (a=1, b=2) at test.c:7            
    7               *p = 666;# 可见在test.c中的第7行,出现了问题。
# (gdb) backtrace // 输入backtrace   
   --->            
    #00x000000000040053c in square (a=1, b=2) at test.c:7   // 可见在test.c中的第7行,出现了问题。            
    #10x0000000000400564 in doCalc (num1=1, num2=2) at test.c:14            
    #20x0000000000400591 in main () at test.c:22 六、风险及解决方案

打开coredump功能,存在以下风险:
1)若系统中存在native进程反复crash自启,尤其在研发阶段这种征象很普遍,会导致持续不断产生core文件,磁盘空间很快被占满。
解决方案:联合quota机制,core文件路径存储空间分配project_id,设置quota阈值(存储空间上限),凌驾阈值就自动覆盖老的文件

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Linux CoreDump机制详解