Linux CoreDump机制详解

打印 上一主题 下一主题

主题 803|帖子 803|积分 2409

目次
一、配景
二、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. 1) 检查系统 coredump 是否开启
  2.     ulimit -c  // 返回 0,则未启用
  3. 2) 打开coredump   
  4.    ulimit -c 1024 // 设置成 1024 byte   
  5.    或者
  6.    ulimit -c unlimited  // 设置成无限大
复制代码
也为单个进程设置core size,在代码端实现,如下:
  1. void coreSetLimit(pid_t pid, uint64_t size)
  2.     struct rlimit64 rlim64;
  3.     rlim64.rlim_cur = size;
  4.     rlim64.rlim_max = size;
  5.     int ret = prlimit64(pid, RLIMIT_CORE, &rlim64, NULL);
  6. }
复制代码
2.设置coredump生成文件的路径
  1. // 如果不设置文件路径,core文件生成的位置默认是可执行文件所在的位置
  2. 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)用户空间设置
  1. mkdir /data/xxx/coredump 0777 root root
  2.     chmod 0777 data/xxx/coredump 0777
  3.     restorecon data/xxx/coredump 0777
  4.     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数据转储。
  1. // kernel/fs/coredump.c
  2. void do_coredump(const kernel_siginfo_t *siginfo)
  3. {
  4.         struct core_state core_state;
  5.         struct core_name cn;
  6.         struct mm_struct *mm = current->mm;
  7.         struct linux_binfmt * binfmt;
  8.         const struct cred *old_cred;
  9.         struct cred *cred;
  10.         int retval = 0;
  11.         int ispipe;
  12.         size_t *argv = NULL;
  13.         int argc = 0;
  14.         /* require nonrelative corefile path and be extra careful */
  15.         bool need_suid_safe = false;
  16.         bool core_dumped = false;
  17.         static atomic_t core_dump_count = ATOMIC_INIT(0);
  18.         struct coredump_params cprm = {
  19.                 .siginfo = siginfo,
  20.                 .regs = signal_pt_regs(),
  21.                 .limit = rlimit(RLIMIT_CORE),
  22.                 /*
  23.                  * We must use the same mm->flags while dumping core to avoid
  24.                  * inconsistency of bit flags, since this flag is not protected
  25.                  * by any locks.
  26.                  */
  27.                 .mm_flags = mm->flags,
  28.                 .vma_meta = NULL,
  29.         };
  30.         audit_core_dumps(siginfo->si_signo);
  31.         binfmt = mm->binfmt;
  32.         if (!binfmt || !binfmt->core_dump)
  33.                 goto fail;
  34.         if (!__get_dumpable(cprm.mm_flags))
  35.                 goto fail;
  36.         cred = prepare_creds();
  37.         if (!cred)
  38.                 goto fail;
  39.         /*
  40.          * We cannot trust fsuid as being the "true" uid of the process
  41.          * nor do we know its entire history. We only know it was tainted
  42.          * so we dump it as root in mode 2, and only into a controlled
  43.          * environment (pipe handler or fully qualified path).
  44.          */
  45.         if (__get_dumpable(cprm.mm_flags) == SUID_DUMP_ROOT) {
  46.                 /* Setuid core dump mode */
  47.                 cred->fsuid = GLOBAL_ROOT_UID;        /* Dump root private */
  48.                 need_suid_safe = true;
  49.         }
  50.         retval = coredump_wait(siginfo->si_signo, &core_state);
  51.         if (retval < 0)
  52.                 goto fail_creds;
  53.         old_cred = override_creds(cred);
  54.         // 1. 判断是否采用管道转储
  55.         ispipe = format_corename(&cn, &cprm, &argv, &argc);
  56.         
  57.         /* 2. 如果是管道转储,则设置管道并调用用户模式辅助进程;
  58.         如果是文件转储,则打开文件并进行写入 */
  59.         if (ispipe) {
  60.                 int argi;
  61.                 int dump_count;
  62.                 char **helper_argv;
  63.                 struct subprocess_info *sub_info;
  64.                 if (ispipe < 0) {
  65.                         printk(KERN_WARNING "format_corename failed\n");
  66.                         printk(KERN_WARNING "Aborting core\n");
  67.                         goto fail_unlock;
  68.                 }
  69.                 if (cprm.limit == 1) {
  70.                        
  71.                         printk(KERN_WARNING
  72.                                 "Process %d(%s) has RLIMIT_CORE set to 1\n",
  73.                                 task_tgid_vnr(current), current->comm);
  74.                         printk(KERN_WARNING "Aborting core\n");
  75.                         goto fail_unlock;
  76.                 }
  77.                 cprm.limit = RLIM_INFINITY;
  78.                 dump_count = atomic_inc_return(&core_dump_count);
  79.                 if (core_pipe_limit && (core_pipe_limit < dump_count)) {
  80.                         printk(KERN_WARNING "Pid %d(%s) over core_pipe_limit\n",
  81.                                task_tgid_vnr(current), current->comm);
  82.                         printk(KERN_WARNING "Skipping core dump\n");
  83.                         goto fail_dropcount;
  84.                 }
  85.                 helper_argv = kmalloc_array(argc + 1, sizeof(*helper_argv),
  86.                                             GFP_KERNEL);
  87.                 if (!helper_argv) {
  88.                         printk(KERN_WARNING "%s failed to allocate memory\n",
  89.                                __func__);
  90.                         goto fail_dropcount;
  91.                 }
  92.                 for (argi = 0; argi < argc; argi++)
  93.                         helper_argv[argi] = cn.corename + argv[argi];
  94.                 helper_argv[argi] = NULL;
  95.                 retval = -ENOMEM;
  96.                 // 2.1 设置用户模式辅助程序
  97.                 sub_info = call_usermodehelper_setup(helper_argv[0],
  98.                                                 helper_argv, NULL, GFP_KERNEL,
  99.                                                 umh_pipe_setup, NULL, &cprm);
  100.                 // 2.2 内核执行用户辅助程序
  101.                 if (sub_info)
  102.                         retval = call_usermodehelper_exec(sub_info,
  103.                                                           UMH_WAIT_EXEC);
  104.                 kfree(helper_argv);
  105.                 if (retval) {
  106.                         printk(KERN_INFO "Core dump to |%s pipe failed\n",
  107.                                cn.corename);
  108.                         goto close_fail;
  109.                 }
  110.         } else {
  111.             // 文件转储
  112.            ....     
  113.         }
  114.         ...
  115.         // 3. 查是否中断,如果没有中断,则写入核心转储数据
  116.         if (!dump_interrupted()) {
  117.                 /*
  118.                  * umh disabled with CONFIG_STATIC_USERMODEHELPER_PATH="" would
  119.                  * have this set to NULL.
  120.                  */
  121.                 if (!cprm.file) {
  122.                         pr_info("Core dump to |%s disabled\n", cn.corename);
  123.                         goto close_fail;
  124.                 }
  125.                 if (!dump_vma_snapshot(&cprm))
  126.                         goto close_fail;
  127.                 file_start_write(cprm.file);
  128.                 core_dumped = binfmt->core_dump(&cprm);
  129.                 /*
  130.                  * Ensures that file size is big enough to contain the current
  131.                  * file postion. This prevents gdb from complaining about
  132.                  * a truncated file if the last "write" to the file was
  133.                  * dump_skip.
  134.                  */
  135.                 if (cprm.to_skip) {
  136.                         cprm.to_skip--;
  137.                         dump_emit(&cprm, "", 1);
  138.                 }
  139.                 file_end_write(cprm.file);
  140.                 free_vma_snapshot(&cprm);
  141.         }
  142.         // 4. 进行清理工作,包括关闭文件、减少核心转储计数、释放内存、结束核心转储等
  143.         if (ispipe && core_pipe_limit)
  144.                 wait_for_dump_helpers(cprm.file);
  145. close_fail:
  146.         if (cprm.file)
  147.                 filp_close(cprm.file, NULL);
  148. fail_dropcount:
  149.         if (ispipe)
  150.                 atomic_dec(&core_dump_count);
  151. fail_unlock:
  152.         kfree(argv);
  153.         kfree(cn.corename);
  154.         coredump_finish(core_dumped);
  155.         revert_creds(old_cred);
  156. fail_creds:
  157.         put_cred(cred);
  158. fail:
  159.         return;
  160. }
  161. static void wait_for_dump_helpers(struct file *file)
  162. {
  163.         // 1. 获取管道的信息
  164.         struct pipe_inode_info *pipe = file->private_data;
  165.         // 2.锁定管道,以防止其他进程同时修改管道的状态
  166.         pipe_lock(pipe);
  167.         // 3. 增加管道的读者计数,并减少写者计数。这表明有一个新的读者(核心转储辅助进程)正在等待数据
  168.         pipe->readers++;
  169.         pipe->writers--;
  170.         // 4. 唤醒所有在管道读等待队列上等待的进程
  171.         wake_up_interruptible_sync(&pipe->rd_wait);
  172.         // 5. 向所有注册了异步通知的读者发送 ​SIGIO​ 信号,通知它们有数据可读
  173.         kill_fasync(&pipe->fasync_readers, SIGIO, POLL_IN);
  174.         // 6. 解锁管道,允许其他进程访问管道
  175.         pipe_unlock(pipe);
  176.         /*
  177.          * We actually want wait_event_freezable() but then we need
  178.          * to clear TIF_SIGPENDING and improve dump_interrupted().
  179.          */
  180.          // 7. 当前进程进入可中断的等待状态,直到管道的读者计数等于1。这表明核心转储数据已经被读取完毕。
  181.         wait_event_interruptible(pipe->rd_wait, pipe->readers == 1);
  182.         // 8. 再次锁定管道,以进行后续的状态更新
  183.         pipe_lock(pipe);
  184.         // 9. 减少管道的读者计数,并增加写者计数。这表明读者已经完成了数据读取。
  185.         pipe->readers--;
  186.         pipe->writers++;
  187.         // 10. 解锁管道,允许其他进程访问管道
  188.         pipe_unlock(pipe);
  189. }
  190. // 设置管道
  191. static int umh_pipe_setup(struct subprocess_info *info, struct cred *new)
  192. {
  193.         struct file *files[2];
  194.         struct coredump_params *cp = (struct coredump_params *)info->data;
  195.         // 1. 创建一个管道,并将管道的两个文件描述符存储在 files数组中
  196.         int err = create_pipe_files(files, 0);
  197.         if (err)
  198.                 return err;
  199.         // 2. 管道的写端(​files[1]​)设置为 ​cp->file​,以便后续的核心转储数据可以通过这个文件描述符写入
  200.         cp->file = files[1];
  201.         // 3. 将当前进程的标准输入(fd 0)替换为管道的读端(​files[0]​)。
  202.         // ​replace_fd​ 函数用于替换文件描述符,​fput​ 函数用于减少文件引用计数
  203.         err = replace_fd(0, files[0], 0);
  204.         fput(files[0]);
  205.         /* and disallow core files too */
  206.         // 4. 设置当前进程的核心文件大小限制为1,用于防止递归核心转储
  207.         current->signal->rlim[RLIMIT_CORE] = (struct rlimit){1, 1};
  208.         return err;
  209. }
复制代码


  • format_corename
format_corename函数作用:根据给定的模式字符串生成焦点转储文件的名称,并处置惩罚管道模式,代码如下:
  1. static int format_corename(struct core_name *cn, struct coredump_params *cprm,
  2.                            size_t **argv, int *argc)
  3. {
  4.         const struct cred *cred = current_cred();
  5.         const char *pat_ptr = core_pattern;
  6.         int ispipe = (*pat_ptr == '|');
  7.         bool was_space = false;
  8.         int pid_in_pattern = 0;
  9.         int err = 0;
  10.         cn->used = 0;
  11.         cn->corename = NULL;
  12.         if (expand_corename(cn, core_name_size))
  13.                 return -ENOMEM;
  14.         cn->corename[0] = '\0';
  15.         // 1. 如果模式以管道符号开头,则分配内存用于存储命令行参数,并初始化参数数组
  16.         if (ispipe) {
  17.                 int argvs = sizeof(core_pattern) / 2;
  18.                 (*argv) = kmalloc_array(argvs, sizeof(**argv), GFP_KERNEL);
  19.                 if (!(*argv))
  20.                         return -ENOMEM;
  21.                 (*argv)[(*argc)++] = 0;
  22.                 ++pat_ptr;
  23.                 if (!(*pat_ptr))
  24.                         return -ENOMEM;
  25.         }
  26.         /* Repeat as long as we have more pattern to process and more output
  27.            space */
  28.         while (*pat_ptr) {
  29.                 /*
  30.                  * Split on spaces before doing template expansion so that
  31.                  * %e and %E don't get split if they have spaces in them
  32.                  */
  33.                 if (ispipe) {
  34.                         if (isspace(*pat_ptr)) {
  35.                                 if (cn->used != 0)
  36.                                         was_space = true;
  37.                                 pat_ptr++;
  38.                                 continue;
  39.                         } else if (was_space) {
  40.                                 was_space = false;
  41.                                 err = cn_printf(cn, "%c", '\0');
  42.                                 if (err)
  43.                                         return err;
  44.                                 (*argv)[(*argc)++] = cn->used;
  45.                         }
  46.                 }
  47.                 // 遍历模式字符串,根据不同的模式字符(如 ​%p​、​%u​、​%s​ 等)生成相应的文件名
  48.                 if (*pat_ptr != '%') {
  49.                         err = cn_printf(cn, "%c", *pat_ptr++);
  50.                 } else {
  51.                         switch (*++pat_ptr) {
  52.                         /* single % at the end, drop that */
  53.                         case 0:
  54.                                 goto out;
  55.                         /* Double percent, output one percent */
  56.                         case '%':
  57.                                 err = cn_printf(cn, "%c", '%');
  58.                                 break;
  59.                         /* pid */
  60.                         case 'p':
  61.                                 pid_in_pattern = 1;
  62.                                 err = cn_printf(cn, "%d",
  63.                                               task_tgid_vnr(current));
  64.                                 break;
  65.                         /* global pid */
  66.                         case 'P':
  67.                                 err = cn_printf(cn, "%d",
  68.                                               task_tgid_nr(current));
  69.                                 break;
  70.                        ...                     
  71.                         default:
  72.                                 break;
  73.                         }
  74.                         ++pat_ptr;
  75.                 }
  76.                 if (err)
  77.                         return err;
  78.         }
  79.         ...
  80. }
复制代码


  • call_usermodehelper_setup
call_usermodehelper_setup函数作用:内核设置一个用户空间辅助进程的执行环境
  1. // kernel/kernel/umh.c
  2. struct subprocess_info *call_usermodehelper_setup(const char *path, char **argv,
  3.                 char **envp, gfp_t gfp_mask,
  4.                 int (*init)(struct subprocess_info *info, struct cred *new),
  5.                 void (*cleanup)(struct subprocess_info *info),
  6.                 void *data)
  7. {
  8.         // 1. 分配内存用于存储 ​subprocess_info​ 结构体
  9.         struct subprocess_info *sub_info;
  10.         sub_info = kzalloc(sizeof(struct subprocess_info), gfp_mask);
  11.         if (!sub_info)
  12.                 goto out;
  13.         // 2. 初始化工作队列,用于执行用户空间的辅助进程
  14.         INIT_WORK(&sub_info->work, call_usermodehelper_exec_work);
  15.         // 3. 设置路径、参数、环境变量以及初始化和清理函数
  16. #ifdef CONFIG_STATIC_USERMODEHELPER
  17.         sub_info->path = CONFIG_STATIC_USERMODEHELPER_PATH;
  18. #else
  19.         sub_info->path = path;
  20. #endif
  21.         sub_info->argv = argv;
  22.         sub_info->envp = envp;
  23.         sub_info->cleanup = cleanup;
  24.         sub_info->init = init;
  25.         sub_info->data = data;
  26.   out:
  27.         return sub_info;
  28. }
