8304815: Use NMT for more precise hs_err location printing
Reviewed-by: jsjolen, rkennke
This commit is contained in:
parent
34e66ce1ef
commit
41a3db267d
src/hotspot/share
runtime
services
mallocHeader.hppmallocHeader.inline.hppmallocTracker.cppmallocTracker.hppmemTracker.cppmemTracker.hppvirtualMemoryTracker.cpp
utilities
test/hotspot/gtest
@ -1202,6 +1202,11 @@ void os::print_location(outputStream* st, intptr_t x, bool verbose) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Still nothing? If NMT is enabled, we can ask what it thinks...
|
||||
if (MemTracker::print_containing_region(addr, st)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try an OS specific find
|
||||
if (os::find(addr, st)) {
|
||||
return;
|
||||
|
@ -96,11 +96,11 @@ class MallocHeader {
|
||||
const uint8_t _unused;
|
||||
uint16_t _canary;
|
||||
|
||||
static const uint16_t _header_canary_life_mark = 0xE99E;
|
||||
static const uint16_t _header_canary_live_mark = 0xE99E;
|
||||
static const uint16_t _header_canary_dead_mark = 0xD99D;
|
||||
static const uint16_t _footer_canary_life_mark = 0xE88E;
|
||||
static const uint16_t _footer_canary_live_mark = 0xE88E;
|
||||
static const uint16_t _footer_canary_dead_mark = 0xD88D;
|
||||
NOT_LP64(static const uint32_t _header_alt_canary_life_mark = 0xE99EE99E;)
|
||||
NOT_LP64(static const uint32_t _header_alt_canary_live_mark = 0xE99EE99E;)
|
||||
NOT_LP64(static const uint32_t _header_alt_canary_dead_mark = 0xD88DD88D;)
|
||||
|
||||
// We discount sizes larger than these
|
||||
@ -139,6 +139,13 @@ public:
|
||||
inline void mark_block_as_dead();
|
||||
inline void revive();
|
||||
|
||||
|
||||
bool is_dead() const { return _canary == _header_canary_dead_mark; }
|
||||
bool is_live() const { return _canary == _header_canary_live_mark; }
|
||||
|
||||
// Used for debugging purposes only. Check header if it could constitute a valid (live or dead) header.
|
||||
inline bool looks_valid() const;
|
||||
|
||||
// If block is broken, fill in a short descriptive text in out,
|
||||
// an option pointer to the corruption in p_corruption, and return false.
|
||||
// Return true if block is fine.
|
||||
|
@ -36,22 +36,22 @@
|
||||
|
||||
inline MallocHeader::MallocHeader(size_t size, MEMFLAGS flags, uint32_t mst_marker)
|
||||
: _size(size), _mst_marker(mst_marker), _flags(flags),
|
||||
_unused(0), _canary(_header_canary_life_mark)
|
||||
_unused(0), _canary(_header_canary_live_mark)
|
||||
{
|
||||
assert(size < max_reasonable_malloc_size, "Too large allocation size?");
|
||||
// On 32-bit we have some bits more, use them for a second canary
|
||||
// guarding the start of the header.
|
||||
NOT_LP64(_alt_canary = _header_alt_canary_life_mark;)
|
||||
set_footer(_footer_canary_life_mark); // set after initializing _size
|
||||
NOT_LP64(_alt_canary = _header_alt_canary_live_mark;)
|
||||
set_footer(_footer_canary_live_mark); // set after initializing _size
|
||||
}
|
||||
|
||||
inline void MallocHeader::revive() {
|
||||
assert(_canary == _header_canary_dead_mark, "must be dead");
|
||||
assert(get_footer() == _footer_canary_dead_mark, "must be dead");
|
||||
NOT_LP64(assert(_alt_canary == _header_alt_canary_dead_mark, "must be dead"));
|
||||
_canary = _header_canary_life_mark;
|
||||
NOT_LP64(_alt_canary = _header_alt_canary_life_mark);
|
||||
set_footer(_footer_canary_life_mark);
|
||||
_canary = _header_canary_live_mark;
|
||||
NOT_LP64(_alt_canary = _header_alt_canary_live_mark);
|
||||
set_footer(_footer_canary_live_mark);
|
||||
}
|
||||
|
||||
// The effects of this method must be reversible with MallocHeader::revive()
|
||||
@ -116,12 +116,22 @@ inline const MallocHeader* MallocHeader::resolve_checked(const void* memblock) {
|
||||
return MallocHeader::resolve_checked_impl<const void*, const MallocHeader*>(memblock);
|
||||
}
|
||||
|
||||
|
||||
// Used for debugging purposes only. Check header if it could constitute a valid (live or dead) header.
|
||||
inline bool MallocHeader::looks_valid() const {
|
||||
// Note: we define these restrictions loose enough to also catch moderately corrupted blocks.
|
||||
// E.g. we don't check footer canary.
|
||||
return ( (_canary == _header_canary_live_mark NOT_LP64(&& _alt_canary == _header_alt_canary_live_mark)) ||
|
||||
(_canary == _header_canary_dead_mark NOT_LP64(&& _alt_canary == _header_alt_canary_dead_mark)) ) &&
|
||||
_size > 0 && _size < max_reasonable_malloc_size;
|
||||
}
|
||||
|
||||
inline bool MallocHeader::check_block_integrity(char* msg, size_t msglen, address* p_corruption) const {
|
||||
// Note: if you modify the error messages here, make sure you
|
||||
// adapt the associated gtests too.
|
||||
|
||||
// Check header canary
|
||||
if (_canary != _header_canary_life_mark) {
|
||||
if (_canary != _header_canary_live_mark) {
|
||||
*p_corruption = (address)this;
|
||||
jio_snprintf(msg, msglen, "header canary broken");
|
||||
return false;
|
||||
@ -129,7 +139,7 @@ inline bool MallocHeader::check_block_integrity(char* msg, size_t msglen, addres
|
||||
|
||||
#ifndef _LP64
|
||||
// On 32-bit we have a second canary, check that one too.
|
||||
if (_alt_canary != _header_alt_canary_life_mark) {
|
||||
if (_alt_canary != _header_alt_canary_live_mark) {
|
||||
*p_corruption = (address)this;
|
||||
jio_snprintf(msg, msglen, "header canary broken");
|
||||
return false;
|
||||
@ -144,7 +154,7 @@ inline bool MallocHeader::check_block_integrity(char* msg, size_t msglen, addres
|
||||
}
|
||||
|
||||
// Check footer canary
|
||||
if (get_footer() != _footer_canary_life_mark) {
|
||||
if (get_footer() != _footer_canary_live_mark) {
|
||||
*p_corruption = footer_address();
|
||||
jio_snprintf(msg, msglen, "footer canary broken at " PTR_FORMAT " (buffer overflow?)",
|
||||
p2i(footer_address()));
|
||||
|
@ -1,6 +1,8 @@
|
||||
/*
|
||||
* Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2021, 2023 SAP SE. All rights reserved.
|
||||
* Copyright (c) 2023, Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -190,31 +192,89 @@ void MallocTracker::deaccount(MallocHeader::FreeInfo free_info) {
|
||||
}
|
||||
}
|
||||
|
||||
// Given a pointer, if it seems to point to the start of a valid malloced block,
|
||||
// print the block. Note that since there is very low risk of memory looking
|
||||
// Given a pointer, look for the containing malloc block.
|
||||
// Print the block. Note that since there is very low risk of memory looking
|
||||
// accidentally like a valid malloc block header (canaries and all) this is not
|
||||
// totally failproof. Only use this during debugging or when you can afford
|
||||
// signals popping up, e.g. when writing an hs_err file.
|
||||
bool MallocTracker::print_pointer_information(const void* p, outputStream* st) {
|
||||
assert(MemTracker::enabled(), "NMT must be enabled");
|
||||
if (os::is_readable_pointer(p)) {
|
||||
const NMT_TrackingLevel tracking_level = MemTracker::tracking_level();
|
||||
const MallocHeader* mhdr = malloc_header(p);
|
||||
char msg[256];
|
||||
address p_corrupted;
|
||||
if (os::is_readable_pointer(mhdr) &&
|
||||
mhdr->check_block_integrity(msg, sizeof(msg), &p_corrupted)) {
|
||||
st->print_cr(PTR_FORMAT " malloc'd " SIZE_FORMAT " bytes by %s",
|
||||
p2i(p), mhdr->size(), NMTUtil::flag_to_name(mhdr->flags()));
|
||||
if (tracking_level == NMT_detail) {
|
||||
NativeCallStack ncs;
|
||||
if (mhdr->get_stack(ncs)) {
|
||||
ncs.print_on(st);
|
||||
st->cr();
|
||||
assert(MemTracker::enabled(), "NMT not enabled");
|
||||
|
||||
address addr = (address)p;
|
||||
|
||||
// Carefully feel your way upwards and try to find a malloc header. Then check if
|
||||
// we are within the block.
|
||||
// We give preference to found live blocks; but if no live block had been found,
|
||||
// but the pointer points into remnants of a dead block, print that instead.
|
||||
const MallocHeader* likely_dead_block = nullptr;
|
||||
const MallocHeader* likely_live_block = nullptr;
|
||||
{
|
||||
const size_t smallest_possible_alignment = sizeof(void*);
|
||||
const uint8_t* here = align_down(addr, smallest_possible_alignment);
|
||||
const uint8_t* const end = here - (0x1000 + sizeof(MallocHeader)); // stop searching after 4k
|
||||
for (; here >= end; here -= smallest_possible_alignment) {
|
||||
if (!os::is_readable_pointer(here)) {
|
||||
// Probably OOB, give up
|
||||
return false;
|
||||
}
|
||||
const MallocHeader* const candidate = (const MallocHeader*)here;
|
||||
if (!candidate->looks_valid()) {
|
||||
// This is definitely not a header, go on to the next candidate.
|
||||
continue;
|
||||
}
|
||||
|
||||
// fudge factor:
|
||||
// We don't report blocks for which p is clearly outside of. That would cause us to return true and possibly prevent
|
||||
// subsequent tests of p, see os::print_location(). But if p is just outside of the found block, this may be a
|
||||
// narrow oob error and we'd like to know that.
|
||||
const int fudge = 8;
|
||||
const address start_block = (address)candidate;
|
||||
const address start_payload = (address)(candidate + 1);
|
||||
const address end_payload = start_payload + candidate->size();
|
||||
const address end_payload_plus_fudge = end_payload + fudge;
|
||||
if (addr >= start_block && addr < end_payload_plus_fudge) {
|
||||
// We found a block the pointer is pointing into, or almost into.
|
||||
// If its a live block, we have our info. If its a dead block, we still
|
||||
// may be within the borders of a larger live block we have not found yet -
|
||||
// continue search.
|
||||
if (candidate->is_live()) {
|
||||
likely_live_block = candidate;
|
||||
break;
|
||||
} else {
|
||||
likely_dead_block = candidate;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// If we've found a reasonable candidate. Print the info.
|
||||
const MallocHeader* block = likely_live_block != nullptr ? likely_live_block : likely_dead_block;
|
||||
if (block != nullptr) {
|
||||
const char* where = nullptr;
|
||||
const address start_block = (address)block;
|
||||
const address start_payload = (address)(block + 1);
|
||||
const address end_payload = start_payload + block->size();
|
||||
if (addr < start_payload) {
|
||||
where = "into header of";
|
||||
} else if (addr < end_payload) {
|
||||
where = "into";
|
||||
} else {
|
||||
where = "just outside of";
|
||||
}
|
||||
st->print_cr(PTR_FORMAT " %s %s malloced block starting at " PTR_FORMAT ", size " SIZE_FORMAT ", tag %s",
|
||||
p2i(p), where,
|
||||
(block->is_dead() ? "dead" : "live"),
|
||||
p2i(block + 1), // lets print the payload start, not the header
|
||||
block->size(), NMTUtil::flag_to_enum_name(block->flags()));
|
||||
if (MemTracker::tracking_level() == NMT_detail) {
|
||||
NativeCallStack ncs;
|
||||
if (block->get_stack(ncs)) {
|
||||
ncs.print_on(st);
|
||||
st->cr();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -308,8 +308,8 @@ class MallocTracker : AllStatic {
|
||||
// under category f would hit either the global limit or the limit for category f.
|
||||
static inline bool check_exceeds_limit(size_t s, MEMFLAGS f);
|
||||
|
||||
// Given a pointer, if it seems to point to the start of a valid malloced block,
|
||||
// print the block. Note that since there is very low risk of memory looking
|
||||
// Given a pointer, look for the containing malloc block.
|
||||
// Print the block. Note that since there is very low risk of memory looking
|
||||
// accidentally like a valid malloc block header (canaries and all) this is not
|
||||
// totally failproof. Only use this during debugging or when you can afford
|
||||
// signals popping up, e.g. when writing an hs_err file.
|
||||
|
@ -132,6 +132,14 @@ void MemTracker::final_report(outputStream* output) {
|
||||
}
|
||||
}
|
||||
|
||||
// Given an unknown pointer, check if it points into a known region; print region if found
|
||||
// and return true; false if not found.
|
||||
bool MemTracker::print_containing_region(const void* p, outputStream* out) {
|
||||
return enabled() &&
|
||||
(MallocTracker::print_pointer_information(p, out) ||
|
||||
VirtualMemoryTracker::print_containing_region(p, out));
|
||||
}
|
||||
|
||||
void MemTracker::report(bool summary_only, outputStream* output, size_t scale) {
|
||||
assert(output != nullptr, "No output stream");
|
||||
MemBaseline baseline;
|
||||
|
@ -234,6 +234,10 @@ class MemTracker : AllStatic {
|
||||
// under category f would hit either the global limit or the limit for category f.
|
||||
static inline bool check_exceeds_limit(size_t s, MEMFLAGS f);
|
||||
|
||||
// Given an unknown pointer, check if it points into a known region; print region if found
|
||||
// and return true; false if not found.
|
||||
static bool print_containing_region(const void* p, outputStream* out);
|
||||
|
||||
private:
|
||||
static void report(bool summary_only, outputStream* output, size_t scale);
|
||||
|
||||
|
@ -679,8 +679,8 @@ public:
|
||||
|
||||
bool do_allocation_site(const ReservedMemoryRegion* rgn) {
|
||||
if (rgn->contain_address(_p)) {
|
||||
_st->print_cr(PTR_FORMAT " in mmap'd memory region [" PTR_FORMAT " - " PTR_FORMAT "] by %s",
|
||||
p2i(_p), p2i(rgn->base()), p2i(rgn->base() + rgn->size()), rgn->flag_name());
|
||||
_st->print_cr(PTR_FORMAT " in mmap'd memory region [" PTR_FORMAT " - " PTR_FORMAT "], tag %s",
|
||||
p2i(_p), p2i(rgn->base()), p2i(rgn->base() + rgn->size()), NMTUtil::flag_to_enum_name(rgn->flag()));
|
||||
if (MemTracker::tracking_level() == NMT_detail) {
|
||||
rgn->call_stack()->print_on(_st);
|
||||
_st->cr();
|
||||
|
@ -398,15 +398,8 @@ extern "C" JNIEXPORT void pp(void* p) {
|
||||
// catch the signal and disable the pp() command for further use.
|
||||
// In order to avoid that, switch off SIGSEGV handling with "handle SIGSEGV nostop" before
|
||||
// invoking pp()
|
||||
if (MemTracker::enabled()) {
|
||||
// Does it point into a known mmapped region?
|
||||
if (VirtualMemoryTracker::print_containing_region(p, tty)) {
|
||||
return;
|
||||
}
|
||||
// Does it look like the start of a malloced block?
|
||||
if (MallocTracker::print_pointer_information(p, tty)) {
|
||||
return;
|
||||
}
|
||||
if (MemTracker::print_containing_region(p, tty)) {
|
||||
return;
|
||||
}
|
||||
tty->print_cr(PTR_FORMAT, p2i(p));
|
||||
}
|
||||
|
@ -33,6 +33,7 @@
|
||||
#include "gc/shared/gcLogPrecious.hpp"
|
||||
#include "jvm.h"
|
||||
#include "logging/logConfiguration.hpp"
|
||||
#include "memory/allocation.hpp"
|
||||
#include "memory/metaspace.hpp"
|
||||
#include "memory/metaspaceUtils.hpp"
|
||||
#include "memory/resourceArea.inline.hpp"
|
||||
|
123
test/hotspot/gtest/nmt/test_nmt_locationprinting.cpp
Normal file
123
test/hotspot/gtest/nmt/test_nmt_locationprinting.cpp
Normal file
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2023, Red Hat, Inc. and/or its affiliates.
|
||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
#include "precompiled.hpp"
|
||||
#include "memory/allocation.hpp"
|
||||
#include "runtime/os.hpp"
|
||||
#include "services/mallocHeader.inline.hpp"
|
||||
#include "services/memTracker.hpp"
|
||||
#include "unittest.hpp"
|
||||
|
||||
// Uncomment to get test output
|
||||
//#define LOG_PLEASE
|
||||
#include "testutils.hpp"
|
||||
|
||||
using ::testing::HasSubstr;
|
||||
|
||||
static void test_pointer(const void* p, bool expected_return_code, const char* expected_message) {
|
||||
stringStream ss;
|
||||
const bool b = MemTracker::print_containing_region(p, &ss);
|
||||
LOG_HERE("MemTracker::print_containing_region(" PTR_FORMAT ") yielded: %d \"%s\"", p2i(p), b, ss.base());
|
||||
EXPECT_EQ(b, expected_return_code);
|
||||
if (b) {
|
||||
EXPECT_THAT(ss.base(), HasSubstr(expected_message));
|
||||
}
|
||||
}
|
||||
|
||||
static void test_for_live_c_heap_block(size_t sz, ssize_t offset) {
|
||||
char* c = NEW_C_HEAP_ARRAY(char, sz, mtTest);
|
||||
LOG_HERE("C-block starts " PTR_FORMAT ", size " SIZE_FORMAT ".", p2i(c), offset);
|
||||
memset(c, 0, sz);
|
||||
if (MemTracker::enabled()) {
|
||||
const char* expected_string = "into live malloced block";
|
||||
if (offset < 0) {
|
||||
expected_string = "into header of live malloced block";
|
||||
} else if ((size_t)offset >= sz) {
|
||||
expected_string = "just outside of live malloced block";
|
||||
}
|
||||
test_pointer(c + offset, true, expected_string);
|
||||
} else {
|
||||
// NMT disabled: we should see nothing.
|
||||
test_pointer(c + offset, false, "");
|
||||
}
|
||||
FREE_C_HEAP_ARRAY(char, c);
|
||||
}
|
||||
|
||||
static void test_for_dead_c_heap_block(size_t sz, ssize_t offset) {
|
||||
if (!MemTracker::enabled()) {
|
||||
return;
|
||||
}
|
||||
char* c = NEW_C_HEAP_ARRAY(char, sz, mtTest);
|
||||
LOG_HERE("C-block starts " PTR_FORMAT ", size " SIZE_FORMAT ".", p2i(c), offset);
|
||||
memset(c, 0, sz);
|
||||
// We cannot just free the allocation to try dead block printing, since the memory
|
||||
// may be immediately reused by concurrent code. Instead, we mark the block as dead
|
||||
// manually, and revert that before freeing it.
|
||||
MallocHeader* const hdr = MallocHeader::resolve_checked(c);
|
||||
hdr->mark_block_as_dead();
|
||||
|
||||
const char* expected_string = "into dead malloced block";
|
||||
if (offset < 0) {
|
||||
expected_string = "into header of dead malloced block";
|
||||
} else if ((size_t)offset >= sz) {
|
||||
expected_string = "just outside of dead malloced block";
|
||||
}
|
||||
|
||||
test_pointer(c + offset, true, expected_string);
|
||||
|
||||
hdr->revive();
|
||||
FREE_C_HEAP_ARRAY(char, c);
|
||||
}
|
||||
|
||||
TEST_VM(NMT, location_printing_cheap_live_1) { test_for_live_c_heap_block(2 * K, 0); } // start of payload
|
||||
TEST_VM(NMT, location_printing_cheap_live_2) { test_for_live_c_heap_block(2 * K, -7); } // into header
|
||||
TEST_VM(NMT, location_printing_cheap_live_3) { test_for_live_c_heap_block(2 * K, K + 1); } // into payload
|
||||
TEST_VM(NMT, location_printing_cheap_live_4) { test_for_live_c_heap_block(2 * K, K + 2); } // into payload (check for even/odd errors)
|
||||
TEST_VM(NMT, location_printing_cheap_live_5) { test_for_live_c_heap_block(2 * K + 1, 2 * K + 2); } // just outside payload
|
||||
TEST_VM(NMT, location_printing_cheap_live_6) { test_for_live_c_heap_block(4, 0); } // into a very small block
|
||||
TEST_VM(NMT, location_printing_cheap_live_7) { test_for_live_c_heap_block(4, 4); } // just outside a very small block
|
||||
|
||||
#ifdef LINUX
|
||||
TEST_VM(NMT, location_printing_cheap_dead_1) { test_for_dead_c_heap_block(2 * K, 0); } // start of payload
|
||||
TEST_VM(NMT, location_printing_cheap_dead_2) { test_for_dead_c_heap_block(2 * K, -7); } // into header
|
||||
TEST_VM(NMT, location_printing_cheap_dead_3) { test_for_dead_c_heap_block(2 * K, K + 1); } // into payload
|
||||
TEST_VM(NMT, location_printing_cheap_dead_4) { test_for_dead_c_heap_block(2 * K, K + 2); } // into payload (check for even/odd errors)
|
||||
TEST_VM(NMT, location_printing_cheap_dead_5) { test_for_dead_c_heap_block(2 * K + 1, 2 * K + 2); } // just outside payload
|
||||
TEST_VM(NMT, location_printing_cheap_dead_6) { test_for_dead_c_heap_block(4, 0); } // into a very small block
|
||||
TEST_VM(NMT, location_printing_cheap_dead_7) { test_for_dead_c_heap_block(4, 4); } // just outside a very small block
|
||||
#endif
|
||||
|
||||
static void test_for_mmap(size_t sz, ssize_t offset) {
|
||||
char* addr = os::reserve_memory(sz, false, mtTest);
|
||||
if (MemTracker::enabled()) {
|
||||
test_pointer(addr + offset, true, "in mmap'd memory region");
|
||||
} else {
|
||||
// NMT disabled: we should see nothing.
|
||||
test_pointer(addr + offset, false, "");
|
||||
}
|
||||
os::release_memory(addr, os::vm_page_size());
|
||||
}
|
||||
|
||||
TEST_VM(NMT, location_printing_mmap_1) { test_for_mmap(os::vm_page_size(), 0); }
|
||||
TEST_VM(NMT, location_printing_mmap_2) { test_for_mmap(os::vm_page_size(), os::vm_page_size() - 1); }
|
@ -30,8 +30,8 @@
|
||||
#include "unittest.hpp"
|
||||
|
||||
// convenience log. switch on if debugging tests. Don't use tty, plain stdio only.
|
||||
#define LOG(...) { printf(__VA_ARGS__); printf("\n"); fflush(stdout); }
|
||||
//#define LOG(...)
|
||||
//#define LOG(...) { printf(__VA_ARGS__); printf("\n"); fflush(stdout); }
|
||||
#define LOG(...)
|
||||
|
||||
static size_t get_total_malloc_invocs() {
|
||||
return MallocMemorySummary::as_snapshot()->total_count();
|
||||
|
@ -62,6 +62,10 @@ public:
|
||||
|
||||
#define ASSERT_ALIGN(p, n) ASSERT_TRUE(is_aligned(p, n))
|
||||
|
||||
#ifdef LOG_PLEASE
|
||||
#define LOG_HERE(s, ...) { printf(s, __VA_ARGS__); printf("\n"); fflush(stdout); }
|
||||
#else
|
||||
#define LOG_HERE(s, ...)
|
||||
#endif
|
||||
|
||||
#endif // TESTUTILS_HPP
|
||||
|
Loading…
x
Reference in New Issue
Block a user