After hours of debugging build scripts, compiling gcc with -g, recompiling it with -O0 -g3, debugging gcc with gdb and analyzing build logs I’ve found the problem.
When platformio (actually scons) run arm-none-eabi-gcc.exe, it pass include parameters via @longcmd-xxx.txt file (to get around 32kb char command line limit in Windows), but gcc for some reason invokes cc1.exe without command-line file - just with process args. For nrf52 with PIO_FRAMEWORK_MBED_RTOS_PRESENT defined there are lot of defines and includes (more than for stm32, about 35kb vs 25kb), so executing cc1.exe leads to arm-none-eabi-gcc: error: CreateProcess: No such file or directory .
The actual command line for cc1 (or cc1plus in case of g++) can be obtained when adding -v to build_flags and running pio run -v.
As a workaround for now, I use .mbedignore files to minimize gcc commands (and build time). One in packages\framework-mbed\components\802.15.4_RF\ with * in it, and another in packages\framework-mbed\features\ with the following content: