ข้ามไปยังเนื้อหา

SWD และ GDB เบื้องต้น

เมื่อจบบทเรียนนี้ คุณจะ

  1. เริ่มการดีบักบนบอร์ดผ่าน SWD แล้วหยุดที่ breakpoint ในฟังก์ชันที่กำหนดได้
  2. ใช้คำสั่ง GDB ดูค่าตัวแปร ดู backtrace และเดินโปรแกรมทีละบรรทัดได้
  3. อธิบายว่าทำไมการหยุดที่ breakpoint อาจเปลี่ยนพฤติกรรมของระบบที่มีจังหวะเวลาหรือหลายคอร์

ใช้เวลาประมาณ 70 นาที (แนวคิด 15 · ฝึก 25 · แล็บ 25 · เช็ก 5) ฝึก GDB บนคอมพิวเตอร์ก่อน แล้วค่อยใช้กับบอร์ด

ทวนจากโมดูล 1 และ 2 สองข้อ

  1. แม่แบบของ SDK ตั้ง CONFIG=Release ไว้ใน common.mk ด้วยเครื่องหมาย = ถ้าคุณ build ด้วย make build CONFIG=Debug ค่าไหนชนะ (บทเรียน 2.2)
  2. ถ้าลูป for (i = 0; i <= 8; i++) เขียนลงอาร์เรย์ขนาด 8 ไบต์ที่อยู่ใน struct ไบต์ที่เก้าจะไปตกที่ไหน (บทเรียน 1.3 เรื่อง struct และ padding)

สิ่งที่ต้องมีเพิ่ม: GDB บนคอมพิวเตอร์ (ติดตั้งแยก เช่น แพ็กเกจ gdb บน Linux หรือใช้ lldb บน macOS ซึ่งคำสั่งต่างกันเล็กน้อย) และสำหรับบอร์ด Eclipse IDE for ModusToolbox™ ที่มากับ ModusToolbox 3.6

เปิด examples/05_find_the_overrun.c ทายก่อนรัน ว่าบรรทัด round 0 จะพิมพ์ count เท่าไร

Terminal window
gcc -std=c11 -Wall -Wextra -g -O0 -o overrun examples/05_find_the_overrun.c
./overrun

ผลคือ round 0: count = 16 (expected 8) โปรแกรมบวก count ทีละ 8 แต่มีใครบางคนเขียนทับ count ก่อน การอ่านโค้ดทีละบรรทัดหาคำตอบได้ แต่ debugger ตอบได้เร็วกว่าและพิสูจน์ได้ ด้วยคำสั่งเดียว: “หยุดทันทีที่มีใครเขียนตรงนี้”

GDB <--TCP--> OpenOCD (GDB server) <--USB--> KitProg3 บนบอร์ด <--SWD--> CPU ใน PSOC™ Edge
  • SWD (Serial Wire Debug) คือพอร์ตดีบักของ Arm ใช้สายสัญญาณสองเส้น SWDIO กับ SWCLK (บวกกราวด์) ผ่านสายนี้ debugger หยุด CPU อ่านเขียนหน่วยความจำและรีจิสเตอร์ และตั้ง breakpoint กับ watchpoint ที่เป็นฮาร์ดแวร์ของคอร์ได้
  • KitProg3 คือ debugger ที่อยู่บนบอร์ดเอง ต่อผ่าน USB ตัวเดียวกับที่ใช้ flash และเป็นทาง UART console ด้วย
  • OpenOCD เป็นโปรแกรมตัวกลางที่คุยกับ KitProg3 แล้วเปิดพอร์ตให้ GDB ต่อ ModusToolbox เป็นคนเรียกให้เมื่อคุณเริ่มการดีบักจาก IDE
  • GDB คือตัวที่คุณพิมพ์คำสั่ง หรือที่ IDE ใช้อยู่เบื้องหลัง

