Admin Panel
/*
main.c - entry point of bgdbserver
Copyright (C) 2018-2019 by Stefan "Bebbo" Franke <stefan@franke.ms>
This file is part of bgdbserver.
bgdbserver 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 3 of the License, or
(at your option) any later version.
bgdbserver 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 bgdbserver. If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <clib/alib_protos.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <dos/dostags.h>
#include <exec/execbase.h>
#include <inline/bsdsocket.h>
#include "common.h"
#include "breakpoint.h"
void unload(void);
void load(char const * progname, char const * clargs);
struct Library * SocketBase;
// the message port of the debugger.
struct MsgPort * dport;
struct PcMessage msg;
LONG trap;
// the loaded segments, if any
static BPTR seglist;
static char * codeseg;
static char * dataseg;
static char * bssseg;
static struct Process * theProc;
#define E_EOS 1
#define E_BUFFER_TO_SMALL 2
#define BUFFERSIZE 2050
static char buffer[BUFFERSIZE + 2];
/**
* Cleanup function.
*/
void cleanup(void) {
Printf((UBYTE*)"*** CLEANUP ***\n");
if (dport) {
DeleteMsgPort(dport);
dport = 0;
}
if (SocketBase)
CloseLibrary(SocketBase);
}
/**
* Allocate what's necessary.
*/
void setup(void) {
atexit(cleanup);
dport = CreatePort(0, 0);
if (!dport) {
puts("FATAL: no message port");
exit(1);
}
SocketBase = OpenLibrary((UBYTE*)"bsdsocket.library", 0);
if (!SocketBase) {
puts("FATAL: could not open bsdsocket.library!");
exit(1);
}
dprintf("port %lx, sig lib %lx\n", dport, SocketBase);
struct TagItem list[] = {
{ SBTM_SETVAL(SBTC_BREAKMASK), SIGBREAKF_CTRL_C },
{ TAG_END,TAG_END }
};
/* I will assume this always is successful */
SocketBaseTagList(list);
}
/**
* Read a line until 0xa.
*/
int readline(int fd) {
for (int pos = 0; pos < BUFFERSIZE;) {
int n = recv(fd, buffer + pos, BUFFERSIZE - pos, 0);
if (n <= 0)
return E_EOS;
pos += n;
buffer[pos] = 0;
char * p = strchr(buffer, 10);
if (p) {
*p = 0;
dprintf("readline: %s\n", buffer);
return 0;
}
}
return E_BUFFER_TO_SMALL;
}
/**
* Read a command until #xy
*/
int readcmd(int fd) {
for (int pos = 0; pos < BUFFERSIZE;) {
int n = recv(fd, buffer + pos, BUFFERSIZE - pos, 0);
if (n <= 0)
return -1;
pos += n;
char *p;
if (pos > 3)
p = buffer + pos - 3;
else
p = 0;
if (p && *p == '#') {
p[3] = 0;
dprintf("::<%s>\n", buffer);
return pos;
}
buffer[pos] = 0;
dprintf("%lx %ld:<%s>\n", (int)(buffer[0]), pos, buffer);
// CTRL+C ends up here
if (pos == 1 && buffer[0] == 3) {
return pos;
}
}
return -2;
}
/**
* Convert the <len> hex digits into the value.
*/
unsigned hex2n(char * p, short len) {
unsigned n = 0;
while (--len >= 0) {
unsigned char c = *p++;
if (!c)
break;
n <<= 4;
if (c > '9') {
c |= 0x20;
n += c - 'a' + 10;
} else {
n += c - '0';
}
}
return n;
}
/**
* Convert the the value n int <len> hex digits.
*/
void n2hex(int n, char * p, short len) {
p += len;
*p = 0;
while (--len >= 0) {
char c = n & 0xf;
n >>= 4;
if (c > 9)
c += 'a' - 10;
else
c += '0';
*--p = c;
}
}
/**
* True if string starts with part.
*/
short startswith(char const * string, char const * part) {
while(*part) {
if(*part++ != *string++)
return 0;
}
return 1;
}
/**
* Send a reply to gdb.
*
* Calculates the check sum and does the 'formatting'.
*/
short reply(int sockfd, char const * q) {
static char buffer[1030];
if (strlen(q) > 1024)
return -1;
dprintf("<- %s\n", q);
buffer[0] = '$';
char * p = buffer + 1;
short sum = 0;
while (*q) {
sum += *p++ = *q++;
}
*p++ = '#';
n2hex(sum, p, 2);
p += 2;
int n = send(sockfd, buffer, p - buffer, 0);
*p = 0;
// dprintf("<- %s\n", buffer);
return n;
}
/**
* Find the PC starting from the saved SP.
*/
UWORD * findPC(APTR in) {
UWORD * sp = (UWORD *) in;
UWORD attn = SysBase->AttnFlags;
// printf("start at sp=%p attn=%04x\n", sp, attn);
//
// for (int i = 0; i < 16; ++i)
// printf("%04x ", sp[i]);
// printf("\n");
if (attn > 1) {
// 68020+
unsigned chk = *(UBYTE*)sp;
// printf("ch = %02x\n", chk);
if (chk) {
// FPU was active
sp += 1 + 3 * 2 + 8 * 6 + 1; // ctrl word, FPCR/FPSR/FPIAR, FP0-FP7, ???
if (chk == 0x90) {
// mid insn
sp += 3*2; // mid insn frame
}
UWORD v = * sp;
if (v & 0xff00)
sp += (v & 0xff) / 2;
}
sp += 2; // FRESTORE
}
// 68000 and 68010
UWORD ** pcp = (UWORD **)sp;
// printf("pc=%p at %p\n", *pcp, sp);
return *pcp;
}
/**
* Let the client continue.
*/
void messageSlave(enum action myaction) {
newpc = reginfo.pc;
action = myaction;
ReplyMsg(&msg.msg);
}
void rungdb(int sockfd, char const * prg) {
ULONG portMask = (1 << dport->mp_SigBit);
dprintf("sock=%ld\n", sockfd);
fd_set readfds;
for (;;) {
FD_ZERO(&readfds);
FD_SET(sockfd, &readfds);
ULONG signales = SIGBREAKF_CTRL_C | portMask;
WaitSelect(sockfd + 1, &readfds, NULL, NULL, 0, &signales);
// dprintf("signaled %lx\n", signaled);
if (signales & SIGBREAKF_CTRL_C)
break;
// is there a message from the debugged process?
if (signales & portMask) {
if (GetMsg(dport)) {
dprintf("GOT MSG pc=%lX\n", reginfo.pc);
if (action == STEP_BEFORE_CONTINUE) {
enableBreakpoints();
messageSlave(CONTINUE);
} else {
disableBreakpoints();
// still running
if (reginfo.pc) {
sprintf(buffer, "T051:%08x;", reginfo.pc);
reply(sockfd, buffer);
} else {
// is in endProc -> unload seglist
messageSlave(PARK);
unload();
reply(sockfd, "X09");
break;
}
}
}
}
if (!FD_ISSET(sockfd, &readfds))
continue;
// dprintf("reading\n");
int n = readcmd(sockfd);
if (n <= 0)
break;
dprintf("-->%s<--\n", buffer);
// handle CTRL+C
if (n == 1 && buffer[0] == 3) {
dprintf("CTRL + C received %08lx sp=%08lx, %x\n", theProc, theProc->pr_Task.tc_SPReg, offsetof(struct Task, tc_SPReg));
if (!theProc) {
reply(sockfd, "E 01");
continue;
}
Printf("CTRL + C received\n");
do {
Forbid();
APTR cursp = theProc->pr_Task.tc_SPReg;
int chk = *(UBYTE*)cursp;
volatile UWORD * pc = findPC(cursp);
// check that the pc is somewhere inside the loaded segments
short ok = 0;
for (ULONG * s = (ULONG *) BADDR(seglist); s; s = (ULONG *) BADDR(*s)) {
if ((ULONG *)pc >= s && (ULONG *)pc < s + *(s - 1) - 8) {
ok = 1;
break;
}
}
if (ok) {
// try to insert a break point at pc
disableBreakpoints();
addBreakpoint(pc, 1, 0);
enableBreakpoints();
Permit();
reply(sockfd, "OK");
Printf("break at 0x%08lx\n", pc);
continue;
}
Permit();
Printf("can't break at 0x%08lx - %02lx\n", pc, chk);
Delay(1);
} while(SetSignal(0L,0L) == 0);
continue;
}
short start = 0;
while (start < n && buffer[start] != '$')
++start;
short sum = 0;
for (short i = n - 4; i > start; --i) {
sum += buffer[i];
}
sum &= 0xff;
short check = hex2n(buffer + n - 2, 2);
// check packet
if (n < 4 || buffer[start] != '$' || buffer[n - 3] != '#'
|| check != sum) {
send(sockfd, "-", 1, 0);
continue;
}
// dprintf(":: checksum ok\n");
send(sockfd, "+", 1, 0);
char * cmd = buffer + start + 1;
buffer[n - 3] = 0;
dprintf("-> %s\n", cmd);
if (startswith(cmd, "?")) {
sprintf(buffer, "T051:%08x;", reginfo.pc);
reply(sockfd, buffer);
continue;
}
if (startswith(cmd, "qSupported")) {
reply(sockfd, "qSupported:swbreak+;multiprocess-;vCont+");
continue;
}
if (startswith(cmd, "vMustReplyEmpty")) {
reply(sockfd, "");
continue;
}
if (startswith(cmd, "H")) {
reply(sockfd, "OK");
continue;
}
if (startswith(cmd, "qTStatus")) {
//reply(sockfd, "Trunning;tnotrun:0");
reply(sockfd, "");
continue;
}
if (startswith(cmd, "qfThreadInfo")) {
reply(sockfd, "m1");
continue;
}
if (startswith(cmd, "qsThreadInfo")) {
reply(sockfd, "l");
continue;
}
if (startswith(cmd, "qThreadExtraInfo")) {
reply(sockfd, "6D61696E"); // main as hex
continue;
}
if (startswith(cmd, "qAttached")) {
reply(sockfd, "0");
continue;
}
if (startswith(cmd, "qC")) {
// reply(sockfd, "QC1");
reply(sockfd, "");
continue;
}
if (startswith(cmd, "qOffsets")) {
if (bssseg)
sprintf(buffer, "Text=%08x;Data=%08x;Bss=%08x", codeseg, dataseg,
bssseg);
else if (dataseg)
sprintf(buffer, "Text=%08x;Data=%08x;Bss=%08x", codeseg, dataseg,
dataseg);
else
sprintf(buffer, "Text=%08x;Data=%08x;Bss=%08x", codeseg, 0, 0);
reply(sockfd, buffer);
continue;
}
if (startswith(cmd, "qTfV")) {
reply(sockfd, "");
continue;
}
// read all registers 16?
if (startswith(cmd, "g")) {
sprintf(buffer,
"%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x",
reginfo.regs[0], reginfo.regs[1], reginfo.regs[2],
reginfo.regs[3], reginfo.regs[4], reginfo.regs[5],
reginfo.regs[6], reginfo.regs[7], reginfo.regs[8],
reginfo.regs[9], reginfo.regs[10], reginfo.regs[11],
reginfo.regs[12], reginfo.regs[13], reginfo.regs[14],
reginfo.usp);
reply(sockfd, buffer);
continue;
}
// specific register, 11 == pc
if (startswith(cmd, "p")) {
sprintf(buffer, "%08x", reginfo.pc);
reply(sockfd, buffer);
continue;
}
// target wants symbols?
if (startswith(cmd, "qSymbol")) {
reply(sockfd, "OK");
continue;
}
// read memory
if (startswith(cmd, "m")) {
unsigned char * address = 0;
int len = 0;
sscanf(cmd + 1, "%08x,%08x", &address, &len);
char * p = buffer;
for (int i = 0; i < len; ++i) {
sprintf(p, "%02x", *address);
++address;
p += 2;
}
*p = 0;
reply(sockfd, buffer);
continue;
}
// supported vCont commands
if (startswith(cmd, "vCont?")) {
reply(sockfd, "vCont;cst");
continue;
}
// set sw breakpoints
if (startswith(cmd, "Z")) {
// Z0,address,kind
int no = 0;
UWORD * addr = 0;
int kind = 0; // unused atm
sscanf(cmd + 1, "%d,%08x,%d", &no, &addr, &kind);
addBreakpoint(addr, 0, 0);
reply(sockfd, "OK");
continue;
}
// clear sw breakpoints
if (startswith(cmd, "z")) {
// Z0,address,kind
int no = 0;
UWORD * addr = 0;
int kind = 0; // unused atm
sscanf(cmd + 1, "%d,%08x,%d", &no, &addr, &kind);
delBreakpoint(addr);
reply(sockfd, "OK");
continue;
}
// continue
if (startswith(cmd, "c")) {
messageSlave(STEP_BEFORE_CONTINUE);
continue;
}
if (startswith(cmd, "vKill")) {
reginfo.pc = (UWORD *)endProc;
messageSlave(CONTINUE);
continue;
}
// step
if (startswith(cmd, "s")) {
messageSlave(STEP);
continue;
}
// stop
if (startswith(cmd, "T")) {
// thread alive
if (seglist)
reply(sockfd, "OK");
else
reply(sockfd, "E 01");
continue;
}
if (startswith(cmd, "X")) {
char * p = 0;
int len = 0;
char * q = strchr(cmd, ':');
if (*q) {
*q = 0;
sscanf(cmd + 1, "%x,%d", &p, &len);
}
if (p) {
++q;
while (len-- > 0) {
char c = *q++;
if (c == 0x7d)
c = *q++ ^ 0x20;
dprintf("%02x", (short)c);
*p++ = c;
}
dprintf("\n");
reply(sockfd, "OK");
} else
reply(sockfd, "E 01");
continue;
}
Printf("unknown packet %s\n", cmd);
reply(sockfd, "E 42");
if (n < 0) {
puts("ERROR writing to socket");
break;
}
}
}
/**
* Open a socket on the given port.
*/
int openSocket(int portno, struct sockaddr_in * serv_addr) {
/* First call to socket() function */
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
Printf("ERROR opening socket\n");
return -1;
}
int enable = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) < 0) {
Printf("setsockopt(SO_REUSEADDR) failed\n");
}
/* Initialize socket structure */
bzero((char *) serv_addr, sizeof(struct sockaddr_in));
dprintf("create server socket %ld for port: %ld\n", sockfd, portno);
serv_addr->sin_family = AF_INET;
serv_addr->sin_addr.s_addr = INADDR_ANY;
serv_addr->sin_port = htons(portno);
/* Now bind the host address using bind() call.*/
if (bind(sockfd, (struct sockaddr *) serv_addr, sizeof(struct sockaddr_in))
< 0) {
Printf("ERROR on binding\n");
shutdown(sockfd, 0);
CloseSocket(sockfd);
return -1;
}
dprintf("bound server socket %ld for port: %ld\n", sockfd, portno);
listen(sockfd, 5);
return sockfd;
}
int wait4Connect(int sockfd, struct sockaddr_in * cli_addr) {
dprintf("waiting for connection on %ld\n", sockfd);
socklen_t clilen = sizeof(struct sockaddr_in);
bzero(cli_addr, clilen);
int newsockfd = accept(sockfd, (struct sockaddr *) cli_addr, &clilen);
dprintf("got socket %ld\n", newsockfd);
return newsockfd;
}
/**
* Run gdbserver on the given port.
*/
int runGdbServer(char * p, int fd) {
Printf("Running GDB server on port %s\n", p + 1);
while (*p && *p != ':')
++p;
if (!p)
return 3;
char * cmd = ++p;
while (*cmd >= '0' && *cmd <= '9')
++cmd;
if (*cmd)
*cmd++ = 0;
int gdbport = atoi(p);
while (*cmd && *cmd <= 32)
++cmd;
// find end
p = cmd;
while (*p)
++p;
// find last ;
while (p > cmd && *p != ';')
--p;
if (p > cmd)
*p = 0;
p = cmd;
while (*p && *p > 32)
++p;
if (*p) *p++ = 0;
char const * clargs = p;
dprintf("port = %ld, cmd = %s\n", gdbport, cmd);
struct sockaddr_in serv_addr;
int gdbfd = openSocket(gdbport, &serv_addr);
dprintf("socket =%ld\n", gdbfd);
if (gdbfd < 0)
return 4;
if (fd > 0) {
load(cmd, clargs);
if (seglist) {
sprintf(buffer, "Process %s created; pid = 1\n", cmd);
send(fd, buffer, strlen(buffer), 0);
sprintf(buffer, "Listening on port %d\n", gdbport);
send(fd, buffer, strlen(buffer), 0);
} else {
sprintf(buffer, "Failed to load: %s\n", cmd);
send(fd, buffer, strlen(buffer), 0);
}
}
if (seglist) {
struct sockaddr_in cli;
int newsockfd = wait4Connect(gdbfd, &cli);
if (newsockfd >= 0) {
rungdb(newsockfd, cmd);
dprintf("shutdown %ld\n", newsockfd);
shutdown(newsockfd, 0);
CloseSocket(newsockfd);
}
}
dprintf("shutdown %ld\n", gdbfd);
shutdown(gdbfd, 0);
CloseSocket(gdbfd);
return 0;
}
/**
* Open a port on 514 and wait for the gdbserver command
*/
int runRshFake(char const * sport) {
int err = 1;
int port = atoi(sport + 1);
Printf("Starting RSH server on port %s\n", sport + 1);
// open rsh port
struct sockaddr_in serv_addr;
int rshfd = openSocket(port, &serv_addr);
if (rshfd >= 0) {
do {
struct sockaddr_in cli;
int newsockfd = wait4Connect(rshfd, &cli);
if (newsockfd < 0)
break;
err = readline(newsockfd);
if (!err && 0 == strncmp("gdbserver ", buffer, 10))
err = runGdbServer(buffer + 10, newsockfd);
shutdown(newsockfd, 0);
CloseSocket(newsockfd);
} while (err >= 0);
shutdown(rshfd, 0);
CloseSocket(rshfd);
} else {
puts("ERROR: could not open socket on port 514\n");
err = 1;
}
// negative means clean exit
if (err < 0)
err = 0;
return err;
}
/**
* Show some usage info.
*/
int showUsage() {
printf("USAGE:\n"
"\tbgdbserver [:<port=514>]\n"
"\t\topen rsh at <port> and wait for a gdbserver command line\n"
"\t\tthen load the program into the bgdbserver\n"
"\tbgdbserver [:<port=2345>] <program> [arguments...] \n"
"\t\tload the given program into the bgdbserver and listen on port <port>\n");
return 0;
}
/**
* Unload everything.
*/
void unload(void) {
clearBreakpoints();
if (theProc)
Signal((struct Task *)theProc, SIGBREAKB_CTRL_C);
if (seglist) {
UnLoadSeg(seglist);
seglist = 0;
puts("unloaded program");
}
codeseg = dataseg = bssseg = 0;
theProc = 0;
}
struct MyHunk {
LONG size;
BPTR next;
UWORD jmp;
ULONG offset;
} dummyhunk;
/**
* Load a program.
*/
void load(char const * progname, char const * clargs) {
unload();
seglist = (BPTR) LoadSeg((UBYTE * )progname);
if (!seglist) {
printf("failed to load program %s\n", progname);
return;
}
printf("loaded program %s -> %p\n", progname, seglist);
int n = 0;
for (ULONG * s = (ULONG *) BADDR(seglist); s; s = (ULONG *) BADDR(*s)) {
printf("hunk at: %p sz=%08x\n", s, *(s - 1) - 8);
if (n == 0)
codeseg = (char *) (s + 1);
else if (n == 1)
dataseg = (char *) (s + 1);
else if (n == 2)
bssseg = (char *) (s + 1);
++n;
}
// init the message port
msg.msg.mn_Node.ln_Type = NT_MESSAGE;
msg.msg.mn_Length = sizeof(struct PcMessage);
reginfo.pc = (UWORD *) codeseg;
BPTR stdio = Open((UBYTE* )"*", MODE_READWRITE);
dprintf("con %lx\n", stdio);
theProc = CreateNewProcTags(NP_Entry, (ULONG )startProc,
NP_Input, (ULONG)stdio,
NP_Output, (ULONG )stdio,
NP_Arguments, (ULONG)clargs,
NP_FreeSeglist, 0,
NP_StackSize, 100000,
NP_Cli, 1,
NP_Name, (ULONG )progname,
TAG_END);
dprintf("proc = %08lx\n", theProc);
}
/**
* Bebbo's Debug Server.
*
* provide a dumy rsh server which waits for
* "gdbserver :port <program>"
* then open the desired port and act like a gdbserver.
*/
int main(int argc, char *argv[]) {
char * sport = 0;
Printf("bgdbserver 1.3 (c) by Stefan 'Bebbo' Franke 2018-2019\n");
setup();
atexit(unload);
int argx = 1;
if (argx < argc && argv[argx][0] == ':') {
sport = argv[argx];
++argx;
}
if (argx < argc && argv[argx][0] == '-') {
if (argv[argx][1] == 'r')
puts("-r is deprecated");
else {
showUsage();
return 0;
}
++argx;
}
if (argx == argc)
return runRshFake(sport ? sport : ":514" );
// load the given program and run the gdbserver on given port
struct Process * p = (struct Process *)FindTask(0);
char const * clargs = (char *)p->pr_Arguments;
for (int i = 1; i <= argx; ++i) {
clargs = strstr(clargs, argv[i]);
clargs += strlen(argv[i]);
while (*clargs > ' ')
++clargs;
}
load(argv[argx], clargs);
return runGdbServer(sport ? sport : ":2345" , 0);
}