2010-01-18 11 views
32

Recientemente se nos ha pedido que enviemos una versión de Linux de una de nuestras bibliotecas, anteriormente desarrollábamos bajo Linux y se enviaban para Windows, donde el despliegue de bibliotecas generalmente es mucho más fácil. El problema al que nos hemos referido es al quitar los símbolos exportados a solo aquellos en la interfaz expuesta. Hay tres buenas razones para querer hacer estoStripping linux shared libraries

  • Para proteger a los aspectos de propiedad de nuestra tecnología de la exposición a través de los símbolos exportados.
  • Para evitar que los usuarios tengan problemas con los nombres de símbolos conflictivos.
  • Para acelerar la carga de la biblioteca (al menos eso me dicen).

Tomando un ejemplo sencillo, entonces:

test.cpp

#include <cmath> 

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

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

compilado con (g ++ 4.3.2, ld 2.18.93.20081009)

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

y la inspección de los símbolos con

nm -DC libtest.so 

da

  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 

obviamente inadecuada. Así que la próxima nos redeclare la función pública como

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

y compilar con

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

que da

  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 

lo cual es bueno, excepto que std :: abs está expuesto. Más problemático es cuando comenzamos a vincular en otras bibliotecas (estáticas) fuera de nuestro control, todos los símbolos que utilizamos de esas bibliotecas se exportan. Además, al empezar a utilizar contenedores STL:

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

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

nos encontramos con muchas exportaciones adicionales de la biblioteca de 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 optimizaciones en que necesita para asegurarse de que el vector es realmente utilizado para que el compilador no optimice los símbolos no utilizados.

Creo que mi colega ha logrado construir una solución ad-hoc que implica archivos de la versión y modificar las cabeceras de STL que parece funcionar, pero me gustaría preguntar (!):

¿Hay una manera limpia para quitar todos los símbolos innecesarios (IE que no son parte de la funcionalidad de la biblioteca expuesta) de una biblioteca compartida de Linux? He intentado bastantes opciones tanto para g ++ como para ld con poco éxito, así que preferiría respuestas que se sabe que funcionan en lugar de creer.

En particular:

  • Símbolos de la (de código cerrado) bibliotecas estáticas no se exportan.
  • Los símbolos de la biblioteca estándar no se exportan.
  • Los símbolos no públicos de los archivos objeto no se exportan.

Nuestra interfaz es exportada C.

Soy consciente de las otras preguntas similares sobre SO:

pero h Ave tuvo poco éxito con las respuestas.

+1

En enlace estático de bibliotecas de sistema: es ilegal que lo haga. Es decir, dado que [(e)] (http://www.eglibc.org/) [GLIBC] (http://www.gnu.org/software/libc/) tiene licencia bajo [LGPL] (http://opensource.org/licenses/LGPL-3.0) y dado que esa licencia se aplica a todos los códigos que la usan, excepto si están vinculados dinámicamente, al vincular estáticamente usted hace que su código esté cubierto por LGPL y se les exige que proporcionen las fuentes (a cualquier persona a quien le haya proporcionado información binaria piden fuentes). Esto no se aplica a libgcc y libstdC++, que específicamente no se aplican a ningún código que use solo API pública, sin importar cuán vinculado esté. –

+0

Soy consciente de esto, y no me refiero a los símbolos de glibc, todos los símbolos anteriores se generan mediante instanciación de plantilla de la biblioteca estándar de C++ y, por necesidad, se generan en mis archivos de objeto (ya que las instancias de plantilla pueden ' estar en la biblioteca!). –

Respuesta

6

Así que la solución que tenemos por ahora es la siguiente:

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

exportaciones.Versión

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

compilado con

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

da

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 

que es bastante cerca de lo que estamos buscando. Hay algunos aspectos críticos sin embargo:

  • Tenemos que asegurarnos de que no usamos el prefijo "exportado" (en este ejemplo sencillo "público", pero obviamente algo más útil en nuestro caso) en el código interno.
  • Muchos nombres de símbolos aún terminan en la tabla de cadenas, que parece estar en RTTI, -fno-rtti hace que desaparezcan en mis pruebas simples, pero es una solución más bien nuclear.

¡Me complace aceptar cualquier solución mejor que se le ocurra a cualquiera!

+0

Estoy aceptando nuestra solución final, ya que se adapta mejor a nuestras necesidades, pero para el beneficio de cualquier otra persona en la misma situación, quisiera agregar que las otras respuestas son todas soluciones perfectamente viables si su situación difiere un poco de la nuestra. –

+0

¿Este método también oculta los símbolos de otras bibliotecas estáticas? Me estoy encontrando con el mismo problema exacto con muchos productos derivados de varias dependencias externas que se exportan. –

+0

Parece, sí. Los únicos símbolos eran nuestras exportaciones y algunos símbolos a los que estábamos vinculados en las bibliotecas de C/C++. –

2

En general, en varios sistemas Linux y Unix, la respuesta aquí es que no hay respuesta aquí en el momento del enlace. es bastante fundamental cómo funciona ld.so.

Esto lleva a algunas alternativas bastante laboriosas. Por ejemplo, cambiamos el nombre de STL para que viva en _STL en lugar de std para evitar conflictos con STL, y usamos espacios de nombre alto, bajo y medio para mantener nuestros símbolos alejados de posibles conflictos con los símbolos de otras personas.

He aquí una solución que no va a encantar:

  1. crear un pequeño .so con sólo su API expuesta la misma.
  2. Haga que abra la implementación real con dlopen, y enlace con dlsym.

Siempre que no utilice RTLD_GLOBAL, ahora tiene aislamiento completo si no secreto en particular .. -Bsymbolic también podría ser deseable.

+0

Desgraciadamente, ocultar los detalles de nuestro algoritmo es la mitad del problema para nosotros. –

+0

Bueno, podría participar en el cambio de nombre al por mayor si usa mi enfoque de dos objetos compartidos. Las cosas de @ joshperry podrían hacer el trabajo. – bmargulies

5

Su uso del atributo de visibilidad predeterminado y -fvisibility = hidden debe ser aumentado con -fvisibility-inlines-hidden.

También debe olvidarse de tratar de ocultar las exportaciones de stdlib, consulte this GCC bug para saber por qué.

Además, si tiene todos sus símbolos públicos en un encabezado específico, puede envolverlos en #pragma GCC visibility push(default) y #pragma GCC visibility pop en lugar de usar atributos. Aunque si está creando una biblioteca multiplataforma, eche un vistazo a Controlling Exported Symbols of Shared Libraries para obtener una técnica para unificar su DLL de Windows y la estrategia de exportación de Linux DSO.

+0

Gracias por tomarse el tiempo para responder, los enlaces hicieron una lectura interesante. 1. Exponemos una interfaz C pura (para mayor compatibilidad), ¿por qué deberíamos exponer los detalles de nuestra implementación? El hecho de que * podamos * compartir RTTI, etc. a través de los límites de la biblioteca no significa que * lo * hagamos. 2. En mis ejemplos simples -fvisibilidad-inlines-hidden no hizo diferencia, no creo que afecte a nuestra interfaz (o resuelva nuestros problemas) pero puede ser útil en el futuro. 3.Lamentablemente, el artículo al que se hace referencia no parece ofrecer soluciones a ninguno de nuestros problemas (más allá de lo que tenemos). –

+0

Veo lo que quiere decir que private_struct está en la instanciación vectorial que se exporta. ¿Qué cambio está haciendo tu compañero de trabajo en los encabezados que hacen que estos desaparezcan? – joshperry

4

Si envuelve su parte privada en un espacio de nombres en el anonimato que, ni tampoco std::absprivate_function se puede ver en la tabla de símbolos:

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

compilar (g ++ 4.3.3):

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

inspecting:

# 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

Si bien funciona como un ejemplo simple y es una buena forma de ocultar construcciones locales, los espacios de nombres privados no se escalarán al proyecto completo. –

5

Solo para señalar que Ulrich Drepper escribió un ensayo sobre (¿todos?) Aspectos de writing shared libraries para Linux/Unix, que cubre el control de símbolos exportados entre muchos otros temas.

Esto fue muy útil para dejar en claro cómo exportar solo las funciones en una lista blanca de una biblioteca compartida.