From: Sergey Belyashov Date: Fri, 25 Sep 2020 11:40:42 +0000 (+0300) Subject: Add basic Z80 CPU support X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=3dab9e15d3ab1cde555dfbf4a958eb17eec2ecb3;p=binutils-gdb.git Add basic Z80 CPU support Supported ISAs: - Z80 (all undocumented instructions) - Z180 - eZ80 (Z80 mode only) Datasheets: Z80: https://www.zilog.com/manage_directlink.php?filepath=docs/z80/um0080&extn=.pdf Z180: https://www.zilog.com/manage_directlink.php?filepath=docs/z180/ps0140&extn=.pdf eZ80: http://www.zilog.com/force_download.php?filepath=YUhSMGNEb3ZMM2QzZHk1NmFXeHZaeTVqYjIwdlpHOWpjeTlWVFRBd056Y3VjR1Jt To debug Z80 programs using GDB you must configure and embed z80-stub.c to your program (SDCC compiler is required). Or you may use some simulator with GDB support. gdb/ChangeLog: * Makefile.in (ALL_TARGET_OBS): Add z80-tdep.c. * NEWS: Mention z80 support. * configure.tgt: Handle z80*. * features/Makefile (XMLTOC): Add z80.xml. * features/z80-cpu.xml: New. * features/z80.c: Generate. * features/z80.xml: New. * z80-tdep.c: New file. * z80-tdep.h: New file. gdb/stubs/ChangeLog: * z80-stub.c: New file. Change-Id: Id0b7a6e210c3f93c6853c5e3031b7bcee47d0db9 --- diff --git a/gdb/Makefile.in b/gdb/Makefile.in index 2274b9b6a61..73a1bf83c85 100644 --- a/gdb/Makefile.in +++ b/gdb/Makefile.in @@ -853,7 +853,8 @@ ALL_TARGET_OBS = \ xstormy16-tdep.o \ xtensa-config.o \ xtensa-linux-tdep.o \ - xtensa-tdep.o + xtensa-tdep.o \ + z80-tdep.o # The following native-target dependent variables are defined on # configure.nat. diff --git a/gdb/NEWS b/gdb/NEWS index 44b8bbc88ee..062249aea98 100644 --- a/gdb/NEWS +++ b/gdb/NEWS @@ -429,6 +429,7 @@ alias [-a] [--] ALIAS = COMMAND [DEFAULT-ARGS...] GNU/Linux/RISC-V (gdbserver) riscv*-*-linux* BPF bpf-unknown-none +Z80 z80-unknown-* * Python API diff --git a/gdb/configure.tgt b/gdb/configure.tgt index 643973917fe..97a5a57c378 100644 --- a/gdb/configure.tgt +++ b/gdb/configure.tgt @@ -761,6 +761,10 @@ xtensa*-*-*linux*) # Target: GNU/Linux Xtensa gdb_target_obs="xtensa-linux-tdep.o symfile-mem.o linux-tdep.o" ;; +z80*) + # Target: Z80 + gdb_target_obs="z80-tdep.o" + ;; esac diff --git a/gdb/features/Makefile b/gdb/features/Makefile index 522ad58aab0..ded8c3bb9da 100644 --- a/gdb/features/Makefile +++ b/gdb/features/Makefile @@ -170,7 +170,8 @@ XMLTOC = \ s390x-tevx-linux64.xml \ s390x-vx-linux64.xml \ s390-gs-linux64.xml \ - s390x-gs-linux64.xml + s390x-gs-linux64.xml \ + z80.xml TDESC_CFILES = $(patsubst %.xml,%.c,$(XMLTOC)) GDB = false diff --git a/gdb/features/z80-cpu.xml b/gdb/features/z80-cpu.xml new file mode 100644 index 00000000000..98498b1bcc1 --- /dev/null +++ b/gdb/features/z80-cpu.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gdb/features/z80.c b/gdb/features/z80.c new file mode 100644 index 00000000000..944b563aca4 --- /dev/null +++ b/gdb/features/z80.c @@ -0,0 +1,44 @@ +/* THIS FILE IS GENERATED. -*- buffer-read-only: t -*- vi:set ro: + Original: z80.xml */ + +#include "defs.h" +#include "osabi.h" +#include "target-descriptions.h" + +struct target_desc *tdesc_z80; +static void +initialize_tdesc_z80 (void) +{ + target_desc_up result = allocate_target_description (); + set_tdesc_architecture (result.get (), bfd_scan_arch ("z80")); + + struct tdesc_feature *feature; + + feature = tdesc_create_feature (result.get (), "org.gnu.gdb.z80.cpu"); + tdesc_type_with_fields *type_with_fields; + type_with_fields = tdesc_create_flags (feature, "af_flags", 2); + tdesc_add_flag (type_with_fields, 0, "C"); + tdesc_add_flag (type_with_fields, 1, "N"); + tdesc_add_flag (type_with_fields, 2, "P/V"); + tdesc_add_flag (type_with_fields, 3, "F3"); + tdesc_add_flag (type_with_fields, 4, "H"); + tdesc_add_flag (type_with_fields, 5, "F5"); + tdesc_add_flag (type_with_fields, 6, "Z"); + tdesc_add_flag (type_with_fields, 7, "S"); + + tdesc_create_reg (feature, "af", 0, 1, NULL, 16, "af_flags"); + tdesc_create_reg (feature, "bc", 1, 1, NULL, 16, "uint16"); + tdesc_create_reg (feature, "de", 2, 1, NULL, 16, "data_ptr"); + tdesc_create_reg (feature, "hl", 3, 1, NULL, 16, "data_ptr"); + tdesc_create_reg (feature, "sp", 4, 1, NULL, 16, "data_ptr"); + tdesc_create_reg (feature, "pc", 5, 1, NULL, 32, "code_ptr"); + tdesc_create_reg (feature, "ix", 6, 1, NULL, 16, "data_ptr"); + tdesc_create_reg (feature, "iy", 7, 1, NULL, 16, "data_ptr"); + tdesc_create_reg (feature, "af'", 8, 1, NULL, 16, "af_flags"); + tdesc_create_reg (feature, "bc'", 9, 1, NULL, 16, "uint16"); + tdesc_create_reg (feature, "de'", 10, 1, NULL, 16, "data_ptr"); + tdesc_create_reg (feature, "hl'", 11, 1, NULL, 16, "data_ptr"); + tdesc_create_reg (feature, "ir", 12, 1, NULL, 16, "uint16"); + + tdesc_z80 = result.release (); +} diff --git a/gdb/features/z80.xml b/gdb/features/z80.xml new file mode 100644 index 00000000000..238687a127e --- /dev/null +++ b/gdb/features/z80.xml @@ -0,0 +1,12 @@ + + + + + + z80 + + diff --git a/gdb/stubs/z80-stub.c b/gdb/stubs/z80-stub.c new file mode 100644 index 00000000000..0ec128fbe6a --- /dev/null +++ b/gdb/stubs/z80-stub.c @@ -0,0 +1,1355 @@ +/* Debug stub for Z80. + + Copyright (C) 2021 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 . */ + +/* 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 getDebugChar() and 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 _debug_swbreak + ... + .org 0x66 ;NMI handler + jp _debug_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 toggle_swbreak(int set, void *addr); + function must return 0 on success. */ +//#define DBG_SWBREAK toggle_swbreak +#define DBG_SWBREAK + +/* 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 toggle_hwbreak(int set, void *addr); + function must return 0 on success. */ +//#define DBG_HWBREAK 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 150 + +/* 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 + stub_main function. Statement should include semicolon. */ +//#define DBG_ENTER debug_enter(); + +/* 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 "\ +\ + \ +\ + \ +\ +" +*/ +#endif /* DBG_CONFIGURED */ +/******************************************************************************\ + Public Interface +\******************************************************************************/ + +/* Enter to debug mode from software or hardware breakpoint. + Assume address of next instruction after breakpoint call is on top of stack. + Do JP _debug_swbreak or JP _debug_hwbreak from RST handler, for example. + */ +void debug_swbreak (void); +void debug_hwbreak (void); + +/* Jump to this function from NMI handler. Just replace RETN instruction by + JP _debug_nmi + Use if NMI detects request to enter to debug mode. + */ +void debug_nmi (void); + +/* Jump to this function from INT handler. Just replace EI+RETI instructions by + JP _debug_int + Use if INT detects request to enter to debug mode. + */ +void debug_int (void); + +#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 */ + +/* 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 + */ +void debug_exception (int ex); + +/* Prints to debugger console. */ +void debug_print(const char *str); +/******************************************************************************\ + Required functions +\******************************************************************************/ + +extern int getDebugChar (void); +extern void putDebugChar (int ch); + +#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 +extern int DBG_SWBREAK(int set, void *addr); +#endif + +#undef EXPAND +#undef DO_EXPAND +#endif /* DBG_SWBREAK */ + +#ifdef DBG_HWBREAK +extern int DBG_HWBREAK(int set, void *addr); +#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 + +/******************************************************************************\ + IMPLEMENTATION +\******************************************************************************/ + +#include + +#ifndef NULL +# define NULL (void*)0 +#endif + +typedef unsigned char byte; +typedef unsigned short word; + +/* 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 */ +static byte state[NUMREGBYTES]; + +#if DBG_PACKET_SIZE < (NUMREGBYTES*2+5) +#error "Too small DBG_PACKET_SIZE" +#endif + +#ifndef FASTCALL +#define FASTCALL __z88dk_fastcall +#endif + +/* dedicated stack */ +#ifdef DBG_STACK_SIZE + +#define LOAD_SP ld sp, #_stack + DBG_STACK_SIZE + +static char stack[DBG_STACK_SIZE]; + +#else + +#undef DBG_USE_TRAMPOLINE +#define LOAD_SP + +#endif + +#ifndef DBG_ENTER +#define DBG_ENTER +#endif + +#ifndef DBG_RESUME +#define DBG_RESUME ret +#endif + +static signed char sigval; + +static void stub_main (int sigval, int pc_adj); +static char high_hex (byte v) FASTCALL; +static char low_hex (byte v) FASTCALL; +static char put_packet_info (const char *buffer) FASTCALL; +static void save_cpu_state (void); +static void rest_cpu_state (void); + +/******************************************************************************/ +#ifdef DBG_SWBREAK +#ifdef DBG_SWBREAK_RST +#define DBG_SWBREAK_SIZE 1 +#else +#define DBG_SWBREAK_SIZE 3 +#endif +void +debug_swbreak (void) __naked +{ + __asm + ld (#_state + R_SP), sp + LOAD_SP + call _save_cpu_state + ld hl, #-DBG_SWBREAK_SIZE + push hl + ld hl, #EX_SWBREAK + push hl + call _stub_main + .globl _break_handler +#ifdef DBG_SWBREAK_RST +_break_handler = DBG_SWBREAK_RST +#else +_break_handler = _debug_swbreak +#endif + __endasm; +} +#endif /* DBG_SWBREAK */ +/******************************************************************************/ +#ifdef DBG_HWBREAK +#ifndef DBG_HWBREAK_SIZE +#define DBG_HWBREAK_SIZE 0 +#endif /* DBG_HWBREAK_SIZE */ +void +debug_hwbreak (void) __naked +{ + __asm + ld (#_state + R_SP), sp + LOAD_SP + call _save_cpu_state + ld hl, #-DBG_HWBREAK_SIZE + push hl + ld hl, #EX_HWBREAK + push hl + call _stub_main + __endasm; +} +#endif /* DBG_HWBREAK_SET */ +/******************************************************************************/ +void +debug_exception (int ex) __naked +{ + __asm + ld (#_state + R_SP), sp + LOAD_SP + call _save_cpu_state + ld hl, #0 + push hl +#ifdef __SDCC_gbz80 + ld hl, #_state + R_SP + ld a, (hl+) + ld h, (hl) + ld l, a +#else + ld hl, (#_state + R_SP) +#endif + inc hl + inc hl + ld e, (hl) + inc hl + ld d, (hl) + push de + call _stub_main + __endasm; + (void)ex; +} +/******************************************************************************/ +#ifndef __SDCC_gbz80 +void +debug_nmi(void) __naked +{ + __asm + ld (#_state + R_SP), sp + LOAD_SP + call _save_cpu_state + ld hl, #0 ;pc_adj + push hl + ld hl, #DBG_NMI_EX + push hl + ld hl, #_stub_main + push hl + push hl + retn + __endasm; +} +#endif +/******************************************************************************/ +void +debug_int(void) __naked +{ + __asm + ld (#_state + R_SP), sp + LOAD_SP + call _save_cpu_state + ld hl, #0 ;pc_adj + push hl + ld hl, #DBG_INT_EX + push hl + ld hl, #_stub_main + push hl + push hl + ei + reti + __endasm; +} +/******************************************************************************/ +#ifdef DBG_PRINT +void +debug_print(const char *str) +{ + putDebugChar ('$'); + putDebugChar ('O'); + char csum = 'O'; + for (; *str != '\0'; ) + { + char c = high_hex (*str); + csum += c; + putDebugChar (c); + c = low_hex (*str++); + csum += c; + putDebugChar (c); + } + putDebugChar ('#'); + putDebugChar (high_hex (csum)); + putDebugChar (low_hex (csum)); +} +#endif /* DBG_PRINT */ +/******************************************************************************/ +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 char* byte2hex(char *buf, byte val); +static int hex2int (const char **buf) FASTCALL; +static char* int2hex (char *buf, int v); +static void get_packet (char *buffer); +static void put_packet (const char *buffer); +static char process (char *buffer) FASTCALL; +static void rest_cpu_state (void); + +static void +stub_main (int ex, int pc_adj) +{ + char buffer[DBG_PACKET_SIZE+1]; + sigval = (signed char)ex; + store_pc_sp (pc_adj); + + DBG_ENTER + + /* after starting gdb_stub must always return stop reason */ + *buffer = '?'; + for (; process (buffer);) + { + put_packet (buffer); + get_packet (buffer); + } + put_packet (buffer); + 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 (;; putDebugChar ('-')) + { + /* wait for packet start character */ + while (getDebugChar () != '$'); +retry: + csum = 0; + esc = 0; + p = buffer; + count = DBG_PACKET_SIZE; + do + { + ch = 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 = getDebugChar (); + if (ch != high_hex (csum)) + continue; + ch = getDebugChar (); + if (ch != low_hex (csum)) + continue; + break; + } + putDebugChar ('+'); +} + +static void +put_packet (const char *buffer) +{ + /* $#. */ + for (;;) + { + putDebugChar ('$'); + char checksum = put_packet_info (buffer); + putDebugChar ('#'); + putDebugChar (high_hex(checksum)); + putDebugChar (low_hex(checksum)); + for (;;) + { + char c = getDebugChar (); + switch (c) + { + case '+': return; + case '-': break; + default: + 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 */ + putDebugChar ('}'); + checksum += '}'; + ch ^= 0x20; + } + putDebugChar (ch); + checksum += ch; + } + return checksum; +} + +static void +store_pc_sp (int pc_adj) FASTCALL +{ + byte *sp = get_reg_value (&state[R_SP]); + byte *pc = get_reg_value (sp); + pc += pc_adj; + set_reg_value (&state[R_PC], pc); + set_reg_value (&state[R_SP], sp + REG_SIZE); +} + +static char *mem2hex (char *buf, const byte *mem, unsigned bytes); +static char *hex2mem (byte *mem, const 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, &state[R_AF]); + p = format_reg_value(p, R_SP/REG_SIZE, &state[R_SP]); + p = format_reg_value(p, R_PC/REG_SIZE, &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) +#ifdef DBG_MEMORY_MAP +static void read_memory_map (char *buffer, unsigned offset, unsigned length); +#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 + memcpy (p, ";swbreak+", 9); + p += 9; +#endif +#ifdef 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 + *p = '\0'; + return 0; + } +#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_memory_map (buffer, offset, length); + 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, state, NUMREGBYTES); + return 0; +} + +static signed char +process_G (char *buffer) FASTCALL +{ + hex2mem (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 (&state[R_PC], addr); + } + rest_cpu_state (); + return 0; +} + +static signed char +process_D (char *buffer) FASTCALL +{/* 'D' - detach the program: continue execution */ + *buffer = '\0'; + return -2; +} + +static signed char +process_k (char *buffer) FASTCALL +{/* 'k' - Kill the program */ + set_reg_value (&state[R_PC], 0); + 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 */ + return DBG_SWBREAK_PROC(set, addr); +#endif +#ifdef DBG_HWBREAK + case '1': /* hw break */ + 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 '?': 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 */ + } +} + +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; +} + +static char * +byte2hex (char *p, byte v) +{ + *p++ = high_hex (v); + *p++ = low_hex (v); + return p; +} + +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; +} + +static int +hex2int (const char **buf) FASTCALL +{ + word r = 0; + for (;; (*buf)++) + { + signed char a = hex2val(**buf); + if (a < 0) + break; + r <<= 4; + r += (byte)a; + } + return (int)r; +} + +static char * +int2hex (char *buf, int v) +{ + buf = byte2hex(buf, (word)v >> 8); + return byte2hex(buf, (byte)v); +} + +static char +high_hex (byte v) FASTCALL +{ + return low_hex(v >> 4); +} + +static char +low_hex (byte v) FASTCALL +{ +/* + __asm + ld a, l + and a, #0x0f + add a, #0x90 + daa + adc a, #0x40 + daa + ld l, a + __endasm; + (void)v; +*/ + v &= 0x0f; + v += '0'; + if (v < '9'+1) + return v; + return v + 'a' - '0' - 10; +} + +/* 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; +} + +/* 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 const char * +hex2mem (byte *mem, const char *buf, unsigned bytes) +{ + if (bytes != 0) + { + do + { + *mem++ = hex2byte (buf); + buf += 2; + } + while (--bytes); + } + return buf; +} + +#ifdef DBG_MEMORY_MAP +static void +read_memory_map (char *buffer, unsigned offset, unsigned length) +{ + const char *map = DBG_MEMORY_MAP; + const unsigned map_sz = strlen(map); + if (offset >= map_sz) + { + buffer[0] = 'l'; + buffer[1] = '\0'; + return; + } + if (offset + length > map_sz) + length = map_sz - offset; + buffer[0] = 'm'; + memcpy (&buffer[1], &map[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 state.except PC and SP */ +static void +save_cpu_state() __naked +{ + __asm + push af + ld a, l + ld (#_state + R_HL + 0), a + ld a, h + ld (#_state + R_HL + 1), a + ld hl, #_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 state and continue execution */ +static void +rest_cpu_state() __naked +{ + __asm +;restore SP + ld a, (#_state + R_SP + 0) + ld l,a + ld a, (#_state + R_SP + 1) + ld h,a + ld sp, hl +;push PC value as return address + ld a, (#_state + R_PC + 0) + ld l, a + ld a, (#_state + R_PC + 1) + ld h, a + push hl +;restore registers + ld hl, #_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 state.except PC and SP */ +static void +save_cpu_state() __naked +{ + __asm + ld (#_state + R_HL), hl + ld (#_state + R_DE), de + ld (#_state + R_BC), bc + push af + pop hl + ld (#_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, #_state + R_IR + ld (hl), a + inc hl + ld (hl), e + inc hl + ld (hl), d + ld a, MB + ld (#_state + R_AF+2), a +#else + ld l, a + ld a, i + ld h, a + ld (#_state + R_IR), hl +#endif /* __SDCC_ez80_adl */ + ld (#_state + R_IX), ix + ld (#_state + R_IY), iy + ex af, af' ;' + exx + ld (#_state + R_HL_), hl + ld (#_state + R_DE_), de + ld (#_state + R_BC_), bc + push af + pop hl + ld (#_state + R_AF_), hl + ret + __endasm; +} + +/* restore CPU state and continue execution */ +static void +rest_cpu_state() __naked +{ + __asm +#ifdef DBG_USE_TRAMPOLINE + ld sp, _stack + DBG_STACK_SIZE + ld hl, (#_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, (#_state + R_AF_) + push hl + pop af + ld bc, (#_state + R_BC_) + ld de, (#_state + R_DE_) + ld hl, (#_state + R_HL_) + exx + ex af, af' ;' + ld iy, (#_state + R_IY) + ld ix, (#_state + R_IX) +#ifdef __SDCC_ez80_adl + ld a, (#_state + R_AF + 2) + ld MB, a + ld hl, (#_state + R_IR + 1) ;I register + ld i, hl + ld a, (#_state + R_IR + 0) ; R register + ld l, a +#else + ld hl, (#_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, (#_state + R_DE) + ld bc, (#_state + R_BC) + ld hl, (#_state + R_AF) + push hl + pop af + ld sp, (#_state + R_SP) +#ifndef DBG_USE_TRAMPOLINE + ld hl, (#_state + R_PC) + push hl + ld hl, (#_state + R_HL) + DBG_RESUME +#else + ld hl, (#_state + R_HL) +#ifdef __SDCC_ez80_adl + jp #_stack + DBG_STACK_SIZE - 4 +#else + jp #_stack + DBG_STACK_SIZE - 3 +#endif +#endif /* DBG_USE_TRAMPOLINE */ + __endasm; +} +#endif /* __SDCC_gbz80 */ diff --git a/gdb/z80-tdep.c b/gdb/z80-tdep.c new file mode 100644 index 00000000000..7b9a7e23501 --- /dev/null +++ b/gdb/z80-tdep.c @@ -0,0 +1,1461 @@ +/* Target-dependent code for the Z80. + + Copyright (C) 1986-2021 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 "defs.h" +#include "arch-utils.h" +#include "dis-asm.h" +#include "frame.h" +#include "frame-unwind.h" +#include "frame-base.h" +#include "trad-frame.h" +#include "gdbcmd.h" +#include "gdbcore.h" +#include "gdbtypes.h" +#include "inferior.h" +#include "objfiles.h" +#include "symfile.h" + +#include "z80-tdep.h" +#include "features/z80.c" + +/* You need to define __gdb_break_handler symbol pointing to the breakpoint + handler. The value of the symbol will be used to determine the instruction + for software breakpoint. If __gdb_break_handler points to one of standard + RST addresses (0x00, 0x08, 0x10,... 0x38) then RST __gdb_break_handler + instruction will be used, else CALL __gdb_break_handler + +;breakpoint handler + .globl __gdb_break_handler + .org 8 +__gdb_break_handler: + jp _debug_swbreak + +*/ + +/* Meaning of terms "previous" and "next": + previous frame - frame of callee, which is called by current function + current frame - frame of current function which has called callee + next frame - frame of caller, which has called current function +*/ + +struct gdbarch_tdep +{ + /* Number of bytes used for address: + 2 bytes for all Z80 family + 3 bytes for eZ80 CPUs operating in ADL mode */ + int addr_length; + + /* Type for void. */ + struct type *void_type; + /* Type for a function returning void. */ + struct type *func_void_type; + /* Type for a pointer to a function. Used for the type of PC. */ + struct type *pc_type; +}; + +/* At any time stack frame contains following parts: + [] + [] + [ + ] + [] + In simplest case is pointer to the call instruction + (or call __call_hl). There are more difficult cases: interrupt handler or + push/ret and jp; but they are untrackable. +*/ + +struct z80_unwind_cache +{ + /* The previous frame's inner most stack address (SP after call executed), + it is current frame's frame_id. */ + CORE_ADDR prev_sp; + + /* Size of the frame, prev_sp + size = next_frame.prev_sp */ + ULONGEST size; + + /* size of saved state (including frame pointer and return address), + assume: prev_sp + size = IX + state_size */ + ULONGEST state_size; + + struct + { + int called:1; /* there is return address on stack */ + int load_args:1; /* prologues loads args using POPs */ + int fp_sdcc:1; /* prologue saves and adjusts frame pointer IX */ + int interrupt:1; /* __interrupt handler */ + int critical:1; /* __critical function */ + } prologue_type; + + /* Table indicating the location of each and every register. */ + struct trad_frame_saved_reg *saved_regs; +}; + +enum instruction_type +{ + insn_default, + insn_z80, + insn_adl, + insn_z80_ed, + insn_adl_ed, + insn_z80_ddfd, + insn_adl_ddfd, + insn_djnz_d, + insn_jr_d, + insn_jr_cc_d, + insn_jp_nn, + insn_jp_rr, + insn_jp_cc_nn, + insn_call_nn, + insn_call_cc_nn, + insn_rst_n, + insn_ret, + insn_ret_cc, + insn_push_rr, + insn_pop_rr, + insn_dec_sp, + insn_inc_sp, + insn_ld_sp_nn, + insn_ld_sp_6nn9, /* ld sp, (nn) */ + insn_ld_sp_rr, + insn_force_nop /* invalid opcode prefix */ +}; + +struct insn_info +{ + gdb_byte code; + gdb_byte mask; + gdb_byte size; /* without prefix(es) */ + enum instruction_type type; +}; + +/* Constants */ + +static const struct insn_info * +z80_get_insn_info (struct gdbarch *gdbarch, const gdb_byte *buf, int *size); + +static const char *z80_reg_names[] = +{ + /* 24 bit on eZ80, else 16 bit */ + "af", "bc", "de", "hl", + "sp", "pc", "ix", "iy", + "af'", "bc'", "de'", "hl'", + "ir", + /* eZ80 only */ + "sps" +}; + +/* Return the name of register REGNUM. */ +static const char * +z80_register_name (struct gdbarch *gdbarch, int regnum) +{ + if (regnum >= 0 && regnum < ARRAY_SIZE (z80_reg_names)) + return z80_reg_names[regnum]; + + return NULL; +} + +/* Return the type of a register specified by the architecture. Only + the register cache should call this function directly; others should + use "register_type". */ +static struct type * +z80_register_type (struct gdbarch *gdbarch, int reg_nr) +{ + return builtin_type (gdbarch)->builtin_data_ptr; +} + +/* The next 2 functions check BUF for instruction. If it is pop/push rr, then + it returns register number OR'ed with 0x100 */ +static int +z80_is_pop_rr (const gdb_byte buf[], int *size) +{ + switch (buf[0]) + { + case 0xc1: + *size = 1; + return Z80_BC_REGNUM | 0x100; + case 0xd1: + *size = 1; + return Z80_DE_REGNUM | 0x100; + case 0xe1: + *size = 1; + return Z80_HL_REGNUM | 0x100; + case 0xf1: + *size = 1; + return Z80_AF_REGNUM | 0x100; + case 0xdd: + *size = 2; + return (buf[1] == 0xe1) ? (Z80_IX_REGNUM | 0x100) : 0; + case 0xfd: + *size = 2; + return (buf[1] == 0xe1) ? (Z80_IY_REGNUM | 0x100) : 0; + } + *size = 0; + return 0; +} + +static int +z80_is_push_rr (const gdb_byte buf[], int *size) +{ + switch (buf[0]) + { + case 0xc5: + *size = 1; + return Z80_BC_REGNUM | 0x100; + case 0xd5: + *size = 1; + return Z80_DE_REGNUM | 0x100; + case 0xe5: + *size = 1; + return Z80_HL_REGNUM | 0x100; + case 0xf5: + *size = 1; + return Z80_AF_REGNUM | 0x100; + case 0xdd: + *size = 2; + return (buf[1] == 0xe5) ? (Z80_IX_REGNUM | 0x100) : 0; + case 0xfd: + *size = 2; + return (buf[1] == 0xe5) ? (Z80_IY_REGNUM | 0x100) : 0; + } + *size = 0; + return 0; +} + +/* Function: z80_scan_prologue + + This function decodes a function prologue to determine: + 1) the size of the stack frame + 2) which registers are saved on it + 3) the offsets of saved regs + This information is stored in the z80_unwind_cache structure. + Small SDCC functions may just load args using POP instructions in prologue: + pop af + pop de + pop hl + pop bc + push bc + push hl + push de + push af + SDCC function prologue may have up to 3 sections (all are optional): + 1) save state + a) __critical functions: + ld a,i + di + push af + b) __interrupt (both int and nmi) functions: + push af + push bc + push de + push hl + push iy + 2) save and adjust frame pointer + a) call to special function (size optimization) + call ___sdcc_enter_ix + b) inline (speed optimization) + push ix + ld ix, #0 + add ix, sp + c) without FP, but saving it (IX is optimized out) + push ix + 3) allocate local variables + a) via series of PUSH AF and optional DEC SP (size optimization) + push af + ... + push af + dec sp ;optional, if allocated odd numbers of bytes + b) via SP decrements + dec sp + ... + dec sp + c) via addition (for large frames: 5+ for speed and 9+ for size opt.) + ld hl, #xxxx ;size of stack frame + add hl, sp + ld sp, hl + d) same, but using register IY (arrays or for __z88dk_fastcall functions) + ld iy, #xxxx ;size of stack frame + add iy, sp + ld sp, iy + e) same as c, but for eZ80 + lea hl, ix - #nn + ld sp, hl + f) same as d, but for eZ80 + lea iy, ix - #nn + ld sp, iy +*/ + +static int +z80_scan_prologue (struct gdbarch *gdbarch, CORE_ADDR pc_beg, CORE_ADDR pc_end, + struct z80_unwind_cache *info) +{ + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); + int addr_len = gdbarch_tdep (gdbarch)->addr_length; + gdb_byte prologue[32]; /* max prologue is 24 bytes: __interrupt with local array */ + int pos = 0; + int len; + int reg; + CORE_ADDR value; + + len = pc_end - pc_beg; + if (len > (int)sizeof (prologue)) + len = sizeof (prologue); + + read_memory (pc_beg, prologue, len); + + /* stage0: check for series of POPs and then PUSHs */ + if ((reg = z80_is_pop_rr(prologue, &pos))) + { + int i; + int size = pos; + gdb_byte regs[8]; /* Z80 have only 6 register pairs */ + regs[0] = reg & 0xff; + for (i = 1; i < 8 && (regs[i] = z80_is_pop_rr (&prologue[pos], &size)); + ++i, pos += size); + /* now we expect series of PUSHs in reverse order */ + for (--i; i >= 0 && regs[i] == z80_is_push_rr (&prologue[pos], &size); + --i, pos += size); + if (i == -1 && pos > 0) + info->prologue_type.load_args = 1; + else + pos = 0; + } + /* stage1: check for __interrupt handlers and __critical functions */ + else if (!memcmp (&prologue[pos], "\355\127\363\365", 4)) + { /* ld a, i; di; push af */ + info->prologue_type.critical = 1; + pos += 4; + info->state_size += addr_len; + } + else if (!memcmp (&prologue[pos], "\365\305\325\345\375\345", 6)) + { /* push af; push bc; push de; push hl; push iy */ + info->prologue_type.interrupt = 1; + pos += 6; + info->state_size += addr_len * 5; + } + + /* stage2: check for FP saving scheme */ + if (prologue[pos] == 0xcd) /* call nn */ + { + struct bound_minimal_symbol msymbol; + msymbol = lookup_minimal_symbol ("__sdcc_enter_ix", NULL, NULL); + if (msymbol.minsym) + { + value = BMSYMBOL_VALUE_ADDRESS (msymbol); + if (value == extract_unsigned_integer (&prologue[pos+1], addr_len, byte_order)) + { + pos += 1 + addr_len; + info->prologue_type.fp_sdcc = 1; + } + } + } + else if (!memcmp (&prologue[pos], "\335\345\335\041\000\000", 4+addr_len) && + !memcmp (&prologue[pos+4+addr_len], "\335\071\335\371", 4)) + { /* push ix; ld ix, #0; add ix, sp; ld sp, ix */ + pos += 4 + addr_len + 4; + info->prologue_type.fp_sdcc = 1; + } + else if (!memcmp (&prologue[pos], "\335\345", 2)) + { /* push ix */ + pos += 2; + info->prologue_type.fp_sdcc = 1; + } + + /* stage3: check for local variables allocation */ + switch (prologue[pos]) + { + case 0xf5: /* push af */ + info->size = 0; + while (prologue[pos] == 0xf5) + { + info->size += addr_len; + pos++; + } + if (prologue[pos] == 0x3b) /* dec sp */ + { + info->size++; + pos++; + } + break; + case 0x3b: /* dec sp */ + info->size = 0; + while (prologue[pos] == 0x3b) + { + info->size++; + pos++; + } + break; + case 0x21: /*ld hl, -nn */ + if (prologue[pos+addr_len] == 0x39 && prologue[pos+addr_len] >= 0x80 && + prologue[pos+addr_len+1] == 0xf9) + { /* add hl, sp; ld sp, hl */ + info->size = -extract_signed_integer(&prologue[pos+1], addr_len, byte_order); + pos += 1 + addr_len + 2; + } + break; + case 0xfd: /* ld iy, -nn */ + if (prologue[pos+1] == 0x21 && prologue[pos+1+addr_len] >= 0x80 && + !memcmp (&prologue[pos+2+addr_len], "\375\071\375\371", 4)) + { + info->size = -extract_signed_integer(&prologue[pos+2], addr_len, byte_order); + pos += 2 + addr_len + 4; + } + break; + case 0xed: /* check for lea xx, ix - n */ + switch (prologue[pos+1]) + { + case 0x22: /* lea hl, ix - n */ + if (prologue[pos+2] >= 0x80 && prologue[pos+3] == 0xf9) + { /* ld sp, hl */ + info->size = -extract_signed_integer(&prologue[pos+2], 1, byte_order); + pos += 4; + } + break; + case 0x55: /* lea iy, ix - n */ + if (prologue[pos+2] >= 0x80 && prologue[pos+3] == 0xfd && + prologue[pos+4] == 0xf9) + { /* ld sp, iy */ + info->size = -extract_signed_integer(&prologue[pos+2], 1, byte_order); + pos += 5; + } + break; + } + break; + } + len = 0; + + if (info->prologue_type.interrupt) + { + info->saved_regs[Z80_AF_REGNUM].set_addr (len++); + info->saved_regs[Z80_BC_REGNUM].set_addr (len++); + info->saved_regs[Z80_DE_REGNUM].set_addr (len++); + info->saved_regs[Z80_HL_REGNUM].set_addr (len++); + info->saved_regs[Z80_IY_REGNUM].set_addr (len++); + } + + if (info->prologue_type.critical) + len++; /* just skip IFF2 saved state */ + + if (info->prologue_type.fp_sdcc) + info->saved_regs[Z80_IX_REGNUM].set_addr (len++); + + info->state_size += len * addr_len; + + return pc_beg + pos; +} + +static CORE_ADDR +z80_skip_prologue (struct gdbarch *gdbarch, CORE_ADDR pc) +{ + CORE_ADDR func_addr, func_end; + CORE_ADDR prologue_end; + + if (!find_pc_partial_function (pc, NULL, &func_addr, &func_end)) + return pc; + + prologue_end = skip_prologue_using_sal (gdbarch, func_addr); + if (prologue_end != 0) + return std::max (pc, prologue_end); + + { + struct z80_unwind_cache info = {0}; + struct trad_frame_saved_reg saved_regs[Z80_NUM_REGS]; + + info.saved_regs = saved_regs; + + /* Need to run the prologue scanner to figure out if the function has a + prologue. */ + + prologue_end = z80_scan_prologue (gdbarch, func_addr, func_end, &info); + + if (info.prologue_type.fp_sdcc || info.prologue_type.interrupt || + info.prologue_type.critical) + return std::max (pc, prologue_end); + } + + if (prologue_end != 0) + { + struct symtab_and_line prologue_sal = find_pc_line (func_addr, 0); + struct compunit_symtab *compunit = SYMTAB_COMPUNIT (prologue_sal.symtab); + const char *debug_format = COMPUNIT_DEBUGFORMAT (compunit); + + if (debug_format != NULL && + !strncasecmp ("dwarf", debug_format, strlen("dwarf"))) + return std::max (pc, prologue_end); + } + + return pc; +} + +/* Return the return-value convention that will be used by FUNCTION + to return a value of type VALTYPE. FUNCTION may be NULL in which + case the return convention is computed based only on VALTYPE. + + If READBUF is not NULL, extract the return value and save it in this buffer. + + If WRITEBUF is not NULL, it contains a return value which will be + stored into the appropriate register. This can be used when we want + to force the value returned by a function (see the "return" command + for instance). */ +static enum return_value_convention +z80_return_value (struct gdbarch *gdbarch, struct value *function, + struct type *valtype, struct regcache *regcache, + gdb_byte *readbuf, const gdb_byte *writebuf) +{ + /* Byte are returned in L, word in HL, dword in DEHL. */ + int len = TYPE_LENGTH (valtype); + + if ((valtype->code () == TYPE_CODE_STRUCT + || valtype->code () == TYPE_CODE_UNION + || valtype->code () == TYPE_CODE_ARRAY) + && len > 4) + return RETURN_VALUE_STRUCT_CONVENTION; + + if (writebuf != NULL) + { + if (len > 2) + { + regcache->cooked_write_part (Z80_DE_REGNUM, 0, len - 2, writebuf+2); + len = 2; + } + regcache->cooked_write_part (Z80_HL_REGNUM, 0, len, writebuf); + } + + if (readbuf != NULL) + { + if (len > 2) + { + regcache->cooked_read_part (Z80_DE_REGNUM, 0, len - 2, readbuf+2); + len = 2; + } + regcache->cooked_read_part (Z80_HL_REGNUM, 0, len, readbuf); + } + + return RETURN_VALUE_REGISTER_CONVENTION; +} + +/* function unwinds current stack frame and returns next one */ +static struct z80_unwind_cache * +z80_frame_unwind_cache (struct frame_info *this_frame, + void **this_prologue_cache) +{ + CORE_ADDR start_pc, current_pc; + ULONGEST this_base; + int i; + gdb_byte buf[sizeof(void*)]; + struct z80_unwind_cache *info; + struct gdbarch *gdbarch = get_frame_arch (this_frame); + int addr_len = gdbarch_tdep (gdbarch)->addr_length; + + if (*this_prologue_cache) + return (struct z80_unwind_cache *) *this_prologue_cache; + + info = FRAME_OBSTACK_ZALLOC (struct z80_unwind_cache); + memset (info, 0, sizeof (*info)); + info->saved_regs = trad_frame_alloc_saved_regs (this_frame); + *this_prologue_cache = info; + + start_pc = get_frame_func (this_frame); + current_pc = get_frame_pc (this_frame); + if ((start_pc > 0) && (start_pc <= current_pc)) + z80_scan_prologue (get_frame_arch (this_frame), + start_pc, current_pc, info); + + if (info->prologue_type.fp_sdcc) + { + /* With SDCC standard prologue, IX points to the end of current frame + (where previous frame pointer and state are saved). */ + this_base = get_frame_register_unsigned (this_frame, Z80_IX_REGNUM); + info->prev_sp = this_base + info->size; + } + else + { + CORE_ADDR addr; + CORE_ADDR sp; + CORE_ADDR sp_mask = (1 << gdbarch_ptr_bit(gdbarch)) - 1; + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); + /* Assume that the FP is this frame's SP but with that pushed + stack space added back. */ + this_base = get_frame_register_unsigned (this_frame, Z80_SP_REGNUM); + sp = this_base + info->size; + for (;; ++sp) + { + sp &= sp_mask; + if (sp < this_base) + { /* overflow, looks like end of stack */ + sp = this_base + info->size; + break; + } + /* find return address */ + read_memory (sp, buf, addr_len); + addr = extract_unsigned_integer(buf, addr_len, byte_order); + read_memory (addr-addr_len-1, buf, addr_len+1); + if (buf[0] == 0xcd || (buf[0] & 0307) == 0304) /* Is it CALL */ + { /* CALL nn or CALL cc,nn */ + static const char *names[] = + { + "__sdcc_call_ix", "__sdcc_call_iy", "__sdcc_call_hl" + }; + addr = extract_unsigned_integer(buf+1, addr_len, byte_order); + if (addr == start_pc) + break; /* found */ + for (i = sizeof(names)/sizeof(*names)-1; i >= 0; --i) + { + struct bound_minimal_symbol msymbol; + msymbol = lookup_minimal_symbol (names[i], NULL, NULL); + if (!msymbol.minsym) + continue; + if (addr == BMSYMBOL_VALUE_ADDRESS (msymbol)) + break; + } + if (i >= 0) + break; + continue; + } + else + continue; /* it is not call_nn, call_cc_nn */ + } + info->prev_sp = sp; + } + + /* Adjust all the saved registers so that they contain addresses and not + offsets. */ + for (i = 0; i < gdbarch_num_regs (gdbarch) - 1; i++) + if (info->saved_regs[i].addr () > 0) + info->saved_regs[i].set_addr + (info->prev_sp - info->saved_regs[i].addr () * addr_len); + + /* Except for the startup code, the return PC is always saved on + the stack and is at the base of the frame. */ + info->saved_regs[Z80_PC_REGNUM].set_addr (info->prev_sp); + + /* The previous frame's SP needed to be computed. Save the computed + value. */ + info->saved_regs[Z80_SP_REGNUM].set_value (info->prev_sp + addr_len); + return info; +} + +/* Given a GDB frame, determine the address of the calling function's + frame. This will be used to create a new GDB frame struct. */ +static void +z80_frame_this_id (struct frame_info *this_frame, void **this_cache, + struct frame_id *this_id) +{ + struct frame_id id; + struct z80_unwind_cache *info; + CORE_ADDR base; + CORE_ADDR func; + + /* The FUNC is easy. */ + func = get_frame_func (this_frame); + + info = z80_frame_unwind_cache (this_frame, this_cache); + /* Hopefully the prologue analysis either correctly determined the + frame's base (which is the SP from the previous frame), or set + that base to "NULL". */ + base = info->prev_sp; + if (base == 0) + return; + + id = frame_id_build (base, func); + *this_id = id; +} + +static struct value * +z80_frame_prev_register (struct frame_info *this_frame, + void **this_prologue_cache, int regnum) +{ + struct z80_unwind_cache *info + = z80_frame_unwind_cache (this_frame, this_prologue_cache); + + if (regnum == Z80_PC_REGNUM) + { + if (info->saved_regs[Z80_PC_REGNUM].is_addr ()) + { + /* Reading the return PC from the PC register is slightly + abnormal. */ + ULONGEST pc; + gdb_byte buf[3]; + struct gdbarch *gdbarch = get_frame_arch (this_frame); + struct gdbarch_tdep *tdep = gdbarch_tdep (gdbarch); + enum bfd_endian byte_order = gdbarch_byte_order (gdbarch); + + read_memory (info->saved_regs[Z80_PC_REGNUM].addr (), + buf, tdep->addr_length); + pc = extract_unsigned_integer (buf, tdep->addr_length, byte_order); + return frame_unwind_got_constant (this_frame, regnum, pc); + } + + return frame_unwind_got_optimized (this_frame, regnum); + } + + return trad_frame_get_prev_register (this_frame, info->saved_regs, regnum); +} + +/* Return the breakpoint kind for this target based on *PCPTR. */ +static int +z80_breakpoint_kind_from_pc (struct gdbarch *gdbarch, CORE_ADDR *pcptr) +{ + static int addr = -1; + if (addr == -1) + { + struct bound_minimal_symbol bh; + bh = lookup_minimal_symbol ("_break_handler", NULL, NULL); + if (bh.minsym) + addr = BMSYMBOL_VALUE_ADDRESS (bh); + else + { + warning(_("Unable to determine inferior's software breakpoint type: " + "couldn't find `_break_handler' function in inferior. Will " + "be used default software breakpoint instruction RST 0x08.")); + addr = 0x0008; + } + } + return addr; +} + +/* Return the software breakpoint from KIND. KIND is just address of breakpoint + handler. If address is on of standard RSTs, then RST n instruction is used + as breakpoint. + SIZE is set to the software breakpoint's length in memory. */ +static const gdb_byte * +z80_sw_breakpoint_from_kind (struct gdbarch *gdbarch, int kind, int *size) +{ + static gdb_byte break_insn[8]; + + if ((kind & 070) == kind) + { + break_insn[0] = kind | 0307; + *size = 1; + } + else /* kind is non-RST address, use CALL instead, but it is dungerous */ + { + gdb_byte *p = break_insn; + *p++ = 0xcd; + *p++ = (kind >> 0) & 0xff; + *p++ = (kind >> 8) & 0xff; + if (gdbarch_tdep (gdbarch)->addr_length > 2) + *p++ = (kind >> 16) & 0xff; + *size = p - break_insn; + } + return break_insn; +} + +/* Return a vector of addresses on which the software single step + breakpoints should be inserted. NULL means software single step is + not used. + Only one breakpoint address will be returned: conditional branches + will be always evaluated. */ +static std::vector +z80_software_single_step (struct regcache *regcache) +{ + static const int flag_mask[] = {1 << 6, 1 << 0, 1 << 2, 1 << 7}; + gdb_byte buf[8]; + ULONGEST t; + ULONGEST addr; + int opcode; + int size; + const struct insn_info *info; + std::vector ret (1); + struct gdbarch *gdbarch = target_gdbarch (); + + regcache->cooked_read (Z80_PC_REGNUM, &addr); + read_memory (addr, buf, sizeof(buf)); + info = z80_get_insn_info (gdbarch, buf, &size); + ret[0] = addr + size; + if (info == NULL) /* possible in case of double prefix */ + { /* forced NOP, TODO: replace by NOP */ + return ret; + } + opcode = buf[size - info->size]; /* take opcode instead of prefix */ + /* stage 1: check for conditions */ + switch (info->type) + { + case insn_djnz_d: + regcache->cooked_read (Z80_BC_REGNUM, &t); + if ((t & 0xff00) != 0x100) + return ret; + break; + case insn_jr_cc_d: + opcode &= 030; /* JR NZ,d has cc equal to 040, but others 000 */ + /* fall through */ + case insn_jp_cc_nn: + case insn_call_cc_nn: + case insn_ret_cc: + regcache->cooked_read (Z80_AF_REGNUM, &t); + /* lower bit of condition inverts match, so invert flags if set */ + if ((opcode & 010) != 0) + t = ~t; + /* two higher bits of condition field defines flag, so use them only + to check condition of "not execute" */ + if (t & flag_mask[(opcode >> 4) & 3]) + return ret; + break; + } + /* stage 2: compute address */ + /* TODO: implement eZ80 MADL support */ + switch (info->type) + { + default: + return ret; + case insn_djnz_d: + case insn_jr_d: + case insn_jr_cc_d: + addr += size; + addr += (signed char)buf[size-1]; + break; + case insn_jp_rr: + if (size == 1) + opcode = Z80_HL_REGNUM; + else + opcode = (buf[size-2] & 0x20) ? Z80_IY_REGNUM : Z80_IX_REGNUM; + regcache->cooked_read (opcode, &addr); + break; + case insn_jp_nn: + case insn_jp_cc_nn: + case insn_call_nn: + case insn_call_cc_nn: + addr = buf[size-1] * 0x100 + buf[size-2]; + if (info->size > 3) /* long instruction mode */ + addr = addr * 0x100 + buf[size-3]; + break; + case insn_rst_n: + addr = opcode & 070; + break; + case insn_ret: + case insn_ret_cc: + regcache->cooked_read (Z80_SP_REGNUM, &addr); + read_memory (addr, buf, 3); + addr = buf[1] * 0x100 + buf[0]; + if (gdbarch_bfd_arch_info (gdbarch)->mach == bfd_mach_ez80_adl) + addr = addr * 0x100 + buf[2]; + break; + } + ret[0] = addr; + return ret; +} + +/* Cached, dynamically allocated copies of the target data structures: */ +static unsigned (*cache_ovly_region_table)[3] = 0; +static unsigned cache_novly_regions; +static CORE_ADDR cache_ovly_region_table_base = 0; +enum ovly_index + { + VMA, OSIZE, MAPPED_TO_LMA + }; + +static void +z80_free_overlay_region_table (void) +{ + if (cache_ovly_region_table) + xfree (cache_ovly_region_table); + cache_novly_regions = 0; + cache_ovly_region_table = NULL; + cache_ovly_region_table_base = 0; +} + +/* Read an array of ints of size SIZE from the target into a local buffer. + Convert to host order. LEN is number of ints. */ + +static void +read_target_long_array (CORE_ADDR memaddr, unsigned int *myaddr, + int len, int size, enum bfd_endian byte_order) +{ + /* alloca is safe here, because regions array is very small. */ + gdb_byte *buf = (gdb_byte *) alloca (len * size); + int i; + + read_memory (memaddr, buf, len * size); + for (i = 0; i < len; i++) + myaddr[i] = extract_unsigned_integer (size * i + buf, size, byte_order); +} + +static int +z80_read_overlay_region_table () +{ + struct bound_minimal_symbol novly_regions_msym; + struct bound_minimal_symbol ovly_region_table_msym; + struct gdbarch *gdbarch; + int word_size; + enum bfd_endian byte_order; + + z80_free_overlay_region_table (); + novly_regions_msym = lookup_minimal_symbol ("_novly_regions", NULL, NULL); + if (! novly_regions_msym.minsym) + { + error (_("Error reading inferior's overlay table: " + "couldn't find `_novly_regions'\n" + "variable in inferior. Use `overlay manual' mode.")); + return 0; + } + + ovly_region_table_msym = lookup_bound_minimal_symbol ("_ovly_region_table"); + if (! ovly_region_table_msym.minsym) + { + error (_("Error reading inferior's overlay table: couldn't find " + "`_ovly_region_table'\n" + "array in inferior. Use `overlay manual' mode.")); + return 0; + } + + const enum overlay_debugging_state save_ovly_dbg = overlay_debugging; + /* prevent infinite recurse */ + overlay_debugging = ovly_off; + + gdbarch = ovly_region_table_msym.objfile->arch (); + word_size = gdbarch_long_bit (gdbarch) / TARGET_CHAR_BIT; + byte_order = gdbarch_byte_order (gdbarch); + + cache_novly_regions = read_memory_integer ( + BMSYMBOL_VALUE_ADDRESS (novly_regions_msym), + 4, byte_order); + cache_ovly_region_table + = (unsigned int (*)[3]) xmalloc (cache_novly_regions * + sizeof (*cache_ovly_region_table)); + cache_ovly_region_table_base + = BMSYMBOL_VALUE_ADDRESS (ovly_region_table_msym); + read_target_long_array (cache_ovly_region_table_base, + (unsigned int *) cache_ovly_region_table, + cache_novly_regions * 3, word_size, byte_order); + + overlay_debugging = save_ovly_dbg; + return 1; /* SUCCESS */ +} + +static int +z80_overlay_update_1 (struct obj_section *osect) +{ + int i; + asection *bsect = osect->the_bfd_section; + unsigned lma; + unsigned vma = bfd_section_vma (bsect); + + /* find region corresponding to the section VMA */ + for (i = 0; i < cache_novly_regions; i++) + if (cache_ovly_region_table[i][VMA] == vma) + break; + if (i == cache_novly_regions) + return 0; /* no such region */ + + lma = cache_ovly_region_table[i][MAPPED_TO_LMA]; + i = 0; + + /* we have interest for sections with same VMA */ + for (objfile *objfile : current_program_space->objfiles ()) + ALL_OBJFILE_OSECTIONS (objfile, osect) + if (section_is_overlay (osect)) + { + osect->ovly_mapped = (lma == bfd_section_lma (osect->the_bfd_section)); + i |= osect->ovly_mapped; /* true, if at least one section is mapped */ + } + return i; +} + +/* Refresh overlay mapped state for section OSECT. */ +static void +z80_overlay_update (struct obj_section *osect) +{ + /* Always need to read the entire table anew. */ + if (!z80_read_overlay_region_table ()) + return; + + /* Were we given an osect to look up? NULL means do all of them. */ + if (osect != nullptr && z80_overlay_update_1 (osect)) + return; + + /* Update all sections, even if only one was requested. */ + for (objfile *objfile : current_program_space->objfiles ()) + ALL_OBJFILE_OSECTIONS (objfile, osect) + { + if (!section_is_overlay (osect)) + continue; + + asection *bsect = osect->the_bfd_section; + bfd_vma lma = bfd_section_lma (bsect); + bfd_vma vma = bfd_section_vma (bsect); + + for (int i = 0; i < cache_novly_regions; ++i) + if (cache_ovly_region_table[i][VMA] == vma) + osect->ovly_mapped = + (cache_ovly_region_table[i][MAPPED_TO_LMA] == lma); + } +} + +/* Return non-zero if the instruction at ADDR is a call; zero otherwise. */ +static int +z80_insn_is_call (struct gdbarch *gdbarch, CORE_ADDR addr) +{ + gdb_byte buf[8]; + int size; + const struct insn_info *info; + read_memory (addr, buf, sizeof(buf)); + info = z80_get_insn_info (gdbarch, buf, &size); + if (info) + switch (info->type) + { + case insn_call_nn: + case insn_call_cc_nn: + case insn_rst_n: + return 1; + } + return 0; +} + +/* Return non-zero if the instruction at ADDR is a return; zero otherwise. */ +static int +z80_insn_is_ret (struct gdbarch *gdbarch, CORE_ADDR addr) +{ + gdb_byte buf[8]; + int size; + const struct insn_info *info; + read_memory (addr, buf, sizeof(buf)); + info = z80_get_insn_info (gdbarch, buf, &size); + if (info) + switch (info->type) + { + case insn_ret: + case insn_ret_cc: + return 1; + } + return 0; +} + +/* Return non-zero if the instruction at ADDR is a jump; zero otherwise. */ +static int +z80_insn_is_jump (struct gdbarch *gdbarch, CORE_ADDR addr) +{ + gdb_byte buf[8]; + int size; + const struct insn_info *info; + read_memory (addr, buf, sizeof(buf)); + info = z80_get_insn_info (gdbarch, buf, &size); + if (info) + switch (info->type) + { + case insn_jp_nn: + case insn_jp_cc_nn: + case insn_jp_rr: + case insn_jr_d: + case insn_jr_cc_d: + case insn_djnz_d: + return 1; + } + return 0; +} + +static const struct frame_unwind +z80_frame_unwind = +{ + "z80", + NORMAL_FRAME, + default_frame_unwind_stop_reason, + z80_frame_this_id, + z80_frame_prev_register, + NULL, /*unwind_data*/ + default_frame_sniffer + /*dealloc_cache*/ + /*prev_arch*/ +}; + +/* Initialize the gdbarch struct for the Z80 arch */ +static struct gdbarch * +z80_gdbarch_init (struct gdbarch_info info, struct gdbarch_list *arches) +{ + struct gdbarch *gdbarch; + struct gdbarch_tdep *tdep; + struct gdbarch_list *best_arch; + tdesc_arch_data_up tdesc_data; + unsigned long mach = info.bfd_arch_info->mach; + const struct target_desc *tdesc = info.target_desc; + + if (!tdesc_has_registers (tdesc)) + /* Pick a default target description. */ + tdesc = tdesc_z80; + + /* Check any target description for validity. */ + if (tdesc_has_registers (tdesc)) + { + const struct tdesc_feature *feature; + int valid_p; + + feature = tdesc_find_feature (tdesc, "org.gnu.gdb.z80.cpu"); + if (feature == NULL) + return NULL; + + tdesc_data = tdesc_data_alloc (); + + valid_p = 1; + + for (unsigned i = 0; i < Z80_NUM_REGS; i++) + valid_p &= tdesc_numbered_register (feature, tdesc_data.get (), i, + z80_reg_names[i]); + + if (!valid_p) + return NULL; + } + + /* If there is already a candidate, use it. */ + for (best_arch = gdbarch_list_lookup_by_info (arches, &info); + best_arch != NULL; + best_arch = gdbarch_list_lookup_by_info (best_arch->next, &info)) + { + if (mach == gdbarch_bfd_arch_info (best_arch->gdbarch)->mach) + return best_arch->gdbarch; + } + + /* None found, create a new architecture from the information provided. */ + tdep = XCNEW (struct gdbarch_tdep); + gdbarch = gdbarch_alloc (&info, tdep); + + if (mach == bfd_mach_ez80_adl) + { + tdep->addr_length = 3; + set_gdbarch_max_insn_length (gdbarch, 6); + } + else + { + tdep->addr_length = 2; + set_gdbarch_max_insn_length (gdbarch, 4); + } + + /* Create a type for PC. We can't use builtin types here, as they may not + be defined. */ + tdep->void_type = arch_type (gdbarch, TYPE_CODE_VOID, TARGET_CHAR_BIT, + "void"); + tdep->func_void_type = make_function_type (tdep->void_type, NULL); + tdep->pc_type = arch_pointer_type (gdbarch, + tdep->addr_length * TARGET_CHAR_BIT, + NULL, tdep->func_void_type); + + set_gdbarch_short_bit (gdbarch, TARGET_CHAR_BIT); + set_gdbarch_int_bit (gdbarch, 2 * TARGET_CHAR_BIT); + set_gdbarch_long_bit (gdbarch, 4 * TARGET_CHAR_BIT); + set_gdbarch_ptr_bit (gdbarch, tdep->addr_length * TARGET_CHAR_BIT); + set_gdbarch_addr_bit (gdbarch, tdep->addr_length * TARGET_CHAR_BIT); + + set_gdbarch_num_regs (gdbarch, (mach == bfd_mach_ez80_adl) ? EZ80_NUM_REGS + : Z80_NUM_REGS); + set_gdbarch_sp_regnum (gdbarch, Z80_SP_REGNUM); + set_gdbarch_pc_regnum (gdbarch, Z80_PC_REGNUM); + + set_gdbarch_register_name (gdbarch, z80_register_name); + set_gdbarch_register_type (gdbarch, z80_register_type); + + /* TODO: get FP type from binary (extra flags required) */ + set_gdbarch_float_bit (gdbarch, 4 * TARGET_CHAR_BIT); + set_gdbarch_double_bit (gdbarch, 4 * TARGET_CHAR_BIT); + set_gdbarch_long_double_bit (gdbarch, 4 * TARGET_CHAR_BIT); + set_gdbarch_float_format (gdbarch, floatformats_ieee_single); + set_gdbarch_double_format (gdbarch, floatformats_ieee_single); + set_gdbarch_long_double_format (gdbarch, floatformats_ieee_single); + + set_gdbarch_return_value (gdbarch, z80_return_value); + + set_gdbarch_skip_prologue (gdbarch, z80_skip_prologue); + set_gdbarch_inner_than (gdbarch, core_addr_lessthan); // falling stack + + set_gdbarch_software_single_step (gdbarch, z80_software_single_step); + set_gdbarch_breakpoint_kind_from_pc (gdbarch, z80_breakpoint_kind_from_pc); + set_gdbarch_sw_breakpoint_from_kind (gdbarch, z80_sw_breakpoint_from_kind); + set_gdbarch_insn_is_call (gdbarch, z80_insn_is_call); + set_gdbarch_insn_is_jump (gdbarch, z80_insn_is_jump); + set_gdbarch_insn_is_ret (gdbarch, z80_insn_is_ret); + + set_gdbarch_overlay_update (gdbarch, z80_overlay_update); + + frame_unwind_append_unwinder (gdbarch, &z80_frame_unwind); + if (tdesc_data) + tdesc_use_registers (gdbarch, tdesc, std::move (tdesc_data)); + + return gdbarch; +} + +/* Table to disassemble machine codes without prefix. */ +static const struct insn_info +ez80_main_insn_table[] = +{ /* table with double prefix check */ + { 0100, 0377, 0, insn_force_nop}, //double prefix + { 0111, 0377, 0, insn_force_nop}, //double prefix + { 0122, 0377, 0, insn_force_nop}, //double prefix + { 0133, 0377, 0, insn_force_nop}, //double prefix + /* initial table for eZ80_z80 */ + { 0100, 0377, 1, insn_z80 }, //eZ80 mode prefix + { 0111, 0377, 1, insn_z80 }, //eZ80 mode prefix + { 0122, 0377, 1, insn_adl }, //eZ80 mode prefix + { 0133, 0377, 1, insn_adl }, //eZ80 mode prefix + /* here common Z80/Z180/eZ80 opcodes */ + { 0000, 0367, 1, insn_default }, //"nop", "ex af,af'" + { 0061, 0377, 3, insn_ld_sp_nn }, //"ld sp,nn" + { 0001, 0317, 3, insn_default }, //"ld rr,nn" + { 0002, 0347, 1, insn_default }, //"ld (rr),a", "ld a,(rr)" + { 0042, 0347, 3, insn_default }, //"ld (nn),hl/a", "ld hl/a,(nn)" + { 0063, 0377, 1, insn_inc_sp }, //"inc sp" + { 0073, 0377, 1, insn_dec_sp }, //"dec sp" + { 0003, 0303, 1, insn_default }, //"inc rr", "dec rr", ... + { 0004, 0307, 1, insn_default }, //"inc/dec r/(hl)" + { 0006, 0307, 2, insn_default }, //"ld r,n", "ld (hl),n" + { 0020, 0377, 2, insn_djnz_d }, //"djnz dis" + { 0030, 0377, 2, insn_jr_d }, //"jr dis" + { 0040, 0347, 2, insn_jr_cc_d }, //"jr cc,dis" + { 0100, 0300, 1, insn_default }, //"ld r,r", "halt" + { 0200, 0300, 1, insn_default }, //"alu_op a,r" + { 0300, 0307, 1, insn_ret_cc }, //"ret cc" + { 0301, 0317, 1, insn_pop_rr }, //"pop rr" + { 0302, 0307, 3, insn_jp_cc_nn }, //"jp cc,nn" + { 0303, 0377, 3, insn_jp_nn }, //"jp nn" + { 0304, 0307, 3, insn_call_cc_nn}, //"call cc,nn" + { 0305, 0317, 1, insn_push_rr }, //"push rr" + { 0306, 0307, 2, insn_default }, //"alu_op a,n" + { 0307, 0307, 1, insn_rst_n }, //"rst n" + { 0311, 0377, 1, insn_ret }, //"ret" + { 0313, 0377, 2, insn_default }, //CB prefix + { 0315, 0377, 3, insn_call_nn }, //"call nn" + { 0323, 0367, 2, insn_default }, //"out (n),a", "in a,(n)" + { 0335, 0337, 1, insn_z80_ddfd }, //DD/FD prefix + { 0351, 0377, 1, insn_jp_rr }, //"jp (hl)" + { 0355, 0377, 1, insn_z80_ed }, //ED prefix + { 0371, 0377, 1, insn_ld_sp_rr }, //"ld sp,hl" + { 0000, 0000, 1, insn_default } //others +} ; + +static const struct insn_info +ez80_adl_main_insn_table[] = +{ /* table with double prefix check */ + { 0100, 0377, 0, insn_force_nop}, //double prefix + { 0111, 0377, 0, insn_force_nop}, //double prefix + { 0122, 0377, 0, insn_force_nop}, //double prefix + { 0133, 0377, 0, insn_force_nop}, //double prefix + /* initial table for eZ80_adl */ + { 0000, 0367, 1, insn_default }, //"nop", "ex af,af'" + { 0061, 0377, 4, insn_ld_sp_nn }, //"ld sp,Mmn" + { 0001, 0317, 4, insn_default }, //"ld rr,Mmn" + { 0002, 0347, 1, insn_default }, //"ld (rr),a", "ld a,(rr)" + { 0042, 0347, 4, insn_default }, //"ld (Mmn),hl/a", "ld hl/a,(Mmn)" + { 0063, 0377, 1, insn_inc_sp }, //"inc sp" + { 0073, 0377, 1, insn_dec_sp }, //"dec sp" + { 0003, 0303, 1, insn_default }, //"inc rr", "dec rr", ... + { 0004, 0307, 1, insn_default }, //"inc/dec r/(hl)" + { 0006, 0307, 2, insn_default }, //"ld r,n", "ld (hl),n" + { 0020, 0377, 2, insn_djnz_d }, //"djnz dis" + { 0030, 0377, 2, insn_jr_d }, //"jr dis" + { 0040, 0347, 2, insn_jr_cc_d }, //"jr cc,dis" + { 0100, 0377, 1, insn_z80 }, //eZ80 mode prefix (short instruction) + { 0111, 0377, 1, insn_z80 }, //eZ80 mode prefix (short instruction) + { 0122, 0377, 1, insn_adl }, //eZ80 mode prefix (long instruction) + { 0133, 0377, 1, insn_adl }, //eZ80 mode prefix (long instruction) + { 0100, 0300, 1, insn_default }, //"ld r,r", "halt" + { 0200, 0300, 1, insn_default }, //"alu_op a,r" + { 0300, 0307, 1, insn_ret_cc }, //"ret cc" + { 0301, 0317, 1, insn_pop_rr }, //"pop rr" + { 0302, 0307, 4, insn_jp_cc_nn }, //"jp cc,nn" + { 0303, 0377, 4, insn_jp_nn }, //"jp nn" + { 0304, 0307, 4, insn_call_cc_nn}, //"call cc,Mmn" + { 0305, 0317, 1, insn_push_rr }, //"push rr" + { 0306, 0307, 2, insn_default }, //"alu_op a,n" + { 0307, 0307, 1, insn_rst_n }, //"rst n" + { 0311, 0377, 1, insn_ret }, //"ret" + { 0313, 0377, 2, insn_default }, //CB prefix + { 0315, 0377, 4, insn_call_nn }, //"call Mmn" + { 0323, 0367, 2, insn_default }, //"out (n),a", "in a,(n)" + { 0335, 0337, 1, insn_adl_ddfd }, //DD/FD prefix + { 0351, 0377, 1, insn_jp_rr }, //"jp (hl)" + { 0355, 0377, 1, insn_adl_ed }, //ED prefix + { 0371, 0377, 1, insn_ld_sp_rr }, //"ld sp,hl" + { 0000, 0000, 1, insn_default } //others +}; + +/* ED prefix opcodes table. + Note the instruction length does include the ED prefix (+ 1 byte) +*/ +static const struct insn_info +ez80_ed_insn_table[] = +{ + /* eZ80 only instructions */ + { 0002, 0366, 2, insn_default }, //"lea rr,ii+d" + { 0124, 0376, 2, insn_default }, //"lea ix,iy+d", "lea iy,ix+d" + { 0145, 0377, 2, insn_default }, //"pea ix+d" + { 0146, 0377, 2, insn_default }, //"pea iy+d" + { 0164, 0377, 2, insn_default }, //"tstio n" + /* Z180/eZ80 only instructions */ + { 0060, 0376, 1, insn_default }, //not an instruction + { 0000, 0306, 2, insn_default }, //"in0 r,(n)", "out0 (n),r" + { 0144, 0377, 2, insn_default }, //"tst a, n" + /* common instructions */ + { 0173, 0377, 3, insn_ld_sp_6nn9 }, //"ld sp,(nn)" + { 0103, 0307, 3, insn_default }, //"ld (nn),rr", "ld rr,(nn)" + { 0105, 0317, 1, insn_ret }, //"retn", "reti" + { 0000, 0000, 1, insn_default } +}; + +static const struct insn_info +ez80_adl_ed_insn_table[] = +{ + { 0002, 0366, 2, insn_default }, //"lea rr,ii+d" + { 0124, 0376, 2, insn_default }, //"lea ix,iy+d", "lea iy,ix+d" + { 0145, 0377, 2, insn_default }, //"pea ix+d" + { 0146, 0377, 2, insn_default }, //"pea iy+d" + { 0164, 0377, 2, insn_default }, //"tstio n" + { 0060, 0376, 1, insn_default }, //not an instruction + { 0000, 0306, 2, insn_default }, //"in0 r,(n)", "out0 (n),r" + { 0144, 0377, 2, insn_default }, //"tst a, n" + { 0173, 0377, 4, insn_ld_sp_6nn9 }, //"ld sp,(nn)" + { 0103, 0307, 4, insn_default }, //"ld (nn),rr", "ld rr,(nn)" + { 0105, 0317, 1, insn_ret }, //"retn", "reti" + { 0000, 0000, 1, insn_default } +}; + +/* table for FD and DD prefixed instructions */ +static const struct insn_info +ez80_ddfd_insn_table[] = +{ + /* ez80 only instructions */ + { 0007, 0307, 2, insn_default }, //"ld rr,(ii+d)" + { 0061, 0377, 2, insn_default }, //"ld ii,(ii+d)" + /* common instructions */ + { 0011, 0367, 2, insn_default }, //"add ii,rr" + { 0041, 0377, 3, insn_default }, //"ld ii,nn" + { 0042, 0367, 3, insn_default }, //"ld (nn),ii", "ld ii,(nn)" + { 0043, 0367, 1, insn_default }, //"inc ii", "dec ii" + { 0044, 0366, 1, insn_default }, //"inc/dec iih/iil" + { 0046, 0367, 2, insn_default }, //"ld iih,n", "ld iil,n" + { 0064, 0376, 2, insn_default }, //"inc (ii+d)", "dec (ii+d)" + { 0066, 0377, 2, insn_default }, //"ld (ii+d),n" + { 0166, 0377, 0, insn_default }, //not an instruction + { 0160, 0370, 2, insn_default }, //"ld (ii+d),r" + { 0104, 0306, 1, insn_default }, //"ld r,iih", "ld r,iil" + { 0106, 0307, 2, insn_default }, //"ld r,(ii+d)" + { 0140, 0360, 1, insn_default }, //"ld iih,r", "ld iil,r" + { 0204, 0306, 1, insn_default }, //"alu_op a,iih", "alu_op a,iil" + { 0206, 0307, 2, insn_default }, //"alu_op a,(ii+d)" + { 0313, 0377, 3, insn_default }, //DD/FD CB dd oo instructions + { 0335, 0337, 0, insn_force_nop}, //double DD/FD prefix, exec DD/FD as NOP + { 0341, 0373, 1, insn_default }, //"pop ii", "push ii" + { 0343, 0377, 1, insn_default }, //"ex (sp),ii" + { 0351, 0377, 1, insn_jp_rr }, //"jp (ii)" + { 0371, 0377, 1, insn_ld_sp_rr}, //"ld sp,ii" + { 0000, 0000, 0, insn_default } //not an instruction, exec DD/FD as NOP +}; + +static const struct insn_info +ez80_adl_ddfd_insn_table[] = +{ + { 0007, 0307, 2, insn_default }, //"ld rr,(ii+d)" + { 0061, 0377, 2, insn_default }, //"ld ii,(ii+d)" + { 0011, 0367, 1, insn_default }, //"add ii,rr" + { 0041, 0377, 4, insn_default }, //"ld ii,nn" + { 0042, 0367, 4, insn_default }, //"ld (nn),ii", "ld ii,(nn)" + { 0043, 0367, 1, insn_default }, //"inc ii", "dec ii" + { 0044, 0366, 1, insn_default }, //"inc/dec iih/iil" + { 0046, 0367, 2, insn_default }, //"ld iih,n", "ld iil,n" + { 0064, 0376, 2, insn_default }, //"inc (ii+d)", "dec (ii+d)" + { 0066, 0377, 3, insn_default }, //"ld (ii+d),n" + { 0166, 0377, 0, insn_default }, //not an instruction + { 0160, 0370, 2, insn_default }, //"ld (ii+d),r" + { 0104, 0306, 1, insn_default }, //"ld r,iih", "ld r,iil" + { 0106, 0307, 2, insn_default }, //"ld r,(ii+d)" + { 0140, 0360, 1, insn_default }, //"ld iih,r", "ld iil,r" + { 0204, 0306, 1, insn_default }, //"alu_op a,iih", "alu_op a,iil" + { 0206, 0307, 2, insn_default }, //"alu_op a,(ii+d)" + { 0313, 0377, 3, insn_default }, //DD/FD CB dd oo instructions + { 0335, 0337, 0, insn_force_nop}, //double DD/FD prefix, exec DD/FD as NOP + { 0341, 0373, 1, insn_default }, //"pop ii", "push ii" + { 0343, 0377, 1, insn_default }, //"ex (sp),ii" + { 0351, 0377, 1, insn_jp_rr }, //"jp (ii)" + { 0371, 0377, 1, insn_ld_sp_rr}, //"ld sp,ii" + { 0000, 0000, 0, insn_default } //not an instruction, exec DD/FD as NOP +}; + +/* Return pointer to instruction information structure corresponded to opcode + in buf. */ +static const struct insn_info * +z80_get_insn_info (struct gdbarch *gdbarch, const gdb_byte *buf, int *size) +{ + int code; + const struct insn_info *info; + unsigned long mach = gdbarch_bfd_arch_info (gdbarch)->mach; + *size = 0; + switch (mach) + { + case bfd_mach_ez80_z80: + info = &ez80_main_insn_table[4]; /* skip force_nops */ + break; + case bfd_mach_ez80_adl: + info = &ez80_adl_main_insn_table[4]; /* skip force_nops */ + break; + default: + info = &ez80_main_insn_table[8]; /* skip eZ80 prefices and force_nops */ + break; + } + do + { + for (; ((code = buf[*size]) & info->mask) != info->code; ++info) + ; + *size += info->size; + /* process instruction type */ + switch (info->type) + { + case insn_z80: + if (mach == bfd_mach_ez80_z80 || mach == bfd_mach_ez80_adl) + info = &ez80_main_insn_table[0]; + else + info = &ez80_main_insn_table[8]; + break; + case insn_adl: + info = &ez80_adl_main_insn_table[0]; + break; + /* These two (for GameBoy Z80 & Z80 Next CPUs) haven't been tested. + + case bfd_mach_gbz80: + info = &gbz80_main_insn_table[0]; + break; + case bfd_mach_z80n: + info = &z80n_main_insn_table[0]; + break; + */ + case insn_z80_ddfd: + if (mach == bfd_mach_ez80_z80 || mach == bfd_mach_ez80_adl) + info = &ez80_ddfd_insn_table[0]; + else + info = &ez80_ddfd_insn_table[2]; + break; + case insn_adl_ddfd: + info = &ez80_adl_ddfd_insn_table[0]; + break; + case insn_z80_ed: + info = &ez80_ed_insn_table[0]; + break; + case insn_adl_ed: + info = &ez80_adl_ed_insn_table[0]; + break; + case insn_force_nop: + return NULL; + default: + return info; + } + } + while (1); +} + +extern initialize_file_ftype _initialize_z80_tdep; + +void +_initialize_z80_tdep () +{ + register_gdbarch_init (bfd_arch_z80, z80_gdbarch_init); + initialize_tdesc_z80 (); +} diff --git a/gdb/z80-tdep.h b/gdb/z80-tdep.h new file mode 100644 index 00000000000..4d73aeb17fa --- /dev/null +++ b/gdb/z80-tdep.h @@ -0,0 +1,52 @@ +/* Target-dependent code for the Z80. + + Copyright (C) 2002-2021 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 . */ + +#ifndef Z80_TDEP_H +#define Z80_TDEP_H + +/* Register pair constants + Order optimized for gdb-stub implementation + Most of register pairs are 16 bit length on Z80 and + 24 bit on eZ80 in ADL or MADL modes */ +enum z80_regnum +{ + Z80_AF_REGNUM, + Z80_BC_REGNUM, + Z80_DE_REGNUM, + Z80_HL_REGNUM, + Z80_SP_REGNUM, /* SPL on eZ80 CPU */ + Z80_PC_REGNUM, + Z80_IX_REGNUM, + Z80_IY_REGNUM, + Z80_AFA_REGNUM, + Z80_BCA_REGNUM, + Z80_DEA_REGNUM, + Z80_HLA_REGNUM, + Z80_IR_REGNUM, +/* eZ80 only registers */ + Z80_SPS_REGNUM /* SPS register of eZ80 CPU */ +}; + +#define Z80_NUM_REGS 13 +#define Z80_REG_BYTES (Z80_NUM_REGS*2) + +#define EZ80_NUM_REGS (Z80_NUM_REGS + 1) +#define EZ80_REG_BYTES (EZ80_NUM_REGS*3) + +#endif /* z80-tdep.h */