ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
31.98k stars 2.34k forks source link

Incorrect _STAT_VER* constants when cross compiling to arm32 on old glibc #20054

Open bjia56 opened 1 month ago

bjia56 commented 1 month ago

Zig Version

0.13.0-dev.260+fb88cfdf6

Steps to Reproduce and Observed Behavior

tl;dr: Compiling with arm32 glibc 2.31 target pulls in incorrect _STAT_VER constants, causing fstatat function to return EINVAL.

Issue

Cross compiling the following code with zig cc -target arm-linux-gnueabihf.2.31 fstatat.c -o fstatat, creating an empty file /tmp/test.txt, and running the code will print fstatat: Invalid argument:

#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int main() {
    int dirfd = open("/tmp", O_RDONLY | O_DIRECTORY);
    if (dirfd == -1) {
        perror("open");
        return 1;
    }

    const char *filename = "test.txt";

    struct stat statbuf;

    if (fstatat(dirfd, filename, &statbuf, 0) == -1) {
        perror("fstatat");
        close(dirfd);
        return 1;
    }

    close(dirfd);
    return 0;
}

Findings from debugging

Debugging this on a Raspberry Pi 3B+ armv7l running Debian Bullseye shows that the program enters __GI___fxstatat which then calls __xstat32_conv. Disassembling __xstat32_conv shows a suspicious comparison with the constant "3":

Dump of assembler code for function __xstat32_conv:
=> 0x76ef6944 <+0>:     cmp     r0, #3
   0x76ef6948 <+4>:     push    {lr}            ; (str lr, [sp, #-4]!)
... truncated

which matches the value of _STAT_VER_LINUX:

pi@raspberrypi3:/tmp $ cat /usr/include/arm-linux-gnueabihf/bits/stat.h | grep _STAT_VER
#define _STAT_VER_LINUX_OLD     1
#define _STAT_VER_KERNEL        1
#define _STAT_VER_SVR4          2
#define _STAT_VER_LINUX         3
#define _STAT_VER               _STAT_VER_LINUX /* The one defined below.  */

However, Zig's fstatat function passes in the constant "0":

Dump of assembler code for function fstatat:
   0x00023fe0 <+0>:     push    {r11, lr}
   0x00023fe4 <+4>:     mov     r11, sp
   0x00023fe8 <+8>:     sub     sp, sp, #8
   0x00023fec <+12>:    mov     r12, r2
   0x00023ff0 <+16>:    mov     r2, r1
   0x00023ff4 <+20>:    mov     r1, r0
   0x00023ff8 <+24>:    str     r3, [sp]
   0x00023ffc <+28>:    mov     r0, #0
   0x00024000 <+32>:    mov     r3, r12
   0x00024004 <+36>:    bl      0x44960
   0x00024008 <+40>:    mov     sp, r11
   0x0002400c <+44>:    pop     {r11, pc}
End of assembler dump.

Expected Behavior

fstatat should not return EINVAL. In the sample program, there should be no output.

iacore commented 2 weeks ago

Can reproduce with

Works with

the program to test this:

#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
int main() {
  struct stat st;
  int ret = stat("/tmp/", &st);
  printf("%d %d", ret, errno);
  return 0;
}