tests: manually wrap libc functions

The way we're wrapping libc functions via dlsym() is pretty fragile
and breaks on FreeBSD. The failures happen in our CI and are pretty
random, see e.g. [1].

Use a more manual way to wrap via a function pointer.

[1]: https://gitlab.freedesktop.org/wayland/wayland/-/jobs/44204010

Signed-off-by: Simon Ser <contact@emersion.fr>
This commit is contained in:
Simon Ser 2023-06-26 12:34:59 +02:00
parent 4a7348e48c
commit 4ec379ebcc
2 changed files with 47 additions and 62 deletions

View file

@ -42,6 +42,12 @@
#include "wayland-os.h" #include "wayland-os.h"
/* used by tests */
int (*wl_fcntl)(int fildes, int cmd, ...) = fcntl;
int (*wl_socket)(int domain, int type, int protocol) = socket;
ssize_t (*wl_recvmsg)(int socket, struct msghdr *message, int flags) = recvmsg;
int (*wl_epoll_create1)(int flags) = epoll_create1;
static int static int
set_cloexec_or_close(int fd) set_cloexec_or_close(int fd)
{ {
@ -50,11 +56,11 @@ set_cloexec_or_close(int fd)
if (fd == -1) if (fd == -1)
return -1; return -1;
flags = fcntl(fd, F_GETFD); flags = wl_fcntl(fd, F_GETFD);
if (flags == -1) if (flags == -1)
goto err; goto err;
if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) if (wl_fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1)
goto err; goto err;
return fd; return fd;
@ -69,13 +75,13 @@ wl_os_socket_cloexec(int domain, int type, int protocol)
{ {
int fd; int fd;
fd = socket(domain, type | SOCK_CLOEXEC, protocol); fd = wl_socket(domain, type | SOCK_CLOEXEC, protocol);
if (fd >= 0) if (fd >= 0)
return fd; return fd;
if (errno != EINVAL) if (errno != EINVAL)
return -1; return -1;
fd = socket(domain, type, protocol); fd = wl_socket(domain, type, protocol);
return set_cloexec_or_close(fd); return set_cloexec_or_close(fd);
} }
@ -124,13 +130,13 @@ wl_os_dupfd_cloexec(int fd, int minfd)
{ {
int newfd; int newfd;
newfd = fcntl(fd, F_DUPFD_CLOEXEC, minfd); newfd = wl_fcntl(fd, F_DUPFD_CLOEXEC, minfd);
if (newfd >= 0) if (newfd >= 0)
return newfd; return newfd;
if (errno != EINVAL) if (errno != EINVAL)
return -1; return -1;
newfd = fcntl(fd, F_DUPFD, minfd); newfd = wl_fcntl(fd, F_DUPFD, minfd);
return set_cloexec_or_close(newfd); return set_cloexec_or_close(newfd);
} }
@ -143,7 +149,7 @@ recvmsg_cloexec_fallback(int sockfd, struct msghdr *msg, int flags)
int *fd; int *fd;
int *end; int *end;
len = recvmsg(sockfd, msg, flags); len = wl_recvmsg(sockfd, msg, flags);
if (len == -1) if (len == -1)
return -1; return -1;
@ -179,7 +185,7 @@ wl_os_recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags)
#else #else
ssize_t len; ssize_t len;
len = recvmsg(sockfd, msg, flags | MSG_CMSG_CLOEXEC); len = wl_recvmsg(sockfd, msg, flags | MSG_CMSG_CLOEXEC);
if (len >= 0) if (len >= 0)
return len; return len;
if (errno != EINVAL) if (errno != EINVAL)
@ -194,7 +200,7 @@ wl_os_epoll_create_cloexec(void)
int fd; int fd;
#ifdef EPOLL_CLOEXEC #ifdef EPOLL_CLOEXEC
fd = epoll_create1(EPOLL_CLOEXEC); fd = wl_epoll_create1(EPOLL_CLOEXEC);
if (fd >= 0) if (fd >= 0)
return fd; return fd;
if (errno != EINVAL) if (errno != EINVAL)

View file

