nkeynes@998: /** nkeynes@1020: * $Id$ nkeynes@998: * nkeynes@998: * GDB RDP server stub - SH4 + ARM nkeynes@998: * nkeynes@998: * Copyright (c) 2009 Nathan Keynes. nkeynes@998: * nkeynes@998: * This program is free software; you can redistribute it and/or modify nkeynes@998: * it under the terms of the GNU General Public License as published by nkeynes@998: * the Free Software Foundation; either version 2 of the License, or nkeynes@998: * (at your option) any later version. nkeynes@998: * nkeynes@998: * This program is distributed in the hope that it will be useful, nkeynes@998: * but WITHOUT ANY WARRANTY; without even the implied warranty of nkeynes@998: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nkeynes@998: * GNU General Public License for more details. nkeynes@998: */ nkeynes@998: nkeynes@998: nkeynes@998: #include nkeynes@998: #include nkeynes@998: #include nkeynes@998: #include nkeynes@998: #include nkeynes@998: #include nkeynes@998: #include nkeynes@998: #include nkeynes@998: #include "lxdream.h" nkeynes@998: #include "dreamcast.h" nkeynes@1077: #include "ioutil.h" nkeynes@998: #include "cpu.h" nkeynes@998: nkeynes@998: #define DEFAULT_BUFFER_SIZE 1024 nkeynes@998: #define BUFFER_SIZE_MARGIN 32 nkeynes@998: #define MAX_BUFFER_SIZE 65536 nkeynes@998: nkeynes@998: /* These are just local interpretations - they're not interpreted by GDB nkeynes@998: * in any way shape or form. nkeynes@998: */ nkeynes@998: #define GDB_ERROR_FORMAT 1 /* Badly formatted command */ nkeynes@998: #define GDB_ERROR_INVAL 2 /* Invalid data */ nkeynes@998: #define GDB_ERROR_FAIL 3 /* Command failed */ nkeynes@998: struct gdb_server { nkeynes@998: cpu_desc_t cpu; nkeynes@998: gboolean mmu; nkeynes@998: int fd; nkeynes@998: const gchar *peer_name; nkeynes@998: char *buf; nkeynes@998: int buf_size; nkeynes@998: int buf_posn; nkeynes@998: }; nkeynes@998: nkeynes@998: void gdb_server_free( gpointer data ) nkeynes@998: { nkeynes@998: struct gdb_server *server = (struct gdb_server *)data; nkeynes@998: free((char *)server->peer_name); nkeynes@998: free(server->buf); nkeynes@998: free(data); nkeynes@998: } nkeynes@998: nkeynes@998: int gdb_checksum( char *data, int length ) nkeynes@998: { nkeynes@998: int i; nkeynes@998: int result = 0; nkeynes@998: for( i=0; ifd, out, length+4 ); nkeynes@998: } nkeynes@998: nkeynes@998: /** nkeynes@998: * Send bulk data (ie memory dump) as hex, with optional string prefix. nkeynes@998: * Saves double copying when going through gdb_send_frame. nkeynes@998: */ nkeynes@998: void gdb_send_hex_data( struct gdb_server *server, char *prefix, unsigned char *data, int datalen ) nkeynes@998: { nkeynes@998: int prefixlen = 0; nkeynes@998: if( prefix != NULL ) nkeynes@998: prefixlen = strlen(prefix); nkeynes@998: int totallen = datalen*2 + prefixlen + 4; nkeynes@998: char out[totallen+1]; nkeynes@998: char *p = &out[1]; nkeynes@998: int i; nkeynes@998: nkeynes@998: out[0] = '$'; nkeynes@998: if( prefix != NULL ) { nkeynes@998: p += sprintf( p, "%s", prefix ); nkeynes@998: } nkeynes@998: for( i=0; ifd, out, totallen ); nkeynes@998: } nkeynes@998: nkeynes@998: /** nkeynes@998: * Parse bulk hex data - buffer should be at least datalen/2 bytes long nkeynes@998: */ nkeynes@1025: size_t gdb_read_hex_data( struct gdb_server *server, unsigned char *buf, char *data, int datalen ) nkeynes@998: { nkeynes@998: char *p = data; nkeynes@998: for( int i=0; icpu->get_register(i); nkeynes@998: if( val == NULL ) { nkeynes@998: sprintf( p, "00000000" ); nkeynes@998: } else { nkeynes@998: sprintf( p, "%02x%02x%02x%02x", val[0], val[1], val[2], val[3] ); nkeynes@998: } nkeynes@998: p += 8; nkeynes@998: } nkeynes@998: nkeynes@998: return i - firstreg; nkeynes@998: } nkeynes@998: nkeynes@998: void gdb_set_registers( struct gdb_server *server, char *buf, int firstreg, int regcount ) nkeynes@998: { nkeynes@998: int i; nkeynes@998: char *p = buf; nkeynes@998: for( i=firstreg; i < firstreg + regcount; i++ ) { nkeynes@998: uint8_t *val = server->cpu->get_register(i); nkeynes@1020: unsigned int a,b,c,d; nkeynes@998: if( val != NULL ) { nkeynes@1020: sscanf( p, "%02x%02x%02x%02x", &a, &b, &c, &d ); nkeynes@1020: val[0] = (uint8_t)a; nkeynes@1020: val[1] = (uint8_t)b; nkeynes@1020: val[2] = (uint8_t)c; nkeynes@1020: val[3] = (uint8_t)d; nkeynes@998: } nkeynes@998: p += 8; nkeynes@998: } nkeynes@998: } nkeynes@998: nkeynes@998: /** nkeynes@998: * Send a 2-digit error code. There's no actual definition for any of the codes nkeynes@998: * so they're more for our own amusement really. nkeynes@998: */ nkeynes@998: void gdb_send_error( struct gdb_server *server, int error ) nkeynes@998: { nkeynes@998: char out[4]; nkeynes@998: snprintf( out, 4, "E%02X", (error&0xFF) ); nkeynes@998: gdb_send_frame( server, out, 3 ); nkeynes@998: } nkeynes@998: nkeynes@998: void gdb_server_handle_frame( struct gdb_server *server, int command, char *data, int length ) nkeynes@998: { nkeynes@998: unsigned int tmp, tmp2, tmp3; nkeynes@998: char buf[512]; nkeynes@998: nkeynes@998: switch( command ) { nkeynes@998: case '!': /* Enable extended mode */ nkeynes@998: gdb_send_frame( server, "OK", 2 ); nkeynes@998: break; nkeynes@998: case '?': /* Get stop reason - always return 5 (TRAP) */ nkeynes@998: gdb_send_frame( server, "S05", 3 ); nkeynes@998: break; nkeynes@998: case 'c': /* Continue */ nkeynes@998: dreamcast_run(); nkeynes@998: gdb_send_frame( server, "S05", 3 ); nkeynes@998: break; nkeynes@998: case 'g': /* Read all general registers */ nkeynes@998: gdb_print_registers( server, buf, sizeof(buf), 0, server->cpu->num_gpr_regs ); nkeynes@998: gdb_send_frame( server, buf, strlen(buf) ); nkeynes@998: break; nkeynes@998: case 'G': /* Write all general registers */ nkeynes@998: if( length != server->cpu->num_gpr_regs*8 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_FORMAT ); nkeynes@998: } else { nkeynes@998: gdb_set_registers( server, data, 0, server->cpu->num_gpr_regs ); nkeynes@998: gdb_send_frame( server, "OK", 2 ); nkeynes@998: } nkeynes@998: break; nkeynes@998: case 'H': /* Set thread - only thread 1 is supported here */ nkeynes@998: if( length < 2 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_FORMAT ); nkeynes@998: } else { nkeynes@998: int thread; nkeynes@998: sscanf( data+1, "%d", &thread ); nkeynes@998: if( thread >= -1 && thread <= 1 ) { nkeynes@998: gdb_send_frame( server, "OK", 2 ); nkeynes@998: } else { nkeynes@998: gdb_send_error( server, GDB_ERROR_INVAL ); nkeynes@998: } nkeynes@998: } nkeynes@998: break; nkeynes@998: case 'k': /* kill - do nothing */ nkeynes@998: gdb_send_frame( server, "", 0 ); nkeynes@998: break; nkeynes@998: case 'm': /* Read memory */ nkeynes@998: if( sscanf( data, "%x,%x", &tmp, &tmp2 ) != 2 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_FORMAT ); nkeynes@998: } else { nkeynes@998: size_t datalen; nkeynes@1025: unsigned char mem[tmp2]; nkeynes@998: if( server->mmu ) { nkeynes@998: datalen = server->cpu->read_mem_vma(mem, tmp, tmp2); nkeynes@998: } else { nkeynes@998: datalen = server->cpu->read_mem_phys(mem, tmp, tmp2); nkeynes@998: } nkeynes@998: if( datalen == 0 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_INVAL ); nkeynes@998: } else { nkeynes@998: gdb_send_hex_data( server, NULL, mem, datalen ); nkeynes@998: } nkeynes@998: } nkeynes@998: break; nkeynes@998: case 'M': /* Write memory */ nkeynes@998: if( sscanf( data, "%x,%x:%n", &tmp, &tmp2, &tmp3 ) != 2 || nkeynes@998: length-tmp3 != tmp2*2 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_FORMAT ); nkeynes@998: } else { nkeynes@998: size_t len; nkeynes@1025: unsigned char mem[tmp2]; nkeynes@998: len = gdb_read_hex_data( server, mem, data+tmp3, length-tmp3 ); nkeynes@998: if( len != tmp2 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_FORMAT ); nkeynes@998: } else { nkeynes@998: if( server->mmu ) { nkeynes@998: len = server->cpu->write_mem_vma(tmp, mem, tmp2); nkeynes@998: } else { nkeynes@998: len = server->cpu->write_mem_phys(tmp, mem, tmp2); nkeynes@998: } nkeynes@998: if( len != tmp2 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_INVAL ); nkeynes@998: } else { nkeynes@998: gdb_send_frame( server, "OK", 2 ); nkeynes@998: } nkeynes@998: } nkeynes@998: } nkeynes@998: break; nkeynes@998: case 'p': /* Read single register */ nkeynes@998: if( sscanf( data, "%x", &tmp ) != 1 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_FORMAT ); nkeynes@998: } else if( tmp >= server->cpu->num_gdb_regs ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_INVAL ); nkeynes@998: } else { nkeynes@998: gdb_print_registers( server, buf, sizeof(buf), tmp, 1 ); nkeynes@998: gdb_send_frame( server, buf, 8 ); nkeynes@998: } nkeynes@998: break; nkeynes@998: case 'P': /* Write single register. */ nkeynes@998: if( sscanf( data, "%x=%n", &tmp, &tmp2 ) != 1 || nkeynes@998: length-tmp2 != 8) { nkeynes@998: gdb_send_error( server, GDB_ERROR_FORMAT ); nkeynes@998: } else if( tmp >= server->cpu->num_gdb_regs ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_INVAL ); nkeynes@998: } else { nkeynes@998: gdb_set_registers( server, data+tmp2, tmp, 1 ); nkeynes@998: gdb_send_frame( server, "OK", 2 ); nkeynes@998: } nkeynes@998: break; nkeynes@998: case 'q': /* Query data */ nkeynes@998: if( strcmp( data, "C" ) == 0 ) { nkeynes@998: gdb_send_frame( server, "QC1", 3 ); nkeynes@998: } else if( strcmp( data, "fThreadInfo" ) == 0 ) { nkeynes@998: gdb_send_frame( server, "m1", 2 ); nkeynes@998: } else if( strcmp( data, "sThreadInfo" ) == 0 ) { nkeynes@998: gdb_send_frame( server, "l", 1 ); nkeynes@998: } else if( strncmp( data, "Supported", 9 ) == 0 ) { nkeynes@998: gdb_send_frame( server, "PacketSize=4000", 15 ); nkeynes@998: } else if( strcmp( data, "Symbol::" ) == 0 ) { nkeynes@998: gdb_send_frame( server, "OK", 2 ); nkeynes@998: } else { nkeynes@998: gdb_send_frame( server, "", 0 ); nkeynes@998: } nkeynes@998: break; nkeynes@998: case 's': /* Single-step */ nkeynes@998: if( length != 0 ) { nkeynes@998: if( sscanf( data, "%x", &tmp ) != 1 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_FORMAT ); nkeynes@998: } else { nkeynes@998: *server->cpu->pc = tmp; nkeynes@998: } nkeynes@998: } nkeynes@998: server->cpu->step_func(); nkeynes@998: gdb_send_frame( server, "S05", 3 ); nkeynes@998: break; nkeynes@998: case 'T': /* Thread alive */ nkeynes@998: if( sscanf( data, "%x", &tmp ) != 1 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_FORMAT ); nkeynes@998: } else if( tmp != 1 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_INVAL ); nkeynes@998: } else { nkeynes@998: gdb_send_frame( server, "OK", 2 ); nkeynes@998: } nkeynes@998: break; nkeynes@998: case 'v': /* Verbose */ nkeynes@998: /* Only current one is vCont, which we don't bother supporting nkeynes@998: * at the moment, but don't warn about it either */ nkeynes@998: gdb_send_frame( server, "", 0 ); nkeynes@998: break; nkeynes@998: case 'X': /* Write memory binary */ nkeynes@998: if( sscanf( data, "%x,%x:%n", &tmp, &tmp2, &tmp3 ) != 2 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_FORMAT ); nkeynes@998: } else { nkeynes@1025: unsigned char mem[length - tmp3]; nkeynes@998: size_t len = gdb_read_binary_data( server, mem, data + tmp3, length-tmp3 ); nkeynes@998: if( len != tmp2 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_FORMAT ); nkeynes@998: } else { nkeynes@998: if( server->mmu ) { nkeynes@998: len = server->cpu->write_mem_vma(tmp, mem, tmp2); nkeynes@998: } else { nkeynes@998: len = server->cpu->write_mem_phys(tmp, mem, tmp2); nkeynes@998: } nkeynes@998: if( len != tmp2 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_INVAL ); nkeynes@998: } else { nkeynes@998: gdb_send_frame( server, "OK", 2 ); nkeynes@998: } nkeynes@998: } nkeynes@998: } nkeynes@998: break; nkeynes@998: case 'z': /* Remove Break/watchpoint */ nkeynes@998: if( sscanf( data, "%d,%x,%x", &tmp, &tmp2, &tmp3 ) != 3 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_FORMAT ); nkeynes@998: } else { nkeynes@998: if( tmp == 0 || tmp == 1 ) { /* soft break or hard break */ nkeynes@998: server->cpu->clear_breakpoint( tmp2, BREAK_KEEP ); nkeynes@998: gdb_send_frame( server, "OK", 2 ); nkeynes@998: } else { nkeynes@998: gdb_send_frame( server, "", 0 ); nkeynes@998: } nkeynes@998: } nkeynes@998: break; nkeynes@998: case 'Z': /* Insert Break/watchpoint */ nkeynes@998: if( sscanf( data, "%d,%x,%x", &tmp, &tmp2, &tmp3 ) != 3 ) { nkeynes@998: gdb_send_error( server, GDB_ERROR_FORMAT ); nkeynes@998: } else { nkeynes@998: if( tmp == 0 || tmp == 1 ) { /* soft break or hard break */ nkeynes@998: server->cpu->set_breakpoint( tmp2, BREAK_KEEP ); nkeynes@998: gdb_send_frame( server, "OK", 2 ); nkeynes@998: } else { nkeynes@998: gdb_send_frame( server, "", 0 ); nkeynes@998: } nkeynes@998: } nkeynes@998: break; nkeynes@998: default: nkeynes@998: /* Command unsupported */ nkeynes@998: WARN( "Received unknown GDB command '%c%s'", command, data ); nkeynes@998: gdb_send_frame( server, "", 0 ); nkeynes@998: break; nkeynes@998: } nkeynes@998: nkeynes@998: } nkeynes@998: nkeynes@998: /** nkeynes@998: * Decode out frames from the raw data stream. A frame takes the form of nkeynes@998: * $# nkeynes@998: * where data may not contain a '#' character, and checksum is a simple nkeynes@998: * 8-bit modulo sum of all bytes in data, encoded as a 2-char hex number. nkeynes@998: * nkeynes@998: * The only other legal wire forms are nkeynes@998: * + nkeynes@998: * indicating successful reception of the last message (ignored here), and nkeynes@998: * - nkeynes@998: * indicating failed reception of the last message - need to resend. nkeynes@998: */ nkeynes@998: void gdb_server_process_buffer( struct gdb_server *server ) nkeynes@998: { nkeynes@998: int i, frame_start = -1, frame_len = -1; nkeynes@998: for( i=0; ibuf_posn; i++ ) { nkeynes@998: if( frame_start == -1 ) { nkeynes@998: if( server->buf[i] == '$' ) { nkeynes@998: frame_start = i; nkeynes@998: } else if( server->buf[i] == '+' ) { nkeynes@998: /* Success */ nkeynes@998: continue; nkeynes@998: } else if( server->buf[i] == '-' ) { nkeynes@998: /* Request retransmit */ nkeynes@998: } /* Anything else is noise */ nkeynes@998: } else if( server->buf[i] == '#' ) { nkeynes@998: frame_len = i - frame_start - 1; nkeynes@998: if( i+2 < server->buf_posn ) { nkeynes@998: int calc_checksum = gdb_checksum( &server->buf[frame_start+1], frame_len ); nkeynes@998: int frame_checksum = 0; nkeynes@998: sscanf( &server->buf[i+1], "%02x", &frame_checksum ); nkeynes@998: nkeynes@998: if( calc_checksum != frame_checksum ) { nkeynes@998: WARN( "GDB frame checksum failure (expected %02X but was %02X)", nkeynes@998: calc_checksum, frame_checksum ); nkeynes@998: write( server->fd, "-", 1 ); nkeynes@998: } else if( frame_len == 0 ) { nkeynes@998: /* Empty frame - should never occur as a request */ nkeynes@998: WARN( "Empty GDB frame received" ); nkeynes@998: write( server->fd, "-", 1 ); nkeynes@998: } else { nkeynes@998: /* We have a good frame */ nkeynes@998: write( server->fd, "+", 1 ); nkeynes@998: server->buf[i] = '\0'; nkeynes@998: gdb_server_handle_frame( server, server->buf[frame_start+1], &server->buf[frame_start+2], frame_len-1 ); nkeynes@998: } nkeynes@998: i+=2; nkeynes@998: frame_start = -1; nkeynes@998: } nkeynes@998: } nkeynes@998: } nkeynes@998: if( frame_start == -1 ) { nkeynes@998: server->buf_posn = 0; /* Consumed whole buffer */ nkeynes@998: } else if( frame_start > 0 ) { nkeynes@998: memmove(&server->buf[0], &server->buf[frame_start], server->buf_posn - frame_start); nkeynes@998: server->buf_posn -= frame_start; nkeynes@998: } nkeynes@998: } nkeynes@998: nkeynes@998: gboolean gdb_server_data_callback( int fd, void *data ) nkeynes@998: { nkeynes@998: struct gdb_server *server = (struct gdb_server *)data; nkeynes@998: nkeynes@998: size_t len = read( fd, &server->buf[server->buf_posn], server->buf_size - server->buf_posn ); nkeynes@998: if( len > 0 ) { nkeynes@998: server->buf_posn += len; nkeynes@998: gdb_server_process_buffer( server ); nkeynes@998: nkeynes@998: /* If we have an oversized packet, extend the buffer */ nkeynes@998: if( server->buf_posn > server->buf_size - BUFFER_SIZE_MARGIN && nkeynes@998: server->buf_size < MAX_BUFFER_SIZE ) { nkeynes@998: server->buf_size <<= 1; nkeynes@998: server->buf = realloc( server->buf, server->buf_size ); nkeynes@998: assert( server->buf != NULL ); nkeynes@998: } nkeynes@998: return TRUE; nkeynes@998: } else { nkeynes@998: INFO( "GDB disconnected" ); nkeynes@998: return FALSE; nkeynes@998: } nkeynes@998: } nkeynes@998: nkeynes@998: gboolean gdb_server_connect_callback( int fd, gpointer data ) nkeynes@998: { nkeynes@998: struct sockaddr_in sin; nkeynes@998: socklen_t sinlen; nkeynes@998: struct gdb_server *server = (struct gdb_server *)data; nkeynes@998: int conn_fd = accept( fd, (struct sockaddr *)&sin, &sinlen); nkeynes@998: if( conn_fd != -1 ) { nkeynes@998: struct gdb_server *chan_serv = calloc( sizeof(struct gdb_server), 1 ); nkeynes@998: chan_serv->cpu = server->cpu; nkeynes@998: chan_serv->mmu = server->mmu; nkeynes@998: chan_serv->fd = conn_fd; nkeynes@998: chan_serv->peer_name = g_strdup_printf("%s:%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); nkeynes@998: chan_serv->buf = malloc(1024); nkeynes@998: chan_serv->buf_size = 1024; nkeynes@998: chan_serv->buf_posn = 0; nkeynes@1077: io_register_tcp_listener( conn_fd, gdb_server_data_callback, chan_serv, gdb_server_free ); nkeynes@998: INFO( "GDB connected from %s", chan_serv->peer_name ); nkeynes@998: } nkeynes@998: return TRUE; nkeynes@998: } nkeynes@998: nkeynes@998: /** nkeynes@998: * Bind a network port for a GDB remote server for the specified cpu. The nkeynes@998: * port is registered for the system network callback. nkeynes@998: * nkeynes@998: * @param interface network interface to bind to, or null for the default (all) interface nkeynes@998: * @param port nkeynes@998: * @param cpu CPU to make available over the network port.. nkeynes@998: * @param mmu if TRUE, virtual memory is made available to GDB, otherwise GDB nkeynes@998: * accesses physical memory. nkeynes@998: * @return TRUE if the server was bound successfully. nkeynes@998: */ nkeynes@998: gboolean gdb_init_server( const char *interface, int port, cpu_desc_t cpu, gboolean mmu ) nkeynes@998: { nkeynes@1077: int fd = io_create_server_socket( interface, port ); nkeynes@998: if( fd == -1 ) { nkeynes@998: return FALSE; nkeynes@998: } nkeynes@998: nkeynes@998: struct gdb_server *server = calloc( sizeof(struct gdb_server), 1 ); nkeynes@998: server->cpu = cpu; nkeynes@998: server->mmu = mmu; nkeynes@998: server->fd = fd; nkeynes@1081: gboolean result = io_register_tcp_listener( fd, gdb_server_connect_callback, server, gdb_server_free ) != NULL; nkeynes@1071: INFO( "%s GDB server running on port %d", cpu->name, port ); nkeynes@1071: return result; nkeynes@998: }