复制代码


  • call_usermodehelper_exec
call_usermodehelper_exec函数作用:在内核空间中启动一个用户空间的进程,通常用于执行一些特定的任务,如core文件转储
  1. int call_usermodehelper_exec(struct subprocess_info *sub_info, int wait)
  2. {
  3.         // 1. 初始化了一些变量,并检查 ​sub_info->path​ 是否为空
  4.         unsigned int state = TASK_UNINTERRUPTIBLE;
  5.         DECLARE_COMPLETION_ONSTACK(done);
  6.         int retval = 0;
  7.         if (!sub_info->path) {
  8.                 call_usermodehelper_freeinfo(sub_info);
  9.                 return -EINVAL;
  10.         }
  11.         // 2. 对用户模式辅助进程进行加锁,并检查是否禁用了用户模式辅助进程
  12.         helper_lock();
  13.         if (usermodehelper_disabled) {
  14.                 retval = -EBUSY;
  15.                 goto out;
  16.         }
  17.         /*
  18.          * If there is no binary for us to call, then just return and get out of
  19.          * here.  This allows us to set STATIC_USERMODEHELPER_PATH to "" and
  20.          * disable all call_usermodehelper() calls.
  21.          */
  22.         if (strlen(sub_info->path) == 0)
  23.                 goto out;
  24.         /*
  25.          * Set the completion pointer only if there is a waiter.
  26.          * This makes it possible to use umh_complete to free
  27.          * the data structure in case of UMH_NO_WAIT.
  28.          */
  29.         sub_info->complete = (wait == UMH_NO_WAIT) ? NULL : &done;
  30.         sub_info->wait = wait;
  31.         // 3. 将work排队到系统未绑定工作队列中
  32.         queue_work(system_unbound_wq, &sub_info->work);
  33.         if (wait == UMH_NO_WAIT)        /* task has freed sub_info */
  34.                 goto unlock;
  35.         if (wait & UMH_FREEZABLE)
  36.                 state |= TASK_FREEZABLE;
  37.         if (wait & UMH_KILLABLE) {
  38.                 retval = wait_for_completion_state(&done, state | TASK_KILLABLE);
  39.                 if (!retval)
  40.                         goto wait_done;
  41.                 /* umh_complete() will see NULL and free sub_info */
  42.                 if (xchg(&sub_info->complete, NULL))
  43.                         goto unlock;
  44.                 /*
  45.                  * fallthrough; in case of -ERESTARTSYS now do uninterruptible
  46.                  * wait_for_completion_state(). Since umh_complete() shall call
  47.                  * complete() in a moment if xchg() above returned NULL, this
  48.                  * uninterruptible wait_for_completion_state() will not block
  49.                  * SIGKILL'ed processes for long.
  50.                  */
  51.         }
  52.         wait_for_completion_state(&done, state);
  53. wait_done:
  54.         retval = sub_info->retval;
  55. out:
  56.         call_usermodehelper_freeinfo(sub_info);
  57. unlock:
  58.         helper_unlock();
  59.         return retval;
  60. }
复制代码
3.2.4 用户空间coredump辅助程序Demo   

  1. int main(int argc, char *argv[])
  2. {
  3.     int result = snprintf(name, sizeof(name),
  4.                  "/data/xxx/coredump/core-%s-%s", argv[1], argv[2]);
  5.         ...
  6.     fd = open(name, O_RDWR, 0777);
  7.     while (numread = read(STDIN_FILENO, buf, BUF_SIZE))
  8.     {
  9.         if ((numread == -1) && (errno != EINTR)) {
  10.             break;
  11.         } else if (numread > 0) {
  12.             ptr = buf;
  13.             while (numwrite = write(fd, ptr, numread)) {
  14.                 if ((numwrite == -1) && (errno != EINTR)) break;
  15.                 else if (numwrite == numread) break;
  16.                 else if (numwrite > 0) {
  17.                     ptr += numwrite;
  18.                     numread -= numwrite;
  19.                 }
  20.             }
  21.             if (numwrite == -1) {
  22.                 break;
  23.             }
  24.         }
  25.     }
  26.     close(fd);
  27.     return 0;
  28. }
复制代码

四、coredump实现原理

4.1 根本原理

用户程序发生某些错误或非常时,在Linux内核会捕获到非常,并给用户进程发送signal非常信号,进程在返回用户空间之前处置惩罚信号,调用Linux内核coredump,生成elf格式的core文件,生存到指定的路径。

4.2 焦点代码段

调用 do_coredump 函数来生成 core文件。如下:
  1. void do_coredump(const kernel_siginfo_t *siginfo)
  2. {
  3.         ......
  4.         
  5.         binfmt = mm->binfmt;
  6.         if (!binfmt || !binfmt->core_dump)
  7.                 goto fail;
  8.         if (!__get_dumpable(cprm.mm_flags))
  9.                 goto fail;
  10.         ......
  11.         // 1.生成core文件名称
  12.         ispipe = format_corename(&cn, &cprm, &argv, &argc);
  13.         ......
  14.               
  15.         // 2.创建core文件
  16.         cprm.file = file_open_root(&root, cn.corename, open_flags, 0600);
  17.         ......
  18.         
  19.         // 3.将进程的内存信息写入core文件
  20.         core_dumped = binfmt->core_dump(&cprm);
  21.        ......
  22. }
复制代码
elf_core_dump 函数负责将进程的内存状态信息写入elf格式的core文件,以便后续的gdb调试和分析。如下:
  1. // kernel_platform/msm-kernel/fs/binfmt_elf.c
  2. static int elf_core_dump(struct coredump_params *cprm)
  3. {
  4.         ......
  5.         /*
  6.          * Collect all the non-memory information about the process for the
  7.          * notes.  This also sets up the file header.
  8.          */
  9.          // 1.函数填充 ELF 头部和 notes 信息
  10.         if (!fill_note_info(&elf, e_phnum, &info, cprm))
  11.                 goto end_coredump;
  12.         has_dumped = 1;
  13.         // 2.计算 ELF 头部、程序头部和 notes 节的大小,并分配相应的内存
  14.         offset += sizeof(elf);                                /* Elf header */
  15.         offset += segs * sizeof(struct elf_phdr);        /* Program headers */
  16.        ......
  17.         /* Write program headers for segments dump */
  18.         for (i = 0; i < cprm->vma_count; i++) {
  19.                 struct core_vma_metadata *meta = cprm->vma_meta + i;
  20.                 struct elf_phdr phdr;
  21.                 phdr.p_type = PT_LOAD;
  22.                 phdr.p_offset = offset;
  23.                 phdr.p_vaddr = meta->start;
  24.                 phdr.p_paddr = 0;
  25.                 phdr.p_filesz = meta->dump_size;
  26.                 phdr.p_memsz = meta->end - meta->start;
  27.                 offset += phdr.p_filesz;
  28.                 phdr.p_flags = 0;
  29.                 if (meta->flags & VM_READ)
  30.                         phdr.p_flags |= PF_R;
  31.                 if (meta->flags & VM_WRITE)
  32.                         phdr.p_flags |= PF_W;
  33.                 if (meta->flags & VM_EXEC)
  34.                         phdr.p_flags |= PF_X;
  35.                 phdr.p_align = ELF_EXEC_PAGESIZE;
  36.                 if (!dump_emit(cprm, &phdr, sizeof(phdr)))
  37.                         goto end_coredump;
  38.         }
  39.         // 3.写入 ELF 头部和程序头部
  40.         if (!elf_core_write_extra_phdrs(cprm, offset))
  41.                 goto end_coredump;
  42.          /* write out the notes section */
  43.          // 4.写入 notes信息
  44.         if (!write_note_info(&info, cprm))
  45.                 goto end_coredump;
  46.         /* For cell spufs */
  47.         // 5.写入数据段
  48.         if (elf_coredump_extra_notes_write(cprm))
  49.                 goto end_coredump;
  50.         /* Align to page */
  51.         dump_skip_to(cprm, dataoff);
  52.         for (i = 0; i < cprm->vma_count; i++) {
  53.                 struct core_vma_metadata *meta = cprm->vma_meta + i;
  54.                 if (!dump_user_range(cprm, meta->start, meta->dump_size))
  55.                         goto end_coredump;
  56.         }
  57.         // 6.写入扩展编号
  58.         if (!elf_core_write_extra_data(cprm))
  59.                 goto end_coredump;
  60.         if (e_phnum == PN_XNUM) {
  61.                 if (!dump_emit(cprm, shdr4extnum, sizeof(*shdr4extnum)))
  62.                         goto end_coredump;
  63.         }
  64. end_coredump:
  65.         free_note_info(&info);
  66.         kfree(shdr4extnum);
  67.         kfree(phdr4note);
  68.         return has_dumped;
  69. }
复制代码
4.3 代码时序

非常捕获、信号处置惩罚&生成core文件的功能逻辑的代码时序,如下:

4.4 core文件格式及内容

