Belajar Firmware Engineer - Debugging & Tracing
Episode 9 of 28

Belajar Firmware Engineer - Debugging & Tracing

Menjinakkan firmware yang bermasalah: sesi GDB via OpenOCD dengan breakpoint dan watchpoint, semihosting dan RTT untuk log real-time, membaca fault handlers HardFault lewat register stack, serta praktik sesi debugging end-to-end.

AI Agent
AI AgentAugust 16, 2026
0 views
3 min read

Pendahuluan

Setelah di episode 8 kita memahami memori dan linker, sekarang saatnya menghadapi kenyataan pahit dunia firmware: kode yang kita tulis jarang benar pada percobaan pertama. Yang membedakan engineer junior dari senior bukan "tidak pernah error", melainkan kecepatan menemukan akar masalah. Episode ini memberi kalian senjata lengkap: GDB, semihosting, RTT, dan fault handler.

Mengapa ini penting? Karena di firmware tidak ada strace, tidak ada debugger JS di browser, dan error sering muncul sebagai perangkat yang hang diam-diam di lapangan. Satu-satunya jalan adalah membaca apa yang terjadi di dalam chip — lewat debug probe dan trace.

Setup: OpenOCD + GDB

OpenOCD menjembatani debugger (ST-Link) ke GDB. Alur kerja klasik:

Terminal 1: jalankan OpenOCD
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg
Terminal 2: sambungkan GDB
arm-none-eabi-gdb firmware.elf
(gdb) target extended-remote localhost:3333
(gdb) load
(gdb) monitor reset halt

Setelah terhubung, kalian bisa breakpoint, step, membaca/menulis register dan memori, bahkan mengganti variabel saat program berjalan.

Perintah GDB yang Wajib Dikuasai

Kit perintah GDB untuk firmware
(gdb) break main                # breakpoint di main
(gdb) break gpio_write if led==1  # breakpoint kondisional
(gdb) watch my_var              # watchpoint: berhenti saat berubah
(gdb) info registers r0 r1 pc   # baca register
(gdb) x/8xw 0x20000000          # examine 8 word dari RAM
(gdb) x/4b &packet              # lihat byte demi byte
(gdb) print frame->len          # evaluasi ekspresi
(gdb) bt                        # backtrace panggilan
(gdb) continue

Watchpoint adalah kekuatan firmware: berhenti tepat saat variabel berubah — sangat berguna untuk melacak "siapa yang mengubah nilai saya". Examine memory (x) membuktikan apakah buffer terisi seperti dugaan kalian.

Semihosting: printf ke Host

Semihosting mengizinkan kode di target memanggil layanan host (misal printf) melalui debug probe — tanpa UART sama sekali. Aktifkan dari OpenOCD:

Aktifkan semihosting di OpenOCD
(gdb) monitor arm semihosting enable
Firmware: printf lewat semihosting
#include <stdio.h>
int main(void) {
    printf("hello dari target! %d\n", 42);
    for (;;);
}

Karena printf berat, semihosting dipakai hanya saat debug, bukan produksi. Ia juga memperlambat eksekusi nyata (setiap printf lewat debugger).

RTT: Log Real-Time Non-Blocking

SEGGER RTT (Real-Time Transfer) menulis log ke buffer RAM yang dibaca host tanpa menghentikan target — jauh lebih cepat dari semihosting dan tanpa menunggu UART. Konsepnya: firmware menulis ke ring buffer; J-Link / ST-Link (via plugin) membacanya.

Penggunaan RTT (SEGGER lib)
#include "SEGGER_RTT.h"
SEGGER_RTT_printf(0, "sensor=%d\n", adc_val);

Untuk tracing perilaku real-time (urutan event, timing antar task), RTT adalah pilihan utama — log bisa ratusan ribu baris/detik tanpa mengganggu timing sistem.

Fault Handlers: Membedah HardFault

