文章

gdb高频总结二

怎么debug,怎么看内存泄漏

调试内存泄漏问题通常需要借助工具和手动分析结合的方法。以下是如何使用 GDB 以及工具(如 ValgrindAddressSanitizer (ASan))定位内存泄漏的具体步骤。


1. 使用 GDB 手动跟踪内存泄漏

GDB 可以通过设置断点和捕获内存分配函数(如 mallocfree)来帮助跟踪内存分配和释放的情况。

步骤:

  1. 在内存分配和释放函数上设置断点:

    1
    2
    
    (gdb) break malloc
    (gdb) break free
    
  2. 运行程序:

    1
    
    (gdb) run
    
  3. 跟踪每次内存分配: 当程序暂停在 malloc 时:

    1
    2
    
    (gdb) print size       # 查看分配内存的大小
    (gdb) backtrace        # 查看调用栈,定位内存分配的位置
    
  4. 跟踪内存释放: 当程序暂停在 free 时:

    1
    2
    
    (gdb) print ptr         # 查看被释放的内存地址
    (gdb) backtrace         # 查看释放内存的位置
    
  5. 记录未释放的内存:

    • 观察调用 mallocnew 时的地址。
    • 确认是否有相应的 freedelete 释放。

示例程序:

1
2
3
4
5
6
7
8
9
10
#include <stdlib.h>

void leak_memory() {
    int *p = (int *)malloc(10 * sizeof(int));
}

int main() {
    leak_memory();
    return 0;
}

调试过程:

1
gdb ./a.out

在 GDB 中:

1
2
3
4
5
(gdb) break malloc
(gdb) run
(gdb) print size     # 查看分配的大小
$1 = 40
(gdb) backtrace      # 查看分配调用栈

通过调用栈可以定位到未释放内存的位置(leak_memory 函数)。


2. 使用 Valgrind 检测内存泄漏

Valgrind 是专门用于内存调试的工具,可以自动检测未释放的内存。

步骤:

  1. 运行程序并检测内存泄漏:

    1
    
    valgrind --leak-check=full --track-origins=yes ./your_program
    
  2. 查看内存泄漏报告: Valgrind 会生成详细的内存泄漏报告,包括泄漏大小、分配位置和堆栈跟踪。

示例输出:

1
2
3
4
==12345== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345==    at 0x4C2F18F: malloc (vg_replace_malloc.c:309)
==12345==    by 0x4005F7: leak_memory (example.c:5)
==12345==    by 0x40060F: main (example.c:9)
  • 解释:
    • 40 bytes in 1 blocks 表示有 40 字节的内存未释放。
    • 堆栈信息指向 leak_memory 函数的第 5 行。
  1. 结合 GDB 进一步调试:

    根据 Valgrind 的报告,在 GDB 中设置断点:

    1
    2
    3
    
    gdb ./your_program
    (gdb) break leak_memory
    (gdb) run
    

3. 使用 AddressSanitizer (ASan)

AddressSanitizer 是 GCC 和 Clang 提供的内存调试工具,可以检测内存泄漏和其他内存错误。

步骤:

  1. 编译时启用 AddressSanitizer: 使用 -fsanitize=address-g(调试信息)选项编译程序:

    1
    
    gcc -fsanitize=address -g -o your_program your_program.c
    
  2. 运行程序:

    1
    
    ./your_program
    
  3. 查看 ASan 报告: 如果存在内存泄漏,ASan 会生成类似以下的报告:

    1
    2
    3
    4
    5
    6
    7
    
    =================================================================
    ==12345==ERROR: LeakSanitizer: detected memory leaks
       
    Direct leak of 40 byte(s) in 1 object(s) allocated from:
        #0 0x4c2f18f in malloc (asan_malloc.c:92)
        #1 0x4005f7 in leak_memory (example.c:5)
        #2 0x40060f in main (example.c:9)
    
  4. 结合 GDB 分析: 使用 ASan 的堆栈信息,在 GDB 中调试分配内存的代码:

    1
    2
    3
    
    gdb ./your_program
    (gdb) break leak_memory
    (gdb) run
    