README ของ SDK เตือนเรื่องหนึ่งไว้ชัดเจน: อย่าเรียก openocd ตรง ๆ ด้วย -f target/cat1d.cfg ทีม SDK ลองแล้วได้ wrote 0 bytes ตามด้วย checksum ไม่ตรง เพราะ image อยู่ใน QSPI flash ภายนอกที่ต้องให้ ModusToolbox ตั้งค่าก่อน และเฟิร์มแวร์ที่กำลังรันเสียไปด้วย ให้ flash ด้วย make program และเริ่มการดีบักผ่าน launch configuration ของ ModusToolbox

คำสั่ง ทำอะไร
break fill_samples หรือ break file.c:42 หยุดเมื่อถึงฟังก์ชันหรือบรรทัดนั้น
run / continue (c) เริ่ม / ทำงานต่อจนเจอ breakpoint ถัดไป
next (n) / step (s) / finish ไปบรรทัดถัดไปโดยข้ามฟังก์ชัน / เข้าไปในฟังก์ชัน / ทำจนจบฟังก์ชันนี้
print x (p) print *ptr print arr ดูค่า ตามด้วย pointer ดู struct หรืออาร์เรย์ทั้งก้อน
info locals / info args ดูตัวแปร local และอาร์กิวเมนต์ทั้งหมดของ frame ปัจจุบัน
bt (backtrace) ดูว่ามาถึงตรงนี้ผ่านฟังก์ชันไหนบ้าง
watch -l expr หยุดเมื่อมีการเขียนลงที่อยู่ของ expr (ใช้ hardware watchpoint ของคอร์)
x/4xw addr ดูหน่วยความจำ 4 word เป็นเลขฐานสิบหก

watchpoint คือเครื่องมือที่ทรงพลังที่สุดของตาราง เมื่อค่าเพี้ยนและไม่รู้ว่าใครเขียน อย่าไล่อ่านโค้ด ให้ฮาร์ดแวร์บอก ข้อควรรู้เมื่อใช้กับบอร์ด: แม่แบบของ SDK build แบบ Release เป็นค่าเริ่มต้น (common.mk บรรทัด 20) คอมไพเลอร์จะเก็บตัวแปรไว้ในรีจิสเตอร์ ตัดตัวแปรทิ้ง หรือสลับลำดับบรรทัด GDB จึงอาจแสดง <optimized out> หรือกระโดดข้ามบรรทัดตอน next นั่นไม่ใช่ debugger เสีย ถ้าต้องการเห็นทุกตัวแปร ให้ลอง build ด้วย CONFIG=Debug บน command line ซึ่งชนะ = ใน common.mk แล้วจดลงบันทึกการ build (หลักสูตรนี้ยังไม่ได้ยืนยันบนบอร์ดว่า Debug ของแม่แบบนี้ build และบูตได้ครบ ถ้าไม่ได้ ให้ดีบักบน Release ต่อ)

breakpoint หยุดแค่คอร์ที่ถูกดีบัก ทุกอย่างรอบตัวเดินต่อ ปุ่มที่กำลังถูกกด ข้อมูลที่ไหลเข้า UART เซนเซอร์ และอีกคอร์หนึ่ง ระบบที่พึ่งจังหวะเวลาจึงทำตัวต่างไปเมื่อถูกหยุด เช่น การกดปุ่มที่เกิดขณะหยุดอาจหายไปทั้งครั้ง ไบต์ที่มาถึงระหว่างนั้นล้นบัฟเฟอร์ และถ้าเปิด watchdog ไว้ การหยุดนาน ๆ อาจทำให้บอร์ดรีเซ็ตกลางการดีบัก (บทเรียน 4.3)

บน PSOC™ Edge E84 เรื่องนี้ยิ่งชัดเพราะมีสองคอร์ทำงาน เอกสาร SDK บท G2 บันทึกว่าเมื่อ CM33 ถูกหยุด บรรทัด [HB] หยุดทันที แต่ “the screen stays as it was (CM55 keeps running its last frame)” คอร์ที่ไม่ได้ถูกหยุดยังรอคำตอบทาง IPC จากคอร์ที่ถูกหยุดอยู่ และคอมเมนต์ใน diag_blackbox.h ของ SDK ระบุว่า config ของ openocd ที่ใช้ “exposes no CM55 debug target” คือในชุดเครื่องมือของแม่แบบนี้ เราดีบัก CM33 ได้ แต่หลักฐานจาก CM55 ต้องมาจากตัวนับและบันทึกที่มันเขียนไว้เอง ซึ่งเป็นเรื่องของบทเรียน 3.2

