GDB调试(二)
2025-08-22 16:02:10,

GDB调试

运行中程序GDB调试

测试程序

//test2.c
//功能:从0开始每秒打印
#include <stdio.h>
#include <unistd.h>
int aaa();
int bbb(int n);
int main()
{
    aaa();
}

int aaa()
{
    bbb(0);
}

int bbb(int n)
{
    for(int i = n; i < n+10000; i++)
    {
        printf("i:%d\n", i);
        n++;
        sleep(1);
    }
}
gcc -g -o test2 test2.c
./test2
//结果
:0
i:1
i:2
i:3
i:4
i:5
i:6
i:7
......

操作步骤

1.运行程序

在终端窗口运行编译好的程序

./test2

2.进程ID

在另一个终端窗口,使用ps命令找到正在运行程序的进程ID,其中进程ID是第二个

ps aux | grep test2
//结果如下,其中正在运行程序的进程ID是15554
username   15554  0.0  0.0   2776  1408 pts/1    S+   22:38   0:00 ./test2
username   15557  0.0  0.0  12192  2432 pts/2    S+   22:39   0:00 grep --color=auto test

3.附加GDB

使用GDB附加到正在运行的程序上

sudo gdb test2 -p 15554

这里的sudo加不加看环境,有些不用加

4.GDB调试

在GDB中,你可以使用常用的调试命令如bt(查看调用堆栈),print(打印变量值),C++ontinue(继续执行程序),等等

这里因为sleep延时,直接continue后,test2继续运行,gdb这里卡住了,可以用CTRL-C重新中断

5.结束调试

命令 解析
detach 直接使用detach命令,可以从进程中分离GDB并让程序继续运行
attach PID 重新将GDB附加到某个进程上

GDB调试多进程

测试程序

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
        printf("begin\n");

        if ( fork() != 0 )
        {
                printf("我是父进程:进程pid=%d,父进程ppid=%d\n",getpid(),getppid());

                int ii;
                for(ii=0; ii<200; ii++)
                {
                        printf("父进程ii=%d\n",ii);
                        sleep(1);
                }
                exit(0);
                }

        else
        {
                printf("我是子进程:进程pid=%d,父进程ppid=%d\n",getpid(),getppid());
                int jj;
                for(jj=0; jj<200; jj++)
                {
                        printf("子进程jj=%d\n",jj);
                        sleep(1);
                }
                exit(0);
        }
}
gcc -g -o test3 test3.c
./test3
//运行结果
我是父进程:进程pid=5003,父进程ppid=4675
父进程ii=0
我是子进程:进程pid=5004,父进程ppid=5003
子进程jj=0
父进程ii=1
子进程jj=1
子进程jj=2
父进程ii=2
子进程jj=3
父进程ii=3
子进程jj=4
父进程ii=4
父进程ii=5
子进程jj=5
子进程jj=6
父进程ii=6
子进程jj=7
父进程ii=7
父进程ii=8
子进程jj=8
......


//开始调试
gdb test3

调试命令

命令 解析
set follow-fork-mode child 设置追踪子进程。gdb 默认调试的是父进程,如果想调试子进程,那么在用gdb调试的时候要增加。该命令要在子进程运行前设置
set detach-on-fork on/off 继续其他进程/停止其他进程。当我们调试某个进程的时候,设置别的进程是否运行。默认是on,表示调试当前进程的时候,其他的进程继续运行。off,表示调试当前进程,其他的进程被 gdb 挂起。
info inferior 通过查看可以调试的进程,方便后面切换进程
inferior processNum 可以通过 info inferior 来查看可以调试的进程,当需要切换调试的进程时,根据processNum进行切换。这里的processNum是gdb自己排的进程编号,不是进程ID

以下程序未设置命令,因此默认调试父进程,并且调试的时候子进程是运行的

eading symbols from test3...
(gdb) b 7
Breakpoint 1 at 0x1216: file test3.c, line 7.
(gdb) run
Starting program: /home/lisentao/Desktop/fatenone/project/gcc/gdb2/test3 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-Linux-gnu/libthread_db.so.1".

