Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# LinkLab 报告

姓名:赖柏有
学号:2022201388

## Part A: 思路简述

本实验旨在设计和实现一个简单的链接器,主要功能包括符号解析、重定位和段合并。链接器的核心思想是逐个处理输入的目标文件,合并其各个段(如 .text、.data 等),并解析全局符号。重定位主要针对地址相关的符号进行计算,确保目标文件的符号地址与最终执行文件的虚拟地址相符。

关键的数据结构包括符号表、段表和重定位表。符号表存储所有全局符号及其地址信息;段表管理程序的各个段及其数据;重定位表存储重定位项,确保符号的地址在合并后的文件中得到正确处理。

## Part B: 具体实现分析

### 符号解析实现
在符号解析的过程中,我们首先根据符号类型将符号分为全局符号、局部符号和弱符号。对于全局符号,若在多个目标文件中出现,则检查是否存在强符号与弱符号冲突,确保使用强符号的定义。局部符号仅在所在目标文件内部使用,通过添加目标文件名后缀来避免与其他目标文件中的符号冲突。弱符号优先使用强符号定义。

对于符号冲突的解决,我们采取了两种策略:若存在强符号与弱符号的冲突,则优先选用强符号;若多个强符号定义相同符号,则抛出错误,提示用户存在重复定义。

在符号解析的过程中,错误检查尤为重要。例如,若在重定位时找不到目标符号,我们会抛出 "Undefined symbol" 错误。

### 重定位处理
本实验支持的重定位类型包括 R_X86_64_32、R_X86_64_32S 和 R_X86_64_PC32。针对不同的重定位类型,重定位算法采用不同的处理方式:

对于 R_X86_64_32,将符号的地址填入对应位置,截断为 32 位。
对于 R_X86_64_32S,处理类似,但需要确保符号位的扩展是合法的(符号扩展)。
对于 R_X86_64_PC32,计算相对偏移量,考虑程序计数器 %rip 的位置,确保跳转指令正确。
错误处理方面,我们会检查重定位符号是否已定义,若未定义则抛出 Undefined symbol 错误。此外,重定位过程中需要进行合法性检查,确保所有计算结果符合目标平台的要求。

### 段合并策略
在合并段的过程中,我们首先按段名进行合并。例如,.text 和 .rodata 等段会被合并到目标文件的相应位置。我们特别注意段的对齐,确保每个段的起始地址是 4KB 对齐,以符合常见操作系统的内存管理要求。

局部符号的存储考虑到其只在本目标文件内有效,因此我们采用了符号名称后加目标文件名后缀的方法来避免与全局符号冲突。

## Part C: 关键难点解决
关键难点是第三步重定位,起初test_3一直发生段错误但是找了很久都找不到重定位是哪里出错了,后面看了很久才知道是没有考虑到P是要根据%rip计数器进行调整的,修改了这个才终于把这一关过了

## Part D: 实验反馈
<!-- 芝士 5202 年研发的船新实验,你的反馈对我们至关重要
可以从实验设计,实验文档,框架代码三个方面进行反馈,具体衡量:
1. 实验设计:实验难度是否合适,实验工作量是否合理,是否让你更加理解链接器,链接器够不够有趣
2. 实验文档:文档是否清晰,哪些地方需要补充说明
3. 框架代码:框架代码是否易于理解,接口设计是否合理,实验中遇到的框架代码的问题(请引用在 repo 中你提出的 issue)
-->
实验估计7-15个小时但对于我来说,靠AI和自己硬磕搞了可能二十多个小时都没有怎么搞好,最后还是参考了别人的代码才能完成,我觉得有可能是我自己本身编程能力太烂了,但这个学习的过程确实对链接器更懂了,虽然自己上手写的时候真的写不好

## 参考资料 (可不填)
<!-- 对实现有实质帮助的资料 -->
1. [资料名] - [链接] - [具体帮助点]
2. ...
Binary file added report.pdf
Binary file not shown.
169 changes: 145 additions & 24 deletions src/student/ld.cpp
Original file line number Diff line number Diff line change
@@ -1,30 +1,151 @@
#include "fle.hpp"
#include <cassert>
#include <iostream>
#include <map>
#include <set>
#include <stdexcept>
#include <vector>
#include "fle.hpp"
#include "string_utils.hpp"

