Debugging with XGDB

XGDB is an extension of GDB which adds support for multi-tile debugging of Xcore applications in the form of XE files. Therefore, to the greatest possible extent, XGDB behaves like GDB and therefore most information can be found on 3rd-party websites and resources.

Its use for debugging multi-tile applications is more unusual; this example aims at demonstrating how XGDB can be used to follow an Xcore application as control passes from core to core and tile to tile.

Create example

To illustrate the use of XGDB for debugging, we create a token-passing ring operating across two tiles:

mapfile.xc
#include <platform.h>

typedef chanend chanend_t;

extern "C" {
  void main_tile0(chanend_t, chanend_t);
  void main_tile1(chanend_t, chanend_t);
}

int main(void)
{
  chan tile0_to_tile1;
  chan tile1_to_tile0;

  par {
    on tile[0]: main_tile0(tile1_to_tile0, tile0_to_tile1);
    on tile[1]: main_tile1(tile0_to_tile1, tile1_to_tile0);
  }

  return 0;
}

On each tile, we pass the token via two cores. The ring is kick-started by tile[1]:

main.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <xcore/channel.h>
#include <xcore/parallel.h>

DECLARE_JOB(process, (chanend_t, chanend_t, char *, int));
void process(chanend_t cIn, chanend_t cOut, char * name, int init)
{
  if (init)
  {
    chan_out_word(cOut, 1);
  }

  while (1)
  {
    int token = chan_in_word(cIn);
    printf("%s\n", name);
    chan_out_word(cOut, token);
  }
}

void main_tile0(chanend_t cIn, chanend_t cOut)
{
  channel_t chan = chan_alloc();

  PAR_JOBS(
    PJOB(process, (cIn, chan.end_a, "tile0-core0", 0)),
    PJOB(process, (chan.end_b, cOut, "tile0-core1", 0)));

  chan_free(chan);
}

void main_tile1(chanend_t cIn, chanend_t cOut)
{
  channel_t chan = chan_alloc();

  PAR_JOBS(
    PJOB(process, (cIn, chan.end_a, "tile1-core0", 0)),
    PJOB(process, (chan.end_b, cOut, "tile1-core1", 1))); // Kick-start the ring

  chan_free(chan);
}

Build the example as normal, remembering to use xcc -g:

$ xcc -target=XCORE-200-EXPLORER -g mapfile.xc main.c

Running the example produces the expected results:

$ xrun --io a.xe
tile0-core0
tile0-core1
tile1-core0
tile1-core1
tile0-core0
tile0-core1
...

Interactive debugging

We’ll now use XGDB to observe the multi-tile example as the token is passed from core to core and tile to tile.

To start an interactive debug session:

$ xgdb a.xe

A familiar GDB prompt will be raised. Connect to a target and load the application using the connect and load commands:

(gdb) connect
0x00040000 in _start ()
(gdb) load
Loading setup image to XCore 0
...

Using the tile command, select a tile as the focus of subsequent commands:

(gdb) tile 0
[Switching to task 1 (tile[0] core[0])]#0  0x00040000 in _start ()

Add a breakpoint in the usual way:

(gdb) break main.c:16
Breakpoint 1 at 0x40156: file Z:/docs/docs_tools/tools-user-guide/tools-guide/quick-start/examples/debug\main.c, line 16.

Switch to the tile[1] and add a breakpoint there too:

(gdb) tile 1
[Switching to task 2 (tile[1] core[0])]#0  0x00040000 in _start ()
(gdb) break main.c:16
Breakpoint 2 at 0x4015a: file Z:/docs/docs_tools/tools-user-guide/tools-guide/quick-start/examples/debug\main.c, line 16.

Review all the breakpoints. Notice how the breakpoint appears at different addresses on each tile - this is not unexpected, as the two tiles have entirely independent address spaces with potentially different contents:

(gdb) info breakpoints
Num Type           Disp Enb  Core Address    What
1   breakpoint     keep y    0    0x00040156 in process at Z:/docs/docs_tools/tools-user-guide/tools-guide/quick-start/examples/debug\main.c:16
2   breakpoint     keep y    1    0x0004015a in process at Z:/docs/docs_tools/tools-user-guide/tools-guide/quick-start/examples/debug\main.c:16

We’re now ready to run the multi-tile application. Use the continue and info threads commands to run the application to the next breakpoint and examine which tile/core it has halted at:

(gdb) continue
[Switching to tile[0] core[0]]

Breakpoint 1, process (cIn=2147614978, cOut=2147615234, name=0x4511c "tile0-core0", init=0) at Z:/docs/docs_tools/tools-user-guide/tools-guide/quick-start/examples/debug\main.c:16
16          printf("%s\n", name);
Current language:  auto; currently minimal
(gdb) info threads
  4  tile[1] core[1] (dual issue)  0x000403c4 in chan_in_word ()
  3  tile[1] core[0] (dual issue)  0x000403c4 in chan_in_word ()
  2  tile[0] core[1] (dual issue)  0x000403f4 in chan_in_word ()
* 1  tile[0] core[0]  process (cIn=2147614978, cOut=2147615234, name=0x4511c "tile0-core0", init=0) at Z:/docs/docs_tools/tools-user-guide/tools-guide/quick-start/examples/debug\main.c:16

The asterisk next to each thread shows which tile/core currently has focus for subsequent commands. Watch the asterisk move as the token moves round the ring by repeatedly issuing continue and info threads.

To end the interactive debug session:

(gdb) quit

Scripted debugging

Use of XGDB can be fully or partially scripted using Command Files (just like GDB). Scripted debugging using Command Files can be very powerful. It allows developers to quickly reproduce a particular scenarios, and even share those scenarios with other developers.

To start a scripted debug session, use xgdb -ex with the standard GDB source command:

$ xgdb -ex "source -v cmds.txt" a.xe

This causes the following command file to be executed prior to any interactive debugging; the trailing quit in the example below means that here there will be no interaction and thus the example is fully scripted.

cmds.txt
# Setup
connect
load

# Add breakpoints
tile 0
break main.c:16
tile 1
break main.c:16
info breakpoints

# Run
set $count=5
while $count > -1
  continue
  info threads
  set $count=$count-1
end

quit

Summary

This tutorial has demonstrated how XGDB can be used to debug a multi-tile Xcore application. To start exploring further XMOS-specific commands built on top of GDB, try:

(gdb) help xmos