| /* | 
 |  * Copyright (C) 2018 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 <inttypes.h> | 
 | #include <signal.h> | 
 | #include <stdint.h> | 
 |  | 
 | #include <memory> | 
 | #include <string> | 
 | #include <vector> | 
 |  | 
 | #include <gtest/gtest.h> | 
 |  | 
 | #include <android-base/stringprintf.h> | 
 |  | 
 | #include <unwindstack/LocalUnwinder.h> | 
 |  | 
 | namespace unwindstack { | 
 |  | 
 | static std::vector<LocalFrameData>* g_frame_info; | 
 | static LocalUnwinder* g_unwinder; | 
 |  | 
 | extern "C" void SignalLocalInnerFunction() { | 
 |   g_unwinder->Unwind(g_frame_info, 256); | 
 | } | 
 |  | 
 | extern "C" void SignalLocalMiddleFunction() { | 
 |   SignalLocalInnerFunction(); | 
 | } | 
 |  | 
 | extern "C" void SignalLocalOuterFunction() { | 
 |   SignalLocalMiddleFunction(); | 
 | } | 
 |  | 
 | static void SignalLocalCallerHandler(int, siginfo_t*, void*) { | 
 |   SignalLocalOuterFunction(); | 
 | } | 
 |  | 
 | static std::string ErrorMsg(const std::vector<const char*>& function_names, | 
 |                             const std::vector<LocalFrameData>& frame_info) { | 
 |   std::string unwind; | 
 |   size_t i = 0; | 
 |   for (const auto& frame : frame_info) { | 
 |     unwind += android::base::StringPrintf("#%02zu pc 0x%" PRIx64 " rel_pc 0x%" PRIx64, i++, | 
 |                                           frame.pc, frame.rel_pc); | 
 |     if (frame.map_info != nullptr) { | 
 |       if (!frame.map_info->name.empty()) { | 
 |         unwind += " " + frame.map_info->name; | 
 |       } else { | 
 |         unwind += android::base::StringPrintf(" 0x%" PRIx64 "-0x%" PRIx64, frame.map_info->start, | 
 |                                               frame.map_info->end); | 
 |       } | 
 |       if (frame.map_info->offset != 0) { | 
 |         unwind += android::base::StringPrintf(" offset 0x%" PRIx64, frame.map_info->offset); | 
 |       } | 
 |     } | 
 |     if (!frame.function_name.empty()) { | 
 |       unwind += " " + frame.function_name; | 
 |       if (frame.function_offset != 0) { | 
 |         unwind += android::base::StringPrintf("+%" PRId64, frame.function_offset); | 
 |       } | 
 |     } | 
 |     unwind += '\n'; | 
 |   } | 
 |  | 
 |   return std::string( | 
 |              "Unwind completed without finding all frames\n" | 
 |              "  Looking for function: ") + | 
 |          function_names.front() + "\n" + "Unwind data:\n" + unwind; | 
 | } | 
 |  | 
 | // This test assumes that this code is compiled with optimizations turned | 
 | // off. If this doesn't happen, then all of the calls will be optimized | 
 | // away. | 
 | extern "C" void LocalInnerFunction(LocalUnwinder* unwinder, bool unwind_through_signal) { | 
 |   std::vector<LocalFrameData> frame_info; | 
 |   g_frame_info = &frame_info; | 
 |   g_unwinder = unwinder; | 
 |   std::vector<const char*> expected_function_names; | 
 |  | 
 |   if (unwind_through_signal) { | 
 |     struct sigaction act, oldact; | 
 |     memset(&act, 0, sizeof(act)); | 
 |     act.sa_sigaction = SignalLocalCallerHandler; | 
 |     act.sa_flags = SA_RESTART | SA_ONSTACK; | 
 |     ASSERT_EQ(0, sigaction(SIGUSR1, &act, &oldact)); | 
 |  | 
 |     raise(SIGUSR1); | 
 |  | 
 |     ASSERT_EQ(0, sigaction(SIGUSR1, &oldact, nullptr)); | 
 |  | 
 |     expected_function_names = {"LocalOuterFunction",        "LocalMiddleFunction", | 
 |                                "LocalInnerFunction",        "SignalLocalOuterFunction", | 
 |                                "SignalLocalMiddleFunction", "SignalLocalInnerFunction"}; | 
 |   } else { | 
 |     ASSERT_TRUE(unwinder->Unwind(&frame_info, 256)); | 
 |  | 
 |     expected_function_names = {"LocalOuterFunction", "LocalMiddleFunction", "LocalInnerFunction"}; | 
 |   } | 
 |  | 
 |   for (auto& frame : frame_info) { | 
 |     if (frame.function_name == expected_function_names.back()) { | 
 |       expected_function_names.pop_back(); | 
 |       if (expected_function_names.empty()) { | 
 |         break; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   ASSERT_TRUE(expected_function_names.empty()) << ErrorMsg(expected_function_names, frame_info); | 
 | } | 
 |  | 
 | extern "C" void LocalMiddleFunction(LocalUnwinder* unwinder, bool unwind_through_signal) { | 
 |   LocalInnerFunction(unwinder, unwind_through_signal); | 
 | } | 
 |  | 
 | extern "C" void LocalOuterFunction(LocalUnwinder* unwinder, bool unwind_through_signal) { | 
 |   LocalMiddleFunction(unwinder, unwind_through_signal); | 
 | } | 
 |  | 
 | class LocalUnwinderTest : public ::testing::Test { | 
 |  protected: | 
 |   void SetUp() override { | 
 |     unwinder_.reset(new LocalUnwinder); | 
 |     ASSERT_TRUE(unwinder_->Init()); | 
 |   } | 
 |  | 
 |   std::unique_ptr<LocalUnwinder> unwinder_; | 
 | }; | 
 |  | 
 | TEST_F(LocalUnwinderTest, local) { | 
 |   LocalOuterFunction(unwinder_.get(), false); | 
 | } | 
 |  | 
 | TEST_F(LocalUnwinderTest, local_signal) { | 
 |   LocalOuterFunction(unwinder_.get(), true); | 
 | } | 
 |  | 
 | TEST_F(LocalUnwinderTest, local_multiple) { | 
 |   ASSERT_NO_FATAL_FAILURE(LocalOuterFunction(unwinder_.get(), false)); | 
 |  | 
 |   ASSERT_NO_FATAL_FAILURE(LocalOuterFunction(unwinder_.get(), true)); | 
 |  | 
 |   ASSERT_NO_FATAL_FAILURE(LocalOuterFunction(unwinder_.get(), false)); | 
 |  | 
 |   ASSERT_NO_FATAL_FAILURE(LocalOuterFunction(unwinder_.get(), true)); | 
 | } | 
 |  | 
 | // This test verifies that doing an unwind before and after a dlopen | 
 | // works. It's verifying that the maps read during the first unwind | 
 | // do not cause a problem when doing the unwind using the code in | 
 | // the dlopen'd code. | 
 | TEST_F(LocalUnwinderTest, unwind_after_dlopen) { | 
 |   // Prime the maps data. | 
 |   ASSERT_NO_FATAL_FAILURE(LocalOuterFunction(unwinder_.get(), false)); | 
 |  | 
 |   std::string testlib(testing::internal::GetArgvs()[0]); | 
 |   auto const value = testlib.find_last_of('/'); | 
 |   if (value == std::string::npos) { | 
 |     testlib = "../"; | 
 |   } else { | 
 |     testlib = testlib.substr(0, value + 1) + "../"; | 
 |   } | 
 |   testlib += "libunwindstack_local.so"; | 
 |  | 
 |   void* handle = dlopen(testlib.c_str(), RTLD_NOW); | 
 |   ASSERT_TRUE(handle != nullptr); | 
 |  | 
 |   void (*unwind_function)(void*, void*) = | 
 |       reinterpret_cast<void (*)(void*, void*)>(dlsym(handle, "TestlibLevel1")); | 
 |   ASSERT_TRUE(unwind_function != nullptr); | 
 |  | 
 |   std::vector<LocalFrameData> frame_info; | 
 |   unwind_function(unwinder_.get(), &frame_info); | 
 |  | 
 |   ASSERT_EQ(0, dlclose(handle)); | 
 |  | 
 |   std::vector<const char*> expected_function_names{"TestlibLevel1", "TestlibLevel2", | 
 |                                                    "TestlibLevel3", "TestlibLevel4"}; | 
 |  | 
 |   for (auto& frame : frame_info) { | 
 |     if (frame.function_name == expected_function_names.back()) { | 
 |       expected_function_names.pop_back(); | 
 |       if (expected_function_names.empty()) { | 
 |         break; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   ASSERT_TRUE(expected_function_names.empty()) << ErrorMsg(expected_function_names, frame_info); | 
 | } | 
 |  | 
 | }  // namespace unwindstack |