GNU/Linux >> Belajar Linux >  >> Linux

Tanpa akses root, jalankan R dengan BLAS yang disetel saat ditautkan dengan referensi BLAS

mengapa cara saya tidak berhasil

Pertama, pustaka bersama di UNIX dirancang untuk meniru cara kerja pustaka arsip (pustaka arsip ada lebih dulu). Secara khusus itu berarti bahwa jika Anda memiliki libfoo.so dan libbar.so , keduanya mendefinisikan simbol foo , maka perpustakaan mana pun yang dimuat terlebih dahulu adalah yang menang:semua referensi ke foo dari mana saja dalam program (termasuk dari libbar.so ) akan mengikat ke libfoo.so Definisi foo .

Ini meniru apa yang akan terjadi jika Anda menautkan program Anda ke libfoo.a dan libbar.a , di mana kedua pustaka arsip mendefinisikan simbol yang sama foo . Info selengkapnya tentang penautan arsip di sini.

Harus jelas dari atas, bahwa if libblas.so.3 dan libopenblas.so.0 mendefinisikan kumpulan simbol yang sama (yang mereka lakukan ), dan jika libblas.so.3 dimuat ke dalam proses terlebih dahulu, kemudian rutinitas dari libopenblas.so.0 tidak akan tidak pernah dipanggil.

Kedua, Anda telah memutuskan dengan benar sejak R langsung terhubung ke libR.so , dan sejak libR.so langsung terhubung ke libblas.so.3 , dijamin libopenblas.so.0 akan kalah dalam pertempuran.

Namun, Anda keliru memutuskan bahwa Rscript lebih baik, tetapi bukan:Rscript adalah kecil biner (11K di sistem saya; bandingkan dengan 2,4MB untuk libR.so ), dan kira-kira yang dilakukannya hanyalah exec dari R . Ini sepele untuk dilihat di strace keluaran:

strace -e trace=execve /usr/bin/Rscript --default-packages=base --vanilla /dev/null
execve("/usr/bin/Rscript", ["/usr/bin/Rscript", "--default-packages=base", "--vanilla", "/dev/null"], [/* 42 vars */]) = 0
execve("/usr/lib/R/bin/R", ["/usr/lib/R/bin/R", "--slave", "--no-restore", "--vanilla", "--file=/dev/null", "--args"], [/* 43 vars */]) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=89625, si_status=0, si_utime=0, si_stime=0} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=89626, si_status=0, si_utime=0, si_stime=0} ---
execve("/usr/lib/R/bin/exec/R", ["/usr/lib/R/bin/exec/R", "--slave", "--no-restore", "--vanilla", "--file=/dev/null", "--args"], [/* 51 vars */]) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=89630, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

Artinya, saat skrip Anda mulai dijalankan, libblas.so.3 telah dimuat, dan libopenblas.so.0 yang akan dimuat sebagai ketergantungan mmperf.so tidak akan sebenarnya digunakan untuk apa saja.

apakah mungkin untuk membuatnya berfungsi

Mungkin. Saya dapat memikirkan dua kemungkinan solusi:

  1. Berpura-pura bahwa libopenblas.so.0 sebenarnya adalah libblas.so.3
  2. Buat ulang seluruh R paket terhadap libopenblas.so .

Untuk #1, Anda perlu ln -s libopenblas.so.0 libblas.so.3 , kemudian pastikan salinan libblas.so.3 Anda ditemukan sebelum sistem, dengan menyetel LD_LIBRARY_PATH dengan tepat.

Ini tampaknya bekerja untuk saya:

mkdir /tmp/libblas
# pretend that libc.so.6 is really libblas.so.3
cp /lib/x86_64-linux-gnu/libc.so.6 /tmp/libblas/libblas.so.3
LD_LIBRARY_PATH=/tmp/libblas /usr/bin/Rscript /dev/null
Error in dyn.load(file, DLLpath = DLLpath, ...) :
  unable to load shared object '/usr/lib/R/library/stats/libs/stats.so':
  /usr/lib/liblapack.so.3: undefined symbol: cgemv_
During startup - Warning message:
package ‘stats’ in options("defaultPackages") was not found

Perhatikan bagaimana saya mendapat kesalahan ("berpura-pura" libblas.so.3 saya tidak mendefinisikan simbol yang diharapkan darinya, karena ini benar-benar salinan dari libc.so.6 ).

Anda juga dapat mengonfirmasi versi libblas.so.3 yang mana dimuat dengan cara ini:

LD_DEBUG=libs LD_LIBRARY_PATH=/tmp/libblas /usr/bin/Rscript /dev/null |& grep 'libblas\.so\.3'
     91533: find library=libblas.so.3 [0]; searching
     91533:   trying file=/usr/lib/R/lib/libblas.so.3
     91533:   trying file=/usr/lib/x86_64-linux-gnu/libblas.so.3
     91533:   trying file=/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/amd64/server/libblas.so.3
     91533:   trying file=/tmp/libblas/libblas.so.3
     91533: calling init: /tmp/libblas/libblas.so.3

Untuk #2, Anda mengatakan:

Saya tidak memiliki akses root pada mesin yang ingin saya uji, jadi penautan yang sebenarnya ke OpenBLAS tidak mungkin dilakukan.

tapi itu sepertinya argumen palsu:jika Anda bisa membuat libopenblas , tentunya Anda juga dapat membuat versi R Anda sendiri .

Perbarui:

Anda menyebutkan di awal bahwa libblas.so.3 dan libopenblas.so.0 mendefinisikan simbol yang sama, apa artinya ini? Mereka memiliki SONAME yang berbeda, apakah itu tidak cukup untuk membedakan mereka dengan sistem?

Simbol dan SONAME tidak punya apa-apa hubungannya dengan satu sama lain.

Anda dapat melihat simbol dalam output dari readelf -Ws libblas.so.3 dan readelf -Ws libopenblas.so.0 . Simbol yang terkait dengan BLAS , seperti cgemv_ , akan muncul di kedua pustaka.

Kebingungan Anda tentang SONAME mungkin berasal dari Windows. DLL s di Windows didesain sangat berbeda. Khususnya, saat FOO.DLL mengimpor simbol bar dari BAR.DLL , keduanya nama simbol (bar ) dan DLL dari mana simbol itu diimpor (BAR.DLL ) dicatat dalam FOO.DLL tabel impor.

Itu memudahkan untuk memiliki R impor cgemv_ dari BLAS.DLL , sementara MMPERF.DLL mengimpor simbol yang sama dari OPENBLAS.DLL .

Namun, hal itu membuat penempatan pustaka menjadi sulit, dan bekerja sangat berbeda dari cara kerja pustaka arsip (bahkan di Windows).

Pendapat berbeda tentang desain mana yang lebih baik secara keseluruhan, tetapi tidak ada sistem yang kemungkinan besar akan mengubah modelnya.

Ada beberapa cara bagi UNIX untuk meniru pengikatan simbol gaya Windows:lihat RTLD_DEEPBIND di halaman manual dlopen. Hati-hati:ini penuh dengan bahaya, cenderung membingungkan pakar UNIX, tidak digunakan secara luas, dan kemungkinan memiliki bug implementasi.

Pembaruan 2:

maksud Anda saya mengkompilasi R dan menginstalnya di bawah direktori home saya?

Ya.

Lalu ketika saya ingin menjalankannya, saya harus secara eksplisit memberikan path ke versi saya dari program yang dapat dieksekusi, jika tidak, yang ada di sistem mungkin akan dipanggil? Atau, dapatkah saya meletakkan jalur ini di posisi pertama variabel lingkungan $PATH untuk menipu sistem?

Cara mana pun berhasil.


**********************

Solusi 1:

**********************

Berkat Employed Russian, masalah saya akhirnya terpecahkan. Investigasi membutuhkan keahlian penting dalam debug dan patching sistem Linux , dan saya percaya ini adalah aset besar yang saya pelajari. Di sini saya akan memposting solusi, serta mengoreksi beberapa poin di postingan asli saya.

1 Tentang memanggil R

Di postingan awal saya, saya menyebutkan ada dua cara untuk meluncurkan R, baik melalui R atau Rscript . Namun, saya salah membesar-besarkan perbedaan mereka. Sekarang mari selidiki proses start-up mereka, melalui fasilitas debugging Linux yang penting strace (lihat man strace ). Sebenarnya ada banyak hal menarik yang terjadi setelah kita mengetikkan perintah di shell, dan kita bisa menggunakan

strace -e trace=process [command]

untuk melacak semua panggilan sistem yang melibatkan manajemen proses. Hasilnya, kita dapat melihat fork, wait, dan langkah-langkah eksekusi dari suatu proses. Meskipun tidak disebutkan di halaman manual, @Employed Russian menunjukkan bahwa hanya mungkin untuk menentukan subkelas dari process , misalnya, execve untuk langkah-langkah eksekusi.

Untuk R kami memiliki

~/Desktop/dgemm$ time strace -e trace=execve R --vanilla < /dev/null > /dev/null
execve("/usr/bin/R", ["R", "--vanilla"], [/* 70 vars */]) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=5777, si_status=0, si_utime=0, si_stime=0} ---
execve("/usr/lib/R/bin/exec/R", ["/usr/lib/R/bin/exec/R", "--vanilla"], [/* 79 vars */]) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=5778, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

