自制无glibc的32位ELF加载器:sum程序段错误排查求助
32位ELF简易加载器段错误排查
问题背景
实现了一个不依赖glibc API的32位ELF文件简易加载器,该加载器可正常运行计算斐波那契数的fib程序,但运行数组求和sum程序时触发段错误,请求排查原因。
相关代码与输出
Makefile
all: gcc -g -m32 -no-pie -nostdlib -o fib fib.c gcc -g -m32 -no-pie -nostdlib -o sum sum.c gcc -g -m32 -o loader loader.c clean: -@rm -f fib loader sum
测试代码
fib.c
int fib(int n) { if(n<2) return n; else return fib(n-1)+fib(n-2); } int _start() { int val = fib(40); return val; }
sum.c
#define SIZE 1024 int __attribute__((aligned(4096))) A[SIZE] = {0}; int sum = 0; int _start() { for (int i = 0; i < SIZE; i++) A[i] = 2; for (int i = 0; i < SIZE; i++) sum += A[i]; return sum; }
加载器核心代码
void load_and_run_elf(char **exe) { char *filename = exe[1]; printf("file name : %s\n", filename); fd = open(filename, O_RDONLY); printf("fd : %d\n", fd); off_t size = lseek(fd, 0, SEEK_END); if (size < 0) { printf("error\n"); exit(1); } lseek(fd, 0, SEEK_SET); ehdr = (Elf32_Ehdr *)malloc(sizeof(Elf32_Ehdr)); if (ehdr == NULL) { printf("ERROR : memory not allocated to ehdr\n"); } read(fd, ehdr, sizeof(Elf32_Ehdr)); if (check_elf_header() != 1) { printf("error invalid format\n"); exit(1); } check_elf_class(); lseek(fd, 0, SEEK_SET); phdr = (Elf32_Phdr *)malloc(sizeof(Elf32_Phdr) * ehdr->e_phnum); lseek(fd, ehdr->e_phoff, SEEK_SET); read(fd, phdr, ehdr->e_phnum * sizeof(Elf32_Phdr)); int index; for (int i = 0; i < ehdr->e_phnum; i++) { lseek(fd, ehdr->e_phoff + i * sizeof(Elf32_Phdr), SEEK_SET); read(fd, phdr, sizeof(Elf32_Phdr)); if ((phdr[i].p_type == PT_LOAD) && (ehdr->e_entry >= phdr[i].p_vaddr) && (ehdr->e_entry <= (phdr[i].p_vaddr + phdr[i].p_memsz))) { index = i; break; } lseek(fd, 0, SEEK_SET); } void *virtual_memory = mmap(NULL, phdr[index].p_memsz, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (virtual_memory == MAP_FAILED) { printf("ERROR : memory not mapped\n"); exit(1); } lseek(fd, phdr[index].p_offset, SEEK_SET); read(fd, virtual_memory, phdr[index].p_memsz); printf("reached line 65\n"); void *virtual_memory_copy = virtual_memory; virtual_memory_copy = (void *)((uint8_t *)virtual_memory_copy + ehdr->e_entry - phdr->p_vaddr); printf("reached line 69\n"); int (*function)() = (int (*)())virtual_memory_copy; // _start(); printf("%p\n",function); int result = function(); printf("131\n"); printf("entry point : %p\n", virtual_memory_copy); if (munmap(virtual_memory, phdr[index].p_memsz) != 0) { printf("error memory not unmaped\n"); exit(1); } printf("User _start return value = %d\n", result); }
sum程序运行错误输出
file name : ./sum fd : 3 reached line 65 reached line 69 0xf7eca000 Segmentation fault (core dumped)
gdb调试输出
96 for (int i = 0; i < ehdr->e_phnum; i++) (gdb) next 98 lseek(fd, ehdr->e_phoff + i * sizeof(Elf32_Phdr), SEEK_SET); (gdb) next 99 read(fd, phdr, sizeof(Elf32_Phdr)); (gdb) next 100 if ((phdr[i].p_type == PT_LOAD) && (gdb) next 101 (ehdr->e_entry >= phdr[i].p_vaddr) && (gdb) next 100 if ((phdr[i].p_type == PT_LOAD) && (gdb) next 102 (ehdr->e_entry <= (phdr[i].p_vaddr + phdr[i].p_memsz))) (gdb) next 101 (ehdr->e_entry >= phdr[i].p_vaddr) && (gdb) next 104 index = i; (gdb) next 105 break; (gdb) prit ehdr Undefined command: "prit". Try "help". (gdb) print ehdr $3 = (Elf32_Ehdr *) 0x5655a5b0 (gdb) print phdr $4 = (Elf32_Phdr *) 0x5655a5f0 (gdb) print *ehdr $5 = {e_ident = "\177ELF\001\001\001\000\000\000\000\000\000\000\000", e_type = 2, e_machine = 3, e_version = 1, e_entry = 134516736, e_phoff = 52, e_shoff = 13500, e_flags = 0, e_ehsize = 52, e_phentsize = 32, e_phnum = 7, e_shentsize = 40, e_shnum = 17, e_shstrndx = 16} (gdb) print *phdr $6 = {p_type = 1, p_offset = 4096, p_vaddr = 134516736, p_paddr = 134516736, p_filesz = 110, p_memsz = 110, p_flags = 5, p_align = 4096} (gdb) next 109 void *virtual_memory = mmap(NULL, phdr[index].p_memsz, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); (gdb) next 110 if (virtual_memory == MAP_FAILED) (gdb) next 115 lseek(fd, phdr[index].p_offset, SEEK_SET); (gdb) next 116 read(fd, virtual_memory, phdr[index].p_memsz); (gdb) next 117 printf("reached line 65\n"); (gdb) print phdr[index].p_memsz $7 = 110 (gdb) prit sizeof(virtual_memory) Undefined command: "prit". Try "help". (gdb) print sizeof(virtual_memory) $8 = 4 (gdb) next reached line 65 118 void *virtual_memory_copy = virtual_memory; (gdb) next 119 virtual_memory_copy = (void *)((uint8_t *)virtual_memory_copy + ehdr->e_entry - phdr->p_vaddr); (gdb) print virtual_memory-virtual_memory_copy $9 = 0 (gdb) next 120 printf("reached line 69\n"); (gdb) next reached line 69 121 int (*function)() = (int (*)())virtual_memory_copy; // _start(); (gdb) next 122 printf("%p\n",function); (gdb) next 0xf7fbd000 123 int result = function(); (gdb) next 0xf7fbd000 in ?? () (gdb) next Cannot find bounds of current function
另一次调试尝试输出
Breakpoint 1, main (argc=2, argv=0xffffd1d4) at loader.c:136 136 if (argc != 2) (gdb) next 143 load_and_run_elf((argv)); (gdb) next file name : ./sum fd : 3 reached line 65 reached line 69 0xf7fbd000 Breakpoint 2, load_and_run_elf (exe=0xffffd1d4) at loader.c:123 123 int result = function(); (gdb) step Program received signal SIGSEGV, Segmentation fault. 0xf7fbd01c in ?? () (gdb) step
问题原因分析
仅加载单个PT_LOAD段:
sum程序的全局数组A和变量sum存储在独立的PT_LOAD段中,当前加载器只查找并加载了包含_start入口点的PT_LOAD段,未处理其他PT_LOAD段。当_start尝试访问全局变量时,对应内存未被映射,直接触发段错误。fib程序无全局变量,所有代码和数据都在同一个PT_LOAD段,因此能正常运行。程序头表(phdr)读取逻辑错误:
加载器已通过read(fd, phdr, ehdr->e_phnum * sizeof(Elf32_Phdr))读取所有程序头到phdr数组,但后续循环中又执行read(fd, phdr, sizeof(Elf32_Phdr)),会覆盖phdr数组的起始元素,导致程序头数据混乱。虽当前逻辑找到了入口所在段,但后续依赖其他段数据时会出错。虚拟地址计算的潜在漏洞:
计算入口点地址时使用phdr->p_vaddr(等价于phdr[0].p_vaddr),而非phdr[index].p_vaddr。本次因入口点在段起始地址(偏移为0)未暴露问题,但属于逻辑错误。
修复方案
- 加载所有PT_LOAD段:
遍历所有程序头,对每个PT_LOAD段单独进行内存映射并读取文件内容。需注意段间虚拟地址偏移,确保全局变量访问地址正确。示例逻辑:
for (int i = 0; i < ehdr->e_phnum; i++) { if (phdr[i].p_type != PT_LOAD) continue; // 为该段映射内存 void *seg_mem = mmap(NULL, phdr[i].p_memsz, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (seg_mem == MAP_FAILED) { /* 错误处理 */ } // 读取段内容 lseek(fd, phdr[i].p_offset, SEEK_SET); read(fd, seg_mem, phdr[i].p_filesz); // 记录段的虚拟地址与映射内存的对应关系,用于地址修正(若需重定位) }
- 修正程序头读取逻辑:
删除循环中重复读取phdr的代码,直接使用已读取的phdr数组元素:
int index = -1; for (int i = 0; i < ehdr->e_phnum; i++) { if ((phdr[i].p_type == PT_LOAD) && (ehdr->e_entry >= phdr[i].p_vaddr) && (ehdr->e_entry <= (phdr[i].p_vaddr + phdr[i].p_memsz))) { index = i; break; } } if (index == -1) { /* 错误处理:未找到入口所在段 */ }
- 修正入口地址计算:
将phdr->p_vaddr改为phdr[index].p_vaddr,确保偏移计算正确:
virtual_memory_copy = (void *)((uint8_t *)virtual_memory_copy + ehdr->e_entry - phdr[index].p_vaddr);
内容的提问来源于stack exchange,提问作者Harsh Rawat
相关产品推荐
相关产品推荐

