gdb对已调试程序行为的影响`accept()`和`close()`

时间:2018-08-03 20:45:04

标签: c linux linux-kernel gdb

我一直在追问一个问题:退出应用程序挂起,但是在调试时一切都很好。归结为15年前某人做出的错误假设,特别是他假设如果一个线程在accept()中等待-关闭另一个线程中的句柄将导致accept()失败。一些流程展开代码基于此假设(而且我知道此假设是不正确的)。

问题:为什么在调试程序时仍然保持这种假设?正是执行环境发生了什么变化?

编辑:在CentOS 7中已观察到

编辑2:我知道它是UB,需要对其进行修复。我的问题不是“做什么?”但是“为什么会发生?”。我很好奇,因为通过这种副作用来感知调试器的能力非常酷,有一天可能会派上用场。

修改3:

我发现,如果您的进程安装了信号处理程序,并且(在关闭fd之后)将信号(通过pthread_kill())发送到当前在accept()中休眠的线程-该调用总是立即返回(错误为EBADF)。不管您的处理程序在做什么(只要它返回即可)。似乎信号传递导致线程唤醒,中断accept()并重新启动它(这时它检查相关文件“ handle”是否正确并退出并出错)。

我不鼓励依靠这种行为,而是提出对原始问题的可能解释-也许gdb会定期以某种信号唤醒每个线程?还是ptrace d意味着内核(出于某种原因)将定期“唤醒”每个线程,就好像它被信号中断了一样。

1 个答案:

答案 0 :(得分:0)

来自ptrace man

  

信号注入和抑制

     

在跟踪器观察到信号传递停止之后,跟踪器      应该通过调用重新启动跟踪

   ptrace(PTRACE_restart, pid, 0, sig)
     

其中PTRACE_restart是重新启动的ptrace请求之一。如果      为0,则不传送信号。否则,信号信号      已交付。此操作在本手册中称为信号注入      一页,以区别于信号传递停止。

     

sig值可能与WSTOPSIG(status)值不同:      示踪剂会导致注入不同的信号。

     

请注意,被抑制的信号仍然会导致系统调用返回      过早地。在这种情况下,系统调用将重新启动:      示踪剂将观察示踪剂以重新执行被中断的系统      调用(或restart_syscall(2)系统调用,进行一些系统调用,其中      使用不同的重启机制)(如果跟踪程序使用      PTRACE_SYSCALL。甚至不是的系统调用(例如poll(2))      在抑制信号后重启信号后可重启;      但是,存在内核错误,这些错误会导致某些系统调用失败并      即使没有可见信号注入到EINTR,也是如此。

在我的情况下,终止逻辑从传递信号(被gdb截获)开始,然后将其传递到正在跟踪的进程中。 strace-ing gdb产生:

ptrace(PTRACE_PEEKTEXT, 2274, 0x7f0c9d0ee2e0, [0x7f0ca013ab80]) = 0      <-- gdb woke up to SIGTERM directed at tracee
ptrace(PTRACE_PEEKUSER, 2274, 8*SS + 8, [0x7f0ca013a8c0]) = 0
ptrace(PTRACE_GETREGS, 2274, 0, 0x7ffdb4f2c3a0) = 0
...
ptrace(PTRACE_CONT, 2338, 0x1, SIG_0)   = 0
ptrace(PTRACE_CONT, 2274, 0x1, SIGTERM) = 0     <-- SIGTERM is delivered to a thread chosen by kernel
...
ptrace(PTRACE_CONT, 2276, 0x1, SIG_0)   = 0     <-- all other threads are restarted
...

请注意,这还不足以完全解释行为,因为在其他线程关闭文件描述符之前,accept()中处于休眠状态的线程可以重新启动syscall。

但是strace日志充满了类似的命令序列(PTRACE_PEEKTEXT之后是数量不断减少的PTRACE_CONT)。这里发生的是gdb在每次线程终止时唤醒,从跟踪中拉出一些数据并重新启动(剩余的)线程,从而导致重新启动syscall。即当线程彼此退出时,每个剩余的线程将停止并重新启动多次,最终导致accept()在另一线程关闭文件描述符后 。实际上,可以保证发生这种情况,因为该线程在关闭后退出。