GNU/Linux >> Belajar Linux >  >> Linux

Panggilan sistem Intel x86 vs x64

Bagian umum

EDIT:Bagian yang tidak relevan dengan Linux dihapus

Meskipun tidak sepenuhnya salah, mempersempit ke int 0x80 dan syscall terlalu menyederhanakan pertanyaan dengan sysenter setidaknya ada opsi ke-3.

Menggunakan 0x80 dan eax untuk nomor syscall, ebx, ecx, edx, esi, edi, dan ebp untuk meneruskan parameter hanyalah salah satu dari banyak kemungkinan pilihan lain untuk mengimplementasikan panggilan sistem, tetapi register tersebut adalah yang dipilih oleh ABI Linux 32-bit .

Sebelum melihat lebih dekat pada teknik yang terlibat, harus dinyatakan bahwa mereka semua berputar di sekitar masalah melarikan diri dari penjara hak istimewa setiap proses berjalan.

Pilihan lain dari yang disajikan di sini yang ditawarkan oleh arsitektur x86 adalah penggunaan gerbang panggilan (lihat:http://en.wikipedia.org/wiki/Call_gate)

Satu-satunya kemungkinan lain yang ada di semua mesin i386 adalah menggunakan interupsi perangkat lunak, yang memungkinkan ISR (Interrupt Service Routine atau sekadar penangan interupsi ) untuk dijalankan pada tingkat hak istimewa yang berbeda dari sebelumnya.

(Fakta menyenangkan:beberapa OS i386 telah menggunakan pengecualian instruksi tidak valid untuk memasukkan kernel untuk panggilan sistem, karena itu sebenarnya lebih cepat daripada int instruksi pada 386 CPU. Lihat petunjuk syscall/sysret OsDev dan sysenter/sysexit yang mengaktifkan untuk ringkasan kemungkinan mekanisme panggilan sistem.)

Gangguan Perangkat Lunak

Apa yang sebenarnya terjadi setelah interupsi dipicu bergantung pada apakah beralih ke ISR memerlukan perubahan hak istimewa atau tidak:

(Panduan Pengembang Perangkat Lunak Arsitektur Intel® 64 dan IA-32)

6.4.1 Operasi Panggilan dan Pengembalian untuk Prosedur Penanganan Interupsi atau Pengecualian

...

Jika segmen kode untuk prosedur penangan memiliki tingkat hak istimewa yang sama dengan program atau tugas yang sedang dijalankan, prosedur penangan menggunakan tumpukan saat ini; jika penangan mengeksekusi pada tingkat hak istimewa yang lebih tinggi, prosesor beralih ke tumpukan untuk tingkat hak istimewa penangan.

....

Jika peralihan tumpukan terjadi, prosesor akan melakukan hal berikut:

  1. Menyimpan sementara (secara internal) konten register SS, ESP, EFLAGS, CS, dan> EIP saat ini.

  2. Memuat pemilih segmen dan penunjuk tumpukan untuk tumpukan baru (yaitu, tumpukan untuk tingkat hak istimewa yang dipanggil) dari TSS ke register SS dan ESP dan beralih ke tumpukan baru.

  3. Mendorong nilai SS, ESP, EFLAGS, CS, dan EIP yang disimpan sementara untuk tumpukan prosedur yang terputus ke tumpukan baru.

  4. Mendorong kode kesalahan pada tumpukan baru (jika sesuai).

  5. Memuat pemilih segmen untuk segmen kode baru dan penunjuk instruksi baru (dari gerbang interupsi atau gerbang perangkap) masing-masing ke dalam register CS dan EIP.

  6. Jika panggilan melalui gerbang interupsi, kosongkan flag IF di register EFLAGS.

  7. Memulai eksekusi prosedur penangan pada tingkat hak istimewa yang baru.

... huh, ini sepertinya banyak yang harus dilakukan dan bahkan setelah kita selesai, itu tidak menjadi lebih baik:

(kutipan diambil dari sumber yang sama seperti yang disebutkan di atas:Intel® 64 dan IA-32 Architectures Software Developer’s Manual)

Saat menjalankan pengembalian dari penangan interupsi atau pengecualian dari tingkat hak istimewa yang berbeda dari prosedur yang diinterupsi, prosesor melakukan tindakan berikut:

  1. Melakukan pemeriksaan hak istimewa.

  2. Memulihkan register CS dan EIP ke nilainya sebelum interupsi atau pengecualian.

  3. Memulihkan register EFLAGS.

  4. Mengembalikan register SS dan ESP ke nilainya sebelum interupsi atau pengecualian, yang mengakibatkan peralihan tumpukan kembali ke tumpukan prosedur yang terputus.

  5. Melanjutkan eksekusi prosedur yang terputus.

Sisenter

Opsi lain pada platform 32-bit yang sama sekali tidak disebutkan dalam pertanyaan Anda, namun tetap digunakan oleh kernel Linux adalah sysenter instruksi.

(Panduan Pengembang Perangkat Lunak Arsitektur Intel® 64 dan IA-32 Volume 2 (2A, 2B &2C):Referensi Rangkaian Instruksi, A-Z)

Deskripsi Menjalankan panggilan cepat ke prosedur sistem level 0 atau rutin. SYSENTER adalah instruksi pendamping untuk SYSEXIT. Instruksi ini dioptimalkan untuk memberikan performa maksimum untuk panggilan sistem dari kode pengguna yang berjalan pada tingkat hak istimewa 3 ke sistem operasi atau prosedur eksekutif yang berjalan pada tingkat hak istimewa 0.

Salah satu kelemahan menggunakan solusi ini adalah, solusi ini tidak ada di semua mesin 32-bit, sehingga int 0x80 metode masih harus disediakan jika CPU tidak mengetahuinya.

Instruksi SYSENTER dan SYSEXIT diperkenalkan ke arsitektur IA-32 dalam prosesor Pentium II. Ketersediaan instruksi ini pada prosesor ditunjukkan dengan flag fitur SYSENTER/SYSEXITpresent (SEP) yang dikembalikan ke register EDX oleh instruksi CPUID. Sistem operasi yang memenuhi syarat flag SEP juga harus memenuhi syarat keluarga prosesor dan model untuk memastikan bahwa instruksi SYSENTER/SYSEXIT benar-benar ada

Syscall

Kemungkinan terakhir, syscall instruksi, cukup banyak memungkinkan untuk fungsi yang sama dengan sysenter petunjuk. Keberadaan keduanya disebabkan oleh satu (systenter ) diperkenalkan oleh Intel sementara yang lain (syscall ) diperkenalkan oleh AMD.

Khusus Linux

Di kernel Linux salah satu dari tiga kemungkinan yang disebutkan di atas dapat dipilih untuk mewujudkan panggilan sistem.

Lihat juga Panduan Pasti untuk Panggilan Sistem Linux .

Seperti yang sudah disebutkan di atas, int 0x80 metode adalah satu-satunya dari 3 implementasi yang dipilih, yang dapat dijalankan pada CPU i386 apa pun, jadi ini adalah satu-satunya yang selalu tersedia untuk ruang pengguna 32-bit.

(syscall adalah satu-satunya yang selalu tersedia untuk ruang pengguna 64-bit, dan satu-satunya yang harus Anda gunakan dalam kode 64-bit; kernel x86-64 dapat dibangun tanpa CONFIG_IA32_EMULATION , dan int 0x80 masih memanggil ABI 32-bit yang memotong pointer ke 32-bit.)

Untuk memungkinkan untuk beralih di antara semua 3 pilihan, setiap proses yang dijalankan diberi akses ke objek bersama khusus yang memberikan akses ke implementasi panggilan sistem yang dipilih untuk sistem yang sedang berjalan. Ini adalah linux-gate.so.1 yang terlihat aneh Anda mungkin sudah menemukan pustaka yang belum terselesaikan saat menggunakan ldd atau sejenisnya.

(arch/x86/vdso/vdso32-setup.c)

 if (vdso32_syscall()) {                                                                               
        vsyscall = &vdso32_syscall_start;                                                                 
        vsyscall_len = &vdso32_syscall_end - &vdso32_syscall_start;                                       
    } else if (vdso32_sysenter()){                                                                        
        vsyscall = &vdso32_sysenter_start;                                                                
        vsyscall_len = &vdso32_sysenter_end - &vdso32_sysenter_start;                                     
    } else {                                                                                              
        vsyscall = &vdso32_int80_start;                                                                   
        vsyscall_len = &vdso32_int80_end - &vdso32_int80_start;                                           
    }   

Untuk menggunakannya yang harus Anda lakukan adalah memuat semua nomor panggilan sistem register Anda di eax, parameter di ebx, ecx, edx, esi, edi seperti int 0x80 implementasi panggilan sistem dan call rutinitas utama.

Sayangnya tidak semudah itu; untuk meminimalkan risiko keamanan dari alamat tetap yang telah ditentukan sebelumnya, lokasi di mana vdso (objek bersama dinamis virtual ) akan terlihat dalam proses acak, jadi Anda harus mengetahui lokasi yang benar terlebih dahulu.

Alamat ini bersifat individual untuk setiap proses dan diteruskan ke proses setelah dimulai.

Jika Anda tidak tahu, ketika dimulai di Linux, setiap proses mendapatkan penunjuk ke parameter yang diteruskan setelah dimulai dan penunjuk ke deskripsi variabel lingkungan yang dijalankannya di bawah tumpukannya - masing-masing diakhiri oleh NULL.

Selain itu, blok ketiga dari apa yang disebut elf-auxiliary-vectors diteruskan mengikuti yang disebutkan sebelumnya. Lokasi yang benar dikodekan dalam salah satu dari ini yang membawa pengidentifikasi jenis AT_SYSINFO .

Jadi tata letak tumpukan terlihat seperti ini (alamat tumbuh ke bawah):

  • parameter-0
  • ...
  • parameter-m
  • NULL
  • lingkungan-0
  • ....
  • lingkungan-n
  • NULL
  • ...
  • vektor elf tambahan:AT_SYSINFO
  • ...
  • vektor elf tambahan:AT_NULL

Contoh penggunaan

Untuk menemukan alamat yang benar, Anda harus melewati semua argumen dan semua petunjuk lingkungan terlebih dahulu, lalu mulai memindai AT_SYSINFO seperti yang ditunjukkan pada contoh di bawah ini:

#include <stdio.h>
#include <elf.h>

void putc_1 (char c) {
  __asm__ ("movl $0x04, %%eax\n"
           "movl $0x01, %%ebx\n"
           "movl $0x01, %%edx\n"
           "int $0x80"
           :: "c" (&c)
           : "eax", "ebx", "edx");
}

void putc_2 (char c, void *addr) {
  __asm__ ("movl $0x04, %%eax\n"
           "movl $0x01, %%ebx\n"
           "movl $0x01, %%edx\n"
           "call *%%esi"
           :: "c" (&c), "S" (addr)
           : "eax", "ebx", "edx");
}


int main (int argc, char *argv[]) {

  /* using int 0x80 */
  putc_1 ('1');


  /* rather nasty search for jump address */
  argv += argc + 1;     /* skip args */
  while (*argv != NULL) /* skip env */
    ++argv;            

  Elf32_auxv_t *aux = (Elf32_auxv_t*) ++argv; /* aux vector start */

  while (aux->a_type != AT_SYSINFO) {
    if (aux->a_type == AT_NULL)
      return 1;
    ++aux;
  }

  putc_2 ('2', (void*) aux->a_un.a_val);

  return 0;
}

Seperti yang akan Anda lihat dengan melihat cuplikan /usr/include/asm/unistd_32.h berikut di sistem saya:

#define __NR_restart_syscall 0
#define __NR_exit            1
#define __NR_fork            2
#define __NR_read            3
#define __NR_write           4
#define __NR_open            5
#define __NR_close           6

Syscall yang saya gunakan adalah yang bernomor 4 (tulis) sebagaimana diteruskan dalam register eax. Mengambil filedescriptor (ebx =1), penunjuk data (ecx =&c) dan ukuran (edx =1) sebagai argumennya, masing-masing diteruskan dalam daftar yang sesuai.

Singkat cerita

Membandingkan int 0x80 yang seharusnya berjalan lambat panggilan sistem di apa saja Intel CPU dengan (semoga) implementasi yang jauh lebih cepat menggunakan (benar-benar diciptakan oleh AMD) syscall instruksi membandingkan apel dengan jeruk.

IMHO:Kemungkinan besar sysenter instruksi bukan int 0x80 harus untuk ujian di sini.


Ada tiga hal yang perlu terjadi saat Anda memanggil kernel (melakukan panggilan sistem):

  1. Sistem beralih dari "mode pengguna" ke "mode kernel" (dering 0).
  2. Tumpukan beralih dari "mode pengguna" ke "mode kernel".
  3. Lompatan dilakukan ke bagian kernel yang sesuai.

Jelas, begitu berada di dalam kernel, kode kernel perlu mengetahui apa yang sebenarnya Anda ingin kernel lakukan, karenanya meletakkan sesuatu di EAX, dan seringkali lebih banyak hal di register lain karena ada hal-hal seperti "nama file yang ingin Anda buka. " atau "buffer untuk membaca data dari file ke dalam" dll, dll.

Prosesor yang berbeda memiliki cara yang berbeda untuk mencapai tiga langkah di atas. Di x86, ada beberapa pilihan, tetapi dua yang paling populer untuk asm tulisan tangan adalah int 0xnn (mode 32-bit) atau syscall (modus 64-bit). (Ada juga mode 32-bit sysenter , diperkenalkan oleh Intel untuk alasan yang sama AMD memperkenalkan versi mode 32-bit dari syscall :sebagai alternatif yang lebih cepat untuk int 0x80 yang lambat . glibc 32-bit menggunakan mekanisme panggilan sistem efisien mana pun yang tersedia, hanya menggunakan int 0x80 yang lambat jika tidak ada yang lebih baik tersedia.)

Versi 64-bit dari syscall instruksi diperkenalkan dengan arsitektur x86-64 sebagai cara yang lebih cepat untuk memasukkan panggilan sistem. Ini memiliki satu set register (menggunakan mekanisme x86 MSR) yang berisi alamat RIP yang ingin kita lompati, nilai pemilih apa yang akan dimuat ke CS dan SS, dan untuk melakukan transisi Ring3 ke Ring0. Itu juga menyimpan alamat pengirim di ECX/RCX. [Harap baca manual set instruksi untuk semua detail instruksi ini - ini tidak sepenuhnya sepele!]. Karena prosesor mengetahui ini akan beralih ke Ring0, prosesor dapat langsung melakukan hal yang benar.

Salah satu poin utamanya adalah syscall hanya memanipulasi register; itu tidak memuat atau menyimpan apa pun. (Inilah mengapa menimpa RCX dengan RIP yang disimpan dan R11 dengan RFLAGS yang disimpan). Akses memori bergantung pada tabel halaman, dan entri tabel halaman memiliki sedikit yang membuatnya hanya valid untuk kernel, bukan ruang pengguna, jadi lakukan akses memori sementara mengubah tingkat hak istimewa mungkin perlu menunggu vs. hanya menulis register. Setelah dalam mode kernel, kernel biasanya akan menggunakan swapgs atau cara lain untuk menemukan tumpukan kernel. (syscall tidak tidak memodifikasi RSP; itu masih menunjuk ke tumpukan pengguna saat masuk ke kernel.)

Saat kembali menggunakan instruksi SYSRET, nilai dipulihkan dari nilai yang telah ditentukan sebelumnya dalam register, jadi sekali lagi, cepat, karena prosesor hanya perlu mengatur beberapa register. Prosesor mengetahui bahwa itu akan berubah dari Ring0 ke Ring3, sehingga dapat melakukan hal yang benar dengan cepat.

(CPU AMD mendukung syscall instruksi dari ruang pengguna 32-bit; CPU Intel tidak. x86-64 awalnya adalah AMD64; inilah mengapa kami memiliki syscall dalam mode 64-bit. AMD mendesain ulang sisi kernel dari syscall untuk mode 64-bit, jadi syscall 64-bit titik masuk kernel sangat berbeda dari syscall 32-bit titik masuk dalam kernel 64-bit.)

int 0x80 varian yang digunakan dalam mode 32-bit akan memutuskan apa yang harus dilakukan berdasarkan nilai dalam tabel deskriptor interupsi, yang berarti membaca dari memori. Di sana ia menemukan nilai CS dan EIP/RIP yang baru. Register CS baru menentukan level "dering" baru - Ring0 dalam kasus ini. Ini kemudian akan menggunakan nilai CS baru untuk melihat ke Segmen Status Tugas (berdasarkan register TR) untuk mengetahui penunjuk tumpukan mana (ESP/RSP dan SS), dan akhirnya melompat ke alamat baru. Karena ini adalah solusi yang kurang langsung dan lebih umum, ini juga lebih lambat. EIP/RIP dan CS lama disimpan di tumpukan baru, bersama dengan nilai lama SS dan ESP/RSP.

Saat kembali, menggunakan instruksi IRET, prosesor membaca alamat pengirim dan nilai penunjuk tumpukan dari tumpukan, memuat segmen tumpukan baru dan nilai segmen kode dari tumpukan juga. Sekali lagi, prosesnya umum, dan membutuhkan beberapa kali pembacaan memori. Karena generik, prosesor juga harus memeriksa "apakah kita mengubah mode dari Ring0 ke Ring3, jika demikian, ubah hal-hal ini".

Singkatnya, ini lebih cepat karena dimaksudkan untuk bekerja seperti itu.

Untuk kode 32-bit, ya, Anda pasti bisa menggunakan int 0x80 yang lambat dan kompatibel jika Anda mau.

Untuk kode 64-bit, int 0x80 lebih lambat dari syscall dan akan memotong pointer Anda menjadi 32-bit, jadi jangan gunakan itu. Lihat Apa yang terjadi jika Anda menggunakan ABI Linux 32-bit int 0x80 dalam kode 64-bit? Plus, int 0x80 tidak tersedia dalam mode 64-bit di semua kernel, jadi tidak aman bahkan untuk sys_exit yang tidak menggunakan arg penunjuk apa pun:CONFIG_IA32_EMULATION dapat dinonaktifkan, dan terutama is dinonaktifkan pada Subsistem Windows untuk Linux.


Linux
  1. Cara Mengetahui Apakah Sistem Mendukung Intel Amt?

  2. Bagaimana cara memetakan tumpukan untuk panggilan sistem clone () di linux?

  3. cetak tumpukan panggilan dalam C atau C++

  1. Batalkan panggilan sistem dengan ptrace()

  2. Fork panggilan sistem () dan fungsi execv

  3. Memeriksa apakah errno !=EINTR:apa artinya?

  1. Panggilan sistem Linux tercepat

  2. Bagaimana cara meneruskan parameter ke panggilan sistem Linux?

  3. Mengapa nomor panggilan sistem Linux di x86 dan x86_64 berbeda?