二进制文件重排收集

//
//  OrderFileRecorder.h
//  BasicExercise
//
//  Created by DDS on 2026/6/16.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface OrderFileRecorder : NSObject

+ (void)startRecording;
+ (void)stopRecording;
+ (void)resetRecording;
+ (BOOL)isRecording;
+ (nullable NSString *)exportOrderFile;
+ (nullable NSString *)exportOrderFileWithName:(NSString *)fileName;

@end

NS_ASSUME_NONNULL_END

//OrderFileRecorder.m

#import "OrderFileRecorder.h"

#import <dlfcn.h>
#import <stdatomic.h>

#define BE_ORDER_FILE_NO_COVERAGE __attribute__((no_sanitize("coverage")))

static const uint32_t kOrderFileMaxPCCount = 300000;

static atomic_bool isOrderFileRecording = ATOMIC_VAR_INIT(false);
static atomic_bool didBootstrapOrderFileRecording = ATOMIC_VAR_INIT(false);
static atomic_uint recordedPCCount = ATOMIC_VAR_INIT(0);

static uintptr_t *recordedPCBuffer = NULL;
static uint32_t recordedPCCapacity = 0;
static __thread BOOL shouldIgnoreCurrentThread = NO;

BE_ORDER_FILE_NO_COVERAGE
static void BEOrderFilePrepareBufferIfNeeded(void) {
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       recordedPCCapacity = kOrderFileMaxPCCount;
       recordedPCBuffer = (uintptr_t *)calloc(recordedPCCapacity, sizeof(uintptr_t));
   });
}

BE_ORDER_FILE_NO_COVERAGE
static BOOL BEOrderFileShouldSkipSymbol(NSString *symbolName, NSString *imagePath) {
   if (symbolName.length == 0) {
       return YES;
   }

   if ([symbolName containsString:@"OrderFileRecorder"] ||
       [symbolName containsString:@"__sanitizer_cov"] ||
       [symbolName containsString:@"saveOrderFile"]) {
       return YES;
   }

   NSString *bundlePath = [NSBundle mainBundle].bundlePath.stringByStandardizingPath;
   NSString *standardizedImagePath = imagePath.stringByStandardizingPath;
   if (bundlePath.length > 0 && standardizedImagePath.length > 0 && ![standardizedImagePath hasPrefix:bundlePath]) {
       return YES;
   }

   return NO;
}

BE_ORDER_FILE_NO_COVERAGE
static NSString *BEOrderFileFormattedSymbolName(const char *symbol) {
   if (symbol == NULL || symbol[0] == '\0') {
       return nil;
   }

   NSString *symbolName = [NSString stringWithUTF8String:symbol];
   if (symbolName.length == 0) {
       return nil;
   }

   if ([symbolName hasPrefix:@"+[" ] || [symbolName hasPrefix:@"-["]) {
       return symbolName;
   }

   if (![symbolName hasPrefix:@"_"]) {
       symbolName = [@"_" stringByAppendingString:symbolName];
   }
   return symbolName;
}

BE_ORDER_FILE_NO_COVERAGE
static NSString *BEOrderFileExportPath(NSString *fileName) {
   NSString *safeFileName = fileName.length > 0 ? fileName : @"startup.order";
   NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
   if (documentsPath.length == 0) {
       documentsPath = NSTemporaryDirectory();
   }
   return [documentsPath stringByAppendingPathComponent:safeFileName];
}

@implementation OrderFileRecorder

+ (void)startRecording {
   BEOrderFilePrepareBufferIfNeeded();
   atomic_store_explicit(&isOrderFileRecording, true, memory_order_relaxed);
}

+ (void)stopRecording {
   atomic_store_explicit(&isOrderFileRecording, false, memory_order_relaxed);
}

+ (void)resetRecording {
   BEOrderFilePrepareBufferIfNeeded();
   atomic_store_explicit(&recordedPCCount, 0, memory_order_relaxed);
   if (recordedPCBuffer != NULL && recordedPCCapacity > 0) {
       memset(recordedPCBuffer, 0, sizeof(uintptr_t) * recordedPCCapacity);
   }
}

+ (BOOL)isRecording {
   return atomic_load_explicit(&isOrderFileRecording, memory_order_relaxed);
}

+ (NSString *)exportOrderFile {
   return [self exportOrderFileWithName:@"startup.order"];
}

+ (NSString *)exportOrderFileWithName:(NSString *)fileName {
   BEOrderFilePrepareBufferIfNeeded();
   [self stopRecording];

   shouldIgnoreCurrentThread = YES;

   uint32_t count = atomic_load_explicit(&recordedPCCount, memory_order_acquire);
   if (count > recordedPCCapacity) {
       count = recordedPCCapacity;
   }

   NSMutableOrderedSet<NSString *> *orderedSymbols = [[NSMutableOrderedSet alloc] initWithCapacity:count];
   for (NSInteger index = (NSInteger)count - 1; index >= 0; index--) {
       uintptr_t pc = recordedPCBuffer[index];
       if (pc == 0) {
           continue;
       }

       Dl_info info = {0};
       if (dladdr((void *)pc, &info) == 0 || info.dli_sname == NULL) {
           continue;
       }

       NSString *symbolName = BEOrderFileFormattedSymbolName(info.dli_sname);
       NSString *imagePath = info.dli_fname != NULL ? [NSString stringWithUTF8String:info.dli_fname] : @"";
       if (symbolName.length == 0 || BEOrderFileShouldSkipSymbol(symbolName, imagePath)) {
           continue;
       }

       [orderedSymbols addObject:symbolName];
   }

   NSMutableString *content = [NSMutableString string];
   for (NSString *symbol in orderedSymbols) {
       [content appendFormat:@"%@\n", symbol];
   }

   NSString *filePath = BEOrderFileExportPath(fileName);
   NSError *error = nil;
   BOOL success = [content writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
   shouldIgnoreCurrentThread = NO;
   if (!success || error) {
       NSLog(@"order file 写入失败:%@", error);
       return nil;
   }

   NSLog(@"order file 写入成功:%@", filePath);
   return filePath;
}

@end

BE_ORDER_FILE_NO_COVERAGE
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) {
   static uint32_t N;
   if (start == stop || *start) {
       return;
   }

   for (uint32_t *x = start; x < stop; x++) {
       *x = ++N;
   }

   BEOrderFilePrepareBufferIfNeeded();
   if (!atomic_exchange_explicit(&didBootstrapOrderFileRecording, true, memory_order_relaxed)) {
       atomic_store_explicit(&recordedPCCount, 0, memory_order_relaxed);
       atomic_store_explicit(&isOrderFileRecording, true, memory_order_relaxed);
   }
}

BE_ORDER_FILE_NO_COVERAGE
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
   if (guard == NULL || !*guard) {
       return;
   }

   if (!atomic_load_explicit(&isOrderFileRecording, memory_order_relaxed)) {
       return;
   }

   if (shouldIgnoreCurrentThread || recordedPCBuffer == NULL || recordedPCCapacity == 0) {
       return;
   }

   uintptr_t pc = (uintptr_t)__builtin_return_address(0);
   if (pc == 0) {
       return;
   }

   uint32_t index = atomic_fetch_add_explicit(&recordedPCCount, 1, memory_order_relaxed);
   if (index >= recordedPCCapacity) {
       return;
   }

   recordedPCBuffer[index] = pc;
}

使用方法

    NSString *filePath = [OrderFileRecorder exportOrderFile];
    if (filePath.length > 0) {
        [SVProgressHUD showToastTitle:[NSString stringWithFormat:@"order file 已导出到 %@", filePath.lastPathComponent]];
    } else {
        [SVProgressHUD showToastTitle:@"order file 导出失败"];
    }
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容