2012-03-08 25 views
9

Esto ha estado pendiente por mucho tiempo en mi lista ahora. En resumen: necesito ejecutar mocked_dummy() en el lugar de dummy()EN TIEMPO DE EJECUCIÓN, sin modificar factorial(). No me importa el punto de entrada del software. Puedo agregar cualquier cantidad de funciones adicionales (pero no puedo modificar el código dentro de /*---- do not modify ----*/).burlarse en tiempo de ejecución en C?

¿Por qué necesito esto?
Para realizar pruebas unitarias de algunos módulos C heredados. Sé que hay muchas herramientas disponibles, pero si es posible burlarme en el tiempo de ejecución, puedo cambiar mi enfoque UT (agregar componentes reutilizables) para facilitar mi vida :).

Plataforma/Medio ambiente?
Linux, ARM, gcc.

¿Enfoque que estoy intentando?

  • Sé que GDB usa instrucciones trap/ilegales para sumar puntos de corte (gdb internals).
  • Haga que el código sea auto modificable.
  • Reemplace el segmento de código dummy() con instrucciones ilegales, y devuelva como próxima instrucción inmediata.
  • Control de transferencias al manipulador de trampa.
  • Trap handler es una función reutilizable que se lee desde un socket de dominio Unix.
  • Dirección de la función mocked_dummy() se pasa (leer del archivo de mapa).
  • Se ejecuta la función de simulacro.

Hay problemas para seguir adelante desde aquí. También encontré que el enfoque es tedioso y requiere buena cantidad de codificación, algunos también en ensamblaje.

También encontré, en gcc, cada llamada a la función puede ser hooked/instrumented, pero de nuevo no es muy útil, ya que la función está destinada a ser burlada de todos modos se ejecutará.

¿Hay algún otro enfoque que pueda usar?

#include <stdio.h> 
#include <stdlib.h> 

void mocked_dummy(void) 
{ 
    printf("__%s__()\n",__func__); 
} 

/*---- do not modify ----*/ 
void dummy(void) 
{ 
    printf("__%s__()\n",__func__); 
} 

int factorial(int num) 
{ 
    int      fact = 1; 
    printf("__%s__()\n",__func__); 
    while (num > 1) 
    { 
     fact *= num; 
     num--; 
    } 
    dummy(); 
    return fact; 
} 
/*---- do not modify ----*/ 

int main(int argc, char * argv[]) 
{ 
    int (*fp)(int) = atoi(argv[1]); 
    printf("fp = %x\n",fp); 
    printf("factorial of 5 is = %d\n",fp(5)); 
    printf("factorial of 5 is = %d\n",factorial(5)); 
    return 1; 
} 

Respuesta

2

Esta es una pregunta que he intentado responderme a mí mismo. También tengo el requisito de que quiero que el método/las herramientas de burla se hagan en el mismo idioma que mi aplicación. Desafortunadamente, esto no se puede hacer en C de manera portátil, así que he recurrido a lo que se podría llamar un trampolín o un desvío. Esto cae bajo "Hacer que el código sea auto modificable". acercarse a usted mencionado anteriormente. Aquí es donde cambiamos los bytes de una función en runtime para saltar a nuestra función de simulacro.

#include <stdio.h> 
#include <stdlib.h> 

// Additional headers 
#include <stdint.h> // for uint32_t 
#include <sys/mman.h> // for mprotect 
#include <errno.h> // for errno 

void mocked_dummy(void) 
{ 
    printf("__%s__()\n",__func__); 
} 

/*---- do not modify ----*/ 
void dummy(void) 
{ 
    printf("__%s__()\n",__func__); 
} 

int factorial(int num) 
{ 
    int      fact = 1; 
    printf("__%s__()\n",__func__); 
    while (num > 1) 
    { 
     fact *= num; 
     num--; 
    } 
    dummy(); 
    return fact; 
} 
/*---- do not modify ----*/ 

typedef void (*dummy_fun)(void); 

void set_run_mock() 
{ 
    dummy_fun run_ptr, mock_ptr; 
    uint32_t off; 
    unsigned char * ptr, * pg; 

    run_ptr = dummy; 
    mock_ptr = mocked_dummy; 

    if (run_ptr > mock_ptr) { 
     off = run_ptr - mock_ptr; 
     off = -off - 5; 
    } 
    else { 
     off = mock_ptr - run_ptr - 5; 
    } 

    ptr = (unsigned char *)run_ptr; 

    pg = (unsigned char *)(ptr - ((size_t)ptr % 4096)); 
    if (mprotect(pg, 5, PROT_READ | PROT_WRITE | PROT_EXEC)) { 
     perror("Couldn't mprotect"); 
     exit(errno); 
    } 

    ptr[0] = 0xE9; //x86 JMP rel32 
    ptr[1] = off & 0x000000FF; 
    ptr[2] = (off & 0x0000FF00) >> 8; 
    ptr[3] = (off & 0x00FF0000) >> 16; 
    ptr[4] = (off & 0xFF000000) >> 24; 
} 

int main(int argc, char * argv[]) 
{ 
    // Run for realz 
    factorial(5); 

    // Set jmp 
    set_run_mock(); 

    // Run the mock dummy 
    factorial(5); 

    return 0; 
} 

