You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
908 lines
17 KiB
908 lines
17 KiB
/* |
|
* Copyright (c) 2020 Intel Corporation. |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <zephyr/init.h> |
|
#include <zephyr/kernel.h> |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(gdbstub); |
|
|
|
#include <zephyr/sys/util.h> |
|
|
|
#include <ctype.h> |
|
#include <stdbool.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
#include <string.h> |
|
#include <zephyr/toolchain.h> |
|
#include <sys/types.h> |
|
#include <zephyr/sys/util.h> |
|
|
|
#include <zephyr/debug/gdbstub.h> |
|
#include "gdbstub_backend.h" |
|
|
|
/* +1 is for the NULL character added during receive */ |
|
#define GDB_PACKET_SIZE (CONFIG_GDBSTUB_BUF_SZ + 1) |
|
|
|
/* GDB remote serial protocol does not define errors value properly |
|
* and handle all error packets as the same the code error is not |
|
* used. There are informal values used by others gdbstub |
|
* implementation, like qemu. Lets use the same here. |
|
*/ |
|
#define GDB_ERROR_GENERAL "E01" |
|
#define GDB_ERROR_MEMORY "E14" |
|
#define GDB_ERROR_OVERFLOW "E22" |
|
|
|
static bool not_first_start; |
|
|
|
/* Empty memory region array */ |
|
__weak const struct gdb_mem_region gdb_mem_region_array[0]; |
|
|
|
/* Number of memory regions */ |
|
__weak const size_t gdb_mem_num_regions; |
|
|
|
/** |
|
* Given a starting address and length of a memory block, find a memory |
|
* region descriptor from the memory region array where the memory block |
|
* fits inside the memory region. |
|
* |
|
* @param addr Starting address of the memory block |
|
* @param len Length of the memory block |
|
* |
|
* @return Pointer to the memory region description if found. |
|
* NULL if not found. |
|
*/ |
|
static inline const |
|
struct gdb_mem_region *find_memory_region(const uintptr_t addr, const size_t len) |
|
{ |
|
const struct gdb_mem_region *r, *ret = NULL; |
|
unsigned int idx; |
|
|
|
for (idx = 0; idx < gdb_mem_num_regions; idx++) { |
|
r = &gdb_mem_region_array[idx]; |
|
|
|
if ((addr >= r->start) && |
|
(addr < r->end) && |
|
((addr + len) >= r->start) && |
|
((addr + len) < r->end)) { |
|
ret = r; |
|
break; |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
bool gdb_mem_can_read(const uintptr_t addr, const size_t len, uint8_t *align) |
|
{ |
|
bool ret = false; |
|
const struct gdb_mem_region *r; |
|
|
|
if (gdb_mem_num_regions == 0) { |
|
/* |
|
* No region is defined. |
|
* Assume memory access is not restricted, and there is |
|
* no alignment requirement. |
|
*/ |
|
*align = 1; |
|
ret = true; |
|
} else { |
|
r = find_memory_region(addr, len); |
|
if (r != NULL) { |
|
if ((r->attributes & GDB_MEM_REGION_READ) == |
|
GDB_MEM_REGION_READ) { |
|
if (r->alignment > 0) { |
|
*align = r->alignment; |
|
} else { |
|
*align = 1; |
|
} |
|
ret = true; |
|
} |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
bool gdb_mem_can_write(const uintptr_t addr, const size_t len, uint8_t *align) |
|
{ |
|
bool ret = false; |
|
const struct gdb_mem_region *r; |
|
|
|
if (gdb_mem_num_regions == 0) { |
|
/* |
|
* No region is defined. |
|
* Assume memory access is not restricted, and there is |
|
* no alignment requirement. |
|
*/ |
|
*align = 1; |
|
ret = true; |
|
} else { |
|
r = find_memory_region(addr, len); |
|
if (r != NULL) { |
|
if ((r->attributes & GDB_MEM_REGION_WRITE) == |
|
GDB_MEM_REGION_WRITE) { |
|
if (r->alignment > 0) { |
|
*align = r->alignment; |
|
} else { |
|
*align = 1; |
|
} |
|
|
|
ret = true; |
|
} |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
size_t gdb_bin2hex(const uint8_t *buf, size_t buflen, char *hex, size_t hexlen) |
|
{ |
|
if ((hexlen + 1) < buflen * 2) { |
|
return 0; |
|
} |
|
|
|
for (size_t i = 0; i < buflen; i++) { |
|
if (hex2char(buf[i] >> 4, &hex[2 * i]) < 0) { |
|
return 0; |
|
} |
|
if (hex2char(buf[i] & 0xf, &hex[2 * i + 1]) < 0) { |
|
return 0; |
|
} |
|
} |
|
|
|
return 2 * buflen; |
|
} |
|
|
|
__weak |
|
int arch_gdb_add_breakpoint(struct gdb_ctx *ctx, uint8_t type, |
|
uintptr_t addr, uint32_t kind) |
|
{ |
|
return -2; |
|
} |
|
|
|
__weak |
|
int arch_gdb_remove_breakpoint(struct gdb_ctx *ctx, uint8_t type, |
|
uintptr_t addr, uint32_t kind) |
|
{ |
|
return -2; |
|
} |
|
|
|
__weak |
|
void arch_gdb_post_memory_write(uintptr_t addr, size_t len, uint8_t align) |
|
{ |
|
ARG_UNUSED(addr); |
|
ARG_UNUSED(len); |
|
ARG_UNUSED(align); |
|
} |
|
|
|
/** |
|
* Add preamble and termination to the given data. |
|
* |
|
* It returns 0 if the packet was acknowledge, -1 otherwise. |
|
*/ |
|
static int gdb_send_packet(const uint8_t *data, size_t len) |
|
{ |
|
uint8_t buf[2]; |
|
uint8_t checksum = 0; |
|
|
|
/* Send packet start */ |
|
z_gdb_putchar('$'); |
|
|
|
/* Send packet data and calculate checksum */ |
|
while (len-- > 0) { |
|
checksum += *data; |
|
z_gdb_putchar(*data++); |
|
} |
|
|
|
/* Send the checksum */ |
|
z_gdb_putchar('#'); |
|
|
|
if (gdb_bin2hex(&checksum, 1, buf, sizeof(buf)) == 0) { |
|
return -1; |
|
} |
|
|
|
z_gdb_putchar(buf[0]); |
|
z_gdb_putchar(buf[1]); |
|
|
|
if (z_gdb_getchar() == '+') { |
|
return 0; |
|
} |
|
|
|
/* Just got an invalid response */ |
|
return -1; |
|
} |
|
|
|
/** |
|
* Receives one whole GDB packet. |
|
* |
|
* @retval 0 Success |
|
* @retval -1 Checksum error |
|
* @retval -2 Incoming packet too large |
|
*/ |
|
static int gdb_get_packet(uint8_t *buf, size_t buf_len, size_t *len) |
|
{ |
|
uint8_t ch = '0'; |
|
uint8_t expected_checksum, checksum = 0; |
|
uint8_t checksum_buf[2]; |
|
|
|
/* Wait for packet start */ |
|
checksum = 0; |
|
|
|
/* wait for the start character, ignore the rest */ |
|
while (ch != '$') { |
|
ch = z_gdb_getchar(); |
|
} |
|
|
|
*len = 0; |
|
/* Read until receive '#' */ |
|
while (true) { |
|
ch = z_gdb_getchar(); |
|
|
|
if (ch == '#') { |
|
break; |
|
} |
|
|
|
/* Only put into buffer if not full */ |
|
if (*len < (buf_len - 1)) { |
|
buf[*len] = ch; |
|
} |
|
|
|
checksum += ch; |
|
(*len)++; |
|
} |
|
|
|
buf[*len] = '\0'; |
|
|
|
/* Get checksum now */ |
|
checksum_buf[0] = z_gdb_getchar(); |
|
checksum_buf[1] = z_gdb_getchar(); |
|
|
|
if (hex2bin(checksum_buf, 2, &expected_checksum, 1) == 0) { |
|
return -1; |
|
} |
|
|
|
/* Verify checksum */ |
|
if (checksum != expected_checksum) { |
|
LOG_DBG("Bad checksum. Got 0x%x but was expecting: 0x%x", |
|
checksum, expected_checksum); |
|
/* NACK packet */ |
|
z_gdb_putchar('-'); |
|
return -1; |
|
} |
|
|
|
/* ACK packet */ |
|
z_gdb_putchar('+'); |
|
|
|
if (*len >= (buf_len - 1)) { |
|
return -2; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
/* Read memory byte-by-byte */ |
|
static inline int gdb_mem_read_unaligned(uint8_t *buf, size_t buf_len, |
|
uintptr_t addr, size_t len) |
|
{ |
|
uint8_t data; |
|
size_t pos, count = 0; |
|
|
|
/* Read from system memory */ |
|
for (pos = 0; pos < len; pos++) { |
|
data = *(uint8_t *)(addr + pos); |
|
count += gdb_bin2hex(&data, 1, buf + count, buf_len - count); |
|
} |
|
|
|
return count; |
|
} |
|
|
|
/* Read memory with alignment constraint */ |
|
static inline int gdb_mem_read_aligned(uint8_t *buf, size_t buf_len, |
|
uintptr_t addr, size_t len, |
|
uint8_t align) |
|
{ |
|
/* |
|
* Memory bus cannot do byte-by-byte access and |
|
* each access must be aligned. |
|
*/ |
|
size_t read_sz, pos; |
|
size_t remaining = len; |
|
uint8_t *mem_ptr; |
|
size_t count = 0; |
|
int ret; |
|
|
|
union { |
|
uint32_t u32; |
|
uint8_t b8[4]; |
|
} data; |
|
|
|
/* Max alignment */ |
|
if (align > 4) { |
|
ret = -1; |
|
goto out; |
|
} |
|
|
|
/* Round down according to alignment. */ |
|
mem_ptr = UINT_TO_POINTER(ROUND_DOWN(addr, align)); |
|
|
|
/* |
|
* Figure out how many bytes to skip (pos) and how many |
|
* bytes to read at the beginning of aligned memory access. |
|
*/ |
|
pos = addr & (align - 1); |
|
read_sz = MIN(len, align - pos); |
|
|
|
/* Loop till there is nothing more to read. */ |
|
while (remaining > 0) { |
|
data.u32 = *(uint32_t *)mem_ptr; |
|
|
|
/* |
|
* Read read_sz bytes from memory and |
|
* convert the binary data into hexadecimal. |
|
*/ |
|
count += gdb_bin2hex(&data.b8[pos], read_sz, |
|
buf + count, buf_len - count); |
|
|
|
remaining -= read_sz; |
|
if (remaining > align) { |
|
read_sz = align; |
|
} else { |
|
read_sz = remaining; |
|
} |
|
|
|
/* Read the next aligned datum. */ |
|
mem_ptr += align; |
|
|
|
/* |
|
* Any memory accesses after the first one are |
|
* aligned by design. So there is no need to skip |
|
* any bytes. |
|
*/ |
|
pos = 0; |
|
}; |
|
|
|
ret = count; |
|
|
|
out: |
|
return ret; |
|
} |
|
|
|
/** |
|
* Read data from a given memory address and length. |
|
* |
|
* @return Number of bytes read from memory, or -1 if error |
|
*/ |
|
static int gdb_mem_read(uint8_t *buf, size_t buf_len, |
|
uintptr_t addr, size_t len) |
|
{ |
|
uint8_t align; |
|
int ret; |
|
|
|
/* |
|
* Make sure there is enough space in the output |
|
* buffer for hexadecimal representation. |
|
*/ |
|
if ((len * 2) > buf_len) { |
|
ret = -1; |
|
goto out; |
|
} |
|
|
|
if (!gdb_mem_can_read(addr, len, &align)) { |
|
ret = -1; |
|
goto out; |
|
} |
|
|
|
if (align > 1) { |
|
ret = gdb_mem_read_aligned(buf, buf_len, |
|
addr, len, |
|
align); |
|
} else { |
|
ret = gdb_mem_read_unaligned(buf, buf_len, |
|
addr, len); |
|
} |
|
|
|
out: |
|
return ret; |
|
} |
|
|
|
/* Write memory byte-by-byte */ |
|
static int gdb_mem_write_unaligned(const uint8_t *buf, uintptr_t addr, |
|
size_t len) |
|
{ |
|
uint8_t data; |
|
int ret; |
|
size_t count = 0; |
|
|
|
while (len > 0) { |
|
size_t cnt = hex2bin(buf, 2, &data, sizeof(data)); |
|
|
|
if (cnt == 0) { |
|
ret = -1; |
|
goto out; |
|
} |
|
|
|
*(uint8_t *)addr = data; |
|
|
|
count += cnt; |
|
addr++; |
|
buf += 2; |
|
len--; |
|
} |
|
|
|
ret = count; |
|
|
|
out: |
|
return ret; |
|
} |
|
|
|
/* Write memory with alignment constraint */ |
|
static int gdb_mem_write_aligned(const uint8_t *buf, uintptr_t addr, |
|
size_t len, uint8_t align) |
|
{ |
|
size_t pos, write_sz; |
|
uint8_t *mem_ptr; |
|
size_t count = 0; |
|
int ret; |
|
|
|
/* |
|
* Incoming buf is of hexadecimal characters, |
|
* so binary data size is half of that. |
|
*/ |
|
size_t remaining = len; |
|
|
|
union { |
|
uint32_t u32; |
|
uint8_t b8[4]; |
|
} data; |
|
|
|
/* Max alignment */ |
|
if (align > 4) { |
|
ret = -1; |
|
goto out; |
|
} |
|
|
|
/* |
|
* Round down according to alignment. |
|
* Read the data (of aligned size) first |
|
* as we need to do read-modify-write. |
|
*/ |
|
mem_ptr = UINT_TO_POINTER(ROUND_DOWN(addr, align)); |
|
data.u32 = *(uint32_t *)mem_ptr; |
|
|
|
/* |
|
* Figure out how many bytes to skip (pos) and how many |
|
* bytes to write at the beginning of aligned memory access. |
|
*/ |
|
pos = addr & (align - 1); |
|
write_sz = MIN(len, align - pos); |
|
|
|
/* Loop till there is nothing more to write. */ |
|
while (remaining > 0) { |
|
/* |
|
* Write write_sz bytes from memory and |
|
* convert the binary data into hexadecimal. |
|
*/ |
|
size_t cnt = hex2bin(buf, write_sz * 2, |
|
&data.b8[pos], write_sz); |
|
|
|
if (cnt == 0) { |
|
ret = -1; |
|
goto out; |
|
} |
|
|
|
count += cnt; |
|
buf += write_sz * 2; |
|
|
|
remaining -= write_sz; |
|
if (remaining > align) { |
|
write_sz = align; |
|
} else { |
|
write_sz = remaining; |
|
} |
|
|
|
/* Write data to memory */ |
|
*(uint32_t *)mem_ptr = data.u32; |
|
|
|
/* Point to the next aligned datum. */ |
|
mem_ptr += align; |
|
|
|
if (write_sz != align) { |
|
/* |
|
* Since we are not writing a full aligned datum, |
|
* we need to do read-modify-write. Hence reading |
|
* it here before the next hex2bin() call. |
|
*/ |
|
data.u32 = *(uint32_t *)mem_ptr; |
|
} |
|
|
|
/* |
|
* Any memory accesses after the first one are |
|
* aligned by design. So there is no need to skip |
|
* any bytes. |
|
*/ |
|
pos = 0; |
|
}; |
|
|
|
ret = count; |
|
|
|
out: |
|
return ret; |
|
} |
|
|
|
/** |
|
* Write data to a given memory address and length. |
|
* |
|
* @return Number of bytes written to memory, or -1 if error |
|
*/ |
|
static int gdb_mem_write(const uint8_t *buf, uintptr_t addr, |
|
size_t len) |
|
{ |
|
uint8_t align; |
|
int ret; |
|
|
|
if (!gdb_mem_can_write(addr, len, &align)) { |
|
ret = -1; |
|
goto out; |
|
} |
|
|
|
if (align > 1) { |
|
ret = gdb_mem_write_aligned(buf, addr, len, align); |
|
} else { |
|
ret = gdb_mem_write_unaligned(buf, addr, len); |
|
} |
|
|
|
|
|
arch_gdb_post_memory_write(addr, len, align); |
|
|
|
out: |
|
return ret; |
|
} |
|
|
|
/** |
|
* Send a exception packet "T <value>" |
|
*/ |
|
static int gdb_send_exception(uint8_t *buf, size_t len, uint8_t exception) |
|
{ |
|
size_t size; |
|
|
|
#ifdef CONFIG_GDBSTUB_TRACE |
|
printk("gdbstub:%s exception=0x%x\n", __func__, exception); |
|
#endif |
|
|
|
*buf = 'T'; |
|
size = gdb_bin2hex(&exception, 1, buf + 1, len - 1); |
|
if (size == 0) { |
|
return -1; |
|
} |
|
|
|
/* Related to 'T' */ |
|
size++; |
|
|
|
return gdb_send_packet(buf, size); |
|
} |
|
|
|
static bool gdb_qsupported(uint8_t *buf, size_t len, enum gdb_loop_state *next_state) |
|
{ |
|
size_t n = 0; |
|
const char *c_buf = (const char *) buf; |
|
|
|
if (strstr(buf, "qSupported") != c_buf) { |
|
return false; |
|
} |
|
|
|
gdb_send_packet(buf, n); |
|
return true; |
|
} |
|
|
|
static void gdb_q_packet(uint8_t *buf, size_t len, enum gdb_loop_state *next_state) |
|
{ |
|
if (gdb_qsupported(buf, len, next_state)) { |
|
return; |
|
} |
|
|
|
gdb_send_packet(NULL, 0); |
|
} |
|
|
|
static void gdb_v_packet(uint8_t *buf, size_t len, enum gdb_loop_state *next_state) |
|
{ |
|
gdb_send_packet(NULL, 0); |
|
} |
|
|
|
/** |
|
* Synchronously communicate with gdb on the host |
|
*/ |
|
int z_gdb_main_loop(struct gdb_ctx *ctx) |
|
{ |
|
/* 'static' modifier is intentional so the buffer |
|
* is not declared inside running stack, which may |
|
* not have enough space. |
|
*/ |
|
static uint8_t buf[GDB_PACKET_SIZE]; |
|
enum gdb_loop_state state; |
|
|
|
state = GDB_LOOP_RECEIVING; |
|
|
|
/* Only send exception if this is not the first |
|
* GDB break. |
|
*/ |
|
if (not_first_start) { |
|
gdb_send_exception(buf, sizeof(buf), ctx->exception); |
|
} else { |
|
not_first_start = true; |
|
} |
|
|
|
#define CHECK_ERROR(condition) \ |
|
{ \ |
|
if ((condition)) { \ |
|
state = GDB_LOOP_ERROR; \ |
|
break; \ |
|
} \ |
|
} |
|
|
|
#define CHECK_SYMBOL(c) \ |
|
{ \ |
|
CHECK_ERROR(ptr == NULL || *ptr != (c)); \ |
|
ptr++; \ |
|
} |
|
|
|
#define CHECK_UINT(arg) \ |
|
{ \ |
|
arg = strtoul((const char *)ptr, (char **)&ptr, 16); \ |
|
CHECK_ERROR(ptr == NULL); \ |
|
} |
|
|
|
while (state == GDB_LOOP_RECEIVING) { |
|
uint8_t *ptr; |
|
size_t data_len, pkt_len; |
|
uintptr_t addr; |
|
uint32_t type; |
|
int ret; |
|
|
|
ret = gdb_get_packet(buf, sizeof(buf), &pkt_len); |
|
if ((ret == -1) || (ret == -2)) { |
|
/* |
|
* Send error and wait for next packet. |
|
* |
|
* -1: Checksum error. |
|
* -2: Packet too big. |
|
*/ |
|
gdb_send_packet(GDB_ERROR_GENERAL, 3); |
|
continue; |
|
} |
|
|
|
if (pkt_len == 0) { |
|
continue; |
|
} |
|
|
|
ptr = buf; |
|
|
|
#ifdef CONFIG_GDBSTUB_TRACE |
|
printk("gdbstub:%s got '%c'(0x%x) command\n", __func__, *ptr, *ptr); |
|
#endif |
|
|
|
switch (*ptr++) { |
|
|
|
/** |
|
* Read from the memory |
|
* Format: m addr,length |
|
*/ |
|
case 'm': |
|
CHECK_UINT(addr); |
|
CHECK_SYMBOL(','); |
|
CHECK_UINT(data_len); |
|
|
|
/* Read Memory */ |
|
|
|
/* |
|
* GDB ask the guest to read parameters when |
|
* the user request backtrace. If the |
|
* parameter is a NULL pointer this will cause |
|
* a fault. Just send a packet informing that |
|
* this address is invalid |
|
*/ |
|
if (addr == 0L) { |
|
gdb_send_packet(GDB_ERROR_MEMORY, 3); |
|
break; |
|
} |
|
ret = gdb_mem_read(buf, sizeof(buf), addr, data_len); |
|
CHECK_ERROR(ret == -1); |
|
gdb_send_packet(buf, ret); |
|
break; |
|
|
|
/** |
|
* Write to memory |
|
* Format: M addr,length:val |
|
*/ |
|
case 'M': |
|
CHECK_UINT(addr); |
|
CHECK_SYMBOL(','); |
|
CHECK_UINT(data_len); |
|
CHECK_SYMBOL(':'); |
|
|
|
if (addr == 0L) { |
|
gdb_send_packet(GDB_ERROR_MEMORY, 3); |
|
break; |
|
} |
|
|
|
/* Write Memory */ |
|
pkt_len = gdb_mem_write(ptr, addr, data_len); |
|
CHECK_ERROR(pkt_len == -1); |
|
gdb_send_packet("OK", 2); |
|
break; |
|
|
|
/* |
|
* Continue ignoring the optional address |
|
* Format: c addr |
|
*/ |
|
case 'c': |
|
arch_gdb_continue(); |
|
state = GDB_LOOP_CONTINUE; |
|
break; |
|
|
|
/* |
|
* Step one instruction ignoring the optional address |
|
* s addr..addr |
|
*/ |
|
case 's': |
|
arch_gdb_step(); |
|
state = GDB_LOOP_CONTINUE; |
|
break; |
|
|
|
/* |
|
* Read all registers |
|
* Format: g |
|
*/ |
|
case 'g': |
|
pkt_len = arch_gdb_reg_readall(ctx, buf, sizeof(buf)); |
|
CHECK_ERROR(pkt_len == 0); |
|
gdb_send_packet(buf, pkt_len); |
|
break; |
|
|
|
/** |
|
* Write the value of the CPU registers |
|
* Format: G XX... |
|
*/ |
|
case 'G': |
|
pkt_len = arch_gdb_reg_writeall(ctx, ptr, pkt_len - 1); |
|
CHECK_ERROR(pkt_len == 0); |
|
gdb_send_packet("OK", 2); |
|
break; |
|
|
|
/** |
|
* Read the value of a register |
|
* Format: p n |
|
*/ |
|
case 'p': |
|
CHECK_UINT(addr); |
|
|
|
/* Read Register */ |
|
pkt_len = arch_gdb_reg_readone(ctx, buf, sizeof(buf), addr); |
|
CHECK_ERROR(pkt_len == 0); |
|
gdb_send_packet(buf, pkt_len); |
|
break; |
|
|
|
/** |
|
* Write data into a specific register |
|
* Format: P register=value |
|
*/ |
|
case 'P': |
|
CHECK_UINT(addr); |
|
CHECK_SYMBOL('='); |
|
|
|
pkt_len = arch_gdb_reg_writeone(ctx, ptr, strlen(ptr), addr); |
|
CHECK_ERROR(pkt_len == 0); |
|
gdb_send_packet("OK", 2); |
|
break; |
|
|
|
/* |
|
* Breakpoints and Watchpoints |
|
*/ |
|
case 'z': |
|
__fallthrough; |
|
case 'Z': |
|
CHECK_UINT(type); |
|
CHECK_SYMBOL(','); |
|
CHECK_UINT(addr); |
|
CHECK_SYMBOL(','); |
|
CHECK_UINT(data_len); |
|
|
|
if (buf[0] == 'Z') { |
|
ret = arch_gdb_add_breakpoint(ctx, type, |
|
addr, data_len); |
|
} else if (buf[0] == 'z') { |
|
ret = arch_gdb_remove_breakpoint(ctx, type, |
|
addr, data_len); |
|
} |
|
|
|
if (ret == -2) { |
|
/* breakpoint/watchpoint not supported */ |
|
gdb_send_packet(NULL, 0); |
|
} else if (ret == -1) { |
|
state = GDB_LOOP_ERROR; |
|
} else { |
|
gdb_send_packet("OK", 2); |
|
} |
|
|
|
break; |
|
|
|
|
|
/* What cause the pause */ |
|
case '?': |
|
gdb_send_exception(buf, sizeof(buf), |
|
ctx->exception); |
|
break; |
|
|
|
/* Query packets*/ |
|
case 'q': |
|
__fallthrough; |
|
case 'Q': |
|
gdb_q_packet(buf, sizeof(buf), &state); |
|
break; |
|
|
|
/* v packets */ |
|
case 'v': |
|
gdb_v_packet(buf, sizeof(buf), &state); |
|
break; |
|
|
|
/* |
|
* Not supported action |
|
*/ |
|
default: |
|
gdb_send_packet(NULL, 0); |
|
break; |
|
} |
|
|
|
/* |
|
* If this is an recoverable error, send an error message to |
|
* GDB and continue the debugging session. |
|
*/ |
|
if (state == GDB_LOOP_ERROR) { |
|
gdb_send_packet(GDB_ERROR_GENERAL, 3); |
|
state = GDB_LOOP_RECEIVING; |
|
} |
|
} |
|
|
|
#undef CHECK_ERROR |
|
#undef CHECK_UINT |
|
#undef CHECK_SYMBOL |
|
|
|
return 0; |
|
} |
|
|
|
int gdb_init(void) |
|
{ |
|
#ifdef CONFIG_GDBSTUB_TRACE |
|
printk("gdbstub:%s enter\n", __func__); |
|
#endif |
|
if (z_gdb_backend_init() == -1) { |
|
LOG_ERR("Could not initialize gdbstub backend."); |
|
return -1; |
|
} |
|
|
|
arch_gdb_init(); |
|
|
|
#ifdef CONFIG_GDBSTUB_TRACE |
|
printk("gdbstub:%s exit\n", __func__); |
|
#endif |
|
return 0; |
|
} |
|
|
|
#ifdef CONFIG_GDBSTUB_ENTER_IMMEDIATELY |
|
#ifdef CONFIG_XTENSA |
|
/* |
|
* Interrupt stacks are being setup during init and are not |
|
* available before POST_KERNEL. Xtensa needs to trigger |
|
* interrupts to get into GDB stub. So this can only be |
|
* initialized in POST_KERNEL, or else the interrupt would not be |
|
* using the correct interrupt stack and will result in |
|
* double exception. |
|
*/ |
|
SYS_INIT(gdb_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); |
|
#else |
|
SYS_INIT(gdb_init, PRE_KERNEL_2, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); |
|
#endif |
|
#endif
|
|
|