Собственные объекты отладчика в расширениях JavaScript

Собственные объекты отладчика представляют различные конструкции и поведения среды отладчика. Объекты могут передаваться в расширения JavaScript (или получать их в) для управления состоянием отладчика.

Ниже приведены примеры объектов отладчика.

  • Сеанс
  • Потоки / Потоки
  • Процессы / Процесс
  • Кадры стека / кадр стека
  • Локальные переменные
  • Модули / Модуль
  • Служебная программа
  • Состояние
  • Параметры

Например, объект host.namespace.Debugger.Utility.Control.ExecuteCommand можно использовать для отправки команды u в отладчик со следующими двумя строками кода JavaScript.

var ctl = host.namespace.Debugger.Utility.Control;   
var outputLines = ctl.ExecuteCommand("u");

В этом разделе описывается работа с общими объектами и приводятся справочные сведения об их атрибутах и поведении.

Общие сведения о работе с JavaScript см. в разделе Скрипты отладчика JavaScript. Примеры JavaScript, использующие объекты отладчика, см. в разделе Примеры скриптов отладчика JavaScript. Сведения о работе с объектами параметров см. в разделе Settings (Set Debug Settings) (Настройка параметров отладки).

Чтобы изучить объекты, доступные в сеансе отладчика, используйте команду dx (display NatVis Expression). Например, некоторые объекты отладчика верхнего уровня можно отобразить с помощью этой команды dx.

0: kd> dx -r2 Debugger
Debugger                
    Sessions         : [object Object]
        [0x0]            : Remote KD: KdSrv:Server=@{<Local>},Trans=@{NET:Port=50000,Key=1.2.3.4,Target}
    Settings        
        Debug           
        Display         
        EngineInitialization
        Extensions      
        Input           
        Sources         
        Symbols         
        AutoSaveSettings : false
    State           
        DebuggerVariables
        PseudoRegisters 
        Scripts         
        UserVariables   
    Utility         
        Collections     
        Control         
        Objects   

Все перечисленные выше элементы являются кликабельными DML и могут быть рекурсированы далее, чтобы просмотреть структуру объекта отладчика.

Расширение отладчика с помощью модели данных

Модель данных отладчика позволяет создавать интерфейс для получения сведений о приложениях и драйверах в Windows со следующими атрибутами.

  • Возможность обнаружения и упорядочения — логически структурированное пространство имен можно запросить с помощью команды dx.
  • Запросы можно выполнять с помощью LINQ. Это позволяет извлекать и сортировать данные с помощью стандартного языка запросов.
  • Можно логически и согласованно расширять. Расширяемый с помощью методов, описанных в этом разделе, с помощью поставщиков скриптов отладчика, таких как Natvis и JavaScript.

Расширение объекта отладчика в JavaScript

Помимо возможности создания визуализатора в JavaScript, расширения скриптов могут также изменять основные понятия отладчика — сеансы, процессы, потоки, стеки, кадры стека, локальные переменные — и даже публиковать себя в качестве точек расширения, которые могут использовать другие расширения.

В этом разделе описывается, как расширить основную концепцию в отладчике. Расширения, созданные для совместного использования, должны соответствовать рекомендациям, приведенным в статье Native Debugger Objects in JavaScript Extensions — Design and Testing Considerations (Вопросы проектирования и тестирования).

Регистрация расширения

Скрипт может зарегистрировать тот факт, что он предоставляет расширение через запись в массиве, возвращаемом методом initializeScript.

function initializeScript()
{
    return [new host.namedModelParent(comProcessExtension, "Debugger.Models.Process")];
}

Наличие объекта host.namedModelParent в возвращаемом массиве указывает отладчику, что данный объект-прототип или класс ES6 (в данном случае comProcessExtension) будет родительской моделью данных для модели, зарегистрированной под именем Debugger.Models.Process.

Точки расширения объекта отладчика

Следующие точки расширения отладчика являются неотъемлемой частью отладчика и доступны для использования поставщиками скриптов, такими как JavaScript.

Debugger.Models.Sessions: список сеансов (целевых объектов), к которым подключен отладчик.

Debugger.Models.Session: отдельный сеанс (целевой объект), к которому подключен отладчик (динамический пользовательский режим, KD и т. д.).

Debugger.Models.Processes: список процессов в сеансе.

Debugger.Models.Threads: список потоков в процессе.

Debugger.Models.Thread: отдельный поток в процессе (независимо от режима пользователя или ядра).

Debugger.Models.Stack: стек потока

Debugger.Models.StackFrames: коллекция кадров, составляющих стек.

