手动链接

Wednesday, Oct 9, 2024 | 5 minute read | Updated at Wednesday, Oct 9, 2024

@
 手动链接

通过解析ELF文件,实现ELF依赖库的加载

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libelf.h>
#include <gelf.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <link.h>
#include <err.h>
#include <dlfcn.h>

int main(int argc, char **argv)
{
    if (argc < 2)
    {
        errx(EXIT_FAILURE, "usage: %s file-name", argv[0]);
    }

    if (elf_version(EV_CURRENT) == EV_NONE)
    {
        errx(EXIT_FAILURE, "ELF library initialization failed: %s", elf_errmsg(-1));
    }

    const char *filename = argv[1];

    int fd = open(filename, O_RDONLY);
    if (fd < 0)
    {
        errx(EXIT_FAILURE, "open \"%s\" failed", filename);
    }

    Elf *elf = elf_begin(fd, ELF_C_READ, NULL);
    if (!elf)
    {
        close(fd);
        errx(EXIT_FAILURE, "elf_begin() failed: %s.", elf_errmsg(-1));
    }

    if (elf_kind(elf) != ELF_K_ELF)
    {
        elf_end(elf);
        close(fd);
        errx(EXIT_FAILURE, "\"%s\" is not an ELF object.", filename);
    }

    GElf_Ehdr ehdr;
    if (gelf_getehdr(elf, &ehdr) != &ehdr)
    {
        elf_end(elf);
        close(fd);
        err(EXIT_FAILURE, "gelf_getehdr failed");
    }

    void *base_addr = 0;
    GElf_Addr rela_addr = 0;
    size_t rela_size = 0;
    // GElf_Addr strtab_addr = 0;
    void *handle = NULL; // 目前就libc,先测试下

    Elf64_Sym *symtab = NULL; // 字符串偏移表
    const char *strtab = NULL;

    // 获取字符串表数据
    // Elf_Data *strtab_data = elf_getdata_rawchunk(elf, strtab_addr, 1024, ELF_T_BYTE); // 假定字符串表大小为 1024 字节
    // if (!strtab_data)
    // {
    //     elf_end(elf);
    //     close(fd);
    //     errx(EXIT_FAILURE, "elf_getdata_rawchunk() failed to get string table: %s\n", elf_errmsg(-1));
    // }

    for (int i = 0; i < ehdr.e_phnum; i++)
    {
        GElf_Phdr phdr;
        if (gelf_getphdr(elf, i, &phdr) != &phdr)
        {
            elf_end(elf);
            close(fd);
            errx(EXIT_FAILURE, "get phdr %d error", i);
        }

        if (phdr.p_type == PT_LOAD) // LOAD
        {
            /*
            Elf64_Off offset = phdr.p_offset & ~(phdr.p_align - 1);
            Elf64_Addr vaddr = phdr.p_vaddr & ~(phdr.p_align - 1);
            // Elf64_Xword filesz = phdr.p_filesz + (phdr.p_offset - offset);
            Elf64_Xword memsz = phdr.p_memsz + (phdr.p_vaddr - vaddr);

            int prot = 0;
            if (phdr.p_flags & PF_R)
                prot |= PROT_READ;
            if (phdr.p_flags & PF_W)
                prot |= PROT_WRITE;
            if (phdr.p_flags & PF_X)
                prot |= PROT_EXEC;

            void *addr = mmap((void *)vaddr, memsz, prot, MAP_PRIVATE | MAP_FIXED, fd, offset);
            if (addr == MAP_FAILED)
            {
                elf_end(elf);
                close(fd);
                err(EXIT_FAILURE, "mmap load error");
            }

            if (!base_addr)
            {
                base_addr = addr;
            }

            // 清零bss段(未初始化数据部分)
            if (!(prot & PROT_WRITE) && memsz > phdr.p_filesz)
            {
                memset((void *)(addr + phdr.p_paddr - offset + phdr.p_filesz), 0, memsz - phdr.p_filesz);
            }*/

            size_t align = phdr.p_align ? phdr.p_align : sysconf(_SC_PAGESIZE);
            // 文件偏移量对齐到页边界,向下取整
            off_t aligned_offset = phdr.p_offset & ~(align - 1);

            // 调整虚拟地址,对齐到页边界,向下取整
            uintptr_t aligned_vaddr = phdr.p_vaddr & ~(align - 1);

            // 计算实际需要映射的大小,向上取整
            size_t offset_adjustment = phdr.p_offset - aligned_offset;
            size_t segment_size = phdr.p_filesz + offset_adjustment;
            size_t aligned_segment_size = (segment_size + align - 1) & ~(align - 1);

            int prot = 0;
            if (phdr.p_flags & PF_R)
                prot |= PROT_READ;
            if (phdr.p_flags & PF_W)
                prot |= PROT_WRITE;
            if (phdr.p_flags & PF_X)
                prot |= PROT_EXEC;

            // 映射内存
            void *mapped_addr = mmap((void *)/*aligned_vaddr*/NULL, aligned_segment_size,
                                     prot, MAP_PRIVATE, fd, aligned_offset);
            if (mapped_addr == MAP_FAILED)
            {
                elf_end(elf);
                close(fd);
                err(EXIT_FAILURE, "mmap load error");
            }
            if (0 == base_addr)
            {
                base_addr = (GElf_Addr)mapped_addr + offset_adjustment;
            }

            // 清理段末尾的多余部分(BSS段)
            if (phdr.p_memsz > phdr.p_filesz)
            {
                uintptr_t bss_start = (uintptr_t)mapped_addr + phdr.p_filesz + offset_adjustment;
                size_t bss_size = phdr.p_memsz - phdr.p_filesz;
                memset((void *)bss_start, 0, bss_size);
            }

            // Clear the area after the file data up to the memory size
            // if (aligned_size < phdr.p_memsz) {
            //     size_t extra_size = phdr.p_memsz - aligned_size;
            //     memset((char *)seg_addr + aligned_size, 0, extra_size);
            // }
        }

        if (phdr.p_type == PT_DYNAMIC) // DYNAMIC
        {
            Elf_Data *data = elf_getdata_rawchunk(elf, phdr.p_offset, phdr.p_filesz, ELF_T_DYN);
            size_t num_entries = phdr.p_filesz / sizeof(Elf64_Dyn);

            for (size_t j = 0; j < num_entries; ++j)
            {
                GElf_Dyn dyn;
                if (!gelf_getdyn(data, j, &dyn))
                {
                    elf_end(elf);
                    close(fd);
                    errx(EXIT_FAILURE, "gelf_getdyn() failed: %s\n", elf_errmsg(-1));
                }

                if (dyn.d_tag == DT_STRTAB)
                {
                    strtab = (const char *)(base_addr + dyn.d_un.d_ptr);
                    // strtab = (const char *)(filename + dyn_entries[i].d_un.d_ptr);
                }

                if (dyn.d_tag == DT_SYMTAB)
                {
                    symtab = (Elf64_Sym *)(base_addr + dyn.d_un.d_ptr);
                }
            }

            for (size_t j = 0; j < num_entries; ++j)
            {
                GElf_Dyn dyn;
                if (!gelf_getdyn(data, j, &dyn))
                {
                    elf_end(elf);
                    close(fd);
                    errx(EXIT_FAILURE, "gelf_getdyn() failed: %s\n", elf_errmsg(-1));
                }

                if (dyn.d_tag == DT_NEEDED)
                {
                    const char *library_name = strtab + dyn.d_un.d_val;
                    if (*library_name == '\0')
                    {
                        continue;
                    }
                    printf("Needed library: %s\n", library_name);
                    handle = dlopen(library_name, RTLD_NOW);
                    if (!handle)
                    {
                        errx(EXIT_FAILURE, "Failed to load library: %s\n", library_name);
                    }
                    printf("Loaded library: %s\n", library_name);
                }
                else if (dyn.d_tag == DT_RELA)
                {
                    rela_addr = (GElf_Addr)(base_addr + dyn.d_un.d_ptr);
                }
                else if (dyn.d_tag == DT_RELASZ)
                {
                    rela_size = dyn.d_un.d_val;
                }
            }
        }
    }

    if (rela_addr == 0 || rela_size == 0)
    {
        elf_end(elf);
        close(fd);
        err(EXIT_FAILURE, "No DT_RELA or DT_RELASZ found");
    }

    printf("Found DT_RELA at 0x%lx with size %zu\n", rela_addr, rela_size);

    // 获取重定位表的起始地址
    Elf64_Rela *rela_entries = (Elf64_Rela *)(rela_addr);

    size_t num_relas = rela_size / sizeof(Elf64_Rela);

    // 处理每个重定位条目
    for (size_t i = 0; i < num_relas; ++i)
    {
        Elf64_Rela *rela = &rela_entries[i];
        // 应用重定位
        switch (ELF64_R_TYPE(rela->r_info))
        {
        case R_X86_64_RELATIVE:
        {
            void *reloc_addr = base_addr + rela->r_offset;
            *(uintptr_t *)reloc_addr = (uintptr_t)(base_addr + rela->r_addend);
            printf("Applied R_X86_64_RELATIVE relocation at %p: %lx\n",
                   reloc_addr, *(uintptr_t *)reloc_addr);
            break;
        }
        case R_X86_64_JUMP_SLOT: // S
        case R_X86_64_GLOB_DAT:
        {
            void *reloc_addr = base_addr + rela->r_offset;

            // *location =base_addr + rela->r_addend;
            // 这里我们假设重定位目标已经确定,例如目标函数地址
            // 通常情况下,你可能需要从符号表或其他地方获取目标函数地址

            Elf64_Sym *sym = &symtab[ELF64_R_SYM(rela->r_info)];
            const char *sym_name = strtab + sym->st_name;

            // 获取动态库的名称
            // Dl_info dlinfo;
            // if (dladdr((void *)sym->st_value, &dlinfo) && dlinfo.dli_fname) {
            //     printf("Symbol: %s, Library: %s\n", sym_name, dlinfo.dli_fname);
            // }

            void *symbol_addr = dlsym(handle, sym_name);
            if (!symbol_addr)
            {
                fprintf(stderr, "dlsym failed: %s\n", dlerror());
                exit(EXIT_FAILURE);
            }

            *(uintptr_t *)reloc_addr = (uintptr_t)symbol_addr;
            printf("Applied R_X86_64_JUMP_SLOT relocation at %p: %lx\n",
                   reloc_addr, *(uintptr_t *)reloc_addr);
            break;
        }
        default:
            break;
        }
    }

    Elf64_Addr entry_point = ehdr.e_entry;

    // 调用入口函数
    typedef void (*entry_func_t)(void);
    // 获取程序头表和入口地址
    entry_func_t entry_func = (entry_func_t)entry_point;
    entry_func();

    elf_end(elf);
    close(fd);

    return 0;
}

© 2016 - 2025 Caisong's Blog

🌱 Powered by Hugo with theme Dream.

About Me

大龄程序员,喜欢折腾各种环境部署、软件应用。

博客记录日常。