| /* |
| * 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: |
| * |
| * <target version="1.0"> |
| * <architecture>i386</architecture> |
| * </target> |
| * |
| * But architectures may also reports information on specific features |
| * such as extended registers definitions or hardware breakpoint |
| * definitions. |
| * |
| * Each `<feature>' describes some logical portion of the target |
| * system. |
| * A `<feature>' element has this form: |
| * |
| * <feature name="NAME"> |
| * [TYPE...] |
| * REG... |
| * </feature> |
| * |
| * 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: |
| * |
| * <reg name="NAME" |
| * bitsize="SIZE" |
| * [regnum="NUM"] |
| * [save-restore="SAVE-RESTORE"] |
| * [type="TYPE"] |
| * [group="GROUP"]/> |
| * |
| * 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: |
| * |
| * <feature name="HW_BP_FEATURE"> |
| * <defaults |
| * max_bp="MAX_BP" |
| * max_inst_bp="MAX_INST_BP" |
| * max_watch_bp="MAX_WATCH_BP" |
| * length="LENGTH" |
| * > |
| * HW_BP_DESC... |
| * </feature> |
| * |
| * 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: |
| * |
| * <hwbp type="ACCESS_TYPE" |
| * [length="LENGTH"] |
| * [max_bp="MAX_BP"] |
| * /> |
| * |
| * 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: |
| * |
| * <feature name="WR_AGENT_FEATURE"> |
| * <config max_sw_bp="MAX_SW_BP" |
| * step_only_on_bp="STEP_ONLY_ON_BP" |
| * /> |
| * </feature> |
| * |
| * 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: |
| * %<notificationName:<notificationData>#<checksum> |
| * |
| * For example: |
| * %CONFIG_GDB_REMOTE_SERIAL_EXT_NOTIF_PREFIX_STR:<notificationData>#<checksum> |
| */ |
| |
| #include <kernel.h> |
| #include <zephyr/types.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <kernel_structs.h> |
| #include <board.h> |
| #include <device.h> |
| #include <uart.h> |
| #include <cache.h> |
| #include <init.h> |
| #include <debug/gdb_arch.h> |
| #include <debug/mem_safe.h> |
| #include <gdb_server.h> |
| #include <debug_info.h> |
| #ifdef CONFIG_GDB_SERVER_INTERRUPT_DRIVEN |
| #include <drivers/console/uart_console.h> |
| #endif |
| #ifdef CONFIG_REBOOT |
| #include <misc/reboot.h> |
| #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 = |
| "<?xml version=\"1.0\"?> " |
| "<!DOCTYPE target SYSTEM " |
| "\"gdb-target.dtd\"> <target version=\"1.0\">\n"; |
| |
| static const char *xml_target_footer = "</target>"; |
| 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 u32_t write_to_console(char *buf, u32_t len); |
| #endif |
| |
| #ifdef CONFIG_GDB_SERVER_INTERRUPT_DRIVEN |
| static int console_irq_input_hook(u8_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; |
| |
| /* $<packet info>#<checksum>. */ |
| 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 u32_t write_to_console(char *buf, u32_t len) |
| { |
| u32_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; |
| u32_t max_packet_size; |
| u32_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 `%<data>#<checksum>', 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 #<checksum> */ |
| 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:<dataAddr> : Write access |
| * rwatch:<dataAddr> : Read access |
| * awatch:<dataAddr> : 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) |
| { |
| unsigned 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, " <architecture>%s</architecture>\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<regno> */ |
| |
| 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<addr>,<length> */ |
| |
| 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]<addr>,<length>:<val><val>...<val> */ |
| |
| 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((char *) 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 <type> & <addr> */ |
| 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) |
| { |
| unsigned 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(u8_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, |
| GDB_RAM_SIZE, SYS_MEM_SAFE_READ); |
| (void)_mem_safe_region_add((void *)CONFIG_GDB_RAM_ADDRESS, |
| 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); |