crashpad/handler/win/crashy_test_program.cc
Nico Weber d7aeda9fca Prepare for building with clang
* Switch to Chromium's clang package on Windows. Fuchsia's clang
  package uses symlinks, which don't Just Work. See comment 3
  on the linked bug for details.

* Also tweak the installed path of the clang package to use
  win-amd64 instead of windows-amd64 to match gn's host_os
  variable, which is needed by the mini_chromium change in the
  roll mentioned below.

* Add `__declspec(noinline)` to sources of some end-to-end test
  binaries where the test expects a certain stack layout that's
  perturbed by inlining

* self_destroying_test_program.cc failed with Access Violation
  instead of with Breakpoint. I'm not 100% sure what's happening
  there as I couldn't reproduce it on my machine. The test uses
  VirtualFree(..., MEM_DECOMMIT) to make parts of the stack unreadable
  to make sure crashpad can handle that. My theory is that clang
  optimized out the call to `_alloca()`, which results in not enough
  of the stack being around for VirtualFree() to be able to decommit.
  https://geidav.wordpress.com/tag/reserved-memory/#:~:text=How%20does%20Windows%20manage%20stack%20memory%3F
  and https://godbolt.org/z/YdbYdKeMh suggest that this theory is
  not completely off -- but the
  `[[maybe_unused]] volatile void* do_not_optimize_away` bit feels
  homeopathic. And yet, with it this seems to pass CQ (see try results
  at patch sets 12 and 20) and without it it doesn't (see patch set 19).
  Maybe this was just luck and the test still feels flakily!
  (The linker's `/STACK` defaults to 1MB stack reserved and 4K
  committed -- 4K kind of seems like it should be enough for that
  VirtualFree call to succeed if that really is the problem.)
  So this part is a bit experimental.

Anyways, nothing uses clang yet, so no behavior change at this point
(other than `gclient sync` downloading less stuff and not failing
by default on Windows).

Includes a one-rev mini_chromium roll:

12ef786772..08d490553b

$ git log 12ef78677..08d490553 --date=short --no-merges --format='%ad %ae %s'
2025-02-13 thakis Prepare mini_chromium for using clang on Windows

Bug: 384682775
Change-Id: I33b54acf18cb6eadfd2167c76c0b4a5824efa64d
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/6242577
Reviewed-by: Mark Mentovai <mark@chromium.org>
Commit-Queue: Nico Weber <thakis@chromium.org>
2025-02-24 11:37:06 -08:00

267 lines
8.6 KiB
C++