real    0m0.345s
user    0m0.256s
sys     0m0.068s

sedangkan untuk Rscript kami memiliki

~/Desktop/dgemm$ time strace -e trace=execve Rscript --default-packages=base --vanilla /dev/null
execve("/usr/bin/Rscript", ["Rscript", "--default-packages=base", "--vanilla", "/dev/null"], [/* 70 vars */]) = 0
execve("/usr/lib/R/bin/R", ["/usr/lib/R/bin/R", "--slave", "--no-restore", "--vanilla", "--file=/dev/null"], [/* 71 vars */]) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=5822, si_status=0, si_utime=0, si_stime=0} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=5823, si_status=0, si_utime=0, si_stime=0} ---
execve("/usr/lib/R/bin/exec/R", ["/usr/lib/R/bin/exec/R", "--slave", "--no-restore", "--vanilla", "--file=/dev/null"], [/* 80 vars */]) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=5827, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

real    0m0.063s
user    0m0.020s
sys     0m0.028s

Kami juga telah menggunakan time untuk mengukur waktu start-up. Perhatikan bahwa

  1. Rscript sekitar 5,5 kali lebih cepat daripada R . Salah satu alasannya adalah R itu akan memuat 6 paket default saat start-up, sedangkan Rscript hanya memuat satu base paket dengan kontrol:--default-packages=base . Tapi masih jauh lebih cepat bahkan tanpa pengaturan ini.
  2. Akhirnya kedua proses start-up diarahkan ke $(R RHOME)/bin/exec/R , dan di postingan asli saya, saya sudah mengeksploitasi readelf -d untuk menunjukkan bahwa eksekusi ini akan memuat libR.so , yang ditautkan dengan libblas.so.3 . Menurut penjelasan @Employed Russian, perpustakaan BLAS yang dimuat lebih dulu akan menang, jadi metode asli saya tidak mungkin berhasil.
  3. Agar berhasil menjalankan strace , kami telah menggunakan luar biasa berkas /dev/null sebagai file input dan file output bila diperlukan. Misalnya, Rscript meminta file input, sedangkan R menuntut keduanya. Kami memberi makan perangkat null untuk membuat perintah berjalan lancar dan output bersih. Perangkat nol adalah file yang ada secara fisik, tetapi berfungsi dengan luar biasa. Saat membacanya, tidak ada isinya; saat menulis untuk itu, itu membuang semuanya.

2. Curang R

Sekarang sejak libblas.so akan tetap dimuat, satu-satunya hal yang dapat kami lakukan adalah menyediakan versi kami sendiri dari perpustakaan ini. Seperti yang sudah saya katakan di postingan awal, jika kita memiliki akses root, ini sangat mudah, yaitu dengan menggunakan update-alternatives --config libblas.so.3 , sehingga sistem Linux akan membantu kami menyelesaikan peralihan ini. Tapi @Employed Russian menawarkan cara mengagumkan untuk menipu sistem tanpa akses root:mari kita periksa bagaimana R menemukan perpustakaan BLAS saat start-up, dan pastikan kita memberi makan versi kita sebelum default sistem ditemukan! Untuk memantau bagaimana pustaka bersama ditemukan dan dimuat, gunakan variabel lingkungan LD_DEBUG .

Ada sejumlah variabel lingkungan Linux dengan awalan LD_ , seperti yang didokumentasikan dalam man ld.so . Variabel-variabel ini dapat diberikan sebelum dieksekusi, sehingga kita dapat mengubah fitur yang sedang berjalan dari suatu program. Beberapa variabel yang berguna meliputi:

  • LD_LIBRARY_PATH untuk menyetel jalur penelusuran pustaka run time;
  • LD_DEBUG untuk melacak pencarian dan pemuatan pustaka bersama;
  • LD_TRACE_LOADED_OBJECTS untuk menampilkan semua pustaka yang dimuat oleh suatu program (berperilaku mirip dengan ldd );
  • LD_PRELOAD untuk memaksa menyuntikkan perpustakaan ke program di awal, sebelum semua perpustakaan lain dicari;
  • LD_PROFILE dan LD_PROFILE_OUTPUT untuk membuat profil satu pustaka bersama yang ditentukan. Pengguna R yang telah membaca bagian 3.4.1.1 sprof Penulisan ekstensi R harus ingat bahwa ini digunakan untuk membuat profil kode yang dikompilasi dari dalam R.

Penggunaan LD_DEBUG dapat dilihat dengan:

