昨日設計了一個更適合接下來的開發的小程式,並且將展示重定區段的功能新增進入 readelf 裡面。今天的目標就是試試看我們能不能產生重定區段,並且讓連結器能夠使用呢?
繼續沿用我們的測試程式,簡單回顧一下的話,fin2.s 是進入點,並且在裡面呼叫了 write 函式;fin1.s 則定義了該函式。所以實作的大方向就是要在組譯結果的 fin2.o 中安插一個重定區段。細節來講,我們必須在 as 中做到:
R_RISCV_NONE
的空重定R_RISCV_CALL
的重定 Rela64 結構內容
Off
成員在 call 的位置Info
成員是 R_RISCV_CALL
重定型態與該標籤編號的複合體Addend
成員的值是 0理論上這樣就足夠了。
嚴格來說,這應該是 rvgc 函式庫的責任:因為 as 抓到一個指令之後,會送交給 rvgc.InstToBin
去處理。所以這裡我們先新增一個指令型態叫做虛擬指令:
type RV_INST_TYPE uint32
const (
...
RV_INST_U_TYPE RV_INST_TYPE = 5
RV_INST_J_TYPE RV_INST_TYPE = 6
RV_INST_PSEUDO RV_INST_TYPE = 7
)
然後,我們可以將 call 指令視為這個新的形態:
var mnem2type = map[string]RV_INST_TYPE{
...
"ecall": RV_INST_NONE,
"call": RV_INST_PSEUDO,
}
由於虛擬指令並沒有真正的 opcode,所以 InstToBin
的起始邏輯需要稍微修正:
func InstToBin(inst []string) []byte {
t := mnem2type[inst[0]]
if t != RV_INST_PSEUDO {
op := mnem2opcode[inst[0]]
}
於是就可以在後面的 switch 結構之中加入相關的處理了!為了避免重複造輪子,我們可以呼叫自己兩次:
...
case RV_INST_NONE:
bits |= uint32(op)
case RV_INST_PSEUDO:
if inst[0] == "call" {
ret := make([]byte, 0)
ret = append(ret, InstToBin("auipc ra, 0"))
ret = append(ret, InstToBin("jalr ra, 0(ra)"))
return ret
}
這個部分筆者決定繼續修改 rvgc 的行為,因為關於指令的分析處理,幾乎都是在 rvgc 函式庫之內進行,若是要將這個流程拿出來到 as 裡面,會變得綁手綁腳的。這裡決定讓 InstToBin
函數多回傳一個參數:重定型態。所以,必須修改 as 的 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))
if r != elf.R_RISCV_NONE {
如果這個指令有回傳重定型態,則必須開始一連串安插重定區段的步驟。
今日完成的是初步的處理,我們明日再來嘗試把這個部分補完吧!