--- # Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. title: Debugging with GDB description: | How to use GDB to debug a Pebble app in the emulator. guide_group: debugging order: 0 --- As of SDK 3.10 (and [Pebble Tool](/guides/tools-and-resources/pebble-tool) 4.2), developers can use the powerful [GDB](https://www.gnu.org/software/gdb/) debugging tool to find and fix errors in Pebble apps while they are running in an emulator. GDB allows the user to observe the state of the app at any point in time, including the value of global and local variables, as well as current function parameters and a backtrace. Strategically placing breakpoints and observing these values can quickly reveal the source of a bug. {% alert notice %} GDB cannot be used to debug an app running on a real watch. {% endalert %} ## Starting GDB To begin using GDB, start an emulator and install an app: ```text $ pebble install --emulator basalt ``` Once the app is installed, begin using GDB: ```text $ pebble gdb --emulator basalt ``` Once the `(gdb)` prompt appears, the app is paused by GDB for observation. To resume execution, use the `continue` (or `c`) command. Similarly, the app can be paused for debugging by pressing `control + c`. ```text (gdb) c Continuing. ``` A short list of useful commands (many more are available) can be also be obtained from the `pebble` tool. Read the [*Emulator Interaction*](/guides/tools-and-resources/pebble-tool/#gdb) section of the {% guide_link tools-and-resources/pebble-tool %} guide for more details on this list. ```text $ pebble gdb --help ``` ## Observing App State To see the value of variables and parameters at any point, set a breakpoint by using the `break` (or `b`) command and specifying either a function name, or file name with a line number. For example, the snippet below shows a typical ``TickHandler`` implementation with line numbers in comments: ```c /* 58 */ static void tick_handler(struct tm *tick_time, TimeUnits changed) { /* 59 */ int hours = tick_time->tm_hour; /* 60 */ int mins = tick_time->tm_min; /* 61 */ /* 62 */ if(hours < 10) { /* 63 */ /* other code */ /* 64 */ } /* 65 */ } ``` To observe the values of `hours` and `mins`, a breakpoint is set in this file at line 61: ```text (gdb) b main.c:61 Breakpoint 2 at 0x200204d6: file ../src/main.c, line 61. ``` > Use `info break` to see a list of all breakpoints currently registered. Each > can be deleted with `delete n`, where `n` is the breakpoint number. With this breakpoint set, use the `c` command to let the app continue until it encounters the breakpoint: ```text $ c Continuing. ``` When execution arrives at the breakpoint, the next line will be displayed along with the state of the function's parameters: ```text Breakpoint 2, tick_handler (tick_time=0x20018770, units_changed=(SECOND_UNIT | MINUTE_UNIT)) at ../src/main.c:62 62 if(hours < 10) { ``` The value of `hours` and `mins` can be found using the `info locals` command: ```text (gdb) info locals hours = 13 mins = 23 ``` GDB can be further used here to view the state of variables using the `p` command, such as other parts of the `tm` object beyond those being used to assign values to `hours` and `mins`. For example, the day of the month: ```text (gdb) p tick_time->tm_mday $2 = 14 ``` A backtrace can be generated that describes the series of function calls that got the app to the breakpoint using the `bt` command: ```text (gdb) bt #0 segment_logic (this=0x200218a0) at ../src/drawable/segment.c:18 #1 0x2002033c in digit_logic (this=0x20021858) at ../src/drawable/digit.c:141 #2 0x200204c4 in pge_logic () at ../src/main.c:29 #3 0x2002101a in draw_frame_update_proc (layer=, ctx=) at ../src/pge/pge.c:190 #4 0x0802627c in ?? () #5 0x0805ecaa in ?? () #6 0x0801e1a6 in ?? () #7 0x0801e24c in app_event_loop () #8 0x2002108a in main () at ../src/pge/pge.c:34 #9 0x080079de in ?? () #10 0x00000000 in ?? () ``` > Lines that include '??' denote a function call in the firmware. Building the > app with `pebble build --debug` will disable some optimizations and can > produce more readable output from GDB. However, this can increase code size > which may break apps that are pushing the heap space limit. ## Fixing a Crash When an app is paused for debugging, the developer can manually advance each statement and precisely follow the path taken through the code and observe how the state of each variable changes over time. This is very useful for tracking down bugs caused by unusual input to functions that do not adequately check them. For example, a `NULL` pointer. The app code below demonstrates a common cause of an app crash, caused by a misunderstanding of how the ``Window`` stack works. The ``TextLayer`` is created in the `.load` handler, but this is not called until the ``Window`` is pushed onto the stack. The attempt to set the time to the ``TextLayer`` by calling `update_time()` before it is displayed will cause the app to crash. ```c #include static Window *s_window; static TextLayer *s_time_layer; static void window_load(Window *window) { Layer *window_layer = window_get_root_layer(window); GRect bounds = layer_get_bounds(window_layer); s_time_layer = text_layer_create(bounds); text_layer_set_text(s_time_layer, "00:00"); text_layer_set_text_alignment(s_time_layer, GTextAlignmentCenter); layer_add_child(window_layer, text_layer_get_layer(s_time_layer)); } static void update_time() { time_t now = time(NULL); struct tm *tick_time = localtime(&now); static char s_buffer[8]; strftime(s_buffer, sizeof(s_buffer), "%H:%M", tick_time); text_layer_set_text(s_time_layer, s_buffer); } static void init() { s_window = window_create(); window_set_window_handlers(s_window, (WindowHandlers) { .load = window_load }); update_time(); window_stack_push(s_window, true); } static void deinit() { window_destroy(s_window); } int main() { init(); app_event_loop(); deinit(); } ``` Supposing the cause of this crash was not obvious from the order of execution, GDB can be used to identify the cause of the crash with ease. It is known that the app crashes on launch, so the first breakpoint is placed at the beginning of `init()`. After continuing execution, the app will pause at this location: ```text (gdb) b init Breakpoint 2 at 0x2002010c: file ../src/main.c, line 26. (gdb) c Continuing. Breakpoint 2, main () at ../src/main.c:41 41 init(); ``` Using the `step` command (or Enter key), the developer can step through all the statements that occur during app initialization until the crash is found (and the `app_crashed` breakpoint is encountered. Alternatively, `bt full` can be used after the crash occurs to inspect the local variables at the time of the crash: ```text (gdb) c Continuing. Breakpoint 1, 0x0804af6c in app_crashed () (gdb) bt full #0 0x0804af6c in app_crashed () No symbol table info available. #1 0x0800bfe2 in ?? () No symbol table info available. #2 0x0800c078 in ?? () No symbol table info available. #3 0x0804c306 in ?? () No symbol table info available. #4 0x080104f0 in ?? () No symbol table info available. #5 0x0804c5c0 in ?? () No symbol table info available. #6 0x0805e6ea in text_layer_set_text () No symbol table info available. #7 0x20020168 in update_time () at ../src/main.c:22 now = 2076 tick_time = s_buffer = "10:38\000\000" #8 init () at ../src/main.c:31 No locals. #9 main () at ../src/main.c:41 No locals. #10 0x080079de in ?? () No symbol table info available. #11 0x00000000 in ?? () No symbol table info available. ``` The last statement to be executed before the crash is a call to `text_layer_set_text()`, which implies that one of its input variables was bad. It is easy to determine which by printing local variable values with the `p` command: ```text Breakpoint 4, update_time () at ../src/main.c:22 22 text_layer_set_text(s_time_layer, s_buffer); (gdb) p s_time_layer $1 = (TextLayer *) 0x0 <__pbl_app_info> ``` In this case, GDB displays `0x0` (`NULL`) for the value of `s_time_layer`, which shows it has not yet been allocated, and so will cause `text_layer_set_text()` to crash. And thus, the source of the crash has been methodically identified. A simple fix here is to swap `update_time()` and ``window_stack_push()`` around so that `init()` now becomes: ```c static void init() { // Create a Window s_window = window_create(); window_set_window_handlers(s_window, (WindowHandlers) { .load = window_load }); // Display the Window window_stack_push(s_window, true); // Set the time update_time(); } ``` In this new version of the code the ``Window`` will be pushed onto the stack, calling its `.load` handler in the process, and the ``TextLayer`` will be allocated and available for use once execution subsequently reaches `update_time()`.