2010-01-18 20 views
32

Ci è stato recentemente chiesto di spedire una versione Linux di una delle nostre librerie, in precedenza abbiamo sviluppato sotto Linux e spedito per Windows dove la distribuzione di librerie è generalmente molto più semplice. Il problema che abbiamo riscontrato è quello di rimuovere i simboli esportati solo verso quelli nell'interfaccia esposta. Ci sono tre buoni motivi per voler fare questoStripping librerie condivise di linux

  • Per proteggere gli aspetti proprietari della nostra tecnologia dall'esposizione attraverso i simboli esportati.
  • Per evitare che gli utenti abbiano problemi con nomi di simboli in conflitto.
  • Per velocizzare il caricamento della libreria (almeno così mi è stato detto).

Prendendo un esempio semplice allora:

test.cpp

#include <cmath> 

float private_function(float f) 
{ 
    return std::abs(f); 
} 

extern "C" float public_function(float f) 
{ 
    return private_function(f); 
} 

compilato con (g ++ 4.3.2, ld 2.18.93.20081009)

g++ -shared -o libtest.so test.cpp -s 

e di ispezione delle simboli con

nm -DC libtest.so 

  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 

ovviamente inadeguata. Così la prossima abbiamo ridichiarare la funzione pubblica come

extern "C" float __attribute__ ((visibility ("default"))) 
    public_function(float f) 

e compilare con

g++ -shared -o libtest.so test.cpp -s -fvisibility=hidden 

che dà

  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 

che è buono, se non che std :: abs è esposto. Più problematico è quando iniziamo il collegamento in altre librerie (statiche) al di fuori del nostro controllo, tutti i simboli che usiamo da quelle librerie vengono esportati. Inoltre, quando iniziare a usare contenitori STL:

#include <vector> 
struct private_struct 
{ 
    float f; 
}; 

void other_private_function() 
{ 
    std::vector<private_struct> v; 
} 

si finisce con molte esportazioni aggiuntivi dalla libreria 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: Con ottimizzazioni su Avrai bisogno di assicurarsi che il vettore è effettivamente utilizzato in modo che il compilatore non ottimizzi i simboli non utilizzati.

Credo che la mia collega è riuscito a costruire una soluzione ad hoc che coinvolgono file di versione e modificando le intestazioni STL che sembra funzionare, ma vorrei chiedere (!):

C'è un modo pulito rimuovere tutti i simboli non necessari (quelli che non fanno parte della funzionalità della libreria esposta) da una libreria condivisa Linux? Ho provato un sacco di opzioni per g + + e ld con scarso successo quindi preferirei le risposte che funzionano meglio di quanto si creda.

In particolare:

  • Simboli (closed-source) librerie statiche non vengono esportati.
  • I simboli della libreria standard non vengono esportati.
  • I simboli non pubblici dai file oggetto non vengono esportati.

La nostra interfaccia è esportato C.

sono a conoscenza delle altre domande simili su SO:

ma h ave avuto poco successo con le risposte.

+1

Sul collegamento statico delle librerie di sistema: è illegale per voi farlo. Cioè, poiché [(e)] (http://www.eglibc.org/) [GLIBC] (http://www.gnu.org/software/libc/) è concesso in licenza in [LGPL] (http://opensource.org/licenses/LGPL-3.0) e dal momento che tale licenza si applica a tutto il codice che lo utilizza eccetto se collegato dinamicamente, collegando staticamente il codice viene coperto da LGPL e viene richiesto di fornire le fonti (a chiunque abbia dato il binario e chiedono delle fonti). Questo non si applica a libgcc e libstdC++, che in particolare non si applicano a nessun codice utilizzando solo API pubbliche, indipendentemente dal collegamento. –

+0

Ne sono a conoscenza, e non mi riferivo ai simboli di glibc, tutti i simboli sopra sono generati dall'istanza dei template dalla libreria standard C++ e sono, per necessità, generati nei miei file oggetto (poiché le istanze dei template possono ' essere in biblioteca!). –

risposta

6

Quindi la soluzione che abbiamo per ora è la seguente:

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); 
} 

esportazioni.Versione

LIBTEST 
{ 
global: 
    public*; 
local: 
    *; 
}; 

compilato con

g++ -shared test.cpp -o libtest.so -fvisibility=hidden -fvisibility-inlines-hidden -s -Wl,--version-script=exports.version 

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 

che è abbastanza vicino a quello che stiamo cercando. Ci sono un paio di grattacapi però:

  • dobbiamo garantire non usiamo il prefisso "esportato" (in questo semplice esempio "pubblico", ma ovviamente qualcosa di più utile nel nostro caso) nel codice interno.
  • Molti nomi di simboli finiscono ancora nella tabella delle stringhe, che sembra essere in giù a RTTI, -fno-rtti li fa andare via nei miei test semplici, ma è una soluzione piuttosto nucleare.

