// Copyright 2014 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 "test/multiprocess.h" #include #include #include #include #include #include #include "base/auto_reset.h" #include "base/check_op.h" #include "base/files/scoped_file.h" #include "base/posix/eintr_wrapper.h" #include "base/strings/stringprintf.h" #include "build/build_config.h" #include "gtest/gtest.h" #include "test/errors.h" #include "util/misc/scoped_forbid_return.h" #include "util/posix/signals.h" #if BUILDFLAG(IS_APPLE) #include "test/mac/exception_swallower.h" #endif namespace crashpad { namespace test { namespace internal { struct MultiprocessInfo { MultiprocessInfo() : pipe_c2p_read(-1), pipe_c2p_write(-1), pipe_p2c_read(-1), pipe_p2c_write(-1), child_pid(0) {} base::ScopedFD pipe_c2p_read; // child to parent base::ScopedFD pipe_c2p_write; // child to parent base::ScopedFD pipe_p2c_read; // parent to child base::ScopedFD pipe_p2c_write; // parent to child pid_t child_pid; // valid only in parent }; } // namespace internal Multiprocess::Multiprocess() : info_(nullptr), code_(EXIT_SUCCESS), reason_(kTerminationNormal) { } void Multiprocess::Run() { ASSERT_EQ(info_, nullptr); std::unique_ptr info( new internal::MultiprocessInfo); base::AutoReset reset_info(&info_, info.get()); ASSERT_NO_FATAL_FAILURE(PreFork()); #if BUILDFLAG(IS_APPLE) // If the child is expected to crash, set up an exception swallower to swallow // the exception instead of allowing it to be seen by the system’s crash // reporter. std::unique_ptr exception_swallower; if (reason_ == kTerminationSignal && Signals::IsCrashSignal(code_)) { exception_swallower.reset(new ExceptionSwallower()); } #endif // BUILDFLAG(IS_APPLE) pid_t pid = fork(); ASSERT_GE(pid, 0) << ErrnoMessage("fork"); if (pid > 0) { info_->child_pid = pid; RunParent(); // Waiting for the child happens here instead of in RunParent() because even // if RunParent() returns early due to a Google Test fatal assertion // failure, the child should still be reaped. // This will make the parent hang up on the child as much as would be // visible from the child’s perspective. The child’s side of the pipe will // be broken, the child’s remote port will become a dead name, and an // attempt by the child to look up the service will fail. If this weren’t // done, the child might hang while waiting for a parent that has already // triggered a fatal assertion failure to do something. info.reset(); info_ = nullptr; int status; pid_t wait_pid = HANDLE_EINTR(waitpid(pid, &status, 0)); ASSERT_EQ(wait_pid, pid) << ErrnoMessage("waitpid"); TerminationReason reason; int code; std::string message; if (WIFEXITED(status)) { reason = kTerminationNormal; code = WEXITSTATUS(status); message = base::StringPrintf("Child exited with code %d", code); } else if (WIFSIGNALED(status)) { reason = kTerminationSignal; code = WTERMSIG(status); message = base::StringPrintf("Child terminated by signal %d (%s)%s", code, strsignal(code), WCOREDUMP(status) ? " (core dumped)" : ""); } else { FAIL() << base::StringPrintf("Unknown termination reason 0x%x", status); } if (reason_ == kTerminationNormal) { message += base::StringPrintf(", expected exit with code %d", code_); } else if (reason_ == kTerminationSignal) { message += base::StringPrintf(", expected termination by signal %d (%s)", code_, strsignal(code_)); } if (reason != reason_ || code != code_) { ADD_FAILURE() << message; } } else { #if BUILDFLAG(IS_APPLE) if (exception_swallower.get()) { ExceptionSwallower::SwallowExceptions(); } #elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID) if (reason_ == kTerminationSignal && Signals::IsCrashSignal(code_)) { Signals::InstallDefaultHandler(code_); } #endif // BUILDFLAG(IS_APPLE) RunChild(); } } void Multiprocess::SetExpectedChildTermination(TerminationReason reason, ReturnCodeType code) { EXPECT_EQ(info_, nullptr) << "SetExpectedChildTermination() must be called before Run()"; reason_ = reason; code_ = code; } void Multiprocess::SetExpectedChildTerminationBuiltinTrap() { #if defined(ARCH_CPU_ARM64) || defined(ARCH_CPU_MIPS_FAMILY) SetExpectedChildTermination(kTerminationSignal, SIGTRAP); #else SetExpectedChildTermination(kTerminationSignal, SIGILL); #endif } Multiprocess::~Multiprocess() { } void Multiprocess::PreFork() { int pipe_fds_c2p[2]; int rv = pipe(pipe_fds_c2p); ASSERT_EQ(rv, 0) << ErrnoMessage("pipe"); info_->pipe_c2p_read.reset(pipe_fds_c2p[0]); info_->pipe_c2p_write.reset(pipe_fds_c2p[1]); int pipe_fds_p2c[2]; rv = pipe(pipe_fds_p2c); ASSERT_EQ(rv, 0) << ErrnoMessage("pipe"); info_->pipe_p2c_read.reset(pipe_fds_p2c[0]); info_->pipe_p2c_write.reset(pipe_fds_p2c[1]); } pid_t Multiprocess::ChildPID() const { EXPECT_NE(info_->child_pid, 0); return info_->child_pid; } FileHandle Multiprocess::ReadPipeHandle() const { int fd = info_->child_pid ? info_->pipe_c2p_read.get() : info_->pipe_p2c_read.get(); CHECK_NE(fd, -1); return fd; } FileHandle Multiprocess::WritePipeHandle() const { int fd = info_->child_pid ? info_->pipe_p2c_write.get() : info_->pipe_c2p_write.get(); CHECK_NE(fd, -1); return fd; } void Multiprocess::CloseReadPipe() { if (info_->child_pid) { info_->pipe_c2p_read.reset(); } else { info_->pipe_p2c_read.reset(); } } void Multiprocess::CloseWritePipe() { if (info_->child_pid) { info_->pipe_p2c_write.reset(); } else { info_->pipe_c2p_write.reset(); } } void Multiprocess::RunParent() { // The parent uses the read end of c2p and the write end of p2c. info_->pipe_c2p_write.reset(); info_->pipe_p2c_read.reset(); MultiprocessParent(); info_->pipe_c2p_read.reset(); info_->pipe_p2c_write.reset(); } void Multiprocess::RunChild() { ScopedForbidReturn forbid_return; // The child uses the write end of c2p and the read end of p2c. info_->pipe_c2p_read.reset(); info_->pipe_p2c_write.reset(); MultiprocessChild(); info_->pipe_c2p_write.reset(); info_->pipe_p2c_read.reset(); if (testing::Test::HasFailure()) { // Trigger the ScopedForbidReturn destructor. return; } // In a forked child, exit() is unsafe. Use _exit() instead. _exit(EXIT_SUCCESS); } } // namespace test } // namespace crashpad