2011-02-23 7 views
7

Antecedentes:¿Por qué este código dinámico de carga de la biblioteca funciona con gcc?

me he encontrado con la difícil tarea de portar una aplicación de GNU/Linux C++ a Windows. Una de las cosas que hace esta aplicación es buscar bibliotecas compartidas en rutas específicas y luego carga las clases de ellas dinámicamente usando las llamadas posix dlopen() y dlsym(). Tenemos una muy buena razón para cargar de esta manera que no voy a entrar aquí.

El problema:

Para descubrir dinámicamente símbolos generados por un compilador C++ con dlsym() o GetProcAddress() que debe ser unmangled utilizando un extern "C" bloque de ligamiento. Por ejemplo:

#include <list> 
#include <string> 

using std::list; 
using std::string; 

extern "C" { 

    list<string> get_list() 
    { 
     list<string> myList; 
     myList.push_back("list object"); 
     return myList; 
    } 

} 

Este código es perfectamente válida C++ y compila y se ejecuta en numerosos compiladores en Linux y Windows. Sin embargo, no compila con MSVC porque "el tipo de devolución no es válido C". La solución que hemos llegado con es cambiar la función para devolver un puntero a la lista en lugar del objeto de lista:

#include <list> 
#include <string> 

using std::list; 
using std::string; 

extern "C" { 

    list<string>* get_list() 
    { 
     list<string>* myList = new list<string>(); 
     myList->push_back("ptr to list"); 
     return myList; 
    } 

} 

que he estado tratando de encontrar una solución óptima para el cargador de GNU/Linux que funcionará tanto con las nuevas funciones como con el prototipo de la antigua función heredada o, al menos, detectará cuándo se encuentra la función en desuso y emitirá una advertencia. Sería indecoroso para nuestros usuarios si el código solo fallase cuando intentaron usar una biblioteca anterior. Mi idea original fue establecer un manejador de señal SIGSEGV durante la llamada a get_list (sé que esto es asqueroso - estoy abierto a mejores ideas). Así que solo para confirmar que al cargar una biblioteca antigua se segfault donde pensé que correría una biblioteca usando el viejo prototipo de función (devolviendo un objeto de lista) a través del nuevo código de carga (que espera un puntero a una lista) y para mi sorpresa solo funcionó. La pregunta que tengo es ¿por qué?

El siguiente código de carga funciona con los dos prototipos de función enumerados anteriormente. Confirmé que funciona en Fedora 12, RedHat 5.5 y RedHawk 5.1 utilizando las versiones 4.1.2 y 4.4.4 de gcc. Compile las bibliotecas usando g ++ con -shared y -fPIC y el ejecutable debe estar vinculado contra dl (-ldl).

#include <dlfcn.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <list> 
#include <string> 

using std::list; 
using std::string; 

int main(int argc, char **argv) 
{ 
    void *handle; 
    list<string>* (*getList)(void); 
    char *error; 

    handle = dlopen("library path", RTLD_LAZY); 
    if (!handle) 
    { 
     fprintf(stderr, "%s\n", dlerror()); 
     exit(EXIT_FAILURE); 
    } 

    dlerror(); 

    *(void **) (&getList) = dlsym(handle, "get_list"); 

    if ((error = dlerror()) != NULL) 
    { 
     printf("%s\n", error); 
     exit(EXIT_FAILURE); 
    } 

    list<string>* libList = (*getList)(); 

    for(list<string>::iterator iter = libList->begin(); 
      iter != libList->end(); iter++) 
    { 
     printf("\t%s\n", iter->c_str()); 
    } 

    dlclose(handle); 

    exit(EXIT_SUCCESS); 
} 
+3

Porque tienes suerte. Sospecho que si probaras este tipo de cosas con un programa más complicado, comenzarías a ver los efectos de una pila rota o similar. – aschepler

+0

El código que publiqué está simplificado. La aplicación real tiene alrededor de 100k líneas de código y he ejecutado algunos casos de prueba bastante extensos que parecen funcionar. Aunque estoy de acuerdo, esto no debería funcionar a menos que exista alguna peculiaridad con GCC en este caso. – bckohan

+1

No estoy seguro si 'they must is unmangled' es cierto. Si le pides a dlsym() el nombre destrozado, no lo encontrará correctamente. –

Respuesta

4

Como dice aschepler, es porque tienes suerte.

Como resultado, la ABI utilizada para gcc (y la mayoría de los otros compiladores) para x86 y x64 devuelve estructuras 'grandes' (demasiado grandes para caber en un registro) al pasar un puntero extra 'oculto' a la función, que utiliza ese puntero como espacio para almacenar el valor de retorno y luego devuelve el puntero. Así que resulta que una función de la forma

struct foo func(...) 

es más o menos equivlant a

struct foo *func(..., struct foo *) 

donde se espera que la persona que llama para asignar espacio para un 'foo' (probablemente en la pila) y pase un puntero a eso.

Ocurre que si tiene una función que espera ser llamada de esta manera (esperando devolver una estructura) y la llama mediante un puntero de función que devuelve un puntero, PUEDE parecer que funciona - si los bits de basura que recibe el argumento adicional (contenido de registro aleatorio dejado por la persona que llama) indican que se puede escribir, la función llamada escribirá su valor de retorno y luego devolverá ese puntero, de modo que el código invocado recuperará algo que se parece a un puntero válido a la estructura que está esperando. Por lo tanto, el código puede parecer que funciona superficialmente, pero en realidad probablemente sea un trozo de memoria aleatoria que puede ser importante más adelante.

Cuestiones relacionadas