Message ID | 20181005034254.890-2-rashmica.g@gmail.com |
---|---|
State | Superseded |
Headers | show |
Series | Basic gdbserver for POWER8 | expand |
Context | Check | Description |
---|---|---|
snowpatch_ozlabs/apply_patch | success | master/apply_patch Successfully applied |
snowpatch_ozlabs/build-multiarch | fail | Test build-multiarch on branch master |
On Fri, 5 Oct 2018 13:42:51 +1000 Rashmica Gupta <rashmica.g@gmail.com> wrote: > I have changed a few bits here and there but this patch is largely > authored by Alistair Popple. > > From: Alistair Popple <alistair@popple.id.au> > Signed-off-by: Rashmica Gupta <rashmica.g@gmail.com> > --- > .gitignore | 1 + > Makefile.am | 13 +- > configure.ac | 3 + > libpdbg/libpdbg.h | 1 + > libpdbg/operations.h | 3 - > libpdbg/target.h | 1 + > src/gdb_parser.rl | 146 ++++++++++++++ > src/main.c | 6 +- > src/pdbgproxy.c | 547 +++++++++++++++++++++++++++++++++++++++++++++++++++ > src/pdbgproxy.h | 13 ++ > 10 files changed, 727 insertions(+), 7 deletions(-) > create mode 100644 src/gdb_parser.rl > create mode 100644 src/pdbgproxy.c > create mode 100644 src/pdbgproxy.h > > diff --git a/.gitignore b/.gitignore > index c25b053..79f1ef9 100644 > --- a/.gitignore > +++ b/.gitignore > @@ -39,3 +39,4 @@ optcmd_test > *.trs > *.log > test-driver > +src/gdb_parser.c > diff --git a/Makefile.am b/Makefile.am > index 88aabe9..98f42b5 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -83,7 +83,9 @@ pdbg_SOURCES = \ > src/optcmd.h \ > src/options.h \ > src/parsers.h \ > - src/progress.h > + src/progress.h \ > + src/pdbgproxy.c \ > + src/gdb_parser.c > > src/main.c: $(DT_headers) > > @@ -193,6 +195,10 @@ GEN_V = $(GEN_V_$(V)) > GEN_V_ = $(GEN_V_$(AM_DEFAULT_VERBOSITY)) > GEN_V_0 = @echo " GEN " $@; > > +RAGEL_V = $(RAGEL_V_$(V)) > +RAGEL_V_ = $(RAGEL_V_$(AM_DEFAULT_VERBOSITY)) > +RAGEL_V_0 = @echo " RAGEL " $@; > + > %.dts: %.dts.m4 > $(M4_V)$(M4) -I$(dir $<) $< | $(DTC) -I dts -O dts > $@ > > @@ -213,4 +219,7 @@ p9z-fsi.dts: p9z-fsi.dts.m4 p9-fsi.dtsi > %.dtb.o: %.dtb > $(AM_V_CC)$(CC) -c $(srcdir)/template.S -DSYMBOL_PREFIX=$(shell echo $@ | tr '.-' '_') -DFILENAME=\"$<\" -o $@ > > -MOSTLYCLEANFILES = *.dtb *.dts *.dt.h p9-fsi.dtsi > +%.c: %.rl > + $(RAGEL_V)$(RAGEL) -o $@ $< > + > +MOSTLYCLEANFILES = *.dtb *.dts *.dt.h p9-fsi.dtsi src/gdb_parser.c > diff --git a/configure.ac b/configure.ac > index e48e80f..3486969 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -17,6 +17,9 @@ if test x"$ac_cv_path_DTC" = x ; then > fi > AC_SUBST([DTC]) > > +AC_PATH_PROG([RAGEL], [ragel]) > +AC_SUBST([RAGEL]) > + > AC_CONFIG_MACRO_DIR([m4]) > AC_CONFIG_HEADERS([config.h]) > AC_CONFIG_FILES([Makefile]) > diff --git a/libpdbg/libpdbg.h b/libpdbg/libpdbg.h > index 8bf7042..c802020 100644 > --- a/libpdbg/libpdbg.h > +++ b/libpdbg/libpdbg.h > @@ -137,6 +137,7 @@ struct thread_regs { > }; > > int ram_putmsr(struct pdbg_target *target, uint64_t val); > +int ram_getmem(struct pdbg_target *thread, uint64_t addr, uint64_t *value); > int ram_putnia(struct pdbg_target *target, uint64_t val); > int ram_putspr(struct pdbg_target *target, int spr, uint64_t val); > int ram_putgpr(struct pdbg_target *target, int spr, uint64_t val); > diff --git a/libpdbg/operations.h b/libpdbg/operations.h > index 93e5df5..96a7c01 100644 > --- a/libpdbg/operations.h > +++ b/libpdbg/operations.h > @@ -70,9 +70,6 @@ > > #define MXSPR_SPR(opcode) (((opcode >> 16) & 0x1f) | ((opcode >> 6) & 0x3e0)) > > -/* GDB server functionality */ > -int gdbserver_start(uint16_t port); > - > enum fsi_system_type {FSI_SYSTEM_P8, FSI_SYSTEM_P9W, FSI_SYSTEM_P9R, FSI_SYSTEM_P9Z}; > enum chip_type get_chip_type(uint64_t chip_id); > > diff --git a/libpdbg/target.h b/libpdbg/target.h > index c8da048..d1a6aec 100644 > --- a/libpdbg/target.h > +++ b/libpdbg/target.h > @@ -165,4 +165,5 @@ struct chiplet { > int (*getring)(struct chiplet *, uint64_t, int64_t, uint32_t[]); > }; > #define target_to_chiplet(x) container_of(x, struct chiplet, target) > + > #endif > diff --git a/src/gdb_parser.rl b/src/gdb_parser.rl > new file mode 100644 > index 0000000..6259b96 > --- /dev/null > +++ b/src/gdb_parser.rl > @@ -0,0 +1,146 @@ > +#include <stdio.h> > +#include <stdint.h> > +#include <string.h> > +#include <assert.h> > + > +#include "src/pdbgproxy.h" > + > +%%{ > + machine gdb; > + > + action reset { > + cmd = 0; > + rsp = NULL; > + data = stack; > + memset(stack, 0, sizeof(stack)); > + crc = 0; > + } > + > + action crc { > + crc += *p; > + } > + > + action push { > + data++; > + assert(data < &stack[10]); > + } > + > + action hex_digit { > + *data *= 16; > + > + if (*p >= '0' && *p <= '9') > + *data += *p - '0'; > + else if (*p >= 'a' && *p <= 'f') > + *data += *p - 'a' + 10; > + else if (*p >= 'A' && *p <= 'F') > + *data += *p - 'A' + 10; > + } > + > + action end { > + /* *data should point to the CRC */ > + if (crc != *data) { > + printf("CRC error\n"); > + send_nack(priv); > + } else { > + printf("Cmd %d\n", cmd); > + send_ack(priv); > + > + /* Push the response onto the stack */ > + if (rsp) > + *data = (uintptr_t)rsp; > + else > + *data = 0; > + > + command_callbacks[cmd](stack, priv); > + } > + } > + > + get_mem = ('m' @{cmd = GET_MEM;} > + xdigit+ $hex_digit %push > + ',' > + xdigit+ $hex_digit %push); > + > + put_mem = ('M' any* @{cmd = PUT_MEM;} > + xdigit+ $hex_digit %push > + ',' > + xdigit+ $hex_digit %push > + ':' > + xdigit+ $hex_digit %push); > + > + get_gprs = ('g' @{cmd = GET_GPRS;}); > + > + get_spr = ('p' @{cmd = GET_SPR;} > + xdigit+ $hex_digit %push); > + > + stop_reason = ('?' @{cmd = STOP_REASON;}); > + > + set_thread = ('H' any* @{cmd = SET_THREAD;}); > + > + disconnect = ('D' @{cmd = DISCONNECT;} > + xdigit+ $hex_digit %push); > + > + # TODO: We don't actually listen to what's supported > + q_attached = ('qAttached:' xdigit* @{rsp = "1";}); > + q_C = ('qC' @{rsp = "QC1";}); > + q_supported = ('qSupported:' any* @{rsp = "multiprocess+;vContSupported+";}); > + qf_threadinfo = ('qfThreadInfo' @{rsp = "m1l";}); > + > + # vCont packet parsing > + v_contq = ('vCont?' @{rsp = "vCont;c;C;s;S";}); > + v_contc = ('vCont;c' any* @{cmd = V_CONTC;}); > + v_conts = ('vCont;s' any* @{cmd = V_CONTS;}); > + > + interrupt = (3 @{command_callbacks[INTERRUPT](stack, priv);}); > + > + commands = (get_mem | get_gprs | get_spr | stop_reason | set_thread | > + q_attached | q_C | q_supported | qf_threadinfo | q_C | > + v_contq | v_contc | v_conts | put_mem | disconnect ); > + > + cmd = ((commands & ^'#'*) | ^'#'*) $crc > + ('#' xdigit{2} $hex_digit @end); > + > + # We ignore ACK/NACK for the moment > + ack = ('+'); > + nack = ('-'); > + > + main := (( ^('$' | interrupt)*('$' | interrupt) @reset) (cmd | ack | nack))*; > + > +}%% > + > +static enum gdb_command cmd = NONE; > +static uint64_t stack[10], *data = stack; > +static char *rsp; > +static uint8_t crc; > +static int cs; > + > +command_cb *command_callbacks; > + > +%%write data; > + > +void parser_init(command_cb *callbacks) > +{ > + %%write init; > + > + command_callbacks = callbacks; > +} > + > +int parse_buffer(char *buf, size_t len, void *priv) > +{ > + char *p = buf; > + char *pe = p + len + 1; > + > + %%write exec; > + > + return 0; > +} > + > +#if 0 > +int main(int argc, char **argv) > +{ > + parser_init(NULL); > + > + if (argc > 1) > + parse_buffer(argv[1], strlen(argv[1]), NULL); > + return 0; > +} > +#endif > diff --git a/src/main.c b/src/main.c > index 8b7e2dd..a0adeba 100644 > --- a/src/main.c > +++ b/src/main.c > @@ -38,6 +38,7 @@ > #include "options.h" > #include "optcmd.h" > #include "progress.h" > +#include "pdbgproxy.h" > > #define PR_ERROR(x, args...) \ > pdbg_log(PDBG_ERROR, x, ##args) > @@ -88,7 +89,7 @@ extern struct optcmd_cmd > optcmd_getring, optcmd_start, optcmd_stop, optcmd_step, > optcmd_threadstatus, optcmd_sreset, optcmd_regs, optcmd_probe, > optcmd_getmem, optcmd_putmem, optcmd_getxer, optcmd_putxer, > - optcmd_getcr, optcmd_putcr; > + optcmd_getcr, optcmd_putcr, optcmd_gdbserver; > > static struct optcmd_cmd *cmds[] = { > &optcmd_getscom, &optcmd_putscom, &optcmd_getcfam, &optcmd_putcfam, > @@ -97,7 +98,7 @@ static struct optcmd_cmd *cmds[] = { > &optcmd_getring, &optcmd_start, &optcmd_stop, &optcmd_step, > &optcmd_threadstatus, &optcmd_sreset, &optcmd_regs, &optcmd_probe, > &optcmd_getmem, &optcmd_putmem, &optcmd_getxer, &optcmd_putxer, > - &optcmd_getcr, &optcmd_putcr, > + &optcmd_getcr, &optcmd_putcr, &optcmd_gdbserver, > }; > > /* Purely for printing usage text. We could integrate printing argument and flag > @@ -136,6 +137,7 @@ static struct action actions[] = { > { "threadstatus", "", "Print the status of a thread" }, > { "sreset", "", "Reset" }, > { "regs", "", "State" }, > + { "gdbserver", "", "Start a gdb server" }, > }; > > static void print_usage(char *pname) > diff --git a/src/pdbgproxy.c b/src/pdbgproxy.c > new file mode 100644 > index 0000000..1ec66d6 > --- /dev/null > +++ b/src/pdbgproxy.c > @@ -0,0 +1,547 @@ > +#define _GNU_SOURCE > +#include <stdint.h> > +#include <stdbool.h> > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <fcntl.h> > +#include <unistd.h> > +#include <sys/types.h> > +#include <sys/socket.h> > +#include <netinet/in.h> > +#include <netdb.h> > +#include <inttypes.h> > +#include <assert.h> > +#include <getopt.h> > +#include <errno.h> > +#include <ccan/array_size/array_size.h> > + > +#include <backend.h> > +#include <operations.h> > + > +#include "pdbgproxy.h" > +#include "main.h" > +#include "optcmd.h" > +#include "debug.h" > +#include "chip.h" > + > +/* Maximum packet size */ > +#define BUFFER_SIZE 8192 > + > +/* GDB packets */ > +#define STR(e) "e" > +#define ACK "+" > +#define NACK "-" > +#define OK "OK" > +#define TRAP "S05" > +#define ERROR(e) "E"STR(e) > + > +#define TEST_SKIBOOT_ADDR 0x40000000 > + > +static struct pdbg_target *thread_target = NULL; > +static struct timeval timeout; > +static int poll_interval = 100; > +static int fd = -1; > +static int littleendian = 1; > +enum client_state {IDLE, SIGNAL_WAIT}; > +static enum client_state state = IDLE; > + > +static void destroy_client(int dead_fd); > + > +static uint8_t gdbcrc(char *data) > +{ > + uint8_t crc = 0; > + int i; > + > + for (i = 0; i < strlen(data); i++) > + crc += data[i]; > + > + return crc; > +} > + > +static void send_response(int fd, char *response) > +{ > + int len; > + char *result; > + > + len = asprintf(&result, "$%s#%02x", response, gdbcrc(response)); > + PR_INFO("Send: %s\n", result); > + send(fd, result, len, 0); > + free(result); > +} > + > +void send_nack(void *priv) > +{ > + PR_INFO("Send: -\n"); > + send(fd, NACK, 1, 0); > +} > + > +void send_ack(void *priv) > +{ > + PR_INFO("Send: +\n"); > + send(fd, ACK, 1, 0); > +} > + > +static void set_thread(uint64_t *stack, void *priv) > +{ > + send_response(fd, OK); > +} > + > +static void stop_reason(uint64_t *stack, void *priv) > +{ > + send_response(fd, TRAP); > +} > + > +static void disconnect(uint64_t *stack, void *priv) > +{ > + PR_INFO("Terminating connection with client. pid %16" PRIi64 "\n", stack[0]); > + send_response(fd, OK); > +} > + > +/* 32 registers represented as 16 char hex numbers with null-termination */ > +#define REG_DATA_SIZE (32*16+1) > +static void get_gprs(uint64_t *stack, void *priv) > +{ > + char data[REG_DATA_SIZE] = ""; > + struct thread_regs regs; > + int i; > + > + if(ram_state_thread(thread_target, ®s)) > + PR_ERROR("Error reading gprs\n"); > + > + for (i = 0; i < 32; i++) { > + PR_INFO("r%d = 0x%016" PRIx64 "\n", i, regs.gprs[i]); > + snprintf(data + i*16, 17, "%016" PRIx64 , __builtin_bswap64(regs.gprs[i])); > + } > + > + send_response(fd, data); > +} > + > +static void get_spr(uint64_t *stack, void *priv) > +{ > + char data[REG_DATA_SIZE]; > + uint64_t value; > + > + switch (stack[0]) { > + case 0x40: > + /* Get PC/NIA */ > + if (ram_getnia(thread_target, &value)) > + PR_ERROR("Error reading NIA\n"); > + snprintf(data, REG_DATA_SIZE, "%016" PRIx64 , __builtin_bswap64(value)); > + send_response(fd, data); > + break; > + > + case 0x41: > + /* Get MSR */ > + if (ram_getmsr(thread_target, &value)) > + PR_ERROR("Error reading MSR\n"); > + snprintf(data, REG_DATA_SIZE, "%016" PRIx64 , __builtin_bswap64(value)); > + send_response(fd, data); > + break; > + > + case 0x42: > + /* Get CR */ > + if (ram_getcr(thread_target, (uint32_t *)&value)) > + PR_ERROR("Error reading CR \n"); > + snprintf(data, REG_DATA_SIZE, "%016" PRIx64 , __builtin_bswap64(value)); > + send_response(fd, data); > + break; > + > + case 0x43: > + /* Get LR */ > + if (ram_getspr(thread_target, 8, &value)) > + PR_ERROR("Error reading LR\n"); > + snprintf(data, REG_DATA_SIZE, "%016" PRIx64 , __builtin_bswap64(value)); > + send_response(fd, data); > + break; > + > + case 0x44: > + /* Get CTR */ > + if (ram_getspr(thread_target, 9, &value)) > + PR_ERROR("Error reading CTR\n"); > + snprintf(data, REG_DATA_SIZE, "%016" PRIx64 , __builtin_bswap64(value)); > + send_response(fd, data); > + break; > + > + case 0x45: > + /* We can't get the whole XER register in RAM mode as part of it > + * is in latches that we need to stop the clocks to get. Probably > + * not helpful to only return part of a register in a debugger so > + * return unavailable. */ > + send_response(fd, "xxxxxxxxxxxxxxxx"); > + break; > + > + default: > + send_response(fd, "xxxxxxxxxxxxxxxx"); > + break; > + } > +} > + > +#define MAX_DATA 0x1000 > + > +/* Returns a real address to use with adu_getmem or -1UL if we > + * couldn't determine a real address. At the moment we only deal with > + * kernel linear mapping but in future we could walk that page > + * tables. */ > +static uint64_t get_real_addr(uint64_t addr) > +{ > + if (GETFIELD(PPC_BITMASK(0, 3), addr) == 0xc) > + /* Assume all 0xc... addresses are part of the linux linear map */ > + addr &= ~PPC_BITMASK(0, 1); > + else if (addr < TEST_SKIBOOT_ADDR) > + return addr; > + else > + addr = -1UL; > + > + return addr; > +} > + > +static void get_mem(uint64_t *stack, void *priv) > +{ > + struct pdbg_target *adu; > + uint64_t addr, len, linear_map; > + int i, err = 0; > + uint64_t data[MAX_DATA/sizeof(uint64_t)]; > + char result[2*MAX_DATA]; > + > + /* stack[0] is the address and stack[1] is the length */ > + addr = stack[0]; > + len = stack[1]; > + > + pdbg_for_each_class_target("adu", adu) { > + if (pdbg_target_probe(adu) == PDBG_TARGET_ENABLED) > + break; > + } > + > + if (adu == NULL) { > + PR_ERROR("ADU NOT FOUND\n"); > + err=3; > + goto out; > + } > + > + if (len > MAX_DATA) { > + PR_INFO("Too much memory requested, truncating\n"); > + len = MAX_DATA; > + } > + > + if (!addr) { > + err = 2; > + goto out; > + } > + > + linear_map = get_real_addr(addr); > + if (linear_map != -1UL) { > + if (adu_getmem(adu, addr, (uint8_t *) data, len)) { > + PR_ERROR("Unable to read memory\n"); > + err = 1; > + } > + } else { > + /* Virtual address */ > + for (i = 0; i < len; i += sizeof(uint64_t)) { > + if (ram_getmem(thread_target, addr, &data[i/sizeof(uint64_t)])) { > + PR_ERROR("Fault reading memory\n"); > + err = 2; > + break; > + } > + } > + } > + > +out: > + if (!err) > + for (i = 0; i < len; i ++) { > + sprintf(&result[i*2], "%02x", *(((uint8_t *) data) + i)); > + } > + else > + sprintf(result, "E%02x", err); > + > + send_response(fd, result); > +} > + > +static void put_mem(uint64_t *stack, void *priv) > +{ > + struct pdbg_target *adu; > + uint64_t addr, len; > + uint8_t *data; > + uint8_t attn_opcode[] = {0x00, 0x00, 0x02, 0x00}; > + int err = 0; > + struct thread *thread = target_to_thread(thread_target); > + > + if (littleendian) { > + attn_opcode[1] = 0x02; > + attn_opcode[2] = 0x00; > + } Good catch with the endian fix. So we can't do a reliable break point in code that executes with a different endian than the CPU is currently in :( > + > + addr = stack[0]; > + len = stack[1]; > + data = (uint8_t *) &stack[2]; > + > + pdbg_for_each_class_target("adu", adu) { > + if (pdbg_target_probe(adu) == PDBG_TARGET_ENABLED) > + break; > + } > + > + if (adu == NULL) { > + PR_ERROR("ADU NOT FOUND\n"); > + err=3; > + goto out; > + } > + > + addr = get_real_addr(addr); > + if (addr == -1UL) { > + PR_ERROR("TODO: No virtual address support for putmem\n"); > + err = 1; > + goto out; > + } > + > + > + if (len == 4 && stack[2] == 0x0810827d) { > + /* According to linux-ppc-low.c gdb only uses this > + * op-code for sw break points so we replace it with > + * the correct attn opcode which is what we need for > + * breakpoints. > + * > + * TODO: Upstream a patch to gdb so that it uses the > + * right opcode for baremetal debug. */ > + PR_INFO("Breakpoint opcode detected, replacing with attn\n"); > + data = attn_opcode; Somehow coping with wrong endian would be nice. I wonder if you could keep track of all the breakpoints the client requested, and for each one you save additional instructions after the break point, and replace them with a sequence of instructions that will branch to different attns based on endian (see: FIXUP_ENDIAN), then fix things up according to which break you hit. You probably lose CFAR in the wrong-endian case, but maybe better than nothing. Anyway that's clearly for a later change. > + > + /* Need to enable the attn instruction in HID0 */ > + if (thread->enable_attn(thread_target)) > + goto out; Do you need to disable attn somewhere after detach? Thanks, Nick
diff --git a/.gitignore b/.gitignore index c25b053..79f1ef9 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ optcmd_test *.trs *.log test-driver +src/gdb_parser.c diff --git a/Makefile.am b/Makefile.am index 88aabe9..98f42b5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -83,7 +83,9 @@ pdbg_SOURCES = \ src/optcmd.h \ src/options.h \ src/parsers.h \ - src/progress.h + src/progress.h \ + src/pdbgproxy.c \ + src/gdb_parser.c src/main.c: $(DT_headers) @@ -193,6 +195,10 @@ GEN_V = $(GEN_V_$(V)) GEN_V_ = $(GEN_V_$(AM_DEFAULT_VERBOSITY)) GEN_V_0 = @echo " GEN " $@; +RAGEL_V = $(RAGEL_V_$(V)) +RAGEL_V_ = $(RAGEL_V_$(AM_DEFAULT_VERBOSITY)) +RAGEL_V_0 = @echo " RAGEL " $@; + %.dts: %.dts.m4 $(M4_V)$(M4) -I$(dir $<) $< | $(DTC) -I dts -O dts > $@ @@ -213,4 +219,7 @@ p9z-fsi.dts: p9z-fsi.dts.m4 p9-fsi.dtsi %.dtb.o: %.dtb $(AM_V_CC)$(CC) -c $(srcdir)/template.S -DSYMBOL_PREFIX=$(shell echo $@ | tr '.-' '_') -DFILENAME=\"$<\" -o $@ -MOSTLYCLEANFILES = *.dtb *.dts *.dt.h p9-fsi.dtsi +%.c: %.rl + $(RAGEL_V)$(RAGEL) -o $@ $< + +MOSTLYCLEANFILES = *.dtb *.dts *.dt.h p9-fsi.dtsi src/gdb_parser.c diff --git a/configure.ac b/configure.ac index e48e80f..3486969 100644 --- a/configure.ac +++ b/configure.ac @@ -17,6 +17,9 @@ if test x"$ac_cv_path_DTC" = x ; then fi AC_SUBST([DTC]) +AC_PATH_PROG([RAGEL], [ragel]) +AC_SUBST([RAGEL]) + AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_FILES([Makefile]) diff --git a/libpdbg/libpdbg.h b/libpdbg/libpdbg.h index 8bf7042..c802020 100644 --- a/libpdbg/libpdbg.h +++ b/libpdbg/libpdbg.h @@ -137,6 +137,7 @@ struct thread_regs { }; int ram_putmsr(struct pdbg_target *target, uint64_t val); +int ram_getmem(struct pdbg_target *thread, uint64_t addr, uint64_t *value); int ram_putnia(struct pdbg_target *target, uint64_t val); int ram_putspr(struct pdbg_target *target, int spr, uint64_t val); int ram_putgpr(struct pdbg_target *target, int spr, uint64_t val); diff --git a/libpdbg/operations.h b/libpdbg/operations.h index 93e5df5..96a7c01 100644 --- a/libpdbg/operations.h +++ b/libpdbg/operations.h @@ -70,9 +70,6 @@ #define MXSPR_SPR(opcode) (((opcode >> 16) & 0x1f) | ((opcode >> 6) & 0x3e0)) -/* GDB server functionality */ -int gdbserver_start(uint16_t port); - enum fsi_system_type {FSI_SYSTEM_P8, FSI_SYSTEM_P9W, FSI_SYSTEM_P9R, FSI_SYSTEM_P9Z}; enum chip_type get_chip_type(uint64_t chip_id); diff --git a/libpdbg/target.h b/libpdbg/target.h index c8da048..d1a6aec 100644 --- a/libpdbg/target.h +++ b/libpdbg/target.h @@ -165,4 +165,5 @@ struct chiplet { int (*getring)(struct chiplet *, uint64_t, int64_t, uint32_t[]); }; #define target_to_chiplet(x) container_of(x, struct chiplet, target) + #endif diff --git a/src/gdb_parser.rl b/src/gdb_parser.rl new file mode 100644 index 0000000..6259b96 --- /dev/null +++ b/src/gdb_parser.rl @@ -0,0 +1,146 @@ +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <assert.h> + +#include "src/pdbgproxy.h" + +%%{ + machine gdb; + + action reset { + cmd = 0; + rsp = NULL; + data = stack; + memset(stack, 0, sizeof(stack)); + crc = 0; + } + + action crc { + crc += *p; + } + + action push { + data++; + assert(data < &stack[10]); + } + + action hex_digit { + *data *= 16; + + if (*p >= '0' && *p <= '9') + *data += *p - '0'; + else if (*p >= 'a' && *p <= 'f') + *data += *p - 'a' + 10; + else if (*p >= 'A' && *p <= 'F') + *data += *p - 'A' + 10; + } + + action end { + /* *data should point to the CRC */ + if (crc != *data) { + printf("CRC error\n"); + send_nack(priv); + } else { + printf("Cmd %d\n", cmd); + send_ack(priv); + + /* Push the response onto the stack */ + if (rsp) + *data = (uintptr_t)rsp; + else + *data = 0; + + command_callbacks[cmd](stack, priv); + } + } + + get_mem = ('m' @{cmd = GET_MEM;} + xdigit+ $hex_digit %push + ',' + xdigit+ $hex_digit %push); + + put_mem = ('M' any* @{cmd = PUT_MEM;} + xdigit+ $hex_digit %push + ',' + xdigit+ $hex_digit %push + ':' + xdigit+ $hex_digit %push); + + get_gprs = ('g' @{cmd = GET_GPRS;}); + + get_spr = ('p' @{cmd = GET_SPR;} + xdigit+ $hex_digit %push); + + stop_reason = ('?' @{cmd = STOP_REASON;}); + + set_thread = ('H' any* @{cmd = SET_THREAD;}); + + disconnect = ('D' @{cmd = DISCONNECT;} + xdigit+ $hex_digit %push); + + # TODO: We don't actually listen to what's supported + q_attached = ('qAttached:' xdigit* @{rsp = "1";}); + q_C = ('qC' @{rsp = "QC1";}); + q_supported = ('qSupported:' any* @{rsp = "multiprocess+;vContSupported+";}); + qf_threadinfo = ('qfThreadInfo' @{rsp = "m1l";}); + + # vCont packet parsing + v_contq = ('vCont?' @{rsp = "vCont;c;C;s;S";}); + v_contc = ('vCont;c' any* @{cmd = V_CONTC;}); + v_conts = ('vCont;s' any* @{cmd = V_CONTS;}); + + interrupt = (3 @{command_callbacks[INTERRUPT](stack, priv);}); + + commands = (get_mem | get_gprs | get_spr | stop_reason | set_thread | + q_attached | q_C | q_supported | qf_threadinfo | q_C | + v_contq | v_contc | v_conts | put_mem | disconnect ); + + cmd = ((commands & ^'#'*) | ^'#'*) $crc + ('#' xdigit{2} $hex_digit @end); + + # We ignore ACK/NACK for the moment + ack = ('+'); + nack = ('-'); + + main := (( ^('$' | interrupt)*('$' | interrupt) @reset) (cmd | ack | nack))*; + +}%% + +static enum gdb_command cmd = NONE; +static uint64_t stack[10], *data = stack; +static char *rsp; +static uint8_t crc; +static int cs; + +command_cb *command_callbacks; + +%%write data; + +void parser_init(command_cb *callbacks) +{ + %%write init; + + command_callbacks = callbacks; +} + +int parse_buffer(char *buf, size_t len, void *priv) +{ + char *p = buf; + char *pe = p + len + 1; + + %%write exec; + + return 0; +} + +#if 0 +int main(int argc, char **argv) +{ + parser_init(NULL); + + if (argc > 1) + parse_buffer(argv[1], strlen(argv[1]), NULL); + return 0; +} +#endif diff --git a/src/main.c b/src/main.c index 8b7e2dd..a0adeba 100644 --- a/src/main.c +++ b/src/main.c @@ -38,6 +38,7 @@ #include "options.h" #include "optcmd.h" #include "progress.h" +#include "pdbgproxy.h" #define PR_ERROR(x, args...) \ pdbg_log(PDBG_ERROR, x, ##args) @@ -88,7 +89,7 @@ extern struct optcmd_cmd optcmd_getring, optcmd_start, optcmd_stop, optcmd_step, optcmd_threadstatus, optcmd_sreset, optcmd_regs, optcmd_probe, optcmd_getmem, optcmd_putmem, optcmd_getxer, optcmd_putxer, - optcmd_getcr, optcmd_putcr; + optcmd_getcr, optcmd_putcr, optcmd_gdbserver; static struct optcmd_cmd *cmds[] = { &optcmd_getscom, &optcmd_putscom, &optcmd_getcfam, &optcmd_putcfam, @@ -97,7 +98,7 @@ static struct optcmd_cmd *cmds[] = { &optcmd_getring, &optcmd_start, &optcmd_stop, &optcmd_step, &optcmd_threadstatus, &optcmd_sreset, &optcmd_regs, &optcmd_probe, &optcmd_getmem, &optcmd_putmem, &optcmd_getxer, &optcmd_putxer, - &optcmd_getcr, &optcmd_putcr, + &optcmd_getcr, &optcmd_putcr, &optcmd_gdbserver, }; /* Purely for printing usage text. We could integrate printing argument and flag @@ -136,6 +137,7 @@ static struct action actions[] = { { "threadstatus", "", "Print the status of a thread" }, { "sreset", "", "Reset" }, { "regs", "", "State" }, + { "gdbserver", "", "Start a gdb server" }, }; static void print_usage(char *pname) diff --git a/src/pdbgproxy.c b/src/pdbgproxy.c new file mode 100644 index 0000000..1ec66d6 --- /dev/null +++ b/src/pdbgproxy.c @@ -0,0 +1,547 @@ +#define _GNU_SOURCE +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> +#include <inttypes.h> +#include <assert.h> +#include <getopt.h> +#include <errno.h> +#include <ccan/array_size/array_size.h> + +#include <backend.h> +#include <operations.h> + +#include "pdbgproxy.h" +#include "main.h" +#include "optcmd.h" +#include "debug.h" +#include "chip.h" + +/* Maximum packet size */ +#define BUFFER_SIZE 8192 + +/* GDB packets */ +#define STR(e) "e" +#define ACK "+" +#define NACK "-" +#define OK "OK" +#define TRAP "S05" +#define ERROR(e) "E"STR(e) + +#define TEST_SKIBOOT_ADDR 0x40000000 + +static struct pdbg_target *thread_target = NULL; +static struct timeval timeout; +static int poll_interval = 100; +static int fd = -1; +static int littleendian = 1; +enum client_state {IDLE, SIGNAL_WAIT}; +static enum client_state state = IDLE; + +static void destroy_client(int dead_fd); + +static uint8_t gdbcrc(char *data) +{ + uint8_t crc = 0; + int i; + + for (i = 0; i < strlen(data); i++) + crc += data[i]; + + return crc; +} + +static void send_response(int fd, char *response) +{ + int len; + char *result; + + len = asprintf(&result, "$%s#%02x", response, gdbcrc(response)); + PR_INFO("Send: %s\n", result); + send(fd, result, len, 0); + free(result); +} + +void send_nack(void *priv) +{ + PR_INFO("Send: -\n"); + send(fd, NACK, 1, 0); +} + +void send_ack(void *priv) +{ + PR_INFO("Send: +\n"); + send(fd, ACK, 1, 0); +} + +static void set_thread(uint64_t *stack, void *priv) +{ + send_response(fd, OK); +} + +static void stop_reason(uint64_t *stack, void *priv) +{ + send_response(fd, TRAP); +} + +static void disconnect(uint64_t *stack, void *priv) +{ + PR_INFO("Terminating connection with client. pid %16" PRIi64 "\n", stack[0]); + send_response(fd, OK); +} + +/* 32 registers represented as 16 char hex numbers with null-termination */ +#define REG_DATA_SIZE (32*16+1) +static void get_gprs(uint64_t *stack, void *priv) +{ + char data[REG_DATA_SIZE] = ""; + struct thread_regs regs; + int i; + + if(ram_state_thread(thread_target, ®s)) + PR_ERROR("Error reading gprs\n"); + + for (i = 0; i < 32; i++) { + PR_INFO("r%d = 0x%016" PRIx64 "\n", i, regs.gprs[i]); + snprintf(data + i*16, 17, "%016" PRIx64 , __builtin_bswap64(regs.gprs[i])); + } + + send_response(fd, data); +} + +static void get_spr(uint64_t *stack, void *priv) +{ + char data[REG_DATA_SIZE]; + uint64_t value; + + switch (stack[0]) { + case 0x40: + /* Get PC/NIA */ + if (ram_getnia(thread_target, &value)) + PR_ERROR("Error reading NIA\n"); + snprintf(data, REG_DATA_SIZE, "%016" PRIx64 , __builtin_bswap64(value)); + send_response(fd, data); + break; + + case 0x41: + /* Get MSR */ + if (ram_getmsr(thread_target, &value)) + PR_ERROR("Error reading MSR\n"); + snprintf(data, REG_DATA_SIZE, "%016" PRIx64 , __builtin_bswap64(value)); + send_response(fd, data); + break; + + case 0x42: + /* Get CR */ + if (ram_getcr(thread_target, (uint32_t *)&value)) + PR_ERROR("Error reading CR \n"); + snprintf(data, REG_DATA_SIZE, "%016" PRIx64 , __builtin_bswap64(value)); + send_response(fd, data); + break; + + case 0x43: + /* Get LR */ + if (ram_getspr(thread_target, 8, &value)) + PR_ERROR("Error reading LR\n"); + snprintf(data, REG_DATA_SIZE, "%016" PRIx64 , __builtin_bswap64(value)); + send_response(fd, data); + break; + + case 0x44: + /* Get CTR */ + if (ram_getspr(thread_target, 9, &value)) + PR_ERROR("Error reading CTR\n"); + snprintf(data, REG_DATA_SIZE, "%016" PRIx64 , __builtin_bswap64(value)); + send_response(fd, data); + break; + + case 0x45: + /* We can't get the whole XER register in RAM mode as part of it + * is in latches that we need to stop the clocks to get. Probably + * not helpful to only return part of a register in a debugger so + * return unavailable. */ + send_response(fd, "xxxxxxxxxxxxxxxx"); + break; + + default: + send_response(fd, "xxxxxxxxxxxxxxxx"); + break; + } +} + +#define MAX_DATA 0x1000 + +/* Returns a real address to use with adu_getmem or -1UL if we + * couldn't determine a real address. At the moment we only deal with + * kernel linear mapping but in future we could walk that page + * tables. */ +static uint64_t get_real_addr(uint64_t addr) +{ + if (GETFIELD(PPC_BITMASK(0, 3), addr) == 0xc) + /* Assume all 0xc... addresses are part of the linux linear map */ + addr &= ~PPC_BITMASK(0, 1); + else if (addr < TEST_SKIBOOT_ADDR) + return addr; + else + addr = -1UL; + + return addr; +} + +static void get_mem(uint64_t *stack, void *priv) +{ + struct pdbg_target *adu; + uint64_t addr, len, linear_map; + int i, err = 0; + uint64_t data[MAX_DATA/sizeof(uint64_t)]; + char result[2*MAX_DATA]; + + /* stack[0] is the address and stack[1] is the length */ + addr = stack[0]; + len = stack[1]; + + pdbg_for_each_class_target("adu", adu) { + if (pdbg_target_probe(adu) == PDBG_TARGET_ENABLED) + break; + } + + if (adu == NULL) { + PR_ERROR("ADU NOT FOUND\n"); + err=3; + goto out; + } + + if (len > MAX_DATA) { + PR_INFO("Too much memory requested, truncating\n"); + len = MAX_DATA; + } + + if (!addr) { + err = 2; + goto out; + } + + linear_map = get_real_addr(addr); + if (linear_map != -1UL) { + if (adu_getmem(adu, addr, (uint8_t *) data, len)) { + PR_ERROR("Unable to read memory\n"); + err = 1; + } + } else { + /* Virtual address */ + for (i = 0; i < len; i += sizeof(uint64_t)) { + if (ram_getmem(thread_target, addr, &data[i/sizeof(uint64_t)])) { + PR_ERROR("Fault reading memory\n"); + err = 2; + break; + } + } + } + +out: + if (!err) + for (i = 0; i < len; i ++) { + sprintf(&result[i*2], "%02x", *(((uint8_t *) data) + i)); + } + else + sprintf(result, "E%02x", err); + + send_response(fd, result); +} + +static void put_mem(uint64_t *stack, void *priv) +{ + struct pdbg_target *adu; + uint64_t addr, len; + uint8_t *data; + uint8_t attn_opcode[] = {0x00, 0x00, 0x02, 0x00}; + int err = 0; + struct thread *thread = target_to_thread(thread_target); + + if (littleendian) { + attn_opcode[1] = 0x02; + attn_opcode[2] = 0x00; + } + + addr = stack[0]; + len = stack[1]; + data = (uint8_t *) &stack[2]; + + pdbg_for_each_class_target("adu", adu) { + if (pdbg_target_probe(adu) == PDBG_TARGET_ENABLED) + break; + } + + if (adu == NULL) { + PR_ERROR("ADU NOT FOUND\n"); + err=3; + goto out; + } + + addr = get_real_addr(addr); + if (addr == -1UL) { + PR_ERROR("TODO: No virtual address support for putmem\n"); + err = 1; + goto out; + } + + + if (len == 4 && stack[2] == 0x0810827d) { + /* According to linux-ppc-low.c gdb only uses this + * op-code for sw break points so we replace it with + * the correct attn opcode which is what we need for + * breakpoints. + * + * TODO: Upstream a patch to gdb so that it uses the + * right opcode for baremetal debug. */ + PR_INFO("Breakpoint opcode detected, replacing with attn\n"); + data = attn_opcode; + + /* Need to enable the attn instruction in HID0 */ + if (thread->enable_attn(thread_target)) + goto out; + } else + stack[2] = __builtin_bswap64(stack[2]) >> 32; + + PR_INFO("put_mem 0x%016" PRIx64 " = 0x%016" PRIx64 "\n", addr, stack[2]); + + if (adu_putmem(adu, addr, data, len)) { + PR_ERROR("Unable to write memory\n"); + err = 3; + } + +out: + if (err) + send_response(fd, ERROR(EPERM)); + else + send_response(fd, OK); +} + +static void v_conts(uint64_t *stack, void *priv) +{ + ram_step_thread(thread_target, 1); + send_response(fd, TRAP); +} + +#define VCONT_POLL_DELAY 100000 +static void v_contc(uint64_t *stack, void *priv) +{ + ram_start_thread(thread_target); + state = SIGNAL_WAIT; + poll_interval = 1; +} + +static void interrupt(uint64_t *stack, void *priv) +{ + PR_INFO("Interrupt\n"); + ram_stop_thread(thread_target); + send_response(fd, TRAP); + + return; +} + +static void poll(void) +{ + uint64_t nia; + struct thread_state status; + + thread_target->probe(thread_target); + status = thread_status(thread_target); + + switch (state) { + case IDLE: + break; + + case SIGNAL_WAIT: + if (!(status.quiesced)) + break; + + state = IDLE; + poll_interval = VCONT_POLL_DELAY; + if (!(status.active)) { + PR_ERROR("Thread inactive after trap\n"); + send_response(fd, ERROR(EPERM)); + return; + } + + /* Restore NIA */ + if (ram_getnia(thread_target, &nia)) + PR_ERROR("Error during getnia\n"); + if (ram_putnia(thread_target, nia - 4)) + PR_ERROR("Error during putnia\n"); + send_response(fd, TRAP); + break; + } +} + +static void cmd_default(uint64_t *stack, void *priv) +{ + uintptr_t tmp = stack[0]; + if (stack[0]) { + send_response(fd, (char *) tmp); + } else + send_response(fd, ""); +} + +static void create_client(int new_fd) +{ + PR_INFO("Client connected\n"); + fd = new_fd; +} + +static void destroy_client(int dead_fd) +{ + PR_INFO("Client disconnected\n"); + close(dead_fd); + fd = -1; +} + +static int read_from_client(int fd) +{ + char buffer[BUFFER_SIZE + 1]; + int nbytes; + + nbytes = read(fd, buffer, sizeof(buffer)); + if (nbytes < 0) { + perror(__FUNCTION__); + return -1; + } else if (nbytes == 0) { + PR_INFO("0 bytes\n"); + return -1; + } else { + buffer[nbytes] = '\0'; + PR_INFO("%x\n", buffer[0]); + PR_INFO("Recv: %s\n", buffer); + parse_buffer(buffer, nbytes, &fd); + } + + return 0; +} + +command_cb callbacks[LAST_CMD + 1] = { + cmd_default, + get_gprs, + get_spr, + get_mem, + stop_reason, + set_thread, + v_contc, + v_conts, + put_mem, + interrupt, + disconnect, + NULL}; + +int gdbserver_start(struct pdbg_target *target, uint16_t port) +{ + int sock, i; + struct sockaddr_in name; + fd_set active_fd_set, read_fd_set; + + parser_init(callbacks); + thread_target = target; + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock < 0) { + perror(__FUNCTION__); + return -1; + } + + name.sin_family = AF_INET; + name.sin_port = htons(port); + name.sin_addr.s_addr = htonl(INADDR_ANY); + if (bind(sock, (struct sockaddr *) &name, sizeof(name)) < 0) { + perror(__FUNCTION__); + return -1; + } + + if (listen(sock, 1) < 0) { + perror(__FUNCTION__); + return -1; + } + + FD_ZERO(&active_fd_set); + FD_SET(sock, &active_fd_set); + + while (1) { + read_fd_set = active_fd_set; + timeout.tv_sec = 0; + timeout.tv_usec = poll_interval; + if (select(FD_SETSIZE, &read_fd_set, NULL, NULL, &timeout) < 0) { + perror(__FUNCTION__); + return -1; + } + + for (i = 0; i < FD_SETSIZE; i++) { + if (FD_ISSET(i, &read_fd_set)) { + if (i == sock) { + int new; + new = accept(sock, NULL, NULL); + if (new < 0) { + perror(__FUNCTION__); + return -1; + } + + if (fd > 0) + /* It only makes sense to accept a single client */ + close(new); + else { + create_client(new); + FD_SET(new, &active_fd_set); + } + } else { + if (read_from_client(i) < 0) { + destroy_client(i); + FD_CLR(i, &active_fd_set); + } + } + } + } + + poll(); + } + + return 1; +} + +static int gdbserver(uint16_t port) +{ + struct pdbg_target *target = NULL; + uint64_t msr; + int rc; + + for_each_class_target("thread", target) { + if (!target_selected(target)) + continue; + if (pdbg_target_probe(target) == PDBG_TARGET_ENABLED) + break; + } + if (!target->class) + return -1; + assert(!strcmp(target->class, "thread")); + // Temporary until I can get this working a bit smoother on p9 + if (strcmp(target->compatible, "ibm,power8-thread")) { + PR_ERROR("GDBSERVER is only tested on POWER8\n"); + return -1; + } + + /* Check endianess in MSR */ + rc = ram_getmsr(target, &msr); + if (rc) { + PR_ERROR("Couldn't read the MSR. Are all threads on this chiplet quiesced?\n"); + return 1; + } + littleendian = 0x01 & msr; + + gdbserver_start(target, port); + return 0; +} +OPTCMD_DEFINE_CMD_WITH_ARGS(gdbserver, gdbserver, (DATA16)); diff --git a/src/pdbgproxy.h b/src/pdbgproxy.h new file mode 100644 index 0000000..1fe67f2 --- /dev/null +++ b/src/pdbgproxy.h @@ -0,0 +1,13 @@ +#ifndef __PDBGPROXY_H +#define __PDBGPROXY_H + +enum gdb_command {NONE, GET_GPRS, GET_SPR, GET_MEM, + STOP_REASON, SET_THREAD, V_CONTC, V_CONTS, + PUT_MEM, INTERRUPT, DISCONNECT, LAST_CMD}; +typedef void (*command_cb)(uint64_t *stack, void *priv); + +void parser_init(command_cb *callbacks); +int parse_buffer(char *buf, size_t len, void *priv); +void send_nack(void *priv); +void send_ack(void *priv); +#endif