一个例子让我们用gdb调试FB2000

        
            使用gdb调试FB2000
 
    1.故事背景
      
    之前发现一个很奇怪的bug,就是有些站务下不了站,下站的时候就会又
 
转到好友菜单,一开始只是一两个站务id遇到,出现也是随机的,很难调试,
 
曾经试着看代码跟踪,没看出什么问题。最近这个问题终于引发了全站性的
 
“灾难”。发现某天早上忽然web登陆不了,但telnet使用正常只是偶尔会
 
很卡,根据之前经验,先查看了一下bbshome下的两个日志文件:
 
bbs目录下的trace和usies, 问题就在这里,trace文件已超过2G,32位的机
 
器读超过2G的大文件会出现问题,但又有一个疑问出现了:为什么在10几天
 
内这个日志文件为什么会超过2G呢? more了一把,发现有两个站务id在下不
 
了站的时候一次写了大量的日志,满满的全是:
 
SYSOP 03月26日19:31:28 brc_save_all
SYSOP 03月26日19:31:28 brc_save_all
SYSOP 03月26日19:31:28 brc_save_all
 
数量大得可怕,一次写1w左右?为什么会这样呢?看了一下打出这个日志的地方
 
发现是brc_saveall()函数打出来的。好,查一下调用brc_saveall()的地方,
 
只有main.c跟bbs.c调用了。按分析,下站时出现这个问题应该在main.c的
 
u_exit()函数里面。但怎么看都看不出问题,就开始怀疑是信号引起的,后来
 
打了很多信息,也发现不是这个问题,那就怪了为什么程序会不停地在这个函数
 
里转呢?会调用多次这个函数呢?看了很多次代码都不得其解,最后实在没办法了
 
只好出动超级武器--gdb。
 
    2.武器出动
 
    想要用gdb调试程序要先在编译里加上-g或者-gdb
 
    1) 修改$bbshome/bbssrc/src下的Makefile文件
    将原来的   CFLAGS   = -O2 -Wunused -I../include
    修改成这样 CFLAGS   = -ggdb -Wunused -I../include
     
    2)在$bbshome/bbssrc/src下编译程序
    make clean;make
 
    3)在$bbshome/bbssrc/src直接执行bbsd
  ./bbsd 端口   如: ./bbsd 2008
    不要跟你系统现在运行的端口一样,这个可以分得清哪个是你要
  抓的进程,等下就可以把它捉下来慢慢折磨它^_^
    
    4)用一个id登陆进去
    telnet ip 端口  如:telnet 127.0.0.1 2008
    用不能退出的站务id登陆
 
    5)查一下你的进程号
     bbs @Fedora :~/bbssrc/src$  ps -ef|grep bbsd
    bbs       7096     1  0 01:53 ?        00:00:00 ./bbsd 2008
    bbs       7111  7096  0 01:55 ?        00:00:00 ./bbsd 2008
    7096的进程是我们的守护进行,因为他的父ID是init进程(1进程),我们
    要捉的是下面那个用户登陆进程(7111)。
 
    6)运行gdb调试bbsd
     bbs @Fedora :~/bbssrc/src$  gdb bbsd
    GNU gdb 6.1.1
    Copyright 2004 Free Software Foundation, Inc.
    GDB is free software, covered by the GNU General Public License, and you are
    welcome to change it and/or distribute copies of it under certain conditions.
    Type "show copying" to see the conditions.
    There is absolutely no warranty for GDB.  Type "show warranty" for details.
    This GDB was configured as "i486-slackware-linux"...Using host 
    libthread_db      library "/lib/libthread_db.so.1".
    (gdb) list
    504         register time_t uptime;
    505         int readset;
    506         int value;
    507         struct timeval tv;
    508
    509        /* --------------------------------------------------- */
    510        /* setup standalone                                    */
    511        /* --------------------------------------------------- */
    512
    513         start_daemon();
    (gdb) 
    
    可以看到,当我们试着执行list的时候,可以看到程序的代码了。
   7)现在我们可以执行attach+进程号把进程捉下来慢慢看了
    (gdb) attach 7111 
    Attaching to program: /bbs/bbssrc/src/bbsd, process 7111
    Reading symbols from /lib/libtermcap.so.2...done.
    Loaded symbols for /lib/libtermcap.so.2
    Reading symbols from /lib/libdl.so.2...done.
    Loaded symbols for /lib/libdl.so.2
    Reading symbols from /lib/libnsl.so.1...done.
    Loaded symbols for /lib/libnsl.so.1
    Reading symbols from /lib/libcrypt.so.1...done.
    Loaded symbols for /lib/libcrypt.so.1
    Reading symbols from /lib/libc.so.6...done.
    Loaded symbols for /lib/libc.so.6
    Reading symbols from /lib/ld-linux.so.2...done.
    Loaded symbols for /lib/ld-linux.so.2
    Reading symbols from /lib/libnss_files.so.2...done.
    Loaded symbols for /lib/libnss_files.so.2
    0x40143c62 in select () from /lib/libc.so.6
    (gdb) list
    514
    515         (void) signal(SIGCHLD, reapchild);
    516         (void) signal(SIGTERM, close_daemon);
    517
    518
    519        /* --------------------------------------------------- */
    520        /* port binding                                        */
    521        /* --------------------------------------------------- */
    522
    523         xsin.sin_family = AF_INET;
   
     当我们再执行list的时候可以看到程序执行到当前的代码,这时你登陆
