-/**********************************************************************
- * Shared range support:
- *
- * Some libraries contain many pages worth of selector references.
- * In most processes, these libraries get loaded at the same addresses,
- * so the selectors are uniqued to the same values. To save memory,
- * the runtime tries to share these memory pages across processes.
- *
- * A file /tmp/objc_sharing_<arch>_<euid> records memory ranges and process
- * IDs. When a set of selector refs is to be uniqued, this file is checked
- * for a matching memory range being shared by another process. If
- * such a range is found:
- * 1. map the sharing process's memory somewhere into this address space
- * 2. read from the real selector refs and write into the mapped memory.
- * 3. vm_copy from the mapped memory to the real selector refs location
- * 4. deallocate the mapped memory
- *
- * The mapped memory is merely used as a guess. Correct execution is
- * guaranteed no matter what values the mapped memory actually contains.
- * If the mapped memory really matches the values needed in this process,
- * the mapped memory will be unchanged. If the mapped memory doesn't match,
- * or contains random values, it will be fixed up to the correct values.
- * The memory is shared whenever the guess happens to be correct.
- *
- * The file of shared ranges is imprecise. Processes may die leaving
- * their entries in the file. A PID may be recycled to some process that
- * does not use Objective-C. The sharing mechanism is robust in the face
- * of these failures. Bad shared memory is simply fixed up. No shared
- * memory means the selectors are fixed in place. If an entry in the
- * file is found to be unusable, the process that finds it will instead
- * offer to share its own memory, replacing the bad entry in the file.
- *
- * Individual entries in the file are written atomically, but the file is
- * otherwise unsynchronized. At worst, a sharing opportunity may be missed
- * because two new entries are written simultaneously in the same place.
- **********************************************************************/
-
-
-struct remote_range_t {
- vm_range_t range;
- pid_t pid;
-};
-
-
-// Cache for the last shared range file used, and its EUID.
-static pthread_mutex_t sharedRangeLock = PTHREAD_MUTEX_INITIALIZER;
-static uid_t sharedRangeEUID = 0;
-static FILE * sharedRangeFile = NULL;
-static BOOL sharedRangeFileInUse = NO;
-
-
-/**********************************************************************
-* open_shared_range_file
-* Open the shared range file "/tmp/objc_sharing_<arch>_<euid>" in
-* the given mode.
-* The returned file should be closed with close_shared_range_file().
-**********************************************************************/
-static FILE *open_shared_range_file(BOOL create)
-{
- const char arch[] =
-#if defined(__ppc__) || defined(ppc)
- "ppc";
-#elif defined(__ppc64__) || defined(ppc64)
- "ppc64";
-#elif defined(__i386__) || defined(i386)
- "i386";
-#else
-# error "unknown architecture"
-#endif
- char filename[18 + sizeof(arch) + 1 + 3*sizeof(uid_t) + 1];
- uid_t euid;
- FILE *file = NULL;
- int fd;
-
- // Never share when superuser
- euid = geteuid();
- if (euid == 0) {
- if (PrintSharing) {
- _objc_inform("SHARING: superuser never shares");
- }
- return NULL;
- }
-
- // Return cached file if it matches and it's not still being used
- pthread_mutex_lock(&sharedRangeLock);
- if (!sharedRangeFileInUse && euid == sharedRangeEUID) {
- file = sharedRangeFile;
- sharedRangeFileInUse = YES;
- pthread_mutex_unlock(&sharedRangeLock);
- rewind(file);
- return file;
- }
- pthread_mutex_unlock(&sharedRangeLock);
-
- // Open /tmp/objc_sharing_<euid>
- snprintf(filename,sizeof(filename), "/tmp/objc_sharing_%s_%u", arch, euid);
- fd = secure_open(filename, O_RDWR | (create ? O_CREAT : 0), euid);
- if (fd >= 0) {
- file = fdopen(fd, "r+");
- }
-
- if (file) {
- // Cache this file if there's no already-open file cached
- pthread_mutex_lock(&sharedRangeLock);
- if (!sharedRangeFileInUse) {
- sharedRangeFile = file;
- sharedRangeEUID = euid;
- sharedRangeFileInUse = YES;
- }
- pthread_mutex_unlock(&sharedRangeLock);
- }
- else {
- // open() or fdopen() failed
- if (PrintSharing) {
- _objc_inform("SHARING: bad or missing sharing file '%s': %s",
- filename, errno ? strerror(errno) :
- "potential security violation");
- }
- }
-
- return file;
-}
-
-
-/**********************************************************************
-* close_shared_range_file
-* Close a file opened with open_shared_range_file.
-* The file may actually be kept open and cached for a future
-* open_shared_range_file call. If so, clear_shared_range_file_cache()
-* can be used to really close the file.
-**********************************************************************/
-static void close_shared_range_file(FILE *file)
-{
- // Flush any writes in case the file is kept open.
- fflush(file);
-
- pthread_mutex_lock(&sharedRangeLock);
- if (file == sharedRangeFile && sharedRangeFileInUse) {
- // This file is the cached shared file.
- // Leave the file open and cached, but no longer in use.
- sharedRangeFileInUse = NO;
- } else {
- // This is not the cached file.
- fclose(file);
- }
- pthread_mutex_unlock(&sharedRangeLock);
-}
-
-
-/**********************************************************************
-* clear_shared_range_file_cache
-* Really close any file left open by close_shared_range_file.
-* This is called by map_images() after loading multiple images, each
-* of which may have used the shared range file.
-**********************************************************************/
-static void clear_shared_range_file_cache(void)
-{
- pthread_mutex_lock(&sharedRangeLock);
- if (sharedRangeFile && !sharedRangeFileInUse) {
- fclose(sharedRangeFile);
- sharedRangeFile = NULL;
- sharedRangeEUID = 0;
- sharedRangeFileInUse = 0;
- }
- pthread_mutex_unlock(&sharedRangeLock);
-}
-
-
-/**********************************************************************
-* get_shared_range
-* Try to find a shared range matching addresses [aligned_start..aligned_end).
-* If a range is found, it is mapped into this process and returned.
-* If no range is found, or the found range could not be mapped for
-* some reason, the range {0, 0} is returned.
-* aligned_start and aligned_end must be page-aligned.
-**********************************************************************/
-static vm_range_t get_shared_range(vm_address_t aligned_start,
- vm_address_t aligned_end)
-{
- struct remote_range_t remote;
- vm_range_t result;
- FILE *file;
-
- result.address = 0;
- result.size = 0;
-
- // Open shared range file, but don't create it
- file = open_shared_range_file(NO);
- if (!file) return result;
-
- // Search for the desired memory range
- while (1 == fread(&remote, sizeof(remote), 1, file)) {
- if (remote.pid != 0 &&
- remote.range.address == aligned_start &&
- remote.range.size == aligned_end - aligned_start)
- {
- // Found a match in the file - try to grab the memory
- mach_port_name_t remote_task;
- vm_prot_t cur_prot, max_prot;
- vm_address_t local_addr;
- kern_return_t kr;
-
- // Find the task offering the memory
- kr = task_for_pid(mach_task_self(), remote.pid, &remote_task);
- if (kr != KERN_SUCCESS) {
- // task is dead
- if (PrintSharing) {
- _objc_inform("SHARING: no task for pid %d: %s",
- remote.pid, mach_error_string(kr));
- }
- break;
- }
-
- // Map the memory into our process
- local_addr = 0;
- kr = vm_remap(mach_task_self(), &local_addr, remote.range.size,
- 0 /*alignment*/, 1 /*anywhere*/,
- remote_task, remote.range.address,
- 1 /*copy*/, &cur_prot, &max_prot, VM_INHERIT_NONE);
- mach_port_deallocate(mach_task_self(), remote_task);
-
- if (kr != KERN_SUCCESS) {
- // couldn't map memory
- if (PrintSharing) {
- _objc_inform("SHARING: vm_remap from pid %d failed: %s",
- remote.pid, mach_error_string(kr));
- }
- break;
- }
-
- if (!(cur_prot & VM_PROT_READ) || !(cur_prot & VM_PROT_WRITE)) {
- // Received memory is not mapped read/write - don't use it
- // fixme try to change permissions? check max_prot?
- if (PrintSharing) {
- _objc_inform("SHARING: memory from pid %d not read/write",
- remote.pid);
- }
- vm_deallocate(mach_task_self(), local_addr, remote.range.size);
- break;
- }
-
- // Success
- result.address = local_addr;
- result.size = remote.range.size;
- }
- }
-
- close_shared_range_file(file);
- return result;
-}
-
-
-/**********************************************************************
-* offer_shared_range
-* Offer memory range [aligned_start..aligned_end) in this process
-* to other Objective-C-using processes.
-* If some other entry in the shared range list matches this range,
-* is is overwritten with this process's PID. (Thus any stale PIDs are
-* replaced.)
-* If the shared range file could not be updated for any reason, this
-* function fails silently.
-* aligned_start and aligned_end must be page-aligned.
-**********************************************************************/
-static void offer_shared_range(vm_address_t aligned_start,
- vm_address_t aligned_end)
-{
- struct remote_range_t remote;
- struct remote_range_t local;
- BOOL found = NO;
- FILE *file;
- int err = 0;
-
- local.range.address = aligned_start;
- local.range.size = aligned_end - aligned_start;
- local.pid = getpid();
-
- // Open shared range file, creating if necessary
- file = open_shared_range_file(YES);
- if (!file) return;
-
- // Find an existing entry for this range, if any
- while (1 == fread(&remote, sizeof(remote), 1, file)) {
- if (remote.pid != 0 &&
- remote.range.address == aligned_start &&
- remote.range.size == aligned_end - aligned_start)
- {
- // Found a match - overwrite it
- err = fseek(file, -sizeof(remote), SEEK_CUR);
- found = YES;
- break;
- }
- }
-
- if (!found) {
- // No existing entry - write at the end of the file
- err = fseek(file, 0, SEEK_END);
- }
-
- if (err == 0) {
- fwrite(&local, sizeof(local), 1, file);
- }
-
- close_shared_range_file(file);
-}
-
-
-/**********************************************************************
-* install_shared_range
-* Install a shared range received from get_shared_range() into
-* its final resting place.
-* If possible, the memory is copied using virtual memory magic rather
-* than actual data writes. dst always gets updated values, even if
-* virtual memory magic is not possible.
-* The shared range is always deallocated.
-* src and dst must be page-aligned.
-**********************************************************************/
-static void install_shared_range(vm_range_t src, vm_address_t dst)
-{
- kern_return_t kr;
-
- // Copy from src to dst
- kr = vm_copy(mach_task_self(), src.address, src.size, dst);
- if (kr != KERN_SUCCESS) {
- // VM copy failed. Use non-VM copy.
- if (PrintSharing) {
- _objc_inform("SHARING: vm_copy failed: %s", mach_error_string(kr));
- }
- memmove((void *)dst, (void *)src.address, src.size);
- }
-
- // Unmap the shared range at src
- vm_deallocate(mach_task_self(), src.address, src.size);
-}