+/***********************************************************************
+* dataSegmentsContain
+* Returns true if the given address lies within a data segment in any
+* loaded image.
+*
+* This is optimized for use where the return value is expected to be
+* true. A call where the return value is false always results in a
+* slow linear search of all loaded images. A call where the return
+* value is fast will often be fast due to caching.
+**********************************************************************/
+static bool dataSegmentsContain(const void *ptr) {
+ struct Range {
+ uintptr_t start, end;
+ bool contains(uintptr_t ptr) {
+ return start <= ptr && ptr <= end;
+ }
+ };
+
+ // This is a really simple linear searched cache. On a cache hit,
+ // the hit entry is moved to the front of the array. On a cache
+ // miss where a range is successfully found on the slow path, the
+ // found range is inserted at the beginning of the cache. This gives
+ // us fast access to the most recently used elements, and LRU
+ // eviction.
+ enum { cacheCount = 16 };
+ static Range cache[cacheCount];
+
+ uintptr_t addr = (uintptr_t)ptr;
+
+ // Special case a hit on the first entry of the cache. No
+ // bookkeeping is required at all in this case.
+ if (cache[0].contains(addr)) {
+ return true;
+ }
+
+ // Search the rest of the cache.
+ for (unsigned i = 1; i < cacheCount; i++) {
+ if (cache[i].contains(addr)) {
+ // Cache hit. Move all preceding entries down one element,
+ // then place this entry at the front.
+ Range r = cache[i];
+ memmove(&cache[1], &cache[0], i * sizeof(cache[0]));
+ cache[0] = r;
+ return true;
+ }
+ }
+
+ // Cache miss. Find the image header containing the given address.
+ // If there isn't one, then we're definitely not in any image,
+ // so return false.
+ Range found = { 0, 0 };
+ auto *h = (headerType *)dyld_image_header_containing_address(ptr);
+ if (h == nullptr)
+ return false;
+
+ // Iterate over the data segments in the found image. If the address
+ // lies within one, note the data segment range in `found`.
+ // TODO: this is more work than we'd like to do. All we really need
+ // is the full range of the image. Addresses within the TEXT segment
+ // would also be acceptable for our use case. If possible, we should
+ // change this to work with the full address range of the found
+ // image header. Another possibility would be to use the range
+ // from `h` to the end of the page containing `addr`.
+ foreach_data_segment(h, [&](const segmentType *seg, intptr_t slide) {
+ Range r;
+ r.start = seg->vmaddr + slide;
+ r.end = r.start + seg->vmsize;
+ if (r.contains(addr))
+ found = r;
+ });
+
+ if (found.start != 0) {
+ memmove(&cache[1], &cache[0], (cacheCount - 1) * sizeof(cache[0]));
+ cache[0] = found;
+ return true;
+ }
+
+ return false;
+}
+
+
+/***********************************************************************
+* isKnownClass
+* Return true if the class is known to the runtime (located within the
+* shared cache, within the data segment of a loaded image, or has been
+* allocated with obj_allocateClassPair).
+**********************************************************************/
+static bool isKnownClass(Class cls) {
+ // The order of conditionals here is important for speed. We want to
+ // put the most common cases first, but also the fastest cases
+ // first. Checking the shared region is both fast and common.
+ // Checking allocatedClasses is fast, but may not be common,
+ // depending on what the program is doing. Checking if data segments
+ // contain the address is slow, so do it last.
+ return (sharedRegionContains(cls) ||
+ NXHashMember(allocatedClasses, cls) ||
+ dataSegmentsContain(cls));
+}
+
+
+/***********************************************************************
+* addClassTableEntry
+* Add a class to the table of all classes. If addMeta is true,
+* automatically adds the metaclass of the class as well.
+* Locking: runtimeLock must be held by the caller.
+**********************************************************************/
+static void addClassTableEntry(Class cls, bool addMeta = true) {
+ runtimeLock.assertLocked();
+
+ // This class is allowed to be a known class via the shared cache or via
+ // data segments, but it is not allowed to be in the dynamic table already.
+ assert(!NXHashMember(allocatedClasses, cls));
+
+ if (!isKnownClass(cls))
+ NXHashInsert(allocatedClasses, cls);
+ if (addMeta)
+ addClassTableEntry(cls->ISA(), false);
+}
+
+
+/***********************************************************************
+* checkIsKnownClass
+* Checks the given class against the list of all known classes. Dies
+* with a fatal error if the class is not known.
+* Locking: runtimeLock must be held by the caller.
+**********************************************************************/
+static void checkIsKnownClass(Class cls)
+{
+ if (!isKnownClass(cls))
+ _objc_fatal("Attempt to use unknown class %p.", cls);
+}
+
+