# Error state handler

The Beyond firmware since 0.2.16 includes an error handler feature. On detection of an error state
(Hard fault, stack overflow, or entering an unassigned interrupt handler) the Beyond will enter
a special error handler mode. This mode resets the USB device and connects with a unique PID so
the HMD Utility can recognize that the error handler was entered. The error handler will also
allow the Utility to transfer the processor and memory state to a file, representing the microcontroller
at the time of the error.

## Further details on error state handler

The Beyond standard application USB PID is 0x0101, the error state is 0x1001. It is only a Generic HID
Device instead of USB Audio and HID devices.

The error state can be downloaded using HID commands. Just like in the normal application, messages from PC
to Beyond must use a "send_feature_report" HID function instead of "write".

### Command list
| Command (ASCII) | Command (Byte) | Description | 
|---|---|---|
| * | 0x2A | Get software version |
| R | 0x52 | Get error reason (and IRQn, HFSR, CFSR) |
| S | 0x53 | Get processor state (registers) |
| T | 0x54 | Get task name (only valid if task overflow occurred) |
| G | 0x47 | Get number of memory regions |
| I | 0x49 | Get information about a memory region | 
| M | 0x4D | Get memory from address |
| C | 0x43 | Restart Beyond (back to normal application) |
| B | 0x42 | Restart Beyond in bootloader mode |

# Debugging an error log

The Utility can save the error log automatically when a Beyond is detected in an error state. The logs are saved in the same folder as the Utility executable with file format "error_YYYY_MM_DD_HH_MM_SS.log".

To load these error logs in a live debugging session, able to examine all global, static, and local variables,
there are some extra steps to follow.

1. Grab the "crashdebug" utility from https://github.com/adamgreen/CrashDebug This is designed to work with the CrashCatcher firmware from the same author, but don't worry we got a solution...
2. Convert the .log file to a CrashCatcher text file using the [create_crashdebug.py](../scripts/create_crashdebug.py) script.
 - This is a command line script with only one argument, the .log filename / filepath. 
3. Grab the arm-none-eabi-gdb.exe instead of any x86/64 or standard GDB flavors
 - Found at [ARM GNU Toolchain](https://developer.arm.com/Tools%20and%20Software/GNU%20Toolchain)
 - Add the bin folder to your path so you can simply type `>arm-none-eabi-gdb` to start GDB.
4. Make sure you have in the same directory:
 - the .elf file that was generated by the compiler for the particular version of firmware that was running during the crash.
 - the create_crashdebug.py generated text file
 - the gdb script file [freertos_helpers.gdb](./freertos_helpers.gdb)
5. Load GDB with the following commands:
`>arm-none-eabi-gdb ELF_FILE.elf -ex "target set-charset ASCII" -ex "target remote | CrashDebug --elf ELF_FILE.elf --dump CRASHDEBUG_FILE.txt" -x "freertos_helpers.gdb"`
 - __NOTE__: there's a powershell script [crash_debug.ps1](./crash_debug.ps1) for your convenience. Just change the filenames at the top to your situation.
6. You should now have GDB running and CrashDebug pretending that our processor is connected. Of course the processor will be locked in the state at the point of the error. You will not be able to run any code, but all
of the regular GDB commands to view memory or variables will work. In particular, stack _backtrace_, and _info frame_ and _info locals_ can be very helpful commands.
7. Unfortunately, the processor will be locked into the main thread of execution, where the Hard Fault handler 
was called. This means that any activity in the many FreeRTOS threads will not be easy to examine. They will not be the active stack. But we can fix this!
 - use the command `freertos_show_threads`. This is a custom command that was loaded by the freertos_helpers.gdb script file. You will see something like:
```
TCB Address     Task name               Status
0x20007060      IDLE                    <---RUNNING
0x20006ee0      USART                   Ready
0x20005e08      HID Report              Blocked
0x200067a8      LED                     Blocked
0x20006540      ADC                     Blocked
0x20006c78      video_proc              Blocked
0x20005ba0      HID Commands            Suspended
0x200062d8      TIMED_START             Suspended
0x20006a10      FAN                     Suspended
0x20007c88      Tmr Svc                 Suspended
0x20006070      I2C                     Suspended
0x20005938      USBLoad                 Suspended
```
The Task Control Block (TCB) address for each task is what we want here. We can use that to exchange the stack 
frame and examine a different task.
You can also simply inspect that TCB in GDB with (example of _video\_proc_ above):

`print *(TCB_t*)0x20006C78`

But that doesn't give you the stack frame. We'll need to go back to step 2 above and do something extra. This time, convert the .log file to a CrashCatcher text file _and change the stack frame_ using the `-s` input argument.
 - Example: `>py create_crashdebug.py ERROR_LOG_FILE.log -s 0x20006C78`

This will replace the processor registers in the created crashdebug text file with the stacked values of your selected task. It emulates the way the real processor would re-enter a task that isn't currently running. Make sure that the address has 0x prepended for hexadecimal input. Otherwise it is assumed to be a decimal memory address. The address should be the TCB for that task, e.g. the value shown by `freertos_show_threads` in GDB.

Now run GDB using this adjusted CrashDebug text file. The stack can be examined from the perspective of the task you just switched to!