FLEObject FLE_ld(const std::vector<FLEObject>& input_objects) {
// 1. 合并段并收集符号
// - 遍历每个输入对象的段
// - 按段名合并并计算各段的偏移量
// - 设置段的读/写/执行属性
auto local_symbol_sets = std::vector<std::set<std::string>>(); // 存储每个目标文件的局部符号
for (const auto& obj : input_objects) {
local_symbol_sets.push_back(std::set<std::string>());
for (const auto& symbol : obj.symbols) {
if (symbol.type == SymbolType::LOCAL)
local_symbol_sets.back().insert(symbol.name);
}
}

// 创建目标 FLEObject
auto linked_object = FLEObject();
auto global_symbols = std::map<std::string, Symbol>();
auto segment_names = std::vector<std::string>{".text", ".rodata", ".data", ".bss"};
auto segment_flags = std::vector<uint32_t>{5, 4, 6, 6}; // 设置段的权限标志
auto segment_vaddr = std::vector<size_t>(4, 0); // 各段的虚拟地址
size_t virtual_address = 0x400000; // 初始化虚拟内存基址

// 所有段并合并
for (int i = 0; i < 4; i++) {
auto current_segment_name = segment_names[i];
FLESection current_merged_section;
current_merged_section.has_symbols = false;
size_t section_offset = 0; // 当前段内的偏移量
segment_vaddr[i] = virtual_address;

// 遍历每个输入文件
for (size_t j = 0; j < input_objects.size(); j++) {
auto current_object = input_objects[j];
for (const auto& section_with_name : current_object.sections) {
auto current_subsection_name = section_with_name.first;
if (!starts_with(current_subsection_name, current_segment_name)) // 处理类似 .rodata.str1.1 的情况
continue;

auto current_section = section_with_name.second;
size_t section_size = 0;

// 获取当前段的大小
for (const auto& section_header : current_object.shdrs) {
if (section_header.name == current_subsection_name) {
section_size = section_header.size;
break;
}
}

// 将当前段的数据复制到合并段
current_merged_section.data.insert(current_merged_section.data.end(), current_section.data.begin(), current_section.data.end());

// 合并符号表
for (auto symbol : current_object.symbols) {
if (symbol.section != current_subsection_name)
continue;
if (symbol.type == SymbolType::LOCAL) // 对本地符号进行重命名
symbol.name = symbol.name + "@" + current_object.name;
symbol.offset += virtual_address;

auto existing_symbol = global_symbols.find(symbol.name);
if (existing_symbol == global_symbols.end() ||
(existing_symbol->second.type == SymbolType::UNDEFINED && (symbol.type == SymbolType::GLOBAL || symbol.type == SymbolType::WEAK)) ||
(existing_symbol->second.type == SymbolType::WEAK && symbol.type == SymbolType::GLOBAL)) {
global_symbols[symbol.name] = symbol;
}

if (existing_symbol != global_symbols.end() &&
existing_symbol->second.type == SymbolType::GLOBAL &&
symbol.type == SymbolType::GLOBAL) {
throw std::runtime_error("Multiple definitions of strong symbol: " + symbol.name);
}
current_merged_section.has_symbols = true;
}

// 合并重定位项
for (auto reloc : current_section.relocs) {
reloc.offset += section_offset;
if (local_symbol_sets[j].count(reloc.symbol) == 1) // 对本地符号重命名
reloc.symbol = reloc.symbol + "@" + current_object.name;
current_merged_section.relocs.push_back(reloc);
}

// 更新当前段偏移量和虚拟地址
section_offset += section_size;
virtual_address += section_size;
}
}

// 更新最终的可执行文件中的段
linked_object.sections[current_segment_name] = current_merged_section;
ProgramHeader header;
header.name = current_segment_name;
header.vaddr = segment_vaddr[i];
header.size = section_offset;
header.flags = segment_flags[i];
linked_object.phdrs.push_back(header);

// 对齐虚拟地址
virtual_address = (virtual_address + 0xfff) & ~0xfff;
}

// 3. 处理重定位
for (int i = 0; i < 4; i++) {
auto section_name = segment_names[i];
auto& section = linked_object.sections[section_name];
for (const auto& reloc : section.relocs) {
if (global_symbols.count(reloc.symbol) == 0) {
auto symbol_name = reloc.symbol;
symbol_name = symbol_name.substr(0, symbol_name.find('@')); // 重命名回本地符号
throw std::runtime_error("Undefined symbol: " + symbol_name);
}

auto symbol = global_symbols.at(reloc.symbol);
assert(symbol.type != SymbolType::UNDEFINED);
int size = reloc.type == RelocationType::R_X86_64_64 ? 8 : 4;

symbol.offset -= reloc.type == RelocationType::R_X86_64_PC32 ? segment_vaddr[i] + reloc.offset : 0;
symbol.offset += reloc.addend;

// 写入重定位值(按小端序存储)
for (int j = 0; j < size; j++) {
section.data[reloc.offset + j] = symbol.offset & 0xff;
symbol.offset >>= 8;
}
}

// 清空当前段的重定位项
section.relocs.clear();
}

// 4. 设置程序入口点并返回可执行文件
linked_object.type = ".exe";
for (const auto& symbol : global_symbols) {
if (symbol.second.name == "_start") {
linked_object.entry = symbol.second.offset;
break;
}
}

FLEObject FLE_ld(const std::vector<FLEObject>& objects)
{
// TODO: 实现链接器
// 1. 收集和合并段
// - 遍历所有输入对象的段
// - 按段名分组并计算偏移量
// - 设置段的属性(读/写/执行)

// 2. 处理符号
// - 收集所有全局符号和局部符号
// - 处理符号冲突(强符号/弱符号)

// 3. 重定位
// - 遍历所有重定位项
// - 计算并填充重定位值
// - 注意不同重定位类型的处理

// 4. 生成可执行文件
// - 设置程序入口点(_start)
// - 确保所有必要的段都已正确设置

throw std::runtime_error("Not implemented");
}
return linked_object;
}
48 changes: 34 additions & 14 deletions src/student/nm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,39 @@

