From 11526e2fc607624dbb8bd4acf2ffdd3a187e9c02 Mon Sep 17 00:00:00 2001 From: Christopher Ferris Date: Thu, 14 Oct 2021 22:44:47 +0000 Subject: Add execinfo functionality. Bug: 27877410 Test: Add new unit tests. Change-Id: Id5d7eb27a23f50e99a04f5ee1ab64047ba269bab --- tests/Android.bp | 5 + tests/NOTICE | 28 ++++++ tests/execinfo_test.cpp | 244 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 277 insertions(+) create mode 100644 tests/execinfo_test.cpp (limited to 'tests') diff --git a/tests/Android.bp b/tests/Android.bp index cbf0a9f36..8a57dbbf4 100644 --- a/tests/Android.bp +++ b/tests/Android.bp @@ -474,6 +474,9 @@ cc_test_library { // musl doesn't have error.h "error_test.cpp", + // musl doesn't have execinfo.h + "execinfo_test.cpp", + // musl doesn't define noreturn for C++ "stdnoreturn_test.cpp", @@ -745,6 +748,7 @@ cc_test_library { "dl_test.cpp", "dlfcn_symlink_support.cpp", "dlfcn_test.cpp", + "execinfo_test.cpp", "link_test.cpp", "pthread_dlfcn_test.cpp", ], @@ -1141,6 +1145,7 @@ cc_test_host { "dlfcn_symlink_support.cpp", "dlfcn_test.cpp", "dl_test.cpp", + "execinfo_test.cpp", "gtest_globals.cpp", "gtest_main.cpp", "pthread_dlfcn_test.cpp", diff --git a/tests/NOTICE b/tests/NOTICE index a58cf46d7..c9b65d07b 100644 --- a/tests/NOTICE +++ b/tests/NOTICE @@ -354,3 +354,31 @@ limitations under the License. ------------------------------------------------------------------- +Copyright (C) 2021 The Android Open Source Project +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +------------------------------------------------------------------- + diff --git a/tests/execinfo_test.cpp b/tests/execinfo_test.cpp new file mode 100644 index 000000000..b8e13252d --- /dev/null +++ b/tests/execinfo_test.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +TEST(execinfo, backtrace_errors) { + void* frames[20]; + ASSERT_EQ(0, backtrace(frames, 0)); + ASSERT_EQ(0, backtrace(frames, -1)); +} + +static constexpr int kMaxFrames = 50; + +// Disable optimizations so that these functions show up properly in +// the backtrace. +#pragma clang optimize off +extern "C" __attribute__((__noinline__)) void CallTwo(std::vector& frames) { + int num_frames = backtrace(frames.data(), static_cast(frames.size())); + ASSERT_LT(0, num_frames); + frames.resize(static_cast(num_frames)); +} + +extern "C" __attribute__((__noinline__)) void CallOne(std::vector& frames) { + CallTwo(frames); +} +#pragma clang optimize on + +static std::string DumpFrames(std::vector& frames) { + std::string frame_data; + for (auto frame : frames) { + frame_data += android::base::StringPrintf("[%p]", frame); + Dl_info info; + if (dladdr(frame, &info) != 0 && info.dli_sname != nullptr) { + frame_data += ' '; + frame_data += info.dli_sname; + } + frame_data += '\n'; + } + return frame_data; +} + +static size_t FindFunction(std::vector& frames, uintptr_t func_addr) { + for (size_t i = 0; i < frames.size(); i++) { + uintptr_t frame_addr = reinterpret_cast(frames[i]); + if (frame_addr >= func_addr && frame_addr <= func_addr + 0x100) { + return i + 1; + } + } + return 0; +} + +static void VerifyCalls(std::vector& frames, size_t* one_idx = nullptr, + size_t* two_idx = nullptr) { + // Try and find the CallOne and CallTwo function addresses. + size_t call_one_idx = FindFunction(frames, reinterpret_cast(&CallOne)); + ASSERT_TRUE(call_one_idx != 0) << DumpFrames(frames); + size_t call_two_idx = FindFunction(frames, reinterpret_cast(&CallTwo)); + ASSERT_TRUE(call_two_idx != 0) << DumpFrames(frames); + + ASSERT_LT(call_two_idx, call_one_idx) << "CallTwo function found after CallOne\n" + << DumpFrames(frames); + + if (one_idx != nullptr) *one_idx = call_one_idx; + if (two_idx != nullptr) *two_idx = call_two_idx; +} + +TEST(execinfo, backtrace) { + std::vector frames(kMaxFrames); + ASSERT_NO_FATAL_FAILURE(CallOne(frames)); + + // Verfiy that there are at least two frames. + ASSERT_LT(3U, frames.size()) << DumpFrames(frames); + + VerifyCalls(frames); +} + +TEST(execinfo, backtrace_cutoff_frames) { + // Verify the max frames is handled properly + std::vector frames(1); + ASSERT_NO_FATAL_FAILURE(CallOne(frames)); + ASSERT_EQ(1U, frames.size()) << DumpFrames(frames); +} + +TEST(execinfo, backtrace_symbols_errors) { + void* frames[kMaxFrames]; + // glibc incorrectly returns memory when a zero is passed in. + // Since we know this works properly on bionic, only verify + // this there. +#if defined(__BIONIC__) + ASSERT_EQ(nullptr, backtrace_symbols(frames, 0)); +#endif + ASSERT_EQ(nullptr, backtrace_symbols(frames, -1)); +} + +static void VerifyLineFormat(std::string& line) { + // Verify that the format of the line is one of these: + // elf_file(FuncName+0xFuncAddr) [0xAddress] + // elf_file(+0xRelAddress) [0xAddress] + // elf_file [0xAddress] + // [0xAddress] +#if defined(__GLIBC__) + // For some reason, glibc will print a space before [0xAddress] for + // backtrace symbols, and no space for backtrace_symbols_fd. Allow this + // only for glibc. + std::regex format1("[^\\(\\s]+\\([^\\+]+\\+0x[0-9a-fA-F]+\\) ?\\[0x[0-9a-fA-F]+\\]"); + std::regex format2("[^\\(\\s]+\\(+\\+0x[0-9a-fA-F]+\\) ?\\[0x[0-9a-fA-F]+\\]"); + std::regex format3("[^\\(\\s]+ ?\\[0x[0-9a-fA-F]+\\]"); +#else + std::regex format1("[^\\(\\s]+\\([^\\+]+\\+0x[0-9a-fA-F]+\\) \\[0x[0-9a-fA-F]+\\]"); + std::regex format2("[^\\(\\s]+\\(+\\+0x[0-9a-fA-F]+\\) \\[0x[0-9a-fA-F]+\\]"); + std::regex format3("[^\\(\\s]+ \\[0x[0-9a-fA-F]+\\]"); +#endif + std::regex format4("\\[0x[0-9a-fA-F]+\\]"); + + EXPECT_TRUE(std::regex_match(line, format1) || std::regex_match(line, format2) || + std::regex_match(line, format3) || std::regex_match(line, format4)) + << "Unknown format of line:\n" + << line; +} + +static void VerifyLineFormat(char* raw_line, size_t length) { + std::string line(raw_line, length); + VerifyLineFormat(line); +} + +TEST(execinfo, backtrace_symbols) { + std::vector frames(kMaxFrames); + ASSERT_NO_FATAL_FAILURE(CallOne(frames)); + ASSERT_LT(3U, frames.size()) << DumpFrames(frames); + + char** symbols = backtrace_symbols(frames.data(), static_cast(frames.size())); + ASSERT_TRUE(symbols != nullptr); + for (size_t i = 0; i < frames.size(); i++) { + ASSERT_TRUE(frames[i] != nullptr); + VerifyLineFormat(symbols[i], strlen(symbols[i])); + } + + size_t call_one_idx; + size_t call_two_idx; + ASSERT_NO_FATAL_FAILURE(VerifyCalls(frames, &call_one_idx, &call_two_idx)); + // Now verify that those frames contain the function names we expect. + SCOPED_TRACE(DumpFrames(frames)); + ASSERT_MATCH(symbols[call_one_idx - 1], "\\(CallOne+"); + ASSERT_MATCH(symbols[call_two_idx - 1], "\\(CallTwo+"); + free(symbols); +} + +TEST(execinfo, backtrace_symbols_fd_errors) { + void* frames[kMaxFrames]; + frames[0] = reinterpret_cast(&backtrace_symbols); + + { + TemporaryFile tf; + backtrace_symbols_fd(frames, 0, tf.fd); + close(tf.fd); + std::string content; + ASSERT_TRUE(android::base::ReadFileToString(tf.path, &content)); + // Verify that no data is written to the file. + ASSERT_TRUE(content.empty()); + } + + { + TemporaryFile tf; + backtrace_symbols_fd(frames, -1, tf.fd); + close(tf.fd); + std::string content; + ASSERT_TRUE(android::base::ReadFileToString(tf.path, &content)); + // Verify that no data is written to the file. + ASSERT_TRUE(content.empty()); + } + + // Verify that there isn't a crash. + backtrace_symbols_fd(frames, 0, -1); +} + +TEST(execinfo, backtrace_symbols_fd) { + std::vector frames(kMaxFrames); + ASSERT_NO_FATAL_FAILURE(CallOne(frames)); + ASSERT_LT(3U, frames.size()) << DumpFrames(frames); + + TemporaryFile tf; + backtrace_symbols_fd(frames.data(), static_cast(frames.size()), tf.fd); + close(tf.fd); + + size_t call_one_idx; + size_t call_two_idx; + ASSERT_NO_FATAL_FAILURE(VerifyCalls(frames, &call_one_idx, &call_two_idx)); + + std::ifstream frame_stream(tf.path); + ASSERT_TRUE(frame_stream.is_open()); + size_t num_lines = 0; + std::string line; + while (std::getline(frame_stream, line)) { + ASSERT_FALSE(line.empty()); + VerifyLineFormat(line); + num_lines++; + + if (num_lines == call_one_idx) { + EXPECT_MATCH(line, "\\(CallOne+"); + } else if (num_lines == call_two_idx) { + EXPECT_MATCH(line, "\\(CallTwo+"); + } + } + ASSERT_EQ(num_lines, frames.size()) << "Number of lines in file does not match number of frames."; +} -- cgit v1.2.3