【GDB】Common operations

GDB common operations

log

(gdb) set logging file test.log # 设置 log 文件名,
(gdb) set logging enabled on # 打开 log
(gdb) info b # 实际操作,显示断点
(gdb) set logging enabled off # 关闭 log
(gdb) quit # 退出 gdb 调试

log file example

Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000001200 in main at test.c:20
        i locals
        i args
2       breakpoint     keep y   0x000000000000117b in binary_search at test.c:5
	stop only if target == 5
        i locals
        i args

debug

There are generally two methods for code debugging: printf and debug.

print is simpler and more crude than using the debugger to set breakpoints, and sometimes it can even be more useful. However, debugger has three advantages over print:

  • No need to recompile

  • Variables can be changed while debugging

  • Debugger can perform complex operations that print cannot.

session/history/command file

Usually we only start gdb when there is a problem with the program, start debugging, and exit after debugging is completed. However, it might be better to leave gdb open all the time. Every veteran gdb driver knows that gdb will load the latest version of the current program when running r. In other words, even if you do not exit gdb, the latest version will be run every time. Not exiting the current debugging session has two benefits:

  • Debugging context can be preserved. No need to reset breakpoints every time you run.

  • Once the core dump is obtained, the location of the core dump can be displayed without the need to reboot with the core.

When developing a C/C++ project, the general workflow is: an editor is open in one window, and compilation is also performed in this window; gdb is opened in another window, and this window is also used to run the program. Once it's time to debug (or segment fault again), you can start working on it.

Of course, after a long day of work, you always need to turn off your computer and go home. At this time, you can only exit gdb. Don't want to set breakpoints again tomorrow morning? gdb provides the function of retaining breakpoints. Enter save br .gdb_bp, gdb will save the breakpoint of this session in .gdb_bp. When I come back tomorrow morning, when I start gdb, I add the command - x .gdb_bpto let gdb treat . gdb_bp as a command file and re-execute it one by one. Everything will be back to last night.

.gdb_bpThe content of the file is as follows

break /home/tyustli/code/c_project/test.c:5
break /home/tyustli/code/c_project/test.c:6
break /home/tyustli/code/c_project/test.c:7

In fact, the gdb command is saved, and the command is automatically added to the debugging when loading.

watch

The dichotomy code is as follows

#include <stdio.h>

int binary_search(int *ary, unsigned int ceiling, int target)
{
    
    
    unsigned int floor = 0;
    while (ceiling> floor)
    {
    
    
        unsigned int pivot = (ceiling + floor) / 2;
        if (ary[pivot] < target)
            floor = pivot + 1;
        else if (ary[pivot] > target)
            ceiling = pivot - 1; /* 正确的代码应该为: ceiling = pivot */
        else
            return pivot;
    }
    return -1;
}

int main(int argc, char *argv)
{
    
    
    int a[] = {
    
    1, 2, 4, 5, 6};
    printf("%d\r\n", binary_search(a, 5, 7)); /* -1 */
    printf("%d\r\n", binary_search(a, 5, 6)); /* 4 */
    printf("%d\r\n", binary_search(a, 5, 5)); /* 期望 3,实际运行结果是 - 1 */

    return 0;
}

Compile and run gdb debugging

gcc -g test.c
gdb a.out

I plan to debug binary_search(a, 5, 5)this combination. If you use the print method, insert a few prints into binary_search and take a quick look after running to see what the running flow looks like when target=5.

The debugger method seems a little more complicated. If you insert a breakpoint in binary_search, you can only skip the first two calls by pressing c. It's actually not that complicated, gdb allows users to set conditional breakpoints. You can set it like this:

b binary_search if target == 5

Now only the third call will trigger the breakpoint.

The problem seems to be related to changes in floor and ceiling values. To observe their values, p floor and p ceiling. But there is a simple way, you can set watch breakpoints on them: wa floor if target == 5. When the value of floor changes, the breakpoint will be triggered.

For our sample program, the changes in these two values ​​can be calculated by just relying on imagination. Setting breakpoints specifically seems like a big deal. However, watch breakpoints are very useful when debugging real programs, especially when you are unfamiliar with the relevant code. Using watch breakpoints can better help you understand the program flow, and sometimes there may even be unexpected surprises. In addition, combined with the debugger's ability to modify values ​​during runtime, you can set the target value the next moment the value changes and observe whether similar problems will occur if you take different paths. If necessary, you can also set a breakpoint for a certain memory address: wa *0x7fffffffda40.

In addition to watch, gdb also has a type of catch breakpoint, which can be used to catch exceptions/system calls/signals. Because it is not very useful (I have never actually used it), I will not introduce it. If you are interested, take a look at help catch in gdb.

command

Refer to GDB command command

define

Refer to GDB custom commands

Several GDB built-in variables

$argc  自定义命令的参数个数
$arg0  自定义命令的第一个参数
$arg1  自定义命令的第二个参数
$arg2  自定义命令的第三个参数
$argN  自定义命令的第 N+1 个参数

Give a practical example. As the source code changes, we need to update the breakpoint location. The usual approach is to delete the original breakpoint and set a new one. Let's learn it now and use macros to combine these two steps into one step:

