调试和自由执行中的信号处理

Signal handling in debug and in free execution

我必须在使用 Boost.Asio 的程序中处理 SIGINTSIGTERM。我为此使用 boost::asio::signal_set::async_wait()

问题是信号处理程序仅在我简单地 运行 应用程序时获得控制权,但在我调试它时却没有。

这是一些代码:

Proxy::Proxy():
    signals_(ioContext_, SIGINT, SIGTERM)
{
    signals_.async_wait(
        [this](const boost::system::error_code& error, int)
        {
            if (!error)
                ioContext_.stop();
        }
    );
}

Proxy::run()
{
    ioContext_.run();
}

当我们run() ProxyioContext_ 开始处理事件。如果我们只是简单地 运行 程序并在终端中执行 Ctrl+C ,信号处理程序(即 lambda)将停止 ioContext_ (如我们所料)并且 io_context::run 将控制权交还.但在调试模式下,程序对 Ctrl+C 做出反应,但执行在 epoll_wait() 的某处停止。如果我们继续执行,它会挂在 epoll_wait() 的某处,依此类推。

这是执行停止位置的堆栈跟踪:

epoll_wait
boost::asio::detail::epoll_reactor::run
boost::asio::detail::scheduler::do_one_run
boost::asio::detail::scheduler::run
boost::asio::io_context::run
Proxy::run
main

为什么在调试模式下会发生,而在其他模式下不会发生?

这里的问题是 GDB 使用 SIGINT 作为中断程序并允许您开始调试的机制。

(gdb) info signals SIGINT
Signal        Stop      Print   Pass to program Description
SIGINT        Yes       Yes     No              Interrupt

这是说 GDB 不应该将 SIGINTs 传递给程序,但应该使用它来停止程序并让您进入 GDB 提示符。将它发送到您的程序的最简单机制是此时从 GDB 发送信号:

(gdb) signal SIGINT

现在您的程序应该按预期继续运行。


根据您执行此操作的频率,键入 signal SIGINT 可能会带来不便。幸运的是,GDB 允许您修改它处理 signals 的方式。您希望 SIGINT 不停止程序(让您进入 GDB 提示符)并将其传递给程序。

(gdb) handle SIGINT nostop pass
SIGINT is used by the debugger.
Are you sure you want to change it? (y or n) y
Signal        Stop      Print   Pass to program Description
SIGINT        No        Yes     Yes             Interrupt

我们现在处于 "slightly inadvisable," 的境地,因为我们不能再使用 Ctrl+C 跳转到我们的 GDB 提示符。您将不得不依赖预设断点和其他机制。

如果您想更高级,可以使用 catchcommands 来确定 SIGINT 的来源(从 中提取):

catch signal SIGINT
commands
  if $_siginfo._sifields._kill.si_pid == 0
    print "Received SIGINT from tty"
  else
    printf "Received SIGINT from %d; continuing\n", $_siginfo._sifields._kill.si_pid
    signal SIGINT
  end
end