/* * Copyright (c) 2015 Wind River Systems, Inc. * * SPDX-License-Identifier: Apache-2.0 */ /** * @file Generic part of the GDB server * * This module provides the embedded GDB Remote Serial Protocol for Zephyr. * * The following is a list of all currently defined GDB RSP commands. * * `?' * Indicate the reason the target halted. * * `c [addr]' * Continue. addr is address to resume. If addr is omitted, resume at * current address. * * `C sig[;addr]' * Continue with signal sig (hex signal number). If `;addr' is omitted, * resume at same address. * * _WRS_XXX - Current limitation: Even if this syntax is understood, the * GDB server does not resume the context with the specified signal but * resumes it with the exception vector that caused the context to stop. * * `D' * Detach GDB from the remote system. * * `g' * Read general registers. * * `G XX...' * Write general registers. * * `k' * Detach GDB from the remote system. * * `m addr,length' * Read length bytes of memory starting at address addr. Note that addr * may not be aligned to any particular boundary. The stub need not use * any particular size or alignment when gathering data from memory for * the response; even if addr is word-aligned and length is a multiple of * the word size, the stub is free to use byte accesses, or not. For this * reason, this packet may not be suitable for accessing memory-mapped I/O * devices. * * `M addr,length:XX...' * Write length bytes of memory starting at address addr. XX... is the data. * Each byte is transmitted as a two-digit hexadecimal number. * * `p n' * Read the value of register n; n is in hex. * * `P n...=r...' * Write register n... with value r.... The register number n is in * hexadecimal, and r... contains two hex digits for each byte in the * register (target byte order). * * `q name params...' * General query. See General Query Packets description * * `s [addr]' * Single step. addr is the address at which to resume. If addr is omitted, * resume at same address. * * `S sig[;addr]' * Step with signal. This is analogous to the `C' packet, but requests a * single-step, rather than a normal resumption of execution. * * NOTE: Current limitation: Even if this syntax is understood, the GDB * server steps the context without the specified signal (i.e like the * `s [addr]' command). * * `T thread-id' * Find out if the thread thread-id is alive. * * `vCont[;action[:thread-id]]...' * Resume the inferior, specifying different actions for each thread. If an * action is specified with no thread-id, then it is applied to any threads * that don't have a specific action specified; if no default action is * specified then other threads should remain stopped. Specifying multiple * default actions is an error; specifying no actions is also an error. * * Currently supported actions are: * * `c' * Continue. * `C sig' * Continue with signal sig. The signal sig should be two hex digits. * `s' * Step. * `S sig' * Step with signal sig. The signal sig should be two hex digits. * * The optional argument addr normally associated with the `c', `C', `s', * and `S' packets is not supported in `vCont'. * * `X addr,length:XX...' * Write length bytes of memory starting at address addr, where the data * is transmitted in binary. The binary data representation uses 7d (ascii * ‘}’) as an escape character. Any escaped byte is transmitted as the * escape character followed by the original character XORed with 0x20. * * `z type,addr,length' * `Z type,addr,length' * Insert (`Z') or remove (`z') a type breakpoint starting at addr address * of length length. * * General Query Packets: * `qC' * Return the current thread ID. * * `qSupported' * Query the GDB server for features it supports. This packet allows * client to take advantage of GDB server's features. * * These are the currently defined GDB server features, in more detail: * * `PacketSize=bytes' * The GDB server can accept packets up to at least bytes in length. * client will send packets up to this size for bulk transfers, and * will never send larger packets. This is a limit on the data * characters in the packet, including the frame and checksum. There * is no trailing NUL byte in a remote protocol packet; * * `qXfer:features:read' * Access the target description. Target description can identify the * architecture of the remote target and (for some architectures) * provide information about custom register sets. They can also * identify the OS ABI of the remote target. Client can use this * information to autoconfigure for your target, or to warn you if you * connect to an unsupported target. * * By default, the following simple target description is supported: * * * i386 * * * But architectures may also reports information on specific features * such as extended registers definitions or hardware breakpoint * definitions. * * Each `' describes some logical portion of the target * system. * A `' element has this form: * * * [TYPE...] * REG... * * * Each feature's name should be unique within the description. The * name of a feature does not matter unless GDB has some special * knowledge of the contents of that feature; if it does, the feature * should have its standard name. * * Extended registers definitions are reported following the standard * register format defined by GDB Remote protocol: * * Each register is represented as an element with this form: * * * * The components are as follows: * * NAME * The register's name; it must be unique within the target * description. * * BITSIZE * The register's size, in bits. * * REGNUM * The register's number. If omitted, a register's number is one * greater than that of the previous register (either in the * current feature or in a preceding feature); the first register * in the target description defaults to zero. This register * number is used to read or write the register; e.g. it is used * in the remote `p' and `P' packets, and registers appear in the * `g' and `G' packets in order of increasing register number. * * SAVE-RESTORE * Whether the register should be preserved across inferior * function calls; this must be either `yes' or `no'. The default * is `yes', which is appropriate for most registers except for * some system control registers; this is not related to the * target's ABI. * * TYPE * The type of the register. TYPE may be a predefined type, a * type defined in the current feature, or one of the special * types `int' and `float'. `int' is an integer type of the * correct size for BITSIZE, and `float' is a floating point type * (in the architecture's normal floating point format) of the * correct size for BITSIZE. The default is `int'. * * GROUP * The register group to which this register belongs. GROUP must * be either `general', `float', or `vector'. If no GROUP is * specified, GDB will not display the register in `info * registers'. * * * Hardware breakpoint definitions are reported using the following * format: * * * * HW_BP_DESC... * * * The defaults section allows to define some default values and avoid * to list them in each HW_BP_DESC. * * Each HW_BP_DESC entry has the form: * * * * If HW_BP_DESC defines an item which has a default value defined, * then it overwrite the default value for HW_BP_DESC entry. * * Items in [brackets] are optional. The components are as follows: * * MAX_BP * Maximum number of hardware breakpoints that can be set. * * MAX_INST_BP * Maximum number of instruction hardware breakpoints that can be * set. * * MAX_WATCH_BP * Maximum number of data hardware breakpoints that can be set. * * LENGTH * Supported access lengths (in hexadecimal without 0x prefix). * Access lengths are encoded as powers of 2 which can be OR'ed. * For example, if an hardware breakpoint type supports 1, 2, 4, * 8 bytes access, length will be f (0x1|0x2|0x4|0x8). * * ACCESS_TYPE * Hardware breakpoint type: * inst : Instruction breakpoint * watch : Write access breakpoint * rwatch: Read access breakpoint * awatch: Read|Write access breakpoint * * The GDB server can also reports additional information using the * "WR_AGENT_FEATURE" feature. The purpose of this feature is to report * information about the agent configuration. * The GDB server feature is using the following format: * * * * * * The components are as follows: * * MAX_SW_BP * Maximum number of software breakpoint that can be set. * * STEP_ONLY_ON_BP * This parameter is set to 1 if the GDB server is only able to * step the context which hit the breakpoint. * This parameter is set to 0 if the GDB server is able to step * any context. * * `QStartNoAckMode' * By default, when either the client or the server sends a packet, * the first response expected is an acknowledgment: either `+' (to * indicate the package was received correctly) or `-' (to request * retransmission). This mechanism allows the GDB remote protocol to * operate over unreliable transport mechanisms, such as a serial * line. * * In cases where the transport mechanism is itself reliable (such as * a pipe or TCP connection), the `+'/`-' acknowledgments are * redundant. It may be desirable to disable them in that case to * reduce communication overhead, or for other reasons. This can be * accomplished by means of the `QStartNoAckMode' packet. * * `CONFIG_GDB_REMOTE_SERIAL_EXT_NOTIF_PREFIX_STR` * This parameter indicates that the GDB server supports transfer of * Zephyr console I/O to the client using GDB notification packets. * * NOTE: Current limitation: For now, the GDB server only supports the * console output. * * Notification Packets: * Extension of the GDB Remote Serial Protocol uses notification packets * (See `CONFIG_GDB_REMOTE_SERIAL_EXT_NOTIF_PREFIX_STR` support). * Those packets are transferred using the following format: * %# * * For example: * %CONFIG_GDB_REMOTE_SERIAL_EXT_NOTIF_PREFIX_STR:# */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_GDB_SERVER_INTERRUPT_DRIVEN #include #endif #ifdef CONFIG_REBOOT #include #endif #define STUB_OK "OK" #define STUB_ERROR "ENN" /* Size of notification data buffers */ #ifndef GDB_NOTIF_DATA_SIZE #define GDB_NOTIF_DATA_SIZE 100 #endif /* Overhead size for notification packet encoding. */ #define NOTIF_PACKET_OVERHEAD 6 /* Maximum number of software breakpoints */ #define MAX_SW_BP CONFIG_GDB_SERVER_MAX_SW_BP #define GDB_INVALID_REG_SET ((void *)-1) #define fill_output_buffer(x) strncpy((char *)gdb_buffer, x, GDB_BUF_SIZE - 1) #ifdef CONFIG_GDB_SERVER_BOOTLOADER #define STR_TYPE ";type=zephyr_boot" #else #define STR_TYPE ";type=zephyr" #endif #ifdef GDB_ARCH_HAS_RUNCONTROL #define RESUME_SYSTEM() resume_system() #define REMOVE_ALL_INSTALLED_BREAKPOINTS() \ remove_all_installed_breakpoints() #define UNINSTALL_BREAKPOINTS() uninstall_breakpoints() #else #define RESUME_SYSTEM() #define REMOVE_ALL_INSTALLED_BREAKPOINTS() #define UNINSTALL_BREAKPOINTS() #endif #ifdef GDB_ARCH_HAS_RUNCONTROL struct bp_array { gdb_instr_t *addr; /* breakpoint address */ gdb_instr_t instr; /* saved instruction */ char valid; /* breakpoint is valid? */ char enabled; /* breakpoint is enabled? */ }; #endif static const unsigned char hex_chars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; static int client_is_connected; static int in_no_ack_mode; static int valid_registers; static volatile int event_is_pending; static volatile int cpu_stop_signal = GDB_SIG_NULL; static volatile int cpu_pending_signal; static struct gdb_reg_set gdb_regs; static const char *xml_target_header = " " " \n"; static const char *xml_target_footer = ""; static unsigned char gdb_buffer[GDB_BUF_SIZE]; static unsigned char tmp_reg_buffer[GDB_NUM_REG_BYTES]; #ifdef GDB_ARCH_HAS_RUNCONTROL #ifdef GDB_ARCH_HAS_HW_BP static int hw_bp_cnt; static struct gdb_debug_regs dbg_regs; #endif static int trace_lock_key; /* * GDB breakpoint table. Note that all the valid entries in the breakpoint * table are kept contiguous. When parsing the table, the first invalid entry * in the table marks the end of the table. */ static struct bp_array bp_array[MAX_SW_BP]; #ifdef GDB_ARCH_NO_SINGLE_STEP static gdb_instr_t *gdb_step_emu_next_pc; static gdb_instr_t gdb_step_emu_instr; #endif #endif #ifdef GDB_ARCH_HAS_REMOTE_SERIAL_EXT_USING_NOTIF_PACKETS static volatile int notif_pkt_pending; static volatile int notif_data_idx; static unsigned char notif_data[GDB_NOTIF_DATA_SIZE]; #endif #ifdef CONFIG_GDB_SERVER_INTERRUPT_DRIVEN static struct k_fifo avail_queue; static struct k_fifo cmds_queue; #endif static struct device *uart_console_dev; /* global definitions */ volatile int gdb_debug_status = NOT_DEBUGGING; #ifdef GDB_ARCH_HAS_RUNCONTROL #ifdef GDB_ARCH_HAS_HW_BP volatile int cpu_stop_bp_type = GDB_SOFT_BP; long cpu_stop_hw_bp_addr; #endif #endif static int put_packet(unsigned char *buffer); static void put_debug_char(unsigned char ch); static unsigned char get_debug_char(void); static void post_event(void); static void control_loop(void); static void handle_system_stop(NANO_ISF *reg, int sig); #ifdef GDB_ARCH_HAS_RUNCONTROL #define ADD_DEL_BP_SIG(x) \ int(x)(enum gdb_bp_type type, long addr, int len, \ enum gdb_error_code *err) typedef ADD_DEL_BP_SIG (add_del_bp_t); static ADD_DEL_BP_SIG(add_bp); static ADD_DEL_BP_SIG(delete_bp); static void resume_system(void); static int set_instruction(void *addr, gdb_instr_t *instruction, size_t size); static void remove_all_installed_breakpoints(void); #endif #ifdef GDB_ARCH_HAS_REMOTE_SERIAL_EXT_USING_NOTIF_PACKETS static void handle_notification(void); static void request_notification_packet_flush(void); static uint32_t write_to_console(char *buf, uint32_t len); #endif #ifdef CONFIG_GDB_SERVER_INTERRUPT_DRIVEN static int console_irq_input_hook(uint8_t ch); #endif static int get_hex_char_value(unsigned char ch) { if ((ch >= 'a') && (ch <= 'f')) { return ch - 'a' + 10; } if ((ch >= '0') && (ch <= '9')) { return ch - '0'; } if ((ch >= 'A') && (ch <= 'F')) { return ch - 'A' + 10; } return -1; } /** * @brief Consume a hex number from a string and convert to long long number * * Consume the string up to the end of the hex number, i.e. the pointer to the * string is advanced and output that number via the @a value parameter. * * Does not handle negative numbers. * * @return The number of characters consumed from the string. */ static int hex_str_to_longlong(unsigned char **hex_str, long long *value) { int num_chars_consumed = 0; int hex_value; *value = 0; while (**hex_str) { hex_value = get_hex_char_value(**hex_str); if (hex_value < 0) { break; } *value = (*value << 4) | hex_value; num_chars_consumed++; (*hex_str)++; } return num_chars_consumed; } /* * Similar to hex_str_to_longlong, but outputs an int and handles negative * numbers. */ static int hex_str_to_int(unsigned char **ptr, int *value) { int num_chars_consumed = 0; int hex_value; int neg = 0; *value = 0; if (**ptr == '-') { neg = 1; (*ptr)++; num_chars_consumed++; } while (**ptr) { hex_value = get_hex_char_value(**ptr); if (hex_value < 0) { break; } *value = (*value << 4) | hex_value; num_chars_consumed++; (*ptr)++; } if (neg) { if (num_chars_consumed == 1) { (*ptr)--; num_chars_consumed = 0; } else { *value = -(*value); } } return num_chars_consumed; } /* * Consume two hex characters from a string and return the corresponding * value. */ static int hex_str_to_byte(unsigned char **str) { unsigned char *ptr = *str; int byte; byte = get_hex_char_value(*ptr++); byte = (byte << 4) + get_hex_char_value(*ptr++); if (byte >= 0) { *str = ptr; } return byte; } /* * Turn a one-byte value into two hex characters and write them to the buffer. * Return the next position in the buffer. */ static unsigned char *put_hex_byte(unsigned char *buf, int value) { *buf++ = hex_chars[value >> 4]; *buf++ = hex_chars[value & 0xf]; return buf; } static inline int is_size_encoder(int num) { /* * It is not possible to use the '$', '#' and '%' characters to encode * the size per GDB remote protocol specification. */ return (num + 29) != '$' && (num + 29) != '#' && (num + 29) != '%'; } /* * Compress memory pointed to by buffer into its ascii hex value, the * character '*' and the number of times it is reapeated, placing result in * the same buffer. Return a pointer to the last char put in buf (null). */ static unsigned char *compress(unsigned char *buf) { unsigned char *read_ptr = buf; unsigned char *write_ptr = buf; unsigned char ch; size_t count = strlen((char *)buf); int max_repeat = 126 - 29; size_t ix; for (ix = 0; ix < count; ix++) { int num = 0; int jx; ch = *read_ptr++; *write_ptr++ = ch; for (jx = 1; ((jx + ix) < count) && (jx < max_repeat); jx++) { if (read_ptr[jx - 1] == ch) { num++; } else { break; } } if (num >= 3) { /* * Skip characters that cannot be used as size * encoders. */ while (!is_size_encoder(num)) { num--; } *write_ptr++ = '*'; *write_ptr++ = (unsigned char)(num + 29); read_ptr += num; ix += num; } } *write_ptr = 0; return write_ptr; } /** * @brief encode memory data using hexadecimal value of chars from '0' to 'f' * * For example, 0x3 (CTRL+C) will be encoded with hexadecimal values of * '0' (0x30) and '3' (0x33): 0x3033. * * Use mem2hex() to encode a buffer avoid to send control chars that could * perturbate communication protocol. * * @param data to encode * @param output buffer * @param size of data to encode * @param Compress output data ? * * @return Pointer to the last char put in buf (null). */ static unsigned char *mem2hex(unsigned char *mem, unsigned char *buf, int count, int do_compress) { int i; unsigned char ch; unsigned char *saved_buf = buf; for (i = 0; i < count; i++) { ch = *mem++; buf = put_hex_byte(buf, ch); } *buf = 0; if (do_compress) { return compress(saved_buf); } return buf; } static inline int do_mem_probe(char *addr, int mode, int width, int preserve, long *dummy) { char *p = (char *)dummy; if (preserve) { if (_mem_probe(addr, SYS_MEM_SAFE_READ, width, p) < 0) { return -1; } } if (_mem_probe(addr, mode, width, p) < 0) { return -1; } return 0; } /** * @brief Probe if memory location is valid * * @param addr Address to test * @param mode Mode of access (SYS_MEM_SAFE_READ/WRITE) * @param size Number of bytes to test * @param width Width of memory access (1, 2, or 4) * @param preserve Preserve memory on write test ? * * @return 0 if memory is accessible, -1 otherwise. */ static int mem_probe(char *addr, int mode, int size, int width, int preserve) { long dummy; /* if memory length is zero, test is done */ if (size == 0) { return 0; } /* Validate parameters */ preserve = mode == SYS_MEM_SAFE_READ ? 0 : preserve; if (width == 0) { width = 1; } else { if ((width != 1) && (width != 2) && (width != 4)) { return -1; } } /* Check addr, size & width parameters coherency */ if (((unsigned long)addr % width) || (size % width)) { return -1; } /* Check first address */ if (do_mem_probe(addr, mode, width, preserve, &dummy) < 0) { return -1; } /* Check if we have tested the whole memory */ if (width == size) { return 0; } /* Check last address */ addr = addr + size - width; if (do_mem_probe(addr, mode, width, preserve, &dummy) < 0) { return -1; } return 0; } static int put_packet(unsigned char *buffer) { unsigned char checksum = 0; int count = 0; unsigned char ch; /* $#. */ do { put_debug_char('$'); checksum = 0; count = 0; /* Buffer terminated with null character */ while ((ch = buffer[count])) { put_debug_char(ch); checksum = (unsigned char)(checksum + ch); count += 1; } put_debug_char('#'); put_debug_char(hex_chars[(checksum >> 4)]); put_debug_char(hex_chars[(checksum & 0xf)]); if (!in_no_ack_mode) { /* Wait for ack */ ch = get_debug_char(); if (ch == '+') { return 0; } if (ch == '$') { put_debug_char('-'); return 0; } if (ch == GDB_STOP_CHAR) { cpu_stop_signal = GDB_SIG_INT; gdb_debug_status = DEBUGGING; post_event(); return 0; } } else { return 0; } } while (1); } #ifdef GDB_ARCH_HAS_REMOTE_SERIAL_EXT_USING_NOTIF_PACKETS /* * Request a flush of pending notification packets. This is done by setting * notif_pkt_pending to 1 before stopping the CPU. Once stopped, the * control loop will send pending packets before resuming the system. */ static void request_notification_packet_flush(void) { /* * Before stopping CPU we must indicate that we're stopping the system * to handle a packet notification. During the packet notification, we * should prevent CPU from reading protocol... */ if (gdb_debug_status != NOT_DEBUGGING) { return; } notif_pkt_pending = 1; gdb_debug_status = DEBUGGING; control_loop(); gdb_debug_status = NOT_DEBUGGING; RESUME_SYSTEM(); } /* * If the notification buffer for current CPU is full, or if we found a new * line or carriage return character, then we must flush received data to * remote client. */ static inline int must_flush_notification_buffer(unsigned char ch) { return (notif_data_idx == GDB_NOTIF_DATA_SIZE) || (ch == '\n') || (ch == '\r'); } /* * Write data to debug agent console. For performance reason, the data is * bufferized until we receive a carriage return character or until the buffer * gets full. * * The buffer is also automatically flushed when system is stopped. */ static uint32_t write_to_console(char *buf, uint32_t len) { uint32_t ix; unsigned char ch; int key = irq_lock(); /* Copy data to notification buffer for current CPU */ for (ix = 0; ix < len; ix++) { ch = buf[ix]; notif_data[notif_data_idx++] = ch; if (must_flush_notification_buffer(ch)) { notif_data[notif_data_idx] = '\0'; request_notification_packet_flush(); } } irq_unlock(key); return len; } /* * Handle pending notification packets for the CPU. It is invoked while * running in GDB CPU control loop (When system is stopped). */ static void handle_notification(void) { const char *name = CONFIG_GDB_REMOTE_SERIAL_EXT_NOTIF_PREFIX_STR; unsigned char checksum = 0; int ix = 0; unsigned char ch; int more_data = 0; uint32_t max_packet_size; uint32_t data_size; unsigned char *ptr = notif_data; /* First, check if there is pending data */ if (notif_data[0] == '\0') { return; } again: /* * Build notification packet. * * A notification packet has the form `%#', where data * is the content of the notification, and checksum is a checksum of * data, computed and formatted as for ordinary gdb packets. A * notification's data never contains `$', `%' or `#' characters. Upon * receiving a notification, the recipient sends no `+' or `-' to * acknowledge the notification's receipt or to report its corruption. * * Every notification's data begins with a name, which contains no * colon characters, followed by a colon character. */ put_debug_char('%'); checksum = 0; /* Add name to notification packet */ ix = 0; while ((ch = name[ix++])) { put_debug_char(ch); checksum += ch; } /* Name must be followed by a colon character. */ put_debug_char(':'); checksum += ':'; /* * Add data to notification packet. * Warning: The value to hex encoding double the size of the data, * so we must not encode more than the remaining GDB buffer size * divided by 2. */ max_packet_size = GDB_BUF_SIZE - (strlen(name) + NOTIF_PACKET_OVERHEAD); data_size = strlen((char *)ptr); if (data_size <= (max_packet_size / 2)) { more_data = 0; } else { data_size = max_packet_size / 2; more_data = 1; /* Not enough room in notif packet */ } /* Encode data using hex values */ for (ix = 0; ix < data_size; ix++) { ch = hex_chars[(*ptr >> 4)]; put_debug_char(ch); checksum += ch; ch = hex_chars[(*ptr & 0xf)]; put_debug_char(ch); checksum += ch; ptr++; } /* Terminate packet with # */ put_debug_char('#'); put_debug_char(hex_chars[(checksum >> 4)]); put_debug_char(hex_chars[(checksum & 0xf)]); if (more_data) { goto again; } /* Clear buffer & index */ notif_data[0] = '\0'; notif_data_idx = 0; } #endif #ifdef GDB_ARCH_HAS_HW_BP static int has_hit_a_hw_bp(void) { /* instruction hw breakpoints are reported as sw breakpoints */ return (cpu_stop_signal == GDB_SIG_TRAP) && (cpu_stop_bp_type != GDB_SOFT_BP) && (cpu_stop_bp_type != GDB_HW_INST_BP); } #endif static void do_post_event_hw_bp(unsigned char **buf, size_t *buf_size) { #ifdef GDB_ARCH_HAS_HW_BP /* * If it's an hardware breakpoint, report the address and access * type at the origin of the HW breakpoint. Supported syntaxes: * watch: : Write access * rwatch: : Read access * awatch: : Read/Write Access * Instruction hardware breakpoints are reported as software * breakpoints */ if (!has_hit_a_hw_bp()) { return; } int count = 0; switch (cpu_stop_bp_type) { case GDB_HW_DATA_WRITE_BP: count = snprintf((char *)*buf, *buf_size, ";watch"); break; case GDB_HW_DATA_READ_BP: count = snprintf((char *)*buf, *buf_size, ";rwatch"); break; case GDB_HW_DATA_ACCESS_BP: count = snprintf((char *)*buf, *buf_size, ";awatch"); break; } if (count != 0) { *buf += count; *buf_size -= count; count = snprintf((char *)*buf, *buf_size, ":%lx", cpu_stop_hw_bp_addr); *buf += count; *buf_size -= count; } cpu_stop_hw_bp_addr = 0; cpu_stop_bp_type = GDB_SOFT_BP; cpu_stop_signal = GDB_SIG_NULL; #endif } static void write_regs_to_buffer(unsigned char **buf, size_t *buf_size) { unsigned char *saved_buf; int count; #ifdef GDB_ARCH_HAS_ALL_REGS count = snprintf((char *)*buf, *buf_size, ";regs:"); *buf += count; *buf_size -= count; saved_buf = *buf; *buf = mem2hex(tmp_reg_buffer, *buf, sizeof(gdb_regs), 1); *buf_size -= (*buf - saved_buf); #else int offset = 0; int size = 4; count = snprintf((char *)*buf, *buf_size, ";%x:", GDB_PC_REG); *buf += count; *buf_size -= count; gdb_arch_reg_info_get(GDB_PC_REG, &size, &offset); saved_buf = *buf; *buf = mem2hex(tmp_reg_buffer + offset, *buf, size, 1); *buf_size -= (*buf - saved_buf); #endif } static void do_post_event(void) { unsigned char *buf = gdb_buffer; size_t buf_size = GDB_BUF_SIZE; int count; if (buf != gdb_buffer) { *buf++ = '|'; buf_size--; } count = snprintf((char *)buf, buf_size, "T%02xthread:%02x", cpu_stop_signal, 1); buf += count; buf_size -= count; do_post_event_hw_bp(&buf, &buf_size); if (valid_registers) { gdb_arch_regs_get(&gdb_regs, (char *)tmp_reg_buffer); write_regs_to_buffer(&buf, &buf_size); } /* clear stop reason */ cpu_stop_signal = GDB_SIG_NULL; *buf = '\0'; } static void post_event(void) { event_is_pending = 0; if (cpu_stop_signal != GDB_SIG_NULL) { do_post_event(); } else { (void)snprintf((char *)gdb_buffer, GDB_BUF_SIZE, "S%02x", GDB_SIG_INT); } (void)put_packet(gdb_buffer); } /** * @brief Get a character from serial line * * It loops until it has received a character or until it has detected that a * GDB event is pending and should be handled. * * Note that this routine should only be called from the gdb control loop when * the system is stopped. * * @return -1 if no character has been received and there is a GDB event * pending or debug operation pending, number of received character otherwise. */ static int get_debug_char_raw(void) { char ch; while (uart_poll_in(uart_console_dev, &ch) != 0) { if (event_is_pending) { return -1; } } return ch; } static unsigned char get_debug_char(void) { return (unsigned char)get_debug_char_raw(); } /** * @brief Get a GDB serial packet * * Poll the serial line to get a full GDB serial packet. Once * the packet is received, it computes its checksum and return acknowledgment. * It then returns the packet to the caller. * * This routine must only be called when all CPUs are stopped (from the GDB * CPU control loop). * * If a pending GDB event is detected or if a stop event is received from the * client, the corresponding GDB stop event is sent to the client. This * loop does also handle the GDB cpu loop hooks by the intermediate of * get_debug_char() API. * * If a debug operation is pending, this routine returns immediately. * * @return Pointer to received packet or NULL on pending debug operation */ static unsigned char *get_packet(unsigned char *buffer, size_t size) { unsigned char checksum, c, *p; while (1) { while ((c = get_debug_char()) != '$') { if (!event_is_pending) { return NULL; } /* ignore other chars than GDB break character */ if ((c == GDB_STOP_CHAR) || event_is_pending) { post_event(); } } checksum = 0; p = buffer; /* * Continue reading characters until a '#' is found or until * the end of the buffer is reached. */ while (p < &buffer[size]) { c = get_debug_char(); if (c == '#') { break; } else if (c == '$') { /* start over */ checksum = 0; p = buffer; continue; } else { checksum += c; *p++ = c; } } *p = 0; if (c == '#') { if (in_no_ack_mode) { (void)get_debug_char(); (void)get_debug_char(); return buffer; } unsigned char cs[2]; cs[0] = get_hex_char_value(get_debug_char()) << 4; cs[1] = get_hex_char_value(get_debug_char()); if (checksum != (cs[0] | cs[1])) { /* checksum failed */ put_debug_char('-'); } else { /* checksum passed */ put_debug_char('+'); if (buffer[2] == ':') { put_debug_char(buffer[0]); put_debug_char(buffer[1]); return &buffer[3]; } return buffer; } } } return NULL; } /** * @brief write a XML string into output buffer * * It takes care of offset, length and also deal with overflow (if the XML * string is bigger than the output buffer). */ static void write_xml_string(char *buf, const char *xml_str, int off, int len) { size_t max_len = strlen(xml_str); if (off == max_len) { strncat((char *)buf, "l", len - 1); } else if (off > max_len) { fill_output_buffer("E00"); } else { if ((off + max_len) <= len) { /* we can read the full data */ buf[0] = 'l'; int size_to_copy = len <= (GDB_BUF_SIZE - 2) ? len : GDB_BUF_SIZE - 2; strncpy(&buf[1], xml_str + off, size_to_copy); } else { buf[0] = 'm'; strncpy(&buf[1], xml_str + off, GDB_BUF_SIZE - 2); buf[len + 1] = '\0'; } } } /** * @brief get XML target description * * This routine is used to build the string that will hold the XML target * description provided to the GDB client. * * NOTE: Non-re-entrant, since it uses a static buffer. * * @return a pointer on XML target description */ static char *get_xml_target_description(void) { static char target_description[GDB_BUF_SIZE] = { 0 }; char *ptr = target_description; size_t buf_size = sizeof(target_description); size_t size; if (target_description[0] != 0) { return target_description; } strncpy(ptr, xml_target_header, GDB_BUF_SIZE - 1); size = strlen(ptr); ptr += size; buf_size -= size; /* Add architecture definition */ (void)snprintf(ptr, buf_size, " %s\n", GDB_TGT_ARCH); size = strlen(ptr); ptr += size; buf_size -= size; strncpy(ptr, xml_target_footer, GDB_BUF_SIZE - (ptr - target_description) - 1); return target_description; } /* utility functions for handling each case of protocal_parse() */ static void handle_new_connection(void) { /* * This is a new connection. Clear in_no_ack_mode field if it was set * and send acknowledgment for this command that has not been sent as * it should have. */ if (in_no_ack_mode) { put_debug_char('+'); in_no_ack_mode = 0; } snprintf((char *)gdb_buffer, GDB_BUF_SIZE, "T02thread:%02x;", 1); /* * This is an initial connection, should remove all * the breakpoints and cleanup. */ REMOVE_ALL_INSTALLED_BREAKPOINTS(); client_is_connected = 1; } static void reboot(void) { #ifdef CONFIG_REBOOT sys_reboot(SYS_REBOOT_COLD); fill_output_buffer(STUB_OK); #endif } static void detach(void) { fill_output_buffer(STUB_OK); REMOVE_ALL_INSTALLED_BREAKPOINTS(); client_is_connected = 0; gdb_debug_status = NOT_DEBUGGING; RESUME_SYSTEM(); in_no_ack_mode = 0; } static unsigned char *handle_thread_query(unsigned char *packet) { int thread; if (!hex_str_to_int(&packet, &thread)) { gdb_buffer[0] = '\0'; return packet; } if (thread != 1) { fill_output_buffer(STUB_ERROR); } else { fill_output_buffer(STUB_OK); } return packet; } #ifdef CONFIG_REBOOT #define STR_REBOOT ";reboot+" static size_t concat_reboot_feature_if_supported(size_t size) { strncat((char *)gdb_buffer, STR_REBOOT, size); return sizeof(STR_REBOOT); } #else #define concat_reboot_feature_if_supported(size) (0) #endif static ALWAYS_INLINE int is_valid_xml_query(unsigned char **packet, int *off, int *len) { unsigned char *p = *packet; int is_valid = hex_str_to_int(&p, off) && *p++ == ',' && hex_str_to_int(&p, len) && *p == '\0'; *packet = p; return is_valid; } static unsigned char *handle_xml_query(unsigned char *packet) { int off, len; packet += 11; if (is_valid_xml_query(&packet, &off, &len)) { char *xml = get_xml_target_description(); write_xml_string((char *)gdb_buffer, xml, off, len); } else { fill_output_buffer(STUB_ERROR); } return packet; } static const char *supported_features_cmd = "PacketSize=%x;qXfer:features:read+;QStartNoAckMode+" #ifdef GDB_ARCH_HAS_REMOTE_SERIAL_EXT_USING_NOTIF_PACKETS ";" CONFIG_GDB_REMOTE_SERIAL_EXT_NOTIF_PREFIX_STR "+" #endif ; static unsigned char *handle_general_query(unsigned char *packet) { if (packet[0] == 'C') { snprintf((char *)gdb_buffer, GDB_BUF_SIZE, "QC%x", 1); } else if (strncmp((char *)packet, "wr.", 3) == 0) { packet += 3; gdb_buffer[0] = '\0'; } else if (strcmp((char *)packet, "Supported") == 0) { size_t size = GDB_BUF_SIZE; snprintf((char *)gdb_buffer, size, supported_features_cmd, GDB_BUF_SIZE); size -= (strlen((char *)gdb_buffer) + 1); size -= concat_reboot_feature_if_supported(size); strncat((char *)gdb_buffer, STR_TYPE, size); size -= sizeof(STR_TYPE); } else if (strncmp((char *)packet, "Xfer:features:read:", 19) == 0) { packet += 19; if (strncmp((char *)packet, "target.xml:", 11) == 0) { packet = handle_xml_query(packet); } else { gdb_buffer[0] = '\0'; } } else { gdb_buffer[0] = '\0'; } return packet; } static ALWAYS_INLINE void handle_get_registers(void) { if (!valid_registers) { fill_output_buffer("E02"); return; } (void)gdb_arch_regs_get(&gdb_regs, (char *)tmp_reg_buffer); mem2hex(tmp_reg_buffer, gdb_buffer, GDB_NUM_REG_BYTES, 1); } static ALWAYS_INLINE unsigned char *handle_write_registers(unsigned char *packet) { if (!valid_registers) { fill_output_buffer("E02"); return packet; } (void)gdb_arch_regs_get(&gdb_regs, (char *)tmp_reg_buffer); for (int i = 0; i < GDB_NUM_REG_BYTES; i++) { int value = hex_str_to_byte(&packet); if (value < 0) { break; } tmp_reg_buffer[i] = (unsigned char)value; } gdb_arch_regs_set(&gdb_regs, (char *)tmp_reg_buffer); fill_output_buffer(STUB_OK); return packet; } #ifdef GDB_HAS_SINGLE_REG_ACCESS static ALWAYS_INLINE unsigned char *handle_write_single_register(unsigned char *packet) { int reg_num = 0; int offset = 0; int size = 4; int i, value; if (!valid_registers) { fill_output_buffer("E02"); return packet; } if (!hex_str_to_int(&packet, ®_num) || *(packet++) != '=') { fill_output_buffer("E02"); return packet; } gdb_arch_regs_get(&gdb_regs, (char *)tmp_reg_buffer); gdb_arch_reg_info_get(reg_num, &size, &offset); for (i = 0; i < size; i++) { value = hex_str_to_byte(&packet); if (value < 0) { break; } tmp_reg_buffer[offset + i] = (unsigned char)value; } if (i != size) { fill_output_buffer(STUB_ERROR); return packet; } gdb_arch_regs_set(&gdb_regs, (char *)tmp_reg_buffer); fill_output_buffer(STUB_OK); return packet; } static ALWAYS_INLINE unsigned char *handle_read_single_register(unsigned char *packet) { int reg_num = 0; int offset = 0; int size = 4; if (!valid_registers) { fill_output_buffer("E02"); return packet; } /* p */ if (!hex_str_to_int(&packet, ®_num)) { fill_output_buffer("E02"); return packet; } gdb_arch_regs_get(&gdb_regs, (char *)tmp_reg_buffer); gdb_arch_reg_info_get(reg_num, &size, &offset); mem2hex(tmp_reg_buffer + offset, gdb_buffer, size, 1); return packet; } #endif static ALWAYS_INLINE unsigned char *handle_read_memory(unsigned char *packet) { /* m, */ long long addr; int length; void *p; if (hex_str_to_longlong((unsigned char **)&packet, &addr) == 0) { fill_output_buffer("E01"); return packet; } if (!(*packet++ == ',' && hex_str_to_int(&packet, &length))) { fill_output_buffer("E01"); return packet; } p = (void *)((long)addr); if (mem_probe(p, SYS_MEM_SAFE_READ, length, 0, 1) == -1) { /* No read access */ fill_output_buffer("E01"); return packet; } /* Now read memory */ mem2hex(p, gdb_buffer, length, 1); return packet; } #define WRITE_MEM_SIG(x) \ unsigned char *(x)(unsigned char *packet, unsigned char *dest, int len) typedef WRITE_MEM_SIG (write_mem_t); static ALWAYS_INLINE unsigned char *handle_write_memory(unsigned char *packet, write_mem_t *write_mem) { long long addr; int len; unsigned char *p; /* [X or P],:... */ if (hex_str_to_longlong(&packet, &addr) == 0) { fill_output_buffer("E02"); return packet; } p = packet; /* to allow the if expression to fit on one line */ if (!(*p++ == ',' && hex_str_to_int(&p, &len) && *p++ == ':')) { fill_output_buffer("E02"); return p; } packet = p; p = (void *)((long)addr); if (mem_probe(p, SYS_MEM_SAFE_WRITE, len, 0, 1) == -1) { /* No write access */ fill_output_buffer("E02"); return packet; } packet = write_mem(packet, p, len); fill_output_buffer(STUB_OK); return packet; } static WRITE_MEM_SIG(write_memory) { unsigned char value; int i; for (i = 0; i < len; i++) { value = hex_str_to_byte(&packet); if (value < 0) { break; } dest[i] = (unsigned char)value; } return packet; } static WRITE_MEM_SIG(write_memory_from_binary_format) { unsigned char value; int i; for (i = 0; i < len; i++) { value = packet[0]; packet++; if (value == '}') { value = packet[0] ^ 0x20; packet++; } dest[i] = (unsigned char)value; } return packet; } static ALWAYS_INLINE unsigned char *handle_pass_signal_to_context(unsigned char *packet) { int signal; /* read signal number */ if (!hex_str_to_int(&packet, &signal)) { fill_output_buffer("E02"); return packet; } cpu_pending_signal = signal; if (*packet == ';') { packet++; } return packet; } static ALWAYS_INLINE unsigned char *handle_continue_execution(unsigned char *packet) { long long addr; /* try to read optional parameter, PC unchanged if no param */ hex_str_to_longlong(&packet, &addr); gdb_debug_status = NOT_DEBUGGING; return packet; } static ALWAYS_INLINE unsigned char *handle_step(unsigned char *packet) { long long addr; /* try to read optional parameter, PC unchanged if no param */ hex_str_to_longlong(&packet, &addr); gdb_debug_status = SINGLE_STEP; return packet; } static ALWAYS_INLINE unsigned char *handle_vcont_action(unsigned char *packet, int *do_not_send_ack) { char action; int signal = 0, thread; packet += 5; action = *packet++; if ((action != 'c') && (action != 'C') && (action != 's') && (action != 'S')) { gdb_buffer[0] = '\0'; return packet; } if ((action == 'C') || (action == 'S')) { /* read signal number */ if (!hex_str_to_int(&packet, &signal)) { fill_output_buffer("E02"); return packet; } } if (*packet == ':') { packet++; hex_str_to_int(&packet, &thread); } if (signal != 0) { cpu_pending_signal = signal; } if ((action == 'c') || (action == 'C')) { gdb_debug_status = NOT_DEBUGGING; } else { gdb_debug_status = SINGLE_STEP; } *do_not_send_ack = 1; return packet; } #ifdef GDB_ARCH_HAS_RUNCONTROL static ALWAYS_INLINE unsigned char *handle_breakpoint_install(unsigned char *packet, add_del_bp_t *bp_op) { /* remove (ztype,addr,length) or insert (Ztype,addr,length) */ int type, len; long long addr; enum gdb_error_code err; /* read & */ if (!(hex_str_to_int(&packet, &type) && *packet++ == ',' && hex_str_to_longlong(&packet, &addr))) { fill_output_buffer("E07"); return packet; } /* read length */ if (!(*packet++ == ',' && hex_str_to_int(&packet, &len))) { fill_output_buffer("E07"); return packet; } if (bp_op(type, (long)addr, len, &err) == 0) { fill_output_buffer(STUB_OK); } else { snprintf((char *)gdb_buffer, GDB_BUF_SIZE, "E%02d", err); } return packet; } #endif /** * @brief parse given GDB command string * * Parse and execute the given GDB command string, and send acknowledgment if * acknowledgment is enabled. * * @return 0 on success, -1 if failed to send acknowledgment. */ static int protocol_parse(unsigned char *packet) { unsigned char ch; int do_not_send_ack = 0; ch = *packet++; switch (ch) { case '?': handle_new_connection(); break; case 'k': /* Kill request: we use it to reboot */ reboot(); break; case 'D': detach(); break; case 'T': packet = handle_thread_query(packet); break; case 'Q': /* the only 'Q' command we support is "start no-ack mode" */ if (strcmp((const char *)packet, "StartNoAckMode") == 0) { in_no_ack_mode = 1; fill_output_buffer(STUB_OK); } else { gdb_buffer[0] = '\0'; } break; case 'q': packet = handle_general_query(packet); break; case 'g': handle_get_registers(); break; case 'G': packet = handle_write_registers(packet); break; #ifdef GDB_HAS_SINGLE_REG_ACCESS case 'P': packet = handle_write_single_register(packet); break; case 'p': packet = handle_read_single_register(packet); break; #endif case 'm': packet = handle_read_memory(packet); break; case 'M': packet = handle_write_memory(packet, write_memory); break; case 'X': packet = handle_write_memory(packet, write_memory_from_binary_format); break; case 'C': packet = handle_pass_signal_to_context(packet); /* fall through */ case 'c': packet = handle_continue_execution(packet); do_not_send_ack = 1; break; case 'S': packet = handle_pass_signal_to_context(packet); /* fall through */ case 's': packet = handle_step(packet); do_not_send_ack = 1; break; case 'v': if (strcmp((const char *)packet, "Cont?") == 0) { fill_output_buffer("vCont;c;s;C;S"); break; } else if (strncmp((const char *)packet, "Cont;", 5) != 0) { gdb_buffer[0] = '\0'; break; } packet = handle_vcont_action(packet, &do_not_send_ack); break; #ifdef GDB_ARCH_HAS_RUNCONTROL case 'z': packet = handle_breakpoint_install(packet, delete_bp); break; case 'Z': packet = handle_breakpoint_install(packet, add_bp); break; #endif default: /* in case of an unsupported command, send empty response */ gdb_buffer[0] = '\0'; break; } /* Send the acknowledgment command when necessary */ if (!do_not_send_ack) { if (put_packet(gdb_buffer) < 0) { return -1; } } return 0; } /* * function: put_debug_char * description: * - "What you must do for the stub" * - Write a single character from a port. */ static void put_debug_char(unsigned char ch) { (void)uart_poll_out(uart_console_dev, ch); } #ifdef GDB_ARCH_HAS_RUNCONTROL /** * @brief add an hardware breakpoint to debug registers set * * This routine adds an hardware breakpoint to debug registers structure. * It does not update the debug registers. * * @param addr Address where to set the breakpoint * @param type Type of breakpoint * @param len Length of data * @param err Container for returning error code * * @return 0 on success, -1 if failed (Error code returned via @a err). */ static int add_hw_bp(long addr, enum gdb_bp_type type, int len, enum gdb_error_code *err) { #ifdef GDB_ARCH_HAS_HW_BP if (gdb_hw_bp_set(&dbg_regs, addr, type, len, err) == -1) { return -1; } hw_bp_cnt++; return 0; #else *err = GDB_ERROR_HW_BP_NOT_SUP; return -1; #endif } /** * @brief remove an hardware breakpoint from debug registers set * * This routine removes an hardware breakpoint from debug registers structure. * It does not update the debug registers. * * @param addr Address where to set the breakpoint * @param type Type of breakpoint * @param len Length of data * @param err Container for returning error code * * @return 0 on success, -1 if failed (Error code returned via @a err). */ static int remove_hw_bp(long addr, enum gdb_bp_type type, int len, enum gdb_error_code *err) { #ifdef GDB_ARCH_HAS_HW_BP if (gdb_hw_bp_clear(&dbg_regs, addr, type, len, err) == -1) { return -1; } hw_bp_cnt--; return 0; #else *err = GDB_ERROR_HW_BP_NOT_SUP; return -1; #endif } /** * @brief add a new breakpoint or watchpoint to breakpoint list * * This routine adds a new breakpoint or watchpoint to breakpoint list. For * watchpoints, this routine checks that the given type/length combination is * supported on current architecture, and that debug registers are not full. * * @param type GDB breakpoint type: * * 0 : software breakpoint (GDB_SOFT_BP) * 1 : hardware breakpoint (GDB_HW_INST_BP) * 2 : write watchpoint (GDB_HW_DATA_WRITE_BP) * 3 : read watchpoint (GDB_HW_DATA_READ_BP) * 4 : access watchpoint (GDB_HW_DATA_ACCESS_BP) * * @param addr Breakpoint address * @param len For a software breakpoint, len specifies the size of the * instruction to be patched. For hardware breakpoints and * watchpoints length specifies the memory region to be monitored. * @param err Pointer to error code if failed to add breakpoint. * * @return 0 on success, -1 if failed to add breakpoint. */ static int add_bp(enum gdb_bp_type type, long addr, int len, enum gdb_error_code *err) { if (type != GDB_SOFT_BP) { return add_hw_bp(addr, type, len, err); } if (mem_probe((void *)addr, SYS_MEM_SAFE_READ, len, 0, 1) == -1) { *err = GDB_ERROR_INVALID_MEM; return -1; } /* Add software breakpoint to BP list */ for (int ix = 0; ix < MAX_SW_BP; ix++) { if (bp_array[ix].valid == 0) { bp_array[ix].valid = 1; bp_array[ix].enabled = 0; bp_array[ix].addr = (gdb_instr_t *)addr; return 0; } } *err = GDB_ERROR_BP_LIST_FULL; return -1; } /** * @brief delete a breakpoint or watchpoint from breakpoint list * * @return 0 on success, -1 if failed to remove breakpoint. */ static int delete_bp(enum gdb_bp_type type, long addr, int len, enum gdb_error_code *err) { gdb_instr_t *bp_addr = (gdb_instr_t *)addr; if (type != GDB_SOFT_BP) { return remove_hw_bp(addr, type, len, err); } for (int ix = 0; ix < MAX_SW_BP; ix++) { if (bp_array[ix].valid && bp_array[ix].addr == bp_addr) { bp_array[ix].valid = 0; /* * Make sure all valid entries are contiguous to speed * up breakpoint table parsing. */ for (int jx = ix + 1; jx < MAX_SW_BP; jx++) { if (bp_array[jx].valid == 1) { bp_array[jx - 1] = bp_array[jx]; bp_array[jx].valid = 0; } else { break; } } return 0; } else if (!bp_array[ix].valid) { break; } } *err = GDB_ERROR_INVALID_BP; return -1; } static void remove_all_installed_breakpoints(void) { for (int ix = 0; ix < MAX_SW_BP; ix++) { if (!bp_array[ix].valid) { break; } bp_array[ix].valid = 0; } } #ifdef GDB_ARCH_HAS_HW_BP static inline void set_debug_regs_for_hw_breakpoints(void) { if (hw_bp_cnt > 0) { gdb_dbg_regs_set(&dbg_regs); } } #else #define set_debug_regs_for_hw_breakpoints() do { } while ((0)) #endif /* * Physically install breakpoints, and make sure that modified memory is * flushed on all CPUs. * * Must only be called when ready to exit the CPU control loop. */ static void install_breakpoints(void) { gdb_instr_t instr = GDB_BREAK_INSTRUCTION; /* Software breakpoints installation */ for (int ix = 0; ix < MAX_SW_BP; ix++) { if (bp_array[ix].valid && !bp_array[ix].enabled) { gdb_instr_t *addr = bp_array[ix].addr; bp_array[ix].instr = *addr; (void)set_instruction(addr, &instr, sizeof(instr)); bp_array[ix].enabled = 1; } else if (!bp_array[ix].valid) { break; } } set_debug_regs_for_hw_breakpoints(); } #ifdef GDB_ARCH_HAS_HW_BP static inline void clear_debug_regs_for_hw_breakpoints(void) { if (hw_bp_cnt > 0) { gdb_dbg_regs_clear(); } } #else #define clear_debug_regs_for_hw_breakpoints() do { } while ((0)) #endif /* * Physically uninstall breakpoints, and make sure that modified memory is * flushed on all CPUs. * * Must only be called in the CPU control loop. */ static void uninstall_breakpoints(void) { for (int ix = 0; ix < MAX_SW_BP; ix++) { if (bp_array[ix].valid == 1 && bp_array[ix].enabled) { gdb_instr_t *addr = bp_array[ix].addr; (void)set_instruction(addr, &bp_array[ix].instr, sizeof(gdb_instr_t)); bp_array[ix].enabled = 0; } else if (bp_array[ix].valid == 0) { break; } } clear_debug_regs_for_hw_breakpoints(); } /* Re-install breakpoints and resume the system. */ static void resume_system(void) { /* * System must not be resumed if we're going to execute a single step. */ if (gdb_debug_status == SINGLE_STEP) { return; } install_breakpoints(); } static inline void enter_trace_mode(void) { #ifdef GDB_ARCH_NO_SINGLE_STEP gdb_instr_t bp_instr = GDB_BREAK_INSTRUCTION; gdb_step_emu_next_pc = gdb_get_next_pc(&gdb_regs); gdb_step_emu_instr = *gdb_step_emu_next_pc; (void)set_instruction(gdb_step_emu_next_pc, &bp_instr, sizeof(gdb_instr_t)); trace_lock_key = gdb_int_regs_lock(&gdb_regs); #else /* Handle single step request for runcontrol CPU */ trace_lock_key = gdb_trace_mode_set(&gdb_regs); #endif } static inline void disable_trace_mode(void) { #ifdef GDB_ARCH_NO_SINGLE_STEP /* remove temporary breakpoint */ (void)set_instruction(gdb_step_emu_next_pc, &gdb_step_emu_instr, sizeof(gdb_instr_t)); /* Disable trace mode */ gdb_int_regs_unlock(&gdb_regs, trace_lock_key); #else /* Disable trace mode */ gdb_trace_mode_clear(&gdb_regs, trace_lock_key); #endif } /** * @brief stop mode agent BP/trace handler * * Common handler of breakpoint and trace mode exceptions. * It is invoked with interrupts locked. * * @return n/a */ void gdb_handler(enum gdb_exc_mode mode, void *esf, int signal) { /* Save BP/Trace handler registers */ gdb_arch_regs_from_esf(&gdb_regs, (NANO_ESF *)esf); valid_registers = 1; if (mode == GDB_EXC_TRACE) { /* Check if GDB did request a step */ if (gdb_debug_status != SINGLE_STEP) { return; } /* No longer pending trace mode exception */ gdb_debug_status = DEBUGGING; disable_trace_mode(); } event_is_pending = 1; cpu_stop_signal = signal; /* Enter stop mode agent control loop */ control_loop(); /* Restore BP handler registers */ gdb_arch_regs_to_esf(&gdb_regs, (NANO_ESF *)esf); /* Resume system if not handling a single step */ RESUME_SYSTEM(); } static int set_instruction(void *addr, gdb_instr_t *instr, size_t size) { if (_mem_safe_write_to_text_section(addr, (char *)instr, size) < 0) { return -EFAULT; } sys_cache_flush((vaddr_t) addr, size); return 0; } #endif /* GDB_ARCH_HAS_RUNCONTROL */ static inline void setup_singlestep_if_non_steppable_instruction(void) { #ifdef GDB_ARCH_CAN_STEP if (gdb_debug_status == SINGLE_STEP) { if (!GDB_ARCH_CAN_STEP(&gdb_regs)) { gdb_debug_status = DEBUGGING; event_is_pending = 1; cpu_stop_signal = GDB_SIG_TRAP; } } #endif } static inline int handle_single_stepping(void) { #ifdef GDB_ARCH_HAS_RUNCONTROL setup_singlestep_if_non_steppable_instruction(); if (gdb_debug_status == SINGLE_STEP) { enter_trace_mode(); return 1; } #endif return 0; } /** * @brief GDB control loop * * The CPU control loop is an active wait loop used to stop CPU activity. * * It must be called with interrupts locked. * * It loops while waiting for debug events which can be: * * - System resumed: gdb_debug_status != NOT_DEBUGGING * The control loop must be exited. * * - Single step request: gdb_debug_status == SINGLE_STEP * Notify client that CPU is already stopped. * This is done by setting event_is_pending = 1. * event_is_pending will be handled by next get_packet(). * * @return n/a */ static void control_loop(void) { char ch; UNINSTALL_BREAKPOINTS(); /* Flush input buffer */ while (uart_poll_in(uart_console_dev, &ch) == 0) { if (ch == GDB_STOP_CHAR) { gdb_debug_status = DEBUGGING; cpu_stop_signal = GDB_SIG_INT; event_is_pending = 1; break; } } while (gdb_debug_status != NOT_DEBUGGING) { #ifdef GDB_ARCH_HAS_REMOTE_SERIAL_EXT_USING_NOTIF_PACKETS /* * Check if system has been stopped to handle a notification * packet: If a notification is pending (notif_pkt_pending), * but no stop signal has been set. */ if ((cpu_stop_signal == GDB_SIG_NULL) && notif_pkt_pending) { handle_notification(); /* Mark packet notification as done */ notif_pkt_pending = 0; break; } #endif unsigned char *packet = get_packet(gdb_buffer, GDB_BUF_SIZE); if (packet) { protocol_parse(packet); } if (handle_single_stepping()) { return; } } } /** * @brief handle a system stop request * * The purpose of this routine is to handle a stop request issued by remote * debug client. It is called when receiving a break char. * * It indicates that a GDB event is pending (the answer to stop request) and * transfer control from the runtime system to the stop mode agent. The event * will be posted by this control loop. * * @return n/a */ static void handle_system_stop(NANO_ISF *regs, int signal) { int key = irq_lock(); gdb_debug_status = DEBUGGING; if (signal != 0) { cpu_stop_signal = signal; } else { cpu_stop_signal = GDB_SIG_INT; /* Stopped by a command */ } /* Save registers */ if (regs == GDB_INVALID_REG_SET) { valid_registers = 0; } else { if (!regs) { regs = sys_debug_current_isf_get(); } gdb_arch_regs_from_isf(&gdb_regs, regs); valid_registers = 1; } /* A GDB event is pending */ event_is_pending = 1; /* Transfer control to the control loop */ control_loop(); /* Load registers */ if (valid_registers) { gdb_arch_regs_to_isf(&gdb_regs, regs); } /* Resume system if not a single step request */ RESUME_SYSTEM(); irq_unlock(key); } /** * @brief wrapper to send a character to console * * This routine is a specific wrapper to send a character to console. * If the GDB Server is started, this routine intercepts the data and transfer * it to the connected debug clients using a GDB notification packet. * * @return n/a */ static UART_CONSOLE_OUT_DEBUG_HOOK_SIG(gdb_console_out) { #ifdef GDB_ARCH_HAS_REMOTE_SERIAL_EXT_USING_NOTIF_PACKETS /* * If remote debug client is connected, then transfer data to remote * client. Otherwise, discard this character. */ if (client_is_connected) { write_to_console(&c, 1); return UART_CONSOLE_DEBUG_HOOK_HANDLED; } #endif return !UART_CONSOLE_DEBUG_HOOK_HANDLED; } #ifdef CONFIG_GDB_SERVER_INTERRUPT_DRIVEN static int console_irq_input_hook(uint8_t ch) { if (ch == GDB_STOP_CHAR) { (void)irq_lock(); handle_system_stop(NULL, 0); return 1; } return 0; } #endif void system_stop_here(void *regs) { int key = irq_lock(); handle_system_stop((NANO_ISF *) regs, GDB_SIG_STOP); irq_unlock(key); } void _debug_fatal_hook(const NANO_ESF *esf) { struct gdb_reg_set regs; gdb_arch_regs_from_esf(®s, (NANO_ESF *) esf); system_stop_here((void *)®s); gdb_arch_regs_to_esf(®s, (NANO_ESF *) esf); } #ifdef CONFIG_GDB_SERVER_INTERRUPT_DRIVEN static void init_interrupt_handling(void) { k_fifo_init(&cmds_queue); k_fifo_init(&avail_queue); uart_console_in_debug_hook_install(console_irq_input_hook); uart_register_input(&avail_queue, &cmds_queue, NULL); } #else #define init_interrupt_handling() do { } while ((0)) #endif #ifdef CONFIG_MEM_SAFE_NUM_EXTRA_REGIONS static void init_mem_safe_access(void) { (void)_mem_safe_region_add((void *)CONFIG_GDB_RAM_ADDRESS, CONFIG_GDB_RAM_SIZE, SYS_MEM_SAFE_READ); (void)_mem_safe_region_add((void *)CONFIG_GDB_RAM_ADDRESS, CONFIG_GDB_RAM_SIZE, SYS_MEM_SAFE_WRITE); } #else #define init_mem_safe_access() do { } while ((0)) #endif static int init_gdb_server(struct device *unused) { static int gdb_is_initialized; if (gdb_is_initialized) { return -1; } gdb_arch_init(); uart_console_dev = device_get_binding(CONFIG_UART_CONSOLE_ON_DEV_NAME); uart_console_out_debug_hook_install(gdb_console_out); init_interrupt_handling(); init_mem_safe_access(); gdb_is_initialized = 1; system_stop_here(GDB_INVALID_REG_SET); return 0; } SYS_INIT(init_gdb_server, POST_KERNEL, 1);