Breakpoint 1, main () at test3.c:7
7            printf("begin\n");
(gdb) n
begin
9            if ( fork() != 0 )
(gdb) n
[Detaching after fork from child process 5242]
我是子进程:进程pid=5242,父进程ppid=5239
子进程jj=0
11                    printf("我是父进程:进程pid=%d,父进程ppid=%d\n",getpid(),getppid());
(gdb) 子进程jj=1
子进程jj=2
子进程jj=3
子进程jj=4
子进程jj=5
子进程jj=6

使用set follow-fork-mode child命令调试子进程,并使用set detach-on-fork off命令挂起父进程,随后再切换调试父进程

Reading symbols from test3...
(gdb) b 7
Breakpoint 1 at 0x1216: file test3.c, line 7.
(gdb) set follow-fork-mode child
(gdb) set detach-on-fork off
(gdb) r
Starting program: /home/lisentao/Desktop/fatenone/project/gcc/gdb2/test3 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main () at test3.c:7
7            printf("begin\n");
(gdb) n
begin
9            if ( fork() != 0 )
(gdb) n
[Attaching after Thread 0x7ffff7fa9740 (LWP 5333) fork to child process 5336]
[New inferior 2 (process 5336)]
Reading symbols from /usr/lib/debug/.build-id/49/0fef8403240c91833978d494d39e537409b92e.debug...
Reading symbols from /usr/lib/debug/.build-id/41/86944c50f8a32b47d74931e3f512b811813b64.debug...
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[Switching to Thread 0x7ffff7fa9740 (LWP 5336)]
main () at test3.c:9
9            if ( fork() != 0 )
(gdb) n
24                    printf("我是子进程:进程pid=%d,父进程ppid=%d\n",getpid(),getppid());
(gdb) n
我是子进程:进程pid=5336,父进程ppid=5333
26                    for(jj=0; jj<200; jj++)
(gdb) n
28                            printf("子进程jj=%d\n",jj);
(gdb) n
子进程jj=0
29                            sleep(1);
(gdb) n
26                    for(jj=0; jj<200; jj++)
(gdb) info inferior
  Num  Description       Connection           Executable        
  1    process 5333      1 (Native)           /home/lisentao/Desktop/fatenone/project/gcc/gdb2/test3 
* 2    process 5336      1 (native)           /home/lisentao/Desktop/fatenone/project/gcc/gdb2/test3 
(gdb) inferior 1
[Switching to inferior 1 [process 5333] (/home/lisentao/Desktop/fatenone/project/gcc/gdb2/test3)]
[Switching to thread 1.1 (Thread 0x7ffff7fa9740 (LWP 5333))]
#0  arch_fork (ctid=0x7ffff7fa9a10) at ../sysdeps/unix/sysv/linux/arch-fork.h:52
52    ../sysdeps/unix/sysv/linux/arch-fork.h: 没有那个文件或目录.
(gdb) info inferior
  Num  Description       Connection           Executable        
* 1    process 5333      1 (native)           /home/lisentao/Desktop/fatenone/project/gcc/gdb2/test3 
  2    process 5336      1 (native)           /home/lisentao/Desktop/fatenone/project/gcc/gdb2/test3 
(gdb) n
56    in ../sysdeps/unix/sysv/linux/arch-fork.h
(gdb) n
__GI__Fork () at ../sysdeps/nptl/_Fork.c:29
29    ../sysdeps/nptl/_Fork.c: 没有那个文件或目录.
(gdb) n
50    in ../sysdeps/nptl/_Fork.c
(gdb) n
__libc_fork () at ./posix/fork.c:75
75    ./posix/fork.c: 没有那个文件或目录.
(gdb) n
113    in ./posix/fork.c
(gdb) n
126    in ./posix/fork.c
(gdb) n
128    in ./posix/fork.c
(gdb) n
132    in ./posix/fork.c
(gdb) n
main () at test3.c:9
9            if ( fork() != 0 )
(gdb) n
11                    printf("我是父进程:进程pid=%d,父进程ppid=%d\n",getpid(),getppid());
(gdb) n
我是父进程:进程pid=5333,父进程ppid=5328
14                    for(ii=0; ii<200; ii++)
(gdb) n
16                            printf("父进程ii=%d\n",ii);
(gdb) n
父进程ii=0
17                            sleep(1);
(gdb) n
n14                    for(ii=0; ii<200; ii++)
(gdb) n
16                            printf("父进程ii=%d\n",ii);
(gdb) 

