Add more ptrace process resumption tests.
Add tests to verify that ptrace unlink happens immediately for unreaped processes. Test: /data/nativetest/bionic-unit-tests/bionic-unit-tests --gtest_filter="Ptrace*" Test: /data/nativetest64/bionic-unit-tests/bionic-unit-tests --gtest_filter="Ptrace*" Change-Id: I9803ee5be2a0686c21556598ecf17348df09f601
This commit is contained in:
parent
df3b922fcf
commit
bc055cae45
|
@ -17,6 +17,7 @@
|
||||||
#include <sys/ptrace.h>
|
#include <sys/ptrace.h>
|
||||||
|
|
||||||
#include <elf.h>
|
#include <elf.h>
|
||||||
|
#include <err.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
#include <sys/prctl.h>
|
#include <sys/prctl.h>
|
||||||
|
@ -26,11 +27,16 @@
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
#include <android-base/macros.h>
|
#include <android-base/macros.h>
|
||||||
#include <android-base/unique_fd.h>
|
#include <android-base/unique_fd.h>
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
using android::base::unique_fd;
|
using android::base::unique_fd;
|
||||||
|
|
||||||
// Host libc does not define this.
|
// Host libc does not define this.
|
||||||
|
@ -367,31 +373,39 @@ TEST(sys_ptrace, hardware_breakpoint) {
|
||||||
|
|
||||||
class PtraceResumptionTest : public ::testing::Test {
|
class PtraceResumptionTest : public ::testing::Test {
|
||||||
public:
|
public:
|
||||||
|
unique_fd worker_pipe_write;
|
||||||
|
|
||||||
pid_t worker = -1;
|
pid_t worker = -1;
|
||||||
|
pid_t tracer = -1;
|
||||||
|
|
||||||
PtraceResumptionTest() {
|
PtraceResumptionTest() {
|
||||||
|
unique_fd worker_pipe_read;
|
||||||
|
int pipefd[2];
|
||||||
|
if (pipe2(pipefd, O_CLOEXEC) != 0) {
|
||||||
|
err(1, "failed to create pipe");
|
||||||
|
}
|
||||||
|
|
||||||
|
worker_pipe_read.reset(pipefd[0]);
|
||||||
|
worker_pipe_write.reset(pipefd[1]);
|
||||||
|
|
||||||
|
worker = fork();
|
||||||
|
if (worker == -1) {
|
||||||
|
err(1, "failed to fork worker");
|
||||||
|
} else if (worker == 0) {
|
||||||
|
char buf;
|
||||||
|
worker_pipe_write.reset();
|
||||||
|
TEMP_FAILURE_RETRY(read(worker_pipe_read.get(), &buf, sizeof(buf)));
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~PtraceResumptionTest() {
|
~PtraceResumptionTest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssertDeath(int signo);
|
void AssertDeath(int signo);
|
||||||
void Start(std::function<void()> f) {
|
|
||||||
unique_fd worker_pipe_read, worker_pipe_write;
|
|
||||||
int pipefd[2];
|
|
||||||
ASSERT_EQ(0, pipe2(pipefd, O_CLOEXEC));
|
|
||||||
worker_pipe_read.reset(pipefd[0]);
|
|
||||||
worker_pipe_write.reset(pipefd[1]);
|
|
||||||
|
|
||||||
worker = fork();
|
void StartTracer(std::function<void()> f) {
|
||||||
ASSERT_NE(-1, worker);
|
tracer = fork();
|
||||||
if (worker == 0) {
|
|
||||||
char buf;
|
|
||||||
worker_pipe_write.reset();
|
|
||||||
TEMP_FAILURE_RETRY(read(worker_pipe_read.get(), &buf, sizeof(buf)));
|
|
||||||
exit(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pid_t tracer = fork();
|
|
||||||
ASSERT_NE(-1, tracer);
|
ASSERT_NE(-1, tracer);
|
||||||
if (tracer == 0) {
|
if (tracer == 0) {
|
||||||
f();
|
f();
|
||||||
|
@ -400,26 +414,66 @@ class PtraceResumptionTest : public ::testing::Test {
|
||||||
}
|
}
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WaitForTracer() {
|
||||||
|
if (tracer == -1) {
|
||||||
|
errx(1, "tracer not started");
|
||||||
|
}
|
||||||
|
|
||||||
int result;
|
int result;
|
||||||
pid_t rc = waitpid(tracer, &result, 0);
|
pid_t rc = waitpid(tracer, &result, 0);
|
||||||
ASSERT_EQ(tracer, rc);
|
if (rc != tracer) {
|
||||||
EXPECT_TRUE(WIFEXITED(result) || WIFSIGNALED(result));
|
printf("waitpid returned %d (%s)\n", rc, strerror(errno));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!WIFEXITED(result) && !WIFSIGNALED(result)) {
|
||||||
|
printf("!WIFEXITED && !WIFSIGNALED\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (WIFEXITED(result)) {
|
if (WIFEXITED(result)) {
|
||||||
if (WEXITSTATUS(result) != 0) {
|
if (WEXITSTATUS(result) != 0) {
|
||||||
FAIL() << "tracer failed";
|
printf("tracer failed\n");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rc = waitpid(worker, &result, WNOHANG);
|
return true;
|
||||||
ASSERT_EQ(0, rc);
|
}
|
||||||
|
|
||||||
|
bool WaitForWorker() {
|
||||||
|
if (worker == -1) {
|
||||||
|
errx(1, "worker not started");
|
||||||
|
}
|
||||||
|
|
||||||
|
int result;
|
||||||
|
pid_t rc = waitpid(worker, &result, WNOHANG);
|
||||||
|
if (rc != 0) {
|
||||||
|
printf("worker exited prematurely\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
worker_pipe_write.reset();
|
worker_pipe_write.reset();
|
||||||
|
|
||||||
rc = waitpid(worker, &result, 0);
|
rc = waitpid(worker, &result, 0);
|
||||||
ASSERT_EQ(worker, rc);
|
if (rc != worker) {
|
||||||
EXPECT_TRUE(WIFEXITED(result));
|
printf("waitpid for worker returned %d (%s)\n", rc, strerror(errno));
|
||||||
EXPECT_EQ(WEXITSTATUS(result), 0);
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!WIFEXITED(result)) {
|
||||||
|
printf("worker didn't exit\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WEXITSTATUS(result) != 0) {
|
||||||
|
printf("worker exited with status %d\n", WEXITSTATUS(result));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -436,22 +490,74 @@ static void wait_for_ptrace_stop(pid_t pid) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(PtraceResumptionTest, smoke) {
|
||||||
|
// Make sure that the worker doesn't exit before the tracer stops tracing.
|
||||||
|
StartTracer([this]() {
|
||||||
|
ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno);
|
||||||
|
ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 0, 0)) << strerror(errno);
|
||||||
|
wait_for_ptrace_stop(worker);
|
||||||
|
std::this_thread::sleep_for(500ms);
|
||||||
|
});
|
||||||
|
|
||||||
|
worker_pipe_write.reset();
|
||||||
|
std::this_thread::sleep_for(250ms);
|
||||||
|
|
||||||
|
int result;
|
||||||
|
ASSERT_EQ(0, waitpid(worker, &result, WNOHANG));
|
||||||
|
ASSERT_TRUE(WaitForTracer());
|
||||||
|
ASSERT_EQ(worker, waitpid(worker, &result, 0));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(PtraceResumptionTest, seize) {
|
TEST_F(PtraceResumptionTest, seize) {
|
||||||
Start([this]() { ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno); });
|
StartTracer([this]() { ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno); });
|
||||||
|
ASSERT_TRUE(WaitForTracer());
|
||||||
|
ASSERT_TRUE(WaitForWorker());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(PtraceResumptionTest, seize_interrupt) {
|
TEST_F(PtraceResumptionTest, seize_interrupt) {
|
||||||
Start([this]() {
|
StartTracer([this]() {
|
||||||
ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno);
|
ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno);
|
||||||
ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 0, 0)) << strerror(errno);
|
ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 0, 0)) << strerror(errno);
|
||||||
|
wait_for_ptrace_stop(worker);
|
||||||
});
|
});
|
||||||
|
ASSERT_TRUE(WaitForTracer());
|
||||||
|
ASSERT_TRUE(WaitForWorker());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(PtraceResumptionTest, seize_interrupt_cont) {
|
TEST_F(PtraceResumptionTest, seize_interrupt_cont) {
|
||||||
Start([this]() {
|
StartTracer([this]() {
|
||||||
ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno);
|
ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno);
|
||||||
ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 0, 0)) << strerror(errno);
|
ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 0, 0)) << strerror(errno);
|
||||||
wait_for_ptrace_stop(worker);
|
wait_for_ptrace_stop(worker);
|
||||||
ASSERT_EQ(0, ptrace(PTRACE_CONT, worker, 0, 0)) << strerror(errno);
|
ASSERT_EQ(0, ptrace(PTRACE_CONT, worker, 0, 0)) << strerror(errno);
|
||||||
});
|
});
|
||||||
|
ASSERT_TRUE(WaitForTracer());
|
||||||
|
ASSERT_TRUE(WaitForWorker());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PtraceResumptionTest, zombie_seize) {
|
||||||
|
StartTracer([this]() { ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno); });
|
||||||
|
ASSERT_TRUE(WaitForWorker());
|
||||||
|
ASSERT_TRUE(WaitForTracer());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PtraceResumptionTest, zombie_seize_interrupt) {
|
||||||
|
StartTracer([this]() {
|
||||||
|
ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno);
|
||||||
|
ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 0, 0)) << strerror(errno);
|
||||||
|
wait_for_ptrace_stop(worker);
|
||||||
|
});
|
||||||
|
ASSERT_TRUE(WaitForWorker());
|
||||||
|
ASSERT_TRUE(WaitForTracer());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PtraceResumptionTest, zombie_seize_interrupt_cont) {
|
||||||
|
StartTracer([this]() {
|
||||||
|
ASSERT_EQ(0, ptrace(PTRACE_SEIZE, worker, 0, 0)) << strerror(errno);
|
||||||
|
ASSERT_EQ(0, ptrace(PTRACE_INTERRUPT, worker, 0, 0)) << strerror(errno);
|
||||||
|
wait_for_ptrace_stop(worker);
|
||||||
|
ASSERT_EQ(0, ptrace(PTRACE_CONT, worker, 0, 0)) << strerror(errno);
|
||||||
|
});
|
||||||
|
ASSERT_TRUE(WaitForWorker());
|
||||||
|
ASSERT_TRUE(WaitForTracer());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue