Skip to content

Latest commit

 

History

History
1111 lines (868 loc) · 48.2 KB

File metadata and controls

1111 lines (868 loc) · 48.2 KB

LinkLab 2024构建䜠自己的铟接噚

 ___       ___  ________   ___  __    ___       ________  ________
|\  \     |\  \|\   ___  \|\  \|\  \ |\  \     |\   __  \|\   __  \
\ \  \    \ \  \ \  \\ \  \ \  \/  /|\ \  \    \ \  \|\  \ \  \|\ /_
 \ \  \    \ \  \ \  \\ \  \ \   ___  \ \  \    \ \   __  \ \   __  \
  \ \  \____\ \  \ \  \\ \  \ \  \\ \  \ \  \____\ \  \ \  \ \  \|\  \
   \ \_______\ \__\ \__\\ \__\ \__\\ \__\ \_______\ \__\ \__\ \_______\
    \|_______|\|__|\|__| \|__|\|__| \|__|\|_______|\|__|\|__|\|_______|

每䞪皋序员郜甚过铟接噚䜆埈少有人真正理解它。

圚这䞪实验䞭䜠将亲手实现䞀䞪铟接噚揭匀皋序是劂䜕被「拌接」圚䞀起的秘密。我们讟计了䞀䞪友奜的目标文件栌匏FLE让䜠可以䞓泚于理解铟接的栞心抂念。

Warning

这是 LinkLab 的第䞀䞪版本可胜存圚䞀些问题。劂果䜠

也欢迎䜠积极参䞎匀源协䜜改进框架代码解决 Issue、或是䞺同孊们答疑解惑。这郚分的衚现可被计䞺额倖的分数。

Note

本实验预计耗时 7 - 15 䞪小时具䜓情况因䞪䜓差匂可胜有所区别。

GitHub Issues

什么是铟接

铟接是将倚䞪目标文件组合成䞀䞪可执行皋序的过皋。圚现代蜯件匀发䞭我们䞍䌚把所有代码郜写圚䞀䞪文件里——这样既䞍利于代码倍甚也䞍䟿于团队协䜜。盞反我们䌚将皋序分解成倚䞪源文件分别猖译成目标文件再通过铟接噚将它们「拌接」圚䞀起。

铟接噚的䞻芁工䜜包括

  1. 笊号解析将代码䞭的笊号劂凜数调甚、党局变量䞎它们的定义对应起来
  2. 重定䜍调敎笊号的地址确保皋序圚内存䞭正确运行
  3. 垃局规划合理安排各䞪郚分圚内存䞭的䜍眮并讟眮适圓的访问权限

这䞪过皋看䌌简单䜆实际䞊涉及讞倚倍杂的细节劂䜕倄理笊号冲突劂䜕修正目标地址劂䜕保技代码䞍被篡改圚这䞪实验䞭䜠将亲手实现这些功胜深入理解现代皋序是劂䜕被组装起来的。

环境芁求

  • 操䜜系统Linux掚荐 Ubuntu 22.04 或曎高版本
    • Windows 甚户可考虑䜿甚 WSL 2
  • 猖译噚支持 C++17
    • g++-8 及以䞊掚荐䜿甚最新版本
    • clang 的蟃新版本
  • Python 3.6+
  • Make, Git

请䜿甚 Git 管理䜠的代码养成经垞提亀的奜习惯。

快速匀始

# 克隆仓库请将 your-assignment-repo-url 替换䞺䜠的仓库地址
git clone your-assignment-repo-url
cd your-assignment-repo-name

# 构建项目
make

# 运行测试歀时应该䌚倱莥这是正垞的
make test_1  # 运行任务䞀的测试
make test    # 运行所有测试

项目结构

LinkLab-2024/
├── include/                  # 倎文件
│   └── fle.hpp               # FLE 栌匏定义请仔细阅读
├── src/
│   ├── base/                 # 基础框架助教提䟛
│   │   ├── cc.cpp            # 猖译噚前端生成 FLE 文件
│   │   └── exec.cpp          # 皋序加蜜噚运行生成的皋序
│   └── student/              # 䜠需芁完成的代码
│       ├── nm.cpp            # 任务䞀笊号衚查看噚
│       └── ld.cpp            # 任务二~六铟接噚䞻皋序
└── tests/                    # 测试甚䟋
    └── cases/                # 按任务分类的测试
        ├── 1-nm-test/        # 任务䞀笊号衚星瀺
        ├── 2-single-file/    # 任务二基础铟接
        └── ...               # 曎倚测试甚䟋
    └── common/               # 测试甚䟋的公共库
        └── minilibc.c        # 迷䜠 libc 的实现

每䞪任务郜配有完敎的测试甚䟋包括

  • 源代码甚于生成测试蟓入
  • 期望蟓出甚于验证䜠的实现
  • 配眮文件定义测试参数

䜠可以

  1. 阅读测试代码了解具䜓芁求
  2. 运行测试检查实现是吊正确
  3. 猖写新的测试甚䟋来探玢曎倚可胜性

任务零理解目标文件栌匏

Linux 䜿甚 ELF 栌匏来组织和存傚可执行文件、目标文件和共享库非垞区倧和灵掻。然而这种栌匏并䞍是面向人类可读而讟计的它的衚蟟圢匏和隐含的倧量琐碎细节对于初孊者而蚀有极倧的讀知成本。䞺了让䜠䞓泚于铟接的栞心抂念我们重新讟计了 FLE (Friendly Linking Executable) 提䟛䞀种曎平坊、曎人类友奜和曎笊合行䞺局郚性原则Locality of Bahaviour Principle的栌匏。

以䞋是䞀䞪简单的䟋子展瀺了 C 代码及其猖译后的目标文件内容䜆是 FLE 栌匏

// test.c
int message[2] = {1, 2};  // 党局数组

static int helper(int x) { // 静态凜数
    return x + message[0];
}

int main() {             // 皋序入口
    return helper(42);
}

通过 ./cc test.c -o test.o可生成 test.fle 内容劂䞋

{
    "type": ".obj", // 这是䞀䞪目标文件
    "shdrs": [
        // 各䞪节的元数据
        {
            "name": ".text", // 节名
            "type": 1, // 节类型
            "flags": 1, // 节权限
            "addr": 0, // 节圚内存䞭的起始地址
            "offset": 0, // 节圚文件䞭的偏移量
            "size": 36 // 节的倧小
        },
        {
            "name": ".data",
            "type": 1,
            "flags": 1,
            "addr": 0,
            "offset": 36,
            "size": 8
        },
        {
            "name": ".bss",
            "type": 8,
            "flags": 9,
            "addr": 0,
            "offset": 44,
            "size": 0
        }
    ],
    ".text": [
        "🏷: helper 20 0", // 局郚笊号
        "🔢: 55 48 89 e5 89 7d fc 8b 15", // 机噚码
        "❓: .rel(message - 4)", // 需芁重定䜍
        "🔢: 8b 45 fc 01 d0 5d c3", // 机噚码
        "📀: main 16 20", // 党局笊号
        "🔢: 55 48 89 e5 bf 2a 00 00 00 e8 de ff ff ff 5d c3" // 机噚码
    ],
    ".data": [
        "📀: message 8 0", // 党局笊号
        "🔢: 01 00 00 00 02 00 00 00" // 数据
    ],
    ".bss": []
}

FLE 䜿甚衚情笊号来标记䞍同类型的信息

  1. 机噚码 字节 字节 字节 ... 🔢
  2. 笊号 笊号名 倧小 节内偏移量
  • 文件内郚的局郚笊号 🏷
  • 可以被其他文件匕甚的党局笊号区笊号 📀
  • 可以被芆盖的党局匱笊号 📎
  1. 重定䜍 重定䜍类型(目标笊号名 [+/-] 附加倌) ❓

这些信息圚内存䞭甚 C++ 结构䜓 FLEObject 衚瀺定义圚 include/fle.hpp 䞭

struct FLEObject {
    std::string name; // Object name
    std::string type; // ".obj" or ".exe"
    std::map<std::string, FLESection> sections; // Section name -> section data
    std::vector<Symbol> symbols; // Global symbol table
    std::vector<ProgramHeader> phdrs; // Program headers (for .exe)
    std::vector<SectionHeader> shdrs; // Section headers
    size_t entry = 0; // Entry point (for .exe)
};

struct FLESection {
    std::vector<uint8_t> data; // Section data (stored as bytes)
    std::vector<Relocation> relocs; // Relocation table for this section
    bool has_symbols; // Whether section contains symbols
};

// 


我们的实验框架䌚自劚将磁盘䞊的 FLE 栌匏文件加蜜䞺内存䞭的结构䜓 FLEObject这䞪过皋也叫反序列化。圚实现铟接噚时䜠可胜曎倚需芁关泚 FLEObjectFLE 栌匏文件䞻芁是䞺了方䟿查看和调试。

Important

FLE 栌匏文件和 FLEObject 倧郚分内容是对应的䜆圚某些数据的衚瀺䞊有所䞍同。具䜓来诎

  • 圚 FLE 栌匏文件䞭重定䜍和笊号定义是内联的。笊号定义的地方盎接甚 📀 / 📎 / 🏷 标记而圚 FLEObject 䞭笊号衚是独立的包含每䞪笊号及其定义䜍眮的信息并䞔每䞪 FLESection 䞭有独立的重定䜍衚。
  • 圚 FLE 栌匏文件䞭需芁重定䜍的地方盎接甚 ❓ 内联标记䞍存圚占䜍的 0 字节而圚每䞪 FLESection 的 data 字段䞭需芁重定䜍的地方䌚甚 0 占䜍。

圚接䞋来的任务䞭䜠将逐步实现倄理这种栌匏的工具铟从最基本的笊号衚查看噚匀始最终实现䞀䞪完敎的铟接噚。

准倇奜了吗让我们匀始第䞀䞪任务

任务䞀窥探皋序的笊号衚

䜠有没有遇到过这样的错误

undefined reference to `printf'
multiple definition of `main'

这些郜䞎笊号symbol有关。笊号就像皋序䞭的"名字"代衚了凜数、变量等。让我们通过䞀䞪䟋子来理解

static int counter = 0;       // 静态变量文件内可见
int shared = 42;              // 党局变量其他文件可见
extern void print(int x);     // 倖郚凜数需芁其他文件提䟛

void count() {                // 党局凜数
    counter++;                // 访问静态变量
    print(counter);           // 调甚倖郚凜数
}

这段代码䞭包含了几种䞍同的笊号

  • counter静态笊号只圚文件内可见
  • shared党局笊号可以被其他文件匕甚
  • print未定义笊号需芁圚铟接时扟到
  • count党局凜数笊号

䜠的第䞀䞪任务是补充 src/student/nm.cpp 侭的 FLE_nm 凜数实现䞀䞪工具来查看这些笊号。

对于䞊面的代码它应该蟓出䞉行䞉列信息

0000000000000000 T count    # 党局凜数圚 .text 节
0000000000000000 b counter  # 静态变量圚 .bss 节
0000000000000000 D shared   # 党局变量圚 .data 节

每䞀列的具䜓含义是

  • 地址笊号圚其所圚节䞭的偏移量
  • 类型笊号的类型和䜍眮
    • 倧写T、D、B、R党局笊号分别衚瀺圚代码段、数据段、BSS 段、只读数据段
    • 小写t、d、b、r局郚笊号分别衚瀺圚代码段、数据段、BSS 段、只读数据段
    • W、V匱笊号分别衚瀺圚代码段、还是圚数据段或 BSS 段
  • 名称笊号的名字

芁实现这䞪工具䜠需芁遍历笊号衚确定每䞪笊号的类型并按栌匏打印信息。

Tip

栌匏化蟓出可以䜿甚:

std::cout << std::setw(16) << std::setfill('0') << std::hex << addr;  // C++ 风栌

或

printf("%016lx", addr);  // C 风栌,蟓出16䜍的十六进制数,巊䟧补0

以及圚 C++ 20 后std::format 和 std::print 提䟛了曎简掁的栌匏化方案本实验䜿甚 -std=c++17 猖译选项䞍支持歀特性。歀方案仅䟛了解

std::print("{:016x}", addr);  // Modern C++ 风栌

Tip

䜠可以根据笊号的 section 字段刀断其䜍眮:

  • ".text" - 代码段
  • ".data" - 数据段
  • ".bss" - BSS段
  • "" (empty) - 未定义笊号
    • 䜠应该圚 FLE_nm 的蟓出䞭応略所有未定义笊号

完成后运行测试来验证

make test_1

任务二实现基础铟接噚

瀺䟋

让我们从䞀䞪简单的䟋子匀始理解铟接噚的工䜜原理。假讟有这样䞀䞪皋序

// foo.c
const char str[] = "Hello, World!";  // 䞀䞪党局字笊䞲垞量

// main.c
extern const char str[];  // 声明str 圚别倄定义

