* [PATCH 0/4] nolibc: syscall() and abs() fixes, plus a cleanup
@ 2026-07-26 10:13 Ammar Faizi
2026-07-26 10:13 ` [PATCH 1/4] tools/nolibc: evaluate syscall() arguments before the arch macros Ammar Faizi
` (3 more replies)
0 siblings, 4 replies; 10+ messages in thread
From: Ammar Faizi @ 2026-07-26 10:13 UTC (permalink / raw)
To: Willy Tarreau, Thomas Weißschuh
Cc: Ammar Faizi, Linux Kernel Mailing List,
Linux Kselftest Mailing List, LLVM Mailing List, Yichun Zhang,
Alviro Iskandar Setiawan, Shuah Khan, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, gwml
Hi,
A small set of fixes and a cleanup. Tested on x86-64 and i386.
This contains:
- Evaluate syscall() arguments before they reach the arch macros. The
__nolibc_syscallN() macros drop their arguments into local register
variables, which GCC does not guarantee to survive a function call,
so an argument such as strlen(msg) clobbers the registers that were
already set up and the wrong syscall is made. Reproduced with gcc on
i386 and x86-64 at -O0, -O1, -O2, -O3 and -Os.
- Avoid signed overflow in abs(), labs() and llabs(): negate in the
corresponding unsigned type, so passing the type minimum no longer
invokes undefined behavior. The value is still not representable in
the result type, so the minimum is returned unchanged. Under the:
-fsanitize=undefined -fsanitize-trap=all
flags the selftests are built with, the old code did not merely
return an unspecified answer, it died with SIGILL (thrown by ud2).
- Add an abs() range test walking abs(), labs() and llabs() over the
interesting points of their argument type: the minimum, the minimum
plus one, an ordinary negative value, zero and the maximum.
- Remove the dead __ARCH_WANT_SYS_OLD_SELECT from arch-x86.h and
arch-arm.h. It lost its last user when select() was reimplemented on
top of pselect6 for every architecture.
Patches 1 and 2 are fixes. Patch 3 is the selftest that covers patch 2,
and patch 4 is a pure cleanup.
Signed-off-by: Ammar Faizi <ammarfaizi2@openresty.com>
---
Ammar Faizi (4):
tools/nolibc: evaluate syscall() arguments before the arch macros
tools/nolibc: stdlib: avoid signed overflow in abs() and friends
selftests/nolibc: add abs() range test
tools/nolibc: remove dead __ARCH_WANT_SYS_OLD_SELECT
tools/include/nolibc/arch-arm.h | 3 -
tools/include/nolibc/arch-x86.h | 3 -
tools/include/nolibc/stdlib.h | 12 +++-
tools/include/nolibc/sys/syscall.h | 72 +++++++++++++++++++-
tools/testing/selftests/nolibc/nolibc-test.c | 47 +++++++++++++
5 files changed, 127 insertions(+), 10 deletions(-)
base-commit: 070cf03f5ec454eec9c42827ee9f3c0b3fad09f1
--
Ammar Faizi
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH 1/4] tools/nolibc: evaluate syscall() arguments before the arch macros 2026-07-26 10:13 [PATCH 0/4] nolibc: syscall() and abs() fixes, plus a cleanup Ammar Faizi @ 2026-07-26 10:13 ` Ammar Faizi 2026-07-26 20:16 ` Thomas Weißschuh 2026-07-26 10:13 ` [PATCH 2/4] tools/nolibc: stdlib: avoid signed overflow in abs() and friends Ammar Faizi ` (2 subsequent siblings) 3 siblings, 1 reply; 10+ messages in thread From: Ammar Faizi @ 2026-07-26 10:13 UTC (permalink / raw) To: Willy Tarreau, Thomas Weißschuh Cc: Ammar Faizi, Linux Kernel Mailing List, Linux Kselftest Mailing List, LLVM Mailing List, Yichun Zhang, Alviro Iskandar Setiawan, Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt, gwml Per the GCC docs, local `register` variables aren't guaranteed to survive a function call, and you shouldn't put code between the register assignment and the `asm` block: https://gcc.gnu.org/onlinedocs/gcc/Local-Register-Variables.html The __nolibc_syscallN() macros put their arguments into local register variables. syscall() drops caller expressions straight into those macros, so this: static char msg[] = "Hello, World!\n"; syscall(__NR_write, 1, msg, strlen(msg)); runs the wrong syscall. On x86-64, GCC doesn't reload %rax, %rdi, and %rsi after the strlen() call, so only %rdx ends up correct. Fix it by evaluating the arguments into temporaries first, before they reach the arch macros. Before this patch (bug): ``` 0000000000401000 <main>: pushq %rcx movl $0x403000,%edi callq 401153 <strlen> # strlen() clobbers %rax, %rdi, %rsi. movq %rax,%rdx # BUG: %rax, %rdi, %rsi are wrong. syscall # Only %rdx is correct. [...] popq %rdx retq ``` After this patch (fixed): ``` 0000000000401000 <main>: pushq %rcx movl $0x403000,%edi callq 40116c <strlen> movl $0x1,%edi # %rdi = 1 (stdout) movl $0x403000,%esi # %rsi = msg movq %rax,%rdx # %rdx = strlen(msg) movl $0x1,%eax # %rax = __NR_write syscall [...] popq %rdx retq ``` Reproduced with gcc on i386 and x86-64 at -O0, -O1, -O2, -O3 and -Os. Found this bug after a chat with Alviro. I also attempted to report a similar problem to the GCC bugzilla: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126290 The solution from GNU developers is: use hard register constraints (introduced in GCC 16) to reliably force asm operands into specific registers, though Clang hasn't merged its version yet: https://gcc.gnu.org/onlinedocs/gcc-16.1.0/gcc/Hard-Register-Constraints.html https://github.com/llvm/llvm-project/pull/85846 Once hard register constraints are widely available (in the future), a reliable inline asm for a syscall may look like this: ``` #define syscall6(N, ARG1, ARG2, ARG3, ARG4, ARG5, ARG6) ({ \ long long ret = (N); \ __asm__ volatile( \ "syscall" \ : "+{rax}"(ret) \ : "{rdi}"(ARG1), \ "{rsi}"(ARG2), \ "{rdx}"(ARG3), \ "{r10}"(ARG4), \ "{r8}"(ARG5), \ "{r9}"(ARG6) \ : "rcx", "r11", "memory"); \ (ret); \ }) ``` Cc: Yichun Zhang <yichun@openresty.com> Fixes: 53fcfafa8c5c ("tools/nolibc/unistd: add syscall()") Reported-by: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org> Signed-off-by: Ammar Faizi <ammarfaizi2@openresty.com> --- tools/include/nolibc/sys/syscall.h | 72 +++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/tools/include/nolibc/sys/syscall.h b/tools/include/nolibc/sys/syscall.h index 7f06314fcf0d..b91b82846fe4 100644 --- a/tools/include/nolibc/sys/syscall.h +++ b/tools/include/nolibc/sys/syscall.h @@ -10,9 +10,79 @@ #ifndef _NOLIBC_SYS_SYSCALL_H #define _NOLIBC_SYS_SYSCALL_H +/* + * The __nolibc_syscallN() macros assign their arguments to local register + * variables. A compiler only has to keep such a variable in its register + * right before the asm statement it feeds, so an argument expression which + * contains a function call gets evaluated once the earlier arguments already + * sit in their registers, and the call then clobbers them. + * + * Caller-supplied expressions enter here, so bind them to temporaries first + * and only hand plain variables over. __auto_type preserves the original + * type, so nothing gets truncated on the way. + */ +#define __nolibc_syscall_eval0(_n) \ +({ \ + __auto_type __sc_n = (_n); \ + __nolibc_syscall0(__sc_n); \ +}) +#define __nolibc_syscall_eval1(_n, _a1) \ +({ \ + __auto_type __sc_n = (_n); \ + __auto_type __sc_a1 = (_a1); \ + __nolibc_syscall1(__sc_n, __sc_a1); \ +}) +#define __nolibc_syscall_eval2(_n, _a1, _a2) \ +({ \ + __auto_type __sc_n = (_n); \ + __auto_type __sc_a1 = (_a1); \ + __auto_type __sc_a2 = (_a2); \ + __nolibc_syscall2(__sc_n, __sc_a1, __sc_a2); \ +}) +#define __nolibc_syscall_eval3(_n, _a1, _a2, _a3) \ +({ \ + __auto_type __sc_n = (_n); \ + __auto_type __sc_a1 = (_a1); \ + __auto_type __sc_a2 = (_a2); \ + __auto_type __sc_a3 = (_a3); \ + __nolibc_syscall3(__sc_n, __sc_a1, __sc_a2, __sc_a3); \ +}) +#define __nolibc_syscall_eval4(_n, _a1, _a2, _a3, _a4) \ +({ \ + __auto_type __sc_n = (_n); \ + __auto_type __sc_a1 = (_a1); \ + __auto_type __sc_a2 = (_a2); \ + __auto_type __sc_a3 = (_a3); \ + __auto_type __sc_a4 = (_a4); \ + __nolibc_syscall4(__sc_n, __sc_a1, __sc_a2, __sc_a3, __sc_a4); \ +}) +#define __nolibc_syscall_eval5(_n, _a1, _a2, _a3, _a4, _a5) \ +({ \ + __auto_type __sc_n = (_n); \ + __auto_type __sc_a1 = (_a1); \ + __auto_type __sc_a2 = (_a2); \ + __auto_type __sc_a3 = (_a3); \ + __auto_type __sc_a4 = (_a4); \ + __auto_type __sc_a5 = (_a5); \ + __nolibc_syscall5(__sc_n, __sc_a1, __sc_a2, __sc_a3, __sc_a4, \ + __sc_a5); \ +}) +#define __nolibc_syscall_eval6(_n, _a1, _a2, _a3, _a4, _a5, _a6) \ +({ \ + __auto_type __sc_n = (_n); \ + __auto_type __sc_a1 = (_a1); \ + __auto_type __sc_a2 = (_a2); \ + __auto_type __sc_a3 = (_a3); \ + __auto_type __sc_a4 = (_a4); \ + __auto_type __sc_a5 = (_a5); \ + __auto_type __sc_a6 = (_a6); \ + __nolibc_syscall6(__sc_n, __sc_a1, __sc_a2, __sc_a3, __sc_a4, \ + __sc_a5, __sc_a6); \ +}) + #define ___nolibc_syscall_narg(_0, _1, _2, _3, _4, _5, _6, N, ...) N #define __nolibc_syscall_narg(...) ___nolibc_syscall_narg(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0) -#define __nolibc_syscall(N, ...) __nolibc_syscall##N(__VA_ARGS__) +#define __nolibc_syscall(N, ...) __nolibc_syscall_eval##N(__VA_ARGS__) #define __nolibc_syscall_n(N, ...) __nolibc_syscall(N, __VA_ARGS__) #define _syscall(...) __nolibc_syscall_n(__nolibc_syscall_narg(__VA_ARGS__), ##__VA_ARGS__) #define syscall(...) __sysret(_syscall(__VA_ARGS__)) -- Ammar Faizi ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 1/4] tools/nolibc: evaluate syscall() arguments before the arch macros 2026-07-26 10:13 ` [PATCH 1/4] tools/nolibc: evaluate syscall() arguments before the arch macros Ammar Faizi @ 2026-07-26 20:16 ` Thomas Weißschuh 0 siblings, 0 replies; 10+ messages in thread From: Thomas Weißschuh @ 2026-07-26 20:16 UTC (permalink / raw) To: Ammar Faizi Cc: Willy Tarreau, Linux Kernel Mailing List, Linux Kselftest Mailing List, LLVM Mailing List, Yichun Zhang, Alviro Iskandar Setiawan, Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt, gwml Thanks! On 2026-07-26 17:13:02+0700, Ammar Faizi wrote: (...) > +#define __nolibc_syscall_eval6(_n, _a1, _a2, _a3, _a4, _a5, _a6) \ > +({ \ > + __auto_type __sc_n = (_n); \ > + __auto_type __sc_a1 = (_a1); \ > + __auto_type __sc_a2 = (_a2); \ > + __auto_type __sc_a3 = (_a3); \ > + __auto_type __sc_a4 = (_a4); \ > + __auto_type __sc_a5 = (_a5); \ > + __auto_type __sc_a6 = (_a6); \ __auto_type is only supported from GCC 4.9. I think this is old enough, but it should be mentioned at least. We really should have a documented policy for that. > + __nolibc_syscall6(__sc_n, __sc_a1, __sc_a2, __sc_a3, __sc_a4, \ > + __sc_a5, __sc_a6); \ > +}) > + > #define ___nolibc_syscall_narg(_0, _1, _2, _3, _4, _5, _6, N, ...) N > #define __nolibc_syscall_narg(...) ___nolibc_syscall_narg(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0) > -#define __nolibc_syscall(N, ...) __nolibc_syscall##N(__VA_ARGS__) > +#define __nolibc_syscall(N, ...) __nolibc_syscall_eval##N(__VA_ARGS__) I'd like to apply the same thing to the __nolibc_syscallN() usage within nolibc itself. While today we seem not to have any problematic cases, at least I was not aware of the issue and breakage might creep in accidentally. We can problably rename the architecture-specific macros to __nolibc_syscall_archN() and make __nolibc_syscall() the properly evaluating wrapper. > #define __nolibc_syscall_n(N, ...) __nolibc_syscall(N, __VA_ARGS__) > #define _syscall(...) __nolibc_syscall_n(__nolibc_syscall_narg(__VA_ARGS__), ##__VA_ARGS__) > #define syscall(...) __sysret(_syscall(__VA_ARGS__)) Thomas ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 2/4] tools/nolibc: stdlib: avoid signed overflow in abs() and friends 2026-07-26 10:13 [PATCH 0/4] nolibc: syscall() and abs() fixes, plus a cleanup Ammar Faizi 2026-07-26 10:13 ` [PATCH 1/4] tools/nolibc: evaluate syscall() arguments before the arch macros Ammar Faizi @ 2026-07-26 10:13 ` Ammar Faizi 2026-07-26 14:13 ` David Laight 2026-07-26 10:13 ` [PATCH 3/4] selftests/nolibc: add abs() range test Ammar Faizi 2026-07-26 10:13 ` [PATCH 4/4] tools/nolibc: remove dead __ARCH_WANT_SYS_OLD_SELECT Ammar Faizi 3 siblings, 1 reply; 10+ messages in thread From: Ammar Faizi @ 2026-07-26 10:13 UTC (permalink / raw) To: Willy Tarreau, Thomas Weißschuh Cc: Ammar Faizi, Linux Kernel Mailing List, Linux Kselftest Mailing List, LLVM Mailing List, Yichun Zhang, Alviro Iskandar Setiawan, Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt, gwml Negating the smallest negative value of a signed type overflows, which is undefined behavior. The selftests are built with: -fsanitize=undefined -fsanitize-trap=all so a caller passing INT_MIN does not merely get an unspecified answer, it dies (on both x86-64 and i386): A simple test program: printf("x = %d\n", abs(INT_MIN)); $ ./ab Illegal instruction (core dumped) (gdb) bt #0 0x0000000000401009 in main () (gdb) x/6i main 0x401000 <main>: mov $0x80000000,%eax 0x401005 <main+5>: neg %eax 0x401007 <main+7>: jno 0x40100b <main+11> => 0x401009 <main+9>: ud2 0x40100b <main+11>: push %rax 0x40100c <main+12>: mov $0x80000000,%esi Negate in the corresponding unsigned type instead. The value still cannot be represented in the result type, so the minimum is returned unchanged. Cc: Yichun Zhang <yichun@openresty.com> Cc: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org> Fixes: bf5e8a78bede ("tools/nolibc: add abs() and friends") Signed-off-by: Ammar Faizi <ammarfaizi2@openresty.com> --- tools/include/nolibc/stdlib.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/tools/include/nolibc/stdlib.h b/tools/include/nolibc/stdlib.h index 1816c2368b68..8d86044f759f 100644 --- a/tools/include/nolibc/stdlib.h +++ b/tools/include/nolibc/stdlib.h @@ -32,22 +32,28 @@ static __attribute__((unused)) char itoa_buffer[21]; * As much as possible, please keep functions alphabetically sorted. */ +/* + * The absolute value of the smallest negative value is not representable in + * the result type. Negate in the unsigned type so that the overflow is + * defined and return it unchanged, like the other libcs do. + */ + static __inline__ int abs(int j) { - return j >= 0 ? j : -j; + return j >= 0 ? j : (int)-(unsigned int)j; } static __inline__ long labs(long j) { - return j >= 0 ? j : -j; + return j >= 0 ? j : (long)-(unsigned long)j; } static __inline__ long long llabs(long long j) { - return j >= 0 ? j : -j; + return j >= 0 ? j : (long long)-(unsigned long long)j; } /* must be exported, as it's used by libgcc for various divide functions */ -- Ammar Faizi ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 2/4] tools/nolibc: stdlib: avoid signed overflow in abs() and friends 2026-07-26 10:13 ` [PATCH 2/4] tools/nolibc: stdlib: avoid signed overflow in abs() and friends Ammar Faizi @ 2026-07-26 14:13 ` David Laight 2026-07-26 16:01 ` Willy Tarreau 0 siblings, 1 reply; 10+ messages in thread From: David Laight @ 2026-07-26 14:13 UTC (permalink / raw) To: Ammar Faizi Cc: Willy Tarreau, Thomas Weißschuh, Linux Kernel Mailing List, Linux Kselftest Mailing List, LLVM Mailing List, Yichun Zhang, Alviro Iskandar Setiawan, Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt, gwml On Sun, 26 Jul 2026 17:13:03 +0700 Ammar Faizi <ammarfaizi2@openresty.com> wrote: > Negating the smallest negative value of a signed type overflows, which > is undefined behavior. The selftests are built with: > > -fsanitize=undefined -fsanitize-trap=all > > so a caller passing INT_MIN does not merely get an unspecified answer, > it dies (on both x86-64 and i386): > > A simple test program: > > printf("x = %d\n", abs(INT_MIN)); > > $ ./ab > Illegal instruction (core dumped) > > (gdb) bt > #0 0x0000000000401009 in main () > (gdb) x/6i main > 0x401000 <main>: mov $0x80000000,%eax > 0x401005 <main+5>: neg %eax > 0x401007 <main+7>: jno 0x40100b <main+11> > => 0x401009 <main+9>: ud2 > 0x40100b <main+11>: push %rax > 0x40100c <main+12>: mov $0x80000000,%esi > > Negate in the corresponding unsigned type instead. The value still > cannot be represented in the result type, so the minimum is returned > unchanged. > > Cc: Yichun Zhang <yichun@openresty.com> > Cc: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org> > Fixes: bf5e8a78bede ("tools/nolibc: add abs() and friends") > Signed-off-by: Ammar Faizi <ammarfaizi2@openresty.com> > --- > tools/include/nolibc/stdlib.h | 12 +++++++++--- > 1 file changed, 9 insertions(+), 3 deletions(-) > > diff --git a/tools/include/nolibc/stdlib.h b/tools/include/nolibc/stdlib.h > index 1816c2368b68..8d86044f759f 100644 > --- a/tools/include/nolibc/stdlib.h > +++ b/tools/include/nolibc/stdlib.h > @@ -32,22 +32,28 @@ static __attribute__((unused)) char itoa_buffer[21]; > * As much as possible, please keep functions alphabetically sorted. > */ > > +/* > + * The absolute value of the smallest negative value is not representable in > + * the result type. Negate in the unsigned type so that the overflow is > + * defined and return it unchanged, like the other libcs do. > + */ > + > static __inline__ > int abs(int j) > { > - return j >= 0 ? j : -j; > + return j >= 0 ? j : (int)-(unsigned int)j; An alternative expression is -(j + 1) - 1 gcc (and I think clang) optimise it to just -j. David > } > > static __inline__ > long labs(long j) > { > - return j >= 0 ? j : -j; > + return j >= 0 ? j : (long)-(unsigned long)j; > } > > static __inline__ > long long llabs(long long j) > { > - return j >= 0 ? j : -j; > + return j >= 0 ? j : (long long)-(unsigned long long)j; > } > > /* must be exported, as it's used by libgcc for various divide functions */ ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH 2/4] tools/nolibc: stdlib: avoid signed overflow in abs() and friends 2026-07-26 14:13 ` David Laight @ 2026-07-26 16:01 ` Willy Tarreau 0 siblings, 0 replies; 10+ messages in thread From: Willy Tarreau @ 2026-07-26 16:01 UTC (permalink / raw) To: David Laight Cc: Ammar Faizi, Thomas Weißschuh, Linux Kernel Mailing List, Linux Kselftest Mailing List, LLVM Mailing List, Yichun Zhang, Alviro Iskandar Setiawan, Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt, gwml On Sun, Jul 26, 2026 at 03:13:34PM +0100, David Laight wrote: > On Sun, 26 Jul 2026 17:13:03 +0700 > Ammar Faizi <ammarfaizi2@openresty.com> wrote: > > > Negating the smallest negative value of a signed type overflows, which > > is undefined behavior. The selftests are built with: > > > > -fsanitize=undefined -fsanitize-trap=all > > > > so a caller passing INT_MIN does not merely get an unspecified answer, > > it dies (on both x86-64 and i386): > > > > A simple test program: > > > > printf("x = %d\n", abs(INT_MIN)); > > > > $ ./ab > > Illegal instruction (core dumped) > > > > (gdb) bt > > #0 0x0000000000401009 in main () > > (gdb) x/6i main > > 0x401000 <main>: mov $0x80000000,%eax > > 0x401005 <main+5>: neg %eax > > 0x401007 <main+7>: jno 0x40100b <main+11> > > => 0x401009 <main+9>: ud2 > > 0x40100b <main+11>: push %rax > > 0x40100c <main+12>: mov $0x80000000,%esi > > > > Negate in the corresponding unsigned type instead. The value still > > cannot be represented in the result type, so the minimum is returned > > unchanged. > > > > Cc: Yichun Zhang <yichun@openresty.com> > > Cc: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org> > > Fixes: bf5e8a78bede ("tools/nolibc: add abs() and friends") > > Signed-off-by: Ammar Faizi <ammarfaizi2@openresty.com> > > --- > > tools/include/nolibc/stdlib.h | 12 +++++++++--- > > 1 file changed, 9 insertions(+), 3 deletions(-) > > > > diff --git a/tools/include/nolibc/stdlib.h b/tools/include/nolibc/stdlib.h > > index 1816c2368b68..8d86044f759f 100644 > > --- a/tools/include/nolibc/stdlib.h > > +++ b/tools/include/nolibc/stdlib.h > > @@ -32,22 +32,28 @@ static __attribute__((unused)) char itoa_buffer[21]; > > * As much as possible, please keep functions alphabetically sorted. > > */ > > > > +/* > > + * The absolute value of the smallest negative value is not representable in > > + * the result type. Negate in the unsigned type so that the overflow is > > + * defined and return it unchanged, like the other libcs do. > > + */ > > + > > static __inline__ > > int abs(int j) > > { > > - return j >= 0 ? j : -j; > > + return j >= 0 ? j : (int)-(unsigned int)j; > > An alternative expression is -(j + 1) - 1 > gcc (and I think clang) optimise it to just -j. This one would give -j -2, but ~(j - 1) would work, just like (~j + 1). However here the benefit of the casts in Ammar's version is that it's obvious that it's only playing with same size casts with no extra operation. Willy ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 3/4] selftests/nolibc: add abs() range test 2026-07-26 10:13 [PATCH 0/4] nolibc: syscall() and abs() fixes, plus a cleanup Ammar Faizi 2026-07-26 10:13 ` [PATCH 1/4] tools/nolibc: evaluate syscall() arguments before the arch macros Ammar Faizi 2026-07-26 10:13 ` [PATCH 2/4] tools/nolibc: stdlib: avoid signed overflow in abs() and friends Ammar Faizi @ 2026-07-26 10:13 ` Ammar Faizi 2026-07-26 20:00 ` Thomas Weißschuh 2026-07-26 10:13 ` [PATCH 4/4] tools/nolibc: remove dead __ARCH_WANT_SYS_OLD_SELECT Ammar Faizi 3 siblings, 1 reply; 10+ messages in thread From: Ammar Faizi @ 2026-07-26 10:13 UTC (permalink / raw) To: Willy Tarreau, Thomas Weißschuh Cc: Ammar Faizi, Linux Kernel Mailing List, Linux Kselftest Mailing List, LLVM Mailing List, Yichun Zhang, Alviro Iskandar Setiawan, Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt, gwml The existing abs/abs_noop cases only cover abs(-10) and abs(10), and labs() and llabs() have no coverage at all. Nothing exercises the type minimum, where negating the argument overflows. Add a case walking all three functions over the interesting points of their argument type: the minimum, the minimum plus one, an ordinary negative value, zero and the maximum. At the minimum the absolute value is not representable and the argument is returned unchanged. Both the arguments and the results have to be hidden from the optimizer. The compiler knows these functions never return a negative value, so it folds the comparisons at build time otherwise, and that would equally hide the overflow being tested for. Note that the overflow itself is only reported through the: -fsanitize=undefined -fsanitize-trap=all flags the suite already builds with; without them the wrapped result is the same as the correct one. With the flags, and with the preceding fix reverted, the run dies with SIGILL on this test on both i386 and x86-64. Cc: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org> Signed-off-by: Ammar Faizi <ammarfaizi2@openresty.com> --- tools/testing/selftests/nolibc/nolibc-test.c | 47 ++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c index 996e8d13508e..4dc2ffea63bd 100644 --- a/tools/testing/selftests/nolibc/nolibc-test.c +++ b/tools/testing/selftests/nolibc/nolibc-test.c @@ -1728,6 +1728,52 @@ int test_alloca(void) return *x - 0x1234; } +/* abs(), labs() and llabs() over the whole range of their argument type */ +int test_abs_range(void) +{ + int i, ri; + long l, rl; + long long ll, rll; + + /* + * Both the inputs and the results have to stay opaque: the compiler + * knows abs() and friends never return a negative value and would + * otherwise fold the comparisons below at build time, which would also + * hide the undefined behavior that is being tested for. + */ + i = INT_MIN; l = LONG_MIN; ll = LLONG_MIN; + __asm__ ("" : "+r" (i), "+r" (l), "+r" (ll)); + ri = abs(i); rl = labs(l); rll = llabs(ll); + __asm__ ("" : "+r" (ri), "+r" (rl), "+r" (rll)); + /* the absolute value is not representable, the input is returned */ + if (ri != INT_MIN || rl != LONG_MIN || rll != LLONG_MIN) + return 1; + + i = INT_MIN + 1; l = LONG_MIN + 1; ll = LLONG_MIN + 1; + __asm__ ("" : "+r" (i), "+r" (l), "+r" (ll)); + ri = abs(i); rl = labs(l); rll = llabs(ll); + __asm__ ("" : "+r" (ri), "+r" (rl), "+r" (rll)); + if (ri != INT_MAX || rl != LONG_MAX || rll != LLONG_MAX) + return 2; + + i = -42; l = -42; ll = -42; + __asm__ ("" : "+r" (i), "+r" (l), "+r" (ll)); + if (abs(i) != 42 || labs(l) != 42 || llabs(ll) != 42) + return 3; + + i = 0; l = 0; ll = 0; + __asm__ ("" : "+r" (i), "+r" (l), "+r" (ll)); + if (abs(i) != 0 || labs(l) != 0 || llabs(ll) != 0) + return 4; + + i = INT_MAX; l = LONG_MAX; ll = LLONG_MAX; + __asm__ ("" : "+r" (i), "+r" (l), "+r" (ll)); + if (abs(i) != INT_MAX || labs(l) != LONG_MAX || llabs(ll) != LLONG_MAX) + return 5; + + return 0; +} + int test_difftime(void) { if (difftime(200., 100.) != 100.) @@ -1943,6 +1989,7 @@ int run_stdlib(int min, int max) CASE_TEST(toupper_noop); EXPECT_EQ(1, toupper('A'), 'A'); break; CASE_TEST(abs); EXPECT_EQ(1, abs(-10), 10); break; CASE_TEST(abs_noop); EXPECT_EQ(1, abs(10), 10); break; + CASE_TEST(abs_range); EXPECT_ZR(1, test_abs_range()); break; CASE_TEST(alloca); EXPECT_ZR(1, test_alloca()); break; CASE_TEST(difftime); EXPECT_ZR(1, test_difftime()); break; CASE_TEST(memchr_foobar6_o); EXPECT_STREQ(1, memchr("foobar", 'o', 6), "oobar"); break; -- Ammar Faizi ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 3/4] selftests/nolibc: add abs() range test 2026-07-26 10:13 ` [PATCH 3/4] selftests/nolibc: add abs() range test Ammar Faizi @ 2026-07-26 20:00 ` Thomas Weißschuh 0 siblings, 0 replies; 10+ messages in thread From: Thomas Weißschuh @ 2026-07-26 20:00 UTC (permalink / raw) To: Ammar Faizi Cc: Willy Tarreau, Linux Kernel Mailing List, Linux Kselftest Mailing List, LLVM Mailing List, Yichun Zhang, Alviro Iskandar Setiawan, Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt, gwml On 2026-07-26 17:13:04+0700, Ammar Faizi wrote: (...) > Cc: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org> > Signed-off-by: Ammar Faizi <ammarfaizi2@openresty.com> > --- > tools/testing/selftests/nolibc/nolibc-test.c | 47 ++++++++++++++++++++ > 1 file changed, 47 insertions(+) > > diff --git a/tools/testing/selftests/nolibc/nolibc-test.c b/tools/testing/selftests/nolibc/nolibc-test.c > index 996e8d13508e..4dc2ffea63bd 100644 > --- a/tools/testing/selftests/nolibc/nolibc-test.c > +++ b/tools/testing/selftests/nolibc/nolibc-test.c > @@ -1728,6 +1728,52 @@ int test_alloca(void) > return *x - 0x1234; > } > > +/* abs(), labs() and llabs() over the whole range of their argument type */ > +int test_abs_range(void) > +{ > + int i, ri; > + long l, rl; > + long long ll, rll; Reverse xmas? > + > + /* > + * Both the inputs and the results have to stay opaque: the compiler > + * knows abs() and friends never return a negative value and would > + * otherwise fold the comparisons below at build time, which would also > + * hide the undefined behavior that is being tested for. > + */ > + i = INT_MIN; l = LONG_MIN; ll = LLONG_MIN; > + __asm__ ("" : "+r" (i), "+r" (l), "+r" (ll)); We have _NOLIBC_OPTIMIZER_HIDE_VAR() for this. Maybe it can be made variadic. > + ri = abs(i); rl = labs(l); rll = llabs(ll); > + __asm__ ("" : "+r" (ri), "+r" (rl), "+r" (rll)); > + /* the absolute value is not representable, the input is returned */ > + if (ri != INT_MIN || rl != LONG_MIN || rll != LLONG_MIN) > + return 1; (...) ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH 4/4] tools/nolibc: remove dead __ARCH_WANT_SYS_OLD_SELECT 2026-07-26 10:13 [PATCH 0/4] nolibc: syscall() and abs() fixes, plus a cleanup Ammar Faizi ` (2 preceding siblings ...) 2026-07-26 10:13 ` [PATCH 3/4] selftests/nolibc: add abs() range test Ammar Faizi @ 2026-07-26 10:13 ` Ammar Faizi 2026-07-26 20:01 ` Thomas Weißschuh 3 siblings, 1 reply; 10+ messages in thread From: Ammar Faizi @ 2026-07-26 10:13 UTC (permalink / raw) To: Willy Tarreau, Thomas Weißschuh Cc: Ammar Faizi, Linux Kernel Mailing List, Linux Kselftest Mailing List, LLVM Mailing List, Yichun Zhang, Alviro Iskandar Setiawan, Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt, gwml The macro lost its last user when select() was reimplemented on top of pselect6 for every architecture; sys/select.h no longer looks at it, and nothing else in the tree does either. See commit 668e43737279 ("tools/nolibc/select: drop non-pselect based implementations"). The comment that goes with it is stale for the same reason, as neither architecture can end up calling old_select any more. Drop both from arch-x86.h and arch-arm.h. Cc: Alviro Iskandar Setiawan <alviro.iskandar@gnuweeb.org> Signed-off-by: Ammar Faizi <ammarfaizi2@openresty.com> --- tools/include/nolibc/arch-arm.h | 3 --- tools/include/nolibc/arch-x86.h | 3 --- 2 files changed, 6 deletions(-) diff --git a/tools/include/nolibc/arch-arm.h b/tools/include/nolibc/arch-arm.h index 8681922e05ca..b88686fb19c4 100644 --- a/tools/include/nolibc/arch-arm.h +++ b/tools/include/nolibc/arch-arm.h @@ -33,10 +33,7 @@ * with r7 before calling svc, and r6 is marked as clobbered. * We're just using any regular register which we assign to r7 after saving * it. - * - * Also, ARM supports the old_select syscall if newselect is not available */ -#define __ARCH_WANT_SYS_OLD_SELECT #if (defined(__THUMBEB__) || defined(__THUMBEL__)) && \ !defined(NOLIBC_OMIT_FRAME_POINTER) diff --git a/tools/include/nolibc/arch-x86.h b/tools/include/nolibc/arch-x86.h index fe152ac2650b..577e9b682eb3 100644 --- a/tools/include/nolibc/arch-x86.h +++ b/tools/include/nolibc/arch-x86.h @@ -25,10 +25,7 @@ * don't have to experience issues with register constraints. * - the syscall number is always specified last in order to allow to force * some registers before (gcc refuses a %-register at the last position). - * - * Also, i386 supports the old_select syscall if newselect is not available */ -#define __ARCH_WANT_SYS_OLD_SELECT #define __nolibc_syscall0(num) \ ({ \ -- Ammar Faizi ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH 4/4] tools/nolibc: remove dead __ARCH_WANT_SYS_OLD_SELECT 2026-07-26 10:13 ` [PATCH 4/4] tools/nolibc: remove dead __ARCH_WANT_SYS_OLD_SELECT Ammar Faizi @ 2026-07-26 20:01 ` Thomas Weißschuh 0 siblings, 0 replies; 10+ messages in thread From: Thomas Weißschuh @ 2026-07-26 20:01 UTC (permalink / raw) To: Ammar Faizi Cc: Willy Tarreau, Linux Kernel Mailing List, Linux Kselftest Mailing List, LLVM Mailing List, Yichun Zhang, Alviro Iskandar Setiawan, Shuah Khan, Nathan Chancellor, Nick Desaulniers, Bill Wendling, Justin Stitt, gwml On 2026-07-26 17:13:05+0700, Ammar Faizi wrote: > The macro lost its last user when select() was reimplemented on top of > pselect6 for every architecture; sys/select.h no longer looks at it, > and nothing else in the tree does either. See commit 668e43737279 > ("tools/nolibc/select: drop non-pselect based implementations"). > > The comment that goes with it is stale for the same reason, as neither > architecture can end up calling old_select any more. > > Drop both from arch-x86.h and arch-arm.h. Thanks, applied. ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-07-26 20:16 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-26 10:13 [PATCH 0/4] nolibc: syscall() and abs() fixes, plus a cleanup Ammar Faizi 2026-07-26 10:13 ` [PATCH 1/4] tools/nolibc: evaluate syscall() arguments before the arch macros Ammar Faizi 2026-07-26 20:16 ` Thomas Weißschuh 2026-07-26 10:13 ` [PATCH 2/4] tools/nolibc: stdlib: avoid signed overflow in abs() and friends Ammar Faizi 2026-07-26 14:13 ` David Laight 2026-07-26 16:01 ` Willy Tarreau 2026-07-26 10:13 ` [PATCH 3/4] selftests/nolibc: add abs() range test Ammar Faizi 2026-07-26 20:00 ` Thomas Weißschuh 2026-07-26 10:13 ` [PATCH 4/4] tools/nolibc: remove dead __ARCH_WANT_SYS_OLD_SELECT Ammar Faizi 2026-07-26 20:01 ` Thomas Weißschuh
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.