Winapi — C ++ Stack, идущий на Windows

Я строю менеджер памяти для C ++, используя очень стиль .NET. При этом мне нужно знать, какие объекты считаются достижимыми; и объект считается достижимым, если достижимый объект имеет дескриптор рассматриваемого объекта. Таким образом, это ставит вопрос о том, какой объект (ы) является корнем нашего поиска? Ответ мог бы состоять в том, что эти объекты «eve» находятся в стеке, будь то в форме дескриптора управляемого объекта или экземпляра локального объекта области действия, который сам по себе имеет дескриптор управляемого объекта.

Я прочитал несколько статей на эту тему, а также ознакомился с подробностями реализации MSDN о методе StackWalk в Win32 API.

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

2

Решение

Ваши требования вроде бы похожи на небольшой проект, над которым я сейчас работаю, но моя цель не в том, чтобы создать диспетчер памяти, а в том, чтобы инструментально использовать dmalloc (и долгосрочное приложение в режиме отладки, в котором оно работает. выполняется с возможностью периодически останавливать выполнение и сканировать память в поисках выделений кучи, на которые нет ссылок. Вроде как «тупой» сборщик мусора, но не с целью освобождения памяти; вместо этого, с целью регистрации утечек выделенных ресурсов для последующего анализа (наряду со следами стека, захваченными во время выделения, которые я уже добавил в dmalloc). Обратите внимание, что как сборщик мусора диспетчера памяти общего назначения, это будет довольно неэффективный процесс и займет «долгое» время для запуска (я еще не закончил, но я не удивлюсь, если каждый раз он его запускает останавливает нормальное выполнение программы более чем на 10 секунд), но для моих собственных целей меня не очень заботит производительность, потому что я буду включать ее только один раз в несколько месяцев для проверки новых утечек памяти в продукте моей компании.

В любом случае, я предполагаю, что ваш диспетчер памяти будет единственным источником кучи памяти в вашем приложении? И что потоки в вашей системе работают в среде с полностью разделяемой памятью, где ни у какого потока нет памяти, включая пространство стека и пространство локального хранилища, которое не видно из других потоков? Если так…

Я полагаю, что есть только четыре категории памяти, в которых вы можете найти указатели для распределения кучи:

  1. На стеках вызовов каждого потока
  2. В пределах кучи выделений сами
  3. В статически выделенной доступной для записи памяти (.bss & .data / .sdata, но
    не .rdata / .rodata)
  4. В локальном потоке памяти для каждого потока

Вы уже знаете, что в стеке могут возникать указатели на выделение кучи. Указатели на выделения могут также вместо) храниться в куче самих объектов, и даже не храниться в стеке. Ваш вопрос предполагает, что вы, возможно, надеетесь использовать стек как «корень» поиска вашего сборщика мусора; Я предполагаю, что это означает, что вы надеетесь, что сможете следовать указателям в стеке за пределы в другие выделения, выполнять поиск от одного объекта к другому по памяти, пока вы не пройдете все объекты в памяти и не найдете все указатели на все распределения. «Корневые» указатели могут также существовать в статически размещенных объектах, на которые можно ссылаться напрямую, даже если указатель на такой объект в стеке отсутствует, поэтому нельзя просто предположить, что все выделения достижимы из «указателей», которые вы найдете в стек. Кроме того, к сожалению, в C ++, если вы не в состоянии узнать структуру каждого размещения (что не получится без помощи компилятора), вам придется предположить, что любое местоположение, возможно, является указателем. Поэтому вам придется сканировать каждую из этих четырех категорий памяти в поисках потенциальных указателей на все существующие распределения, помечая каждую из них флагом «возможно, все еще используется», если вы найдете в памяти значение, соответствующее адресу выделения, действительно ли это указатель. Когда вы просматриваете память, в каждом байтовом местоположении (или в каждом байтовом местоположении, равномерно делимом на sizeof (void *), если вы знаете, что ваша платформа не может иметь указатели на смещенные адреса), вам придется искать свой список распределений чтобы увидеть, есть ли это значение в вашем списке распределений.