coredump抓取的core文件为elf格式,可以利用gdb调试,定位分析题目。
core文件内容,如下:
  1. ELF Header:
  2.   Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
  3.   Class:                             ELF64
  4.   Data:                              2's complement, little endian
  5.   Version:                           1 (current)
  6.   OS/ABI:                            UNIX - System V
  7.   ABI Version:                       0
  8.   Type:                              CORE (Core file)
  9.   Machine:                           AArch64
  10.   Version:                           0x1
  11.   Entry point address:               0x0
  12.   Start of program headers:          64 (bytes into file)
  13.   Start of section headers:          0 (bytes into file)
  14.   Flags:                             0x0
  15.   Size of this header:               64 (bytes)
  16.   Size of program headers:           56 (bytes)
  17.   Number of program headers:         138
  18.   Size of section headers:           0 (bytes)
  19.   Number of section headers:         0
  20.   Section header string table index: 0
  21.   
  22.   Program Headers:
  23.   Type           Offset             VirtAddr           PhysAddr
  24.                  FileSiz            MemSiz              Flags  Align
  25.   NOTE           0x0000000000001e70 0x0000000000000000 0x0000000000000000
  26.                  0x00000000000018a8 0x0000000000000000         0x0
  27.   LOAD           0x0000000000004000 0x000000560ca89000 0x0000000000000000
  28.                  0x0000000000000000 0x0000000000002000  R      0x1000
  29.   LOAD           0x0000000000004000 0x000000560ca8b000 0x0000000000000000
  30.                  0x0000000000000000 0x0000000000003000  R E    0x1000
  31.   LOAD           0x0000000000004000 0x000000560ca8e000 0x0000000000000000
  32.                  0x0000000000001000 0x0000000000001000  R      0x1000
  33. ...
  34. Displaying notes found at file offset 0x00001e70 with length 0x000018a8:
  35.   Owner                Data size         Description
  36.   CORE                 0x00000188        NT_PRSTATUS (prstatus structure)
  37.   CORE                 0x00000088        NT_PRPSINFO (prpsinfo structure)
  38.   CORE                 0x00000080        NT_SIGINFO (siginfo_t data)
  39.   CORE                 0x00000150        NT_AUXV (auxiliary vector)
  40.   CORE                 0x00000f6e        NT_FILE (mapped files)
  41.     Page size: 4096
  42.                  Start                 End         Page Offset
  43.     0x000000560ca89000  0x000000560ca8b000  0x0000000000000000
  44.         /system/bin/coredump-test-bin
  45.     0x000000560ca8b000  0x000000560ca8e000  0x0000000000000002
  46.         /system/bin/coredump-test-bin
  47. ...
  48. CORE                 0x00000210        NT_FPREGSET (floating point registers)
  49.   LINUX                0x00000010        NT_ARM_TLS (AArch TLS registers)
  50.    description data: 00 10 e4 45 7e 00 00 00 00 00 00 00 00 00 00 00
  51.   LINUX                0x00000108        NT_ARM_HW_BREAK (AArch hardware breakpoint registers)
  52.    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
  53.   LINUX                0x00000108        NT_ARM_HW_WATCH (AArch hardware watchpoint registers)
  54.    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
  55.   LINUX                0x00000004        Unknown note type: (0x00000404)
  56.    description data: ff ff ff ff
  57.   LINUX                0x00000010        Unknown note type: (0x00000406)
  58.    description data: 00 00 00 00 80 ff 7f 00 00 00 00 00 80 ff 7f 00
  59.   LINUX                0x00000008        Unknown note type: (0x0000040a)
  60.    description data: 0f 00 00 00 00 00 00 00
  61.   LINUX                0x00000008        Unknown note type: (0x00000409)
  62.    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文件来正确定位分析这类题目。
  1. Cmdline: ../../system/bin/coredump-test-bin use-after-free
  2. pid: 11966, tid: 11966, name: coredump-test-b  >>> ../../system/bin/coredump-test-bin <<<
  3. uid: 0
  4. ...
  5. backtrace:
  6.       #01 pc 0000000000090088  /system/lib64/libc.so (__vfprintf+10416) (BuildId: 567e41669f1cb528e72fe319cd09033b)
  7.       #02 pc 00000000000ac06c  /system/lib64/libc.so (vsnprintf+192) (BuildId: 567e41669f1cb528e72fe319cd09033b)
  8.       #03 pc 0000000000006afc  /system/lib64/liblog.so (__android_log_print+184) (BuildId: 87ba6a9314f00fab650fb8fad7913d58)
  9.       #04 pc 00000000000010a4  /system/bin/coredump-test-bin (main+80) (BuildId: c97bade065c198c12dcca74f107c513c)
  10.       #05 pc 0000000000048768  /system/lib64/libc.so (__libc_init+96) (BuildId: 567e41669f1cb
  11. ...
复制代码
3)生成的core文件
打开coredump功能,抓取core文件。core文件为elf格式,可以用gdb调试。

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

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农妇山泉一亩田

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表