Oyuncak nesne dosyamızla ilgili sorun, her iki işlevin de harici bağlantı ile bildirilmesidir - C'deki tum işlevler ve genel değişkenler icin varsayılan ayar ve her iki işlev de aynı dosyada bildirilse de, derleyici add5kodun nerede olacağından emin değildir. hedef ikili haline gelir. Boylece derleyici herhangi bir varsayım yapmaktan kacınır ve callqtalimatların goreli ofset argumanını hesaplamaz . Maymun yamamızı kaldırarak ve add5işlevi şu şekilde bildirerek bunu doğrulayalım static:

Kod:
loader.c : ... /* the first add5 callq argument is located at offset 0x20 and should be 0xffffffdc: * 0x1f is the instruction offset + 1 byte instruction prefix */ /* *((uint32_t *)(text_runtime_base + 0x1f + 1)) = 0xffffffdc; */ /* the second add5 callq argument is located at offset 0x2d and should be 0xffffffcf */ /* *((uint32_t *)(text_runtime_base + 0x2c + 1)) = 0xffffffcf; */ ... obj.c : /* int add5(int num) */ static int add5(int num) ...


Yeniden derlemek ve parcalara ayırmak obj.obize şunları verir:

Kod:
$ gcc -c obj.c $ objdump --disassemble --section=.text obj.o obj.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 : 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 4: 89 7d fc mov %edi,-0x4(%rbp) 7: 8b 45 fc mov -0x4(%rbp),%eax a: 83 c0 05 add $0x5,%eax d: 5d pop %rbp e: c3 retq 000000000000000f : f: 55 push %rbp 10: 48 89 e5 mov %rsp,%rbp 13: 48 83 ec 08 sub $0x8,%rsp 17: 89 7d fc mov %edi,-0x4(%rbp) 1a: 8b 45 fc mov -0x4(%rbp),%eax 1d: 89 c7 mov %eax,%edi 1f: e8 dc ff ff ff callq 0 24: 89 45 fc mov %eax,-0x4(%rbp) 27: 8b 45 fc mov -0x4(%rbp),%eax 2a: 89 c7 mov %eax,%edi 2c: e8 cf ff ff ff callq 0 31: c9 leaveq 32: c3 retq


add5Fonksiyonu dahili bağlantı ile yeniden ilan ettiğimiz icin , derleyici şimdi daha kendine guveniyor ve callqargumanları doğru hesaplıyor (x86 sistemlerinin kucuk endian olduğunu , bu nedenle cokbaytlı sayıların 0xffffffdcilk once en az anlamlı bayt ile temsil edileceğini unutmayın). loaderTest aracımızı yeniden derleyip calıştırarak bunu iki kez kontrol edebiliriz :

Kod:
$ gcc -o loader loader.c $ ./loader Executing add5... add5(42) = 47 Executing add10... add10(42) = 52


add5İşlev olarak bildirilse bile , temelde artık "dahili" bir işlev olduğu gerceğini gormezden gelerek, onu aractan staticcağırabiliriz loader. Bu nedenle, staticanahtar kelime API'leri potansiyel kotu niyetli kullanıcılardan gizlemek icin bir guvenlik ozelliği olarak kullanılmamalıdır.

Ama geri adım atalım ve add5fonksiyonumuzu obj.charici bağlantılı olana geri alalım :

Kod:
obj.c : int add5(int num) ... $ gcc -c obj.c $ ./loader Executing add5... add5(42) = 47 Executing add10... add10(42) = 42


Yukarıda belirlediğimiz gibi, derleyici callqyeterli bilgiye sahip olmadığı icin bizim icin uygun argumanları hesaplamadı . Ancak sonraki aşamalar (yani bağlayıcı) bu bilgiye sahip olacaktır, bu nedenle derleyici bunun yerine bu argumanları nasıl duzelteceğine dair bazı ipucları bırakır. Bu ipuclarına - veya sonraki aşamalar icin talimatlara - yer değiştirme adı verilir . Onları kendi kendimize okuma aracı olan arkadaşımızla inceleyebiliriz . obj.oBolumler tablosunu tekrar inceleyelim :

