GDB dòng lệnh cơ bản (03 – Cơ bản)


Trong bài số 02, ta đã nói đến những việc mà GDB có thể giúp chúng ta.

Về cơ bản GDB, có thể chạy để debug mọi chương trình, tuy nhiên nếu không muốn càng debug càng rối thì ta nên sử dụng tham số -g khi biên dịch để giúp quá trình debug xác định được vị trí mỗi đoạn binary trong source ban đầu.

Trong bài này, ta hãy cùng xem cách sử dụng thực tế sẽ như thế nào.
Giả sử ta có một source Hello world như sau:

.
├── include
│   ├── greetings_en.h
│   ├── greetings_ja.h
│   └── greetings_vi.h
└── src
├── greetings_en.c
├── greetings_ja.c
├── greetings_vi.c
├── mainapp
└── mainapp.c

Trong đó, các file có nội dung như sau:

// mainapp.c

#include
#include"greetings_en.h"
#include"greetings_ja.h"
#include"greetings_vi.h"

//
// Main functions
//
int main(int argc, char* argv[])
{
greeting_en();
greeting_ja();
greeting_vi();

return 0;
}

// greetings_en.h
void greeting_en();

// greetings_ja.h
void greeting_ja();

// greetings_vi.h
void greeting_vi();

// greetings_en.c
#include
void greeting_en()
{
printf("Hello world\n");
}

// greetings_ja.c
#include
void greeting_ja()
{
printf("Konnichiwa \n");
}

// greetings_vi.c
#include
void greeting_vi()
{
printf("Xin chao \n");
}

Lệnh biên dịch từ src:

oedev@OEDEV-Ubuntu:~/src$gcc -g -o mainapp mainapp.c -I../include greetings_*.c

Chú ý, để sử dụng GDB tốt nhất, ta nên thêm tham số -g (viết thường) như ở trên.
Kết quả sau khi chạy sẽ như thế này:

oedev@OEDEV-Ubuntu:~/src$./mainapp
Hello world
Konnichiwa
Xin chao

OK, đó là 1 chương trình đơn giản để làm ví dụ các phần bên dưới đây.

1. Chạy chương trình với GDB

-Ta chỉ cần thêm gdb vào đầu câu lệnh thông thường, ta sẽ thấy GDB được khởi động với giao diện sau:

oedev@OEDEV-Ubuntu:~/src$gdb ./mainapp
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./mainapp...done.
(gdb)

Ta thấy giao diện giao tiếp bằng dòng lệnh hiện ra, chương trình chính vẫn chưa được chạy.
Đến đây, hầu hết là thông tin về phiên bản, rồi cấu hình của GBD thôi. Nhưng có 1 dòng ta sẽ để ý thấy.
Đó là :

Reading symbols from ./mainapp…done.

Nếu ta biên dịch mà không có -g (như đã nói ở trên), ta sẽ nhận một dòng khác như sau:

Reading symbols from ./mainapp…(no debugging symbols found)…done.

Hiểu nôm na là, khi có -g, GDB sẽ load được các symbol (là tên các hàm sử dụng trong ứng dụng), còn không có thì nó không thể load được.

Rồi, ta mới chỉ thấy được rằng, GDB đã thực hiện xong các bước kiểm tra các symbol có trong file chạy và load các source code trong đường dẫn nó biết.

-Giờ ta bắt đầu chạy, nếu muốn GDB dừng lại ở hàm main()thì hãy dùng lệnh start, còn muốn chạy luôn không cần dừng thì hãy dùng run(viết ngắn là r).
Khi *start:

(gdb) start
Temporary breakpoint 1 at 0x400535: file mainapp.c, line 11.
Starting program: /home/oedev/code/debug_03/src/mainapp 

Temporary breakpoint 1, main (argc=1, argv=0x7fffffffdcb8) at mainapp.c:11
11      greeting_en();
(gdb) 

2. Breakpoint

-Ta chỉ có thể đặt khi chương trình chính không chạy (chưa chạy hoặc đang bị paused).
Mỗi breakpoint sẽ gắn với dòng tại 1 file nào đó.
Về cơ bản, ta sử dụng lệnh break (viết ngắn b) để đặt breakpoint, đưa 2 tham số là tên file và chỉ số dòng vào là ok.
Ví dụ, đặt breakpoint ở dòng 13 của mainapp.c:

(gdb) b mainapp.c:13
Breakpoint 2 at 0x400549: file mainapp.c, line 13.
(gdb) 

GDB cũng hỗ trợ đặt break theo tên hàm nữa.
Ví dụ, đặt breakpoint ở hàm greeting_en() trong file greetings_en.c:

(gdb) b greetings_en.c:greeting_en
Breakpoint 3 at 0x40055e: file greetings_en.c, line 4.

-Xem danh sách các breakpoint bằng info break:

(gdb) info break
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000000000400549 in main at mainapp.c:13
3       breakpoint     keep y   0x000000000040055e in greeting_en at greetings_en.c:4

