GNU/Linux >> Belajar Linux >  >> Linux

Mengapa Linux/gnu linker memilih alamat 0x400000?

Alamat awal biasanya disetel oleh skrip penaut.

Misalnya, di GNU/Linux, lihat /usr/lib/ldscripts/elf_x86_64.x kita melihat:

...
PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x400000)); \
    . = SEGMENT_START("text-segment", 0x400000) + SIZEOF_HEADERS;

Nilai 0x400000 adalah nilai default untuk SEGMENT_START() berfungsi di platform ini.

Anda dapat mengetahui lebih lanjut tentang skrip penaut dengan menjelajahi manual penaut:

% info ld Scripts

ld Skrip penaut default memiliki 0x400000 nilai yang dimasukkan untuk executable non-PIE.

PIE (Position Independent Executables) tidak memiliki alamat dasar default; mereka selalu dipindahkan oleh kernel, dengan kernel standarnya adalah 0x0000555... ditambah beberapa offset ASLR kecuali ASLR dinonaktifkan untuk proses ini atau seluruh sistem. ld tidak memiliki kendali atas hal ini. Perhatikan bahwa sebagian besar sistem modern mengonfigurasi GCC untuk menggunakan -fPIE -pie secara default, sehingga melewati -pie ke ld , dan mengubah C menjadi asm yang tidak tergantung posisi. asm tulisan tangan harus mengikuti aturan yang sama jika Anda menautkannya seperti itu.

Tapi apa yang membuat 0x400000 (4 MiB) default yang bagus?

Itu harus di atas mmap_min_addr =65536 =64K secara default.

Dan berada jauh dari 0 memberi lebih banyak ruang untuk menjaga dari NULL deref dengan pembacaan offset .text atau .data /.bss memori (array[i] di mana array adalah NULL). Bahkan tanpa meningkatkan mmap_min_addr (yang menyisakan ruang untuk tanpa merusak executable), biasanya mmap secara acak memilih alamat tinggi sehingga dalam praktiknya kami memiliki setidaknya 4MiB penjagaan terhadap NULL deref.

2M-selaras bagus

Ini menempatkannya di awal direktori halaman di tingkat berikutnya dari tabel halaman berarti jumlah entri tabel halaman 4K yang sama akan dibagi menjadi entri direktori halaman 2M yang lebih sedikit, menghemat memori tabel halaman kernel dan halaman bantuan -berjalan tembolok perangkat keras lebih baik. Untuk array statis besar, mendekati awal subpohon 1G dari level berikutnya juga bagus.

IDK mengapa 4MiB bukannya 2MiB, atau apa alasan pengembangnya. 4MiB adalah ukuran halaman besar 32-bit tanpa PAE (PTE 4-byte jadi 10 bit per level, bukan 9), tetapi CPU harus menggunakan tabel halaman x86-64 untuk berada dalam mode 64-bit.

Alamat awal yang rendah memungkinkan hampir 2 GiB array statis

(Tanpa menggunakan model kode yang lebih besar, di mana setidaknya array besar harus ditangani dengan cara yang terkadang kurang efisien. Lihat bagian 3.5.1 Batasan Arsitektur dalam dokumen ABI Sistem V x86-64 untuk detail tentang model kode.)

Model kode default untuk executable non-PIE ("kecil") memungkinkan program berasumsi bahwa setiap alamat statis berada dalam ruang alamat virtual 2GiB yang rendah. Jadi alamat absolut apa pun di .text /.rodata , .data , .bss dapat digunakan sebagai 32-bit sign-extended direct di kode mesin yang lebih efisien.

(Hal ini tidak berlaku di PIE atau pustaka bersama:lihat Alamat absolut 32-bit tidak lagi diizinkan di x86-64 Linux? untuk hal-hal yang Anda / kompiler tidak dapat lakukan di x86-64 asm sebagai hasilnya, terutama addss xmm0, [foo + rdi*4] sebagai gantinya membutuhkan RIP-relatif LEA untuk mendapatkan alamat awal array ke dalam register. satu-satunya mode pengalamatan relatif RIP x86-64 adalah [RIP+rel32], tanpa register tujuan umum apa pun.)

Memulai bagian/segmen yang dapat dieksekusi di dekat bagian bawah ruang alamat virtual membuat hampir seluruh 2GiB tersedia untuk teks+data+bss sebesar itu. (Mungkin saja memiliki default yang lebih tinggi, dan memiliki executable yang besar membuat ld memilih alamat yang lebih rendah agar sesuai, tetapi itu akan menjadi skrip linker yang lebih rumit.)

Ini termasuk array yang diinisialisasi nol di .bss yang tidak membuat file yang dapat dieksekusi besar, hanya gambar proses dalam memori. Dalam praktiknya, pemrogram Fortran cenderung mengalami ini lebih dari C dan C++, karena array statis populer di sana. Misalnya gfortran for dummies:Apa sebenarnya yang dilakukan mcmodel=medium? memiliki penjelasan yang bagus tentang kesalahan build dengan small default model, dan perbedaan asm x86-64 yang dihasilkan untuk medium (di mana objek di atas ambang batas ukuran tertentu tidak dianggap berada dalam 2G rendah atau dalam +-2G dari kode. Tetapi kode dan data statis yang lebih kecil tetap demikian sehingga penalti kecepatannya kecil.)

Misalnya static float arr[1UL<<28]; adalah larik 1 GiB. Jika Anda memiliki 3 dari mereka, mereka tidak dapat memulai semuanya di dalam 2 GiB rendah (yang mungkin Anda butuhkan untuk asm tulisan tangan), apalagi setiap elemen dapat diakses.

gcc -fno-pie diharapkan dapat mengkompilasi float *p = &arr[size-1]; ke mov $arr+1073741820, %edi , sebuah mov $imm32 5-byte . RIP-relatif tidak akan berfungsi jika alamat target berjarak lebih dari 2GiB dari kode yang menghasilkan alamat (atau memuat darinya dengan movss arr+1073741820(%rip), %xmm0; RIP-relatif adalah cara normal untuk memuat/menyimpan data statis bahkan dalam non-PIE, ketika tidak ada indeks variabel runtime.) Itu sebabnya model PIC kecil juga memiliki batas ukuran 2GiB pada teks+data+bss (ditambah kesenjangan antar segmen):semua data statis dan kode harus berada dalam 2GiB dari yang lain yang mungkin ingin menjangkaunya.

Jika kode Anda hanya mengakses elemen tinggi atau alamatnya melalui indeks variabel runtime, Anda hanya memerlukan awal dari setiap larik, simbol itu sendiri, berada di 2 GiB rendah. Saya lupa jika linker memberlakukan end-of-bss dalam 2GiB rendah; mungkin karena skrip linker meletakkan simbol di sana yang mungkin dirujuk oleh beberapa kode startup CRT.

Catatan kaki 1 :Tidak ada ukuran lebih kecil yang berguna untuk model kode yang lebih kecil dari 2GiB. kode mesin x86-64 menggunakan 8 atau 32-bit untuk mode segera dan pengalamatan. 8-bit (256 byte) terlalu kecil untuk dapat digunakan, dan banyak instruksi penting seperti call rel32 , mov r32, imm32 , dan [rip+rel32] pengalamatan, hanya tersedia dengan konstanta 4-byte bukan 1-byte.

Membatasi hingga 2 GiB rendah (bukan 4) berarti bahwa alamat dapat dengan aman diperpanjang nol seperti dengan mov edi, OFFSET arr , atau sign-extended, seperti mov eax, [arr + rdi*4] . Ingatlah bahwa alamat bukan satu-satunya kasus penggunaan untuk [reg + disp32] mode pengalamatan; [rbp - 256] sering masuk akal, jadi sebaiknya tanda kode mesin x86-64 memperluas disp8 dan disp32 ke 64 bit, bukan nol.

Ekstensi nol implisit ke 64-bit terjadi saat menulis register 32-bit, seperti pada mov -segera untuk memasukkan alamat dalam register, di mana ukuran operan 32-bit adalah instruksi kode mesin yang lebih kecil daripada ukuran operan 64-bit. Lihat Cara memuat alamat fungsi atau label ke dalam register (yang juga mencakup LEA relatif RIP).

Terkait untuk Windows 32-bit

Raymond Chen menulis artikel tentang mengapa 0x400000 sama alamat dasar adalah default untuk Windows 32-bit .

Dia menyebutkan bahwa DLL dimuat di alamat tinggi secara default, dan alamat rendah jauh dari itu. x86-64 Objek bersama SysV dapat dimuat di mana pun ada celah ruang alamat yang cukup besar, dengan default kernel mendekati bagian atas ruang alamat virtual ruang pengguna, yaitu bagian atas rentang kanonis. Tapi objek bersama ELF harus sepenuhnya dapat dipindahkan sehingga akan berfungsi dengan baik di mana saja.

Pilihan 4MiB untuk Windows 32-bit juga dimotivasi dengan menghindari 64K rendah (NULL deref), dan dengan memilih awal direktori halaman untuk tabel halaman 32-bit lama. (Di mana ukuran "halaman besar" adalah 4M, bukan 2M untuk x86-64 atau PAE.) Dengan sekumpulan peta memori lama Win95 dan Win3.1 alasan mengapa setidaknya 1MiB atau 4MiB sebagian diperlukan, dan hal-hal seperti bekerja di sekitar CPU bug.


Linux
  1. Mengapa Nullglob Tidak Default?

  2. Alamat Default

  3. Cara Menemukan IP Gateway Default di Linux

  1. BCRYPT - Mengapa Distribusi Linux tidak menggunakannya secara default?

  2. Mengapa Perl diinstal secara default dengan sebagian besar distribusi Linux?

  3. Mengapa distribusi linux tidak secara default memasang tmpfs dengan inode tak terbatas?

  1. Mengapa Linux sangat penting untuk komputasi tepi

  2. Mengapa saya tetap menggunakan xterm

  3. Linux – Cara Portabel Untuk Mendapatkan Alamat Sumber Rute Default?