How to implement a stub or server using RDA (Red Hat Debug Agent and library). ================================================== Andrew Cagney Fernando Nasser Frank Eigler Introduction ------------ This is the implementation of a server for the gdb remote protocol. It is available as a host library that provides most of the common code required. By adding a relatively small amount of target dependent code, one can build a server that allows gdb to connect to that target using the gdb "target remote" command. Gdb connects to this server through a TCP/IP connection. The server waits for a connection from GDB by listening to a non-restricted TCP port. Anyone with GDB (or even telnet) network connectivity to the machine running the server can initiate a connection to it. Note: The user of this software is advised to ensure that access to the TCP port being used is restricted to secure siblings. For instance, the user may need to take action that ensures that `The Internet' is not able to access the TCP port being used. Architecture ------------ The following diagram shows the components of RDA. (GDB) <---"remote"---> CLIENT <--> SERVER <--> TARGET ------- ------------------------------ The client. The server. Where: CLIENT Provides the raw interface between GDB and the internal SERVER mechanism. The client passes on any data relevant to the server. The SERVER presents CLIENT with raw output to be returned to GDB. SERVER Implements the state-machine that is capable of decoding / processing various GDB remote protocol requests. TARGET The embedded system proper. SERVER passes decoded requests onto the TARGET while TARGET notifies the SERVER of any target state changes. Looking at SERVER in more detail, that component consists of three sub components INPUT, OUTPUT and STATE. Visually: .------------ fromtarget_* \|/ fromclient_* -> INPUT -> STATE (M/C) -> target->* /|\ | | \|/ | client->write <- OUTPUT <----' Where: INPUT Filters the raw input stream from the client breaking it up into complete packets and passing them onto STATE. This component co-operates with OUTPUT for the ACK/NAK of input packets. OUTPUT Wraps up and then passes onto the client raw output packets ready for transmission on the output stream. This component co-operates with INPUT for the ACK/NAK of output packets. STATE The state machine proper. The Appendix provides various scenarios showing in some detail how typical operations are handled by these components. It may be convenient to read the next section first before turning to the Appendix. Adding the target dependent code -------------------------------- In the following discussion, assume your debugger (gdb) is using the remote protocol to communicate with the code you are building here. The "target" is a simulator, hardware device, board, program or whatever you are interfacing to with your stub/server. The library provides gdbsocket_*() and gdbserv_fromtarget_*() routines that must be called and requires you to set a series of callback routines. You make these callbacks known/available to the library by filling the entries of a "gdbserv_target" object. You must also provide an extra function that will be invoked when a new connection is detected on the input port (a socket). Handling the connection: The server is capable of handling several connections (named, sessions) simultaneously. This is possible because each session data is kept on an instance of a GDBSERV object. A routine called gdbserv_fromclient_attach() is called when a connection request is received by the socket. If the connection is accepted, a new instance of a GDBSERV object is returned, otherwise NULL is returned indicating that it has been rejected (a EOF will be sensed on the other end). The server will, in turn, pass the request onto the target. The target can either reject the connection (returning NULL) or accept the connection (returning a target object). That is where you come in. You must provide a routine like (please see the precise API in the header file lib/gdbserv-client.h): typedef struct gdbserv_target *(gdbserv_target_attach) (struct gdbserv *gdbserv, void *attatch_data); This will be called by the server to notify that the client has initiated a connection. You must return a GDBSERV_TARGET struct for the session or null if your target has rejected the connection. The recommended way to create your gdbsert_target object is: struct gdbserv_target *gdbtarget = malloc (sizeof (struct gdbserv_target)); memset (gdbtarget, 0, sizeof (*gdbtarget)); gdbtarget->process_get_gen = process_get_gen; (...) If you initialize it like this, you will not have to change your code even if new entries are added to this struct. Handling target events: The fromtarget_* routines may be called by your target. Before you despair, let me tell you that they are only three: reset, break and exit. They indicate that some interesting condition happened in your target that requires debugger attention. Reset and exit are quite obvious and break is for everything else. These routines must *not* be called from within a gdbserv_target callback function, so it may be necessary for your target to set some flags to signal state change calls later. If your target is a simulator, call those when the corresponding condition arises in your simulated target. If it is a board, it will probably be called from your ISRs. A special case occurs if your target is a device that has to be polled in order to detect state changes. If this is the case, you will call these routines from the polling code. The main loop: At some point you must get things going by: 1) looking for connection requests from gdb and 2) causing remote protocol commands to be read from the input port (socket). You must (repeatedly) call a function called gdbloop_poll(). This will be in your main loop. If it is a server it will be in you main() function. If it is a simulator that also accepts input from other sources, it will have to be added to the existing main loop. If it is a board, it will probably be called from the timer ISR. Before doing that you must initialize your socket interface. Look in the main() routine of the sample program to see what to call. When calling gdbsocket_startup() you must give the address of the callback that handles connection requests for your target. If your target has to be polled for state changes, you will have to alternate calls to poll() and to check your target status. As I mentioned before, your status checking routine must call the appropriate fromtarget_* function. The callbacks: All that is left now is to implement your callbacks, which are, directly or indirectly, called as a result of a remote protocol packet being received and processed by some fromclient_* routine. I will assume that the remote protocol packets are known. If not, please refer to the "Remote Protocol" section of the gdb manual. There you will find how your target should be affected by each packet and what the response should be. It is always useful to look at what some other remote targets do as well. The list of all callbacks with the situation in which they are invoked can be found in the header file lib/gdbserv-target.h. Have fun! Appendix -------- SCENARIOS ATTACH: Creating a connection into the server/target. Client receives a connect request from the remote GDB. A socket interface would see this as an attach. For Cygmon this is the ``transfer'' command. The client creates an output object (struct gdbserv_client) and passes that and the target side attach function onto GDBSERV. SERVER will then initialize itself and call ``to_target_attach'' for final approval of the request. struct gdbserv_client *client; ... client->write = client_write_to_output_port; client->data = ... local state ... server = gdbserv_fromclient_attach (client, to_target_attach, target_data); The target_attach() function is expected to either create and return a gdbserv_target object or return NULL. The latter indicates that the connection request is rejected. Finally gdbserv_fromclient_attach() returns a SERVER state object that should be passed to all client->server calls. DATA-IN: Data being sent to the client from the remote GDB The client passes the data vis: len = read (fd, buf, sizeof (buf)); gdbserv_fromclient_data (server, buf, len); BREAK-IN: Request to stop sent to the client from the remote GDB The client passes the request to SERVER: server.c: volatile break_handler () { gdbserv_fromclient_break (server); } If the target is currently running, the server will in turn pass the request onto the TARGET: ->target->break_program (server); and the target will record the break request and then return control back to the SERVER. The server returning control to the client. Later, once the target has halted, the SERVER is notified of the state change vis: target.c: gdbserv_fromtarget_break (server); NB: Often ``break'' is sent across the stream interface as a special character sequence. FIXME: gdbserv-input.c should be able to parse such character sequences. DATA-OUT: Data for the remote GDB from SERVER The ``write'' function specified in the ``struct gdbserv_client'' object is called. DETACH: Remote GDB disconnects from the SERVER The CLIENT notifies SERVER via the gdbserv_fromclient_detach() call. HARD-RESET: A hard reset is performed on a serial board. Assuming a standalone system, the client/target side sequences would be performed: /* create the server */ server = gdbserv_fromclient_attach (...); /* notify the server that the target was just reset */ gdbserv_fromtarget_reset (server); WRITE-MEMORY: GDB sends the server a write-memory packet. Eventually SERVER passes the request onto the TARGET with the call: ->target->process_set_mem (server, addr, len); where ADDR and LEN contain the uninterpreted address/length of the data to follow. target_process_set_mem() might be implemented as: long long addr; long int; gdbserv_reg_to_ulonglong (server, reg_addr, &addr); gdbserv_reg_to_ulong (server, reg_len, &len); /* just blat local memory */ len = gdbserv_input_bytes (server, addr, len); READ-MEMORY: GDB requests memory from the target. Eventually target_process_get_mem() function is called. An implementation may look like: gdbserv_reg_to_ulonglong (gdbserv, reg_addr, &addr); gdbserv_reg_to_ulong (gdbserv, reg_len, &len); /* assume we're reading raw memory from the local mc */ gdbserv_output_bytes (gdbserv, addr, len); TARGET-RESUME: GDB requests that the target resume execution. Eventually ->target->continue_program() is called. That function should record the request and then wait for SERVER to exit before actually continuing the target. --