没有调试工具的调试技术

6
我发现自己处于一个困难的情境中,需要在几乎没有调试工具的情况下调试Qt应用程序:当应用程序一遍又一遍地运行同一操作时,似乎会越来越多地使用CPU;经过多个小时后,CPU完全饱和。
该应用程序在ARM Linux嵌入式设备上运行,其中gdb似乎无法工作,可能是由于所提供的工具链存在难以发现的问题。strace似乎只报告计时器活动(这是OpenGL应用程序,所以这是可以预料的)。ltrace不可用,并且编译它会导致一个困难的任务,可能是无用的。我没有编写该应用程序,但源代码可用。
除了跟踪应用程序执行的所有方法调用外,我还能做些什么来发现应用程序在消耗如此多资源时正在忙碌做什么?是否有其他技术可以用来猜测问题或集中注意力的位置?
编辑:这是gdb的一个问题之一:在ARM上,gdb只报告回溯中的问号。即使编写一个模拟段错误的十行应用程序也会出现这种情况。

1
您可以通过外部端口,例如串行端口或网络输出文本吗?然后您可以添加记录。或者只需将日志写入文件即可。 - Some programmer dude
是的,我可以在任何地方放置日志。但请考虑这是一个大型代码,有数千行代码,还使用了一些外部库,可能会隐藏错误。是否有自动放置日志的方法? - Luca Carlon
你提到源代码是可用的,我强烈建议在一个受支持的平台上重新编译(任何*nix系统都可以),看看是否会出现这个错误。很有可能这个错误是应用程序相关的,而不是平台相关的。作为额外的好处,你将获得调试符号。 - Matthieu M.
很遗憾,该应用程序使用的库仅适用于该平台和硬件(硬件加速),且源代码不可用。测试这些库表明它们并未引起问题。如果我能够为另一个平台编译,我可以简单地使用x86和gdb。不幸的是,在这种情况下无法实现。无论如何,我可以使用符号编译应用程序。 - Luca Carlon
http://secretgeek.net/image/real-programmers-code-in-binary_ftfy.png - valdo
显示剩余2条评论
5个回答

4
您能在机器上启用核心转储吗?然后当它出现问题时,您可以向其发送SIGABRT并将核心转储复制到您的dev机器上,并使用带有源代码和未削减可执行文件的交叉调试器来检查它。
同时重要的是要汲取教训,下次不要使用支持如此糟糕的工具链。
如果有选择的话,您可以尝试具有gdbserver支持的其他工具链。我一直很喜欢CodeSourcery ARM Lite工具链。
编辑:您的情况下gdb有两种版本:
- 在开发主机上运行的跨gdb - 运行在目标上的本地gdb
gdbserver允许您在开发主机上运行交叉-gdb并连接到目标以远程调试正在其中运行的内容。因此,核心转储或gdbserver是使用交叉-gdb检查目标上的某些内容的两种方法,但仅使用gdbserver不能帮助您太多。
如果您的交叉编译器是像arm-none-linux-gnueabi-gcc这样的东西,请查看您的dev主机上是否有arm-none-linux-gnueabi-gdb可用。

这非常有趣!但我看到我需要使用gdb来读取转储文件。我不认为我在该平台上有可用的gdb,只有用于远程调试的gdbserver,而且似乎也无法正常工作。我能否尝试使用该工具进行调试? - Luca Carlon
这是某种非常不寻常的ARM设备吗?如果不是,您应该能够轻松地从源代码构建gdb。 - dbrank0
这是一个好建议,谢谢!这是一款嵌入式Linux设备,从头开始构建。 - Luca Carlon

3

您可以尝试在应用程序中添加一些调试代码。

选择一些信号,例如SIGINT。为此信号添加信号处理程序。在此处理程序中打印堆栈跟踪或至少指令指针值。然后启动应用程序并发送多次SIGINT以查看您的应用程序正在做什么。


是的,我已经尝试过了。但是像gdb一样,似乎我不能在回溯中得到函数的名称,这使得回溯没有用处。即使符号在我的二进制文件中,backtrace_symbols似乎也无法将指针与字符串匹配。 - Luca Carlon
你可以随时打印指令指针,然后在映射文件中找到它。 - Evgeny Kluev
我在一个示例代码上尝试了这个方法,使用rdynamic导出符号似乎是有效的!但问题是,在我分析的应用程序中,由于某种未知原因,我无法捕获SIGINT信号。 - Luca Carlon
1
也许你无法捕获它,因为它已经在应用程序的某个地方被捕获了...尝试使用其他信号。 - Evgeny Kluev

0

假设gcc有类似于MSVC文件和行宏的东西,可以扩展到当前文件和当前行,您可以制作自己的伪性能分析函数。将其放在头文件中:

void MyPseudoProfileLog(const char* file, int line, int* count);
#define MY_PSEUDO_PROFILE_LOG(file, line) { static int count = 0; MyPseudoProfileLog(file, line, &count); }

这段代码应该放在 cpp 文件中(如果你把它放在头文件中,每个包含该头文件的 cpp 文件都会有一个静态变量的副本):

void MyPseudoProfileLog(const char* file, int line, int* count)
{
    static FILE* f = NULL;
    const int max_count = 1000;
    if (++(*count) == max_count)
    {
        *count = 0;
        if (!f)
            f = fopen("/tmp/my_pseudo_profile_log.log");
        fprintf(f, "file=\"%s\", line=%d was passed %d times\n", file, line, max_count);
        fflush(f);
    }
}

然后你可以粘贴

MY_PSEUDO_PROFILE_LOG(__FILE__, __LINE__);

在您的代码中的各个位置添加以查看它们被调用的频率。请记住,这不是线程安全的,因此仅在主线程中使用。


放置日志行是我正在做的事情,问题是有数百个方法调用在各种库中。可能需要几周时间才能找到那些加载CPU的方法。 - Luca Carlon

0
尝试记录不同函数的执行时间。首先记录最有可能的候选函数的执行时间,如果已经排除了它们,请继续记录程序中其他不太可能的函数的执行时间。
记录消息的最简单方式是使用std::cout(或printf)并将输出重定向到文件中,以便稍后查看日志。

0

你可以尝试运行Zoom分析器的ARM版本,这将告诉你代码花费大部分时间的位置 - 你可以免费下载30天的评估许可证。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接