Add memcpy with bounds checks.
The memcpy implementation is not completely stupid but is almost certainly not as good as a carefully tuned and optimised one. Building snmalloc with FreeBSD's libc memcpy + jemalloc and with this, each 10 times, does not show a statistically significant performance difference at 95% confidence. The snmalloc version has very slightly lower median and worst-case times. This is in no way a sensible benchmark, but it serves as a smoke test for significant performance regressions. The CI self-host job now uses the checked memcpy. This also fixes an off-by-one error in the external bounds. This is triggered by ninja, so we will see breakage in CI if it is reintroduced. In debug builds, we provide a verbose error containing the address of the allocation, the base and bounds of the allocation, and a backtrace. The backtrace was broken by the CI cleanup moving the BACKTRACE_HEADER macro into the SNMALLOC_ namespace. This is also fixed. The test involves hijacking `abort`, which doesn't work everywhere. It also requires `backtrace` to work in configurations where stack traces are enabled. This is disabled in QEMU because `backtrace` appears to crash reliably in QEMU user mode. For now, in the -checks build configurations, we are hitting a slow path in the pagemap on accesses so that the pages that are `PROT_NONE` don't cause crashes. These need to be made read-only, but this requires a PAL change.
This commit is contained in:
committed by
David Chisnall
parent
d524ef5cac
commit
51e75bca89
155
src/test/func/memcpy/func-memcpy.cc
Normal file
155
src/test/func/memcpy/func-memcpy.cc
Normal file
@@ -0,0 +1,155 @@
|
||||
// Windows doesn't like changing the linkage spec of abort.
|
||||
#if defined(_MSC_VER)
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
// QEMU user mode does not support the code that generates backtraces and so we
|
||||
// also need to skip this test if we are doing a debug build and targeting
|
||||
// QEMU.
|
||||
# if defined(SNMALLOC_QEMU_WORKAROUND) && defined(SNMALLOC_BACKTRACE_HEADER)
|
||||
# undef SNMALLOC_BACKTRACE_HEADER
|
||||
# endif
|
||||
# ifdef SNMALLOC_STATIC_LIBRARY_PREFIX
|
||||
# undef SNMALLOC_STATIC_LIBRARY_PREFIX
|
||||
# endif
|
||||
# ifdef SNMALLOC_FAIL_FAST
|
||||
# undef SNMALLOC_FAIL_FAST
|
||||
# endif
|
||||
# define SNMALLOC_FAIL_FAST false
|
||||
# define SNMALLOC_STATIC_LIBRARY_PREFIX my_
|
||||
# include "ds/defines.h"
|
||||
# ifndef SNMALLOC_PASS_THROUGH
|
||||
# include "override/malloc.cc"
|
||||
# else
|
||||
# define my_malloc(x) malloc(x)
|
||||
# define my_free(x) free(x)
|
||||
# endif
|
||||
# include "override/memcpy.cc"
|
||||
|
||||
# include <assert.h>
|
||||
# include <csetjmp>
|
||||
# include <stdio.h>
|
||||
# include <stdlib.h>
|
||||
# include <string.h>
|
||||
|
||||
/**
|
||||
* Jump buffer used to jump out of `abort()` for recoverable errors.
|
||||
*/
|
||||
static std::jmp_buf jmp;
|
||||
|
||||
/**
|
||||
* Flag indicating whether `jmp` is valid. If this is set then calls to
|
||||
* `abort` will jump to the jump buffer, rather than exiting.
|
||||
*/
|
||||
static bool can_longjmp;
|
||||
|
||||
/**
|
||||
* Replacement for the C standard `abort` that returns to the `setjmp` call for
|
||||
* recoverable errors.
|
||||
*/
|
||||
extern "C" void abort()
|
||||
{
|
||||
if (can_longjmp)
|
||||
{
|
||||
longjmp(jmp, 1);
|
||||
}
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that memcpy works in correct use. This allocates a pair of buffers,
|
||||
* fills one with a well-known pattern, and then copies subsets of this at
|
||||
* one-byte increments to a target. This gives us unaligned starts.
|
||||
*/
|
||||
void check_size(size_t size)
|
||||
{
|
||||
auto* s = reinterpret_cast<unsigned char*>(my_malloc(size + 1));
|
||||
auto* d = reinterpret_cast<unsigned char*>(my_malloc(size + 1));
|
||||
d[size] = 0;
|
||||
s[size] = 255;
|
||||
for (size_t start = 0; start < size; start++)
|
||||
{
|
||||
unsigned char* src = s + start;
|
||||
unsigned char* dst = d + start;
|
||||
size_t sz = (size - start);
|
||||
for (size_t i = 0; i < sz; ++i)
|
||||
{
|
||||
src[i] = static_cast<unsigned char>(i);
|
||||
}
|
||||
for (size_t i = 0; i < sz; ++i)
|
||||
{
|
||||
dst[i] = 0;
|
||||
}
|
||||
my_memcpy(dst, src, sz);
|
||||
for (size_t i = 0; i < sz; ++i)
|
||||
{
|
||||
if (dst[i] != static_cast<unsigned char>(i))
|
||||
{
|
||||
fprintf(
|
||||
stderr,
|
||||
"Testing size %zd %hhx == %hhx\n",
|
||||
sz,
|
||||
static_cast<unsigned char>(i),
|
||||
dst[i]);
|
||||
}
|
||||
SNMALLOC_CHECK(dst[i] == (unsigned char)i);
|
||||
}
|
||||
SNMALLOC_CHECK(d[size] == 0);
|
||||
}
|
||||
my_free(s);
|
||||
my_free(d);
|
||||
}
|
||||
|
||||
void check_bounds(size_t size, size_t out_of_bounds)
|
||||
{
|
||||
auto* s = reinterpret_cast<unsigned char*>(my_malloc(size));
|
||||
auto* d = reinterpret_cast<unsigned char*>(my_malloc(size));
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
s[i] = static_cast<unsigned char>(i);
|
||||
}
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
d[i] = 0;
|
||||
}
|
||||
bool bounds_error = false;
|
||||
can_longjmp = true;
|
||||
if (setjmp(jmp) == 0)
|
||||
{
|
||||
my_memcpy(d, s, size + out_of_bounds);
|
||||
}
|
||||
else
|
||||
{
|
||||
bounds_error = true;
|
||||
}
|
||||
can_longjmp = false;
|
||||
SNMALLOC_CHECK(bounds_error == (out_of_bounds > 0));
|
||||
my_free(s);
|
||||
my_free(d);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
// Skip the checks that expect bounds checks to fail when we are not the
|
||||
// malloc implementation.
|
||||
# if !defined(SNMALLOC_PASS_THROUGH)
|
||||
// Some sizes to check for out-of-bounds access
|
||||
std::initializer_list<size_t> sizes = {16, 1024, 2 * 1024 * 1024};
|
||||
for (auto sz : sizes)
|
||||
{
|
||||
// Check in bounds
|
||||
check_bounds(sz, 0);
|
||||
// Check one byte out
|
||||
check_bounds(sz, 1);
|
||||
// Check one object out of bounds
|
||||
check_bounds(sz, sz);
|
||||
}
|
||||
# endif
|
||||
for (size_t x = 0; x < 2048; x++)
|
||||
{
|
||||
check_size(x);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user