SWD and GDB basics
Objectives
Section titled “Objectives”By the end of this lesson, you will be able to
- Start a debug session on the board over SWD, and stop at a breakpoint in a given function.
- Use GDB commands to inspect variables, view a backtrace, and step line by line.
- Explain why halting at a breakpoint can change the behaviour of a timing-sensitive or multi-core system.
Takes about 70 minutes (concepts 15 · practice 25 · lab 25 · check 5). Practice GDB on your computer first, then use it with the board.
Before you start
Section titled “Before you start”Two review questions from modules 1 and 2.
- The SDK’s template sets
CONFIG=Releaseincommon.mkwith an=sign. If you build withmake build CONFIG=Debug, which value wins (lesson 2.2)? - If a loop
for (i = 0; i <= 8; i++)writes into an 8-byte array inside a struct, where does the ninth byte land (lesson 1.3, on structs and padding)?
Additional things you need: GDB on your computer (installed separately, such as the gdb package on Linux, or lldb on macOS, whose commands differ slightly), and for the board, the Eclipse IDE for ModusToolbox™ that comes with ModusToolbox 3.6.
See it work first
Section titled “See it work first”Open examples/05_find_the_overrun.c. Predict before you run it: what count will the round 0 line print?
gcc -std=c11 -Wall -Wextra -g -O0 -o overrun examples/05_find_the_overrun.c./overrunThe result is round 0: count = 16 (expected 8). The program adds 8 to count each time, but something is overwriting count first. Reading the code line by line can find the answer, but a debugger answers faster and proves it, with a single command: “stop the instant anyone writes here.”
Concepts
Section titled “Concepts”1. The chain from GDB to the chip
Section titled “1. The chain from GDB to the chip”GDB <--TCP--> OpenOCD (GDB server) <--USB--> KitProg3 on the board <--SWD--> the CPU in PSOC™ Edge- SWD (Serial Wire Debug) is Arm’s debug port, using two signal wires, SWDIO and SWCLK (plus ground). Over this wire, a debugger can halt the CPU, read and write memory and registers, and set the core’s own hardware breakpoints and watchpoints.
- KitProg3 is the debugger built into the board itself, connected through the same USB port used for flashing, which also carries the UART console.
- OpenOCD is the middle program that talks to KitProg3 and opens a port for GDB to connect to. ModusToolbox launches it for you when you start a debug session from the IDE.
- GDB is what you type commands into, or what the IDE drives behind the scenes.
The SDK’s README warns clearly about one thing: never call openocd directly with -f target/cat1d.cfg. The SDK team tried it and got wrote 0 bytes followed by a checksum mismatch, because the image lives in external QSPI flash that ModusToolbox must configure first, and it corrupted the firmware that was running. Flash with make program, and start debugging through ModusToolbox’s launch configuration.
2. GDB commands you’ll use every day
Section titled “2. GDB commands you’ll use every day”| Command | What it does |
|---|---|
break fill_samples or break file.c:42 |
Stop when execution reaches that function or line |
run / continue (c) |
Start / resume until the next breakpoint |
next (n) / step (s) / finish |
Advance a line, stepping over a function call / stepping into it / running until this function returns |
print x (p), print *ptr, print arr |
Show a value, follow a pointer, or view a whole struct or array |
info locals / info args |
Show every local variable and argument of the current frame |
bt (backtrace) |
See which functions led to this point |
watch -l expr |
Stop whenever anything writes to expr’s address (uses the core’s hardware watchpoint) |
x/4xw addr |
View 4 words of memory in hexadecimal |
The watchpoint is the most powerful tool in this table. When a value is corrupted and you don’t know who’s writing it, don’t chase it through the code by reading — let the hardware tell you. One thing to know when using this on the board: the SDK’s template builds as Release by default (common.mk line 20). The compiler will keep variables in registers, eliminate some entirely, or reorder lines, so GDB may show <optimized out>, or next may skip past a line — that’s not a broken debugger. If you need to see every variable, try building with CONFIG=Debug on the command line, which beats the = in common.mk, and note it in your build record. (This course has not yet confirmed on the board that this template’s Debug build builds and boots completely — if it doesn’t, keep debugging on Release.)
3. Halting the CPU does not halt the world
Section titled “3. Halting the CPU does not halt the world”A breakpoint only halts the core being debugged. Everything around it keeps running: a button being pressed, data streaming into a UART, sensors, and the other core. A system that depends on timing behaves differently once halted — a button press that happens while halted can be missed entirely, bytes that arrive during that time overflow a buffer, and if a watchdog is enabled, halting for too long can reset the board mid-debug session (lesson 4.3).
On the PSOC™ Edge E84 this is even more visible because two cores are running. The SDK documentation’s chapter G2 records that when CM33 is halted, the [HB] line stops immediately, but “the screen stays as it was (CM55 keeps running its last frame)” — the core that wasn’t halted is still waiting for an answer over IPC from the one that was. And a comment in the SDK’s diag_blackbox.h notes that the openocd config this template uses “exposes no CM55 debug target” — in this template’s toolset, we can debug CM33, but evidence from CM55 has to come from the counters and logs it writes for itself, which is the subject of lesson 3.2.
The most important trap in the mtb-only template is the SDK documentation’s Appendix X #16: attaching a debugger to a board that’s already running leaves CM33 stuck in the boot ROM’s loop until power is cut. A comment in proj_cm33_ns/main.c recounts spending hours blaming the firmware before this was understood, and chapter G2 recommends that if you need a debugger with this variant, “flash and halt-on-reset from a fresh programming session rather than attaching to a live board.”
Worked example
Section titled “Worked example”Practice on your computer with examples/05_find_the_overrun.c, which is written in three parts.
- Part 1:
fill_samples()fills an 8-slot array through a loop that has one bug. - Part 2:
main()calls it three times, adding 8 to count each time. - Part 3: prints the result against what it should be.
An interactive session on your computer (type each line after (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) quitAfter continue, GDB stops on the line inside fill_samples’s loop, showing the old and new value of r->count. print i gives 8, which tells you the write that overwrites count is r->samples[8] — a slot outside the array — and bt shows it came from main. Try changing <= to <, recompile, and run the whole program and this session again — does the watchpoint still trigger, and where?
Practice
Section titled “Practice”Open practice/05_watch.gdb, a GDB script that automates the session above. There are 3 gaps to fill in (marked ____): the function name to stop at, the variable to watch, and the command to view the call stack. Run it with
gdb -q -batch -x practice/05_watch.gdb ./overrunThe expected result is a stop at the watchpoint inside fill_samples’s loop, with print i giving 8.
Solution
Section titled “Solution”Try it yourself for at least 15 minutes first, then open solution/05_watch.gdb. The most common mistake is writing watch r->count without -l, which makes GDB watch it tied to fill_samples’s frame and delete the watchpoint once that function returns. -l computes the address once and watches that address instead, which is what you want when asking “who is writing to this piece of memory?”
Check your understanding
Section titled “Check your understanding”Answer the 5 questions in quiz.yaml (shown at the bottom of this page on the website), covering all three objectives. Getting 4 or more right counts as finishing the lesson.
Task: halt CM33 in the SDK’s GPIO example with a breakpoint, and watch the debounce variables while you press the button.
- Build the template with the GPIO example selected. If you want to try Debug, add
CONFIG=Debugand note it in your build record.Terminal window make build -j ENABLE_PAGE_EXAMPLES=1 SDK_EXAMPLE_CM33=cm33/io/04_gpio_led_button - Open the template in the Eclipse IDE for ModusToolbox and start a debug session using the Debug (KitProg3_MiniProg4) launch configuration for the CM33 non-secure project from the Quick Panel (the full name depends on your project’s name). This programs the board and starts from reset. Never use the attach-to-a-running-board variant (Appendix X #16).
- Set a breakpoint at
example_io_gpio_led_button, then resume. Once halted, typebtin the Debugger Console or check the Call Stack window. Note which functions called into it (you should see the SDK’s example runner). - Use
nextto step pastleds_init()andbutton_init()one line at a time, then set another breakpoint at the linepresses++;. Press SW2 once. When it halts, look at the values ofstable,cand,agreeandpresses. - While CM33 is halted, watch the serial console and the screen. Which lines stop, and what keeps moving? Note it down, then explain it using concept 3.
- Remove all breakpoints, resume, and press the button five more times. Does the final
pressescount printed match how many times you pressed it? Compare with when breakpoints were still active.
Evidence to keep in your portfolio: a screenshot of the call stack when halted in step 3, a screenshot of the variables window in step 4, notes on what stopped versus what kept moving in step 5, and the count result in step 6. If GDB shows <optimized out>, note which variable it was and which build configuration you used.
Going further
Section titled “Going further”- The GDB manual’s “Setting Watchpoints” section explains when a watchpoint becomes a hardware watchpoint, and how many can exist at once depending on the core. Try setting several on the board and see what GDB says once you exceed the limit.
- The OpenOCD manual’s “GDB and OpenOCD” section explains how GDB connects to OpenOCD. Useful for background, but for this board, always start through ModusToolbox, per the SDK’s warning.
- Read the SDK documentation’s chapter G2 — The heartbeat: living without a REPL in full — the real story of a measurement tool that “is also the murder weapon.”
Next lesson: lesson 3.2, diagnosing faults from evidence
Reflect
Section titled “Reflect”- What kind of bug do you think a debugger helps with the least, needing counters or logs instead?
- If a breakpoint makes a bug disappear (often called a heisenbug), what does that tell you about its cause?
References
Section titled “References”- GDB documentation
- OpenOCD User’s Guide
- SDK: the mtb-only template README (flashing through KitProg)
- SDK: the repository’s main README (the warning about openocd)
- G2 — The heartbeat: living without a REPL (SDK docs built from commit ef72c1b)
- Appendix X — Traps and anti-patterns (SDK docs built from commit ef72c1b)
- Infineon mtb-example-psoc-edge-hello-world @ release-v2.1.0: using the code example (Debugging section)
Review questions
Answer on your own first, then open the answer.
-
A board is running mtb-only firmware and you want to stop in a function. Which approach follows the SDK docs? (Objective 1)
- attach debugger เข้ากับบอร์ดที่กำลังทำงานอยู่ แล้วตั้ง breakpoint
- เรียก openocd ตรง ๆ ด้วย -f target/cat1d.cfg แล้วต่อ GDB เอง
- เริ่ม launch configuration แบบ Debug ที่ program แล้วเริ่มจาก reset จากนั้นตั้ง breakpoint ที่ฟังก์ชัน
- ใส่ printf ใน CM55 แล้วอ่านจาก console
Show answer
Answer: 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 ใหม่
-
Order the debug chain from your computer to the CPU. (Objective 1)
- KitProg3 บนบอร์ด
- GDB
- CPU ใน PSOC Edge ผ่านสาย SWD
- OpenOCD (GDB server)
Show answer
Correct order: B. GDB → D. OpenOCD (GDB server) → A. KitProg3 บนบอร์ด → C. CPU ใน PSOC Edge ผ่านสาย SWD
GDB ส่งคำสั่งให้ OpenOCD ทาง TCP OpenOCD คุยกับ KitProg3 ทาง USB และ KitProg3 คุยกับ debug port ของชิปผ่าน SWD
-
rec.count is corrupted and you do not know who writes it. Which GDB command answers that most directly? (Objective 2)
- print rec.count ทุก ๆ บรรทัด
- watch -l rec.count แล้ว continue
- break main
- info registers
Show answer
Answer: B. watch -l rec.count แล้ว continue
watchpoint ให้ฮาร์ดแวร์หยุด CPU ทันทีที่มีการเขียนลงที่อยู่นั้น แล้ว bt บอกได้ว่าใครเขียน -l ทำให้เฝ้าที่อยู่ต่อได้แม้ออกจาก frame ที่ตั้ง
-
Stopped in a function of a Release build, GDB shows <optimized out>. Which are true? (choose all that apply) (Objective 2)
- คอมไพเลอร์อาจเก็บตัวแปรไว้ในรีจิสเตอร์หรือตัดทิ้ง จึงไม่มีตำแหน่งให้ GDB อ่าน
- แปลว่า debugger หรือบอร์ดเสีย ต้องเปลี่ยนสาย
- build ใหม่ด้วย CONFIG=Debug บน command line จะชนะ CONFIG=Release ใน common.mk และมักเห็นตัวแปรครบขึ้น
- next อาจกระโดดข้ามหรือย้อนบรรทัด เพราะคำสั่งเครื่องถูกจัดลำดับใหม่
Show answer
Answer: A. คอมไพเลอร์อาจเก็บตัวแปรไว้ในรีจิสเตอร์หรือตัดทิ้ง จึงไม่มีตำแหน่งให้ GDB อ่าน · C. build ใหม่ด้วย CONFIG=Debug บน command line จะชนะ CONFIG=Release ใน common.mk และมักเห็นตัวแปรครบขึ้น · D. next อาจกระโดดข้ามหรือย้อนบรรทัด เพราะคำสั่งเครื่องถูกจัดลำดับใหม่
optimisation เปลี่ยนความสัมพันธ์ระหว่างบรรทัดกับคำสั่งเครื่อง ไม่ใช่ความเสียหายของเครื่องมือ ค่าจาก command line ชนะการกำหนดด้วย = ในไฟล์ (บทเรียน 2.2) และควรจดลงบันทึกการ build ว่าใช้ config ไหน
-
While CM33 is halted at a breakpoint, which are likely? (choose all that apply) (Objective 3)
- บรรทัด [HB] บน console หยุด
- CM55 หยุดตามทันทีเพราะเป็นชิปเดียวกัน
- การกดปุ่มที่เกิดระหว่างหยุดอาจหายไป เพราะโค้ดที่ถามปุ่มไม่ได้รัน
- คอร์อีกตัวที่รอคำตอบทาง IPC จาก CM33 จะรอต่อไปโดยไม่ได้คำตอบ
Show answer
Answer: A. บรรทัด [HB] บน console หยุด · C. การกดปุ่มที่เกิดระหว่างหยุดอาจหายไป เพราะโค้ดที่ถามปุ่มไม่ได้รัน · D. คอร์อีกตัวที่รอคำตอบทาง IPC จาก CM33 จะรอต่อไปโดยไม่ได้คำตอบ
breakpoint หยุดแค่คอร์ที่ดีบัก เอกสาร G2 ของ SDK บันทึกว่า [HB] หยุดทันทีแต่ CM55 ยังวาดเฟรมล่าสุดต่อ โลกภายนอกและคอร์อีกตัวเดินต่อ จังหวะเวลาของระบบจึงเปลี่ยนเมื่อถูกดีบัก
Cite this lesson
If you teach from this lesson or reuse it in slides or documents, credit it with the text below. If you changed it, add (adapted) after the title.
"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
Thai attribution: "SWD และ GDB เบื้องต้น" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
Lesson link: https://tesaiot.github.io/tesa-qualification-program/en/courses/embedded-c-foundations/m03-debugging/l01-swd-and-gdb/
This lesson adapts the source below; keep its credit too.
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 Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
Content is licensed CC BY-NC 4.0. Reuse it non-commercially and credit the Thai Embedded Systems Association (TESA) every time. · How to cite TESA