4. 手动检查和记录

如果没有使用工具,以下方法可以帮助手动检查内存泄漏:

步骤:

  1. 审查动态内存分配和释放的匹配:

    • 确保每个 mallocnew 都有对应的 freedelete
    • 检查代码中的所有可能路径,确保没有遗漏释放操作。
  2. 添加日志记录: 在动态内存分配和释放函数中记录日志,帮助跟踪未释放的内存:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    void* my_malloc(size_t size) {
        void* ptr = malloc(size);
        printf("Allocated %p (%zu bytes)\n", ptr, size);
        return ptr;
    }
       
    void my_free(void* ptr) {
        printf("Freed %p\n", ptr);
        free(ptr);
    }
    
  3. 运行程序并分析日志:

    • 查找分配但未释放的内存地址。
    • 确认是否有遗漏释放操作。

总结

  1. 使用 GDB
    • 手动设置断点跟踪 mallocfree
    • 检查调用栈和分配/释放逻辑。
  2. 使用 Valgrind
    • 自动检测内存泄漏并提供详细报告。
    • 结合 GDB 定位问题代码。
  3. 使用 AddressSanitizer
    • 在编译时启用,运行时报告泄漏的堆栈信息。
    • 更适合现代 C/C++ 开发。
  4. 代码审查和日志
    • 手动排查动态内存管理是否匹配。
    • 添加日志帮助跟踪动态内存操作。

通过结合以上方法,你可以高效定位和解决内存泄漏问题。

gdb使用 -> 多线程程序切换到某线程栈帧 -> 如何查看寄存器值

在使用 GDB 调试多线程程序时,可以切换到特定线程和栈帧,然后查看寄存器的值。以下是具体步骤:


1. 查看当前线程列表

使用以下命令查看所有线程及其状态:

1
info threads

输出示例:

1
2
3
  Id   Target Id         Frame
* 1    Thread 0x7ffff7e3d700 (LWP 12345) "main" at main.c:10
  2    Thread 0x7ffff7c3b700 (LWP 12346) "worker" at worker.c:20
  • 每个线程都有一个 ID(如 12)。
  • * 表示当前活跃的线程。

2. 切换到指定线程

使用以下命令切换到某个线程:

1
thread thread_id

示例:

1
(gdb) thread 2

此时,GDB 的上下文切换到了线程 2


3. 查看线程的栈帧

切换到目标线程后,可以查看该线程的调用栈:

1
backtrace

输出示例:

1
2
3
#0  worker_function (arg=0x7ffff7e3a010) at worker.c:20
#1  0x00007ffff7c10f00 in start_thread () from /lib/x86_64-linux-gnu/libpthread.so.0
#2  0x00007ffff7b00fb3 in clone () from /lib/x86_64-linux-gnu/libc.so.6

4. 切换到特定栈帧

使用以下命令切换到某个栈帧:

1
frame frame_number

示例:

1
(gdb) frame 0

此时,GDB 的上下文切换到了线程的栈帧 #0


5. 查看寄存器值

使用以下命令查看寄存器的值:

  1. 查看所有寄存器值

    1
    
    info registers
    

    示例输出:

    1
    2
    3
    4
    5
    
    rax            0x1                 1
    rbx            0x7ffff7bcd000      140737349632000
    rcx            0x0                 0
    rdx            0x7fffffffe5d0      140737488346064
    ...
    
  2. 查看单个寄存器值 如果只需要查看特定寄存器:

    1
    
    print $register_name
    

    示例:

    1
    
    print $rax
    
  3. 动态修改寄存器值 如果需要更改寄存器的值(用于测试或模拟):

    1
    
    set $register_name = value
    

    示例:

    1
    2
    
    set $rax = 42
    print $rax
    

完整操作示例

假设调试一个多线程程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void *worker_function(void *arg) {
    int counter = 0;
    while (1) {
        counter++;
        usleep(100000);
    }
    return NULL;
}

int main() {
    pthread_t worker_thread;
    pthread_create(&worker_thread, NULL, worker_function, NULL);

    while (1) {
        sleep(1);
    }

    return 0;
}

调试步骤:

  1. 编译并运行程序:

    1
    2
    
    gcc -g -pthread -o multithread_program multithread_program.c
    gdb ./multithread_program
    
  2. 在 GDB 中查看线程:

    1
    
    (gdb) info threads
    

    输出示例:

    1
    2
    3
    
      Id   Target Id         Frame
    * 1    Thread 0x7ffff7e3d700 (LWP 12345) "main" at main.c:10
      2    Thread 0x7ffff7c3b700 (LWP 12346) "worker" at worker_function.c:6
    
  3. 切换到线程 2

    1
    
    (gdb) thread 2
    
  4. 查看栈帧并切换:

    1
    2
    
    (gdb) backtrace
    (gdb) frame 0
    
  5. 查看寄存器:

    1
    
    (gdb) info registers
    
  6. 修改寄存器值(可选):

    1
    2
    
    (gdb) set $rax = 42
    (gdb) print $rax
    

提示

  • 线程 ID vs 系统线程 ID:GDB 的线程 ID 与操作系统的线程 ID(如 LWP ID)不同,调试时需使用 GDB 提供的线程 ID。
  • 定期检查线程状态:使用 info threads 确认线程的运行状态。
  • 保护原始寄存器值:如果需要修改寄存器值,建议记录原始值以便恢复。

怎么分析C++的core文件

分析 C++ 程序的 core 文件 是调试程序崩溃的重要手段。以下是分析核心转储文件(core 文件)的步骤和方法:


1. 什么是 core 文件?

  • core 文件 是程序崩溃时生成的内存快照,包含崩溃时的内存状态、栈帧信息、寄存器值等。
  • 可以使用调试器(如 GDB)加载 core 文件,分析程序崩溃的原因。

2. 生成 core 文件

  1. 确保系统允许生成 core 文件: 查看当前的 core 文件大小限制:

    1
    
    ulimit -c
    

    如果返回 0,表示 core 文件被禁用。

    启用 core 文件生成:

    1
    
    ulimit -c unlimited
    
  2. 设置 core 文件的生成路径(可选): Core 文件默认生成在当前目录,可以通过以下命令设置路径:

    1
    
    echo "/path/to/core_directory/core.%e.%p" | sudo tee /proc/sys/kernel/core_pattern
    

    其中:

    • %e:崩溃的程序名。
    • %p:进程 ID。
  3. 运行崩溃的程序: 当程序崩溃时(如发生段错误 Segmentation Fault),系统会生成 core 文件。


3. 使用 GDB 分析 core 文件

  1. 加载程序和 core 文件:

    1
    
    gdb ./your_program core
    
  2. 查看程序崩溃位置:

    • 打印崩溃时的栈帧:

      1
      
      (gdb) backtrace
      

      输出示例:

      1
      2
      3
      4
      
      #0  0x00007ffff7a33422 in raise () from /lib/x86_64-linux-gnu/libc.so.6
      #1  0x00007ffff7a3502a in abort () from /lib/x86_64-linux-gnu/libc.so.6
      #2  0x00005555555548c3 in crash_function() at main.cpp:15
      #3  0x00005555555548e1 in main() at main.cpp:20
      
    • 确认崩溃的函数和代码行(如 main.cpp:15)。

  3. 查看崩溃时的变量值:

    • 切换到崩溃栈帧:

      1
      
      (gdb) frame 0
      
    • 查看局部变量:

      1
      
      (gdb) info locals
      
    • 打印具体变量值:

      1
      
      (gdb) print variable_name
      
  4. 查看崩溃时的寄存器状态:

    1
    
    (gdb) info registers
    
  5. 查看内存地址: 如果崩溃涉及指针,可以查看指针指向的地址:

    1
    2
    
    (gdb) print *pointer
    (gdb) x/4xw 0x7fffffffdea0
    

4. 关键调试步骤