Chú ý là, breakpoint đầu tiên ở đầu hàm main (do lệnh start) không được tính.

-Xóa một breakpoint
Ta cũng có thể dùng cách tương tự như đặt chỉ thay command break bằng clear là ok.
Nhưng có một cách đơn giản hơn.
Ta để ý thấy mỗi khi đặt một breakpoint mới, ta thấy các dòng kết quả:

Breakpoint 1..
Breakpoint 2..

Ta có thể xóa theo các tham số đó bằng lệnh del:

(gdb) info break
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000000000400549 in main at mainapp.c:13
3       breakpoint     keep y   0x000000000040055e in greeting_en at greetings_en.c:4
(gdb) del 3
(gdb) info break
Num     Type           Disp Enb Address            What
2       breakpoint     keep y   0x0000000000400549 in main at mainapp.c:13

Dù xóa breakpoint 3 đi thì số 3 đó cũng không được sử dụng lại để đánh số cho các breakpoint được đánh dấu sau này (trừ khi khởi động lại GDB).

3. Các thao tác debug khi chương trình dừng tại breakpoint

Sau khi đặt breakpoint, ta tiến hành chạy chương trình.
Vì chương trình đang dừng (paused), ta dùng lệnh continue (viết ngắn là c) để chạy tiếp. Chương trình sẽ dừng lại khi gặp breakpoint.
Sau khi dừng lại, nó sẽ có dạng như sau:

(gdb) c
Continuing.
Hello world
Konnichiwa 

Breakpoint 2, main (argc=1, argv=0x7fffffffdcb8) at mainapp.c:13
13      greeting_vi();
(gdb)

-Kiểm tra xem chương trình xem đã bị dừng ở đâu
Thực ra nhìn vào trạng thái dừng ở trên ta cũng biết được rằng, chương trình đang dừng ở mainapp.c, dòng 13, chỗ lời gọi hàm greeting_vi().
Có một cách trực quan hơn là lệnh layout (viết ngắn là l).

(gdb) l
8   //
9   int main(int argc, char* argv[])
10  {
11      greeting_en();
12      greeting_ja();
13      greeting_vi();
14  
15      return 0;
16  }

GDB sẽ hiển thị cả đoạn source xung quanh điểm đang bị dừng (còn giao diện trực quan hơn nữa sẽ được giới thiệu ở bài sau),

-Thực hiện dòng hiện tại (không nhảy sâu vào hàm nếu đang gọi hàm):
Lệnh next (viết ngắn là n)

(gdb) n
Xin chao 
15      return 0;

GDB sẽ dừng lại ở dòng tiếp theo, dòng 15 như ở trên (vì 14 không có gì)

-Thực hiện gọi sâu vào hàm
Giả sử đang dừng ở lời gọi greeting_vi(),
Sử dụng step, GDB sẽ chạy vào bên trong greeting_vi() thay vì thực hiện ngay toàn bộ xử lý trong nó giống như next.

(gdb) step
greeting_vi () at greetings_vi.c:4
4       printf("Xin chao \n");
(gdb) l
1   #include
2   void greeting_vi()
3   {
4       printf("Xin chao \n");
5   

4. Xem giá trị khi chương trình dừng tại breakpoint

Trước khi đi vào phần này, ta sửa lại nội dung file mainapp.c ở trên thành như sau:

#include 
#include 
#include 
#include"greetings_en.h"
#include"greetings_ja.h"
#include"greetings_vi.h"

typedef struct _currency_
{
    char iso4217Code[5];
    char name[255];
}ST_CURRENCY;

typedef struct _country_
{
    char capital[255];
    float area;
    uint32_t population;
    float density;
    ST_CURRENCY currency;
    int16_t timezone;
    char dateFormat[100];
}ST_COUNTRY;

void printCountry(void *data)
{
    ST_COUNTRY *country = (ST_COUNTRY*)data;

    printf("Capital :%s\n",country->capital);
    printf("Area :%.2f km2\n",country->area);
    printf("Density :%.2f /km2\n",country->density);
    printf("Currency : %s (%s) \n",country->currency.iso4217Code, country->currency.name);
    printf("Timezone : GMT + %d \n",country->timezone);
    printf("Dateformat :%s\n",country->dateFormat);
}
//
// Main functions
//
int main(int argc, char* argv[])
{
    greeting_en();
    greeting_ja();
    greeting_vi();

    /*Vietnam*/
    ST_COUNTRY vietnam;
    memset(&vietnam, 0x00, sizeof(england));

    strcpy(vietnam.capital,"Hanoi");
    vietnam.area = 332698;  // km2
    vietnam.population = 91700000;
    strcpy(vietnam.currency.iso4217Code,"VND");
    strcpy(vietnam.currency.name,"Dong");
    vietnam.timezone = +7;
    vietnam.density = 276.03;
    strcpy(vietnam.dateFormat,"dd/mm/yyyy");

    printCountry(&vietnam);

    return 0;
}

Ta tiến hành chạy với GDB như trên và dừng ở lời gọi hàm printCountry(&vietnam)

-Sử dụng print (viết ngắn là p)
Ta lần lượt sẽ hiển thị giá trị nguyên, thực, chuỗi, cấu trúc như sau:

Breakpoint 1, main (argc=1, argv=0x7fffffffdcb8) at mainapp.c:58
58      printCountry(&vietnam);
(gdb) l
53      strcpy(vietnam.currency.name,"Dong");
54      vietnam.timezone = +7;
55      vietnam.density = 276.03;
56      strcpy(vietnam.dateFormat,"dd/mm/yyyy");
57      
58      printCountry(&vietnam);
59      
60      return 0;
61  }
(gdb) p vietnam.population
$1 = 91700000
(gdb) p vietnam.density
$2 = 276.029999
(gdb) p vietnam.capital
$3 = "Hanoi", '\000' 
(gdb) p vietnam
$4 = {capital = "Hanoi", '\000' , area = 332698, 
  population = 91700000, density = 276.029999, currency = {
    iso4217Code = "VND\000", name = "Dong", '\000' }, 
  timezone = 7, dateFormat = "dd/mm/yyyy", '\000' }
