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