Bagaimana cara melakukan runtime binding berdasarkan kemampuan CPU di linux

Apakah mungkin perpustakaan linux (mis. "libloader.so") memuat perpustakaan lain untuk menyelesaikan simbol eksternal?

Saya punya banyak kode yang dikompilasi secara kondisional agar level SIMD didukung ( SSE2, AVX, AVX2 ). Ini berfungsi dengan baik jika platform build sama dengan platform runtime. Namun hal ini menghambat penggunaan kembali pada generasi prosesor yang berbeda.

Salah satu pemikirannya adalah memiliki executable yang memanggil function tautan ke libloader.so yang tidak secara langsung mengimplementasikan function. Sebaliknya, ia menyelesaikan (mengikat?) simbol itu dari perpustakaan lain yang dimuat, mis. libimpl_sse2.so, libimpl_avx2.so atau seterusnya tergantung cpuflagnya.

Ada ratusan fungsi yang perlu diikat secara dinamis dengan cara ini, sehingga mengubah deklarasi atau kode pemanggilan tidaklah praktis. Tautan program cukup mudah diubah. Variabel lingkungan runtime juga dapat diubah, tetapi saya memilih untuk tidak mengubahnya.

Saya telah membuat executable yang membangun dan memulai dengan simbol eksternal yang belum terselesaikan (UES) melalui flag ld --unresolved-symbols=ignore-all. Namun pemuatan lib impl selanjutnya tidak mengubah nilai fungsi UES dari NULL.


person Mark Borgerding    schedule 08.04.2015    source sumber
comment
Ini mungkin tidak dapat diterapkan pada kasus Anda, tetapi dengan Intel Compiler Anda dapat mencapainya menggunakan keluarga flag -ax* (yaitu jika Anda ingin jalur kode SSE dan AVX tersedia - gunakan -axAVX selama kompilasi).   -  person zam    schedule 12.04.2015
comment
@zam, Terima kasih, tapi -ax tampaknya hanya berlaku untuk optimasi otomatis (yang dihasilkan kompiler) pada intel. Saya memerlukan solusi yang memungkinkan jalur #ifdef dikompilasi secara terpisah. Itu juga harus bekerja dengan gcc.   -  person Mark Borgerding    schedule 12.04.2015


Jawaban (1)


Sunting: Saya kemudian mengetahui bahwa teknik yang dijelaskan di bawah ini hanya akan berhasil dalam keadaan terbatas. Secara khusus, perpustakaan bersama Anda harus berisi fungsi saja, tanpa variabel global apa pun. Jika ada global di dalam perpustakaan tempat Anda ingin mengirim, maka Anda akan mendapatkan error linker dinamis runtime. Ini terjadi karena variabel global dipindahkan sebelum konstruktor perpustakaan bersama dipanggil. Oleh karena itu, linker perlu menyelesaikan referensi tersebut lebih awal, sebelum skema pengiriman yang dijelaskan di sini dapat dijalankan.


Salah satu cara untuk mencapai apa yang Anda inginkan adalah dengan (ab) menggunakan kolom DT_SONAME di header ELF perpustakaan bersama Anda. Ini dapat digunakan untuk mengubah nama file yang dimuat oleh pemuat dinamis (ld-linux-so*) saat runtime untuk mengatasi ketergantungan perpustakaan bersama. Hal ini paling baik dijelaskan dengan sebuah contoh. Katakanlah saya mengkompilasi perpustakaan bersama libtest.so dengan baris perintah berikut:

g++ test.cc -shared -o libtest.so -Wl,-soname,libtest_dispatch.so

Ini akan membuat perpustakaan bersama dengan nama file libtest.so, namun bidang DT_SONAME disetel ke libtest_dispatch.so. Mari kita lihat apa yang terjadi jika kita menghubungkan suatu program dengan program tersebut:

g++ testprog.cc -o test -ltest

Mari kita periksa dependensi perpustakaan runtime untuk biner aplikasi yang dihasilkan test:

> ldd test
linux-vdso.so.1 =>  (0x00007fffcc5fe000)
libtest_dispatch.so => not found
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd1e4a55000)
/lib64/ld-linux-x86-64.so.2 (0x00007fd1e4e4f000)

Perhatikan bahwa alih-alih mencari libtest.so, pemuat dinamis malah ingin memuat libtest_dispatch.so. Anda dapat memanfaatkan ini untuk mengimplementasikan fungsi pengiriman yang Anda inginkan. Inilah cara saya melakukannya:

  • Buat berbagai versi perpustakaan bersama Anda. Saya berasumsi bahwa ada beberapa versi "generik" yang selalu dapat digunakan, dengan versi lain yang dioptimalkan digunakan saat runtime sebagaimana mestinya. Saya akan memberi nama versi generik dengan nama perpustakaan "biasa" libtest.so, dan memberi nama yang lain sesuai pilihan Anda (misalnya libtest_sse2.so, libtest_avx.so, dll.).

  • Saat menautkan versi generik perpustakaan, ganti DT_SONAME-nya dengan yang lain, seperti libtest_dispatch.so.

  • Buat perpustakaan operator bernama libtest_dispatch.so. Ketika operator dimuat saat aplikasi dimulai, ia bertanggung jawab untuk memuat implementasi perpustakaan yang sesuai. Berikut pseudocode implementasi libtest_dispatch.so:

    #include <dlfcn.h>
    #include <stdlib.h>
    
    // the __attribute__ ensures that this function is called when the library is loaded
    __attribute__((constructor)) void init()
    {
        // manually load the appropriate shared library based upon what the CPU supports
        // at runtime
        if (avx_is_available) dlopen("libtest_avx.so", RTLD_NOW | RTLD_GLOBAL);
        else if (sse2_is_available) dlopen("libtest_sse2.so", RTLD_NOW | RTLD_GLOBAL);
        else dlopen("libtest.so", RTLD_NOW | RTLD_GLOBAL);
        // NOTE: this is just an example; you should check the return values from 
        // dlopen() above and handle errors accordingly
    }
    
  • Saat menautkan aplikasi ke perpustakaan Anda, tautkan aplikasi tersebut ke "vanilla" libtest.so, yang DT_SONAME-nya ditimpa untuk menunjuk ke perpustakaan operator. Hal ini membuat pengiriman pada dasarnya transparan bagi pembuat aplikasi mana pun yang menggunakan perpustakaan Anda.

Ini akan berfungsi seperti yang dijelaskan di atas di Linux. Pada Mac OS, perpustakaan bersama memiliki "nama instalasi" yang analog dengan DT_SONAME yang digunakan dalam perpustakaan bersama ELF, sehingga proses yang sangat mirip dengan di atas dapat digunakan sebagai gantinya. Saya tidak yakin apakah hal serupa dapat digunakan di Windows.

Catatan: Ada satu asumsi penting yang dibuat di atas: kompatibilitas ABI antara berbagai implementasi perpustakaan. Artinya, perpustakaan Anda harus dirancang sedemikian rupa sehingga aman untuk ditautkan ke versi paling umum pada waktu penautan saat menggunakan versi yang dioptimalkan (misalnya libtest_avx.so) pada waktu proses.

person Jason R    schedule 10.04.2015
comment
Jawaban yang bagus. Bakat Anda dalam memecahkan masalah hanya diimbangi dengan kemampuan Anda dalam memberikan penjelasan. - person Mark Borgerding; 10.04.2015
comment
Satu catatan: di Mac OS, saat Anda menautkan aplikasi Anda ke libtest.so, Anda perlu memberikan tanda tautan -flat_namespace. Perilaku defaultnya adalah mengharuskan simbol berasal dari perpustakaan yang diharapkan berasal darinya. Dalam hal ini, berarti libtest_dispatch.so akan bertanggung jawab menyediakan semua simbol. Jika Anda mengaktifkan namespace datar, simbol tersebut akan diikat ke pustaka lain yang dimuat oleh petugas operator, yang seperti ditunjukkan dalam halaman manual ld, lebih mirip dengan perilaku pada sistem operasi lain (misalnya Linux). - person Jason R; 10.04.2015