Это то, о чем я очень рад рассказать вам.
Но сначала немного мотивации. В управляемых приложениях существует огромное количество инструментов и способов проверки содержимого кучи управления. Вы можете использовать профилировщик памяти для просмотра ссылок между объектами и проверки отдельных объектов. Вы можете использовать WinDbg с расширением SOS для выгрузки всех объектов определенного типа и выполнения дополнительных сценариев и команд для каждого объекта. Вы даже можете написать код C #, который использует библиотеку ClrMd для анализа содержимого кучи и написания собственных диагностических инструментов.
C ++ не имеет ничего подобного. И это то, что я хотел изменить. Объекты кучи C ++ часто являются классами. У классов часто есть виртуальные методы. И классы с виртуальными методами имеют указатель vtable в качестве своего первого поля, что позволяет идентифицировать их и отображать их.
Введите heap_stat.py , скрипт, который вы можете запустить в WinDbg для поиска и отображения объектов кучи (учитывая, что у них есть vtable).
Чтобы использовать heap_stat.py , вам нужно установить PyKD . Это бесплатное расширение для WinDbg, которое позволяет писать скрипты Python, которые обращаются к большей части API механизма отладчика. Это определенно проще, чем написание полноценных расширений C ++, и определенно более мощный, чем сценарии WinDbg .
Затем вы загружаете pykd.pyd и начинаете проверять вашу кучу C ++:
0:001> .load pykd.pyd 0:001> !py heap_stat.py Running x /2 *!*`vftable' command...DONE Running !heap -h 0 command...DONE Enumerating 218 heap blocks 005c4170 MSVCP110!std::locale::_Locimp 005c6a70 MSVCR110!std::bad_alloc 005ccc00 Payroll!employee 005ccc28 Payroll!employee 005ccc50 Payroll!employee 005ccc78 Payroll!employee ... snipped ... 005cee90 Payroll!employee Statistics: Type name Count Size Payroll!employee 100 3200 MSVCP110!std::ctype<char> 1 Unknown MSVCP110!std::ctype<wchar_t> 1 Unknown ... snipped ... Payroll!manager 1 44 MSVCP110!std::locale::_Locimp 1 Unknown
Вы также можете запросить отображение только статистики, используя ключ -stat :
0:001> !py heap_stat.py -stat Running x /2 *!*`vftable' command...DONE Running !heap -h 0 command...DONE Enumerating 218 heap blocks Enumerated 100 heap blocks Enumerated 200 heap blocks
Статистика:
Type name Count Size Payroll!employee 100 3200 MSVCP110!std::ctype<char> 1 Unknown MSVCP110!std::ctype<wchar_t> 1 Unknown MSVCP110!std::ctype<unsigned short> 1 Unknown MSVCR110!std::bad_alloc 1 Unknown Payroll!manager 1 44 MSVCP110!std::locale::_Locimp 1 Unknown
Хорошо, предположим, что вы сейчас заинтересованы в этих объектах сотрудников Payroll ! . Вы можете попросить, чтобы выходные данные были отфильтрованы только к этим объектам ( используемый здесь ключ -type принимает любое регулярное выражение, понятное Python, поэтому вы можете делать такие вещи, как Payroll! (Employee | manager)):
0:001> !py heap_stat.py -type Payroll!employee Running x /2 *!*`vftable' command...DONE Running !heap -h 0 command...DONE Enumerating 218 heap blocks 005ccc00 Payroll!employee 005ccc28 Payroll!employee 005ccc50 Payroll!employee 005ccc78 Payroll!employee ... snipped ... 005cee90 Payroll!employee Statistics: Type name Count Size Payroll!employee 100 3200
Вы также можете запросить короткий вывод, который позволяет запускать другие команды и сценарии для каждого объекта. За это отвечает — короткий переключатель. Предположим, вы хотите, чтобы заработная плата всех сотрудников зарабатывала более 97 500 долларов:
0:001> .foreach (emp {!py heap_stat.py -type Payroll!employee -short}) { .block { r? $t0=(Payroll!employee*)0x${emp}; .if (@@c++(@$t0->_salary) > 0n97500) { .printf "%ma earns $%d\n", @@c++(@$t0->_name._Bx._Buf), @@c++(@$t0->_salary) } } } Kate earns $97757 Lyanna earns $97662
Вау. Здесь есть много чего переварить. Сценарий просто возвращает набор адресов для объектов сотрудников. Большая часть работы находится за пределами сценария: для каждого из этих объектов мы выполняем несколько дополнительных команд отладчика, которые получают имя сотрудника и зарплату из объекта и отображают его.
Наконец, есть поддержка экономии времени с помощью ключей -save и -load . -Save коммутатор сохраняет некоторую отладочную информацию в файл (размер типа, виртуальные таблицы адресов и т.д.), которую -load коммутатор может затем загрузить. Это может значительно сократить время выполнения, особенно если у вас большое количество модулей со многими vtables.
Конечно, этот скрипт основан на множестве эвристик и может быть одурачен определенными типами множественного наследования, а также просто битовым шаблоном, который совпадает с адресом виртуальной таблицы. Тем не менее, я считаю, что этот подход может быть довольно полезным для многих разработчиков приложений C ++. Я намерен продолжать добавлять функции в сценарий по мере необходимости, и, конечно, если вы хотите внести свой вклад, приветствуются запросы на получение запросов на GitHub .