[译] SIGSEGV:Linux 容器中的分段错误(退出代码 139)

2022/07/19 14:48 下午 posted in  操作系统

翻译自 《SIGSEGV: Segmentation fault in Linux containers (exit code 139)》
原文链接:https://komodor.com/learn/`SIGSEGV`-segmentation-faults-signal-11-exit-code-139/

什么是 SIGSEGV

SIGSEGV,也称为分段违规或分段错误,是基于 Unix 的操作系统(如 Linux)使用的信号。它表示程序尝试在其分配的内存之外进行写入或读取,由于编程错误、软件或硬件兼容性问题或恶意攻击(例如缓冲区溢出)。

SIGSEGV 由以下代码表示:

  • 在 Unix/Linux 中,SIGSEGV 是操作系统信号 11
  • 在 Docker 容器中,当 Docker 容器由于 SIGSEGV 错误而终止时,它会抛出退出码 139

SIGSEGV 的默认操作是进程异常终止。此外,还可能发生以下情况:

  • 通常会生成 core 文件以启用调试;
  • 出于故障排除和安全目的,SIGSEGV 信号在日志中被记录地更加详细;
  • 操作系统可以执行特定于平台的操作;
  • 操作系统可能允许进程本身处理分段错误。

SIGSEGV 是 Kubernetes 中容器终止的常见原因。但是,Kubernetes 不会直接触发 SIGSEGV。要解决此问题,您需要调试有问题的容器或底层主机。

SIGSEGV 与 SIGABRT

SIGSEGVSIGABRT 是两个可以导致进程终止的 Unix 信号。

SIGSEGV 由操作系统触发,它检测到一个进程存在内存违规,可能因此终止它。
SIGABRT(信号中止)是由进程本身触发的信号。它异常终止进程,关闭并刷新打开的流。一旦被触发,就不能被进程阻塞(类似于SIGKILL,不同的是SIGKILL是由操作系统触发的)。

在发送 SIGABRT 信号之前,进程可以:

  • 调用 libc 库中的 abort() 函数,解锁 SIGABRT 信号。然后进程可以通过触发 SIGABRT 自行中止
  • 调用用于调试的 assert() 宏,如果断言为假,则使用 SIGABRT 中止程序。

退出码 139 和 134 与 Docker 容器中的 SIGSEGVSIGABRT 并行:

  • Docker 退出码 139:表示容器由于内存冲突而收到底层操作系统的 SIGSEGV
  • Docker 退出码 134:表示容器触发了 SIGABRT 并被异常终止

什么导致 SIGSEGV?

现代通用计算系统包括内存管理单元 (MMU)。 MMU 可以在 Linux 等操作系统中实现内存保护,防止不同进程访问或修改彼此的内存,除非通过严格控制的 API。这简化了故障排除并使进程更具弹性,因为它们被彼此隔离开来了。

当进程尝试使用 MMU 未分配给它的内存地址时,会发生 SIGSEGV 信号或分段错误。这可能由于三个常见原因而发生:

  1. 编码错误:如果进程未正确初始化,或者如果它试图通过指向先前释放的内存的指针访问内存,则可能发生分段冲突。这将导致在特定情况下特定进程或二进制文件中的分段错误。
  2. 二进制文件和库之间的不兼容:如果进程运行的二进制文件与共享库不兼容,则可能导致分段错误。例如,如果开发人员更新了库,更改了其二进制接口,但没有更新版本号,则可能会针对较新版本加载较旧的二进制文件。这可能会导致较旧的二进制文件尝试访问错误的内存地址。
  3. 硬件不兼容或配置错误:如果在多个库中频繁发生分段错误,并且没有重复模式,这可能表明机器上的内存子系统存在问题或不正确的低级系统配置设置。

处理 SIGSEGV 错误

在基于 Unix 的操作系统上,默认情况下,SIGSEGV 信号将导致违规进程异常终止。

操作系统执行的其他操作

除了终止进程外,操作系统还可以生成 core 文件来辅助调试,也可以执行其他平台相关的操作。例如,在 Linux 上,您可以使用 grsecurity 实用程序详细记录 SIGSEGV 信号,以监控相关的安全风险,例如缓冲区溢出。

允许进程处理 SIGSEGV

在 Linux 和 Windows 上,操作系统允许进程处理它们对分段错误的响应。例如,该程序可以收集堆栈跟踪信息,其中包含处理器寄存器值和分段错误中涉及的内存地址等信息。

segvcatch 就是一个例子,它是一个支持多个操作系统的 C++ 库,能够将分段错误和其他与硬件相关的异常转换为软件语言异常。这使得使用简单的 try/catch 代码处理“硬”错误成为可能,例如分段错误。这使得软件可以识别分段错误并在程序执行期间进行纠正。

SIGSEGV 故障排除

在对分段错误进行故障排除或测试程序以避免这些错误时,可能需要故意引发分段违规以调查其影响。大多数操作系统都可以以这样一种方式处理 SIGSEGV,即使发生分段错误,它们也允许程序运行,以便进行调查和记录。

排查 Kubernetes 中常见的分段故障

SIGSEGV 故障与 Kubernetes 用户和管理员高度相关。容器由于分段违规而失败是很常见的。

但是,与 SIGTERMSIGKILL 等其他信号不同,Kubernetes 不会直接触发 SIGSEGV 信号。相反,当容器被发现执行内存违规时,Kubernetes 节点上的主机可以触发 SIGSEGV。然后容器终止,Kubernetes 检测到这一点,并可能根据 pod 配置尝试重新启动它。

当 Docker 容器被 SIGSEGV 信号终止时,它会抛出退出码 139。这可以表明:

  • 容器上运行的其中一个库中的应用程序代码存在问题;
  • 容器上运行的不同库之间不兼容;
  • 这些库与主机上的硬件不兼容;
  • 主机内存管理系统或内存配置错误的问题。

要调试和解决容器上的 SIGSEGV 问题,请执行以下步骤:

  1. 获取主机的 root 访问权限,并查看日志以查看有关有问题的容器的其他信息。 SIGSEGV 错误在 kubelet 日志中如下所示:
    [signal SIGSEGV: segmentation violation code=0x1 addr=0x18 pc=0x1bdaed0] 
    
  2. 尝试确定错误发生在容器映像的哪一层 —— 它可能在您的特定应用程序代码中,或在容器更底层的基础映像中。
  3. 运行 docker pull [image-id] 为由 SIGSEGV 终止的容器拉取镜像。
  4. 确保您已安装或添加调试工具(例如 curlvim)。
  5. 使用 kubectl 执行到容器中。查看您是否可以复现 SIGSEGV 错误以确认导致问题的库。
  6. 如果您已确定导致内存违规的库,请尝试修改您的镜像以修复导致内存违规的库,或将其替换为另一个库。很多时候,更新一个库 到较新版本或与主机环境兼容的版本将解决此问题。
  7. 如果您无法识别始终导致错误的库,则问题可能出在主机上。检查主机内存配置或内存硬件是否存在问题。

上述过程可以帮助您解决直接的 SIGSEGV 错误,但在许多情况下,故障排除可能会变得非常复杂,并且需要涉及多个组件的非线性调查。