commit c58c8faac3679113fe5f1ec15439136193d8a0c4 Author: empathicqubit Date: Sat Dec 31 22:09:30 2022 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0fe8558 --- /dev/null +++ b/Makefile @@ -0,0 +1,53 @@ +CFLAGS?=-O3 --opt-code-speed +PLATFORM?=ti8x + +CC=$(shell which zcc z88dk.zcc | head -1) +LD=$(CC) +SHELL=bash + +BUILD=build + +wilder_card=$(wildcard $(1)/**/$(2)) $(wildcard $(1)/$(2)) +define source_directory +$(eval $(1)=$(2)) +$(eval $(1)_FILES=$(call wilder_card,$(2),*.s) $(call wilder_card,$(2),*.c)) +$(eval $(1)_OBJECT_FILES=$$(subst $(2)/,$$(BUILD)/$(2)/,$$(patsubst %.s,%.o,$$(patsubst %.c,%.o,$$($(1)_FILES))))) +$(eval vpath %.c $(2)) +$(eval vpath %.s $(2)) +endef + +$(call source_directory,SRC,src) + +# It's extremely important that the source file paths are absolute, otherwise the +# extension will have trouble mapping the paths. +define source_compile + mkdir -p "$(dir $@)" + $(CC) +$(PLATFORM) $(CFLAGS) $(1) -o "$@" "$(realpath $<)" +endef + +.ONESHELL: + +all: $(BUILD)/gdb.lib + +clean: + rm -rf build + +$(BUILD): + @mkdir -p "$@" + +$(BUILD)/gdb.lib: $(BUILD)/gdb.lst $(SRC_OBJECT_FILES) + cd $(BUILD) && z88dk.z88dk-z80asm -d -xgdb "@$(subst $(BUILD)/,,$<)" + +$(BUILD)/gdb.lst: | $(BUILD) + echo > "$@" + for each in $(SRC_FILES) ; do + echo $${each%.*} >> "$@" + done + +.PRECIOUS: $(BUILD)/%.o +$(BUILD)/%.o: %.s | $(BUILD) + $(call source_compile,-c) + +.PRECIOUS: $(BUILD)/%.o +$(BUILD)/%.o: %.c | $(BUILD) + $(call source_compile,-c) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6a5d40e --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# z88dk-gdbstub + +A GDB stub library for z88dk z80 projects. You must implement the following +functions in your program for it to compile: + +* unsigned char gdb_getDebugChar (void) +* void gdb_putDebugChar (unsigned char ch) + +Other functions will also be needed to do anything useful. + +See [The template project for TI 8x calculators](https://github.com/empathicqubit/z88dk-ti8xp-template) for an example implementation. diff --git a/src/gdb.c b/src/gdb.c new file mode 100644 index 0000000..13f6fdc --- /dev/null +++ b/src/gdb.c @@ -0,0 +1,142 @@ +#include "lib.h" +#include "gdb.h" + +// All publicly exported functions go here + +void gdb_exception (int ex) __naked { + __asm + ld (__gdb_state+R_SP), sp + LOAD_SP + call __gdb_save_cpu_state + ld hl, 0 + push hl +#ifdef __SDCC_gbz80 + ld hl, __gdb_state + R_SP + ld a, (hl+) + ld h, (hl) + ld l, a +#else + ld hl, (__gdb_state + R_SP) +#endif + inc hl + inc hl + ld e, (hl) + inc hl + ld d, (hl) + push de + call __gdb_stub_main + __endasm; + (void)ex; +} + +#ifdef DBG_HWBREAK +#ifndef DBG_HWBREAK_SIZE +#define DBG_HWBREAK_SIZE 0 +#endif /* DBG_HWBREAK_SIZE */ +void +gdb_hwbreak (void) __naked +{ + __asm + ld (__gdb_state + R_SP), sp + LOAD_SP + call __gdb_save_cpu_state + ld hl, -DBG_HWBREAK_SIZE + push hl + ld hl, EX_HWBREAK + push hl + call __gdb_stub_main + __endasm; +} +#endif /* DBG_HWBREAK_SET */ + +void gdb_int(void) __naked { + __asm + ld (__gdb_state + R_SP), sp + LOAD_SP + call __gdb_save_cpu_state + ld hl, 0 ;pc_adj + push hl + ld hl, DBG_INT_EX + push hl + ld hl, __gdb_stub_main + push hl + push hl + ei + reti + __endasm; +} + +#ifndef __SDCC_gbz80 +void gdb_nmi(void) __naked { + __asm + ld (__gdb_state + R_SP), sp + LOAD_SP + call __gdb_save_cpu_state + ld hl, 0 ;pc_adj + push hl + ld hl, DBG_NMI_EX + push hl + ld hl, __gdb_stub_main + push hl + push hl + retn + __endasm; +} +#endif + +#ifdef DBG_SWBREAK +#ifdef DBG_SWBREAK_RST +#define DBG_SWBREAK_SIZE 1 +#else +#define DBG_SWBREAK_SIZE 3 +#endif + +void gdb_swbreak (void) __naked { + __asm + ld (__gdb_state + R_SP), sp + LOAD_SP + call __gdb_save_cpu_state + ld hl, -DBG_SWBREAK_SIZE + push hl + ld hl, EX_SWBREAK + push hl + call __gdb_stub_main + ;.globl _break_handler +#ifdef DBG_SWBREAK_RST +_break_handler = DBG_SWBREAK_RST +#else +_break_handler = _gdb_swbreak +#endif + __endasm; +} +#endif /* DBG_SWBREAK */ + +void gdb_set_enter(void (*func)(void)) { + _gdb_enter_func = func; +} + +void gdb_set_hwbreak_toggle(int (*func)(int set, void *addr)) { + _gdb_toggle_hwbreak = func; +} + +#ifdef DBG_TOGGLESTEP +void gdb_set_step_toggle(int (*func)(int set)) { + _gdb_toggle_step = func; +} +#endif + +#ifdef DGB_SWBREAK +void gdb_set_swbreak_toggle(int (*func)(int set, void *addr)) { + _gdb_toggle_swbreak = func; +} +#endif + +// Set calls to here in the holes, then remove them when we +// reenter +#if defined(DBG_TOGGLESTEP) && defined(DBG_SWBREAK) +void gdb_step (void) __naked { + __asm + jp _gdb_swbreak + __endasm +} +#endif diff --git a/src/gdb.h b/src/gdb.h new file mode 100644 index 0000000..521140a --- /dev/null +++ b/src/gdb.h @@ -0,0 +1,68 @@ +#ifndef __GDB_GDB_H__ +#define __GDB_GDB_H__ + +#include "lib.h" + +#define export(returntype, signature) { \ + returntype signature; \ + extern returntype __LIB__ signature; \ +} + +/* These functions must be defined by the application */ +unsigned char gdb_getDebugChar(void); +void gdb_putDebugChar(unsigned char ch); + +/* This file contains the library exports. */ + +/* Enter to debug mode from software or hardware breakpoint. + Assume address of next instruction after breakpoint call is on top of stack. + Do JP _gdb_swbreak or JP _gdb_hwbreak from RST handler, for example. + */ +export(void, gdb_swbreak(void) __naked); +export(void, gdb_hwbreak(void) __naked); +export(void, gdb_step(void) __naked); +export(void, gdb_step (void)); +export(void, gdb_exception (int ex) __naked); + +/* Jump to this function from NMI handler. Just replace RETN instruction by + JP _gdb_nmi + Use if NMI detects request to enter to debug mode. + */ +export(void, gdb_nmi (void) __naked); + +/* Jump to this function from INT handler. Just replace EI+RETI instructions by + JP _gdb_int + Use if INT detects request to enter to debug mode. + */ +export(void, gdb_int (void) __naked); + +/* Prints to debugger console. */ +export(void, gdb_print(const char *str)); + +/* Set the function which gets a packet character. This is required. */ +export(void, gdb_set_get_char(unsigned char (*getter)(void))); + +/* Set the function which puts a packet character. This is required. */ +export(void, gdb_set_put_char(void (*putter)(unsigned char))); + +/* Set the function which turns a software break in a particular location on or off */ +export(void, gdb_set_swbreak_toggle(int (*func)(int set, void *addr))); + +/* Set the function which turns a hardware break in a particular location on or off */ +export(void, gdb_set_hwbreak_toggle(int (*func)(int set, void *addr))); + +/* Set the function which turns line stepping on or off */ +export(void, gdb_set_step_toggle(int (*func)(int set))); + +/* Set the function which is called when the debugger is entered */ +export(void, gdb_set_enter(void (*func)(void))); + +/* Enter to debug mode (after receiving BREAK from GDB, for example) + * Assume: + * program PC in (SP+0) + * caught signal in (SP+2) + * program SP is SP+4 + */ +export(void, gdb_exception (int ex)); + +#endif // __GDB_GDB_H__ \ No newline at end of file diff --git a/src/lib.c b/src/lib.c new file mode 100644 index 0000000..5099101 --- /dev/null +++ b/src/lib.c @@ -0,0 +1,1011 @@ +/* This file contains stuff internal to the library */ + +/* Debug stub for Z80. + + Copyright (C) 2021-2022 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 3 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, see . */ + +#include "debug.h" +#include "lib.h" +#include "gdb.h" + +#ifdef DBG_SWBREAK +#define DO_EXPAND(VAL) VAL ## 123456 +#define EXPAND(VAL) DO_EXPAND(VAL) + +#if EXPAND(DBG_SWBREAK) != 123456 +#define DBG_SWBREAK_PROC DBG_SWBREAK +int (*_gdb_toggle_swbreak)(int set, void *addr) = NULL; +#endif + +#undef EXPAND +#undef DO_EXPAND +#endif /* DBG_SWBREAK */ + +#ifdef DBG_HWBREAK +int (*_gdb_toggle_hwbreak)(int set, void *addr) = NULL; +#endif + +#ifdef DBG_TOGGLESTEP +int (*_gdb_toggle_step)(int set) = NULL; +#endif + +#ifdef DBG_MEMCPY +extern void* DBG_MEMCPY (void *dest, const void *src, unsigned n); +#endif + +#ifdef DBG_WWATCH +extern int DBG_WWATCH(int set, void *addr, unsigned size); +#endif + +#ifdef DBG_RWATCH +extern int DBG_RWATCH(int set, void *addr, unsigned size); +#endif + +#ifdef DBG_AWATCH +extern int DBG_AWATCH(int set, void *addr, unsigned size); +#endif + +#include + +byte _gdb_state[NUMREGBYTES]; + +#if DBG_PACKET_SIZE < (NUMREGBYTES*2+5) +#error "Too small DBG_PACKET_SIZE" +#endif + +#ifndef FASTCALL +#define FASTCALL __z88dk_fastcall +#endif + +#ifndef DBG_ENTER +#define DBG_ENTER +#else +void (*_gdb_enter_func)(void) = NULL; +#endif + +#ifndef DBG_RESUME +#define DBG_RESUME ret +#endif + +static signed char sigval; +static unsigned char first_entry = 0; + +static char put_packet_info (const char *buffer) FASTCALL; + +/************** UTILITY FUNCTIONS ********************/ +static char low_hex (byte v) FASTCALL { + v &= 0x0f; + v += '0'; + if (v < '9'+1) + return v; + return v + 'a' - '0' - 10; +} + +static char high_hex (byte v) FASTCALL { + return low_hex(v >> 4); +} + +static char * byte2hex (char *p, byte v) { + *p++ = high_hex (v); + *p++ = low_hex (v); + return p; +} + +/* convert the memory, pointed to by mem into hex, placing result in buf */ +/* return a pointer to the last char put in buf (null) */ +static char * mem2hex (char *buf, const byte *mem, unsigned bytes) { + char *d = buf; + if (bytes != 0) + { + do + { + d = byte2hex (d, *mem++); + } + while (--bytes); + } + *d = 0; + return d; +} + +static signed char hex2val (unsigned char hex) FASTCALL { + if (hex <= '9') + return hex - '0'; + hex &= 0xdf; /* make uppercase */ + hex -= 'A' - 10; + return (hex >= 10 && hex < 16) ? hex : -1; +} + +static int hex2byte (const char *p) FASTCALL { + signed char h = hex2val (p[0]); + signed char l = hex2val (p[1]); + if (h < 0 || l < 0) + return -1; + return (byte)((byte)h << 4) | (byte)l; +} + +/* convert the hex array pointed to by buf into binary, to be placed in mem + return a pointer to the character after the last byte written */ +static char *hex2mem (byte *mem, char *buf, unsigned bytes) { + if (bytes != 0) + { + do + { + *mem++ = hex2byte (buf); + buf += 2; + } + while (--bytes); + } + return buf; +} + +static int hex2int (const char **buf) FASTCALL { + word r = 0; + while(1) + { + signed char a = hex2val(**buf); + if (a < 0) + break; + r <<= 4; + r += (byte)a; + (*buf)++; + } + return (int)r; +} + +static char * int2hex (char *buf, int v) { + buf = byte2hex(buf, (word)v >> 8); + return byte2hex(buf, (byte)v); +} +/******************************************************************************/ +static void store_pc_sp (int pc_adj) FASTCALL; +#define get_reg_value(mem) (*(void* const*)(mem)) +#define set_reg_value(mem,val) do { (*(void**)(mem) = (val)); } while (0) +static void get_packet (char *buffer); +static void put_packet (const char *buffer); +static char process (char *buffer) FASTCALL; + +#ifdef DBG_PRINT +void gdb_print(const char *str) { + gdb_putDebugChar('$'); + gdb_putDebugChar('O'); + char csum = 'O'; + for (; *str != '\0'; ) { + char c = high_hex (*str); + csum += c; + gdb_putDebugChar (c); + c = low_hex (*str++); + csum += c; + gdb_putDebugChar (c); + } + gdb_putDebugChar('#'); + gdb_putDebugChar(high_hex (csum)); + gdb_putDebugChar(low_hex (csum)); +} +#endif /* DBG_PRINT */ + +void _gdb_stub_main (int ex, int pc_adj) { + char buffer[DBG_PACKET_SIZE+1]; + sigval = (signed char)ex; + store_pc_sp (pc_adj); + + DBG_ENTER + + if(!gdb_getDebugChar || !gdb_putDebugChar) { + _gdb_rest_cpu_state(); + } + + if(!first_entry) { + // put some extra bytes on the line to fill the cable's buffer + first_entry = 1; + gdb_putDebugChar(0); + gdb_putDebugChar(0); + } + + #if defined(DBG_SWBREAK) && defined(DBG_TOGGLESTEP) + if(DBG_TOGGLESTEP) { + DBG_TOGGLESTEP(0); + } + #endif + + /* after starting gdb_stub must always return stop reason */ + *buffer = '?'; + for (; process (buffer);) + { + put_packet (buffer); + get_packet (buffer); + } + put_packet (buffer); + _gdb_rest_cpu_state (); +} + +static void get_packet (char *buffer) { + byte csum; + char ch; + char *p; + byte esc; +#if DBG_PACKET_SIZE <= 256 + byte count; /* it is OK to use up to 256 here */ +#else + unsigned count; +#endif + for (;; gdb_putDebugChar ('-')) + { + /* wait for packet start character */ + while((ch = gdb_getDebugChar()) != '$'); +retry: + csum = 0; + esc = 0; + p = buffer; + count = DBG_PACKET_SIZE; + do + { + ch = gdb_getDebugChar(); + switch (ch) + { + case '$': + goto retry; + case '#': + goto finish; + case '}': + esc = 0x20; + break; + default: + *p++ = ch ^ esc; + esc = 0; + --count; + } + csum += ch; + } + while (count != 0); +finish: + *p = '\0'; + if (ch != '#') /* packet is too large */ + continue; + ch = gdb_getDebugChar(); + if (ch != high_hex (csum)) + continue; + ch = gdb_getDebugChar(); + if (ch != low_hex (csum)) + continue; + break; + } + gdb_putDebugChar('+'); +} + +static void put_packet (const char *buffer) { + /* $#. */ + for (;;) + { + gdb_putDebugChar('$'); + char checksum = put_packet_info (buffer); + gdb_putDebugChar('#'); + gdb_putDebugChar(high_hex(checksum)); + gdb_putDebugChar(low_hex(checksum)); + for (;;) + { + char c = gdb_getDebugChar (); + switch (c) + { + case '+': return; + case '-': break; + default: + //gdb_putDebugChar(c); + continue; + } + break; + } + } +} + +static char put_packet_info (const char *src) FASTCALL { + char ch; + char checksum = 0; + for (;;) + { + ch = *src++; + if (ch == '\0') + break; + if (ch == '}' || ch == '*' || ch == '#' || ch == '$') + { + /* escape special characters */ + gdb_putDebugChar('}'); + checksum += '}'; + ch ^= 0x20; + } + gdb_putDebugChar(ch); + checksum += ch; + } + return checksum; +} + +static void store_pc_sp (int pc_adj) FASTCALL { + byte *sp = get_reg_value (&_gdb_state[R_SP]); + byte *pc = get_reg_value (sp); + pc += pc_adj; + set_reg_value (&_gdb_state[R_PC], pc); + set_reg_value (&_gdb_state[R_SP], sp + REG_SIZE); +} + +static char *hex2mem (byte *mem, char *buf, unsigned bytes); + +/* Command processors. Takes pointer to buffer (begins from command symbol), + modifies buffer, returns: -1 - empty response (ignore), 0 - success, + positive: error code. */ + +#ifdef DBG_MIN_SIZE +static signed char process_question (char *p) FASTCALL { + signed char sig; + *p++ = 'S'; + sig = sigval; + if (sig <= 0) + sig = EX_SIGTRAP; + p = byte2hex (p, (byte)sig); + *p = '\0'; + return 0; +} +#else /* DBG_MIN_SIZE */ +static char *format_reg_value (char *p, unsigned reg_num, const byte *value); + +static signed char process_question (char *p) FASTCALL { + signed char sig; + *p++ = 'T'; + sig = sigval; + if (sig <= 0) + sig = EX_SIGTRAP; + p = byte2hex (p, (byte)sig); + p = format_reg_value(p, R_AF/REG_SIZE, &_gdb_state[R_AF]); + p = format_reg_value(p, R_SP/REG_SIZE, &_gdb_state[R_SP]); + p = format_reg_value(p, R_PC/REG_SIZE, &_gdb_state[R_PC]); +#if defined(DBG_SWBREAK_PROC) || defined(DBG_HWBREAK) || defined(DBG_WWATCH) || defined(DBG_RWATCH) || defined(DBG_AWATCH) + const char *reason; + unsigned addr = 0; + switch (sigval) + { +#ifdef DBG_SWBREAK_PROC + case EX_SWBREAK: + reason = "swbreak"; + break; +#endif +#ifdef DBG_HWBREAK + case EX_HWBREAK: + reason = "hwbreak"; + break; +#endif +#ifdef DBG_WWATCH + case EX_WWATCH: + reason = "watch"; + addr = 1; + break; +#endif +#ifdef DBG_RWATCH + case EX_RWATCH: + reason = "rwatch"; + addr = 1; + break; +#endif +#ifdef DBG_AWATCH + case EX_AWATCH: + reason = "awatch"; + addr = 1; + break; +#endif + default: + goto finish; + } + while ((*p++ = *reason++)) + ; + --p; + *p++ = ':'; + if (addr != 0) + p = int2hex(p, addr); + *p++ = ';'; +finish: +#endif /* DBG_HWBREAK, DBG_WWATCH, DBG_RWATCH, DBG_AWATCH */ + *p++ = '\0'; + return 0; +} +#endif /* DBG_MINSIZE */ + +#define STRING2(x) #x +#define STRING1(x) STRING2(x) +#define STRING(x) STRING1(x) +#if defined(DBG_MEMORY_MAP) || defined(DBG_FEATURE_STR) +static void read_xml_document (char *buffer, unsigned offset, unsigned length, const char *doc); +#endif + +static signed char process_q (char *buffer) FASTCALL { + char *p; + if (memcmp (buffer + 1, "Supported", 9) == 0) { + memcpy (buffer, "PacketSize=", 11); + p = int2hex (&buffer[11], DBG_PACKET_SIZE); +#ifndef DBG_MIN_SIZE +#ifdef DBG_SWBREAK_PROC + if(DBG_SWBREAK_PROC) { + memcpy (p, ";swbreak+", 9); + p += 9; + } +#endif +#ifdef DBG_HWBREAK + if(DBG_HWBREAK) { + memcpy (p, ";hwbreak+", 9); + p += 9; + } +#endif +#endif /* DBG_MIN_SIZE */ + +#ifdef DBG_MEMORY_MAP + memcpy (p, ";qXfer:memory-map:read+", 23); + p += 23; +#endif +#ifdef DBG_FEATURE_STR + memcpy (p, ";qXfer:features:read+", 21); + p += 21; +#endif + *p = '\0'; + return 0; + } +#ifdef DBG_FEATURE_STR + if (memcmp (buffer + 1, "Xfer:features:read:", 19) == 0) { + p = strchr (buffer + 1 + 19, ':'); + if (p == NULL) { + return 1; + } + ++p; + unsigned offset = hex2int (&p); + if (*p++ != ',') { + return 2; + } + unsigned length = hex2int (&p); + if (length == 0) { + return 3; + } + if (length > strlen(DBG_FEATURE_STR)) { + length = strlen(DBG_FEATURE_STR); + } + if (length > DBG_PACKET_SIZE) { + return 4; + } + read_xml_document (buffer, offset, length, DBG_FEATURE_STR); + return 0; + } +#endif +#ifdef DBG_MEMORY_MAP + if (memcmp (buffer + 1, "Xfer:memory-map:read:", 21) == 0) { + p = strchr (buffer + 1 + 21, ':'); + if (p == NULL) + return 1; + ++p; + unsigned offset = hex2int (&p); + if (*p++ != ',') + return 2; + unsigned length = hex2int (&p); + if (length == 0) + return 3; + if (length > DBG_PACKET_SIZE) + return 4; + read_xml_document (buffer, offset, length, DBG_MEMORY_MAP); + return 0; + } +#endif +#ifndef DBG_MIN_SIZE + if (memcmp (&buffer[1], "Attached", 9) == 0) { + /* Just report that GDB attached to existing process + if it is not applicable for you, then send patches */ + memcpy(buffer, "1", 2); + return 0; + } +#endif /* DBG_MIN_SIZE */ + *buffer = '\0'; + return -1; +} + +static signed char process_g (char *buffer) FASTCALL { + mem2hex (buffer, _gdb_state, NUMREGBYTES); + return 0; +} + +static signed char process_G (char *buffer) FASTCALL { + hex2mem (_gdb_state, &buffer[1], NUMREGBYTES); + /* OK response */ + *buffer = '\0'; + return 0; +} + +static signed char process_m (char *buffer) FASTCALL { + /* mAA..AA,LLLL Read LLLL bytes at address AA..AA */ + char *p = &buffer[1]; + byte *addr = (void*)hex2int(&p); + if (*p++ != ',') + return 1; + unsigned len = (unsigned)hex2int(&p); + if (len == 0) + return 2; + if (len > DBG_PACKET_SIZE/2) + return 3; + p = buffer; +#ifdef DBG_MEMCPY + do { + byte tmp[16]; + unsigned tlen = sizeof(tmp); + if (tlen > len) + tlen = len; + if (!DBG_MEMCPY(tmp, addr, tlen)) + return 4; + p = mem2hex (p, tmp, tlen); + addr += tlen; + len -= tlen; + } + while (len); +#else + p = mem2hex (p, addr, len); +#endif + return 0; +} + +static signed char process_M (char *buffer) FASTCALL { + /* MAA..AA,LLLL: Write LLLL bytes at address AA.AA return OK */ + char *p = &buffer[1]; + byte *addr = (void*)hex2int(&p); + if (*p != ',') + return 1; + ++p; + unsigned len = (unsigned)hex2int(&p); + if (*p++ != ':') + return 2; + if (len == 0) + goto end; + if (len*2 + (p - buffer) > DBG_PACKET_SIZE) + return 3; +#ifdef DBG_MEMCPY + do + { + byte tmp[16]; + unsigned tlen = sizeof(tmp); + if (tlen > len) + tlen = len; + p = hex2mem (tmp, p, tlen); + if (!DBG_MEMCPY(addr, tmp, tlen)) + return 4; + addr += tlen; + len -= tlen; + } + while (len); +#else + hex2mem (addr, p, len); +#endif +end: + /* OK response */ + *buffer = '\0'; + return 0; +} + +#ifndef DBG_MIN_SIZE +static signed char process_X (char *buffer) FASTCALL { + /* XAA..AA,LLLL: Write LLLL binary bytes at address AA.AA return OK */ + char *p = &buffer[1]; + byte *addr = (void*)hex2int(&p); + if (*p != ',') + return 1; + ++p; + unsigned len = (unsigned)hex2int(&p); + if (*p++ != ':') + return 2; + if (len == 0) + goto end; + if (len + (p - buffer) > DBG_PACKET_SIZE) + return 3; +#ifdef DBG_MEMCPY + if (!DBG_MEMCPY(addr, p, len)) + return 4; +#else + memcpy (addr, p, len); +#endif +end: + /* OK response */ + *buffer = '\0'; + return 0; +} +#else /* DBG_MIN_SIZE */ +static signed char +process_X (char *buffer) FASTCALL +{ + (void)buffer; + return -1; +} +#endif /* DBG_MIN_SIZE */ + +static signed char process_c (char *buffer) FASTCALL { + /* 'cAAAA' - Continue at address AAAA(optional) */ + const char *p = &buffer[1]; + if (*p != '\0') + { + void *addr = (void*)hex2int(&p); + set_reg_value (&_gdb_state[R_PC], addr); + } + _gdb_rest_cpu_state (); + return 0; +} + +static signed char process_s (char *buffer) FASTCALL { + /* 'sAAAA' - Step at address AAAA(optional) */ + #if defined(DBG_TOGGLESTEP) && defined(DBG_SWBREAK) + if(!DBG_TOGGLESTEP) { + return -1; + } + int err = DBG_TOGGLESTEP(1); + if(err) { + return err; + } + const char *p = &buffer[1]; + if (*p != '\0') + { + void *addr = (void*)hex2int(&p); + set_reg_value (&_gdb_state[R_PC], addr); + } + _gdb_rest_cpu_state (); + return 0; + #else + return -1; + #endif +} + +static signed char process_D (char *buffer) FASTCALL { + /* 'D' - detach the program: continue execution */ + if(!DBG_SWBREAK_PROC) { + return -1; + } + + DBG_SWBREAK_PROC(0, NULL); + _gdb_rest_cpu_state (); + return 0; +} + +static signed char +process_k (char *buffer) FASTCALL { + /* 'k' - Kill the program */ + set_reg_value (&_gdb_state[R_PC], 0); + _gdb_rest_cpu_state (); + (void)buffer; + return 0; +} + +static signed char process_v (char *buffer) FASTCALL { +#ifndef DBG_MIN_SIZE + if (memcmp (&buffer[1], "Cont", 4) == 0) + { + if (buffer[5] == '?') + { + /* result response will be "vCont;c;C"; C action must be + supported too, because GDB reguires at lease both of them */ + memcpy (&buffer[5], ";c;C", 5); + return 0; + } + buffer[0] = '\0'; + if (buffer[5] == ';' && (buffer[6] == 'c' || buffer[6] == 'C')) + return -2; /* resume execution */ + return 1; + } +#endif /* DBG_MIN_SIZE */ + return -1; +} + +static signed char process_zZ (char *buffer) FASTCALL { + /* insert/remove breakpoint */ +#if defined(DBG_SWBREAK_PROC) || defined(DBG_HWBREAK) || \ + defined(DBG_WWATCH) || defined(DBG_RWATCH) || defined(DBG_AWATCH) + const byte set = (*buffer == 'Z'); + const char *p = &buffer[3]; + void *addr = (void*)hex2int(&p); + if (*p != ',') + return 1; + p++; + int kind = hex2int(&p); + *buffer = '\0'; + switch (buffer[1]) { +#ifdef DBG_SWBREAK_PROC + case '0': /* sw break */ + if(!DBG_SWBREAK_PROC) { + return -1; + } + return DBG_SWBREAK_PROC(set, addr); +#endif +#ifdef DBG_HWBREAK + case '1': /* hw break */ + if(!DBG_HWBREAK) { + return -1; + } + return DBG_HWBREAK(set, addr); +#endif +#ifdef DBG_WWATCH + case '2': /* write watch */ + return DBG_WWATCH(set, addr, kind); +#endif +#ifdef DBG_RWATCH + case '3': /* read watch */ + return DBG_RWATCH(set, addr, kind); +#endif +#ifdef DBG_AWATCH + case '4': /* access watch */ + return DBG_AWATCH(set, addr, kind); +#endif + default:; /* not supported */ + } +#endif + (void)buffer; + return -1; +} + +static signed char do_process (char *buffer) FASTCALL { + switch (*buffer) { + case 's': return process_s(buffer); + case '?': return process_question (buffer); + case 'G': return process_G (buffer); + case 'k': return process_k (buffer); + case 'M': return process_M (buffer); + case 'X': return process_X (buffer); + case 'Z': return process_zZ (buffer); + case 'c': return process_c (buffer); + case 'D': return process_D (buffer); + case 'g': return process_g (buffer); + case 'm': return process_m (buffer); + case 'q': return process_q (buffer); + case 'v': return process_v (buffer); + case 'z': return process_zZ (buffer); + default: return -1; /* empty response */ + } + + return -1; +} + +static char process (char *buffer) FASTCALL { + signed char err = do_process (buffer); + char *p = buffer; + char ret = 1; + if (err == -2) + { + ret = 0; + err = 0; + } + if (err > 0) + { + *p++ = 'E'; + p = byte2hex (p, err); + *p = '\0'; + } + else if (err < 0) + { + *p = '\0'; + } + else if (*p == '\0') + memcpy(p, "OK", 3); + return ret; +} + +#if defined(DBG_MEMORY_MAP) || defined(DBG_FEATURE_STR) +static void read_xml_document (char *buffer, unsigned offset, unsigned length, const char *doc) { + const unsigned doc_sz = strlen(doc); + if (offset >= doc_sz) { + buffer[0] = 'l'; + buffer[1] = '\0'; + return; + } + if (offset + length >= doc_sz) { + length = doc_sz - offset; + buffer[0] = 'l'; + } + else { + buffer[0] = 'm'; + } + memcpy (&buffer[1], &doc[offset], length); + buffer[1+length] = '\0'; +} +#endif + +/* write string like " nn:0123" and return pointer after it */ +#ifndef DBG_MIN_SIZE +static char * format_reg_value (char *p, unsigned reg_num, const byte *value) { + char *d = p; + unsigned char i; + d = byte2hex(d, reg_num); + *d++ = ':'; + value += REG_SIZE; + i = REG_SIZE; + do { + d = byte2hex(d, *--value); + } + while (--i != 0); + *d++ = ';'; + return d; +} +#endif /* DBG_MIN_SIZE */ + +#ifdef __SDCC_gbz80 +/* saves all _gdb_state.except PC and SP */ +void _gdb_save_cpu_state() __naked { + __asm + push af + ld a, l + ld (__gdb_state + R_HL + 0), a + ld a, h + ld (__gdb_state + R_HL + 1), a + ld hl, __gdb_state + R_HL - 1 + ld (hl), d + dec hl + ld (hl), e + dec hl + ld (hl), b + dec hl + ld (hl), c + dec hl + pop bc + ld (hl), b + dec hl + ld (hl), c + ret + __endasm; +} + +/* restore CPU _gdb_state and continue execution */ +void _gdb_rest_cpu_state() __naked { + __asm +;restore SP + ld a, (__gdb_state + R_SP + 0) + ld l,a + ld a, (__gdb_state + R_SP + 1) + ld h,a + ld sp, hl +;push PC value as return address + ld a, (__gdb_state + R_PC + 0) + ld l, a + ld a, (__gdb_state + R_PC + 1) + ld h, a + push hl +;restore registers + ld hl, __gdb_state + R_AF + ld c, (hl) + inc hl + ld b, (hl) + inc hl + push bc + ld c, (hl) + inc hl + ld b, (hl) + inc hl + ld e, (hl) + inc hl + ld d, (hl) + inc hl + ld a, (hl) + inc hl + ld h, (hl) + ld l, a + pop af + ret + __endasm; +} +#else +/* saves all _gdb_state.except PC and SP */ +void _gdb_save_cpu_state() __naked { + __asm + ld (__gdb_state + R_HL), hl + ld (__gdb_state + R_DE), de + ld (__gdb_state + R_BC), bc + push af + pop hl + ld (__gdb_state + R_AF), hl + ld a, r ;R is increased by 7 or by 8 if called via RST + ld l, a + sub a, 7 + xor a, l + and a, 0x7f + xor a, l +#ifdef __SDCC_ez80_adl + ld hl, i + ex de, hl + ld hl, __gdb_state + R_IR + ld (hl), a + inc hl + ld (hl), e + inc hl + ld (hl), d + ld a, MB + ld (__gdb_state + R_AF+2), a +#else + ld l, a + ld a, i + ld h, a + ld (__gdb_state + R_IR), hl +#endif /* __SDCC_ez80_adl */ + ld (__gdb_state + R_IX), ix + ld (__gdb_state + R_IY), iy + ex af, af' ;' + exx + ld (__gdb_state + R_HL_), hl + ld (__gdb_state + R_DE_), de + ld (__gdb_state + R_BC_), bc + push af + pop hl + ld (__gdb_state + R_AF_), hl + ret + __endasm; +} + +/* restore CPU _gdb_state and continue execution */ +void _gdb_rest_cpu_state() __naked { + __asm +#ifdef DBG_USE_TRAMPOLINE + ld sp, __gdb_stack + DBG_STACK_SIZE + ld hl, (__gdb_state + R_PC) + push hl /* resume address */ +#ifdef __SDCC_ez80_adl + ld hl, 0xc30000 ; use 0xc34000 for jp.s +#else + ld hl, 0xc300 +#endif + push hl /* JP opcode */ +#endif /* DBG_USE_TRAMPOLINE */ + ld hl, (__gdb_state + R_AF_) + push hl + pop af + ld bc, (__gdb_state + R_BC_) + ld de, (__gdb_state + R_DE_) + ld hl, (__gdb_state + R_HL_) + exx + ex af, af' ;' + ld iy, (__gdb_state + R_IY) + ld ix, (__gdb_state + R_IX) +#ifdef __SDCC_ez80_adl + ld a, (__gdb_state + R_AF + 2) + ld MB, a + ld hl, (__gdb_state + R_IR + 1) ;I register + ld i, hl + ld a, (__gdb_state + R_IR + 0) ; R register + ld l, a +#else + ld hl, (__gdb_state + R_IR) + ld a, h + ld i, a + ld a, l +#endif /* __SDCC_ez80_adl */ + sub a, 10 ;number of M1 cycles after ld r,a + xor a, l + and a, 0x7f + xor a, l + ld r, a + ld de, (__gdb_state + R_DE) + ld bc, (__gdb_state + R_BC) + ld hl, (__gdb_state + R_AF) + push hl + pop af + ld sp, (__gdb_state + R_SP) +#ifndef DBG_USE_TRAMPOLINE + ld hl, (__gdb_state + R_PC) + push hl + ld hl, (__gdb_state + R_HL) + DBG_RESUME +#else + ld hl, (__gdb_state + R_HL) +#ifdef __SDCC_ez80_adl + jp _gdb_stack + DBG_STACK_SIZE - 4 +#else + jp _gdb_stack + DBG_STACK_SIZE - 3 +#endif +#endif /* DBG_USE_TRAMPOLINE */ + __endasm; +} +#endif /* __SDCC_gbz80 */ \ No newline at end of file diff --git a/src/lib.h b/src/lib.h new file mode 100644 index 0000000..e76ab2c --- /dev/null +++ b/src/lib.h @@ -0,0 +1,254 @@ +#ifndef __GDB_LIB_H__ +#define __GDB_LIB_H__ + +typedef unsigned char byte; +typedef unsigned short word; + +/* This file contains stuff internal to the library */ + +/* Usage: + 1. Copy this file to project directory + 2. Configure it commenting/uncommenting macros below or define DBG_CONFIGURED + and all required macros and then include this file to one of your C-source + files. + 3. Implement gdb_getDebugChar() and gdb_putDebugChar(), functions must not return + until data received or sent. + 4. Implement all optional functions used to toggle breakpoints/watchpoints, + if supported. Do not write fuctions to toggle software breakpoints if + you unsure (GDB will do itself). + 5. Implement serial port initialization routine called at program start. + 6. Add necessary debugger entry points to your program, for example: + .org 0x08 ;RST 8 handler + jp _gdb_swbreak + ... + .org 0x66 ;NMI handler + jp _gdb_nmi + ... + main_loop: + halt + call isDbgInterrupt + jr z,101$ + ld hl, 2 ;EX_SIGINT + push hl + call _debug_exception + 101$: + ... + 7. Compile file using SDCC (supported ports are: z80, z180, z80n, gbz80 and + ez80_z80), do not use --peep-asm option. For example: + $ sdcc -mz80 --opt-code-size --max-allocs-per-node 50000 z80-stub.c +*/ +/******************************************************************************\ + Configuration +\******************************************************************************/ +#ifndef DBG_CONFIGURED +/* Uncomment this line, if stub size is critical for you */ +//#define DBG_MIN_SIZE + +/* Comment this line out if software breakpoints are unsupported. + If you have special function to toggle software breakpoints, then provide + here name of these function. Expected prototype: + int _gdb_toggle_swbreak(int set, void *addr); + function must return 0 on success. */ +#define DBG_SWBREAK _gdb_toggle_swbreak + +/* Define the function to disable and enable software stepping + int _gdb_toggle_step(int set); +*/ +#define DBG_TOGGLESTEP _gdb_toggle_step + +/* Define if one of standard RST handlers is used as software + breakpoint entry point */ +//#define DBG_SWBREAK_RST 0x08 + +/* if platform supports hardware breakpoints then define following macro + by name of function. Fuction must have next prototype: + int _gdb_toggle_hwbreak(int set, void *addr); + function must return 0 on success. */ +#define DBG_HWBREAK _gdb_toggle_hwbreak + +/* if platform supports hardware watchpoints then define all or some of + following macros by names of functions. Fuctions prototypes: + int toggle_watch(int set, void *addr, size_t size); // memory write watch + int toggle_rwatch(int set, void *addr, size_t size); // memory read watch + int toggle_awatch(int set, void *addr, size_t size); // memory access watch + function must return 0 on success. */ +//#define DBG_WWATCH toggle_watch +//#define DBG_RWATCH toggle_rwatch +//#define DBG_AWATCH toggle_awatch + +/* Size of hardware breakpoint. Required to correct PC. */ +#define DBG_HWBREAK_SIZE 0 + +/* Define following macro if you need custom memory read/write routine. + Function should return non-zero on success, and zero on failure + (for example, write to ROM area). + Useful with overlays (bank switching). + Do not forget to define: + _ovly_table - overlay table + _novlys - number of items in _ovly_table + or + _ovly_region_table - overlay regions table + _novly_regions - number of items in _ovly_region_table + + _ovly_debug_prepare - function is called before overlay mapping + _ovly_debug_event - function is called after overlay mapping + */ +//#define DBG_MEMCPY memcpy + +/* define dedicated stack size if required */ +//#define DBG_STACK_SIZE 256 + +/* max GDB packet size + should be much less that DBG_STACK_SIZE because it will be allocated on stack +*/ +#define DBG_PACKET_SIZE 800 + +/* Uncomment if required to use trampoline when resuming operation. + Useful with dedicated stack when stack pointer do not point to the stack or + stack is not writable */ +//#define DBG_USE_TRAMPOLINE + +/* Uncomment following macro to enable debug printing to debugger console */ +#define DBG_PRINT + +#define DBG_NMI_EX EX_HWBREAK +#define DBG_INT_EX EX_SIGINT + +/* Define following macro to statement, which will be exectuted after entering to + _gdb_stub_main function. Statement should include semicolon. */ +#define DBG_ENTER if(_gdb_enter_func) { _gdb_enter_func(); }; + +/* Define following macro to instruction(s), which will be execute before return + control to the program. It is useful when gdb-stub is placed in one of overlays. + This procedure must not change any register. On top of stack before invocation + will be return address of the program. */ +//#define DBG_RESUME jp _restore_bank + +/* Define following macro to the string containing memory map definition XML. + GDB will use it to select proper breakpoint type (HW or SW). */ +/*#define DBG_MEMORY_MAP "\ +\ + \ +\ + \ +\ +" +*/ + +/* Define following macro to the string containing feature definition XML. */ +#define DBG_FEATURE_STR ""\ +""\ +""\ +""\ +""\ +""\ +""\ +""\ +""\ +""\ +""\ +""\ +""\ +""\ +""\ +""\ +"z80"\ +"" + +#endif /* DBG_CONFIGURED */ +/******************************************************************************\ + Public Interface +\******************************************************************************/ + +#ifndef NULL +# define NULL ((void*)0) +#endif + +#define EX_SWBREAK 0 /* sw breakpoint */ +#define EX_HWBREAK -1 /* hw breakpoint */ +#define EX_WWATCH -2 /* memory write watch */ +#define EX_RWATCH -3 /* memory read watch */ +#define EX_AWATCH -4 /* memory access watch */ +#define EX_SIGINT 2 +#define EX_SIGTRAP 5 +#define EX_SIGABRT 6 +#define EX_SIGBUS 10 +#define EX_SIGSEGV 11 +/* or any standard *nix signal value */ + +#endif // __GDB_LIB_H__ + +/******************************************************************************\ + IMPLEMENTATION +\******************************************************************************/ + +/* CPU state */ +#ifdef __SDCC_ez80_adl +# define REG_SIZE 3 +#else +# define REG_SIZE 2 +#endif /* __SDCC_ez80_adl */ + +#define R_AF (0*REG_SIZE) +#define R_BC (1*REG_SIZE) +#define R_DE (2*REG_SIZE) +#define R_HL (3*REG_SIZE) +#define R_SP (4*REG_SIZE) +#define R_PC (5*REG_SIZE) + +#ifndef __SDCC_gbz80 +#define R_IX (6*REG_SIZE) +#define R_IY (7*REG_SIZE) +#define R_AF_ (8*REG_SIZE) +#define R_BC_ (9*REG_SIZE) +#define R_DE_ (10*REG_SIZE) +#define R_HL_ (11*REG_SIZE) +#define R_IR (12*REG_SIZE) + +#ifdef __SDCC_ez80_adl +#define R_SPS (13*REG_SIZE) +#define NUMREGBYTES (14*REG_SIZE) +#else +#define NUMREGBYTES (13*REG_SIZE) +#endif /* __SDCC_ez80_adl */ +#else +#define NUMREGBYTES (6*REG_SIZE) +#define FASTCALL +#endif /*__SDCC_gbz80 */ +extern byte _gdb_state[NUMREGBYTES]; + +void _gdb_save_cpu_state (void); +void _gdb_rest_cpu_state (void); +void _gdb_stub_main (int sigval, int pc_adj); + +/* dedicated stack */ +#ifdef DBG_STACK_SIZE + +#define LOAD_SP ld sp, __gdb_stack + DBG_STACK_SIZE + +static char _gdb_stack[DBG_STACK_SIZE]; + +#else + +#undef DBG_USE_TRAMPOLINE +#define LOAD_SP + +#endif + +#ifdef DBG_HWBREAK +extern int (*_gdb_toggle_hwbreak)(int set, void *addr); +#endif + +#if defined(DBG_TOGGLESTEP) +extern int (*_gdb_toggle_step)(int set); +#endif + +#ifdef DBG_SWBREAK +extern int (*_gdb_toggle_swbreak)(int set, void *addr); +#endif + +#ifdef DBG_ENTER +extern void (*_gdb_enter_func)(void); +#endif \ No newline at end of file