可执行文件不可避免
我们希望能有简单的 flat 汇编器, 直接编译出来 code + data, 并且用相对当前位置的转跳 offset, 来处理其中指针的相互引用。
但是这是不可能的,原因有二。
首先,现代操作系统要求 code segment 与 data segment 分别 mmap, 因为 code segment 与 data segment 的权限不同, code segment 部分被强制要求是不可写的, 而 data segment 被要求是可写的。 可以使用一个连续的 buffer 来同时保存 code segment 与 data segment, 但是要知道二者的边界才能分别设置权限, 因此,可执行文件中至少要包含这个边界信息。
其次,除了 code segment 与 data segment 之间的相互引用, 还有 code 或 data 到 c 函数的引用,这些函数在加载时才能知道地。 因此可执行文件中至少要包含 relocation table。
可执行文件的设计
// - why: every field is 8 bytes,
// because arm and risc-v require 8 bytes alignment.
// - why: the size of code + data + space segment must be less than 2G,
// so that relative addressing can work.
struct __attribute__((packed)) xexe_header_t {
uint8_t magic[8]; // "xexe\0\0\0\0" magic number
uint8_t machine[8]; // "x86-64\0\0"
uint64_t version;
uint64_t code_file_offset;
uint64_t code_size;
uint64_t entry_code_segment_offset;
uint64_t data_file_offset;
uint64_t data_size;
uint64_t space_size;
uint64_t string_table_file_offset;
uint64_t string_table_size;
uint64_t label_table_file_offset;
uint64_t label_table_size;
uint64_t relocation_table_file_offset;
uint64_t relocation_table_size;
};固定的三个 segment:
- code segment -- 保存可执代码。
- data segment -- 保存编译时数据。
- space segment -- 为运行时数据预留空间,可执行文件中只需要记录 size。
string table -- 记录可执行文件中出现的所有字符串。 一段连续的区域,保存 \0 结尾的 c string。
// - note: string in string table must be unique,
// so that we can compare string by pointer.
typedef uint64_t xexe_string_t; // offset to string tablelabel table
保存 code、data、space 三个 segment 之中,label 到地址的映射。
typedef uint64_t xexe_segment_kind_t;
#define XEXE_CODE_SEGMENT 0
#define XEXE_DATA_SEGMENT 1
#define XEXE_SPACE_SEGMENT 2
struct __attribute__((packed)) xexe_label_entry_t {
xexe_string_t name;
xexe_segment_kind_t segment_kind;
uint64_t segment_offset;
};relocation table
记录信息以明确在 load 时如何把数据回填到 segment 中留空的位置。
struct __attribute__((packed)) xexe_relocation_entry_t {
xexe_string_t type;
xexe_string_t name;
xexe_segment_kind_t segment_kind;
uint64_t segment_offset;
};type 和 name 决定了如何在 load 过程中获得数据以回填到 segment 中。
汇编语言
汇编语言在于控制。
通过 exe 格式,来控制 loader 的行为,也是控制中的一部分。
通过 (relocation <type> <name>) 来创建 xexe_relocation_entry_t。