diff --git a/articles/20230919-elf2flt-elf2flt-src-analysis.md b/articles/20230919-elf2flt-elf2flt-src-analysis.md
new file mode 100644
index 0000000000000000000000000000000000000000..14f4870c2bea4ab5bcb71408c443cf57a859894c
--- /dev/null
+++ b/articles/20230919-elf2flt-elf2flt-src-analysis.md
@@ -0,0 +1,361 @@
+> Corrector: [TinyCorrect](https://gitee.com/tinylab/tinycorrect) v0.2-rc2 - [toc comments codeinline refs pangu]
+> Author: Odysseus <320873791@qq.com>
+> Date: 2023/09/19
+> Revisor: walimis <>
+> Project: [RISC-V Linux 内核剖析](https://gitee.com/tinylab/riscv-linux)
+> Proposal: [为 ELF2FLT 完善独立编译与安装支持](https://gitee.com/tinylab/riscv-linux/issues/I79PO2)
+> Sponsor: PLCT Lab, ISCAS
+
+# 从源码看 elf2flt 原理
+
+## 前言
+
+`elf2flt` 是一个专门设计用于将 ELF 二进制格式转换为 bFLT 二进制格式的工具,目的是使得二进制文件能够在没有内存管理单元 (MMU) 的 CPU 上正常运行。虽然 elf2flt 的代码总量达到了约两千行,但是其中的大部分代码是用于支持不同的 CPU 架构的。在本文中,我们将专门针对 RISC-V 架构进行深入探讨。
+
+## 目录结构
+
+elf2flt 的目录结构如下所示:
+
+```bash
+.
+├── elf2flt
+│ ├── compress.c
+│ ├── compress.h
+│ ├── config.guess
+│ ├── config.sub
+│ ├── configure
+│ ├── configure.ac
+│ ├── e1-elf2flt.ld
+│ ├── elf
+│ │ ├── reloc-macros.h
+│ │ └── riscv.h
+│ ├── elf2flt.c
+│ ├── elf2flt.ld.in
+│ ├── filenames.h
+│ ├── flat.h
+│ ├── flthdr.c
+│ ├── install-sh
+│ ├── ld-elf2flt.c
+│ ├── ld-elf2flt.in
+│ ├── LICENSE.TXT
+│ ├── Makefile.in
+│ ├── README.md
+│ ├── README_riscv64.md
+│ ├── stubs.c
+│ ├── stubs.h
+│ ├── tests
+│ │ ├── flthdr
+│ │ │ ├── basic.good
+│ │ │ ├── generate.c
+│ │ │ ├── multi.good
+│ │ │ └── test.sh
+│ │ └── lib.sh
+│ └── travis
+│ ├── arches.sh
+│ ├── lib.sh
+│ └── main.sh
+```
+
+从目录结构我们可以看出,程序的主要入口是 `elf2flt.c`,这是一个大约有两千行 C 代码的文件。 `tests` 和 `travis` 目录都是作测试用途。
+
+## 文件解析
+
+程序首先对 ELF 文件进行解析,其主要的解析过程如下:
+
+1. 定义了一系列的变量用于存储输入文件和其他相关的信息。
+2. 初始化这些变量并准备进行文件解析。
+3. 使用 `bfd` 库来读取和解析输入文件的符号表和段信息。
+4. 根据段的属性(如代码段、数据段、`.bss` 段等)来确定段的位置和大小。
+5. 根据解析得到的信息,将这些段的内容读取到相应的内存区域中。
+
+首先,我们来看 `main` 函数。在函数前段的定义中,包含了输入输出文件、操作流、操作、栈、符号表、各个数据段的大小和起止、数据段地址、重定位地址等变量。
+
+```c
+ int fd;
+ bfd *rel_bfd, *abs_bfd;
+ asection *s;
+ char *ofile=NULL, *pfile=NULL, *abs_file = NULL, *rel_file = NULL;
+ char *fname = NULL;
+ int opt;
+ int i;
+ int stack;
+ stream gf;
+
+ asymbol **symbol_table;
+ long number_of_symbols;
+
+ uint32_t data_len = 0;
+ uint32_t bss_len = 0;
+ uint32_t text_len = 0;
+ uint32_t reloc_len;
+
+ uint32_t data_vma = ~0;
+ uint32_t bss_vma = ~0;
+ uint32_t text_vma = ~0;
+
+ uint32_t text_offs;
+
+ void *text;
+ void *data;
+ uint32_t *reloc;
+```
+
+在对栈空间进行分配后,进行常规的参数处理,通过 `bfd` 库对输入文件格式进行错误检查。之后,我们开始正式进行转换。
+
+转换的第一步,是解析输入文件,获取符号表和段表信息。
+
+```C
+ symbol_table = get_symbols(abs_bfd, &number_of_symbols);
+
+ /* Group output sections into text, data, and bss, and calc their sizes. */
+ for (s = abs_bfd->sections; s != NULL; s = s->next) {
+ uint32_t *vma, *len;
+ bfd_size_type sec_size;
+ bfd_vma sec_vma;
+
+ if ((s->flags & SEC_CODE) ||
+ ro_reloc_data_section_should_be_in_text(s)) {
+ vma = &text_vma;
+ len = &text_len;
+ } else if (s->flags & SEC_DATA) {
+ vma = &data_vma;
+ len = &data_len;
+ } else if (s->flags & SEC_ALLOC) {
+ vma = &bss_vma;
+ len = &bss_len;
+ } else
+ continue;
+
+ sec_size = elf2flt_bfd_section_size(s);
+ sec_vma = elf2flt_bfd_section_vma(s);
+
+ if (sec_vma < *vma) {
+ if (*len > 0)
+ *len += sec_vma - *vma;
+ else
+ *len = sec_size;
+ *vma = sec_vma;
+ } else if (sec_vma + sec_size > *vma + *len)
+ *len = sec_vma + sec_size - *vma;
+ }
+```
+
+这里的 `get_symbols` 函数主要使用 `bfd` 库进行符号表的解析和填充。
+
+而在 `sections` 的解析中,通过对节头元数据的判断来确定节的类型,并将其填入预先分配的地址中并获取其长度。
+
+接着处理 `.text` 段,如果段长度为 0 则出错;如果不为 0,分配对应长度的内存分配,然后将特定类型的输入节(代码节和只读重定位数据节)读取到 `.text` 段里面:
+
+```C
+ for (s = abs_bfd->sections; s != NULL; s = s->next)
+ if ((s->flags & SEC_CODE) ||
+ ro_reloc_data_section_should_be_in_text(s))
+ if (!bfd_get_section_contents(abs_bfd, s,
+ text + (s->vma - text_vma), 0,
+ elf2flt_bfd_section_size(s)))
+ {
+ fatal("read error section %s", s->name);
+ }
+```
+
+接着,将其他的数据节(除了上述提到的只读重定位数据节)都读到数据输出段内,将符号表放入 `.bss` 段内,再进行一些必要的错误判断,初始的解析就完成了。
+
+## 重定位处理
+
+对于 RISC-V 架构,重定位的处理是关键的一步。简化的步骤如下:
+
+1. 判断哪些段需要进行重定位。
+2. 为需要重定位的段确定重定位的起始位置。
+3. 使用 `bfd` 库来解析输入文件的重定位条目。
+4. 根据这些条目和当前段的信息,更新段的内容以完成重定位。
+
+我们具体看 elf2flt。首先,从源码中相应的调用我们可以看见:
+
+```C
+ reloc = (uint32_t *)
+ output_relocs(abs_bfd, symbol_table, number_of_symbols, &reloc_len,
+ text, text_len, text_vma, data, data_len, data_vma, rel_bfd);
+```
+
+重定位是通过调用 `output_relocs` 函数来完成的,这个函数占据了两千行中的一千行,是程序的核心代码。但实际上,大部分代码用来处理不同架构, RISC-V 相关的代码只占一小部分。
+
+这里我们只以 RISC-V 为例,来看整个过程是怎么实现的。
+
+这里首先获取 GOT 表的大小。由于 GOT 表终止于 -1,所以这一步不算困难。(这里的重定位和绝对地址都有这个终止记号)
+
+```C
+ if (pic_with_got && !use_resolved) {
+ uint32_t *lp = (uint32_t *)data;
+ /* Should call ntohl(*lp) here but is isn't going to matter */
+ while (*lp != 0xffffffff) lp++;
+ got_size = ((unsigned char *)lp) - data;
+```
+
+接着,扫描每一个节,如果使用位置无关代码(PIC)或者是全局偏移表(GOT),则只针对可写数据节中进行重定位,否则也对 `.text` 段和只读数据进行重定位。
+
+```C
+if ((!pic_with_got || ALWAYS_RELOC_TEXT) &&
+ ((a->flags & SEC_CODE) ||
+ ro_reloc_data_section_should_be_in_text(a)))
+ sectionp = text + (a->vma - text_vma);
+else if (a->flags & SEC_DATA)
+ sectionp = data + (a->vma - data_vma);
+else
+ continue;
+```
+
+然后查找与当前处理的程序部分相关的二进制重定位文件中的信息,然后使用这些信息来构建重定位条目。
+
+在进行一些检查排除了不需要进行重定位的情况后,正式开始进行重定位,这里针对不同的系统架构进行了不同的重定位解析,以 RISC-V 为例,如下:
+
+```C
+#elif defined(TARGET_riscv64)
+ case R_RISCV_32_PCREL:
+ case R_RISCV_ADD32:
+ case R_RISCV_ADD64:
+ case R_RISCV_SUB32:
+ case R_RISCV_SUB64:
+ continue;
+ case R_RISCV_32:
+ case R_RISCV_64:
+ goto good_32bit_resolved_reloc;
+ default:
+ goto bad_resolved_reloc;
+```
+
+我们发现,只有 R_RISCV_32、R_RISCV_64 这两种重定位方式会进入到 `good_32bit_resolved_reloc` 这个标签中,这里的代码如下:
+
+```C
+good_32bit_resolved_reloc:
+ if (bfd_big_endian (abs_bfd))
+ sym_addr =
+ (r_mem[0] << 24)
+ + (r_mem[1] << 16)
+ + (r_mem[2] << 8)
+ + r_mem[3];
+ else
+ sym_addr =
+ r_mem[0]
+ + (r_mem[1] << 8)
+ + (r_mem[2] << 16)
+ + (r_mem[3] << 24);
+ relocation_needed = 1;
+ update_text = 0;
+ break;
+```
+
+这里,我们可以看到,对于 32 位的重定位,我们将通过是否是大小端的判断,将其转换为一个 32 位的地址。然后,我们将其放入符号表中:
+
+```C
+if (relocation_needed) {
+ if (verbose)
+ printf(" RELOC[%d]: offset=0x%"BFD_VMA_FMT"x symbol=%s%s "
+ "section=%s size=%d "
+ "fixup=0x%x (reloc=0x%"BFD_VMA_FMT"x)\n",
+ flat_reloc_count,
+ q->address, sym_name, addstr,
+ section_name, sym_reloc_size,
+ sym_addr, section_vma + q->address);
+
+#ifndef TARGET_bfin
+ flat_relocs = realloc(flat_relocs,
+ (flat_reloc_count + 1) * sizeof(uint32_t));
+#ifndef TARGET_e1
+ flat_relocs[flat_reloc_count] = pflags |
+ (section_vma + q->address);
+
+#else
+ // ...
+#endif
+ flat_reloc_count++;
+#endif //TARGET_bfin
+ relocation_needed = 0;
+ pflags = 0;
+ }
+```
+
+这里可以看到,重定位地址由段地址加上相对偏移,再与 `pflags` 进行按位或操作完成。RISC-V 没有用到 `pflags` 。
+
+到此处,我们实际上已经知道各段大小。我们的 GOT 表放在 `.data` 段的最上面,因此由此可以确定重定位入口,到此重定位完成。
+
+## header 构建
+
+为了能够成功地加载和执行 bFLT 格式的二进制文件,我们需要构建一个 bFLT header,这个 header 包含了关于二进制文件的所有必要信息。
+
+```C
+ memcpy(hdr.magic,"bFLT",4);
+ hdr.rev = htonl(FLAT_VERSION);
+ hdr.entry = htonl(sizeof(hdr) + bfd_get_start_address(abs_bfd));
+ hdr.data_start = htonl(sizeof(hdr) + text_offs + text_len);
+ hdr.data_end = htonl(sizeof(hdr) + text_offs + text_len +data_len);
+ hdr.bss_end = htonl(sizeof(hdr) + text_offs + text_len +data_len+bss_len);
+ hdr.stack_size = htonl(stack); /* FIXME */
+ hdr.reloc_start = htonl(sizeof(hdr) + text_offs + text_len +data_len);
+ hdr.reloc_count = htonl(reloc_len);
+ hdr.flags = htonl(0
+ | (load_to_ram || text_has_relocs ? FLAT_FLAG_RAM : 0)
+ | (ktrace ? FLAT_FLAG_KTRACE : 0)
+ | (pic_with_got ? FLAT_FLAG_GOTPIC : 0)
+ | (docompress ? (docompress == 2 ? FLAT_FLAG_GZDATA : FLAT_FLAG_GZIP) : 0)
+ );
+ hdr.build_date = htonl((uint32_t)get_build_date());
+ memset(hdr.filler, 0x00, sizeof(hdr.filler));
+
+ for (i=0; i