blob: 59414f341da97b6f895829fd88e2795d92b01d54 [file] [log] [blame]
// 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 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 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());
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());
// Detach before exiting
hr = p_client->DetachProcesses();
CHECK(SUCCEEDED(hr));
}
} // namespace v8windbg_test
} // namespace internal
} // namespace v8