2012-03-08 5 views
9

Это последнее время в моем списке. Короче говоря, мне нужно запустить mocked_dummy() вместо dummy()ON RUN-TIME, без изменения factorial(). Меня не интересует точка входа в программное обеспечение. Я могу добавить любое количество дополнительных функций (но не могу изменить код в пределах /*---- do not modify ----*/).Время исполнения в C?

Зачем мне это нужно?
Выполнение модульных тестов некоторых устаревших модулей C. Я знаю, что есть много инструментов, доступных во всем, но если можно было бы насмехаться во время выполнения, я могу изменить мой подход к UT (добавить многоразовые компоненты), сделать мою жизнь проще :).

Платформа/Окружающая среда?
Linux, ARM, gcc.

Подход, с которым я пытаюсь?

  • Я знаю, что GDB использует ловушку/незаконные инструкции для сложения контрольных точек (gdb internals).
  • Сделайте код самомодифицируемым.
  • Заменить dummy() сегмент кода с незаконной инструкцией и return в качестве следующей следующей инструкции.
  • Управление передачей в ловушку.
  • Trap handler - это функция многократного использования, которая читается из сокета домена unix.
  • Адрес функции mocked_dummy() передается (читается из файла карты).
  • Выполнение функции фрейма.

Есть проблемы, идущие отсюда. Я также нашел подход утомительным и требует хорошего количества кодирования, некоторые в сборе тоже.

Я также нашел, под gcc каждый вызов функции может быть hooked/instrumented, но опять же не очень полезно, так как функция предназначена для издевательства, в любом случае будет выполнена.

Есть ли другой подход, который я мог бы использовать?

#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; 
} 

ответ

2

Это вопрос, который я пытался ответить самому себе. У меня также есть требование, чтобы я хотел, чтобы насмешливый метод/инструменты выполнялись на том же языке, что и мое приложение. К сожалению, это невозможно сделать на C портативным способом, поэтому я прибег к тому, что вы можете назвать батутом или обходным. Это подпадает под «Сделать код самомодифицируемым». подход, упомянутый выше. Это было изменение фактических байтов функции на времени выполнения, чтобы перейти к нашей функции макета.

#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; 
} 

Переносимость объяснение ...

mprotect() - Изменяет права доступа страницы памяти, так что мы можем на самом деле писать в память, которая содержит код функции. Это не очень портативно, и в WINAPI env вам может потребоваться использовать VirtualProtect().

Параметр памяти для mprotect выровнен по сравнению с предыдущей страницей 4k, это также может изменяться с системы на систему, 4k подходит для ядра vanilla linux.

Метод, который мы используем для jmp для функции mock, состоит в том, чтобы фактически записать наши собственные коды операций, это, вероятно, самая большая проблема с переносимостью, потому что код операции, который я использовал, будет работать только на небольшом endian x86 (большинство настольных компьютеров). Таким образом, это нужно будет обновить для каждой арки, которую вы планируете запустить (что может быть полупростым для работы в макросах CPP.)

Сама функция должна быть не менее пяти байтов. Обычно это так, потому что каждая функция обычно имеет не менее 5 байтов в своем прологе и эпилоге.

Потенциальные улучшения ...

set_mock_run() вызов может быть легко настроен, чтобы принять параметры для повторного использования. Кроме того, вы можете сохранить пять перезаписанных байтов из исходной функции, чтобы восстановить код позже, если хотите.

Я не могу проверить, но я читал, что в ARM ... вы бы сделали подобное, но вы можете перейти к адресу (а не смещению) с помощью кода операции ветвления ... который для безусловного ветви, у вас будут первые байты 0xEA, а следующие 3 байта - это адрес.

Chenz

+0

Будет ли приведенный выше код компилироваться, как есть? –

+0

Обновленный код, поэтому он строит. –

+0

Я сомневался больше в этом утверждении '#define dummy (m.dummy)' Как бы вы запретили препроцессор НЕ заменять фиктивную функцию? –

5

test-dept относительно недавно система тестирования блок C, что позволяет выполнять во время выполнения гася функций. Я нашел его очень прост в использовании - вот пример из их документации:

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

Хотя раздел загрузки немного устарел, он кажется довольно активно развивается - автор исправил вопрос, который я имел очень быстро. Вы можете получить самую последнюю версию (которую я использую без проблем) с:

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

версии была обновлена ​​в октябре 2011 года

Однако, поскольку раскорчевка является achieved using assembler, возможно, потребуется некоторые усилия, чтобы заставить его поддерживать ARM.

+0

Используя этот подход, вы все еще пишете свои тесты в чистом C, но структура использует сборку. Пока вы находитесь на поддерживаемой платформе, вам фактически не нужно понимать (или даже видеть) сборку. Но да, это, вероятно, не соответствует «чистым C» от ​​конца до конца :) –

3

Подход, который я использовал в прошлом, который хорошо работал, заключается в следующем.

Для каждого модуля C опубликуйте «интерфейс», который могут использовать другие модули. Эти интерфейсы представляют собой структуры, содержащие указатели на функции.

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

Во время инициализации каждый модуль инициализирует эти указатели функций своими функциями реализации.

Когда вы пишете тесты модулей, вы можете динамически изменять эти указатели на свои макетные реализации и после тестирования восстановить исходную реализацию.

Пример:

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

Вы можете заменить каждую функцию при использовании LD_PRELOAD. Вы должны создать общую библиотеку, которая загружается LD_PRELOAD. Это стандартная функция, используемая для превращения программ без поддержки SOCKS в SOCKS aware programs. Here - это учебник, который объясняет это.

Смежные вопросы