Вызов импортируемой функции, наивный способ.


Перевод с блога The Old New Thing. Оригинал здесь.

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

В 16-битном мире, это не создает проблем вовсе. Компилятор генерирует far call инструкцию и оставляет в объектном файле запись о том, что адрес вызываемой функции доложен быть записан компоновщиком. В то же время, компоновщик обнаруживает, что данная запись соответствует импортируемой функции, поэтому он собирает все места откуда происходит обращение к данной функции, объединяет их в связный список, и создает запись в таблице импорта данного модуля. Во время загрузки, при подстройке адресов, туда записываются фактические адреса функций и в итоге все счастливы.

Давайте посмотрим, как наивный 32-битный компилятор поступит в подобной ситуации. Компилятор сгенерирует обычную команду call, оставляя на компоновщик вопрос об адресе вызова. Компоновщик, в свою очередь, видит, что вызываемая функция на самом деле является импортируемой и… опаньки! прямой вызов необходимо заменить на косвенный. Но компоновщик не может изменять код, сгенерированный компилятором. Так что же он сделает?

Решение заключается в том, чтобы ввести еще один уровень косвенности. (Предупреждение: дальнейшая информация не соответствует действительности в точности, но она недалеко от истины. Мы рассмотрим детали позднее.)

Для каждой экспортируемой функции в библиотеке импорта генерируются два внешних символьных имени. Первое используется для обращения к таблице импортируемых функций, и выглядит в виде __imp__FunctionName. Очевидно, наивный компилятор не знает об этом чудесном префиксе __imp__. Он просто генерирует код для инструкции call FunctionName и ожидает, что компоновщик подставит туда адрес.

Вот для этого и существует второй символ. Это и есть тот долгожданный символ FunctionName, функция, состоящая из одной команды – jmp [__imp__FunctionName]. Эта крошечная функция-заглушка соответствует внешней ссылке FunctionName и, в свою очередь, создает внешнюю ссылку на __imp__FunctionName, которая разрешается этой же библиотекой импорта указанием на запись в таблице импорта.

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

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

В следующий раз мы рассмотрим директиву dllexport, а также то, как более умный компилятор генерирует код для импортируемой функции.

Реклама
Запись опубликована в рубрике DLL. Добавьте в закладки постоянную ссылку.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s