可执行文件中全局const变量的偏移量

时间:2012-09-23 10:45:56

标签: c linker executable linker-scripts

希望有一个可执行文件通过修改自己的全局常量来保存其状态。只是为了获得一个完全独立的可执行文件。

想到一些解决方案/黑客:

  1. 使用libelf并让程序自行解析以找到偏移量。
  2. 添加特定标记,然后在可执行文件中搜索它。我想这甚至可能有点跨平台?
  3. 使用对象转储工具确定可执行文件中的地址。这可能需要始终作为项目构建的后期处理来完成..
  4. 让链接器提供此信息会很简洁。

    是否可以让链接器提供可执行文件中只读部分的偏移量?

    由于

3 个答案:

答案 0 :(得分:6)

你想要做的是棘手且不可移植。

然而,您可以研究GNU emacs unexec功能,例如在src/unexelf.c中(对于Linux;其他操作系统有类似的文件)。

您还可以播放链接技巧,例如使用您自己的ld脚本。

请注意,这些技巧可能是特定于处理器,编译器,内核和libc的版本。

也许你想要application checkpointing。特别是,BLCR可能对您有用。

答案 1 :(得分:5)

你实际上是在谈论二进制重写。在不摆弄编译过程的情况下实现此目的的一种方法是将虚拟地址映射到物理地址然后对其进行修补。有趣的是,这是我在master's thesis中介绍的内容。从该文档中提取以下图像和文本:

http://localhostr.com/file/hyB1iFuiL0nV/Loading_Binary.jpg

请注意,我原始项目背后的概念是在其他二进制文件中重写代码,假设编译过程无法修改。如果您的要求和假设不同,这可能不是最简单和最好的方法。

这里最重要的一点是磁盘表示中的一个部分在映射到内存时会被保留(不分割)。这意味着在加载到内存中后,磁盘表示中某个部分偏移的数据将偏移相同的数量。

libelf中,与libbfd类似,可执行文件包含一组代码和数据都可以驻留的部分。当操作系统将可执行文件加载到内存中时,每个部分都基于某个基址。我们可以将其反转以将虚拟内存地址映射到物理文件偏移量。如果可以找到物理文件偏移量,则可以将这些字节修补为常规文件。

  • 首先,使用libelf解析可执行文件的节头。 这允许我们获得一组部分,最重要的是,为 每个部分libelf可以告诉我们三件事:
    1. 部分大小部分的大小。
    2. 部分基地址当磁盘上的可执行文件加载到内存中时该部分所基于的地址。
    3. 部分磁盘偏移量部分的磁盘偏移量。
  • 通过迭代上一步中提取的部分信息,可以找出包含任意虚拟内存地址的部分。在修补期间,我们感兴趣的内存地址是代码的地址。绕道而行。可以通过(virtual_memory_address - section_base_address)计算虚拟内存地址到该部分的偏移量。
  • 因此,虚拟内存地址的磁盘偏移量可以通过(section_disk_offset + (virtual_memory_address - section_base_address))计算。

此过程允许将任意虚拟内存地址映射到其相应的磁盘文件偏移量。然后可以使用常规C文件IO函数修补此偏移量,例如fopen / fseek / fwrite / fclose

这是我使用上述步骤将虚拟地址映射到物理文件偏移的代码:

/*
 * Returns the corresponding 32 bit executable file offset of a virtual memory
 * address.
 */
uint32_t vaddr32_to_file_offset(char * filepath, uint32_t vaddr)
{
    int      fd     = open(filepath, O_RDONLY);
    Elf *    e      = elf_begin(fd, ELF_C_READ, NULL);
    uint32_t offset = 0;

    Elf_Scn * scn = NULL;
    while((scn = elf_nextscn(e, scn)) != NULL) {
        Elf32_Shdr * shdr = elf32_getshdr(scn);
        if(vaddr >= shdr->sh_addr &&
                (vaddr <= (shdr->sh_addr + shdr->sh_size))) {
            offset = shdr->sh_offset + (vaddr - shdr->sh_addr);
            break;
        }
    }

    elf_end(e);
    close(fd);
    return offset;
}

/*
 * Returns the corresponding 64 bit executable file offset of a virtual memory
 * address.
 */
uint64_t vaddr64_to_file_offset(char * filepath, uint64_t vaddr)
{
    int      fd     = open(filepath, O_RDONLY);
    Elf *    e      = elf_begin(fd, ELF_C_READ, NULL);
    uint64_t offset = 0;

    Elf_Scn * scn = NULL;
    while((scn = elf_nextscn(e, scn)) != NULL) {
        Elf64_Shdr * shdr = elf64_getshdr(scn);
        if(vaddr >= shdr->sh_addr &&
                (vaddr <= (shdr->sh_addr + shdr->sh_size))) {
            offset = shdr->sh_offset + (vaddr - shdr->sh_addr);
            break;
        }
    }

    elf_end(e);
    close(fd);
    return offset;
}

这是在给定偏移量的情况下修补ELF可执行文件的代码:

/*
 * Sets the bytes at an arbitrary offset of a file to the contents of buffer.
 */
static bool patch_file(char * filepath, uint64_t offset, void * buffer,
        size_t size)
{
    FILE * pFile = fopen(filepath, "r+");

    if(pFile == NULL) {
        return FALSE;
    }

    fseek(pFile, offset, SEEK_SET);
    fwrite(buffer, 1, size, pFile);
    fclose(pFile);
    return TRUE;
}

可以在报告中找到更详细的信息,这些信息是公开的here

答案 2 :(得分:4)

这是不可能的,因为编译器通常会将全局和静态常量替换为机器码中的immediates值(如果可能)。例如:

const int x=3;

int main()
{
 return x;
}

为main()(OSX / gcc -O3)生成此代码:

_main:
Leh_func_begin1:
    pushq   %rbp
Ltmp0:
    movq    %rsp, %rbp
Ltmp1:
    movl    $3, %eax // <= immediate constant!
    popq    %rbp
    ret