X-Git-Url: https://git.saurik.com/apple/dyld.git/blobdiff_plain/e1ecafb2149c7d823cefefdc04fce5dc6dbe6e4d..ba4c3badc27ea8c4637b4e91a49725742a02a53c:/src/dyld_process_info.cpp diff --git a/src/dyld_process_info.cpp b/src/dyld_process_info.cpp index fc47bce..e373fa9 100644 --- a/src/dyld_process_info.cpp +++ b/src/dyld_process_info.cpp @@ -38,77 +38,127 @@ #include "dyld_images.h" #include "dyld_priv.h" +#include "Tracing.h" + // this was in dyld_priv.h but it is no longer exported extern "C" { const struct dyld_all_image_infos* _dyld_get_all_image_infos(); } -namespace { - -void withRemoteBuffer(task_t task, vm_address_t remote_address, size_t remote_size, bool allow_truncation, kern_return_t *kr, void (^block)(void *buffer, size_t size)) { - kern_return_t r = KERN_SUCCESS; - mach_vm_address_t local_address = 0; - mach_vm_address_t local_size = remote_size; - while (1) { - vm_prot_t cur_protection, max_protection; - r = mach_vm_remap(mach_task_self(), - &local_address, - local_size, - 0, // mask - VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR | VM_FLAGS_RESILIENT_CODESIGN, - task, - remote_address, - TRUE, // Copy semantics: changes to this memory by the target process will not be visible in this process - &cur_protection, - &max_protection, - VM_INHERIT_DEFAULT); - //Do this here to allow chaining of multiple embedded blocks with a single error out; - if (kr != NULL) { - *kr = r; - } - if (r == KERN_SUCCESS) { - // We got someting, call the block and then exit - block(reinterpret_cast(local_address), local_size); - vm_deallocate(mach_task_self(), local_address, local_size); - break; - } - if (!allow_truncation) { - break; - } - // We did not get something, but we are allowed to truncate and try again - uint64_t trunc_size = ((remote_address + local_size - 1) & PAGE_MASK) + 1; - if (local_size <= trunc_size) { - //Even if we truncate it will be in the same page, time to accept defeat - break; - } else { - local_size -= trunc_size; +RemoteBuffer::RemoteBuffer() : _localAddress(0), _size(0) {} +bool RemoteBuffer::map(task_t task, mach_vm_address_t remote_address, bool shared) { + vm_prot_t cur_protection = VM_PROT_NONE; + vm_prot_t max_protection = VM_PROT_NONE; + if (_size == 0) { + _kr = KERN_NO_SPACE; + return false; + } + _localAddress = 0; + _kr = mach_vm_remap(mach_task_self(), + &_localAddress, + _size, + 0, // mask + VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR | (shared ? 0 : VM_FLAGS_RESILIENT_CODESIGN), + task, + remote_address, + !shared, + &cur_protection, + &max_protection, + VM_INHERIT_NONE); + dyld3::kdebug_trace_dyld_marker(DBG_DYLD_DEBUGGING_VM_REMAP, _localAddress, (uint64_t)_size, _kr, remote_address); + if (shared && (cur_protection != (VM_PROT_READ|VM_PROT_WRITE))) { + if (_kr == KERN_SUCCESS && _localAddress != 0) { + _kr = vm_deallocate(mach_task_self(), _localAddress, _size); + dyld3::kdebug_trace_dyld_marker(DBG_DYLD_DEBUGGING_VM_UNMAP, _localAddress, (uint64_t)_size, _kr, 0); } + _localAddress = 0; + _kr = KERN_PROTECTION_FAILURE; } + return (_kr == KERN_SUCCESS); } -template -void withRemoteObject(task_t task, vm_address_t remote_address, kern_return_t *kr, void (^block)(T t)) -{ - withRemoteBuffer(task, remote_address, sizeof(T), false, kr, ^(void *buffer, size_t size) { - block(*reinterpret_cast(buffer)); - }); +RemoteBuffer::RemoteBuffer(task_t task, mach_vm_address_t remote_address, size_t remote_size, bool shared, bool allow_truncation) + : _localAddress(0), _size(remote_size), _kr(KERN_SUCCESS) { + // Try the initial map + if (map(task, remote_address, shared)) return; + // It failed, try to calculate the largest size that can fit in the same page as the remote_address + uint64_t newSize = PAGE_SIZE - remote_address%PAGE_SIZE;; + // If truncation is allowed and the newSize is different than the original size try that + if (!allow_truncation && newSize != _size) return; + _size = newSize; + if (map(task, remote_address, shared)) return; + // That did not work, null out the buffer + _size = 0; + _localAddress = 0; } -}; +RemoteBuffer::~RemoteBuffer() { + if (_localAddress) { + _kr = vm_deallocate(mach_task_self(), _localAddress, _size); + dyld3::kdebug_trace_dyld_marker(DBG_DYLD_DEBUGGING_VM_UNMAP, _localAddress, (uint64_t)_size, _kr, 0); + } +} +void *RemoteBuffer::getLocalAddress() { return (void *)_localAddress; } +size_t RemoteBuffer::getSize() { return _size; } +kern_return_t RemoteBuffer::getKernelReturn() { return _kr; } + +void withRemoteBuffer(task_t task, mach_vm_address_t remote_address, size_t remote_size, bool shared, bool allow_truncation, kern_return_t *kr, void (^block)(void *buffer, size_t size)) { + kern_return_t krSink = KERN_SUCCESS; + if (kr == nullptr) { + kr = &krSink; + } + RemoteBuffer buffer(task, remote_address, remote_size, shared, allow_truncation); + *kr = buffer.getKernelReturn(); + if (*kr == KERN_SUCCESS) { + block(buffer.getLocalAddress(), buffer.getSize()); + } +} + // // Opaque object returned by _dyld_process_info_create() // +struct __attribute__((visibility("hidden"))) dyld_process_info_deleter { // deleter + // dyld_process_info_deleter() {}; + // dyld_process_info_deleter(const dyld_process_info_deleter&) { } + // dyld_process_info_deleter(dyld_process_info_deleter&) {} + // dyld_process_info_deleter(dyld_process_info_deleter&&) {} + void operator()(dyld_process_info_base* p) const { + if (p) { + free(p); + } + }; +}; + +static dyld_process_info_deleter deleter; +typedef std::unique_ptr dyld_process_info_ptr; + struct __attribute__((visibility("hidden"))) dyld_process_info_base { - static dyld_process_info_base* make(task_t task, const dyld_all_image_infos_64& allImageInfo, const dyld_image_info_64 imageArray[], kern_return_t* kr); - static dyld_process_info_base* makeSuspended(task_t task, kern_return_t* kr); + template + static dyld_process_info_ptr make(task_t task, const T1& allImageInfo, uint64_t timestamp, kern_return_t* kr); + template + static dyld_process_info_ptr makeSuspended(task_t task, const T& allImageInfo, kern_return_t* kr); - uint32_t& retainCount() const { return _retainCount; } + std::atomic& retainCount() const { return _retainCount; } dyld_process_cache_info* cacheInfo() const { return (dyld_process_cache_info*)(((char*)this) + _cacheInfoOffset); } dyld_process_state_info* stateInfo() const { return (dyld_process_state_info*)(((char*)this) + _stateInfoOffset); } void forEachImage(void (^callback)(uint64_t machHeaderAddress, const uuid_t uuid, const char* path)) const; void forEachSegment(uint64_t machHeaderAddress, void (^callback)(uint64_t segmentAddress, uint64_t segmentSize, const char* segmentName)) const; + void retain() + { + _retainCount++; + } + + void release() + { + uint32_t newCount = --_retainCount; + + if ( newCount == 0 ) { + free(this); + } + } + private: struct ImageInfo { uuid_t uuid; @@ -129,6 +179,7 @@ private: static bool inCache(uint64_t addr) { return (addr > SHARED_REGION_BASE) && (addr < SHARED_REGION_BASE+SHARED_REGION_SIZE); } kern_return_t addImage(task_t task, bool sameCacheAsThisProcess, uint64_t imageAddress, uint64_t imagePath, const char* imagePathLocal); + kern_return_t addDyldImage(task_t task, uint64_t dyldAddress, uint64_t dyldPathAddress, const char* localPath); bool invalid() { return ((char*)_stringRevBumpPtr < (char*)_curSegment); } @@ -137,11 +188,12 @@ private: const char* copySegmentName(const char*); void addInfoFromLoadCommands(const mach_header* mh, uint64_t addressInTask, size_t size); + kern_return_t addInfoFromRemoteLoadCommands(task_t task, uint64_t remoteMH); void inspectLocalImageLoadCommands(uint64_t imageAddress, void* func); kern_return_t inspectRemoteImageLoadCommands(task_t task, uint64_t imageAddress, void* func); - mutable uint32_t _retainCount; + mutable std::atomic _retainCount; const uint32_t _cacheInfoOffset; const uint32_t _stateInfoOffset; const uint32_t _imageInfosOffset; @@ -174,103 +226,175 @@ dyld_process_info_base::dyld_process_info_base(unsigned imageCount, size_t total { } - -dyld_process_info_base* dyld_process_info_base::make(task_t task, const dyld_all_image_infos_64& allImageInfo, const dyld_image_info_64 imageArray[], kern_return_t* kr) +template +dyld_process_info_ptr dyld_process_info_base::make(task_t task, const T1& allImageInfo, uint64_t timestamp, kern_return_t* kr) { - // figure out how many path strings will need to be copied and their size - const dyld_all_image_infos* myInfo = _dyld_get_all_image_infos(); - bool sameCacheAsThisProcess = !allImageInfo.processDetachedFromSharedRegion - && !myInfo->processDetachedFromSharedRegion - && ((memcmp(myInfo->sharedCacheUUID, allImageInfo.sharedCacheUUID, 16) == 0) - && (myInfo->sharedCacheSlide == allImageInfo.sharedCacheSlide)); - unsigned countOfPathsNeedingCopying = 0; - if ( sameCacheAsThisProcess ) { - for (int i=0; i < allImageInfo.infoArrayCount; ++i) { - if ( !inCache(imageArray[i].imageFilePath) ) - ++countOfPathsNeedingCopying; - } - } - else { - countOfPathsNeedingCopying = allImageInfo.infoArrayCount+1; - } - unsigned imageCountWithDyld = allImageInfo.infoArrayCount+1; + __block dyld_process_info_ptr result = nullptr; - // allocate result object - size_t allocationSize = sizeof(dyld_process_info_base) - + sizeof(dyld_process_cache_info) - + sizeof(dyld_process_state_info) - + sizeof(ImageInfo)*(imageCountWithDyld) - + sizeof(SegmentInfo)*imageCountWithDyld*5 - + countOfPathsNeedingCopying*PATH_MAX; - void* storage = malloc(allocationSize); - dyld_process_info_base* obj = new (storage) dyld_process_info_base(imageCountWithDyld, allocationSize); // placement new() - - // fill in base info - dyld_process_cache_info* cacheInfo = obj->cacheInfo(); - memcpy(cacheInfo->cacheUUID, allImageInfo.sharedCacheUUID, 16); - cacheInfo->cacheBaseAddress = allImageInfo.sharedCacheBaseAddress; - cacheInfo->privateCache = allImageInfo.processDetachedFromSharedRegion; - // if no cache is used, allImageInfo has all zeros for cache UUID - cacheInfo->noCache = true; - for (int i=0; i < 16; ++i) { - if ( cacheInfo->cacheUUID[i] != 0 ) { - cacheInfo->noCache = false; + // bail out of dyld is too old + if ( allImageInfo.version < 15 ) { + *kr = KERN_FAILURE; + return nullptr; + } + + // Check if the process is suspended + if (allImageInfo.infoArrayChangeTimestamp == 0) { + result = dyld_process_info_base::makeSuspended(task, allImageInfo, kr); + // If we have a result return it, otherwise rescan + if (result) { + // If it returned the process is suspended and there is nothing more to do + return std::move(result); + } else { + // Check to see if the process change timestamp is greater than 0, if not then sleep to let the process + // finish initializing + if (allImageInfo.infoArrayChangeTimestamp == 0) { + usleep(1000 * 50); // 50ms + } } } - dyld_process_state_info* stateInfo = obj->stateInfo(); - stateInfo->timestamp = allImageInfo.infoArrayChangeTimestamp; - stateInfo->imageCount = imageCountWithDyld; - stateInfo->initialImageCount = (uint32_t)(allImageInfo.initialImageCount+1); - if ( allImageInfo.infoArray != 0 ) - stateInfo->dyldState = dyld_process_state_dyld_initialized; - if ( allImageInfo.libSystemInitialized != 0 ) { - stateInfo->dyldState = dyld_process_state_libSystem_initialized; - if ( allImageInfo.initialImageCount != allImageInfo.infoArrayCount ) - stateInfo->dyldState = dyld_process_state_program_running; - } - if ( allImageInfo.errorMessage != 0 ) - stateInfo->dyldState = allImageInfo.terminationFlags ? dyld_process_state_terminated_before_inits : dyld_process_state_dyld_terminated; + // Test to see if there are no changes and we can exit early + if (timestamp != 0 && timestamp == allImageInfo.infoArrayChangeTimestamp) { + *kr = KERN_SUCCESS; + return nullptr; + } + + for(uint32_t i = 0; i < 10; ++i) { + uint64_t currentTimestamp = allImageInfo.infoArrayChangeTimestamp; + mach_vm_address_t infoArray = allImageInfo.infoArray; + if (currentTimestamp == 0) continue; + if (infoArray == 0) { + // Check if the task is suspended mid dylib load and exit early + mach_task_basic_info ti; + mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT; + if ((*kr = task_info(task, MACH_TASK_BASIC_INFO, (task_info_t)&ti, &count))) { + continue; + } - // fill in info for dyld - if ( allImageInfo.dyldPath != 0 ) { - if ( kern_return_t r = obj->addDyldImage(task, allImageInfo.dyldImageLoadAddress, allImageInfo.dyldPath, NULL) ) { - if ( kr != NULL ) - *kr = r; - goto fail; - } - } + // The task is suspended, exit + if (ti.suspend_count != 0) { + // Not exactly correct, but conveys that operation may succeed in the future + *kr = KERN_RESOURCE_SHORTAGE; + return nullptr; + } + continue; + }; + + // For the moment we are going to truncate any image list longer than 8192 because some programs do + // terrible things that corrupt their own image lists and we need to stop clients from crashing + // reading them. We can try to do something more advanced in the future. rdar://27446361 + uint32_t imageCount = allImageInfo.infoArrayCount; + imageCount = MIN(imageCount, 8192); + size_t imageArraySize = imageCount * sizeof(T2); + + withRemoteBuffer(task, infoArray, imageArraySize, false, false, kr, ^(void *buffer, size_t size) { + // figure out how many path strings will need to be copied and their size + T2* imageArray = (T2 *)buffer; + const dyld_all_image_infos* myInfo = _dyld_get_all_image_infos(); + bool sameCacheAsThisProcess = !allImageInfo.processDetachedFromSharedRegion + && !myInfo->processDetachedFromSharedRegion + && ((memcmp(myInfo->sharedCacheUUID, &allImageInfo.sharedCacheUUID[0], 16) == 0) + && (myInfo->sharedCacheSlide == allImageInfo.sharedCacheSlide)); + unsigned countOfPathsNeedingCopying = 0; + if ( sameCacheAsThisProcess ) { + for (uint32_t i=0; i < imageCount; ++i) { + if ( !inCache(imageArray[i].imageFilePath) ) + ++countOfPathsNeedingCopying; + } + } + else { + countOfPathsNeedingCopying = imageCount+1; + } + unsigned imageCountWithDyld = imageCount+1; + + // allocate result object + size_t allocationSize = sizeof(dyld_process_info_base) + + sizeof(dyld_process_cache_info) + + sizeof(dyld_process_state_info) + + sizeof(ImageInfo)*(imageCountWithDyld) + + sizeof(SegmentInfo)*imageCountWithDyld*5 + + countOfPathsNeedingCopying*PATH_MAX; + void* storage = malloc(allocationSize); + auto info = dyld_process_info_ptr(new (storage) dyld_process_info_base(imageCountWithDyld, allocationSize), deleter); + //info = new (storage) dyld_process_info_base(imageCountWithDyld, allocationSize); // placement new() + + // fill in base info + dyld_process_cache_info* cacheInfo = info->cacheInfo(); + memcpy(cacheInfo->cacheUUID, &allImageInfo.sharedCacheUUID[0], 16); + cacheInfo->cacheBaseAddress = allImageInfo.sharedCacheBaseAddress; + cacheInfo->privateCache = allImageInfo.processDetachedFromSharedRegion; + // if no cache is used, allImageInfo has all zeros for cache UUID + cacheInfo->noCache = true; + for (int i=0; i < 16; ++i) { + if ( cacheInfo->cacheUUID[i] != 0 ) { + cacheInfo->noCache = false; + } + } - // fill in info for each image - for (uint32_t i=0; i < allImageInfo.infoArrayCount; ++i) { - if ( kern_return_t r = obj->addImage(task, sameCacheAsThisProcess, imageArray[i].imageLoadAddress, imageArray[i].imageFilePath, NULL) ) { - if ( kr != NULL ) - *kr = r; - goto fail; - } - } + dyld_process_state_info* stateInfo = info->stateInfo(); + stateInfo->timestamp = currentTimestamp; + stateInfo->imageCount = imageCountWithDyld; + stateInfo->initialImageCount = (uint32_t)(allImageInfo.initialImageCount+1); + stateInfo->dyldState = dyld_process_state_dyld_initialized; - // sanity check internal data did not overflow - if ( obj->invalid() ) - goto fail; + if ( allImageInfo.libSystemInitialized != 0 ) { + stateInfo->dyldState = dyld_process_state_libSystem_initialized; + if ( allImageInfo.initialImageCount != imageCount ) { + stateInfo->dyldState = dyld_process_state_program_running; + } + } + if ( allImageInfo.errorMessage != 0 ) { + stateInfo->dyldState = allImageInfo.terminationFlags ? dyld_process_state_terminated_before_inits : dyld_process_state_dyld_terminated; + } + // fill in info for dyld + if ( allImageInfo.dyldPath != 0 ) { + if ((*kr = info->addDyldImage(task, allImageInfo.dyldImageLoadAddress, allImageInfo.dyldPath, NULL))) { + result = nullptr; + return; + } + } + // fill in info for each image + for (uint32_t i=0; i < imageCount; ++i) { + if ((*kr = info->addImage(task, sameCacheAsThisProcess, imageArray[i].imageLoadAddress, imageArray[i].imageFilePath, NULL))) { + result = nullptr; + return; + } + } + // sanity check internal data did not overflow + if ( info->invalid() ) { + *kr = KERN_FAILURE; + result = nullptr; + return; + } - return obj; + result = std::move(info); + }); -fail: - free(obj); - return NULL; + if (result) break; + } + + return std::move(result); } -dyld_process_info_base* dyld_process_info_base::makeSuspended(task_t task, kern_return_t* kr) +template +dyld_process_info_ptr dyld_process_info_base::makeSuspended(task_t task, const T& allImageInfo, kern_return_t* kr) { pid_t pid; - kern_return_t result = pid_for_task(task, &pid); - if ( result != KERN_SUCCESS ) { - if ( kr != NULL ) - *kr = result; + if ((*kr = pid_for_task(task, &pid))) { return NULL; } + mach_task_basic_info ti; + mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT; + if ((*kr = task_info(task, MACH_TASK_BASIC_INFO, (task_info_t)&ti, &count))) { + return nullptr; + } + + // The task is not suspended, exit + if (ti.suspend_count == 0) { + return nullptr; + } + __block unsigned imageCount = 0; // main executable and dyld __block uint64_t mainExecutableAddress = 0; __block uint64_t dyldAddress = 0; @@ -283,14 +407,14 @@ dyld_process_info_base* dyld_process_info_base::makeSuspended(task_t task, kern_ vm_region_basic_info_data_64_t info; mach_port_t objectName; unsigned int infoCount = VM_REGION_BASIC_INFO_COUNT_64; - result = mach_vm_region(task, &address, &size, VM_REGION_BASIC_INFO, - (vm_region_info_t)&info, &infoCount, &objectName); - if ( result != KERN_SUCCESS ) + if (kern_return_t r = mach_vm_region(task, &address, &size, VM_REGION_BASIC_INFO, + (vm_region_info_t)&info, &infoCount, &objectName)) { break; + } if ( info.protection != (VM_PROT_READ|VM_PROT_EXECUTE) ) continue; // read start of vm region to verify it is a mach header - withRemoteObject(task, address, NULL, ^(mach_header_64 mhBuffer){ + withRemoteObject(task, address, false, NULL, ^(mach_header_64 mhBuffer){ if ( (mhBuffer.magic != MH_MAGIC) && (mhBuffer.magic != MH_MAGIC_64) ) return; // now know the region is the start of a mach-o file @@ -324,8 +448,7 @@ dyld_process_info_base* dyld_process_info_base::makeSuspended(task_t task, kern_ + sizeof(SegmentInfo)*imageCount*5 + imageCount*PATH_MAX; void* storage = malloc(allocationSize); - dyld_process_info_base* obj = new (storage) dyld_process_info_base(imageCount, allocationSize); // placement new() - + auto obj = dyld_process_info_ptr(new (storage) dyld_process_info_base(imageCount, allocationSize), deleter); // fill in base info dyld_process_cache_info* cacheInfo = obj->cacheInfo(); bzero(cacheInfo->cacheUUID, 16); @@ -341,24 +464,32 @@ dyld_process_info_base* dyld_process_info_base::makeSuspended(task_t task, kern_ // fill in info for dyld if ( dyldAddress != 0 ) { - if ( kern_return_t r = obj->addDyldImage(task, dyldAddress, 0, dyldPath) ) { - if ( kr != NULL ) - *kr = r; - free(obj); - return NULL; + if ((*kr = obj->addDyldImage(task, dyldAddress, 0, dyldPath))) { + return nullptr; } } // fill in info for each image if ( mainExecutableAddress != 0 ) { - if ( kern_return_t r = obj->addImage(task, false, mainExecutableAddress, 0, mainExecutablePath) ) { - if ( kr != NULL ) - *kr = r; - free(obj); - return NULL; + if ((*kr = obj->addImage(task, false, mainExecutableAddress, 0, mainExecutablePath))) { + return nullptr; } } + if (allImageInfo.infoArrayChangeTimestamp != 0) { + return nullptr; + } + + count = MACH_TASK_BASIC_INFO_COUNT; + if ((*kr = task_info(task, MACH_TASK_BASIC_INFO, (task_info_t)&ti, &count))) { + return nullptr; + } + + // The task is not suspended, exit + if (ti.suspend_count == 0) { + return nullptr; + } + return obj; } @@ -375,15 +506,15 @@ const char* dyld_process_info_base::addString(const char* str, size_t maxlen) const char* dyld_process_info_base::copyPath(task_t task, uint64_t stringAddressInTask, kern_return_t* kr) { __block const char* retval = NULL; - withRemoteBuffer(task, stringAddressInTask, PATH_MAX, true, kr, ^(void *buffer, size_t size) { + withRemoteBuffer(task, stringAddressInTask, PATH_MAX, false, true, kr, ^(void *buffer, size_t size) { retval = addString(static_cast(buffer), size); }); - return retval; } kern_return_t dyld_process_info_base::addImage(task_t task, bool sameCacheAsThisProcess, uint64_t imageAddress, uint64_t imagePath, const char* imagePathLocal) { + kern_return_t kr = KERN_SUCCESS; _curImage->loadAddress = imageAddress; _curImage->segmentStartIndex = _curSegmentIndex; if ( imagePathLocal != NULL ) { @@ -393,25 +524,17 @@ kern_return_t dyld_process_info_base::addImage(task_t task, bool sameCacheAsThis _curImage->path = (const char*)imagePath; } else { - kern_return_t kr; _curImage->path = copyPath(task, imagePath, &kr); - if ( kr ) + if ( kr != KERN_SUCCESS) return kr; } if ( sameCacheAsThisProcess && inCache(imageAddress) ) { addInfoFromLoadCommands((mach_header*)imageAddress, imageAddress, 32*1024); } else { - __block kern_return_t kr = KERN_SUCCESS; - withRemoteObject(task, imageAddress, &kr, ^(mach_header_64 mhBuffer) { - size_t headerPagesSize = sizeof(mach_header_64) + mhBuffer.sizeofcmds; - withRemoteBuffer(task, imageAddress, headerPagesSize, false, &kr, ^(void * buffer, size_t size) { - addInfoFromLoadCommands((mach_header*)buffer, imageAddress, size); - }); - }); - if (kr != KERN_SUCCESS) { + kr = addInfoFromRemoteLoadCommands(task, imageAddress); + if ( kr != KERN_SUCCESS) return kr; - } } _curImage->segmentsCount = _curSegmentIndex - _curImage->segmentStartIndex; _curImage++; @@ -419,6 +542,34 @@ kern_return_t dyld_process_info_base::addImage(task_t task, bool sameCacheAsThis } +kern_return_t dyld_process_info_base::addInfoFromRemoteLoadCommands(task_t task, uint64_t remoteMH) { + __block kern_return_t kr = KERN_SUCCESS; + __block size_t headerPagesSize = 0; + __block bool done = false; + + //Since the minimum we can reasonably map is a page, map that. + withRemoteBuffer(task, remoteMH, PAGE_SIZE, false, false, &kr, ^(void * buffer, size_t size) { + const mach_header* mh = (const mach_header*)buffer; + headerPagesSize = sizeof(mach_header) + mh->sizeofcmds; + if (headerPagesSize <= PAGE_SIZE) { + addInfoFromLoadCommands(mh, remoteMH, size); + done = true; + } + }); + + //The load commands did not fit in the first page, but now we know the size, so remap and try again + if (!done) { + if (kr != KERN_SUCCESS) { + return kr; + } + withRemoteBuffer(task, remoteMH, headerPagesSize, false, false, &kr, ^(void * buffer, size_t size) { + addInfoFromLoadCommands((mach_header*)buffer, remoteMH, size); + }); + } + + return kr; +} + kern_return_t dyld_process_info_base::addDyldImage(task_t task, uint64_t dyldAddress, uint64_t dyldPathAddress, const char* localPath) { __block kern_return_t kr = KERN_SUCCESS; @@ -429,19 +580,13 @@ kern_return_t dyld_process_info_base::addDyldImage(task_t task, uint64_t dyldAdd } else { _curImage->path = copyPath(task, dyldPathAddress, &kr); - if ( kr ) + if ( kr != KERN_SUCCESS) return kr; } - withRemoteObject(task, dyldAddress, &kr, ^(mach_header_64 mhBuffer) { - size_t headerPagesSize = sizeof(mach_header_64) + mhBuffer.sizeofcmds; - withRemoteBuffer(task, dyldAddress, headerPagesSize, false, &kr, ^(void * buffer, size_t size) { - addInfoFromLoadCommands((mach_header*)buffer, dyldAddress, size); - }); - }); - if (kr != KERN_SUCCESS) { + kr = addInfoFromRemoteLoadCommands(task, dyldAddress); + if ( kr != KERN_SUCCESS) return kr; - } _curImage->segmentsCount = _curSegmentIndex - _curImage->segmentStartIndex; _curImage++; @@ -514,14 +659,14 @@ void dyld_process_info_base::forEachSegment(uint64_t machHeaderAddress, void (^c for (const ImageInfo* p = _firstImage; p < _curImage; ++p) { if ( p->loadAddress == machHeaderAddress ) { uint64_t slide = 0; - for (int i=0; i < p->segmentsCount; ++i) { + for (uint32_t i=0; i < p->segmentsCount; ++i) { const SegmentInfo* seg = &_firstSegment[p->segmentStartIndex+i]; if ( strcmp(seg->name, "__TEXT") == 0 ) { slide = machHeaderAddress - seg->addr; break; } } - for (int i=0; i < p->segmentsCount; ++i) { + for (uint32_t i=0; i < p->segmentsCount; ++i) { const SegmentInfo* seg = &_firstSegment[p->segmentStartIndex+i]; callback(seg->addr + slide, seg->size, seg->name); } @@ -530,163 +675,47 @@ void dyld_process_info_base::forEachSegment(uint64_t machHeaderAddress, void (^c } } - - - - -// Implementation that works with existing dyld data structures -static dyld_process_info _dyld_process_info_create_inner(task_t task, uint64_t timestamp, kern_return_t* kr) +dyld_process_info _dyld_process_info_create(task_t task, uint64_t timestamp, kern_return_t* kr) { - if ( kr != NULL ) - *kr = KERN_SUCCESS; + __block dyld_process_info result = nullptr; + kern_return_t krSink = KERN_SUCCESS; + if (kr == nullptr) { + kr = &krSink; + } + *kr = KERN_SUCCESS; task_dyld_info_data_t task_dyld_info; mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; if ( kern_return_t r = task_info(task, TASK_DYLD_INFO, (task_info_t)&task_dyld_info, &count) ) { - if ( kr != NULL ) - *kr = r; - return NULL; + *kr = r; + return nullptr; } //The kernel will return MACH_VM_MIN_ADDRESS for an executable that has not had dyld loaded if (task_dyld_info.all_image_info_addr == MACH_VM_MIN_ADDRESS) - return NULL; + return nullptr; if ( task_dyld_info.all_image_info_size > sizeof(dyld_all_image_infos_64) ) - return NULL; - - // read all_image_infos struct - dyld_all_image_infos_64 allImageInfo64; - mach_vm_size_t readSize = task_dyld_info.all_image_info_size; - if ( kern_return_t r = mach_vm_read_overwrite(task, task_dyld_info.all_image_info_addr, task_dyld_info.all_image_info_size, (vm_address_t)&allImageInfo64, &readSize) ) { - if ( kr != NULL ) - *kr = r; - return NULL; - } - if ( allImageInfo64.infoArrayCount == 0 ) { - // could be task was launch suspended or still launching, wait a moment to see - usleep(1000 * 50); // 50ms - if ( kern_return_t r = mach_vm_read_overwrite(task, task_dyld_info.all_image_info_addr, task_dyld_info.all_image_info_size, (vm_address_t)&allImageInfo64, &readSize) ) { - if ( kr != NULL ) - *kr = r; - return NULL; - } - // if infoArrayCount is still zero, then target was most likely launched suspended - if ( allImageInfo64.infoArrayCount == 0 ) - return dyld_process_info_base::makeSuspended(task, kr); - } - - // bail out of dyld is too old - if ( allImageInfo64.version < 15 ) { - if ( kr != NULL ) - *kr = KERN_INVALID_HOST; - return NULL; - } - - // normalize by expanding 32-bit all_image_infos into 64-bit one - uint32_t imageCount = allImageInfo64.infoArrayCount; - size_t imageArraySize = imageCount * sizeof(dyld_image_info_64); - if ( task_dyld_info.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) { - const dyld_all_image_infos_32* allImageInfo32 = (dyld_all_image_infos_32*)&allImageInfo64; - dyld_all_image_infos_64 info64; - bzero(&info64, sizeof(info64)); - info64.version = allImageInfo32->version; - info64.infoArrayCount = allImageInfo32->infoArrayCount; - info64.infoArray = allImageInfo32->infoArray; - info64.processDetachedFromSharedRegion = allImageInfo32->processDetachedFromSharedRegion; - info64.libSystemInitialized = allImageInfo32->libSystemInitialized; - info64.dyldImageLoadAddress = allImageInfo32->dyldImageLoadAddress; - info64.initialImageCount = allImageInfo32->initialImageCount; - info64.uuidArrayCount = allImageInfo32->uuidArrayCount; - info64.uuidArray = allImageInfo32->uuidArray; - info64.dyldAllImageInfosAddress = allImageInfo32->dyldAllImageInfosAddress; - info64.sharedCacheSlide = allImageInfo32->sharedCacheSlide; - info64.infoArrayChangeTimestamp = allImageInfo32->infoArrayChangeTimestamp; - info64.sharedCacheBaseAddress = allImageInfo32->sharedCacheBaseAddress; - info64.dyldPath = allImageInfo32->dyldPath; - memcpy((void*)(info64.sharedCacheUUID), (void*)(allImageInfo32->sharedCacheUUID), 16); - allImageInfo64 = info64; - imageCount = allImageInfo64.infoArrayCount; - imageArraySize = imageCount * sizeof(dyld_image_info_32); - } - - // don't do any (more) work if target process's dyld timestamp has not changed since previous query - if ( (timestamp != 0) && (timestamp == allImageInfo64.infoArrayChangeTimestamp) ) { - if ( kr != NULL ) - *kr = KERN_SUCCESS; - return NULL; - } - - // For the moment we are going to truncate any image list longer than 8192 because some programs do - // terrible things that corrupt their own image lists and we need to stop clients from crashing - // reading them. We can try to do something more advanced in the future. rdar://27446361 - imageCount = MIN(imageCount, 8192); - - // read image array - if ( allImageInfo64.infoArray == 0 ) { - // dyld is in middle of updating image list, try again - return NULL; - } - dyld_image_info_64 imageArray64[imageCount]; - if ( kern_return_t r = mach_vm_read_overwrite(task, allImageInfo64.infoArray, imageArraySize, (vm_address_t)&imageArray64, &readSize) ) { - // if image array moved, try whole thing again - if ( kr != NULL ) { - *kr = r; - } - return NULL; - } - // normalize by expanding 32-bit image_infos into 64-bit ones - if ( task_dyld_info.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) { - const dyld_image_info_32* imageArray32 = (dyld_image_info_32*)&imageArray64; - dyld_image_info_64 tempArray[imageCount]; - for (uint32_t i=0; i < imageCount; ++i) { - tempArray[i].imageLoadAddress = imageArray32[i].imageLoadAddress; - tempArray[i].imageFilePath = imageArray32[i].imageFilePath; - tempArray[i].imageFileModDate = imageArray32[i].imageFileModDate; - } - memcpy(imageArray64, tempArray, sizeof(dyld_image_info_64)*imageCount); - } - - // create object based on local copy of all image infos and image array - dyld_process_info result = dyld_process_info_base::make(task, allImageInfo64, imageArray64, kr); - - // verify nothing has changed by re-reading all_image_infos struct and checking timestamp - if ( result != NULL ) { - dyld_all_image_infos_64 allImageInfo64again; - readSize = task_dyld_info.all_image_info_size; - if ( kern_return_t r = mach_vm_read_overwrite(task, task_dyld_info.all_image_info_addr, task_dyld_info.all_image_info_size, (vm_address_t)&allImageInfo64again, &readSize) ) { - if ( kr != NULL ) - *kr = r; - free((void*)result); - return NULL; - } - uint64_t doneTimeStamp = allImageInfo64again.infoArrayChangeTimestamp; - if ( task_dyld_info.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) { - const dyld_all_image_infos_32* allImageInfo32 = (dyld_all_image_infos_32*)&allImageInfo64again; - doneTimeStamp = allImageInfo32->infoArrayChangeTimestamp; + return nullptr; + + // We use a true shared memory buffer here, that way by making sure that libdyld in both processes + // reads and writes the the timestamp atomically we can make sure we get a coherent view of the + // remote process. + // That also means that we *MUST* directly read the memory, which is why we template the make() call + withRemoteBuffer(task, task_dyld_info.all_image_info_addr, task_dyld_info.all_image_info_size, true, false, kr, ^(void *buffer, size_t size) { + dyld_process_info_ptr base; + if (task_dyld_info.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 ) { + const dyld_all_image_infos_32* info = (const dyld_all_image_infos_32*)buffer; + base = dyld_process_info_base::make(task, *info, timestamp, kr); + } else { + const dyld_all_image_infos_64* info = (const dyld_all_image_infos_64*)buffer; + base = dyld_process_info_base::make(task, *info, timestamp, kr); } - if ( allImageInfo64.infoArrayChangeTimestamp != doneTimeStamp ) { - // image list has changed since we started reading it - // throw out what we have and start over - free((void*)result); - result = nullptr; + if (base) { + result = base.release(); } - } - - return result; -} - - -dyld_process_info _dyld_process_info_create(task_t task, uint64_t timestamp, kern_return_t* kr) -{ - // Other process may be loading and unloading as we read its memory, which can cause a read failure - // Retry if something fails - for (int i=0; i < 100; ++i) { - if ( dyld_process_info result = _dyld_process_info_create_inner( task, timestamp, kr) ) - return result; - - } - return NULL; + }); + return result; } void _dyld_process_info_get_state(dyld_process_info info, dyld_process_state_info* stateInfo) @@ -699,16 +728,14 @@ void _dyld_process_info_get_cache(dyld_process_info info, dyld_process_cache_inf *cacheInfo = *info->cacheInfo(); } -void _dyld_process_info_retain(dyld_process_info info) +void _dyld_process_info_retain(dyld_process_info object) { - info->retainCount() += 1; + const_cast(object)->retain(); } -void _dyld_process_info_release(dyld_process_info info) +void _dyld_process_info_release(dyld_process_info object) { - info->retainCount() -= 1; - if ( info->retainCount() == 0 ) - free((void*)info); + const_cast(object)->release(); } void _dyld_process_info_for_each_image(dyld_process_info info, void (^callback)(uint64_t machHeaderAddress, const uuid_t uuid, const char* path))