iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 26
0

前情提要


昨日在 riscv-go 上面的 riscvdev 分支裡挖掘到了重定向型態短少的原因,也從隔壁的 riscv-binutils-gdb 專案取得了正確的對照。今天的內容就先來了解這些重定在 ELF 檔中的樣子吧!

展示 main.o 使用到的重定


$ 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

理論上,重定型態的展示應該也寫在 API 裡面,從線上的官方文件裡的 PPC(IBM 的架構)為例:

type R_PPC int

const (
        R_PPC_NONE            R_PPC = 0 /* No relocation. */
        R_PPC_ADDR32          R_PPC = 1
        R_PPC_ADDR24          R_PPC = 2
        R_PPC_ADDR16          R_PPC = 3
...
        {115, "R_PPC_EMB_BIT_FLD"},
        {116, "R_PPC_EMB_RELSDA"},
}

func (i R_PPC) String() string   { return stringName(uint32(i), rppcStrings, false) }
func (i R_PPC) GoString() string { return stringName(uint32(i), rppcStrings, true) }

就跟我們之前在實作 readelf 的時候一樣,我們曾經使用過這些 API。而幸好,雖然他們只有移植 12 種動態連結必須的重定型態,這些 API 是沒有少的。也就是說,我們仍然可以仿照 nm 實作標籤區段的展示那樣,為 readelf 新增 -r 參數來展示重定區段;然而我們預期的行為是,因為實際上缺乏這些重定,因此進化過的我們的 readelf 應該會沒有辦法正確地顯示,嚴重的話甚至可能會在存取字串索引時出現問題。

重定結構


我們已經聊重定型態、重定在連結中的意義很多次了。但它們在 ELF 檔裡面到底是什麼東西?開始一探究竟之前,我們可以先看看 main.o 物件檔的f區段檔頭資訊:

Section Header:                                                    
Number          Name       Type             Flags                           Address Offset Size Link Info Alignment
0               .shstrtab  elf.SHT_STRTAB   0x0                             0       290    66   0    0    0x1
1               .strtab    elf.SHT_STRTAB   0x0                             0       198    29   0    0    0x1
2               .symtab    elf.SHT_SYMTAB   0x0                             0       90     264  8    8    0x8
3               .comment   elf.SHT_PROGBITS elf.SHF_MERGE+elf.SHF_STRINGS   0       7c     18   0    0    0x1
4               .rodata    elf.SHT_PROGBITS elf.SHF_ALLOC                   0       78     4    0    0    0x8
5               .bss       elf.SHT_NOBITS   elf.SHF_WRITE+elf.SHF_ALLOC     0       74     0    0    0    0x1
6               .data      elf.SHT_PROGBITS elf.SHF_WRITE+elf.SHF_ALLOC     0       74     0    0    0    0x1
7               .rela.text elf.SHT_RELA     elf.SHF_INFO_LINK               0       1b8    216  7    1    0x8
8               .text      elf.SHT_PROGBITS elf.SHF_ALLOC+elf.SHF_EXECINSTR 0       40     52   0    0    0x2
9                          elf.SHT_NULL     0x0                             0       0      0    0    0    0x0

有一個我們之前沒看過的區段,它想必就是存放連結所需資訊的那個區段了:.rela.text.text 我們可以理解是程式碼內容的代表,但是 .rela 卻是什麼?我們可以從 elf 函式庫的結構定義來理解這一點:

/* ELF64 relocations that don't need an addend field. */
type Rel64 struct {
        Off  uint64 /* Location to be relocated. */
        Info uint64 /* Relocation type and symbol index. */
}

/* ELF64 relocations that need an addend field. */
type Rela64 struct {
        Off    uint64 /* Location to be relocated. */
        Info   uint64 /* Relocation type and symbol index. */
        Addend int64  /* Addend. */
}

這裡有兩個兄弟結構,唯一的差別是 Rela 有多一個成員。Off 代表需要重定的位址,而 Info 又再度是一個複合欄位,分別用來代表重定型態和相關連的標籤的索引號碼。如果有需要那個 Addend(筆者姑且譯作補正量)資訊,那麼連結器在解決重定區域的時候就會使用到。這一點我們節錄一下 RISC-V 的 PS-ABI 文件:

Enum ELF Reloc Type Description Details
0 _NONE None
1 _32 Runtime relocation word32 = S + A
2 _64 Runtime relocation word64 = S + A
3 _RELATIVE Runtime relocation word32,64 = B + A
... ... ... ...

其中後附說明中, A 就是這個修正量的意義。那麼其他的呢?也在同一份文件之中:S 代表的是該指定 symbol 的位址,B 則是代表動態連結函式庫在載入記憶體裡面的時候的基底。

回頭再仔細看這個 .rela.text 區段的展示:

Section Header:                                                    
Number          Name       Type             Flags                           Address Offset Size Link Info Alignment
...
7               .rela.text elf.SHT_RELA     elf.SHF_INFO_LINK               0       1b8    216  7    1    0x8

看到區段型態是 SHT_RELA,就不必多說了吧。這個區段旗標的 SHF_INFO_LINK 則是表示說,在這個區段的 Info 欄位中,存放的重定所在的區域。Link 欄位向來是代表該區段要參考到的區段,在這個例子,也就是之前為了實作 nm 有好好摸一下的 .symtab 區段啦。但是眼尖的讀者應該有發現,明明 .text 區段就是 8 而不是 1、.symtab 區段是 2 而不是 7,那麼這裡怎麼會變成這樣呢?其實是因為我們的 readelf 透過 go 語言的讀取,會把原先的順序倒轉過來的緣故,所以有一個 mapping。

當然,這是日後必須處理的問題。

Size 是 9?沒錯。Rela64 結構包含 3 個 uint64 整數,所以是 24 個 bytes;總共有 9 個重定被安插在 main.o 中,所以總共是 216 位元組無誤。對齊量是 8 bytes,所以我們不用考慮其他修修補補的問題。

觀察內裡內容


使用二進位檔展示工具 hexdump,尋找 1b8 的區段:

000001b0                           00 00 00 00 00 00 00 00  |        ........|
000001c0  2b 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |+...............|

000001d0  0c 00 00 00 00 00 00 00  12 00 00 00 09 00 00 00  |................|
000001e0  00 00 00 00 00 00 00 00  0c 00 00 00 00 00 00 00  |................|
000001f0  33 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |3...............|

00000200  18 00 00 00 00 00 00 00  1a 00 00 00 06 00 00 00  |................|
00000210  00 00 00 00 00 00 00 00  18 00 00 00 00 00 00 00  |................|
00000220  33 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |3...............|

00000230  1c 00 00 00 00 00 00 00  1b 00 00 00 06 00 00 00  |................|
00000240  00 00 00 00 00 00 00 00  1c 00 00 00 00 00 00 00  |................|
00000250  33 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |3...............|

00000260  20 00 00 00 00 00 00 00  12 00 00 00 0a 00 00 00  | ...............|
00000270  00 00 00 00 00 00 00 00  20 00 00 00 00 00 00 00  |........ .......|
00000280  33 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |3...............|

未求方便閱讀,筆者除了第一組重定資訊,其他的兩個兩個一組,因為 24 bytes 相當於是 1.5 行。再複習一下 Rela64 的結構:

type Rela64 struct {
        Off    uint64 /* Location to be relocated. */
        Info   uint64 /* Relocation type and symbol index. */
        Addend int64  /* Addend. */
}

而這個 Info 成員的組合方式是:

func R_SYM64(info uint64) uint32    { return uint32(info >> 32) }
func R_TYPE64(info uint64) uint32   { return uint32(info) }
func R_INFO(sym, typ uint32) uint64 { return uint64(sym)<<32 | uint64(typ) }

也就是說,剛好切成一半的意思啦!比較高位的一半是(因為小頭排序的緣故,所以是右半)對應到的標籤,而比較低位的一半則是型態。我們來隨意檢驗一下,比方說位在 278 的最後一組重定結構,第一個成員 Off 的內容是 0x20,沒有指定 symbol,重定型態是編號 0x33,也就是 51,這個東西剛好就是 R_RISCV_RELAX 的重定型態;他的前一個,則是型態編號為 0x12 的 R_RISCV_CALL,應該是呼叫到 printf 函式的那一組吧?檢查一下反組譯內容:

0000000000000000 <main>:
...
  20:   00000097                auipc   ra,0x0
                        20: R_RISCV_CALL        printf
                        20: R_RISCV_RELAX       *ABS*

果然如此吧!

小結


今日我們看了 Rela 的結構型態,並且理解它在 ELF 檔之中的角色。各位讀者天冷請不要著涼啦!我們明日再會!


上一篇
第二十五日:眾裡尋 relocation type 千百度......
下一篇
第二十七日:readelf 重定擴充
系列文
與妖精共舞:在 RISC-V 架構上使用 GO 語言實作 binutils 工具包30

尚未有邦友留言

立即登入留言