如何在 Go 代码中捕获 C/C++ lib 异常

How to catch the C/C++ lib exceptions in Go code

我在 Go 代码中使用 Cgo 访问 C/C++ 库,我发现了一些异常日志,如下所示:

fatal error: unexpected signal during runtime execution
[signal SIGSEGV: segmentation violation code=0x1 addr=0x90 pc=0x7ff0fbdc23ff]

....

STACK ...

现在我可以确认异常来自C/C++库,但是这个异常会使我的Go程序崩溃,即使我写了恢复代码。(PS:看来我无法恢复致命错误)。

我的场景:

  1. Go 程序将从 MQ 接收消息
  2. Go程序调用C库处理消息
  3. 标记消息处理完成。

在这个过程中,Go 程序可能会收到错误的消息(例如:无效的消息格式)。错误的信息可能会导致C库崩溃,而在Go程序中找不到,C库崩溃时我无能为力,即使我想在Go程序重新启动时跳过错误信息。

有什么方法可以从 C/C++ 库中捕获异常吗?

或者一般来说,Cgo 中错误处理的最佳实践是什么?

我想强调一下@Not_a_Golfer 所说的:当 OS 遇到它试图访问内存时,进程会收到 SIGSEGV 信号,它一定从未尝试过访问.

问题是导致此类错误的原因可能确实是“无害的”(见下文),也可能不是。

  • 无害可能就像尝试 读取 某个地址处的内存,该地址对进程无效。最常见的情况是试图取消引用所谓的 NULL 指针。

    在这种情况下,进程可能不会覆盖一段内存,如果你很幸运,中止操作主要是让进程突然消失¹。

    但这不是独角兽和彩虹:如果进程在操作开始之前分配了一些内存,您很可能会以内存泄漏告终。

  • 严重的情况是写入一个不适合进程的内存区域。
    它们的问题是,当进程到达无效内存区域时,它可能已经覆盖了它自己的活动数据结构,这不是预期的。

    在这种情况下,所有的赌注都没有了。

无论是哪个class导致无效内存访问的特定问题,请注意,它表明程序至少包含一个逻辑错误,并且执行了执行该错误的代码路径。这意味着该过程现在处于某种未定义的状态,因为此类错误很容易“传播”:当程序的其他不相关部分可能开始行为不端时,它们可能会导致级联效应,因为它们的逻辑所基于的不变量被无意中更改。

在你的例子中,代码似乎在地址 0x90 访问内存,这看起来像一个 classic 指针算法涉及一个 NULL 指针(只是一个猜测,但仍然)。

在这种情况下我会做的是:

  • 将此库包装在单独的 进程中 并通过任何类型的 IPC 与其通信。
  • 一旦它死亡,生成另一个副本代替它并重试。

如果可能的话,请务必尝试解决根本原因。


¹ 在 OS 陷入对无效内存区域的访问后正确恢复执行本身就是一项艰巨的任务——参见 this 例如。
基本上你必须实现一个自定义信号处理程序,它将以 OS 将重新开始执行你的进程代码的方式设置,而不是从实际访问该内存块并爆炸的 CPU 指令向上但有一个已知的好位置(据说应该是库的入口点函数出口附近的某个地方,该函数在其调用路径的某个地方执行了错误代码。
并且您需要正确恢复堆栈指针,并且可能是其他东西。

真的,这不是你经常做的事情。
对库映像进行二进制修补甚至可以减少资源消耗,以防止错误的代码路径被执行或将它们转移到固定的对应部分,添加到映像中——很像通过类似于 those done for TTD 的二进制修补完成的错误修复,例如。