diff --git a/FreeRTOS/Source/portable/esp8266/port.c b/FreeRTOS/Source/portable/esp8266/port.c
index 21a0d55..a9dac2d 100644
--- a/FreeRTOS/Source/portable/esp8266/port.c
+++ b/FreeRTOS/Source/portable/esp8266/port.c
@@ -73,6 +73,7 @@
 #include <malloc.h>
 #include <unistd.h>
 #include <stdio.h>
+#include <xtensa_ops.h>
 
 #include "FreeRTOS.h"
 #include "task.h"
@@ -87,7 +88,7 @@ char level1_int_disabled;
    After tasks start, task stacks are all allocated from the heap and
    FreeRTOS checks for stack overflow.
 */
-static uint32_t xPortSupervisorStackPointer;
+uint32_t xPortSupervisorStackPointer;
 
 /*
  * Stack initialization
@@ -220,7 +221,7 @@ size_t xPortGetFreeHeapSize( void )
 
     uint32_t sp = xPortSupervisorStackPointer;
     if(sp == 0) /* scheduler not started */
-        __asm__ __volatile__ ("mov %0, a1\n" : "=a"(sp));
+        SP(sp);
     return sp - brk_val + mi.fordblks;
 }
 
diff --git a/common.mk b/common.mk
index 8da70cd..c8fee7a 100644
--- a/common.mk
+++ b/common.mk
@@ -75,6 +75,9 @@ FLAVOR ?= release # or debug
 # Compiler names, etc. assume gdb
 CROSS ?= xtensa-lx106-elf-
 
+# Path to the filteroutput.py tool
+FILTEROUTPUT ?= $(ROOT)/utils/filteroutput.py
+
 AR = $(CROSS)ar
 CC = $(CROSS)gcc
 CPP = $(CROSS)cpp
@@ -385,7 +388,7 @@ size: $(PROGRAM_OUT)
 	$(Q) $(CROSS)size --format=sysv $(PROGRAM_OUT)
 
 test: flash
-	screen $(ESPPORT) 115200
+	$(FILTEROUTPUT) --port $(ESPPORT) --baud 115200 --elf $(PROGRAM_OUT)
 
 # the rebuild target is written like this so it can be run in a parallel build
 # environment without causing weird side effects
diff --git a/core/app_main.c b/core/app_main.c
index 45f7e2f..a38fb5f 100644
--- a/core/app_main.c
+++ b/core/app_main.c
@@ -39,8 +39,6 @@ void user_init(void);
 #define RTCMEM_BACKUP_PHY_VER  31
 #define RTCMEM_SYSTEM_PP_VER   62
 
-#define halt()  while (1) {}
-
 extern uint32_t _bss_start;
 extern uint32_t _bss_end;
 
@@ -63,7 +61,6 @@ static void IRAM set_spi0_divisor(uint32_t divisor);
 static void zero_bss(void);
 static void init_networking(sdk_phy_info_t *phy_info, uint8_t *mac_addr);
 static void init_g_ic(void);
-static void dump_excinfo(void);
 static void user_start_phase2(void);
 static void dump_flash_sector(uint32_t start_sector, uint32_t length);
 static void dump_flash_config_sectors(uint32_t start_sector);
