pwm_init utilizes a non-maskable interrupt (NMI) handler. ESP-iot-sdk contains some NMI handler code in libmain.a. The interrupt handler (_NMILevelVector) performs a 32-bit load of a value called _Pri_3_HandlerAddress. The symbol isn't marked as 4-byte aligned. Therefore, the linker is free to place it on a non-aligned address, like here for example:
3ffe8000 A _data_start
3ffe8005 D timer2_ms_flag
3ffe8006 D dhcps_flag
3ffe8007 D dhcpc_flag
3ffe8009 D reconnect_internal
3ffe800a D _Pri_3_HandlerAddress
3ffe8010 D default_hostname
Unaligned memory accesses cause an exception, and apparently hang the processor when triggered inside an NMI handler.
The same issue can occur when using the NMI source with the hw_timer.c code from the SDK.
My guess is that ESP should fix their sdk to mark the symbol as aligned. In the meantime, as a simple workaround, the symbol can be placed manually in the linker script.
After the line:
_data_start = ABSOLUTE(.);
_Pri_3_HandlerAddress = ABSOLUTE(.);
. = ABSOLUTE(4);
The reason why pwm_init seems to work with esphttpd is that esphttpd's makefile puts all the code inside a static library (.a). This in turn influences the linking order and memory alignment. With esphttpd, the linker luckily places the _Pri_3_blahblah symbol at a 4-byte aligned address.