dyld_process_info_notify.cpp [plain text]
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdio.h>
#include <mach/shared_region.h>
#include <mach/mach_vm.h>
#include <libkern/OSAtomic.h>
#include "dyld_process_info.h"
#include "dyld_process_info_internal.h"
#include "dyld_images.h"
#include "dyld_priv.h"
#include "LaunchCache.h"
#include "Loading.h"
#include "AllImages.h"
typedef void (^Notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path);
typedef void (^NotifyExit)();
typedef void (^NotifyMain)();
struct __attribute__((visibility("hidden"))) dyld_process_info_notify_base
{
static dyld_process_info_notify_base* make(task_t task, dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, kern_return_t* kr);
~dyld_process_info_notify_base();
uint32_t& retainCount() const { return _retainCount; }
void setNotifyMain(NotifyMain notifyMain) const { _notifyMain = notifyMain; }
static void* operator new(size_t sz) { return malloc(sz); }
static void operator delete(void* p) { return free(p); }
private:
dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, task_t task);
kern_return_t makePorts();
kern_return_t pokeSendPortIntoTarget();
kern_return_t unpokeSendPortInTarget();
void setMachSourceOnQueue();
mutable uint32_t _retainCount;
dispatch_queue_t _queue;
Notify _notify;
NotifyExit _notifyExit;
mutable NotifyMain _notifyMain;
task_t _targetTask;
dispatch_source_t _machSource;
uint64_t _portAddressInTarget;
mach_port_t _sendPortInTarget; mach_port_t _receivePortInMonitor; };
dyld_process_info_notify_base::dyld_process_info_notify_base(dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, task_t task)
: _retainCount(1), _queue(queue), _notify(notify), _notifyExit(notifyExit), _notifyMain(NULL), _targetTask(task), _machSource(NULL), _portAddressInTarget(0), _sendPortInTarget(0), _receivePortInMonitor(0)
{
dispatch_retain(_queue);
}
dyld_process_info_notify_base::~dyld_process_info_notify_base()
{
if ( _machSource ) {
dispatch_release(_machSource);
_machSource = NULL;
}
if ( _portAddressInTarget ) {
unpokeSendPortInTarget();
_portAddressInTarget = 0;
}
if ( _sendPortInTarget ) {
_sendPortInTarget = 0;
}
dispatch_release(_queue);
if ( _receivePortInMonitor != 0 ) {
mach_port_deallocate(mach_task_self(), _receivePortInMonitor);
_receivePortInMonitor = 0;
}
}
dyld_process_info_notify_base* dyld_process_info_notify_base::make(task_t task, dispatch_queue_t queue, Notify notify, NotifyExit notifyExit, kern_return_t* kr)
{
dyld_process_info_notify_base* obj = new dyld_process_info_notify_base(queue, notify, notifyExit, task);
if ( kern_return_t r = obj->makePorts() ) {
if ( kr != NULL )
*kr = r;
goto fail;
}
obj->setMachSourceOnQueue();
if ( kern_return_t r = obj->pokeSendPortIntoTarget() ) {
if ( kr != NULL )
*kr = r;
goto fail;
}
if ( kr != NULL )
*kr = KERN_SUCCESS;
return obj;
fail:
delete obj;
return NULL;
}
kern_return_t dyld_process_info_notify_base::makePorts()
{
if ( kern_return_t r = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &_receivePortInMonitor) )
return r;
if ( kern_return_t r = mach_port_insert_right(mach_task_self(), _receivePortInMonitor, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND) )
return r;
if ( kern_return_t r = mach_port_allocate(_targetTask, MACH_PORT_RIGHT_DEAD_NAME, &_sendPortInTarget) )
return r;
if ( kern_return_t r = mach_port_mod_refs(_targetTask, _sendPortInTarget, MACH_PORT_RIGHT_DEAD_NAME, -1) )
return r;
if ( kern_return_t r = mach_port_insert_right(_targetTask, _sendPortInTarget, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND) )
return r;
mach_port_t previous = MACH_PORT_NULL;
if ( kern_return_t r = mach_port_request_notification(_targetTask, _sendPortInTarget, MACH_NOTIFY_DEAD_NAME, 0, _receivePortInMonitor, MACH_MSG_TYPE_MAKE_SEND_ONCE, &previous))
return r;
return KERN_SUCCESS;
}
void dyld_process_info_notify_base::setMachSourceOnQueue()
{
NotifyExit exitHandler = _notifyExit;
_machSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_MACH_RECV, _receivePortInMonitor, 0, _queue);
dispatch_source_set_event_handler(_machSource, ^{
uint8_t messageBuffer[DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE];
mach_msg_header_t* h = (mach_msg_header_t*)messageBuffer;
kern_return_t r = mach_msg(h, MACH_RCV_MSG, 0, sizeof(messageBuffer), _receivePortInMonitor, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
if ( r == KERN_SUCCESS ) {
if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_LOAD_ID || h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID ) {
const dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)messageBuffer;
const dyld_process_info_image_entry* entries = (dyld_process_info_image_entry*)&messageBuffer[header->imagesOffset];
const char* const stringPool = (char*)&messageBuffer[header->stringsOffset];
for (unsigned i=0; i < header->imageCount; ++i) {
bool isUnload = (h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID);
_notify(isUnload, header->timestamp, entries[i].loadAddress, entries[i].uuid, stringPool + entries[i].pathStringOffset);
}
mach_msg_header_t replyHeader;
replyHeader.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND);
replyHeader.msgh_id = 0;
replyHeader.msgh_local_port = MACH_PORT_NULL;
replyHeader.msgh_remote_port = h->msgh_remote_port;
replyHeader.msgh_reserved = 0;
replyHeader.msgh_size = sizeof(replyHeader);
mach_msg(&replyHeader, MACH_SEND_MSG | MACH_SEND_TIMEOUT, replyHeader.msgh_size, 0, MACH_PORT_NULL, 100, MACH_PORT_NULL);
}
else if ( h->msgh_id == DYLD_PROCESS_INFO_NOTIFY_MAIN_ID ) {
if ( _notifyMain != NULL ) {
_notifyMain();
}
mach_msg_header_t replyHeader;
replyHeader.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, MACH_MSG_TYPE_MAKE_SEND);
replyHeader.msgh_id = 0;
replyHeader.msgh_local_port = MACH_PORT_NULL;
replyHeader.msgh_remote_port = h->msgh_remote_port;
replyHeader.msgh_reserved = 0;
replyHeader.msgh_size = sizeof(replyHeader);
mach_msg(&replyHeader, MACH_SEND_MSG | MACH_SEND_TIMEOUT, replyHeader.msgh_size, 0, MACH_PORT_NULL, 100, MACH_PORT_NULL);
}
else if ( h->msgh_id == MACH_NOTIFY_PORT_DELETED ) {
mach_port_t deadPort = ((mach_port_deleted_notification_t *)h)->not_port;
if ( deadPort == _sendPortInTarget ) {
_sendPortInTarget = 0;
mach_port_deallocate(mach_task_self(), _receivePortInMonitor);
_receivePortInMonitor = 0;
_portAddressInTarget = 0;
exitHandler();
}
}
else {
fprintf(stderr, "received unknown message id=0x%X, size=%d\n", h->msgh_id, h->msgh_size);
}
}
});
dispatch_resume(_machSource);
}
kern_return_t dyld_process_info_notify_base::pokeSendPortIntoTarget()
{
task_dyld_info_data_t taskDyldInfo;
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
kern_return_t r = task_info(_targetTask, TASK_DYLD_INFO, (task_info_t)&taskDyldInfo, &count);
if ( r )
return r;
mach_vm_address_t mappedAddress = 0;
mach_vm_size_t mappedSize = taskDyldInfo.all_image_info_size;
vm_prot_t curProt = VM_PROT_NONE;
vm_prot_t maxProt = VM_PROT_NONE;
r = mach_vm_remap(mach_task_self(), &mappedAddress, mappedSize, 0, VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR,
_targetTask, taskDyldInfo.all_image_info_addr, false, &curProt, &maxProt, VM_INHERIT_NONE);
if ( r )
return r;
if ( curProt != (VM_PROT_READ|VM_PROT_WRITE) )
return KERN_PROTECTION_FAILURE;
static_assert(sizeof(mach_port_t) == sizeof(uint32_t), "machport size not 32-bits");
mach_vm_address_t mappedAddressToPokePort = 0;
if ( taskDyldInfo.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32 )
mappedAddressToPokePort = mappedAddress + offsetof(dyld_all_image_infos_32,notifyMachPorts);
else
mappedAddressToPokePort = mappedAddress + offsetof(dyld_all_image_infos_64,notifyMachPorts);
bool slotFound = false;
for (int slotIndex=0; slotIndex < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slotIndex) {
if ( OSAtomicCompareAndSwap32Barrier(0, _sendPortInTarget, (volatile int32_t*)mappedAddressToPokePort) ) {
slotFound = true;
break;
}
mappedAddressToPokePort += sizeof(uint32_t);
}
if ( !slotFound ) {
mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize);
return KERN_UREFS_OVERFLOW;
}
_portAddressInTarget = taskDyldInfo.all_image_info_addr + mappedAddressToPokePort - mappedAddress;
mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize);
return r;
}
kern_return_t dyld_process_info_notify_base::unpokeSendPortInTarget()
{
mach_vm_address_t mappedAddress = 0;
mach_vm_size_t mappedSize = sizeof(mach_port_t);
vm_prot_t curProt = VM_PROT_NONE;
vm_prot_t maxProt = VM_PROT_NONE;
kern_return_t r = mach_vm_remap(mach_task_self(), &mappedAddress, mappedSize, 0, VM_FLAGS_ANYWHERE | VM_FLAGS_RETURN_DATA_ADDR,
_targetTask, _portAddressInTarget, false, &curProt, &maxProt, VM_INHERIT_NONE);
if ( r )
return r;
if ( curProt != (VM_PROT_READ|VM_PROT_WRITE) )
return KERN_PROTECTION_FAILURE;
OSAtomicCompareAndSwap32Barrier(_sendPortInTarget, 0, (volatile int32_t*)mappedAddress);
mach_vm_deallocate(mach_task_self(), mappedAddress, mappedSize);
return r;
}
dyld_process_info_notify _dyld_process_info_notify(task_t task, dispatch_queue_t queue,
void (^notify)(bool unload, uint64_t timestamp, uint64_t machHeader, const uuid_t uuid, const char* path),
void (^notifyExit)(),
kern_return_t* kr)
{
return dyld_process_info_notify_base::make(task, queue, notify, notifyExit, kr);
}
void _dyld_process_info_notify_main(dyld_process_info_notify object, void (^notifyMain)())
{
object->setNotifyMain(notifyMain);
}
void _dyld_process_info_notify_retain(dyld_process_info_notify object)
{
object->retainCount() += 1;
}
void _dyld_process_info_notify_release(dyld_process_info_notify object)
{
object->retainCount() -= 1;
if ( object->retainCount() == 0 )
delete object;
}
namespace dyld3 {
static mach_port_t sNotifyReplyPorts[DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT];
static bool sZombieNotifiers[DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT];
static void notifyMonitoringDyld(bool unloading, unsigned portSlot, const launch_cache::DynArray<loader::ImageInfo>& imageInfos)
{
if ( sZombieNotifiers[portSlot] )
return;
unsigned entriesSize = (unsigned)imageInfos.count()*sizeof(dyld_process_info_image_entry);
unsigned pathsSize = 0;
for (uintptr_t i=0; i < imageInfos.count(); ++i) {
launch_cache::Image image(imageInfos[i].imageData);
pathsSize += (strlen(image.path()) + 1);
}
unsigned totalSize = (sizeof(dyld_process_info_notify_header) + MAX_TRAILER_SIZE + entriesSize + pathsSize + 127) & -128; if ( totalSize > DYLD_PROCESS_INFO_NOTIFY_MAX_BUFFER_SIZE ) {
unsigned imageHalfCount = (unsigned)imageInfos.count()/2;
const launch_cache::DynArray<loader::ImageInfo> firstHalf(imageHalfCount, (loader::ImageInfo*)&imageInfos[0]);
const launch_cache::DynArray<loader::ImageInfo> secondHalf(imageInfos.count() - imageHalfCount, (loader::ImageInfo*)&imageInfos[imageHalfCount]);
notifyMonitoringDyld(unloading, portSlot, firstHalf);
notifyMonitoringDyld(unloading, portSlot, secondHalf);
return;
}
dyld_all_image_infos* allImageInfo = gAllImages.oldAllImageInfo();
uint8_t buffer[totalSize];
dyld_process_info_notify_header* header = (dyld_process_info_notify_header*)buffer;
header->version = 1;
header->imageCount = (uint32_t)imageInfos.count();
header->imagesOffset = sizeof(dyld_process_info_notify_header);
header->stringsOffset = sizeof(dyld_process_info_notify_header) + entriesSize;
header->timestamp = allImageInfo->infoArrayChangeTimestamp;
dyld_process_info_image_entry* entries = (dyld_process_info_image_entry*)&buffer[header->imagesOffset];
char* const pathPoolStart = (char*)&buffer[header->stringsOffset];
char* pathPool = pathPoolStart;
for (uintptr_t i=0; i < imageInfos.count(); ++i) {
launch_cache::Image image(imageInfos[i].imageData);
strcpy(pathPool, image.path());
uint32_t len = (uint32_t)strlen(pathPool);
memcpy(entries->uuid, image.uuid(), sizeof(uuid_t));
entries->loadAddress = (uint64_t)imageInfos[i].loadAddress;
entries->pathStringOffset = (uint32_t)(pathPool - pathPoolStart);
entries->pathLength = len;
pathPool += (len +1);
++entries;
}
if ( sNotifyReplyPorts[portSlot] == 0 ) {
if ( !mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sNotifyReplyPorts[portSlot]) )
mach_port_insert_right(mach_task_self(), sNotifyReplyPorts[portSlot], sNotifyReplyPorts[portSlot], MACH_MSG_TYPE_MAKE_SEND);
}
mach_msg_header_t* h = (mach_msg_header_t*)buffer;
h->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND); h->msgh_id = unloading ? DYLD_PROCESS_INFO_NOTIFY_UNLOAD_ID : DYLD_PROCESS_INFO_NOTIFY_LOAD_ID;
h->msgh_local_port = sNotifyReplyPorts[portSlot];
h->msgh_remote_port = allImageInfo->notifyPorts[portSlot];
h->msgh_reserved = 0;
h->msgh_size = (mach_msg_size_t)sizeof(buffer);
kern_return_t sendResult = mach_msg(h, MACH_SEND_MSG | MACH_RCV_MSG | MACH_RCV_TIMEOUT, h->msgh_size, h->msgh_size, sNotifyReplyPorts[portSlot], 2000, MACH_PORT_NULL);
if ( sendResult == MACH_SEND_INVALID_DEST ) {
mach_port_deallocate(mach_task_self(), allImageInfo->notifyPorts[portSlot]);
mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[portSlot]);
allImageInfo->notifyPorts[portSlot] = 0;
sNotifyReplyPorts[portSlot] = 0;
}
else if ( sendResult == MACH_RCV_TIMED_OUT ) {
sZombieNotifiers[portSlot] = true;
mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[portSlot]);
sNotifyReplyPorts[portSlot] = 0;
}
}
void AllImages::notifyMonitorMain()
{
dyld_all_image_infos* allImageInfo = gAllImages.oldAllImageInfo();
for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) {
if ( (allImageInfo->notifyPorts[slot] != 0 ) && !sZombieNotifiers[slot] ) {
if ( sNotifyReplyPorts[slot] == 0 ) {
if ( !mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &sNotifyReplyPorts[slot]) )
mach_port_insert_right(mach_task_self(), sNotifyReplyPorts[slot], sNotifyReplyPorts[slot], MACH_MSG_TYPE_MAKE_SEND);
}
uint8_t messageBuffer[sizeof(mach_msg_header_t) + MAX_TRAILER_SIZE];
mach_msg_header_t* h = (mach_msg_header_t*)messageBuffer;
h->msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND,MACH_MSG_TYPE_MAKE_SEND); h->msgh_id = DYLD_PROCESS_INFO_NOTIFY_MAIN_ID;
h->msgh_local_port = sNotifyReplyPorts[slot];
h->msgh_remote_port = allImageInfo->notifyPorts[slot];
h->msgh_reserved = 0;
h->msgh_size = (mach_msg_size_t)sizeof(messageBuffer);
kern_return_t sendResult = mach_msg(h, MACH_SEND_MSG | MACH_RCV_MSG | MACH_RCV_TIMEOUT, h->msgh_size, h->msgh_size, sNotifyReplyPorts[slot], 2000, MACH_PORT_NULL);
if ( sendResult == MACH_SEND_INVALID_DEST ) {
mach_port_deallocate(mach_task_self(), allImageInfo->notifyPorts[slot]);
mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]);
allImageInfo->notifyPorts[slot] = 0;
sNotifyReplyPorts[slot] = 0;
}
else if ( sendResult == MACH_RCV_TIMED_OUT ) {
sZombieNotifiers[slot] = true;
mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]);
sNotifyReplyPorts[slot] = 0;
}
}
}
}
void AllImages::notifyMonitorLoads(const launch_cache::DynArray<loader::ImageInfo>& newImages)
{
dyld_all_image_infos* allImageInfo = gAllImages.oldAllImageInfo();
for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) {
if ( allImageInfo->notifyPorts[slot] != 0 ) {
notifyMonitoringDyld(false, slot, newImages);
}
else if ( sNotifyReplyPorts[slot] != 0 ) {
mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]);
sNotifyReplyPorts[slot] = 0;
sZombieNotifiers[slot] = false;
}
}
}
void AllImages::notifyMonitorUnloads(const launch_cache::DynArray<loader::ImageInfo>& unloadingImages)
{
dyld_all_image_infos* allImageInfo = gAllImages.oldAllImageInfo();
for (int slot=0; slot < DYLD_MAX_PROCESS_INFO_NOTIFY_COUNT; ++slot) {
if ( allImageInfo->notifyPorts[slot] != 0 ) {
notifyMonitoringDyld(true, slot, unloadingImages);
}
else if ( sNotifyReplyPorts[slot] != 0 ) {
mach_port_deallocate(mach_task_self(), sNotifyReplyPorts[slot]);
sNotifyReplyPorts[slot] = 0;
sZombieNotifiers[slot] = false;
}
}
}
}