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