1 // Copyright 2020 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "tools/v8windbg/src/v8windbg-extension.h"
6
7 #include <iostream>
8
9 #include "tools/v8windbg/base/utilities.h"
10 #include "tools/v8windbg/src/cur-isolate.h"
11 #include "tools/v8windbg/src/js-stack.h"
12 #include "tools/v8windbg/src/local-variables.h"
13 #include "tools/v8windbg/src/object-inspection.h"
14
15 std::unique_ptr<Extension> Extension::current_extension_ = nullptr;
16 const wchar_t* pcur_isolate = L"curisolate";
17 const wchar_t* pjs_stack = L"jsstack";
18 const wchar_t* pv8_object = L"v8object";
19
CreateExtension()20 HRESULT CreateExtension() {
21 if (Extension::Current() != nullptr || sp_data_model_manager == nullptr ||
22 sp_debug_host == nullptr) {
23 return E_FAIL;
24 } else {
25 std::unique_ptr<Extension> new_extension(new (std::nothrow) Extension());
26 if (new_extension == nullptr) return E_FAIL;
27 RETURN_IF_FAIL(new_extension->Initialize());
28 Extension::SetExtension(std::move(new_extension));
29 return S_OK;
30 }
31 }
32
DestroyExtension()33 void DestroyExtension() { Extension::SetExtension(nullptr); }
34
DoesTypeDeriveFromObject(const WRL::ComPtr<IDebugHostType> & sp_type)35 bool Extension::DoesTypeDeriveFromObject(
36 const WRL::ComPtr<IDebugHostType>& sp_type) {
37 _bstr_t name;
38 HRESULT hr = sp_type->GetName(name.GetAddress());
39 if (!SUCCEEDED(hr)) return false;
40 if (std::string(static_cast<const char*>(name)) == kObject) return true;
41
42 WRL::ComPtr<IDebugHostSymbolEnumerator> sp_super_class_enumerator;
43 hr = sp_type->EnumerateChildren(SymbolKind::SymbolBaseClass, nullptr,
44 &sp_super_class_enumerator);
45 if (!SUCCEEDED(hr)) return false;
46
47 while (true) {
48 WRL::ComPtr<IDebugHostSymbol> sp_type_symbol;
49 if (sp_super_class_enumerator->GetNext(&sp_type_symbol) != S_OK) break;
50 WRL::ComPtr<IDebugHostBaseClass> sp_base_class;
51 if (FAILED(sp_type_symbol.As(&sp_base_class))) continue;
52 WRL::ComPtr<IDebugHostType> sp_base_type;
53 hr = sp_base_class->GetType(&sp_base_type);
54 if (!SUCCEEDED(hr)) continue;
55 if (DoesTypeDeriveFromObject(sp_base_type)) {
56 return true;
57 }
58 }
59
60 return false;
61 }
62
GetV8ObjectType(WRL::ComPtr<IDebugHostContext> & sp_ctx)63 WRL::ComPtr<IDebugHostType> Extension::GetV8ObjectType(
64 WRL::ComPtr<IDebugHostContext>& sp_ctx) {
65 return GetTypeFromV8Module(sp_ctx, kObjectU);
66 }
67
GetTypeFromV8Module(WRL::ComPtr<IDebugHostContext> & sp_ctx,const char16_t * type_name)68 WRL::ComPtr<IDebugHostType> Extension::GetTypeFromV8Module(
69 WRL::ComPtr<IDebugHostContext>& sp_ctx, const char16_t* type_name) {
70 bool is_equal;
71 if (sp_v8_module_ctx_ == nullptr ||
72 !SUCCEEDED(sp_v8_module_ctx_->IsEqualTo(sp_ctx.Get(), &is_equal)) ||
73 !is_equal) {
74 // Context changed; clear the dictionary.
75 cached_v8_module_types_.clear();
76 }
77
78 GetV8Module(sp_ctx); // Will force the correct module to load
79 if (sp_v8_module_ == nullptr) return nullptr;
80
81 auto& dictionary_entry = cached_v8_module_types_[type_name];
82 if (dictionary_entry == nullptr) {
83 const std::wstring type_name_w(reinterpret_cast<const wchar_t*>(type_name));
84 // The contract from debug_helper functions is to provide type names that
85 // would be valid if used in C++ code within the v8::internal namespace.
86 // They might be fully qualified but aren't required to be. Thus, we must
87 // simluate an "unqualified name lookup" here, by searching for the type
88 // starting in the innermost namespace and working outward.
89 if (SUCCEEDED(sp_v8_module_->FindTypeByName(
90 (L"v8::internal::" + type_name_w).c_str(), &dictionary_entry))) {
91 return dictionary_entry;
92 }
93 if (SUCCEEDED(sp_v8_module_->FindTypeByName((L"v8::" + type_name_w).c_str(),
94 &dictionary_entry))) {
95 return dictionary_entry;
96 }
97 sp_v8_module_->FindTypeByName(reinterpret_cast<PCWSTR>(type_name),
98 &dictionary_entry);
99 }
100 return dictionary_entry;
101 }
102
103 namespace {
104
105 // Returns whether the given module appears to have symbols for V8 code.
IsV8Module(IDebugHostModule * module)106 bool IsV8Module(IDebugHostModule* module) {
107 WRL::ComPtr<IDebugHostSymbol> sp_isolate_sym;
108 // The below symbol is specific to the main V8 module and is specified with
109 // V8_NOINLINE, so it should always be present.
110 if (FAILED(module->FindSymbolByName(
111 L"v8::internal::Isolate::PushStackTraceAndDie", &sp_isolate_sym))) {
112 return false;
113 }
114 return true;
115 }
116
117 } // namespace
118
GetV8Module(WRL::ComPtr<IDebugHostContext> & sp_ctx)119 WRL::ComPtr<IDebugHostModule> Extension::GetV8Module(
120 WRL::ComPtr<IDebugHostContext>& sp_ctx) {
121 // Return the cached version if it exists and the context is the same
122
123 // Note: Context will often have the CUSTOM flag set, which never compares
124 // equal. So for now DON'T compare by context, but by proc_id. (An API is in
125 // progress to compare by address space, which should be usable when shipped).
126 /*
127 if (sp_v8_module_ != nullptr) {
128 bool is_equal;
129 if (SUCCEEDED(sp_v8_module_ctx_->IsEqualTo(sp_ctx.Get(), &is_equal)) &&
130 is_equal) { return sp_v8_module_; } else { sp_v8_module_ = nullptr;
131 sp_v8_module_ctx_ = nullptr;
132 }
133 }
134 */
135 WRL::ComPtr<IDebugSystemObjects> sp_sys_objects;
136 ULONG proc_id = 0;
137 if (SUCCEEDED(sp_debug_control.As(&sp_sys_objects))) {
138 if (SUCCEEDED(sp_sys_objects->GetCurrentProcessSystemId(&proc_id))) {
139 if (proc_id == v8_module_proc_id_ && sp_v8_module_ != nullptr)
140 return sp_v8_module_;
141 }
142 }
143
144 // Search first for a few known module names, to avoid loading symbols for
145 // unrelated modules if we can easily avoid it. Generally, failing to find a
146 // module is fast but failing to find a symbol within a module is slow. Note
147 // that "v8" is listed first because it's highly likely to be the correct
148 // module if it exists. The others might include V8 symbols depending on the
149 // build configuration.
150 std::vector<const wchar_t*> known_names = {
151 L"v8", L"v8_for_testing", L"cctest_exe", L"chrome",
152 L"d8", L"msedge", L"node", L"unittests_exe"};
153 for (const wchar_t* name : known_names) {
154 WRL::ComPtr<IDebugHostModule> sp_module;
155 if (SUCCEEDED(sp_debug_host_symbols->FindModuleByName(sp_ctx.Get(), name,
156 &sp_module))) {
157 if (IsV8Module(sp_module.Get())) {
158 sp_v8_module_ = sp_module;
159 sp_v8_module_ctx_ = sp_ctx;
160 v8_module_proc_id_ = proc_id;
161 return sp_v8_module_;
162 }
163 }
164 }
165
166 // Loop through all modules looking for the one that holds a known symbol.
167 WRL::ComPtr<IDebugHostSymbolEnumerator> sp_enum;
168 if (SUCCEEDED(
169 sp_debug_host_symbols->EnumerateModules(sp_ctx.Get(), &sp_enum))) {
170 HRESULT hr = S_OK;
171 while (true) {
172 WRL::ComPtr<IDebugHostSymbol> sp_mod_sym;
173 hr = sp_enum->GetNext(&sp_mod_sym);
174 // hr == E_BOUNDS : hit the end of the enumerator
175 // hr == E_ABORT : a user interrupt was requested
176 if (FAILED(hr)) break;
177 WRL::ComPtr<IDebugHostModule> sp_module;
178 if (SUCCEEDED(sp_mod_sym.As(&sp_module))) /* should always succeed */
179 {
180 if (IsV8Module(sp_module.Get())) {
181 sp_v8_module_ = sp_module;
182 sp_v8_module_ctx_ = sp_ctx;
183 v8_module_proc_id_ = proc_id;
184 break;
185 }
186 }
187 }
188 }
189 // This will be the located module, or still nullptr if above fails
190 return sp_v8_module_;
191 }
192
193 Extension::Extension() = default;
194
Initialize()195 HRESULT Extension::Initialize() {
196 // Create an instance of the DataModel parent for v8::internal::Object types.
197 auto object_data_model{WRL::Make<V8ObjectDataModel>()};
198 RETURN_IF_FAIL(sp_data_model_manager->CreateDataModelObject(
199 object_data_model.Get(), &sp_object_data_model_));
200 RETURN_IF_FAIL(sp_object_data_model_->SetConcept(
201 __uuidof(IStringDisplayableConcept),
202 static_cast<IStringDisplayableConcept*>(object_data_model.Get()),
203 nullptr));
204 RETURN_IF_FAIL(sp_object_data_model_->SetConcept(
205 __uuidof(IDynamicKeyProviderConcept),
206 static_cast<IDynamicKeyProviderConcept*>(object_data_model.Get()),
207 nullptr));
208
209 // Register that parent model for all known types of V8 object.
210 std::vector<std::u16string> object_class_names = ListObjectClasses();
211 object_class_names.push_back(kObjectU);
212 object_class_names.push_back(kTaggedValueU);
213 for (const std::u16string& name : object_class_names) {
214 WRL::ComPtr<IDebugHostTypeSignature> sp_object_type_signature;
215 RETURN_IF_FAIL(sp_debug_host_symbols->CreateTypeSignature(
216 reinterpret_cast<const wchar_t*>(name.c_str()), nullptr,
217 &sp_object_type_signature));
218 RETURN_IF_FAIL(sp_data_model_manager->RegisterModelForTypeSignature(
219 sp_object_type_signature.Get(), sp_object_data_model_.Get()));
220 registered_types_.push_back(
221 {sp_object_type_signature.Get(), sp_object_data_model_.Get()});
222 }
223
224 // Create an instance of the DataModel parent for custom iterable fields.
225 auto indexed_field_model{WRL::Make<IndexedFieldParent>()};
226 RETURN_IF_FAIL(sp_data_model_manager->CreateDataModelObject(
227 indexed_field_model.Get(), &sp_indexed_field_model_));
228 RETURN_IF_FAIL(sp_indexed_field_model_->SetConcept(
229 __uuidof(IIndexableConcept),
230 static_cast<IIndexableConcept*>(indexed_field_model.Get()), nullptr));
231 RETURN_IF_FAIL(sp_indexed_field_model_->SetConcept(
232 __uuidof(IIterableConcept),
233 static_cast<IIterableConcept*>(indexed_field_model.Get()), nullptr));
234
235 // Create an instance of the DataModel parent class for v8::Local<*> types.
236 auto local_data_model{WRL::Make<V8LocalDataModel>()};
237 RETURN_IF_FAIL(sp_data_model_manager->CreateDataModelObject(
238 local_data_model.Get(), &sp_local_data_model_));
239
240 // Register that parent model for all known types that act like v8::Local.
241 std::vector<const wchar_t*> handle_class_names = {
242 L"v8::Local<*>", L"v8::MaybeLocal<*>", L"v8::internal::Handle<*>",
243 L"v8::internal::MaybeHandle<*>"};
244 for (const wchar_t* name : handle_class_names) {
245 WRL::ComPtr<IDebugHostTypeSignature> signature;
246 RETURN_IF_FAIL(
247 sp_debug_host_symbols->CreateTypeSignature(name, nullptr, &signature));
248 RETURN_IF_FAIL(sp_data_model_manager->RegisterModelForTypeSignature(
249 signature.Get(), sp_local_data_model_.Get()));
250 registered_types_.push_back({signature.Get(), sp_local_data_model_.Get()});
251 }
252
253 // Add the 'Value' property to the parent model.
254 auto local_value_property{WRL::Make<V8LocalValueProperty>()};
255 WRL::ComPtr<IModelObject> sp_local_value_property_model;
256 RETURN_IF_FAIL(CreateProperty(sp_data_model_manager.Get(),
257 local_value_property.Get(),
258 &sp_local_value_property_model));
259 RETURN_IF_FAIL(sp_local_data_model_->SetKey(
260 L"Value", sp_local_value_property_model.Get(), nullptr));
261
262 // Register all function aliases.
263 std::vector<std::pair<const wchar_t*, WRL::ComPtr<IModelMethod>>> functions =
264 {{pcur_isolate, WRL::Make<CurrIsolateAlias>()},
265 {pjs_stack, WRL::Make<JSStackAlias>()},
266 {pv8_object, WRL::Make<InspectV8ObjectMethod>()}};
267 for (const auto& function : functions) {
268 WRL::ComPtr<IModelObject> method;
269 RETURN_IF_FAIL(CreateMethod(sp_data_model_manager.Get(),
270 function.second.Get(), &method));
271 RETURN_IF_FAIL(sp_debug_host_extensibility->CreateFunctionAlias(
272 function.first, method.Get()));
273 }
274
275 // Register a handler for supplying stack frame locals. It has to override the
276 // getter functions for "LocalVariables" and "Parameters".
277 WRL::ComPtr<IModelObject> stack_frame;
278 RETURN_IF_FAIL(sp_data_model_manager->AcquireNamedModel(
279 L"Debugger.Models.StackFrame", &stack_frame));
280 RETURN_IF_FAIL(OverrideLocalsGetter(stack_frame.Get(), L"LocalVariables",
281 /*is_parameters=*/false));
282 RETURN_IF_FAIL(OverrideLocalsGetter(stack_frame.Get(), L"Parameters",
283 /*is_parameters=*/true));
284
285 // Add node_id property for v8::internal::compiler::Node.
286 RETURN_IF_FAIL(
287 RegisterAndAddPropertyForClass<V8InternalCompilerNodeIdProperty>(
288 L"v8::internal::compiler::Node", L"node_id",
289 sp_compiler_node_data_model_));
290
291 // Add bitset_name property for v8::internal::compiler::Type.
292 RETURN_IF_FAIL(
293 RegisterAndAddPropertyForClass<V8InternalCompilerBitsetNameProperty>(
294 L"v8::internal::compiler::Type", L"bitset_name",
295 sp_compiler_type_data_model_));
296
297 return S_OK;
298 }
299
300 template <class PropertyClass>
RegisterAndAddPropertyForClass(const wchar_t * class_name,const wchar_t * property_name,WRL::ComPtr<IModelObject> sp_data_model)301 HRESULT Extension::RegisterAndAddPropertyForClass(
302 const wchar_t* class_name, const wchar_t* property_name,
303 WRL::ComPtr<IModelObject> sp_data_model) {
304 // Create an instance of the DataModel parent class.
305 auto instance_data_model{WRL::Make<V8LocalDataModel>()};
306 RETURN_IF_FAIL(sp_data_model_manager->CreateDataModelObject(
307 instance_data_model.Get(), &sp_data_model));
308
309 // Register that parent model.
310 WRL::ComPtr<IDebugHostTypeSignature> class_signature;
311 RETURN_IF_FAIL(sp_debug_host_symbols->CreateTypeSignature(class_name, nullptr,
312 &class_signature));
313 RETURN_IF_FAIL(sp_data_model_manager->RegisterModelForTypeSignature(
314 class_signature.Get(), sp_data_model.Get()));
315 registered_types_.push_back({class_signature.Get(), sp_data_model.Get()});
316
317 // Add the property to the parent model.
318 auto property{WRL::Make<PropertyClass>()};
319 WRL::ComPtr<IModelObject> sp_property_model;
320 RETURN_IF_FAIL(CreateProperty(sp_data_model_manager.Get(), property.Get(),
321 &sp_property_model));
322 RETURN_IF_FAIL(
323 sp_data_model->SetKey(property_name, sp_property_model.Get(), nullptr));
324
325 return S_OK;
326 }
327
OverrideLocalsGetter(IModelObject * stack_frame,const wchar_t * key_name,bool is_parameters)328 HRESULT Extension::OverrideLocalsGetter(IModelObject* stack_frame,
329 const wchar_t* key_name,
330 bool is_parameters) {
331 WRL::ComPtr<IModelObject> original_boxed_getter;
332 WRL::ComPtr<IKeyStore> original_getter_metadata;
333 RETURN_IF_FAIL(stack_frame->GetKey(key_name, &original_boxed_getter,
334 &original_getter_metadata));
335 WRL::ComPtr<IModelPropertyAccessor> original_getter;
336 RETURN_IF_FAIL(UnboxProperty(original_boxed_getter.Get(), &original_getter));
337 auto new_getter{WRL::Make<V8LocalVariables>(original_getter, is_parameters)};
338 WRL::ComPtr<IModelObject> new_boxed_getter;
339 RETURN_IF_FAIL(CreateProperty(sp_data_model_manager.Get(), new_getter.Get(),
340 &new_boxed_getter));
341 RETURN_IF_FAIL(stack_frame->SetKey(key_name, new_boxed_getter.Get(),
342 original_getter_metadata.Get()));
343 overridden_properties_.push_back(
344 {stack_frame, reinterpret_cast<const char16_t*>(key_name),
345 original_boxed_getter.Get(), original_getter_metadata.Get()});
346 return S_OK;
347 }
348
349 Extension::PropertyOverride::PropertyOverride() = default;
PropertyOverride(IModelObject * parent,std::u16string key_name,IModelObject * original_value,IKeyStore * original_metadata)350 Extension::PropertyOverride::PropertyOverride(IModelObject* parent,
351 std::u16string key_name,
352 IModelObject* original_value,
353 IKeyStore* original_metadata)
354 : parent(parent),
355 key_name(std::move(key_name)),
356 original_value(original_value),
357 original_metadata(original_metadata) {}
358 Extension::PropertyOverride::~PropertyOverride() = default;
359 Extension::PropertyOverride::PropertyOverride(const PropertyOverride&) =
360 default;
361 Extension::PropertyOverride& Extension::PropertyOverride::operator=(
362 const PropertyOverride&) = default;
363
364 Extension::RegistrationType::RegistrationType() = default;
RegistrationType(IDebugHostTypeSignature * sp_signature,IModelObject * sp_data_model)365 Extension::RegistrationType::RegistrationType(
366 IDebugHostTypeSignature* sp_signature, IModelObject* sp_data_model)
367 : sp_signature(sp_signature), sp_data_model(sp_data_model) {}
368 Extension::RegistrationType::~RegistrationType() = default;
369 Extension::RegistrationType::RegistrationType(const RegistrationType&) =
370 default;
371 Extension::RegistrationType& Extension::RegistrationType::operator=(
372 const RegistrationType&) = default;
373
~Extension()374 Extension::~Extension() {
375 sp_debug_host_extensibility->DestroyFunctionAlias(pcur_isolate);
376 sp_debug_host_extensibility->DestroyFunctionAlias(pjs_stack);
377 sp_debug_host_extensibility->DestroyFunctionAlias(pv8_object);
378
379 for (const auto& registered : registered_types_) {
380 sp_data_model_manager->UnregisterModelForTypeSignature(
381 registered.sp_data_model.Get(), registered.sp_signature.Get());
382 }
383
384 for (const auto& override : overridden_properties_) {
385 override.parent->SetKey(
386 reinterpret_cast<const wchar_t*>(override.key_name.c_str()),
387 override.original_value.Get(), override.original_metadata.Get());
388 }
389 }
390