GNU/Linux >> Znalost Linux >  >> Linux

Bez přístupu root spusťte R s vyladěným BLAS, když je propojen s referenčním BLAS

proč můj způsob nefunguje

Za prvé, sdílené knihovny na UNIXu jsou navrženy tak, aby napodobovaly způsob, jakým fungují archivní knihovny (archivní knihovny tu byly první). Konkrétně to znamená, že pokud máte libfoo.so a libbar.so , oba definují symbol foo , pak vyhraje knihovna, která se načte jako první:všechny odkazy na foo odkudkoli v programu (včetně z libbar.so ) se naváže na libfoo.so s definice foo .

To napodobuje, co by se stalo, kdybyste svůj program propojili s libfoo.a a libbar.a , kde obě archivní knihovny definovaly stejný symbol foo . Více informací o propojení archivu zde.

Shora by mělo být jasné, že pokud libblas.so.3 a libopenblas.so.0 definovat stejnou sadu symbolů (což dělají ), a pokud libblas.so.3 je nejprve načten do procesu a poté rutiny z libopenblas.so.0 nikdy být volán.

Za druhé, správně jste se rozhodli, že od R přímé odkazy proti libR.so a od libR.so přímé odkazy proti libblas.so.3 , je zaručeno, že libopenblas.so.0 prohraje bitvu.

Vy však omylně rozhodl, že Rscript je lepší, ale není:Rscript je maličkost binární (11 kB v mém systému; srovnání s 2,4 MB pro libR.so ) a přibližně vše, co dělá, je exec z R . To je triviální vidět v strace výstup:

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 +++

Což znamená, že v době, kdy se váš skript spustí, bude libblas.so.3 byl načten a libopenblas.so.0 která bude načtena jako závislost mmperf.so ve skutečnosti nebude být použit na cokoli.

je vůbec možné, aby to fungovalo

Pravděpodobně. Napadají mě dvě možná řešení:

  1. Předstírejte, že libopenblas.so.0 je ve skutečnosti libblas.so.3
  2. Znovu sestavte celý R balíček proti libopenblas.so .

Pro číslo 1 potřebujete ln -s libopenblas.so.0 libblas.so.3 , pak ujistěte se, že vaše kopie libblas.so.3 se najde před systémovým nastavením LD_LIBRARY_PATH přiměřeně.

Zdá se, že toto pro mě funguje:

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

Všimněte si, jak jsem dostal chybu (můj „předstírat“ libblas.so.3 nedefinuje symboly, které se od něj očekávají, protože se ve skutečnosti jedná o kopii libc.so.6 ).

Můžete také potvrdit, která verze libblas.so.3 se načítá tímto způsobem:

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

Pro číslo 2 jste řekli:

Nemám root přístup na počítačích, které chci testovat, takže skutečné propojení s OpenBLAS je nemožné.

ale to se zdá být falešný argument:pokud dokážete sestavit libopenblas , jistě si také můžete vytvořit svou vlastní verzi R .

Aktualizace:

Na začátku jste zmínil, že libblas.so.3 a libopenblas.so.0 definují stejný symbol, co to znamená? Mají odlišné SONAME, nestačí to k jejich odlišení systémem?

Symboly a SONAME nemají nic k sobě navzájem.

Ve výstupu z readelf -Ws libblas.so.3 můžete vidět symboly a readelf -Ws libopenblas.so.0 . Symboly související s BLAS , například cgemv_ , se objeví v obou knihovnách.

Vaše nejasnosti ohledně SONAME možná pochází z Windows. DLL s ve Windows jsou navrženy úplně jinak. Zejména když FOO.DLL importuje symbol bar z BAR.DLL , obě název symbolu (bar ) a DLL ze kterého byl tento symbol importován (BAR.DLL ) jsou zaznamenány v FOO.DLL s tabulkou importu.

Díky tomu je snadné mít R import cgemv_ z BLAS.DLL , zatímco MMPERF.DLL importuje stejný symbol z OPENBLAS.DLL .

To však ztěžuje vkládání knihoven a funguje zcela jinak, než jak fungují archivní knihovny (dokonce i ve Windows).

Názory na to, který design je celkově lepší, se různí, ale ani jeden systém pravděpodobně svůj model nikdy nezmění.

Existují způsoby, jak UNIX emulovat vazbu symbolů ve stylu Windows:viz RTLD_DEEPBIND v manuálové stránce dlopen. Pozor:tyto jsou plné nebezpečí, pravděpodobně zmást odborníky na UNIX, nejsou široce používány a pravděpodobně obsahují chyby v implementaci.

Aktualizace 2:

myslíte, že zkompiluji R a nainstaluji jej do svého domovského adresáře?

Ano.

Když jej pak chci vyvolat, měl bych výslovně uvést cestu ke své verzi spustitelného programu, jinak by se místo toho mohla vyvolat ta v systému? Nebo mohu dát tuto cestu na první pozici proměnné prostředí $PATH, abych podváděl systém?

Oba způsoby fungují.


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

Řešení 1:

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

Díky Employed Russian je můj problém konečně vyřešen. Vyšetřování vyžaduje důležité dovednosti v oblasti ladění a oprav systému Linux a věřím, že je to velký přínos, který jsem se naučil. Zde bych zveřejnil řešení a také opravil několik bodů v mém původním příspěvku.

1 O vyvolání R

Ve svém původním příspěvku jsem zmínil, že existují dva způsoby, jak spustit R, buď přes R nebo Rscript . Jejich rozdíl jsem však neprávem zveličil. Pojďme nyní prozkoumat jejich proces spouštění prostřednictvím důležitého linuxového ladícího zařízení strace (viz man strace ). Ve skutečnosti se po zadání příkazu do shellu děje spousta zajímavých věcí, které můžeme použít

strace -e trace=process [command]

ke sledování všech systémových volání zahrnujících řízení procesů. V důsledku toho můžeme sledovat kroky rozvětvení, čekání a provádění procesu. Ačkoli to není uvedeno v manuálové stránce, @Employed Russian ukazuje, že je možné zadat pouze podtřídu process , například execve pro kroky provedení.

Pro R máme

~/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

zatímco pro Rscript máme

~/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

Také jsme použili time k měření doby spouštění. Všimněte si, že

  1. Rscript je asi 5,5krát rychlejší než R . Jedním z důvodů je R načte 6 výchozích balíčků při spuštění, zatímco Rscript načte pouze jeden base balíček ovládáním:--default-packages=base . Ale i bez tohoto nastavení je stále mnohem rychlejší.
  2. Nakonec jsou oba spouštěcí procesy přesměrovány na $(R RHOME)/bin/exec/R a ve svém původním příspěvku jsem již využil readelf -d ukázat, že tento spustitelný soubor načte libR.so , které jsou propojeny s libblas.so.3 . Podle vysvětlení @Employed Russian vyhraje knihovna BLAS načtená jako první, takže moje původní metoda nebude fungovat.
  3. Pro úspěšné spuštění strace , použili jsme úžasné soubor /dev/null jako vstupní soubor a v případě potřeby výstupní soubor. Například Rscript vyžaduje vstupní soubor, zatímco R požaduje obojí. Nulové zařízení přivádíme do obou, aby příkaz běžel hladce a výstup byl čistý. Nulové zařízení je fyzicky existující soubor, ale funguje úžasně. Při čtení z něj neobsahuje nic; při zápisu do něj vše zahodí.

2. Cheat R

Nyní od libblas.so se stejně načte, jediné, co můžeme udělat, je poskytnout naši vlastní verzi této knihovny. Jak jsem řekl v původním příspěvku, pokud máme přístup root, je to opravdu snadné pomocí update-alternatives --config libblas.so.3 , takže nám systém Linux pomůže tento přechod dokončit. @Employed Russian ale nabízí úžasný způsob, jak podvádět systém bez přístupu root:podívejme se, jak R najde knihovnu BLAS při spuštění, a ujistěte se, že nahrajeme naši verzi dříve, než bude nalezeno výchozí nastavení systému! Chcete-li sledovat, jak jsou sdílené knihovny nalezeny a načítány, použijte proměnnou prostředí LD_DEBUG .

Existuje řada proměnných prostředí Linux s předponou LD_ , jak je zdokumentováno v man ld.so . Tyto proměnné lze přiřadit před spustitelný soubor, takže můžeme změnit funkci běhu programu. Některé užitečné proměnné zahrnují:

  • LD_LIBRARY_PATH pro nastavení vyhledávací cesty knihovny běhu;
  • LD_DEBUG pro sledování vyhledávání a načítání sdílených knihoven;
  • LD_TRACE_LOADED_OBJECTS pro zobrazení všech načtených knihoven programem (chová se podobně jako ldd );
  • LD_PRELOAD za vynucení vložení knihovny do programu na úplném začátku, ještě předtím, než budou vyhledány všechny ostatní knihovny;
  • LD_PROFILE a LD_PROFILE_OUTPUT pro profilování jeden zadaná sdílená knihovna. Uživatel R, který si přečetl sekci 3.4.1.1 sprof rozšíření Writing R by mělo připomenout, že se používá pro profilování kompilovaného kódu z R.

Použití LD_DEBUG může být viděn:

~/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.

Zde nás zvláště zajímá použití LD_DEBUG=libs . Například,

~/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]

ukazuje různé pokusy, které se program R pokusil najít a načíst libblas.so.3 . Pokud bychom tedy mohli poskytnout naši vlastní verzi libblas.so.3 a ujistěte se, že jej R najde jako první, pak je problém vyřešen.

Udělejme nejprve symbolický odkaz libblas.so.3 v naší pracovní cestě ke knihovně OpenBLAS libopenblas.so a poté rozbalte výchozí LD_LIBRARY_PATH s naší pracovní cestou (a exportem to):

~/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

Nyní znovu zkontrolujeme proces načítání knihovny:

~/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]

Skvělý! Úspěšně jsme podvedli R.

3. Experimentujte s OpenBLAS

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

Nyní vše funguje podle očekávání!

4. Zrušte nastavení LD_LIBRARY_PATH (pro jistotu)

Je dobrým zvykem zrušit nastavení LD_LIBRARY_PATH po použití.

~/Desktop/dgemm$ unset LD_LIBRARY_PATH

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

Řešení 2:

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

Zde nabízíme další řešení, a to využitím proměnné prostředí LD_PRELOAD uvedené v našem řešení 1 . Použití LD_PRELOAD je "brutálnější", protože nutí načíst danou knihovnu do programu před jakýmkoli jiným programem, dokonce i před knihovnou C libc.so ! To se často používá pro urgentní opravy při vývoji Linuxu.

Jak je uvedeno v části 2 původního příspěvku , sdílená knihovna BLAS libopenblas.soSONAME libopenblas.so.0 . SONAME je interní název, který by zavaděč dynamické knihovny hledal za běhu, takže musíme vytvořit symbolický odkaz na libopenblas.so s tímto SONAME :

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

pak jej exportujeme:

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

Všimněte si, že úplná cesta na libopenblas.so.0 musí být napájeno na LD_PRELOAD pro úspěšné načtení, i když libopenblas.so.0 je pod $(pwd) .

Nyní spustíme Rscript a zkontrolujte, co se stane do 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]

Porovnání s tím, co jsme viděli v řešení 1 podváděním R s naší vlastní verzí libblas.so.3 , to vidíme

  • libopenblas.so.0 je načten jako první, tedy nalezen jako první pomocí Rscript;
  • po libopenblas.so.0 je nalezen, Rscript pokračuje ve vyhledávání a načítání libblas.so.3 . Toto však nebude mít žádný vliv na „kdo dřív přijde, je dřív na řadě“ pravidlo, vysvětlené v původní odpovědi.

Dobře, všechno funguje, takže testujeme naše mmperf.c program:

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

Výsledek 9,62 je větší než 8,77, který jsme viděli v dřívějším řešení pouze náhodou. Jako test pro použití OpenBLAS nespouštíme experiment mnohokrát pro přesnější výsledek.

Potom jako obvykle na konci zrušíme nastavení proměnné prostředí:

~/Desktop/dgemm$ unset LD_PRELOAD

Linux
  1. Jak spustit konkrétní program jako root bez výzvy k zadání hesla?

  2. Provádí se při běhu na úroveň běhu předchozí úrovně běhu?

  3. Jak spustit příkaz bez vlastností root?

  1. při použití CPAN v linux ubuntu bych jej měl spustit pomocí sudo / jako root nebo jako můj výchozí uživatel

  2. Jak lokálně nainstalovat .deb bez přístupu apt-get, dpkg nebo root?

  3. Vazba na porty menší než 1024 bez přístupu root

  1. Jak spustit příkaz, který zahrnuje přesměrování nebo potrubí pomocí Sudo?

  2. Instalovat zsh bez přístupu root?

  3. Linux:produktivní správci systému bez root (zabezpečení duševního vlastnictví)?