Выполнение кода в mmap для создания ошибок в исполняемом коде - PullRequest
0 голосов
/ 04 мая 2018

Я пытаюсь написать функцию, которая копирует функцию (и в конечном итоге изменяет ее сборку) и возвращает ее. Это прекрасно работает для одного уровня косвенности, но в два я получаю сегфо.

Вот минимальный (не) рабочий пример:

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

#define BODY_SIZE 100

int f(void) { return 42; }
int (*G(void))(void) { return f; }
int (*(*H(void))(void))(void) { return G; }

int (*g(void))(void) {
    void *r = mmap(0, BODY_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    memcpy(r, f, BODY_SIZE);
    return r;
}

int (*(*h(void))(void))(void) {
    void *r = mmap(0, BODY_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    memcpy(r, g, BODY_SIZE);
    return r;
}

int main() {
    printf("%d\n", f());
    printf("%d\n", G()());
    printf("%d\n", g()());
    printf("%d\n", H()()());
    printf("%d\n", h()()()); // This one fails - why?

    return 0;
}

Я могу использовать memcpy в области mmap один раз, чтобы создать допустимую функцию, которую можно вызывать (g()()). Но если я попытаюсь применить его снова (h()()()), он будет сбоем. Я подтвердил, что он правильно создает скопированную версию g, но когда я запускаю эту версию, я получаю ошибку segfault.

Есть ли причина, по которой я не могу выполнить код в одной области mmap из другой области mmap? Из исследовательских операций gdb с проверками x/i кажется, что я могу успешно вызвать вызов, но при возврате функция, с которой я пришел, была стерта и заменена на 0.

Как мне заставить это поведение работать? Это вообще возможно?

БОЛЬШОЕ РЕДАКТИРОВАНИЕ:

Многие спрашивали о моем обосновании, поскольку я, очевидно, здесь занимаюсь проблемой XY. Это правда и намеренно. Видите ли, чуть менее месяца назад этот вопрос был размещен на бирже стека кодов гольфа. Он также получил неплохую награду за решение C / Assembly. Я немного задумался над этой проблемой и понял, что, копируя тело функции, одновременно заглушая адрес с каким-то уникальным значением, я могу искать в его памяти это значение и заменять его действительным адресом, что позволяет мне эффективно создавать лямбда-функции, которые взять один указатель в качестве аргумента. Используя это, я мог бы работать с одним карри, но мне нужно более общее карри. Таким образом, мое текущее частичное решение связано здесь . Это полный код, показывающий ошибку, которую я пытаюсь избежать. Хотя это в значительной степени определение плохой идеи, я нахожу ее интересной и хотела бы знать, жизнеспособен ли мой подход или нет. Единственное, чего мне не хватает - это возможности запустить функцию, созданную из функции, но я не могу заставить это работать.

Ответы [ 3 ]

0 голосов
/ 04 мая 2018

Я пытаюсь написать функцию, которая копирует функцию

Я думаю, что это прагматически неправильный подход, если вы не очень хорошо знаете машинный код для вашей платформы (и тогда вы не зададите вопрос). Помните о позиционно-независимом коде (полезно, потому что в общем случае mmap (2) будет использовать ASLR и даст некоторую "случайность" в адресах). Кстати, подлинный самоизменяющийся машинный код (т. Е. Изменение некоторых байтов существующего действительного машинного кода) сегодня кеш и предсказатель ветвления недружелюбен и его следует избегать на практике.

Я предлагаю два связанных подхода (выберите один из них).

  • Создайте некоторый временный C файл (см. Также this ), например, в /tmp/generated.c, затем разветвите компиляцию, используя gcc -Wall -g -O -fPIC /tmp/generated.c -shared -o /tmp/generated.so, в плагин , затем dlopen (3) (для динамическая загрузка ), что /tmp/generated.so плагин общего объекта (и, вероятно, используйте dlsym (3) , чтобы найти в нем указатели на функции ...). Подробнее об общих объектах читайте в статье Дреппера Как писать общие библиотеки . Сегодня вы можете dlopen многих сотен тысяч таких общих библиотек (см. Мой пример manydl.c ) и компиляторов C (например, недавние GCC ) достаточно быстрыми, чтобы скомпилировать несколько тысячи строк кода за время, совместимое с взаимодействием (например, менее одной десятой секунды). Генерация кода на C - это широко используемая практика. На практике вы бы представляли некоторую AST в памяти сгенерированного кода C перед его излучением.

  • Используйте некоторую библиотеку JIT , такую ​​как GCCJIT , или LLVM , или libjit , или asmjit и т. д ...., который генерирует функцию в памяти, выполняет необходимые перемещения и дает вам некоторый указатель на нее.

Кстати, вместо кодирования на C, вы можете рассмотреть возможность использования некоторой гомо-логической языковой реализации (например, SBCL для Common Lisp, которая компилируется в машинный код при каждом REPL взаимодействие или любое динамически создаваемое S-expr представление программы).

Понятия замыканий и обратных вызовов стоит знать. Прочитайте SICP и, возможно, Lisp In Small Pieces (и, конечно, Dragon Book , для общей культуры компилятора).

0 голосов
/ 05 мая 2018

этот вопрос был опубликован в коде golf.SE

Я обновил ответ 1686-битного гольф-кода 8086 на вопрос о суммировании аргументов, включив в него разборку с комментариями.

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

В обычных соглашениях о вызовах первый аргумент передается последним, поэтому вы не можете просто добавить еще один push imm32 перед трейлером фиксированного размера call target / leave / ret. Если вы пишете чистый asm-ответ, вы можете использовать альтернативное соглашение о вызовах, в котором аргументы помещаются в другом порядке. Или вы можете иметь вступление фиксированного размера, а затем постоянно растущую последовательность push imm32 + call / left / ret.

Сама функция каррирования может использовать соглашение о вызовах register-arg, даже если вы хотите, чтобы целевая функция использовала i386 System V, например (аргументы стека).

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

Учитывая то, как написан новый вызов code-golf, я думаю, вы бы сравнили общее количество аргументов с карри и количество аргументов, которые принимает целевая функция ввода.


Я не думаю, что есть шанс, что вы сможете сделать эту работу в чистом C, просто memcpy; Вы должны изменить машинный код.

0 голосов
/ 04 мая 2018

Код использует относительные вызовы для вызова mmap и memcpy, поэтому скопированный код в итоге вызывает неверное местоположение.

Вы можете вызывать их через указатель, например ::100100

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

#define BODY_SIZE 100

void* (*mmap_ptr)(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset) = mmap;
void* (*memcpy_ptr)(void *dest, const void *src, size_t n) = memcpy;

int f(void) { return 42; }
int (*G(void))(void) { return f; }
int (*(*H(void))(void))(void) { return G; }

int (*g(void))(void) {
    void *r = mmap_ptr(0, BODY_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    memcpy_ptr(r, f, BODY_SIZE);
    return r;
}

int (*(*h(void))(void))(void) {
    void *r = mmap_ptr(0, BODY_SIZE, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    memcpy_ptr(r, g, BODY_SIZE);
    return r;
}

int main() {
    printf("%d\n", f());
    printf("%d\n", G()());
    printf("%d\n", g()());
    printf("%d\n", H()()());
    printf("%d\n", h()()()); // This one fails - why?

    return 0;
}
...