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 <cstdio>
6 #include <exception>
7 #include <vector>
8
9 #include "src/base/logging.h"
10 #include "tools/v8windbg/test/debug-callbacks.h"
11
12 // See the docs at
13 // https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/using-the-debugger-engine-api
14
15 namespace v8 {
16 namespace internal {
17 namespace v8windbg_test {
18
19 namespace {
20
21 // Loads a named extension library upon construction and unloads it upon
22 // destruction.
23 class V8_NODISCARD LoadExtensionScope {
24 public:
LoadExtensionScope(WRL::ComPtr<IDebugControl4> p_debug_control,std::wstring extension_path)25 LoadExtensionScope(WRL::ComPtr<IDebugControl4> p_debug_control,
26 std::wstring extension_path)
27 : p_debug_control_(p_debug_control),
28 extension_path_(std::move(extension_path)) {
29 p_debug_control->AddExtensionWide(extension_path_.c_str(), 0, &ext_handle_);
30 // HACK: Below fails, but is required for the extension to actually
31 // initialize. Just the AddExtension call doesn't actually load and
32 // initialize it.
33 p_debug_control->CallExtension(ext_handle_, "Foo", "Bar");
34 }
~LoadExtensionScope()35 ~LoadExtensionScope() {
36 // Let the extension uninitialize so it can deallocate memory, meaning any
37 // reported memory leaks should be real bugs.
38 p_debug_control_->RemoveExtension(ext_handle_);
39 }
40
41 private:
42 LoadExtensionScope(const LoadExtensionScope&) = delete;
43 LoadExtensionScope& operator=(const LoadExtensionScope&) = delete;
44 WRL::ComPtr<IDebugControl4> p_debug_control_;
45 ULONG64 ext_handle_;
46 // This string is part of the heap snapshot when the extension loads, so keep
47 // it alive until after the extension unloads and checks for any heap changes.
48 std::wstring extension_path_;
49 };
50
51 // Initializes COM upon construction and uninitializes it upon destruction.
52 class V8_NODISCARD ComScope {
53 public:
ComScope()54 ComScope() { hr_ = CoInitializeEx(nullptr, COINIT_MULTITHREADED); }
~ComScope()55 ~ComScope() {
56 // "To close the COM library gracefully on a thread, each successful call to
57 // CoInitialize or CoInitializeEx, including any call that returns S_FALSE,
58 // must be balanced by a corresponding call to CoUninitialize."
59 // https://docs.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex
60 if (SUCCEEDED(hr_)) {
61 CoUninitialize();
62 }
63 }
hr()64 HRESULT hr() { return hr_; }
65
66 private:
67 HRESULT hr_;
68 };
69
70 // Sets a breakpoint. Returns S_OK if the function name resolved successfully
71 // and the breakpoint is in a non-deferred state.
SetBreakpoint(WRL::ComPtr<IDebugControl4> p_debug_control,const char * function_name)72 HRESULT SetBreakpoint(WRL::ComPtr<IDebugControl4> p_debug_control,
73 const char* function_name) {
74 WRL::ComPtr<IDebugBreakpoint> bp;
75 HRESULT hr =
76 p_debug_control->AddBreakpoint(DEBUG_BREAKPOINT_CODE, DEBUG_ANY_ID, &bp);
77 if (FAILED(hr)) return hr;
78 hr = bp->SetOffsetExpression(function_name);
79 if (FAILED(hr)) return hr;
80 hr = bp->AddFlags(DEBUG_BREAKPOINT_ENABLED);
81 if (FAILED(hr)) return hr;
82
83 // Check whether the symbol could be found.
84 uint64_t offset;
85 hr = bp->GetOffset(&offset);
86 return hr;
87 }
88
89 // Sets a breakpoint. Depending on the build configuration, the function might
90 // be in the v8 or d8 module, so this function tries to set both.
SetBreakpointInV8OrD8(WRL::ComPtr<IDebugControl4> p_debug_control,const std::string & function_name)91 HRESULT SetBreakpointInV8OrD8(WRL::ComPtr<IDebugControl4> p_debug_control,
92 const std::string& function_name) {
93 // Component builds call the V8 module "v8". Try this first, because there is
94 // also a module named "d8" or "d8_exe" where we should avoid attempting to
95 // set a breakpoint.
96 HRESULT hr = SetBreakpoint(p_debug_control, ("v8!" + function_name).c_str());
97 if (SUCCEEDED(hr)) return hr;
98
99 // x64 release builds call it "d8".
100 hr = SetBreakpoint(p_debug_control, ("d8!" + function_name).c_str());
101 if (SUCCEEDED(hr)) return hr;
102
103 // x86 release builds call it "d8_exe".
104 return SetBreakpoint(p_debug_control, ("d8_exe!" + function_name).c_str());
105 }
106
RunAndCheckOutput(const char * friendly_name,const char * command,std::vector<const char * > expected_substrings,MyOutput * output,IDebugControl4 * p_debug_control)107 void RunAndCheckOutput(const char* friendly_name, const char* command,
108 std::vector<const char*> expected_substrings,
109 MyOutput* output, IDebugControl4* p_debug_control) {
110 output->ClearLog();
111 CHECK(SUCCEEDED(p_debug_control->Execute(DEBUG_OUTCTL_ALL_CLIENTS, command,
112 DEBUG_EXECUTE_ECHO)));
113 for (const char* expected : expected_substrings) {
114 CHECK(output->GetLog().find(expected) != std::string::npos);
115 }
116 }
117
118 } // namespace
119
RunTests()120 void RunTests() {
121 // Initialize COM... Though it doesn't seem to matter if you don't!
122 ComScope com_scope;
123 CHECK(SUCCEEDED(com_scope.hr()));
124
125 // Get the file path of the module containing this test function. It should be
126 // in the output directory alongside the data dependencies required by this
127 // test (d8.exe, v8windbg.dll, and v8windbg-test-script.js).
128 HMODULE module = nullptr;
129 bool success =
130 GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
131 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
132 reinterpret_cast<LPCWSTR>(&RunTests), &module);
133 CHECK(success);
134 wchar_t this_module_path[MAX_PATH];
135 DWORD path_size = GetModuleFileName(module, this_module_path, MAX_PATH);
136 CHECK(path_size != 0);
137 HRESULT hr = PathCchRemoveFileSpec(this_module_path, MAX_PATH);
138 CHECK(SUCCEEDED(hr));
139
140 // Get the Debug client
141 WRL::ComPtr<IDebugClient5> p_client;
142 hr = DebugCreate(__uuidof(IDebugClient5), &p_client);
143 CHECK(SUCCEEDED(hr));
144
145 WRL::ComPtr<IDebugSymbols3> p_symbols;
146 hr = p_client->QueryInterface(__uuidof(IDebugSymbols3), &p_symbols);
147 CHECK(SUCCEEDED(hr));
148
149 // Symbol loading fails if the pdb is in the same folder as the exe, but it's
150 // not on the path.
151 hr = p_symbols->SetSymbolPathWide(this_module_path);
152 CHECK(SUCCEEDED(hr));
153
154 // Set the event callbacks
155 MyCallback callback;
156 hr = p_client->SetEventCallbacks(&callback);
157 CHECK(SUCCEEDED(hr));
158
159 // Launch the process with the debugger attached
160 std::wstring command_line =
161 std::wstring(L"\"") + this_module_path + L"\\d8.exe\" \"" +
162 this_module_path + L"\\obj\\tools\\v8windbg\\v8windbg-test-script.js\"";
163 DEBUG_CREATE_PROCESS_OPTIONS proc_options;
164 proc_options.CreateFlags = DEBUG_PROCESS;
165 proc_options.EngCreateFlags = 0;
166 proc_options.VerifierFlags = 0;
167 proc_options.Reserved = 0;
168 hr = p_client->CreateProcessWide(
169 0, const_cast<wchar_t*>(command_line.c_str()), DEBUG_PROCESS);
170 CHECK(SUCCEEDED(hr));
171
172 // Wait for the attach event
173 WRL::ComPtr<IDebugControl4> p_debug_control;
174 hr = p_client->QueryInterface(__uuidof(IDebugControl4), &p_debug_control);
175 CHECK(SUCCEEDED(hr));
176 hr = p_debug_control->WaitForEvent(0, INFINITE);
177 CHECK(SUCCEEDED(hr));
178
179 // Break again after non-delay-load modules are loaded.
180 hr = p_debug_control->AddEngineOptions(DEBUG_ENGOPT_INITIAL_BREAK);
181 CHECK(SUCCEEDED(hr));
182 hr = p_debug_control->WaitForEvent(0, INFINITE);
183 CHECK(SUCCEEDED(hr));
184
185 // Set a breakpoint in a C++ function called by the script.
186 hr = SetBreakpointInV8OrD8(p_debug_control, "v8::internal::JsonStringify");
187 CHECK(SUCCEEDED(hr));
188
189 hr = p_debug_control->SetExecutionStatus(DEBUG_STATUS_GO);
190 CHECK(SUCCEEDED(hr));
191
192 // Wait for the breakpoint.
193 hr = p_debug_control->WaitForEvent(0, INFINITE);
194 CHECK(SUCCEEDED(hr));
195
196 ULONG type, proc_id, thread_id, desc_used;
197 byte desc[1024];
198 hr = p_debug_control->GetLastEventInformation(
199 &type, &proc_id, &thread_id, nullptr, 0, nullptr,
200 reinterpret_cast<PSTR>(desc), 1024, &desc_used);
201 CHECK(SUCCEEDED(hr));
202
203 LoadExtensionScope extension_loaded(
204 p_debug_control, this_module_path + std::wstring(L"\\v8windbg.dll"));
205
206 // Set the output callbacks after the extension is loaded, so it gets
207 // destroyed before the extension unloads. This avoids reporting incorrectly
208 // reporting that the output buffer was leaked during extension teardown.
209 MyOutput output(p_client);
210
211 // Set stepping mode.
212 hr = p_debug_control->SetCodeLevel(DEBUG_LEVEL_SOURCE);
213 CHECK(SUCCEEDED(hr));
214
215 // Do some actual testing
216 RunAndCheckOutput("bitfields",
217 "p;dx replacer.Value.shared_function_info.flags",
218 {"kNamedExpression"}, &output, p_debug_control.Get());
219
220 RunAndCheckOutput("in-object properties",
221 "dx object.Value.@\"in-object properties\"[1]",
222 {"NullValue", "Oddball"}, &output, p_debug_control.Get());
223
224 RunAndCheckOutput(
225 "arrays of structs",
226 "dx object.Value.map.instance_descriptors.descriptors[1].key",
227 {"\"secondProp\"", "SeqOneByteString"}, &output, p_debug_control.Get());
228
229 // TODO(v8:11527): enable this when symbol information for the in-Isolate
230 // builtins is available.
231 // RunAndCheckOutput(
232 // "local variables",
233 // "dx -r1 @$curthread.Stack.Frames.Where(f => "
234 // "f.ToDisplayString().Contains(\"InterpreterEntryTrampoline\")).Skip(1)."
235 // "First().LocalVariables.@\"memory interpreted as Objects\"",
236 // {"\"hello\""}, &output, p_debug_control.Get());
237
238 RunAndCheckOutput("js stack", "dx @$jsstack()[0].function_name",
239 {"\"a\"", "SeqOneByteString"}, &output,
240 p_debug_control.Get());
241
242 RunAndCheckOutput("js stack", "dx @$jsstack()[1].function_name",
243 {"\"b\"", "SeqOneByteString"}, &output,
244 p_debug_control.Get());
245
246 RunAndCheckOutput("js stack", "dx @$jsstack()[2].function_name",
247 {"empty_string \"\"", "SeqOneByteString"}, &output,
248 p_debug_control.Get());
249
250 // Detach before exiting
251 hr = p_client->DetachProcesses();
252 CHECK(SUCCEEDED(hr));
253 }
254
255 } // namespace v8windbg_test
256 } // namespace internal
257 } // namespace v8
258