zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Who prints “(core dumped)” after a segmentation fault?

Core After Segmentation who Fault
2023-09-11 14:15:47 时间

When a CLI process in Linux exits after a segmentation fault, the following message is typically printed to stdout: “Segmentation fault (core dumped)”. We are assuming here that the process did not register a handler for the SIGSEGV signal. I was a bit curious about who was printing the message so started to dig a bit.

My first hypothesis was that libc had a default handler for this signal. After running the application with strace, I found no sys_write system call: the application and its libraries were not printing anything.

If a process registers no handler for SIGSEGV, do_coredump function (fs/coredump.c – Linux kernel) is executed. Caught my attention that the Kernel creates a new task and launches a user mode application. This application is /usr/libexec/abrt-hook-ccpp in Fedora, and the goal is to record and report the crash. I ran strings over its binary and dynamically linked libraries (libc, libreport, libabrt, etc.) but no clues.

 

After some more source code grepping over potential candidates, such as systemd-coredump, my curiosity was even larger and finally opted for the hard way: monitor every single sys_write system call in the kernel.

The first place to insert a monitor code was n_tty_write function (drivers/tty/n_tty.c – Linux kernel), as every TTY write hits there. I set a breakpoint in the monitor code and it was immediately triggered. Looking at the task memory map, I realized it was bash. But then I thought: what if it’s not bash but a child process sending the message through a pipe?

A second monitor code was placed a bit higher in the call stack: __vfs_write function (fs/read_write.c – Linux kernel). A breakpoint there was hit and it was bash again. Downloaded the bash source and could verify it.

Here it’s the monitor:

C

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

unsigned char* kBuf = NULL;

if (count > 0) {

    kBuf = (unsigned char*)kmalloc(count + 1, GFP_KERNEL);

    if (kBuf != NULL) {

        memset(kBuf, 0, count + 1);

        if (copy_from_user(kBuf, p, count) == 0) {

            if (strstr(kBuf, "core dumped")) {

                asm("nop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\t");

                printk("PID: %d\n", current->pid);

                asm("nop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\tnop\n\t");

            }

        }

        kfree(kBuf);

    }

}

Notes: 1) nops are there to make finding the address for the breakpoint easier, and 2) memset could probably be removed if using the proper flags when calling kmalloc.

Here it’s how it looks in assembly:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

0xffffffff81271cae <__vfs_write+510>:        nop

0xffffffff81271caf <__vfs_write+511>:        nop

0xffffffff81271cb0 <__vfs_write+512>:        nop

0xffffffff81271cb1 <__vfs_write+513>:        nop

0xffffffff81271cb2 <__vfs_write+514>:        nop

0xffffffff81271cb3 <__vfs_write+515>:        nop

0xffffffff81271cb4 <__vfs_write+516>:        nop

0xffffffff81271cb5 <__vfs_write+517>:        mov    %gs:0xd300,%rax

0xffffffff81271cbe <__vfs_write+526>:        mov    0x900(%rax),%esi

0xffffffff81271cc4 <__vfs_write+532>:        mov    $0xffffffff81ca0551,%rdi

0xffffffff81271ccb <__vfs_write+539>:        callq  0xffffffff811065c1 <printk>

0xffffffff81271cd0 <__vfs_write+544>:        nop

0xffffffff81271cd1 <__vfs_write+545>:        nop

0xffffffff81271cd2 <__vfs_write+546>:        nop

0xffffffff81271cd3 <__vfs_write+547>:        nop

0xffffffff81271cd4 <__vfs_write+548>:        nop

0xffffffff81271cd5 <__vfs_write+549>:        nop

0xffffffff81271cd6 <__vfs_write+550>:        nop

RAX register has a pointer to the current task struct.

This is how we can have a look at the task memory map and determine the file associated to the first segment:

1

2

(gdb) x/s ((struct task_struct*)$rax)->mm->mmap->vm_file->f_path->dentry->d_name->name  

0xffff88007b7de278:     "bash"


结束!