426 lines
7.7 KiB
C
426 lines
7.7 KiB
C
/*
|
|
* Copyright (c) 2020 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <device.h>
|
|
#include <kernel.h>
|
|
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(gdbstub);
|
|
|
|
#include <sys/util.h>
|
|
|
|
#include <ctype.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "gdbstub_backend.h"
|
|
|
|
#define GDB_PACKET_SIZE 256
|
|
|
|
/* 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"
|
|
|
|
/**
|
|
* 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 (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 a packet
|
|
*
|
|
* Return 0 in case of success, otherwise -1
|
|
*/
|
|
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 # or the end of the buffer */
|
|
while (*len < (buf_len - 1)) {
|
|
ch = z_gdb_getchar();
|
|
|
|
if (ch == '#') {
|
|
break;
|
|
}
|
|
|
|
checksum += ch;
|
|
buf[*len] = 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('+');
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Read data from a given memory.
|
|
*
|
|
* Return 0 in case of success, otherwise -1
|
|
*/
|
|
static int gdb_mem_read(uint8_t *buf, size_t buf_len,
|
|
uintptr_t addr, size_t len)
|
|
{
|
|
uint8_t data;
|
|
size_t pos, count = 0;
|
|
|
|
if (len > buf_len) {
|
|
return -1;
|
|
}
|
|
|
|
/* Read from system memory */
|
|
for (pos = 0; pos < len; pos++) {
|
|
data = *(uint8_t *)(addr + pos);
|
|
count += bin2hex(&data, 1, buf + count, buf_len - count);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* Write data in a given memory.
|
|
*
|
|
* Return 0 in case of success, otherwise -1
|
|
*/
|
|
static int gdb_mem_write(const uint8_t *buf, uintptr_t addr,
|
|
size_t len)
|
|
{
|
|
uint8_t data;
|
|
|
|
while (len > 0) {
|
|
size_t ret = hex2bin(buf, 2, &data, sizeof(data));
|
|
|
|
if (ret == 0) {
|
|
return -1;
|
|
}
|
|
|
|
*(uint8_t *)addr = data;
|
|
|
|
addr++;
|
|
buf += 2;
|
|
len--;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Send a exception packet "T <value>"
|
|
*/
|
|
static int gdb_send_exception(uint8_t *buf, size_t len, uint8_t exception)
|
|
{
|
|
size_t size;
|
|
|
|
*buf = 'T';
|
|
size = bin2hex(&exception, 1, buf + 1, len - 1);
|
|
if (size == 0) {
|
|
return -1;
|
|
}
|
|
|
|
/* Related to 'T' */
|
|
size++;
|
|
|
|
return gdb_send_packet(buf, size);
|
|
}
|
|
|
|
/**
|
|
* Synchronously communicate with gdb on the host
|
|
*/
|
|
int z_gdb_main_loop(struct gdb_ctx *ctx, bool start)
|
|
{
|
|
uint8_t buf[GDB_PACKET_SIZE];
|
|
enum loop_state {
|
|
RECEIVING,
|
|
CONTINUE,
|
|
FAILED
|
|
} state;
|
|
|
|
state = RECEIVING;
|
|
|
|
if (start == false) {
|
|
gdb_send_exception(buf, sizeof(buf), ctx->exception);
|
|
}
|
|
|
|
#define CHECK_FAILURE(condition) \
|
|
{ \
|
|
if ((condition)) { \
|
|
state = FAILED; \
|
|
break; \
|
|
} \
|
|
}
|
|
|
|
#define CHECK_SYMBOL(c) \
|
|
{ \
|
|
CHECK_FAILURE(ptr == NULL || *ptr != (c)); \
|
|
ptr++; \
|
|
}
|
|
|
|
#define CHECK_INT(arg) \
|
|
{ \
|
|
arg = strtol((const char *)ptr, (char **)&ptr, 16); \
|
|
CHECK_FAILURE(ptr == NULL); \
|
|
}
|
|
|
|
while (state == RECEIVING) {
|
|
uint8_t *ptr;
|
|
size_t data_len, pkt_len;
|
|
uintptr_t addr;
|
|
int ret;
|
|
|
|
ret = gdb_get_packet(buf, sizeof(buf), &pkt_len);
|
|
CHECK_FAILURE(ret == -1);
|
|
|
|
if (pkt_len == 0) {
|
|
continue;
|
|
}
|
|
|
|
ptr = buf;
|
|
|
|
switch (*ptr++) {
|
|
|
|
/**
|
|
* Read from the memory
|
|
* Format: m addr,length
|
|
*/
|
|
case 'm':
|
|
CHECK_INT(addr);
|
|
CHECK_SYMBOL(',');
|
|
CHECK_INT(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_FAILURE(ret == -1);
|
|
gdb_send_packet(buf, ret);
|
|
break;
|
|
|
|
/**
|
|
* Write to memory
|
|
* Format: M addr,length:val
|
|
*/
|
|
case 'M':
|
|
CHECK_INT(addr);
|
|
CHECK_SYMBOL(',');
|
|
CHECK_INT(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_FAILURE(pkt_len == -1);
|
|
gdb_send_packet("OK", 2);
|
|
break;
|
|
|
|
/*
|
|
* Continue ignoring the optional address
|
|
* Format: c addr
|
|
*/
|
|
case 'c':
|
|
arch_gdb_continue();
|
|
state = CONTINUE;
|
|
break;
|
|
|
|
/*
|
|
* Step one instruction ignoring the optional address
|
|
* s addr..addr
|
|
*/
|
|
case 's':
|
|
arch_gdb_step();
|
|
state = CONTINUE;
|
|
break;
|
|
|
|
/*
|
|
* Read all registers
|
|
* Format: g
|
|
*/
|
|
case 'g':
|
|
pkt_len = bin2hex((const uint8_t *)&(ctx->registers),
|
|
sizeof(ctx->registers), buf, sizeof(buf));
|
|
CHECK_FAILURE(pkt_len == 0);
|
|
gdb_send_packet(buf, pkt_len);
|
|
break;
|
|
|
|
/**
|
|
* Write the value of the CPU registers
|
|
* Fromat: G XX...
|
|
*/
|
|
case 'G':
|
|
pkt_len = hex2bin(ptr, pkt_len - 1,
|
|
(uint8_t *)&(ctx->registers),
|
|
sizeof(ctx->registers));
|
|
CHECK_FAILURE(pkt_len == 0);
|
|
gdb_send_packet("OK", 2);
|
|
break;
|
|
|
|
/**
|
|
* Read the value of a register
|
|
* Format: p n
|
|
*/
|
|
case 'p':
|
|
CHECK_INT(addr);
|
|
CHECK_FAILURE(addr >= ARCH_GDB_NUM_REGISTERS);
|
|
|
|
/* Read Register */
|
|
pkt_len = bin2hex(
|
|
(const uint8_t *)&(ctx->registers[addr]),
|
|
sizeof(ctx->registers[addr]),
|
|
buf, sizeof(buf));
|
|
CHECK_FAILURE(pkt_len == 0);
|
|
gdb_send_packet(buf, pkt_len);
|
|
break;
|
|
|
|
/**
|
|
* Write data into a specific register
|
|
* Format: P register=value
|
|
*/
|
|
case 'P':
|
|
CHECK_INT(addr);
|
|
CHECK_SYMBOL('=');
|
|
|
|
/*
|
|
* GDB requires orig_eax that seems to be
|
|
* Linux specific. Unfortunately if we just
|
|
* return "E01" gdb will stop. So, we just
|
|
* send "OK" and ignore it.
|
|
*/
|
|
if (addr < ARCH_GDB_NUM_REGISTERS) {
|
|
pkt_len = hex2bin(ptr, strlen(ptr),
|
|
(uint8_t *)&(ctx->registers[addr]),
|
|
sizeof(ctx->registers[addr]));
|
|
CHECK_FAILURE(pkt_len == 0);
|
|
}
|
|
gdb_send_packet("OK", 2);
|
|
break;
|
|
|
|
|
|
/* What cause the pause */
|
|
case '?':
|
|
gdb_send_exception(buf, sizeof(buf),
|
|
ctx->exception);
|
|
break;
|
|
|
|
/*
|
|
* Not supported action
|
|
*/
|
|
default:
|
|
gdb_send_packet(NULL, 0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (state == FAILED) {
|
|
gdb_send_packet(GDB_ERROR_GENERAL, 3);
|
|
return -1;
|
|
}
|
|
|
|
#undef CHECK_FAILURE
|
|
#undef CHECK_INT
|
|
#undef CHECK_SYMBOL
|
|
|
|
return 0;
|
|
}
|
|
|
|
int gdb_init(const struct device *arg)
|
|
{
|
|
ARG_UNUSED(arg);
|
|
|
|
if (z_gdb_backend_init() == -1) {
|
|
LOG_ERR("Could not initialize gdbstub backend.");
|
|
return -1;
|
|
}
|
|
|
|
arch_gdb_init();
|
|
return 0;
|
|
}
|
|
|
|
SYS_INIT(gdb_init, PRE_KERNEL_2, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT);
|