2012-05-04 4 views
6

У меня проблема с обходными узлами. Обход, как вы все знаете, может перемещаться только между 5 байтами пространства (например, «jmp» и 4-байтовый адрес). Из-за этого невозможно иметь функцию «hook» в классе (методе), вы не можете указать «этот» указатель, потому что просто недостаточно места (here's проблема более подробно объяснена). Таким образом, я весь день занимался мозговым штурмом для решения, и теперь мне нужны ваши мысли по этому вопросу, поэтому я не начинаю проект на 3-5 дней, не зная, возможно ли это или нет.C++ и FULLY динамические функции

У меня было 3 цели изначально, я хотел, чтобы функции «hook» были методами класса, я хотел, чтобы весь подход был объектно-ориентированным (без статических функций или глобальных объектов), а наихудшая/сложная часть полностью динамичный. Это мое (теоретически) решение; с сборкой можно изменять функции во время выполнения (идеальным примером является любой метод обхода). Так как я могу динамически изменять функции, не должен ли я также динамически их создавать? Например; Я выделяю память, скажем ~ 30 байт (через malloc/new). Невозможно было бы просто заменить все байты двоичными числами, соответствующими различным операторам сборки (например, 0xE9 - «jmp»), а затем вызвать адрес напрямую (поскольку он будет содержать функцию)?

ПРИМЕЧАНИЕ. Я знаю заранее, что возвращаемое значение и все аргументы для всех функций, которые я хочу объединить, и поскольку я использую GCC, соглашение thiscall практически идентично _cdecl.

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

Каждый аргумент - это описание аргументов, которые получит крючок (размер, и является ли это указателем или нет). Итак, допустим, я хочу создать класс Function для int * RandomClass::IntCheckNum(short arg1);. Тогда мне просто нужно было сделать вот так: Function func(Type(4, true), Type(4, true), Type(2, false));. Где «Тип» определяется как Type(uint size, bool pointer). Затем через сборку я мог динамически создавать функцию (обратите внимание: все это будет использовать соглашение о вызове _cdecl), так как я могу рассчитать количество аргументов и общий размер.

EDIT: В примере, Type(4, true) является возвращаемым значением (целое *), то scond Type(4, true) является RandomClass «это» указателем и Type(2, false) описывает первый аргумент (короткий arg1).

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

Так что я хотел знать; Это возможно? Сколько потребуется этой работы, и я нахожусь здесь над головой?

EDIT: Извините, если я представил все немного нечеткое, но если есть что-то, что вы хотите более подробно объяснить, спросите!

EDIT2: Я также хотел бы знать, могу ли я найти шестнадцатеричные значения для всех операторов сборки где-нибудь? Список поможет тонну! И/или если можно как-то «сохранить» asm («»); код по адресу памяти (который я очень сомневаюсь).

+0

Зачем использовать обходные пути? Не можете ли вы использовать чистое решение на C++, такое как 'std :: function', или я что-то упускаю? –

+0

Не так, как я мог помочь вам просто прояснить ситуацию. Вы хотите перезаписывать функцию в классе? (Я имею в виду, что вы можете изменить их во время выполнения). Если это так, я думаю, что (когда это будет сделано) возможно откроет гигантские возможности для программирования ИИ на языке C++. +1 – akaltar

+0

@akaltar Это известно как [генетическое программирование] (http://en.wikipedia.org/wiki/Genetic_programming) и на самом деле не нуждается в перезаписываемых функциях вообще. –

ответ

4

То, что вы описываете, обычно называется «thunking» и обычно используется. Исторически наиболее распространенной целью было сопоставление между 16-битным и 32-битным кодом (путем автогенерации новой 32-битной функции, которая вызывает существующий 16-разрядный или наоборот). Я считаю, что некоторые компиляторы C++ генерируют аналогичные функции для настройки указателей базового класса на указатели подкласса при множественном наследовании.

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

Вы можете найти эту ссылку полезно, особенно при работе в Win32: http://www.codeproject.com/Articles/16785/Thunking-in-Win32-Simplifying-Callbacks-to-Non-sta

Что касается нахождения шестнадцатеричных значений сборочных операций, лучшая ссылка, которую я знаю, это Приложение к руководству по ассемблеру NASM (и я не просто сказать, потому что я помог написать его). Там есть копия, доступная здесь: http://www.posix.nl/linuxassembly/nasmdochtml/nasmdoca.html

+0

Ничего себе отличные ссылки! Это было действительно интересное чтение о процессе thunking (слишком плохо было Win32, хотя). Теперь извините меня, если я говорю глупо, но, как упоминалось ранее, я не особенно разбираюсь в сборке (я знаю только немного синтаксиса AT & T), поэтому мне пришлось спросить об ассемблере NASM, на который вы ссылались. У меня есть 2 вопроса; все ли операторы ASM используют только 1 байт? А во-вторых, поскольку для каждого оператора задано несколько разных значений, на что я заинтересован? Я думаю, это зависит от размера моих переменных, но для «push» есть 13 разных значений, откуда я знаю, какой из них я хочу? –

+1

Это разные варианты для разных типов push-команд (виды регистра, непосредственные значения, ссылки косвенной памяти). В верхней части руководства есть описание всех различных режимов, поэтому используйте это, чтобы определить, какой из них вы хотите, а затем просто просмотрите список, чтобы найти нужный формат инструкции. Скажем, вы хотите нажать EBX: это reg32, поэтому вам нужен второй вариант, который является «o32 50 + r». o32 - префикс размера операнда, который игнорируется, если вы работаете в 32-битном коде; 50 + r - 50 hex плюс код для регистра (3, они указаны вверху), поэтому 53h - ваш код. – Jules

+1

В ответ на ваш первый вопрос нет инструкций, длина которых превышает один байт, а некоторые инструкции различаются по размеру в зависимости от контекста (см. Пример PUSH выше: префикс «o32» не генерирует никакого кода в 32 бит mode, однако, если вы создаете 16-битный код, это будет дополнительный 66-байтовый байт, который появляется в начале инструкции). Однако все наиболее распространенные инструкции - это один байт. – Jules