diff options
author | Christopher Ferris <cferris@google.com> | 2022-04-21 02:04:00 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2022-04-21 02:04:00 +0000 |
commit | 5ff2ac47e5169fe2a2ac34ac10353ed05c85b774 (patch) | |
tree | da0c365d3a91eb655abe41fbe7a08c0e8833ce7a | |
parent | f9eedfa1dfcef1ecca5a174057891a98c62b4f6e (diff) | |
parent | 1654af4eeab2540927aa5a04e674102a8ecf5a81 (diff) | |
download | unwinding-5ff2ac47e5169fe2a2ac34ac10353ed05c85b774.tar.gz |
Add the AndroidUnwinder objects. am: 215644709b am: 0b96208143 am: 7d76ac3305 am: 422b01c9de am: 1654af4eea
Original change: https://android-review.googlesource.com/c/platform/system/unwinding/+/2029306
Change-Id: I007bd3ea1ee28b18fc8328264846da01c01be2f9
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
21 files changed, 1215 insertions, 164 deletions
diff --git a/libbacktrace/UnwindStack.cpp b/libbacktrace/UnwindStack.cpp index 95022fb..d2e65cb 100644 --- a/libbacktrace/UnwindStack.cpp +++ b/libbacktrace/UnwindStack.cpp @@ -95,10 +95,6 @@ bool Backtrace::Unwind(unwindstack::Regs* regs, BacktraceMap* back_map, error->error_code = BACKTRACE_UNWIND_ERROR_INVALID_ELF; break; - case unwindstack::ERROR_SYSTEM_CALL: - error->error_code = BACKTRACE_UNWIND_ERROR_INTERNAL; - break; - case unwindstack::ERROR_THREAD_DOES_NOT_EXIST: error->error_code = BACKTRACE_UNWIND_ERROR_THREAD_DOESNT_EXIST; break; @@ -106,6 +102,13 @@ bool Backtrace::Unwind(unwindstack::Regs* regs, BacktraceMap* back_map, case unwindstack::ERROR_THREAD_TIMEOUT: error->error_code = BACKTRACE_UNWIND_ERROR_THREAD_TIMEOUT; break; + + case unwindstack::ERROR_SYSTEM_CALL: + case unwindstack::ERROR_MAPS_PARSE: + case unwindstack::ERROR_BAD_ARCH: + case unwindstack::ERROR_INVALID_PARAMETER: + error->error_code = BACKTRACE_UNWIND_ERROR_INTERNAL; + break; } } diff --git a/libunwindstack/Android.bp b/libunwindstack/Android.bp index 9ceae7f..bed17e4 100644 --- a/libunwindstack/Android.bp +++ b/libunwindstack/Android.bp @@ -52,6 +52,7 @@ cc_defaults { } libunwindstack_common_src_files = [ + "AndroidUnwinder.cpp", "ArmExidx.cpp", "DexFiles.cpp", "DwarfCfa.cpp", @@ -239,6 +240,7 @@ cc_library { srcs: [ "utils/MemoryFake.cpp", "utils/OfflineUnwindUtils.cpp", + "utils/PidUtils.cpp", "utils/ProcessTracer.cpp", ], } @@ -266,6 +268,7 @@ cc_defaults { defaults: ["libunwindstack_flags"], srcs: [ + "tests/AndroidUnwinderTest.cpp", "tests/ArmExidxDecodeTest.cpp", "tests/ArmExidxExtractTest.cpp", "tests/DexFileTest.cpp", @@ -573,13 +576,13 @@ cc_benchmark { static_libs: [ "libunwindstack_utils", + "libprocinfo", ], target: { android: { static_libs: [ "libmeminfo", - "libprocinfo", ], }, }, diff --git a/libunwindstack/AndroidUnwinder.cpp b/libunwindstack/AndroidUnwinder.cpp new file mode 100644 index 0000000..24e991e --- /dev/null +++ b/libunwindstack/AndroidUnwinder.cpp @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2022 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 <inttypes.h> +#include <sys/types.h> +#include <unistd.h> + +#include <memory> +#include <mutex> +#include <string> +#include <vector> + +#include <android-base/stringprintf.h> +#include <android-base/threads.h> + +#include <unwindstack/AndroidUnwinder.h> +#include <unwindstack/Arch.h> +#include <unwindstack/DexFiles.h> +#include <unwindstack/Error.h> +#include <unwindstack/JitDebug.h> +#include <unwindstack/Maps.h> +#include <unwindstack/Memory.h> +#include <unwindstack/Regs.h> +#include <unwindstack/RegsGetLocal.h> +#include <unwindstack/Unwinder.h> + +#if defined(__BIONIC__) +#include <bionic/reserved_signals.h> +static constexpr int kThreadUnwindSignal = BIONIC_SIGNAL_BACKTRACE; +#else +#include <signal.h> +static int kThreadUnwindSignal = SIGRTMIN; +#endif + +// Use the demangler from libc++. +extern "C" char* __cxa_demangle(const char*, char*, size_t*, int* status); + +namespace unwindstack { + +void AndroidUnwinderData::DemangleFunctionNames() { + for (auto& frame : frames) { + char* demangled_name = __cxa_demangle(frame.function_name.c_str(), nullptr, nullptr, nullptr); + if (demangled_name != nullptr) { + frame.function_name = demangled_name; + free(demangled_name); + } + } +} + +std::string AndroidUnwinderData::GetErrorString() { + std::string error_msg(GetErrorCodeString(error.code)); + if (error.address != 0) { + error_msg += android::base::StringPrintf(" at address 0x%" PRIx64, error.address); + } + return error_msg; +} + +AndroidUnwinder* AndroidUnwinder::Create(pid_t pid) { + if (pid == getpid()) { + return new AndroidLocalUnwinder; + } else { + return new AndroidRemoteUnwinder(pid); + } +} + +bool AndroidUnwinder::Initialize(ErrorData& error) { + // Android stores the jit and dex file location only in the library + // libart.so or libartd.so. + static std::vector<std::string> search_libs [[clang::no_destroy]] = {"libart.so", "libartd.so"}; + + bool initialize = true; + std::call_once(initialize_, [this, &initialize, &error]() { + initialize = InternalInitialize(error); + if (!initialize) { + return; + } + + jit_debug_ = CreateJitDebug(arch_, process_memory_, search_libs); + +#if defined(DEXFILE_SUPPORT) + dex_files_ = CreateDexFiles(arch_, process_memory_, search_libs); +#endif + }); + + return initialize; +} + +std::string AndroidUnwinder::FormatFrame(const FrameData& frame) const { + if (arch_ == ARCH_UNKNOWN) { + return ""; + } + return Unwinder::FormatFrame(arch_, frame); +} + +bool AndroidLocalUnwinder::InternalInitialize(ErrorData& error) { + arch_ = Regs::CurrentArch(); + + maps_.reset(new LocalUpdatableMaps); + if (!maps_->Parse()) { + error.code = ERROR_MAPS_PARSE; + return false; + } + + if (process_memory_ == nullptr) { + process_memory_ = Memory::CreateProcessMemoryThreadCached(getpid()); + } + + return true; +} + +FrameData AndroidUnwinder::BuildFrameFromPcOnly(uint64_t pc) { + return Unwinder::BuildFrameFromPcOnly(pc, arch_, maps_.get(), jit_debug_.get(), process_memory_, + true); +} + +bool AndroidUnwinder::Unwind(AndroidUnwinderData& data) { + return Unwind(std::nullopt, data); +} + +bool AndroidUnwinder::Unwind(std::optional<pid_t> tid, AndroidUnwinderData& data) { + if (!Initialize(data.error)) { + return false; + } + + return InternalUnwind(tid, data); +} + +bool AndroidUnwinder::Unwind(void* ucontext, AndroidUnwinderData& data) { + if (ucontext == nullptr) { + data.error.code = ERROR_INVALID_PARAMETER; + return false; + } + std::unique_ptr<Regs> regs(Regs::CreateFromUcontext(arch_, ucontext)); + return Unwind(regs.get(), data); +} + +bool AndroidUnwinder::Unwind(Regs* initial_regs, AndroidUnwinderData& data) { + if (initial_regs == nullptr) { + data.error.code = ERROR_INVALID_PARAMETER; + return false; + } + + if (!Initialize(data.error)) { + return false; + } + + if (arch_ != initial_regs->Arch()) { + data.error.code = ERROR_BAD_ARCH; + return false; + } + + std::unique_ptr<Regs> regs(initial_regs->Clone()); + if (data.saved_initial_regs) { + (*data.saved_initial_regs).reset(initial_regs->Clone()); + } + Unwinder unwinder(data.max_frames.value_or(max_frames_), maps_.get(), regs.get(), + process_memory_); + unwinder.SetJitDebug(jit_debug_.get()); + unwinder.SetDexFiles(dex_files_.get()); + unwinder.Unwind(data.show_all_frames ? nullptr : &initial_map_names_to_skip_, + &map_suffixes_to_ignore_); + data.frames = unwinder.ConsumeFrames(); + data.error = unwinder.LastError(); + return data.frames.size() != 0; +} + +bool AndroidLocalUnwinder::InternalUnwind(std::optional<pid_t> tid, AndroidUnwinderData& data) { + if (!tid) { + *tid = android::base::GetThreadId(); + } + + if (static_cast<uint64_t>(*tid) == android::base::GetThreadId()) { + // Unwind current thread. + std::unique_ptr<Regs> regs(Regs::CreateFromLocal()); + RegsGetLocal(regs.get()); + return AndroidUnwinder::Unwind(regs.get(), data); + } + + ThreadUnwinder unwinder(data.max_frames.value_or(max_frames_), maps_.get(), process_memory_); + unwinder.SetJitDebug(jit_debug_.get()); + unwinder.SetDexFiles(dex_files_.get()); + std::unique_ptr<Regs>* initial_regs = nullptr; + if (data.saved_initial_regs) { + initial_regs = &data.saved_initial_regs.value(); + } + unwinder.UnwindWithSignal(kThreadUnwindSignal, *tid, initial_regs, + data.show_all_frames ? nullptr : &initial_map_names_to_skip_, + &map_suffixes_to_ignore_); + data.frames = unwinder.ConsumeFrames(); + data.error = unwinder.LastError(); + return data.frames.size() != 0; +} + +bool AndroidRemoteUnwinder::InternalInitialize(ErrorData& error) { + if (arch_ == ARCH_UNKNOWN) { + arch_ = Regs::RemoteGetArch(pid_); + } + if (arch_ == ARCH_UNKNOWN) { + error.code = ERROR_BAD_ARCH; + return false; + } + + maps_.reset(new RemoteMaps(pid_)); + if (!maps_->Parse()) { + error.code = ERROR_MAPS_PARSE; + return false; + } + + if (process_memory_ == nullptr) { + process_memory_ = Memory::CreateProcessMemoryCached(pid_); + } + + return true; +} + +bool AndroidRemoteUnwinder::InternalUnwind(std::optional<pid_t> tid, AndroidUnwinderData& data) { + if (!tid) { + *tid = pid_; + } + + std::unique_ptr<Regs> regs(Regs::RemoteGet(*tid)); + return AndroidUnwinder::Unwind(regs.get(), data); +} + +} // namespace unwindstack diff --git a/libunwindstack/ThreadUnwinder.cpp b/libunwindstack/ThreadUnwinder.cpp index 2bdb910..7b45261 100644 --- a/libunwindstack/ThreadUnwinder.cpp +++ b/libunwindstack/ThreadUnwinder.cpp @@ -72,6 +72,10 @@ static void SignalHandler(int, siginfo_t*, void* sigcontext) { ThreadUnwinder::ThreadUnwinder(size_t max_frames, Maps* maps) : UnwinderFromPid(max_frames, getpid(), Regs::CurrentArch(), maps) {} +ThreadUnwinder::ThreadUnwinder(size_t max_frames, Maps* maps, + std::shared_ptr<Memory>& process_memory) + : UnwinderFromPid(max_frames, getpid(), Regs::CurrentArch(), maps, process_memory) {} + ThreadUnwinder::ThreadUnwinder(size_t max_frames, const ThreadUnwinder* unwinder) : UnwinderFromPid(max_frames, getpid(), Regs::CurrentArch()) { process_memory_ = unwinder->process_memory_; diff --git a/libunwindstack/Unwinder.cpp b/libunwindstack/Unwinder.cpp index f6def23..facff86 100644 --- a/libunwindstack/Unwinder.cpp +++ b/libunwindstack/Unwinder.cpp @@ -145,6 +145,7 @@ void Unwinder::Unwind(const std::vector<std::string>* initial_map_names_to_skip, uint64_t step_pc; uint64_t rel_pc; Elf* elf; + bool ignore_frame = false; if (map_info == nullptr) { step_pc = regs_->pc(); rel_pc = step_pc; @@ -155,7 +156,11 @@ void Unwinder::Unwind(const std::vector<std::string>* initial_map_names_to_skip, } elf = nullptr; } else { - if (ShouldStop(map_suffixes_to_ignore, map_info->name())) { + ignore_frame = + initial_map_names_to_skip != nullptr && + std::find(initial_map_names_to_skip->begin(), initial_map_names_to_skip->end(), + android::base::Basename(map_info->name())) != initial_map_names_to_skip->end(); + if (!ignore_frame && ShouldStop(map_suffixes_to_ignore, map_info->name())) { break; } elf = map_info->GetElf(process_memory_, arch_); @@ -186,9 +191,7 @@ void Unwinder::Unwind(const std::vector<std::string>* initial_map_names_to_skip, } FrameData* frame = nullptr; - if (map_info == nullptr || initial_map_names_to_skip == nullptr || - std::find(initial_map_names_to_skip->begin(), initial_map_names_to_skip->end(), - android::base::Basename(map_info->name())) == initial_map_names_to_skip->end()) { + if (!ignore_frame) { if (regs_->dex_pc() != 0) { // Add a frame to represent the dex file. FillInDexFrame(); @@ -296,8 +299,12 @@ void Unwinder::Unwind(const std::vector<std::string>* initial_map_names_to_skip, } std::string Unwinder::FormatFrame(const FrameData& frame) const { + return FormatFrame(arch_, frame, display_build_id_); +} + +std::string Unwinder::FormatFrame(ArchEnum arch, const FrameData& frame, bool display_build_id) { std::string data; - if (ArchIs32Bit(arch_)) { + if (ArchIs32Bit(arch)) { data += android::base::StringPrintf(" #%02zu pc %08" PRIx64, frame.num, frame.rel_pc); } else { data += android::base::StringPrintf(" #%02zu pc %016" PRIx64, frame.num, frame.rel_pc); @@ -334,7 +341,7 @@ std::string Unwinder::FormatFrame(const FrameData& frame) const { data += ')'; } - if (map_info != nullptr && display_build_id_) { + if (map_info != nullptr && display_build_id) { std::string build_id = map_info->GetPrintableBuildID(); if (!build_id.empty()) { data += " (BuildId: " + build_id + ')'; @@ -347,7 +354,7 @@ std::string Unwinder::FormatFrame(size_t frame_num) const { if (frame_num >= frames_.size()) { return ""; } - return FormatFrame(frames_[frame_num]); + return FormatFrame(arch_, frames_[frame_num], display_build_id_); } void Unwinder::SetJitDebug(JitDebug* jit_debug) { diff --git a/libunwindstack/benchmarks/local_unwind_benchmarks.cpp b/libunwindstack/benchmarks/local_unwind_benchmarks.cpp index dd12ab5..78c8c13 100644 --- a/libunwindstack/benchmarks/local_unwind_benchmarks.cpp +++ b/libunwindstack/benchmarks/local_unwind_benchmarks.cpp @@ -22,6 +22,7 @@ #include <android-base/strings.h> +#include <unwindstack/AndroidUnwinder.h> #include <unwindstack/Maps.h> #include <unwindstack/Memory.h> #include <unwindstack/Regs.h> @@ -99,6 +100,54 @@ static void BM_local_unwind_cached_process_memory(benchmark::State& state) { } BENCHMARK(BM_local_unwind_cached_process_memory); +static void BM_local_android_unwind_uncached_process_memory(benchmark::State& state) { + auto process_memory = unwindstack::Memory::CreateProcessMemory(getpid()); + unwindstack::AndroidLocalUnwinder unwinder(process_memory); + unwindstack::ErrorData error; + if (!unwinder.Initialize(error)) { + state.SkipWithError("Failed to initialize."); + } + + for (auto _ : state) { + if (LocalCall1( + [](void* u) -> size_t { + unwindstack::AndroidLocalUnwinder* unwinder = + reinterpret_cast<unwindstack::AndroidLocalUnwinder*>(u); + unwindstack::AndroidUnwinderData data; + unwinder->Unwind(data); + return data.frames.size(); + }, + &unwinder) < 5) { + state.SkipWithError("Failed to unwind."); + } + } +} +BENCHMARK(BM_local_android_unwind_uncached_process_memory); + +static void BM_local_android_unwind_cached_process_memory(benchmark::State& state) { + auto process_memory = unwindstack::Memory::CreateProcessMemoryCached(getpid()); + unwindstack::AndroidLocalUnwinder unwinder(process_memory); + unwindstack::ErrorData error; + if (!unwinder.Initialize(error)) { + state.SkipWithError("Failed to initialize."); + } + + for (auto _ : state) { + if (LocalCall1( + [](void* u) -> size_t { + unwindstack::AndroidLocalUnwinder* unwinder = + reinterpret_cast<unwindstack::AndroidLocalUnwinder*>(u); + unwindstack::AndroidUnwinderData data; + unwinder->Unwind(data); + return data.frames.size(); + }, + &unwinder) < 5) { + state.SkipWithError("Failed to unwind."); + } + } +} +BENCHMARK(BM_local_android_unwind_cached_process_memory); + static void BM_local_unwind_local_updatable_maps_uncached(benchmark::State& state) { auto process_memory = unwindstack::Memory::CreateProcessMemory(getpid()); unwindstack::LocalUpdatableMaps maps; diff --git a/libunwindstack/benchmarks/remote_unwind_benchmarks.cpp b/libunwindstack/benchmarks/remote_unwind_benchmarks.cpp index ef07e39..29c416d 100644 --- a/libunwindstack/benchmarks/remote_unwind_benchmarks.cpp +++ b/libunwindstack/benchmarks/remote_unwind_benchmarks.cpp @@ -24,35 +24,26 @@ #include <benchmark/benchmark.h> +#include <unwindstack/AndroidUnwinder.h> #include <unwindstack/Maps.h> #include <unwindstack/Memory.h> #include <unwindstack/Regs.h> #include <unwindstack/Unwinder.h> #include "MemoryRemote.h" +#include "PidUtils.h" #include "tests/TestUtils.h" static bool WaitForRemote(pid_t pid, volatile bool* ready_ptr) { - usleep(1000); - for (size_t i = 0; i < 1000; i++) { - if (ptrace(PTRACE_ATTACH, pid, 0, 0) == 0) { - unwindstack::TestQuiescePid(pid); - - unwindstack::MemoryRemote memory(pid); - bool ready; - uint64_t ready_addr = reinterpret_cast<uint64_t>(ready_ptr); - if (memory.ReadFully(ready_addr, &ready, sizeof(ready)) && ready) { - return true; - } - } else if (errno != ESRCH) { - // Attach failed with unknown error. - perror("Ptrace failed:"); - return false; + return unwindstack::RunWhenQuiesced(pid, true, [pid, ready_ptr]() { + unwindstack::MemoryRemote memory(pid); + bool ready; + uint64_t ready_addr = reinterpret_cast<uint64_t>(ready_ptr); + if (memory.ReadFully(ready_addr, &ready, sizeof(ready)) && ready) { + return unwindstack::PID_RUN_PASS; } - usleep(5000); - } - printf("Pid %d did not quiesce in a timely fashion.\n", pid); - return false; + return unwindstack::PID_RUN_KEEP_GOING; + }); } size_t RemoteCall6(volatile bool* ready) { @@ -141,3 +132,42 @@ static void BM_remote_unwind_cached(benchmark::State& state) { RemoteUnwind(state, true); } BENCHMARK(BM_remote_unwind_cached); + +static void RemoteAndroidUnwind(benchmark::State& state, bool cached) { + pid_t pid = StartRemoteRun(); + if (pid == -1) { + state.SkipWithError("Failed to start remote process."); + } + unwindstack::TestScopedPidReaper reap(pid); + + std::shared_ptr<unwindstack::Memory> process_memory; + if (cached) { + process_memory = unwindstack::Memory::CreateProcessMemoryCached(pid); + } else { + process_memory = unwindstack::Memory::CreateProcessMemory(pid); + } + unwindstack::AndroidRemoteUnwinder unwinder(pid, process_memory); + unwindstack::ErrorData error; + if (!unwinder.Initialize(error)) { + state.SkipWithError("Failed to initialize unwinder."); + } + + for (auto _ : state) { + unwindstack::AndroidUnwinderData data; + if (!unwinder.Unwind(data) || data.frames.size() < 5) { + state.SkipWithError("Failed to unwind properly."); + } + } + + ptrace(PTRACE_DETACH, pid, 0, 0); +} + +static void BM_remote_android_unwind_uncached(benchmark::State& state) { + RemoteAndroidUnwind(state, true); +} +BENCHMARK(BM_remote_android_unwind_uncached); + +static void BM_remote_android_unwind_cached(benchmark::State& state) { + RemoteAndroidUnwind(state, true); +} +BENCHMARK(BM_remote_android_unwind_cached); diff --git a/libunwindstack/include/unwindstack/AndroidUnwinder.h b/libunwindstack/include/unwindstack/AndroidUnwinder.h new file mode 100644 index 0000000..3f82429 --- /dev/null +++ b/libunwindstack/include/unwindstack/AndroidUnwinder.h @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2022 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_ANDROID_UNWINDER_H +#define _LIBUNWINDSTACK_ANDROID_UNWINDER_H + +#include <stdint.h> +#include <sys/types.h> +#include <unistd.h> + +#include <memory> +#include <mutex> +#include <optional> +#include <string> +#include <utility> +#include <vector> + +#include <unwindstack/Arch.h> +#include <unwindstack/DexFiles.h> +#include <unwindstack/Error.h> +#include <unwindstack/JitDebug.h> +#include <unwindstack/Maps.h> +#include <unwindstack/Memory.h> +#include <unwindstack/Regs.h> +#include <unwindstack/SharedString.h> +#include <unwindstack/Unwinder.h> + +namespace unwindstack { + +struct AndroidUnwinderData { + AndroidUnwinderData() = default; + explicit AndroidUnwinderData(const size_t max_frames) : max_frames(max_frames) {} + explicit AndroidUnwinderData(const bool show_all_frames) : show_all_frames(show_all_frames) {} + + void DemangleFunctionNames(); + + std::string GetErrorString(); + + std::vector<FrameData> frames; + ErrorData error; + std::optional<std::unique_ptr<Regs>> saved_initial_regs; + const std::optional<size_t> max_frames; + const bool show_all_frames = false; +}; + +class AndroidUnwinder { + public: + AndroidUnwinder(pid_t pid) : pid_(pid) {} + AndroidUnwinder(pid_t pid, std::shared_ptr<Memory>& memory) + : pid_(pid), process_memory_(memory) {} + AndroidUnwinder(pid_t pid, ArchEnum arch) : pid_(pid), arch_(arch) {} + AndroidUnwinder(pid_t pid, const std::vector<std::string> initial_map_names_to_skip) + : pid_(pid), initial_map_names_to_skip_(std::move(initial_map_names_to_skip)) {} + AndroidUnwinder(pid_t pid, const std::vector<std::string> initial_map_names_to_skip, + const std::vector<std::string> map_suffixes_to_ignore) + : pid_(pid), + initial_map_names_to_skip_(std::move(initial_map_names_to_skip)), + map_suffixes_to_ignore_(std::move(map_suffixes_to_ignore)) {} + virtual ~AndroidUnwinder() = default; + + bool Initialize(ErrorData& error); + + std::shared_ptr<Memory>& GetProcessMemory() { return process_memory_; } + unwindstack::Maps* GetMaps() { return maps_.get(); } + + const JitDebug& GetJitDebug() { return *jit_debug_.get(); } + const DexFiles& GetDexFiles() { return *dex_files_.get(); } + + std::string FormatFrame(const FrameData& frame) const; + + bool Unwind(AndroidUnwinderData& data); + bool Unwind(std::optional<pid_t> tid, AndroidUnwinderData& data); + bool Unwind(void* ucontext, AndroidUnwinderData& data); + bool Unwind(Regs* initial_regs, AndroidUnwinderData& data); + + FrameData BuildFrameFromPcOnly(uint64_t pc); + + static AndroidUnwinder* Create(pid_t pid); + + protected: + virtual bool InternalInitialize(ErrorData& error) = 0; + + virtual bool InternalUnwind(std::optional<pid_t> tid, AndroidUnwinderData& data) = 0; + + pid_t pid_; + + size_t max_frames_ = kMaxNumFrames; + std::vector<std::string> initial_map_names_to_skip_; + std::vector<std::string> map_suffixes_to_ignore_; + std::once_flag initialize_; + + ArchEnum arch_ = ARCH_UNKNOWN; + + std::shared_ptr<Maps> maps_; + std::shared_ptr<Memory> process_memory_; + std::unique_ptr<JitDebug> jit_debug_; + std::unique_ptr<DexFiles> dex_files_; + + static constexpr size_t kMaxNumFrames = 512; +}; + +class AndroidLocalUnwinder : public AndroidUnwinder { + public: + AndroidLocalUnwinder() : AndroidUnwinder(getpid()) { + initial_map_names_to_skip_.emplace_back(kUnwindstackLib); + } + AndroidLocalUnwinder(std::shared_ptr<Memory>& process_memory) + : AndroidUnwinder(getpid(), process_memory) { + initial_map_names_to_skip_.emplace_back(kUnwindstackLib); + } + AndroidLocalUnwinder(const std::vector<std::string>& initial_map_names_to_skip) + : AndroidUnwinder(getpid(), initial_map_names_to_skip) { + initial_map_names_to_skip_.emplace_back(kUnwindstackLib); + } + AndroidLocalUnwinder(const std::vector<std::string>& initial_map_names_to_skip, + const std::vector<std::string>& map_suffixes_to_ignore) + : AndroidUnwinder(getpid(), initial_map_names_to_skip, map_suffixes_to_ignore) { + initial_map_names_to_skip_.emplace_back(kUnwindstackLib); + } + virtual ~AndroidLocalUnwinder() = default; + + protected: + static constexpr const char* kUnwindstackLib = "libunwindstack.so"; + + bool InternalInitialize(ErrorData& error) override; + + bool InternalUnwind(std::optional<pid_t> tid, AndroidUnwinderData& data) override; +}; + +class AndroidRemoteUnwinder : public AndroidUnwinder { + public: + AndroidRemoteUnwinder(pid_t pid) : AndroidUnwinder(pid) {} + AndroidRemoteUnwinder(pid_t pid, std::shared_ptr<Memory>& process_memory) + : AndroidUnwinder(pid, process_memory) {} + AndroidRemoteUnwinder(pid_t pid, ArchEnum arch) : AndroidUnwinder(pid, arch) {} + AndroidRemoteUnwinder(pid_t pid, const std::vector<std::string> initial_map_names_to_skip) + : AndroidUnwinder(pid, initial_map_names_to_skip) {} + AndroidRemoteUnwinder(pid_t pid, const std::vector<std::string> initial_map_names_to_skip, + const std::vector<std::string> map_suffixes_to_ignore) + : AndroidUnwinder(pid, initial_map_names_to_skip, map_suffixes_to_ignore) {} + virtual ~AndroidRemoteUnwinder() = default; + + protected: + bool InternalInitialize(ErrorData& error) override; + + bool InternalUnwind(std::optional<pid_t> tid, AndroidUnwinderData& data) override; +}; + +} // namespace unwindstack + +#endif // _LIBUNWINDSTACK_ANDROID_UNWINDER_H diff --git a/libunwindstack/include/unwindstack/Error.h b/libunwindstack/include/unwindstack/Error.h index 1610d55..abd59ad 100644 --- a/libunwindstack/include/unwindstack/Error.h +++ b/libunwindstack/include/unwindstack/Error.h @@ -41,7 +41,10 @@ enum ErrorCode : uint8_t { // not exist. ERROR_THREAD_TIMEOUT, // Timeout trying to unwind a local thread. ERROR_SYSTEM_CALL, // System call failed while unwinding. - ERROR_MAX = ERROR_SYSTEM_CALL, + ERROR_BAD_ARCH, // Arch invalid (none, or mismatched). + ERROR_MAPS_PARSE, // Failed to parse maps data. + ERROR_INVALID_PARAMETER, // Invalid parameter passed to function. + ERROR_MAX = ERROR_INVALID_PARAMETER, }; static inline const char* GetErrorCodeString(ErrorCode error) { @@ -68,6 +71,12 @@ static inline const char* GetErrorCodeString(ErrorCode error) { return "Thread Timeout"; case ERROR_SYSTEM_CALL: return "System Call Failed"; + case ERROR_BAD_ARCH: + return "Invalid arch detected"; + case ERROR_MAPS_PARSE: + return "Failed to parse maps"; + case ERROR_INVALID_PARAMETER: + return "Invalid parameter"; } } diff --git a/libunwindstack/include/unwindstack/Unwinder.h b/libunwindstack/include/unwindstack/Unwinder.h index de3438f..462d39b 100644 --- a/libunwindstack/include/unwindstack/Unwinder.h +++ b/libunwindstack/include/unwindstack/Unwinder.h @@ -83,6 +83,9 @@ class Unwinder { std::string FormatFrame(size_t frame_num) const; std::string FormatFrame(const FrameData& frame) const; + static std::string FormatFrame(ArchEnum arch, const FrameData& frame, + bool display_build_id = true); + void SetArch(ArchEnum arch) { arch_ = arch; }; void SetJitDebug(JitDebug* jit_debug); @@ -102,6 +105,7 @@ class Unwinder { void SetDexFiles(DexFiles* dex_files); + const ErrorData& LastError() { return last_error_; } ErrorCode LastErrorCode() { return last_error_.code; } const char* LastErrorCodeString() { return GetErrorCodeString(last_error_.code); } uint64_t LastErrorAddress() { return last_error_.address; } @@ -120,6 +124,8 @@ class Unwinder { Unwinder(size_t max_frames, Maps* maps = nullptr) : max_frames_(max_frames), maps_(maps) {} Unwinder(size_t max_frames, ArchEnum arch, Maps* maps = nullptr) : max_frames_(max_frames), maps_(maps), arch_(arch) {} + Unwinder(size_t max_frames, ArchEnum arch, Maps* maps, std::shared_ptr<Memory>& process_memory) + : max_frames_(max_frames), maps_(maps), process_memory_(process_memory), arch_(arch) {} void ClearErrors() { warnings_ = WARNING_NONE; @@ -149,14 +155,15 @@ class UnwinderFromPid : public Unwinder { public: UnwinderFromPid(size_t max_frames, pid_t pid, Maps* maps = nullptr) : Unwinder(max_frames, maps), pid_(pid) {} + UnwinderFromPid(size_t max_frames, pid_t pid, std::shared_ptr<Memory>& process_memory) + : Unwinder(max_frames, nullptr, process_memory), pid_(pid) {} UnwinderFromPid(size_t max_frames, pid_t pid, ArchEnum arch, Maps* maps = nullptr) : Unwinder(max_frames, arch, maps), pid_(pid) {} + UnwinderFromPid(size_t max_frames, pid_t pid, ArchEnum arch, Maps* maps, + std::shared_ptr<Memory>& process_memory) + : Unwinder(max_frames, arch, maps, process_memory), pid_(pid) {} virtual ~UnwinderFromPid() = default; - void SetProcessMemory(std::shared_ptr<Memory>& process_memory) { - process_memory_ = process_memory; - } - bool Init(); void Unwind(const std::vector<std::string>* initial_map_names_to_skip = nullptr, @@ -173,6 +180,7 @@ class UnwinderFromPid : public Unwinder { class ThreadUnwinder : public UnwinderFromPid { public: ThreadUnwinder(size_t max_frames, Maps* maps = nullptr); + ThreadUnwinder(size_t max_frames, Maps* maps, std::shared_ptr<Memory>& process_memory); ThreadUnwinder(size_t max_frames, const ThreadUnwinder* unwinder); virtual ~ThreadUnwinder() = default; diff --git a/libunwindstack/tests/AndroidUnwinderTest.cpp b/libunwindstack/tests/AndroidUnwinderTest.cpp new file mode 100644 index 0000000..1794acc --- /dev/null +++ b/libunwindstack/tests/AndroidUnwinderTest.cpp @@ -0,0 +1,426 @@ +/* + * Copyright (C) 2022 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 <stdint.h> +#include <string.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#include <gtest/gtest.h> + +#include <atomic> +#include <string> +#include <thread> +#include <vector> + +#include <android-base/strings.h> +#include <android-base/threads.h> + +#include <unwindstack/AndroidUnwinder.h> +#include <unwindstack/Error.h> +#include <unwindstack/Regs.h> +#include <unwindstack/RegsArm.h> +#include <unwindstack/RegsArm64.h> +#include <unwindstack/RegsGetLocal.h> +#include <unwindstack/RegsX86.h> +#include <unwindstack/RegsX86_64.h> +#include <unwindstack/UcontextArm.h> +#include <unwindstack/UcontextArm64.h> +#include <unwindstack/UcontextX86.h> +#include <unwindstack/UcontextX86_64.h> +#include <unwindstack/Unwinder.h> + +#include "PidUtils.h" +#include "TestUtils.h" + +namespace unwindstack { + +static std::string GetBacktrace(AndroidUnwinder& unwinder, std::vector<FrameData>& frames) { + std::string backtrace_str; + for (auto& frame : frames) { + backtrace_str += unwinder.FormatFrame(frame) + '\n'; + } + return backtrace_str; +} + +static pid_t ForkWaitForever() { + pid_t pid; + if ((pid = fork()) == 0) { + // Do a loop that guarantees the terminating leaf frame will be in + // the test executable and not any other library function. + bool run = true; + while (run) { + DoNotOptimize(run = true); + } + exit(1); + } + return pid; +} + +TEST(AndroidUnwinderDataTest, demangle_function_names) { + AndroidUnwinderData data; + + // Add a few frames with and without demangled function names. + data.frames.resize(4); + data.frames[0].function_name = "no_demangle()"; + data.frames[1].function_name = "_Z4fakeb"; + data.frames[3].function_name = "_Z8demanglei"; + + data.DemangleFunctionNames(); + EXPECT_EQ("no_demangle()", data.frames[0].function_name); + EXPECT_EQ("fake(bool)", data.frames[1].function_name); + EXPECT_EQ("", data.frames[2].function_name); + EXPECT_EQ("demangle(int)", data.frames[3].function_name); + + // Make sure that this action is idempotent. + data.DemangleFunctionNames(); + EXPECT_EQ("no_demangle()", data.frames[0].function_name); + EXPECT_EQ("fake(bool)", data.frames[1].function_name); + EXPECT_EQ("", data.frames[2].function_name); + EXPECT_EQ("demangle(int)", data.frames[3].function_name); +} + +TEST(AndroidUnwinderDataTest, get_error_string) { + AndroidUnwinderData data; + + EXPECT_EQ("None", data.GetErrorString()); + data.error.code = ERROR_INVALID_ELF; + EXPECT_EQ("Invalid Elf", data.GetErrorString()); + data.error.code = ERROR_MEMORY_INVALID; + EXPECT_EQ("Memory Invalid", data.GetErrorString()); + data.error.address = 0x1000; + EXPECT_EQ("Memory Invalid at address 0x1000", data.GetErrorString()); +} + +TEST(AndroidUnwinderTest, unwind_errors) { + AndroidLocalUnwinder unwinder; + + AndroidUnwinderData data; + void* ucontext = nullptr; + EXPECT_FALSE(unwinder.Unwind(ucontext, data)); + EXPECT_EQ(ERROR_INVALID_PARAMETER, data.error.code); + std::unique_ptr<Regs> regs; + EXPECT_FALSE(unwinder.Unwind(regs.get(), data)); + EXPECT_EQ(ERROR_INVALID_PARAMETER, data.error.code); + // Make sure that we are using a different arch from the + // current arch. + if (Regs::CurrentArch() == ARCH_ARM) { + regs.reset(new RegsArm64); + } else { + regs.reset(new RegsArm); + } + EXPECT_FALSE(unwinder.Unwind(regs.get(), data)); + EXPECT_EQ(ERROR_BAD_ARCH, data.error.code); +} + +TEST(AndroidUnwinderTest, create) { + // Verify the local unwinder object is created. + std::unique_ptr<AndroidUnwinder> unwinder(AndroidUnwinder::Create(getpid())); + AndroidUnwinderData data; + ASSERT_TRUE(unwinder->Unwind(data)); + + pid_t pid = ForkWaitForever(); + ASSERT_NE(-1, pid); + TestScopedPidReaper reap(pid); + + ASSERT_TRUE(RunWhenQuiesced(pid, false, [pid, &unwinder]() { + // Verify the remote unwinder object is created. + unwinder.reset(AndroidUnwinder::Create(pid)); + AndroidUnwinderData data; + if (!unwinder->Unwind(data)) { + printf("Failed to unwind %s\n", data.GetErrorString().c_str()); + return PID_RUN_FAIL; + } + return PID_RUN_PASS; + })); +} + +TEST(AndroidLocalUnwinderTest, initialize_before) { + AndroidLocalUnwinder unwinder; + ErrorData error; + ASSERT_TRUE(unwinder.Initialize(error)); + + AndroidUnwinderData data; + ASSERT_TRUE(unwinder.Unwind(data)); +} + +TEST(AndroidLocalUnwinderTest, suffix_ignore) { + AndroidLocalUnwinder unwinder(std::vector<std::string>{}, std::vector<std::string>{"so"}); + AndroidUnwinderData data; + // This should work as long as the first frame is in the test executable. + ASSERT_TRUE(unwinder.Unwind(data)); + // Make sure the unwind doesn't include any .so frames. + for (const auto& frame : data.frames) { + ASSERT_TRUE(frame.map_info == nullptr || + !android::base::EndsWith(frame.map_info->name(), ".so")) + << GetBacktrace(unwinder, data.frames); + } +} + +TEST(AndroidUnwinderTest, verify_all_unwind_functions) { + AndroidLocalUnwinder unwinder; + AndroidUnwinderData data; + ASSERT_TRUE(unwinder.Unwind(data)); + ASSERT_TRUE(unwinder.Unwind(std::nullopt, data)); + ASSERT_TRUE(unwinder.Unwind(getpid(), data)); + std::unique_ptr<Regs> regs(Regs::CreateFromLocal()); + RegsGetLocal(regs.get()); + + void* ucontext; + switch (regs->Arch()) { + case ARCH_ARM: { + arm_ucontext_t* arm_ucontext = + reinterpret_cast<arm_ucontext_t*>(malloc(sizeof(arm_ucontext_t))); + ucontext = arm_ucontext; + memcpy(&arm_ucontext->uc_mcontext.regs[0], regs->RawData(), ARM_REG_LAST * sizeof(uint32_t)); + } break; + case ARCH_ARM64: { + arm64_ucontext_t* arm64_ucontext = + reinterpret_cast<arm64_ucontext_t*>(malloc(sizeof(arm64_ucontext_t))); + ucontext = arm64_ucontext; + memcpy(&arm64_ucontext->uc_mcontext.regs[0], regs->RawData(), + ARM64_REG_LAST * sizeof(uint64_t)); + } break; + case ARCH_X86: { + x86_ucontext_t* x86_ucontext = + reinterpret_cast<x86_ucontext_t*>(malloc(sizeof(x86_ucontext_t))); + ucontext = x86_ucontext; + RegsX86* regs_x86 = static_cast<RegsX86*>(regs.get()); + + x86_ucontext->uc_mcontext.edi = (*regs_x86)[X86_REG_EDI]; + x86_ucontext->uc_mcontext.esi = (*regs_x86)[X86_REG_ESI]; + x86_ucontext->uc_mcontext.ebp = (*regs_x86)[X86_REG_EBP]; + x86_ucontext->uc_mcontext.esp = (*regs_x86)[X86_REG_ESP]; + x86_ucontext->uc_mcontext.ebx = (*regs_x86)[X86_REG_EBX]; + x86_ucontext->uc_mcontext.edx = (*regs_x86)[X86_REG_EDX]; + x86_ucontext->uc_mcontext.ecx = (*regs_x86)[X86_REG_ECX]; + x86_ucontext->uc_mcontext.eax = (*regs_x86)[X86_REG_EAX]; + x86_ucontext->uc_mcontext.eip = (*regs_x86)[X86_REG_EIP]; + } break; + case ARCH_X86_64: { + x86_64_ucontext_t* x86_64_ucontext = + reinterpret_cast<x86_64_ucontext_t*>(malloc(sizeof(x86_64_ucontext_t))); + ucontext = x86_64_ucontext; + RegsX86_64* regs_x86_64 = static_cast<RegsX86_64*>(regs.get()); + + memcpy(&x86_64_ucontext->uc_mcontext.r8, &(*regs_x86_64)[X86_64_REG_R8], + 8 * sizeof(uint64_t)); + + x86_64_ucontext->uc_mcontext.rdi = (*regs_x86_64)[X86_64_REG_RDI]; + x86_64_ucontext->uc_mcontext.rsi = (*regs_x86_64)[X86_64_REG_RSI]; + x86_64_ucontext->uc_mcontext.rbp = (*regs_x86_64)[X86_64_REG_RBP]; + x86_64_ucontext->uc_mcontext.rbx = (*regs_x86_64)[X86_64_REG_RBX]; + x86_64_ucontext->uc_mcontext.rdx = (*regs_x86_64)[X86_64_REG_RDX]; + x86_64_ucontext->uc_mcontext.rax = (*regs_x86_64)[X86_64_REG_RAX]; + x86_64_ucontext->uc_mcontext.rcx = (*regs_x86_64)[X86_64_REG_RCX]; + x86_64_ucontext->uc_mcontext.rsp = (*regs_x86_64)[X86_64_REG_RSP]; + x86_64_ucontext->uc_mcontext.rip = (*regs_x86_64)[X86_64_REG_RIP]; + } break; + default: + ucontext = nullptr; + break; + } + ASSERT_TRUE(ucontext != nullptr); + ASSERT_TRUE(unwinder.Unwind(ucontext, data)); + free(ucontext); + AndroidUnwinderData reg_data; + ASSERT_TRUE(unwinder.Unwind(regs.get(), reg_data)); + ASSERT_EQ(data.frames.size(), reg_data.frames.size()); + // Make sure all of the frame data is exactly the same. + for (size_t i = 0; i < data.frames.size(); i++) { + SCOPED_TRACE("\nMismatch at Frame " + std::to_string(i) + "\nucontext trace:\n" + + GetBacktrace(unwinder, data.frames) + "\nregs trace:\n" + + GetBacktrace(unwinder, reg_data.frames)); + const auto& frame_context = data.frames[i]; + const auto& frame_reg = reg_data.frames[i]; + ASSERT_EQ(frame_context.num, frame_reg.num); + ASSERT_EQ(frame_context.rel_pc, frame_reg.rel_pc); + ASSERT_EQ(frame_context.pc, frame_reg.pc); + ASSERT_EQ(frame_context.sp, frame_reg.sp); + ASSERT_STREQ(frame_context.function_name.c_str(), frame_reg.function_name.c_str()); + ASSERT_EQ(frame_context.function_offset, frame_reg.function_offset); + ASSERT_EQ(frame_context.map_info.get(), frame_reg.map_info.get()); + } +} + +TEST(AndroidLocalUnwinderTest, unwind_current_thread) { + AndroidLocalUnwinder unwinder; + AndroidUnwinderData data; + ASSERT_TRUE(unwinder.Unwind(data)); + // Verify that the libunwindstack.so does not appear in the first frame. + ASSERT_TRUE(data.frames[0].map_info == nullptr || + !android::base::EndsWith(data.frames[0].map_info->name(), "/libunwindstack.so")) + << "libunwindstack.so not removed properly\n" + << GetBacktrace(unwinder, data.frames); +} + +TEST(AndroidLocalUnwinderTest, unwind_current_thread_show_all_frames) { + AndroidLocalUnwinder unwinder; + AndroidUnwinderData data(true); + ASSERT_TRUE(unwinder.Unwind(data)); + // Verify that the libunwindstack.so does appear in the first frame. + ASSERT_TRUE(data.frames[0].map_info != nullptr && + android::base::EndsWith(data.frames[0].map_info->name(), "/libunwindstack.so")) + << "libunwindstack.so was removed improperly\n" + << GetBacktrace(unwinder, data.frames); +} + +TEST(AndroidLocalUnwinderTest, unwind_different_thread) { + std::atomic<pid_t> tid; + std::atomic_bool keep_running = true; + std::thread thread([&tid, &keep_running] { + tid = android::base::GetThreadId(); + while (keep_running) { + } + return nullptr; + }); + + while (tid == 0) { + } + + { + AndroidLocalUnwinder unwinder; + AndroidUnwinderData data; + ASSERT_TRUE(unwinder.Unwind(data)); + // Verify that the libunwindstack.so does not appear in the first frame. + ASSERT_TRUE(data.frames[0].map_info == nullptr || + !android::base::EndsWith(data.frames[0].map_info->name(), "/libunwindstack.so")) + << "libunwindstack.so not removed properly\n" + << GetBacktrace(unwinder, data.frames); + } + + { + AndroidLocalUnwinder unwinder; + AndroidUnwinderData data(true); + ASSERT_TRUE(unwinder.Unwind(data)); + // Verify that the libunwindstack.so does appear in the first frame. + ASSERT_TRUE(data.frames[0].map_info != nullptr && + android::base::EndsWith(data.frames[0].map_info->name(), "/libunwindstack.so")) + << "libunwindstack.so was removed improperly\n" + << GetBacktrace(unwinder, data.frames); + } + + // Allow the thread to terminate normally. + keep_running = false; + thread.join(); +} + +TEST(AndroidRemoteUnwinderTest, initialize_before) { + pid_t pid = ForkWaitForever(); + ASSERT_NE(-1, pid); + TestScopedPidReaper reap(pid); + + ASSERT_TRUE(Attach(pid)); + + AndroidRemoteUnwinder unwinder(pid); + ErrorData error; + ASSERT_TRUE(unwinder.Initialize(error)); + + AndroidUnwinderData data; + ASSERT_TRUE(unwinder.Unwind(data)); + + ASSERT_TRUE(Detach(pid)); +} + +static bool Verify(pid_t pid, std::function<PidRunEnum(const FrameData& frame)> fn) { + return RunWhenQuiesced(pid, false, [pid, &fn]() { + AndroidRemoteUnwinder unwinder(pid); + AndroidUnwinderData data; + if (!unwinder.Unwind(data)) { + printf("Failed to unwind %s\n", data.GetErrorString().c_str()); + return PID_RUN_FAIL; + } + const auto& frame = data.frames[0]; + return fn(frame); + }); +} + +TEST(AndroidRemoteUnwinderTest, skip_libraries) { + void* test_lib = GetTestLibHandle(); + ASSERT_TRUE(test_lib != nullptr); + int (*wait_func)() = reinterpret_cast<int (*)()>(dlsym(test_lib, "WaitForever")); + ASSERT_TRUE(wait_func != nullptr); + + pid_t pid; + if ((pid = fork()) == 0) { + DoNotOptimize(wait_func()); + exit(0); + } + ASSERT_NE(-1, pid); + TestScopedPidReaper reap(pid); + + ASSERT_TRUE(Verify(pid, [pid](const FrameData& frame) { + // Make sure that the frame is in the dlopen'd library before proceeding. + if (frame.map_info == nullptr || + !android::base::EndsWith(frame.map_info->name(), "/libunwindstack_local.so")) { + return PID_RUN_KEEP_GOING; + } + + // Do an unwind removing the libunwindstack_local.so library. + AndroidRemoteUnwinder unwinder(pid, std::vector<std::string>{"libunwindstack_local.so"}); + AndroidUnwinderData data; + if (!unwinder.Unwind(data)) { + printf("Failed to unwind %s\n", data.GetErrorString().c_str()); + return PID_RUN_FAIL; + } + + // Verify that library is properly ignored. + if (android::base::EndsWith(data.frames[0].map_info->name(), "/libunwindstack_local.so")) { + printf("Failed to strip libunwindstack_local.so\n%s\n", + GetBacktrace(unwinder, data.frames).c_str()); + return PID_RUN_FAIL; + } + return PID_RUN_PASS; + })); +} + +TEST(AndroidRemoteUnwinderTest, suffix_ignore) { + pid_t pid = ForkWaitForever(); + ASSERT_NE(-1, pid); + TestScopedPidReaper reap(pid); + + ASSERT_TRUE(Verify(pid, [pid](const FrameData& frame) { + // Wait until the forked process is no longer in libc.so. + if (frame.map_info != nullptr && android::base::EndsWith(frame.map_info->name(), ".so")) { + return PID_RUN_KEEP_GOING; + } + + AndroidRemoteUnwinder unwinder(pid, std::vector<std::string>{}, std::vector<std::string>{"so"}); + AndroidUnwinderData data; + if (!unwinder.Unwind(data)) { + printf("Failed to unwind %s\n", data.GetErrorString().c_str()); + + AndroidRemoteUnwinder normal_unwinder(pid); + if (normal_unwinder.Unwind(data)) { + printf("Full unwind %s\n", GetBacktrace(normal_unwinder, data.frames).c_str()); + } + return PID_RUN_FAIL; + } + + // Make sure the unwind doesn't include any .so frames. + for (const auto& frame : data.frames) { + if (frame.map_info != nullptr && android::base::EndsWith(frame.map_info->name(), ".so")) { + printf("Found unexpected .so frame\n%s\n", GetBacktrace(unwinder, data.frames).c_str()); + return PID_RUN_FAIL; + } + } + return PID_RUN_PASS; + })); +} + +} // namespace unwindstack diff --git a/libunwindstack/tests/MemoryMteTest.cpp b/libunwindstack/tests/MemoryMteTest.cpp index 9aab0c0..8129394 100644 --- a/libunwindstack/tests/MemoryMteTest.cpp +++ b/libunwindstack/tests/MemoryMteTest.cpp @@ -25,6 +25,7 @@ #include "MemoryLocal.h" #include "MemoryRemote.h" +#include "PidUtils.h" #include "TestUtils.h" namespace unwindstack { @@ -68,14 +69,14 @@ TEST(MemoryMteTest, remote_read_tag) { ASSERT_LT(0, pid); TestScopedPidReaper reap(pid); - ASSERT_TRUE(TestAttach(pid)); + ASSERT_TRUE(Attach(pid)); MemoryRemote remote(pid); EXPECT_EQ(1, remote.ReadTag(mapping)); EXPECT_EQ(0, remote.ReadTag(mapping + 16)); - ASSERT_TRUE(TestDetach(pid)); + ASSERT_TRUE(Detach(pid)); } TEST(MemoryMteTest, local_read_tag) { diff --git a/libunwindstack/tests/MemoryRemoteTest.cpp b/libunwindstack/tests/MemoryRemoteTest.cpp index 2b6e682..ed58eb9 100644 --- a/libunwindstack/tests/MemoryRemoteTest.cpp +++ b/libunwindstack/tests/MemoryRemoteTest.cpp @@ -32,6 +32,7 @@ #include "MemoryRemote.h" +#include "PidUtils.h" #include "TestUtils.h" #include "utils/MemoryFake.h" @@ -49,7 +50,7 @@ TEST(MemoryRemoteTest, read) { ASSERT_LT(0, pid); TestScopedPidReaper reap(pid); - ASSERT_TRUE(TestAttach(pid)); + ASSERT_TRUE(Attach(pid)); MemoryRemote remote(pid); @@ -59,7 +60,7 @@ TEST(MemoryRemoteTest, read) { ASSERT_EQ(0x4cU, dst[i]) << "Failed at byte " << i; } - ASSERT_TRUE(TestDetach(pid)); + ASSERT_TRUE(Detach(pid)); } TEST(MemoryRemoteTest, read_large) { @@ -78,7 +79,7 @@ TEST(MemoryRemoteTest, read_large) { ASSERT_LT(0, pid); TestScopedPidReaper reap(pid); - ASSERT_TRUE(TestAttach(pid)); + ASSERT_TRUE(Attach(pid)); MemoryRemote remote(pid); @@ -88,7 +89,7 @@ TEST(MemoryRemoteTest, read_large) { ASSERT_EQ(i / getpagesize(), dst[i]) << "Failed at byte " << i; } - ASSERT_TRUE(TestDetach(pid)); + ASSERT_TRUE(Detach(pid)); } TEST(MemoryRemoteTest, read_partial) { @@ -111,7 +112,7 @@ TEST(MemoryRemoteTest, read_partial) { // Unmap from our process. ASSERT_EQ(0, munmap(mapping, 3 * getpagesize())); - ASSERT_TRUE(TestAttach(pid)); + ASSERT_TRUE(Attach(pid)); MemoryRemote remote(pid); @@ -132,7 +133,7 @@ TEST(MemoryRemoteTest, read_partial) { ASSERT_EQ(0x4cU, dst[i]) << "Failed at byte " << i; } - ASSERT_TRUE(TestDetach(pid)); + ASSERT_TRUE(Detach(pid)); } TEST(MemoryRemoteTest, read_fail) { @@ -152,7 +153,7 @@ TEST(MemoryRemoteTest, read_fail) { ASSERT_LT(0, pid); TestScopedPidReaper reap(pid); - ASSERT_TRUE(TestAttach(pid)); + ASSERT_TRUE(Attach(pid)); MemoryRemote remote(pid); @@ -171,7 +172,7 @@ TEST(MemoryRemoteTest, read_fail) { ASSERT_EQ(0, munmap(src, pagesize)); - ASSERT_TRUE(TestDetach(pid)); + ASSERT_TRUE(Detach(pid)); } TEST(MemoryRemoteTest, read_overflow) { @@ -184,7 +185,7 @@ TEST(MemoryRemoteTest, read_overflow) { ASSERT_LT(0, pid); TestScopedPidReaper reap(pid); - ASSERT_TRUE(TestAttach(pid)); + ASSERT_TRUE(Attach(pid)); MemoryRemote remote(pid); @@ -192,7 +193,7 @@ TEST(MemoryRemoteTest, read_overflow) { std::vector<uint8_t> dst(200); ASSERT_FALSE(remote.ReadFully(UINT64_MAX - 100, dst.data(), 200)); - ASSERT_TRUE(TestDetach(pid)); + ASSERT_TRUE(Detach(pid)); } TEST(MemoryRemoteTest, read_illegal) { @@ -204,7 +205,7 @@ TEST(MemoryRemoteTest, read_illegal) { ASSERT_LT(0, pid); TestScopedPidReaper reap(pid); - ASSERT_TRUE(TestAttach(pid)); + ASSERT_TRUE(Attach(pid)); MemoryRemote remote(pid); @@ -212,7 +213,7 @@ TEST(MemoryRemoteTest, read_illegal) { ASSERT_FALSE(remote.ReadFully(0, dst.data(), 1)); ASSERT_FALSE(remote.ReadFully(0, dst.data(), 100)); - ASSERT_TRUE(TestDetach(pid)); + ASSERT_TRUE(Detach(pid)); } TEST(MemoryRemoteTest, read_mprotect_hole) { @@ -233,7 +234,7 @@ TEST(MemoryRemoteTest, read_mprotect_hole) { ASSERT_EQ(0, munmap(mapping, 3 * page_size)); - ASSERT_TRUE(TestAttach(pid)); + ASSERT_TRUE(Attach(pid)); MemoryRemote remote(pid); std::vector<uint8_t> dst(getpagesize() * 4, 0xCC); @@ -247,7 +248,7 @@ TEST(MemoryRemoteTest, read_mprotect_hole) { ASSERT_EQ(0xCC, dst[i]); } - ASSERT_TRUE(TestDetach(pid)); + ASSERT_TRUE(Detach(pid)); } TEST(MemoryRemoteTest, read_munmap_hole) { @@ -270,7 +271,7 @@ TEST(MemoryRemoteTest, read_munmap_hole) { ASSERT_EQ(0, munmap(mapping, page_size)); ASSERT_EQ(0, munmap(static_cast<char*>(mapping) + 2 * page_size, page_size)); - ASSERT_TRUE(TestAttach(pid)); + ASSERT_TRUE(Attach(pid)); MemoryRemote remote(pid); std::vector<uint8_t> dst(getpagesize() * 4, 0xCC); @@ -283,7 +284,7 @@ TEST(MemoryRemoteTest, read_munmap_hole) { ASSERT_EQ(0xCC, dst[i]); } - ASSERT_TRUE(TestDetach(pid)); + ASSERT_TRUE(Detach(pid)); } // Verify that the memory remote object chooses a memory read function @@ -307,7 +308,7 @@ TEST(MemoryRemoteTest, read_choose_correctly) { ASSERT_EQ(0, munmap(mapping, 2 * page_size)); - ASSERT_TRUE(TestAttach(pid)); + ASSERT_TRUE(Attach(pid)); // We know that process_vm_readv of a mprotect'd PROT_NONE region will fail. // Read from the PROT_NONE area first to force the choice of ptrace. @@ -336,7 +337,7 @@ TEST(MemoryRemoteTest, read_choose_correctly) { ASSERT_EQ(sizeof(value), bytes); ASSERT_EQ(0xfcfcfcfcU, value); - ASSERT_TRUE(TestDetach(pid)); + ASSERT_TRUE(Detach(pid)); } } // namespace unwindstack diff --git a/libunwindstack/tests/RegsRemoteTest.cpp b/libunwindstack/tests/RegsRemoteTest.cpp index 4b5223b..2427501 100644 --- a/libunwindstack/tests/RegsRemoteTest.cpp +++ b/libunwindstack/tests/RegsRemoteTest.cpp @@ -26,7 +26,7 @@ #include <unwindstack/Regs.h> -#include "TestUtils.h" +#include "PidUtils.h" namespace unwindstack { @@ -40,15 +40,14 @@ class RegsRemoteTest : public ::testing::Test { exit(1); } ASSERT_TRUE(pid_ != -1); - ASSERT_TRUE(TestAttach(pid_)); - ASSERT_TRUE(TestQuiescePid(pid_)); + ASSERT_TRUE(Attach(pid_)); } void TearDown() override { if (pid_ == -1) { return; } - EXPECT_TRUE(TestDetach(pid_)); + EXPECT_TRUE(Detach(pid_)); kill(pid_, SIGKILL); waitpid(pid_, nullptr, 0); } diff --git a/libunwindstack/tests/TestLocal.cpp b/libunwindstack/tests/TestLocal.cpp index f8684d7..25bd42f 100644 --- a/libunwindstack/tests/TestLocal.cpp +++ b/libunwindstack/tests/TestLocal.cpp @@ -17,6 +17,8 @@ #include <stdint.h> #include <stdlib.h> +#include "TestUtils.h" + // The loop in this function is only guaranteed to not be optimized away by the compiler // if optimizations are turned off. This is partially because the compiler doesn't have // any idea about the function since it is retrieved using dlsym. @@ -25,7 +27,17 @@ // 1. The loop iteration variable is volatile. // 2. A call to this function should be wrapped in TestUtils::DoNotOptimize(). extern "C" int BusyWait() { - for (volatile size_t i = 0; i < 1000000; ++i) - ; + for (size_t i = 0; i < 1000000;) { + unwindstack::DoNotOptimize(i++); + } return 0; } + +// Do a loop that guarantees the terminating leaf frame will be in +// the this library and not a function from a different library. +extern "C" void WaitForever() { + bool run = true; + while (run) { + unwindstack::DoNotOptimize(run = true); + } +} diff --git a/libunwindstack/tests/TestUtils.cpp b/libunwindstack/tests/TestUtils.cpp index f2046f8..1f748ba 100644 --- a/libunwindstack/tests/TestUtils.cpp +++ b/libunwindstack/tests/TestUtils.cpp @@ -18,8 +18,12 @@ #include <malloc.h> #include <stdint.h> +#include <string> + #include <gtest/gtest.h> +#include "TestUtils.h" + namespace unwindstack { void TestCheckForLeaks(void (*unwind_func)(void*), void* data) { diff --git a/libunwindstack/tests/TestUtils.h b/libunwindstack/tests/TestUtils.h index 44b4e75..2378fd5 100644 --- a/libunwindstack/tests/TestUtils.h +++ b/libunwindstack/tests/TestUtils.h @@ -17,12 +17,10 @@ #ifndef _LIBUNWINDSTACK_TESTS_TEST_UTILS_H #define _LIBUNWINDSTACK_TESTS_TEST_UTILS_H -#include <errno.h> #include <signal.h> -#include <sys/ptrace.h> +#include <stdint.h> #include <sys/types.h> #include <sys/wait.h> -#include <unistd.h> namespace unwindstack { @@ -38,62 +36,6 @@ class TestScopedPidReaper { pid_t pid_; }; -inline bool TestQuiescePid(pid_t pid) { - siginfo_t si; - // Wait for up to 10 seconds. - for (size_t i = 0; i < 10000; i++) { - if (ptrace(PTRACE_GETSIGINFO, pid, 0, &si) == 0) { - return true; - } - if (errno != ESRCH) { - if (errno == EINVAL) { - // The process is in group-stop state, so try and kick the - // process out of that state. - if (ptrace(PTRACE_LISTEN, pid, 0, 0) == -1) { - perror("ptrace listen failed."); - return false; - } - } else { - return false; - } - } - usleep(1000); - } - return false; -} - -inline bool TestAttach(pid_t pid) { - // Wait up to 10 seconds to attach. - for (size_t j = 0; j < 10000; j++) { - if (ptrace(PTRACE_ATTACH, pid, 0, 0) == 0) { - break; - } - if (errno == ESRCH) { - usleep(1000); - continue; - } - perror("Failed to attach."); - return false; - } - - if (TestQuiescePid(pid)) { - return true; - } - - if (ptrace(PTRACE_DETACH, pid, 0, 0) == -1) { - perror("Failed to detach."); - } - return false; -} - -inline bool TestDetach(pid_t pid) { - if (ptrace(PTRACE_DETACH, pid, 0, 0) == -1) { - perror("ptrace detach failed"); - return false; - } - return true; -} - void TestCheckForLeaks(void (*unwind_func)(void*), void* data); void* GetTestLibHandle(); diff --git a/libunwindstack/tests/UnwindTest.cpp b/libunwindstack/tests/UnwindTest.cpp index 2ed1b9b..9a5a96f 100644 --- a/libunwindstack/tests/UnwindTest.cpp +++ b/libunwindstack/tests/UnwindTest.cpp @@ -40,6 +40,7 @@ #include <unwindstack/Unwinder.h> #include "MemoryRemote.h" +#include "PidUtils.h" #include "TestUtils.h" namespace unwindstack { @@ -236,28 +237,15 @@ TEST_F(UnwindTest, local_use_from_pid_check_for_leak) { TestCheckForLeaks(LocalUnwind, &test_type); } -void WaitForRemote(pid_t pid, uint64_t addr, bool leave_attached, bool* completed) { - *completed = false; - // Need to sleep before attempting first ptrace. Without this, on the - // host it becomes impossible to attach and ptrace sets errno to EPERM. - usleep(1000); - for (size_t i = 0; i < 4000; i++) { - ASSERT_TRUE(TestAttach(pid)); - - MemoryRemote memory(pid); - // Read the remote value to see if we are ready. +static bool WaitForRemote(pid_t pid, bool leave_attached, uint64_t addr) { + MemoryRemote memory(pid); + return RunWhenQuiesced(pid, leave_attached, [addr, &memory]() { bool value; if (memory.ReadFully(addr, &value, sizeof(value)) && value) { - *completed = true; - } - if (!*completed || !leave_attached) { - ASSERT_TRUE(TestDetach(pid)); - } - if (*completed) { - break; + return PID_RUN_PASS; } - usleep(5000); - } + return PID_RUN_KEEP_GOING; + }); } TEST_F(UnwindTest, remote) { @@ -269,9 +257,7 @@ TEST_F(UnwindTest, remote) { ASSERT_NE(-1, pid); TestScopedPidReaper reap(pid); - bool completed; - WaitForRemote(pid, reinterpret_cast<uint64_t>(&g_ready_for_remote), true, &completed); - ASSERT_TRUE(completed) << "Timed out waiting for remote process to be ready."; + ASSERT_TRUE(WaitForRemote(pid, true, reinterpret_cast<uint64_t>(&g_ready_for_remote))); RemoteMaps maps(pid); ASSERT_TRUE(maps.Parse()); @@ -280,7 +266,7 @@ TEST_F(UnwindTest, remote) { VerifyUnwind(pid, &maps, regs.get(), kFunctionOrder); - ASSERT_TRUE(TestDetach(pid)); + ASSERT_TRUE(Detach(pid)); } TEST_F(UnwindTest, unwind_from_pid_remote) { @@ -292,9 +278,7 @@ TEST_F(UnwindTest, unwind_from_pid_remote) { ASSERT_NE(-1, pid); TestScopedPidReaper reap(pid); - bool completed; - WaitForRemote(pid, reinterpret_cast<uint64_t>(&g_ready_for_remote), true, &completed); - ASSERT_TRUE(completed) << "Timed out waiting for remote process to be ready."; + ASSERT_TRUE(WaitForRemote(pid, true, reinterpret_cast<uint64_t>(&g_ready_for_remote))); std::unique_ptr<Regs> regs(Regs::RemoteGet(pid)); ASSERT_TRUE(regs.get() != nullptr); @@ -304,7 +288,7 @@ TEST_F(UnwindTest, unwind_from_pid_remote) { VerifyUnwind(&unwinder, kFunctionOrder); - ASSERT_TRUE(TestDetach(pid)); + ASSERT_TRUE(Detach(pid)); } static void RemoteCheckForLeaks(void (*unwind_func)(void*)) { @@ -316,13 +300,11 @@ static void RemoteCheckForLeaks(void (*unwind_func)(void*)) { ASSERT_NE(-1, pid); TestScopedPidReaper reap(pid); - bool completed; - WaitForRemote(pid, reinterpret_cast<uint64_t>(&g_ready_for_remote), true, &completed); - ASSERT_TRUE(completed) << "Timed out waiting for remote process to be ready."; + ASSERT_TRUE(WaitForRemote(pid, true, reinterpret_cast<uint64_t>(&g_ready_for_remote))); TestCheckForLeaks(unwind_func, &pid); - ASSERT_TRUE(TestDetach(pid)); + ASSERT_TRUE(Detach(pid)); } static void RemoteUnwind(void* data) { @@ -368,8 +350,8 @@ TEST_F(UnwindTest, from_context) { act.sa_sigaction = SignalHandler; act.sa_flags = SA_RESTART | SA_SIGINFO | SA_ONSTACK; ASSERT_EQ(0, sigaction(SIGUSR1, &act, &oldact)); - // Wait for the tid to get set. - for (size_t i = 0; i < 100; i++) { + // Wait 20 seconds for the tid to get set. + for (time_t start_time = time(nullptr); time(nullptr) - start_time < 20;) { if (tid.load() != 0) { break; } @@ -378,9 +360,9 @@ TEST_F(UnwindTest, from_context) { ASSERT_NE(0, tid.load()); ASSERT_EQ(0, tgkill(getpid(), tid.load(), SIGUSR1)) << "Error: " << strerror(errno); - // Wait for context data. + // Wait 20 seconds for context data. void* ucontext; - for (size_t i = 0; i < 2000; i++) { + for (time_t start_time = time(nullptr); time(nullptr) - start_time < 20;) { ucontext = reinterpret_cast<void*>(g_ucontext.load()); if (ucontext != nullptr) { break; @@ -416,14 +398,11 @@ static void RemoteThroughSignal(int signal, unsigned int sa_flags) { ASSERT_NE(-1, pid); TestScopedPidReaper reap(pid); - bool completed; if (signal != SIGSEGV) { - WaitForRemote(pid, reinterpret_cast<uint64_t>(&g_ready_for_remote), false, &completed); - ASSERT_TRUE(completed) << "Timed out waiting for remote process to be ready."; + ASSERT_TRUE(WaitForRemote(pid, false, reinterpret_cast<uint64_t>(&g_ready_for_remote))); ASSERT_EQ(0, kill(pid, SIGUSR1)); } - WaitForRemote(pid, reinterpret_cast<uint64_t>(&g_signal_ready_for_remote), true, &completed); - ASSERT_TRUE(completed) << "Timed out waiting for remote process to be in signal handler."; + ASSERT_TRUE(WaitForRemote(pid, true, reinterpret_cast<uint64_t>(&g_signal_ready_for_remote))); RemoteMaps maps(pid); ASSERT_TRUE(maps.Parse()); @@ -432,7 +411,7 @@ static void RemoteThroughSignal(int signal, unsigned int sa_flags) { VerifyUnwind(pid, &maps, regs.get(), kFunctionSignalOrder); - ASSERT_TRUE(TestDetach(pid)); + ASSERT_TRUE(Detach(pid)); } TEST_F(UnwindTest, remote_through_signal) { diff --git a/libunwindstack/tests/UnwinderTest.cpp b/libunwindstack/tests/UnwinderTest.cpp index 74bc516..2da3ee1 100644 --- a/libunwindstack/tests/UnwinderTest.cpp +++ b/libunwindstack/tests/UnwinderTest.cpp @@ -1761,8 +1761,7 @@ TEST_F(UnwinderTest, build_frame_pc_in_jit) { TEST_F(UnwinderTest, unwinder_from_pid_set_process_memory) { auto process_memory = Memory::CreateProcessMemoryCached(getpid()); - UnwinderFromPid unwinder(10, getpid()); - unwinder.SetProcessMemory(process_memory); + UnwinderFromPid unwinder(10, getpid(), process_memory); unwinder.SetArch(unwindstack::Regs::CurrentArch()); ASSERT_TRUE(unwinder.Init()); ASSERT_EQ(process_memory.get(), unwinder.GetProcessMemory().get()); diff --git a/libunwindstack/utils/PidUtils.cpp b/libunwindstack/utils/PidUtils.cpp new file mode 100644 index 0000000..4268225 --- /dev/null +++ b/libunwindstack/utils/PidUtils.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2022 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 <errno.h> +#include <stdint.h> +#include <stdio.h> +#include <sys/ptrace.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#include "PidUtils.h" + +namespace unwindstack { + +static bool Exited(pid_t pid) { + int status; + pid_t wait_pid = waitpid(pid, &status, WNOHANG); + if (wait_pid != pid) { + return false; + } + + if (WIFEXITED(status)) { + fprintf(stderr, "%d died: Process exited with code %d\n", pid, WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + fprintf(stderr, "%d died: Process exited due to signal %d\n", pid, WTERMSIG(status)); + } else { + fprintf(stderr, "%d died: Process finished for unknown reason\n", pid); + } + return true; +} + +bool Quiesce(pid_t pid) { + siginfo_t si; + // Wait for up to 10 seconds. + for (time_t start_time = time(nullptr); time(nullptr) - start_time < 10;) { + if (ptrace(PTRACE_GETSIGINFO, pid, 0, &si) == 0) { + return true; + } + if (errno != ESRCH) { + if (errno == EINVAL) { + // The process is in group-stop state, so try and kick the + // process out of that state. + if (ptrace(PTRACE_LISTEN, pid, 0, 0) == -1) { + // Cannot recover from this, so just pretend it worked and see + // if we can unwind. + return true; + } + } else { + perror("ptrace getsiginfo failed"); + return false; + } + } + usleep(5000); + } + fprintf(stderr, "Did not quiesce in 10 seconds\n"); + return false; +} + +bool Attach(pid_t pid) { + // Wait up to 45 seconds to attach. + for (time_t start_time = time(nullptr); time(nullptr) - start_time < 45;) { + if (ptrace(PTRACE_ATTACH, pid, 0, 0) == 0) { + break; + } + if (errno != ESRCH) { + perror("Failed to attach"); + return false; + } + usleep(5000); + } + + if (Quiesce(pid)) { + return true; + } + + if (ptrace(PTRACE_DETACH, pid, 0, 0) == -1) { + perror("Failed to detach"); + } + return false; +} + +bool Detach(pid_t pid) { + if (ptrace(PTRACE_DETACH, pid, 0, 0) == -1) { + perror("ptrace detach failed"); + return false; + } + return true; +} + +bool RunWhenQuiesced(pid_t pid, bool leave_attached, std::function<PidRunEnum()> fn) { + // Wait up to 120 seconds to run the fn. + PidRunEnum status = PID_RUN_KEEP_GOING; + for (time_t start_time = time(nullptr); + time(nullptr) - start_time < 120 && status == PID_RUN_KEEP_GOING;) { + if (Attach(pid)) { + status = fn(); + if (status == PID_RUN_PASS && leave_attached) { + return true; + } + + if (!Detach(pid)) { + return false; + } + } else if (Exited(pid)) { + return false; + } + usleep(5000); + } + if (status == PID_RUN_KEEP_GOING) { + fprintf(stderr, "Timed out waiting for pid %d to be ready\n", pid); + } + return status == PID_RUN_PASS; +} + +} // namespace unwindstack diff --git a/libunwindstack/utils/PidUtils.h b/libunwindstack/utils/PidUtils.h new file mode 100644 index 0000000..5dbd4ae --- /dev/null +++ b/libunwindstack/utils/PidUtils.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 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_UTILS_PID_UTILS_H +#define _LIBUNWINDSTACK_UTILS_PID_UTILS_H + +#include <stdint.h> +#include <sys/types.h> + +#include <functional> + +namespace unwindstack { + +enum PidRunEnum : uint8_t { + PID_RUN_KEEP_GOING, + PID_RUN_PASS, + PID_RUN_FAIL, +}; + +bool Quiesce(pid_t pid); + +bool Attach(pid_t pid); + +bool Detach(pid_t pid); + +bool RunWhenQuiesced(pid_t pid, bool leave_attached, std::function<PidRunEnum()> fn); + +} // namespace unwindstack + +#endif // _LIBUNWINDSTACK_UTILS_PID_UTILS_H |