如何读代码

来源:互联网 发布:股权投资成功率数据 编辑:程序博客网 时间:2024/05/16 07:55
复习得郁闷了, 休息一下. 认真读过的大 project 代码不多, 主要是 argo 的 telnet/web 源代码, netbsd & freebsd kernel 源码部分, ACM FTP Search Engine 源码 (前面有分析报告) 等, 随手抓来看看抄抄的就多了. 一点体会


一. 搞清楚分析源码的目的

不 同的目的有不同的分析源码的方法, 要做某个 project 的 maintainer, 不但要把整个 project 的文档代码读透, 还要知道它的历史源流等等. 要理解 linux 的核心算法, 当然直奔 kernel, mm 目录而去, 但是要写 linux 的设备驱动, 读法可能又不同了. 所以首先要搞清楚自己想要在源代码里得到什么, 然后才钻进去, 像无头苍蝇一样在代码里面乱转是不会有什么收获的.


二. 好的工具

这里主要讲 *nix 下的了.

ctags/etags, 生成一个交叉引用的 tags 文件, 以便在源代码各处跳转. 比如, 生成 tags 后, 在 vim 下对着某个函数调用 ctrl+] 就能跳到该函数的实现, ctrl+t 就能跳回来, 非常方便.

需要更复杂的功能, 分析更大型的代码的时候. 就要用到 cscope, 它不但能做到 tags 一样的功能, 还能对某个函数 or struct 列出代码中调用他们的地方, 以及其他更复杂的功能. vim 和 emacs 都能很好地跟 cscope 集成.

gdb, 标准的调试工具了. 调试功能很强, 调试一些网络服务程序的时候, attach 一个进程的功能很好用.

strace, 可以跟踪系统调用. 通过系统调用, 可以知道系统的运行情况.

还有各种 unix 标准工具啦, 如 grep 之类.


三. 一些分析代码的基本功

首 先第一点, 就是获得代码外每一个你可以获得的文档, 并把他们发挥到极致. 这些文档包括官方的开发者文档, 邮件列表上的讨论 (需要的时候可以 google 出来), 还有更重要的, 代码内的注释. 好的文档一句话就能解决看 2000 行代码都没看懂的问题.

然后, 第一步应该是: 让程序跑起来. 那种光对着代码发呆的静态分析方法效率是很低的, 首先应该是参照文档, 编译程序并让系统正常运行. 在这之中也可以了解到一些系统的基本结构和工作方式, 比方说 ACM FTP Search Engine, 就有好几个分立的程序, 他们都会自动定时运行(所以还要看 crontab 表), 读某些其他程序生成的数据, 产生某些数据给其他程序读. 如果不知道这些程序是什么时候, 如何运行的, 也不知道他们有什么效果, 就完全谈不上分析代码了.

接着就是找对入口, 这当然不完全是指程序的 main() 函数, 而是指它在 main() 初始化, 处理参数等等奇怪的工作之后所进入的真正的处理工作. 比如说对 httpd 等网络服务程序来说, 怎么变 daemon 绑端口 fork 等都是差不多的, 没有分析价值, 它的入口应该是监听进程 fork 出子进程之后开始的真正的服务处理.

入口处展现的就是真正的程序处理代码了, 在这里, 我们可以大致浏览一下, 确定这个程序的组成风格 --- 风格也由应用决定, 像 BBS 这种菜单式, 与用户大量交互的系统, 就应该会使用一个大的类似 {"命令名", 处理例程} 的列表, 由这里转向各个功能项的实现, 而一个纯算法程序的结构显然会与这个不同. 通过这些能够大概地知道下一步我们应该去哪里找到我们想要的东西, 不会乱打乱撞 --- 听起来有点像一个有经验的盗墓贼进入一个大墓的做法, 嗯.

然 后, 无论是什么类型的程序, 这时候我们都应该看数据结构了. 数据结构是一个程序的灵魂, 特别是已经确定分析某个模块的时候, 就应该看它的数据结构, 看他们是怎么组织起来 (链表? 还是其他方法?), 看谁从属于谁. 好的程序数据结构声明附近都有大量的注释, 说明这是一个什么咚咚, 是怎么用的, 看完这些, 对整个模块的运行机理就算不看代码也有大概的感觉了.

至此之后, 就是读代码了. 不同的程序或模块有不同的读法. 比方说在 NetBSD 代码分析中, 对 VFS (虚拟文件系统) 的阅读最好是从最顶层的系统调用响应函数 (如 sys_read(), sys_write()) 开始, 一步步深入到底层, 看一个读/写文件的系统调用是怎么完成的; 而读内存管理系统的时候, 由于它有复杂的数据结构, 就应该由低至上, 从最小的数据结构单位 (vm_page) 开始, 看用什么办法操纵这些数据结构; 若要读调度算法, 就应该用事件触发的方法, 先搞清楚什么时候会引发调度, 分哪些情况 (这好像是一道 OS 考试题), 出现这些情况时系统处于什么样的状况, 知道这些背景和触发事件, 再去读调度代码就会明白很多.


