大家好欢迎光临
在这个讲座中, 我想谈谈调用堆栈, 它到底是什么, 以及如何阅读它以进一步帮助调试程序｡
每当您的应用因致命错误而崩溃时, 都会生成堆栈跟踪（本质上就是调用堆栈）｡
之所以使用术语堆栈, 是因为它指的是编程中常见的数据结构｡
堆栈是一种被称为LIFO的数据结构, 后进先出, 这就是数据的存储方式｡
添加的最后一个数据元素是您检索回来的第一个数据元素｡
你可以想象一下, 如果你在吃一叠煎饼, 第一个煎饼在你加进去的最下面.
最后一个煎饼在上面｡
当你去吃它们的时候, 你吃最后加进去的｡
因此, 当程序崩溃时, 堆栈跟踪是非常有用的, 因为它包含帮助您调试它的信息｡
因此, 堆栈跟踪显示了导致错误的函数调用的列表｡
在程序中, 当你调用main函数时, 这就是入口点｡
主函数将调用其他函数｡
这些函数将调用其他函数, 所有这些被实现或被调用的函数将被放在堆栈上｡
所以你可以做的是当一个程序崩溃时, 调用堆栈会告诉你, 最后一个被调用的函数,
以及导致错误的特定代码行｡
所以Callstack包含了很多有用的信息｡
它将包括导致异常或致命错误发生的代码的文件名和行号｡
堆栈的顶部包含导致错误的最后一个调用, 并且可能存在嵌套调用｡
堆栈底部包含启动调用链以调用错误的第一个调用｡
所以如果你在C中, 通常会在main例程中, 你调用main, main调用另一个函数,
它调用另一个函数, 等等｡
最后一行是详细的｡
当应用程序崩溃时, 它非常非常有帮助｡
所以当一个C程序崩溃时, 它可能会产生一个分段错误, 而这并不能告诉你很多信息｡
只意味着发生了不好的事｡
这也将生成一个核心文件｡
您可以分析包含调用堆栈的核心文件, 这将告诉您导致崩溃问题的确切代码行｡
因此, 您需要在应用程序中找到导致崩溃的调用｡
调用堆栈可能是实现此目的的最佳方法｡
而且您不必总是查看核心文件｡
您还可以使用带有调试器的GDB来分析调用堆栈｡
我想给你们看看这是什么样的｡
程序员还可以随时转储堆栈跟踪｡
因此, 如果您确实捕获到错误, 或者您意识到可能发生致命错误, 则可以转储调用堆栈以查看发生了什么｡
如果您实际上不知道错误将在何时发生, 您仍然可以转储调用堆栈以查看导致特定调用的当前跟踪｡
当使用GDB时, 调用堆栈看起来像这样｡
当你在GDB中运行调试器的时候, 你可以通过菜单访问调用栈, 我将向你展示如何做到这一点,
但这就是它的样子, 你在这里看到的是它有一个数字, 一个地址, 然后是函数｡
这个调用堆栈有很多不同的调用, 所以它一定做了很多不同的嵌套和很多不同的函数调用.
但是在堆栈的最底部第22行, 它说GTK main｡
这意味着｡
这意味着它可能执行了main函数｡
然后在第21行, 它看起来像是执行了｡
也许一个语句说主循环运行｡
然后它看起来像是调用了一个名为G main context iterate的函数｡
在这个函数中, 每当有另一个调用IAG主上下文调度函数时,
就会有一些｡
这里有一个iterate函数, 然后是一个dispatch函数, 第18行中的问号表示没有调试信息｡
它不知道那里发生了什么｡
这就是为什么当你构建你的程序时, 你可以为发布而构建它, 或者你用调试信息来构建它｡
当您使用调试信息构建它时, 它在编译时包含额外的信息, 以便在运行时可以查看调用堆栈｡
当你释放它的时候, 为释放而构建一些东西｡
可执行文件要小得多, 可能不包含此信息｡
它可能包含更多的问号｡
但这也会告诉你, 如果你调用了任何库, 任何额外的程序, 它会调用这些, 并会告诉你从那里调用堆栈｡
但这再次表明, 在最顶部的调用的不同嵌套事件是重要的,
因为这将表明导致崩溃的原因｡
所以看起来在这个特定的调用堆栈中, 内部刷新导致了一个问题｡
你可以看到从第22行到第0行, 有很多不同的代码调用｡
但最终程序崩溃在第0行调用堆栈中｡
您还可以在程序运行时查看调用堆栈, 以便查看信息, 从而可以跟踪它｡
所以如果你在一个函数中放置一个断点, 你调试, 你运行调试器｡
调试器将在该断点处停止｡
然后, 您可以调出调用堆栈, 它会告诉您如何到达那行代码｡
此this函数在此函数之前调用｡
同样, 它通常会从主函数开始｡
你也可以切换到不同的框架, 如果你熟悉堆栈和概念, 以及它在内存中的方式,
你可以查看不同的框架, 但这是一个非常有用的工具, 主要是在C中查找崩溃,
而且它非常容易阅读｡
如果你只看最上面的部分, 对调用堆栈了解不多, 你可以看看最上面的部分,
然后说, 哦, 那是一个源代码, 那是导致崩溃的一行代码｡
这是我在网上找到的一个很好的例子｡
Excma.
埃杜
它阐释了函数调用堆栈示例｡
所以如果我们看一个代码示例, 我们会看到我们只有一些简单的代码｡
计算并不重要｡
我们基本上只是调用一些函数｡
所以我们有这个mogrify函数和这个truly half函数, main函数将调用一个mogrify函数,
然后它将调用truly half, 然后它将调用mogrify, 所以我们可以说明调用堆栈以及这些调用的情况,
基本上只是在main中设置一个简单的断点, 然后在我们进行调用时查看调用堆栈｡
所以, 我们将更熟悉断点和类似的信息｡
但我只想快速演示一下调用堆栈, 我们稍后会给出一个更详细的调用堆栈示例｡
所以我在这里设置了一个断点｡
我可以继续编译这个代码, 然后启动调试器, 我们就能到达断点了｡
现在你会注意到, 你可以看到所有的调试器菜单在这里, 在底部的右手边,
你会看到调用堆栈｡
让我们把这个弄大一点｡
但是当它停在第14行时, 这里只有一些赋值语句, 你会看到调用堆栈显示main｡
如果我们跳过它, 转到下一行, 我们实际上可以看到它在调用Mogrify之前转到了第15行,
对吗？
所以你可以看到调用堆栈仍然是Main的｡
如果我们进入, 您现在将看到调用堆栈显示Mogrify｡
所以我们实际上是在这个函数里面｡
我们可以跨过去｡
所以它执行第一行, 然后返回temp｡
它仍然停留在那个函数中, 然后它返回在第16行打印F的地方, 你可以看到调用堆栈是如何变化的｡
然后我们转到第18行, 在main中调用stack stills, 然后我们进入真正的半函数｡
现在你可以看到调用堆栈的变化, 它显示顶部的一半函数始终是上次调用的函数｡
再一次, 我们走过去, 你会看到我们仍然在真正的一半｡
我们回到Main, 在第19行打印一些东西, 然后转到第21行｡
我们再次跳转到modify函数, 你可以在调用堆栈上看到它, 我们跳转, 我们跳转,
最终我们在第23行打印出最终结果, 并从程序返回｡
但是调用堆栈再次向我们显示了从一个函数到下一个函数调用的函数顺序｡
所以如果你有嵌套的函数, 它可能会越来越复杂｡
调用堆栈的优点是, 如果你有一行代码崩溃了, 比如说在第四行, 你会有一个调用堆栈,
它在最上面包含了这行代码崩溃的地方｡
所以你可以通过调用堆栈看到, 哪一行代码是最后一个被调用的,
这将告诉你哪一行代码被崩溃了｡
但是当你的程序崩溃时你会想要使用这个工具｡
谢谢大家｡