Debugging nesC code in GDB

gdb does not (yet!) have a nesC-specific mode. Instead, in gdb you are effectively debugging the C code generated by the nesC compiler. However, the nesC compiler includes #line directives in the code it generates, so single-stepping through nesC code will display the correct nesC source code, and breakpoints can be set based on the line numbers and file names of nesC components. The situation for variable, function, command and event names is however not as straightforward. If you wish to refer to one of these, you must use its name in the generated C code, as explained below.

When debugging tossim code, life is further complicated by the fact that the generated C code emulates multiple motes. Thus all module variables (but not global variables in C files) become arrays, indexed by mote id (if the variable was itself an array, the mote id is the first dimension). At any point in time, the "current" mote is found in tos_state.current_node.

By default, nesC does a lot of inlining, which makes debugging tricky. In most cases, pass the -g -O1 -fnesc-no-inline options to nesC to produce code that is easier to debug (the debug option to the standard TinyOS Makefile does this). If you have a problem which requires debugging fully optimised code, just include -g and prepare for a slightly painful experience... (the debugopt to the standard TinyOS Makefile does this).

Mapping from nesC names to C names

Types, variables and functions in C files (included via the includes statement) are left unchanged in the generated C code, except if they correspond to a nesC keyword. In this last case, the name is prefixed with __nesc_keyword_, so components becomes __nesc_keyword_components.

A module variable (top-level data declarations in modules)  X in module M is called M$X in the generated C code.

A function F in module M is called M$F in the generated C code.

Local variable names in modules are left unchanged in the generated C code.

A command or event C in module M is called M$C in the generated C code.

A command or event C of interface instance I in module M is called M$I$C in the generated C code.

To complicate matters a little, gdb does not directly accept $ in function names in break (set a breakpoint) statements. Instead, you must precede the function name with a *, e.g, b *BlinkM$StdControl$init. Note that this sets a breakpoint on the first instruction of the function (normally part of the function preamble setting up the function's stack frame) rather than on the first executable statement of the function. As a result, gdb may not correctly display argument values, etc, until you single-step into the function body.

Example

This example debugs the CntToLeds application from TinyOS on a mica mote, using on-chip debugging with a JTAG ICE pod (see this document for instructions on setting the JTAG ICE up):

We compile the application with debugging and no inlining by passing the TinyOS-specific debug option to make:
[dgay@barnowl CntToLeds]$ make mica debug
    compiling CntToLeds to a mica binary
ncc -o build/mica/main.exe -O1 -g -fnesc-no-inline -board=micasb -target=mica -I%T/lib/Counters -Wall -Wshadow -DDEF_TOS_AM_GROUP=0x42 -Wnesc-all -finline-limit=100000 -fnesc-cfile=build/mica/app.c  CntToLeds.nc -lm
    compiled CntToLeds to build/mica/main.exe
            2588 bytes in ROM
              46 bytes in RAM
avr-objcopy --output-target=srec build/mica/main.exe build/mica/main.srec

We start ice-gdb to download and debug CntToLeds:
[dgay@barnowl CntToLeds]$ ice-gdb build/mica/main.exe
AVaRICE version 2.0.20030821cvs, Aug 21 2003 15:36:04
 
JTAG config starting.
Hardware Version: 0xc0
Software Version: 0x69
Reported JTAG device ID: 0x9702
Configured for device ID: 0x9702 atmega128
LockBits -> 0xff
 
Reading Fuse Bytes:
  Extended Fuse byte -> 0xfd
      High Fuse byte -> 0x19
       Low Fuse byte -> 0xfe
JTAG config complete.
Downloading FLASH image to target......................
 
Download complete.
Waiting for connection on port 6423.
GNU gdb cvs-pre6.0-tinyos
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "--host=i686-pc-linux-gnu --target=avr"...
Connection opened by host 127.0.0.1, port 33805.
0x00000000 in __vectors ()

The program is stopped. We set a breakpoint at the Timer.fired event in the Counter module. Note the *:
(gdb) b *Counter$Timer$fired
Hardware assisted breakpoint 1 at 0x826: file /home/dgay/motes/tinyos-1.x/tos/lib/Counters/Counter.nc, line 67.

And let the program continue:
(gdb) c
Continuing.
 
Breakpoint 1, Counter$Timer$fired () at /home/dgay/motes/tinyos-1.x/tos/lib/Counters/Counter.nc:67
67          state++;

The breakpont was reached. We continue again...
(gdb) c
Continuing.
 
Breakpoint 1, Counter$Timer$fired () at /home/dgay/motes/tinyos-1.x/tos/lib/Counters/Counter.nc:67
67          state++;

Let's examine the state variable of the Counter module:
(gdb) p Counter$state
$1 = 1

And then follow where the IntOutput.output command takes us.
(gdb) n
68          return call IntOutput.output(state);
(gdb) s
Counter$IntOutput$output (arg_0x84fde28=2) at /home/dgay/motes/tinyos-1.x/tos/interfaces/IntOutput.nc:52
52        command result_t output(uint16_t value);

The debugger shows us the command in the interface, we must step again to reach our destination:
(gdb) s
IntToLedsM$IntOutput$output (value=2) at /home/dgay/motes/tinyos-1.x/tos/lib/Counters/IntToLedsM.nc:70
70          if (value & 1) call Leds.redOn();

The names of local variables (and function parameters) are unchanged:
(gdb) p value
$2 = 2

That's all folks!
(gdb) quit