strace 可以告诉我系统调用在我的代码中的什么位置被调用吗?

Can strace tell me where in my code a syscall is called?

我正在尝试调试为什么 ngspice 在 运行 模拟时将烦人的换行符打印到 stderr。我试图在追溯到 1993 年的 2400 个源文件之一中找到它,但它并不像听起来那么容易!然而,这确实意味着我有一个嵌入了所有调试信息的二进制文件。

我的第一个想法是 strace 可以帮助我找到我认为有问题的调用并将其追溯到源代码。例如,我很确定这是有问题的系统调用:

   brk(0x55d1a84e9000)                     = 0x55d1a84e9000                                                                                                                                         
   clock_gettime(CLOCK_PROCESS_CPUTIME_ID, {tv_sec=0, tv_nsec=61462905}) = 0                                                                                                                        
>> write(2, "\n", 1)                       = 1                                                                                                                                                      
   getrusage(RUSAGE_SELF, {ru_utime={tv_sec=0, tv_usec=26269}, ru_stime={tv_sec=0, tv_usec=35243}, ...}) = 0                                                                                        
   openat(AT_FDCWD, "/proc/self/statm", O_RDONLY) = 3                                   

我曾希望如果我跟踪一个有调试信息的可执行文件,strace 会告诉我源代码中的位置,但这并没有自动发生,而且手册有点让人不知所措。

我在手册中找到了一个名为 Tracing 的部分,但找不到任何具体内容。

strace 是否可行,如果可行:如何实现?如果没有,您还有其他建议吗?

My first idea was that strace could help me locate what I believe is the offending call and trace it back to the source code.

您猜对了,但一定是在 strace 手册页中忽略了这一点:

<b>-i </b>打印系统调用时的指令指针

使用 gdb,您可以根据系统调用的参数设置条件系统调用捕获点(类似于您根据函数调用的参数在函数入口处设置条件断点的方式)。然后,当catchpoint被触发时,可以看到调用者在哪里(文件名、行号、源代码)。

这是 x86_64 的示例。

$ cat gtest.c
#include <unistd.h>
int main()
{
        write(1, "text\n", 5);
        write(2, "text2\n", 6);
        write(2, "\n", 1);
        return 0;
}

$ cc gtest.c -g -o gtest


$ gdb -q gtest
Reading symbols from gtest...done.
(gdb) list
1       #include <unistd.h>
2       int main()
3       {
4               write(1, "text\n", 5);
5               write(2, "text2\n", 6);
6               write(2, "\n", 1);
7               return 0;
8       }
(gdb) catch syscall write
Catchpoint 1 (syscall 'write' [1])
(gdb) condition 1 $rdi == 2 && *(char *)$rsi == '\n' && $rdx == 1
(gdb) r
Starting program: /home/mp/gtest
text
text2

Catchpoint 1 (call to syscall write), 0x00007fffff13b970 in __write_nocancel ()
    at ../sysdeps/unix/syscall-template.S:84
84      ../sysdeps/unix/syscall-template.S: No such file or directory.
(gdb) bt
#0  0x00007fffff13b970 in __write_nocancel () at ../sysdeps/unix/syscall-template.S:84
#1  0x00000000080006f6 in main () at gtest.c:6
(gdb) up
#1  0x00000000080006f6 in main () at gtest.c:6
6               write(2, "\n", 1);
(gdb)

事后看来很明显,但一个非常有用的标志是 -k。来自手册页:

-k Print the execution stack trace of the traced processes after each system call.

这需要一个带有调试信息的二进制文件,它会变得非常嘈杂,但结合一个简单的过滤器(-e write 在这种情况下)你最终会得到如下所示的东西:

write(2, "\n", 1
)                       = 1
 > /lib/x86_64-linux-gnu/libc-2.28.so(__write+0x14) [0xea504]
 > /lib/x86_64-linux-gnu/libc-2.28.so(_IO_file_write+0x2d) [0x7b3bd]
 > /lib/x86_64-linux-gnu/libc-2.28.so(_IO_file_setbuf+0xef) [0x7a75f]
 > /lib/x86_64-linux-gnu/libc-2.28.so(_IO_do_write+0x19) [0x7c509]
 > /lib/x86_64-linux-gnu/libc-2.28.so(_IO_file_overflow+0x103) [0x7c8f3]
 > /home/pipe/src/ngspice/debug/src/ngspice(OUTendPlot+0x1ae) [0xd7643]
 > /home/pipe/src/ngspice/debug/src/ngspice(DCop+0x167) [0x4cd788]
 > /home/pipe/src/ngspice/debug/src/ngspice(CKTdoJob+0x428) [0x4c70dd]
 > /home/pipe/src/ngspice/debug/src/ngspice(if_run+0x3b9) [0xe5d3e]
 > /home/pipe/src/ngspice/debug/src/ngspice(dosim+0x428) [0xe02ee]

在跟踪一些函数内联优化后,我最终可以从中找到正确的位置。