DS GDB Debugger Stub
This page contains a GDB debugger stub library which allows you to debug your DS homebrew
using a ARM targeted version of GDB.
The stub is in the early stages of development but you can do useful things with it so it has
been made available.
You can discuss the stub over on the gbadev.org forums.
The top one is the most recent.
What has changed?
Release 20061010
- Change to communications interface to increase TCP performance (requires dswifi hack).
Release 20060831
- Added support for G command (setting register values).
- All PC affecting Thumb instructions can be stepped.
- ARM LDR and LDM instructions affecting the PC can be stepped.
- Experimental ARM9 monitor .nds build added (see below).
- Some corrections.
Release 20060815
- Partial stepping - non program counter affecting instructions can be stepped. Thumb branch and pop
instructions can be stepped. ARM branch instructions can be stepped.
- Corrected r15 value given to GDB - GDB expects the r15 value to be the address of the current instruction
not the value as if read from the register.
- Avoids some data aborts when reading/writing memory via GDB - only allows access to addresses above
0x02000000.
- Change to directory structure - pre-built libraries and headers are available in the
libs
and include directories. Debug comms source moved into subdirectories.
- Some corrections.
ToDo List
- ARM7 support.
- Be able to break a running target (Ctrl-C command).
- Continue from a different address.
- Full stepping
- Handle breakpoints on the DS. Not sure if this will happen as GDB can step over breakpoints with stepping
implemented.
- Read and write memory using the requested word size, i.e. 8bit, 16bit, 32bit. Would be useful for reading
and setting registers.
- Detect, as different, a data and prefetch abort. If a data abort occurs the DS app is very likely to die as it
is assumed to be a pretch abort and will use the wrong return address.
- Wait for GDB to reconnect if the TCP socket disconnects.
What Is Supported
Partial stepping (certain pc affecting instructions cannot be stepped), continue, memory read and write
and reading the register contents are supported at the moment.
Breakpointing is all handled by GDB replacing instructions in the code with undefined instructions (arm code)
or breakpoint instruction (thumb code) and therefore causing an exception when these are executed.
The stub does not maintain the breakpoints at all.
If you try to use unsupported features then the stub will break.
With stepping partially supported, there is no need to clear a breakpoint before continuing after the
breakpoint has been hit.
Stub to PC communications
The communications and the debug stub logic are seperate (build into seperate libraries).
The communications interface is given to the debug stub at runtime. Therefore you can use
whatever mechanism you want for PC to stub comms.
Currently wireless TCP network comms (using dswifi library - tested with version 0.3a) and
my SPI/UART bridge serial port comms have been implemented. The TCP network comms use the Nintendo
WFC settings for connecting to the AP and get the IP address.
Implementing a Communications Mechanism
If you want to implement your own comms mechanism have a look at the debug_tcp.c and debug_serial.c files
to see two seperate examples.
PC Side Setup
You will need a version of GDB that understands an ARM target. Windows users can use Insight/GDB available
as part of devKitPro. For Linux you can get a pre compiled GDB for ARM targets from
www.codesourcery.com (you will
be wanting the eabi version).
For other platforms you may be able to build GDB from
the source. Read through the READMEs in the source
tree for help with building. The target you need to set is arm-none-eabi
(./configure --target=arm-none-eabi). This has worked for me (under Linux) using version 6.4 of GDB.
For other platforms and Linux, you can get a visual interface by compiling
Insight (which includes GDB). Set the target in the same way
as described for compiling GDB.
Debugging the Demo Application using TCP
Put debug_tcp.nds on your DS by some means. This will run and connect to the AP and get an IP
and the like. At this point the DS will sit waiting for somebody to connect to it (tcp port 30000 in the demo).
GDB will needs the corresponding .elf file to correctly inteprete what the DS stub is telling it which is
debug_tcp.arm9.elf.
GDB must be told how to connect to the DS. If using the command prompt, to connect over TCP requires a
command in the following form:
target remote ip_addr:port_number
Your DS will have displayed its IP address if the wifi side connected correctly. For my DS and using the demo
app I type the following:
target remote 192.168.12.2:30000
If succesful the DS stub will now be connected to GDB.
The demo app immediately causes an exception (calling debugHalt()) jumping into the debug stub.
This allows the DS stub and GDB to pass messages and get in sync (without this the GDB could timeout
waiting for a response from the stub).
You can now do some debugging, remembering the restrictions on what is supported.
If the connection is lost for some reason you will have to restart the DS.
Using the Debug Stub with Your Applications
This guide assumes you are using TCP comms.
The Debug Stub Libraries and Header Files
Inside the libs directory you will find a number of libraries.
- libdebugstub9.a - the gdb debugger stub
- libdebugtcp9.a - the dswifi TCP network comms code
- libdebugserial9.a - spi/uart bridge comms code
- libserial9.a - spi/uart bridge driver code
Copy the first two to somewhere where the linker will search for libraries (your libnds/lib
directory will do). Inside the include directory you will find some header files.
- debug_stub.h - the application facing debug stub function interface.
- debug_tcp.h - the debug stub TCP comms interface.
- debug_serial.h - the debug stub spi/uart bridge comms interface.
- spi_uart_bridge.h - spi/uart bridge driver interface.
Copy the first two to somewhere the compile will search for header files (your libnds/include directory will do).
Your Application
You will need to enable wifi in your ARM7 code (an example is in the arm7 directory).
In your ARM9 code include the stub and comms header:
#include <debug_stub.h>
#include <debug_tcp.h>
When using TCP comms you must call irqInit() before initialising the debug stub.
Create and initialise a struct tcp_debug_comms_init_data giving the port number to use to listen for
GDB connections. Then call the init_debug function passing it a pointer to the TCP comm interface
structure (tcpCommsIf_debug - defined in debug_tcp.h) and a pointer to the
struct tcp_debug_comms_init_data structure you created earlier. Here is an example using port 30000:
{
struct tcp_debug_comms_init_data init_data = {
.port = 30000
};
if ( !init_debug( &tcpCommsIf_debug, &init_data)) {
iprintf("Failed to initialise the debugger stub - cannot continue\n");
while(1);
}
}
Now call debugHalt() to cause your program to jump into the debug stub and allow it to
sync up with the GDB debugger.
To debug your application follow the instructions given for the demo app in the first post.
Source in the monitor and build_things directories.
The ARM9 monitor (added version 20060831) allows an ARM9
application to be debugged by downloading the code and data from an ELF file. The application
does not have to be debugger aware, only needing to be built against a special version of
libnds and the ARM startup code. The monitor lives in the first 256K of the DS main memory.
Application Restrictions
The application being debugged cannot install an exception handler or using the ITCM memory
area.
Any functionality required for debug communications mechanism cannot be used by the
appliation. For example, if using TCP for debugging the application cannot use any
wireless functionality.
The main memory used by the monitor is not available to the appliation. This restriction
is defined in the linker file build_things/ds_debug_arm9.ld.
Special libnds build
libs/libdebugnds9.a
The ARM9 interrupt handler in libnds installs itself into ITCM memory. The special build of
libnds simply places the interrupt handler into main memory.
ARM9 startup code
build_things/ds_debug_arm9_crt0.asm
The ARM9 startup code is the same as the normal code but with the protection unit setup,
stack pointers setup and copying to ITCM and DTCM memory removed.
The build_things/ds_debug_arm9.specs file directs the application to use the
startup code.
Debugging the Test Application
An example application can be found in the monitor directory. The source for the
application is main.c and some_thumb_code.c and the makefile is
Makefile.app. Note that the application does not get built to a .nds file but
only to the .elf stage (test_app.elf)
In order to debug the application, run the monitor on the target DS (monitor_tcp.nds in
most cases). This will leave the DS with two white screens and, if the wifi connects correctly,
the power LED blinking. At this point the monitor is ready for GDB to connect.
Startup GDB/Insight with the test_app.elf file and connect to the target (see above).
Download the application to the target using the load command or through the Insight
menu. GDB sets the monitor to execute the startup code of the downloaded application.
The application can now be debugged as normal. NOTE: since there is no way to
break a running target make sure to set some breakpoints before continuing.
This hack is against the dswifi library CVS source dated 2006-10-10.
The dswifi library TCP implementation, like most others, buffers data before sending out a TCP packet
in order to reduce the number of packets sent. The buffered data is only sent once a threshold amount of
data is ready or the buffered data have been waiting for a period of time.
Unfortunately this causes the debug stub TCP communication to perform badly as the stub and GDB often
communicate using small packets. Therefore the small packets get buffered and have to wait for the timeout
before they are sent.
Berkley socket implementations usually have a socket option (TCP_NODELAY) to cause the TCP packet
to be sent immediately when the send() function is called. The dswifi library has not implemented this
option yet. To hardcode this no delay behaviour change the sgIP_TCP_Send in the sgIP_TCP.c file as described
below.
Find the following if statement (about line 862).
if(j>SGIP_TCP_TRANSMIT_IMMTHRESH && rec->tcpstate==SGIP_TCP_STATE_ESTABLISHED) {
Remove the threshold comparison by changing to the following.
if( rec->tcpstate==SGIP_TCP_STATE_ESTABLISHED) {
|