| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "ios/components/order_file/save_order_file.h" |
| |
| #import <Foundation/Foundation.h> |
| #import <dlfcn.h> |
| #import <libkern/OSAtomicQueue.h> |
| |
| #import "base/logging.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "ios/components/order_file/order_file_common.h" |
| |
| namespace { |
| |
| // Returns the directory used to save generated order files. |
| NSString* CRWGetOutputsDirectory() { |
| NSArray<NSString*>* paths = NSSearchPathForDirectoriesInDomains( |
| NSDocumentDirectory, NSUserDomainMask, YES); |
| NSString* path = |
| [paths.firstObject stringByAppendingPathComponent:@"order_file"]; |
| path = [path stringByStandardizingPath]; |
| |
| NSFileManager* fileManager = [NSFileManager defaultManager]; |
| BOOL isDir; |
| if ([fileManager fileExistsAtPath:path isDirectory:&isDir]) { |
| if (!isDir) { |
| @throw [NSException |
| exceptionWithName:@"OrderFileGenerationError" |
| reason:[NSString |
| stringWithFormat: |
| @"Could not create directory %@ for output", |
| path] |
| userInfo:nil]; |
| } |
| } else { |
| NSError* error = nil; |
| BOOL success = [fileManager createDirectoryAtPath:path |
| withIntermediateDirectories:YES |
| attributes:nil |
| error:&error]; |
| if (success) { |
| LOG(WARNING) << "Created dir: " << base::SysNSStringToUTF8(path) << "\n"; |
| } else { |
| @throw [NSException |
| exceptionWithName:@"OrderFileGenerationError" |
| reason:[NSString |
| stringWithFormat: |
| @"Failed to create directory %@ for output", |
| path] |
| userInfo:nil]; |
| } |
| } |
| return path; |
| } |
| |
| // Dedups function calls and write order file to disk. |
| BOOL CRWDedupAndSaveOrderFile(NSString* fileName, |
| NSArray<NSString*>* functions) { |
| // Dedup and reverse function calls. This is done in a separate loop because |
| // it is important that the first call of each function is the one that is |
| // kept. |
| NSMutableArray<NSString*>* uniqueFunctionCalls = |
| [NSMutableArray arrayWithCapacity:functions.count]; |
| NSMutableSet<NSString*>* storedFunctionCalls = [[NSMutableSet alloc] init]; |
| NSEnumerator<NSString*>* enumerator = [functions reverseObjectEnumerator]; |
| NSString* functionName; |
| while ((functionName = [enumerator nextObject])) { |
| if (uniqueFunctionCalls.count % 1000 == 0) { |
| // Print once every 1000 times to save some time while de-queuing. |
| LOG(WARNING) << "Reordering and deduping functions: " |
| << uniqueFunctionCalls.count << " unique\n"; |
| } |
| |
| if (![storedFunctionCalls containsObject:functionName]) { |
| [uniqueFunctionCalls addObject:functionName]; |
| [storedFunctionCalls addObject:functionName]; |
| } |
| } |
| |
| LOG(WARNING) << "Saving order file for " << base::SysNSStringToUTF8(fileName) |
| << ". " << uniqueFunctionCalls.count |
| << " unique function calls recorded.\n"; |
| |
| [uniqueFunctionCalls addObject:@""]; // Adding a new line to end of the file. |
| NSString* output = [uniqueFunctionCalls componentsJoinedByString:@"\n"]; |
| |
| // Save order file to disk. |
| NSString* filePath = [CRWGetOutputsDirectory() |
| stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.order", |
| fileName]]; |
| NSData* fileContents = [output dataUsingEncoding:NSUTF8StringEncoding]; |
| NSError* error; |
| BOOL success = [fileContents writeToFile:filePath |
| options:NSDataWritingFileProtectionNone |
| error:&error]; |
| if (success) { |
| LOG(WARNING) << "Order file saved to path: " |
| << base::SysNSStringToUTF8(filePath) << "\n"; |
| } else { |
| LOG(WARNING) << "Order file save failed. Path: " |
| << base::SysNSStringToUTF8(filePath) << "\n, Error: " |
| << base::SysNSStringToUTF8(error.debugDescription) << "\n"; |
| } |
| return success; |
| } |
| |
| } // namespace |
| |
| extern "C" { |
| |
| // Finish sanitizer collecting and dump order files. |
| void CRWSaveOrderFile() { |
| // Disable collection of further procedure calls. |
| if (gCRWFinishedCollecting) { |
| return; |
| } |
| gCRWFinishedCollecting = YES; |
| __sync_synchronize(); |
| LOG(WARNING) << "Generating order file.\n"; |
| |
| // If this function is called, the app is being run to generate an order file, |
| // so a blocking thread can be used. |
| NSMutableArray<NSString*>* allFunctions = [NSMutableArray array]; |
| NSUInteger procedureCallCount = 0; |
| while (YES) { |
| if (procedureCallCount % 1000 == 0) { |
| // Print once every 1000 times to save some time while de-queuing. |
| LOG(WARNING) << "Dequeuing functions: " << allFunctions.count |
| << " valid / " << procedureCallCount << " total\n"; |
| } |
| CRWProcedureCallNode* node = (CRWProcedureCallNode*)OSAtomicDequeue( |
| &gCRWSanitizerQueue, offsetof(CRWProcedureCallNode, next)); |
| if (node == nullptr) { |
| break; |
| } |
| Dl_info dlInfo; |
| if (dladdr(node->procedureCall, &dlInfo) == 0 || !dlInfo.dli_sname) { |
| continue; |
| } |
| procedureCallCount++; |
| |
| NSString* functionName = @(dlInfo.dli_sname); |
| BOOL isObjc = |
| [functionName hasPrefix:@"+["] || [functionName hasPrefix:@"-["]; |
| functionName = |
| isObjc ? functionName : [@"_" stringByAppendingString:functionName]; |
| |
| [allFunctions addObject:functionName]; |
| } |
| |
| if (allFunctions.count > 0) { |
| LOG(WARNING) << "Out of " << procedureCallCount |
| << " recorded function calls, " << allFunctions.count |
| << " had a valid function name.\n"; |
| } else { |
| LOG(WARNING) << "No functions found in order file generation.\n"; |
| return; |
| } |
| |
| BOOL success = CRWDedupAndSaveOrderFile(@"app", allFunctions); |
| if (success) { |
| LOG(WARNING) << "ORDER_FILE_DUMPED\n"; |
| exit(0); |
| } else { |
| LOG(WARNING) << "ORDER_FILE_DUMP_FAILED\n"; |
| exit(1); |
| } |
| } |
| |
| } // extern "C" |