У меня несколько вопросов. Ответте кто нибудь хотя бы на один. Это относительно Windows95.
1. Если в процессе несколько раза вызывается LoadLibrary("xx.dll") а FreeLibrary() на 1 раз меньше и процесс завершается, то останется ли xx.dll в памяти?
2. Какова политика Microsoft по этому вопросу.
3. Есть ли нормы на секцию импорта для PE формата, разумеется не те что в winnt.h,
а все допустимые варианты.
4. Почему Microsoft загружает Advapi32.dll для некоторых "своих" dll
(например rnaui.dll) не по базовому адресу, а ниже приблизительно на 0x10000
байт и их, "свои" dll, боундирует изначально?
Здравствуйте ua1zcl, Вы писали:
U>1. Если в процессе несколько раза вызывается LoadLibrary("xx.dll") а FreeLibrary() на 1 раз меньше и процесс завершается, то останется ли xx.dll в памяти?
U>2. Какова политика Microsoft по этому вопросу.
Когда завершается процесс выгружаются все используемые им dll и разрушается адресное пространство процесса. Этот процесс не зависит от количества вызовов LoadLibrary/FreeLibrary.
U>3. Есть ли нормы на секцию импорта для PE формата, разумеется не те что в winnt.h,
U> а все допустимые варианты.
Не очень понятен вопрос. Секция импорта описана в различных описаниям PE-формата (например, от Hard Wisdom). И не понятно, чем Вам не угодил winnt.h.
С другой стороны есть некоторые нюансы, которые всплывают при детальном рассмотрении реальных exe-шников, слинкованных различными линкерами. Я сравнивал два: vc++, borland cpp. Что меня сильно удивило, так это то, что компании (и соответственно их линкеры) абсолютно различно понимают значения некоторых полей в PE-заголовке (!). Например, можете сравнить пару полей SizeOfRawData и Misc.VirtualSize в структуре IMAGE_SECTION_HEADER.
Такие же проблемы есть и в
реальной секции импорта. Ключевая часть секции — это список структур IMAGE_IMPORT_DESCRIPTOR, которая заканчивается нулем.
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
};
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
Поле Name структуры ссылается на имя dll (например, "kernel32.dll"). Поле OriginalFirstThunk ссылается на массив структур IMAGE_THUNK_DATA.
typedef struct _IMAGE_THUNK_DATA {
union {
DWORD ForwarderString; // PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA;
Каждая ячейка в этом массиве — это описание импортируемой функции. Функции могут импортироваться разными способами. Например, по имени. Это самый распространенный способ. При этом IMAGE_THUNK_DATA.u1.AddressOfData указывает на структуру вида:
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
BYTE Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
Где Hint — это номер функции, а Name — имя. Например,
Hint = 0x22C, Name = "ResumeThread"
Также функции могут импортироваться по ordinal (по номеру). Тогда IMAGE_THUNK_DATA.u1.Ordinal имеет установленный старший бит, а младшее слово содержит сам ordinal импотрируемой функции.
И т.п. Остальные варианты можно посмотреть в описаниях. Это не главное.
Вернемся к структуре IMAGE_IMPORT_DESCRIPTOR. Поле OriginalFirstThunk ссылается на массив, содержащий описание импортируемых функций. Поле FirstThunk ссылается на точно такой же массив такого же размера. Но заполняется он во время загрузки реальными адресами функций. Т.е. загрузчик пробегает массив OriginalFirstThunk, для каждого его элемента определяет реальный адрес функции и записывает этот адрес в массив FirstThunk.
Так должно быть в идеале, и так обычно и бывает. Обычно, но не всегда. Мне встречались exe-шники, в которых не было массива OriginalFirstThunk (!) (если не изменяет память, то это Delphi так портачит). Там исходный массив лежит все в том же массиве FirstThunk, значения которого после загрузки заменяются адресами функций.
Все конечно понятно — минимизация памяти, но это выходит за рамки спецификации. И главное — из-за этого не правильно функционируют некоторые программы pedump-еры. Т.к. в Win32 SDK написано, что массив структур IMAGE_IMPORT_DESCRIPTOR следует просматривать, пока поле OriginalFirstThunk (Characteristics) не равно 0. А в Delphi-программе это поле у всех записей равно 0.
И таких мелочей встречается в PE-формате достаточно много. И описания, о которых говорилось выше, об этом молчат (или мне не удалось найти иного).
Ну а настоящие "нормы на секцию импорта для PE формата" имхо можно определить только дизассемблировав виндузовый PE загрузчик.
U>4. Почему Microsoft загружает Advapi32.dll для некоторых "своих" dll
U>(например rnaui.dll) не по базовому адресу, а ниже приблизительно на 0x10000
U>байт и их, "свои" dll, боундирует изначально?
Загрузка по адресу, отличному от базового — это сродни ошибке. Если это и случается у Microsoft, то явно не нарочно. Это происходит в том случае, если несколько подгружаемых dll претендуют на один и тот же диапазон адресов адресного пространства. Часто это случается, если несколько dll имеют одинаковые базовые адреса. Microsoft обычно следит, чтобы такого не происходило (по крайней мере для системных dll).
Ну а связывание (bind) делается для ускорения загрузки приложения. Как выше было рассказано, загрузчику достается достаточно много работы при загрузке pe-файла. Он должен просмотреть секции импорта всех модулей, для каждой функции определить ее адрес, что является не особо быстрой процедурой.
Возникает вопрос: зачем загрузчику выполнять эту процедуру для системных библиотек? Например, зачем при загрузке kernel32.dll каждый раз заново определять адреса всех импортируемых ей функций из ntdll.dll? Ведь они уже и так разнесены на разные бызовые адреса (т.е. при загрузке не будет необходимости совершать перемещение), адреса функцих постоянны и заранее известны.
Поэтому приняли гениальное решение — в массив IMAGE_IMPORT_DESCRIPTOR.FirstThunk заранее занесли адреса функций и специальным образом пометили, что мол этот IMAGE_IMPORT_DESCRIPTOR уже связан (bound).
Загрузчику остается ерунда — загрузить все необходимые модули.
Конечно, это привязывает pe-файл к конкретной версии другого, что не подходит, скажем, для привязки Вашего приложения к kernel32.dll, т.к. очевидно приложение должно работать на разных версиях операционной системы. Но это отлично подходит для системых dll, которые всегда идут вместе.