Demo
Penting: Mulai dari MacOS 10.14 Pendekatan yang digunakan di perpustakaan tidak berfungsi lagi, sepertinya Apple telah membatasi beberapa rutinitas tingkat rendah. Di Linux setidaknya di Ubuntu 20.04 masih berfungsi dengan baik.
Jet-Live adalah perpustakaan untuk C ++ "Hot Code Reloading". Ini bekerja pada Linux dan MacOS modern (10.12+ saya kira) pada sistem 64 bit yang ditenagai oleh CPU dengan set instruksi x86-64. Terlepas dari pemuatan ulang fungsi, ia dapat menjaga statis aplikasi dan global aplikasi tidak berubah setelah kode dimuat ulang (silakan merujuk pada "cara kerjanya" untuk apa itu dan mengapa itu penting). Diuji pada Ubuntu 18.04 dengan dentang 6.0.1/7.0.1, LLD-7, GCC 6.4.0/7.3.0, GNU LD 2.30, CMAKE 3.10.2, NINJA 1.8.2, Buat 4.1 dan MacOS 10.13.6 dengan Xcode 8.3.3, CMake 3.8.2, Make 3.81.
PENTING: Perpustakaan ini tidak memaksa Anda untuk mengatur kode Anda dengan cara khusus (seperti di RCCPP atau CR), Anda tidak perlu memisahkan kode yang dapat dimuat kembali menjadi beberapa perpustakaan bersama, Jet-Live harus bekerja dengan proyek apa pun dengan cara yang paling tidak mengganggu.
Jika Anda memerlukan sesuatu yang serupa untuk Windows, coba Blink, saya tidak punya rencana untuk mendukung Windows.
Anda memerlukan kompiler yang sesuai dengan c++11 . Juga ada beberapa ketergantungan yang dibundel, kebanyakan dari mereka hanya header atau perpustakaan pasangan H/CPP tunggal. Silakan merujuk ke direktori lib untuk detailnya.
Perpustakaan ini paling cocok untuk proyek berdasarkan sistem CMake dan Make atau Ninja Build, default disesuaikan untuk alat-alat ini. Opsi cmakelists.txt akan menambahkan set(CMAKE_EXPORT_COMPILE_COMMANDS ON) untuk compile_commands.json dan ubah bendera kompiler dan tautan. Ini penting dan tidak bisa dihindari. Untuk detailnya silakan lihat cmakelists.txt. Jika Anda menggunakan ninja, tambahkan -d keepdepfile Ninja Flag saat menjalankan Ninja, ini diperlukan untuk melacak dependensi antara file sumber dan header
include ( path /to/jet-live/cmake/jet_live_setup.cmake) # setup needed compiler and linker flags, include this file in your root CMakeLists.txt
set (JET_LIVE_BUILD_EXAMPLE OFF )
set (JET_LIVE_SHARED ON ) # if you want to
add_subdirectory ( path /to/jet-live)
target_link_libraries (your-app- target jet-live)jet::Live classliveInstance->update()liveInstance->tryReload()Penting: Perpustakaan ini tidak aman. Ini menggunakan utas di bawah kap untuk menjalankan kompiler, tetapi Anda harus memanggil semua metode perpustakaan dari utas yang sama.
Saya juga menggunakan pustaka ini hanya dengan debug builds ( -O0 , tidak dilucuti, tanpa -fvisibility=hidden dan hal -hal seperti itu) untuk tidak berurusan dengan fungsi dan variabel yang dioptimalkan dan dilapisi. Saya tidak tahu cara kerjanya pada build stripped yang sangat dioptimalkan, kemungkinan besar itu tidak akan berhasil sama sekali.
Secara pribadi saya menggunakannya seperti ini. Saya memiliki pintasan Ctrl+r yang ditugaskan oleh tryReload dalam aplikasi saya. Juga aplikasi aplikasi aplikasi saya di Runloop utama dan mendengarkan acara onCodePreLoad update onCodePostLoad untuk membuat ulang beberapa objek atau mengevaluasi kembali beberapa fungsi:
Ctrl+r Jet-Live akan memantau perubahan file, mengkompilasi ulang file yang diubah dan hanya ketika tryReload akan menunggu semua proses kompilasi saat ini menyelesaikan dan memuat ulang kode baru. Tolong jangan hubungi tryReload pada setiap pembaruan, itu tidak akan berfungsi seperti yang Anda harapkan, sebut saja ketika kode sumber Anda siap untuk dimuat ulang.
Jika Anda tidak ingin beralih bolak -balik antara editor kode dan aplikasi Anda, Anda dapat mengonfigurasi pintasan keyboard yang menjalankan perintah shell kill -s USR1 $(pgrep <your_app_name>) , pustaka akan memicu ulang kode saat sinyal SIGUSR1 diterima. Ini bekerja setidaknya di Emacs, Xcode, Clion dan Vscode, tapi saya yakin itu dapat dicapai dalam editor dan IDE lain, hanya Google It. Jika debugger Anda adalah LLDB dan menangkap sinyal ini dan menghentikan aplikasi, tambahkan perintah ini ke file ~/.lldbinit :
breakpoint set --name main
breakpoint command add
process handle -n true -p true -s false SIGUSR1
continue
DONE
Pada macOS Anda dapat menggunakan generator cmake -G Xcode selain dari Make dan Ninja. Dalam hal ini, silakan instal xcpretty Gem:
gem install xcpretty
Ada aplikasi contoh sederhana, cukup jalankan:
git clone https://github.com/ddovod/jet-live.git && cd jet-live
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Debug .. && make
./example/example dan coba perintah hello . Jangan lupa untuk menjalankan perintah reload setelah memperbaiki fungsi.
Ada rangkaian tes yang tidak terlalu komprehensif, tetapi terus -menerus memperbarui. Untuk menjalankannya:
git clone https://github.com/ddovod/jet-live.git && cd jet-live
mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Debug -DJET_LIVE_BUILD_TESTS=ON .. && make
../tools/tests/test_runner.py -b . -s ../tests/src/Diterapkan:
compile_commands.json setelah file .cpp baru dibuat)Akan dilaksanakan:
Tidak akan diimplementasikan sama sekali:
Jet-Live disesuaikan untuk bekerja dengan alat CMake dan Make/Ninja, tetapi jika Anda ingin mengadopsinya ke alat build lain, ada cara untuk menyesuaikan perilaku 'dalam beberapa aspek. Silakan merujuk ke sumber dan dokumentasi. Juga merupakan ide yang baik untuk membuat pendengar Anda sendiri untuk menerima acara dari perpustakaan. Silakan merujuk ke dokumentasi ILiveListener dan LiveConfig .
PENTING: Sangat disarankan untuk mencatat semua pesan dari perpustakaan menggunakan ILiveListener::onLog untuk melihat apakah ada yang salah.
Perpustakaan membaca header ELF dan bagian dari perpustakaan bersama yang dapat dieksekusi dan semua dimuat ini, menemukan semua simbol dan mencoba mencari tahu mana dari mereka yang dapat dikaitkan (fungsi) atau harus ditransfer/dipindahkan (variabel statis/global). Juga menemukan ukuran simbol dan alamat "nyata".
Selain itu, Jet-Live mencoba menemukan compile_commands.json di dekat Anda yang dapat dieksekusi atau di 'direktori induknya secara rekursif. Menggunakan file ini membedakan:
.o (objek).d (depfile) Ketika semua unit kompilasi diuraikan, itu membedakan direktori yang paling umum untuk semua file sumber dan mulai menonton semua direktori dengan file sumber, dependensi mereka dan beberapa file layanan seperti compile_commands.json .
Selain itu perpustakaan mencoba menemukan semua dependensi untuk setiap unit kompilasi. Secara default akan membaca depfiles di dekat file objek (lihat -MD opsi kompiler). Misalkan file objek terletak di:
/home/coolhazker/projects/some_project/build/main.cpp.o
Jet-Live akan mencoba menemukan Depfile di:
/home/coolhazker/projects/some_project/build/main.cpp.o.d
or
/home/coolhazker/projects/some_project/build/main.cpp.d
Ini akan mengambil semua dependensi yang berada di bawah direktori menonton, jadi hal -hal seperti /usr/include/elf.h tidak akan diperlakukan sebagai ketergantungan bahkan jika file ini benar -benar termasuk dalam beberapa file .cpp Anda.
Sekarang perpustakaan diinisialisasi.
Selanjutnya, ketika Anda mengedit beberapa file sumber dan menyimpannya, Jet-Live segera memulai kompilasi semua file dependen di latar belakang. Secara default jumlah proses kompilasi simultan adalah 4, tetapi Anda dapat mengonfigurasinya. Ini akan menulis untuk mencatat tentang keberhasilan dan kesalahan menggunakan ILiveListener::onLog Method of Listener. Jika Anda memicu kompilasi beberapa file saat sudah dikompilasi (atau menunggu dalam antrian), proses kompilasi lama akan dibunuh dan yang baru akan ditambahkan ke antrian, jadi agak aman untuk tidak menunggu kompilasi untuk menyelesaikan dan membuat perubahan baru dari kode. Juga setelah setiap file dikompilasi, ia akan memperbarui dependensi untuk file yang dikompilasi karena kompiler dapat membuat ulang depfile untuk itu jika versi baru unit kompilasi memiliki dependensi baru.
Saat Anda menelepon Live::tryReload , perpustakaan akan menunggu proses kompilasi yang belum selesai dan kemudian semua file objek baru yang terakumulasi akan ditautkan bersama di perpustakaan bersama dan ditempatkan di dekat Anda yang dapat dieksekusi dengan nama lib_reloadXXX.so , di mana XXX adalah sejumlah "Reload" selama sesi ini. Jadi lib_reloadXXX.so berisi semua kode baru.
Jet-live memuat perpustakaan ini menggunakan dlopen , membaca header dan bagian Mach-O Mach-O dan menemukan semua simbol. Juga memuat info relokasi dari file objek yang digunakan untuk membangun perpustakaan baru ini. Setelah itu:
memcpy dari lokasi lama ke yang baru PENTING: ILiveListener::onCodePreLoad ditembakkan tepat sebelum lib_reloadXXX.so dimuat ke dalam memori proses. ILiveListener::onCodePostLoad dipecat tepat setelah semua mesin pemuatan-pemuatan kode selesai.
Anda dapat membaca lebih lanjut tentang pengait fungsi di sini. Perpustakaan ini menggunakan perpustakaan subhook yang mengagumkan untuk mengarahkan aliran fungsi dari yang lama ke yang baru. Anda dapat melihat bahwa pada platform 32 bit fungsi Anda harus setidaknya 5 byte untuk dikeluarkan. Pada 64 bit Anda membutuhkan setidaknya 14 byte yang banyak, dan misalnya fungsi rintisan kosong mungkin tidak akan muat menjadi 14 byte. Dari pengamatan saya, dentang secara default menghasilkan kode dengan penyelarasan fungsi 16-byte. GCC tidak melakukan ini secara default, jadi untuk GCC -falign-functions=16 bendera digunakan. Itu berarti jarak antara dimulai dari 2 fungsi tidak kurang dari 16 byte, yang memungkinkan untuk menghubungkan fungsi apa pun.
Versi fungsi baru harus menggunakan statika dan global yang sudah hidup dalam aplikasi. Mengapa itu penting? Misalkan Anda memiliki (sedikit contoh sintetis, tapi bagaimanapun):
// Singleton.hpp
class Singleton
{
public:
static Singleton& instance ();
};
int veryUsefulFunction ( int value);
// Singleton.cpp
Singleton& Singleton::instance ()
{
static Singleton ins;
return ins;
}
int veryUsefulFunction ( int value)
{
return value * 2 ;
} Maka Anda ingin memperbarui veryUsefulFunction ke Smth seperti ini:
int veryUsefulFunction ( int value)
{
return value * 3 ;
} Hebat, sekarang ini melipatgandakan argumen dengan 3. Tetapi karena seluruh Singleton.cpp akan dimuat ulang dan Singleton::instance akan dikaitkan untuk memanggil versi baru, lib_reloadXXX.so akan berisi variabel statis baru static Singleton ins yang tidak diinisialisasi, dan jika Anda memanggil Singleton::instance() yang tidak diinisialisasi. Itulah mengapa kita perlu memindahkan semua statika dan global ke kode baru dan mentransfer variabel statika penjaga. Sebagian besar relokasi waktu tautan yang terkait dengan statika dan global adalah 32-bit. Jadi jika pustaka bersama dengan kode baru akan dimuat terlalu jauh dalam memori dari aplikasi, tidak mungkin untuk memindahkan variabel dengan cara ini. Untuk menyelesaikan ini, perpustakaan bersama baru ditautkan menggunakan bendera tautan khusus yang memungkinkan kami memuatnya ke lokasi yang telah dihitung sebelumnya dalam memori virtual (lihat -image_base di Apple LD, --image-base di llvm LLD dan -Ttext-segment + -z max-page-size di GNU linker flags).
Juga aplikasi Anda mungkin akan macet jika Anda mencoba mengubah tata letak memori dari tipe data Anda dalam kode yang dapat diisi ulang.
Misalkan Anda memiliki contoh kelas ini yang dialokasikan di suatu tempat di tumpukan atau di tumpukan:
class SomeClass
{
public:
void calledEachUpdate () {
m_someVar1++;
}
private:
int m_someVar1 = 0 ;
}Anda mengeditnya dan sekarang sepertinya:
class SomeClass
{
public:
void calledEachUpdate () {
m_someVar1++;
m_someVar2++;
}
private:
int m_someVar1 = 0 ;
int m_someVar2 = 0 ;
} Setelah kode dimuat ulang, Anda mungkin akan mengamati crash karena objek yang sudah dialokasikan memiliki tata letak data yang berbeda, ia tidak memiliki variabel instance m_someVar2 , tetapi versi baru calledEachUpdate akan mencoba memodifikasinya sebenarnya memodifikasi data acak. Dalam kasus seperti itu, Anda harus menghapus contoh ini dalam panggilan balik onCodePreLoad dan membuatnya kembali dalam panggilan balik onCodePostLoad . Transfer keadaan yang benar terserah Anda. Efek yang sama akan terjadi jika Anda akan mencoba mengubah tata letak struktur data statis. Hal yang sama juga benar untuk kelas polimorfik (vTable) dan lambdas dengan tangkapan (tangkapan disimpan di dalam bidang data Lambdas).
Mit
Untuk lisensi perpustakaan bekas, silakan merujuk ke direktori dan kode sumbernya.