diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp index a6bf73073..435ed9462 100644 --- a/libunwindstack/Android.bp +++ b/libunwindstack/Android.bp @@ -62,6 +62,7 @@ cc_library { "MapInfo.cpp", "Maps.cpp", "Memory.cpp", + "LocalUnwinder.cpp", "Regs.cpp", "RegsArm.cpp", "RegsArm64.cpp", @@ -125,6 +126,21 @@ cc_library { //------------------------------------------------------------------------- // Unit Tests //------------------------------------------------------------------------- +cc_test_library { + name: "libunwindstack_local", + defaults: ["libunwindstack_flags"], + srcs: ["tests/TestLocal.cpp"], + + cflags: [ + "-O0", + "-g", + ], + + shared_libs: [ + "libunwindstack", + ], +} + cc_test { name: "libunwindstack_test", defaults: ["libunwindstack_flags"], @@ -151,6 +167,7 @@ cc_test { "tests/ElfTest.cpp", "tests/ElfTestUtils.cpp", "tests/JitDebugTest.cpp", + "tests/LocalUnwinderTest.cpp", "tests/LogFake.cpp", "tests/MapInfoGetElfTest.cpp", "tests/MapInfoGetLoadBiasTest.cpp", diff --git a/libunwindstack/LocalUnwinder.cpp b/libunwindstack/LocalUnwinder.cpp new file mode 100644 index 000000000..952b332f0 --- /dev/null +++ b/libunwindstack/LocalUnwinder.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2018 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 + +namespace unwindstack { + +bool LocalUnwinder::Init() { + pthread_rwlock_init(&maps_rwlock_, nullptr); + + // Create the maps. + maps_.reset(new unwindstack::LocalUpdatableMaps()); + if (!maps_->Parse()) { + maps_.reset(); + return false; + } + + process_memory_ = unwindstack::Memory::CreateProcessMemory(getpid()); + + return true; +} + +bool LocalUnwinder::ShouldSkipLibrary(const std::string& map_name) { + for (const std::string& skip_library : skip_libraries_) { + if (skip_library == map_name) { + return true; + } + } + return false; +} + +MapInfo* LocalUnwinder::GetMapInfo(uint64_t pc) { + pthread_rwlock_rdlock(&maps_rwlock_); + MapInfo* map_info = maps_->Find(pc); + pthread_rwlock_unlock(&maps_rwlock_); + + if (map_info == nullptr) { + pthread_rwlock_wrlock(&maps_rwlock_); + // This is guaranteed not to invalidate any previous MapInfo objects so + // we don't need to worry about any MapInfo* values already in use. + if (maps_->Reparse()) { + map_info = maps_->Find(pc); + } + pthread_rwlock_unlock(&maps_rwlock_); + } + + return map_info; +} + +bool LocalUnwinder::Unwind(std::vector* frame_info, size_t max_frames) { + std::unique_ptr regs(unwindstack::Regs::CreateFromLocal()); + unwindstack::RegsGetLocal(regs.get()); + + size_t num_frames = 0; + bool adjust_pc = false; + while (true) { + uint64_t cur_pc = regs->pc(); + uint64_t cur_sp = regs->sp(); + + MapInfo* map_info = GetMapInfo(cur_pc); + if (map_info == nullptr) { + break; + } + + Elf* elf = map_info->GetElf(process_memory_, true); + uint64_t rel_pc = elf->GetRelPc(cur_pc, map_info); + uint64_t step_pc = rel_pc; + uint64_t pc_adjustment; + if (adjust_pc) { + pc_adjustment = regs->GetPcAdjustment(rel_pc, elf); + } else { + pc_adjustment = 0; + } + step_pc -= pc_adjustment; + // Skip any locations that are within this library. + if (num_frames != 0 || !ShouldSkipLibrary(map_info->name)) { + // Add frame information. + std::string func_name; + uint64_t func_offset; + if (elf->GetFunctionName(rel_pc, &func_name, &func_offset)) { + frame_info->emplace_back(map_info, cur_pc - pc_adjustment, rel_pc - pc_adjustment, + func_name, func_offset); + } else { + frame_info->emplace_back(map_info, cur_pc - pc_adjustment, rel_pc - pc_adjustment, "", 0); + } + num_frames++; + } + if (!elf->valid()) { + break; + } + if (frame_info->size() == max_frames) { + break; + } + + adjust_pc = true; + bool finished; + if (!elf->Step(rel_pc, step_pc, regs.get(), process_memory_.get(), &finished) || finished) { + break; + } + // pc and sp are the same, terminate the unwind. + if (cur_pc == regs->pc() && cur_sp == regs->sp()) { + break; + } + } + return num_frames != 0; +} + +} // namespace unwindstack diff --git a/libunwindstack/Maps.cpp b/libunwindstack/Maps.cpp index bb682ea69..e676a5a97 100644 --- a/libunwindstack/Maps.cpp +++ b/libunwindstack/Maps.cpp @@ -105,4 +105,83 @@ const std::string RemoteMaps::GetMapsFile() const { return "/proc/" + std::to_string(pid_) + "/maps"; } +const std::string LocalUpdatableMaps::GetMapsFile() const { + return "/proc/self/maps"; +} + +bool LocalUpdatableMaps::Reparse() { + // New maps will be added at the end without deleting the old ones. + size_t last_map_idx = maps_.size(); + if (!Parse()) { + // Delete any maps added by the Parse call. + for (size_t i = last_map_idx; i < maps_.size(); i++) { + delete maps_[i]; + } + maps_.resize(last_map_idx); + return false; + } + + size_t total_entries = maps_.size(); + size_t search_map_idx = 0; + for (size_t new_map_idx = last_map_idx; new_map_idx < maps_.size(); new_map_idx++) { + MapInfo* new_map_info = maps_[new_map_idx]; + uint64_t start = new_map_info->start; + uint64_t end = new_map_info->end; + uint64_t flags = new_map_info->flags; + std::string* name = &new_map_info->name; + for (size_t old_map_idx = search_map_idx; old_map_idx < last_map_idx; old_map_idx++) { + MapInfo* info = maps_[old_map_idx]; + if (start == info->start && end == info->end && flags == info->flags && *name == info->name) { + // No need to check + search_map_idx = old_map_idx + 1; + delete new_map_info; + maps_[new_map_idx] = nullptr; + total_entries--; + break; + } else if (info->start > start) { + // Stop, there isn't going to be a match. + search_map_idx = old_map_idx; + break; + } + + // Never delete these maps, they may be in use. The assumption is + // that there will only every be a handfull of these so waiting + // to destroy them is not too expensive. + saved_maps_.push_back(info); + maps_[old_map_idx] = nullptr; + total_entries--; + } + if (search_map_idx >= last_map_idx) { + break; + } + } + + // Now move out any of the maps that never were found. + for (size_t i = search_map_idx; i < last_map_idx; i++) { + saved_maps_.push_back(maps_[i]); + maps_[i] = nullptr; + total_entries--; + } + + // Sort all of the values such that the nullptrs wind up at the end, then + // resize them away. + std::sort(maps_.begin(), maps_.end(), [](const auto* a, const auto* b) { + if (a == nullptr) { + return false; + } else if (b == nullptr) { + return true; + } + return a->start < b->start; + }); + maps_.resize(total_entries); + + return true; +} + +LocalUpdatableMaps::~LocalUpdatableMaps() { + for (auto map_info : saved_maps_) { + delete map_info; + } +} + } // namespace unwindstack diff --git a/libunwindstack/include/unwindstack/LocalUnwinder.h b/libunwindstack/include/unwindstack/LocalUnwinder.h new file mode 100644 index 000000000..80bb53ec1 --- /dev/null +++ b/libunwindstack/include/unwindstack/LocalUnwinder.h @@ -0,0 +1,86 @@ +/* + * 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. + */ + +#ifndef _LIBUNWINDSTACK_LOCAL_UNWINDER_H +#define _LIBUNWINDSTACK_LOCAL_UNWINDER_H + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace unwindstack { + +// Forward declarations. +class Elf; +struct MapInfo; + +struct LocalFrameData { + LocalFrameData(MapInfo* map_info, uint64_t pc, uint64_t rel_pc, const std::string& function_name, + uint64_t function_offset) + : map_info(map_info), + pc(pc), + rel_pc(rel_pc), + function_name(function_name), + function_offset(function_offset) {} + + MapInfo* map_info; + uint64_t pc; + uint64_t rel_pc; + std::string function_name; + uint64_t function_offset; +}; + +// This is a specialized class that should only be used for doing local unwinds. +// The Unwind call can be made as multiple times on the same object, and it can +// be called by multiple threads at the same time. +// It is designed to be used in debugging circumstances to get a stack trace +// as fast as possible. +class LocalUnwinder { + public: + LocalUnwinder() = default; + LocalUnwinder(const std::vector& skip_libraries) : skip_libraries_(skip_libraries) {} + ~LocalUnwinder() = default; + + bool Init(); + + bool Unwind(std::vector* frame_info, size_t max_frames); + + bool ShouldSkipLibrary(const std::string& map_name); + + MapInfo* GetMapInfo(uint64_t pc); + + ErrorCode LastErrorCode() { return last_error_.code; } + uint64_t LastErrorAddress() { return last_error_.address; } + + private: + pthread_rwlock_t maps_rwlock_; + std::unique_ptr maps_ = nullptr; + std::shared_ptr process_memory_; + std::vector skip_libraries_; + ErrorData last_error_; +}; + +} // namespace unwindstack + +#endif // _LIBUNWINDSTACK_LOCAL_UNWINDER_H diff --git a/libunwindstack/include/unwindstack/Maps.h b/libunwindstack/include/unwindstack/Maps.h index 74e5c4729..67fbed21a 100644 --- a/libunwindstack/include/unwindstack/Maps.h +++ b/libunwindstack/include/unwindstack/Maps.h @@ -87,6 +87,19 @@ class LocalMaps : public RemoteMaps { virtual ~LocalMaps() = default; }; +class LocalUpdatableMaps : public Maps { + public: + LocalUpdatableMaps() : Maps() {} + virtual ~LocalUpdatableMaps(); + + bool Reparse(); + + const std::string GetMapsFile() const override; + + private: + std::vector saved_maps_; +}; + class BufferMaps : public Maps { public: BufferMaps(const char* buffer) : buffer_(buffer) {} diff --git a/libunwindstack/tests/LocalUnwinderTest.cpp b/libunwindstack/tests/LocalUnwinderTest.cpp new file mode 100644 index 000000000..56a18cde2 --- /dev/null +++ b/libunwindstack/tests/LocalUnwinderTest.cpp @@ -0,0 +1,207 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include + +namespace unwindstack { + +static std::vector* 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& function_names, + const std::vector& 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 frame_info; + g_frame_info = &frame_info; + g_unwinder = unwinder; + std::vector 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 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(dlsym(handle, "TestlibLevel1")); + ASSERT_TRUE(unwind_function != nullptr); + + std::vector frame_info; + unwind_function(unwinder_.get(), &frame_info); + + ASSERT_EQ(0, dlclose(handle)); + + std::vector 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 diff --git a/libunwindstack/tests/TestLocal.cpp b/libunwindstack/tests/TestLocal.cpp new file mode 100644 index 000000000..fa0baff36 --- /dev/null +++ b/libunwindstack/tests/TestLocal.cpp @@ -0,0 +1,39 @@ +/* + * 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 + +#include + +extern "C" void TestlibLevel4(void* unwinder_data, void* frame_data) { + unwindstack::LocalUnwinder* unwinder = + reinterpret_cast(unwinder_data); + std::vector* frame_info = + reinterpret_cast*>(frame_data); + unwinder->Unwind(frame_info, 256); +} + +extern "C" void TestlibLevel3(void* unwinder_data, void* frame_data) { + TestlibLevel4(unwinder_data, frame_data); +} + +extern "C" void TestlibLevel2(void* unwinder_data, void* frame_data) { + TestlibLevel3(unwinder_data, frame_data); +} + +extern "C" void TestlibLevel1(void* unwinder_data, void* frame_data) { + TestlibLevel2(unwinder_data, frame_data); +} diff --git a/libunwindstack/tests/UnwindTest.cpp b/libunwindstack/tests/UnwindTest.cpp index 242cc6a6f..83695bbcd 100644 --- a/libunwindstack/tests/UnwindTest.cpp +++ b/libunwindstack/tests/UnwindTest.cpp @@ -106,15 +106,12 @@ static void VerifyUnwind(pid_t pid, Maps* maps, Regs* regs, Unwinder unwinder(512, maps, regs, process_memory); unwinder.Unwind(); - std::string expected_function = expected_function_names.back(); - expected_function_names.pop_back(); for (auto& frame : unwinder.frames()) { - if (frame.function_name == expected_function) { + if (frame.function_name == expected_function_names.back()) { + expected_function_names.pop_back(); if (expected_function_names.empty()) { break; } - expected_function = expected_function_names.back(); - expected_function_names.pop_back(); } }