@@ -80,11 +77,11 @@ static void IRAM get_otp_mac_address(uint8_t *buf) {
     if (!(otp_flags & 0x8000)) {
         //FIXME: do we really need this check?
         printf("Firmware ONLY supports ESP8266!!!\n");
-        halt();
+        abort();
     }
     if (otp_id0 == 0 && otp_id1 == 0) {
         printf("empty otp\n");
-        halt();
+        abort();
     }
     if (otp_flags & 0x1000) {
         // If bit 12 is set, it indicates that the vendor portion of the MAC
@@ -125,23 +122,6 @@ static void IRAM set_spi0_divisor(uint32_t divisor) {
     SPI(0).CTRL0 = SET_FIELD(SPI(0).CTRL0, SPI_CTRL0_CLOCK, clkdiv);
 }
 
-// .text+0x148
-void IRAM sdk_user_fatal_exception_handler(void) {
-    if (!sdk_NMIIrqIsOn) {
-        vPortEnterCritical();
-        do {
-            DPORT.DPORT0 &= 0xffffffe0;
-        } while (DPORT.DPORT0 & 0x00000001);
-    }
-    Cache_Read_Disable();
-    Cache_Read_Enable(0, 0, 1);
-    dump_excinfo();
-    uart_flush_txfifo(0);
-    uart_flush_txfifo(1);
-    sdk_system_restart_in_nmi();
-    halt();
-}
-
 
 static void IRAM default_putc(char c) {
     uart_putc(0, c);
@@ -255,7 +235,7 @@ static void zero_bss(void) {
 static void init_networking(sdk_phy_info_t *phy_info, uint8_t *mac_addr) {
     if (sdk_register_chipv6_phy(phy_info)) {
         printf("FATAL: sdk_register_chipv6_phy failed");
-        halt();
+        abort();
     }
     uart_set_baud(0, 74906);
     uart_set_baud(1, 74906);
@@ -311,37 +291,6 @@ static void init_g_ic(void) {
     }
 }
 
-// .Lfunc008 -- .irom0.text+0x2a0
-static void dump_excinfo(void) {
-    uint32_t exccause, epc1, epc2, epc3, excvaddr, depc, excsave1;
-    uint32_t excinfo[8];
-
-    RSR(exccause, exccause);
-    printf("Fatal exception (%d): \n", (int)exccause);
-    RSR(epc1, epc1);
-    RSR(epc2, epc2);
-    RSR(epc3, epc3);
-    RSR(excvaddr, excvaddr);
-    RSR(depc, depc);
-    RSR(excsave1, excsave1);
-    printf("%s=0x%08x\n", "epc1", epc1);
-    printf("%s=0x%08x\n", "epc2", epc2);
-    printf("%s=0x%08x\n", "epc3", epc3);
-    printf("%s=0x%08x\n", "excvaddr", excvaddr);
-    printf("%s=0x%08x\n", "depc", depc);
-    printf("%s=0x%08x\n", "excsave1", excsave1);
-    sdk_system_rtc_mem_read(0, excinfo, 32); // Why?
-    excinfo[0] = 2;
-    excinfo[1] = exccause;
-    excinfo[2] = epc1;
-    excinfo[3] = epc2;
-    excinfo[4] = epc3;
-    excinfo[5] = excvaddr;
-    excinfo[6] = depc;
-    excinfo[7] = excsave1;
-    sdk_system_rtc_mem_write(0, excinfo, 32);
-}
-
 // .irom0.text+0x398
 void sdk_wdt_init(void) {
     WDT.CTRL &= ~WDT_CTRL_ENABLE;
diff --git a/core/debug_dumps.c b/core/debug_dumps.c
new file mode 100644
index 0000000..7f1a1ca
--- /dev/null
+++ b/core/debug_dumps.c
@@ -0,0 +1,231 @@
+/* Code for dumping status/debug output/etc, including fatal
+ * exception handling & abort implementation.
+ *
+ * Part of esp-open-rtos
+ *
+ * Partially reverse engineered from MIT licensed Espressif RTOS SDK Copyright (C) Espressif Systems.
+ * Additions Copyright (C) 2015 Superhouse Automation Pty Ltd
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <string.h>
+#include <stdio.h>
+#include <FreeRTOS.h>
+#include <task.h>
+#include <malloc.h>
+#include <unistd.h>
+
+#include "debug_dumps.h"
+#include "common_macros.h"
+#include "xtensa_ops.h"
+#include "esp/rom.h"
+#include "esp/uart.h"
+#include "espressif/esp_common.h"
+#include "sdk_internal.h"
+
+/* Forward declarations */
+static void IRAM fatal_handler_prelude(void);
+/* Inner parts of crash handlers */
+typedef void __attribute__((noreturn)) (*fatal_exception_handler_fn)(uint32_t *sp, bool registers_saved_on_stack);
+static void __attribute__((noreturn)) standard_fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack);
+static void __attribute__((noreturn)) second_fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack);
+static void __attribute__((noinline)) __attribute__((noreturn)) abort_handler_inner(uint32_t *caller, uint32_t *sp);
+
+static IRAM_DATA fatal_exception_handler_fn fatal_exception_handler_inner = standard_fatal_exception_handler_inner;
+
+/* fatal_exception_handler called from any unhandled user exception
+ *
+ * (similar to a hard fault on other processor architectures)
+ *
+ * This function is run from IRAM, but the majority of the handler
+ * runs from flash after fatal_handler_prelude ensures it is mapped
+ * safely.
+ */
+void IRAM __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp, bool registers_saved_on_stack) {
+    fatal_handler_prelude();
+    fatal_exception_handler_fn inner_fn = fatal_exception_handler_inner;
+    inner_fn(sp, registers_saved_on_stack);
+}
+
+/* Abort implementation
+ *
+ * Replaces the weak-linked abort implementation provided by newlib libc.
+ *
+ * Disable interrupts, enable flash mapping, dump stack & caller
+ * address, restart.
+ *
+ * This function is run from IRAM, but the majority of the abort
+ * handler runs from flash after fatal_handler_prelude ensures it is
+ * mapped safely.
+ *
+ */
+void IRAM abort(void) {
+    uint32_t *sp, *caller;
+    RETADDR(caller);
+    /* abort() caller is one instruction before our return address */
+    caller = (uint32_t *)((intptr_t)caller - 3);
+    SP(sp);
+    fatal_handler_prelude();
+    abort_handler_inner(caller, sp);
+}
+
+/* Dump exception information from special function registers */
+static void dump_excinfo(void) {
+    uint32_t exccause, epc1, epc2, epc3, excvaddr, depc, excsave1;
+    uint32_t excinfo[8];
+
+    RSR(exccause, exccause);
+    printf("Fatal exception (%d): \n", (int)exccause);
+    RSR(epc1, epc1);
+    RSR(epc2, epc2);
+    RSR(epc3, epc3);
+    RSR(excvaddr, excvaddr);
+    RSR(depc, depc);
+    RSR(excsave1, excsave1);
+    printf("%s=0x%08x\n", "epc1", epc1);
+    printf("%s=0x%08x\n", "epc2", epc2);
+    printf("%s=0x%08x\n", "epc3", epc3);
+    printf("%s=0x%08x\n", "excvaddr", excvaddr);
+    printf("%s=0x%08x\n", "depc", depc);
+    printf("%s=0x%08x\n", "excsave1", excsave1);
+    sdk_system_rtc_mem_read(0, excinfo, 32); // Why?
+    excinfo[0] = 2;
+    excinfo[1] = exccause;
+    excinfo[2] = epc1;
+    excinfo[3] = epc2;
+    excinfo[4] = epc3;
+    excinfo[5] = excvaddr;
+    excinfo[6] = depc;
+    excinfo[7] = excsave1;
+    sdk_system_rtc_mem_write(0, excinfo, 32);
+}
+
+/* dump stack memory (frames above sp) to stdout
+
+   There's a lot of smart stuff we could do while dumping stack
+   but for now we just dump what looks like our stack region.
+
+   Probably dumps more memory than it needs to, the first instance of
+   0xa5a5a5a5 probably constitutes the end of our stack.
+*/
+void dump_stack(uint32_t *sp) {
+    printf("\nStack: SP=%p\n", sp);
+    for(uint32_t *p = sp; p < sp + 32; p += 4) {
+        if((intptr_t)p >= 0x3fffc000) {
+            break; /* approximate end of RAM */
+        }
+        printf("%p: %08x %08x %08x %08x\n", p, p[0], p[1], p[2], p[3]);
+        if(p[0] == 0xa5a5a5a5 && p[1] == 0xa5a5a5a5
+           && p[2] == 0xa5a5a5a5 && p[3] == 0xa5a5a5a5) {
+            break; /* FreeRTOS uses this pattern to mark untouched stack space */
+        }
+    }
+}
+
+/* Dump normal registers that were stored above 'sp'
+   by the exception handler preamble
+*/
+void dump_registers_in_exception_handler(uint32_t *sp) {
+    uint32_t excsave1;
+    uint32_t *saved = sp - (0x50 / sizeof(uint32_t));
+    printf("Registers:\n");
+    RSR(excsave1, excsave1);
+    printf("a0 %08x ", excsave1);
+    printf("a1 %08x ", (intptr_t)sp);
+    for(int a = 2; a < 14; a++) {
+        printf("a%-2d %08x%c", a, saved[a+3], a == 3 || a == 7 || a == 11 ? '\n':' ');
+    }
+    printf("SAR %08x\n", saved[0x13]);
+}
+
+static void __attribute__((noreturn)) post_crash_reset(void) {
+    uart_flush_txfifo(0);
+    uart_flush_txfifo(1);
+    sdk_system_restart_in_nmi();
+    while(1) {}
+}
+
+/* Prelude ensures exceptions/NMI off and flash is mapped, allowing
+   calls to non-IRAM functions.
+*/
+static void IRAM fatal_handler_prelude(void) {
+    if (!sdk_NMIIrqIsOn) {
+        vPortEnterCritical();
+        do {
+            DPORT.DPORT0 &= 0xffffffe0;
+        } while (DPORT.DPORT0 & 0x00000001);
+    }
+    Cache_Read_Disable();
+    Cache_Read_Enable(0, 0, 1);
+}
+
+/* Main part of fatal exception handler, is run from flash to save
+   some IRAM.
+*/
+static void standard_fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack) {
+    /* Replace the fatal exception handler 'inner' function so we
+       don't end up in a crash loop if this handler crashes. */
+    fatal_exception_handler_inner = second_fatal_exception_handler_inner;
+    dump_excinfo();
+    if (sp) {
+        if (registers_saved_on_stack) {
+            dump_registers_in_exception_handler(sp);
+        }
+        dump_stack(sp);
+    }
+    dump_heapinfo();
+    post_crash_reset();
+}
+
+/* This is the exception handler that gets called if a crash occurs inside the standard handler,
+   so we don't end up in a crash loop. It doesn't rely on contents of stack or heap.
+*/
+static void second_fatal_exception_handler_inner(uint32_t *sp, bool registers_saved_on_stack) {
+    dump_excinfo();
+    printf("Second fatal exception occured inside fatal exception handler. Can't continue.\n");
+    post_crash_reset();
+}
+
+void dump_heapinfo(void)
+{
+    extern char _heap_start;
+    extern uint32_t xPortSupervisorStackPointer;
+    struct mallinfo mi = mallinfo();
+    uint32_t brk_val = (uint32_t) sbrk(0);
+    uint32_t sp = xPortSupervisorStackPointer;
+    if(sp == 0) {
+        SP(sp);
+    }
+
+    /* Total free heap is all memory that could be allocated via
+       malloc (assuming fragmentation doesn't become a problem) */
+    printf("\nFree Heap: %d\n", sp - brk_val + mi.fordblks);
+
+    /* delta between brk & supervisor sp is the contiguous memory
+       region that is available to be put into heap space via
+       brk(). */
+    printf("_heap_start %p brk 0x%08x supervisor sp 0x%08x sp-brk %d bytes\n",
+           &_heap_start, brk_val, sp, sp-brk_val);
+
+    /* arena/fordblks/uordblks determines the amount of free space
+      inside the heap region already added via brk(). May be
+      fragmented.
+
+       The values in parentheses are the values used internally by
+       nano-mallocr.c, the field names outside parentheses are the
+       POSIX compliant field names of the mallinfo structure.
+
+       "arena" should be equal to brk-_heap_start ie total size available.
+     */
+    printf("arena (total_size) %d fordblks (free_size) %d uordblocks (used_size) %d\n",
+           mi.arena, mi.fordblks, mi.uordblks);
+}
+
+/* Main part of abort handler, can be run from flash to save some
+   IRAM.
+*/
+static void abort_handler_inner(uint32_t *caller, uint32_t *sp) {
+    printf("abort() invoked at %p.\n", caller);
+    dump_stack(sp);
+    dump_heapinfo();
+    post_crash_reset();
+}
diff --git a/core/esp_interrupts.c b/core/esp_interrupts.c
index 4fd68b7..b3aefff 100644
--- a/core/esp_interrupts.c
+++ b/core/esp_interrupts.c
@@ -9,6 +9,8 @@
 
 _xt_isr isr[16];
 
+bool esp_in_isr;
+
 void IRAM _xt_isr_attach(uint8_t i, _xt_isr func)
 {
     isr[i] = func;
@@ -20,6 +22,8 @@ void IRAM _xt_isr_attach(uint8_t i, _xt_isr func)
 */
 uint16_t IRAM _xt_isr_handler(uint16_t intset)
 {
+    esp_in_isr = true;
+
     /* WDT has highest priority (occasional WDT resets otherwise) */
     if(intset & BIT(INUM_WDT)) {
         _xt_clear_ints(BIT(INUM_WDT));
@@ -35,5 +39,7 @@ uint16_t IRAM _xt_isr_handler(uint16_t intset)
         intset -= mask;
     }
 
+    esp_in_isr = false;
+
     return 0;
 }
diff --git a/core/exception_vectors.S b/core/exception_vectors.S
index c1f1761..000e742 100644
--- a/core/exception_vectors.S
+++ b/core/exception_vectors.S
@@ -68,7 +68,9 @@ DebugExceptionVector:
         .type   DebugExceptionVector, @function
 
         wsr     a0, excsave2
-        call0   sdk_user_fatal_exception_handler
+        mov     a2, a1
+        movi    a3, 0
+        call0   fatal_exception_handler
         rfi     2
 
         .org    VecBase + 0x20
@@ -81,7 +83,9 @@ KernelExceptionVector:
         .type   KernelExceptionVector, @function
 
         break   1, 0
-        call0   sdk_user_fatal_exception_handler
+        mov     a2, a1
+        movi    a3, 0
+        call0   fatal_exception_handler
         rfe
 
         .org    VecBase + 0x50
@@ -98,7 +102,9 @@ DoubleExceptionVector:
         .type   DoubleExceptionVector, @function
 
         break   1, 4
-        call0   sdk_user_fatal_exception_handler
+        mov     a2, a1
+        movi    a3, 0
+        call0   fatal_exception_handler
 
 /* Reset vector at offset 0x80 is unused, as vecbase gets reset to mask ROM
  * vectors on chip reset. */
@@ -251,11 +257,13 @@ LoadStoreErrorHandler:
          * will have correct values */
         wsr     a0, sar
         l32i    a0, sp, 0
-        l32i    a2, sp, 0x08
+        /*l32i    a2, sp, 0x08*/
         l32i    a3, sp, 0x0c
         l32i    a4, sp, 0x10
         rsr     a1, excsave1
-        call0   sdk_user_fatal_exception_handler
+        mov     a2, a1
+        movi    a3, 0
+        call0   fatal_exception_handler
 
         .balign 4
 .LSE_assign_a1:
@@ -515,7 +523,9 @@ UserExceptionHandler:
         .literal_position
 .LUserFailOtherExceptionCause:
         break   1, 1
-        call0   sdk_user_fatal_exception_handler
+        addi    a2, a1, 0x50 /* UserExceptionHandler pushes stack down 0x50 */
+        movi    a3, 1
+        call0   fatal_exception_handler
 
 /* _xt_user_exit is pushed onto the stack as part of the user exception handler,
    restores same set registers which were saved there and returns from exception */
diff --git a/core/include/common_macros.h b/core/include/common_macros.h
index 7c6dd56..4aa6248 100644
--- a/core/include/common_macros.h
+++ b/core/include/common_macros.h
@@ -65,18 +65,16 @@
 */
 #define IRAM __attribute__((section(".iram1.text")))
 
-/* Use this macro to place read-only data into Instruction RAM (IRAM)
+/* Use this macro to place data into Instruction RAM (IRAM)
    instead of loaded into rodata which resides in DRAM.
 
+   (IRAM can also be written to as necessary.)
+
    This may be useful to free up data RAM. However all data read from
    the instruction space must be 32-bit aligned word reads
    (non-aligned reads will use an interrupt routine to "fix" them and
    still work, but are very slow..
 */
-#ifdef	__cplusplus
-    #define IRAM_DATA __attribute__((section(".iram1.rodata")))
-#else
-    #define IRAM_DATA __attribute__((section(".iram1.rodata"))) const
-#endif
+#define IRAM_DATA __attribute__((section(".iram1.rodata")))
 
 #endif
diff --git a/core/include/debug_dumps.h b/core/include/debug_dumps.h
new file mode 100644
index 0000000..ab3c972
--- /dev/null
+++ b/core/include/debug_dumps.h
@@ -0,0 +1,25 @@
+/* Functions for dumping status/debug output/etc, including fatal
+ * exception handling.
+ *
+ * Part of esp-open-rtos
+ *
+ * Copyright (C) 2015-2016 Superhouse Automation Pty Ltd
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef _DEBUG_DUMPS_H
+#define _DEBUG_DUMPS_H
+#include <stdint.h>
+
+/* Dump stack memory to stdout, starting from stack pointer address sp. */
+void dump_stack(uint32_t *sp);
+
+/* Dump heap statistics to stdout */
+void dump_heapinfo(void);
+
+/* Called from exception_vectors.S when a fatal exception occurs.
+
+   Probably not useful to be called in other contexts.
+*/
+void __attribute__((noreturn)) fatal_exception_handler(uint32_t *sp, bool registers_saved_on_stack);
+
+#endif
diff --git a/core/include/xtensa_ops.h b/core/include/xtensa_ops.h
index 49d1fbe..52eba2a 100644
--- a/core/include/xtensa_ops.h
+++ b/core/include/xtensa_ops.h
@@ -14,6 +14,20 @@
 // GCC macros for reading, writing, and exchanging Xtensa processor special
 // registers:
 
+/* Read stack pointer to variable.
+ *
+ * Note that the compiler will push a stack frame (minimum 16 bytes)
+ * in the prelude of a C function that calls any other functions.
+ */
+#define SP(var) asm volatile ("mov %0, a1" : "=r" (var))
+
+/* Read the function return address to a variable.
+ *
+ * Depends on the containing function being simple enough that a0 is
+ * being used as a working register.
+ */
+#define RETADDR(var) asm volatile ("mov %0, a0" : "=r" (var))
+
 #define RSR(var, reg) asm volatile ("rsr %0, " #reg : "=r" (var));
 #define WSR(var, reg) asm volatile ("wsr %0, " #reg : : "r" (var));
 #define XSR(var, reg) asm volatile ("xsr %0, " #reg : "+r" (var));
diff --git a/core/newlib_syscalls.c b/core/newlib_syscalls.c
index b414481..0d4b95e 100644
--- a/core/newlib_syscalls.c
+++ b/core/newlib_syscalls.c
@@ -25,7 +25,7 @@ IRAM caddr_t _sbrk_r (struct _reent *r, int incr)
        if (heap_end + incr > stack_ptr)
        {
        _write (1, "_sbrk: Heap collided with stack\n", 32);
-       while(1) {}
+       abort();
        }
     */
     heap_end += incr;
diff --git a/examples/experiments/unaligned_load/unaligned_load.c b/examples/experiments/unaligned_load/unaligned_load.c
index 258b4d0..4244804 100644
--- a/examples/experiments/unaligned_load/unaligned_load.c
+++ b/examples/experiments/unaligned_load/unaligned_load.c
@@ -305,7 +305,7 @@ static void test_system_interaction()
     }
     uint32_t ticks = xTaskGetTickCount() - start;
     printf("Timer interaction test PASSED after %dms.\n", ticks*portTICK_RATE_MS);
-    while(1) {}
+    abort();
 }
 
 /* The following "sanity tests" are designed to try to execute every code path
diff --git a/examples/http_get_mbedtls/http_get_mbedtls.c b/examples/http_get_mbedtls/http_get_mbedtls.c
index 017f8fc..7aad0e3 100644
--- a/examples/http_get_mbedtls/http_get_mbedtls.c
+++ b/examples/http_get_mbedtls/http_get_mbedtls.c
@@ -115,7 +115,7 @@ void http_get_task(void *pvParameters)
                                     strlen(pers))) != 0)
     {
         printf(" failed\n  ! mbedtls_ctr_drbg_seed returned %d\n", ret);
-        while(1) {} /* todo: replace with abort() */
+        abort();
     }
 
     printf(" ok\n");
