Bỏ qua điều hướng, tới nội dung chính
Học C
Bài 35.122 phút đọc

Biên dịch để gỡ lỗi và khởi động GDB

Sau bài này bạn sẽ làm được

  • Dịch với thông tin gỡ lỗi đầy đủ
  • Giải thích vì sao gỡ lỗi ở -O2 rất khó
  • Nạp chương trình có tham số dòng lệnh
  • Gắn GDB vào tiến trình đang chạy và vào core dump

GDB không đoán được gì cho bạn. Nó chỉ trả lời câu hỏi bạn đặt, nên giá trị của nó phụ thuộc vào việc bạn đã có giả thuyết hay chưa. Bài này lo phần chuẩn bị: dịch đúng cách và mở đúng cách.

#Ba cờ dịch

gcc -g main.c -o main            # thong tin go loi co ban
gcc -g3 main.c -o main           # them ca macro, xem duoc #define trong gdb
gcc -g3 -O0 main.c -o main       # khuyen dung: khong toi uu gi ca
gcc -ggdb3 -O0 main.c -o main    # dinh dang rieng cua GDB, chi tiet hon
CờThêm gìNên dùng
-gSố dòng, tên biến, tên hàm, kiểuTối thiểu
-g3Thêm định nghĩa macroNên dùng, kích thước lớn hơn chút
-ggdb3Định dạng riêng của GDB, chi tiết nhấtKhi chỉ dùng GDB
-O0Tắt mọi tối ưuBắt buộc khi gỡ lỗi
-fno-omit-frame-pointerGiữ con trỏ khung, cho backtrace tin cậyNên bật
-fno-inlineKhông nội tuyến hàmKhi cần bước vào hàm nhỏ

#Vì sao gỡ lỗi ở -O2 rất khó

Gỡ lỗi ở -O2
$ gcc -g -O2 -o prog prog.c
$ gdb ./prog

(gdb) print x
$1 = <optimized out>

(gdb) next
main () at prog.c:12
(gdb) next
main () at prog.c:8            <- nhay NGUOC?

(gdb) break tinh
(gdb) run
                               <- khong bao gio dung: ham da bi noi tuyen

(gdb) bt
#0  main ()                    <- mat ca chuoi goi ham
Gỡ lỗi ở -O0
$ gcc -g3 -O0 -o prog prog.c
$ gdb ./prog

(gdb) print x
$1 = 42

(gdb) next
main () at prog.c:9            <- di theo dung thu tu ma nguon

(gdb) break tinh
(gdb) run
Breakpoint 1, tinh (a=3, b=4) at prog.c:5

(gdb) bt
#0  tinh (a=3, b=4) at prog.c:5
#1  main () at prog.c:12

#Năm cách nạp chương trình

# 1. Binh thuong
gdb ./main

# 2. Co tham so dong lenh
gdb --args ./main tep.txt --dai-dong

# Hoac dat sau khi vao gdb:
(gdb) set args tep.txt --dai-dong
(gdb) show args

# 3. Gan vao tien trinh dang chay
gdb -p 12345
gdb ./main 12345

# 4. Phan tich core dump
gdb ./main core
gdb ./main /var/lib/systemd/coredump/core.main.1000.xxx

# 5. Go loi tu xa, dung cho he nhung
gdbserver :1234 ./main          # tren may dich
gdb ./main                      # tren may cua ban
(gdb) target remote 192.168.1.5:1234

#Giao diện TUI

gdb -tui ./main                  # bat ngay tu dau

# Hoac trong gdb:
(gdb) layout src                 # cua so ma nguon
(gdb) layout asm                 # cua so hop ngu
(gdb) layout split               # ca hai
(gdb) layout regs                # them cua so thanh ghi
(gdb) tui disable                # tat

# Phim tat khi dang o TUI:
#   Ctrl-x a      bat tat TUI
#   Ctrl-x 1      mot cua so
#   Ctrl-x 2      hai cua so
#   Ctrl-l        ve lai man hinh khi bi loan
#   Ctrl-p, Ctrl-n   lich su lenh, vi mui ten dieu khien cuon ma nguon
Tệp .gdbinit tối thiểu
# Dat o ~/.gdbinit de ap dung cho moi phien

set print pretty on            # in struct xuong dong, de doc
set print array on             # in mang xuong dong
set print array-indexes on     # kem chi so: [0] = 5, [1] = 3
set pagination off             # khong dung lai hoi "Press enter"
set history save on            # nho lenh giua cac phien
set history size 10000
set confirm off                # khong hoi lai khi quit hay delete

# Bai 35.6 se noi ky ve tep nay, ke ca lenh tu dinh nghia.

#Phiên làm việc đầu tiên

dslk.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Nut {
    int         ma;
    char        ten[16];
    struct Nut *sau;
} Nut;

static Nut *them(Nut *dau, int ma, const char *ten) {
    Nut *n = malloc(sizeof *n);

    n->ma = ma;
    strncpy(n->ten, ten, sizeof n->ten - 1);
    n->ten[sizeof n->ten - 1] = '\0';
    n->sau = dau;

    return n;
}

static int tong(const Nut *dau) {
    int t = 0;

    for (const Nut *p = dau; p != NULL; p = p->sau)
        t += p->ma;

    return t;
}

int main(void) {
    Nut *dau = NULL;

    dau = them(dau, 10, "mot");
    dau = them(dau, 20, "hai");
    dau = them(dau, 30, "ba");

    int a[5] = { 5, 3, 9, 1, 7 };
    int t = tong(dau);

    printf("tong = %d\n", t);
    printf("a[2] = %d\n", a[2]);

    return 0;
}
terminal
gcc -std=c11 -g3 -O0 -o dslk.exe dslk.c
# Tệp lệnh g1.gdb: break tong, run, bt, info args, print *dau
gdb -q -batch -x g1.gdb ./dslk.exe
Breakpoint 1 at 0x4015be: file dslk.c, line 23.

Thread 1 hit Breakpoint 1, tong (dau=0x752830) at dslk.c:23
23	    int t = 0;
#0  tong (dau=0x752830) at dslk.c:23
#1  0x000000000040168c in main () at dslk.c:39
dau = 0x752830
$1 = {ma = 30, ten = "ba", '\000' <repeats 13 times>, sau = 0x7527e0}

Tự làm thử

  1. Dịch cùng một chương trình với -g và -g3, so kích thước tệp nhị phân.
  2. Thử print một macro với -g rồi với -g3.
  3. Dịch ở -O2 và đếm xem bao nhiêu biến hiện <optimized out>.
  4. Dịch ở -Og và so với cả -O0 lẫn -O2.
  5. Chạy một chương trình vòng lặp vô tận rồi gắn GDB vào nó bằng -p.
  6. Viết một tệp .gdbinit và xác nhận nó được nạp.

Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.

Tóm tắt

  • -g3 cho xem cả macro, còn -g chỉ cho số dòng và tên biến.
  • -g không làm chương trình chậm đi, chỉ -O0 mới làm, và đó là hai cờ riêng biệt.
  • Ở -O2 thì biến biến mất, thứ tự dòng bị đảo, hàm bị nội tuyến và backtrace ngắn đi.
  • gdb -p gắn vào tiến trình đang chạy, và thread apply all bt là ba lệnh giải quyết phần lớn khóa chéo.
  • gdb -q -batch -x tep.gdb chạy một chuỗi lệnh không tương tác, rất tiện để lặp lại và để chia sẻ.