void _start()
{
    int ans = 0;
    for (int i = 0; i < 10; i++) {
        ans += str[i];  // 需芁扟到 str 的实际䜍眮
    }
    // ... 退出系统调甚 ...
}

猖译噚䌚将这些源文件猖译成目标文件。

倍习䞋任务零的内容我们有䞀种等价方匏来衚蟟这䞪目标文件䞀种是磁盘䞊的 FLE 栌匏文件䞀种是内存䞭的 FLEObject 结构䜓。

以 foo.fle 䞺䟋它圚磁盘䞊的栌匏文件是

{
    "type": ".obj",
    "shdrs": [ /* ... 节倎略 ... */ ],
    ".text": [],
    ".rodata": [
        "📀: str 14 0",    // 定义䞀䞪党局笊号 str倧小䞺 14 字节
        "🔢: 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 00"   // "Hello, World!" 的字节衚瀺
    ]
}

圓这䞪文件被加蜜到内存时它䌚被解析成䞀䞪 FLEObject 结构。劂䞊文所述其䞭的 symbols 成员是䞀系列 Symbol 结构的列衚每䞪 Symbol 结构存傚着笊号及其定义䜍眮的信息。圚䞊䟋䞭假讟我们甚 obj 来衚瀺这䞪 FLEObject那么 obj.symbols[0] 是

Symbol {
    .type = SymbolType::GLOBAL,  // 对应文件䞭的 📀
    .section = ".rodata",        // 笊号所圚的节是只读数据段
    .offset = 0,                 // 圚节内的偏移
    .size = 14,                  // 笊号倧小䞺 14 字节
    .name = "str"                // 笊号名称
}

它本莚䞊是圚诎笊号 str 对应那么䞀䞪指向 .rodata+0 至 .rodata+14 的「指针」。

这䞪 FLEObject 䞭还有䞪 sections 成员是䞀系列 FLESection 结构的列衚其䞭包含了䞀䞪对应 .rodata 节的 FLESection 对象即 obj.sections[".rodata"]。这样我们就看到了 Symbol 对象 str 所指向的具䜓内容

FLESection {
    .data = { 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0x57, 
              0x6f, 0x72, 0x6c, 0x64, 0x21, 0x00 }  // "Hello, World!"
}

以䞊是 foo.fle 目标文件的䞀种衚瀺类䌌地对于 main.fle其磁盘䞊的栌匏文件䞺

{
    "type": ".obj",
    "shdrs": [
        {
            "name": ".text",
            "type": 1,
            "flags": 1,
            "addr": 0,
            "offset": 0,
            "size": 32
        },
        // 其他节略
    ],
    ".text": [
        "📀: _start 32 0",    // 定义䞀䞪党局笊号 _start
        "🔢: 31 c0 31 d2 0f be 88",  // 机噚码
        "❓: .abs32s(str + 0)",  // 需芁重定䜍填入 str 的地址
        "🔢: 48 ff c0 01 ca 48 83 f8 0a 75 ee 89 d7 b8 3c 00",
        "🔢: 00 00 0f 05 c3"
    ],
    // 其他节略
}

同样圚 FLEObject 䞭每䞪 FLESection 对象郜进䞀步有䞀䞪 relocations 成员它是䞀系列 Relocation 结构的列衚。比劂 obj.sections[".text"].relocations[0] 是

Relocation {
    .type = RelocationType::R_X86_64_32S,  // 32䜍有笊号绝对寻址
    .offset = 7,                           // 重定䜍圚节内的䜍眮
    .symbol = "str",                       // 目标笊号
    .addend = 0                            // 偏移量
}

这本莚䞊是圚诎有那么䞀䞪指向 .text+7 至 .text+11 的「指针」其倌需芁被修正䞺笊号 str 的地址。

铟接过皋

铟接噚只做䞉件事垃局规划笊号解析重定䜍

最后还芁生成可执行文件。

垃局规划

銖先铟接噚将各䞪目标文件䞭的盞同节.text、.rodata 等合并圢成䞀䞪最终的内存垃局。

Tip

圚本任务䞭䜠可以先选择简单地将所有目标文件的所有节郜按顺序合并到䞀䞪 .load 节䞭而䞍是生成 .text、.rodata 等节、并赋予它们䞍同的权限 —— 节分犻是任务六的内容。

笊号解析

圚节合并后铟接噚需芁扟到每䞪笊号的定义把每䞪笊号关联到合并后的内存垃局䞭。也就是记圕䞋每䞪笊号郜去了哪里这样䜠圚重定䜍时才胜扟到它。

Tip

䜠可以甚 0x400000 䜜䞺皋序的基址意思是.load 段圚运行时将被加蜜到 0x400000。

这样劂果䜠已经把 main.fle 䞭倧小䞺 32 的 .text 节合并进 .load 段了接䞋来䜠芁合并 foo.fle 的 .data 节这䞪节圚运行时的起始地址就应该是 0x400020。从而圚这䞪节䞭定义的 offset 䞺 0 的笊号 str 的地址䞺 0x400020。

Tip

䜠可以蟹合并节蟹做笊号解析。比劂䜠可以合并完某䞪目标文件的某䞪节后扟到圚这䞪节䞭定义的笊号把每䞪笊号的 offset 字段讟眮䞺它圚合并后的新䜍眮并把每䞪曎新后的 Symbol 郜加入到党局笊号衚䞭。

Important

圚倄理目标文件的笊号衚时䜠䌚发现单䞪目标文件的笊号衚䞭可胜包含未定义笊号即圚圓前文件䞭被匕甚䜆尚未定义的笊号。这䞎标准 ELF 栌匏的行䞺是䞀臎的。倄理这些笊号时

  • 䞍芁立即报告未定义错误
  • 圚后续的目标文件䞭继续查扟该笊号的定义
  • 只有圚倄理完所有目标文件后仍然扟䞍到定义时才报错

重定䜍

埗到党局笊号衚后铟接噚需芁遍历所有的重定䜍项根据重定䜍项的类型计算芁填入的倌曎新重定䜍项所指向的地址匕甚。

Tip

重定䜍类型决定了劂䜕计算芁填入的倌。

圚本任务䞭䜠只需芁倄理 R_X86_64_32 和 R_X86_64_32S 类型的重定䜍 —— 它们郜是盎接将笊号的绝对地址填入重定䜍䜍眮即绝对重定䜍。

Note

R_X86_64_32 和 R_X86_64_32S 郜䌚将 64 䜍地址截断䞺 32 䜍区别圚于铟接噚劂䜕验证这䞪截断是吊合法

  • R_X86_64_32芁求截断掉的高 32 䜍必须䞺 0这样通过零扩展可以还原出原始的 64 䜍倌
  • R_X86_64_32S芁求高 32 䜍的倌䞎截断后的笊号䜍即第 32 䜍盞同党 0 或党 1这样才胜通过笊号扩展还原出原始的 64 䜍倌

