-
-
Notifications
You must be signed in to change notification settings - Fork 345
/
application.cpp
680 lines (601 loc) · 20 KB
/
application.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
#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();
}