mirror of
https://github.com/wolfcw/libfaketime.git
synced 2026-05-17 00:26:16 +03:00
Merge pull request #313 from dkg/test-variadic-promotion
Test variadic promotion
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
46
test/variadic/inner.c
Normal 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
70
test/variadic/main.c
Normal 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
21
test/variadic/outer.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user