Во время выполнения дразнить в C? - PullRequest
9 голосов
/ 08 марта 2012

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

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

Платформа / Среда?
Linux, ARM, gcc.

Подход, который я пробую?

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

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

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

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

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

Ответы [ 4 ]

5 голосов
/ 08 марта 2012

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 года.

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

3 голосов
/ 23 марта 2012

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

Для каждого модуля 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 голосов
/ 04 апреля 2012

Это вопрос, на который я пытался ответить сам.У меня также есть требование, чтобы метод / инструменты макетирования выполнялись на том же языке, что и мое приложение.К сожалению, это не может быть сделано в 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 вам может понадобиться использовать VirtualProtect ().

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

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

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

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

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

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

Chenz

2 голосов
/ 28 марта 2012

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

...