# gdb_macro
define mv
    if $argc == 2
        delete $arg0
        # 注意新创建的断点编号和被删除断点的编号不同
        break $arg1
    else
        print "输入参数数目不对,help mv 以获得用法"
    end
end

# (gdb) help mv 会输出以下帮助文档
document mv
Move breakpoint.
Usage: mv old_breakpoint_num new_breakpoint
Example:
    (gdb) mv 1 binary_search -- move breakpoint 1 to `b binary_search`
end
# vi:set ft=gdb ts=4 sw=4 et

Place the above command .gdbinitin a file to start debugging.
Before using mvthe command, check the breakpoints

info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000001200 in main at test.c:20
        i locals
        i args
2       breakpoint     keep y   0x000000000000117b in binary_search at test.c:5
	stop only if target == 5
        i locals
        i args
Breakpoint 3 at 0x117b: file test.c, line 5.

usemv

mv 1 5
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x000000000000117b in binary_search at test.c:5
	stop only if target == 5
        i locals
        i args
3       breakpoint     keep y   0x000000000000117b in binary_search at test.c:5

Accounting

Imagine the following scenario: a project shows signs of a memory leak. The memory pool allocated in advance becomes full as it is used, swallowing up the system's memory again and again. Memory management is implemented by itself, so it cannot be analyzed with valgrind. Since the memory management code has not been changed in recent versions, I guess someone in the business logic code borrowed the memory and did not return it. Now you need to dig it out. One way is to add a log to the allocation and release of memory, compile it, and then rerun the program to try to reproduce the memory leak scenario. But a faster way is to type this piece of code:

Assume that the interface for allocating memory is my_malloc(size_t size) and the interface for releasing memory is my_free(char *p)

b my_malloc
comm
printf "my_malloc %lu\n", size
end

b my_free
comm
printf "my_free 0x%x\n", p
end

c source code is as follows


void * my_malloc(size_t size)
{
    
    
    return malloc(size);
}

void my_free(void *p)
{
    
    
    free(p);
}

int main(int argc, char *argv)
{
    
    
    void *p1 = my_malloc(6);
    void *p2 = my_malloc(7);
    void *p3 = my_malloc(8);
    void *p4 = my_malloc(9);

    my_free(p1);
    my_free(p2);
    my_free(p3);

    return 0;
}

After starting debugging, save the log file to. test.logThe
final logs related to malloc and free are as follows

Breakpoint 3, my_malloc (size=6) at test.c:23
my_malloc 6
0x0000555555555247 in main (argc=1, argv=0x7fffffffe0a8 "D\343\377\377\377\177") at test.c:33
Value returned is $2 = (void *) 0x5555555592a0
Continuing.

Breakpoint 3, my_malloc (size=7) at test.c:23
my_malloc 7
0x0000555555555255 in main (argc=1, argv=0x7fffffffe0a8 "D\343\377\377\377\177") at test.c:34
Value returned is $3 = (void *) 0x5555555592c0
Continuing.

Breakpoint 3, my_malloc (size=8) at test.c:23
my_malloc 8
0x0000555555555263 in main (argc=1, argv=0x7fffffffe0a8 "D\343\377\377\377\177") at test.c:35
Value returned is $4 = (void *) 0x5555555592e0
Continuing.

Breakpoint 3, my_malloc (size=9) at test.c:23
my_malloc 9
0x0000555555555271 in main (argc=1, argv=0x7fffffffe0a8 "D\343\377\377\377\177") at test.c:36
Value returned is $5 = (void *) 0x555555559300
Continuing.

Breakpoint 4, my_free (p=0x5555555592a0) at test.c:28
my_free 0x555592a0
Continuing.

Breakpoint 4, my_free (p=0x5555555592c0) at test.c:28
my_free 0x555592c0
Continuing.

Breakpoint 4, my_free (p=0x5555555592e0) at test.c:28
my_free 0x555592e0
Continuing.

.gdbinitdocument

layout src

b main
b binary_search if target == 5

# 断点 1 触发执行的命令
command 1
i locals
i args
end

# 断点 2 触发执行的命令
comm 2
i locals
i args
end

# 自定义一个 print-tyustli 命令
define print-tyustli
    echo hello, world\n
end

# 自定义命令 print-tyustli 的帮助文档
document print-tyustli
    usage: print-list LIST NODE_TYPE NEXT_FIELD [COUNT]
    打印 tyustli

    data:   2023-09-27
    author: tyustli
end

# gdb_macro
define mv
    if $argc == 2
        delete $arg0
        # 注意新创建的断点编号和被删除断点的编号不同
        break $arg1
    else
        print "输入参数数目不对,help mv以获得用法"
    end
end

# (gdb) help mv 会输出以下帮助文档
document mv
Move breakpoint.
Usage: mv old_breakpoint_num new_breakpoint
Example:
    (gdb) mv 1 binary_search -- move breakpoint 1 to `b binary_search`

end
# vi:set ft=gdb ts=4 sw=4 et

b my_malloc
comm
printf "my_malloc %lu\n", size
finish # 查看函数 malloc 返回的指针
end

b my_free
comm
printf "my_free 0x%x\n", p
end

reference

https://segmentfault.com/a/1190000005367875

Guess you like

Origin blog.csdn.net/tyustli/article/details/133419055