[v8windbg] Add jsstack command
Change-Id: I8ea9403fa2ae8d45300c291a6d9a55b9293e7c1d Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2805731 Commit-Queue: Z Nguyen-Huu <duongn@microsoft.com> Reviewed-by: Seth Brenith <seth.brenith@microsoft.com> Cr-Commit-Position: refs/heads/master@{#73846}
This commit is contained in:
parent
0167fddb87
commit
9512bd22e6
@ -40,6 +40,8 @@ v8_shared_library("v8windbg") {
|
||||
"base/dbgext.def",
|
||||
"src/cur-isolate.cc",
|
||||
"src/cur-isolate.h",
|
||||
"src/js-stack.cc",
|
||||
"src/js-stack.h",
|
||||
"src/list-chunks.cc",
|
||||
"src/list-chunks.h",
|
||||
"src/local-variables.cc",
|
||||
|
@ -43,6 +43,8 @@ functions that can be called from within `dx` commands:
|
||||
current thread has a JavaScript Isolate associated.
|
||||
- `@$listchunks()` returns a list of the memory chunks in the Heap for the
|
||||
current Isolate.
|
||||
- `@$jsstack()` returns a list of the JS stack frames, including information
|
||||
about script and function.
|
||||
|
||||
*Tip:*: to see what objects are present in a chunk of heap memory, you can cast
|
||||
it to an array of `TaggedValue`, like this:
|
||||
@ -67,6 +69,8 @@ functions declared in `dbgext.h` to create and destroy the extension instance.
|
||||
- `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.
|
||||
- `js-stack.{cc,h}` implements the `IModelMethod` for `@$jsstack()`. 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.
|
||||
|
229
tools/v8windbg/src/js-stack.cc
Normal file
229
tools/v8windbg/src/js-stack.cc
Normal file
@ -0,0 +1,229 @@
|
||||
// Copyright 2021 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/js-stack.h"
|
||||
|
||||
HRESULT GetJSStackFrames(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_stack;
|
||||
RETURN_IF_FAIL(sp_curr_thread->GetKeyValue(L"Stack", &sp_stack, nullptr));
|
||||
|
||||
RETURN_IF_FAIL(sp_stack->GetKeyValue(L"Frames", &sp_result, nullptr));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// v8windbg!JSStackAlias::Call
|
||||
IFACEMETHODIMP JSStackAlias::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<StackFrames>()};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
FrameData::FrameData() = default;
|
||||
FrameData::~FrameData() = default;
|
||||
FrameData::FrameData(const FrameData&) = default;
|
||||
FrameData::FrameData(FrameData&&) = default;
|
||||
FrameData& FrameData::operator=(const FrameData&) = default;
|
||||
FrameData& FrameData::operator=(FrameData&&) = default;
|
||||
|
||||
StackFrameIterator::StackFrameIterator(
|
||||
WRL::ComPtr<IDebugHostContext>& host_context)
|
||||
: sp_ctx_(host_context) {}
|
||||
StackFrameIterator::~StackFrameIterator() = default;
|
||||
|
||||
HRESULT StackFrameIterator::PopulateFrameData() {
|
||||
frames_.clear();
|
||||
WRL::ComPtr<IModelObject> sp_frames;
|
||||
|
||||
RETURN_IF_FAIL(GetJSStackFrames(sp_frames));
|
||||
|
||||
// Iterate over the array of frames.
|
||||
WRL::ComPtr<IIterableConcept> sp_iterable;
|
||||
RETURN_IF_FAIL(
|
||||
sp_frames->GetConcept(__uuidof(IIterableConcept), &sp_iterable, nullptr));
|
||||
|
||||
WRL::ComPtr<IModelIterator> sp_frame_iterator;
|
||||
RETURN_IF_FAIL(sp_iterable->GetIterator(sp_frames.Get(), &sp_frame_iterator));
|
||||
|
||||
// Loop through all the frames in the array.
|
||||
WRL::ComPtr<IModelObject> sp_frame;
|
||||
while (sp_frame_iterator->GetNext(&sp_frame, 0, nullptr, nullptr) !=
|
||||
E_BOUNDS) {
|
||||
// Skip non-JS frame (frame that doesn't have a function_name).
|
||||
WRL::ComPtr<IModelObject> sp_local_variables;
|
||||
HRESULT hr =
|
||||
sp_frame->GetKeyValue(L"LocalVariables", &sp_local_variables, nullptr);
|
||||
if (FAILED(hr)) continue;
|
||||
|
||||
WRL::ComPtr<IModelObject> sp_currently_executing_jsfunction;
|
||||
hr = sp_local_variables->GetKeyValue(L"currently_executing_jsfunction",
|
||||
&sp_currently_executing_jsfunction,
|
||||
nullptr);
|
||||
if (FAILED(hr)) continue;
|
||||
|
||||
WRL::ComPtr<IModelObject> sp_function_name, sp_script_name,
|
||||
sp_script_source, sp_function_character_offset;
|
||||
RETURN_IF_FAIL(sp_local_variables->GetKeyValue(L"script_name",
|
||||
&sp_script_name, nullptr));
|
||||
RETURN_IF_FAIL(sp_local_variables->GetKeyValue(L"script_source",
|
||||
&sp_script_source, nullptr));
|
||||
RETURN_IF_FAIL(sp_local_variables->GetKeyValue(L"function_name",
|
||||
&sp_function_name, nullptr));
|
||||
RETURN_IF_FAIL(sp_local_variables->GetKeyValue(
|
||||
L"function_character_offset", &sp_function_character_offset, nullptr));
|
||||
|
||||
FrameData frame_entry;
|
||||
frame_entry.script_name = sp_script_name;
|
||||
frame_entry.script_source = sp_script_source;
|
||||
frame_entry.function_name = sp_function_name;
|
||||
frame_entry.function_character_offset = sp_function_character_offset;
|
||||
frames_.push_back(frame_entry);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP StackFrameIterator::Reset() noexcept {
|
||||
position_ = 0;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP StackFrameIterator::GetNext(IModelObject** object,
|
||||
ULONG64 dimensions,
|
||||
IModelObject** indexers,
|
||||
IKeyStore** metadata) noexcept {
|
||||
if (dimensions > 1) return E_INVALIDARG;
|
||||
|
||||
if (position_ == 0) {
|
||||
RETURN_IF_FAIL(PopulateFrameData());
|
||||
}
|
||||
|
||||
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 StackFrameIterator::GetAt(uint64_t index, IModelObject** result) const {
|
||||
if (index >= frames_.size()) return E_BOUNDS;
|
||||
|
||||
// Create the synthetic object representing the frame here.
|
||||
const FrameData& curr_frame = frames_.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"script_name", curr_frame.script_name.Get(), nullptr));
|
||||
RETURN_IF_FAIL(sp_value->SetKey(L"script_source",
|
||||
curr_frame.script_source.Get(), nullptr));
|
||||
RETURN_IF_FAIL(sp_value->SetKey(L"function_name",
|
||||
curr_frame.function_name.Get(), nullptr));
|
||||
RETURN_IF_FAIL(sp_value->SetKey(L"function_character_offset",
|
||||
curr_frame.function_character_offset.Get(),
|
||||
nullptr));
|
||||
|
||||
*result = sp_value.Detach();
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
StackFrames::StackFrames() = default;
|
||||
StackFrames::~StackFrames() = default;
|
||||
|
||||
IFACEMETHODIMP StackFrames::GetDimensionality(
|
||||
IModelObject* context_object, ULONG64* dimensionality) noexcept {
|
||||
*dimensionality = 1;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP StackFrames::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_frames_ == nullptr) {
|
||||
opt_frames_ = WRL::Make<StackFrameIterator>(sp_ctx);
|
||||
_ASSERT(opt_frames_ != nullptr);
|
||||
RETURN_IF_FAIL(opt_frames_->PopulateFrameData());
|
||||
}
|
||||
|
||||
uint64_t index;
|
||||
RETURN_IF_FAIL(UnboxULong64(indexers[0], &index, true /*convert*/));
|
||||
|
||||
return opt_frames_->GetAt(index, object);
|
||||
}
|
||||
|
||||
IFACEMETHODIMP StackFrames::SetAt(IModelObject* context_object,
|
||||
ULONG64 indexer_count,
|
||||
IModelObject** indexers,
|
||||
IModelObject* value) noexcept {
|
||||
return E_NOTIMPL;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP StackFrames::GetDefaultIndexDimensionality(
|
||||
IModelObject* context_object, ULONG64* dimensionality) noexcept {
|
||||
*dimensionality = 1;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP StackFrames::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<StackFrameIterator>(sp_ctx)};
|
||||
*iterator = sp_memory_iterator.Detach();
|
||||
return S_OK;
|
||||
}
|
98
tools/v8windbg/src/js-stack.h
Normal file
98
tools/v8windbg/src/js-stack.h
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright 2021 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_JS_STACK_H_
|
||||
#define V8_TOOLS_V8WINDBG_SRC_JS_STACK_H_
|
||||
|
||||
#include <crtdbg.h>
|
||||
#include <wrl/implements.h>
|
||||
|
||||
#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 JSStackAlias
|
||||
: 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 FrameData {
|
||||
FrameData();
|
||||
~FrameData();
|
||||
FrameData(const FrameData&);
|
||||
FrameData(FrameData&&);
|
||||
FrameData& operator=(const FrameData&);
|
||||
FrameData& operator=(FrameData&&);
|
||||
WRL::ComPtr<IModelObject> script_name;
|
||||
WRL::ComPtr<IModelObject> script_source;
|
||||
WRL::ComPtr<IModelObject> function_name;
|
||||
WRL::ComPtr<IModelObject> function_character_offset;
|
||||
};
|
||||
|
||||
class StackFrameIterator
|
||||
: public WRL::RuntimeClass<
|
||||
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
|
||||
IModelIterator> {
|
||||
public:
|
||||
StackFrameIterator(WRL::ComPtr<IDebugHostContext>& host_context);
|
||||
~StackFrameIterator() override;
|
||||
|
||||
HRESULT PopulateFrameData();
|
||||
|
||||
IFACEMETHOD(Reset)();
|
||||
|
||||
IFACEMETHOD(GetNext)
|
||||
(IModelObject** object, ULONG64 dimensions, IModelObject** indexers,
|
||||
IKeyStore** metadata);
|
||||
|
||||
HRESULT GetAt(uint64_t index, IModelObject** result) const;
|
||||
|
||||
private:
|
||||
ULONG position_ = 0;
|
||||
std::vector<FrameData> frames_;
|
||||
WRL::ComPtr<IDebugHostContext> sp_ctx_;
|
||||
};
|
||||
|
||||
class StackFrames
|
||||
: public WRL::RuntimeClass<
|
||||
WRL::RuntimeClassFlags<WRL::RuntimeClassType::ClassicCom>,
|
||||
IIndexableConcept, IIterableConcept> {
|
||||
public:
|
||||
StackFrames();
|
||||
~StackFrames() 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<StackFrameIterator> opt_frames_;
|
||||
};
|
||||
|
||||
#endif // V8_TOOLS_V8WINDBG_SRC_JS_STACK_H_
|
@ -8,12 +8,14 @@
|
||||
|
||||
#include "tools/v8windbg/base/utilities.h"
|
||||
#include "tools/v8windbg/src/cur-isolate.h"
|
||||
#include "tools/v8windbg/src/js-stack.h"
|
||||
#include "tools/v8windbg/src/list-chunks.h"
|
||||
#include "tools/v8windbg/src/local-variables.h"
|
||||
#include "tools/v8windbg/src/object-inspection.h"
|
||||
|
||||
std::unique_ptr<Extension> Extension::current_extension_ = nullptr;
|
||||
const wchar_t* pcur_isolate = L"curisolate";
|
||||
const wchar_t* pjs_stack = L"jsstack";
|
||||
const wchar_t* plist_chunks = L"listchunks";
|
||||
const wchar_t* pv8_object = L"v8object";
|
||||
|
||||
@ -260,6 +262,7 @@ HRESULT Extension::Initialize() {
|
||||
// Register all function aliases.
|
||||
std::vector<std::pair<const wchar_t*, WRL::ComPtr<IModelMethod>>> functions =
|
||||
{{pcur_isolate, WRL::Make<CurrIsolateAlias>()},
|
||||
{pjs_stack, WRL::Make<JSStackAlias>()},
|
||||
{plist_chunks, WRL::Make<ListChunksAlias>()},
|
||||
{pv8_object, WRL::Make<InspectV8ObjectMethod>()}};
|
||||
for (const auto& function : functions) {
|
||||
@ -371,6 +374,7 @@ Extension::RegistrationType& Extension::RegistrationType::operator=(
|
||||
|
||||
Extension::~Extension() {
|
||||
sp_debug_host_extensibility->DestroyFunctionAlias(pcur_isolate);
|
||||
sp_debug_host_extensibility->DestroyFunctionAlias(pjs_stack);
|
||||
sp_debug_host_extensibility->DestroyFunctionAlias(plist_chunks);
|
||||
sp_debug_host_extensibility->DestroyFunctionAlias(pv8_object);
|
||||
|
||||
|
@ -226,12 +226,26 @@ void RunTests() {
|
||||
"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());
|
||||
// TODO(v8:11527): enable this when symbol information for the in-Isolate
|
||||
// builtins is available.
|
||||
// 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());
|
||||
|
||||
RunAndCheckOutput("js stack", "dx @$jsstack()[0].function_name",
|
||||
{"\"a\"", "SeqOneByteString"}, &output,
|
||||
p_debug_control.Get());
|
||||
|
||||
RunAndCheckOutput("js stack", "dx @$jsstack()[1].function_name",
|
||||
{"\"b\"", "SeqOneByteString"}, &output,
|
||||
p_debug_control.Get());
|
||||
|
||||
RunAndCheckOutput("js stack", "dx @$jsstack()[2].function_name",
|
||||
{"empty_string \"\"", "SeqOneByteString"}, &output,
|
||||
p_debug_control.Get());
|
||||
|
||||
// Detach before exiting
|
||||
hr = p_client->DetachProcesses();
|
||||
|
Loading…
Reference in New Issue
Block a user