c++ - dinamicas - Desmontaje de bibliotecas compartidas de Linux
biblioteca dinamica c (5)
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 esto
- Para proteger 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 simple, 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
e inspeccionando 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 inadecuado. Luego, redeclaramos 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
lo 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, cuando comenzamos a usar contenedores STL:
#include <vector>
struct private_struct
{
float f;
};
void other_private_function()
{
std::vector<private_struct> v;
}
terminamos con muchas exportaciones adicionales de la biblioteca 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 las optimizaciones necesarias, debes asegurarte de que el vector se utilice realmente, de modo 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 versión y modifica los encabezados de STL (!) Que parece funcionar, pero me gustaría preguntar:
¿Existe alguna forma clara de eliminar 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:
- Los símbolos de las bibliotecas estáticas (fuente cerrada) 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 exportada es C.
Soy consciente de las otras preguntas similares sobre SO:
- NO compartir todas las clases con la biblioteca compartida
- Cómo REALMENTE desnudar un binario en MacOs
- Enlazador GNU: alternativa a --version-script para listar símbolos exportados en la línea de comando?
pero han tenido poco éxito con las respuestas.
El uso del atributo de visibilidad predeterminado y -fvisibility = hidden se debe aumentar con -fvisibility-inlines-hidden.
También debe olvidarse de intentar ocultar las exportaciones de stdlib, consulte este error de GCC 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
lugar de usar atributos. Aunque si está creando una biblioteca multiplataforma, consulte 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.
En general, en múltiples 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 nombres altos, bajos e intermedios para mantener nuestros símbolos alejados de posibles conflictos con los símbolos de otras personas.
Aquí hay una solución que no amarás:
- Crea un .so pequeño con solo tu API expuesta.
- Haga que abra la implementación real con dlopen, y enlace con dlsym.
Mientras no uses RTLD_GLOBAL, ahora tienes aislamiento completo, si no secreto en particular. -Bsymbolic también podría ser deseable.
Entonces 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);
}
exports.version
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 parecido a lo que estamos buscando. Sin embargo, hay algunas trampas:
- Tenemos que asegurarnos de que no usemos el prefijo "exportado" (en este ejemplo simple "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 a la que se le ocurra!
Si envuelve su parte privada en un espacio de nombre anónimo, ni std::abs
ni private_function
pueden verse 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);
}
compilando (g ++ 4.3.3):
g++ -shared -o libtest.so test.cpp -s
inspeccionando
# 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
Solo para señalar que Ulrich Drepper escribió un ensayo sobre (¿todos?) Aspectos de la escritura de bibliotecas compartidas para Linux / Unix, que cubre el control de símbolos exportados entre muchos otros temas.
Esto fue muy útil para dejar claro cómo exportar solo las funciones en una lista blanca de una biblioteca compartida.