RISC-V Standalone Firmware: Find Semihosting Hidden Behind Console I/O
A console message that works through a debugger can depend on the host computer rather than a peripheral in the product. After standalone deployment, the same call may enter an unserviced breakpoint path. Removing a visible printf() can hide the symptom while leaving other host-dependent library operations in the image.
Investigate the linked I/O backend first. The concrete examples here use Picolibc 1.8.10, the ratified RISC-V Semihosting 1.0 binary interface and OpenOCD 0.12.0's implementation. They do not imply that every RISC-V C library, printf() implementation or breakpoint uses semihosting.

Find the dependency in the final link
Picolibc's versioned usage guide describes libsemihost.a as a separate library that provides console I/O and termination support through a debug or emulation environment. Its interfaces are needed by libc, so the guide places it after libc in the link order. The Picolibc specs option --oslib=semihost selects that support.
Inspect the final link command and map for this option, the archive and the members actually selected. Then follow the called output implementation in the linked disassembly. A library listed on a command line is a useful lead, but the code satisfying the program's references is the relevant evidence.
Do not assume a universal syscall name. In Picolibc's documented small-console arrangement, the application provides stdin, stdout and stderr pointers to FILE objects whose callbacks implement character I/O. Other configurations can use different lower-level interfaces. Trace the selected stream and callback instead of looking only for a newlib-style _write symbol.
Recognize the RISC-V request rather than an arbitrary breakpoint
The Semihosting 1.0 binary interface defines three consecutive 32-bit instructions: slli x0,x0,31, ebreak and srai x0,x0,7. The surrounding marker instructions distinguish this request from an ordinary debugger breakpoint. They must remain 32-bit instructions even when the processor supports compressed instructions.
The operation number is supplied in a0, the parameter in a1, and the result returns in a0. Registers and data-block fields follow XLEN. Inspect the instructions around the stopped program counter together with the caller and arguments; finding an isolated ebreak is not enough to identify this ABI.
For example, a linked output callback that builds a semihosting request and reaches that sequence establishes a host-service dependency. It does not establish that a UART was initialized, that characters reached a physical pin or that the same service exists after the debug session ends.
A debugger must recognize and complete the request
The inspected OpenOCD RISC-V implementation first checks that semihosting is active. It then reads the instructions at the program counter and the adjacent four-byte positions, requiring the exact marker sequence. Only after recognition does it obtain the operation and parameter registers for the common service implementation.
Completion is another condition. The code advances the program counter past the four-byte breakpoint and returns its handled result only when the operation is resumable and no file-I/O request is pending. Thus a debugger connection alone is not evidence that a particular request has completed successfully.
Without a servicing environment, the outcome depends on the platform's debug and trap configuration. Do not turn this diagnosis into a blanket claim that an unserviced request always hangs, or repair it by blindly skipping every breakpoint. Such a change can conceal an ordinary fault and cannot manufacture a valid I/O result.
Replace the backend deliberately and test the deployment mode
Picolibc's system-interface guide describes board-supplied stream callbacks, including a UART-backed example. Use the supported interface for the product's actual library configuration, with defined blocking and error behavior. Simply removing --oslib=semihost can expose unresolved interfaces; it does not create their board implementations.
Audit more than the first log statement. Error paths, assertions, termination and file operations may have pulled in the same host-dependent support. Decide which operations the product supports and provide an intentional implementation or failure policy for each required interface.
For acceptance, propose a standalone boot using the production image and an observation independent of the host console. Exercise ordinary output and a controlled error path, then compare the selected backend with the intended release configuration. Keep any debug-only semihosted variant explicit. The result should establish where I/O is implemented, rather than treating text visible in a development session as proof of standalone operation.