(gdb)

-Hiển thị giá trị biến cấu trúc đẹp hơn
Bật chế độ in đẹp bằng set print pretty on, rồi thực hiện in như bình thường.

(gdb) p vietnam
$4 = {capital = "Hanoi", '\000' , area = 332698, 
  population = 91700000, density = 276.029999, currency = {
    iso4217Code = "VND\000", name = "Dong", '\000' }, 
  timezone = 7, dateFormat = "dd/mm/yyyy", '\000' }
(gdb) set print pretty on
(gdb) p vietnam
$5 = {
  capital = "Hanoi", '\000' , 
  area = 332698, 
  population = 91700000, 
  density = 276.029999, 
  currency = {
    iso4217Code = "VND\000", 
    name = "Dong", '\000' 
  }, 
  timezone = 7, 
  dateFormat = "dd/mm/yyyy", '\000' 
}

Ta có thể thấy rõ sự khác bọt giữa khi pretty là off/on.

-Hiển thị giá trị con trỏ có ép kiểu
Đặt breakpoint ở hàm printCountry, sau khi dừng ta sẽ có như sau:

Breakpoint 2, printCountry (data=0x7fffffffd950) at mainapp.c:27
27      ST_COUNTRY *country = (ST_COUNTRY*)data;
(gdb) p data
$6 = (void *) 0x7fffffffd950

Tham số data được truyền vào là kiểu void. Nên khi ta dùng lệnh print thì chỉ thấy được địa chỉ con trỏ thôi. Ta có thể ép kiểu bất cứ kiểu nào.
Nhưng vì biết đó là kiểu con trỏ của ST_COUNTRY rồi, ta có thể làm như sau:

(gdb) p (*(ST_COUNTRY*)data)
$7 = {
  capital = "Hanoi", '\000' , 
  area = 332698, 
  population = 91700000, 
  density = 276.029999, 
  currency = {
    iso4217Code = "VND\000", 
    name = "Dong", '\000' 
  }, 
  timezone = 7, 
  dateFormat = "dd/mm/yyyy", '\000' 
}

-Hiển thị mảng
Ở trên ta đã thấy chuỗi được hiển thị rồi, chuỗi cũng là mảng nên tả hãy thử hiện thị độ dài bất kì của mảng đó xem sao, cách làm phổ biến như sau:

(gdb) x/10xb country->capital 
0x7fffffffd950: 0x48    0x61    0x6e    0x6f    0x69    0x00    0x00    0x00
0x7fffffffd958: 0x00    0x00

Câu lệnh trên sẽ hiển thị 10 bytes (b cuối cùng là byte) đầu tiên của mảng country->capital, ở dang cơ số 16(chính là x sau số 10).

5. Kiểm tra stacktrace

-backtrace (viết ngắn là bt) được sử dụng để cho biết luồng xử lý đi như thế nào để đến được điểm dang dừng lại, ta có thể sử dụng thêm bt full để thấy các lời gọi ở vị trí nào trong code.

(gdb) bt
#0  printCountry (data=0x7fffffffd950) at mainapp.c:29
#1  0x00000000004007ef in main (argc=1, argv=0x7fffffffdcb8) at mainapp.c:58
(gdb) bt full
#0  printCountry (data=0x7fffffffd950) at mainapp.c:29
        country = 0x7fffffffd950
#1  0x00000000004007ef in main (argc=1, argv=0x7fffffffdcb8) at mainapp.c:58
        vietnam = {
          capital = "Hanoi", '\000' , 
          area = 332698, 
          population = 91700000, 
          density = 276.029999, 
          currency = {
            iso4217Code = "VND\000", 
            name = "Dong", '\000' 
          }, 
          timezone = 7, 
          dateFormat = "dd/mm/yyyy", '\000' 
        }
Advertisements

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất / Thay đổi )

Connecting to %s