กับดักที่สำคัญที่สุดของแม่แบบ mtb-only คือ Appendix X #16 ของเอกสาร SDK: การ attach debugger เข้ากับบอร์ดที่กำลังทำงาน ทำให้ CM33 ไปค้างในลูปของ boot ROM จนกว่าจะตัดไฟ คอมเมนต์ใน proj_cm33_ns/main.c เล่าว่ากว่าจะรู้เรื่องนี้ต้องเสียเวลาหลายชั่วโมงไปกับการโทษเฟิร์มแวร์ และบท G2 แนะนำว่าถ้าต้องใช้ debugger กับ variant นี้ ให้ “flash and halt-on-reset from a fresh programming session rather than attaching to a live board”

ฝึกบนคอมพิวเตอร์กับ examples/05_find_the_overrun.c ซึ่งเขียนเป็นสามท่า

  • ท่าที่ 1 fill_samples() เติมค่าลงอาร์เรย์ 8 ช่อง ด้วยลูปที่มีบั๊กหนึ่งจุด
  • ท่าที่ 2 main() เรียกสามรอบ และบวก count ทีละ 8
  • ท่าที่ 3 พิมพ์ค่าที่ได้เทียบกับค่าที่ควรเป็น

session แบบโต้ตอบบนคอมพิวเตอร์ (พิมพ์ทีละบรรทัดหลัง (gdb))

$ gdb -q ./overrun
(gdb) break fill_samples
(gdb) run
(gdb) info args
(gdb) print *r
(gdb) next
(gdb) print i
(gdb) watch -l r->count
(gdb) continue
(gdb) bt
(gdb) print i
(gdb) quit

หลัง continue GDB จะหยุดที่บรรทัดในลูปของ fill_samples พร้อมแสดงค่าเก่าและค่าใหม่ของ r->count print i ได้ 8 ซึ่งบอกว่าคำสั่งที่เขียนทับ count คือ r->samples[8] ช่องที่อยู่นอกอาร์เรย์ และ bt แสดงว่ามาจาก main ลองแก้ <= เป็น < คอมไพล์ใหม่ แล้วรันทั้งโปรแกรมและ session นี้อีกครั้ง watchpoint ยังหยุดอยู่ไหม และหยุดที่ไหน

เปิด practice/05_watch.gdb เป็นสคริปต์ GDB ที่ทำ session ข้างบนแบบอัตโนมัติ มีช่องให้เติม 3 จุด (____) ชื่อฟังก์ชันที่จะหยุด ตัวแปรที่จะเฝ้า และคำสั่งดู call stack รันด้วย

Terminal window
gdb -q -batch -x practice/05_watch.gdb ./overrun

ผลที่ต้องได้คือการหยุดด้วย watchpoint ในลูปของ fill_samples และ print i ได้ 8

ลองเองก่อนอย่างน้อย 15 นาที แล้วเปิด solution/05_watch.gdb จุดที่มักพลาดคือเขียน watch r->count โดยไม่มี -l ซึ่ง GDB จะเฝ้าแบบผูกกับ frame ของ fill_samples และลบ watchpoint ทิ้งเมื่อฟังก์ชันจบ ส่วน -l คำนวณที่อยู่ครั้งเดียวแล้วเฝ้าที่อยู่นั้น ซึ่งคือสิ่งที่ต้องการเมื่อสงสัยว่า “ใครเขียนลงหน่วยความจำตรงนี้”

ตอบคำถาม 5 ข้อใน quiz.yaml (บนเว็บไซต์อยู่ท้ายหน้านี้) ครอบคลุมเป้าหมายทั้งสามข้อ ตอบถูกตั้งแต่ 4 ข้อขึ้นไปถือว่าจบบทเรียน

