Bagikan melalui


Pembuatan Skrip JavaScript Debugger

Topik ini menjelaskan cara menggunakan JavaScript untuk membuat skrip yang memahami objek debugger dan memperluas dan menyesuaikan kemampuan debugger.

Gambaran Umum Pembuatan Skrip JavaScript Debugger

Penyedia skrip menjembatani bahasa skrip ke model objek internal debugger. Penyedia skrip debugger JavaScript, memungkinkan penggunaan JavaScript dengan debugger.

Ketika JavaScript dimuat melalui perintah .scriptload, kode akar skrip dijalankan, nama-nama yang ada dalam skrip dihubungkan ke dalam namespace layanan akar debugger (dx Debugger) dan skrip tetap tinggal dalam memori sampai dibongkar dan semua referensi ke objeknya dirilis. Skrip dapat menyediakan fungsi baru ke evaluator ekspresi debugger, memodifikasi model objek debugger, atau dapat bertindak sebagai visualizer dengan cara yang sama seperti yang dilakukan visualizer NatVis.

Topik ini menjelaskan beberapa hal yang dapat Anda lakukan dengan pembuatan skrip debugger JavaScript.

Kedua topik ini memberikan informasi tambahan tentang bekerja dengan JavaScript di debugger.

Contoh Skrip JavaScript Debugger

Objek Asli di Ekstensi JavaScript

Video Pembuatan Skrip JavaScript

Defrag Tools #170 - Andy dan Bill menunjukkan ekstensibilitas dan kemampuan pembuatan skrip JavaScript di debugger.

Penyedia JavaScript Debugger

Penyedia JavaScript yang disertakan dengan debugger memanfaatkan sepenuhnya penyempurnaan objek dan kelas ECMAScript6 terbaru. Untuk informasi selengkapnya, lihat ECMAScript 6 — Fitur Baru: Gambaran Umum & Perbandingan.

JsProvider.dll

JsProvider.dll adalah penyedia JavaScript yang dimuat untuk mendukung JavaScript Debugger Scripting.

Persyaratan

JavaScript Debugger Scripting dirancang untuk bekerja dengan semua versi Windows yang didukung.

Memuat Penyedia Skrip JavaScript

Sebelum menggunakan salah satu perintah .script, penyedia skrip perlu dimuat. Gunakan perintah .scriptproviders untuk mengonfirmasi bahwa penyedia JavaScript dimuat.

0:000> .scriptproviders
Available Script Providers:
    NatVis (extension '.NatVis')
    JavaScript (extension '.js')

Perintah Meta Skrip JavaScript

Perintah berikut ini tersedia untuk bekerja dengan JavaScript Debugger Scripting.

Persyaratan

Sebelum menggunakan salah satu perintah .script, penyedia skrip perlu dimuat. Gunakan perintah .scriptproviders untuk mengonfirmasi bahwa penyedia JavaScript dimuat.

0:000> .scriptproviders
Available Script Providers:
    NatVis (extension '.NatVis')
    JavaScript (extension '.js')

.scriptproviders (Penyedia Skrip Daftar)

Perintah .scriptproviders akan mencantumkan semua bahasa skrip yang saat ini dipahami oleh debugger dan ekstensi tempat mereka terdaftar.

Dalam contoh di bawah ini, penyedia JavaScript dan NatVis dimuat.

0:000> .scriptproviders
Available Script Providers:
    NatVis (extension '.NatVis')
    JavaScript (extension '.js')

File apa pun yang berakhiran ". NatVis" dipahami sebagai skrip NatVis dan file apa pun yang berakhiran ".js" dipahami sebagai skrip JavaScript. Salah satu jenis skrip dapat dimuat dengan perintah .scriptload.

Untuk informasi selengkapnya, lihat .scriptproviders (Penyedia Skrip Daftar)

.scriptload (Load Script)

Perintah .scriptload akan memuat skrip dan menjalankan kode akar skrip dan fungsi inisialisasiScript . Jika ada kesalahan dalam beban awal dan eksekusi skrip, kesalahan akan ditampilkan ke konsol. Perintah berikut menunjukkan beban TestScript.js yang berhasil.

0:000> .scriptload C:\WinDbg\Scripts\TestScript.js
JavaScript script successfully loaded from 'C:\WinDbg\Scripts\TestScript.js'

Setiap manipulasi model objek yang dibuat oleh skrip akan tetap di tempat sampai skrip kemudian dibongkar atau dijalankan lagi dengan konten yang berbeda.

Untuk informasi selengkapnya, lihat .scriptload (Load Script)

.scriptrun

Perintah .scriptrun akan memuat skrip, menjalankan kode akar skrip, inisialisasiScript , dan fungsi invokeScript . Jika ada kesalahan dalam beban awal dan eksekusi skrip, kesalahan akan ditampilkan ke konsol.

0:000> .scriptrun C:\WinDbg\Scripts\helloWorld.js
JavaScript script successfully loaded from 'C:\WinDbg\Scripts\helloWorld.js'
Hello World!  We are in JavaScript!

Setiap manipulasi model objek debugger yang dibuat oleh skrip akan tetap ada sampai skrip kemudian dibongkar atau dijalankan lagi dengan konten yang berbeda.

Untuk informasi selengkapnya, lihat .scriptrun (Jalankan Skrip).

.scriptunload (Skrip Unload)

Perintah .scriptunload membongkar skrip yang dimuat dan memanggil fungsi uninitializeScript . Gunakan sintaks perintah berikut untuk membongkar skrip

0:000:x86> .scriptunload C:\WinDbg\Scripts\TestScript.js
JavaScript script unloaded from 'C:\WinDbg\Scripts\TestScript.js'

Untuk informasi selengkapnya, lihat .scriptunload (Unload Script).

.scriptlist (Daftar Skrip yang Dimuat)

Perintah .scriptlist akan mencantumkan skrip apa pun yang telah dimuat melalui .scriptload atau perintah .scriptrun. Jika TestScript berhasil dimuat menggunakan .scriptload, perintah .scriptlist akan menampilkan nama skrip yang dimuat.

