| //===-- MachDYLD.cpp --------------------------------------------*- C++ -*-===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // Created by Greg Clayton on 6/29/07. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "MachDYLD.h" |
| #include "DNB.h" |
| #include "DNBDataRef.h" |
| #include <mach-o/loader.h> |
| #include "DNBLog.h" |
| |
| MachDYLD::MachDYLD() : |
| m_pid(INVALID_NUB_PROCESS), |
| m_addr_size(4), |
| m_dyld_addr(INVALID_NUB_ADDRESS), |
| m_dyld_all_image_infos_addr(INVALID_NUB_ADDRESS), |
| m_dylib_info_header(), |
| m_current_dylibs(), |
| m_changed_dylibs(), |
| m_notify_break_id(INVALID_NUB_BREAK_ID), |
| m_dyld_info_mutex(PTHREAD_MUTEX_RECURSIVE) |
| { |
| } |
| |
| MachDYLD::~MachDYLD() |
| { |
| Clear(); |
| } |
| |
| |
| void |
| MachDYLD::Clear() |
| { |
| PThreadMutex::Locker locker(m_dyld_info_mutex); |
| |
| nub_process_t pid = m_pid; |
| if (pid != INVALID_NUB_PROCESS) |
| { |
| DNBProcessSetSharedLibraryInfoCallback ( pid, NULL, NULL); |
| DNBBreakpointClear(pid, m_notify_break_id); |
| } |
| |
| m_addr_size = 4; |
| m_dyld_addr = INVALID_NUB_ADDRESS; |
| m_dyld_all_image_infos_addr = INVALID_NUB_ADDRESS; |
| m_dylib_info_header.Clear(); |
| m_current_dylibs.clear(); |
| m_changed_dylibs.clear(); |
| m_notify_break_id = INVALID_NUB_BREAK_ID; |
| } |
| |
| |
| void |
| MachDYLD::Initialize(nub_process_t pid) |
| { |
| //printf("MachDYLD::%s(0x%4.4x)\n", __FUNCTION__, pid); |
| Clear(); |
| m_pid = pid; |
| } |
| |
| |
| void |
| MachDYLD::ProcessStateChanged(nub_state_t state) |
| { |
| //printf("MachDYLD::%s(%s)\n", __FUNCTION__, DNBStateAsString(state)); |
| |
| switch (state) |
| { |
| case eStateInvalid: |
| case eStateUnloaded: |
| case eStateExited: |
| case eStateDetached: |
| case eStateAttaching: |
| case eStateLaunching: |
| Clear(); |
| break; |
| |
| case eStateStopped: |
| // Keep trying find dyld each time we stop until we do |
| if (!FoundDYLD()) |
| { |
| assert(m_pid != INVALID_NUB_PROCESS); |
| DNBProcessSetSharedLibraryInfoCallback ( m_pid, CopySharedInfoCallback, this); |
| CheckForDYLDInMemory(); |
| } |
| break; |
| |
| case eStateRunning: |
| case eStateStepping: |
| case eStateCrashed: |
| case eStateSuspended: |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| void |
| MachDYLD::SharedLibraryStateChanged(DNBExecutableImageInfo *image_infos, nub_size_t num_image_infos) |
| { |
| //printf("MachDYLD::%s(%p, %u)\n", __FUNCTION__, image_infos, image_infos); |
| |
| } |
| |
| bool |
| MachDYLD::FoundDYLD() const |
| { |
| return m_dyld_addr != INVALID_NUB_ADDRESS; |
| } |
| |
| bool |
| MachDYLD::CheckForDYLDInMemory() |
| { |
| #if defined (__arm__) |
| return CheckForDYLDInMemory(0x2fe00000); |
| #else |
| return CheckForDYLDInMemory(0x8fe00000); |
| #endif |
| } |
| |
| bool |
| MachDYLD::CheckForDYLDInMemory(nub_addr_t addr) |
| { |
| std::vector<uint8_t> dyld_header; |
| nub_size_t page_size = 0x1000; |
| dyld_header.resize(page_size); |
| nub_size_t bytes_read = DNBProcessMemoryRead(m_pid, addr, dyld_header.size(), &dyld_header[0]); |
| if (bytes_read > 0) |
| { |
| DNBDataRef::offset_t offset = 0; |
| DNBDataRef data(&dyld_header[0], bytes_read, false); |
| struct mach_header *header = (struct mach_header*)data.GetData(&offset, sizeof(struct mach_header)); |
| if (header) |
| { |
| switch (header->magic) |
| { |
| case MH_MAGIC: |
| case MH_CIGAM: |
| data.SetPointerSize(4); |
| m_addr_size = 4; |
| break; |
| |
| case MH_MAGIC_64: |
| case MH_CIGAM_64: |
| data.SetPointerSize(8); |
| m_addr_size = 8; |
| break; |
| |
| default: |
| return false; |
| } |
| |
| if (header->filetype == MH_DYLINKER) |
| { |
| // printf( "Found DYLD mach image at %8.8p", addr); |
| |
| m_dyld_all_image_infos_addr = DNBProcessLookupAddress(m_pid, "dyld_all_image_infos", "/usr/lib/dyld"); |
| |
| #if defined (__arm__) |
| m_dyld_all_image_infos_addr = 0x2fe3a004; |
| #endif |
| |
| if (m_dyld_all_image_infos_addr != INVALID_NUB_ADDRESS) |
| { |
| // printf( "Found DYLD data symbol 'dyld_all_image_infos' is %8.8p", m_dyld_all_image_infos_addr); |
| |
| if (ReadDYLIBInfo()) |
| { |
| if (m_dylib_info_header.notification != INVALID_NUB_ADDRESS) |
| { |
| m_notify_break_id = DNBBreakpointSet(m_pid, m_dylib_info_header.notification, 4, true); |
| if (NUB_BREAK_ID_IS_VALID(m_notify_break_id)) |
| { |
| DNBBreakpointSetCallback(m_pid, m_notify_break_id, MachDYLD::BreakpointHit, this); |
| m_dyld_addr = addr; |
| } |
| } |
| } |
| // if (DNBLogCheckLogBit(LOG_SHLIB)) |
| // Dump(DNBLogGetLogFile()); |
| } |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| nub_bool_t |
| MachDYLD::BreakpointHit(nub_process_t pid, nub_thread_t tid, nub_break_t breakID, void *baton) |
| { |
| MachDYLD *dyld = (MachDYLD*) baton; |
| //printf("MachDYLD::BreakpointHit called"); |
| dyld->ReadDYLIBInfo(); |
| DNBProcessSharedLibrariesUpdated(pid); |
| return false; // Don't stop the process, let it continue |
| } |
| |
| bool |
| MachDYLD::ReadDYLIBInfo() |
| { |
| nub_addr_t addr = m_dyld_all_image_infos_addr; |
| if (addr != INVALID_NUB_ADDRESS) |
| { |
| PThreadMutex::Locker locker(m_dyld_info_mutex); |
| //printf("MachDYLD::ReadDYLIBInfo(addr =%8.8p)", addr); |
| bool swap = false; |
| uint32_t i = 0; |
| DYLIBInfo::collection previous_dylibs; |
| previous_dylibs.swap(m_current_dylibs); |
| uint8_t all_dylib_info_data[32]; |
| nub_size_t count = 8 + m_addr_size * 2; |
| nub_size_t bytes_read = DNBProcessMemoryRead(m_pid, addr, count, &all_dylib_info_data[0]); |
| if (bytes_read != count) |
| { |
| m_dylib_info_header.Clear(); |
| return false; |
| } |
| |
| DNBDataRef data(all_dylib_info_data, sizeof(all_dylib_info_data), swap); |
| data.SetPointerSize(m_addr_size); |
| DNBDataRef::offset_t offset = 0; |
| m_dylib_info_header.version = data.Get32(&offset); |
| m_dylib_info_header.dylib_info_count = data.Get32(&offset); |
| m_dylib_info_header.dylib_info_addr = data.GetPointer(&offset); |
| m_dylib_info_header.notification = data.GetPointer(&offset); |
| //printf( "%s: version=%d, count=%d, addr=%8.8p, notify=%8.8p", |
| // __PRETTY_FUNCTION__, |
| // m_dylib_info_header.version, |
| // m_dylib_info_header.dylib_info_count, |
| // m_dylib_info_header.dylib_info_addr, |
| // m_dylib_info_header.notification); |
| |
| switch (m_dylib_info_header.version) |
| { |
| case 1: // 10.4.x and prior |
| { |
| } |
| break; |
| |
| case 2: // 10.5 and later |
| { |
| } |
| break; |
| |
| default: |
| //printf( "Invalid dyld all_dylib_infos version number: %d", m_dylib_info_header.version); |
| return false; |
| break; |
| } |
| |
| // If we made it here, we are assuming that the all dylib info data should |
| // be valid, lets read the info array. |
| if (m_dylib_info_header.dylib_info_count > 0) |
| { |
| if (m_dylib_info_header.dylib_info_addr == 0) |
| { |
| //printf( "dyld is currently updating all_dylib_infos."); |
| } |
| else |
| { |
| m_current_dylibs.resize(m_dylib_info_header.dylib_info_count); |
| count = m_current_dylibs.size() * 3 * m_addr_size; |
| std::vector<uint8_t> info_data(count, 0); |
| bytes_read = DNBProcessMemoryRead(m_pid, m_dylib_info_header.dylib_info_addr, count, &info_data[0]); |
| if (bytes_read == count) |
| { |
| DNBDataRef::offset_t info_data_offset = 0; |
| DNBDataRef info_data_ref(&info_data[0], info_data.size(), swap); |
| info_data_ref.SetPointerSize(m_addr_size); |
| for (i = 0; info_data_ref.ValidOffset(info_data_offset); i++) |
| { |
| assert (i < m_current_dylibs.size()); |
| m_current_dylibs[i].address = info_data_ref.GetPointer(&info_data_offset); |
| nub_addr_t path_addr = info_data_ref.GetPointer(&info_data_offset); |
| m_current_dylibs[i].mod_date = info_data_ref.GetPointer(&info_data_offset); |
| |
| char raw_path[PATH_MAX]; |
| char resolved_path[PATH_MAX]; |
| bytes_read = DNBProcessMemoryRead(m_pid, path_addr, sizeof(raw_path), (char*)&raw_path[0]); |
| if (::realpath(raw_path, resolved_path)) |
| m_current_dylibs[i].path = resolved_path; |
| else |
| m_current_dylibs[i].path = raw_path; |
| } |
| assert(i == m_dylib_info_header.dylib_info_count); |
| |
| UpdateUUIDs(); |
| } |
| else |
| { |
| //printf( "unable to read all data for all_dylib_infos."); |
| m_current_dylibs.clear(); |
| return false; |
| } |
| } |
| } |
| // Read any UUID values that we can get |
| if (m_current_dylibs.empty()) |
| { |
| m_changed_dylibs = previous_dylibs; |
| const size_t num_changed_dylibs = m_changed_dylibs.size(); |
| for (i = 0; i < num_changed_dylibs; i++) |
| { |
| // Indicate the shared library was unloaded by giving it an invalid |
| // address... |
| m_changed_dylibs[i].address = INVALID_NUB_ADDRESS; |
| } |
| } |
| else |
| { |
| m_changed_dylibs.clear(); |
| |
| // Most of the time when we get shared library changes, they just |
| // get appended to the end of the list, so find out the min number |
| // of entries in the current and previous list that match and see |
| // how many are equal. |
| uint32_t curr_dylib_count = m_current_dylibs.size(); |
| uint32_t prev_dylib_count = previous_dylibs.size(); |
| uint32_t common_count = std::min<uint32_t>(prev_dylib_count, curr_dylib_count); |
| MachDYLD::DYLIBInfo::const_iterator curr_pos = m_current_dylibs.begin(); |
| MachDYLD::DYLIBInfo::const_iterator curr_end = m_current_dylibs.end(); |
| MachDYLD::DYLIBInfo::iterator prev_pos = previous_dylibs.begin(); |
| uint32_t idx; |
| for (idx = 0; idx < common_count; idx++) |
| { |
| if (*curr_pos == *prev_pos) |
| { |
| ++curr_pos; |
| ++prev_pos; |
| } |
| else |
| break; |
| } |
| |
| // Remove all the entries that were at the exact same index and that |
| // matched between the previous_dylibs and m_current_dylibs arrays. This will cover |
| // most of the cases as when shared libraries get loaded they get |
| // appended to the end of the list. |
| if (prev_pos != previous_dylibs.begin()) |
| { |
| previous_dylibs.erase(previous_dylibs.begin(), prev_pos); |
| } |
| |
| if (previous_dylibs.empty()) |
| { |
| // We only have new libraries to add, they are the only ones that |
| // have changed. |
| if (curr_pos != curr_end) |
| { |
| m_changed_dylibs.assign(curr_pos, curr_end); |
| } |
| } |
| else |
| { |
| // We still have items in our previous dylib list which means either |
| // one or more shared libraries got unloaded somewhere in the middle |
| // of the list, so we will manually search for each remaining item |
| // in our current list in the previous list |
| for (; curr_pos != curr_end; ++curr_pos) |
| { |
| MachDYLD::DYLIBInfo::iterator pos = std::find(previous_dylibs.begin(), previous_dylibs.end(), *curr_pos); |
| if (pos == previous_dylibs.end()) |
| { |
| // This dylib wasn't here before, add it to our change list |
| m_changed_dylibs.push_back(*curr_pos); |
| } |
| else |
| { |
| // This dylib was in our previous dylib list, it didn't |
| // change, so lets remove it from the previous list so we |
| // don't see it again. |
| previous_dylibs.erase(pos); |
| } |
| } |
| |
| // The only items left if our previous_dylibs array will be shared |
| // libraries that got unloaded (still in previous list, and not |
| // mentioned in the current list). |
| if (!previous_dylibs.empty()) |
| { |
| const size_t num_previous_dylibs = previous_dylibs.size(); |
| for (i = 0; i < num_previous_dylibs; i++) |
| { |
| // Indicate the shared library was unloaded by giving it |
| // an invalid address... |
| previous_dylibs[i].address = INVALID_NUB_ADDRESS; |
| } |
| // Add all remaining previous_dylibs to the changed list with |
| // invalidated addresses so we know they got unloaded. |
| m_changed_dylibs.insert(m_changed_dylibs.end(), previous_dylibs.begin(), previous_dylibs.end()); |
| } |
| } |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| |
| void |
| MachDYLD::UpdateUUIDs() |
| { |
| bool swap = false; |
| nub_size_t page_size = 0x1000; |
| uint32_t i; |
| // Read any UUID values that we can get |
| for (i = 0; i < m_dylib_info_header.dylib_info_count; i++) |
| { |
| if (!m_current_dylibs[i].UUIDValid()) |
| { |
| std::vector<uint8_t> bytes(page_size, 0); |
| nub_size_t bytes_read = DNBProcessMemoryRead(m_pid, m_current_dylibs[i].address, page_size, &bytes[0]); |
| if (bytes_read > 0) |
| { |
| DNBDataRef::offset_t offset = 0; |
| DNBDataRef data(&bytes[0], bytes_read, swap); |
| struct mach_header *header = (struct mach_header*)data.GetData(&offset, sizeof(struct mach_header)); |
| if (header) |
| { |
| switch (header->magic) |
| { |
| case MH_MAGIC: |
| case MH_CIGAM: |
| data.SetPointerSize(4); |
| m_addr_size = 4; |
| break; |
| |
| case MH_MAGIC_64: |
| case MH_CIGAM_64: |
| data.SetPointerSize(8); |
| m_addr_size = 8; |
| offset += 4; // Skip the extra reserved field in the 64 bit mach header |
| break; |
| |
| default: |
| continue; |
| } |
| |
| if (header->sizeofcmds > bytes_read) |
| { |
| bytes.resize(header->sizeofcmds); |
| nub_addr_t addr = m_current_dylibs[i].address + bytes_read; |
| bytes_read += DNBProcessMemoryRead(m_pid, addr , header->sizeofcmds - bytes_read, &bytes[bytes_read]); |
| } |
| assert(bytes_read >= header->sizeofcmds); |
| uint32_t cmd_idx; |
| DNBSegment segment; |
| |
| for (cmd_idx = 0; cmd_idx < header->ncmds; cmd_idx++) |
| { |
| if (data.ValidOffsetForDataOfSize(offset, sizeof(struct load_command))) |
| { |
| struct load_command load_cmd; |
| DNBDataRef::offset_t load_cmd_offset = offset; |
| load_cmd.cmd = data.Get32(&offset); |
| load_cmd.cmdsize = data.Get32(&offset); |
| switch (load_cmd.cmd) |
| { |
| case LC_SEGMENT: |
| { |
| strncpy(segment.name, data.GetCStr(&offset, 16), 16); |
| memset(&segment.name[16], 0, DNB_MAX_SEGMENT_NAME_LENGTH - 16); |
| segment.addr = data.Get32(&offset); |
| segment.size = data.Get32(&offset); |
| m_current_dylibs[i].segments.push_back(segment); |
| } |
| break; |
| |
| case LC_SEGMENT_64: |
| { |
| strncpy(segment.name, data.GetCStr(&offset, 16), 16); |
| memset(&segment.name[16], 0, DNB_MAX_SEGMENT_NAME_LENGTH - 16); |
| segment.addr = data.Get64(&offset); |
| segment.size = data.Get64(&offset); |
| m_current_dylibs[i].segments.push_back(segment); |
| } |
| break; |
| |
| case LC_UUID: |
| // We found our UUID, we can stop now... |
| memcpy(m_current_dylibs[i].uuid, data.GetData(&offset, 16), 16); |
| // if (DNBLogCheckLogBit(LOG_SHLIB)) |
| // { |
| // DNBLogThreaded("UUID found for aii[%d]:", i); |
| // m_current_dylibs[i].Dump(DNBLogGetLogFile()); |
| // } |
| break; |
| |
| default: |
| break; |
| } |
| // Set offset to be the beginning of the next load command. |
| offset = load_cmd_offset + load_cmd.cmdsize; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| nub_addr_t |
| MachDYLD::GetSharedLibraryHeaderAddress(const char *shlib_path) const |
| { |
| if (!m_current_dylibs.empty() && shlib_path && shlib_path[0]) |
| { |
| uint32_t i; |
| for (i = 0; i<m_current_dylibs.size(); i++) |
| { |
| if (m_current_dylibs[i].path == shlib_path) |
| return m_current_dylibs[i].address; |
| } |
| } |
| return INVALID_NUB_ADDRESS; |
| } |
| |
| |
| nub_size_t |
| MachDYLD::CopySharedLibraryInfo(DYLIBInfo::collection& dylib_coll, DNBExecutableImageInfo **image_infos) |
| { |
| if (!dylib_coll.empty()) |
| { |
| size_t i; |
| size_t total_num_segments = 0; |
| size_t segment_index = 0; |
| for (i = 0; i<dylib_coll.size(); i++) |
| { |
| total_num_segments += dylib_coll[i].segments.size(); |
| } |
| size_t image_infos_byte_size = sizeof(DNBExecutableImageInfo) * dylib_coll.size(); |
| size_t all_segments_byte_size = sizeof(DNBSegment) * total_num_segments; |
| size_t total_byte_size = image_infos_byte_size + all_segments_byte_size; |
| |
| // Allocate enough space to fit all of the shared library information in |
| // a single buffer so consumers can free a single chunk of data when done |
| uint8_t *buf = (uint8_t*)malloc (total_byte_size); |
| |
| DNBExecutableImageInfo *info = (DNBExecutableImageInfo*)buf; |
| DNBSegment *all_segments = (DNBSegment*)(buf + image_infos_byte_size); |
| if (info) |
| { |
| for (i = 0; i<dylib_coll.size(); i++) |
| { |
| strncpy(info[i].name, dylib_coll[i].path.c_str(), PATH_MAX); |
| // NULL terminate paths that are too long (redundant for path |
| // that fit, but harmless |
| info[i].name[PATH_MAX-1] = '\0'; |
| info[i].header_addr = dylib_coll[i].address; |
| info[i].state = (dylib_coll[i].address == INVALID_NUB_ADDRESS ? eShlibStateUnloaded : eShlibStateLoaded); |
| memcpy(info[i].uuid, dylib_coll[i].uuid, sizeof(uuid_t)); |
| info[i].num_segments = dylib_coll[i].segments.size(); |
| if (info[i].num_segments == 0) |
| { |
| info[i].segments = NULL; |
| } |
| else |
| { |
| info[i].segments = &all_segments[segment_index]; |
| memcpy(info[i].segments, &(dylib_coll[i].segments[0]), sizeof(DNBSegment) * info[i].num_segments); |
| segment_index += info[i].num_segments; |
| } |
| |
| } |
| // Release ownership of the shared library array to the caller |
| *image_infos = info; |
| return dylib_coll.size(); |
| } |
| } |
| *image_infos = NULL; |
| return 0; |
| } |
| |
| |
| |
| nub_size_t |
| MachDYLD::CopySharedInfoCallback(nub_process_t pid, struct DNBExecutableImageInfo **image_infos, nub_bool_t only_changed, void *baton) |
| { |
| MachDYLD *dyld = (MachDYLD*) baton; |
| |
| if (only_changed) |
| return dyld->CopyChangedShlibInfo(image_infos); |
| else |
| return dyld->CopyCurrentShlibInfo(image_infos); |
| |
| *image_infos = NULL; |
| return 0; |
| } |
| |
| nub_size_t |
| MachDYLD::CopyCurrentShlibInfo(DNBExecutableImageInfo **image_infos) |
| { |
| PThreadMutex::Locker locker(m_dyld_info_mutex); |
| return CopySharedLibraryInfo(m_current_dylibs, image_infos); |
| } |
| |
| |
| nub_size_t |
| MachDYLD::CopyChangedShlibInfo(DNBExecutableImageInfo **image_infos) |
| { |
| PThreadMutex::Locker locker(m_dyld_info_mutex); |
| return CopySharedLibraryInfo(m_changed_dylibs, image_infos); |
| } |
| |
| |
| |
| void |
| MachDYLD::DYLIBInfo::Dump(FILE *f) const |
| { |
| if (f == NULL) |
| return; |
| if (address == INVALID_NUB_ADDRESS) |
| { |
| if (UUIDValid()) |
| { |
| fprintf(f, "UNLOADED %8.8llx %2.2X%2.2X%2.2X%2.2X-%2.2X%2.2X-%2.2X%2.2X-%2.2X%2.2X-%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X %s", |
| (uint64_t)mod_date, |
| uuid[ 0], uuid[ 1], uuid[ 2], uuid[ 3], |
| uuid[ 4], uuid[ 5], uuid[ 6], uuid[ 7], |
| uuid[ 8], uuid[ 9], uuid[10], uuid[11], |
| uuid[12], uuid[13], uuid[14], uuid[15], |
| path.c_str()); |
| } |
| else |
| { |
| fprintf(f, "UNLOADED %8.8llx %s", (uint64_t)mod_date, path.c_str()); |
| } |
| } |
| else |
| { |
| if (UUIDValid()) |
| { |
| fprintf(f, "%8.8llx %8.8llx %2.2X%2.2X%2.2X%2.2X-%2.2X%2.2X-%2.2X%2.2X-%2.2X%2.2X-%2.2X%2.2X%2.2X%2.2X%2.2X%2.2X %s", |
| (uint64_t)address, |
| (uint64_t)mod_date, |
| uuid[ 0], uuid[ 1], uuid[ 2], uuid[ 3], |
| uuid[ 4], uuid[ 5], uuid[ 6], uuid[ 7], |
| uuid[ 8], uuid[ 9], uuid[10], uuid[11], |
| uuid[12], uuid[13], uuid[14], uuid[15], |
| path.c_str()); |
| } |
| else |
| { |
| fprintf(f, "%8.8llx %8.8llx %s", (uint64_t)address, (uint64_t)mod_date, path.c_str()); |
| } |
| } |
| } |
| |
| void |
| MachDYLD::Dump(FILE *f) const |
| { |
| if (f == NULL) |
| return; |
| |
| PThreadMutex::Locker locker(m_dyld_info_mutex); |
| fprintf(f, "\n\tMachDYLD.m_dylib_info_header: version=%d, count=%d, addr=0x%llx, notify=0x%llx", |
| m_dylib_info_header.version, |
| m_dylib_info_header.dylib_info_count, |
| (uint64_t)m_dylib_info_header.dylib_info_addr, |
| (uint64_t)m_dylib_info_header.notification); |
| uint32_t i; |
| fprintf(f, "\n\tMachDYLD.m_current_dylibs"); |
| for (i = 0; i<m_current_dylibs.size(); i++) |
| m_current_dylibs[i].Dump(f); |
| fprintf(f, "\n\tMachDYLD.m_changed_dylibs"); |
| for (i = 0; i<m_changed_dylibs.size(); i++) |
| m_changed_dylibs[i].Dump(f); |
| } |
| |