Split glibc and linux ucontext_t
definitions
#23802
Labels
bug
Observed behavior contradicts documented or intended behavior
os-linux
standard library
This issue involves writing Zig code for the standard library.
Milestone
Zig Version
0.15.0-dev.451+bd230215f
Steps to Reproduce and Observed Behavior
Following on #23601 to split the linux kernel
sigset_t
from the glibcsigset_t
, some work may be needed to defineucontext_t
separately for the Zig linux and glibc APIs. At the least, there is an embeddedsigset_t
in it. Currently for Linux, Zig defines a glibc-compatibleucontext_t
atstd.os.linux.<arch>.ucontext_t
, aliases that tostd.os.linux.ucontext_t
and then aliases that onstd.c.ucontext_t
.There are two variations of the
ucontext_t
in a Linux glibc environment. First there is theucontext
that the linux kernel fills out and puts on the stack for a signal handler (this is what the third parameter in a sigaction signal handler points to). This kernel-created state is implicitly the machine state to be restored when returning from the signal handler. The second variation is theucontext_t
filled out by the glibcgetcontext()
andmakecontext()
calls. The kernel and glibc structures are slightly incompatible when compared directly, but are technically compatible if used "correctly". The structures have a consistent layout up to theuc_sigmask
signal mask field. Thesigset_t
field is different sizes in the two structures (1024-bit in glibc and 64/128-bit in kernel). And the fields after that are not consistent (e.g., floating point state, alternative stack state, etc) --- see the structure declarations below. Only the valid first 64/128 bits of thesigset_t
should be accessed if used correctly. And the extra state beyond that should be accessed via embedded pointers in the common fields. Specifically, thefpregs
field in themcontext_t
is a (maybe NULL) pointer to the (variable sized) floating point state, and extra shadow stack state is accessed via a pointer in thestack_t
property.Additionally, I believe this amorphous state can grow over time. For example, Rust ran into problems because glibc v2.28 added the
__ssp
state to theirucontext_t
(see rust-lang/libc#1410). I believe the kernel has added more floating point state to its saved state over time, too, as processors add more CPU state to be saved and restored. I'm mostly familiar with the x86 flavors, but each architecture has its own history here. This variable size is annoying because callers are expected to allocate theucontext_t
instance, and then invokegetcontext()
to initialize it. So the caller really must have the correct size structure.Note that OpenBSD and Android do not support
getcontext()
. I don't think Darwin supports it either. (These systems generally do supportsigsetjmp
which is a predecessor togetcontext
). Alsogetcontext
, and its related functions were removed from POSIX: https://man7.org/linux/man-pages/man3/getcontext.3.html:I see a couple ways of fixing the
ucontext_t
declaration in Zig:getcontext()
, so the only usage ofucontext_t
would be in a signal handler. This removes the need for a Zig translation of the glibcucontext_t
. The Linuxucontext_t
structure can just contain the "public" fields, and leave the variable sized ones (fpregs, ssp, etc) off. This structure would be the wrong size if allocated directly, but for use in decoding state in a signal handler, it should be sufficient. AFAICT, the only current use case forgetcontext()
in Zig, is for the backtrace code instd.debug
. I believe that code does not need a full context with signal masks.ucontext_t
with opaque padding bytes so the structure can still be allocated by a caller ofgetcontext()
, but the specific layout is more clearly opaque to callers. Having a little extra padding (e.g., to cover the shadow stack state) should be harmless. It is not clear if future glibc versions will add more state to this structure, and how Zig should handle that.getcontext()
(like option 1) but provide a moral equivalent togetcontext()
, and friends. That is, Zig provides a set of functions that returns the full machine state and signal mask, but does so in a structure that is amenable to copies and amenable to future changes. And probably does it in a way that only the relevant machine state is captured, depending on the caller's requirements (e.g., signal mask, floating point state, shadow stacks, etc could be optional).Kernel and glibc ucontext_t declarations
The Linux kernel's "uapi"
struct ucontext
is a subset of the state actually pushed on the stack, and contains the bits they're committed to maintaining compatibility for: (linux include/uapi/asm-generic/ucontext.h):Note that the
sigset_t
here is a kernel-defined signal mask (so 64 or 128 bits), not a glibc 1024-bit mask. Also note that the floating point register state is not explicitly mentioned, even though it is adjacent to this structure in memory (and could be the last field of the struct) -- it's meant to be accessed via theuc_mcontext
.The glibc x86
uncontext_t
(glibc sysdeps/unix/sysv/linux/x86/sys/ucontext.h):Note here that the
uc_sigmask
is a 1024-bit glibc signal mask. This struct does include the floating point and shadow stack state, but callers are not expected to access these directly, but instead via pointers in theuc_mcontext
anduc_stack
fields. For example, consider casting a pointer to the signal state on the stack pushed by the kernel into a pointer to this struct. The actual fp register content probably overlaps with the 1024-bit sigmask, because the kernel only pushed a 64-bit signal mask. But as long as the pointers in theuc_mcontext
are used to access that fp state, it should be fine.Expected Behavior
ucontext_t
is relatively safe and useful both with and without a C library linked.The text was updated successfully, but these errors were encountered: