-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
errno
seemingly lost during fstatat
when -target
with -gnu
is specified
#17034
Comments
This actually only reproduces when the
Works fine without
EDIT: EDIT#2: Here's a better/more understandable test case: const std = @import("std");
test {
try std.testing.expectError(error.FileNotFound, std.fs.cwd().statFile("test_file_that_doesnt_exist"));
} Works with
|
errno
seemingly lost during fstatat
when linking glibcerrno
seemingly lost during fstatat
when -target
with -gnu
is specified
we experienced a strange potentially related bug here: oven-sh/bun#4440 |
Stepping through the generated code in a debugger, the fstatat syscall executes, fails, returns the correct status code, then that code is converted into an errno and stashed in a thread-local variable. The call returns and Zig code fetches errno, but seems to fetch it from a different place than where errno was stored. In the fstatat code the negative syscall return code is converted to an errno and saved with:
From what I understand the %fs register is the thread-local-storage segment, and this writes the errno value at -8 from the TLS pointer. I believe it is "normal" for a TLS pointer to point to the middle of the logical structure, so I think this is fine. Invoking "write" in a similar manner to fstatat (e.g., The write code for saving errno looks quite different:
This looks up a constant at a RIP-relative address. GDB helpfully decodes the address in its comment there: (gdb) p* 0x7ffff7f8fde0 So I think this code stores the errno at -152 from the TLS pointer. The errno-lookup code is:
This is trying to return the address of errno, not the value, so it just adds the offset to the %fs base. This results in the same So the fstatat code is using the wrong errno … Oh, the fstatat code isn't from the external C library. Its Zig-compiled code. (You can see the difference in the disassembly addresses above.) GDB says its compiled from I suspect how this can happen will be obvious to one or two people on the planet, but in case they're not paying attention I'll keep digging and see if I can figure out why the wrong fstatat is being used. My test case is (compiled with
|
The
I wrote a short script to show the "fstatat" symbols ("nm | grep fstatat") in each of the object files:
So "fstatat64" is the undefined symbol in the main object file (confused-tls.o). Oddly the "libc.so.6" binary doesn't have any I'll look into why there is no fstatat* in libc.so.6. It also seems like the libc_nonshared should also be using the real C library's errno? Or maybe the libc_nonshared should not be linked in if the system libc is being used? |
I came to the same conclusion as above, and I found that this patch happens to prevent the wrong errno from being used: diff --git a/src/glibc.zig b/src/glibc.zig
index cf12e8ea4..7179cfde4 100644
--- a/src/glibc.zig
+++ b/src/glibc.zig
@@ -353,7 +353,6 @@ pub fn buildCRTFile(comp: *Compilation, crt_file: CRTFile, prog_node: *std.Progr
"-D_LIBC_REENTRANT",
"-include",
try lib_path(comp, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-modules.h"),
- "-DMODULE_NAME=libc",
"-Wno-nonportable-include-path",
"-include",
try lib_path(comp, arena, lib_libc_glibc ++ "include" ++ path.sep_str ++ "libc-symbols.h"), However, I have no idea if this change is correct or even makes sense. |
It seems So, removing the "-DMODULE_NAME=libc" from the libc_nonshared compile might be the right fix. Presumably that tells the nonshared library to use an external errno (instead of its own, local, static one). I'll see what I can dig up to verify this. I'm very curious how you came to this diff. :) |
I just looked at the code and changed defines until it worked. :) |
The errno.c that is linked into libc_nonshared ( The other thing that doesn't make sense is that the libc_nonshared.c fstat implementation was a raw syscall. The libc_noshared implementation is meant to forward to the "real" libc, via a non-standard "xstat()" implementation. (The whole point of the nonshared.a library is to link in stubs that jump into the dynamically linked library using a version-aware API....) Hrm, it looks like the libc_nonshared (also called "static-only-routines" in the Makefiles) fstat* wrappers were excised from libc_nonshared in Oct 2020: https://sourceware.org/git/?p=glibc.git;a=commit;h=4d97cc8cf3da925fd06fc37d4daebafce3247719 Yeah, my local (Debian) libc_nonshared.a doesn't have the "stat" family of functions in it:
They're in libc.so.6 now:
Here's some historical Zig context: #11137 I found too. Having Zig clone the /build system/ for glibc and keep in sync with internal changes like this seems like a losing game. Problems in this area are usually more subtle than losing errno because they don't break anything in the short-term. They just create future backwards compatibility problems. |
Here's a half a draft change to update the libc_nonshared stub as far as I understand how it works. I'm not sure how to add the symbols removed from libc_nonshared (fstat*) to the libc.so though: #17521 |
At a minimum required glibc is v2.17, as earlier versions do not define some symbols (e.g., getauxval()) used by the Zig standard library. Additionally, glibc only supports some architectures at more recent versions (e.g., riscv64 support came in glibc v2.27). So add a `glibc_min` field to `available_libcs` for architectures with stronger version requirements. Extend the existing `canBuildLibC` function to check the target against the Zig minimum, and the architecture/os minimum. Also filter the list shown by `zig targets`, too: $ zig targets | jq -c '.glibc' ["2.17.0","2.18.0","2.19.0","2.20.0","2.21.0","2.22.0","2.23.0","2.24.0","2.25.0","2.26.0","2.27.0","2.28.0","2.29.0","2.30.0","2.31.0","2.32.0","2.33.0","2.34.0","2.35.0","2.36.0","2.37.0","2.38.0"] Fixes ziglang#17034 Fixes ziglang#17769
At a minimum required glibc is v2.17, as earlier versions do not define some symbols (e.g., getauxval()) used by the Zig standard library. Additionally, glibc only supports some architectures at more recent versions (e.g., riscv64 support came in glibc v2.27). So add a `glibc_min` field to `available_libcs` for architectures with stronger version requirements. Extend the existing `canBuildLibC` function to check the target against the Zig minimum, and the architecture/os minimum. Also filter the list shown by `zig targets`, too: $ zig targets | jq -c '.glibc' ["2.17.0","2.18.0","2.19.0","2.20.0","2.21.0","2.22.0","2.23.0","2.24.0","2.25.0","2.26.0","2.27.0","2.28.0","2.29.0","2.30.0","2.31.0","2.32.0","2.33.0","2.34.0","2.35.0","2.36.0","2.37.0","2.38.0"] Fixes ziglang#17034 Fixes ziglang#17769
At a minimum required glibc is v2.17, as earlier versions do not define some symbols (e.g., getauxval()) used by the Zig standard library. Additionally, glibc only supports some architectures at more recent versions (e.g., riscv64 support came in glibc v2.27). So add a `glibc_min` field to `available_libcs` for architectures with stronger version requirements. Extend the existing `canBuildLibC` function to check the target against the Zig minimum, and the architecture/os minimum. Also filter the list shown by `zig targets`, too: $ zig targets | jq -c '.glibc' ["2.17.0","2.18.0","2.19.0","2.20.0","2.21.0","2.22.0","2.23.0","2.24.0","2.25.0","2.26.0","2.27.0","2.28.0","2.29.0","2.30.0","2.31.0","2.32.0","2.33.0","2.34.0","2.35.0","2.36.0","2.37.0","2.38.0"] Fixes ziglang#17034 Fixes ziglang#17769
At a minimum required glibc is v2.17, as earlier versions do not define some symbols (e.g., getauxval()) used by the Zig standard library. Additionally, glibc only supports some architectures at more recent versions (e.g., riscv64 support came in glibc v2.27). So add a `glibc_min` field to `available_libcs` for architectures with stronger version requirements. Extend the existing `canBuildLibC` function to check the target against the Zig minimum, and the architecture/os minimum. Also filter the list shown by `zig targets`, too: $ zig targets | jq -c '.glibc' ["2.17.0","2.18.0","2.19.0","2.20.0","2.21.0","2.22.0","2.23.0","2.24.0","2.25.0","2.26.0","2.27.0","2.28.0","2.29.0","2.30.0","2.31.0","2.32.0","2.33.0","2.34.0","2.35.0","2.36.0","2.37.0","2.38.0"] Fixes ziglang#17034 Fixes ziglang#17769
At a minimum required glibc is v2.17, as earlier versions do not define some symbols (e.g., getauxval()) used by the Zig standard library. Additionally, glibc only supports some architectures at more recent versions (e.g., riscv64 support came in glibc v2.27). So add a `glibc_min` field to `available_libcs` for architectures with stronger version requirements. Extend the existing `canBuildLibC` function to check the target against the Zig minimum, and the architecture/os minimum. Also filter the list shown by `zig targets`, too: $ zig targets | jq -c '.glibc' ["2.17.0","2.18.0","2.19.0","2.20.0","2.21.0","2.22.0","2.23.0","2.24.0","2.25.0","2.26.0","2.27.0","2.28.0","2.29.0","2.30.0","2.31.0","2.32.0","2.33.0","2.34.0","2.35.0","2.36.0","2.37.0","2.38.0"] Fixes ziglang#17034 Fixes ziglang#17769
At a minimum required glibc is v2.17, as earlier versions do not define some symbols (e.g., getauxval()) used by the Zig standard library. Additionally, glibc only supports some architectures at more recent versions (e.g., riscv64 support came in glibc v2.27). So add a `glibc_min` field to `available_libcs` for architectures with stronger version requirements. Extend the existing `canBuildLibC` function to check the target against the Zig minimum, and the architecture/os minimum. Also filter the list shown by `zig targets`, too: $ zig targets | jq -c '.glibc' ["2.17.0","2.18.0","2.19.0","2.20.0","2.21.0","2.22.0","2.23.0","2.24.0","2.25.0","2.26.0","2.27.0","2.28.0","2.29.0","2.30.0","2.31.0","2.32.0","2.33.0","2.34.0","2.35.0","2.36.0","2.37.0","2.38.0"] Fixes ziglang#17034 Fixes ziglang#17769
At a minimum required glibc is v2.17, as earlier versions do not define some symbols (e.g., getauxval()) used by the Zig standard library. Additionally, glibc only supports some architectures at more recent versions (e.g., riscv64 support came in glibc v2.27). So add a `glibc_min` field to `available_libcs` for architectures with stronger version requirements. Extend the existing `canBuildLibC` function to check the target against the Zig minimum, and the architecture/os minimum. Also filter the list shown by `zig targets`, too: $ zig targets | jq -c '.glibc' ["2.17.0","2.18.0","2.19.0","2.20.0","2.21.0","2.22.0","2.23.0","2.24.0","2.25.0","2.26.0","2.27.0","2.28.0","2.29.0","2.30.0","2.31.0","2.32.0","2.33.0","2.34.0","2.35.0","2.36.0","2.37.0","2.38.0"] Fixes ziglang#17034 Fixes ziglang#17769
At a minimum required glibc is v2.17, as earlier versions do not define some symbols (e.g., getauxval()) used by the Zig standard library. Additionally, glibc only supports some architectures at more recent versions (e.g., riscv64 support came in glibc v2.27). So add a `glibc_min` field to `available_libcs` for architectures with stronger version requirements. Extend the existing `canBuildLibC` function to check the target against the Zig minimum, and the architecture/os minimum. Also filter the list shown by `zig targets`, too: $ zig targets | jq -c '.glibc' ["2.17.0","2.18.0","2.19.0","2.20.0","2.21.0","2.22.0","2.23.0","2.24.0","2.25.0","2.26.0","2.27.0","2.28.0","2.29.0","2.30.0","2.31.0","2.32.0","2.33.0","2.34.0","2.35.0","2.36.0","2.37.0","2.38.0"] Fixes ziglang#17034 Fixes ziglang#17769
At a minimum required glibc is v2.17, as earlier versions do not define some symbols (e.g., getauxval()) used by the Zig standard library. Additionally, glibc only supports some architectures at more recent versions (e.g., riscv64 support came in glibc v2.27). So add a `glibc_min` field to `available_libcs` for architectures with stronger version requirements. Extend the existing `canBuildLibC` function to check the target against the Zig minimum, and the architecture/os minimum. Also filter the list shown by `zig targets`, too: $ zig targets | jq -c '.glibc' ["2.17.0","2.18.0","2.19.0","2.20.0","2.21.0","2.22.0","2.23.0","2.24.0","2.25.0","2.26.0","2.27.0","2.28.0","2.29.0","2.30.0","2.31.0","2.32.0","2.33.0","2.34.0","2.35.0","2.36.0","2.37.0","2.38.0"] Fixes ziglang#17034 Fixes ziglang#17769
Zig Version
0.12.0-dev.244+f4c9e19bc
Steps to Reproduce and Observed Behavior
When linking glibc, the
fstatat
error seems to get lost, and some other error is returned fromgetErrno
/c._errno().*
instead (seenESUCCESS
andEEXIST
, but expectedENOENT
).Test code:
built with:
output:
commented strace with only the relevant parts:
(so both syscalls are the same, but somehow the glibc version doesn't
The same problem exists in the equivalent C code compiled by Zig:
but the same C code works as expected when compiled with clang directly:
(2 is
ENOENT
)The strace between the Zig-compiled C version and the Clang-compiled C version are the same:
Expected Behavior
fstatat
errors to be accurately returned when linking glibcThe text was updated successfully, but these errors were encountered: