Pregunta

Quiero anular ciertas llamadas de función a varias API por el simple hecho de registrar las llamadas, pero también podría querer manipular los datos antes de que se envíen a la función real.

Por ejemplo, digamos que uso una función llamada getObjectName miles de veces en mi código fuente. Quiero anular temporalmente esta función a veces porque quiero cambiar el comportamiento de esta función para ver el resultado diferente.

Creo un nuevo archivo fuente como este:

#include <apiheader.h>    

const char *getObjectName (object *anObject)
{
    if (anObject == NULL)
        return "(null)";
    else
        return "name should be here";
}

Compilo todas mis otras fuentes como lo haría normalmente, pero las vinculo con esta función primero antes de enlazar con la biblioteca de la API. Esto funciona bien, excepto que obviamente no puedo llamar a la función real dentro de mi función de reemplazo.

¿Hay una forma más fácil de " anular " ¿Una función sin obtener enlaces / compilar errores / advertencias? Lo ideal es poder anular la función simplemente compilando y vinculando uno o dos archivos adicionales en lugar de jugar con las opciones de enlace o alterar el código fuente real de mi programa.

¿Fue útil?

Solución

Si solo es para su fuente que desea capturar / modificar las llamadas, la solución más sencilla es armar un archivo de encabezado ( intercept.h ) con:

#ifdef INTERCEPT
    #define getObjectName(x) myGetObectName(x)
#endif

e implemente la función de la siguiente manera (en intercept.c que no incluye intercept.h ):

const char *myGetObjectName (object *anObject) {
    if (anObject == NULL)
        return "(null)";
    else
        return getObjectName(anObject);
}

Luego, asegúrese de que cada archivo de origen donde desea interceptar la llamada tenga:

#include "intercept.h"

en la parte superior.

Luego, cuando compiles con " -DINTERCEPT " ;, todos los archivos llamarán a tu función en lugar de a la real y tu función aún podrá llamar a la real.

Compilar sin el " -DINTERCEPT " evitará que ocurra la interceptación.

Es un poco más complicado si quiere interceptar todas las llamadas (no solo las de su fuente). Generalmente, esto se puede hacer con la carga dinámica y la resolución de la función real (con dlload- y < código> dlsym- tipo llamadas) pero no creo que sea necesario en su caso.

Otros consejos

Con gcc, en Linux puede usar el indicador del enlazador --wrap de esta manera:

gcc program.c -Wl,-wrap,getObjectName -o program

y define tu función como:

const char *__wrap_getObjectName (object *anObject)
{
    if (anObject == NULL)
        return "(null)";
    else
        return __real_getObjectName( anObject ); // call the real function
}

Esto asegurará que todas las llamadas a getObjectName () se redirijan a su función de contenedor (en el momento del enlace). Sin embargo, este indicador muy útil está ausente en gcc bajo Mac OS X.

Recuerda declarar la función de envoltorio con extern " C " si estás compilando con g ++.

Puede anular una función utilizando el truco de LD_PRELOAD ; consulte man ld.so . Compila la biblioteca compartida con su función e inicia el binario (¡ni siquiera necesita modificar el binario!) Como LD_PRELOAD = mylib.so myprog .

En el cuerpo de su función (en biblioteca compartida) escribe así:

const char *getObjectName (object *anObject) {
  static char * (*func)();

  if(!func)
    func = (char *(*)()) dlsym(RTLD_NEXT, "getObjectName");
  printf("Overridden!\n");     
  return(func(anObject));    // call original function
}

Puede anular cualquier función de la biblioteca compartida, incluso desde stdlib, sin modificar / recompilar el programa, por lo que podría hacer el truco en programas para los que no tiene una fuente. ¿No es agradable?

Si usa GCC, puede hacer que su función sea débil . Aquellos pueden anularse por funciones no débiles:

test.c :

#include <stdio.h>

__attribute__((weak)) void test(void) { 
    printf("not overridden!\n"); 
}

int main() {
    test();
}

¿Qué hace?

$ gcc test.c
$ ./a.out
not overridden!

test1.c :

#include <stdio.h>

void test(void) {
    printf("overridden!\n");
}

¿Qué hace?

$ gcc test1.c test.c
$ ./a.out
overridden!

Lamentablemente, eso no funcionará para otros compiladores. Pero puede tener las declaraciones débiles que contienen funciones reemplazables en su propio archivo, colocando solo una inclusión en los archivos de implementación de la API si está compilando con GCC:

weakdecls.h :

__attribute__((weak)) void test(void);
... other weak function declarations ...

functions.c :

/* for GCC, these will become weak definitions */
#ifdef __GNUC__
#include "weakdecls.h"
#endif

void test(void) { 
    ...
}

... other functions ...

La desventaja de esto es que no funciona por completo sin hacer algo con los archivos api (que necesitan esas tres líneas y los puntos débiles). Pero una vez que hizo ese cambio, las funciones se pueden anular fácilmente escribiendo una definición global en un archivo y vinculándolas.

  

A menudo es conveniente modificar el   comportamiento de las bases de código existentes por   Funciones de envoltura o reemplazo. Cuando   editando el codigo fuente de esos   funciones es una opción viable, esto puede   Ser un proceso directo. Cuando   la fuente de las funciones no puede ser   editado (por ejemplo, si las funciones son   proporcionado por la biblioteca del sistema C),   entonces las técnicas alternativas son   necesario. Aquí presentamos tales   Técnicas para UNIX, Windows y   Plataformas Macintosh OS X.

Este es un excelente PDF que cubre cómo se hizo en OS X, Linux y Windows.

No tiene trucos asombrosos que no hayan sido documentados aquí (este es un increíble conjunto de respuestas BTW) ... pero es una buena lectura.

Interceptación de funciones arbitrarias en sistemas operativos Windows, UNIX y Macintosh X plataformas (2004), por Daniel S. Myers y Adam L. Bazinet .

Puede descargar el PDF directamente desde una ubicación alternativa (para redundancia) .

Y, por último, si las dos fuentes anteriores se incendian, aquí hay un resultado de búsqueda de Google .

Puede definir un puntero de función como una variable global. La sintaxis de los llamadores no cambiaría. Cuando se inicia el programa, puede verificar si un indicador de línea de comandos o una variable de entorno está configurada para habilitar el registro, luego guardar el valor original del puntero de función y reemplazarlo con su función de registro. No necesitaría un " registro habilitado " construir. Los usuarios podrían habilitar el registro " en el campo " ;.

Necesitará poder modificar el código fuente de las personas que llaman, pero no la persona que recibe la llamada (por lo que esto funcionaría al llamar a bibliotecas de terceros).

foo.h:

typedef const char* (*GetObjectNameFuncPtr)(object *anObject);
extern GetObjectNameFuncPtr GetObjectName;

foo.cpp:

const char* GetObjectName_real(object *anObject)
{
    return "object name";
}

const char* GetObjectName_logging(object *anObject)
{
    if (anObject == null)
        return "(null)";
    else
        return GetObjectName_real(anObject);
}

GetObjectNameFuncPtr GetObjectName = GetObjectName_real;

void main()
{
    GetObjectName(NULL); // calls GetObjectName_real();

    if (isLoggingEnabled)
        GetObjectName = GetObjectName_logging;

    GetObjectName(NULL); // calls GetObjectName_logging();
}

También hay un método difícil de hacerlo en el vinculador que involucra dos bibliotecas de código auxiliar.

La biblioteca # 1 está vinculada a la biblioteca del host y expone el símbolo que se está redefiniendo con otro nombre.

La biblioteca # 2 está vinculada a la biblioteca # 1, interecepting la llamada y llama a la versión redefinida en la biblioteca # 1.

Tenga mucho cuidado con los pedidos de enlaces aquí o no funcionará.

Aprovechando la respuesta de @Johannes Schaub con una solución adecuada para el código que no posee.

Alias ??la función que desea anular a una función débilmente definida, y luego reimplementarla usted mismo.

override.h

#define foo(x) __attribute__((weak))foo(x)

foo.c

function foo() { return 1234; }

override.c

function foo() { return 5678; }

Use valores variables específicos del patrón en su Makefile para agregar la marca del compilador -include override.h .

%foo.o: ALL_CFLAGS += -include override.h

Aparte: Quizás también puedas usar -D 'foo (x) __attribute __ ((débil)) foo (x)' para definir tus macros.

Compile y vincule el archivo con su reimplementación ( override.c ).

  • Esto le permite anular una función única de cualquier archivo fuente, sin tener que modificar el código.

  • El inconveniente es que debe usar un archivo de encabezado separado para cada archivo que desee anular.

También puede usar una biblioteca compartida (Unix) o una DLL (Windows) para hacer esto (sería un poco de una penalización de rendimiento). Luego puede cambiar la DLL / para que se cargue (una versión para depuración, una versión para no depuración).

He hecho algo similar en el pasado (no para lograr lo que intentas lograr, pero la premisa básica es la misma) y funcionó bien.

[Edición basada en comentario OP]

  

De hecho, una de las razones por las que quiero   funciones de anulación es porque   sospechar que se comportan de manera diferente en   diferentes sistemas operativos.

Hay dos maneras comunes (que conozco) de lidiar con eso, la forma de lib / dll compartida o escribir diferentes implementaciones con las que se vincula.

Para ambas soluciones (bibliotecas compartidas o enlaces diferentes) tendrías foo_linux.c, foo_osx.c, foo_win32.c (o una mejor manera es linux / foo.c, osx / foo.c y win32 / foo.c ) y luego compilar y enlazar con el apropiado.

Si está buscando un código diferente para diferentes plataformas Y una versión de depuración -vs- probablemente me inclinaría por usar la solución compartida lib / DLL ya que es la más flexible.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top