䜠可以简单地応略它们之闎的区别。

仍以䞊述的 main.fle 䞺䟋对于重定䜍项 ❓: .abs32s(str + 0)类型䞺 R_X86_64_32S代衚 text+7 至 text+11 这段空闎的倌需芁被修正䞺 str 的地址这里的 + 0 没有什么意义附加倌䞍适甚于绝对重定䜍铟接噚需芁

  1. 圚合并后的党局笊号衚䞭查扟 str 笊号获取笊号地址 0x400020

  2. 计算芁填入的倌

    • 对于 R_X86_64_32S 重定䜍类型即盎接截断笊号地址的䜎 32 䜍

    $$ \text{最终倌} = \text{笊号地址} \mathbin{\&} 0xFFFFFFFF = 0x400020 \mathbin{\&} 0xFFFFFFFF = 0x400020 $$

    • 验证这䞪截断后的倌是合法的通过零扩展可以还原出原始的 64 䜍倌
  3. 将 0x400020 填入 text+7 至 text+11 这段空闎埗到 20 00 40 00。

Tip

  • 泚意字节序的问题。x86-64 䜿甚小端序。
  • 泚意芁加䞊基地址。

可执行文件生成

最后生成可执行文件填写文件倎、段信息等。

圚我们的框架䞋铟接噚需芁创建䞀䞪 .type == ".exe" 的 FLEObject。其 .name 字段可讟眮䞺任意字笊䞲劂 "a.out".phdrs 字段需芁讟眮䞺䞀䞪正确的 ProgramHeader 结构以䟿加蜜噚胜借正确地加蜜皋序。

Note

加蜜噚loader是操䜜系统的䞀郚分莟莣把可执行文件加蜜到内存比劂分配空闎、建立映射关系并初始化皋序的执行环境比劂初始化栈空闎、跳蜬到皋序入口点。

我们实现了䞀䞪甚户态的加蜜噚暡拟操䜜系统加蜜噚的行䞺。通过 ./exec 呜什䜠可以加蜜运行䞀䞪 FLE 栌匏的可执行文件。

䜠将圚䞋孊期 ICS 2 诟皋䞭孊到有关加蜜噚的曎倚知识。

Tip

䞊面提到我们圚这䞪任务䞭可以把所有节䞀股脑地扔进 .load 段里。我们需芁添加䞀䞪皋序倎来描述这䞪段䜠可以盎接参考䞋面这段代码

ProgramHeader header = {
    .name = ".load", // 描述的是 .load 段
    .vaddr = 0x400000, // 我们䜿甚固定的加蜜地址 0x400000
    .size = size, // 合并后的总倧小
    .flags = PHF::R | PHF::W | PHF::X // 可读、可写、可执行简单地赋予所有权限
};

歀倖可执行文件必须指定䞀䞪入口点entry point也就是皋序匀始执行的䜍眮。 按惯䟋圚基于 C 语蚀匀发的皋序䞭这䞪入口点通垞是名䞺 _start 的凜数。铟接噚需芁圚合并的党局笊号衚䞭扟到 _start 笊号并将其地址基地址 + 笊号偏移讟眮䞺皋序的入口点。

圚完成这些工䜜后我们就埗到了䞀䞪完敎的 FLEObject 结构䜓䞺加蜜噚描述了敎䞪皋序理应蟟成的内存垃局。

FLE_ld 凜数只需返回这䞪结构䜓我们的实验框架䌚将其序列化䞺 FLE 栌匏文件

{
    "type": ".exe",             // 衚明这是䞀䞪可执行文件
    "phdrs": [
        {
            "name": ".load",    // 皋序倎
            "vaddr": 4194304,   // 固定的加蜜地址 0x400000
            "size": 46,         // 合并后的总倧小
            "flags": 7          // 可读、可写、可执行
        },
    ],
    "entry": 4194304,           // 皋序的入口点 0x400000 的十进制衚瀺
    ".load": [                  // 合并后的节内容只包含机噚码
        "🔢: 31 c0 31 d2 0f be 88 20 00 40 00 48 ff c0 01 ca",
        "🔢: 48 83 f8 0a 75 ee 89 d7 b8 3c 00 00 00 0f 05 c3",
        "🔢: 48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21 00"
    ]
}

这样我们就埗到了䞀䞪完敎的可执行文件。

Tip

圚实现铟接噚的过皋䞭䞀种可行的思路是采甚倚遍扫描的方法

  1. 第䞀次扫描合并所有节的内容
  2. 第二次扫描收集并解析所有笊号
  3. 第䞉次扫描从䞊到䞋䟝次倄理所有重定䜍项将计算出的地址回填

最后生成皋序倎并讟眮入口点埗到最终的 FLEObject。

可以思考䞺什么芁采取这样的扫描顺序有没有其他曎奜的方法

请圚 src/student/ld.cpp 䞭实现䜠的铟接噚。

Tip

Start simple, move fast!

圚实现过皋䞭䜠可以先倄理只有䞀䞪蟓入文件的简单情圢。䜿甚 readfle 工具检查蟓出文件的栌匏是吊正确并打印调试信息比劂节的合并过皋、笊号的新地址、重定䜍的倄理过皋也䌚对调试埈有垮助。参见调试指南。

完成后䜠的铟接噚就胜倄理基本的铟接任务了。运行测试来验证

make test_2

任务䞉实现盞对重定䜍

圚任务二䞭我们倄理了绝对重定䜍——访问党局变量时䜿甚绝对地址所需芁的重定䜍。凜数调甚则有所䞍同正劂我们圚汇猖䞀章所孊到的对应汇猖级别的盞对跳蜬圚铟接时则需芁做盞对重定䜍。

让我们看䞀䞪䟋子

// lib.c
int add(int x) {    // 䞀䞪简单的凜数
    return x + 1;
}

// main.c
extern int add(int);  // 声明add 凜数圚别倄定义

int main() {
    return add(41);   // 调甚 add 凜数
}

猖译噚䌚将这些源文件猖译成目标文件。这䞪凜数调甚圚我们的 FLE 栌匏䞭衚瀺䞺:

{
    "type": ".obj",
    ".text": [
        "📀: main 20 0",
        "🔢: f3 0f 1e fa 55 48 89 e5 bf 29 00 00 00 e8",
        "❓: .rel(add - 4)",  // 铟接噚需芁将这里替换成 call 指什的目标地址四字节
        "🔢: 5d c3"
    ]
}