四. 一些技巧.

这些技巧能够帮你迅速地定位想要的咚咚的位置, 或者帮你迅速分析它的运行情况.

利用系统输出.
系 统输出包括标准/错误输出输出中的结果, log 文件等. 可以知道, 一些不寻常的系统输出 ("没有那个文件或目录" 的那些就应该去看 strace 的结果了) 肯定会在代码里出现, 虽然会不那么直接 (比如与一个常量关联). 总之, 遇到一个奇怪的输出, 用多几次 grep 就能大概找到输出这个信息的代码位置, 阅读它的上下文, 就能确定出现这样的信息的原因. 有时系统的输入是难以猜测的, 只有 log 文件能清楚地描述系统的运行状况, 从而推导系统的输入. 在进行 argo 的维护时, 往往唯一能够让人相信的就是系统的 log.

我 们还可以利用 strace 的结果, 特别是那些莫名其妙挂掉的程序, 他们往往挂在系统调用上 (因自身逻辑而挂的通常会有自己的提示). 所以第一件要做的事并不是在代码里大海捞针, 而是读 strace 的结果, 看它到底做了哪些事, 打开了什么文件, 最后在哪里挂掉. 即使没仔细读过代码, 也能建立一些基本的印象, 这时候再来读代码, 目的性就明确很多了, 你可以知道哪些东西才能产生 strace 结果中出现的系统调用, 这样就能迅速定位有问题的地方了. 我移植过 zebra 到一块 arm 的板子上, 结果出现了一些 PC 机上从未出现过的问题. 我在只是大概阅读过其代码的情况下, 没有用复杂的远程 gdb 调试方法, 仅仅用 strace 就定位了问题并解决了.

利用调试语句.
这 是一个非常传统的技术, 在分析源代码的时候也特别有用. 特别是在程序逻辑非常复杂, 程序输入难以确定等情况下, 只要知道少数几个关键的点 (比方说若前面的错误检测都正常, 就应该能到这一点) 加上一些打印语句, 我们就能迅速确定代码的运行路径, 从而理解其运行机理. 比方说分析 libnids 代码时, 我发现一个奇怪的无法跟踪 TCP 三次握手的问题 (其实根本就是自己的粗心), 使用了加入关键点调试语句的方法, 很快就找到了问题所在.

以上几种都是在动态的程序运行中了解程序运行机理的方法. 不但适用于代码分析, 也适用于大型项目开发中的 debug 等. 总之, 我的观点是, 不到最后时刻, 不使用 gdb 等源代码级调试器. 细心, 聪明的观察, 就能发现很多东西.

具体化策略.
好 的程序往往使用了一些通用而抽象的例程. 他们能让程序变得优美, 却往往由于过于抽象而增加了分析的难度. 这时候可以采用将一个具体的对通用例程的调用的参数代入分析的方法, 就很容易理解程序的真实运行方式了. 例如 ARGO telnet 端代码使用了 i_read() 作为版面文章列表, 信件列表, 精华区文章列表的通用列表操作例程, 光读 i_read() 是很难知道一些功能的工作方式的, 而把如阅读文章时的参数代入, 就可以知道一个操作的真实执行路径, 理解就简单很多. 有如 ARGO http 端使用了通用的 script.c 生成页面, 处理模块向它传入一些值, 作一些简单的控制, 再结合一个 pattern 文件, 就能生成具体页面. 只要找一个具体的功能实现, 结合相关的 pattern 文件, 具体地看一个页面是怎么生成的, 就会对它有很好的理解.

比较相似的代码.
比 较相似的代码能找到他们的相同和不同之处, 了解他们在策略上的区别乃至采用某策略的原因. 比方说freebsd 的传统 VM (虚拟内存管理) 和 netbsd 的 UVM对比来读, 就能发现对相似的数据结构的不同的处理策略, 再来阅读那篇描述 UVM 的论文, 就能对 netbsd 的 UVM 有个更深刻的了解. 对各个 telnet-based BBS 版本的对比分析, 再结合它们的源流关系, 也能发现很多东西.


五. 好习惯

最 后, 分析源代码的一个好习惯就是做笔记. 我在源代码分析的时候一般使用纸做笔记. 因为这样可以乱涂乱画, 可以用各种自己喜欢的形式去记录关键的运行原理. 通常我会一个功能模块用一张 A4 纸记录. (在这里插播一句, 请珍惜用纸, 我们常常会单面打印很多资料, 用完后它们的反面就是最好的草稿纸, 用完后的草稿纸也不要乱扔, 小部分可以拿来垫饭盒, 剩下的大部分还是可以回收的)

代码分析完之后, 应该将纸面的笔记整理一次. 这个整理过程也是再一次的代码阅读分析过程. 纸面笔记的整理就可以是电子版的并和大家共享的. 比如我的 NetBSD 核心源代码分析. 阅读代码是很好的学习方法, 也希望各位牛人能够共享自己的源代码分析笔记和分析心得~ 
原创粉丝点击