linux开发常用-程序调试跟踪

目录

杂言

综述

1.gdb

    1.1 说明

       1.2 启动gdb

       1.3 条件和数据断点

       1.4 frame层查看

       1.5 内存数据查看

       1.6 struct成员查看

       1.7 内存数据分析

       1.8 代码地址转换

       1.9 无中断执行

       1.10 汇编分析

2.systemtap

  2.1 如何使用

  2.2 更改数据结构和流程

2.3 总结

3.strace


杂言

  对它简单处理,它就简单;
  对它复杂处理,它就复杂;
  复杂难以把控,无法预测;
  可预测的程度,关系成败;

综述

  总结个人以下几种方式学习和常用的调试和跟踪要点。以下有可以很好的对程序做hotfix动作的机制,可以有效降低程序的故障等。也可以提高我们学习和理解代码的效率

  • 打印日志
  • gdb
  • systemtap
  • strace

1.gdb

    1.1 说明


      gdb是我们理解和调试程序的重要工具,也是我们解决线上服务出现bug的重要手段
      官方使用文档是:https://sourceware.org/gdb/current/onlinedocs/gdb.pdf
      文档里面有很多详细的说明,可以有时间的时候慢慢学习下。

      官方说明支持的语言有:
      C, C++, D, Go, Objective-C, Fortran, OpenCL C, Pascal, Rust, assembly, Modula-2, and Ada

      这里不会罗列所有的用法,总结了下最近使用比较重要的几个点, 我个人实际中比较常用的

  • 启动gdb
  • 条件和数据断点
  • frame层查看
  • 内存数据查看
  • struct成员查看
  • 内存数据分析
  • 代码地址转换
  • 无中断执行
  • 汇编分析

       1.2 启动gdb

  •  gdb program       #直接启动程序
  •  gdb program 1234  #绑定到运行的进程
  •  gdb -p 1234 #绑定到运行的进程
  •  gdb attach 1234 #绑定到运行的进程  一般调试线上程序的时候基本用这个方式

       例如: gdb  hello_test

       1.3 条件和数据断点


         断点一般分为:一般断点,条件断点,数据断点,捕获断点。 结合下面 程序1 代码来说明
         条件断点:
           break 8  if i == 50    #通过行号来设置,在第8行设置断点
           如果设置成这个 break test_func if  i == 50  会不成功,在函数那个开始地址上没有 i 变量的上下文
           条件断点对于多次循环和具体的分支调试非常有用
         数据断点:
           
如果直接设置 watch watch_b_test, 很有可能没有上下文而失败
           一般设置数据断点通过地址方式或者明确有上下文的时候设置,地址方式如下:
          先通过一般函数或者行断点找到变量的地址,之后设置断点:
              (gdb) p &watch_b_test
               $2 = (int *) 0x7fffffffe2bc
              (gdb) watch *(int *) 0x7fffffffe2bc
               Hardware watchpoint 2: *(int *) 0x7fffffffe2bc   #设置成功

            watch_b_test = 1 在这条语句的时候会断下。
            数据断点在内存访问异常,非预期的行为调试非常有用。对于全局变量等数据断点一般是可以比较方便的直接使用

#include <stdio.h>

void test_func()
{
    int i = 0;

    for (; i < 100; i++) {
        if (i == 50) {          //第8行 测试条件断点
            printf("test\n");
        }
    }
}

void main()
{
    int watch_b_test = 0;
    char str_test[] = "str_test";
    char str_test1[] = "str_test1";
    char *p;

    test_func();

    /*测试数据断点*/
    watch_b_test = 1;

    /*测试内存访问*/
    p = str_test1;
    printf("prev test %s\n", str_test);
    p[18] = 'o';
    printf("after test %s\n", str_test);
}

程序1

       1.4 frame层查看


         打印出来调用栈后,有很多层的函数,每一层都有上下文,独自的上下文只有在当前层才能看到。如下图举例:在frame 0 层没法看到 watch_b_test 这个变量,需要切换到frame 1

           

       1.5 内存数据查看


         查看内存里面的数据,可以16进制查看
         命令格式 : x  /nfu  addr   
                                 n :要显示的数量;
                                 f :显示的格式 比如16进制;(‘x’, ‘d’, ‘u’, ‘o’, ‘t’, ‘a’, ‘c’, ‘f’, ‘s’), ‘i’ (for machine instructions)
                                 u:显示的宽度;b 一个字节,h 两个字节, w 四个字节, g 八个字节
          例子:
          
  

       1.6 struct成员查看


         两种情况 一种是查看标准结构, 一种是强制转换

代码段

    typedef struct _s_test {
        int s_t_a;
        int s_t_b;
    } s_test;    

    s_test st;
    char *ps;

    st.s_t_a = 1;
    st.s_t_b = 2;


    ps = (char *)&st;

第一种 :直接查看     

    gdb) p  st.s_t_a

第二种:地址转换

    gdb)p ((s_test *)ps)->s_t_a

       1.7 内存数据分析


         有时候在程序异常,特别是内存问题的时候需要分析内存的情况,比如是否越界,是否有非法内存使用等,个人经常会先分析附近内存的是否有使用不当导致出现非法使用。
         参考前面的 程序1, 在str_test1操作的时候,影响了str_test

上面输出结果:
    prev test str_test
    after test sto_test 
 str_test 被非法改成了 sto_test ,如何去发现呢,有一个方法是用 x 命令来查看下内存的使用情况。

分析:

  char str_test[] = "str_test"

  char str_test1[] = "str_test1"

