libsanitizer, Darwin: Updates for building with GCC.

This replaces the Darwin-specific changes previously applied
in b53f7de3e6 and addresses the
FIXME there.

1. The upstream sources do not, in general, support the range
of Darwin versions covered by GCC.

In order to support versions back to Darwin17, at least we
provide definitions for missing macro values and ensure that
headers are only conditionally included where they apply.

2. GCC does not support the clang __builtin_os_log_format and
therefore must fall back to older reporting methods.

3. Finally, we address a FIXME (for missing Blocks support)
used implement the search for dyld on macOS >= 13 with the
dyld_shared_cache_iterate_text() interface which requires an
(Apple) Block closure as a parameter.

If the compiler supports blocks (__BLOCKS__ is defined) then we
use the upstream implementation.  If not, then we synthesize the
equivalent code-gen manually.
This commit is contained in:
Iain Sandoe 2025-12-01 13:40:20 +00:00
parent c915bfc0f7
commit 2249f61437
3 changed files with 163 additions and 14 deletions

View File

@ -71,7 +71,15 @@ extern char ***_NSGetArgv(void);
# include <mach/mach_time.h>
# include <mach/vm_statistics.h>
# include <malloc/malloc.h>
# include <os/log.h>
# if defined(__has_builtin) && __has_builtin(__builtin_os_log_format)
# include <os/log.h>
# else
/* Without support for __builtin_os_log_format, fall back to the older
method. */
# define OS_LOG_DEFAULT 0
# define os_log_error(A,B,C) \
asl_log(nullptr, nullptr, ASL_LEVEL_ERR, "%s", (C))
# endif
# include <pthread.h>
# include <pthread/introspection.h>
# include <sched.h>
@ -869,19 +877,23 @@ void LogFullErrorReport(const char *buffer) {
// When logging with os_log_error this will make it into the crash log.
if (internal_strncmp(SanitizerToolName, "AddressSanitizer",
sizeof("AddressSanitizer") - 1) == 0)
SANITIZER_OS_LOG(OS_LOG_DEFAULT, "Address Sanitizer reported a failure.");
SANITIZER_OS_LOG(OS_LOG_DEFAULT, "%{public}s",
"Address Sanitizer reported a failure.");
else if (internal_strncmp(SanitizerToolName, "UndefinedBehaviorSanitizer",
sizeof("UndefinedBehaviorSanitizer") - 1) == 0)
SANITIZER_OS_LOG(OS_LOG_DEFAULT,
SANITIZER_OS_LOG(OS_LOG_DEFAULT, "%{public}s",
"Undefined Behavior Sanitizer reported a failure.");
else if (internal_strncmp(SanitizerToolName, "ThreadSanitizer",
sizeof("ThreadSanitizer") - 1) == 0)
SANITIZER_OS_LOG(OS_LOG_DEFAULT, "Thread Sanitizer reported a failure.");
SANITIZER_OS_LOG(OS_LOG_DEFAULT, "%{public}s",
"Thread Sanitizer reported a failure.");
else
SANITIZER_OS_LOG(OS_LOG_DEFAULT, "Sanitizer tool reported a failure.");
SANITIZER_OS_LOG(OS_LOG_DEFAULT, "%{public}s",
"Sanitizer tool reported a failure.");
if (common_flags()->log_to_syslog)
SANITIZER_OS_LOG(OS_LOG_DEFAULT, "Consult syslog for more information.");
SANITIZER_OS_LOG(OS_LOG_DEFAULT, "%{public}s",
"Consult syslog for more information.");
// Log to syslog.
// The logging on OS X may call pthread_create so we need the threading
@ -952,6 +964,10 @@ void SignalContext::InitPcSpBp() {
GetPcSpBp(context, &pc, &sp, &bp);
}
#ifndef KERN_DENIED
#define KERN_DENIED 53
#endif
// ASan/TSan use mmap in a way that creates “deallocation gaps” which triggers
// EXC_GUARD exceptions on macOS 10.15+ (XNU 19.0+).
static void DisableMmapExcGuardExceptions() {

View File

@ -14,6 +14,26 @@
#include "sanitizer_common.h"
#include "sanitizer_platform.h"
/* TARGET_OS_OSX is not present in SDKs before Darwin16 (macOS 10.12) use
TARGET_OS_MAC (we have no support for iOS in any form for these versions,
so there's no ambiguity). */
#if !defined(TARGET_OS_OSX) && TARGET_OS_MAC
# define TARGET_OS_OSX 1
#endif
/* Other TARGET_OS_xxx are not present on earlier versions, define them to
0 (we have no support for them; they are not valid targets anyway). */
#ifndef TARGET_OS_IOS
#define TARGET_OS_IOS 0
#endif
#ifndef TARGET_OS_TV
#define TARGET_OS_TV 0
#endif
#ifndef TARGET_OS_WATCH
#define TARGET_OS_WATCH 0
#endif
#if SANITIZER_APPLE
#include "sanitizer_posix.h"

View File

@ -239,19 +239,18 @@ typedef struct dyld_shared_cache_dylib_text_info
extern bool _dyld_get_shared_cache_uuid(uuid_t uuid);
extern const void *_dyld_get_shared_cache_range(size_t *length);
extern intptr_t _dyld_get_image_slide(const struct mach_header* mh);
} // extern "C"
#ifdef __BLOCKS__
extern "C" {
extern int dyld_shared_cache_iterate_text(
const uuid_t cacheUuid,
void (^callback)(const dyld_shared_cache_dylib_text_info *info));
} // extern "C"
static mach_header *GetDyldImageHeaderViaSharedCache() {
uuid_t uuid;
bool hasCache = _dyld_get_shared_cache_uuid(uuid);
if (!hasCache)
return nullptr;
static mach_header *GetDyldImageHeaderViaSharedCache(uuid_t uuid) {
size_t cacheLength;
__block uptr cacheStart = (uptr)_dyld_get_shared_cache_range(&cacheLength);
const uptr cacheStart = (uptr)_dyld_get_shared_cache_range(&cacheLength);
CHECK(cacheStart && cacheLength);
__block mach_header *dyldHdr = nullptr;
@ -268,12 +267,126 @@ static mach_header *GetDyldImageHeaderViaSharedCache() {
return dyldHdr;
}
#else
/* Here we implement a manual emulation of the Blocks closure and callback
that is needed by dyld_shared_cache_iterate_text (). In the compiler-
generated code, the [local] names are mangled differently, but that
should not matter to the function (since all the entities are TU-local). */
extern "C" {
/* Some descriptions of the blocks interfaces/runtime that we need. */
extern void *_NSConcreteStackBlock;
extern void _Block_object_assign(void *, void *, int);
extern void _Block_object_dispose(void *, int);
enum {
BLOCK_FIELD_IS_OBJECT = 3, /* id, NSObject, __attribute__((NSObject)), block, ... */
BLOCK_FIELD_IS_BLOCK = 7, /* a block variable */
BLOCK_FIELD_IS_BYREF = 8, /* the on stack structure holding the __block variable */
BLOCK_FIELD_IS_WEAK = 16, /* declared __weak, only used in byref copy helpers */
BLOCK_BYREF_CALLER = 128 /* called from __block (byref) copy/dispose support routines. */
};
/* 1. __block mach_header *dyldHdr; Uses this on-stack object. */
typedef struct __block_byref_dyldHdr {
void *__isa;
struct __block_byref_dyldHdr *forwarding;
int flags; // 1<<25 iff we need copy helper;
int size;
mach_header *dyldHdr_cp;
} dyldHdr_type;
/* The closure, support functions and the actuall callback code. */
/* 1. closure descriptor. A constant instance of this will be created. */
struct Block_descriptor_cb {
unsigned long int reserved; // NULL
unsigned long int size; // sizeof(struct Block_literal_cb)
// optional helper functions
void (*copy_helper)(struct Block_literal_cb *dst, struct Block_literal_cb *src); // IFF (1<<25)
void (*dispose_helper)(struct Block_literal_cb *src); // IFF (1<<25)
// required ABI.2010.3.16
const char *signature; // IFF (1<<30)
void *no_idea; // not documented in the current ABI
};
/* 2. This is the closure object. In this case, it will be allocated on the
stack and does not need to be moved (since the closure never escapes
from its enclosing scope). However, the infrastructure still provides
support for moving it to the heap if required. */
typedef struct Block_literal_cb {
void *__isa;
int flags;
int reserved;
void (*invoke) (Block_literal_cb*, const dyld_shared_cache_dylib_text_info *info);
const struct Block_descriptor_cb *descriptor;
dyldHdr_type *h_holder;
uptr cache_start;
} CallbackBlk;
/* 3. Supporting functions to allow for the closure to be copied to the heap on
demand. */
static void
__block_copy_cb (struct Block_literal_cb *dst, struct Block_literal_cb *src)
{
//_Block_byref_assign_copy(&dst->captured_i, src->captured_i);
_Block_object_assign(&dst->h_holder, src->h_holder, BLOCK_FIELD_IS_BYREF);
}
static void
__block_dispose_cb (struct Block_literal_cb *src) {
//_Block_byref_release(src->captured_i);
_Block_object_dispose(src->h_holder, BLOCK_FIELD_IS_BYREF);
}
/* 4. Here is the actual code implementing the callback. */
static void
__block_invoke_cb (struct Block_literal_cb *_block,
const dyld_shared_cache_dylib_text_info *info)
{
mach_header *hdr = (mach_header *)(_block->cache_start + info->textSegmentOffset);
if (IsDyldHdr(hdr))
_block->h_holder->forwarding->dyldHdr_cp = hdr;
}
/* 6. The constant instance of the descriptor mentioned in 1 above. */
static const struct Block_descriptor_cb cb_descr = {
0, sizeof (struct Block_literal_cb), __block_copy_cb, __block_dispose_cb,
"v16@?0r^{dyld_shared_cache_dylib_text_info=QQQ[16C]*Q}8", nullptr
};
/* Declare dyld_shared_cache_iterate_text () as taking a regular pointer to
the closure; we cannot use the ^ syntax yet in GCC. */
extern int dyld_shared_cache_iterate_text(const uuid_t cacheUuid, CallbackBlk*);
} // extern "C"
/* The non-blocks version of GetDyldImageHeaderViaSharedCache (). */
static mach_header *GetDyldImageHeaderViaSharedCache(uuid_t uuid) {
size_t cacheLength;
const uptr cacheStart = (uptr)_dyld_get_shared_cache_range(&cacheLength);
CHECK(cacheStart && cacheLength);
/* __block mach_header *dyldHdr = nullptr; */
dyldHdr_type dyldHdr
= { nullptr, &dyldHdr, 0, sizeof (struct __block_byref_dyldHdr), nullptr };
/* Callback cb = ^(const dyld_shared_cache_dylib_text_info *info... */
CallbackBlk cb
= { _NSConcreteStackBlock, 0x42000000, 0, __block_invoke_cb,
&cb_descr, &dyldHdr, cacheStart };
int res = dyld_shared_cache_iterate_text ( uuid, &cb );
CHECK_EQ(res, 0);
return dyldHdr.forwarding->dyldHdr_cp;
}
#endif
const mach_header *get_dyld_hdr() {
if (!dyld_hdr) {
// On macOS 13+, dyld itself has moved into the shared cache. Looking it up
// via vm_region_recurse_64() causes spins/hangs/crashes.
if (GetMacosAlignedVersion() >= MacosVersion(13, 0)) {
dyld_hdr = GetDyldImageHeaderViaSharedCache();
uuid_t uuid;
if (!_dyld_get_shared_cache_uuid(uuid))
VReport(1, "Failed get the shared cache on macOS 13+\n");
else
dyld_hdr = GetDyldImageHeaderViaSharedCache(uuid);
if (!dyld_hdr) {
VReport(1,
"Failed to lookup the dyld image header in the shared cache on "