Memodifikasi biner selama eksekusi

10

Saya sering menemukan situasi ketika mengembangkan, di mana saya menjalankan file biner, katakan a.outdi latar belakang karena melakukan pekerjaan yang panjang. Sementara melakukan itu, saya membuat perubahan pada kode C yang diproduksi a.outdan dikompilasi a.outlagi. Sejauh ini, saya tidak punya masalah dengan ini. Proses yang berjalan a.outberlanjut seperti biasa, tidak pernah macet, dan selalu menjalankan kode lama dari mana awalnya dimulai.

Namun, katakanlah a.outitu file yang sangat besar, mungkin sebanding dengan ukuran RAM. Apa yang akan terjadi dalam kasus ini? Dan katakan itu tertaut ke file objek bersama libblas.so,, bagaimana jika saya memodifikasi libblas.soselama runtime? Apa yang akan terjadi?

Pertanyaan utama saya adalah - apakah OS menjamin bahwa ketika saya menjalankan a.out, maka kode asli akan selalu berjalan normal, sesuai dengan biner asli , terlepas dari ukuran biner atau file yang ditautkan .so, bahkan ketika file .odan .sofile tersebut dimodifikasi selama runtime?

Saya tahu ada pertanyaan-pertanyaan ini yang membahas masalah serupa: /programming/8506865/when-a-binary-file-runs-does-it-copy-its-entire-binary-data-into-memory -at-once Apa yang terjadi jika Anda mengedit skrip selama eksekusi? Bagaimana mungkin melakukan pembaruan langsung saat program sedang berjalan?

Yang telah membantu saya memahami sedikit lebih banyak tentang ini tetapi saya tidak berpikir bahwa mereka menanyakan apa yang saya inginkan, yang merupakan aturan umum untuk konsekuensi dari memodifikasi biner selama eksekusi

texasflood
sumber
Bagi saya, pertanyaan yang Anda tautkan (terutama Stack Overflow) sudah memberikan bantuan yang signifikan dalam memahami konsekuensi ini (atau tidak adanya hal tersebut). Karena kernel memuat program Anda ke dalam wilayah / segmen teks memori , ia seharusnya tidak terpengaruh oleh perubahan yang dilakukan melalui subsistem file.
John WH Smith
@JohnWHSmith Pada Stackoverflow, jawaban teratas mengatakan if they are read-only copies of something already on disc (like an executable, or a shared object file), they just get de-allocated and are reloaded from their source, jadi saya mendapat kesan bahwa jika biner Anda besar, maka jika bagian dari biner Anda kehabisan RAM, tetapi kemudian diperlukan lagi itu adalah "reloaded from source" - jadi setiap perubahan dalam yang .(s)ofile akan tercermin selama eksekusi. Tapi tentu saja saya mungkin salah paham - itulah sebabnya saya mengajukan pertanyaan yang lebih spesifik ini
texasflood
@ JohnWHSmith Juga jawaban kedua mengatakan No, it only loads the necessary pages into memory. This is demand paging.Jadi saya sebenarnya mendapat kesan bahwa apa yang saya minta tidak dapat dijamin.
texasflood

Jawaban:

11

Sementara pertanyaan Stack Overflow tampaknya cukup pada awalnya, saya mengerti, dari komentar Anda, mengapa Anda mungkin masih ragu tentang ini. Bagi saya, ini persis seperti situasi kritis yang terlibat ketika dua subsistem UNIX (proses dan file) berkomunikasi.

Seperti yang Anda ketahui, sistem UNIX biasanya dibagi menjadi dua subsistem: subsistem file, dan subsistem proses. Sekarang, kecuali diinstruksikan sebaliknya melalui panggilan sistem, kernel seharusnya tidak memiliki dua subsistem ini berinteraksi satu sama lain. Namun ada satu pengecualian: memuat file yang dapat dieksekusi ke dalam wilayah teks proses . Tentu saja, orang mungkin berpendapat bahwa operasi ini juga dipicu oleh pemanggilan sistem ( execve), tetapi ini biasanya dikenal sebagai satu - satunya kasus di mana subsistem proses membuat permintaan implisit ke subsistem file.

Karena subsistem proses secara alami tidak memiliki cara untuk menangani file (jika tidak maka tidak ada gunanya membagi semuanya menjadi dua), ia harus menggunakan apa pun yang disediakan oleh subsistem file untuk mengakses file. Ini juga berarti bahwa subsistem proses dikirimkan ke ukuran apa pun yang diambil subsistem file terkait edisi file / penghapusan. Pada titik ini, saya akan merekomendasikan membaca jawaban Gilles untuk pertanyaan U&L ini . Sisa jawaban saya didasarkan pada yang lebih umum dari Gilles ini.