@@ -129,7 +129,7 @@ void http_get_task(void *pvParameters)
     if(ret < 0)
     {
         printf(" failed\n  !  mbedtls_x509_crt_parse returned -0x%x\n\n", -ret);
-        while(1) {} /* todo: replace with abort() */
+        abort();
     }
 
     printf(" ok (%d skipped)\n", ret);
@@ -138,7 +138,7 @@ void http_get_task(void *pvParameters)
     if((ret = mbedtls_ssl_set_hostname(&ssl, WEB_SERVER)) != 0)
     {
         printf(" failed\n  ! mbedtls_ssl_set_hostname returned %d\n\n", ret);
-        while(1) {} /* todo: replace with abort() */
+        abort();
     }
 
     /*
diff --git a/examples/tls_server/tls_server.c b/examples/tls_server/tls_server.c
index d253bc9..9030dc0 100644
--- a/examples/tls_server/tls_server.c
+++ b/examples/tls_server/tls_server.c
@@ -85,7 +85,7 @@ void tls_server_task(void *pvParameters)
                                     strlen(pers))) != 0)
     {
         printf(" failed\n  ! mbedtls_ctr_drbg_seed returned %d\n", ret);
-        while(1) {} /* todo: replace with abort() */
+        abort();
     }
 
     printf(" ok\n");
@@ -99,7 +99,7 @@ void tls_server_task(void *pvParameters)
     if(ret < 0)
     {
         printf(" failed\n  !  mbedtls_x509_crt_parse returned -0x%x\n\n", -ret);
-        while(1) {} /* todo: replace with abort() */
+        abort();
     }
 
     printf(" ok (%d skipped)\n", ret);
@@ -109,7 +109,7 @@ void tls_server_task(void *pvParameters)
     if(ret != 0)
     {
         printf(" failed\n ! mbedtls_pk_parse_key returned - 0x%x\n\n", -ret);
-        while(1) { } /*todo: replace with abort() */
+        abort();
     }
 
     printf(" ok\n");
