Menghubungkan libstdc ++ secara statis: ada gotchas?

90

Saya perlu menerapkan aplikasi C ++ yang dibangun di Ubuntu 12.10 dengan libstdc ++ GCC 4.7 ke sistem yang menjalankan Ubuntu 10.04, yang hadir dengan versi libstdc ++ yang jauh lebih lama.

Saat ini, saya sedang mengkompilasi -static-libstdc++ -static-libgcc, seperti yang disarankan oleh posting blog ini: Menghubungkan libstdc ++ secara statis . Penulis memperingatkan agar tidak menggunakan kode C ++ yang dimuat secara dinamis saat mengompilasi libstdc ++ secara statis, yang merupakan sesuatu yang belum saya periksa. Namun, semuanya tampaknya berjalan lancar sejauh ini: Saya dapat menggunakan fitur C ++ 11 di Ubuntu 10.04, yang saya cari.

Saya perhatikan bahwa artikel ini berasal dari tahun 2005, dan mungkin banyak yang berubah sejak saat itu. Apakah nasihatnya masih berlaku? Apakah ada masalah tersembunyi yang harus saya waspadai?

Nick Hutchinson
sumber
Tidak, menautkan secara statis ke libstdc ++ tidak berarti demikian. Jika itu menyiratkan bahwa maka tidak akan ada gunanya -static-libstdc++opsi, Anda hanya akan menggunakan-static
Jonathan Wakely
@JonathanWakely -static akan mendapatkan kernel too oldkesalahan di beberapa sistem ubuntu 1404. Glibc.so seperti kernel32.dlldi jendela, ini adalah bagian dari antarmuka sistem operasi, kita tidak boleh menyematkannya di biner kita. Anda dapat menggunakan objdump -T [binary path]untuk melihatnya dimuat secara dinamis libstdc++.soatau tidak. Untuk programer golang, Anda dapat menambahkan #cgo linux LDFLAGS: -static-libstdc++ -static-libgccsebelum mengimpor "C"
pria perunggu
@bronzeman, tetapi yang kita bicarakan -static-libstdc++tidak -staticbegitu libc.sotidak akan ditautkan secara statis.
Jonathan Wakely
1
@NickHutchinson posting blog yang ditautkan ke hilang. Pertanyaan SO ini adalah pencarian populer untuk istilah yang relevan di sini. Dapatkah Anda mereproduksi info penting dari entri blog tersebut dalam pertanyaan Anda, atau menawarkan tautan baru jika Anda tahu ke mana pos itu dipindahkan?
Brian Cain
1
@BrianCain Arsip internet memilikinya: web.archive.org/web/20160313071116/http://www.trilithium.com/…
Rob Keniger

Jawaban:

135

Posting blog itu sangat tidak akurat.

Sejauh yang saya tahu, perubahan C ++ ABI telah diperkenalkan dengan setiap rilis utama GCC (yaitu yang memiliki komponen nomor versi pertama atau kedua yang berbeda).

Tidak benar. Satu-satunya perubahan C ++ ABI yang diperkenalkan sejak GCC 3.4 telah kompatibel dengan versi sebelumnya, yang berarti C ++ ABI telah stabil selama hampir sembilan tahun.

Lebih buruk lagi, sebagian besar distribusi Linux utama menggunakan cuplikan GCC dan / atau menambal versi GCC mereka, sehingga hampir tidak mungkin untuk mengetahui dengan tepat versi GCC apa yang mungkin Anda hadapi saat mendistribusikan biner.

Perbedaan antara versi GCC yang ditambal dari distribusi kecil, dan bukan ABI yang berubah, misalnya Fedora 4.6.3 20120306 (Red Hat 4.6.3-2) adalah ABI yang kompatibel dengan rilis FSF 4.6.x upstream dan hampir pasti dengan semua 4.6. x dari distro lain.

Pada pustaka runtime GNU / Linux GCC menggunakan versi simbol ELF sehingga mudah untuk memeriksa versi simbol yang dibutuhkan oleh objek dan pustaka, dan jika Anda memiliki libstdc++.soyang menyediakan simbol-simbol itu, itu akan berfungsi, tidak masalah jika itu versi tambalan yang sedikit berbeda dari versi lain distro Anda.

tetapi tidak ada kode C ++ (atau kode apa pun yang menggunakan dukungan runtime C ++) yang dapat ditautkan secara dinamis jika ini ingin bekerja.

Ini juga tidak benar.

Meskipun demikian, menautkan ke secara statis libstdc++.aadalah salah satu opsi untuk Anda.

Alasan mengapa ini mungkin tidak berfungsi jika Anda secara dinamis memuat pustaka (menggunakan dlopen) adalah karena simbol libstdc ++ yang bergantung padanya mungkin tidak diperlukan oleh aplikasi Anda saat Anda (secara statis) menautkannya, jadi simbol itu tidak akan ada dalam file yang dapat dieksekusi. Itu dapat diselesaikan dengan secara dinamis menautkan pustaka bersama ke libstdc++.so(yang merupakan hal yang benar untuk dilakukan jika bergantung padanya.) Interposisi simbol ELF berarti simbol yang ada dalam eksekusi Anda akan digunakan oleh pustaka bersama, tetapi yang lain tidak hadir di executable Anda akan ditemukan di mana pun libstdc++.soia tertaut. Jika aplikasi Anda tidak menggunakan dlopenAnda tidak perlu peduli tentang itu.

Opsi lain (dan yang saya sukai) adalah menerapkan yang lebih baru di libstdc++.sosamping aplikasi Anda dan memastikannya ditemukan sebelum sistem default libstdc++.so, yang dapat dilakukan dengan memaksa penaut dinamis untuk mencari di tempat yang tepat, baik menggunakan $LD_LIBRARY_PATHvariabel lingkungan saat dijalankan- waktu, atau dengan mengatur RPATHdi eksekusi pada link-time. Saya lebih suka menggunakan RPATHkarena tidak bergantung pada lingkungan yang disetel dengan benar agar aplikasi berfungsi. Jika Anda menghubungkan aplikasi Anda dengan '-Wl,-rpath,$ORIGIN'(perhatikan tanda kutip tunggal untuk mencegah shell mencoba untuk memperluas $ORIGIN) maka eksekusi akan memiliki RPATHdari $ORIGINyang menceritakan linker dinamis untuk mencari shared library di direktori yang sama dengan eksekusi itu sendiri. Jika Anda menempatkan yang lebih barulibstdc++.sodalam direktori yang sama dengan yang dapat dieksekusi, itu akan ditemukan pada waktu proses, masalah terpecahkan. (Pilihan lain adalah memasukkan file yang dapat dieksekusi /some/path/bin/dan libstdc ++ yang lebih baru. Jadi /some/path/lib/dan menautkan dengan '-Wl,-rpath,$ORIGIN/../lib'atau lokasi tetap lainnya yang relatif terhadap yang dapat dieksekusi, dan mengatur RPATH relatif ke $ORIGIN)

Jonathan Wakely
sumber
8
Penjelasan ini, khususnya tentang RPATH, sangat mulia.
nilweed
3
Mengirim libstdc ++ dengan aplikasi Anda di Linux adalah saran yang buruk. Google untuk "steam libstdc ++" untuk melihat semua drama yang dibawanya. Singkatnya, jika beban exe Anda libs eksternal (seperti, opengl) yang ingin dlopen libstdc ++ lagi (seperti, driver radeon), mereka libs akan menggunakan Anda libstdc ++ karena sudah dimuat, bukan mereka sendiri, yang adalah apa yang mereka perlu dan mengharapkan. Jadi Anda kembali ke titik awal.
7
@cap, OP secara khusus menanyakan tentang penerapan ke distro yang sistemnya libstdc ++ lebih lama. Masalah Steam adalah bahwa mereka membundel libstdc ++. Jadi itu lebih tua dari sistem (mungkin itu lebih baru pada saat mereka membundelnya, tetapi distro pindah ke yang lebih baru). Itu dapat diatasi dengan mengarahkan RPATH ke direktori yang berisi libstdc++.so.6symlink yang diatur pada waktu instalasi untuk menunjuk ke lib yang dibundel atau ke sistem jika lebih baru. Ada model hubungan campuran yang lebih rumit, seperti yang digunakan oleh Red Hat DTS, tetapi sulit dilakukan sendiri.
Jonathan Wakely
5
hai man, saya minta maaf jika saya tidak ingin model saya untuk pengiriman biner backwards-compat menyertakan "mempercayai orang lain untuk menyimpan libstdc ++ ABI compat" atau "secara bersyarat menautkan libstdc ++ saat runtime" ... jika hal itu mengganggu beberapa masalah di sini dan di sana, apa yang bisa saya lakukan, maksud saya tidak ada rasa tidak hormat. Dan jika Anda ingat drama memcpy@GLIBC_2.14, Anda tidak bisa benar-benar menyalahkan saya karena memiliki masalah kepercayaan dengan ini :)
6
Saya harus menggunakan '-Wl, -rpath, $ ORIGIN' (perhatikan '-' di depan rpath). Saya tidak dapat mengedit jawaban karena hasil edit minimal harus 6 karakter ....
user368507
11

