2022-04-02 01:21:55 +00:00
|
|
|
|
// Copyright 2014 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/multiprocess.h"
|
|
|
|
|
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
|
|
#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"
|
|
|
|
|
|
2022-08-16 00:48:53 +00:00
|
|
|
|
#if BUILDFLAG(IS_APPLE)
|
2022-04-02 01:21:55 +00:00
|
|
|
|
#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<internal::MultiprocessInfo> info(
|
|
|
|
|
new internal::MultiprocessInfo);
|
|
|
|
|
base::AutoReset<internal::MultiprocessInfo*> reset_info(&info_, info.get());
|
|
|
|
|
|
|
|
|
|
ASSERT_NO_FATAL_FAILURE(PreFork());
|
|
|
|
|
|
2022-08-16 00:48:53 +00:00
|
|
|
|
#if BUILDFLAG(IS_APPLE)
|
2022-04-02 01:21:55 +00:00
|
|
|
|
// 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<ExceptionSwallower> exception_swallower;
|
|
|
|
|
if (reason_ == kTerminationSignal && Signals::IsCrashSignal(code_)) {
|
|
|
|
|
exception_swallower.reset(new ExceptionSwallower());
|
|
|
|
|
}
|
2022-08-16 00:48:53 +00:00
|
|
|
|
#endif // BUILDFLAG(IS_APPLE)
|
2022-04-02 01:21:55 +00:00
|
|
|
|
|
|
|
|
|
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 {
|
2022-08-16 00:48:53 +00:00
|
|
|
|
#if BUILDFLAG(IS_APPLE)
|
2022-04-02 01:21:55 +00:00
|
|
|
|
if (exception_swallower.get()) {
|
|
|
|
|
ExceptionSwallower::SwallowExceptions();
|
|
|
|
|
}
|
2022-08-16 00:48:53 +00:00
|
|
|
|
#elif BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_ANDROID)
|
2022-04-02 01:21:55 +00:00
|
|
|
|
if (reason_ == kTerminationSignal && Signals::IsCrashSignal(code_)) {
|
|
|
|
|
Signals::InstallDefaultHandler(code_);
|
|
|
|
|
}
|
2022-08-16 00:48:53 +00:00
|
|
|
|
#endif // BUILDFLAG(IS_APPLE)
|
2022-04-02 01:21:55 +00:00
|
|
|
|
|
|
|
|
|
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
|