2009-03-06 13 views
58

Quiero anular ciertas llamadas de función a varias API con el fin de registrar las llamadas, pero también podría querer manipular datos antes de enviarlos a la función real.Anular una llamada de función en C

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

puedo crear un nuevo archivo de origen como esto:

#include <apiheader.h>  

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

puedo compilar toda mi otra fuente como lo haría normalmente, pero vincularlo en contra de esta primera función 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 principal.

¿Existe alguna manera más fácil de "anular" una función sin tener que obtener errores de enlace/compilación/advertencias? Lo ideal sería poder anular la función simplemente compilando y vinculando un archivo extra o dos en lugar de manipular las opciones de vinculación o modificar el código fuente real de mi programa.

+0

@dreamlax, nos estamos moviendo de lo general (C) para soluciones específicas (gcc/Linux) ahora - que sería una buena idea para aclarar lo que se está ejecutando en el fin de orientar mejor las respuestas . – paxdiablo

+1

Bueno, estoy desarrollando en Linux pero los objetivos son Mac OS, Linux y Windows. De hecho, una de las razones por las que quiero anular las funciones es porque sospecho que se comportan de manera diferente en diferentes sistemas operativos. – dreamlax

Respuesta

61

Si es sólo para la fuente que desea capturar/modificar las llamadas, la solución más simple es poner juntos un archivo de cabecera (intercept.h) con:

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

e implementar la función de la siguiente manera (en intercept.c, que no incluyen intercept.h):

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

a continuación, asegúrese de que cada archivo de origen en la que desea interceptar la llamada tiene:

#include "intercept.h" 

en la parte superior.

Luego, al compilar con "-DINTERCEPT", todos los archivos llamarán a su función en lugar de a la real y su función aún podrá llamar a la real.

La compilación sin el "-DINTERCEPT" evitará la intercepción.

Es un poco más complicado si se desea interceptar todas las llamadas (no sólo los de su fuente) - por regla general, se puede hacer con la carga y la resolución de la función real dinámico (con dlload- y dlsym- llamadas de tipo), pero yo no' Creo que es necesario en tu caso.

+0

Usar una definición no es una verdadera respuesta polimórfica, pero estoy de acuerdo con el uso del ingenio: P – Suroot

+0

Gracias, esa es una muy buena idea, pero como dije, quiero intentar y evitar modificar el código fuente. Si no puedo encontrar otra manera, entonces tendré que hacerlo así, supongo. Debe ser fácil desactivar la intercepción. – dreamlax

+1

Utilice una bandera en tiempo de compilación para controlar la interceptación (vea la respuesta actualizada). Nuevamente, esto también se puede hacer en tiempo de ejecución, solo necesita detectarlo en myGetObjectName() y siempre llamar a getObjectName si se establece el indicador de tiempo de ejecución (es decir, aún se intercepta pero cambia el comportamiento). – paxdiablo

3

También hay un método complicado de hacerlo en el enlazador que implica dos bibliotecas de código auxiliar.

La biblioteca n.º 1 está vinculada a la biblioteca de host y expone el símbolo que se redefine con otro nombre.

La biblioteca n. ° 2 está vinculada a la biblioteca n. ° 1, intereceptando la llamada y llamando a la versión redefinida en la biblioteca n. ° 1.

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

+0

Suena complicado, pero evita modificar la fuente. Muy buena sugerencia. – dreamlax

+0

No creo que puedas forzar que getObjectName vaya a una biblioteca específica sin el truco de dlopen/dlsym. – paxdiablo

+0

Cualquier operación de tiempo de enlace que arrastre en la biblioteca de host dará como resultado un símbolo definido de forma múltiple. – paxdiablo

7

Puede definir un puntero a la función como una variable global. La sintaxis de los llamantes no cambiaría.Cuando se inicia el programa, puede verificar si algún indicador de línea de comando o variable de entorno está configurado para habilitar el registro, luego guarda el valor original del puntero de función y lo reemplaza con su función de registro. No necesitaría una compilación especial de "inicio de sesión habilitado". Los usuarios podrían habilitar el registro "en el campo".

Deberá poder modificar el código fuente de las personas que llaman, pero no el destinatario (para que esto funcione 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(); 
} 
+0

He considerado este método pero requiere modificar el código fuente, algo que no quiero hacer a menos que sea necesario. Aunque esto tiene el beneficio adicional de cambiar durante el tiempo de ejecución. – dreamlax

21

Si utiliza GCC, usted puede hacer su función weak. Aquellos can be overridden por funciones que no son débiles:

test.c:

#include <stdio.h> 

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

int main() { 
    test(); 
} 

¿Qué hacer?

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

test1.c:

#include <stdio.h> 

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

¿Qué hacer?

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

