我一直在追问一个问题:退出应用程序挂起,但是在调试时一切都很好。归结为15年前某人做出的错误假设,特别是他假设如果一个线程在accept()
中等待-关闭另一个线程中的句柄将导致accept()
失败。一些流程展开代码基于此假设(而且我知道此假设是不正确的)。
问题:为什么在调试程序时仍然保持这种假设?正是执行环境发生了什么变化?
编辑:在CentOS 7中已观察到
编辑2:我知道它是UB,需要对其进行修复。我的问题不是“做什么?”但是“为什么会发生?”。我很好奇,因为通过这种副作用来感知调试器的能力非常酷,有一天可能会派上用场。
修改3:
我发现,如果您的进程安装了信号处理程序,并且(在关闭fd之后)将信号(通过pthread_kill()
)发送到当前在accept()
中休眠的线程-该调用总是立即返回(错误为EBADF
)。不管您的处理程序在做什么(只要它返回即可)。似乎信号传递导致线程唤醒,中断accept()
并重新启动它(这时它检查相关文件“ handle”是否正确并退出并出错)。
我不鼓励依靠这种行为,而是提出对原始问题的可能解释-也许gdb
会定期以某种信号唤醒每个线程?还是ptrace
d意味着内核(出于某种原因)将定期“唤醒”每个线程,就好像它被信号中断了一样。
答案 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()
在另一线程关闭文件描述符后 。实际上,可以保证发生这种情况,因为该线程在关闭后退出。