/*
* Copyright (c) 2006-2008 Apple Computer, Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#include <CoreSymbolication/CoreSymbolication.h>
#include <CoreSymbolication/CoreSymbolicationPrivate.h>
#import <mach/mach.h>
#include <mach/mach_error.h>
#include <servers/bootstrap.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#import <sys/sysctl.h>
// /System//Library/Frameworks/System.framework/Versions/B/PrivateHeaders/sys/proc_info.h
#include <System/sys/proc_info.h>
// This must be done *after* any references to Foundation.h!
#define uint_t __Solaris_uint_t
#import "libproc.h"
#import "libproc_apple.h"
#import <spawn.h>
#import <pthread.h>
#include <crt_externs.h>
extern int _dtrace_mangled;
/*
* This is a helper method, it does extended lookups following roughly these rules
*
* 1. An exact match (i.e. a full pathname): "/usr/lib/libc.so.1"
* 2. An exact basename match: "libc.so.1"
* 3. An initial basename match up to a '.' suffix: "libc.so" or "libc"
* 4. The literal string "a.out" is an alias for the executable mapping
*/
CSSymbolOwnerRef symbolOwnerForName(CSSymbolicatorRef symbolicator, const char* name) {
// Check for a.out specifically
if (strcmp(name, "a.out") == 0) {
__block CSSymbolOwnerRef owner = kCSNull;
if (CSSymbolicatorForeachSymbolOwnerWithFlagsAtTime(symbolicator, kCSSymbolOwnerIsAOut, kCSNow, ^(CSSymbolOwnerRef t) { owner = t; }) == 1) {
return owner;
}
return kCSNull;
}
// Try path based matching. Multile matches are legal, take the first.
__block CSSymbolOwnerRef owner = kCSNull;
if (CSSymbolicatorForeachSymbolOwnerWithPathAtTime(symbolicator, name, kCSNow, ^(CSSymbolOwnerRef t) { if (CSIsNull(owner)) owner = t; }) > 0)
return owner;
// Try name based matching. Multiple matches are legal, take the first.
if (CSSymbolicatorForeachSymbolOwnerWithNameAtTime(symbolicator, name, kCSNow, ^(CSSymbolOwnerRef t) { if (CSIsNull(owner)) owner = t; }) > 0)
return owner;
// Strip off extensions. We know there are no direct matches now.
size_t nameLength = strlen(name);
CSSymbolicatorForeachSymbolOwnerAtTime(symbolicator, kCSNow, ^(CSSymbolOwnerRef candidate) {
// We check CSIsNull to skip remaining work after finding a match.
if (CSIsNull(owner)) {
const char* candidateName = CSSymbolOwnerGetName(candidate);
size_t candidateNameLength = strlen(candidateName);
// We're going to cheat a bit.
//
// A match at this point will always be a prefix match. I.E. libSystem match against libSystem.B.dylib
// We make the following assertions
// 1) For a match to be possible, the candidate must always be longer than the search name
// 2) The match must always begin at the root of the candidate name
// 3) The next character in the candidate must be a '.'
if (nameLength < candidateNameLength) {
if (strstr(candidateName, name) == candidateName) {
if (candidateName[nameLength] == '.') {
// Its a match!
owner = candidate;
}
}
}
}
});
return owner;
}
#define APPLE_PCREATE_BAD_SYMBOLICATOR 0x0F000001
#define APPLE_PCREATE_BAD_ARCHITECTURE 0x0F000002
//
// Helper function so that Pcreate & Pgrab can use the same code.
//
// NOTE!
//
// We're doing something really hideous here.
//
// For each target process, there are *two* threads created. A dtrace control thread, and a CoreSymbolication
// dyld listener thread. The listener thread does not directly call into dtrace. The reason is that the thread
// calling into dtrace sometimes gets put to sleep. If dtrace decides to release/deallocate due to a notice
// from the listener thread, it deadlocks waiting for the listener to acknowledge that it has shut down.
static struct ps_prochandle* createProcAndSymbolicator(pid_t pid, task_t task, int* perr, bool should_queue_proc_activity_notices) {
// The symbolicator block captures proc, and actually uses it before completing.
// We allocate and initialize it first.
struct ps_prochandle* proc = calloc(sizeof(struct ps_prochandle), 1);
proc->current_symbol_owner_generation = 1; // MUST start with generation of 1 or higher.
proc->status.pr_pid = pid;
(void) pthread_mutex_init(&proc->proc_activity_queue_mutex, NULL);
(void) pthread_cond_init(&proc->proc_activity_queue_cond, NULL);
// Only enable this if we're going to generate events...
if (should_queue_proc_activity_notices)
proc->proc_activity_queue_enabled = true;
CSSymbolicatorRef symbolicator = CSSymbolicatorCreateWithTaskFlagsAndNotification(task, kCSSymbolicatorTrackDyldActivity, ^(uint32_t notification_type, CSNotificationData data) {
switch (notification_type) {
case kCSNotificationPing:
dt_dprintf("pid // We're faking a "POSTINIT" breakpoint here.
if (should_queue_proc_activity_notices)
Pcreate_async_proc_activity(proc, RD_POSTINIT);
break;
case kCSNotificationInitialized:
dt_dprintf("pid // We're faking a "PREINIT" breakpoint here.
if (should_queue_proc_activity_notices)
Pcreate_async_proc_activity(proc, RD_PREINIT);
break;
case kCSNotificationDyldLoad:
dt_dprintf("pid if (should_queue_proc_activity_notices)
Pcreate_sync_proc_activity(proc, RD_DLACTIVITY);
break;
case kCSNotificationDyldUnload:
dt_dprintf("pid break;
case kCSNotificationTimeout:
dt_dprintf("pid if (should_queue_proc_activity_notices)
Pcreate_async_proc_activity(proc, RD_DYLD_LOST);
break;
case kCSNotificationTaskExit:
dt_dprintf("pid if (should_queue_proc_activity_notices)
Pcreate_async_proc_activity(proc, RD_DYLD_EXIT);
break;
case kCSNotificationFini:
dt_dprintf("pid break;
default:
dt_dprintf("pid }
});
if (!CSIsNull(symbolicator)) {
proc->symbolicator = symbolicator; // Starts with a retain count of 1
proc->status.pr_dmodel = CSArchitectureIs64Bit(CSSymbolicatorGetArchitecture(symbolicator)) ? PR_MODEL_LP64 : PR_MODEL_ILP32;
} else {
free(proc);
proc = NULL;
*perr = APPLE_PCREATE_BAD_SYMBOLICATOR;
}
return proc;
}
struct ps_prochandle *
Pcreate(const char *file, /* executable file name */
char *const *argv, /* argument vector */
int *perr, /* pointer to error return code */
char *path, /* if non-null, holds exec path name on return */
size_t len, /* size of the path buffer */
cpu_type_t arch) /* architecture to launch */
{
struct ps_prochandle* proc = NULL;
int pid;
posix_spawnattr_t attr;
task_t task;
*perr = posix_spawnattr_init(&attr);
if (0 != *perr) goto destroy_attr;
if (arch != CPU_TYPE_ANY) {
*perr = posix_spawnattr_setbinpref_np(&attr, 1, &arch, NULL);
if (0 != *perr) goto destroy_attr;
}
*perr = posix_spawnattr_setflags(&attr, POSIX_SPAWN_START_SUSPENDED);
if (0 != *perr) goto destroy_attr;
setenv("DYLD_INSERT_LIBRARIES", "/usr/lib/dtrace/libdtrace_dyld.dylib", 1);
*perr = posix_spawnp(&pid, file, NULL, &attr, argv, *_NSGetEnviron());
unsetenv("DYLD_INSERT_LIBRARIES"); /* children must not have this present in their env */
destroy_attr:
posix_spawnattr_destroy(&attr);
if (0 == *perr) {
*perr = task_for_pid(mach_task_self(), pid, &task);
if (*perr == KERN_SUCCESS) {
proc = createProcAndSymbolicator(pid, task, perr, true);
} else {
*perr = -(*perr); // Make room for mach errors
}
} else if (*perr == EBADARCH) {
*perr = APPLE_PCREATE_BAD_ARCHITECTURE;
}
return proc;
}
/*
* Return a printable string corresponding to a Pcreate() error return.
*/
const char *
Pcreate_error(int error)
{
const char *str;
switch (error) {
case C_FORK:
str = "cannot fork";
break;
case C_PERM:
str = "file is set-id or unreadable [Note: the '-c' option requires a full pathname to the file]\n";
break;
case C_NOEXEC:
str = "cannot execute file";
break;
case C_INTR:
str = "operation interrupted";
break;
case C_LP64:
str = "program is _LP64, self is not";
break;
case C_STRANGE:
str = "unanticipated system error";
break;
case C_NOENT:
str = "cannot find executable file";
break;
case APPLE_PCREATE_BAD_SYMBOLICATOR:
str = "Could not create symbolicator for task";
break;
case APPLE_PCREATE_BAD_ARCHITECTURE:
str = "requested architecture missing from executable";
break;
default:
if (error < 0)
str = mach_error_string(-error);
else
str = "unknown error";
break;
}
return (str);
}
/*
* Grab an existing process.
* Return an opaque pointer to its process control structure.
*
* pid: UNIX process ID.
* flags:
* PGRAB_RETAIN Retain tracing flags (default clears all tracing flags).
* PGRAB_FORCE Grab regardless of whether process is already traced.
* PGRAB_RDONLY Open the address space file O_RDONLY instead of O_RDWR,
* and do not open the process control file.
* PGRAB_NOSTOP Open the process but do not force it to stop.
* perr: pointer to error return code.
*/
/*
* APPLE NOTE:
*
* We don't seem to have an equivalent to the tracing
* flag(s), so we're also going to punt on them for now.
*/
#define APPLE_PGRAB_BAD_SYMBOLICATOR 0x0F0F0F0E
#define APPLE_PGRAB_UNSUPPORTED_FLAGS 0x0F0F0F0F
struct ps_prochandle *Pgrab(pid_t pid, int flags, int *perr) {
struct ps_prochandle* proc = NULL;
if (flags & PGRAB_RDONLY || (0 == flags)) {
task_t task;
*perr = task_for_pid(mach_task_self(), pid, &task);
if (*perr == KERN_SUCCESS) {
if (0 == (flags & PGRAB_RDONLY))
(void)task_suspend(task);
proc = createProcAndSymbolicator(pid, task, perr, (flags & PGRAB_RDONLY) ? false : true);
}
} else {
*perr = APPLE_PGRAB_UNSUPPORTED_FLAGS;
}
return proc;
}
const char *Pgrab_error(int err) {
const char* str;
switch (err) {
case APPLE_PGRAB_BAD_SYMBOLICATOR:
str = "Pgrab could not create symbolicator for pid";
case APPLE_PGRAB_UNSUPPORTED_FLAGS:
str = "Pgrab was called with unsupported flags";
default:
str = mach_error_string(err);
}
return str;
}
/*
* Release the process. Frees the process control structure.
* flags:
* PRELEASE_CLEAR Clear all tracing flags.
* PRELEASE_RETAIN Retain current tracing flags.
* PRELEASE_HANG Leave the process stopped and abandoned.
* PRELEASE_KILL Terminate the process with SIGKILL.
*/
/*
* APPLE NOTE:
*
* We're ignoring most flags for now. They will eventually need to be honored.
*/
void Prelease(struct ps_prochandle *P, int flags) {
if (0 == flags) {
if (P->status.pr_flags & PR_KLC)
(void)kill(P->status.pr_pid, SIGKILL);
} else if (flags & PRELEASE_KILL) {
(void)kill(P->status.pr_pid, SIGKILL);
} else if (flags & PRELEASE_HANG)
(void)kill(P->status.pr_pid, SIGSTOP);
// Shouldn't be leaking events. Do this before releasing the symbolicator,
// so the dyld activity thread isn't blocked waiting on an event.
pthread_mutex_lock(&P->proc_activity_queue_mutex);
// Prevent any new events from being queue'd
P->proc_activity_queue_enabled = false;
// Destroy any existing events.
struct ps_proc_activity_event* temp = P->proc_activity_queue;
while (temp != NULL) {
struct ps_proc_activity_event* next = temp->next;
Pdestroy_proc_activity(temp);
temp = next;
}
pthread_mutex_unlock(&P->proc_activity_queue_mutex);
// We don't have to check for kCSNull...
CSRelease(P->symbolicator);
(void) pthread_mutex_destroy(&P->proc_activity_queue_mutex);
(void) pthread_cond_destroy(&P->proc_activity_queue_cond);
free(P);
}
/*
* APPLE NOTE:
*
* These low-level breakpoint functions are no-ops. We expect dyld to make RPC
* calls to give us roughly the same functionality.
*
*/
int Psetbkpt(struct ps_prochandle *P, uintptr_t addr, ulong_t *instr) {
return 0;
}
int Pdelbkpt(struct ps_prochandle *P, uintptr_t addr, ulong_t instr) {
return 0;
}
int Pxecbkpt(struct ps_prochandle *P, ulong_t instr) {
return 0;
}
/*
* APPLE NOTE:
*
* Psetflags/Punsetflags has three caller values at this time.
* PR_KLC - proc kill on last close
* PR_RLC - proc resume/run on last close
* PR_BPTAD - x86 only, breakpoint adjust eip
*
* We are not supporting any of these at this time.
*/
int Psetflags(struct ps_prochandle *P, long flags) {
P->status.pr_flags |= flags;
return 0;
}
int Punsetflags(struct ps_prochandle *P, long flags) {
P->status.pr_flags &= ~flags;
return 0;
}
int pr_open(struct ps_prochandle *P, const char *foo, int bar, mode_t baz) {
NSLog(@"libProc.a UNIMPLEMENTED: pr_open()");
return 0;
}
int pr_close(struct ps_prochandle *P, int foo) {
NSLog(@"libProc.a UNIMPLEMENTED: pr_close");
return 0;
}
int pr_ioctl(struct ps_prochandle *P, int foo, int bar, void *baz, size_t blah) {
NSLog(@"libProc.a UNIMPLEMENTED: pr_ioctl");
return 0;
}
/*
* Search the process symbol tables looking for a symbol whose name matches the
* specified name and whose object and link map optionally match the specified
* parameters. On success, the function returns 0 and fills in the GElf_Sym
* symbol table entry. On failure, -1 is returned.
*/
/*
* APPLE NOTE:
*
* We're completely blowing off the lmid.
*
* It looks like the only GElf_Sym entries used are value & size.
* Most of the time, prsyminfo_t is null, and when it is used, only
* prs_lmid is set.
*/
int Pxlookup_by_name(
struct ps_prochandle *P,
Lmid_t lmid, /* link map to match, or -1 (PR_LMID_EVERY) for any */
const char *oname, /* load object name */
const char *sname, /* symbol name */
GElf_Sym *symp, /* returned symbol table entry */
prsyminfo_t *sip) /* returned symbol info */
{
int err = -1;
__block CSSymbolRef symbol = kCSNull;
if (oname != NULL) {
CSSymbolOwnerRef owner = symbolOwnerForName(P->symbolicator, oname);
if (_dtrace_mangled) {
CSSymbolOwnerForeachSymbolWithMangledName(owner, sname, ^(CSSymbolRef s) { if (CSIsNull(symbol)) symbol = s; });
} else {
CSSymbolOwnerForeachSymbolWithName(owner, sname, ^(CSSymbolRef s) { if (CSIsNull(symbol)) symbol = s; });
}
} else {
if (_dtrace_mangled) {
CSSymbolicatorForeachSymbolWithMangledNameAtTime(P->symbolicator, sname, kCSNow, ^(CSSymbolRef s) { if (CSIsNull(symbol)) symbol = s; });
} else {
CSSymbolicatorForeachSymbolWithNameAtTime(P->symbolicator, sname, kCSNow, ^(CSSymbolRef s) { if (CSIsNull(symbol)) symbol = s; });
}
}
// Filter out symbols we do not want to instrument
if (!CSIsNull(symbol)) {
if (CSSymbolIsDyldStub(symbol)) symbol = kCSNull;
if (!CSSymbolIsFunction(symbol)) symbol = kCSNull;
if (CSSymbolOwnerIsCommpage(CSSymbolGetSymbolOwner(symbol))) symbol = kCSNull; // <rdar://problem/4877551>
}
if (!CSIsNull(symbol)) {
err = 0;
if (symp) {
CSRange addressRange = CSSymbolGetRange(symbol);
symp->st_name = 0;
symp->st_info = GELF_ST_INFO((STB_GLOBAL), (STT_FUNC));
symp->st_other = 0;
symp->st_shndx = SHN_MACHO;
symp->st_value = addressRange.location;
symp->st_size = addressRange.length;
}
if (sip) {
sip->prs_lmid = LM_ID_BASE;
}
}
return err;
}
/*
* Search the process symbol tables looking for a symbol whose
* value to value+size contain the address specified by addr.
* Return values are:
* sym_name_buffer containing the symbol name
* GElf_Sym symbol table entry
* prsyminfo_t ancillary symbol information
* Returns 0 on success, -1 on failure.
*/
/*
* APPLE NOTE:
*
* This function is called directly by the plockstat binary and it passes the
* psyminfo_t argument We only set fields that plockstat actually uses.
* sip->prs_table and sip->prs_id are not used by plockstat so we don't
* attempt to set them.
*/
int
Pxlookup_by_addr(
struct ps_prochandle *P,
mach_vm_address_t addr, /* process address being sought */
char *sym_name_buffer, /* buffer for the symbol name */
size_t bufsize, /* size of sym_name_buffer */
GElf_Sym *symbolp, /* returned symbol table entry */
prsyminfo_t *sip) /* returned symbol info (used only by plockstat) */
{
int err = -1;
CSSymbolRef symbol = CSSymbolicatorGetSymbolWithAddressAtTime(P->symbolicator, (mach_vm_address_t)addr, kCSNow);
// See comments in Ppltdest()
// Filter out symbols we do not want to instrument
// if ([symbol isDyldStub]) symbol = nil;
// if (![symbol isFunction]) symbol = nil;
if (!CSIsNull(symbol)) {
if (_dtrace_mangled) {
const char *mangledName = CSSymbolGetMangledName(symbol);
if (strlen(mangledName) >= 3 &&
mangledName[0] == '_' &&
mangledName[1] == '_' &&
mangledName[2] == 'Z') {
// mangled name - use it
strncpy(sym_name_buffer, mangledName, bufsize);
} else {
strncpy(sym_name_buffer, CSSymbolGetName(symbol), bufsize);
}
} else
strncpy(sym_name_buffer, CSSymbolGetName(symbol), bufsize);
err = 0;
if (symbolp) {
CSRange addressRange = CSSymbolGetRange(symbol);
symbolp->st_name = 0;
symbolp->st_info = GELF_ST_INFO((STB_GLOBAL), (STT_FUNC));
symbolp->st_other = 0;
symbolp->st_shndx = SHN_MACHO;
symbolp->st_value = addressRange.location;
symbolp->st_size = addressRange.length;
}
if (sip) {
CSSymbolOwnerRef owner = CSSymbolGetSymbolOwner(symbol);
sip->prs_name = (bufsize == 0 ? NULL : sym_name_buffer);
sip->prs_object = CSSymbolOwnerGetName(owner);
// APPLE: The following fields set by Solaris code are not used by
// plockstat, hence we don't return them.
//sip->prs_id = (symp == sym1p) ? i1 : i2;
//sip->prs_table = (symp == sym1p) ? PR_SYMTAB : PR_DYNSYM;
// FIXME:!!!
//sip->prs_lmid = (fptr->file_lo == NULL) ? LM_ID_BASE : fptr->file_lo->rl_lmident;
sip->prs_lmid = LM_ID_BASE;
}
}
return err;
}
int Plookup_by_addr(struct ps_prochandle *P, mach_vm_address_t addr, char *buf, size_t size, GElf_Sym *symp) {
return Pxlookup_by_addr(P, addr, buf, size, symp, NULL);
}
/*
* APPLE NOTE:
*
* We're just calling task_resume(). Returns 0 on success, -1 on failure.
*/
int Psetrun(struct ps_prochandle *P,
int sig, /* Ignored in OS X. Nominally supposed to be the signal passed to the target process */
int flags /* Ignored in OS X. PRSTEP|PRSABORT|PRSTOP|PRCSIG|PRCFAULT */)
{
/* If PR_KLC is set, we created the process with posix_spawn(); otherwise we grabbed it with task_suspend. */
if (P->status.pr_flags & PR_KLC)
return kill(P->status.pr_pid, SIGCONT); // Advances BSD p_stat from SSTOP to SRUN
else
return (int)task_resume(CSSymbolicatorGetTask(P->symbolicator));
}
ssize_t Pread(struct ps_prochandle *P, void *buf, size_t nbyte, mach_vm_address_t address) {
vm_offset_t mapped_address;
mach_msg_type_number_t mapped_size;
ssize_t bytes_read = 0;
kern_return_t err = mach_vm_read(CSSymbolicatorGetTask(P->symbolicator), (mach_vm_address_t)address, (mach_vm_size_t)nbyte, &mapped_address, &mapped_size);
if (! err) {
bytes_read = nbyte;
memcpy(buf, (void*)mapped_address, nbyte);
vm_deallocate(mach_task_self(), (vm_address_t)mapped_address, (vm_size_t)mapped_size);
}
return bytes_read;
}
int Pobject_iter(struct ps_prochandle *P, proc_map_f *func, void *cd) {
__block int err = 0;
CSSymbolicatorForeachSymbolOwnerAtTime(P->symbolicator, kCSNow, ^(CSSymbolOwnerRef owner) {
// We work through "generations of symbol owners. At any given point, we only want to
// look at what has changed since the last processing attempt. Dyld may load library after
// library with the same load timestamp. So we mark the symbol owners with a "generation"
// and only look at those that are unmarked, or are the current generation.
uintptr_t generation = CSSymbolOwnerGetTransientUserData(owner);
if (generation == 0 || generation == P->current_symbol_owner_generation) {
if (generation == 0)
CSSymbolOwnerSetTransientUserData(owner, P->current_symbol_owner_generation);
if (err) return; // skip everything after error
if (CSSymbolOwnerIsCommpage(owner)) return; // <rdar://problem/4877551>
prmap_t map;
const char* name = CSSymbolOwnerGetName(owner);
map.pr_vaddr = CSSymbolOwnerGetBaseAddress(owner);
map.pr_mflags = MA_READ;
err = func(cd, &map, name);
}
});
return err;
}
// The solaris version of XYZ_to_map() didn't require the prmap_t* map argument.
// They relied on their backing store to allocate and manage the prmap_t's. We don't
// have an equivalent, and these are cheaper to fill in on the fly than to store.
const prmap_t *Paddr_to_map(struct ps_prochandle *P, mach_vm_address_t addr, prmap_t* map) {
CSSymbolOwnerRef owner = CSSymbolicatorGetSymbolOwnerWithAddressAtTime(P->symbolicator, addr, kCSNow);
// <rdar://problem/4877551>
if (!CSIsNull(owner) && !CSSymbolOwnerIsCommpage(owner)) {
map->pr_vaddr = CSSymbolOwnerGetBaseAddress(owner);
map->pr_mflags = MA_READ; // Anything we get from a symbolicator is readable
return map;
}
return NULL;
}
const prmap_t *Pname_to_map(struct ps_prochandle *P, const char *name, prmap_t* map) {
return (Plmid_to_map(P, PR_LMID_EVERY, name, map));
}
/*
* Given a shared object name, return the map_info_t for it. If no matching
* object is found, return NULL. Normally, the link maps contain the full
* object pathname, e.g. /usr/lib/libc.so.1. We allow the object name to
* take one of the following forms:
*
* 1. An exact match (i.e. a full pathname): "/usr/lib/libc.so.1"
* 2. An exact basename match: "libc.so.1"
* 3. An initial basename match up to a '.' suffix: "libc.so" or "libc"
* 4. The literal string "a.out" is an alias for the executable mapping
*
* The third case is a convenience for callers and may not be necessary.
*
* As the exact same object name may be loaded on different link maps (see
* dlmopen(3DL)), we also allow the caller to resolve the object name by
* specifying a particular link map id. If lmid is PR_LMID_EVERY, the
* first matching name will be returned, regardless of the link map id.
*/
/*
* APPLE NOTE:
*
* It appears there are only 3 uses of this currently. A check for ld.so (dtrace fails against static exe's),
* and a test for a.out'ness. dtrace looks up the map for a.out, and the map for the module, and makes sure
* they share the same v_addr.
*/
const prmap_t *Plmid_to_map(struct ps_prochandle *P, Lmid_t ignored, const char *cname, prmap_t* map) {
// Need to handle some special case defines
if (cname == PR_OBJ_LDSO)
cname = "dyld";
CSSymbolOwnerRef owner = symbolOwnerForName(P->symbolicator, cname);
// CSSymbolOwnerRef owner = CSSymbolicatorGetSymbolOwnerWithNameAtTime(P->symbolicator, cname, kCSNow);
// <rdar://problem/4877551>
if (!CSIsNull(owner) && !CSSymbolOwnerIsCommpage(owner)) {
map->pr_vaddr = CSSymbolOwnerGetBaseAddress(owner);
map->pr_mflags = MA_READ; // Anything we get from a symbolicator is readable
return map;
}
return NULL;
}
/*
* Given a virtual address, return the name of the underlying
* mapped object (file), as provided by the dynamic linker.
* Return NULL on failure (no underlying shared library).
*/
char *Pobjname(struct ps_prochandle *P, mach_vm_address_t addr, char *buffer, size_t bufsize) {
CSSymbolOwnerRef owner = CSSymbolicatorGetSymbolOwnerWithAddressAtTime(P->symbolicator, addr, kCSNow);
if (!CSIsNull(owner)) {
strncpy(buffer, CSSymbolOwnerGetPath(owner), bufsize);
buffer[bufsize-1] = 0; // Make certain buffer is NULL terminated.
return buffer;
}
buffer[0] = 0;
return NULL;
}
/*
* Given a virtual address, return the link map id of the underlying mapped
* object (file), as provided by the dynamic linker. Return -1 on failure.
*/
/*
* APPLE NOTE:
*
* We are treating everything as being in the base map, so no work is needed.
*/
int Plmid(struct ps_prochandle *P, mach_vm_address_t addr, Lmid_t *lmidp) {
*lmidp = LM_ID_BASE;
return 0;
}
/*
* This is an Apple only proc method. It is used by the objc provider,
* to iterate all classes and methods.
*/
int Pobjc_method_iter(struct ps_prochandle *P, proc_objc_f *func, void *cd) {
__block int err = 0;
CSSymbolicatorForeachSymbolOwnerAtTime(P->symbolicator, kCSNow, ^(CSSymbolOwnerRef owner) {
// We work through "generations of symbol owners. At any given point, we only want to
// look at what has changed since the last processing attempt. Dyld may load library after
// library with the same load timestamp. So we mark the symbol owners with a "generation"
// and only look at those that are unmarked, or are the current generation.
uintptr_t generation = CSSymbolOwnerGetTransientUserData(owner);
if (generation == 0 || generation == P->current_symbol_owner_generation) {
if (generation == 0)
CSSymbolOwnerSetTransientUserData(owner, P->current_symbol_owner_generation);
if (err) return; // Have to bail out on error condition
CSSymbolOwnerForeachSymbol(owner, ^(CSSymbolRef symbol) {
if (err) return; // Have to bail out on error condition
if (CSSymbolIsObjcMethod(symbol)) {
GElf_Sym gelf_sym;
CSRange addressRange = CSSymbolGetRange(symbol);
gelf_sym.st_name = 0;
gelf_sym.st_info = GELF_ST_INFO((STB_GLOBAL), (STT_FUNC));
gelf_sym.st_other = 0;
gelf_sym.st_shndx = SHN_MACHO;
gelf_sym.st_value = addressRange.location;
gelf_sym.st_size = addressRange.length;
const char* symbolName = CSSymbolGetName(symbol);
size_t symbolNameLength = strlen(symbolName);
// First find the split point
size_t split_index = 0;
while (symbolName[split_index] != ' ' && symbolName[split_index] != 0)
split_index++;
if (split_index < symbolNameLength) {
// We know the combined length will be +1 byte for an extra NULL, and -3 for no '[', ']', or ' '
char backingStore[256];
char* className = (symbolNameLength < sizeof(backingStore)) ? backingStore : malloc(symbolNameLength);
// Class name range is [2, split_index)
size_t classNameLength = &symbolName[split_index] - &symbolName[2];
strncpy(className, &symbolName[2], classNameLength);
// method name range is [split_index+1, length-1)
char* methodName = &className[classNameLength];
*methodName++ = 0; // Null terminate the className string;
*methodName++ = symbolName[0]; // Apply the -/+ instance/class modifier.
size_t methodNameLength = &symbolName[symbolNameLength] - &symbolName[split_index+1] - 1;
strncpy(methodName, &symbolName[split_index+1], methodNameLength);
methodName[methodNameLength] = 0; // Null terminate!
methodName -= 1; // Move back to cover the modifier.
err = func(cd, &gelf_sym, className, methodName);
// Free any memory we had to allocate
if (className != backingStore)
free(className);
}
}
});
}
});
return err;
}
/*
* APPLE NOTE:
*
* object_name == CSSymbolOwner name
* which == PR_SYMTAB || PR_DYNSYM
* mask == BIND_ANY | TYPE_FUNC (Binding type and func vs data?)
* cd = caller data, pass through
*
* If which is not PR_SYMTAB, return success without doing any work
* We're ignoring the binding type, but honoring TYPE_FUNC
*
* Note that we do not actually iterate in address order!
*/
int Psymbol_iter_by_addr(struct ps_prochandle *P, const char *object_name, int which, int mask, proc_sym_f *func, void *cd) {
__block int err = 0;
if (which != PR_SYMTAB)
return err;
CSSymbolOwnerRef owner = symbolOwnerForName(P->symbolicator, object_name);
// <rdar://problem/4877551>
if (!CSIsNull(owner) && !CSSymbolOwnerIsCommpage(owner)) {
// We work through "generations of symbol owners. At any given point, we only want to
// look at what has changed since the last processing attempt. Dyld may load library after
// library with the same load timestamp. So we mark the symbol owners with a "generation"
// and only look at those that are unmarked, or are the current generation.
uintptr_t generation = CSSymbolOwnerGetTransientUserData(owner);
if (generation == 0 || generation == P->current_symbol_owner_generation) {
if (generation == 0)
CSSymbolOwnerSetTransientUserData(owner, P->current_symbol_owner_generation);
CSSymbolOwnerForeachSymbol(owner, ^(CSSymbolRef symbol) {
if (err)
return; // Bail out on error.
if (CSSymbolIsDyldStub(symbol)) // We never instrument dyld stubs
return;
if ((mask & TYPE_FUNC) && !CSSymbolIsFunction(symbol))
return;
GElf_Sym gelf_sym;
CSRange addressRange = CSSymbolGetRange(symbol);
gelf_sym.st_name = 0;
gelf_sym.st_info = GELF_ST_INFO((STB_GLOBAL), (STT_FUNC));
gelf_sym.st_other = 0;
gelf_sym.st_shndx = SHN_MACHO;
gelf_sym.st_value = addressRange.location;
gelf_sym.st_size = addressRange.length;
const char *mangledName;
if (_dtrace_mangled &&
(mangledName = CSSymbolGetMangledName(symbol)) &&
strlen(mangledName) >= 3 &&
mangledName[0] == '_' &&
mangledName[1] == '_' &&
mangledName[2] == 'Z') {
// mangled name - use it
err = func(cd, &gelf_sym, mangledName);
} else {
err = func(cd, &gelf_sym, CSSymbolGetName(symbol));
}
});
}
} else {
// We must fail if the owner cannot be found
err = -1;
}
return err;
}
void Pupdate_maps(struct ps_prochandle *P) {
}
//
// This method is called after dtrace has handled dyld activity.
// It should "checkpoint" the timestamp of the most recently processed library.
// Following invocations to "iter_by_xyz()" should only process libraries newer
// than the checkpoint time.
//
void Pcheckpoint_syms(struct ps_prochandle *P) {
// In future iterations of the symbolicator, we will only process symbol owners older than what we have already seen.
P->current_symbol_owner_generation++;
}
void Pupdate_syms(struct ps_prochandle *P) {
}
/*
* Given an address, Ppltdest() determines if this is part of a PLT, and if
* so returns a pointer to the symbol name that will be used for resolution.
* If the specified address is not part of a PLT, the function returns NULL.
*/
/*
* APPLE NOTE:
*
* I believe a PLT is equivalent to our dyld stubs. This method is difficult
* to implement properly, there isn't a good place to free a string allocated
* here. Currently, the only use of this method does not actually use the
* returned string, it simply checks !NULL.
*
* We're cheating in one other way. Plookup_by_addr() is used in dt_pid.c to
* match functions to instrument in the pid provider. For that use, we want to
* screen out (!functions && dyldStubs). However, the stack backtrace also uses
* Plookup_by_addr(), and should be able to find dyldStubs.
*
* Because dt_pid.c calls this method just before lookups, we're going to overload
* it to also screen out !functions.
*/
const char *Ppltdest(struct ps_prochandle *P, mach_vm_address_t addr) {
const char* err = NULL;
CSSymbolRef symbol = CSSymbolicatorGetSymbolWithAddressAtTime(P->symbolicator, addr, kCSNow);
// Do not allow dyld stubs, !functions, or commpage entries (<rdar://problem/4877551>)
if (!CSIsNull(symbol) && (CSSymbolIsDyldStub(symbol) || !CSSymbolIsFunction(symbol) || CSSymbolOwnerIsCommpage(CSSymbolGetSymbolOwner(symbol))))
err = "Ppltdest is not implemented";
return err;
}
rd_agent_t *Prd_agent(struct ps_prochandle *P) {
return (rd_agent_t *)P; // rd_agent_t is type punned onto ps_proc_handle (see below).
}
void Penqueue_proc_activity(struct ps_prochandle* P, struct ps_proc_activity_event* activity)
{
pthread_mutex_lock(&P->proc_activity_queue_mutex);
if (P->proc_activity_queue_enabled) {
// Events are processed in order. Add the new activity to the end.
if (P->proc_activity_queue) {
struct ps_proc_activity_event* temp = P->proc_activity_queue;
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = activity;
} else {
P->proc_activity_queue = activity;
}
} else {
// The queue is disabled. destroy the events.
Pdestroy_proc_activity(activity);
}
pthread_mutex_unlock(&P->proc_activity_queue_mutex);
pthread_cond_broadcast(&P->proc_activity_queue_cond);
}
void Pcreate_async_proc_activity(struct ps_prochandle* P, rd_event_e type)
{
struct ps_proc_activity_event* activity = malloc(sizeof(struct ps_proc_activity_event));
activity->rd_event.type = type;
activity->rd_event.u.state = RD_CONSISTENT;
activity->synchronous = false;
activity->destroyed = false;
activity->next = NULL;
Penqueue_proc_activity(P, activity);
}
void Pcreate_sync_proc_activity(struct ps_prochandle* P, rd_event_e type)
{
// Sync proc activity can use stack allocation.
struct ps_proc_activity_event activity;
activity.rd_event.type = type;
activity.rd_event.u.state = RD_CONSISTENT;
activity.synchronous = true;
activity.destroyed = false;
activity.next = NULL;
pthread_mutex_init(&activity.synchronous_mutex, NULL);
pthread_cond_init(&activity.synchronous_cond, NULL);
Penqueue_proc_activity(P, &activity);
// Now wait for the activity to be processed.
pthread_mutex_lock(&activity.synchronous_mutex);
while (!activity.destroyed) {
pthread_cond_wait(&activity.synchronous_cond, &activity.synchronous_mutex);
}
pthread_mutex_unlock(&activity.synchronous_mutex);
pthread_mutex_destroy(&activity.synchronous_mutex);
pthread_cond_destroy(&activity.synchronous_cond);
return;
}
void* Pdequeue_proc_activity(struct ps_prochandle* P)
{
struct ps_proc_activity_event* ret = NULL;
pthread_mutex_lock(&P->proc_activity_queue_mutex);
while (P->proc_activity_queue_enabled && !P->proc_activity_queue)
pthread_cond_wait(&P->proc_activity_queue_cond, &P->proc_activity_queue_mutex);
// Did we get anything?
if (ret = P->proc_activity_queue) {
P->proc_activity_queue = ret->next;
P->rd_event = ret->rd_event;
}
pthread_mutex_unlock(&P->proc_activity_queue_mutex);
// RD_NONE is used to wakeup the control thread.
// Return NULL to indicate that.
if (ret && (ret->rd_event.type == RD_NONE)) {
Pdestroy_proc_activity((void*)ret);
ret = NULL;
}
return ret;
}
void Pdestroy_proc_activity(void* opaque)
{
if (opaque) {
struct ps_proc_activity_event* activity = (struct ps_proc_activity_event*)opaque;
// sync events own their memory, we just notify them.
if (activity->synchronous) {
pthread_mutex_lock(&activity->synchronous_mutex);
activity->destroyed = true;
pthread_mutex_unlock(&activity->synchronous_mutex);
pthread_cond_broadcast(&activity->synchronous_cond);
} else {
free(activity);
}
}
}
//
// Shims for Solaris run-time loader SPI.
// The (opaque) rd_agent type is type punned onto ps_prochandle.
// "breakpoint" addresses corresponding to rd_events are mapped to the event type (a small enum value),
// that's sufficient to uniquely identify the event.
//
rd_err_e rd_event_getmsg(rd_agent_t *oo7, rd_event_msg_t *rdm) {
struct ps_prochandle *P = (struct ps_prochandle *)oo7;
*rdm = P->rd_event;
return RD_OK;
}
//
// Procedural replacement called from dt_proc_bpmatch() to simulate "psp->pr_reg[R_PC]"
//
psaddr_t rd_event_mock_addr(struct ps_prochandle *P)
{
return (psaddr_t)(P->rd_event.type);
}
rd_err_e rd_event_enable(rd_agent_t *oo7, int onoff) {
return RD_OK;
}
rd_err_e rd_event_addr(rd_agent_t *oo7, rd_event_e ev, rd_notify_t *rdn)
{
rdn->type = RD_NOTIFY_BPT;
rdn->u.bptaddr = (psaddr_t)ev;
return RD_OK;
}
char *rd_errstr(rd_err_e err) {
switch (err) {
case RD_ERR:
return "RD_ERR";
case RD_OK:
return "RD_OK";
case RD_NOCAPAB:
return "RD_NOCAPAB";
case RD_DBERR:
return "RD_DBERR";
case RD_NOBASE:
return "RD_NOBASE";
case RD_NODYNAM:
return "RD_NODYNAM";
case RD_NOMAPS:
return "RD_NOMAPS";
default:
return "RD_UNKNOWN";
}
/* NOTREACHED */
}
extern int proc_pidinfo(int pid, int flavor, uint64_t arg, void *buffer, int buffersize);
int Pstate(struct ps_prochandle *P) {
int retval = 0;
struct proc_bsdinfo pbsd;
// Overloaded usage. If we received an explicity LOST/EXIT, dont querry proc_pidinfo
if (P->rd_event.type == RD_DYLD_LOST)
return PS_LOST;
if (P->rd_event.type == RD_DYLD_EXIT)
return PS_UNDEAD;
retval = proc_pidinfo(P->status.pr_pid, PROC_PIDTBSDINFO, (uint64_t)0, &pbsd, sizeof(struct proc_bsdinfo));
if (retval == -1) {
return -1;
} else if (retval == 0) {
return PS_LOST;
} else {
switch(pbsd.pbi_status) {
case SIDL:
return PS_IDLE;
case SRUN:
return PS_RUN;
case SSTOP:
return PS_STOP;
case SZOMB:
return PS_UNDEAD;
case SSLEEP:
return PS_RUN;
default:
return -1;
}
}
/* NOTREACHED */
}
const pstatus_t *Pstatus(struct ps_prochandle *P) {
return &P->status;
}