Debugger.Models.StackFrame: отдельный кадр стека в стеке

Debugger.Models.LocalVariables: локальные переменные в кадре стека.

Debugger.Models.Parameters: параметры для вызова в кадре стека.

Debugger.Models.Module: отдельный модуль в адресном пространстве процесса.

Дополнительные объекты модели данных

Кроме того, существуют дополнительные объекты модели данных, которые определяются основной моделью данных.

DataModel.Models.Intrinsic: встроенное значение (порядковые номера, числа с плавающей точкой и т. д.).

DataModel.Models.String: строка

DataModel.Models.Array: собственный массив

DataModel.Models.Guid: GUID

DataModel.Models.Error: объект ошибки

DataModel.Models.Concepts.Iterable: применяется к каждому объекту, который является итерируемым.

DataModel.Models.Concepts.StringDisplayable: применяется к каждому объекту, который имеет преобразование отображаемой строки.

Общие сведения о расширении объекта отладчика COM

Рассмотрим пример. Представьте, что вы хотите создать расширение отладчика для отображения сведений, относящихся к COM, таких как глобальная таблица интерфейса (GIT).

В прошлом могло существовать расширение отладчика с рядом команд, которые предоставляют средства для доступа к данным о COM. Одна команда может отображать информацию, ориентированную на процесс (например, глобальную таблицу интерфейса). Другая команда может предоставить информацию, ориентированную на потоки, например, какой код подразделения выполняется в. Возможно, вам потребуется узнать о и загрузить второе расширение отладчика, чтобы изучить другие аспекты COM.

Вместо набора трудно обнаруживаемых команд расширение JavaScript может изменить концепцию отладчика о том, что такое процесс и поток, чтобы добавить эти сведения таким образом, чтобы они были естественными, просматриваемыми и компонуемыми с другими расширениями отладчика.

Расширение объекта отладчика пользовательского режима или режима ядра

Отладчик и объекты отладчика имеют разное поведение в пользовательском режиме и режиме ядра. При создании объектов модели отладчика необходимо решить, в каких средах вы будете работать. Так как мы будем работать с COM в пользовательском режиме, мы создадим и протестируем это расширение com в пользовательском режиме. В других ситуациях можно создать отладчик JavaScript, который будет работать как в пользовательском режиме, так и в режиме ядра.

Создание вложенного пространства имен

Возвращаясь к нашему примеру, можно определить прототип или класс ES6 comProcessExtension , который содержит набор вещей, которые мы хотим добавить в объект процесса.

Важно Целью подпространства имен является создание логически структурированной и естественно просматриваемой парадигмы. Например, избегайте дампа несвязанных элементов в одно и то же подпространство имен. Перед созданием подпространства имен внимательно изучите сведения, рассмотренные в статье Объекты машинного отладчика в расширениях JavaScript— рекомендации по проектированию и тестированию .

В этом фрагменте кода мы создадим подпространство имен с именем COM в существующий объект отладчика процесса.

var comProcessExtension =
{
    //
    // Add a sub-namespace called 'COM' on process.
    //
    get COM()
    {
        //
        // What is 'this' below...?  It's the debugger's process object.  Yes -- this means that there is a cross-language
        // object hierarchy here.  A C++ object implemented in the debugger has a parent model (prototype) which is
        // implemented in JavaScript.
        //
        return new comNamespace(this);
    }
}

Реализация пространства имен

Затем создайте объект , который реализует вложенное пространство имен COM в процессе.

Важно Может существовать несколько процессов (присоединенных к таким в пользовательском режиме или в KD). Это расширение не может предполагать, что текущее состояние отладчика является тем, что предназначено пользователем. Кто-то может записать <someProcess.COM> в переменной и изменить ее, что может привести к представлению информации из неправильного контекста процесса. Решение заключается в добавлении кода в расширение, чтобы каждый экземпляр отслеживал, к какому процессу он присоединен. В этом примере кода эти сведения передаются через указатель "this" свойства .

this.__process = process;

class comNamespace
{
    constructor(process)
    {
        //
        // This is an entirely JavaScript object.  Each instantiation of a comNamespace will keep track
        // of what process it is attached to (passed via the ''this'' pointer of the property getter
        // we authored above.
        //
        this.__process = process;
    }
    
    get GlobalObjects()
    {
        return new globalObjects(this.__process);
    }
}

Логика реализации для таблицы глобального интерфейса COM