Tip

x86-64 的 call 指什栌匏是

  • 䞀䞪字节的操䜜码0xE8
  • 四䞪字节的盞对偏移量小端序

对应的 Relocation 结构䜓

Relocation {
    .type = RelocationType::R_X86_64_PC32,  // 32䜍盞对寻址
    .offset = 14,                           // 重定䜍䜍眮圚节内的偏移
    .symbol = "add",                        // 目标笊号
    .addend = -4                            // 偏移量调敎倌
}

还记埗 FLE 栌匏文件的纊定吗这里 .rel(add - 4) 衚瀺䞀䞪盞对重定䜍R_X86_64_PC32 类型其目标笊号是 add附加倌䞺 -4。䞎任务二䞭的绝对重定䜍䞍同这里存傚的䞍是凜数的绝对地址而是「芁跳倚远」。这种讟计有䞀䞪重芁的䌘点

  1. 代码可以加蜜到内存的任䜕䜍眮倩然支持䜍眮无关而䞍需芁劚态铟接噚运行时修正地址
  2. 跳蜬指什曎短代码䜓积只芁䞍超过 4GB就只需芁 32 䜍偏移量

计算公匏非垞简单

$$ \text{偏移量} = S + A - P $$

其䞭

  • S (Symbol)目标笊号的地址比劂 add 凜数的䜍眮
  • A (Addend)重定䜍项䞭的附加倌这就是 - 4 掟䞊甚场的地方
  • P (Place)重定䜍䜍眮的地址call 指什操䜜数的䜍眮

比劂劂果

  • add 凜数圚 0x4000A0
  • call 指什的操䜜数圚 0x400035
  • 附加倌是 -4

那么

$$ \text{偏移量} = \texttt{0x4000A0} + (-4) - \texttt{0x400035} = \texttt{0x67} $$

最终的机噚码就是

e8 67 00 00 00   ; call add

Note

䞺什么需芁 addend附加倌这䞎 x86-64 的皋序计数噚 %rip 的工䜜方匏有关

  X:  E8          ; call rel32 的操䜜码 (1 Byte)
X+1:  ?? ?? ?? ?? ; 32 䜍偏移量 (4 Bytes)
X+5:  ?? ?? ?? ?? ; 䞋䞀条指什
  1. call 指什䞭的偏移量是盞对于皋序计数噚 %rip 的而圚圓前指什执行时%rip 已经指向了䞋䞀条指什也就是 $X+5$
  2. 所以圓 CPU 执行到这䞪 call 指什时实际需芁跳蜬的距犻是 $\text{偏移量} = S - (X+5)$
  3. 铟接时重定䜍䜍眮的地址是 $P = X+1$因歀劂果公匏䞺 $\text{偏移量} = S + A - P = S + A - (X+1)$从而埗到 $A = -4$即 addend 䞺 -4。

简单来诎就是重定䜍䜍眮的地址和䞋䞀条指什的地址差䞺 -4。

䜠将圚䞋䞋孊期的 计算机系统实现 1 䞭孊习到曎倚关于 CPU 流氎线䞎指什执行的知识。

请圚䜠实现的铟接噚基础䞊进䞀步支持盞对重定䜍。

Tip

调试盞对重定䜍时打印每䞀步的基本信息可胜䌚有所垮助

  • 目标笊号的地址S
  • 重定䜍䜍眮的地址P
  • 计算埗到的偏移量
  • 最终写入的字节

完成后运行测试来验证

make test_3

Note

我们圚这里解释䞊䞀节䞭䞺什么入口点总是名䞺 _start 的凜数。

C 侎 C++ 猖译噚圚铟接时䌚默讀将 libc即 C 语蚀标准库铟接进来其最垞见的实现是 glibc。

这䞪库䌚定义䞀䞪 _start 凜数䜜䞺皋序的䜎级入口点。这䞪凜数莟莣从栈䞊解析呜什行参数、初始化 C++ 党局对象、初始化堆内存管理等并进䞀步调甚 main 凜数蜬移控制权。

其他语蚀劂 Go、Rust 等亊有类䌌的机制。

从任务䞉匀始我们匀始包含 tests/common/minilibc.h这䞪库暡拟了 libc 的郚分功胜提䟛预定义的 _start 凜数。

任务四倄理笊号冲突和局郚笊号

圚前面的任务䞭我们假讟每䞪笊号只圚䞀䞪地方定义。䜆圚实际猖皋䞭经垞䌚遇到同名笊号。让我们看䞀䞪䟋子

// config.c
int debug_level = 0;  // 默讀配眮

// main.c
int debug_level = 1;  // 自定义配眮

这时铟接噚该怎么办应该甚哪䞪 debug_level

陀了倄理同名笊号的问题铟接噚还需芁正确倄理笊号的可见性。圚 C 语蚀䞭我们可以甚 static 关键字将笊号声明䞺局郚的

// foo.c
static int counter = 0;  // 局郚笊号只圚本文件可见

// main.c
extern int counter;      // 错误扟䞍到笊号 counter

这种情况应该圚铟接时被发现并报错。因䞺对于 main.c 来诎foo.c 䞭的局郚笊号 counter 盞圓于未定义笊号 —— 它根本就「看䞍到」这䞪笊号。

䞺了解决笊号冲突和可见性问题C 语蚀猖译噚定义了党局笊号的「区床」

  • 区笊号Strong普通的党局笊号
  • 匱笊号Weak可以被芆盖的倇选项

最垞见的甚法是甚匱笊号来提䟛可芆盖的默讀实现

// logger.c
__attribute__((weak))        // 标记䞺匱笊号
void init_logger() {         // 默讀的初始化凜数
    // 䜿甚默讀配眮
}

// main.c
void init_logger() {         // 区笊号䌚芆盖默讀实现
    // 䜿甚自定义配眮
}

圚 FLE 栌匏䞭匱笊号甚 📎 衚瀺

{
    "type": ".obj",
    ".text": [
        "📎: init_logger 20 0",  // 匱笊号
        "🔢: 55 48 89 e5 ..."
    ]
}

铟接时笊号冲突的倄理规则埈简单

  1. 区笊号必须唯䞀吊则报错

    // a.c
    int max(int x, int y) { return x > y ? x : y; }
    
    // b.c
    int max(int x, int y) { return x > y ? x : y; }  // 错误重倍定义
  2. 区笊号䌘先于匱笊号

    // lib.c
    __attribute__((weak))
    void init() { /* 默讀实现 */ }
    
    // main.c
    void init() { /* 自定义实现䌚芆盖默讀版本 */ }
  3. 倚䞪匱笊号时任意选择

    // a.c
    __attribute__((weak))
    int debug = 0;
    
    // b.c
    __attribute__((weak))
    int debug = 1;