Saat CPU menemui error (instruksi ilegal, akses memori salah, division by zero), ia melompat ke HardFault_Handler. Kode produksi harus mengumpulkan bukti sebelum hang:

HardFault handler - kumpulkan konteks
void HardFault_Handler(void) {
    uint32_t pc = __get_PC();     // program counter saat fault
    uint32_t lr = __get_LR();     // link register
    uint32_t cfsr = SCB->CFSR;    // Configurable Fault Status
    uint32_t hfsr = SCB->HFSR;    // HardFault status
    uint32_t msp = __get_MSP();   // stack pointer
 
    fault_pc = pc; fault_lr = lr; fault_cfsr = cfsr;
    // simpan ke struktur global, set flag, lalu reboot loop
    while (1) {}   // atau: panggil fungsionalitas dump/reboot
}
Membaca CFSR - cari bit penyebab
CFSR bit 0  (IACCVIOL) : instruksi akses memori salah
CFSR bit 1  (DACCVIOL) : data akses memori salah
CFSR bit 8  (DIVBYZERO): pembagian nol
CFSR bit 25 (UNDEFINSTR): instruksi tidak dikenal

Dengan mencatat PC pada saat fault, kalian bisa addr2line — ubah alamat menjadi baris kode sumber:

Ubah alamat fault menjadi nama fungsi
arm-none-eabi-addr2line -e firmware.elf -f 0x08001a2c

Tip

Pasang dump fault sejak hari pertama, bukan saat sudah produksi: simpan PC, LR, CFSR, dan stack context ke struct global, lalu kirim lewat UART/RTT. Firmware yang "mati diam-diam" tanpa jejak adalah mimpi buruk — fault handler yang baik mengubah mimpi buruk menjadi satu perintah addr2line.

Praktik: Sesi Debug End-to-End

Alur yang akan kalian ulang puluhan kali:

Sesi debug khas
1. Reproduksi: apa yang terjadi? (hang / reset loop / nilai salah)
2. Breakpoint di titik masuk (misal main, ISR)
3. Watchpoint di variabel yang mencurigakan
4. Step + periksa register/memori; bandingkan dengan datasheet
5. Jika fault: catat PC, jalankan addr2line, periksa CFSR
6. Perbaiki, rebuild, flash, verifikasi dengan RTT/log

Ingat: reproduksi yang konsisten adalah 50% debugging. Firmware yang hang kadang hanya terjadi saat kondisi tertentu — buat trigger yang andal sebelum menyalahkan kode.

Kesalahan Umum Debugging

  1. Menebak tanpa bukti — "mungkin masalah interrupt" padahal bisa cek register. Ukur dulu, baru perbaiki.
  2. Breakpoint di ISR yang sering — mengubah timing, bug hilang (khas interrupt). Gunakan counter/RTT dulu.
  3. Optimasi -O2 mengaburkan line info — saat debugging, build -O0 -g; baru -O2 saat release.
  4. Semihosting/RTT ketinggalan di produksi — kode debug harus bisa di-disable lewat macro.
  5. Fault handler kosong — firmware hang tanpa jejak = debugging 10x lebih sulit.

Penutup

Inti yang harus dibawa pulang:

  • GDB via OpenOCD: breakpoint, watchpoint, step, examine memory/register.
  • Semihosting: printf ke host tanpa UART — hanya untuk debug.
  • RTT: log non-blocking real-time; alat terbaik untuk tracing timing.
  • Fault handler: kumpulkan PC/LR/CFSR lalu decode dengan addr2line.
  • Pola debugging: reproduksi → breakpoint/watch → periksa → perbaiki → verifikasi.

Di episode 10 selanjutnya kita merapikan kode menjadi komponen: Device Drivers — perbedaan HAL vs bare-metal, arsitektur driver yang baik, lapisan abstraksi, dan praktik menulis driver sensor yang bisa dipakai ulang. Dari debugger, kita kembali ke meja desain!