Ở các phần trước, chúng ta đã tốn không ít mồ hôi công sức để rèn nên một thanh “Ỷ Thiên Kiếm” (cài đặt thành công OpenOCD) và học được bộ “Cửu Âm Chân Kinh” (hiểu rõ bản chất JTAG). Giờ là lúc thanh gươm báu được tuốt ra khỏi vỏ.
Hôm nay, đại ca sẽ không nói lý thuyết nữa, mà sẽ kể lại một màn “truyền công” thực tế cho một chú em “gà mờ”. Qua đó, các chú sẽ thấy được những đường GDB cơ bản nhất được “múa” trên một bài toán kinh điển: làm cho con LED “lên bờ xuống ruộng”.
[MEME: Một nhân vật võ hiệp đang múa kiếm cực ngầu]
Màn 1: Dựng “Võ Đài” & Sai Lầm Ngớ Ngẩn
Chuyện bắt đầu khi tôi quăng cho chú em một cái task “dễ như ăn cháo”: “Ê, tạo cho anh một project Blinky trên con ESP32-S3 đi, rồi dùng JTAG để debug xem nó chạy thế nào”. Chú em hí hửng nhận lời, một lúc sau nó vứt cho tôi một “đống” và hồn nhiên hỏi: “Đại ca, em làm y hệt trên mạng, sao nó không chạy?”.
Tôi liếc mắt qua, và đây là “kiệt tác” của nó.
Dựng “Võ Đài” (Các file cấu hình)
Chú em cũng biết tạo file, nhưng chắc là chỉ copy paste thôi chứ không hiểu gì.
prj.conf:
# Bật GPIO và Logging
CONFIG_GPIO=y
CONFIG_LOG=y
boards/esp32s3_devkitc.overlay:
/ {
aliases {
led0 = &my_led;
};
};
&gpio0 {
my_led: led_0 {
gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>;
label = "User LED";
};
};
“Hạt Sạn” Trong Bát Cơm - src/main.c
Và đây, “tuyệt phẩm” main.c mà chú em nó chép được:
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
// Lỗi sai #2: Lồng ALIAS vào thẳng macro, một sự sáng tạo đi vào lòng đất
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0));
// Lỗi sai #1: Không "khai báo hộ khẩu" cho module
// LOG_MODULE_REGISTER(main, LOG_LEVEL_DBG);
void main(void)
{
int ret;
// Lỗi sai #3: Dùng hàm is_ready() sai phiên bản
if (!gpio_is_ready(led.port)) {
// LOG_ERR(...)
return;
}
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
return;
}
while (1) {
ret = gpio_pin_toggle_dt(&led);
if (ret < 0) {
return;
}
k_msleep(1000);
}
}
Màn 2: Đại Ca Ra Tay “Chẩn Bệnh”
Nhìn vào “thảm họa” này, tôi chỉ muốn hét lên: “Trời ơi, nó là một thằng ngốc!”. Nhưng thôi, với vai trò là một người thầy, tôi phải kiên nhẫn.
[MEME: Any meme thể hiện sự bất lực, facepalm]
Đại ca: “Chú em, lại đây anh bảo. Code của chú có 3 ‘tử huyệt’, y hệt như 3 cái huyệt đạo bị điểm sai, khiến nội công tẩu hỏa nhập ma, chương trình không thể chạy được.”
Lỗi #1: Vô Danh Vô Phận - Thiếu LOG_MODULE_REGISTER
Đại ca: “Chú thấy cái
LOG_ERRmà anh comment lại không? Chú mà bật nó lên là trình biên dịch nó chửi cho sấp mặt ngay. Tại sao? Vì chú chưa ‘khai báo hộ khẩu’ cho cái filemain.cnày với hệ thống log của Zephyr. Mỗi file muốn dùngLOG_...đều phải có dòngLOG_MODULE_REGISTER(tên_module, LOG_LEVEL_DBG)ở đầu. Nó như cái thẻ nhân viên vậy, không có thẻ thì bảo vệ nó không cho vào!”
Lỗi #2: Tẩu Hỏa Nhập Ma - Sai Cú Pháp GPIO_DT_SPEC_GET
Đại ca: “Há, cái dòng này mới thật là ‘đê tiện’ chứ hả:
GPIO_DT_SPEC_GET(DT_ALIAS(led0))! Chú em à,DT_ALIAS(led0)nó trả về một cái node identifier, một cái định danh cho node thôi. Còn cái hàmGPIO_DT_SPEC_GETnó cần một cái node identifier làm tham số, chứ nó không nuốt cả một cái macroDT_ALIASvào bụng được!”“Cách làm đúng của các cao thủ là phải tách bạch, rành mạch:”
// Bước 1: Lấy cái định danh của node từ alias #define LED0_NODE DT_ALIAS(led0) // Bước 2: Dùng cái định danh đó để lấy spec static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);“Thấy sự khác biệt chưa? Rõ ràng, tường minh! Code là phải như vậy!”
Lỗi #3: Râu Ông Nọ Cắm Cằm Bà Kia - gpio_is_ready
Đại ca: “Cuối cùng, cái hàm
gpio_is_ready(led.port). Chú dùnggpio_dt_speclà một struct xịn sò, hiện đại của Zephyr. Mà lại đi gọi hàmgpio_is_ready()của thời ‘đồ đá’, chỉ nhận vào một con trỏdevice*. Nó giống như chú có một cái chìa khóa xe Ferrari (struct gpio_dt_spec) mà lại cố nhét vào ổ khóa xe đạp (device*) vậy.”“Khi đã dùng
_dtspec, thì tất cả các hàm liên quan cũng phải có đuôi_dtcho nó đồng bộ! Phải làgpio_is_ready_dt(&led).”
Phiên bản “Hoàn Hảo”
Sau một hồi “chửi yêu”, tôi đưa cho chú em phiên bản đã được sửa:
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/log/log.h>
// Đã có "thẻ nhân viên"!
LOG_MODULE_REGISTER(main_app, LOG_LEVEL_DBG);
#define LED0_NODE DT_ALIAS(led0)
#define SLEEP_TIME_MS 1000
// Đã tách bạch, rõ ràng
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);
void main(void)
{
int ret;
// Đã dùng đúng hàm _dt
if (!gpio_is_ready_dt(&led)) {
LOG_ERR("GPIO device %s is not ready", led.port->name);
return;
}
ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure GPIO pin");
return;
}
LOG_INF("Blinky sample started!");
while (1) {
ret = gpio_pin_toggle_dt(&led);
if (ret < 0) {
LOG_ERR("Failed to toggle GPIO pin");
return;
}
k_msleep(SLEEP_TIME_MS);
}
}
Màn 3: “Truyền Thụ Võ Công” - Các Chiêu Thức GDB
Code đã chuẩn, giờ là lúc “múa gươm”.
- Build code:
west build -b esp32s3_devkitc . - Mở 2 Terminal: Một cho OpenOCD, một cho GDB.
- Terminal 1:
openocd -f board/esp32s3-builtin.cfg - Terminal 2:
west debug
- Terminal 1:
Sau khi chạy west debug, chương trình sẽ tự động dừng lại ở main, và dấu nhắc (gdb) hiện ra, sẵn sàng nhận lệnh.
Đại ca: “Giờ thì nhìn cho kỹ đây, ta sẽ dạy cho cậu 5 chiêu GDB căn bản nhất để hành tẩu giang hồ.”
Chiêu 1: list - “Soi Kịch Bản”
“Chiêu này giúp chú xem code. Gõ
lhoặclistđể xem 10 dòng xung quanh vị trí hiện tại. Gõlist mainđể xem code của hàmmain, hoặclist 35để xem code quanh dòng 35. Biết người biết ta, trăm trận trăm thắng!”
Chiêu 2: break - “Đặt Bẫy”
“Chiêu này dùng để đặt ‘bẫy’ ở những nơi hiểm yếu. Gõ
break 40để đặt bẫy ở dòng 40 (dònggpio_pin_toggle_dt). Khi chương trình chạy đến đây, nó sẽ dừng lại. Gõinfo bđể xem có bao nhiêu cái bẫy đã được giăng.”
Chiêu 3: continue - “Thả Hổ Về Rừng”
“Gõ
choặccontinue. Chương trình sẽ chạy tự do cho đến khi… sập bẫy. Nó sẽ dừng ngay tại dòng 40 mà chúng ta đã đặt.”
Chiêu 4: print - “Móc Túi Xem Giấy Tờ”
“Khi đã ‘bắt’ được chương trình, dùng chiêu này để ‘lục soát’ nó. Gõ
p ledđể xem nội dung của cái structled. Chú sẽ thấy nó chứa con trỏportvà sốpin.”
Chiêu 5: next & step - “Đi Theo Dấu Chân”
“Đây là hai chiêu dễ nhầm nhất.
n(next) là đi qua một dòng, coi như nó là một bước. Nếu dòng đó là một hàm, nó sẽ chạy hết hàm đó rồi mới dừng. Còns(step) cũng là đi một bước, nhưng nếu gặp hàm, nó sẽ ‘nhảy’ vào bên trong hàm đó. Dùngskhi muốn xem xét nội tình bên trong một hàm, dùngnkhi chỉ muốn đi tiếp.”
Lời Kết: “Giờ Thì Ai Cũng Làm Được!”
Đại ca: “Đấy, chỉ với 5 chiêu
l,b,c,p,n/slà chú đã có thể ngao du trong thế giới GDB rồi. Giờ thì tự mình ‘múa’ tiếp đi, đặt thêm bẫy, ‘in’ thêm vài biến nữa xem sao.”
Từ giờ, bug không còn là nỗi sợ, mà là một con mồi trong tầm ngắm của đại ca. Chúc mừng, đại ca đã tốt nghiệp khóa vỡ lòng GDB!
[MEME: Một nhân vật game lên level hoặc nhận được item quý]