~/Desktop/dgemm$ LD_DEBUG=help cat
Valid options for the LD_DEBUG environment variable are:

  libs        display library search paths
  reloc       display relocation processing
  files       display progress for input file
  symbols     display symbol table processing
  bindings    display information about symbol binding
  versions    display version dependencies
  scopes      display scope information
  all         all previous options combined
  statistics  display relocation statistics
  unused      determined unused DSOs
  help        display this help message and exit

  To direct the debugging output into a file instead of standard output a filename can be specified using the LD_DEBUG_OUTPUT environment variable.

Di sini kami sangat tertarik untuk menggunakan LD_DEBUG=libs . Misalnya,

~/Desktop/dgemm$ LD_DEBUG=libs Rscript --default-packages=base --vanilla /dev/null |& grep blas
  5974: find library=libblas.so.3 [0]; searching
  5974:   trying file=/usr/lib/R/lib/libblas.so.3
  5974:   trying file=/usr/lib/i386-linux-gnu/i686/sse2/libblas.so.3
  5974:   trying file=/usr/lib/i386-linux-gnu/i686/cmov/libblas.so.3
  5974:   trying file=/usr/lib/i386-linux-gnu/i686/libblas.so.3
  5974:   trying file=/usr/lib/i386-linux-gnu/sse2/libblas.so.3
  5974:   trying file=/usr/lib/i386-linux-gnu/libblas.so.3
  5974:   trying file=/usr/lib/jvm/java-7-openjdk-i386/jre/lib/i386/client/libblas.so.3
  5974:   trying file=/usr/lib/libblas.so.3
  5974: calling init: /usr/lib/libblas.so.3
  5974: calling fini: /usr/lib/libblas.so.3 [0]

menunjukkan berbagai upaya yang dicoba oleh program R untuk menemukan dan memuat libblas.so.3 . Jadi jika kami dapat menyediakan libblas.so.3 versi kami sendiri , dan pastikan R menemukannya terlebih dahulu, lalu masalahnya selesai.

Pertama-tama mari kita buat tautan simbolis libblas.so.3 di jalur kerja kami ke pustaka OpenBLAS libopenblas.so , lalu perluas LD_LIBRARY_PATH default dengan jalur kerja kami (dan ekspor itu):

~/Desktop/dgemm$ ln -sf libopenblas.so libblas.so.3
~/Desktop/dgemm$ export LD_LIBRARY_PATH = $(pwd):$LD_LIBRARY_PATH  ## put our working path at top

Sekarang mari kita periksa kembali proses loading library:

~/Desktop/dgemm$ LD_DEBUG=libs Rscript --default-packages=base --vanilla /dev/null |& grep blas
  6063: find library=libblas.so.3 [0]; searching
  6063:   trying file=/usr/lib/R/lib/libblas.so.3
  6063:   trying file=/usr/lib/i386-linux-gnu/i686/sse2/libblas.so.3
  6063:   trying file=/usr/lib/i386-linux-gnu/i686/cmov/libblas.so.3
  6063:   trying file=/usr/lib/i386-linux-gnu/i686/libblas.so.3
  6063:   trying file=/usr/lib/i386-linux-gnu/sse2/libblas.so.3
  6063:   trying file=/usr/lib/i386-linux-gnu/libblas.so.3
  6063:   trying file=/usr/lib/jvm/java-7-openjdk-i386/jre/lib/i386/client/libblas.so.3
  6063:   trying file=/home/zheyuan/Desktop/dgemm/libblas.so.3
  6063: calling init: /home/zheyuan/Desktop/dgemm/libblas.so.3
  6063: calling fini: /home/zheyuan/Desktop/dgemm/libblas.so.3 [0]

Besar! Kami telah berhasil menipu R.

3. Bereksperimenlah dengan OpenBLAS

~/Desktop/dgemm$ Rscript --default-packages=base --vanilla mmperf.R
GFLOPs = 8.77

Sekarang, semuanya berfungsi seperti yang diharapkan!

4. Hapus LD_LIBRARY_PATH (agar aman)

Merupakan praktik yang baik untuk menghapus LD_LIBRARY_PATH setelah digunakan.

~/Desktop/dgemm$ unset LD_LIBRARY_PATH

**********************

Solusi 2:

**********************

Di sini kami menawarkan solusi lain, dengan mengeksploitasi variabel lingkungan LD_PRELOAD disebutkan dalam solusi 1 kami . Penggunaan LD_PRELOAD lebih "brutal", karena memaksa memuat perpustakaan tertentu ke dalam program sebelum program lain, bahkan sebelum perpustakaan C libc.so ! Ini sering digunakan untuk patching mendesak dalam pengembangan Linux.

