diff --git a/articles/20230615-section-gc-part3.md b/articles/20230615-section-gc-part3.md
new file mode 100644
index 0000000000000000000000000000000000000000..44572e5808836226bd93312a8cea4eda4134d12c
--- /dev/null
+++ b/articles/20230615-section-gc-part3.md
@@ -0,0 +1,304 @@
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.1 - [spaces comments codeinline pangu]
+> Author: 谭源
+> Date: 2022/06/15
+> Revisor: Falcon
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+> Sponsor: PLCT Lab, ISCAS
+
+# Section GC 分析 —— 引用建立过程
+
+## 概述
+
+[上一篇文章](20230526-section-gc-part2.md) 我们介绍了在开启 `--gc-sections` 选项后,gold 链接器删除未使用到的 section 的过程。
+
+这篇文章我们将结合 `bfd` 链接器(即默认使用的 `ld`)源码,探索链接器建立引用关系的过程。
+
+## 准备工作
+
+### 编译
+
+```bash
+make all-ld -j
+```
+
+编译生成的 gold 链接器位于 `ld/ld-new`。
+
+### 配置调试环境
+
+编写一个用来测试的程序 `test.c`:
+
+```c
+int fun1()
+{
+ used = 1;
+ return 0;
+}
+
+int fun2()
+{
+ return 0;
+}
+
+int un_used(){
+ return 0;
+}
+
+int main(){
+ fun1();
+ fun2();
+ return 0;
+}
+```
+
+`fun1()` 和 `fun2()` 都被 `main()` 调用了,需要在 GC 过程中保留;`un_used()` 函数没有被使用过,需要在 GC 过程中删除。
+
+和上一篇文章一样,我们编写一个配置文件,让我们能直接在 VSCode 中进行调试。具体使用方法可以参考 [上一篇文章](20230526-section-gc-part2.md)。
+
+```json
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "GDB BFD",
+ "type": "cppdbg",
+ "request": "launch",
+ "program": "${workspaceFolder}/ld/ld-new",
+ "args": [
+ "--gc-sections",
+ "-dynamic-linker",
+ "/lib64/ld-linux-x86-64.so.2",
+ "-pie",
+ "/usr/lib/gcc/x86_64-pc-linux-gnu/13.1.1/../../../../lib/Scrt1.o",
+ "/usr/lib/gcc/x86_64-pc-linux-gnu/13.1.1/../../../../lib/crti.o",
+ "/usr/lib/gcc/x86_64-pc-linux-gnu/13.1.1/crtbeginS.o",
+ "-L/usr/lib/gcc/x86_64-pc-linux-gnu/13.1.1",
+ "-L/usr/lib/gcc/x86_64-pc-linux-gnu/13.1.1/../../../../lib",
+ "-L/lib/../lib",
+ "-L/usr/lib/../lib",
+ "-L/usr/lib/gcc/x86_64-pc-linux-gnu/13.1.1/../../..",
+ "test.o",
+ "-lgcc_s",
+ "-lc",
+ "-lgcc",
+ "/usr/lib/gcc/x86_64-pc-linux-gnu/13.1.1/crtendS.o",
+ "/usr/lib/gcc/x86_64-pc-linux-gnu/13.1.1/../../../../lib/crtn.o"
+ ],
+ "cwd": "${workspaceFolder}",
+ "setupCommands": [
+ {
+ "description": "Enable pretty-printing for gdb",
+ "text": "-enable-pretty-printing"
+ }
+ ],
+ "stopAtEntry": false
+ }
+ ]
+}
+```
+
+### 术语解释
+
+- 符号(Symbol):符号通常指代一个变量或者函数的名称。例如,在 C 语言中,当声明一个函数或变量,编译器会把它们的名称保存为符号。符号表是一个保存了所有符号及其相关信息的数据结构,链接器主要通过它来寻找和解决引用。
+
+- 重定位(Relocation):在编译和链接过程中,重定位是一个重要步骤。当编译器编译源代码时,它并不知道每个符号最后会被放置在内存的什么位置。因此,编译器生成的对象文件中,会包含一些需要在链接过程中被填充真正地址的占位符,这些占位符就需要重定位。例如,如果一个函数调用了另一个函数,编译器在编译时可能并不知道被调用的函数在内存中的真正地址,所以它会生成一个占位符。然后在链接过程中,链接器会找到被调用函数的真正地址,替换掉占位符,完成重定位。
+
+- 重定位条目(Relocation Entry):汇编器遇到最终位置未知的目标引用,会生成一个重定位条目,告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。
+
+ ```C
+ typedef struct
+ {
+ Elf64_Addr r_offset; // 需要被修改的引用的节偏移
+ Elf64_Xword r_info; // 存储符号表索引和重定位类型。
+ Elf64_Sxword r_addend;
+ } Elf64_Rela;
+ ```
+
+## 函数调用链分析
+
+`elflink.c` 中的 `_bfd_elf_gc_mark()` 函数显而易见是用来标记已经用到的 section 的。
+
+```C
+bool
+_bfd_elf_gc_mark (struct bfd_link_info *info,
+ asection *sec,
+ elf_gc_mark_hook_fn gc_mark_hook)
+{
+ bool ret;
+ asection *group_sec, *eh_frame;
+
+ sec->gc_mark = 1;
+
+ /* Mark all the sections in the group. */
+ group_sec = elf_section_data (sec)->next_in_group;
+ if (group_sec && !group_sec->gc_mark)
+ if (!_bfd_elf_gc_mark (info, group_sec, gc_mark_hook))
+ return false;
+
+ /* Look through the section relocs. */
+ ret = true;
+ eh_frame = elf_eh_frame_section (sec->owner);
+ if ((sec->flags & SEC_RELOC) != 0
+ && sec->reloc_count > 0
+ && sec != eh_frame)
+ {
+ struct elf_reloc_cookie cookie;
+
+ if (!init_reloc_cookie_for_section (&cookie, info, sec))
+ ret = false;
+ else
+ {
+ for (; cookie.rel < cookie.relend; cookie.rel++)
+ if (!_bfd_elf_gc_mark_reloc (info, sec, gc_mark_hook, &cookie))
+ {
+ ret = false;
+ break;
+ }
+ fini_reloc_cookie_for_section (&cookie, sec);
+ }
+ }
+
+ if (ret && eh_frame && elf_fde_list (sec))
+ {
+ struct elf_reloc_cookie cookie;
+
+ if (!init_reloc_cookie_for_section (&cookie, info, eh_frame))
+ ret = false;
+ else
+ {
+ if (!_bfd_elf_gc_mark_fdes (info, sec, eh_frame,
+ gc_mark_hook, &cookie))
+ ret = false;
+ fini_reloc_cookie_for_section (&cookie, eh_frame);
+ }
+ }
+
+ eh_frame = elf_section_eh_frame_entry (sec);
+ if (ret && eh_frame && !eh_frame->gc_mark)
+ if (!_bfd_elf_gc_mark (info, eh_frame, gc_mark_hook))
+ ret = false;
+
+ return ret;
+}
+```
+
+我们暂时不关心它的逻辑,先看看它的调用链。
+
+在该函数处打断点,一直 continue 到 `sec.name` 为 `.text.main`。
+
+
+
+可以看到左下方的调用栈,有两个 `_bfd_elf_gc_mark()` 在栈中,`r_offset` 为 10。
+
+如果在 13829 行继续运行,进入函数 `_bfd_elf_gc_mark_reloc()` 后,该函数又会调用一次 `_bfd_elf_gc_mark()`。
+
+
+
+这又向调用栈压入了两个 frame,有了三个 `_bfd_elf_gc_mark()` 栈。点击左侧的 Call Stack 某一项可以切换栈,查看不同栈的值。
+
+| `frame` | `sec.name` |
+|-----------|--------------|
+| `frame 5` | `.text.fun1` |
+| `frame 3` | `.text.main` |
+| `frame 1` | `.text` |
+
+上表是不同 frame 下变量 `sec.name` 的值,表示当前 frame 处理的 section 名。说明此时压入了栈正在处理 `.text.fun1`。
+
+
+
+等到 `frame 5` 和 `frame 4` 执行完,返回到 `frame 3` 执行时,for 循环 `cookie.rel` 执行了++操作,这里又开始遍历 `.text.main` 的下一个引用。从上图我们可以得知,该引用项的 `r_offset` 为 20。这里调用 `_bfd_elf_gc_mark_reloc()` 函数,该函数又会调用 `_bfd_elf_gc_mark()` 来处理这个引用,即压入新的栈,重新建立了 `frame 4` 和 `frame 5`。
+
+
+
+下表是重新建立 `frame 5` 后当前调用栈的状态。和之前表不同,此时 `frame 5` 的 `sec.name` 值为 `.text.fun2`。
+
+| `frame` | `sec.name` |
+|-----------|--------------|
+| `frame 5` | `.text.fun2` |
+| `frame 3` | `.text.main` |
+| `frame 1` | `.text` |
+
+据此可以推测出,这里是在递归扫描 section 引用到的其他 section,即扫描一个 section 时,会将当前 section 的 gc_mark 置为 1,然后遍历该 section 的引用(压入调用栈),直到栈空且 for 循环执行完毕,对该 section 的扫描才结束。
+
+## 数据结构和代码解析
+
+遍历当前 section 引用到的 section 是 `_bfd_elf_gc_mark()` 函数中的这段代码完成的:
+
+```C
+ for (; cookie.rel < cookie.relend; cookie.rel++)
+ if (!_bfd_elf_gc_mark_reloc (info, sec, gc_mark_hook, &cookie))
+ {
+ ret = false;
+ break;
+ }
+```
+
+`_bfd_elf_gc_mark()` 函数会调用 `_bfd_elf_gc_mark_reloc()` 函数
+
+这里 `cookie` 的类型是 `elf_reloc_cookie`:
+
+```c
+struct elf_reloc_cookie
+{
+ Elf_Internal_Rela *rels, *rel, *relend; // 表示 ELF 文件中的重定位条目。分别表示重定位条目数组的开始、末尾,和当前处理的重定位条目
+ Elf_Internal_Sym *locsyms; // ELF 文件中的本地符号表。
+ bfd *abfd;
+ size_t locsymcount;
+ size_t extsymoff;
+ struct elf_link_hash_entry **sym_hashes;
+ int r_sym_shift;
+ bool bad_symtab;
+};
+```
+
+那么这个循环的目的是遍历所有的重定位条目(从 `cookie.rel` 到 `cookie.relend` 之间的所有条目)。在每次循环中,都会调用 `_bfd_elf_gc_mark_reloc` 函数对当前的重定位条目进行处理。
+
+下表是处理到 `.text.fun2` 时,栈的情况:
+
+| `frame` | 调用函数 | 处理对象 |
+|-----------|----------------------------|--------------|
+| `frame 5` | `_bfd_elf_gc_mark()` | `.text.fun2` |
+| `frame 4` | `_bfd_elf_gc_mark_reloc()` | `.text.fun2` |
+| `frame 3` | `_bfd_elf_gc_mark()` | `.text.main` |
+| `frame 5` | `_bfd_elf_gc_mark_reloc()` | `.text.main` |
+| `frame 1` | `_bfd_elf_gc_mark()` | `.text` |
+
+## ELF 中的重定位条目
+
+经过上面的解析,我们可以知道链接器是通过重定位条目来得知一个 section 引用了哪些其他 section 的。重定位条目其实就存储在 ELF 文件中。
+
+```bash
+$readelf -r test.o
+
+Relocation section '.rela.text.main' at offset 0x278 contains 2 entries:
+ Offset Info Type Sym. Value Sym. Name + Addend
+00000000000a 000600000004 R_X86_64_PLT32 0000000000000000 fun1 - 4
+000000000014 000700000004 R_X86_64_PLT32 0000000000000000 fun2 - 4
+
+Relocation section '.rela.eh_frame' at offset 0x2a8 contains 4 entries:
+ Offset Info Type Sym. Value Sym. Name + Addend
+000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text.fun1 + 0
+000000000040 000300000002 R_X86_64_PC32 0000000000000000 .text.fun2 + 0
+000000000060 000400000002 R_X86_64_PC32 0000000000000000 .text.un_used + 0
+000000000080 000500000002 R_X86_64_PC32 0000000000000000 .text.main + 0
+```
+
+从这个命令的输出我们可以得到下表:
+
+| Sym. Name | Offset 十六进制 | Offset 十进制 |
+|-----------|-----------------|---------------|
+| `fun1` | 00000000000a | 10 |
+| `fun2` | 000000000014 | 20 |
+
+这和函数调用链分析中的值分别为 10 和 20 的 `r_offset` 相同,同时 `.rela.text.main` 的条目项没有 `un_used`。说明链接器就是读取的这部分信息来解析引用关系的。
+
+## 总结
+
+我们通过研究链接器链接一个简单程序的例子,从源码层面分析了开启 `--gc-sections` 选项后链接器是如何确定一个函数的 section 引用了哪些其他函数 section 的。
+
+链接器会从 ELF 文件中的重定位条目中解析处理引用信息。
+
+其实对于全局变量来说,链接器会做一样的操作。`-fdata-sections` 选项会把每个全局变量放入单独的 `.bss` section 中。假如 `fun1()` 使用了全局变量 used,那么在遍历 `fun1()` 的引用时就会解析 `.bss.used` section。
+
+## 参考资料
+
+- Tiny Linux Kernel Project: Section Garbage Collection Patchset
diff --git a/articles/images/20230615-section-gc-part3/image-20230601152244101.png b/articles/images/20230615-section-gc-part3/image-20230601152244101.png
new file mode 100644
index 0000000000000000000000000000000000000000..566f28bda83643411122e078442c85713666097a
Binary files /dev/null and b/articles/images/20230615-section-gc-part3/image-20230601152244101.png differ
diff --git a/articles/images/20230615-section-gc-part3/image-20230601153457712.png b/articles/images/20230615-section-gc-part3/image-20230601153457712.png
new file mode 100644
index 0000000000000000000000000000000000000000..26d8316a426790cdfd8cfac39e274c7c86906ff2
Binary files /dev/null and b/articles/images/20230615-section-gc-part3/image-20230601153457712.png differ
diff --git a/articles/images/20230615-section-gc-part3/image-20230601222533263.png b/articles/images/20230615-section-gc-part3/image-20230601222533263.png
new file mode 100644
index 0000000000000000000000000000000000000000..c50b437f9cb22e43d3c267618e9a1de89b7ad40f
Binary files /dev/null and b/articles/images/20230615-section-gc-part3/image-20230601222533263.png differ
diff --git a/articles/images/20230615-section-gc-part3/image-20230615160546236.png b/articles/images/20230615-section-gc-part3/image-20230615160546236.png
new file mode 100644
index 0000000000000000000000000000000000000000..30341018368f3618a8af5774cc98fe68ce1660d8
Binary files /dev/null and b/articles/images/20230615-section-gc-part3/image-20230615160546236.png differ