Commit af76dd6e authored by Seth Brenith's avatar Seth Brenith Committed by Commit Bot

[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/+/2031700Reviewed-by: 's avatarMichael Stanton <mvstanton@chromium.org>
Reviewed-by: 's avatarTamer Tas <tmrts@chromium.org>
Reviewed-by: 's avatarTobias Tebbi <tebbi@chromium.org>
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#66319}
parent 6ce65b96
...@@ -233,6 +233,25 @@ template("v8_component") { ...@@ -233,6 +233,25 @@ template("v8_component") {
} }
} }
template("v8_shared_library") {
shared_library(target_name) {
forward_variables_from(invoker,
"*",
[
"configs",
"remove_configs",
])
configs -= v8_remove_configs
configs += v8_add_configs
if (defined(invoker.remove_configs)) {
configs -= invoker.remove_configs
}
if (defined(invoker.configs)) {
configs += invoker.configs
}
}
}
template("v8_static_library") { template("v8_static_library") {
static_library(target_name) { static_library(target_name) {
complete_static_lib = true complete_static_lib = true
......
...@@ -14,17 +14,11 @@ config("cctest_config") { ...@@ -14,17 +14,11 @@ config("cctest_config") {
v8_executable("cctest") { v8_executable("cctest") {
testonly = true testonly = true
sources = [ sources = [ "cctest.cc" ]
"cctest.cc",
]
deps = [ deps = [ ":cctest_sources" ]
":cctest_sources",
]
data_deps = [ data_deps = [ "../../tools:v8_testrunner" ]
"../../tools:v8_testrunner",
]
data = [ data = [
"testcfg.py", "testcfg.py",
...@@ -58,9 +52,7 @@ v8_header_set("cctest_headers") { ...@@ -58,9 +52,7 @@ v8_header_set("cctest_headers") {
"../..:internal_config_base", "../..:internal_config_base",
] ]
sources = [ sources = [ "cctest.h" ]
"cctest.h",
]
} }
v8_source_set("cctest_sources") { v8_source_set("cctest_sources") {
...@@ -400,9 +392,7 @@ v8_source_set("cctest_sources") { ...@@ -400,9 +392,7 @@ v8_source_set("cctest_sources") {
] ]
defines = [] defines = []
deps = [ deps = [ "../..:run_torque" ]
"../..:run_torque",
]
if (v8_enable_i18n_support) { if (v8_enable_i18n_support) {
defines += [ "V8_INTL_SUPPORT" ] defines += [ "V8_INTL_SUPPORT" ]
...@@ -432,6 +422,11 @@ v8_source_set("cctest_sources") { ...@@ -432,6 +422,11 @@ v8_source_set("cctest_sources") {
# MSVS wants this for gay-{precision,shortest}.cc. # MSVS wants this for gay-{precision,shortest}.cc.
cflags += [ "/bigobj" ] cflags += [ "/bigobj" ]
if (symbol_level == 2) {
sources += [ "test-v8windbg.cc" ]
deps += [ "../../tools/v8windbg:v8windbg_test" ]
}
} }
if (v8_use_perfetto) { if (v8_use_perfetto) {
...@@ -465,7 +460,5 @@ v8_executable("generate-bytecode-expectations") { ...@@ -465,7 +460,5 @@ v8_executable("generate-bytecode-expectations") {
"//build/win:default_exe_manifest", "//build/win:default_exe_manifest",
] ]
data = [ data = [ "interpreter/bytecode_expectations/" ]
"interpreter/bytecode_expectations/",
]
} }
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "test/cctest/cctest.h"
#include "tools/v8windbg/test/v8windbg-test.h"
namespace v8 {
namespace internal {
UNINITIALIZED_TEST(V8windbg) { v8windbg_test::RunTests(); }
} // namespace internal
} // namespace v8
...@@ -14,25 +14,23 @@ group("gn_all") { ...@@ -14,25 +14,23 @@ group("gn_all") {
"gcmole:v8_run_gcmole", "gcmole:v8_run_gcmole",
"jsfunfuzz:v8_jsfunfuzz", "jsfunfuzz:v8_jsfunfuzz",
] ]
if (is_win) {
data_deps += [ "v8windbg" ]
}
} }
group("v8_check_static_initializers") { group("v8_check_static_initializers") {
data_deps = [ data_deps = [ "..:d8" ]
"..:d8",
]
data = [ data = [ "check-static-initializers.sh" ]
"check-static-initializers.sh",
]
} }
group("v8_android_test_runner_deps") { group("v8_android_test_runner_deps") {
testonly = true testonly = true
if (is_android && !build_with_chromium) { if (is_android && !build_with_chromium) {
data_deps = [ data_deps = [ "//build/android:test_runner_py" ]
"//build/android:test_runner_py",
]
data = [ data = [
# This is used by android.py, but not included by test_runner_py above. # This is used by android.py, but not included by test_runner_py above.
"//third_party/catapult/devil/devil/android/perf/", "//third_party/catapult/devil/devil/android/perf/",
...@@ -44,9 +42,9 @@ group("v8_testrunner") { ...@@ -44,9 +42,9 @@ group("v8_testrunner") {
testonly = true testonly = true
data_deps = [ data_deps = [
"..:v8_python_base",
"..:v8_dump_build_config",
":v8_android_test_runner_deps", ":v8_android_test_runner_deps",
"..:v8_dump_build_config",
"..:v8_python_base",
] ]
data = [ data = [
......
# Copyright 2020 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("../../gni/v8.gni")
config("v8windbg_config") {
# Required for successful compilation of SDK header file DbgModel.h.
cflags_cc = [ "/Zc:twoPhase-" ]
include_dirs = [ "../.." ]
}
# Basic support for WinDbg extensions, with nothing specific to V8.
source_set("v8windbg_base") {
testonly = true
sources = [
"base/dbgext.cc",
"base/dbgext.h",
"base/utilities.cc",
"base/utilities.h",
]
libs = [
"DbgEng.lib",
"DbgModel.lib",
"RuntimeObject.lib",
"comsuppwd.lib",
]
public_configs = [ ":v8windbg_config" ]
}
# An extension DLL that can be loaded into WinDbg with `.load v8windbg`.
v8_shared_library("v8windbg") {
testonly = true
sources = [
"base/dbgext.def",
"src/cur-isolate.cc",
"src/cur-isolate.h",
"src/list-chunks.cc",
"src/list-chunks.h",
"src/local-variables.cc",
"src/local-variables.h",
"src/object-inspection.cc",
"src/object-inspection.h",
"src/v8-debug-helper-interop.cc",
"src/v8-debug-helper-interop.h",
"src/v8windbg-extension.cc",
"src/v8windbg-extension.h",
]
deps = [
":v8windbg_base",
"../debug_helper:v8_debug_helper",
]
}
# Copies Windows SDK files that v8windbg_test needs.
action("copy_prereqs") {
testonly = true
script = "copy-prereqs.py"
inputs = [
script,
"//build/vs_toolchain.py",
]
outputs = [ "$root_out_dir/dbgeng.dll" ]
args = [
rebase_path("//build"),
rebase_path(root_out_dir),
target_cpu,
]
}
# A test that launches a separate d8 process and debugs it using v8windbg.
v8_source_set("v8windbg_test") {
testonly = true
sources = [
"test/debug-callbacks.cc",
"test/debug-callbacks.h",
"test/v8windbg-test.cc",
"test/v8windbg-test.h",
]
deps = [ "../..:v8_libbase" ] # For CHECK macro.
data_deps = [
":copy_prereqs",
":v8windbg",
":v8windbg_test_script",
"../..:d8",
]
libs = [
"DbgEng.lib",
"DbgModel.lib",
"Pathcch.lib",
"RuntimeObject.lib",
]
configs = [ ":v8windbg_config" ]
}
# Copies the JavaScript file used by v8windbg_test.
copy("v8windbg_test_script") {
testonly = true
sources = [ "test/script.js" ]
outputs = [ "$target_out_dir/v8windbg-test-script.js" ]
}
# 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()`.
[DataModel]: https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/data-model-cpp-overview
[DbgEng]: https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/writing-dbgeng-extension-code
[C++/WinRT COM]: https://docs.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/consume-com
[WRL COM]: https://docs.microsoft.com/en-us/cpp/cppcx/wrl/windows-runtime-cpp-template-library-wrl?view=vs-2019
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/v8windbg/base/dbgext.h"
#include <crtdbg.h>
#include <wrl/module.h>
#include "tools/v8windbg/base/utilities.h"
// See
// https://docs.microsoft.com/en-us/visualstudio/debugger/crt-debugging-techniques
// for the memory leak and debugger reporting macros used from <crtdbg.h>
_CrtMemState mem_old, mem_new, mem_diff;
int original_crt_dbg_flag = 0;
WRL::ComPtr<IDataModelManager> sp_data_model_manager;
WRL::ComPtr<IDebugHost> sp_debug_host;
WRL::ComPtr<IDebugControl5> sp_debug_control;
WRL::ComPtr<IDebugHostMemory2> sp_debug_host_memory;
WRL::ComPtr<IDebugHostSymbols> sp_debug_host_symbols;
WRL::ComPtr<IDebugHostExtensibility> sp_debug_host_extensibility;
extern "C" {
HRESULT
__stdcall DebugExtensionInitialize(PULONG /*pVersion*/, PULONG /*pFlags*/) {
original_crt_dbg_flag = _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF);
_CrtMemCheckpoint(&mem_old);
WRL::ComPtr<IDebugClient> sp_debug_client;
WRL::ComPtr<IHostDataModelAccess> sp_data_model_access;
RETURN_IF_FAIL(DebugCreate(__uuidof(IDebugClient), &sp_debug_client));
RETURN_IF_FAIL(sp_debug_client.As(&sp_data_model_access));
RETURN_IF_FAIL(sp_debug_client.As(&sp_debug_control));
RETURN_IF_FAIL(sp_data_model_access->GetDataModel(&sp_data_model_manager,
&sp_debug_host));
RETURN_IF_FAIL(sp_debug_host.As(&sp_debug_host_memory));
RETURN_IF_FAIL(sp_debug_host.As(&sp_debug_host_symbols));
RETURN_IF_FAIL(sp_debug_host.As(&sp_debug_host_extensibility));
return CreateExtension();
}
void __stdcall DebugExtensionUninitialize() {
DestroyExtension();
sp_debug_host = nullptr;
sp_data_model_manager = nullptr;
sp_debug_host_memory = nullptr;
sp_debug_host_symbols = nullptr;
sp_debug_host_extensibility = nullptr;
_CrtMemCheckpoint(&mem_new);
if (_CrtMemDifference(&mem_diff, &mem_old, &mem_new)) {
_CrtMemDumpStatistics(&mem_diff);
}
_CrtSetDbgFlag(original_crt_dbg_flag);
}
HRESULT __stdcall DebugExtensionCanUnload(void) {
if (!WRL::Module<WRL::InProc>::GetModule().Terminate()) {
_RPTF0(_CRT_WARN, "Failed to unload WRL\n");
return S_FALSE;
}
return S_OK;
}
void __stdcall DebugExtensionUnload() { return; }
} // extern "C"
EXPORTS
DebugExtensionInitialize
DebugExtensionUninitialize
DebugExtensionCanUnload
DebugExtensionUnload
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_TOOLS_V8WINDBG_BASE_DBGEXT_H_
#define V8_TOOLS_V8WINDBG_BASE_DBGEXT_H_
#if !defined(UNICODE) || !defined(_UNICODE)
#error Unicode not defined
#endif
#include <DbgEng.h>
#include <DbgModel.h>
#include <Windows.h>
#include <crtdbg.h>
#include <wrl/client.h>
#include <string>
namespace WRL = Microsoft::WRL;
// Globals for use throughout the extension. (Populated on load).
extern WRL::ComPtr<IDataModelManager> sp_data_model_manager;
extern WRL::ComPtr<IDebugHost> sp_debug_host;
extern WRL::ComPtr<IDebugControl5> sp_debug_control;
extern WRL::ComPtr<IDebugHostMemory2> sp_debug_host_memory;
extern WRL::ComPtr<IDebugHostSymbols> sp_debug_host_symbols;
extern WRL::ComPtr<IDebugHostExtensibility> sp_debug_host_extensibility;
// To be implemented by the custom extension code. (Called on load).
HRESULT CreateExtension();
void DestroyExtension();
#endif // V8_TOOLS_V8WINDBG_BASE_DBGEXT_H_
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/v8windbg/base/utilities.h"
#include <comutil.h>
#include <oleauto.h>
#include <vector>
namespace {
HRESULT BoxObject(IDataModelManager* p_manager, IUnknown* p_object,
ModelObjectKind kind, IModelObject** pp_model_object) {
*pp_model_object = nullptr;
VARIANT vt_val;
vt_val.vt = VT_UNKNOWN;
vt_val.punkVal = p_object;
HRESULT hr = p_manager->CreateIntrinsicObject(kind, &vt_val, pp_model_object);
return hr;
}
} // namespace
HRESULT CreateProperty(IDataModelManager* p_manager,
IModelPropertyAccessor* p_property,
IModelObject** pp_property_object) {
return BoxObject(p_manager, p_property, ObjectPropertyAccessor,
pp_property_object);
}
HRESULT CreateMethod(IDataModelManager* p_manager, IModelMethod* p_method,
IModelObject** pp_method_object) {
return BoxObject(p_manager, p_method, ObjectMethod, pp_method_object);
}
HRESULT UnboxProperty(IModelObject* object, IModelPropertyAccessor** result) {
ModelObjectKind kind = (ModelObjectKind)-1;
RETURN_IF_FAIL(object->GetKind(&kind));
if (kind != ObjectPropertyAccessor) return E_FAIL;
_variant_t variant;
RETURN_IF_FAIL(object->GetIntrinsicValue(&variant));
if (variant.vt != VT_UNKNOWN) return E_FAIL;
WRL::ComPtr<IModelPropertyAccessor> accessor;
RETURN_IF_FAIL(WRL::ComPtr<IUnknown>(variant.punkVal).As(&accessor));
*result = accessor.Detach();
return S_OK;
}
HRESULT CreateTypedIntrinsic(uint64_t value, IDebugHostType* type,
IModelObject** result) {
// Figure out what kind of VARIANT we need to make.
IntrinsicKind kind;
VARTYPE carrier;
RETURN_IF_FAIL(type->GetIntrinsicType(&kind, &carrier));
VARIANT vt_val;
switch (carrier) {
case VT_BOOL:
vt_val.boolVal = value ? VARIANT_TRUE : VARIANT_FALSE;
break;
case VT_I1:
vt_val.cVal = static_cast<int8_t>(value);
break;
case VT_UI1:
vt_val.bVal = static_cast<uint8_t>(value);
break;
case VT_I2:
vt_val.iVal = static_cast<int16_t>(value);
break;
case VT_UI2:
vt_val.uiVal = static_cast<uint16_t>(value);
break;
case VT_INT:
vt_val.intVal = static_cast<int>(value);
break;
case VT_UINT:
vt_val.uintVal = static_cast<unsigned int>(value);
break;
case VT_I4:
vt_val.lVal = static_cast<int32_t>(value);
break;
case VT_UI4:
vt_val.ulVal = static_cast<uint32_t>(value);
break;
case VT_INT_PTR:
vt_val.llVal = static_cast<intptr_t>(value);
break;
case VT_UINT_PTR:
vt_val.ullVal = static_cast<uintptr_t>(value);
break;
case VT_I8:
vt_val.llVal = static_cast<int64_t>(value);
break;
case VT_UI8:
vt_val.ullVal = static_cast<uint64_t>(value);
break;
default:
return E_FAIL;
}
vt_val.vt = carrier;
return sp_data_model_manager->CreateTypedIntrinsicObject(&vt_val, type,
result);
}
HRESULT CreateULong64(ULONG64 value, IModelObject** pp_int) {
HRESULT hr = S_OK;
*pp_int = nullptr;
VARIANT vt_val;
vt_val.vt = VT_UI8;
vt_val.ullVal = value;
hr = sp_data_model_manager->CreateIntrinsicObject(ObjectIntrinsic, &vt_val,
pp_int);
return hr;
}
HRESULT UnboxULong64(IModelObject* object, ULONG64* value, bool convert) {
ModelObjectKind kind = (ModelObjectKind)-1;
RETURN_IF_FAIL(object->GetKind(&kind));
if (kind != ObjectIntrinsic) return E_FAIL;
_variant_t variant;
RETURN_IF_FAIL(object->GetIntrinsicValue(&variant));
if (convert) {
RETURN_IF_FAIL(VariantChangeType(&variant, &variant, 0, VT_UI8));
}
if (variant.vt != VT_UI8) return E_FAIL;
*value = variant.ullVal;
return S_OK;
}
HRESULT CreateInt32(int value, IModelObject** pp_int) {
HRESULT hr = S_OK;
*pp_int = nullptr;
VARIANT vt_val;
vt_val.vt = VT_I4;
vt_val.intVal = value;
hr = sp_data_model_manager->CreateIntrinsicObject(ObjectIntrinsic, &vt_val,
pp_int);
return hr;
}
HRESULT CreateUInt32(uint32_t value, IModelObject** pp_int) {
HRESULT hr = S_OK;
*pp_int = nullptr;
VARIANT vt_val;
vt_val.vt = VT_UI4;
vt_val.uintVal = value;
hr = sp_data_model_manager->CreateIntrinsicObject(ObjectIntrinsic, &vt_val,
pp_int);
return hr;
}
HRESULT CreateBool(bool value, IModelObject** pp_val) {
HRESULT hr = S_OK;
*pp_val = nullptr;
VARIANT vt_val;
vt_val.vt = VT_BOOL;
vt_val.boolVal = value;
hr = sp_data_model_manager->CreateIntrinsicObject(ObjectIntrinsic, &vt_val,
pp_val);
return hr;
}
HRESULT CreateNumber(double value, IModelObject** pp_val) {
HRESULT hr = S_OK;
*pp_val = nullptr;
VARIANT vt_val;
vt_val.vt = VT_R8;
vt_val.dblVal = value;
hr = sp_data_model_manager->CreateIntrinsicObject(ObjectIntrinsic, &vt_val,
pp_val);
return hr;
}
HRESULT CreateString(std::u16string value, IModelObject** pp_val) {
HRESULT hr = S_OK;
*pp_val = nullptr;
VARIANT vt_val;
vt_val.vt = VT_BSTR;
vt_val.bstrVal =
::SysAllocString(reinterpret_cast<const OLECHAR*>(value.c_str()));
hr = sp_data_model_manager->CreateIntrinsicObject(ObjectIntrinsic, &vt_val,
pp_val);
return hr;
}
HRESULT UnboxString(IModelObject* object, BSTR* value) {
ModelObjectKind kind = (ModelObjectKind)-1;
RETURN_IF_FAIL(object->GetKind(&kind));
if (kind != ObjectIntrinsic) return E_FAIL;
_variant_t variant;
RETURN_IF_FAIL(object->GetIntrinsicValue(&variant));
if (variant.vt != VT_BSTR) return E_FAIL;
*value = variant.Detach().bstrVal;
return S_OK;
}
HRESULT GetModelAtIndex(WRL::ComPtr<IModelObject>& sp_parent,
WRL::ComPtr<IModelObject>& sp_index,
IModelObject** p_result) {
WRL::ComPtr<IIndexableConcept> sp_indexable_concept;
RETURN_IF_FAIL(sp_parent->GetConcept(__uuidof(IIndexableConcept),
&sp_indexable_concept, nullptr));
std::vector<IModelObject*> p_indexers{sp_index.Get()};
return sp_indexable_concept->GetAt(sp_parent.Get(), 1, p_indexers.data(),
p_result, nullptr);
}
HRESULT GetCurrentThread(WRL::ComPtr<IDebugHostContext>& sp_host_context,
IModelObject** p_current_thread) {
WRL::ComPtr<IModelObject> sp_boxed_context, sp_root_namespace;
WRL::ComPtr<IModelObject> sp_debugger, sp_sessions, sp_processes, sp_threads;
WRL::ComPtr<IModelObject> sp_curr_session, sp_curr_process;
RETURN_IF_FAIL(BoxObject(sp_data_model_manager.Get(), sp_host_context.Get(),
ObjectContext, &sp_boxed_context));
RETURN_IF_FAIL(sp_data_model_manager->GetRootNamespace(&sp_root_namespace));
RETURN_IF_FAIL(
sp_root_namespace->GetKeyValue(L"Debugger", &sp_debugger, nullptr));
RETURN_IF_FAIL(sp_debugger->GetKeyValue(L"Sessions", &sp_sessions, nullptr));
RETURN_IF_FAIL(
GetModelAtIndex(sp_sessions, sp_boxed_context, &sp_curr_session));
RETURN_IF_FAIL(
sp_curr_session->GetKeyValue(L"Processes", &sp_processes, nullptr));
RETURN_IF_FAIL(
GetModelAtIndex(sp_processes, sp_boxed_context, &sp_curr_process));
RETURN_IF_FAIL(
sp_curr_process->GetKeyValue(L"Threads", &sp_threads, nullptr));
return GetModelAtIndex(sp_threads, sp_boxed_context, p_current_thread);
}
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_TOOLS_V8WINDBG_BASE_UTILITIES_H_
#define V8_TOOLS_V8WINDBG_BASE_UTILITIES_H_
#include "tools/v8windbg/base/dbgext.h"
inline const wchar_t* U16ToWChar(const char16_t* p_u16) {
static_assert(sizeof(wchar_t) == sizeof(char16_t), "wrong wchar size");
return reinterpret_cast<const wchar_t*>(p_u16);
}
inline const wchar_t* U16ToWChar(std::u16string& str) {
return U16ToWChar(str.data());
}
#if defined(WIN32)
inline std::u16string ConvertToU16String(std::string utf8_string) {
int len_chars =
::MultiByteToWideChar(CP_UTF8, 0, utf8_string.c_str(), -1, nullptr, 0);
char16_t* p_buff =
static_cast<char16_t*>(malloc(len_chars * sizeof(char16_t)));
// On Windows wchar_t is the same a char16_t
static_assert(sizeof(wchar_t) == sizeof(char16_t), "wrong wchar size");
len_chars =
::MultiByteToWideChar(CP_UTF8, 0, utf8_string.c_str(), -1,
reinterpret_cast<wchar_t*>(p_buff), len_chars);
std::u16string result{p_buff};
free(p_buff);
return result;
}
#else
#error String encoding conversion must be provided for the target platform.
#endif
HRESULT CreateProperty(IDataModelManager* p_manager,
IModelPropertyAccessor* p_property,
IModelObject** pp_property_object);
HRESULT CreateMethod(IDataModelManager* p_manager, IModelMethod* p_method,
IModelObject** pp_method_object);
HRESULT UnboxProperty(IModelObject* object, IModelPropertyAccessor** result);
HRESULT CreateTypedIntrinsic(uint64_t value, IDebugHostType* type,
IModelObject** result);
HRESULT CreateULong64(ULONG64 value, IModelObject** pp_int);
HRESULT UnboxULong64(IModelObject* object, ULONG64* value,
bool convert = false);
HRESULT CreateInt32(int value, IModelObject** pp_int);
HRESULT CreateUInt32(uint32_t value, IModelObject** pp_int);
HRESULT CreateBool(bool value, IModelObject** pp_val);
HRESULT CreateNumber(double value, IModelObject** pp_val);
HRESULT CreateString(std::u16string value, IModelObject** pp_val);
HRESULT UnboxString(IModelObject* object, BSTR* value);
HRESULT GetModelAtIndex(WRL::ComPtr<IModelObject>& sp_parent,
WRL::ComPtr<IModelObject>& sp_index,
IModelObject** p_result);
HRESULT GetCurrentThread(WRL::ComPtr<IDebugHostContext>& sp_host_context,
IModelObject** p_current_thread);
#define RETURN_IF_FAIL(expression) \
do { \
HRESULT hr = expression; \
if (FAILED(hr)) { \
return hr; \
} \
} while (false)
#endif // V8_TOOLS_V8WINDBG_BASE_UTILITIES_H_
#!/usr/bin/env python
# Copyright 2020 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This program copies dbgeng.dll from the Windows SDK to the output directory,
so that we can test v8windbg. (The version of dbgeng.dll in system32, which
would be loaded otherwise, is insufficient.)
Arguments:
1. The directory that contains vs_toolchain.py
2. The directory to copy to
3. The cpu type for this build
"""
import sys
import os
vs_toolchain_dir, target_dir, target_cpu = sys.argv[1:]
sys.path.insert(0, vs_toolchain_dir)
import vs_toolchain
def CopyDebuggerFile(debug_file):
win_sdk_dir = vs_toolchain.SetEnvironmentAndGetSDKDir()
if not win_sdk_dir:
return
full_path = os.path.join(win_sdk_dir, 'Debuggers', target_cpu, debug_file)
if not os.path.exists(full_path):
return
target_path = os.path.join(target_dir, debug_file)
vs_toolchain._CopyRuntimeImpl(target_path, full_path, verbose=False)
# Ninja expects the file's timestamp to be newer than this script.
os.utime(target_path, None)
CopyDebuggerFile('dbgeng.dll')
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/v8windbg/src/cur-isolate.h"
HRESULT GetIsolateKey(WRL::ComPtr<IDebugHostContext>& sp_ctx,
int* isolate_key) {
auto sp_v8_module = Extension::Current()->GetV8Module(sp_ctx);
if (sp_v8_module == nullptr) return E_FAIL;
WRL::ComPtr<IDebugHostSymbol> sp_isolate_sym;
RETURN_IF_FAIL(sp_v8_module->FindSymbolByName(kIsolateKey, &sp_isolate_sym));
SymbolKind kind;
RETURN_IF_FAIL(sp_isolate_sym->GetSymbolKind(&kind));
if (kind != SymbolData) return E_FAIL;
WRL::ComPtr<IDebugHostData> sp_isolate_key_data;
RETURN_IF_FAIL(sp_isolate_sym.As(&sp_isolate_key_data));
Location loc;
RETURN_IF_FAIL(sp_isolate_key_data->GetLocation(&loc));
ULONG64 bytes_read;
RETURN_IF_FAIL(sp_debug_host_memory->ReadBytes(
sp_ctx.Get(), loc, isolate_key, sizeof(isolate_key), &bytes_read));
return S_OK;
}
HRESULT GetCurrentIsolate(WRL::ComPtr<IModelObject>& sp_result) {
sp_result = nullptr;
// Get the current context
WRL::ComPtr<IDebugHostContext> sp_host_context;
RETURN_IF_FAIL(sp_debug_host->GetCurrentContext(&sp_host_context));
WRL::ComPtr<IModelObject> sp_curr_thread;
RETURN_IF_FAIL(GetCurrentThread(sp_host_context, &sp_curr_thread));
WRL::ComPtr<IModelObject> sp_environment, sp_environment_block;
WRL::ComPtr<IModelObject> sp_tls_slots, sp_slot_index, sp_isolate_ptr;
RETURN_IF_FAIL(
sp_curr_thread->GetKeyValue(L"Environment", &sp_environment, nullptr));
RETURN_IF_FAIL(sp_environment->GetKeyValue(L"EnvironmentBlock",
&sp_environment_block, nullptr));
// EnvironmentBlock and TlsSlots are native types (TypeUDT) and thus
// GetRawValue rather than GetKeyValue should be used to get field (member)
// values.
ModelObjectKind kind;
RETURN_IF_FAIL(sp_environment_block->GetKind(&kind));
if (kind != ModelObjectKind::ObjectTargetObject) return E_FAIL;
RETURN_IF_FAIL(sp_environment_block->GetRawValue(SymbolField, L"TlsSlots", 0,
&sp_tls_slots));
int isolate_key = -1;
RETURN_IF_FAIL(GetIsolateKey(sp_host_context, &isolate_key));
RETURN_IF_FAIL(CreateInt32(isolate_key, &sp_slot_index));
RETURN_IF_FAIL(GetModelAtIndex(sp_tls_slots, sp_slot_index, &sp_isolate_ptr));
// Need to dereference the slot and then get the address held in it
WRL::ComPtr<IModelObject> sp_dereferenced_slot;
RETURN_IF_FAIL(sp_isolate_ptr->Dereference(&sp_dereferenced_slot));
uint64_t isolate_ptr;
RETURN_IF_FAIL(UnboxULong64(sp_dereferenced_slot.Get(), &isolate_ptr));
Location isolate_addr{isolate_ptr};
// If we got the isolate_key OK, then must have the V8 module loaded
// Get the internal Isolate type from it
WRL::ComPtr<IDebugHostType> sp_isolate_type, sp_isolate_ptr_type;
RETURN_IF_FAIL(Extension::Current()
->GetV8Module(sp_host_context)
->FindTypeByName(kIsolate, &sp_isolate_type));
RETURN_IF_FAIL(
sp_isolate_type->CreatePointerTo(PointerStandard, &sp_isolate_ptr_type));
RETURN_IF_FAIL(sp_data_model_manager->CreateTypedObject(
sp_host_context.Get(), isolate_addr, sp_isolate_type.Get(), &sp_result));
return S_OK;
}
IFACEMETHODIMP CurrIsolateAlias::Call(IModelObject* p_context_object,
ULONG64 arg_count,
IModelObject** pp_arguments,
IModelObject** pp_result,
IKeyStore** pp_metadata) noexcept {
*pp_result = nullptr;
WRL::ComPtr<IModelObject> sp_result;
RETURN_IF_FAIL(GetCurrentIsolate(sp_result));
*pp_result = sp_result.Detach();
return S_OK;
}
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_TOOLS_V8WINDBG_SRC_CUR_ISOLATE_H_
#define V8_TOOLS_V8WINDBG_SRC_CUR_ISOLATE_H_
#include <crtdbg.h>
#include <wrl/implements.h>
#include <string>
#include <vector>
#include "tools/v8windbg/base/utilities.h"
#include "tools/v8windbg/src/v8-debug-helper-interop.h"
#include "tools/v8windbg/src/v8windbg-extension.h"
HRESULT GetCurrentIsolate(WRL::ComPtr<IModelObject>& sp_result);
constexpr wchar_t kIsolateKey[] = L"isolate_key_";
constexpr wchar_t kIsolate[] = L"v8::internal::Isolate";
class CurrIsolateAlias
: public WRL::RuntimeClass<
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
IModelMethod> {
public:
IFACEMETHOD(Call)
(IModelObject* p_context_object, ULONG64 arg_count,
_In_reads_(arg_count) IModelObject** pp_arguments, IModelObject** pp_result,
IKeyStore** pp_metadata);
};
#endif // V8_TOOLS_V8WINDBG_SRC_CUR_ISOLATE_H_
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/v8windbg/src/list-chunks.h"
#include "tools/v8windbg/src/cur-isolate.h"
// v8windbg!ListChunksAlias::Call
IFACEMETHODIMP ListChunksAlias::Call(IModelObject* p_context_object,
ULONG64 arg_count,
_In_reads_(arg_count)
IModelObject** pp_arguments,
IModelObject** pp_result,
IKeyStore** pp_metadata) noexcept {
WRL::ComPtr<IDebugHostContext> sp_ctx;
RETURN_IF_FAIL(sp_debug_host->GetCurrentContext(&sp_ctx));
WRL::ComPtr<IModelObject> result;
RETURN_IF_FAIL(
sp_data_model_manager->CreateSyntheticObject(sp_ctx.Get(), &result));
auto sp_iterator{WRL::Make<MemoryChunks>()};
RETURN_IF_FAIL(result->SetConcept(
__uuidof(IIndexableConcept),
static_cast<IIndexableConcept*>(sp_iterator.Get()), nullptr));
RETURN_IF_FAIL(result->SetConcept(
__uuidof(IIterableConcept),
static_cast<IIterableConcept*>(sp_iterator.Get()), nullptr));
*pp_result = result.Detach();
if (pp_metadata) {
*pp_metadata = nullptr;
}
return S_OK;
}
ChunkData::ChunkData() = default;
ChunkData::~ChunkData() = default;
ChunkData::ChunkData(const ChunkData&) = default;
ChunkData::ChunkData(ChunkData&&) = default;
ChunkData& ChunkData::operator=(const ChunkData&) = default;
ChunkData& ChunkData::operator=(ChunkData&&) = default;
MemoryChunkIterator::MemoryChunkIterator(
WRL::ComPtr<IDebugHostContext>& host_context)
: sp_ctx_(host_context) {}
MemoryChunkIterator::~MemoryChunkIterator() = default;
HRESULT MemoryChunkIterator::PopulateChunkData() {
WRL::ComPtr<IModelObject> sp_isolate, sp_heap, sp_space;
chunks_.clear();
RETURN_IF_FAIL(GetCurrentIsolate(sp_isolate));
RETURN_IF_FAIL(
sp_isolate->GetRawValue(SymbolField, L"heap_", RawSearchNone, &sp_heap));
RETURN_IF_FAIL(
sp_heap->GetRawValue(SymbolField, L"space_", RawSearchNone, &sp_space));
WRL::ComPtr<IDebugHostType> sp_space_type;
RETURN_IF_FAIL(sp_space->GetTypeInfo(&sp_space_type));
// Iterate over the array of Space pointers
WRL::ComPtr<IIterableConcept> sp_iterable;
RETURN_IF_FAIL(
sp_space->GetConcept(__uuidof(IIterableConcept), &sp_iterable, nullptr));
WRL::ComPtr<IModelIterator> sp_space_iterator;
RETURN_IF_FAIL(sp_iterable->GetIterator(sp_space.Get(), &sp_space_iterator));
// Loop through all the spaces in the array
WRL::ComPtr<IModelObject> sp_space_ptr;
while (sp_space_iterator->GetNext(&sp_space_ptr, 0, nullptr, nullptr) !=
E_BOUNDS) {
// Should have gotten a "v8::internal::Space *". Dereference, then get field
// "memory_chunk_list_" [Type: v8::base::List<v8::internal::MemoryChunk>]
WRL::ComPtr<IModelObject> sp_space, sp_chunk_list, sp_mem_chunk_ptr,
sp_mem_chunk;
RETURN_IF_FAIL(sp_space_ptr->Dereference(&sp_space));
RETURN_IF_FAIL(sp_space->GetRawValue(SymbolField, L"memory_chunk_list_",
RawSearchNone, &sp_chunk_list));
// Then get field "front_" [Type: v8::internal::MemoryChunk *]
RETURN_IF_FAIL(sp_chunk_list->GetRawValue(
SymbolField, L"front_", RawSearchNone, &sp_mem_chunk_ptr));
// Loop here on the list of MemoryChunks for the space
while (true) {
// See if it is a nullptr (i.e. no chunks in this space)
uint64_t front_val;
RETURN_IF_FAIL(
UnboxULong64(sp_mem_chunk_ptr.Get(), &front_val, true /*convert*/));
if (front_val == 0) {
break;
}
// Dereference and get fields "area_start_" and "area_end_" (both uint64)
RETURN_IF_FAIL(sp_mem_chunk_ptr->Dereference(&sp_mem_chunk));
WRL::ComPtr<IModelObject> sp_start, sp_end;
RETURN_IF_FAIL(sp_mem_chunk->GetRawValue(SymbolField, L"area_start_",
RawSearchNone, &sp_start));
RETURN_IF_FAIL(sp_mem_chunk->GetRawValue(SymbolField, L"area_end_",
RawSearchNone, &sp_end));
ChunkData chunk_entry;
chunk_entry.area_start = sp_start;
chunk_entry.area_end = sp_end;
chunk_entry.space = sp_space;
chunks_.push_back(chunk_entry);
// Follow the list_node_.next_ to the next memory chunk
WRL::ComPtr<IModelObject> sp_list_node;
RETURN_IF_FAIL(sp_mem_chunk->GetRawValue(SymbolField, L"list_node_",
RawSearchNone, &sp_list_node));
sp_mem_chunk_ptr = nullptr;
sp_mem_chunk = nullptr;
RETURN_IF_FAIL(sp_list_node->GetRawValue(
SymbolField, L"next_", RawSearchNone, &sp_mem_chunk_ptr));
// Top of the loop will check if this is a nullptr and exit if so
}
sp_space_ptr = nullptr;
}
return S_OK;
}
IFACEMETHODIMP MemoryChunkIterator::Reset() noexcept {
position_ = 0;
return S_OK;
}
IFACEMETHODIMP MemoryChunkIterator::GetNext(IModelObject** object,
ULONG64 dimensions,
IModelObject** indexers,
IKeyStore** metadata) noexcept {
if (dimensions > 1) return E_INVALIDARG;
if (position_ == 0) {
RETURN_IF_FAIL(PopulateChunkData());
}
if (metadata != nullptr) *metadata = nullptr;
WRL::ComPtr<IModelObject> sp_index, sp_value;
if (dimensions == 1) {
RETURN_IF_FAIL(CreateULong64(position_, &sp_index));
}
RETURN_IF_FAIL(GetAt(position_, &sp_value));
// Now update counter and transfer ownership of results, because nothing can
// fail from this point onward.
++position_;
if (dimensions == 1) {
*indexers = sp_index.Detach();
}
*object = sp_value.Detach();
return S_OK;
}
HRESULT MemoryChunkIterator::GetAt(uint64_t index,
IModelObject** result) const {
if (index >= chunks_.size()) return E_BOUNDS;
// Create the synthetic object representing the chunk here
const ChunkData& curr_chunk = chunks_.at(index);
WRL::ComPtr<IModelObject> sp_value;
RETURN_IF_FAIL(
sp_data_model_manager->CreateSyntheticObject(sp_ctx_.Get(), &sp_value));
RETURN_IF_FAIL(
sp_value->SetKey(L"area_start", curr_chunk.area_start.Get(), nullptr));
RETURN_IF_FAIL(
sp_value->SetKey(L"area_end", curr_chunk.area_end.Get(), nullptr));
RETURN_IF_FAIL(sp_value->SetKey(L"space", curr_chunk.space.Get(), nullptr));
*result = sp_value.Detach();
return S_OK;
}
MemoryChunks::MemoryChunks() = default;
MemoryChunks::~MemoryChunks() = default;
IFACEMETHODIMP MemoryChunks::GetDimensionality(
IModelObject* context_object, ULONG64* dimensionality) noexcept {
*dimensionality = 1;
return S_OK;
}
IFACEMETHODIMP MemoryChunks::GetAt(IModelObject* context_object,
ULONG64 indexer_count,
IModelObject** indexers,
IModelObject** object,
IKeyStore** metadata) noexcept {
if (indexer_count != 1) return E_INVALIDARG;
if (metadata != nullptr) *metadata = nullptr;
WRL::ComPtr<IDebugHostContext> sp_ctx;
RETURN_IF_FAIL(context_object->GetContext(&sp_ctx));
// This should be instantiated once for each synthetic object returned,
// so should be able to cache/reuse an iterator
if (opt_chunks_ == nullptr) {
opt_chunks_ = WRL::Make<MemoryChunkIterator>(sp_ctx);
_ASSERT(opt_chunks_ != nullptr);
RETURN_IF_FAIL(opt_chunks_->PopulateChunkData());
}
uint64_t index;
RETURN_IF_FAIL(UnboxULong64(indexers[0], &index, true /*convert*/));
return opt_chunks_->GetAt(index, object);
}
IFACEMETHODIMP MemoryChunks::SetAt(IModelObject* context_object,
ULONG64 indexer_count,
IModelObject** indexers,
IModelObject* value) noexcept {
return E_NOTIMPL;
}
IFACEMETHODIMP MemoryChunks::GetDefaultIndexDimensionality(
IModelObject* context_object, ULONG64* dimensionality) noexcept {
*dimensionality = 1;
return S_OK;
}
IFACEMETHODIMP MemoryChunks::GetIterator(IModelObject* context_object,
IModelIterator** iterator) noexcept {
WRL::ComPtr<IDebugHostContext> sp_ctx;
RETURN_IF_FAIL(context_object->GetContext(&sp_ctx));
auto sp_memory_iterator{WRL::Make<MemoryChunkIterator>(sp_ctx)};
*iterator = sp_memory_iterator.Detach();
return S_OK;
}
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_TOOLS_V8WINDBG_SRC_LIST_CHUNKS_H_
#define V8_TOOLS_V8WINDBG_SRC_LIST_CHUNKS_H_
#include <crtdbg.h>
#include <wrl/implements.h>
#include <optional>
#include <string>
#include <vector>
#include "src/base/optional.h"
#include "tools/v8windbg/base/utilities.h"
#include "tools/v8windbg/src/v8-debug-helper-interop.h"
#include "tools/v8windbg/src/v8windbg-extension.h"
class ListChunksAlias
: public WRL::RuntimeClass<
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
IModelMethod> {
public:
IFACEMETHOD(Call)
(IModelObject* p_context_object, ULONG64 arg_count,
_In_reads_(arg_count) IModelObject** pp_arguments, IModelObject** pp_result,
IKeyStore** pp_metadata);
};
struct ChunkData {
ChunkData();
~ChunkData();
ChunkData(const ChunkData&);
ChunkData(ChunkData&&);
ChunkData& operator=(const ChunkData&);
ChunkData& operator=(ChunkData&&);
WRL::ComPtr<IModelObject> area_start;
WRL::ComPtr<IModelObject> area_end;
WRL::ComPtr<IModelObject> space;
};
class MemoryChunkIterator
: public WRL::RuntimeClass<
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
IModelIterator> {
public:
MemoryChunkIterator(WRL::ComPtr<IDebugHostContext>& host_context);
~MemoryChunkIterator() override;
HRESULT PopulateChunkData();
IFACEMETHOD(Reset)();
IFACEMETHOD(GetNext)
(IModelObject** object, ULONG64 dimensions, IModelObject** indexers,
IKeyStore** metadata);
const std::vector<ChunkData>& GetChunks() const { return chunks_; }
HRESULT GetAt(uint64_t index, IModelObject** result) const;
private:
ULONG position_ = 0;
std::vector<ChunkData> chunks_;
WRL::ComPtr<IDebugHostContext> sp_ctx_;
};
class MemoryChunks
: public WRL::RuntimeClass<
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
IIndexableConcept, IIterableConcept> {
public:
MemoryChunks();
~MemoryChunks() override;
// IIndexableConcept members
IFACEMETHOD(GetDimensionality)
(IModelObject* context_object, ULONG64* dimensionality);
IFACEMETHOD(GetAt)
(IModelObject* context_object, ULONG64 indexer_count, IModelObject** indexers,
IModelObject** object, IKeyStore** metadata);
IFACEMETHOD(SetAt)
(IModelObject* context_object, ULONG64 indexer_count, IModelObject** indexers,
IModelObject* value);
// IIterableConcept
IFACEMETHOD(GetDefaultIndexDimensionality)
(IModelObject* context_object, ULONG64* dimensionality);
IFACEMETHOD(GetIterator)
(IModelObject* context_object, IModelIterator** iterator);
private:
WRL::ComPtr<MemoryChunkIterator> opt_chunks_;
};
#endif // V8_TOOLS_V8WINDBG_SRC_LIST_CHUNKS_H_
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/v8windbg/src/local-variables.h"
#include "tools/v8windbg/base/utilities.h"
#include "tools/v8windbg/src/v8windbg-extension.h"
V8LocalVariables::V8LocalVariables(WRL::ComPtr<IModelPropertyAccessor> original,
bool is_parameters)
: original_(original), is_parameters_(is_parameters) {}
V8LocalVariables::~V8LocalVariables() = default;
IFACEMETHODIMP V8LocalVariables::GetValue(PCWSTR key, IModelObject* context,
IModelObject** value) noexcept {
// See if the frame can fetch locals based on symbols. If so, it's a normal
// C++ frame, so we can be done.
HRESULT original_hr = original_->GetValue(key, context, value);
if (SUCCEEDED(original_hr)) return original_hr;
// Next, try to find out about the instruction pointer. If it is within the V8
// module, or points to unknown space outside a module (generated code), then
// we're interested. Otherwise, we have nothing useful to do.
WRL::ComPtr<IModelObject> attributes;
RETURN_IF_FAIL(context->GetKeyValue(L"Attributes", &attributes, nullptr));
WRL::ComPtr<IModelObject> boxed_instruction_offset;
RETURN_IF_FAIL(attributes->GetKeyValue(L"InstructionOffset",
&boxed_instruction_offset, nullptr));
ULONG64 instruction_offset{};
RETURN_IF_FAIL(
UnboxULong64(boxed_instruction_offset.Get(), &instruction_offset));
WRL::ComPtr<IDebugHostSymbols> symbols;
RETURN_IF_FAIL(sp_debug_host.As(&symbols));
WRL::ComPtr<IDebugHostContext> host_context;
RETURN_IF_FAIL(sp_debug_host->GetCurrentContext(&host_context));
WRL::ComPtr<IDebugHostModule> module;
if (SUCCEEDED(symbols->FindModuleByLocation(host_context.Get(),
instruction_offset, &module))) {
Location module_base;
RETURN_IF_FAIL(module->GetBaseLocation(&module_base));
WRL::ComPtr<IDebugHostModule> v8_module =
Extension::Current()->GetV8Module(host_context);
if (v8_module == nullptr) {
// Anything in a module must not be in the V8 module if the V8 module
// doesn't exist.
return original_hr;
}
Location v8_base;
RETURN_IF_FAIL(v8_module->GetBaseLocation(&v8_base));
if (module_base != v8_base) {
// It's in a module, but not the one that contains V8.
return original_hr;
}
}
// Initialize an empty result object.
WRL::ComPtr<IModelObject> result;
RETURN_IF_FAIL(sp_data_model_manager->CreateSyntheticObject(
host_context.Get(), &result));
WRL::ComPtr<IModelObject> parent_model;
RETURN_IF_FAIL(sp_data_model_manager->AcquireNamedModel(
is_parameters_ ? L"Debugger.Models.Parameters"
: L"Debugger.Models.LocalVariables",
&parent_model));
RETURN_IF_FAIL(result->AddParentModel(parent_model.Get(), /*context=*/nullptr,
/*override=*/false));
if (is_parameters_) {
// We're not actually adding any parameters data yet; we just need it to not
// fail so that the locals pane displays the LocalVariables. The locals pane
// displays nothing if getting either LocalVariables or Parameters fails.
*value = result.Detach();
return S_OK;
}
// Get the stack and frame pointers for the current frame.
WRL::ComPtr<IModelObject> boxed_stack_offset;
RETURN_IF_FAIL(
attributes->GetKeyValue(L"StackOffset", &boxed_stack_offset, nullptr));
ULONG64 stack_offset{};
RETURN_IF_FAIL(UnboxULong64(boxed_stack_offset.Get(), &stack_offset));
WRL::ComPtr<IModelObject> boxed_frame_offset;
RETURN_IF_FAIL(
attributes->GetKeyValue(L"FrameOffset", &boxed_frame_offset, nullptr));
ULONG64 frame_offset{};
RETURN_IF_FAIL(UnboxULong64(boxed_frame_offset.Get(), &frame_offset));
// Eventually v8_debug_helper will provide some help here, but for now, just
// provide the option to view the whole stack frame as tagged data. It can
// be somewhat useful.
WRL::ComPtr<IDebugHostType> object_type =
Extension::Current()->GetV8ObjectType(host_context);
if (object_type == nullptr) {
// There's nothing useful to do if we can't find the symbol for
// v8::internal::Object.
return original_hr;
}
ULONG64 object_size{};
RETURN_IF_FAIL(object_type->GetSize(&object_size));
ULONG64 num_objects = (frame_offset - stack_offset) / object_size;
ArrayDimension dimensions[] = {
{/*start=*/0, /*length=*/num_objects, /*stride=*/object_size}};
WRL::ComPtr<IDebugHostType> object_array_type;
RETURN_IF_FAIL(object_type->CreateArrayOf(/*dimensions=*/1, dimensions,
&object_array_type));
WRL::ComPtr<IModelObject> array;
RETURN_IF_FAIL(sp_data_model_manager->CreateTypedObject(
host_context.Get(), stack_offset, object_array_type.Get(), &array));
RETURN_IF_FAIL(
result->SetKey(L"memory interpreted as Objects", array.Get(), nullptr));
*value = result.Detach();
return S_OK;
}
IFACEMETHODIMP V8LocalVariables::SetValue(PCWSTR key, IModelObject* context,
IModelObject* value) noexcept {
return E_NOTIMPL;
}
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_TOOLS_V8WINDBG_SRC_LOCAL_VARIABLES_H_
#define V8_TOOLS_V8WINDBG_SRC_LOCAL_VARIABLES_H_
#include <comutil.h>
#include <wrl/implements.h>
#include "tools/v8windbg/base/dbgext.h"
// An implementation of the property accessor for the "LocalVariables" or
// "Parameters" property on Debugger.Models.StackFrame. This allows us to modify
// the variables shown in each frame.
class V8LocalVariables
: public WRL::RuntimeClass<
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
IModelPropertyAccessor> {
public:
V8LocalVariables(WRL::ComPtr<IModelPropertyAccessor> original,
bool is_parameters);
~V8LocalVariables() override;
IFACEMETHOD(GetValue)
(PCWSTR key, IModelObject* context, IModelObject** value);
IFACEMETHOD(SetValue)(PCWSTR key, IModelObject* context, IModelObject* value);
private:
// The built-in accessor which we are overriding.
WRL::ComPtr<IModelPropertyAccessor> original_;
// Whether this is for Parameters rather than LocalVariables.
bool is_parameters_;
};
#endif // V8_TOOLS_V8WINDBG_SRC_LOCAL_VARIABLES_H_
This diff is collapsed.
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_TOOLS_V8WINDBG_SRC_OBJECT_INSPECTION_H_
#define V8_TOOLS_V8WINDBG_SRC_OBJECT_INSPECTION_H_
#include <comutil.h>
#include <wrl/implements.h>
#include <sstream>
#include <string>
#include <vector>
#include "tools/v8windbg/base/dbgext.h"
#include "tools/v8windbg/src/v8-debug-helper-interop.h"
#include "tools/v8windbg/src/v8windbg-extension.h"
// The representation of the underlying V8 object that will be cached on the
// DataModel representation. (Needs to implement IUnknown).
class __declspec(uuid("6392E072-37BB-4220-A5FF-114098923A02")) IV8CachedObject
: public IUnknown {
public:
virtual HRESULT __stdcall GetCachedV8HeapObject(
V8HeapObject** pp_heap_object) = 0;
};
class V8CachedObject
: public WRL::RuntimeClass<
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
IV8CachedObject> {
public:
V8CachedObject(Location location, std::string uncompressed_type_name,
WRL::ComPtr<IDebugHostContext> context, bool is_compressed);
V8CachedObject(V8HeapObject heap_object);
~V8CachedObject() override;
static HRESULT Create(IModelObject* p_v8_object_instance,
IV8CachedObject** result);
IFACEMETHOD(GetCachedV8HeapObject)(V8HeapObject** pp_heap_object);
private:
// The properties and description of the object, if already read.
V8HeapObject heap_object_;
bool heap_object_initialized_ = false;
// Data that is necessary for reading the object.
Location location_;
std::string uncompressed_type_name_;
WRL::ComPtr<IDebugHostContext> context_;
bool is_compressed_ = false;
};
// A simple COM wrapper class to hold data required for IndexedFieldParent.
// (Needs to implement IUnknown).
class __declspec(uuid("6392E072-37BB-4220-A5FF-114098923A03")) IIndexedFieldData
: public IUnknown {
public:
// Get a pointer to the Property object held by this IIndexedFieldData. The
// pointer returned in this way is valid only while the containing
// IIndexedFieldData is alive.
virtual HRESULT __stdcall GetProperty(Property** property) = 0;
};
class IndexedFieldData
: public WRL::RuntimeClass<
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
IIndexedFieldData> {
public:
IndexedFieldData(Property property);
~IndexedFieldData() override;
// Get a pointer to the Property object held by this IndexedFieldData. The
// pointer returned in this way is valid only while the containing
// IndexedFieldData is alive.
IFACEMETHOD(GetProperty)(Property** property);
private:
Property property_;
};
// A parent model that provides indexing support for fields that contain arrays
// of something more complicated than basic native types.
class IndexedFieldParent
: public WRL::RuntimeClass<
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
IDataModelConcept, IIterableConcept, IIndexableConcept> {
public:
// IDataModelConcept
IFACEMETHOD(InitializeObject)
(IModelObject* model_object, IDebugHostTypeSignature* matching_type_signature,
IDebugHostSymbolEnumerator* wildcard_matches);
// IDataModelConcept
IFACEMETHOD(GetName)(BSTR* model_name);
// IIndexableConcept
IFACEMETHOD(GetAt)
(IModelObject* context_object, ULONG64 indexer_count, IModelObject** indexers,
_COM_Errorptr_ IModelObject** object, IKeyStore** metadata);
// IIndexableConcept
IFACEMETHOD(GetDimensionality)
(IModelObject* context_object, ULONG64* dimensionality);
// IIndexableConcept
IFACEMETHOD(SetAt)
(IModelObject* context_object, ULONG64 indexer_count, IModelObject** indexers,
IModelObject* value);
// IIterableConcept
IFACEMETHOD(GetDefaultIndexDimensionality)
(IModelObject* context_object, ULONG64* dimensionality);
// IIterableConcept
IFACEMETHOD(GetIterator)
(IModelObject* context_object, IModelIterator** iterator);
};
// An iterator for the values within an array field.
class IndexedFieldIterator
: public WRL::RuntimeClass<
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
IModelIterator> {
public:
IndexedFieldIterator(IModelObject* context_object);
~IndexedFieldIterator() override;
IFACEMETHOD(Reset)();
IFACEMETHOD(GetNext)
(IModelObject** object, ULONG64 dimensions, IModelObject** indexers,
IKeyStore** metadata);
private:
size_t next_ = 0;
WRL::ComPtr<IModelObject> context_object_;
};
// Enumerates the names of fields on V8 objects.
class V8ObjectKeyEnumerator
: public WRL::RuntimeClass<
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
IKeyEnumerator> {
public:
V8ObjectKeyEnumerator(WRL::ComPtr<IV8CachedObject>& v8_cached_object);
~V8ObjectKeyEnumerator() override;
IFACEMETHOD(Reset)();
// This method will be called with a nullptr 'value' for each key if returned
// from an IDynamicKeyProviderConcept. It will call GetKey on the
// IDynamicKeyProviderConcept interface after each key returned.
IFACEMETHOD(GetNext)(BSTR* key, IModelObject** value, IKeyStore** metadata);
private:
int index_ = 0;
WRL::ComPtr<IV8CachedObject> sp_v8_cached_object_;
};
// A parent model for V8 handle types such as v8::internal::Handle<*>.
class V8LocalDataModel
: public WRL::RuntimeClass<
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
IDataModelConcept> {
public:
IFACEMETHOD(InitializeObject)
(IModelObject* model_object, IDebugHostTypeSignature* matching_type_signature,
IDebugHostSymbolEnumerator* wildcard_matches);
IFACEMETHOD(GetName)(BSTR* model_name);
};
// A parent model for V8 object types such as v8::internal::Object.
class V8ObjectDataModel
: public WRL::RuntimeClass<
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
IDataModelConcept, IStringDisplayableConcept,
IDynamicKeyProviderConcept> {
public:
HRESULT GetCachedObject(IModelObject* context_object,
IV8CachedObject** result) {
// Get the IModelObject for this parent object. As it is a dynamic provider,
// there is only one parent directly on the object.
WRL::ComPtr<IModelObject> sp_parent_model, sp_context_adjuster;
RETURN_IF_FAIL(context_object->GetParentModel(0, &sp_parent_model,
&sp_context_adjuster));
// See if the cached object is already present
WRL::ComPtr<IUnknown> sp_context;
HRESULT hr = context_object->GetContextForDataModel(sp_parent_model.Get(),
&sp_context);
WRL::ComPtr<IV8CachedObject> sp_v8_cached_object;
if (SUCCEEDED(hr)) {
RETURN_IF_FAIL(sp_context.As(&sp_v8_cached_object));
} else {
RETURN_IF_FAIL(
V8CachedObject::Create(context_object, &sp_v8_cached_object));
RETURN_IF_FAIL(sp_v8_cached_object.As(&sp_context));
RETURN_IF_FAIL(context_object->SetContextForDataModel(
sp_parent_model.Get(), sp_context.Get()));
}
*result = sp_v8_cached_object.Detach();
return S_OK;
}
IFACEMETHOD(InitializeObject)
(IModelObject* model_object, IDebugHostTypeSignature* matching_type_signature,
IDebugHostSymbolEnumerator* wildcard_matches);
IFACEMETHOD(GetName)(BSTR* model_name);
IFACEMETHOD(ToDisplayString)
(IModelObject* context_object, IKeyStore* metadata, BSTR* display_string);
// IDynamicKeyProviderConcept
IFACEMETHOD(GetKey)
(IModelObject* context_object, PCWSTR key, IModelObject** key_value,
IKeyStore** metadata, bool* has_key);
IFACEMETHOD(SetKey)
(IModelObject* context_object, PCWSTR key, IModelObject* key_value,
IKeyStore* metadata);
IFACEMETHOD(EnumerateKeys)
(IModelObject* context_object, IKeyEnumerator** pp_enumerator);
};
// The implemention of the "Value" getter for V8 handle types.
class V8LocalValueProperty
: public WRL::RuntimeClass<
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
IModelPropertyAccessor> {
public:
IFACEMETHOD(GetValue)
(PCWSTR pwsz_key, IModelObject* p_v8_object_instance,
IModelObject** pp_value);
IFACEMETHOD(SetValue)
(PCWSTR /*pwsz_key*/, IModelObject* /*p_process_instance*/,
IModelObject* /*p_value*/);
};
// A way that someone can directly inspect a tagged value, even if that value
// isn't in memory (from a register, or the user's imagination, etc.).
class InspectV8ObjectMethod
: public WRL::RuntimeClass<
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
IModelMethod> {
public:
IFACEMETHOD(Call)
(IModelObject* p_context_object, ULONG64 arg_count,
_In_reads_(arg_count) IModelObject** pp_arguments, IModelObject** pp_result,
IKeyStore** pp_metadata);
};
#endif // V8_TOOLS_V8WINDBG_SRC_OBJECT_INSPECTION_H_
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/v8windbg/src/v8-debug-helper-interop.h"
#include <Windows.h>
#include <crtdbg.h>
#include "src/common/globals.h"
#include "tools/debug_helper/debug-helper.h"
#include "tools/v8windbg/base/utilities.h"
#include "tools/v8windbg/src/v8windbg-extension.h"
namespace d = v8::debug_helper;
// We need a plain C function pointer for interop with v8_debug_helper. We can
// use this to get one as long as we never need two at once.
class MemReaderScope {
public:
explicit MemReaderScope(WRL::ComPtr<IDebugHostContext> sp_context)
: sp_context_(sp_context) {
_ASSERTE(!context_);
context_ = sp_context_.Get();
}
~MemReaderScope() { context_ = nullptr; }
d::MemoryAccessor GetReader() { return &MemReaderScope::Read; }
private:
MemReaderScope(const MemReaderScope&) = delete;
MemReaderScope& operator=(const MemReaderScope&) = delete;
static d::MemoryAccessResult Read(uintptr_t address, uint8_t* destination,
size_t byte_count) {
ULONG64 bytes_read;
Location loc{address};
HRESULT hr = sp_debug_host_memory->ReadBytes(context_, loc, destination,
byte_count, &bytes_read);
// TODO determine when an address is valid but inaccessible
return SUCCEEDED(hr) ? d::MemoryAccessResult::kOk
: d::MemoryAccessResult::kAddressNotValid;
}
WRL::ComPtr<IDebugHostContext> sp_context_;
static IDebugHostContext* context_;
};
IDebugHostContext* MemReaderScope::context_;
StructField::StructField(std::u16string field_name, std::u16string type_name,
std::string uncompressed_type_name, uint64_t offset,
uint8_t num_bits, uint8_t shift_bits)
: name(field_name),
type_name(type_name),
uncompressed_type_name(uncompressed_type_name),
offset(offset),
num_bits(num_bits),
shift_bits(shift_bits) {}
StructField::~StructField() = default;
StructField::StructField(const StructField&) = default;
StructField::StructField(StructField&&) = default;
StructField& StructField::operator=(const StructField&) = default;
StructField& StructField::operator=(StructField&&) = default;
Property::Property(std::u16string property_name, std::u16string type_name,
std::string uncompressed_type_name, uint64_t address,
size_t item_size)
: name(property_name),
type(PropertyType::kPointer),
type_name(type_name),
uncompressed_type_name(uncompressed_type_name),
addr_value(address),
item_size(item_size) {}
Property::~Property() = default;
Property::Property(const Property&) = default;
Property::Property(Property&&) = default;
Property& Property::operator=(const Property&) = default;
Property& Property::operator=(Property&&) = default;
V8HeapObject::V8HeapObject() = default;
V8HeapObject::~V8HeapObject() = default;
V8HeapObject::V8HeapObject(const V8HeapObject&) = default;
V8HeapObject::V8HeapObject(V8HeapObject&&) = default;
V8HeapObject& V8HeapObject::operator=(const V8HeapObject&) = default;
V8HeapObject& V8HeapObject::operator=(V8HeapObject&&) = default;
V8HeapObject GetHeapObject(WRL::ComPtr<IDebugHostContext> sp_context,
uint64_t tagged_ptr, uint64_t referring_pointer,
const char* type_name, bool is_compressed) {
// Read the value at the address, and see if it is a tagged pointer
V8HeapObject obj;
MemReaderScope reader_scope(sp_context);
d::HeapAddresses heap_addresses = {0, 0, 0, 0};
// TODO ideally we'd provide real heap page pointers. For now, just testing
// decompression based on the pointer to wherever we found this value,
// which is likely (though not guaranteed) to be a heap pointer itself.
heap_addresses.any_heap_pointer = referring_pointer;
auto props = d::GetObjectProperties(tagged_ptr, reader_scope.GetReader(),
heap_addresses, type_name);
obj.friendly_name = ConvertToU16String(props->brief);
for (size_t property_index = 0; property_index < props->num_properties;
++property_index) {
const auto& source_prop = *props->properties[property_index];
Property dest_prop(ConvertToU16String(source_prop.name),
ConvertToU16String(source_prop.type),
source_prop.decompressed_type, source_prop.address,
source_prop.size);
if (source_prop.kind != d::PropertyKind::kSingle) {
dest_prop.type = PropertyType::kArray;
dest_prop.length = source_prop.num_values;
}
if (dest_prop.type_name.empty() || source_prop.num_struct_fields > 0) {
// If the helper library didn't provide a type, then it should have
// provided struct fields instead. Set the struct type flag and copy the
// fields into the result.
dest_prop.type =
static_cast<PropertyType>(static_cast<int>(dest_prop.type) |
static_cast<int>(PropertyType::kStruct));
for (size_t field_index = 0; field_index < source_prop.num_struct_fields;
++field_index) {
const auto& struct_field = *source_prop.struct_fields[field_index];
dest_prop.fields.push_back({ConvertToU16String(struct_field.name),
ConvertToU16String(struct_field.type),
struct_field.decompressed_type,
struct_field.offset, struct_field.num_bits,
struct_field.shift_bits});
}
}
obj.properties.push_back(dest_prop);
}
// For each guessed type, create a synthetic property that will request data
// about the same object again but with a more specific type hint.
if (referring_pointer != 0) {
for (size_t type_index = 0; type_index < props->num_guessed_types;
++type_index) {
const std::string& type_name = props->guessed_types[type_index];
Property dest_prop(
ConvertToU16String(("guessed type " + type_name).c_str()),
ConvertToU16String(is_compressed ? kTaggedValue : type_name),
type_name, referring_pointer,
is_compressed ? i::kTaggedSize : sizeof(void*));
obj.properties.push_back(dest_prop);
}
}
return obj;
}
std::vector<std::u16string> ListObjectClasses() {
const d::ClassList* class_list = d::ListObjectClasses();
std::vector<std::u16string> result;
for (size_t i = 0; i < class_list->num_class_names; ++i) {
result.push_back(ConvertToU16String(class_list->class_names[i]));
}
return result;
}
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_TOOLS_V8WINDBG_SRC_V8_DEBUG_HELPER_INTEROP_H_
#define V8_TOOLS_V8WINDBG_SRC_V8_DEBUG_HELPER_INTEROP_H_
#include <wrl/client.h>
#include <DbgModel.h>
#include <cstdint>
#include <map>
#include <string>
#include <vector>
namespace WRL = Microsoft::WRL;
constexpr char kObject[] = "v8::internal::Object";
constexpr char16_t kObjectU[] = u"v8::internal::Object";
constexpr char kTaggedValue[] = "v8::internal::TaggedValue";
constexpr char16_t kTaggedValueU[] = u"v8::internal::TaggedValue";
enum class PropertyType {
kPointer = 0,
kArray = 1,
kStruct = 2,
kStructArray = kArray | kStruct,
};
struct StructField {
StructField(std::u16string field_name, std::u16string type_name,
std::string uncompressed_type_name, uint64_t address,
uint8_t num_bits, uint8_t shift_bits);
~StructField();
StructField(const StructField&);
StructField(StructField&&);
StructField& operator=(const StructField&);
StructField& operator=(StructField&&);
std::u16string name;
// Statically-determined type, such as from .tq definition.
std::u16string type_name;
// In some cases, |type_name| may be a simple type representing a compressed
// pointer such as v8::internal::TaggedValue. In those cases,
// |uncompressed_type_name| will contain the type of the object when
// decompressed. Otherwise, |uncompressed_type_name| will match |type_name|.
// In any case, it is safe to pass the |uncompressed_type_name| value as the
// type_hint on a subsequent call to GetObjectProperties.
std::string uncompressed_type_name;
// Offset, in bytes, from beginning of struct.
uint64_t offset;
// The number of bits that are present, if this value is a bitfield. Zero
// indicates that this value is not a bitfield (the full value is stored).
uint8_t num_bits;
// The number of bits by which this value has been left-shifted for storage as
// a bitfield.
uint8_t shift_bits;
};
struct Property {
Property(std::u16string property_name, std::u16string type_name,
std::string uncompressed_type_name, uint64_t address,
size_t item_size);
~Property();
Property(const Property&);
Property(Property&&);
Property& operator=(const Property&);
Property& operator=(Property&&);
std::u16string name;
PropertyType type;
// Statically-determined type, such as from .tq definition. Can be an empty
// string if this property is itself a Torque-defined struct; in that case use
// |fields| instead.
std::u16string type_name;
// In some cases, |type_name| may be a simple type representing a compressed
// pointer such as v8::internal::TaggedValue. In those cases,
// |uncompressed_type_name| will contain the type of the object when
// decompressed. Otherwise, |uncompressed_type_name| will match |type_name|.
// In any case, it is safe to pass the |uncompressed_type_name| value as the
// type_hint on a subsequent call to GetObjectProperties.
std::string uncompressed_type_name;
// The address where the property value can be found in the debuggee's address
// space, or the address of the first value for an array.
uint64_t addr_value;
// Size of each array item, if this property is an array.
size_t item_size;
// Number of array items, if this property is an array.
size_t length = 0;
// Fields within this property, if this property is a struct, or fields within
// each array element, if this property is a struct array.
std::vector<StructField> fields;
};
struct V8HeapObject {
V8HeapObject();
~V8HeapObject();
V8HeapObject(const V8HeapObject&);
V8HeapObject(V8HeapObject&&);
V8HeapObject& operator=(const V8HeapObject&);
V8HeapObject& operator=(V8HeapObject&&);
std::u16string friendly_name; // String to print in single-line description.
std::vector<Property> properties;
};
V8HeapObject GetHeapObject(WRL::ComPtr<IDebugHostContext> sp_context,
uint64_t address, uint64_t referring_pointer,
const char* type_name, bool is_compressed);
// Expand a compressed pointer from 32 bits to the format that
// GetObjectProperties expects for compressed pointers.
inline uint64_t ExpandCompressedPointer(uint32_t ptr) { return ptr; }
std::vector<std::u16string> ListObjectClasses();
#endif // V8_TOOLS_V8WINDBG_SRC_V8_DEBUG_HELPER_INTEROP_H_
This diff is collapsed.
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_TOOLS_V8WINDBG_SRC_V8WINDBG_EXTENSION_H_
#define V8_TOOLS_V8WINDBG_SRC_V8WINDBG_EXTENSION_H_
#include <memory>
#include <unordered_map>
#include <vector>
#include "tools/v8windbg/base/utilities.h"
// Responsible for initializing and uninitializing the extension. Also provides
// various convenience functions.
class Extension {
public:
Extension();
HRESULT Initialize();
~Extension();
WRL::ComPtr<IDebugHostModule> GetV8Module(
WRL::ComPtr<IDebugHostContext>& sp_ctx);
WRL::ComPtr<IDebugHostType> GetTypeFromV8Module(
WRL::ComPtr<IDebugHostContext>& sp_ctx, const char16_t* type_name);
WRL::ComPtr<IDebugHostType> GetV8ObjectType(
WRL::ComPtr<IDebugHostContext>& sp_ctx);
void TryRegisterType(WRL::ComPtr<IDebugHostType>& sp_type,
std::u16string type_name);
bool DoesTypeDeriveFromObject(const WRL::ComPtr<IDebugHostType>& sp_type);
static Extension* Current() { return current_extension_.get(); }
static void SetExtension(std::unique_ptr<Extension> new_extension) {
current_extension_ = std::move(new_extension);
}
// Returns the parent model for instances of v8::internal::Object and similar
// classes, which contain as their first and only field a tagged V8 value.
IModelObject* GetObjectDataModel() { return sp_object_data_model_.Get(); }
// Returns the parent model that provides indexing support for fields that
// contain arrays of something more complicated than basic native types.
IModelObject* GetIndexedFieldDataModel() {
return sp_indexed_field_model_.Get();
}
private:
HRESULT OverrideLocalsGetter(IModelObject* parent, const wchar_t* key_name,
bool is_parameters);
// A property that has been overridden by this extension. The original value
// must be put back in place during ~Extension.
struct PropertyOverride {
PropertyOverride();
PropertyOverride(IModelObject* parent, std::u16string key_name,
IModelObject* original_value,
IKeyStore* original_metadata);
~PropertyOverride();
PropertyOverride(const PropertyOverride&);
PropertyOverride& operator=(const PropertyOverride&);
WRL::ComPtr<IModelObject> parent;
std::u16string key_name;
WRL::ComPtr<IModelObject> original_value;
WRL::ComPtr<IKeyStore> original_metadata;
};
static std::unique_ptr<Extension> current_extension_;
WRL::ComPtr<IModelObject> sp_object_data_model_;
WRL::ComPtr<IModelObject> sp_local_data_model_;
WRL::ComPtr<IModelObject> sp_indexed_field_model_;
WRL::ComPtr<IDebugHostModule> sp_v8_module_;
std::unordered_map<std::u16string, WRL::ComPtr<IDebugHostType>>
cached_v8_module_types_;
std::vector<WRL::ComPtr<IDebugHostTypeSignature>> registered_object_types_;
std::vector<WRL::ComPtr<IDebugHostTypeSignature>> registered_handle_types_;
std::vector<PropertyOverride> overridden_properties_;
WRL::ComPtr<IDebugHostContext> sp_v8_module_ctx_;
ULONG v8_module_proc_id_;
};
#endif // V8_TOOLS_V8WINDBG_SRC_V8WINDBG_EXTENSION_H_
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tools/v8windbg/test/debug-callbacks.h"
namespace v8 {
namespace internal {
namespace v8windbg_test {
MyOutput::MyOutput(WRL::ComPtr<IDebugClient5> p_client) : p_client_(p_client) {
p_client_->SetOutputCallbacks(this);
}
MyOutput::~MyOutput() { p_client_->SetOutputCallbacks(nullptr); }
HRESULT __stdcall MyOutput::QueryInterface(REFIID InterfaceId,
PVOID* Interface) {
return E_NOTIMPL;
}
ULONG __stdcall MyOutput::AddRef(void) { return 0; }
ULONG __stdcall MyOutput::Release(void) { return 0; }
HRESULT __stdcall MyOutput::Output(ULONG Mask, PCSTR Text) {
if (Mask & DEBUG_OUTPUT_NORMAL) {
log_ += Text;
}
return S_OK;
}
HRESULT __stdcall MyCallback::QueryInterface(REFIID InterfaceId,
PVOID* Interface) {
return E_NOTIMPL;
}
ULONG __stdcall MyCallback::AddRef(void) { return S_OK; }
ULONG __stdcall MyCallback::Release(void) { return S_OK; }
HRESULT __stdcall MyCallback::GetInterestMask(PULONG Mask) {
*Mask = DEBUG_EVENT_BREAKPOINT | DEBUG_EVENT_CREATE_PROCESS;
return S_OK;
}
HRESULT __stdcall MyCallback::Breakpoint(PDEBUG_BREAKPOINT Bp) {
ULONG64 bp_offset;
HRESULT hr = Bp->GetOffset(&bp_offset);
if (FAILED(hr)) return hr;
// Break on breakpoints? Seems reasonable.
return DEBUG_STATUS_BREAK;
}
HRESULT __stdcall MyCallback::Exception(PEXCEPTION_RECORD64 Exception,
ULONG FirstChance) {
return E_NOTIMPL;
}
HRESULT __stdcall MyCallback::CreateThread(ULONG64 Handle, ULONG64 DataOffset,
ULONG64 StartOffset) {
return E_NOTIMPL;
}
HRESULT __stdcall MyCallback::ExitThread(ULONG ExitCode) { return E_NOTIMPL; }
HRESULT __stdcall MyCallback::ExitProcess(ULONG ExitCode) { return E_NOTIMPL; }
HRESULT __stdcall MyCallback::LoadModule(ULONG64 ImageFileHandle,
ULONG64 BaseOffset, ULONG ModuleSize,
PCSTR ModuleName, PCSTR ImageName,
ULONG CheckSum, ULONG TimeDateStamp) {
return E_NOTIMPL;
}
HRESULT __stdcall MyCallback::UnloadModule(PCSTR ImageBaseName,
ULONG64 BaseOffset) {
return E_NOTIMPL;
}
HRESULT __stdcall MyCallback::SystemError(ULONG Error, ULONG Level) {
return E_NOTIMPL;
}
HRESULT __stdcall MyCallback::SessionStatus(ULONG Status) { return E_NOTIMPL; }
HRESULT __stdcall MyCallback::ChangeDebuggeeState(ULONG Flags,
ULONG64 Argument) {
return E_NOTIMPL;
}
HRESULT __stdcall MyCallback::ChangeEngineState(ULONG Flags, ULONG64 Argument) {
return E_NOTIMPL;
}
HRESULT __stdcall MyCallback::ChangeSymbolState(ULONG Flags, ULONG64 Argument) {
return E_NOTIMPL;
}
HRESULT __stdcall MyCallback::CreateProcessW(
ULONG64 ImageFileHandle, ULONG64 Handle, ULONG64 BaseOffset,
ULONG ModuleSize, PCSTR ModuleName, PCSTR ImageName, ULONG CheckSum,
ULONG TimeDateStamp, ULONG64 InitialThreadHandle, ULONG64 ThreadDataOffset,
ULONG64 StartOffset) {
// Should fire once the target process is launched. Break to create
// breakpoints, etc.
return DEBUG_STATUS_BREAK;
}
} // namespace v8windbg_test
} // namespace internal
} // namespace v8
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_TOOLS_V8WINDBG_TEST_DEBUG_CALLBACKS_H_
#define V8_TOOLS_V8WINDBG_TEST_DEBUG_CALLBACKS_H_
#if !defined(UNICODE) || !defined(_UNICODE)
#error Unicode not defined
#endif
#include <DbgEng.h>
#include <DbgModel.h>
#include <Windows.h>
#include <crtdbg.h>
#include <pathcch.h>
#include <wrl/client.h>
#include <string>
namespace WRL = Microsoft::WRL;
namespace v8 {
namespace internal {
namespace v8windbg_test {
class MyOutput : public IDebugOutputCallbacks {
public:
MyOutput(WRL::ComPtr<IDebugClient5> p_client);
~MyOutput();
MyOutput(const MyOutput&) = delete;
MyOutput& operator=(const MyOutput&) = delete;
// Inherited via IDebugOutputCallbacks
HRESULT __stdcall QueryInterface(REFIID InterfaceId,
PVOID* Interface) override;
ULONG __stdcall AddRef(void) override;
ULONG __stdcall Release(void) override;
HRESULT __stdcall Output(ULONG Mask, PCSTR Text) override;
const std::string& GetLog() const { return log_; }
void ClearLog() { log_.clear(); }
private:
WRL::ComPtr<IDebugClient5> p_client_;
std::string log_;
};
// For return values, see:
// https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debug-status-xxx
class MyCallback : public IDebugEventCallbacks {
public:
// Inherited via IDebugEventCallbacks
HRESULT __stdcall QueryInterface(REFIID InterfaceId,
PVOID* Interface) override;
ULONG __stdcall AddRef(void) override;
ULONG __stdcall Release(void) override;
HRESULT __stdcall GetInterestMask(PULONG Mask) override;
HRESULT __stdcall Breakpoint(PDEBUG_BREAKPOINT Bp) override;
HRESULT __stdcall Exception(PEXCEPTION_RECORD64 Exception,
ULONG FirstChance) override;
HRESULT __stdcall CreateThread(ULONG64 Handle, ULONG64 DataOffset,
ULONG64 StartOffset) override;
HRESULT __stdcall ExitThread(ULONG ExitCode) override;
HRESULT __stdcall ExitProcess(ULONG ExitCode) override;
HRESULT __stdcall LoadModule(ULONG64 ImageFileHandle, ULONG64 BaseOffset,
ULONG ModuleSize, PCSTR ModuleName,
PCSTR ImageName, ULONG CheckSum,
ULONG TimeDateStamp) override;
HRESULT __stdcall UnloadModule(PCSTR ImageBaseName,
ULONG64 BaseOffset) override;
HRESULT __stdcall SystemError(ULONG Error, ULONG Level) override;
HRESULT __stdcall SessionStatus(ULONG Status) override;
HRESULT __stdcall ChangeDebuggeeState(ULONG Flags, ULONG64 Argument) override;
HRESULT __stdcall ChangeEngineState(ULONG Flags, ULONG64 Argument) override;
HRESULT __stdcall ChangeSymbolState(ULONG Flags, ULONG64 Argument) override;
HRESULT __stdcall CreateProcessW(ULONG64 ImageFileHandle, ULONG64 Handle,
ULONG64 BaseOffset, ULONG ModuleSize,
PCSTR ModuleName, PCSTR ImageName,
ULONG CheckSum, ULONG TimeDateStamp,
ULONG64 InitialThreadHandle,
ULONG64 ThreadDataOffset,
ULONG64 StartOffset) override;
};
} // namespace v8windbg_test
} // namespace internal
} // namespace v8
#endif // V8_TOOLS_V8WINDBG_TEST_DEBUG_CALLBACKS_H_
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
function a() {
JSON.stringify({firstProp: 12345, secondProp: null}, function replacer() {});
}
function b() {
var hello = 'hello';
a();
}
b();
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstdio>
#include <exception>
#include <vector>
#include "src/base/logging.h"
#include "tools/v8windbg/test/debug-callbacks.h"
// See the docs at
// https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/using-the-debugger-engine-api
namespace v8 {
namespace internal {
namespace v8windbg_test {
namespace {
// Loads a named extension library upon construction and unloads it upon
// destruction.
class LoadExtensionScope {
public:
LoadExtensionScope(WRL::ComPtr<IDebugControl4> p_debug_control,
std::wstring extension_path)
: p_debug_control_(p_debug_control),
extension_path_(std::move(extension_path)) {
p_debug_control->AddExtensionWide(extension_path_.c_str(), 0, &ext_handle_);
// HACK: Below fails, but is required for the extension to actually
// initialize. Just the AddExtension call doesn't actually load and
// initialize it.
p_debug_control->CallExtension(ext_handle_, "Foo", "Bar");
}
~LoadExtensionScope() {
// Let the extension uninitialize so it can deallocate memory, meaning any
// reported memory leaks should be real bugs.
p_debug_control_->RemoveExtension(ext_handle_);
}
private:
LoadExtensionScope(const LoadExtensionScope&) = delete;
LoadExtensionScope& operator=(const LoadExtensionScope&) = delete;
WRL::ComPtr<IDebugControl4> p_debug_control_;
ULONG64 ext_handle_;
// This string is part of the heap snapshot when the extension loads, so keep
// it alive until after the extension unloads and checks for any heap changes.
std::wstring extension_path_;
};
// Initializes COM upon construction and uninitializes it upon destruction.
class ComScope {
public:
ComScope() { hr_ = CoInitializeEx(nullptr, COINIT_MULTITHREADED); }
~ComScope() {
// "To close the COM library gracefully on a thread, each successful call to
// CoInitialize or CoInitializeEx, including any call that returns S_FALSE,
// must be balanced by a corresponding call to CoUninitialize."
// https://docs.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex
if (SUCCEEDED(hr_)) {
CoUninitialize();
}
}
HRESULT hr() { return hr_; }
private:
HRESULT hr_;
};
// Sets a breakpoint. Returns S_OK if the function name resolved successfully
// and the breakpoint is in a non-deferred state.
HRESULT SetBreakpoint(WRL::ComPtr<IDebugControl4> p_debug_control,
const char* function_name) {
WRL::ComPtr<IDebugBreakpoint> bp;
HRESULT hr =
p_debug_control->AddBreakpoint(DEBUG_BREAKPOINT_CODE, DEBUG_ANY_ID, &bp);
if (FAILED(hr)) return hr;
hr = bp->SetOffsetExpression(function_name);
if (FAILED(hr)) return hr;
hr = bp->AddFlags(DEBUG_BREAKPOINT_ENABLED);
if (FAILED(hr)) return hr;
// Check whether the symbol could be found.
uint64_t offset;
hr = bp->GetOffset(&offset);
return hr;
}
// Sets a breakpoint. Depending on the build configuration, the function might
// be in the v8 or d8 module, so this function tries to set both.
HRESULT SetBreakpointInV8OrD8(WRL::ComPtr<IDebugControl4> p_debug_control,
const std::string& function_name) {
// Component builds call the V8 module "v8". Try this first, because there is
// also a module named "d8" or "d8_exe" where we should avoid attempting to
// set a breakpoint.
HRESULT hr = SetBreakpoint(p_debug_control, ("v8!" + function_name).c_str());
if (SUCCEEDED(hr)) return hr;
// x64 release builds call it "d8".
hr = SetBreakpoint(p_debug_control, ("d8!" + function_name).c_str());
if (SUCCEEDED(hr)) return hr;
// x86 release builds call it "d8_exe".
return SetBreakpoint(p_debug_control, ("d8_exe!" + function_name).c_str());
}
void RunAndCheckOutput(const char* friendly_name, const char* command,
std::vector<const char*> expected_substrings,
MyOutput* output, IDebugControl4* p_debug_control) {
output->ClearLog();
CHECK(SUCCEEDED(p_debug_control->Execute(DEBUG_OUTCTL_ALL_CLIENTS, command,
DEBUG_EXECUTE_ECHO)));
for (const char* expected : expected_substrings) {
CHECK(output->GetLog().find(expected) != std::string::npos);
}
}
} // namespace
void RunTests() {
// Initialize COM... Though it doesn't seem to matter if you don't!
ComScope com_scope;
CHECK(SUCCEEDED(com_scope.hr()));
// Get the file path of the module containing this test function. It should be
// in the output directory alongside the data dependencies required by this
// test (d8.exe, v8windbg.dll, and v8windbg-test-script.js).
HMODULE module = nullptr;
bool success =
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(&RunTests), &module);
CHECK(success);
wchar_t this_module_path[MAX_PATH];
DWORD path_size = GetModuleFileName(module, this_module_path, MAX_PATH);
CHECK(path_size != 0);
HRESULT hr = PathCchRemoveFileSpec(this_module_path, MAX_PATH);
CHECK(SUCCEEDED(hr));
// Get the Debug client
WRL::ComPtr<IDebugClient5> p_client;
hr = DebugCreate(__uuidof(IDebugClient5), &p_client);
CHECK(SUCCEEDED(hr));
WRL::ComPtr<IDebugSymbols3> p_symbols;
hr = p_client->QueryInterface(__uuidof(IDebugSymbols3), &p_symbols);
CHECK(SUCCEEDED(hr));
// Symbol loading fails if the pdb is in the same folder as the exe, but it's
// not on the path.
hr = p_symbols->SetSymbolPathWide(this_module_path);
CHECK(SUCCEEDED(hr));
// Set the event callbacks
MyCallback callback;
hr = p_client->SetEventCallbacks(&callback);
CHECK(SUCCEEDED(hr));
// Launch the process with the debugger attached
std::wstring command_line =
std::wstring(L"\"") + this_module_path + L"\\d8.exe\" \"" +
this_module_path + L"\\obj\\tools\\v8windbg\\v8windbg-test-script.js\"";
DEBUG_CREATE_PROCESS_OPTIONS proc_options;
proc_options.CreateFlags = DEBUG_PROCESS;
proc_options.EngCreateFlags = 0;
proc_options.VerifierFlags = 0;
proc_options.Reserved = 0;
hr = p_client->CreateProcessWide(
0, const_cast<wchar_t*>(command_line.c_str()), DEBUG_PROCESS);
CHECK(SUCCEEDED(hr));
// Wait for the attach event
WRL::ComPtr<IDebugControl4> p_debug_control;
hr = p_client->QueryInterface(__uuidof(IDebugControl4), &p_debug_control);
CHECK(SUCCEEDED(hr));
hr = p_debug_control->WaitForEvent(0, INFINITE);
CHECK(SUCCEEDED(hr));
// Break again after non-delay-load modules are loaded.
hr = p_debug_control->AddEngineOptions(DEBUG_ENGOPT_INITIAL_BREAK);
CHECK(SUCCEEDED(hr));
hr = p_debug_control->WaitForEvent(0, INFINITE);
CHECK(SUCCEEDED(hr));
// Set a breakpoint in a C++ function called by the script.
hr = SetBreakpointInV8OrD8(p_debug_control, "v8::internal::JsonStringify");
CHECK(SUCCEEDED(hr));
hr = p_debug_control->SetExecutionStatus(DEBUG_STATUS_GO);
CHECK(SUCCEEDED(hr));
// Wait for the breakpoint.
hr = p_debug_control->WaitForEvent(0, INFINITE);
CHECK(SUCCEEDED(hr));
ULONG type, proc_id, thread_id, desc_used;
byte desc[1024];
hr = p_debug_control->GetLastEventInformation(
&type, &proc_id, &thread_id, nullptr, 0, nullptr,
reinterpret_cast<PSTR>(desc), 1024, &desc_used);
CHECK(SUCCEEDED(hr));
LoadExtensionScope extension_loaded(
p_debug_control, this_module_path + std::wstring(L"\\v8windbg.dll"));
// Set the output callbacks after the extension is loaded, so it gets
// destroyed before the extension unloads. This avoids reporting incorrectly
// reporting that the output buffer was leaked during extension teardown.
MyOutput output(p_client);
// Set stepping mode.
hr = p_debug_control->SetCodeLevel(DEBUG_LEVEL_SOURCE);
CHECK(SUCCEEDED(hr));
// Do some actual testing
RunAndCheckOutput("bitfields",
"p;dx replacer.Value.shared_function_info.flags",
{"kNamedExpression"}, &output, p_debug_control.Get());
RunAndCheckOutput("in-object properties",
"dx object.Value.@\"in-object properties\"[1]",
{"NullValue", "Oddball"}, &output, p_debug_control.Get());
RunAndCheckOutput(
"arrays of structs",
"dx object.Value.map.instance_descriptors.descriptors[1].key",
{"\"secondProp\"", "SeqOneByteString"}, &output, p_debug_control.Get());
RunAndCheckOutput(
"local variables",
"dx -r1 @$curthread.Stack.Frames.Where(f => "
"f.ToDisplayString().Contains(\"InterpreterEntryTrampoline\")).Skip(1)."
"First().LocalVariables.@\"memory interpreted as Objects\"",
{"\"hello\""}, &output, p_debug_control.Get());
// Detach before exiting
hr = p_client->DetachProcesses();
CHECK(SUCCEEDED(hr));
}
} // namespace v8windbg_test
} // namespace internal
} // namespace v8
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef V8_TOOLS_V8WINDBG_TEST_V8WINDBG_TEST_H_
#define V8_TOOLS_V8WINDBG_TEST_V8WINDBG_TEST_H_
namespace v8 {
namespace internal {
namespace v8windbg_test {
void RunTests();
}
} // namespace internal
} // namespace v8
#endif // V8_TOOLS_V8WINDBG_TEST_V8WINDBG_TEST_H_
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment