Page MenuHomePhabricator

trunk
No OneTemporary

File Metadata

Created
Wed, Jan 22, 4:09 AM
This file is larger than 256 KB, so syntax highlighting was skipped.
Index: tools/debugserver/source/RNBServices.cpp
===================================================================
--- tools/debugserver/source/RNBServices.cpp (revision 333398)
+++ tools/debugserver/source/RNBServices.cpp (revision 333399)
@@ -1,235 +1,235 @@
//===-- RNBServices.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 Christopher Friesen on 3/21/08.
//
//===----------------------------------------------------------------------===//
#include "RNBServices.h"
#include "CFString.h"
#include "DNBLog.h"
#include "MacOSX/CFUtils.h"
#include <CoreFoundation/CoreFoundation.h>
#include <libproc.h>
#include <sys/sysctl.h>
#include <unistd.h>
#include <vector>
// For now only SpringBoard has a notion of "Applications" that it can list for
// us.
// So we have to use the SpringBoard API's here.
#if defined(WITH_SPRINGBOARD) || defined(WITH_BKS)
#include <SpringBoardServices/SpringBoardServices.h>
#endif
// From DNB.cpp
size_t GetAllInfos(std::vector<struct kinfo_proc> &proc_infos);
int GetProcesses(CFMutableArrayRef plistMutableArray, bool all_users) {
if (plistMutableArray == NULL)
return -1;
// Running as root, get all processes
std::vector<struct kinfo_proc> proc_infos;
const size_t num_proc_infos = GetAllInfos(proc_infos);
if (num_proc_infos > 0) {
const pid_t our_pid = getpid();
const uid_t our_uid = getuid();
uint32_t i;
CFAllocatorRef alloc = kCFAllocatorDefault;
for (i = 0; i < num_proc_infos; i++) {
struct kinfo_proc &proc_info = proc_infos[i];
bool kinfo_user_matches;
// Special case, if lldb is being run as root we can attach to anything.
if (all_users)
kinfo_user_matches = true;
else
kinfo_user_matches = proc_info.kp_eproc.e_pcred.p_ruid == our_uid;
const pid_t pid = proc_info.kp_proc.p_pid;
// Skip zombie processes and processes with unset status
if (kinfo_user_matches == false || // User is acceptable
pid == our_pid || // Skip this process
pid == 0 || // Skip kernel (kernel pid is zero)
proc_info.kp_proc.p_stat ==
SZOMB || // Zombies are bad, they like brains...
proc_info.kp_proc.p_flag & P_TRACED || // Being debugged?
proc_info.kp_proc.p_flag & P_WEXIT || // Working on exiting?
proc_info.kp_proc.p_flag &
P_TRANSLATED) // Skip translated ppc (Rosetta)
continue;
// Create a new mutable dictionary for each application
CFReleaser<CFMutableDictionaryRef> appInfoDict(
::CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
// Get the process id for the app (if there is one)
const int32_t pid_int32 = pid;
CFReleaser<CFNumberRef> pidCFNumber(
::CFNumberCreate(alloc, kCFNumberSInt32Type, &pid_int32));
::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_PID_KEY,
pidCFNumber.get());
- // Set the a boolean to indicate if this is the front most
+ // Set a boolean to indicate if this is the front most
::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_FRONTMOST_KEY,
kCFBooleanFalse);
const char *pid_basename = proc_info.kp_proc.p_comm;
char proc_path_buf[PATH_MAX];
int return_val = proc_pidpath(pid, proc_path_buf, PATH_MAX);
if (return_val > 0) {
// Okay, now search backwards from that to see if there is a
// slash in the name. Note, even though we got all the args we don't
// care
// because the list data is just a bunch of concatenated null terminated
// strings
// so strrchr will start from the end of argv0.
pid_basename = strrchr(proc_path_buf, '/');
if (pid_basename) {
// Skip the '/'
++pid_basename;
} else {
// We didn't find a directory delimiter in the process argv[0], just
// use what was in there
pid_basename = proc_path_buf;
}
CFString cf_pid_path(proc_path_buf);
if (cf_pid_path.get())
::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_PATH_KEY,
cf_pid_path.get());
}
if (pid_basename && pid_basename[0]) {
CFString pid_name(pid_basename);
::CFDictionarySetValue(appInfoDict.get(),
DTSERVICES_APP_DISPLAY_NAME_KEY, pid_name.get());
}
// Append the application info to the plist array
::CFArrayAppendValue(plistMutableArray, appInfoDict.get());
}
}
return 0;
}
int ListApplications(std::string &plist, bool opt_runningApps,
bool opt_debuggable) {
int result = -1;
CFAllocatorRef alloc = kCFAllocatorDefault;
// Create a mutable array that we can populate. Specify zero so it can be of
// any size.
CFReleaser<CFMutableArrayRef> plistMutableArray(
::CFArrayCreateMutable(alloc, 0, &kCFTypeArrayCallBacks));
const uid_t our_uid = getuid();
#if defined(WITH_SPRINGBOARD) || defined(WITH_BKS)
if (our_uid == 0) {
bool all_users = true;
result = GetProcesses(plistMutableArray.get(), all_users);
} else {
CFReleaser<CFStringRef> sbsFrontAppID(
::SBSCopyFrontmostApplicationDisplayIdentifier());
CFReleaser<CFArrayRef> sbsAppIDs(::SBSCopyApplicationDisplayIdentifiers(
opt_runningApps, opt_debuggable));
// Need to check the return value from SBSCopyApplicationDisplayIdentifiers.
CFIndex count = sbsAppIDs.get() ? ::CFArrayGetCount(sbsAppIDs.get()) : 0;
CFIndex i = 0;
for (i = 0; i < count; i++) {
CFStringRef displayIdentifier =
(CFStringRef)::CFArrayGetValueAtIndex(sbsAppIDs.get(), i);
// Create a new mutable dictionary for each application
CFReleaser<CFMutableDictionaryRef> appInfoDict(
::CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
// Get the process id for the app (if there is one)
pid_t pid = INVALID_NUB_PROCESS;
if (::SBSProcessIDForDisplayIdentifier((CFStringRef)displayIdentifier,
&pid) == true) {
CFReleaser<CFNumberRef> pidCFNumber(
::CFNumberCreate(alloc, kCFNumberSInt32Type, &pid));
::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_PID_KEY,
pidCFNumber.get());
}
- // Set the a boolean to indicate if this is the front most
+ // Set a boolean to indicate if this is the front most
if (sbsFrontAppID.get() && displayIdentifier &&
(::CFStringCompare(sbsFrontAppID.get(), displayIdentifier, 0) ==
kCFCompareEqualTo))
::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_FRONTMOST_KEY,
kCFBooleanTrue);
else
::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_FRONTMOST_KEY,
kCFBooleanFalse);
CFReleaser<CFStringRef> executablePath(
::SBSCopyExecutablePathForDisplayIdentifier(displayIdentifier));
if (executablePath.get() != NULL) {
::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_PATH_KEY,
executablePath.get());
}
CFReleaser<CFStringRef> iconImagePath(
::SBSCopyIconImagePathForDisplayIdentifier(displayIdentifier));
if (iconImagePath.get() != NULL) {
::CFDictionarySetValue(appInfoDict.get(), DTSERVICES_APP_ICON_PATH_KEY,
iconImagePath.get());
}
CFReleaser<CFStringRef> localizedDisplayName(
::SBSCopyLocalizedApplicationNameForDisplayIdentifier(
displayIdentifier));
if (localizedDisplayName.get() != NULL) {
::CFDictionarySetValue(appInfoDict.get(),
DTSERVICES_APP_DISPLAY_NAME_KEY,
localizedDisplayName.get());
}
// Append the application info to the plist array
::CFArrayAppendValue(plistMutableArray.get(), appInfoDict.get());
}
}
#else // #if defined (WITH_SPRINGBOARD) || defined (WITH_BKS)
// When root, show all processes
bool all_users = (our_uid == 0);
GetProcesses(plistMutableArray.get(), all_users);
#endif
CFReleaser<CFDataRef> plistData(
::CFPropertyListCreateXMLData(alloc, plistMutableArray.get()));
// write plist to service port
if (plistData.get() != NULL) {
CFIndex size = ::CFDataGetLength(plistData.get());
const UInt8 *bytes = ::CFDataGetBytePtr(plistData.get());
if (bytes != NULL && size > 0) {
plist.assign((const char *)bytes, size);
return 0; // Success
} else {
DNBLogError("empty application property list.");
result = -2;
}
} else {
DNBLogError("serializing task list.");
result = -3;
}
return result;
}
Index: tools/debugserver/source/MacOSX/MachProcess.mm
===================================================================
--- tools/debugserver/source/MacOSX/MachProcess.mm (revision 333398)
+++ tools/debugserver/source/MacOSX/MachProcess.mm (revision 333399)
@@ -1,3911 +1,3911 @@
//===-- MachProcess.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/15/07.
//
//===----------------------------------------------------------------------===//
#include "DNB.h"
#include "MacOSX/CFUtils.h"
#include "SysSignal.h"
#include <dlfcn.h>
#include <inttypes.h>
#include <mach-o/loader.h>
#include <mach/mach.h>
#include <mach/task.h>
#include <pthread.h>
#include <signal.h>
#include <spawn.h>
#include <sys/fcntl.h>
#include <sys/ptrace.h>
#include <sys/stat.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <unistd.h>
#include <uuid/uuid.h>
#include <algorithm>
#include <map>
#import <Foundation/Foundation.h>
#include "DNBDataRef.h"
#include "DNBLog.h"
#include "DNBThreadResumeActions.h"
#include "DNBTimer.h"
#include "MachProcess.h"
#include "PseudoTerminal.h"
#include "CFBundle.h"
#include "CFString.h"
#ifdef WITH_SPRINGBOARD
#include <CoreFoundation/CoreFoundation.h>
#include <SpringBoardServices/SBSWatchdogAssertion.h>
#include <SpringBoardServices/SpringBoardServer.h>
static bool IsSBProcess(nub_process_t pid) {
CFReleaser<CFArrayRef> appIdsForPID(
::SBSCopyDisplayIdentifiersForProcessID(pid));
return appIdsForPID.get() != NULL;
}
#endif // WITH_SPRINGBOARD
#if defined(WITH_SPRINGBOARD) || defined(WITH_BKS) || defined(WITH_FBS)
// This returns a CFRetained pointer to the Bundle ID for app_bundle_path,
// or NULL if there was some problem getting the bundle id.
static CFStringRef CopyBundleIDForPath(const char *app_bundle_path,
DNBError &err_str);
#endif
#if defined(WITH_BKS) || defined(WITH_FBS)
#import <Foundation/Foundation.h>
static const int OPEN_APPLICATION_TIMEOUT_ERROR = 111;
typedef void (*SetErrorFunction)(NSInteger, DNBError &);
typedef bool (*CallOpenApplicationFunction)(NSString *bundleIDNSStr,
NSDictionary *options,
DNBError &error, pid_t *return_pid);
// This function runs the BKSSystemService (or FBSSystemService) method
// openApplication:options:clientPort:withResult,
// messaging the app passed in bundleIDNSStr.
// The function should be run inside of an NSAutoReleasePool.
//
// It will use the "options" dictionary passed in, and fill the error passed in
// if there is an error.
// If return_pid is not NULL, we'll fetch the pid that was made for the
// bundleID.
// If bundleIDNSStr is NULL, then the system application will be messaged.
template <typename OpenFlavor, typename ErrorFlavor,
ErrorFlavor no_error_enum_value, SetErrorFunction error_function>
static bool CallBoardSystemServiceOpenApplication(NSString *bundleIDNSStr,
NSDictionary *options,
DNBError &error,
pid_t *return_pid) {
// Now make our systemService:
OpenFlavor *system_service = [[OpenFlavor alloc] init];
if (bundleIDNSStr == nil) {
bundleIDNSStr = [system_service systemApplicationBundleIdentifier];
if (bundleIDNSStr == nil) {
// Okay, no system app...
error.SetErrorString("No system application to message.");
return false;
}
}
mach_port_t client_port = [system_service createClientPort];
__block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block ErrorFlavor open_app_error = no_error_enum_value;
bool wants_pid = (return_pid != NULL);
__block pid_t pid_in_block;
const char *cstr = [bundleIDNSStr UTF8String];
if (!cstr)
cstr = "<Unknown Bundle ID>";
NSString *description = [options description];
DNBLog("About to launch process for bundle ID: %s - options:\n%s", cstr,
[description UTF8String]);
[system_service
openApplication:bundleIDNSStr
options:options
clientPort:client_port
withResult:^(NSError *bks_error) {
// The system service will cleanup the client port we created for
// us.
if (bks_error)
open_app_error = (ErrorFlavor)[bks_error code];
if (open_app_error == no_error_enum_value) {
if (wants_pid) {
pid_in_block =
[system_service pidForApplication:bundleIDNSStr];
DNBLog(
"In completion handler, got pid for bundle id, pid: %d.",
pid_in_block);
DNBLogThreadedIf(
LOG_PROCESS,
"In completion handler, got pid for bundle id, pid: %d.",
pid_in_block);
} else
DNBLogThreadedIf(LOG_PROCESS,
"In completion handler: success.");
} else {
const char *error_str =
[(NSString *)[bks_error localizedDescription] UTF8String];
DNBLogThreadedIf(LOG_PROCESS, "In completion handler for send "
"event, got error \"%s\"(%ld).",
error_str ? error_str : "<unknown error>",
open_app_error);
// REMOVE ME
DNBLogError("In completion handler for send event, got error "
"\"%s\"(%ld).",
error_str ? error_str : "<unknown error>",
open_app_error);
}
[system_service release];
dispatch_semaphore_signal(semaphore);
}
];
const uint32_t timeout_secs = 30;
dispatch_time_t timeout =
dispatch_time(DISPATCH_TIME_NOW, timeout_secs * NSEC_PER_SEC);
long success = dispatch_semaphore_wait(semaphore, timeout) == 0;
dispatch_release(semaphore);
if (!success) {
DNBLogError("timed out trying to send openApplication to %s.", cstr);
error.SetError(OPEN_APPLICATION_TIMEOUT_ERROR, DNBError::Generic);
error.SetErrorString("timed out trying to launch app");
} else if (open_app_error != no_error_enum_value) {
error_function(open_app_error, error);
DNBLogError("unable to launch the application with CFBundleIdentifier '%s' "
"bks_error = %u",
cstr, open_app_error);
success = false;
} else if (wants_pid) {
*return_pid = pid_in_block;
DNBLogThreadedIf(
LOG_PROCESS,
"Out of completion handler, pid from block %d and passing out: %d",
pid_in_block, *return_pid);
}
return success;
}
#endif
#if defined(WITH_BKS) || defined(WITH_FBS)
static void SplitEventData(const char *data, std::vector<std::string> &elements)
{
elements.clear();
if (!data)
return;
const char *start = data;
while (*start != '\0') {
const char *token = strchr(start, ':');
if (!token) {
elements.push_back(std::string(start));
return;
}
if (token != start)
elements.push_back(std::string(start, token - start));
start = ++token;
}
}
#endif
#ifdef WITH_BKS
#import <Foundation/Foundation.h>
extern "C" {
#import <BackBoardServices/BKSOpenApplicationConstants_Private.h>
#import <BackBoardServices/BKSSystemService_LaunchServices.h>
#import <BackBoardServices/BackBoardServices.h>
}
static bool IsBKSProcess(nub_process_t pid) {
BKSApplicationStateMonitor *state_monitor =
[[BKSApplicationStateMonitor alloc] init];
BKSApplicationState app_state =
[state_monitor mostElevatedApplicationStateForPID:pid];
return app_state != BKSApplicationStateUnknown;
}
static void SetBKSError(NSInteger error_code, DNBError &error) {
error.SetError(error_code, DNBError::BackBoard);
NSString *err_nsstr = ::BKSOpenApplicationErrorCodeToString(
(BKSOpenApplicationErrorCode)error_code);
const char *err_str = NULL;
if (err_nsstr == NULL)
err_str = "unknown BKS error";
else {
err_str = [err_nsstr UTF8String];
if (err_str == NULL)
err_str = "unknown BKS error";
}
error.SetErrorString(err_str);
}
static bool BKSAddEventDataToOptions(NSMutableDictionary *options,
const char *event_data,
DNBError &option_error) {
std::vector<std::string> values;
SplitEventData(event_data, values);
bool found_one = false;
for (std::string value : values)
{
if (value.compare("BackgroundContentFetching") == 0) {
DNBLog("Setting ActivateForEvent key in options dictionary.");
NSDictionary *event_details = [NSDictionary dictionary];
NSDictionary *event_dictionary = [NSDictionary
dictionaryWithObject:event_details
forKey:
BKSActivateForEventOptionTypeBackgroundContentFetching];
[options setObject:event_dictionary
forKey:BKSOpenApplicationOptionKeyActivateForEvent];
found_one = true;
} else if (value.compare("ActivateSuspended") == 0) {
DNBLog("Setting ActivateSuspended key in options dictionary.");
[options setObject:@YES forKey: BKSOpenApplicationOptionKeyActivateSuspended];
found_one = true;
} else {
DNBLogError("Unrecognized event type: %s. Ignoring.", value.c_str());
option_error.SetErrorString("Unrecognized event data");
}
}
return found_one;
}
static NSMutableDictionary *BKSCreateOptionsDictionary(
const char *app_bundle_path, NSMutableArray *launch_argv,
NSMutableDictionary *launch_envp, NSString *stdio_path, bool disable_aslr,
const char *event_data) {
NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
if (launch_argv != nil)
[debug_options setObject:launch_argv forKey:BKSDebugOptionKeyArguments];
if (launch_envp != nil)
[debug_options setObject:launch_envp forKey:BKSDebugOptionKeyEnvironment];
[debug_options setObject:stdio_path forKey:BKSDebugOptionKeyStandardOutPath];
[debug_options setObject:stdio_path
forKey:BKSDebugOptionKeyStandardErrorPath];
[debug_options setObject:[NSNumber numberWithBool:YES]
forKey:BKSDebugOptionKeyWaitForDebugger];
if (disable_aslr)
[debug_options setObject:[NSNumber numberWithBool:YES]
forKey:BKSDebugOptionKeyDisableASLR];
// That will go in the overall dictionary:
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:debug_options
forKey:BKSOpenApplicationOptionKeyDebuggingOptions];
// And there are some other options at the top level in this dictionary:
[options setObject:[NSNumber numberWithBool:YES]
forKey:BKSOpenApplicationOptionKeyUnlockDevice];
DNBError error;
BKSAddEventDataToOptions(options, event_data, error);
return options;
}
static CallOpenApplicationFunction BKSCallOpenApplicationFunction =
CallBoardSystemServiceOpenApplication<
BKSSystemService, BKSOpenApplicationErrorCode,
BKSOpenApplicationErrorCodeNone, SetBKSError>;
#endif // WITH_BKS
#ifdef WITH_FBS
#import <Foundation/Foundation.h>
extern "C" {
#import <FrontBoardServices/FBSOpenApplicationConstants_Private.h>
#import <FrontBoardServices/FBSSystemService_LaunchServices.h>
#import <FrontBoardServices/FrontBoardServices.h>
#import <MobileCoreServices/LSResourceProxy.h>
#import <MobileCoreServices/MobileCoreServices.h>
}
#ifdef WITH_BKS
static bool IsFBSProcess(nub_process_t pid) {
BKSApplicationStateMonitor *state_monitor =
[[BKSApplicationStateMonitor alloc] init];
BKSApplicationState app_state =
[state_monitor mostElevatedApplicationStateForPID:pid];
return app_state != BKSApplicationStateUnknown;
}
#else
static bool IsFBSProcess(nub_process_t pid) {
// FIXME: What is the FBS equivalent of BKSApplicationStateMonitor
return true;
}
#endif
static void SetFBSError(NSInteger error_code, DNBError &error) {
error.SetError((DNBError::ValueType)error_code, DNBError::FrontBoard);
NSString *err_nsstr = ::FBSOpenApplicationErrorCodeToString(
(FBSOpenApplicationErrorCode)error_code);
const char *err_str = NULL;
if (err_nsstr == NULL)
err_str = "unknown FBS error";
else {
err_str = [err_nsstr UTF8String];
if (err_str == NULL)
err_str = "unknown FBS error";
}
error.SetErrorString(err_str);
}
static bool FBSAddEventDataToOptions(NSMutableDictionary *options,
const char *event_data,
DNBError &option_error) {
std::vector<std::string> values;
SplitEventData(event_data, values);
bool found_one = false;
for (std::string value : values)
{
if (value.compare("BackgroundContentFetching") == 0) {
DNBLog("Setting ActivateForEvent key in options dictionary.");
NSDictionary *event_details = [NSDictionary dictionary];
NSDictionary *event_dictionary = [NSDictionary
dictionaryWithObject:event_details
forKey:
FBSActivateForEventOptionTypeBackgroundContentFetching];
[options setObject:event_dictionary
forKey:FBSOpenApplicationOptionKeyActivateForEvent];
found_one = true;
} else if (value.compare("ActivateSuspended") == 0) {
DNBLog("Setting ActivateSuspended key in options dictionary.");
[options setObject:@YES forKey: FBSOpenApplicationOptionKeyActivateSuspended];
found_one = true;
} else {
DNBLogError("Unrecognized event type: %s. Ignoring.", value.c_str());
option_error.SetErrorString("Unrecognized event data.");
}
}
return found_one;
}
static NSMutableDictionary *
FBSCreateOptionsDictionary(const char *app_bundle_path,
NSMutableArray *launch_argv,
NSDictionary *launch_envp, NSString *stdio_path,
bool disable_aslr, const char *event_data) {
NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
if (launch_argv != nil)
[debug_options setObject:launch_argv forKey:FBSDebugOptionKeyArguments];
if (launch_envp != nil)
[debug_options setObject:launch_envp forKey:FBSDebugOptionKeyEnvironment];
[debug_options setObject:stdio_path forKey:FBSDebugOptionKeyStandardOutPath];
[debug_options setObject:stdio_path
forKey:FBSDebugOptionKeyStandardErrorPath];
[debug_options setObject:[NSNumber numberWithBool:YES]
forKey:FBSDebugOptionKeyWaitForDebugger];
if (disable_aslr)
[debug_options setObject:[NSNumber numberWithBool:YES]
forKey:FBSDebugOptionKeyDisableASLR];
// That will go in the overall dictionary:
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:debug_options
forKey:FBSOpenApplicationOptionKeyDebuggingOptions];
// And there are some other options at the top level in this dictionary:
[options setObject:[NSNumber numberWithBool:YES]
forKey:FBSOpenApplicationOptionKeyUnlockDevice];
// We have to get the "sequence ID & UUID" for this app bundle path and send
// them to FBS:
NSURL *app_bundle_url =
[NSURL fileURLWithPath:[NSString stringWithUTF8String:app_bundle_path]
isDirectory:YES];
LSApplicationProxy *app_proxy =
[LSApplicationProxy applicationProxyForBundleURL:app_bundle_url];
if (app_proxy) {
DNBLog("Sending AppProxy info: sequence no: %lu, GUID: %s.",
app_proxy.sequenceNumber,
[app_proxy.cacheGUID.UUIDString UTF8String]);
[options
setObject:[NSNumber numberWithUnsignedInteger:app_proxy.sequenceNumber]
forKey:FBSOpenApplicationOptionKeyLSSequenceNumber];
[options setObject:app_proxy.cacheGUID.UUIDString
forKey:FBSOpenApplicationOptionKeyLSCacheGUID];
}
DNBError error;
FBSAddEventDataToOptions(options, event_data, error);
return options;
}
static CallOpenApplicationFunction FBSCallOpenApplicationFunction =
CallBoardSystemServiceOpenApplication<
FBSSystemService, FBSOpenApplicationErrorCode,
FBSOpenApplicationErrorCodeNone, SetFBSError>;
#endif // WITH_FBS
#if 0
#define DEBUG_LOG(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#define DEBUG_LOG(fmt, ...)
#endif
#ifndef MACH_PROCESS_USE_POSIX_SPAWN
#define MACH_PROCESS_USE_POSIX_SPAWN 1
#endif
#ifndef _POSIX_SPAWN_DISABLE_ASLR
#define _POSIX_SPAWN_DISABLE_ASLR 0x0100
#endif
MachProcess::MachProcess()
: m_pid(0), m_cpu_type(0), m_child_stdin(-1), m_child_stdout(-1),
m_child_stderr(-1), m_path(), m_args(), m_task(this),
m_flags(eMachProcessFlagsNone), m_stdio_thread(0),
m_stdio_mutex(PTHREAD_MUTEX_RECURSIVE), m_stdout_data(),
m_profile_enabled(false), m_profile_interval_usec(0), m_profile_thread(0),
m_profile_data_mutex(PTHREAD_MUTEX_RECURSIVE), m_profile_data(),
m_thread_actions(), m_exception_messages(),
m_exception_messages_mutex(PTHREAD_MUTEX_RECURSIVE), m_thread_list(),
m_activities(), m_state(eStateUnloaded),
m_state_mutex(PTHREAD_MUTEX_RECURSIVE), m_events(0, kAllEventsMask),
m_private_events(0, kAllEventsMask), m_breakpoints(), m_watchpoints(),
m_name_to_addr_callback(NULL), m_name_to_addr_baton(NULL),
m_image_infos_callback(NULL), m_image_infos_baton(NULL),
m_sent_interrupt_signo(0), m_auto_resume_signo(0), m_did_exec(false),
m_dyld_process_info_create(nullptr),
m_dyld_process_info_for_each_image(nullptr),
m_dyld_process_info_release(nullptr),
m_dyld_process_info_get_cache(nullptr) {
m_dyld_process_info_create =
(void *(*)(task_t task, uint64_t timestamp, kern_return_t * kernelError))
dlsym(RTLD_DEFAULT, "_dyld_process_info_create");
m_dyld_process_info_for_each_image =
(void (*)(void *info, void (^)(uint64_t machHeaderAddress,
const uuid_t uuid, const char *path)))
dlsym(RTLD_DEFAULT, "_dyld_process_info_for_each_image");
m_dyld_process_info_release =
(void (*)(void *info))dlsym(RTLD_DEFAULT, "_dyld_process_info_release");
m_dyld_process_info_get_cache = (void (*)(void *info, void *cacheInfo))dlsym(
RTLD_DEFAULT, "_dyld_process_info_get_cache");
DNBLogThreadedIf(LOG_PROCESS | LOG_VERBOSE, "%s", __PRETTY_FUNCTION__);
}
MachProcess::~MachProcess() {
DNBLogThreadedIf(LOG_PROCESS | LOG_VERBOSE, "%s", __PRETTY_FUNCTION__);
Clear();
}
pid_t MachProcess::SetProcessID(pid_t pid) {
// Free any previous process specific data or resources
Clear();
// Set the current PID appropriately
if (pid == 0)
m_pid = ::getpid();
else
m_pid = pid;
return m_pid; // Return actually PID in case a zero pid was passed in
}
nub_state_t MachProcess::GetState() {
// If any other threads access this we will need a mutex for it
PTHREAD_MUTEX_LOCKER(locker, m_state_mutex);
return m_state;
}
const char *MachProcess::ThreadGetName(nub_thread_t tid) {
return m_thread_list.GetName(tid);
}
nub_state_t MachProcess::ThreadGetState(nub_thread_t tid) {
return m_thread_list.GetState(tid);
}
nub_size_t MachProcess::GetNumThreads() const {
return m_thread_list.NumThreads();
}
nub_thread_t MachProcess::GetThreadAtIndex(nub_size_t thread_idx) const {
return m_thread_list.ThreadIDAtIndex(thread_idx);
}
nub_thread_t
MachProcess::GetThreadIDForMachPortNumber(thread_t mach_port_number) const {
return m_thread_list.GetThreadIDByMachPortNumber(mach_port_number);
}
nub_bool_t MachProcess::SyncThreadState(nub_thread_t tid) {
MachThreadSP thread_sp(m_thread_list.GetThreadByID(tid));
if (!thread_sp)
return false;
kern_return_t kret = ::thread_abort_safely(thread_sp->MachPortNumber());
DNBLogThreadedIf(LOG_THREAD, "thread = 0x%8.8" PRIx32
" calling thread_abort_safely (tid) => %u "
"(GetGPRState() for stop_count = %u)",
thread_sp->MachPortNumber(), kret,
thread_sp->Process()->StopCount());
if (kret == KERN_SUCCESS)
return true;
else
return false;
}
ThreadInfo::QoS MachProcess::GetRequestedQoS(nub_thread_t tid, nub_addr_t tsd,
uint64_t dti_qos_class_index) {
return m_thread_list.GetRequestedQoS(tid, tsd, dti_qos_class_index);
}
nub_addr_t MachProcess::GetPThreadT(nub_thread_t tid) {
return m_thread_list.GetPThreadT(tid);
}
nub_addr_t MachProcess::GetDispatchQueueT(nub_thread_t tid) {
return m_thread_list.GetDispatchQueueT(tid);
}
nub_addr_t MachProcess::GetTSDAddressForThread(
nub_thread_t tid, uint64_t plo_pthread_tsd_base_address_offset,
uint64_t plo_pthread_tsd_base_offset, uint64_t plo_pthread_tsd_entry_size) {
return m_thread_list.GetTSDAddressForThread(
tid, plo_pthread_tsd_base_address_offset, plo_pthread_tsd_base_offset,
plo_pthread_tsd_entry_size);
}
const char *MachProcess::GetDeploymentInfo(const struct load_command& lc,
uint64_t load_command_address,
uint32_t& major_version,
uint32_t& minor_version,
uint32_t& patch_version) {
uint32_t cmd = lc.cmd & ~LC_REQ_DYLD;
bool lc_cmd_known =
cmd == LC_VERSION_MIN_IPHONEOS || cmd == LC_VERSION_MIN_MACOSX ||
cmd == LC_VERSION_MIN_TVOS || cmd == LC_VERSION_MIN_WATCHOS;
if (lc_cmd_known) {
struct version_min_command vers_cmd;
if (ReadMemory(load_command_address, sizeof(struct version_min_command),
&vers_cmd) != sizeof(struct version_min_command)) {
return nullptr;
}
major_version = vers_cmd.sdk >> 16;
minor_version = (vers_cmd.sdk >> 8) & 0xffu;
patch_version = vers_cmd.sdk & 0xffu;
switch (cmd) {
case LC_VERSION_MIN_IPHONEOS:
return "ios";
case LC_VERSION_MIN_MACOSX:
return "macosx";
case LC_VERSION_MIN_TVOS:
return "tvos";
case LC_VERSION_MIN_WATCHOS:
return "watchos";
default:
return nullptr;
}
}
#if defined (LC_BUILD_VERSION)
if (cmd == LC_BUILD_VERSION) {
struct build_version_command build_vers;
if (ReadMemory(load_command_address, sizeof(struct build_version_command),
&build_vers) != sizeof(struct build_version_command)) {
return nullptr;
}
major_version = build_vers.sdk >> 16;;
minor_version = (build_vers.sdk >> 8) & 0xffu;
patch_version = build_vers.sdk & 0xffu;
switch (build_vers.platform) {
case PLATFORM_MACOS:
return "macosx";
case PLATFORM_IOS:
return "ios";
case PLATFORM_TVOS:
return "tvos";
case PLATFORM_WATCHOS:
return "watchos";
case PLATFORM_BRIDGEOS:
return "bridgeos";
}
}
#endif
return nullptr;
}
// Given an address, read the mach-o header and load commands out of memory to
// fill in
// the mach_o_information "inf" object.
//
// Returns false if there was an error in reading this mach-o file header/load
// commands.
bool MachProcess::GetMachOInformationFromMemory(
nub_addr_t mach_o_header_addr, int wordsize,
struct mach_o_information &inf) {
uint64_t load_cmds_p;
if (wordsize == 4) {
struct mach_header header;
if (ReadMemory(mach_o_header_addr, sizeof(struct mach_header), &header) !=
sizeof(struct mach_header)) {
return false;
}
load_cmds_p = mach_o_header_addr + sizeof(struct mach_header);
inf.mach_header.magic = header.magic;
inf.mach_header.cputype = header.cputype;
// high byte of cpusubtype is used for "capability bits", v.
// CPU_SUBTYPE_MASK, CPU_SUBTYPE_LIB64 in machine.h
inf.mach_header.cpusubtype = header.cpusubtype & 0x00ffffff;
inf.mach_header.filetype = header.filetype;
inf.mach_header.ncmds = header.ncmds;
inf.mach_header.sizeofcmds = header.sizeofcmds;
inf.mach_header.flags = header.flags;
} else {
struct mach_header_64 header;
if (ReadMemory(mach_o_header_addr, sizeof(struct mach_header_64),
&header) != sizeof(struct mach_header_64)) {
return false;
}
load_cmds_p = mach_o_header_addr + sizeof(struct mach_header_64);
inf.mach_header.magic = header.magic;
inf.mach_header.cputype = header.cputype;
// high byte of cpusubtype is used for "capability bits", v.
// CPU_SUBTYPE_MASK, CPU_SUBTYPE_LIB64 in machine.h
inf.mach_header.cpusubtype = header.cpusubtype & 0x00ffffff;
inf.mach_header.filetype = header.filetype;
inf.mach_header.ncmds = header.ncmds;
inf.mach_header.sizeofcmds = header.sizeofcmds;
inf.mach_header.flags = header.flags;
}
for (uint32_t j = 0; j < inf.mach_header.ncmds; j++) {
struct load_command lc;
if (ReadMemory(load_cmds_p, sizeof(struct load_command), &lc) !=
sizeof(struct load_command)) {
return false;
}
if (lc.cmd == LC_SEGMENT) {
struct segment_command seg;
if (ReadMemory(load_cmds_p, sizeof(struct segment_command), &seg) !=
sizeof(struct segment_command)) {
return false;
}
struct mach_o_segment this_seg;
char name[17];
::memset(name, 0, sizeof(name));
memcpy(name, seg.segname, sizeof(seg.segname));
this_seg.name = name;
this_seg.vmaddr = seg.vmaddr;
this_seg.vmsize = seg.vmsize;
this_seg.fileoff = seg.fileoff;
this_seg.filesize = seg.filesize;
this_seg.maxprot = seg.maxprot;
this_seg.initprot = seg.initprot;
this_seg.nsects = seg.nsects;
this_seg.flags = seg.flags;
inf.segments.push_back(this_seg);
}
if (lc.cmd == LC_SEGMENT_64) {
struct segment_command_64 seg;
if (ReadMemory(load_cmds_p, sizeof(struct segment_command_64), &seg) !=
sizeof(struct segment_command_64)) {
return false;
}
struct mach_o_segment this_seg;
char name[17];
::memset(name, 0, sizeof(name));
memcpy(name, seg.segname, sizeof(seg.segname));
this_seg.name = name;
this_seg.vmaddr = seg.vmaddr;
this_seg.vmsize = seg.vmsize;
this_seg.fileoff = seg.fileoff;
this_seg.filesize = seg.filesize;
this_seg.maxprot = seg.maxprot;
this_seg.initprot = seg.initprot;
this_seg.nsects = seg.nsects;
this_seg.flags = seg.flags;
inf.segments.push_back(this_seg);
}
if (lc.cmd == LC_UUID) {
struct uuid_command uuidcmd;
if (ReadMemory(load_cmds_p, sizeof(struct uuid_command), &uuidcmd) ==
sizeof(struct uuid_command))
uuid_copy(inf.uuid, uuidcmd.uuid);
}
uint32_t major_version, minor_version, patch_version;
if (const char *platform = GetDeploymentInfo(lc, load_cmds_p,
major_version, minor_version,
patch_version)) {
inf.min_version_os_name = platform;
inf.min_version_os_version = "";
inf.min_version_os_version += std::to_string(major_version);
inf.min_version_os_version += ".";
inf.min_version_os_version += std::to_string(minor_version);
if (patch_version != 0) {
inf.min_version_os_version += ".";
inf.min_version_os_version += std::to_string(patch_version);
}
}
load_cmds_p += lc.cmdsize;
}
return true;
}
// Given completely filled in array of binary_image_information structures,
// create a JSONGenerator object
// with all the details we want to send to lldb.
JSONGenerator::ObjectSP MachProcess::FormatDynamicLibrariesIntoJSON(
const std::vector<struct binary_image_information> &image_infos) {
JSONGenerator::ArraySP image_infos_array_sp(new JSONGenerator::Array());
const size_t image_count = image_infos.size();
for (size_t i = 0; i < image_count; i++) {
JSONGenerator::DictionarySP image_info_dict_sp(
new JSONGenerator::Dictionary());
image_info_dict_sp->AddIntegerItem("load_address",
image_infos[i].load_address);
image_info_dict_sp->AddIntegerItem("mod_date", image_infos[i].mod_date);
image_info_dict_sp->AddStringItem("pathname", image_infos[i].filename);
uuid_string_t uuidstr;
uuid_unparse_upper(image_infos[i].macho_info.uuid, uuidstr);
image_info_dict_sp->AddStringItem("uuid", uuidstr);
if (image_infos[i].macho_info.min_version_os_name.empty() == false &&
image_infos[i].macho_info.min_version_os_version.empty() == false) {
image_info_dict_sp->AddStringItem(
"min_version_os_name", image_infos[i].macho_info.min_version_os_name);
image_info_dict_sp->AddStringItem(
"min_version_os_sdk",
image_infos[i].macho_info.min_version_os_version);
}
JSONGenerator::DictionarySP mach_header_dict_sp(
new JSONGenerator::Dictionary());
mach_header_dict_sp->AddIntegerItem(
"magic", image_infos[i].macho_info.mach_header.magic);
mach_header_dict_sp->AddIntegerItem(
"cputype", (uint32_t)image_infos[i].macho_info.mach_header.cputype);
mach_header_dict_sp->AddIntegerItem(
"cpusubtype",
(uint32_t)image_infos[i].macho_info.mach_header.cpusubtype);
mach_header_dict_sp->AddIntegerItem(
"filetype", image_infos[i].macho_info.mach_header.filetype);
// DynamicLoaderMacOSX doesn't currently need these fields, so
// don't send them.
// mach_header_dict_sp->AddIntegerItem ("ncmds",
// image_infos[i].macho_info.mach_header.ncmds);
// mach_header_dict_sp->AddIntegerItem ("sizeofcmds",
// image_infos[i].macho_info.mach_header.sizeofcmds);
// mach_header_dict_sp->AddIntegerItem ("flags",
// image_infos[i].macho_info.mach_header.flags);
image_info_dict_sp->AddItem("mach_header", mach_header_dict_sp);
JSONGenerator::ArraySP segments_sp(new JSONGenerator::Array());
for (size_t j = 0; j < image_infos[i].macho_info.segments.size(); j++) {
JSONGenerator::DictionarySP segment_sp(new JSONGenerator::Dictionary());
segment_sp->AddStringItem("name",
image_infos[i].macho_info.segments[j].name);
segment_sp->AddIntegerItem("vmaddr",
image_infos[i].macho_info.segments[j].vmaddr);
segment_sp->AddIntegerItem("vmsize",
image_infos[i].macho_info.segments[j].vmsize);
segment_sp->AddIntegerItem("fileoff",
image_infos[i].macho_info.segments[j].fileoff);
segment_sp->AddIntegerItem(
"filesize", image_infos[i].macho_info.segments[j].filesize);
segment_sp->AddIntegerItem("maxprot",
image_infos[i].macho_info.segments[j].maxprot);
// DynamicLoaderMacOSX doesn't currently need these fields,
// so don't send them.
// segment_sp->AddIntegerItem ("initprot",
// image_infos[i].macho_info.segments[j].initprot);
// segment_sp->AddIntegerItem ("nsects",
// image_infos[i].macho_info.segments[j].nsects);
// segment_sp->AddIntegerItem ("flags",
// image_infos[i].macho_info.segments[j].flags);
segments_sp->AddItem(segment_sp);
}
image_info_dict_sp->AddItem("segments", segments_sp);
image_infos_array_sp->AddItem(image_info_dict_sp);
}
JSONGenerator::DictionarySP reply_sp(new JSONGenerator::Dictionary());
;
reply_sp->AddItem("images", image_infos_array_sp);
return reply_sp;
}
// Get the shared library information using the old (pre-macOS 10.12, pre-iOS
// 10, pre-tvOS 10, pre-watchOS 3)
// code path. We'll be given the address of an array of structures in the form
// {void* load_addr, void* mod_date, void* pathname}
//
// In macOS 10.12 etc and newer, we'll use SPI calls into dyld to gather this
// information.
JSONGenerator::ObjectSP MachProcess::GetLoadedDynamicLibrariesInfos(
nub_process_t pid, nub_addr_t image_list_address, nub_addr_t image_count) {
JSONGenerator::DictionarySP reply_sp;
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
struct kinfo_proc processInfo;
size_t bufsize = sizeof(processInfo);
if (sysctl(mib, (unsigned)(sizeof(mib) / sizeof(int)), &processInfo, &bufsize,
NULL, 0) == 0 &&
bufsize > 0) {
uint32_t pointer_size = 4;
if (processInfo.kp_proc.p_flag & P_LP64)
pointer_size = 8;
std::vector<struct binary_image_information> image_infos;
size_t image_infos_size = image_count * 3 * pointer_size;
uint8_t *image_info_buf = (uint8_t *)malloc(image_infos_size);
if (image_info_buf == NULL) {
return reply_sp;
}
if (ReadMemory(image_list_address, image_infos_size, image_info_buf) !=
image_infos_size) {
return reply_sp;
}
//// First the image_infos array with (load addr, pathname, mod date)
///tuples
for (size_t i = 0; i < image_count; i++) {
struct binary_image_information info;
nub_addr_t pathname_address;
if (pointer_size == 4) {
uint32_t load_address_32;
uint32_t pathname_address_32;
uint32_t mod_date_32;
::memcpy(&load_address_32, image_info_buf + (i * 3 * pointer_size), 4);
::memcpy(&pathname_address_32,
image_info_buf + (i * 3 * pointer_size) + pointer_size, 4);
::memcpy(&mod_date_32, image_info_buf + (i * 3 * pointer_size) +
pointer_size + pointer_size,
4);
info.load_address = load_address_32;
info.mod_date = mod_date_32;
pathname_address = pathname_address_32;
} else {
uint64_t load_address_64;
uint64_t pathname_address_64;
uint64_t mod_date_64;
::memcpy(&load_address_64, image_info_buf + (i * 3 * pointer_size), 8);
::memcpy(&pathname_address_64,
image_info_buf + (i * 3 * pointer_size) + pointer_size, 8);
::memcpy(&mod_date_64, image_info_buf + (i * 3 * pointer_size) +
pointer_size + pointer_size,
8);
info.load_address = load_address_64;
info.mod_date = mod_date_64;
pathname_address = pathname_address_64;
}
char strbuf[17];
info.filename = "";
uint64_t pathname_ptr = pathname_address;
bool still_reading = true;
while (still_reading &&
ReadMemory(pathname_ptr, sizeof(strbuf) - 1, strbuf) ==
sizeof(strbuf) - 1) {
strbuf[sizeof(strbuf) - 1] = '\0';
info.filename += strbuf;
pathname_ptr += sizeof(strbuf) - 1;
// Stop if we found nul byte indicating the end of the string
for (size_t i = 0; i < sizeof(strbuf) - 1; i++) {
if (strbuf[i] == '\0') {
still_reading = false;
break;
}
}
}
uuid_clear(info.macho_info.uuid);
image_infos.push_back(info);
}
if (image_infos.size() == 0) {
return reply_sp;
}
free(image_info_buf);
//// Second, read the mach header / load commands for all the dylibs
for (size_t i = 0; i < image_count; i++) {
if (!GetMachOInformationFromMemory(image_infos[i].load_address,
pointer_size,
image_infos[i].macho_info)) {
return reply_sp;
}
}
//// Third, format all of the above in the JSONGenerator object.
return FormatDynamicLibrariesIntoJSON(image_infos);
}
return reply_sp;
}
// From dyld SPI header dyld_process_info.h
typedef void *dyld_process_info;
struct dyld_process_cache_info {
uuid_t cacheUUID; // UUID of cache used by process
uint64_t cacheBaseAddress; // load address of dyld shared cache
bool noCache; // process is running without a dyld cache
bool privateCache; // process is using a private copy of its dyld cache
};
// Use the dyld SPI present in macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer
// to get
// the load address, uuid, and filenames of all the libraries.
// This only fills in those three fields in the 'struct
// binary_image_information' - call
// GetMachOInformationFromMemory to fill in the mach-o header/load command
// details.
void MachProcess::GetAllLoadedBinariesViaDYLDSPI(
std::vector<struct binary_image_information> &image_infos) {
kern_return_t kern_ret;
if (m_dyld_process_info_create) {
dyld_process_info info =
m_dyld_process_info_create(m_task.TaskPort(), 0, &kern_ret);
if (info) {
m_dyld_process_info_for_each_image(
info,
^(uint64_t mach_header_addr, const uuid_t uuid, const char *path) {
struct binary_image_information image;
image.filename = path;
uuid_copy(image.macho_info.uuid, uuid);
image.load_address = mach_header_addr;
image_infos.push_back(image);
});
m_dyld_process_info_release(info);
}
}
}
// Fetch information about all shared libraries using the dyld SPIs that exist
// in
// macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer.
JSONGenerator::ObjectSP
MachProcess::GetAllLoadedLibrariesInfos(nub_process_t pid) {
JSONGenerator::DictionarySP reply_sp;
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
struct kinfo_proc processInfo;
size_t bufsize = sizeof(processInfo);
if (sysctl(mib, (unsigned)(sizeof(mib) / sizeof(int)), &processInfo, &bufsize,
NULL, 0) == 0 &&
bufsize > 0) {
uint32_t pointer_size = 4;
if (processInfo.kp_proc.p_flag & P_LP64)
pointer_size = 8;
std::vector<struct binary_image_information> image_infos;
GetAllLoadedBinariesViaDYLDSPI(image_infos);
const size_t image_count = image_infos.size();
for (size_t i = 0; i < image_count; i++) {
GetMachOInformationFromMemory(image_infos[i].load_address, pointer_size,
image_infos[i].macho_info);
}
return FormatDynamicLibrariesIntoJSON(image_infos);
}
return reply_sp;
}
// Fetch information about the shared libraries at the given load addresses
// using the
// dyld SPIs that exist in macOS 10.12, iOS 10, tvOS 10, watchOS 3 and newer.
JSONGenerator::ObjectSP MachProcess::GetLibrariesInfoForAddresses(
nub_process_t pid, std::vector<uint64_t> &macho_addresses) {
JSONGenerator::DictionarySP reply_sp;
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
struct kinfo_proc processInfo;
size_t bufsize = sizeof(processInfo);
if (sysctl(mib, (unsigned)(sizeof(mib) / sizeof(int)), &processInfo, &bufsize,
NULL, 0) == 0 &&
bufsize > 0) {
uint32_t pointer_size = 4;
if (processInfo.kp_proc.p_flag & P_LP64)
pointer_size = 8;
std::vector<struct binary_image_information> all_image_infos;
GetAllLoadedBinariesViaDYLDSPI(all_image_infos);
std::vector<struct binary_image_information> image_infos;
const size_t macho_addresses_count = macho_addresses.size();
const size_t all_image_infos_count = all_image_infos.size();
for (size_t i = 0; i < macho_addresses_count; i++) {
for (size_t j = 0; j < all_image_infos_count; j++) {
if (all_image_infos[j].load_address == macho_addresses[i]) {
image_infos.push_back(all_image_infos[j]);
}
}
}
const size_t image_infos_count = image_infos.size();
for (size_t i = 0; i < image_infos_count; i++) {
GetMachOInformationFromMemory(image_infos[i].load_address, pointer_size,
image_infos[i].macho_info);
}
return FormatDynamicLibrariesIntoJSON(image_infos);
}
return reply_sp;
}
// From dyld's internal podyld_process_info.h:
JSONGenerator::ObjectSP MachProcess::GetSharedCacheInfo(nub_process_t pid) {
JSONGenerator::DictionarySP reply_sp(new JSONGenerator::Dictionary());
;
kern_return_t kern_ret;
if (m_dyld_process_info_create && m_dyld_process_info_get_cache) {
dyld_process_info info =
m_dyld_process_info_create(m_task.TaskPort(), 0, &kern_ret);
if (info) {
struct dyld_process_cache_info shared_cache_info;
m_dyld_process_info_get_cache(info, &shared_cache_info);
reply_sp->AddIntegerItem("shared_cache_base_address",
shared_cache_info.cacheBaseAddress);
uuid_string_t uuidstr;
uuid_unparse_upper(shared_cache_info.cacheUUID, uuidstr);
reply_sp->AddStringItem("shared_cache_uuid", uuidstr);
reply_sp->AddBooleanItem("no_shared_cache", shared_cache_info.noCache);
reply_sp->AddBooleanItem("shared_cache_private_cache",
shared_cache_info.privateCache);
m_dyld_process_info_release(info);
}
}
return reply_sp;
}
nub_thread_t MachProcess::GetCurrentThread() {
return m_thread_list.CurrentThreadID();
}
nub_thread_t MachProcess::GetCurrentThreadMachPort() {
return m_thread_list.GetMachPortNumberByThreadID(
m_thread_list.CurrentThreadID());
}
nub_thread_t MachProcess::SetCurrentThread(nub_thread_t tid) {
return m_thread_list.SetCurrentThread(tid);
}
bool MachProcess::GetThreadStoppedReason(nub_thread_t tid,
struct DNBThreadStopInfo *stop_info) {
if (m_thread_list.GetThreadStoppedReason(tid, stop_info)) {
if (m_did_exec)
stop_info->reason = eStopTypeExec;
return true;
}
return false;
}
void MachProcess::DumpThreadStoppedReason(nub_thread_t tid) const {
return m_thread_list.DumpThreadStoppedReason(tid);
}
const char *MachProcess::GetThreadInfo(nub_thread_t tid) const {
return m_thread_list.GetThreadInfo(tid);
}
uint32_t MachProcess::GetCPUType() {
if (m_cpu_type == 0 && m_pid != 0)
m_cpu_type = MachProcess::GetCPUTypeForLocalProcess(m_pid);
return m_cpu_type;
}
const DNBRegisterSetInfo *
MachProcess::GetRegisterSetInfo(nub_thread_t tid,
nub_size_t *num_reg_sets) const {
MachThreadSP thread_sp(m_thread_list.GetThreadByID(tid));
if (thread_sp) {
DNBArchProtocol *arch = thread_sp->GetArchProtocol();
if (arch)
return arch->GetRegisterSetInfo(num_reg_sets);
}
*num_reg_sets = 0;
return NULL;
}
bool MachProcess::GetRegisterValue(nub_thread_t tid, uint32_t set, uint32_t reg,
DNBRegisterValue *value) const {
return m_thread_list.GetRegisterValue(tid, set, reg, value);
}
bool MachProcess::SetRegisterValue(nub_thread_t tid, uint32_t set, uint32_t reg,
const DNBRegisterValue *value) const {
return m_thread_list.SetRegisterValue(tid, set, reg, value);
}
void MachProcess::SetState(nub_state_t new_state) {
// If any other threads access this we will need a mutex for it
uint32_t event_mask = 0;
// Scope for mutex locker
{
PTHREAD_MUTEX_LOCKER(locker, m_state_mutex);
const nub_state_t old_state = m_state;
if (old_state == eStateExited) {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::SetState(%s) ignoring new "
"state since current state is exited",
DNBStateAsString(new_state));
} else if (old_state == new_state) {
DNBLogThreadedIf(
LOG_PROCESS,
"MachProcess::SetState(%s) ignoring redundant state change...",
DNBStateAsString(new_state));
} else {
if (NUB_STATE_IS_STOPPED(new_state))
event_mask = eEventProcessStoppedStateChanged;
else
event_mask = eEventProcessRunningStateChanged;
DNBLogThreadedIf(
LOG_PROCESS, "MachProcess::SetState(%s) upating state (previous "
"state was %s), event_mask = 0x%8.8x",
DNBStateAsString(new_state), DNBStateAsString(old_state), event_mask);
m_state = new_state;
if (new_state == eStateStopped)
m_stop_count++;
}
}
if (event_mask != 0) {
m_events.SetEvents(event_mask);
m_private_events.SetEvents(event_mask);
if (event_mask == eEventProcessStoppedStateChanged)
m_private_events.ResetEvents(eEventProcessRunningStateChanged);
else
m_private_events.ResetEvents(eEventProcessStoppedStateChanged);
// Wait for the event bit to reset if a reset ACK is requested
m_events.WaitForResetAck(event_mask);
}
}
void MachProcess::Clear(bool detaching) {
// Clear any cached thread list while the pid and task are still valid
m_task.Clear();
// Now clear out all member variables
m_pid = INVALID_NUB_PROCESS;
if (!detaching)
CloseChildFileDescriptors();
m_path.clear();
m_args.clear();
SetState(eStateUnloaded);
m_flags = eMachProcessFlagsNone;
m_stop_count = 0;
m_thread_list.Clear();
{
PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex);
m_exception_messages.clear();
}
m_activities.Clear();
if (m_profile_thread) {
pthread_join(m_profile_thread, NULL);
m_profile_thread = NULL;
}
}
bool MachProcess::StartSTDIOThread() {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s ( )", __FUNCTION__);
// Create the thread that watches for the child STDIO
return ::pthread_create(&m_stdio_thread, NULL, MachProcess::STDIOThread,
this) == 0;
}
void MachProcess::SetEnableAsyncProfiling(bool enable, uint64_t interval_usec,
DNBProfileDataScanType scan_type) {
m_profile_enabled = enable;
m_profile_interval_usec = static_cast<useconds_t>(interval_usec);
m_profile_scan_type = scan_type;
if (m_profile_enabled && (m_profile_thread == NULL)) {
StartProfileThread();
} else if (!m_profile_enabled && m_profile_thread) {
pthread_join(m_profile_thread, NULL);
m_profile_thread = NULL;
}
}
bool MachProcess::StartProfileThread() {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s ( )", __FUNCTION__);
// Create the thread that profiles the inferior and reports back if enabled
return ::pthread_create(&m_profile_thread, NULL, MachProcess::ProfileThread,
this) == 0;
}
nub_addr_t MachProcess::LookupSymbol(const char *name, const char *shlib) {
if (m_name_to_addr_callback != NULL && name && name[0])
return m_name_to_addr_callback(ProcessID(), name, shlib,
m_name_to_addr_baton);
return INVALID_NUB_ADDRESS;
}
bool MachProcess::Resume(const DNBThreadResumeActions &thread_actions) {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Resume ()");
nub_state_t state = GetState();
if (CanResume(state)) {
m_thread_actions = thread_actions;
PrivateResume();
return true;
} else if (state == eStateRunning) {
DNBLog("Resume() - task 0x%x is already running, ignoring...",
m_task.TaskPort());
return true;
}
DNBLog("Resume() - task 0x%x has state %s, can't continue...",
m_task.TaskPort(), DNBStateAsString(state));
return false;
}
bool MachProcess::Kill(const struct timespec *timeout_abstime) {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Kill ()");
nub_state_t state = DoSIGSTOP(true, false, NULL);
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Kill() DoSIGSTOP() state = %s",
DNBStateAsString(state));
errno = 0;
DNBLog("Sending ptrace PT_KILL to terminate inferior process.");
::ptrace(PT_KILL, m_pid, 0, 0);
DNBError err;
err.SetErrorToErrno();
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Kill() DoSIGSTOP() ::ptrace "
"(PT_KILL, pid=%u, 0, 0) => 0x%8.8x (%s)",
m_pid, err.Status(), err.AsString());
m_thread_actions = DNBThreadResumeActions(eStateRunning, 0);
PrivateResume();
// Try and reap the process without touching our m_events since
// we want the code above this to still get the eStateExited event
const uint32_t reap_timeout_usec =
1000000; // Wait 1 second and try to reap the process
const uint32_t reap_interval_usec = 10000; //
uint32_t reap_time_elapsed;
for (reap_time_elapsed = 0; reap_time_elapsed < reap_timeout_usec;
reap_time_elapsed += reap_interval_usec) {
if (GetState() == eStateExited)
break;
usleep(reap_interval_usec);
}
DNBLog("Waited %u ms for process to be reaped (state = %s)",
reap_time_elapsed / 1000, DNBStateAsString(GetState()));
return true;
}
bool MachProcess::Interrupt() {
nub_state_t state = GetState();
if (IsRunning(state)) {
if (m_sent_interrupt_signo == 0) {
m_sent_interrupt_signo = SIGSTOP;
if (Signal(m_sent_interrupt_signo)) {
DNBLogThreadedIf(
LOG_PROCESS,
"MachProcess::Interrupt() - sent %i signal to interrupt process",
m_sent_interrupt_signo);
return true;
} else {
m_sent_interrupt_signo = 0;
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Interrupt() - failed to "
"send %i signal to interrupt process",
m_sent_interrupt_signo);
}
} else {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Interrupt() - previously "
"sent an interrupt signal %i that hasn't "
"been received yet, interrupt aborted",
m_sent_interrupt_signo);
}
} else {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Interrupt() - process already "
"stopped, no interrupt sent");
}
return false;
}
bool MachProcess::Signal(int signal, const struct timespec *timeout_abstime) {
DNBLogThreadedIf(LOG_PROCESS,
"MachProcess::Signal (signal = %d, timeout = %p)", signal,
reinterpret_cast<const void *>(timeout_abstime));
nub_state_t state = GetState();
if (::kill(ProcessID(), signal) == 0) {
// If we were running and we have a timeout, wait for the signal to stop
if (IsRunning(state) && timeout_abstime) {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Signal (signal = %d, timeout "
"= %p) waiting for signal to stop "
"process...",
signal, reinterpret_cast<const void *>(timeout_abstime));
m_private_events.WaitForSetEvents(eEventProcessStoppedStateChanged,
timeout_abstime);
state = GetState();
DNBLogThreadedIf(
LOG_PROCESS,
"MachProcess::Signal (signal = %d, timeout = %p) state = %s", signal,
reinterpret_cast<const void *>(timeout_abstime),
DNBStateAsString(state));
return !IsRunning(state);
}
DNBLogThreadedIf(
LOG_PROCESS,
"MachProcess::Signal (signal = %d, timeout = %p) not waiting...",
signal, reinterpret_cast<const void *>(timeout_abstime));
return true;
}
DNBError err(errno, DNBError::POSIX);
err.LogThreadedIfError("kill (pid = %d, signo = %i)", ProcessID(), signal);
return false;
}
bool MachProcess::SendEvent(const char *event, DNBError &send_err) {
DNBLogThreadedIf(LOG_PROCESS,
"MachProcess::SendEvent (event = %s) to pid: %d", event,
m_pid);
if (m_pid == INVALID_NUB_PROCESS)
return false;
// FIXME: Shouldn't we use the launch flavor we were started with?
#if defined(WITH_FBS) || defined(WITH_BKS)
return BoardServiceSendEvent(event, send_err);
#endif
return true;
}
nub_state_t MachProcess::DoSIGSTOP(bool clear_bps_and_wps, bool allow_running,
uint32_t *thread_idx_ptr) {
nub_state_t state = GetState();
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::DoSIGSTOP() state = %s",
DNBStateAsString(state));
if (!IsRunning(state)) {
if (clear_bps_and_wps) {
DisableAllBreakpoints(true);
DisableAllWatchpoints(true);
clear_bps_and_wps = false;
}
// If we already have a thread stopped due to a SIGSTOP, we don't have
// to do anything...
uint32_t thread_idx =
m_thread_list.GetThreadIndexForThreadStoppedWithSignal(SIGSTOP);
if (thread_idx_ptr)
*thread_idx_ptr = thread_idx;
if (thread_idx != UINT32_MAX)
return GetState();
// No threads were stopped with a SIGSTOP, we need to run and halt the
// process with a signal
DNBLogThreadedIf(LOG_PROCESS,
"MachProcess::DoSIGSTOP() state = %s -- resuming process",
DNBStateAsString(state));
if (allow_running)
m_thread_actions = DNBThreadResumeActions(eStateRunning, 0);
else
m_thread_actions = DNBThreadResumeActions(eStateSuspended, 0);
PrivateResume();
// Reset the event that says we were indeed running
m_events.ResetEvents(eEventProcessRunningStateChanged);
state = GetState();
}
// We need to be stopped in order to be able to detach, so we need
// to send ourselves a SIGSTOP
DNBLogThreadedIf(LOG_PROCESS,
"MachProcess::DoSIGSTOP() state = %s -- sending SIGSTOP",
DNBStateAsString(state));
struct timespec sigstop_timeout;
DNBTimer::OffsetTimeOfDay(&sigstop_timeout, 2, 0);
Signal(SIGSTOP, &sigstop_timeout);
if (clear_bps_and_wps) {
DisableAllBreakpoints(true);
DisableAllWatchpoints(true);
// clear_bps_and_wps = false;
}
uint32_t thread_idx =
m_thread_list.GetThreadIndexForThreadStoppedWithSignal(SIGSTOP);
if (thread_idx_ptr)
*thread_idx_ptr = thread_idx;
return GetState();
}
bool MachProcess::Detach() {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Detach()");
uint32_t thread_idx = UINT32_MAX;
nub_state_t state = DoSIGSTOP(true, true, &thread_idx);
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::Detach() DoSIGSTOP() returned %s",
DNBStateAsString(state));
{
m_thread_actions.Clear();
m_activities.Clear();
DNBThreadResumeAction thread_action;
thread_action.tid = m_thread_list.ThreadIDAtIndex(thread_idx);
thread_action.state = eStateRunning;
thread_action.signal = -1;
thread_action.addr = INVALID_NUB_ADDRESS;
m_thread_actions.Append(thread_action);
m_thread_actions.SetDefaultThreadActionIfNeeded(eStateRunning, 0);
PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex);
ReplyToAllExceptions();
}
m_task.ShutDownExcecptionThread();
// Detach from our process
errno = 0;
nub_process_t pid = m_pid;
int ret = ::ptrace(PT_DETACH, pid, (caddr_t)1, 0);
DNBError err(errno, DNBError::POSIX);
if (DNBLogCheckLogBit(LOG_PROCESS) || err.Fail() || (ret != 0))
err.LogThreaded("::ptrace (PT_DETACH, %u, (caddr_t)1, 0)", pid);
// Resume our task
m_task.Resume();
- // NULL our task out as we have already retored all exception ports
+ // NULL our task out as we have already restored all exception ports
m_task.Clear();
// Clear out any notion of the process we once were
const bool detaching = true;
Clear(detaching);
SetState(eStateDetached);
return true;
}
//----------------------------------------------------------------------
// ReadMemory from the MachProcess level will always remove any software
// breakpoints from the memory buffer before returning. If you wish to
// read memory and see those traps, read from the MachTask
// (m_task.ReadMemory()) as that version will give you what is actually
// in inferior memory.
//----------------------------------------------------------------------
nub_size_t MachProcess::ReadMemory(nub_addr_t addr, nub_size_t size,
void *buf) {
// We need to remove any current software traps (enabled software
// breakpoints) that we may have placed in our tasks memory.
// First just read the memory as is
nub_size_t bytes_read = m_task.ReadMemory(addr, size, buf);
// Then place any opcodes that fall into this range back into the buffer
// before we return this to callers.
if (bytes_read > 0)
m_breakpoints.RemoveTrapsFromBuffer(addr, bytes_read, buf);
return bytes_read;
}
//----------------------------------------------------------------------
// WriteMemory from the MachProcess level will always write memory around
// any software breakpoints. Any software breakpoints will have their
// opcodes modified if they are enabled. Any memory that doesn't overlap
// with software breakpoints will be written to. If you wish to write to
// inferior memory without this interference, then write to the MachTask
// (m_task.WriteMemory()) as that version will always modify inferior
// memory.
//----------------------------------------------------------------------
nub_size_t MachProcess::WriteMemory(nub_addr_t addr, nub_size_t size,
const void *buf) {
// We need to write any data that would go where any current software traps
// (enabled software breakpoints) any software traps (breakpoints) that we
// may have placed in our tasks memory.
std::vector<DNBBreakpoint *> bps;
const size_t num_bps =
m_breakpoints.FindBreakpointsThatOverlapRange(addr, size, bps);
if (num_bps == 0)
return m_task.WriteMemory(addr, size, buf);
nub_size_t bytes_written = 0;
nub_addr_t intersect_addr;
nub_size_t intersect_size;
nub_size_t opcode_offset;
const uint8_t *ubuf = (const uint8_t *)buf;
for (size_t i = 0; i < num_bps; ++i) {
DNBBreakpoint *bp = bps[i];
const bool intersects = bp->IntersectsRange(
addr, size, &intersect_addr, &intersect_size, &opcode_offset);
UNUSED_IF_ASSERT_DISABLED(intersects);
assert(intersects);
assert(addr <= intersect_addr && intersect_addr < addr + size);
assert(addr < intersect_addr + intersect_size &&
intersect_addr + intersect_size <= addr + size);
assert(opcode_offset + intersect_size <= bp->ByteSize());
// Check for bytes before this breakpoint
const nub_addr_t curr_addr = addr + bytes_written;
if (intersect_addr > curr_addr) {
// There are some bytes before this breakpoint that we need to
// just write to memory
nub_size_t curr_size = intersect_addr - curr_addr;
nub_size_t curr_bytes_written =
m_task.WriteMemory(curr_addr, curr_size, ubuf + bytes_written);
bytes_written += curr_bytes_written;
if (curr_bytes_written != curr_size) {
// We weren't able to write all of the requested bytes, we
// are done looping and will return the number of bytes that
// we have written so far.
break;
}
}
// Now write any bytes that would cover up any software breakpoints
// directly into the breakpoint opcode buffer
::memcpy(bp->SavedOpcodeBytes() + opcode_offset, ubuf + bytes_written,
intersect_size);
bytes_written += intersect_size;
}
// Write any remaining bytes after the last breakpoint if we have any left
if (bytes_written < size)
bytes_written += m_task.WriteMemory(
addr + bytes_written, size - bytes_written, ubuf + bytes_written);
return bytes_written;
}
void MachProcess::ReplyToAllExceptions() {
PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex);
if (m_exception_messages.empty() == false) {
MachException::Message::iterator pos;
MachException::Message::iterator begin = m_exception_messages.begin();
MachException::Message::iterator end = m_exception_messages.end();
for (pos = begin; pos != end; ++pos) {
DNBLogThreadedIf(LOG_EXCEPTIONS, "Replying to exception %u...",
(uint32_t)std::distance(begin, pos));
int thread_reply_signal = 0;
nub_thread_t tid =
m_thread_list.GetThreadIDByMachPortNumber(pos->state.thread_port);
const DNBThreadResumeAction *action = NULL;
if (tid != INVALID_NUB_THREAD) {
action = m_thread_actions.GetActionForThread(tid, false);
}
if (action) {
thread_reply_signal = action->signal;
if (thread_reply_signal)
m_thread_actions.SetSignalHandledForThread(tid);
}
DNBError err(pos->Reply(this, thread_reply_signal));
if (DNBLogCheckLogBit(LOG_EXCEPTIONS))
err.LogThreadedIfError("Error replying to exception");
}
// Erase all exception message as we should have used and replied
// to them all already.
m_exception_messages.clear();
}
}
void MachProcess::PrivateResume() {
PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex);
m_auto_resume_signo = m_sent_interrupt_signo;
if (m_auto_resume_signo)
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::PrivateResume() - task 0x%x "
"resuming (with unhandled interrupt signal "
"%i)...",
m_task.TaskPort(), m_auto_resume_signo);
else
DNBLogThreadedIf(LOG_PROCESS,
"MachProcess::PrivateResume() - task 0x%x resuming...",
m_task.TaskPort());
ReplyToAllExceptions();
// bool stepOverBreakInstruction = step;
// Let the thread prepare to resume and see if any threads want us to
// step over a breakpoint instruction (ProcessWillResume will modify
// the value of stepOverBreakInstruction).
m_thread_list.ProcessWillResume(this, m_thread_actions);
// Set our state accordingly
if (m_thread_actions.NumActionsWithState(eStateStepping))
SetState(eStateStepping);
else
SetState(eStateRunning);
// Now resume our task.
m_task.Resume();
}
DNBBreakpoint *MachProcess::CreateBreakpoint(nub_addr_t addr, nub_size_t length,
bool hardware) {
DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::CreateBreakpoint ( addr = "
"0x%8.8llx, length = %llu, hardware = %i)",
(uint64_t)addr, (uint64_t)length, hardware);
DNBBreakpoint *bp = m_breakpoints.FindByAddress(addr);
if (bp)
bp->Retain();
else
bp = m_breakpoints.Add(addr, length, hardware);
if (EnableBreakpoint(addr)) {
DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::CreateBreakpoint ( addr = "
"0x%8.8llx, length = %llu) => %p",
(uint64_t)addr, (uint64_t)length,
reinterpret_cast<void *>(bp));
return bp;
} else if (bp->Release() == 0) {
m_breakpoints.Remove(addr);
}
// We failed to enable the breakpoint
return NULL;
}
DNBBreakpoint *MachProcess::CreateWatchpoint(nub_addr_t addr, nub_size_t length,
uint32_t watch_flags,
bool hardware) {
DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::CreateWatchpoint ( addr = "
"0x%8.8llx, length = %llu, flags = "
"0x%8.8x, hardware = %i)",
(uint64_t)addr, (uint64_t)length, watch_flags, hardware);
DNBBreakpoint *wp = m_watchpoints.FindByAddress(addr);
// since the Z packets only send an address, we can only have one watchpoint
// at
// an address. If there is already one, we must refuse to create another
// watchpoint
if (wp)
return NULL;
wp = m_watchpoints.Add(addr, length, hardware);
wp->SetIsWatchpoint(watch_flags);
if (EnableWatchpoint(addr)) {
DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::CreateWatchpoint ( addr = "
"0x%8.8llx, length = %llu) => %p",
(uint64_t)addr, (uint64_t)length,
reinterpret_cast<void *>(wp));
return wp;
} else {
DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::CreateWatchpoint ( addr = "
"0x%8.8llx, length = %llu) => FAILED",
(uint64_t)addr, (uint64_t)length);
m_watchpoints.Remove(addr);
}
// We failed to enable the watchpoint
return NULL;
}
void MachProcess::DisableAllBreakpoints(bool remove) {
DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::%s (remove = %d )",
__FUNCTION__, remove);
m_breakpoints.DisableAllBreakpoints(this);
if (remove)
m_breakpoints.RemoveDisabled();
}
void MachProcess::DisableAllWatchpoints(bool remove) {
DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::%s (remove = %d )",
__FUNCTION__, remove);
m_watchpoints.DisableAllWatchpoints(this);
if (remove)
m_watchpoints.RemoveDisabled();
}
bool MachProcess::DisableBreakpoint(nub_addr_t addr, bool remove) {
DNBBreakpoint *bp = m_breakpoints.FindByAddress(addr);
if (bp) {
// After "exec" we might end up with a bunch of breakpoints that were
// disabled
// manually, just ignore them
if (!bp->IsEnabled()) {
// Breakpoint might have been disabled by an exec
if (remove && bp->Release() == 0) {
m_thread_list.NotifyBreakpointChanged(bp);
m_breakpoints.Remove(addr);
}
return true;
}
// We have multiple references to this breakpoint, decrement the ref count
// and if it isn't zero, then return true;
if (remove && bp->Release() > 0)
return true;
DNBLogThreadedIf(
LOG_BREAKPOINTS | LOG_VERBOSE,
"MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, remove = %d )",
(uint64_t)addr, remove);
if (bp->IsHardware()) {
bool hw_disable_result = m_thread_list.DisableHardwareBreakpoint(bp);
if (hw_disable_result == true) {
bp->SetEnabled(false);
// Let the thread list know that a breakpoint has been modified
if (remove) {
m_thread_list.NotifyBreakpointChanged(bp);
m_breakpoints.Remove(addr);
}
DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::DisableBreakpoint ( "
"addr = 0x%8.8llx, remove = %d ) "
"(hardware) => success",
(uint64_t)addr, remove);
return true;
}
return false;
}
const nub_size_t break_op_size = bp->ByteSize();
assert(break_op_size > 0);
const uint8_t *const break_op =
DNBArchProtocol::GetBreakpointOpcode(bp->ByteSize());
if (break_op_size > 0) {
// Clear a software breakpoint instruction
uint8_t curr_break_op[break_op_size];
bool break_op_found = false;
// Read the breakpoint opcode
if (m_task.ReadMemory(addr, break_op_size, curr_break_op) ==
break_op_size) {
bool verify = false;
if (bp->IsEnabled()) {
- // Make sure we have the a breakpoint opcode exists at this address
+ // Make sure a breakpoint opcode exists at this address
if (memcmp(curr_break_op, break_op, break_op_size) == 0) {
break_op_found = true;
// We found a valid breakpoint opcode at this address, now restore
// the saved opcode.
if (m_task.WriteMemory(addr, break_op_size,
bp->SavedOpcodeBytes()) == break_op_size) {
verify = true;
} else {
DNBLogError("MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, "
"remove = %d ) memory write failed when restoring "
"original opcode",
(uint64_t)addr, remove);
}
} else {
DNBLogWarning("MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, "
"remove = %d ) expected a breakpoint opcode but "
"didn't find one.",
(uint64_t)addr, remove);
// Set verify to true and so we can check if the original opcode has
// already been restored
verify = true;
}
} else {
DNBLogThreadedIf(LOG_BREAKPOINTS | LOG_VERBOSE,
"MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, "
"remove = %d ) is not enabled",
(uint64_t)addr, remove);
// Set verify to true and so we can check if the original opcode is
// there
verify = true;
}
if (verify) {
uint8_t verify_opcode[break_op_size];
// Verify that our original opcode made it back to the inferior
if (m_task.ReadMemory(addr, break_op_size, verify_opcode) ==
break_op_size) {
// compare the memory we just read with the original opcode
if (memcmp(bp->SavedOpcodeBytes(), verify_opcode, break_op_size) ==
0) {
// SUCCESS
bp->SetEnabled(false);
// Let the thread list know that a breakpoint has been modified
if (remove && bp->Release() == 0) {
m_thread_list.NotifyBreakpointChanged(bp);
m_breakpoints.Remove(addr);
}
DNBLogThreadedIf(LOG_BREAKPOINTS,
"MachProcess::DisableBreakpoint ( addr = "
"0x%8.8llx, remove = %d ) => success",
(uint64_t)addr, remove);
return true;
} else {
if (break_op_found)
DNBLogError("MachProcess::DisableBreakpoint ( addr = "
"0x%8.8llx, remove = %d ) : failed to restore "
"original opcode",
(uint64_t)addr, remove);
else
DNBLogError("MachProcess::DisableBreakpoint ( addr = "
"0x%8.8llx, remove = %d ) : opcode changed",
(uint64_t)addr, remove);
}
} else {
DNBLogWarning("MachProcess::DisableBreakpoint: unable to disable "
"breakpoint 0x%8.8llx",
(uint64_t)addr);
}
}
} else {
DNBLogWarning("MachProcess::DisableBreakpoint: unable to read memory "
"at 0x%8.8llx",
(uint64_t)addr);
}
}
} else {
DNBLogError("MachProcess::DisableBreakpoint ( addr = 0x%8.8llx, remove = "
"%d ) invalid breakpoint address",
(uint64_t)addr, remove);
}
return false;
}
bool MachProcess::DisableWatchpoint(nub_addr_t addr, bool remove) {
DNBLogThreadedIf(LOG_WATCHPOINTS,
"MachProcess::%s(addr = 0x%8.8llx, remove = %d)",
__FUNCTION__, (uint64_t)addr, remove);
DNBBreakpoint *wp = m_watchpoints.FindByAddress(addr);
if (wp) {
// If we have multiple references to a watchpoint, removing the watchpoint
// shouldn't clear it
if (remove && wp->Release() > 0)
return true;
nub_addr_t addr = wp->Address();
DNBLogThreadedIf(
LOG_WATCHPOINTS,
"MachProcess::DisableWatchpoint ( addr = 0x%8.8llx, remove = %d )",
(uint64_t)addr, remove);
if (wp->IsHardware()) {
bool hw_disable_result = m_thread_list.DisableHardwareWatchpoint(wp);
if (hw_disable_result == true) {
wp->SetEnabled(false);
if (remove)
m_watchpoints.Remove(addr);
DNBLogThreadedIf(LOG_WATCHPOINTS, "MachProcess::Disablewatchpoint ( "
"addr = 0x%8.8llx, remove = %d ) "
"(hardware) => success",
(uint64_t)addr, remove);
return true;
}
}
// TODO: clear software watchpoints if we implement them
} else {
DNBLogError("MachProcess::DisableWatchpoint ( addr = 0x%8.8llx, remove = "
"%d ) invalid watchpoint ID",
(uint64_t)addr, remove);
}
return false;
}
uint32_t MachProcess::GetNumSupportedHardwareWatchpoints() const {
return m_thread_list.NumSupportedHardwareWatchpoints();
}
bool MachProcess::EnableBreakpoint(nub_addr_t addr) {
DNBLogThreadedIf(LOG_BREAKPOINTS,
"MachProcess::EnableBreakpoint ( addr = 0x%8.8llx )",
(uint64_t)addr);
DNBBreakpoint *bp = m_breakpoints.FindByAddress(addr);
if (bp) {
if (bp->IsEnabled()) {
DNBLogWarning("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ): "
"breakpoint already enabled.",
(uint64_t)addr);
return true;
} else {
if (bp->HardwarePreferred()) {
bp->SetHardwareIndex(m_thread_list.EnableHardwareBreakpoint(bp));
if (bp->IsHardware()) {
bp->SetEnabled(true);
return true;
}
}
const nub_size_t break_op_size = bp->ByteSize();
assert(break_op_size != 0);
const uint8_t *const break_op =
DNBArchProtocol::GetBreakpointOpcode(break_op_size);
if (break_op_size > 0) {
// Save the original opcode by reading it
if (m_task.ReadMemory(addr, break_op_size, bp->SavedOpcodeBytes()) ==
break_op_size) {
// Write a software breakpoint in place of the original opcode
if (m_task.WriteMemory(addr, break_op_size, break_op) ==
break_op_size) {
uint8_t verify_break_op[4];
if (m_task.ReadMemory(addr, break_op_size, verify_break_op) ==
break_op_size) {
if (memcmp(break_op, verify_break_op, break_op_size) == 0) {
bp->SetEnabled(true);
// Let the thread list know that a breakpoint has been modified
m_thread_list.NotifyBreakpointChanged(bp);
DNBLogThreadedIf(LOG_BREAKPOINTS, "MachProcess::"
"EnableBreakpoint ( addr = "
"0x%8.8llx ) : SUCCESS.",
(uint64_t)addr);
return true;
} else {
DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx "
"): breakpoint opcode verification failed.",
(uint64_t)addr);
}
} else {
DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ): "
"unable to read memory to verify breakpoint opcode.",
(uint64_t)addr);
}
} else {
DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ): "
"unable to write breakpoint opcode to memory.",
(uint64_t)addr);
}
} else {
DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ): "
"unable to read memory at breakpoint address.",
(uint64_t)addr);
}
} else {
DNBLogError("MachProcess::EnableBreakpoint ( addr = 0x%8.8llx ) no "
"software breakpoint opcode for current architecture.",
(uint64_t)addr);
}
}
}
return false;
}
bool MachProcess::EnableWatchpoint(nub_addr_t addr) {
DNBLogThreadedIf(LOG_WATCHPOINTS,
"MachProcess::EnableWatchpoint(addr = 0x%8.8llx)",
(uint64_t)addr);
DNBBreakpoint *wp = m_watchpoints.FindByAddress(addr);
if (wp) {
nub_addr_t addr = wp->Address();
if (wp->IsEnabled()) {
DNBLogWarning("MachProcess::EnableWatchpoint(addr = 0x%8.8llx): "
"watchpoint already enabled.",
(uint64_t)addr);
return true;
} else {
// Currently only try and set hardware watchpoints.
wp->SetHardwareIndex(m_thread_list.EnableHardwareWatchpoint(wp));
if (wp->IsHardware()) {
wp->SetEnabled(true);
return true;
}
// TODO: Add software watchpoints by doing page protection tricks.
}
}
return false;
}
// Called by the exception thread when an exception has been received from
// our process. The exception message is completely filled and the exception
// data has already been copied.
void MachProcess::ExceptionMessageReceived(
const MachException::Message &exceptionMessage) {
PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex);
if (m_exception_messages.empty())
m_task.Suspend();
DNBLogThreadedIf(LOG_EXCEPTIONS, "MachProcess::ExceptionMessageReceived ( )");
// Use a locker to automatically unlock our mutex in case of exceptions
// Add the exception to our internal exception stack
m_exception_messages.push_back(exceptionMessage);
}
task_t MachProcess::ExceptionMessageBundleComplete() {
// We have a complete bundle of exceptions for our child process.
PTHREAD_MUTEX_LOCKER(locker, m_exception_messages_mutex);
DNBLogThreadedIf(LOG_EXCEPTIONS, "%s: %llu exception messages.",
__PRETTY_FUNCTION__, (uint64_t)m_exception_messages.size());
bool auto_resume = false;
if (!m_exception_messages.empty()) {
m_did_exec = false;
// First check for any SIGTRAP and make sure we didn't exec
const task_t task = m_task.TaskPort();
size_t i;
if (m_pid != 0) {
bool received_interrupt = false;
uint32_t num_task_exceptions = 0;
for (i = 0; i < m_exception_messages.size(); ++i) {
if (m_exception_messages[i].state.task_port == task) {
++num_task_exceptions;
const int signo = m_exception_messages[i].state.SoftSignal();
if (signo == SIGTRAP) {
// SIGTRAP could mean that we exec'ed. We need to check the
// dyld all_image_infos.infoArray to see if it is NULL and if
// so, say that we exec'ed.
const nub_addr_t aii_addr = GetDYLDAllImageInfosAddress();
if (aii_addr != INVALID_NUB_ADDRESS) {
const nub_addr_t info_array_count_addr = aii_addr + 4;
uint32_t info_array_count = 0;
if (m_task.ReadMemory(info_array_count_addr, 4,
&info_array_count) == 4) {
if (info_array_count == 0) {
m_did_exec = true;
// Force the task port to update itself in case the task port
// changed after exec
DNBError err;
const task_t old_task = m_task.TaskPort();
const task_t new_task =
m_task.TaskPortForProcessID(err, true);
if (old_task != new_task)
DNBLogThreadedIf(
LOG_PROCESS,
"exec: task changed from 0x%4.4x to 0x%4.4x", old_task,
new_task);
}
} else {
DNBLog("error: failed to read all_image_infos.infoArrayCount "
"from 0x%8.8llx",
(uint64_t)info_array_count_addr);
}
}
break;
} else if (m_sent_interrupt_signo != 0 &&
signo == m_sent_interrupt_signo) {
received_interrupt = true;
}
}
}
if (m_did_exec) {
cpu_type_t process_cpu_type =
MachProcess::GetCPUTypeForLocalProcess(m_pid);
if (m_cpu_type != process_cpu_type) {
DNBLog("arch changed from 0x%8.8x to 0x%8.8x", m_cpu_type,
process_cpu_type);
m_cpu_type = process_cpu_type;
DNBArchProtocol::SetArchitecture(process_cpu_type);
}
m_thread_list.Clear();
m_activities.Clear();
m_breakpoints.DisableAll();
}
if (m_sent_interrupt_signo != 0) {
if (received_interrupt) {
DNBLogThreadedIf(LOG_PROCESS,
"MachProcess::ExceptionMessageBundleComplete(): "
"process successfully interrupted with signal %i",
m_sent_interrupt_signo);
// Mark that we received the interrupt signal
m_sent_interrupt_signo = 0;
// Not check if we had a case where:
// 1 - We called MachProcess::Interrupt() but we stopped for another
// reason
// 2 - We called MachProcess::Resume() (but still haven't gotten the
// interrupt signal)
// 3 - We are now incorrectly stopped because we are handling the
// interrupt signal we missed
// 4 - We might need to resume if we stopped only with the interrupt
// signal that we never handled
if (m_auto_resume_signo != 0) {
// Only auto_resume if we stopped with _only_ the interrupt signal
if (num_task_exceptions == 1) {
auto_resume = true;
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::"
"ExceptionMessageBundleComplete(): "
"auto resuming due to unhandled "
"interrupt signal %i",
m_auto_resume_signo);
}
m_auto_resume_signo = 0;
}
} else {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::"
"ExceptionMessageBundleComplete(): "
"didn't get signal %i after "
"MachProcess::Interrupt()",
m_sent_interrupt_signo);
}
}
}
// Let all threads recover from stopping and do any clean up based
// on the previous thread state (if any).
m_thread_list.ProcessDidStop(this);
m_activities.Clear();
// Let each thread know of any exceptions
for (i = 0; i < m_exception_messages.size(); ++i) {
// Let the thread list figure use the MachProcess to forward all
// exceptions
// on down to each thread.
if (m_exception_messages[i].state.task_port == task)
m_thread_list.NotifyException(m_exception_messages[i].state);
if (DNBLogCheckLogBit(LOG_EXCEPTIONS))
m_exception_messages[i].Dump();
}
if (DNBLogCheckLogBit(LOG_THREAD))
m_thread_list.Dump();
bool step_more = false;
if (m_thread_list.ShouldStop(step_more) && auto_resume == false) {
// Wait for the eEventProcessRunningStateChanged event to be reset
// before changing state to stopped to avoid race condition with
// very fast start/stops
struct timespec timeout;
// DNBTimer::OffsetTimeOfDay(&timeout, 0, 250 * 1000); // Wait for 250
// ms
DNBTimer::OffsetTimeOfDay(&timeout, 1, 0); // Wait for 250 ms
m_events.WaitForEventsToReset(eEventProcessRunningStateChanged, &timeout);
SetState(eStateStopped);
} else {
// Resume without checking our current state.
PrivateResume();
}
} else {
DNBLogThreadedIf(
LOG_EXCEPTIONS, "%s empty exception messages bundle (%llu exceptions).",
__PRETTY_FUNCTION__, (uint64_t)m_exception_messages.size());
}
return m_task.TaskPort();
}
nub_size_t
MachProcess::CopyImageInfos(struct DNBExecutableImageInfo **image_infos,
bool only_changed) {
if (m_image_infos_callback != NULL)
return m_image_infos_callback(ProcessID(), image_infos, only_changed,
m_image_infos_baton);
return 0;
}
void MachProcess::SharedLibrariesUpdated() {
uint32_t event_bits = eEventSharedLibsStateChange;
// Set the shared library event bit to let clients know of shared library
// changes
m_events.SetEvents(event_bits);
// Wait for the event bit to reset if a reset ACK is requested
m_events.WaitForResetAck(event_bits);
}
void MachProcess::SetExitInfo(const char *info) {
if (info && info[0]) {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s(\"%s\")", __FUNCTION__,
info);
m_exit_info.assign(info);
} else {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s(NULL)", __FUNCTION__);
m_exit_info.clear();
}
}
void MachProcess::AppendSTDOUT(char *s, size_t len) {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (<%llu> %s) ...", __FUNCTION__,
(uint64_t)len, s);
PTHREAD_MUTEX_LOCKER(locker, m_stdio_mutex);
m_stdout_data.append(s, len);
m_events.SetEvents(eEventStdioAvailable);
// Wait for the event bit to reset if a reset ACK is requested
m_events.WaitForResetAck(eEventStdioAvailable);
}
size_t MachProcess::GetAvailableSTDOUT(char *buf, size_t buf_size) {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (&%p[%llu]) ...", __FUNCTION__,
reinterpret_cast<void *>(buf), (uint64_t)buf_size);
PTHREAD_MUTEX_LOCKER(locker, m_stdio_mutex);
size_t bytes_available = m_stdout_data.size();
if (bytes_available > 0) {
if (bytes_available > buf_size) {
memcpy(buf, m_stdout_data.data(), buf_size);
m_stdout_data.erase(0, buf_size);
bytes_available = buf_size;
} else {
memcpy(buf, m_stdout_data.data(), bytes_available);
m_stdout_data.clear();
}
}
return bytes_available;
}
nub_addr_t MachProcess::GetDYLDAllImageInfosAddress() {
DNBError err;
return m_task.GetDYLDAllImageInfosAddress(err);
}
size_t MachProcess::GetAvailableSTDERR(char *buf, size_t buf_size) { return 0; }
void *MachProcess::STDIOThread(void *arg) {
MachProcess *proc = (MachProcess *)arg;
DNBLogThreadedIf(LOG_PROCESS,
"MachProcess::%s ( arg = %p ) thread starting...",
__FUNCTION__, arg);
#if defined(__APPLE__)
pthread_setname_np("stdio monitoring thread");
#endif
// We start use a base and more options so we can control if we
// are currently using a timeout on the mach_msg. We do this to get a
// bunch of related exceptions on our exception port so we can process
// then together. When we have multiple threads, we can get an exception
// per thread and they will come in consecutively. The main thread loop
// will start by calling mach_msg to without having the MACH_RCV_TIMEOUT
// flag set in the options, so we will wait forever for an exception on
// our exception port. After we get one exception, we then will use the
// MACH_RCV_TIMEOUT option with a zero timeout to grab all other current
// exceptions for our process. After we have received the last pending
// exception, we will get a timeout which enables us to then notify
// our main thread that we have an exception bundle available. We then wait
// for the main thread to tell this exception thread to start trying to get
// exceptions messages again and we start again with a mach_msg read with
// infinite timeout.
DNBError err;
int stdout_fd = proc->GetStdoutFileDescriptor();
int stderr_fd = proc->GetStderrFileDescriptor();
if (stdout_fd == stderr_fd)
stderr_fd = -1;
while (stdout_fd >= 0 || stderr_fd >= 0) {
::pthread_testcancel();
fd_set read_fds;
FD_ZERO(&read_fds);
if (stdout_fd >= 0)
FD_SET(stdout_fd, &read_fds);
if (stderr_fd >= 0)
FD_SET(stderr_fd, &read_fds);
int nfds = std::max<int>(stdout_fd, stderr_fd) + 1;
int num_set_fds = select(nfds, &read_fds, NULL, NULL, NULL);
DNBLogThreadedIf(LOG_PROCESS,
"select (nfds, &read_fds, NULL, NULL, NULL) => %d",
num_set_fds);
if (num_set_fds < 0) {
int select_errno = errno;
if (DNBLogCheckLogBit(LOG_PROCESS)) {
err.SetError(select_errno, DNBError::POSIX);
err.LogThreadedIfError(
"select (nfds, &read_fds, NULL, NULL, NULL) => %d", num_set_fds);
}
switch (select_errno) {
case EAGAIN: // The kernel was (perhaps temporarily) unable to allocate
// the requested number of file descriptors, or we have
// non-blocking IO
break;
case EBADF: // One of the descriptor sets specified an invalid descriptor.
return NULL;
break;
case EINTR: // A signal was delivered before the time limit expired and
// before any of the selected events occurred.
case EINVAL: // The specified time limit is invalid. One of its components
// is negative or too large.
default: // Other unknown error
break;
}
} else if (num_set_fds == 0) {
} else {
char s[1024];
s[sizeof(s) - 1] = '\0'; // Ensure we have NULL termination
ssize_t bytes_read = 0;
if (stdout_fd >= 0 && FD_ISSET(stdout_fd, &read_fds)) {
do {
bytes_read = ::read(stdout_fd, s, sizeof(s) - 1);
if (bytes_read < 0) {
int read_errno = errno;
DNBLogThreadedIf(LOG_PROCESS,
"read (stdout_fd, ) => %zd errno: %d (%s)",
bytes_read, read_errno, strerror(read_errno));
} else if (bytes_read == 0) {
// EOF...
DNBLogThreadedIf(
LOG_PROCESS,
"read (stdout_fd, ) => %zd (reached EOF for child STDOUT)",
bytes_read);
stdout_fd = -1;
} else if (bytes_read > 0) {
proc->AppendSTDOUT(s, bytes_read);
}
} while (bytes_read > 0);
}
if (stderr_fd >= 0 && FD_ISSET(stderr_fd, &read_fds)) {
do {
bytes_read = ::read(stderr_fd, s, sizeof(s) - 1);
if (bytes_read < 0) {
int read_errno = errno;
DNBLogThreadedIf(LOG_PROCESS,
"read (stderr_fd, ) => %zd errno: %d (%s)",
bytes_read, read_errno, strerror(read_errno));
} else if (bytes_read == 0) {
// EOF...
DNBLogThreadedIf(
LOG_PROCESS,
"read (stderr_fd, ) => %zd (reached EOF for child STDERR)",
bytes_read);
stderr_fd = -1;
} else if (bytes_read > 0) {
proc->AppendSTDOUT(s, bytes_read);
}
} while (bytes_read > 0);
}
}
}
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (%p): thread exiting...",
__FUNCTION__, arg);
return NULL;
}
void MachProcess::SignalAsyncProfileData(const char *info) {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (%s) ...", __FUNCTION__, info);
PTHREAD_MUTEX_LOCKER(locker, m_profile_data_mutex);
m_profile_data.push_back(info);
m_events.SetEvents(eEventProfileDataAvailable);
// Wait for the event bit to reset if a reset ACK is requested
m_events.WaitForResetAck(eEventProfileDataAvailable);
}
size_t MachProcess::GetAsyncProfileData(char *buf, size_t buf_size) {
DNBLogThreadedIf(LOG_PROCESS, "MachProcess::%s (&%p[%llu]) ...", __FUNCTION__,
reinterpret_cast<void *>(buf), (uint64_t)buf_size);
PTHREAD_MUTEX_LOCKER(locker, m_profile_data_mutex);
if (m_profile_data.empty())
return 0;
size_t bytes_available = m_profile_data.front().size();
if (bytes_available > 0) {
if (bytes_available > buf_size) {
memcpy(buf, m_profile_data.front().data(), buf_size);
m_profile_data.front().erase(0, buf_size);
bytes_available = buf_size;
} else {
memcpy(buf, m_profile_data.front().data(), bytes_available);
m_profile_data.erase(m_profile_data.begin());
}
}
return bytes_available;
}
void *MachProcess::ProfileThread(void *arg) {
MachProcess *proc = (MachProcess *)arg;
DNBLogThreadedIf(LOG_PROCESS,
"MachProcess::%s ( arg = %p ) thread starting...",
__FUNCTION__, arg);
#if defined(__APPLE__)
pthread_setname_np("performance profiling thread");
#endif
while (proc->IsProfilingEnabled()) {
nub_state_t state = proc->GetState();
if (state == eStateRunning) {
std::string data =
proc->Task().GetProfileData(proc->GetProfileScanType());
if (!data.empty()) {
proc->SignalAsyncProfileData(data.c_str());
}
} else if ((state == eStateUnloaded) || (state == eStateDetached) ||
(state == eStateUnloaded)) {
// Done. Get out of this thread.
break;
}
// A simple way to set up the profile interval. We can also use select() or
// dispatch timer source if necessary.
usleep(proc->ProfileInterval());
}
return NULL;
}
pid_t MachProcess::AttachForDebug(pid_t pid, char *err_str, size_t err_len) {
// Clear out and clean up from any current state
Clear();
if (pid != 0) {
DNBError err;
// Make sure the process exists...
if (::getpgid(pid) < 0) {
err.SetErrorToErrno();
const char *err_cstr = err.AsString();
::snprintf(err_str, err_len, "%s",
err_cstr ? err_cstr : "No such process");
return INVALID_NUB_PROCESS;
}
SetState(eStateAttaching);
m_pid = pid;
// Let ourselves know we are going to be using SBS or BKS if the correct flag
// bit is set...
#if defined(WITH_FBS) || defined(WITH_BKS)
bool found_app_flavor = false;
#endif
#if defined(WITH_FBS)
if (!found_app_flavor && IsFBSProcess(pid)) {
found_app_flavor = true;
m_flags |= eMachProcessFlagsUsingFBS;
}
#elif defined(WITH_BKS)
if (!found_app_flavor && IsBKSProcess(pid)) {
found_app_flavor = true;
m_flags |= eMachProcessFlagsUsingBKS;
}
#elif defined(WITH_SPRINGBOARD)
if (IsSBProcess(pid))
m_flags |= eMachProcessFlagsUsingSBS;
#endif
if (!m_task.StartExceptionThread(err)) {
const char *err_cstr = err.AsString();
::snprintf(err_str, err_len, "%s",
err_cstr ? err_cstr : "unable to start the exception thread");
DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to pid %d", pid);
m_pid = INVALID_NUB_PROCESS;
return INVALID_NUB_PROCESS;
}
errno = 0;
if (::ptrace(PT_ATTACHEXC, pid, 0, 0))
err.SetError(errno);
else
err.Clear();
if (err.Success()) {
m_flags |= eMachProcessFlagsAttached;
// Sleep a bit to let the exception get received and set our process
// status
// to stopped.
::usleep(250000);
DNBLogThreadedIf(LOG_PROCESS, "successfully attached to pid %d", pid);
return m_pid;
} else {
::snprintf(err_str, err_len, "%s", err.AsString());
DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to pid %d", pid);
}
}
return INVALID_NUB_PROCESS;
}
Genealogy::ThreadActivitySP
MachProcess::GetGenealogyInfoForThread(nub_thread_t tid, bool &timed_out) {
return m_activities.GetGenealogyInfoForThread(m_pid, tid, m_thread_list,
m_task.TaskPort(), timed_out);
}
Genealogy::ProcessExecutableInfoSP
MachProcess::GetGenealogyImageInfo(size_t idx) {
return m_activities.GetProcessExecutableInfosAtIndex(idx);
}
bool MachProcess::GetOSVersionNumbers(uint64_t *major, uint64_t *minor,
uint64_t *patch) {
#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \
(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 101000)
return false;
#else
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSOperatingSystemVersion vers =
[[NSProcessInfo processInfo] operatingSystemVersion];
if (major)
*major = vers.majorVersion;
if (minor)
*minor = vers.minorVersion;
if (patch)
*patch = vers.patchVersion;
[pool drain];
return true;
#endif
}
// Do the process specific setup for attach. If this returns NULL, then there's
// no
// platform specific stuff to be done to wait for the attach. If you get
// non-null,
// pass that token to the CheckForProcess method, and then to
// CleanupAfterAttach.
// Call PrepareForAttach before attaching to a process that has not yet
// launched
// This returns a token that can be passed to CheckForProcess, and to
// CleanupAfterAttach.
// You should call CleanupAfterAttach to free the token, and do whatever other
// cleanup seems good.
const void *MachProcess::PrepareForAttach(const char *path,
nub_launch_flavor_t launch_flavor,
bool waitfor, DNBError &attach_err) {
#if defined(WITH_SPRINGBOARD) || defined(WITH_BKS) || defined(WITH_FBS)
// Tell SpringBoard to halt the next launch of this application on startup.
if (!waitfor)
return NULL;
const char *app_ext = strstr(path, ".app");
const bool is_app =
app_ext != NULL && (app_ext[4] == '\0' || app_ext[4] == '/');
if (!is_app) {
DNBLogThreadedIf(
LOG_PROCESS,
"MachProcess::PrepareForAttach(): path '%s' doesn't contain .app, "
"we can't tell springboard to wait for launch...",
path);
return NULL;
}
#if defined(WITH_FBS)
if (launch_flavor == eLaunchFlavorDefault)
launch_flavor = eLaunchFlavorFBS;
if (launch_flavor != eLaunchFlavorFBS)
return NULL;
#elif defined(WITH_BKS)
if (launch_flavor == eLaunchFlavorDefault)
launch_flavor = eLaunchFlavorBKS;
if (launch_flavor != eLaunchFlavorBKS)
return NULL;
#elif defined(WITH_SPRINGBOARD)
if (launch_flavor == eLaunchFlavorDefault)
launch_flavor = eLaunchFlavorSpringBoard;
if (launch_flavor != eLaunchFlavorSpringBoard)
return NULL;
#endif
std::string app_bundle_path(path, app_ext + strlen(".app"));
CFStringRef bundleIDCFStr =
CopyBundleIDForPath(app_bundle_path.c_str(), attach_err);
std::string bundleIDStr;
CFString::UTF8(bundleIDCFStr, bundleIDStr);
DNBLogThreadedIf(LOG_PROCESS,
"CopyBundleIDForPath (%s, err_str) returned @\"%s\"",
app_bundle_path.c_str(), bundleIDStr.c_str());
if (bundleIDCFStr == NULL) {
return NULL;
}
#if defined(WITH_FBS)
if (launch_flavor == eLaunchFlavorFBS) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *stdio_path = nil;
NSFileManager *file_manager = [NSFileManager defaultManager];
const char *null_path = "/dev/null";
stdio_path =
[file_manager stringWithFileSystemRepresentation:null_path
length:strlen(null_path)];
NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
NSMutableDictionary *options = [NSMutableDictionary dictionary];
DNBLogThreadedIf(LOG_PROCESS, "Calling BKSSystemService openApplication: "
"@\"%s\",options include stdio path: \"%s\", "
"BKSDebugOptionKeyDebugOnNextLaunch & "
"BKSDebugOptionKeyWaitForDebugger )",
bundleIDStr.c_str(), null_path);
[debug_options setObject:stdio_path
forKey:FBSDebugOptionKeyStandardOutPath];
[debug_options setObject:stdio_path
forKey:FBSDebugOptionKeyStandardErrorPath];
[debug_options setObject:[NSNumber numberWithBool:YES]
forKey:FBSDebugOptionKeyWaitForDebugger];
[debug_options setObject:[NSNumber numberWithBool:YES]
forKey:FBSDebugOptionKeyDebugOnNextLaunch];
[options setObject:debug_options
forKey:FBSOpenApplicationOptionKeyDebuggingOptions];
FBSSystemService *system_service = [[FBSSystemService alloc] init];
mach_port_t client_port = [system_service createClientPort];
__block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block FBSOpenApplicationErrorCode attach_error_code =
FBSOpenApplicationErrorCodeNone;
NSString *bundleIDNSStr = (NSString *)bundleIDCFStr;
[system_service openApplication:bundleIDNSStr
options:options
clientPort:client_port
withResult:^(NSError *error) {
// The system service will cleanup the client port we
// created for us.
if (error)
attach_error_code =
(FBSOpenApplicationErrorCode)[error code];
[system_service release];
dispatch_semaphore_signal(semaphore);
}];
const uint32_t timeout_secs = 9;
dispatch_time_t timeout =
dispatch_time(DISPATCH_TIME_NOW, timeout_secs * NSEC_PER_SEC);
long success = dispatch_semaphore_wait(semaphore, timeout) == 0;
if (!success) {
DNBLogError("timed out trying to launch %s.", bundleIDStr.c_str());
attach_err.SetErrorString(
"debugserver timed out waiting for openApplication to complete.");
attach_err.SetError(OPEN_APPLICATION_TIMEOUT_ERROR, DNBError::Generic);
} else if (attach_error_code != FBSOpenApplicationErrorCodeNone) {
SetFBSError(attach_error_code, attach_err);
DNBLogError("unable to launch the application with CFBundleIdentifier "
"'%s' bks_error = %ld",
bundleIDStr.c_str(), (NSInteger)attach_error_code);
}
dispatch_release(semaphore);
[pool drain];
}
#endif
#if defined(WITH_BKS)
if (launch_flavor == eLaunchFlavorBKS) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *stdio_path = nil;
NSFileManager *file_manager = [NSFileManager defaultManager];
const char *null_path = "/dev/null";
stdio_path =
[file_manager stringWithFileSystemRepresentation:null_path
length:strlen(null_path)];
NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
NSMutableDictionary *options = [NSMutableDictionary dictionary];
DNBLogThreadedIf(LOG_PROCESS, "Calling BKSSystemService openApplication: "
"@\"%s\",options include stdio path: \"%s\", "
"BKSDebugOptionKeyDebugOnNextLaunch & "
"BKSDebugOptionKeyWaitForDebugger )",
bundleIDStr.c_str(), null_path);
[debug_options setObject:stdio_path
forKey:BKSDebugOptionKeyStandardOutPath];
[debug_options setObject:stdio_path
forKey:BKSDebugOptionKeyStandardErrorPath];
[debug_options setObject:[NSNumber numberWithBool:YES]
forKey:BKSDebugOptionKeyWaitForDebugger];
[debug_options setObject:[NSNumber numberWithBool:YES]
forKey:BKSDebugOptionKeyDebugOnNextLaunch];
[options setObject:debug_options
forKey:BKSOpenApplicationOptionKeyDebuggingOptions];
BKSSystemService *system_service = [[BKSSystemService alloc] init];
mach_port_t client_port = [system_service createClientPort];
__block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block BKSOpenApplicationErrorCode attach_error_code =
BKSOpenApplicationErrorCodeNone;
NSString *bundleIDNSStr = (NSString *)bundleIDCFStr;
[system_service openApplication:bundleIDNSStr
options:options
clientPort:client_port
withResult:^(NSError *error) {
// The system service will cleanup the client port we
// created for us.
if (error)
attach_error_code =
(BKSOpenApplicationErrorCode)[error code];
[system_service release];
dispatch_semaphore_signal(semaphore);
}];
const uint32_t timeout_secs = 9;
dispatch_time_t timeout =
dispatch_time(DISPATCH_TIME_NOW, timeout_secs * NSEC_PER_SEC);
long success = dispatch_semaphore_wait(semaphore, timeout) == 0;
if (!success) {
DNBLogError("timed out trying to launch %s.", bundleIDStr.c_str());
attach_err.SetErrorString(
"debugserver timed out waiting for openApplication to complete.");
attach_err.SetError(OPEN_APPLICATION_TIMEOUT_ERROR, DNBError::Generic);
} else if (attach_error_code != BKSOpenApplicationErrorCodeNone) {
SetBKSError(attach_error_code, attach_err);
DNBLogError("unable to launch the application with CFBundleIdentifier "
"'%s' bks_error = %ld",
bundleIDStr.c_str(), attach_error_code);
}
dispatch_release(semaphore);
[pool drain];
}
#endif
#if defined(WITH_SPRINGBOARD)
if (launch_flavor == eLaunchFlavorSpringBoard) {
SBSApplicationLaunchError sbs_error = 0;
const char *stdout_err = "/dev/null";
CFString stdio_path;
stdio_path.SetFileSystemRepresentation(stdout_err);
DNBLogThreadedIf(LOG_PROCESS, "SBSLaunchApplicationForDebugging ( @\"%s\" "
", NULL, NULL, NULL, @\"%s\", @\"%s\", "
"SBSApplicationDebugOnNextLaunch | "
"SBSApplicationLaunchWaitForDebugger )",
bundleIDStr.c_str(), stdout_err, stdout_err);
sbs_error = SBSLaunchApplicationForDebugging(
bundleIDCFStr,
(CFURLRef)NULL, // openURL
NULL, // launch_argv.get(),
NULL, // launch_envp.get(), // CFDictionaryRef environment
stdio_path.get(), stdio_path.get(),
SBSApplicationDebugOnNextLaunch | SBSApplicationLaunchWaitForDebugger);
if (sbs_error != SBSApplicationLaunchErrorSuccess) {
attach_err.SetError(sbs_error, DNBError::SpringBoard);
return NULL;
}
}
#endif // WITH_SPRINGBOARD
DNBLogThreadedIf(LOG_PROCESS, "Successfully set DebugOnNextLaunch.");
return bundleIDCFStr;
#else // !(defined (WITH_SPRINGBOARD) || defined (WITH_BKS) || defined
// (WITH_FBS))
return NULL;
#endif
}
// Pass in the token you got from PrepareForAttach. If there is a process
// for that token, then the pid will be returned, otherwise INVALID_NUB_PROCESS
// will be returned.
nub_process_t MachProcess::CheckForProcess(const void *attach_token,
nub_launch_flavor_t launch_flavor) {
if (attach_token == NULL)
return INVALID_NUB_PROCESS;
#if defined(WITH_FBS)
if (launch_flavor == eLaunchFlavorFBS) {
NSString *bundleIDNSStr = (NSString *)attach_token;
FBSSystemService *systemService = [[FBSSystemService alloc] init];
pid_t pid = [systemService pidForApplication:bundleIDNSStr];
[systemService release];
if (pid == 0)
return INVALID_NUB_PROCESS;
else
return pid;
}
#endif
#if defined(WITH_BKS)
if (launch_flavor == eLaunchFlavorBKS) {
NSString *bundleIDNSStr = (NSString *)attach_token;
BKSSystemService *systemService = [[BKSSystemService alloc] init];
pid_t pid = [systemService pidForApplication:bundleIDNSStr];
[systemService release];
if (pid == 0)
return INVALID_NUB_PROCESS;
else
return pid;
}
#endif
#if defined(WITH_SPRINGBOARD)
if (launch_flavor == eLaunchFlavorSpringBoard) {
CFStringRef bundleIDCFStr = (CFStringRef)attach_token;
Boolean got_it;
nub_process_t attach_pid;
got_it = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &attach_pid);
if (got_it)
return attach_pid;
else
return INVALID_NUB_PROCESS;
}
#endif
return INVALID_NUB_PROCESS;
}
// Call this to clean up after you have either attached or given up on the
// attach.
// Pass true for success if you have attached, false if you have not.
// The token will also be freed at this point, so you can't use it after calling
// this method.
void MachProcess::CleanupAfterAttach(const void *attach_token,
nub_launch_flavor_t launch_flavor,
bool success, DNBError &err_str) {
if (attach_token == NULL)
return;
#if defined(WITH_FBS)
if (launch_flavor == eLaunchFlavorFBS) {
if (!success) {
FBSCleanupAfterAttach(attach_token, err_str);
}
CFRelease((CFStringRef)attach_token);
}
#endif
#if defined(WITH_BKS)
if (launch_flavor == eLaunchFlavorBKS) {
if (!success) {
BKSCleanupAfterAttach(attach_token, err_str);
}
CFRelease((CFStringRef)attach_token);
}
#endif
#if defined(WITH_SPRINGBOARD)
// Tell SpringBoard to cancel the debug on next launch of this application
// if we failed to attach
if (launch_flavor == eMachProcessFlagsUsingSpringBoard) {
if (!success) {
SBSApplicationLaunchError sbs_error = 0;
CFStringRef bundleIDCFStr = (CFStringRef)attach_token;
sbs_error = SBSLaunchApplicationForDebugging(
bundleIDCFStr, (CFURLRef)NULL, NULL, NULL, NULL, NULL,
SBSApplicationCancelDebugOnNextLaunch);
if (sbs_error != SBSApplicationLaunchErrorSuccess) {
err_str.SetError(sbs_error, DNBError::SpringBoard);
return;
}
}
CFRelease((CFStringRef)attach_token);
}
#endif
}
pid_t MachProcess::LaunchForDebug(
const char *path, char const *argv[], char const *envp[],
const char *working_directory, // NULL => don't change, non-NULL => set
// working directory for inferior to this
const char *stdin_path, const char *stdout_path, const char *stderr_path,
bool no_stdio, nub_launch_flavor_t launch_flavor, int disable_aslr,
const char *event_data, DNBError &launch_err) {
// Clear out and clean up from any current state
Clear();
DNBLogThreadedIf(LOG_PROCESS,
"%s( path = '%s', argv = %p, envp = %p, "
"launch_flavor = %u, disable_aslr = %d )",
__FUNCTION__, path, reinterpret_cast<const void *>(argv),
reinterpret_cast<const void *>(envp), launch_flavor,
disable_aslr);
// Fork a child process for debugging
SetState(eStateLaunching);
switch (launch_flavor) {
case eLaunchFlavorForkExec:
m_pid = MachProcess::ForkChildForPTraceDebugging(path, argv, envp, this,
launch_err);
break;
#ifdef WITH_FBS
case eLaunchFlavorFBS: {
const char *app_ext = strstr(path, ".app");
if (app_ext && (app_ext[4] == '\0' || app_ext[4] == '/')) {
std::string app_bundle_path(path, app_ext + strlen(".app"));
m_flags |= eMachProcessFlagsUsingFBS;
if (BoardServiceLaunchForDebug(app_bundle_path.c_str(), argv, envp,
no_stdio, disable_aslr, event_data,
launch_err) != 0)
return m_pid; // A successful SBLaunchForDebug() returns and assigns a
// non-zero m_pid.
else
break; // We tried a FBS launch, but didn't succeed lets get out
}
} break;
#endif
#ifdef WITH_BKS
case eLaunchFlavorBKS: {
const char *app_ext = strstr(path, ".app");
if (app_ext && (app_ext[4] == '\0' || app_ext[4] == '/')) {
std::string app_bundle_path(path, app_ext + strlen(".app"));
m_flags |= eMachProcessFlagsUsingBKS;
if (BoardServiceLaunchForDebug(app_bundle_path.c_str(), argv, envp,
no_stdio, disable_aslr, event_data,
launch_err) != 0)
return m_pid; // A successful SBLaunchForDebug() returns and assigns a
// non-zero m_pid.
else
break; // We tried a BKS launch, but didn't succeed lets get out
}
} break;
#endif
#ifdef WITH_SPRINGBOARD
case eLaunchFlavorSpringBoard: {
// .../whatever.app/whatever ?
// Or .../com.apple.whatever.app/whatever -- be careful of ".app" in
// "com.apple.whatever" here
const char *app_ext = strstr(path, ".app/");
if (app_ext == NULL) {
// .../whatever.app ?
int len = strlen(path);
if (len > 5) {
if (strcmp(path + len - 4, ".app") == 0) {
app_ext = path + len - 4;
}
}
}
if (app_ext) {
std::string app_bundle_path(path, app_ext + strlen(".app"));
if (SBLaunchForDebug(app_bundle_path.c_str(), argv, envp, no_stdio,
disable_aslr, launch_err) != 0)
return m_pid; // A successful SBLaunchForDebug() returns and assigns a
// non-zero m_pid.
else
break; // We tried a springboard launch, but didn't succeed lets get out
}
} break;
#endif
case eLaunchFlavorPosixSpawn:
m_pid = MachProcess::PosixSpawnChildForPTraceDebugging(
path, DNBArchProtocol::GetArchitecture(), argv, envp, working_directory,
stdin_path, stdout_path, stderr_path, no_stdio, this, disable_aslr,
launch_err);
break;
default:
// Invalid launch
launch_err.SetError(NUB_GENERIC_ERROR, DNBError::Generic);
return INVALID_NUB_PROCESS;
}
if (m_pid == INVALID_NUB_PROCESS) {
// If we don't have a valid process ID and no one has set the error,
// then return a generic error
if (launch_err.Success())
launch_err.SetError(NUB_GENERIC_ERROR, DNBError::Generic);
} else {
m_path = path;
size_t i;
char const *arg;
for (i = 0; (arg = argv[i]) != NULL; i++)
m_args.push_back(arg);
m_task.StartExceptionThread(launch_err);
if (launch_err.Fail()) {
if (launch_err.AsString() == NULL)
launch_err.SetErrorString("unable to start the exception thread");
DNBLog("Could not get inferior's Mach exception port, sending ptrace "
"PT_KILL and exiting.");
::ptrace(PT_KILL, m_pid, 0, 0);
m_pid = INVALID_NUB_PROCESS;
return INVALID_NUB_PROCESS;
}
StartSTDIOThread();
if (launch_flavor == eLaunchFlavorPosixSpawn) {
SetState(eStateAttaching);
errno = 0;
int err = ::ptrace(PT_ATTACHEXC, m_pid, 0, 0);
if (err == 0) {
m_flags |= eMachProcessFlagsAttached;
DNBLogThreadedIf(LOG_PROCESS, "successfully spawned pid %d", m_pid);
launch_err.Clear();
} else {
SetState(eStateExited);
DNBError ptrace_err(errno, DNBError::POSIX);
DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to spawned pid "
"%d (err = %i, errno = %i (%s))",
m_pid, err, ptrace_err.Status(),
ptrace_err.AsString());
launch_err.SetError(NUB_GENERIC_ERROR, DNBError::Generic);
}
} else {
launch_err.Clear();
}
}
return m_pid;
}
pid_t MachProcess::PosixSpawnChildForPTraceDebugging(
const char *path, cpu_type_t cpu_type, char const *argv[],
char const *envp[], const char *working_directory, const char *stdin_path,
const char *stdout_path, const char *stderr_path, bool no_stdio,
MachProcess *process, int disable_aslr, DNBError &err) {
posix_spawnattr_t attr;
short flags;
DNBLogThreadedIf(LOG_PROCESS, "%s ( path='%s', argv=%p, envp=%p, "
"working_dir=%s, stdin=%s, stdout=%s "
"stderr=%s, no-stdio=%i)",
__FUNCTION__, path, reinterpret_cast<const void *>(argv),
reinterpret_cast<const void *>(envp), working_directory,
stdin_path, stdout_path, stderr_path, no_stdio);
err.SetError(::posix_spawnattr_init(&attr), DNBError::POSIX);
if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
err.LogThreaded("::posix_spawnattr_init ( &attr )");
if (err.Fail())
return INVALID_NUB_PROCESS;
flags = POSIX_SPAWN_START_SUSPENDED | POSIX_SPAWN_SETSIGDEF |
POSIX_SPAWN_SETSIGMASK;
if (disable_aslr)
flags |= _POSIX_SPAWN_DISABLE_ASLR;
sigset_t no_signals;
sigset_t all_signals;
sigemptyset(&no_signals);
sigfillset(&all_signals);
::posix_spawnattr_setsigmask(&attr, &no_signals);
::posix_spawnattr_setsigdefault(&attr, &all_signals);
err.SetError(::posix_spawnattr_setflags(&attr, flags), DNBError::POSIX);
if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
err.LogThreaded(
"::posix_spawnattr_setflags ( &attr, POSIX_SPAWN_START_SUSPENDED%s )",
flags & _POSIX_SPAWN_DISABLE_ASLR ? " | _POSIX_SPAWN_DISABLE_ASLR"
: "");
if (err.Fail())
return INVALID_NUB_PROCESS;
// Don't do this on SnowLeopard, _sometimes_ the TASK_BASIC_INFO will fail
// and we will fail to continue with our process...
// On SnowLeopard we should set "DYLD_NO_PIE" in the inferior environment....
#if !defined(__arm__)
// We don't need to do this for ARM, and we really shouldn't now that we
// have multiple CPU subtypes and no posix_spawnattr call that allows us
// to set which CPU subtype to launch...
if (cpu_type != 0) {
size_t ocount = 0;
err.SetError(::posix_spawnattr_setbinpref_np(&attr, 1, &cpu_type, &ocount),
DNBError::POSIX);
if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
err.LogThreaded("::posix_spawnattr_setbinpref_np ( &attr, 1, cpu_type = "
"0x%8.8x, count => %llu )",
cpu_type, (uint64_t)ocount);
if (err.Fail() != 0 || ocount != 1)
return INVALID_NUB_PROCESS;
}
#endif
PseudoTerminal pty;
posix_spawn_file_actions_t file_actions;
err.SetError(::posix_spawn_file_actions_init(&file_actions), DNBError::POSIX);
int file_actions_valid = err.Success();
if (!file_actions_valid || DNBLogCheckLogBit(LOG_PROCESS))
err.LogThreaded("::posix_spawn_file_actions_init ( &file_actions )");
int pty_error = -1;
pid_t pid = INVALID_NUB_PROCESS;
if (file_actions_valid) {
if (stdin_path == NULL && stdout_path == NULL && stderr_path == NULL &&
!no_stdio) {
pty_error = pty.OpenFirstAvailableMaster(O_RDWR | O_NOCTTY);
if (pty_error == PseudoTerminal::success) {
stdin_path = stdout_path = stderr_path = pty.SlaveName();
}
}
// if no_stdio or std paths not supplied, then route to "/dev/null".
if (no_stdio || stdin_path == NULL || stdin_path[0] == '\0')
stdin_path = "/dev/null";
if (no_stdio || stdout_path == NULL || stdout_path[0] == '\0')
stdout_path = "/dev/null";
if (no_stdio || stderr_path == NULL || stderr_path[0] == '\0')
stderr_path = "/dev/null";
err.SetError(::posix_spawn_file_actions_addopen(&file_actions, STDIN_FILENO,
stdin_path,
O_RDONLY | O_NOCTTY, 0),
DNBError::POSIX);
if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
err.LogThreaded("::posix_spawn_file_actions_addopen (&file_actions, "
"filedes=STDIN_FILENO, path='%s')",
stdin_path);
err.SetError(::posix_spawn_file_actions_addopen(
&file_actions, STDOUT_FILENO, stdout_path,
O_WRONLY | O_NOCTTY | O_CREAT, 0640),
DNBError::POSIX);
if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
err.LogThreaded("::posix_spawn_file_actions_addopen (&file_actions, "
"filedes=STDOUT_FILENO, path='%s')",
stdout_path);
err.SetError(::posix_spawn_file_actions_addopen(
&file_actions, STDERR_FILENO, stderr_path,
O_WRONLY | O_NOCTTY | O_CREAT, 0640),
DNBError::POSIX);
if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
err.LogThreaded("::posix_spawn_file_actions_addopen (&file_actions, "
"filedes=STDERR_FILENO, path='%s')",
stderr_path);
// TODO: Verify if we can set the working directory back immediately
// after the posix_spawnp call without creating a race condition???
if (working_directory)
::chdir(working_directory);
err.SetError(::posix_spawnp(&pid, path, &file_actions, &attr,
const_cast<char *const *>(argv),
const_cast<char *const *>(envp)),
DNBError::POSIX);
if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
err.LogThreaded("::posix_spawnp ( pid => %i, path = '%s', file_actions = "
"%p, attr = %p, argv = %p, envp = %p )",
pid, path, &file_actions, &attr, argv, envp);
} else {
// TODO: Verify if we can set the working directory back immediately
// after the posix_spawnp call without creating a race condition???
if (working_directory)
::chdir(working_directory);
err.SetError(::posix_spawnp(&pid, path, NULL, &attr,
const_cast<char *const *>(argv),
const_cast<char *const *>(envp)),
DNBError::POSIX);
if (err.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
err.LogThreaded("::posix_spawnp ( pid => %i, path = '%s', file_actions = "
"%p, attr = %p, argv = %p, envp = %p )",
pid, path, NULL, &attr, argv, envp);
}
// We have seen some cases where posix_spawnp was returning a valid
// looking pid even when an error was returned, so clear it out
if (err.Fail())
pid = INVALID_NUB_PROCESS;
if (pty_error == 0) {
if (process != NULL) {
int master_fd = pty.ReleaseMasterFD();
process->SetChildFileDescriptors(master_fd, master_fd, master_fd);
}
}
::posix_spawnattr_destroy(&attr);
if (pid != INVALID_NUB_PROCESS) {
cpu_type_t pid_cpu_type = MachProcess::GetCPUTypeForLocalProcess(pid);
DNBLogThreadedIf(LOG_PROCESS,
"MachProcess::%s ( ) pid=%i, cpu_type=0x%8.8x",
__FUNCTION__, pid, pid_cpu_type);
if (pid_cpu_type)
DNBArchProtocol::SetArchitecture(pid_cpu_type);
}
if (file_actions_valid) {
DNBError err2;
err2.SetError(::posix_spawn_file_actions_destroy(&file_actions),
DNBError::POSIX);
if (err2.Fail() || DNBLogCheckLogBit(LOG_PROCESS))
err2.LogThreaded("::posix_spawn_file_actions_destroy ( &file_actions )");
}
return pid;
}
uint32_t MachProcess::GetCPUTypeForLocalProcess(pid_t pid) {
int mib[CTL_MAXNAME] = {
0,
};
size_t len = CTL_MAXNAME;
if (::sysctlnametomib("sysctl.proc_cputype", mib, &len))
return 0;
mib[len] = pid;
len++;
cpu_type_t cpu;
size_t cpu_len = sizeof(cpu);
if (::sysctl(mib, static_cast<u_int>(len), &cpu, &cpu_len, 0, 0))
cpu = 0;
return cpu;
}
pid_t MachProcess::ForkChildForPTraceDebugging(const char *path,
char const *argv[],
char const *envp[],
MachProcess *process,
DNBError &launch_err) {
PseudoTerminal::Status pty_error = PseudoTerminal::success;
// Use a fork that ties the child process's stdin/out/err to a pseudo
// terminal so we can read it in our MachProcess::STDIOThread
// as unbuffered io.
PseudoTerminal pty;
pid_t pid = pty.Fork(pty_error);
if (pid < 0) {
//--------------------------------------------------------------
// Status during fork.
//--------------------------------------------------------------
return pid;
} else if (pid == 0) {
//--------------------------------------------------------------
// Child process
//--------------------------------------------------------------
::ptrace(PT_TRACE_ME, 0, 0, 0); // Debug this process
::ptrace(PT_SIGEXC, 0, 0, 0); // Get BSD signals as mach exceptions
// If our parent is setgid, lets make sure we don't inherit those
// extra powers due to nepotism.
if (::setgid(getgid()) == 0) {
// Let the child have its own process group. We need to execute
// this call in both the child and parent to avoid a race condition
// between the two processes.
::setpgid(0, 0); // Set the child process group to match its pid
// Sleep a bit to before the exec call
::sleep(1);
// Turn this process into
::execv(path, const_cast<char *const *>(argv));
}
// Exit with error code. Child process should have taken
// over in above exec call and if the exec fails it will
// exit the child process below.
::exit(127);
} else {
//--------------------------------------------------------------
// Parent process
//--------------------------------------------------------------
// Let the child have its own process group. We need to execute
// this call in both the child and parent to avoid a race condition
// between the two processes.
::setpgid(pid, pid); // Set the child process group to match its pid
if (process != NULL) {
// Release our master pty file descriptor so the pty class doesn't
// close it and so we can continue to use it in our STDIO thread
int master_fd = pty.ReleaseMasterFD();
process->SetChildFileDescriptors(master_fd, master_fd, master_fd);
}
}
return pid;
}
#if defined(WITH_SPRINGBOARD) || defined(WITH_BKS) || defined(WITH_FBS)
// This returns a CFRetained pointer to the Bundle ID for app_bundle_path,
// or NULL if there was some problem getting the bundle id.
static CFStringRef CopyBundleIDForPath(const char *app_bundle_path,
DNBError &err_str) {
CFBundle bundle(app_bundle_path);
CFStringRef bundleIDCFStr = bundle.GetIdentifier();
std::string bundleID;
if (CFString::UTF8(bundleIDCFStr, bundleID) == NULL) {
struct stat app_bundle_stat;
char err_msg[PATH_MAX];
if (::stat(app_bundle_path, &app_bundle_stat) < 0) {
err_str.SetError(errno, DNBError::POSIX);
snprintf(err_msg, sizeof(err_msg), "%s: \"%s\"", err_str.AsString(),
app_bundle_path);
err_str.SetErrorString(err_msg);
DNBLogThreadedIf(LOG_PROCESS, "%s() error: %s", __FUNCTION__, err_msg);
} else {
err_str.SetError(-1, DNBError::Generic);
snprintf(err_msg, sizeof(err_msg),
"failed to extract CFBundleIdentifier from %s", app_bundle_path);
err_str.SetErrorString(err_msg);
DNBLogThreadedIf(
LOG_PROCESS,
"%s() error: failed to extract CFBundleIdentifier from '%s'",
__FUNCTION__, app_bundle_path);
}
return NULL;
}
DNBLogThreadedIf(LOG_PROCESS, "%s() extracted CFBundleIdentifier: %s",
__FUNCTION__, bundleID.c_str());
CFRetain(bundleIDCFStr);
return bundleIDCFStr;
}
#endif // #if defined (WITH_SPRINGBOARD) || defined (WITH_BKS) || defined
// (WITH_FBS)
#ifdef WITH_SPRINGBOARD
pid_t MachProcess::SBLaunchForDebug(const char *path, char const *argv[],
char const *envp[], bool no_stdio,
bool disable_aslr, DNBError &launch_err) {
// Clear out and clean up from any current state
Clear();
DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv)", __FUNCTION__, path);
// Fork a child process for debugging
SetState(eStateLaunching);
m_pid = MachProcess::SBForkChildForPTraceDebugging(path, argv, envp, no_stdio,
this, launch_err);
if (m_pid != 0) {
m_flags |= eMachProcessFlagsUsingSBS;
m_path = path;
size_t i;
char const *arg;
for (i = 0; (arg = argv[i]) != NULL; i++)
m_args.push_back(arg);
m_task.StartExceptionThread(launch_err);
if (launch_err.Fail()) {
if (launch_err.AsString() == NULL)
launch_err.SetErrorString("unable to start the exception thread");
DNBLog("Could not get inferior's Mach exception port, sending ptrace "
"PT_KILL and exiting.");
::ptrace(PT_KILL, m_pid, 0, 0);
m_pid = INVALID_NUB_PROCESS;
return INVALID_NUB_PROCESS;
}
StartSTDIOThread();
SetState(eStateAttaching);
int err = ::ptrace(PT_ATTACHEXC, m_pid, 0, 0);
if (err == 0) {
m_flags |= eMachProcessFlagsAttached;
DNBLogThreadedIf(LOG_PROCESS, "successfully attached to pid %d", m_pid);
} else {
SetState(eStateExited);
DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to pid %d", m_pid);
}
}
return m_pid;
}
#include <servers/bootstrap.h>
pid_t MachProcess::SBForkChildForPTraceDebugging(
const char *app_bundle_path, char const *argv[], char const *envp[],
bool no_stdio, MachProcess *process, DNBError &launch_err) {
DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv, %p)", __FUNCTION__,
app_bundle_path, process);
CFAllocatorRef alloc = kCFAllocatorDefault;
if (argv[0] == NULL)
return INVALID_NUB_PROCESS;
size_t argc = 0;
// Count the number of arguments
while (argv[argc] != NULL)
argc++;
// Enumerate the arguments
size_t first_launch_arg_idx = 1;
CFReleaser<CFMutableArrayRef> launch_argv;
if (argv[first_launch_arg_idx]) {
size_t launch_argc = argc > 0 ? argc - 1 : 0;
launch_argv.reset(
::CFArrayCreateMutable(alloc, launch_argc, &kCFTypeArrayCallBacks));
size_t i;
char const *arg;
CFString launch_arg;
for (i = first_launch_arg_idx; (i < argc) && ((arg = argv[i]) != NULL);
i++) {
launch_arg.reset(
::CFStringCreateWithCString(alloc, arg, kCFStringEncodingUTF8));
if (launch_arg.get() != NULL)
CFArrayAppendValue(launch_argv.get(), launch_arg.get());
else
break;
}
}
// Next fill in the arguments dictionary. Note, the envp array is of the form
// Variable=value but SpringBoard wants a CF dictionary. So we have to
// convert
// this here.
CFReleaser<CFMutableDictionaryRef> launch_envp;
if (envp[0]) {
launch_envp.reset(
::CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks));
const char *value;
int name_len;
CFString name_string, value_string;
for (int i = 0; envp[i] != NULL; i++) {
value = strstr(envp[i], "=");
// If the name field is empty or there's no =, skip it. Somebody's
// messing with us.
if (value == NULL || value == envp[i])
continue;
name_len = value - envp[i];
// Now move value over the "="
value++;
name_string.reset(
::CFStringCreateWithBytes(alloc, (const UInt8 *)envp[i], name_len,
kCFStringEncodingUTF8, false));
value_string.reset(
::CFStringCreateWithCString(alloc, value, kCFStringEncodingUTF8));
CFDictionarySetValue(launch_envp.get(), name_string.get(),
value_string.get());
}
}
CFString stdio_path;
PseudoTerminal pty;
if (!no_stdio) {
PseudoTerminal::Status pty_err =
pty.OpenFirstAvailableMaster(O_RDWR | O_NOCTTY);
if (pty_err == PseudoTerminal::success) {
const char *slave_name = pty.SlaveName();
DNBLogThreadedIf(LOG_PROCESS,
"%s() successfully opened master pty, slave is %s",
__FUNCTION__, slave_name);
if (slave_name && slave_name[0]) {
::chmod(slave_name, S_IRWXU | S_IRWXG | S_IRWXO);
stdio_path.SetFileSystemRepresentation(slave_name);
}
}
}
if (stdio_path.get() == NULL) {
stdio_path.SetFileSystemRepresentation("/dev/null");
}
CFStringRef bundleIDCFStr = CopyBundleIDForPath(app_bundle_path, launch_err);
if (bundleIDCFStr == NULL)
return INVALID_NUB_PROCESS;
// This is just for logging:
std::string bundleID;
CFString::UTF8(bundleIDCFStr, bundleID);
DNBLogThreadedIf(LOG_PROCESS, "%s() serialized launch arg array",
__FUNCTION__);
// Find SpringBoard
SBSApplicationLaunchError sbs_error = 0;
sbs_error = SBSLaunchApplicationForDebugging(
bundleIDCFStr,
(CFURLRef)NULL, // openURL
launch_argv.get(),
launch_envp.get(), // CFDictionaryRef environment
stdio_path.get(), stdio_path.get(),
SBSApplicationLaunchWaitForDebugger | SBSApplicationLaunchUnlockDevice);
launch_err.SetError(sbs_error, DNBError::SpringBoard);
if (sbs_error == SBSApplicationLaunchErrorSuccess) {
static const useconds_t pid_poll_interval = 200000;
static const useconds_t pid_poll_timeout = 30000000;
useconds_t pid_poll_total = 0;
nub_process_t pid = INVALID_NUB_PROCESS;
Boolean pid_found = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &pid);
// Poll until the process is running, as long as we are getting valid
// responses and the timeout hasn't expired
// A return PID of 0 means the process is not running, which may be because
// it hasn't been (asynchronously) started
// yet, or that it died very quickly (if you weren't using waitForDebugger).
while (!pid_found && pid_poll_total < pid_poll_timeout) {
usleep(pid_poll_interval);
pid_poll_total += pid_poll_interval;
DNBLogThreadedIf(LOG_PROCESS,
"%s() polling Springboard for pid for %s...",
__FUNCTION__, bundleID.c_str());
pid_found = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &pid);
}
CFRelease(bundleIDCFStr);
if (pid_found) {
if (process != NULL) {
// Release our master pty file descriptor so the pty class doesn't
// close it and so we can continue to use it in our STDIO thread
int master_fd = pty.ReleaseMasterFD();
process->SetChildFileDescriptors(master_fd, master_fd, master_fd);
}
DNBLogThreadedIf(LOG_PROCESS, "%s() => pid = %4.4x", __FUNCTION__, pid);
} else {
DNBLogError("failed to lookup the process ID for CFBundleIdentifier %s.",
bundleID.c_str());
}
return pid;
}
DNBLogError("unable to launch the application with CFBundleIdentifier '%s' "
"sbs_error = %u",
bundleID.c_str(), sbs_error);
return INVALID_NUB_PROCESS;
}
#endif // #ifdef WITH_SPRINGBOARD
#if defined(WITH_BKS) || defined(WITH_FBS)
pid_t MachProcess::BoardServiceLaunchForDebug(
const char *path, char const *argv[], char const *envp[], bool no_stdio,
bool disable_aslr, const char *event_data, DNBError &launch_err) {
DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv)", __FUNCTION__, path);
// Fork a child process for debugging
SetState(eStateLaunching);
m_pid = BoardServiceForkChildForPTraceDebugging(
path, argv, envp, no_stdio, disable_aslr, event_data, launch_err);
if (m_pid != 0) {
m_path = path;
size_t i;
char const *arg;
for (i = 0; (arg = argv[i]) != NULL; i++)
m_args.push_back(arg);
m_task.StartExceptionThread(launch_err);
if (launch_err.Fail()) {
if (launch_err.AsString() == NULL)
launch_err.SetErrorString("unable to start the exception thread");
DNBLog("Could not get inferior's Mach exception port, sending ptrace "
"PT_KILL and exiting.");
::ptrace(PT_KILL, m_pid, 0, 0);
m_pid = INVALID_NUB_PROCESS;
return INVALID_NUB_PROCESS;
}
StartSTDIOThread();
SetState(eStateAttaching);
int err = ::ptrace(PT_ATTACHEXC, m_pid, 0, 0);
if (err == 0) {
m_flags |= eMachProcessFlagsAttached;
DNBLogThreadedIf(LOG_PROCESS, "successfully attached to pid %d", m_pid);
} else {
SetState(eStateExited);
DNBLogThreadedIf(LOG_PROCESS, "error: failed to attach to pid %d", m_pid);
}
}
return m_pid;
}
pid_t MachProcess::BoardServiceForkChildForPTraceDebugging(
const char *app_bundle_path, char const *argv[], char const *envp[],
bool no_stdio, bool disable_aslr, const char *event_data,
DNBError &launch_err) {
if (argv[0] == NULL)
return INVALID_NUB_PROCESS;
DNBLogThreadedIf(LOG_PROCESS, "%s( '%s', argv, %p)", __FUNCTION__,
app_bundle_path, this);
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
size_t argc = 0;
// Count the number of arguments
while (argv[argc] != NULL)
argc++;
// Enumerate the arguments
size_t first_launch_arg_idx = 1;
NSMutableArray *launch_argv = nil;
if (argv[first_launch_arg_idx]) {
size_t launch_argc = argc > 0 ? argc - 1 : 0;
launch_argv = [NSMutableArray arrayWithCapacity:launch_argc];
size_t i;
char const *arg;
NSString *launch_arg;
for (i = first_launch_arg_idx; (i < argc) && ((arg = argv[i]) != NULL);
i++) {
launch_arg = [NSString stringWithUTF8String:arg];
// FIXME: Should we silently eat an argument that we can't convert into a
// UTF8 string?
if (launch_arg != nil)
[launch_argv addObject:launch_arg];
else
break;
}
}
NSMutableDictionary *launch_envp = nil;
if (envp[0]) {
launch_envp = [[NSMutableDictionary alloc] init];
const char *value;
int name_len;
NSString *name_string, *value_string;
for (int i = 0; envp[i] != NULL; i++) {
value = strstr(envp[i], "=");
// If the name field is empty or there's no =, skip it. Somebody's
// messing with us.
if (value == NULL || value == envp[i])
continue;
name_len = value - envp[i];
// Now move value over the "="
value++;
name_string = [[NSString alloc] initWithBytes:envp[i]
length:name_len
encoding:NSUTF8StringEncoding];
value_string = [NSString stringWithUTF8String:value];
[launch_envp setObject:value_string forKey:name_string];
}
}
NSString *stdio_path = nil;
NSFileManager *file_manager = [NSFileManager defaultManager];
PseudoTerminal pty;
if (!no_stdio) {
PseudoTerminal::Status pty_err =
pty.OpenFirstAvailableMaster(O_RDWR | O_NOCTTY);
if (pty_err == PseudoTerminal::success) {
const char *slave_name = pty.SlaveName();
DNBLogThreadedIf(LOG_PROCESS,
"%s() successfully opened master pty, slave is %s",
__FUNCTION__, slave_name);
if (slave_name && slave_name[0]) {
::chmod(slave_name, S_IRWXU | S_IRWXG | S_IRWXO);
stdio_path = [file_manager
stringWithFileSystemRepresentation:slave_name
length:strlen(slave_name)];
}
}
}
if (stdio_path == nil) {
const char *null_path = "/dev/null";
stdio_path =
[file_manager stringWithFileSystemRepresentation:null_path
length:strlen(null_path)];
}
CFStringRef bundleIDCFStr = CopyBundleIDForPath(app_bundle_path, launch_err);
if (bundleIDCFStr == NULL) {
[pool drain];
return INVALID_NUB_PROCESS;
}
// Instead of rewriting CopyBundleIDForPath for NSStrings, we'll just use
// toll-free bridging here:
NSString *bundleIDNSStr = (NSString *)bundleIDCFStr;
// Okay, now let's assemble all these goodies into the BackBoardServices
// options mega-dictionary:
NSMutableDictionary *options = nullptr;
pid_t return_pid = INVALID_NUB_PROCESS;
bool success = false;
#ifdef WITH_BKS
if (m_flags & eMachProcessFlagsUsingBKS) {
options =
BKSCreateOptionsDictionary(app_bundle_path, launch_argv, launch_envp,
stdio_path, disable_aslr, event_data);
success = BKSCallOpenApplicationFunction(bundleIDNSStr, options, launch_err,
&return_pid);
}
#endif
#ifdef WITH_FBS
if (m_flags & eMachProcessFlagsUsingFBS) {
options =
FBSCreateOptionsDictionary(app_bundle_path, launch_argv, launch_envp,
stdio_path, disable_aslr, event_data);
success = FBSCallOpenApplicationFunction(bundleIDNSStr, options, launch_err,
&return_pid);
}
#endif
if (success) {
int master_fd = pty.ReleaseMasterFD();
SetChildFileDescriptors(master_fd, master_fd, master_fd);
CFString::UTF8(bundleIDCFStr, m_bundle_id);
}
[pool drain];
return return_pid;
}
bool MachProcess::BoardServiceSendEvent(const char *event_data,
DNBError &send_err) {
bool return_value = true;
if (event_data == NULL || *event_data == '\0') {
DNBLogError("SendEvent called with NULL event data.");
send_err.SetErrorString("SendEvent called with empty event data");
return false;
}
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (strcmp(event_data, "BackgroundApplication") == 0) {
// This is an event I cooked up. What you actually do is foreground the system
// app, so:
#ifdef WITH_BKS
if (m_flags & eMachProcessFlagsUsingBKS) {
return_value = BKSCallOpenApplicationFunction(nil, nil, send_err, NULL);
}
#endif
#ifdef WITH_FBS
if (m_flags & eMachProcessFlagsUsingFBS) {
return_value = FBSCallOpenApplicationFunction(nil, nil, send_err, NULL);
}
#endif
if (!return_value) {
DNBLogError("Failed to background application, error: %s.",
send_err.AsString());
}
} else {
if (m_bundle_id.empty()) {
// See if we can figure out the bundle ID for this PID:
DNBLogError(
"Tried to send event \"%s\" to a process that has no bundle ID.",
event_data);
return false;
}
NSString *bundleIDNSStr =
[NSString stringWithUTF8String:m_bundle_id.c_str()];
NSMutableDictionary *options = [NSMutableDictionary dictionary];
#ifdef WITH_BKS
if (m_flags & eMachProcessFlagsUsingBKS) {
if (!BKSAddEventDataToOptions(options, event_data, send_err)) {
[pool drain];
return false;
}
return_value = BKSCallOpenApplicationFunction(bundleIDNSStr, options,
send_err, NULL);
DNBLogThreadedIf(LOG_PROCESS,
"Called BKSCallOpenApplicationFunction to send event.");
}
#endif
#ifdef WITH_FBS
if (m_flags & eMachProcessFlagsUsingFBS) {
if (!FBSAddEventDataToOptions(options, event_data, send_err)) {
[pool drain];
return false;
}
return_value = FBSCallOpenApplicationFunction(bundleIDNSStr, options,
send_err, NULL);
DNBLogThreadedIf(LOG_PROCESS,
"Called FBSCallOpenApplicationFunction to send event.");
}
#endif
if (!return_value) {
DNBLogError("Failed to send event: %s, error: %s.", event_data,
send_err.AsString());
}
}
[pool drain];
return return_value;
}
#endif // defined(WITH_BKS) || defined (WITH_FBS)
#ifdef WITH_BKS
void MachProcess::BKSCleanupAfterAttach(const void *attach_token,
DNBError &err_str) {
bool success;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Instead of rewriting CopyBundleIDForPath for NSStrings, we'll just use
// toll-free bridging here:
NSString *bundleIDNSStr = (NSString *)attach_token;
// Okay, now let's assemble all these goodies into the BackBoardServices
// options mega-dictionary:
// First we have the debug sub-dictionary:
NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
[debug_options setObject:[NSNumber numberWithBool:YES]
forKey:BKSDebugOptionKeyCancelDebugOnNextLaunch];
// That will go in the overall dictionary:
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:debug_options
forKey:BKSOpenApplicationOptionKeyDebuggingOptions];
success =
BKSCallOpenApplicationFunction(bundleIDNSStr, options, err_str, NULL);
if (!success) {
DNBLogError("error trying to cancel debug on next launch for %s: %s",
[bundleIDNSStr UTF8String], err_str.AsString());
}
[pool drain];
}
#endif // WITH_BKS
#ifdef WITH_FBS
void MachProcess::FBSCleanupAfterAttach(const void *attach_token,
DNBError &err_str) {
bool success;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Instead of rewriting CopyBundleIDForPath for NSStrings, we'll just use
// toll-free bridging here:
NSString *bundleIDNSStr = (NSString *)attach_token;
// Okay, now let's assemble all these goodies into the BackBoardServices
// options mega-dictionary:
// First we have the debug sub-dictionary:
NSMutableDictionary *debug_options = [NSMutableDictionary dictionary];
[debug_options setObject:[NSNumber numberWithBool:YES]
forKey:FBSDebugOptionKeyCancelDebugOnNextLaunch];
// That will go in the overall dictionary:
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:debug_options
forKey:FBSOpenApplicationOptionKeyDebuggingOptions];
success =
FBSCallOpenApplicationFunction(bundleIDNSStr, options, err_str, NULL);
if (!success) {
DNBLogError("error trying to cancel debug on next launch for %s: %s",
[bundleIDNSStr UTF8String], err_str.AsString());
}
[pool drain];
}
#endif // WITH_FBS
Index: tools/lldb-mi/MIReadMe.txt
===================================================================
--- tools/lldb-mi/MIReadMe.txt (revision 333398)
+++ tools/lldb-mi/MIReadMe.txt (revision 333399)
@@ -1,37 +1,37 @@
========================================================================
The MI Driver - LLDB Machine Interface V2 (MI) Project Overview
========================================================================
The MI Driver is a stand alone executable that either be used via a
client i.e. Eclipse or directly from the command line.
For help information on using the MI driver type at the command line:
lldb-mi --interpreter --help
-A blog about the MI Driver is available on CodePlay's website. ALthough it may not be
+A blog about the MI Driver is available on CodePlay's website. Although it may not be
completely accurate after the recent changes in lldb-mi.
http://www.codeplay.com/portal/lldb-mi-driver---part-1-introduction
In MI mode and invoked with --log option, lldb-mi generates lldb-mi-log.txt
This file keeps a history of the MI Driver's activity for one session. It is
used to aid the debugging of the MI Driver. It also gives warnings about
command's which do not support certain argument or options.
Note any command or text sent to the MI Driver in MI mode that is not a command
-registered in the MI Driver's Command Factory will be rejected and an error messsage
+registered in the MI Driver's Command Factory will be rejected and an error message
will be generated.
All the files prefix with MI are specifically for the MI driver code only.
File MIDriverMain.cpp contains the executables main() function.
=========================================================================
Current limitations:
1. Not all commands and their options have been implemented. Please see
the source code for details.
-2. LLDB-MI may have additinal arguments not used in GDB MI. Please see
-MIExtesnsions.txt
+2. LLDB-MI may have additional arguments not used in GDB MI. Please see
+MIExtensions.txt
=========================================================================
The MI Driver build configuration:
MICmnConfig.h defines various preprocessor build options.
Index: tools/lldb-mi/MICmnResources.cpp
===================================================================
--- tools/lldb-mi/MICmnResources.cpp (revision 333398)
+++ tools/lldb-mi/MICmnResources.cpp (revision 333399)
@@ -1,622 +1,622 @@
//===-- MICmnResources.cpp --------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Third party headers
#include "assert.h"
#include <inttypes.h> // For PRIx64
// In-house headers:
#include "MICmnResources.h"
// Instantiations:
const CMICmnResources::SRsrcTextData
CMICmnResources::ms_pResourceId2TextData[] = {
{IDS_PROJNAME,
"LLDB Machine Interface Driver (MI) All rights reserved"},
{IDS_MI_VERSION_DESCRIPTION_DEBUG,
"Version: 1.0.0.9 (Debug)"}, // See version history in MIDriverMain.cpp
{IDS_MI_VERSION_DESCRIPTION, "Version: 1.0.0.9"},
{IDS_MI_APPNAME_SHORT, "MI"},
{IDS_MI_APPNAME_LONG, "Machine Interface Driver"},
{IDS_MI_APP_FILEPATHNAME, "Application: %s"},
{IDS_MI_APP_ARGS, "Command line args: "},
{IDE_MI_VERSION_GDB, "Version: GNU gdb (GDB) 7.4 \n(This is a MI stub "
"on top of LLDB and not GDB)\nAll rights "
"reserved.\n"}, // *** Eclipse needs this
// exactly!!
{IDS_UTIL_FILE_ERR_INVALID_PATHNAME,
"File Handler. Invalid file name path"},
{IDS_UTIL_FILE_ERR_OPENING_FILE, "File Handler. Error %s opening '%s'"},
{IDS_UTIL_FILE_ERR_OPENING_FILE_UNKNOWN,
"File Handler. Unknown error opening '%s'"},
{IDE_UTIL_FILE_ERR_WRITING_FILE, "File Handler. Error %s writing '%s'"},
{IDE_UTIL_FILE_ERR_WRITING_NOTOPEN,
"File Handler. File '%s' not open for write"},
{IDS_RESOURCES_ERR_STRING_NOT_FOUND,
"Resources. String (%d) not found in resources"},
{IDS_RESOURCES_ERR_STRING_TABLE_INVALID,
"Resources. String resource table is not set up"},
{IDS_MI_CLIENT_MSG, "Client message: \"%s\""},
{IDS_LOG_MSG_CREATION_DATE, "Creation date %s time %s%s"},
{IDS_LOG_MSG_FILE_LOGGER_PATH, "File logger path: %s%s"},
{IDS_LOG_MSG_VERSION, "Version: %s%s"},
{IDS_LOG_ERR_FILE_LOGGER_DISABLED,
"Log. File logger temporarily disabled due to file error '%s'"},
{IDS_LOG_MEDIUM_ERR_INIT, "Log. Medium '%s' initialise failed. %s"},
{IDS_LOG_MEDIUM_ERR_WRITE_ANY,
"Log. Failed to write log data to any medium."},
{IDS_LOG_MEDIUM_ERR_WRITE_MEDIUMFAIL,
"Log. One or mediums failed writing log data."},
{IDS_MEDIUMFILE_NAME, "File"},
{IDS_MEDIUMFILE_ERR_INVALID_PATH, "<Invalid - not set>"},
{IDS_MEDIUMFILE_ERR_FILE_HEADER, "<Invalid - header not set>"},
{IDS_MEDIUMFILE_NAME_LOG, "File medium. %s"},
{IDE_OS_ERR_UNKNOWN, "Unknown OS error"},
{IDE_OS_ERR_RETRIEVING, "Unabled to retrieve OS error message"},
{IDS_DRIVERMGR_DRIVER_ERR_INIT,
"Driver Manager. Driver '%s' (ID:'%s') initialise failed. %s"},
{IDE_MEDIUMSTDERR_NAME, "Stderr"},
{IDE_MEDIUMSTDOUT_NAME, "Stdout"},
{IDE_MI_APP_DESCRIPTION,
"Description:\nThe Machine Interface Driver (MI Driver) is a stand "
"alone executable\nthat either be used via "
"a client i.e. Eclipse or directly from the command\nline. It "
"processes MI commands, actions those commands "
"using the internal\ndebugger then forms MI response formatted text "
"which is returned to the\nclient."},
{IDE_MI_APP_INFORMATION,
"Information:\nCurrent limitations. The MI Driver currently only "
"handles remote target\ndebugging. Local "
"debugging has not been implemented. The MI Driver has\nbeen designed "
"primarily to be used with Eclipse Juno "
"and a custom plugin.\nThe custom plugin is not necessary to operate "
"the MI Driver."},
{IDE_MI_APP_ARG_USAGE, "\nMI driver usage:\n\n\tlldb-mi [--longOption] "
"[-s hortOption] [executeable]\n\n[] = optional "
"argument."},
{IDE_MI_APP_ARG_HELP, "-h\n--help\n\tPrints out usage information for "
"the MI debugger. Exit the MI\n\tDriver "
"immediately."},
{IDE_MI_APP_ARG_VERSION, "--version\n\tPrints out GNU (gdb) version "
"information. Exit the MI "
"Driver\n\timmediately."},
{IDE_MI_APP_ARG_VERSION_LONG, "--versionLong\n\tPrints out MI Driver "
"version information. Exit the MI "
"Driver\n\timmediately."},
{IDE_MI_APP_ARG_INTERPRETER, "--interpreter\n\t This option is kept "
"for backward compatibility. This "
"executable always run in MI mode"},
{IDE_MI_APP_ARG_EXECUTEABLE, "--executable\n\tUse the MI Driver in MI "
"mode for the debugging the specified "
"executable."},
{IDE_MI_APP_ARG_SOURCE, "-s <filename>\n--source <filename>\n\t"
"Tells the debugger to read in and execute the "
"lldb commands in the\n\t"
"given file, after any file provided on the "
"command line has been\n\tloaded."},
{IDE_MI_APP_ARG_APP_LOG, "--log\n\tUse this argument to tell the MI "
"Driver to update it's log\n\tfile '%s'."},
{IDE_MI_APP_ARG_APP_LOG_DIR,
"--log-dir\n\tUse this argument to specify the directory the MI "
"Driver\n\twill place the log file in, i.e --log-dir=/tmp."},
{IDE_MI_APP_ARG_EXAMPLE, "Example MI command:\n\t3-info-gdb-mi-command "
"gdb-set\n\t3^done,command={exists=\"true\"}"},
{IDE_MI_APP_ARG_EXECUTABLE, "executable (NOT IMPLEMENTED)\n\tThe file "
"path to the executable i.e. '\"C:\\My "
"Dev\\foo.exe\"'."},
{IDE_MI_APP_ARG_SYNCHRONOUS, "--synchronous\n\tBlock until each command "
"has finished executing.\n\tUsed for testing only."},
{IDS_STDIN_ERR_INVALID_PROMPT,
"Stdin. Invalid prompt description '%s'"},
{IDS_STDIN_ERR_THREAD_CREATION_FAILED,
"Stdin. Thread creation failed '%s'"},
{IDS_STDIN_ERR_THREAD_DELETE, "Stdin. Thread failed to delete '%s'"},
{IDS_STDIN_ERR_CHKING_BYTE_AVAILABLE,
"Stdin. Peeking on stdin stream '%s'"},
{IDS_STDIN_INPUT_CTRL_CHARS,
"Stdin. Receive characters not handled as a command: "},
{IDS_CMD_QUIT_HELP,
"MI Driver Command: quit\n\tExit the MI Driver application."},
{IDS_THREADMGR_ERR_THREAD_ID_INVALID,
"Thread Mgr. Thread ID '%s' is not valid"},
{IDS_THREADMGR_ERR_THREAD_FAIL_CREATE,
"Thread Mgr: Failed to create thread '%s'"},
{IDS_THREADMGR_ERR_THREAD_ID_NOT_FOUND,
"Thread Mgr: Thread with ID '%s' not found"},
{IDS_THREADMGR_ERR_THREAD_STILL_ALIVE, "Thread Mgr: The thread(s) are "
"still alive at Thread Mgr "
"shutdown: %s"},
{IDS_FALLTHRU_DRIVER_CMD_RECEIVED,
"Fall Thru Driver. Received command '%s'. Is was %shandled"},
{IDS_CMDFACTORY_ERR_INVALID_CMD_NAME,
"Command factory. MI command name '%s' is invalid"},
{IDS_CMDFACTORY_ERR_INVALID_CMD_CR8FN,
"Command factory. Command creation function invalid for command '%s'. "
"Does function exist? Pointer assigned to it?"},
{IDS_CMDFACTORY_ERR_CMD_NOT_REGISTERED,
"Command factory. Command '%s' not registered"},
{IDS_CMDFACTORY_ERR_CMD_ALREADY_REGED,
"Command factory. Command '%s' by that name already registered"},
{IDS_CMDMGR_ERR_CMD_FAILED_CREATE,
"Command manager. Command creation failed. %s"},
{IDS_CMDMGR_ERR_CMD_INVOKER, "Command manager. %s "},
{IDS_MI_INIT_ERR_LOG, "Log. Error occurred during initialisation %s"},
{IDS_MI_INIT_ERR_RESOURCES,
"Resources. Error occurred during initialisation %s"},
{IDS_MI_INIT_ERR_INIT,
"Driver. Error occurred during initialisation %s"},
{IDS_MI_INIT_ERR_STREAMSTDIN,
"Stdin. Error occurred during initialisation %s"},
{IDS_MI_INIT_ERR_STREAMSTDIN_OSHANDLER, "Stdin. The OS specific stdin "
"stream handler has not been "
"specified for this OS"},
{IDS_MI_INIT_ERR_OS_STDIN_HANDLER,
"Stdin handler. Error occurred during initialisation %s"},
{IDS_MI_INIT_ERR_STREAMSTDOUT,
"Stdout. Error occurred during initialisation %s"},
{IDS_MI_INIT_ERR_STREAMSTDERR,
"Stderr. Error occurred during initialisation %s"},
{IDS_MI_INIT_ERR_FALLTHRUDRIVER,
"Fall Through Driver. Error occurred during initialisation %s"},
{IDS_MI_INIT_ERR_THREADMGR,
"Thread Mgr. Error occurred during initialisation %s"},
{IDS_MI_INIT_ERR_CMDINTERPRETER, "Command interpreter. %s"},
{IDS_MI_INIT_ERR_CMDMGR, "Command manager. %s"},
{IDS_MI_INIT_ERR_CMDFACTORY, "Command factory. %s"},
{IDS_MI_INIT_ERR_CMDINVOKER, "Command invoker. %s"},
{IDS_MI_INIT_ERR_CMDMONITOR, "Command monitor. %s"},
{IDS_MI_INIT_ERR_LLDBDEBUGGER, "LLDB Debugger. %s"},
{IDS_MI_INIT_ERR_DRIVERMGR, "Driver manager. %s"},
{IDS_MI_INIT_ERR_DRIVER, "Driver. %s"},
{IDS_MI_INIT_ERR_OUTOFBANDHANDLER, "Out-of-band handler. %s "},
{IDS_MI_INIT_ERR_DEBUGSESSIONINFO, "LLDB debug session info. %s "},
{IDS_MI_INIT_ERR_THREADMANAGER, "Unable to init thread manager."},
{IDS_MI_INIT_ERR_CLIENT_USING_DRIVER,
"Initialising the client to this driver failed."},
{IDS_MI_INIT_ERR_LOCAL_DEBUG_SESSION,
"Initialising a local debug session failed."},
{IDS_CODE_ERR_INVALID_PARAMETER_VALUE,
"Code. Invalid parameter passed to function '%s'"},
{IDS_CODE_ERR_INVALID_PARAM_NULL_POINTER,
"Code. NULL pointer passes as a parameter to function '%s'"},
{IDS_CODE_ERR_INVALID_ENUMERATION_VALUE,
"Code. Invalid enumeration value encountered in function '%s'"},
{
IDS_LLDBDEBUGGER_ERR_INVALIDLISTENER,
"LLDB Debugger. LLDB Listener is not valid",
},
{
IDS_LLDBDEBUGGER_ERR_INVALIDDEBUGGER,
"LLDB Debugger. LLDB Debugger is not valid",
},
{IDS_LLDBDEBUGGER_ERR_CLIENTDRIVER,
"LLDB Debugger. CMIDriverBase derived driver needs to be set prior to "
"CMICmnLLDBDDebugger initialisation"},
{IDS_LLDBDEBUGGER_ERR_STARTLISTENER,
"LLDB Debugger. Starting listening events for '%s' failed"},
{IDS_LLDBDEBUGGER_ERR_THREADCREATIONFAIL,
"LLDB Debugger. Thread creation failed '%s'"},
{IDS_LLDBDEBUGGER_ERR_THREAD_DELETE,
"LLDB Debugger. Thread failed to delete '%s'"},
{IDS_LLDBDEBUGGER_ERR_INVALIDBROADCASTER,
"LLDB Debugger. Invalid SB broadcaster class name '%s' "},
{IDS_LLDBDEBUGGER_ERR_INVALIDCLIENTNAME,
"LLDB Debugger. Invalid client name '%s' "},
{IDS_LLDBDEBUGGER_ERR_CLIENTNOTREGISTERED,
"LLDB Debugger. Client name '%s' not registered for listening events"},
{IDS_LLDBDEBUGGER_ERR_STOPLISTENER, "LLDB Debugger. Failure occurred "
"stopping event for client '%s' "
"SBBroadcaster '%s'"},
{IDS_LLDBDEBUGGER_ERR_BROADCASTER_NAME,
"LLDB Debugger. Broadcaster's name '%s' is not valid"},
{IDS_LLDBDEBUGGER_WRN_UNKNOWN_EVENT,
"LLDB Debugger. Unhandled event '%s'"},
{IDS_LLDBOUTOFBAND_ERR_UNKNOWN_EVENT,
"LLDB Out-of-band. Handling event for '%s', an event enumeration '%d' "
"not recognised"},
{IDS_LLDBOUTOFBAND_ERR_PROCESS_INVALID,
"LLDB Out-of-band. Invalid '%s' in '%s'"},
{IDS_LLDBOUTOFBAND_ERR_BRKPT_NOTFOUND, "LLDB Out-of-band. %s. "
"Breakpoint information for "
"breakpoint ID %d not found"},
{IDS_LLDBOUTOFBAND_ERR_BRKPT_INFO_GET,
"LLDB Out-of-band. %s. Failed to retrieve breakpoint information for "
"for breakpoint ID %d"},
{IDS_LLDBOUTOFBAND_ERR_BRKPT_INFO_SET, "LLDB Out-of-band. %s. Failed "
"to set breakpoint information "
"for for breakpoint ID %d"},
{IDS_LLDBOUTOFBAND_ERR_FORM_MI_RESPONSE,
"LLDB Out-of-band. %s. Failed to form the MI Out-of-band response"},
{IDS_LLDBOUTOFBAND_ERR_FRAME_INFO_GET,
"LLDB Out-of-band. %s. Failed to retrieve frame information"},
{IDS_LLDBOUTOFBAND_ERR_SETNEWDRIVERSTATE,
"LLDB Out-of-band. %s. Event handler tried to set new MI Driver "
"running state and failed. %s"},
{IDS_LLDBOUTOFBAND_ERR_BRKPT_CNT_EXCEEDED,
"LLDB Out-of-band. '%s'. Number of valid breakpoint exceeded %d. "
"Cannot create new breakpoint with ID %d"},
{IDS_DBGSESSION_ERR_SHARED_DATA_ADD, "LLDB debug session info. Failed "
"to add '%s' data to the shared "
"data command container"},
{IDS_MI_SHTDWN_ERR_LOG, "Log. Error occurred during shutdown. %s"},
{IDS_MI_SHUTDOWN_ERR, "Server shutdown failure. %s"},
{IDE_MI_SHTDWN_ERR_RESOURCES,
"Resources. Error occurred during shutdown. %s"},
{IDE_MI_SHTDWN_ERR_STREAMSTDIN,
"Stdin. Error occurred during shutdown. %s"},
{IDS_MI_SHTDWN_ERR_OS_STDIN_HANDLER,
"Stdin handler. Error occurred during shutdown. %s"},
{IDS_MI_SHTDWN_ERR_STREAMSTDOUT,
"Stdout. Error occurred during shutdown. %s"},
{IDS_MI_SHTDWN_ERR_STREAMSTDERR,
"Stderr. Error occurred during shutdown. %s"},
{IDS_MI_SHTDWN_ERR_THREADMGR,
"Thread Mgr. Error occurred during shutdown. %s"},
{IDS_MI_SHTDWN_ERR_CMDINTERPRETER,
"Command interpreter. Error occurred during shutdown. %s"},
{IDS_MI_SHTDWN_ERR_CMDMGR,
"Command manager. Error occurred during shutdown. %s"},
{IDS_MI_SHTDWN_ERR_CMDFACTORY,
"Command factory. Error occurred during shutdown. %s"},
{IDS_MI_SHTDWN_ERR_CMDMONITOR,
"Command invoker. Error occurred during shutdown. %s"},
{IDS_MI_SHTDWN_ERR_CMDINVOKER,
"Command monitor. Error occurred during shutdown. %s"},
{IDS_MI_SHTDWN_ERR_LLDBDEBUGGER,
"LLDB Debugger. Error occurred during shutdown. %s"},
{IDS_MI_SHTDWN_ERR_DRIVERMGR,
"Driver manager. Error occurred during shutdown. %s"},
{IDS_MI_SHTDWN_ERR_DRIVER,
"Driver. Error occurred during shutdown. %s"},
{IDS_MI_SHTDWN_ERR_OUTOFBANDHANDLER,
"Out-of-band handler. Error occurred during shutdown. %s"},
{IDS_MI_SHTDWN_ERR_DEBUGSESSIONINFO,
"LLDB debug session info. Error occurred during shutdown. %s"},
{IDE_MI_SHTDWN_ERR_THREADMANAGER, "Unable to shutdown thread manager"},
{IDS_DRIVER_ERR_PARSE_ARGS,
"Driver. Driver '%s'. Parse args error '%s'"},
{IDS_DRIVER_ERR_PARSE_ARGS_UNKNOWN,
"Driver. Driver '%s'. Parse args error unknown"},
{IDS_DRIVER_ERR_CURRENT_NOT_SET,
"Driver. Current working driver has not been set. Call "
"CMIDriverMgr::SetUseThisDriverToDoWork()"},
{IDS_DRIVER_ERR_NON_REGISTERED, "Driver. No suitable drivers "
"registered with the CMIDriverMgr to "
"do work"},
{IDS_DRIVER_SAY_DRIVER_USING, "Driver. Using driver '%s' internally"},
{IDS_DRIVER_ERR_ID_INVALID, "Driver. Driver '%s' invalid ID '%s'"},
{IDS_DRIVER_ERR_FALLTHRU_DRIVER_ERR,
"Driver. Fall through driver '%s' (ID:'%s') error '%s'"},
{IDS_DRIVER_CMD_RECEIVED,
"Driver. Received command '%s'. It was %shandled%s"},
{IDS_DRIVER_CMD_NOT_IN_FACTORY,
". Command '%s' not in Command Factory"},
{
IDS_DRIVER_ERR_DRIVER_STATE_ERROR,
"Driver. Driver running state error. Cannot go to next state from "
"present state as not allowed",
},
{IDS_DRIVER_WAITING_STDIN_DATA, "Driver. Main thread suspended waiting "
"on Stdin Monitor to resume main "
"thread"},
{IDS_DRIVER_ERR_MAINLOOP, "Driver. Error in do main loop. %s"},
{IDS_DRIVER_ERR_LOCAL_DEBUG_NOT_IMPL, "Driver. --executable argument "
"given. Local debugging is not "
"implemented."},
{IDS_DRIVER_ERR_LOCAL_DEBUG_INIT, "Driver. --executable argument "
"given. Initialising local debugging "
"failed."},
{IDS_STDERR_ERR_NOT_ALL_DATA_WRITTEN,
"Stderr. Not all data was written to stream. The data '%s'"},
{IDS_CMD_ARGS_ERR_OPTION_NOT_FOUND,
"Command Args. Option '%s' not found"},
{IDS_CMD_ARGS_ERR_VALIDATION_MANDATORY, "Mandatory args not found: %s"},
{IDS_CMD_ARGS_ERR_VALIDATION_INVALID, "Invalid args: %s"},
{IDS_CMD_ARGS_ERR_VALIDATION_MAN_INVALID,
"Mandatory args not found: %s. Invalid args: %s"},
{IDS_CMD_ARGS_ERR_VALIDATION_MISSING_INF,
"Args missing additional information: %s"},
{IDS_CMD_ARGS_ERR_CONTEXT_NOT_ALL_EATTEN,
"Not all arguments or options were recognised: %s"},
{IDS_CMD_ARGS_ERR_PREFIX_MSG, "Command Args. Validation failed. "},
{IDS_VARIANT_ERR_USED_BASECLASS, "Variant container: Variant object "
"used the base class. See "
"CMIUtilVariant"},
{IDS_VARIANT_ERR_MAP_KEY_INVALID, "Variant container: Invalid ID '%s'"},
{IDS_WORD_INVALIDBRKTS, "<Invalid>"},
{IDS_WORD_NONE, "None"},
{IDS_WORD_NOT, "not"},
{IDS_WORD_INVALIDEMPTY, "<empty>"},
{IDS_WORD_INVALIDNULLPTR, "<NULL ptr>"},
{IDS_WORD_UNKNOWNBRKTS, "<unknown>"},
{IDS_WORD_NOT_IMPLEMENTED, "Not implemented"},
{IDS_WORD_NOT_IMPLEMENTED_BRKTS, "<not implemented>"},
{IDS_WORD_UNKNOWNTYPE_BRKTS, "<unknowntype>"},
{IDS_WORD_ERR_MSG_NOT_IMPLEMENTED_BRKTS,
"<error message not implemented>"},
{IDS_CMD_ERR_N_OPTIONS_REQUIRED,
"Command '%s'. Missing options, %d required"},
{IDS_CMD_ERR_OPTION_NOT_FOUND, "Command '%s'. Option '%s' not found"},
{IDS_CMD_ERR_ARGS, "Command '%s'. %s"},
{IDS_CMD_WRN_ARGS_NOT_HANDLED, "Command '%s'. Warning the following "
"options not handled by the command: "
"%s"},
{IDS_CMD_ERR_FNFAILED, "Command '%s'. Fn '%s' failed"},
{IDS_CMD_ERR_SHARED_DATA_NOT_FOUND,
"Command '%s'. Shared data '%s' not found"},
{IDS_CMD_ERR_LLDBPROCESS_DETACH,
"Command '%s'. Process detach failed. '%s'"},
{IDS_CMD_ERR_LLDBPROCESS_DESTROY,
"Command '%s'. Process destroy failed. '%s'"},
{IDS_CMD_ERR_SETWKDIR,
"Command '%s'. Failed to set working directory '%s'"},
{IDS_CMD_ERR_INVALID_TARGET,
"Command '%s'. Target binary '%s' is invalid. %s"},
{IDS_CMD_ERR_INVALID_TARGET_CURRENT,
"Command '%s'. Current SBTarget is invalid"},
{IDS_CMD_ERR_INVALID_TARGET_TYPE,
"Command '%s'. Target type '%s' is not recognised"},
{IDS_CMD_ERR_INVALID_TARGET_PLUGIN,
"Command '%s'. Target plugin is invalid. %s"},
{IDS_CMD_ERR_CONNECT_TO_TARGET,
"Command '%s'. Error connecting to target: '%s'"},
{IDS_CMD_ERR_INVALID_TARGETPLUGINCURRENT,
"Command '%s'. Current target plugin is invalid"},
{IDS_CMD_ERR_NOT_IMPLEMENTED, "Command '%s'. Command not implemented"},
{IDS_CMD_ERR_NOT_IMPLEMENTED_DEPRECATED,
"Command '%s'. Command not implemented as it has been deprecated"},
{IDS_CMD_ERR_CREATE_TARGET, "Command '%s'. Create target failed: %s"},
{IDS_CMD_ERR_BRKPT_LOCATION_FORMAT,
"Command '%s'. Incorrect format for breakpoint location '%s'"},
{IDS_CMD_ERR_BRKPT_LOCATION_NOT_FOUND,
"Command '%s'. Breakpoint location '%s' not found"},
{IDS_CMD_ERR_BRKPT_INVALID, "Command '%s'. Breakpoint '%s' invalid"},
{IDS_CMD_ERR_BRKPT_CNT_EXCEEDED, "Command '%s'. Number of valid "
"breakpoint exceeded %d. Cannot "
"create new breakpoint '%s'"},
{IDS_CMD_ERR_SOME_ERROR, "Command '%s'. Error: %s"},
{IDS_CMD_ERR_THREAD_INVALID, "Command '%s'. Thread ID invalid"},
{IDS_CMD_ERR_THREAD_FRAME_RANGE_INVALID,
"Command '%s'. Thread frame range invalid"},
{IDS_CMD_ERR_FRAME_INVALID, "Command '%s'. Frame ID invalid"},
{IDS_CMD_ERR_VARIABLE_DOESNOTEXIST,
"Command '%s'. Variable '%s' does not exist"},
{IDS_CMD_ERR_VARIABLE_ENUM_INVALID, "Command '%s'. Invalid enumeration "
"for variable '%s' formatted "
"string '%s'"},
{IDS_CMD_ERR_VARIABLE_EXPRESSIONPATH,
"Command '%s'. Failed to get expression for variable '%s'"},
{IDS_CMD_ERR_VARIABLE_CREATION_FAILED,
"Failed to create variable object for '%s'"},
{IDS_CMD_ERR_VARIABLE_CHILD_RANGE_INVALID,
"Command '%s'. Variable children range invalid"},
{IDS_CMD_ERR_CMD_RUN_BUT_NO_ACTION, "<Error: Command run but command "
"did not do anything useful. No MI "
"response formed>"},
{IDS_CMD_ERR_EVENT_HANDLED_BUT_NO_ACTION,
"<Error: Command run and event caught, did nothing useful. No MI "
"Out-of-Bound formed>"},
{IDS_CMD_ERR_DISASM_ADDR_START_INVALID,
"Command '%s'. Invalid start value '%s'"},
{IDS_CMD_ERR_DISASM_ADDR_END_INVALID,
"Command '%s'. Invalid end value '%s'"},
{IDS_CMD_ERR_MEMORY_ALLOC_FAILURE,
"Command '%s'. Failed to allocate memory %d bytes"},
{IDS_CMD_ERR_LLDB_ERR_NOT_READ_WHOLE_BLK,
"Command '%s'. LLDB unable to read entire memory block of %u bytes at "
"address 0x%016" PRIx64},
{IDS_CMD_ERR_LLDB_ERR_READ_MEM_BYTES, "Command '%s'. Unable to read "
"memory block of %u bytes at "
"address 0x%016" PRIx64 ": %s "},
{IDS_CMD_ERR_INVALID_PROCESS,
"Command '%s'. Invalid process during debug session"},
{IDS_CMD_ERR_INVALID_PRINT_VALUES,
"Command '%s'. Unknown value for PRINT_VALUES: must be: 0 or "
"\"--no-values\", 1 or \"--all-values\", 2 or \"--simple-values\""},
{IDS_CMD_ERR_INVALID_LOCATION_FORMAT,
"Command '%s'. Invalid location format '%s'"},
{IDS_CMD_ERR_INVALID_FORMAT_TYPE,
"Command '%s'. Invalid var format type '%s'"},
{IDS_CMD_ERR_BRKPT_INFO_OBJ_NOT_FOUND,
"Command '%s'. Breakpoint information for breakpoint ID %d not found"},
{IDS_CMD_ERR_LLDB_ERR_WRITE_MEM_BYTES, "Command '%s'. Unable to write "
"memory block of %u bytes at "
"address 0x%016" PRIx64 ": %s "},
{IDS_CMD_ERR_LLDB_ERR_NOT_WRITE_WHOLEBLK,
"Command '%s'. LLDB unable to write entire memory block of %u bytes "
"at address 0x%016" PRIX64},
{IDS_CMD_ERR_SET_NEW_DRIVER_STATE, "Command '%s'. Command tried to set "
"new MI Driver running state and "
"failed. %s"},
{IDS_CMD_ERR_INFO_PRINTFN_NOT_FOUND,
"The request '%s' was not recognised, not implemented"},
{IDS_CMD_ERR_INFO_PRINTFN_FAILED, "The request '%s' failed."},
{IDS_CMD_ERR_GDBSET_OPT_TARGETASYNC,
"'target-async' expects \"on\" or \"off\""},
{IDS_CMD_ERR_GDBSET_OPT_SOLIBSEARCHPATH,
"'solib-search-path' requires at least one argument"},
{IDS_CMD_ERR_GDBSET_OPT_PRINT_BAD_ARGS,
"'print' expects option-name and \"on\" or \"off\""},
{IDS_CMD_ERR_GDBSET_OPT_PRINT_UNKNOWN_OPTION,
"'print' error. The option '%s' not found"},
{IDS_CMD_ERR_GDBSHOW_OPT_PRINT_BAD_ARGS,
"'print' expects option-name and \"on\" or \"off\""},
{IDS_CMD_ERR_GDBSHOW_OPT_PRINT_UNKNOWN_OPTION,
"'print' error. The option '%s' not found"},
{IDS_CMD_ERR_EXPR_INVALID, "Failed to evaluate expression: %s"},
{IDS_CMD_ERR_ATTACH_FAILED,
- "Command '%s'. Attach to processs failed: %s"},
+ "Command '%s'. Attach to process failed: %s"},
{IDS_CMD_ERR_ATTACH_BAD_ARGS,
"Command '%s'. Must specify either a PID or a Name"}};
//++
//------------------------------------------------------------------------------------
// Details: CMICmnResources constructor.
// Type: Method.
// Args: None.
// Return: None.
// Throws: None.
//--
CMICmnResources::CMICmnResources() : m_nResourceId2TextDataSize(0) {
// Do not use this constructor, use Initialize()
}
//++
//------------------------------------------------------------------------------------
// Details: CMICmnResources destructor.
// Type: Overridden.
// Args: None.
// Return: None.
// Throws: None.
//--
CMICmnResources::~CMICmnResources() {
// Do not use this destructor, use Shutdown()
}
//++
//------------------------------------------------------------------------------------
// Details: Initialize the resources and set locality for the server.
// Type: Method.
// Args: None.
// Return: MIstatus::success - Functional succeeded.
// MIstatus::failure - Functional failed.
// Throws: None.
//--
bool CMICmnResources::Initialize() {
m_clientUsageRefCnt++;
if (m_bInitialized)
return MIstatus::success;
m_bInitialized = ReadResourceStringData();
return m_bInitialized;
}
//++
//------------------------------------------------------------------------------------
// Details: Release resources for *this object.
// Type: Method.
// Args: None.
// Return: MIstatus::success - Functional succeeded.
// MIstatus::failure - Functional failed.
// Throws: None.
//--
bool CMICmnResources::Shutdown() {
if (--m_clientUsageRefCnt > 0)
return MIstatus::success;
if (!m_bInitialized)
return MIstatus::success;
// Tear down resource explicitly
m_mapRscrIdToTextData.clear();
m_bInitialized = false;
return MIstatus::success;
}
//++
//------------------------------------------------------------------------------------
// Details: Initialize the resources and set locality for the server.
// Type: Method.
// Args: None.
// Return: MIstatus::success - Functional succeeded.
// MIstatus::failure - Functional failed.
// Throws: None.
//--
bool CMICmnResources::ReadResourceStringData() {
m_nResourceId2TextDataSize =
sizeof ms_pResourceId2TextData / sizeof ms_pResourceId2TextData[0];
for (MIuint i = 0; i < m_nResourceId2TextDataSize; i++) {
const SRsrcTextData *pRscrData = &ms_pResourceId2TextData[i];
MapPairRscrIdToTextData_t pr(pRscrData->id, pRscrData->pTextData);
m_mapRscrIdToTextData.insert(pr);
}
return MIstatus::success;
}
//++
//------------------------------------------------------------------------------------
// Details: Retrieve the corresponding text assigned to the resource ID.
// Type: Method.
// Args: vResourceId - (R) MI resource ID.
// Return: CMIUtilString - Resource text.
// Throws: None.
//--
CMIUtilString CMICmnResources::GetString(const MIuint vResourceId) const {
CMIUtilString str;
const bool bFound = GetStringFromResource(vResourceId, str);
MIunused(bFound);
assert(bFound);
return str;
}
//++
//------------------------------------------------------------------------------------
// Details: Determine the MI resource ID existings.
// Type: Method.
// Args: vResourceId - (R) MI resource ID.
// Return: True - Exists.
// False - Not found.
// Throws: None.
//--
bool CMICmnResources::HasString(const MIuint vResourceId) const {
CMIUtilString str;
return GetStringFromResource(vResourceId, str);
}
//++
//------------------------------------------------------------------------------------
// Details: Retrieve the resource text data for the given resource ID. If a
// resource ID
// cannot be found and error is given returning the ID of the resource
// that
// cannot be located.
// Type: Method.
// Args: vResourceId - (R) MI resource ID.
// vrwResourceString - (W) Text.
// Return: MIstatus::success - Functional succeeded.
// MIstatus::failure - Functional failed.
// Throws: None.
//--
bool CMICmnResources::GetStringFromResource(
const MIuint vResourceId, CMIUtilString &vrwResourceString) const {
MapRscrIdToTextData_t::const_iterator it =
m_mapRscrIdToTextData.find(vResourceId);
if (it == m_mapRscrIdToTextData.end()) {
// Check this is a static variable init that needs this before we are ready
if (!m_bInitialized) {
(const_cast<CMICmnResources *>(this))->Initialize();
it = m_mapRscrIdToTextData.find(vResourceId);
if (it == m_mapRscrIdToTextData.end()) {
vrwResourceString = MIRSRC(IDS_RESOURCES_ERR_STRING_TABLE_INVALID);
return MIstatus::failure;
}
}
if (it == m_mapRscrIdToTextData.end()) {
vrwResourceString = CMIUtilString::Format(
MIRSRC(IDS_RESOURCES_ERR_STRING_NOT_FOUND), vResourceId);
return MIstatus::failure;
}
}
const MIuint nRsrcId((*it).first);
MIunused(nRsrcId);
const char *pRsrcData((*it).second);
// Return result
vrwResourceString = pRsrcData;
return MIstatus::success;
}
Index: source/Expression/DWARFExpression.cpp
===================================================================
--- source/Expression/DWARFExpression.cpp (revision 333398)
+++ source/Expression/DWARFExpression.cpp (revision 333399)
@@ -1,3375 +1,3375 @@
//===-- DWARFExpression.cpp -------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "lldb/Expression/DWARFExpression.h"
// C Includes
#include <inttypes.h>
// C++ Includes
#include <vector>
#include "lldb/Core/RegisterValue.h"
#include "lldb/Core/Scalar.h"
#include "lldb/Core/Value.h"
#include "lldb/Core/dwarf.h"
#include "lldb/Utility/DataEncoder.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/StreamString.h"
#include "lldb/Utility/VMRange.h"
#include "lldb/Host/Host.h"
#include "lldb/Utility/Endian.h"
#include "lldb/Symbol/Function.h"
#include "lldb/Target/ABI.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/RegisterContext.h"
#include "lldb/Target/StackFrame.h"
#include "lldb/Target/StackID.h"
#include "lldb/Target/Thread.h"
#include "Plugins/SymbolFile/DWARF/DWARFUnit.h"
using namespace lldb;
using namespace lldb_private;
static lldb::addr_t
ReadAddressFromDebugAddrSection(const DWARFUnit *dwarf_cu,
uint32_t index) {
uint32_t index_size = dwarf_cu->GetAddressByteSize();
dw_offset_t addr_base = dwarf_cu->GetAddrBase();
lldb::offset_t offset = addr_base + index * index_size;
return dwarf_cu->GetSymbolFileDWARF()->get_debug_addr_data().GetMaxU64(
&offset, index_size);
}
//----------------------------------------------------------------------
// DWARFExpression constructor
//----------------------------------------------------------------------
DWARFExpression::DWARFExpression(DWARFUnit *dwarf_cu)
: m_module_wp(), m_data(), m_dwarf_cu(dwarf_cu),
m_reg_kind(eRegisterKindDWARF), m_loclist_slide(LLDB_INVALID_ADDRESS) {}
DWARFExpression::DWARFExpression(const DWARFExpression &rhs)
: m_module_wp(rhs.m_module_wp), m_data(rhs.m_data),
m_dwarf_cu(rhs.m_dwarf_cu), m_reg_kind(rhs.m_reg_kind),
m_loclist_slide(rhs.m_loclist_slide) {}
DWARFExpression::DWARFExpression(lldb::ModuleSP module_sp,
const DataExtractor &data,
DWARFUnit *dwarf_cu,
lldb::offset_t data_offset,
lldb::offset_t data_length)
: m_module_wp(), m_data(data, data_offset, data_length),
m_dwarf_cu(dwarf_cu), m_reg_kind(eRegisterKindDWARF),
m_loclist_slide(LLDB_INVALID_ADDRESS) {
if (module_sp)
m_module_wp = module_sp;
}
//----------------------------------------------------------------------
// Destructor
//----------------------------------------------------------------------
DWARFExpression::~DWARFExpression() {}
bool DWARFExpression::IsValid() const { return m_data.GetByteSize() > 0; }
void DWARFExpression::SetOpcodeData(const DataExtractor &data) {
m_data = data;
}
void DWARFExpression::CopyOpcodeData(lldb::ModuleSP module_sp,
const DataExtractor &data,
lldb::offset_t data_offset,
lldb::offset_t data_length) {
const uint8_t *bytes = data.PeekData(data_offset, data_length);
if (bytes) {
m_module_wp = module_sp;
m_data.SetData(DataBufferSP(new DataBufferHeap(bytes, data_length)));
m_data.SetByteOrder(data.GetByteOrder());
m_data.SetAddressByteSize(data.GetAddressByteSize());
}
}
void DWARFExpression::CopyOpcodeData(const void *data,
lldb::offset_t data_length,
ByteOrder byte_order,
uint8_t addr_byte_size) {
if (data && data_length) {
m_data.SetData(DataBufferSP(new DataBufferHeap(data, data_length)));
m_data.SetByteOrder(byte_order);
m_data.SetAddressByteSize(addr_byte_size);
}
}
void DWARFExpression::CopyOpcodeData(uint64_t const_value,
lldb::offset_t const_value_byte_size,
uint8_t addr_byte_size) {
if (const_value_byte_size) {
m_data.SetData(
DataBufferSP(new DataBufferHeap(&const_value, const_value_byte_size)));
m_data.SetByteOrder(endian::InlHostByteOrder());
m_data.SetAddressByteSize(addr_byte_size);
}
}
void DWARFExpression::SetOpcodeData(lldb::ModuleSP module_sp,
const DataExtractor &data,
lldb::offset_t data_offset,
lldb::offset_t data_length) {
m_module_wp = module_sp;
m_data.SetData(data, data_offset, data_length);
}
void DWARFExpression::DumpLocation(Stream *s, lldb::offset_t offset,
lldb::offset_t length,
lldb::DescriptionLevel level,
ABI *abi) const {
if (!m_data.ValidOffsetForDataOfSize(offset, length))
return;
const lldb::offset_t start_offset = offset;
const lldb::offset_t end_offset = offset + length;
while (m_data.ValidOffset(offset) && offset < end_offset) {
const lldb::offset_t op_offset = offset;
const uint8_t op = m_data.GetU8(&offset);
switch (level) {
default:
break;
case lldb::eDescriptionLevelBrief:
if (offset > start_offset)
s->PutChar(' ');
break;
case lldb::eDescriptionLevelFull:
case lldb::eDescriptionLevelVerbose:
if (offset > start_offset)
s->EOL();
s->Indent();
if (level == lldb::eDescriptionLevelFull)
break;
// Fall through for verbose and print offset and DW_OP prefix..
s->Printf("0x%8.8" PRIx64 ": %s", op_offset,
op >= DW_OP_APPLE_uninit ? "DW_OP_APPLE_" : "DW_OP_");
break;
}
switch (op) {
case DW_OP_addr:
*s << "DW_OP_addr(" << m_data.GetAddress(&offset) << ") ";
break; // 0x03 1 address
case DW_OP_deref:
*s << "DW_OP_deref";
break; // 0x06
case DW_OP_const1u:
s->Printf("DW_OP_const1u(0x%2.2x) ", m_data.GetU8(&offset));
break; // 0x08 1 1-byte constant
case DW_OP_const1s:
s->Printf("DW_OP_const1s(0x%2.2x) ", m_data.GetU8(&offset));
break; // 0x09 1 1-byte constant
case DW_OP_const2u:
s->Printf("DW_OP_const2u(0x%4.4x) ", m_data.GetU16(&offset));
break; // 0x0a 1 2-byte constant
case DW_OP_const2s:
s->Printf("DW_OP_const2s(0x%4.4x) ", m_data.GetU16(&offset));
break; // 0x0b 1 2-byte constant
case DW_OP_const4u:
s->Printf("DW_OP_const4u(0x%8.8x) ", m_data.GetU32(&offset));
break; // 0x0c 1 4-byte constant
case DW_OP_const4s:
s->Printf("DW_OP_const4s(0x%8.8x) ", m_data.GetU32(&offset));
break; // 0x0d 1 4-byte constant
case DW_OP_const8u:
s->Printf("DW_OP_const8u(0x%16.16" PRIx64 ") ", m_data.GetU64(&offset));
break; // 0x0e 1 8-byte constant
case DW_OP_const8s:
s->Printf("DW_OP_const8s(0x%16.16" PRIx64 ") ", m_data.GetU64(&offset));
break; // 0x0f 1 8-byte constant
case DW_OP_constu:
s->Printf("DW_OP_constu(0x%" PRIx64 ") ", m_data.GetULEB128(&offset));
break; // 0x10 1 ULEB128 constant
case DW_OP_consts:
s->Printf("DW_OP_consts(0x%" PRId64 ") ", m_data.GetSLEB128(&offset));
break; // 0x11 1 SLEB128 constant
case DW_OP_dup:
s->PutCString("DW_OP_dup");
break; // 0x12
case DW_OP_drop:
s->PutCString("DW_OP_drop");
break; // 0x13
case DW_OP_over:
s->PutCString("DW_OP_over");
break; // 0x14
case DW_OP_pick:
s->Printf("DW_OP_pick(0x%2.2x) ", m_data.GetU8(&offset));
break; // 0x15 1 1-byte stack index
case DW_OP_swap:
s->PutCString("DW_OP_swap");
break; // 0x16
case DW_OP_rot:
s->PutCString("DW_OP_rot");
break; // 0x17
case DW_OP_xderef:
s->PutCString("DW_OP_xderef");
break; // 0x18
case DW_OP_abs:
s->PutCString("DW_OP_abs");
break; // 0x19
case DW_OP_and:
s->PutCString("DW_OP_and");
break; // 0x1a
case DW_OP_div:
s->PutCString("DW_OP_div");
break; // 0x1b
case DW_OP_minus:
s->PutCString("DW_OP_minus");
break; // 0x1c
case DW_OP_mod:
s->PutCString("DW_OP_mod");
break; // 0x1d
case DW_OP_mul:
s->PutCString("DW_OP_mul");
break; // 0x1e
case DW_OP_neg:
s->PutCString("DW_OP_neg");
break; // 0x1f
case DW_OP_not:
s->PutCString("DW_OP_not");
break; // 0x20
case DW_OP_or:
s->PutCString("DW_OP_or");
break; // 0x21
case DW_OP_plus:
s->PutCString("DW_OP_plus");
break; // 0x22
case DW_OP_plus_uconst: // 0x23 1 ULEB128 addend
s->Printf("DW_OP_plus_uconst(0x%" PRIx64 ") ",
m_data.GetULEB128(&offset));
break;
case DW_OP_shl:
s->PutCString("DW_OP_shl");
break; // 0x24
case DW_OP_shr:
s->PutCString("DW_OP_shr");
break; // 0x25
case DW_OP_shra:
s->PutCString("DW_OP_shra");
break; // 0x26
case DW_OP_xor:
s->PutCString("DW_OP_xor");
break; // 0x27
case DW_OP_skip:
s->Printf("DW_OP_skip(0x%4.4x)", m_data.GetU16(&offset));
break; // 0x2f 1 signed 2-byte constant
case DW_OP_bra:
s->Printf("DW_OP_bra(0x%4.4x)", m_data.GetU16(&offset));
break; // 0x28 1 signed 2-byte constant
case DW_OP_eq:
s->PutCString("DW_OP_eq");
break; // 0x29
case DW_OP_ge:
s->PutCString("DW_OP_ge");
break; // 0x2a
case DW_OP_gt:
s->PutCString("DW_OP_gt");
break; // 0x2b
case DW_OP_le:
s->PutCString("DW_OP_le");
break; // 0x2c
case DW_OP_lt:
s->PutCString("DW_OP_lt");
break; // 0x2d
case DW_OP_ne:
s->PutCString("DW_OP_ne");
break; // 0x2e
case DW_OP_lit0: // 0x30
case DW_OP_lit1: // 0x31
case DW_OP_lit2: // 0x32
case DW_OP_lit3: // 0x33
case DW_OP_lit4: // 0x34
case DW_OP_lit5: // 0x35
case DW_OP_lit6: // 0x36
case DW_OP_lit7: // 0x37
case DW_OP_lit8: // 0x38
case DW_OP_lit9: // 0x39
case DW_OP_lit10: // 0x3A
case DW_OP_lit11: // 0x3B
case DW_OP_lit12: // 0x3C
case DW_OP_lit13: // 0x3D
case DW_OP_lit14: // 0x3E
case DW_OP_lit15: // 0x3F
case DW_OP_lit16: // 0x40
case DW_OP_lit17: // 0x41
case DW_OP_lit18: // 0x42
case DW_OP_lit19: // 0x43
case DW_OP_lit20: // 0x44
case DW_OP_lit21: // 0x45
case DW_OP_lit22: // 0x46
case DW_OP_lit23: // 0x47
case DW_OP_lit24: // 0x48
case DW_OP_lit25: // 0x49
case DW_OP_lit26: // 0x4A
case DW_OP_lit27: // 0x4B
case DW_OP_lit28: // 0x4C
case DW_OP_lit29: // 0x4D
case DW_OP_lit30: // 0x4E
case DW_OP_lit31:
s->Printf("DW_OP_lit%i", op - DW_OP_lit0);
break; // 0x4f
case DW_OP_reg0: // 0x50
case DW_OP_reg1: // 0x51
case DW_OP_reg2: // 0x52
case DW_OP_reg3: // 0x53
case DW_OP_reg4: // 0x54
case DW_OP_reg5: // 0x55
case DW_OP_reg6: // 0x56
case DW_OP_reg7: // 0x57
case DW_OP_reg8: // 0x58
case DW_OP_reg9: // 0x59
case DW_OP_reg10: // 0x5A
case DW_OP_reg11: // 0x5B
case DW_OP_reg12: // 0x5C
case DW_OP_reg13: // 0x5D
case DW_OP_reg14: // 0x5E
case DW_OP_reg15: // 0x5F
case DW_OP_reg16: // 0x60
case DW_OP_reg17: // 0x61
case DW_OP_reg18: // 0x62
case DW_OP_reg19: // 0x63
case DW_OP_reg20: // 0x64
case DW_OP_reg21: // 0x65
case DW_OP_reg22: // 0x66
case DW_OP_reg23: // 0x67
case DW_OP_reg24: // 0x68
case DW_OP_reg25: // 0x69
case DW_OP_reg26: // 0x6A
case DW_OP_reg27: // 0x6B
case DW_OP_reg28: // 0x6C
case DW_OP_reg29: // 0x6D
case DW_OP_reg30: // 0x6E
case DW_OP_reg31: // 0x6F
{
uint32_t reg_num = op - DW_OP_reg0;
if (abi) {
RegisterInfo reg_info;
if (abi->GetRegisterInfoByKind(m_reg_kind, reg_num, reg_info)) {
if (reg_info.name) {
s->PutCString(reg_info.name);
break;
} else if (reg_info.alt_name) {
s->PutCString(reg_info.alt_name);
break;
}
}
}
s->Printf("DW_OP_reg%u", reg_num);
break;
} break;
case DW_OP_breg0:
case DW_OP_breg1:
case DW_OP_breg2:
case DW_OP_breg3:
case DW_OP_breg4:
case DW_OP_breg5:
case DW_OP_breg6:
case DW_OP_breg7:
case DW_OP_breg8:
case DW_OP_breg9:
case DW_OP_breg10:
case DW_OP_breg11:
case DW_OP_breg12:
case DW_OP_breg13:
case DW_OP_breg14:
case DW_OP_breg15:
case DW_OP_breg16:
case DW_OP_breg17:
case DW_OP_breg18:
case DW_OP_breg19:
case DW_OP_breg20:
case DW_OP_breg21:
case DW_OP_breg22:
case DW_OP_breg23:
case DW_OP_breg24:
case DW_OP_breg25:
case DW_OP_breg26:
case DW_OP_breg27:
case DW_OP_breg28:
case DW_OP_breg29:
case DW_OP_breg30:
case DW_OP_breg31: {
uint32_t reg_num = op - DW_OP_breg0;
int64_t reg_offset = m_data.GetSLEB128(&offset);
if (abi) {
RegisterInfo reg_info;
if (abi->GetRegisterInfoByKind(m_reg_kind, reg_num, reg_info)) {
if (reg_info.name) {
s->Printf("[%s%+" PRIi64 "]", reg_info.name, reg_offset);
break;
} else if (reg_info.alt_name) {
s->Printf("[%s%+" PRIi64 "]", reg_info.alt_name, reg_offset);
break;
}
}
}
s->Printf("DW_OP_breg%i(0x%" PRIx64 ")", reg_num, reg_offset);
} break;
case DW_OP_regx: // 0x90 1 ULEB128 register
{
uint32_t reg_num = m_data.GetULEB128(&offset);
if (abi) {
RegisterInfo reg_info;
if (abi->GetRegisterInfoByKind(m_reg_kind, reg_num, reg_info)) {
if (reg_info.name) {
s->PutCString(reg_info.name);
break;
} else if (reg_info.alt_name) {
s->PutCString(reg_info.alt_name);
break;
}
}
}
s->Printf("DW_OP_regx(%" PRIu32 ")", reg_num);
break;
} break;
case DW_OP_fbreg: // 0x91 1 SLEB128 offset
s->Printf("DW_OP_fbreg(%" PRIi64 ")", m_data.GetSLEB128(&offset));
break;
case DW_OP_bregx: // 0x92 2 ULEB128 register followed by SLEB128 offset
{
uint32_t reg_num = m_data.GetULEB128(&offset);
int64_t reg_offset = m_data.GetSLEB128(&offset);
if (abi) {
RegisterInfo reg_info;
if (abi->GetRegisterInfoByKind(m_reg_kind, reg_num, reg_info)) {
if (reg_info.name) {
s->Printf("[%s%+" PRIi64 "]", reg_info.name, reg_offset);
break;
} else if (reg_info.alt_name) {
s->Printf("[%s%+" PRIi64 "]", reg_info.alt_name, reg_offset);
break;
}
}
}
s->Printf("DW_OP_bregx(reg=%" PRIu32 ",offset=%" PRIi64 ")", reg_num,
reg_offset);
} break;
case DW_OP_piece: // 0x93 1 ULEB128 size of piece addressed
s->Printf("DW_OP_piece(0x%" PRIx64 ")", m_data.GetULEB128(&offset));
break;
case DW_OP_deref_size: // 0x94 1 1-byte size of data retrieved
s->Printf("DW_OP_deref_size(0x%2.2x)", m_data.GetU8(&offset));
break;
case DW_OP_xderef_size: // 0x95 1 1-byte size of data retrieved
s->Printf("DW_OP_xderef_size(0x%2.2x)", m_data.GetU8(&offset));
break;
case DW_OP_nop:
s->PutCString("DW_OP_nop");
break; // 0x96
case DW_OP_push_object_address:
s->PutCString("DW_OP_push_object_address");
break; // 0x97 DWARF3
case DW_OP_call2: // 0x98 DWARF3 1 2-byte offset of DIE
s->Printf("DW_OP_call2(0x%4.4x)", m_data.GetU16(&offset));
break;
case DW_OP_call4: // 0x99 DWARF3 1 4-byte offset of DIE
s->Printf("DW_OP_call4(0x%8.8x)", m_data.GetU32(&offset));
break;
case DW_OP_call_ref: // 0x9a DWARF3 1 4- or 8-byte offset of DIE
s->Printf("DW_OP_call_ref(0x%8.8" PRIx64 ")", m_data.GetAddress(&offset));
break;
// case DW_OP_call_frame_cfa: s << "call_frame_cfa"; break;
// // 0x9c DWARF3
// case DW_OP_bit_piece: // 0x9d DWARF3 2
// s->Printf("DW_OP_bit_piece(0x%x, 0x%x)",
// m_data.GetULEB128(&offset), m_data.GetULEB128(&offset));
// break;
// case DW_OP_lo_user: s->PutCString("DW_OP_lo_user"); break;
// // 0xe0
// case DW_OP_hi_user: s->PutCString("DW_OP_hi_user"); break;
// // 0xff
// case DW_OP_APPLE_extern:
// s->Printf("DW_OP_APPLE_extern(%" PRIu64 ")",
// m_data.GetULEB128(&offset));
// break;
// case DW_OP_APPLE_array_ref:
// s->PutCString("DW_OP_APPLE_array_ref");
// break;
case DW_OP_form_tls_address:
s->PutCString("DW_OP_form_tls_address"); // 0x9b
break;
case DW_OP_GNU_addr_index: // 0xfb
s->Printf("DW_OP_GNU_addr_index(0x%" PRIx64 ")",
m_data.GetULEB128(&offset));
break;
case DW_OP_GNU_const_index: // 0xfc
s->Printf("DW_OP_GNU_const_index(0x%" PRIx64 ")",
m_data.GetULEB128(&offset));
break;
case DW_OP_GNU_push_tls_address:
s->PutCString("DW_OP_GNU_push_tls_address"); // 0xe0
break;
case DW_OP_APPLE_uninit:
s->PutCString("DW_OP_APPLE_uninit"); // 0xF0
break;
// case DW_OP_APPLE_assign: // 0xF1 - pops value off and
// assigns it to second item on stack (2nd item must have
// assignable context)
// s->PutCString("DW_OP_APPLE_assign");
// break;
// case DW_OP_APPLE_address_of: // 0xF2 - gets the address of
// the top stack item (top item must be a variable, or have
// value_type that is an address already)
// s->PutCString("DW_OP_APPLE_address_of");
// break;
// case DW_OP_APPLE_value_of: // 0xF3 - pops the value off the
// stack and pushes the value of that object (top item must be a
// variable, or expression local)
// s->PutCString("DW_OP_APPLE_value_of");
// break;
// case DW_OP_APPLE_deref_type: // 0xF4 - gets the address of
// the top stack item (top item must be a variable, or a clang
// type)
// s->PutCString("DW_OP_APPLE_deref_type");
// break;
// case DW_OP_APPLE_expr_local: // 0xF5 - ULEB128 expression
// local index
// s->Printf("DW_OP_APPLE_expr_local(%" PRIu64 ")",
// m_data.GetULEB128(&offset));
// break;
// case DW_OP_APPLE_constf: // 0xF6 - 1 byte float size,
// followed by constant float data
// {
// uint8_t float_length = m_data.GetU8(&offset);
// s->Printf("DW_OP_APPLE_constf(<%u> ", float_length);
// m_data.Dump(s, offset, eFormatHex, float_length, 1,
// UINT32_MAX, DW_INVALID_ADDRESS, 0, 0);
// s->PutChar(')');
// // Consume the float data
// m_data.GetData(&offset, float_length);
// }
// break;
// case DW_OP_APPLE_scalar_cast:
// s->Printf("DW_OP_APPLE_scalar_cast(%s)",
// Scalar::GetValueTypeAsCString
// ((Scalar::Type)m_data.GetU8(&offset)));
// break;
// case DW_OP_APPLE_clang_cast:
// {
// clang::Type *clang_type = (clang::Type
// *)m_data.GetMaxU64(&offset, sizeof(void*));
// s->Printf("DW_OP_APPLE_clang_cast(%p)", clang_type);
// }
// break;
// case DW_OP_APPLE_clear:
// s->PutCString("DW_OP_APPLE_clear");
// break;
// case DW_OP_APPLE_error: // 0xFF - Stops expression
// evaluation and returns an error (no args)
// s->PutCString("DW_OP_APPLE_error");
// break;
}
}
}
void DWARFExpression::SetLocationListSlide(addr_t slide) {
m_loclist_slide = slide;
}
int DWARFExpression::GetRegisterKind() { return m_reg_kind; }
void DWARFExpression::SetRegisterKind(RegisterKind reg_kind) {
m_reg_kind = reg_kind;
}
bool DWARFExpression::IsLocationList() const {
return m_loclist_slide != LLDB_INVALID_ADDRESS;
}
void DWARFExpression::GetDescription(Stream *s, lldb::DescriptionLevel level,
addr_t location_list_base_addr,
ABI *abi) const {
if (IsLocationList()) {
// We have a location list
lldb::offset_t offset = 0;
uint32_t count = 0;
addr_t curr_base_addr = location_list_base_addr;
while (m_data.ValidOffset(offset)) {
addr_t begin_addr_offset = LLDB_INVALID_ADDRESS;
addr_t end_addr_offset = LLDB_INVALID_ADDRESS;
if (!AddressRangeForLocationListEntry(m_dwarf_cu, m_data, &offset,
begin_addr_offset, end_addr_offset))
break;
if (begin_addr_offset == 0 && end_addr_offset == 0)
break;
if (begin_addr_offset < end_addr_offset) {
if (count > 0)
s->PutCString(", ");
VMRange addr_range(curr_base_addr + begin_addr_offset,
curr_base_addr + end_addr_offset);
addr_range.Dump(s, 0, 8);
s->PutChar('{');
lldb::offset_t location_length = m_data.GetU16(&offset);
DumpLocation(s, offset, location_length, level, abi);
s->PutChar('}');
offset += location_length;
} else {
if ((m_data.GetAddressByteSize() == 4 &&
(begin_addr_offset == UINT32_MAX)) ||
(m_data.GetAddressByteSize() == 8 &&
(begin_addr_offset == UINT64_MAX))) {
curr_base_addr = end_addr_offset + location_list_base_addr;
// We have a new base address
if (count > 0)
s->PutCString(", ");
*s << "base_addr = " << end_addr_offset;
}
}
count++;
}
} else {
// We have a normal location that contains DW_OP location opcodes
DumpLocation(s, 0, m_data.GetByteSize(), level, abi);
}
}
static bool ReadRegisterValueAsScalar(RegisterContext *reg_ctx,
lldb::RegisterKind reg_kind,
uint32_t reg_num, Status *error_ptr,
Value &value) {
if (reg_ctx == NULL) {
if (error_ptr)
error_ptr->SetErrorStringWithFormat("No register context in frame.\n");
} else {
uint32_t native_reg =
reg_ctx->ConvertRegisterKindToRegisterNumber(reg_kind, reg_num);
if (native_reg == LLDB_INVALID_REGNUM) {
if (error_ptr)
error_ptr->SetErrorStringWithFormat("Unable to convert register "
"kind=%u reg_num=%u to a native "
"register number.\n",
reg_kind, reg_num);
} else {
const RegisterInfo *reg_info =
reg_ctx->GetRegisterInfoAtIndex(native_reg);
RegisterValue reg_value;
if (reg_ctx->ReadRegister(reg_info, reg_value)) {
if (reg_value.GetScalarValue(value.GetScalar())) {
value.SetValueType(Value::eValueTypeScalar);
value.SetContext(Value::eContextTypeRegisterInfo,
const_cast<RegisterInfo *>(reg_info));
if (error_ptr)
error_ptr->Clear();
return true;
} else {
// If we get this error, then we need to implement a value buffer in
// the dwarf expression evaluation function...
if (error_ptr)
error_ptr->SetErrorStringWithFormat(
"register %s can't be converted to a scalar value",
reg_info->name);
}
} else {
if (error_ptr)
error_ptr->SetErrorStringWithFormat("register %s is not available",
reg_info->name);
}
}
}
return false;
}
// bool
// DWARFExpression::LocationListContainsLoadAddress (Process* process, const
// Address &addr) const
//{
// return LocationListContainsLoadAddress(process,
// addr.GetLoadAddress(process));
//}
//
// bool
// DWARFExpression::LocationListContainsLoadAddress (Process* process, addr_t
// load_addr) const
//{
// if (load_addr == LLDB_INVALID_ADDRESS)
// return false;
//
// if (IsLocationList())
// {
// lldb::offset_t offset = 0;
//
// addr_t loc_list_base_addr = m_loclist_slide.GetLoadAddress(process);
//
// if (loc_list_base_addr == LLDB_INVALID_ADDRESS)
// return false;
//
// while (m_data.ValidOffset(offset))
// {
// // We need to figure out what the value is for the location.
// addr_t lo_pc = m_data.GetAddress(&offset);
// addr_t hi_pc = m_data.GetAddress(&offset);
// if (lo_pc == 0 && hi_pc == 0)
// break;
// else
// {
// lo_pc += loc_list_base_addr;
// hi_pc += loc_list_base_addr;
//
// if (lo_pc <= load_addr && load_addr < hi_pc)
// return true;
//
// offset += m_data.GetU16(&offset);
// }
// }
// }
// return false;
//}
static offset_t GetOpcodeDataSize(const DataExtractor &data,
const lldb::offset_t data_offset,
const uint8_t op) {
lldb::offset_t offset = data_offset;
switch (op) {
case DW_OP_addr:
case DW_OP_call_ref: // 0x9a 1 address sized offset of DIE (DWARF3)
return data.GetAddressByteSize();
// Opcodes with no arguments
case DW_OP_deref: // 0x06
case DW_OP_dup: // 0x12
case DW_OP_drop: // 0x13
case DW_OP_over: // 0x14
case DW_OP_swap: // 0x16
case DW_OP_rot: // 0x17
case DW_OP_xderef: // 0x18
case DW_OP_abs: // 0x19
case DW_OP_and: // 0x1a
case DW_OP_div: // 0x1b
case DW_OP_minus: // 0x1c
case DW_OP_mod: // 0x1d
case DW_OP_mul: // 0x1e
case DW_OP_neg: // 0x1f
case DW_OP_not: // 0x20
case DW_OP_or: // 0x21
case DW_OP_plus: // 0x22
case DW_OP_shl: // 0x24
case DW_OP_shr: // 0x25
case DW_OP_shra: // 0x26
case DW_OP_xor: // 0x27
case DW_OP_eq: // 0x29
case DW_OP_ge: // 0x2a
case DW_OP_gt: // 0x2b
case DW_OP_le: // 0x2c
case DW_OP_lt: // 0x2d
case DW_OP_ne: // 0x2e
case DW_OP_lit0: // 0x30
case DW_OP_lit1: // 0x31
case DW_OP_lit2: // 0x32
case DW_OP_lit3: // 0x33
case DW_OP_lit4: // 0x34
case DW_OP_lit5: // 0x35
case DW_OP_lit6: // 0x36
case DW_OP_lit7: // 0x37
case DW_OP_lit8: // 0x38
case DW_OP_lit9: // 0x39
case DW_OP_lit10: // 0x3A
case DW_OP_lit11: // 0x3B
case DW_OP_lit12: // 0x3C
case DW_OP_lit13: // 0x3D
case DW_OP_lit14: // 0x3E
case DW_OP_lit15: // 0x3F
case DW_OP_lit16: // 0x40
case DW_OP_lit17: // 0x41
case DW_OP_lit18: // 0x42
case DW_OP_lit19: // 0x43
case DW_OP_lit20: // 0x44
case DW_OP_lit21: // 0x45
case DW_OP_lit22: // 0x46
case DW_OP_lit23: // 0x47
case DW_OP_lit24: // 0x48
case DW_OP_lit25: // 0x49
case DW_OP_lit26: // 0x4A
case DW_OP_lit27: // 0x4B
case DW_OP_lit28: // 0x4C
case DW_OP_lit29: // 0x4D
case DW_OP_lit30: // 0x4E
case DW_OP_lit31: // 0x4f
case DW_OP_reg0: // 0x50
case DW_OP_reg1: // 0x51
case DW_OP_reg2: // 0x52
case DW_OP_reg3: // 0x53
case DW_OP_reg4: // 0x54
case DW_OP_reg5: // 0x55
case DW_OP_reg6: // 0x56
case DW_OP_reg7: // 0x57
case DW_OP_reg8: // 0x58
case DW_OP_reg9: // 0x59
case DW_OP_reg10: // 0x5A
case DW_OP_reg11: // 0x5B
case DW_OP_reg12: // 0x5C
case DW_OP_reg13: // 0x5D
case DW_OP_reg14: // 0x5E
case DW_OP_reg15: // 0x5F
case DW_OP_reg16: // 0x60
case DW_OP_reg17: // 0x61
case DW_OP_reg18: // 0x62
case DW_OP_reg19: // 0x63
case DW_OP_reg20: // 0x64
case DW_OP_reg21: // 0x65
case DW_OP_reg22: // 0x66
case DW_OP_reg23: // 0x67
case DW_OP_reg24: // 0x68
case DW_OP_reg25: // 0x69
case DW_OP_reg26: // 0x6A
case DW_OP_reg27: // 0x6B
case DW_OP_reg28: // 0x6C
case DW_OP_reg29: // 0x6D
case DW_OP_reg30: // 0x6E
case DW_OP_reg31: // 0x6F
case DW_OP_nop: // 0x96
case DW_OP_push_object_address: // 0x97 DWARF3
case DW_OP_form_tls_address: // 0x9b DWARF3
case DW_OP_call_frame_cfa: // 0x9c DWARF3
case DW_OP_stack_value: // 0x9f DWARF4
case DW_OP_GNU_push_tls_address: // 0xe0 GNU extension
return 0;
// Opcodes with a single 1 byte arguments
case DW_OP_const1u: // 0x08 1 1-byte constant
case DW_OP_const1s: // 0x09 1 1-byte constant
case DW_OP_pick: // 0x15 1 1-byte stack index
case DW_OP_deref_size: // 0x94 1 1-byte size of data retrieved
case DW_OP_xderef_size: // 0x95 1 1-byte size of data retrieved
return 1;
// Opcodes with a single 2 byte arguments
case DW_OP_const2u: // 0x0a 1 2-byte constant
case DW_OP_const2s: // 0x0b 1 2-byte constant
case DW_OP_skip: // 0x2f 1 signed 2-byte constant
case DW_OP_bra: // 0x28 1 signed 2-byte constant
case DW_OP_call2: // 0x98 1 2-byte offset of DIE (DWARF3)
return 2;
// Opcodes with a single 4 byte arguments
case DW_OP_const4u: // 0x0c 1 4-byte constant
case DW_OP_const4s: // 0x0d 1 4-byte constant
case DW_OP_call4: // 0x99 1 4-byte offset of DIE (DWARF3)
return 4;
// Opcodes with a single 8 byte arguments
case DW_OP_const8u: // 0x0e 1 8-byte constant
case DW_OP_const8s: // 0x0f 1 8-byte constant
return 8;
// All opcodes that have a single ULEB (signed or unsigned) argument
case DW_OP_constu: // 0x10 1 ULEB128 constant
case DW_OP_consts: // 0x11 1 SLEB128 constant
case DW_OP_plus_uconst: // 0x23 1 ULEB128 addend
case DW_OP_breg0: // 0x70 1 ULEB128 register
case DW_OP_breg1: // 0x71 1 ULEB128 register
case DW_OP_breg2: // 0x72 1 ULEB128 register
case DW_OP_breg3: // 0x73 1 ULEB128 register
case DW_OP_breg4: // 0x74 1 ULEB128 register
case DW_OP_breg5: // 0x75 1 ULEB128 register
case DW_OP_breg6: // 0x76 1 ULEB128 register
case DW_OP_breg7: // 0x77 1 ULEB128 register
case DW_OP_breg8: // 0x78 1 ULEB128 register
case DW_OP_breg9: // 0x79 1 ULEB128 register
case DW_OP_breg10: // 0x7a 1 ULEB128 register
case DW_OP_breg11: // 0x7b 1 ULEB128 register
case DW_OP_breg12: // 0x7c 1 ULEB128 register
case DW_OP_breg13: // 0x7d 1 ULEB128 register
case DW_OP_breg14: // 0x7e 1 ULEB128 register
case DW_OP_breg15: // 0x7f 1 ULEB128 register
case DW_OP_breg16: // 0x80 1 ULEB128 register
case DW_OP_breg17: // 0x81 1 ULEB128 register
case DW_OP_breg18: // 0x82 1 ULEB128 register
case DW_OP_breg19: // 0x83 1 ULEB128 register
case DW_OP_breg20: // 0x84 1 ULEB128 register
case DW_OP_breg21: // 0x85 1 ULEB128 register
case DW_OP_breg22: // 0x86 1 ULEB128 register
case DW_OP_breg23: // 0x87 1 ULEB128 register
case DW_OP_breg24: // 0x88 1 ULEB128 register
case DW_OP_breg25: // 0x89 1 ULEB128 register
case DW_OP_breg26: // 0x8a 1 ULEB128 register
case DW_OP_breg27: // 0x8b 1 ULEB128 register
case DW_OP_breg28: // 0x8c 1 ULEB128 register
case DW_OP_breg29: // 0x8d 1 ULEB128 register
case DW_OP_breg30: // 0x8e 1 ULEB128 register
case DW_OP_breg31: // 0x8f 1 ULEB128 register
case DW_OP_regx: // 0x90 1 ULEB128 register
case DW_OP_fbreg: // 0x91 1 SLEB128 offset
case DW_OP_piece: // 0x93 1 ULEB128 size of piece addressed
case DW_OP_GNU_addr_index: // 0xfb 1 ULEB128 index
case DW_OP_GNU_const_index: // 0xfc 1 ULEB128 index
data.Skip_LEB128(&offset);
return offset - data_offset;
// All opcodes that have a 2 ULEB (signed or unsigned) arguments
case DW_OP_bregx: // 0x92 2 ULEB128 register followed by SLEB128 offset
case DW_OP_bit_piece: // 0x9d ULEB128 bit size, ULEB128 bit offset (DWARF3);
data.Skip_LEB128(&offset);
data.Skip_LEB128(&offset);
return offset - data_offset;
case DW_OP_implicit_value: // 0x9e ULEB128 size followed by block of that size
// (DWARF4)
{
uint64_t block_len = data.Skip_LEB128(&offset);
offset += block_len;
return offset - data_offset;
}
default:
break;
}
return LLDB_INVALID_OFFSET;
}
lldb::addr_t DWARFExpression::GetLocation_DW_OP_addr(uint32_t op_addr_idx,
bool &error) const {
error = false;
if (IsLocationList())
return LLDB_INVALID_ADDRESS;
lldb::offset_t offset = 0;
uint32_t curr_op_addr_idx = 0;
while (m_data.ValidOffset(offset)) {
const uint8_t op = m_data.GetU8(&offset);
if (op == DW_OP_addr) {
const lldb::addr_t op_file_addr = m_data.GetAddress(&offset);
if (curr_op_addr_idx == op_addr_idx)
return op_file_addr;
else
++curr_op_addr_idx;
} else if (op == DW_OP_GNU_addr_index) {
uint64_t index = m_data.GetULEB128(&offset);
if (curr_op_addr_idx == op_addr_idx) {
if (!m_dwarf_cu) {
error = true;
break;
}
return ReadAddressFromDebugAddrSection(m_dwarf_cu, index);
} else
++curr_op_addr_idx;
} else {
const offset_t op_arg_size = GetOpcodeDataSize(m_data, offset, op);
if (op_arg_size == LLDB_INVALID_OFFSET) {
error = true;
break;
}
offset += op_arg_size;
}
}
return LLDB_INVALID_ADDRESS;
}
bool DWARFExpression::Update_DW_OP_addr(lldb::addr_t file_addr) {
if (IsLocationList())
return false;
lldb::offset_t offset = 0;
while (m_data.ValidOffset(offset)) {
const uint8_t op = m_data.GetU8(&offset);
if (op == DW_OP_addr) {
const uint32_t addr_byte_size = m_data.GetAddressByteSize();
// We have to make a copy of the data as we don't know if this data is
// from a read only memory mapped buffer, so we duplicate all of the data
// first, then modify it, and if all goes well, we then replace the data
// for this expression
// So first we copy the data into a heap buffer
std::unique_ptr<DataBufferHeap> head_data_ap(
new DataBufferHeap(m_data.GetDataStart(), m_data.GetByteSize()));
// Make en encoder so we can write the address into the buffer using the
// correct byte order (endianness)
DataEncoder encoder(head_data_ap->GetBytes(), head_data_ap->GetByteSize(),
m_data.GetByteOrder(), addr_byte_size);
// Replace the address in the new buffer
if (encoder.PutMaxU64(offset, addr_byte_size, file_addr) == UINT32_MAX)
return false;
// All went well, so now we can reset the data using a shared pointer to
// the heap data so "m_data" will now correctly manage the heap data.
m_data.SetData(DataBufferSP(head_data_ap.release()));
return true;
} else {
const offset_t op_arg_size = GetOpcodeDataSize(m_data, offset, op);
if (op_arg_size == LLDB_INVALID_OFFSET)
break;
offset += op_arg_size;
}
}
return false;
}
bool DWARFExpression::ContainsThreadLocalStorage() const {
// We are assuming for now that any thread local variable will not have a
// location list. This has been true for all thread local variables we have
// seen so far produced by any compiler.
if (IsLocationList())
return false;
lldb::offset_t offset = 0;
while (m_data.ValidOffset(offset)) {
const uint8_t op = m_data.GetU8(&offset);
if (op == DW_OP_form_tls_address || op == DW_OP_GNU_push_tls_address)
return true;
const offset_t op_arg_size = GetOpcodeDataSize(m_data, offset, op);
if (op_arg_size == LLDB_INVALID_OFFSET)
return false;
else
offset += op_arg_size;
}
return false;
}
bool DWARFExpression::LinkThreadLocalStorage(
lldb::ModuleSP new_module_sp,
std::function<lldb::addr_t(lldb::addr_t file_addr)> const
&link_address_callback) {
// We are assuming for now that any thread local variable will not have a
// location list. This has been true for all thread local variables we have
// seen so far produced by any compiler.
if (IsLocationList())
return false;
const uint32_t addr_byte_size = m_data.GetAddressByteSize();
// We have to make a copy of the data as we don't know if this data is from a
// read only memory mapped buffer, so we duplicate all of the data first,
// then modify it, and if all goes well, we then replace the data for this
// expression
// So first we copy the data into a heap buffer
std::shared_ptr<DataBufferHeap> heap_data_sp(
new DataBufferHeap(m_data.GetDataStart(), m_data.GetByteSize()));
// Make en encoder so we can write the address into the buffer using the
// correct byte order (endianness)
DataEncoder encoder(heap_data_sp->GetBytes(), heap_data_sp->GetByteSize(),
m_data.GetByteOrder(), addr_byte_size);
lldb::offset_t offset = 0;
lldb::offset_t const_offset = 0;
lldb::addr_t const_value = 0;
size_t const_byte_size = 0;
while (m_data.ValidOffset(offset)) {
const uint8_t op = m_data.GetU8(&offset);
bool decoded_data = false;
switch (op) {
case DW_OP_const4u:
// Remember the const offset in case we later have a
// DW_OP_form_tls_address or DW_OP_GNU_push_tls_address
const_offset = offset;
const_value = m_data.GetU32(&offset);
decoded_data = true;
const_byte_size = 4;
break;
case DW_OP_const8u:
// Remember the const offset in case we later have a
// DW_OP_form_tls_address or DW_OP_GNU_push_tls_address
const_offset = offset;
const_value = m_data.GetU64(&offset);
decoded_data = true;
const_byte_size = 8;
break;
case DW_OP_form_tls_address:
case DW_OP_GNU_push_tls_address:
// DW_OP_form_tls_address and DW_OP_GNU_push_tls_address must be preceded
// by a file address on the stack. We assume that DW_OP_const4u or
// DW_OP_const8u is used for these values, and we check that the last
// opcode we got before either of these was DW_OP_const4u or
// DW_OP_const8u. If so, then we can link the value accodingly. For
// Darwin, the value in the DW_OP_const4u or DW_OP_const8u is the file
// address of a structure that contains a function pointer, the pthread
// key and the offset into the data pointed to by the pthread key. So we
// must link this address and also set the module of this expression to
// the new_module_sp so we can resolve the file address correctly
if (const_byte_size > 0) {
lldb::addr_t linked_file_addr = link_address_callback(const_value);
if (linked_file_addr == LLDB_INVALID_ADDRESS)
return false;
// Replace the address in the new buffer
if (encoder.PutMaxU64(const_offset, const_byte_size,
linked_file_addr) == UINT32_MAX)
return false;
}
break;
default:
const_offset = 0;
const_value = 0;
const_byte_size = 0;
break;
}
if (!decoded_data) {
const offset_t op_arg_size = GetOpcodeDataSize(m_data, offset, op);
if (op_arg_size == LLDB_INVALID_OFFSET)
return false;
else
offset += op_arg_size;
}
}
// If we linked the TLS address correctly, update the module so that when the
// expression is evaluated it can resolve the file address to a load address
// and read the
// TLS data
m_module_wp = new_module_sp;
m_data.SetData(heap_data_sp);
return true;
}
bool DWARFExpression::LocationListContainsAddress(
lldb::addr_t loclist_base_addr, lldb::addr_t addr) const {
if (addr == LLDB_INVALID_ADDRESS)
return false;
if (IsLocationList()) {
lldb::offset_t offset = 0;
if (loclist_base_addr == LLDB_INVALID_ADDRESS)
return false;
while (m_data.ValidOffset(offset)) {
// We need to figure out what the value is for the location.
addr_t lo_pc = LLDB_INVALID_ADDRESS;
addr_t hi_pc = LLDB_INVALID_ADDRESS;
if (!AddressRangeForLocationListEntry(m_dwarf_cu, m_data, &offset, lo_pc,
hi_pc))
break;
if (lo_pc == 0 && hi_pc == 0)
break;
lo_pc += loclist_base_addr - m_loclist_slide;
hi_pc += loclist_base_addr - m_loclist_slide;
if (lo_pc <= addr && addr < hi_pc)
return true;
offset += m_data.GetU16(&offset);
}
}
return false;
}
bool DWARFExpression::GetLocation(addr_t base_addr, addr_t pc,
lldb::offset_t &offset,
lldb::offset_t &length) {
offset = 0;
if (!IsLocationList()) {
length = m_data.GetByteSize();
return true;
}
if (base_addr != LLDB_INVALID_ADDRESS && pc != LLDB_INVALID_ADDRESS) {
addr_t curr_base_addr = base_addr;
while (m_data.ValidOffset(offset)) {
// We need to figure out what the value is for the location.
addr_t lo_pc = LLDB_INVALID_ADDRESS;
addr_t hi_pc = LLDB_INVALID_ADDRESS;
if (!AddressRangeForLocationListEntry(m_dwarf_cu, m_data, &offset, lo_pc,
hi_pc))
break;
if (lo_pc == 0 && hi_pc == 0)
break;
lo_pc += curr_base_addr - m_loclist_slide;
hi_pc += curr_base_addr - m_loclist_slide;
length = m_data.GetU16(&offset);
if (length > 0 && lo_pc <= pc && pc < hi_pc)
return true;
offset += length;
}
}
offset = LLDB_INVALID_OFFSET;
length = 0;
return false;
}
bool DWARFExpression::DumpLocationForAddress(Stream *s,
lldb::DescriptionLevel level,
addr_t base_addr, addr_t address,
ABI *abi) {
lldb::offset_t offset = 0;
lldb::offset_t length = 0;
if (GetLocation(base_addr, address, offset, length)) {
if (length > 0) {
DumpLocation(s, offset, length, level, abi);
return true;
}
}
return false;
}
bool DWARFExpression::Evaluate(ExecutionContextScope *exe_scope,
lldb::addr_t loclist_base_load_addr,
const Value *initial_value_ptr,
const Value *object_address_ptr, Value &result,
Status *error_ptr) const {
ExecutionContext exe_ctx(exe_scope);
return Evaluate(&exe_ctx, nullptr, loclist_base_load_addr, initial_value_ptr,
object_address_ptr, result, error_ptr);
}
bool DWARFExpression::Evaluate(ExecutionContext *exe_ctx,
RegisterContext *reg_ctx,
lldb::addr_t loclist_base_load_addr,
const Value *initial_value_ptr,
const Value *object_address_ptr, Value &result,
Status *error_ptr) const {
ModuleSP module_sp = m_module_wp.lock();
if (IsLocationList()) {
lldb::offset_t offset = 0;
addr_t pc;
StackFrame *frame = NULL;
if (reg_ctx)
pc = reg_ctx->GetPC();
else {
frame = exe_ctx->GetFramePtr();
if (!frame)
return false;
RegisterContextSP reg_ctx_sp = frame->GetRegisterContext();
if (!reg_ctx_sp)
return false;
pc = reg_ctx_sp->GetPC();
}
if (loclist_base_load_addr != LLDB_INVALID_ADDRESS) {
if (pc == LLDB_INVALID_ADDRESS) {
if (error_ptr)
error_ptr->SetErrorString("Invalid PC in frame.");
return false;
}
addr_t curr_loclist_base_load_addr = loclist_base_load_addr;
while (m_data.ValidOffset(offset)) {
// We need to figure out what the value is for the location.
addr_t lo_pc = LLDB_INVALID_ADDRESS;
addr_t hi_pc = LLDB_INVALID_ADDRESS;
if (!AddressRangeForLocationListEntry(m_dwarf_cu, m_data, &offset,
lo_pc, hi_pc))
break;
if (lo_pc == 0 && hi_pc == 0)
break;
lo_pc += curr_loclist_base_load_addr - m_loclist_slide;
hi_pc += curr_loclist_base_load_addr - m_loclist_slide;
uint16_t length = m_data.GetU16(&offset);
if (length > 0 && lo_pc <= pc && pc < hi_pc) {
return DWARFExpression::Evaluate(
exe_ctx, reg_ctx, module_sp, m_data, m_dwarf_cu, offset, length,
m_reg_kind, initial_value_ptr, object_address_ptr, result,
error_ptr);
}
offset += length;
}
}
if (error_ptr)
error_ptr->SetErrorString("variable not available");
return false;
}
// Not a location list, just a single expression.
return DWARFExpression::Evaluate(
exe_ctx, reg_ctx, module_sp, m_data, m_dwarf_cu, 0, m_data.GetByteSize(),
m_reg_kind, initial_value_ptr, object_address_ptr, result, error_ptr);
}
bool DWARFExpression::Evaluate(
ExecutionContext *exe_ctx, RegisterContext *reg_ctx,
lldb::ModuleSP module_sp, const DataExtractor &opcodes,
DWARFUnit *dwarf_cu, const lldb::offset_t opcodes_offset,
const lldb::offset_t opcodes_length, const lldb::RegisterKind reg_kind,
const Value *initial_value_ptr, const Value *object_address_ptr,
Value &result, Status *error_ptr) {
if (opcodes_length == 0) {
if (error_ptr)
error_ptr->SetErrorString(
"no location, value may have been optimized out");
return false;
}
std::vector<Value> stack;
Process *process = NULL;
StackFrame *frame = NULL;
if (exe_ctx) {
process = exe_ctx->GetProcessPtr();
frame = exe_ctx->GetFramePtr();
}
if (reg_ctx == NULL && frame)
reg_ctx = frame->GetRegisterContext().get();
if (initial_value_ptr)
stack.push_back(*initial_value_ptr);
lldb::offset_t offset = opcodes_offset;
const lldb::offset_t end_offset = opcodes_offset + opcodes_length;
Value tmp;
uint32_t reg_num;
/// Insertion point for evaluating multi-piece expression.
uint64_t op_piece_offset = 0;
Value pieces; // Used for DW_OP_piece
// Make sure all of the data is available in opcodes.
if (!opcodes.ValidOffsetForDataOfSize(opcodes_offset, opcodes_length)) {
if (error_ptr)
error_ptr->SetErrorString(
"invalid offset and/or length for opcodes buffer.");
return false;
}
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));
while (opcodes.ValidOffset(offset) && offset < end_offset) {
const lldb::offset_t op_offset = offset;
const uint8_t op = opcodes.GetU8(&offset);
if (log && log->GetVerbose()) {
size_t count = stack.size();
log->Printf("Stack before operation has %" PRIu64 " values:",
(uint64_t)count);
for (size_t i = 0; i < count; ++i) {
StreamString new_value;
new_value.Printf("[%" PRIu64 "]", (uint64_t)i);
stack[i].Dump(&new_value);
log->Printf(" %s", new_value.GetData());
}
log->Printf("0x%8.8" PRIx64 ": %s", op_offset, DW_OP_value_to_name(op));
}
switch (op) {
//----------------------------------------------------------------------
// The DW_OP_addr operation has a single operand that encodes a machine
// address and whose size is the size of an address on the target machine.
//----------------------------------------------------------------------
case DW_OP_addr:
stack.push_back(Scalar(opcodes.GetAddress(&offset)));
stack.back().SetValueType(Value::eValueTypeFileAddress);
// Convert the file address to a load address, so subsequent
// DWARF operators can operate on it.
if (frame)
stack.back().ConvertToLoadAddress(module_sp.get(),
frame->CalculateTarget().get());
break;
//----------------------------------------------------------------------
// The DW_OP_addr_sect_offset4 is used for any location expressions in
// shared libraries that have a location like:
// DW_OP_addr(0x1000)
// If this address resides in a shared library, then this virtual address
// won't make sense when it is evaluated in the context of a running
// process where shared libraries have been slid. To account for this, this
// new address type where we can store the section pointer and a 4 byte
// offset.
//----------------------------------------------------------------------
// case DW_OP_addr_sect_offset4:
// {
// result_type = eResultTypeFileAddress;
// lldb::Section *sect = (lldb::Section
// *)opcodes.GetMaxU64(&offset, sizeof(void *));
// lldb::addr_t sect_offset = opcodes.GetU32(&offset);
//
// Address so_addr (sect, sect_offset);
// lldb::addr_t load_addr = so_addr.GetLoadAddress();
// if (load_addr != LLDB_INVALID_ADDRESS)
// {
// // We successfully resolve a file address to a load
// // address.
// stack.push_back(load_addr);
// break;
// }
// else
// {
// // We were able
// if (error_ptr)
// error_ptr->SetErrorStringWithFormat ("Section %s in
// %s is not currently loaded.\n",
// sect->GetName().AsCString(),
// sect->GetModule()->GetFileSpec().GetFilename().AsCString());
// return false;
// }
// }
// break;
//----------------------------------------------------------------------
// OPCODE: DW_OP_deref
// OPERANDS: none
// DESCRIPTION: Pops the top stack entry and treats it as an address.
// The value retrieved from that address is pushed. The size of the data
// retrieved from the dereferenced address is the size of an address on the
// target machine.
//----------------------------------------------------------------------
case DW_OP_deref: {
if (stack.empty()) {
if (error_ptr)
error_ptr->SetErrorString("Expression stack empty for DW_OP_deref.");
return false;
}
Value::ValueType value_type = stack.back().GetValueType();
switch (value_type) {
case Value::eValueTypeHostAddress: {
void *src = (void *)stack.back().GetScalar().ULongLong();
intptr_t ptr;
::memcpy(&ptr, src, sizeof(void *));
stack.back().GetScalar() = ptr;
stack.back().ClearContext();
} break;
case Value::eValueTypeLoadAddress:
if (exe_ctx) {
if (process) {
lldb::addr_t pointer_addr =
stack.back().GetScalar().ULongLong(LLDB_INVALID_ADDRESS);
Status error;
lldb::addr_t pointer_value =
process->ReadPointerFromMemory(pointer_addr, error);
if (pointer_value != LLDB_INVALID_ADDRESS) {
stack.back().GetScalar() = pointer_value;
stack.back().ClearContext();
} else {
if (error_ptr)
error_ptr->SetErrorStringWithFormat(
"Failed to dereference pointer from 0x%" PRIx64
" for DW_OP_deref: %s\n",
pointer_addr, error.AsCString());
return false;
}
} else {
if (error_ptr)
error_ptr->SetErrorStringWithFormat(
"NULL process for DW_OP_deref.\n");
return false;
}
} else {
if (error_ptr)
error_ptr->SetErrorStringWithFormat(
"NULL execution context for DW_OP_deref.\n");
return false;
}
break;
default:
break;
}
} break;
//----------------------------------------------------------------------
// OPCODE: DW_OP_deref_size
// OPERANDS: 1
// 1 - uint8_t that specifies the size of the data to dereference.
// DESCRIPTION: Behaves like the DW_OP_deref operation: it pops the top
// stack entry and treats it as an address. The value retrieved from that
// address is pushed. In the DW_OP_deref_size operation, however, the size
// in bytes of the data retrieved from the dereferenced address is
// specified by the single operand. This operand is a 1-byte unsigned
// integral constant whose value may not be larger than the size of an
// address on the target machine. The data retrieved is zero extended to
// the size of an address on the target machine before being pushed on the
// expression stack.
//----------------------------------------------------------------------
case DW_OP_deref_size: {
if (stack.empty()) {
if (error_ptr)
error_ptr->SetErrorString(
"Expression stack empty for DW_OP_deref_size.");
return false;
}
uint8_t size = opcodes.GetU8(&offset);
Value::ValueType value_type = stack.back().GetValueType();
switch (value_type) {
case Value::eValueTypeHostAddress: {
void *src = (void *)stack.back().GetScalar().ULongLong();
intptr_t ptr;
::memcpy(&ptr, src, sizeof(void *));
// I can't decide whether the size operand should apply to the bytes in
// their
// lldb-host endianness or the target endianness.. I doubt this'll ever
// come up but I'll opt for assuming big endian regardless.
switch (size) {
case 1:
ptr = ptr & 0xff;
break;
case 2:
ptr = ptr & 0xffff;
break;
case 3:
ptr = ptr & 0xffffff;
break;
case 4:
ptr = ptr & 0xffffffff;
break;
// the casts are added to work around the case where intptr_t is a 32
// bit quantity;
// presumably we won't hit the 5..7 cases if (void*) is 32-bits in this
// program.
case 5:
ptr = (intptr_t)ptr & 0xffffffffffULL;
break;
case 6:
ptr = (intptr_t)ptr & 0xffffffffffffULL;
break;
case 7:
ptr = (intptr_t)ptr & 0xffffffffffffffULL;
break;
default:
break;
}
stack.back().GetScalar() = ptr;
stack.back().ClearContext();
} break;
case Value::eValueTypeLoadAddress:
if (exe_ctx) {
if (process) {
lldb::addr_t pointer_addr =
stack.back().GetScalar().ULongLong(LLDB_INVALID_ADDRESS);
uint8_t addr_bytes[sizeof(lldb::addr_t)];
Status error;
if (process->ReadMemory(pointer_addr, &addr_bytes, size, error) ==
size) {
DataExtractor addr_data(addr_bytes, sizeof(addr_bytes),
process->GetByteOrder(), size);
lldb::offset_t addr_data_offset = 0;
switch (size) {
case 1:
stack.back().GetScalar() = addr_data.GetU8(&addr_data_offset);
break;
case 2:
stack.back().GetScalar() = addr_data.GetU16(&addr_data_offset);
break;
case 4:
stack.back().GetScalar() = addr_data.GetU32(&addr_data_offset);
break;
case 8:
stack.back().GetScalar() = addr_data.GetU64(&addr_data_offset);
break;
default:
stack.back().GetScalar() =
addr_data.GetPointer(&addr_data_offset);
}
stack.back().ClearContext();
} else {
if (error_ptr)
error_ptr->SetErrorStringWithFormat(
"Failed to dereference pointer from 0x%" PRIx64
" for DW_OP_deref: %s\n",
pointer_addr, error.AsCString());
return false;
}
} else {
if (error_ptr)
error_ptr->SetErrorStringWithFormat(
"NULL process for DW_OP_deref.\n");
return false;
}
} else {
if (error_ptr)
error_ptr->SetErrorStringWithFormat(
"NULL execution context for DW_OP_deref.\n");
return false;
}
break;
default:
break;
}
} break;
//----------------------------------------------------------------------
// OPCODE: DW_OP_xderef_size
// OPERANDS: 1
// 1 - uint8_t that specifies the size of the data to dereference.
// DESCRIPTION: Behaves like the DW_OP_xderef operation: the entry at
// the top of the stack is treated as an address. The second stack entry is
// treated as an "address space identifier" for those architectures that
// support multiple address spaces. The top two stack elements are popped,
// a data item is retrieved through an implementation-defined address
// calculation and pushed as the new stack top. In the DW_OP_xderef_size
// operation, however, the size in bytes of the data retrieved from the
// dereferenced address is specified by the single operand. This operand is
// a 1-byte unsigned integral constant whose value may not be larger than
// the size of an address on the target machine. The data retrieved is zero
// extended to the size of an address on the target machine before being
// pushed on the expression stack.
//----------------------------------------------------------------------
case DW_OP_xderef_size:
if (error_ptr)
error_ptr->SetErrorString("Unimplemented opcode: DW_OP_xderef_size.");
return false;
//----------------------------------------------------------------------
// OPCODE: DW_OP_xderef
// OPERANDS: none
// DESCRIPTION: Provides an extended dereference mechanism. The entry at
// the top of the stack is treated as an address. The second stack entry is
// treated as an "address space identifier" for those architectures that
// support multiple address spaces. The top two stack elements are popped,
// a data item is retrieved through an implementation-defined address
// calculation and pushed as the new stack top. The size of the data
// retrieved from the dereferenced address is the size of an address on the
// target machine.
//----------------------------------------------------------------------
case DW_OP_xderef:
if (error_ptr)
error_ptr->SetErrorString("Unimplemented opcode: DW_OP_xderef.");
return false;
//----------------------------------------------------------------------
// All DW_OP_constXXX opcodes have a single operand as noted below:
//
// Opcode Operand 1
// --------------- ----------------------------------------------------
// DW_OP_const1u 1-byte unsigned integer constant DW_OP_const1s
// 1-byte signed integer constant DW_OP_const2u 2-byte unsigned integer
// constant DW_OP_const2s 2-byte signed integer constant DW_OP_const4u
// 4-byte unsigned integer constant DW_OP_const4s 4-byte signed integer
// constant DW_OP_const8u 8-byte unsigned integer constant DW_OP_const8s
// 8-byte signed integer constant DW_OP_constu unsigned LEB128 integer
// constant DW_OP_consts signed LEB128 integer constant
//----------------------------------------------------------------------
case DW_OP_const1u:
stack.push_back(Scalar((uint8_t)opcodes.GetU8(&offset)));
break;
case DW_OP_const1s:
stack.push_back(Scalar((int8_t)opcodes.GetU8(&offset)));
break;
case DW_OP_const2u:
stack.push_back(Scalar((uint16_t)opcodes.GetU16(&offset)));
break;
case DW_OP_const2s:
stack.push_back(Scalar((int16_t)opcodes.GetU16(&offset)));
break;
case DW_OP_const4u:
stack.push_back(Scalar((uint32_t)opcodes.GetU32(&offset)));
break;
case DW_OP_const4s:
stack.push_back(Scalar((int32_t)opcodes.GetU32(&offset)));
break;
case DW_OP_const8u:
stack.push_back(Scalar((uint64_t)opcodes.GetU64(&offset)));
break;
case DW_OP_const8s:
stack.push_back(Scalar((int64_t)opcodes.GetU64(&offset)));
break;
case DW_OP_constu:
stack.push_back(Scalar(opcodes.GetULEB128(&offset)));
break;
case DW_OP_consts:
stack.push_back(Scalar(opcodes.GetSLEB128(&offset)));
break;
//----------------------------------------------------------------------
// OPCODE: DW_OP_dup
// OPERANDS: none
// DESCRIPTION: duplicates the value at the top of the stack
//----------------------------------------------------------------------
case DW_OP_dup:
if (stack.empty()) {
if (error_ptr)
error_ptr->SetErrorString("Expression stack empty for DW_OP_dup.");
return false;
} else
stack.push_back(stack.back());
break;
//----------------------------------------------------------------------
// OPCODE: DW_OP_drop
// OPERANDS: none
// DESCRIPTION: pops the value at the top of the stack
//----------------------------------------------------------------------
case DW_OP_drop:
if (stack.empty()) {
if (error_ptr)
error_ptr->SetErrorString("Expression stack empty for DW_OP_drop.");
return false;
} else
stack.pop_back();
break;
//----------------------------------------------------------------------
// OPCODE: DW_OP_over
// OPERANDS: none
// DESCRIPTION: Duplicates the entry currently second in the stack at
// the top of the stack.
//----------------------------------------------------------------------
case DW_OP_over:
if (stack.size() < 2) {
if (error_ptr)
error_ptr->SetErrorString(
"Expression stack needs at least 2 items for DW_OP_over.");
return false;
} else
stack.push_back(stack[stack.size() - 2]);
break;
//----------------------------------------------------------------------
// OPCODE: DW_OP_pick
// OPERANDS: uint8_t index into the current stack
// DESCRIPTION: The stack entry with the specified index (0 through 255,
// inclusive) is pushed on the stack
//----------------------------------------------------------------------
case DW_OP_pick: {
uint8_t pick_idx = opcodes.GetU8(&offset);
if (pick_idx < stack.size())
stack.push_back(stack[pick_idx]);
else {
if (error_ptr)
error_ptr->SetErrorStringWithFormat(
"Index %u out of range for DW_OP_pick.\n", pick_idx);
return false;
}
} break;
//----------------------------------------------------------------------
// OPCODE: DW_OP_swap
// OPERANDS: none
// DESCRIPTION: swaps the top two stack entries. The entry at the top
// of the stack becomes the second stack entry, and the second entry
// becomes the top of the stack
//----------------------------------------------------------------------
case DW_OP_swap:
if (stack.size() < 2) {
if (error_ptr)
error_ptr->SetErrorString(
"Expression stack needs at least 2 items for DW_OP_swap.");
return false;
} else {
tmp = stack.back();
stack.back() = stack[stack.size() - 2];
stack[stack.size() - 2] = tmp;
}
break;
//----------------------------------------------------------------------
// OPCODE: DW_OP_rot
// OPERANDS: none
// DESCRIPTION: Rotates the first three stack entries. The entry at
// the top of the stack becomes the third stack entry, the second entry
// becomes the top of the stack, and the third entry becomes the second
// entry.
//----------------------------------------------------------------------
case DW_OP_rot:
if (stack.size() < 3) {
if (error_ptr)
error_ptr->SetErrorString(
"Expression stack needs at least 3 items for DW_OP_rot.");
return false;
} else {
size_t last_idx = stack.size() - 1;
Value old_top = stack[last_idx];
stack[last_idx] = stack[last_idx - 1];
stack[last_idx - 1] = stack[last_idx - 2];
stack[last_idx - 2] = old_top;
}
break;
//----------------------------------------------------------------------
// OPCODE: DW_OP_abs
// OPERANDS: none
// DESCRIPTION: pops the top stack entry, interprets it as a signed
// value and pushes its absolute value. If the absolute value can not be
// represented, the result is undefined.
//----------------------------------------------------------------------
case DW_OP_abs:
if (stack.empty()) {
if (error_ptr)
error_ptr->SetErrorString(
"Expression stack needs at least 1 item for DW_OP_abs.");
return false;
} else if (stack.back().ResolveValue(exe_ctx).AbsoluteValue() == false) {
if (error_ptr)
error_ptr->SetErrorString(
"Failed to take the absolute value of the first stack item.");
return false;
}
break;
//----------------------------------------------------------------------
// OPCODE: DW_OP_and
// OPERANDS: none
// DESCRIPTION: pops the top two stack values, performs a bitwise and
// operation on the two, and pushes the result.
//----------------------------------------------------------------------
case DW_OP_and:
if (stack.size() < 2) {
if (error_ptr)
error_ptr->SetErrorString(
"Expression stack needs at least 2 items for DW_OP_and.");
return false;
} else {
tmp = stack.back();
stack.pop_back();
stack.back().ResolveValue(exe_ctx) =
stack.back().ResolveValue(exe_ctx) & tmp.ResolveValue(exe_ctx);
}
break;
//----------------------------------------------------------------------
// OPCODE: DW_OP_div
// OPERANDS: none
// DESCRIPTION: pops the top two stack values, divides the former second
// entry by the former top of the stack using signed division, and pushes
// the result.
//----------------------------------------------------------------------
case DW_OP_div:
if (stack.size() < 2) {
if (error_ptr)
error_ptr->SetErrorString(
"Expression stack needs at least 2 items for DW_OP_div.");
return false;
} else {
tmp = stack.back();
if (tmp.ResolveValue(exe_ctx).IsZero()) {
if (error_ptr)
error_ptr->SetErrorString("Divide by zero.");
return false;
} else {
stack.pop_back();
stack.back() =
stack.back().ResolveValue(exe_ctx) / tmp.ResolveValue(exe_ctx);
if (!stack.back().ResolveValue(exe_ctx).IsValid()) {
if (error_ptr)
error_ptr->SetErrorString("Divide failed.");
return false;
}
}
}
break;
//----------------------------------------------------------------------
// OPCODE: DW_OP_minus
// OPERANDS: none
// DESCRIPTION: pops the top two stack values, subtracts the former top
// of the stack from the former second entry, and pushes the result.
//----------------------------------------------------------------------
case DW_OP_minus:
if (stack.size() < 2) {
if (error_ptr)
error_ptr->SetErrorString(
"Expression stack needs at least 2 items for DW_OP_minus.");
return false;
} else {
tmp = stack.back();
stack.pop_back();
stack.back().ResolveValue(exe_ctx) =
stack.back().ResolveValue(exe_ctx) - tmp.ResolveValue(exe_ctx);
}
break;
//----------------------------------------------------------------------
// OPCODE: DW_OP_mod
// OPERANDS: none