diff options
Diffstat (limited to 'tools/gdb/remote.c')
-rw-r--r-- | tools/gdb/remote.c | 928 |
1 files changed, 928 insertions, 0 deletions
diff --git a/tools/gdb/remote.c b/tools/gdb/remote.c new file mode 100644 index 0000000..b8b2470 --- /dev/null +++ b/tools/gdb/remote.c @@ -0,0 +1,928 @@ +/* + * taken from gdb/remote.c + * + * I am only interested in the write to memory stuff - everything else + * has been ripped out + * + * all the copyright notices etc have been left in + */ + +/* enough so that it will compile */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +/*nicked from gcc..*/ + +#ifndef alloca +#ifdef __GNUC__ +#define alloca __builtin_alloca +#else /* not GNU C. */ +#if (!defined (__STDC__) && defined (sparc)) || defined (__sparc__) || defined (__sparc) || defined (__sgi) +#include <alloca.h> +#else /* not sparc */ +#if defined (MSDOS) && !defined (__TURBOC__) +#include <malloc.h> +#else /* not MSDOS, or __TURBOC__ */ +#if defined(_AIX) +#include <malloc.h> + #pragma alloca +#else /* not MSDOS, __TURBOC__, or _AIX */ +#ifdef __hpux +#endif /* __hpux */ +#endif /* not _AIX */ +#endif /* not MSDOS, or __TURBOC__ */ +#endif /* not sparc. */ +#endif /* not GNU C. */ +#ifdef __cplusplus +extern "C" { +#endif + void* alloca(size_t); +#ifdef __cplusplus +} +#endif +#endif /* alloca not defined. */ + + +#include "serial.h" +#include "error.h" +#include "remote.h" +#define REGISTER_BYTES 0 +#define fprintf_unfiltered fprintf +#define fprintf_filtered fprintf +#define fputs_unfiltered fputs +#define fputs_filtered fputs +#define fputc_unfiltered fputc +#define fputc_filtered fputc +#define printf_unfiltered printf +#define printf_filtered printf +#define puts_unfiltered puts +#define puts_filtered puts +#define putchar_unfiltered putchar +#define putchar_filtered putchar +#define fputstr_unfiltered(a,b,c) fputs((a), (c)) +#define gdb_stdlog stderr +#define SERIAL_READCHAR(fd,timo) serialreadchar((fd), (timo)) +#define SERIAL_WRITE(fd, addr, len) serialwrite((fd), (addr), (len)) +#define error Error +#define perror_with_name Perror +#define gdb_flush fflush +#define max(a,b) (((a)>(b))?(a):(b)) +#define min(a,b) (((a)<(b))?(a):(b)) +#define target_mourn_inferior() {} +#define ULONGEST unsigned long +#define CORE_ADDR unsigned long + +static int putpkt (char *); +static int putpkt_binary(char *, int); +static void getpkt (char *, int); + +static int remote_debug = 0, remote_register_buf_size = 0, watchdog = 0; + +int remote_desc = -1, remote_timeout = 10; + +static void +fputstrn_unfiltered(char *s, int n, int x, FILE *fp) +{ + while (n-- > 0) + fputc(*s++, fp); +} + +void +remote_reset(void) +{ + SERIAL_WRITE(remote_desc, "+", 1); +} + +void +remote_continue(void) +{ + putpkt("c"); +} + +/* Remote target communications for serial-line targets in custom GDB protocol + Copyright 1988, 91, 92, 93, 94, 95, 96, 97, 98, 1999 + Free Software Foundation, Inc. + + This file is part of GDB. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ +/* *INDENT-OFF* */ +/* Remote communication protocol. + + A debug packet whose contents are <data> + is encapsulated for transmission in the form: + + $ <data> # CSUM1 CSUM2 + + <data> must be ASCII alphanumeric and cannot include characters + '$' or '#'. If <data> starts with two characters followed by + ':', then the existing stubs interpret this as a sequence number. + + CSUM1 and CSUM2 are ascii hex representation of an 8-bit + checksum of <data>, the most significant nibble is sent first. + the hex digits 0-9,a-f are used. + + Receiver responds with: + + + - if CSUM is correct and ready for next packet + - - if CSUM is incorrect + + <data> is as follows: + Most values are encoded in ascii hex digits. Signal numbers are according + to the numbering in target.h. + + Request Packet + + set thread Hct... Set thread for subsequent operations. + c = 'c' for thread used in step and + continue; t... can be -1 for all + threads. + c = 'g' for thread used in other + operations. If zero, pick a thread, + any thread. + reply OK for success + ENN for an error. + + read registers g + reply XX....X Each byte of register data + is described by two hex digits. + Registers are in the internal order + for GDB, and the bytes in a register + are in the same order the machine uses. + or ENN for an error. + + write regs GXX..XX Each byte of register data + is described by two hex digits. + reply OK for success + ENN for an error + + write reg Pn...=r... Write register n... with value r..., + which contains two hex digits for each + byte in the register (target byte + order). + reply OK for success + ENN for an error + (not supported by all stubs). + + read mem mAA..AA,LLLL AA..AA is address, LLLL is length. + reply XX..XX XX..XX is mem contents + Can be fewer bytes than requested + if able to read only part of the data. + or ENN NN is errno + + write mem MAA..AA,LLLL:XX..XX + AA..AA is address, + LLLL is number of bytes, + XX..XX is data + reply OK for success + ENN for an error (this includes the case + where only part of the data was + written). + + write mem XAA..AA,LLLL:XX..XX + (binary) AA..AA is address, + LLLL is number of bytes, + XX..XX is binary data + reply OK for success + ENN for an error + + continue cAA..AA AA..AA is address to resume + If AA..AA is omitted, + resume at same address. + + step sAA..AA AA..AA is address to resume + If AA..AA is omitted, + resume at same address. + + continue with Csig;AA..AA Continue with signal sig (hex signal + signal number). If ;AA..AA is omitted, + resume at same address. + + step with Ssig;AA..AA Like 'C' but step not continue. + signal + + last signal ? Reply the current reason for stopping. + This is the same reply as is generated + for step or cont : SAA where AA is the + signal number. + + detach D Reply OK. + + There is no immediate reply to step or cont. + The reply comes when the machine stops. + It is SAA AA is the signal number. + + or... TAAn...:r...;n...:r...;n...:r...; + AA = signal number + n... = register number (hex) + r... = register contents + n... = `thread' + r... = thread process ID. This is + a hex integer. + n... = other string not starting + with valid hex digit. + gdb should ignore this n,r pair + and go on to the next. This way + we can extend the protocol. + or... WAA The process exited, and AA is + the exit status. This is only + applicable for certains sorts of + targets. + or... XAA The process terminated with signal + AA. + or (obsolete) NAA;tttttttt;dddddddd;bbbbbbbb + AA = signal number + tttttttt = address of symbol "_start" + dddddddd = base of data section + bbbbbbbb = base of bss section. + Note: only used by Cisco Systems + targets. The difference between this + reply and the "qOffsets" query is that + the 'N' packet may arrive spontaneously + whereas the 'qOffsets' is a query + initiated by the host debugger. + or... OXX..XX XX..XX is hex encoding of ASCII data. This + can happen at any time while the + program is running and the debugger + should continue to wait for + 'W', 'T', etc. + + thread alive TXX Find out if the thread XX is alive. + reply OK thread is still alive + ENN thread is dead + + remote restart RXX Restart the remote server + + extended ops ! Use the extended remote protocol. + Sticky -- only needs to be set once. + + kill request k + + toggle debug d toggle debug flag (see 386 & 68k stubs) + reset r reset -- see sparc stub. + reserved <other> On other requests, the stub should + ignore the request and send an empty + response ($#<checksum>). This way + we can extend the protocol and GDB + can tell whether the stub it is + talking to uses the old or the new. + search tAA:PP,MM Search backwards starting at address + AA for a match with pattern PP and + mask MM. PP and MM are 4 bytes. + Not supported by all stubs. + + general query qXXXX Request info about XXXX. + general set QXXXX=yyyy Set value of XXXX to yyyy. + query sect offs qOffsets Get section offsets. Reply is + Text=xxx;Data=yyy;Bss=zzz + + Responses can be run-length encoded to save space. A '*' means that + the next character is an ASCII encoding giving a repeat count which + stands for that many repititions of the character preceding the '*'. + The encoding is n+29, yielding a printable character where n >=3 + (which is where rle starts to win). Don't use an n > 126. + + So + "0* " means the same as "0000". */ +/* *INDENT-ON* */ + +/* This variable (available to the user via "set remotebinarydownload") + dictates whether downloads are sent in binary (via the 'X' packet). + We assume that the stub can, and attempt to do it. This will be cleared if + the stub does not understand it. This switch is still needed, though + in cases when the packet is supported in the stub, but the connection + does not allow it (i.e., 7-bit serial connection only). */ +static int remote_binary_download = 1; + +/* Have we already checked whether binary downloads work? */ +static int remote_binary_checked; + +/* Maximum number of bytes to read/write at once. The value here + is chosen to fill up a packet (the headers account for the 32). */ +#define MAXBUFBYTES(N) (((N)-32)/2) + +/* Having this larger than 400 causes us to be incompatible with m68k-stub.c + and i386-stub.c. Normally, no one would notice because it only matters + for writing large chunks of memory (e.g. in downloads). Also, this needs + to be more than 400 if required to hold the registers (see below, where + we round it up based on REGISTER_BYTES). */ +/* Round up PBUFSIZ to hold all the registers, at least. */ +#define PBUFSIZ ((REGISTER_BYTES > MAXBUFBYTES (400)) \ + ? (REGISTER_BYTES * 2 + 32) \ + : 400) + + +/* This variable sets the number of bytes to be written to the target + in a single packet. Normally PBUFSIZ is satisfactory, but some + targets need smaller values (perhaps because the receiving end + is slow). */ + +static int remote_write_size = 0x7fffffff; + +/* This variable sets the number of bits in an address that are to be + sent in a memory ("M" or "m") packet. Normally, after stripping + leading zeros, the entire address would be sent. This variable + restricts the address to REMOTE_ADDRESS_SIZE bits. HISTORY: The + initial implementation of remote.c restricted the address sent in + memory packets to ``host::sizeof long'' bytes - (typically 32 + bits). Consequently, for 64 bit targets, the upper 32 bits of an + address was never sent. Since fixing this bug may cause a break in + some remote targets this variable is principly provided to + facilitate backward compatibility. */ + +static int remote_address_size; + +/* Convert hex digit A to a number. */ + +static int +fromhex (int a) +{ + if (a >= '0' && a <= '9') + return a - '0'; + else if (a >= 'a' && a <= 'f') + return a - 'a' + 10; + else if (a >= 'A' && a <= 'F') + return a - 'A' + 10; + else { + error ("Reply contains invalid hex digit %d", a); + return -1; + } +} + +/* Convert number NIB to a hex digit. */ + +static int +tohex (int nib) +{ + if (nib < 10) + return '0' + nib; + else + return 'a' + nib - 10; +} + +/* Return the number of hex digits in num. */ + +static int +hexnumlen (ULONGEST num) +{ + int i; + + for (i = 0; num != 0; i++) + num >>= 4; + + return max (i, 1); +} + +/* Set BUF to the hex digits representing NUM. */ + +static int +hexnumstr (char *buf, ULONGEST num) +{ + int i; + int len = hexnumlen (num); + + buf[len] = '\0'; + + for (i = len - 1; i >= 0; i--) + { + buf[i] = "0123456789abcdef"[(num & 0xf)]; + num >>= 4; + } + + return len; +} + +/* Mask all but the least significant REMOTE_ADDRESS_SIZE bits. */ + +static CORE_ADDR +remote_address_masked (CORE_ADDR addr) +{ + if (remote_address_size > 0 + && remote_address_size < (sizeof (ULONGEST) * 8)) + { + /* Only create a mask when that mask can safely be constructed + in a ULONGEST variable. */ + ULONGEST mask = 1; + mask = (mask << remote_address_size) - 1; + addr &= mask; + } + return addr; +} + +/* Determine whether the remote target supports binary downloading. + This is accomplished by sending a no-op memory write of zero length + to the target at the specified address. It does not suffice to send + the whole packet, since many stubs strip the eighth bit and subsequently + compute a wrong checksum, which causes real havoc with remote_write_bytes. + + NOTE: This can still lose if the serial line is not eight-bit clean. In + cases like this, the user should clear "remotebinarydownload". */ +static void +check_binary_download (CORE_ADDR addr) +{ + if (remote_binary_download && !remote_binary_checked) + { + char *buf = alloca (PBUFSIZ); + char *p; + remote_binary_checked = 1; + + p = buf; + *p++ = 'X'; + p += hexnumstr (p, (ULONGEST) addr); + *p++ = ','; + p += hexnumstr (p, (ULONGEST) 0); + *p++ = ':'; + *p = '\0'; + + putpkt_binary (buf, (int) (p - buf)); + getpkt (buf, 0); + + if (buf[0] == '\0') + remote_binary_download = 0; + } + + if (remote_debug) + { + if (remote_binary_download) + fprintf_unfiltered (gdb_stdlog, + "binary downloading suppported by target\n"); + else + fprintf_unfiltered (gdb_stdlog, + "binary downloading NOT suppported by target\n"); + } +} + +/* Write memory data directly to the remote machine. + This does not inform the data cache; the data cache uses this. + MEMADDR is the address in the remote memory space. + MYADDR is the address of the buffer in our space. + LEN is the number of bytes. + + Returns number of bytes transferred, or 0 for error. */ + +int +remote_write_bytes (memaddr, myaddr, len) + CORE_ADDR memaddr; + char *myaddr; + int len; +{ + unsigned char *buf = alloca (PBUFSIZ); + int max_buf_size; /* Max size of packet output buffer */ + int origlen; + extern int verbose; + + /* Verify that the target can support a binary download */ + check_binary_download (memaddr); + + /* Chop the transfer down if necessary */ + + max_buf_size = min (remote_write_size, PBUFSIZ); + if (remote_register_buf_size != 0) + max_buf_size = min (max_buf_size, remote_register_buf_size); + + /* Subtract header overhead from max payload size - $M<memaddr>,<len>:#nn */ + max_buf_size -= 2 + hexnumlen (memaddr + len - 1) + 1 + hexnumlen (len) + 4; + + origlen = len; + while (len > 0) + { + unsigned char *p, *plen; + int todo; + int i; + + /* construct "M"<memaddr>","<len>":" */ + /* sprintf (buf, "M%lx,%x:", (unsigned long) memaddr, todo); */ + memaddr = remote_address_masked (memaddr); + p = buf; + if (remote_binary_download) + { + *p++ = 'X'; + todo = min (len, max_buf_size); + } + else + { + *p++ = 'M'; + todo = min (len, max_buf_size / 2); /* num bytes that will fit */ + } + + p += hexnumstr ((char *)p, (ULONGEST) memaddr); + *p++ = ','; + + plen = p; /* remember where len field goes */ + p += hexnumstr ((char *)p, (ULONGEST) todo); + *p++ = ':'; + *p = '\0'; + + /* We send target system values byte by byte, in increasing byte + addresses, each byte encoded as two hex characters (or one + binary character). */ + if (remote_binary_download) + { + int escaped = 0; + for (i = 0; + (i < todo) && (i + escaped) < (max_buf_size - 2); + i++) + { + switch (myaddr[i] & 0xff) + { + case '$': + case '#': + case 0x7d: + /* These must be escaped */ + escaped++; + *p++ = 0x7d; + *p++ = (myaddr[i] & 0xff) ^ 0x20; + break; + default: + *p++ = myaddr[i] & 0xff; + break; + } + } + + if (i < todo) + { + /* Escape chars have filled up the buffer prematurely, + and we have actually sent fewer bytes than planned. + Fix-up the length field of the packet. */ + + /* FIXME: will fail if new len is a shorter string than + old len. */ + + plen += hexnumstr ((char *)plen, (ULONGEST) i); + *plen++ = ':'; + } + } + else + { + for (i = 0; i < todo; i++) + { + *p++ = tohex ((myaddr[i] >> 4) & 0xf); + *p++ = tohex (myaddr[i] & 0xf); + } + *p = '\0'; + } + + putpkt_binary ((char *)buf, (int) (p - buf)); + getpkt ((char *)buf, 0); + + if (buf[0] == 'E') + { + /* There is no correspondance between what the remote protocol uses + for errors and errno codes. We would like a cleaner way of + representing errors (big enough to include errno codes, bfd_error + codes, and others). But for now just return EIO. */ + errno = EIO; + return 0; + } + + /* Increment by i, not by todo, in case escape chars + caused us to send fewer bytes than we'd planned. */ + myaddr += i; + memaddr += i; + len -= i; + + if (verbose) + putc('.', stderr); + } + return origlen; +} + +/* Stuff for dealing with the packets which are part of this protocol. + See comment at top of file for details. */ + +/* Read a single character from the remote end, masking it down to 7 bits. */ + +static int +readchar (int timeout) +{ + int ch; + + ch = SERIAL_READCHAR (remote_desc, timeout); + + switch (ch) + { + case SERIAL_EOF: + error ("Remote connection closed"); + case SERIAL_ERROR: + perror_with_name ("Remote communication error"); + case SERIAL_TIMEOUT: + return ch; + default: + return ch & 0x7f; + } +} + +static int +putpkt (buf) + char *buf; +{ + return putpkt_binary (buf, strlen (buf)); +} + +/* Send a packet to the remote machine, with error checking. The data + of the packet is in BUF. The string in BUF can be at most PBUFSIZ - 5 + to account for the $, # and checksum, and for a possible /0 if we are + debugging (remote_debug) and want to print the sent packet as a string */ + +static int +putpkt_binary (buf, cnt) + char *buf; + int cnt; +{ + int i; + unsigned char csum = 0; + char *buf2 = alloca (PBUFSIZ); + char *junkbuf = alloca (PBUFSIZ); + + int ch; + int tcount = 0; + char *p; + + /* Copy the packet into buffer BUF2, encapsulating it + and giving it a checksum. */ + + if (cnt > BUFSIZ - 5) /* Prosanity check */ + abort (); + + p = buf2; + *p++ = '$'; + + for (i = 0; i < cnt; i++) + { + csum += buf[i]; + *p++ = buf[i]; + } + *p++ = '#'; + *p++ = tohex ((csum >> 4) & 0xf); + *p++ = tohex (csum & 0xf); + + /* Send it over and over until we get a positive ack. */ + + while (1) + { + int started_error_output = 0; + + if (remote_debug) + { + *p = '\0'; + fprintf_unfiltered (gdb_stdlog, "Sending packet: "); + fputstrn_unfiltered (buf2, p - buf2, 0, gdb_stdlog); + fprintf_unfiltered (gdb_stdlog, "..."); + gdb_flush (gdb_stdlog); + } + if (SERIAL_WRITE (remote_desc, buf2, p - buf2)) + perror_with_name ("putpkt: write failed"); + + /* read until either a timeout occurs (-2) or '+' is read */ + while (1) + { + ch = readchar (remote_timeout); + + if (remote_debug) + { + switch (ch) + { + case '+': + case SERIAL_TIMEOUT: + case '$': + if (started_error_output) + { + putchar_unfiltered ('\n'); + started_error_output = 0; + } + } + } + + switch (ch) + { + case '+': + if (remote_debug) + fprintf_unfiltered (gdb_stdlog, "Ack\n"); + return 1; + case SERIAL_TIMEOUT: + tcount++; + if (tcount > 3) + return 0; + break; /* Retransmit buffer */ + case '$': + { + /* It's probably an old response, and we're out of sync. + Just gobble up the packet and ignore it. */ + getpkt (junkbuf, 0); + continue; /* Now, go look for + */ + } + default: + if (remote_debug) + { + if (!started_error_output) + { + started_error_output = 1; + fprintf_unfiltered (gdb_stdlog, "putpkt: Junk: "); + } + fputc_unfiltered (ch & 0177, gdb_stdlog); + } + continue; + } + break; /* Here to retransmit */ + } + +#if 0 + /* This is wrong. If doing a long backtrace, the user should be + able to get out next time we call QUIT, without anything as + violent as interrupt_query. If we want to provide a way out of + here without getting to the next QUIT, it should be based on + hitting ^C twice as in remote_wait. */ + if (quit_flag) + { + quit_flag = 0; + interrupt_query (); + } +#endif + } +} + +/* Come here after finding the start of the frame. Collect the rest + into BUF, verifying the checksum, length, and handling run-length + compression. Returns 0 on any error, 1 on success. */ + +static int +read_frame (char *buf) +{ + unsigned char csum; + char *bp; + int c; + + csum = 0; + bp = buf; + + while (1) + { + c = readchar (remote_timeout); + + switch (c) + { + case SERIAL_TIMEOUT: + if (remote_debug) + fputs_filtered ("Timeout in mid-packet, retrying\n", gdb_stdlog); + return 0; + case '$': + if (remote_debug) + fputs_filtered ("Saw new packet start in middle of old one\n", + gdb_stdlog); + return 0; /* Start a new packet, count retries */ + case '#': + { + unsigned char pktcsum; + + *bp = '\000'; + + pktcsum = fromhex (readchar (remote_timeout)) << 4; + pktcsum |= fromhex (readchar (remote_timeout)); + + if (csum == pktcsum) + { + return 1; + } + + if (remote_debug) + { + fprintf_filtered (gdb_stdlog, + "Bad checksum, sentsum=0x%x, csum=0x%x, buf=", + pktcsum, csum); + fputs_filtered (buf, gdb_stdlog); + fputs_filtered ("\n", gdb_stdlog); + } + return 0; + } + case '*': /* Run length encoding */ + csum += c; + c = readchar (remote_timeout); + csum += c; + c = c - ' ' + 3; /* Compute repeat count */ + + if (c > 0 && c < 255 && bp + c - 1 < buf + PBUFSIZ - 1) + { + memset (bp, *(bp - 1), c); + bp += c; + continue; + } + + *bp = '\0'; + printf_filtered ("Repeat count %d too large for buffer: ", c); + puts_filtered (buf); + puts_filtered ("\n"); + return 0; + default: + if (bp < buf + PBUFSIZ - 1) + { + *bp++ = c; + csum += c; + continue; + } + + *bp = '\0'; + puts_filtered ("Remote packet too long: "); + puts_filtered (buf); + puts_filtered ("\n"); + + return 0; + } + } +} + +/* Read a packet from the remote machine, with error checking, and + store it in BUF. BUF is expected to be of size PBUFSIZ. If + FOREVER, wait forever rather than timing out; this is used while + the target is executing user code. */ + +static void +getpkt (buf, forever) + char *buf; + int forever; +{ + int c; + int tries; + int timeout; + int val; + + strcpy (buf, "timeout"); + + if (forever) + { + timeout = watchdog > 0 ? watchdog : -1; + } + + else + timeout = remote_timeout; + +#define MAX_TRIES 3 + + for (tries = 1; tries <= MAX_TRIES; tries++) + { + /* This can loop forever if the remote side sends us characters + continuously, but if it pauses, we'll get a zero from readchar + because of timeout. Then we'll count that as a retry. */ + + /* Note that we will only wait forever prior to the start of a packet. + After that, we expect characters to arrive at a brisk pace. They + should show up within remote_timeout intervals. */ + + do + { + c = readchar (timeout); + + if (c == SERIAL_TIMEOUT) + { + if (forever) /* Watchdog went off. Kill the target. */ + { + target_mourn_inferior (); + error ("Watchdog has expired. Target detached.\n"); + } + if (remote_debug) + fputs_filtered ("Timed out.\n", gdb_stdlog); + goto retry; + } + } + while (c != '$'); + + /* We've found the start of a packet, now collect the data. */ + + val = read_frame (buf); + + if (val == 1) + { + if (remote_debug) + { + fprintf_unfiltered (gdb_stdlog, "Packet received: "); + fputstr_unfiltered (buf, 0, gdb_stdlog); + fprintf_unfiltered (gdb_stdlog, "\n"); + } + SERIAL_WRITE (remote_desc, "+", 1); + return; + } + + /* Try the whole thing again. */ + retry: + SERIAL_WRITE (remote_desc, "-", 1); + } + + /* We have tried hard enough, and just can't receive the packet. Give up. */ + + printf_unfiltered ("Ignoring packet error, continuing...\n"); + SERIAL_WRITE (remote_desc, "+", 1); +} |