gdbstub: move to a separate component, support multiple targets

This commit is contained in:
Ivan Grokhotkov
2019-06-14 00:43:49 +08:00
parent 240192f9fa
commit 66e0b2f9df
16 changed files with 1023 additions and 576 deletions

View File

@@ -0,0 +1,343 @@
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string.h>
#include "esp_gdbstub.h"
#include "esp_gdbstub_common.h"
#include "sdkconfig.h"
#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
static void init_task_info();
static void find_paniced_task_index();
static int handle_task_commands(unsigned char *cmd, int len);
#endif
static void send_reason();
static esp_gdbstub_scratch_t s_scratch;
void esp_gdbstub_panic_handler(esp_gdbstub_frame_t *frame)
{
#ifndef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
esp_gdbstub_frame_to_regfile(frame, &s_scratch.regfile);
#else
if (s_scratch.state == GDBSTUB_STARTED) {
/* We have re-entered GDB Stub. Try disabling task support. */
s_scratch.state = GDBSTUB_TASK_SUPPORT_DISABLED;
/* Flush any pending GDB packet (this creates a garbage value) */
esp_gdbstub_send_end();
} else if (s_scratch.state == GDBSTUB_NOT_STARTED) {
s_scratch.state = GDBSTUB_STARTED;
/* Save the paniced frame and get the list of tasks */
memcpy(&s_scratch.paniced_frame, frame, sizeof(*frame));
esp_gdbstub_frame_to_regfile(frame, &s_scratch.regfile);
init_task_info();
find_paniced_task_index();
/* Current task is the paniced task */
if (s_scratch.paniced_task_index == GDBSTUB_CUR_TASK_INDEX_UNKNOWN) {
s_scratch.current_task_index = 0;
}
}
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
esp_gdbstub_target_init();
s_scratch.signal = esp_gdbstub_get_signal(frame);
send_reason();
while (true) {
unsigned char *cmd;
size_t size;
int res = esp_gdbstub_read_command(&cmd, &size);
if (res > 0) {
/* character received instead of a command */
continue;
}
if (res == GDBSTUB_ST_ERR) {
esp_gdbstub_send_str_packet("E01");
continue;
}
res = esp_gdbstub_handle_command(cmd, size);
if (res == GDBSTUB_ST_ERR) {
esp_gdbstub_send_str_packet(NULL);
}
}
}
static void send_reason()
{
esp_gdbstub_send_start();
esp_gdbstub_send_char('T');
esp_gdbstub_send_hex(s_scratch.signal, 8);
esp_gdbstub_send_end();
}
static uint32_t gdbstub_hton(uint32_t i)
{
return __builtin_bswap32(i);
}
/** Send all registers to gdb */
static void handle_g_command(const unsigned char* cmd, int len)
{
uint32_t *p = (uint32_t *) &s_scratch.regfile;
esp_gdbstub_send_start();
for (int i = 0; i < sizeof(s_scratch.regfile) / sizeof(*p); ++i) {
esp_gdbstub_send_hex(gdbstub_hton(*p++), 32);
}
esp_gdbstub_send_end();
}
/** Receive register values from gdb */
static void handle_G_command(const unsigned char* cmd, int len)
{
uint32_t *p = (uint32_t *) &s_scratch.regfile;
for (int i = 0; i < sizeof(s_scratch.regfile) / sizeof(*p); ++i) {
*p++ = gdbstub_hton(esp_gdbstub_gethex(&cmd, 32));
}
esp_gdbstub_send_str_packet("OK");
}
/** Read memory to gdb */
static void handle_m_command(const unsigned char* cmd, int len)
{
intptr_t addr = (intptr_t) esp_gdbstub_gethex(&cmd, -1);
cmd++;
uint32_t size = esp_gdbstub_gethex(&cmd, -1);
if (esp_gdbstub_readmem(addr) < 0 || esp_gdbstub_readmem(addr + size - 1) < 0) {
esp_gdbstub_send_str_packet("E01");
return;
}
esp_gdbstub_send_start();
for (int i = 0; i < size; ++i) {
int b = esp_gdbstub_readmem(addr++);
esp_gdbstub_send_hex(b, 8);
}
esp_gdbstub_send_end();
}
/** Handle a command received from gdb */
int esp_gdbstub_handle_command(unsigned char *cmd, int len)
{
unsigned char *data = cmd + 1;
if (cmd[0] == 'g')
{
handle_g_command(data, len - 1);
} else if (cmd[0] == 'G') {
/* receive content for all registers from gdb */
handle_G_command(data, len - 1);
} else if (cmd[0] == 'm') {
/* read memory to gdb */
handle_m_command(data, len - 1);
} else if (cmd[0] == '?') {
/* Reply with stop reason */
send_reason();
#if CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
} else if (s_scratch.state != GDBSTUB_TASK_SUPPORT_DISABLED) {
return handle_task_commands(cmd, len);
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
} else {
/* Unrecognized command */
return GDBSTUB_ST_ERR;
}
return GDBSTUB_ST_OK;
}
/* Everything below is related to the support for listing FreeRTOS tasks as threads in GDB */
#ifdef CONFIG_ESP_GDBSTUB_SUPPORT_TASKS
static void init_task_info()
{
unsigned tcb_size;
s_scratch.task_count = uxTaskGetSnapshotAll(s_scratch.tasks, GDBSTUB_TASKS_NUM, &tcb_size);
}
static bool get_task_handle(size_t index, TaskHandle_t *handle)
{
if (index >= s_scratch.task_count) {
return false;
}
*handle = (TaskHandle_t) s_scratch.tasks[index].pxTCB;
return true;
}
/** Get the index of the task running on the current CPU, and save the result */
static void find_paniced_task_index()
{
TaskHandle_t cur_handle = xTaskGetCurrentTaskHandleForCPU(xPortGetCoreID());
TaskHandle_t handle;
for (int i = 0; i < s_scratch.task_count; i++) {
if (get_task_handle(i, &handle) && cur_handle == handle) {
s_scratch.paniced_task_index = i;
return;
}
}
s_scratch.paniced_task_index = GDBSTUB_CUR_TASK_INDEX_UNKNOWN;
}
/** H command sets the "current task" for the purpose of further commands */
static void handle_H_command(const unsigned char* cmd, int len)
{
if (cmd[1] == 'g' || cmd[1] == 'c') {
const char *ret = "OK";
cmd += 2;
int requested_task_index = esp_gdbstub_gethex(&cmd, -1);
if (requested_task_index == s_scratch.paniced_task_index ||
(requested_task_index == 0 && s_scratch.current_task_index == GDBSTUB_CUR_TASK_INDEX_UNKNOWN)) {
/* Get the registers of the paniced task */
esp_gdbstub_frame_to_regfile(&s_scratch.paniced_frame, &s_scratch.regfile);
} else if (requested_task_index > s_scratch.task_count) {
ret = "E00";
} else {
TaskHandle_t handle;
get_task_handle(requested_task_index, &handle);
/* FIXME: for the task currently running on the other CPU, extracting the registers from TCB
* isn't valid. Need to use some IPC mechanism to obtain the registers of the other CPU
*/
esp_gdbstub_tcb_to_regfile(handle, &s_scratch.regfile);
}
esp_gdbstub_send_str_packet(ret);
} else {
esp_gdbstub_send_str_packet(NULL);
}
}
/** qC returns the current thread ID */
static void handle_qC_command(const unsigned char* cmd, int len)
{
esp_gdbstub_send_start();
esp_gdbstub_send_str("QC");
esp_gdbstub_send_hex(s_scratch.current_task_index, 32);
esp_gdbstub_send_end();
}
/** T command checks if the task is alive.
* Since GDB isn't going to ask about the tasks which haven't been listed by q*ThreadInfo,
* and the state of tasks can not change (no stepping allowed), simply return "OK" here.
*/
static void handle_T_command(const unsigned char* cmd, int len)
{
esp_gdbstub_send_str_packet("OK");
}
/** qfThreadInfo requests the start of the thread list, qsThreadInfo (below) is repeated to
* get the subsequent threads.
*/
static void handle_qfThreadInfo_command(const unsigned char* cmd, int len)
{
/* The first task in qfThreadInfo reply is going to be the one which GDB will request to stop.
* Therefore it has to be the paniced task.
* Reply with the paniced task index, and later skip over this index while handling qsThreadInfo
*/
esp_gdbstub_send_start();
esp_gdbstub_send_str("m");
esp_gdbstub_send_hex(s_scratch.paniced_task_index, 32);
esp_gdbstub_send_end();
s_scratch.thread_info_index = 0;
}
static void handle_qsThreadInfo_command(const unsigned char* cmd, int len)
{
int next_task_index = ++s_scratch.thread_info_index;
if (next_task_index == s_scratch.task_count) {
/* No more tasks */
esp_gdbstub_send_str_packet("l");
return;
}
if (next_task_index == s_scratch.paniced_task_index) {
/* Have already sent this one in the reply to qfThreadInfo, skip over it */
handle_qsThreadInfo_command(cmd, len);
return;
}
esp_gdbstub_send_start();
esp_gdbstub_send_str("m");
esp_gdbstub_send_hex(next_task_index, 32);
esp_gdbstub_send_end();
}
/** qThreadExtraInfo requests the thread name */
static void handle_qThreadExtraInfo_command(const unsigned char* cmd, int len)
{
cmd += sizeof("qThreadExtraInfo,") - 1;
int task_index = esp_gdbstub_gethex(&cmd, -1);
TaskHandle_t handle;
if (!get_task_handle(task_index, &handle)) {
esp_gdbstub_send_str_packet("E01");
return;
}
esp_gdbstub_send_start();
const char* task_name = pcTaskGetTaskName(handle);
while (*task_name) {
esp_gdbstub_send_hex(*task_name, 8);
task_name++;
}
/** TODO: add "Running" or "Suspended" and "CPU0" or "CPU1" */
esp_gdbstub_send_end();
}
bool command_name_matches(const char* pattern, const unsigned char* ucmd, int len)
{
const char* cmd = (const char*) ucmd;
const char* end = cmd + len;
for (; *pattern && cmd < end; ++cmd, ++pattern) {
if (*pattern == '?') {
continue;
}
if (*pattern != *cmd) {
return false;
}
}
return *pattern == 0 && (cmd == end || *cmd == ',');
}
/** Handle all the thread-related commands */
static int handle_task_commands(unsigned char *cmd, int len)
{
if (cmd[0] == 'H') {
/* Continue with task */
handle_H_command(cmd, len);
} else if (cmd[0] == 'T') {
/* Task alive check */
handle_T_command(cmd, len);
} else if (cmd[0] == 'q') {
if (command_name_matches("qfThreadInfo", cmd, len)) {
handle_qfThreadInfo_command(cmd, len);
} else if (command_name_matches("qsThreadInfo", cmd, len)) {
handle_qsThreadInfo_command(cmd, len);
} else if (command_name_matches("qC", cmd, len)) {
handle_qC_command(cmd, len);
} else if (command_name_matches("qThreadExtraInfo", cmd, len)) {
handle_qThreadExtraInfo_command(cmd, len);
} else {
/* Unrecognized command */
return GDBSTUB_ST_ERR;
}
} else {
/* Unrecognized command */
return GDBSTUB_ST_ERR;
}
return GDBSTUB_ST_OK;
}
#endif // CONFIG_ESP_GDBSTUB_SUPPORT_TASKS