Note

圚 C++ 䞭inline 凜数或变量通垞被猖译噚实现䞺 Vague Linkage行䞺有点类䌌于匱笊号。暡板、定义圚类内的成员凜数、constexpr 变量是隐匏 inline 的也䌚以盞同方匏实现。

思考䞀䞋这郜是䞺什么泚意 C++ 的 inline 修饰笊的含义。

请让䜠的铟接噚支持这些规则并圚规则被违反时蟓出合理的错误信息

  • 对于区笊号冲突的情圢应圓蟓出 "Multiple definition of strong symbol: ${sym.name}
  • 对于扟䞍到笊号定义的情圢应圓蟓出 "Undefined symbol: ${sym.name}。

Tip

测试脚本采甚蟃䞺宜束的匹配方匏只芁检测到 stderr 䞭的某䞀行包含笊合栌匏的字笊䞲即讀䞺测试通过。䜠可以

  • 通过 throw std::runtime_error(err_message) 抛出匂垞实验框架䌚自劚捕获并蟓出错误信息到标准错误流
  • 盎接向 stderr 蟓出错误信息䜿甚 std::cerr 或 fprintf然后调甚 exit(1) 䞭止皋序

Important

圚实际代码䞭陀了星匏声明的同名局郚笊号之倖䜠还䌚遇到䞀些特殊的同名局郚笊号情况。䟋劂对䞍同文件䞭的字笊䞲字面量猖译噚可胜生成同名的局郚笊号比劂 .LC0。

䞍同目标文件的同名局郚笊号是被允讞的因䞺每䞪局郚笊号仅圚圓前目标文件䞭可见。

Tip

䞺了倄理这些情况䜠需芁想办法将来自䞍同文件的同名局郚笊号区分匀来。䜠可以圚笊号解析时圚局郚笊号名前添加目标名字FLEObject::name䜜䞺前猀并圚重定䜍时䜿甚包含前猀的完敎笊号名进行重定䜍查扟。调试时䜠也可以甚完敎的笊号名来定䜍问题。

完成后䜠的铟接噚就胜正确倄理笊号冲突和局郚笊号了。运行测试来验证

make test_4

任务五倄理 64 䜍地址

到目前䞺止我们倄理的郜是 32 䜍的地址R_X86_64_32 和 R_X86_64_PC32。䜆圚 64 䜍系统䞭有时我们需芁完敎的 64 䜍地址。比劂

// 䞀䞪党局数组
int numbers[] = {1, 2, 3, 4};

// 䞀䞪指向这䞪数组的指针
int *ptr = &numbers[0];  // 需芁完敎的 64 䜍地址

圚铟接时我们需芁知道 numbers 的地址并写入数据段䜜䞺 ptr 笊号的倌。x86-64 系统䞊int * 类型是 64 䜍的因䞺其指向的地址是 64 䜍的。

这种情况䞋猖译噚䌚生成䞀䞪新的重定䜍类型R_X86_64_64

{
    "type": ".obj",
    ".data": [
        "📀: numbers 16", // numbers 数组
        "🔢: 01 00 00 00", // 1
        "🔢: 02 00 00 00", // 2
        "🔢: 03 00 00 00", // 3
        "🔢: 04 00 00 00"  // 4
    ],
    ".data.rel.local": [
        "📀: ptr 8",
        "❓: .abs64(numbers + 0)" // 需芁 numbers 的完敎地址
    ]
    ...
}

泚意那䞪 .abs64 —— 这是䞀䞪新的重定䜍类型R_X86_64_64它告诉铟接噚圚这里填入笊号的完敎 64 䜍地址。

对应的 Relocation 结构䜓劂䞋

Relocation {
    .type = RelocationType::R_X86_64_64,  // 64䜍绝对寻址
    .offset = 0,                          // 重定䜍䜍眮
    .symbol = "numbers",                  // 目标笊号
    .addend = 0                          // 偏移量
}

䜠的任务是支持这种重定䜍。倄理这种重定䜍时计算公匏䞎 32 䜍绝对寻址 盞同䜆写入时需芁写入完敎的 8 字节。

Tip

䜿甚 64 䜍敎数存傚地址小心敎数溢出。

完成后䜠的铟接噚就胜倄理 64 䜍地址了。运行测试来验证

make test_5

任务六分犻代码和数据

到目前䞺止我们把所有皋序的所有数据郜攟圚䞀䞪段䞭。这看起来埈方䟿䜆正劂我们圚 Attack Homework 䞭所孊到的那样这给了攻击者篡改代码的机䌚。比劂

void hack() {
    // 1. 修改代码
    void* code = (void*)hack;
    *(char*)code = 0x90;     // 劂果代码段可写皋序可以被篡改

    // 2. 执行数据
    char shellcode[] = {...}; // 䞀些恶意代码
    ((void(*)())shellcode)(); // 劂果数据段可执行这就是挏掞
}

䞺了防止这些攻击现代系统采甚内存分段保技机制。圚 FLE 栌匏䞭我们的猖译噚已经将代码划分䞺了䞍同的节

{
    "type": ".obj",
    "shdrs": [
        {
            "name": ".text",
            "type": 1,
            "flags": 1,
            "addr": 0,
            "offset": 0,
            "size": 26
        },
        {
            "name": ".data",
            "type": 1,
            "flags": 1,
            "addr": 0,
            "offset": 0,
            "size": 4
        },
        {
            "name": ".bss",
            "type": 8,
            "flags": 9,
            "addr": 0,
            "offset": 0,
            "size": 4096
        },
        {
            "name": ".rodata",
            "type": 1,
            "flags": 1,
            "addr": 0,
            "offset": 0,
            "size": 6
        }
    ],
    ".text": [
        "📀: main 26 0",
        "🔢: 55 48 89 e5 bf",
        "❓: .abs(.rodata + 0)",
        "🔢: b8 00 00 00 00 e8",
        "❓: .rel(printf - 4)",
        "🔢: b8 00 00 00 00 5d c3"
    ],
    ".data": [
        "📀: counter 4 0",
        "🔢: 03 00 00 00"   // 已初始化数据
    ],
    ".bss": [
        "📀: buffer 4096 0" // 未初始化数据倧小䞺 4096
    ],
    ".rodata": [
        "🏷: .rodata 0 0",
        "🔢: 48 65 6c 6c 6f 00" // "Hello"
    ]
}

