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

Gỡ lỗi con trỏ và cấu trúc dữ liệu

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

  • Đi theo chuỗi con trỏ nhiều tầng bằng một lệnh print
  • Duyệt danh sách liên kết bằng biến tiện dụng và while
  • Xem một struct dưới dạng byte thô để phát hiện đệm
  • Nhận ra con trỏ hỏng từ giá trị của nó

Con trỏ là chỗ GDB có ích nhất, vì đây đúng là thứ printf không giúp được: bạn không in được một cấu trúc dữ liệu bằng cách rắc thêm dòng in, nhưng bạn duyệt được nó bằng ba lệnh GDB.

#Đi theo chuỗi con trỏ

terminal
# break tong, run, bt, info args, rồi đi theo chuỗi sau
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}
$2 = "ba", '\000' <repeats 13 times>
$3 = "hai", '\000' <repeats 12 times>
$4 = "mot", '\000' <repeats 12 times>
$5 = (struct Nut *) 0x0
(gdb) print *dau
$1 = {ma = 30, ten = "ba", '\000' <repeats 13 times>, sau = 0x7527e0}

(gdb) print dau->ten
$2 = "ba", '\000' <repeats 13 times>

(gdb) print dau->sau->ten
$3 = "hai", '\000' <repeats 12 times>

(gdb) print dau->sau->sau->ten
$4 = "mot", '\000' <repeats 12 times>

(gdb) print dau->sau->sau->sau
$5 = (struct Nut *) 0x0                  /* het danh sach */

#Duyệt danh sách bằng vòng lặp

Vòng lặp trong GDB
(gdb) set $p = dau
(gdb) while $p != 0
 >print $p->ma
 >print $p->ten
 >set $p = $p->sau
 >end

$1 = 30
$2 = "ba", '\000' <repeats 13 times>
$3 = 20
$4 = "hai", '\000' <repeats 12 times>
$5 = 10
$6 = "mot", '\000' <repeats 12 times>
# Dong thanh mot lenh tu dinh nghia, dat trong .gdbinit

define in_ds
  set $p = $arg0
  set $i = 0
  while $p != 0
    printf "[%d] ma=%-4d ten=%s\n", $i, $p->ma, $p->ten
    set $p = $p->sau
    set $i = $i + 1
  end
end

document in_ds
In toan bo danh sach lien ket bat dau tu nut duoc truyen vao.
Vi du: in_ds dau
end

# Dung:
(gdb) in_ds dau
(gdb) help in_ds

# Bai 35.6 se noi ky ve .gdbinit va lenh tu dinh nghia.

#Nhận ra con trỏ hỏng

Giá trị con trỏNghĩa gần như chắc chắn
0x0NULL, chưa gán hoặc hàm trả về NULL
0x1 tới 0xfffNULL cộng một độ dời, tức p->truong với p là NULL
0xffffffffffffffffTrả về -1 bị ép thành con trỏ
0xdeadbeef, 0xbaadf00dMẫu đánh dấu do bộ cấp phát gỡ lỗi ghi vào
0xcdcdcdcdBộ nhớ heap chưa khởi tạo, quy ước của MSVC
0xfeeefeeeBộ nhớ heap đã giải phóng, quy ước của MSVC
Giá trị trông như văn bản ASCIIBộ đệm bị ghi đè lên con trỏ
Địa chỉ hợp lý nhưng dữ liệu là rácDùng sau khi giải phóng
# Ba lenh kiem tra mot con tro dang nghi ngo

(gdb) print p
$1 = (Nut *) 0x752830

(gdb) print *p                    # co doc duoc khong?
$2 = {ma = 30, ten = "ba", ...}   # doc duoc, va du lieu hop ly

(gdb) x/32xb p                    # xem byte tho
0x752830:  0x1e 0x00 0x00 0x00 0x62 0x61 0x00 0x00
...

(gdb) info symbol p               # p co tro vao mot ky hieu biet ten khong?
No symbol matches p.              # binh thuong voi bo nho heap

(gdb) info proc mappings          # p nam trong vung nho nao?

#Đi lại giữa các khung

Khung 0 là nơi lỗi lộ ra. Nguyên nhân thường nằm ở một khung phía dưới.
terminal
gdb -q -batch -x g8.gdb ./buoc.exe
Breakpoint 1 at 0x40155b: file buoc.c, line 4.

Thread 1 hit Breakpoint 1, binh_phuong (x=3) at buoc.c:4
4	    int t = x * x;
#0  binh_phuong (x=3) at buoc.c:4
#1  0x0000000000401584 in tinh (a=3, b=4) at buoc.c:10
#2  0x00000000004015be in main () at buoc.c:17
bt                    # ca chuoi goi
bt 5                  # chi 5 khung tren cung
bt -5                 # chi 5 khung duoi cung
bt full               # kem bien cuc bo cua moi khung

frame 1               # chuyen sang khung 1     (viet tat: f 1)
up                    # xuong duoi mot bac, tuc ve phia ham goi
down                  # len tren mot bac
frame                 # in khung hien tai

info frame            # dia chi tra ve, con tro khung, thanh ghi da luu

#Xem struct dưới dạng byte thô

terminal
gdb -q -batch -x g2.gdb ./dslk.exe
0x61fe30:	5	3	9	1
0x61fe40:	7
0x61fe30:	0x05	0x00	0x00	0x00	0x03	0x00	0x00	0x00
0x61fe38:	0x09	0x00	0x00	0x00	0x01	0x00	0x00	0x00
0x61fe40:	0x07	0x00	0x00	0x00
0xb82834:	"ba"
(gdb) print sizeof(Nut)
$1 = 32

(gdb) x/32xb dau              /* 32 byte tho cua mot nut */
(gdb) x/8xw dau               /* cung vung, doc theo word 4 byte */
(gdb) x/4xg dau               /* cung vung, doc theo o 8 byte */

/* Ba cach nhin cung mot vung nho. Dung khi:
     - kiem tra phan dem cua struct, doi chieu voi ptype/o
     - doi chieu voi mot dinh dang tep nhi phan
     - tim mot mau byte bi ghi de */

Tự làm thử

  1. Đi theo chuỗi dau->sau->sau cho tới khi gặp NULL.
  2. Viết vòng lặp while trong GDB in cả danh sách.
  3. Đóng nó thành lệnh define in_ds và thêm document.
  4. Viết lệnh phát hiện vòng lặp bằng hai con trỏ và thử trên một danh sách có vòng.
  5. Thử print a ở khung 0 rồi frame 1 và thử lại.
  6. Dùng x/20xb trên một mảng int để xác định thứ tự byte.

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

  • print dau->sau->sau->ten đi qua nhiều tầng con trỏ trong một lệnh.
  • Vòng lặp while với biến tiện dụng $p duyệt được bất kỳ cấu trúc dữ liệu nào mà không phải dịch lại.
  • Con trỏ hỏng có dấu hiệu nhận biết: giá trị rất nhỏ, mẫu đánh dấu như 0xdeadbeef, hoặc trông như văn bản ASCII.
  • print chỉ thấy biến của khung hiện tại, nên phải frame n trước, hoặc viết ten_ham::ten_bien.
  • Đọc backtrace từ trên xuống và dừng ở khung đầu tiên thuộc mã của bạn.