0:000> .scriptlist
Command Loaded Scripts:
    JavaScript script from 'C:\WinDbg\Scripts\TestScript.js'

Untuk informasi selengkapnya, lihat .scriptlist (Daftar Skrip yang Dimuat).

Mulai menggunakan Scripting JavaScript Debugger

Contoh Skrip HelloWorld

Bagian ini menjelaskan cara membuat dan menjalankan skrip debugger JavaScript sederhana yang dicetak, Halo Dunia.

// WinDbg JavaScript sample
// Prints Hello World
function initializeScript()
{
    host.diagnostics.debugLog("***> Hello World! \n");
}

Gunakan editor teks seperti Notepad untuk membuat file teks bernama HelloWorld.js yang berisi kode JavaScript yang ditunjukkan di atas.

Gunakan perintah .scriptload untuk memuat dan menjalankan skrip. Karena kami menggunakan nama fungsi inisialisasiScript, kode dalam fungsi dijalankan saat skrip dimuat.

0:000> .scriptload c:\WinDbg\Scripts\HelloWorld.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\HelloWorld.js'
***> Hello World! 

Setelah skrip dimuat, fungsionalitas tambahan tersedia di debugger. Gunakan perintah dx (Tampilkan Ekspresi NatVis) untuk menampilkan Debugger.State.Scripts untuk melihat bahwa skrip kami sekarang menjadi residen.

0:000> dx Debugger.State.Scripts
Debugger.State.Scripts                
    HelloWorld 

Dalam contoh berikutnya, kita akan menambahkan dan memanggil fungsi bernama.

Menambahkan Contoh Skrip Dua Nilai

Bagian ini menjelaskan cara membuat dan menjalankan skrip debugger JavaScript sederhana yang menambahkan mengambil input dan menambahkan dua angka.

Skrip sederhana ini menyediakan satu fungsi, addTwoValues.

// WinDbg JavaScript sample
// Adds two functions
function addTwoValues(a, b)
 {
     return a + b;
 }

Menggunakan editor teks seperti Notepad untuk membuat file teks bernama FirstSampleFunction.js

Gunakan perintah .scriptload untuk memuat skrip.

0:000> .scriptload c:\WinDbg\Scripts\FirstSampleFunction.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\FirstSampleFunction.js'

Setelah skrip dimuat, fungsionalitas tambahan tersedia di debugger. Gunakan perintah dx (Tampilkan Ekspresi NatVis) untuk menampilkan Debugger.State.Scripts untuk melihat bahwa skrip kami sekarang menjadi residen.

0:000> dx Debugger.State.Scripts
Debugger.State.Scripts                
    FirstSampleFunction    

Kita dapat memilih FirstSampleFunction, untuk melihat fungsi apa yang disediakannya.

0:000> dx -r1 -v Debugger.State.Scripts.FirstSampleFunction.Contents
Debugger.State.Scripts.FirstSampleFunction.Contents                 : [object Object]
    host             : [object Object]
    addTwoValues    
 ... 

Untuk membuat skrip sedikit lebih nyaman untuk dikerjakan, tetapkan variabel di debugger untuk menyimpan konten skrip menggunakan perintah dx.

0:000> dx @$myScript = Debugger.State.Scripts.FirstSampleFunction.Contents

Gunakan evaluator ekspresi dx untuk memanggil fungsi addTwoValues.

0:000> dx @$myScript.addTwoValues(10, 41),d
@$myScript.addTwoValues(10, 41),d : 51

Anda juga dapat menggunakan alias bawaan @$scriptContents untuk bekerja dengan skrip. Alias @$scriptContents menggabungkan semua . Konten semua skrip yang dimuat.

0:001> dx @$scriptContents.addTwoValues(10, 40),d
@$scriptContents.addTwoValues(10, 40),d : 50

Ketika Anda selesai bekerja dengan skrip, gunakan perintah .scriptunload untuk membongkar skrip.

0:000> .scriptunload c:\WinDbg\Scripts\FirstSampleFunction.js
JavaScript script successfully unloaded from 'c:\WinDbg\Scripts\FirstSampleFunction.js'

Automasi Perintah Debugger

Bagian ini menjelaskan cara membuat dan menjalankan skrip debugger JavaScript sederhana yang mengotomatiskan pengiriman perintah u (Tidak Dirakit). Sampel juga menunjukkan cara mengumpulkan dan menampilkan output perintah dalam perulangan.

Skrip ini menyediakan satu fungsi, RunCommands().

// WinDbg JavaScript sample
// Shows how to call a debugger command and display results
"use strict";

function RunCommands()
{
var ctl = host.namespace.Debugger.Utility.Control;   
var output = ctl.ExecuteCommand("u");
host.diagnostics.debugLog("***> Displaying command output \n");

for (var line of output)
   {
   host.diagnostics.debugLog("  ", line, "\n");
   }

host.diagnostics.debugLog("***> Exiting RunCommands Function \n");

}

Menggunakan editor teks seperti Notepad untuk membuat file teks bernama RunCommands.js

Gunakan perintah .scriptload untuk memuat skrip RunCommands.

0:000> .scriptload c:\WinDbg\Scripts\RunCommands.js 
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\RunCommands.js'

Setelah skrip dimuat, fungsionalitas tambahan tersedia di debugger. Gunakan perintah dx (Tampilkan Ekspresi NatVis) untuk menampilkan Debugger.State.Scripts.RunCommands untuk melihat bahwa skrip kami sekarang tinggal.