以下是分析 core 文件的核心命令和解释:

命令用途
bt or backtrace查看崩溃时的调用栈,定位问题函数及代码行。
frame <frame_number>切换到指定栈帧,分析某个函数的上下文。
info locals查看当前栈帧的局部变量值。
info args查看当前函数的参数值。
print <variable>打印变量的值(可以是局部变量、全局变量或表达式)。
info registers查看崩溃时的寄存器状态。
disassemble查看崩溃点的反汇编代码(适用于二进制文件)。

5. 示例:调试 C++ 程序 core 文件

假设程序如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <cstring>

void crash_function() {
    char *ptr = nullptr;
    strcpy(ptr, "This will crash");
}

int main() {
    crash_function();
    return 0;
}

编译程序:

1
g++ -g -o crash_program crash_program.cpp

运行程序:

1
2
./crash_program
Segmentation fault (core dumped)

加载 core 文件:

1
gdb ./crash_program core

分析崩溃:

  1. 查看调用栈:

    1
    
    (gdb) backtrace
    

    输出示例:

    1
    2
    3
    
    #0  0x00007ffff7a34422 in strcpy () from /lib/x86_64-linux-gnu/libc.so.6
    #1  0x00005555555548c3 in crash_function() at crash_program.cpp:6
    #2  0x00005555555548e1 in main() at crash_program.cpp:11
    
  2. 切换到崩溃点:

    1
    
    (gdb) frame 0
    
  3. 检查变量值:

    1
    2
    
    (gdb) print ptr
    $1 = 0x0
    

    确认 ptrnullptr,导致 strcpy 操作崩溃。


6. 结合源码定位问题

根据 GDB 的输出,回到代码中查看问题函数(crash_function),修复崩溃原因。


7. 注意事项

  1. 编译时添加调试信息: 使用 -g 编译选项生成包含调试信息的可执行文件。
  2. 匹配程序和 core 文件:
    • 确保 core 文件对应的程序未被修改,否则调试信息可能不准确。
    • 如果程序使用了动态库,确保调试时库版本与运行时一致。
  3. 启用内存调试工具: 如果问题复杂,可结合 ValgrindASan 检测内存访问和泄漏问题。

通过以上步骤,你可以高效分析 C++ 程序的 core 文件,定位崩溃的原因并修复问题。

GDB有哪些命令

GDB 是功能强大的调试工具,提供了大量命令来帮助开发者调试程序。以下是 GDB 中常用命令的分类及其用途:


程序启动与运行控制

命令用途
runr启动程序(可附加命令行参数和环境变量)。
start从程序的入口点(main)开始运行并暂停。
continuec继续执行程序直到遇到断点或结束。
kill停止正在运行的程序。
quitq退出 GDB。
steps单步执行代码,进入函数内部。
nextn单步执行代码,但跳过函数调用。
finish执行完当前函数的其余部分,并返回到调用者。
until执行到循环结束或指定行。

断点与观察点

命令用途
breakb在指定的位置设置断点(函数、文件行号或地址)。
tbreak设置临时断点(触发一次后自动删除)。
condition给断点添加触发条件(例如:condition 1 x > 5)。
info breakpoints查看所有断点的信息(编号、位置、状态)。
deleted删除指定断点(delete 1 删除编号为 1 的断点)。
disable禁用某个断点(保留断点,但不触发)。
enable启用某个断点。
watch设置观察点(监视变量值的变化)。
rwatch设置只读观察点(监视变量的读取操作)。
awatch设置访问观察点(监视变量的读写操作)。

调用栈与函数

命令用途
backtracebt显示当前调用栈信息。
framef切换到指定的栈帧(例如:frame 1)。
info frame显示当前栈帧的详细信息。
info args显示当前函数的参数值。
info locals显示当前栈帧的局部变量。
up切换到上一层栈帧(调用者)。
down切换到下一层栈帧(被调用者)。

变量与表达式

命令用途
printp打印变量值或表达式结果(例如:print x + y)。
set修改变量值(例如:set x = 10)。
display每次暂停时自动显示指定变量值(例如:display x)。
undisplay停止显示某个变量(例如:undisplay 1)。
whatis查看变量的类型(例如:whatis x)。
ptype查看复杂类型的定义(例如:结构体或类)。
x查看内存内容(例如:x/4xw address 显示 4 个 32 位字的内容)。

寄存器与内存

命令用途
info registers显示所有寄存器的当前值。
print $register打印指定寄存器的值(例如:print $eax)。
set $register=value修改寄存器的值(例如:set $rax = 42)。
x查看指定内存地址的内容(例如:x/10x 0x7fffffffdc00)。
x/format address以指定格式查看内存(格式:x 十六进制、d 十进制)。

线程调试

命令用途
info threads显示所有线程的信息(线程 ID 和状态)。
thread切换到指定线程(例如:thread 2)。
thread apply对指定线程执行命令(例如:thread apply all bt)。

共享库与动态链接

命令用途
info sharedlibrary查看程序加载的共享库信息。
sharedlibrary手动加载共享库的符号表。
set environment设置程序运行时的环境变量(例如:set env VAR value)。

程序文件与加载

命令用途
file加载调试的可执行文件(例如:file ./a.out)。
core加载 core 文件进行调试(例如:core ./core)。
symbol-file手动加载符号文件。
listl查看源码(例如:list main 显示 main 函数)。

反汇编与低级调试

命令用途
disassemble显示当前函数的反汇编代码。
stepi单步执行一条汇编指令。
nexti单步执行并跳过函数调用。

自动化与脚本

命令用途
commands给断点绑定自动执行的命令(例如:打印变量并继续运行)。
define定义自定义命令(例如:定义批量操作)。
source从文件中加载 GDB 命令脚本。

其他有用命令

命令用途
info查看各种调试信息(如:断点、线程、寄存器等)。
help获取命令的帮助信息(例如:help break)。
set logging将调试输出保存到文件(例如:set logging on)。

通过熟练掌握这些命令,你可以高效地使用 GDB 进行各种调试任务,从简单的变量检查到多线程调试和内存分析。

Linux下程序有问题,如何调试?

在 Linux 下调试程序问题,可以使用多种方法,具体选择取决于问题的类型(如崩溃、死锁、内存泄漏或逻辑错误)。以下是常见问题的调试步骤和工具:


1. 确认问题类型

(1)程序崩溃

  • 症状:程序退出,伴随 Segmentation FaultBus Error 等错误信息。
  • 解决工具GDBcore 文件

(2)程序卡死或性能问题

  • 症状:程序停止响应或运行缓慢。
  • 解决工具straceperfGDB

(3)内存泄漏或内存问题

  • 症状:程序内存占用异常增长或非法内存访问。
  • 解决工具ValgrindAddressSanitizer (ASan)

(4)逻辑错误

  • 症状:程序输出错误结果或行为异常。
  • 解决工具GDB、日志分析。

2. 常用调试方法

(1)调试崩溃问题

步骤:

  1. 编译时添加调试信息 确保程序使用 -g 编译选项生成调试信息:

    1
    
    gcc -g -o your_program your_program.c
    
  2. 运行程序并捕获崩溃 如果程序崩溃(Segmentation Fault),可能会生成 core 文件。

  3. 启用 core 文件生成

    • 确认 core 文件未被禁用:

      1
      
      ulimit -c unlimited
      
    • 如果生成 core 文件,使用 GDB 加载:

      1
      
      gdb ./your_program core
      
    • 在 GDB 中分析崩溃点:

      1
      2
      3
      
      (gdb) backtrace
      (gdb) frame 0
      (gdb) print variable_name
      
  4. 直接调试程序 使用 GDB 运行程序:

    1
    
    gdb ./your_program
    

    设置断点并运行:

    1
    2
    
    (gdb) break main
    (gdb) run
    

(2)调试卡死或性能问题

工具 1:strace

跟踪系统调用,检查程序是否被某些操作阻塞:

1
strace -p <PID>

输出示例:

