blob: 14299e6bc4ea8374247edec2af1ce74146f8c54d [file] [log] [blame]
Yavor Goulishev9c08e842020-04-29 14:03:33 -07001// Copyright 2015 The Crashpad Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#include "test/win/win_child_process.h"
16
17#include <windows.h>
18#include <shellapi.h>
19
20#include <string>
21#include <utility>
22
23#include "base/logging.h"
24#include "base/strings/stringprintf.h"
25#include "base/strings/utf_string_conversions.h"
26#include "gtest/gtest.h"
27#include "test/test_paths.h"
28#include "util/stdlib/string_number_conversion.h"
29#include "util/string/split_string.h"
30#include "util/win/handle.h"
31#include "util/win/scoped_local_alloc.h"
32
33namespace crashpad {
34namespace test {
35
36namespace {
37
38constexpr char kIsMultiprocessChild[] = "--is-multiprocess-child";
39
40bool GetSwitch(const char* switch_name, std::string* value) {
41 int num_args;
42 wchar_t** args = CommandLineToArgvW(GetCommandLine(), &num_args);
43 ScopedLocalAlloc scoped_args(args); // Take ownership.
44 if (!args) {
45 PLOG(FATAL) << "CommandLineToArgvW";
46 return false;
47 }
48
49 std::string switch_name_with_equals(switch_name);
50 switch_name_with_equals += "=";
51 for (int i = 1; i < num_args; ++i) {
52 const wchar_t* arg = args[i];
53 std::string arg_as_utf8 = base::UTF16ToUTF8(arg);
54 if (arg_as_utf8.compare(
55 0, switch_name_with_equals.size(), switch_name_with_equals) == 0) {
56 if (value)
57 *value = arg_as_utf8.substr(switch_name_with_equals.size());
58 return true;
59 }
60 }
61
62 return false;
63}
64
65ScopedKernelHANDLE LaunchCommandLine(wchar_t* command_line) {
66 STARTUPINFO startup_info = {0};
67 startup_info.cb = sizeof(startup_info);
68 startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
69 startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
70 startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
71 startup_info.dwFlags = STARTF_USESTDHANDLES;
72 PROCESS_INFORMATION process_info;
73 if (!CreateProcess(TestPaths::Executable().value().c_str(),
74 &command_line[0], // This cannot be constant, per MSDN.
75 nullptr,
76 nullptr,
77 true, // Inherit handles.
78 0,
79 nullptr,
80 nullptr,
81 &startup_info,
82 &process_info)) {
83 PLOG(ERROR) << "CreateProcess";
84 return ScopedKernelHANDLE();
85 }
86 if (!CloseHandle(process_info.hThread)) {
87 PLOG(ERROR) << "CloseHandle";
88 if (!CloseHandle(process_info.hProcess))
89 PLOG(ERROR) << "CloseHandle";
90 return ScopedKernelHANDLE();
91 }
92 return ScopedKernelHANDLE(process_info.hProcess);
93}
94
95bool UnsetHandleInheritance(HANDLE handle) {
96 if (!SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0)) {
97 PLOG(ERROR) << "SetHandleInformation";
98 ADD_FAILURE() << "SetHandleInformation";
99 return false;
100 }
101 return true;
102}
103
104bool CreateInheritablePipe(ScopedFileHANDLE* read_handle,
105 bool read_inheritable,
106 ScopedFileHANDLE* write_handle,
107 bool write_inheritable) {
108 // Mark both sides as inheritable via the SECURITY_ATTRIBUTES and use
109 // SetHandleInformation as necessary to restrict inheritance of either side.
110 SECURITY_ATTRIBUTES security_attributes = {0};
111 security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES);
112 security_attributes.bInheritHandle = true;
113
114 HANDLE read, write;
115 BOOL result = CreatePipe(&read, &write, &security_attributes, 0);
116 if (!result) {
117 PLOG(ERROR) << "CreatePipe";
118 ADD_FAILURE() << "CreatePipe failed";
119 return false;
120 }
121 ScopedFileHANDLE temp_read(read);
122 ScopedFileHANDLE temp_write(write);
123
124 if (!read_inheritable && !UnsetHandleInheritance(temp_read.get()))
125 return false;
126 if (!write_inheritable && !UnsetHandleInheritance(temp_write.get()))
127 return false;
128
129 *read_handle = std::move(temp_read);
130 *write_handle = std::move(temp_write);
131
132 return true;
133}
134
135} // namespace
136
137WinChildProcess::WinChildProcess() {
138 std::string switch_value;
139 CHECK(GetSwitch(kIsMultiprocessChild, &switch_value));
140
141 // Set up the handles we inherited from the parent. These are inherited from
142 // the parent and so are open and have the same value as in the parent. The
143 // values are passed to the child on the command line.
144 std::string left, right;
145 CHECK(SplitStringFirst(switch_value, '|', &left, &right));
146
147 // left and right were formatted as 0x%x, so they need to be converted as
148 // unsigned ints.
149 unsigned int write, read;
150 CHECK(StringToNumber(left, &write));
151 CHECK(StringToNumber(right, &read));
152
153 pipe_write_.reset(IntToHandle(write));
154 pipe_read_.reset(IntToHandle(read));
155
156 // Notify the parent that it's OK to proceed. We only need to wait to get to
157 // the process entry point, but this is the easiest place we can notify.
158 char c = ' ';
159 CheckedWriteFile(WritePipeHandle(), &c, sizeof(c));
160}
161
162// static
163bool WinChildProcess::IsChildProcess() {
164 return GetSwitch(kIsMultiprocessChild, nullptr);
165}
166
167// static
168std::unique_ptr<WinChildProcess::Handles> WinChildProcess::Launch() {
169 // Make pipes for child-to-parent and parent-to-child communication.
170 std::unique_ptr<Handles> handles_for_parent(new Handles);
171 ScopedFileHANDLE read_for_child;
172 ScopedFileHANDLE write_for_child;
173
174 if (!CreateInheritablePipe(
175 &handles_for_parent->read, false, &write_for_child, true)) {
176 return std::unique_ptr<Handles>();
177 }
178
179 if (!CreateInheritablePipe(
180 &read_for_child, true, &handles_for_parent->write, false)) {
181 return std::unique_ptr<Handles>();
182 }
183
184 // Build a command line for the child process that tells it only to run the
185 // current test, and to pass down the values of the pipe handles. Use
186 // --gtest_also_run_disabled_tests because the test may be DISABLED_, but if
187 // it managed to run in the parent, disabled tests must be running.
188 const ::testing::TestInfo* const test_info =
189 ::testing::UnitTest::GetInstance()->current_test_info();
190 std::wstring command_line =
191 TestPaths::Executable().value() +
192 base::UTF8ToUTF16(base::StringPrintf(
193 " --gtest_filter=%s.%s %s=0x%x|0x%x --gtest_also_run_disabled_tests",
194 test_info->test_case_name(),
195 test_info->name(),
196 kIsMultiprocessChild,
197 HandleToInt(write_for_child.get()),
198 HandleToInt(read_for_child.get())));
199
200 // Command-line buffer cannot be constant, per CreateProcess signature.
201 handles_for_parent->process = LaunchCommandLine(&command_line[0]);
202 if (!handles_for_parent->process.is_valid())
203 return std::unique_ptr<Handles>();
204
205 // Block until the child process has launched. CreateProcess() returns
206 // immediately, and test code expects process initialization to have
207 // completed so it can, for example, read the process memory.
208 char c;
209 if (!LoggingReadFileExactly(handles_for_parent->read.get(), &c, sizeof(c))) {
210 ADD_FAILURE() << "LoggedReadFile";
211 return std::unique_ptr<Handles>();
212 }
213
214 if (c != ' ') {
215 ADD_FAILURE() << "invalid data read from child";
216 return std::unique_ptr<Handles>();
217 }
218
219 return handles_for_parent;
220}
221
222FileHandle WinChildProcess::ReadPipeHandle() const {
223 return pipe_read_.get();
224}
225
226FileHandle WinChildProcess::WritePipeHandle() const {
227 return pipe_write_.get();
228}
229
230void WinChildProcess::CloseReadPipe() {
231 pipe_read_.reset();
232}
233
234void WinChildProcess::CloseWritePipe() {
235 pipe_write_.reset();
236}
237
238} // namespace test
239} // namespace crashpad