View File

@@ -0,0 +1,177 @@
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "esp_gdbstub_common.h"
// GDB command input buffer
static unsigned char s_cmd[GDBSTUB_CMD_BUFLEN];
// Running checksum of the output packet
static char s_chsum;
// Send the start of a packet; reset checksum calculation.
void esp_gdbstub_send_start()
{
s_chsum = 0;
esp_gdbstub_putchar('$');
}
// Send a char as part of a packet
void esp_gdbstub_send_char(char c)
{
if (c == '#' || c == '$' || c == '}' || c == '*') {
esp_gdbstub_putchar('}');
esp_gdbstub_putchar(c ^ 0x20);
s_chsum += (c ^ 0x20) + '}';
} else {
esp_gdbstub_putchar(c);
s_chsum += c;
}
}
// Send a string as part of a packet
void esp_gdbstub_send_str(const char *c)
{
while (*c != 0) {
esp_gdbstub_send_char(*c);
c++;
}
}
// Send a hex val as part of a packet.
// 'bits'/4 dictates the number of hex chars sent.
void esp_gdbstub_send_hex(int val, int bits)
{
const char* hex_chars = "0123456789abcdef";
for (int i = bits; i > 0; i -= 4) {
esp_gdbstub_send_char(hex_chars[(val >> (i - 4)) & 0xf]);
}
}
// Finish sending a packet.
void esp_gdbstub_send_end()
{
esp_gdbstub_putchar('#');
esp_gdbstub_send_hex(s_chsum, 8);
}
// Send a packet with a string as content
void esp_gdbstub_send_str_packet(const char* str)
{
esp_gdbstub_send_start();
if (str != NULL) {
esp_gdbstub_send_str(str);
}
esp_gdbstub_send_end();
}
// Grab a hex value from the gdb packet. Ptr will get positioned on the end
// of the hex string, as far as the routine has read into it. Bits/4 indicates
// the max amount of hex chars it gobbles up. Bits can be -1 to eat up as much
// hex chars as possible.
uint32_t esp_gdbstub_gethex(const unsigned char **ptr, int bits)
{
int i;
int no;
uint32_t v = 0;
char c;
no = bits / 4;
if (bits == -1) {
no = 64;
}
for (i = 0; i < no; i++) {
c = **ptr;
(*ptr)++;
if (c >= '0' && c <= '9') {
v <<= 4;
v |= (c - '0');
} else if (c >= 'A' && c <= 'F') {
v <<= 4;
v |= (c - 'A') + 10;
} else if (c >= 'a' && c <= 'f') {
v <<= 4;
v |= (c - 'a') + 10;
} else if (c == '#') {
if (bits == -1) {
(*ptr)--;
return v;
}
return GDBSTUB_ST_ENDPACKET;
} else {
if (bits == -1) {
(*ptr)--;
return v;
}
return GDBSTUB_ST_ERR;
}
}
return v;
}
// Lower layer: grab a command packet and check the checksum
// Calls gdbHandleCommand on the packet if the checksum is OK
// Returns GDBSTUB_ST_OK on success, GDBSTUB_ST_ERR when checksum fails, a
// character if it is received instead of the GDB packet
// start char.
int esp_gdbstub_read_command(unsigned char **out_cmd, size_t *out_size)
{
unsigned char c;
unsigned char chsum = 0;
unsigned char sentchs[2];
int p = 0;
c = esp_gdbstub_getchar();
if (c != '$') {
return c;
}
while (1) {
c = esp_gdbstub_getchar();
if (c == '#') {
// end of packet, checksum follows
s_cmd[p] = 0;
break;
}
chsum += c;
if (c == '$') {
// restart packet?
chsum = 0;
p = 0;
continue;
}
if (c == '}') {
//escape the next char
c = esp_gdbstub_getchar();
chsum += c;
c ^= 0x20;
}
s_cmd[p++] = c;
if (p >= GDBSTUB_CMD_BUFLEN) {
return GDBSTUB_ST_ERR;
}
}
// A # has been received. Get and check the received chsum.
sentchs[0] = esp_gdbstub_getchar();
sentchs[1] = esp_gdbstub_getchar();
const unsigned char* c_ptr = &sentchs[0];
unsigned char rchsum = esp_gdbstub_gethex(&c_ptr, 8);
if (rchsum != chsum) {
esp_gdbstub_putchar('-');
return GDBSTUB_ST_ERR;
} else {
esp_gdbstub_putchar('+');
*out_cmd = s_cmd;
*out_size = p;
return GDBSTUB_ST_OK;
}
}