• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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