Merge pull request #313 from dkg/test-variadic-promotion

Test variadic promotion
This commit is contained in:
Wolfgang Hommel
2021-03-06 09:46:07 +01:00
committed by GitHub
8 changed files with 201 additions and 20 deletions

1
.gitignore vendored
View File

@@ -8,6 +8,7 @@ test/run_*
test/repeat_random
test/getentropy_test
test/syscall_test
test/variadic_promotion
src/libfaketime.dylib.1
src/libfaketime.1.dylib

View File

@@ -48,6 +48,11 @@
# - (On GNU/Linux only) intercept glibc's syscall() for known relevant syscalls.
# If enabled, this currently only works to divert the getrandom syscall.
#
# - note that on unusual architectures, if INTERCEPT_SYSCALL is set, you may
# need to explicitly define variadic_promotion_t (e.g. by putting
# -Dvariadic_promotion_t=int into CFLAGS). See src/faketime_common.h for
# more info.
#
# FORCE_MONOTONIC_FIX
# - If the test program hangs forever on
# " pthread_cond_timedwait: CLOCK_MONOTONIC test

View File

@@ -58,4 +58,43 @@ struct ft_shared_s
#include <mach/mach_port.h>
#endif
/*
Variadic Argument Re-packing
Functions with variadic arguments typically have most arguments
passed on the stack, but it varies across ABIs.
C specifies that variadic arguments that are smaller than some
standard promotion size are promoted to "int or larger". If your
platform's ABI only promotes to "int" and not "long" (and "int" and
"long" differ on your platform), you should probably add
-Dvariadic_promotion_t=int to CFLAGS.
Note that some ABIs do not put all the variadic arguments on the
stack. For example, x86-64 puts float and double variadic
arguments into floating point registers, according to
https://www.uclibc.org/docs/psABI-x86_64.pdf
The only variadic function faketime cares about intercepting is
syscall. But we don't believe that any syscalls expect float or
double arguments, so we hope all the rest will be on the stack.
tests/variadic/ attempts to confirm this if you are compiling
with -DINTERCEPT_SYSCALL.
If libc were capable of exposing a variadic form of syscall, we
could depend on that and drop this approach, which would be
preferable: https://sourceware.org/bugzilla/show_bug.cgi?id=27508
*/
#ifndef variadic_promotion_t
#define variadic_promotion_t long
#endif
/*
The Linux kernel appears to have baked-in 6 as the maximum number
of arguments for a syscall beyond the syscall number itself.
*/
#ifndef syscall_max_args
#define syscall_max_args 6
#endif
#endif

View File

@@ -3781,23 +3781,9 @@ long syscall(long number, ...) {
return clock_gettime(clk_id, tp);
}
/*
Invocations of C variadic arguments that are smaller than int are
promoted to int. For larger arguments, it's likely that they are
chopped into int-sized pieces.
So the passthrough part of this code is attempting to reverse that
ABI so we can pass the arguments back into syscall().
Note that the Linux kernel appears to have baked-in 6 as the
maximum number of arguments for a syscall beyond the syscall number
itself.
*/
#define vararg_promotion_t long
#define syscall_max_args 6
vararg_promotion_t a[syscall_max_args];
variadic_promotion_t a[syscall_max_args];
for (int i = 0; i < syscall_max_args; i++)
a[i] = va_arg(ap, vararg_promotion_t);
a[i] = va_arg(ap, variadic_promotion_t);
va_end(ap);
if (!initialized)
ftpl_init();

View File