งาน: หยุด CM33 ในตัวอย่าง GPIO ของ SDK ด้วย breakpoint แล้วดูตัวแปรของตัวกันเด้งขณะคุณกดปุ่ม

  1. build แม่แบบโดยเลือกตัวอย่าง GPIO ถ้าจะลอง Debug ให้เพิ่ม CONFIG=Debug และจดลงบันทึกการ build
    Terminal window
    make build -j ENABLE_PAGE_EXAMPLES=1 SDK_EXAMPLE_CM33=cm33/io/04_gpio_led_button
  2. เปิดแม่แบบใน Eclipse IDE for ModusToolbox แล้วเริ่มการดีบักด้วย launch configuration แบบ Debug (KitProg3_MiniProg4) ของโปรเจกต์ CM33 non-secure จาก Quick Panel (ชื่อเต็มขึ้นกับชื่อโปรเจกต์ของคุณ) ซึ่ง program แล้วเริ่มจาก reset ห้ามใช้แบบ attach กับบอร์ดที่กำลังทำงาน (Appendix X #16)
  3. ตั้ง breakpoint ที่ example_io_gpio_led_button แล้ว resume เมื่อหยุด พิมพ์ bt ใน Debugger Console หรือดูหน้าต่าง Call Stack จดชื่อฟังก์ชันที่เรียกเข้ามา (ควรเห็นตัวรันตัวอย่างของ SDK)
  4. ใช้ next ข้าม leds_init() และ button_init() ทีละบรรทัด แล้วตั้ง breakpoint อีกตัวที่บรรทัด presses++; กดปุ่ม SW2 หนึ่งครั้ง เมื่อหยุด ดูค่า stable cand agree และ presses
  5. ระหว่างที่ CM33 หยุดอยู่ สังเกต serial console และหน้าจอ บรรทัดไหนหยุด อะไรยังขยับ จดลงบันทึก แล้วอธิบายด้วยแนวคิดข้อ 3
  6. ลบ breakpoint ทั้งหมด resume แล้วกดปุ่มอีกห้าครั้ง จำนวน presses ที่พิมพ์ตอนจบตรงกับที่กดไหม เทียบกับตอนที่ยังมี breakpoint อยู่

หลักฐานที่เก็บไว้ใน portfolio: ภาพหน้าจอ call stack ตอนหยุดในข้อ 3 ภาพหน้าต่างตัวแปรในข้อ 4 บันทึกสิ่งที่หยุดและสิ่งที่ยังขยับในข้อ 5 และผลการนับในข้อ 6 ถ้า GDB แสดง <optimized out> ให้จดไว้ด้วยว่าเป็นตัวแปรไหน และ build แบบไหน

  • คู่มือ GDB หัวข้อ “Setting Watchpoints” อธิบายว่าเมื่อไรเป็น hardware watchpoint และมีได้กี่ตัวพร้อมกันขึ้นกับคอร์ ลองตั้งหลายตัวบนบอร์ดแล้วดูว่า GDB บอกอะไรเมื่อเกินจำนวน
  • คู่มือ OpenOCD หัวข้อ GDB and OpenOCD อธิบายว่า GDB ต่อกับ OpenOCD อย่างไร ใช้ประกอบความเข้าใจ แต่สำหรับบอร์ดนี้ให้เริ่มผ่าน ModusToolbox ตามคำเตือนของ SDK
  • อ่านบท G2 — The heartbeat: living without a REPL ของเอกสาร SDK ทั้งบท เรื่องจริงของเครื่องมือวัดที่ “is also the murder weapon”

บทถัดไป: บทเรียน 3.2 วินิจฉัยความผิดพลาดจากหลักฐาน

  • บั๊กแบบไหนที่คุณคิดว่า debugger ช่วยได้น้อย และต้องใช้ตัวนับหรือ log แทน
  • ถ้า breakpoint ทำให้บั๊กหายไป (มักเรียกว่า heisenbug) นั่นบอกอะไรคุณเกี่ยวกับสาเหตุของมัน

คำถามทบทวน

ลองตอบเองก่อน แล้วค่อยเปิดดูเฉลย

  1. บอร์ดรันเฟิร์มแวร์ mtb-only อยู่ และคุณต้องการหยุดที่ฟังก์ชันหนึ่ง วิธีใดตรงกับคำแนะนำของเอกสาร SDK (เป้าหมายข้อ 1)

    1. attach debugger เข้ากับบอร์ดที่กำลังทำงานอยู่ แล้วตั้ง breakpoint
    2. เรียก openocd ตรง ๆ ด้วย -f target/cat1d.cfg แล้วต่อ GDB เอง
    3. เริ่ม launch configuration แบบ Debug ที่ program แล้วเริ่มจาก reset จากนั้นตั้ง breakpoint ที่ฟังก์ชัน
    4. ใส่ printf ใน CM55 แล้วอ่านจาก console
    ดูเฉลย

    คำตอบ: C. เริ่ม launch configuration แบบ Debug ที่ program แล้วเริ่มจาก reset จากนั้นตั้ง breakpoint ที่ฟังก์ชัน

    Appendix X #16: การ attach กับบอร์ด mtb-only ที่กำลังทำงานทำให้ CM33 ค้างในลูปของ boot ROM ส่วน README เตือนว่าเรียก openocd ตรง ๆ แล้วเขียนได้ 0 ไบต์และทำให้เฟิร์มแวร์เสีย และ CM55 ไม่มี console เอกสาร G2 แนะนำให้ flash แล้ว halt-on-reset จาก session ใหม่

  2. เรียงสายโซ่การดีบักจากเครื่องของคุณไปจนถึง CPU (เป้าหมายข้อ 1)

    1. KitProg3 บนบอร์ด
    2. GDB
    3. CPU ใน PSOC Edge ผ่านสาย SWD
    4. OpenOCD (GDB server)
    ดูเฉลย

    ลำดับที่ถูก: B. GDB → D. OpenOCD (GDB server) → A. KitProg3 บนบอร์ด → C. CPU ใน PSOC Edge ผ่านสาย SWD

    GDB ส่งคำสั่งให้ OpenOCD ทาง TCP OpenOCD คุยกับ KitProg3 ทาง USB และ KitProg3 คุยกับ debug port ของชิปผ่าน SWD

  3. ค่าของ rec.count เพี้ยนโดยไม่รู้ว่าใครเขียน คำสั่ง GDB ใดตอบคำถามนี้ได้ตรงที่สุด (เป้าหมายข้อ 2)

    1. print rec.count ทุก ๆ บรรทัด
    2. watch -l rec.count แล้ว continue
    3. break main
    4. info registers
    ดูเฉลย

    คำตอบ: B. watch -l rec.count แล้ว continue

    watchpoint ให้ฮาร์ดแวร์หยุด CPU ทันทีที่มีการเขียนลงที่อยู่นั้น แล้ว bt บอกได้ว่าใครเขียน -l ทำให้เฝ้าที่อยู่ต่อได้แม้ออกจาก frame ที่ตั้ง

  4. หยุดอยู่ในฟังก์ชันหนึ่งบนบอร์ดที่ build แบบ Release แล้ว GDB แสดง <optimized out> ข้อใดถูก (เลือกได้หลายข้อ) (เป้าหมายข้อ 2)

    1. คอมไพเลอร์อาจเก็บตัวแปรไว้ในรีจิสเตอร์หรือตัดทิ้ง จึงไม่มีตำแหน่งให้ GDB อ่าน
    2. แปลว่า debugger หรือบอร์ดเสีย ต้องเปลี่ยนสาย
    3. build ใหม่ด้วย CONFIG=Debug บน command line จะชนะ CONFIG=Release ใน common.mk และมักเห็นตัวแปรครบขึ้น
    4. next อาจกระโดดข้ามหรือย้อนบรรทัด เพราะคำสั่งเครื่องถูกจัดลำดับใหม่
    ดูเฉลย

    คำตอบ: A. คอมไพเลอร์อาจเก็บตัวแปรไว้ในรีจิสเตอร์หรือตัดทิ้ง จึงไม่มีตำแหน่งให้ GDB อ่าน · C. build ใหม่ด้วย CONFIG=Debug บน command line จะชนะ CONFIG=Release ใน common.mk และมักเห็นตัวแปรครบขึ้น · D. next อาจกระโดดข้ามหรือย้อนบรรทัด เพราะคำสั่งเครื่องถูกจัดลำดับใหม่

    optimisation เปลี่ยนความสัมพันธ์ระหว่างบรรทัดกับคำสั่งเครื่อง ไม่ใช่ความเสียหายของเครื่องมือ ค่าจาก command line ชนะการกำหนดด้วย = ในไฟล์ (บทเรียน 2.2) และควรจดลงบันทึกการ build ว่าใช้ config ไหน

  5. ขณะที่ CM33 หยุดที่ breakpoint อยู่ ข้อใดน่าจะเกิดขึ้น (เลือกได้หลายข้อ) (เป้าหมายข้อ 3)

    1. บรรทัด [HB] บน console หยุด
    2. CM55 หยุดตามทันทีเพราะเป็นชิปเดียวกัน
    3. การกดปุ่มที่เกิดระหว่างหยุดอาจหายไป เพราะโค้ดที่ถามปุ่มไม่ได้รัน
    4. คอร์อีกตัวที่รอคำตอบทาง IPC จาก CM33 จะรอต่อไปโดยไม่ได้คำตอบ
    ดูเฉลย

    คำตอบ: A. บรรทัด [HB] บน console หยุด · C. การกดปุ่มที่เกิดระหว่างหยุดอาจหายไป เพราะโค้ดที่ถามปุ่มไม่ได้รัน · D. คอร์อีกตัวที่รอคำตอบทาง IPC จาก CM33 จะรอต่อไปโดยไม่ได้คำตอบ

    breakpoint หยุดแค่คอร์ที่ดีบัก เอกสาร G2 ของ SDK บันทึกว่า [HB] หยุดทันทีแต่ CM55 ยังวาดเฟรมล่าสุดต่อ โลกภายนอกและคอร์อีกตัวเดินต่อ จังหวะเวลาของระบบจึงเปลี่ยนเมื่อถูกดีบัก

อ้างอิงบทเรียนนี้

ถ้านำบทเรียนนี้ไปสอน ทำสไลด์ หรือทำเอกสารต่อ ให้อ้างอิงด้วยข้อความนี้ ถ้าดัดแปลงเนื้อหา ให้เติม (ดัดแปลง)ต่อท้ายชื่อบทเรียน

"SWD และ GDB เบื้องต้น" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

ข้อความอ้างอิงภาษาอังกฤษ: "SWD and GDB basics" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0

ลิงก์บทเรียน: https://tesaiot.github.io/tesa-qualification-program/courses/embedded-c-foundations/m03-debugging/l01-swd-and-gdb/

บทเรียนนี้ดัดแปลงจากต้นฉบับด้านล่าง เมื่ออ้างอิงให้คงเครดิตต้นฉบับไว้ด้วย
https://github.com/tesaiot/tesaiot-pse84-devkit-sdk/tree/ef72c1b658178eee8c38b1e47d28b006f80a59b5 · SDK examples and docs are linked at this commit, not copied into this course. Lessons quote short excerpts (at most 25 lines) with a link to the file at this commit and the credit (Apache-2.0, tesaiot-pse84-devkit-sdk).

วิธีอ้างอิง TESA ฉบับเต็ม

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

เนื้อหาเผยแพร่ภายใต้ CC BY-NC 4.0 นำไปใช้ต่อในงานที่ไม่ใช่เพื่อการค้าได้ โปรดอ้างอิงสมาคมสมองกลฝังตัวไทย (TESA) ทุกครั้ง · วิธีอ้างอิง TESA