277 lines
9.4 KiB
C++
277 lines
9.4 KiB
C++
/*
|
|
* Copyright (C) 2021 The Android Open Source Project
|
|
*
|
|
* 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 <dlfcn.h>
|
|
#include <gtest/gtest.h>
|
|
#include <sys/ptrace.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <cerrno>
|
|
#include <csignal>
|
|
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <filesystem>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <system_error>
|
|
#include <thread>
|
|
|
|
#include <android-base/file.h>
|
|
#include <procinfo/process.h>
|
|
|
|
#include "OfflineUnwindUtils.h"
|
|
#include "ProcessTracer.h"
|
|
#include "tests/TestUtils.h"
|
|
|
|
namespace unwindstack {
|
|
namespace {
|
|
|
|
class ProcessTracerTest : public ::testing::TestWithParam<bool> {
|
|
protected:
|
|
enum class BoolOrTimeout {
|
|
kSuccess = 0,
|
|
kFail,
|
|
kTimeout,
|
|
};
|
|
|
|
// Setup a child process that has a few threads that simply busy wait.
|
|
void SetUp() override {
|
|
// Setup signal handlers for child to let parent know that it is ready and for parent
|
|
// to kill the child.
|
|
child_is_ready_ = false;
|
|
ASSERT_NE(SIG_ERR, signal(kChildIsReadySignal, [](int) { child_is_ready_ = true; }))
|
|
<< "Failed to set up signal handler for kChildIsReadySignal: " << strerror(errno);
|
|
child_keep_running_ = true;
|
|
ASSERT_NE(SIG_ERR, signal(kStopChildSignal, [](int) { child_keep_running_ = false; }))
|
|
<< "Failed to set up signal handler for kStopChildSignal: " << strerror(errno);
|
|
|
|
pid_t parent_pid = getpid();
|
|
child_pid_ = fork();
|
|
if (child_pid_ == static_cast<pid_t>(-1)) FAIL() << "SetUp: fork() failed: " << strerror(errno);
|
|
if (child_pid_ == 0) {
|
|
ASSERT_NO_FATAL_FAILURE(ChildProcSpin(parent_pid));
|
|
}
|
|
|
|
// Make sure the child process has set up its threads before running the test.
|
|
sigset_t signal_mask, old_mask;
|
|
sigemptyset(&signal_mask);
|
|
sigaddset(&signal_mask, kChildIsReadySignal);
|
|
sigprocmask(SIG_BLOCK, &signal_mask, &old_mask);
|
|
while (!child_is_ready_) sigsuspend(&old_mask);
|
|
sigprocmask(SIG_UNBLOCK, &signal_mask, NULL);
|
|
}
|
|
|
|
void TearDown() override {
|
|
// Send signal to join threads and exit.
|
|
if (-1 == kill(child_pid_, kStopChildSignal)) {
|
|
std::cerr << "TearDown: kill sending kStopChildSignal failed: " << strerror(errno) << ".\n";
|
|
kill(child_pid_, SIGKILL);
|
|
}
|
|
}
|
|
|
|
void ChildProcSpin(pid_t parent_pid) {
|
|
// Busy wait in a dlopened local library so we can reliably test (across different
|
|
// architecture) if a process is within a desired ELF.
|
|
std::unique_ptr<void, decltype(&dlclose)> test_lib_handle(GetTestLibHandle(), &dlclose);
|
|
ASSERT_TRUE(test_lib_handle);
|
|
int (*busy_wait_func)() = reinterpret_cast<int (*)()>(dlsym(test_lib_handle.get(), "BusyWait"));
|
|
ASSERT_NE(nullptr, busy_wait_func);
|
|
|
|
std::array<std::thread, kNumThreads> threads;
|
|
std::array<std::atomic_bool, kNumThreads> threads_are_ready{false, false, false, false, false};
|
|
for (size_t i = 0; i < kNumThreads; ++i) {
|
|
threads.at(i) = std::thread([&threads_are_ready, i, &busy_wait_func]() {
|
|
while (child_keep_running_) {
|
|
DoNotOptimize(busy_wait_func());
|
|
threads_are_ready.at(i) = true;
|
|
}
|
|
});
|
|
}
|
|
// Wait until all threads have entered the loop before informing parent child is
|
|
// ready to avoid a race.
|
|
while (!std::all_of(threads_are_ready.begin(), threads_are_ready.end(),
|
|
[&](const std::atomic_bool& el) { return el == true; })) {
|
|
usleep(100);
|
|
}
|
|
ASSERT_NE(-1, kill(parent_pid, kChildIsReadySignal) == -1)
|
|
<< "TearDown: kill sending kChildIsReady failed: " << strerror(errno) << ".\n";
|
|
for (size_t i = 0; i < kNumThreads; ++i) {
|
|
threads.at(i).join();
|
|
}
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
BoolOrTimeout StopInDesiredElfTimeout(ProcessTracer& proc, const std::string& elf_name,
|
|
size_t timeout_sec = 2) {
|
|
static BoolOrTimeout result = BoolOrTimeout::kSuccess;
|
|
if (SIG_ERR == signal(SIGALRM, [](int) {
|
|
result = BoolOrTimeout::kTimeout;
|
|
// StopInDesiredElf contains signal handler for SIGINT mainly so that we could stop the
|
|
// search easily when running unwind_for_offline and we can use it here too.
|
|
kill(getpid(), SIGINT);
|
|
})) {
|
|
std::cerr << "Failed to set up signal handler for SIGALRM: " << strerror(errno) << ".\n";
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
alarm(timeout_sec);
|
|
if (proc.StopInDesiredElf(elf_name)) {
|
|
result = BoolOrTimeout::kSuccess;
|
|
} else if (result != BoolOrTimeout::kTimeout) {
|
|
result = BoolOrTimeout::kFail;
|
|
}
|
|
alarm(0);
|
|
return result;
|
|
}
|
|
|
|
static constexpr size_t kNumThreads = 5;
|
|
static constexpr int kChildIsReadySignal = SIGUSR1;
|
|
static constexpr int kStopChildSignal = SIGUSR2;
|
|
static inline std::atomic_bool child_is_ready_ = false;
|
|
static inline std::atomic_bool child_keep_running_ = true;
|
|
pid_t child_pid_;
|
|
};
|
|
|
|
static void VerifyState(pid_t tid, bool running) {
|
|
while (true) {
|
|
android::procinfo::ProcessInfo proc_info;
|
|
ASSERT_TRUE(GetProcessInfo(tid, &proc_info));
|
|
if (running) {
|
|
if (proc_info.state == android::procinfo::kProcessStateRunning ||
|
|
proc_info.state == android::procinfo::kProcessStateSleeping) {
|
|
break;
|
|
}
|
|
} else if (proc_info.state == android::procinfo::kProcessStateStopped) {
|
|
break;
|
|
}
|
|
usleep(1000);
|
|
}
|
|
}
|
|
|
|
static void VerifyState(ProcessTracer& proc, bool running) {
|
|
// Verify that the main thread and all threads are in the expected state.
|
|
VerifyState(proc.pid(), running);
|
|
if (::testing::Test::HasFatalFailure()) return;
|
|
for (const pid_t& tid : proc.tids()) {
|
|
VerifyState(tid, running);
|
|
if (::testing::Test::HasFatalFailure()) return;
|
|
}
|
|
}
|
|
|
|
TEST_P(ProcessTracerTest, stop_and_resume) {
|
|
ProcessTracer proc(child_pid_, /*is_tracing_threads*/ GetParam());
|
|
|
|
ASSERT_TRUE(proc.Stop());
|
|
VerifyState(proc, /*running*/ false);
|
|
if (::testing::Test::HasFatalFailure()) return;
|
|
|
|
ASSERT_TRUE(proc.Resume());
|
|
VerifyState(proc, /*running*/ true);
|
|
if (::testing::Test::HasFatalFailure()) return;
|
|
}
|
|
|
|
TEST_P(ProcessTracerTest, attach_and_detach) {
|
|
ProcessTracer proc(child_pid_, /*is_tracing_threads*/ GetParam());
|
|
|
|
ASSERT_TRUE(proc.Attach(child_pid_));
|
|
// Attaching to the same pid should result in failure and errno indicating that we cannot trace
|
|
// the priocess because it is already being traced after the call to Attach().
|
|
ASSERT_EQ(-1, ptrace(PTRACE_ATTACH, child_pid_, nullptr, nullptr));
|
|
ASSERT_EQ(EPERM, errno);
|
|
ASSERT_TRUE(proc.Detach(child_pid_));
|
|
for (const pid_t& tid : proc.tids()) {
|
|
ASSERT_TRUE(proc.Attach(tid));
|
|
ASSERT_EQ(-1, ptrace(PTRACE_ATTACH, tid, nullptr, nullptr));
|
|
ASSERT_EQ(EPERM, errno);
|
|
ASSERT_TRUE(proc.Detach(tid));
|
|
}
|
|
}
|
|
|
|
TEST_P(ProcessTracerTest, consecutive_attach_fail) {
|
|
if (!GetParam()) GTEST_SKIP();
|
|
ProcessTracer proc(child_pid_, /*is_tracing_threads*/ GetParam());
|
|
|
|
bool is_first_thread = true;
|
|
for (const pid_t& tid : proc.tids()) {
|
|
if (is_first_thread) {
|
|
ASSERT_TRUE(proc.Attach(tid));
|
|
is_first_thread = false;
|
|
} else {
|
|
ASSERT_FALSE(proc.Attach(tid));
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(ProcessTracerTest, trace_invalid_tid) {
|
|
if (GetParam()) GTEST_SKIP();
|
|
ProcessTracer proc(child_pid_, /*is_tracing_threads*/ GetParam());
|
|
ASSERT_FALSE(proc.Attach(getpid()));
|
|
ASSERT_FALSE(proc.Detach(getpid()));
|
|
}
|
|
|
|
TEST_P(ProcessTracerTest, detach_with_no_attached) {
|
|
if (GetParam()) GTEST_SKIP();
|
|
ProcessTracer proc(child_pid_, /*is_tracing_threads*/ GetParam());
|
|
ASSERT_FALSE(proc.Detach(child_pid_));
|
|
}
|
|
|
|
TEST_P(ProcessTracerTest, uses_shared_library) {
|
|
ProcessTracer proc(child_pid_, /*is_tracing_threads*/ GetParam());
|
|
|
|
std::string elf_name = "libunwindstack_local.so";
|
|
ASSERT_TRUE(proc.UsesSharedLibrary(child_pid_, elf_name));
|
|
for (const pid_t& tid : proc.tids()) {
|
|
ASSERT_TRUE(proc.UsesSharedLibrary(tid, elf_name));
|
|
}
|
|
}
|
|
|
|
TEST_P(ProcessTracerTest, does_not_use_shared_library) {
|
|
ProcessTracer proc(child_pid_, /*is_tracing_threads*/ GetParam());
|
|
|
|
std::string elf_name = "libfake.so";
|
|
ASSERT_FALSE(proc.UsesSharedLibrary(child_pid_, elf_name));
|
|
for (const pid_t& tid : proc.tids()) {
|
|
ASSERT_FALSE(proc.UsesSharedLibrary(tid, elf_name));
|
|
}
|
|
}
|
|
|
|
TEST_P(ProcessTracerTest, stop_in_elf_we_use) {
|
|
// Skip the run with is_tracing_threads=false because main thread only uses
|
|
// the threading library.
|
|
if (!GetParam()) GTEST_SKIP();
|
|
ProcessTracer proc(child_pid_, /*is_tracing_threads*/ GetParam());
|
|
std::string elf_name = "libunwindstack_local.so";
|
|
|
|
EXPECT_EQ(BoolOrTimeout::kSuccess, StopInDesiredElfTimeout(proc, elf_name));
|
|
}
|
|
|
|
TEST_P(ProcessTracerTest, timeout_when_try_to_stop_in_elf_we_do_not_use) {
|
|
ProcessTracer proc(child_pid_, /*is_tracing_threads*/ GetParam());
|
|
std::string elf_name = "libfake.so";
|
|
|
|
EXPECT_EQ(BoolOrTimeout::kTimeout, StopInDesiredElfTimeout(proc, elf_name));
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(IsTracingThreads, ProcessTracerTest, testing::Values(false, true));
|
|
|
|
} // namespace
|
|
} // namespace unwindstack
|