Linux中的分段错误
1. 简介
理解错误消息的含义是任何 Linux 管理员的基本技能。虽然“没有这样的文件或目录”或“权限被拒绝”等一些错误的含义很清楚,但其他错误可能听起来有点神秘。可怕的“分段错误”错误就是这种情况。
在本教程中,我们将了解它是什么,导致它的原因,以及如何对产生这种错误的代码进行故障排除。
2. 什么是分段错误?
简而言之,分段错误是指由于进程尝试访问它不应该访问的内存区域而导致的错误。当内核检测到奇怪的内存访问行为时,它会终止发出分段违规信号 (SIGSEGV)的进程。
较低级别的语言,包括C (包括 Linux 在内的 Unix 系统的基础语言)通常在内存使用和分配方面具有很大的灵活性。因此,它们将许多内存分配控制方面留给开发人员自行决定。
虽然它会导致更简单并且有望更快的二进制编译代码,但它更容易出现编程错误和内存使用疏忽。
3. 什么导致分段错误?
在 Linux 中,分段错误可能发生在以下情况:
- Segment Violation Mapping Error (SEGV_MAPERR):访问应用程序地址空间之外的内存
- Segment Violation Access Error (SEGV_ACCERR):访问应用程序没有权限的内存或试图在只读内存空间上写入
乍一看,似乎只有明显的错误才会导致这些情况。然而,事实并非如此。
诸如取消引用空指针、未初始化或已释放指针(引用内存区域的变量)、缓冲区或堆栈溢出等错误可能发生在非常常见的编程错误之后。
例如,调用具有不正确或未初始化的指针作为引用参数的函数,或具有失败停止条件的递归函数可能会导致分段错误。
4. 例子
让我们看一个非常简单的代码片段,它会产生分段违规:
void main (void) {
char *buffer; /* Non initialized buffer */
buffer[0] = 0; /* Trying to assign 0 to its first position will cause a segmentation fault */
}
我们可以编译并运行它:
$ ulimit -S -c unlimited
$ gcc -o seg_fault -ggdb seg_fault.c
$ ./seg_fault
Segmentation fault (core dumped)
ulimit 命令可以在出错时生成进程的内存转储。使用gcc 完成编译,编译时的–ggdb选项将在生成的二进制文件中插入调试信息。
此外,我们启用了调试信息和核心转储,因此我们可以查看错误发生的位置:
$ gdb ./seg_fault /var/crash/core.seg_fault
... <snip> ...
Reading symbols from ./seg_fault...
[New LWP 6291]
Core was generated by `./seg_fault'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x000055ea4064c135 in main () at seg_fault.c:4
4 buffer[0] = 0;
但是,如果我们不必调试代码中的信息,我们仍然会得到一些有用的信息,例如出错的函数名。
5. 如何防止分段错误?
在使用指针、引用和内存数组进行编程时,我们必须确保所有内存访问都在正确的边界内,并且符合当前的访问限制。
更具体地说,我们必须仔细检查以下内容:
- 动态内存分配
- 通过指针间接访问内存
- 值高于其当前分配大小的数组索引
- 整个代码和函数参数调用约定的类型一致性
- 字符串和缓冲区操作
- 指针和缓冲区分配(注意未分配的指针)
- 递归函数中的停止条件
此外,这些技巧不仅对提高代码的健壮性很重要,而且对安全性也很重要。此外,其中一些缺陷可能会打开我们的代码以攻击恶意代码插入或拒绝服务利用等攻击媒介。