1
futex(0x7ffe3a5a6c90, FUTEX_WAIT, 2, NULL
  • 如果程序卡在 futex 或文件操作,可以怀疑是线程同步或 IO 问题。
工具 2:GDB

使用 GDB 附加到卡死程序:

1
gdb ./your_program <PID>

在 GDB 中查看所有线程:

1
(gdb) info threads

切换到卡住的线程并查看栈帧:

1
2
(gdb) thread <thread_id>
(gdb) backtrace
工具 3:perf

使用 perf topperf record 分析性能瓶颈:

1
perf top

记录 CPU 消耗最多的函数。


(3)调试内存问题

工具 1:Valgrind

检测内存泄漏或非法访问:

1
valgrind --leak-check=full --track-origins=yes ./your_program

输出示例:

1
2
==12345== Invalid write of size 4
==12345==    at 0x4005F7: main (example.c:10)
  • 提供泄漏位置和调用栈。
工具 2:AddressSanitizer (ASan)

编译时启用 ASan:

1
gcc -fsanitize=address -g -o your_program your_program.c

运行程序后,ASan 会报告内存问题:

1
==12345==ERROR: AddressSanitizer: heap-buffer-overflow

(4)调试逻辑错误

工具 1:日志

通过增加日志打印调试信息:

1
printf("Value of x: %d\n", x);

重定向日志到文件以便分析:

1
./your_program > log.txt 2>&1
工具 2:GDB

使用 GDB 逐步调试,检查变量值:

1
2
3
4
5
6
gdb ./your_program
(gdb) break main
(gdb) run
(gdb) print variable_name
(gdb) next
(gdb) step
工具 3:动态调试(LD_PRELOAD

通过 LD_PRELOAD 插入自定义的动态库,拦截某些函数调用(如 malloc)以观察行为。


3. 实际调试示例

示例 1:调试崩溃问题

代码:

1
2
3
4
5
6
7
8
#include <stdio.h>
#include <string.h>

int main() {
    char *ptr = NULL;
    strcpy(ptr, "Crash!");  // 崩溃
    return 0;
}
调试步骤:
  1. 编译代码:

    1
    
    gcc -g -o crash_program crash_program.c
    
  2. 运行并崩溃:

    1
    2
    
    ./crash_program
    Segmentation fault (core dumped)
    
  3. 加载 core 文件:

    1
    
    gdb ./crash_program core
    

    在 GDB 中查看调用栈:

    1
    2
    3
    
    (gdb) backtrace
    #0  0x00007ffff7a34422 in strcpy () from /lib/x86_64-linux-gnu/libc.so.6
    #1  0x00005555555548c3 in main () at crash_program.c:6
    

    确认崩溃发生在 strcpy 调用处,分析原因是 ptrNULL


示例 2:调试内存泄漏

代码:

1
2
3
4
5
6
7
8
9
10
11
#include <stdlib.h>

void leak_memory() {
    int *arr = malloc(10 * sizeof(int));
    arr[0] = 42;  // 漏掉释放
}

int main() {
    leak_memory();
    return 0;
}
调试步骤:
  1. 运行 Valgrind:

    1
    
    valgrind --leak-check=full ./memory_leak
    

    输出示例:

    1
    2
    3
    4
    
    ==12345== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
    ==12345==    at 0x4C2F18F: malloc (vg_replace_malloc.c:309)
    ==12345==    by 0x4005F7: leak_memory (example.c:5)
    ==12345==    by 0x40060F: main (example.c:9)
    

    显示未释放的内存分配位置。


4. 调试工具总结

工具适用场景主要命令
GDB崩溃、逻辑错误、卡死runbacktraceinfo threads
Valgrind内存泄漏、非法访问valgrind --leak-check=full
strace系统调用、IO 问题strace -p <PID>
perf性能分析perf topperf record
ASan内存问题编译时使用 -fsanitize=address

通过结合这些工具和方法,可以有效调试 Linux 下的程序问题并快速定位问题原因。

本文由作者按照 CC BY 4.0 进行授权