在内存中地址如下 ,先定义的在前面地址,并且是占位了16个地址:

  (gdb) p &str_test[0]
   $1 = 0x7fffffffe290 "str_test"
  (gdb) p &str_test1[0]
   $2 = 0x7fffffffe280 "str_test1"

用 x 命令查看str_test附近的内存, 观察str_test附近有 str_test1,可以猜测是否有关联是操作str_test1造成了str_test被非法修改  :

  (gdb) x /32sb 0x7fffffffe280
   0x7fffffffe280:    "str_test1"
   0x7fffffffe28a:    "@"
   0x7fffffffe28c:    ""
   0x7fffffffe28d:    ""
   0x7fffffffe28e:    ""
   0x7fffffffe28f:    ""
   0x7fffffffe290:    "str_test"
   0x7fffffffe299:    ""
   0x7fffffffe29a:    ""

       1.8 代码地址转换


         在代码运行的时候出现bug或者段出错的时候,linux会打印出来一个代码地址,通过这个代码地址可以查看对应的源代码,通过 addr2line 来实现。这个对于比较清晰的bug很容易定位出来

   如这个代码, 会引起执行错误

     *(int *)0 = 1;

   dmesg的时候会打印

     test[7588]: segfault at 0 ip 00000000004005e4 sp 00007ffc22c3ab20 error 6 in test[400000+1000]

     通过addr2line来算出源文件的代码段
     执行命令:addr2line -e test 0x4005e4
     ---> ./test/test.c:28

     直接算出来出问题的程序是在test.c:28行

       1.9 无中断执行


         一般在线上服务的程序出问题的时候,很多时候不能直接gdb,这样会影响服务,特别是对于大型的服务器上面有成千上万的tcp连接,如果中断程序影响会比较大。在这种情况下为了收集一些信息和定位问题,不中断程序变的很重要了。gdb提供了脚本来实现这个功能

define commandname  #自定义名字

    print $arg0 + $arg1 + $arg2  #参数传递

    statement  #自定义语句

    ......  

end


可以在gdb里面直接运行上面脚本,也可以在gdb里面通过source命令来执行一个脚本文件

例子 定义一个脚本文件 test.gdb ,收集调用栈立即退出gdb,从而达到减少影响服务的目的:

define myprint_backtrace

   run              #启动程序

   break test_func  #定义断点

   bt        #断点后打印调用栈

   c         #断点后继续运行

   quit     #调用栈打印完后退出gdb

end

脚本运行:

   (gdb)source  test.gdb  

有空的时候再研究下是否可以整个过程都是可以脚本化


脚本里面还可以进一步编程用if,while等语句匹配自己想要的信息

define  matcharg0

    if $arg0 == 0

        printf "arg0 is 0"

    end

end

       1.10 汇编分析


         通过编译工具 比如objdump等来生成汇编和源文件的关系,从而分析一些问题。汇编这个比较复杂,后面好好学习下专门写个专题

2.systemtap

  systemtap是linux系统的一个调试跟踪程序的一个开源软件。个人觉得它是一个功能强大的热补丁工具hotfix,可以在运行的程序插入自己的代码和更改数据结构,可以很方便的跟踪程序执行情况。

官方文档

SystemTaphttps://sourceware.org/systemtap/documentation.html个人常用的功能:

  • 打印执行日志
  • 更改内核和应用程序的数据结构与流程,修复线上bug,标准hotfix。比如对nginx的更改
  • 理解程序执行的内部逻辑

  2.1 如何使用


    先看如下程序2: 基本功能是给结构体赋值,每3秒打印一次global_test ok

#include <stdio.h>

typedef struct _s_test {
    int a;
    int b;
} s_test;

int global_test = 1;
s_test global_s_t;

void test_func()
{
    if (global_test) {
        printf("global_test ok");
    }

    global_s_t.a = 1;
    global_s_t.b = 2;
}

void main()
{
    while(1) {
        test_func();
        sleep(3);
    }
}

    systemtap最简单的理解就是在函数执行前和执行后加入hook,从而控制和跟踪运行的程序

centos安装和测试

   sudo yum install systemtap 

   stap -ve 'probe begin { log("hello systap!") exit() }' #测试是否可以成功使用

   sudo yum install kernel-devel-3.10.0-1160.el7.x86_64 -y #需要安装对应的内核开发包

函数执行前hook脚本pre_func.stap如下

#!/usr/bin/stap

probe begin
{
    print("begin to probe")
}


#在函数test_func开始调用的时候做如下动作

probe process("/home/chenyinghong/test/stap_test").function("test_func")
{
    #print_backtrace(); 打印调用栈
    print("global_test is ", $global_test)  #显示变量global_test的内容
}

probe end
{
    print("end to probe")
}

执行命令

  sudo stap -gv ./pre_func.stap

函数执行后hook脚本只要改下,加下return

  probe process("/home/chenyinghong/test/stap_test").function("test_func").return

  2.2 更改数据结构和流程


    对于干预程序的运行非常重要,如以下例子:

probe process("/home/chenyinghong/test/stap_test").function("test_func")
{
    #print_backtrace(); 打印调用栈
    print("global_test is ", $global_test)  #显示变量global_test的内容

    $global_test = 0  #更改变量的内容,以控制流程等
}

2.3 总结

systemtap相关的功能非常强大,具体可以参考文文档。有时候灵活使用往往可以降低我们程序故障的发生

3.strace

  个人主要用在系统调用的性能分析,异常分析,进程hung分析

  优点是无语言限制,只要是liunx跑的基本都可以

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

X-道至简

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值