打印函数调用堆栈

入职腾讯以来,天天都很忙。今天难得清闲,借此机会记录一下前段时间搞的一个小功能,打印函数的调用堆栈。

什么是函数的调用堆栈?给你看一段代码你马上就明白了。
在这里插入图片描述
比如我在DecideAllTaskID中调用A,那么打log之后我们就可以看到LogBacktrace的堆栈回溯结果了。

在这里插入图片描述
以上就是堆栈回溯的结果了,前面两个null可能是符号表中没有找到符号。

话不多说,直接上堆栈回溯的代码:

extern "C"
{
	struct BacktraceState
	{
		void** current;
		void** end;
	};

	static _Unwind_Reason_Code UnwindCallback(struct _Unwind_Context* context, void* arg)
	{
		BacktraceState* state = static_cast<BacktraceState*>(arg);
		uintptr_t pc = _Unwind_GetIP(context);
		if (pc)
		{
			if (state->current == state->end)
			{
				return _URC_END_OF_STACK;
			}
			else
			{
				*state->current++ = reinterpret_cast<void*>(pc);
			}
		}
		return _URC_NO_REASON;
	}

	static size_t CaptureBacktrace(void** buffer, size_t max)
	{
		BacktraceState state = { buffer, buffer + max };
		_Unwind_Backtrace(UnwindCallback, &state);

		return state.current - buffer;
	}

	static void LogBacktrace()
	{
		const size_t max = 100;
		void* buffer[max];
		const size_t count = CaptureBacktrace(buffer, max);


		for (size_t idx = 0; idx < count; ++idx)
		{
			const void* addr = buffer[idx];
			const char* symbol = "Null";
			const char* fileName = "";

			Dl_info info;
			if (dladdr(addr, &info))
			{
				if (info.dli_sname)
				{
					symbol = info.dli_sname;
				}

				if (info.dli_fname)
				{
					fileName = info.dli_fname;
				}
			}
			printf_console_release(
				"[zzxxw]#%02d  pc %x %s %s\n",
				idx,
				(unsigned int)addr - (unsigned int)info.dli_fbase,
				fileName,
				symbol
			);
		}
	}
}

以上就是堆栈回溯的全部代码,当然需要引入两个头文件以及其连带的头文件:

#include <unwind.h>
#include <dlfcn.h>

这两个头文件网上都有,这里就不贴了。

这里要说一下unwind.h这个头文件,他是linux下libunwind.h的win版,功能都是一样的。

接下来大致解析一下这段回溯代码:

	struct BacktraceState
	{
		void** current;
		void** end;
	};

这段是堆栈回溯的两个指针,current指向当前位置,end指向栈顶位置(这里为什么是栈顶,因为栈的生长方向是自顶向下的)。


	static _Unwind_Reason_Code UnwindCallback(struct _Unwind_Context* context, void* arg)
	{
		BacktraceState* state = static_cast<BacktraceState*>(arg);
		uintptr_t pc = _Unwind_GetIP(context);
		if (pc)
		{
			if (state->current == state->end)
			{
				return _URC_END_OF_STACK;
			}
			else
			{
				*state->current++ = reinterpret_cast<void*>(pc);
			}
		}
		return _URC_NO_REASON;
	}

这段代码就是堆栈回溯的本体了,其实本身代码并不复杂,原理就是通过_Unwind_GetIP函数来获得context的pc值(在linux系统中,pc的位置为每个堆栈的栈顶),我们只要找到每个栈顶也就是pc值,其实就算完成了回溯。在这里这个函数帮你封装的很好,当达到root的时候,则会返回_URC_NO_REASON,否则返回_URC_END_OF_STACK。


	static size_t CaptureBacktrace(void** buffer, size_t max)
	{
		BacktraceState state = { buffer, buffer + max };
		_Unwind_Backtrace(UnwindCallback, &state);
		return state.current - buffer;
	}

这里传入的两个参数 void** buffer和size_t max,前者是函数指针,后者是回溯的最大层数。这里声明了一个新的BacktraceState 作为子容器,_Unwind_Backtrace通过回调来触发UnwindCallback,返回值state.current - buffer为当前共进行回溯的次数(也可以理解为层数)。


	static void LogBacktrace()
	{
		const size_t max = 100;
		void* buffer[max];
		const size_t count = CaptureBacktrace(buffer, max);
		for (size_t idx = 0; idx < count; ++idx)
		{
			const void* addr = buffer[idx];
			const char* symbol = "Null";
			const char* fileName = "";
			Dl_info info;
			if (dladdr(addr, &info))
			{
				if (info.dli_sname)
				{
					symbol = info.dli_sname;
				}
				if (info.dli_fname)
				{
					fileName = info.dli_fname;
				}
			}
			printf_console_release(
				"[zzxxw]#%02d  pc %x %s %s\n",
				idx,
				(unsigned int)addr - (unsigned int)info.dli_fbase,
				fileName,
				symbol
			);
		}
	}

在这个函数中,count已经记录了回溯的次数,只需要轮训打印即可输出堆栈,但此时我们获取到的仅仅是地址而已,地址是没有可读性的,往往需要将地址转换为函数名或者so名,这里就用到了Dl_info。Dl_info可以获取某个地址的符号信息,并存到info中。

我们来去头文件看看info这个结构体:

typedef struct
{
  const char *dli_fname;	/* File name of defining object.  */
  void *dli_fbase;		/* Load address of that object.  */
  const char *dli_sname;	/* Name of nearest symbol.  */
  void *dli_saddr;		/* Exact value of nearest symbol.  */
} Dl_info;

这里已经讲的很清楚了,我们只需要直接访问info.dli_sname就可以获得改地址对应的函数名了。甚至还可以通过info.dli_fname获得该内存地址对应的so文件名。

其实这个头文件的宏和库函数帮我们做了很多事情,只需要调用就可以了,但我们需要做的事,是如何调用以及为什么要这么调用。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是在Android9的system/core/init/init.cpp文件HandleControlMessage函数中打印函数调用堆栈信息的实现代码: ```c++ void HandleControlMessage(const std::vector<Subcontext>& subcontexts, const std::string& msg) { std::string cmd = android::base::Trim(msg); if (cmd.empty()) { return; } ALOGV("control message '%s'", cmd.c_str()); // 打印函数调用堆栈信息 android::base::ScopedLogSeverity severity(android::base::DEBUG); android::base::CallStack stack; ALOGD("Function call stack:\n%s", stack.ToString().c_str()); if (cmd == "restart") { // 重启系统 RestartProcess(); } else if (cmd == "reboot") { // 重启设备 DoReboot(); } else if (cmd == "shutdown") { // 关闭设备 DoShutdown(); } else if (StartsWith(cmd, "sigstop")) { // 发送SIGSTOP信号给指定进程 HandleSignalCommand(subcontexts, cmd, SIGSTOP); } else if (StartsWith(cmd, "sigcont")) { // 发送SIGCONT信号给指定进程 HandleSignalCommand(subcontexts, cmd, SIGCONT); } else if (StartsWith(cmd, "signal ")) { // 发送指定信号给指定进程 HandleSignalCommand(subcontexts, cmd.substr(strlen("signal ")), 0); } } ``` 其中,打印函数调用堆栈信息的代码为: ```c++ // 打印函数调用堆栈信息 android::base::ScopedLogSeverity severity(android::base::DEBUG); android::base::CallStack stack; ALOGD("Function call stack:\n%s", stack.ToString().c_str()); ``` 这段代码使用了Android库中的android::base::CallStack类,用于获取当前函数调用堆栈信息,并使用Android库中的android::base::Log类打印调用堆栈信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值