Adres değiştirme minimal örneği
Adresin yeniden yerleştirilmesi, bağlamanın en önemli işlevlerinden biridir.
Öyleyse minimal bir örnekle nasıl çalıştığına bir bakalım.
0) Giriş
Özet: yer değiştirme, çevrilecek .text
nesne dosyalarının bölümünü düzenler :
- nesne dosyası adresi
- yürütülebilir dosyanın son adresine
Bu, bağlayıcı tarafından yapılmalıdır çünkü derleyici bir seferde yalnızca bir girdi dosyası görür, ancak nasıl yapılacağına karar vermek için tüm nesne dosyalarını aynı anda bilmemiz gerekir:
- tanımlı tanımsız işlevler gibi tanımsız sembolleri çözer
- birden çok nesne dosyasının birden çok bölümünü
.text
ve .data
bölümünü çakışmaz
Ön koşullar: asgari düzeyde anlayış:
Bağlamanın özellikle C veya C ++ ile ilgisi yoktur: derleyiciler sadece nesne dosyalarını oluşturur. Bağlayıcı daha sonra onları hangi dilin derlediğini bilmeden girdi olarak alır. Fortran da olabilir.
Kabuğu azaltmak için, bir NASM x86-64 ELF Linux merhaba dünyasını inceleyelim:
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
derlenmiş ve derlenmiş:
nasm -o hello_world.o hello_world.asm
ld -o hello_world.out hello_world.o
NASM 2.10.09 ile.
1) .o metni
İlk .text
önce nesne dosyasının bölümünü derledik :
objdump -d hello_world.o
hangi verir:
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
önemli satırlar:
a: 48 be 00 00 00 00 00 movabs $0x0,%rsi
11: 00 00 00
hello world dizgisinin adresini rsi
yazma sistem çağrısına iletilen yazmacıya taşımalıdır .
Fakat bekle! Derleyici "Hello world!"
, program yüklendiğinde bellekte nereye gideceğini nasıl bilebilir ?
Pekala, özellikle bir grup .o
dosyayı birden çok .data
bölümle birbirine bağladıktan sonra olamaz .
Tüm bu nesne dosyalarına yalnızca o sahip olacağından, bunu yalnızca bağlayıcı yapabilir.
Yani derleyici sadece:
0x0
derlenen çıktıya bir yer tutucu değeri koyar
- derlenmiş kodun iyi adreslerle nasıl değiştirileceğine dair bağlayıcıya bazı ekstra bilgiler verir
Bu "ek bilgi", .rela.text
nesne dosyasının bölümünde bulunur
2) .rela.text
.rela.text
".text bölümünün yeniden konumlandırılması" anlamına gelir.
Yeniden konumlandırma kelimesi, bağlayıcının adresi nesneden yürütülebilir dosyaya yeniden konumlandırması gerekeceğinden kullanılır.
.rela.text
Bölümü şu şekilde demonte edebiliriz :
readelf -r hello_world.o
içerir;
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
Bu bölümün biçimi şu adreste sabitlenmiştir: http://www.sco.com/developers/gabi/2003-12-17/ch4.reloc.html
Her giriş, bağlayıcıya yeniden konumlandırılması gereken bir adres hakkında bilgi verir, burada dize için yalnızca bir adresimiz vardır.
Biraz sadeleştirmek gerekirse, bu belirli satır için aşağıdaki bilgilere sahibiz:
Offset = C
: .text
bu girişin değiştirdiği ilk bayt nedir .
Derlenmiş metne geri dönüp bakarsak, tam olarak kritik movabs $0x0,%rsi
olanın içindedir ve x86-64 komut kodlamasını bilenler, bunun komutun 64 bit adres bölümünü kodladığını fark edeceklerdir.
Name = .data
: adres .data
bölümü işaret eder
Type = R_X86_64_64
, adresi çevirmek için tam olarak hangi hesaplamanın yapılması gerektiğini belirtir.
Bu alan aslında işlemciye bağlıdır ve bu nedenle AMD64 System V ABI uzantısı bölüm 4.4 "Yer Değiştirme" belgesinde belgelenmiştir .
Bu belge şunu söylüyor R_X86_64_64
:
Field = word64
: 8 bayt, dolayısıyla 00 00 00 00 00 00 00 00
adres0xC
Calculation = S + A
S
olduğu değer adresinde böylece Yeniden yerleştirilen00 00 00 00 00 00 00 00
A
burada bulunan eklentidir 0
. Bu, yer değiştirme girişinin bir alanıdır.
Böylece S + A == 0
, .data
bölümün ilk adresine taşınacağız .
3) .out metni
Şimdi ld
bizim için oluşturulan çalıştırılabilir dosyanın metin alanına bakalım:
objdump -d hello_world.out
verir:
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
Dolayısıyla, nesne dosyasından değişen tek şey kritik satırlardır:
4000ba: 48 be d8 00 60 00 00 movabs $0x6000d8,%rsi
4000c1: 00 00 00
şimdi yerine adresi 0x6000d8
( d8 00 60 00 00 00 00 00
little-endian olarak) gösterir 0x0
.
Bu hello_world
dizi için doğru konum mu?
Karar vermek için Linux'a her bölümü nereye yükleyeceğini söyleyen program başlıklarını kontrol etmeliyiz.
Onları şu şekilde parçalara ayırıyoruz:
readelf -l hello_world.out
hangi verir:
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
Bu bize .data
ikinci olan bölümün VirtAddr
= ile başladığını söyler 0x06000d8
.
Ve veri bölümündeki tek şey merhaba dünya dizimizdir.
Bonus seviyesi