681 lines
20 KiB
C++
681 lines
20 KiB
C++
#include <SmingCore.h>
|
|
#include "HardwareTimer.h"
|
|
#include <gdb/gdb_syscall.h>
|
|
#include <IFS/Gdb/FileSystem.h>
|
|
#include <Data/Stream/GdbFileStream.h>
|
|
#include <Data/Buffer/LineBuffer.h>
|
|
#include <Platform/OsMessageInterceptor.h>
|
|
|
|
#define LED_PIN 2 // Note: LED is attached to UART1 TX output
|
|
|
|
namespace
|
|
{
|
|
// Max length of debug command
|
|
const unsigned MAX_COMMAND_LENGTH = 64;
|
|
|
|
#define TIMERTYPE_HARDWARE 1
|
|
#define TIMERTYPE_SIMPLE 2
|
|
#define TIMERTYPE_TIMER 3
|
|
|
|
/*
|
|
* This example uses the hardware timer for best timing accuracy. There is only one of these on the ESP8266,
|
|
* so it may not be available if another module requires it.
|
|
*
|
|
* Most software timing applications can use a `SimpleTimer`, which is good for intervals of up to about
|
|
* 429 seconds, or around 2 hours if you compile with USE_US_TIMER=0.
|
|
*
|
|
* For longer intervals and delegate callback support use a `Timer`.
|
|
*/
|
|
#define TIMER_TYPE TIMERTYPE_HARDWARE
|
|
|
|
/*
|
|
* We use the timer to blink the LED at this rate
|
|
*/
|
|
#define BLINK_INTERVAL_MS 1000
|
|
|
|
/*
|
|
* HardwareTimer defaults to non-maskable mode, so the timer callback cannot be interrupted even by the
|
|
* debugger. To use break/watchpoints we must set the timer to use maskable mode.
|
|
*/
|
|
#define HWTIMER_TYPE eHWT_Maskable
|
|
|
|
#if TIMER_TYPE == TIMERTYPE_HARDWARE
|
|
HardwareTimer1<TIMER_CLKDIV_16, HWTIMER_TYPE> procTimer;
|
|
// Hardware timer callbacks must always be in IRAM
|
|
#define CALLBACK_ATTR IRAM_ATTR
|
|
#elif TIMER_TYPE == TIMERTYPE_SIMPLE
|
|
SimpleTimer procTimer;
|
|
#define CALLBACK_ATTR GDB_IRAM_ATTR
|
|
#else
|
|
Timer procTimer;
|
|
#define CALLBACK_ATTR GDB_IRAM_ATTR
|
|
#endif
|
|
|
|
// A simple log file stored on the host
|
|
GdbFileStream logFile;
|
|
#define LOG_FILENAME "testlog.txt"
|
|
|
|
// Handles messages from SDK
|
|
OsMessageInterceptor osMessageInterceptor;
|
|
|
|
// Supports `consoleOff` command to prevent re-enabling when debugger is attached
|
|
bool consoleOffRequested;
|
|
|
|
//
|
|
IFS::Gdb::FileSystem gdbfs;
|
|
|
|
// Forward declarations
|
|
bool handleCommand(const String& cmd);
|
|
void readConsole();
|
|
|
|
/*
|
|
* Notice: Software breakpoints work only on code that is in RAM.
|
|
* In Sming you have to use the GDB_IRAM_ATTR to do this.
|
|
*/
|
|
void CALLBACK_ATTR blink()
|
|
{
|
|
static bool ledState;
|
|
|
|
ledState = !ledState;
|
|
digitalWrite(LED_PIN, ledState);
|
|
}
|
|
|
|
void showPrompt()
|
|
{
|
|
switch(gdb_present()) {
|
|
case eGDB_Attached:
|
|
Serial.print(_F("\r(Attached) "));
|
|
break;
|
|
case eGDB_Detached:
|
|
Serial.print(_F("\r(Detached) "));
|
|
break;
|
|
case eGDB_NotPresent:
|
|
default:
|
|
Serial.print(_F("\r(Non-GDB) "));
|
|
}
|
|
}
|
|
|
|
void onDataReceived(Stream& source, char arrivedChar, unsigned short availableCharsCount)
|
|
{
|
|
static LineBuffer<MAX_COMMAND_LENGTH> commandBuffer;
|
|
|
|
// Error detection
|
|
unsigned status = Serial.getStatus();
|
|
if(status != 0) {
|
|
Serial.println();
|
|
if(bitRead(status, eSERS_Overflow)) {
|
|
Serial.println(_F("** RECEIVE OVERFLOW **"));
|
|
}
|
|
if(bitRead(status, eSERS_BreakDetected)) {
|
|
Serial.println(_F("** BREAK DETECTED **"));
|
|
}
|
|
if(bitRead(status, eSERS_FramingError)) {
|
|
Serial.println(_F("** FRAMING ERROR **"));
|
|
}
|
|
if(bitRead(status, eSERS_ParityError)) {
|
|
Serial.println(_F("** PARITY ERROR **"));
|
|
}
|
|
// Discard what is likely to be garbage
|
|
Serial.clear(SERIAL_RX_ONLY);
|
|
commandBuffer.clear();
|
|
showPrompt();
|
|
return;
|
|
}
|
|
|
|
switch(commandBuffer.process(source, Serial)) {
|
|
case LineBufferBase::Action::clear:
|
|
showPrompt();
|
|
break;
|
|
case LineBufferBase::Action::submit: {
|
|
if(commandBuffer) {
|
|
handleCommand(String(commandBuffer));
|
|
commandBuffer.clear();
|
|
}
|
|
showPrompt();
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Demonstrate opening and reading a file from the host.
|
|
*/
|
|
void readFile(const char* filename, bool display)
|
|
{
|
|
int file = gdbfs.open(filename, File::ReadOnly);
|
|
Serial << _F("gdbfs.open(\"") << filename << "\") = " << file << endl;
|
|
if(file >= 0) {
|
|
OneShotFastMs timer;
|
|
char buf[256];
|
|
size_t total{0};
|
|
int len;
|
|
do {
|
|
len = gdbfs.read(file, buf, sizeof(buf));
|
|
if(len > 0) {
|
|
total += size_t(len);
|
|
if(display) {
|
|
Serial.write(buf, len);
|
|
}
|
|
}
|
|
} while(len == sizeof(buf));
|
|
auto elapsed = timer.elapsedTime();
|
|
Serial.println();
|
|
Serial << _F("gdbfs.read() = ") << len << _F(", total = ") << total << _F(", elapsed = ") << elapsed.toString()
|
|
<< _F(", av. ") << (total == 0 ? 0 : 1000U * total / elapsed) << _F(" bytes/sec") << endl;
|
|
|
|
gdbfs.close(file);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* A more advanced way to use host File I/O using asynchronous syscalls.
|
|
* The initial open() is performed via readFileAsync(), the remaining operations are handled
|
|
* in this callback function.
|
|
*/
|
|
void asyncReadCallback(const GdbSyscallInfo& info)
|
|
{
|
|
// Buffer for performing asynchronous reads
|
|
static char buf[256];
|
|
|
|
// State information so we can calculate average throughput
|
|
static struct {
|
|
long start; // millis() when open() call completed
|
|
size_t total; // Total number of file bytes read so far
|
|
} transfer;
|
|
|
|
switch(info.command) {
|
|
case eGDBSYS_open: {
|
|
int fd = info.result;
|
|
String filename(FPSTR(info.open.filename));
|
|
Serial << _F("gdb_syscall_open(\"") << filename << "\") = " << fd << endl;
|
|
if(fd > 0) {
|
|
transfer.start = millis();
|
|
transfer.total = 0;
|
|
gdb_syscall_read(fd, buf, sizeof(buf), asyncReadCallback);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eGDBSYS_read: {
|
|
// m_printf(_F("\rgdb_syscall_read() = %d, total = %u"), info.result, prog.total);
|
|
if(info.result > 0) {
|
|
transfer.total += info.result;
|
|
}
|
|
if(info.result == sizeof(buf)) {
|
|
gdb_syscall_read(info.read.fd, buf, sizeof(buf), asyncReadCallback);
|
|
} else {
|
|
gdb_syscall_close(info.read.fd, asyncReadCallback);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case eGDBSYS_close: {
|
|
long elapsed = millis() - transfer.start;
|
|
long bps = (transfer.total == 0) ? 0 : 1000U * transfer.total / elapsed;
|
|
Serial << _F("readFileAsync: total = ") << transfer.total << _F(", elapsed = ") << elapsed << _F(" ms, av. ")
|
|
<< bps << _F(" bytes/sec") << endl;
|
|
readConsole();
|
|
break;
|
|
}
|
|
|
|
default:;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Read a file using callbacks.
|
|
* Note that filename must be in persistent memory (e.g. flash string) as call may not be started
|
|
* immediately.
|
|
*/
|
|
void readFileAsync(const char* filename)
|
|
{
|
|
gdb_syscall_open(filename, O_RDONLY, 0, asyncReadCallback);
|
|
}
|
|
|
|
void fileStat(const char* filename)
|
|
{
|
|
gdb_stat_t stat;
|
|
int res = gdb_syscall_stat(filename, &stat);
|
|
Serial << _F("gdb_syscall_stat(\"") << filename << _F("\") returned ") << res << endl;
|
|
if(res != 0) {
|
|
return;
|
|
}
|
|
|
|
#define PRT(x) Serial << _F(" " #x " = ") << stat.x << endl
|
|
#define PRT_HEX(x) Serial << _F(" " #x " = 0x") << String(stat.x, HEX) << endl
|
|
#define PRT_TIME(x) Serial << _F(" " #x " = ") << DateTime(stat.x).toFullDateTimeString() << endl
|
|
|
|
PRT(st_dev);
|
|
PRT(st_ino);
|
|
PRT_HEX(st_mode);
|
|
PRT(st_nlink);
|
|
PRT_HEX(st_uid);
|
|
PRT_HEX(st_gid);
|
|
PRT(st_rdev);
|
|
PRT(st_size);
|
|
PRT(st_blksize);
|
|
PRT(st_blocks);
|
|
PRT_TIME(st_atime);
|
|
PRT_TIME(st_mtime);
|
|
PRT_TIME(st_ctime);
|
|
#undef PRT
|
|
#undef PRT_HEX
|
|
#undef PRT_TIME
|
|
}
|
|
|
|
/*
|
|
* Keep commands and their description together to ensure 'help' is consistent.
|
|
* This also helps to keep the code clean and easy to read.
|
|
*/
|
|
#define COMMAND_MAP(XX) \
|
|
XX(readfile1, "Use syscall file I/O functions to read and display a host file\n" \
|
|
"Calls are blocking so the application is paused during the entire operation") \
|
|
XX(readfile2, "Read a larger host file asynchronously\n" \
|
|
"Data is processed in a callback function to avoid pausing the application un-necessarily") \
|
|
XX(stat, "Use `syscall_stat` function to get details for a host file") \
|
|
XX(ls, "Use `syscall_system` function to perform a directory listing on the host") \
|
|
XX(time, "Use `syscall_gettimeofday` to get current time from host") \
|
|
XX(log, "Show state of log file\n" \
|
|
"The log file is written to \"" LOG_FILENAME "\" in the current working directory") \
|
|
XX(break, "Demonstrate `gdb_do_break()` function to pause this application and obtain a GDB command prompt\n" \
|
|
"Similar to Ctrl+C except we control exactly where the application stops") \
|
|
XX(queueBreak, "Demonstrate `gdb_do_break()` function called via task queue\n" \
|
|
"If you run `bt` you'll see a much smaller stack trace") \
|
|
XX(consoleOff, "Break into debugger and stop reading from console\n" \
|
|
"Do this if you need to set live breakpoints or observe debug output,\n" \
|
|
"as both are blocked during console reading") \
|
|
XX(hang, "Enter infinite loop to force a watchdog timeout\n" \
|
|
"Tests the crash handler which should display a message,\n" \
|
|
"then break into the debugger, if available") \
|
|
XX(read0, "Read from invalid address\n" \
|
|
"Attempting to read from address #0 will trigger a LOAD_PROHIBITED exception") \
|
|
XX(write0, "Write to invalid address\n" \
|
|
"Attempting to write to address #0 will trigger a STORE_PROHIBITED exception") \
|
|
XX(malloc0, "Call malloc(0)") \
|
|
XX(freetwice, "Free allocated memory twice") \
|
|
XX(restart, "Restart the system\n" \
|
|
"GDB should reconnect automatically, but if not run from a terminal.\n" \
|
|
"Windows versions of GDB don't handle serial control lines well,\n" \
|
|
"so a nodeMCU, for example, may restart in the wrong mode") \
|
|
XX(disconnect, "Terminates the connection between the debugger and the remote debug target\n" \
|
|
"Calls gdb_detach() - the application will resume normal operation") \
|
|
XX(help, "Display this command summary")
|
|
|
|
/*
|
|
* Macro to simplify command handler function creation.
|
|
* Function returns true to start another 'readConsole' request.
|
|
* If the operation is completed via callback then it returns false instead, and the readConsole called at that point.
|
|
*/
|
|
#define COMMAND_HANDLER(name) static bool handleCommand_##name()
|
|
|
|
COMMAND_HANDLER(readfile1)
|
|
{
|
|
// Read a small file and display it
|
|
readFile(_F("Makefile"), true);
|
|
return true;
|
|
}
|
|
|
|
COMMAND_HANDLER(readfile2)
|
|
{
|
|
// Read a larger file asynchronously and analyse transfer speed
|
|
Serial.println(_F("Please wait..."));
|
|
readFileAsync(PSTR("README.md"));
|
|
return false; // When read has completed, readConsole() will be called again
|
|
}
|
|
|
|
COMMAND_HANDLER(stat)
|
|
{
|
|
fileStat(_F("Makefile"));
|
|
return true;
|
|
}
|
|
|
|
COMMAND_HANDLER(time)
|
|
{
|
|
gdb_timeval_t tv;
|
|
int res = gdb_syscall_gettimeofday(&tv, nullptr);
|
|
if(res < 0) {
|
|
Serial << _F("gdb_syscall_gettimeofday() returned ") << res << endl;
|
|
} else {
|
|
Serial << _F("tv_sec = ") << tv.tv_sec << _F(", tv_usec = ") << tv.tv_usec << ", "
|
|
<< DateTime(tv.tv_sec).toFullDateTimeString() << _F(" UTC") << endl;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
COMMAND_HANDLER(log)
|
|
{
|
|
if(logFile.isValid()) {
|
|
Serial << _F("Log file is open, size = ") << logFile.getPos() << _F(" bytes") << endl;
|
|
} else {
|
|
Serial.println(_F("Log file not available"));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
COMMAND_HANDLER(ls)
|
|
{
|
|
int res = gdb_syscall_system(PSTR("ls -la"));
|
|
Serial << _F("gdb_syscall_system() returned ") << res << endl;
|
|
return true;
|
|
}
|
|
|
|
COMMAND_HANDLER(break)
|
|
{
|
|
Serial.println(_F("Calling gdb_do_break()"));
|
|
gdb_do_break();
|
|
return true;
|
|
}
|
|
|
|
COMMAND_HANDLER(queueBreak)
|
|
{
|
|
Serial.println(_F("Queuing a call to gdb_do_break()\r\n"
|
|
"This differs from `break` in that a console read will be in progress when the break is called"));
|
|
System.queueCallback(handleCommand_break);
|
|
return true;
|
|
}
|
|
|
|
COMMAND_HANDLER(consoleOff)
|
|
{
|
|
Serial.println(_F("To re-enable console reading, enter `call readConsole()` from GDB prompt"));
|
|
gdb_do_break();
|
|
consoleOffRequested = true;
|
|
return false;
|
|
}
|
|
|
|
COMMAND_HANDLER(hang)
|
|
{
|
|
Serial.println(_F("Entering infinite loop..."));
|
|
Serial.flush();
|
|
while(true) {
|
|
//
|
|
}
|
|
return true;
|
|
}
|
|
|
|
COMMAND_HANDLER(read0)
|
|
{
|
|
Serial.println(_F("Crashing app by reading from address 0\r\n"
|
|
"At GDB prompt, enter `set $pc = $pc + 3` to skip offending instruction,\r\n"
|
|
"then enter `c` to continue"));
|
|
Serial.flush();
|
|
uint8_t value = *(volatile uint8_t*)0;
|
|
Serial << _F("Value at address 0 = 0x") << String(value, HEX, 2) << endl;
|
|
return true;
|
|
}
|
|
|
|
COMMAND_HANDLER(write0)
|
|
{
|
|
Serial.println(_F("Crashing app by writing to address 0\r\n"
|
|
"At GDB prompt, enter `set $pc = $pc + 3` to skip offending instruction,\r\n"
|
|
"then enter `c` to continue"));
|
|
Serial.flush();
|
|
*(volatile uint8_t*)0 = 0;
|
|
Serial.println(_F("...still running!"));
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief See if the OS debug message is something we're interested in.
|
|
* @param msg
|
|
* @retval bool true if we want to report this
|
|
*/
|
|
bool __noinline parseOsMessage(OsMessage& msg)
|
|
{
|
|
m_printf(_F("[OS] %s\r\n"), msg.getBuffer());
|
|
if(msg.startsWith(_F("E:M "))) {
|
|
Serial.println(_F("** OS Memory Error **"));
|
|
return true;
|
|
} else if(msg.contains(_F(" assert "))) {
|
|
Serial.println(_F("** OS Assert **"));
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Called when the OS outputs a debug message using os_printf, etc.
|
|
* @param msg The message
|
|
*/
|
|
void onOsMessage(OsMessage& msg)
|
|
{
|
|
// Note: We do the check in a separate function to avoid messing up the stack pointer
|
|
if(parseOsMessage(msg)) {
|
|
if(gdb_present() == eGDB_Attached) {
|
|
gdb_do_break();
|
|
} else {
|
|
#ifdef ARCH_ESP8266
|
|
register uint32_t sp __asm__("a1");
|
|
debug_print_stack(sp + 0x10, 0x3fffffb0);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
COMMAND_HANDLER(malloc0)
|
|
{
|
|
Serial.println(
|
|
_F("Attempting to allocate a zero-length array results in an OS debug message.\r\n"
|
|
"The message starts with 'E:M ...' and can often indicate a more serious memory allocation issue."));
|
|
|
|
auto mem = os_malloc(0);
|
|
os_free(mem);
|
|
|
|
return true;
|
|
}
|
|
|
|
COMMAND_HANDLER(freetwice)
|
|
{
|
|
Serial.println(_F("Attempting to free the same memory twice is a common bug.\r\n"
|
|
"On the test system we see an assertion failure message from the OS."));
|
|
|
|
auto mem = static_cast<char*>(os_malloc(123));
|
|
os_free(mem);
|
|
os_free(mem);
|
|
|
|
return true;
|
|
}
|
|
|
|
COMMAND_HANDLER(restart)
|
|
{
|
|
Serial.println(_F("Restarting...."));
|
|
System.restart();
|
|
return false;
|
|
}
|
|
|
|
COMMAND_HANDLER(disconnect)
|
|
{
|
|
// End console test
|
|
Serial.print(_F("Calling gdb_detach() - "));
|
|
if(gdb_present() == eGDB_Attached) {
|
|
Serial.println(_F("resuming normal program execution."));
|
|
} else if(gdb_present() == eGDB_Detached) {
|
|
Serial.println(_F("not attached, so does nothing"));
|
|
} else {
|
|
Serial.println(_F("Application isn't compiled using ENABLE_GDB so this does nothing."));
|
|
}
|
|
Serial.flush();
|
|
gdb_detach();
|
|
return false;
|
|
}
|
|
|
|
COMMAND_HANDLER(help)
|
|
{
|
|
Serial.print(_F("LiveDebug interactive debugger sample. Available commands:\r\n"));
|
|
|
|
auto print = [](const char* tag, const char* desc) {
|
|
const unsigned indent = 10;
|
|
Serial << " " << String(tag).pad(indent) << " : ";
|
|
|
|
// Print multi-line descriptions in sections to maintain correct line indentation
|
|
String s;
|
|
s.pad(2 + indent + 3);
|
|
|
|
for(;;) {
|
|
auto end = strchr(desc, '\n');
|
|
if(end == nullptr) {
|
|
Serial.println(desc);
|
|
break;
|
|
} else {
|
|
Serial.write(desc, end - desc);
|
|
Serial.println();
|
|
desc = end + 1;
|
|
Serial.print(s);
|
|
}
|
|
}
|
|
};
|
|
|
|
#define XX(tag, desc) print(_F(#tag), _F(desc));
|
|
COMMAND_MAP(XX)
|
|
#undef XX
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @brief User typed a command. Deal with it.
|
|
* @retval bool true to continue reading another command
|
|
*/
|
|
bool handleCommand(const String& cmd)
|
|
{
|
|
if(logFile.isValid()) {
|
|
logFile << _F("handleCommand('") << cmd << "')" << endl;
|
|
}
|
|
|
|
#define XX(tag, desc) \
|
|
if(cmd.equalsIgnoreCase(F(#tag))) { \
|
|
return handleCommand_##tag(); \
|
|
}
|
|
COMMAND_MAP(XX)
|
|
#undef XX
|
|
|
|
Serial << _F("Unknown command '") << cmd << _F("', try 'help'") << endl;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Completion callback for console read test. See readConsole().
|
|
*
|
|
* When the syscall is executed, GDB is instructed to read a line of text from the console.
|
|
* GDB implements a line-editor, so information will only be sent when you hit return.
|
|
* Typing Ctrl+D sends the line immediately without any return (note: this doesn't work on Windows.)
|
|
*
|
|
* We continue running until GDB is ready to send the result, which is written to the
|
|
* buffer provided in the original call. This callback function then gets called via the
|
|
* task queue.
|
|
*
|
|
* Data received will include any return character typed.
|
|
*/
|
|
void onConsoleReadCompleted(const GdbSyscallInfo& info)
|
|
{
|
|
int result = info.result;
|
|
char* bufptr = static_cast<char*>(info.read.buffer);
|
|
|
|
debug_i("gdb_read_console() returned %d", result);
|
|
if(result > 0) {
|
|
// Remove trailing newline character
|
|
unsigned len = result;
|
|
if(bufptr[len - 1] == '\n') {
|
|
--len;
|
|
}
|
|
|
|
if(len > 0) {
|
|
String cmd(bufptr, len);
|
|
if(!handleCommand(cmd)) {
|
|
return; // Don't call readConsole
|
|
}
|
|
}
|
|
}
|
|
|
|
// Start another console read
|
|
readConsole();
|
|
}
|
|
|
|
/*
|
|
* Demonstrate GDB console access.
|
|
* We actually queue this so it can be called directly from GDB to re-enable console reading
|
|
* after using the `consoleOff` command.
|
|
*/
|
|
void readConsole()
|
|
{
|
|
consoleOffRequested = false;
|
|
System.queueCallback(InterruptCallback([]() {
|
|
showPrompt();
|
|
if(gdb_present() == eGDB_Attached) {
|
|
// Issue the syscall
|
|
static char buffer[MAX_COMMAND_LENGTH];
|
|
int res = gdb_console_read(buffer, MAX_COMMAND_LENGTH, onConsoleReadCompleted);
|
|
if(res < 0) {
|
|
Serial.printf(_F("gdb_console_read() failed, %d\r\n"), res);
|
|
Serial.println(_F("Is GDBSTUB_ENABLE_SYSCALL enabled ?"));
|
|
showPrompt();
|
|
}
|
|
|
|
/*
|
|
* GDB executes the system call, finished in onReadCompleted().
|
|
* Note that any serial output gets ignored by GDB whilst executing a system
|
|
* call.
|
|
*/
|
|
} else {
|
|
/*
|
|
* GDB is either detached or not present, serial callback will process input
|
|
*/
|
|
}
|
|
}));
|
|
}
|
|
|
|
void printTimerDetails()
|
|
{
|
|
Serial << procTimer << ", maxTicks = " << procTimer.maxTicks()
|
|
<< ", maxTime = " << procTimer.micros().ticksToTime(procTimer.maxTicks()).value() << endl;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
extern "C" void gdb_on_attach(bool attached)
|
|
{
|
|
debug_i("GdbAttach(%d)", attached);
|
|
if(attached) {
|
|
// Open a log file on the host to demonstrate use of GdbFileStream
|
|
logFile.open(F(LOG_FILENAME), File::WriteOnly | File::Create);
|
|
debug_i("open log %d", logFile.getLastError());
|
|
logFile.println();
|
|
|
|
logFile.println(_F("\r\n=== OPENED ==="));
|
|
gdb_timeval_t tv;
|
|
gdb_syscall_gettimeofday(&tv, nullptr);
|
|
logFile.println(DateTime(tv.tv_sec).toFullDateTimeString());
|
|
|
|
// Start interacting with GDB
|
|
if(!consoleOffRequested) {
|
|
readConsole();
|
|
}
|
|
} else {
|
|
// Note: GDB is already detached so underlying call to gdb_syscall_close() will fail silently
|
|
logFile.close();
|
|
}
|
|
}
|
|
|
|
void GDB_IRAM_ATTR init()
|
|
{
|
|
Serial.begin(SERIAL_BAUD_RATE);
|
|
Serial.onDataReceived(onDataReceived);
|
|
Serial.systemDebugOutput(true);
|
|
|
|
Serial.println(_F("LiveDebug sample\r\n"
|
|
"Explore some capabilities of the GDB debugger.\r\n"));
|
|
|
|
// Install a debug output hook to monitor OS debug messages
|
|
osMessageInterceptor.begin(onOsMessage);
|
|
|
|
if(gdb_present() != eGDB_Attached) {
|
|
System.onReady(showPrompt);
|
|
}
|
|
|
|
pinMode(LED_PIN, OUTPUT);
|
|
procTimer.initializeMs<BLINK_INTERVAL_MS>(blink).start();
|
|
|
|
printTimerDetails();
|
|
}
|