会员注册
会员登陆
取回密码
欢迎您回来
实易文章 || 发表文章 || 管理文章

如何阅读源代码--工具篇

分类:: Unix/Linux / 发表时间 :: 2005-07-13 19:14:01
作者 :: 16hot | 人气 ::  |  评论数目 (0) | 发送 | 来源 ::


By Wing


  在上一篇文章((http://www.linuxaid.com.cn/developer/showdev.jsp?i=469))中, 我讲述了一些如何阅读GNU,Open Source源代码的原则,经验和技巧。上次曾经提到,有一些工具能够帮助我们更加快速,准确,有效的阅读源代码,掌握其结构。在这一篇文章中我将具体介绍几个工具,帮助我们阅读,分析源代码。
  首先要介绍的工具叫做ctags. 这个工具在Unix下是一个常用的分析静态程序的工具,相信大家都用过。如果你对这个工具不熟悉,也不要紧。相信很多人都用过Windows系统下的开发工具,很多图形化界面的开发工具,诸如Visual C++, C++ Builder的IDE开发环境都提供了一种功能,就是在编辑器中可以准确的定位一个函数或者一个类的申明,或者实现,或者列出所有的在程序中调用该函数的地方。这种功能给程序员和阅读程序的人提供了很大的方便,不用在庞大的程序文本中到处搜寻一个字符串,只要轻轻的点一下鼠标就能准确的找到要找的东西。其实,Unix/Linux也有这样的工具,而且,继承了Unix程序小巧,精炼,功能强大,容易配合其它程序使用的特点,比Visual C++, C++ Builder的IDE环境更加方便实用,而且,还没有它们那么庞大。ctags结合vi,就是这么一个工具。
  先来看看在Unix下,ctags的功能。我用的是HP UX。有这么一个小程序,是用来解释ctags的用法的。

$cat test.c
int i;
main()
{
        f();
        g();
        f();
}
f()
{
        i = h();
}
用命令产生一个tags文件。
$ctags test.c
缺省情况下,ctags生成的文件叫做tags. 来看一看它的内容。
$cat tags
Mtest test.c /^main()$/
f test.c /^f()$/

  这个文件有三栏:
1、tag的名称,可以在稍后通过引用它来定位光标。
2、文件的名称。这个文件是tag所在的文件的名称。
3、搜索的方法。在我的系统上,ctags是用正则表达式的方式来搜索定位的。
  如何使用这个tags文件呢?
  我们以vi为例子来展示它的功能。vi是一个强大的文本编辑器,它的简洁的操作和强大的功能使它成为了Unix平台上的最流行的编辑器之一。ctags支持vi的功能。使用方法很简单。如果我们要定位main()函数,只要$vi -t Mtest
  vi自动的打开了test.c文件,然后把光标定位到main()函数的开头处。在vi中,如果要使用其它的tag来定位别的函数,也只要使用:ta tag命令就可以了。比如在本例中,我们要定位f函数。那么,只要用 :ta f 光标就自动定位到f()函数的入口处。很简单吧?
  在Linux下,也可以找到ctags. 一般的Linux发行版都包括了这个工具。如果你的系统上没有ctags, 也可以到http://ctags.sourceforge.net/下载它。它的作者是Darren Hiebert , , 主页地址是http://darren.hiebert.com/
  Linux下的ctags比Unix下的ctags功能更加强大,而且更加可以定制。Unix下的ctags(我的系统上是这样)只支持三种语言:C, Pascal, Fortran, 而Linux下的ctags支持的语言有:Assembler, AWK, BETA, Bourne shell, C/C++, COBOL, Eiffel, Fortran, Java, Lisp, Perl, Python, Scheme, Tcl. 而且Linux下的ctags支持的编辑器也很多,有:vi 和它的派生Vim, Vile, Lemmy, CRiSP, emacs, FTE (Folding Text Editor), NEdit (Nirvana Edit), TSE (The SemWare Editor), X2, Zeus.
  好,现在就让我们来看一看ctags的强大功能以及它在阅读源代码的时候的用处吧。我将仍然以webalizer为例子,因为这个程序是在上一篇文章中使用过的,为了一贯性,也为了读者能够通过本文的阅读从而更加的了解这个程序和学到更多的经验技巧。考虑一下,当我们拿到一个C程序,我们如何能够快速的掌握它的结构呢?C程序是由一系列的函数,变量,宏,预编译指令组成的。而我们最为关心的是函数,和全局变量。那好,用ctags可以很方便的得到我们感兴趣的东西。
  以webalizer的主程序webalizer.c为例子,我们可以用:

[webalizer-2.01-09]$ ctags -x --c-types=f webalizer.c

  得到的结果如下

clear_month function 1614 webalizer.c void clear_month()
ctry_idx function 1738 webalizer.c u_long ctry_idx(char *str)
cur_time function 1695 webalizer.c char *cur_time()
from_hex function 1751 webalizer.c char from_hex(char c)
/* convert hex to dec */
get_config function 1358 webalizer.c void get_config(char *fname)
get_domain function 1852 webalizer.c char *get_domain(char *str)
init_counters function 1627 webalizer.c void init_counters()
ispage function 1714 webalizer.c int ispage(char *str)
isurlchar function 1728 webalizer.c int isurlchar(char ch)
jdate function 1919 webalizer.c u_long jdate( int day, int month, int year )
main             
function 231 webalizer.c int main(int argc, char *argv[])
our_gzgets function 1873 webalizer.c
char *our_gzgets(gzFile fp, char *buf, int size)
print_opts function 1657 webalizer.c void print_opts(char *pname)
print_version function 1670 webalizer.c void print_version()
save_opt function 1600 webalizer.c static char *save_opt(char *str)
srch_string function 1794 webalizer.c void srch_string(char *ptr)
unescape function 1763 webalizer.c char *unescape(char *str)

  在输出中可以看到所有的在webalizer.c中的函数,出现的行号,和它们的申明。方便吧?(当然,仅仅这样是不能读通源代码的,还是需要上一篇文章中的原则和技巧才能实际的读懂源代码。这个只是一个辅助工具,能够让我们更加方便快速准确而已)。要看函数的原型也很简单:

[webalizer-2.01-09]$ ctags -x --c-types=p webalizer.c

  得到的结果如下

clear_month prototype 87 webalizer.c void clear_month();
/* clear monthly stuff */
from_hex       
prototype 89 webalizer.c char from_hex(char);
/* convert hex to dec */
get_config prototype 93 webalizer.c void get_config(char *);
/* Read a config file */
get_domain prototype 96 webalizer.c char *get_domain(char *);
/* return domain name */
isurlchar prototype 92 webalizer.c int isurlchar(char);
/* valid URL char fnc. */
our_gzgets prototype 97 webalizer.c char *our_gzgets(gzFile, char *, int);
/* our gzgets */
print_opts prototype 90 webalizer.c void print_opts(char *);
/* print options */
print_version prototype 91 webalizer.c void print_version();
/* duhh... */
save_opt prototype 94 webalizer.c static char *save_opt(char *);
/* save conf option */
srch_string prototype 95 webalizer.c void srch_string(char *);
/* srch str analysis */
unescape prototype 88 webalizer.c char *unescape(char *);
/* unescape URL's */

  也可以看该程序中的变量:

[webalizer-2.01-09]$ ctags -x --c-types=v webalizer.c | more

  得到的结果如下

all_agents variable 157 webalizer.c int all_agents = 0;
/* List All User Agents */
all_refs variable 156 webalizer.c int all_refs = 0;
/* List All Referrers */
all_search variable 158 webalizer.c int all_search = 0;
/* List All Search Strings */
all_sites variable 154 webalizer.c int all_sites = 0;
/* List All sites (0=no) */
all_urls variable 155 webalizer.c int all_urls = 0;
/* List All URL's (0=no) */
all_users variable 159 webalizer.c int all_users = 0;
/* List All Usernames */
blank_str variable 138 webalizer.c char *blank_str = "";
/* blank string */
buffer variable 217 webalizer.c char buffer[BUFSIZE];
/* log file record buffer */
check_dup variable 179 webalizer.c int check_dup=0;
/* check for dup flag */
conf_fname variable 135 webalizer.c char *conf_fname = NULL;
/* nameof config file */
copyright variable 106 webalizer.c
char *copyright = "Copyright 1997-2001 by Bradford L. Barrett";
ctry_graph variable 117 webalizer.c int ctry_graph = 1;
/* country graph display */
cur_day variable 171 webalizer.c cur_day=0, cur_hour=0,
/* tracking variables */
cur_hour variable 171 webalizer.c cur_day=0, cur_hour=0,
/* tracking variables */
cur_min variable 172 webalizer.c cur_min=0, cur_sec=0;
cur_month variable 170 webalizer.c int cur_year=0, cur_month=0,
/* y

  由于内容太多,就不一一列在这里了。读者可以自己练习一下。
  用同一个命令格式,可以看到更多的内容,用--c-types来指定。它们是:
              c 类
              d 宏定义
              e 枚举
              f 函数定义
              g 枚举名称
              m 类,结构和联合
              n 名字空间
              p 函数原型和申明
              s 结构名称
              t typedef
              u 联合名称
              v 变量申明
              x 外部和引用变量申明
  也可以不指定--c-types,来列举所有的类型。
  也可以用这种命令格式来分析.h头文件,例如,列举在webalizer.h头文件中定义的所有的宏,

[webalizer-2.01-09]$ ctags -x --c-types=d webalizer.h | more
BUFSIZE macro 14 webalizer.h #define BUFSIZE 4096
/* Max buffer size for log record */
IDX_2C macro 5 webalizer.h #define IDX_2C(c1,c2)
(((c1-'a'+1)1) printf("%s ",msg_put_hist);
      for (i=0;icount;
  t2=(*(UNODEPTR *)cp2)->count;
  if (t1!=t2) return (t2string,
                  (*(UNODEPTR *)cp2)->string );
}

  这个函数是根据unode中的count的大小来判断两个元素的大小。为了方便,我们把unode的结构定义也列在下面。

struct unode { char *string; /* url hash table structure */
                int flag; /* Object type (REG, HIDE, GRP) */
              u_long count; /* requests counter */
              u_long files; /* files counter */
              u_long entry; /* entry page counter */
              u_long exit; /* exit page counter */
              double xfer; /* xfer size in bytes */
              struct unode *next; }; /* pointer to next node */
/*********************************************/
/* QS_SITE_CMPK - QSort cmp site by bytes */
/*********************************************/
int qs_site_cmpk(const void *cp1, const void *cp2)
{
  double t1, t2;
  t1=(*(HNODEPTR *)cp1)->xfer;
  t2=(*(HNODEPTR *)cp2)->xfer;
  if (t1!=t2) return (t2string,
                  (*(HNODEPTR *)cp2)->string );
}

  这个函数是根据hnode中的xfer的大小来判断两个元素的大小。

/*********************************************/
/* QS_URL_CMPH - QSort compare URL by hits */
/*********************************************/
int qs_url_cmph(const void *cp1, const void *cp2)
{
  u_long t1, t2;
  t1=(*(UNODEPTR *)cp1)->count;
  t2=(*(UNODEPTR *)cp2)->count;
  if (t1!=t2) return (t2string,
                  (*(UNODEPTR *)cp2)->string );
}

  这个函数是根据unode中的count的大小来判断两个元素的大小。

/*********************************************/
/* QS_URL_CMPK - QSort compare URL by bytes */
/*********************************************/
int qs_url_cmpk(const void *cp1, const void *cp2)
{
  double t1, t2;
  t1=(*(UNODEPTR *)cp1)->xfer;
  t2=(*(UNODEPTR *)cp2)->xfer;
  if (t1!=t2) return (t2string,
                  (*(UNODEPTR *)cp2)->string );
}

  这个函数是根据unode中的xfer的大小来判断两个元素的大小。

/*********************************************/
/* QS_URL_CMPN - QSort compare URL by entry */
/*********************************************/
int qs_url_cmpn(const void *cp1, const void *cp2)
{
  double t1, t2;
  t1=(*(UNODEPTR *)cp1)->entry;
  t2=(*(UNODEPTR *)cp2)->entry;
  if (t1!=t2) return (t2string,
                  (*(UNODEPTR *)cp2)->string );
}

  这个函数是根据unode中的entry的大小来判断两个元素的大小。

/*********************************************/
/* QS_URL_CMPX - QSort compare URL by exit */
/*********************************************/
int qs_url_cmpx(const void *cp1, const void *cp2)
{
  double t1, t2;
  t1=(*(UNODEPTR *)cp1)->exit;
  t2=(*(UNODEPTR *)cp2)->exit;
  if (t1!=t2) return (t2string,
                  (*(UNODEPTR *)cp2)->string );
}

  这个函数是根据unode中的exit的大小来判断两个元素的大小。

/*********************************************/
/* QS_REF_CMPH - QSort compare Refs by hits */
/*********************************************/
int qs_ref_cmph(const void *cp1, const void *cp2)
{
  u_long t1, t2;
  t1=(*(RNODEPTR *)cp1)->count;
  t2=(*(RNODEPTR *)cp2)->count;
  if (t1!=t2) return (t2string,
                  (*(RNODEPTR *)cp2)->string );
}

  这个函数是根据rnode中的count的大小来判断两个元素的大小。

/*********************************************/
/* QS_AGNT_CMPH - QSort cmp Agents by hits */
/*********************************************/
int qs_agnt_cmph(const void *cp1, const void *cp2)
{
  u_long t1, t2;
  t1=(*(ANODEPTR *)cp1)->count;
  t2=(*(ANODEPTR *)cp2)->count;
  if (t1!=t2) return (t2string,
                  (*(ANODEPTR *)cp2)->string );
}

  这个函数是根据anode中的count的大小来判断两个元素的大小。

/*********************************************/
/* QS_SRCH_CMPH - QSort cmp srch str by hits */
/*********************************************/
int qs_srch_cmph(const void *cp1, const void *cp2)
{
  u_long t1, t2;
  t1=(*(SNODEPTR *)cp1)->count;
  t2=(*(SNODEPTR *)cp2)->count;
  if (t1!=t2) return (t2string,
                  (*(SNODEPTR *)cp2)->string );
}

  这个函数是根据snode中的count的大小来判断两个元素的大小。

/*********************************************/
/* QS_IDENT_CMPH - QSort cmp ident by hits */
/*********************************************/
int qs_ident_cmph(const void *cp1, const void *cp2)
{
  u_long t1, t2;
  t1=(*(INODEPTR *)cp1)->count;
  t2=(*(INODEPTR *)cp2)->count;
  if (t1!=t2) return (t2string,
                  (*(INODEPTR *)cp2)->string );
}

  这个函数是根据inode中的count的大小来判断两个元素的大小。分析清楚了这些函数,下面的工作就容易得多了。因为对于每一个hash表,如unode, snode等等,都有一套这样的函数,load_XXX_array和相应的qsort及其比较大小的函数,下面的程序代码段只不过是针对每一种数据给出一套新的处理办法,本质上是一样的。比如,

  /* do hostname (sites) related stuff here, sorting appropriately... */
  if ( (a_ctr=load_site_array(NULL)) )
  {
    if ( (h_array=malloc(sizeof(HNODEPTR)*(a_ctr))) !=NULL )
    {
    a_ctr=load_site_array(h_array); /* load up our sort array */
    if (ntop_sites' 'dump_sites)
    {
      qsort(h_array,a_ctr,sizeof(HNODEPTR),qs_site_cmph);
      if (ntop_sites) top_sites_table(0); /* Top sites table (by hits) */
      if (dump_sites) dump_all_sites(); /* Dump sites tab file */
    }
    if (ntop_sitesK) /* Top Sites table (by kbytes) */
    {
      qsort(h_array,a_ctr,sizeof(HNODEPTR),qs_site_cmpk);
      top_sites_table(1);
    }
    free(h_array);
    }
    else if (verbose) fprintf(stderr,"%s [h_array] ",msg_nomem_ts); /* err */
  }
针对hnode排序并且输出。
  /* do referrer related stuff here, sorting appropriately... */
  if ( (a_ctr=load_ref_array(NULL)) )
  {
    if ( (r_array=malloc(sizeof(RNODEPTR)*(a_ctr))) != NULL)
    {
    a_ctr=load_ref_array(r_array);
    if (ntop_refs' 'dump_refs)
    {
      qsort(r_array,a_ctr,sizeof(RNODEPTR),qs_ref_cmph);
      if (ntop_refs) top_refs_table(); /* Top referrers table */
      if (dump_refs) dump_all_refs(); /* Dump referrers tab file */
    }
    free(r_array);
    }
    else if (verbose) fprintf(stderr,"%s [r_array] ",msg_nomem_tr); /* err */
  }

  针对rnode排序并且输出。
  其实,到了程序的结尾,我们能够很清楚的认识到这个程序的流程结构了,那就是,从日志文件中得到每一条日志记录,将其分别放入到各个hash表中,如hnode, unode等等,最后根据不同的标准来排序输出。当然,这中间我省略了一些错误处理,格式化的工作。到这里为止,我们已经完全结束了这个程序的阅读,以及如何采用工具去分析,辅助阅读源代码。当然,仅仅这些是不够的。找来一个有源代码程序,并且实际的去阅读来作为练习是最好的学习和实践的方法。
  责任编辑:ariesram(2002-01-21 09:10)
  来源:ariesram



[ 返回 ]


■ 相关文章
· 让firefox自动调用下载器 (2005-09-07)
· 浅谈linux优化及安全配置 (2005-09-07)
· python入门1 (2005-09-07)
· 以非超级用户身份安装 mod_perl (2005-09-07)
· FC3中的JAVA安装及配置 (2005-09-07)
· Perl与MandrakeLinux (2005-09-07)
· Apache服务器实现用户验证 (2005-09-07)
· 受限制环境安装Perl模块方法 (2005-09-07)
· Linux内核研究系列之可执行文件格式 (2005-07-13)
· 如何阅读源代码--工具篇 (2005-07-13)
· 如何阅读源代码 (2005-07-13)
· Linux内核编程风格 (2005-07-13)
· Linux网络代码导读v0.2 (2005-07-13)
· Linuxinodecache分析 (2005-07-13)
· 目录项缓存dcache (2005-07-13)
· Linux对I/O端口资源的管理 (2005-07-13)
· Linux对ISA总线DMA的实现 (2005-07-13)
· 基于i386的Linux实现特点剖析——基础的基础 (2005-07-13)
· 基于i386的Linux实现特点剖析——关于中断 (2005-07-13)
· 基于i386体系结构的Linux实现特点剖析——内存与进程 (2005-07-13)
· ELF可执行联接规范(英汉对照版) (2005-07-13)
· Linux内核0.11(0.95)详细注释 (2005-07-13)
· ar和nm命令的使用 (2005-07-13)
· JIDEv1.7 (2005-07-13)
· Rhide-1.4.7 (2005-07-13)
· KDevelop1.3 (2005-07-13)
· Xwpe1.5.26a (2005-07-13)
· XwpeFAQ (2005-07-13)
· C-Forge1.6-4 (2005-07-13)
· cvs客户端大全 (2005-07-13)

■ 发表评论
友情提示: 本站不允许匿名发表评论。如果您是会员,请先登陆;否则,请先注册
如下内容仅代表作者个人观点,本站概不负责!
评论标题:
评论内容:
  

■ 相关评论 更多评论...
http://www.isyi.com
Copyright © 2002-2005 实易数码. All rights Reserved 
版权声明:实易数码是本Blog托管服务提供商。实易数码不承担任何责任,请与Blog使用者联系解决。
粤ICP备05023051号