如何将文本文件的内容添加为 ELF 文件中的一个部分?

How do I add contents of text file as a section in an ELF file?

我有一个正在汇编和链接的 NASM 汇编文件(在 Intel-64 Linux 上)。

有一个文本文件,我希望文本文件的内容出现在生成的二进制文件中(基本上是一个字符串)。二进制文件是一个 ELF 可执行文件。

我的计划是在 ELF 文件中创建一个新的只读数据段(相当于常规的 .rodata 段)。

理想情况下,应该有一个工具可以将文件逐字添加为 elf 文件中的新部分,或者可以使用链接器选项逐字包含文件。

这可能吗?

使用 BINUTILS 中的 OBJCOPY 可以实现并且最容易完成。您有效地将数据文件作为二进制输入,然后将其输出为 object 文件格式,可以 link 编辑到您的程序中。

OBJCOPY 甚至会生成开始和结束符号以及数据区域的大小,以便您可以在代码中引用它们。基本思想是您要告诉它您的输入文件是二进制的(即使它是文本);您将以 x86-64 object 文件为目标;指定输入文件名和输出文件名。

假设我们有一个名为 myfile.txt 的输入文件,其内容为:

the
quick
brown
fox
jumps
over
the
lazy
dog

像这样的东西将是一个起点:

objcopy --input binary \
    --output elf64-x86-64 \
    --binary-architecture i386:x86-64 \
    myfile.txt myfile.o

如果您想生成 32 位 objects,您可以使用:

objcopy --input binary \
    --output elf32-i386 \
    --binary-architecture i386 \
    myfile.txt myfile.o

输出将是一个名为 myfile.o 的 object 文件。如果我们使用 OBJDUMP 和类似 objdump -x myfile.o 的命令查看 object 文件的 headers,我们会看到类似这样的内容:

myfile.o:     file format elf64-x86-64
myfile.o
architecture: i386:x86-64, flags 0x00000010:
HAS_SYMS
start address 0x0000000000000000

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .data         0000002c  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, DATA
SYMBOL TABLE:
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 g       .data  0000000000000000 _binary_myfile_txt_start
000000000000002c g       .data  0000000000000000 _binary_myfile_txt_end
000000000000002c g       *ABS*  0000000000000000 _binary_myfile_txt_size

默认情况下,它会创建一个包含文件内容的 .data 部分,并会创建一些可用于引用数据的符号。

_binary_myfile_txt_start
_binary_myfile_txt_end
_binary_myfile_txt_size

这实际上是从文件 myfile.txt 放入 object 的起始字节地址、结束字节地址和数据大小。 OBJCOPY 将使符号基于输入文件名。 myfile.txt 被分解为 myfile_txt 并用于创建符号。

一个问题是创建了一个 .data 部分,它是 read/write/data ,如下所示:

Idx Name          Size      VMA               LMA               File off  Algn
  0 .data         0000002c  0000000000000000  0000000000000000  00000040  2**0
                  CONTENTS, ALLOC, LOAD, DATA

您特别请求 .rodata 部分,该部分还指定了 READONLY 标志。您可以使用 --rename-section 选项将 .data 更改为 .rodata 并指定所需的标志。您可以将其添加到命令行:

--rename-section .data=.rodata,CONTENTS,ALLOC,LOAD,READONLY,DATA

当然,如果您想将该部分称为 .rodata 以外的其他名称,并且具有与只读部分相同的标志,您可以将上面一行中的 .rodata 更改为您想要使用的名称对于该部分。

应该生成您想要的 object 类型的命令的最终版本是:

objcopy --input binary \
    --output elf64-x86-64 \
    --binary-architecture i386:x86-64 \
    --rename-section .data=.rodata,CONTENTS,ALLOC,LOAD,READONLY,DATA \
    myfile.txt myfile.o

现在您有了一个 object 文件,您如何在 C 代码中使用它(作为示例)。生成的符号有点不正常,在OS Dev Wiki:

上有合理的解释

A common problem is getting garbage data when trying to use a value defined in a linker script. This is usually because they're dereferencing the symbol. A symbol defined in a linker script (e.g. _ebss = .;) is only a symbol, not a variable. If you access the symbol using extern uint32_t _ebss; and then try to use _ebss the code will try to read a 32-bit integer from the address indicated by _ebss.

The solution to this is to take the address of _ebss either by using it as &_ebss or by defining it as an unsized array (extern char _ebss[];) and casting to an integer. (The array notation prevents accidental reads from _ebss as arrays must be explicitly dereferenced)

牢记这一点,我们可以创建名为 main.c:

C 文件
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>

/* These are external references to the symbols created by OBJCOPY */
extern char _binary_myfile_txt_start[];
extern char _binary_myfile_txt_end[];
extern char _binary_myfile_txt_size[];

int main()
{
    char *data_start     = _binary_myfile_txt_start;
    char *data_end       = _binary_myfile_txt_end;
    size_t data_size  = (size_t)_binary_myfile_txt_size;

    /* Print out the pointers and size */
    printf ("data_start %p\n", data_start);
    printf ("data_end   %p\n", data_end);
    printf ("data_size  %zu\n", data_size);

    /* Print out each byte until we reach the end */
    while (data_start < data_end)
        printf ("%c", *data_start++);

    return 0;
}

您可以编译并 link 使用:

gcc -O3 main.c myfile.o

输出应该类似于:

data_start 0x4006a2
data_end   0x4006ce
data_size  44
the
quick
brown
fox
jumps
over
the
lazy
dog

A NASM 用法示例在本质上类似于 C 代码。以下名为 nmain.asm 的汇编程序使用 Linux x86-64 System Calls 将相同的字符串写入标准输出:

bits 64
global _start

extern _binary_myfile_txt_start
extern _binary_myfile_txt_end
extern _binary_myfile_txt_size

section .text

_start:
    mov eax, 1                        ; SYS_Write system call
    mov edi, eax                      ; Standard output FD = 1
    mov rsi, _binary_myfile_txt_start ; Address to start of string
    mov rdx, _binary_myfile_txt_size  ; Length of string
    syscall

    xor edi, edi                      ; Return value = 0
    mov eax, 60                       ; SYS_Exit system call
    syscall

这可以组装和 link编辑:

nasm -f elf64 -o nmain.o nmain.asm
gcc -m64 -nostdlib nmain.o myfile.o

输出应显示为:

the
quick
brown
fox
jumps
over
the
lazy
dog