Windows для профессионалов


Статические данные разделяются несколькими экземплярами EXE или DLL


По умолчанию для большей безопасности глобальные и статические данные нс разделя ются несколькими проекциями одного и того же EXE или DLL. Но иногда удобнее, чтобы несколько проекций EXE разделяли единственный экземпляр переменной. Например, в Windows не так-то просто определить, запущено ли несколько экземп ляров приложения. Если бы у Вас была переменная, доступная всем экземплярам при ложения, она могла бы отражать число этих экземпляров Тогда при запуске нового экземпляра приложения его поток просто проверил бы значение глобальной пере менной (обновленное другим экземпляром приложения) и, будь оно больше 1, сооб щил бы пользователю, что запустить можно лишь один экземпляр; после чего эта копия приложения была бы завершена.

В этом разделе мы рассмотрим метод, обеспечивающий совместное использова ние переменных всеми экземплярами EXE или DLL. Но сначала Вам понадобятся кое какие базовые сведения.

Любой образ EXE- или DLL-файла состоит из группы разделов. По соглашению имя каждого стандартного раздела начинается с точки Например, при компиляции про граммы весь код помещается в раздел .text, неинициализированные данные - в раз дел .bss, а инициализированные — в раздел .data.

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

Атрибут Описание
READ

Разрешает чтение из раздела
WRITE Разрешает запись в раздел
EXECUTЕ Содержимое раздела можно исполнять
SHARED Раздел доступен нескольким экземплярам приложения (этот атрибут отклю чает механизм копирования при записи)

Запустив утилиту DumpBin из Microsoft Visual Studio (c ключом /Headers), Вы уви дите список разделов в файле образа EXE или DLL Пример такого списка, показан ный ниже, относится к ЕХЕ-файлу.

SECTION HEADER #1 text name 11A70 virtual size 1000 virtual address 12000 size of raw data 1000 file pointer to raw data

0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 60000020 flags Code Execute Read


SECTION HEADER #2 rdata name

1F6 virtual size

13000 virtual address 1000 size of raw data 13000 file pointer to raw data

0 file poinLer lo relocation tabie 0 file pointer to line numbers 0 number ot relocations 0 number of line numbers 40000040 flags

Initialized Data Read Only

SECTION HEADER #3 .data name

560 virtual size 14000 virtual address 1000 size of raw data 14000 file pointer to raw data

0 filc pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers C0000040 flags

Initialized Data Read Write

SECTION HtADER #4 .idata name

58D virtual size 15000 virtual address 1000 size of raw data 15000 file pointer to raw data

0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers C0000040 flags

Initialized Data Read Write

SECTION HEADER #5 .didat name

7A2 vi rtual size 16000 virtual address 1000 size of raw data 16000 file pointer to raw data

0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers C0000040 flags

Initialized Data Read Write

SECTION HEADER #6 .reloc name

26D virtual size 17000 virtual address 1000 size of raw data 17000 file pointer to raw data

0 file pointer to relocation table 0 file pointer to line numbers 0 number of relocations 0 number of line numbers 42000040 flags

Initialized Data Discardable Read Only

Summary

1000 data 1000 didat 1000 idata 1000 rdata 1000 .reloc 12000 text

Некоторые из часто встречающихся разделов перечислены в таблице ниже

Имя раздела Описание
bss Неинициализированные данные
CRT Неизменяемые данные библиотеки С
data Инициализированные данные
.debug Отладочная информация
.didat Таблица имен для отложенного импорта (delay imported names table)
edata Таблица экспортируемых имен
idata Таблица импортируемых имен
.rdata Неизменяемые данные периода выполнения
.reloc Настроечная информация — таблица переадресации (relocation table)
.rsrc Ресурсы
.text Код ЕХЕ или DLL
.tls Локальная память потока
.xdata Таблица для обработки исключений
<


Кроме стандартных разделов, генерируемых компилятором и компоновщиком, можно создавать свои разделы в EXE- или DLL-файле, используя директиву компи лятора:

#pragma data_seg("имя_раздела")

Например, можно создать раздел Shared, в котором содержится единственная пе ременная типа LONG:

#pragma data_seg("Shared") LONG g_lInstanceCount = 0;
#pragma data_seg()

Обрабатывая этот код, компилятор создаст раздел Shared и поместит в него все инициализированные переменные, встретившиеся после директивы #pragma. В нашем примере в этом разделе находится переменная g_lInstanceCount. Директива #pragma data_seg() сообщает компилятору, чти следующие за ней переменные нужно вновь помещать в стандартный раздел данных, а нс в Shared. Важно помнить, что компиля тор помещает в новый раздел только инициализированные переменные. Если из пре дыдущего фрагмента кода исключить инициализацию переменной, она будет вклю чена в другой раздел:

#pragma data_seg("Shared") LONG g_lInslanceCount;
#pragma data_seg()

Однако в компиляторе Microsoft Visual C++ 6.0 предусмотрен спецификатор allo cate, который позволяет помещать неинициализированные данные в любой раздел. Взгляните на этот код:

// создаем раздел Shared и заставляем компилятор
// поместить в него инициализированные данные
#pragma data_seg("Shared")

// инициализированная переменная, по умолчанию помещается в раздел Shared
int а = 0;

// неинициализированная переменная, по умолчанию помещается в другой раздел
int b;

// сообщаем компилятору прекратить включение инициализированных данных
// в раздел Shared
#pragma data_seg()

// инициализированная переменная, принудительно помещается в раздел Shared
__declspec(allocate("Shared")) int с = 0;

// неинициализированная переменная, принудительно помещается в раздел Shared
__declspec(allocate("Shared")) int d;

// инициализированная переменная, по умолчанию помещается в другой раздел
int e = 0;

// неинициализированная переменная, по умолчанию помещается в другой раздел


int f;

Чтобы спецификатор allocate работал корректно, сначала должен быть создан соответствующий раздел. Так что, убрав из предыдущего фрагмента кода первую стро ку #pragma data_seg, Вы нс смогли бы его скомпилировать.

Чаще всего переменные помещают в собственные разделы, намереваясь сделать их разделяемыми между несколькими проекциями EXE или DLL. По умолчанию каж дая проекция получает свой набор переменных. Но можно сгруппировать в отдель ном разделе переменные, которые должны быть доступны всем проекциям EXE или DLL; тогда система не станет создавать новые экземпляры этих переменных для каж дой проекции EXE или DLL.

Чтобы переменные стали разделяемыми, одного указания компилятору выделить их в какой-то раздел мало. Надо также сообщить компоновщику, что переменные в

этом разделе должны быть общими Для этого предназначен ключ /SECTION компоновщика

/SECTION:имя,атрибуты

За двоеточием укажите имя раздела, атрибуты которого Вы хотите изменить В нашем примере нужно изменить атрибуты раздела Shared, поэтому ключ должен выг лядеть так

/SECTION:Shared,RWS

После запятой мы задаем требуемые атрибуты При этом используются такие со кращения R (READ), W (WRITE), E (EXECUTE) и S(SHARED) В данном случае мы ука чали, что раздел Shared должен быть "читаемым", "записываемым" и "разделяемым" Если Вы хотите изменить атрибуты более чем у одного раздела, указывайте ключ /SECTION для каждого такого раздела

Соответствующие директивы для компоновщика можно вставлять прямо в исходный код

#pragma comment(linker, /SECTION Shared,RWS )

Эта строка заставляет компилятор включить строку "/SECTION Shared,RWS" в осо бый раздел drectve Компоновщик, собирая OBJ-модули, проверяет этот раздел в каж дом OBJ-модуле и действует так, словно все эти строки переданы ему как аргументы в командной строке Я всегда применяю этот оченъ удобный метод перемещая файл исходного кода в новый проект, не надо изменять никаких параметров в диалоговом окне Project Settings в Visual C++



Хотя создавать общие разделы можно, Microsoft нс рекомендует это делать Во первых, разделение памяти таким способом может нарушить защиту Во вторых, на личие общих переменных означает, что ошибка в одном приложении повлияет на другое, так как этот блок данных нс удастся защитить от случайной записи

Представьте, Вы написали два приложения, каждое из которых требует от пользо вателя вводить пароль При этом Вы решили чуть-чуть облегчить жизнь пользовате лю если одна из программ уже выполняется на момент запуска другой, то вторая счи тывает пароль из общей памяти Так что пользователю не пужно повторно вводить пароль, если одно из приложений уже запущено

Все выглядит вполне невинно В конце концов только Ваши приложения загружа ют данную DLL, и только они знают, где искать пароль, содержащийся в общем разде ле памяти Но хакеры не дремлют, и если им захочется узнать Ваш пароль, то макси мум, что им понадобится, — написать небольшую программу, загружающую Вашу DLL, и понаблюдать за общим блоком памяти Когда пользователь введет пароль, хакере кая программа тут же ею узнает

Трудолюбивая хакерская программа может также предпринять серию попыток угадать пароль, записывая его варианты в общую память А угадав, сможет посылать любые команды этим двум приложениям Данную проблему можно было бы решить, если бы существовал какой-нибудь способ разрешать загрузку ULL только определен ным программам Но пока эю невозможно — любая протрамма, вызвав IoadLibrary, способна явно загрузить любую DLL


Содержание раздела