From 4860f64923df01fddc96803da3ffc14f47668d2a Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Sat, 31 Oct 2015 11:45:39 -0700 Subject: [PATCH 1/8] win: Handle binary with embedded CodeView debug record I considered writing the CodeView records to the minidump, but I didn't find a ton of docs and debugging is only lightly supported (e.g. http://www.debuginfo.com/articles/gendebuginfo.html#debuggersandformats and it doesn't attempt to load at all on more recent Visual Studios). As we won't be generating symbols in this format, and we don't expect to have symbols for any weird modules that get injected into us in the wild, it seems like we don't lose anything by just ignoring them. R=mark@chromium.org BUG=crashpad:47 Review URL: https://codereview.chromium.org/1430773003 . --- handler/handler.gyp | 23 +++++++++ handler/win/crashy_test_z7_loader.cc | 73 +++++++++++++++++++++++++++ handler/win/z7_test.cpp | 32 ++++++++++++ handler/win/z7_test.dll | Bin 0 -> 168264 bytes snapshot/win/end_to_end_test.py | 24 ++++++++- snapshot/win/module_snapshot_win.cc | 8 +++ snapshot/win/pe_image_reader.cc | 58 +++++++++++---------- util/win/process_info.cc | 6 +-- 8 files changed, 193 insertions(+), 31 deletions(-) create mode 100644 handler/win/crashy_test_z7_loader.cc create mode 100644 handler/win/z7_test.cpp create mode 100644 handler/win/z7_test.dll diff --git a/handler/handler.gyp b/handler/handler.gyp index 04433ac8..398c5404 100644 --- a/handler/handler.gyp +++ b/handler/handler.gyp @@ -110,6 +110,29 @@ ], }, ], + 'conditions': [ + # Cannot create an x64 DLL with embedded debug info. + ['target_arch=="ia32"', { + 'targets': [ + { + 'target_name': 'crashy_z7_loader', + 'type': 'executable', + 'dependencies': [ + '../client/client.gyp:crashpad_client', + '../test/test.gyp:crashpad_test', + '../third_party/mini_chromium/mini_chromium.gyp:base', + '../tools/tools.gyp:crashpad_tool_support', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'win/crashy_test_z7_loader.cc', + ], + }, + ], + }], + ], }, { 'targets': [], }], diff --git a/handler/win/crashy_test_z7_loader.cc b/handler/win/crashy_test_z7_loader.cc new file mode 100644 index 00000000..4788dbe9 --- /dev/null +++ b/handler/win/crashy_test_z7_loader.cc @@ -0,0 +1,73 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// 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 +#include + +#include "base/files/file_path.h" +#include "base/logging.h" +#include "build/build_config.h" +#include "client/crashpad_client.h" +#include "test/paths.h" +#include "tools/tool_support.h" + +#if !defined(ARCH_CPU_X86) +#error This test is only supported on x86. +#endif // !ARCH_CPU_X86 + +namespace crashpad { +namespace { + +int CrashyLoadZ7Main(int argc, char* argv[]) { + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return EXIT_FAILURE; + } + + CrashpadClient client; + if (!client.SetHandler(argv[1])) { + LOG(ERROR) << "SetHandler"; + return EXIT_FAILURE; + } + if (!client.UseHandler()) { + LOG(ERROR) << "UseHandler"; + return EXIT_FAILURE; + } + + // The DLL has /Z7 symbols embedded in the binary (rather than in a .pdb). + // There's only an x86 version of this dll as newer x64 toolchains can't + // generate this format any more. + base::FilePath z7_path = test::Paths::TestDataRoot().Append( + FILE_PATH_LITERAL("handler/win/z7_test.dll")); + HMODULE z7_test = LoadLibrary(z7_path.value().c_str()); + if (!z7_test) { + PLOG(ERROR) << "LoadLibrary"; + return EXIT_FAILURE; + } + FARPROC crash_me = GetProcAddress(z7_test, "CrashMe"); + if (!crash_me) { + PLOG(ERROR) << "GetProcAddress"; + return EXIT_FAILURE; + } + reinterpret_cast(crash_me)(); + + return EXIT_SUCCESS; +} + +} // namespace +} // namespace crashpad + +int wmain(int argc, wchar_t* argv[]) { + return crashpad::ToolSupport::Wmain(argc, argv, crashpad::CrashyLoadZ7Main); +} diff --git a/handler/win/z7_test.cpp b/handler/win/z7_test.cpp new file mode 100644 index 00000000..ad7b5d98 --- /dev/null +++ b/handler/win/z7_test.cpp @@ -0,0 +1,32 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// 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. + +// Build in VC++6 or older command prompt with: +// +// cl /nologo /W4 /MT /Z7 z7_test.cpp /link /dll /out:z7_test.dll /debugtype:cv /pdb:none +// +// Given that this is quite tedious to build, the result is also checked in. + +#include +#include + +extern "C" __declspec(dllexport) void CrashMe() { + volatile int* foo = reinterpret_cast(7); + *foo = 42; +} + +BOOL WINAPI DllMain(HINSTANCE hinstance, DWORD reason, LPVOID) { + printf("%p %d\n", hinstance, reason); + return TRUE; +} diff --git a/handler/win/z7_test.dll b/handler/win/z7_test.dll new file mode 100644 index 0000000000000000000000000000000000000000..d470742e8c3f7282cbe373575708eeb593b67871 GIT binary patch literal 168264 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 GO8ysNlo`7K literal 0 HcmV?d00001 diff --git a/snapshot/win/end_to_end_test.py b/snapshot/win/end_to_end_test.py index 9ed5fda7..74cbc56e 100644 --- a/snapshot/win/end_to_end_test.py +++ b/snapshot/win/end_to_end_test.py @@ -121,6 +121,10 @@ def GetDumpFromSelfDestroyingProgram(out_dir, pipe_name): return GetDumpFromProgram(out_dir, pipe_name, 'self_destroying_program.exe') +def GetDumpFromZ7Program(out_dir, pipe_name): + return GetDumpFromProgram(out_dir, pipe_name, 'crashy_z7_loader.exe') + + class CdbRun(object): """Run cdb.exe passing it a cdb command and capturing the output. `Check()` searches for regex patterns in sequence allowing verification of @@ -155,7 +159,7 @@ class CdbRun(object): sys.exit(1) -def RunTests(cdb_path, dump_path, destroyed_dump_path, pipe_name): +def RunTests(cdb_path, dump_path, destroyed_dump_path, z7_dump_path, pipe_name): """Runs various tests in sequence. Runs a new cdb instance on the dump for each block of tests to reduce the chances that output from one command is confused for output from another. @@ -212,6 +216,15 @@ def RunTests(cdb_path, dump_path, destroyed_dump_path, pipe_name): r'FreeOwnStackAndBreak.*\nquit:', 'at correct location, no additional stack entries') + if z7_dump_path: + out = CdbRun(cdb_path, z7_dump_path, '.ecxr;lm') + out.Check('This dump file has an exception of interest stored in it', + 'captured exception in z7 module') + out.Check(r'z7_test\+0x[0-8a-f]+:', 'exception in z7 at correct location') + out.Check(r'z7_test C \(codeview symbols\) z7_test.dll', + 'expected non-pdb symbol format') + + def main(args): try: if len(args) != 1: @@ -242,7 +255,14 @@ def main(args): if not destroyed_dump_path: return 1 - RunTests(cdb_path, crashy_dump_path, destroyed_dump_path, pipe_name) + z7_dump_path = None + if not args[0].endswith('x64'): + z7_dump_path = GetDumpFromZ7Program(args[0], pipe_name) + if not z7_dump_path: + return 1 + + RunTests(cdb_path, crashy_dump_path, destroyed_dump_path, z7_dump_path, + pipe_name) return 0 finally: diff --git a/snapshot/win/module_snapshot_win.cc b/snapshot/win/module_snapshot_win.cc index 3876fb1e..194d15ac 100644 --- a/snapshot/win/module_snapshot_win.cc +++ b/snapshot/win/module_snapshot_win.cc @@ -60,6 +60,14 @@ bool ModuleSnapshotWin::Initialize( &uuid_, &age_dword, &pdb_name_)) { static_assert(sizeof(DWORD) == sizeof(uint32_t), "unexpected age size"); age_ = age_dword; + } else { + // If we fully supported all old debugging formats, we would want to extract + // and emit a different type of CodeView record here (as old Microsoft tools + // would do). As we don't expect to ever encounter a module that wouldn't be + // be using .PDB that we actually have symbols for, we simply set a + // plausible name here, but this will never correspond to symbols that we + // have. + pdb_name_ = base::UTF16ToUTF8(name_); } INITIALIZATION_STATE_SET_VALID(initialized_); diff --git a/snapshot/win/pe_image_reader.cc b/snapshot/win/pe_image_reader.cc index b6bb7aa6..82bce064 100644 --- a/snapshot/win/pe_image_reader.cc +++ b/snapshot/win/pe_image_reader.cc @@ -176,35 +176,41 @@ bool PEImageReader::ReadDebugDirectoryInformation(UUID* uuid, if (debug_directory.Type != IMAGE_DEBUG_TYPE_CODEVIEW) continue; - if (debug_directory.SizeOfData < sizeof(CodeViewRecordPDB70)) { - LOG(WARNING) << "CodeView debug entry of unexpected size"; - continue; - } - scoped_ptr data(new char[debug_directory.SizeOfData]); - if (!CheckedReadMemory(Address() + debug_directory.AddressOfRawData, - debug_directory.SizeOfData, - data.get())) { - LOG(WARNING) << "could not read debug directory"; - return false; - } + if (debug_directory.AddressOfRawData) { + if (debug_directory.SizeOfData < sizeof(CodeViewRecordPDB70)) { + LOG(WARNING) << "CodeView debug entry of unexpected size"; + continue; + } - if (*reinterpret_cast(data.get()) != - CodeViewRecordPDB70::kSignature) { - // TODO(scottmg): Consider supporting other record types, see + scoped_ptr data(new char[debug_directory.SizeOfData]); + if (!CheckedReadMemory(Address() + debug_directory.AddressOfRawData, + debug_directory.SizeOfData, + data.get())) { + LOG(WARNING) << "could not read debug directory"; + return false; + } + + if (*reinterpret_cast(data.get()) != + CodeViewRecordPDB70::kSignature) { + LOG(WARNING) << "encountered non-7.0 CodeView debug record"; + continue; + } + + CodeViewRecordPDB70* codeview = + reinterpret_cast(data.get()); + *uuid = codeview->uuid; + *age = codeview->age; + // This is a NUL-terminated string encoded in the codepage of the system + // where the binary was linked. We have no idea what that was, so we just + // assume ASCII. + *pdbname = std::string(reinterpret_cast(&codeview->pdb_name[0])); + return true; + } else if (debug_directory.PointerToRawData) { + // This occurs for non-PDB based debug information. We simply ignore these + // as we don't expect to encounter modules that will be in this format + // for which we'll actually have symbols. See // https://crashpad.chromium.org/bug/47. - LOG(WARNING) << "encountered non-7.0 CodeView debug record"; - continue; } - - CodeViewRecordPDB70* codeview = - reinterpret_cast(data.get()); - *uuid = codeview->uuid; - *age = codeview->age; - // This is a NUL-terminated string encoded in the codepage of the system - // where the binary was linked. We have no idea what that was, so we just - // assume ASCII. - *pdbname = std::string(reinterpret_cast(&codeview->pdb_name[0])); - return true; } return false; diff --git a/util/win/process_info.cc b/util/win/process_info.cc index cdc27b1e..d5316590 100644 --- a/util/win/process_info.cc +++ b/util/win/process_info.cc @@ -514,14 +514,14 @@ bool ProcessInfo::Initialize(HANDLE process) { system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; } -#if ARCH_CPU_32_BITS +#if defined(ARCH_CPU_32_BITS) if (is_64_bit_) { LOG(ERROR) << "Reading x64 process from x86 process not supported"; return false; } -#endif +#endif // ARCH_CPU_32_BITS -#if ARCH_CPU_64_BITS +#if defined(ARCH_CPU_64_BITS) bool result = GetProcessBasicInformation( process, is_wow64_, this, &peb_address_, &peb_size_); #else From 3ea517298217c85c284d5ef9aef0003c4be22d33 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Mon, 2 Nov 2015 12:19:44 -0500 Subject: [PATCH 2/8] win: Let Doxygen see CRASHPAD_SIMULATE_CRASH() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Doxygen doesn’t generate documentation for macros unless the file that defines it has a \file directive. R=scottmg@chromium.org Review URL: https://codereview.chromium.org/1406253009 . --- client/simulate_crash_win.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/simulate_crash_win.h b/client/simulate_crash_win.h index 81c0af93..a20f3dad 100644 --- a/client/simulate_crash_win.h +++ b/client/simulate_crash_win.h @@ -20,6 +20,8 @@ #include "client/crashpad_client.h" #include "util/win/capture_context.h" +//! \file + //! \brief Captures the CPU context and captures a dump without an exception. #define CRASHPAD_SIMULATE_CRASH() \ do { \ From 04d97ca86e420e15438696d9c06a272e3f4154c9 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Mon, 2 Nov 2015 12:20:25 -0500 Subject: [PATCH 3/8] win: Add a note about /Z7 obsolescence regarding CodeView debug info BUG=crashpad:47 R=scottmg@chromium.org Review URL: https://codereview.chromium.org/1414323003 . --- compat/non_win/dbghelp.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compat/non_win/dbghelp.h b/compat/non_win/dbghelp.h index 08f07ae8..ecf8bab8 100644 --- a/compat/non_win/dbghelp.h +++ b/compat/non_win/dbghelp.h @@ -564,7 +564,9 @@ struct __attribute__((packed, aligned(4))) MINIDUMP_MODULE { //! CodeView 4.1 format. Signatures seen in the wild include “NB09” //! (0x3930424e) for CodeView 4.1 and “NB11” (0x3131424e) for CodeView 5.0. //! This form of debugging information within the module, as opposed to a link - //! to an external `.pdb` file, is chosen by building with `/Z7`. + //! to an external `.pdb` file, is chosen by building with `/Z7` in Visual + //! Studio 6.0 (1998) and earlier. This embedded form of debugging information + //! is now considered obsolete. //! //! On Windows, the CodeView record is taken from a module’s //! IMAGE_DEBUG_DIRECTORY entry whose Type field has the value From 3e4130ad5d608855e09edf2a9ccdcd130a325293 Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Mon, 2 Nov 2015 09:35:08 -0800 Subject: [PATCH 4/8] win: Also look in PROGRAMW6432 for cdb This is necessary for 64 bit tools installed on a 64 bit OS, but with the tests run from a 32 bit Python. (sigh) Doesn't happen on bots, but comes up occasionally testing on VMs. R=mark@chromium.org Review URL: https://codereview.chromium.org/1425153003 . --- snapshot/win/end_to_end_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/snapshot/win/end_to_end_test.py b/snapshot/win/end_to_end_test.py index 74cbc56e..e9ea0f4b 100644 --- a/snapshot/win/end_to_end_test.py +++ b/snapshot/win/end_to_end_test.py @@ -41,6 +41,7 @@ def CleanUpTempDirs(): def FindInstalledWindowsApplication(app_path): search_paths = [os.getenv('PROGRAMFILES(X86)'), os.getenv('PROGRAMFILES'), + os.getenv('PROGRAMW6432'), os.getenv('LOCALAPPDATA')] search_paths += os.getenv('PATH', '').split(os.pathsep) From c295e9d7487ab6a3a7f827c4464efacb9c3bb091 Mon Sep 17 00:00:00 2001 From: Scott Graham Date: Mon, 2 Nov 2015 10:28:01 -0800 Subject: [PATCH 5/8] Fix exception location in z7 test on older bots The cdb on the x86 bot displays relative to exported DLL symbols, but newer ones don't seem to, so it's either: z7_test!CrashMe+0xe or z7_test+0x100e https://build.chromium.org/p/client.crashpad/builders/crashpad_win_x86_rel/builds/110/steps/run%20tests/logs/stdio R=mark@chromium.org BUG=crashpad:47 Review URL: https://codereview.chromium.org/1430633006 . --- snapshot/win/end_to_end_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/snapshot/win/end_to_end_test.py b/snapshot/win/end_to_end_test.py index e9ea0f4b..31134eec 100644 --- a/snapshot/win/end_to_end_test.py +++ b/snapshot/win/end_to_end_test.py @@ -221,7 +221,10 @@ def RunTests(cdb_path, dump_path, destroyed_dump_path, z7_dump_path, pipe_name): out = CdbRun(cdb_path, z7_dump_path, '.ecxr;lm') out.Check('This dump file has an exception of interest stored in it', 'captured exception in z7 module') - out.Check(r'z7_test\+0x[0-8a-f]+:', 'exception in z7 at correct location') + # Older versions of cdb display relative to exports for /Z7 modules, newer + # ones just display the offset. + out.Check(r'z7_test(!CrashMe\+0xe|\+0x100e):', + 'exception in z7 at correct location') out.Check(r'z7_test C \(codeview symbols\) z7_test.dll', 'expected non-pdb symbol format') From 740c668e872456d969aa990a0316b1b8fdaba944 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Mon, 2 Nov 2015 13:59:36 -0500 Subject: [PATCH 6/8] win: Implement CrashpadClient::StartHandler() BUG=crashpad:69 R=scottmg@chromium.org Review URL: https://codereview.chromium.org/1428803006 . --- client/crashpad_client.h | 2 + client/crashpad_client_win.cc | 99 +++++++++-- handler/crashpad_handler.ad | 19 ++- handler/main.cc | 15 +- handler/win/crashy_test_program.cc | 37 +++- snapshot/win/end_to_end_test.py | 49 ++++-- snapshot/win/exception_snapshot_win_test.cc | 4 +- test/multiprocess_exec_win.cc | 3 +- test/win/child_launcher.cc | 35 +--- test/win/child_launcher.h | 10 +- util/util.gyp | 2 + util/util_test.gyp | 1 + util/win/command_line.cc | 58 +++++++ util/win/command_line.h | 38 +++++ util/win/command_line_test.cc | 177 ++++++++++++++++++++ util/win/exception_handler_server.cc | 14 +- util/win/exception_handler_server.h | 7 +- util/win/exception_handler_server_test.cc | 2 +- util/win/process_info_test.cc | 1 + 19 files changed, 478 insertions(+), 95 deletions(-) create mode 100644 util/win/command_line.cc create mode 100644 util/win/command_line.h create mode 100644 util/win/command_line_test.cc diff --git a/client/crashpad_client.h b/client/crashpad_client.h index cefa179b..c45f0607 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -146,6 +146,8 @@ class CrashpadClient { private: #if defined(OS_MACOSX) base::mac::ScopedMachSendRight exception_port_; +#elif defined(OS_WIN) + std::string ipc_port_; #endif DISALLOW_COPY_AND_ASSIGN(CrashpadClient); diff --git a/client/crashpad_client_win.cc b/client/crashpad_client_win.cc index 92529ae1..5e7346fa 100644 --- a/client/crashpad_client_win.cc +++ b/client/crashpad_client_win.cc @@ -19,10 +19,13 @@ #include "base/atomicops.h" #include "base/logging.h" +#include "base/rand_util.h" #include "base/strings/string16.h" +#include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "base/synchronization/lock.h" #include "util/file/file_io.h" +#include "util/win/command_line.h" #include "util/win/critical_section_with_debug_info.h" #include "util/win/registration_protocol_win.h" #include "util/win/scoped_handle.h" @@ -102,11 +105,17 @@ LONG WINAPI UnhandledExceptionHandler(EXCEPTION_POINTERS* exception_pointers) { return EXCEPTION_CONTINUE_SEARCH; } +std::wstring FormatArgumentString(const std::string& name, + const std::wstring& value) { + return std::wstring(L"--") + base::UTF8ToUTF16(name) + L"=" + value; +} + } // namespace namespace crashpad { -CrashpadClient::CrashpadClient() { +CrashpadClient::CrashpadClient() + : ipc_port_() { } CrashpadClient::~CrashpadClient() { @@ -118,11 +127,81 @@ bool CrashpadClient::StartHandler( const std::string& url, const std::map& annotations, const std::vector& arguments) { - LOG(FATAL) << "SetHandler should be used on Windows"; - return false; + DCHECK(ipc_port_.empty()); + + ipc_port_ = + base::StringPrintf("\\\\.\\pipe\\crashpad_%d_", GetCurrentProcessId()); + for (int index = 0; index < 16; ++index) { + ipc_port_.append(1, static_cast(base::RandInt('A', 'Z'))); + } + + std::wstring command_line; + AppendCommandLineArgument(handler.value(), &command_line); + for (const std::string& argument : arguments) { + AppendCommandLineArgument(base::UTF8ToUTF16(argument), &command_line); + } + if (!database.value().empty()) { + AppendCommandLineArgument(FormatArgumentString("database", + database.value()), + &command_line); + } + if (!url.empty()) { + AppendCommandLineArgument(FormatArgumentString("url", + base::UTF8ToUTF16(url)), + &command_line); + } + for (const auto& kv : annotations) { + AppendCommandLineArgument( + FormatArgumentString("annotation", + base::UTF8ToUTF16(kv.first + '=' + kv.second)), + &command_line); + } + AppendCommandLineArgument(FormatArgumentString("pipe-name", + base::UTF8ToUTF16(ipc_port_)), + &command_line); + + STARTUPINFO startup_info = {}; + startup_info.cb = sizeof(startup_info); + startup_info.dwFlags = STARTF_USESTDHANDLES; + startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); + PROCESS_INFORMATION process_info; + BOOL rv = CreateProcess(handler.value().c_str(), + &command_line[0], + nullptr, + nullptr, + true, + 0, + nullptr, + nullptr, + &startup_info, + &process_info); + if (!rv) { + PLOG(ERROR) << "CreateProcess"; + return false; + } + + rv = CloseHandle(process_info.hThread); + PLOG_IF(WARNING, !rv) << "CloseHandle thread"; + + rv = CloseHandle(process_info.hProcess); + PLOG_IF(WARNING, !rv) << "CloseHandle process"; + + return true; } bool CrashpadClient::SetHandler(const std::string& ipc_port) { + DCHECK(ipc_port_.empty()); + DCHECK(!ipc_port.empty()); + + ipc_port_ = ipc_port; + + return true; +} + +bool CrashpadClient::UseHandler() { + DCHECK(!ipc_port_.empty()); DCHECK_EQ(g_signal_exception, INVALID_HANDLE_VALUE); DCHECK_EQ(g_signal_non_crash_dump, INVALID_HANDLE_VALUE); DCHECK_EQ(g_non_crash_dump_done, INVALID_HANDLE_VALUE); @@ -154,7 +233,7 @@ bool CrashpadClient::SetHandler(const std::string& ipc_port) { ServerToClientMessage response = {0}; if (!SendToCrashHandlerServer( - base::UTF8ToUTF16(ipc_port), message, &response)) { + base::UTF8ToUTF16(ipc_port_), message, &response)) { return false; } @@ -168,16 +247,6 @@ bool CrashpadClient::SetHandler(const std::string& ipc_port) { g_non_crash_dump_lock = new base::Lock(); - return true; -} - -bool CrashpadClient::UseHandler() { - if (g_signal_exception == INVALID_HANDLE_VALUE || - g_signal_non_crash_dump == INVALID_HANDLE_VALUE || - g_non_crash_dump_done == INVALID_HANDLE_VALUE) { - return false; - } - // In theory we could store the previous handler but it is not clear what // use we have for it. SetUnhandledExceptionFilter(&UnhandledExceptionHandler); @@ -188,7 +257,7 @@ bool CrashpadClient::UseHandler() { void CrashpadClient::DumpWithoutCrash(const CONTEXT& context) { if (g_signal_non_crash_dump == INVALID_HANDLE_VALUE || g_non_crash_dump_done == INVALID_HANDLE_VALUE) { - LOG(ERROR) << "haven't called SetHandler()"; + LOG(ERROR) << "haven't called UseHandler()"; return; } diff --git a/handler/crashpad_handler.ad b/handler/crashpad_handler.ad index f00ca054..a97e2088 100644 --- a/handler/crashpad_handler.ad +++ b/handler/crashpad_handler.ad @@ -33,14 +33,23 @@ collection server. Uploads are disabled by default, and can only be enabled by a Crashpad client using the Crashpad client library, typically in response to a user requesting this behavior. -This server is normally started by its initial client, and it performs a -handshake with this client via a pipe established by the client that is +On OS X, this server is normally started by its initial client, and it performs +a handshake with this client via a pipe established by the client that is inherited by the server, referenced by the *--handshake-fd* argument. During the handshake, the server furnishes the client with a send right that the client may use as an exception port. The server retains the corresponding receive right, which it monitors for exception messages. When the receive right loses all senders, the server exits after allowing any upload in progress to complete. +On Windows, clients register with this server by communicating with it via the +named pipe identified by the *--pipe-name* argument. During registration, a +client provides the server with an OS event object that it will signal should it +crash. The server obtains the client’s process handle and waits on the crash +event object for a crash, as well as the client’s process handle for the client +to exit cleanly without crashing. When the server loses all clients and +*--persistent* is not specified, it exits after allowing any upload in progress +to complete. + It is not normally appropriate to invoke this program directly. Usually, it will be invoked by a Crashpad client using the Crashpad client library. Arbitrary programs may be run with a Crashpad handler by using @@ -77,6 +86,12 @@ of 'PATH' exists. Perform the handshake with the initial client on the file descriptor at 'FD'. This option is required. This option is only valid on Mac OS X. +*--persistent*:: +Continue running after the last client exits. If this option is not specified, +this server will exit as soon as it has no clients, although on startup, it +always waits for at least one client to connect. This option is only valid on +Windows. + *--pipe-name*='PIPE':: Listen on the given pipe name for connections from clients. 'PIPE' must be of the form +\\.\pipe\+. This option is required. This option is only diff --git a/handler/main.cc b/handler/main.cc index de3171ed..9a9b6f5d 100644 --- a/handler/main.cc +++ b/handler/main.cc @@ -60,6 +60,7 @@ void Usage(const base::FilePath& me) { " --reset-own-crash-exception-port-to-system-default\n" " reset the server's exception handler to default\n" #elif defined(OS_WIN) +" --persistent continue running after all clients exit\n" " --pipe-name=PIPE communicate with the client over PIPE\n" #endif // OS_MACOSX " --url=URL send crash reports to this Breakpad server URL,\n" @@ -84,6 +85,7 @@ int HandlerMain(int argc, char* argv[]) { kOptionHandshakeFD, kOptionResetOwnCrashExceptionPortToSystemDefault, #elif defined(OS_WIN) + kOptionPersistent, kOptionPipeName, #endif // OS_MACOSX kOptionURL, @@ -101,8 +103,9 @@ int HandlerMain(int argc, char* argv[]) { int handshake_fd; bool reset_own_crash_exception_port_to_system_default; #elif defined(OS_WIN) + bool persistent; std::string pipe_name; -#endif +#endif // OS_MACOSX } options = {}; #if defined(OS_MACOSX) options.handshake_fd = -1; @@ -119,8 +122,9 @@ int HandlerMain(int argc, char* argv[]) { nullptr, kOptionResetOwnCrashExceptionPortToSystemDefault}, #elif defined(OS_WIN) + {"persistent", no_argument, nullptr, kOptionPersistent}, {"pipe-name", required_argument, nullptr, kOptionPipeName}, -#endif +#endif // OS_MACOSX {"url", required_argument, nullptr, kOptionURL}, {"help", no_argument, nullptr, kOptionHelp}, {"version", no_argument, nullptr, kOptionVersion}, @@ -163,6 +167,10 @@ int HandlerMain(int argc, char* argv[]) { break; } #elif defined(OS_WIN) + case kOptionPersistent: { + options.persistent = true; + break; + } case kOptionPipeName: { options.pipe_name = optarg; break; @@ -228,7 +236,8 @@ int HandlerMain(int argc, char* argv[]) { ExceptionHandlerServer exception_handler_server(receive_right.Pass()); #elif defined(OS_WIN) - ExceptionHandlerServer exception_handler_server(options.pipe_name); + ExceptionHandlerServer exception_handler_server(options.pipe_name, + options.persistent); #endif // OS_MACOSX scoped_ptr database(CrashReportDatabase::Initialize( diff --git a/handler/win/crashy_test_program.cc b/handler/win/crashy_test_program.cc index 66504048..15c7b613 100644 --- a/handler/win/crashy_test_program.cc +++ b/handler/win/crashy_test_program.cc @@ -12,16 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include +#include +#include +#include + // ntstatus.h conflicts with windows.h so define this locally. #ifndef STATUS_NO_SUCH_FILE #define STATUS_NO_SUCH_FILE static_cast(0xC000000F) #endif #include "base/basictypes.h" +#include "base/files/file_path.h" #include "base/logging.h" +#include "base/strings/utf_string_conversions.h" #include "client/crashpad_client.h" #include "tools/tool_support.h" #include "util/win/critical_section_with_debug_info.h" @@ -88,19 +95,31 @@ void SomeCrashyFunction() { } int CrashyMain(int argc, char* argv[]) { - if (argc != 2) { + CrashpadClient client; + + if (argc == 2) { + if (!client.SetHandler(argv[1])) { + LOG(ERROR) << "SetHandler"; + return EXIT_FAILURE; + } + } else if (argc == 3) { + if (!client.StartHandler(base::FilePath(base::UTF8ToUTF16(argv[1])), + base::FilePath(base::UTF8ToUTF16(argv[2])), + std::string(), + std::map(), + std::vector())) { + LOG(ERROR) << "StartHandler"; + return EXIT_FAILURE; + } + } else { fprintf(stderr, "Usage: %s \n", argv[0]); - return 1; + fprintf(stderr, " %s \n", argv[0]); + return EXIT_FAILURE; } - CrashpadClient client; - if (!client.SetHandler(argv[1])) { - LOG(ERROR) << "SetHandler"; - return 1; - } if (!client.UseHandler()) { LOG(ERROR) << "UseHandler"; - return 1; + return EXIT_FAILURE; } AllocateMemoryOfVariousProtections(); @@ -112,7 +131,7 @@ int CrashyMain(int argc, char* argv[]) { SomeCrashyFunction(); - return 0; + return EXIT_SUCCESS; } } // namespace diff --git a/snapshot/win/end_to_end_test.py b/snapshot/win/end_to_end_test.py index 31134eec..3bbcdb98 100644 --- a/snapshot/win/end_to_end_test.py +++ b/snapshot/win/end_to_end_test.py @@ -78,9 +78,11 @@ def GetCdbPath(): def GetDumpFromProgram(out_dir, pipe_name, executable_name): - """Initialize a crash database, run crashpad_handler, run |executable_name| - connecting to the crash_handler. Returns the minidump generated by - crash_handler for further testing. + """Initialize a crash database, and run |executable_name| connecting to a + crash handler. If pipe_name is set, crashpad_handler will be started first. If + pipe_name is empty, the executable is responsible for starting + crashpad_handler. Returns the minidump generated by crashpad_handler for + further testing. """ test_database = MakeTempDir() handler = None @@ -92,13 +94,18 @@ def GetDumpFromProgram(out_dir, pipe_name, executable_name): print 'could not initialize report database' return None - handler = subprocess.Popen([ - os.path.join(out_dir, 'crashpad_handler.exe'), - '--pipe-name=' + pipe_name, - '--database=' + test_database - ]) + if pipe_name is not None: + handler = subprocess.Popen([ + os.path.join(out_dir, 'crashpad_handler.exe'), + '--pipe-name=' + pipe_name, + '--database=' + test_database + ]) - subprocess.call([os.path.join(out_dir, executable_name), pipe_name]) + subprocess.call([os.path.join(out_dir, executable_name), pipe_name]) + else: + subprocess.call([os.path.join(out_dir, executable_name), + os.path.join(out_dir, 'crashpad_handler.exe'), + test_database]) out = subprocess.check_output([ os.path.join(out_dir, 'crashpad_database_util.exe'), @@ -160,7 +167,12 @@ class CdbRun(object): sys.exit(1) -def RunTests(cdb_path, dump_path, destroyed_dump_path, z7_dump_path, pipe_name): +def RunTests(cdb_path, + dump_path, + start_handler_dump_path, + destroyed_dump_path, + z7_dump_path, + pipe_name): """Runs various tests in sequence. Runs a new cdb instance on the dump for each block of tests to reduce the chances that output from one command is confused for output from another. @@ -172,6 +184,13 @@ def RunTests(cdb_path, dump_path, destroyed_dump_path, z7_dump_path, pipe_name): 'crashy_program!crashpad::`anonymous namespace\'::SomeCrashyFunction', 'exception at correct location') + out = CdbRun(cdb_path, start_handler_dump_path, '.ecxr') + out.Check('This dump file has an exception of interest stored in it', + 'captured exception (using StartHandler())') + out.Check( + 'crashy_program!crashpad::`anonymous namespace\'::SomeCrashyFunction', + 'exception at correct location (using StartHandler())') + out = CdbRun(cdb_path, dump_path, '!peb') out.Check(r'PEB at', 'found the PEB') out.Check(r'Ldr\.InMemoryOrderModuleList:.*\d+ \. \d+', 'PEB_LDR_DATA saved') @@ -255,6 +274,10 @@ def main(args): if not crashy_dump_path: return 1 + start_handler_dump_path = GetDumpFromCrashyProgram(args[0], None) + if not start_handler_dump_path: + return 1 + destroyed_dump_path = GetDumpFromSelfDestroyingProgram(args[0], pipe_name) if not destroyed_dump_path: return 1 @@ -265,7 +288,11 @@ def main(args): if not z7_dump_path: return 1 - RunTests(cdb_path, crashy_dump_path, destroyed_dump_path, z7_dump_path, + RunTests(cdb_path, + crashy_dump_path, + start_handler_dump_path, + destroyed_dump_path, + z7_dump_path, pipe_name) return 0 diff --git a/snapshot/win/exception_snapshot_win_test.cc b/snapshot/win/exception_snapshot_win_test.cc index fe626a38..8e2bc61b 100644 --- a/snapshot/win/exception_snapshot_win_test.cc +++ b/snapshot/win/exception_snapshot_win_test.cc @@ -128,7 +128,7 @@ void TestCrashingChild(const base::string16& directory_modification) { ScopedKernelHANDLE completed(CreateEvent(nullptr, false, false, nullptr)); CrashingDelegate delegate(server_ready.get(), completed.get()); - ExceptionHandlerServer exception_handler_server(pipe_name); + ExceptionHandlerServer exception_handler_server(pipe_name, true); RunServerThread server_thread(&exception_handler_server, &delegate); server_thread.Start(); ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread( @@ -230,7 +230,7 @@ void TestDumpWithoutCrashingChild( ScopedKernelHANDLE completed(CreateEvent(nullptr, false, false, nullptr)); SimulateDelegate delegate(server_ready.get(), completed.get()); - ExceptionHandlerServer exception_handler_server(pipe_name); + ExceptionHandlerServer exception_handler_server(pipe_name, true); RunServerThread server_thread(&exception_handler_server, &delegate); server_thread.Start(); ScopedStopServerAndJoinThread scoped_stop_server_and_join_thread( diff --git a/test/multiprocess_exec_win.cc b/test/multiprocess_exec_win.cc index 23853dd0..2a58f846 100644 --- a/test/multiprocess_exec_win.cc +++ b/test/multiprocess_exec_win.cc @@ -17,7 +17,7 @@ #include "base/logging.h" #include "base/strings/utf_string_conversions.h" #include "gtest/gtest.h" -#include "test/win/child_launcher.h" +#include "util/win/command_line.h" namespace crashpad { namespace test { @@ -119,7 +119,6 @@ void MultiprocessExec::PreFork() { command_line_.clear(); AppendCommandLineArgument(base::UTF8ToUTF16(command_), &command_line_); for (size_t i = 0; i < arguments_.size(); ++i) { - command_line_ += L" "; AppendCommandLineArgument(base::UTF8ToUTF16(arguments_[i]), &command_line_); } diff --git a/test/win/child_launcher.cc b/test/win/child_launcher.cc index 2b82e057..e35f1cb8 100644 --- a/test/win/child_launcher.cc +++ b/test/win/child_launcher.cc @@ -15,6 +15,7 @@ #include "test/win/child_launcher.h" #include "gtest/gtest.h" +#include "util/win/command_line.h" namespace crashpad { namespace test { @@ -96,39 +97,5 @@ DWORD ChildLauncher::WaitForExit() { return exit_code; } -// Ref: http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx -void AppendCommandLineArgument(const std::wstring& argument, - std::wstring* command_line) { - // Don't bother quoting if unnecessary. - if (!argument.empty() && - argument.find_first_of(L" \t\n\v\"") == std::wstring::npos) { - command_line->append(argument); - } else { - command_line->push_back(L'"'); - for (std::wstring::const_iterator i = argument.begin();; ++i) { - size_t backslash_count = 0; - while (i != argument.end() && *i == L'\\') { - ++i; - ++backslash_count; - } - if (i == argument.end()) { - // Escape all backslashes, but let the terminating double quotation mark - // we add below be interpreted as a metacharacter. - command_line->append(backslash_count * 2, L'\\'); - break; - } else if (*i == L'"') { - // Escape all backslashes and the following double quotation mark. - command_line->append(backslash_count * 2 + 1, L'\\'); - command_line->push_back(*i); - } else { - // Backslashes aren't special here. - command_line->append(backslash_count, L'\\'); - command_line->push_back(*i); - } - } - command_line->push_back(L'"'); - } -} - } // namespace test } // namespace crashpad diff --git a/test/win/child_launcher.h b/test/win/child_launcher.h index 456d969d..22463fa5 100644 --- a/test/win/child_launcher.h +++ b/test/win/child_launcher.h @@ -42,7 +42,7 @@ class ChildLauncher { void Start(); //! \brief Waits for the child process to exit. - //! + //! //! \return The process exit code. DWORD WaitForExit(); @@ -67,14 +67,6 @@ class ChildLauncher { ScopedFileHANDLE stdin_write_handle_; }; -//! \brief Utility function for building escaped command lines. -//! -//! \param[in] argument Appended to \a command_line surrounded by properly -//! escaped quotation marks, if necessary. -//! \param[inout] command_line The command line being constructed. -void AppendCommandLineArgument(const std::wstring& argument, - std::wstring* command_line); - } // namespace test } // namespace crashpad diff --git a/util/util.gyp b/util/util.gyp index e8206b91..65791b5a 100644 --- a/util/util.gyp +++ b/util/util.gyp @@ -152,6 +152,8 @@ 'win/capture_context.asm', 'win/capture_context.h', 'win/checked_win_address_range.h', + 'win/command_line.cc', + 'win/command_line.h', 'win/critical_section_with_debug_info.cc', 'win/critical_section_with_debug_info.h', 'win/exception_handler_server.cc', diff --git a/util/util_test.gyp b/util/util_test.gyp index 6517e0b4..40bb64fd 100644 --- a/util/util_test.gyp +++ b/util/util_test.gyp @@ -80,6 +80,7 @@ 'thread/thread_log_messages_test.cc', 'thread/thread_test.cc', 'win/capture_context_test.cc', + 'win/command_line_test.cc', 'win/critical_section_with_debug_info_test.cc', 'win/exception_handler_server_test.cc', 'win/get_function_test.cc', diff --git a/util/win/command_line.cc b/util/win/command_line.cc new file mode 100644 index 00000000..5829c12d --- /dev/null +++ b/util/win/command_line.cc @@ -0,0 +1,58 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// 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 "util/win/command_line.h" + +namespace crashpad { + +// Ref: +// http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx +void AppendCommandLineArgument(const std::wstring& argument, + std::wstring* command_line) { + if (!command_line->empty()) { + command_line->push_back(L' '); + } + + // Don’t bother quoting if unnecessary. + if (!argument.empty() && + argument.find_first_of(L" \t\n\v\"") == std::wstring::npos) { + command_line->append(argument); + } else { + command_line->push_back(L'"'); + for (std::wstring::const_iterator i = argument.begin();; ++i) { + size_t backslash_count = 0; + while (i != argument.end() && *i == L'\\') { + ++i; + ++backslash_count; + } + if (i == argument.end()) { + // Escape all backslashes, but let the terminating double quotation mark + // we add below be interpreted as a metacharacter. + command_line->append(backslash_count * 2, L'\\'); + break; + } else if (*i == L'"') { + // Escape all backslashes and the following double quotation mark. + command_line->append(backslash_count * 2 + 1, L'\\'); + command_line->push_back(*i); + } else { + // Backslashes aren’t special here. + command_line->append(backslash_count, L'\\'); + command_line->push_back(*i); + } + } + command_line->push_back(L'"'); + } +} + +} // namespace crashpad diff --git a/util/win/command_line.h b/util/win/command_line.h new file mode 100644 index 00000000..eb3f7124 --- /dev/null +++ b/util/win/command_line.h @@ -0,0 +1,38 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// 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. + +#ifndef CRASHPAD_UTIL_WIN_COMMAND_LINE_H_ +#define CRASHPAD_UTIL_WIN_COMMAND_LINE_H_ + +#include + +namespace crashpad { + +//! \brief Utility function for building escaped command lines. +//! +//! This builds a command line so that individual arguments can be reliably +//! decoded by `CommandLineToArgvW()`. +//! +//! \a argument is appended to \a command_line. If necessary, it will be placed +//! in quotation marks and escaped properly. If \a command_line is initially +//! non-empty, a space will precede \a argument. +//! +//! \param[in] argument The argument to append to \a command_line. +//! \param[inout] command_line The command line being constructed. +void AppendCommandLineArgument(const std::wstring& argument, + std::wstring* command_line); + +} // namespace crashpad + +#endif // CRASHPAD_UTIL_WIN_COMMAND_LINE_H_ diff --git a/util/win/command_line_test.cc b/util/win/command_line_test.cc new file mode 100644 index 00000000..5dc9e901 --- /dev/null +++ b/util/win/command_line_test.cc @@ -0,0 +1,177 @@ +// Copyright 2015 The Crashpad Authors. All rights reserved. +// +// 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 "util/win/command_line.h" + +#include +#include + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/scoped_generic.h" +#include "gtest/gtest.h" +#include "test/errors.h" + +namespace crashpad { +namespace test { +namespace { + +struct LocalAllocTraits { + static HLOCAL InvalidValue() { + return nullptr; + } + + static void Free(HLOCAL memory) { + PLOG_IF(ERROR, LocalFree(memory) != nullptr) << "LocalFree"; + } +}; +using ScopedLocalAlloc = base::ScopedGeneric; + +// Calls AppendCommandLineArgument() for every argument in argv, then calls +// CommandLineToArgvW() to decode the string into a vector again, and compares +// the input and output. +void AppendCommandLineArgumentTest(size_t argc, const wchar_t* argv[]) { + std::wstring command_line; + for (size_t index = 0; index < argc; ++index) { + AppendCommandLineArgument(argv[index], &command_line); + } + + int test_argc; + wchar_t** test_argv = CommandLineToArgvW(command_line.c_str(), &test_argc); + + ASSERT_TRUE(test_argv) << ErrorMessage("CommandLineToArgvW"); + ScopedLocalAlloc test_argv_owner(test_argv); + ASSERT_EQ(argc, test_argc); + + for (size_t index = 0; index < argc; ++index) { + EXPECT_STREQ(argv[index], test_argv[index]) << "index " << index; + } + EXPECT_FALSE(test_argv[argc]); +} + +TEST(CommandLine, AppendCommandLineArgument) { + // Most of these test cases come from + // http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx, + // which was also a reference for the implementation of + // AppendCommandLineArgument(). + + { + SCOPED_TRACE("simple"); + + const wchar_t* kArguments[] = { + L"child.exe", + L"argument 1", + L"argument 2", + }; + AppendCommandLineArgumentTest(arraysize(kArguments), kArguments); + } + + { + SCOPED_TRACE("path with spaces"); + + const wchar_t* kArguments[] = { + L"child.exe", + L"argument1", + L"argument 2", + L"\\some\\path with\\spaces", + }; + AppendCommandLineArgumentTest(arraysize(kArguments), kArguments); + } + + { + SCOPED_TRACE("argument with embedded quotation marks"); + + const wchar_t* kArguments[] = { + L"child.exe", + L"argument1", + L"she said, \"you had me at hello\"", + L"\\some\\path with\\spaces", + }; + AppendCommandLineArgumentTest(arraysize(kArguments), kArguments); + } + + { + SCOPED_TRACE("argument with unbalanced quotation marks"); + + const wchar_t* kArguments[] = { + L"child.exe", + L"argument1", + L"argument\"2", + L"argument3", + L"argument4", + }; + AppendCommandLineArgumentTest(arraysize(kArguments), kArguments); + } + + { + SCOPED_TRACE("argument ending with backslash"); + + const wchar_t* kArguments[] = { + L"child.exe", + L"\\some\\directory with\\spaces\\", + L"argument2", + }; + AppendCommandLineArgumentTest(arraysize(kArguments), kArguments); + } + + { + SCOPED_TRACE("empty argument"); + + const wchar_t* kArguments[] = { + L"child.exe", + L"", + L"argument2", + }; + AppendCommandLineArgumentTest(arraysize(kArguments), kArguments); + } + + { + SCOPED_TRACE("funny nonprintable characters"); + + const wchar_t* kArguments[] = { + L"child.exe", + L"argument 1", + L"argument\t2", + L"argument\n3", + L"argument\v4", + L"argument\"5", + L" ", + L"\t", + L"\n", + L"\v", + L"\"", + L" x", + L"\tx", + L"\nx", + L"\vx", + L"\"x", + L"x ", + L"x\t", + L"x\n", + L"x\v", + L"x\"", + L" ", + L"\t\t", + L"\n\n", + L"\v\v", + L"\"\"", + L" \t\n\v\"", + }; + AppendCommandLineArgumentTest(arraysize(kArguments), kArguments); + } +} + +} // namespace +} // namespace test +} // namespace crashpad diff --git a/util/win/exception_handler_server.cc b/util/win/exception_handler_server.cc index 234d3149..50daa39a 100644 --- a/util/win/exception_handler_server.cc +++ b/util/win/exception_handler_server.cc @@ -234,11 +234,13 @@ class ClientData { ExceptionHandlerServer::Delegate::~Delegate() { } -ExceptionHandlerServer::ExceptionHandlerServer(const std::string& pipe_name) +ExceptionHandlerServer::ExceptionHandlerServer(const std::string& pipe_name, + bool persistent) : pipe_name_(pipe_name), port_(CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1)), clients_lock_(), - clients_() { + clients_(), + persistent_(persistent) { } ExceptionHandlerServer::~ExceptionHandlerServer() { @@ -297,11 +299,11 @@ void ExceptionHandlerServer::Run(Delegate* delegate) { // outstanding threadpool waits are complete. This is important because the // process handle can be signalled *before* the dump request is signalled. internal::ClientData* client = reinterpret_cast(key); - { - base::AutoLock lock(clients_lock_); - clients_.erase(client); - } + base::AutoLock lock(clients_lock_); + clients_.erase(client); delete client; + if (!persistent_ && clients_.empty()) + break; } // Signal to the named pipe instances that they should terminate. diff --git a/util/win/exception_handler_server.h b/util/win/exception_handler_server.h index d1243406..d4a81f16 100644 --- a/util/win/exception_handler_server.h +++ b/util/win/exception_handler_server.h @@ -64,7 +64,10 @@ class ExceptionHandlerServer { //! //! \param[in] pipe_name The name of the pipe to listen on. Must be of the //! form "\\.\pipe\". - explicit ExceptionHandlerServer(const std::string& pipe_name); + //! \param[in] persistent `true` if Run() should not return until Stop() is + //! called. If `false`, Run() will return when all clients have exited, + //! although Run() will always wait for the first client to connect. + ExceptionHandlerServer(const std::string& pipe_name, bool persistent); ~ExceptionHandlerServer(); @@ -92,6 +95,8 @@ class ExceptionHandlerServer { base::Lock clients_lock_; std::set clients_; + bool persistent_; + DISALLOW_COPY_AND_ASSIGN(ExceptionHandlerServer); }; diff --git a/util/win/exception_handler_server_test.cc b/util/win/exception_handler_server_test.cc index 9fe593b5..c1e5d7f4 100644 --- a/util/win/exception_handler_server_test.cc +++ b/util/win/exception_handler_server_test.cc @@ -83,7 +83,7 @@ class ExceptionHandlerServerTest : public testing::Test { base::StringPrintf("%08x", GetCurrentProcessId())), server_ready_(CreateEvent(nullptr, false, false, nullptr)), delegate_(server_ready_.get()), - server_(pipe_name_), + server_(pipe_name_, true), server_thread_(&server_, &delegate_) {} TestDelegate& delegate() { return delegate_; } diff --git a/util/win/process_info_test.cc b/util/win/process_info_test.cc index c997d499..0fc52a58 100644 --- a/util/win/process_info_test.cc +++ b/util/win/process_info_test.cc @@ -31,6 +31,7 @@ #include "test/win/child_launcher.h" #include "util/file/file_io.h" #include "util/misc/uuid.h" +#include "util/win/command_line.h" #include "util/win/get_function.h" #include "util/win/scoped_handle.h" From 7f939285dea16391721f79a446311b8830e43acd Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Mon, 2 Nov 2015 17:00:06 -0500 Subject: [PATCH 7/8] win: Rename CrashpadClient::SetHandler() to SetHandlerIPCPipe() In https://codereview.chromium.org/1414533006/, I'm adding a few Mac-specific SetHandler() variants, so it makes sense to name each SetHandler() variant for what it does. I'm also making it take a wstring argument, which seems like a more natural fit for what it does. There should be fewer string conversions this way. R=scottmg@chromium.org Review URL: https://codereview.chromium.org/1406993008 . --- client/crashpad_client.h | 12 ++++----- client/crashpad_client_win.cc | 25 +++++++++---------- handler/handler.gyp | 3 --- handler/win/crashy_test_program.cc | 13 +++++----- handler/win/crashy_test_z7_loader.cc | 9 +++---- handler/win/self_destroying_test_program.cc | 9 +++---- .../crashpad_snapshot_test_crashing_child.cc | 9 +++++-- ...pad_snapshot_test_dump_without_crashing.cc | 8 ++++-- util/win/exception_handler_server_test.cc | 7 +++--- 9 files changed, 48 insertions(+), 47 deletions(-) diff --git a/client/crashpad_client.h b/client/crashpad_client.h index c45f0607..d3d7e347 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -51,9 +51,6 @@ class CrashpadClient { //! send right corresponding to a receive right held by the handler process. //! The handler process runs an exception server on this port. //! - //! On Windows, SetHandler() is normally used instead since the handler is - //! started by other means. - //! //! \param[in] handler The path to a Crashpad handler executable. //! \param[in] database The path to a Crashpad database. The handler will be //! started with this path as its `--database` argument. @@ -85,10 +82,11 @@ class CrashpadClient { //! registration is done with a crash handler using the appropriate database //! and upload server. //! - //! \param[in] ipc_port The full name of the crash handler IPC port. + //! \param[in] ipc_pipe The full name of the crash handler IPC pipe. This is + //! a string of the form `"\\.\pipe\NAME"`. //! //! \return `true` on success and `false` on failure. - bool SetHandler(const std::string& ipc_port); + bool SetHandlerIPCPipe(const std::wstring& ipc_pipe); //! \brief Requests that the handler capture a dump even though there hasn't //! been a crash. @@ -101,7 +99,7 @@ class CrashpadClient { //! \brief Configures the process to direct its crashes to a Crashpad handler. //! //! The Crashpad handler must previously have been started by StartHandler() - //! or configured by SetHandler(). + //! or configured by SetHandlerIPCPipe(). //! //! On Mac OS X, this method sets the task’s exception port for `EXC_CRASH`, //! `EXC_RESOURCE`, and `EXC_GUARD` exceptions to the Mach send right obtained @@ -147,7 +145,7 @@ class CrashpadClient { #if defined(OS_MACOSX) base::mac::ScopedMachSendRight exception_port_; #elif defined(OS_WIN) - std::string ipc_port_; + std::wstring ipc_pipe_; #endif DISALLOW_COPY_AND_ASSIGN(CrashpadClient); diff --git a/client/crashpad_client_win.cc b/client/crashpad_client_win.cc index 5e7346fa..c4715ad5 100644 --- a/client/crashpad_client_win.cc +++ b/client/crashpad_client_win.cc @@ -115,7 +115,7 @@ std::wstring FormatArgumentString(const std::string& name, namespace crashpad { CrashpadClient::CrashpadClient() - : ipc_port_() { + : ipc_pipe_() { } CrashpadClient::~CrashpadClient() { @@ -127,13 +127,14 @@ bool CrashpadClient::StartHandler( const std::string& url, const std::map& annotations, const std::vector& arguments) { - DCHECK(ipc_port_.empty()); + DCHECK(ipc_pipe_.empty()); - ipc_port_ = + std::string ipc_pipe = base::StringPrintf("\\\\.\\pipe\\crashpad_%d_", GetCurrentProcessId()); for (int index = 0; index < 16; ++index) { - ipc_port_.append(1, static_cast(base::RandInt('A', 'Z'))); + ipc_pipe.append(1, static_cast(base::RandInt('A', 'Z'))); } + ipc_pipe_ = base::UTF8ToUTF16(ipc_pipe); std::wstring command_line; AppendCommandLineArgument(handler.value(), &command_line); @@ -156,8 +157,7 @@ bool CrashpadClient::StartHandler( base::UTF8ToUTF16(kv.first + '=' + kv.second)), &command_line); } - AppendCommandLineArgument(FormatArgumentString("pipe-name", - base::UTF8ToUTF16(ipc_port_)), + AppendCommandLineArgument(FormatArgumentString("pipe-name", ipc_pipe_), &command_line); STARTUPINFO startup_info = {}; @@ -191,17 +191,17 @@ bool CrashpadClient::StartHandler( return true; } -bool CrashpadClient::SetHandler(const std::string& ipc_port) { - DCHECK(ipc_port_.empty()); - DCHECK(!ipc_port.empty()); +bool CrashpadClient::SetHandlerIPCPipe(const std::wstring& ipc_pipe) { + DCHECK(ipc_pipe_.empty()); + DCHECK(!ipc_pipe.empty()); - ipc_port_ = ipc_port; + ipc_pipe_ = ipc_pipe; return true; } bool CrashpadClient::UseHandler() { - DCHECK(!ipc_port_.empty()); + DCHECK(!ipc_pipe_.empty()); DCHECK_EQ(g_signal_exception, INVALID_HANDLE_VALUE); DCHECK_EQ(g_signal_non_crash_dump, INVALID_HANDLE_VALUE); DCHECK_EQ(g_non_crash_dump_done, INVALID_HANDLE_VALUE); @@ -232,8 +232,7 @@ bool CrashpadClient::UseHandler() { ServerToClientMessage response = {0}; - if (!SendToCrashHandlerServer( - base::UTF8ToUTF16(ipc_port_), message, &response)) { + if (!SendToCrashHandlerServer(ipc_pipe_, message, &response)) { return false; } diff --git a/handler/handler.gyp b/handler/handler.gyp index 398c5404..20df3d73 100644 --- a/handler/handler.gyp +++ b/handler/handler.gyp @@ -81,7 +81,6 @@ 'dependencies': [ '../client/client.gyp:crashpad_client', '../third_party/mini_chromium/mini_chromium.gyp:base', - '../tools/tools.gyp:crashpad_tool_support', '../util/util.gyp:crashpad_util', ], 'include_dirs': [ @@ -99,7 +98,6 @@ '../compat/compat.gyp:crashpad_compat', '../snapshot/snapshot.gyp:crashpad_snapshot', '../third_party/mini_chromium/mini_chromium.gyp:base', - '../tools/tools.gyp:crashpad_tool_support', '../util/util.gyp:crashpad_util', ], 'include_dirs': [ @@ -121,7 +119,6 @@ '../client/client.gyp:crashpad_client', '../test/test.gyp:crashpad_test', '../third_party/mini_chromium/mini_chromium.gyp:base', - '../tools/tools.gyp:crashpad_tool_support', ], 'include_dirs': [ '..', diff --git a/handler/win/crashy_test_program.cc b/handler/win/crashy_test_program.cc index 15c7b613..7d61c256 100644 --- a/handler/win/crashy_test_program.cc +++ b/handler/win/crashy_test_program.cc @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include #include @@ -28,9 +29,7 @@ #include "base/basictypes.h" #include "base/files/file_path.h" #include "base/logging.h" -#include "base/strings/utf_string_conversions.h" #include "client/crashpad_client.h" -#include "tools/tool_support.h" #include "util/win/critical_section_with_debug_info.h" #include "util/win/get_function.h" @@ -94,17 +93,17 @@ void SomeCrashyFunction() { *foo = 42; } -int CrashyMain(int argc, char* argv[]) { +int CrashyMain(int argc, wchar_t* argv[]) { CrashpadClient client; if (argc == 2) { - if (!client.SetHandler(argv[1])) { + if (!client.SetHandlerIPCPipe(argv[1])) { LOG(ERROR) << "SetHandler"; return EXIT_FAILURE; } } else if (argc == 3) { - if (!client.StartHandler(base::FilePath(base::UTF8ToUTF16(argv[1])), - base::FilePath(base::UTF8ToUTF16(argv[2])), + if (!client.StartHandler(base::FilePath(argv[1]), + base::FilePath(argv[2]), std::string(), std::map(), std::vector())) { @@ -138,5 +137,5 @@ int CrashyMain(int argc, char* argv[]) { } // namespace crashpad int wmain(int argc, wchar_t* argv[]) { - return crashpad::ToolSupport::Wmain(argc, argv, crashpad::CrashyMain); + return crashpad::CrashyMain(argc, argv); } diff --git a/handler/win/crashy_test_z7_loader.cc b/handler/win/crashy_test_z7_loader.cc index 4788dbe9..284afa86 100644 --- a/handler/win/crashy_test_z7_loader.cc +++ b/handler/win/crashy_test_z7_loader.cc @@ -20,7 +20,6 @@ #include "build/build_config.h" #include "client/crashpad_client.h" #include "test/paths.h" -#include "tools/tool_support.h" #if !defined(ARCH_CPU_X86) #error This test is only supported on x86. @@ -29,14 +28,14 @@ namespace crashpad { namespace { -int CrashyLoadZ7Main(int argc, char* argv[]) { +int CrashyLoadZ7Main(int argc, wchar_t* argv[]) { if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, "Usage: %ls \n", argv[0]); return EXIT_FAILURE; } CrashpadClient client; - if (!client.SetHandler(argv[1])) { + if (!client.SetHandlerIPCPipe(argv[1])) { LOG(ERROR) << "SetHandler"; return EXIT_FAILURE; } @@ -69,5 +68,5 @@ int CrashyLoadZ7Main(int argc, char* argv[]) { } // namespace crashpad int wmain(int argc, wchar_t* argv[]) { - return crashpad::ToolSupport::Wmain(argc, argv, crashpad::CrashyLoadZ7Main); + return crashpad::CrashyLoadZ7Main(argc, argv); } diff --git a/handler/win/self_destroying_test_program.cc b/handler/win/self_destroying_test_program.cc index d00fa476..ac358ef0 100644 --- a/handler/win/self_destroying_test_program.cc +++ b/handler/win/self_destroying_test_program.cc @@ -21,7 +21,6 @@ #include "base/strings/stringprintf.h" #include "client/crashpad_client.h" #include "snapshot/win/process_reader_win.h" -#include "tools/tool_support.h" namespace crashpad { namespace { @@ -65,14 +64,14 @@ bool FreeOwnStackAndBreak() { return true; } -int SelfDestroyingMain(int argc, char* argv[]) { +int SelfDestroyingMain(int argc, wchar_t* argv[]) { if (argc != 2) { - fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, "Usage: %ls \n", argv[0]); return EXIT_FAILURE; } CrashpadClient client; - if (!client.SetHandler(argv[1])) { + if (!client.SetHandlerIPCPipe(argv[1])) { LOG(ERROR) << "SetHandler"; return EXIT_FAILURE; } @@ -93,5 +92,5 @@ int SelfDestroyingMain(int argc, char* argv[]) { } // namespace crashpad int wmain(int argc, wchar_t* argv[]) { - return crashpad::ToolSupport::Wmain(argc, argv, crashpad::SelfDestroyingMain); + return crashpad::SelfDestroyingMain(argc, argv); } diff --git a/snapshot/win/crashpad_snapshot_test_crashing_child.cc b/snapshot/win/crashpad_snapshot_test_crashing_child.cc index 7f4b8e5b..d4023c65 100644 --- a/snapshot/win/crashpad_snapshot_test_crashing_child.cc +++ b/snapshot/win/crashpad_snapshot_test_crashing_child.cc @@ -14,20 +14,25 @@ #include +#include "base/files/file_path.h" #include "base/logging.h" #include "client/crashpad_client.h" #include "util/file/file_io.h" #include "util/win/address_types.h" +namespace { + __declspec(noinline) crashpad::WinVMAddress CurrentAddress() { return reinterpret_cast(_ReturnAddress()); } -int main(int argc, char* argv[]) { +} // namespace + +int wmain(int argc, wchar_t* argv[]) { CHECK_EQ(argc, 2); crashpad::CrashpadClient client; - CHECK(client.SetHandler(argv[1])); + CHECK(client.SetHandlerIPCPipe(argv[1])); CHECK(client.UseHandler()); HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); diff --git a/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc b/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc index 1ecc18ed..cf5ab950 100644 --- a/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc +++ b/snapshot/win/crashpad_snapshot_test_dump_without_crashing.cc @@ -20,15 +20,19 @@ #include "util/file/file_io.h" #include "util/win/address_types.h" +namespace { + __declspec(noinline) crashpad::WinVMAddress CurrentAddress() { return reinterpret_cast(_ReturnAddress()); } -int main(int argc, char* argv[]) { +} // namespace + +int wmain(int argc, wchar_t* argv[]) { CHECK_EQ(argc, 2); crashpad::CrashpadClient client; - CHECK(client.SetHandler(argv[1])); + CHECK(client.SetHandlerIPCPipe(argv[1])); CHECK(client.UseHandler()); HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); diff --git a/util/win/exception_handler_server_test.cc b/util/win/exception_handler_server_test.cc index c1e5d7f4..8cfbb45f 100644 --- a/util/win/exception_handler_server_test.cc +++ b/util/win/exception_handler_server_test.cc @@ -21,6 +21,7 @@ #include "base/basictypes.h" #include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" #include "client/crashpad_client.h" #include "gtest/gtest.h" #include "test/win/win_child_process.h" @@ -134,7 +135,7 @@ TEST_F(ExceptionHandlerServerTest, StopWhileConnected) { &server(), &server_thread()); ASSERT_NO_FATAL_FAILURE(delegate().WaitForStart()); CrashpadClient client; - client.SetHandler(pipe_name()); // Connect to server. + client.SetHandlerIPCPipe(base::UTF8ToUTF16(pipe_name())); // Leaving this scope causes the server to be stopped, while the connection // is still open. } @@ -161,9 +162,9 @@ class TestClient final : public WinChildProcess { private: int Run() override { - std::string pipe_name = ReadString(ReadPipeHandle()); + std::wstring pipe_name = base::UTF8ToUTF16(ReadString(ReadPipeHandle())); CrashpadClient client; - if (!client.SetHandler(pipe_name)) { + if (!client.SetHandlerIPCPipe(pipe_name)) { ADD_FAILURE(); return EXIT_FAILURE; } From a30db914afae7904237b9ccd9576828808868f34 Mon Sep 17 00:00:00 2001 From: Mark Mentovai Date: Mon, 2 Nov 2015 23:15:22 -0500 Subject: [PATCH 8/8] win: Add CrashpadClient::GetHandlerIPCPipe() For multiprocess architectures, this method allows the pipe used for registration to be obtained from CrashpadHandler, even when CrashpadHandler chooses its own name. This may happen if the handler is not running on a well-known pipe name but was instead started by CrashpadHandler::StartHandler(). If Chrome uses this interface, for example, the browser process will need to call CrashpadClient::GetHandlerIPCPipe() and pass the pipe name to its child processes. R=scottmg@chromium.org Review URL: https://codereview.chromium.org/1427163004 . --- client/crashpad_client.h | 21 ++++++++++++++++----- client/crashpad_client_win.cc | 5 +++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/client/crashpad_client.h b/client/crashpad_client.h index d3d7e347..18b7b131 100644 --- a/client/crashpad_client.h +++ b/client/crashpad_client.h @@ -72,22 +72,33 @@ class CrashpadClient { const std::vector& arguments); #if defined(OS_WIN) || DOXYGEN - //! \brief Sets the IPC port of a presumably-running Crashpad handler process + //! \brief Sets the IPC pipe of a presumably-running Crashpad handler process //! which was started with StartHandler() or by other compatible means //! and does an IPC message exchange to register this process with the //! handler. However, just like StartHandler(), crashes are not serviced //! until UseHandler() is called. //! - //! The IPC port name (somehow) encodes enough information so that - //! registration is done with a crash handler using the appropriate database - //! and upload server. - //! //! \param[in] ipc_pipe The full name of the crash handler IPC pipe. This is //! a string of the form `"\\.\pipe\NAME"`. //! //! \return `true` on success and `false` on failure. bool SetHandlerIPCPipe(const std::wstring& ipc_pipe); + //! \brief Retrieves the IPC pipe name used to register with the Crashpad + //! handler. + //! + //! This method retrieves the IPC pipe name set by SetHandlerIPCPipe(), or a + //! suitable IPC pipe name chosen by StartHandler(). It is intended to be used + //! to obtain the IPC pipe name so that it may be passed to other processes, + //! so that they may register with an existing Crashpad handler by calling + //! SetHandlerIPCPipe(). + //! + //! This method is only defined on Windows. + //! + //! \return The full name of the crash handler IPC pipe, a string of the form + //! `"\\.\pipe\NAME"`. + std::wstring GetHandlerIPCPipe() const; + //! \brief Requests that the handler capture a dump even though there hasn't //! been a crash. //! diff --git a/client/crashpad_client_win.cc b/client/crashpad_client_win.cc index c4715ad5..3f23dde1 100644 --- a/client/crashpad_client_win.cc +++ b/client/crashpad_client_win.cc @@ -200,6 +200,11 @@ bool CrashpadClient::SetHandlerIPCPipe(const std::wstring& ipc_pipe) { return true; } +std::wstring CrashpadClient::GetHandlerIPCPipe() const { + DCHECK(!ipc_pipe_.empty()); + return ipc_pipe_; +} + bool CrashpadClient::UseHandler() { DCHECK(!ipc_pipe_.empty()); DCHECK_EQ(g_signal_exception, INVALID_HANDLE_VALUE);