我正在尝试弄清楚如何在 Mac OS X 上的 C++ 应用程序中存储然后打印当前堆栈。主要问题似乎是在主可执行文件内给定地址时让 dladdr 返回正确的符号。我怀疑问题实际上是编译选项,但我不确定。
我已经尝试了 Darwin/Leopard 的回溯代码,但它调用了 dladdr,并且与我自己的调用 dladdr 的代码有相同的问题。
原帖: 目前我正在使用以下代码捕获堆栈:
int BackTrace(Addr *buffer, int max_frames)
{
void **frame = (void **)__builtin_frame_address(0);
void **bp = ( void **)(*frame);
void *ip = frame[1];
int i;
for ( i = 0; bp && ip && i < max_frames; i++ )
{
*(buffer++) = ip;
ip = bp[1];
bp = (void**)(bp[0]);
}
return i;
}
这似乎工作正常。然后使用 dladdr 打印我正在查看的堆栈,如下所示:
Dl_info dli;
if (dladdr(Ip, &dli))
{
ptrdiff_t offset;
int c = 0;
if (dli.dli_fname && dli.dli_fbase)
{
offset = (ptrdiff_t)Ip - (ptrdiff_t)dli.dli_fbase;
c = snprintf(buf, buflen, "%s+0x%x", dli.dli_fname, offset );
}
if (dli.dli_sname && dli.dli_saddr)
{
offset = (ptrdiff_t)Ip - (ptrdiff_t)dli.dli_saddr;
c += snprintf(buf+c, buflen-c, "(%s+0x%x)", dli.dli_sname, offset );
}
if (c > 0)
snprintf(buf+c, buflen-c, " [%p]", Ip);
几乎可以工作,一些示例输出:
/Users/matthew/Library/Frameworks/Lgi.framework/Versions/A/Lgi+0x2473d(LgiStackTrace+0x5d) [0x102c73d]
/Users/matthew/Code/Lgi/LgiRes/build/Debug/LgiRes.app/Contents/MacOS/LgiRes+0x2a006(tart+0x28e72) [0x2b006]
/Users/matthew/Code/Lgi/LgiRes/build/Debug/LgiRes.app/Contents/MacOS/LgiRes+0x2f438(tart+0x2e2a4) [0x30438]
/Users/matthew/Code/Lgi/LgiRes/build/Debug/LgiRes.app/Contents/MacOS/LgiRes+0x35e9c(tart+0x34d08) [0x36e9c]
/Users/matthew/Code/Lgi/LgiRes/build/Debug/LgiRes.app/Contents/MacOS/LgiRes+0x1296(tart+0x102) [0x2296]
/Users/matthew/Code/Lgi/LgiRes/build/Debug/LgiRes.app/Contents/MacOS/LgiRes+0x11bd(tart+0x29) [0x21bd]
它为共享对象获取正确的方法名称,但不为主应用程序获取正确的方法名称。这些只是映射到“tart”(或“start”减去第一个字符)。
理想情况下,我想要行号以及此时的方法名称。但我会为初学者选择正确的函数/方法名称。也许在那之后拍摄行号,在 Linux 上我听说你必须为拥有自己的指令集的私有 ELF 块编写自己的解析器。听起来很可怕。
无论如何,任何人都可以整理一下这段代码,以便它获得正确的方法名称吗?
在 macOS 上,您可以使用
atos
从调试符号中查找源文件和行号。 atos
是一个命令行实用程序,因此您需要启动一个单独的进程。像这样的事情会起作用:
#include <iostream>
#include <sstream>
#include <dlfcn.h>
#include <execinfo.h>
#include <stdio.h>
void print_backtrace() {
constexpr int kBacktraceDepth = 15;
void* backtrace_addrs[kBacktraceDepth];
int trace_size = backtrace(backtrace_addrs, kBacktraceDepth);
for (int i = 0; i < trace_size; ++i) {
Dl_info info;
dladdr(backtrace_addrs[i], &info);
std::stringstream cmd(std::ios_base::out);
cmd << "atos -o " << info.dli_fname << " -l " << std::hex
<< reinterpret_cast<uint64_t>(info.dli_fbase) << ' '
<< reinterpret_cast<uint64_t>(backtrace_addrs[i]);
FILE* atos = popen(cmd.str().c_str(), "r");
constexpr int kBufferSize = 200;
char buffer[kBufferSize];
fgets(buffer, kBufferSize, atos);
pclose(atos);
std::cout << buffer;
}
std::cout << std::flush;
}