Hal pertama yang harus diperhatikan adalah bahwa secara internal, file hanya dapat diakses melalui inode . Jika kernel diberikan path, langkah pertama adalah menerjemahkannya menjadi inode yang akan digunakan untuk semua operasi lainnya. Ketika suatu proses memuat sebuah executable ke dalam memori, ia melakukannya melalui inode-nya, yang telah disediakan oleh subsistem file setelah terjemahan suatu path. Inode dapat dikaitkan dengan beberapa jalur (tautan), dan program hanya dapat menghapus tautan. Untuk menghapus file dan inode-nya, userland harus menghapus semua tautan yang ada ke inode itu, dan memastikan bahwa itu benar-benar tidak digunakan. Ketika kondisi ini terpenuhi, kernel akan secara otomatis menghapus file dari disk.

Jika Anda melihat bagian pengganti yang dapat dieksekusi dari jawaban Gilles, Anda akan melihat bahwa tergantung pada bagaimana Anda mengedit / menghapus file, kernel akan bereaksi / beradaptasi secara berbeda, selalu melalui mekanisme yang diterapkan dalam subsistem file.

  • Jika Anda mencoba strategi satu ( buka / truncate ke nol / tulis atau buka / tulis / truncate ke ukuran baru ), Anda akan melihat bahwa kernel tidak akan repot menangani permintaan Anda. Anda akan mendapatkan kesalahan 26: File teks sibuk ( ETXTBSY). Tidak ada konsekuensi apa pun.
  • Jika Anda mencoba strategi dua, langkah pertama adalah menghapus executable Anda. Namun, karena sedang digunakan oleh suatu proses, subsistem file akan menendang dan mencegah file (dan inode) dari yang benar - benar dihapus dari disk. Dari titik ini, satu-satunya cara untuk mengakses konten file lama adalah dengan melakukannya melalui inode-nya, yang merupakan apa yang dilakukan subsistem proses setiap kali perlu memuat data baru ke dalam bagian teks (secara internal, tidak ada gunanya menggunakan jalur, kecuali saat menerjemahkannya ke dalam inode). Meskipun Anda telah memutuskan tautanfile (menghapus semua jalurnya), proses masih dapat menggunakannya seolah-olah Anda tidak melakukan apa-apa. Membuat file baru dengan path lama tidak akan mengubah apa pun: file baru akan diberikan inode yang sama sekali baru, yang tidak diketahui oleh proses yang sedang berjalan.

Strategi 2 dan 3 juga aman untuk executable: walaupun menjalankan executable (dan perpustakaan yang dimuat secara dinamis) tidak membuka file dalam arti memiliki deskriptor file, mereka berperilaku dengan cara yang sangat mirip. Selama beberapa program menjalankan kode, file tetap di disk bahkan tanpa entri direktori.

  • Strategi tiga sangat mirip karena mvoperasi adalah satu atom. Ini mungkin akan memerlukan penggunaan renamepanggilan sistem, dan karena proses tidak dapat diinterupsi saat dalam mode kernel, tidak ada yang dapat mengganggu operasi ini sampai selesai (berhasil atau tidak). Sekali lagi, tidak ada perubahan inode file lama: yang baru dibuat, dan proses yang sudah berjalan tidak akan mengetahuinya, bahkan jika dikaitkan dengan salah satu tautan inode lama.

Dengan strategi 3, langkah memindahkan file baru ke nama yang ada menghapus entri direktori yang mengarah ke konten lama dan membuat entri direktori yang mengarah ke konten baru. Ini dilakukan dalam satu operasi atom, jadi strategi ini memiliki keuntungan besar: jika suatu proses membuka file kapan saja, ia akan melihat konten lama atau konten baru - tidak ada risiko mendapatkan konten campuran atau file tidak ada.

Mengkompilasi ulang file : ketika menggunakan gcc(dan perilaku ini mungkin serupa untuk banyak kompiler lain), Anda menggunakan strategi 2. Anda dapat melihat bahwa dengan menjalankan straceproses kompiler Anda:

stat("a.out", {st_mode=S_IFREG|0750, st_size=8511, ...}) = 0
unlink("a.out") = 0
open("a.out", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
chmod("a.out", 0750) = 0
  • Compiler mendeteksi bahwa file sudah ada melalui statdan lstatpanggilan sistem.
  • File tidak terhubung . Di sini, sementara itu tidak lagi dapat diakses melalui nama a.out, inode dan isinya tetap pada disk, selama mereka digunakan oleh proses yang sudah berjalan.
  • File baru dibuat dan dapat dieksekusi di bawah nama a.out. Ini adalah inode baru, dan konten baru, yang proses yang sudah berjalan tidak peduli.

Sekarang, ketika datang ke perpustakaan bersama, perilaku yang sama akan berlaku. Selama objek perpustakaan digunakan oleh suatu proses, itu tidak akan dihapus dari disk, tidak peduli bagaimana Anda mengubah tautannya. Kapan pun sesuatu harus dimuat ke dalam memori, kernel akan melakukannya melalui inode file, dan karena itu akan mengabaikan perubahan yang Anda buat pada tautannya (seperti mengaitkannya dengan file baru).

John WH Smith
sumber
Fantastis, jawaban terinci. Itu menjelaskan kebingungan saya. Jadi saya benar dalam mengasumsikan bahwa karena inode masih tersedia, data dari file biner asli masih pada disk dan menggunakan dfuntuk menghitung jumlah byte gratis pada disk salah karena tidak mengambil inode yang sudahkah semua tautan sistem file dihapus diperhitungkan? Jadi saya harus menggunakan df -i? (Ini hanya keingintahuan teknis, saya tidak benar-benar perlu tahu persis penggunaan disk!)
texasflood
1
Hanya untuk mengklarifikasi bagi pembaca masa depan - kebingungan saya adalah bahwa saya berpikir tentang eksekusi, seluruh biner akan dimuat ke RAM, jadi jika RAM kecil, maka bagian dari biner akan meninggalkan RAM dan harus dimuat ulang dari disk - yang akan menyebabkan masalah jika Anda mengubah file. Tetapi jawabannya telah memperjelas bahwa biner tidak pernah benar-benar dihapus dari disk bahkan jika Anda rmatau mvsebagai inode ke file asli tidak dihapus sampai semua proses menghapus tautan mereka ke inode itu.
texasflood
@texasflood Tepat. Setelah semua jalur telah dihapus, tidak ada proses baru ( dftermasuk) yang bisa mendapatkan informasi tentang inode. Apa pun informasi baru yang Anda temukan terkait dengan file baru, dan inode baru. Poin utama di sini adalah bahwa subsistem proses tidak tertarik pada masalah ini, sehingga pengertian manajemen memori (paging permintaan, swapping proses, kesalahan halaman, ...) sama sekali tidak relevan. Ini adalah masalah subsistem file, dan ditangani oleh subsistem file. Subsistem proses tidak repot dengan itu, bukan itu yang ada di sini.
John WH Smith
@texasflood Catatan tentang df -i: alat ini mungkin mengambil informasi dari superblok fs ', atau cache-nya, yang berarti bahwa ia mungkin menyertakan inode biner lama (yang semua tautannya telah dihapus). Ini tidak berarti bahwa proses baru bebas menggunakan data lama itu.
John WH Smith
2

Pemahaman saya adalah bahwa karena pemetaan memori dari proses yang sedang berjalan, kernel tidak akan memperbolehkan memperbarui sebagian dari file yang dipetakan. Saya kira seandainya suatu proses sedang berjalan maka semua file disediakan maka memperbarui itu karena Anda mengkompilasi versi baru dari sumber Anda benar-benar menghasilkan pembuatan set inode baru. Singkatnya, versi yang lebih lama dari executable Anda tetap dapat diakses pada disk melalui peristiwa kesalahan halaman. Jadi, bahkan jika Anda memperbarui file besar, itu harus tetap dapat diakses dan kernel masih harus melihat versi yang tidak tersentuh selama proses berjalan. Inode file asli tidak boleh digunakan kembali selama proses berjalan.

Ini tentu saja harus dikonfirmasi.


sumber
2

Ini tidak selalu terjadi ketika mengganti file .jar. Sumberdaya jar dan beberapa loader kelas refleksi runtime tidak dibaca dari disk sampai program secara eksplisit meminta informasi.

Ini hanya masalah karena toples hanyalah arsip daripada satu executable yang dipetakan ke dalam memori. Ini sedikit off-stopic tetapi masih merupakan bagian dari pertanyaan Anda dan sesuatu yang telah saya bidik sendiri.

Jadi untuk executable: ya. Untuk file jar: mungkin (tergantung pada implementasi).

Zhro
sumber