Merge "Add tests for elf unwinding in memory."

This commit is contained in:
Christopher Ferris 2015-05-13 17:57:24 +00:00 committed by Gerrit Code Review
commit 34804b90ec
2 changed files with 304 additions and 4 deletions

View file

@ -110,12 +110,14 @@ backtrace_test_ldlibs_host := \
backtrace_test_shared_libraries := \
libbacktrace_test \
libbacktrace \
backtrace_test_shared_libraries_target := \
libbase \
libcutils \
backtrace_test_static_libraries_host := \
libcutils \
backtrace_test_shared_libraries_target += \
libdl \
backtrace_test_ldlibs_host += \
-ldl \
module := backtrace_test
module_tag := debug

View file

@ -16,7 +16,9 @@
#define _GNU_SOURCE 1
#include <dirent.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <pthread.h>
#include <signal.h>
@ -25,12 +27,14 @@
#include <stdlib.h>
#include <string.h>
#include <sys/ptrace.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <algorithm>
#include <list>
#include <memory>
#include <string>
#include <vector>
@ -38,6 +42,7 @@
#include <backtrace/Backtrace.h>
#include <backtrace/BacktraceMap.h>
#include <base/stringprintf.h>
#include <cutils/atomic.h>
#include <cutils/threads.h>
@ -1023,6 +1028,7 @@ void ForkedReadTest() {
}
TEST(libbacktrace, process_read) {
g_ready = 0;
pid_t pid;
if ((pid = fork()) == 0) {
ForkedReadTest();
@ -1069,6 +1075,297 @@ TEST(libbacktrace, process_read) {
ASSERT_TRUE(test_executed);
}
void VerifyFunctionsFound(const std::vector<std::string>& found_functions) {
// We expect to find these functions in libbacktrace_test. If we don't
// find them, that's a bug in the memory read handling code in libunwind.
std::list<std::string> expected_functions;
expected_functions.push_back("test_recursive_call");
expected_functions.push_back("test_level_one");
expected_functions.push_back("test_level_two");
expected_functions.push_back("test_level_three");
expected_functions.push_back("test_level_four");
for (const auto& found_function : found_functions) {
for (const auto& expected_function : expected_functions) {
if (found_function == expected_function) {
expected_functions.remove(found_function);
break;
}
}
}
ASSERT_TRUE(expected_functions.empty()) << "Not all functions found in shared library.";
}
const char* CopySharedLibrary() {
#if defined(__LP64__)
const char* lib_name = "lib64";
#else
const char* lib_name = "lib";
#endif
#if defined(__BIONIC__)
const char* tmp_so_name = "/data/local/tmp/libbacktrace_test.so";
std::string cp_cmd = android::base::StringPrintf("cp /system/%s/libbacktrace_test.so %s",
lib_name, tmp_so_name);
#else
const char* tmp_so_name = "/tmp/libbacktrace_test.so";
if (getenv("ANDROID_HOST_OUT") == NULL) {
fprintf(stderr, "ANDROID_HOST_OUT not set, make sure you run lunch.");
return nullptr;
}
std::string cp_cmd = android::base::StringPrintf("cp %s/%s/libbacktrace_test.so %s",
getenv("ANDROID_HOST_OUT"), lib_name,
tmp_so_name);
#endif
// Copy the shared so to a tempory directory.
system(cp_cmd.c_str());
return tmp_so_name;
}
TEST(libbacktrace, check_unreadable_elf_local) {
const char* tmp_so_name = CopySharedLibrary();
ASSERT_TRUE(tmp_so_name != nullptr);
struct stat buf;
ASSERT_TRUE(stat(tmp_so_name, &buf) != -1);
uintptr_t map_size = buf.st_size;
int fd = open(tmp_so_name, O_RDONLY);
ASSERT_TRUE(fd != -1);
void* map = mmap(NULL, map_size, PROT_READ, MAP_PRIVATE, fd, 0);
ASSERT_TRUE(map != MAP_FAILED);
close(fd);
ASSERT_TRUE(unlink(tmp_so_name) != -1);
std::vector<std::string> found_functions;
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(BACKTRACE_CURRENT_PROCESS,
BACKTRACE_CURRENT_THREAD));
ASSERT_TRUE(backtrace.get() != nullptr);
// Needed before GetFunctionName will work.
backtrace->Unwind(0);
// Loop through the entire map, and get every function we can find.
map_size += reinterpret_cast<uintptr_t>(map);
std::string last_func;
for (uintptr_t read_addr = reinterpret_cast<uintptr_t>(map);
read_addr < map_size; read_addr += 4) {
uintptr_t offset;
std::string func_name = backtrace->GetFunctionName(read_addr, &offset);
if (!func_name.empty() && last_func != func_name) {
found_functions.push_back(func_name);
}
last_func = func_name;
}
ASSERT_TRUE(munmap(map, map_size - reinterpret_cast<uintptr_t>(map)) == 0);
VerifyFunctionsFound(found_functions);
}
TEST(libbacktrace, check_unreadable_elf_remote) {
const char* tmp_so_name = CopySharedLibrary();
ASSERT_TRUE(tmp_so_name != nullptr);
g_ready = 0;
struct stat buf;
ASSERT_TRUE(stat(tmp_so_name, &buf) != -1);
uintptr_t map_size = buf.st_size;
pid_t pid;
if ((pid = fork()) == 0) {
int fd = open(tmp_so_name, O_RDONLY);
if (fd == -1) {
fprintf(stderr, "Failed to open file %s: %s\n", tmp_so_name, strerror(errno));
unlink(tmp_so_name);
exit(0);
}
void* map = mmap(NULL, map_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (map == MAP_FAILED) {
fprintf(stderr, "Failed to map in memory: %s\n", strerror(errno));
unlink(tmp_so_name);
exit(0);
}
close(fd);
if (unlink(tmp_so_name) == -1) {
fprintf(stderr, "Failed to unlink: %s\n", strerror(errno));
exit(0);
}
g_addr = reinterpret_cast<uintptr_t>(map);
g_ready = 1;
while (true) {
usleep(US_PER_MSEC);
}
exit(0);
}
ASSERT_TRUE(pid > 0);
std::vector<std::string> found_functions;
uint64_t start = NanoTime();
while (true) {
ASSERT_TRUE(ptrace(PTRACE_ATTACH, pid, 0, 0) == 0);
// Wait for the process to get to a stopping point.
WaitForStop(pid);
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, BACKTRACE_CURRENT_THREAD));
ASSERT_TRUE(backtrace.get() != nullptr);
uintptr_t read_addr;
ASSERT_EQ(sizeof(uintptr_t), backtrace->Read(reinterpret_cast<uintptr_t>(&g_ready), reinterpret_cast<uint8_t*>(&read_addr), sizeof(uintptr_t)));
if (read_addr) {
ASSERT_EQ(sizeof(uintptr_t), backtrace->Read(reinterpret_cast<uintptr_t>(&g_addr), reinterpret_cast<uint8_t*>(&read_addr), sizeof(uintptr_t)));
// Needed before GetFunctionName will work.
backtrace->Unwind(0);
// Loop through the entire map, and get every function we can find.
map_size += read_addr;
std::string last_func;
for (; read_addr < map_size; read_addr += 4) {
uintptr_t offset;
std::string func_name = backtrace->GetFunctionName(read_addr, &offset);
if (!func_name.empty() && last_func != func_name) {
found_functions.push_back(func_name);
}
last_func = func_name;
}
break;
}
ASSERT_TRUE(ptrace(PTRACE_DETACH, pid, 0, 0) == 0);
if ((NanoTime() - start) > 5 * NS_PER_SEC) {
break;
}
usleep(US_PER_MSEC);
}
kill(pid, SIGKILL);
ASSERT_EQ(waitpid(pid, nullptr, 0), pid);
VerifyFunctionsFound(found_functions);
}
bool FindFuncFrameInBacktrace(Backtrace* backtrace, uintptr_t test_func, size_t* frame_num) {
backtrace_map_t map;
backtrace->FillInMap(test_func, &map);
if (!BacktraceMap::IsValid(map)) {
return false;
}
// Loop through the frames, and find the one that is in the map.
*frame_num = 0;
for (Backtrace::const_iterator it = backtrace->begin(); it != backtrace->end(); ++it) {
if (BacktraceMap::IsValid(it->map) && map.start == it->map.start &&
it->pc >= test_func) {
*frame_num = it->num;
return true;
}
}
return false;
}
void VerifyUnreadableElfFrame(Backtrace* backtrace, uintptr_t test_func, size_t frame_num) {
ASSERT_LT(backtrace->NumFrames(), static_cast<size_t>(MAX_BACKTRACE_FRAMES))
<< DumpFrames(backtrace);
ASSERT_TRUE(frame_num != 0) << DumpFrames(backtrace);
// Make sure that there is at least one more frame above the test func call.
ASSERT_LT(frame_num, backtrace->NumFrames()) << DumpFrames(backtrace);
uintptr_t diff = backtrace->GetFrame(frame_num)->pc - test_func;
ASSERT_LT(diff, 200U) << DumpFrames(backtrace);
}
void VerifyUnreadableElfBacktrace(uintptr_t test_func) {
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(BACKTRACE_CURRENT_PROCESS,
BACKTRACE_CURRENT_THREAD));
ASSERT_TRUE(backtrace.get() != nullptr);
ASSERT_TRUE(backtrace->Unwind(0));
size_t frame_num;
ASSERT_TRUE(FindFuncFrameInBacktrace(backtrace.get(), test_func, &frame_num));
VerifyUnreadableElfFrame(backtrace.get(), test_func, frame_num);
}
typedef int (*test_func_t)(int, int, int, int, void (*)(uintptr_t), uintptr_t);
TEST(libbacktrace, unwind_through_unreadable_elf_local) {
const char* tmp_so_name = CopySharedLibrary();
ASSERT_TRUE(tmp_so_name != nullptr);
void* lib_handle = dlopen(tmp_so_name, RTLD_NOW);
ASSERT_TRUE(lib_handle != nullptr);
ASSERT_TRUE(unlink(tmp_so_name) != -1);
test_func_t test_func;
test_func = reinterpret_cast<test_func_t>(dlsym(lib_handle, "test_level_one"));
ASSERT_TRUE(test_func != nullptr);
ASSERT_NE(test_func(1, 2, 3, 4, VerifyUnreadableElfBacktrace,
reinterpret_cast<uintptr_t>(test_func)), 0);
ASSERT_TRUE(dlclose(lib_handle) == 0);
}
TEST(libbacktrace, unwind_through_unreadable_elf_remote) {
const char* tmp_so_name = CopySharedLibrary();
ASSERT_TRUE(tmp_so_name != nullptr);
void* lib_handle = dlopen(tmp_so_name, RTLD_NOW);
ASSERT_TRUE(lib_handle != nullptr);
ASSERT_TRUE(unlink(tmp_so_name) != -1);
test_func_t test_func;
test_func = reinterpret_cast<test_func_t>(dlsym(lib_handle, "test_level_one"));
ASSERT_TRUE(test_func != nullptr);
pid_t pid;
if ((pid = fork()) == 0) {
test_func(1, 2, 3, 4, 0, 0);
exit(0);
}
ASSERT_TRUE(pid > 0);
ASSERT_TRUE(dlclose(lib_handle) == 0);
uint64_t start = NanoTime();
bool done = false;
while (!done) {
ASSERT_TRUE(ptrace(PTRACE_ATTACH, pid, 0, 0) == 0);
// Wait for the process to get to a stopping point.
WaitForStop(pid);
std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, BACKTRACE_CURRENT_THREAD));
ASSERT_TRUE(backtrace.get() != nullptr);
ASSERT_TRUE(backtrace->Unwind(0));
size_t frame_num;
if (FindFuncFrameInBacktrace(backtrace.get(),
reinterpret_cast<uintptr_t>(test_func), &frame_num)) {
VerifyUnreadableElfFrame(backtrace.get(), reinterpret_cast<uintptr_t>(test_func), frame_num);
done = true;
}
ASSERT_TRUE(ptrace(PTRACE_DETACH, pid, 0, 0) == 0);
if ((NanoTime() - start) > 5 * NS_PER_SEC) {
break;
}
usleep(US_PER_MSEC);
}
kill(pid, SIGKILL);
ASSERT_EQ(waitpid(pid, nullptr, 0), pid);
ASSERT_TRUE(done) << "Test function never found in unwind.";
}
#if defined(ENABLE_PSS_TESTS)
#include "GetPss.h"
@ -1142,3 +1439,4 @@ TEST(libbacktrace, check_for_leak_remote) {
ASSERT_EQ(waitpid(pid, nullptr, 0), pid);
}
#endif