有人说程序的美有两种:

  • 结构
  • 效率

好的程序能最大程度传达程序员的思想。阅读一段优秀的代码,如同读诗,作者心中的蓝图仿佛就铺在眼前。这就是结构之美。程序最终是拿来运行的。如何让程序占用的资源最少,做更多的事情,是热爱效率之美的程序员的使命。

吹完牛逼之后,开始说正事。要欣赏程序之美,首先得会理解程序。理解程序相应地也有两种,静态和动态。理解静态的程序就是读代码,读代码,读代码,知道这个程序应该在干什么。理解动态的程序就是运行它,运行它,运行它,观察这个程序真正在干什么。现在并没有一个工具能提供所有理解程序的功能,甚至有的单一需求,至今都没有一个过得去的工具(比如代码阅读和搜索)。这份小抄,收录了一部分好用的工具,然后扯扯这个世界怎样会更好。

先说说程序的动态行为。

在深入了解一个程序之前,可以先看看它是怎样和操作系统交互的,大致摸清楚它的作风,比如在读哪些文件,开了多少进程,申请了多少内存(申请的频率),是不是在下载或者上传。这些就是操作系统和用户程序的接口。如果一个用户程序不触碰这些边界的接口的话,其实它基本没啥用,除了浪费CPU资源。这些接口,就是系统调用。也就是所谓的ABI(Application Binary Interface)。

从上图我们可以看到,用户程序和机器的边界是由系统调用和用户级别的CPU指令构成的。理论上,编译好的一个二进制文件,假如放到另一个系统调用和用户指令兼容的平台上,它是可以直接运行的!API是源代码级别的兼容(需要重新编译),ABI是二进制级别。

strace 是监控程序系统调用的利器。它可以截获被监控进程的所有系统调用,然后打印出来。每一个对程序动态分析感兴趣的朋友都应该学会用strace。通过观察对fork,exec,clone的调用,可以知道目标程序在启动哪些进程,以及用的什么参数。也可以打印open,stat,看看它在碰哪些文件。跑完strace,我们已经可以对程序有一个大致的了解。

接着我们就要突破ABI,深入到用户程序了。我们还是不想直接看代码,最好能先看一下程序的高层架构。当然我们是没有文档的。Profiler可以帮助我们生成一个近似的架构图。Profiler更多是用来发现程序的瓶颈做优化的。不过那些用时最长的函数大多数时候也正是最重要的函数。所以我们可以用Profiler生成的程序调用图(call graph)来学习目标函数的架构。有名的Profiler包括:gperftools, linux kernel自带的perf。下图就是网上找的gpertools生成的程序调用图:

最近比较流行的还有火焰图(Flame graph)。基本上跟Profiler提供一样的信息,但是用不一样的形式展现出来。Netflix的Brendan Gregg 维护了一个非常详尽的页面。Chrome也内置了火焰图,用来做Javascript调优。

当然,终究还是要看代码的。这个时候就该Debugger出场了。通过之前的准备,我们基本知道了哪些函数值得关注,那就设置好断点,单步调试吧。比较老牌的工具就是GDB啦。geohot在Google参与了一个项目,叫qira,尝试给Debugger加上更好的回溯的功能。假如一不小心点太快,忘了看某个变量的值就continue了,你还可以undo, 回去看那个值哟。

以上是通用的程序动态分析的方法。对于应用开发者来说应该可以满足差不多的需求了。不过假如要做极致的性能分析或者逆向工程的话,那就有更多数不清的定制的工具啦。