Реферат: API Spying
Проблема заключается в том, что (увы!) статистика собирается не магически, её собирает наш код, внедрённый в исследуемое приложение. У этого простого факта есть три неприятных следствия:
При добавлении сбора статистики изменится скорость работы функций. Обычно это ни на что не влияет, но если в середине критичного к скорости выполнения кода мы неожиданно (для приложения и его автора) начнём запись в файл, может получиться плохо. Например, FPS упадёт раз в десять :) Но FPS – это не страшно, страшно, если вы исследуете многопоточное приложение c некорректно написанной синхронизацией, и изменение времени выполнения потоков приведет к дедлокам, падениям или просто непонятному поведению.
Помимо процессорного времени, наш код использует и другие ресурсы: память, стек, (возможно) окна, объекты ядра (файлы, события, и т.п.) и другие. В некотором фантастическом случае это может стать последней каплей, приводящей к исчерпанию доступных ресурсов потоком, процессом, или даже системой.
Если автор приложения решил позаботиться о защите своего детища, он вполне в состоянии засечь наши манипуляции, обидеться (он же не знает, что мы ничего плохого не хотели) и сделать какую-нибудь бяку. Например, откажется работать, или станет работать неправильно, или отформатирует случайно выбранный диск…
Все эти следствия в той или иной степени свойственны любой программной реализации API Spying-а, и ни в одной из этих ситуаций я не могу посоветовать вам ничего хорошего. Можно только попытаться уменьшить степень влияния и избежать столь пагубных последствий.
Предпроектные исследования: функции в Intel x86
Как вы уже, наверное, поняли, нам предстоит динамическая генерация кода функций-шпионов. Хотя ничего особо сложного в этом не будет (они действительно очень простые), небольшое теоретическое введение поможет вам понять (а мне – объяснить), как должна быть написана функция-шпион, чтобы вызов отслеживаемой функции завершился без помех.
Вызов
С точки зрения процессора вызов функции выполняет инструкция call, имеющая несколько разных форм:
call xxxxxxh call xxxxh:xxxxxxh call eax call [eax] ... |
Она сохраняет в стеке адрес, по которому нужно передать управление после окончания функции, и передаёт управление на начало функции.
Передача параметров
Процессор Intel x86 ничего не знает о параметрах вызываемых функций, поэтому механизм передачи параметров может быть произвольным, главное чтобы вызывающий и вызываемый код договорились о нём заранее. Мест, где можно сохранить параметры, не так уж и много: либо в регистрах, либо в стеке, либо часть там, а часть там.
ПРИМЕЧАНИЕ Конечно, можно передавать параметры по ссылке или по значению, в прямом порядке или в обратном, но это для нас не важно, важно только то, где передаваемая информация (параметры или их адреса) находится. |
Передача параметров через регистры используется в основном в двух случаях:
Компилятором для оптимизации.
Ассемблер-программистом из лени или в погоне за производительностью. Чтобы достать параметры из стека, надо написать несколько дополнительных команд, а в регистрах они сразу под рукой.
В большинстве остальных случаев параметры передаются через стек. При этом вызов функции выглядит примерно так:
push ... ; Параметр push ... ; Ещё один параметр push ... ; И последний параметр call xxxxxh ; Вызов |
А стек к моменту начала выполнения функции – так: