Bölüm 1: Compile, link, load
Bu bölümde bir programın, kaynak koddan başlayıp ilk instruction’ı çalıştırılana kadar başından geçenleri konuşuyoruz.
Transkript
00:40 - Programlama dili nedir
- Syntax + semantic tanimlar = dil
- Bunun yaninda da bir standart kutuphane
- C ve C++ komite ile tasarlanan diller, bir standart var
- Rust ve Python bu sekilde degil, compiler/interpreter ne yapiyorsa dil odur
- Commitee driven design sikintilari: yavas gelistirme, aralikli release’ler
- Standartsiz diller cok daha hizli ilerleyebiliyor
- Birden cok implementation gereken isler var: havacilik vs, bunlar icin standart onemli
- C++ implementation’lari: gcc, clang, msvc vs tamamen birbirinden bagimsiz
- EDG sirketlere C++ front-end’i satan bir sirket, intel (icc), nvidia (nvcc) vs kullaniyor
- Compiler yapisi: front end’ler bir programlama dilindeki kaynak kodu alip derleyicinin icindeki IR’a (intermediate representation) donusturur
- Backend IR’i alip bir makina diline cevirir
- Backend’leri dogurdan mimariyi tasarlayan sirket (ornegin ARM) saglayabilir veya topluluk gelistirebilir
- ARM nedir? Bir standart degil, her formal dokuman bir standart degil
- ARM, bir ISA (Instruction Set Architecture) tasarlayip, bunu diger sirketlere lisansliyor
- Instruction’larin yaninda ekstra ozellikleri de tasarlarlar (multicore synchronization, bus vs)
- Biri ARM’dan lisans aldigi zaman, islemciyi kendileri dizayn edebilirler (ornegin Apple M1, A serisi vs)
- ISA’de su instructionlar olmali ve su encoding’e sahip olmali gibi detaylar var, lisanslayan taraf bunlara uymak zorunda
- ARM ayni zamanda implementation da saglayabilir (ornegin Cortex A serisi, veya Neoverse serisi)
- Apple kadar buyuk olmayan veya kaynak ayirmak istemeyen sirketler bunlari aliyor
- Yanina IO islerini saglayacak bloklari lisanslayan taraf dizayn ediyor, ve bunlar teoride islemciden bagimsiz olabilir
- Ornegin ayni seri port blogunu hem ARM hem Risc-V islemciye koyabiliriz
09:50 Toolchain nedir
- Tek bir kaynak koddan executable cikarmak mumkun olsa da pratik degil: gelistirmek zor, derleme zamanlari uzar vs
- Birden cok dosyayi ayri ayri derleyeyim ve birlestireyim
- Ornegin paralel olarak veya farkli zamanlarda (kisacasi kutuphane)
- Bunlari birlestiren programa linker denir
- Compiler 1 kaynak dosyasini alip 1 tane object file cikartir
- Compiler’lar genellikle derleyicinin kendisi degil, butun toolchain’in driver’i
gcc
dedigimiz zaman, aslinda preprocessor + compiler + linker calisiyor- Tek bir dosyan olsa bile bu gecerli
- Cunku bir programin giris noktasi gercekte
main
degil, main’e gelmeden biseyler calisiyor - Hosted C++ icin bir programin girisi
int main()
ve varyantlari - Freestanding icin herhangi bir kisit yok, girisi kendimiz yazdigimiz icin
main
diyebiliriz ama istedigimiz ismi kullanabiliriz. kaynak
15:00 Linker detaylari
- Linker birbirini kullanan dosyalardaki sembolleri birbirine baglayan program
- Compiler tanimini gormedigi bir fonksyon gordugu zaman normal bir
call
instruction’u generate edip, uydurma bir adres koyar - Bu uydurma adresin duzeltilmesi icin de object file icine bir relocation koyuyor
- Relocation: farkli bir yerden gelecek adresin object file’da duzeltilmesi
- Bir linker temel olarak relocation cozer
- Bir programda cozulmemis relocation kalmazsa, elimizde executable bir program olmus olur
- Bir fonksyonu cagirmak icin derleyicinin fonksyonun tanimini gormesine gerek yok, declaration’dan argumanlari nasil gececegini anlayabilir (hangi register’dan ne gidecek vs gibi)
- Farkli fonksyonlar birbirine isimleri ustunden baglaniyor
- Bu C icin tamam, ama C++’da overloading var
- Cozum: name mangling
sort(vector<int>)
dedigimizde compiler bunusort_vector_int
gibi bir isme ceviriyor- C icin ayni isimli farkli tanima sahip 2 fonksyon varsa sikinti yaratir
- Cozum olarak genellikle birden cok tanimi olan fonksyonlari linker reddeder
inline
bu kontrolu kapatiyor, linker istedigi tanimi secmekte ozgur- Dolayisiyla ayni isimli ve farkli tanimli 2
inline
fonksyon varsa, basiniz belaya gelistirebilir - Buna ODR violation denir, ve undefined behaviour’a sonuc verir
20:30 Inline nedir
- Bir fonksyona
inline
yazmasak da compiler o fonksyonu inline edebilir - Veya
inline
yazsak da inline etmeyebilir - Compiler’in bir programciya gore cok daha fazla bilgisi var
- Compiler her call site icin bir inline skoru hesapliyor
- Fonksyon
inline
ise, bu skor biraz yukseliyor - Skor belli bir limitin altinda kalirsa inlinelanmaz
- Dynamic linking icin sikinti cikarir, bu durumlar icin bu fonksyonu kesinlikle inline’lama diyebiliyoruz
22:30 Dynamic linking nedir
- Calismasi icin OS gerekmeyebilir, sadece bir dynamic linker’a ihtiyac var
- Freestanding bir embedded sistemde de gerceklestirilebilir
- Kisaca yaptigi sey, ozel bir relocation koyup, bu relocation’in calisma zamaninda gelecegini soyleyebiliyor
- Hangi dinamik kutuphanelere ihtiyac duydugumuz executable icinde tutuluyor
- Dynamic linker programi calistirmadan once butun kutuphaneleri acip relocation’lari cozmeye calisiyor
- Static linking’in aksine, dosyayi degistirmiyoruz, sadece memory’deki adresleri guncelliyoruz
24:40 Assembler nedir
- Derleyiciler assembly mi cikarir?
- Genellikle compiler’lar dogrudan binary dosya cikarir, cunku assembler da zaten compiler’in parcasi ve araya bir assembly adimi koymak gereksiz overhead
25:50 Binary nedir
- Compiler’larin cikardigi dosya nedir? Dumduz binary mi?
- Linux ortaminda ve siklikla embedded icin derleyiciler ELF dosyasi cikarir
- Duz binary’ler executable kod tasimali ve dolayisiyla relocation’lar vs tasinamaz
- Her OS boyle degil, macOS icin mach-o, windows icin portable executable cikariliyor
- ELF dosyasini bir container gibi dusunebiliriz
- Icinde section’lardan olusan segmentler var
- Segmentlerin yuklenmesi gereken adresleri var
- Sabit adreslerden dolayi virtual memory’ye ihtiyac duyuyoruz
- Address space layout randomization (ASLR)
- Adresler hep ayni olursa, bir exploit hep calisabilir cunku hersey hep ayni
- Return oriented programming: fonksyonlarin donus adresini degistirerek istenmeyen seyler calistirmak
- ROP yapmamizin sebebi, disardan calistirilabilir kod yuklemek artik pek mumkun degil
- Loader programi istedigi yere degil rastgele bir adrese koyuyor
- Rastgele koymak problemler cikarabilir: relocation’lar vs bosa gitcek
- Temel cozum: position independent code (PIC) koymak
- PIC kodlarda dogrudan adresler koymak yerine, oldugumuz instruction’dan offset koyulur
- Rastgele yerlere koyarsak da, offsetler ayni kalacagi icin direk calisacak
- Embedded dunyada ise, segmentler tam olarak istenen yere gider
- Linker’a hangi segmentin nereye gidecegini linker scriplerle soyluyoruz
- Kendimiz istedigimiz segmentleri tanimlayabiliriz, ama hic bir tool o segmenti anlamayacak
.mytext
‘e gidebilir butun kod, ama loader yukleyemez- Debugging bilgisi ELF dosyalarinda ozel bir segment icinde duruyor
- Programi her calistirdigimizda debug bilgisi RAM’e yuklenmiyor
- Belirli convention’lar var
33:40 Debugging bilgisi
- En basit olarak, su adresler arasinda su fonksyon tanimi var diyebilir
- Daha basiti
nm
ile fonksyonlarin baslangic adreslerini alip, binary search ile stack trace cikarabiliriz - Gunumuzde bunlar iyice karisti tabi, bir fonksyon parcalanabilir vs
- Machine function splitting: her fonksyonu hot-cold iki parcaya ayirip, fonksyonlarin hot kisimlarini bir araya koymak
- Cold yerlerde bol bol cache miss alsak da cok problem degil
36:00 Embedded calistirma
- Execute in place (XIP): programlarin
.text
‘ini RAM’e yuklememize gerek yok, direk flash’tan calisabilir - Flash ve RAM birbirinden tamamen ayri
- Calisirken instruction’lar RAM’i asla gormuyor
- Flash yavas oldugu icin arada bir cache oluyor, ornegin STM’lerde ART accelerator var
- Olmayabilir de ama anlamsiz yavas olacaktir
- Islemcilerin durmasina stall denir
- Segment’ler Flash’ta tutulup, RAM’den adreslenebilir, ornegin initialized data
- Bu tarz durumlarda, datayi flash’tan alip RAM’e yuklemek bizim isimiz oluyor
40:00 Loading nedir
- Embedded veya bir PC ustunde loading cok da farkli degil aslinda
- PC’de de
.text
aslinda RAM’e kopyalanmiyor - Ihtiyac duydukca diskten RAM’e getiriliyor
- Bu durumda RAM, text icin bir onbellek gibi kullaniliyor
- Linux yuklemeyi runtime’da yapiyor
- Embedded tarafta onden yapiliyor
- Aslinda tamamen ayni isi yapiyoruz
- Datayi kopyaladiktan sonra,
main
programina zipliyoruz - Bu noktada toolchain’in isi bitti, programimiz calisiyor
main
e girmeden once global constructor’lar calistiriliyor- Dolayisiyla,
main
bir programin tam olarak girisi olamaz - Isletim sistemi, baska bir yere zipliyor (
_start
olur genelde) - Libc tarafindan saglanabilir