// Copyright 2015 The Crashpad Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <intrin.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <windows.h>
#include <winternl.h>
#include <iterator>
#include <map>
#include <string>
#include <type_traits>
#include <vector>
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "build/build_config.h"
#include "client/crashpad_client.h"
#include "client/crashpad_info.h"
#include "util/win/critical_section_with_debug_info.h"
#include "util/win/get_function.h"
// ntstatus.h conflicts with windows.h so define this locally.
#ifndef STATUS_NO_SUCH_FILE
#define STATUS_NO_SUCH_FILE static_cast<NTSTATUS>(0xC000000F)
#endif
namespace crashpad {
uint32_t* g_extra_memory_pointer;
uint32_t* g_extra_memory_not_saved;
namespace {
CRITICAL_SECTION g_test_critical_section;
unsigned char g_test_memory[] = {
99, 98, 97, 96, 95, 94, 93, 92, 91, 90,
89, 88, 87, 86, 85, 84, 83, 82, 81, 80,
};
ULONG RtlNtStatusToDosError(NTSTATUS status) {
static const auto rtl_nt_status_to_dos_error =
GET_FUNCTION_REQUIRED(L"ntdll.dll", ::RtlNtStatusToDosError);
return rtl_nt_status_to_dos_error(status);
}
void AllocateMemoryOfVariousProtections() {
SYSTEM_INFO system_info;
GetSystemInfo(&system_info);
const size_t kPageSize = system_info.dwPageSize;
static constexpr uint32_t kPageTypes[] = {
PAGE_NOACCESS,
PAGE_READONLY,
PAGE_READWRITE,
PAGE_EXECUTE,
PAGE_EXECUTE_READ,
PAGE_EXECUTE_READWRITE,
// PAGE_NOACCESS is invalid with PAGE_GUARD.
PAGE_READONLY | PAGE_GUARD,
PAGE_READWRITE | PAGE_GUARD,
PAGE_EXECUTE | PAGE_GUARD,
PAGE_EXECUTE_READ | PAGE_GUARD,
PAGE_EXECUTE_READWRITE | PAGE_GUARD,
};
// All of these allocations are leaked, we want to view them in windbg via
// !vprot.
void* reserve = VirtualAlloc(
nullptr, std::size(kPageTypes) * kPageSize, MEM_RESERVE, PAGE_READWRITE);
PCHECK(reserve) << "VirtualAlloc MEM_RESERVE";
uintptr_t reserve_as_int = reinterpret_cast<uintptr_t>(reserve);
for (size_t i = 0; i < std::size(kPageTypes); ++i) {
void* result =
VirtualAlloc(reinterpret_cast<void*>(reserve_as_int + (kPageSize * i)),
kPageSize,
MEM_COMMIT,
kPageTypes[i]);
PCHECK(result) << "VirtualAlloc MEM_COMMIT " << i;
}
}
DWORD WINAPI NullThreadProc(void* param) {
return 0;
}
// Creates a suspended background thread, and sets EDI/RDI/X17 to point at
// g_test_memory so we can confirm it's available in the minidump.
bool CreateThreadWithRegisterPointingToTestMemory() {
HANDLE thread = CreateThread(
nullptr, 0, &NullThreadProc, nullptr, CREATE_SUSPENDED, nullptr);
if (!thread) {
PLOG(ERROR) << "CreateThread";
return false;
}
CONTEXT context = {0};
context.ContextFlags = CONTEXT_INTEGER;
if (!GetThreadContext(thread, &context)) {
PLOG(ERROR) << "GetThreadContext";
return false;
}
#if defined(ARCH_CPU_X86_64)
context.Rdi = reinterpret_cast<DWORD64>(g_test_memory);
#elif defined(ARCH_CPU_X86)
context.Edi = reinterpret_cast<DWORD>(g_test_memory);
#elif defined(ARCH_CPU_ARM64)
context.X17 = reinterpret_cast<DWORD64>(g_test_memory);
#endif
if (!SetThreadContext(thread, &context)) {
PLOG(ERROR) << "SetThreadContext";
return false;
}
return true;
}
__declspec(noinline) void SomeCrashyFunction() {
// SetLastError and NTSTATUS so that we have something to view in !gle in
// windbg. RtlNtStatusToDosError() stores STATUS_NO_SUCH_FILE into the
// LastStatusError of the TEB as a side-effect, and we'll be setting
// ERROR_FILE_NOT_FOUND for GetLastError().
SetLastError(RtlNtStatusToDosError(STATUS_NO_SUCH_FILE));
volatile int* foo = reinterpret_cast<volatile int*>(7);
*foo = 42;
}
void AllocateExtraMemoryToBeSaved(
crashpad::SimpleAddressRangeBag* extra_ranges) {
constexpr size_t kNumVals = 2000;
auto extra_memory = new uint32_t[kNumVals];
g_extra_memory_pointer = extra_memory;
for (size_t i = 0; i < kNumVals; ++i)
extra_memory[i] =
static_cast<std::remove_reference<decltype(extra_memory[0])>::type>(
i * 13 + 2);
extra_ranges->Insert(extra_memory, sizeof(extra_memory[0]) * kNumVals);
extra_ranges->Insert(&g_extra_memory_pointer, sizeof(g_extra_memory_pointer));
}
void AllocateExtraUnsavedMemory(crashpad::SimpleAddressRangeBag* extra_ranges) {
// Allocate some extra memory, and then Insert() but also Remove() it so we
// can confirm it doesn't get saved.
constexpr size_t kNumVals = 2000;
auto extra_memory = new uint32_t[kNumVals];
g_extra_memory_not_saved = extra_memory;
for (size_t i = 0; i < kNumVals; ++i)
extra_memory[i] =
static_cast<std::remove_reference<decltype(extra_memory[0])>::type>(
i * 17 + 7);
extra_ranges->Insert(extra_memory, sizeof(extra_memory[0]) * kNumVals);
extra_ranges->Insert(&g_extra_memory_not_saved,
sizeof(g_extra_memory_not_saved));
// We keep the pointer's memory, but remove the pointed-to memory.
extra_ranges->Remove(extra_memory, sizeof(extra_memory[0]) * kNumVals);
}
int CrashyMain(int argc, wchar_t* argv[]) {
CrashpadClient client;
if (argc == 2) {
if (!client.SetHandlerIPCPipe(argv[1])) {
LOG(ERROR) << "SetHandler";
return EXIT_FAILURE;
}
} else if (argc == 3) {
if (!client.StartHandler(base::FilePath(argv[1]),
base::FilePath(argv[2]),
base::FilePath(),
std::string(),
std::map<std::string, std::string>(),
std::vector<std::string>(),
false,
true)) {
LOG(ERROR) << "StartHandler";
return EXIT_FAILURE;
}
} else {
fprintf(stderr, "Usage: %ls <server_pipe_name>\n", argv[0]);
fprintf(stderr, " %ls <handler_path> <database_path>\n", argv[0]);
return EXIT_FAILURE;
}
crashpad::SimpleAddressRangeBag* extra_ranges =
new crashpad::SimpleAddressRangeBag();
crashpad::CrashpadInfo::GetCrashpadInfo()->set_extra_memory_ranges(
extra_ranges);
AllocateExtraMemoryToBeSaved(extra_ranges);
AllocateExtraUnsavedMemory(extra_ranges);
// Load and unload some uncommonly used modules so we can see them in the list
// reported by `lm`. At least two so that we confirm we got the element size
// advancement of RTL_UNLOAD_EVENT_TRACE correct.
CHECK(GetModuleHandle(L"lz32.dll") == nullptr);
CHECK(GetModuleHandle(L"wmerror.dll") == nullptr);
HMODULE lz32 = LoadLibrary(L"lz32.dll");
HMODULE wmerror = LoadLibrary(L"wmerror.dll");
FreeLibrary(lz32);
FreeLibrary(wmerror);
// Make sure data pointed to by the stack is captured.
constexpr int kDataSize = 512;
int* pointed_to_data = new int[kDataSize];
for (int i = 0; i < kDataSize; ++i)
pointed_to_data[i] = i | ((i % 2 == 0) ? 0x80000000 : 0);
int* offset_pointer = &pointed_to_data[128];
// Encourage the compiler to keep this variable around.
printf("%p, %p\n", offset_pointer, &offset_pointer);
crashpad::CrashpadInfo::GetCrashpadInfo()
->set_gather_indirectly_referenced_memory(
TriState::kEnabled, std::numeric_limits<uint32_t>::max());
std::vector<uint8_t> data_stream1(128, 'x');
crashpad::CrashpadInfo::GetCrashpadInfo()->AddUserDataMinidumpStream(
222222,
reinterpret_cast<const void*>(data_stream1.data()),
data_stream1.size());
std::vector<uint8_t> data_stream2(4096, 'z');
crashpad::CrashpadInfo::GetCrashpadInfo()->AddUserDataMinidumpStream(
333333,
reinterpret_cast<const void*>(data_stream2.data()),
data_stream2.size());
AllocateMemoryOfVariousProtections();
if (InitializeCriticalSectionWithDebugInfoIfPossible(
&g_test_critical_section)) {
EnterCriticalSection(&g_test_critical_section);
}
if (!CreateThreadWithRegisterPointingToTestMemory())
return EXIT_FAILURE;
SomeCrashyFunction();
return EXIT_SUCCESS;
}
} // namespace
} // namespace crashpad
int wmain(int argc, wchar_t* argv[]) {
return crashpad::CrashyMain(argc, argv);
}