昨天大概在抽象的層面理解連結這個動作的必要性和最後可以產出的結果,今天就來讓我們一窺 RISC-V 如何提供連結的選項。
我們可以重新回顧昨日展示的物件檔內容
$ riscv64-unknown-linux-gnu-objdump -dr ~/main.o
/root/main.o: file format elf64-littleriscv
Disassembly of section .text:
0000000000000000 <main>:
0: 1141 addi sp,sp,-16
0: R_RISCV_ALIGN *ABS*
2: e406 sd ra,8(sp)
4: e022 sd s0,0(sp)
6: 0800 addi s0,sp,16
8: 4589 li a1,2
a: 4505 li a0,1
c: 00000097 auipc ra,0x0
c: R_RISCV_CALL add
c: R_RISCV_RELAX *ABS*
10: 000080e7 jalr ra
14: 87aa mv a5,a0
16: 85be mv a1,a5
18: 000007b7 lui a5,0x0
18: R_RISCV_HI20 .LC0
18: R_RISCV_RELAX *ABS*
1c: 00078513 mv a0,a5
1c: R_RISCV_LO12_I .LC0
1c: R_RISCV_RELAX *ABS*
20: 00000097 auipc ra,0x0
20: R_RISCV_CALL printf
20: R_RISCV_RELAX *ABS*
24: 000080e7 jalr ra
28: 4781 li a5,0
2a: 853e mv a0,a5
2c: 60a2 ld ra,8(sp)
2e: 6402 ld s0,0(sp)
30: 0141 addi sp,sp,16
32: 8082 ret
其中我們可以看到數個 R_RISCV
開頭的東西,那些就是所謂的重定標記。可以使用 readelf 工具進一步觀察:
riscv64-unknown-linux-gnu-readelf -r ~/main.o
Relocation section '.rela.text' at offset 0x1b8 contains 9 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000000000 00000000002b R_RISCV_ALIGN 0
00000000000c 000900000012 R_RISCV_CALL 0000000000000000 add + 0
00000000000c 000000000033 R_RISCV_RELAX 0
000000000018 00060000001a R_RISCV_HI20 0000000000000000 .LC0 + 0
000000000018 000000000033 R_RISCV_RELAX 0
00000000001c 00060000001b R_RISCV_LO12_I 0000000000000000 .LC0 + 0
00000000001c 000000000033 R_RISCV_RELAX 0
000000000020 000a00000012 R_RISCV_CALL 0000000000000000 printf + 0
000000000020 000000000033 R_RISCV_RELAX 0
這些內容可以在 RISC-V 基金會的官方文件中窺知一二,他們的意義分別是:
%d\n
的格式字串,這個字串因為存在於全域的某個位址,因此雖然乍看之下看不出來,但其實也需要一組重定向。編譯器安排給我們的是 lui+addi 的組合。auipc+jalr
或是 lui+addi
這種組合化約成單一指令的行為。本來筆者正要思考從 as 的強化還是 ld 的骨架切入,結果發現我們使用的 riscv-go 沒有支援這麼多重定項目!請看:
1730 // Relocation types for RISC-V processors.
1731 type R_RISCV int
1732
1733 const (
1734 R_RISCV_NONE R_RISCV = 0 /* No relocation. */
1735 R_RISCV_32 R_RISCV = 1 /* Add 32 bit zero extended symbol value */
1736 R_RISCV_64 R_RISCV = 2 /* Add 64 bit symbol value. */
1737 R_RISCV_RELATIVE R_RISCV = 3 /* Add load address of shared object. */
1738 R_RISCV_COPY R_RISCV = 4 /* Copy data from shared object. */
1739 R_RISCV_JUMP_SLOT R_RISCV = 5 /* Set GOT entry to code address. */
1740 R_RISCV_TLS_DTPMOD32 R_RISCV = 6 /* 32 bit ID of module containing symbol */
1741 R_RISCV_TLS_DTPMOD64 R_RISCV = 7 /* ID of module containing symbol */
1742 R_RISCV_TLS_DTPREL32 R_RISCV = 8 /* 32 bit relative offset in TLS block */
1743 R_RISCV_TLS_DTPREL64 R_RISCV = 9 /* Relative offset in TLS block */
1744 R_RISCV_TLS_TPREL32 R_RISCV = 10 /* 32 bit relative offset in static TLS block */
1745 R_RISCV_TLS_TPREL64 R_RISCV = 11 /* Relative offset in static TLS block */
1746 )
這是 src/debug/elf.go 檔案裡面的重定部分,看來筆者終究是一頭撞上了高牆;這個連 R_RISCV_CALL
的重定項目都沒有,該如何繼續下去?不過沒關係,讓我們化危機為轉機吧!明天,我們來補強這份文件,然後貢獻回 riscv/riscv-go 的 github 上游!各位讀者,我們明天再會!