diff --git a/articles/20230928-section-gc-no-more-keep-part2.md b/articles/20230928-section-gc-no-more-keep-part2.md
new file mode 100644
index 0000000000000000000000000000000000000000..88ebc299346b68c5e0da4163ad340dba35512a82
--- /dev/null
+++ b/articles/20230928-section-gc-no-more-keep-part2.md
@@ -0,0 +1,260 @@
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [codeblock pangu]
+> Author: Yuan Tan
+> Date: 20230929
+> Revisor: Falcon
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+> Sponsor: PLCT Lab, ISCAS
+
+# 解决 Linux 内核 Section GC 失败问题 - Part 2
+
+## 背景简介
+
+前面几篇文章介绍了 Section GC 的使用方法和原理,以及 Linux 内核中的 Section GC 失败问题。
+
+要彻底解决这个问题,我们需要让 `.pushsection` 能够正确的建立引用关系,避免强制保留的使用,以杜绝依赖反转。
+
+经过翻阅文档和社区的讨论,我们总结了两种能够手动建立引用关系的方法。
+
+## 解决方案
+
+### Section Group
+
+在同一个 section group 中的节只要有一个节被保留,那么 group 中的所有节都会被保留,这是我们解决 Section GC 失败的有力工具。
+
+section 所属的 section group 可以在汇编创建 section 的时候指定。
+
+```assembly
+.section name , "flags"G, @type, GroupName[, linkage]
+```
+
+或者使用 flag `?`,让该 section 跟随父 section 的 section group。
+
+```assembly
+.section name , "flags"?
+```
+
+现在的思路是:
+
+1. 为父函数增加 section group 属性。
+2. 使用 flag `?`,让 Pushed Section 跟随父 Section 的 section group。
+
+这样两个节就能被同时保留。
+
+有两种方法可以完成第一步,给 C 语言的函数添加 section group 属性。
+
+- 方法一:
+
+使用汇编指令 `.attach_to_group name`,在 section 已经创建完毕后为其添加 group。
+
+```C
+int fun1() {
+ asm(".attach_to_group \"MyGroup\"");
+
+ asm(".pushsection .test,\"ax?\",@progbits\n"
+ ".string \"hello\"\n"
+ ".popsection");
+ return 0;
+}
+
+int main() {
+ fun1();
+ return 0;
+}
+```
+
+```bash
+$ riscv64-linux-gnu-gcc -ffunction-sections -Wl,--gc-sections example.c -c
+$ riscv64-linux-gnu-readelf -g example.o
+
+group section [ 1] `.group' [MyGroup] contains 2 sections:
+ [Index] Name
+ [ 5] .text.fun1
+ [ 6] .test
+$ riscv64-linux-gnu-gcc -ffunction-sections -Wl,--gc-sections,--print-gc-sections example.c
+ld: removing unused section '.rodata.cst4' in file '/usr/riscv64-linux-gnu/usr/lib/Scrt1.o'
+ld: removing unused section '.riscv.attributes' in file '/usr/lib/gcc/riscv64-linux-gnu/12.2.0/crti.o'
+ld: removing unused section '.riscv.attributes' in file '/usr/lib/gcc/riscv64-linux-gnu/12.2.0/crtn.o'
+```
+
+`.test` 没有被 GC,和 group 中的 `.text.main` 一起被保留了下来。
+
+- 方法二:
+
+不推荐该方法。
+
+在 `__attribute__((section("section-name)))` 中写汇编,加入 flag 和 GroupName,然后用类似 SQL 注入的方式,手动添加 `#` 来截断编译器生成的该行后续的汇编代码。
+
+```C
+int __attribute__((section(".text.fun1,\"axG\",@progbits,\"MyGroup\" #")))fun1()
+{
+ asm(".pushsection .test1,\"axG\",@progbits,\"MyGroup\"\n"
+ " .string \"hello\"\n"
+ ".popsection");
+ return 0;
+}
+```
+
+生成的汇编代码为:
+
+```assembly
+.section .text.fun1,"axG",@progbits,"MyGroup" #,"ax",@progbits
+```
+
+汇编器实际上处理的是
+
+```assembly
+.section .text.fun1,"axG",@progbits,"MyGroup"
+```
+
+#### 有缺陷的解决方案
+
+上一篇文章中提到,`__ex_table` 的 `.pushsection` 引入了依赖反转问题,它的建立方式是这样定义的:
+
+```C
+// arch/riscv/include/asm/asm-extable.h:14
+
+#define __ASM_EXTABLE_RAW(insn, fixup, type, data) \
+ ".pushsection __ex_table, \"a\"\n" \
+ ".balign 4\n" \
+ ".long ((" insn ") - .)\n" \
+ ".long ((" fixup ") - .)\n" \
+ ".short (" type ")\n" \
+ ".short (" data ")\n" \
+ ".popsection\n"
+```
+
+要让它和父函数在一个 section group 中,和父函数同时被保留下来,最简单的做法是,直接在该宏里增加 `.attach_to_group "GroupName"`,同时在 `.pushsection` 的 flag 中增加 `?`:
+
+```c
+#define __ASM_EXTABLE_RAW(insn, fixup, type, data) \
+ ".attach_to_group GroupName" \
+ ".pushsection __ex_table, \"a?\"\n" \
+ ".balign 4\n" \
+ ".long ((" insn ") - .)\n" \
+ ".long ((" fixup ") - .)\n" \
+ ".short (" type ")\n" \
+ ".short (" data ")\n" \
+ ".popsection\n"
+```
+
+这样无论该宏在何处展开,都会为 Section Pusher 增加一个 group,并且和 `.pushsection` 在同一个 group 中。
+
+每个 Section Pusher 都应该处于不同的 group,否则一个 Section Pusher 被保留,其他没被使用到的 Section Pusher 也被保留了。
+
+此外,这个宏可能会在一个函数内多次展开,即 Section Pusher 调用了多次 `.pushsection`,那么 Section Pusher 就会执行多次 `.attach_to_group`,链接器会产生警告。我们希望 `.attach_to_group` 在一个函数里只执行一次。
+
+- 如果仅使用文件名作为 GroupName,并且使用 ifdef 来辅助判断当前 Section Pusher 是否已经在 group 中,如果不在,则 `.attach_to_group`。那么一个文件的所有函数都会加入一个 group 里,会被同时 GC 或者保留,不满足需求。
+- 如果使用文件名和行号作为 GroupName,宏仍可能一个函数内展开多次,且无法判断当前 Section Pusher 是否在 group 中。
+
+因此我们需要一个**函数级别**独特的字符串来作为 GroupName。就能使用 ifdef 来辅助判断当前 Section Pusher 是否已经在 group 中
+
+函数名是做容易想到的方法,但是无论是 `__func__` 或者 `__FUNCTION__` 都不是宏,是在编译时候才能确定的。因此我们无法使用函数名来作为 GroupName。我们只能暂时使用类似文件名和行号的形式。
+
+```
+#define ___PASTE(a,b) a##b
+#define __PASTE(a,b) ___PASTE(a,b)
+
+#ifndef __UNIQUE_ID
+# define __UNIQUE_ID __PASTE(__PASTE(__COUNTER__, _), __LINE__)
+#endif
+
+#define __ASM_EXTABLE_RAW(insn, fixup, type, data) \
+ ".attach_to_group "__stringify(__UNIQUE_ID_Extable)"\n" \
+ ".pushsection __ex_table, \"a?\"\n" \
+ ".balign 4\n" \
+ ".long ((" insn ") - .)\n" \
+ ".long ((" fixup ") - .)\n" \
+ ".short (" type ")\n" \
+ ".short (" data ")\n" \
+ ".popsection\n"
+```
+
+这样编译后会出现警告:
+
+```
+Warning: section .text.main already has a group (GroupName)
+```
+
+内核社区是不接受有警告的代码存在的,而且链接器并未提供选项来关闭这个警告,所有这个方案虽然能解决问题,但并不能合入主线。
+
+### SHF_LINK_ORDER
+
+查看 as 的 [文档][001],可以查看 `.pushsection` 和 `.section` 的定义。
+
+```
+.pushsection name [, subsection] [, "flags"[, @type[,arguments]]]
+.section name [, "flags"[, @type[,flag_specific_arguments]]]
+```
+
+`flags` 中有一个符合我们的要求:
+
+```
+o
+section references a symbol defined in another section (the linked-to section) in the same file.
+
+If flags contains the o flag, then the type argument must be present along with an additional field like this:
+
+.section name,"flags"o,@type,SymbolName|SectionIndex
+```
+
+我们可以使用该 flag 来手动指定该 section 引用到了 Section Pusher,来建立引用。
+
+```C
+void unused_function() {
+ return;
+}
+
+int main() {
+ asm("Section_Pusher_Symbol:\n");
+ asm(".pushsection .should_not_GC,\"ao\",@progbits,Section_Pusher_Symbol\n"
+ ".popsection");
+ return 0;
+}
+```
+
+```
+$ riscv64-linux-gnu-gcc -ffunction-sections -Wl,--gc-sections,--print-gc-sections example_shf.c
+ld: removing unused section '.rodata.cst4' in file '/usr/lib/gcc-cross/riscv64-linux-gnu/11/../../../../riscv64-linux-gnu/lib/Scrt1.o'
+ld: removing unused section '.riscv.attributes' in file '/usr/lib/gcc-cross/riscv64-linux-gnu/11/crti.o'
+ld: removing unused section '.text.unused_function' in file '/tmp/cceocy7f.o'
+ld: removing unused section '.riscv.attributes' in file '/usr/lib/gcc-cross/riscv64-linux-gnu/11/crtn.o'
+```
+
+在这段代码中,我们在 `main()` 中新建了一个 label `Section_Pusher_Symbol`,然后在 `.pushsection` 使用 `o` flag,指定为该 symbol。
+
+可以看到,在增加了 `o` flag 后,pushsed section 没有被 GC,实现了项目目标。
+
+## 在 Linux 内核中验证
+
+Linux Lab 已经集成了基于上述两种方案的 Patch。切换到 Linux Lab 的 section-gc 分支即可进行验证。
+
+```bash
+git checkout section-gc
+```
+
+启用 dse feature 并编译:
+
+```
+make test b=riscv64/virt f=dse LINUX=v6.6-rc2
+```
+
+查看保留的系统调用数量:
+
+```
+$ nm build/riscv64/virt/linux/v6.6-rc2/vmlinux | grep "T __riscv_sys" | grep -v sys_ni_syscall | wc -l
+40
+```
+
+结果表明,裁剪掉了许多系统调用,验证了方案可行性。
+
+## 总结
+
+这篇文章中我们提出了两种解决 Linux 内核 Section GC 失败问题方法。它们能在不产生副作用的情况下,避免 `KEEP` 的使用,让所有节建立正确的依赖关系,为链接器提供更多的信息。
+
+## 参考资料
+
+- [Section (Using as)][001]
+- [\[PATCH 0/1\] gas: add new command line option --no-group-check][002]
+
+[001]: https://sourceware.org/binutils/docs/as/Section.html
+[002]: https://sourceware.org/pipermail/binutils/2023-July/128521.html