8.3 KiB
title | author | tags | |
---|---|---|---|
Introducing App Debugging | katharine |
|
Happy leap day! Today is a once-every-four-years day of bizarre date-related bugs, and thus an opportune moment for us to introduce our latest developer feature: app debugging in the emulator! This gives you a powerful new way to hunt down errors in your apps.
A new command is available in our preview release of pebble tool 4.2 and
SDK 3.10: pebble gdb
.
Running this command from a project directory on an emulator with your app
installed will pause the emulator and attach a gdb debugger instance
to it:
katharine@scootaloo ~/p/pebble-caltrain (master)> pebble gdb --emulator basalt
Reading symbols from /Users/katharine/Library/Application Support/Pebble SDK/SDKs/3.10-beta4/sdk-core/pebble/basalt/qemu/basalt_sdk_debug.elf...(no debugging symbols found)...done.
Remote debugging using :49229
0x0801cd8c in ?? ()
add symbol table from file "/Users/katharine/projects/pebble-caltrain/build/basalt/pebble-app.elf" at
.text_addr = 0x200200a8
.data_addr = 0x20023968
.bss_addr = 0x200239b8
Reading symbols from /Users/katharine/projects/pebble-caltrain/build/basalt/pebble-app.elf...done.
Breakpoint 1 at 0x804aebc
Press ctrl-D or type 'quit' to exit.
Try `pebble gdb --help` for a short cheat sheet.
Note that the emulator does not yet crash on memory access violations.
(gdb)
Do note that once we pause execution by launching gdb, emulator buttons and
any pebble tool command that interacts with the emulator won't work until we
continue execution (using continue
or c
) or quit gdb.
Once we're here, we can add some breakpoints to the app in interesting places.
Here we are debugging my
Caltrain app. Let's say I think
there's a bug in the search for the next train: I probably want to poke around
in find_next_train
.
We can run b find_next_train
to add a breakpoint there:
(gdb) b find_next_train
Breakpoint 2 at 0x20020f1e: file ../src/planning.c, line 5.
(gdb)
Now we can use the c
or continue
command to set my app running again, until
it stops at find_next_train
:
(gdb) c
Continuing.
The app runs as usual until we open a station, which causes it to look up a train, where it hits the breakpoint and pauses the app so we can inspect it:
Breakpoint 2, find_next_train (count=184, times=0x2002f414, nb=0x2001873c,
sb=0x20018738) at ../src/planning.c:5
5 TrainTime *best[2] = {NULL, NULL};
(gdb)
Now we can see how we got here using the backtrace
or bt
command:
(gdb) bt
#0 find_next_train (count=184, times=0x2002f414, nb=0x2001873c, sb=0x20018738)
at ../src/planning.c:7
#1 0x200211b2 in next_train_at_station (station=13 '\r',
northbound=0x20025a0c <s_northbound>, southbound=0x20025a14 <s_southbound>)
at ../src/planning.c:76
#2 0x200215c8 in prv_update_times () at ../src/stop_info.c:106
#3 0x200216f8 in show_stop_info (stop_id=13 '\r') at ../src/stop_info.c:174
#4 0x200219f0 in prv_handle_menu_click (menu_layer=0x2002fe3c,
cell_index=0x2002ff0c, context=0x2002fe3c) at ../src/stop_list.c:57
#5 0x0805cb1c in ?? ()
#6 0x0805a962 in ?? ()
#7 0x0801ebca in ?? ()
#8 0x0801e1fa in ?? ()
#9 0x200202d6 in main () at ../src/main.c:23
#10 0x080079de in ?? ()
#11 0x00000000 in ?? ()
The ??
entries are inside the pebble firmware; the rest are in the Caltrain
app.
We can step forward a few times to get to an interesting point using the step
or s
command:
(gdb) s
7 const time_t timestamp = time(NULL);
(gdb) s
8 const uint16_t minute = current_minute();
(gdb) s
current_minute () at ../src/model.c:183
183 const time_t timestamp = time(NULL);
(gdb)
Now we've stepped into another function, current_minute
. Let's say we're
confident in the implementation of this (maybe we wrote unit tests), so we can
jump back up to find_next_train
using the finish
command:
(gdb) finish
Run till exit from #0 current_minute () at ../src/model.c:183
0x20020f38 in find_next_train (count=184, times=0x2002f414, nb=0x2001873c,
sb=0x20018738) at ../src/planning.c:8
8 const uint16_t minute = current_minute();
Value returned is $2 = 738
(gdb)
When we step to the next line, we see it has a similar current_day
that we
don't need to inspect closely, so we jump over it using the next
or n
command:
(gdb) s
9 const uint8_t day = current_day();
(gdb) n
11 for(int i = 0; i < count; ++i) {
(gdb)
Now we can double check our current state by using info locals
to look at all
our local variables, and info args
to look at what was originally passed in:
(gdb) info locals
i = 184
best = {0x0 <__pbl_app_info>, 0x0 <__pbl_app_info>}
timestamp = 1456776942
minute = 738
day = 1 '\001'
(gdb) info args
count = 184
times = 0x2002f414
nb = 0x2001873c
sb = 0x20018738
(gdb)
timestamp
, minute
and day
all have the values they gained from our last
few function calls. best
is still a pair of NULL pointers, and i
hasn't been
assigned yet, so its value is garbage. Once we step another line it'll be filled
in, which we can check using the print
or p
command:
(gdb) s
12 TrainTime *train_time = &times[i];
(gdb) p i
$3 = 0
Now let's step forward and have it fill in train_time
, and see what we get:
(gdb) s
14 trip_get(train_time->trip, &trip);
(gdb) p train_time
$4 = (TrainTime *) 0x2002f414
(gdb)
This is unenlightening — it's just the same pointer as times
, which is what we
expect when referencing ×[0]
. Fortunately, print
/p
will evaluate
arbitrary expressions, so we can dereference the pointer to see what it actually
points at:
(gdb) p *train_time
$5 = {trip = 189, time = 309, stop = 13 '\r', sequence = 10 '\n'}
(gdb)
Better! It might be more interesting to just print that out for each loop
iteration, so let's set a breakpoint here and have it print *train_time
and continue:
(gdb) b
Breakpoint 3 at 0x20020f62: file ../src/planning.c, line 14.
(gdb) commands
Type commands for breakpoint(s) 3, one per line.
End with a line saying just "end".
>p *train_time
>c
>end
(gdb) c
Continuing.
Breakpoint 3, find_next_train (count=184, times=0x2002f414, nb=0x2001873c,
sb=0x20018738) at ../src/planning.c:14
14 trip_get(train_time->trip, &trip);
$6 = {trip = 209, time = 344, stop = 13 '\r', sequence = 11 '\v'}
Breakpoint 3, find_next_train (count=184, times=0x2002f414, nb=0x2001873c,
sb=0x20018738) at ../src/planning.c:14
14 trip_get(train_time->trip, &trip);
$7 = {trip = 199, time = 345, stop = 13 '\r', sequence = 13 '\r'}
…and so on. A bit noisy, so let's remove that breakpoint now:
(gdb) delete 3
(gdb)
Finally, let's have our program continue on its way by running c
again:
(gdb) c
Continuing
When we want to get out of gdb we'll need our (gdb)
prompt back, so press
ctrl-C to pause the app again:
^C
Program received signal SIGINT, Interrupt.
0x08007072 in ?? ()
(gdb)
This will most likely pause execution inside some firmware code, as we did when we initially launched gdb. We can now do anything we've done before, but we're just going to quit:
(gdb) quit
A debugging session is active.
Inferior 1 [Remote target] will be killed.
Quit anyway? (y or n) y
katharine@scootaloo ~/p/pebble-caltrain (master)>
Hopefully this has given you some ideas as to how you might be able to use gdb to debug your own apps. If you'd like to know more about gdb, here is a Q&A-style tutorial that will answer many questions you might have. Good luck and happy debugging!