Skip to content

Commit

Permalink
Extend gtests
Browse files Browse the repository at this point in the history
  • Loading branch information
tstuefe committed Nov 19, 2021
1 parent 2247b5e commit 188f0ea
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 14 deletions.
14 changes: 8 additions & 6 deletions src/hotspot/share/services/mallocTracker.cpp
Expand Up @@ -160,14 +160,15 @@ void MallocHeader::print_block_on_error(outputStream* st, address bad_address) c
// then trigger a fatal error.
void MallocHeader::check_block_integrity() const {

#define PREFIX "NMT corruption: "
// Note: if you modify the error messages here, make sure you
// adapt the associated gtests too.

// Weed out obviously wrong block addresses of NULL or very low
// values. Note that we should not call this for ::free(NULL),
// which should be handled by os::free() above us.
if (((size_t)p2i(this)) < K) {
fatal("Block at " PTR_FORMAT ": invalid block address", p2i(this));
fatal(PREFIX "Block at " PTR_FORMAT ": invalid block address", p2i(this));
}

// From here on we assume the block pointer to be valid. We could
Expand All @@ -187,35 +188,36 @@ void MallocHeader::check_block_integrity() const {
// fix up.
if (!is_aligned(this, sizeof(uint64_t))) {
print_block_on_error(tty, (address)this);
fatal("Block at " PTR_FORMAT ": block address is unaligned", p2i(this));
fatal(PREFIX "Block at " PTR_FORMAT ": block address is unaligned", p2i(this));
}

// Check header canary
if (_canary != _header_canary_life_mark) {
print_block_on_error(tty, (address)this);
fatal("Block at " PTR_FORMAT ": header canary broken.", p2i(this));
fatal(PREFIX "Block at " PTR_FORMAT ": header canary broken.", p2i(this));
}

#ifndef _LP64
// On 32-bit we have a second canary, check that one too.
if (_alt_canary != _header_alt_canary_life_mark) {
print_block_on_error(tty, (address)this);
fatal("Block at " PTR_FORMAT ": header alternate canary broken.", p2i(this));
fatal(PREFIX "Block at " PTR_FORMAT ": header alternate canary broken.", p2i(this));
}
#endif

// Does block size seems reasonable?
if (_size >= max_reasonable_malloc_size) {
print_block_on_error(tty, (address)this);
fatal("Block at " PTR_FORMAT ": header looks invalid (weirdly large block size)", p2i(this));
fatal(PREFIX "Block at " PTR_FORMAT ": header looks invalid (weirdly large block size)", p2i(this));
}

// Check footer canary
if (get_footer() != _footer_canary_life_mark) {
print_block_on_error(tty, footer_address());
fatal("Block at " PTR_FORMAT ": footer canary broken at " PTR_FORMAT " (buffer overflow?)",
fatal(PREFIX "Block at " PTR_FORMAT ": footer canary broken at " PTR_FORMAT " (buffer overflow?)",
p2i(this), p2i(footer_address()));
}
#undef PREFIX
}

bool MallocHeader::record_malloc_site(const NativeCallStack& stack, size_t size,
Expand Down
59 changes: 54 additions & 5 deletions test/hotspot/gtest/nmt/test_nmt_buffer_overflow_detection.cpp
Expand Up @@ -29,9 +29,16 @@
#include "utilities/debug.hpp"
#include "utilities/ostream.hpp"
#include "unittest.hpp"
#include "testutils.hpp"

#if INCLUDE_NMT

// This prefix shows up on any c heap corruption NMT detects. If unsure which assert will
// come, just use this one.
#define COMMON_NMT_HEAP_CORRUPTION_MESSAGE_PREFIX "NMT corruption"



#define DEFINE_TEST(test_function, expected_assertion_message) \
TEST_VM_FATAL_ERROR_MSG(NMT, test_function, ".*" expected_assertion_message ".*") { \
if (MemTracker::tracking_level() > NMT_off) { \
Expand Down Expand Up @@ -68,13 +75,15 @@ DEFINE_TEST(test_overwrite_back, "footer canary broken")

// A overwriter farther away from the NMT header; the report should show the hex dump split up
// in two parts, containing both header and corruption site.
static void test_overwrite_back_long() {
address p = (address) os::malloc(0x2000, mtTest);
*(p + 0x2000) = 'a';
static void test_overwrite_back_long(size_t distance) {
address p = (address) os::malloc(distance, mtTest);
*(p + distance) = 'a';
os::free(p);
}

DEFINE_TEST(test_overwrite_back_long, "footer canary broken")
static void test_overwrite_back_long_aligned_distance() { test_overwrite_back_long(0x2000); }
DEFINE_TEST(test_overwrite_back_long_aligned_distance, "footer canary broken")
static void test_overwrite_back_long_unaligned_distance() { test_overwrite_back_long(0x2001); }
DEFINE_TEST(test_overwrite_back_long_unaligned_distance, "footer canary broken")

///////

Expand Down Expand Up @@ -117,4 +126,44 @@ static void test_unaliged_block_address() {
}
DEFINE_TEST(test_unaliged_block_address, "block address is unaligned");

///////

// Test that we notice block corruption on realloc too
static void test_corruption_on_realloc(size_t s1, size_t s2) {
address p1 = (address) os::malloc(s1, mtTest);
*(p1 + s1) = 'a';
address p2 = (address) os::realloc(p1, s2, mtTest);

// Still here?
tty->print_cr("NMT did not detect corruption on os::realloc?");
// Note: don't use ASSERT here, that does not work as expected in death tests. Just
// let the test run its course, it should notice something is amiss.
}
static void test_corruption_on_realloc_growing() { test_corruption_on_realloc(0x10, 0x11); }
DEFINE_TEST(test_corruption_on_realloc_growing, COMMON_NMT_HEAP_CORRUPTION_MESSAGE_PREFIX);
static void test_corruption_on_realloc_shrinking() { test_corruption_on_realloc(0x11, 0x10); }
DEFINE_TEST(test_corruption_on_realloc_shrinking, COMMON_NMT_HEAP_CORRUPTION_MESSAGE_PREFIX);

///////

// realloc is the trickiest of the bunch. Test that realloc works and correctly takes over
// NMT header and footer to the resized block. We just test that nothing crashes - if the
// header/footer get corrupted, NMT heap corruption checker will trigger alert on os::free()).
TEST_VM(NMT, test_realloc) {
// We test both directions (growing and shrinking) and a small range for each to cover all
// size alignment variants. Should not matter, but this should be cheap.
for (size_t s1 = 0xF0; s1 < 0x110; s1 ++) {
for (size_t s2 = 0x100; s2 > 0xF0; s2 --) {
address p1 = (address) os::malloc(s1, mtTest);
ASSERT_NOT_NULL(p1);
GtestUtils::mark_range(p1, s1); // mark payload range...
address p2 = (address) os::realloc(p1, s2, mtTest);
ASSERT_NOT_NULL(p2);
ASSERT_RANGE_IS_MARKED(p2, MIN2(s1, s2)) // ... and check that it survived the resize
<< s1 << "->" << s2 << std::endl;
os::free(p2); // <- if NMT headers/footers got corrupted this asserts
}
}
}

#endif // INCLUDE_NMT
3 changes: 2 additions & 1 deletion test/hotspot/gtest/testutils.cpp
Expand Up @@ -57,7 +57,8 @@ bool GtestUtils::check_range(const void* p, size_t s, uint8_t expected) {
}

if (first_wrong != NULL) {
tty->print_cr("wrong pattern around " PTR_FORMAT, p2i(first_wrong));
tty->print_cr("check_range [" PTR_FORMAT ".." PTR_FORMAT "), 0x%X, : wrong pattern around " PTR_FORMAT,
p2i(p), p2i(p) + s, expected, p2i(first_wrong));
// Note: We deliberately print the surroundings too without bounds check. Might be interesting,
// and os::print_hex_dump uses SafeFetch, so this is fine without bounds checks.
os::print_hex_dump(tty, (address)(align_down(p2, 0x10) - 0x10),
Expand Down
4 changes: 2 additions & 2 deletions test/hotspot/gtest/testutils.hpp
Expand Up @@ -51,8 +51,8 @@ class GtestUtils : public AllStatic {
#define ASSERT_RANGE_IS_MARKED(p, size) ASSERT_TRUE(GtestUtils::check_range(p, size))

// Convenience asserts
#define ASSERT_NOT_NULL(p) ASSERT_NE(p, (char*)NULL)
#define ASSERT_NULL(p) ASSERT_EQ(p, (char*)NULL)
#define ASSERT_NOT_NULL(p) ASSERT_NE(p2i(p), 0)
#define ASSERT_NULL(p) ASSERT_EQ(p2i(p), 0)

#define ASSERT_ALIGN(p, n) ASSERT_TRUE(is_aligned(p, n))

Expand Down

0 comments on commit 188f0ea

Please sign in to comment.