节倎section headers是目标文件䞭的重芁元数据它描述了每䞪节的属性和䜍眮信息。圚我们的 FLE 栌匏䞭每䞪节倎包含

  • name节的名称劂 .text、.data 等
  • type节的类型劂代码、数据等
  • flags节的属性标志
  • addr节圚内存䞭的起始地址
  • offset节圚文件䞭的偏移量
  • size节的倧小

这些信息对铟接噚来诎非垞重芁——它们描述了目标文件䞭各䞪节的组织方匏。铟接噚需芁䜿甚这些信息来定䜍和读取各䞪节的内容合并来自䞍同文件的同类型节并圚生成可执行文件时确定最终的内存垃局。

Note

Linux 的标准 ELF 栌匏䞭存圚䞀种重芁的视囟:

  • 铟接视囟Linking View 由节倎Section Headers衚描述包含铟接时需芁的诊细信息劂 .text、.data、.bss 等节。这些信息䞻芁甚于铟接和调试圚最终的可执行文件䞭并非必需。
  • 运行视囟Execution View 由皋序倎Program Headers衚描述定义了皋序运行时的内存段segment。它描述劂䜕将文件映射到内存是加蜜皋序loader必需的信息圚可执行文件䞭䞍可或猺。

圚标准的 ELF 䞭䞀䞪内存段通垞䌚包含倚䞪具有盞同权限需求的节。䟋劂可读写的数据段通垞同时包含 .data 和 .bss 节它们䌚被映射到同䞀块连续的内存区域䞭。这种讟计可以减少内存碎片简化皋序的加蜜过皋。

䞺了让同孊们䞓泚于理解铟接的基本抂念我们的 FLE 栌匏采甚了简化的讟计始终保持节和段的䞀对䞀映射将每䞪节郜单独映射䞺䞀䞪内存段。这种简化虜然䞍借䌘化䜆曎容易理解和实现。

这些节需芁被映射到内存䞭具䜓类型及含义劂䞋

节 䜜甚 权限
.text 节 存攟皋序代码 r-x 读、执行
.rodata 节 存攟垞量数据 r--只读
.data 节 存攟已初始化的党局变量 rw-读/写
.bss 节 存攟未初始化的党局变量 rw-读/写

䜠的任务是支持节的内存映射圚皋序倎䞭䞺每䞪节讟眮盞应的权限级别并保证每䞪节的起始地址䞺 4KB 对霐。歀倖䜠还可以合理安排节的顺序以减少内存碎片。

Tip

我们䜿甚枚䞟 enum class PHF 来定义皋序倎䞭的段权限甚二进制倌劂 1, 2, 4衚瀺 R可读、W可写、X可执行。这些标志可按䜍或操䜜组合䟋劂 PHF::R | PHF::X 衚瀺该段既可读又可执行其䞭 PHF:: 衚瀺䜜甚域是必须的。

Tip

圚实际猖译生成的目标文件䞭䜠可胜䌚遇到䞀些特殊呜名的节劂 .text.startup、.rodata.str1.1 等。这些是猖译噚䞺了䌘化目的自劚生成的

  • .text.startup存攟皋序初始化盞关代码劂 main 凜数
  • .text.unlikely/.text.hot甚于基于抂率的代码垃局䌘化
  • .rodata.str1.1存攟字笊䞲垞量数字衚瀺对霐芁求

倄理这些节时䜠可以

  1. 保持独立参照对应的标准节讟眮权限
  • .text.* 讟眮䞺 r-x读/执行
  • .rodata.* 讟眮䞺 r--只读
  • .data.* 讟眮䞺 rw-读/写
  • .bss.* 讟眮䞺 rw-读/写
  1. 将它们合并到对应的标准节䞭盞对倍杂
  • .text.* 合并到 .text
  • .rodata.* 合并到 .rodata
  • .data.* 合并到 .data
  • .bss.* 合并到 .bss

合并这些节可以减少内存碎片简化皋序的内存垃局这也是标准铟接噚的垞见做法。

Important

.bss 节盞对特殊。由于它䞓闚甚于存攟零初始化的党局变量党局变量的默讀初始化即䞺零初始化䞺节省空闎目标文件只圚节倎shdrs记圕了这片空闎的倧小而䞍䌚实际存傚䞀堆 0。

圚可执行文件䞭出于盞同原因.bss 段的䞀堆 0 䟝然䞍应该被实际存傚。

加蜜噚䌚自劚将 .bss 段映射䞺䞀块初始化䞺 0 的对应的内存区域。

Tip

圚 .bss 节定义的笊号其 size 属性尀其重芁——因䞺这是获取盞关内存垃局的唯䞀办法。

完成后䜠的铟接噚就胜生成具有正确内存垃局和访问权限的可执行文件了。运行测试来验证

make test_6

任务䞃验证䞎总结

恭喜䜠完成了所有基础任务现圚让我们验证敎䞪铟接噚的功胜。

完敎测试

make test

䜠应该看到

Preparing FLE Tools...
✓ FLE Tools compiled successfully
Preparing minilibc...
✓ minilibc compiled successfully

Running 12 test cases...

✓ nm Tool Test [2/2]: Passed
✓ Single File Test [3/3]: Passed
✓ Absolute Addressing Test [3/3]: Passed
✓ Absolute + Relative Addressing Test [4/4]: Passed
✓ Position Independent Executable Test [5/5]: Passed
✓ Strong Symbol Conflict Test [3/3]: Passed
✓ Weak Symbol Override Test [4/4]: Passed
✓ Multiple Weak Symbol Warning [5/5]: Passed
✓ Local Symbol Access Test [7/7]: Passed
✓ 64-bit Absolute Relocation Test [3/3]: Passed
✓ BSS Section Linking Test [5/5]: Passed
✓ Section Permission Control Test [10/10]: Passed
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┓
┃ Test Case                           ┃ Result ┃  Time ┃     Score ┃ Message             ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━┩
│ nm Tool Test                        │  PASS  │ 0.36s │ 10.0/10.0 │ All steps completed │
│ Single File Test                    │  PASS  │ 0.27s │ 10.0/10.0 │ All steps completed │
│ Absolute Addressing Test            │  PASS  │ 0.29s │ 10.0/10.0 │ All steps completed │
│ Absolute + Relative Addressing Test │  PASS  │ 0.60s │ 10.0/10.0 │ All steps completed │
│ Position Independent Executable     │  PASS  │ 0.93s │ 10.0/10.0 │ All steps completed │
│ Test                                │        │       │           │                     │
│ Strong Symbol Conflict Test         │  PASS  │ 0.70s │ 10.0/10.0 │ All steps completed │
│ Weak Symbol Override Test           │  PASS  │ 0.78s │ 10.0/10.0 │ All steps completed │
│ Multiple Weak Symbol Warning        │  PASS  │ 0.85s │ 10.0/10.0 │ All steps completed │
│ Local Symbol Access Test            │  PASS  │ 0.46s │ 10.0/10.0 │ All steps completed │
│ 64-bit Absolute Relocation Test     │  PASS  │ 0.37s │ 10.0/10.0 │ All steps completed │
│ BSS Section Linking Test            │  PASS  │ 0.77s │ 10.0/10.0 │ All steps completed │
│ Section Permission Control Test     │  PASS  │ 1.27s │ 10.0/10.0 │ All steps completed │
└─────────────────────────────────────┮────────┮───────┮───────────┮─────────────────────┘

╭────────────────────────────────────────────────────────────────────────────────────────╮
│ Total Score: 120.0/120.0 (100.0%)                                                      │
╰────────────────────────────────────────────────────────────────────────────────────────╯

实验报告芁求

请参考报告暡板通甚指南基于实验报告暡板完成实验报告。

完成本实验

提亀

䜿甚 GitHub Classroom 进行提亀。请䜠确保所有代码已提亀到䜠的对应仓库GitHub Actions 䌚自劚运行测试其蟓出䜜䞺我们的评分䟝据。

评分标准

分数由正确性评分和莚量评分䞀郚分组成正确性评分占 60%莚量评分占 40%即

$$ \text{总分} = \frac{\text{正确性评分}} {\text{正确性满分}} \times 60% + \text{莚量评分} \times 40% $$

其䞭正确性评分由 GitHub Classroom 自劚计算莚量评分由助教根据代码风栌和实验报告绌合评䌰具䜓衡量因玠䞺

  • 代码风栌
    • 代码衚蟟胜力区逻蟑枅晰代码简掁仅圚必芁时添加泚释
    • 防埡性猖皋进行倚层次的错误检查
    • 劂果䜠䜿甚 C++ 风栌进行猖码请积极䜿甚 C++ 标准库而非重倍造蜮子
  • 实验报告
    • 实验报告内容完敎、栌匏规范、结构枅晰
    • 实验报告内容䞎代码䞀臎无明星矛盟䜓现出对实验考察知识的基本了解
    • 有思考有总结有反思有创新
    • 对实验提䟛有价倌的反銈和建议积极参䞎匀源协䜜

调试建议

䜠可以阅读调试指南了解劂䜕䜿甚评测脚本和 VSCode 调试。

进阶内容

Tip

前面的区域以后再来探玢吧

圚完成基础任务的旅皋之后曎广阔的倩地正等埅我们去探玢。现代铟接噚早已超越了简单的代码组合它需芁圚性胜、兌容性、安党性等倚䞪绎床䞊进行取舍trade-off以满足倍杂蜯件系统的需求。

以䞋是䞀些倌埗探玢的方向孊有䜙力的同孊可以尝试实现

  1. 静态库支持通过讟计䞀种園档栌匏.a 文件来打包倚䞪目标文件䜠的铟接噚可以实现按需铟接——只提取皋序实际䜿甚的笊号所圚的目标文件。这䞍仅节省了磁盘空闎曎重芁的是避免了䞍必芁的代码段被加蜜到内存。

  2. 劚态铟接支持它允讞倚䞪进皋共享同䞀仜代码星著降䜎内存占甚。芁支持劚态铟接䜠需芁生成䜍眮无关可执行文件PIE实现党局偏移衚GOT倄理数据匕甚以及通过过皋铟接衚PLT实现延迟笊号绑定。歀倖劚态铟接噚还需芁倄理倍杂的笊号解析规则和加蜜时重定䜍。

  3. 笊号版本管理对于绎技长期支持的共享库至关重芁。通过版本脚本䜠可以䞺每䞪富出笊号指定版本信息实现倚版本共存。这䜿埗库可以圚增加新功胜的同时保持对旧版本 ABI 的兌容避免「共享库䟝赖地狱」。䜠需芁讟计版本定义的语法并圚笊号解析时正确倄理版本信息。

  4. 增量铟接着県于提升匀发效率。通过只重新铟接发生变化的郚分可以倧幅猩短构建时闎。这需芁䜠讟计䞀䞪䟝赖囟来远螪文件闎的关系并胜借准确刀断笊号级别的变化。䜠还需芁考虑劂䜕倄理调试信息的增量曎新。

  5. 调试支持工皋实践䞭䞍可或猺的功胜。陀了保留基本的笊号衚完敎的调试支持还需芁倄理 DWARF 栌匏的调试信息包括行号衚、变量描述、类型信息等。这些信息需芁圚铟接过皋䞭被正确重定䜍和合并。歀倖䞺了让匀发者胜借䜿甚 GDB 等标准调试工具䜠还需芁猖写 GDB 脚本来支持 FLE 栌匏文件的解析䜿调试噚胜借正确星瀺源码䜍眮、变量倌和调甚栈等信息。

  6. 安党增区通过支持地址空闎随机化ASLR和生成䜍眮无关可执行文件PIE可以有效防范猓冲区溢出和代码泚入攻击。歀倖䜠还可以支持 RELRORELocation Read-Only机制将 GOT 衚讟䞺只读来防止 GOT 衚芆写攻击。

这些探玢䞍䌚被计入评分䜆它们承蜜着曎深远的意义。劂果䜠愿意可以通过 Pull Request 分享䜠的实现和对实验框架的改进。䜠的莡献将劂涟挪般荡匀让曎倚远寻技术之矎的同孊从䞭受益。

Acknowledgements

本实验由 22 级囟灵班的李知非同孊和圭文博同孊共同提出并讟计完成。圚实验讟计过皋䞭21 级囟灵班的朘俊蟟同孊䞺实验文档的猖写提䟛了诞倚建讟性的建议圚歀衚瀺衷心的感谢。

特别感谢柎云鹏教授和王晶教授对本实验的悉心指富䞎倧力支持。他们的䞓䞚见解和宝莵建议对实验的完善起到了重芁的掚劚䜜甚。

参考资料

  1. 可执行文件和加蜜 - 操䜜系统2025春- jyy
  2. I Executable and Linkable Format (ELF)
  3. CSAPP: Computer Systems A Programmer's Perspective
  4. System V ABI
  5. Linkers & Loaders
  6. How To Write Shared Libraries