Ладно, я предполагаю, что здесь есть сторона Windows. Когда вы загружаете PE-файл, происходит следующее: загрузчик (содержащийся в NTDLL) будет делать следующее:
- Найдите каждую из DLL, используя семантику поиска DLL (специфичную для системы и уровня исправления), известные библиотеки DLL отчасти освобождены от этого
- Отображение файла в память (MMF), где страницы копируются при записи (CoW)
- Перейдите в каталог импорта и для каждого начала импорта (рекурсивно) в точке 1.
- Разрешить перемещения, которые в большинстве случаев представляют собой лишь очень ограниченное число объектов, поскольку сам код является позиционно-независимым кодом (PIC)
- (IIRC) исправляет EAT от RVA (относительный виртуальный адрес) до VA (виртуальный адрес в текущем пространстве памяти процесса)
- Исправление IAT (таблица адресов импорта) для ссылки на импорт с их фактическим адресом в пространстве памяти процесса
- Для вызова DLL
DLLMain()
для EXE создайте поток, начальный адрес которого находится в точке входа PE-файла (это также упрощено, поскольку фактический начальный адрес находится внутри kernel32.dll для процессов Win32)
Теперь, когда вы компилируете код, это зависит от компоновщика, на который ссылается внешняя функция. Некоторые компоновщики создают заглушки, так что - теоретически - попытка проверить адрес функции по NULL всегда скажет, что это не NULL. Это странная вещь, о которой вы должны знать, если и когда ваш компоновщик затронут. Другие ссылаются на запись IAT напрямую, и в этом случае адрес функции без ссылки (например, DLL с задержкой загрузки) может иметь значение NULL, а обработчик SEH затем вызовет помощника с задержкой загрузки и (попытается) разрешить адрес функции перед возобновлением выполнения в указать это не удалось.
В вышеописанном процессе задействовано много красной ленты, которую я упрощенно назвал.
Суть того, что вы хотели знать, заключается в том, что отображение в процессе происходит как MMF , хотя вы можете искусственно имитировать поведение с кучей пространства. Однако, если вы помните пункт о CoW, в этом суть идеи DLL. Фактически, та же самая копия (большей части) страниц библиотеки DLL будет совместно использоваться процессами, которые загружают определенную библиотеку DLL. Страницы, которые не являются общими, - это те, на которые мы писали, например, при разрешении перемещений и подобных вещей. В этом случае каждый процесс имеет - теперь измененную - копию исходной страницы.
И предупреждение относительно упаковщиков EXE на DLL. Они побеждают именно этот механизм CoW, который я описал, поскольку они выделяют пространство для распакованного содержимого DLL в куче процесса, в который загружается DLL. Таким образом, хотя фактическое содержимое файла по-прежнему отображается как MMF и используется совместно, распакованное содержимое занимает один и тот же объем памяти для каждого процесса, загружающего DLL, вместо того, чтобы делиться этим.