Seperti yang ditunjukkan di bagian 2 dari postingan asli , pustaka BLAS bersama libopenblas.so memiliki SONAME libopenblas.so.0 . Sebuah SONAME adalah nama internal yang akan dicari oleh pemuat perpustakaan dinamis pada saat dijalankan, jadi kita perlu membuat tautan simbolis ke libopenblas.so dengan SONAME ini :

~/Desktop/dgemm$ ln -sf libopenblas.so libopenblas.so.0

lalu kami mengekspornya:

~/Desktop/dgemm$ export LD_PRELOAD=$(pwd)/libopenblas.so.0

Perhatikan bahwa jalur lengkap ke libopenblas.so.0 perlu dimasukkan ke LD_PRELOAD untuk pemuatan yang berhasil, meskipun libopenblas.so.0 berada di bawah $(pwd) .

Sekarang kami meluncurkan Rscript dan periksa apa yang terjadi dengan LD_DEBUG :

~/Desktop/dgemm$ LD_DEBUG=libs Rscript --default-packages=base --vanilla /dev/null |& grep blas
  4860: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4860: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4865: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4868: calling fini: /home/zheyuan/Desktop/dgemm/libopenblas.so [0]
  4870: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4869: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4867: calling fini: /home/zheyuan/Desktop/dgemm/libopenblas.so [0]
  4860: find library=libblas.so.3 [0]; searching
  4860:   trying file=/usr/lib/R/lib/libblas.so.3
  4860:   trying file=/usr/lib/i386-linux-gnu/i686/sse2/libblas.so.3
  4860:   trying file=/usr/lib/i386-linux-gnu/i686/cmov/libblas.so.3
  4860:   trying file=/usr/lib/i386-linux-gnu/i686/libblas.so.3
  4860:   trying file=/usr/lib/i386-linux-gnu/sse2/libblas.so.3
  4860:   trying file=/usr/lib/i386-linux-gnu/libblas.so.3
  4860:   trying file=/usr/lib/jvm/java-7-openjdk-i386/jre/lib/i386/client/libblas.so.3
  4860:   trying file=/usr/lib/libblas.so.3
  4860: calling init: /usr/lib/libblas.so.3
  4860: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4874: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4876: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4860: calling fini: /home/zheyuan/Desktop/dgemm/libopenblas.so [0]
  4860: calling fini: /usr/lib/libblas.so.3 [0]

Membandingkan dengan apa yang kami lihat di solusi 1 dengan menipu R dengan libblas.so.3 versi kita sendiri , kita bisa melihatnya

  • libopenblas.so.0 dimuat terlebih dahulu, karenanya ditemukan terlebih dahulu oleh Rscript;
  • setelah libopenblas.so.0 ditemukan, Rscript terus mencari dan memuat libblas.so.3 . Namun, ini tidak akan berpengaruh pada "datang pertama, dilayani pertama" aturan, dijelaskan dalam jawaban aslinya.

Bagus, semuanya berfungsi, jadi kami menguji mmperf.c kami program:

~/Desktop/dgemm$ Rscript --default-packages=base --vanilla mmperf.R
GFLOPs = 9.62

Hasil 9,62 lebih besar dari 8,77 yang kita lihat pada solusi sebelumnya hanya karena kebetulan. Sebagai pengujian untuk menggunakan OpenBLAS kami tidak menjalankan percobaan berkali-kali untuk hasil yang lebih tepat.

Kemudian seperti biasa, kami menghapus variabel lingkungan pada akhirnya:

~/Desktop/dgemm$ unset LD_PRELOAD

Linux
  1. Bagaimana Cara Menjalankan Program Tertentu Sebagai Root Tanpa Prompt Kata Sandi?

  2. Bagaimana Cara Menjalankan Perintah Yang Melibatkan Pengalihan Atau Perpipaan Dengan Sudo?

  3. Bagaimana Cara Menjalankan Perintah Tanpa Properti Root?

  1. ketika menggunakan CPAN di linux ubuntu haruskah saya menjalankannya menggunakan sudo / sebagai root atau sebagai pengguna default saya

  2. Instal zsh tanpa akses root?

  3. Bagaimana cara membuat pengguna dengan akses hanya baca ke semua file? (yaitu root tanpa izin menulis)

  1. Saat Berlari Ke Level Run, Apakah Itu Menjalankan Level Run Sebelumnya?

  2. Bagaimana cara menginstal .deb secara lokal tanpa akses apt-get, dpkg atau root?

  3. Linux:sysadmin produktif tanpa root (mengamankan kekayaan intelektual)?