Как получить указатель на бинарный раздел в MSVC?

Я пишу код, который хранит некоторые структуры данных в специальном именованном двоичном разделе. Это все экземпляры одной и той же структуры, разбросанные по многим файлам C и не входящие в область действия друг друга. Поместив их все в именованный раздел, я могу перебрать их все.

В GCC я использую _attribute_((section(...)) плюс некоторые внешние указатели со специальными именами, которые волшебным образом заполняются компоновщиком. Вот тривиальный пример:

#include <stdio.h>

extern int __start___mysection[];
extern int __stop___mysection[];

static int x __attribute__((section("__mysection"))) = 4;
static int y __attribute__((section("__mysection"))) = 10;
static int z __attribute__((section("__mysection"))) = 22;

#define SECTION_SIZE(sect) \
    ((size_t)((__stop_##sect - __start_##sect)))

int main(void)
{
    size_t sz = SECTION_SIZE(__mysection);
    int i;

    printf("Section size is %u\n", sz);

    for (i=0; i < sz; i++) {
        printf("%d\n", __start___mysection[i]);
    }

    return 0;
}

Я пытаюсь понять, как это сделать в MSVC, но ничего не получается. Из документации компилятора я вижу, что я могу объявить раздел с помощью __pragma(section(...)) и объявить данные в этом разделе с помощью __declspec(allocate(...)) но я не вижу, как я могу получить указатель на начало и конец раздела во время выполнения.

Я видел несколько примеров в Интернете, связанных с выполнением _attribute_((constructor)) в MSVC, но это похоже на взлом, специфичный для CRT, а не на общий способ получить указатель на начало /конец раздела. У кого-нибудь есть идеи?


person Andrew B.    schedule 27.09.2010    source источник
comment
Могу я спросить, почему вы хотите управлять именованием двоичных разделов в первую очередь?   -  person Reinderien    schedule 28.09.2010
comment
Это для высокопроизводительной инструментальной среды. Представьте себе вызов printf(format, args...), в котором все строки формата хранятся в двоичном разделе, и единственное, что регистрируется, — это аргументы плюс значение поиска. Подстановка аргумента происходит в постобработке.   -  person Andrew B.    schedule 28.09.2010
comment
Лучшим примером этого является программа, которая позволяет вам добавлять модули путем повторной компоновки, а не перекомпиляции (и, возможно, повторной генерации некоторого кода). Если вы можете рассматривать весь раздел как массив некоторой структуры, вы можете перебирать его и выполнять какое-либо действие над/для каждой записи, например, вызывать cur_entry[i]->init(&cur_entry). Вы также можете использовать специальные знания о шаблонах использования памяти для оптимизации подкачки и локальности кэша, делая это. Обычно это не связано с Windows (насколько я знаю), но это также может потребоваться для процессоров с гарвардской архитектурой.   -  person nategoose    schedule 28.09.2010
comment
Да, мой механизм логирования работает аналогично. Специальный раздел представляет собой большой массив структур, содержащих все метаданные для точек инструментирования. Я хочу иметь возможность перебирать этот массив, чтобы выгружать все метаданные для последующего использования в постобработке.   -  person Andrew B.    schedule 28.09.2010
comment
Я также использую эту конструкцию для запуска юнит-тестов. Таким образом, мне не нужна основная функция, которая знает все модули в моей системе, но каждый модуль объявляет структуры unittest, которые хранятся в специальном разделе. Функция main перебирает все эти структуры так же, как это делает функция main() в примере Эндрю Б.   -  person Bart    schedule 20.04.2011
comment
Попытка вашего кода дает undefined reference to '__stop___mysection и undefined reference to '__start___mysection. Потребуется ли для этого какая-то магия ld script?   -  person speakman    schedule 27.04.2011
comment
Гораздо лучше сделать это с помощью языковых возможностей.   -  person David Heffernan    schedule 07.05.2011


Ответы (4)


Существует также способ сделать это без использования файла сборки.

#pragma section(".init$a")
#pragma section(".init$u")
#pragma section(".init$z")

__declspec(allocate(".init$a")) int InitSectionStart = 0;
__declspec(allocate(".init$z")) int InitSectionEnd   = 0;

__declspec(allocate(".init$u")) int token1 = 0xdeadbeef;
__declspec(allocate(".init$u")) int token2 = 0xdeadc0de;

Первые 3 строки определяют сегменты. Они определяют разделы и заменяют файл сборки. В отличие от прагмы data_seg, прагма section создает только секцию. Строки __declspec(allocate()) говорят компилятору поместить элемент в этот сегмент.

Со страницы microsoft: Здесь важен порядок. Имена разделов должны содержать не более 8 символов. Разделы с одинаковыми именами до $ объединяются в один раздел. Порядок их объединения определяется сортировкой символов после символа $.

Еще один важный момент, о котором следует помнить, это то, что секции дополняются 0 до 256 байт. Указатели START и END НЕ будут находиться непосредственно перед и после, как вы ожидаете.

Если вы настроили свою таблицу как указатели на функции или другие значения, отличные от NULL, должно быть легко пропустить записи NULL до и после таблицы из-за заполнения раздела

Дополнительные сведения см. на этой странице msdn.

person shimpossible    schedule 09.02.2013

Прежде всего, вам нужно создать ASM-файл, содержащий все интересующие вас разделы (например, section.asm):

.686
.model flat

PUBLIC C __InitSectionStart
PUBLIC C __InitSectionEnd

INIT$A SEGMENT DWORD PUBLIC FLAT alias(".init$a")
        __InitSectionStart EQU $
INIT$A ENDS

INIT$Z SEGMENT DWORD PUBLIC FLAT alias(".init$z")
        __InitSectionEnd EQU $
INIT$Z ENDS

END

Далее в вашем коде вы можете использовать следующее:

#pragma data_seg(".init$u")
int token1 = 0xdeadbeef;
int token2 = 0xdeadc0de;
#pragma data_seg()

Это дает такой MAP-файл:

 Start         Length     Name                   Class
 0003:00000000 00000000H .init$a                 DATA
 0003:00000000 00000008H .init$u                 DATA
 0003:00000008 00000000H .init$z                 DATA

  Address         Publics by Value              Rva+Base       Lib:Object
 0003:00000000       ?token1@@3HA               10005000     dllmain.obj
 0003:00000000       ___InitSectionStart        10005000     section.obj
 0003:00000004       ?token2@@3HA               10005004     dllmain.obj
 0003:00000008       ___InitSectionEnd          10005008     section.obj

Итак, как вы видите, раздел с именем .init$u расположен между .init$a и .init$z, и это дает вам возможность получить указатель на начало данных через символ __InitSectionStart и на конец данных через символ __InitSectionEnd.

person Ilya Matveychikov    schedule 07.05.2011

Здесь я немного поэкспериментировал и попытался реализовать версию без файла сборки, однако столкнулся со случайным количеством байтов заполнения между разделами, из-за чего почти невозможно найти начало части раздела .init$u, если содержимое это не просто указатели или другие простые элементы, которые можно проверить на NULL или какой-либо другой известный шаблон. Вставлено ли заполнение, по-видимому, коррелирует с использованием параметра отладки Zi. При задании отступы вставляются, без них все разделы отображаются именно так, как хотелось бы.

person don    schedule 03.07.2013

ML64 позволяет значительно снизить шум сборки:

public foo_start
public foo_stop

.code foo$a
foo_start:

.code foo$z
foo_stop:

end
person diapir    schedule 28.05.2019