8369527: NMT: print malloc-site when a malloc'd memory detected as corrupted

Reviewed-by: dholmes, jsjolen
This commit is contained in:
Afshin Zafari 2025-10-20 11:32:48 +00:00
parent 73923601d8
commit c867971340
7 changed files with 199 additions and 5 deletions

View File

@ -26,6 +26,7 @@
#include "nmt/mallocHeader.inline.hpp"
#include "nmt/mallocSiteTable.hpp"
#include "nmt/memTag.hpp"
#include "nmt/memTracker.hpp"
#include "runtime/os.hpp"
#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"
@ -36,7 +37,7 @@
// fitting into eight bits.
STATIC_ASSERT(sizeof(MemTag) == sizeof(uint8_t));
void MallocHeader::print_block_on_error(outputStream* st, address bad_address) const {
void MallocHeader::print_block_on_error(outputStream* st, address bad_address, address block_address) const {
assert(bad_address >= (address)this, "sanity");
// This function prints block information, including hex dump, in case of a detected
@ -48,6 +49,18 @@ void MallocHeader::print_block_on_error(outputStream* st, address bad_address) c
st->print_cr("NMT Block at " PTR_FORMAT ", corruption at: " PTR_FORMAT ": ",
p2i(this), p2i(bad_address));
if (MemTracker::tracking_level() == NMT_TrackingLevel::NMT_detail) {
MallocHeader* mh = (MallocHeader*)block_address;
NativeCallStack stack;
if (MallocSiteTable::access_stack(stack, *mh)) {
st->print_cr("allocated from:");
stack.print_on(st);
} else {
st->print_cr("allocation-site cannot be shown since the marker is also corrupted.");
}
st->print_cr("");
}
static const size_t min_dump_length = 256;
address from1 = align_down((address)this, sizeof(void*)) - (min_dump_length / 2);
address to1 = from1 + min_dump_length;

View File

@ -106,7 +106,7 @@ class MallocHeader {
// We discount sizes larger than these
static const size_t max_reasonable_malloc_size = LP64_ONLY(256 * G) NOT_LP64(3500 * M);
void print_block_on_error(outputStream* st, address bad_address) const;
void print_block_on_error(outputStream* st, address bad_address, address block_address) const;
static uint16_t build_footer(uint8_t b1, uint8_t b2) { return (uint16_t)(((uint16_t)b1 << 8) | (uint16_t)b2); }

View File

@ -103,7 +103,7 @@ inline OutTypeParam MallocHeader::resolve_checked_impl(InTypeParam memblock) {
}
OutTypeParam header_pointer = (OutTypeParam)memblock - 1;
if (!header_pointer->check_block_integrity(msg, sizeof(msg), &corruption)) {
header_pointer->print_block_on_error(tty, corruption != nullptr ? corruption : (address)header_pointer);
header_pointer->print_block_on_error(tty, corruption != nullptr ? corruption : (address)header_pointer, (address)header_pointer);
fatal("NMT has detected a memory corruption bug. Block at " PTR_FORMAT ": %s", p2i(memblock), msg);
}
return header_pointer;

View File

@ -163,13 +163,17 @@ MallocSite* MallocSiteTable::lookup_or_add(const NativeCallStack& key, uint32_t*
// Access malloc site
MallocSite* MallocSiteTable::malloc_site(uint32_t marker) {
uint16_t bucket_idx = bucket_idx_from_marker(marker);
assert(bucket_idx < table_size, "Invalid bucket index");
if (bucket_idx >= table_size) {
return nullptr;
}
const uint16_t pos_idx = pos_idx_from_marker(marker);
MallocSiteHashtableEntry* head = _table[bucket_idx];
for (size_t index = 0;
index < pos_idx && head != nullptr;
index++, head = (MallocSiteHashtableEntry*)head->next()) {}
assert(head != nullptr, "Invalid position index");
if (head == nullptr) {
return nullptr;
}
return head->data();
}

View File

@ -164,4 +164,14 @@ TEST_VM(NMT, test_realloc) {
}
}
TEST_VM_FATAL_ERROR_MSG(NMT, memory_corruption_call_stack, ".*header canary.*") {
if (MemTracker::tracking_level() != NMT_detail) {
guarantee(false, "fake message ignore this - header canary");
}
const size_t SIZE = 1024;
char* p = (char*)os::malloc(SIZE, mtTest);
*(p - 1) = 0;
os::free(p);
}
#endif // !INCLUDE_ASAN

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) 2025, 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 021MALLOC_SIZE-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.
*/
/*
* @test
* @summary Check the allocation-site stack trace of a corrupted memory at free() time
* @modules java.base/jdk.internal.misc
* @library /test/lib
* @build jdk.test.whitebox.WhiteBox
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:NativeMemoryTracking=detail NMTPrintMallocSiteOfCorruptedMemory
*/
import jdk.test.lib.Utils;
import jdk.test.lib.process.ProcessTools;
import jdk.test.lib.process.OutputAnalyzer;
import jdk.test.whitebox.WhiteBox;
public class NMTPrintMallocSiteOfCorruptedMemory {
private static final String HEADER_ARG = "header";
private static final String FOOTER_ARG = "footer";
private static final String HEADER_AND_SITE_ARG = "header-and-site";
private static final String FOOTER_AND_SITE_ARG = "footer-and-site";
private static final int MALLOC_SIZE = 10;
private static WhiteBox wb = WhiteBox.getWhiteBox();
static {
System.loadLibrary("MallocHeaderModifier");
}
public static native byte modifyHeaderCanary(long malloc_memory);
public static native byte modifyFooterCanary(long malloc_memory, long size);
public static native byte modifyHeaderCanaryAndSiteMarker(long malloc_memory);
public static native byte modifyFooterCanaryAndSiteMarker(long malloc_memory, long size);
private static void runThisTestWith(String arg) throws Exception {
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(new String[] {"-Xbootclasspath/a:.",
"-XX:+UnlockDiagnosticVMOptions",
"-XX:+WhiteBoxAPI",
"-XX:NativeMemoryTracking=detail",
"-Djava.library.path=" + Utils.TEST_NATIVE_PATH,
"NMTPrintMallocSiteOfCorruptedMemory",
arg});
OutputAnalyzer output = new OutputAnalyzer(pb.start());
output.shouldMatch("NMT Block at .*, corruption at: ");
switch(arg) {
case HEADER_AND_SITE_ARG, FOOTER_AND_SITE_ARG -> output.shouldContain("allocation-site cannot be shown since the marker is also corrupted.");
case HEADER_ARG, FOOTER_ARG -> {
output.shouldContain("allocated from:");
output.shouldMatch("\\[.*\\]WB_NMTMalloc\\+0x.*");
}
}
}
private static void testModifyHeaderCanary() {
long addr = wb.NMTMalloc(MALLOC_SIZE);
modifyHeaderCanary(addr);
wb.NMTFree(addr);
}
private static void testModifyFooterCanary() {
long addr = wb.NMTMalloc(MALLOC_SIZE);
modifyFooterCanary(addr, MALLOC_SIZE);
wb.NMTFree(addr);
}
private static void testModifyHeaderCanaryAndSiteMarker() {
long addr = wb.NMTMalloc(MALLOC_SIZE);
modifyHeaderCanaryAndSiteMarker(addr);
wb.NMTFree(addr);
}
private static void testModifyFooterCanaryAndSiteMarker() {
long addr = wb.NMTMalloc(MALLOC_SIZE);
modifyFooterCanaryAndSiteMarker(addr, MALLOC_SIZE);
wb.NMTFree(addr);
}
public static void main(String args[]) throws Exception {
if (args != null && args.length == 1) {
switch (args[0]) {
case HEADER_ARG -> testModifyHeaderCanary();
case FOOTER_ARG -> testModifyFooterCanary();
case HEADER_AND_SITE_ARG -> testModifyHeaderCanaryAndSiteMarker();
case FOOTER_AND_SITE_ARG -> testModifyFooterCanaryAndSiteMarker();
default -> throw new RuntimeException("Invalid argument for NMTPrintMallocSiteOfCorruptedMemory (" + args[0] + ")");
}
} else {
runThisTestWith(HEADER_ARG);
runThisTestWith(FOOTER_ARG);
runThisTestWith(HEADER_AND_SITE_ARG);
runThisTestWith(FOOTER_AND_SITE_ARG);
}
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2025, 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 "jni.h"
#include <stdint.h>
#include <string.h>
JNIEXPORT jint JNICALL
Java_NMTPrintMallocSiteOfCorruptedMemory_modifyHeaderCanary(JNIEnv *env, jclass cls, jlong addr) {
*((jint*)(uintptr_t)addr - 1) = 0;
return 0;
}
JNIEXPORT jint JNICALL
Java_NMTPrintMallocSiteOfCorruptedMemory_modifyFooterCanary(JNIEnv *env, jclass cls, jlong addr, jint size) {
*((jbyte*)(uintptr_t)addr + size + 1) = 0;
return 0;
}
JNIEXPORT jint JNICALL
Java_NMTPrintMallocSiteOfCorruptedMemory_modifyHeaderCanaryAndSiteMarker(JNIEnv *env, jclass cls, jlong addr) {
jbyte* p = (jbyte*)(uintptr_t)addr - 16;
memset(p, 0xFF , 16);
return 0;
}
JNIEXPORT jint JNICALL
Java_NMTPrintMallocSiteOfCorruptedMemory_modifyFooterCanaryAndSiteMarker(JNIEnv *env, jclass cls, jlong addr, jint size) {
jbyte* p = (jbyte*)(uintptr_t)addr - 16;
memset(p, 0xFF , 16);
*((jbyte*)(uintptr_t)addr + size + 1) = 0;
return 0;
}