Skip to content

Split glibc and linux sigset_t ABIs and the accessor functions #23601

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

rootbeer
Copy link
Contributor

@rootbeer rootbeer commented Apr 18, 2025

Zig's c.zig glibc/musl layer and the linux.zig kernel syscall wrapper layers both define sigset_t. Currently linux.zig uses a definition compatible with glibc/musl, and then c.zig imports that definition. The glibc ABI defines a 1024-bit sigset_t. The linux kernel ABI generally defines a 64-bit sigset_t (or 128-bit on MIPS). At run-time, glibc (and Zig) just presents the lowest bits of the larger sigset_t to the kernel. Glibc "needs" the extra space in its ABI so it can accommodate future kernels that support a larger number of signals. Zig does not need that compatibility unless linking the C library. The linux.zig ABI should be able to define a sigset_t that matches the underlying kernel ABI.

The array of words backing sigset_t array must match the kernel's layout. Two 32-bit words are not the same as one 64-bit word on a big-endian platform. The kernel headers use unsigned long as the element, so I used c_ulong for consistency. This means the array is sometimes of 32-bit words, and sometimes made from 64-bit words. I added some tests that make sure blocking specific signals works to check we're getting the bit indexes correct. (In practice most signal numbers are less than 32, so this wasn't much of a practical problem.)

The sigfillset() call needs to go through the dynamically linked C library, when one is present. The run-time library might decide to reserve some signals. Similarly, sigaddset() might reject some signal numbers reserved by the C library. Also, Zig's pre-filled filled_sigset can't work when linking a C library, because Zig can't invoke external symbols in a static initializer (at least, that's what I understood the error I got to mean). This PR provides POSIX-like sigemptyset() and sigfillset() functions, but in Zig they return the initialized set (instead of taking the set as an output parameter). This PR also removes linux.all_mask.

Sigaction contains a sigset_t. In Zig this structure is already mapped (in the sigaction wrapper) into a k_sigaction that has a "restorer" field and an ad-hoc, right-sized signal mask field. With a properly defined sigset_t, some differences in std.os.linx.Sigaction versions collapse (e.g., the MIPS variations were just encapsulating sigset_t differences.) The Zig-only Sigaction structure doesn't need a restorer, so remove that. Also doesn't need to be an extern struct.

The context_t structure also contains a glibc-compatible sigset_t. I made that explicit in this PR, and avoided fixing it for now, as I think there are deeper changes required around this structure.

Using a u6 for the signal number is too narrow. Signal numbers can go up to 128 on some platforms. Changed to a u8 parameter since the actual boundary isn't always a power of two. Means we need to @truncate when converting signal numbers into bit masks in some places.

I am unclear about the darwin.zig changes. I'm not sure if the sigset accessors (sigaddset(), sigfillset(), etc) are defined in the Darwin C library (but I assume they are?).

Added a std.os.posix layer to hide differences (mostly just error handling) between C-library sigset manipulators and the direct linux kernel accessor. I can drop these if folks would rather not see new code added to posix, but they were handy for making tests to make sure all the implementations are consistent.

PR inspired by conversation on https://github.com/ziglang/zig/pull/23502\#discussion\_r2034064507

@alexrp alexrp self-assigned this Apr 18, 2025
@alexrp alexrp self-requested a review April 18, 2025 21:29
@rootbeer
Copy link
Contributor Author

Thanks for quick the review! I've got some test failures to dig into, in addition to your feedback to address. I'll bring this back from a draft when its ready for another look.

@rootbeer rootbeer marked this pull request as draft April 19, 2025 06:49
Export the sigset_t ops (sigaddset, etc) from the C library.  Don't rely
on the linux.zig defintions (which will be defined to use the kernel ABI).

Move Darwin sigset and NSIG declarations into darwin.zig.  Remove
extraneous (?) sigaddset.  The C library sigaddset can reject some signals
being added, so need to defer to it.
The kernel ABI sigset_t is smaller than the glibc one.  Define the
right-sized sigset_t and fixup the sigaction() wrapper to leverage it.
The Sigaction wrapper here is not an ABI, so relax it (drop the "extern"
and the "restorer" fields), the existing `k_sigaction` is the ABI
sigaction struct.

Linux defines `sigset_t` with a c_ulong, so it can be 32-bit or 64-bit,
depending on the platform.  This can make a difference on big-endian
systems.

Patch up `ucontext_t` so that this change doesn't impact its layout.
AFAICT, its currently the glibc layout.
Unify the C library sigset_t and Linux native sigset_t and the accessor
operations.

Add tests that the various sigset_t operations are working.  And clean up
existing tests a bit.
When linking a libc, Zig should defer to the C library for sigset
operations.  The pre-filled constants signal sets (empty_sigset,
filled_sigset) are not compatible with C library initialization, so remove
them and use the runtime `sigemptyset` and `sigfillset` methods to
initialize any sigset.
…gset_t

By returning an initialized sigset (instead of taking the set as an output
parameter), these functions can be used to directly initialize the `mask`
parameter of a `Sigaction` instance.
@rootbeer
Copy link
Contributor Author

Looks like I still have some work to do ...

error: the following command exited with error code 1:
/home/ci/actions-runner9/_work/zig/zig/zig-local-cache/o/81247a531862a8e65f403d02274c5c82/shared_lib_unwind 
slices differ. first difference occurs at index 0 (0x0)

============ expected this output: =============  len: 5 (0x5)

[0]: 17718044
[1]: 17715288
[2]: 281472964925348
[3]: 281472964925616
[4]: 17715004

============= instead found this: ==============  len: 5 (0x5)

[0]: 12297829382473034410
[1]: 12297829382473034410
[2]: 12297829382473034410
[3]: 12297829382473034410
[4]: 12297829382473034410

================================================

error: TestExpectedEqual
/home/ci/actions-runner9/_work/zig/zig/build-debug/../lib/std/testing.zig:435:5: 0x10e4c3f in expectEqualSlices__anon_23789 (shared_lib_unwind)
    return error.TestExpectedEqual;
    ^
/home/ci/actions-runner9/_work/zig/zig/build-debug/../lib/std/testing.zig:[128](https://github.com/ziglang/zig/actions/runs/14627905825/job/41043798928?pr=23601#step:3:129):27: 0x10e4ddf in expectEqualInner__anon_23774 (shared_lib_unwind)
        .array => |array| try expectEqualSlices(array.child, &expected, &actual),
                          ^
/home/ci/actions-runner9/_work/zig/zig/test/standalone/stack_iterator/shared_lib_unwind.zig:47:5: 0x10e4f97 in main (shared_lib_unwind)
    try testing.expectEqual(expected, unwound);

So far all I know is that 12297829382473034410 is 0xaaaaaaaaaaaaaaaa, and this is on aarch64-linux. I'm guessing I messed up ucontext_t somehow to have broken the backtrace ....

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.

2 participants