@@ -134,7 +134,7 @@ void tls_server_task(void *pvParameters)
     if( ( ret = mbedtls_ssl_conf_own_cert( &conf, &srvcert, &pkey ) ) != 0 )
     {
         printf( " failed\n  ! mbedtls_ssl_conf_own_cert returned %d\n\n", ret );
-        while(1) { }
+        abort();
     }
 
     mbedtls_ssl_conf_rng(&conf, mbedtls_ctr_drbg_random, &ctr_drbg);
diff --git a/libc/xtensa-lx106-elf/lib/libc.a b/libc/xtensa-lx106-elf/lib/libc.a
index 6a256cd..a43b5ed 100644
Binary files a/libc/xtensa-lx106-elf/lib/libc.a and b/libc/xtensa-lx106-elf/lib/libc.a differ
diff --git a/libc/xtensa-lx106-elf/lib/libg.a b/libc/xtensa-lx106-elf/lib/libg.a
index 6a256cd..4d3d50a 100644
Binary files a/libc/xtensa-lx106-elf/lib/libg.a and b/libc/xtensa-lx106-elf/lib/libg.a differ
diff --git a/libc/xtensa-lx106-elf/lib/libm.a b/libc/xtensa-lx106-elf/lib/libm.a
index 7d96963..2e9a859 100644
Binary files a/libc/xtensa-lx106-elf/lib/libm.a and b/libc/xtensa-lx106-elf/lib/libm.a differ
diff --git a/lwip/include/arch/cc.h b/lwip/include/arch/cc.h
index a09ea0c..04dab26 100644
--- a/lwip/include/arch/cc.h
+++ b/lwip/include/arch/cc.h
@@ -94,7 +94,7 @@ typedef int sys_prot_t;
         _Pragma("GCC diagnostic pop")                   \
     } while(0)
 #define LWIP_PLATFORM_ASSERT(x) do { printf("Assertion \"%s\" failed at line %d in %s\n", \
-					    x, __LINE__, __FILE__); while(1) {} } while(0)
+                                            x, __LINE__, __FILE__); abort(); } while(0)
 
 #define LWIP_ERROR(message, expression, handler) do { if (!(expression)) { \
   printf("Assertion \"%s\" failed at line %d in %s\n", message, __LINE__, __FILE__); \
diff --git a/lwip/sys_arch.c b/lwip/sys_arch.c
index f625fc6..ad936e4 100644
--- a/lwip/sys_arch.c
+++ b/lwip/sys_arch.c
@@ -1,585 +1,592 @@
-/*
- * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice,
- *    this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- *    this list of conditions and the following disclaimer in the documentation
- *    and/or other materials provided with the distribution.
- * 3. The name of the author may not be used to endorse or promote products
- *    derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
- * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
- * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
- * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
- * OF SUCH DAMAGE.
- *
- * This file is part of the lwIP TCP/IP stack.
- *
- * Author: Adam Dunkels <adam@sics.se>
- *
- */
-
-//*****************************************************************************
-//
-// Include OS functionality.
-//
-//*****************************************************************************
-
-/* ------------------------ System architecture includes ----------------------------- */
-#include "arch/sys_arch.h"
-
-/* ------------------------ lwIP includes --------------------------------- */
-#include "lwip/opt.h"
-
-#include "lwip/err.h"
-#include "lwip/debug.h"
-#include "lwip/def.h"
-#include "lwip/sys.h"
-#include "lwip/mem.h"
-#include "lwip/stats.h"
-
-/* Very crude mechanism used to determine if the critical section handling
-functions are being called from an interrupt context or not.  This relies on
-the interrupt handler setting this variable manually. */
-portBASE_TYPE xInsideISR = pdFALSE;
-
-/*---------------------------------------------------------------------------*
- * Routine:  sys_mbox_new
- *---------------------------------------------------------------------------*
- * Description:
- *      Creates a new mailbox
- * Inputs:
- *      int size                -- Size of elements in the mailbox
- * Outputs:
- *      sys_mbox_t              -- Handle to new mailbox
- *---------------------------------------------------------------------------*/
-err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize )
-{
-err_t xReturn = ERR_MEM;
-
-	*pxMailBox = xQueueCreate( iSize, sizeof( void * ) );
-
-	if( *pxMailBox != NULL )
-	{
-		xReturn = ERR_OK;
-		SYS_STATS_INC_USED( mbox );
-	}
-
-	return xReturn;
-}
-
-
-/*---------------------------------------------------------------------------*
- * Routine:  sys_mbox_free
- *---------------------------------------------------------------------------*
- * Description:
- *      Deallocates a mailbox. If there are messages still present in the
- *      mailbox when the mailbox is deallocated, it is an indication of a
- *      programming error in lwIP and the developer should be notified.
- * Inputs:
- *      sys_mbox_t mbox         -- Handle of mailbox
- * Outputs:
- *      sys_mbox_t              -- Handle to new mailbox
- *---------------------------------------------------------------------------*/
-void sys_mbox_free( sys_mbox_t *pxMailBox )
-{
-unsigned long ulMessagesWaiting;
-
-	ulMessagesWaiting = uxQueueMessagesWaiting( *pxMailBox );
-	configASSERT( ( ulMessagesWaiting == 0 ) );
-
-	#if SYS_STATS
-	{
-		if( ulMessagesWaiting != 0UL )
-		{
-			SYS_STATS_INC( mbox.err );
-		}
-
-		SYS_STATS_DEC( mbox.used );
-	}
-	#endif /* SYS_STATS */
-
-	vQueueDelete( *pxMailBox );
-}
-
-/*---------------------------------------------------------------------------*
- * Routine:  sys_mbox_post
- *---------------------------------------------------------------------------*
- * Description:
- *      Post the "msg" to the mailbox.
- * Inputs:
- *      sys_mbox_t mbox         -- Handle of mailbox
- *      void *data              -- Pointer to data to post
- *---------------------------------------------------------------------------*/
-void sys_mbox_post( sys_mbox_t *pxMailBox, void *pxMessageToPost )
-{
-	while( xQueueSendToBack( *pxMailBox, &pxMessageToPost, portMAX_DELAY ) != pdTRUE );
-}
-
-/*---------------------------------------------------------------------------*
- * Routine:  sys_mbox_trypost
- *---------------------------------------------------------------------------*
- * Description:
- *      Try to post the "msg" to the mailbox.  Returns immediately with
- *      error if cannot.
- * Inputs:
- *      sys_mbox_t mbox         -- Handle of mailbox
- *      void *msg               -- Pointer to data to post
- * Outputs:
- *      err_t                   -- ERR_OK if message posted, else ERR_MEM
- *                                  if not.
- *---------------------------------------------------------------------------*/
-err_t sys_mbox_trypost( sys_mbox_t *pxMailBox, void *pxMessageToPost )
-{
-err_t xReturn;
-portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
-
-	if( xInsideISR != pdFALSE )
-	{
-		xReturn = xQueueSendFromISR( *pxMailBox, &pxMessageToPost, &xHigherPriorityTaskWoken );
-	}
-	else
-	{
-		xReturn = xQueueSend( *pxMailBox, &pxMessageToPost, ( portTickType ) 0 );
-	}
-
-	if( xReturn == pdPASS )
-	{
-		xReturn = ERR_OK;
-	}
-	else
-	{
-		/* The queue was already full. */
-		xReturn = ERR_MEM;
-		SYS_STATS_INC( mbox.err );
-	}
-
-	return xReturn;
-}
-
-/*---------------------------------------------------------------------------*
- * Routine:  sys_arch_mbox_fetch
- *---------------------------------------------------------------------------*
- * Description:
- *      Blocks the thread until a message arrives in the mailbox, but does
- *      not block the thread longer than "timeout" milliseconds (similar to
- *      the sys_arch_sem_wait() function). The "msg" argument is a result
- *      parameter that is set by the function (i.e., by doing "*msg =
- *      ptr"). The "msg" parameter maybe NULL to indicate that the message
- *      should be dropped.
- *
- *      The return values are the same as for the sys_arch_sem_wait() function:
- *      Number of milliseconds spent waiting or SYS_ARCH_TIMEOUT if there was a
- *      timeout.
- *
- *      Note that a function with a similar name, sys_mbox_fetch(), is
- *      implemented by lwIP.
- * Inputs:
- *      sys_mbox_t mbox         -- Handle of mailbox
- *      void **msg              -- Pointer to pointer to msg received
- *      u32_t timeout           -- Number of milliseconds until timeout
- * Outputs:
- *      u32_t                   -- SYS_ARCH_TIMEOUT if timeout, else number
- *                                  of milliseconds until received.
- *---------------------------------------------------------------------------*/
-u32_t sys_arch_mbox_fetch( sys_mbox_t *pxMailBox, void **ppvBuffer, u32_t ulTimeOut )
-{
-void *pvDummy;
-portTickType xStartTime, xEndTime, xElapsed;
-unsigned long ulReturn;
-
-	xStartTime = xTaskGetTickCount();
-
-	if( NULL == ppvBuffer )
-	{
-		ppvBuffer = &pvDummy;
-	}
-
-	if( ulTimeOut != 0UL )
-	{
-		configASSERT( xInsideISR == ( portBASE_TYPE ) 0 );
-
-		if( pdTRUE == xQueueReceive( *pxMailBox, &( *ppvBuffer ), ulTimeOut/ portTICK_RATE_MS ) )
-		{
-			xEndTime = xTaskGetTickCount();
-			xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS;
-
-			ulReturn = xElapsed;
-		}
-		else
-		{
-			/* Timed out. */
-			*ppvBuffer = NULL;
-			ulReturn = SYS_ARCH_TIMEOUT;
-		}
-	}
-	else
-	{
-		while( pdTRUE != xQueueReceive( *pxMailBox, &( *ppvBuffer ), portMAX_DELAY ) );
-		xEndTime = xTaskGetTickCount();
-		xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS;
-
-		if( xElapsed == 0UL )
-		{
-			xElapsed = 1UL;
-		}
-
-		ulReturn = xElapsed;
-	}
-
-	return ulReturn;
-}
-
-/*---------------------------------------------------------------------------*
- * Routine:  sys_arch_mbox_tryfetch
- *---------------------------------------------------------------------------*
- * Description:
- *      Similar to sys_arch_mbox_fetch, but if message is not ready
- *      immediately, we'll return with SYS_MBOX_EMPTY.  On success, 0 is
- *      returned.
- * Inputs:
- *      sys_mbox_t mbox         -- Handle of mailbox
- *      void **msg              -- Pointer to pointer to msg received
- * Outputs:
- *      u32_t                   -- SYS_MBOX_EMPTY if no messages.  Otherwise,
- *                                  return ERR_OK.
- *---------------------------------------------------------------------------*/
-u32_t sys_arch_mbox_tryfetch( sys_mbox_t *pxMailBox, void **ppvBuffer )
-{
-void *pvDummy;
-unsigned long ulReturn;
-long lResult;
-portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
-
-	if( ppvBuffer== NULL )
-	{
-		ppvBuffer = &pvDummy;
-	}
-
-	if( xInsideISR != pdFALSE )
-	{
-		lResult = xQueueReceiveFromISR( *pxMailBox, &( *ppvBuffer ), &xHigherPriorityTaskWoken );
-	}
-	else
-	{
-		lResult = xQueueReceive( *pxMailBox, &( *ppvBuffer ), 0UL );
-	}
-
-	if( lResult == pdPASS )
-	{
-		ulReturn = ERR_OK;
-	}
-	else
-	{
-		ulReturn = SYS_MBOX_EMPTY;
-	}
-
-	return ulReturn;
-}
-
-/*---------------------------------------------------------------------------*
- * Routine:  sys_sem_new
- *---------------------------------------------------------------------------*
- * Description:
- *      Creates and returns a new semaphore. The "ucCount" argument specifies
- *      the initial state of the semaphore.
- *      NOTE: Currently this routine only creates counts of 1 or 0
- * Inputs:
- *      sys_mbox_t mbox         -- Handle of mailbox
- *      u8_t ucCount              -- Initial ucCount of semaphore (1 or 0)
- * Outputs:
- *      sys_sem_t               -- Created semaphore or 0 if could not create.
- *---------------------------------------------------------------------------*/
-err_t sys_sem_new( sys_sem_t *pxSemaphore, u8_t ucCount )
-{
-err_t xReturn = ERR_MEM;
-
-	vSemaphoreCreateBinary( ( *pxSemaphore ) );
-
-	if( *pxSemaphore != NULL )
-	{
-		if( ucCount == 0U )
-		{
-			xSemaphoreTake( *pxSemaphore, 1UL );
-		}
-
-		xReturn = ERR_OK;
-		SYS_STATS_INC_USED( sem );
-	}
-	else
-	{
-		SYS_STATS_INC( sem.err );
-	}
-
-	return xReturn;
-}
-
-/*---------------------------------------------------------------------------*
- * Routine:  sys_arch_sem_wait
- *---------------------------------------------------------------------------*
- * Description:
- *      Blocks the thread while waiting for the semaphore to be
- *      signaled. If the "timeout" argument is non-zero, the thread should
- *      only be blocked for the specified time (measured in
- *      milliseconds).
- *
- *      If the timeout argument is non-zero, the return value is the number of
- *      milliseconds spent waiting for the semaphore to be signaled. If the
- *      semaphore wasn't signaled within the specified time, the return value is
- *      SYS_ARCH_TIMEOUT. If the thread didn't have to wait for the semaphore
- *      (i.e., it was already signaled), the function may return zero.
- *
- *      Notice that lwIP implements a function with a similar name,
- *      sys_sem_wait(), that uses the sys_arch_sem_wait() function.
- * Inputs:
- *      sys_sem_t sem           -- Semaphore to wait on
- *      u32_t timeout           -- Number of milliseconds until timeout
- * Outputs:
- *      u32_t                   -- Time elapsed or SYS_ARCH_TIMEOUT.
- *---------------------------------------------------------------------------*/
-u32_t sys_arch_sem_wait( sys_sem_t *pxSemaphore, u32_t ulTimeout )
-{
-portTickType xStartTime, xEndTime, xElapsed;
-unsigned long ulReturn;
-
-	xStartTime = xTaskGetTickCount();
-
-	if( ulTimeout != 0UL )
-	{
-		if( xSemaphoreTake( *pxSemaphore, ulTimeout / portTICK_RATE_MS ) == pdTRUE )
-		{
-			xEndTime = xTaskGetTickCount();
-			xElapsed = (xEndTime - xStartTime) * portTICK_RATE_MS;
-			ulReturn = xElapsed;
-		}
-		else
-		{
-			ulReturn = SYS_ARCH_TIMEOUT;
-		}
-	}
-	else
-	{
-		while( xSemaphoreTake( *pxSemaphore, portMAX_DELAY ) != pdTRUE );
-		xEndTime = xTaskGetTickCount();
-		xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS;
-
-		if( xElapsed == 0UL )
-		{
-			xElapsed = 1UL;
-		}
-
-		ulReturn = xElapsed;
-	}
-
-	return ulReturn;
-}
-
-/** Create a new mutex
- * @param mutex pointer to the mutex to create
- * @return a new mutex */
-err_t sys_mutex_new( sys_mutex_t *pxMutex )
-{
-err_t xReturn = ERR_MEM;
-
-	*pxMutex = xSemaphoreCreateMutex();
-
-	if( *pxMutex != NULL ) 
-	{
-		xReturn = ERR_OK;
-		SYS_STATS_INC_USED( mutex );
-	} 
-	else 
-	{
-		SYS_STATS_INC( mutex.err );
-	}
-	
-	return xReturn;
-}
-
-/** Lock a mutex
- * @param mutex the mutex to lock */
-void sys_mutex_lock( sys_mutex_t *pxMutex )
-{
-	while( xSemaphoreTake( *pxMutex, portMAX_DELAY ) != pdPASS );
-}
-
-/** Unlock a mutex
- * @param mutex the mutex to unlock */
-void sys_mutex_unlock(sys_mutex_t *pxMutex )
-{
-	xSemaphoreGive( *pxMutex );
-}
-
-
-/** Delete a semaphore
- * @param mutex the mutex to delete */
-void sys_mutex_free( sys_mutex_t *pxMutex )
-{
-	SYS_STATS_DEC( mutex.used );
-	vQueueDelete( *pxMutex );
-}
-
-
-/*---------------------------------------------------------------------------*
- * Routine:  sys_sem_signal
- *---------------------------------------------------------------------------*
- * Description:
- *      Signals (releases) a semaphore
- * Inputs:
- *      sys_sem_t sem           -- Semaphore to signal
- *---------------------------------------------------------------------------*/
-void sys_sem_signal( sys_sem_t *pxSemaphore )
-{
-portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
-
-	if( xInsideISR != pdFALSE )
-	{
-		xSemaphoreGiveFromISR( *pxSemaphore, &xHigherPriorityTaskWoken );
-	}
-	else
-	{
-		xSemaphoreGive( *pxSemaphore );
-	}
-}
-
-/*---------------------------------------------------------------------------*
- * Routine:  sys_sem_free
- *---------------------------------------------------------------------------*
- * Description:
- *      Deallocates a semaphore
- * Inputs:
- *      sys_sem_t sem           -- Semaphore to free
- *---------------------------------------------------------------------------*/
-void sys_sem_free( sys_sem_t *pxSemaphore )
-{
-	SYS_STATS_DEC(sem.used);
-	vQueueDelete( *pxSemaphore );
-}
-
-/*---------------------------------------------------------------------------*
- * Routine:  sys_init
- *---------------------------------------------------------------------------*
- * Description:
- *      Initialize sys arch
- *---------------------------------------------------------------------------*/
-void sys_init(void)
-{
-}
-
-u32_t sys_now(void)
-{
-	return xTaskGetTickCount();
-}
-
-/*---------------------------------------------------------------------------*
- * Routine:  sys_thread_new
- *---------------------------------------------------------------------------*
- * Description:
- *      Starts a new thread with priority "prio" that will begin its
- *      execution in the function "thread()". The "arg" argument will be
- *      passed as an argument to the thread() function. The id of the new
- *      thread is returned. Both the id and the priority are system
- *      dependent.
- * Inputs:
- *      char *name              -- Name of thread
- *      void (* thread)(void *arg) -- Pointer to function to run.
- *      void *arg               -- Argument passed into function
- *      int stacksize           -- Required stack amount in bytes
- *      int prio                -- Thread priority
- * Outputs:
- *      sys_thread_t            -- Pointer to per-thread timeouts.
- *---------------------------------------------------------------------------*/
-sys_thread_t sys_thread_new( const char *pcName, void( *pxThread )( void *pvParameters ), void *pvArg, int iStackSize, int iPriority )
-{
-xTaskHandle xCreatedTask;
-portBASE_TYPE xResult;
-sys_thread_t xReturn;
-
-	xResult = xTaskCreate( pxThread, ( signed char * ) pcName, iStackSize, pvArg, iPriority, &xCreatedTask );
-
-	if( xResult == pdPASS )
-	{
-		xReturn = xCreatedTask;
-	}
-	else
-	{
-		xReturn = NULL;
-	}
-
-	return xReturn;
-}
-
-/*---------------------------------------------------------------------------*
- * Routine:  sys_arch_protect
- *---------------------------------------------------------------------------*
- * Description:
- *      This optional function does a "fast" critical region protection and
- *      returns the previous protection level. This function is only called
- *      during very short critical regions. An embedded system which supports
- *      ISR-based drivers might want to implement this function by disabling
- *      interrupts. Task-based systems might want to implement this by using
- *      a mutex or disabling tasking. This function should support recursive
- *      calls from the same task or interrupt. In other words,
- *      sys_arch_protect() could be called while already protected. In
- *      that case the return value indicates that it is already protected.
- *
- *      sys_arch_protect() is only required if your port is supporting an
- *      operating system.
- * Outputs:
- *      sys_prot_t              -- Previous protection level (not used here)
- *---------------------------------------------------------------------------*/
-sys_prot_t sys_arch_protect( void )
-{
-	if( xInsideISR == pdFALSE )
-	{
-		taskENTER_CRITICAL();
-	}
-	return ( sys_prot_t ) 1;
-}
-
-/*---------------------------------------------------------------------------*
- * Routine:  sys_arch_unprotect
- *---------------------------------------------------------------------------*
- * Description:
- *      This optional function does a "fast" set of critical region
- *      protection to the value specified by pval. See the documentation for
- *      sys_arch_protect() for more information. This function is only
- *      required if your port is supporting an operating system.
- * Inputs:
- *      sys_prot_t              -- Previous protection level (not used here)
- *---------------------------------------------------------------------------*/
-void sys_arch_unprotect( sys_prot_t xValue )
-{
-	(void) xValue;
-	if( xInsideISR == pdFALSE )
-	{
-		taskEXIT_CRITICAL();
-	}
-}
-
-/*
- * Prints an assertion messages and aborts execution.
- */
-void sys_assert( const char *pcMessage )
-{
-	(void) pcMessage;
-
-	for (;;)
-	{
-	}
-}
-/*-------------------------------------------------------------------------*
- * End of File:  sys_arch.c
- *-------------------------------------------------------------------------*/
-
+/*
+ * Copyright (c) 2001-2003 Swedish Institute of Computer Science.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ *    this list of conditions and the following disclaimer in the documentation
+ *    and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+ * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+ * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ *
+ * This file is part of the lwIP TCP/IP stack.
+ *
+ * Author: Adam Dunkels <adam@sics.se>
+ *
+ */
+
+//*****************************************************************************
+//
+// Include OS functionality.
+//
+//*****************************************************************************
+
+/* ------------------------ System architecture includes ----------------------------- */
+#include "arch/sys_arch.h"
+
+/* ------------------------ lwIP includes --------------------------------- */
+#include "lwip/opt.h"
+
+#include "lwip/err.h"
+#include "lwip/debug.h"
+#include "lwip/def.h"
+#include "lwip/sys.h"
+#include "lwip/mem.h"
+#include "lwip/stats.h"
+
+extern bool esp_in_isr;
+
+/* Based on the default xInsideISR mechanism to determine
+   if an ISR is running.
+
+   Doesn't support the possibility that LWIP functions are called from the NMI
+   handler (none are called from NMI when using current/SDK implementation.)
+*/
+static inline bool is_inside_isr()
+{
+    return esp_in_isr;
+}
+
+/*---------------------------------------------------------------------------*
+ * Routine:  sys_mbox_new
+ *---------------------------------------------------------------------------*
+ * Description:
+ *      Creates a new mailbox
+ * Inputs:
+ *      int size                -- Size of elements in the mailbox
+ * Outputs:
+ *      sys_mbox_t              -- Handle to new mailbox
+ *---------------------------------------------------------------------------*/
+err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize )
+{
+    err_t xReturn = ERR_MEM;
+
+    *pxMailBox = xQueueCreate( iSize, sizeof( void * ) );
+
+    if( *pxMailBox != NULL )
+    {
+        xReturn = ERR_OK;
+        SYS_STATS_INC_USED( mbox );
+    }
+
+    return xReturn;
+}
+
+
+/*---------------------------------------------------------------------------*
+ * Routine:  sys_mbox_free
+ *---------------------------------------------------------------------------*
+ * Description:
+ *      Deallocates a mailbox. If there are messages still present in the
+ *      mailbox when the mailbox is deallocated, it is an indication of a
+ *      programming error in lwIP and the developer should be notified.
+ * Inputs:
+ *      sys_mbox_t mbox         -- Handle of mailbox
+ * Outputs:
+ *      sys_mbox_t              -- Handle to new mailbox
+ *---------------------------------------------------------------------------*/
+void sys_mbox_free( sys_mbox_t *pxMailBox )
+{
+unsigned long ulMessagesWaiting;
+
+    ulMessagesWaiting = uxQueueMessagesWaiting( *pxMailBox );
+    configASSERT( ( ulMessagesWaiting == 0 ) );
+
+    #if SYS_STATS
+    {
+        if( ulMessagesWaiting != 0UL )
+        {
+            SYS_STATS_INC( mbox.err );
+        }
+
+        SYS_STATS_DEC( mbox.used );
+    }
+    #endif /* SYS_STATS */
+
+    vQueueDelete( *pxMailBox );
+}
+
+/*---------------------------------------------------------------------------*
+ * Routine:  sys_mbox_post
+ *---------------------------------------------------------------------------*
+ * Description:
+ *      Post the "msg" to the mailbox.
+ * Inputs:
+ *      sys_mbox_t mbox         -- Handle of mailbox
+ *      void *data              -- Pointer to data to post
+ *---------------------------------------------------------------------------*/
+void sys_mbox_post( sys_mbox_t *pxMailBox, void *pxMessageToPost )
+{
+    while( xQueueSendToBack( *pxMailBox, &pxMessageToPost, portMAX_DELAY ) != pdTRUE );
+}
+
+/*---------------------------------------------------------------------------*
+ * Routine:  sys_mbox_trypost
+ *---------------------------------------------------------------------------*
+ * Description:
+ *      Try to post the "msg" to the mailbox.  Returns immediately with
+ *      error if cannot.
+ * Inputs:
+ *      sys_mbox_t mbox         -- Handle of mailbox
+ *      void *msg               -- Pointer to data to post
+ * Outputs:
+ *      err_t                   -- ERR_OK if message posted, else ERR_MEM
+ *                                  if not.
+ *---------------------------------------------------------------------------*/
+err_t sys_mbox_trypost( sys_mbox_t *pxMailBox, void *pxMessageToPost )
+{
+err_t xReturn;
+portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
+
+    if( is_inside_isr() != pdFALSE )
+    {
+        xReturn = xQueueSendFromISR( *pxMailBox, &pxMessageToPost, &xHigherPriorityTaskWoken );
+    }
+    else
+    {
+        xReturn = xQueueSend( *pxMailBox, &pxMessageToPost, ( portTickType ) 0 );
+    }
+
+    if( xReturn == pdPASS )
+    {
+        xReturn = ERR_OK;
+    }
+    else
+    {
+        /* The queue was already full. */
+        xReturn = ERR_MEM;
+        SYS_STATS_INC( mbox.err );
+    }
+
+    return xReturn;
+}
+
+/*---------------------------------------------------------------------------*
+ * Routine:  sys_arch_mbox_fetch
+ *---------------------------------------------------------------------------*
+ * Description:
+ *      Blocks the thread until a message arrives in the mailbox, but does
+ *      not block the thread longer than "timeout" milliseconds (similar to
+ *      the sys_arch_sem_wait() function). The "msg" argument is a result
+ *      parameter that is set by the function (i.e., by doing "*msg =
+ *      ptr"). The "msg" parameter maybe NULL to indicate that the message
+ *      should be dropped.
+ *
+ *      The return values are the same as for the sys_arch_sem_wait() function:
+ *      Number of milliseconds spent waiting or SYS_ARCH_TIMEOUT if there was a
+ *      timeout.
+ *
+ *      Note that a function with a similar name, sys_mbox_fetch(), is
+ *      implemented by lwIP.
+ * Inputs:
+ *      sys_mbox_t mbox         -- Handle of mailbox
+ *      void **msg              -- Pointer to pointer to msg received
+ *      u32_t timeout           -- Number of milliseconds until timeout
+ * Outputs:
+ *      u32_t                   -- SYS_ARCH_TIMEOUT if timeout, else number
+ *                                  of milliseconds until received.
+ *---------------------------------------------------------------------------*/
+u32_t sys_arch_mbox_fetch( sys_mbox_t *pxMailBox, void **ppvBuffer, u32_t ulTimeOut )
+{
+void *pvDummy;
+portTickType xStartTime, xEndTime, xElapsed;
+unsigned long ulReturn;
+
+    xStartTime = xTaskGetTickCount();
+
+    if( NULL == ppvBuffer )
+    {
+        ppvBuffer = &pvDummy;
+    }
+
+    if( ulTimeOut != 0UL )
+    {
+        configASSERT( is_inside_isr() == ( portBASE_TYPE ) 0 );
+
+        if( pdTRUE == xQueueReceive( *pxMailBox, &( *ppvBuffer ), ulTimeOut/ portTICK_RATE_MS ) )
+        {
+            xEndTime = xTaskGetTickCount();
+            xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS;
+
+            ulReturn = xElapsed;
+        }
+        else
+        {
+            /* Timed out. */
+            *ppvBuffer = NULL;
+            ulReturn = SYS_ARCH_TIMEOUT;
+        }
+    }
+    else
+    {
+        while( pdTRUE != xQueueReceive( *pxMailBox, &( *ppvBuffer ), portMAX_DELAY ) );
+        xEndTime = xTaskGetTickCount();
+        xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS;
+
+        if( xElapsed == 0UL )
+        {
+            xElapsed = 1UL;
+        }
+
+        ulReturn = xElapsed;
+    }
+
+    return ulReturn;
+}
+
+/*---------------------------------------------------------------------------*
+ * Routine:  sys_arch_mbox_tryfetch
+ *---------------------------------------------------------------------------*
+ * Description:
+ *      Similar to sys_arch_mbox_fetch, but if message is not ready
+ *      immediately, we'll return with SYS_MBOX_EMPTY.  On success, 0 is
+ *      returned.
+ * Inputs:
+ *      sys_mbox_t mbox         -- Handle of mailbox
+ *      void **msg              -- Pointer to pointer to msg received
+ * Outputs:
+ *      u32_t                   -- SYS_MBOX_EMPTY if no messages.  Otherwise,
+ *                                  return ERR_OK.
+ *---------------------------------------------------------------------------*/
+u32_t sys_arch_mbox_tryfetch( sys_mbox_t *pxMailBox, void **ppvBuffer )
+{
+void *pvDummy;
+unsigned long ulReturn;
+long lResult;
+portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
+
+    if( ppvBuffer== NULL )
+    {
+        ppvBuffer = &pvDummy;
+    }
+
+    if( is_inside_isr() != pdFALSE )
+    {
+        lResult = xQueueReceiveFromISR( *pxMailBox, &( *ppvBuffer ), &xHigherPriorityTaskWoken );
+    }
+    else
+    {
+        lResult = xQueueReceive( *pxMailBox, &( *ppvBuffer ), 0UL );
+    }
+
+    if( lResult == pdPASS )
+    {
+        ulReturn = ERR_OK;
+    }
+    else
+    {
+        ulReturn = SYS_MBOX_EMPTY;
+    }
+
+    return ulReturn;
+}
+
+/*---------------------------------------------------------------------------*
+ * Routine:  sys_sem_new
+ *---------------------------------------------------------------------------*
+ * Description:
+ *      Creates and returns a new semaphore. The "ucCount" argument specifies
+ *      the initial state of the semaphore.
+ *      NOTE: Currently this routine only creates counts of 1 or 0
+ * Inputs:
+ *      sys_mbox_t mbox         -- Handle of mailbox
+ *      u8_t ucCount              -- Initial ucCount of semaphore (1 or 0)
+ * Outputs:
+ *      sys_sem_t               -- Created semaphore or 0 if could not create.
+ *---------------------------------------------------------------------------*/
+err_t sys_sem_new( sys_sem_t *pxSemaphore, u8_t ucCount )
+{
+err_t xReturn = ERR_MEM;
+
+    vSemaphoreCreateBinary( ( *pxSemaphore ) );
+
+    if( *pxSemaphore != NULL )
+    {
+        if( ucCount == 0U )
+        {
+            xSemaphoreTake( *pxSemaphore, 1UL );
+        }
+
+        xReturn = ERR_OK;
+        SYS_STATS_INC_USED( sem );
+    }
+    else
+    {
+        SYS_STATS_INC( sem.err );
+    }
+
+    return xReturn;
+}
+
+/*---------------------------------------------------------------------------*
+ * Routine:  sys_arch_sem_wait
+ *---------------------------------------------------------------------------*
+ * Description:
+ *      Blocks the thread while waiting for the semaphore to be
+ *      signaled. If the "timeout" argument is non-zero, the thread should
+ *      only be blocked for the specified time (measured in
+ *      milliseconds).
+ *
+ *      If the timeout argument is non-zero, the return value is the number of
+ *      milliseconds spent waiting for the semaphore to be signaled. If the
+ *      semaphore wasn't signaled within the specified time, the return value is
+ *      SYS_ARCH_TIMEOUT. If the thread didn't have to wait for the semaphore
+ *      (i.e., it was already signaled), the function may return zero.
+ *
+ *      Notice that lwIP implements a function with a similar name,
+ *      sys_sem_wait(), that uses the sys_arch_sem_wait() function.
+ * Inputs:
+ *      sys_sem_t sem           -- Semaphore to wait on
+ *      u32_t timeout           -- Number of milliseconds until timeout
+ * Outputs:
+ *      u32_t                   -- Time elapsed or SYS_ARCH_TIMEOUT.
+ *---------------------------------------------------------------------------*/
+u32_t sys_arch_sem_wait( sys_sem_t *pxSemaphore, u32_t ulTimeout )
+{
+portTickType xStartTime, xEndTime, xElapsed;
+unsigned long ulReturn;
+
+    xStartTime = xTaskGetTickCount();
+
+    if( ulTimeout != 0UL )
+    {
+        if( xSemaphoreTake( *pxSemaphore, ulTimeout / portTICK_RATE_MS ) == pdTRUE )
+        {
+            xEndTime = xTaskGetTickCount();
+            xElapsed = (xEndTime - xStartTime) * portTICK_RATE_MS;
+            ulReturn = xElapsed;
+        }
+        else
+        {
+            ulReturn = SYS_ARCH_TIMEOUT;
+        }
+    }
+    else
+    {
+        while( xSemaphoreTake( *pxSemaphore, portMAX_DELAY ) != pdTRUE );
+        xEndTime = xTaskGetTickCount();
+        xElapsed = ( xEndTime - xStartTime ) * portTICK_RATE_MS;
+
+        if( xElapsed == 0UL )
+        {
+            xElapsed = 1UL;
+        }
+
+        ulReturn = xElapsed;
+    }
+
+    return ulReturn;
+}
+
+/** Create a new mutex
+ * @param mutex pointer to the mutex to create
+ * @return a new mutex */
+err_t sys_mutex_new( sys_mutex_t *pxMutex )
+{
+err_t xReturn = ERR_MEM;
+
+    *pxMutex = xSemaphoreCreateMutex();
+
+    if( *pxMutex != NULL )
+    {
+        xReturn = ERR_OK;
+        SYS_STATS_INC_USED( mutex );
+    }
+    else
+    {
+        SYS_STATS_INC( mutex.err );
+    }
+
+    return xReturn;
+}
+
+/** Lock a mutex
+ * @param mutex the mutex to lock */
+void sys_mutex_lock( sys_mutex_t *pxMutex )
+{
+    while( xSemaphoreTake( *pxMutex, portMAX_DELAY ) != pdPASS );
+}
+
+/** Unlock a mutex
+ * @param mutex the mutex to unlock */
+void sys_mutex_unlock(sys_mutex_t *pxMutex )
+{
+    xSemaphoreGive( *pxMutex );
+}
+
+
+/** Delete a semaphore
+ * @param mutex the mutex to delete */
+void sys_mutex_free( sys_mutex_t *pxMutex )
+{
+    SYS_STATS_DEC( mutex.used );
+    vQueueDelete( *pxMutex );
+}
+
+
+/*---------------------------------------------------------------------------*
+ * Routine:  sys_sem_signal
+ *---------------------------------------------------------------------------*
+ * Description:
+ *      Signals (releases) a semaphore
+ * Inputs:
+ *      sys_sem_t sem           -- Semaphore to signal
+ *---------------------------------------------------------------------------*/
+void sys_sem_signal( sys_sem_t *pxSemaphore )
+{
+portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
+
+    if( is_inside_isr() != pdFALSE )
+    {
+        xSemaphoreGiveFromISR( *pxSemaphore, &xHigherPriorityTaskWoken );
+    }
+    else
+    {
+        xSemaphoreGive( *pxSemaphore );
+    }
+}
+
+/*---------------------------------------------------------------------------*
+ * Routine:  sys_sem_free
+ *---------------------------------------------------------------------------*
+ * Description:
+ *      Deallocates a semaphore
+ * Inputs:
+ *      sys_sem_t sem           -- Semaphore to free
+ *---------------------------------------------------------------------------*/
+void sys_sem_free( sys_sem_t *pxSemaphore )
+{
+    SYS_STATS_DEC(sem.used);
+    vQueueDelete( *pxSemaphore );
+}
+
+/*---------------------------------------------------------------------------*
+ * Routine:  sys_init
+ *---------------------------------------------------------------------------*
+ * Description:
+ *      Initialize sys arch
+ *---------------------------------------------------------------------------*/
+void sys_init(void)
+{
+}
+
+u32_t sys_now(void)
+{
+    return xTaskGetTickCount();
+}
+
+/*---------------------------------------------------------------------------*
+ * Routine:  sys_thread_new
+ *---------------------------------------------------------------------------*
+ * Description:
+ *      Starts a new thread with priority "prio" that will begin its
+ *      execution in the function "thread()". The "arg" argument will be
+ *      passed as an argument to the thread() function. The id of the new
+ *      thread is returned. Both the id and the priority are system
+ *      dependent.
+ * Inputs:
+ *      char *name              -- Name of thread
+ *      void (* thread)(void *arg) -- Pointer to function to run.
+ *      void *arg               -- Argument passed into function
+ *      int stacksize           -- Required stack amount in bytes
+ *      int prio                -- Thread priority
+ * Outputs:
+ *      sys_thread_t            -- Pointer to per-thread timeouts.
+ *---------------------------------------------------------------------------*/
+sys_thread_t sys_thread_new( const char *pcName, void( *pxThread )( void *pvParameters ), void *pvArg, int iStackSize, int iPriority )
+{
+xTaskHandle xCreatedTask;
+portBASE_TYPE xResult;
+sys_thread_t xReturn;
+
+    xResult = xTaskCreate( pxThread, ( signed char * ) pcName, iStackSize, pvArg, iPriority, &xCreatedTask );
+
+    if( xResult == pdPASS )
+    {
+        xReturn = xCreatedTask;
+    }
+    else
+    {
+        xReturn = NULL;
+    }
+
+    return xReturn;
+}
+
+/*---------------------------------------------------------------------------*
+ * Routine:  sys_arch_protect
+ *---------------------------------------------------------------------------*
+ * Description:
+ *      This optional function does a "fast" critical region protection and
+ *      returns the previous protection level. This function is only called
+ *      during very short critical regions. An embedded system which supports
+ *      ISR-based drivers might want to implement this function by disabling
+ *      interrupts. Task-based systems might want to implement this by using
+ *      a mutex or disabling tasking. This function should support recursive
+ *      calls from the same task or interrupt. In other words,
+ *      sys_arch_protect() could be called while already protected. In
+ *      that case the return value indicates that it is already protected.
+ *
+ *      sys_arch_protect() is only required if your port is supporting an
+ *      operating system.
+ * Outputs:
+ *      sys_prot_t              -- Previous protection level (not used here)
+ *---------------------------------------------------------------------------*/
+sys_prot_t sys_arch_protect( void )
+{
+    if( is_inside_isr() == pdFALSE )
+    {
+        taskENTER_CRITICAL();
+    }
+    return ( sys_prot_t ) 1;
+}
+
+/*---------------------------------------------------------------------------*
+ * Routine:  sys_arch_unprotect
+ *---------------------------------------------------------------------------*
+ * Description:
+ *      This optional function does a "fast" set of critical region
+ *      protection to the value specified by pval. See the documentation for
+ *      sys_arch_protect() for more information. This function is only
+ *      required if your port is supporting an operating system.
+ * Inputs:
+ *      sys_prot_t              -- Previous protection level (not used here)
+ *---------------------------------------------------------------------------*/
+void sys_arch_unprotect( sys_prot_t xValue )
+{
+    (void) xValue;
+    if( is_inside_isr() == pdFALSE )
+    {
+        taskEXIT_CRITICAL();
+    }
+}
+
+/*
+ * Prints an assertion messages and aborts execution.
+ */
+void sys_assert( const char *pcMessage )
+{
+    (void) pcMessage;
+
+    for (;;)
+    {
+    }
+}
+/*-------------------------------------------------------------------------*
+ * End of File:  sys_arch.c
+ *-------------------------------------------------------------------------*/
diff --git a/utils/filteroutput.py b/utils/filteroutput.py
new file mode 100755
index 0000000..5e9b1cf
--- /dev/null
+++ b/utils/filteroutput.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+#
+# A thin Python wrapper around addr2line, can monitor esp-open-rtos
+# output and uses gdb to convert any suitable looking hex numbers
+# found in the output into function and line numbers.
+#
+# Works with a serial port if the --port option is supplied.
+# Otherwise waits for input on stdin.
+#
+import serial
+import argparse
+import re
+import os
+import os.path
+import subprocess
+import termios
+import sys
+import time
+
+# Try looking up anything in the executable address space
+RE_EXECADDR = r"(0x)?40([0-9]|[a-z]){6}"
+
+def find_elf_file():
+    out_files = []
+    for top,_,files in os.walk('.', followlinks=False):
+        for f in files:
+            if f.endswith(".out"):
+                out_files.append(os.path.join(top,f))
+    if len(out_files) == 1:
+        return out_files[0]
+    elif len(out_files) > 1:
+        print("Found multiple .out files: %s. Please specify one with the --elf option." % out_files)
+    else:
+        print("No .out file found under current directory. Please specify one with the --elf option.")
+    sys.exit(1)
+
+def main():
+    parser = argparse.ArgumentParser(description='esp-open-rtos output filter tool', prog='filteroutput')
+    parser.add_argument(
+        '--elf', '-e',
+        help="ELF file (*.out file) to load symbols from (if not supplied, will search for one)"),
+    parser.add_argument(
+        '--port', '-p',
+        help='Serial port to monitor (will monitor stdin if None)',
+        default=None)
+    parser.add_argument(
+        '--baud', '-b',
+        help='Baud rate for serial port',
+        type=int,
+        default=74880)
+    parser.add_argument(
+        '--reset-on-connect', '-r',
+        help='Reset ESP8266 (via DTR) on serial connect. (Linux resets even if not set, except when using NodeMCU-style auto-reset circuit.)',
+        action='store_true')
+
+    args = parser.parse_args()
+
+    if args.elf is None:
+        args.elf = find_elf_file()
+    elif not os.path.exists(args.elf):
+        print("ELF file '%s' not found" % args.elf)
+        sys.exit(1)
+
+    if args.port is not None:
+        print("Opening %s at %dbps..." % (args.port, args.baud))
+        port = serial.Serial(args.port, baudrate=args.baud)
+        if args.reset_on_connect:
+            print("Resetting...")
+            port.setDTR(False)
+            time.sleep(0.1)
+            port.setDTR(True)
+
+    else:
+        print("Reading from stdin...")
+        port = sys.stdin
+        # disable echo
+	try:
+            old_attr = termios.tcgetattr(sys.stdin.fileno())
+	    attr = termios.tcgetattr(sys.stdin.fileno())
+	    attr[3] = attr[3] & ~termios.ECHO
+	    termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, attr)
+	except termios.error:
+	     pass
+
+    try:
+        while True:
+            line = port.readline()
+            if line == '':
+                break
+            print(line.strip())
+            for match in re.finditer(RE_EXECADDR, line, re.IGNORECASE):
+                addr = match.group(0)
+                if not addr.startswith("0x"):
+                    addr = "0x"+addr
+                    # keeping addr2line and feeding it addresses on stdin didn't seem to work smoothly
+                addr2line = subprocess.check_output(["xtensa-lx106-elf-addr2line","-pfia","-e","%s" % args.elf, addr], cwd=".").strip()
+                if not addr2line.endswith(": ?? ??:0"):
+                    print("\n%s\n" % addr2line.strip())
+    finally:
+        if args.port is None:
+            # restore echo
+            termios.tcsetattr(sys.stdin.fileno(), termios.TCSADRAIN, old_attr)
+
+if __name__ == "__main__":
+    main()