llvm-project/lldb/source/Host/posix/ProcessLauncherPosixFork.cpp

297 lines
9.1 KiB
C++

//===-- ProcessLauncherPosixFork.cpp --------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "lldb/Host/posix/ProcessLauncherPosixFork.h"
#include "lldb/Host/Host.h"
#include "lldb/Host/HostProcess.h"
#include "lldb/Host/Pipe.h"
#include "lldb/Host/ProcessLaunchInfo.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/Log.h"
#include "llvm/Support/Errno.h"
#include "llvm/Support/FileSystem.h"
#include <climits>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <unistd.h>
#include <sstream>
#include <csignal>
#ifdef __ANDROID__
#include <android/api-level.h>
#define PT_TRACE_ME PTRACE_TRACEME
#endif
#if defined(__ANDROID_API__) && __ANDROID_API__ < 15
#include <linux/personality.h>
#elif defined(__linux__)
#include <sys/personality.h>
#endif
using namespace lldb;
using namespace lldb_private;
// Begin code running in the child process
// NB: This code needs to be async-signal safe, since we're invoking fork from
// multithreaded contexts.
static void write_string(int error_fd, const char *str) {
int r = write(error_fd, str, strlen(str));
(void)r;
}
[[noreturn]] static void ExitWithError(int error_fd,
const char *operation) {
int err = errno;
write_string(error_fd, operation);
write_string(error_fd, " failed: ");
// strerror is not guaranteed to be async-signal safe, but it usually is.
write_string(error_fd, strerror(err));
_exit(1);
}
static void DisableASLR(int error_fd) {
#if defined(__linux__)
const unsigned long personality_get_current = 0xffffffff;
int value = personality(personality_get_current);
if (value == -1)
ExitWithError(error_fd, "personality get");
value = personality(ADDR_NO_RANDOMIZE | value);
if (value == -1)
ExitWithError(error_fd, "personality set");
#endif
}
static void DupDescriptor(int error_fd, const char *file, int fd, int flags) {
int target_fd = llvm::sys::RetryAfterSignal(-1, ::open, file, flags, 0666);
if (target_fd == -1)
ExitWithError(error_fd, "DupDescriptor-open");
if (target_fd == fd)
return;
if (::dup2(target_fd, fd) == -1)
ExitWithError(error_fd, "DupDescriptor-dup2");
::close(target_fd);
}
namespace {
struct ForkFileAction {
ForkFileAction(const FileAction &act);
FileAction::Action action;
int fd;
std::string path;
int arg;
};
struct ForkLaunchInfo {
ForkLaunchInfo(const ProcessLaunchInfo &info);
bool separate_process_group;
bool debug;
bool disable_aslr;
std::string wd;
const char **argv;
Environment::Envp envp;
std::vector<ForkFileAction> actions;
bool has_action(int fd) const {
for (const ForkFileAction &action : actions) {
if (action.fd == fd)
return true;
}
return false;
}
};
} // namespace
[[noreturn]] static void ChildFunc(int error_fd, const ForkLaunchInfo &info) {
if (info.separate_process_group) {
if (setpgid(0, 0) != 0)
ExitWithError(error_fd, "setpgid");
}
for (const ForkFileAction &action : info.actions) {
switch (action.action) {
case FileAction::eFileActionClose:
if (close(action.fd) != 0)
ExitWithError(error_fd, "close");
break;
case FileAction::eFileActionDuplicate:
if (dup2(action.fd, action.arg) == -1)
ExitWithError(error_fd, "dup2");
break;
case FileAction::eFileActionOpen:
DupDescriptor(error_fd, action.path.c_str(), action.fd, action.arg);
break;
case FileAction::eFileActionNone:
break;
}
}
// Change working directory
if (!info.wd.empty() && 0 != ::chdir(info.wd.c_str()))
ExitWithError(error_fd, "chdir");
if (info.disable_aslr)
DisableASLR(error_fd);
// Clear the signal mask to prevent the child from being affected by any
// masking done by the parent.
sigset_t set;
if (sigemptyset(&set) != 0 ||
pthread_sigmask(SIG_SETMASK, &set, nullptr) != 0)
ExitWithError(error_fd, "pthread_sigmask");
if (info.debug) {
// Do not inherit setgid powers.
if (setgid(getgid()) != 0)
ExitWithError(error_fd, "setgid");
// HACK:
// Close everything besides stdin, stdout, and stderr that has no file
// action to avoid leaking. Only do this when debugging, as elsewhere we
// actually rely on passing open descriptors to child processes.
// NB: This code is not async-signal safe, but we currently do not launch
// processes for debugging from within multithreaded contexts.
const llvm::StringRef proc_fd_path = "/proc/self/fd";
std::error_code ec;
bool result;
ec = llvm::sys::fs::is_directory(proc_fd_path, result);
if (result) {
std::vector<int> files_to_close;
// Directory iterator doesn't ensure any sequence.
for (llvm::sys::fs::directory_iterator iter(proc_fd_path, ec), file_end;
iter != file_end && !ec; iter.increment(ec)) {
int fd = std::stoi(iter->path().substr(proc_fd_path.size() + 1));
// Don't close first three entries since they are stdin, stdout and
// stderr.
if (fd > 2 && !info.has_action(fd) && fd != error_fd)
files_to_close.push_back(fd);
}
for (int file_to_close : files_to_close)
close(file_to_close);
} else {
// Since /proc/self/fd didn't work, trying the slow way instead.
int max_fd = sysconf(_SC_OPEN_MAX);
for (int fd = 3; fd < max_fd; ++fd)
if (!info.has_action(fd) && fd != error_fd)
close(fd);
}
// Start tracing this child that is about to exec.
if (ptrace(PT_TRACE_ME, 0, nullptr, 0) == -1)
ExitWithError(error_fd, "ptrace");
}
// Execute. We should never return...
execve(info.argv[0], const_cast<char *const *>(info.argv), info.envp);
#if defined(__linux__)
if (errno == ETXTBSY) {
// On android M and earlier we can get this error because the adb daemon
// can hold a write handle on the executable even after it has finished
// uploading it. This state lasts only a short time and happens only when
// there are many concurrent adb commands being issued, such as when
// running the test suite. (The file remains open when someone does an "adb
// shell" command in the fork() child before it has had a chance to exec.)
// Since this state should clear up quickly, wait a while and then give it
// one more go.
usleep(50000);
execve(info.argv[0], const_cast<char *const *>(info.argv), info.envp);
}
#endif
// ...unless exec fails. In which case we definitely need to end the child
// here.
ExitWithError(error_fd, "execve");
}
// End of code running in the child process.
ForkFileAction::ForkFileAction(const FileAction &act)
: action(act.GetAction()), fd(act.GetFD()), path(act.GetPath().str()),
arg(act.GetActionArgument()) {}
static std::vector<ForkFileAction>
MakeForkActions(const ProcessLaunchInfo &info) {
std::vector<ForkFileAction> result;
for (size_t i = 0; i < info.GetNumFileActions(); ++i)
result.emplace_back(*info.GetFileActionAtIndex(i));
return result;
}
static Environment::Envp FixupEnvironment(Environment env) {
#ifdef __ANDROID__
// If there is no PATH variable specified inside the environment then set the
// path to /system/bin. It is required because the default path used by
// execve() is wrong on android.
env.try_emplace("PATH", "/system/bin");
#endif
return env.getEnvp();
}
ForkLaunchInfo::ForkLaunchInfo(const ProcessLaunchInfo &info)
: separate_process_group(
info.GetFlags().Test(eLaunchFlagLaunchInSeparateProcessGroup)),
debug(info.GetFlags().Test(eLaunchFlagDebug)),
disable_aslr(info.GetFlags().Test(eLaunchFlagDisableASLR)),
wd(info.GetWorkingDirectory().GetPath()),
argv(info.GetArguments().GetConstArgumentVector()),
envp(FixupEnvironment(info.GetEnvironment())),
actions(MakeForkActions(info)) {}
HostProcess
ProcessLauncherPosixFork::LaunchProcess(const ProcessLaunchInfo &launch_info,
Status &error) {
// A pipe used by the child process to report errors.
PipePosix pipe;
const bool child_processes_inherit = false;
error = pipe.CreateNew(child_processes_inherit);
if (error.Fail())
return HostProcess();
const ForkLaunchInfo fork_launch_info(launch_info);
::pid_t pid = ::fork();
if (pid == -1) {
// Fork failed
error.SetErrorStringWithFormatv("Fork failed with error message: {0}",
llvm::sys::StrError());
return HostProcess(LLDB_INVALID_PROCESS_ID);
}
if (pid == 0) {
// child process
pipe.CloseReadFileDescriptor();
ChildFunc(pipe.ReleaseWriteFileDescriptor(), fork_launch_info);
}
// parent process
pipe.CloseWriteFileDescriptor();
char buf[1000];
int r = read(pipe.GetReadFileDescriptor(), buf, sizeof buf);
if (r == 0)
return HostProcess(pid); // No error. We're done.
error.SetErrorString(buf);
llvm::sys::RetryAfterSignal(-1, waitpid, pid, nullptr, 0);
return HostProcess();
}