C语言 gdb step指令不会通过`gettimeofday`

bzzcjhmw  于 2023-08-03  发布在  其他
关注(0)|答案(2)|浏览(99)

当尝试按照this SO answer的建议反汇编程序的每条指令时,我发现gdb在将指令步进到gettimeofday后永远不会完成处理。下面是一个最小可重复的示例:
main.c中:

#include <stdio.h>
#include <stdlib.h>

#include <sys/time.h>

int main()
{
    struct timeval tv;
    
    gettimeofday(&tv, NULL);

    return 0;
}

字符串
使用gcc main.c编译
运行:gdb -silent a.out

Reading symbols from a.out...
(gdb) set height 0
(gdb) b main
Breakpoint 1 at 0x1175: file main.c, line 7.
(gdb) set logging file ./log.txt
(gdb) set logging redirect on
(gdb) set logging on
Redirecting output to ./log.txt.
Copying debug output to ./log.txt.
(gdb) r
(gdb) while 1
 >si
 >end


log.txt显示:

Starting program: /home/user/tst-gdb-step/a.out 

Breakpoint 1, main () at main.c:7
7   {
0x000055555555517e  7   {
0x0000555555555182  7   {
10      gettimeofday(&tv, NULL);
0x0000555555555188  10      gettimeofday(&tv, NULL);
0x000055555555518d  10      gettimeofday(&tv, NULL);
0x0000555555555190  10      gettimeofday(&tv, NULL);
0x0000555555555070 in gettimeofday@plt ()
0x0000555555555074 in gettimeofday@plt ()
0x00007ffff7fcd690 in gettimeofday ()
0x00007ffff7fcd691 in gettimeofday ()
0x00007ffff7fcd698 in gettimeofday ()
0x00007ffff7fcd69b in gettimeofday ()
0x00007ffff7fcd69d in gettimeofday ()
0x00007ffff7fcd69e in gettimeofday ()
0x00007ffff7fcd6a1 in gettimeofday ()
0x00007ffff7fcd6a7 in gettimeofday ()
0x00007ffff7fcd6aa in gettimeofday ()
0x00007ffff7fcd6ae in gettimeofday ()
0x00007ffff7fcd6b4 in gettimeofday ()
0x00007ffff7fcd6ba in gettimeofday ()
0x00007ffff7fcd6bd in gettimeofday ()
0x00007ffff7fcd6c3 in gettimeofday ()
0x00007ffff7fcd6c6 in gettimeofday ()
0x00007ffff7fcd6c8 in gettimeofday ()
0x00007ffff7fcd6cc in gettimeofday ()
0x00007ffff7fcd6cf in gettimeofday ()
0x00007ffff7fcd6d2 in gettimeofday ()
0x00007ffff7fcd6d8 in gettimeofday ()
0x00007ffff7fcd6df in gettimeofday ()
0x00007ffff7fcd6e6 in gettimeofday ()
0x00007ffff7fcd6ed in gettimeofday ()
0x00007ffff7fcd6f0 in gettimeofday ()
0x00007ffff7fcd6f2 in gettimeofday ()
0x00007ffff7fcd6f5 in gettimeofday ()
0x00007ffff7fcd6f9 in gettimeofday ()
0x00007ffff7fcd6fc in gettimeofday ()
0x00007ffff7fcd702 in gettimeofday ()
0x00007ffff7fcd709 in gettimeofday ()
0x00007ffff7fcd70c in gettimeofday ()
0x00007ffff7fcd70f in gettimeofday ()
0x00007ffff7fcd6a7 in gettimeofday ()
0x00007ffff7fcd6aa in gettimeofday ()
0x00007ffff7fcd6ae in gettimeofday ()
0x00007ffff7fcd6b4 in gettimeofday ()
0x00007ffff7fcd6ba in gettimeofday ()
0x00007ffff7fcd6bd in gettimeofday ()
0x00007ffff7fcd6c3 in gettimeofday ()
0x00007ffff7fcd6c6 in gettimeofday ()
0x00007ffff7fcd6c8 in gettimeofday ()
0x00007ffff7fcd6cc in gettimeofday ()
0x00007ffff7fcd6cf in gettimeofday ()
0x00007ffff7fcd6d2 in gettimeofday ()
0x00007ffff7fcd6d8 in gettimeofday ()
0x00007ffff7fcd6df in gettimeofday ()
...


在执行了整整3分钟后,gdblog.txt写入了近500,000行(并且没有任何停止的标志),这显然不是正常情况,因为vdso的速度很快。日志还显示无限循环。
但是如果使用n而不是si,则程序可以退出,没有任何问题。
我使用的工具:

$ uname -a
Linux 5.15.0-69-generic #76~20.04.1-Ubuntu SMP Mon Mar 20 15:54:19 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
$ gcc --version
gcc (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0
$ gdb --version
GNU gdb (Ubuntu 10.2-0ubuntu1~20.04~1) 10.2


我想知道为什么会这样。谢谢你,谢谢

hwazgwia

hwazgwia1#

这是一个基于源潜水的猜测:我相信单步执行gettimeofday的核心会大大减慢这些核心的速度,以至于它们陷入了一个无休止的循环,试图满足结果的内部精度要求。
x86-64/Linux上的gettimeofday代码位于linux/lib/vdso/gettimeofday.c中。我所说的“guts”是do_hres函数。(我已经链接到内核5.15.0,因为这就是你所拥有的,但是这个文件并不经常改变。)注意这个函数中的循环。在循环内部,它计算时间,本质上是(由内核维护的粗略计数器)+(经常溢出的硬件高精度时钟)。每次内核更新粗略计数器时,它也会更新在循环条件中读取的“序列号”。设计者希望这个循环最多循环两次--关键是,如果你在错误的时刻进入循环,并读取了关于粗略计数器的 * 不一致 * 数据,从而计算出一个无意义的时间,你会重试,第二次就能正确。
但是,即使是以自动化的方式,一个指令一个指令地执行这个代码指令,也会使它变得如此缓慢,以至于内核总是在循环体执行时更改粗略计数器和序列号,因此您会陷入循环中。
(Do不要低估与正常执行相比,一条指令一条指令地执行机器代码的速度有多慢。您在 * 每条指令 * 中添加了两个上下文切换和几个命令的时间开销,这些命令使用的是GDB速度并不特别快的脚本语言。如果上下文切换和随之而来的缓存和TLB颠簸足以触发这个活锁,我不会感到惊讶。)

fumotvh3

fumotvh32#

下面是我用来绕过这个问题的gdb脚本:

# load the program binary, symbol table, etc., and start it, requiring gdb > 8.1
starti
# print location for every instruction
display/i $pc
# tell gdb to output without caring window height
set height 0

while 1
    # run the functions to completion if they cause problems
    if ($pc == gettimeofday)
        fin
    end
    if ($pc == clock_gettime)
        fin
    end
    # optional:
    # newer x86 version of `memset` uses `rep` instructions
    # which will expand to a whole lot of lines. So ignore it too.
    if ($pc == memset)
        fin
    end
    # most importantly, step instruction on default
    si
end

字符串
这并不适用于所有情况(例如,在与时间相关的事件上进行分支),并且显然很慢,但它确实适合我。

相关问题