@@ -1,7 +1,7 @@
CC = gcc
CFLAGS = -std=gnu99 -Wall -DFAKE_STAT -Werror -Wextra $(FAKETIME_COMPILE_CFLAGS)
LDFLAGS = -lrt -lpthread
CFLAGS += -std=gnu99 -Wall -DFAKE_STAT -Werror -Wextra $(FAKETIME_COMPILE_CFLAGS)
LDFLAGS += -lrt -lpthread
SRC = timetest.c
OBJ = ${SRC:.c=.o}
@@ -9,7 +9,13 @@ OBJ = ${SRC:.c=.o}
TEST_SNIPPETS = $(notdir $(basename $(wildcard snippets/*.c)))
EXPECTATIONS= $(notdir $(basename $(wildcard snippets/*.variable)))
all: timetest test
ALL_TESTS = timetest test
ifneq ($(filter -DINTERCEPT_SYSCALL,${CFLAGS}),)
ALL_TESTS += confirm_variadic_promotion
endif
all: $(ALL_TESTS)
.c.o:
${CC} -c ${CFLAGS} $<
@@ -31,6 +37,13 @@ functest:
randomtest: repeat_random
./randomtest.sh
# ensure our variadic argument unpacking/repacking works as expected
confirm_variadic_promotion: variadic_promotion
./variadic_promotion
variadic_promotion: variadic/main.o variadic/outer.o variadic/inner.o
${CC} -o $@ ${CFLAGS} $^
variadic/%.o: variadic/%.c
${CC} -c -o $@ ${CFLAGS} $<
# run snippet tests
snippets: test_variable_data test_library_constructors
@@ -57,7 +70,7 @@ use_lib_%: _use_lib_test.c snippets/%.c lib%.so
## cleanup and metainformation
clean:
@rm -f ${OBJ} timetest getrandom_test syscall_test $(foreach f,${TEST_SNIPPETS},use_lib_${f} lib${f}.so run_${f})
@rm -f ${OBJ} timetest getrandom_test syscall_test $(foreach f,${TEST_SNIPPETS},use_lib_${f} lib${f}.so run_${f}) variadic_promotion variadic/*.o
distclean: clean
@echo

46
test/variadic/inner.c Normal file
View File

@@ -0,0 +1,46 @@
#include <stdio.h>
#include <wchar.h>
#include <stdarg.h>
#include <stddef.h>
/* round 0: c, s, wc, i, wi */
long inner0(char *out, ...) {
char c = 0;
short s = 0;
wchar_t wc = 0;
int i = 0;
wint_t wi = 0;
va_list ap;
va_start(ap, out);
c = va_arg(ap, int);
s = va_arg(ap, int);
wc = va_arg(ap, typeof(wc));
i = va_arg(ap, typeof(i));
wi = va_arg(ap, typeof(wi));
va_end(ap);
int ret = sprintf(out, "c: 0x%x s: 0x%x wc: 0x%lx i: 0x%x wi: 0x%x\n", c, s, (long)wc, i, wi);
return ret;
}
/* round 1: l, ll, ptr, pd, sz */
long inner1(char *out, ...) {
long l = 0;
long long ll = 0;
void *ptr = NULL;
ptrdiff_t pd = 0;
size_t sz = 0;
va_list ap;
va_start(ap, out);
l = va_arg(ap, typeof(l));
ll = va_arg(ap, typeof(ll));
ptr = va_arg(ap, typeof(ptr));
pd = va_arg(ap, typeof(pd));
sz = va_arg(ap, typeof(sz));
va_end(ap);
int ret = sprintf(out, "l: 0x%lx ll: 0x%llx ptr: %p pd: 0x%tx sz: 0x%zx\n", l, ll, ptr, pd, sz);
return ret;
}

70
test/variadic/main.c Normal file
View File

@@ -0,0 +1,70 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <wchar.h>
extern long outer(long num, char *out, ...);
extern long inner0(char *out, ...);
extern long inner1(char *out, ...);
#define bufsize 2048
static int compare_buffers(int round,
long ret_outer, long ret_inner,
const char* outer, const char* inner) {
int ret = 0;
if (ret_outer != ret_inner) {
printf("Round %d: return values differ (outer: %ld inner: %ld)\n", round, ret_outer, ret_inner);
ret++;
}
if (memcmp(outer, inner, bufsize)) {
printf("Round %d strings differ:\n - outer: %s\n - inner: %s\n", round, outer, inner);
ret++;
}
if (ret == 0)
printf("Round %d success: %s\n", round, outer);
return ret;
}
int main() {
/* sizes of intrinsic types as reported by echo | cpp -dM | grep
SIZEOF, pruned to avoid floating point types. Should work with
both clang and gcc, not sure about other C preprocessors.
Note that we set bits in every high octet and every low octet to
see that they end up in the right spot.
*/
char c = 0x03L;
short s = (0x04L << ((__SIZEOF_SHORT__ - 1) * 8)) + 0xff;
wchar_t wc = (0x05L << ((__SIZEOF_WCHAR_T__ - 1) * 8)) + 0xfe;
int i = (0x06L << ((__SIZEOF_INT__ - 1) * 8)) + 0xfd;
wint_t wi = (0x07L << ((__SIZEOF_WINT_T__ - 1) * 8)) + 0xfc;
long l = (0x08L << ((__SIZEOF_LONG__ - 1) * 8) ) + 0xfb;
long long ll = (0x09LL << ((__SIZEOF_LONG_LONG__ - 1) * 8)) + 0xfa;
void *ptr = (void*)((0x0aL << ((__SIZEOF_POINTER__ - 1) * 8)) + 0xf9);
ptrdiff_t pd = (0x0bL << ((__SIZEOF_PTRDIFF_T__ -1) * 8)) + 0xf9;
size_t sz = (0x0cL << ((__SIZEOF_SIZE_T__ - 1) * 8)) + 0xf8;
char *buf[2];
for (int j = 0; j < 2; j++)
buf[j] = malloc(bufsize);
int ret[2];
int errors = 0;
#define reset_buffers(n) for (int j = 0; j < 2; j++) memset(buf[j], n, bufsize)
#define check_buffers(n) errors += compare_buffers(n, ret[0], ret[1], buf[0], buf[1])
reset_buffers(0);
ret[0] = outer(0, buf[0], c, s, wc, i, wi);
ret[1] = inner0(buf[1], c, s, wc, i, wi);
check_buffers(0);
reset_buffers(1);
ret[0] = outer(1, buf[0], l, ll, ptr, pd, sz);
ret[1] = inner1(buf[1], l, ll, ptr, pd, sz);
check_buffers(1);
return (int)errors;
}

21
test/variadic/outer.c Normal file
View File

@@ -0,0 +1,21 @@
#include <stdarg.h>
#include <time.h>
#include "../../src/faketime_common.h"
extern long inner0(char *out, ...);
extern long inner1(char *out, ...);
long outer(long num, char *out, ...) {
va_list ap;
va_start(ap, out);
variadic_promotion_t a[syscall_max_args];
for (int i = 0; i < syscall_max_args; i++)
a[i] = va_arg(ap, variadic_promotion_t);
va_end(ap);
if (num == 0)
return inner0(out, a[0], a[1], a[2], a[3], a[4], a[5]);
if (num == 1)
return inner1(out, a[0], a[1], a[2], a[3], a[4], a[5]);
else return -1;
}