void FLE_nm(const FLEObject& obj)
{
// TODO: 实现符号表显示工具
// 1. 遍历所有符号
// - 跳过未定义符号 (section为空的情况)
// - 使用16进制格式显示符号地址
// 遍历符号表
for (const auto& symbol : obj.symbols) {
// 跳过未定义符号
if (symbol.section.empty()) {
continue; // 符号没有 section,表示是未定义符号
}

// 2. 确定符号类型字符
// - 处理弱符号: 代码段用'W',其他段用'V'
// - 根据段类型(.text/.data/.bss/.rodata)和符号类型(GLOBAL/LOCAL)确定显示字符
// - 全局符号用大写字母,局部符号用小写字母
// 确定符号类型和输出格式
std::string type;
if (symbol.type == SymbolType::WEAK) {
type = (symbol.section == ".text") ? "W" : "V";
} else if (symbol.type == SymbolType::GLOBAL) {
if (symbol.section == ".text") {
type = "T"; // 全局函数符号
} else if (symbol.section == ".data") {
type = "D"; // 全局数据符号
} else if (symbol.section == ".bss") {
type = "B"; // 全局未初始化数据符号
}
} else if (symbol.type == SymbolType::LOCAL) {
if (symbol.section == ".text") {
type = "t"; // 局部函数符号
} else if (symbol.section == ".data") {
type = "d"; // 局部数据符号
} else if (symbol.section == ".bss") {
type = "b"; // 局部未初始化数据符号
}
}

// 3. 按格式输出
// - [地址] [类型] [符号名]
// - 地址使用16位十六进制,左侧补0

throw std::runtime_error("Not implemented");
}
// 输出符号信息
// 确保地址是16位十六进制,左侧补零
std::cout << std::setw(16) << std::setfill('0') << std::hex << symbol.offset << " "
<< type << " "
<< symbol.name << std::endl;
}
}