From 4e6aa64c48953c15bb07f365df6f2e45174db209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=91=A3=E5=8D=9A=E6=96=AF?= Date: Sat, 30 Aug 2025 10:43:25 +0800 Subject: [PATCH] add the offlineparse interface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 董博斯 --- interfaces/innerkits/backtrace/BUILD.gn | 8 +- .../innerkits/backtrace/dfx_kernel_stack.cpp | 30 ++- .../backtrace/include/dfx_kernel_stack.h | 9 +- interfaces/innerkits/dump_catcher/BUILD.gn | 3 +- .../dump_catcher/dfx_dump_catcher.cpp | 21 +- .../kernel_stack_async_collector.cpp | 6 +- interfaces/innerkits/formatter/BUILD.gn | 1 + .../formatter/dfx_json_formatter.cpp | 53 +++-- .../formatter/include/dfx_json_formatter.h | 4 +- interfaces/innerkits/unwinder/BUILD.gn | 1 + .../unwinder/include/dfx_offline_parser.h | 48 +++++ interfaces/innerkits/unwinder/libunwinder.map | 1 + .../unwinder/src/utils/dfx_offline_parser.cpp | 185 ++++++++++++++++++ .../backtrace/backtrace_local_test.cpp | 109 ++++++++++- .../dump_catcher/dumpcatcher_command_test.cpp | 148 ++++++++++++++ .../unittest/process_dump/dump_utils_test.cpp | 6 +- test/unittest/unwind/BUILD.gn | 1 + test/unittest/unwind/offline_parser_test.cpp | 147 ++++++++++++++ tools/dump_catcher/BUILD.gn | 9 + tools/dump_catcher/dump_catcher.cpp | 116 ++++++++++- tools/dump_catcher/dump_catcher.h | 18 +- tools/dump_catcher/main.cpp | 61 +++--- tools/process_dump/BUILD.gn | 1 + 23 files changed, 911 insertions(+), 75 deletions(-) create mode 100644 interfaces/innerkits/unwinder/include/dfx_offline_parser.h create mode 100644 interfaces/innerkits/unwinder/src/utils/dfx_offline_parser.cpp create mode 100644 test/unittest/unwind/offline_parser_test.cpp diff --git a/interfaces/innerkits/backtrace/BUILD.gn b/interfaces/innerkits/backtrace/BUILD.gn index d3a41e32c..f181d62e7 100644 --- a/interfaces/innerkits/backtrace/BUILD.gn +++ b/interfaces/innerkits/backtrace/BUILD.gn @@ -43,6 +43,7 @@ if (defined(ohos_lite)) { deps = [ "$faultloggerd_common_path/dfxlog:dfx_hilog", "$faultloggerd_common_path/dfxutil:dfx_util", + "$faultloggerd_common_path/trace:dfx_trace_dlsym", "$faultloggerd_interfaces_path/innerkits/procinfo:libdfx_procinfo", "$faultloggerd_interfaces_path/innerkits/unwinder:libunwinder", ] @@ -68,7 +69,10 @@ if (defined(ohos_lite)) { "$faultloggerd_interfaces_path/innerkits/unwinder/include", ] - defines = [ "is_ohos=${is_ohos}" ] + defines = [ + "is_ohos=${is_ohos}", + "DFX_ENABLE_TRACE", + ] } ohos_shared_library("libbacktrace_local") { @@ -83,6 +87,7 @@ if (defined(ohos_lite)) { deps = [ "$faultloggerd_common_path/dfxlog:dfx_hilog", "$faultloggerd_common_path/dfxutil:dfx_util", + "$faultloggerd_common_path/trace:dfx_trace_dlsym", "$faultloggerd_interfaces_path/innerkits/procinfo:libdfx_procinfo", "$faultloggerd_interfaces_path/innerkits/unwinder:libunwinder", ] @@ -120,6 +125,7 @@ if (defined(ohos_lite)) { deps = [ "$faultloggerd_common_path/dfxlog:dfx_hilog_base_static", + "$faultloggerd_common_path/trace:dfx_trace_dlsym_static", "$faultloggerd_interfaces_path/innerkits/procinfo:dfx_procinfo_static", "$faultloggerd_interfaces_path/innerkits/unwinder:libunwinder_base", ] diff --git a/interfaces/innerkits/backtrace/dfx_kernel_stack.cpp b/interfaces/innerkits/backtrace/dfx_kernel_stack.cpp index 524a8bab0..d3809aa60 100644 --- a/interfaces/innerkits/backtrace/dfx_kernel_stack.cpp +++ b/interfaces/innerkits/backtrace/dfx_kernel_stack.cpp @@ -23,9 +23,12 @@ #include #include "dfx_log.h" +#include "dfx_trace_dlsym.h" +#include "elapsed_time.h" #include "smart_fd.h" #define LOGGER_GET_STACK _IO(0xAB, 9) +#define LOGGER_GET_STACK_ARKTS _IO(0xAB, 10) namespace OHOS { namespace HiviewDFX { namespace { @@ -39,7 +42,7 @@ typedef struct HstackVal { char hstackLogBuff[BUFF_STACK_SIZE] {0}; } HstackVal; } -int32_t DfxGetKernelStack(int32_t pid, std::string& kernelStack) +int32_t DfxGetKernelStack(int32_t pid, std::string& kernelStack, bool needArkts) { auto kstackBuf = std::make_shared(); if (kstackBuf == nullptr) { @@ -53,8 +56,8 @@ int32_t DfxGetKernelStack(int32_t pid, std::string& kernelStack) DFXLOGW("Failed to open bbox, pid:%{public}d, errno:%{public}d", pid, errno); return KERNELSTACK_EOPEN; } - - int ret = ioctl(fd.GetFd(), LOGGER_GET_STACK, kstackBuf.get()); + int ctlCode = needArkts ? LOGGER_GET_STACK_ARKTS : LOGGER_GET_STACK; + int ret = ioctl(fd.GetFd(), ctlCode, kstackBuf.get()); int32_t res = KERNELSTACK_ESUCCESS; if (ret != 0) { DFXLOGW("Failed to get pid(%{public}d) kernel stack, errno:%{public}d", pid, errno); @@ -65,9 +68,11 @@ int32_t DfxGetKernelStack(int32_t pid, std::string& kernelStack) return res; } -bool FormatThreadKernelStack(const std::string& kernelStack, DfxThreadStack& threadStack) +bool FormatThreadKernelStack(const std::string& kernelStack, DfxThreadStack& threadStack, + DfxOfflineParser *parser) { #ifdef __aarch64__ + DFX_TRACE_SCOPED_DLSYM("FormatThreadKernelStack"); std::regex headerPattern(R"(name=(.{1,20}), tid=(\d{1,10}), ([\w\=\.]{1,256}, ){3}pid=(\d{1,10}))"); std::smatch result; if (!regex_search(kernelStack, result, headerPattern)) { @@ -93,6 +98,10 @@ bool FormatThreadKernelStack(const std::string& kernelStack, DfxThreadStack& thr base = 16; // 16 : Hexadecimal frame.relPc = strtoull((*it)[1].str().c_str(), nullptr, base); frame.mapName = (*it)[2].str(); // 2 : second of searched element is map name + if (parser) { + DFX_TRACE_SCOPED_DLSYM("ParseSymbolWithFrame:%s", frame.mapName.c_str()); + parser->ParseSymbolWithFrame(frame); + } threadStack.frames.emplace_back(frame); } return true; @@ -101,9 +110,11 @@ bool FormatThreadKernelStack(const std::string& kernelStack, DfxThreadStack& thr #endif } -bool FormatProcessKernelStack(const std::string& kernelStack, std::vector& processStack) +bool FormatProcessKernelStack(const std::string& kernelStack, std::vector& processStack, + bool needParseSymbol, const std::string& bundleName) { #if !defined(is_ohos_lite) && defined(__aarch64__) + ElapsedTime counter; std::vector threadKernelStackVec; std::string keyWord = "Thread info:"; OHOS::SplitStr(kernelStack, keyWord, threadKernelStackVec); @@ -111,12 +122,19 @@ bool FormatProcessKernelStack(const std::string& kernelStack, std::vector parser = nullptr; + if (needParseSymbol) { + parser = std::make_unique(bundleName); + } for (const std::string& threadKernelStack : threadKernelStackVec) { DfxThreadStack threadStack; - if (FormatThreadKernelStack(threadKernelStack, threadStack)) { + if (FormatThreadKernelStack(threadKernelStack, threadStack, parser.get())) { processStack.emplace_back(threadStack); } } + DfxEnableTraceDlsym(false); + DFXLOGI("format kernel stack cost time = %{public}" PRId64 " ms", counter.Elapsed()); return true; #else return false; diff --git a/interfaces/innerkits/backtrace/include/dfx_kernel_stack.h b/interfaces/innerkits/backtrace/include/dfx_kernel_stack.h index 5f480ccdd..3ad41918b 100644 --- a/interfaces/innerkits/backtrace/include/dfx_kernel_stack.h +++ b/interfaces/innerkits/backtrace/include/dfx_kernel_stack.h @@ -18,6 +18,7 @@ #include #include #include "dfx_frame.h" +#include "dfx_offline_parser.h" namespace OHOS { namespace HiviewDFX { @@ -32,9 +33,11 @@ enum KernelStackErrorCode : int32_t { KERNELSTACK_EOPEN, KERNELSTACK_EIOCTL, }; -int32_t DfxGetKernelStack(int32_t pid, std::string& kernelStack); -bool FormatThreadKernelStack(const std::string& kernelStack, DfxThreadStack& threadStack); -bool FormatProcessKernelStack(const std::string& kernelStack, std::vector& processStack); +int32_t DfxGetKernelStack(int32_t pid, std::string& kernelStack, bool needArkts = false); +bool FormatThreadKernelStack(const std::string& kernelStack, DfxThreadStack& threadStack, + DfxOfflineParser *parser = nullptr); +bool FormatProcessKernelStack(const std::string& kernelStack, std::vector& processStack, + bool needParseSymbol = false, const std::string& bundleName = std::string()); } } #endif \ No newline at end of file diff --git a/interfaces/innerkits/dump_catcher/BUILD.gn b/interfaces/innerkits/dump_catcher/BUILD.gn index cadaca700..1c5b55388 100644 --- a/interfaces/innerkits/dump_catcher/BUILD.gn +++ b/interfaces/innerkits/dump_catcher/BUILD.gn @@ -33,6 +33,7 @@ if (defined(ohos_lite)) { "$faultloggerd_interfaces_path/innerkits/backtrace/include", "$faultloggerd_interfaces_path/innerkits/faultloggerd_client/include", "$faultloggerd_interfaces_path/innerkits/procinfo/include", + "$faultloggerd_interfaces_path/innerkits/unwinder/include", "$hilog_lite_include_path", ] @@ -81,9 +82,9 @@ if (defined(ohos_lite)) { "$faultloggerd_interfaces_path/innerkits/backtrace:libbacktrace_local", "$faultloggerd_interfaces_path/innerkits/faultloggerd_client:libfaultloggerd", "$faultloggerd_interfaces_path/innerkits/procinfo:libdfx_procinfo", - "$faultloggerd_path/interfaces/innerkits/signal_handler:dfx_signalhandler", "$faultloggerd_interfaces_path/innerkits/stack_printer:libstack_printer", "$faultloggerd_interfaces_path/innerkits/unwinder:libunwinder", + "$faultloggerd_path/interfaces/innerkits/signal_handler:dfx_signalhandler", ] external_deps = [ diff --git a/interfaces/innerkits/dump_catcher/dfx_dump_catcher.cpp b/interfaces/innerkits/dump_catcher/dfx_dump_catcher.cpp index ceaa3e70e..5589de5dd 100644 --- a/interfaces/innerkits/dump_catcher/dfx_dump_catcher.cpp +++ b/interfaces/innerkits/dump_catcher/dfx_dump_catcher.cpp @@ -135,6 +135,7 @@ private: void DealWithSdkDumpRet(int sdkdumpRet, int pid, int32_t& ret, std::string& msg); std::pair DealWithDumpCatchRet(int pid, int32_t& ret, std::string& msg); void ReportDumpCatcherStats(int32_t pid, uint64_t requestTime, int32_t ret, void* retAddr); + void GetKernelStack(int32_t uid, int pid); static int32_t KernelRet2DumpcatchRet(int32_t ret); static const int DUMPCATCHER_REMOTE_P90_TIMEOUT = 1000; @@ -478,6 +479,13 @@ void DfxDumpCatcher::Impl::DealWithPollRet(int pollRet, int pid, int32_t& ret, s } } +void DfxDumpCatcher::Impl::GetKernelStack(int32_t uid, int pid) +{ + if (uid == HIVIEW_UID || uid == FOUNDATION_UID) { + stack_ = stackKit_.GetProcessStackWithTimeout(pid, WAIT_GET_KERNEL_STACK_TIMEOUT); + } +} + void DfxDumpCatcher::Impl::DealWithSdkDumpRet(int sdkdumpRet, int pid, int32_t& ret, std::string& msg) { uint32_t uid = getuid(); @@ -492,24 +500,19 @@ void DfxDumpCatcher::Impl::DealWithSdkDumpRet(int sdkdumpRet, int pid, int32_t& msg.append("Result: pid(" + std::to_string(pid) + ") process has exited.\n"); ret = DUMPCATCH_NO_PROCESS; } else if (sdkdumpRet == ResponseCode::SDK_PROCESS_CRASHED) { + GetKernelStack(uid, pid); msg.append("Result: pid(" + std::to_string(pid) + ") process has been crashed.\n"); ret = DUMPCATCH_HAS_CRASHED; } else if (sdkdumpRet == ResponseCode::CONNECT_FAILED) { - if (uid == HIVIEW_UID || uid == FOUNDATION_UID) { - stack_ = stackKit_.GetProcessStackWithTimeout(pid, WAIT_GET_KERNEL_STACK_TIMEOUT); - } + GetKernelStack(uid, pid); msg.append("Result: pid(" + std::to_string(pid) + ") process fail to conntect faultloggerd.\n"); ret = DUMPCATCH_ECONNECT; } else if (sdkdumpRet == ResponseCode::SEND_DATA_FAILED) { - if (uid == HIVIEW_UID || uid == FOUNDATION_UID) { - stack_ = stackKit_.GetProcessStackWithTimeout(pid, WAIT_GET_KERNEL_STACK_TIMEOUT); - } + GetKernelStack(uid, pid); msg.append("Result: pid(" + std::to_string(pid) + ") process fail to write to faultloggerd.\n"); ret = DUMPCATCH_EWRITE; } else { - if (uid == HIVIEW_UID || uid == FOUNDATION_UID) { - stack_ = stackKit_.GetProcessStackWithTimeout(pid, WAIT_GET_KERNEL_STACK_TIMEOUT); - } + GetKernelStack(uid, pid); msg.append("Result: pid(" + std::to_string(pid) + ") faultloggerd maybe exception occurred.\n"); ret = DUMPCATCH_EFAULTLOGGERD; } diff --git a/interfaces/innerkits/dump_catcher/kernel_stack_async_collector.cpp b/interfaces/innerkits/dump_catcher/kernel_stack_async_collector.cpp index 7adb813c3..6fc14da11 100644 --- a/interfaces/innerkits/dump_catcher/kernel_stack_async_collector.cpp +++ b/interfaces/innerkits/dump_catcher/kernel_stack_async_collector.cpp @@ -132,12 +132,14 @@ void KernelStackAsyncCollector::CollectKernelStackTask(int pid, std::promise stackTask = [&kernelStackInfo, &kernelRet](int tid) { + bool isMainThread = true; + std::function stackTask = [&kernelStackInfo, &kernelRet, &isMainThread](int tid) { if (tid <= 0) { return false; } std::string tidKernelStackInfo; - int32_t ret = DfxGetKernelStack(tid, tidKernelStackInfo); + int32_t ret = DfxGetKernelStack(tid, tidKernelStackInfo, isMainThread); + isMainThread = false; if (ret == 0) { kernelStackInfo.append(tidKernelStackInfo); } else if (kernelRet == 0) { diff --git a/interfaces/innerkits/formatter/BUILD.gn b/interfaces/innerkits/formatter/BUILD.gn index c7a8b71fd..e054c1c8b 100644 --- a/interfaces/innerkits/formatter/BUILD.gn +++ b/interfaces/innerkits/formatter/BUILD.gn @@ -30,6 +30,7 @@ if (defined(ohos_lite)) { ] deps = [ "$faultloggerd_interfaces_path/innerkits/backtrace:libbacktrace_local", + "$faultloggerd_interfaces_path/innerkits/unwinder:libunwinder", ] sources = [ "dfx_json_formatter.cpp" ] diff --git a/interfaces/innerkits/formatter/dfx_json_formatter.cpp b/interfaces/innerkits/formatter/dfx_json_formatter.cpp index 477d73388..216aa69ef 100644 --- a/interfaces/innerkits/formatter/dfx_json_formatter.cpp +++ b/interfaces/innerkits/formatter/dfx_json_formatter.cpp @@ -19,6 +19,7 @@ #include #include "dfx_kernel_stack.h" #ifndef is_ohos_lite +#include "dfx_frame_formatter.h" #include "json/json.h" #endif @@ -146,19 +147,39 @@ static bool FormatKernelStackStr(const std::vector& processStack std::string ss = "Tid:" + std::to_string(threadStack.tid) + ", Name:" + threadStack.threadName + "\n"; formattedStack.append(ss); for (size_t frameIdx = 0; frameIdx < threadStack.frames.size(); ++frameIdx) { - std::string file = threadStack.frames[frameIdx].mapName; - char buf[FRAME_BUF_LEN] = {0}; - char format[] = "#%02zu pc %016" PRIx64 " %s"; - if (snprintf_s(buf, sizeof(buf), sizeof(buf) - 1, format, frameIdx, threadStack.frames[frameIdx].relPc, - file.empty() ? "Unknown" : file.c_str()) <= 0) { - continue; - } - formattedStack.append(std::string(buf, strlen(buf)) + "\n"); + formattedStack.append(DfxFrameFormatter::GetFrameStr(threadStack.frames[frameIdx])); } } return true; } +static void FillJsFrameJson(const DfxFrame& frame, Json::Value& frameJson) +{ + frameJson["file"] = frame.mapName; + frameJson["packageName"] = frame.packageName; + frameJson["symbol"] = frame.funcName; + frameJson["line"] = frame.line; + frameJson["column"] = frame.column; +} + +static void FillNativeFrameJson(const DfxFrame& frame, Json::Value& frameJson) +{ + frameJson["pc"] = StringPrintf("%016lx", frame.relPc); + if (!frame.funcName.empty() && frame.funcName.length() <= MAX_FUNC_NAME_LEN) { + frameJson["symbol"] = frame.funcName; + } else { + frameJson["symbol"] = ""; + } + frameJson["offset"] = frame.funcOffset; + frameJson["file"] = frame.mapName.empty() ? "Unknown" : frame.mapName; + frameJson["buildId"] = frame.buildId; +} + +static void FillFrameJson(const DfxFrame& frame, Json::Value& frameJson) +{ + frame.isJsFrame ? FillJsFrameJson(frame, frameJson) : FillNativeFrameJson(frame, frameJson); +} + static bool FormatKernelStackJson(std::vector processStack, std::string& formattedStack) { if (processStack.empty()) { @@ -172,16 +193,7 @@ static bool FormatKernelStackJson(std::vector processStack, std: Json::Value frames(Json::arrayValue); for (const auto& frame : threadStack.frames) { Json::Value frameJson; - char buf[FRAME_BUF_LEN] = {0}; - char format[] = "%016" PRIx64; - if (snprintf_s(buf, sizeof(buf), sizeof(buf) - 1, format, frame.relPc) <= 0) { - continue; - } - frameJson["pc"] = std::string(buf); - frameJson["symbol"] = ""; - frameJson["offset"] = 0; - frameJson["file"] = frame.mapName.empty() ? "Unknown" : frame.mapName; - frameJson["buildId"] = ""; + FillFrameJson(frame, frameJson); frames.append(frameJson); } threadInfo["frames"] = frames; @@ -192,11 +204,12 @@ static bool FormatKernelStackJson(std::vector processStack, std: } #endif -bool DfxJsonFormatter::FormatKernelStack(const std::string& kernelStack, std::string& formattedStack, bool jsonFormat) +bool DfxJsonFormatter::FormatKernelStack(const std::string& kernelStack, std::string& formattedStack, bool jsonFormat, + bool needParseSymbol, const std::string& bundleName) { #ifdef __aarch64__ std::vector processStack; - if (!FormatProcessKernelStack(kernelStack, processStack)) { + if (!FormatProcessKernelStack(kernelStack, processStack, needParseSymbol, bundleName)) { return false; } if (jsonFormat) { diff --git a/interfaces/innerkits/formatter/include/dfx_json_formatter.h b/interfaces/innerkits/formatter/include/dfx_json_formatter.h index 0c5b5f8ce..bd46df5e1 100644 --- a/interfaces/innerkits/formatter/include/dfx_json_formatter.h +++ b/interfaces/innerkits/formatter/include/dfx_json_formatter.h @@ -43,8 +43,8 @@ public: * @param jsonFormat whether return json format stack, default false * @return if succeed return true, otherwise return false */ - static bool FormatKernelStack(const std::string& kernelStack, - std::string& formattedStack, bool jsonFormat = false); + static bool FormatKernelStack(const std::string& kernelStack, std::string& formattedStack, bool jsonFormat = false, + bool needParseSymbol = false, const std::string& bundleName = ""); #endif }; } // namespace HiviewDFX diff --git a/interfaces/innerkits/unwinder/BUILD.gn b/interfaces/innerkits/unwinder/BUILD.gn index 12a6da598..ec7c74e64 100644 --- a/interfaces/innerkits/unwinder/BUILD.gn +++ b/interfaces/innerkits/unwinder/BUILD.gn @@ -38,6 +38,7 @@ dfx_unwinder_sources = [ "src/utils/dfx_frame_formatter.cpp", "src/utils/dfx_instr_statistic.cpp", "src/utils/dfx_instructions.cpp", + "src/utils/dfx_offline_parser.cpp", "src/utils/dfx_ptrace.cpp", "src/utils/safe_reader.cpp", "src/utils/unwinder_config.cpp", diff --git a/interfaces/innerkits/unwinder/include/dfx_offline_parser.h b/interfaces/innerkits/unwinder/include/dfx_offline_parser.h new file mode 100644 index 000000000..c73978f95 --- /dev/null +++ b/interfaces/innerkits/unwinder/include/dfx_offline_parser.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * 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 DFX_OFFLINE_PARSER_H +#define DFX_OFFLINE_PARSER_H + +#include "dfx_ark.h" +#include "dfx_elf.h" +#include "dfx_maps.h" +#include "dfx_frame.h" + +namespace OHOS { +namespace HiviewDFX { +class DfxOfflineParser { +public: + DfxOfflineParser(const std::string& bundleName); + ~DfxOfflineParser(); + DfxOfflineParser(const DfxOfflineParser&) = delete; + DfxOfflineParser& operator= (const DfxOfflineParser&) = delete; + bool ParseSymbolWithFrame(DfxFrame& frame); +private: + static bool IsJsFrame(const DfxFrame& frame); + bool ParseNativeSymbol(DfxFrame& frame); + bool ParseJsSymbol(DfxFrame& frame); + bool ParseJsSymbolForArkHap(const DfxFrame& frame, JsFunction& jsFunction); + bool ParseJsSymbolForArkCode(const DfxFrame& frame, JsFunction& jsFunction); + std::string GetBundlePath(const std::string& originPath) const; + std::shared_ptr GetElfForFrame(const DfxFrame& frame); + bool CachedEnableMiniDebugInfo_ {false}; + bool CachedEnableLoadSymbolLazily_ {false}; + uintptr_t arkSymbolExtractorPtr_ {0}; + std::string bundleName_ {""}; + std::shared_ptr dfxMaps_ {nullptr}; +}; +} +} +#endif \ No newline at end of file diff --git a/interfaces/innerkits/unwinder/libunwinder.map b/interfaces/innerkits/unwinder/libunwinder.map index 4099f0dcc..3fd02d940 100644 --- a/interfaces/innerkits/unwinder/libunwinder.map +++ b/interfaces/innerkits/unwinder/libunwinder.map @@ -122,6 +122,7 @@ OHOS::HiviewDFX::DfxJsvm::JsvmDestroyJsSymbolExtractor*; OHOS::HiviewDFX::DfxJsvm::ParseJsvmFrameInfo*; OHOS::HiviewDFX::DfxJsvm::Instance*; + OHOS::HiviewDFX::DfxOfflineParser*; }; local: *; diff --git a/interfaces/innerkits/unwinder/src/utils/dfx_offline_parser.cpp b/interfaces/innerkits/unwinder/src/utils/dfx_offline_parser.cpp new file mode 100644 index 000000000..9cc372f98 --- /dev/null +++ b/interfaces/innerkits/unwinder/src/utils/dfx_offline_parser.cpp @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * 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 "dfx_offline_parser.h" + +#include +#include "dfx_ark.h" +#include "dfx_log.h" +#include "dfx_symbols.h" +#include "elf_factory.h" +#include "string_util.h" +#include "unwinder_config.h" + +namespace OHOS { +namespace HiviewDFX { +namespace { +#undef LOG_DOMAIN +#undef LOG_TAG +#define LOG_DOMAIN 0xD002D11 +#define LOG_TAG "DfxOfflineParser" + +const char* const SANDBOX_PATH_PREFIX = "/data/storage/el1/bundle/"; +const char* const BUNDLE_PATH_PREFIX = "/data/app/el1/bundle/public/"; +} +DfxOfflineParser::DfxOfflineParser(const std::string& bundleName) : bundleName_(bundleName) +{ + CachedEnableMiniDebugInfo_ = UnwinderConfig::GetEnableMiniDebugInfo(); + CachedEnableLoadSymbolLazily_ = UnwinderConfig::GetEnableLoadSymbolLazily(); + UnwinderConfig::SetEnableMiniDebugInfo(true); + UnwinderConfig::SetEnableLoadSymbolLazily(true); + dfxMaps_ = std::make_shared(); +} + +DfxOfflineParser::~DfxOfflineParser() +{ + UnwinderConfig::SetEnableMiniDebugInfo(CachedEnableMiniDebugInfo_); + UnwinderConfig::SetEnableLoadSymbolLazily(CachedEnableLoadSymbolLazily_); + if (arkSymbolExtractorPtr_ != 0) { + DfxArk::Instance().ArkDestoryJsSymbolExtractor(arkSymbolExtractorPtr_); + arkSymbolExtractorPtr_ = 0; + } +} + +bool DfxOfflineParser::ParseSymbolWithFrame(DfxFrame& frame) +{ + return IsJsFrame(frame) ? ParseJsSymbol(frame) : ParseNativeSymbol(frame); +} + +bool DfxOfflineParser::IsJsFrame(const DfxFrame& frame) +{ + return DfxMaps::IsArkHapMapItem(frame.mapName) || DfxMaps::IsArkCodeMapItem(frame.mapName); +} + +bool DfxOfflineParser::ParseNativeSymbol(DfxFrame& frame) +{ + auto elf = GetElfForFrame(frame); + if (elf == nullptr) { + return false; + } + frame.buildId = elf->GetBuildId(); + if (!DfxSymbols::GetFuncNameAndOffsetByPc(frame.relPc, elf, frame.funcName, frame.funcOffset)) { + DFXLOGU("Failed to get symbol, relPc: %{public}" PRIx64 ", mapName: %{public}s", + frame.relPc, frame.mapName.c_str()); + return false; + } + frame.parseSymbolState.SetParseSymbolState(true); + return true; +} + +bool DfxOfflineParser::ParseJsSymbolForArkHap(const DfxFrame& frame, JsFunction& jsFunction) +{ + std::string realPath = GetBundlePath(frame.mapName); + bool success = DfxArk::Instance().ParseArkFileInfo( + static_cast(frame.relPc), 0, + realPath.c_str(), arkSymbolExtractorPtr_, &jsFunction) >= 0; + if (!success) { + DFXLOGW("Failed to parse ark file info, relPc: %{private}p, hapName: %{private}s", + reinterpret_cast(frame.relPc), realPath.c_str()); + } + return success; +} + +bool DfxOfflineParser::ParseJsSymbolForArkCode(const DfxFrame& frame, JsFunction& jsFunction) +{ + std::string realPath = GetBundlePath(frame.mapName); + SmartFd smartFd(open(realPath.c_str(), O_RDONLY)); + if (!smartFd) { + DFXLOGE("Failed to open file: %{public}s, errno(%{public}d)", realPath.c_str(), errno); + return false; + } + off_t size = lseek(smartFd.GetFd(), 0, SEEK_END); + if (size <= 0) { + DFXLOGE("fd is empty or error, fd(%{public}d)", smartFd.GetFd()); + return false; + } + void* mptr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, smartFd.GetFd(), 0); + if (mptr == MAP_FAILED) { + DFXLOGE("mmap failed, fd(%{public}d), errno(%{public}d)", smartFd.GetFd(), errno); + return false; + } + bool success = DfxArk::Instance().ParseArkFrameInfo( + static_cast(frame.relPc), 0, 0, + static_cast(mptr), + size, arkSymbolExtractorPtr_, &jsFunction) >= 0; + if (!success) { + DFXLOGW("Failed to parse ark frame info, relPc: %{private}p, codeName: %{private}s", + reinterpret_cast(frame.relPc), realPath.c_str()); + } + munmap(mptr, size); + return success; +} + +bool DfxOfflineParser::ParseJsSymbol(DfxFrame& frame) +{ + if (arkSymbolExtractorPtr_ == 0) { + if (DfxArk::Instance().ArkCreateJsSymbolExtractor(&arkSymbolExtractorPtr_) < 0) { + DFXLOGE("Failed to create ark js symbol extractor"); + return false; + } + } + JsFunction jsFunction; + bool success = DfxMaps::IsArkHapMapItem(frame.mapName) + ? ParseJsSymbolForArkHap(frame, jsFunction) + : ParseJsSymbolForArkCode(frame, jsFunction); + if (!success) { + return false; + } + frame.isJsFrame = true; + frame.mapName = std::string(jsFunction.url); + frame.funcName = std::string(jsFunction.functionName); + frame.packageName = std::string(jsFunction.packageName); + frame.line = static_cast(jsFunction.line); + frame.column = jsFunction.column; + return true; +} + +std::string DfxOfflineParser::GetBundlePath(const std::string& originPath) const +{ + if (originPath.find(SANDBOX_PATH_PREFIX) != 0 || bundleName_.empty()) { + return originPath; + } + return BUNDLE_PATH_PREFIX + bundleName_ + "/" + originPath.substr(std::strlen(SANDBOX_PATH_PREFIX)); +} + +std::shared_ptr DfxOfflineParser::GetElfForFrame(const DfxFrame& frame) +{ + if (dfxMaps_ == nullptr) { + return nullptr; + } + auto maps = dfxMaps_->GetMaps(); + auto it = std::find_if(maps.begin(), maps.end(), [&](const std::shared_ptr& map) { + return map->name == frame.mapName; + }); + if (it != maps.end()) { + return (*it)->elf; + } + RegularElfFactory factory(GetBundlePath(frame.mapName)); + auto elf = factory.Create(); + if (elf == nullptr) { + DFXLOGE("elf is nullptr"); + return nullptr; + } + if (!elf->IsValid()) { + DFXLOGE("elf is invalid"); + return nullptr; + } + auto newMap = std::make_shared(); + newMap->name = frame.mapName; + newMap->elf = elf; + dfxMaps_->AddMap(newMap); + return elf; +} +} // namespace HiviewDFX +} // namespace OHOS \ No newline at end of file diff --git a/test/unittest/backtrace/backtrace_local_test.cpp b/test/unittest/backtrace/backtrace_local_test.cpp index f701620fb..9a407e48a 100644 --- a/test/unittest/backtrace/backtrace_local_test.cpp +++ b/test/unittest/backtrace/backtrace_local_test.cpp @@ -438,7 +438,7 @@ HWTEST_F(BacktraceLocalTest, BacktraceLocalTest011, TestSize.Level2) ASSERT_GT(formattedStack.size(), 0); ASSERT_TRUE(formattedStack.find("Tid:") != std::string::npos) << formattedStack; ASSERT_TRUE(formattedStack.find("backtrace_local_test") != std::string::npos) << formattedStack; - + ASSERT_TRUE(DfxJsonFormatter::FormatKernelStack(kernelStack, formattedStack, true)); ASSERT_TRUE(formattedStack.find("\"tid\":") != std::string::npos) << formattedStack; ASSERT_TRUE(formattedStack.find("backtrace_local_test") != std::string::npos) << formattedStack; @@ -717,5 +717,112 @@ HWTEST_F(BacktraceLocalTest, BacktraceLocalTest019, TestSize.Level2) ASSERT_GT(g_tid, 0) << "Failed to create child thread.\n"; g_tid = 0; } + +/** + * @tc.name: BacktraceLocalTest020 + * @tc.desc: test get thread kernel stack with ark + * @tc.type: FUNC + */ +HWTEST_F(BacktraceLocalTest, BacktraceLocalTest020, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "BacktraceLocalTest020: start."; + std::string res = ExecuteCommands("uname"); + if (res.find("Linux") != std::string::npos) { + ASSERT_NE(res.find("Linux"), std::string::npos); + } else { + std::string kernelStack; + ASSERT_EQ(DfxGetKernelStack(gettid(), kernelStack, true), 0); + GTEST_LOG_(INFO) << "BacktraceLocalTest020: end."; + } +} + +/** + * @tc.name: BacktraceLocalTest021 + * @tc.desc: test get FormatThreadKernelStack with parser + * @tc.type: FUNC + */ +HWTEST_F(BacktraceLocalTest, BacktraceLocalTest021, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "BacktraceLocalTest021: start."; + std::string res = ExecuteCommands("uname"); + if (res.find("Linux") != std::string::npos) { + ASSERT_NE(res.find("Linux"), std::string::npos); + } else { + std::string kernelStack; + ASSERT_EQ(DfxGetKernelStack(gettid(), kernelStack), 0); + DfxThreadStack threadStack; + auto parser = std::make_unique(""); + ASSERT_TRUE(FormatThreadKernelStack(kernelStack, threadStack, parser.get())); + ASSERT_GT(threadStack.frames.size(), 0); + for (const auto& frame : threadStack.frames) { + auto line = DfxFrameFormatter::GetFrameStr(frame); + ASSERT_NE(line.find("#"), std::string::npos); + ASSERT_NE(line.find("("), std::string::npos); + GTEST_LOG_(INFO) << line; + } + GTEST_LOG_(INFO) << "BacktraceLocalTest021: end."; + } +} + +/** + * @tc.name: BacktraceLocalTest022 + * @tc.desc: test FormatProcessKernelStack with parsesymbol + * @tc.type: FUNC + */ +HWTEST_F(BacktraceLocalTest, BacktraceLocalTest022, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "BacktraceLocalTest022: start."; + std::string res = ExecuteCommands("uname"); + if (res.find("Linux") != std::string::npos) { + ASSERT_NE(res.find("Linux"), std::string::npos); + } else { + pid_t pid = GetProcessPid(FOUNDATION_NAME); + std::vector tids; + std::vector nstids; + ASSERT_TRUE(GetTidsByPid(pid, tids, nstids)); + std::string processKernelStackInfo; + for (const auto& tid : tids) { + std::string kernelStack; + ASSERT_EQ(DfxGetKernelStack(tid, kernelStack), 0); + processKernelStackInfo += kernelStack; + } + std::vector processStack; + ASSERT_TRUE(FormatProcessKernelStack(processKernelStackInfo, processStack, true)); + for (const auto& threadStack : processStack) { + ASSERT_GT(threadStack.frames.size(), 0); + for (auto const& frame : threadStack.frames) { + auto line = DfxFrameFormatter::GetFrameStr(frame); + ASSERT_NE(line.find("#"), std::string::npos); + ASSERT_NE(line.find("("), std::string::npos); + GTEST_LOG_(INFO) << line; + } + } + GTEST_LOG_(INFO) << "BacktraceLocalTest022: end."; + } +} + +/** + * @tc.name: BacktraceLocalTest023 + * @tc.desc: test FormatKernelStack with parsesymbol + * @tc.type: FUNC + */ +HWTEST_F(BacktraceLocalTest, BacktraceLocalTest023, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "BacktraceLocalTest023: start."; + std::string res = ExecuteCommands("uname"); + if (res.find("Linux") != std::string::npos) { + ASSERT_NE(res.find("Linux"), std::string::npos); + } else { + std::string kernelStack; + ASSERT_EQ(DfxGetKernelStack(gettid(), kernelStack), 0); + std::string formattedStack; + ASSERT_TRUE(DfxJsonFormatter::FormatKernelStack(kernelStack, formattedStack, false, true)); + ASSERT_GT(formattedStack.size(), 0); + ASSERT_TRUE(formattedStack.find("Tid:") != std::string::npos) << formattedStack; + ASSERT_TRUE(formattedStack.find("backtrace_local_test") != std::string::npos) << formattedStack; + ASSERT_TRUE(formattedStack.find("OHOS::HiviewDFX::DfxGetKernelStack") != std::string::npos) << formattedStack; + GTEST_LOG_(INFO) << "BacktraceLocalTest023: end."; + } +} } // namespace HiviewDFX } // namepsace OHOS diff --git a/test/unittest/dump_catcher/dumpcatcher_command_test.cpp b/test/unittest/dump_catcher/dumpcatcher_command_test.cpp index 4c9ef82ff..b3ecaa443 100644 --- a/test/unittest/dump_catcher/dumpcatcher_command_test.cpp +++ b/test/unittest/dump_catcher/dumpcatcher_command_test.cpp @@ -306,5 +306,153 @@ HWTEST_F(DumpCatcherCommandTest, DumpCatcherCommandTest017, TestSize.Level2) EXPECT_EQ(count, len) << "DumpCatcherCommandTest017 Failed"; GTEST_LOG_(INFO) << "DumpCatcherCommandTest017: end."; } + +/** + * @tc.name: DumpCatcherCommandTest018 + * @tc.desc: test dumpcatcher command: -k [test hap] + * @tc.type: FUNC + */ +HWTEST_F(DumpCatcherCommandTest, DumpCatcherCommandTest018, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "DumpCatcherCommandTest018: start."; + std::string res = ExecuteCommands("uname"); + if (res.find("Linux") != std::string::npos) { + ASSERT_NE(res.find("Linux"), std::string::npos); + } else { + bool isSuccess = g_testPid != 0; + if (!isSuccess) { + ASSERT_FALSE(isSuccess); + GTEST_LOG_(ERROR) << "Failed to launch target hap."; + } else { + isSuccess = CheckProcessComm(g_testPid, TRUNCATE_TEST_BUNDLE_NAME); + if (!isSuccess) { + ASSERT_FALSE(isSuccess); + GTEST_LOG_(ERROR) << "Error process comm"; + } else { + string testCommand = "dumpcatcher -k " + to_string(g_testPid); + string dumpRes = ExecuteCommands(testCommand); + GTEST_LOG_(INFO) << dumpRes; + string log[] = {"tid=", "name=", "/system"}; + log[0] = log[0] + to_string(g_testPid); + log[1] = log[1] + TRUNCATE_TEST_BUNDLE_NAME; + int len = sizeof(log) / sizeof(log[0]); + int count = GetKeywordsNum(dumpRes, log, len); + EXPECT_EQ(count, len) << "DumpCatcherCommandTest018 Failed"; + GTEST_LOG_(INFO) << "DumpCatcherCommandTest018: end."; + } + } + } +} + +/** + * @tc.name: DumpCatcherCommandTest019 + * @tc.desc: test dumpcatcher command: -k [test hap] -a + * @tc.type: FUNC + */ +HWTEST_F(DumpCatcherCommandTest, DumpCatcherCommandTest019, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "DumpCatcherCommandTest019: start."; + std::string res = ExecuteCommands("uname"); + if (res.find("Linux") != std::string::npos) { + ASSERT_NE(res.find("Linux"), std::string::npos); + } else { + bool isSuccess = g_testPid != 0; + if (!isSuccess) { + ASSERT_FALSE(isSuccess); + GTEST_LOG_(ERROR) << "Failed to launch target hap."; + } else { + isSuccess = CheckProcessComm(g_testPid, TRUNCATE_TEST_BUNDLE_NAME); + if (!isSuccess) { + ASSERT_FALSE(isSuccess); + GTEST_LOG_(ERROR) << "Error process comm"; + } else { + string testCommand = "dumpcatcher -k " + to_string(g_testPid) + " -a"; + string dumpRes = ExecuteCommands(testCommand); + GTEST_LOG_(INFO) << dumpRes; + string log[] = {"tid=", "name=", "/system"}; + log[0] = log[0] + to_string(g_testPid); + log[1] = log[1] + TRUNCATE_TEST_BUNDLE_NAME; + int len = sizeof(log) / sizeof(log[0]); + int count = GetKeywordsNum(dumpRes, log, len); + EXPECT_EQ(count, len) << "DumpCatcherCommandTest019 Failed"; + GTEST_LOG_(INFO) << "DumpCatcherCommandTest019: end."; + } + } + } +} + +/** + * @tc.name: DumpCatcherCommandTest020 + * @tc.desc: test dumpcatcher command: -k [test hap] -f + * @tc.type: FUNC + */ +HWTEST_F(DumpCatcherCommandTest, DumpCatcherCommandTest020, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "DumpCatcherCommandTest020: start."; + std::string res = ExecuteCommands("uname"); + if (res.find("Linux") != std::string::npos) { + ASSERT_NE(res.find("Linux"), std::string::npos); + } else { + bool isSuccess = g_testPid != 0; + if (!isSuccess) { + ASSERT_FALSE(isSuccess); + GTEST_LOG_(ERROR) << "Failed to launch target hap."; + } else { + isSuccess = CheckProcessComm(g_testPid, TRUNCATE_TEST_BUNDLE_NAME); + if (!isSuccess) { + ASSERT_FALSE(isSuccess); + GTEST_LOG_(ERROR) << "Error process comm"; + } else { + string testCommand = "dumpcatcher -k " + to_string(g_testPid) + " -f"; + string dumpRes = ExecuteCommands(testCommand); + GTEST_LOG_(INFO) << dumpRes; + string log[] = {"Tid:", "Name:", "#00", "#01", "#02", "/system"}; + log[0] = log[0] + to_string(g_testPid); + log[1] = log[1] + TRUNCATE_TEST_BUNDLE_NAME; + int len = sizeof(log) / sizeof(log[0]); + int count = GetKeywordsNum(dumpRes, log, len); + EXPECT_EQ(count, len) << "DumpCatcherCommandTest020 Failed"; + GTEST_LOG_(INFO) << "DumpCatcherCommandTest020: end."; + } + } + } +} + +/** + * @tc.name: DumpCatcherCommandTest021 + * @tc.desc: test dumpcatcher command: -k [test hap] -a -f + * @tc.type: FUNC + */ +HWTEST_F(DumpCatcherCommandTest, DumpCatcherCommandTest021, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "DumpCatcherCommandTest021: start."; + std::string res = ExecuteCommands("uname"); + if (res.find("Linux") != std::string::npos) { + ASSERT_NE(res.find("Linux"), std::string::npos); + } else { + bool isSuccess = g_testPid != 0; + if (!isSuccess) { + ASSERT_FALSE(isSuccess); + GTEST_LOG_(ERROR) << "Failed to launch target hap."; + } else { + isSuccess = CheckProcessComm(g_testPid, TRUNCATE_TEST_BUNDLE_NAME); + if (!isSuccess) { + ASSERT_FALSE(isSuccess); + GTEST_LOG_(ERROR) << "Error process comm"; + } else { + string testCommand = "dumpcatcher -k " + to_string(g_testPid) + " -a -f"; + string dumpRes = ExecuteCommands(testCommand); + GTEST_LOG_(INFO) << dumpRes; + string log[] = {"Tid:", "Name:", "#00", "#01", "#02", "/system"}; + log[0] = log[0] + to_string(g_testPid); + log[1] = log[1] + TRUNCATE_TEST_BUNDLE_NAME; + int len = sizeof(log) / sizeof(log[0]); + int count = GetKeywordsNum(dumpRes, log, len); + EXPECT_EQ(count, len) << "DumpCatcherCommandTest021 Failed"; + GTEST_LOG_(INFO) << "DumpCatcherCommandTest021: end."; + } + } + } +} } // namespace HiviewDFX } // namepsace OHOS \ No newline at end of file diff --git a/test/unittest/process_dump/dump_utils_test.cpp b/test/unittest/process_dump/dump_utils_test.cpp index 2eb3c9f1c..6fe2d358a 100644 --- a/test/unittest/process_dump/dump_utils_test.cpp +++ b/test/unittest/process_dump/dump_utils_test.cpp @@ -49,7 +49,7 @@ void DumpUtilsTest::SetUpTestCase(void) UnwinderConfig::SetEnableMiniDebugInfo(true); UnwinderConfig::SetEnableLoadSymbolLazily(true); } - + namespace { #ifdef __aarch64__ constexpr const int LOCK_TYPE_IDX = 0; @@ -235,7 +235,7 @@ HWTEST_F(DumpUtilsTest, LockParserUnittest003, TestSize.Level2) } GTEST_LOG_(INFO) << "LockParserUnittest003: end."; } - + /** * @tc.name: LockParserUnittest004 * @tc.desc: test lock parser parse errorcheck lock @@ -274,7 +274,7 @@ HWTEST_F(DumpUtilsTest, LockParserUnittest004, TestSize.Level2) } GTEST_LOG_(INFO) << "LockParserUnittest004: end."; } - + /** * @tc.name: LockParserUnittest005 * @tc.desc: test lock parser parse PTHREAD_MUTEX_RECURSIVE lock diff --git a/test/unittest/unwind/BUILD.gn b/test/unittest/unwind/BUILD.gn index d3c6dcf4b..8a7bd7e9f 100644 --- a/test/unittest/unwind/BUILD.gn +++ b/test/unittest/unwind/BUILD.gn @@ -40,6 +40,7 @@ ohos_unittest("test_unwind") { "jsvm_test.cpp", "maps_test.cpp", "memory_test.cpp", + "offline_parser_test.cpp", "regs_test.cpp", "signal_test.cpp", "symbols_test.cpp", diff --git a/test/unittest/unwind/offline_parser_test.cpp b/test/unittest/unwind/offline_parser_test.cpp new file mode 100644 index 000000000..dbe0a89e0 --- /dev/null +++ b/test/unittest/unwind/offline_parser_test.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * 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 "dfx_offline_parser.h" + +using namespace OHOS::HiviewDFX; +using namespace testing::ext; +using namespace std; + +namespace OHOS { +namespace HiviewDFX { +class DfxOfflineParserTest : public testing::Test { +public: + static void SetUpTestCase(void) {} + static void TearDownTestCase(void) {} + void SetUp() {} + void TearDown() {} +}; + +/** + * @tc.name: DfxOfflineParserTest001 + * @tc.desc: test IsJsFrame + * @tc.type: FUNC + */ +HWTEST_F(DfxOfflineParserTest, DfxOfflineParserTest001, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "DfxOfflineParserTest001: start."; + DfxFrame frame; + frame.mapName = "xxx.hap"; + bool isJsFrame = DfxOfflineParser::IsJsFrame(frame); + ASSERT_TRUE(isJsFrame); + frame.mapName = "xxx.so"; + isJsFrame = DfxOfflineParser::IsJsFrame(frame); + ASSERT_FALSE(isJsFrame); + GTEST_LOG_(INFO) << "DfxOfflineParserTest001: end."; +} + +/** + * @tc.name: DfxOfflineParserTest002 + * @tc.desc: test GetBundlePath with empty bundlename + * @tc.type: FUNC + */ +HWTEST_F(DfxOfflineParserTest, DfxOfflineParserTest002, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "DfxOfflineParserTest002: start."; + DfxOfflineParser parser(""); + std::string originPath = "/system/lib/ld-musl-arm.so.1"; + std::string realPath = parser.GetBundlePath(originPath); + EXPECT_EQ(realPath, originPath); + originPath = "/data/storage/el1/bundle/libs/arm/xxx.so"; + realPath = parser.GetBundlePath(originPath); + EXPECT_EQ(realPath, originPath); + GTEST_LOG_(INFO) << "DfxOfflineParserTest002: end."; +} + +/** + * @tc.name: DfxOfflineParserTest003 + * @tc.desc: test GetBundlePath with bundlename + * @tc.type: FUNC + */ +HWTEST_F(DfxOfflineParserTest, DfxOfflineParserTest003, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "DfxOfflineParserTest003: start."; + DfxOfflineParser parser("testhap"); + std::string originPath = "/system/lib/ld-musl-arm.so.1"; + std::string realPath = parser.GetBundlePath(originPath); + EXPECT_EQ(realPath, originPath); + originPath = "/data/storage/el1/bundle/libs/arm/xxx.so"; + realPath = parser.GetBundlePath(originPath); + EXPECT_EQ(realPath, "/data/app/el1/bundle/public/testhap/libs/arm/xxx.so") << realPath; + GTEST_LOG_(INFO) << "DfxOfflineParserTest003: end."; +} + +/** + * @tc.name: DfxOfflineParserTest004 + * @tc.desc: test GetElfForFrame with dfxmaps nullptr + * @tc.type: FUNC + */ +HWTEST_F(DfxOfflineParserTest, DfxOfflineParserTest004, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "DfxOfflineParserTest004: start."; + DfxOfflineParser parser("testhap"); + parser.dfxMaps_ = nullptr; + DfxFrame frame; + auto elf = parser.GetElfForFrame(frame); + EXPECT_EQ(elf, nullptr); + GTEST_LOG_(INFO) << "DfxOfflineParserTest004: end."; +} + +/** + * @tc.name: DfxOfflineParserTest005 + * @tc.desc: test GetElfForFrame with find the same map in dfxmaps + * @tc.type: FUNC + */ +HWTEST_F(DfxOfflineParserTest, DfxOfflineParserTest005, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "DfxOfflineParserTest005: start."; + DfxOfflineParser parser("testhap"); + auto newMap = std::make_shared(); + auto elf = std::make_shared(); + newMap->elf = elf; + newMap->name = "testmap"; + parser.dfxMaps_->AddMap(newMap); + + DfxFrame frame; + frame.mapName = "testmap"; + auto retElf = parser.GetElfForFrame(frame); + EXPECT_EQ(retElf, elf); + GTEST_LOG_(INFO) << "DfxOfflineParserTest005: end."; +} + +/** + * @tc.name: DfxOfflineParserTest006 + * @tc.desc: test GetElfForFrame with invalid mapname + * @tc.type: FUNC + */ +HWTEST_F(DfxOfflineParserTest, DfxOfflineParserTest006, TestSize.Level2) +{ + GTEST_LOG_(INFO) << "DfxOfflineParserTest006: start."; + DfxOfflineParser parser("testhap"); + DfxFrame frame; + frame.mapName = "invalidmapname"; + auto elf = parser.GetElfForFrame(frame); + EXPECT_EQ(elf, nullptr); + GTEST_LOG_(INFO) << "DfxOfflineParserTest006: end."; +} +} // namespace HiviewDFX +} // namespace OHOS \ No newline at end of file diff --git a/tools/dump_catcher/BUILD.gn b/tools/dump_catcher/BUILD.gn index d21642bce..7da132d41 100644 --- a/tools/dump_catcher/BUILD.gn +++ b/tools/dump_catcher/BUILD.gn @@ -16,6 +16,7 @@ import("//base/hiviewdfx/faultloggerd/faultloggerd.gni") if (defined(ohos_lite)) { executable("dumpcatcher") { visibility = [ "*:*" ] + defines = [ "is_ohos_lite" ] include_dirs = [ ".", "$faultloggerd_interfaces_path/common", @@ -47,6 +48,7 @@ if (defined(ohos_lite)) { ".", "$faultloggerd_interfaces_path/common", "$faultloggerd_path/interfaces/innerkits/signal_handler", + "$faultloggerd_path/tools/process_dump", ] } @@ -69,12 +71,19 @@ if (defined(ohos_lite)) { "$faultloggerd_interfaces_path/innerkits/faultloggerd_client:libfaultloggerd", "$faultloggerd_path/common/dfxlog:dfx_hilog", "$faultloggerd_path/common/dfxutil:dfx_util", + "$faultloggerd_path/interfaces/innerkits/backtrace:libbacktrace_local", "$faultloggerd_path/interfaces/innerkits/dump_catcher:libdfx_dumpcatcher", + "$faultloggerd_path/interfaces/innerkits/formatter:libjson_stack_formatter", + "$faultloggerd_path/interfaces/innerkits/procinfo:libdfx_procinfo", ] external_deps = [ + "bundle_framework:appexecfwk_base", + "bundle_framework:appexecfwk_core", "c_utils:utils", "hilog:libhilog", + "ipc:ipc_core", + "samgr:samgr_proxy", ] } else { sources = [ "dummy_dumpcatcher.cpp" ] diff --git a/tools/dump_catcher/dump_catcher.cpp b/tools/dump_catcher/dump_catcher.cpp index 436023a7d..910000fae 100644 --- a/tools/dump_catcher/dump_catcher.cpp +++ b/tools/dump_catcher/dump_catcher.cpp @@ -18,20 +18,80 @@ #include "dfx_define.h" #include "dfx_dump_catcher.h" #include "dfx_log.h" +#include "dfx_json_formatter.h" +#include "dfx_kernel_stack.h" +#include "elapsed_time.h" +#include "procinfo.h" +#include "proc_util.h" +#include "dump_utils.h" +#ifndef is_ohos_lite +#include "bundle_mgr_interface.h" +#include "bundle_mgr_proxy.h" +#include "if_system_ability_manager.h" +#include "iservice_registry.h" +#include "system_ability_definition.h" +#endif namespace OHOS { namespace HiviewDFX { +namespace { +constexpr int WAIT_GET_KERNEL_STACK_TIMEOUT = 1000; // 1000 : time out 1000 ms + +#ifndef is_ohos_lite +sptr GetBundleManager() +{ + auto systemManager = SystemAbilityManagerClient::GetInstance().GetSystemAbilityManager(); + if (!systemManager) { + DFXLOGE("Get system ability manager failed"); + return nullptr; + } + auto remoteObject = systemManager->GetSystemAbility(BUNDLE_MGR_SERVICE_SYS_ABILITY_ID); + if (!remoteObject) { + DFXLOGE("Get system ability failed"); + return nullptr; + } + sptr bundleMgrProxy = iface_cast(remoteObject); + return bundleMgrProxy; +} +#endif + +std::string GetBundleNameByUid(int32_t uid) +{ +#ifndef is_ohos_lite + constexpr long minUid = 10000; // 10000 : minimum uid for hap + if (uid < minUid) { + return ""; + } + auto bundleInstance = GetBundleManager(); + if (bundleInstance == nullptr) { + DFXLOGE("bundleInstance is nullptr"); + return ""; + } + std::string bundleName; + auto ret = bundleInstance->GetNameForUid(uid, bundleName); + if (ret != ERR_OK) { + DFXLOGE("GetNameForUid failed! ret = %{public}d", ret); + return ""; + } + return bundleName; +#endif + return ""; +} +} DumpCatcher &DumpCatcher::GetInstance() { static DumpCatcher ins; return ins; } -void DumpCatcher::Dump(int32_t pid, int32_t tid, int timeout) const +void DumpCatcher::Dump(const DumpOptions& dumpOpt) const { + ElapsedTime counter; DfxDumpCatcher dfxDump; std::string msg = ""; - auto dumpResult = dfxDump.DumpCatchWithTimeout(pid, msg, timeout, tid); + int timeout = dumpOpt.timeout > WAIT_GET_KERNEL_STACK_TIMEOUT ? dumpOpt.timeout : WAIT_GET_KERNEL_STACK_TIMEOUT; + auto dumpResult = dfxDump.DumpCatchWithTimeout(dumpOpt.pid, msg, timeout, dumpOpt.tid); + auto dumpTime = counter.Elapsed(); std::string toFind = "Result:"; size_t startPos = msg.find(toFind); if (startPos != std::string::npos) { @@ -49,6 +109,58 @@ void DumpCatcher::Dump(int32_t pid, int32_t tid, int timeout) const } printf("%s", dumpResult.second.c_str()); printf("%s\n", msg.c_str()); + printf("total cost:(%" PRId64 ")ms\n", dumpTime); +} + +void DumpCatcher::DumpKernelStack(const DumpOptions& dumpOpt) const +{ + ElapsedTime counter; + std::string kernelStackInfo; + std::function stackTask = [&kernelStackInfo, &dumpOpt](int tid) { + if (tid <= 0) { + return false; + } + std::string tidKernelStackInfo; + auto ret = DfxGetKernelStack(tid, tidKernelStackInfo, dumpOpt.needArk); + if (ret == 0) { + kernelStackInfo.append(tidKernelStackInfo); + } + return true; + }; + std::vector tids; + (void)GetTidsByPidWithFunc(dumpOpt.pid, tids, stackTask); + auto getKernelStackTime = counter.Elapsed(); + counter.Reset(); + std::string parsedKernelStack; + bool ret = false; + time_t parseSymbolTime = 0; + if (dumpOpt.needParse) { + std::string bundleName = GetBundleName(dumpOpt.pid); + ret = DfxJsonFormatter::FormatKernelStack(kernelStackInfo, parsedKernelStack, false, true, bundleName); + parseSymbolTime = counter.Elapsed(); + } + if (ret) { + printf("%s\n", parsedKernelStack.c_str()); + } else { + printf("no need to format kernel stack or format failed.\n"); + printf("%s\n", kernelStackInfo.c_str()); + } + printf("threads size:(%zu)\n", tids.size()); + printf("get kernel stack cost:(%" PRId64 ")ms\n", getKernelStackTime); + if (dumpOpt.needParse) { + printf("parse symbol cost:(%" PRId64 ")ms\n", parseSymbolTime); + } +} + +std::string DumpCatcher::GetBundleName(int32_t pid) const +{ + long uid = 0; + uint64_t sigBlk = 0; + if (!GetUidAndSigBlk(pid, uid, sigBlk)) { + printf("GetUidAndSigBlk failed for pid(%d).\n", pid); + return ""; + } + return GetBundleNameByUid(uid); } } // namespace HiviewDFX } // namespace OHOS diff --git a/tools/dump_catcher/dump_catcher.h b/tools/dump_catcher/dump_catcher.h index 4dc901856..4a2fb3206 100644 --- a/tools/dump_catcher/dump_catcher.h +++ b/tools/dump_catcher/dump_catcher.h @@ -17,19 +17,35 @@ #define DFX_DUMP_CATCHER_H #include +#include #include "nocopyable.h" namespace OHOS { namespace HiviewDFX { +enum class DumpType { + DUMP_USER_STACK, + DUMP_KERNEL_STACK, +}; + +struct DumpOptions { + DumpType type = DumpType::DUMP_USER_STACK; + int32_t pid = 0; + int32_t tid = 0; + int timeout = 3000; // 3000 : default timeout 3000ms in dump user stack + bool needArk = false; + bool needParse = false; +}; class DumpCatcher final { public: static DumpCatcher &GetInstance(); ~DumpCatcher() = default; - void Dump(int32_t pid, int32_t tid, int timeout) const; + void Dump(const DumpOptions& dumpOpt) const; + void DumpKernelStack(const DumpOptions& dumpOpt) const; private: DumpCatcher() = default; + std::string GetBundleName(int32_t pid) const; DISALLOW_COPY_AND_MOVE(DumpCatcher); }; } // namespace HiviewDFX diff --git a/tools/dump_catcher/main.cpp b/tools/dump_catcher/main.cpp index 4810d55df..e62151842 100644 --- a/tools/dump_catcher/main.cpp +++ b/tools/dump_catcher/main.cpp @@ -28,22 +28,27 @@ #include "dfx_signal_local_handler.h" #endif +using namespace OHOS::HiviewDFX; static const std::string DUMP_STACK_TAG_USAGE = "Usage:"; static const std::string DUMP_STACK_TAG_FAILED = "Failed:"; -static constexpr int WAIT_GET_KERNEL_STACK_TIMEOUT = 1000; // 1000 : time out 1000 ms static constexpr int SAVE_CORE_DUMP_TIMEOUT = 10000; // time out 10000 ms static void PrintCommandHelp() { printf("%s\n", DUMP_STACK_TAG_USAGE.c_str()); - printf("-p pid -t tid dump the stacktrace of the thread with given tid.\n"); - printf("-p pid dump the stacktrace of all the threads with given pid.\n"); - printf("-T timeout(ms) dump in the timeout.\n"); + printf("-p pid | dump the stacktrace of all the threads with given pid, timeout default 3000ms.\n"); + printf("-p pid -t tid | dump the stacktrace of the thread with given tid .\n"); + printf("-p pid -T timeout(ms) | dump the stacktrace of the thread with given tid in timeout.\n"); + printf("-k pid | dump the origin kernel stacktrace with noark of the pid.\n"); + printf("-k pid -a | dump the origin kernel stacktrace with ark of the pid.\n"); + printf("-k pid -a -f | dump the origin kernel stacktrace with ark of the pid and format parse stack.\n"); + printf("-c save pid | begin to coredump the process of the pid.\n"); + printf("-c cancel pid | cancel to coredump the process of the pid.\n"); } static void PrintCommandFailed() { - printf("%s\npid and tid must > 0 and timeout must > 1000.\n", DUMP_STACK_TAG_FAILED.c_str()); + printf("%s\npid and tid and timeout must > 0.\n", DUMP_STACK_TAG_FAILED.c_str()); } @@ -82,7 +87,7 @@ static int ExecuteCoredumpCmd(std::string subCmd, char* pidChar) } } -static int ParseParamters(int argc, char *argv[], int32_t &pid, int32_t &tid, int &timeout) +static int ParseParamters(int argc, char *argv[], DumpOptions& dumpOpt) { int ret = 0; if (argc <= 1) { @@ -91,17 +96,14 @@ static int ParseParamters(int argc, char *argv[], int32_t &pid, int32_t &tid, in DFXLOGD("[%{public}d]: argc: %{public}d, argv1: %{public}s", __LINE__, argc, argv[1]); int optRet; - const char *optString = "p:t:c:T:"; + const char *optString = "p:t:c:T:k:af"; while ((optRet = getopt(argc, argv, optString)) != -1) { if (optarg == nullptr) { continue; } switch (optRet) { - case 'p': - ret = GetIdFromArgs(optarg, pid); - break; - case 't': - ret = GetIdFromArgs(optarg, tid); + case 'a': + dumpOpt.needArk = true; break; case 'c': if (optind >= argc) { @@ -110,13 +112,22 @@ static int ParseParamters(int argc, char *argv[], int32_t &pid, int32_t &tid, in } ExecuteCoredumpCmd(optarg, argv[optind]); return 0; + case 'f': + dumpOpt.needParse = true; + break; + case 'k': + ret = GetIdFromArgs(optarg, dumpOpt.pid); + dumpOpt.type = DumpType::DUMP_KERNEL_STACK; + break; + case 'p': + ret = GetIdFromArgs(optarg, dumpOpt.pid); + dumpOpt.type = DumpType::DUMP_USER_STACK; + break; + case 't': + ret = GetIdFromArgs(optarg, dumpOpt.tid); + break; case 'T': - if (atoi(optarg) > WAIT_GET_KERNEL_STACK_TIMEOUT) { - timeout = atoi(optarg); - } else { - ret = -1; - PrintCommandFailed(); - } + ret = GetIdFromArgs(optarg, dumpOpt.timeout); break; default: ret = 0; @@ -136,18 +147,20 @@ int main(int argc, char *argv[]) DFX_InstallLocalSignalHandler(); #endif - int32_t pid = 0; - int32_t tid = 0; - int timeout = 3000; + DumpOptions dumpOpt; alarm(DUMPCATCHER_TIMEOUT); setsid(); - if (ParseParamters(argc, argv, pid, tid, timeout) <= 0) { + if (ParseParamters(argc, argv, dumpOpt) <= 0) { return 0; } - DFXLOGD("pid: %{public}d, tid: %{public}d, timeout: %{public}d", pid, tid, timeout); - OHOS::HiviewDFX::DumpCatcher::GetInstance().Dump(pid, tid, timeout); + DFXLOGD("pid: %{public}d, tid: %{public}d, timeout: %{public}d", dumpOpt.pid, dumpOpt.tid, dumpOpt.timeout); + if (dumpOpt.type == DumpType::DUMP_USER_STACK) { + DumpCatcher::GetInstance().Dump(dumpOpt); + } else if (dumpOpt.type == DumpType::DUMP_KERNEL_STACK) { + DumpCatcher::GetInstance().DumpKernelStack(dumpOpt); + } return 0; } diff --git a/tools/process_dump/BUILD.gn b/tools/process_dump/BUILD.gn index 5a53f81df..911376fb4 100644 --- a/tools/process_dump/BUILD.gn +++ b/tools/process_dump/BUILD.gn @@ -161,6 +161,7 @@ if (defined(ohos_lite)) { "hilog:libhilog", "hisysevent:libhisysevent", "hitrace:libhitracechain", + "hitrace:hitrace_meter", "init:libbegetutil", "jsoncpp:jsoncpp", "ipc:ipc_core", -- Gitee