through source-level debugging of an LVGL application, all inside a QEMU/virt64 environment. No hardware needed — just a modern Linux host and developer tools
Today we’ll dive into the execution of an LVGL application and explore the LVGL library internals — all inside a QEMU/virt64 emulated environment. Yep: no hardware required. 🙂
What you’ll do
- Build the default Infrabase environment and QEMU.
- Enable debug symbols for your LVGL app and the LVGL library.
- Launch the emulated GUI and attach VS Code + gdb-multiarch.
- Inspect the call stack and step through the LVGL main loop.
1) Build the base system
From a fresh clone, bootstrap and build the default configuration:
$ source env.sh
$ build.sh -a
The first build takes ~45 minutes (it compiles a Buildroot-based root filesystem).
Since we’re using an emulated target, build QEMU as well:
$ build.sh -q
Start the graphical environment:
$ ./stg.sh
You should see the LVGL demo windows pop up in a desktop-like session.
Tip: stg.sh launches QEMU with a GDB server so your debugger can attach on localhost:1234.

The lvglsim application running in the QEMU/virt64 environment
2) Turn on debug symbols for the app and LVGL
We want source-level debugging with function names and line info.
Edit linux/usr/src/lvgl/lv_port_linux/CMakeLists.txt and set a debug build. If there’s a line that sets CMAKE_BUILD_TYPE to Debug, uncomment it (or add this if needed):
set(CMAKE_BUILD_TYPE Debug)

Uncomment the line to enable Debug (line 13)
Then clean the user build and rebuild with debug info:
$ cd linux/usr
$ rm -rf build/
$ ./build.sh
# or from repo root:
$ build.sh -u
This ensures your binaries are compiled with -g.
Heads-up: Make sure any stripping step is disabled for your debug build (don’t strip symbols).
3) VS Code debug configuration
Install the C/C++ extension (Microsoft) and ensure gdb-multiarch is available on your host (e.g., sudo apt install gdb-multiarch on Debian/Ubuntu).
Create or update .vscode/launch.json with:
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) USR lvglsim",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/linux/usr/build/bin/lvglsim",
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "/usr/bin/gdb-multiarch",
"miDebuggerServerAddress": "localhost:1234",
"setupCommands": [
{ "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true },
{ "text": "set remote Z-packet on" },
{ "text": "set output-radix 16" }
]
}
]
}
Why this works: When stg.sh starts QEMU, it exposes a GDB server on localhost:1234. VS Code’s cppdbg connects to it using gdb-multiarch, while program points to your local ELF with symbols so the debugger can resolve sources.
4) Attach, pause, and inspect the stack
- Launch the “(gdb) USR lvglsim” configuration in VS Code.
- It will auto-connect to QEMU (localhost:1234).
- Hit Pause to freeze the target and open the Call Stack.
You should land somewhere inside the LVGL driver/timer machinery.

Inspect the source code and stack trace of a running LVGL application
Typical path:
main()
→ run_loop_fbdev()
→ lv_timer_handler()
→ lv_timer_exec() // internal timer execution
→ … draw/invalidations …
LVGL runs a timer-driven event loop that triggers area invalidation and re-drawing. Stepping through this loop is incredibly useful for understanding rendering flow, timing, and where your app’s events slot in.
5) Handy breakpoints to try
- main() — entry point of your app.
- run_loop_fbdev() — where the frame-buffer loop hooks into LVGL.
- lv_timer_handler() — heart of LVGL’s scheduling.
- lv_refr_area() / lv_disp_refr_* — screen refresh paths.
- Your widget callbacks (e.g., event handlers) — to trace UI interaction flow.
Pro-tip: If you see an enormous stack, use stack filtering in VS Code and add function breakpoints for specific LVGL symbols to jump right where it matters.
6) Troubleshooting
- Can’t connect to localhost:1234?Make sure stg.sh/QEMU is running. If your setup uses a different port, update miDebuggerServerAddress.
- No source lines / function names?Confirm CMAKE_BUILD_TYPE=Debug is active and that you rebuilt after cleaning linux/usr/build/. Check your final binary with file linux/usr/build/bin/lvglsim (should mention “with debug_info”).
- Wrong architecture warnings in gdb?Use gdb-multiarch (not the host’s default gdb). The target is aarch64/virt64.
What you learn
- How LVGL’s timer engine orchestrates redraws.
- Where your app code participates in the event → draw pipeline.
- How to pause anywhere, read a precise call stack, and step through rendering.
That’s it — enjoy LVGL debugging with Infrabase!
If you haven’t read the intro to our BitBake/OpenEmbedded setup yet, start here.Questions or tips from your own sessions are very welcome in the thread!
