Semihosting is a mechanism that allows code running on an embedded target (or simulator) to make host-machine I/O calls -- such as file reads, writes, and console output -- by trapping into a debug agent rather than implementing those services on the target itself. It is primarily used during development to get printf-style output and file access without dedicating target peripherals or writing custom drivers for them.
In practice
On Arm targets, semihosting works by executing a special trap instruction with a defined register convention; on Thumb/Thumb-2 cores (Cortex-M, Cortex-A in Thumb state) this is BKPT 0xAB, though the exact encoding differs by architecture state and core family. The connected debugger (via JTAG, SWD, or other debug transport -- using tools such as OpenOCD, J-Link, or pyOCD) intercepts the halt, services the host-side I/O request, places the return value in R0, and resumes the core. The C library retargets calls like printf, fopen, and fread through this channel. Arm's CMSIS and several vendor SDKs (STM32CubeIDE, Keil MDK, IAR Embedded Workbench) provide semihosting support, though the degree of out-of-the-box stub integration and required linker configuration varies by environment.
The most common use case is getting printf output during early bringup before a UART or RTT channel is available. It also appears heavily in simulator-based development workflows -- QEMU, for example, supports Arm semihosting for many of its target machines when semihosting mode is enabled, which makes it practical to run and test firmware on a host PC without physical hardware. This is relevant to CI pipelines for embedded projects, as described in blog posts like "Simulating Your Embedded Project on Your Computer (Part 2)" and "Continuous Integration for Embedded Systems."
The key pitfall is leaving semihosting calls in production code. If the debug probe is absent and the target executes the BKPT instruction, execution halts or faults (depending on CoreDebug configuration and whether a debugger is attached). Some projects accidentally ship semihosting-enabled libraries because the linker pulled in a semihosting-capable C runtime. The fix is to use a "retargeted" or "nosys" stub library -- e.g., linking against --specs=nosys.specs in GCC Arm Embedded -- which replaces semihosting syscalls with no-op stubs. Full UART-backed retargeting requires additional custom code (typically a syscalls.c providing your own _write, _read, etc.) on top of that.
Performance is another consideration: each semihosted call crosses the debug interface and involves the host OS, making it orders of magnitude slower than a local UART. Printing inside a tight loop or inside an ISR via semihosting will stall the target noticeably and can cause timing-sensitive code to behave differently than in production.
Frequently asked
Does semihosting work without a physical debugger connected?
On ARM Cortex-M cores, executing the BKPT 0xAB instruction with no
debugger attached will trigger a HardFault if the CoreDebug DHCSR.C_DEBUGEN bit is not set. QEMU and other simulators implement the semihosting trap in software, so they work without hardware. If you need to run without a debugger on real hardware, replace the semihosting stubs with no-ops or a
UART-backed implementation before deployment.
What is the difference between semihosting and SWO/ITM trace output?
SWO/ITM (available on Cortex-M3/M4/M7/M33 and similar cores that include the ITM peripheral) can stream debug data asynchronously over a dedicated trace pin without halting the CPU; with appropriate formatter code it is commonly used for printf-style logging. Semihosting halts the core at a BKPT instruction and waits for the
debugger to service the request. ITM is far less intrusive for timing-sensitive code; semihosting is simpler to set up on simulators that do not model an ITM peripheral.
How do I make sure semihosting is not included in my release build?
With
GCC Arm Embedded (arm-none-eabi-gcc), link against --specs=nosys.specs or --specs=nano.specs combined with a custom syscalls.c that provides your own _write, _read, etc. implementations. In Keil MDK and IAR, select a non-semihosting runtime library variant in the project settings. Verify by checking the
linker map file -- symbols like __semihosting_library_function or _sys_write from the semihosting library should not appear.
Can semihosting be used for file I/O, not just printf?
Yes. The ARM semihosting specification defines operations for open, close, read, write, seek, file length, and several others (operation codes 0x01 through 0x31). This is useful in simulator-based testing workflows where firmware can read input stimulus from a host file and write results back, enabling automated test comparisons on a PC without hardware.
Is semihosting specific to ARM cores?
The BKPT-based semihosting protocol is defined by Arm and is specific to Arm architecture cores (Cortex-M, Cortex-A, Cortex-R, and older ARM7/ARM9). Other architectures have analogous mechanisms -- MIPS uses the SDBBP instruction with a similar convention, and
RISC-V simulators like QEMU implement a semihosting extension -- but the register conventions and operation codes differ. When targeting non-ARM parts, check the
toolchain and simulator documentation for the equivalent feature.
Differentiators vs similar concepts
Semihosting is often confused with ITM/SWO trace output and with RTT (SEGGER Real-Time Transfer). All three can deliver printf-style output over a debug connection, but they differ significantly. ITM/SWO streams data asynchronously over a dedicated trace pin without halting the CPU, making it much less intrusive; it is available on Cortex-M3 and later cores that include the ITM peripheral. RTT uses a shared memory ring buffer polled by the J-Link debug probe, also without halting the core, and works on any Cortex-M device (including Cortex-M0, which lacks ITM). Semihosting halts the target at each call, is the simplest to enable in simulators (QEMU supports it natively for many target configurations), and provides full host file-system access -- not just console output. In production or timing-sensitive code, semihosting should be replaced; ITM or RTT are preferred for on-hardware debug logging.