//===-- 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 #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 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 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(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 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; iCopyChangedShlibInfo(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