静态全局指针赋值触发段错误,求故障排查与解决方法
简易malloc实现中
list_head = header处崩溃的问题分析与解决 问题描述
实现简易malloc时,代码执行到list_head = header处触发段错误。通过GDB调试发现:
- 指针
header能正确获取base的值 - 对
header成员的赋值操作均正常 - 仅在给静态全局指针
list_head赋值时崩溃
疑问:若header指向的地址不可访问,程序应在header->size = heap_size处崩溃,而非全局指针赋值阶段。同时需要确认Makefile是否存在问题。
相关代码片段
heap_header结构体定义
typedef struct _heap_header { enum{ HEAP_BLOCK_FREE = 0xABABABAB, HEAP_BLOCK_USED = 0xCDCDCDCD, } type; unsigned size; struct _heap_header *next; struct _heap_header *prev; }heap_header;
自定义brk实现(原错误版本)
//Linux brk system call static int brk(void* end_data_segment) { int ret = 0; asm("movl $45, %%eax \n\t" "movl %1 , %%ebx \n\t" "int $0x80 \n\t" "movl %%eax, %0 \n\t" : "=m"(ret) : "m"(end_data_segment) ); return ret; }
堆初始化函数
#define ADDR_ADD(a, o) (((char*)(a)) + o) #define HEADER_SIZE (sizeof(heap_header)) static heap_header* list_head = NULL; int mini_crt_heap_init() { void* base = NULL; heap_header* header = NULL; unsigned heap_size = 1024 * 1024 * 32; base = (void*)brk(0); void* end = ADDR_ADD(base, heap_size); end = (void*)brk(end); if(!end) return 0; header = (heap_header*)base; header->size = heap_size; header->type = HEAP_BLOCK_FREE; header->next = NULL; header->prev = NULL; list_head = header; return 1; }
Makefile内容
STATIC_SRC = malloc.c stdio.c string.c STATIC_OBJ = $(patsubst %.c,%.o,$(STATIC_SRC)) CFLAGS = -c -fno-builtin -nostdlib -fno-stack-protector -m32 static: gcc -g $(CFLAGS) $(STATIC_SRC) entry.c ar -rs minicrt.a $(STATIC_OBJ) test: gcc -g $(CFLAGS) test.c ld -static -melf_i386 -e mini_crt_entry entry.o test.o minicrt.a -o test all: static test clean: rm -f *.o *.a test core*
原因分析
自定义brk函数破坏寄存器状态
在32位Linux调用约定中,ebx是被调用者保存寄存器,函数修改该寄存器前必须保存并恢复。原brk实现直接修改ebx但未做保存,导致编译器后续使用ebx时出现寄存器状态混乱,进而引发不可预测的内存访问错误——这就是为什么前序header成员赋值正常,而全局指针赋值阶段崩溃的核心原因。brk返回值处理错误
Linux的brk系统调用:- 调用
brk(0)时,返回当前程序的堆起始地址(program break) - 调用
brk(addr)扩展堆时,成功返回之前的堆起始地址,失败返回-1
原实现错误地将返回值直接转为int返回,导致堆扩展成功时被误判为失败(返回0时被当作NULL),同时无法正确识别调用失败的情况。
- 调用
解决方案
1. 修复自定义brk函数
// Linux brk system call (32-bit) static void* brk(void* end_data_segment) { unsigned long ret; // 保存并恢复ebx寄存器,避免破坏调用者状态 asm volatile("pushl %%ebx\n\t" "movl $45, %%eax\n\t" "movl %1, %%ebx\n\t" "int $0x80\n\t" "movl %%eax, %0\n\t" "popl %%ebx\n\t" : "=r"(ret) : "r"(end_data_segment) : "eax"); // 系统调用失败时返回NULL if (ret == (unsigned long)-1) { return NULL; } return (void*)ret; }
2. 修正堆初始化函数的返回值判断
int mini_crt_heap_init() { void* base = NULL; heap_header* header = NULL; unsigned heap_size = 1024 * 1024 * 32; // 获取当前堆起始地址 base = brk(0); if (base == NULL) { return 0; } void* end = ADDR_ADD(base, heap_size); // 扩展堆到指定地址,验证是否成功 void* prev_brk = brk(end); if (prev_brk == NULL || prev_brk != base) { return 0; } header = (heap_header*)base; header->size = heap_size; header->type = HEAP_BLOCK_FREE; header->next = NULL; header->prev = NULL; list_head = header; return 1; }
3. Makefile优化(可选)
在test目标的链接命令中添加-Map test.map参数,生成链接映射文件,可查看各个段的地址分布,确认静态全局变量所在的.data/.bss段为可写状态,且堆起始地址位于该段之后:
test: gcc -g $(CFLAGS) test.c ld -static -melf_i386 -e mini_crt_entry -Map test.map entry.o test.o minicrt.a -o test
内容的提问来源于stack exchange,提问作者Ete_Bosh
相关产品推荐
相关产品推荐