Чтобы более четко отделить логику реализации для таблицы глобального интерфейса COM, мы определим один класс ES6, gipTable , который абстрагирует таблицу COM GIP, и другой, globalObjects, который возвращается из метода получения GlobalObjects(), определенного в приведенном выше фрагменте кода реализации пространства имен. Все эти сведения можно скрыть внутри закрытия initializeScript, чтобы избежать публикации каких-либо из этих внутренних сведений в пространстве имен отладчика.

// gipTable:
//
// Internal class which abstracts away the GIP Table.  It iterates objects of the form
// {entry : GIPEntry, cookie : GIT cookie}
//
class gipTable
{
    constructor(gipProcess)
    {
        //
        // Windows 8 through certain builds of Windows 10, it's in CGIPTable::_palloc.  In certain builds
        // of Windows 10 and later, this has been moved to GIPEntry::_palloc.  We need to check which.
        //
        var gipAllocator = undefined;
        try
        {
            gipAllocator = host.getModuleSymbol("combase.dll", "CGIPTable::_palloc", "CPageAllocator", gipProcess)._pgalloc;
        }
        catch(err)
        {
        }

        if (gipAllocator == undefined)
        {
            gipAllocator = host.getModuleSymbol("combase.dll", "GIPEntry::_palloc", "CPageAllocator", gipProcess)._pgalloc;
        }

        this.__data = {
            process : gipProcess,
            allocator : gipAllocator,
            pageList : gipAllocator._pPageListStart,
            pageCount : gipAllocator._cPages,
            entriesPerPage : gipAllocator._cEntriesPerPage,
            bytesPerEntry : gipAllocator._cbPerEntry,
            PAGESHIFT : 16,
            PAGEMASK : 0x0000FFFF,
            SEQNOMASK : 0xFF00
        };
    }

    *[Symbol.iterator]()
    {
        for (var pageNum = 0; pageNum < this.__data.pageCount; ++pageNum)
        {
            var page = this.__data.pageList[pageNum];
            for (var entryNum = 0; entryNum < this.__data.entriesPerPage; ++entryNum)
            {
                var entryAddress = page.address.add(this.__data.bytesPerEntry * entryNum);
                var gipEntry = host.createPointerObject(entryAddress, "combase.dll", "GIPEntry *", this.__data.process);
                if (gipEntry.cUsage != -1 && gipEntry.dwType != 0)
                {
                    yield {entry : gipEntry, cookie : (gipEntry.dwSeqNo | (pageNum << this.__data.PAGESHIFT) | entryNum)};
                }
            }
        }
    }

    entryFromCookie(cookie)
    {
        var sequenceNo = (cookie & this.__data.SEQNOMASK);
        cookie = cookie & ~sequenceNo;
        var pageNum = (cookie >> this.__data.PAGESHIFT);
        if (pageNum < this.__data.pageCount)
        {
            var page = this.__data.pageList[pageNum];
            var entryNum = (cookie & this.__data.PAGEMASK);
            if (entryNum < this.__data.entriesPerPage)
            {
                var entryAddress = page.address.add(this.__data.bytesPerEntry * entryNum);
                var gipEntry = host.createPointerObject(entryAddress, "combase.dll", "GIPEntry *", this.__data.process);
                if (gipEntry.cUsage != -1 && gipEntry.dwType != 0 && gipEntry.dwSeqNo == sequenceNo)
                {
                    return {entry : gipEntry, cookie : (gipEntry.dwSeqNo | (pageNum << this.__data.PAGESHIFT) | entryNum)};
                }
            }
        }

        //
        // If this exception flows back to C/C++, it will be a failed HRESULT (according to the type of error -- here E_BOUNDS)
        // with the message being encapsulated by an error object.
        //
        throw new RangeError("Unable to find specified value");
    }
}
// globalObjects:
//
// The class which presents how we want the GIP table to look to the data model.  It iterates the actual objects
// in the GIP table indexed by their cookie.
//
class globalObjects
{
    constructor(process)
    {
        this.__gipTable = new gipTable(process);
    }

    *[Symbol.iterator]()
    {
        for (var gipCombo of this.__gipTable)
        {
            yield new host.indexedValue(gipCombo.entry.pUnk, [gipCombo.cookie]);
        }
    }

    getDimensionality()
    {
        return 1;
    }

    getValueAt(cookie)
    {
        return this.__gipTable.entryFromCookie(cookie).entry.pUnk;
    }
}

Наконец, используйте host.namedModelRegistration, чтобы зарегистрировать новые функции COM.

function initializeScript()
{
    return [new host.namedModelParent(comProcessExtension, "Debugger.Models.Process"),
            new host.namedModelRegistration(comNamespace, "Debugger.Models.ComProcess")];
}

