Contoh alamat relokasi minimal
Relokasi alamat adalah salah satu fungsi penting dari menghubungkan.
Jadi mari kita lihat cara kerjanya dengan contoh minimal.
0) Pendahuluan
Ringkasan: relokasi mengedit .text
bagian file objek yang akan diterjemahkan:
- alamat file objek
- ke alamat akhir file yang dapat dieksekusi
Ini harus dilakukan oleh linker karena kompilator hanya melihat satu file input pada satu waktu, tetapi kita harus mengetahui semua file objek sekaligus untuk memutuskan cara:
- menyelesaikan simbol yang tidak terdefinisi seperti fungsi tak terdefinisi yang dideklarasikan
- tidak bentrok beberapa
.text
dan .data
bagian dari beberapa file objek
Prasyarat: pemahaman minimal tentang:
- x86-64 atau rakitan IA-32
- struktur global dari file ELF. Saya telah membuat tutorial untuk itu
Menautkan tidak ada hubungannya dengan C atau C ++ secara khusus: kompiler hanya membuat file objek. Linker kemudian mengambilnya sebagai masukan tanpa pernah mengetahui bahasa apa yang menyusunnya. Mungkin juga Fortran.
Jadi untuk mengurangi kerak, mari pelajari NASM x86-64 ELF Linux hello world:
section .data
hello_world db "Hello world!", 10
section .text
global _start
_start:
; sys_write
mov rax, 1
mov rdi, 1
mov rsi, hello_world
mov rdx, 13
syscall
; sys_exit
mov rax, 60
mov rdi, 0
syscall
disusun dan dirakit dengan:
nasm -o hello_world.o hello_world.asm
ld -o hello_world.out hello_world.o
dengan NASM 2.10.09.
1) .teks dari .o
Pertama kita mendekompilasi .text
bagian dari file objek:
objdump -d hello_world.o
pemberian yang mana:
0000000000000000 <_start>:
0: b8 01 00 00 00 mov $0x1,%eax
5: bf 01 00 00 00 mov $0x1,%edi
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi
11: 00 00 00
14: ba 0d 00 00 00 mov $0xd,%edx
19: 0f 05 syscall
1b: b8 3c 00 00 00 mov $0x3c,%eax
20: bf 00 00 00 00 mov $0x0,%edi
25: 0f 05 syscall
garis krusialnya adalah:
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi
11: 00 00 00
yang harus memindahkan alamat string hello world ke dalam rsi
register, yang diteruskan ke panggilan sistem tulis.
Tapi tunggu! Bagaimana mungkin kompilator mengetahui di mana "Hello world!"
akan berakhir di memori ketika program dimuat?
Ya, itu tidak bisa, terutama setelah kami menautkan banyak .o
file bersama dengan beberapa .data
bagian.
Hanya penaut yang dapat melakukannya karena hanya dia yang akan memiliki semua file objek tersebut.
Jadi kompilernya hanya:
- menempatkan nilai placeholder
0x0
pada output yang dikompilasi
- memberikan beberapa informasi tambahan kepada linker tentang cara mengubah kode yang dikompilasi dengan alamat yang baik
"Informasi tambahan" ini terdapat di .rela.text
bagian file objek
2) .rela.text
.rela.text
singkatan dari "relokasi bagian teks.".
Kata relokasi digunakan karena linker harus merelokasi alamat dari objek ke file yang dapat dieksekusi.
Kita dapat membongkar .rela.text
bagian tersebut dengan:
readelf -r hello_world.o
yang mengandung;
Relocation section '.rela.text' at offset 0x340 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
00000000000c 000200000001 R_X86_64_64 0000000000000000 .data + 0
Format bagian ini diperbaiki didokumentasikan di: http://www.sco.com/developers/gabi/2003-12-17/ch4.reloc.html
Setiap entri memberi tahu linker tentang satu alamat yang perlu direlokasi, di sini kita hanya memiliki satu untuk string.
Sedikit menyederhanakan, untuk baris khusus ini kami memiliki informasi berikut:
Offset = C
: apa byte pertama .text
yang diubah entri ini.
Jika kita melihat kembali teks yang didekompilasi, itu persis di dalam kritis movabs $0x0,%rsi
, dan mereka yang tahu pengkodean instruksi x86-64 akan melihat bahwa ini mengkodekan bagian alamat 64-bit dari instruksi.
Name = .data
: alamat menunjuk ke .data
bagian tersebut
Type = R_X86_64_64
, yang menentukan apa sebenarnya kalkulasi yang harus dilakukan untuk menerjemahkan alamat.
Bidang ini sebenarnya bergantung pada prosesor, dan karenanya didokumentasikan pada ekstensi AMD64 System V ABI bagian 4.4 "Relokasi".
Dokumen itu mengatakan bahwa R_X86_64_64
:
Field = word64
: 8 byte, jadi 00 00 00 00 00 00 00 00
alamat at0xC
Calculation = S + A
S
adalah nilai di alamat yang akan direlokasi00 00 00 00 00 00 00 00
A
adalah tambahan yang ada di 0
sini. Ini adalah bidang entri relokasi.
Jadi S + A == 0
dan kami akan dipindahkan ke alamat pertama dari .data
bagian tersebut.
3) .teks dari .out
Sekarang mari kita lihat area teks yang dapat dieksekusi yang ld
dibuat untuk kita:
objdump -d hello_world.out
memberikan:
00000000004000b0 <_start>:
4000b0: b8 01 00 00 00 mov $0x1,%eax
4000b5: bf 01 00 00 00 mov $0x1,%edi
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
4000c1: 00 00 00
4000c4: ba 0d 00 00 00 mov $0xd,%edx
4000c9: 0f 05 syscall
4000cb: b8 3c 00 00 00 mov $0x3c,%eax
4000d0: bf 00 00 00 00 mov $0x0,%edi
4000d5: 0f 05 syscall
Jadi satu-satunya hal yang berubah dari file objek adalah baris kritis:
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
4000c1: 00 00 00
yang sekarang mengarah ke alamat 0x6000d8
( d8 00 60 00 00 00 00 00
dalam little-endian), bukan 0x0
.
Apakah ini lokasi yang tepat untuk hello_world
string?
Untuk memutuskan, kita harus memeriksa header program, yang memberi tahu Linux tempat memuat setiap bagian.
Kami membongkar mereka dengan:
readelf -l hello_world.out
pemberian yang mana:
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x00000000000000d7 0x00000000000000d7 R E 200000
LOAD 0x00000000000000d8 0x00000000006000d8 0x00000000006000d8
0x000000000000000d 0x000000000000000d RW 200000
Section to Segment mapping:
Segment Sections...
00 .text
01 .data
Ini memberitahu kita bahwa .data
bagian, yang kedua, dimulai pada VirtAddr
= 0x06000d8
.
Dan satu-satunya hal di bagian data adalah string hello world kami.
Tingkat bonus