Merge pull request #64 from mkiiskila/diagnostics_tutorial

Tutorial for error diagnostics
diff --git a/docs/tutorials/tooling/error_diagnostics.rst b/docs/tutorials/tooling/error_diagnostics.rst
new file mode 100644
index 0000000..d468581
--- /dev/null
+++ b/docs/tutorials/tooling/error_diagnostics.rst
@@ -0,0 +1,557 @@
+Diagnosing Errors
+=================
+
+Here we list some of the tools Mynewt has available. This is by no means
+comprehensive. It does give an overview of some facilities more commonly
+used.
+
+To create output for this demo, I'm including the 'test/crash_test' package
+while building the 'slinky' app.
+
+Crash with Console
+------------------
+When system restarts due to an error, we always attempt to do it
+in a way which allows us to dump the current system state. Calls to
+assert(), error interrupts (regular faults, memory faults) both take
+this path.
+
+The basic facility is the printout of registers to console.
+
+Here is an example of what it looks like with Cortex-M4:
+
+.. code-block:: console
+
+    041088 Unhandled interrupt (3), exception sp 0x20001d28
+    041088  r0:0x00000000  r1:0x00017b61  r2:0x00000000  r3:0x0000002a
+    041088  r4:0x20001dc8  r5:0x00000000  r6:0x200034e4  r7:0x20003464
+    041088  r8:0x2000349c  r9:0x20001f08 r10:0x00017bd4 r11:0x00000000
+    041088 r12:0x00000000  lr:0x00014e29  pc:0x00014e58 psr:0x61000000
+    041088 ICSR:0x00421803 HFSR:0x40000000 CFSR:0x02000000
+    041088 BFAR:0xe000ed38 MMFAR:0xe000ed34
+
+Output includes the values of registers. The most interesting pieces
+would be $pc (program counter), and $lr (link register), which show
+the instruction where the fault happened, and the return address from
+that function.
+
+You would then take these values, and match them against the image
+you're running on the target.
+
+For example:
+
+.. code-block:: console
+
+    [marko@IsMyLaptop:~/src2/incubator-mynewt-blinky]$ arm-elf-linux-gdb bin/targets/slinky_nrf52/app/apps/slinky/slinky.elf
+    GNU gdb (GDB) 7.8.1
+      ...
+    Reading symbols from bin/targets/slinky_nrf52/app/apps/slinky/slinky.elf...done.
+    (gdb) list *0x00014e58
+    0x14e58 is in crash_device (repos/apache-mynewt-core/test/crash_test/src/crash_test.c:47).
+    42	    if (!strcmp(how, "div0")) {
+    43	
+    44	        val1 = 42;
+    45	        val2 = 0;
+    46	
+    47	        val3 = val1 / val2;
+    48	        console_printf("42/0 = %d\n", val3);
+    49	    } else if (!strcmp(how, "jump0")) {
+    50	        ((void (*)(void))0)();
+    51	    } else if (!strcmp(how, "ref0")) {
+
+You can see that the system crashed due to divide-by-zero.
+
+Crash with Verbose Location
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We often call assert(), testing specific conditions which should be met
+for program execution to continue. If the condition fails, we reset with a
+message listing the instruction where assert() was called from.
+
+.. code-block:: console
+
+    829008 compat> Assert @ 0x14e9f
+    831203 Unhandled interrupt (2), exception sp 0x20001d20
+
+This address (here 0x14e9f) should then be used to find the line in
+the program where it happened. Note that this address is specific to your
+binary. So you have to do the search to locate the specific file/line using
+your .elf file.
+
+You can also enable more verbose report by setting syscfg variable
+OS_CRASH_FILE_LINE to 1. The call to assert() from above would then look
+like so:
+
+.. code-block:: console
+
+    000230 compat> Assert @ 0x1503b - repos/apache-mynewt-core/test/crash_test/src/crash_test.c:54
+    001462 Unhandled interrupt (2), exception sp 0x20001d20
+
+Note that this will increase the program text size, as we now need to
+store the names for the files which call assert().
+
+Crash with Console with Stacktrace
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Quite often you need a deeper view of the call chain to figure out
+how the program got to place where it crashed. Especially if the routine
+can be reached via multiple call chain routes.
+
+You can enable a dump of possible call chain candidates by setting
+syscfg variable OS_CRASH_STACKTRACE to 1.
+
+When crash happens, the dumping routine walks backwards in the stack,
+and prints any address which falls within text region. All these values
+are candidates of being part of the call chain. However, you need to
+filter it yourself to figure out which values are actually part of it, and
+which addresses just happen to be in the stack at that time.
+
+Here is a sample output:
+
+.. code-block:: console
+    
+    004067 Unhandled interrupt (3), exception sp 0x20001d28
+    004067  r0:0x00000000  r1:0x00017c55  r2:0x00000000  r3:0x0000002a
+    004067  r4:0x20001dc8  r5:0x00000000  r6:0x200034e4  r7:0x20003464
+    004067  r8:0x2000349c  r9:0x20001f08 r10:0x00017cc8 r11:0x00000000
+    004067 r12:0x00000000  lr:0x00014ef9  pc:0x00014f28 psr:0x61000000
+    004067 ICSR:0x00421803 HFSR:0x40000000 CFSR:0x02000000
+    004067 BFAR:0xe000ed38 MMFAR:0xe000ed34
+    004067 task:main
+    004067  0x20001d64: 0x0001501b
+    004067  0x20001d68: 0x00017338
+    004067  0x20001dd0: 0x00017cc8
+    004067  0x20001dd4: 0x00015c2b
+    004067  0x20001dd8: 0x00015c1d
+    004067  0x20001de4: 0x0001161d
+    004067  0x20001df4: 0x00011841
+    004067  0x20001e04: 0x000117c9
+    004067  0x20001e0c: 0x00013d8f
+    004067  0x20001e20: 0x00014375
+    004067  0x20001e4c: 0x00013dfd
+    004067  0x20001e54: 0x00013e09
+    004067  0x20001e58: 0x00013e01
+    004067  0x20001e5c: 0x0000925d
+    004067  0x20001e64: 0x00008933
+    004067  0x20001e7c: 0x00008cc3
+    004067  0x20001e98: 0x000169e0
+    004067  0x20001e9c: 0x00008cb9
+    004067  0x20001ea0: 0x000088a9
+
+You can see that it contains the usual dump of registers in the beginning,
+followed by addresses to stack, and the value at that location. Note that
+we skip the area of memory within stack which contains the stashed register
+values. This means that $pc and $lr values will not show up in the array
+of addresses. Depending on your CPU architecture, different stuff gets
+pushed to stack. Given where Mynewt is ported to, there's always function
+return address pushed there.
+
+You would then take these values, and see if they seem legit. Here we'll
+show what to do with the output above.
+
+.. code-block:: console
+    
+    [marko@IsMyLaptop:~/src2/incubator-mynewt-blinky]$ arm-elf-linux-gdb bin/targets/slinky_nrf52/app/apps/slinky/slinky.elf
+    GNU gdb (GDB) 7.8.1
+    Copyright (C) 2014 Free Software Foundation, Inc.
+    ...
+    No symbol table is loaded.  Use the "file" command.
+    Reading symbols from bin/targets/slinky_nrf52/app/apps/slinky/slinky.elf...done.
+    (gdb) list *0x0001501b
+    0x1501b is in crash_test_nmgr_write (repos/apache-mynewt-core/test/crash_test/src/crash_nmgr.c:68).
+    63	    if (rc != 0) {
+    64	        return MGMT_ERR_EINVAL;
+    65	    }
+    66	
+    67	    rc = crash_device(tmp_str);  <--- That is likely part of it
+    68	    if (rc != 0) {
+    69	        return MGMT_ERR_EINVAL;
+    70	    }
+    71	
+    72	    rc = mgmt_cbuf_setoerr(cb, 0);
+    (gdb) list *0x00017338  <--- not relevant
+    (gdb) list *0x00017cc8  <--- neither is this
+    (gdb) list *0x00015c2b
+    0x15c2b is in cbor_mbuf_writer (repos/apache-mynewt-core/encoding/tinycbor/src/cbor_mbuf_writer.c:32).
+    27	{
+    28	    int rc;
+    29	    struct cbor_mbuf_writer *cb = (struct cbor_mbuf_writer *) arg;
+    30	
+    31	    rc = os_mbuf_append(cb->m, data, len);  <-- Does not seem likely. This probably ended here due to an earlier work that this task was doing.
+    32	    if (rc) {
+    33	        return CborErrorOutOfMemory;
+    34	    }
+    35	    cb->enc.bytes_written += len;
+    36	    return CborNoError;
+    (gdb) list *0x00015c1d
+    0x15c1d is in cbor_mbuf_writer (repos/apache-mynewt-core/encoding/tinycbor/src/cbor_mbuf_writer.c:27).
+    22	#include <tinycbor/cbor.h>
+    23	#include <tinycbor/cbor_mbuf_writer.h>
+    24	
+    25	int
+    26	cbor_mbuf_writer(struct cbor_encoder_writer *arg, const char *data, int len)
+    27	{
+    28	    int rc;      <---- Nope. Not relevant.
+    29	    struct cbor_mbuf_writer *cb = (struct cbor_mbuf_writer *) arg;
+    30	
+    31	    rc = os_mbuf_append(cb->m, data, len);
+    (gdb) list *0x0001161d
+    0x1161d is in create_container (repos/apache-mynewt-core/encoding/tinycbor/src/cborencoder.c:244).
+    239	    memcpy(where, &v, sizeof(v));
+    240	}
+    241	
+    242	static inline CborError append_to_buffer(CborEncoder *encoder, const void *data, size_t len)
+    243	{
+    244	    return encoder->writer->write(encoder->writer, data, len);  <--- Hmmm, unlikely
+    245	}
+    246	
+    247	static inline CborError append_byte_to_buffer(CborEncoder *encoder, uint8_t byte)
+    248	{
+    (gdb) list *0x00011841
+    0x11841 is in preparse_value (repos/apache-mynewt-core/encoding/tinycbor/src/cborparser.c:182).
+    177	    /* are we at the end? */
+    178	    if (it->offset == parser->end)
+    179	        return CborErrorUnexpectedEOF;
+    180	
+    181	    uint8_t descriptor = parser->d->get8(parser->d, it->offset); <--- Probably not relevant.
+    182	    uint8_t type = descriptor & MajorTypeMask;
+    183	    it->type = type;
+    184	    it->flags = 0;
+    185	    it->extra = (descriptor &= SmallValueMask);
+    186	
+    (gdb) list *0x000117c9
+    0x117c9 is in cbor_encoder_create_map (repos/apache-mynewt-core/encoding/tinycbor/src/cborencoder.c:521).
+    516	 */
+    517	CborError cbor_encoder_create_map(CborEncoder *encoder, CborEncoder *mapEncoder, size_t length)
+    518	{
+    519	    if (length != CborIndefiniteLength && length > SIZE_MAX / 2)
+    520	        return CborErrorDataTooLarge;
+    521	    return create_container(encoder, mapEncoder, length, MapType << MajorTypeShift);     <-- I don't think this is relevant either.
+    522	}
+    523	
+    524	/**
+    525	 * Creates a indefinite-length byte string in the CBOR stream provided by
+    (gdb) list *0x00013d8f
+    0x13d8f is in nmgr_handle_req (repos/apache-mynewt-core/mgmt/newtmgr/src/newtmgr.c:261).
+    256	        } else {
+    257	            rc = MGMT_ERR_ENOENT;
+    258	        }
+    259	    } else if (hdr.nh_op == NMGR_OP_WRITE) {
+    260	        if (handler->mh_write) {
+    261	            rc = handler->mh_write(&nmgr_task_cbuf.n_b); <-- This is part of it.
+    262	        } else {
+    263	            rc = MGMT_ERR_ENOENT;
+    264	        }
+    265	    } else {
+
+I was sending a newtmgr command which crashed the system. As you can see from this this example, little less than 50% of the addresses were part of the call chain. So read this output with care.
+
+
+Coredump
+--------
+
+Coredump contains a full dump of system memory, and CPU registers. You can inspect the system state, including stack of the failing task, or any of the global state.
+
+You can enable coredumps by setting syscfg variable OS_COREDUMP to 1. When crash happens, dumper will write the contents to flash_map region COREDUMP_FLASH_AREA.
+After the crash, you can use imgmgr to download the coredump for offline analysis. To enable download/erase commands, you need to set syscfg variable IMGMGR_COREDUMP to 1.
+
+Coredump package does not overwrite a previous coredump, if it exists in the flash. To get a new one, you first need to erase the area.
+
+You can either use a dedicated area of flash, or use image slot 1 as the target.
+If image slot1 and coredump areas are co-located, coredump will overwrite the image, unless image upgrade is in progress (slot1 marked as pending, or slot0 is not confirmed).
+
+Fetching Coredump with newtmgr
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+newtmgr coredump management commands are handled by imgmgr. You can query whether corefile is present, download one if it exists, and then erase the area afterwards.
+
+.. code-block:: console
+    
+    [marko@IsMyLaptop:~/src2/incubator-mynewt-blinky]$ newtmgr -c ttys005 image corelist
+    Corefile present
+    [marko@IsMyLaptop:~/src2/incubator-mynewt-blinky]$ newtmgr -c ttys005 image coredownload -e core.elf
+    0
+    512
+    1024
+    1536
+     ...
+    65676
+    Done writing core file to core.elf; hash=7e20dcdd136c6796fcb2a51e7384e90800d2ec045f2ee088af32529e929e2130
+    [marko@IsMyLaptop:~/src2/incubator-mynewt-blinky]$ newtmgr -c ttys005 image coreerase
+    Done
+
+I specified option '-e' to coredownload command. This converts the internal data representation to ELF file; a format that gdb understands.
+
+Coredump Analysis Offline
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Not all GDB configurations support corefiles. Specifically for the
+architecture we're showing here, 'arm-none-eabi-gdb' does not have it
+built in. However, 'arm-elf-linux-gdb' does.
+
+Here's how I built it with MacOS:
+
+.. code-block:: console
+    
+    tar xvzf gdb-7.8.1.tar.gz
+    cd gdb-7.8.1
+    ./configure --target=arm-elf-linux --without-lzma --without-guile --without-libunwind-ia64 --with-zlib
+    make
+    gdb/gdb -v
+
+And this is how I built it for Linux:
+
+
+.. code-block:: console
+    
+    sudo apt-get install zlibc zlib1g zlib1g-dev libexpat-dev libncurses5-dev liblzma-dev
+    tar xvzf gdb-7.8.1.tar.gz
+    cd gdb-7.8.1
+    ./configure --target=arm-elf-linux --with-lzma --with-expat --without-libunwind-ia64 --with-zlib --without-babeltrace
+    make
+    gdb/gdb -v
+
+Now that I have a suitable version of gdb at hand, I can use it to analyze
+the corefile.
+
+
+.. code-block:: console
+    
+    [marko@IsMyLaptop:~/src2/incubator-mynewt-blinky]$ arm-elf-linux-gdb bin/targets/slinky_nrf52/app/apps/slinky/slinky.elf core.elf 
+    GNU gdb (GDB) 7.8.1
+    Copyright (C) 2014 Free Software Foundation, Inc.
+    License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
+    This is free software: you are free to change and redistribute it.
+    There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
+    and "show warranty" for details.
+    This GDB was configured as "--host=x86_64-apple-darwin14.5.0 --target=arm-elf-linux".
+    Type "show configuration" for configuration details.
+    For bug reporting instructions, please see:
+    <http://www.gnu.org/software/gdb/bugs/>.
+    Find the GDB manual and other documentation resources online at:
+    <http://www.gnu.org/software/gdb/documentation/>.
+    For help, type "help".
+    Type "apropos word" to search for commands related to "word"...
+    No symbol table is loaded.  Use the "file" command.
+    Reading symbols from bin/targets/slinky_nrf52/app/apps/slinky/slinky.elf...done.
+    [New process 1]
+    #0  0x00014f28 in crash_device (
+         how=how@entry=0x20001dc8 <os_main_stack+3904> "div0")
+         at repos/apache-mynewt-core/test/crash_test/src/crash_test.c:47
+    47	        val3 = val1 / val2;
+    (gdb) bt
+    #0  0x00014f28 in crash_device (
+        how=how@entry=0x20001dc8 <os_main_stack+3904> "div0")
+        at repos/apache-mynewt-core/test/crash_test/src/crash_test.c:47
+    #1  0x0001501a in crash_test_nmgr_write (cb=0x20003464 <nmgr_task_cbuf>)
+        at repos/apache-mynewt-core/test/crash_test/src/crash_nmgr.c:67
+    #2  0x00013d8e in nmgr_handle_req (
+        nt=nt@entry=0x200034e4 <nmgr_shell_transport>, 
+        req=0x20002014 <os_msys_init_1_data+292>)
+        at repos/apache-mynewt-core/mgmt/newtmgr/src/newtmgr.c:261
+    #3  0x00013dfc in nmgr_process (nt=0x200034e4 <nmgr_shell_transport>)
+        at repos/apache-mynewt-core/mgmt/newtmgr/src/newtmgr.c:325
+    #4  0x00013e08 in nmgr_event_data_in (ev=<optimized out>)
+        at repos/apache-mynewt-core/mgmt/newtmgr/src/newtmgr.c:332
+    #5  0x0000925c in os_eventq_run (evq=<optimized out>)
+        at repos/apache-mynewt-core/kernel/os/src/os_eventq.c:162
+    #6  0x00008932 in main (argc=<optimized out>, argv=<optimized out>)
+        at repos/apache-mynewt-core/apps/slinky/src/main.c:289
+    (gdb) p g_current_task 
+    $1 = (struct os_task *) 0x20001e88 <os_main_task>
+
+Setting up coredumps, setting up a mechanism to download them, managing
+them on the device and building gdb is certainly more work, but you'll
+have much more data available when you start analyzing problems.
+I don't think I can emphasize too much how valuable having all this data
+is, when you're stuck with a difficult-to-solve crash.
+
+
+Task Stack Use
+--------------
+
+One of the common problems is stack overflow within a task. Mynewt OS fills
+the stack of a task with a pattern at the time task gets created. If you're
+suspecting that stack might be blown, check the start of the stack (and
+memory right before it).
+
+There are also APIs to check the stack use. Task statistics, accessible
+with console CLI or newtmgr, attempt to report the stack use by the tasks.
+They do it by inspecting the presence of this pattern, so it is possible
+to fool them also.
+
+Here is what that data looks like at console. 'stksz' is the amount of
+stack, and 'stkuse' is how much of it we think the task used it.
+
+.. code-block:: console
+
+    7109237 compat> tasks
+    7109387 Tasks: 
+    7109387     task pri tid  runtime      csw    stksz   stkuse   lcheck   ncheck flg
+    7109390     idle 255   0  7109375    72810       64       26        0        0
+    7109392     main 127   1       15     6645     1024      388        0        0
+    7109394    task1   8   2        0    55543      192      116        0        0
+    7109396    task2   9   3        0    55543       64       28        0        0
+
+There are some helper macros for this when using gdb also. You'll need
+to eyeball the stack manually, but at least it will tell you where to
+find the stacks.
+
+.. code-block:: console
+
+    [marko@IsMyLaptop:~/src2/incubator-mynewt-blinky]$ newt debug slinky_nrf52
+    Debugging bin/targets/slinky_nrf52/app/apps/slinky/slinky.elf
+     ...
+    Reading symbols from bin/targets/slinky_nrf52/app/apps/slinky/slinky.elf...done.
+    os_tick_idle (ticks=128)
+         at repos/apache-mynewt-core/hw/mcu/nordic/nrf52xxx/src/hal_os_tick.c:164
+    164	    if (ticks > 0) {
+    (gdb) os_tasks
+    Undefined command: "os_tasks".  Try "help".
+    (gdb) source repos/apache-mynewt-core/compiler/gdbmacros/os.gdb 
+    (gdb) os_tasks
+     prio state      stack  stksz       task name
+    * 255   0x1 0x20000e88     64 0x200035fc idle
+      127   0x2 0x20001e88   1024 0x20001e88 main
+        8   0x2 0x20003ab0    192 0x20000cbc task1
+        9   0x2 0x20003bc0     64 0x20000d0c task2
+
+
+Stack Check during Context Switch
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You can ask OS to check the top of the stack on every context switch.
+It'll walk over X number of os_stack_t elements, checking if our pattern
+is still there for the task which is about to be switched out.
+
+You can enable this by setting syscfg variable OS_CTX_SW_STACK_CHECK to 1.
+Another syscfg variable, OS_CTX_SW_STACK_GUARD, controls how far into
+the stack we peek.
+
+Memory Corruption
+-----------------
+
+The recommended way of allocating blocks of memory is by using the
+OS memory pools. As with all dynamically allocated memory, there can
+issues with using after it gets freed, or it does not get freed at
+all, code tries to free the same element multiple times, or you end up
+overwriting the data within the next element.
+
+There are syscfg variables you can set to get OS to do some of
+sanity checks for you.
+
+Sanity Check on Free
+~~~~~~~~~~~~~~~~~~~~
+
+By setting syscfg variable OS_MEMPOOL_CHECK to 1, OS will act when
+an element is freed. It will walk through the list of already freed
+entries, and check that the entry being freed does not already appear
+in the free list. It will also check that the pool entry the code is
+trying to free is within the memory range belonging to that pool.
+
+
+Poisoning Pool
+~~~~~~~~~~~~~~
+
+If you set syscfg variable OS_MEMPOOL_POISON, the OS fills pool
+entry memory with a data pattern whenever that entry is being freed.
+It then checks that this pattern has not been disturbed when that
+pool entry is allocated again. This would help to detect the scenarios
+when code is still using a freed pool entry. Of course, the detection
+will only be triggered if the code tries to modify the data belonging
+tot that entry.
+
+Pool Guard Areas
+~~~~~~~~~~~~~~~~
+
+Normally OS takes the memory region allocated for pool use and
+splits it into chunks which are exactly the size of the pool entry (taking
+into account the alignment restriction). By setting syscfg variable
+OS_MEMPOOL_GUARD to 1, it creates a 'guard area' between pool elements,
+and writes a specific data pattern to it. This is then checked whenever
+the pool entry is freed, or allocated. If code writes past the data area
+belonging to pool entry, the pattern would likely be disturbed, and would
+cause an assert.
+
+Exhausting Memory
+~~~~~~~~~~~~~~~~~
+
+Memory pool usage can be monitored through the mempool API. The data
+collected there can be seen via the console CLI and/or using newtmgr.
+
+Here is that data as seen from console:
+
+.. code-block:: console
+
+    7375292 compat> mpool
+    7375581 Mempools: 
+    7375581                             name blksz  cnt free  min
+    7375584                           msys_1   292   12   12    8
+    7375585              modlog_mapping_pool    12   16   12   12
+
+
+We list the memory pools OS knows about. 'cnt' tells how many
+elements the pool has in total. 'free' tells how many elements are
+currently in the free pool, and 'min' tells what's the low point
+for the number of elements within the pool.
+
+What you should check is the 'min' number. If it gets to 0, it means
+that the pool has been exhausted at one point. Depending on what
+the pool is used for, you might want to take action.
+
+There's some gdb helper macros to inspect the data belonging to
+the default networking memory pool, msys_1.
+
+.. code-block:: console
+
+    (gdb) source repos/apache-mynewt-core/compiler/gdbmacros/mbuf.gdb 
+    (gdb) mn_msys1_print 
+    Mbuf addr: 0x20001ef0
+    Mbuf header: $1 = {om_data = 0x20002380 <os_msys_init_1_data+1168> "\244$", 
+      om_flags = 0 '\000', om_pkthdr_len = 8 '\b', om_len = 14, 
+      om_omp = 0x20002ca0 <os_msys_init_1_mbuf_pool>, om_next = {sle_next = 0x0}, 
+      om_databuf = 0x20001f00 <os_msys_init_1_data+16> "\016"}
+    Packet header: $2 = {omp_len = 14, omp_flags = 0, omp_next = {stqe_next = 0x0}}
+      ....
+    ---Type <return> to continue, or q <return> to quit---q
+    Quit
+    (gdb) help mn_msys1_print
+    usage: mn_msys1_print
+    
+    Prints all mbufs in the first msys pool.  Both allocated and unallocated mbufs
+    are printed.
+    (gdb) mn_msys1_free_print
+    Mbuf addr: 0x20002138
+     ....
+    Mbuf addr: 0x20002b7c
+
+
+Crash in Log
+------------
+
+System restarts can also be recorded in reboot log. Assuming reboot log
+has been set up, it'll write a record either when system is asked
+to reset (managed reset), or when system is coming back after an
+unexpected reset.
+
+To facilitate the latter, you should include a call from main()
+to reboot package, reporting what HAL is telling you.
+
+.. code-block:: c
+
+    int
+    main(int argc, char **argv)
+    {
+
+        sysinit();
+
+	/* .... */
+        reboot_start(hal_reset_cause());
+
+This will then show if the system was restarted due to POR, brownout,
+or hardware watchdog (the cause determination is MCU specific, so YMMV).
+As you remember, exit via assert() and unhandled interrupt is going
+via the dumper. So these kind of reboots will show up as 'SOFT' reset.
+
+Restarts due to hardware watchdog will show up here, however. You can also
+add a printout to beginning of main(), outputting what hal_reset_cause_str()
+returns. That will generate a report on the console.
diff --git a/docs/tutorials/tooling/tooling.rst b/docs/tutorials/tooling/tooling.rst
index 648ac9f..015d887 100644
--- a/docs/tutorials/tooling/tooling.rst
+++ b/docs/tutorials/tooling/tooling.rst
@@ -5,4 +5,5 @@
    :maxdepth: 1
   
    Segger RTT <segger_rtt>
-   Segger Sysview <segger_sysview>
\ No newline at end of file
+   Segger Sysview <segger_sysview>
+   Error Diagnostics <error_diagnostics>