Статьи

Отображение и поиск содержимого std :: map в WinDbg

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