0:000>dx -r3 Debugger.State.Scripts.RunCommands
Debugger.State.Scripts.RunCommands                
    Contents         : [object Object]
        host             : [object Object]
            diagnostics      : [object Object]
            namespace       
            currentSession   : Live user mode: <Local>
            currentProcess   : notepad.exe
            currentThread    : ntdll!DbgUiRemoteBreakin (00007ffd`87f2f440) 
            memory           : [object Object]

Gunakan perintah dx untuk memanggil fungsi RunCommands dalam skrip RunCommands.

0:000> dx Debugger.State.Scripts.RunCommands.Contents.RunCommands()
  ***> Displaying command output
  ntdll!ExpInterlockedPopEntrySListEnd+0x17 [d:\rs1\minkernel\ntos\rtl\amd64\slist.asm @ 196]:
  00007ffd`87f06e67 cc              int     3
  00007ffd`87f06e68 cc              int     3
  00007ffd`87f06e69 0f1f8000000000  nop     dword ptr [rax]
  ntdll!RtlpInterlockedPushEntrySList [d:\rs1\minkernel\ntos\rtl\amd64\slist.asm @ 229]:
  00007ffd`87f06e70 0f0d09          prefetchw [rcx]
  00007ffd`87f06e73 53              push    rbx
  00007ffd`87f06e74 4c8bd1          mov     r10,rcx
  00007ffd`87f06e77 488bca          mov     rcx,rdx
  00007ffd`87f06e7a 4c8bda          mov     r11,rdx
***> Exiting RunCommands Function

Fungsi Debugger JavaScript Khusus

Ada beberapa fungsi khusus dalam skrip JavaScript yang disebut oleh penyedia skrip itu sendiri.

inisialisasiScript

Ketika skrip JavaScript dimuat dan dijalankan, skrip melewati serangkaian langkah sebelum variabel, fungsi, dan objek lain dalam skrip memengaruhi model objek debugger.

  • Skrip dimuat ke dalam memori dan diurai.
  • Kode akar dalam skrip dijalankan.
  • Jika skrip memiliki metode yang disebut initializeScript, metode tersebut dipanggil.
  • Nilai pengembalian dari initializeScript digunakan untuk menentukan cara mengubah model objek debugger secara otomatis.
  • Nama-nama dalam skrip dihubungkan ke namespace layanan debugger.

Seperti disebutkan, inisialisasiScript akan segera dipanggil setelah kode akar skrip dijalankan. Tugasnya adalah mengembalikan array JavaScript objek pendaftaran ke penyedia yang menunjukkan cara memodifikasi model objek debugger.

function initializeScript()
{
    // Add code here that you want to run every time the script is loaded. 
    // We will just send a message to indicate that function was called.
    host.diagnostics.debugLog("***> initializeScript was called\n");
}

invokeScript

Metode invokeScript adalah metode skrip utama dan dipanggil ketika .scriptload dan .scriptrun dijalankan.

function invokeScript()
{
    // Add code here that you want to run every time the script is executed. 
    // We will just send a message to indicate that function was called.
    host.diagnostics.debugLog("***> invokeScript was called\n");
}

uninitializeScript

Metode uninitializeScript adalah kebalikan perilaku dari initializeScript. Ini dipanggil ketika skrip dilepas tautannya dan bersiap untuk membongkar. Tugasnya adalah untuk membatalkan perubahan apa pun pada model objek yang dibuat skrip secara imperatif selama eksekusi dan/atau untuk menghancurkan objek apa pun yang di-cache skrip.

Jika skrip tidak membuat manipulasi imperatif ke model objek atau hasil cache, skrip tidak perlu memiliki metode uninitializeScript. Setiap perubahan pada model objek yang dilakukan seperti yang ditunjukkan oleh nilai pengembalian initializeScript dibatalkan secara otomatis oleh penyedia. Perubahan tersebut tidak memerlukan metode uninitializeScript eksplisit.

function uninitializeScript()
{
    // Add code here that you want to run every time the script is unloaded. 
    // We will just send a message to indicate that function was called.
    host.diagnostics.debugLog("***> uninitialize was called\n");
}

Ringkasan Fungsi yang Dipanggil oleh Perintah Skrip

Tabel ini meringkas fungsi mana yang dipanggil oleh perintah skrip

Perintah .scriptload .scriptrun (Jalankan Skrip) .scriptunload (Skrip Unload)
akar yes yes
inisialisasiScript yes yes
invokeScript yes
uninitializeScript yes

Gunakan kode sampel ini untuk melihat kapan setiap fungsi dipanggil saat skrip dimuat, dijalankan, dan dibongkar.

// Root of Script
host.diagnostics.debugLog("***>; Code at the very top (root) of the script is always run \n");


function initializeScript()
{
    // Add code here that you want to run every time the script is loaded. 
    // We will just send a message to indicate that function was called.
    host.diagnostics.debugLog("***>; initializeScript was called \n");
}

function invokeScript()
{
    // Add code here that you want to run every time the script is executed. 
    // We will just send a message to indicate that function was called.
    host.diagnostics.debugLog("***>; invokeScript was called \n");
}


function uninitializeScript()
{
    // Add code here that you want to run every time the script is unloaded. 
    // We will just send a message to indicate that function was called.
    host.diagnostics.debugLog("***>; uninitialize was called\n");
}


function main()
{
    // main is just another function name in JavaScript
    // main is not called by .scriptload or .scriptrun  
    host.diagnostics.debugLog("***>; main was called \n");
}

Membuat Visualizer Debugger di JavaScript

File visualisasi kustom memungkinkan Anda mengelompokkan dan menata data dalam struktur visualisasi yang lebih mencerminkan hubungan data dan konten. Anda dapat menggunakan ekstensi debugger JavaScript untuk menulis visualizer debugger yang bertindak dengan cara yang sangat mirip dengan NatVis. Ini dicapai melalui penulisan objek prototipe JavaScript (atau kelas ES6) yang bertindak sebagai visualizer untuk jenis data tertentu. Untuk informasi selengkapnya tentang NatVis dan debugger lihat dx (Tampilkan Ekspresi NatVis).

Contoh kelas - Simple1DArray

Pertimbangkan contoh kelas C++ yang mewakili array dimensi tunggal. Kelas ini memiliki dua anggota, m_size yang merupakan ukuran keseluruhan array, dan m_pValues yang merupakan penunjuk ke sejumlah ints dalam memori yang sama dengan bidang m_size.

class Simple1DArray
{
private:

    ULONG64 m_size;
    int *m_pValues;
};

Kita dapat menggunakan perintah dx untuk melihat penyajian struktur data default.

0:000> dx g_array1D
g_array1D                 [Type: Simple1DArray]
    [+0x000] m_size           : 0x5 [Type: unsigned __int64]
    [+0x008] m_pValues        : 0x8be32449e0 : 0 [Type: int *]

JavaScript Visualizer

Untuk memvisualisasikan jenis ini, kita perlu menulis kelas prototipe (atau ES6) yang memiliki semua bidang dan properti yang kita inginkan untuk ditampilkan debugger. Kita juga harus memiliki metode initializeScript mengembalikan objek yang memberi tahu penyedia JavaScript untuk menautkan prototipe kita sebagai visualizer untuk jenis yang diberikan.

function initializeScript()
{
    //
    // Define a visualizer class for the object.
    //
    class myVisualizer
    {
        //
        // Create an ES6 generator function which yields back all the values in the array.
        //
        *[Symbol.iterator]()
        {
            var size = this.m_size;
            var ptr = this.m_pValues;
            for (var i = 0; i < size; ++i)
            {
                yield ptr.dereference();

                //
                // Note that the .add(1) method here is effectively doing pointer arithmetic on
                // the underlying pointer.  It is moving forward by the size of 1 object.
                //
                ptr = ptr.add(1);
            }
        }
    }

    return [new host.typeSignatureRegistration(myVisualizer, "Simple1DArray")];
}

Simpan skrip dalam file bernama arrayVisualizer.js.

Gunakan perintah .load (Load Extension DLL) untuk memuat penyedia JavaScript.

0:000> .load C:\ScriptProviders\jsprovider.dll

Gunakan .scriptload untuk memuat skrip visualizer array.

0:000> .scriptload c:\WinDbg\Scripts\arrayVisualizer.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\arrayVisualizer.js'

Sekarang, ketika perintah dx digunakan, visualizer skrip akan menampilkan baris konten array.

0:000> dx g_array1D
g_array1D                 : [object Object] [Type: Simple1DArray]
    [<Raw View>]     [Type: Simple1DArray]
    [0x0]            : 0x0
    [0x1]            : 0x1
    [0x2]            : 0x2
    [0x3]            : 0x3
    [0x4]            : 0x4

Selain itu, visualisasi JavaScript ini menyediakan fungsionalitas LINQ, seperti Pilih.

0:000> dx g_array1D.Select(n => n * 3),d
g_array1D.Select(n => n * 3),d                
    [0]              : 0
    [1]              : 3
    [2]              : 6
    [3]              : 9
    [4]              : 12

Apa yang Memengaruhi Visualisasi

Prototipe atau kelas yang dibuat visualizer untuk jenis asli melalui pengembalian objek host.typeSignatureRegistration dari initializeScript akan memiliki semua properti dan metode dalam JavaScript yang ditambahkan ke jenis asli. Selain itu, semantik berikut berlaku:

  • Nama apa pun yang tidak dimulai dengan dua garis bawah (__) akan tersedia dalam visualisasi.

  • Nama yang merupakan bagian dari objek JavaScript standar atau merupakan bagian dari protokol yang dibuat penyedia JavaScript tidak akan muncul dalam visualisasi.

  • Objek dapat dibuat iterable melalui dukungan [Symbol.iterator].

  • Objek dapat dibuat dapat diindeks melalui dukungan protokol kustom yang terdiri dari beberapa fungsi: getDimensionality, getValueAt, dan opsional setValueAt.

Jembatan Objek Asli dan JavaScript

Jembatan antara JavaScript dan model objek debugger adalah dua arah. Objek asli dapat diteruskan ke objek JavaScript dan JavaScript dapat diteruskan ke evaluator ekspresi Debugger. Sebagai contoh dari ini, pertimbangkan penambahan metode berikut dalam skrip kami:

function multiplyBySeven(val)
{
    return val * 7;
}

Metode ini sekarang dapat digunakan dalam contoh kueri LINQ di atas. Pertama, kita memuat visualisasi JavaScript.

0:000> .scriptload c:\WinDbg\Scripts\arrayVisualizer2.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\arrayVisualizer2.js'

0:000> dx @$myScript = Debugger.State.Scripts.arrayVisualizer2.Contents

Kemudian kita dapat menggunakan fungsi multiplyBySeven sebaris seperti yang ditunjukkan di bawah ini.

0:000> dx g_array1D.Select(@$myScript.multiplyBySeven),d
g_array1D.Select(@$myScript.multiplyBySeven),d                
    [0]              : 0
    [1]              : 7
    [2]              : 14
    [3]              : 21
    [4]              : 28

Titik Henti Bersyarah dengan JavaScript

Anda dapat menggunakan JavaScript untuk melakukan pemrosesan tambahan setelah titik henti tertembak. Misalnya, skrip dapat digunakan untuk memeriksa nilai run time lainnya lalu menentukan apakah Anda ingin secara otomatis melanjutkan eksekusi kode atau menghentikan dan melakukan penelusuran kesalahan manual tambahan.

Untuk informasi umum tentang bekerja dengan titik henti, lihat Metode Mengontrol Titik Henti.

DebugHandler.js Contoh Skrip Pemrosesan Titik Henti

Contoh ini akan mengevaluasi dialog buka dan simpan notepad: notepad! ShowOpenSaveDialog. Skrip ini akan mengevaluasi variabel pszCaption untuk menentukan apakah dialog saat ini adalah dialog "Buka" atau apakah itu adalah dialog "Simpan Sebagai". Jika dialog terbuka, eksekusi kode akan dilanjutkan. Jika itu adalah dialog simpan sebagai, eksekusi kode akan berhenti, dan debugger akan masuk.

 // Use JavaScript strict mode 
"use strict";

// Define the invokeScript method to handle breakpoints

 function invokeScript()
 {
    var ctl = host.namespace.Debugger.Utility.Control;

    //Get the address of my string
    var address = host.evaluateExpression("pszCaption");

    // The open and save dialogs use the same function
    // When we hit the open dialog, continue.
    // When we hit the save dialog, break.
    if (host.memory.readWideString(address) == "Open") {
        // host.diagnostics.debugLog("We're opening, let's continue!\n");
        ctl.ExecuteCommand("gc");
    }
    else
    {
        //host.diagnostics.debugLog("We're saving, let's break!\n");
    }
  }

Perintah ini mengatur titik henti di notepad! ShowOpenSaveDialog, dan akan menjalankan skrip di atas setiap kali titik henti tersebut tertembak.

bp notepad!ShowOpenSaveDialog ".scriptrun C:\\WinDbg\\Scripts\\DebugHandler.js"

Kemudian ketika opsi Simpan File > dipilih di notepad, skrip dijalankan, perintah g tidak dikirim, dan hentian eksekusi kode terjadi.

JavaScript script successfully loaded from 'C:\WinDbg\Scripts\DebugHandler.js'
notepad!ShowOpenSaveDialog:
00007ff6`f9761884 48895c2408      mov     qword ptr [rsp+8],rbx ss:000000db`d2a9f2f0=0000021985fe2060

Bekerja Dengan Nilai 64-Bit di Ekstensi JavaScript

Bagian ini menjelaskan bagaimana nilai 64-bit diteruskan ke ekstensi debugger JavaScript berperilaku. Masalah ini muncul karena JavaScript hanya memiliki kemampuan untuk menyimpan angka menggunakan 53-bit.

Penyimpanan 53-Bit 64-Bit dan JavaScript

Nilai ordinal yang diteruskan ke JavaScript biasanya dinamai sebagai angka JavaScript. Masalahnya adalah bahwa angka JavaScript adalah nilai titik mengambang presisi ganda 64-bit. Setiap ordinal lebih dari 53-bit akan kehilangan presisi pada entri ke JavaScript. Ini menyajikan masalah untuk pointer 64-bit dan nilai ordinal 64-bit lainnya yang mungkin memiliki bendera dalam byte tertinggi. Untuk menangani hal ini, nilai asli 64-bit apa pun (baik dari kode asli atau model data) yang memasukkan JavaScript sebagai jenis pustaka -- bukan sebagai nomor JavaScript. Jenis pustaka ini akan melakukan pulang pergi kembali ke kode asli tanpa kehilangan presisi numerik.

Konversi Otomatis

Jenis pustaka untuk nilai ordinal 64-bit mendukung konversi nilai JavaScriptof standar. Jika objek digunakan dalam operasi matematika atau konstruksi lain yang memerlukan konversi nilai, objek akan secara otomatis dikonversi ke nomor JavaScript. Jika hilangnya presisi akan terjadi (nilai menggunakan lebih dari 53-bit presisi ordinal), penyedia JavaScript akan memberikan pengecualian.

Perhatikan bahwa jika Anda menggunakan operator bitwise di JavaScript, Anda lebih lanjut dibatasi hingga 32-bit presisi ordinal.

Kode sampel ini menjumlahkan dua angka dan akan digunakan untuk menguji konversi nilai 64 bit.

function playWith64BitValues(a64, b64)
{
    // Sum two numbers to demonstrate 64-bit behavior.
    //
    // Imagine a64==100, b64==1000
    // The below would result in sum==1100 as a JavaScript number.  No exception is thrown.  The values auto-convert.
    //
    // Imagine a64==2^56, b64=1
    // The below will **Throw an Exception**.  Conversion to numeric results in loss of precision!
    //
    var sum = a64 + b64;
    host.diagnostics.debugLog("Sum   >> ", sum, "\n");

}

function performOp64BitValues(a64, b64, op)
{
    //
    // Call a data model method passing 64-bit value.  There is no loss of precision here.  This round trips perfectly.
    // For example:
    //  0:000> dx @$myScript.playWith64BitValues(0x4444444444444444ull, 0x3333333333333333ull, (x, y) => x + y)
    //  @$myScript.playWith64BitValues(0x4444444444444444ull, 0x3333333333333333ull, (x, y) => x + y) : 0x7777777777777777
    //
    return op(a64, b64);
}

Menggunakan editor teks seperti Notepad untuk membuat file teks bernama PlayWith64BitValues.js

Gunakan perintah .scriptload untuk memuat skrip.

0:000> .scriptload c:\WinDbg\Scripts\PlayWith64BitValues.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\PlayWith64BitValues.js'

Untuk membuat skrip sedikit lebih nyaman untuk dikerjakan, tetapkan variabel di debugger untuk menyimpan konten skrip menggunakan perintah dx.

0:000> dx @$myScript = Debugger.State.Scripts.PlayWith64BitValues.Contents

Gunakan evaluator ekspresi dx untuk memanggil fungsi addTwoValues.

Pertama kita akan menghitung nilai 2^53 =9007199254740992 (Hex 0x20000000000000).

Pertama untuk menguji, kita akan menggunakan (2^53) - 2 dan melihat bahwa itu mengembalikan nilai yang benar untuk jumlah.

0:000> dx @$myScript.playWith64BitValues(9007199254740990, 9007199254740990)
Sum   >> 18014398509481980

Kemudian kita akan menghitung (2^53) -1 =9007199254740991. Ini mengembalikan kesalahan yang menunjukkan bahwa proses konversi akan kehilangan presisi, jadi ini adalah nilai terbesar yang dapat digunakan dengan metode penjumlahan dalam kode JavaScript.

0:000> dx @$myScript.playWith64BitValues(9007199254740990, 9007199254740991)
Error: 64 bit value loses precision on conversion to number

Panggil metode model data yang meneruskan nilai 64-bit. Tidak ada kehilangan presisi di sini.

0:001> dx @$myScript.performOp64BitValues( 0x7FFFFFFFFFFFFFFF,  0x7FFFFFFFFFFFFFFF, (x, y) => x + y)
@$myScript.performOp64BitValues( 0x7FFFFFFFFFFFFFFF,  0x7FFFFFFFFFFFFFFF, (x, y) => x + y) : 0xfffffffffffffffe

Perbandingan

Jenis pustaka 64-bit adalah objek JavaScript dan bukan jenis nilai seperti nomor JavaScript. Ini memiliki beberapa implikasi untuk operasi perbandingan. Biasanya, kesetaraan (==) pada objek akan menunjukkan bahwa operand merujuk ke objek yang sama dan bukan nilai yang sama. Penyedia JavaScript mengurangi ini dengan melacak referensi langsung ke nilai 64-bit dan mengembalikan objek "tidak dapat diubah" yang sama untuk nilai 64-bit yang tidak dikumpulkan. Ini berarti bahwa untuk perbandingan, hal berikut akan terjadi.

// Comparison with 64 Bit Values

function comparisonWith64BitValues(a64, b64)
{
    //
    // No auto-conversion occurs here.  This is an *EFFECTIVE* value comparison.  This works with ordinals with above 53-bits of precision.
    //
    var areEqual = (a64 == b64);
    host.diagnostics.debugLog("areEqual   >> ", areEqual, "\n");
    var areNotEqual = (a64 != b64);
    host.diagnostics.debugLog("areNotEqual   >> ", areNotEqual, "\n");

    //
    // Auto-conversion occurs here.  This will throw if a64 does not pack into a JavaScript number with no loss of precision.
    //
    var isEqualTo42 = (a64 == 42);
    host.diagnostics.debugLog("isEqualTo42   >> ", isEqualTo42, "\n");
    var isLess = (a64 < b64);
    host.diagnostics.debugLog("isLess   >> ", isLess, "\n");

Menggunakan editor teks seperti Notepad untuk membuat file teks bernama ComparisonWith64BitValues.js

Gunakan perintah .scriptload untuk memuat skrip.

0:000> .scriptload c:\WinDbg\Scripts\ComparisonWith64BitValues.js
JavaScript script successfully loaded from 'c:\WinDbg\Scripts\ComparisonWith64BitValues.js'

Untuk membuat skrip sedikit lebih nyaman untuk dikerjakan, tetapkan variabel di debugger untuk menyimpan konten skrip menggunakan perintah dx.

0:000> dx @$myScript = Debugger.State.Scripts.comparisonWith64BitValues.Contents

Pertama untuk menguji, kita akan menggunakan (2^53) - 2 dan melihat bahwa itu mengembalikan nilai yang diharapkan.

0:001> dx @$myScript.comparisonWith64BitValues(9007199254740990, 9007199254740990)
areEqual   >> true
areNotEqual   >> false
isEqualTo42   >> false
isLess   >> false

Kami juga akan mencoba angka 42 sebagai nilai pertama untuk memvalidasi operator perbandingan berfungsi sebagaimana mestinya.

0:001> dx @$myScript.comparisonWith64BitValues(42, 9007199254740990)
areEqual   >> false
areNotEqual   >> true
isEqualTo42   >> true
isLess   >> true

Kemudian kita akan menghitung (2^53) -1 =9007199254740991. Nilai ini mengembalikan kesalahan yang menunjukkan bahwa proses konversi akan kehilangan presisi, jadi ini adalah nilai terbesar yang dapat digunakan dengan operator perbandingan dalam kode JavaScript.

0:000> dx @$myScript.playWith64BitValues(9007199254740990, 9007199254740991)
Error: 64 bit value loses precision on conversion to number

Mempertahankan Presisi dalam Operasi

Untuk memungkinkan ekstensi debugger mempertahankan presisi, serangkaian fungsi matematika diproyeksikan di atas jenis pustaka 64-bit. Jika kebutuhan ekstensi (atau mungkin) membutuhkan presisi di atas 53-bit untuk nilai 64-bit yang masuk, metode berikut harus digunakan alih-alih mengandalkan operator standar:

Nama Metode Tanda tangan Keterangan
asNumber .asNumber() Mengonversi nilai 64-bit menjadi angka JavaScript. Jika hilangnya presisi terjadi, **PENGECUALIAN DILEMPARKAN**
convertToNumber .convertToNumber() Mengonversi nilai 64-bit menjadi angka JavaScript. Jika kehilangan presisi terjadi, **TIDAK ADA PENGECUALIAN YANG DILEMPARKAN**
getLowPart .getLowPart() Mengonversi 32-bit yang lebih rendah dari nilai 64-bit ke angka JavaScript
getHighPart .getHighPart() Mengonversi nilai 32-bit tinggi dari nilai 64-bit ke angka JavaScript
tambahkan .add(value) Menambahkan nilai ke nilai 64-bit dan mengembalikan hasilnya
Mengurangi .subtract(value) Mengurangi nilai dari nilai 64-bit dan mengembalikan hasilnya
multiply .multiply(value) Mengalikan nilai 64-bit dengan nilai yang disediakan dan mengembalikan hasilnya
divide .divide(value) Membagi nilai 64-bit dengan nilai yang disediakan dan mengembalikan hasilnya
bitwiseAnd .bitwiseAnd(value) Menghitung bitwise dan dari nilai 64-bit dengan nilai yang disediakan dan mengembalikan hasilnya
bitwiseOr .bitwiseOr(value) Menghitung bitwise atau dari nilai 64-bit dengan nilai yang disediakan dan mengembalikan hasilnya
bitwiseXor .bitwiseXor(value) Menghitung xor bitwise dari nilai 64-bit dengan nilai yang disediakan dan mengembalikan hasilnya
bitwiseShiftLeft .bitwiseShiftLeft(value) Menggeser nilai 64-bit yang ditinggalkan oleh jumlah yang diberikan dan mengembalikan hasilnya
bitwiseShiftRight .bitwiseShiftRight(value) Menggeser nilai 64-bit ke kanan dengan jumlah yang diberikan dan mengembalikan hasilnya
toString .toString([radix]) Mengonversi nilai 64-bit menjadi string tampilan dalam radix default (atau radix yang disediakan secara opsional)

Metode ini juga tersedia.

Nama Metode Tanda tangan Keterangan
compareTo .compareTo(value) Membandingkan nilai 64-bit dengan nilai 64-bit lainnya.

Penelusuran Kesalahan JavaScript

Bagian ini menjelaskan cara menggunakan kemampuan debugging skrip debugger. Debugger memiliki dukungan terintegrasi untuk men-debug skrip JavaScript menggunakan perintah .scriptdebug (Debug JavaScript).

Catatan

Untuk menggunakan Penelusuran Kesalahan JavaScript dengan WinDbg, jalankan debugger sebagai Administrator.

Gunakan kode sampel ini untuk menjelajahi penelusuran kesalahan JavaScript. Untuk panduan ini, kami akan menamainya DebuggableSample.js dan menyimpannya di direktori C:\MyScripts.

"use strict";

class myObj
{
    toString()
    {
        var x = undefined[42];
        host.diagnostics.debugLog("BOO!\n");
    }
}

class iterObj
{
    *[Symbol.iterator]()
    {
        throw new Error("Oopsies!");
    }
}

function foo()
{
    return new myObj();
}

function iter()
{
    return new iterObj();
}

function throwAndCatch()
{
    var outer = undefined;
    var someObj = {a : 99, b : {c : 32, d: "Hello World"} };
    var curProc = host.currentProcess;
    var curThread = host.currentThread;

    try
    {
        var x = undefined[42];
    } catch(e) 
    {
        outer = e;
    }

    host.diagnostics.debugLog("This is a fun test\n");
    host.diagnostics.debugLog("Of the script debugger\n");
    var foo = {a : 99, b : 72};
    host.diagnostics.debugLog("foo.a = ", foo.a, "\n");

    return outer;
}

function throwUnhandled()
{
    var proc = host.currentProcess;
    var thread = host.currentThread;
    host.diagnostics.debugLog("Hello...  About to throw an exception!\n");
    throw new Error("Oh me oh my!  This is an unhandled exception!\n");
    host.diagnostics.debugLog("Oh...  this will never be hit!\n");
    return proc;
}

function outer()
{
    host.diagnostics.debugLog("inside outer!\n");
    var foo = throwAndCatch();
    host.diagnostics.debugLog("Caught and returned!\n");
    return foo;
}

function outermost()
{
    var x = 99;
    var result = outer();
    var y = 32;
    host.diagnostics.debugLog("Test\n");
    return result;
}

function initializeScript()
{
    //
    // Return an array of registration objects to modify the object model of the debugger
    // See the following for more details:
    //
    //     https://aka.ms/JsDbgExt
    //
}

Muat sampel skrip.

.scriptload C:\MyScripts\DebuggableSample.js

Mulai debug skrip secara aktif menggunakan perintah .scriptdebug .

0:000> .scriptdebug C:\MyScripts\DebuggableSample.js
>>> ****** DEBUGGER ENTRY DebuggableSample ******
           No active debug event!

>>> Debug [DebuggableSample <No Position>] >

Setelah Anda melihat perintah >>> Debug [DebuggableSample <No Position>] > dan permintaan input, Anda berada di dalam debugger skrip.

Gunakan perintah .help untuk menampilkan daftar perintah di lingkungan Debugging JavaScript.

>>> Debug [DebuggableSample <No Position>] >.help
Script Debugger Commands (*NOTE* IDs are **PER SCRIPT**):
    ? .................................. Get help
    ? <expr>  .......................... Evaluate expression <expr> and display result
    ?? <expr>  ......................... Evaluate expression <expr> and display result
    |  ................................. List available scripts
    |<scriptid>s  ...................... Switch context to the given script
    bc \<bpid\>  ......................... Clear breakpoint by specified \<bpid\>
    bd \<bpid\>  ......................... Disable breakpoint by specified \<bpid\>
    be \<bpid\>  ......................... Enable breakpoint by specified \<bpid\>
    bl  ................................ List breakpoints
    bp <line>:<column>  ................ Set breakpoint at the specified line and column
    bp <function-name>  ................ Set breakpoint at the (global) function specified by the given name
    bpc  ............................... Set breakpoint at current location
    dv  ................................ Display local variables of current frame
    g  ................................. Continue script
    gu   ............................... Step out
    k  ................................. Get stack trace
    p  ................................. Step over
    q  ................................. Exit script debugger (resume execution)
    sx  ................................ Display available events/exceptions to break on
    sxe <event>  ....................... Enable break on <event>
    sxd <event>  ....................... Disable break on <event>
    t  ................................. Step in
    .attach <scriptId>  ................ Attach debugger to the script specified by <scriptId>
    .detach [<scriptId>]  .............. Detach debugger from the script specified by <scriptId>
    .frame <index>  .................... Switch to frame number <index>
    .f+  ............................... Switch to next stack frame
    .f-  ............................... Switch to previous stack frame
    .help  ............................. Get help

Gunakan perintah debugger skrip sx untuk melihat daftar peristiwa yang dapat kita jebak.

>>> Debug [DebuggableSample <No Position>] >sx              
sx                                                          
    ab  [   inactive] .... Break on script abort            
    eh  [   inactive] .... Break on any thrown exception    
    en  [   inactive] .... Break on entry to the script     
    uh  [     active] .... Break on unhandled exception     

Gunakan perintah debugger skrip sxe untuk mengaktifkan pemutusan entri sehingga skrip akan terperangkap ke debugger skrip segera setelah kode apa pun di dalamnya dijalankan.

>>> Debug [DebuggableSample <No Position>] >sxe en          
sxe en                                                      
Event filter 'en' is now active                             

Keluar dari debugger skrip dan kita akan melakukan panggilan fungsi ke dalam skrip yang akan terjebak ke debugger.

>>> Debug [DebuggableSample <No Position>] >q

Pada titik ini, Anda kembali ke debugger normal. Jalankan perintah berikut untuk memanggil skrip.

dx @$scriptContents.outermost()

Sekarang, Anda kembali ke debugger skrip dan rusak di baris pertama fungsi JavaScript terluar.

>>> ****** SCRIPT BREAK DebuggableSample [BreakIn] ******   
           Location: line = 73, column = 5                  
           Text: var x = 99                                 

>>> Debug [DebuggableSample 73:5] >                         

Selain melihat pembobolan debugger, Anda mendapatkan informasi tentang baris (73) dan kolom (5) tempat jeda berlangsung serta cuplikan kode sumber yang relevan: var x = 99.

Mari kita langkah beberapa kali dan sampai ke tempat lain dalam skrip.

    p
    t
    p
    t
    p
    p

Pada titik ini, Anda harus dipecah menjadi metode throwAndCatch pada baris 34.

...
>>> ****** SCRIPT BREAK DebuggableSample [Step Complete] ******                       
           Location: line = 34, column = 5                                            
           Text: var curProc = host.currentProcess                                    

Anda dapat memverifikasi ini dengan menjalankan pelacakan tumpukan.

>>> Debug [DebuggableSample 34:5] >k                                                  
k                                                                                     
    ##  Function                         Pos    Source Snippet                        
-> [00] throwAndCatch                    034:05 (var curProc = host.currentProcess)   
   [01] outer                            066:05 (var foo = throwAndCatch())           
   [02] outermost                        074:05 (var result = outer())                

Dari sini, Anda dapat menyelidiki nilai variabel.

>>> Debug [DebuggableSample 34:5] >??someObj                
??someObj                                                   
someObj          : {...}                                    
    __proto__        : {...}                                
    a                : 0x63                                 
    b                : {...}                                
>>> Debug [DebuggableSample 34:5] >??someObj.b              
??someObj.b                                                 
someObj.b        : {...}                                    
    __proto__        : {...}                                
    c                : 0x20                                 
    d                : Hello World                          

Mari kita atur titik henti pada baris kode saat ini dan melihat titik henti apa yang sekarang diatur.

>>> Debug [DebuggableSample 34:5] >bpc                      
bpc                                                         
Breakpoint 1 set at 34:5                                    
>>> Debug [DebuggableSample 34:5] >bl                       
bl                                                          
      Id State    Pos                                       
       1 enabled  34:5                                      

Dari sini, kita akan menonaktifkan peristiwa entri (en) menggunakan perintah debugger skrip sxd .

>>> Debug [DebuggableSample 34:5] >sxd en                                                                              
sxd en                                                                                                                 
Event filter 'en' is now inactive                                                                                      

Dan kemudian hanya pergi dan membiarkan skrip berlanjut ke akhir.

>>> Debug [DebuggableSample 34:5] >g                                                                                   
g                                                                                                                      
This is a fun test                                                                                                     
Of the script debugger                                                                                                 
foo.a = 99                                                                                                             
Caught and returned!                                                                                                   
Test                                                                                                                   
...

Jalankan metode skrip lagi dan tonton titik henti kami terpukul.

0:000> dx @$scriptContents.outermost()                                                
inside outer!                                                                         
>>> ****** SCRIPT BREAK DebuggableSample [Breakpoint 1] ******                        
           Location: line = 34, column = 5                                            
           Text: var curProc = host.currentProcess                                    

Tampilkan tumpukan panggilan.

>>> Debug [DebuggableSample 34:5] >k                                                  
k                                                                                     
    ##  Function                         Pos    Source Snippet                        
-> [00] throwAndCatch                    034:05 (var curProc = host.currentProcess)   
   [01] outer                            066:05 (var foo = throwAndCatch())           
   [02] outermost                        074:05 (var result = outer())                

Pada titik ini, kita ingin berhenti men-debug skrip ini, jadi kita melepaskannya.

>>> Debug [DebuggableSample 34:5] >.detach                  
.detach                                                     
Debugger has been detached from script!                     

Lalu ketik q untuk berhenti.

q                                                           
This is a fun test                                          
Of the script debugger                                      
foo.a = 99                                                  
Caught and returned!                                        
Test                                                        

Menjalankan fungsi lagi tidak akan lagi masuk ke debugger.

0:007> dx @$scriptContents.outermost()
inside outer!
This is a fun test
Of the script debugger
foo.a = 99
Caught and returned!
Test

JavaScript di VSCode - Menambahkan IntelliSense

Jika Anda ingin bekerja dengan objek model data debugger di VSCode, Anda dapat menggunakan file definisi yang tersedia di kit pengembangan Windows. File definisi IntelliSense menyediakan dukungan untuk semua API objek debugger host.* . Jika Anda menginstal kit di direktori default pada PC 64 bit, kit tersebut terletak di sini:

C:\Program Files (x86)\Windows Kits\10\Debuggers\x64\winext\JsProvider.d.ts

Untuk menggunakan file definisi IntelliSense di VSCode:

  1. Temukan file definisi - JSProvider.d.ts

  2. Salin file definisi ke folder yang sama dengan skrip Anda.

  3. Tambahkan /// <reference path="JSProvider.d.ts" /> ke bagian atas file skrip JavaScript Anda.

Dengan referensi tersebut dalam file JavaScript Anda, VISUAL Code akan secara otomatis memberi Anda IntelliSense pada API host yang disediakan oleh JSProvider selain struktur dalam skrip Anda. Misalnya, ketik "host." dan Anda akan melihat IntelliSense untuk semua API model debugger yang tersedia.

Sumber Daya JavaScript

Berikut ini adalah sumber daya JavaScript yang mungkin berguna saat Anda mengembangkan ekstensi penelusuran kesalahan JavaScript.

Lihat juga

Contoh Skrip JavaScript Debugger

Objek Asli di Ekstensi JavaScript