Поскольку вы уверены, что знаете, что делаете, ваш менеджер памяти, вероятно, отслеживает эти выделения в сбалансированной древовидной структуре (возможно, в красно-черном дереве или дереве Андерссона), которая дает вам O (log n) вставку & посмотрите на эти распределения, но константа пропорциональности для навигации по этим деревьям действительно убьет производительность вашего сборщика мусора. Перед выполнением сканирования сборки мусора вы захотите скопировать указатели размещения дерева в плоский непрерывный буфер (то есть «массив») по порядку (то есть по возрастанию или по убыванию с использованием обхода inorder). Я предлагаю массив void* адреса каждого выделения и отдельного битового массива (не bool массив) с одним битом на выделение, инициализированный для всех нулей, где соответствующий бит выделения установлен в 1, если вы обнаружите потенциальную ссылку на него. Это все равно даст вам O (log n) поиска (используя бинарный поиск), пока вы сканируете сборщик мусора, но с гораздо более управляемой константой пропорциональности для ваших поисков; Кроме того, эта более компактная структура данных будет иметь лучшую производительность по кэшу, чем сбалансированное дерево.

Теперь я расскажу о каждой из трех категорий памяти, которые вам нужно сканировать:

  • CallStacks каждого потока

Для этого вам понадобится запросить у менеджера потоков & дно стеков каждой нити. Если вы можете получить только текущий указатель стека для каждого потока, то вы можете использовать API «backtrace» для получения списка адресов возврата функций в этом стеке. Исходя из этого, вы можете отсканировать назад к базе каждого стека (чего вы не знаете), отмечая каждый обратный адрес галочкой, пока не дойдете до последнего обратного адреса, где вы затем нашли базу стека (или достаточно близко) , Что касается «текущего потока», убедитесь, что он не содержит никаких стековых кадров, связанных с вашим менеджером памяти; резервное копирование нескольких стековых фреймов & игнорируйте адреса, связанные с вашим сборщиком мусора, иначе вы можете найти адреса утечек в локальных переменных вашего сборщика мусора и принять их за

  • В пределах кучи выделений сами

Объекты кучи могут ссылаться друг на друга, и у вас может быть сеть пропущенных объектов, которые все ссылаются друг на друга еще как группа, они пропущены. Вы не хотите видеть их указатели друг на друга & относитесь к ним как к «используемым», так что вы должны обращаться с ними осторожно … и наконец. Как только все другие категории будут закончены, вы можете свернуть / разделить ваш плоский массив void* адреса распределения, составление отдельного списка «рассмотренных в использовании» распределений и «еще не проверенных» распределений. Просматривайте «рассматриваемые в использовании» распределения в поисках потенциальных указателей на распределения, которые еще находятся в «еще не проверенном» списке. По мере того, как вы их найдете, переместите их из списка «еще не проверено» в конец списка «рассматриваемых в использовании», чтобы в конечном итоге вы их также сканировали.

  • В статически выделенной доступной для записи памяти (.bss & .data / .sdata, но не
    .rdata / .rodata)

Для этого вам нужно получить символы от вашего компоновщика в начале & конец (или длина) каждого из этих разделов. Если таких символов еще нет или вы не можете получить эту информацию из API платформы, вам необходимо получить командный сценарий компоновщика (скрипт компоновщика) и изменить его, чтобы добавить & инициализировать глобальные символы по начальному адресу & Конечный адрес (или длина) каждого из этих разделов. Раздел .bss содержит неинициализированные глобальные, файловые области и статические члены класса. Разделы .data / .sdata содержат неконстантные предварительно инициализированные глобальные, файловые области и члены статических данных класса. Вам не нужно беспокоиться о разделах .rdata / .rodata, потому что ваша программа не будет записывать адреса распределения кучи в статические данные const.

  • В локальном потоке памяти для каждого потока

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


Если вы все еще на борту и хотите это сделать, к настоящему времени вы, вероятно, поняли, что это более крупный проект, чем вы могли подумать изначально. Дайте мне знать, как это происходит!

3

Другие решения

Других решений пока нет …

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector