Untuk memahami linker, sebaiknya pahami dulu apa yang terjadi "di balik terpal" saat Anda mengonversi file sumber (seperti file C atau C ++) menjadi file yang dapat dijalankan (file yang dapat dieksekusi adalah file yang dapat dijalankan di komputer Anda atau mesin orang lain yang menjalankan arsitektur mesin yang sama).
Di bawah tenda, ketika sebuah program dikompilasi, kompilator mengubah file sumber menjadi kode byte objek. Kode byte ini (terkadang disebut kode objek) adalah instruksi mnemonik yang hanya dimengerti oleh arsitektur komputer Anda. Biasanya, file ini memiliki ekstensi .OBJ.
Setelah file objek dibuat, linker mulai bekerja. Lebih sering daripada tidak, program nyata yang melakukan sesuatu yang berguna perlu mereferensikan file lain. Di C, misalnya, program sederhana untuk mencetak nama Anda ke layar akan terdiri dari:
printf("Hello Kristina!\n");
Ketika kompilator mengkompilasi program Anda ke dalam file obj, itu hanya menempatkan referensi ke printf
fungsi tersebut. Linker menyelesaikan referensi ini. Sebagian besar bahasa pemrograman memiliki pustaka rutinitas standar untuk mencakup hal-hal dasar yang diharapkan dari bahasa itu. Linker menautkan file OBJ Anda dengan pustaka standar ini. Linker juga dapat menghubungkan file OBJ Anda dengan file OBJ lainnya. Anda dapat membuat file OBJ lain yang memiliki fungsi yang dapat dipanggil oleh file OBJ lain. Linker bekerja hampir seperti copy dan paste pengolah kata. Ini "menyalin" semua fungsi yang diperlukan yang direferensikan oleh program Anda dan membuat satu executable. Terkadang pustaka lain yang disalin bergantung pada OBJ atau file pustaka lainnya. Terkadang penaut harus cukup rekursif untuk melakukan tugasnya.
Perhatikan bahwa tidak semua sistem operasi membuat satu file yang dapat dieksekusi. Windows, misalnya, menggunakan DLL yang menyimpan semua fungsi ini bersama-sama dalam satu file. Ini mengurangi ukuran eksekusi Anda, tetapi membuat eksekusi Anda bergantung pada DLL spesifik ini. DOS dulu menggunakan sesuatu yang disebut Overlay (file .OVL). Ini memiliki banyak tujuan, tetapi salah satunya adalah untuk menjaga fungsi yang umum digunakan bersama dalam 1 file (tujuan lain yang dilayaninya, jika Anda bertanya-tanya, adalah untuk dapat memasukkan program besar ke dalam memori. DOS memiliki batasan dalam memori dan overlay dapat akan "dibongkar" dari memori dan overlay lainnya dapat "dimuat" di atas memori tersebut, oleh karena itu disebut "overlay"). Linux telah berbagi pustaka, yang pada dasarnya ide yang sama dengan DLL (orang-orang Linux hard core yang saya tahu akan memberi tahu saya bahwa ada BANYAK perbedaan BESAR).
Semoga ini bisa membantu Anda memahami!
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: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:
.text
dan.data
bagian dari beberapa file objekPrasyarat: pemahaman minimal tentang:
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:
disusun dan dirakit dengan:
dengan NASM 2.10.09.
1) .teks dari .o
Pertama kita mendekompilasi
.text
bagian dari file objek:pemberian yang mana:
garis krusialnya adalah:
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:
0x0
pada output yang dikompilasi"Informasi tambahan" ini terdapat di
.rela.text
bagian file objek2) .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:yang mengandung;
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 tersebutType = 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, jadi00 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 di0
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:memberikan:
Jadi satu-satunya hal yang berubah dari file objek adalah baris kritis:
yang sekarang mengarah ke alamat
0x6000d8
(d8 00 60 00 00 00 00 00
dalam little-endian), bukan0x0
.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:
pemberian yang mana:
Ini memberitahu kita bahwa
.data
bagian, yang kedua, dimulai padaVirtAddr
=0x06000d8
.Dan satu-satunya hal di bagian data adalah string hello world kami.
Tingkat bonus
PIE
menghubungkan: Apa itu opsi -fPIE untuk executable posisi-independen di gcc dan ld?sumber
Dalam bahasa seperti 'C', setiap modul kode secara tradisional dikompilasi secara terpisah menjadi gumpalan kode objek, yang siap dieksekusi dalam segala hal selain dari semua referensi yang dibuat modul di luar dirinya (yaitu ke perpustakaan atau ke modul lain). belum terselesaikan (yaitu kosong, menunggu seseorang datang dan membuat semua koneksi).
Apa yang dilakukan linker adalah melihat semua modul bersama-sama, melihat apa yang dibutuhkan setiap modul untuk terhubung ke luar itu sendiri, dan melihat semua hal yang diekspornya. Itu kemudian memperbaiki semuanya, dan menghasilkan eksekusi akhir, yang kemudian dapat dijalankan.
Di mana penautan dinamis juga terjadi, keluaran dari penaut masih belum dapat dijalankan - masih ada beberapa referensi ke pustaka eksternal yang belum diselesaikan, dan mereka diselesaikan oleh OS pada saat memuat aplikasi (atau mungkin bahkan nanti saat berlari).
sumber
Ketika kompilator menghasilkan file objek, itu menyertakan entri untuk simbol yang didefinisikan dalam file objek itu, dan referensi ke simbol yang tidak didefinisikan dalam file objek itu. Linker mengambilnya dan menyatukannya sehingga (ketika semuanya bekerja dengan baik) semua referensi eksternal dari setiap file dipenuhi oleh simbol yang ditentukan dalam file objek lain.
Kemudian menggabungkan semua file objek tersebut bersama-sama dan memberikan alamat ke masing-masing simbol, dan di mana satu file objek memiliki referensi eksternal ke file objek lain, itu mengisi alamat setiap simbol di mana pun itu digunakan oleh objek lain. Dalam kasus umum, itu juga akan membangun tabel dari alamat absolut yang digunakan, sehingga loader dapat / akan "memperbaiki" alamat ketika file dimuat (yaitu, itu akan menambahkan alamat pemuatan dasar ke masing-masing alamat sehingga semuanya mengacu pada alamat memori yang benar).
Beberapa linker modern juga dapat melakukan beberapa (dalam beberapa kasus banyak ) "hal" lain, seperti mengoptimalkan kode dengan cara yang hanya mungkin dilakukan setelah semua modul terlihat (misalnya, menghapus fungsi yang disertakan karena ada kemungkinan bahwa beberapa modul lain mungkin memanggilnya, tetapi setelah semua modul disatukan, jelas tidak ada yang memanggilnya).
sumber