Skip to content

feat: add clang sysroot#60

Merged
ivmarkov merged 4 commits intoesp-rs:mainfrom
inomotech-foss:feat/sysroot
Feb 25, 2026
Merged

feat: add clang sysroot#60
ivmarkov merged 4 commits intoesp-rs:mainfrom
inomotech-foss:feat/sysroot

Conversation

@scootermon
Copy link
Contributor

@scootermon scootermon commented Feb 22, 2026

Closes: #51

Verified by running the srp example on an esp32c6 while hosting the network using ot_cli on an esp32c5:

esp32c5> ot srp server host
srp-example-1b86624d.default.service.arpa.
    deleted: false
    addresses: [fd1f:<SNIP>:dc8b]
Old progress updates

This PR is still not quite finished. The current status is that we can compile the library with the sysroot, but actually building the ESP example is failing due to linker issues. This is totally expected though.

Update 1:
Funnily enough the linker errors were caused by me not using extern "C" blocks in the headers. Since OpenThread is mostly C++ code it actually makes a difference for once.
So at this point the ESP examples both compile and link. I just need access to an ESP to test it on. Should be able to do that tomorrow.

There are some symbols like exit and the assertion fail handler otPlatAssertFail that we don't define yet. Apparently they aren't required for the examples. I'll have to dig into those a bit to see under what conditions we actually need them.

This is largely just copying over the existing work done by @ivmarkov for mbedtls-rs-sys.
This commit does not enable the use of clang yet because without a sysroot there's little reason to use it.
A subsequent commit will introduce a sysroot so clang can be used by default.
@ivmarkov
Copy link
Collaborator

ivmarkov commented Feb 23, 2026

Closes: #51

This PR is still not quite finished. The current status is that we can compile the library with the sysroot, but actually building the ESP example is failing due to linker issues. This is totally expected though.

Interested to see the linker issues.

W.r.t. linking, there should be no differences between compiling with GCC and compiling with CLang, I believe.

In either case we don't really link with any form of "native" "embedded" libc (or try hard not to!). In either case, we should link against the ESP ROM (esp-sys-rom or whatever the crate name was) which does contain most of the symbols we need. If we miss a symbol, then linking also with tinyrlibc should help. BTW, we use exclusively tinyrlibc for the NRF52 examples, which don't have a "mini-libc-in-the-ROM" like the ESPs do.

@scootermon
Copy link
Contributor Author

scootermon commented Feb 23, 2026

@ivmarkov, that was some great timing. I just finished updating the description after you added your comment.

Interested to see the linker issues.

W.r.t. linking, there should be no differences between compiling with GCC and compiling with CLang, I believe.

Yeah, the linking issue was entirely an oversight on my part. The sysroot headers were missing the extern "C" { block. For instance the OpenThread string.cpp file includes string.h and the corresponding object file string.cpp.obj ended up using C++ name mangling for the _support_snprintf symbol, which caused the linker error.
I was expecting to see some linker errors since we have a few functions in the headers which are never defined, but as it turns out the issue was entirely unrelated.
I'm still somewhat surprised that there are no linker errors with the example now. I kind of expected an error related to either exit or otPlatAssertFail since both are in some way tied to how mbedtls / openthread handle assertions. I don't think esp-rom-sys defines a exit implementation, but I'll have to double check.

@scootermon
Copy link
Contributor Author

scootermon commented Feb 23, 2026

Here's the list of undefined symbols across all archive files in the openthread-sys build directory for riscv32imac-unknown-none-elf:

# libc (I removed our support library for this list so we can see all symbols)
exit
iscntrl
isprint
# funnily enough `isupper` is gone in the new version
memchr
memcmp
memcpy
memmove
memset
snprintf
strchr
strcmp
strcpy
strlen
strncpy
strstr
vsnprintf

otPlatAlarmMilliGetNow
otPlatAlarmMilliStartAt
otPlatAlarmMilliStop
otPlatEntropyGet
otPlatGetResetReason
otPlatLog
otPlatRadioDisable
otPlatRadioEnable
otPlatRadioEnergyScan
otPlatRadioGetCaps
otPlatRadioGetCcaEnergyDetectThreshold
otPlatRadioGetIeeeEui64
otPlatRadioGetPromiscuous
otPlatRadioGetReceiveSensitivity
otPlatRadioGetRssi
otPlatRadioGetTransmitBuffer
otPlatRadioGetTransmitPower
otPlatRadioReceive
otPlatRadioSetCcaEnergyDetectThreshold
otPlatRadioSetExtendedAddress
otPlatRadioSetPanId
otPlatRadioSetPromiscuous
otPlatRadioSetShortAddress
otPlatRadioSetTransmitPower
otPlatRadioSleep
otPlatRadioTransmit
otPlatReset
otPlatSettingsDeinit
otPlatSettingsDelete
otPlatSettingsGet
otPlatSettingsInit
otPlatSettingsSet
otPlatSettingsWipe
otPlatWakeHost

SEGGER_RTT_SetFlagsUpBuffer
SEGGER_RTT_WriteNoLock

tcplp_sys_accept_ready
tcplp_sys_accepted_connection
tcplp_sys_autobind
tcplp_sys_connection_lost
tcplp_sys_free_message
tcplp_sys_generate_isn
tcplp_sys_get_millis
tcplp_sys_get_ticks
tcplp_sys_hostswap16
tcplp_sys_hostswap32
tcplp_sys_log
tcplp_sys_new_message
tcplp_sys_on_state_change
tcplp_sys_panic
tcplp_sys_send_message
tcplp_sys_set_timer
tcplp_sys_stop_timer

@ivmarkov
Copy link
Collaborator

  • All otPlat are actually callbacks that we have to implement. Some of these have default implementations inside OT with __weak__ linkage attributes (i.e. they are used unless we define a strong symbol)
  • SEGGER sounds like a debug thing we won't need
  • TCPIP we don't enable (yet) but even if we did, these tcpip-sys functions look suspiciously named. My wild guess is that they are only used to bind to some sort of "system" bsd-sockets-like impl, which is also irrelevant for our case
  • The rest (libc) is what the user needs to provide, not us. On embedded, this is either via tinyrlibc, or ROM functions, or both. On regular OSes these come from the OS libc

@scootermon
Copy link
Contributor Author

* SEGGER sounds like a debug thing we won't need

Those get pulled in by the 'openthread-platform-utils' library. I'm pretty sure we don't even need this library, but there's currently no way to exclude it from compilation.

* The rest (libc) is what the **user** needs to provide, not us. On embedded, this is either via `tinyrlibc`, or ROM functions, or both. On regular OSes these come from the OS libc

I agree with this, but I feel like the situation is currently not as clear-cut. openthread-sys provides its own vsnprintf and snprintf functions and openthread adds iscntrl, isprint, and optionally isupper on top.
Anyway, this isn't really relevant to this PR. I will run a sanity test on an ESP32C6 today to ensure everything still works as expected and then this PR should be good to go!

@ivmarkov
Copy link
Collaborator

* SEGGER sounds like a debug thing we won't need

Those get pulled in by the 'openthread-platform-utils' library. I'm pretty sure we don't even need this library, but there's currently no way to exclude it from compilation.

But the question remains how come this is not a problem when compiling with GCC? In other words, who is providing these symbols in the first place?

* The rest (libc) is what the **user** needs to provide, not us. On embedded, this is either via `tinyrlibc`, or ROM functions, or both. On regular OSes these come from the OS libc

I agree with this, but I feel like the situation is currently not as clear-cut. openthread-sys provides its own vsnprintf and snprintf functions and openthread adds iscntrl, isprint, and optionally isupper on top.

The vsprintf and snprintf symbols provided by openthread-sys are a copy-paste of the ones provided by tinyrlibc. The reason for the copy-paste (which might not even be quite legal, as these are not Apache+MIT, as openthread) is that vsprintf snprintf are the only two symbols in tinyrlibc coded in C rather than in Rust (and thus requiring a GCC compiler around). Since building with clang was not an option until this very PR, the copy-paste workaround + caching the pre-built openthread .a libs for certain embedded platforms relieved the user from having to install an embedded GCC MCU toolchain. Granted, with the introduction of clang we might revisit this.

As for iscntrl, isprint and isupper - these were either remnants from before the "use tinyrlibc to backfill" mantra was introduced OR tinyrlibc does not have an impl of those.

Anyway, this isn't really relevant to this PR. I will run a sanity test on an ESP32C6 today to ensure everything still works as expected and then this PR should be good to go!

I agree. We can fix the tech debt from above later, as long as ESP (and NRF!) examples do compile cleanly. But this is tested by the CI anyway.

@scootermon
Copy link
Contributor Author

scootermon commented Feb 24, 2026

But the question remains how come this is not a problem when compiling with GCC? In other words, who is providing these symbols in the first place?

I should've maybe clarified a bit further. The list of symbols is simply the entire set of undefined symbols in the libraries (specifically all the object files in the various "lib*.a" archive files). Whether or not these symbols are actually needed entirely depends on whether we end up calling a function that directly or indirectly uses one of the symbols.
A lot of the "surprising" symbols will simply never make it into the final binary since they're not used.

For reference, here's the same list generated from the pre-compiled archive files that are currently checked in on the main branch:

# Apparently gcc likes to use a lot more compiler runtime stuff :)
__ashldi3
__ashrdi3
__bswapsi2
__clzsi2
__ctzsi2
__errno
__lshrdi3
__udivdi3
__umoddi3

exit
iscntrl
isprint
memcmp
memcpy
memmove
memset
strchr
strcmp
strcpy
strerror
strlen
strncpy
strstr
strtoul
utoa

otPlatAlarmMilliGetNow
otPlatAlarmMilliStartAt
otPlatAlarmMilliStop
otPlatEntropyGet
otPlatGetResetReason
otPlatLog
otPlatRadioDisable
otPlatRadioEnable
otPlatRadioEnergyScan
otPlatRadioGetCaps
otPlatRadioGetCcaEnergyDetectThreshold
otPlatRadioGetIeeeEui64
otPlatRadioGetPromiscuous
otPlatRadioGetReceiveSensitivity
otPlatRadioGetRssi
otPlatRadioGetTransmitBuffer
otPlatRadioGetTransmitPower
otPlatRadioReceive
otPlatRadioSetCcaEnergyDetectThreshold
otPlatRadioSetExtendedAddress
otPlatRadioSetPanId
otPlatRadioSetPromiscuous
otPlatRadioSetShortAddress
otPlatRadioSetTransmitPower
otPlatRadioSleep
otPlatRadioTransmit
otPlatReset
otPlatSettingsDeinit
otPlatSettingsDelete
otPlatSettingsGet
otPlatSettingsInit
otPlatSettingsSet
otPlatSettingsWipe
otPlatWakeHost

SEGGER_RTT_SetFlagsUpBuffer
SEGGER_RTT_WriteNoLock

tcplp_sys_accept_ready
tcplp_sys_accepted_connection
tcplp_sys_autobind
tcplp_sys_connection_lost
tcplp_sys_free_message
tcplp_sys_generate_isn
tcplp_sys_get_millis
tcplp_sys_get_ticks
tcplp_sys_hostswap16
tcplp_sys_hostswap32
tcplp_sys_log
tcplp_sys_new_message
tcplp_sys_on_state_change
tcplp_sys_panic
tcplp_sys_send_message
tcplp_sys_set_timer
tcplp_sys_stop_timer

Notice how the list is pretty much the same. The only notable difference is that the pre-compiled archives use the strerror symbol, which disappeared in my version because the sysroot defines SPINEL_PLATFORM_DOESNT_IMPLEMENT_ERRNO_VAR as 1 (this disables the code branch that ends up calling strerror).

So for example, SEGGER_RTT_SetFlagsUpBuffer is called by otPlatUartEnable in the platform utils. As long as we don't call it (it's not even included in our bindings currently) we won't run into a linker error.

@ivmarkov
Copy link
Collaborator

OK - just mark it as ready for review when you believe it is ready.

@scootermon scootermon marked this pull request as ready for review February 24, 2026 19:07
@ivmarkov ivmarkov merged commit 4de7f24 into esp-rs:main Feb 25, 2026
1 check passed
@scootermon scootermon deleted the feat/sysroot branch February 25, 2026 09:29
scootermon added a commit to inomotech-foss/openthread-rs that referenced this pull request Feb 25, 2026
I forgot to update the xtask code in <esp-rs#60>,
breaking it.
ivmarkov pushed a commit that referenced this pull request Feb 25, 2026
I forgot to update the xtask code in <#60>,
breaking it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Build OpenThread .a libs without sysroot

2 participants