Сохраните код для GipTableAbstractor.js с помощью такого приложения, как Блокнот.

Ниже приведены сведения о процессе, доступные в пользовательском режиме перед загрузкой этого расширения.

0:000:x86> dx @$curprocess
@$curprocess                 : DataBinding.exe
    Name             : DataBinding.exe
    Id               : 0x1b9c
    Threads         
    Modules  

Загрузите расширение JavaScript.

0:000:x86> .scriptload C:\JSExtensions\GipTableAbstractor.js
JavaScript script successfully loaded from 'C:\JSExtensions\GipTableAbstractor.js'

Затем используйте команду dx, чтобы отобразить сведения о процессе с помощью предопределенного @$curprocess.

0:000:x86> dx @$curprocess
@$curprocess                 : DataBinding.exe
    Name             : DataBinding.exe
    Id               : 0x1b9c
    Threads         
    Modules         
    COM              : [object Object]
0:000:x86> dx @$curprocess.COM
@$curprocess.COM                 : [object Object]
    GlobalObjects    : [object Object]
0:000:x86> dx @$curprocess.COM.GlobalObjects
@$curprocess.COM.GlobalObjects                 : [object Object]
    [0x100]          : 0x12f4fb0 [Type: IUnknown *]
    [0x201]          : 0x37cfc50 [Type: IUnknown *]
    [0x302]          : 0x37ea910 [Type: IUnknown *]
    [0x403]          : 0x37fcfe0 [Type: IUnknown *]
    [0x504]          : 0x12fe1d0 [Type: IUnknown *]
    [0x605]          : 0x59f04e8 [Type: IUnknown *]
    [0x706]          : 0x59f0eb8 [Type: IUnknown *]
    [0x807]          : 0x59f5550 [Type: IUnknown *]
    [0x908]          : 0x12fe340 [Type: IUnknown *]
    [0xa09]          : 0x5afcb58 [Type: IUnknown *]

Эта таблица также доступна программным способом через файл cookie GIT.

0:000:x86> dx @$curprocess.COM.GlobalObjects[0xa09]
@$curprocess.COM.GlobalObjects[0xa09]                 : 0x5afcb58 [Type: IUnknown *]
    [+0x00c] __abi_reference_count [Type: __abi_FTMWeakRefData]
    [+0x014] __capture        [Type: Platform::Details::__abi_CapturePtr]

Расширение основных понятий объекта отладчика с помощью LINQ

Помимо возможности расширения таких объектов, как процесс и поток, JavaScript также может расширить понятия, связанные с моделью данных. Например, можно добавить новый метод LINQ к каждому итерируемому объекту. Рассмотрим пример расширения DuplicateDataModel, которое дублирует каждую запись в итерируемом N раз. В следующем коде показано, как это можно реализовать.

function initializeScript()
{
    var newLinqMethod =
    {
        Duplicate : function *(n)
        {
            for (var val of this)
            {
                for (var i = 0; i < n; ++i)
                {
                    yield val;
                }
            };
        }
    };

    return [new host.namedModelParent(newLinqMethod, "DataModel.Models.Concepts.Iterable")];
}

Сохраните код для DuplicateDataModel.js с помощью такого приложения, как Блокнот.

При необходимости загрузите поставщик сценариев JavaScript, а затем загрузите расширение DuplicateDataModel.js.

0:000:x86> !load jsprovider.dll
0:000:x86> .scriptload C:\JSExtensions\DuplicateDataModel.js
JavaScript script successfully loaded from 'C:\JSExtensions\DuplicateDataModel.js'

Используйте команду dx, чтобы протестировать новую функцию Дублировать.

0: kd> dx -r1 Debugger.Sessions.First().Processes.First().Threads.Duplicate(2),d
Debugger.Sessions.First().Processes.First().Threads.Duplicate(2),d                 : [object Generator]
    [0]              : nt!DbgBreakPointWithStatus (fffff800`9696ca60) 
    [1]              : nt!DbgBreakPointWithStatus (fffff800`9696ca60) 
    [2]              : intelppm!MWaitIdle+0x18 (fffff805`0e351348) 
    [3]              : intelppm!MWaitIdle+0x18 (fffff805`0e351348) 
…

См. также раздел

Собственные объекты отладчика в расширениях JavaScript — сведения об объекте отладчика

Собственные объекты отладчика в расширениях JavaScript — рекомендации по проектированию и тестированию

Скрипты отладчика JavaScript

Примеры скриптов отладчика JavaScript