With CONFIG_FDPIC selected, a module built by apps/Application.mk is now an
FDPIC shared object. Nothing about how a module is written or built
changes: the same MODULE = m in the same Makefile, the same crt0 and the
same linker script.
Two things differ from the position independent build beside it. The
compiler is told -mfdpic -fPIC, and the link is done by an
arm-uclinuxfdpiceabi linker. The stock arm-none-eabi compiler emits correct
FDPIC objects for both C and C++, so only the link needs it: the stock
linker carries the armelf emulation alone and would turn every import into
an R_ARM_JUMP_SLOT, one word, where the ABI wants an R_ARM_FUNCDESC_VALUE,
which is two, a code address and the data base that goes with it. Such a
module links cleanly and then calls out of itself with the caller's data
base still in r9. That linker is in the CI image.
gnu-elf.ld.in gains the two segments an FDPIC module needs, under
CONFIG_FDPIC, because the loader places its read-only and writable segments
independently, and names .dynamic, because a shared object is bound through
it. The sections themselves are untouched and so are the symbols crt0.c
walks, so one script serves both and both build systems get it.
.bss moves to the end of the script, for every configuration and not only
FDPIC. It held no file content but sat ahead of .got and .dynamic, which
do, so the writable segment's p_filesz had to span it and the module file
carried the whole of .bss. A module with 16 KiB of .bss went from 26724 to
10340 bytes, and its writable segment from p_filesz 0x40ac to 0xac against
an unchanged p_memsz. The loader reads p_filesz off the media, so it read
those bytes too.
Built for mps3-an547:picostest with apps/examples/elf, CONFIG_FDPIC both
ways. With it on, every module in apps/bin is ARM FDPIC with two PT_LOAD
segments and enters at _start; hello++3, which has a static C++ object,
carries DT_INIT_ARRAY and DT_FINI_ARRAY. With it off the generated script
has no PHDRS and the modules are what they were.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
gotindex named the .got section header, and every user then reached through
shdr[] for what it actually wanted. Only one of the five wanted the index.
gotbase and gotsize say it directly. gotsize is the extent of .got and is
also what says the object has one, and gotbase is where the GOT ended up:
the placed address of .got for an ordinary object, or DT_PLTGOT for an FDPIC
one, which libelf_bind() already reads. Both are set in libelf_loadfile(),
after the sections are placed, so gotbase is the address the object will be
read at rather than the one it was linked for.
The GOT walk in libelf_loadfile() now runs only when there is a base, which
also keeps it off an FDPIC object. An FDPIC object's sections are never
placed, so .got carried a link time sh_addr there, and the walk read and
wrote through it. Its GOT is relocated through its own relocations.
The check that gates libelf_xipacquire() runs before the load, when neither
field is set, so it looks the section up by name. It hands the index it
found to libelf_loadfile(), which is the only reason that function takes
one: the object is searched once, not twice.
One behaviour changes: a .got that exists but is empty now reads as no GOT.
There is nothing for any of the five users to do with an empty one.
Built for pimoroni-pico-2-plus with CONFIG_PIC, CONFIG_ELF and
CONFIG_LIBC_ELF, and for mps3-an547:bl, which is the board that read the
index. Run on QEMU with mps3-an547:picostest, which loads PIC ELF modules
from a romfs: hello prints, and ostest reaches the timed mutex test, the
same as before the change.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
The FDPIC work touches these files, and nxstyle reports errors on the lines
around every hunk, which fails the check job. The errors are older than
this series: a switch body indented two columns too deep in elf_symbols.c,
and declarations with no blank line after them.
Whitespace and one reworded comment, no change in behaviour.
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
A module that dlopen()s a library gets back function addresses from
dlsym() and calls them. Under FDPIC a bare code address is not enough:
the callee needs its own data base as well, so what dlsym() returns has
to be a function descriptor.
The exported symbol table carries no type information -- symtab_s is a
name and a value, and its own comment says typing would have to be added
to support anything but function pointers -- so by the time dlsym() is
asked there is no way to tell a function from an object.
libelf_insertsymtab() is the last point that can: st_info is still in
hand there. So an FDPIC object's exported functions are published as the
address of a descriptor carved from the module's pool, and dlopen(),
dlsym() and the module registry need no knowledge of FDPIC at all. The
pool is sized for the dynamic symbol table as well as the relocations,
since both can draw from it.
That leaves the symbol values themselves, which were wrong for any
ET_DYN object. libelf_loadsymtab() adds the symbol's section address to
its value, which is right for ET_REL, where the section address is where
the section was actually placed and the value is relative to it. In a
shared object both are already full link-time addresses, so adding them
counts the section twice. It needs translating onto wherever the object
was placed instead.
Library data is shared between everything that dlopen()s it, because the
registry holds one instance per name. Giving each user its own copy
would mean teaching the registry about instances, which is a much larger
change to shared code; an executable loaded through exec() already gets
its own data, since that path loads a fresh copy each time.
Built and run on lm3s6965-ek with the examples/elf ROMFS; the FDPIC
module continues to load, relocate and call through its own descriptors.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Running one for the first time turned up two holes in the ET_DYN path.
Neither shows up in a build.
An undefined symbol is resolved with libelf_findglobal(), which searches
only the table of globally registered symbols. The export table that
exec() hands its caller went no further than the ET_REL path, so an
ET_DYN module could not import anything the caller supplied. Invisible
while such modules resolved everything internally; an FDPIC module
imports its libc, and every import failed with "Unable to resolve addr of
ext ref printf" although the caller had passed a table containing printf.
The export table is now threaded into libelf_relocatedyn() and consulted
when the global table has no answer, leaving the existing lookup order
intact.
A relocation naming a symbol defined inside the object was dropped
silently. The code handles a relocation with no symbol, and one against
an undefined symbol, but a defined symbol fell through both. That was
harmless while every dynamic relocation arriving here had symbol index
zero, which is the case for R_ARM_RELATIVE. FDPIC brings the first ones
that do not: a pointer to a static function is emitted against the
*section* symbol, so the value is the section base and the offset within
it -- including the Thumb bit -- is carried as the addend. Deriving a
value from the word being patched, as the no-symbol case does, would
translate that addend as though it were an address. Confirmed against a
real module: .text at 0x23c plus an addend of 0x95 gives 0x2d1, which is
the function with its Thumb bit.
Also stop libelf_symname() reporting a nameless symbol as an error. A
section symbol has no name, and libelf_findsymbol() walks the whole table
looking for optional entries such as nx_stacksize, so it meets these
routinely and checks for -ESRCH itself. At error level it printed ten or
more lines per module load and buried the diagnostics that matter.
Built and run on lm3s6965-ek with the examples/elf ROMFS. The ET_REL
test modules load as before, and an FDPIC module now loads, relocates,
resolves printf and puts from the table exec() supplied, and calls
through a function descriptor of its own.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
libelf_relocatedyn() reads the handful of DT_* tags it needs to walk the
relocation tables and ignores the rest. Three more matter now.
DT_PLTGOT is where the object's data base lives. An FDPIC module runs
with that in the PIC base register, and every function descriptor built
for it names the same base as the one its callee should run with, so
without it there is nothing to put in a descriptor's second word.
The DT_*_ARRAY tags are the constructor and destructor tables. These are
already found through the section headers a few lines further down, and
that path is kept, but the dynamic tags are the authoritative copy and an
object is not obliged to carry section headers at all. Both paths now
translate through libelf_addr(), so they agree on the answer rather than
depending on which ran last. The tag values themselves were missing from
include/elf.h and are added.
Sizing the descriptor pool has to happen here rather than later.
R_ARM_FUNCDESC asks the loader to manufacture a descriptor and hand back
its address, which means the space must exist by the time the relocation
is applied, and by then the segment has been placed. So libelf_elfsize()
reserves it behind the writable data, bounded by the relocation count --
one relocation cannot ask for more than one descriptor. That bound has
slack in it, but a descriptor is two words and modules are small, which
is cheaper than walking every relocation twice to get an exact count.
Nothing here runs for a non-FDPIC object. Built and booted
mps3-an547:picostest with no change in behaviour.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
An ET_DYN object is loaded into one allocation with its data behind its
text, because its data references sit at a fixed distance from the code
that makes them. An FDPIC object does not work that way: it reaches its
data through a base register, so the two segments can be placed wherever
suits, and the point of the format is that the read-only one is left on
the media and executed there while only the writable one is copied. One
copy of the text then serves every instance.
So libelf_load() grows a second case. The object announces itself in the
OS/ABI byte, which is noted once in libelf_loadhdrs() rather than
re-derived; e_flags cannot be used for this, as an FDPIC object's are an
unremarkable EABI version and testing them would reject every valid
module. Text is taken from the media address plus the segment's own file
offset -- the same arithmetic the ET_REL path already does with
sh_offset -- and libelf_loadfile() does not read it. If the filesystem
cannot show its media, the loader copies the text to RAM instead. The
module then loses the shared text and the flash saving, but it runs.
Obtaining that address needs two mechanisms, and they are not
interchangeable. A compacting filesystem can move a file's blocks, so it
hands out an address only with a pin that holds them still and expects
the pin back; xipfs is the one in tree. A filesystem whose layout never
changes has nothing to hold and answers FIOC_XIPBASE with a bare address;
romfs and tmpfs are those. libelf_xipacquire() asks for the pin first,
because a filesystem that needs one is not safe without it, and
libelf_unload() gives it back. The loader asks for a pin only if it can
hold one, or the pin would stay for ever.
The pin is thus not specific to FDPIC. Any module that executes in place
from a compacting filesystem takes one, and gives it back at unload.
mmap() is not used, though both filesystems implement it. The mapping
would be recorded against whichever task called the loader, while the
release happens when the module's own task exits, which is a different
group -- so the pin would outlive the module and the extent would never
become movable again.
Unloading has to change with placement: the existing path frees only
textalloc because ET_DYN had a single allocation, which would leak an
FDPIC object's data and free media the filesystem only lent us.
Nothing here runs for a non-FDPIC object; every branch is behind the flag
and the single-allocation path is untouched. Built and booted
mps3-an547:picostest, which is CONFIG_ELF with CONFIG_PIC, with no change
in behaviour.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Replace the local EINTR retry loop with nxsem_wait_uninterruptible().
This keeps the master implementation aligned with the libc semaphore API
without changing cancellation behavior.
Keep the cleanup separate so release branches where the helper is not
available to Protected user space can use the functional commit without a
downstream compatibility patch.
Assisted-by: Codex:GPT-5
Signed-off-by: DuoYuWang <thirteenking.wang@gmail.com>
Implement the handle-based create, queue, priority, cancellation, and
teardown APIs for CONFIG_LIBC_USRWORK. Custom queues use configurable
pthread worker pools while the predefined USRWORK queue remains available.
Match scheduler-backend delay, replacement, cancellation, and lifecycle
semantics. Restrict the libc backend to task context because it uses
blocking synchronization.
Tested on an STM32H7 PX4 FMUv6C with ostest wqueue in Protected user space.
Assisted-by: Codex:GPT-5
Signed-off-by: DuoYuWang <thirteenking.wang@gmail.com>
Both files put the body of the relocation switch at the same indent as the
switch braces, so nxstyle reports forty-four errors in each and any patch
whose hunks land near them fails the check job.
Giving the body its level takes the bit diagrams in the comments one column
past the line limit. The rulers say Instr rather than Instructions, which is
enough and is what the same rulers further down already do. A comment that
had no code on its line becomes a sentence of its own, and two that were a
column out are put right.
Whitespace and comments only. Compiled before and after for cortex-m7 and
cortex-m33: the disassembly is identical.
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Increase LINK_MAX from _POSIX_LINK_MAX (8) to 128 to allow
directories to have a reasonable number of subdirectories while
still enforcing a hard link limit.
Also fix pathconf(_PC_LINK_MAX) to return the actual LINK_MAX
value instead of the minimum _POSIX_LINK_MAX.
Signed-off-by: yukangzhi <yukangzhi@xiaomi.com>
CI feeds nxstyle the diff hunks with three lines of context, so style errors
that are older than this change, in the lines around the hunks, fail the
check job. They are a switch body indented two columns too deep, an
initializer brace one level in, and two declarations with no blank line
after them.
Whitespace only, no change in behaviour.
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
The ET_DYN path computes run-time addresses from link-time ones in five
places, each open-coding the arithmetic, and two of them disagree about
how: libelf_relocatedyn() adds textalloc to a relocation's r_offset in
one branch and subtracts datasec before adding datastart in the next,
while the value translation a few lines further down picks between those
two forms with an explicit test on datasec.
Collect that into libelf_addr(), which makes the test once: an address
below the data segment's link-time base belongs to text, anything at or
above it to data.
This changes nothing today. libelf_elfsize() sets
segpad = datasec - (text_vaddr + textsize)
and libelf_load() then places
datastart = textalloc + textsize + segpad
so datastart - datasec is textalloc, and the data branch reduces to
textalloc + vaddr -- exactly what the text branch returns, and exactly
what adding a single load bias did before. The two forms are the same
arithmetic written twice.
They stop being the same once text and data are placed independently,
which is what an FDPIC object requires: its two PT_LOAD segments are
relocated separately so that the read-only one can be mapped in place on
the media while only the writable one is copied. Having the translation
in one function is what makes that possible without auditing every
open-coded expression again.
Built for mps3-an547:picostest, which is CONFIG_ELF with CONFIG_PIC, and
boots identically to the same configuration without this change.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Implement atomic_lock/atomic_unlock using hwspinlock when
CONFIG_LIBC_ATOMIC_HWSPINLOCK is selected, and using up_irq_save/
up_irq_restore when CONFIG_LIBC_ATOMIC_IRQ is selected. Rename
arch_atomic_irq.c to arch_atomic.c.
The 64-bit atomic operations use spinlock (spin_lock_irqsave)
regardless of the selected backend, ensuring multi-core safety.
Signed-off-by: zhangyu117 <zhangyu117@xiaomi.com>
The link support is no longer limited to the pseudo file system and now
covers both soft (symbolic) links and hard links across the VFS. Rename
the configuration option PSEUDOFS_SOFTLINKS to the more accurate FS_LINKS
and update all references in the source, headers, Kconfig, documentation
and board defconfigs accordingly.
This is a configuration rename; any out-of-tree defconfig that still
selects PSEUDOFS_SOFTLINKS must be updated to FS_LINKS.
Signed-off-by: zhengyu16 <zhengyu16@xiaomi.com>
Replace ILLD intrinsics (__swap, __ld32, __cmpAndSwap) with inline
assembly functions (tricore_atomic_swap, tricore_atomic_cmpswap) to
remove the dependency on IfxCpu_Intrinsics.h.
Also fix the expect parameter type to use volatile void * to match
the declaration in atomic.h, avoiding type conflicts.
Signed-off-by: zhangyu117 <zhangyu117@xiaomi.com>
libelf_elfsize() takes textalign and dataalign from the section headers,
which only the ET_REL path walks. An ET_DYN object is sized from its
program headers instead, so both fields stay at zero, and the allocation
a few lines later asks for that alignment:
loadinfo->textalloc = lib_memalign(loadinfo->textalign, ...);
Zero is not a valid alignment, and every path that receives it divides by
it. mm_memalign() accepts zero as a power of two, because 0 & -0 is 0,
then takes the "alignment <= MM_ALIGN" branch and evaluates
"((uintptr_t)ptr) % alignment" in a DEBUGASSERT. With
CONFIG_MM_HEAP_MEMPOOL and a pool that fits the request the object never
reaches that branch and gets ALIGN_UP(blk, 0) instead, which is
((blk - 1) / 0) * 0.
On Cortex-M this is usually invisible: UDIV returns zero for a division
by zero unless CCR.DIV_0_TRP is set, which NuttX does not set, so the
assertion compares zero against zero and passes. It is a SIGFPE on the
simulator, and the mempool path returns a null pointer wherever the
division yields zero, which the loader reports as -ENOMEM.
Ask for a natural word when the program headers gave nothing. p_align is
the linker's page granularity, not a section requirement, so honouring it
would cost a page per module for no gain, and the sections of a shared
object need no more than a word.
Built for mps3-an547:picostest, which is CONFIG_ELF with CONFIG_PIC.
Runtime evidence on hardware follows.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Rename atomic_fetch_add/sub/or/and/xor to atomic_add/sub/or/and/xor
to avoid conflicts with the C/C++ standard library naming. The
atomic_fetch_xxx naming is reserved by the standard; keeping it causes
function name conflicts when source files indirectly include both
<nuttx/atomic.h> and <atomic>/<stdatomic.h>.
Signed-off-by: zhangyu117 <zhangyu117@xiaomi.com>
The reason for using builtin atomic is that in C++, when include <atomic> in <nuttx/atomic.h> easily conflicts with third-party function libraries. We wanted to completely separate the implementation of <nuttx/atomic.h>.
There are two points:
1. use builtin function directly.
2. Without the standard library implementation, need implement "atomic_fetch_xxx", leading conflicts with the standard library used by third-party programs, introducing redefinition issues and requiring name changes.
Signed-off-by: zhangyu117 <zhangyu117@xiaomi.com>
Split the atomic implementation into three files:
- arch_atomic.h: Shared macros (STORE, LOAD, etc.) using atomic_lock()/
atomic_unlock() abstraction
- arch_atomic_irq.c: 32-bit atomic functions using IRQ disable (conditional
on CONFIG_LIBC_ATOMIC_IRQ via Make.defs)
- arch_atomic64.c: 64-bit atomic functions using spinlock (always compiled,
multi-core safe). The __atomic_* functions are always provided (GCC
runtime helpers), while nx_atomic_* functions are conditional on
!CONFIG_LIBC_ATOMIC_TOOLCHAIN.
Signed-off-by: zhangyu117 <zhangyu117@xiaomi.com>
Refine the atomic Kconfig to support multiple backends:
LIBC_ATOMIC_TOOLCHAIN (compiler builtins), LIBC_ATOMIC_ARCH (arch
instructions), and LIBC_ATOMIC_IRQ (interrupt disable). Rename
arch_atomic.c to arch_atomic_irq.c since it supports the IRQ backend.
Signed-off-by: zhangyu117 <zhangyu117@xiaomi.com>
Tricore gcc does not support atomic interface but some users need to
use atomic operations, so support atomic function using tricore arch
instructions (__cmpAndSwap/__swap/__ld32).
Signed-off-by: zhangyu117 <zhangyu117@xiaomi.com>
The atomic implementation of machine/arch_atomic.c is achieved by
switching interrupts. This version does not support SMP.
Signed-off-by: zhangyu117 <zhangyu117@xiaomi.com>
The BSD string functions take a word path only when both pointers are
aligned, and a byte path otherwise. A pair at the same offset from a
boundary takes the byte path even though copying or comparing a few leading
bytes aligns both at once, since aligning one aligns the other.
Add MISALIGNED(), which asks whether two pointers disagree about where a
boundary falls, and walk an agreeing pair up to the boundary before the
existing path selection. MISALIGNED4() does the same for the 4-byte path,
so a pair that is 4-byte but not 8-byte aligned reaches the wide path
instead of the middle one. No existing line changes: the walk is a new step
ahead of the current decisions. A pair at differing offsets still takes the
byte path, since no single boundary serves both.
Measured on an EIC7700 EVB (EIC7700X, RV64GC, 1.4GHz) with the BSD string
functions selected and the RISC-V assembly ones disabled, using the
benchmark in apps#3706, medians of 3 runs in MB/s at its largest size:
equal offset aligned
memcpy 414 -> 4148 10.0x 4214 -> 4208
memcmp 41 -> 361 8.8x 362 -> 360
strncmp 28 -> 202 7.4x 207 -> 207
strcmp 42 -> 273 6.5x 278 -> 276
strncpy 377 -> 1676 4.5x 1824 -> 1748
stpncpy 376 -> 1654 4.4x 1843 -> 1724
stpcpy 551 -> 1833 3.3x 1970 -> 1939
memccpy 650 -> 2012 3.1x 2478 -> 2016
strcpy 636 -> 1837 2.9x 1678 -> 1965
Cases the walk never runs for move in both directions by up to a third, the
largest being memccpy at differing offsets, 648 -> 414. Their code is
unchanged, so that is code placement rather than an effect of the change.
The change is architecture independent but has only been measured on
RV64GC. Word size, alignment cost and byte loop codegen all differ
elsewhere, so the balance wants measuring on other architectures.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
memcmp, strncmp and strcmp reach their word loops only when both pointers
are already on a register boundary:
or t0, a0, a1
andi t0, t0, SZREG-1
That asks more than the loops need. They load from the two pointers at
the same boundary, so what matters is that the two agree about where a
boundary falls, not that either is already on one. A pair offset by the
same amount can be walked up to the boundary a byte at a time and
compared a register at a time from there.
The union also holds far less often than the difference. For arbitrary
pointers on RV64 it is true about one time in 64 against one in eight,
and the case it rejects, two strings carved out of the same buffer, is
the common one.
Test the difference of the pointers, and walk to the boundary first.
arch_strcpy.S and arch_memcpy.S already do this. Keeping every access
aligned is not only faster here: the base ISA does not require misaligned
loads and stores to be supported at all, so a routine in a machine
directory cannot assume one will work, whatever it costs.
Measured on a 1.4 GHz rv64, source and destination misaligned by one:
before after
memcmp 32K 34.4 458.0 MB/s
strncmp 32K 32.4 253.0 MB/s
strcmp 32K 41.0 280.0 MB/s
Each of those was the rate of the byte loop the word loop was meant to
replace. Pointers that genuinely disagree still take the byte loop, and
the aligned rates are unchanged.
The measurements come from the benchmark in apache/nuttx-apps#3706.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
The word loop walks src to a register boundary and then stores a whole
register at a time to dst, but nothing establishes that dst is on a
boundary too. Where the two pointers disagree about where a boundary
falls, every store in that loop is misaligned.
The base ISA does not require misaligned stores to be supported. Where
firmware emulates them each store traps into machine mode, and where
nothing emulates them the store faults, so this is not only a question of
speed. Measured on a 1.4 GHz rv64 that emulates them, with a 32 KB
string whose src and dst are misaligned by different amounts:
generic C 410.4 MB/s
this file 7.5 MB/s
which is around 178 cycles per byte, flat from 512 bytes to 32 KB.
Test the two pointers against each other before going wide, as
arch_strcpy.S already does. Pointers that agree still reach the word
loop, since walking src to a boundary walks dst to one as well; pointers
that disagree take the byte path, where no single boundary serves both.
After the change the misaligned case runs at 490 MB/s and the aligned
rates are unchanged.
The measurements come from the benchmark in apache/nuttx-apps#3706.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
libc_data_t is 8 bytes wide, so a buffer which is 4-byte but not
8-byte aligned falls back to the byte at a time loop. Add a 32-bit
middle path so such buffers still handle four bytes per iteration.
* Add DETECTNULL32/DETECTCHAR32, UNALIGNED4/UNALIGNED4_X,
LITTLEBLOCKSIZE4/BIGBLOCKSIZE4 and TOO_SMALL4 to libs/libc/libc.h.
* Take the new path in memccpy, memcmp, memcpy, memset, stpcpy,
stpncpy, strcmp, strcpy, strncmp and strncpy when both pointers are
4-byte aligned but the 8-byte path can't be used.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Xiang Xiao <xiaoxiang@xiaomi.com>
When the 'c' parameter has bit 7 set (e.g. 0x80), the int value gets
sign extended (to 0xffffff80 on the signed char platforms). The word
sized fill pattern was built without truncating to unsigned char
first, so the fast word aligned path wrote the wrong bytes.
Fix both lib_memset.c and lib_bsdmemset.c by casting 'c' to unsigned
char before building the fill pattern, as required by C11 7.24.6.1
which states that memset converts 'c' to unsigned char.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Bowen Wang <wangbowen6@xiaomi.com>
memrchr scans backward, so the original implementation aligned
(x + 1) rather than x:
#define UNALIGNED(x) ((long)(uintptr_t)((x) + 1) & (sizeof(long) - 1))
while the common UNALIGNED_X() macro checks the pointer itself. Pass
src0 + 1 to UNALIGNED_X() to restore the original behavior, otherwise
asrc is off by one byte and the word loop reads the wrong data.
Assisted-by: Claude:claude-opus-5
Signed-off-by: anjiahao <anjiahao@xiaomi.com>
Remove the incorrect address restoration logic in the memrchr fast
path. The UNALIGNED_X loop already ensures the proper alignment, so
the subsequent address recalculation is unnecessary and makes memrchr
return the wrong position.
This fixes the syslog message corruption where memrchr reports the
incorrect newline position.
Assisted-by: Claude:claude-opus-5
Signed-off-by: fangpeina <fangpeina@xiaomi.com>
Most hardware accesses the memory through a 64-bit bus, so handle the
data in 64-bit chunks instead of "long" chunks which are only 32-bit
wide on the 32-bit platforms.
* Add the libc_data_t type (unsigned long long) and move the shared
UNALIGNED/UNALIGNED_X/ALIGNED, LITTLEBLOCKSIZE, TOO_SMALL and
DETECTNULL helpers from the individual C files to libs/libc/libc.h.
* Convert all lib_bsd*.c implementations to the new type and macros,
which also drops the duplicated LONG_MAX conditionals.
Assisted-by: Claude:claude-opus-5
Signed-off-by: anjiahao <anjiahao@xiaomi.com>
wait4() is BSD/Linux-standard (used by toybox's "time" applet) but NuttX
only had waitpid()+getrusage() separately. Add it to libs/libc/unistd/
built on top of those two existing primitives, so it needs no syscall
plumbing of its own and works unmodified across flat/protected/kernel
build separation. Prototype added to include/sys/wait.h.
Signed-off-by: Alan C. Assis <acassis@gmail.com>
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
padlen = sizeof(void *) - (addr % sizeof(void *)) never returns 0, even
when addr is already pointer-aligned -- it returns a full alignment unit
instead. Since callers size buflen for zero padding, the subsequent
"buflen < padlen + reqdlen" check then always fails, so getgrgid()/
getgrnam() and their _r variants always return ERANGE.
Found via `id` on sim:toybox, which resolves gid 0 to "root" through
this path.
Signed-off-by: Alan C. Assis <acassis@gmail.com>
Assisted-by: Claude Sonnet 5 <noreply@anthropic.com>
Complete the POSIX credential setters for real/effective/saved UID and
GID so login and privilege-drop paths can clear saved-root without
relying on setreuid patterns alone.
Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
Track supplementary GIDs per task group, wire setgroups/getgroups
syscalls when CONFIG_SCHED_NGROUPS > 0, and honor them in DAC checks
via nxsched_has_gid(). When NGROUPS is 0, libc provides getgroups/
setgroups stubs. initgroups() fails instead of silently truncating
when membership exceeds CONFIG_SCHED_NGROUPS.
Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
Rewrite arch_memcpy.S and arch_memset.S to be register-width aware on
both RV32 and RV64 using REG_L/REG_S/SZREG macros from asm.h.
memcpy gains:
- 16xSZREG unrolled main loop (128B/iter on RV64, 64B on RV32).
- Shift-merge path for misaligned src: reads two aligned words
straddling each output word and shifts them together, so no load
or store is ever misaligned.
- Single SZREG and byte loops for remainder and small copies.
memset gains:
- 32xSZREG unrolled main loop (256B/iter on RV64, 128B on RV32)
using Duff's device for non-power-of-two remainders.
- .option norvc ensures fixed 4-byte instruction width for correct
jump offset calculation in the Duff's device entry.
- Zero-length input handled correctly (branch to guarded tail).
The old memcpy always used lw/sw even on RV64, wasting half the
memory bandwidth. The old memset unrolled only 16 bytes per iteration.
Signed-off-by: ganjing <ganjing@xiaomi.com>
Add word-at-a-time strlcpy using DETECTNULL for both the copy phase
and the strlen tail when truncated. The copy loop aligns src and
processes a register at a time, falling to bytewise for the last word
containing the terminator. When truncated, the remaining src length
is measured with a second word-at-a-time loop.
strlcpy has 46 call sites in a typical kernel image (more than strcpy)
and is not covered by newlib OPTSPEED, making it a high-value target.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: ganjing <ganjing@xiaomi.com>
Reduce branch overhead in the memcmp main loop by comparing four
words per iteration: XOR each pair, OR the four differences together,
and branch once. On a mismatch the single-word loop locates the
exact differing word within four words of the fault.
Add a beqz guard at .Lbyte_cmp entry to handle the case where the
4-word loop consumes all remaining bytes exactly.
Measured on QEMU RV32: memcmp(128) 313 -> 271 cycles (13% faster).
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: ganjing <ganjing@xiaomi.com>
Add assembly-optimized implementations for 14 string/memory functions
using word-at-a-time techniques (DETECTNULL, broadcast+XOR) and
XLEN-adaptive macros for both RV32 and RV64:
- memmove: direction check + forward tail to memcpy, reverse path
with 16xSZREG unroll and shift-merge for misaligned src.
- memcmp: word-granularity compare when both pointers share alignment,
bytewise fallback for mismatched pointers.
- memchr: broadcast target byte, XOR with each word, DETECTNULL to
find matches. Counter-based bounds (no pointer overflow).
- strlen: DETECTNULL word loop, constants loaded from .srodata.
- strnlen: strlen with counter-based length limit.
- strcpy/strncpy: word loop with DETECTNULL, zero-fill remainder
for strncpy. strncpy reuses strcpy via #define USE_AS_STRNCPY.
- stpcpy/stpncpy: reuse strcpy/strncpy via #define USE_AS_STPCPY.
- strchr/strchrnul: broadcast+XOR detecting both target char and
null simultaneously. strchrnul reuses strchr via #define.
- strrchr: forward scan recording last match position.
- strncmp: word-at-a-time compare with null detection and counter.
- strcat: strlen(dst) then strcpy(dst_end, src) word-at-a-time.
Each function is independently selectable via CONFIG_RISCV_<FUNC>,
or all enabled together with CONFIG_RISCV_STRING_FUNCTION=y.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: ganjing <ganjing@xiaomi.com>
Implement architecture-specific ELF header definitions and relocation handling
for the MIPS architecture to enable loadable modules.
Fixes#19178.
Changes include:
- Add `arch/mips/include/elf.h` with MIPS ELF relocation types and
architecture-specific ELF data structures (`arch_elfdata_s`).
- Implement `libs/libc/machine/mips/arch_elf.c` containing `up_checkarch`,
`up_relocate`, and `up_relocateadd` functions handling `R_MIPS_NONE`,
`R_MIPS_32`, `R_MIPS_26`, `R_MIPS_HI16`, and `R_MIPS_LO16` relocations.
- Integrate MIPS machine-specific C library support in
`libs/libc/machine/mips/Make.defs`.
- Update `LDMODULEFLAGS` in `arch/mips/src/mips32/Toolchain.defs` to include the
little-endian (`-EL`) flag.
- Update `up_coherent_dcache` for proper cache synchronization on JZ4780.
Signed-off-by: Lwazi Dube <lwazeh@gmail.com>
NuttX implemented fork() and vfork() as the same function. Both were libc
wrappers around a single up_fork() syscall; vfork() differed only by a
trailing waitpid(). Underneath, the child joined the parent's address
environment -- the same addrenv_join() that pthread_create() uses -- and got
a private copy of the stack. So the child shared .data, .bss and the heap
with its parent and ran concurrently with it.
That is not fork(). It is vfork()-with-a-private-stack under fork()'s name,
and the history says so: today's fork() is NuttX's old vfork(), renamed in
c33d1c9c97 (2023) without any change of behaviour. The failure was silent --
a program written against POSIX fork() compiled, ran, and had its child's
writes land in the parent's variables.
Separate them into two primitives, chosen by which function the caller
called rather than by what the hardware happens to be:
fork() child gets its own copy of the parent's memory at the same
virtual addresses; runs concurrently. Only where an address
environment can be duplicated -- elsewhere it is not declared at
all, so calling it is a build error naming the function.
vfork() child shares the parent's memory; parent suspended until the
child _exit()s or exec()s. Implementable everywhere.
Below libc there is still one syscall. up_fork() gains a bool saying which
primitive the caller used, since the per-architecture register snapshot is
the same for both, and passes it to nxtask_setup_fork(), which is the single
place the memory semantics are decided. The argument arrives in the first
argument register and is never touched: each architecture's snapshot takes
some other call-clobbered register for its scratch, so the flag is simply
still there when the C worker is called.
The vfork() parent suspension moves out of libc into nxtask_start_fork(),
released from nxsched_release_tcb() by nxtask_resume_vfork(). Two things
follow: the parent is resumed at exec(), since exec_swap() has already handed
the child's pid to the loaded program by the time the vfork stub exits, and
vfork() no longer depends on CONFIG_SCHED_WAITPID.
Releasing there requires one fix in nxtask_exit(). It raises rtcb->lockcount
directly rather than through sched_lock() while it tears the TCB down, so the
nxsem_post() that wakes the vfork() parent leaves it queued where a blocked
task collects while pre-emption is off -- g_pendingtasks, or g_readytorun on
SMP -- and the matching raw lockcount-- does not publish it the way
sched_unlock() would, leaving the parent stranded with nothing to move it on.
The fix mirrors sched_unlock() for each case: nxsched_merge_pending(), or
nxsched_deliver_task() under CONFIG_SMP. Both are no-ops while pre-emption is
still disabled, and up_exit() re-reads this_task() afterwards, so a change of
the ready-to-run head is honoured. Without it vfork() deadlocks wherever no
other task happens to call sched_unlock() afterwards -- rv-virt:nsh64 and
rv-virt:pnsh64, where NSH is blocked in waitpid() holding the lock, and
qemu-armv8a:citest_smp, which hangs the moment the vfork() test runs.
fork() is built on a new addrenv_fork(), backed by an up_addrenv_fork() hook
that duplicates an address environment into freshly allocated pages mapped at
the same virtual addresses -- unlike up_addrenv_clone(), which copies only
the representation and leaves both pointing at the same page tables. The
child then adopts the parent's stack geometry rather than being given a
relocated copy: a pointer to a stack local taken before fork() must name the
same object in the child that it named in the parent, and the parent's stack
is already in the duplicate, with its contents, at the parent's address.
No architecture implements up_addrenv_fork() yet, so this commit leaves
fork() unavailable everywhere. That is the intended state. It withdraws
fork() from ARCH_ARM, flat ARCH_ARM64, ARCH_RISCV, ARCH_SIM and ARCH_X86_64,
where until now it named the sharing primitive; per-architecture patches
restore it, with POSIX semantics, as up_addrenv_fork() lands. In the
meantime the sharing primitive is still there under the name that describes
it: vfork() for a child that runs a program, pthread_create() for a second
flow of control that shares memory, posix_spawn() for both at once.
Kconfig: ARCH_HAVE_VFORK inherits ARCH_HAVE_FORK's select lines, conditions
included, so no configuration gains machinery; ARCH_HAVE_FORK is redefined to
mean "can provide POSIX fork() semantics" and now depends on ARCH_ADDRENV.
There is one deliberate departure from "verbatim". ARCH_ARM selected the
fork family unconditionally, BUILD_KERNEL included, and that has never
worked: on a kernel build the architecture's fork entry point sees the
kernel's return address and stack pointer rather than the caller's, so the
child resumes at a kernel address. On qemu-armv7a:knsh master faults in
ostest's fork case with "Child did not run" and then a data abort; without
the condition this change faults the same way through vfork(). ARCH_ARM64
and ARCH_X86_64 already carried "if !BUILD_KERNEL" for exactly this reason --
ARM was the outlier. Conditioning it turns a runtime fault into an honest
absence, which is the whole point of the change; arch/arm takes the condition
off again in the patch that adds its saved-syscall-frame path. Only the
MMU-capable ARM ports are affected, since Cortex-M cannot build BUILD_KERNEL
at all.
Also fixes two latent syntax errors found on the way: a missing comma in
riscv_fork.c and mips_fork.c, both in *_FRAMEPOINTER && !SAVE_GP branches
that are never compiled today.
BREAKING CHANGE: fork() is withdrawn from every architecture. It is no
longer declared in unistd.h, so code that calls it fails to build with an error
naming the function, and the sharing behaviour it used to have is gone rather
than renamed. CONFIG_ARCH_HAVE_FORK no longer means "fork() exists"; it means
"this configuration can provide POSIX fork() semantics", and no architecture
selects it yet.
Quick fix, chosen by why the call was made:
to run a program vfork() + exec*(), or better posix_spawn()
a second flow of control that pthread_create()
shares the caller's memory
a genuinely independent copy keep fork(), and wait for the per-arch patch
of the process that implements up_addrenv_fork() and selects
CONFIG_ARCH_HAVE_FORK
Out-of-tree code that tests CONFIG_ARCH_HAVE_FORK to decide whether a
fork-then-exec path is available wants CONFIG_ARCH_HAVE_VFORK instead, which is
selected in exactly the places CONFIG_ARCH_HAVE_FORK used to be. The full
migration guide is Documentation/guides/fork_vfork_migration.rst.
Assisted-by: Claude Code:claude-opus-5
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
This change fixes NuttX’s CMake support when NuttX is embedded
in another project via add_subdirectory(). CMake’s CMAKE_SOURCE_DIR
and CMAKE_BINARY_DIR refer to the outermost project, causing NuttX
to access its .config, generated files, host tools, and build artifacts
in the parent project’s directories. The fix introduces NUTTX_DIR and
NUTTX_BINARY_DIR, based on CMAKE_CURRENT_SOURCE_DIR and
CMAKE_CURRENT_BINARY_DIR, and consistently uses them for NuttX
self-references while preserving existing standalone builds. It fixes
the Kconfig initialization failure reported in #19697 and allows an
embedded sim:nsh build to configure, build, and boot successfully.
The change affects only the CMake build system (not Make or Kconfig
defaults), requires the corresponding nuttx-apps change, and does not
extend add_subdirectory() support to cross-compiled non-sim boards due
to CMake’s toolchain-file limitation.
Fixes#19697.
Assisted-by: Claude:claude-sonnet-5
Signed-off-by: Alan Carvalho de Assis <acassis@gmail.com>
A mutex records its holder as a task id in the low 31 bits of a word
whose top bit means "someone is blocked on this". The id was stored
without masking, so an id with its top bit set became a holder with the
blocking bit raised.
Task ids are normally small and positive, but not always.
nxsched_gettid() reports -ESRCH for a context that no longer maps to a
running task, and there is a window where that is exactly what the
running context is: nxtask_exit() marks the next task ready to run
while the dying task is still executing on its own stack, and only then
releases the TCB. Freeing the group inside that release takes and
drops the group's mutexes, so the lock stores 0xfffffffd and the unlock
compares 0x7ffffffd, which are not equal.
With assertions enabled the unlock trips its holder check, and every
exit of a process that frees memory panics. In a kernel build that is
every exit, so no program could be run twice, and running one at all
took the shell down with it. Without assertions the failure is silent:
the accidental blocking bit sends the unlock looking for a waiter that
never existed.
Encode the id the same way everywhere it is stored or compared, so that
a lock and an unlock from one context agree whatever the id's sign.
The masked forms of -1 and -2 would alias the "no holder" and "reset"
values, but nxsched_gettid() yields only valid ids and -ESRCH.
mm_lock() already sidesteps this window with a note that gettid() may
return -ESRCH during a context switch; this gives the generic mutex the
same footing rather than a second special case.
Test case, on the EIC7700 EVB, which is a kernel build with assertions:
nsh> hello
Hello, World!!
Before, that printed and then panicked in sem_post, taking the shell
with it, every time. After, five runs in a row complete and the shell
survives. ps over telnet still completes.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
dns_recv_response() checked for room using sizeof(struct dns_answer_s),
but that structure is the 10-byte header plus a union holding the largest
address it can carry. With IPv6 built the union is 16 bytes, so the check
demanded 26 bytes where 10 were needed, and any answer sitting at the end
of a response was rejected as truncated.
An A record answer supplies 14 bytes, so whether a lookup worked depended
on how much padding the server happened to send after it:
$ dig +noedns @10.1.1.2 github.com A # ANSWER 1, AUTHORITY 0, ADDITIONAL 0
-> answer is last in the packet, 14 bytes remain, rejected
$ dig +noedns @10.11.5.254 github.com A # ANSWER 1, AUTHORITY 13, ADDITIONAL 7
-> 26+ bytes remain, accepted
On the board, before and after, against the first of those servers:
nsh> nslookup apache.org
[CPU1] dns_recv_response: DNS answer header truncated
Host: apache.org Addr: 2a04:4e42::644 <- A record lost
nsh> nslookup apache.org
Host: apache.org Addr: 2a04:4e42::644
Host: apache.org Addr: 151.101.2.132 <- both returned
The address that follows the header is already bounds checked separately,
where its real length is known, so only the header check was wrong. The
size is now a named constant next to the structure, since the rest of this
function already used the literal 10 for the same quantity.
Only IPv4-only builds escaped it, where sizeof happens to equal 14 and an
A record fits exactly.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Justin Hammond <justin@dynam.ac>
allsyms_lookup() derived a symbol's size from the physically next table
entry, assuming address order. Under CONFIG_SYMTAB_ORDEREDBYNAME the
table is sorted by name instead, producing a huge bogus size in
%pS/backtrace output.
Scan for the closest larger address instead of relying on table order.
Signed-off-by: liang.huang <liang.huang@houmo.ai>
Assisted-by: Claude Code:claude-sonnet-5
allsyms_findbyvalue()/%pS printed a bogus name/offset for addresses
outside the real symbol table's coverage, due to the boundary sentinels
being matchable as real symbols.
Compute the high sentinel from the actual symbol range and treat a
sentinel match as "not found".
Signed-off-by: liang.huang <liang.huang@houmo.ai>
Assisted-by: Claude Code:claude-sonnet-5
g_allsyms/g_nallsyms only exist in the kernel image, but symtab_allsyms.c
is unconditionally built into libc.a, so user-mode code under
CONFIG_BUILD_PROTECTED/CONFIG_BUILD_KERNEL fails to link.
Guard the affected code so user-mode libc.a no longer references these
kernel-only symbols.
Signed-off-by: liang.huang <liang.huang@houmo.ai>
Assisted-by: Claude Code:claude-sonnet-5
dlopen() of a library that is already loaded fails. libelf_insert()
rejects a name that is already in the module registry with EEXIST, and
dlinsert() passes that straight out, so the second caller gets NULL.
POSIX says dlopen() shall return a handle to the object, and there is no
way today for two modules to hold the same library at once -- which is
what a shared library is for.
So dlopen() now takes another reference on a library that is already
there, and dlclose() only tears it down when the last handle goes. The
count lives in the dlfcn layer rather than in libelf_insert() so that
insmod keeps its own behaviour: a second insmod of the same name still
fails with EEXIST, which is right for a kernel module.
The module name is what makes any of this possible, and a PROTECTED build
did not have one. Names were defined for CONFIG_BUILD_FLAT or the kernel
side of a split build, on the reasoning that only the kernel needed them,
which predates dlopen() being usable from user space. Without a name the
user-space copy of libelf cannot recognise a second open of a library,
cannot count opens, and cannot make dlclose() mean anything -- two
dlopen()s there produce two independent copies of the library and lose
track of the first. Names are therefore defined wherever CONFIG_LIBC_DLFCN
is, which costs NAME_MAX per loaded module in that configuration.
The path no longer has to be copied either. The module name is the
basename of the file and libelf_insert() takes it as a const string, so
dlinsert() finds it with strrchr() instead of handing a writable
duplicate of the whole path to basename().
BUILD_KERNEL is deliberately untouched. dlopen() returns NULL there
unconditionally: dlinsert() is a stub, because sharing a library between
processes with separate address spaces needs the text in a shared region
and the data per process at a matching virtual address, which is a
different problem from this one.
Built for mps3-an547:picostest with and without CONFIG_LIBC_DLFCN, and
for stm32f4discovery:kostest, a PROTECTED configuration, with it enabled.
Assisted-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Marco Casaroli <marco.casaroli@gmail.com>
Perform pseudo-filesystem permission checks inside inode_reserve() and
inode_remove() while the inode tree lock is held, and hold that lock across
pseudorename mutations so symlink swaps cannot bypass directory checks.
Hold a read lock around pseudo-fs open permission checks.
On setuid/setgid exec, update saved set-IDs, mark the task group secure,
sanitize dangerous environment variables, clear debug/dumpable flags, and
add issetugid(), secure_getenv(), and PR_SET/GET_DUMPABLE support.
Signed-off-by: Abhishek Mishra <mishra.abhishek2808@gmail.com>
The strto* interfaces document explicit bases in the range 2 through 36, and lib_isbasedigit() already supports alphabetic digits through base 36. However, lib_checkbase() rejects every explicit base above 26 with EINVAL.
Raise the validation limit to 36 so the conversion interfaces accept the full documented range while continuing to reject base 37 and above.
Signed-off-by: hanzhijian <hanzhijian@zepp.com>