进去的id已经动不了了,因为进行一attach就停掉了,释放时可以执行detach。
    
    8)设置断点break(b)
    之前我们分析bug出现在main.c的u_exit()函数,所以我们可以执行以下
  命令:b u_exit
   
    (gdb) b u_exit
    Breakpoint 1 at 0x807b010: file main.c, line 224.
    
    显示断点的位置。当程序执行到这个函数的时候就会停止下来,这时你
   可以对变量,栈等变量进行查看,对函数调用进行查看。
 
  9)让程序继续执行continue(c)
    (gdb) c        
    Continuing.
        
    这里程序继续执行了,你登陆的id也可以活动了,当执行到8)我们设置
  的断点的时候才会再次停止下来。
 
  10)程序执行到断点
    (gdb) c        
    Continuing.
    Breakpoint 1, u_exit () at main.c:224
    224         if (strcmp(currentuser.userid, "guest") != 0)
    下站时执行到u_exit()函数,所以程序暂停了。
 
  11)让程序单步往下执行next(n) / step(s)
    (gdb) n
    225             brc_saveall();          // 保存所有阅读记录
    (gdb) s
    brc_saveall () at boards.c:1172
    1172        report("brc_save_all");
    (gdb)
    next是跳过函数往下执行,也就是把函数当成一条语句。
  step是跳进函数内部再执行。
  上面当我们执行一次n后发现接下来是brc_saveall()函数,因为我们
  的问题出现在这个函数里面,我们需要进这个函数内部分析,所以我
  们执行了s,跳进函数内部去一步一步执行。
 
    12)查看变量
  print(p) +变量名 可以打印出变量当前的值
  如果想打印数组,也可以直接 p + 数组名,但有可以看起来不是很
  舒服,这时我们可以把一个开关打开,看起来就会舒服很多.
    set print pretty on
    再打一个变量就会漂亮得多了。
   (gdb) p brc_read_num
    $1 = 2
   (gdb) p brc_read
    $2 = {{bid = 1, list = {1158002774 <repeats 60 times>}, num = 60, changed = 0 '\0', readpos = 0}, {bid = 5, list = {1158013150, 1, 
      0 <repeats 58 times>}, num = 2, changed = 0 '\0', readpos = 0}, {bid = 0, list = {0 <repeats 60 times>}, num = 0, 
    changed = 0 '\0', readpos = 0} <repeats 248 times>}
   (gdb) set print pretty on
   (gdb) p brc_read         
    $3 = {{
        bid = 1, 
        list = {1158002774 <repeats 60 times>}, 
        num = 60, 
        changed = 0 '\0', 
        readpos = 0
      }, {
        bid = 5, 
        list = {1158013150, 1, 0 <repeats 58 times>}, 
        num = 2, 
        changed = 0 '\0', 
        readpos = 0
      }, {
        bid = 0, 
        list = {0 <repeats 60 times>}, 
        num = 0, 
        changed = 0 '\0', 
        readpos = 0
     } <repeats 248 times>}
 
    13)发现问题
  一直往下next的时候,发现在执行
    1183               ptr =
    1184                    brc_putrecord(ptr, b_name, brc_read[i].num,
    1185                                  brc_read[i].list);
  函数时,会收到memcpy内存溢出的信号。
  
    14)s进函数brc_putrecord()
      1158            memcpy(ptr, list, num * sizeof(int));
      看到有这样一句。在memcpy的时候溢出了。
 
    15)可以用bt看一下函数堆栈