GDB调试多线程

测试程序

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

#define MAX 50
//全局变量
int number;
//创建一把互斥锁
pthread_mutex_t mutex;

void* funcA_num(void* arg)
{
    for(int i=0; i<MAX; ++i)
    {
        pthread_mutex_lock(&mutex);
        number++;
        printf("Thread A, id = %lu, number = %d\n", pthread_self(), number);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

void* funcB_num(void* arg)
{
    for(int i=0; i<MAX; ++i)
    {
        pthread_mutex_lock(&mutex);
    number++;
        printf("Thread B, id = %lu, number = %d\n", pthread_self(), number);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    return NULL;
}

int main(int argc, const char* argv[])
{
    pthread_t p1, p2;

    // 初始化互斥锁
    pthread_mutex_init(&mutex, NULL);

    // 创建两个子线程
    pthread_create(&p1, NULL, funcA_num, NULL);
    pthread_create(&p2, NULL, funcB_num, NULL);

    for(int k = 0; k < MAX; k++)
    {
        pthread_mutex_lock(&mutex);
    number++;
        printf("Thread Main, id = %lu, number = %d\n", pthread_self(), number);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }

    // 阻塞,资源回收
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);

    // 销毁互斥锁
    // 线程销毁之后, 再去释放互斥锁
    pthread_mutex_destroy(&mutex);

    return 0;
}
gcc -g -o test2 test2.c
./test2
//结果
:0
i:1
i:2
i:3
i:4
i:5
i:6
i:7
......
0

线程查看命令

命令 解析
ps -aL | grep xxx 查看轻量级进程。轻量级的进程就是线程:p——process:进程; s——state:状态;a——all:全部;L——light:轻
pstree -p 主线程id 主线程和子线程的关系用树形展开
gcc -g -o test2 test2.c
./test2
//结果
:0
i:1
i:2
i:3
i:4
i:5
i:6
i:7
......
1

调试命令

命令 解析
info threads 查看线程
thread 线程号 切换到某个线程,这个线程号可以查看info threads
set scheduler-locking on 可以用来锁定当前线程,只观察这个线程的运行情况。当锁定这个线程时,其他线程就处于了暂停状态,也就是说你在当前线程执行 next、step、until、finish、return 命令时,其他线程是不会运行的。需要注意的是,你在使用 set scheduler-locking on/step 选项时要确认下当前线程是否是你期望锁定的线程,如果不是,可以使用 thread <线程编号> 切换到你需要的线程,然后再进行锁定。
set scheduler-locking step 也是用来锁定当前线程,当且仅当使用 next 或 step 命令做单步调试时会锁定当前线程,如果使用 until、finish、return 等线程内的非单步调试命令,其他线程还是有机会运行的。相比较 on 选项值,step 选项值给为单步调试提供了更加精细化的控制,因为通常我们只希望在单步调试时,不希望其他线程对当前调试的各个变量值造成影响。
set scheduler-locking off 关闭锁定当前线程,所有的线程都会执行。
gcc -g -o test2 test2.c
./test2
//结果
:0
i:1
i:2
i:3
i:4
i:5
i:6
i:7
......
2

Core Dump基础

有时候写程序会出现 coredump 的错误,也就是内存溢出,程序挂掉。要去查看是程序中的那个地方导致了内存溢出,可以在GDB调试的时候加上 core 文件,core 文件里面记录了程序挂掉的一些重要信息。

调试命令

命令 解析
ulimt -a 系统参数,core文件的默认大小是 0
ulimit -c unlimited 将core文件的大小修改为无限制

引用

gdb调试正在运行中的程序_gdb调试正在运行的程序-CSDN博客

深入理解GDB调试:多进程、多线程及核心文件剖析-CSDN博客

GDB-2——GDB调试多线程 - Hello-World3 - 博客园

Linux多线程调试没那么难,别就会一个printf! - 知乎