C: перезаписать другую функцию побайтно - PullRequest
6 голосов
/ 26 января 2012

Предположим, у меня есть функция:

int f1(int x){
 // some more or less complicated operations on x
 return x;
}

А это у меня другая функция

int f2(int x){
 // we simply return x
 return x;
}

Я бы хотел сделать что-то вроде следующего:

char* _f1 = (char*)f1;
char* _f2 = (char*)f2;
int i;
for (i=0; i<FUN_LENGTH; ++i){
 f1[i] = f2[i];
}

т.е. Я хотел бы интерпретировать f1 и f2 как необработанные байтовые массивы и «перезаписывать f1 байт за байтом» и, таким образом, заменить его на f2.

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

Итак, мой подход возможен в принципе? Или есть какие-то проблемы, связанные с машиной / реализацией / какими-либо зависимыми, которые я должен принимать во внимание?

Ответы [ 4 ]

8 голосов
/ 26 января 2012

Было бы проще заменить первые несколько байтов f1 машинной jump инструкцией к началу f2. Таким образом, вам не придется иметь дело с любыми возможными проблемами перемещения кода.

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

Для x86 требуемый код операции инструкции относительного перехода E9 (согласно здесь ). Это 32-битный относительный переход, который означает, что вам нужно вычислить относительное смещение между f2 и f1. Этот код может сделать это:

int offset = (int)f2 - ((int)f1 + 5); // 5 bytes for size of instruction
char *pf1 = (char *)f1;
pf1[0] = 0xe9;
pf1[1] = offset & 0xff;
pf1[2] = (offset >> 8) & 0xff;
pf1[3] = (offset >> 16) & 0xff;
pf1[4] = (offset >> 24) & 0xff;

Смещение берется из end инструкции JMP, поэтому при вычислении смещения к адресу f1 добавляется 5.

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

1 голос
/ 26 января 2012

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

int complex_implementation(int x) { 
    // do complex stuff with x
    return x;
}

int simple_implementation(int x) { 
   return x;
}

int (*f1)(int) = complex_implementation;

Вы бы использовали это что-то вроде:

for (int i=0; i<limit; i++) {
    a = f1(a);
    if (whatever_condition)
        f1 = simple_implementation;
}

... и после назначения вызов f1 просто вернет введенное значение.

Вызов функции через указатель налагает некоторые накладные расходы, но (благодаря тому, что это распространено в ОО-языках), большинство компиляторов и процессоров довольно хорошо справляются с минимизацией этих накладных расходов.

1 голос
/ 26 января 2012

Ваш подход - неопределенное поведение для стандарта C.

И во многих операционных системах (например, Linux) ваш пример будет зависать: код функции находится внутри сегмента (и раздела) только для чтения .text исполняемого файла ELF, и этот сегмент (вроде как) mmap -ed-только для чтения execve (или dlopen или динамическим компоновщиком), поэтому вы не можете писать внутри него.

0 голосов
/ 26 января 2012

Большинство архитектур памяти не позволит вам писать поверх кода функции.Это может привести к сбою ... Но на некоторых встроенных устройствах вы можете делать такие вещи, но это опасно, если вы не знаете, что места достаточно, вызов будет в порядке, стек будет в порядке, и т. Д. И т. Д...

Скорее всего, есть ПУТЬ лучший способ решить проблему.

...