#include <u.h>
#include <libc.h>
#include <bio.h>
#include "gdb.h"
#define BUFSIZE (64*1024)
static char Echecksum[] = "bad checksum";
static char Eescape[] = "bad escape";
static char Enotsup[] = "not supported";
static char Epacket[] = "bad packet";
static char Erunlen[] = "bad run-length";
static char Etimeout[] = "timed out";
static int fd;
static Biobuf bin;
static int acked;
static int errored;
static int notified;
int chattygdb;
static int
checksum(char *s)
{
int sum;
sum = 0;
while(*s != '\0')
sum += *s++;
return sum % 256;
}
int
gdbsend(char *s)
{
long n;
if(chattygdb)
fprint(2, "<- %s\n", s);
n = fprint(fd, s);
if(n < 0)
sysfatal("%r");
return n;
}
int
gdbsendp(char *s)
{
long n;
if(chattygdb)
fprint(2, "<- %s\n", s);
n = fprint(fd, "$%s#%02ux", s, checksum(s));
if(n < 0)
sysfatal("%r");
return n;
}
int
gdback(void)
{
return gdbsend("+");
}
int
gdbresend(void)
{
return gdbsend("-");
}
static int
recv(char *buf, int len)
{
char *p, *e;
int state, sum, c;
static char cksum[2+1];
enum { None, Data, Checksum };
sum = 0;
state = None;
acked = errored = notified = 0;
p = buf;
e = buf + len;
while(p < e)
switch(state){
case None:
switch(Bgetc(&bin)){
case -1:
sysfatal("%r");
case '%':
notified++;
/* fall through */
case '$':
state = Data;
break;
case '+':
acked++;
return 0;
case '-':
acked++;
return -1;
}
break;
case Data:
switch(c = Bgetc(&bin)){
case -1:
sysfatal("%r");
case '#':
state = Checksum;
break;
default:
*p++ = c;
sum += c;
}
break;
case Checksum:
if(Bread(&bin, cksum, sizeof cksum - 1) < 0)
sysfatal("%r");
if(strtoul(cksum, nil, 16) != sum%256){
werrstr(Echecksum);
return -1;
}
return p - buf;
}
werrstr(Epacket);
return -1;
}
static int
expand(char *s, char *buf, int len)
{
char *p, *e, c;
int rlen;
p = buf;
e = buf + len;
while(*s != '\0')
switch(c = *s++){
case '*':
if(*s == '\0' || p == buf){
werrstr(Erunlen);
return -1;
}
rlen = *s++ - 29;
while(rlen--)
p = seprint(p, e, "%c", s[-3]);
break;
case '}':
if(*s == '\0'){
werrstr(Eescape);
return -1;
}
c = *s++ ^ 0x20;
/* fall through */
default:
p = seprint(p, e, "%c", c);
}
return p - buf;
}
int
gdbrecv(char *buf, int len)
{
int n;
static char buf2[BUFSIZE];
/*
* Unfortunately, some targets persist in use of run-length
* encoding. We first read the packet to a holding buffer and
* expand to the user-specified buffer.
*/
n = recv(buf2, sizeof buf2);
if(n < 0)
return -1;
if(acked)
return 0;
buf2[n] = '\0';
if(chattygdb)
fprint(2, "-> %s\n", buf2);
n = expand(buf2, buf, len);
if(n < 0)
return -1;
else if(n == 0){ /* $#00 */
werrstr(Enotsup);
errored++;
}
else if(*buf == 'E'){
werrstr(buf);
errored++;
}
return n;
}
int
wasack(void)
{
return acked;
}
int
waserror(void)
{
return errored;
}
int
wasnotify(void)
{
return notified;
}
int
gdbcommand(char *s, char *buf, int len)
{
int i;
long n;
enum { Nretries = 10 };
i = 0;
again:
for(; i < Nretries; ++i)
if(gdbsendp(s) > 0)
if(gdbrecv(buf, len) == 0)
if(wasack())
break;
for(; i < Nretries; ++i){
n = gdbrecv(buf, len);
if(n < 0)
gdbresend();
else{
gdback();
if(wasnotify())
goto again;
return n;
}
}
werrstr(Etimeout);
return -1;
}
int
gdbopen(char *target)
{
/*
* Remote targets support both serial and network access. We
* assume serial will either be served by consolefs(4) or
* uart(3), hence we have a file to open.
*/
if(access(target, AEXIST) == 0)
fd = open(target, ORDWR);
else
fd = dial(target, nil, nil, nil);
Binit(&bin, fd, OREAD);
return fd;
}
void
gdbclose(void)
{
Bterm(&bin);
close(fd);
}
void
gdbkill(void)
{
/*
* The kill request (understandably) may not provide a
* response. We short circuit gdbcommand to send the packet.
* It is not clear that waiting for an acknowlegement is
* worthwhile; a resend will fail miserably anyway.
*/
gdbsendp("k");
gdbclose();
}
|