summaryrefslogtreecommitdiff
path: root/deps/v8/tools/v8windbg/test/v8windbg-test.cc
blob: ecbe04a329fa96edaf532d3bdd5b33a7969e270c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
// 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 <cstdio>
#include <exception>
#include <vector>

#include "src/base/logging.h"
#include "tools/v8windbg/test/debug-callbacks.h"

// See the docs at
// https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/using-the-debugger-engine-api

namespace v8 {
namespace internal {
namespace v8windbg_test {

namespace {

// Loads a named extension library upon construction and unloads it upon
// destruction.
class V8_NODISCARD LoadExtensionScope {
 public:
  LoadExtensionScope(WRL::ComPtr<IDebugControl4> p_debug_control,
                     std::wstring extension_path)
      : p_debug_control_(p_debug_control),
        extension_path_(std::move(extension_path)) {
    p_debug_control->AddExtensionWide(extension_path_.c_str(), 0, &ext_handle_);
    // HACK: Below fails, but is required for the extension to actually
    // initialize. Just the AddExtension call doesn't actually load and
    // initialize it.
    p_debug_control->CallExtension(ext_handle_, "Foo", "Bar");
  }
  ~LoadExtensionScope() {
    // Let the extension uninitialize so it can deallocate memory, meaning any
    // reported memory leaks should be real bugs.
    p_debug_control_->RemoveExtension(ext_handle_);
  }

 private:
  LoadExtensionScope(const LoadExtensionScope&) = delete;
  LoadExtensionScope& operator=(const LoadExtensionScope&) = delete;
  WRL::ComPtr<IDebugControl4> p_debug_control_;
  ULONG64 ext_handle_;
  // This string is part of the heap snapshot when the extension loads, so keep
  // it alive until after the extension unloads and checks for any heap changes.
  std::wstring extension_path_;
};

// Initializes COM upon construction and uninitializes it upon destruction.
class V8_NODISCARD ComScope {
 public:
  ComScope() { hr_ = CoInitializeEx(nullptr, COINIT_MULTITHREADED); }
  ~ComScope() {
    // "To close the COM library gracefully on a thread, each successful call to
    // CoInitialize or CoInitializeEx, including any call that returns S_FALSE,
    // must be balanced by a corresponding call to CoUninitialize."
    // https://docs.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-coinitializeex
    if (SUCCEEDED(hr_)) {
      CoUninitialize();
    }
  }
  HRESULT hr() { return hr_; }

 private:
  HRESULT hr_;
};

// Sets a breakpoint. Returns S_OK if the function name resolved successfully
// and the breakpoint is in a non-deferred state.
HRESULT SetBreakpoint(WRL::ComPtr<IDebugControl4> p_debug_control,
                      const char* function_name) {
  WRL::ComPtr<IDebugBreakpoint> bp;
  HRESULT hr =
      p_debug_control->AddBreakpoint(DEBUG_BREAKPOINT_CODE, DEBUG_ANY_ID, &bp);
  if (FAILED(hr)) return hr;
  hr = bp->SetOffsetExpression(function_name);
  if (FAILED(hr)) return hr;
  hr = bp->AddFlags(DEBUG_BREAKPOINT_ENABLED);
  if (FAILED(hr)) return hr;

  // Check whether the symbol could be found.
  uint64_t offset;
  hr = bp->GetOffset(&offset);
  return hr;
}

// Sets a breakpoint. Depending on the build configuration, the function might
// be in the v8 or d8 module, so this function tries to set both.
HRESULT SetBreakpointInV8OrD8(WRL::ComPtr<IDebugControl4> p_debug_control,
                              const std::string& function_name) {
  // Component builds call the V8 module "v8". Try this first, because there is
  // also a module named "d8" or "d8_exe" where we should avoid attempting to
  // set a breakpoint.
  HRESULT hr = SetBreakpoint(p_debug_control, ("v8!" + function_name).c_str());
  if (SUCCEEDED(hr)) return hr;

  // x64 release builds call it "d8".
  hr = SetBreakpoint(p_debug_control, ("d8!" + function_name).c_str());
  if (SUCCEEDED(hr)) return hr;

  // x86 release builds call it "d8_exe".
  return SetBreakpoint(p_debug_control, ("d8_exe!" + function_name).c_str());
}

void RunAndCheckOutput(const char* friendly_name, const char* command,
                       std::vector<const char*> expected_substrings,
                       MyOutput* output, IDebugControl4* p_debug_control) {
  output->ClearLog();
  CHECK(SUCCEEDED(p_debug_control->Execute(DEBUG_OUTCTL_ALL_CLIENTS, command,
                                           DEBUG_EXECUTE_ECHO)));
  for (const char* expected : expected_substrings) {
    CHECK(output->GetLog().find(expected) != std::string::npos);
  }
}

}  // namespace

void RunTests() {
  // Initialize COM... Though it doesn't seem to matter if you don't!
  ComScope com_scope;
  CHECK(SUCCEEDED(com_scope.hr()));

  // Get the file path of the module containing this test function. It should be
  // in the output directory alongside the data dependencies required by this
  // test (d8.exe, v8windbg.dll, and v8windbg-test-script.js).
  HMODULE module = nullptr;
  bool success =
      GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
                            GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
                        reinterpret_cast<LPCWSTR>(&RunTests), &module);
  CHECK(success);
  wchar_t this_module_path[MAX_PATH];
  DWORD path_size = GetModuleFileName(module, this_module_path, MAX_PATH);
  CHECK(path_size != 0);
  HRESULT hr = PathCchRemoveFileSpec(this_module_path, MAX_PATH);
  CHECK(SUCCEEDED(hr));

  // Get the Debug client
  WRL::ComPtr<IDebugClient5> p_client;
  hr = DebugCreate(__uuidof(IDebugClient5), &p_client);
  CHECK(SUCCEEDED(hr));

  WRL::ComPtr<IDebugSymbols3> p_symbols;
  hr = p_client->QueryInterface(__uuidof(IDebugSymbols3), &p_symbols);
  CHECK(SUCCEEDED(hr));

  // Symbol loading fails if the pdb is in the same folder as the exe, but it's
  // not on the path.
  hr = p_symbols->SetSymbolPathWide(this_module_path);
  CHECK(SUCCEEDED(hr));

  // Set the event callbacks
  MyCallback callback;
  hr = p_client->SetEventCallbacks(&callback);
  CHECK(SUCCEEDED(hr));

  // Launch the process with the debugger attached
  std::wstring command_line =
      std::wstring(L"\"") + this_module_path + L"\\d8.exe\" \"" +
      this_module_path + L"\\obj\\tools\\v8windbg\\v8windbg-test-script.js\"";
  DEBUG_CREATE_PROCESS_OPTIONS proc_options;
  proc_options.CreateFlags = DEBUG_PROCESS;
  proc_options.EngCreateFlags = 0;
  proc_options.VerifierFlags = 0;
  proc_options.Reserved = 0;
  hr = p_client->CreateProcessWide(
      0, const_cast<wchar_t*>(command_line.c_str()), DEBUG_PROCESS);
  CHECK(SUCCEEDED(hr));

  // Wait for the attach event
  WRL::ComPtr<IDebugControl4> p_debug_control;
  hr = p_client->QueryInterface(__uuidof(IDebugControl4), &p_debug_control);
  CHECK(SUCCEEDED(hr));
  hr = p_debug_control->WaitForEvent(0, INFINITE);
  CHECK(SUCCEEDED(hr));

  // Break again after non-delay-load modules are loaded.
  hr = p_debug_control->AddEngineOptions(DEBUG_ENGOPT_INITIAL_BREAK);
  CHECK(SUCCEEDED(hr));
  hr = p_debug_control->WaitForEvent(0, INFINITE);
  CHECK(SUCCEEDED(hr));

  // Set a breakpoint in a C++ function called by the script.
  hr = SetBreakpointInV8OrD8(p_debug_control, "v8::internal::JsonStringify");
  CHECK(SUCCEEDED(hr));

  hr = p_debug_control->SetExecutionStatus(DEBUG_STATUS_GO);
  CHECK(SUCCEEDED(hr));

  // Wait for the breakpoint.
  hr = p_debug_control->WaitForEvent(0, INFINITE);
  CHECK(SUCCEEDED(hr));

  ULONG type, proc_id, thread_id, desc_used;
  byte desc[1024];
  hr = p_debug_control->GetLastEventInformation(
      &type, &proc_id, &thread_id, nullptr, 0, nullptr,
      reinterpret_cast<PSTR>(desc), 1024, &desc_used);
  CHECK(SUCCEEDED(hr));

  LoadExtensionScope extension_loaded(
      p_debug_control, this_module_path + std::wstring(L"\\v8windbg.dll"));

  // Set the output callbacks after the extension is loaded, so it gets
  // destroyed before the extension unloads. This avoids reporting incorrectly
  // reporting that the output buffer was leaked during extension teardown.
  MyOutput output(p_client);

  // Set stepping mode.
  hr = p_debug_control->SetCodeLevel(DEBUG_LEVEL_SOURCE);
  CHECK(SUCCEEDED(hr));

  // Do some actual testing
  RunAndCheckOutput("bitfields",
                    "p;dx replacer.Value.shared_function_info.flags",
                    {"kNamedExpression"}, &output, p_debug_control.Get());

  RunAndCheckOutput("in-object properties",
                    "dx object.Value.@\"in-object properties\"[1]",
                    {"NullValue", "Oddball"}, &output, p_debug_control.Get());

  RunAndCheckOutput(
      "arrays of structs",
      "dx object.Value.map.instance_descriptors.descriptors[1].key",
      {"\"secondProp\"", "SeqOneByteString"}, &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());

  // TODO(v8:13484): try re-enabling the following jsstack() tests once we've
  // updated to a newer VS toolchain. Stack walking is broken in dbghelp.dll
  // version 10.0.20348.1, but works in the (much newer) version
  // 10.0.25155.1000. With incorrect frame pointers, the code in
  // V8LocalVariables::GetValue of course produces incorrect results.

  // 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());

  // Test for @$curisolate(). This should have the same output with
  // `dx v8::internal::g_current_isolate_`.
  output.ClearLog();
  CHECK(SUCCEEDED(p_debug_control->Execute(
      DEBUG_OUTCTL_ALL_CLIENTS, "dx v8::internal::g_current_isolate_",
      DEBUG_EXECUTE_ECHO)));
  size_t addr_pos = output.GetLog().find("0x");
  CHECK(addr_pos != std::string::npos);
  std::string expected_output = output.GetLog().substr(addr_pos);

  output.ClearLog();
  CHECK(SUCCEEDED(p_debug_control->Execute(
      DEBUG_OUTCTL_ALL_CLIENTS, "dx @$curisolate()", DEBUG_EXECUTE_ECHO)));
  CHECK_EQ(output.GetLog().substr(output.GetLog().find("0x")), expected_output);

  // Detach before exiting
  hr = p_client->DetachProcesses();
  CHECK(SUCCEEDED(hr));
}

}  // namespace v8windbg_test
}  // namespace internal
}  // namespace v8