[sanitizer-common] Improve diagnostic when ASAN fails to allocate shadow (#158378)

Sometimes we are unable to find a sufficiently large gap to allocate the
dynamic ASAN shadow.

If a gap is not found, we will now output a (consolidated) memory map to
show the user what regions were unavailable and how much memory we need.

rdar://159142896
This commit is contained in:
Andrew Haberlandt
2025-09-15 17:26:30 -07:00
committed by GitHub
parent 9b95e10d5e
commit 1fea3c507c

View File

@@ -22,6 +22,11 @@
# endif
# include <stdio.h>
// Start searching for available memory region past PAGEZERO, which is
// 4KB on 32-bit and 4GB on 64-bit.
# define GAP_SEARCH_START_ADDRESS \
((SANITIZER_WORDSIZE == 32) ? 0x000000001000 : 0x000100000000)
# include "sanitizer_common.h"
# include "sanitizer_file.h"
# include "sanitizer_flags.h"
@@ -58,6 +63,7 @@ extern char ***_NSGetArgv(void);
# include <dlfcn.h> // for dladdr()
# include <errno.h>
# include <fcntl.h>
# include <inttypes.h>
# include <libkern/OSAtomic.h>
# include <mach-o/dyld.h>
# include <mach/mach.h>
@@ -1106,6 +1112,67 @@ static void StripEnv() {
}
#endif // SANITIZER_GO
// Prints out a consolidated memory map: contiguous regions
// are merged together.
static void PrintVmmap() {
const mach_vm_address_t max_vm_address = GetMaxVirtualAddress() + 1;
mach_vm_address_t address = GAP_SEARCH_START_ADDRESS;
kern_return_t kr = KERN_SUCCESS;
Report("Memory map:\n");
mach_vm_address_t last = 0;
mach_vm_address_t lastsz = 0;
while (1) {
mach_vm_size_t vmsize = 0;
natural_t depth = 0;
vm_region_submap_short_info_data_64_t vminfo;
mach_msg_type_number_t count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
kr = mach_vm_region_recurse(mach_task_self(), &address, &vmsize, &depth,
(vm_region_info_t)&vminfo, &count);
if (kr == KERN_DENIED) {
Report(
"ERROR: mach_vm_region_recurse got KERN_DENIED when printing memory "
"map.\n");
Report(
"HINT: Check whether mach_vm_region_recurse is allowed by "
"sandbox.\n");
}
if (kr == KERN_SUCCESS && address < max_vm_address) {
if (last + lastsz == address) {
// This region is contiguous with the last; merge together.
lastsz += vmsize;
} else {
if (lastsz)
Printf("|| `[%p, %p]` || size=0x%016" PRIx64 " ||\n", last,
last + lastsz, lastsz);
last = address;
lastsz = vmsize;
}
address += vmsize;
} else {
// We've reached the end of the memory map. Print the last remaining
// region, if there is one.
if (lastsz)
Printf("|| `[%p, %p]` || size=0x%016" PRIx64 " ||\n", last,
last + lastsz, lastsz);
break;
}
}
}
static void ReportShadowAllocFail(uptr shadow_size_bytes, uptr alignment) {
Report(
"FATAL: Failed to allocate shadow memory. Tried to allocate %p bytes "
"(alignment=%p).\n",
shadow_size_bytes, alignment);
PrintVmmap();
}
char **GetArgv() {
return *_NSGetArgv();
}
@@ -1213,10 +1280,11 @@ uptr MapDynamicShadow(uptr shadow_size_bytes, uptr shadow_scale,
if (new_max_vm < max_occupied_addr) {
Report("Unable to find a memory range for dynamic shadow.\n");
Report(
"space_size = %p, largest_gap_found = %p, max_occupied_addr = %p, "
"new_max_vm = %p\n",
(void *)space_size, (void *)largest_gap_found,
(void *)max_occupied_addr, (void *)new_max_vm);
"\tspace_size = %p\n\tlargest_gap_found = %p\n\tmax_occupied_addr "
"= %p\n\tnew_max_vm = %p\n",
(void*)space_size, (void*)largest_gap_found, (void*)max_occupied_addr,
(void*)new_max_vm);
ReportShadowAllocFail(shadow_size_bytes, alignment);
CHECK(0 && "cannot place shadow");
}
RestrictMemoryToMaxAddress(new_max_vm);
@@ -1227,6 +1295,7 @@ uptr MapDynamicShadow(uptr shadow_size_bytes, uptr shadow_scale,
nullptr, nullptr);
if (shadow_start == 0) {
Report("Unable to find a memory range after restricting VM.\n");
ReportShadowAllocFail(shadow_size_bytes, alignment);
CHECK(0 && "cannot place shadow after restricting vm");
}
}
@@ -1242,26 +1311,19 @@ uptr MapDynamicShadowAndAliases(uptr shadow_size, uptr alias_size,
}
uptr FindAvailableMemoryRange(uptr size, uptr alignment, uptr left_padding,
uptr *largest_gap_found,
uptr *max_occupied_addr) {
typedef vm_region_submap_short_info_data_64_t RegionInfo;
enum { kRegionInfoSize = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64 };
// Start searching for available memory region past PAGEZERO, which is
// 4KB on 32-bit and 4GB on 64-bit.
mach_vm_address_t start_address =
(SANITIZER_WORDSIZE == 32) ? 0x000000001000 : 0x000100000000;
uptr* largest_gap_found,
uptr* max_occupied_addr) {
const mach_vm_address_t max_vm_address = GetMaxVirtualAddress() + 1;
mach_vm_address_t address = start_address;
mach_vm_address_t free_begin = start_address;
mach_vm_address_t address = GAP_SEARCH_START_ADDRESS;
mach_vm_address_t free_begin = GAP_SEARCH_START_ADDRESS;
kern_return_t kr = KERN_SUCCESS;
if (largest_gap_found) *largest_gap_found = 0;
if (max_occupied_addr) *max_occupied_addr = 0;
while (kr == KERN_SUCCESS) {
mach_vm_size_t vmsize = 0;
natural_t depth = 0;
RegionInfo vminfo;
mach_msg_type_number_t count = kRegionInfoSize;
vm_region_submap_short_info_data_64_t vminfo;
mach_msg_type_number_t count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64;
kr = mach_vm_region_recurse(mach_task_self(), &address, &vmsize, &depth,
(vm_region_info_t)&vminfo, &count);