Usuwanie bibliotek współdzielonych Linuksa
Ostatnio zostaliśmy poproszeni o dostarczenie Linuksowej wersji jednej z naszych bibliotek, wcześniej rozwijanej pod Linuksem i dostarczonej dla systemu Windows, gdzie wdrażanie bibliotek jest ogólnie dużo łatwiejsze. Problem, który napotkaliśmy, polega na tym, że eksportowane symbole ograniczają się tylko do tych znajdujących się w odsłoniętym interfejsie. Istnieją trzy powody, dla których warto to zrobić
- aby chronić zastrzeżone aspekty naszej technologii przed ekspozycją poprzez eksportowane symbole.
- To zapobiegaj problemom użytkowników ze sprzecznymi nazwami symboli.
- aby przyspieszyć ładowanie biblioteki (przynajmniej tak mi powiedziano).
Biorąc prosty przykład:
Test.cpp
#include <cmath>
float private_function(float f)
{
return std::abs(f);
}
extern "C" float public_function(float f)
{
return private_function(f);
}
Skompilowany z (g++ 4.3.2, ld 2.18.93.20081009)
g++ -shared -o libtest.so test.cpp -s
I sprawdzanie symboli za pomocą
nm -DC libtest.so
Daje
w _Jv_RegisterClasses
0000047c T private_function(float)
000004ba W std::abs(float)
0000200c A __bss_start
w __cxa_finalize
w __gmon_start__
0000200c A _edata
00002014 A _end
00000508 T _fini
00000358 T _init
0000049b T public_function
Oczywiście nieodpowiednie. Następnie ponownie deklarujemy funkcję publiczną jako
extern "C" float __attribute__ ((visibility ("default")))
public_function(float f)
I kompilować z
g++ -shared -o libtest.so test.cpp -s -fvisibility=hidden
Które daje
w _Jv_RegisterClasses
0000047a W std::abs(float)
0000200c A __bss_start
w __cxa_finalize
w __gmon_start__
0000200c A _edata
00002014 A _end
000004c8 T _fini
00000320 T _init
0000045b T public_function
Co jest dobre, poza tym, że std::abs jest odsłonięty. Bardziej problematyczne jest to, gdy zaczynamy linkować w innych (statycznych) bibliotekach poza naszą kontrolą, wszystkie symbole, których używamy z tych bibliotek, są eksportowane . Dodatkowo, kiedy zaczynamy używać kontenerów STL:
#include <vector>
struct private_struct
{
float f;
};
void other_private_function()
{
std::vector<private_struct> v;
}
Kończymy z wieloma dodatkowymi eksportami z biblioteki C++
00000b30 W __gnu_cxx::new_allocator<private_struct>::deallocate(private_struct*, unsigned int)
00000abe W __gnu_cxx::new_allocator<private_struct>::new_allocator()
00000a90 W __gnu_cxx::new_allocator<private_struct>::~new_allocator()
00000ac4 W std::allocator<private_struct>::allocator()
00000a96 W std::allocator<private_struct>::~allocator()
00000ad8 W std::_Vector_base<private_struct, std::allocator<private_struct> >::_Vector_impl::_Vector_impl()
00000aaa W std::_Vector_base<private_struct, std::allocator<private_struct> >::_Vector_impl::~_Vector_impl()
00000b44 W std::_Vector_base<private_struct, std::allocator<private_struct> >::_M_deallocate(private_struct*, unsigned int)
00000a68 W std::_Vector_base<private_struct, std::allocator<private_struct> >::_M_get_Tp_allocator()
00000b08 W std::_Vector_base<private_struct, std::allocator<private_struct> >::_Vector_base()
00000b6e W std::_Vector_base<private_struct, std::allocator<private_struct> >::~_Vector_base()
00000b1c W std::vector<private_struct, std::allocator<private_struct> >::vector()
00000bb2 W std::vector<private_struct, std::allocator<private_struct> >::~vector()
NB: przy włączonej optymalizacji musisz upewnić się, że wektor jest rzeczywiście używany, aby kompilator nie optymalizował nieużywane symbole Na Zewnątrz.
Wydaje mi się, że mojemu koledze udało się skonstruować rozwiązanie ad-hoc obejmujące pliki wersji i modyfikację nagłówków STL (!) to chyba działa, ale chciałbym zapytać:
Czy istnieje czysty sposób na usunięcie wszystkich niepotrzebnych symboli (tj. tych, które nie są częścią funkcji exposed library) z biblioteki współdzielonej Linuksa? próbowałem dość wiele opcji zarówno do G++ i ld z małym sukcesem, więc wolałbym odpowiedzi, które są znane do pracy raczej niż sądzono.
W szczególności:
- symbole z bibliotek statycznych (closed-source) nie są eksportowane.
- symbole z biblioteki standardowej nie są eksportowane.
- Niepubliczne symbole z plików obiektowych nie są eksportowane.
Nasz eksportowany interfejs To C.
Znam inne podobne pytania na SO:
- Nie współdzielenie wszystkich klas z biblioteką współdzieloną
- jak naprawdę rozebrać a binary in MacOs
- GNU linker: alternatywa dla -- version-script do wyświetlania eksportowanych symboli w wierszu poleceń?
Ale nie odniosły sukcesu z odpowiedziami.
5 answers
Więc rozwiązanie, które mamy na razie jest następujące:
Test.cpp
#include <cmath>
#include <vector>
#include <typeinfo>
struct private_struct
{
float f;
};
float private_function(float f)
{
return std::abs(f);
}
void other_private_function()
{
std::vector<private_struct> f(1);
}
extern "C" void __attribute__ ((visibility ("default"))) public_function2()
{
other_private_function();
}
extern "C" float __attribute__ ((visibility ("default"))) public_function1(float f)
{
return private_function(f);
}
Eksport.wersja
LIBTEST
{
global:
public*;
local:
*;
};
Skompilowane z
g++ -shared test.cpp -o libtest.so -fvisibility=hidden -fvisibility-inlines-hidden -s -Wl,--version-script=exports.version
Daje
00000000 A LIBTEST
w _Jv_RegisterClasses
U _Unwind_Resume
U std::__throw_bad_alloc()
U operator delete(void*)
U operator new(unsigned int)
w __cxa_finalize
w __gmon_start__
U __gxx_personality_v0
000005db T public_function1
00000676 T public_function2
Co jest zbliżone do tego, czego szukamy. Jest jednak kilka gotchas:
- musimy się upewnić, że nie używamy prefiksu "exported" (w tym prostym przykładzie "public", ale oczywiście czegoś bardziej użytecznego w naszym przypadku) w kodzie wewnętrznym.
- wiele nazw symboli wciąż kończy się w łańcuchu tabela, która wydaje się być do RTTI, - fno-rtti sprawia, że znikają w moich prostych testach, ale jest raczej nuklearnym rozwiązaniem.
Z radością przyjmuję wszelkie lepsze rozwiązania, które każdy wymyśli!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-19 09:33:13
Twoje użycie domyślnego atrybutu visibility i -fvisibility = hidden powinno zostać rozszerzone o-fvisibility-inlines-hidden.
Powinieneś także zapomnieć o próbie ukrycia eksportu stdlib, zobacz ten błąd GCC Dlaczego.
Jeśli masz wszystkie publiczne symbole w określonych nagłówkach, możesz je zawinąć w #pragma GCC visibility push(default)
i #pragma GCC visibility pop
zamiast używać atrybutów. Jeśli jednak tworzysz międzyplatformową bibliotekę, spójrz na kontrolowanie eksportowanych symboli współdzielonych Biblioteki dla techniki unifikacji strategii eksportu DSO systemu Windows i Linux.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-18 21:05:48
Dla przypomnienia, że Ulrich Drepper napisał esej dotyczący (all?) aspekty pisania bibliotek współdzielonych dla Linuksa/Uniksa, które obejmują kontrolę eksportowanych symboli wśród wielu innych tematów.
Było to bardzo przydatne w wyjaśnieniu, jak eksportować tylko funkcje na białej liście ze współdzielonej biblioteki lib.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-10 15:46:49
Jeśli zamkniesz swoją prywatną część w anonimowej przestrzeni nazw, to ani std::abs
, ani private_function
nie będą widoczne w tabeli symboli:
namespace{
#include<cmath>
float private_function(float f)
{
return std::abs(f);
}
}
extern "C" float public_function(float f)
{
return private_function(f);
}
Kompilowanie (g++ 4.3.3):
g++ -shared -o libtest.so test.cpp -s
: {]}
# nm -DC libtest.so
w _Jv_RegisterClasses
0000200c A __bss_start
w __cxa_finalize
w __gmon_start__
0000200c A _edata
00002014 A _end
000004a8 T _fini
000002f4 T _init
00000445 T public_function
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-19 09:49:35
Ogólnie rzecz biorąc, w wielu systemach Linux i Unix, odpowiedź tutaj jest taka, że nie ma odpowiedzi tutaj w czasie połączenia. to dość fundamentalne dla tego, jak ld.so działa.
Prowadzi to do pracochłonnych alternatyw. Na przykład zmieniamy nazwę STL na live w _STL zamiaststd
, aby uniknąć konfliktów o STL, i używamy przestrzeni nazw high, low I in-between, aby trzymać nasze symbole z dala od możliwych konfliktów z symbolami innych ludzi.
Oto rozwiązanie, którego nie miłość:
- utwórz małe. więc tylko z narażonym API to.
- niech otworzy rzeczywistą implementację za pomocą dlopen i połączy się z dlsym.
Dopóki nie używasz RTLD_GLOBAL, masz teraz pełną izolację, jeśli nie szczególną tajemnicę .. - Bsymbolic też może być pożądane.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-18 19:34:45