You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自制无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

问题原因分析

  1. 仅加载单个PT_LOAD段:
    sum程序的全局数组A和变量sum存储在独立的PT_LOAD段中,当前加载器只查找并加载了包含_start入口点的PT_LOAD段,未处理其他PT_LOAD段。当_start尝试访问全局变量时,对应内存未被映射,直接触发段错误。fib程序无全局变量,所有代码和数据都在同一个PT_LOAD段,因此能正常运行。

  2. 程序头表(phdr)读取逻辑错误:
    加载器已通过read(fd, phdr, ehdr->e_phnum * sizeof(Elf32_Phdr))读取所有程序头到phdr数组,但后续循环中又执行read(fd, phdr, sizeof(Elf32_Phdr)),会覆盖phdr数组的起始元素,导致程序头数据混乱。虽当前逻辑找到了入口所在段,但后续依赖其他段数据时会出错。

  3. 虚拟地址计算的潜在漏洞:
    计算入口点地址时使用phdr->p_vaddr(等价于phdr[0].p_vaddr),而非phdr[index].p_vaddr。本次因入口点在段起始地址(偏移为0)未暴露问题,但属于逻辑错误。

修复方案

  1. 加载所有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);
    // 记录段的虚拟地址与映射内存的对应关系,用于地址修正(若需重定位)
}
  1. 修正程序头读取逻辑:
    删除循环中重复读取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) { /* 错误处理:未找到入口所在段 */ }
  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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 12:12:32