sigill是进程执行非法指令时触发的信号,如损坏二进制、未启用cpu扩展指令或jit生成非法码;需用sigaction注册处理器,禁用非异步安全函数,不可恢复执行;windows对应exception_illegal_instruction,通过seh处理。

SIGILL 是程序执行了 CPU 不认识或当前权限不允许的指令时触发的信号,比如尝试运行损坏的二进制代码、使用未启用的 CPU 扩展指令(如 AVX-512 但内核未开启)、或 JIT 编译器生成非法机器码。它不能靠 try/catch 捕获,必须用系统级信号机制处理。
用 sigaction 注册 SIGILL 处理器(推荐)
signal() 虽简单,但行为不可靠(例如某些平台会重置 handler、不屏蔽递归信号),sigaction 才是生产环境唯一稳妥选择。关键点在于:设置 SA_RESETHAND 防止 handler 被自动重置;用 sa_mask 屏蔽其他信号避免干扰;不调用非异步信号安全函数(如 std::cout、malloc)。
- 必须用
sigemptyset(&act.sa_mask)初始化信号集,否则行为未定义 -
act.sa_flags = SA_RESETHAND | SA_NODEFER可防止 handler 被重复触发导致死循环 - 处理函数里只做最简操作:写入固定缓冲区、调用
write()、_exit(),绝不能用printf或抛 C++ 异常 - 示例中
write(2, "SIGILL caught\n", 14)是安全的,std::cerr 不是
SIGILL 常见触发场景与验证方式
不是所有 SIGILL 都来自 bug —— 它可能暴露底层兼容性问题。验证时别依赖“随便写个非法指令”,要模拟真实路径:
- 在不支持 AVX 的机器上运行含
vaddps的汇编片段(需__builtin_ia32_addps等 intrinsic) - 用
asm volatile ("ud2")主动触发(x86-64 的“未定义指令”陷阱) - 加载被截断或符号损坏的动态库(
dlopen后调用函数指针) - JIT 场景下生成未对齐的跳转地址或非法编码指令
注意:raise(SIGILL) 可用于测试 handler 是否注册成功,但它不等价于真实硬件异常 —— 缺少寄存器上下文,无法做栈回溯。
为什么不能在 SIGILL handler 里恢复执行
和 SIGSEGV 不同,SIGILL 几乎总是表示指令流已不可信。即使你用 setjmp/longjmp 尝试跳回,CPU 状态(EIP/RIP、标志寄存器)可能已损坏,继续执行大概率引发二次崩溃或静默数据错误。
- POSIX 明确不保证
SIGILL可安全恢复;Linux 内核也从未提供可靠恢复路径 - 某些嵌入式平台(ARM Cortex-M)允许从
HardFault恢复,但那是硬件异常,不是 POSIXSIGILL - 真正需要“容错执行”的场景(如解释器),应在指令解码/发射前做合法性检查,而非依赖信号兜底
Windows 上没有 SIGILL,但有对应机制
Windows 不用 POSIX 信号,而是通过结构化异常处理(SEH)捕获类似事件。对应的是 EXCEPTION_ILLEGAL_INSTRUCTION(代码 0xc000001d)。它和 SIGILL 语义一致,但触发条件更细粒度(例如访问 NX 页 + 执行位未设也会触发此异常)。
- 必须用
SetUnhandledExceptionFilter注册顶层过滤器 - 不能仅靠
set_terminate或 CRT 信号(signal(SIGILL, ...)在 Windows 上无效) - 获取崩溃上下文需调用
MiniDumpWriteDump,而非 Linux 的backtrace() - 跨平台项目建议封装一层:Linux 用
sigaction,Windows 用 SEH,统一入口转发到日志/dump 逻辑
真正棘手的是混合环境:比如 WSL2 下跑 Linux 二进制,但宿主 Windows 触发了硬件异常——此时信号可能被截断或延迟,SIGILL handler 未必能及时响应。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