Kod:
$ readelf --sections obj.o There are 12 section headers, starting at offset 0x2b0: Section Headers: [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [ 0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [ 1] .text PROGBITS 0000000000000000 00000040 0000000000000033 0000000000000000 AX 0 0 1 [ 2] .rela.text RELA 0000000000000000 000001f0 0000000000000030 0000000000000018 I 9 1 8 [ 3] .data PROGBITS 0000000000000000 00000073 0000000000000000 0000000000000000 WA 0 0 1 [ 4] .bss NOBITS 0000000000000000 00000073 0000000000000000 0000000000000000 WA 0 0 1 [ 5] .comment PROGBITS 0000000000000000 00000073 000000000000001d 0000000000000001 MS 0 0 1 [ 6] .note.GNU-stack PROGBITS 0000000000000000 00000090 0000000000000000 0000000000000000 0 0 1 [ 7] .eh_frame PROGBITS 0000000000000000 00000090 0000000000000058 0000000000000000 A 0 0 8 [ 8] .rela.eh_frame RELA 0000000000000000 00000220 0000000000000030 0000000000000018 I 9 7 8 [ 9] .symtab SYMTAB 0000000000000000 000000e8 00000000000000f0 0000000000000018 10 8 8 [10] .strtab STRTAB 0000000000000000 000001d8 0000000000000012 0000000000000000 0 0 1 [11] .shstrtab STRTAB 0000000000000000 00000250 0000000000000059 0000000000000000 0 0 1 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), l (large), p (processor specific)


Derleyicinin adında yeni bir bolum oluşturduğunu goruyoruz .rela.text. Kural olarak, adlandırılmış bir bolum icin yer değiştirmeleri olan bir bolum .foocağrılacaktır .rela.foo, boylece derleyicinin bolum icin yer değiştirmelerle bir bolum oluşturduğunu gorebiliriz .text. Yer değiştirmeleri daha ayrıntılı inceleyebiliriz:

Kod:
$ readelf --relocs obj.o Relocation section '.rela.text' at offset 0x1f0 contains 2 entries: Offset Info Type Sym. Value Sym. Name + Addend 000000000020 000800000004 R_X86_64_PLT32 0000000000000000 add5 - 4 00000000002d 000800000004 R_X86_64_PLT32 0000000000000000 add5 - 4 Relocation section '.rela.eh_frame' at offset 0x220 contains 2 entries: Offset Info Type Sym. Value Sym. Name + Addend 000000000020 000200000002 R_X86_64_PC32 0000000000000000 .text + 0 000000000040 000200000002 R_X86_64_PC32 0000000000000000 .text + f


Bolumdeki yer değiştirmeleri gormezden gelelim .rela.eh_framecunku bunlar bu yazının kapsamı dışındadır. Bunun yerine, yer değiştirmeleri şunlardan anlamaya calışalım .rela.text:

Offsetsutunu bize hedef bolumde tam olarak nerede ( .textbu durumda) duzeltmenin / ayarlamanın gerekli olduğunu soyler . Bu ofsetlerin yukarıdaki kendi kendine hesaplanan maymun yamasında olduğu gibi tamamen aynı olduğuna dikkat edin. Infobirleşik bir değerdir: ust 32 bit - yukarıdaki cıktıda sadece 16 bit gosterilmektedir - yeniden konumlandırmanın gercekleştirilmesine gore sembol tablosundaki sembolun indeksini temsil eder. Orneğimizde oyle 8ve eğer calıştırırsak readelf --symbols obj.o, add5işleve karşılık gelen bir girişi işaret ettiğini goreceğiz . Daha duşuk 32 bit ( 4bizim durumumuzda) bir yeniden konumlandırma turudur (aşağıya Typebakın). Typeyer değiştirme turunu acıklar. Bu sozde bir sutundur: readelfaslında onu alanın alt 32 bitinden uretir Info. Yer değiştirmeyi gercekleştirmek icin uygulamamız gereken farklı yer değiştirme turlerinin farklı formulleri vardır. Sym. Valueyeniden yerleştirme turune bağlı olarak farklı şeyler ifade edebilir, ancak coğu zaman yeniden konumlandırmayı gercekleştirdiğimiz sembol ofsetidir. Uzaklık, o sembolun bolumunun başından itibaren hesaplanır. Addendyer değiştirme formulunde kullanmamız gerekebilecek bir sabittir. Yeniden konumlandırma turune bağlı olarak, readelf aslında kodu cozulmuş sembol adını cıktıya ekler, bu nedenle sutun adı Sym. Name + Addendyukarıdadır, ancak gercek alan yalnızca eki depolar. Ozetle, bu girişler bize .textbolumu ofsetler 0x20ve 0x2d. Oraya ne koyacağımızı hesaplamak icin, R_X86_64_PLT32yer değiştirme turu formulunu uygulamamız gerekir . Gibi - Online aranıyor, farklı ELF ozellikleri bulabilirsiniz bu bir - nasıl uygulamaya bize soyler R_X86_64_PLT32taşınma. Spesifikasyon, bu yer değiştirmenin sonucunun word32- ki callqbizim durumumuzda argumanlar 32 bit olduğu icin beklediğimiz şey - ve uygulamamız gereken formul şudur L + A - P:

Lyer değiştirmenin gercekleştirildiği sembolun adresidir ( add5bizim durumumuzda) Asabit eklentidir ( 4bizim durumumuzda) P yer değiştirmenin sonucunu sakladığımız adres / ofsettir Yer değiştirme formulu bazı sembol adreslerine veya ofsetlere referans verdiğinde, hesaplamalarda gercek - bizim durumumuzda calışma zamanı - adreslerini kullanmalıyız. Orneğin, kullanıyor olacak text_runtime_base + 0x2dşekilde Pikinci tehcir icin değil, sadece 0x2d. Oyleyse bu yer değiştirme mantığını nesne yukleyicimizde uygulamaya calışalım:

Kod:
loader.c : ... /* from https://elixir.bootlin.com/linux/v5.11.6/source/arch/x86/include/asm/elf.h#L51 */ #define R_X86_64_PLT32 4 ... static uint8_t *section_runtime_base(const Elf64_Shdr *section) static void do_text_relocations(void) int num_relocations = rela_text_hdr->sh_size / rela_text_hdr->sh_entsize; const Elf64_Rela *relocations = (Elf64_Rela *)(obj.base + rela_text_hdr->sh_offset); for (int i = 0; i < num_relocations; i++) } } static void parse_obj(void) ...


Kopyamızı yurutulebilir olarak do_text_relocationsişaretlemeden once şimdi işlevi cağırıyoruz .text. Yer değiştirme hesaplamalarının sonucunu incelemek icin bazı hata ayıklama cıktıları da ekledik. Hadi deneyelim:

Kod:
$ gcc -o loader loader.c $ ./loader Calculated relocation: 0xffffffdc Calculated relocation: 0xffffffcf Executing add5... add5(42) = 47 Executing add10... add10(42) = 52


Harika! İce aktarılan kodumuz artık beklendiği gibi calışıyor. Derleyicinin bize bıraktığı yer değiştirme ipuclarını takip ederek, bu yazının başındaki maymun yama hesaplamalarımızla aynı sonucları aldık. Yer değiştirme hesaplamalarımız text_runtime_base, derleme zamanında mevcut olmayan adresleri de iceriyordu . Bu nedenle derleyici callqilk etapta argumanları hesaplayamadı ve bunun yerine yer değiştirmeleri yayınlamak zorunda kaldı.