• Linux基础 30分钟GDB调试快速突破


    前言  gdb 开始调试开始上手

    1. 开启core, 采集程序崩溃的状态

      首先你跟着我做开启core崩溃状态采集. 可以通过 ulimit -c 查看 如果是0表示没有开启. 开启按照下面操作

    复制代码
    su root
    
    vi /etc/profile
    Shift + G
    i
    # No core files by default 0, unlimited is oo
    ulimit -S -c unlimited > /dev/null 2>&1
    wq!
    
    source /etc/profile
    复制代码

    上面shell 操作是 在 /etc/profile 最后一行添加 上面设置全局开启 core文件调试,大小不限. 最后 立即生效.

    再跟着我做, 因为生成的core文件同名会覆盖. 这里为其加上一个 core命名规则, 让其变成 [core.pid] 格式.

    复制代码
    su root
    
    vi /etc/sysctl.conf
    Shift + G
    i
    
    # open, add core.pid 
    kernel.core_pattern = ./core_%t_%p_%e
    kernel.core_uses_pid = 1 wq! sysctl -p /etc/sysctl.conf
    复制代码

    在 /etc/sysctl.conf 文件中添加系统配置. 后面立即启用. 最后是下面状态表示core启用都搞好了.

    (上面是ubuntu 15.10 环境中, 后面测试用的是centos 6.4)

    2. 简单接触 GDB , 开始调试 r n p

    第一个演示代码 heoo.c

    复制代码
    #include <stdio.h>
    
    int g_var = 0;
    
    static int _add(int a, int b) {
        printf("_add callad, a:%d, b:%d
    ", a, b);
        return a+b;
    }
    
    int main(void) {
        int n = 1;
        
        printf("one n=%d, g_var=%d
    ", n, g_var);
        ++n;
        --n;
        
        g_var += 20;
        g_var -= 10;
        n = _add(1, g_var);
        printf("two n=%d, g_var=%d
    ", n, g_var);
        
        return 0;
    }
    复制代码

    我们下面从图说起.(如果用视频说更好,文字和图意义在于查询方便.更简约)

    第一个命令 gdb heoo.out 表示 gdb加载 heoo.out 开始调试. 如果需要使用gdb调试的话编译的时候 gcc 需要加上 -g命令.

    其中l命令表示 查看加载源码内容. 下面将演示如何加断点.

    r 表示调试的程序开始运行.

    p 命令表示 打印值. n表示过程调试, 到下一步. 不管子过程如何都不进入. 直接一次跳过.

     

    上面用的s 表示单步调试, 遇到子函数,会进入函数内部调试.

    总结一下 . l 查看源码 , b 加断点, r 开始运行调试, n 下一步, s下一步但是会进入子函数. p 输出数据.

    到这里gdb 基本会用了. 是不是也很容易. 直白. 小代码可以随便调试了.

    看到这里基础知识普及完毕了. 后面可以不看了. 有机会再看. 好那我们接着扯.

    正文 第一部分 gdb其它开发中用的命令

      开始扯一点, linux总是敲命令操作, 也很不安全. 有时候晕了. 写这样编译命令.

    gcc -g -Wall -o heoo.c heoo.out

    非常恐怖, heoo.c 代码删除了. heoo.out => heoo.c 先创建后生成失败退出. 原先的内容被抹掉了. 哈哈. 服务器开发, 经验不足, 熟练度不够.自己都怕自己.

    1.  gdb 其它常用命令用法 c q b info

    首先看 用到的调试文件 houge.c

    复制代码
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    
    /*
     * arr 只能是数组
     * 返回当前数组长度
     */
    #define LEN(arr) (sizeof(arr)/sizeof(*arr))
    
    // 简单数组打印函数
    static void _parrs(int a[], int len) {
        int i = -1;
        puts("当前数组内容值如下:");
    
        while(++i < len) 
            printf("%d ", a[i]);    
        putchar('
    ');
    }
    
    // 简单包装宏, arr必须是数组
    #define PARRS(arr) 
        _parrs(arr, LEN(arr))
    
    #define _INT_OLD (23)
    
    /*
     * 主函数,简单测试
     * 测试 core文件, 
     * 测试 宏调试
     * 测试 堆栈内存信息
     */
    int main(void) {
        int i;
        int a[_INT_OLD];
        int* ptr = NULL;    
    
        // 来个随机数填充值吧
        srand((unsigned)time(NULL));
        for(i=0; i<LEN(a); ++i)
            a[i] = rand()%222;
        
        PARRS(a);
    
        //全员加double, 包含一个错误方便测试
        for(i=1; i<=LEN(a); ++i)
            a[i] <<= 1;
        PARRS(a);
    
        // 为了错,强制错
        *ptr = 0;
    
        return 0;
    }
    复制代码

    同样需要仔细看下面图中使用的命令. 首先对前言部分加深一些. 看下面

    这个图是前言的补充, c跳过直到下一个断点处, q表示程序退出.

    在 houge.c 中我们开始调试. 一运行段错误, 出现了我们的 core.pid 文件

    通过 gdb houge.out core.27047 开始调试. 马上定位出来了错误原因.

    2. 调试 内存堆栈信息

    刚开始 print a , 在main中当做数组处理.打印的信息多. 后面在_add函数中, a就是个形参数组地址.

    主要看 info args  查看当前函数参数值

    info locals 看当前函数栈上值信息, info registers 表示查看寄存器值.

    后面查看内存信息 需要记得东西多一些. 先看图

    x /23dw a 意思是  查看 从a地址开始 23个 4字节 有符号十进制数 输出.

    关于x 更加详细见下面

    复制代码
    用gdb查看内存格式: 
        x /nfu ptr
    
    说明
    x 是 examine 的缩写
    n表示要显示的内存单元的个数
    
    f表示显示方式, 可取如下值
    x 按十六进制格式显示变量。
    d 按十进制格式显示变量。
    u 按十进制格式显示无符号整型。
    o 按八进制格式显示变量。
    t 按二进制格式显示变量。
    a 按十六进制格式显示变量。
    i 指令地址格式
    c 按字符格式显示变量。
    f 按浮点数格式显示变量。
    
    u表示一个地址单元的长度
    b表示单字节,
    h表示双字节,
    w表示四字节,
    g表示八字节
    
    Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),
    t(binary), f(float), a(address), i(instruction), c(char) and s(string).
    Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes)
    
    ptr 表示从那个地址开始
    复制代码

    这个命令常用于监测内存变化.调试中特别常用.

    3. gdb 设置条件断点

    很简单 b 17 if i == 8. 在17行设置一个断点,并且只有i==8的时候才会触发.

    4. gdb 删除断点

    gdb 删除有d 后面跟断点索引1,2,3..

    clear 行数或名称. 删除哪一行断点. 看下面演示

    到这里 介绍的gdb调试技巧基本都够用了. 感觉用图形ide,例如vs调试也就用到这些了.

    估计gdb调试突破20min过去了.够用了.  后面可以不用看了.

    正文 第二部分 gdb 多线程多进程调试

      到这里实战中用的机会少了, 也就老鸟会用上些. 这部分可以调试,不好调试. 一般一调估计小半天就走了. 好,那我们处理最后10min.

    1. 首先对上面正文第一部分加深 gdb调试宏

    首先看上面命令 

      macro expand 宏(参数) => 得到宏导出内容.

      info macro 宏名 => 宏定义内容

    如果你需要用到上面gdb功能, 查看和导出宏的话.还需要gcc 支持,生成的时候加上 -ggdb3如下

    gcc -Wall -ggdb3 -o houge.out houge.c

    就可以使用了. 扩展一下 对于 gcc 编译的有个过程叫做 预编译 gcc -E -o *.i *.c.

    这时候处理多数宏,直接展开, 也可以查看最后结果. 也算也是一个黑科技.

    2. 开始多线程调试

    首先看测试用例 dasheng.c

    复制代码
    #include <stdio.h>
    #include <stdlib.h>
    #include <pthread.h>
    
    // 声明一个都用的量
    static int _old;
    
    // 线程跑的函数
    static void* _run(void* arg) {
        int piyo = 10;    
        int n = *(int*)arg;
        int i;
        
        //设置线程分离
        pthread_detach(pthread_self());
        
        for(i=0; i<n; ++i) {
            printf("n=%d, i=%d
    ", n, i);
            ++_old;
            printf("n=%d, piyo = %d, _old=%d
    ", n, piyo, _old);
        }
    
        return NULL;
    }
    
    #define _INT_PTX (3)
    
    int main(void) {
        int i, rt, j;
        pthread_t tx[_INT_PTX];
    
        puts("main beign");    
    
        for(i=0; i<_INT_PTX; ++i) {
            // &i 是有问题的, 但是这里为了测试, 可以乱搞
            rt = pthread_create(tx+i, NULL, _run, &i);
            if(rt < 0) {
                printf("pthread_create create error! rt = %d, i=%d
    ", rt, i);
                break;
            }
        }
    
        //CPU忙等待
        for(j=0; j<1000000000; ++j)
            ;        
        puts("end");    
    
        return 0;
    }
    复制代码

    编译命令

    gcc -Wall -g -o dasheng.out dasheng.c -lpthread

    那先看下面测试图

    上面 info threads 查看所有运行的线程信息. *表示当前调试的线程.

    后面 l _run 表示查看 _run附近代码. 当然还有 l 16 查看16行附近文件内容.

    gdb多线程切换 测试如下

     thread 3表示切换到第三个线程, info threads 第一列id 就是 thread 切换的id.

    上面测试线程 就算你切换到 thread 3. 其它线程还是在跑的. 我们用下面命令 只让待调试的线程跑. 其它线程阻塞.

    set scheduler-locking on  开始多线程单独调试. 不用了 设置 set scheduler-locking off 关闭. 又会回到你调试这个, 其它线程不阻塞.

    总结 多线程调试常用就这三个实用命令

    info threads

    thread id

    set scheduler-locking on/off

    分别是查看,切换,设置同步调试.到这里多线程调试基本完毕了.

    3. 开始gdb 多进程调试

    首先看 liaobude.c 测试代码

    复制代码
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    
    // 声明一个都用的量
    static int _old;
    
    // 线程跑的函数
    static void _run(int n) {
        int piyo = 10;    
        int i;
        
        ++n;    
        for(i=0; i<n; ++i) {
            printf("n=%d, i=%d
    ", n, i);
            ++_old;
            printf("n=%d, piyo = %d, _old=%d
    ", n, piyo, _old);
        }
    }
    
    #define _INT_PTX (3)
    
    int main(void) {
        int i;
        pid_t rt;
    
        puts("main beign");    
    
        for(i=0; i<_INT_PTX; ++i) {
            // &i 是有问题的, 但是这里为了测试, 可以乱搞
            rt = fork();
            if(rt < 0) {
                printf("fork clone error! rt = %d, i=%d
    ", rt, i);
                break;
            }
            if(rt == 0) {
                _run(i);
                exit(EXIT_FAILURE);
            }
        }
    
        //等待子进程结束
      for(;;) {
        rt = waitpid(-1, NULL, WNOHANG);
        if(rt>=0 || errno==EINTR)
          continue;
        break;
      } puts("end"); // 这里继续等待 for(i=0; i<190; ++i){ printf("等待 有缘人[%d]! ", i); sleep(1); } return 0; }
    复制代码

    编译命令

    gcc -Wall -g -o liaobude.out liaobude.c

     其实对多进程调试, 先介绍一个 常用的, 调试正在运行的程序. 首先让 ./liaobude.out 跑起来.

     再通过 ps -ef 找到需要调试的进程. 复制进程文件描述符pid.

    这时候启动gdb.

    attach pid

    gdb就把pid那个进程加载进来了. 加载的进程会阻塞到当前正在运行的地方. 直到使用命令控制. 这个功能还是非常猛的.

    最后介绍 进程调试的有关命令(需要最新的gdb才会支持). 多进程的调试思路和多线程调试流程很相似.

    复制代码
    GDB可以同时调试多个程序。
    只需要设置follow-fork-mode(默认值:parent)和detach-on-fork(默认值:on)即可。
    
       设置方法:set follow-fork-mode [parent|child]   set detach-on-fork [on|off]
    
       查询正在调试的进程:info inferiors
       切换调试的进程: inferior <infer number>
        
    复制代码

    具体的意思有

    set follow-fork-mode [parent|child]   set detach-on-fork [on|off]

     parent                   on               只调试主进程(gdb默认)
     child                      on               只调试子进程
     parent                   off              同时调试两个进程,gdb跟主进程,子进程block在fork位置
     child                      off              同时调试两个进程,gdb跟子进程,主进程block在fork位置

    更加详细的 gdb 多进程调试demo 可以参照  http://blog.csdn.net/pbymw8iwm/article/details/7876797

    使用方式和线程调试思路是一样的. 就是gdb 的命令换了字符. 工作中多进程调试遇到少. 

    遇到了很少用gdb调试. 会用下面2种调试好办法

    2) 写单元测试

    3) 打日志检测日志,分析

    到这里 gdb30分钟内容讲解完毕. 多试试写写练一练, gdb基本突破没有问题.

    后记

      错误是难免的, 有问题可以随时交流. 拜~~, 周六下午愉快. 希望明天仍然是个好天气~~

  • 相关阅读:
    整理ASP.NET MVC 5各种错误请求[401,403,404,500]的拦截及自定义页面处理实例
    Sql Server 创建表添加说明
    c# 去除字符串中重复字符
    WebStorm中常用的快捷键及使用技巧
    git bash中的快捷键
    git bash here 的 ~/.bashrc 配置文件。和 vue/cli 3. 0 的 .vuerc文件(preset )
    右键菜单添加打开CMD选项
    Vue中使用Vue.component定义两个全局组件,用单标签应用组件时,只显示一个组件的问题和 $emit的使用。
    vue和stylus在subline中显示高亮
    stylus入门教程,在webstorm中配置stylus
  • 原文地址:https://www.cnblogs.com/guochaoxxl/p/10598132.html
Copyright © 2020-2023  润新知