Portabilidad explicación ...

mprotect() - Esto cambia los permisos de acceso de páginas de memoria de manera que en realidad podemos escribir en la memoria que contiene el código de función. Esto no es muy portátil, y en un entorno WINAPI, puede que necesite usar VirtualProtect().

El parámetro de memoria para mprotect está alineado con la página 4k anterior, esto también puede cambiar de sistema a sistema, 4k es apropiado para kernel de vanilla linux.

El método que usamos para ejecutar la función de simulacro es realmente poner nuestros propios códigos de operación, este es probablemente el mayor problema con la portabilidad porque el código de operación que he utilizado solo funcionará en un pequeño endian x86 (la mayoría de los escritorios) Por lo tanto, debería actualizarse para cada arco en el que planee ejecutar (que podría ser semi-fácil de manejar en macros CPP).

La función en sí tiene que tener al menos cinco bytes. Suele ser el caso porque cada función normalmente tiene al menos 5 bytes en su prólogo y epílogo.

posibles mejoras ...

El set_mock_run() llamada podrían fácilmente ser configurado para aceptar parámetros para su reutilización. Además, puede guardar los cinco bytes sobrescritos de la función original para restaurarlos más tarde en el código si lo desea.

No puedo probar, pero he leído que en ARM ... harías algo similar, pero puedes saltar a una dirección (no a un desplazamiento) con el código de operación de la rama ... que para un incondicional la rama que tendría los primeros bytes sería 0xEA y los siguientes 3 bytes son la dirección.

Chenz

+0

¿Compilará el código anterior tal como está? –

+0

Código actualizado para que se construya. –

+0

Mi duda era más hacia esta afirmación '#define dummy (m.dummy)' ¿Cómo evitaría que el preprocesador NO reemplazara la función ficticia? –

5

test-dept es un marco de pruebas de unidad C relativamente reciente que le permite hacer tropezar tiempo de ejecución de las funciones. Me pareció muy fácil de usar - he aquí un ejemplo de sus documentos:

void test_stringify_cannot_malloc_returns_sane_result() { 
    replace_function(&malloc, &always_failing_malloc); 
    char *h = stringify('h'); 
    assert_string_equals("cannot_stringify", h); 
} 

Aunque la sección de descargas es un poco fuera de fecha, parece bastante desarrollado activamente - el autor ha solucionado un problema que tuve muy rápidamente. Puede obtener la versión más reciente (que he estado utilizando sin problemas) con:

svn checkout http://test-dept.googlecode.com/svn/trunk/ test-dept-read-only 

la versión no se actualizó en octubre de 2011.

Sin embargo el pasado, ya que el tropezar es achieved using assembler, puede ser necesario algún esfuerzo para que sea compatible con ARM.

+0

Usando este enfoque, usted todavía escribe sus pruebas en C puro, pero la estructura utiliza el ensamblaje. Siempre que esté en una plataforma compatible, no necesita comprender (o incluso ver) el ensamblaje. Pero sí, probablemente no se ajusta a "C puro" de extremo a extremo :) –

3

Un enfoque que he utilizado en el pasado que ha funcionado bien es el siguiente.

Para cada módulo C, publique una 'interfaz' que otros módulos pueden usar. Estas interfaces son estructuras que contienen punteros a funciones.

struct Module1 
{ 
    int (*getTemperature)(void); 
    int (*setKp)(int Kp); 
} 

Durante la inicialización, cada módulo inicializa estos punteros de función con sus funciones de implementación.

Cuando escribe las pruebas del módulo, puede cambiar dinámicamente estos punteros a sus implementaciones simuladas y, después de las pruebas, restaurar la implementación original.

Ejemplo:

void mocked_dummy(void) 
{ 
    printf("__%s__()\n",__func__); 
} 
/*---- do not modify ----*/ 
void dummyFn(void) 
{ 
    printf("__%s__()\n",__func__); 
} 
static void (*dummy)(void) = dummyFn; 
int factorial(int num) 
{ 
    int      fact = 1; 
     printf("__%s__()\n",__func__); 
    while (num > 1) 
    { 
     fact *= num; 
     num--; 
    } 
    dummy(); 
    return fact; 
} 

/*---- do not modify ----*/ 
int main(int argc, char * argv[]) 
{ 
    void (*oldDummy) = dummy; 

/* with the original dummy function */ 
    printf("factorial of 5 is = %d\n",factorial(5)); 

/* with the mocked dummy */ 
    oldDummy = dummy; /* save the old dummy */ 
    dummy = mocked_dummy; /* put in the mocked dummy */ 
    printf("factorial of 5 is = %d\n",factorial(5)); 
    dummy = oldDummy; /* restore the old dummy */ 
    return 1; 
} 
2

Puede reemplazar todas las funciones mediante el uso de LD_PRELOAD. Debes crear una biblioteca compartida, que LDGPR_CARGA carga. Esta es una función estándar utilizada para convertir programas sin soporte para SOCKS en SOCKS aware programs. Here es un tutorial que lo explica.

Cuestiones relacionadas