#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)
{
Clear();
m_pid = pid;
}
void
MachDYLD::ProcessStateChanged(nub_state_t state)
{
switch (state)
{
case eStateInvalid:
case eStateUnloaded:
case eStateExited:
case eStateDetached:
case eStateAttaching:
case eStateLaunching:
Clear();
break;
case eStateStopped:
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)
{
}
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)
{
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)
{
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;
}
}
}
}
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;
dyld->ReadDYLIBInfo();
DNBProcessSharedLibrariesUpdated(pid);
return false; }
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);
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);
switch (m_dylib_info_header.version)
{
case 1: {
}
break;
case 2: {
}
break;
default:
return false;
break;
}
if (m_dylib_info_header.dylib_info_count > 0)
{
if (m_dylib_info_header.dylib_info_addr == 0)
{
}
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
{
m_current_dylibs.clear();
return false;
}
}
}
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++)
{
m_changed_dylibs[i].address = INVALID_NUB_ADDRESS;
}
}
else
{
m_changed_dylibs.clear();
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;
}
if (prev_pos != previous_dylibs.begin())
{
previous_dylibs.erase(previous_dylibs.begin(), prev_pos);
}
if (previous_dylibs.empty())
{
if (curr_pos != curr_end)
{
m_changed_dylibs.assign(curr_pos, curr_end);
}
}
else
{
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())
{
m_changed_dylibs.push_back(*curr_pos);
}
else
{
previous_dylibs.erase(pos);
}
}
if (!previous_dylibs.empty())
{
const size_t num_previous_dylibs = previous_dylibs.size();
for (i = 0; i < num_previous_dylibs; i++)
{
previous_dylibs[i].address = INVALID_NUB_ADDRESS;
}
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;
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; 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:
memcpy(m_current_dylibs[i].uuid, data.GetData(&offset, 16), 16);
break;
default:
break;
}
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;
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);
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;
}
}
*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);
}