v8/tools/v8windbg
Seth Brenith af76dd6e7e [tools] Add v8windbg, a WinDbg extension for V8
Please take a look at tools/v8windbg/README.md for an overview of what
v8windbg can do and how it's structured. This platform-specific
debugging plugin makes use of the data provided by the V8 postmortem
debugging API in tools/debug_helper.

Note: This code began as https://github.com/billti/v8dbg and then moved
into the Edge repository, where I added features gradually and got code
reviews for individual changes. Now, taken in its entirety, it's an
obnoxiously large CL. I'm open to breaking it up into a few chunks if
that would be preferable.

Bug: v8:9376
Change-Id: I3e503de00bb1aea870ae83e9bd99e4e2eab9ef98
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2031700
Reviewed-by: Michael Stanton <mvstanton@chromium.org>
Reviewed-by: Tamer Tas <tmrts@chromium.org>
Reviewed-by: Tobias Tebbi <tebbi@chromium.org>
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#66319}
2020-02-18 19:16:18 +00:00
..
base [tools] Add v8windbg, a WinDbg extension for V8 2020-02-18 19:16:18 +00:00
src [tools] Add v8windbg, a WinDbg extension for V8 2020-02-18 19:16:18 +00:00
test [tools] Add v8windbg, a WinDbg extension for V8 2020-02-18 19:16:18 +00:00
BUILD.gn [tools] Add v8windbg, a WinDbg extension for V8 2020-02-18 19:16:18 +00:00
copy-prereqs.py [tools] Add v8windbg, a WinDbg extension for V8 2020-02-18 19:16:18 +00:00
README.md [tools] Add v8windbg, a WinDbg extension for V8 2020-02-18 19:16:18 +00:00

v8windbg

V8windbg is a WinDbg extension for the V8 engine. It adjusts the behavior of the Locals pane and corresponding dx commands to display useful data when inspecting V8 object types. It is intended to be as robust as possible in dumps with limited memory, and should work equally well in live sessions, crash dumps, and time travel debugging.

Building

Run autoninja v8windbg in your output directory.

Using

In WinDbgX, run .load path\to\your\output\dir\v8windbg.dll to load the extension. To inspect V8 objects, use the Locals window or the dx command as usual.

Important notes:

  • The version of v8windbg must exactly match the version and build configuration of the process you're debugging. (To find the version number of a module in a crash dump, enter lm and click the module name, or run lmDvm modulename.)
  • V8windbg relies on detailed symbols (symbol_level = 2).
  • Ensure also that WinDbg can load the symbols (.pdb file) for the module containing V8.
  • Cross-architecture debugging is possible in some cases:
    • To debug an x86 process on x64, load the x86 build of v8windbg.
    • To debug an ARM64 process on x64, load the ARM64 simulator build of v8windbg (built with target_cpu="x64" and v8_target_cpu="arm64").

As well as improving the Locals pane behavior, v8windbg also provides a few functions that can be called from within dx commands:

  • @$v8object() returns information about the fields of a tagged V8 value, passed in as a plain number like dx @$v8object(0x34f49880471). This invokes the same logic that is used for the locals pane. You may also pass a type hint as an optional second parameter if you find that v8windbg is not inferring the correct type (which can happen when the memory for the object's Map wasn't collected in a crash dump). The type hint is a fully-qualified C++ class name, like dx @$v8object(0x34f49880471, "v8::internal::JSArray").
  • @$curisolate() gets the Isolate pointer for the current thread, if the current thread has a JavaScript Isolate associated.
  • @$listchunks() returns a list of the memory chunks in the Heap for the current Isolate.

Tip:: to see what objects are present in a chunk of heap memory, you can cast it to an array of TaggedValue, like this:

dx (v8::internal::TaggedValue(*)[64])0x34f49880450

Architecture

V8windbg uses the DataModel as much as possible as opposed to the older DbgEng APIs. It uses the WRL COM APIs due to limitations in Clang's support for C++/WinRT COM.

Where possible, v8windbg uses the cross-platform v8_debug_helper library to avoid depending on V8 internals.

The source in ./base is a generic starting point for implementing a WinDbg extension. The V8-specific implementation under ./src then implements the two functions declared in dbgext.h to create and destroy the extension instance.

./src file index:

  • cur-isolate.{cc,h} implements the IModelMethod for @$curisolate().
  • list-chunks.{cc,h} implements the IModelMethod for @$listchunks(). Its result is a custom object that supports iteration and indexing.
  • local-variables.{cc,h} implements the IModelPropertyAccessor that provides content to show in the Locals pane for stack frames corresponding to builtins or runtime-generated code.
  • object-inspection.{cc,h} contains various classes that allow the debugger to show fields within V8 objects.
  • v8-debug-helper-interop.{cc,h} makes requests to the V8 postmortem debugging API, and converts the results into simple C++ structs.
  • v8windbg-extension.{cc,h} is responsible for initializing the extension and cleaning up when the extension is unloaded.

When the extension is initialized (Extension::Initialize()):

  • It registers a "parent model" for all known V8 object types, such as v8::internal::HeapObject and v8::internal::Symbol. Any time WinDbg needs to represent a value with one of these types, it creates an IModelObject representing the value and attaches the parent model. This particular parent model supports IStringDisplayableConcept and IDynamicKeyProviderConcept, meaning the debugger will call a custom method every time it wants to get a description string or a list of fields for any of these objects.
  • It registers a different parent model, with a single property getter named "Value", for handle types such as v8::internal::Handle<*>. The "Value" getter returns the correctly-typed tagged pointer contained by the handle.
  • It overrides the getter functions for "LocalVariables" and "Parameters" on the parent model for stack frames. When the user selects a stack frame, WinDbg calls these getter functions to determine what it should show in the Locals pane.
  • It registers the function aliases such as @$curisolate().

The ./test directory contains a test function that exercises v8windbg. It does not require WinDbg, but uses DbgEng.dll and DbgModel.dll from the Windows SDK (these are slightly older versions of the same modules used by WinDbg). The test function launches a separate d8 process, attaches to that process as a debugger, lets d8 run until it hits a breakpoint, and then checks the output of a few dx commands.

Debugging the extension

To debug the extension, launch a WinDbgx instance to debug with an active target, e.g.

windbgx \src\github\v8\out\x64.debug\d8.exe -e "console.log('hello');"

or

windbgx \src\github\v8\out\x64.debug\d8.exe c:\temp\test.js

The WinDbgx process itself does not host the extensions, but uses a helper process. Attach another instance of WinDbgx to the enghost.exe helper process, e.g.

windbgx -pn enghost.exe

Set a breakpoint in this second session for when the extension initializes, e.g.

bm v8windbg!DebugExtensionInitialize

..and/or whenever a function of interest is invoked, e.g.

  • bp v8windbg!CurrIsolateAlias::Call for the invocation of @$curisolate()
  • bp v8windbg!GetHeapObject for the interpretation of V8 objects.

Load the extension in the target debugger (the first WinDbg session), which should trigger the breakpoint.

.load "C:\\src\\github\\v8windbg\\x64\\v8windbg.dll"

Note: For D8, the below is a good breakpoint to set just before any script is run:

bp d8_exe!v8::Shell::ExecuteString

..or the below for once the V8 engine is entered (for component builds):

bp v8!v8::Script::Run

Then trigger the extension code of interest via something like dx source or dx @$curisolate().