Статьи

Поиск и отображение объектов кучи C ++ в WinDbg

Это то, о чем я очень рад рассказать вам.

Но сначала немного мотивации. В управляемых приложениях существует огромное количество инструментов и способов проверки содержимого кучи управления. Вы можете использовать профилировщик памяти для просмотра ссылок между объектами и проверки отдельных объектов. Вы можете использовать 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 .