Skip to content

Commit c867971

Browse files
author
Afshin Zafari
committed
8369527: NMT: print malloc-site when a malloc'd memory detected as corrupted
Reviewed-by: dholmes, jsjolen
1 parent 7392360 commit c867971

File tree

7 files changed

+199
-5
lines changed

7 files changed

+199
-5
lines changed

src/hotspot/share/nmt/mallocHeader.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "nmt/mallocHeader.inline.hpp"
2727
#include "nmt/mallocSiteTable.hpp"
2828
#include "nmt/memTag.hpp"
29+
#include "nmt/memTracker.hpp"
2930
#include "runtime/os.hpp"
3031
#include "utilities/debug.hpp"
3132
#include "utilities/globalDefinitions.hpp"
@@ -36,7 +37,7 @@
3637
// fitting into eight bits.
3738
STATIC_ASSERT(sizeof(MemTag) == sizeof(uint8_t));
3839

39-
void MallocHeader::print_block_on_error(outputStream* st, address bad_address) const {
40+
void MallocHeader::print_block_on_error(outputStream* st, address bad_address, address block_address) const {
4041
assert(bad_address >= (address)this, "sanity");
4142

4243
// 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
4849

4950
st->print_cr("NMT Block at " PTR_FORMAT ", corruption at: " PTR_FORMAT ": ",
5051
p2i(this), p2i(bad_address));
52+
if (MemTracker::tracking_level() == NMT_TrackingLevel::NMT_detail) {
53+
MallocHeader* mh = (MallocHeader*)block_address;
54+
NativeCallStack stack;
55+
if (MallocSiteTable::access_stack(stack, *mh)) {
56+
st->print_cr("allocated from:");
57+
stack.print_on(st);
58+
} else {
59+
st->print_cr("allocation-site cannot be shown since the marker is also corrupted.");
60+
}
61+
st->print_cr("");
62+
}
63+
5164
static const size_t min_dump_length = 256;
5265
address from1 = align_down((address)this, sizeof(void*)) - (min_dump_length / 2);
5366
address to1 = from1 + min_dump_length;

src/hotspot/share/nmt/mallocHeader.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class MallocHeader {
106106
// We discount sizes larger than these
107107
static const size_t max_reasonable_malloc_size = LP64_ONLY(256 * G) NOT_LP64(3500 * M);
108108

109-
void print_block_on_error(outputStream* st, address bad_address) const;
109+
void print_block_on_error(outputStream* st, address bad_address, address block_address) const;
110110

111111
static uint16_t build_footer(uint8_t b1, uint8_t b2) { return (uint16_t)(((uint16_t)b1 << 8) | (uint16_t)b2); }
112112

src/hotspot/share/nmt/mallocHeader.inline.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ inline OutTypeParam MallocHeader::resolve_checked_impl(InTypeParam memblock) {
103103
}
104104
OutTypeParam header_pointer = (OutTypeParam)memblock - 1;
105105
if (!header_pointer->check_block_integrity(msg, sizeof(msg), &corruption)) {
106-
header_pointer->print_block_on_error(tty, corruption != nullptr ? corruption : (address)header_pointer);
106+
header_pointer->print_block_on_error(tty, corruption != nullptr ? corruption : (address)header_pointer, (address)header_pointer);
107107
fatal("NMT has detected a memory corruption bug. Block at " PTR_FORMAT ": %s", p2i(memblock), msg);
108108
}
109109
return header_pointer;

src/hotspot/share/nmt/mallocSiteTable.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,13 +163,17 @@ MallocSite* MallocSiteTable::lookup_or_add(const NativeCallStack& key, uint32_t*
163163
// Access malloc site
164164
MallocSite* MallocSiteTable::malloc_site(uint32_t marker) {
165165
uint16_t bucket_idx = bucket_idx_from_marker(marker);
166-
assert(bucket_idx < table_size, "Invalid bucket index");
166+
if (bucket_idx >= table_size) {
167+
return nullptr;
168+
}
167169
const uint16_t pos_idx = pos_idx_from_marker(marker);
168170
MallocSiteHashtableEntry* head = _table[bucket_idx];
169171
for (size_t index = 0;
170172
index < pos_idx && head != nullptr;
171173
index++, head = (MallocSiteHashtableEntry*)head->next()) {}
172-
assert(head != nullptr, "Invalid position index");
174+
if (head == nullptr) {
175+
return nullptr;
176+
}
173177
return head->data();
174178
}
175179

test/hotspot/gtest/nmt/test_nmt_buffer_overflow_detection.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,4 +164,14 @@ TEST_VM(NMT, test_realloc) {
164164
}
165165
}
166166

167+
TEST_VM_FATAL_ERROR_MSG(NMT, memory_corruption_call_stack, ".*header canary.*") {
168+
if (MemTracker::tracking_level() != NMT_detail) {
169+
guarantee(false, "fake message ignore this - header canary");
170+
}
171+
const size_t SIZE = 1024;
172+
char* p = (char*)os::malloc(SIZE, mtTest);
173+
*(p - 1) = 0;
174+
os::free(p);
175+
}
176+
167177
#endif // !INCLUDE_ASAN
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 021MALLOC_SIZE-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
/*
25+
* @test
26+
* @summary Check the allocation-site stack trace of a corrupted memory at free() time
27+
* @modules java.base/jdk.internal.misc
28+
* @library /test/lib
29+
* @build jdk.test.whitebox.WhiteBox
30+
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
31+
* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:NativeMemoryTracking=detail NMTPrintMallocSiteOfCorruptedMemory
32+
*/
33+
34+
import jdk.test.lib.Utils;
35+
import jdk.test.lib.process.ProcessTools;
36+
import jdk.test.lib.process.OutputAnalyzer;
37+
import jdk.test.whitebox.WhiteBox;
38+
39+
public class NMTPrintMallocSiteOfCorruptedMemory {
40+
private static final String HEADER_ARG = "header";
41+
private static final String FOOTER_ARG = "footer";
42+
private static final String HEADER_AND_SITE_ARG = "header-and-site";
43+
private static final String FOOTER_AND_SITE_ARG = "footer-and-site";
44+
private static final int MALLOC_SIZE = 10;
45+
private static WhiteBox wb = WhiteBox.getWhiteBox();
46+
47+
static {
48+
System.loadLibrary("MallocHeaderModifier");
49+
}
50+
51+
public static native byte modifyHeaderCanary(long malloc_memory);
52+
public static native byte modifyFooterCanary(long malloc_memory, long size);
53+
public static native byte modifyHeaderCanaryAndSiteMarker(long malloc_memory);
54+
public static native byte modifyFooterCanaryAndSiteMarker(long malloc_memory, long size);
55+
56+
private static void runThisTestWith(String arg) throws Exception {
57+
ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(new String[] {"-Xbootclasspath/a:.",
58+
"-XX:+UnlockDiagnosticVMOptions",
59+
"-XX:+WhiteBoxAPI",
60+
"-XX:NativeMemoryTracking=detail",
61+
"-Djava.library.path=" + Utils.TEST_NATIVE_PATH,
62+
"NMTPrintMallocSiteOfCorruptedMemory",
63+
arg});
64+
OutputAnalyzer output = new OutputAnalyzer(pb.start());
65+
output.shouldMatch("NMT Block at .*, corruption at: ");
66+
switch(arg) {
67+
case HEADER_AND_SITE_ARG, FOOTER_AND_SITE_ARG -> output.shouldContain("allocation-site cannot be shown since the marker is also corrupted.");
68+
case HEADER_ARG, FOOTER_ARG -> {
69+
output.shouldContain("allocated from:");
70+
output.shouldMatch("\\[.*\\]WB_NMTMalloc\\+0x.*");
71+
}
72+
}
73+
}
74+
75+
private static void testModifyHeaderCanary() {
76+
long addr = wb.NMTMalloc(MALLOC_SIZE);
77+
modifyHeaderCanary(addr);
78+
wb.NMTFree(addr);
79+
}
80+
81+
private static void testModifyFooterCanary() {
82+
long addr = wb.NMTMalloc(MALLOC_SIZE);
83+
modifyFooterCanary(addr, MALLOC_SIZE);
84+
wb.NMTFree(addr);
85+
}
86+
87+
private static void testModifyHeaderCanaryAndSiteMarker() {
88+
long addr = wb.NMTMalloc(MALLOC_SIZE);
89+
modifyHeaderCanaryAndSiteMarker(addr);
90+
wb.NMTFree(addr);
91+
}
92+
93+
private static void testModifyFooterCanaryAndSiteMarker() {
94+
long addr = wb.NMTMalloc(MALLOC_SIZE);
95+
modifyFooterCanaryAndSiteMarker(addr, MALLOC_SIZE);
96+
wb.NMTFree(addr);
97+
}
98+
99+
public static void main(String args[]) throws Exception {
100+
if (args != null && args.length == 1) {
101+
switch (args[0]) {
102+
case HEADER_ARG -> testModifyHeaderCanary();
103+
case FOOTER_ARG -> testModifyFooterCanary();
104+
case HEADER_AND_SITE_ARG -> testModifyHeaderCanaryAndSiteMarker();
105+
case FOOTER_AND_SITE_ARG -> testModifyFooterCanaryAndSiteMarker();
106+
default -> throw new RuntimeException("Invalid argument for NMTPrintMallocSiteOfCorruptedMemory (" + args[0] + ")");
107+
}
108+
} else {
109+
runThisTestWith(HEADER_ARG);
110+
runThisTestWith(FOOTER_ARG);
111+
runThisTestWith(HEADER_AND_SITE_ARG);
112+
runThisTestWith(FOOTER_AND_SITE_ARG);
113+
}
114+
}
115+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation.
8+
*
9+
* This code is distributed in the hope that it will be useful, but WITHOUT
10+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12+
* version 2 for more details (a copy is included in the LICENSE file that
13+
* accompanied this code).
14+
*
15+
* You should have received a copy of the GNU General Public License version
16+
* 2 along with this work; if not, write to the Free Software Foundation,
17+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18+
*
19+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20+
* or visit www.oracle.com if you need additional information or have any
21+
* questions.
22+
*/
23+
24+
#include "jni.h"
25+
#include <stdint.h>
26+
#include <string.h>
27+
28+
JNIEXPORT jint JNICALL
29+
Java_NMTPrintMallocSiteOfCorruptedMemory_modifyHeaderCanary(JNIEnv *env, jclass cls, jlong addr) {
30+
*((jint*)(uintptr_t)addr - 1) = 0;
31+
return 0;
32+
}
33+
34+
JNIEXPORT jint JNICALL
35+
Java_NMTPrintMallocSiteOfCorruptedMemory_modifyFooterCanary(JNIEnv *env, jclass cls, jlong addr, jint size) {
36+
*((jbyte*)(uintptr_t)addr + size + 1) = 0;
37+
return 0;
38+
}
39+
JNIEXPORT jint JNICALL
40+
Java_NMTPrintMallocSiteOfCorruptedMemory_modifyHeaderCanaryAndSiteMarker(JNIEnv *env, jclass cls, jlong addr) {
41+
jbyte* p = (jbyte*)(uintptr_t)addr - 16;
42+
memset(p, 0xFF , 16);
43+
return 0;
44+
}
45+
46+
JNIEXPORT jint JNICALL
47+
Java_NMTPrintMallocSiteOfCorruptedMemory_modifyFooterCanaryAndSiteMarker(JNIEnv *env, jclass cls, jlong addr, jint size) {
48+
jbyte* p = (jbyte*)(uintptr_t)addr - 16;
49+
memset(p, 0xFF , 16);
50+
*((jbyte*)(uintptr_t)addr + size + 1) = 0;
51+
return 0;
52+
}

0 commit comments

Comments
 (0)