На этот раз мы готовы к более сложной задаче. Мы хотим автоматически отображать и, возможно, искать и фильтровать объекты 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 равны корню дерева (в этом случае дерево реализовано именно так).