Satu tambahan untuk jawaban luar biasa Jonathan Wakely, mengapa dlopen () bermasalah:

Karena kumpulan penanganan pengecualian baru di GCC 5 (lihat PR 64535 dan PR 65434 ), jika Anda membuka dan menutup pustaka yang secara statis ditautkan ke libstdc ++, Anda akan mendapatkan kebocoran memori (dari objek kumpulan) setiap saat. Jadi, jika ada kemungkinan Anda pernah menggunakan dlopen, sepertinya ide yang buruk untuk menautkan libstdc ++ secara statis. Perhatikan bahwa ini adalah kebocoran nyata dibandingkan dengan kebocoran jinak yang disebutkan dalam PR 65434 .

Emil Styrke
sumber
1
Fungsi tersebut __gnu_cxx::__freeres()tampaknya menyediakan setidaknya beberapa bantuan untuk masalah ini, karena ini membebaskan buffer internal objek kumpulan. Tapi bagi saya agak tidak jelas implikasi mana yang dimiliki panggilan ke fungsi ini sehubungan dengan pengecualian yang secara tidak sengaja dilemparkan setelahnya.
phlipsy
3

Tambahan untuk jawaban Jonathan Wakely tentang RPATH:

RPATH hanya akan berfungsi jika RPATH yang dimaksud adalah RPATH dari aplikasi yang sedang berjalan . Jika Anda memiliki perpustakaan yang secara dinamis menautkan ke perpustakaan mana pun melalui RPATH-nya sendiri, RPATH perpustakaan akan ditimpa oleh RPATH dari aplikasi yang memuatnya. Ini adalah masalah ketika Anda tidak dapat menjamin bahwa RPATH aplikasi sama dengan perpustakaan Anda, misalnya jika Anda mengharapkan dependensi Anda berada di direktori tertentu, tetapi direktori itu bukan bagian dari RPATH aplikasi.

Misalnya, Anda memiliki aplikasi App.exe yang memiliki dependensi yang ditautkan secara dinamis di libstdc ++. So.x untuk GCC 4.9. App.exe memiliki ketergantungan ini diselesaikan melalui RPATH, yaitu

App.exe (RPATH=.:./gcc4_9/libstdc++.so.x)

Sekarang katakanlah ada library lain Dependency.so, yang memiliki dependensi yang ditautkan secara dinamis pada libstdc ++. So.y untuk GCC 5.5. Ketergantungan di sini diselesaikan melalui RPATH perpustakaan, yaitu

Dependency.so (RPATH=.:./gcc5_5/libstdc++.so.y)

Ketika App.exe memuat Dependency.so, itu tidak menambahkan atau menambahkan RPATH perpustakaan . Itu tidak berkonsultasi sama sekali. Satu-satunya RPATH yang dipertimbangkan akan menjadi aplikasi yang berjalan, atau App.exe dalam contoh ini. Artinya, jika pustaka mengandalkan simbol yang ada di gcc5_5 / libstdc ++. So.y tetapi tidak di gcc4_9 / libstdc ++. So.x, pustaka tersebut akan gagal dimuat.

Ini hanya sebagai peringatan, karena saya sendiri pernah mengalami masalah ini di masa lalu. RPATH adalah alat yang sangat berguna tetapi implementasinya masih memiliki beberapa kendala.

Jonathan McDevitt
sumber
jadi RPATH untuk perpustakaan bersama tidak ada gunanya! Dan saya berharap, bahwa mereka sedikit meningkatkan Linux dalam hal ini dalam 2 dekade terakhir ...
Frank Puck
2

Anda mungkin juga perlu memastikan bahwa Anda tidak bergantung pada glibc dinamis. Jalankan lddpada hasil yang dapat dieksekusi dan catat setiap dependensi dinamis (libc / libm / libpthread adalah tersangka yang digunakan).

Latihan tambahan akan membangun banyak contoh C ++ 11 yang terlibat menggunakan metodologi ini dan benar-benar mencoba biner yang dihasilkan pada sistem 10,04 nyata. Dalam kebanyakan kasus, kecuali Anda melakukan sesuatu yang aneh dengan pemuatan dinamis, Anda akan langsung tahu apakah program itu bekerja atau macet.

Alexander L. Belikoff
sumber
1
Apa masalahnya tergantung pada glibc dinamis?
Nick Hutchinson
Saya percaya setidaknya beberapa waktu yang lalu libstdc ++ menyiratkan ketergantungan pada glibc. Tidak yakin di mana keadaannya hari ini.
Alexander L. Belikoff
9
libstdc ++ memang bergantung pada glibc (mis. iostreams diimplementasikan dalam istilah printf) tetapi selama glibc di Ubuntu 10.04 menyediakan semua fitur yang dibutuhkan oleh libstdc ++ yang lebih baru tidak ada masalah dengan bergantung pada glibc dinamis, sebenarnya sangat disarankan untuk tidak pernah menautkan secara statis ke glibc
Jonathan Wakely
1

Saya ingin menambahkan jawaban Jonathan Wakely berikut ini.

Bermain-main -static-libstdc++di linux, saya menghadapi masalah dlclose(). Misalkan kita memiliki aplikasi 'A' yang ditautkan secara statis libstdc++dan dimuat secara dinamis ke libstdc++plugin 'P' saat runtime. Tidak apa-apa. Tetapi ketika 'A' membongkar 'P', terjadi kesalahan segmentasi. Asumsi saya adalah setelah bongkar libstdc++.so, 'A' tidak bisa lagi menggunakan simbol yang berhubungan dengan libstdc++. Perhatikan bahwa jika 'A' dan 'P' ditautkan secara statis libstdc++, atau jika 'A' ditautkan secara dinamis dan 'P' secara statis, masalah tidak terjadi.

Ringkasan: jika aplikasi Anda memuat / mengeluarkan plugin yang mungkin ditautkan secara dinamis libstdc++, aplikasi tersebut juga harus ditautkan secara dinamis. Ini hanya pengamatan saya dan saya ingin mendapatkan komentar Anda.

Fedorov7890
sumber
1
Ini mungkin mirip dengan menggabungkan implementasi libc (katakanlah menautkan secara dinamis ke plugin yang secara dinamis menautkan glibc, sedangkan aplikasi itu sendiri secara statis ditautkan ke musl-libc). Rich Felker, penulis musl-libc, mengklaim bahwa masalah dalam skenario seperti itu adalah bahwa manajemen memori glibc (menggunakan sbrk) membuat asumsi tertentu dan cukup banyak berharap untuk sendirian dalam satu proses ... tidak yakin apakah ini terbatas pada a versi glibc tertentu atau apa pun.
0xC0000022L
dan orang-orang masih belum melihat keuntungan dari antarmuka heap windows, yang mampu menangani banyak salinan independen libc ++ / libc dalam satu proses. Orang seperti itu seharusnya tidak merancang perangkat lunak.
Frank Puck
@FrankPuck memiliki pengalaman Windows dan Linux yang layak. Saya dapat memberitahu Anda bahwa cara "Windows" melakukannya tidak akan membantu Anda ketika MSVC adalah pihak yang memutuskan pengalokasi apa yang akan digunakan dan bagaimana. Keuntungan utama yang saya lihat dengan heaps di Windows adalah Anda dapat membagikan potongan-potongan dan kemudian membebaskannya dalam satu gerakan. Tetapi dengan MSVC Anda masih akan mengalami cukup banyak masalah yang dijelaskan di atas, misalnya ketika melewatkan pointer yang dialokasikan oleh runtime VC lain (rilis vs. debug atau terhubung secara statis vs. dinamis). Jadi "Windows" tidak kebal. Perawatan harus diambil pada kedua sistem.
0xC0000022L