На этот раз мы готовы к более сложной задаче. Мы хотим автоматически отображать и, возможно, искать и фильтровать объекты std :: map в WinDbg. Скрипт для std :: vectors был относительно простым из-за плоской структуры данных в векторе; карты более сложные звери.
В частности, карта в Visual C ++ STL реализована в виде красно-черного дерева . Каждый узел дерева имеет три важных указателя: _Left , _Right и _Parent . Кроме того, каждый узел имеет поле _Myval , содержащее пару std :: с ключом и значением, представленным узлом.
Итерация древовидной структуры требует рекурсии, а сценарии WinDbg не имеют синтаксиса для определения функций. Однако мы можем вызвать скрипт рекурсивно — ему разрешено содержать команду $$> a <, которая снова вызывает ее с другим набором аргументов. Путь к сценарию также доступен в $ {$ arg0} .
Прежде чем я покажу вам сценарий, мне нужно было решить одну небольшую проблему. Когда вы вызываете скрипт рекурсивно, значения псевдорегистров (например, $ t0 ) будут засорены рекурсивным вызовом. Я был на грани динамического выделения памяти или вызова процесса оболочки для хранения и загрузки переменных, когда наткнулся на команды .push и .pop , которые хранят регистр и загружают его соответственно. Это необходимо для рекурсивных сценариев WinDbg.
Итак, предположим, что вы хотите отобразить значения из std :: map <int, point>, где ключ меньше или равен 2. Здесь мы идем:
0:000> $$>a< traverse_map.script my_map -c ".block { .if (@@(@$t9.first) <= 2) { .echo ----; ?? @$t9.second } }" size = 10 ---- struct point +0x000 x : 0n1 +0x004 y : 0n2 +0x008 data : extra_data ---- struct point +0x000 x : 0n0 +0x004 y : 0n1 +0x008 data : extra_data ---- struct point +0x000 x : 0n2 +0x004 y : 0n3 +0x008 data : extra_data
Для каждой пары (хранящейся в псевдорегире $ t9 ) блок проверяет, меньше или равен первый компонент 2, и, если он есть, выводит второй компонент.
Далее вот сценарий. Обратите внимание, что это значительно сложнее, чем то, что мы делали с векторами, потому что он по существу вызывает себя с другим набором параметров, а затем повторяется рекурсивно.
.if ($sicmp("${$arg1}", "-n") == 0) { .if (@@(@$t0->_Isnil) == 0) { .if (@$t2 == 1) { .printf /D "<exec cmd=\"db %p L10\">%p</exec>\n", @$t0, @$t0 .printf "key = " ?? @$t0->_Myval.first .printf "value = " ?? @$t0->_Myval.second } .else { r? $t9 = @$t0->_Myval command } } $$ Recurse into _Left, _Right unless they point to the root of the tree .if (@@(@$t0->_Left) != @@(@$t1)) { .push /r /q r? $t0 = @$t0->_Left $$>a< ${$arg0} -n .pop /r /q } .if (@@(@$t0->_Right) != @@(@$t1)) { .push /r /q r? $t0 = @$t0->_Right $$>a< ${$arg0} -n .pop /r /q } } .else { r? $t0 = ${$arg1} .if (${/d:$arg2}) { .if ($sicmp("${$arg2}", "-c") == 0) { r $t2 = 0 aS ${/v:command} "${$arg3}" } } .else { r $t2 = 1 aS ${/v:command} " " } .printf "size = %d\n", @@(@$t0._Mysize) r? $t0 = @$t0._Myhead->_Parent r? $t1 = @$t0->_Parent $$>a< ${$arg0} -n ad command }
Особо следует отметить команду aS, которая настраивает псевдоним, который затем используется рекурсивным вызовом для вызова блока команд для каждого из элементов карты; $ sicmp функция , которая сравнивает строку; и .printf / D функция, которая выводит порцию DML. Наконец, рекурсия завершается, когда _Left или _Right равны корню дерева (в этом случае дерево реализовано именно так).