Lamentablemente, eso no funcionará para otros compiladores. Pero usted puede tener las declaraciones débiles que contienen funciones reemplazables en su propio archivo, colocando simplemente un include 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 ... 

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

+1

Eso requeriría modificar la API, ¿no? – dreamlax

+0

su nombre de función será el mismo. tampoco cambiará el ABI o API de ninguna manera. solo incluya el archivo principal al vincular, y las llamadas se realizarán a la función no débil. libc/pthread hacer ese truco: cuando pthread está enlazado, se usan sus funciones de seguridad en lugar del débil de la libc –

+0

he agregado un enlace. no sé si se adapta a sus objetivos (es decir, si puede vivir con esa cosilla de GCC. si msvc tiene algo similar, podría #definir DEBILLO). pero si estuviese en Linux, lo usaría (tal vez haya una mejor manera, no tengo ni idea. Mire también el control de versiones). –

67

con GCC, en Linux se puede usar la bandera --wrap enlazador como esto:.

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

y definir su 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() son redirigidas a su función de contenedor (en tiempo de enlace). Sin embargo, esta bandera muy útil está ausente en gcc bajo Mac OS X.

Recuerde declarar la función de contenedor con extern "C" si está compilando con g ++.

+3

esa es una buena manera. no sabía sobre eso. pero si estoy leyendo la página de manual correctamente, debería ser "__real_getObjectName (anObject);" que el enrutador enruta a getObjectName. de lo contrario, volverás a llamar a __wrap_getObjectName recursivamente. ¿O me estoy perdiendo algo? –

+0

Tienes razón; necesita ser __real_getObjectName, gracias. Debería haber revisado dos veces en la página de manual :) – codelogic

+1

Estoy decepcionado de que ld en Mac OS X no admita la bandera '--wrap'. –

0

Puede utilizar una biblioteca compartida (Unix) o una DLL (Windows) para hacer esto también (sería una pequeña penalización de rendimiento).A continuación, puede cambiar la DLL/para que se cargue (una versión para la depuración, una para la no depuración).

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

[Editar comentario sobre la base de la OP]

De hecho una de las razones por las que quiero funciones de anulación se debe a que sospechoso se comportan de manera diferente en sistemas operativos diferentes.

Existen dos formas comunes (que yo conozco) de lidiar con eso, la forma compartida de lib/dll o escribir diferentes implementaciones con las que se enlaza.

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

Si está buscando tanto un código diferente para plataformas diferentes como debug -vs-release, probablemente estaría inclinado a ir con la solución lib/DLL compartida ya que es la más flexible.

34

Puede anular una función utilizando el truco LD_PRELOAD - consulte man ld.so. Usted compila lib compartido con su función e inicia el binario (¡incluso no necesita modificar el binario!) Como LD_PRELOAD=mylib.so myprog.

En el cuerpo de su función (en lib compartido) se 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 la función de biblioteca compartida, incluso de stdlib, sin modificar/recompilar el programa, por lo que podría haz el truco en programas para los que no tienes una fuente. ¿No es agradable?

+2

No, solo puede anular una función proporcionada por una ** biblioteca compartida ** de esta manera. –

+2

@ChrisStratton Tiene razón, syscall no puede ser anulado de esta manera, he editado mi respuesta. – qrdl

9

A menudo es deseable modificar el comportamiento de bases de código existentes por de envoltura o sustitución de funciones. Cuando editar el código fuente de esas funciones es una opción viable, esto puede ser un proceso sencillo. Cuando el origen de las funciones no puede ser editado (por ejemplo, si las funciones son proporcionadas por la biblioteca C del sistema), entonces se requieren técnicas alternativas . Aquí presentamos las técnicas para las plataformas Macintosh OS X de UNIX, Windows y .

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

No tiene ningún truco sorprendente que no se haya documentado aquí (este es un conjunto increíble de respuestas, por cierto) ... pero es una buena lectura.

http://wwwold.cs.umd.edu/Library/TRs/CS-TR-4585/CS-TR-4585.pdf

+0

¿Desea compartir lo que podría ser este PDF? – HanClinto

+0

lattice.umiacs.umd.edu/ files/functions_tr.pdf - Enlace Agregado –

+0

El enlace está muerto, por cierto. – paxdiablo

1

Sobre la base de la respuesta de @Johannes Schaub con una solución conveniente para el código no es propietario.

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

override.h

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

foo.c

function foo() { return 1234; } 

override.c

function foo() { return 5678; } 

Uso pattern-specific variable values en su Makefile para agregar el indicador del compilador -include override.h.

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

Aparte: Tal vez también se puede utilizar para definir -D 'foo(x) __attribute__((weak))foo(x)' sus macros.

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

  • Esto le permite anular una sola función 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 desea sobrescribir.

Cuestiones relacionadas