15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту

Память в Win32.

Директива #pragma data_seg().

(c) И.В. Радинский, 06.10.1999 г.


 

Как-то раз, передо мной встала проблема - у меня была куча EXE- и DLL-файлов, входящих в одну большую систему. Как водится в больших системах, у нее были одни глобальные настройки и объекты, которые использовали те или иные модули одновременно. Настройки - это не проблема ведь можно просто хранить их в одном файле, который читают все модули. Проблема в объектах - я захотел создать группу разделяемых объектов по работе с БД, и чтобы все модули видели их, ну и заодно можно было бы хранить и общие настройки. Я хотел это реализовать в одной DLL, которую загружали бы модули, и получали бы, таким образом, доступ к этим общим данным.

Проблема нетривиальная - ведь в Win32 каждый процесс имеет свое собственное адресное пространство в 4 Гб, и это все, что он "видит". Если процесс загружает DLL, то ее код проецируется в его адресное пространство и она (DLL) при этом теряет почти всю свою индивидуальность: для потоков код и данные DLL - просто дополнительный код и данные, оказавшиеся в адресном пространстве процесса. Когда поток вызывает из DLL какую-то функцию, та считывает свои параметры из стека потока и размещает в этом стеке собственные локальные переменные. Кроме того, любые созданные кодом DLL объекты принадлежат вызывающему потоку или процессу - DLL в Win32 ничем не владеет.

Но. Есть одно "но". Переменные в EXE- или DLL-файлах можно поместить в особые раделы, которые можно разделить для всех процессов.

Немного о разделах. Начнем с того, что любой образ EXE- или DLL-файла состоит из группы разделов (sections). Вот названия наиболее часто встречающихся разделов:

Имя раздела Описание
.text Код приложения или DLL.
.bss Неинициализированные данные.
.rdata Неизменяемые данные периода выполнения.
.rsrc Ресурсы.
.edata Таблица экспортируемых имен.
.data Инициализированные данные.
.xdata Таблица для обработки исключений.
.idata Таблица импортируемых имен.
.CRT Неизменяемые данные стандартной библиотеки C.
.reloc Настроечная информация - таблица переадресации (fixup table).
.debug Отладочная информация.
.tls Локальная память потока.

 

Попробуйте посмотреть любой EXE- или DLL-файл - и вы наверняка найдете несколько разделов из этой таблицы.

Так вот, например, этот код:

#pragma data_seg("MySection")
TCHAR szName[
100] = {0};
LONG lModuleUsage;
#pragma data_seg()
#pragma comment(linker,"/SECTION:MySection, RWS")

заставит компилятор создать раздел MySection и поместить в него все ИНИЦИАЛИЗИРОВАННЫЕ переменные, встретившиеся между #pragma data_seg("MySection") и #pragama data_seg(). Директива #pragma comment(linker, "/SECTION:MySection, RWS") говорит компановщику включить строку сделать раздел MySection с аттрибутами READ, WRITE и SHARED. Угадайте с трех раз, какие переменные попадут в эту раздел. Вот они - грабли !!! Только szName и попадет ! А lModuleUsage попадает в секцию .bss (неинициализированные данные) - т.е. каждый поток будет иметь свою собственную lModuleUsage ! Отсюда сразу выходит, что код типа

#pragma data_seg("MySection")
CSesssion g_Session;
#pragma data_seg()
#pragma comment(linker, "/SECTION:MySection, RWS")

обречен - переменная g_Session будет в разделе .bss, а никак не в MySection.

Если подумать, то это справедливо - ведь каждый процесс вызывает конструктор объекта. А где исполняется конструктор - правильно, в адресном пространстве процесса. И все побочные данные объекта где будут? В адресном пространстве этого самого процесс а, что для других процессов уже никак не подходит.

А от такой схемы пришлось отказаться. Во-первых таким образом нарушается зашита уровня B (B-level security). Во-вторых ошибки в приложении с общими переменными могу повлиять на другие приложения, т.е. одна программа может "завалить" всю систему.