@ -34,7 +34,6 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <dlfcn.h>
#include <errno.h> #include <errno.h>
#include <stdarg.h> #include <stdarg.h>
#include <fcntl.h> #include <fcntl.h>
@ -45,50 +44,20 @@
#include "test-runner.h" #include "test-runner.h"
#include "wayland-os.h" #include "wayland-os.h"
extern int (*wl_socket)(int domain, int type, int protocol);
extern int (*wl_fcntl)(int fildes, int cmd, ...);
extern ssize_t (*wl_recvmsg)(int socket, struct msghdr *message, int flags);
extern int (*wl_epoll_create1)(int flags);
static int fall_back; static int fall_back;
/* Play nice with sanitizers static int wrapped_calls_socket = 0;
* static int wrapped_calls_fcntl = 0;
* Sanitizers need to intercept syscalls in the compiler run-time library. As static int wrapped_calls_recvmsg = 0;
* this isn't a separate ELF object, the usual dlsym(RTLD_NEXT) approach won't static int wrapped_calls_epoll_create1 = 0;
* work: there can only be one function named "socket" etc. To support this, the
* sanitizer library names its interceptors with the prefix __interceptor_ ("__"
* being reserved for the implementation) and then weakly aliases it to the real
* function. The functions we define below will override the weak alias, and we
* can call them by the __interceptor_ name directly. This allows the sanitizer
* to do its work before calling the next version of the function via dlsym.
*
* However! We also don't know which of these functions the sanitizer actually
* wants to override, so we have to declare our own weak symbols for
* __interceptor_ and check at run time if they linked to anything or not.
*/
#define DECL(ret_type, func, ...) \ static int
ret_type __interceptor_ ## func(__VA_ARGS__) __attribute__((weak)); \ socket_wrapper(int domain, int type, int protocol)
static ret_type (*real_ ## func)(__VA_ARGS__); \
static int wrapped_calls_ ## func;
#define REAL(func) (__interceptor_ ## func) ? \
__interceptor_ ## func : \
(__typeof__(&__interceptor_ ## func))dlsym(RTLD_NEXT, #func)
DECL(int, socket, int, int, int);
DECL(int, fcntl, int, int, ...);
DECL(ssize_t, recvmsg, int, struct msghdr *, int);
DECL(int, epoll_create1, int);
static void
init_fallbacks(int do_fallbacks)
{
fall_back = do_fallbacks;
real_socket = REAL(socket);
real_fcntl = REAL(fcntl);
real_recvmsg = REAL(recvmsg);
real_epoll_create1 = REAL(epoll_create1);
}
__attribute__ ((visibility("default"))) int
socket(int domain, int type, int protocol)
{ {
wrapped_calls_socket++; wrapped_calls_socket++;
@ -97,11 +66,11 @@ socket(int domain, int type, int protocol)
return -1; return -1;
} }
return real_socket(domain, type, protocol); return socket(domain, type, protocol);
} }
__attribute__ ((visibility("default"))) int static int
(fcntl)(int fd, int cmd, ...) fcntl_wrapper(int fd, int cmd, ...)
{ {
va_list ap; va_list ap;
int arg; int arg;
@ -131,13 +100,13 @@ __attribute__ ((visibility("default"))) int
} }
if (has_arg) { if (has_arg) {
return real_fcntl(fd, cmd, arg); return fcntl(fd, cmd, arg);
} }
return real_fcntl(fd, cmd); return fcntl(fd, cmd);
} }
__attribute__ ((visibility("default"))) ssize_t static ssize_t
recvmsg(int sockfd, struct msghdr *msg, int flags) recvmsg_wrapper(int sockfd, struct msghdr *msg, int flags)
{ {
wrapped_calls_recvmsg++; wrapped_calls_recvmsg++;
@ -146,11 +115,11 @@ recvmsg(int sockfd, struct msghdr *msg, int flags)
return -1; return -1;
} }
return real_recvmsg(sockfd, msg, flags); return recvmsg(sockfd, msg, flags);
} }
__attribute__ ((visibility("default"))) int static int
epoll_create1(int flags) epoll_create1_wrapper(int flags)
{ {
wrapped_calls_epoll_create1++; wrapped_calls_epoll_create1++;
@ -160,7 +129,17 @@ epoll_create1(int flags)
return -1; return -1;
} }
return real_epoll_create1(flags); return epoll_create1(flags);
}
static void
init_fallbacks(int do_fallbacks)
{
fall_back = do_fallbacks;
wl_fcntl = fcntl_wrapper;
wl_socket = socket_wrapper;
wl_recvmsg = recvmsg_wrapper;
wl_epoll_create1 = epoll_create1_wrapper;
} }
static void static void