(gdb) bt
#0  brc_putrecord (ptr=0xbfff2ed0 "junk", name=0x403d4430 "junk", num=2, list=0x8194244) at boards.c:1156
#1  0x08067319 in brc_saveall () at boards.c:1183
#2  0x0807b02d in u_exit () at main.c:225
#3  0x0805c92a in Q_Goodbye () at bbs.c:3451
#4  0x0805cda6 in Goodbye () at bbs.c:3557
#5  0x0806ca4a in domenu (menu_name=0x80bcb32 "TOPMENU") at comm_lists.c:704
#6  0x0807d221 in start_client () at main.c:1281
#7  0x0805f180 in main (argc=2, argv=0xbffff7d4) at bbsd.c:651
     
    这是函数调用的顺序。
 
    我print了一下该用户的brc_read数组,确实发现有一个的数据相当异常。
 
    至此发现了问题的所在了,是brc_read数组数据的异常引起的,回到主站
 
   把该用户下面的.boardbrc删除掉,可以暂时让用户正常下站,因为删除了
 
   这个记录用户未读文章的记录文件,系统会自动生成一个,但会让该用户
 
   的所有文章标记为未读。 
  
   如:$bbshome/bbs/home/S/SYSOP/.boardbrc
    
     分析:
 
   问题是找到了,但还有几个未解决问题:
 
   1)现在删除.boardbrc只是治标不治本,因为当我删除某些站务的这个
 
   文件后,用户是能正常下站了,可是过了几天又不行了,问题当然是
 
   出在生成这个文件的程序,或者是某些版面的配置有问题。
 
    经过这次程序在网上查了一下资料,看到先辈们的一些讨论也对这个
 
   结构体有了一定的了解,下次再细说一下。
 
      2)当程序出到内存溢出的信号后程序会自动将这个函数再执行一次,
 
   加了-ggdb后,只会执行一次,但如果加了-O2会执行n次,搞不明白
 
      3)到底为什么会溢出?是越界了,还是?因为是站务个人的id,不能拿
 
   他们的id直接来调试,有空的时候再把.boardbrc文件转到测试站上
 
   再试一把。
 
      4)为什么只有站务会出现这个问题,其他人不会?最大的可能是引发
 
   这个bug的版面是内站版面,也可能是某些站务的习惯引起bug的爆发,
 
      也可能是之前有对一些版面做特殊处理所引起的。
 
      5)如果暂时找不到bug的根原,是否在出现内存溢出信号时直接返回,
 
   exit掉进程,影响的是当前用户的未读文章记录,但不至于影响整
 
   个站的正常运行。
 
       6)目前两个不能下站的站务id,表现出是不同的现象,有一个会不
 
    停地写AXXED记录,其他一个不会,有可能引发的原因是不同的?
 
      那就更麻烦了。
    
 后记 :(都是一些使用gdb的技巧)
 1)如果跟踪fork后的子进程 ,设置 set follow-fork-mode child 

    参考: http://www.ibm.com/developerworks/cn/linux/l-cn-gdbmp/index.html

 2)打印结构体的内容,p *structname

 3)bt查看函数堆栈后,用frame可以跳到相应层调试 如:

 4)set print pretty on 可以美化输出格式

----------- 2,3,4 示例-------------------------------------------------------------------------

(gdb) bt
#0  0x00007f2764232221 in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x00007f27641f3e82 in vfprintf () from /lib/x86_64-linux-gnu/libc.so.6
#2  0x00007f276421a814 in vsprintf () from /lib/x86_64-linux-gnu/libc.so.6
#3  0x00007f27641fca07 in sprintf () from /lib/x86_64-linux-gnu/libc.so.6
#4  0x000000000043434a in do_userlist () at list.c:303
#5  0x00000000004344d7 in show_userlist () at list.c:363
#6  0x0000000000436205 in choose (update=1, defaultn=0, title_show=0x43380e <print_title>, key_deal=0x4344ed <show_userlist+272>, 
    list_show=0x4343d6 <do_userlist+1085>, read=0x435af4 <setlistrange+11>) at list.c:958
#7  0x0000000000435e15 in t_friends () at list.c:873
#8  0x000000000042abd2 in domenu (menu_name=0xe2a1a9 "M_TALK") at comm_lists.c:738
#9  0x000000000042abd2 in domenu (menu_name=0x4867bb "TOPMENU") at comm_lists.c:738
#10 0x000000000043ebda in start_client () at main.c:1331
#11 0x000000000041af58 in main (argc=2, argv=0x7fff12def758) at bbsd.c:667
(gdb) frame 4
#4  0x000000000043434a in do_userlist () at list.c:303
303                 snprintf(user_info_str, STRLEN * 2,
(gdb) p *uentp
$1 = {active = 1, uid = 3, pid = 26460, invisible = 0, sockactive = 0, sockaddr = 0, destuid = 0, mode = 35, pager = 15, 
  in_chat = 0, fnum = 0, ext_idle = 1, chatid = "\000\000\000\000\000\000\000\000\000", 
  from = "localhost", '\000' <repeats 50 times>, idle_time = 1353765641, userid = "suit", '\000' <repeats 15 times>, 
  realname = "xxxxx", '\000' <repeats 14 times>, username = "suit", '\000' <repeats 35 times>, friend = {0 <repeats 200 times>}, 
  reject = {0 <repeats 32 times>}, board = '\000' <repeats 79 times>, define = 4294901759}
(gdb) set print pretty on
(gdb) p *uentp
$2 = {
  active = 1, 
  uid = 3, 
  pid = 26460, 
  invisible = 0, 
  sockactive = 0, 
  sockaddr = 0, 
  destuid = 0, 
  mode = 35, 
  pager = 15, 
  in_chat = 0, 
  fnum = 0, 
  ext_idle = 1, 
  chatid = "\000\000\000\000\000\000\000\000\000", 
  from = "localhost", '\000' <repeats 50 times>, 
  idle_time = 1353765641, 
  userid = "suit", '\000' <repeats 15 times>, 
  realname = "xxxxx", '\000' <repeats 14 times>, 
  username = "suit", '\000' <repeats 35 times>, 
  friend = {0 <repeats 200 times>}, 
  reject = {0 <repeats 32 times>}, 
  board = '\000' <repeats 79 times>, 
  define = 4294901759
}
(gdb) 


-----------------------------------------------------------------------------------------------

5)设置断点

gdb设置断点:单个文件
b line-number(行数);
b function-name(函数);
b line-or-function if condition(条件成功,断点);


多个文件
b filename:line-number (文件名:行数)
b filename:function-name (文件名:函数)




 
 

 

转载于:https://my.oschina.net/mawx/blog/91448

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值