Sono felice di accettare qualsiasi soluzione migliore che qualcuno si avvicini!

+0

Accetto la nostra soluzione finale, poiché meglio si adatta alle nostre esigenze, ma a beneficio di chiunque altro nella stessa situazione vorrebbe aggiungere che le altre risposte sono tutte soluzioni perfettamente valide se la vostra situazione differisce leggermente dalla nostra! –

+0

Questo metodo nasconde anche i simboli dalle altre librerie statiche? Sto correndo nello stesso identico problema con un sacco di cruft da varie dipendenze esterne che vengono esportate. –

+0

Sembra, sì. Gli unici simboli erano le nostre esportazioni e alcuni simboli a cui stavamo collegando nelle librerie C/C++. –

2

In generale, su più sistemi Linux e Unix, la risposta è che qui non c'è risposta al momento del collegamento. è abbastanza fondamentale su come funziona ld.so.

Questo porta ad alcune alternative piuttosto laboriose. Ad esempio, rinominiamo STL per vivere in _STL anziché std per evitare conflitti su AWL, e usiamo namespace alti, bassi e intermedi per mantenere i nostri simboli lontani da possibili conflitti con i simboli di altre persone.

Ecco una soluzione che non vi piacerà:

  1. creare un piccolo .so solo con l'API esposta esso.
  2. Aprite l'implementazione reale con dlopen e collegate con dlsym.

Fintanto che non si utilizza RTLD_GLOBAL, ora si dispone di isolamento completo se non di segretezza particolare .. -Bsymbolic potrebbe anche essere auspicabile.

+0

Purtroppo nascondere i dettagli del nostro algoritmo è metà del problema per noi. –

+0

Bene, potresti ricorrere alla ridenominazione dei simboli all'ingrosso se usi il mio approccio basato su due oggetti condivisi. @ roba di joshperry potrebbe fare il lavoro. – bmargulies

5

L'utilizzo dell'attributo di visibilità predefinito e -fvisibility = hidden deve essere aumentato con -fvisibility -inlines-hidden.

Si dovrebbe anche dimenticare di provare a nascondere le esportazioni di stdlib, vedere this GCC bug per il motivo.

Inoltre, se si dispone di tutti i simboli pubblici in intestazioni specifiche, è possibile racchiuderli in #pragma GCC visibility push(default) e #pragma GCC visibility pop anziché utilizzare gli attributi. Anche se stai creando una libreria multipiattaforma, dai un'occhiata a Controlling Exported Symbols of Shared Libraries per una tecnica per unificare la tua strategia di esportazione di Windows DLL e Linux DSO.

+0

Grazie per aver trovato il tempo di rispondere, i link fatti per una lettura interessante. 1. Esporremo un'interfaccia C pura (per compatibilità soprattutto), perché dovremmo esporre i dettagli della nostra implementazione? Solo perché * potremmo * condividere RTTI, ecc. Oltre i confini delle biblioteche non significa che * lo *. 2. Nei miei esempi semplici -visibilità -in lineari-nascosti non fa differenza, non credo che influenzerà la nostra interfaccia (o risolverà i nostri problemi) ma potrebbe essere utile in futuro. 3.Sfortunatamente, l'articolo di riferimento non sembra offrire soluzioni a nessuno dei nostri problemi (oltre a quello che abbiamo). –

+0

Capisco cosa intendi per private_struct essere nella istanza del vettore che viene esportata. Che cambiamento sta facendo il tuo collega alle intestazioni che le fanno sparire? – joshperry

4

Se si avvolgono la vostra parte privata in uno spazio dei nomi anonima allora né std::absprivate_function si può vedere nella tabella dei simboli:

namespace{ 
#include<cmath> 
    float private_function(float f) 
    { 
    return std::abs(f); 
    } 
} 
extern "C" float public_function(float f) 
{ 
     return private_function(f); 
} 

compilazione (g ++ 4.3.3):

g++ -shared -o libtest.so test.cpp -s

ispezione:

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

Sebbene funzioni per un semplice esempio ed è un buon modo per nascondere i costrutti locali, gli spazi dei nomi privati ​​non verranno ridimensionati per l'intero progetto. –

5

Basta notare che Ulrich Drepper ha scritto un saggio sugli aspetti (tutti?) Di writing shared libraries per Linux/Unix, che copre il controllo dei simboli esportati tra molti altri argomenti.

Questo è stato molto utile per chiarire come esportare solo le funzioni su una lista bianca da una lib condivisa.

Problemi correlati