mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-19 23:22:16 +03:00
Add gdb stub library
This commit is contained in:
parent
4f35207951
commit
ed785b5f0b
@ -129,8 +129,12 @@ static void do_global_ctors(void) {
|
||||
(*p)();
|
||||
}
|
||||
|
||||
extern "C" void __gdb_init() {}
|
||||
extern "C" void gdb_init(void) __attribute__ ((weak, alias("__gdb_init")));
|
||||
|
||||
void init_done() {
|
||||
system_set_os_print(1);
|
||||
gdb_init();
|
||||
do_global_ctors();
|
||||
esp_schedule();
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ extern void __real_system_restart_local();
|
||||
extern cont_t g_cont;
|
||||
|
||||
|
||||
static void uart_write_char_d(char c);
|
||||
void uart_write_char_d(char c);
|
||||
static void uart0_write_char_d(char c);
|
||||
static void uart1_write_char_d(char c);
|
||||
static void print_stack(uint32_t start, uint32_t end);
|
||||
@ -133,8 +133,8 @@ void uart_write_char_d(char c) {
|
||||
uart1_write_char_d(c);
|
||||
}
|
||||
|
||||
void uart0_write_char_d(char c) {
|
||||
while (((USS(0) >> USTXC) & 0xff) >= 0x7e) { }
|
||||
static void uart0_write_char_d(char c) {
|
||||
while (((USS(0) >> USTXC) & 0xff)) { }
|
||||
|
||||
if (c == '\n') {
|
||||
USF(0) = '\r';
|
||||
@ -142,7 +142,7 @@ void uart0_write_char_d(char c) {
|
||||
USF(0) = c;
|
||||
}
|
||||
|
||||
void uart1_write_char_d(char c) {
|
||||
static void uart1_write_char_d(char c) {
|
||||
while (((USS(1) >> USTXC) & 0xff) >= 0x7e) { }
|
||||
|
||||
if (c == '\n') {
|
||||
|
49
libraries/GDBStub/README.md
Normal file
49
libraries/GDBStub/README.md
Normal file
@ -0,0 +1,49 @@
|
||||
## Using GDB stub
|
||||
|
||||
- Add `#include <GDBStub.h>` to the sketch
|
||||
- Upload the sketch
|
||||
- Redirect serial port to TCP port:
|
||||
```
|
||||
tcp_serial_redirect.py -p /dev/tty.SLAB_USBtoUART -b 115200 --spy -P 9980 --rts=0 --dtr=0
|
||||
```
|
||||
Change port and baud rate as necessary. This command requires python and pyserial.
|
||||
- Observe serial output:
|
||||
```
|
||||
nc localhost 9980
|
||||
```
|
||||
- When crash happens, `Trap %d: pc=%p va=%p` line will appear in serial output.
|
||||
- Close nc and start gdb:
|
||||
```
|
||||
xtensa-lx106-elf-gdb /path/to/Sketch.cpp.elf -ex "target remote :9980"
|
||||
```
|
||||
- Use gdb to inspect program state at the point of an exception.
|
||||
|
||||
## Tips and tricks
|
||||
|
||||
- To halt the target when software WDT fires, add
|
||||
```
|
||||
((int*)0) = 0;
|
||||
```
|
||||
at the top of `__wrap_system_restart_local` in core_esp8266_postmortem.c.
|
||||
|
||||
## License
|
||||
|
||||
GDB Server stub by Marko Mikulicic was taken from Cesanta's smart.js
|
||||
|
||||
https://github.com/cesanta/smart.js
|
||||
|
||||
Copyright (c) 2013-2014 Cesanta Software Limited
|
||||
All rights reserved
|
||||
|
||||
This software is dual-licensed: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation. For the terms of this
|
||||
license, see <http://www.gnu.org/licenses>.
|
||||
|
||||
You are free to use this software under the terms of the GNU General
|
||||
Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
Alternatively, you can license this software under a commercial
|
||||
license, as set out in <https://www.cesanta.com/license>.
|
9
libraries/GDBStub/library.properties
Normal file
9
libraries/GDBStub/library.properties
Normal file
@ -0,0 +1,9 @@
|
||||
name=GDBStub
|
||||
version=0.1
|
||||
author=Marko Mikulicic (Cesanta)
|
||||
maintainer=Ivan Grokhotkov <ivan@esp8266.com>
|
||||
sentence=GDB server stub from Cesanta's Smart.js
|
||||
paragraph=GDB server stub helps debug crashes when JTAG isn't an option.
|
||||
category=Uncategorized
|
||||
url=https://github.com/cesanta/smart.js
|
||||
architectures=esp8266
|
355
libraries/GDBStub/src/GDBStub.c
Normal file
355
libraries/GDBStub/src/GDBStub.c
Normal file
@ -0,0 +1,355 @@
|
||||
/* GDB Server stub by Marko Mikulicic (Cesanta)
|
||||
|
||||
Copyright (c) 2013-2014 Cesanta Software Limited
|
||||
All rights reserved
|
||||
|
||||
This software is dual-licensed: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License version 2 as
|
||||
published by the Free Software Foundation. For the terms of this
|
||||
license, see <http://www.gnu.org/licenses>.
|
||||
|
||||
You are free to use this software under the terms of the GNU General
|
||||
Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||
warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU General Public License for more details.
|
||||
|
||||
Alternatively, you can license this software under a commercial
|
||||
license, as set out in <https://www.cesanta.com/license>.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "ets_sys.h"
|
||||
#include "user_interface.h"
|
||||
#include "esp8266_peri.h"
|
||||
|
||||
#include "xtensa/corebits.h"
|
||||
#include "xtensa/specreg.h"
|
||||
|
||||
#define __stringify_1(x...) #x
|
||||
#define __stringify(x...) __stringify_1(x)
|
||||
#define RSR(sr) \
|
||||
({ \
|
||||
uint32_t r; \
|
||||
asm volatile("rsr %0,"__stringify(sr) : "=a"(r)); \
|
||||
r; \
|
||||
})
|
||||
|
||||
|
||||
/*
|
||||
* the saved registers begin at a fixed position in the xtos
|
||||
* low-level exception handler. I don't know if 0x100 it's just an
|
||||
* artifact of the actual xtos build ESP8266EX is using (although this nice
|
||||
* round number looks deliberate). The exception handler is burned on rom
|
||||
* so it should work on future SDK updates, but not necessarily on future
|
||||
* revisions of the chip.
|
||||
*/
|
||||
#define V7_GDB_SP_OFFSET 0x100
|
||||
|
||||
/*
|
||||
* Addresses in this range are guaranteed to be readable without faulting.
|
||||
* Contains ranges that are unmapped but innocuous.
|
||||
*
|
||||
* Putative ESP8266 memory map at:
|
||||
* https://github.com/esp8266/esp8266-wiki/wiki/Memory-Map
|
||||
*/
|
||||
#define ESP_LOWER_VALID_ADDRESS 0x20000000
|
||||
#define ESP_UPPER_VALID_ADDRESS 0x60000000
|
||||
|
||||
/*
|
||||
* Constructed by xtos.
|
||||
*
|
||||
* There is a UserFrame structure in
|
||||
* ./esp_iot_rtos_sdk/extra_include/xtensa/xtruntime-frames.h
|
||||
*/
|
||||
struct xtos_saved_regs {
|
||||
uint32_t pc; /* instruction causing the trap */
|
||||
uint32_t ps;
|
||||
uint32_t sar;
|
||||
uint32_t vpri; /* current xtos virtual priority */
|
||||
uint32_t a0; /* when __XTENSA_CALL0_ABI__ is true */
|
||||
uint32_t a[16]; /* a2 - a15 */
|
||||
};
|
||||
|
||||
/*
|
||||
* Register file in the format lx106 gdb port expects it.
|
||||
*
|
||||
* Inspired by gdb/regformats/reg-xtensa.dat from
|
||||
* https://github.com/jcmvbkbc/crosstool-NG/blob/lx106-g%2B%2B/overlays/xtensa_lx106.tar
|
||||
*/
|
||||
struct regfile {
|
||||
uint32_t a[16];
|
||||
uint32_t pc;
|
||||
uint32_t sar;
|
||||
uint32_t litbase;
|
||||
uint32_t sr176;
|
||||
uint32_t sr208;
|
||||
uint32_t ps;
|
||||
};
|
||||
|
||||
#define printf ets_printf
|
||||
extern void uart_write_char_d(char c);
|
||||
|
||||
/* TODO(mkm): not sure if gdb guarantees lowercase hex digits */
|
||||
#define fromhex(c) \
|
||||
(((c) &0x40) ? ((c) &0x20 ? (c) - 'a' + 10 : (c) - 'A' + 10) : (c) - '0')
|
||||
#define hexdigit(n) (((n) < 10) ? '0' + (n) : 'a' + ((n) -10))
|
||||
|
||||
static struct regfile regs = {0};
|
||||
static uint8_t gdb_send_checksum;
|
||||
|
||||
int gdb_read_uart() {
|
||||
static char buf[512];
|
||||
static char pos = 0;
|
||||
|
||||
if (pos == 0) {
|
||||
size_t rx_count;
|
||||
while ((rx_count = (USS(0) >> USRXC) & 0xff)>0 && pos < sizeof(buf)) {
|
||||
buf[pos++] = U0F;
|
||||
}
|
||||
}
|
||||
if (pos == 0) {
|
||||
return -1;
|
||||
}
|
||||
return buf[--pos];
|
||||
}
|
||||
|
||||
|
||||
uint8_t read_unaligned_byte(uint8_t *addr) {
|
||||
uint32_t *base = (uint32_t *) ((uintptr_t) addr & ~0x3);
|
||||
uint32_t word;
|
||||
|
||||
word = *base;
|
||||
return (uint8_t)(word >> 8 * ((uintptr_t) addr & 0x3));
|
||||
}
|
||||
|
||||
void gdb_nack() {
|
||||
printf("-");
|
||||
}
|
||||
|
||||
void gdb_ack() {
|
||||
printf("+");
|
||||
}
|
||||
|
||||
void gdb_begin_packet() {
|
||||
printf("$");
|
||||
gdb_send_checksum = 0;
|
||||
}
|
||||
|
||||
void gdb_end_packet() {
|
||||
printf("#%c%c", hexdigit(gdb_send_checksum >> 4),
|
||||
hexdigit(gdb_send_checksum & 0xF));
|
||||
}
|
||||
|
||||
void gdb_putchar(char ch) {
|
||||
gdb_send_checksum += (uint8_t) ch;
|
||||
printf("%c", ch);
|
||||
}
|
||||
|
||||
/* output a string while computing the checksum */
|
||||
void gdb_putstr(char *str) {
|
||||
while (*str) gdb_putchar(*str++);
|
||||
}
|
||||
|
||||
void gdb_putbyte(uint8_t val) {
|
||||
gdb_putchar(hexdigit(val >> 4));
|
||||
gdb_putchar(hexdigit(val & 0xF));
|
||||
}
|
||||
|
||||
/* 32-bit integer in native byte order */
|
||||
void gdb_putint(uint32_t val) {
|
||||
int i;
|
||||
uint8_t *v = (uint8_t *) &val;
|
||||
for (i = 0; i < 4; i++) {
|
||||
gdb_putbyte(v[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* send a gdb packet with checksum */
|
||||
void gdb_send_packet(char *str) {
|
||||
gdb_begin_packet();
|
||||
gdb_putstr(str);
|
||||
gdb_end_packet();
|
||||
}
|
||||
|
||||
uint8_t gdb_read_unaligned(uint8_t *addr) {
|
||||
if (addr < (uint8_t *) ESP_LOWER_VALID_ADDRESS ||
|
||||
addr >= (uint8_t *) ESP_UPPER_VALID_ADDRESS) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return read_unaligned_byte(addr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Handles the GDB server protocol.
|
||||
* We currently support only the simple command set.
|
||||
*
|
||||
* Data is exchanged in packets like `$Cxxxxx#cc`
|
||||
* where `C` is a single letter command name, `xxxx` is some data payload,
|
||||
* and `cc` is a two digit hex checksum of the packet body.
|
||||
* Replies follow the same structure except that they lack the command symbol.
|
||||
*
|
||||
* For a more complete description of the protocol, see
|
||||
* https://sourceware.org/gdb/current/onlinedocs/gdb/Remote-Protocol.html
|
||||
*/
|
||||
void gdb_handle_char(int ch) {
|
||||
static enum {
|
||||
GDB_JUNK,
|
||||
GDB_DATA,
|
||||
GDB_CHECKSUM,
|
||||
GDB_CHECKSUM2
|
||||
} state = GDB_JUNK;
|
||||
static char data[128];
|
||||
static int pos = 0;
|
||||
static uint8_t checksum;
|
||||
|
||||
switch (state) {
|
||||
case GDB_JUNK:
|
||||
if (ch == '$') {
|
||||
checksum = 0;
|
||||
state = GDB_DATA;
|
||||
}
|
||||
break;
|
||||
case GDB_DATA:
|
||||
if (ch == '#') {
|
||||
state = GDB_CHECKSUM;
|
||||
break;
|
||||
}
|
||||
/* ignore too long commands, by acking and sending empty response */
|
||||
if (pos > sizeof(data)) {
|
||||
state = GDB_JUNK;
|
||||
gdb_ack();
|
||||
gdb_send_packet("");
|
||||
break;
|
||||
}
|
||||
checksum += (uint8_t) ch;
|
||||
data[pos++] = ch;
|
||||
break;
|
||||
case GDB_CHECKSUM:
|
||||
if (fromhex(ch) != (checksum >> 4)) {
|
||||
gdb_nack();
|
||||
state = GDB_JUNK;
|
||||
} else {
|
||||
state = GDB_CHECKSUM2;
|
||||
}
|
||||
break;
|
||||
case GDB_CHECKSUM2:
|
||||
state = GDB_JUNK;
|
||||
if (fromhex(ch) != (checksum & 0xF)) {
|
||||
gdb_nack();
|
||||
pos = 0;
|
||||
break;
|
||||
}
|
||||
gdb_ack();
|
||||
|
||||
/* process commands */
|
||||
switch (data[0]) {
|
||||
case '?':
|
||||
/* stop status */
|
||||
gdb_send_packet("S09"); /* TRAP */
|
||||
break;
|
||||
case 'm': {
|
||||
/* read memory */
|
||||
int i;
|
||||
uint32_t addr = 0;
|
||||
uint32_t num = 0;
|
||||
for (i = 1; i < pos && data[i] != ','; i++) {
|
||||
addr <<= 4;
|
||||
addr |= fromhex(data[i]);
|
||||
}
|
||||
for (i++; i < pos; i++) {
|
||||
num <<= 4;
|
||||
num |= fromhex(data[i]); /* should be decimal */
|
||||
}
|
||||
gdb_begin_packet();
|
||||
for (i = 0; i < num; i++) {
|
||||
gdb_putbyte(gdb_read_unaligned(((uint8_t *) addr) + i));
|
||||
}
|
||||
gdb_end_packet();
|
||||
break;
|
||||
}
|
||||
case 'g': {
|
||||
/* dump registers */
|
||||
int i;
|
||||
gdb_begin_packet();
|
||||
for (i = 0; i < sizeof(regs); i++) {
|
||||
gdb_putbyte(((uint8_t *) ®s)[i]);
|
||||
}
|
||||
gdb_end_packet();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
gdb_send_packet("");
|
||||
break;
|
||||
}
|
||||
pos = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* The user should detach and let gdb do the talkin' */
|
||||
void gdb_server() {
|
||||
printf("waiting for gdb\n");
|
||||
/*
|
||||
* polling since we cannot wait for interrupts inside
|
||||
* an interrupt handler of unknown level.
|
||||
*
|
||||
* Interrupts disabled so that the user (or v7 prompt)
|
||||
* uart interrupt handler doesn't interfere.
|
||||
*/
|
||||
xthal_set_intenable(0);
|
||||
for (;;) {
|
||||
int ch = gdb_read_uart();
|
||||
if (ch != -1) gdb_handle_char(ch);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* xtos low level exception handler (in rom)
|
||||
* populates an xtos_regs structure with (most) registers
|
||||
* present at the time of the exception and passes it to the
|
||||
* high-level handler.
|
||||
*
|
||||
* Note that the a1 (sp) register is clobbered (bug? necessity?),
|
||||
* however the original stack pointer can be inferred from the address
|
||||
* of the saved registers area, since the exception handler uses the same
|
||||
* user stack. This might be different in other execution modes on the
|
||||
* quite variegated xtensa platform family, but that's how it works on ESP8266.
|
||||
*/
|
||||
void ICACHE_RAM_ATTR gdb_exception_handler(struct xtos_saved_regs *frame) {
|
||||
ESP8266_REG(0x900) &= 0x7e; // Disable WDT
|
||||
int i;
|
||||
uint32_t cause = RSR(EXCCAUSE);
|
||||
uint32_t vaddr = RSR(EXCVADDR);
|
||||
memcpy(®s.a[2], frame->a, sizeof(frame->a));
|
||||
|
||||
regs.a[0] = frame->a0;
|
||||
regs.a[1] = (uint32_t) frame + V7_GDB_SP_OFFSET;
|
||||
regs.pc = frame->pc;
|
||||
regs.sar = frame->sar;
|
||||
regs.ps = frame->ps;
|
||||
regs.litbase = RSR(LITBASE);
|
||||
|
||||
U0IE = 0;
|
||||
ets_install_putc1(&uart_write_char_d);
|
||||
|
||||
printf("\nTrap %d: pc=%p va=%p\n", cause, frame->pc, vaddr);
|
||||
gdb_server();
|
||||
|
||||
_ResetVector();
|
||||
}
|
||||
|
||||
void gdb_init() {
|
||||
char causes[] = {EXCCAUSE_ILLEGAL, EXCCAUSE_INSTR_ERROR,
|
||||
EXCCAUSE_LOAD_STORE_ERROR, EXCCAUSE_DIVIDE_BY_ZERO,
|
||||
EXCCAUSE_UNALIGNED, EXCCAUSE_INSTR_PROHIBITED,
|
||||
EXCCAUSE_LOAD_PROHIBITED, EXCCAUSE_STORE_PROHIBITED};
|
||||
int i;
|
||||
for (i = 0; i < (int) sizeof(causes); i++) {
|
||||
_xtos_set_exception_handler(causes[i], gdb_exception_handler);
|
||||
}
|
||||
}
|
6
libraries/GDBStub/src/GDBStub.h
Normal file
6
libraries/GDBStub/src/GDBStub.h
Normal file
@ -0,0 +1,6 @@
|
||||
#ifndef GDBSTUB_H
|
||||
#define GDBSTUB_H
|
||||
|
||||
// this header is intentionally left blank
|
||||
|
||||
#endif //GDBSTUB_H
|
Loading…
x
Reference in New Issue
Block a user