【问题标题】:Configure kern.log to give more info about a segfault配置 kern.log 以提供有关段错误的更多信息
【发布时间】:2014-11-01 02:19:23
【问题描述】:

目前我可以在 kern.log 中找到这样的条目:

[6516247.445846] ex3.x[30901]: segfault at 0 ip 0000000000400564 sp 00007fff96ecb170 error 6 in ex3.x[400000+1000]
[6516254.095173] ex3.x[30907]: segfault at 0 ip 0000000000400564 sp 00007fff0001dcf0 error 6 in ex3.x[400000+1000]
[6516662.523395] ex3.x[31524]: segfault at 7fff80000000 ip 00007f2e11e4aa79 sp 00007fff807061a0 error 4 in libc-2.13.so[7f2e11dcf000+180000]

(你看,导致段错误的应用程序被命名为 ex3.x,表示练习 3 可执行文件)。

有没有办法让 kern.log 记录完整的路径?比如:

[6...] /home/user/cclass/ex3.x[3...]: segfault at 0 ip 0564 sp 07f70 error 6 in ex3.x[4...]

所以我可以很容易地从这个 ex3.x 中找出谁(用户/学生)?

谢谢! 贝科

【问题讨论】:

    标签: linux logging segmentation-fault kernel debian


    【解决方案1】:

    该日志消息来自具有固定格式的内核,仅包含可执行文件的前 16 个字母,不包括按照 show_signal_msg 的路径,请参阅其他相关行以了解非 x86 架构上的分段错误。

    正如 Makyen 所说,如果不对内核进行重大更改并重新编译,传递给 syslog 的传递给 klogd 的消息将不会包含您请求的信息。

    我不知道 syslog 或 klogd 中有任何日志转换或注入功能,它们允许您获取文件名并在文件系统上运行 locate 或 file 以找到完整路径。

    获取所需信息的最佳方式是使用崩溃拦截软件,如apportabrtcorekeeper。这些工具存储来自 /proc 文件系统的进程元数据,包括进程的命令行,该命令行将包含运行的目录,假设二进制文件使用完整路径运行,并且不在路径中。

    另一种更通用的方法是启用核心转储,然后将 /proc/sys/kernel/core_pattern 设置为包含%E,以使核心文件名包含二进制文件的路径。

    【讨论】:

    • 正如我在接受的答案中评论的那样,您非常有创意,可以开箱即用,并给了我很好的建议来弄清楚如何解决这个小问题。谢谢你。为此+1。我可能会尝试“核心转储”方案。你真聪明。
    • +1 也来自我。该答案为潜在问题提供了替代解决方案,而不仅仅是表达的问题。这在现实生活中往往更为重要。
    【解决方案2】:

    简短的回答是:不,不更改代码并重新编译内核是不可能的。这个问题的正常解决方案是指导你的学生将他们的可执行文件命名为<student user name>_ex3.x,这样你就可以很容易地获得这些信息。

    但是,可以通过其他方法获取您想要的信息Appleman1234 在他对这个问题的回答中提供了一些替代方案。

    我们如何知道答案是“如果不重新编译内核,则无法获得 kern.log 段错误消息中的完整路径”:

    我们查看内核源代码以了解消息是如何产生的以及是否有任何配置选项。

    有问题的文件是内核源代码的一部分。您可以将整个内核源代码下载为 rpm 包(或其他类型的包),适用于您从各个地方运行的任何版本的 linux/debian。

    具体来说,您看到的输出是从以下适用于您的架构的文件中生成的:

    来自文件之一的相关功能示例(linux/arch/x86/mm/fault.c):

    /*
     * Print out info about fatal segfaults, if the show_unhandled_signals
     * sysctl is set:
     */
    static inline void
    show_signal_msg(struct pt_regs *regs, unsigned long error_code,
            unsigned long address, struct task_struct *tsk)
    {
        if (!unhandled_signal(tsk, SIGSEGV))
            return;
    
        if (!printk_ratelimit())
            return;
    
        printk("%s%s[%d]: segfault at %lx ip %p sp %p error %lx",
            task_pid_nr(tsk) > 1 ? KERN_INFO : KERN_EMERG,
            tsk->comm, task_pid_nr(tsk), address,
            (void *)regs->ip, (void *)regs->sp, error_code);
    
        print_vma_addr(KERN_CONT " in ", regs->ip);
    
        printk(KERN_CONT "\n");
    }
    

    从中我们看到传递给打印输出进程标识符的变量是tsk->comm,其中struct task_struct *tskregs->ip,其中struct pt_regs *regs

    然后来自linux/include/linux/sched.h

    struct task_struct {
        ...
        char comm[TASK_COMM_LEN]; /* executable name excluding path
                                     - access with [gs]et_task_comm (which lock
                                       it with task_lock())
                                     - initialized normally by setup_new_exec */
    

    注释清楚地表明可执行文件的路径未存储在结构中。

    对于regs->ip 其中struct pt_regs *regs,它是在以下适合您的架构的任何一个中定义的:

    从那里我们看到struct pt_regs 正在为架构定义寄存器。 ip 只是: unsigned long ip;

    因此,我们必须看看print_vma_addr() 做了什么。定义在mm/memory.c

    /*
     * Print the name of a VMA.
     */
    void print_vma_addr(char *prefix, unsigned long ip)
    {
        struct mm_struct *mm = current->mm;
        struct vm_area_struct *vma;
    
        /*
         * Do not print if we are in atomic
         * contexts (in exception stacks, etc.):
         */
        if (preempt_count())
            return;
    
        down_read(&mm->mmap_sem);
        vma = find_vma(mm, ip);
        if (vma && vma->vm_file) {
            struct file *f = vma->vm_file;
            char *buf = (char *)__get_free_page(GFP_KERNEL);
            if (buf) {
                char *p;
    
                p = d_path(&f->f_path, buf, PAGE_SIZE);
                if (IS_ERR(p))
                    p = "?";
                printk("%s%s[%lx+%lx]", prefix, kbasename(p),
                        vma->vm_start,
                        vma->vm_end - vma->vm_start);
                free_page((unsigned long)buf);
            }
        }
        up_read(&mm->mmap_sem);
    }
    

    这向我们表明路径是可用的。我们需要检查它是否是路径,但进一步查看代码会提示它可能无关紧要。我们需要看看kbasename() 对传递给它的路径做了什么。 kbasename()include/linux/string.h 中定义为:

    /**
     * kbasename - return the last part of a pathname.
     *
     * @path: path to extract the filename from.
     */
    static inline const char *kbasename(const char *path)
    {
        const char *tail = strrchr(path, '/');
        return tail ? tail + 1 : path;
    }
    

    即使完整路径在它之前可用,它也会删除除了路径名的最后一部分之外的所有内容,留下文件名。

    因此,任何运行时配置选项都不允许在您看到的段错误消息中打印出文件的完整路径名。


    注意:我已将内核源代码的所有链接更改为存档,而不是原始位置。这些链接将接近我写这篇文章时的代码,2104-09。毫不奇怪,代码确实会随着时间而发展,因此当您阅读本文时,当前的代码可能相似,也可能不同,或者按照此处描述的方式执行。

    【讨论】:

    • 谢谢。不错的工作。你的回答是否完全符合我的要求。你得到了赏金,恭喜。但我要指出@Appleman1234 非常有创意,可以跳出框框思考并提出其他方法。我很想接受他的回答,但在阅读了我的问题之后,公平地说,你成功了。
    • 是的,我应该跳出框框思考这个问题。我想我没有这样做的原因是我真的不认为重新编译内核是一个重大问题。我应该更多地考虑在您可能的环境中什么是合理的,在自定义内核上重新编译和运行可能是一个重要问题。
    • 有趣的是,我又用谷歌搜索了一遍,结果就到了这里。 +1 多年后带我回到这里。
    • 试图将现在悬空的链接更新为例如elixir.bootlin.com/linux/latest/source/arch/x86/mm/fault.c#L768
    • @MartinDorey 我已经更新了内核代码链接到archive.org 链接的about。鉴于这是在某个时间点谈论内核代码,我认为我们不能做得比这更好。有可能在捕获日期更接近我实际发布此内容时记录了内核其他镜像的档案,但我不确定努力寻找它们是否有好处。我应该在发布此内容时为这些链接创建档案,但显然当时并没有想到。
    猜你喜欢
    • 2018-03-01
    • 1970-01-01
    • 2023-03-08
    • 1970-01-01
    • 2020-06-14
    • 2016-03-09
    • 1970-01-01
    • 2016-03-02
    • 1970-01-01
    相关资源
    最近更新 更多