昨日為 as 的功能加強起了個頭,今天讓我們來完成它吧!
R_RISCV_NONE
的空重定R_RISCV_CALL
的重定 Rela64 結構內容
Off
成員在 call 的位置Info
成員是 R_RISCV_CALL
重定型態與該標籤編號的複合體Addend
成員的值是 0這個部份我們可以參考之前為 as 加入標籤區段的經驗。回顧一下,當時的作法是:
func (asu *asUtil) addLabel(lab string) {
asu.obj.sections[".strtab"].content = append(asu.obj.sections[".strtab"].content, lab)
asu.symtab = append(asu.symtab, &elf.Sym64{
Name: currentOffsetStr,
Info: elf.ST_INFO(elf.STB_GLOBAL, elf.STT_FUNC),
Shndx: asu.obj.header.Shnum - 1,
})
currentOffsetStr += uint32(len(lab) + 1)
}
我們透過一個額外的成員變數 symtab
(這是一個可變長度陣列)來彰顯標籤區段的特殊性。每一次新增標籤,就將之置放在這個區段之中。事實上,以我們的目的:將 call write
指令轉變為重定區段內的一筆資料而言,我們也必須考慮經過 addLabel
的過程,因為對於我們的 fin2.o 檔來說,它會把 write 當作一個字串存在 .strtab
之中,而將它的相關屬性存在 .symtab
之中,
$ riscv64-unknown-linux-gnu-readelf -s ~/fin2.o
Symbol table '.symtab' contains 6 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000000000 0 SECTION LOCAL DEFAULT 1
2: 0000000000000000 0 SECTION LOCAL DEFAULT 3
3: 0000000000000000 0 SECTION LOCAL DEFAULT 4
4: 0000000000000000 0 NOTYPE GLOBAL DEFAULT 1 _start
5: 0000000000000000 0 NOTYPE GLOBAL DEFAULT UND write
我們的 write
函式在這裡被定義成 NOTYPE
、連結的屬性是 GLOBAL
、值和大小都是未知、所屬的區段是 UND
未定義!最後則是將 .rela.text
區段裡面的一個結構設置完成,這樣應該就能夠被 ld 使用來作連結了。
.rela.text
區段理論上,這裡應該要在整個檔案完結卻沒有完成所有標籤的定義的時候才新增這個區段,但是筆者現在已經認定 go-binutils 這個專案必須面臨大型重構,所以我們這裡就用快而骯髒的方式把它作掉吧。
首先我們先來解決檔頭的問題。.rela.text
檔頭所需要的資訊有這些項目:
$ riscv64-unknown-linux-gnu-readelf -S ~/fin2.o
There are 8 section headers, starting at offset 0x170:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
...
[ 2] .rela.text RELA 0000000000000000 00000108
0000000000000030 0000000000000018 I 5 1 8
...
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
p (processor specific)
大部分資訊都能夠在可以在 addSection
函式中完成。但是大家若回想 .symtab
時的經驗,當時在輸出前才決定它自己如何將 Link 屬性連結到 .strtab
區段檔頭。重定區段的 Link 必須連結到相應的標籤區段(本例的 5 正是 .symtab
),而 Info 必須連結到相應的程式區段(本例的 1 正是 .text
)。所以以上資訊分別應該這樣解決:
func (asu *asUtil) addSection(sec string) error {
...
switch sec {
case "":
thisSec.header.Type = uint32(elf.SHT_NULL)
...
case "rela.text":
thisSec.header.Type = uint32(elf.SHT_RELA)
thisSec.header.Flags = uint64(elf.SHF_INFO)
thisSec.header.Addralign = 0x8
thisSec.header.Entsize = 0x18
}
...
這是固定屬性的部份,非得到最後一刻決定不可的屬性則是:
if name == ".strtab" {
asu.obj.sections[".symtab"].header.Link = uint32(i)
asu.obj.sections[".symtab"].header.Info = 2
} else if name == ".symtab" {
asu.obj.sections[".rela.text"].header.Link = uint32(i)
} else if name == ".text" {
asu.obj.sections[".rela.text"].header.Info = uint32(i)
}
第一個 .strtab
的判斷之內是之前 .symtab
使用的部份。這裡我們為 .rela.text
新增了兩個判斷。如此一來,檔頭的資訊就不怕沒有了。
如同其他區段,我們會需要在開頭的地方安插一個虛無的零區段(之前 .symtab
的零區段安插在第零個區段標頭的所在,所以這裡如法炮製):
case "":
thisSec.header.Type = uint32(elf.SHT_NULL)
asu.symtab = append(asu.symtab, &elf.Sym64{
Name: 0,
Info: elf.ST_INFO(elf.STB_LOCAL, elf.STT_NOTYPE),
Shndx: 0,
})
+ asu.rela = append(asu.rela, &elf.Rela64{
+ Off: 0,
+ Info: elf.R_INFO(0, elf.R_RISCV_NOTYPE),
+ Shndx: 0,
+ })
再來就是最關鍵的部份了:我們該如何在看見 call write
的時候,讓 as 這隻程式能夠插入一個重定區段?
我們還是得從判斷開始一步一步走,最適合的切入點當然就是處理任何指令的 inst
函式。我們在這裡會取得 call write
的指令本體,所以在這裡,我們已經可以透過之前調整的 rvgc.InstToBin
函式判斷出一道指令是否含有任何重定;之後,將後面的 write 透過 addLabel
函式走正常的新增流程。然後我們可以創建一個 Rela64 結構,使之指向剛才為 write 建立的標籤結構。這樣應該就差不多了吧?
在 inst
函式之中加入:
func (asu *asUtil) inst(d []string) {
b, r := rvgc.InstToBin(d)
asu.obj.sections[currentSection].content = append(asu.obj.sections[currentSection].content, string(b))
switch r {
case elf.R_RISCV_NONE:
break
case elf.R_RISCV_CALL:
asu.obj.sections[".strtab"].content = append(asu.obj.sections[".strtab"].content, d[1])
asu.symtab = append(asu.symtab, &elf.Sym64{
Name: currentOffsetStr,
Info: elf.ST_INFO(elf.STB_GLOBAL, elf.STT_NOTYPE),
Shndx: 0,
})
asu.rela = append(asu.rela, &elf.Rela64{
Off: 2,
Info: elf.R_INFO(uint32(len(asu.symtab)-1), r),
Addend: 0,
})
currentOffsetStr += uint32(len(lab) + 1)
}
}
原本只有最前面的加入組譯結果的兩行,後面的 switch 結構則是為了判斷重定使用的東西。由於 addLabel
的流程假設了該標籤的性質,因此我們這裡將 addLabel
函式的內容複製出來手動修改。Info 成員的部份,由於剛加入 .symtab
,所以可以直接使用 len 內建函數,**但是要知道的是這只是便宜行事,這裡應該要先搜尋過 .symtab
才可以下手!**總之我們先這麼做了;重定型態的部份當然是放入由 InstToBin
判斷得到的 r,也就是 RIRISCV_CALL
。但是為什麼 Off 成員的值是 2 呢?因為筆者完全忘記這件事情啦!
為了要得到當前的 offset,我們必須從組譯開始,就開始累計:**4, 8, 12, 16, ...**這樣子計算下去。但是之前我們完全沒有考慮這點,所以這裡筆者選擇新增一個全域成員 currentOffset
,讓它從零開始紀錄當前的 offset 所在。
func (asu *asUtil) inst(d []string) {
b, r := rvgc.InstToBin(d)
+ currentOffset += len(b)
如此一來就可以拿來使用在 asu.rela
成員的新元素之中了。然而,以上工作之外,我們還需要最後的輸出部份。
這個部份我們也是可以參考 .symtab
的輸出方法。
case ".symtab":
for _, syment := range asu.symtab {
var binbuf bytes.Buffer
binary.Write(&binbuf, binary.LittleEndian, syment)
temp, _ := asu.objFile.Write(binbuf.Bytes())
size = size + uint64(temp)
}
+ case ".rela.text":
+ for _, rent := range asu.rela {
+ var binbuf bytes.Buffer
+ binary.Write(&binbuf, binary.LittleEndian, rent)
+ temp, _ := asu.objFile.Write(binbuf.Bytes())
+ size = size + uint64(temp)
+ }
這樣應該就可以了吧!來試試看吧~
$ make run
/riscv-tools/riscv-gnu-toolchain/riscv-qemu/build/riscv64-linux-user/qemu-riscv64 ./fin
ABCD
今日完成了可以連結的物件檔。剩下最後一天,很遺憾已經沒有時間可以實作連結器了,也很抱歉讓各位讀者期待了。明日,請讓筆者回顧一下過去這 30 天的奇幻旅程,以及未來的方向。感謝大家!