| // 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/v8windbg-extension.h" |
| |
| #include <iostream> |
| |
| #include "tools/v8windbg/base/utilities.h" |
| #include "tools/v8windbg/src/cur-isolate.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* plist_chunks = L"listchunks"; |
| const wchar_t* pv8_object = L"v8object"; |
| |
| HRESULT CreateExtension() { |
| if (Extension::Current() != nullptr || sp_data_model_manager == nullptr || |
| sp_debug_host == nullptr) { |
| return E_FAIL; |
| } else { |
| std::unique_ptr<Extension> new_extension(new (std::nothrow) Extension()); |
| if (new_extension == nullptr) return E_FAIL; |
| RETURN_IF_FAIL(new_extension->Initialize()); |
| Extension::SetExtension(std::move(new_extension)); |
| return S_OK; |
| } |
| } |
| |
| void DestroyExtension() { Extension::SetExtension(nullptr); } |
| |
| bool Extension::DoesTypeDeriveFromObject( |
| const WRL::ComPtr<IDebugHostType>& sp_type) { |
| _bstr_t name; |
| HRESULT hr = sp_type->GetName(name.GetAddress()); |
| if (!SUCCEEDED(hr)) return false; |
| if (std::string(static_cast<const char*>(name)) == kObject) return true; |
| |
| WRL::ComPtr<IDebugHostSymbolEnumerator> sp_super_class_enumerator; |
| hr = sp_type->EnumerateChildren(SymbolKind::SymbolBaseClass, nullptr, |
| &sp_super_class_enumerator); |
| if (!SUCCEEDED(hr)) return false; |
| |
| while (true) { |
| WRL::ComPtr<IDebugHostSymbol> sp_type_symbol; |
| if (sp_super_class_enumerator->GetNext(&sp_type_symbol) != S_OK) break; |
| WRL::ComPtr<IDebugHostBaseClass> sp_base_class; |
| if (FAILED(sp_type_symbol.As(&sp_base_class))) continue; |
| WRL::ComPtr<IDebugHostType> sp_base_type; |
| hr = sp_base_class->GetType(&sp_base_type); |
| if (!SUCCEEDED(hr)) continue; |
| if (DoesTypeDeriveFromObject(sp_base_type)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| WRL::ComPtr<IDebugHostType> Extension::GetV8ObjectType( |
| WRL::ComPtr<IDebugHostContext>& sp_ctx) { |
| return GetTypeFromV8Module(sp_ctx, kObjectU); |
| } |
| |
| WRL::ComPtr<IDebugHostType> Extension::GetTypeFromV8Module( |
| WRL::ComPtr<IDebugHostContext>& sp_ctx, const char16_t* type_name) { |
| bool is_equal; |
| if (sp_v8_module_ctx_ == nullptr || |
| !SUCCEEDED(sp_v8_module_ctx_->IsEqualTo(sp_ctx.Get(), &is_equal)) || |
| !is_equal) { |
| // Context changed; clear the dictionary. |
| cached_v8_module_types_.clear(); |
| } |
| |
| GetV8Module(sp_ctx); // Will force the correct module to load |
| if (sp_v8_module_ == nullptr) return nullptr; |
| |
| auto& dictionary_entry = cached_v8_module_types_[type_name]; |
| if (dictionary_entry == nullptr) { |
| const std::wstring type_name_w(reinterpret_cast<const wchar_t*>(type_name)); |
| // The contract from debug_helper functions is to provide type names that |
| // would be valid if used in C++ code within the v8::internal namespace. |
| // They might be fully qualified but aren't required to be. Thus, we must |
| // simluate an "unqualified name lookup" here, by searching for the type |
| // starting in the innermost namespace and working outward. |
| if (SUCCEEDED(sp_v8_module_->FindTypeByName( |
| (L"v8::internal::" + type_name_w).c_str(), &dictionary_entry))) { |
| return dictionary_entry; |
| } |
| if (SUCCEEDED(sp_v8_module_->FindTypeByName((L"v8::" + type_name_w).c_str(), |
| &dictionary_entry))) { |
| return dictionary_entry; |
| } |
| sp_v8_module_->FindTypeByName(reinterpret_cast<PCWSTR>(type_name), |
| &dictionary_entry); |
| } |
| return dictionary_entry; |
| } |
| |
| namespace { |
| |
| // Returns whether the given module appears to have symbols for V8 code. |
| bool IsV8Module(IDebugHostModule* module) { |
| WRL::ComPtr<IDebugHostSymbol> sp_isolate_sym; |
| // The below symbol is specific to the main V8 module. |
| if (FAILED(module->FindSymbolByName(L"v8::Script::Run", &sp_isolate_sym))) { |
| return false; |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| WRL::ComPtr<IDebugHostModule> Extension::GetV8Module( |
| WRL::ComPtr<IDebugHostContext>& sp_ctx) { |
| // Return the cached version if it exists and the context is the same |
| |
| // Note: Context will often have the CUSTOM flag set, which never compares |
| // equal. So for now DON'T compare by context, but by proc_id. (An API is in |
| // progress to compare by address space, which should be usable when shipped). |
| /* |
| if (sp_v8_module_ != nullptr) { |
| bool is_equal; |
| if (SUCCEEDED(sp_v8_module_ctx_->IsEqualTo(sp_ctx.Get(), &is_equal)) && |
| is_equal) { return sp_v8_module_; } else { sp_v8_module_ = nullptr; |
| sp_v8_module_ctx_ = nullptr; |
| } |
| } |
| */ |
| WRL::ComPtr<IDebugSystemObjects> sp_sys_objects; |
| ULONG proc_id = 0; |
| if (SUCCEEDED(sp_debug_control.As(&sp_sys_objects))) { |
| if (SUCCEEDED(sp_sys_objects->GetCurrentProcessSystemId(&proc_id))) { |
| if (proc_id == v8_module_proc_id_ && sp_v8_module_ != nullptr) |
| return sp_v8_module_; |
| } |
| } |
| |
| // Search first for a few known module names, to avoid loading symbols for |
| // unrelated modules if we can easily avoid it. Generally, failing to find a |
| // module is fast but failing to find a symbol within a module is slow. Note |
| // that "v8" is listed first because it's highly likely to be the correct |
| // module if it exists. The others might include V8 symbols depending on the |
| // build configuration. |
| std::vector<const wchar_t*> known_names = { |
| L"v8", L"v8_for_testing", L"cctest_exe", L"chrome", |
| L"d8", L"msedge", L"node", L"unittests_exe"}; |
| for (const wchar_t* name : known_names) { |
| WRL::ComPtr<IDebugHostModule> sp_module; |
| if (SUCCEEDED(sp_debug_host_symbols->FindModuleByName(sp_ctx.Get(), name, |
| &sp_module))) { |
| if (IsV8Module(sp_module.Get())) { |
| sp_v8_module_ = sp_module; |
| sp_v8_module_ctx_ = sp_ctx; |
| v8_module_proc_id_ = proc_id; |
| return sp_v8_module_; |
| } |
| } |
| } |
| |
| // Loop through all modules looking for the one that holds a known symbol. |
| WRL::ComPtr<IDebugHostSymbolEnumerator> sp_enum; |
| if (SUCCEEDED( |
| sp_debug_host_symbols->EnumerateModules(sp_ctx.Get(), &sp_enum))) { |
| HRESULT hr = S_OK; |
| while (true) { |
| WRL::ComPtr<IDebugHostSymbol> sp_mod_sym; |
| hr = sp_enum->GetNext(&sp_mod_sym); |
| // hr == E_BOUNDS : hit the end of the enumerator |
| // hr == E_ABORT : a user interrupt was requested |
| if (FAILED(hr)) break; |
| WRL::ComPtr<IDebugHostModule> sp_module; |
| if (SUCCEEDED(sp_mod_sym.As(&sp_module))) /* should always succeed */ |
| { |
| if (IsV8Module(sp_module.Get())) { |
| sp_v8_module_ = sp_module; |
| sp_v8_module_ctx_ = sp_ctx; |
| v8_module_proc_id_ = proc_id; |
| break; |
| } |
| } |
| } |
| } |
| // This will be the located module, or still nullptr if above fails |
| return sp_v8_module_; |
| } |
| |
| Extension::Extension() = default; |
| |
| HRESULT Extension::Initialize() { |
| // Create an instance of the DataModel parent for v8::internal::Object types. |
| auto object_data_model{WRL::Make<V8ObjectDataModel>()}; |
| RETURN_IF_FAIL(sp_data_model_manager->CreateDataModelObject( |
| object_data_model.Get(), &sp_object_data_model_)); |
| RETURN_IF_FAIL(sp_object_data_model_->SetConcept( |
| __uuidof(IStringDisplayableConcept), |
| static_cast<IStringDisplayableConcept*>(object_data_model.Get()), |
| nullptr)); |
| RETURN_IF_FAIL(sp_object_data_model_->SetConcept( |
| __uuidof(IDynamicKeyProviderConcept), |
| static_cast<IDynamicKeyProviderConcept*>(object_data_model.Get()), |
| nullptr)); |
| |
| // Register that parent model for all known types of V8 object. |
| std::vector<std::u16string> object_class_names = ListObjectClasses(); |
| object_class_names.push_back(kObjectU); |
| object_class_names.push_back(kTaggedValueU); |
| for (const std::u16string& name : object_class_names) { |
| WRL::ComPtr<IDebugHostTypeSignature> sp_object_type_signature; |
| RETURN_IF_FAIL(sp_debug_host_symbols->CreateTypeSignature( |
| reinterpret_cast<const wchar_t*>(name.c_str()), nullptr, |
| &sp_object_type_signature)); |
| RETURN_IF_FAIL(sp_data_model_manager->RegisterModelForTypeSignature( |
| sp_object_type_signature.Get(), sp_object_data_model_.Get())); |
| registered_types_.push_back( |
| {sp_object_type_signature.Get(), sp_object_data_model_.Get()}); |
| } |
| |
| // Create an instance of the DataModel parent for custom iterable fields. |
| auto indexed_field_model{WRL::Make<IndexedFieldParent>()}; |
| RETURN_IF_FAIL(sp_data_model_manager->CreateDataModelObject( |
| indexed_field_model.Get(), &sp_indexed_field_model_)); |
| RETURN_IF_FAIL(sp_indexed_field_model_->SetConcept( |
| __uuidof(IIndexableConcept), |
| static_cast<IIndexableConcept*>(indexed_field_model.Get()), nullptr)); |
| RETURN_IF_FAIL(sp_indexed_field_model_->SetConcept( |
| __uuidof(IIterableConcept), |
| static_cast<IIterableConcept*>(indexed_field_model.Get()), nullptr)); |
| |
| // Create an instance of the DataModel parent class for v8::Local<*> types. |
| auto local_data_model{WRL::Make<V8LocalDataModel>()}; |
| RETURN_IF_FAIL(sp_data_model_manager->CreateDataModelObject( |
| local_data_model.Get(), &sp_local_data_model_)); |
| |
| // Register that parent model for all known types that act like v8::Local. |
| std::vector<const wchar_t*> handle_class_names = { |
| L"v8::Local<*>", L"v8::MaybeLocal<*>", L"v8::internal::Handle<*>", |
| L"v8::internal::MaybeHandle<*>"}; |
| for (const wchar_t* name : handle_class_names) { |
| WRL::ComPtr<IDebugHostTypeSignature> signature; |
| RETURN_IF_FAIL( |
| sp_debug_host_symbols->CreateTypeSignature(name, nullptr, &signature)); |
| RETURN_IF_FAIL(sp_data_model_manager->RegisterModelForTypeSignature( |
| signature.Get(), sp_local_data_model_.Get())); |
| registered_types_.push_back({signature.Get(), sp_local_data_model_.Get()}); |
| } |
| |
| // Add the 'Value' property to the parent model. |
| auto local_value_property{WRL::Make<V8LocalValueProperty>()}; |
| WRL::ComPtr<IModelObject> sp_local_value_property_model; |
| RETURN_IF_FAIL(CreateProperty(sp_data_model_manager.Get(), |
| local_value_property.Get(), |
| &sp_local_value_property_model)); |
| RETURN_IF_FAIL(sp_local_data_model_->SetKey( |
| L"Value", sp_local_value_property_model.Get(), nullptr)); |
| |
| // Register all function aliases. |
| std::vector<std::pair<const wchar_t*, WRL::ComPtr<IModelMethod>>> functions = |
| {{pcur_isolate, WRL::Make<CurrIsolateAlias>()}, |
| {plist_chunks, WRL::Make<ListChunksAlias>()}, |
| {pv8_object, WRL::Make<InspectV8ObjectMethod>()}}; |
| for (const auto& function : functions) { |
| WRL::ComPtr<IModelObject> method; |
| RETURN_IF_FAIL(CreateMethod(sp_data_model_manager.Get(), |
| function.second.Get(), &method)); |
| RETURN_IF_FAIL(sp_debug_host_extensibility->CreateFunctionAlias( |
| function.first, method.Get())); |
| } |
| |
| // Register a handler for supplying stack frame locals. It has to override the |
| // getter functions for "LocalVariables" and "Parameters". |
| WRL::ComPtr<IModelObject> stack_frame; |
| RETURN_IF_FAIL(sp_data_model_manager->AcquireNamedModel( |
| L"Debugger.Models.StackFrame", &stack_frame)); |
| RETURN_IF_FAIL(OverrideLocalsGetter(stack_frame.Get(), L"LocalVariables", |
| /*is_parameters=*/false)); |
| RETURN_IF_FAIL(OverrideLocalsGetter(stack_frame.Get(), L"Parameters", |
| /*is_parameters=*/true)); |
| |
| // Add node_id property for v8::internal::compiler::Node. |
| RETURN_IF_FAIL( |
| RegisterAndAddPropertyForClass<V8InternalCompilerNodeIdProperty>( |
| L"v8::internal::compiler::Node", L"node_id", |
| sp_compiler_node_data_model_)); |
| |
| // Add bitset_name property for v8::internal::compiler::Type. |
| RETURN_IF_FAIL( |
| RegisterAndAddPropertyForClass<V8InternalCompilerBitsetNameProperty>( |
| L"v8::internal::compiler::Type", L"bitset_name", |
| sp_compiler_type_data_model_)); |
| |
| return S_OK; |
| } |
| |
| template <class PropertyClass> |
| HRESULT Extension::RegisterAndAddPropertyForClass( |
| const wchar_t* class_name, const wchar_t* property_name, |
| WRL::ComPtr<IModelObject> sp_data_model) { |
| // Create an instance of the DataModel parent class. |
| auto instance_data_model{WRL::Make<V8LocalDataModel>()}; |
| RETURN_IF_FAIL(sp_data_model_manager->CreateDataModelObject( |
| instance_data_model.Get(), &sp_data_model)); |
| |
| // Register that parent model. |
| WRL::ComPtr<IDebugHostTypeSignature> class_signature; |
| RETURN_IF_FAIL(sp_debug_host_symbols->CreateTypeSignature(class_name, nullptr, |
| &class_signature)); |
| RETURN_IF_FAIL(sp_data_model_manager->RegisterModelForTypeSignature( |
| class_signature.Get(), sp_data_model.Get())); |
| registered_types_.push_back({class_signature.Get(), sp_data_model.Get()}); |
| |
| // Add the property to the parent model. |
| auto property{WRL::Make<PropertyClass>()}; |
| WRL::ComPtr<IModelObject> sp_property_model; |
| RETURN_IF_FAIL(CreateProperty(sp_data_model_manager.Get(), property.Get(), |
| &sp_property_model)); |
| RETURN_IF_FAIL( |
| sp_data_model->SetKey(property_name, sp_property_model.Get(), nullptr)); |
| |
| return S_OK; |
| } |
| |
| HRESULT Extension::OverrideLocalsGetter(IModelObject* stack_frame, |
| const wchar_t* key_name, |
| bool is_parameters) { |
| WRL::ComPtr<IModelObject> original_boxed_getter; |
| WRL::ComPtr<IKeyStore> original_getter_metadata; |
| RETURN_IF_FAIL(stack_frame->GetKey(key_name, &original_boxed_getter, |
| &original_getter_metadata)); |
| WRL::ComPtr<IModelPropertyAccessor> original_getter; |
| RETURN_IF_FAIL(UnboxProperty(original_boxed_getter.Get(), &original_getter)); |
| auto new_getter{WRL::Make<V8LocalVariables>(original_getter, is_parameters)}; |
| WRL::ComPtr<IModelObject> new_boxed_getter; |
| RETURN_IF_FAIL(CreateProperty(sp_data_model_manager.Get(), new_getter.Get(), |
| &new_boxed_getter)); |
| RETURN_IF_FAIL(stack_frame->SetKey(key_name, new_boxed_getter.Get(), |
| original_getter_metadata.Get())); |
| overridden_properties_.push_back( |
| {stack_frame, reinterpret_cast<const char16_t*>(key_name), |
| original_boxed_getter.Get(), original_getter_metadata.Get()}); |
| return S_OK; |
| } |
| |
| Extension::PropertyOverride::PropertyOverride() = default; |
| Extension::PropertyOverride::PropertyOverride(IModelObject* parent, |
| std::u16string key_name, |
| IModelObject* original_value, |
| IKeyStore* original_metadata) |
| : parent(parent), |
| key_name(std::move(key_name)), |
| original_value(original_value), |
| original_metadata(original_metadata) {} |
| Extension::PropertyOverride::~PropertyOverride() = default; |
| Extension::PropertyOverride::PropertyOverride(const PropertyOverride&) = |
| default; |
| Extension::PropertyOverride& Extension::PropertyOverride::operator=( |
| const PropertyOverride&) = default; |
| |
| Extension::RegistrationType::RegistrationType() = default; |
| Extension::RegistrationType::RegistrationType( |
| IDebugHostTypeSignature* sp_signature, IModelObject* sp_data_model) |
| : sp_signature(sp_signature), sp_data_model(sp_data_model) {} |
| Extension::RegistrationType::~RegistrationType() = default; |
| Extension::RegistrationType::RegistrationType(const RegistrationType&) = |
| default; |
| Extension::RegistrationType& Extension::RegistrationType::operator=( |
| const RegistrationType&) = default; |
| |
| Extension::~Extension() { |
| sp_debug_host_extensibility->DestroyFunctionAlias(pcur_isolate); |
| sp_debug_host_extensibility->DestroyFunctionAlias(plist_chunks); |
| sp_debug_host_extensibility->DestroyFunctionAlias(pv8_object); |
| |
| for (const auto& registered : registered_types_) { |
| sp_data_model_manager->UnregisterModelForTypeSignature( |
| registered.sp_data_model.Get(), registered.sp_signature.Get()); |
| } |
| |
| for (const auto& override : overridden_properties_) { |
| override.parent->SetKey( |
| reinterpret_cast<const wchar_t*>(override.key_name.c_str()), |
| override.original_value.Get(), override.original_metadata.Get()); |
| } |
| } |