回顾: 从C代码到指令序列
预处理 -> 编译 -> 汇编 -> 链接 -> 执行
AM项目有那么多源文件, 最后如何生成一个可执行文件?
本次课内容:
代码 + 数据
没有了吗?
有很多实际的需求
Enable debug information
会影响NEMU的大小addr2line
根据调试信息将地址转换为源文件位置(gdb)
需要更多的数据
ld - the GNU linker.
as - the GNU assembler.
addr2line - Converts addresses into filenames and line numbers.
ar - A utility for creating, modifying and extracting from archives.
c++filt - Filter to demangle encoded C++ symbols.
dlltool - Creates files for building and using DLLs.
elfedit - Allows alteration of ELF format files.
gprof - Displays profiling information.
gprofng - Collects and displays application performance data.
nlmconv - Converts object code into an NLM.
nm - Lists symbols from object files.
objcopy - Copies and translates object files.
objdump - Displays information from object files.
ranlib - Generates an index to the contents of an archive.
readelf - Displays information from any ELF format object file.
size - Lists the section sizes of an object or archive file.
strings - Lists printable strings from files.
strip - Discards symbols.
对系统程序员很有用
objcopy
可以生成Verilog中readmemh()
的输入需求: 代码和数据分离
节 | 说明 | | | 节 | 说明 |
---|---|---|---|---|
.text | 代码 | | | .bss | 未初始化数据 |
.data | 可写数据 | | | .debug | 调试信息 |
.rodata | 只读数据 | | | .line | 行号信息 |
.bss - Block Starting Symbol的缩写(section.c)
C99.6.7.8 #10 - 未初始化的全局/静态变量的初值为0
If an object that has automatic storage duration is not initialized explicitly, its
value is indeterminate. If an object that has static storage duration is not
initialized explicitly, then:
- if it has pointer type, it is initialized to a null pointer;
- if it has arithmetic type, it is initialized to (positive or unsigned) zero;
- if it is an aggregate, every member is initialized (recursively) according to these
rules;
- if it is a union, the first named member is initialized (recursively) according to
these rules.
Q1: 如果节的名称太长, 怎么办?
A1: 把元数据的字符串集中放在另一个区域
name
换成一个 “指针”
name
换成一个偏移量
.strtab
(string
table)Q2: 可以通过节头找到一个节, 那该如何找到节头?
A2: 约定一个位置
Q3: 找到第一个节头后, 如何找到下一个?
A3: 需要考虑如何组织多个节头?
RTFM
man 5 elf
预处理 -> 编译 -> 汇编 -> 链接 -> 执行
编译和汇编均以文件为单位进行,
标记*
的指令/数据无法得知最终地址
袁春风, 南京大学《计算机系统基础》第四章 程序的链接
为了让链接器解析符号, 汇编器需将符号信息记录到ELF目标文件中
.symtab
节 - 符号表(symbol table),
每一个表项记录一个符号的信息
.strtab
节中的位置UND
- 该符号未解析链接器维护两个集合: D(已定义的符号)和U(未解析的符号)
链接器扫描参与链接的文件, 查看其符号表, 分析每个符号的定义情况
x
, 按下表进行操作所属节确定 | 所属节为UND |
|
---|---|---|
均不在D或U中 | 加入D | 加入U |
已在U中 | 将U中的x 解析为当前x ,
并加入D |
加入U |
已在D中 | 多重定义错误 | 解析为D中的x |
multiple definition of xxx
undefined reference to xxx
但如果链接库函数, 就会遇到新的问题(假设不支持动态链接)
.o
文件中
/usr/lib/x86_64-linux-gnu/libc-*.so
接近2MB.o
打包成一个.a
文件,
用户只需要指定这个.a
文件.o
文件,
没用到的.o
文件不参与链接AM将程序自身的.o
,
am-xxx.a
和klib-xxx.a
链接成可执行文件
原因: 链接大量文件时开销较大
.o
文件.o
文件-(
和-)
启用多趟解析(时间开销增大),
具体查阅man ld
带-fcommon
时,
gcc将试探性定义的符号放到目标文件的COMMON块
COM
符号解析时
试探性定义是C标准定义的, 但在实际开发中是个大坑
-fno-common
选项,
将这种变量直接放到.bss
-fcommon
static
修饰(局部符号不参与符号解析)extern
修饰x86-qemu
的AM定义了一个叫cpus
的全局变量,
把做oslab的同学坑惨了 😂例子: AM的链接脚本
.
代表当前地址ENTRY(_start)
ENTRY(_start)
PHDRS { text PT_LOAD; data PT_LOAD; }
SECTIONS {
/* _pmem_start and _entry_offset
are defined in LDFLAGS */
. = _pmem_start + _entry_offset;
.text : {
*(entry)
*(.text*)
} : text
.rodata : { *(.rodata*) }
.data : { *(.data) } : data
.bss : {
_bss_start = .;
*(.bss*)
*(.sbss*)
*(.scommon)
}
_stack_top = ALIGN(0x1000);
. = _stack_top + 0x8000;
_stack_pointer = .;
_end = .;
_heap_start = ALIGN(0x1000);
}
合并节后, 节中的符号相对于该节的位置会有变动
同时也要重填那些引用符号的位置
.relxxx
和.relaxxx
节
xxx
是需要重填的节名称不同的重定位类型需要解不同的方程, 填不同形状的坑
R_RISCV_HI20
/R_RISCV_LO12_I
/R_RISCV_LO12_S
/…以f.o
中访问pa
变量为例,
需要计算offset
, 使得以下assert成立:
算出offset
后,
按照lui
和lw
(I型立即数)的指令格式填进去即可
0x10
的sw
指令要填S型立即数
如果f
距离调用处很近,
感觉用一条jal
指令就可以了?
offset
可以用21位有符号立即数表示,
就可以省一条指令了R_RISCV_RELAX
来指示链接器允许松弛
其他松弛的例子:
auipc ra, 0
+ jalr ra, ra, 0
->
jal ra, offset
auipc ra, 0
+ jalr ra, ra, 0
->
c.jal ra, offset
auipc ra, 0
+ jalr x0, ra, 0
->
c.j ra, offset
lui t0, 0
+ lw t1, 0(t0)
->
lw t1, offset(gp)
采用call
伪指令能编译出auipc
+jalr
,
能寻址当前PC±2GB的范围
这就带来一个权衡: 代码大小 vs. 寻址范围
指令序列 | 寻址范围 |
---|---|
jal |
当前PC±1MB |
auipc +jalr |
当前PC±2GB |
更多指令 | 更大范围 |
code model用来告诉编译器如何选择
RISC-V目前常用的code model有
medlow
- 程序位于0±2GB范围,
可通过lui
寻址medany
- 程序位于任意位置±2GB范围,
可通过auipc
寻址-mcmodel=xxx
来选择(RTFM)x86-64还有一个叫large的code model, 编译器对程序位置和大小不作假设