HiDebug C/C++接口功能独立,需要获取调试信息时直接调用。具体调用示例请参考下文。
下文展示如何在应用内使用HiDebug NDK接口以进行线程栈回溯,且获取进程内线程的CPU使用率:
步骤一:创建项目
使用DevEco Studio新建一个Native C++工程,并新增文件“test_backtrace.cpp”与“test_backtrace.h”,目录结构如下:
entry:
src:
main:
cpp:
- types:
- libentry:
- index.d.ts
- CMakeLists.txt
- napi_init.cpp
- test_backtrace.cpp
- test_backtrace.h
ets:
pages:
- Index.ets 编辑“test_backtrace.h”文件,内容如下:
#ifndef MYAPPLICATION_TESTBACKTRACE_H #define MYAPPLICATION_TESTBACKTRACE_H void BacktraceCurrentThread(); #endif // MYAPPLICATION_TESTBACKTRACE_H
编辑“test_backtrace.cpp”文件,内容如下:
#include "test_backtrace.h"
#include <condition_variable>
#include <csignal>
#include <unistd.h>
#include <sys/syscall.h>
#include "hidebug/hidebug.h"
#include "hilog/log.h"
#define MAX_FRAME_SIZE 256 // 最大栈回溯深度,应根据业务场景调整该值。
namespace {
constexpr auto LOG_PRINT_DOMAIN = 0xFF00;
}
class BackTraceObject { // 封装了抓栈过程中需要使用的资源,在使用过程中请注意线程安全和异步信号安全。
public:
static BackTraceObject& GetInstance();
BackTraceObject(const BackTraceObject&) = delete;
BackTraceObject& operator=(const BackTraceObject&) = delete;
BackTraceObject(BackTraceObject&&) = delete;
BackTraceObject& operator=(BackTraceObject&&) = delete;
bool Init(uint32_t size);
void Release();
int BackTraceFromFp(void* startFp, int size); // 该函数异步信号安全。
void SymbolicAddress(int index); // 该函数耗费性能,请避免频繁调用。
void PrintStackFrame(void* pc, const HiDebug_StackFrame& frame);
private:
BackTraceObject() = default;
~BackTraceObject() = default;
HiDebug_Backtrace_Object backtraceObject_ = nullptr;
void** pcs_ = nullptr;
};
BackTraceObject& BackTraceObject::GetInstance() // 单例模式,用于信号处理和请求抓栈线程的数据交互。注意该类非异步信号安全,业务逻辑应确保同一时刻仅单个线程访问。
{
static BackTraceObject instance;
return instance;
}
bool BackTraceObject::Init(uint32_t size) // 初始化资源。
{
backtraceObject_ = OH_HiDebug_CreateBacktraceObject();
if (backtraceObject_ == nullptr || size > MAX_FRAME_SIZE) {
return false;
}
pcs_ = new (std::nothrow) void* [size]{nullptr};
if (pcs_ == nullptr) {
return false;
}
return true;
}
void BackTraceObject::Release() // 释放资源。
{
OH_HiDebug_DestroyBacktraceObject(backtraceObject_);
backtraceObject_ = nullptr;
delete[] pcs_;
pcs_ = nullptr;
}
int BackTraceObject::BackTraceFromFp(void* startFp, int size) // 栈回溯获取pc地址。
{
if (size <= MAX_FRAME_SIZE) {
return OH_HiDebug_BacktraceFromFp(backtraceObject_, startFp, pcs_, size); // OH_HiDebug_BacktraceFromFp接口调用示例。
}
return 0;
}
void BackTraceObject::PrintStackFrame(void* pc, const HiDebug_StackFrame& frame) // 输出栈内容。
{
if (frame.type == HIDEBUG_STACK_FRAME_TYPE_JS) { // 根据栈帧的类型,区分不同的栈帧输出方式。
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "testTag",
"js stack frame info for pc: %{public}p is "
"relativePc: %{public}p "
"line: %{public}d "
"column: %{public}d "
"mapName: %{public}s "
"functionName: %{public}s "
"url: %{public}s "
"packageName: %{public}s.",
pc,
reinterpret_cast<void*>(frame.frame.js.relativePc),
frame.frame.js.line,
frame.frame.js.column,
frame.frame.js.mapName,
frame.frame.js.functionName,
frame.frame.js.url,
frame.frame.js.packageName);
} else {
OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "testTag",
"native stack frame info for pc: %{public}p is "
"relativePc: %{public}p "
"funcOffset: %{public}p "
"mapName: %{public}s "
"functionName: %{public}s "
"buildId: %{public}s "
"reserved: %{public}s.",
pc,
reinterpret_cast<void*>(frame.frame.native.relativePc),
reinterpret_cast<void*>(frame.frame.native.funcOffset),
frame.frame.native.mapName,
frame.frame.native.functionName,
frame.frame.native.buildId,
frame.frame.native.reserved);
}
}
void BackTraceObject::SymbolicAddress(int index) // 栈解析接口。
{
if (index < 0 || index >= MAX_FRAME_SIZE) {
return;
}
OH_HiDebug_SymbolicAddress(backtraceObject_, pcs_[index], this,
[] (void* pc, void* arg, const HiDebug_StackFrame* frame) {
reinterpret_cast<BackTraceObject*>(arg)->PrintStackFrame(pc, *frame);
}); // 调用OH_HiDebug_SymbolicAddress接口解析栈。
}
void BacktraceCurrentThread() // 该接口非线程安全,同一时刻只能由一个线程使用。
{
if (!BackTraceObject::GetInstance().Init(MAX_FRAME_SIZE)) { // 注意:在调用栈回溯函数之前,需申请资源,且不可重复初始化。
BackTraceObject::GetInstance().Release();
OH_LOG_Print(LOG_APP, LOG_WARN, LOG_PRINT_DOMAIN, "testTag", "failed init backtrace object.");
return;
}
int pcSize = BackTraceObject::GetInstance().BackTraceFromFp(__builtin_frame_address(0), MAX_FRAME_SIZE);
for (int i = 0; i < pcSize; i++) {
BackTraceObject::GetInstance().SymbolicAddress(i); // 主线程获取pc后,对pc值进行栈解析。
}
BackTraceObject::GetInstance().Release(); // 栈回溯并且解析结束后,及时释放资源。
} 编辑“CMakeLists.txt”文件,添加库依赖:
# 新增动态库依赖libohhidebug.so和libhilog_ndk.z.so(日志输出) add_library(entry SHARED napi_init.cpp test_backtrace.cpp test_async_context.cpp) target_link_libraries(entry PUBLIC libace_napi.z.so libhilog_ndk.z.so libohhidebug.so)
编辑“napi_init.cpp”文件,导入依赖文件并定义测试方法:
#include <thread>
#include "hidebug/hidebug.h"
#include "hilog/log.h"
#include "test_backtrace.h"
#include "test_async_context.h"
#undef LOG_TAG
#define LOG_TAG "testTag"
__attribute((noinline)) __attribute((optnone)) void TestNativeFrames(int i)
{
if (i > 0) {
TestNativeFrames(i - 1);
return;
}
BacktraceCurrentThread();
}
__attribute((noinline)) __attribute((optnone)) napi_value TestBackTrace(napi_env env, napi_callback_info info)
{
TestNativeFrames(1);
return nullptr;
}
napi_value TestGetThreadCpuUsage(napi_env env, napi_callback_info info)
{
HiDebug_ThreadCpuUsagePtr cpuUsage = OH_HiDebug_GetAppThreadCpuUsage();
while (cpuUsage != nullptr) {
OH_LOG_INFO(LogType::LOG_APP,
"GetAppThreadCpuUsage: threadId %{public}d, cpuUsage: %{public}f", cpuUsage->threadId, cpuUsage->cpuUsage);
cpuUsage = cpuUsage->next; // 获取下一个线程的cpu使用率对象指针。
}
OH_HiDebug_FreeThreadCpuUsage(&cpuUsage); // 释放内存,防止内存泄漏。
return nullptr;
}
napi_value TestAsyncContext(napi_env env, napi_callback_info info)
{
TestAsyncContextChain();
return nullptr;
} 注册“TestHiDebugNdk”为ArkTS接口并初始化主线程的信号处理函数:
napi_property_descriptor desc[] = {
{ "testGetThreadCpuUsage", nullptr, TestGetThreadCpuUsage, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "testBackTrace", nullptr, TestBackTrace, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "testAsyncContext", nullptr, TestAsyncContext, nullptr, nullptr, nullptr, napi_default, nullptr },
}; 编辑“index.d.ts”文件,声明ArkTS接口:
export const testGetThreadCpuUsage: () => void; export const testBackTrace: () => void; export const testAsyncContext: () => void;
编辑“Index.ets”文件,添加触发接口调用的按钮,示例代码如下:
导入依赖:
import testNapi from 'libentry.so';
定义测试方法:
function testBackTraceJsFrame(i : number) : void {
if (i > 0) {
return testBackTraceJsFrame(i-1);
}
return testNapi.testBackTrace();
}
function testBackTrace() : void {
testBackTraceJsFrame(3);
}
function testGetThreadCpuUsage() : void {
testNapi.testGetThreadCpuUsage();
}
function testAsyncContext() : void {
testNapi.testAsyncContext();
} 添加按钮以触发接口调用:
Button('testGetThreadCpuUsage')
.type(ButtonType.Capsule)
.margin({
top: 20
})
.backgroundColor('#0D9FFB')
.width('60%')
.height('5%')
// 添加点击事件
.onClick(testGetThreadCpuUsage);
Button('testHiDebugBackTrace')
.type(ButtonType.Capsule)
.margin({
top: 20
})
.backgroundColor('#0D9FFB')
.width('60%')
.height('5%')
// 添加点击事件
.onClick(testBackTrace);
Button('testAsyncContext')
.type(ButtonType.Capsule)
.margin({
top: 20
})
.backgroundColor('#0D9FFB')
.width('60%')
.height('5%')
// 添加点击事件
.onClick(testAsyncContext); 步骤二:运行工程
点击DevEco Studio界面中的运行按钮,然后分别单击应用界面上的“testGetThreadCpuUsage”和“testHiDebugBackTrace”按钮。
在DevEco Studio底部切换到“Log”窗口,设置日志过滤条件为“testTag”,即可查看相关日志:
... 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19261, cpuUsage: 0.000104 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19381, cpuUsage: 0.000000 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19382, cpuUsage: 0.000040 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19383, cpuUsage: 0.000010 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19384, cpuUsage: 0.000001 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19386, cpuUsage: 0.000038 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19387, cpuUsage: 0.000000 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19388, cpuUsage: 0.000007 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19389, cpuUsage: 0.000004 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19390, cpuUsage: 0.000007 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19391, cpuUsage: 0.000006 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19393, cpuUsage: 0.000001 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19394, cpuUsage: 0.000004 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19397, cpuUsage: 0.000002 10-22 15:46:05.933 19261-19261 A00000/com.sam...gtool/testTag com.sampl...ebugtool I GetAppThreadCpuUsage: threadId 19401, cpuUsage: 0.000001 ... 10-22 15:46:13.351 19261-19261 A0FF00/com.sam...gtool/testTag com.sampl...ebugtool I native stack frame info for pc: ************ is relativePc: ****** funcOffset: 0x38 mapName: /data/storage/el1/bundle/libs/arm64/libentry.so functionName: TestNativeFrames(int) buildId: b6d3429f6e2e594b1c696e13049dae7e51694099 reserved: (null). 10-22 15:46:13.351 19261-19261 A0FF00/com.sam...gtool/testTag com.sampl...ebugtool I native stack frame info for pc: ************ is relativePc: ****** funcOffset: 0x30 mapName: /data/storage/el1/bundle/libs/arm64/libentry.so functionName: TestNativeFrames(int) buildId: b6d3429f6e2e594b1c696e13049dae7e51694099 reserved: (null). 10-22 15:46:13.351 19261-19261 A0FF00/com.sam...gtool/testTag com.sampl...ebugtool I native stack frame info for pc: ************ is relativePc: ****** funcOffset: 0x1c mapName: /data/storage/el1/bundle/libs/arm64/libentry.so functionName: TestBackTrace(napi_env__*, napi_callback_info__*) buildId: b6d3429f6e2e594b1c696e13049dae7e51694099 reserved: (null). ... 10-22 15:46:13.354 19261-19261 A0FF00/com.sam...gtool/testTag com.sampl...ebugtool I js stack frame info for pc: ************ is relativePc: ****** line: 27 column: 21 mapName: /data/storage/el1/bundle/entry.hap functionName: testBackTraceJsFrame url: entry|entry|1.0.0|src/main/ets/pages/Index.ts packageName: . 10-22 15:46:13.354 19261-19261 A0FF00/com.sam...gtool/testTag com.sampl...ebugtool I js stack frame info for pc: ************ is relativePc: ****** line: 25 column: 16 mapName: /data/storage/el1/bundle/entry.hap functionName: testBackTraceJsFrame url: entry|entry|1.0.0|src/main/ets/pages/Index.ts packageName: . 10-22 15:46:13.354 19261-19261 A0FF00/com.sam...gtool/testTag com.sampl...ebugtool I js stack frame info for pc: ************ is relativePc: ****** line: 25 column: 16 mapName: /data/storage/el1/bundle/entry.hap functionName: testBackTraceJsFrame url: entry|entry|1.0.0|src/main/ets/pages/Index.ts packageName: . 10-22 15:46:13.354 19261-19261 A0FF00/com.sam...gtool/testTag com.sampl...ebugtool I js stack frame info for pc: ************ is relativePc: ****** line: 25 column: 16 mapName: /data/storage/el1/bundle/entry.hap functionName: testBackTraceJsFrame url: entry|entry|1.0.0|src/main/ets/pages/Index.ts packageName: .... ...
从API版本26.0.0开始提供管理异步上下文功能。下文展示如何在应用内使用HiDebug C/C++异步上下文管理接口,构造A->B单层异步调用链,在异步任务提交和完成时分别压入和弹出异步上下文,建立和解除异步调用链关系。
该功能仅支持ARM64架构,且仅可在debug版本应用中使用,需配合hiprofiler调优组件追踪完整的异步调用栈。
编辑“test_async_context.h”文件,声明异步上下文调用链入口函数:
#ifndef MYAPPLICATION_TESTASYNCCONTEXT_H #define MYAPPLICATION_TESTASYNCCONTEXT_H // 测试异步上下文管理接口,构造A->B最小三方异步调用。 void TestAsyncContextChain(); #endif // MYAPPLICATION_TESTASYNCCONTEXT_H
编辑“test_async_context.cpp”文件,构造A->B单层异步调用链,演示四个接口的调用时序(A:Acquire/Release;B:Push/Pop):
#include "test_async_context.h"
#include "hidebug/hidebug.h"
#include "hilog/log.h"
#include <cstdlib>
#include <thread>
#include <chrono>
#undef LOG_TAG
#define LOG_TAG "testTag"
// IsDebuggableHap()与DfxInvokeHiDebugCallback均检查。
// 环境变量 HAP_DEBUGGABLE;setenv 须早于注入,故用 constructor 在 libentry.so 加载时设置。
__attribute__((constructor)) static void SetHapDebuggableEnv()
{
setenv("HAP_DEBUGGABLE", "true", 1);
}
// 模拟异步任务耗时(ms)。
static constexpr int ASYNC_TASK_DURATION_MS = 500;
// 三方异步任务上下文,用于在线程间透传异步上下文句柄
struct AsyncTaskCtx {
uint64_t asyncCtx;
};
// B:三方异步任务,使用std::thread模拟三方异步框架。
static void ThirdPartyAsyncTask(AsyncTaskCtx *ctx)
{
if (ctx == nullptr) {
return;
}
// 异步任务执行时,将异步上下文压入当前线程运行上下文,建立异步调用链。
OH_HiDebug_PushAsyncContext(ctx->asyncCtx);
OH_LOG_INFO(LogType::LOG_APP, "[Async-B] Third-party async task start, push context %{public}llu",
(unsigned long long)ctx->asyncCtx);
std::this_thread::sleep_for(std::chrono::milliseconds(ASYNC_TASK_DURATION_MS)); // 模拟三方异步耗时。
OH_LOG_INFO(LogType::LOG_APP, "[Async-B] Third-party async task done");
// 异步任务完成时,将异步上下文弹出,解除异步调用链。
OH_HiDebug_PopAsyncContext(ctx->asyncCtx);
delete ctx;
}
// A:提交方,在独立线程执行以避免阻塞napi调用线程。
static void OuterTaskFunc()
{
// 异步任务提交前,获取一个异步上下文。
uint64_t asyncCtx = OH_HiDebug_AcquireAsyncContext();
OH_LOG_INFO(LogType::LOG_APP, "[Async-A] Acquired context: %{public}llu", (unsigned long long)asyncCtx);
// 提交三方异步任务B,透传异步上下文句柄。
OH_LOG_INFO(LogType::LOG_APP, "[Async-A] Submit third-party async task B");
auto *ctx = new (std::nothrow) AsyncTaskCtx{asyncCtx};
if (ctx == nullptr) {
OH_HiDebug_ReleaseAsyncContext(asyncCtx);
return;
}
std::thread worker(ThirdPartyAsyncTask, ctx);
worker.join(); // 等待B完成,保证Release在Push/Pop之后
// 异步任务结束后,释放异步上下文资源,防止资源泄漏。
OH_HiDebug_ReleaseAsyncContext(asyncCtx);
OH_LOG_INFO(LogType::LOG_APP, "[Async-A] Released context");
}
// 构造A->B最小三方异步调用,演示管理异步上下文的四个接口。
void TestAsyncContextChain()
{
// 在独立线程执行A,避免阻塞napi调用线程。
std::thread(OuterTaskFunc).detach();
} 编辑“CMakeLists.txt”文件,将新增源文件test_async_context.cpp加入add_library编译目标。
编辑“napi_init.cpp”文件,导入“test_async_context.h”并新增napi包装方法TestAsyncContext:
#include <thread>
#include "hidebug/hidebug.h"
#include "hilog/log.h"
#include "test_backtrace.h"
#include "test_async_context.h"
#undef LOG_TAG
#define LOG_TAG "testTag"
__attribute((noinline)) __attribute((optnone)) void TestNativeFrames(int i)
{
if (i > 0) {
TestNativeFrames(i - 1);
return;
}
BacktraceCurrentThread();
}
__attribute((noinline)) __attribute((optnone)) napi_value TestBackTrace(napi_env env, napi_callback_info info)
{
TestNativeFrames(1);
return nullptr;
}
napi_value TestGetThreadCpuUsage(napi_env env, napi_callback_info info)
{
HiDebug_ThreadCpuUsagePtr cpuUsage = OH_HiDebug_GetAppThreadCpuUsage();
while (cpuUsage != nullptr) {
OH_LOG_INFO(LogType::LOG_APP,
"GetAppThreadCpuUsage: threadId %{public}d, cpuUsage: %{public}f", cpuUsage->threadId, cpuUsage->cpuUsage);
cpuUsage = cpuUsage->next; // 获取下一个线程的cpu使用率对象指针。
}
OH_HiDebug_FreeThreadCpuUsage(&cpuUsage); // 释放内存,防止内存泄漏。
return nullptr;
}
napi_value TestAsyncContext(napi_env env, napi_callback_info info)
{
TestAsyncContextChain();
return nullptr;
} 注册“testAsyncContext”为ArkTS接口:
napi_property_descriptor desc[] = {
{ "testGetThreadCpuUsage", nullptr, TestGetThreadCpuUsage, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "testBackTrace", nullptr, TestBackTrace, nullptr, nullptr, nullptr, napi_default, nullptr },
{ "testAsyncContext", nullptr, TestAsyncContext, nullptr, nullptr, nullptr, napi_default, nullptr },
}; 编辑“index.d.ts”文件,声明ArkTS接口testAsyncContext:
export const testGetThreadCpuUsage: () => void; export const testBackTrace: () => void; export const testAsyncContext: () => void;
编辑“Index.ets”文件,定义测试方法:
function testBackTraceJsFrame(i : number) : void {
if (i > 0) {
return testBackTraceJsFrame(i-1);
}
return testNapi.testBackTrace();
}
function testBackTrace() : void {
testBackTraceJsFrame(3);
}
function testGetThreadCpuUsage() : void {
testNapi.testGetThreadCpuUsage();
}
function testAsyncContext() : void {
testNapi.testAsyncContext();
} 编辑“Index.ets”文件,添加按钮以触发接口调用:
Button('testGetThreadCpuUsage')
.type(ButtonType.Capsule)
.margin({
top: 20
})
.backgroundColor('#0D9FFB')
.width('60%')
.height('5%')
// 添加点击事件
.onClick(testGetThreadCpuUsage);
Button('testHiDebugBackTrace')
.type(ButtonType.Capsule)
.margin({
top: 20
})
.backgroundColor('#0D9FFB')
.width('60%')
.height('5%')
// 添加点击事件
.onClick(testBackTrace);
Button('testAsyncContext')
.type(ButtonType.Capsule)
.margin({
top: 20
})
.backgroundColor('#0D9FFB')
.width('60%')
.height('5%')
// 添加点击事件
.onClick(testAsyncContext); 启动hiprofiler采集任务,使能异步上下文追踪。
hdc shell
hiprofiler_cmd -c - -o /data/local/tmp/hiprofiler_data.htrace -t 60 -s -k <<CONFIG
request_id: 1
session_config {
buffers {
pages: 16384
}
}
plugin_configs {
plugin_name: "nativehook"
sample_interval: 5000
config_data {
save_file: false
smb_pages: 16384
max_stack_depth: 20
process_name: "com.samples.hidebugtool"
fp_unwind: true
blocked: true
callframe_compress: true
record_accurately: true
offline_symbolization: true
startup_mode: false
async_stack_enable: true
async_type: CUSTOMIZE
}
}
CONFIG 点击DevEco Studio界面中的运行按钮,然后单击应用界面上的“testAsyncContext”按钮。
在DevEco Studio底部切换到“Log”窗口,设置日志过滤条件为“testTag”,即可查看异步调用链日志。