// 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 "test/win/win_child_process.h" #include #include #include #include #include "base/logging.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "gtest/gtest.h" #include "test/test_paths.h" #include "util/stdlib/string_number_conversion.h" #include "util/string/split_string.h" #include "util/win/handle.h" #include "util/win/scoped_local_alloc.h" namespace crashpad { namespace test { namespace { const char kIsMultiprocessChild[] = "--is-multiprocess-child"; bool GetSwitch(const char* switch_name, std::string* value) { int num_args; wchar_t** args = CommandLineToArgvW(GetCommandLine(), &num_args); ScopedLocalAlloc scoped_args(args); // Take ownership. if (!args) { PLOG(FATAL) << "CommandLineToArgvW"; return false; } std::string switch_name_with_equals(switch_name); switch_name_with_equals += "="; for (int i = 1; i < num_args; ++i) { const wchar_t* arg = args[i]; std::string arg_as_utf8 = base::UTF16ToUTF8(arg); if (arg_as_utf8.compare( 0, switch_name_with_equals.size(), switch_name_with_equals) == 0) { if (value) *value = arg_as_utf8.substr(switch_name_with_equals.size()); return true; } } return false; } ScopedKernelHANDLE LaunchCommandLine(wchar_t* command_line) { STARTUPINFO startup_info = {0}; startup_info.cb = sizeof(startup_info); startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); startup_info.dwFlags = STARTF_USESTDHANDLES; PROCESS_INFORMATION process_info; if (!CreateProcess(TestPaths::Executable().value().c_str(), &command_line[0], // This cannot be constant, per MSDN. nullptr, nullptr, true, // Inherit handles. 0, nullptr, nullptr, &startup_info, &process_info)) { PLOG(ERROR) << "CreateProcess"; return ScopedKernelHANDLE(); } if (!CloseHandle(process_info.hThread)) { PLOG(ERROR) << "CloseHandle"; if (!CloseHandle(process_info.hProcess)) PLOG(ERROR) << "CloseHandle"; return ScopedKernelHANDLE(); } return ScopedKernelHANDLE(process_info.hProcess); } bool UnsetHandleInheritance(HANDLE handle) { if (!SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0)) { PLOG(ERROR) << "SetHandleInformation"; ADD_FAILURE() << "SetHandleInformation"; return false; } return true; } bool CreateInheritablePipe(ScopedFileHANDLE* read_handle, bool read_inheritable, ScopedFileHANDLE* write_handle, bool write_inheritable) { // Mark both sides as inheritable via the SECURITY_ATTRIBUTES and use // SetHandleInformation as necessary to restrict inheritance of either side. SECURITY_ATTRIBUTES security_attributes = {0}; security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); security_attributes.bInheritHandle = true; HANDLE read, write; BOOL result = CreatePipe(&read, &write, &security_attributes, 0); if (!result) { PLOG(ERROR) << "CreatePipe"; ADD_FAILURE() << "CreatePipe failed"; return false; } ScopedFileHANDLE temp_read(read); ScopedFileHANDLE temp_write(write); if (!read_inheritable && !UnsetHandleInheritance(temp_read.get())) return false; if (!write_inheritable && !UnsetHandleInheritance(temp_write.get())) return false; *read_handle = std::move(temp_read); *write_handle = std::move(temp_write); return true; } } // namespace WinChildProcess::WinChildProcess() { std::string switch_value; CHECK(GetSwitch(kIsMultiprocessChild, &switch_value)); // Set up the handles we inherited from the parent. These are inherited from // the parent and so are open and have the same value as in the parent. The // values are passed to the child on the command line. std::string left, right; CHECK(SplitStringFirst(switch_value, '|', &left, &right)); // left and right were formatted as 0x%x, so they need to be converted as // unsigned ints. unsigned int write, read; CHECK(StringToNumber(left, &write)); CHECK(StringToNumber(right, &read)); pipe_write_.reset(IntToHandle(write)); pipe_read_.reset(IntToHandle(read)); // Notify the parent that it's OK to proceed. We only need to wait to get to // the process entry point, but this is the easiest place we can notify. char c = ' '; CheckedWriteFile(WritePipeHandle(), &c, sizeof(c)); } // static bool WinChildProcess::IsChildProcess() { return GetSwitch(kIsMultiprocessChild, nullptr); } // static std::unique_ptr WinChildProcess::Launch() { // Make pipes for child-to-parent and parent-to-child communication. std::unique_ptr handles_for_parent(new Handles); ScopedFileHANDLE read_for_child; ScopedFileHANDLE write_for_child; if (!CreateInheritablePipe( &handles_for_parent->read, false, &write_for_child, true)) { return std::unique_ptr(); } if (!CreateInheritablePipe( &read_for_child, true, &handles_for_parent->write, false)) { return std::unique_ptr(); } // Build a command line for the child process that tells it only to run the // current test, and to pass down the values of the pipe handles. const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); std::wstring command_line = TestPaths::Executable().value() + L" " + base::UTF8ToUTF16(base::StringPrintf("--gtest_filter=%s.%s %s=0x%x|0x%x", test_info->test_case_name(), test_info->name(), kIsMultiprocessChild, HandleToInt(write_for_child.get()), HandleToInt(read_for_child.get()))); // Command-line buffer cannot be constant, per CreateProcess signature. handles_for_parent->process = LaunchCommandLine(&command_line[0]); if (!handles_for_parent->process.is_valid()) return std::unique_ptr(); // Block until the child process has launched. CreateProcess() returns // immediately, and test code expects process initialization to have // completed so it can, for example, read the process memory. char c; if (!LoggingReadFileExactly(handles_for_parent->read.get(), &c, sizeof(c))) { ADD_FAILURE() << "LoggedReadFile"; return std::unique_ptr(); } if (c != ' ') { ADD_FAILURE() << "invalid data read from child"; return std::unique_ptr(); } return std::move(handles_for_parent); } FileHandle WinChildProcess::ReadPipeHandle() const { return pipe_read_.get(); } FileHandle WinChildProcess::WritePipeHandle() const { return pipe_write_.get(); } void WinChildProcess::CloseReadPipe() { pipe_read_.reset(); } void WinChildProcess::CloseWritePipe() { pipe_write_.reset(); } } // namespace test } // namespace crashpad