代码之家  ›  专栏  ›  技术社区  ›  C.M.

gdb在调试的程序行为w.r.t.`accept()`and`close()中有什么变化`

  •  0
  • C.M.  · 技术社区  · 6 年前

    我一直在寻找一个问题:在退出应用程序挂起,但当你调试它都很好。这可以归结为15年前某人的一个错误假设,特别是他假设如果有一根线在等着 accept() --在另一个线程中关闭该句柄将导致 接受() 失败。一些进程展开代码依赖于这个假设(我知道这个假设是不正确的)。

    问题:当程序被调试时,为什么这个假设成立?准确地说,执行环境有哪些变化?

    编辑: 在CentOS 7观测

    编辑2: 我知道这是一个俱乐部,我需要修复它。我的问题不是“怎么办?”但“为什么会这样?”。我很好奇,因为通过这样的副作用来感知调试器的能力是相当酷的,总有一天可能会派上用场。

    编辑3:

    我发现如果您的进程安装了一个信号处理程序,并且(在关闭fd之后)您发送该信号(通过 pthread_kill() )线程当前正在睡眠 接受() --那通电话总是立即(用 EBADF 错误)。不管你的处理程序在做什么(只要它返回)。看起来信号传递导致线程唤醒,中断 接受() 然后重新启动它(在这一点上,它检查相关文件“句柄”是否好,并退出错误)。

    我不鼓励依赖这种行为,但对最初的问题提出一个可能的解释--也许 gdb 周期性地用一些信号唤醒每个线程?或者是 ptrace d意味着内核(出于某种原因)会周期性地唤醒每个线程,就好像它被一个信号中断了一样?

    1 回复  |  直到 6 年前
        1
  •  0
  •   C.M.    6 年前

    ptrace man :

    信号注入与抑制

    当跟踪器观察到信号传送停止后,跟踪器 应该用呼叫重新启动跟踪对象

       ptrace(PTRACE_restart, pid, 0, sig)
    

    其中PTRACE_restart是重新启动PTRACE请求之一。如果 为0,则不传递信号。否则,信号信号 已送达。这个操作叫做信号注入 另一页,以区别于信号传送停止。

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

    注意a 抑制的信号仍会导致系统调用返回 过早地。在这种情况下,系统调用将重新启动 :的 追踪者将观察被追踪者重新执行中断的系统 调用(或重新启动)系统调用 如果跟踪程序使用 在信号被抑制后重新启动信号后可重新启动; 但是,存在内核错误,这会导致一些系统调用失败。 即使没有可观察到的信号注入被跟踪者。

    在我的例子中,终止逻辑从一个信号的传递(被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() 可以在其他线程关闭文件描述符之前重新启动系统调用。

    但是 斯特拉斯 日志中充满了类似的命令序列( PTRACE_PEEKTEXT 接下来是不断减少 PTRACE_CONT ). 这里发生的是 gdb公司 每次线程终止时唤醒,从跟踪对象中提取一些数据并重新启动(剩余的)线程,导致系统调用重新启动。即当线程逐个退出时,每个剩余线程停止并重新启动多次,最终导致 接受() 重新启动 之后 文件描述符被另一个线程关闭。事实上,它是保证发生的,因为该线程在关闭之后退出。