diff options
Diffstat (limited to 'lldb/source/Plugins/Process/MacOSX-User')
33 files changed, 15833 insertions, 0 deletions
diff --git a/lldb/source/Plugins/Process/MacOSX-User/scripts/cc-swig b/lldb/source/Plugins/Process/MacOSX-User/scripts/cc-swig new file mode 100644 index 00000000000..0bb089a653e --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/scripts/cc-swig @@ -0,0 +1,47 @@ +#!/usr/bin/perl + +use File::Basename; + +sub execute_command +{ + print join(' ', @_), "\n"; + if (scalar(@_) > 0) { + system(@_); + } else { + system($_[0]); + } +} + +my $infile = $ENV{SCRIPT_INPUT_FILE_1}; +my($in_basename, $in_dirname, $in_extension) = fileparse($infile, qr/\.[^.]*/); +my $outdir = "$ENV{DERIVED_FILE_DIR}"; +my $perl_wrap_c = "$outdir/${in_basename}_perl_wrap.c"; +mkdir "$ENV{OBJECT_FILE_DIR}"; +my $perl_wrap_o = "$ENV{OBJECT_FILE_DIR}/${in_basename}_perl_wrap.o"; +my $perl_module = "$outdir/${in_basename}.pm"; +my $header_paths = "-I'../../../../../debugcore/source' -I'../../../../../DebugBase'"; +my $framework_opts = "-F'$ENV{CONFIGURATION_BUILD_DIR}' "; +execute_command("/usr/bin/swig -shadow -perl5 -DHAS_BOOL $header_paths -outdir '$outdir' -o '$perl_wrap_c' '$infile'"); + +# Get any needed perl options for the next compile +my $ccopts = `perl -MExtUtils::Embed -e ccopts`; +my $libperl_dir = undef; +if ($ccopts =~ /-I(\/System.*CORE)/) +{ + $libperl_dir = $1; + print "libperl directory: '$libperl_dir'\n"; +} + +execute_command("cd '$ENV{OBJECT_FILE_DIR}' && ln -s '$libperl_dir/libperl.dylib'"); + + +# Strip out the default architectures it gave us, we will add them back with +# the $arch_opts below +$ccopts =~ s/-arch [a-z_0-9]+//g; + +# Get a list of our build architectures +my $arch_opts = "-arch " . join(' -arch ', split('\s+', $ENV{ARCHS})); + +execute_command("gcc -c -Dbool=char $arch_opts $ccopts $header_paths $framework_opts -I'$ENV{PROJECT_DIR}/source' '$perl_wrap_c' -o '$perl_wrap_o'"); + +execute_command("cp '$perl_module' '$ENV{CONFIGURATION_BUILD_DIR}/$ENV{SHARED_SUPPORT_FOLDER_PATH}'");
\ No newline at end of file diff --git a/lldb/source/Plugins/Process/MacOSX-User/scripts/config.pl b/lldb/source/Plugins/Process/MacOSX-User/scripts/config.pl new file mode 100644 index 00000000000..a6cf6ce2396 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/scripts/config.pl @@ -0,0 +1,71 @@ +#!/usr/bin/perl + +use strict; +my $config_file = "$ENV{SCRIPT_OUTPUT_FILE_0}"; + +# Define the tests we need to run during this configuration +my @config_tests = ( + { + NAME => "HAVE_64_BIT_MACH_EXCEPTIONS", + TEST => "-e '$ENV{SDKROOT}/usr/include/mach/mach_exc.defs'", + COMMENT => "// Defined if we can use 64 bit mach exceptions", + FAIL => "#undef HAVE_64_BIT_MACH_EXCEPTIONS\ +#define mach_exception_data_t exception_data_t\ +#define mach_exception_data_type_t exception_data_type_t\ +#define mach_exc_server exc_server\ +#define MACH_EXCEPTION_CODES 0\n", + SUCCESS => "#define HAVE_64_BIT_MACH_EXCEPTIONS 1\n", + } +); + +#---------------------------------------------------------------------- +# Open the config file +#---------------------------------------------------------------------- +open(CONFIG, "> $config_file") || die "Couldn't open '$config_file' for writing: $!\n"; +print CONFIG "/*" . "-" x 72 . "\n"; +print CONFIG "// This file is auto generated by a config.pl, do not edit by hand!\n"; +print CONFIG "//" . "-" x 72 . "\n"; +print CONFIG "// COMMAND LINE\n"; +print CONFIG "// " . join(' ', @ARGV) . "\n"; +print CONFIG "//" . "-" x 72 . "\n"; +print CONFIG "// ENVIRONMENT\n"; +my $key; +my $val; +while (($key, $val) = each %ENV) +{ + printf CONFIG "// %s = %s\n", $key, $val; +} +print CONFIG "//" . "-" x 72 . "\n"; +print CONFIG "// SETTINGS\n"; +print CONFIG "// config_file: '$config_file'\n"; +print CONFIG "//" . "-" x 72 . "\n"; +print CONFIG "*/\n\n"; +print CONFIG "#ifndef liblldb_PDConfig_h_\n"; +print CONFIG "#define liblldb_PDConfig_h_\n"; + + +#---------------------------------------------------------------------- +# Run the tests +#---------------------------------------------------------------------- +foreach my $test_href (@config_tests) +{ + if (exists $test_href->{COMMENT}) { + print CONFIG "\n$test_href->{COMMENT}\n"; + } else { + print CONFIG "\n// $test_href->{NAME}\n"; + } + + my $test_result = eval "$test_href->{TEST}"; + if ($test_result != 0) + { + print CONFIG "$test_href->{SUCCESS}\n"; + } + else + { + print CONFIG "$test_href->{FAIL}\n"; + } +} + +print CONFIG "#endif // #ifndef liblldb_PDConfig_h_\n"; +close(CONFIG); + diff --git a/lldb/source/Plugins/Process/MacOSX-User/scripts/test-ProcessDebug.pl b/lldb/source/Plugins/Process/MacOSX-User/scripts/test-ProcessDebug.pl new file mode 100755 index 00000000000..96b3115c912 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/scripts/test-ProcessDebug.pl @@ -0,0 +1,409 @@ +#!/usr/bin/perl + +use strict; +use Cwd 'abs_path'; +our $home = $ENV{HOME} || die "ERROR: Couldn't deduce your home directory...\n"; + +our @inc_paths = ( + './include', +); + +my $inc_paths_added = 0; +foreach my $inc_path (@inc_paths) +{ + if (-e $inc_path) + { + push (@INC, abs_path($inc_path)); + $inc_paths_added++; + } +} + +if ($inc_paths_added == 0) +{ + die "Please compile the Release version of lldb\n"; +} + +require lldb; + +# my $state = lldb::eStateAttaching; + +use constant UINT32_MAX => 4294967295; + +#---------------------------------------------------------------------- +# Interactive Commands +#---------------------------------------------------------------------- +our %commands = ( + break => { + name => 'break', # in case an alias is used to get to this command + description => "Sets a breakpoint.", + usage => ["break ADDR"], + function => \&command_set_breakpoint, + runs_target => 0, + }, + delete => { + name => 'delete', # in case an alias is used to get to this command + description => "Deletes one or more breakpoints by ID.\ +If no breakpoint IDs are given all breakpoints will be deleted.\ +If one or more IDs are given, only those breakpoints will be deleted.", + usage => ["delete [ID1 ID2 ...]"], + function => \&command_clear_breakpoint, + runs_target => 0, + }, + continue => { + name => 'continue', # in case an alias is used to get to this command + description => "Continues target execution.", + usage => ["continue [ADDR]"], + function => \&command_continue, + runs_target => 1 + }, + step => { + name => 'step', # in case an alias is used to get to this command + description => "Single steps one instruction.", + usage => ["step"], + function => \&command_step, + runs_target => 1 + }, + info => { + name => 'info', # in case an alias is used to get to this command + description => "Gets info on a variety of things.", + usage => ["info reg", "info thread", "info threads"], + function => \&command_info, + runs_target => 0 + }, + help => { + name => 'help', # in case an alias is used to get to this command + description => "Displays a list of all commands, or help for a specific command.", + usage => ["help", "help CMD"], + function => \&command_help, + runs_target => 0 + } +); + +#---------------------------------------------------------------------- +# Command aliases +#---------------------------------------------------------------------- +our %aliases = ( + b => $commands{break}, + c => $commands{continue}, + s => $commands{step}, + d => $commands{delete}, + h => $commands{help} +); + +our $opt_g = 0; # Enable verbose debug logging +our $opt_v = 0; # Verbose mode +my $prev_command_href = undef; +my $stdio = '/dev/stdin'; +my $launch = 0; +my @env = (); +my @break_ids; + +#---------------------------------------------------------------------- +# Given a command string, return the command hash reference for it, or +# undef if it doesn't exist. +#---------------------------------------------------------------------- +sub get_command_hash_ref +{ + my $cmd = shift; + my $cmd_href = undef; + if (length($cmd) == 0) { $cmd_href = $prev_command_href; } + elsif (exists $aliases{$cmd}) { $cmd_href = $aliases{$cmd}; } + elsif (exists $commands{$cmd}) { $cmd_href = $commands{$cmd}; } + defined $cmd_href and $prev_command_href = $cmd_href; + return $cmd_href; +} + +#---------------------------------------------------------------------- +# Set a breakpoint +#---------------------------------------------------------------------- +sub command_set_breakpoint +{ + my $pid = shift; + my $tid = shift; + $opt_g and print "command_set_breakpoint (pid = $pid, locations = @_)\n"; + foreach my $location (@_) + { + my $success = 0; + my $address = hex($location); + if ($address != 0) + { + my $break_id = lldb::PDBreakpointSet ($pid, $address, 1, 0); + if ($break_id != $lldb::PD_INVALID_BREAK_ID) + { + printf("Breakpoint %i is set.\n", $break_id); + push(@break_ids, $break_id); + $success = 1; + } + } + $success or print("error: failed to set breakpoint at $location.\n"); + } + return 1; +} + +#---------------------------------------------------------------------- +# Clear a breakpoint +#---------------------------------------------------------------------- +sub command_clear_breakpoint +{ + my $pid = shift; + my $tid = shift; + if (@_) + { + my $break_id; + my @cleared_break_ids; + my @new_break_ids; + $opt_g and print "command_clear_breakpoint (pid = $pid, break_ids = @_)\n"; + foreach $break_id (@_) + { + if (lldb::PDBreakpointClear ($pid, $break_id)) + { + printf("Breakpoint %i has been cleared.\n", $break_id); + push (@cleared_break_ids, $break_id); + } + else + { + printf("error: failed to clear breakpoint %i.\n", $break_id); + } + } + + foreach my $old_break_id (@break_ids) + { + my $found_break_id = 0; + foreach $break_id (@cleared_break_ids) + { + if ($old_break_id == $break_id) + { + $found_break_id = 1; + } + } + $found_break_id or push (@new_break_ids, $old_break_id); + } + @break_ids = @new_break_ids; + } + else + { + # Nothing specified, clear all breakpoints + return command_clear_breakpoint($pid, $tid, @break_ids); + } + return 1; +} +#---------------------------------------------------------------------- +# Continue program execution +#---------------------------------------------------------------------- +sub command_continue +{ + my $pid = shift; + my $tid = shift; + $opt_g and print "command_continue (pid = $pid)\n"; + if ($pid != $lldb::PD_INVALID_PROCESS_ID) + { + $opt_v and printf("Resuming pid %d...\n", $pid); + return lldb::PDProcessResume ($pid); + } + return 0; +} + +sub command_step +{ + my $pid = shift; + my $tid = shift; + $opt_g and print "command_step (pid = $pid, tid = $tid)\n"; + if ($pid != $lldb::PD_INVALID_PROCESS_ID) + { + $opt_v and printf("Single stepping pid %d tid = %4.4x...\n", $pid, $tid); + return lldb::PDThreadResume ($pid, $tid, 1); + } + return 0; +} + +sub command_info +{ + my $pid = shift; + my $tid = shift; + $opt_g and print "command_step (pid = $pid, tid = $tid)\n"; + if ($pid != $lldb::PD_INVALID_PROCESS_ID) + { + if (@_) + { + my $info_cmd = shift; + if ($info_cmd eq 'reg') + { + + } + elsif ($info_cmd eq 'thread') + { + # info on the current thread + printf("thread 0x%4.4x %s\n", $tid, lldb::PDThreadGetInfo($pid, $tid)); + } + elsif ($info_cmd eq 'threads') + { + my $num_threads = lldb::PDProcessGetNumThreads( $pid ); + for my $thread_num (1..$num_threads) + { + my $curr_tid = lldb::PDProcessGetThreadAtIndex ( $pid, $thread_num - 1 ); + printf("%c%u - thread 0x%4.4x %s\n", $curr_tid == $tid ? '*' : ' ', $thread_num, $curr_tid, lldb::PDThreadGetInfo($pid, $curr_tid)); + } + } + } + } + return 1; +} +#---------------------------------------------------------------------- +# Get help on all commands, or a specific list of commands +#---------------------------------------------------------------------- +sub command_help +{ + my $pid = shift; + my $tid = shift; + if (@_) + { + $opt_g and print "command_continue (pid = $pid, commands = @_)\n"; + foreach my $cmd (@_) + { + my $cmd_href = get_command_hash_ref($cmd); + if ($cmd_href) + { + print '#', '-' x 72, "\n# $cmd_href->{name}\n", '#', '-' x 72, "\n"; + my $usage_aref = $cmd_href->{usage}; + if (@{$usage_aref}) + { + print " USAGE\n"; + foreach my $usage (@{$usage_aref}) { + print " $usage\n"; + } + print "\n"; + } + print " DESCRIPTION\n $cmd_href->{description}\n\n"; + } + else + { + print " invalid command: '$cmd'\n\n"; + } + } + } + else + { + return command_help($pid, sort keys %commands); + } + return 1; +} + + +#lldb::PDLogSetLogMask ($lldb::PD_LOG_ALL); +#lldb::PDLogSetLogFile ('/dev/stdout'); + +print "running: ", join(' ', @ARGV), "\n"; + +my $pid = lldb::PDProcessLaunch ($ARGV[0], \@ARGV, \@env, "i386", '/dev/stdin', '/dev/stdout', '/dev/stderr', $launch, '', 0); +my $pid_state; +while ($pid) +{ + $opt_g and printf("PDProcessWaitForEvents (%d, 0x%4.4x, SET, 1)\n", $pid, $lldb::PD_ALL_EVENTS); + my $events = lldb::PDProcessWaitForEvents ($pid, $lldb::PD_ALL_EVENTS, 1, 1); + if ($events) + { + $opt_g and printf ("Got event: 0x%8.8x\n", $events); + + if ($events & $lldb::PD_EVENT_IMAGES_CHANGED) + { + $opt_g and printf("pid %d images changed...\n", $pid); + } + + if ($events & $lldb::PD_EVENT_STDIO) + { + $opt_g and printf("pid %d has stdio...\n", $pid); + } + + if ($events & $lldb::PD_EVENT_ASYNC_INTERRUPT) + { + $opt_g and printf("pid %d got async interrupt...\n", $pid); + } + + if ($events & $lldb::PD_EVENT_RUNNING) + { + $pid_state = lldb::PDProcessGetState ($pid); + $opt_v and printf( "pid %d state: %s.\n", $pid, lldb::PDStateAsString ($pid_state) ); + } + + if ($events & $lldb::PD_EVENT_STOPPED) + { + $pid_state = lldb::PDProcessGetState ($pid); + $opt_v and printf( "pid %d state: %s.\n", $pid, lldb::PDStateAsString ($pid_state) ); + + if ($pid_state == $lldb::eStateUnloaded || + $pid_state == $lldb::eStateAttaching || + $pid_state == $lldb::eStateLaunching ) + { + + } + elsif ( $pid_state == $lldb::eStateStopped ) + { + my $tid = lldb::PDProcessGetCurrentThread ( $pid ); + my $pc = lldb::PDThreadGetRegisterHexValueByName($pid, $tid, $lldb::PD_REGISTER_SET_ALL, "eip", 0); + $pc != 0 and printf("pc = 0x%8.8x ", $pc); + # my $sp = lldb::PDThreadGetRegisterHexValueByName($pid, $tid, $lldb::PD_REGISTER_SET_ALL, "esp", 0); + # $sp != 0 and printf("sp = 0x%8.8x ", $sp); + # my $fp = lldb::PDThreadGetRegisterHexValueByName($pid, $tid, $lldb::PD_REGISTER_SET_ALL, "ebp", 0); + # $sp != 0 and printf("fp = 0x%8.8x ", $fp); + # print "\n"; + my $done = 0; + my $input; + while (!$done) + { + print '(pdbg) '; + + chomp($input = <STDIN>); + my @argv = split(/\s+/, $input); + my $cmd = @argv ? shift @argv : undef; + my $cmd_href = get_command_hash_ref ($cmd); + if ($cmd_href) + { + # Print the expanded alias if one was used + if ($opt_v and $cmd_href->{name} ne $cmd) + { + print "$cmd_href->{name} @argv\n"; + } + + # Call the command's callback function to make things happen + if ($cmd_href->{function}($pid, $tid, @argv)) + { + $done = $cmd_href->{runs_target}; + } + } + else + { + print "invalid command: '$cmd'\nType 'help' for a list of all commands.\nType 'help CMD' for help on a specific commmand.\n"; + } + } + } + elsif ( $pid_state == $lldb::eStateRunning || + $pid_state == $lldb::eStateStepping ) + { + + } + elsif ( $pid_state == $lldb::eStateCrashed || + $pid_state == $lldb::eStateDetached || + $pid_state == $lldb::eStateExited ) + { + $pid = 0; + } + elsif ( $pid_state == $lldb::eStateSuspended ) + { + } + else + { + } + } + + if ($pid) + { + $opt_g and printf("PDProcessResetEvents(%d, 0x%8.8x)\n", $pid, $events); + lldb::PDProcessResetEvents($pid, $events); + } + } +} + +if ($pid != $lldb::PD_INVALID_PROCESS_ID) +{ + lldb::PDProcessDetach ($pid); +} diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachException.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachException.cpp new file mode 100644 index 00000000000..7dc8d2ce652 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachException.cpp @@ -0,0 +1,575 @@ +//===-- MachException.cpp ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include <errno.h> +#include <sys/types.h> +#include <sys/ptrace.h> + +#include "lldb/Core/StreamString.h" +#include "lldb/Host/Host.h" + +#include "MachException.h" +#include "ProcessMacOSXLog.h" + +using namespace lldb_private; + +// Routine mach_exception_raise +extern "C" +kern_return_t catch_mach_exception_raise +( + mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt +); + +extern "C" +kern_return_t catch_mach_exception_raise_state +( + mach_port_t exception_port, + exception_type_t exception, + const mach_exception_data_t code, + mach_msg_type_number_t codeCnt, + int *flavor, + const thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt +); + +// Routine mach_exception_raise_state_identity +extern "C" +kern_return_t catch_mach_exception_raise_state_identity +( + mach_port_t exception_port, + mach_port_t thread, + mach_port_t task, + exception_type_t exception, + mach_exception_data_t code, + mach_msg_type_number_t codeCnt, + int *flavor, + thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt +); + +extern "C" boolean_t mach_exc_server( + mach_msg_header_t *InHeadP, + mach_msg_header_t *OutHeadP); + +// Any access to the g_message variable should be done by locking the +// g_message_mutex first, using the g_message variable, then unlocking +// the g_message_mutex. See MachException::Message::CatchExceptionRaise() +// for sample code. + +static MachException::Data *g_message = NULL; +//static pthread_mutex_t g_message_mutex = PTHREAD_MUTEX_INITIALIZER; + + +extern "C" +kern_return_t +catch_mach_exception_raise_state +( + mach_port_t exc_port, + exception_type_t exc_type, + const mach_exception_data_t exc_data, + mach_msg_type_number_t exc_data_count, + int * flavor, + const thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t * new_stateCnt +) +{ + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_EXCEPTIONS); + if (log) + { + log->Printf("::%s ( exc_port = 0x%4.4x, exc_type = %d ( %s ), exc_data = " MACH_EXCEPTION_DATA_FMT_HEX ", exc_data_count = %d)", + __FUNCTION__, + exc_port, + exc_type, MachException::Name(exc_type), + exc_data, + exc_data_count); + } + return KERN_FAILURE; +} + +extern "C" +kern_return_t +catch_mach_exception_raise_state_identity +( + mach_port_t exc_port, + mach_port_t thread_port, + mach_port_t task_port, + exception_type_t exc_type, + mach_exception_data_t exc_data, + mach_msg_type_number_t exc_data_count, + int * flavor, + thread_state_t old_state, + mach_msg_type_number_t old_stateCnt, + thread_state_t new_state, + mach_msg_type_number_t *new_stateCnt +) +{ + kern_return_t kret; + Log * log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_EXCEPTIONS); + if (log) + { + log->Printf("::%s ( exc_port = 0x%4.4x, thd_port = 0x%4.4x, tsk_port = 0x%4.4x, exc_type = %d ( %s ), exc_data[%d] = { " MACH_EXCEPTION_DATA_FMT_HEX ", " MACH_EXCEPTION_DATA_FMT_HEX " })", + __FUNCTION__, + exc_port, + thread_port, + task_port, + exc_type, MachException::Name(exc_type), + exc_data_count, + exc_data_count > 0 ? exc_data[0] : 0xBADDBADD, + exc_data_count > 1 ? exc_data[1] : 0xBADDBADD); + } + kret = mach_port_deallocate (mach_task_self (), task_port); + kret = mach_port_deallocate (mach_task_self (), thread_port); + + return KERN_FAILURE; +} + +extern "C" +kern_return_t +catch_mach_exception_raise +( + mach_port_t exc_port, + mach_port_t thread_port, + mach_port_t task_port, + exception_type_t exc_type, + mach_exception_data_t exc_data, + mach_msg_type_number_t exc_data_count) +{ + Log * log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_EXCEPTIONS); + if (log) + { + log->Printf("::%s ( exc_port = 0x%4.4x, thd_port = 0x%4.4x, tsk_port = 0x%4.4x, exc_type = %d ( %s ), exc_data[%d] = { " MACH_EXCEPTION_DATA_FMT_HEX ", " MACH_EXCEPTION_DATA_FMT_HEX " })", + __FUNCTION__, + exc_port, + thread_port, + task_port, + exc_type, MachException::Name(exc_type), + exc_data_count, + exc_data_count > 0 ? exc_data[0] : 0xBADDBADD, + exc_data_count > 1 ? exc_data[1] : 0xBADDBADD); + } + + g_message->task_port = task_port; + g_message->thread_port = thread_port; + g_message->exc_type = exc_type; + g_message->exc_data.resize(exc_data_count); + ::memcpy (&g_message->exc_data[0], exc_data, g_message->exc_data.size() * sizeof (mach_exception_data_type_t)); + return KERN_SUCCESS; +} + + +void +MachException::Message::PutToLog(Log *log) const +{ + if (log) + { + log->Printf(" exc_msg { bits = 0x%8.8lx size = 0x%8.8lx remote-port = 0x%8.8lx local-port = 0x%8.8lx reserved = 0x%8.8lx id = 0x%8.8lx } ", + exc_msg.hdr.msgh_bits, + exc_msg.hdr.msgh_size, + exc_msg.hdr.msgh_remote_port, + exc_msg.hdr.msgh_local_port, + exc_msg.hdr.msgh_reserved, + exc_msg.hdr.msgh_id); + + log->Printf( "reply_msg { bits = 0x%8.8lx size = 0x%8.8lx remote-port = 0x%8.8lx local-port = 0x%8.8lx reserved = 0x%8.8lx id = 0x%8.8lx }", + reply_msg.hdr.msgh_bits, + reply_msg.hdr.msgh_size, + reply_msg.hdr.msgh_remote_port, + reply_msg.hdr.msgh_local_port, + reply_msg.hdr.msgh_reserved, + reply_msg.hdr.msgh_id); + state.PutToLog(log); + } +} + +bool +MachException::Data::GetStopInfo(Thread::StopInfo *stop_info) const +{ + // Zero out the structure. + stop_info->Clear(); + + // Make sure we have a valid exception before we return anything valid + if (exc_type == 0) + return true; + // We always stop with a mach exceptions + const size_t exc_data_count = exc_data.size(); + stop_info->SetStopReasonWithException(exc_type, exc_data_count); + + // Fill in a text description + const char * exc_name = MachException::Name(exc_type); + StreamString sstr; + if (exc_name) + sstr.PutCString(exc_name); + else + sstr.Printf ("%i", exc_type); + + int signal = SoftSignal(); + if (signal > 0) + { + const char *sig_str = Host::GetSignalAsCString(signal); + if (sig_str) + sstr.Printf (" EXC_SOFT_SIGNAL(%s)", sig_str); + else + sstr.Printf (" EXC_SOFT_SIGNAL(%i)", signal); + } + else + { + // No special disassembly for exception data, just + sstr.Printf (" data[%zu] = {", exc_data_count); + + for (size_t idx = 0; idx < exc_data_count; ++idx) + sstr.Printf (MACH_EXCEPTION_DATA_FMT_MINHEX "%s", exc_data[idx], ((idx + 1 == exc_data_count) ? "" : ",")); + + sstr.PutChar('}'); + } + + stop_info->SetStopDescription (sstr.GetData()); + + // Copy the exception data + size_t i; + for (i=0; i<exc_data_count; i++) + stop_info->SetExceptionDataAtIndex(i, exc_data[i]); + + return true; +} + + +void +MachException::Data::DumpStopReason() const +{ + Log * log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(); + if (log) + { + int signal = SoftSignal(); + if (signal > 0) + { + const char *signal_str = Host::GetSignalAsCString(signal); + if (signal_str) + log->Printf ("signal(%s)", signal_str); + else + log->Printf ("signal(%i)", signal); + return; + } + log->Printf ("%s", Name(exc_type)); + } +} + +kern_return_t +MachException::Message::Receive(mach_port_t port, mach_msg_option_t options, mach_msg_timeout_t timeout, mach_port_t notify_port) +{ + Error err; + Log * log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_EXCEPTIONS); + mach_msg_timeout_t mach_msg_timeout = options & MACH_RCV_TIMEOUT ? timeout : 0; + if (log && ((options & MACH_RCV_TIMEOUT) == 0)) + { + // Dump this log message if we have no timeout in case it never returns + log->Printf ("::mach_msg ( msg->{bits = %#x, size = %u remote_port = %#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, option = %#x, send_size = %u, rcv_size = %u, rcv_name = %#x, timeout = %u, notify = %#x)", + exc_msg.hdr.msgh_bits, + exc_msg.hdr.msgh_size, + exc_msg.hdr.msgh_remote_port, + exc_msg.hdr.msgh_local_port, + exc_msg.hdr.msgh_reserved, + exc_msg.hdr.msgh_id, + options, + 0, + sizeof (exc_msg.data), + port, + mach_msg_timeout, + notify_port); + } + + err = ::mach_msg (&exc_msg.hdr, + options, // options + 0, // Send size + sizeof (exc_msg.data), // Receive size + port, // exception port to watch for exception on + mach_msg_timeout, // timeout in msec (obeyed only if MACH_RCV_TIMEOUT is ORed into the options parameter) + notify_port); + + // Dump any errors we get + if (log && err.GetError() != MACH_RCV_TIMED_OUT) + { + log->Error("::mach_msg ( msg->{bits = %#x, size = %u remote_port = %#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, option = %#x, send_size = %u, rcv_size = %u, rcv_name = %#x, timeout = %u, notify = %#x)", + exc_msg.hdr.msgh_bits, + exc_msg.hdr.msgh_size, + exc_msg.hdr.msgh_remote_port, + exc_msg.hdr.msgh_local_port, + exc_msg.hdr.msgh_reserved, + exc_msg.hdr.msgh_id, + options, + 0, + sizeof (exc_msg.data), + port, + mach_msg_timeout, + notify_port); + } + return err.GetError(); +} + +bool +MachException::Message::CatchExceptionRaise() +{ + bool success = false; + // locker will keep a mutex locked until it goes out of scope +// Mutex::Locker locker(&g_message_mutex); + // log->Printf ("calling mach_exc_server"); + g_message = &state; + // The exc_server function is the MIG generated server handling function + // to handle messages from the kernel relating to the occurrence of an + // exception in a thread. Such messages are delivered to the exception port + // set via thread_set_exception_ports or task_set_exception_ports. When an + // exception occurs in a thread, the thread sends an exception message to + // its exception port, blocking in the kernel waiting for the receipt of a + // reply. The exc_server function performs all necessary argument handling + // for this kernel message and calls catch_exception_raise, + // catch_exception_raise_state or catch_exception_raise_state_identity, + // which should handle the exception. If the called routine returns + // KERN_SUCCESS, a reply message will be sent, allowing the thread to + // continue from the point of the exception; otherwise, no reply message + // is sent and the called routine must have dealt with the exception + // thread directly. + if (mach_exc_server (&exc_msg.hdr, &reply_msg.hdr)) + { + success = true; + } + else + { + Log * log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_EXCEPTIONS); + if (log) + log->Printf ("mach_exc_server returned zero..."); + } + g_message = NULL; + return success; +} + + + +kern_return_t +MachException::Message::Reply(task_t task, pid_t pid, int signal) +{ + // Reply to the exception... + Error err; + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(); + if (log) + log->Printf("MachException::Message::Reply (task = 0x%4.4x, pid = %i, signal = %i)", task, pid, signal); + + // If we had a soft signal, we need to update the thread first so it can + // continue without signaling + int soft_signal = state.SoftSignal(); + int state_pid = LLDB_INVALID_PROCESS_ID; + if (task == state.task_port) + { + // This is our task, so we can update the signal to send to it + state_pid = pid; + } + else + { + err = ::pid_for_task(state.task_port, &state_pid); + } + + if (signal == LLDB_INVALID_SIGNAL_NUMBER) + signal = 0; + + if (log) + log->Printf("MachException::Message::Reply () updating thread signal to %i (original soft_signal = %i)", signal, soft_signal); + + if (state_pid != LLDB_INVALID_PROCESS_ID) + { + errno = 0; + if (::ptrace (PT_THUPDATE, state_pid, (caddr_t)state.thread_port, signal) != 0) + { + if (soft_signal != LLDB_INVALID_SIGNAL_NUMBER) + // We know we currently can't forward signals for threads that didn't stop in EXC_SOFT_SIGNAL... + // So only report it as an error if we should have been able to do it. + err.SetErrorToErrno(); + else + err.Clear(); + } + else + err.Clear(); + + if (log && log->GetMask().IsSet(PD_LOG_EXCEPTIONS) || err.Fail()) + err.PutToLog(log, "::ptrace (request = PT_THUPDATE, pid = %i, tid = 0x%4.4x, signal = %i)", state_pid, state.thread_port, signal); + } + + err = ::mach_msg ( &reply_msg.hdr, + MACH_SEND_MSG | MACH_SEND_INTERRUPT, + reply_msg.hdr.msgh_size, + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + + if (log) + log->LogIf (PD_LOG_EXCEPTIONS, "::mach_msg ( msg->{bits = %#x, size = %u, remote_port = %#x, local_port = %#x, reserved = 0x%x, id = 0x%x}, option = %#x, send_size = %u, rcv_size = %u, rcv_name = %#x, timeout = %u, notify = %#x) = 0x%8.8x", + reply_msg.hdr.msgh_bits, + reply_msg.hdr.msgh_size, + reply_msg.hdr.msgh_remote_port, + reply_msg.hdr.msgh_local_port, + reply_msg.hdr.msgh_reserved, + reply_msg.hdr.msgh_id, + MACH_SEND_MSG | MACH_SEND_INTERRUPT, + reply_msg.hdr.msgh_size, + 0, + MACH_PORT_NULL, + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL, + err.GetError()); + + + if (err.Fail()) + { + if (err.GetError() == MACH_SEND_INTERRUPTED) + { + err.PutToLog(log, "::mach_msg() - send interrupted"); + } + else + { + if (state.task_port == task) + { + err.PutToLog(log, "::mach_msg() - failed (task)"); + abort (); + } + else + { + err.PutToLog(log, "::mach_msg() - failed (child of task)"); + } + } + } + + return err.GetError(); +} + + +void +MachException::Data::PutToLog(Log *log) const +{ + if (log == NULL) + return; + + const char *exc_type_name = MachException::Name(exc_type); + + log->Printf (" state { task_port = 0x%4.4x, thread_port = 0x%4.4x, exc_type = %i (%s) ...", task_port, thread_port, exc_type, exc_type_name ? exc_type_name : "???"); + + const size_t exc_data_count = exc_data.size(); + // Dump any special exception data contents + int soft_signal = SoftSignal(); + if (soft_signal > 0) + { + const char *sig_str = Host::GetSignalAsCString(soft_signal); + log->Printf (" exc_data: EXC_SOFT_SIGNAL (%i (%s))", soft_signal, sig_str ? sig_str : "unknown signal"); + } + else + { + // No special disassembly for this data, just dump the data + size_t idx; + for (idx = 0; idx < exc_data_count; ++idx) + { + log->Printf(" exc_data[%u]: " MACH_EXCEPTION_DATA_FMT_HEX, idx, exc_data[idx]); + } + } +} + + +MachException::PortInfo::PortInfo() : + count(0) +{ + ::bzero (masks, sizeof(masks)); + ::bzero (ports, sizeof(ports)); + ::bzero (behaviors, sizeof(behaviors)); + ::bzero (flavors, sizeof(flavors)); +} + + +kern_return_t +MachException::PortInfo::Save (task_t task) +{ + count = EXC_TYPES_COUNT; + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_EXCEPTIONS); + if (log) + log->Printf ("MachException::PortInfo::Save (task = 0x%4.4x)", task); + Error err; + if (log) + log->Printf("::task_get_exception_ports (task=0x%4.4x, mask=0x%x, maskCnt<=>%u, ports, behaviors, flavors)...", task, EXC_MASK_ALL, count); + err = ::task_get_exception_ports (task, EXC_MASK_ALL, masks, &count, ports, behaviors, flavors); + if (log || err.Fail()) + err.PutToLog(log, "::task_get_exception_ports (task=0x%4.4x, mask=0x%x, maskCnt<=>%u, ports, behaviors, flavors)", task, EXC_MASK_ALL, count); + if (log) + { + mach_msg_type_number_t i; + log->Printf("Index Mask Port Behavior Flavor", masks[i], ports[i], behaviors[i], flavors[i]); + log->Printf("===== -------- -------- -------- --------"); + for (i=0; i<count; ++i) + log->Printf("[%3u] %8.8x %8.8x %8.8x %8.8x", i, masks[i], ports[i], behaviors[i], flavors[i]); + } + if (err.Fail()) + count = 0; + return err.GetError(); +} + +kern_return_t +MachException::PortInfo::Restore (task_t task) +{ + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_EXCEPTIONS); + if (log && log->GetMask().IsSet(PD_LOG_VERBOSE)) + log->Printf("MachException::PortInfo::Restore (task = 0x%4.4x)", task); + uint32_t i = 0; + Error err; + if (count > 0) + { + for (i = 0; i < count; i++) + { + err = ::task_set_exception_ports (task, masks[i], ports[i], behaviors[i], flavors[i]); + if (log || err.Fail()) + err.PutToLog(log, "::task_set_exception_ports ( task = 0x%4.4x, exception_mask = 0x%8.8x, new_port = 0x%4.4x, behavior = 0x%8.8x, new_flavor = 0x%8.8x )", task, masks[i], ports[i], behaviors[i], flavors[i]); + + if (err.Fail()) + break; + } + } + count = 0; + return err.GetError(); +} + +const char * +MachException::Name(exception_type_t exc_type) +{ + switch (exc_type) + { + case EXC_BAD_ACCESS: return "EXC_BAD_ACCESS"; + case EXC_BAD_INSTRUCTION: return "EXC_BAD_INSTRUCTION"; + case EXC_ARITHMETIC: return "EXC_ARITHMETIC"; + case EXC_EMULATION: return "EXC_EMULATION"; + case EXC_SOFTWARE: return "EXC_SOFTWARE"; + case EXC_BREAKPOINT: return "EXC_BREAKPOINT"; + case EXC_SYSCALL: return "EXC_SYSCALL"; + case EXC_MACH_SYSCALL: return "EXC_MACH_SYSCALL"; + case EXC_RPC_ALERT: return "EXC_RPC_ALERT"; +#ifdef EXC_CRASH + case EXC_CRASH: return "EXC_CRASH"; +#endif + default: + break; + } + return NULL; +} + + + diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachException.h b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachException.h new file mode 100644 index 00000000000..1f3aeb07b0a --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachException.h @@ -0,0 +1,148 @@ +//===-- MachException.h -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + + +#ifndef liblldb_MachException_h_ +#define liblldb_MachException_h_ + +#include <mach/mach.h> +#include <vector> +#include "lldb/lldb-private.h" +#include "lldb/Target/Thread.h" +// TODO: Get the config script to run to this plug-in +//#include "PDConfig.h" +#define HAVE_64_BIT_MACH_EXCEPTIONS // REMOVE THIS WHEN PDConfig.h is included above +#ifdef HAVE_64_BIT_MACH_EXCEPTIONS + +#define MACH_EXCEPTION_DATA_FMT_DEC "%lld" +#define MACH_EXCEPTION_DATA_FMT_HEX "0x%16.16llx" +#define MACH_EXCEPTION_DATA_FMT_MINHEX "0x%llx" + +#else + +#define MACH_EXCEPTION_DATA_FMT_DEC "%d" +#define MACH_EXCEPTION_DATA_FMT_HEX "0x%8.8x" +#define MACH_EXCEPTION_DATA_FMT_MINHEX "0x%x" + +#endif + +class MachProcess; + +typedef union MachMessageTag +{ + mach_msg_header_t hdr; + char data[1024]; +} MachMessage; + + +class MachException +{ +public: + + struct PortInfo + { + exception_mask_t masks[EXC_TYPES_COUNT]; + mach_port_t ports[EXC_TYPES_COUNT]; + exception_behavior_t behaviors[EXC_TYPES_COUNT]; + thread_state_flavor_t flavors[EXC_TYPES_COUNT]; + mach_msg_type_number_t count; + + PortInfo(); + kern_return_t Save(task_t task); + kern_return_t Restore(task_t task); + }; + + struct Data + { + task_t task_port; + lldb::tid_t thread_port; + exception_type_t exc_type; + std::vector<mach_exception_data_type_t> exc_data; + Data() : + task_port(TASK_NULL), + thread_port(THREAD_NULL), + exc_type(0), + exc_data() + { + } + + void Clear() + { + task_port = TASK_NULL; + thread_port = THREAD_NULL; + exc_type = 0; + exc_data.clear(); + } + bool IsValid() const + { + return task_port != TASK_NULL && + thread_port != THREAD_NULL && + exc_type != 0; + } + // Return the SoftSignal for this MachException data, or zero if there is none + int SoftSignal() const + { + if (exc_type == EXC_SOFTWARE && exc_data.size() == 2 && exc_data[0] == EXC_SOFT_SIGNAL) + return exc_data[1]; + return LLDB_INVALID_SIGNAL_NUMBER; + } + bool IsBreakpoint() const + { + return (exc_type == EXC_BREAKPOINT) || ((exc_type == EXC_SOFTWARE) && exc_data[0] == 1); + } + void PutToLog(lldb_private::Log *log) const; + void DumpStopReason() const; + bool GetStopInfo(lldb_private::Thread::StopInfo *stop_info) const; + }; + + struct Message + { + MachMessage exc_msg; + MachMessage reply_msg; + Data state; + + Message() : + exc_msg(), + reply_msg(), + state() + { + memset(&exc_msg, 0, sizeof(exc_msg)); + memset(&reply_msg, 0, sizeof(reply_msg)); + } + bool CatchExceptionRaise(); + void PutToLog(lldb_private::Log *log) const; + kern_return_t Reply (task_t task, pid_t pid, int signal); + kern_return_t Receive( mach_port_t receive_port, + mach_msg_option_t options, + mach_msg_timeout_t timeout, + mach_port_t notify_port = MACH_PORT_NULL); + + typedef std::vector<Message> collection; + typedef collection::iterator iterator; + typedef collection::const_iterator const_iterator; + }; + + enum + { + e_actionForward, // Forward signal to inferior process + e_actionStop, // Stop when this signal is received + }; + struct Action + { + task_t task_port; // Set to TASK_NULL for any TASK + lldb::tid_t thread_port; // Set to THREAD_NULL for any thread + exception_type_t exc_mask; // Mach exception mask to watch for + std::vector<mach_exception_data_type_t> exc_data_mask; // Mask to apply to exception data, or empty to ignore exc_data value for exception + std::vector<mach_exception_data_type_t> exc_data_value; // Value to compare to exception data after masking, or empty to ignore exc_data value for exception + uint8_t flags; // Action flags describing what to do with the exception + }; + static const char *Name(exception_type_t exc_type); +}; + +#endif diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachTask.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachTask.cpp new file mode 100644 index 00000000000..1a0c3c48997 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachTask.cpp @@ -0,0 +1,674 @@ +//===-- MachTask.cpp --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MachTask.h" + +// C Includes +// C++ Includes + +// Other libraries and framework includes +#if defined (__arm__) + +#include <CoreFoundation/CoreFoundation.h> +#include <SpringBoardServices/SpringBoardServer.h> +#include <SpringBoardServices/SBSWatchdogAssertion.h> + +#endif + +#include "lldb/Host/Host.h" +#include "lldb/Core/DataExtractor.h" + +// Project includes +#include "ProcessMacOSX.h" +#include "ProcessMacOSXLog.h" + + +using namespace lldb; +using namespace lldb_private; + +//---------------------------------------------------------------------- +// MachTask constructor +//---------------------------------------------------------------------- +MachTask::MachTask(ProcessMacOSX *process) : + m_process (process), + m_task (TASK_NULL), + m_vm_memory (), + m_exception_thread (0), + m_exception_port (MACH_PORT_NULL), + m_exc_port_info() +{ + memset(&m_exc_port_info, 0, sizeof(m_exc_port_info)); + +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +MachTask::~MachTask() +{ + Clear(); +} + + +//---------------------------------------------------------------------- +// MachTask::Suspend +//---------------------------------------------------------------------- +kern_return_t +MachTask::Suspend() +{ + Error err; + task_t task = GetTaskPort(); + err = ::task_suspend (task); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_TASK); + if (log || err.Fail()) + err.PutToLog(log, "::task_suspend ( target_task = 0x%4.4x )", task); + return err.GetError(); +} + + +//---------------------------------------------------------------------- +// MachTask::Resume +//---------------------------------------------------------------------- +kern_return_t +MachTask::Resume() +{ + Error err; + task_t task = GetTaskPort(); + err = ::task_resume (task); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_TASK); + if (log || err.Fail()) + err.PutToLog(log, "::task_resume ( target_task = 0x%4.4x )", task); + return err.GetError(); +} + +int32_t +MachTask::GetSuspendCount () const +{ + struct task_basic_info task_info; + if (BasicInfo(&task_info) == KERN_SUCCESS) + return task_info.suspend_count; + return -1; +} + +//---------------------------------------------------------------------- +// MachTask::ExceptionPort +//---------------------------------------------------------------------- +mach_port_t +MachTask::ExceptionPort() const +{ + return m_exception_port; +} + +//---------------------------------------------------------------------- +// MachTask::ExceptionPortIsValid +//---------------------------------------------------------------------- +bool +MachTask::ExceptionPortIsValid() const +{ + return MACH_PORT_VALID(m_exception_port); +} + + +//---------------------------------------------------------------------- +// MachTask::Clear +//---------------------------------------------------------------------- +void +MachTask::Clear() +{ + // Do any cleanup needed for this task + m_task = TASK_NULL; + m_exception_thread = 0; + m_exception_port = MACH_PORT_NULL; + +} + + +//---------------------------------------------------------------------- +// MachTask::SaveExceptionPortInfo +//---------------------------------------------------------------------- +kern_return_t +MachTask::SaveExceptionPortInfo() +{ + return m_exc_port_info.Save(GetTaskPort()); +} + +//---------------------------------------------------------------------- +// MachTask::RestoreExceptionPortInfo +//---------------------------------------------------------------------- +kern_return_t +MachTask::RestoreExceptionPortInfo() +{ + return m_exc_port_info.Restore(GetTaskPort()); +} + + +//---------------------------------------------------------------------- +// MachTask::ReadMemory +//---------------------------------------------------------------------- +size_t +MachTask::ReadMemory (lldb::addr_t addr, void *buf, size_t size, Error& error) +{ + size_t n = 0; + task_t task = GetTaskPort(); + if (task != TASK_NULL) + { + n = m_vm_memory.Read(task, addr, buf, size, error); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_MEMORY); + if (log) + { + log->Printf ("MachTask::ReadMemory ( addr = 0x%16.16llx, size = %zu, buf = %8.8p) => %u bytes read", (uint64_t)addr, size, buf, n); + if (log->GetMask().IsSet(PD_LOG_MEMORY_DATA_LONG) || (log->GetMask().IsSet(PD_LOG_MEMORY_DATA_SHORT) && size <= 8)) + { + DataExtractor data((uint8_t*)buf, n, eByteOrderHost, 4); + data.PutToLog(log, 0, n, addr, 16, DataExtractor::TypeUInt8); + } + } + } + return n; +} + + +//---------------------------------------------------------------------- +// MachTask::WriteMemory +//---------------------------------------------------------------------- +size_t +MachTask::WriteMemory (lldb::addr_t addr, const void *buf, size_t size, Error& error) +{ + size_t n = 0; + task_t task = GetTaskPort(); + if (task != TASK_NULL) + { + n = m_vm_memory.Write(task, addr, buf, size, error); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_MEMORY); + if (log) + { + log->Printf ("MachTask::WriteMemory ( addr = 0x%16.16llx, size = %zu, buf = %8.8p) => %u bytes written", (uint64_t)addr, size, buf, n); + if (log->GetMask().IsSet(PD_LOG_MEMORY_DATA_LONG) || (log->GetMask().IsSet(PD_LOG_MEMORY_DATA_SHORT) && size <= 8)) + { + DataExtractor data((uint8_t*)buf, n, eByteOrderHost, 4); + data.PutToLog(log, 0, n, addr, 16, DataExtractor::TypeUInt8); + } + } + } + return n; +} + +//---------------------------------------------------------------------- +// MachTask::AllocateMemory +//---------------------------------------------------------------------- +lldb::addr_t +MachTask::AllocateMemory (size_t size, uint32_t permissions, Error& error) +{ + // FIXME: vm_allocate allocates a page at a time, so we should use + // host_page_size to get the host page size and then parcel out the + // page we get back until it is filled. + // FIXME: Add log messages. + + kern_return_t kret; + mach_vm_address_t addr; + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_MEMORY); + + kret = ::mach_vm_allocate (GetTaskPort(), &addr, size, TRUE); + if (kret == KERN_SUCCESS) + { + // Set the protections: + vm_prot_t mach_prot = 0; + if (permissions & lldb::ePermissionsReadable) + mach_prot |= VM_PROT_READ; + if (permissions & lldb::ePermissionsWritable) + mach_prot |= VM_PROT_WRITE; + if (permissions & lldb::ePermissionsExecutable) + mach_prot |= VM_PROT_EXECUTE; + + kret = ::mach_vm_protect (GetTaskPort(), addr, size, 0, mach_prot); + if (kret == KERN_SUCCESS) + { + if (log) + log->Printf("Allocated memory at addr = 0x%16.16llx, size = %zu, prot = 0x%x)", (uint64_t) addr, size, mach_prot); + m_allocations.insert (std::make_pair(addr, size)); + return (lldb::addr_t) addr; + } + else + { + if (log) + log->Printf("Failed to set protections on memory at addr = 0x%16.16llx, size = %zu), prot = 0x%x", (uint64_t) addr, size, mach_prot); + kret = ::mach_vm_deallocate (GetTaskPort(), addr, size); + return LLDB_INVALID_ADDRESS; + } + } + else + { + if (log) + log->Printf("Failed to set allocate memory: size = %zu)", size); + return LLDB_INVALID_ADDRESS; + } +} + +//---------------------------------------------------------------------- +// MachTask::DeallocateMemory +//---------------------------------------------------------------------- +Error +MachTask::DeallocateMemory (lldb::addr_t ptr) +{ + Error error; + // We have to stash away sizes for the allocations... + allocation_collection::iterator pos, end = m_allocations.end(); + for (pos = m_allocations.begin(); pos != end; pos++) + { + if ((*pos).first == ptr) + { + m_allocations.erase (pos); + error = ::mach_vm_deallocate (GetTaskPort(), (vm_address_t) ptr, (*pos).second); + return error; + } + } + error.SetErrorStringWithFormat("no memory allocated at 0x%llx", (uint64_t)ptr); + return error; +} + +//---------------------------------------------------------------------- +// MachTask::TaskPortForProcessID +//---------------------------------------------------------------------- +task_t +MachTask::GetTaskPortForProcessID (Error &err) +{ + err.Clear(); + if (m_task == TASK_NULL && m_process != NULL) + m_task = MachTask::GetTaskPortForProcessID(m_process->GetID(), err); + return m_task; +} + +//---------------------------------------------------------------------- +// MachTask::TaskPortForProcessID +//---------------------------------------------------------------------- +task_t +MachTask::GetTaskPortForProcessID (lldb::pid_t pid, Error &err) +{ + task_t task = TASK_NULL; + if (pid != LLDB_INVALID_PROCESS_ID) + { + mach_port_t task_self = mach_task_self (); + err = ::task_for_pid ( task_self, pid, &task); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_TASK); + if (log || err.Fail()) + { + err.PutToLog(log, "::task_for_pid ( target_tport = 0x%4.4x, pid = %d, task => 0x%4.4x ) %u/%u %u/%u", task_self, pid, task, getuid(), geteuid(), getgid(), getegid()); + } + } + return task; +} + + +//---------------------------------------------------------------------- +// MachTask::BasicInfo +//---------------------------------------------------------------------- +kern_return_t +MachTask::BasicInfo(struct task_basic_info *info) const +{ + return BasicInfo (GetTaskPort(), info); +} + +//---------------------------------------------------------------------- +// MachTask::BasicInfo +//---------------------------------------------------------------------- +kern_return_t +MachTask::BasicInfo(task_t task, struct task_basic_info *info) +{ + if (info == NULL) + return KERN_INVALID_ARGUMENT; + + Error err; + mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; + err = ::task_info (task, TASK_BASIC_INFO, (task_info_t)info, &count); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_TASK); + if (log || err.Fail()) + err.PutToLog(log, "::task_info ( target_task = 0x%4.4x, flavor = TASK_BASIC_INFO, task_info_out => %p, task_info_outCnt => %u )", task, info, count); + if (log && log->GetMask().IsSet(PD_LOG_VERBOSE) && err.Success()) + { + float user = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f; + float system = (float)info->user_time.seconds + (float)info->user_time.microseconds / 1000000.0f; + log->Printf ("task_basic_info = { suspend_count = %i, virtual_size = 0x%8.8x, resident_size = 0x%8.8x, user_time = %f, system_time = %f }", + info->suspend_count, info->virtual_size, info->resident_size, user, system); + } + return err.GetError(); +} + + +//---------------------------------------------------------------------- +// MachTask::IsValid +// +// Returns true if a task is a valid task port for a current process. +//---------------------------------------------------------------------- +bool +MachTask::IsValid () const +{ + return MachTask::IsValid(GetTaskPort()); +} + +//---------------------------------------------------------------------- +// MachTask::IsValid +// +// Returns true if a task is a valid task port for a current process. +//---------------------------------------------------------------------- +bool +MachTask::IsValid (task_t task) +{ + if (task != TASK_NULL) + { + struct task_basic_info task_info; + return BasicInfo(task, &task_info) == KERN_SUCCESS; + } + return false; +} + + +bool +MachTask::StartExceptionThread(Error &err) +{ + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_EXCEPTIONS); + + if (log) + log->Printf ("MachTask::%s ( )", __FUNCTION__); + task_t task = GetTaskPortForProcessID(err); + if (MachTask::IsValid(task)) + { + // Got the mach port for the current process + mach_port_t task_self = mach_task_self (); + + // Allocate an exception port that we will use to track our child process + err = ::mach_port_allocate (task_self, MACH_PORT_RIGHT_RECEIVE, &m_exception_port); + if (log || err.Fail()) + err.PutToLog(log, "::mach_port_allocate (task_self=0x%4.4x, MACH_PORT_RIGHT_RECEIVE, &m_exception_port => 0x%4.4x)", + task_self, m_exception_port); + if (err.Fail()) + return false; + + // Add the ability to send messages on the new exception port + err = ::mach_port_insert_right (task_self, m_exception_port, m_exception_port, MACH_MSG_TYPE_MAKE_SEND); + if (log || err.Fail()) + err.PutToLog(log, "::mach_port_insert_right (task_self=0x%4.4x, m_exception_port=0x%4.4x, m_exception_port=0x%4.4x, MACH_MSG_TYPE_MAKE_SEND)", + task_self, m_exception_port, m_exception_port); + if (err.Fail()) + return false; + + // Save the original state of the exception ports for our child process + err = SaveExceptionPortInfo(); + + // Set the ability to get all exceptions on this port + err = ::task_set_exception_ports (task, EXC_MASK_ALL, m_exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE); + if (log || err.Fail()) + err.PutToLog(log, "::task_set_exception_ports (task, EXC_MASK_ALL, m_exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE)"); + if (err.Fail()) + return false; + + // Create the exception thread + char thread_name[256]; + ::snprintf (thread_name, sizeof(thread_name), "<lldb.process.process-macosx.mach-exception-%d>", m_process->GetID()); + m_exception_thread = Host::ThreadCreate (thread_name, MachTask::ExceptionThread, this, &err); + + return err.Success(); + } + return false; +} + +kern_return_t +MachTask::ShutDownExceptionThread() +{ + Error err; + + if (m_exception_thread == NULL) + return KERN_SUCCESS; + + err = RestoreExceptionPortInfo(); + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_EXCEPTIONS); + + // NULL our our exception port and let our exception thread exit + mach_port_t exception_port = m_exception_port; + m_exception_port = NULL; + + Host::ThreadCancel (m_exception_thread, &err); + if (log || err.Fail()) + err.PutToLog(log, "Host::ThreadCancel ( thread = %p )", m_exception_thread); + + Host::ThreadJoin (m_exception_thread, NULL, &err); + if (log || err.Fail()) + err.PutToLog(log, "Host::ThreadJoin ( thread = %p, result_ptr = NULL)", m_exception_thread); + + // Deallocate our exception port that we used to track our child process + mach_port_t task_self = mach_task_self (); + err = ::mach_port_deallocate (task_self, exception_port); + if (log || err.Fail()) + err.PutToLog(log, "::mach_port_deallocate ( task = 0x%4.4x, name = 0x%4.4x )", task_self, exception_port); + exception_port = NULL; + + Clear(); + return err.GetError(); +} + + +void * +MachTask::ExceptionThread (void *arg) +{ + if (arg == NULL) + return NULL; + + MachTask *mach_task = (MachTask*) arg; + ProcessMacOSX *mach_proc = mach_task->Process(); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_EXCEPTIONS); + if (log) + log->Printf ("MachTask::%s (arg = %p) thread starting...", __FUNCTION__, arg); + + // We keep a count of the number of consecutive exceptions received so + // we know to grab all exceptions without a timeout. 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 loop in this + // thread can stop periodically if needed to service things related to this + // process. + // 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 avaiable. 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. + uint32_t num_exceptions_received = 0; + Error err; + task_t task = mach_task->GetTaskPort(); + mach_msg_timeout_t periodic_timeout = 1000; + +#if defined (__arm__) + mach_msg_timeout_t watchdog_elapsed = 0; + mach_msg_timeout_t watchdog_timeout = 60 * 1000; + lldb::pid_t pid = mach_proc->GetID(); + CFReleaser<SBSWatchdogAssertionRef> watchdog; + + if (mach_proc->ProcessUsingSpringBoard()) + { + // Request a renewal for every 60 seconds if we attached using SpringBoard + watchdog.reset(::SBSWatchdogAssertionCreateForPID(NULL, pid, 60)); + if (log) + log->Printf ("::SBSWatchdogAssertionCreateForPID (NULL, %4.4x, 60 ) => %p", pid, watchdog.get()); + + if (watchdog.get()) + { + ::SBSWatchdogAssertionRenew (watchdog.get()); + + CFTimeInterval watchdogRenewalInterval = ::SBSWatchdogAssertionGetRenewalInterval (watchdog.get()); + if (log) + log->Printf ("::SBSWatchdogAssertionGetRenewalInterval ( %p ) => %g seconds", watchdog.get(), watchdogRenewalInterval); + if (watchdogRenewalInterval > 0.0) + { + watchdog_timeout = (mach_msg_timeout_t)watchdogRenewalInterval * 1000; + if (watchdog_timeout > 3000) + watchdog_timeout -= 1000; // Give us a second to renew our timeout + else if (watchdog_timeout > 1000) + watchdog_timeout -= 250; // Give us a quarter of a second to renew our timeout + } + } + if (periodic_timeout == 0 || periodic_timeout > watchdog_timeout) + periodic_timeout = watchdog_timeout; + } +#endif // #if defined (__arm__) + + while (mach_task->ExceptionPortIsValid()) + { + //::pthread_testcancel (); + + MachException::Message exception_message; + + + if (num_exceptions_received > 0) + { + // No timeout, just receive as many exceptions as we can since we already have one and we want + // to get all currently available exceptions for this task + err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, 0); + } + else if (periodic_timeout > 0) + { + // We need to stop periodically in this loop, so try and get a mach message with a valid timeout (ms) + err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT | MACH_RCV_TIMEOUT, periodic_timeout); + } + else + { + // We don't need to parse all current exceptions or stop periodically, + // just wait for an exception forever. + err = exception_message.Receive(mach_task->ExceptionPort(), MACH_RCV_MSG | MACH_RCV_INTERRUPT, 0); + } + + if (err.GetError() == MACH_RCV_INTERRUPTED) + { + // If we have no task port we should exit this thread + if (!mach_task->ExceptionPortIsValid()) + { + if (log) + log->Printf ("thread cancelled..."); + break; + } + + // Make sure our task is still valid + if (MachTask::IsValid(task)) + { + // Task is still ok + if (log) + log->Printf ("interrupted, but task still valid, continuing..."); + continue; + } + else + { + if (log) + log->Printf ("task has exited..."); + mach_proc->SetPrivateState (eStateExited); + // Our task has died, exit the thread. + break; + } + } + else if (err.GetError() == MACH_RCV_TIMED_OUT) + { + if (num_exceptions_received > 0) + { + // We were receiving all current exceptions with a timeout of zero + // it is time to go back to our normal looping mode + num_exceptions_received = 0; + + // Notify our main thread we have a complete exception message + // bundle available. + mach_proc->ExceptionMessageBundleComplete(); + + // in case we use a timeout value when getting exceptions... + // Make sure our task is still valid + if (MachTask::IsValid(task)) + { + // Task is still ok + if (log) + log->Printf ("got a timeout, continuing..."); + continue; + } + else + { + if (log) + log->Printf ("task has exited..."); + mach_proc->SetPrivateState (eStateExited); + // Our task has died, exit the thread. + break; + } + continue; + } + +#if defined (__arm__) + if (watchdog.get()) + { + watchdog_elapsed += periodic_timeout; + if (watchdog_elapsed >= watchdog_timeout) + { + LogIf(PD_LOG_TASK, "SBSWatchdogAssertionRenew ( %p )", watchdog.get()); + ::SBSWatchdogAssertionRenew (watchdog.get()); + watchdog_elapsed = 0; + } + } +#endif + } + else if (err.GetError() != KERN_SUCCESS) + { + if (log) + log->Printf ("got some other error, do something about it??? nah, continuing for now..."); + // TODO: notify of error? + } + else + { + if (exception_message.CatchExceptionRaise()) + { + ++num_exceptions_received; + mach_proc->ExceptionMessageReceived(exception_message); + } + } + } + +#if defined (__arm__) + if (watchdog.get()) + { + // TODO: change SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel when we + // all are up and running on systems that support it. The SBS framework has a #define + // that will forward SBSWatchdogAssertionRelease to SBSWatchdogAssertionCancel for now + // so it should still build either way. + LogIf(PD_LOG_TASK, "::SBSWatchdogAssertionRelease(%p)", watchdog.get()); + ::SBSWatchdogAssertionRelease (watchdog.get()); + } +#endif // #if defined (__arm__) + + if (log) + log->Printf ("MachTask::%s (arg = %p) thread exiting...", __FUNCTION__, arg); + return NULL; +} + +lldb::addr_t +MachTask::GetDYLDAllImageInfosAddress () +{ +#ifdef TASK_DYLD_INFO + task_dyld_info_data_t dyld_info; + mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; + Error err; + // The actual task shouldn't matter for the DYLD info, so lets just use ours + kern_return_t kret = ::task_info (GetTaskPortForProcessID (err), TASK_DYLD_INFO, (task_info_t)&dyld_info, &count); + if (kret == KERN_SUCCESS) + { + // We now have the address of the all image infos structure + return dyld_info.all_image_info_addr; + } +#endif + return LLDB_INVALID_ADDRESS; +} + + + + + diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachTask.h b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachTask.h new file mode 100644 index 00000000000..228cb7c516b --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachTask.h @@ -0,0 +1,138 @@ +//===-- MachTask.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef __MachTask_h__ +#define __MachTask_h__ + +// C Includes +// C++ Includes +#include <map> +// Other libraries and framework includes +#include <mach/mach.h> +#include <mach/mach_vm.h> +#include <sys/socket.h> + +// Project includes +#include "MachException.h" +#include "MachVMMemory.h" + +class ProcessMacOSX; + +class MachTask +{ +public: + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + MachTask (ProcessMacOSX *process); + + virtual + ~MachTask (); + + void + Clear (); + + kern_return_t + Suspend (); + + kern_return_t + Resume (); + + int32_t + GetSuspendCount () const; + + size_t + ReadMemory (lldb::addr_t addr, void *buf, size_t size, lldb_private::Error& error); + + size_t + WriteMemory (lldb::addr_t addr, const void *buf, size_t size, lldb_private::Error& error); + + lldb::addr_t + AllocateMemory (size_t size, uint32_t permissions, lldb_private::Error& error); + + lldb_private::Error + DeallocateMemory (lldb::addr_t addr); + + mach_port_t + ExceptionPort () const; + + bool + ExceptionPortIsValid () const; + + kern_return_t + SaveExceptionPortInfo (); + + kern_return_t + RestoreExceptionPortInfo (); + + kern_return_t + ShutDownExceptionThread (); + + bool + StartExceptionThread (lldb_private::Error &err); + + lldb::addr_t + GetDYLDAllImageInfosAddress (); + + kern_return_t + BasicInfo (struct task_basic_info *info) const; + + static kern_return_t + BasicInfo (task_t task, struct task_basic_info *info); + + bool + IsValid () const; + + static bool + IsValid (task_t task); + + static void * + ExceptionThread (void *arg); + + task_t + GetTaskPort () const + { + return m_task; + } + + task_t + GetTaskPortForProcessID (lldb_private::Error &err); + + static task_t + GetTaskPortForProcessID (lldb::pid_t pid, lldb_private::Error &err); + + ProcessMacOSX * + Process () + { + return m_process; + } + + const ProcessMacOSX * + Process () const + { + return m_process; + } + +protected: + ProcessMacOSX * m_process; // The mach process that owns this MachTask + task_t m_task; + MachVMMemory m_vm_memory; // Special mach memory reading class that will take care of watching for page and region boundaries + MachException::PortInfo m_exc_port_info; // Saved settings for all exception ports + lldb::thread_t m_exception_thread; // Thread ID for the exception thread in case we need it + mach_port_t m_exception_port; // Exception port on which we will receive child exceptions + + // Maybe sort this by address and use find? + typedef std::map<vm_address_t,size_t> allocation_collection; + allocation_collection m_allocations; + +private: + DISALLOW_COPY_AND_ASSIGN (MachTask); +}; + +#endif // __MachTask_h__ diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext.h b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext.h new file mode 100644 index 00000000000..3166c2802f3 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext.h @@ -0,0 +1,48 @@ +//===-- MachThreadContext.h -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_MachThreadContext_h_ +#define liblldb_MachThreadContext_h_ + +#include <vector> + +#include "MachException.h" + +class ThreadMacOSX; + +class MachThreadContext +{ +public: + MachThreadContext (ThreadMacOSX &thread) : + m_thread (thread) + { + } + + virtual ~MachThreadContext() + { + } + + virtual lldb_private::RegisterContext * + CreateRegisterContext (lldb_private::StackFrame *frame) const = 0; + + virtual void InitializeInstance() = 0; + virtual void ThreadWillResume () = 0; + virtual bool ShouldStop () = 0; + virtual void RefreshStateAfterStop() = 0; + virtual bool NotifyException (MachException::Data& exc) { return false; } + virtual bool StepNotComplete () { return false; } + virtual size_t GetStackFrameData(lldb_private::StackFrame *frame, std::vector<std::pair<lldb::addr_t, lldb::addr_t> >& fp_pc_pairs) { return 0; } +// virtual const uint8_t * SoftwareBreakpointOpcode (size_t byte_size) = 0; + +protected: + ThreadMacOSX &m_thread; + +}; + +#endif // #ifndef liblldb_MachThreadContext_h_ diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_arm.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_arm.cpp new file mode 100644 index 00000000000..4f6b17c81a2 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_arm.cpp @@ -0,0 +1,1884 @@ +//===-- MachThreadContext_arm.cpp -------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MachThreadContext_arm.h" + +#include <sys/sysctl.h> + +#include "ProcessMacOSX.h" +#include "ProcessMacOSXLog.h" +#include "ThreadMacOSX.h" + +using namespace lldb_private; + +//#define DNB_ARCH_MACH_ARM_DEBUG_SW_STEP 1 + +static const uint8_t g_arm_breakpoint_opcode[] = { 0xFE, 0xDE, 0xFF, 0xE7 }; +static const uint8_t g_thumb_breakpooint_opcode[] = { 0xFE, 0xDE }; + +// ARM constants used during decoding +#define REG_RD 0 +#define LDM_REGLIST 1 +#define PC_REG 15 +#define PC_REGLIST_BIT 0x8000 + +// ARM conditions +#define COND_EQ 0x0 +#define COND_NE 0x1 +#define COND_CS 0x2 +#define COND_HS 0x2 +#define COND_CC 0x3 +#define COND_LO 0x3 +#define COND_MI 0x4 +#define COND_PL 0x5 +#define COND_VS 0x6 +#define COND_VC 0x7 +#define COND_HI 0x8 +#define COND_LS 0x9 +#define COND_GE 0xA +#define COND_LT 0xB +#define COND_GT 0xC +#define COND_LE 0xD +#define COND_AL 0xE +#define COND_UNCOND 0xF + +#define MASK_CPSR_T (1u << 5) +#define MASK_CPSR_J (1u << 24) + +#define MNEMONIC_STRING_SIZE 32 +#define OPERAND_STRING_SIZE 128 + +using namespace lldb; +using namespace lldb_private; + +MachThreadContext_arm::MachThreadContext_arm(ThreadMacOSX &thread) : + MachThreadContext(thread), + m_hw_single_chained_step_addr(LLDB_INVALID_ADDRESS), + m_bvr0_reg (LLDB_INVALID_REGNUM), + m_bcr0_reg (LLDB_INVALID_REGNUM), + m_bvr0_save (0), + m_bcr0_save (0) +{ +} + +MachThreadContext_arm::~MachThreadContext_arm() +{ +} + +RegisterContext * +MachThreadContext_arm::CreateRegisterContext (StackFrame *frame) const +{ + return new RegisterContextMach_arm(m_thread, frame); +} + +// Instance init function +void +MachThreadContext_arm::InitializeInstance() +{ + RegisterContext *reg_ctx = m_thread.GetRegisterContext(); + assert (reg_ctx != NULL); + const RegisterInfo * reg_info; + reg_info = reg_ctx->GetRegisterInfoByName ("bvr0"); + if (reg_info) + m_bvr0_reg = reg_info->reg; + + reg_info = reg_ctx->GetRegisterInfoByName ("bcr0"); + if (reg_info) + m_bcr0_reg = reg_info->reg; +} + + + +uint32_t +MachThreadContext_arm::GetCPUType() +{ + return CPU_TYPE_ARM; +} + +void +MachThreadContext_arm::ThreadWillResume() +{ + // Do we need to step this thread? If so, let the mach thread tell us so. + if (m_thread.GetState() == eStateStepping) + { + bool step_handled = false; + // This is the primary thread, let the arch do anything it needs + if (m_thread.GetRegisterContext()->NumSupportedHardwareBreakpoints() > 0) + { +#if defined (DNB_ARCH_MACH_ARM_DEBUG_SW_STEP) + bool half_step = m_hw_single_chained_step_addr != LLDB_INVALID_ADDRESS; +#endif + step_handled = EnableHardwareSingleStep(true) == KERN_SUCCESS; +#if defined (DNB_ARCH_MACH_ARM_DEBUG_SW_STEP) + if (!half_step) + step_handled = false; +#endif + } + +#if defined (ENABLE_ARM_SINGLE_STEP) + if (!step_handled) + { + SetSingleStepSoftwareBreakpoints(); + } +#endif + } +} + +bool +MachThreadContext_arm::ShouldStop () +{ + return true; +} + +void +MachThreadContext_arm::RefreshStateAfterStop () +{ + EnableHardwareSingleStep (false) == KERN_SUCCESS; +} + +#if defined (ENABLE_ARM_SINGLE_STEP) + +bool +MachThreadContext_arm::ShouldStop () +{ + return true; +} + +bool +MachThreadContext_arm::RefreshStateAfterStop () +{ + success = EnableHardwareSingleStep(false) == KERN_SUCCESS; + + bool success = true; + + m_state.InvalidateRegisterSet (GPRRegSet); + m_state.InvalidateRegisterSet (VFPRegSet); + m_state.InvalidateRegisterSet (EXCRegSet); + + // Are we stepping a single instruction? + if (ReadGPRRegisters(true) == KERN_SUCCESS) + { + // We are single stepping, was this the primary thread? + if (m_thread.GetState() == eStateStepping) + { +#if defined (DNB_ARCH_MACH_ARM_DEBUG_SW_STEP) + success = EnableHardwareSingleStep(false) == KERN_SUCCESS; + // Hardware single step must work if we are going to test software + // single step functionality + assert(success); + if (m_hw_single_chained_step_addr == LLDB_INVALID_ADDRESS && m_sw_single_step_next_pc != LLDB_INVALID_ADDRESS) + { + uint32_t sw_step_next_pc = m_sw_single_step_next_pc & 0xFFFFFFFEu; + bool sw_step_next_pc_is_thumb = (m_sw_single_step_next_pc & 1) != 0; + bool actual_next_pc_is_thumb = (m_state.gpr.__cpsr & 0x20) != 0; + if (m_state.gpr.r[15] != sw_step_next_pc) + { + LogError("curr pc = 0x%8.8x - calculated single step target PC was incorrect: 0x%8.8x != 0x%8.8x", m_state.gpr.r[15], sw_step_next_pc, m_state.gpr.r[15]); + exit(1); + } + if (actual_next_pc_is_thumb != sw_step_next_pc_is_thumb) + { + LogError("curr pc = 0x%8.8x - calculated single step calculated mode mismatch: sw single mode = %s != %s", + m_state.gpr.r[15], + actual_next_pc_is_thumb ? "Thumb" : "ARM", + sw_step_next_pc_is_thumb ? "Thumb" : "ARM"); + exit(1); + } + m_sw_single_step_next_pc = LLDB_INVALID_ADDRESS; + } +#else +#if defined (ENABLE_ARM_SINGLE_STEP) + // Are we software single stepping? + if (LLDB_BREAK_ID_IS_VALID(m_sw_single_step_break_id) || m_sw_single_step_itblock_break_count) + { + // Remove any software single stepping breakpoints that we have set + + // Do we have a normal software single step breakpoint? + if (LLDB_BREAK_ID_IS_VALID(m_sw_single_step_break_id)) + { + ProcessMacOSXLog::LogIf(PD_LOG_STEP, "%s: removing software single step breakpoint (breakID=%d)", __FUNCTION__, m_sw_single_step_break_id); + success = m_thread.Process()->DisableBreakpoint(m_sw_single_step_break_id, true); + m_sw_single_step_break_id = LLDB_INVALID_BREAK_ID; + } + + // Do we have any Thumb IT breakpoints? + if (m_sw_single_step_itblock_break_count > 0) + { + // See if we hit one of our Thumb IT breakpoints? + DNBBreakpoint *step_bp = m_thread.Process()->Breakpoints().FindByAddress(m_state.gpr.r[15]); + + if (step_bp) + { + // We did hit our breakpoint, tell the breakpoint it was + // hit so that it can run its callback routine and fixup + // the PC. + ProcessMacOSXLog::LogIf(PD_LOG_STEP, "%s: IT software single step breakpoint hit (breakID=%u)", __FUNCTION__, step_bp->GetID()); + step_bp->BreakpointHit(m_thread.Process()->ProcessID(), m_thread.GetID()); + } + + // Remove all Thumb IT breakpoints + for (int i = 0; i < m_sw_single_step_itblock_break_count; i++) + { + if (LLDB_BREAK_ID_IS_VALID(m_sw_single_step_itblock_break_id[i])) + { + ProcessMacOSXLog::LogIf(PD_LOG_STEP, "%s: removing IT software single step breakpoint (breakID=%d)", __FUNCTION__, m_sw_single_step_itblock_break_id[i]); + success = m_thread.Process()->DisableBreakpoint(m_sw_single_step_itblock_break_id[i], true); + m_sw_single_step_itblock_break_id[i] = LLDB_INVALID_BREAK_ID; + } + } + m_sw_single_step_itblock_break_count = 0; + + // Decode instructions up to the current PC to ensure the internal decoder state is valid for the IT block + // The decoder has to decode each instruction in the IT block even if it is not executed so that + // the fields are correctly updated + DecodeITBlockInstructions(m_state.gpr.r[15]); + } + + } + else +#endif + success = EnableHardwareSingleStep(false) == KERN_SUCCESS; +#endif + } + else + { + // The MachThread will automatically restore the suspend count + // in ShouldStop (), so we don't need to do anything here if + // we weren't the primary thread the last time + } + } + return; +} + + + +bool +MachThreadContext_arm::StepNotComplete () +{ + if (m_hw_single_chained_step_addr != LLDB_INVALID_ADDRESS) + { + kern_return_t kret = KERN_INVALID_ARGUMENT; + kret = ReadGPRRegisters(false); + if (kret == KERN_SUCCESS) + { + if (m_state.gpr.r[15] == m_hw_single_chained_step_addr) + { + //ProcessMacOSXLog::LogIf(PD_LOG_STEP, "Need to step some more at 0x%8.8x", m_hw_single_chained_step_addr); + return true; + } + } + } + + m_hw_single_chained_step_addr = LLDB_INVALID_ADDRESS; + return false; +} + + +void +MachThreadContext_arm::DecodeITBlockInstructions(lldb::addr_t curr_pc) + +{ + uint16_t opcode16; + uint32_t opcode32; + lldb::addr_t next_pc_in_itblock; + lldb::addr_t pc_in_itblock = m_last_decode_pc; + + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: last_decode_pc=0x%8.8x", __FUNCTION__, m_last_decode_pc); + + // Decode IT block instruction from the instruction following the m_last_decoded_instruction at + // PC m_last_decode_pc upto and including the instruction at curr_pc + if (m_thread.Process()->Task().ReadMemory(pc_in_itblock, 2, &opcode16) == 2) + { + opcode32 = opcode16; + pc_in_itblock += 2; + // Check for 32 bit thumb opcode and read the upper 16 bits if needed + if (((opcode32 & 0xE000) == 0xE000) && opcode32 & 0x1800) + { + // Adjust 'next_pc_in_itblock' to point to the default next Thumb instruction for + // a 32 bit Thumb opcode + // Read bits 31:16 of a 32 bit Thumb opcode + if (m_thread.Process()->Task().ReadMemory(pc_in_itblock, 2, &opcode16) == 2) + { + pc_in_itblock += 2; + // 32 bit thumb opcode + opcode32 = (opcode32 << 16) | opcode16; + } + else + { + LogError("%s: Unable to read opcode bits 31:16 for a 32 bit thumb opcode at pc=0x%8.8lx", __FUNCTION__, pc_in_itblock); + } + } + } + else + { + LogError("%s: Error reading 16-bit Thumb instruction at pc=0x%8.8x", __FUNCTION__, pc_in_itblock); + } + + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: pc_in_itblock=0x%8.8x, curr_pc=0x%8.8x", __FUNCTION__, pc_in_itblock, curr_pc); + + next_pc_in_itblock = pc_in_itblock; + while (next_pc_in_itblock <= curr_pc) + { + arm_error_t decodeError; + + m_last_decode_pc = pc_in_itblock; + decodeError = DecodeInstructionUsingDisassembler(pc_in_itblock, m_state.gpr.__cpsr, &m_last_decode_arm, &m_last_decode_thumb, &next_pc_in_itblock); + + pc_in_itblock = next_pc_in_itblock; + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: next_pc_in_itblock=0x%8.8x", __FUNCTION__, next_pc_in_itblock); + } +} + +#endif + +// Set the single step bit in the processor status register. +kern_return_t +MachThreadContext_arm::EnableHardwareSingleStep (bool enable) +{ + Error err; + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_STEP); + + if (log) log->Printf("%s( enable = %d )", __FUNCTION__, enable); + + if (m_bvr0_reg == LLDB_INVALID_REGNUM || m_bcr0_reg == LLDB_INVALID_REGNUM) + return KERN_INVALID_ARGUMENT; + + RegisterContext *reg_ctx = m_thread.GetRegisterContext(); + uint32_t bvr = 0; + uint32_t bcr = 0; + + const uint32_t i = 0; + if (enable) + { + m_hw_single_chained_step_addr = LLDB_INVALID_ADDRESS; + + // Save our previous state + m_bvr0_save = reg_ctx->ReadRegisterAsUnsigned(m_bvr0_reg, 0); + m_bcr0_save = reg_ctx->ReadRegisterAsUnsigned(m_bcr0_reg, 0); + lldb::addr_t pc = reg_ctx->GetPC(LLDB_INVALID_ADDRESS); + lldb::addr_t cpsr = reg_ctx->GetFlags(0); + if (pc == LLDB_INVALID_ADDRESS) + return KERN_INVALID_ARGUMENT; + + // Set a breakpoint that will stop when the PC doesn't match the current one! + bvr = pc & 0xFFFFFFFCu; // Set the current PC as the breakpoint address + bcr = BCR_M_IMVA_MISMATCH | // Stop on address mismatch + S_USER | // Stop only in user mode + BCR_ENABLE; // Enable this breakpoint + if (cpsr & 0x20) + { + // Thumb breakpoint + if (pc & 2) + bcr |= BAS_IMVA_2_3; + else + bcr |= BAS_IMVA_0_1; + + uint16_t opcode; + Error error; + if (sizeof(opcode) == m_thread.GetProcess().ReadMemory(pc, &opcode, sizeof(opcode), error)) + { + if (((opcode & 0xE000) == 0xE000) && opcode & 0x1800) + { + // 32 bit thumb opcode... + if (pc & 2) + { + // We can't take care of a 32 bit thumb instruction single step + // with just IVA mismatching. We will need to chain an extra + // hardware single step in order to complete this single step... + m_hw_single_chained_step_addr = pc + 2; + } + else + { + // Extend the number of bits to ignore for the mismatch + bcr |= BAS_IMVA_ALL; + } + } + } + } + else + { + // ARM breakpoint + bcr |= BAS_IMVA_ALL; // Stop when any address bits change + } + + ProcessMacOSXLog::LogIf(PD_LOG_STEP, "%s: BVR%u=0x%8.8x BCR%u=0x%8.8x", __FUNCTION__, i, bvr, i, bcr); + + m_bvr0_save = reg_ctx->ReadRegisterAsUnsigned(m_bvr0_reg, 0); + m_bcr0_save = reg_ctx->ReadRegisterAsUnsigned(m_bcr0_reg, 0); + +// for (uint32_t j=i+1; j<16; ++j) +// { +// // Disable all others +// m_state.dbg.bvr[j] = 0; +// m_state.dbg.bcr[j] = 0; +// } + } + else + { + // Just restore the state we had before we did single stepping + bvr = m_bvr0_save; + bcr = m_bcr0_save; + } + + if (reg_ctx->WriteRegisterFromUnsigned(m_bvr0_reg, bvr) && + reg_ctx->WriteRegisterFromUnsigned(m_bcr0_reg, bcr)) + return KERN_SUCCESS; + + return KERN_INVALID_ARGUMENT; +} + +#if defined (ENABLE_ARM_SINGLE_STEP) + +// return 1 if bit "BIT" is set in "value" +static inline uint32_t bit(uint32_t value, uint32_t bit) +{ + return (value >> bit) & 1u; +} + +// return the bitfield "value[msbit:lsbit]". +static inline uint32_t bits(uint32_t value, uint32_t msbit, uint32_t lsbit) +{ + assert(msbit >= lsbit); + uint32_t shift_left = sizeof(value) * 8 - 1 - msbit; + value <<= shift_left; // shift anything above the msbit off of the unsigned edge + value >>= shift_left + lsbit; // shift it back again down to the lsbit (including undoing any shift from above) + return value; // return our result +} + +bool +MachThreadContext_arm::ConditionPassed(uint8_t condition, uint32_t cpsr) +{ + uint32_t cpsr_n = bit(cpsr, 31); // Negative condition code flag + uint32_t cpsr_z = bit(cpsr, 30); // Zero condition code flag + uint32_t cpsr_c = bit(cpsr, 29); // Carry condition code flag + uint32_t cpsr_v = bit(cpsr, 28); // Overflow condition code flag + + switch (condition) { + case COND_EQ: // (0x0) + if (cpsr_z == 1) return true; + break; + case COND_NE: // (0x1) + if (cpsr_z == 0) return true; + break; + case COND_CS: // (0x2) + if (cpsr_c == 1) return true; + break; + case COND_CC: // (0x3) + if (cpsr_c == 0) return true; + break; + case COND_MI: // (0x4) + if (cpsr_n == 1) return true; + break; + case COND_PL: // (0x5) + if (cpsr_n == 0) return true; + break; + case COND_VS: // (0x6) + if (cpsr_v == 1) return true; + break; + case COND_VC: // (0x7) + if (cpsr_v == 0) return true; + break; + case COND_HI: // (0x8) + if ((cpsr_c == 1) && (cpsr_z == 0)) return true; + break; + case COND_LS: // (0x9) + if ((cpsr_c == 0) || (cpsr_z == 1)) return true; + break; + case COND_GE: // (0xA) + if (cpsr_n == cpsr_v) return true; + break; + case COND_LT: // (0xB) + if (cpsr_n != cpsr_v) return true; + break; + case COND_GT: // (0xC) + if ((cpsr_z == 0) && (cpsr_n == cpsr_v)) return true; + break; + case COND_LE: // (0xD) + if ((cpsr_z == 1) || (cpsr_n != cpsr_v)) return true; + break; + default: + return true; + break; + } + + return false; +} + +bool +MachThreadContext_arm::ComputeNextPC(lldb::addr_t currentPC, arm_decoded_instruction_t decodedInstruction, bool currentPCIsThumb, lldb::addr_t *targetPC) +{ + lldb::addr_t myTargetPC, addressWherePCLives; + lldb::pid_t mypid; + + uint32_t cpsr_c = bit(m_state.gpr.cpsr, 29); // Carry condition code flag + + uint32_t firstOperand=0, secondOperand=0, shiftAmount=0, secondOperandAfterShift=0, immediateValue=0; + uint32_t halfwords=0, baseAddress=0, immediateOffset=0, addressOffsetFromRegister=0, addressOffsetFromRegisterAfterShift; + uint32_t baseAddressIndex=LLDB_INVALID_INDEX32; + uint32_t firstOperandIndex=LLDB_INVALID_INDEX32; + uint32_t secondOperandIndex=LLDB_INVALID_INDEX32; + uint32_t addressOffsetFromRegisterIndex=LLDB_INVALID_INDEX32; + uint32_t shiftRegisterIndex=LLDB_INVALID_INDEX32; + uint16_t registerList16, registerList16NoPC; + uint8_t registerList8; + uint32_t numRegistersToLoad=0; + + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: instruction->code=%d", __FUNCTION__, decodedInstruction.instruction->code); + + // Get the following in this switch statement: + // - firstOperand, secondOperand, immediateValue, shiftAmount: For arithmetic, logical and move instructions + // - baseAddress, immediateOffset, shiftAmount: For LDR + // - numRegistersToLoad: For LDM and POP instructions + switch (decodedInstruction.instruction->code) + { + // Arithmetic operations that can change the PC + case ARM_INST_ADC: + case ARM_INST_ADCS: + case ARM_INST_ADD: + case ARM_INST_ADDS: + case ARM_INST_AND: + case ARM_INST_ANDS: + case ARM_INST_ASR: + case ARM_INST_ASRS: + case ARM_INST_BIC: + case ARM_INST_BICS: + case ARM_INST_EOR: + case ARM_INST_EORS: + case ARM_INST_ORR: + case ARM_INST_ORRS: + case ARM_INST_RSB: + case ARM_INST_RSBS: + case ARM_INST_RSC: + case ARM_INST_RSCS: + case ARM_INST_SBC: + case ARM_INST_SBCS: + case ARM_INST_SUB: + case ARM_INST_SUBS: + switch (decodedInstruction.addressMode) + { + case ARM_ADDR_DATA_IMM: + if (decodedInstruction.numOperands != 3) + { + LogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + LogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get firstOperand register value (at index=1) + firstOperandIndex = decodedInstruction.op[1].value; // first operand register index + firstOperand = m_state.gpr.r[firstOperandIndex]; + + // Get immediateValue (at index=2) + immediateValue = decodedInstruction.op[2].value; + + break; + + case ARM_ADDR_DATA_REG: + if (decodedInstruction.numOperands != 3) + { + LogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + LogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get firstOperand register value (at index=1) + firstOperandIndex = decodedInstruction.op[1].value; // first operand register index + firstOperand = m_state.gpr.r[firstOperandIndex]; + + // Get secondOperand register value (at index=2) + secondOperandIndex = decodedInstruction.op[2].value; // second operand register index + secondOperand = m_state.gpr.r[secondOperandIndex]; + + break; + + case ARM_ADDR_DATA_SCALED_IMM: + if (decodedInstruction.numOperands != 4) + { + LogError("Expected 4 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + LogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get firstOperand register value (at index=1) + firstOperandIndex = decodedInstruction.op[1].value; // first operand register index + firstOperand = m_state.gpr.r[firstOperandIndex]; + + // Get secondOperand register value (at index=2) + secondOperandIndex = decodedInstruction.op[2].value; // second operand register index + secondOperand = m_state.gpr.r[secondOperandIndex]; + + // Get shiftAmount as immediate value (at index=3) + shiftAmount = decodedInstruction.op[3].value; + + break; + + + case ARM_ADDR_DATA_SCALED_REG: + if (decodedInstruction.numOperands != 4) + { + LogError("Expected 4 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + LogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get firstOperand register value (at index=1) + firstOperandIndex = decodedInstruction.op[1].value; // first operand register index + firstOperand = m_state.gpr.r[firstOperandIndex]; + + // Get secondOperand register value (at index=2) + secondOperandIndex = decodedInstruction.op[2].value; // second operand register index + secondOperand = m_state.gpr.r[secondOperandIndex]; + + // Get shiftAmount from register (at index=3) + shiftRegisterIndex = decodedInstruction.op[3].value; // second operand register index + shiftAmount = m_state.gpr.r[shiftRegisterIndex]; + + break; + + case THUMB_ADDR_HR_HR: + if (decodedInstruction.numOperands != 2) + { + LogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + LogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get firstOperand register value (at index=0) + firstOperandIndex = decodedInstruction.op[0].value; // first operand register index + firstOperand = m_state.gpr.r[firstOperandIndex]; + + // Get secondOperand register value (at index=1) + secondOperandIndex = decodedInstruction.op[1].value; // second operand register index + secondOperand = m_state.gpr.r[secondOperandIndex]; + + break; + + default: + break; + } + break; + + // Logical shifts and move operations that can change the PC + case ARM_INST_LSL: + case ARM_INST_LSLS: + case ARM_INST_LSR: + case ARM_INST_LSRS: + case ARM_INST_MOV: + case ARM_INST_MOVS: + case ARM_INST_MVN: + case ARM_INST_MVNS: + case ARM_INST_ROR: + case ARM_INST_RORS: + case ARM_INST_RRX: + case ARM_INST_RRXS: + // In these cases, the firstOperand is always 0, as if it does not exist + switch (decodedInstruction.addressMode) + { + case ARM_ADDR_DATA_IMM: + if (decodedInstruction.numOperands != 2) + { + LogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + LogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get immediateValue (at index=1) + immediateValue = decodedInstruction.op[1].value; + + break; + + case ARM_ADDR_DATA_REG: + if (decodedInstruction.numOperands != 2) + { + LogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + LogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get secondOperand register value (at index=1) + secondOperandIndex = decodedInstruction.op[1].value; // second operand register index + secondOperand = m_state.gpr.r[secondOperandIndex]; + + break; + + case ARM_ADDR_DATA_SCALED_IMM: + if (decodedInstruction.numOperands != 3) + { + LogError("Expected 4 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + LogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get secondOperand register value (at index=1) + secondOperandIndex = decodedInstruction.op[2].value; // second operand register index + secondOperand = m_state.gpr.r[secondOperandIndex]; + + // Get shiftAmount as immediate value (at index=2) + shiftAmount = decodedInstruction.op[2].value; + + break; + + + case ARM_ADDR_DATA_SCALED_REG: + if (decodedInstruction.numOperands != 3) + { + LogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + LogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get secondOperand register value (at index=1) + secondOperandIndex = decodedInstruction.op[1].value; // second operand register index + secondOperand = m_state.gpr.r[secondOperandIndex]; + + // Get shiftAmount from register (at index=2) + shiftRegisterIndex = decodedInstruction.op[2].value; // second operand register index + shiftAmount = m_state.gpr.r[shiftRegisterIndex]; + + break; + + case THUMB_ADDR_HR_HR: + if (decodedInstruction.numOperands != 2) + { + LogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + if (decodedInstruction.op[0].value != PC_REG) + { + LogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + + // Get secondOperand register value (at index=1) + secondOperandIndex = decodedInstruction.op[1].value; // second operand register index + secondOperand = m_state.gpr.r[secondOperandIndex]; + + break; + + default: + break; + } + + break; + + // Simple branches, used to hop around within a routine + case ARM_INST_B: + *targetPC = decodedInstruction.targetPC; // Known targetPC + return true; + break; + + // Branch-and-link, used to call ARM subroutines + case ARM_INST_BL: + *targetPC = decodedInstruction.targetPC; // Known targetPC + return true; + break; + + // Branch-and-link with exchange, used to call opposite-mode subroutines + case ARM_INST_BLX: + if ((decodedInstruction.addressMode == ARM_ADDR_BRANCH_IMM) || + (decodedInstruction.addressMode == THUMB_ADDR_UNCOND)) + { + *targetPC = decodedInstruction.targetPC; // Known targetPC + return true; + } + else // addressMode == ARM_ADDR_BRANCH_REG + { + // Unknown target unless we're branching to the PC itself, + // although this may not work properly with BLX + if (decodedInstruction.op[REG_RD].value == PC_REG) + { + // this should (almost) never happen + *targetPC = decodedInstruction.targetPC; // Known targetPC + return true; + } + + // Get the branch address and return + if (decodedInstruction.numOperands != 1) + { + LogError("Expected 1 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + // Get branch address in register (at index=0) + *targetPC = m_state.gpr.r[decodedInstruction.op[0].value]; + return true; + } + break; + + // Branch with exchange, used to hop to opposite-mode code + // Branch to Jazelle code, used to execute Java; included here since it + // acts just like BX unless the Jazelle unit is active and JPC is + // already loaded into it. + case ARM_INST_BX: + case ARM_INST_BXJ: + // Unknown target unless we're branching to the PC itself, + // although this can never switch to Thumb mode and is + // therefore pretty much useless + if (decodedInstruction.op[REG_RD].value == PC_REG) + { + // this should (almost) never happen + *targetPC = decodedInstruction.targetPC; // Known targetPC + return true; + } + + // Get the branch address and return + if (decodedInstruction.numOperands != 1) + { + LogError("Expected 1 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + // Get branch address in register (at index=0) + *targetPC = m_state.gpr.r[decodedInstruction.op[0].value]; + return true; + break; + + // Compare and branch on zero/non-zero (Thumb-16 only) + // Unusual condition check built into the instruction + case ARM_INST_CBZ: + case ARM_INST_CBNZ: + // Branch address is known at compile time + // Get the branch address and return + if (decodedInstruction.numOperands != 2) + { + LogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + // Get branch address as an immediate value (at index=1) + *targetPC = decodedInstruction.op[1].value; + return true; + break; + + // Load register can be used to load PC, usually with a function pointer + case ARM_INST_LDR: + if (decodedInstruction.op[REG_RD].value != PC_REG) + { + LogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + switch (decodedInstruction.addressMode) + { + case ARM_ADDR_LSWUB_IMM: + case ARM_ADDR_LSWUB_IMM_PRE: + case ARM_ADDR_LSWUB_IMM_POST: + if (decodedInstruction.numOperands != 3) + { + LogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + // Get baseAddress from register (at index=1) + baseAddressIndex = decodedInstruction.op[1].value; + baseAddress = m_state.gpr.r[baseAddressIndex]; + + // Get immediateOffset (at index=2) + immediateOffset = decodedInstruction.op[2].value; + break; + + case ARM_ADDR_LSWUB_REG: + case ARM_ADDR_LSWUB_REG_PRE: + case ARM_ADDR_LSWUB_REG_POST: + if (decodedInstruction.numOperands != 3) + { + LogError("Expected 3 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + // Get baseAddress from register (at index=1) + baseAddressIndex = decodedInstruction.op[1].value; + baseAddress = m_state.gpr.r[baseAddressIndex]; + + // Get immediateOffset from register (at index=2) + addressOffsetFromRegisterIndex = decodedInstruction.op[2].value; + addressOffsetFromRegister = m_state.gpr.r[addressOffsetFromRegisterIndex]; + + break; + + case ARM_ADDR_LSWUB_SCALED: + case ARM_ADDR_LSWUB_SCALED_PRE: + case ARM_ADDR_LSWUB_SCALED_POST: + if (decodedInstruction.numOperands != 4) + { + LogError("Expected 4 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + // Get baseAddress from register (at index=1) + baseAddressIndex = decodedInstruction.op[1].value; + baseAddress = m_state.gpr.r[baseAddressIndex]; + + // Get immediateOffset from register (at index=2) + addressOffsetFromRegisterIndex = decodedInstruction.op[2].value; + addressOffsetFromRegister = m_state.gpr.r[addressOffsetFromRegisterIndex]; + + // Get shiftAmount (at index=3) + shiftAmount = decodedInstruction.op[3].value; + + break; + + default: + break; + } + break; + + // 32b load multiple operations can load the PC along with everything else, + // usually to return from a function call + case ARM_INST_LDMDA: + case ARM_INST_LDMDB: + case ARM_INST_LDMIA: + case ARM_INST_LDMIB: + if (decodedInstruction.op[LDM_REGLIST].value & PC_REGLIST_BIT) + { + if (decodedInstruction.numOperands != 2) + { + LogError("Expected 2 operands in decoded instruction structure. numOperands is %d!", decodedInstruction.numOperands); + return false; + } + + // Get baseAddress from register (at index=0) + baseAddressIndex = decodedInstruction.op[0].value; + baseAddress = m_state.gpr.r[baseAddressIndex]; + + // Get registerList from register (at index=1) + registerList16 = (uint16_t)decodedInstruction.op[1].value; + + // Count number of registers to load in the multiple register list excluding the PC + registerList16NoPC = registerList16&0x3FFF; // exclude the PC + numRegistersToLoad=0; + for (int i = 0; i < 16; i++) + { + if (registerList16NoPC & 0x1) numRegistersToLoad++; + registerList16NoPC = registerList16NoPC >> 1; + } + } + else + { + LogError("Destination register is not a PC! %s routine should be called on on instructions that modify the PC. Destination register is R%d!", __FUNCTION__, decodedInstruction.op[0].value); + return false; + } + break; + + // Normal 16-bit LD multiple can't touch R15, but POP can + case ARM_INST_POP: // Can also get the PC & updates SP + // Get baseAddress from SP (at index=0) + baseAddress = m_state.gpr.__sp; + + if (decodedInstruction.thumb16b) + { + // Get registerList from register (at index=0) + registerList8 = (uint8_t)decodedInstruction.op[0].value; + + // Count number of registers to load in the multiple register list + numRegistersToLoad=0; + for (int i = 0; i < 8; i++) + { + if (registerList8 & 0x1) numRegistersToLoad++; + registerList8 = registerList8 >> 1; + } + } + else + { + // Get registerList from register (at index=0) + registerList16 = (uint16_t)decodedInstruction.op[0].value; + + // Count number of registers to load in the multiple register list excluding the PC + registerList16NoPC = registerList16&0x3FFF; // exclude the PC + numRegistersToLoad=0; + for (int i = 0; i < 16; i++) + { + if (registerList16NoPC & 0x1) numRegistersToLoad++; + registerList16NoPC = registerList16NoPC >> 1; + } + } + break; + + // 16b TBB and TBH instructions load a jump address from a table + case ARM_INST_TBB: + case ARM_INST_TBH: + // Get baseAddress from register (at index=0) + baseAddressIndex = decodedInstruction.op[0].value; + baseAddress = m_state.gpr.r[baseAddressIndex]; + + // Get immediateOffset from register (at index=1) + addressOffsetFromRegisterIndex = decodedInstruction.op[1].value; + addressOffsetFromRegister = m_state.gpr.r[addressOffsetFromRegisterIndex]; + break; + + // ThumbEE branch-to-handler instructions: Jump to handlers at some offset + // from a special base pointer register (which is unknown at disassembly time) + case ARM_INST_HB: + case ARM_INST_HBP: +// TODO: ARM_INST_HB, ARM_INST_HBP + break; + + case ARM_INST_HBL: + case ARM_INST_HBLP: +// TODO: ARM_INST_HBL, ARM_INST_HBLP + break; + + // Breakpoint and software interrupt jump to interrupt handler (always ARM) + case ARM_INST_BKPT: + case ARM_INST_SMC: + case ARM_INST_SVC: + + // Return from exception, obviously modifies PC [interrupt only!] + case ARM_INST_RFEDA: + case ARM_INST_RFEDB: + case ARM_INST_RFEIA: + case ARM_INST_RFEIB: + + // Other instructions either can't change R15 or are "undefined" if you do, + // so no sane compiler should ever generate them & we don't care here. + // Also, R15 can only legally be used in a read-only manner for the + // various ARM addressing mode (to get PC-relative addressing of constants), + // but can NOT be used with any of the update modes. + default: + LogError("%s should not be called for instruction code %d!", __FUNCTION__, decodedInstruction.instruction->code); + return false; + break; + } + + // Adjust PC if PC is one of the input operands + if (baseAddressIndex == PC_REG) + { + if (currentPCIsThumb) + baseAddress += 4; + else + baseAddress += 8; + } + + if (firstOperandIndex == PC_REG) + { + if (currentPCIsThumb) + firstOperand += 4; + else + firstOperand += 8; + } + + if (secondOperandIndex == PC_REG) + { + if (currentPCIsThumb) + secondOperand += 4; + else + secondOperand += 8; + } + + if (addressOffsetFromRegisterIndex == PC_REG) + { + if (currentPCIsThumb) + addressOffsetFromRegister += 4; + else + addressOffsetFromRegister += 8; + } + + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, + "%s: firstOperand=%8.8x, secondOperand=%8.8x, immediateValue = %d, shiftAmount = %d, baseAddress = %8.8x, addressOffsetFromRegister = %8.8x, immediateOffset = %d, numRegistersToLoad = %d", + __FUNCTION__, + firstOperand, + secondOperand, + immediateValue, + shiftAmount, + baseAddress, + addressOffsetFromRegister, + immediateOffset, + numRegistersToLoad); + + + // Calculate following values after applying shiftAmount: + // - immediateOffsetAfterShift, secondOperandAfterShift + + switch (decodedInstruction.scaleMode) + { + case ARM_SCALE_NONE: + addressOffsetFromRegisterAfterShift = addressOffsetFromRegister; + secondOperandAfterShift = secondOperand; + break; + + case ARM_SCALE_LSL: // Logical shift left + addressOffsetFromRegisterAfterShift = addressOffsetFromRegister << shiftAmount; + secondOperandAfterShift = secondOperand << shiftAmount; + break; + + case ARM_SCALE_LSR: // Logical shift right + addressOffsetFromRegisterAfterShift = addressOffsetFromRegister >> shiftAmount; + secondOperandAfterShift = secondOperand >> shiftAmount; + break; + + case ARM_SCALE_ASR: // Arithmetic shift right + asm("mov %0, %1, asr %2" : "=r" (addressOffsetFromRegisterAfterShift) : "r" (addressOffsetFromRegister), "r" (shiftAmount)); + asm("mov %0, %1, asr %2" : "=r" (secondOperandAfterShift) : "r" (secondOperand), "r" (shiftAmount)); + break; + + case ARM_SCALE_ROR: // Rotate right + asm("mov %0, %1, ror %2" : "=r" (addressOffsetFromRegisterAfterShift) : "r" (addressOffsetFromRegister), "r" (shiftAmount)); + asm("mov %0, %1, ror %2" : "=r" (secondOperandAfterShift) : "r" (secondOperand), "r" (shiftAmount)); + break; + + case ARM_SCALE_RRX: // Rotate right, pulling in carry (1-bit shift only) + asm("mov %0, %1, rrx" : "=r" (addressOffsetFromRegisterAfterShift) : "r" (addressOffsetFromRegister)); + asm("mov %0, %1, rrx" : "=r" (secondOperandAfterShift) : "r" (secondOperand)); + break; + } + + // Emulate instruction to calculate targetPC + // All branches are already handled in the first switch statement. A branch should not reach this switch + switch (decodedInstruction.instruction->code) + { + // Arithmetic operations that can change the PC + case ARM_INST_ADC: + case ARM_INST_ADCS: + // Add with Carry + *targetPC = firstOperand + (secondOperandAfterShift + immediateValue) + cpsr_c; + break; + + case ARM_INST_ADD: + case ARM_INST_ADDS: + *targetPC = firstOperand + (secondOperandAfterShift + immediateValue); + break; + + case ARM_INST_AND: + case ARM_INST_ANDS: + *targetPC = firstOperand & (secondOperandAfterShift + immediateValue); + break; + + case ARM_INST_ASR: + case ARM_INST_ASRS: + asm("mov %0, %1, asr %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); + *targetPC = myTargetPC; + break; + + case ARM_INST_BIC: + case ARM_INST_BICS: + asm("bic %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); + *targetPC = myTargetPC; + break; + + case ARM_INST_EOR: + case ARM_INST_EORS: + asm("eor %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); + *targetPC = myTargetPC; + break; + + case ARM_INST_ORR: + case ARM_INST_ORRS: + asm("orr %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); + *targetPC = myTargetPC; + break; + + case ARM_INST_RSB: + case ARM_INST_RSBS: + asm("rsb %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); + *targetPC = myTargetPC; + break; + + case ARM_INST_RSC: + case ARM_INST_RSCS: + myTargetPC = secondOperandAfterShift - (firstOperand + !cpsr_c); + *targetPC = myTargetPC; + break; + + case ARM_INST_SBC: + case ARM_INST_SBCS: + asm("sbc %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue + !cpsr_c)); + *targetPC = myTargetPC; + break; + + case ARM_INST_SUB: + case ARM_INST_SUBS: + asm("sub %0, %1, %2" : "=r" (myTargetPC) : "r" (firstOperand), "r" (secondOperandAfterShift + immediateValue)); + *targetPC = myTargetPC; + break; + + // Logical shifts and move operations that can change the PC + case ARM_INST_LSL: + case ARM_INST_LSLS: + case ARM_INST_LSR: + case ARM_INST_LSRS: + case ARM_INST_MOV: + case ARM_INST_MOVS: + case ARM_INST_ROR: + case ARM_INST_RORS: + case ARM_INST_RRX: + case ARM_INST_RRXS: + myTargetPC = secondOperandAfterShift + immediateValue; + *targetPC = myTargetPC; + break; + + case ARM_INST_MVN: + case ARM_INST_MVNS: + myTargetPC = !(secondOperandAfterShift + immediateValue); + *targetPC = myTargetPC; + break; + + // Load register can be used to load PC, usually with a function pointer + case ARM_INST_LDR: + switch (decodedInstruction.addressMode) { + case ARM_ADDR_LSWUB_IMM_POST: + case ARM_ADDR_LSWUB_REG_POST: + case ARM_ADDR_LSWUB_SCALED_POST: + addressWherePCLives = baseAddress; + break; + + case ARM_ADDR_LSWUB_IMM: + case ARM_ADDR_LSWUB_REG: + case ARM_ADDR_LSWUB_SCALED: + case ARM_ADDR_LSWUB_IMM_PRE: + case ARM_ADDR_LSWUB_REG_PRE: + case ARM_ADDR_LSWUB_SCALED_PRE: + addressWherePCLives = baseAddress + (addressOffsetFromRegisterAfterShift + immediateOffset); + break; + + default: + break; + } + + mypid = m_thread.ProcessID(); + if (PDProcessMemoryRead(mypid, addressWherePCLives, sizeof(lldb::addr_t), &myTargetPC) != sizeof(lldb::addr_t)) + { + LogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); + return false; + } + + *targetPC = myTargetPC; + break; + + // 32b load multiple operations can load the PC along with everything else, + // usually to return from a function call + case ARM_INST_LDMDA: + mypid = m_thread.ProcessID(); + addressWherePCLives = baseAddress; + if (PDProcessMemoryRead(mypid, addressWherePCLives, sizeof(lldb::addr_t), &myTargetPC) != sizeof(lldb::addr_t)) + { + LogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); + return false; + } + + *targetPC = myTargetPC; + break; + + case ARM_INST_LDMDB: + mypid = m_thread.ProcessID(); + addressWherePCLives = baseAddress - 4; + if (PDProcessMemoryRead(mypid, addressWherePCLives, sizeof(lldb::addr_t), &myTargetPC) != sizeof(lldb::addr_t)) + { + LogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); + return false; + } + + *targetPC = myTargetPC; + break; + + case ARM_INST_LDMIB: + mypid = m_thread.ProcessID(); + addressWherePCLives = baseAddress + numRegistersToLoad*4 + 4; + if (PDProcessMemoryRead(mypid, addressWherePCLives, sizeof(lldb::addr_t), &myTargetPC) != sizeof(lldb::addr_t)) + { + LogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); + return false; + } + + *targetPC = myTargetPC; + break; + + case ARM_INST_LDMIA: // same as pop + // Normal 16-bit LD multiple can't touch R15, but POP can + case ARM_INST_POP: // Can also get the PC & updates SP + mypid = m_thread.ProcessID(); + addressWherePCLives = baseAddress + numRegistersToLoad*4; + if (PDProcessMemoryRead(mypid, addressWherePCLives, sizeof(lldb::addr_t), &myTargetPC) != sizeof(lldb::addr_t)) + { + LogError("Could not read memory at %8.8x to get targetPC when processing the pop instruction!", addressWherePCLives); + return false; + } + + *targetPC = myTargetPC; + break; + + // 16b TBB and TBH instructions load a jump address from a table + case ARM_INST_TBB: + mypid = m_thread.ProcessID(); + addressWherePCLives = baseAddress + addressOffsetFromRegisterAfterShift; + if (PDProcessMemoryRead(mypid, addressWherePCLives, 1, &halfwords) != 1) + { + LogError("Could not read memory at %8.8x to get targetPC when processing the TBB instruction!", addressWherePCLives); + return false; + } + // add 4 to currentPC since we are in Thumb mode and then add 2*halfwords + *targetPC = (currentPC + 4) + 2*halfwords; + break; + + case ARM_INST_TBH: + mypid = m_thread.ProcessID(); + addressWherePCLives = ((baseAddress + (addressOffsetFromRegisterAfterShift << 1)) & ~0x1); + if (PDProcessMemoryRead(mypid, addressWherePCLives, 2, &halfwords) != 2) + { + LogError("Could not read memory at %8.8x to get targetPC when processing the TBH instruction!", addressWherePCLives); + return false; + } + // add 4 to currentPC since we are in Thumb mode and then add 2*halfwords + *targetPC = (currentPC + 4) + 2*halfwords; + break; + + // ThumbEE branch-to-handler instructions: Jump to handlers at some offset + // from a special base pointer register (which is unknown at disassembly time) + case ARM_INST_HB: + case ARM_INST_HBP: + // TODO: ARM_INST_HB, ARM_INST_HBP + break; + + case ARM_INST_HBL: + case ARM_INST_HBLP: + // TODO: ARM_INST_HBL, ARM_INST_HBLP + break; + + // Breakpoint and software interrupt jump to interrupt handler (always ARM) + case ARM_INST_BKPT: + case ARM_INST_SMC: + case ARM_INST_SVC: + // TODO: ARM_INST_BKPT, ARM_INST_SMC, ARM_INST_SVC + break; + + // Return from exception, obviously modifies PC [interrupt only!] + case ARM_INST_RFEDA: + case ARM_INST_RFEDB: + case ARM_INST_RFEIA: + case ARM_INST_RFEIB: + // TODO: ARM_INST_RFEDA, ARM_INST_RFEDB, ARM_INST_RFEIA, ARM_INST_RFEIB + break; + + // Other instructions either can't change R15 or are "undefined" if you do, + // so no sane compiler should ever generate them & we don't care here. + // Also, R15 can only legally be used in a read-only manner for the + // various ARM addressing mode (to get PC-relative addressing of constants), + // but can NOT be used with any of the update modes. + default: + LogError("%s should not be called for instruction code %d!", __FUNCTION__, decodedInstruction.instruction->code); + return false; + break; + } + + return true; +} + +void +MachThreadContext_arm::EvaluateNextInstructionForSoftwareBreakpointSetup(lldb::addr_t currentPC, uint32_t cpsr, bool currentPCIsThumb, lldb::addr_t *nextPC, bool *nextPCIsThumb) +{ + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "MachThreadContext_arm::EvaluateNextInstructionForSoftwareBreakpointSetup() called"); + + lldb::addr_t targetPC = LLDB_INVALID_ADDRESS; + uint32_t registerValue; + arm_error_t decodeError; + lldb::addr_t currentPCInITBlock, nextPCInITBlock; + int i; + bool last_decoded_instruction_executes = true; + + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: default nextPC=0x%8.8x (%s)", __FUNCTION__, *nextPC, *nextPCIsThumb ? "Thumb" : "ARM"); + + // Update *nextPC and *nextPCIsThumb for special cases + if (m_last_decode_thumb.itBlockRemaining) // we are in an IT block + { + // Set the nextPC to the PC of the instruction which will execute in the IT block + // If none of the instruction execute in the IT block based on the condition flags, + // then point to the instruction immediately following the IT block + const int itBlockRemaining = m_last_decode_thumb.itBlockRemaining; + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: itBlockRemaining=%8.8x", __FUNCTION__, itBlockRemaining); + + // Determine the PC at which the next instruction resides + if (m_last_decode_arm.thumb16b) + currentPCInITBlock = currentPC + 2; + else + currentPCInITBlock = currentPC + 4; + + for (i = 0; i < itBlockRemaining; i++) + { + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: currentPCInITBlock=%8.8x", __FUNCTION__, currentPCInITBlock); + decodeError = DecodeInstructionUsingDisassembler(currentPCInITBlock, cpsr, &m_last_decode_arm, &m_last_decode_thumb, &nextPCInITBlock); + + if (decodeError != ARM_SUCCESS) + LogError("unable to disassemble instruction at 0x%8.8lx", currentPCInITBlock); + + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: condition=%d", __FUNCTION__, m_last_decode_arm.condition); + if (ConditionPassed(m_last_decode_arm.condition, cpsr)) + { + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: Condition codes matched for instruction %d", __FUNCTION__, i); + break; // break from the for loop + } + else + { + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: Condition codes DID NOT matched for instruction %d", __FUNCTION__, i); + } + + // update currentPC and nextPCInITBlock + currentPCInITBlock = nextPCInITBlock; + } + + if (i == itBlockRemaining) // We came out of the IT block without executing any instructions + last_decoded_instruction_executes = false; + + *nextPC = currentPCInITBlock; + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: After IT block step-through: *nextPC=%8.8x", __FUNCTION__, *nextPC); + } + + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, + "%s: cpsr = %8.8x, thumb16b = %d, thumb = %d, branch = %d, conditional = %d, knownTarget = %d, links = %d, canSwitchMode = %d, doesSwitchMode = %d", + __FUNCTION__, + cpsr, + m_last_decode_arm.thumb16b, + m_last_decode_arm.thumb, + m_last_decode_arm.branch, + m_last_decode_arm.conditional, + m_last_decode_arm.knownTarget, + m_last_decode_arm.links, + m_last_decode_arm.canSwitchMode, + m_last_decode_arm.doesSwitchMode); + + + if (last_decoded_instruction_executes && // Was this a conditional instruction that did execute? + m_last_decode_arm.branch && // Can this instruction change the PC? + (m_last_decode_arm.instruction->code != ARM_INST_SVC)) // If this instruction is not an SVC instruction + { + // Set targetPC. Compute if needed. + if (m_last_decode_arm.knownTarget) + { + // Fixed, known PC-relative + targetPC = m_last_decode_arm.targetPC; + } + else + { + // if targetPC is not known at compile time (PC-relative target), compute targetPC + if (!ComputeNextPC(currentPC, m_last_decode_arm, currentPCIsThumb, &targetPC)) + { + LogError("%s: Unable to compute targetPC for instruction at 0x%8.8lx", __FUNCTION__, currentPC); + targetPC = LLDB_INVALID_ADDRESS; + } + } + + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: targetPC=0x%8.8x, cpsr=0x%8.8x, condition=0x%hhx", __FUNCTION__, targetPC, cpsr, m_last_decode_arm.condition); + + // Refine nextPC computation + if ((m_last_decode_arm.instruction->code == ARM_INST_CBZ) || + (m_last_decode_arm.instruction->code == ARM_INST_CBNZ)) + { + // Compare and branch on zero/non-zero (Thumb-16 only) + // Unusual condition check built into the instruction + registerValue = m_state.gpr.r[m_last_decode_arm.op[REG_RD].value]; + + if (m_last_decode_arm.instruction->code == ARM_INST_CBZ) + { + if (registerValue == 0) + *nextPC = targetPC; + } + else + { + if (registerValue != 0) + *nextPC = targetPC; + } + } + else if (m_last_decode_arm.conditional) // Is the change conditional on flag results? + { + if (ConditionPassed(m_last_decode_arm.condition, cpsr)) // conditions match + { + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: Condition matched!", __FUNCTION__); + *nextPC = targetPC; + } + else + { + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: Condition did not match!", __FUNCTION__); + } + } + else + { + *nextPC = targetPC; + } + + // Refine nextPCIsThumb computation + if (m_last_decode_arm.doesSwitchMode) + { + *nextPCIsThumb = !currentPCIsThumb; + } + else if (m_last_decode_arm.canSwitchMode) + { + // Legal to switch ARM <--> Thumb mode with this branch + // dependent on bit[0] of targetPC + *nextPCIsThumb = (*nextPC & 1u) != 0; + } + else + { + *nextPCIsThumb = currentPCIsThumb; + } + } + + ProcessMacOSXLog::LogIf(PD_LOG_STEP, "%s: calculated nextPC=0x%8.8x (%s)", __FUNCTION__, *nextPC, *nextPCIsThumb ? "Thumb" : "ARM"); +} + + +arm_error_t +MachThreadContext_arm::DecodeInstructionUsingDisassembler(lldb::addr_t curr_pc, uint32_t curr_cpsr, arm_decoded_instruction_t *decodedInstruction, thumb_static_data_t *thumbStaticData, lldb::addr_t *next_pc) +{ + + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: pc=0x%8.8x, cpsr=0x%8.8x", __FUNCTION__, curr_pc, curr_cpsr); + + const uint32_t isetstate_mask = MASK_CPSR_T | MASK_CPSR_J; + const uint32_t curr_isetstate = curr_cpsr & isetstate_mask; + uint32_t opcode32; + lldb::addr_t nextPC = curr_pc; + arm_error_t decodeReturnCode = ARM_SUCCESS; + + m_last_decode_pc = curr_pc; + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: last_decode_pc=0x%8.8x", __FUNCTION__, m_last_decode_pc); + + switch (curr_isetstate) { + case 0x0: // ARM Instruction + // Read the ARM opcode + if (m_thread.Process()->Task().ReadMemory(curr_pc, 4, &opcode32) != 4) + { + LogError("unable to read opcode bits 31:0 for an ARM opcode at 0x%8.8lx", curr_pc); + decodeReturnCode = ARM_ERROR; + } + else + { + nextPC += 4; + decodeReturnCode = ArmDisassembler((uint64_t)curr_pc, opcode32, false, decodedInstruction, NULL, 0, NULL, 0); + + if (decodeReturnCode != ARM_SUCCESS) + LogError("Unable to decode ARM instruction 0x%8.8x at 0x%8.8lx", opcode32, curr_pc); + } + break; + + case 0x20: // Thumb Instruction + uint16_t opcode16; + // Read the a 16 bit Thumb opcode + if (m_thread.Process()->Task().ReadMemory(curr_pc, 2, &opcode16) != 2) + { + LogError("unable to read opcode bits 15:0 for a thumb opcode at 0x%8.8lx", curr_pc); + decodeReturnCode = ARM_ERROR; + } + else + { + nextPC += 2; + opcode32 = opcode16; + + decodeReturnCode = ThumbDisassembler((uint64_t)curr_pc, opcode16, false, false, thumbStaticData, decodedInstruction, NULL, 0, NULL, 0); + + switch (decodeReturnCode) { + case ARM_SKIP: + // 32 bit thumb opcode + nextPC += 2; + if (m_thread.Process()->Task().ReadMemory(curr_pc+2, 2, &opcode16) != 2) + { + LogError("unable to read opcode bits 15:0 for a thumb opcode at 0x%8.8lx", curr_pc+2); + } + else + { + opcode32 = (opcode32 << 16) | opcode16; + + decodeReturnCode = ThumbDisassembler((uint64_t)(curr_pc+2), opcode16, false, false, thumbStaticData, decodedInstruction, NULL, 0, NULL, 0); + + if (decodeReturnCode != ARM_SUCCESS) + LogError("Unable to decode 2nd half of Thumb instruction 0x%8.4hx at 0x%8.8lx", opcode16, curr_pc+2); + break; + } + break; + + case ARM_SUCCESS: + // 16 bit thumb opcode; at this point we are done decoding the opcode + break; + + default: + LogError("Unable to decode Thumb instruction 0x%8.4hx at 0x%8.8lx", opcode16, curr_pc); + decodeReturnCode = ARM_ERROR; + break; + } + } + break; + + default: + break; + } + + if (next_pc) + *next_pc = nextPC; + + return decodeReturnCode; +} + +bool +MachThreadContext_arm::BreakpointHit(lldb::pid_t pid, lldb::tid_t tid, lldb::user_id_t breakID, void *baton) +{ + lldb::addr_t bkpt_pc = (lldb::addr_t)baton; + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s(pid = %i, tid = %4.4x, breakID = %u, baton = %p): Setting PC to 0x%8.8x", __FUNCTION__, pid, tid, breakID, baton, bkpt_pc); + return PDThreadSetRegisterValueByID(pid, tid, eRegisterKindGeneric, LLDB_REGNUM_GENERIC_PC, bkpt_pc); +} + +// Set the single step bit in the processor status register. +kern_return_t +MachThreadContext_arm::SetSingleStepSoftwareBreakpoints() +{ + Error err; + err = ReadGPRRegisters(false); + + if (err.Fail()) + { + err.Log("%s: failed to read the GPR registers", __FUNCTION__); + return err.GetError(); + } + + lldb::addr_t curr_pc = m_state.gpr.r[15]; + uint32_t curr_cpsr = m_state.gpr.__cpsr; + lldb::addr_t next_pc = curr_pc; + + bool curr_pc_is_thumb = (m_state.gpr.__cpsr & 0x20) != 0; + bool next_pc_is_thumb = curr_pc_is_thumb; + + uint32_t curr_itstate = ((curr_cpsr & 0x6000000) >> 25) | ((curr_cpsr & 0xFC00) >> 8); + bool inITBlock = (curr_itstate & 0xF) ? 1 : 0; + bool lastInITBlock = ((curr_itstate & 0xF) == 0x8) ? 1 : 0; + + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: curr_pc=0x%8.8x (%s), curr_itstate=0x%x, inITBlock=%d, lastInITBlock=%d", __FUNCTION__, curr_pc, curr_pc_is_thumb ? "Thumb" : "ARM", curr_itstate, inITBlock, lastInITBlock); + + // If the instruction is not in the IT block, then decode using the Disassembler and compute next_pc + if (!inITBlock) + { + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: Decoding an instruction NOT in the IT block", __FUNCTION__); + + arm_error_t decodeReturnCode = DecodeInstructionUsingDisassembler(curr_pc, curr_cpsr, &m_last_decode_arm, &m_last_decode_thumb, &next_pc); + + if (decodeReturnCode != ARM_SUCCESS) + { + err = KERN_INVALID_ARGUMENT; + LogError("MachThreadContext_arm::SetSingleStepSoftwareBreakpoints: Unable to disassemble instruction at 0x%8.8lx", curr_pc); + } + } + else + { + next_pc = curr_pc + ((m_last_decode_arm.thumb16b) ? 2 : 4); + } + + // Instruction is NOT in the IT block OR + if (!inITBlock) + { + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: normal instruction", __FUNCTION__); + EvaluateNextInstructionForSoftwareBreakpointSetup(curr_pc, m_state.gpr.__cpsr, curr_pc_is_thumb, &next_pc, &next_pc_is_thumb); + } + else if (inITBlock && !m_last_decode_arm.setsFlags) + { + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: IT instruction that doesn't set flags", __FUNCTION__); + EvaluateNextInstructionForSoftwareBreakpointSetup(curr_pc, m_state.gpr.__cpsr, curr_pc_is_thumb, &next_pc, &next_pc_is_thumb); + } + else if (lastInITBlock && m_last_decode_arm.branch) + { + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: IT instruction which last in the IT block and is a branch", __FUNCTION__); + EvaluateNextInstructionForSoftwareBreakpointSetup(curr_pc, m_state.gpr.__cpsr, curr_pc_is_thumb, &next_pc, &next_pc_is_thumb); + } + else + { + // Instruction is in IT block and can modify the CPSR flags + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: IT instruction that sets flags", __FUNCTION__); + + // NOTE: When this point of code is reached, the instruction at curr_pc has already been decoded + // inside the function ShouldStop (). Therefore m_last_decode_arm, m_last_decode_thumb + // reflect the decoded instruction at curr_pc + + // If we find an instruction inside the IT block which will set/modify the condition flags (NZCV bits in CPSR), + // we set breakpoints at all remaining instructions inside the IT block starting from the instruction immediately + // following this one AND a breakpoint at the instruction immediately following the IT block. We do this because + // we cannot determine the next_pc until the instruction at which we are currently stopped executes. Hence we + // insert (m_last_decode_thumb.itBlockRemaining+1) 16-bit Thumb breakpoints at consecutive memory locations + // starting at addrOfNextInstructionInITBlock. We record these breakpoints in class variable m_sw_single_step_itblock_break_id[], + // and also record the total number of IT breakpoints set in the variable 'm_sw_single_step_itblock_break_count'. + + // The instructions inside the IT block, which are replaced by the 16-bit Thumb breakpoints (opcode=0xDEFE) + // instructions, can be either Thumb-16 or Thumb-32. When a Thumb-32 instruction (say, inst#1) is replaced Thumb + // by a 16-bit breakpoint (OS only supports 16-bit breakpoints in Thumb mode and 32-bit breakpoints in ARM mode), the + // breakpoint for the next instruction (say instr#2) is saved in the upper half of this Thumb-32 (instr#1) + // instruction. Hence if the execution stops at Breakpoint2 corresponding to instr#2, the PC is offset by 16-bits. + // We therefore have to keep track of PC of each instruction in the IT block that is being replaced with the 16-bit + // Thumb breakpoint, to ensure that when the breakpoint is hit, the PC is adjusted to the correct value. We save + // the actual PC corresponding to each instruction in the IT block by associating a call back with each breakpoint + // we set and passing it as a baton. When the breakpoint hits and the callback routine is called, the routine + // adjusts the PC based on the baton that is passed to it. + + lldb::addr_t addrOfNextInstructionInITBlock, pcInITBlock, nextPCInITBlock, bpAddressInITBlock; + uint16_t opcode16; + uint32_t opcode32; + + addrOfNextInstructionInITBlock = (m_last_decode_arm.thumb16b) ? curr_pc + 2 : curr_pc + 4; + + pcInITBlock = addrOfNextInstructionInITBlock; + + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: itBlockRemaining=%d", __FUNCTION__, m_last_decode_thumb.itBlockRemaining); + + m_sw_single_step_itblock_break_count = 0; + for (int i = 0; i <= m_last_decode_thumb.itBlockRemaining; i++) + { + if (LLDB_BREAK_ID_IS_VALID(m_sw_single_step_itblock_break_id[i])) + { + LogError("FunctionProfiler::SetSingleStepSoftwareBreakpoints(): Array m_sw_single_step_itblock_break_id should not contain any valid breakpoint IDs at this point. But found a valid breakID=%d at index=%d", m_sw_single_step_itblock_break_id[i], i); + } + else + { + nextPCInITBlock = pcInITBlock; + // Compute nextPCInITBlock based on opcode present at pcInITBlock + if (m_thread.Process()->Task().ReadMemory(pcInITBlock, 2, &opcode16) == 2) + { + opcode32 = opcode16; + nextPCInITBlock += 2; + + // Check for 32 bit thumb opcode and read the upper 16 bits if needed + if (((opcode32 & 0xE000) == 0xE000) && (opcode32 & 0x1800)) + { + // Adjust 'next_pc_in_itblock' to point to the default next Thumb instruction for + // a 32 bit Thumb opcode + // Read bits 31:16 of a 32 bit Thumb opcode + if (m_thread.Process()->Task().ReadMemory(pcInITBlock+2, 2, &opcode16) == 2) + { + // 32 bit thumb opcode + opcode32 = (opcode32 << 16) | opcode16; + nextPCInITBlock += 2; + } + else + { + LogError("FunctionProfiler::SetSingleStepSoftwareBreakpoints(): Unable to read opcode bits 31:16 for a 32 bit thumb opcode at pc=0x%8.8lx", nextPCInITBlock); + } + } + } + else + { + LogError("FunctionProfiler::SetSingleStepSoftwareBreakpoints(): Error reading 16-bit Thumb instruction at pc=0x%8.8x", nextPCInITBlock); + } + + + // Set breakpoint and associate a callback function with it + bpAddressInITBlock = addrOfNextInstructionInITBlock + 2*i; + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: Setting IT breakpoint[%d] at address: 0x%8.8x", __FUNCTION__, i, bpAddressInITBlock); + + m_sw_single_step_itblock_break_id[i] = m_thread.Process()->CreateBreakpoint(bpAddressInITBlock, 2, false, m_thread.GetID()); + if (!LLDB_BREAK_ID_IS_VALID(m_sw_single_step_itblock_break_id[i])) + err = KERN_INVALID_ARGUMENT; + else + { + ProcessMacOSXLog::LogIf(PD_LOG_STEP, "%s: Set IT breakpoint[%i]=%d set at 0x%8.8x for instruction at 0x%8.8x", __FUNCTION__, i, m_sw_single_step_itblock_break_id[i], bpAddressInITBlock, pcInITBlock); + + // Set the breakpoint callback for these special IT breakpoints + // so that if one of these breakpoints gets hit, it knows to + // update the PC to the original address of the conditional + // IT instruction. + PDBreakpointSetCallback(m_thread.ProcessID(), m_sw_single_step_itblock_break_id[i], MachThreadContext_arm::BreakpointHit, (void*)pcInITBlock); + m_sw_single_step_itblock_break_count++; + } + } + + pcInITBlock = nextPCInITBlock; + } + + ProcessMacOSXLog::LogIf(PD_LOG_STEP | PD_LOG_VERBOSE, "%s: Set %u IT software single breakpoints.", __FUNCTION__, m_sw_single_step_itblock_break_count); + + } + + ProcessMacOSXLog::LogIf(PD_LOG_STEP, "%s: next_pc=0x%8.8x (%s)", __FUNCTION__, next_pc, next_pc_is_thumb ? "Thumb" : "ARM"); + + if (next_pc & 0x1) + { + assert(next_pc_is_thumb); + } + + if (next_pc_is_thumb) + { + next_pc &= ~0x1; + } + else + { + assert((next_pc & 0x3) == 0); + } + + if (!inITBlock || (inITBlock && !m_last_decode_arm.setsFlags) || (lastInITBlock && m_last_decode_arm.branch)) + { + err = KERN_SUCCESS; + +#if defined DNB_ARCH_MACH_ARM_DEBUG_SW_STEP + m_sw_single_step_next_pc = next_pc; + if (next_pc_is_thumb) + m_sw_single_step_next_pc |= 1; // Set bit zero if the next PC is expected to be Thumb +#else + const DNBBreakpoint *bp = m_thread.Process()->Breakpoints().FindByAddress(next_pc); + + if (bp == NULL) + { + m_sw_single_step_break_id = m_thread.Process()->CreateBreakpoint(next_pc, next_pc_is_thumb ? 2 : 4, false, m_thread.GetID()); + if (!LLDB_BREAK_ID_IS_VALID(m_sw_single_step_break_id)) + err = KERN_INVALID_ARGUMENT; + ProcessMacOSXLog::LogIf(PD_LOG_STEP, "%s: software single step breakpoint with breakID=%d set at 0x%8.8x", __FUNCTION__, m_sw_single_step_break_id, next_pc); + } +#endif + } + + return err.GetError(); +} + +#endif + +MachThreadContext* +MachThreadContext_arm::Create (const ArchSpec &arch_spec, ThreadMacOSX &thread) +{ + return new MachThreadContext_arm(thread); +} + +void +MachThreadContext_arm::Initialize() +{ + ArchSpec arch_spec(CPU_TYPE_ARM, CPU_TYPE_ANY); + ProcessMacOSX::AddArchCreateCallback(arch_spec, MachThreadContext_arm::Create); +} diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_arm.h b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_arm.h new file mode 100644 index 00000000000..4ec72dfd0d4 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_arm.h @@ -0,0 +1,63 @@ +//===-- MachThreadContext_arm.h ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_MachThreadContext_arm_h_ +#define liblldb_MachThreadContext_arm_h_ + +#include "MachThreadContext.h" +#include "RegisterContextMach_arm.h" + +class ThreadMacOSX; + +class MachThreadContext_arm : public MachThreadContext +{ +public: + enum { kMaxNumThumbITBreakpoints = 4 }; + + static MachThreadContext* + Create (const lldb_private::ArchSpec &arch_spec, ThreadMacOSX &thread); + + static void + Initialize(); + + MachThreadContext_arm(ThreadMacOSX &thread); + + virtual + ~MachThreadContext_arm(); + + virtual lldb_private::RegisterContext * + CreateRegisterContext (lldb_private::StackFrame *frame) const; + + virtual void + InitializeInstance(); + + virtual void + ThreadWillResume (); + + virtual bool + ShouldStop (); + + virtual void + RefreshStateAfterStop (); + + static uint32_t + GetCPUType (); + +protected: + kern_return_t + EnableHardwareSingleStep (bool enable); + +protected: + lldb::addr_t m_hw_single_chained_step_addr; + uint32_t m_bvr0_reg; + uint32_t m_bcr0_reg; + uint32_t m_bvr0_save; + uint32_t m_bcr0_save; +}; +#endif // #ifndef liblldb_MachThreadContext_arm_h_ diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_i386.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_i386.cpp new file mode 100644 index 00000000000..ea148a69f41 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_i386.cpp @@ -0,0 +1,245 @@ +//===-- MachThreadContext_i386.cpp ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#if defined (__i386__) || defined (__x86_64__) + + +#include "MachThreadContext_i386.h" + +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" + +#include "ProcessMacOSX.h" +#include "ThreadMacOSX.h" + +using namespace lldb; +using namespace lldb_private; + +MachThreadContext_i386::MachThreadContext_i386 (ThreadMacOSX &thread) : + MachThreadContext (thread), + m_flags_reg(LLDB_INVALID_REGNUM) +{ +} + +MachThreadContext_i386::~MachThreadContext_i386() +{ +} + + +MachThreadContext* +MachThreadContext_i386::Create (const ArchSpec &arch_spec, ThreadMacOSX &thread) +{ + return new MachThreadContext_i386(thread); +} + +// Class init function +void +MachThreadContext_i386::Initialize() +{ + ArchSpec arch_spec("i386"); + ProcessMacOSX::AddArchCreateCallback(arch_spec, MachThreadContext_i386::Create); +} + +// Instance init function +void +MachThreadContext_i386::InitializeInstance() +{ + RegisterContext *reg_ctx = m_thread.GetRegisterContext(); + assert (reg_ctx != NULL); + m_flags_reg = reg_ctx->ConvertRegisterKindToRegisterNumber (eRegisterKindGeneric, LLDB_REGNUM_GENERIC_FLAGS); +} + + +uint32_t +MachThreadContext_i386::GetCPUType() +{ + return CPU_TYPE_I386; +} + +void +MachThreadContext_i386::ThreadWillResume() +{ + m_thread.GetRegisterContext()->HardwareSingleStep (m_thread.GetState() == eStateStepping); +} + +bool +MachThreadContext_i386::ShouldStop() +{ + return true; +} + +void +MachThreadContext_i386::RefreshStateAfterStop() +{ + m_thread.GetRegisterContext()->HardwareSingleStep (false); +} + +bool +MachThreadContext_i386::NotifyException (MachException::Data& exc) +{ + switch (exc.exc_type) + { + case EXC_BAD_ACCESS: + break; + case EXC_BAD_INSTRUCTION: + break; + case EXC_ARITHMETIC: + break; + case EXC_EMULATION: + break; + case EXC_SOFTWARE: + break; + case EXC_BREAKPOINT: + if (exc.exc_data.size() >= 2 && exc.exc_data[0] == 2) + { + RegisterContext *reg_ctx = m_thread.GetRegisterContext(); + assert (reg_ctx); + lldb::addr_t pc = reg_ctx->GetPC(LLDB_INVALID_ADDRESS); + if (pc != LLDB_INVALID_ADDRESS && pc > 0) + { + pc -= 1; + reg_ctx->SetPC(pc); + } + return true; + } + break; + case EXC_SYSCALL: + break; + case EXC_MACH_SYSCALL: + break; + case EXC_RPC_ALERT: + break; + } + return false; +} + + +// Set the single step bit in the processor status register. +//kern_return_t +//MachThreadContext_i386::EnableHardwareSingleStep (bool enable) +//{ +// RegisterContext *reg_ctx = m_thread.GetRegisterContext(); +// assert (reg_ctx); +// Scalar rflags_scalar; +// +// if (reg_ctx->ReadRegisterValue (m_flags_reg, rflags_scalar)) +// { +// Flags rflags(rflags_scalar.UInt()); +// const uint32_t trace_bit = 0x100u; +// if (enable) +// { +// // If the trace bit is already cleared, there is nothing to do +// if (rflags.IsSet (trace_bit)) +// return KERN_SUCCESS; +// else +// rflags.Set (trace_bit); +// } +// else +// { +// // If the trace bit is already cleared, there is nothing to do +// if (rflags.IsClear (trace_bit)) +// return KERN_SUCCESS; +// else +// rflags.Clear(trace_bit); +// } +// +// rflags_scalar = rflags.GetAllFlagBits(); +// // If the code makes it here we have changes to the GPRs which +// // we need to write back out, so lets do that. +// if (reg_ctx->WriteRegisterValue(m_flags_reg, rflags_scalar)) +// return KERN_SUCCESS; +// } +// // Return the error code for reading the GPR registers back +// return KERN_INVALID_ARGUMENT; +//} + +RegisterContext * +MachThreadContext_i386::CreateRegisterContext (StackFrame *frame) const +{ + return new RegisterContextMach_i386(m_thread, frame); +} + + +size_t +MachThreadContext_i386::GetStackFrameData(StackFrame *first_frame, std::vector<std::pair<lldb::addr_t, lldb::addr_t> >& fp_pc_pairs) +{ + fp_pc_pairs.clear(); + + std::pair<lldb::addr_t, lldb::addr_t> fp_pc_pair; + + typedef struct Frame_i386 + { + uint32_t fp; + uint32_t pc; + }; + + RegisterContext *reg_ctx = m_thread.GetRegisterContext(); + assert (reg_ctx); + + Frame_i386 frame = { reg_ctx->GetFP(0), reg_ctx->GetPC(LLDB_INVALID_ADDRESS) }; + + fp_pc_pairs.push_back(std::make_pair(frame.fp, frame.pc)); + + const size_t k_frame_size = sizeof(frame); + Error error; + + while (frame.fp != 0 && frame.pc != 0 && ((frame.fp & 7) == 0)) + { + // Read both the FP and PC (8 bytes) + if (m_thread.GetProcess().ReadMemory (frame.fp, &frame.fp, k_frame_size, error) != k_frame_size) + break; + + if (frame.pc != 0) + fp_pc_pairs.push_back(std::make_pair(frame.fp, frame.pc)); + } + if (!fp_pc_pairs.empty()) + { + lldb::addr_t first_frame_pc = fp_pc_pairs.front().second; + if (first_frame_pc != LLDB_INVALID_ADDRESS) + { + const uint32_t resolve_scope = eSymbolContextModule | + eSymbolContextCompUnit | + eSymbolContextFunction | + eSymbolContextSymbol; + + SymbolContext first_frame_sc(first_frame->GetSymbolContext(resolve_scope)); + const AddressRange *addr_range_ptr = NULL; + if (first_frame_sc.function) + addr_range_ptr = &first_frame_sc.function->GetAddressRange(); + else if (first_frame_sc.symbol) + addr_range_ptr = first_frame_sc.symbol->GetAddressRangePtr(); + + if (addr_range_ptr) + { + if (first_frame->GetPC() == addr_range_ptr->GetBaseAddress()) + { + // We are at the first instruction, so we can recover the + // previous PC by dereferencing the SP + lldb::addr_t first_frame_sp = reg_ctx->GetSP(0); + // Read the real second frame return address into frame.pc + if (m_thread.GetProcess().ReadMemory (first_frame_sp, &frame.pc, sizeof(frame.pc), error) == sizeof(frame.pc)) + { + // Construct a correct second frame (we already read the pc for it above + frame.fp = fp_pc_pairs.front().first; + + // Insert the frame + fp_pc_pairs.insert(fp_pc_pairs.begin()+1, std::make_pair(frame.fp, frame.pc)); + + // Correct the fp in the first frame to use the SP + fp_pc_pairs.front().first = first_frame_sp; + } + } + } + } + } + return fp_pc_pairs.size(); +} + + +#endif // #if defined (__i386__) diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_i386.h b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_i386.h new file mode 100644 index 00000000000..4bee2e7be8f --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_i386.h @@ -0,0 +1,57 @@ +//===-- MachThreadContext_i386.h --------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_MachThreadContext_i386_h_ +#define liblldb_MachThreadContext_i386_h_ + +#if defined (__i386__) || defined (__x86_64__) + +#include "MachThreadContext.h" +#include "RegisterContextMach_i386.h" + +class ThreadMacOSX; + +class MachThreadContext_i386 : public MachThreadContext +{ +public: + static MachThreadContext* Create(const lldb_private::ArchSpec &arch_spec, ThreadMacOSX &thread); + + // Class init function + static void Initialize(); + + MachThreadContext_i386(ThreadMacOSX &thread); + + virtual + ~MachThreadContext_i386(); + + virtual lldb_private::RegisterContext * + CreateRegisterContext (lldb_private::StackFrame *frame) const; + + virtual void InitializeInstance(); + virtual void ThreadWillResume(); + virtual bool ShouldStop (); + virtual void RefreshStateAfterStop(); + + virtual bool NotifyException(MachException::Data& exc); + virtual size_t GetStackFrameData(lldb_private::StackFrame *first_frame, std::vector<std::pair<lldb::addr_t, lldb::addr_t> >& fp_pc_pairs); + static uint32_t GetCPUType(); + +protected: +// kern_return_t EnableHardwareSingleStep (bool enable); + uint32_t m_flags_reg; +private: + DISALLOW_COPY_AND_ASSIGN (MachThreadContext_i386); +}; + +//#if defined (__i386__) +//typedef MachThreadContext_i386 DNBArch; +//#endif + +#endif // defined (__i386__) || defined (__x86_64__) +#endif // #ifndef liblldb_MachThreadContext_i386_h_ diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_x86_64.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_x86_64.cpp new file mode 100644 index 00000000000..3ee9eb419de --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_x86_64.cpp @@ -0,0 +1,255 @@ +//===-- MachThreadContext_x86_64.cpp ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#if defined (__i386__) || defined (__x86_64__) + +#include <sys/cdefs.h> + +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/Symbol.h" + +#include "MachThreadContext_x86_64.h" +#include "ProcessMacOSX.h" +#include "ThreadMacOSX.h" + +using namespace lldb; +using namespace lldb_private; + +MachThreadContext_x86_64::MachThreadContext_x86_64(ThreadMacOSX &thread) : + MachThreadContext (thread), + m_flags_reg(LLDB_INVALID_REGNUM) +{ +} + +MachThreadContext_x86_64::~MachThreadContext_x86_64() +{ +} + +MachThreadContext* +MachThreadContext_x86_64::Create(const ArchSpec &arch_spec, ThreadMacOSX &thread) +{ + return new MachThreadContext_x86_64(thread); +} + +// Class init function +void +MachThreadContext_x86_64::Initialize() +{ + ArchSpec arch_spec("x86_64"); + ProcessMacOSX::AddArchCreateCallback(arch_spec, MachThreadContext_x86_64::Create); +} + +// Instance init function +void +MachThreadContext_x86_64::InitializeInstance() +{ + RegisterContext *reg_ctx = m_thread.GetRegisterContext(); + assert (reg_ctx != NULL); + m_flags_reg = reg_ctx->ConvertRegisterKindToRegisterNumber (eRegisterKindGeneric, LLDB_REGNUM_GENERIC_FLAGS); +} + +uint32_t +MachThreadContext_x86_64::GetCPUType() +{ + return CPU_TYPE_X86_64; +} + +void +MachThreadContext_x86_64::ThreadWillResume() +{ + m_thread.GetRegisterContext()->HardwareSingleStep (m_thread.GetState() == eStateStepping); +} + +bool +MachThreadContext_x86_64::ShouldStop() +{ + return true; +} + +void +MachThreadContext_x86_64::RefreshStateAfterStop() +{ + m_thread.GetRegisterContext()->HardwareSingleStep (false); +} + +bool +MachThreadContext_x86_64::NotifyException(MachException::Data& exc) +{ + switch (exc.exc_type) + { + case EXC_BAD_ACCESS: + break; + case EXC_BAD_INSTRUCTION: + break; + case EXC_ARITHMETIC: + break; + case EXC_EMULATION: + break; + case EXC_SOFTWARE: + break; + case EXC_BREAKPOINT: + if (exc.exc_data.size() >= 2 && exc.exc_data[0] == 2) + { + RegisterContext *reg_ctx = m_thread.GetRegisterContext(); + assert (reg_ctx); + lldb::addr_t pc = reg_ctx->GetPC(LLDB_INVALID_ADDRESS); + if (pc != LLDB_INVALID_ADDRESS && pc > 0) + { + pc -= 1; + reg_ctx->SetPC(pc); + } + return true; + } + break; + case EXC_SYSCALL: + break; + case EXC_MACH_SYSCALL: + break; + case EXC_RPC_ALERT: + break; + } + return false; +} + + +// Set the single step bit in the processor status register. +//kern_return_t +//MachThreadContext_x86_64::EnableHardwareSingleStep (bool enable) +//{ +// RegisterContext *reg_ctx = m_thread.GetRegisterContext(); +// assert (reg_ctx); +// Scalar rflags_scalar; +// +// if (reg_ctx->ReadRegisterValue (m_flags_reg, rflags_scalar)) +// { +// Flags rflags(rflags_scalar.UInt()); +// const uint32_t trace_bit = 0x100u; +// if (enable) +// { +// // If the trace bit is already set, there is nothing to do +// if (rflags.IsSet (trace_bit)) +// return KERN_SUCCESS; +// else +// rflags.Set (trace_bit); +// } +// else +// { +// // If the trace bit is already cleared, there is nothing to do +// if (rflags.IsClear (trace_bit)) +// return KERN_SUCCESS; +// else +// rflags.Clear(trace_bit); +// } +// +// rflags_scalar = rflags.GetAllFlagBits(); +// // If the code makes it here we have changes to the GPRs which +// // we need to write back out, so lets do that. +// if (reg_ctx->WriteRegisterValue(m_flags_reg, rflags_scalar)) +// return KERN_SUCCESS; +// } +// // Return the error code for reading the GPR registers back +// return KERN_INVALID_ARGUMENT; +//} +// + +//---------------------------------------------------------------------- +// Register information defintions for 32 bit PowerPC. +//---------------------------------------------------------------------- + + + +RegisterContext * +MachThreadContext_x86_64::CreateRegisterContext (StackFrame *frame) const +{ + return new RegisterContextMach_x86_64(m_thread, frame); +} + + +//bool +//MachThreadContext_x86_64::RegisterSetStateIsValid (uint32_t set) const +//{ +// return m_state.RegisterSetIsCached(set); +//} + + +size_t +MachThreadContext_x86_64::GetStackFrameData(StackFrame *first_frame, std::vector<std::pair<lldb::addr_t, lldb::addr_t> >& fp_pc_pairs) +{ + fp_pc_pairs.clear(); + + std::pair<lldb::addr_t, lldb::addr_t> fp_pc_pair; + + typedef struct Frame_x86_64 + { + uint64_t fp; + uint64_t pc; + }; + + RegisterContext *reg_ctx = m_thread.GetRegisterContext(); + assert (reg_ctx); + + Frame_x86_64 frame = { reg_ctx->GetFP(0), reg_ctx->GetPC(LLDB_INVALID_ADDRESS) }; + + fp_pc_pairs.push_back(std::make_pair(frame.fp, frame.pc)); + Error error; + const size_t k_frame_size = sizeof(frame); + while (frame.fp != 0 && frame.pc != 0 && ((frame.fp & 7) == 0)) + { + // Read both the FP and PC (16 bytes) + if (m_thread.GetProcess().ReadMemory (frame.fp, &frame.fp, k_frame_size, error) != k_frame_size) + break; + + if (frame.pc >= 0x1000) + fp_pc_pairs.push_back(std::make_pair(frame.fp, frame.pc)); + } + if (!fp_pc_pairs.empty()) + { + lldb::addr_t first_frame_pc = fp_pc_pairs.front().second; + if (first_frame_pc != LLDB_INVALID_ADDRESS) + { + const uint32_t resolve_scope = eSymbolContextModule | + eSymbolContextCompUnit | + eSymbolContextFunction | + eSymbolContextSymbol; + + SymbolContext first_frame_sc(first_frame->GetSymbolContext(resolve_scope)); + const AddressRange *addr_range_ptr = NULL; + if (first_frame_sc.function) + addr_range_ptr = &first_frame_sc.function->GetAddressRange(); + else if (first_frame_sc.symbol) + addr_range_ptr = first_frame_sc.symbol->GetAddressRangePtr(); + + if (addr_range_ptr) + { + if (first_frame->GetPC() == addr_range_ptr->GetBaseAddress()) + { + // We are at the first instruction, so we can recover the + // previous PC by dereferencing the SP + lldb::addr_t first_frame_sp = reg_ctx->GetSP(0); + // Read the real second frame return address into frame.pc + if (m_thread.GetProcess().ReadMemory (first_frame_sp, &frame.pc, sizeof(frame.pc), error) == sizeof(frame.pc)) + { + // Construct a correct second frame (we already read the pc for it above + frame.fp = fp_pc_pairs.front().first; + + // Insert the frame + fp_pc_pairs.insert(fp_pc_pairs.begin()+1, std::make_pair(frame.fp, frame.pc)); + + // Correct the fp in the first frame to use the SP + fp_pc_pairs.front().first = first_frame_sp; + } + } + } + } + } + return fp_pc_pairs.size(); +} + + +#endif // #if defined (__i386__) || defined (__x86_64__) diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_x86_64.h b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_x86_64.h new file mode 100644 index 00000000000..45d5a375052 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachThreadContext_x86_64.h @@ -0,0 +1,72 @@ +//===-- MachThreadContext_x86_64.h ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_MachThreadContext_x86_64_h_ +#define liblldb_MachThreadContext_x86_64_h_ + +#if defined (__i386__) || defined (__x86_64__) + +#include "MachThreadContext.h" +#include "RegisterContextMach_x86_64.h" + +class ThreadMacOSX; + +class MachThreadContext_x86_64 : public MachThreadContext +{ +public: + static MachThreadContext* + Create(const lldb_private::ArchSpec &arch_spec, ThreadMacOSX &thread); + + // Class init function + static void + Initialize(); + + // Instance init function + void + InitializeInstance(); + + MachThreadContext_x86_64 (ThreadMacOSX &thread); + + virtual + ~MachThreadContext_x86_64(); + + virtual lldb_private::RegisterContext * + CreateRegisterContext (lldb_private::StackFrame *frame) const; + + virtual void + ThreadWillResume (); + + virtual bool + ShouldStop (); + + virtual void + RefreshStateAfterStop (); + + virtual bool + NotifyException (MachException::Data& exc); + + virtual size_t + GetStackFrameData (lldb_private::StackFrame *first_frame, std::vector<std::pair<lldb::addr_t, lldb::addr_t> >& fp_pc_pairs); + + static uint32_t + GetCPUType(); + +protected: +// kern_return_t EnableHardwareSingleStep (bool enable); + uint32_t m_flags_reg; +private: + DISALLOW_COPY_AND_ASSIGN (MachThreadContext_x86_64); +}; + +//#if defined (__x86_64__) +//typedef MachThreadContext_x86_64 DNBArch; +//#endif + +#endif // defined (__i386__) || defined (__x86_64__) +#endif // #ifndef liblldb_MachThreadContext_x86_64_h_ diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachVMMemory.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachVMMemory.cpp new file mode 100644 index 00000000000..c91af3c3596 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachVMMemory.cpp @@ -0,0 +1,195 @@ +//===-- MachVMMemory.cpp ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "MachVMMemory.h" + +#include <mach/mach_vm.h> + +#include "MachVMRegion.h" +#include "ProcessMacOSXLog.h" + +using namespace lldb_private; + +MachVMMemory::MachVMMemory() : + m_page_size (kInvalidPageSize) +{ +} + +MachVMMemory::~MachVMMemory() +{ +} + +size_t +MachVMMemory::PageSize(lldb_private::Error &error) +{ + if (m_page_size == kInvalidPageSize) + { + error = ::host_page_size( ::mach_host_self(), &m_page_size); + if (error.Fail()) + m_page_size = 0; + } + + if (m_page_size != 0 && m_page_size != kInvalidPageSize) + { + if (error.Success()) + error.SetErrorString ("unable to determine page size"); + } + return m_page_size; +} + +size_t +MachVMMemory::MaxBytesLeftInPage (lldb::addr_t addr, size_t count) +{ + Error error; + const size_t page_size = PageSize(error); + if (page_size > 0) + { + size_t page_offset = (addr % page_size); + size_t bytes_left_in_page = page_size - page_offset; + if (count > bytes_left_in_page) + count = bytes_left_in_page; + } + return count; +} + +size_t +MachVMMemory::Read(task_t task, lldb::addr_t address, void *data, size_t data_count, Error &error) +{ + if (data == NULL || data_count == 0) + return 0; + + size_t total_bytes_read = 0; + lldb::addr_t curr_addr = address; + uint8_t *curr_data = (uint8_t*)data; + while (total_bytes_read < data_count) + { + mach_vm_size_t curr_size = MaxBytesLeftInPage(curr_addr, data_count - total_bytes_read); + mach_msg_type_number_t curr_bytes_read = 0; + vm_offset_t vm_memory = NULL; + error = ::mach_vm_read (task, curr_addr, curr_size, &vm_memory, &curr_bytes_read); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_MEMORY|PD_LOG_VERBOSE); + + if (log || error.Fail()) + error.PutToLog (log, "::mach_vm_read (task = 0x%4.4x, addr = 0x%8.8llx, size = %llu, data => %8.8p, dataCnt => %i)", task, (uint64_t)curr_addr, (uint64_t)curr_size, vm_memory, curr_bytes_read); + + if (error.Success()) + { + if (curr_bytes_read != curr_size) + { + if (log) + error.PutToLog (log, "::mach_vm_read (task = 0x%4.4x, addr = 0x%8.8llx, size = %llu, data => %8.8p, dataCnt=>%i) only read %u of %llu bytes", task, (uint64_t)curr_addr, (uint64_t)curr_size, vm_memory, curr_bytes_read, curr_bytes_read, (uint64_t)curr_size); + } + ::memcpy (curr_data, (void *)vm_memory, curr_bytes_read); + ::vm_deallocate (mach_task_self (), vm_memory, curr_bytes_read); + total_bytes_read += curr_bytes_read; + curr_addr += curr_bytes_read; + curr_data += curr_bytes_read; + } + else + { + break; + } + } + return total_bytes_read; +} + + +size_t +MachVMMemory::Write(task_t task, lldb::addr_t address, const void *data, size_t data_count, Error &error) +{ + MachVMRegion vmRegion(task); + + size_t total_bytes_written = 0; + lldb::addr_t curr_addr = address; + const uint8_t *curr_data = (const uint8_t*)data; + + + while (total_bytes_written < data_count) + { + if (vmRegion.GetRegionForAddress(curr_addr)) + { + mach_vm_size_t curr_data_count = data_count - total_bytes_written; + mach_vm_size_t region_bytes_left = vmRegion.BytesRemaining(curr_addr); + if (region_bytes_left == 0) + { + break; + } + if (curr_data_count > region_bytes_left) + curr_data_count = region_bytes_left; + + if (vmRegion.SetProtections(curr_addr, curr_data_count, VM_PROT_READ | VM_PROT_WRITE)) + { + size_t bytes_written = WriteRegion(task, curr_addr, curr_data, curr_data_count, error); + if (bytes_written <= 0) + { + // Error should have already be posted by WriteRegion... + break; + } + else + { + total_bytes_written += bytes_written; + curr_addr += bytes_written; + curr_data += bytes_written; + } + } + else + { + ProcessMacOSXLog::LogIf (PD_LOG_MEMORY_PROTECTIONS, "Failed to set read/write protections on region for address: [0x%8.8llx-0x%8.8llx)", (uint64_t)curr_addr, (uint64_t)(curr_addr + curr_data_count)); + break; + } + } + else + { + ProcessMacOSXLog::LogIf (PD_LOG_MEMORY_PROTECTIONS, "Failed to get region for address: 0x%8.8llx", (uint64_t)address); + break; + } + } + + return total_bytes_written; +} + + +size_t +MachVMMemory::WriteRegion(task_t task, const lldb::addr_t address, const void *data, const size_t data_count, Error &error) +{ + if (data == NULL || data_count == 0) + return 0; + + size_t total_bytes_written = 0; + lldb::addr_t curr_addr = address; + const uint8_t *curr_data = (const uint8_t*)data; + while (total_bytes_written < data_count) + { + mach_msg_type_number_t curr_data_count = MaxBytesLeftInPage(curr_addr, data_count - total_bytes_written); + error = ::mach_vm_write (task, curr_addr, (pointer_t) curr_data, curr_data_count); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_MEMORY); + if (log || error.Fail()) + error.PutToLog (log, "::mach_vm_write ( task = 0x%4.4x, addr = 0x%8.8llx, data = %8.8p, dataCnt = %u )", task, (uint64_t)curr_addr, curr_data, curr_data_count); + +#if defined (__powerpc__) || defined (__ppc__) + vm_machine_attribute_val_t mattr_value = MATTR_VAL_CACHE_FLUSH; + + error = ::vm_machine_attribute (task, curr_addr, curr_data_count, MATTR_CACHE, &mattr_value); + if (log || error.Fail()) + error.Log(log, "::vm_machine_attribute ( task = 0x%4.4x, addr = 0x%8.8llx, size = %u, attr = MATTR_CACHE, mattr_value => MATTR_VAL_CACHE_FLUSH )", task, (uint64_t)curr_addr, curr_data_count); +#endif + + if (error.Success()) + { + total_bytes_written += curr_data_count; + curr_addr += curr_data_count; + curr_data += curr_data_count; + } + else + { + break; + } + } + return total_bytes_written; +} diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachVMMemory.h b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachVMMemory.h new file mode 100644 index 00000000000..866fa369706 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachVMMemory.h @@ -0,0 +1,36 @@ +//===-- MachVMMemory.h ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_MachVMMemory_h_ +#define liblldb_MachVMMemory_h_ + +#include <mach/mach.h> + +#include "lldb/lldb-private.h" +#include "lldb/Core/Error.h" + +class MachVMMemory +{ +public: + enum { kInvalidPageSize = ~0 }; + MachVMMemory(); + ~MachVMMemory(); + size_t Read(task_t task, lldb::addr_t address, void *data, size_t data_count, lldb_private::Error &error); + size_t Write(task_t task, lldb::addr_t address, const void *data, size_t data_count, lldb_private::Error &error); + size_t PageSize(lldb_private::Error &error); + +protected: + size_t MaxBytesLeftInPage(lldb::addr_t addr, size_t count); + + size_t WriteRegion(task_t task, const lldb::addr_t address, const void *data, const size_t data_count, lldb_private::Error &error); + vm_size_t m_page_size; +}; + + +#endif // #ifndef liblldb_MachVMMemory_h_ diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachVMRegion.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachVMRegion.cpp new file mode 100644 index 00000000000..87044fb31b2 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachVMRegion.cpp @@ -0,0 +1,183 @@ +//===-- MachVMRegion.cpp ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include <assert.h> +#include <mach/mach_vm.h> +#include "MachVMRegion.h" +#include "ProcessMacOSXLog.h" + +using namespace lldb_private; + +MachVMRegion::MachVMRegion(task_t task) : + m_task(task), + m_addr(LLDB_INVALID_ADDRESS), + m_err(), + m_start(LLDB_INVALID_ADDRESS), + m_size(0), + m_depth(-1), + m_data(), + m_curr_protection(0), + m_protection_addr(LLDB_INVALID_ADDRESS), + m_protection_size(0) +{ + memset(&m_data, 0, sizeof(m_data)); +} + +MachVMRegion::~MachVMRegion() +{ + // Restore any original protections and clear our vars + Clear(); +} + +void +MachVMRegion::Clear() +{ + RestoreProtections(); + m_addr = LLDB_INVALID_ADDRESS; + m_err.Clear(); + m_start = LLDB_INVALID_ADDRESS; + m_size = 0; + m_depth = -1; + memset(&m_data, 0, sizeof(m_data)); + m_curr_protection = 0; + m_protection_addr = LLDB_INVALID_ADDRESS; + m_protection_size = 0; +} + +bool +MachVMRegion::SetProtections(mach_vm_address_t addr, mach_vm_size_t size, vm_prot_t prot) +{ + if (ContainsAddress(addr)) + { + mach_vm_size_t prot_size = size; + mach_vm_address_t end_addr = EndAddress(); + if (prot_size > (end_addr - addr)) + prot_size = end_addr - addr; + + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_MEMORY_PROTECTIONS); + if (prot_size > 0) + { + if (prot == (m_curr_protection & VM_PROT_ALL)) + { + if (log) + log->Printf ("MachVMRegion::%s: protections (%u) already sufficient for task 0x%4.4x at address 0x%8.8llx) ", __FUNCTION__, prot, m_task, (uint64_t)addr); + // Protections are already set as requested... + return true; + } + else + { + m_err = ::mach_vm_protect (m_task, addr, prot_size, 0, prot); + if (log || m_err.Fail()) + m_err.PutToLog(log, "::mach_vm_protect ( task = 0x%4.4x, addr = 0x%8.8llx, size = %llu, set_max = %i, prot = %u )", m_task, (uint64_t)addr, (uint64_t)prot_size, 0, prot); + if (m_err.Fail()) + { + // Try again with the ability to create a copy on write region + m_err = ::mach_vm_protect (m_task, addr, prot_size, 0, prot | VM_PROT_COPY); + if (log || m_err.Fail()) + m_err.PutToLog(log, "::mach_vm_protect ( task = 0x%4.4x, addr = 0x%8.8llx, size = %llu, set_max = %i, prot = %u )", m_task, (uint64_t)addr, (uint64_t)prot_size, 0, prot | VM_PROT_COPY); + } + if (m_err.Success()) + { + m_curr_protection = prot; + m_protection_addr = addr; + m_protection_size = prot_size; + return true; + } + } + } + else + { + log->Printf("MachVMRegion::%s: Zero size for task 0x%4.4x at address 0x%8.8llx) ", __FUNCTION__, m_task, (uint64_t)addr); + } + } + return false; +} + +bool +MachVMRegion::RestoreProtections() +{ + if (m_curr_protection != m_data.protection && m_protection_size > 0) + { + m_err = ::mach_vm_protect (m_task, m_protection_addr, m_protection_size, 0, m_data.protection); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_MEMORY_PROTECTIONS); + if (log || m_err.Fail()) + m_err.PutToLog(log, "::mach_vm_protect ( task = 0x%4.4x, addr = 0x%8.8llx, size = %llu, set_max = %i, prot = %u )", m_task, (uint64_t)m_protection_addr, (uint64_t)m_protection_size, 0, m_data.protection); + if (m_err.Success()) + { + m_protection_size = 0; + m_protection_addr = LLDB_INVALID_ADDRESS; + m_curr_protection = m_data.protection; + return true; + } + } + else + { + m_err.Clear(); + return true; + } + + return false; +} + +bool +MachVMRegion::GetRegionForAddress(lldb::addr_t addr) +{ + // Restore any original protections and clear our vars + Clear(); + m_addr = addr; + m_start = addr; + m_depth = 1024; + mach_msg_type_number_t info_size = kRegionInfoSize; + assert(sizeof(info_size) == 4); + m_err = ::mach_vm_region_recurse (m_task, &m_start, &m_size, &m_depth, (vm_region_recurse_info_t)&m_data, &info_size); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_MEMORY_PROTECTIONS); + if (log || m_err.Fail()) + m_err.PutToLog(log, "::mach_vm_region_recurse ( task = 0x%4.4x, address => 0x%8.8llx, size => %llu, nesting_depth => %d, info => %p, infoCnt => %d) addr = 0x%8.8llx ", m_task, (uint64_t)m_start, (uint64_t)m_size, m_depth, &m_data, info_size, (uint64_t)addr); + if (m_err.Fail()) + { + return false; + } + else + { + if (log && log->GetMask().IsSet(PD_LOG_VERBOSE)) + { + log->Printf("info = { prot = %u, " + "max_prot = %u, " + "inheritance = 0x%8.8x, " + "offset = 0x%8.8llx, " + "user_tag = 0x%8.8x, " + "ref_count = %u, " + "shadow_depth = %u, " + "ext_pager = %u, " + "share_mode = %u, " + "is_submap = %d, " + "behavior = %d, " + "object_id = 0x%8.8x, " + "user_wired_count = 0x%4.4x }", + m_data.protection, + m_data.max_protection, + m_data.inheritance, + (uint64_t)m_data.offset, + m_data.user_tag, + m_data.ref_count, + m_data.shadow_depth, + m_data.external_pager, + m_data.share_mode, + m_data.is_submap, + m_data.behavior, + m_data.object_id, + m_data.user_wired_count); + } + } + + m_curr_protection = m_data.protection; + + return true; +} diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachVMRegion.h b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachVMRegion.h new file mode 100644 index 00000000000..b12353df823 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/MachVMRegion.h @@ -0,0 +1,63 @@ +//===-- MachVMRegion.h ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_MachVMRegion_h_ +#define liblldb_MachVMRegion_h_ + +#include <mach/mach.h> +#include "lldb/lldb-private.h" +#include "lldb/Core/Error.h" + +class MachVMRegion +{ +public: + MachVMRegion(task_t task); + ~MachVMRegion(); + + void Clear(); + mach_vm_address_t StartAddress() const { return m_start; } + mach_vm_address_t EndAddress() const { return m_start + m_size; } + mach_vm_address_t BytesRemaining(mach_vm_address_t addr) const + { + if (ContainsAddress(addr)) + return m_size - (addr - m_start); + else + return 0; + } + bool ContainsAddress(mach_vm_address_t addr) const + { + return addr >= StartAddress() && addr < EndAddress(); + } + + bool SetProtections(mach_vm_address_t addr, mach_vm_size_t size, vm_prot_t prot); + bool RestoreProtections(); + bool GetRegionForAddress(lldb::addr_t addr); +protected: +#if defined (VM_REGION_SUBMAP_SHORT_INFO_COUNT_64) + typedef vm_region_submap_short_info_data_64_t RegionInfo; + enum { kRegionInfoSize = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64 }; +#else + typedef vm_region_submap_info_data_64_t RegionInfo; + enum { kRegionInfoSize = VM_REGION_SUBMAP_INFO_COUNT_64 }; +#endif + + task_t m_task; + mach_vm_address_t m_addr; + lldb_private::Error m_err; + mach_vm_address_t m_start; + mach_vm_size_t m_size; + natural_t m_depth; + RegionInfo m_data; + vm_prot_t m_curr_protection; // The current, possibly modified protections. Original value is saved in m_data.protections. + mach_vm_address_t m_protection_addr; // The start address at which protections were changed + mach_vm_size_t m_protection_size; // The size of memory that had its protections changed + +}; + +#endif // #ifndef liblldb_MachVMRegion_h_ diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/ProcessControl-mig.defs b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/ProcessControl-mig.defs new file mode 100644 index 00000000000..7f16fe13356 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/MacOSX/ProcessControl-mig.defs @@ -0,0 +1,16 @@ +/* + * nub.defs + */ + +/* + * DNBConfig.h is autogenerated by a perl script that is run as a build + * script in XCode. XCode is responsible for calling the script and setting + * the include paths correctly to locate it. The file will exist in the + * derived sources directory in the build folder. + * + */ + +#include "DNBConfig.h" + + +#include <mach/mach_exc.defs> diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.cpp new file mode 100644 index 00000000000..3c37b64dc46 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.cpp @@ -0,0 +1,2228 @@ +//===-- ProcessMacOSX.cpp ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// C Includes +#include <errno.h> +#include <mach/mach.h> +#include <mach/mach_vm.h> +#include <spawn.h> +#include <sys/fcntl.h> +#include <sys/types.h> +#include <sys/ptrace.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <unistd.h> + +// C++ Includes +#include <algorithm> +#include <map> + +// Other libraries and framework includes + +#include "lldb/Breakpoint/WatchpointLocation.h" +#include "lldb/Core/ArchSpec.h" +#include "lldb/Core/FileSpec.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/State.h" +#include "lldb/Core/Timer.h" +#include "lldb/Host/TimeValue.h" +#include "lldb/Symbol/ObjectFile.h" +#include "lldb/Target/DynamicLoader.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/TargetList.h" +#include "PseudoTerminal.h" + +#if defined (__arm__) + +#include <CoreFoundation/CoreFoundation.h> +#include <SpringBoardServices/SpringBoardServer.h> +#include <SpringBoardServices/SBSWatchdogAssertion.h> + +#endif // #if defined (__arm__) + +// Project includes +#include "lldb/Host/Host.h" +#include "ProcessMacOSX.h" +#include "ProcessMacOSXLog.h" +#include "ThreadMacOSX.h" + + +#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 + + +#if defined (__arm__) + +static bool +IsSBProcess (lldb::pid_t pid) +{ + bool opt_runningApps = true; + bool opt_debuggable = false; + + CFReleaser<CFArrayRef> sbsAppIDs (::SBSCopyApplicationDisplayIdentifiers (opt_runningApps, opt_debuggable)); + if (sbsAppIDs.get() != NULL) + { + CFIndex count = ::CFArrayGetCount (sbsAppIDs.get()); + CFIndex i = 0; + for (i = 0; i < count; i++) + { + CFStringRef displayIdentifier = (CFStringRef)::CFArrayGetValueAtIndex (sbsAppIDs.get(), i); + + // Get the process id for the app (if there is one) + lldb::pid_t sbs_pid = LLDB_INVALID_PROCESS_ID; + if (::SBSProcessIDForDisplayIdentifier ((CFStringRef)displayIdentifier, &sbs_pid) == TRUE) + { + if (sbs_pid == pid) + return true; + } + } + } + return false; +} + + +#endif // #if defined (__arm__) + +using namespace lldb; +using namespace lldb_private; +// +//void * +//ProcessMacOSX::WaitForChildProcessToExit (void *pid_ptr) +//{ +// const lldb::pid_t pid = *((lldb::user_id_t *)pid_ptr); +// +// Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_THREAD); +// +// if (log) +// log->Printf ("ProcessMacOSX::%s (arg = %p) thread starting...", __FUNCTION__, pid_ptr); +// +// int status = -1; +// +// while (1) +// { +// if (log) +// log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0)...", pid, &status); +// +// lldb::pid_t return_pid = ::waitpid (pid, &status, 0); +// +// if (return_pid < 0) +// { +// if (log) +// log->Printf("%s ::waitpid (pid = %i, stat_loc = %p, options = 0) => errno = %i, status = 0x%8.8x", pid, &status, errno, status); +// break; +// } +// +// bool set_exit_status = false; +// if (WIFSTOPPED(status)) +// { +// if (log) +// log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0) => return_pid = %i, status = 0x%8.8x (STOPPED)", pid, &status, return_pid, status); +// } +// else if (WIFEXITED(status)) +// { +// set_exit_status = true; +// if (log) +// log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0) => return_pid = %i, status = 0x%8.8x (EXITED)", pid, &status, return_pid, status); +// } +// else if (WIFSIGNALED(status)) +// { +// set_exit_status = true; +// if (log) +// log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0) => return_pid = %i, status = 0x%8.8x (SIGNALED)", pid, &status, return_pid, status); +// } +// else +// { +// if (log) +// log->Printf("::waitpid (pid = %i, stat_loc = %p, options = 0) => return_pid = %i, status = 0x%8.8x", pid, &status, return_pid, status); +// } +// +// if (set_exit_status) +// { +// // Try and deliver the news to the process if it is still around +// TargetSP target_sp(TargetList::SharedList().FindTargetWithProcessID (return_pid)); +// if (target_sp.get()) +// { +// ProcessMacOSX *process = dynamic_cast<ProcessMacOSX*>(target_sp->GetProcess().get()); +// if (process) +// { +// process->SetExitStatus (status); +// if (log) +// log->Printf("Setting exit status of %i to 0x%8.8x", pid, status); +// process->Task().ShutDownExceptionThread(); +// } +// } +// // Break out of the loop and return. +// break; +// } +// } +// +// if (log) +// log->Printf ("ProcessMacOSX::%s (arg = %p) thread exiting...", __FUNCTION__, pid_ptr); +// +// return NULL; +//} +// + +const char * +ProcessMacOSX::GetPluginNameStatic() +{ + return "process.macosx"; +} + +const char * +ProcessMacOSX::GetPluginDescriptionStatic() +{ + return "Native MacOSX user process debugging plug-in."; +} + +void +ProcessMacOSX::Terminate() +{ + PluginManager::UnregisterPlugin (ProcessMacOSX::CreateInstance); +} + + +Process* +ProcessMacOSX::CreateInstance (Target &target, Listener &listener) +{ + ProcessMacOSX::Initialize(); + + return new ProcessMacOSX (target, listener); +} + +bool +ProcessMacOSX::CanDebug(Target &target) +{ + // For now we are just making sure the file exists for a given module + ModuleSP exe_module_sp(target.GetExecutableModule()); + if (exe_module_sp.get()) + return exe_module_sp->GetFileSpec().Exists(); + return false; +} + +//---------------------------------------------------------------------- +// ProcessMacOSX constructor +//---------------------------------------------------------------------- +ProcessMacOSX::ProcessMacOSX(Target& target, Listener &listener) : + Process (target, listener), + m_dynamic_loader_ap (), +// m_wait_thread (LLDB_INVALID_HOST_THREAD), + m_byte_order (eByteOrderHost), + m_stdio_ours (false), + m_child_stdin (-1), + m_child_stdout (-1), + m_child_stderr (-1), + m_task (this), + m_flags (eFlagsNone), + m_stdio_thread (LLDB_INVALID_HOST_THREAD), + m_stdio_mutex (Mutex::eMutexTypeRecursive), + m_stdout_data (), + m_exception_messages (), + m_exception_messages_mutex (Mutex::eMutexTypeRecursive), + m_arch_spec () +{ +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +ProcessMacOSX::~ProcessMacOSX() +{ +// m_mach_process.UnregisterNotificationCallbacks (this); + Clear(); +} + +//---------------------------------------------------------------------- +// PluginInterface +//---------------------------------------------------------------------- +const char * +ProcessMacOSX::GetPluginName() +{ + return "Process debugging plug-in for MacOSX"; +} + +const char * +ProcessMacOSX::GetShortPluginName() +{ + return GetPluginNameStatic(); +} + +uint32_t +ProcessMacOSX::GetPluginVersion() +{ + return 1; +} + +void +ProcessMacOSX::GetPluginCommandHelp (const char *command, Stream *strm) +{ + strm->Printf("The following arguments can be supplied to the 'log %s' command:\n", GetShortPluginName()); + strm->PutCString("\tverbose - enable verbose logging\n"); + strm->PutCString("\tprocess - enable process logging\n"); + strm->PutCString("\tthread - enable thread logging\n"); + strm->PutCString("\texceptions - enable exception logging\n"); + strm->PutCString("\tdynamic - enable DynamicLoader logging\n"); + strm->PutCString("\tmemory-calls - enable memory read and write call logging\n"); + strm->PutCString("\tmemory-data-short - log short memory read and write byte data\n"); + strm->PutCString("\tmemory-data-long - log all memory read and write byte data\n"); + strm->PutCString("\tmemory-protections - log memory protection calls\n"); + strm->PutCString("\tbreakpoints - log breakpoint calls\n"); + strm->PutCString("\twatchpoints - log watchpoint calls\n"); + strm->PutCString("\tevents - log event and event queue status\n"); + strm->PutCString("\tstep - log step related activity\n"); + strm->PutCString("\ttask - log task functions\n"); +} + +Error +ProcessMacOSX::ExecutePluginCommand (Args &command, Stream *strm) +{ + Error error; + error.SetErrorString("No plug-in command are currently supported."); + return error; +} + +Log * +ProcessMacOSX::EnablePluginLogging (Stream *strm, Args &command) +{ + return NULL; +} + +//---------------------------------------------------------------------- +// Process Control +//---------------------------------------------------------------------- +Error +ProcessMacOSX::DoLaunch +( + Module* module, + char const *argv[], + char const *envp[], + const char *stdin_path, + const char *stdout_path, + const char *stderr_path +) +{ +// ::LogSetBitMask (PD_LOG_DEFAULT); +// ::LogSetOptions (LLDB_LOG_OPTION_THREADSAFE | LLDB_LOG_OPTION_PREPEND_TIMESTAMP | LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD); +// ::LogSetLogFile ("/dev/stdout"); + + Error error; + ObjectFile * object_file = module->GetObjectFile(); + if (object_file) + { + ArchSpec arch_spec(module->GetArchitecture()); + + // Set our user ID to our process ID. + SetID (LaunchForDebug(argv[0], argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, eLaunchDefault, error)); + } + else + { + // Set our user ID to an invalid process ID. + SetID (LLDB_INVALID_PROCESS_ID); + error.SetErrorToGenericError (); + error.SetErrorStringWithFormat("Failed to get object file from '%s' for arch %s.\n", module->GetFileSpec().GetFilename().AsCString(), module->GetArchitecture().AsCString()); + } + + // Return the process ID we have + return error; +} + +Error +ProcessMacOSX::DoAttach (lldb::pid_t attach_pid) +{ + Error error; + + // Clear out and clean up from any current state + Clear(); + // HACK: require arch be set correctly at the target level until we can + // figure out a good way to determine the arch of what we are attaching to + m_arch_spec = m_target.GetArchitecture(); + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS); + if (attach_pid != LLDB_INVALID_PROCESS_ID) + { + SetPrivateState (eStateAttaching); + SetID(attach_pid); + // Let ourselves know we are going to be using SBS if the correct flag bit is set... +#if defined (__arm__) + if (IsSBProcess(pid)) + m_flags |= eFlagsUsingSBS; +#endif + + if (Task().GetTaskPortForProcessID(error) == TASK_NULL) + { + if (log) + log->Printf ("error attaching to pid %i: %s", GetID(), error.AsCString()); + + } + else + { + Task().StartExceptionThread(error); + + if (error.Success()) + { + errno = 0; + if (::ptrace (PT_ATTACHEXC, GetID(), 0, 0) == 0) + { + m_flags.Set (eFlagsAttached); + // Sleep a bit to let the exception get received and set our process status + // to stopped. + ::usleep(250000); + + if (log) + log->Printf ("successfully attached to pid %d", GetID()); + return error; + } + else + { + error.SetErrorToErrno(); + if (log) + log->Printf ("error: failed to attach to pid %d", GetID()); + } + } + else + { + if (log) + log->Printf ("error: failed to start exception thread for pid %d: %s", GetID(), error.AsCString()); + } + + } + } + SetID (LLDB_INVALID_PROCESS_ID); + if (error.Success()) + error.SetErrorStringWithFormat ("failed to attach to pid %d", attach_pid); + return error; +} + +Error +ProcessMacOSX::WillLaunchOrAttach () +{ + Error error; + // TODO: this is hardcoded for macosx right now. We need this to be more dynamic + m_dynamic_loader_ap.reset(DynamicLoader::FindPlugin(this, "dynamic-loader.macosx-dyld")); + + if (m_dynamic_loader_ap.get() == NULL) + error.SetErrorString("unable to find the dynamic loader named 'dynamic-loader.macosx-dyld'"); + + return error; +} + + +Error +ProcessMacOSX::WillLaunch (Module* module) +{ + return WillLaunchOrAttach (); +} + +void +ProcessMacOSX::DidLaunchOrAttach () +{ + if (GetID() == LLDB_INVALID_PROCESS_ID) + { + m_dynamic_loader_ap.reset(); + } + else + { + Module * exe_module = GetTarget().GetExecutableModule ().get(); + assert (exe_module); + + m_arch_spec = exe_module->GetArchitecture(); + assert (m_arch_spec.IsValid()); + + ObjectFile *exe_objfile = exe_module->GetObjectFile(); + assert (exe_objfile); + + m_byte_order = exe_objfile->GetByteOrder(); + assert (m_byte_order != eByteOrderInvalid); + // Install a signal handler so we can catch when our child process + // dies and set the exit status correctly. + + Host::StartMonitoringChildProcess (Process::SetProcessExitStatus, NULL, GetID(), false); + + if (m_arch_spec == ArchSpec("arm")) + { + // On ARM we want the actual target triple of the OS to get the + // most capable ARM slice for the process. Since this plug-in is + // only used for doing native debugging this will work. + m_target_triple = Host::GetTargetTriple(); + } + else + { + // We want the arch of the process, and the vendor and OS from the + // host OS. + StreamString triple; + + triple.Printf("%s-%s-%s", + m_arch_spec.AsCString(), + Host::GetVendorString().AsCString("apple"), + Host::GetOSString().AsCString("darwin")); + + m_target_triple.SetCString(triple.GetString().c_str()); + } + } +} + +void +ProcessMacOSX::DidLaunch () +{ + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::DidLaunch()"); + DidLaunchOrAttach (); + if (m_dynamic_loader_ap.get()) + m_dynamic_loader_ap->DidLaunch(); +} + +void +ProcessMacOSX::DidAttach () +{ + DidLaunchOrAttach (); + if (m_dynamic_loader_ap.get()) + m_dynamic_loader_ap->DidAttach(); +} + +Error +ProcessMacOSX::WillAttach (lldb::pid_t pid) +{ + return WillLaunchOrAttach (); +} + +Error +ProcessMacOSX::DoResume () +{ + Error error; + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::Resume()"); + const StateType state = m_private_state.GetValue(); + + if (CanResume(state)) + { + error = PrivateResume(LLDB_INVALID_THREAD_ID); + } + else if (state == eStateRunning) + { + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "Resume() - task 0x%x is running, ignoring...", Task().GetTaskPort()); + } + else + { + error.SetErrorStringWithFormat("task 0x%x can't continue, ignoring...", Task().GetTaskPort()); + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "Resume() - task 0x%x can't continue, ignoring...", Task().GetTaskPort()); + } + return error; +} + +size_t +ProcessMacOSX::GetSoftwareBreakpointTrapOpcode (BreakpointSite* bp_site) +{ + const uint8_t *trap_opcode = NULL; + uint32_t trap_opcode_size = 0; + + static const uint8_t g_arm_breakpoint_opcode[] = { 0xFE, 0xDE, 0xFF, 0xE7 }; + //static const uint8_t g_thumb_breakpooint_opcode[] = { 0xFE, 0xDE }; + static const uint8_t g_ppc_breakpoint_opcode[] = { 0x7F, 0xC0, 0x00, 0x08 }; + static const uint8_t g_i386_breakpoint_opcode[] = { 0xCC }; + + switch (m_arch_spec.GetCPUType()) + { + case CPU_TYPE_ARM: + // TODO: fill this in for ARM. We need to dig up the symbol for + // the address in the breakpoint locaiton and figure out if it is + // an ARM or Thumb breakpoint. + trap_opcode = g_arm_breakpoint_opcode; + trap_opcode_size = sizeof(g_arm_breakpoint_opcode); + break; + + case CPU_TYPE_POWERPC: + case CPU_TYPE_POWERPC64: + trap_opcode = g_ppc_breakpoint_opcode; + trap_opcode_size = sizeof(g_ppc_breakpoint_opcode); + break; + + case CPU_TYPE_I386: + case CPU_TYPE_X86_64: + trap_opcode = g_i386_breakpoint_opcode; + trap_opcode_size = sizeof(g_i386_breakpoint_opcode); + break; + + default: + assert(!"Unhandled architecture in ProcessMacOSX::GetSoftwareBreakpointTrapOpcode()"); + return 0; + } + + if (trap_opcode && trap_opcode_size) + { + if (bp_site->SetTrapOpcode(trap_opcode, trap_opcode_size)) + return trap_opcode_size; + } + return 0; +} +uint32_t +ProcessMacOSX::UpdateThreadListIfNeeded () +{ + // locker will keep a mutex locked until it goes out of scope + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_THREAD); + if (log && log->GetMask().IsSet(PD_LOG_VERBOSE)) + log->Printf ("ProcessMacOSX::%s (pid = %4.4x)", __FUNCTION__, GetID()); + + const uint32_t stop_id = GetStopID(); + if (m_thread_list.GetSize(false) == 0 || stop_id != m_thread_list.GetStopID()) + { + // Update the thread list's stop id immediately so we don't recurse into this function. + thread_array_t thread_list = NULL; + mach_msg_type_number_t thread_list_count = 0; + task_t task = Task().GetTaskPort(); + Error err(::task_threads (task, &thread_list, &thread_list_count), eErrorTypeMachKernel); + + if (log || err.Fail()) + err.PutToLog(log, "::task_threads ( task = 0x%4.4x, thread_list => %p, thread_list_count => %u )", task, thread_list, thread_list_count); + + if (err.GetError() == KERN_SUCCESS && thread_list_count > 0) + { + ThreadList curr_thread_list (this); + curr_thread_list.SetStopID(stop_id); + + size_t idx; + // Iterator through the current thread list and see which threads + // we already have in our list (keep them), which ones we don't + // (add them), and which ones are not around anymore (remove them). + for (idx = 0; idx < thread_list_count; ++idx) + { + const lldb::tid_t tid = thread_list[idx]; + ThreadSP thread_sp(GetThreadList().FindThreadByID (tid, false)); + if (thread_sp.get() == NULL) + thread_sp.reset (new ThreadMacOSX (*this, tid)); + curr_thread_list.AddThread(thread_sp); + } + + m_thread_list = curr_thread_list; + + // Free the vm memory given to us by ::task_threads() + vm_size_t thread_list_size = (vm_size_t) (thread_list_count * sizeof (lldb::tid_t)); + ::vm_deallocate (::mach_task_self(), + (vm_address_t)thread_list, + thread_list_size); + } + } + return GetThreadList().GetSize(false); +} + + +void +ProcessMacOSX::RefreshStateAfterStop () +{ + // If we are attaching, let our dynamic loader plug-in know so it can get + // an initial list of shared libraries. + + // We must be attaching if we don't already have a valid architecture + if (!m_arch_spec.IsValid()) + { + Module *exe_module = GetTarget().GetExecutableModule().get(); + if (exe_module) + m_arch_spec = exe_module->GetArchitecture(); + } + // Discover new threads: + UpdateThreadListIfNeeded (); + + // Let all threads recover from stopping and do any clean up based + // on the previous thread state (if any). + m_thread_list.RefreshStateAfterStop(); + + // Let each thread know of any exceptions + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_EXCEPTIONS); + task_t task = Task().GetTaskPort(); + size_t i; + for (i=0; i<m_exception_messages.size(); ++i) + { + // Let the thread list figure use the ProcessMacOSX to forward all exceptions + // on down to each thread. + if (m_exception_messages[i].state.task_port == task) + { + ThreadSP thread_sp(m_thread_list.FindThreadByID(m_exception_messages[i].state.thread_port)); + if (thread_sp.get()) + { + ThreadMacOSX *macosx_thread = (ThreadMacOSX *)thread_sp.get(); + macosx_thread->NotifyException (m_exception_messages[i].state); + } + } + if (log) + m_exception_messages[i].PutToLog(log); + } + +} + +Error +ProcessMacOSX::DoHalt () +{ + return Signal (SIGSTOP); +} + +Error +ProcessMacOSX::WillDetach () +{ + Error error; + const StateType state = m_private_state.GetValue(); + + if (IsRunning(state)) + { + error.SetErrorToGenericError(); + error.SetErrorString("Process must be stopped in order to detach."); + } + return error; +} + +Error +ProcessMacOSX::DoSIGSTOP (bool clear_all_breakpoints) +{ + Error error; + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS); + + if (log) + log->Printf ("ProcessMacOSX::DoSIGSTOP()"); + EventSP event_sp; + TimeValue timeout_time; + + StateType state = m_private_state.GetValue(); + + lldb::pid_t pid = GetID(); + + if (IsRunning(state)) + { + // If our process is running, we need to SIGSTOP it so we can detach. + if (log) + log->Printf ("ProcessMacOSX::DoDestroy() - kill (%i, SIGSTOP)", pid); + + // Send the SIGSTOP and wait a few seconds for it to stop + + // Pause the Private State Thread so it doesn't intercept the events we need to wait for. + PausePrivateStateThread(); + + m_thread_list.DiscardThreadPlans(); + + // First jettison all the current thread plans, since we want to make sure it + // really just stops. + + if (::kill (pid, SIGSTOP) == 0) + error.Clear(); + else + error.SetErrorToErrno(); + + if (error.Fail()) + error.PutToLog(log, "::kill (pid = %i, SIGSTOP)", pid); + + timeout_time = TimeValue::Now(); + timeout_time.OffsetWithSeconds(2); + + state = WaitForStateChangedEventsPrivate (&timeout_time, event_sp); + + // Resume the private state thread at this point. + ResumePrivateStateThread(); + + if (!StateIsStoppedState (state)) + { + if (log) + log->Printf("ProcessMacOSX::DoSIGSTOP() failed to stop after sending SIGSTOP"); + return error; + } + if (clear_all_breakpoints) + GetTarget().DisableAllBreakpoints(); + } + else if (!HasExited(state)) + { + if (clear_all_breakpoints) + GetTarget().DisableAllBreakpoints(); + +// const uint32_t num_threads = GetNumThreads(); +// for (uint32_t thread_idx = 0; thread_idx < num_threads; ++thread_idx) +// { +// Thread *thread = GetThreadAtIndex(thread_idx); +// thread->SetResumeState(eStateRunning); +// if (thread_idx == 0) +// thread->SetResumeSignal(SIGSTOP); +// } + + // Our process was stopped, so resume it and then SIGSTOP it so we can + // detach. + // But discard all the thread plans first, so we don't keep going because we + // are in mid-plan. + + // Pause the Private State Thread so it doesn't intercept the events we need to wait for. + PausePrivateStateThread(); + + m_thread_list.DiscardThreadPlans(); + + if (::kill (pid, SIGSTOP) == 0) + error.Clear(); + else + error.SetErrorToErrno(); + + if (log || error.Fail()) + error.PutToLog(log, "ProcessMacOSX::DoSIGSTOP() ::kill (pid = %i, SIGSTOP)", pid); + + error = PrivateResume(LLDB_INVALID_THREAD_ID); + + // Wait a few seconds for our process to resume + timeout_time = TimeValue::Now(); + timeout_time.OffsetWithSeconds(2); + state = WaitForStateChangedEventsPrivate (&timeout_time, event_sp); + + // Make sure the process resumed + if (StateIsStoppedState (state)) + { + if (log) + log->Printf ("ProcessMacOSX::DoSIGSTOP() couldn't resume process, state = %s", StateAsCString(state)); + error.SetErrorStringWithFormat("ProcessMacOSX::DoSIGSTOP() couldn't resume process, state = %s", StateAsCString(state)); + } + else + { + // Send the SIGSTOP and wait a few seconds for it to stop + timeout_time = TimeValue::Now(); + timeout_time.OffsetWithSeconds(2); + state = WaitForStateChangedEventsPrivate (&timeout_time, event_sp); + if (!StateIsStoppedState (state)) + { + if (log) + log->Printf("ProcessMacOSX::DoSIGSTOP() failed to stop after sending SIGSTOP"); + error.SetErrorString("ProcessMacOSX::DoSIGSTOP() failed to stop after sending SIGSTOP"); + } + } + // Resume the private state thread at this point. + ResumePrivateStateThread(); + } + + return error; +} + +Error +ProcessMacOSX::DoDestroy () +{ + Error error; + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS); + if (log) + log->Printf ("ProcessMacOSX::DoDestroy()"); + + error = DoSIGSTOP (true); + if (error.Success()) + { + StopSTDIOThread(true); + + if (log) + log->Printf ("ProcessMacOSX::DoDestroy() DoSIGSTOP succeeded"); + const StateType state = m_private_state.GetValue(); + // Scope for "locker" so we can reply to all of our exceptions (the SIGSTOP + // exception). + { + Mutex::Locker locker(m_exception_messages_mutex); + ReplyToAllExceptions(); + } + if (log) + log->Printf ("ProcessMacOSX::DoDestroy() replied to all exceptions"); + + // Shut down the exception thread and cleanup our exception remappings + Task().ShutDownExceptionThread(); + + if (log) + log->Printf ("ProcessMacOSX::DoDestroy() exception thread has been shutdown"); + + if (!HasExited(state)) + { + lldb::pid_t pid = GetID(); + + // Detach from our process while we are stopped. + errno = 0; + + // Detach from our process + ::ptrace (PT_KILL, pid, 0, 0); + + error.SetErrorToErrno(); + + if (log || error.Fail()) + error.PutToLog (log, "::ptrace (PT_KILL, %u, 0, 0)", pid); + + // Resume our task and let the SIGKILL do its thing. The thread named + // "ProcessMacOSX::WaitForChildProcessToExit(void*)" will catch the + // process exiting, so we don't need to set our state to exited in this + // function. + Task().Resume(); + } + + // NULL our task out as we have already retored all exception ports + Task().Clear(); + + // Clear out any notion of the process we once were + Clear(); + } + return error; +} + +ByteOrder +ProcessMacOSX::GetByteOrder () const +{ + return m_byte_order; +} + +//------------------------------------------------------------------ +// Process Queries +//------------------------------------------------------------------ + +bool +ProcessMacOSX::IsAlive () +{ + return MachTask::IsValid (Task().GetTaskPort()); +} + +lldb::addr_t +ProcessMacOSX::GetImageInfoAddress() +{ + return Task().GetDYLDAllImageInfosAddress(); +} + +DynamicLoader * +ProcessMacOSX::GetDynamicLoader() +{ + return m_dynamic_loader_ap.get(); +} + +//------------------------------------------------------------------ +// Process Memory +//------------------------------------------------------------------ + +size_t +ProcessMacOSX::DoReadMemory (lldb::addr_t addr, void *buf, size_t size, Error& error) +{ + return Task().ReadMemory(addr, buf, size, error); +} + +size_t +ProcessMacOSX::DoWriteMemory (lldb::addr_t addr, const void *buf, size_t size, Error& error) +{ + return Task().WriteMemory(addr, buf, size, error); +} + +lldb::addr_t +ProcessMacOSX::DoAllocateMemory (size_t size, uint32_t permissions, Error& error) +{ + return Task().AllocateMemory (size, permissions, error); +} + +Error +ProcessMacOSX::DoDeallocateMemory (lldb::addr_t ptr) +{ + return Task().DeallocateMemory (ptr); +} + +//------------------------------------------------------------------ +// Process STDIO +//------------------------------------------------------------------ + +size_t +ProcessMacOSX::GetSTDOUT (char *buf, size_t buf_size, Error &error) +{ + error.Clear(); + Mutex::Locker locker(m_stdio_mutex); + size_t bytes_available = m_stdout_data.size(); + if (bytes_available > 0) + { + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::%s (&%p[%u]) ...", __FUNCTION__, buf, buf_size); + 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(); + + //ResetEventBits(eBroadcastBitSTDOUT); + } + } + return bytes_available; +} + +size_t +ProcessMacOSX::GetSTDERR (char *buf, size_t buf_size, Error &error) +{ + error.Clear(); + return 0; +} + +size_t +ProcessMacOSX::PutSTDIN (const char *buf, size_t buf_size, Error &error) +{ + if (m_child_stdin == -1) + { + error.SetErrorString ("Invalid child stdin handle."); + } + else + { + ssize_t bytes_written = ::write (m_child_stdin, buf, buf_size); + if (bytes_written == -1) + error.SetErrorToErrno(); + else + { + error.Clear(); + return bytes_written; + } + } + return 0; +} + +Error +ProcessMacOSX::EnableBreakpoint (BreakpointSite *bp_site) +{ + Error error; + assert (bp_site != NULL); + const lldb::addr_t addr = bp_site->GetLoadAddress(); + const lldb::user_id_t site_id = bp_site->GetID(); + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_BREAKPOINTS); + if (log) + log->Printf ("ProcessMacOSX::EnableBreakpoint (site_id = %d) addr = 0x%8.8llx", site_id, (uint64_t)addr); + + if (bp_site->IsEnabled()) + { + if (log) + log->Printf ("ProcessMacOSX::EnableBreakpoint (site_id = %d) addr = 0x%8.8llx -- SUCCESS (already enabled)", site_id, (uint64_t)addr); + return error; + } + + if (bp_site->HardwarePreferred()) + { + ThreadMacOSX *thread = (ThreadMacOSX *)m_thread_list.FindThreadByID(bp_site->GetThreadID()).get(); + if (thread) + { + bp_site->SetHardwareIndex (thread->SetHardwareBreakpoint(bp_site)); + if (bp_site->IsHardware()) + { + bp_site->SetEnabled(true); + return error; + } + } + } + + // Just let lldb::Process::EnableSoftwareBreakpoint() handle everything... + return EnableSoftwareBreakpoint (bp_site); +} + +Error +ProcessMacOSX::DisableBreakpoint (BreakpointSite *bp_site) +{ + Error error; + assert (bp_site != NULL); + const lldb::addr_t addr = bp_site->GetLoadAddress(); + const lldb::user_id_t site_id = bp_site->GetID(); + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_BREAKPOINTS); + if (log) + log->Printf ("ProcessMacOSX::DisableBreakpoint (site_id = %d) addr = 0x%8.8llx", site_id, (uint64_t)addr); + + if (bp_site->IsHardware()) + { + ThreadMacOSX *thread = (ThreadMacOSX *)m_thread_list.FindThreadByID(bp_site->GetThreadID()).get(); + if (thread) + { + if (thread->ClearHardwareBreakpoint(bp_site)) + { + bp_site->SetEnabled(false); + if (log) + log->Printf ("ProcessMacOSX::DisableBreakpoint (site_id = %d) addr = 0x%8.8llx -- SUCCESS (hardware)", site_id, (uint64_t)addr); + return error; + } + } + error.SetErrorString("hardware breakpoints are no supported"); + return error; + } + + // Just let lldb::Process::EnableSoftwareBreakpoint() handle everything... + return DisableSoftwareBreakpoint (bp_site); +} + +Error +ProcessMacOSX::EnableWatchpoint (WatchpointLocation *wp) +{ + Error error; + if (wp) + { + lldb::user_id_t watchID = wp->GetID(); + lldb::addr_t addr = wp->GetLoadAddress(); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_WATCHPOINTS); + if (log) + log->Printf ("ProcessMacOSX::EnableWatchpoint(watchID = %d)", watchID); + if (wp->IsEnabled()) + { + if (log) + log->Printf("ProcessMacOSX::EnableWatchpoint(watchID = %d) addr = 0x%8.8llx: watchpoint already enabled.", watchID, (uint64_t)addr); + return error; + } + else + { + ThreadMacOSX *thread = (ThreadMacOSX *)m_thread_list.FindThreadByID(wp->GetThreadID()).get(); + if (thread) + { + wp->SetHardwareIndex (thread->SetHardwareWatchpoint (wp)); + if (wp->IsHardware ()) + { + wp->SetEnabled(true); + return error; + } + } + else + { + error.SetErrorString("Watchpoints currently only support thread specific watchpoints."); + } + } + } + return error; +} + +Error +ProcessMacOSX::DisableWatchpoint (WatchpointLocation *wp) +{ + Error error; + if (wp) + { + lldb::user_id_t watchID = wp->GetID(); + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_WATCHPOINTS); + + lldb::addr_t addr = wp->GetLoadAddress(); + if (log) + log->Printf ("ProcessMacOSX::DisableWatchpoint (watchID = %d) addr = 0x%8.8llx", watchID, (uint64_t)addr); + + if (wp->IsHardware()) + { + ThreadMacOSX *thread = (ThreadMacOSX *)m_thread_list.FindThreadByID(wp->GetThreadID()).get(); + if (thread) + { + if (thread->ClearHardwareWatchpoint (wp)) + { + wp->SetEnabled(false); + if (log) + log->Printf ("ProcessMacOSX::Disablewatchpoint (watchID = %d) addr = 0x%8.8llx (hardware) => success", watchID, (uint64_t)addr); + return error; + } + } + } + // TODO: clear software watchpoints if we implement them + error.SetErrorToGenericError(); + } + else + { + error.SetErrorString("Watchpoint location argument was NULL."); + } + return error; +} + + +static ProcessMacOSX::CreateArchCalback +ArchCallbackMap(const ArchSpec& arch_spec, ProcessMacOSX::CreateArchCalback callback, bool add ) +{ + // We must wrap the "g_arch_map" file static in a function to avoid + // any global constructors so we don't get a build verification error + typedef std::multimap<ArchSpec, ProcessMacOSX::CreateArchCalback> ArchToProtocolMap; + static ArchToProtocolMap g_arch_map; + + if (add) + { + g_arch_map.insert(std::make_pair(arch_spec, callback)); + return callback; + } + else + { + ArchToProtocolMap::const_iterator pos = g_arch_map.find(arch_spec); + if (pos != g_arch_map.end()) + { + return pos->second; + } + } + return NULL; +} + +void +ProcessMacOSX::AddArchCreateCallback(const ArchSpec& arch_spec, CreateArchCalback callback) +{ + ArchCallbackMap (arch_spec, callback, true); +} + +ProcessMacOSX::CreateArchCalback +ProcessMacOSX::GetArchCreateCallback() +{ + return ArchCallbackMap (m_arch_spec, NULL, false); +} + +void +ProcessMacOSX::Clear() +{ + // Clear any cached thread list while the pid and task are still valid + + Task().Clear(); + // Now clear out all member variables + CloseChildFileDescriptors(); + + m_flags = eFlagsNone; + m_thread_list.Clear(); + { + Mutex::Locker locker(m_exception_messages_mutex); + m_exception_messages.clear(); + } + +} + +bool +ProcessMacOSX::StartSTDIOThread() +{ + // If we created and own the child STDIO file handles, then we track the + // STDIO ourselves, else we let whomever owns these file handles track + // the IO themselves. + if (m_stdio_ours) + { + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::%s ( )", __FUNCTION__); + // Create the thread that watches for the child STDIO + m_stdio_thread = Host::ThreadCreate ("<lldb.process.process-macosx.stdio>", ProcessMacOSX::STDIOThread, this, NULL); + return m_stdio_thread != LLDB_INVALID_HOST_THREAD; + } + return false; +} + + +void +ProcessMacOSX::StopSTDIOThread(bool close_child_fds) +{ + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::%s ( )", __FUNCTION__); + // Stop the stdio thread + if (m_stdio_thread != LLDB_INVALID_HOST_THREAD) + { + Host::ThreadCancel (m_stdio_thread, NULL); + thread_result_t result = NULL; + Host::ThreadJoin (m_stdio_thread, &result, NULL); + if (close_child_fds) + CloseChildFileDescriptors(); + else + { + // We may have given up control of these file handles, so just + // set them to invalid values so the STDIO thread can exit when + // we interrupt it with pthread_cancel... + m_child_stdin = -1; + m_child_stdout = -1; + m_child_stderr = -1; + } + } +} + + +void * +ProcessMacOSX::STDIOThread(void *arg) +{ + ProcessMacOSX *proc = (ProcessMacOSX*) arg; + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS); + if (log) + log->Printf ("ProcessMacOSX::%s (arg = %p) thread starting...", __FUNCTION__, arg); + + // 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 avaiable. 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. + Error 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); + if (log) + log->Printf("select (nfds, &read_fds, NULL, NULL, NULL) => %d", num_set_fds); + + if (num_set_fds < 0) + { + int select_errno = errno; + if (log) + { + err.SetError (select_errno, eErrorTypePOSIX); + err.LogIfError(log, "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 + int 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; + if (log) + log->Printf("read (stdout_fd, ) => %d errno: %d (%s)", bytes_read, read_errno, strerror(read_errno)); + } + else if (bytes_read == 0) + { + // EOF... + if (log) + log->Printf("read (stdout_fd, ) => %d (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; + if (log) + log->Printf("read (stderr_fd, ) => %d errno: %d (%s)", bytes_read, read_errno, strerror(read_errno)); + } + else if (bytes_read == 0) + { + // EOF... + if (log) + log->Printf("read (stderr_fd, ) => %d (reached EOF for child STDERR)", bytes_read); + stderr_fd = -1; + } + else if (bytes_read > 0) + { + proc->AppendSTDOUT(s, bytes_read); + } + + } while (bytes_read > 0); + } + } + } + + if (log) + log->Printf("ProcessMacOSX::%s (%p): thread exiting...", __FUNCTION__, arg); + + return NULL; +} + +Error +ProcessMacOSX::DoSignal (int signal) +{ + Error error; + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS); + if (log) + log->Printf ("ProcessMacOSX::DoSignal (signal = %d)", signal); + if (::kill (GetID(), signal) != 0) + { + error.SetErrorToErrno(); + error.LogIfError(log, "ProcessMacOSX::DoSignal (%d)", signal); + } + return error; +} + + +Error +ProcessMacOSX::DoDetach() +{ + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS); + if (log) + log->Printf ("ProcessMacOSX::DoDetach()"); + + Error error (DoSIGSTOP (true)); + if (error.Success()) + { + CloseChildFileDescriptors (); + + // Scope for "locker" so we can reply to all of our exceptions (the SIGSTOP + // exception). + { + Mutex::Locker locker(m_exception_messages_mutex); + ReplyToAllExceptions(); + } + + // Shut down the exception thread and cleanup our exception remappings + Task().ShutDownExceptionThread(); + + lldb::pid_t pid = GetID(); + + // Detach from our process while we are stopped. + errno = 0; + + // Detach from our process + ::ptrace (PT_DETACH, pid, (caddr_t)1, 0); + + error.SetErrorToErrno(); + + if (log || error.Fail()) + error.PutToLog(log, "::ptrace (PT_DETACH, %u, (caddr_t)1, 0)", pid); + + // Resume our task + Task().Resume(); + + // NULL our task out as we have already retored all exception ports + Task().Clear(); + + // Clear out any notion of the process we once were + Clear(); + + SetPrivateState (eStateDetached); + } + return error; +} + + + +Error +ProcessMacOSX::ReplyToAllExceptions() +{ + Error error; + Mutex::Locker locker(m_exception_messages_mutex); + if (m_exception_messages.empty() == false) + { + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_EXCEPTIONS); + + 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) + { + int resume_signal = -1; + ThreadSP thread_sp = m_thread_list.FindThreadByID(pos->state.thread_port); + if (thread_sp.get()) + resume_signal = thread_sp->GetResumeSignal(); + if (log) + log->Printf ("Replying to exception %d for thread 0x%4.4x (resume_signal = %i).", std::distance(begin, pos), thread_sp->GetID(), resume_signal); + Error curr_error (pos->Reply (Task().GetTaskPort(), GetID(), resume_signal)); + + // Only report the first error + if (curr_error.Fail() && error.Success()) + error = curr_error; + + error.LogIfError(log, "Error replying to exception"); + } + + // Erase all exception message as we should have used and replied + // to them all already. + m_exception_messages.clear(); + } + return error; +} + + +Error +ProcessMacOSX::PrivateResume (lldb::tid_t tid) +{ + + Mutex::Locker locker(m_exception_messages_mutex); + Error error (ReplyToAllExceptions()); + + // 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). + //StateType process_state = m_thread_list.ProcessWillResume(this); + + // Set our state accordingly + SetPrivateState (eStateRunning); + + // Now resume our task. + error = Task().Resume(); + return error; +} + +// 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 +ProcessMacOSX::ExceptionMessageReceived (const MachException::Message& exceptionMessage) +{ + Mutex::Locker locker(m_exception_messages_mutex); + + if (m_exception_messages.empty()) + Task().Suspend(); + + ProcessMacOSXLog::LogIf (PD_LOG_EXCEPTIONS, "ProcessMacOSX::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); +} + + +//bool +//ProcessMacOSX::GetProcessInfo (struct kinfo_proc* proc_info) +//{ +// int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, GetID() }; +// size_t buf_size = sizeof(struct kinfo_proc); +// +// if (::sysctl (mib, (unsigned)(sizeof(mib)/sizeof(int)), &proc_info, &buf_size, NULL, 0) == 0) +// return buf_size > 0; +// +// return false; +//} +// +// +void +ProcessMacOSX::ExceptionMessageBundleComplete() +{ + // We have a complete bundle of exceptions for our child process. + Mutex::Locker locker(m_exception_messages_mutex); + ProcessMacOSXLog::LogIf (PD_LOG_EXCEPTIONS, "%s: %d exception messages.", __PRETTY_FUNCTION__, m_exception_messages.size()); + if (!m_exception_messages.empty()) + { + SetPrivateState (eStateStopped); + } + else + { + ProcessMacOSXLog::LogIf (PD_LOG_EXCEPTIONS, "%s empty exception messages bundle.", __PRETTY_FUNCTION__, m_exception_messages.size()); + } +} + +bool +ProcessMacOSX::ReleaseChildFileDescriptors ( int *stdin_fileno, int *stdout_fileno, int *stderr_fileno ) +{ + if (stdin_fileno) + *stdin_fileno = m_child_stdin; + if (stdout_fileno) + *stdout_fileno = m_child_stdout; + if (stderr_fileno) + *stderr_fileno = m_child_stderr; + // Stop the stdio thread if we have one, but don't have it close the child + // file descriptors since we are giving control of these descriptors to the + // caller + bool close_child_fds = false; + StopSTDIOThread(close_child_fds); + return true; +} + +void +ProcessMacOSX::AppendSTDOUT (const char* s, size_t len) +{ + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSX::%s (<%d> %s) ...", __FUNCTION__, len, s); + Mutex::Locker locker(m_stdio_mutex); + m_stdout_data.append(s, len); + + // FIXME: Make a real data object for this and put it out. + BroadcastEventIfUnique (eBroadcastBitSTDOUT); +} + +lldb::pid_t +ProcessMacOSX::LaunchForDebug +( + const char *path, + char const *argv[], + char const *envp[], + ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + PDLaunchType launch_type, + Error &launch_err) +{ + // Clear out and clean up from any current state + Clear(); + + m_arch_spec = arch_spec; + + if (launch_type == eLaunchDefault) + launch_type = eLaunchPosixSpawn; + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS); + if (log) + log->Printf ("%s( path = '%s', argv = %p, envp = %p, launch_type = %u )", __FUNCTION__, path, argv, envp, launch_type); + + // Fork a child process for debugging + SetPrivateState (eStateLaunching); + switch (launch_type) + { + case eLaunchForkExec: + SetID(ProcessMacOSX::ForkChildForPTraceDebugging(path, argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, this, launch_err)); + break; + + case eLaunchPosixSpawn: + SetID(ProcessMacOSX::PosixSpawnChildForPTraceDebugging(path, argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, this, launch_err)); + break; + +#if defined (__arm__) + + case eLaunchSpringBoard: + { + const char *app_ext = strstr(path, ".app"); + if (app_ext != NULL) + { + std::string app_bundle_path(path, app_ext + strlen(".app")); + return SBLaunchForDebug (app_bundle_path.c_str(), argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, launch_err); + } + } + break; + +#endif + + default: + // Invalid launch + launch_err.SetErrorToGenericError (); + return LLDB_INVALID_PROCESS_ID; + } + + lldb::pid_t pid = GetID(); + + if (pid == LLDB_INVALID_PROCESS_ID) + { + // 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.SetErrorToGenericError (); + } + else + { + // Make sure we can get our task port before going any further + Task().GetTaskPortForProcessID (launch_err); + + // If that goes well then kick off our exception thread + if (launch_err.Success()) + Task().StartExceptionThread(launch_err); + + if (launch_err.Success()) + { + //m_path = path; +// size_t i; +// if (argv) +// { +// char const *arg; +// for (i=0; (arg = argv[i]) != NULL; i++) +// m_args.push_back(arg); +// } + + StartSTDIOThread(); + + if (launch_type == eLaunchPosixSpawn) + { + + //SetState (eStateAttaching); + errno = 0; + if (::ptrace (PT_ATTACHEXC, pid, 0, 0) == 0) + launch_err.Clear(); + else + launch_err.SetErrorToErrno(); + + if (launch_err.Fail() || log) + launch_err.PutToLog(log, "::ptrace (PT_ATTACHEXC, pid = %i, 0, 0 )", pid); + + if (launch_err.Success()) + m_flags.Set (eFlagsAttached); + else + SetPrivateState (eStateExited); + } + else + { + launch_err.Clear(); + } + } + else + { + // We were able to launch the process, but not get its task port + // so now we need to make it sleep with da fishes. + SetID(LLDB_INVALID_PROCESS_ID); + ::ptrace (PT_KILL, pid, 0, 0 ); + ::kill (pid, SIGCONT); + pid = LLDB_INVALID_PROCESS_ID; + } + + } + return pid; +} + +lldb::pid_t +ProcessMacOSX::PosixSpawnChildForPTraceDebugging +( + const char *path, + char const *argv[], + char const *envp[], + ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + ProcessMacOSX* process, + Error &err +) +{ + posix_spawnattr_t attr; + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS); + + Error local_err; // Errors that don't affect the spawning. + if (log) + log->Printf ("%s ( path='%s', argv=%p, envp=%p, process )", __FUNCTION__, path, argv, envp); + err.SetError( ::posix_spawnattr_init (&attr), eErrorTypePOSIX); + if (err.Fail() || log) + err.PutToLog(log, "::posix_spawnattr_init ( &attr )"); + if (err.Fail()) + return LLDB_INVALID_PROCESS_ID; + + err.SetError( ::posix_spawnattr_setflags (&attr, POSIX_SPAWN_START_SUSPENDED), eErrorTypePOSIX); + if (err.Fail() || log) + err.PutToLog(log, "::posix_spawnattr_setflags ( &attr, POSIX_SPAWN_START_SUSPENDED )"); + if (err.Fail()) + return LLDB_INVALID_PROCESS_ID; + +#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... + cpu_type_t cpu = arch_spec.GetCPUType(); + if (cpu != 0 && cpu != CPU_TYPE_ANY && cpu != LLDB_INVALID_CPUTYPE) + { + size_t ocount = 0; + err.SetError( ::posix_spawnattr_setbinpref_np (&attr, 1, &cpu, &ocount), eErrorTypePOSIX); + if (err.Fail() || log) + err.PutToLog(log, "::posix_spawnattr_setbinpref_np ( &attr, 1, cpu_type = 0x%8.8x, count => %zu )", cpu, ocount); + + if (err.Fail() != 0 || ocount != 1) + return LLDB_INVALID_PROCESS_ID; + } + +#endif + + lldb_utility::PseudoTerminal pty; + + posix_spawn_file_actions_t file_actions; + err.SetError( ::posix_spawn_file_actions_init (&file_actions), eErrorTypePOSIX); + int file_actions_valid = err.Success(); + if (!file_actions_valid || log) + err.PutToLog(log, "::posix_spawn_file_actions_init ( &file_actions )"); + Error stdio_err; + lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; + if (file_actions_valid) + { + // If the user specified any STDIO files, then use those + if (stdin_path || stdout_path || stderr_path) + { + process->SetSTDIOIsOurs(false); + if (stderr_path != NULL && stderr_path[0]) + { + stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDERR_FILENO, stderr_path, O_RDWR, 0), eErrorTypePOSIX); + if (stdio_err.Fail() || log) + stdio_err.PutToLog(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDERR_FILENO, path = '%s', oflag = O_RDWR, mode = 0 )", stderr_path); + } + + if (stdin_path != NULL && stdin_path[0]) + { + stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDIN_FILENO, stdin_path, O_RDONLY, 0), eErrorTypePOSIX); + if (stdio_err.Fail() || log) + stdio_err.PutToLog(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDIN_FILENO, path = '%s', oflag = O_RDONLY, mode = 0 )", stdin_path); + } + + if (stdout_path != NULL && stdout_path[0]) + { + stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDOUT_FILENO, stdout_path, O_WRONLY, 0), eErrorTypePOSIX); + if (stdio_err.Fail() || log) + stdio_err.PutToLog(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDOUT_FILENO, path = '%s', oflag = O_WRONLY, mode = 0 )", stdout_path); + } + } + else + { + // The user did not specify any STDIO files, use a pseudo terminal. + // Callers can then access the file handles using the + // ProcessMacOSX::ReleaseChildFileDescriptors() function, otherwise + // this class will spawn a thread that tracks STDIO and buffers it. + process->SetSTDIOIsOurs(true); + char error_str[1024]; + if (pty.OpenFirstAvailableMaster(O_RDWR|O_NOCTTY, error_str, sizeof(error_str))) + { + const char* slave_name = pty.GetSlaveName(error_str, sizeof(error_str)); + if (slave_name == NULL) + slave_name = "/dev/null"; + stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDERR_FILENO, slave_name, O_RDWR|O_NOCTTY, 0), eErrorTypePOSIX); + if (stdio_err.Fail() || log) + stdio_err.PutToLog(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDERR_FILENO, path = '%s', oflag = O_RDWR|O_NOCTTY, mode = 0 )", slave_name); + + stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDIN_FILENO, slave_name, O_RDONLY|O_NOCTTY, 0), eErrorTypePOSIX); + if (stdio_err.Fail() || log) + stdio_err.PutToLog(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDIN_FILENO, path = '%s', oflag = O_RDONLY|O_NOCTTY, mode = 0 )", slave_name); + + stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDOUT_FILENO, slave_name, O_WRONLY|O_NOCTTY, 0), eErrorTypePOSIX); + if (stdio_err.Fail() || log) + stdio_err.PutToLog(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDOUT_FILENO, path = '%s', oflag = O_WRONLY|O_NOCTTY, mode = 0 )", slave_name); + } + else + { + if (error_str[0]) + stdio_err.SetErrorString(error_str); + else + stdio_err.SetErrorString("Unable to open master side of pty for inferior."); + } + + } + err.SetError( ::posix_spawnp (&pid, path, &file_actions, &attr, (char * const*)argv, (char * const*)envp), eErrorTypePOSIX); + if (err.Fail() || log) + err.PutToLog(log, "::posix_spawnp ( pid => %i, path = '%s', file_actions = %p, attr = %p, argv = %p, envp = %p )", pid, path, &file_actions, &attr, argv, envp); + + if (stdio_err.Success()) + { + // If we have a valid process and we created the STDIO file handles, + // then remember them on our process class so we can spawn a STDIO + // thread and close them when we are done with them. + if (process != NULL && process->STDIOIsOurs()) + { + int master_fd = pty.ReleaseMasterFileDescriptor (); + process->SetChildFileDescriptors (master_fd, master_fd, master_fd); + } + } + } + else + { + err.SetError( ::posix_spawnp (&pid, path, NULL, &attr, (char * const*)argv, (char * const*)envp), eErrorTypePOSIX); + if (err.Fail() || log) + err.PutToLog(log, "::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 = LLDB_INVALID_PROCESS_ID; + + if (file_actions_valid) + { + local_err.SetError( ::posix_spawn_file_actions_destroy (&file_actions), eErrorTypePOSIX); + if (local_err.Fail() || log) + local_err.PutToLog(log, "::posix_spawn_file_actions_destroy ( &file_actions )"); + } + + return pid; +} + +lldb::pid_t +ProcessMacOSX::ForkChildForPTraceDebugging +( + const char *path, + char const *argv[], + char const *envp[], + ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + ProcessMacOSX* process, + Error &launch_err +) +{ + lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; + + if (stdin_path || stdout_path || stderr_path) + { + assert(!"TODO: ForkChildForPTraceDebugging doesn't currently support fork/exec with user file handles..."); + } + else + { + + // Use a fork that ties the child process's stdin/out/err to a pseudo + // terminal so we can read it in our ProcessMacOSX::STDIOThread + // as unbuffered io. + lldb_utility::PseudoTerminal pty; + char error_str[1024]; + pid = pty.Fork(error_str, sizeof(error_str)); + + if (pid < 0) + { + launch_err.SetErrorString (error_str); + //-------------------------------------------------------------- + // Error 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. + ::setgid (getgid ()); + + // 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, (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.ReleaseMasterFileDescriptor (); + process->SetChildFileDescriptors (master_fd, master_fd, master_fd); + } + } + } + return pid; +} + +#if defined (__arm__) + +lldb::pid_t +ProcessMacOSX::SBLaunchForDebug +( + const char *path, + char const *argv[], + char const *envp[], + ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + Error &launch_err +) +{ + // Clear out and clean up from any current state + Clear(); + + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s( '%s', argv)", __FUNCTION__, path); + + // Fork a child process for debugging + SetState(eStateLaunching); + m_pid = ProcessMacOSX::SBLaunchForDebug(path, argv, envp, this, launch_err); + if (m_pid != 0) + { + m_flags |= eFlagsUsingSBS; + //m_path = path; +// size_t i; +// char const *arg; +// for (i=0; (arg = argv[i]) != NULL; i++) +// m_args.push_back(arg); + Task().StartExceptionThread(); + StartSTDIOThread(); + SetState (eStateAttaching); + int err = ptrace (PT_ATTACHEXC, m_pid, 0, 0); + if (err == 0) + { + m_flags |= eFlagsAttached; + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "successfully attached to pid %d", m_pid); + } + else + { + SetState (eStateExited); + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "error: failed to attach to pid %d", m_pid); + } + } + return m_pid; +} + +#include <servers/bootstrap.h> +#include "CFBundle.h" +#include "CFData.h" +#include "CFString.h" + +lldb::pid_t +ProcessMacOSX::SBLaunchForDebug +( + const char *app_bundle_path, + char const *argv[], + char const *envp[], + ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + ProcessMacOSX* process, + Error &launch_err +) +{ + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s( '%s', argv, %p)", __FUNCTION__, app_bundle_path, process); + CFAllocatorRef alloc = kCFAllocatorDefault; + if (argv[0] == NULL) + return LLDB_INVALID_PROCESS_ID; + + 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 stdout_cf_path; + CFString stderr_cf_path; + PseudoTerminal pty; + + if (stdin_path || stdout_path || stderr_path) + { + process->SetSTDIOIsOurs(false); + if (stdout_path) + stdout_cf_path.SetFileSystemRepresentation (stdout_path); + if (stderr_path) + stderr_cf_path.SetFileSystemRepresentation (stderr_path); + } + else + { + process->SetSTDIOIsOurs(true); + PseudoTerminal::Error pty_err = pty.OpenFirstAvailableMaster(O_RDWR|O_NOCTTY); + if (pty_err == PseudoTerminal::success) + { + const char* slave_name = pty.SlaveName(); + ProcessMacOSXLog::LogIf (PD_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); + stdout_cf_path.SetFileSystemRepresentation (slave_name); + stderr_cf_path.(stdout_cf_path); + } + } + } + + if (stdout_cf_path.get() == NULL) + stdout_cf_path.SetFileSystemRepresentation ("/dev/null"); + if (stderr_cf_path.get() == NULL) + stderr_cf_path.SetFileSystemRepresentation ("/dev/null"); + + CFBundle bundle(app_bundle_path); + CFStringRef bundleIDCFStr = bundle.GetIdentifier(); + std::string bundleID; + if (CFString::UTF8(bundleIDCFStr, bundleID) == NULL) + { + struct stat app_bundle_stat; + if (::stat (app_bundle_path, &app_bundle_stat) < 0) + { + launch_err.SetError(errno, eErrorTypePOSIX); + launch_err.SetErrorStringWithFormat("%s: \"%s\".\n", launch_err.AsString(), app_bundle_path); + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() error: %s", __FUNCTION__, launch_err.AsCString()); + } + else + { + launch_err.SetError(-1, eErrorTypeGeneric); + launch_err.SetErrorStringWithFormat("Failed to extract CFBundleIdentifier from %s.\n", app_bundle_path); + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() error: failed to extract CFBundleIdentifier from '%s'", __FUNCTION__, app_bundle_path); + } + return LLDB_INVALID_PROCESS_ID; + } + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() extracted CFBundleIdentifier: %s", __FUNCTION__, bundleID.c_str()); + + + CFData argv_data(NULL); + + if (launch_argv.get()) + { + if (argv_data.Serialize(launch_argv.get(), kCFPropertyListBinaryFormat_v1_0) == NULL) + { + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() error: failed to serialize launch arg array...", __FUNCTION__); + return LLDB_INVALID_PROCESS_ID; + } + } + + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() serialized launch arg array", __FUNCTION__); + + // Find SpringBoard + SBSApplicationLaunchError sbs_error = 0; + sbs_error = SBSLaunchApplication ( bundleIDCFStr, + (CFURLRef)NULL, // openURL + launch_argv.get(), + launch_envp.get(), // CFDictionaryRef environment + stdout_cf_path.get(), + stderr_cf_path.get(), + SBSApplicationLaunchWaitForDebugger | SBSApplicationLaunchUnlockDevice); + + + launch_err.SetError(sbs_error, eErrorTypeSpringBoard); + + 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; + + lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; + 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; + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() polling Springboard for pid for %s...", __FUNCTION__, bundleID.c_str()); + pid_found = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &pid); + } + + if (pid_found) + { + // If we have a valid process and we created the STDIO file handles, + // then remember them on our process class so we can spawn a STDIO + // thread and close them when we are done with them. + if (process != NULL && process->STDIOIsOurs()) + { + // 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); + } + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() => pid = %4.4x", __FUNCTION__, pid); + } + else + { + LogError("failed to lookup the process ID for CFBundleIdentifier %s.", bundleID.c_str()); + } + return pid; + } + + LogError("unable to launch the application with CFBundleIdentifier '%s' sbs_error = %u", bundleID.c_str(), sbs_error); + return LLDB_INVALID_PROCESS_ID; +} + +#endif // #if defined (__arm__) + + +#include "MachThreadContext_x86_64.h" +#include "MachThreadContext_i386.h" +#include "MachThreadContext_arm.h" + +void +ProcessMacOSX::Initialize() +{ + static bool g_initialized = false; + + if (g_initialized == false) + { + g_initialized = true; + + MachThreadContext_x86_64::Initialize(); + MachThreadContext_i386::Initialize(); + MachThreadContext_arm::Initialize(); + PluginManager::RegisterPlugin (GetPluginNameStatic(), + GetPluginDescriptionStatic(), + CreateInstance); + + Log::Callbacks log_callbacks = { + ProcessMacOSXLog::DisableLog, + ProcessMacOSXLog::EnableLog, + ProcessMacOSXLog::ListLogCategories + }; + + Log::RegisterLogChannel (ProcessMacOSX::GetPluginNameStatic(), log_callbacks); + + + } +} + + + diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.h b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.h new file mode 100644 index 00000000000..8388d4e46fe --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSX.h @@ -0,0 +1,490 @@ +//===-- ProcessMacOSX.h -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_MacOSXProcess_H_ +#define liblldb_MacOSXProcess_H_ + +// C Includes + +// C++ Includes +#include <list> + +// Other libraries and framework includes +#include "lldb/Core/ArchSpec.h" +#include "lldb/Core/ThreadSafeValue.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Thread.h" + +// Project includes +#include "MachTask.h" +#include "MachException.h" + +typedef enum PDLaunch +{ + eLaunchDefault = 0, + eLaunchPosixSpawn, + eLaunchForkExec, +#if defined (__arm__) + eLaunchSpringBoard, +#endif +} PDLaunchType; + + + +class ThreadMacOSX; +class MachThreadContext; + +class ProcessMacOSX : + public lldb_private::Process +{ +public: + friend class ThreadMacOSX; + friend class MachTask; + + typedef MachThreadContext* (*CreateArchCalback) (const lldb_private::ArchSpec &arch_spec, ThreadMacOSX &thread); + + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + static Process* + CreateInstance (lldb_private::Target& target, lldb_private::Listener &listener); + + static void + Initialize(); + + static void + Terminate(); + + static const char * + GetPluginNameStatic(); + + static const char * + GetPluginDescriptionStatic(); + + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + ProcessMacOSX(lldb_private::Target& target, lldb_private::Listener &listener); + + virtual + ~ProcessMacOSX(); + + //------------------------------------------------------------------ + // Check if a given Process + //------------------------------------------------------------------ + virtual bool + CanDebug (lldb_private::Target &target); + + //------------------------------------------------------------------ + // Creating a new process, or attaching to an existing one + //------------------------------------------------------------------ + virtual lldb_private::Error + WillLaunch (lldb_private::Module* module); + + virtual lldb_private::Error + DoLaunch (lldb_private::Module* module, + char const *argv[], // Can be NULL + char const *envp[], // Can be NULL + const char *stdin_path, // Can be NULL + const char *stdout_path, // Can be NULL + const char *stderr_path); // Can be NULL + + virtual void + DidLaunch (); + + virtual lldb_private::Error + WillAttach (lldb::pid_t pid); + + virtual lldb_private::Error + DoAttach (lldb::pid_t pid); + + virtual void + DidAttach (); + + //------------------------------------------------------------------ + // PluginInterface protocol + //------------------------------------------------------------------ + virtual const char * + GetPluginName(); + + virtual const char * + GetShortPluginName(); + + virtual uint32_t + GetPluginVersion(); + + virtual void + GetPluginCommandHelp (const char *command, lldb_private::Stream *strm); + + virtual lldb_private::Error + ExecutePluginCommand (lldb_private::Args &command, lldb_private::Stream *strm); + + virtual lldb_private::Log * + EnablePluginLogging (lldb_private::Stream *strm, lldb_private::Args &command); + + //------------------------------------------------------------------ + // Process Control + //------------------------------------------------------------------ + virtual lldb_private::Error + DoResume (); + + virtual lldb_private::Error + DoHalt (); + + virtual lldb_private::Error + WillDetach (); + + virtual lldb_private::Error + DoDetach (); + + virtual lldb_private::Error + DoSignal (int signal); + + virtual lldb_private::Error + DoDestroy (); + + virtual void + RefreshStateAfterStop(); + + //------------------------------------------------------------------ + // Process Queries + //------------------------------------------------------------------ + virtual bool + IsAlive (); + + virtual lldb::addr_t + GetImageInfoAddress(); + + //------------------------------------------------------------------ + // Process Memory + //------------------------------------------------------------------ + virtual size_t + DoReadMemory (lldb::addr_t addr, void *buf, size_t size, lldb_private::Error &error); + + virtual size_t + DoWriteMemory (lldb::addr_t addr, const void *buf, size_t size, lldb_private::Error &error); + + virtual lldb::addr_t + DoAllocateMemory (size_t size, uint32_t permissions, lldb_private::Error &error); + + virtual lldb_private::Error + DoDeallocateMemory (lldb::addr_t ptr); + + //------------------------------------------------------------------ + // Process STDIO + //------------------------------------------------------------------ + virtual size_t + GetSTDOUT (char *buf, size_t buf_size, lldb_private::Error &error); + + virtual size_t + GetSTDERR (char *buf, size_t buf_size, lldb_private::Error &error); + + virtual size_t + PutSTDIN (const char *buf, size_t buf_size, lldb_private::Error &error); + + //---------------------------------------------------------------------- + // Process Breakpoints + //---------------------------------------------------------------------- + virtual size_t + GetSoftwareBreakpointTrapOpcode (lldb_private::BreakpointSite *bp_site); + + //---------------------------------------------------------------------- + // Process Breakpoints + //---------------------------------------------------------------------- + virtual lldb_private::Error + EnableBreakpoint (lldb_private::BreakpointSite *bp_site); + + virtual lldb_private::Error + DisableBreakpoint (lldb_private::BreakpointSite *bp_site); + + //---------------------------------------------------------------------- + // Process Watchpoints + //---------------------------------------------------------------------- + virtual lldb_private::Error + EnableWatchpoint (lldb_private::WatchpointLocation *wp_loc); + + virtual lldb_private::Error + DisableWatchpoint (lldb_private::WatchpointLocation *wp_loc); + + virtual lldb::ByteOrder + GetByteOrder () const; + + virtual lldb_private::DynamicLoader * + GetDynamicLoader (); + + static void + AddArchCreateCallback(const lldb_private::ArchSpec& arch_spec, + ProcessMacOSX::CreateArchCalback callback); + +protected: + + bool m_stdio_ours; // True if we created and own the child STDIO file handles, false if they were supplied to us and owned by someone else + int m_child_stdin; + int m_child_stdout; + int m_child_stderr; + MachTask m_task; // The mach task for this process + lldb_private::Flags m_flags; // Process specific flags (see eFlags enums) + lldb::thread_t m_stdio_thread; // Thread ID for the thread that watches for child process stdio + lldb_private::Mutex m_stdio_mutex; // Multithreaded protection for stdio + std::string m_stdout_data; + MachException::Message::collection m_exception_messages; // A collection of exception messages caught when listening to the exception port + lldb_private::Mutex m_exception_messages_mutex; // Multithreaded protection for m_exception_messages + lldb_private::ArchSpec m_arch_spec; + std::auto_ptr<lldb_private::DynamicLoader> m_dynamic_loader_ap; +// lldb::thread_t m_wait_thread; + lldb::ByteOrder m_byte_order; + + //---------------------------------------------------------------------- + // Child process control + //---------------------------------------------------------------------- + lldb::pid_t + LaunchForDebug (const char *path, + char const *argv[], + char const *envp[], + lldb_private::ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + PDLaunchType launch_type, + lldb_private::Error &launch_err); + + static lldb::pid_t + ForkChildForPTraceDebugging (const char *path, + char const *argv[], + char const *envp[], + lldb_private::ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + ProcessMacOSX* process, + lldb_private::Error &launch_err); + + static lldb::pid_t + PosixSpawnChildForPTraceDebugging (const char *path, + char const *argv[], + char const *envp[], + lldb_private::ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + ProcessMacOSX* process, + lldb_private::Error &launch_err); + +#if defined (__arm__) + lldb::pid_t + SBLaunchForDebug (const char *path, + char const *argv[], + char const *envp[], + lldb_private::ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + lldb_private::Error &launch_err); + + static lldb::pid_t + SBLaunchForDebug (const char *path, + char const *argv[], + char const *envp[], + lldb_private::ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + ProcessMacOSX* process, + lldb_private::Error &launch_err); +#endif + + //---------------------------------------------------------------------- + // Exception thread functions + //---------------------------------------------------------------------- + bool + StartSTDIOThread (); + + void + StopSTDIOThread (bool close_child_fds); + + static void * + STDIOThread (void *arg); + + void + ExceptionMessageReceived (const MachException::Message& exceptionMessage); + + void + ExceptionMessageBundleComplete (); + + //---------------------------------------------------------------------- + // Accessors + //---------------------------------------------------------------------- + bool + ProcessIDIsValid ( ) const; + + MachTask& + Task() { return m_task; } + + const MachTask& + Task() const { return m_task; } + + bool + IsRunning ( lldb::StateType state ) + { + return state == lldb::eStateRunning || IsStepping(state); + } + + bool + IsStepping ( lldb::StateType state) + { + return state == lldb::eStateStepping; + } + bool + CanResume ( lldb::StateType state) + { + return state == lldb::eStateStopped; + } + + bool + HasExited (lldb::StateType state) + { + return state == lldb::eStateExited; + } + + void + SetChildFileDescriptors (int stdin_fileno, int stdout_fileno, int stderr_fileno) + { + m_child_stdin = stdin_fileno; + m_child_stdout = stdout_fileno; + m_child_stderr = stderr_fileno; + } + + int + GetStdinFileDescriptor () const + { + return m_child_stdin; + } + + int + GetStdoutFileDescriptor () const + { + return m_child_stdout; + } + int + GetStderrFileDescriptor () const + { + return m_child_stderr; + } + bool + ReleaseChildFileDescriptors ( int *stdin_fileno, int *stdout_fileno, int *stderr_fileno ); + + void + AppendSTDOUT (const char* s, size_t len); + + void + CloseChildFileDescriptors () + { + if (m_child_stdin >= 0) + { + ::close (m_child_stdin); + m_child_stdin = -1; + } + if (m_child_stdout >= 0) + { + ::close (m_child_stdout); + m_child_stdout = -1; + } + if (m_child_stderr >= 0) + { + ::close (m_child_stderr); + m_child_stderr = -1; + } + } + + bool + ProcessUsingSpringBoard() const + { + return m_flags.IsSet(eFlagsUsingSBS); + } + + lldb_private::ArchSpec& + GetArchSpec() + { + return m_arch_spec; + } + const lldb_private::ArchSpec& + GetArchSpec() const + { + return m_arch_spec; + } + + CreateArchCalback + GetArchCreateCallback(); + + enum + { + eFlagsNone = 0, + eFlagsAttached = (1 << 0), + eFlagsUsingSBS = (1 << 1) + }; + + void + Clear ( ); + + lldb_private::Error + ReplyToAllExceptions(); + + lldb_private::Error + PrivateResume ( lldb::tid_t tid); + + lldb_private::Flags & + GetFlags () + { + return m_flags; + } + + const lldb_private::Flags & + GetFlags () const + { + return m_flags; + } + + bool + STDIOIsOurs() const + { + return m_stdio_ours; + } + + void + SetSTDIOIsOurs(bool b) + { + m_stdio_ours = b; + } + + uint32_t + UpdateThreadListIfNeeded (); + +private: + + void + DidLaunchOrAttach (); + + lldb_private::Error + DoSIGSTOP (bool clear_all_breakpoints); + + lldb_private::Error + WillLaunchOrAttach (); + +// static void * +// WaitForChildProcessToExit (void *pid_ptr); +// +// + //------------------------------------------------------------------ + // For ProcessMacOSX only + //------------------------------------------------------------------ + DISALLOW_COPY_AND_ASSIGN (ProcessMacOSX); + +}; + +#endif // liblldb_MacOSXProcess_H_ diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSXLog.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSXLog.cpp new file mode 100644 index 00000000000..4bfd1ff466e --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSXLog.cpp @@ -0,0 +1,124 @@ +//===-- ProcessMacOSXLog.cpp ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ProcessMacOSXLog.h" + +#include "lldb/Core/Args.h" +#include "lldb/Core/StreamFile.h" + +#include "ProcessMacOSX.h" + +using namespace lldb; +using namespace lldb_private; + + +static Log* g_log = NULL; // Leak for now as auto_ptr was being cleaned up + // by global constructors before other threads + // were done with it. +Log * +ProcessMacOSXLog::GetLogIfAllCategoriesSet (uint32_t mask) +{ + Log *log = g_log; + if (log && mask) + { + uint32_t log_mask = log->GetMask().GetAllFlagBits(); + if ((log_mask & mask) != mask) + return NULL; + } + return log; +} + +void +ProcessMacOSXLog::DisableLog () +{ + if (g_log) + { + delete g_log; + g_log = NULL; + } +} + +Log * +ProcessMacOSXLog::EnableLog (StreamSP &log_stream_sp, uint32_t log_options, Args &args, Stream *feedback_strm) +{ + DisableLog (); + g_log = new Log (log_stream_sp); + if (g_log) + { + uint32_t flag_bits = 0; + bool got_unknown_category = false; + const size_t argc = args.GetArgumentCount(); + for (size_t i=0; i<argc; ++i) + { + const char *arg = args.GetArgumentAtIndex(i); + + if (::strcasecmp (arg, "all") == 0 ) flag_bits |= PD_LOG_ALL; + else if (::strcasestr (arg, "break") == arg ) flag_bits |= PD_LOG_BREAKPOINTS; + else if (::strcasecmp (arg, "default") == 0 ) flag_bits |= PD_LOG_DEFAULT; + else if (::strcasestr (arg, "exc") == arg ) flag_bits |= PD_LOG_EXCEPTIONS; + else if (::strcasecmp (arg, "memory") == 0 ) flag_bits |= PD_LOG_MEMORY; + else if (::strcasecmp (arg, "data-short") == 0 ) flag_bits |= PD_LOG_MEMORY_DATA_SHORT; + else if (::strcasecmp (arg, "data-long") == 0 ) flag_bits |= PD_LOG_MEMORY_DATA_LONG; + else if (::strcasecmp (arg, "protections")== 0 ) flag_bits |= PD_LOG_MEMORY_PROTECTIONS; + else if (::strcasecmp (arg, "process") == 0 ) flag_bits |= PD_LOG_PROCESS; + else if (::strcasecmp (arg, "step") == 0 ) flag_bits |= PD_LOG_STEP; + else if (::strcasecmp (arg, "task") == 0 ) flag_bits |= PD_LOG_TASK; + else if (::strcasecmp (arg, "thread") == 0 ) flag_bits |= PD_LOG_THREAD; + else if (::strcasecmp (arg, "verbose") == 0 ) flag_bits |= PD_LOG_VERBOSE; + else if (::strcasestr (arg, "watch") == arg ) flag_bits |= PD_LOG_WATCHPOINTS; + else + { + feedback_strm->Printf("error: unrecognized log category '%s'\n", arg); + if (got_unknown_category == false) + { + got_unknown_category = true; + ListLogCategories (feedback_strm); + } + } + } + if (flag_bits == 0) + flag_bits = PD_LOG_DEFAULT; + g_log->GetMask().SetAllFlagBits(flag_bits); + g_log->GetOptions().SetAllFlagBits(log_options); + } + return g_log; +} + +void +ProcessMacOSXLog::ListLogCategories (Stream *strm) +{ + strm->Printf("Logging categories for '%s':\n" + "\tall - turn on all available logging categories\n" + "\tbreak - log breakpoints\n" + "\tdefault - enable the default set of logging categories for liblldb\n" + "\tmemory - log memory reads and writes\n" + "\tdata-short - log memory bytes for memory reads and writes for short transactions only\n" + "\tdata-long - log memory bytes for memory reads and writes for all transactions\n" + "\tprocess - log process events and activities\n" + "\tprotections - log memory protections\n" + "\ttask - log mach task calls\n" + "\tthread - log thread events and activities\n" + "\tstep - log step related activities\n" + "\tverbose - enable verbose loggging\n" + "\twatch - log watchpoint related activities\n", ProcessMacOSX::GetPluginNameStatic()); +} + + +void +ProcessMacOSXLog::LogIf (uint32_t mask, const char *format, ...) +{ + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (mask); + if (log) + { + va_list args; + va_start (args, format); + log->VAPrintf (format, args); + va_end (args); + } +} diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSXLog.h b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSXLog.h new file mode 100644 index 00000000000..cb2a4e8ee98 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSXLog.h @@ -0,0 +1,62 @@ +//===-- ProcessMacOSXLog.h --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_ProcessMacOSXLog_h_ +#define liblldb_ProcessMacOSXLog_h_ + +// C Includes +// C++ Includes +// Other libraries and framework includes + +// Project includes +#include "lldb/Core/Log.h" + +#define PD_LOG_VERBOSE (1u << 0) +#define PD_LOG_PROCESS (1u << 1) +#define PD_LOG_THREAD (1u << 2) +#define PD_LOG_EXCEPTIONS (1u << 3) +#define PD_LOG_MEMORY (1u << 4) // Log memory reads/writes calls +#define PD_LOG_MEMORY_DATA_SHORT (1u << 5) // Log short memory reads/writes bytes +#define PD_LOG_MEMORY_DATA_LONG (1u << 6) // Log all memory reads/writes bytes +#define PD_LOG_MEMORY_PROTECTIONS (1u << 7) // Log memory protection changes +#define PD_LOG_BREAKPOINTS (1u << 8) +#define PD_LOG_WATCHPOINTS (1u << 9) +#define PD_LOG_STEP (1u << 10) +#define PD_LOG_TASK (1u << 11) +#define PD_LOG_ALL (UINT32_MAX) +#define PD_LOG_DEFAULT (PD_LOG_PROCESS |\ + PD_LOG_TASK |\ + PD_LOG_THREAD |\ + PD_LOG_EXCEPTIONS |\ + PD_LOG_MEMORY |\ + PD_LOG_MEMORY_DATA_SHORT |\ + PD_LOG_BREAKPOINTS |\ + PD_LOG_WATCHPOINTS |\ + PD_LOG_STEP ) + +class ProcessMacOSXLog +{ +public: + static lldb_private::Log * + GetLogIfAllCategoriesSet(uint32_t mask = 0); + + static void + DisableLog (); + + static lldb_private::Log * + EnableLog (lldb::StreamSP &log_stream_sp, uint32_t log_options, lldb_private::Args &args, lldb_private::Stream *feedback_strm); + + static void + ListLogCategories (lldb_private::Stream *strm); + + static void + LogIf (uint32_t mask, const char *format, ...); +}; + +#endif // liblldb_ProcessMacOSXLog_h_ diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSXRemote.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSXRemote.cpp new file mode 100644 index 00000000000..835d003a9b2 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSXRemote.cpp @@ -0,0 +1,1819 @@ +//===-- ProcessMacOSXRemote.cpp ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +//---------------------------------------------------------------------- +// +// ProcessMacOSXRemote.cpp +// liblldb +// +// Created by Greg Clayton on 4/21/09. +// +// +//---------------------------------------------------------------------- + +// C Includes +#include <errno.h> + +// C++ Includes +//#include <algorithm> +//#include <map> + +// Other libraries and framework includes + +// Project includes +#include "ProcessMacOSXRemote.h" +#include "ProcessMacOSXLog.h" +#include "ThreadMacOSX.h" + +Process* +ProcessMacOSXRemote::CreateInstance (Target &target) +{ + return new ProcessMacOSXRemote (target); +} + +bool +ProcessMacOSXRemote::CanDebug(Target &target) +{ + // For now we are just making sure the file exists for a given module + ModuleSP exe_module_sp(target.GetExecutableModule()); + if (exe_module_sp.get()) + return exe_module_sp->GetFileSpec().Exists(); + return false; +} + +//---------------------------------------------------------------------- +// ProcessMacOSXRemote constructor +//---------------------------------------------------------------------- +ProcessMacOSXRemote::ProcessMacOSXRemote(Target& target) : + Process (target), + m_flags (0), + m_arch_spec (), + m_dynamic_loader_ap (), + m_byte_order(eByteOrderInvalid) +{ +} + +//---------------------------------------------------------------------- +// Destructor +//---------------------------------------------------------------------- +ProcessMacOSXRemote::~DCProcessMacOSXRemote() +{ + Clear(); +} + +//---------------------------------------------------------------------- +// Process Control +//---------------------------------------------------------------------- +lldb::pid_t +ProcessMacOSXRemote::DoLaunch +( + Module* module, + char const *argv[], + char const *envp[], + const char *stdin_path, + const char *stdout_path, + const char *stderr_path +) +{ +// ::LogSetBitMask (PD_LOG_DEFAULT); +// ::LogSetOptions (LLDB_LOG_OPTION_THREADSAFE | LLDB_LOG_OPTION_PREPEND_TIMESTAMP | LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD); +// ::LogSetLogFile ("/dev/stdout"); + + ObjectFile * object_file = module->GetObjectFile(); + if (object_file) + { + char exec_file_path[PATH_MAX]; + FileSpec* file_spec_ptr = object_file->GetFileSpec(); + if (file_spec_ptr) + file_spec_ptr->GetPath(exec_file_path, sizeof(exec_file_path)); + + ArchSpec arch_spec(module->GetArchitecture()); + + switch (arch_spec.GetCPUType()) + { + + } + // Set our user ID to our process ID. + SetID(LaunchForDebug(exec_file_path, argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, eLaunchDefault, GetError())); + } + else + { + // Set our user ID to an invalid process ID. + SetID(LLDB_INVALID_PROCESS_ID); + GetError().SetErrorToGenericError (); + GetError().SetErrorStringWithFormat ("Failed to get object file from '%s' for arch %s.\n", module->GetFileSpec().GetFilename().AsCString(), module->GetArchitecture().AsCString()); + } + + // Return the process ID we have + return GetID(); +} + +lldb::pid_t +ProcessMacOSXRemote::DoAttach (lldb::pid_t attach_pid) +{ + // Set our user ID to the attached process ID (which can be invalid if + // the attach fails + lldb::pid_t pid = AttachForDebug(attach_pid); + SetID(pid); + +// if (pid != LLDB_INVALID_PROCESS_ID) +// { +// // Wait for a process stopped event, but don't consume it +// if (WaitForEvents(LLDB_EVENT_STOPPED, NULL, 30)) +// { +// } +// } +// + // Return the process ID we have + return pid; +} + + +void +ProcessMacOSXRemote::DidLaunch () +{ + if (GetID() == LLDB_INVALID_PROCESS_ID) + { + m_dynamic_loader_ap.reset(); + } + else + { + Module * exe_module = GetTarget().GetExecutableModule ().get(); + assert(exe_module); + ObjectFile *exe_objfile = exe_module->GetObjectFile(); + assert(exe_objfile); + m_byte_order = exe_objfile->GetByteOrder(); + assert(m_byte_order != eByteOrderInvalid); + // Install a signal handler so we can catch when our child process + // dies and set the exit status correctly. + m_wait_thread = Host::ThreadCreate (ProcessMacOSXRemote::WaitForChildProcessToExit, &m_uid, &m_error); + if (m_wait_thread != LLDB_INVALID_HOST_THREAD) + { + // Don't need to get the return value of this thread, so just let + // it clean up after itself when it dies. + Host::ThreadDetach (m_wait_thread, NULL); + } + m_dynamic_loader_ap.reset(DynamicLoader::FindPlugin(this, "macosx-dyld")); + } + +} + +void +ProcessMacOSXRemote::DidAttach () +{ + DidLaunch (); + m_need_to_run_did_attach = true; +} + +bool +ProcessMacOSXRemote::DoResume () +{ + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSXRemote::Resume()"); + State state = GetState(); + + if (CanResume(state)) + { + PrivateResume(LLDB_INVALID_THREAD_ID); + } + else if (state == eStateRunning) + { + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "Resume() - task 0x%x is running, ignoring...", m_task.TaskPort()); + GetError().Clear(); + + } + else + { + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "Resume() - task 0x%x can't continue, ignoring...", m_task.TaskPort()); + GetError().SetError(UINT_MAX, Error::Generic); + } + + return GetError().Success(); +} + +size_t +ProcessMacOSXRemote::GetSoftwareBreakpointTrapOpcode (BreakpointSite *bp_site) +{ + ModuleSP exe_module_sp(GetTarget().GetExecutableModule()); + if (exe_module_sp.get()) + { + const ArchSpec &exe_arch = exe_module_sp->GetArchitecture(); + const uint8_t *trap_opcode = NULL; + uint32_t trap_opcode_size = 0; + + static const uint8_t g_arm_breakpoint_opcode[] = { 0xFE, 0xDE, 0xFF, 0xE7 }; + //static const uint8_t g_thumb_breakpooint_opcode[] = { 0xFE, 0xDE }; + static const uint8_t g_ppc_breakpoint_opcode[] = { 0x7F, 0xC0, 0x00, 0x08 }; + static const uint8_t g_i386_breakpoint_opcode[] = { 0xCC }; + + switch (exe_arch.GetCPUType()) + { + case CPU_TYPE_ARM: + // TODO: fill this in for ARM. We need to dig up the symbol for + // the address in the breakpoint locaiton and figure out if it is + // an ARM or Thumb breakpoint. + trap_opcode = g_arm_breakpoint_opcode; + trap_opcode_size = sizeof(g_arm_breakpoint_opcode); + break; + + case CPU_TYPE_POWERPC: + case CPU_TYPE_POWERPC64: + trap_opcode = g_ppc_breakpoint_opcode; + trap_opcode_size = sizeof(g_ppc_breakpoint_opcode); + break; + + case CPU_TYPE_I386: + case CPU_TYPE_X86_64: + trap_opcode = g_i386_breakpoint_opcode; + trap_opcode_size = sizeof(g_i386_breakpoint_opcode); + break; + + default: + assert(!"Unhandled architecture in ProcessMacOSXRemote::GetSoftwareBreakpointTrapOpcode()"); + return 0; + } + + if (trap_opcode && trap_opcode_size) + { + if (bp_loc->SetTrapOpcode(trap_opcode, trap_opcode_size)) + return trap_opcode_size; + } + } + // No executable yet, so we can't tell what the breakpoint opcode will be. + return 0; +} +uint32_t +ProcessMacOSXRemote::UpdateThreadListIfNeeded () +{ + // locker will keep a mutex locked until it goes out of scope + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_THREAD); + if (log && log->GetMask().IsSet(PD_LOG_VERBOSE)) + log->Printf ("ProcessMacOSXRemote::%s (pid = %4.4x)", __FUNCTION__, GetID()); + + const uint32_t stop_id = GetStopID(); + if (m_thread_list.GetSize() == 0 || stop_id != m_thread_list.GetID()) + { + m_thread_list.SetID (stop_id); + thread_array_t thread_list = NULL; + mach_msg_type_number_t thread_list_count = 0; + task_t task = Task().TaskPort(); + Error err(::task_threads (task, &thread_list, &thread_list_count), Error::MachKernel); + + if (log || err.Fail()) + err.Log(log, "::task_threads ( task = 0x%4.4x, thread_list => %p, thread_list_count => %u )", task, thread_list, thread_list_count); + + if (err.GetError() == KERN_SUCCESS && thread_list_count > 0) + { + ThreadList curr_thread_list; + + size_t idx; + // Iterator through the current thread list and see which threads + // we already have in our list (keep them), which ones we don't + // (add them), and which ones are not around anymore (remove them). + for (idx = 0; idx < thread_list_count; ++idx) + { + const lldb::tid_t tid = thread_list[idx]; + ThreadSP thread_sp(m_thread_list.FindThreadByID (tid)); + if (thread_sp.get() == NULL) + thread_sp.reset (new ThreadMacOSX (this, tid)); + curr_thread_list.AddThread(thread_sp); + } + + m_thread_list = curr_thread_list; + + // Free the vm memory given to us by ::task_threads() + vm_size_t thread_list_size = (vm_size_t) (thread_list_count * sizeof (lldb::tid_t)); + ::vm_deallocate (::mach_task_self(), + (vm_address_t)thread_list, + thread_list_size); + } + } + return m_thread_list.GetSize(); +} + +bool +ProcessMacOSXRemote::ShouldStop () +{ + // If we are attaching, let our dynamic loader plug-in know so it can get + // an initial list of shared libraries. + if (m_need_to_run_did_attach && m_dynamic_loader_ap.get()) + { + m_need_to_run_did_attach = false; + m_dynamic_loader_ap->DidAttach(); + } + + // We must be attaching if we don't already have a valid architecture + if (!m_arch_spec.IsValid()) + { + Module *exe_module = GetTarget().GetExecutableModule().get(); + if (exe_module) + m_arch_spec = exe_module->GetArchitecture(); + } + // Let all threads recover from stopping and do any clean up based + // on the previous thread state (if any). + UpdateThreadListIfNeeded (); + + if (m_thread_list.ShouldStop()) + { + // Let each thread know of any exceptions + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_EXCEPTIONS); + task_t task = m_task.TaskPort(); + size_t i; + for (i=0; i<m_exception_messages.size(); ++i) + { + // Let the thread list figure use the ProcessMacOSXRemote to forward all exceptions + // on down to each thread. + if (m_exception_messages[i].state.task_port == task) + { + ThreadSP thread_sp(m_thread_list.FindThreadByID(m_exception_messages[i].state.thread_port)); + if (thread_sp.get()) + { + ThreadMacOSX *macosx_thread = (ThreadMacOSX *)thread_sp.get(); + macosx_thread->NotifyException (m_exception_messages[i].state); + } + } + if (log) + m_exception_messages[i].Log(log); + } + return true; + } + return false; +} + +bool +ProcessMacOSXRemote::DoHalt () +{ + return Kill (SIGINT); +} + +bool +ProcessMacOSXRemote::WillDetach () +{ + State state = GetState(); + + if (IsRunning(state)) + { + m_error.SetErrorToGenericError(); + m_error.SetErrorString("Process must be stopped in order to detach."); + return false; + } + return true; +} + +bool +ProcessMacOSXRemote::DoDetach () +{ + m_use_public_queue = false; + bool success = Detach(); + m_use_public_queue = true; + if (success) + SetState (eStateDetached); + return success; +} + +bool +ProcessMacOSXRemote::DoKill (int signal) +{ + return Kill (signal); +} + + +//------------------------------------------------------------------ +// Thread Queries +//------------------------------------------------------------------ + +Thread * +ProcessMacOSXRemote::GetCurrentThread () +{ + return m_thread_list.GetCurrentThread().get(); +} + +ByteOrder +ProcessMacOSXRemote::GetByteOrder () const +{ + return m_byte_order; +} + + + +//------------------------------------------------------------------ +// Process Queries +//------------------------------------------------------------------ + +bool +ProcessMacOSXRemote::IsAlive () +{ + return MachTask::IsValid (Task().TaskPort()); +} + +bool +ProcessMacOSXRemote::IsRunning () +{ + return LLDB_STATE_IS_RUNNING(GetState()); +} + +lldb::addr_t +ProcessMacOSXRemote::GetImageInfoAddress() +{ + return Task().GetDYLDAllImageInfosAddress(); +} + +DynamicLoader * +ProcessMacOSXRemote::GetDynamicLoader() +{ + return m_dynamic_loader_ap.get(); +} + +//------------------------------------------------------------------ +// Process Memory +//------------------------------------------------------------------ + +size_t +ProcessMacOSXRemote::DoReadMemory (lldb::addr_t addr, void *buf, size_t size) +{ + return Task().ReadMemory(addr, buf, size); +} + +size_t +ProcessMacOSXRemote::DoWriteMemory (lldb::addr_t addr, const void *buf, size_t size) +{ + return Task().WriteMemory(addr, buf, size); +} + +//------------------------------------------------------------------ +// Process STDIO +//------------------------------------------------------------------ + +size_t +ProcessMacOSXRemote::GetSTDOUT (char *buf, size_t buf_size) +{ + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSXRemote::%s (&%p[%u]) ...", __FUNCTION__, buf, buf_size); + 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; +} + +size_t +ProcessMacOSXRemote::GetSTDERR (char *buf, size_t buf_size) +{ + return 0; +} + +bool +ProcessMacOSXRemote::EnableBreakpoint (BreakpointLocation *bp) +{ + assert (bp != NULL); + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_BREAKPOINTS); + lldb::user_id_t breakID = bp->GetID(); + lldb::addr_t addr = bp->GetAddress(); + if (bp->IsEnabled()) + { + if (log) + log->Printf("ProcessMacOSXRemote::EnableBreakpoint ( breakID = %d ) breakpoint already enabled.", breakID); + return true; + } + else + { + if (bp->HardwarePreferred()) + { + ThreadMacOSX *thread = (ThreadMacOSX *)m_thread_list.FindThreadByID(bp->GetThreadID()).get(); + if (thread) + { + bp->SetHardwareIndex (thread->EnableHardwareBreakpoint(bp)); + if (bp->IsHardware()) + { + bp->SetEnabled(true); + return true; + } + } + } + + const size_t break_op_size = GetSoftwareBreakpointTrapOpcode (bp); + assert (break_op_size > 0); + const uint8_t * const break_op = bp->GetTrapOpcodeBytes(); + + if (break_op_size > 0) + { + // Save the original opcode by reading it + if (m_task.ReadMemory(addr, bp->GetSavedOpcodeBytes(), break_op_size) == break_op_size) + { + // Write a software breakpoint in place of the original opcode + if (m_task.WriteMemory(addr, break_op, break_op_size) == break_op_size) + { + uint8_t verify_break_op[4]; + if (m_task.ReadMemory(addr, verify_break_op, break_op_size) == break_op_size) + { + if (memcmp(break_op, verify_break_op, break_op_size) == 0) + { + bp->SetEnabled(true); + if (log) + log->Printf("ProcessMacOSXRemote::EnableBreakpoint ( breakID = %d ) SUCCESS.", breakID, (uint64_t)addr); + return true; + } + else + { + GetError().SetErrorString("Failed to verify the breakpoint trap in memory."); + } + } + else + { + GetError().SetErrorString("Unable to read memory to verify breakpoint trap."); + } + } + else + { + GetError().SetErrorString("Unable to write breakpoint trap to memory."); + } + } + else + { + GetError().SetErrorString("Unable to read memory at breakpoint address."); + } + } + } + + if (log) + { + const char *err_string = GetError().AsCString(); + log->Printf ("ProcessMacOSXRemote::EnableBreakpoint ( breakID = %d ) error: %s", + breakID, err_string ? err_string : "NULL"); + } + GetError().SetErrorToGenericError(); + return false; +} + +bool +ProcessMacOSXRemote::DisableBreakpoint (BreakpointLocation *bp) +{ + assert (bp != NULL); + lldb::addr_t addr = bp->GetAddress(); + lldb::user_id_t breakID = bp->GetID(); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_BREAKPOINTS); + if (log) + log->Printf ("ProcessMacOSXRemote::DisableBreakpoint (breakID = %d) addr = 0x%8.8llx", breakID, (uint64_t)addr); + + if (bp->IsHardware()) + { + ThreadMacOSX *thread = (ThreadMacOSX *)m_thread_list.FindThreadByID(bp->GetThreadID()).get(); + if (thread) + { + if (thread->DisableHardwareBreakpoint(bp)) + { + bp->SetEnabled(false); + if (log) + log->Printf ("ProcessMacOSXRemote::DisableBreakpoint (breakID = %d) (hardware) => success", breakID); + return true; + } + } + return false; + } + + const size_t break_op_size = bp->GetByteSize(); + assert (break_op_size > 0); + const uint8_t * const break_op = bp->GetTrapOpcodeBytes(); + if (break_op_size > 0) + { + // Clear a software breakoint instruction + uint8_t curr_break_op[break_op_size]; + bool break_op_found = false; + + // Read the breakpoint opcode + if (m_task.ReadMemory(addr, curr_break_op, break_op_size) == break_op_size) + { + bool verify = false; + if (bp->IsEnabled()) + { + // Make sure we have the 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, bp->GetSavedOpcodeBytes(), break_op_size) == break_op_size) + { + verify = true; + } + else + { + GetError().SetErrorString("Memory write failed when restoring original opcode."); + } + } + else + { + GetError().SetErrorString("Original breakpoint trap is no longer in memory."); + // Set verify to true and so we can check if the original opcode has already been restored + verify = true; + } + } + else + { + if (log) + log->Printf ("ProcessMacOSXRemote::DisableBreakpoint (breakID = %d) is already disabled", breakID); + // 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, verify_opcode, break_op_size) == break_op_size) + { + // compare the memory we just read with the original opcode + if (memcmp(bp->GetSavedOpcodeBytes(), verify_opcode, break_op_size) == 0) + { + // SUCCESS + bp->SetEnabled(false); + if (log) + log->Printf ("ProcessMacOSXRemote::DisableBreakpoint (breakID = %d) SUCCESS", breakID); + return true; + } + else + { + if (break_op_found) + GetError().SetErrorString("Failed to restore original opcode."); + } + } + else + { + GetError().SetErrorString("Failed to read memory to verify that breakpoint trap was restored."); + } + } + } + else + { + GetError().SetErrorString("Unable to read memory that should contain the breakpoint trap."); + } + } + + GetError().SetErrorToGenericError(); + return false; +} + +bool +ProcessMacOSXRemote::EnableWatchpoint (WatchpointLocation *wp) +{ + if (wp) + { + lldb::user_id_t watchID = wp->GetID(); + lldb::addr_t addr = wp->GetAddress(); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_WATCHPOINTS); + if (log) + log->Printf ("ProcessMacOSXRemote::EnableWatchpoint(watchID = %d)", watchID); + if (wp->IsEnabled()) + { + if (log) + log->Printf("ProcessMacOSXRemote::EnableWatchpoint(watchID = %d) addr = 0x%8.8llx: watchpoint already enabled.", watchID, (uint64_t)addr); + return true; + } + else + { + ThreadMacOSX *thread = (ThreadMacOSX *)m_thread_list.FindThreadByID(wp->GetThreadID()).get(); + if (thread) + { + wp->SetHardwareIndex (thread->EnableHardwareWatchpoint (wp)); + if (wp->IsHardware ()) + { + wp->SetEnabled(true); + return true; + } + } + else + { + GetError().SetErrorString("Watchpoints currently only support thread specific watchpoints."); + } + } + } + return false; +} + +bool +ProcessMacOSXRemote::DisableWatchpoint (WatchpointLocation *wp) +{ + if (wp) + { + lldb::user_id_t watchID = wp->GetID(); + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_WATCHPOINTS); + + lldb::addr_t addr = wp->GetAddress(); + if (log) + log->Printf ("ProcessMacOSXRemote::DisableWatchpoint (watchID = %d) addr = 0x%8.8llx", watchID, (uint64_t)addr); + + if (wp->IsHardware()) + { + ThreadMacOSX *thread = (ThreadMacOSX *)m_thread_list.FindThreadByID(wp->GetThreadID()).get(); + if (thread) + { + if (thread->DisableHardwareWatchpoint (wp)) + { + wp->SetEnabled(false); + if (log) + log->Printf ("ProcessMacOSXRemote::Disablewatchpoint (watchID = %d) addr = 0x%8.8llx (hardware) => success", watchID, (uint64_t)addr); + return true; + } + } + } + // TODO: clear software watchpoints if we implement them + } + else + { + GetError().SetErrorString("Watchpoint location argument was NULL."); + } + GetError().SetErrorToGenericError(); + return false; +} + + +static ProcessMacOSXRemote::CreateArchCalback +ArchDCScriptInterpreter::TypeMap(const ArchSpec& arch_spec, ProcessMacOSXRemote::CreateArchCalback callback, bool add ) +{ + // We must wrap the "g_arch_map" file static in a function to avoid + // any global constructors so we don't get a build verification error + typedef std::multimap<ArchSpec, ProcessMacOSXRemote::CreateArchCalback> ArchToProtocolMap; + static ArchToProtocolMap g_arch_map; + + if (add) + { + g_arch_map.insert(std::make_pair(arch_spec, callback)); + return callback; + } + else + { + ArchToProtocolMap::const_iterator pos = g_arch_map.find(arch_spec); + if (pos != g_arch_map.end()) + { + return pos->second; + } + } + return NULL; +} + +void +ProcessMacOSXRemote::AddArchCreateDCScriptInterpreter::Type(const ArchSpec& arch_spec, CreateArchCalback callback) +{ + ArchDCScriptInterpreter::TypeMap (arch_spec, callback, true); +} + +ProcessMacOSXRemote::CreateArchCalback +ProcessMacOSXRemote::GetArchCreateDCScriptInterpreter::Type() +{ + return ArchDCScriptInterpreter::TypeMap (m_arch_spec, NULL, false); +} + +void +ProcessMacOSXRemote::Clear() +{ + // Clear any cached thread list while the pid and task are still valid + + m_task.Clear(); + // Now clear out all member variables + CloseChildFileDescriptors(); + + m_flags = eFlagsNone; + m_thread_list.Clear(); + { + Mutex::Locker locker(m_exception_messages_mutex); + m_exception_messages.clear(); + } + +} + + +bool +ProcessMacOSXRemote::Kill (int signal) +{ + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS); + if (log) + log->Printf ("ProcessMacOSXRemote::Kill(signal = %d)", signal); + State state = GetState(); + + if (IsRunning(state)) + { + if (::kill (GetID(), signal) == 0) + { + GetError().Clear(); + } + else + { + GetError().SetErrorToErrno(); + GetError().LogIfError(log, "ProcessMacOSXRemote::Kill(%d)", signal); + } + } + else + { + if (log) + log->Printf ("ProcessMacOSXRemote::Kill(signal = %d) pid %u (task = 0x%4.4x) was't running, ignoring...", signal, GetID(), m_task.TaskPort()); + GetError().Clear(); + } + return GetError().Success(); + +} + + +bool +ProcessMacOSXRemote::Detach() +{ + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSXRemote::Detach()"); + + State state = GetState(); + + if (!IsRunning(state)) + { + // Resume our process + PrivateResume(LLDB_INVALID_THREAD_ID); + + // We have resumed and now we wait for that event to get posted + Event event; + if (WaitForPrivateEvents(LLDB_EVENT_RUNNING, &event, 2) == false) + return false; + + + // We need to be stopped in order to be able to detach, so we need + // to send ourselves a SIGSTOP + if (Kill(SIGSTOP)) + { + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_PROCESS); + + lldb::pid_t pid = GetID(); + // Wait for our process stop event to get posted + if (WaitForPrivateEvents(LLDB_EVENT_STOPPED, &event, 2) == false) + { + GetError().Log(log, "::kill (pid = %u, SIGSTOP)", pid); + return false; + } + + // Shut down the exception thread and cleanup our exception remappings + m_task.ShutDownExceptionThread(); + + // Detach from our process while we are stopped. + errno = 0; + + // Detach from our process + ::ptrace (PT_DETACH, pid, (caddr_t)1, 0); + + GetError().SetErrorToErrno(); + + if (log || GetError().Fail()) + GetError().Log(log, "::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 + m_task.Clear(); + + // Clear out any notion of the process we once were + Clear(); + } + } + else + { + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSXRemote::Detach() error: process must be stopped (SIGINT the process first)."); + } + return false; +} + + + +void +ProcessMacOSXRemote::ReplyToAllExceptions() +{ + Mutex::Locker locker(m_exception_messages_mutex); + if (m_exception_messages.empty() == false) + { + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_EXCEPTIONS); + + 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) + { + if (log) + log->Printf ("Replying to exception %d...", std::distance(begin, pos)); + int resume_signal = 0; + ThreadSP thread_sp = m_thread_list.FindThreadByID(pos->state.thread_port); + if (thread_sp.get()) + resume_signal = thread_sp->GetResumeSignal(); + GetError() = pos->Reply (Task().TaskPort(), GetID(), resume_signal); + GetError().LogIfError(log, "Error replying to exception"); + } + + // Erase all exception message as we should have used and replied + // to them all already. + m_exception_messages.clear(); + } +} +void +ProcessMacOSXRemote::PrivateResume (lldb::tid_t tid) +{ + Mutex::Locker locker(m_exception_messages_mutex); + ReplyToAllExceptions(); + + // 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). + //StateType process_state = m_thread_list.ProcessWillResume(this); + + // Set our state accordingly + SetState(eStateRunning); + + // Now resume our task. + GetError() = m_task.Resume(); + +} + +// 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 +ProcessMacOSXRemote::ExceptionMessageReceived (const MachException::Message& exceptionMessage) +{ + Mutex::Locker locker(m_exception_messages_mutex); + + if (m_exception_messages.empty()) + m_task.Suspend(); + + ProcessMacOSXLog::LogIf (PD_LOG_EXCEPTIONS, "ProcessMacOSXRemote::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); +} + + +//bool +//ProcessMacOSXRemote::GetProcessInfo (struct kinfo_proc* proc_info) +//{ +// int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, GetID() }; +// size_t buf_size = sizeof(struct kinfo_proc); +// +// if (::sysctl (mib, (unsigned)(sizeof(mib)/sizeof(int)), &proc_info, &buf_size, NULL, 0) == 0) +// return buf_size > 0; +// +// return false; +//} +// +// +void +ProcessMacOSXRemote::ExceptionMessageBundleComplete() +{ + // We have a complete bundle of exceptions for our child process. + Mutex::Locker locker(m_exception_messages_mutex); + ProcessMacOSXLog::LogIf (PD_LOG_EXCEPTIONS, "%s: %d exception messages.", __PRETTY_FUNCTION__, m_exception_messages.size()); + if (!m_exception_messages.empty()) + { + SetState (eStateStopped); + } + else + { + ProcessMacOSXLog::LogIf (PD_LOG_EXCEPTIONS, "%s empty exception messages bundle.", __PRETTY_FUNCTION__, m_exception_messages.size()); + } +} + +bool +ProcessMacOSXRemote::ReleaseChildFileDescriptors ( int *stdin_fileno, int *stdout_fileno, int *stderr_fileno ) +{ + if (stdin_fileno) + *stdin_fileno = m_child_stdin; + if (stdout_fileno) + *stdout_fileno = m_child_stdout; + if (stderr_fileno) + *stderr_fileno = m_child_stderr; + // Stop the stdio thread if we have one, but don't have it close the child + // file descriptors since we are giving control of these descriptors to the + // caller + bool close_child_fds = false; + StopSTDIOThread(close_child_fds); + return true; +} + +void +ProcessMacOSXRemote::AppendSTDOUT (char* s, size_t len) +{ + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "ProcessMacOSXRemote::%s (<%d> %s) ...", __FUNCTION__, len, s); + Mutex::Locker locker(m_stdio_mutex); + m_stdout_data.append(s, len); + AppendEvent (LLDB_EVENT_STDIO); +} + +void * +ProcessMacOSXRemote::STDIOThread(void *arg) +{ + ProcessMacOSXRemote *proc = (ProcessMacOSXRemote*) arg; + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS); + if (log) + log->Printf ("ProcessMacOSXRemote::%s (arg = %p) thread starting...", __FUNCTION__, arg); + + // 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 avaiable. 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. + Error 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); + if (log) + log->Printf("select (nfds, &read_fds, NULL, NULL, NULL) => %d", num_set_fds); + + if (num_set_fds < 0) + { + int select_errno = errno; + if (log) + { + err.SetError (select_errno, Error::POSIX); + err.LogIfError(log, "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 + int 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; + if (log) + log->Printf("read (stdout_fd, ) => %d errno: %d (%s)", bytes_read, read_errno, strerror(read_errno)); + } + else if (bytes_read == 0) + { + // EOF... + if (log) + log->Printf("read (stdout_fd, ) => %d (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; + if (log) + log->Printf("read (stderr_fd, ) => %d errno: %d (%s)", bytes_read, read_errno, strerror(read_errno)); + } + else if (bytes_read == 0) + { + // EOF... + if (log) + log->Printf("read (stderr_fd, ) => %d (reached EOF for child STDERR)", bytes_read); + stderr_fd = -1; + } + else if (bytes_read > 0) + { + proc->AppendSTDOUT(s, bytes_read); + } + + } while (bytes_read > 0); + } + } + } + + if (log) + log->Printf("ProcessMacOSXRemote::%s (%p): thread exiting...", __FUNCTION__, arg); + + return NULL; +} + +lldb::pid_t +ProcessMacOSXRemote::AttachForDebug (lldb::pid_t pid) +{ + // Clear out and clean up from any current state + Clear(); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS); + if (pid != 0) + { + SetState(eStateAttaching); + SetID(pid); + // Let ourselves know we are going to be using SBS if the correct flag bit is set... +#if defined (__arm__) + if (IsSBProcess(pid)) + m_flags |= eFlagsUsingSBS; +#endif + m_task.StartExceptionThread(GetError()); + + if (GetError().Success()) + { + if (ptrace (PT_ATTACHEXC, pid, 0, 0) == 0) + { + m_flags.Set (eFlagsAttached); + // Sleep a bit to let the exception get received and set our process status + // to stopped. + ::usleep(250000); + if (log) + log->Printf ("successfully attached to pid %d", pid); + return GetID(); + } + else + { + GetError().SetErrorToErrno(); + if (log) + log->Printf ("error: failed to attach to pid %d", pid); + } + } + else + { + GetError().Log(log, "ProcessMacOSXRemote::%s (pid = %i) failed to start exception thread", __FUNCTION__, pid); + } + } + return LLDB_INVALID_PROCESS_ID; +} + +lldb::pid_t +ProcessMacOSXRemote::LaunchForDebug +( + const char *path, + char const *argv[], + char const *envp[], + ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + PDLaunchType launch_type, + Error &launch_err) +{ + // Clear out and clean up from any current state + Clear(); + + m_arch_spec = arch_spec; + + if (launch_type == eLaunchDefault) + launch_type = eLaunchPosixSpawn; + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS); + if (log) + log->Printf ("%s( path = '%s', argv = %p, envp = %p, launch_type = %u )", __FUNCTION__, path, argv, envp, launch_type); + + // Fork a child process for debugging + SetState(eStateLaunching); + switch (launch_type) + { + case eLaunchForkExec: + SetID(ProcessMacOSXRemote::ForkChildForPTraceDebugging(path, argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, this, launch_err)); + break; + + case eLaunchPosixSpawn: + SetID(ProcessMacOSXRemote::PosixSpawnChildForPTraceDebugging(path, argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, this, launch_err)); + break; + +#if defined (__arm__) + + case eLaunchSpringBoard: + { + const char *app_ext = strstr(path, ".app"); + if (app_ext != NULL) + { + std::string app_bundle_path(path, app_ext + strlen(".app")); + return SBLaunchForDebug (app_bundle_path.c_str(), argv, envp, arch_spec, stdin_path, stdout_path, stderr_path, launch_err); + } + } + break; + +#endif + + default: + // Invalid launch + launch_err.SetErrorToGenericError (); + return LLDB_INVALID_PROCESS_ID; + } + + lldb::pid_t pid = GetID(); + + if (pid == LLDB_INVALID_PROCESS_ID) + { + // 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.SetErrorToGenericError (); + } + else + { + // Make sure we can get our task port before going any further + m_task.TaskPortForProcessID (launch_err); + + // If that goes well then kick off our exception thread + if (launch_err.Success()) + m_task.StartExceptionThread(launch_err); + + if (launch_err.Success()) + { + //m_path = path; +// size_t i; +// if (argv) +// { +// char const *arg; +// for (i=0; (arg = argv[i]) != NULL; i++) +// m_args.push_back(arg); +// } + + StartSTDIOThread(); + + if (launch_type == eLaunchPosixSpawn) + { + + //SetState (eStateAttaching); + errno = 0; + if (::ptrace (PT_ATTACHEXC, pid, 0, 0) == 0) + launch_err.Clear(); + else + launch_err.SetErrorToErrno(); + + if (launch_err.Fail() || log) + launch_err.Log(log, "::ptrace (PT_ATTACHEXC, pid = %i, 0, 0 )", pid); + + if (launch_err.Success()) + m_flags.Set (eFlagsAttached); + else + SetState (eStateExited); + } + else + { + launch_err.Clear(); + } + } + else + { + // We were able to launch the process, but not get its task port + // so now we need to make it sleep with da fishes. + SetID(LLDB_INVALID_PROCESS_ID); + ::kill (pid, SIGCONT); + ::kill (pid, SIGKILL); + pid = LLDB_INVALID_PROCESS_ID; + } + + } + return pid; +} + +lldb::pid_t +ProcessMacOSXRemote::PosixSpawnChildForPTraceDebugging +( + const char *path, + char const *argv[], + char const *envp[], + ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + ProcessMacOSXRemote* process, + Error &err +) +{ + posix_spawnattr_t attr; + + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_PROCESS); + + Error local_err; // Errors that don't affect the spawning. + if (log) + log->Printf ("%s ( path='%s', argv=%p, envp=%p, process )", __FUNCTION__, path, argv, envp); + err.SetError( ::posix_spawnattr_init (&attr), Error::POSIX); + if (err.Fail() || log) + err.Log(log, "::posix_spawnattr_init ( &attr )"); + if (err.Fail()) + return LLDB_INVALID_PROCESS_ID; + + err.SetError( ::posix_spawnattr_setflags (&attr, POSIX_SPAWN_START_SUSPENDED), Error::POSIX); + if (err.Fail() || log) + err.Log(log, "::posix_spawnattr_setflags ( &attr, POSIX_SPAWN_START_SUSPENDED )"); + if (err.Fail()) + return LLDB_INVALID_PROCESS_ID; + +#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... + cpu_type_t cpu = arch_spec.GetCPUType(); + if (cpu != 0 && cpu != CPU_TYPE_ANY && cpu != LLDB_INVALID_CPUTYPE) + { + size_t ocount = 0; + err.SetError( ::posix_spawnattr_setbinpref_np (&attr, 1, &cpu, &ocount), Error::POSIX); + if (err.Fail() || log) + err.Log(log, "::posix_spawnattr_setbinpref_np ( &attr, 1, cpu_type = 0x%8.8x, count => %zu )", cpu, ocount); + + if (err.Fail() != 0 || ocount != 1) + return LLDB_INVALID_PROCESS_ID; + } + +#endif + + PseudoTerminal pty; + + posix_spawn_file_actions_t file_actions; + err.SetError( ::posix_spawn_file_actions_init (&file_actions), Error::POSIX); + int file_actions_valid = err.Success(); + if (!file_actions_valid || log) + err.Log(log, "::posix_spawn_file_actions_init ( &file_actions )"); + Error stdio_err; + lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; + if (file_actions_valid) + { + // If the user specified any STDIO files, then use those + if (stdin_path || stdout_path || stderr_path) + { + process->SetSTDIOIsOurs(false); + if (stderr_path != NULL && stderr_path[0]) + { + stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDERR_FILENO, stderr_path, O_RDWR, 0), Error::POSIX); + if (stdio_err.Fail() || log) + stdio_err.Log(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDERR_FILENO, path = '%s', oflag = O_RDWR, mode = 0 )", stderr_path); + } + + if (stdin_path != NULL && stdin_path[0]) + { + stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDIN_FILENO, stdin_path, O_RDONLY, 0), Error::POSIX); + if (stdio_err.Fail() || log) + stdio_err.Log(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDIN_FILENO, path = '%s', oflag = O_RDONLY, mode = 0 )", stdin_path); + } + + if (stdout_path != NULL && stdout_path[0]) + { + stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDOUT_FILENO, stdout_path, O_WRONLY, 0), Error::POSIX); + if (stdio_err.Fail() || log) + stdio_err.Log(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDOUT_FILENO, path = '%s', oflag = O_WRONLY, mode = 0 )", stdout_path); + } + } + else + { + // The user did not specify any STDIO files, use a pseudo terminal. + // Callers can then access the file handles using the + // ProcessMacOSXRemote::ReleaseChildFileDescriptors() function, otherwise + // this class will spawn a thread that tracks STDIO and buffers it. + process->SetSTDIOIsOurs(true); + if (pty.OpenFirstAvailableMaster(O_RDWR, &stdio_err)) + { + const char* slave_name = pty.GetSlaveName(&stdio_err); + if (slave_name == NULL) + slave_name = "/dev/null"; + stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDERR_FILENO, slave_name, O_RDWR, 0), Error::POSIX); + if (stdio_err.Fail() || log) + stdio_err.Log(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDERR_FILENO, path = '%s', oflag = O_RDWR, mode = 0 )", slave_name); + + stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDIN_FILENO, slave_name, O_RDONLY, 0), Error::POSIX); + if (stdio_err.Fail() || log) + stdio_err.Log(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDIN_FILENO, path = '%s', oflag = O_RDONLY, mode = 0 )", slave_name); + + stdio_err.SetError( ::posix_spawn_file_actions_addopen(&file_actions, STDOUT_FILENO, slave_name, O_WRONLY, 0), Error::POSIX); + if (stdio_err.Fail() || log) + stdio_err.Log(log, "::posix_spawn_file_actions_addopen ( &file_actions, filedes = STDOUT_FILENO, path = '%s', oflag = O_WRONLY, mode = 0 )", slave_name); + } + } + err.SetError( ::posix_spawnp (&pid, path, &file_actions, &attr, (char * const*)argv, (char * const*)envp), Error::POSIX); + if (err.Fail() || log) + err.Log(log, "::posix_spawnp ( pid => %i, path = '%s', file_actions = %p, attr = %p, argv = %p, envp = %p )", pid, path, &file_actions, &attr, argv, envp); + + if (stdio_err.Success()) + { + // If we have a valid process and we created the STDIO file handles, + // then remember them on our process class so we can spawn a STDIO + // thread and close them when we are done with them. + if (process != NULL && process->STDIOIsOurs()) + { + int master_fd = pty.ReleaseMasterFileDescriptor (); + process->SetChildFileDescriptors (master_fd, master_fd, master_fd); + } + } + } + else + { + err.SetError( ::posix_spawnp (&pid, path, NULL, &attr, (char * const*)argv, (char * const*)envp), Error::POSIX); + if (err.Fail() || log) + err.Log(log, "::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 = LLDB_INVALID_PROCESS_ID; + + if (file_actions_valid) + { + local_err.SetError( ::posix_spawn_file_actions_destroy (&file_actions), Error::POSIX); + if (local_err.Fail() || log) + local_err.Log(log, "::posix_spawn_file_actions_destroy ( &file_actions )"); + } + + return pid; +} + +lldb::pid_t +ProcessMacOSXRemote::ForkChildForPTraceDebugging +( + const char *path, + char const *argv[], + char const *envp[], + ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + ProcessMacOSXRemote* process, + Error &launch_err +) +{ + lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; + + if (stdin_path || stdout_path || stderr_path) + { + assert(!"TODO: ForkChildForPTraceDebugging doesn't currently support fork/exec with user file handles..."); + } + else + { + + // Use a fork that ties the child process's stdin/out/err to a pseudo + // terminal so we can read it in our ProcessMacOSXRemote::STDIOThread + // as unbuffered io. + PseudoTerminal pty; + pid = pty.Fork(&launch_err); + + if (pid < 0) + { + //-------------------------------------------------------------- + // Error 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. + ::setgid (getgid ()); + + // 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, (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.ReleaseMasterFileDescriptor (); + process->SetChildFileDescriptors (master_fd, master_fd, master_fd); + } + } + } + return pid; +} + +#if defined (__arm__) + +lldb::pid_t +ProcessMacOSXRemote::SBLaunchForDebug +( + const char *path, + char const *argv[], + char const *envp[], + ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + Error &launch_err +) +{ + // Clear out and clean up from any current state + Clear(); + + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s( '%s', argv)", __FUNCTION__, path); + + // Fork a child process for debugging + SetState(eStateLaunching); + m_pid = ProcessMacOSXRemote::SBLaunchForDebug(path, argv, envp, this, launch_err); + if (m_pid != 0) + { + m_flags |= eFlagsUsingSBS; + //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(); + StartSTDIOThread(); + SetState (eStateAttaching); + int err = ptrace (PT_ATTACHEXC, m_pid, 0, 0); + if (err == 0) + { + m_flags |= eFlagsAttached; + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "successfully attached to pid %d", m_pid); + } + else + { + SetState (eStateExited); + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "error: failed to attach to pid %d", m_pid); + } + } + return m_pid; +} + +#include <servers/bootstrap.h> +#include "CFBundle.h" +#include "CFData.h" +#include "CFString.h" + +lldb::pid_t +ProcessMacOSXRemote::SBLaunchForDebug +( + const char *app_bundle_path, + char const *argv[], + char const *envp[], + ArchSpec& arch_spec, + const char *stdin_path, + const char *stdout_path, + const char *stderr_path, + ProcessMacOSXRemote* process, + Error &launch_err +) +{ + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s( '%s', argv, %p)", __FUNCTION__, app_bundle_path, process); + CFAllocatorRef alloc = kCFAllocatorDefault; + if (argv[0] == NULL) + return LLDB_INVALID_PROCESS_ID; + + 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 stdout_cf_path; + CFString stderr_cf_path; + PseudoTerminal pty; + + if (stdin_path || stdout_path || stderr_path) + { + process->SetSTDIOIsOurs(false); + if (stdout_path) + stdout_cf_path.SetFileSystemRepresentation (stdout_path); + if (stderr_path) + stderr_cf_path.SetFileSystemRepresentation (stderr_path); + } + else + { + process->SetSTDIOIsOurs(true); + PseudoTerminal::Error pty_err = pty.OpenFirstAvailableMaster(O_RDWR); + if (pty_err == PseudoTerminal::success) + { + const char* slave_name = pty.SlaveName(); + ProcessMacOSXLog::LogIf (PD_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); + stdout_cf_path.SetFileSystemRepresentation (slave_name); + stderr_cf_path.(stdout_cf_path); + } + } + } + + if (stdout_cf_path.get() == NULL) + stdout_cf_path.SetFileSystemRepresentation ("/dev/null"); + if (stderr_cf_path.get() == NULL) + stderr_cf_path.SetFileSystemRepresentation ("/dev/null"); + + CFBundle bundle(app_bundle_path); + CFStringRef bundleIDCFStr = bundle.GetIdentifier(); + std::string bundleID; + if (CFString::UTF8(bundleIDCFStr, bundleID) == NULL) + { + struct stat app_bundle_stat; + if (::stat (app_bundle_path, &app_bundle_stat) < 0) + { + launch_err.SetError(errno, Error::POSIX); + launch_err.SetErrorStringWithFormat ("%s: \"%s\".\n", launch_err.AsString(), app_bundle_path); + } + else + { + launch_err.SetError(-1, Error::Generic); + launch_err.SetErrorStringWithFormat ("Failed to extract CFBundleIdentifier from %s.\n", app_bundle_path); + } + return LLDB_INVALID_PROCESS_ID; + } + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() extracted CFBundleIdentifier: %s", __FUNCTION__, bundleID.c_str()); + + + CFData argv_data(NULL); + + if (launch_argv.get()) + { + if (argv_data.Serialize(launch_argv.get(), kCFPropertyListBinaryFormat_v1_0) == NULL) + { + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() error: failed to serialize launch arg array...", __FUNCTION__); + return LLDB_INVALID_PROCESS_ID; + } + } + + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() serialized launch arg array", __FUNCTION__); + + // Find SpringBoard + SBSApplicationLaunchError sbs_error = 0; + sbs_error = SBSLaunchApplication ( bundleIDCFStr, + (CFURLRef)NULL, // openURL + launch_argv.get(), + launch_envp.get(), // CFDictionaryRef environment + stdout_cf_path.get(), + stderr_cf_path.get(), + SBSApplicationLaunchWaitForDebugger | SBSApplicationLaunchUnlockDevice); + + + launch_err.SetError(sbs_error, Error::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; + + lldb::pid_t pid = LLDB_INVALID_PROCESS_ID; + 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; + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() polling Springboard for pid for %s...", __FUNCTION__, bundleID.c_str()); + pid_found = SBSProcessIDForDisplayIdentifier(bundleIDCFStr, &pid); + } + + if (pid_found) + { + // If we have a valid process and we created the STDIO file handles, + // then remember them on our process class so we can spawn a STDIO + // thread and close them when we are done with them. + if (process != NULL && process->STDIOIsOurs()) + { + // 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); + } + ProcessMacOSXLog::LogIf (PD_LOG_PROCESS, "%s() => pid = %4.4x", __FUNCTION__, pid); + } + else + { + LogError("failed to lookup the process ID for CFBundleIdentifier %s.", bundleID.c_str()); + } + return pid; + } + + LogError("unable to launch the application with CFBundleIdentifier '%s' sbs_error = %u", bundleID.c_str(), sbs_error); + return LLDB_INVALID_PROCESS_ID; +} + +#endif // #if defined (__arm__) + diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSXRemote.h b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSXRemote.h new file mode 100644 index 00000000000..01905c6192a --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/ProcessMacOSXRemote.h @@ -0,0 +1,206 @@ +//===-- ProcessMacOSXRemote.h -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +//---------------------------------------------------------------------- +// +// ProcessMacOSXRemote.h +// liblldb +// +// Created by Greg Clayton on 4/21/09. +// +// +//---------------------------------------------------------------------- + +#ifndef liblldb_ProcessMacOSXRemote_H_ +#define liblldb_ProcessMacOSXRemote_H_ + +// C Includes + +// C++ Includes +#include <list> + +// Other libraries and framework includes +#include "lldb/Target/Process.h" +#include "lldb/Target/Thread.h" + +class ThreadMacOSXRemote; + +class ProcessMacOSXRemote : + public Process +{ +public: + friend class ThreadMacOSX; + + //------------------------------------------------------------------ + // Constructors and Destructors + //------------------------------------------------------------------ + ProcessMacOSXRemote(Target& target); + virtual ~DCProcessMacOSXRemote(); + + static Process* CreateInstance (Target& target); + + //------------------------------------------------------------------ + // Check if a given Process + //------------------------------------------------------------------ + virtual bool CanDebug(Target &target); + + //------------------------------------------------------------------ + // Creating a new process, or attaching to an existing one + //------------------------------------------------------------------ + virtual lldb::pid_t DoLaunch (Module* module, + char const *argv[], // Can be NULL + char const *envp[], // Can be NULL + const char *stdin_path, // Can be NULL + const char *stdout_path, // Can be NULL + const char *stderr_path); // Can be NULL + virtual void DidLaunch (); + virtual lldb::pid_t DoAttach (lldb::pid_t pid); + virtual void DidAttach (); + + //------------------------------------------------------------------ + // Process Control + //------------------------------------------------------------------ +// virtual bool WillResume (); + virtual bool DoResume (); +// virtual void DidResume (); + + virtual bool DoHalt (); + virtual bool WillDetach (); + virtual bool DoDetach (); + virtual bool DoKill (int signal); + + virtual bool ShouldStop (); + + //------------------------------------------------------------------ + // Process Queries + //------------------------------------------------------------------ + virtual bool IsAlive (); + virtual bool IsRunning (); + virtual lldb::addr_t GetImageInfoAddress(); + + //------------------------------------------------------------------ + // Process Memory + //------------------------------------------------------------------ + virtual size_t DoReadMemory (lldb::addr_t addr, void *buf, size_t size); + virtual size_t DoWriteMemory (lldb::addr_t addr, const void *buf, size_t size); + + //------------------------------------------------------------------ + // Process STDIO + //------------------------------------------------------------------ + virtual size_t GetSTDOUT (char *buf, size_t buf_size); + virtual size_t GetSTDERR (char *buf, size_t buf_size); + + //---------------------------------------------------------------------- + // Process Breakpoints + //---------------------------------------------------------------------- + virtual size_t + GetSoftwareBreakpointTrapOpcode (lldb::BreakpointSite *bp_site); + + //---------------------------------------------------------------------- + // Process Breakpoints + //---------------------------------------------------------------------- + virtual bool + EnableBreakpoint (lldb::BreakpointSite *bp_site); + + virtual bool + DisableBreakpoint (lldb::BreakpointSite *bp_site); + + //---------------------------------------------------------------------- + // Process Watchpoints + //---------------------------------------------------------------------- + virtual bool EnableWatchpoint (WatchpointLocation *wp_loc); + virtual bool DisableWatchpoint (WatchpointLocation *wp_loc); + + //------------------------------------------------------------------ + // Thread Queries + //------------------------------------------------------------------ + virtual Thread * GetCurrentThread (); + virtual bool SetCurrentThread (lldb::tid_t tid); + virtual Thread * GetThreadAtIndex (uint32_t idx); + virtual Thread * GetThreadByID (lldb::tid_t tid); + virtual size_t GetNumThreads (); + + virtual ByteOrder GetByteOrder () const; + + virtual DynamicLoader * + GetDynamicLoader (); + +protected: + Flags m_flags; // Process specific flags (see eFlags enums) + ArchSpec m_arch_spec; + std::auto_ptr<DynamicLoader> m_dynamic_loader_ap; + ByteOrder m_byte_order; + + //---------------------------------------------------------------------- + // Accessors + //---------------------------------------------------------------------- + bool + ProcessIDIsValid ( ) const; + + bool + IsRunning ( State state ) + { + return state == eStateRunning || IsStepping(state); + } + + bool + IsStepping ( State state) + { + return state == eStateStepping; + } + bool + CanResume ( State state) + { + return state == eStateStopped; + } + + ArchSpec& + GetArchSpec() + { + return m_arch_spec; + } + const ArchSpec& + GetArchSpec() const + { + return m_arch_spec; + } + + enum + { + eFlagsNone = 0, + eFlagsAttached = (1 << 0), + eFlagsUsingSBS = (1 << 1) + }; + + void + Clear ( ); + + Flags & + GetFlags () + { + return m_flags; + } + + const Flags & + GetFlags () const + { + return m_flags; + } + + uint32_t + UpdateThreadListIfNeeded (); + +private: + //------------------------------------------------------------------ + // For ProcessMacOSXRemote only + //------------------------------------------------------------------ + DISALLOW_COPY_AND_ASSIGN (ProcessMacOSXRemote); + +}; + +#endif // liblldb_ProcessMacOSXRemote_H_ diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_arm.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_arm.cpp new file mode 100644 index 00000000000..37472547f55 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_arm.cpp @@ -0,0 +1,1448 @@ +//===-- RegisterContextMach_arm.cpp -----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "RegisterContextMach_arm.h" + +// C Includes +#include <mach/thread_act.h> + +// C++ Includes +// Other libraries and framework includes +#include "lldb/Core/DataBufferHeap.h" +#include "lldb/Core/DataExtractor.h" +#include "lldb/Core/Scalar.h" + +// Project includes +#include "ARM_GCC_Registers.h" +#include "ARM_DWARF_Registers.h" +#include "ProcessMacOSXLog.h" + +using namespace lldb; +using namespace lldb_private; + +enum +{ + gpr_r0 = 0, + gpr_r1, + gpr_r2, + gpr_r3, + gpr_r4, + gpr_r5, + gpr_r6, + gpr_r7, + gpr_r8, + gpr_r9, + gpr_r10, + gpr_r11, + gpr_r12, + gpr_r13, gpr_sp = gpr_r13, + gpr_r14, gpr_lr = gpr_r14, + gpr_r15, gpr_pc = gpr_r15, + gpr_cpsr, + + fpu_s0, + fpu_s1, + fpu_s2, + fpu_s3, + fpu_s4, + fpu_s5, + fpu_s6, + fpu_s7, + fpu_s8, + fpu_s9, + fpu_s10, + fpu_s11, + fpu_s12, + fpu_s13, + fpu_s14, + fpu_s15, + fpu_s16, + fpu_s17, + fpu_s18, + fpu_s19, + fpu_s20, + fpu_s21, + fpu_s22, + fpu_s23, + fpu_s24, + fpu_s25, + fpu_s26, + fpu_s27, + fpu_s28, + fpu_s29, + fpu_s30, + fpu_s31, + fpu_fpscr, + + exc_exception, + exc_fsr, + exc_far, + + dbg_bvr0, + dbg_bvr1, + dbg_bvr2, + dbg_bvr3, + dbg_bvr4, + dbg_bvr5, + dbg_bvr6, + dbg_bvr7, + dbg_bvr8, + dbg_bvr9, + dbg_bvr10, + dbg_bvr11, + dbg_bvr12, + dbg_bvr13, + dbg_bvr14, + dbg_bvr15, + + dbg_bcr0, + dbg_bcr1, + dbg_bcr2, + dbg_bcr3, + dbg_bcr4, + dbg_bcr5, + dbg_bcr6, + dbg_bcr7, + dbg_bcr8, + dbg_bcr9, + dbg_bcr10, + dbg_bcr11, + dbg_bcr12, + dbg_bcr13, + dbg_bcr14, + dbg_bcr15, + + dbg_wvr0, + dbg_wvr1, + dbg_wvr2, + dbg_wvr3, + dbg_wvr4, + dbg_wvr5, + dbg_wvr6, + dbg_wvr7, + dbg_wvr8, + dbg_wvr9, + dbg_wvr10, + dbg_wvr11, + dbg_wvr12, + dbg_wvr13, + dbg_wvr14, + dbg_wvr15, + + dbg_wcr0, + dbg_wcr1, + dbg_wcr2, + dbg_wcr3, + dbg_wcr4, + dbg_wcr5, + dbg_wcr6, + dbg_wcr7, + dbg_wcr8, + dbg_wcr9, + dbg_wcr10, + dbg_wcr11, + dbg_wcr12, + dbg_wcr13, + dbg_wcr14, + dbg_wcr15, + + k_num_registers +}; + + +RegisterContextMach_arm::RegisterContextMach_arm(Thread &thread, StackFrame *frame) : + RegisterContext(thread, frame), + gpr(), + fpu(), + exc() +{ + uint32_t i; + for (i=0; i<kNumErrors; i++) + { + gpr_errs[i] = -1; + fpu_errs[i] = -1; + exc_errs[i] = -1; + } +} + +RegisterContextMach_arm::~RegisterContextMach_arm() +{ +} + + +#define GPR_OFFSET(idx) ((idx) * 4) +#define FPU_OFFSET(idx) ((idx) * 4 + sizeof (RegisterContextMach_arm::GPR)) +#define EXC_OFFSET(idx) ((idx) * 4 + sizeof (RegisterContextMach_arm::GPR) + sizeof (RegisterContextMach_arm::FPU)) +#define DBG_OFFSET(reg) (offsetof (RegisterContextMach_arm::DBG, reg) + sizeof (RegisterContextMach_arm::GPR) + sizeof (RegisterContextMach_arm::FPU) + sizeof (RegisterContextMach_arm::EXC)) + +#define DEFINE_DBG(reg, i) #reg, NULL, sizeof(((RegisterContextMach_arm::DBG *)NULL)->reg[i]), DBG_OFFSET(reg[i]), eEncodingUint, eFormatHex, dbg_##reg##i, { LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM } +#define REG_CONTEXT_SIZE (sizeof (RegisterContextMach_arm::GPR) + sizeof (RegisterContextMach_arm::FPU) + sizeof (RegisterContextMach_arm::EXC)) +// General purpose registers +static lldb::RegisterInfo +g_register_infos[] = +{ +// NAME ALT SZ OFFSET ENCODING FORMAT NATIVE COMPILER DWARF GENERIC +// ====== ======= == ============= ============= ============ ========== =============== =============== ========= +{ "r0", NULL, 4, GPR_OFFSET(0), eEncodingUint, eFormatHex, gpr_r0, { gcc_r0, dwarf_r0, LLDB_INVALID_REGNUM }}, +{ "r1", NULL, 4, GPR_OFFSET(1), eEncodingUint, eFormatHex, gpr_r1, { gcc_r1, dwarf_r1, LLDB_INVALID_REGNUM }}, +{ "r2", NULL, 4, GPR_OFFSET(2), eEncodingUint, eFormatHex, gpr_r2, { gcc_r2, dwarf_r2, LLDB_INVALID_REGNUM }}, +{ "r3", NULL, 4, GPR_OFFSET(3), eEncodingUint, eFormatHex, gpr_r3, { gcc_r3, dwarf_r3, LLDB_INVALID_REGNUM }}, +{ "r4", NULL, 4, GPR_OFFSET(4), eEncodingUint, eFormatHex, gpr_r4, { gcc_r4, dwarf_r4, LLDB_INVALID_REGNUM }}, +{ "r5", NULL, 4, GPR_OFFSET(5), eEncodingUint, eFormatHex, gpr_r5, { gcc_r5, dwarf_r5, LLDB_INVALID_REGNUM }}, +{ "r6", NULL, 4, GPR_OFFSET(6), eEncodingUint, eFormatHex, gpr_r6, { gcc_r6, dwarf_r6, LLDB_INVALID_REGNUM }}, +{ "r7", NULL, 4, GPR_OFFSET(7), eEncodingUint, eFormatHex, gpr_r7, { gcc_r7, dwarf_r7, LLDB_REGNUM_GENERIC_FP }}, +{ "r8", NULL, 4, GPR_OFFSET(8), eEncodingUint, eFormatHex, gpr_r8, { gcc_r8, dwarf_r8, LLDB_INVALID_REGNUM }}, +{ "r9", NULL, 4, GPR_OFFSET(9), eEncodingUint, eFormatHex, gpr_r9, { gcc_r9, dwarf_r9, LLDB_INVALID_REGNUM }}, +{ "r10", NULL, 4, GPR_OFFSET(10), eEncodingUint, eFormatHex, gpr_r10, { gcc_r10, dwarf_r10, LLDB_INVALID_REGNUM }}, +{ "r11", NULL, 4, GPR_OFFSET(11), eEncodingUint, eFormatHex, gpr_r11, { gcc_r11, dwarf_r11, LLDB_INVALID_REGNUM }}, +{ "r12", NULL, 4, GPR_OFFSET(12), eEncodingUint, eFormatHex, gpr_r12, { gcc_r12, dwarf_r12, LLDB_INVALID_REGNUM }}, +{ "sp", "r13", 4, GPR_OFFSET(13), eEncodingUint, eFormatHex, gpr_sp, { gcc_sp, dwarf_sp, LLDB_REGNUM_GENERIC_SP }}, +{ "lr", "r14", 4, GPR_OFFSET(14), eEncodingUint, eFormatHex, gpr_lr, { gcc_lr, dwarf_lr, LLDB_REGNUM_GENERIC_RA }}, +{ "pc", "r15", 4, GPR_OFFSET(15), eEncodingUint, eFormatHex, gpr_pc, { gcc_pc, dwarf_pc, LLDB_REGNUM_GENERIC_PC }}, +{ "cpsr", "psr", 4, GPR_OFFSET(16), eEncodingUint, eFormatHex, gpr_cpsr, { gcc_cpsr, dwarf_cpsr, LLDB_REGNUM_GENERIC_FLAGS }}, + +{ "s0", NULL, 4, FPU_OFFSET(0), eEncodingIEEE754,eFormatFloat, fpu_s0, { LLDB_INVALID_REGNUM, dwarf_s0, LLDB_INVALID_REGNUM }}, +{ "s1", NULL, 4, FPU_OFFSET(1), eEncodingIEEE754,eFormatFloat, fpu_s1, { LLDB_INVALID_REGNUM, dwarf_s1, LLDB_INVALID_REGNUM }}, +{ "s2", NULL, 4, FPU_OFFSET(2), eEncodingIEEE754,eFormatFloat, fpu_s2, { LLDB_INVALID_REGNUM, dwarf_s2, LLDB_INVALID_REGNUM }}, +{ "s3", NULL, 4, FPU_OFFSET(3), eEncodingIEEE754,eFormatFloat, fpu_s3, { LLDB_INVALID_REGNUM, dwarf_s3, LLDB_INVALID_REGNUM }}, +{ "s4", NULL, 4, FPU_OFFSET(4), eEncodingIEEE754,eFormatFloat, fpu_s4, { LLDB_INVALID_REGNUM, dwarf_s4, LLDB_INVALID_REGNUM }}, +{ "s5", NULL, 4, FPU_OFFSET(5), eEncodingIEEE754,eFormatFloat, fpu_s5, { LLDB_INVALID_REGNUM, dwarf_s5, LLDB_INVALID_REGNUM }}, +{ "s6", NULL, 4, FPU_OFFSET(6), eEncodingIEEE754,eFormatFloat, fpu_s6, { LLDB_INVALID_REGNUM, dwarf_s6, LLDB_INVALID_REGNUM }}, +{ "s7", NULL, 4, FPU_OFFSET(7), eEncodingIEEE754,eFormatFloat, fpu_s7, { LLDB_INVALID_REGNUM, dwarf_s7, LLDB_INVALID_REGNUM }}, +{ "s8", NULL, 4, FPU_OFFSET(8), eEncodingIEEE754,eFormatFloat, fpu_s8, { LLDB_INVALID_REGNUM, dwarf_s8, LLDB_INVALID_REGNUM }}, +{ "s9", NULL, 4, FPU_OFFSET(9), eEncodingIEEE754,eFormatFloat, fpu_s9, { LLDB_INVALID_REGNUM, dwarf_s9, LLDB_INVALID_REGNUM }}, +{ "s10", NULL, 4, FPU_OFFSET(10), eEncodingIEEE754,eFormatFloat, fpu_s10, { LLDB_INVALID_REGNUM, dwarf_s10, LLDB_INVALID_REGNUM }}, +{ "s11", NULL, 4, FPU_OFFSET(11), eEncodingIEEE754,eFormatFloat, fpu_s11, { LLDB_INVALID_REGNUM, dwarf_s11, LLDB_INVALID_REGNUM }}, +{ "s12", NULL, 4, FPU_OFFSET(12), eEncodingIEEE754,eFormatFloat, fpu_s12, { LLDB_INVALID_REGNUM, dwarf_s12, LLDB_INVALID_REGNUM }}, +{ "s13", NULL, 4, FPU_OFFSET(13), eEncodingIEEE754,eFormatFloat, fpu_s13, { LLDB_INVALID_REGNUM, dwarf_s13, LLDB_INVALID_REGNUM }}, +{ "s14", NULL, 4, FPU_OFFSET(14), eEncodingIEEE754,eFormatFloat, fpu_s14, { LLDB_INVALID_REGNUM, dwarf_s14, LLDB_INVALID_REGNUM }}, +{ "s15", NULL, 4, FPU_OFFSET(15), eEncodingIEEE754,eFormatFloat, fpu_s15, { LLDB_INVALID_REGNUM, dwarf_s15, LLDB_INVALID_REGNUM }}, +{ "s16", NULL, 4, FPU_OFFSET(16), eEncodingIEEE754,eFormatFloat, fpu_s16, { LLDB_INVALID_REGNUM, dwarf_s16, LLDB_INVALID_REGNUM }}, +{ "s17", NULL, 4, FPU_OFFSET(17), eEncodingIEEE754,eFormatFloat, fpu_s17, { LLDB_INVALID_REGNUM, dwarf_s17, LLDB_INVALID_REGNUM }}, +{ "s18", NULL, 4, FPU_OFFSET(18), eEncodingIEEE754,eFormatFloat, fpu_s18, { LLDB_INVALID_REGNUM, dwarf_s18, LLDB_INVALID_REGNUM }}, +{ "s19", NULL, 4, FPU_OFFSET(19), eEncodingIEEE754,eFormatFloat, fpu_s19, { LLDB_INVALID_REGNUM, dwarf_s19, LLDB_INVALID_REGNUM }}, +{ "s20", NULL, 4, FPU_OFFSET(20), eEncodingIEEE754,eFormatFloat, fpu_s20, { LLDB_INVALID_REGNUM, dwarf_s20, LLDB_INVALID_REGNUM }}, +{ "s21", NULL, 4, FPU_OFFSET(21), eEncodingIEEE754,eFormatFloat, fpu_s21, { LLDB_INVALID_REGNUM, dwarf_s21, LLDB_INVALID_REGNUM }}, +{ "s22", NULL, 4, FPU_OFFSET(22), eEncodingIEEE754,eFormatFloat, fpu_s22, { LLDB_INVALID_REGNUM, dwarf_s22, LLDB_INVALID_REGNUM }}, +{ "s23", NULL, 4, FPU_OFFSET(23), eEncodingIEEE754,eFormatFloat, fpu_s23, { LLDB_INVALID_REGNUM, dwarf_s23, LLDB_INVALID_REGNUM }}, +{ "s24", NULL, 4, FPU_OFFSET(24), eEncodingIEEE754,eFormatFloat, fpu_s24, { LLDB_INVALID_REGNUM, dwarf_s24, LLDB_INVALID_REGNUM }}, +{ "s25", NULL, 4, FPU_OFFSET(25), eEncodingIEEE754,eFormatFloat, fpu_s25, { LLDB_INVALID_REGNUM, dwarf_s25, LLDB_INVALID_REGNUM }}, +{ "s26", NULL, 4, FPU_OFFSET(26), eEncodingIEEE754,eFormatFloat, fpu_s26, { LLDB_INVALID_REGNUM, dwarf_s26, LLDB_INVALID_REGNUM }}, +{ "s27", NULL, 4, FPU_OFFSET(27), eEncodingIEEE754,eFormatFloat, fpu_s27, { LLDB_INVALID_REGNUM, dwarf_s27, LLDB_INVALID_REGNUM }}, +{ "s28", NULL, 4, FPU_OFFSET(28), eEncodingIEEE754,eFormatFloat, fpu_s28, { LLDB_INVALID_REGNUM, dwarf_s28, LLDB_INVALID_REGNUM }}, +{ "s29", NULL, 4, FPU_OFFSET(29), eEncodingIEEE754,eFormatFloat, fpu_s29, { LLDB_INVALID_REGNUM, dwarf_s29, LLDB_INVALID_REGNUM }}, +{ "s30", NULL, 4, FPU_OFFSET(30), eEncodingIEEE754,eFormatFloat, fpu_s30, { LLDB_INVALID_REGNUM, dwarf_s30, LLDB_INVALID_REGNUM }}, +{ "s31", NULL, 4, FPU_OFFSET(31), eEncodingIEEE754,eFormatFloat, fpu_s31, { LLDB_INVALID_REGNUM, dwarf_s31, LLDB_INVALID_REGNUM }}, +{ "fpscr", NULL, 4, FPU_OFFSET(32), eEncodingUint, eFormatHex, fpu_fpscr, { LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM }}, + +{ "exception",NULL, 4, EXC_OFFSET(0), eEncodingUint, eFormatHex, exc_exception,{ LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM }}, +{ "fsr", NULL, 4, EXC_OFFSET(1), eEncodingUint, eFormatHex, exc_fsr, { LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM }}, +{ "far", NULL, 4, EXC_OFFSET(2), eEncodingUint, eFormatHex, exc_far, { LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM, LLDB_INVALID_REGNUM }}, + +{ DEFINE_DBG (bvr, 0) }, +{ DEFINE_DBG (bvr, 0) }, +{ DEFINE_DBG (bvr, 1) }, +{ DEFINE_DBG (bvr, 2) }, +{ DEFINE_DBG (bvr, 3) }, +{ DEFINE_DBG (bvr, 4) }, +{ DEFINE_DBG (bvr, 5) }, +{ DEFINE_DBG (bvr, 6) }, +{ DEFINE_DBG (bvr, 7) }, +{ DEFINE_DBG (bvr, 8) }, +{ DEFINE_DBG (bvr, 9) }, +{ DEFINE_DBG (bvr, 10) }, +{ DEFINE_DBG (bvr, 11) }, +{ DEFINE_DBG (bvr, 12) }, +{ DEFINE_DBG (bvr, 13) }, +{ DEFINE_DBG (bvr, 14) }, +{ DEFINE_DBG (bvr, 15) }, + +{ DEFINE_DBG (bcr, 0) }, +{ DEFINE_DBG (bcr, 0) }, +{ DEFINE_DBG (bcr, 1) }, +{ DEFINE_DBG (bcr, 2) }, +{ DEFINE_DBG (bcr, 3) }, +{ DEFINE_DBG (bcr, 4) }, +{ DEFINE_DBG (bcr, 5) }, +{ DEFINE_DBG (bcr, 6) }, +{ DEFINE_DBG (bcr, 7) }, +{ DEFINE_DBG (bcr, 8) }, +{ DEFINE_DBG (bcr, 9) }, +{ DEFINE_DBG (bcr, 10) }, +{ DEFINE_DBG (bcr, 11) }, +{ DEFINE_DBG (bcr, 12) }, +{ DEFINE_DBG (bcr, 13) }, +{ DEFINE_DBG (bcr, 14) }, +{ DEFINE_DBG (bcr, 15) }, + +{ DEFINE_DBG (wvr, 0) }, +{ DEFINE_DBG (wvr, 0) }, +{ DEFINE_DBG (wvr, 1) }, +{ DEFINE_DBG (wvr, 2) }, +{ DEFINE_DBG (wvr, 3) }, +{ DEFINE_DBG (wvr, 4) }, +{ DEFINE_DBG (wvr, 5) }, +{ DEFINE_DBG (wvr, 6) }, +{ DEFINE_DBG (wvr, 7) }, +{ DEFINE_DBG (wvr, 8) }, +{ DEFINE_DBG (wvr, 9) }, +{ DEFINE_DBG (wvr, 10) }, +{ DEFINE_DBG (wvr, 11) }, +{ DEFINE_DBG (wvr, 12) }, +{ DEFINE_DBG (wvr, 13) }, +{ DEFINE_DBG (wvr, 14) }, +{ DEFINE_DBG (wvr, 15) }, + +{ DEFINE_DBG (wcr, 0) }, +{ DEFINE_DBG (wcr, 0) }, +{ DEFINE_DBG (wcr, 1) }, +{ DEFINE_DBG (wcr, 2) }, +{ DEFINE_DBG (wcr, 3) }, +{ DEFINE_DBG (wcr, 4) }, +{ DEFINE_DBG (wcr, 5) }, +{ DEFINE_DBG (wcr, 6) }, +{ DEFINE_DBG (wcr, 7) }, +{ DEFINE_DBG (wcr, 8) }, +{ DEFINE_DBG (wcr, 9) }, +{ DEFINE_DBG (wcr, 10) }, +{ DEFINE_DBG (wcr, 11) }, +{ DEFINE_DBG (wcr, 12) }, +{ DEFINE_DBG (wcr, 13) }, +{ DEFINE_DBG (wcr, 14) }, +{ DEFINE_DBG (wcr, 15) } +}; + +// General purpose registers +static uint32_t +g_gpr_regnums[] = +{ + gpr_r0, + gpr_r1, + gpr_r2, + gpr_r3, + gpr_r4, + gpr_r5, + gpr_r6, + gpr_r7, + gpr_r8, + gpr_r9, + gpr_r10, + gpr_r11, + gpr_r12, + gpr_sp, + gpr_lr, + gpr_pc, + gpr_cpsr +}; + +// Floating point registers +static uint32_t +g_fpu_regnums[] = +{ + fpu_s0, + fpu_s1, + fpu_s2, + fpu_s3, + fpu_s4, + fpu_s5, + fpu_s6, + fpu_s7, + fpu_s8, + fpu_s9, + fpu_s10, + fpu_s11, + fpu_s12, + fpu_s13, + fpu_s14, + fpu_s15, + fpu_s16, + fpu_s17, + fpu_s18, + fpu_s19, + fpu_s20, + fpu_s21, + fpu_s22, + fpu_s23, + fpu_s24, + fpu_s25, + fpu_s26, + fpu_s27, + fpu_s28, + fpu_s29, + fpu_s30, + fpu_s31, + fpu_fpscr, +}; + +// Exception registers + +static uint32_t +g_exc_regnums[] = +{ + exc_exception, + exc_fsr, + exc_far, +}; + +static size_t k_num_register_infos = (sizeof(g_register_infos)/sizeof(RegisterInfo)); + +void +RegisterContextMach_arm::Invalidate () +{ + InvalidateAllRegisterStates(); +} + + +size_t +RegisterContextMach_arm::GetRegisterCount () +{ + assert(k_num_register_infos == k_num_registers); + return k_num_registers; +} + +const RegisterInfo * +RegisterContextMach_arm::GetRegisterInfoAtIndex (uint32_t reg) +{ + assert(k_num_register_infos == k_num_registers); + if (reg < k_num_registers) + return &g_register_infos[reg]; + return NULL; +} + +size_t +RegisterContextMach_arm::GetRegisterInfosCount () +{ + return k_num_register_infos; +} + +const RegisterInfo * +RegisterContextMach_arm::GetRegisterInfos () +{ + return g_register_infos; +} + + +// Number of registers in each register set +const size_t k_num_gpr_registers = sizeof(g_gpr_regnums) / sizeof(uint32_t); +const size_t k_num_fpu_registers = sizeof(g_fpu_regnums) / sizeof(uint32_t); +const size_t k_num_exc_registers = sizeof(g_exc_regnums) / sizeof(uint32_t); + +//---------------------------------------------------------------------- +// Register set definitions. The first definitions at register set index +// of zero is for all registers, followed by other registers sets. The +// register information for the all register set need not be filled in. +//---------------------------------------------------------------------- +static const RegisterSet g_reg_sets[] = +{ + { "General Purpose Registers", "gpr", k_num_gpr_registers, g_gpr_regnums, }, + { "Floating Point Registers", "fpu", k_num_fpu_registers, g_fpu_regnums }, + { "Exception State Registers", "exc", k_num_exc_registers, g_exc_regnums } +}; + +const size_t k_num_regsets = sizeof(g_reg_sets) / sizeof(RegisterSet); + + +size_t +RegisterContextMach_arm::GetRegisterSetCount () +{ + return k_num_regsets; +} + +const RegisterSet * +RegisterContextMach_arm::GetRegisterSet (uint32_t reg_set) +{ + if (reg_set < k_num_regsets) + return &g_reg_sets[reg_set]; + return NULL; +} + + +//---------------------------------------------------------------------- +// Register information defintions for 32 bit i386. +//---------------------------------------------------------------------- +int +RegisterContextMach_arm::GetSetForNativeRegNum (int reg) +{ + if (reg < fpu_s0) + return GPRRegSet; + else if (reg < exc_exception) + return FPURegSet; + else if (reg < k_num_registers) + return EXCRegSet; + return -1; +} + +kern_return_t +RegisterContextMach_arm::ReadGPR (bool force) +{ + int set = GPRRegSet; + if (force || !RegisterSetIsCached(set)) + { + mach_msg_type_number_t count = GPRWordCount; + SetError(GPRRegSet, Read, ::thread_get_state(GetThreadID(), set, (thread_state_t)&gpr, &count)); + } + return GetError(GPRRegSet, Read); +} + +kern_return_t +RegisterContextMach_arm::ReadFPU (bool force) +{ + int set = FPURegSet; + if (force || !RegisterSetIsCached(set)) + { + mach_msg_type_number_t count = FPUWordCount; + SetError(FPURegSet, Read, ::thread_get_state(GetThreadID(), set, (thread_state_t)&fpu, &count)); + } + return GetError(FPURegSet, Read); +} + +kern_return_t +RegisterContextMach_arm::ReadEXC (bool force) +{ + int set = EXCRegSet; + if (force || !RegisterSetIsCached(set)) + { + mach_msg_type_number_t count = EXCWordCount; + SetError(EXCRegSet, Read, ::thread_get_state(GetThreadID(), set, (thread_state_t)&exc, &count)); + } + return GetError(EXCRegSet, Read); +} + +kern_return_t +RegisterContextMach_arm::ReadDBG (bool force) +{ + int set = DBGRegSet; + if (force || !RegisterSetIsCached(set)) + { + mach_msg_type_number_t count = DBGWordCount; + SetError(DBGRegSet, Read, ::thread_get_state(GetThreadID(), set, (thread_state_t)&dbg, &count)); + } + return GetError(DBGRegSet, Read); +} + +kern_return_t +RegisterContextMach_arm::WriteGPR () +{ + int set = GPRRegSet; + if (!RegisterSetIsCached(set)) + { + SetError (set, Write, -1); + return KERN_INVALID_ARGUMENT; + } + SetError(GPRRegSet, Write, ::thread_set_state(GetThreadID(), set, (thread_state_t)&gpr, GPRWordCount)); + return GetError(GPRRegSet, Write); +} + +kern_return_t +RegisterContextMach_arm::WriteFPU () +{ + int set = FPURegSet; + if (!RegisterSetIsCached(set)) + { + SetError (set, Write, -1); + return KERN_INVALID_ARGUMENT; + } + SetError(FPURegSet, Write, ::thread_set_state(GetThreadID(), set, (thread_state_t)&fpu, FPUWordCount)); + return GetError(FPURegSet, Write); +} + +kern_return_t +RegisterContextMach_arm::WriteEXC () +{ + int set = EXCRegSet; + if (!RegisterSetIsCached(set)) + { + SetError (set, Write, -1); + return KERN_INVALID_ARGUMENT; + } + SetError(EXCRegSet, Write, ::thread_set_state(GetThreadID(), set, (thread_state_t)&exc, EXCWordCount)); + return GetError(EXCRegSet, Write); +} + +kern_return_t +RegisterContextMach_arm::WriteDBG () +{ + int set = DBGRegSet; + if (!RegisterSetIsCached(set)) + { + SetError (set, Write, -1); + return KERN_INVALID_ARGUMENT; + } + SetError(DBGRegSet, Write, ::thread_set_state(GetThreadID(), set, (thread_state_t)&dbg, DBGWordCount)); + return GetError(DBGRegSet, Write); +} + + +kern_return_t +RegisterContextMach_arm::ReadRegisterSet (uint32_t set, bool force) +{ + switch (set) + { + case GPRRegSet: return ReadGPR(force); + case FPURegSet: return ReadFPU(force); + case EXCRegSet: return ReadEXC(force); + case DBGRegSet: return ReadDBG(force); + default: break; + } + return KERN_INVALID_ARGUMENT; +} + +kern_return_t +RegisterContextMach_arm::WriteRegisterSet (uint32_t set) +{ + // Make sure we have a valid context to set. + if (RegisterSetIsCached(set)) + { + switch (set) + { + case GPRRegSet: return WriteGPR(); + case FPURegSet: return WriteFPU(); + case EXCRegSet: return WriteEXC(); + case DBGRegSet: return WriteDBG(); + default: break; + } + } + return KERN_INVALID_ARGUMENT; +} + +void +RegisterContextMach_arm::LogDBGRegisters (Log *log, const DBG& dbg) +{ + if (log) + { + for (uint32_t i=0; i<16; i++) + log->Printf("BVR%-2u/BCR%-2u = { 0x%8.8x, 0x%8.8x } WVR%-2u/WCR%-2u = { 0x%8.8x, 0x%8.8x }", + i, i, dbg.bvr[i], dbg.bcr[i], + i, i, dbg.wvr[i], dbg.wcr[i]); + } +} + + +bool +RegisterContextMach_arm::ReadRegisterValue (uint32_t reg, Scalar &value) +{ + int set = RegisterContextMach_arm::GetSetForNativeRegNum (reg); + + if (set == -1) + return false; + + if (ReadRegisterSet(set, false) != KERN_SUCCESS) + return false; + + switch (reg) + { + case gpr_r0: + case gpr_r1: + case gpr_r2: + case gpr_r3: + case gpr_r4: + case gpr_r5: + case gpr_r6: + case gpr_r7: + case gpr_r8: + case gpr_r9: + case gpr_r10: + case gpr_r11: + case gpr_r12: + case gpr_sp: + case gpr_lr: + case gpr_pc: + case gpr_cpsr: + value = gpr.r[reg - gpr_r0]; + break; + + case fpu_s0: + case fpu_s1: + case fpu_s2: + case fpu_s3: + case fpu_s4: + case fpu_s5: + case fpu_s6: + case fpu_s7: + case fpu_s8: + case fpu_s9: + case fpu_s10: + case fpu_s11: + case fpu_s12: + case fpu_s13: + case fpu_s14: + case fpu_s15: + case fpu_s16: + case fpu_s17: + case fpu_s18: + case fpu_s19: + case fpu_s20: + case fpu_s21: + case fpu_s22: + case fpu_s23: + case fpu_s24: + case fpu_s25: + case fpu_s26: + case fpu_s27: + case fpu_s28: + case fpu_s29: + case fpu_s30: + case fpu_s31: + value = fpu.floats.s[reg]; + break; + + case fpu_fpscr: + value = fpu.fpscr; + break; + + case exc_exception: + value = exc.exception; + break; + case exc_fsr: + value = exc.fsr; + break; + case exc_far: + value = exc.far; + break; + + default: + return false; + + } + return true; +} + + +bool +RegisterContextMach_arm::WriteRegisterValue (uint32_t reg, const Scalar &value) +{ + int set = GetSetForNativeRegNum (reg); + + if (set == -1) + return false; + + if (ReadRegisterSet(set, false) != KERN_SUCCESS) + return false; + + switch (reg) + { + case gpr_r0: + case gpr_r1: + case gpr_r2: + case gpr_r3: + case gpr_r4: + case gpr_r5: + case gpr_r6: + case gpr_r7: + case gpr_r8: + case gpr_r9: + case gpr_r10: + case gpr_r11: + case gpr_r12: + case gpr_sp: + case gpr_lr: + case gpr_pc: + case gpr_cpsr: + gpr.r[reg - gpr_r0] = value.UInt(0); + break; + + case fpu_s0: + case fpu_s1: + case fpu_s2: + case fpu_s3: + case fpu_s4: + case fpu_s5: + case fpu_s6: + case fpu_s7: + case fpu_s8: + case fpu_s9: + case fpu_s10: + case fpu_s11: + case fpu_s12: + case fpu_s13: + case fpu_s14: + case fpu_s15: + case fpu_s16: + case fpu_s17: + case fpu_s18: + case fpu_s19: + case fpu_s20: + case fpu_s21: + case fpu_s22: + case fpu_s23: + case fpu_s24: + case fpu_s25: + case fpu_s26: + case fpu_s27: + case fpu_s28: + case fpu_s29: + case fpu_s30: + case fpu_s31: + fpu.floats.s[reg] = value.UInt(0); + break; + + case fpu_fpscr: + fpu.fpscr = value.UInt(0); + break; + + case exc_exception: + exc.exception = value.UInt(0); + break; + case exc_fsr: + exc.fsr = value.UInt(0); + break; + case exc_far: + exc.far = value.UInt(0); + break; + + default: + return false; + + } + return WriteRegisterSet(set) == KERN_SUCCESS; +} + +bool +RegisterContextMach_arm::ReadRegisterBytes (uint32_t reg, DataExtractor &data) +{ + int set = RegisterContextMach_arm::GetSetForNativeRegNum (reg); + if (set == -1) + return false; + + if (ReadRegisterSet(set, false) != KERN_SUCCESS) + return false; + + const RegisterInfo * reg_info = GetRegisterInfoAtIndex (reg); + if (reg_info == NULL) + return false; + + switch (reg) + { + case gpr_r0: + case gpr_r1: + case gpr_r2: + case gpr_r3: + case gpr_r4: + case gpr_r5: + case gpr_r6: + case gpr_r7: + case gpr_r8: + case gpr_r9: + case gpr_r10: + case gpr_r11: + case gpr_r12: + case gpr_sp: + case gpr_lr: + case gpr_pc: + case gpr_cpsr: + data.SetData(&gpr.r[reg - gpr_r0], reg_info->byte_size, eByteOrderHost); + break; + + case fpu_s0: + case fpu_s1: + case fpu_s2: + case fpu_s3: + case fpu_s4: + case fpu_s5: + case fpu_s6: + case fpu_s7: + case fpu_s8: + case fpu_s9: + case fpu_s10: + case fpu_s11: + case fpu_s12: + case fpu_s13: + case fpu_s14: + case fpu_s15: + case fpu_s16: + case fpu_s17: + case fpu_s18: + case fpu_s19: + case fpu_s20: + case fpu_s21: + case fpu_s22: + case fpu_s23: + case fpu_s24: + case fpu_s25: + case fpu_s26: + case fpu_s27: + case fpu_s28: + case fpu_s29: + case fpu_s30: + case fpu_s31: + data.SetData(&fpu.floats.s[reg - fpu_s0], reg_info->byte_size, eByteOrderHost); + break; + + case fpu_fpscr: + data.SetData(&fpu.fpscr, reg_info->byte_size, eByteOrderHost); + break; + + case exc_exception: + data.SetData(&exc.exception, reg_info->byte_size, eByteOrderHost); + break; + + case exc_fsr: + data.SetData(&exc.fsr, reg_info->byte_size, eByteOrderHost); + break; + + case exc_far: + data.SetData(&exc.far, reg_info->byte_size, eByteOrderHost); + break; + + default: + return false; + } + return true; +} + +bool +RegisterContextMach_arm::WriteRegisterBytes (uint32_t reg, DataExtractor &data, uint32_t data_offset) +{ + int set = GetSetForNativeRegNum (reg); + + if (set == -1) + return false; + + if (ReadRegisterSet(set, false) != KERN_SUCCESS) + return false; + + + const RegisterInfo * reg_info = GetRegisterInfoAtIndex (reg); + if (reg_info == NULL && data.ValidOffsetForDataOfSize(data_offset, reg_info->byte_size)) + return false; + + uint32_t offset = data_offset; + switch (reg) + { + case gpr_r0: + case gpr_r1: + case gpr_r2: + case gpr_r3: + case gpr_r4: + case gpr_r5: + case gpr_r6: + case gpr_r7: + case gpr_r8: + case gpr_r9: + case gpr_r10: + case gpr_r11: + case gpr_r12: + case gpr_sp: + case gpr_lr: + case gpr_pc: + case gpr_cpsr: + gpr.r[reg - gpr_r0] = data.GetU32 (&offset); + break; + + case fpu_s0: + case fpu_s1: + case fpu_s2: + case fpu_s3: + case fpu_s4: + case fpu_s5: + case fpu_s6: + case fpu_s7: + case fpu_s8: + case fpu_s9: + case fpu_s10: + case fpu_s11: + case fpu_s12: + case fpu_s13: + case fpu_s14: + case fpu_s15: + case fpu_s16: + case fpu_s17: + case fpu_s18: + case fpu_s19: + case fpu_s20: + case fpu_s21: + case fpu_s22: + case fpu_s23: + case fpu_s24: + case fpu_s25: + case fpu_s26: + case fpu_s27: + case fpu_s28: + case fpu_s29: + case fpu_s30: + case fpu_s31: + fpu.floats.s[reg - fpu_s0] = data.GetU32 (&offset); + break; + + case fpu_fpscr: + fpu.fpscr = data.GetU32 (&offset); + break; + + case exc_exception: + fpu.fpscr = data.GetU32 (&offset); + break; + + case exc_fsr: + exc.fsr = data.GetU32 (&offset); + break; + + case exc_far: + exc.far = data.GetU32 (&offset); + break; + + default: + return false; + } + return WriteRegisterSet(set) == KERN_SUCCESS; +} + +bool +RegisterContextMach_arm::ReadAllRegisterValues (lldb::DataBufferSP &data_sp) +{ + data_sp.reset (new DataBufferHeap (REG_CONTEXT_SIZE, 0)); + if (data_sp && + ReadGPR (false) == KERN_SUCCESS && + ReadFPU (false) == KERN_SUCCESS && + ReadEXC (false) == KERN_SUCCESS) + { + uint8_t *dst = data_sp->GetBytes(); + ::memcpy (dst, &gpr, sizeof(gpr)); + dst += sizeof(gpr); + + ::memcpy (dst, &fpu, sizeof(fpu)); + dst += sizeof(gpr); + + ::memcpy (dst, &exc, sizeof(exc)); + return true; + } + return false; +} + +bool +RegisterContextMach_arm::WriteAllRegisterValues (const lldb::DataBufferSP &data_sp) +{ + if (data_sp && data_sp->GetByteSize() == REG_CONTEXT_SIZE) + { + const uint8_t *src = data_sp->GetBytes(); + ::memcpy (&gpr, src, sizeof(gpr)); + src += sizeof(gpr); + + ::memcpy (&fpu, src, sizeof(fpu)); + src += sizeof(gpr); + + ::memcpy (&exc, src, sizeof(exc)); + uint32_t success_count = 0; + if (WriteGPR() == KERN_SUCCESS) + ++success_count; + if (WriteFPU() == KERN_SUCCESS) + ++success_count; + if (WriteEXC() == KERN_SUCCESS) + ++success_count; + return success_count == 3; + } + return false; +} + +uint32_t +RegisterContextMach_arm::ConvertRegisterKindToRegisterNumber (uint32_t kind, uint32_t reg) +{ + if (kind == eRegisterKindGeneric) + { + switch (reg) + { + case LLDB_REGNUM_GENERIC_PC: return gpr_pc; + case LLDB_REGNUM_GENERIC_SP: return gpr_sp; + case LLDB_REGNUM_GENERIC_FP: return gpr_r7; + case LLDB_REGNUM_GENERIC_RA: return gpr_lr; + case LLDB_REGNUM_GENERIC_FLAGS: return gpr_cpsr; + default: + break; + } + } + else if (kind == eRegisterKindDWARF) + { + switch (reg) + { + case dwarf_r0: return gpr_r0; + case dwarf_r1: return gpr_r1; + case dwarf_r2: return gpr_r2; + case dwarf_r3: return gpr_r3; + case dwarf_r4: return gpr_r4; + case dwarf_r5: return gpr_r5; + case dwarf_r6: return gpr_r6; + case dwarf_r7: return gpr_r7; + case dwarf_r8: return gpr_r8; + case dwarf_r9: return gpr_r9; + case dwarf_r10: return gpr_r10; + case dwarf_r11: return gpr_r11; + case dwarf_r12: return gpr_r12; + case dwarf_sp: return gpr_sp; + case dwarf_lr: return gpr_lr; + case dwarf_pc: return gpr_pc; + case dwarf_spsr: return gpr_cpsr; + + case dwarf_s0: return fpu_s0; + case dwarf_s1: return fpu_s1; + case dwarf_s2: return fpu_s2; + case dwarf_s3: return fpu_s3; + case dwarf_s4: return fpu_s4; + case dwarf_s5: return fpu_s5; + case dwarf_s6: return fpu_s6; + case dwarf_s7: return fpu_s7; + case dwarf_s8: return fpu_s8; + case dwarf_s9: return fpu_s9; + case dwarf_s10: return fpu_s10; + case dwarf_s11: return fpu_s11; + case dwarf_s12: return fpu_s12; + case dwarf_s13: return fpu_s13; + case dwarf_s14: return fpu_s14; + case dwarf_s15: return fpu_s15; + case dwarf_s16: return fpu_s16; + case dwarf_s17: return fpu_s17; + case dwarf_s18: return fpu_s18; + case dwarf_s19: return fpu_s19; + case dwarf_s20: return fpu_s20; + case dwarf_s21: return fpu_s21; + case dwarf_s22: return fpu_s22; + case dwarf_s23: return fpu_s23; + case dwarf_s24: return fpu_s24; + case dwarf_s25: return fpu_s25; + case dwarf_s26: return fpu_s26; + case dwarf_s27: return fpu_s27; + case dwarf_s28: return fpu_s28; + case dwarf_s29: return fpu_s29; + case dwarf_s30: return fpu_s30; + case dwarf_s31: return fpu_s31; + + default: + break; + } + } + else if (kind == eRegisterKindGCC) + { + switch (reg) + { + case gcc_r0: return gpr_r0; + case gcc_r1: return gpr_r1; + case gcc_r2: return gpr_r2; + case gcc_r3: return gpr_r3; + case gcc_r4: return gpr_r4; + case gcc_r5: return gpr_r5; + case gcc_r6: return gpr_r6; + case gcc_r7: return gpr_r7; + case gcc_r8: return gpr_r8; + case gcc_r9: return gpr_r9; + case gcc_r10: return gpr_r10; + case gcc_r11: return gpr_r11; + case gcc_r12: return gpr_r12; + case gcc_sp: return gpr_sp; + case gcc_lr: return gpr_lr; + case gcc_pc: return gpr_pc; + case gcc_cpsr: return gpr_cpsr; + } + } + return LLDB_INVALID_REGNUM; +} + + +uint32_t +RegisterContextMach_arm::NumSupportedHardwareBreakpoints () +{ +#if defined (__arm__) + // Set the init value to something that will let us know that we need to + // autodetect how many breakpoints are supported dynamically... + static uint32_t g_num_supported_hw_breakpoints = UINT_MAX + if (g_num_supported_hw_breakpoints == UINT_MAX) + { + // Set this to zero in case we can't tell if there are any HW breakpoints + g_num_supported_hw_breakpoints = 0; + + // Read the DBGDIDR to get the number of available hardware breakpoints + // However, in some of our current armv7 processors, hardware + // breakpoints/watchpoints were not properly connected. So detect those + // cases using a field in a sysctl. For now we are using "hw.cpusubtype" + // field to distinguish CPU architectures. This is a hack until we can + // get <rdar://problem/6372672> fixed, at which point we will switch to + // using a different sysctl string that will tell us how many BRPs + // are available to us directly without having to read DBGDIDR. + uint32_t register_DBGDIDR; + + asm("mrc p14, 0, %0, c0, c0, 0" : "=r" (register_DBGDIDR)); + uint32_t numBRPs = bits(register_DBGDIDR, 27, 24); + // Zero is reserved for the BRP count, so don't increment it if it is zero + if (numBRPs > 0) + numBRPs++; + ProcessMacOSXLog::LogIf(PD_LOG_THREAD, "DBGDIDR=0x%8.8x (number BRP pairs = %u)", register_DBGDIDR, numBRPs); + + if (numBRPs > 0) + { + uint32_t cpu_subtype; + size_t len; + len = sizeof(cpusubtype); + // TODO: remove this hack and change to using hw.optional.xx when implmented + if (::sysctlbyname("hw.cpusubtype", &cpusubtype, &len, NULL, 0) == 0) + { + ProcessMacOSXLog::LogIf(PD_LOG_THREAD, "hw.cpusubtype=0x%d", cpusubtype); + if (cpusubtype == CPU_SUBTYPE_ARM_V7) + ProcessMacOSXLog::LogIf(PD_LOG_THREAD, "Hardware breakpoints disabled for armv7 (rdar://problem/6372672)"); + else + g_num_supported_hw_breakpoints = numBRPs; + } + } + + } + return g_num_supported_hw_breakpoints; +#else + // TODO: figure out remote case here! + return 6; +#endif +} + +uint32_t +RegisterContextMach_arm::SetHardwareBreakpoint (lldb::addr_t addr, size_t size) +{ + // Make sure our address isn't bogus + if (addr & 1) + return LLDB_INVALID_INDEX32; + + kern_return_t kret = ReadDBG (false); + + if (kret == KERN_SUCCESS) + { + const uint32_t num_hw_breakpoints = NumSupportedHardwareBreakpoints(); + uint32_t i; + for (i=0; i<num_hw_breakpoints; ++i) + { + if ((dbg.bcr[i] & BCR_ENABLE) == 0) + break; // We found an available hw breakpoint slot (in i) + } + + // See if we found an available hw breakpoint slot above + if (i < num_hw_breakpoints) + { + // Make sure bits 1:0 are clear in our address + dbg.bvr[i] = addr & ~((lldb::addr_t)3); + + if (size == 2 || addr & 2) + { + uint32_t byte_addr_select = (addr & 2) ? BAS_IMVA_2_3 : BAS_IMVA_0_1; + + // We have a thumb breakpoint + // We have an ARM breakpoint + dbg.bcr[i] = BCR_M_IMVA_MATCH | // Stop on address mismatch + byte_addr_select | // Set the correct byte address select so we only trigger on the correct opcode + S_USER | // Which modes should this breakpoint stop in? + BCR_ENABLE; // Enable this hardware breakpoint + ProcessMacOSXLog::LogIf(PD_LOG_BREAKPOINTS, "RegisterContextMach_arm::EnableHardwareBreakpoint( addr = %8.8p, size = %u ) - BVR%u/BCR%u = 0x%8.8x / 0x%8.8x (Thumb)", + addr, + size, + i, + i, + dbg.bvr[i], + dbg.bcr[i]); + } + else if (size == 4) + { + // We have an ARM breakpoint + dbg.bcr[i] = BCR_M_IMVA_MATCH | // Stop on address mismatch + BAS_IMVA_ALL | // Stop on any of the four bytes following the IMVA + S_USER | // Which modes should this breakpoint stop in? + BCR_ENABLE; // Enable this hardware breakpoint + ProcessMacOSXLog::LogIf(PD_LOG_BREAKPOINTS, "RegisterContextMach_arm::EnableHardwareBreakpoint( addr = %8.8p, size = %u ) - BVR%u/BCR%u = 0x%8.8x / 0x%8.8x (ARM)", + addr, + size, + i, + i, + dbg.bvr[i], + dbg.bcr[i]); + } + + kret = WriteDBG(); + ProcessMacOSXLog::LogIf(PD_LOG_BREAKPOINTS, "RegisterContextMach_arm::EnableHardwareBreakpoint() WriteDBG() => 0x%8.8x.", kret); + + if (kret == KERN_SUCCESS) + return i; + } + else + { + ProcessMacOSXLog::LogIf(PD_LOG_BREAKPOINTS, "RegisterContextMach_arm::EnableHardwareBreakpoint(addr = %8.8p, size = %u) => all hardware breakpoint resources are being used.", addr, size); + } + } + + return LLDB_INVALID_INDEX32; +} + +bool +RegisterContextMach_arm::ClearHardwareBreakpoint (uint32_t hw_index) +{ + kern_return_t kret = ReadDBG (false); + + const uint32_t num_hw_points = NumSupportedHardwareBreakpoints(); + if (kret == KERN_SUCCESS) + { + if (hw_index < num_hw_points) + { + dbg.bcr[hw_index] = 0; + ProcessMacOSXLog::LogIf(PD_LOG_BREAKPOINTS, "RegisterContextMach_arm::SetHardwareBreakpoint( %u ) - BVR%u = 0x%8.8x BCR%u = 0x%8.8x", + hw_index, + hw_index, + dbg.bvr[hw_index], + hw_index, + dbg.bcr[hw_index]); + + kret = WriteDBG(); + + if (kret == KERN_SUCCESS) + return true; + } + } + return false; +} + +uint32_t +RegisterContextMach_arm::NumSupportedHardwareWatchpoints () +{ +#if defined (__arm__) + // Set the init value to something that will let us know that we need to + // autodetect how many watchpoints are supported dynamically... + static uint32_t g_num_supported_hw_watchpoints = UINT_MAX; + if (g_num_supported_hw_watchpoints == UINT_MAX) + { + // Set this to zero in case we can't tell if there are any HW breakpoints + g_num_supported_hw_watchpoints = 0; + // Read the DBGDIDR to get the number of available hardware breakpoints + // However, in some of our current armv7 processors, hardware + // breakpoints/watchpoints were not properly connected. So detect those + // cases using a field in a sysctl. For now we are using "hw.cpusubtype" + // field to distinguish CPU architectures. This is a hack until we can + // get <rdar://problem/6372672> fixed, at which point we will switch to + // using a different sysctl string that will tell us how many WRPs + // are available to us directly without having to read DBGDIDR. + + uint32_t register_DBGDIDR; + asm("mrc p14, 0, %0, c0, c0, 0" : "=r" (register_DBGDIDR)); + uint32_t numWRPs = bits(register_DBGDIDR, 31, 28) + 1; + ProcessMacOSXLog::LogIf(PD_LOG_THREAD, "DBGDIDR=0x%8.8x (number WRP pairs = %u)", register_DBGDIDR, numWRPs); + + if (numWRPs > 0) + { + uint32_t cpusubtype; + size_t len; + len = sizeof(cpusubtype); + // TODO: remove this hack and change to using hw.optional.xx when implmented + if (::sysctlbyname("hw.cpusubtype", &cpusubtype, &len, NULL, 0) == 0) + { + ProcessMacOSXLog::LogIf(PD_LOG_THREAD, "hw.cpusubtype=0x%d", cpusubtype); + + if (cpusubtype == CPU_SUBTYPE_ARM_V7) + ProcessMacOSXLog::LogIf(PD_LOG_THREAD, "Hardware watchpoints disabled for armv7 (rdar://problem/6372672)"); + else + g_num_supported_hw_watchpoints = numWRPs; + } + } + + } + return g_num_supported_hw_watchpoints; +#else + // TODO: figure out remote case here! + return 2; +#endif +} + + +uint32_t +RegisterContextMach_arm::SetHardwareWatchpoint (lldb::addr_t addr, size_t size, bool read, bool write) +{ + ProcessMacOSXLog::LogIf(PD_LOG_WATCHPOINTS, "RegisterContextMach_arm::EnableHardwareWatchpoint(addr = %8.8p, size = %u, read = %u, write = %u)", addr, size, read, write); + + const uint32_t num_hw_watchpoints = NumSupportedHardwareWatchpoints(); + + // Can't watch zero bytes + if (size == 0) + return LLDB_INVALID_INDEX32; + + // We must watch for either read or write + if (read == false && write == false) + return LLDB_INVALID_INDEX32; + + // Can't watch more than 4 bytes per WVR/WCR pair + if (size > 4) + return LLDB_INVALID_INDEX32; + + // We can only watch up to four bytes that follow a 4 byte aligned address + // per watchpoint register pair. Since we have at most so we can only watch + // until the next 4 byte boundary and we need to make sure we can properly + // encode this. + uint32_t addr_word_offset = addr % 4; + ProcessMacOSXLog::LogIf(PD_LOG_WATCHPOINTS, "RegisterContextMach_arm::EnableHardwareWatchpoint() - addr_word_offset = 0x%8.8x", addr_word_offset); + + uint32_t byte_mask = ((1u << size) - 1u) << addr_word_offset; + ProcessMacOSXLog::LogIf(PD_LOG_WATCHPOINTS, "RegisterContextMach_arm::EnableHardwareWatchpoint() - byte_mask = 0x%8.8x", byte_mask); + if (byte_mask > 0xfu) + return LLDB_INVALID_INDEX32; + + // Read the debug state + kern_return_t kret = ReadDBG (false); + + if (kret == KERN_SUCCESS) + { + // Check to make sure we have the needed hardware support + uint32_t i = 0; + + for (i=0; i<num_hw_watchpoints; ++i) + { + if ((dbg.wcr[i] & WCR_ENABLE) == 0) + break; // We found an available hw breakpoint slot (in i) + } + + // See if we found an available hw breakpoint slot above + if (i < num_hw_watchpoints) + { + // Make the byte_mask into a valid Byte Address Select mask + uint32_t byte_address_select = byte_mask << 5; + // Make sure bits 1:0 are clear in our address + dbg.wvr[i] = addr & ~((lldb::addr_t)3); + dbg.wcr[i] = byte_address_select | // Which bytes that follow the IMVA that we will watch + S_USER | // Stop only in user mode + (read ? WCR_LOAD : 0) | // Stop on read access? + (write ? WCR_STORE : 0) | // Stop on write access? + WCR_ENABLE; // Enable this watchpoint; + + kret = WriteDBG(); + ProcessMacOSXLog::LogIf(PD_LOG_WATCHPOINTS, "RegisterContextMach_arm::EnableHardwareWatchpoint() WriteDBG() => 0x%8.8x.", kret); + + if (kret == KERN_SUCCESS) + return i; + } + else + { + ProcessMacOSXLog::LogIf(PD_LOG_WATCHPOINTS, "RegisterContextMach_arm::EnableHardwareWatchpoint(): All hardware resources (%u) are in use.", num_hw_watchpoints); + } + } + return LLDB_INVALID_INDEX32; +} + +bool +RegisterContextMach_arm::ClearHardwareWatchpoint (uint32_t hw_index) +{ + kern_return_t kret = ReadDBG (false); + + const uint32_t num_hw_points = NumSupportedHardwareWatchpoints(); + if (kret == KERN_SUCCESS) + { + if (hw_index < num_hw_points) + { + dbg.wcr[hw_index] = 0; + ProcessMacOSXLog::LogIf(PD_LOG_WATCHPOINTS, "RegisterContextMach_arm::ClearHardwareWatchpoint( %u ) - WVR%u = 0x%8.8x WCR%u = 0x%8.8x", + hw_index, + hw_index, + dbg.wvr[hw_index], + hw_index, + dbg.wcr[hw_index]); + + kret = WriteDBG(); + + if (kret == KERN_SUCCESS) + return true; + } + } + return false; +} + + diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_arm.h b/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_arm.h new file mode 100644 index 00000000000..37821cdd536 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_arm.h @@ -0,0 +1,302 @@ +//===-- RegisterContextMach_arm.h -------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_RegisterContextMach_arm_h_ +#define liblldb_RegisterContextMach_arm_h_ + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "lldb/lldb-private.h" +#include "lldb/Target/RegisterContext.h" + +// BCR address match type +#define BCR_M_IMVA_MATCH ((uint32_t)(0u << 21)) +#define BCR_M_CONTEXT_ID_MATCH ((uint32_t)(1u << 21)) +#define BCR_M_IMVA_MISMATCH ((uint32_t)(2u << 21)) +#define BCR_M_RESERVED ((uint32_t)(3u << 21)) + +// Link a BVR/BCR or WVR/WCR pair to another +#define E_ENABLE_LINKING ((uint32_t)(1u << 20)) + +// Byte Address Select +#define BAS_IMVA_PLUS_0 ((uint32_t)(1u << 5)) +#define BAS_IMVA_PLUS_1 ((uint32_t)(1u << 6)) +#define BAS_IMVA_PLUS_2 ((uint32_t)(1u << 7)) +#define BAS_IMVA_PLUS_3 ((uint32_t)(1u << 8)) +#define BAS_IMVA_0_1 ((uint32_t)(3u << 5)) +#define BAS_IMVA_2_3 ((uint32_t)(3u << 7)) +#define BAS_IMVA_ALL ((uint32_t)(0xfu << 5)) + +// Break only in priveleged or user mode +#define S_RSVD ((uint32_t)(0u << 1)) +#define S_PRIV ((uint32_t)(1u << 1)) +#define S_USER ((uint32_t)(2u << 1)) +#define S_PRIV_USER ((S_PRIV) | (S_USER)) + +#define BCR_ENABLE ((uint32_t)(1u)) +#define WCR_ENABLE ((uint32_t)(1u)) + +// Watchpoint load/store +#define WCR_LOAD ((uint32_t)(1u << 3)) +#define WCR_STORE ((uint32_t)(1u << 4)) + +class RegisterContextMach_arm : public lldb_private::RegisterContext +{ +public: + + RegisterContextMach_arm(lldb_private::Thread &thread, lldb_private::StackFrame *frame); + + virtual + ~RegisterContextMach_arm(); + + virtual void + Invalidate (); + + virtual size_t + GetRegisterCount (); + + virtual const lldb::RegisterInfo * + GetRegisterInfoAtIndex (uint32_t reg); + + virtual size_t + GetRegisterSetCount (); + + virtual const lldb::RegisterSet * + GetRegisterSet (uint32_t set); + + virtual bool + ReadRegisterValue (uint32_t reg, lldb_private::Scalar &value); + + virtual bool + ReadRegisterBytes (uint32_t reg, lldb_private::DataExtractor &data); + + virtual bool + ReadAllRegisterValues (lldb::DataBufferSP &data_sp); + + virtual bool + WriteRegisterValue (uint32_t reg, const lldb_private::Scalar &value); + + virtual bool + WriteRegisterBytes (uint32_t reg, lldb_private::DataExtractor &data, uint32_t data_offset = 0); + + virtual bool + WriteAllRegisterValues (const lldb::DataBufferSP &data_sp); + + virtual uint32_t + ConvertRegisterKindToRegisterNumber (uint32_t kind, uint32_t num); + + virtual uint32_t + NumSupportedHardwareBreakpoints (); + + virtual uint32_t + SetHardwareBreakpoint (lldb::addr_t addr, size_t size); + + virtual bool + ClearHardwareBreakpoint (uint32_t hw_idx); + + virtual uint32_t + NumSupportedHardwareWatchpoints (); + + virtual uint32_t + SetHardwareWatchpoint (lldb::addr_t addr, size_t size, bool read, bool write); + + virtual bool + ClearHardwareWatchpoint (uint32_t hw_index); + + struct GPR + { + uint32_t r[16]; // R0-R15 + uint32_t cpsr; // CPSR + }; + + + struct FPU + { + union { + uint32_t s[32]; + uint64_t d[16]; + } floats; + uint32_t fpscr; + }; + +// struct NeonReg +// { +// uint8_t bytes[16]; +// }; +// +// struct VFPv3 +// { +// union { +// uint32_t s[32]; +// uint64_t d[32]; +// NeonReg q[16]; +// } v3; +// uint32_t fpscr; +// }; + + struct EXC + { + uint32_t exception; + uint32_t fsr; /* Fault status */ + uint32_t far; /* Virtual Fault Address */ + }; + + struct DBG + { + uint32_t bvr[16]; + uint32_t bcr[16]; + uint32_t wvr[16]; + uint32_t wcr[16]; + }; + + static void + LogDBGRegisters (lldb_private::Log *log, const DBG& dbg); + +protected: + + typedef enum + { + GPRRegSet = 1, + FPURegSet = 2, + EXCRegSet = 3, + DBGRegSet = 4, + }; + + enum + { + GPRWordCount = sizeof(GPR)/sizeof(uint32_t), + FPUWordCount = sizeof(FPU)/sizeof(uint32_t), + EXCWordCount = sizeof(EXC)/sizeof(uint32_t), + DBGWordCount = sizeof(DBG)/sizeof(uint32_t) + }; + + enum + { + Read = 0, + Write = 1, + kNumErrors = 2 + }; + + GPR gpr; + FPU fpu; + EXC exc; + DBG dbg; + kern_return_t gpr_errs[2]; // Read/Write errors + kern_return_t fpu_errs[2]; // Read/Write errors + kern_return_t exc_errs[2]; // Read/Write errors + kern_return_t dbg_errs[2]; // Read/Write errors + + void + InvalidateAllRegisterStates() + { + SetError (GPRRegSet, Read, -1); + SetError (FPURegSet, Read, -1); + SetError (EXCRegSet, Read, -1); + } + + kern_return_t + GetError (int flavor, uint32_t err_idx) const + { + if (err_idx < kNumErrors) + { + switch (flavor) + { + // When getting all errors, just OR all values together to see if + // we got any kind of error. + case GPRRegSet: return gpr_errs[err_idx]; + case FPURegSet: return fpu_errs[err_idx]; + case EXCRegSet: return exc_errs[err_idx]; + case DBGRegSet: return dbg_errs[err_idx]; + default: break; + } + } + return -1; + } + + bool + SetError (int flavor, uint32_t err_idx, kern_return_t err) + { + if (err_idx < kNumErrors) + { + switch (flavor) + { + case GPRRegSet: + gpr_errs[err_idx] = err; + return true; + + case FPURegSet: + fpu_errs[err_idx] = err; + return true; + + case EXCRegSet: + exc_errs[err_idx] = err; + return true; + + case DBGRegSet: + exc_errs[err_idx] = err; + return true; + + default: break; + } + } + return false; + } + + bool + RegisterSetIsCached (int set) const + { + return GetError(set, Read) == KERN_SUCCESS; + } + + kern_return_t + ReadGPR (bool force); + + kern_return_t + ReadFPU (bool force); + + kern_return_t + ReadEXC (bool force); + + kern_return_t + ReadDBG (bool force); + + kern_return_t + WriteGPR (); + + kern_return_t + WriteFPU (); + + kern_return_t + WriteEXC (); + + kern_return_t + WriteDBG (); + + kern_return_t + ReadRegisterSet (uint32_t set, bool force); + + kern_return_t + WriteRegisterSet (uint32_t set); + + static uint32_t + GetRegisterNumber (uint32_t reg_kind, uint32_t reg_num); + + static int + GetSetForNativeRegNum (int reg_num); + + static size_t + GetRegisterInfosCount (); + + static const lldb::RegisterInfo * + GetRegisterInfos (); +}; + +#endif // liblldb_RegisterContextMach_arm_h_ diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_i386.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_i386.cpp new file mode 100644 index 00000000000..daa4f0d4916 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_i386.cpp @@ -0,0 +1,1202 @@ +//===-- RegisterContextMach_i386.cpp ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + + +// C Includes +#include <mach/thread_act.h> + +// C++ Includes +// Other libraries and framework includes +#include "lldb/Core/DataBufferHeap.h" +#include "lldb/Core/DataExtractor.h" +#include "lldb/Core/Scalar.h" + +// Project includes +#include "RegisterContextMach_i386.h" +#include "ProcessMacOSXLog.h" + +using namespace lldb; +using namespace lldb_private; + +enum +{ + gpr_eax = 0, + gpr_ebx, + gpr_ecx, + gpr_edx, + gpr_edi, + gpr_esi, + gpr_ebp, + gpr_esp, + gpr_ss, + gpr_eflags, + gpr_eip, + gpr_cs, + gpr_ds, + gpr_es, + gpr_fs, + gpr_gs, + + fpu_fcw, + fpu_fsw, + fpu_ftw, + fpu_fop, + fpu_ip, + fpu_cs, + fpu_dp, + fpu_ds, + fpu_mxcsr, + fpu_mxcsrmask, + fpu_stmm0, + fpu_stmm1, + fpu_stmm2, + fpu_stmm3, + fpu_stmm4, + fpu_stmm5, + fpu_stmm6, + fpu_stmm7, + fpu_xmm0, + fpu_xmm1, + fpu_xmm2, + fpu_xmm3, + fpu_xmm4, + fpu_xmm5, + fpu_xmm6, + fpu_xmm7, + + exc_trapno, + exc_err, + exc_faultvaddr, + + k_num_registers, + + // Aliases + fpu_fctrl = fpu_fcw, + fpu_fstat = fpu_fsw, + fpu_ftag = fpu_ftw, + fpu_fiseg = fpu_cs, + fpu_fioff = fpu_ip, + fpu_foseg = fpu_ds, + fpu_fooff = fpu_dp +}; + +enum +{ + gcc_eax = 0, + gcc_ecx, + gcc_edx, + gcc_ebx, + gcc_ebp, + gcc_esp, + gcc_esi, + gcc_edi, + gcc_eip, + gcc_eflags +}; + +enum +{ + dwarf_eax = 0, + dwarf_ecx, + dwarf_edx, + dwarf_ebx, + dwarf_esp, + dwarf_ebp, + dwarf_esi, + dwarf_edi, + dwarf_eip, + dwarf_eflags, + dwarf_stmm0 = 11, + dwarf_stmm1, + dwarf_stmm2, + dwarf_stmm3, + dwarf_stmm4, + dwarf_stmm5, + dwarf_stmm6, + dwarf_stmm7, + dwarf_xmm0 = 21, + dwarf_xmm1, + dwarf_xmm2, + dwarf_xmm3, + dwarf_xmm4, + dwarf_xmm5, + dwarf_xmm6, + dwarf_xmm7 +}; + +enum +{ + gdb_eax = 0, + gdb_ecx = 1, + gdb_edx = 2, + gdb_ebx = 3, + gdb_esp = 4, + gdb_ebp = 5, + gdb_esi = 6, + gdb_edi = 7, + gdb_eip = 8, + gdb_eflags = 9, + gdb_cs = 10, + gdb_ss = 11, + gdb_ds = 12, + gdb_es = 13, + gdb_fs = 14, + gdb_gs = 15, + gdb_stmm0 = 16, + gdb_stmm1 = 17, + gdb_stmm2 = 18, + gdb_stmm3 = 19, + gdb_stmm4 = 20, + gdb_stmm5 = 21, + gdb_stmm6 = 22, + gdb_stmm7 = 23, + gdb_fctrl = 24, gdb_fcw = gdb_fctrl, + gdb_fstat = 25, gdb_fsw = gdb_fstat, + gdb_ftag = 26, gdb_ftw = gdb_ftag, + gdb_fiseg = 27, gdb_fpu_cs = gdb_fiseg, + gdb_fioff = 28, gdb_ip = gdb_fioff, + gdb_foseg = 29, gdb_fpu_ds = gdb_foseg, + gdb_fooff = 30, gdb_dp = gdb_fooff, + gdb_fop = 31, + gdb_xmm0 = 32, + gdb_xmm1 = 33, + gdb_xmm2 = 34, + gdb_xmm3 = 35, + gdb_xmm4 = 36, + gdb_xmm5 = 37, + gdb_xmm6 = 38, + gdb_xmm7 = 39, + gdb_mxcsr = 40, + gdb_mm0 = 41, + gdb_mm1 = 42, + gdb_mm2 = 43, + gdb_mm3 = 44, + gdb_mm4 = 45, + gdb_mm5 = 46, + gdb_mm6 = 47, + gdb_mm7 = 48 +}; + +RegisterContextMach_i386::RegisterContextMach_i386 (Thread &thread, StackFrame *frame) : + RegisterContext(thread, frame), + gpr(), + fpu(), + exc() +{ + uint32_t i; + for (i=0; i<kNumErrors; i++) + { + gpr_errs[i] = -1; + fpu_errs[i] = -1; + exc_errs[i] = -1; + } +} + +RegisterContextMach_i386::~RegisterContextMach_i386() +{ +} + + + +#define GPR_OFFSET(reg) (offsetof (RegisterContextMach_i386::GPR, reg)) +#define FPU_OFFSET(reg) (offsetof (RegisterContextMach_i386::FPU, reg) + sizeof (RegisterContextMach_i386::GPR)) +#define EXC_OFFSET(reg) (offsetof (RegisterContextMach_i386::EXC, reg) + sizeof (RegisterContextMach_i386::GPR) + sizeof (RegisterContextMach_i386::FPU)) + +// These macros will auto define the register name, alt name, register size, +// register offset, encoding, format and native register. This ensures that +// the register state structures are defined correctly and have the correct +// sizes and offsets. +#define DEFINE_GPR(reg, alt) #reg, alt, sizeof(((RegisterContextMach_i386::GPR *)NULL)->reg), GPR_OFFSET(reg), eEncodingUint, eFormatHex, gpr_##reg +#define DEFINE_FPU_UINT(reg) #reg, NULL, sizeof(((RegisterContextMach_i386::FPU *)NULL)->reg), FPU_OFFSET(reg), eEncodingUint, eFormatHex, fpu_##reg +#define DEFINE_FPU_VECT(reg, i) #reg#i, NULL, sizeof(((RegisterContextMach_i386::FPU *)NULL)->reg[i].bytes), FPU_OFFSET(reg[i]), eEncodingVector, eFormatVectorOfUInt8, fpu_##reg##i, { LLDB_INVALID_REGNUM, dwarf_##reg##i, LLDB_INVALID_REGNUM, gdb_##reg##i } + +#define DEFINE_EXC(reg) #reg, NULL, sizeof(((RegisterContextMach_i386::EXC *)NULL)->reg), EXC_OFFSET(reg), eEncodingUint, eFormatHex, exc_##reg +#define REG_CONTEXT_SIZE (sizeof (RegisterContextMach_i386::GPR) + sizeof (RegisterContextMach_i386::FPU) + sizeof (RegisterContextMach_i386::EXC)) + +static RegisterInfo g_register_infos[] = +{ +// Macro auto defines most stuff GCC REG KIND NUM DWARF REG KIND NUM GENERIC REG KIND NUM GDB REG KIND NUM +// =============================== ======================= =================== ========================== ========================== + { DEFINE_GPR(eax , NULL) , { gcc_eax , dwarf_eax , LLDB_INVALID_REGNUM , gdb_eax }}, + { DEFINE_GPR(ebx , NULL) , { gcc_ebx , dwarf_ebx , LLDB_INVALID_REGNUM , gdb_ebx }}, + { DEFINE_GPR(ecx , NULL) , { gcc_ecx , dwarf_ecx , LLDB_INVALID_REGNUM , gdb_ecx }}, + { DEFINE_GPR(edx , NULL) , { gcc_edx , dwarf_edx , LLDB_INVALID_REGNUM , gdb_edx }}, + { DEFINE_GPR(edi , NULL) , { gcc_edi , dwarf_edi , LLDB_INVALID_REGNUM , gdb_edi }}, + { DEFINE_GPR(esi , NULL) , { gcc_esi , dwarf_esi , LLDB_INVALID_REGNUM , gdb_esi }}, + { DEFINE_GPR(ebp , "fp") , { gcc_ebp , dwarf_ebp , LLDB_REGNUM_GENERIC_FP , gdb_ebp }}, + { DEFINE_GPR(esp , "sp") , { gcc_esp , dwarf_esp , LLDB_REGNUM_GENERIC_SP , gdb_esp }}, + { DEFINE_GPR(ss , NULL) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_ss }}, + { DEFINE_GPR(eflags , "flags") , { gcc_eflags , dwarf_eflags , LLDB_REGNUM_GENERIC_FLAGS , gdb_eflags }}, + { DEFINE_GPR(eip , "pc") , { gcc_eip , dwarf_eip , LLDB_REGNUM_GENERIC_PC , gdb_eip }}, + { DEFINE_GPR(cs , NULL) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_cs }}, + { DEFINE_GPR(ds , NULL) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_ds }}, + { DEFINE_GPR(es , NULL) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_es }}, + { DEFINE_GPR(fs , NULL) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_fs }}, + { DEFINE_GPR(gs , NULL) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_gs }}, + + { DEFINE_FPU_UINT(fcw) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_fcw }}, + { DEFINE_FPU_UINT(fsw) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_fsw }}, + { DEFINE_FPU_UINT(ftw) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_ftw }}, + { DEFINE_FPU_UINT(fop) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_fop }}, + { DEFINE_FPU_UINT(ip) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_ip }}, + { DEFINE_FPU_UINT(cs) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_cs }}, + { DEFINE_FPU_UINT(dp) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_dp }}, + { DEFINE_FPU_UINT(ds) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_ds }}, + { DEFINE_FPU_UINT(mxcsr) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_mxcsr }}, + { DEFINE_FPU_UINT(mxcsrmask) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM }}, + { DEFINE_FPU_VECT(stmm,0) }, + { DEFINE_FPU_VECT(stmm,1) }, + { DEFINE_FPU_VECT(stmm,2) }, + { DEFINE_FPU_VECT(stmm,3) }, + { DEFINE_FPU_VECT(stmm,4) }, + { DEFINE_FPU_VECT(stmm,5) }, + { DEFINE_FPU_VECT(stmm,6) }, + { DEFINE_FPU_VECT(stmm,7) }, + { DEFINE_FPU_VECT(xmm,0) }, + { DEFINE_FPU_VECT(xmm,1) }, + { DEFINE_FPU_VECT(xmm,2) }, + { DEFINE_FPU_VECT(xmm,3) }, + { DEFINE_FPU_VECT(xmm,4) }, + { DEFINE_FPU_VECT(xmm,5) }, + { DEFINE_FPU_VECT(xmm,6) }, + { DEFINE_FPU_VECT(xmm,7) }, + + { DEFINE_EXC(trapno) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM }}, + { DEFINE_EXC(err) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM }}, + { DEFINE_EXC(faultvaddr) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM }} +}; + +static size_t k_num_register_infos = (sizeof(g_register_infos)/sizeof(RegisterInfo)); + +void +RegisterContextMach_i386::Invalidate () +{ + InvalidateAllRegisterStates(); +} + + +size_t +RegisterContextMach_i386::GetRegisterCount () +{ + assert(k_num_register_infos == k_num_registers); + return k_num_registers; +} + +const RegisterInfo * +RegisterContextMach_i386::GetRegisterInfoAtIndex (uint32_t reg) +{ + assert(k_num_register_infos == k_num_registers); + if (reg < k_num_registers) + return &g_register_infos[reg]; + return NULL; +} + +size_t +RegisterContextMach_i386::GetRegisterInfosCount () +{ + return k_num_register_infos; +} + +const RegisterInfo * +RegisterContextMach_i386::GetRegisterInfos () +{ + return g_register_infos; +} + + +// General purpose registers +static uint32_t +g_gpr_regnums[] = +{ + gpr_eax, + gpr_ebx, + gpr_ecx, + gpr_edx, + gpr_edi, + gpr_esi, + gpr_ebp, + gpr_esp, + gpr_ss, + gpr_eflags, + gpr_eip, + gpr_cs, + gpr_ds, + gpr_es, + gpr_fs, + gpr_gs +}; + +// Floating point registers +static uint32_t +g_fpu_regnums[] = +{ + fpu_fcw, + fpu_fsw, + fpu_ftw, + fpu_fop, + fpu_ip, + fpu_cs, + fpu_dp, + fpu_ds, + fpu_mxcsr, + fpu_mxcsrmask, + fpu_stmm0, + fpu_stmm1, + fpu_stmm2, + fpu_stmm3, + fpu_stmm4, + fpu_stmm5, + fpu_stmm6, + fpu_stmm7, + fpu_xmm0, + fpu_xmm1, + fpu_xmm2, + fpu_xmm3, + fpu_xmm4, + fpu_xmm5, + fpu_xmm6, + fpu_xmm7 +}; + +// Exception registers + +static uint32_t +g_exc_regnums[] = +{ + exc_trapno, + exc_err, + exc_faultvaddr +}; + +// Number of registers in each register set +const size_t k_num_gpr_registers = sizeof(g_gpr_regnums) / sizeof(uint32_t); +const size_t k_num_fpu_registers = sizeof(g_fpu_regnums) / sizeof(uint32_t); +const size_t k_num_exc_registers = sizeof(g_exc_regnums) / sizeof(uint32_t); + +//---------------------------------------------------------------------- +// Register set definitions. The first definitions at register set index +// of zero is for all registers, followed by other registers sets. The +// register information for the all register set need not be filled in. +//---------------------------------------------------------------------- +static const RegisterSet g_reg_sets[] = +{ + { "General Purpose Registers", "gpr", k_num_gpr_registers, g_gpr_regnums, }, + { "Floating Point Registers", "fpu", k_num_fpu_registers, g_fpu_regnums }, + { "Exception State Registers", "exc", k_num_exc_registers, g_exc_regnums } +}; + +const size_t k_num_regsets = sizeof(g_reg_sets) / sizeof(RegisterSet); + + +size_t +RegisterContextMach_i386::GetRegisterSetCount () +{ + return k_num_regsets; +} + +const RegisterSet * +RegisterContextMach_i386::GetRegisterSet (uint32_t reg_set) +{ + if (reg_set < k_num_regsets) + return &g_reg_sets[reg_set]; + return NULL; +} + + +//---------------------------------------------------------------------- +// Register information defintions for 32 bit i386. +//---------------------------------------------------------------------- +int +RegisterContextMach_i386::GetSetForNativeRegNum (int reg_num) +{ + if (reg_num < fpu_fcw) + return GPRRegSet; + else if (reg_num < exc_trapno) + return FPURegSet; + else if (reg_num < k_num_registers) + return EXCRegSet; + return -1; +} + + +void +RegisterContextMach_i386::LogGPR(Log *log, const char *title) +{ + if (log) + { + if (title) + log->Printf ("%s", title); + for (uint32_t i=0; i<k_num_gpr_registers; i++) + { + uint32_t reg = gpr_eax + i; + log->Printf("%12s = 0x%8.8x", g_register_infos[reg].name, (&gpr.eax)[reg]); + } + } +} + + + +kern_return_t +RegisterContextMach_i386::ReadGPR (bool force) +{ + int set = GPRRegSet; + if (force || !RegisterSetIsCached(set)) + { + mach_msg_type_number_t count = GPRWordCount; + SetError(set, Read, ::thread_get_state(GetThreadID(), set, (thread_state_t)&gpr, &count)); + LogGPR (ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_THREAD), "RegisterContextMach_i386::ReadGPR()"); + } + return GetError(set, Read); +} + +kern_return_t +RegisterContextMach_i386::ReadFPU (bool force) +{ + int set = FPURegSet; + if (force || !RegisterSetIsCached(set)) + { + mach_msg_type_number_t count = FPUWordCount; + SetError(set, Read, ::thread_get_state(GetThreadID(), set, (thread_state_t)&fpu, &count)); + } + return GetError(set, Read); +} + +kern_return_t +RegisterContextMach_i386::ReadEXC (bool force) +{ + int set = EXCRegSet; + if (force || !RegisterSetIsCached(set)) + { + mach_msg_type_number_t count = EXCWordCount; + SetError(set, Read, ::thread_get_state(GetThreadID(), set, (thread_state_t)&exc, &count)); + } + return GetError(set, Read); +} + +kern_return_t +RegisterContextMach_i386::WriteGPR () +{ + int set = GPRRegSet; + if (!RegisterSetIsCached(set)) + { + SetError (set, Write, -1); + return KERN_INVALID_ARGUMENT; + } + SetError (set, Write, ::thread_set_state(GetThreadID(), set, (thread_state_t)&gpr, GPRWordCount)); + SetError (set, Read, -1); + return GetError(set, Write); +} + +kern_return_t +RegisterContextMach_i386::WriteFPU () +{ + int set = FPURegSet; + if (!RegisterSetIsCached(set)) + { + SetError (set, Write, -1); + return KERN_INVALID_ARGUMENT; + } + SetError (set, Write, ::thread_set_state(GetThreadID(), set, (thread_state_t)&fpu, FPUWordCount)); + SetError (set, Read, -1); + return GetError(set, Write); +} + +kern_return_t +RegisterContextMach_i386::WriteEXC () +{ + int set = EXCRegSet; + if (!RegisterSetIsCached(set)) + { + SetError (set, Write, -1); + return KERN_INVALID_ARGUMENT; + } + SetError (set, Write, ::thread_set_state(GetThreadID(), set, (thread_state_t)&exc, EXCWordCount)); + SetError (set, Read, -1); + return GetError(set, Write); +} + +kern_return_t +RegisterContextMach_i386::ReadRegisterSet (uint32_t set, bool force) +{ + switch (set) + { + case GPRRegSet: return ReadGPR(force); + case FPURegSet: return ReadFPU(force); + case EXCRegSet: return ReadEXC(force); + default: break; + } + return KERN_INVALID_ARGUMENT; +} + +kern_return_t +RegisterContextMach_i386::WriteRegisterSet (uint32_t set) +{ + // Make sure we have a valid context to set. + if (RegisterSetIsCached(set)) + { + switch (set) + { + case GPRRegSet: return WriteGPR(); + case FPURegSet: return WriteFPU(); + case EXCRegSet: return WriteEXC(); + default: break; + } + } + return KERN_INVALID_ARGUMENT; +} + +bool +RegisterContextMach_i386::ReadRegisterValue (uint32_t reg, Scalar &value) +{ + int set = RegisterContextMach_i386::GetSetForNativeRegNum (reg); + + if (set == -1) + return false; + + if (ReadRegisterSet(set, false) != KERN_SUCCESS) + return false; + + switch (reg) + { + case gpr_eax: + case gpr_ebx: + case gpr_ecx: + case gpr_edx: + case gpr_edi: + case gpr_esi: + case gpr_ebp: + case gpr_esp: + case gpr_ss: + case gpr_eflags: + case gpr_eip: + case gpr_cs: + case gpr_ds: + case gpr_es: + case gpr_fs: + case gpr_gs: + value = (&gpr.eax)[reg - gpr_eax]; + break; + + case fpu_fcw: + value = fpu.fcw; + break; + + case fpu_fsw: + value = fpu.fsw; + break; + + case fpu_ftw: + value = fpu.ftw; + break; + + case fpu_fop: + value = fpu.fop; + break; + + case fpu_ip: + value = fpu.ip; + break; + + case fpu_cs: + value = fpu.cs; + break; + + case fpu_dp: + value = fpu.dp; + break; + + case fpu_ds: + value = fpu.ds; + break; + + case fpu_mxcsr: + value = fpu.mxcsr; + break; + + case fpu_mxcsrmask: + value = fpu.mxcsrmask; + break; + + case fpu_stmm0: + case fpu_stmm1: + case fpu_stmm2: + case fpu_stmm3: + case fpu_stmm4: + case fpu_stmm5: + case fpu_stmm6: + case fpu_stmm7: + // These values don't fit into scalar types, + // RegisterContext::ReadRegisterBytes() must be used for these + // registers + //::memcpy (reg_value.value.vector.uint8, fpu.stmm[reg - fpu_stmm0].bytes, 10); + return false; + + case fpu_xmm0: + case fpu_xmm1: + case fpu_xmm2: + case fpu_xmm3: + case fpu_xmm4: + case fpu_xmm5: + case fpu_xmm6: + case fpu_xmm7: + // These values don't fit into scalar types, RegisterContext::ReadRegisterBytes() + // must be used for these registers + //::memcpy (reg_value.value.vector.uint8, fpu.xmm[reg - fpu_xmm0].bytes, 16); + return false; + + case exc_trapno: + value = exc.trapno; + break; + + case exc_err: + value = exc.err; + break; + + case exc_faultvaddr: + value = exc.faultvaddr; + break; + + default: + return false; + } + return true; +} + + +bool +RegisterContextMach_i386::WriteRegisterValue (uint32_t reg, const Scalar &value) +{ + int set = GetSetForNativeRegNum (reg); + + if (set == -1) + return false; + + if (ReadRegisterSet(set, false) != KERN_SUCCESS) + return false; + + switch (reg) + { + case gpr_eax: + case gpr_ebx: + case gpr_ecx: + case gpr_edx: + case gpr_edi: + case gpr_esi: + case gpr_ebp: + case gpr_esp: + case gpr_ss: + case gpr_eflags: + case gpr_eip: + case gpr_cs: + case gpr_ds: + case gpr_es: + case gpr_fs: + case gpr_gs: + (&gpr.eax)[reg - gpr_eax] = value.UInt(0); + break; + + case fpu_fcw: + fpu.fcw = value.UInt(0); + break; + + case fpu_fsw: + fpu.fsw = value.UInt(0); + break; + + case fpu_ftw: + fpu.ftw = value.UInt(0); + break; + + case fpu_fop: + fpu.fop = value.UInt(0); + break; + + case fpu_ip: + fpu.ip = value.UInt(0); + break; + + case fpu_cs: + fpu.cs = value.UInt(0); + break; + + case fpu_dp: + fpu.dp = value.UInt(0); + break; + + case fpu_ds: + fpu.ds = value.UInt(0); + break; + + case fpu_mxcsr: + fpu.mxcsr = value.UInt(0); + break; + + case fpu_mxcsrmask: + fpu.mxcsrmask = value.UInt(0); + break; + + case fpu_stmm0: + case fpu_stmm1: + case fpu_stmm2: + case fpu_stmm3: + case fpu_stmm4: + case fpu_stmm5: + case fpu_stmm6: + case fpu_stmm7: + // These values don't fit into scalar types, RegisterContext::ReadRegisterBytes() + // must be used for these registers + //::memcpy (fpu.stmm[reg - fpu_stmm0].bytes, reg_value.value.vector.uint8, 10); + return false; + + case fpu_xmm0: + case fpu_xmm1: + case fpu_xmm2: + case fpu_xmm3: + case fpu_xmm4: + case fpu_xmm5: + case fpu_xmm6: + case fpu_xmm7: + // These values don't fit into scalar types, RegisterContext::ReadRegisterBytes() + // must be used for these registers + //::memcpy (fpu.xmm[reg - fpu_xmm0].bytes, reg_value.value.vector.uint8, 16); + return false; + + case exc_trapno: + exc.trapno = value.UInt(0); + break; + + case exc_err: + exc.err = value.UInt(0); + break; + + case exc_faultvaddr: + exc.faultvaddr = value.UInt(0); + break; + + default: + return false; + } + return WriteRegisterSet(set) == KERN_SUCCESS; +} + +bool +RegisterContextMach_i386::ReadRegisterBytes (uint32_t reg, DataExtractor &data) +{ + int set = RegisterContextMach_i386::GetSetForNativeRegNum (reg); + if (set == -1) + return false; + + if (ReadRegisterSet(set, false) != KERN_SUCCESS) + return false; + + const RegisterInfo * reg_info = GetRegisterInfoAtIndex (reg); + if (reg_info == NULL) + return false; + + switch (reg) + { + case gpr_eax: + case gpr_ebx: + case gpr_ecx: + case gpr_edx: + case gpr_edi: + case gpr_esi: + case gpr_ebp: + case gpr_esp: + case gpr_ss: + case gpr_eflags: + case gpr_eip: + case gpr_cs: + case gpr_ds: + case gpr_es: + case gpr_fs: + case gpr_gs: + data.SetData(&gpr.eax + reg - gpr_eax, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_fcw: + data.SetData(&fpu.fcw, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_fsw: + data.SetData(&fpu.fsw, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_ftw: + data.SetData(&fpu.ftw, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_fop: + data.SetData(&fpu.fop, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_ip: + data.SetData(&fpu.ip, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_cs: + data.SetData(&fpu.cs, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_dp: + data.SetData(&fpu.dp, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_ds: + data.SetData(&fpu.ds, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_mxcsr: + data.SetData(&fpu.mxcsr, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_mxcsrmask: + data.SetData(&fpu.mxcsrmask, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_stmm0: + case fpu_stmm1: + case fpu_stmm2: + case fpu_stmm3: + case fpu_stmm4: + case fpu_stmm5: + case fpu_stmm6: + case fpu_stmm7: + data.SetData(fpu.stmm[reg - fpu_stmm0].bytes, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_xmm0: + case fpu_xmm1: + case fpu_xmm2: + case fpu_xmm3: + case fpu_xmm4: + case fpu_xmm5: + case fpu_xmm6: + case fpu_xmm7: + data.SetData(fpu.xmm[reg - fpu_xmm0].bytes, reg_info->byte_size, eByteOrderHost); + break; + + case exc_trapno: + data.SetData(&exc.trapno, reg_info->byte_size, eByteOrderHost); + break; + + case exc_err: + data.SetData(&exc.err, reg_info->byte_size, eByteOrderHost); + break; + + case exc_faultvaddr: + data.SetData(&exc.faultvaddr, reg_info->byte_size, eByteOrderHost); + break; + + default: + return false; + } + return true; +} + +bool +RegisterContextMach_i386::WriteRegisterBytes (uint32_t reg, DataExtractor &data, uint32_t data_offset) +{ + int set = GetSetForNativeRegNum (reg); + + if (set == -1) + return false; + + if (ReadRegisterSet(set, false) != KERN_SUCCESS) + return false; + + + const RegisterInfo * reg_info = GetRegisterInfoAtIndex (reg); + if (reg_info == NULL && data.ValidOffsetForDataOfSize(data_offset, reg_info->byte_size)) + return false; + + uint32_t offset = data_offset; + switch (reg) + { + case gpr_eax: + case gpr_ebx: + case gpr_ecx: + case gpr_edx: + case gpr_edi: + case gpr_esi: + case gpr_ebp: + case gpr_esp: + case gpr_ss: + case gpr_eflags: + case gpr_eip: + case gpr_cs: + case gpr_ds: + case gpr_es: + case gpr_fs: + case gpr_gs: + (&gpr.eax)[reg - gpr_eax] = data.GetU32 (&offset); + break; + + case fpu_fcw: + fpu.fcw = data.GetU16(&offset); + break; + + case fpu_fsw: + fpu.fsw = data.GetU16(&offset); + break; + + case fpu_ftw: + fpu.ftw = data.GetU8(&offset); + break; + + case fpu_fop: + fpu.fop = data.GetU16(&offset); + break; + + case fpu_ip: + fpu.ip = data.GetU32(&offset); + break; + + case fpu_cs: + fpu.cs = data.GetU16(&offset); + break; + + case fpu_dp: + fpu.dp = data.GetU32(&offset); + break; + + case fpu_ds: + fpu.ds = data.GetU16(&offset); + break; + + case fpu_mxcsr: + fpu.mxcsr = data.GetU32(&offset); + break; + + case fpu_mxcsrmask: + fpu.mxcsrmask = data.GetU32(&offset); + break; + + case fpu_stmm0: + case fpu_stmm1: + case fpu_stmm2: + case fpu_stmm3: + case fpu_stmm4: + case fpu_stmm5: + case fpu_stmm6: + case fpu_stmm7: + ::memcpy (fpu.stmm[reg - fpu_stmm0].bytes, data.PeekData(offset, reg_info->byte_size), reg_info->byte_size); + return false; + + case fpu_xmm0: + case fpu_xmm1: + case fpu_xmm2: + case fpu_xmm3: + case fpu_xmm4: + case fpu_xmm5: + case fpu_xmm6: + case fpu_xmm7: + // These values don't fit into scalar types, RegisterContext::ReadRegisterBytes() + // must be used for these registers + ::memcpy (fpu.xmm[reg - fpu_xmm0].bytes, data.PeekData(offset, reg_info->byte_size), reg_info->byte_size); + return false; + + case exc_trapno: + exc.trapno = data.GetU32 (&offset); + break; + + case exc_err: + exc.err = data.GetU32 (&offset); + break; + + case exc_faultvaddr: + exc.faultvaddr = data.GetU32 (&offset); + break; + + default: + return false; + } + return WriteRegisterSet(set) == KERN_SUCCESS; +} + +bool +RegisterContextMach_i386::ReadAllRegisterValues (lldb::DataBufferSP &data_sp) +{ + data_sp.reset (new DataBufferHeap (REG_CONTEXT_SIZE, 0)); + if (data_sp && + ReadGPR (false) == KERN_SUCCESS && + ReadFPU (false) == KERN_SUCCESS && + ReadEXC (false) == KERN_SUCCESS) + { + uint8_t *dst = data_sp->GetBytes(); + ::memcpy (dst, &gpr, sizeof(gpr)); + dst += sizeof(gpr); + + ::memcpy (dst, &fpu, sizeof(fpu)); + dst += sizeof(gpr); + + ::memcpy (dst, &exc, sizeof(exc)); + return true; + } + return false; +} + +bool +RegisterContextMach_i386::WriteAllRegisterValues (const lldb::DataBufferSP &data_sp) +{ + if (data_sp && data_sp->GetByteSize() == REG_CONTEXT_SIZE) + { + const uint8_t *src = data_sp->GetBytes(); + ::memcpy (&gpr, src, sizeof(gpr)); + src += sizeof(gpr); + + ::memcpy (&fpu, src, sizeof(fpu)); + src += sizeof(gpr); + + ::memcpy (&exc, src, sizeof(exc)); + uint32_t success_count = 0; + if (WriteGPR() == KERN_SUCCESS) + ++success_count; + if (WriteFPU() == KERN_SUCCESS) + ++success_count; + if (WriteEXC() == KERN_SUCCESS) + ++success_count; + return success_count == 3; + } + return false; +} + + +uint32_t +RegisterContextMach_i386::ConvertRegisterKindToRegisterNumber (uint32_t kind, uint32_t reg) +{ + if (kind == eRegisterKindGeneric) + { + switch (reg) + { + case LLDB_REGNUM_GENERIC_PC: return gpr_eip; + case LLDB_REGNUM_GENERIC_SP: return gpr_esp; + case LLDB_REGNUM_GENERIC_FP: return gpr_ebp; + case LLDB_REGNUM_GENERIC_FLAGS: return gpr_eflags; + case LLDB_REGNUM_GENERIC_RA: + default: + break; + } + } + else if (kind == eRegisterKindGCC || kind == eRegisterKindDWARF) + { + switch (reg) + { + case dwarf_eax: return gpr_eax; + case dwarf_ecx: return gpr_ecx; + case dwarf_edx: return gpr_edx; + case dwarf_ebx: return gpr_ebx; + case dwarf_esp: return gpr_esp; + case dwarf_ebp: return gpr_ebp; + case dwarf_esi: return gpr_esi; + case dwarf_edi: return gpr_edi; + case dwarf_eip: return gpr_eip; + case dwarf_eflags: return gpr_eflags; + case dwarf_stmm0: return fpu_stmm0; + case dwarf_stmm1: return fpu_stmm1; + case dwarf_stmm2: return fpu_stmm2; + case dwarf_stmm3: return fpu_stmm3; + case dwarf_stmm4: return fpu_stmm4; + case dwarf_stmm5: return fpu_stmm5; + case dwarf_stmm6: return fpu_stmm6; + case dwarf_stmm7: return fpu_stmm7; + case dwarf_xmm0: return fpu_xmm0; + case dwarf_xmm1: return fpu_xmm1; + case dwarf_xmm2: return fpu_xmm2; + case dwarf_xmm3: return fpu_xmm3; + case dwarf_xmm4: return fpu_xmm4; + case dwarf_xmm5: return fpu_xmm5; + case dwarf_xmm6: return fpu_xmm6; + case dwarf_xmm7: return fpu_xmm7; + default: + break; + } + } + else if (kind == eRegisterKindGDB) + { + switch (reg) + { + case gdb_eax : return gpr_eax; + case gdb_ebx : return gpr_ebx; + case gdb_ecx : return gpr_ecx; + case gdb_edx : return gpr_edx; + case gdb_esi : return gpr_esi; + case gdb_edi : return gpr_edi; + case gdb_ebp : return gpr_ebp; + case gdb_esp : return gpr_esp; + case gdb_eip : return gpr_eip; + case gdb_eflags : return gpr_eflags; + case gdb_cs : return gpr_cs; + case gdb_ss : return gpr_ss; + case gdb_ds : return gpr_ds; + case gdb_es : return gpr_es; + case gdb_fs : return gpr_fs; + case gdb_gs : return gpr_gs; + case gdb_stmm0 : return fpu_stmm0; + case gdb_stmm1 : return fpu_stmm1; + case gdb_stmm2 : return fpu_stmm2; + case gdb_stmm3 : return fpu_stmm3; + case gdb_stmm4 : return fpu_stmm4; + case gdb_stmm5 : return fpu_stmm5; + case gdb_stmm6 : return fpu_stmm6; + case gdb_stmm7 : return fpu_stmm7; + case gdb_fctrl : return fpu_fctrl; + case gdb_fstat : return fpu_fstat; + case gdb_ftag : return fpu_ftag; + case gdb_fiseg : return fpu_fiseg; + case gdb_fioff : return fpu_fioff; + case gdb_foseg : return fpu_foseg; + case gdb_fooff : return fpu_fooff; + case gdb_fop : return fpu_fop; + case gdb_xmm0 : return fpu_xmm0; + case gdb_xmm1 : return fpu_xmm1; + case gdb_xmm2 : return fpu_xmm2; + case gdb_xmm3 : return fpu_xmm3; + case gdb_xmm4 : return fpu_xmm4; + case gdb_xmm5 : return fpu_xmm5; + case gdb_xmm6 : return fpu_xmm6; + case gdb_xmm7 : return fpu_xmm7; + case gdb_mxcsr : return fpu_mxcsr; + default: + break; + } + } + return LLDB_INVALID_REGNUM; +} + + +bool +RegisterContextMach_i386::HardwareSingleStep (bool enable) +{ + if (ReadGPR(false) != KERN_SUCCESS) + return false; + + const uint32_t trace_bit = 0x100u; + if (enable) + { + // If the trace bit is already set, there is nothing to do + if (gpr.eflags & trace_bit) + return true; + else + gpr.eflags |= trace_bit; + } + else + { + // If the trace bit is already cleared, there is nothing to do + if (gpr.eflags & trace_bit) + gpr.eflags &= ~trace_bit; + else + return true; + } + + return WriteGPR() == KERN_SUCCESS; +} + + + diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_i386.h b/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_i386.h new file mode 100644 index 00000000000..580186752a9 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_i386.h @@ -0,0 +1,256 @@ +//===-- RegisterContextMach_i386.h ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_RegisterContextMach_i386_h_ +#define liblldb_RegisterContextMach_i386_h_ + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "lldb/lldb-private.h" +#include "lldb/Target/RegisterContext.h" + +class RegisterContextMach_i386 : public lldb_private::RegisterContext +{ +public: + + RegisterContextMach_i386(lldb_private::Thread &thread, + lldb_private::StackFrame *frame); + + virtual + ~RegisterContextMach_i386(); + + virtual void + Invalidate (); + + virtual size_t + GetRegisterCount (); + + virtual const lldb::RegisterInfo * + GetRegisterInfoAtIndex (uint32_t reg); + + virtual size_t + GetRegisterSetCount (); + + virtual const lldb::RegisterSet * + GetRegisterSet (uint32_t set); + + virtual bool + ReadRegisterValue (uint32_t reg, lldb_private::Scalar &value); + + virtual bool + ReadRegisterBytes (uint32_t reg, lldb_private::DataExtractor &data); + + virtual bool + ReadAllRegisterValues (lldb::DataBufferSP &data_sp); + + virtual bool + WriteRegisterValue (uint32_t reg, const lldb_private::Scalar &value); + + virtual bool + WriteRegisterBytes (uint32_t reg, lldb_private::DataExtractor &data, uint32_t data_offset = 0); + + virtual bool + WriteAllRegisterValues (const lldb::DataBufferSP &data_sp); + + virtual uint32_t + ConvertRegisterKindToRegisterNumber (uint32_t kind, uint32_t num); + + virtual bool + HardwareSingleStep (bool enable); + + struct GPR + { + uint32_t eax; + uint32_t ebx; + uint32_t ecx; + uint32_t edx; + uint32_t edi; + uint32_t esi; + uint32_t ebp; + uint32_t esp; + uint32_t ss; + uint32_t eflags; + uint32_t eip; + uint32_t cs; + uint32_t ds; + uint32_t es; + uint32_t fs; + uint32_t gs; + }; + + struct MMSReg + { + uint8_t bytes[10]; + uint8_t pad[6]; + }; + + struct XMMReg + { + uint8_t bytes[16]; + }; + + struct FPU + { + uint32_t pad[2]; + uint16_t fcw; + uint16_t fsw; + uint8_t ftw; + uint8_t pad1; + uint16_t fop; + uint32_t ip; + uint16_t cs; + uint16_t pad2; + uint32_t dp; + uint16_t ds; + uint16_t pad3; + uint32_t mxcsr; + uint32_t mxcsrmask; + MMSReg stmm[8]; + XMMReg xmm[8]; + uint8_t pad4[14*16]; + int pad5; + }; + + struct EXC + { + uint32_t trapno; + uint32_t err; + uint32_t faultvaddr; + }; + +protected: + + enum + { + GPRRegSet = 1, + FPURegSet = 2, + EXCRegSet = 3 + }; + + enum + { + GPRWordCount = sizeof(GPR)/sizeof(uint32_t), + FPUWordCount = sizeof(FPU)/sizeof(uint32_t), + EXCWordCount = sizeof(EXC)/sizeof(uint32_t) + }; + + enum + { + Read = 0, + Write = 1, + kNumErrors = 2 + }; + + GPR gpr; + FPU fpu; + EXC exc; + kern_return_t gpr_errs[2]; // Read/Write errors + kern_return_t fpu_errs[2]; // Read/Write errors + kern_return_t exc_errs[2]; // Read/Write errors + + void + InvalidateAllRegisterStates() + { + SetError (GPRRegSet, Read, -1); + SetError (FPURegSet, Read, -1); + SetError (EXCRegSet, Read, -1); + } + + kern_return_t + GetError (int flavor, uint32_t err_idx) const + { + if (err_idx < kNumErrors) + { + switch (flavor) + { + // When getting all errors, just OR all values together to see if + // we got any kind of error. + case GPRRegSet: return gpr_errs[err_idx]; + case FPURegSet: return fpu_errs[err_idx]; + case EXCRegSet: return exc_errs[err_idx]; + default: break; + } + } + return -1; + } + + bool + SetError (int flavor, uint32_t err_idx, kern_return_t err) + { + if (err_idx < kNumErrors) + { + switch (flavor) + { + case GPRRegSet: + gpr_errs[err_idx] = err; + return true; + + case FPURegSet: + fpu_errs[err_idx] = err; + return true; + + case EXCRegSet: + exc_errs[err_idx] = err; + return true; + + default: break; + } + } + return false; + } + + bool + RegisterSetIsCached (int set) const + { + return GetError(set, Read) == KERN_SUCCESS; + } + + void + LogGPR (lldb_private::Log *log, const char *title); + + kern_return_t + ReadGPR (bool force); + + kern_return_t + ReadFPU (bool force); + + kern_return_t + ReadEXC (bool force); + + kern_return_t + WriteGPR (); + + kern_return_t + WriteFPU (); + + kern_return_t + WriteEXC (); + + kern_return_t + ReadRegisterSet (uint32_t set, bool force); + + kern_return_t + WriteRegisterSet (uint32_t set); + + static uint32_t + GetRegisterNumber (uint32_t reg_kind, uint32_t reg_num); + + static int + GetSetForNativeRegNum (int reg_num); + + static size_t + GetRegisterInfosCount (); + + static const lldb::RegisterInfo * + GetRegisterInfos (); +}; + +#endif // liblldb_RegisterContextMach_i386_h_ diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_x86_64.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_x86_64.cpp new file mode 100644 index 00000000000..a7ed32e649a --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_x86_64.cpp @@ -0,0 +1,1328 @@ +//===-- RegisterContextMach_x86_64.cpp --------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + + +// C Includes +#include <mach/thread_act.h> + +// C++ Includes +// Other libraries and framework includes +#include "lldb/Core/DataBufferHeap.h" +#include "lldb/Core/DataExtractor.h" +#include "lldb/Core/Scalar.h" + +// Project includes +#include "RegisterContextMach_x86_64.h" +#include "ProcessMacOSXLog.h" + +using namespace lldb; +using namespace lldb_private; + +enum +{ + gpr_rax = 0, + gpr_rbx, + gpr_rcx, + gpr_rdx, + gpr_rdi, + gpr_rsi, + gpr_rbp, + gpr_rsp, + gpr_r8, + gpr_r9, + gpr_r10, + gpr_r11, + gpr_r12, + gpr_r13, + gpr_r14, + gpr_r15, + gpr_rip, + gpr_rflags, + gpr_cs, + gpr_fs, + gpr_gs, + + fpu_fcw, + fpu_fsw, + fpu_ftw, + fpu_fop, + fpu_ip, + fpu_cs, + fpu_dp, + fpu_ds, + fpu_mxcsr, + fpu_mxcsrmask, + fpu_stmm0, + fpu_stmm1, + fpu_stmm2, + fpu_stmm3, + fpu_stmm4, + fpu_stmm5, + fpu_stmm6, + fpu_stmm7, + fpu_xmm0, + fpu_xmm1, + fpu_xmm2, + fpu_xmm3, + fpu_xmm4, + fpu_xmm5, + fpu_xmm6, + fpu_xmm7, + fpu_xmm8, + fpu_xmm9, + fpu_xmm10, + fpu_xmm11, + fpu_xmm12, + fpu_xmm13, + fpu_xmm14, + fpu_xmm15, + + exc_trapno, + exc_err, + exc_faultvaddr, + + k_num_registers, + + // Aliases + fpu_fctrl = fpu_fcw, + fpu_fstat = fpu_fsw, + fpu_ftag = fpu_ftw, + fpu_fiseg = fpu_cs, + fpu_fioff = fpu_ip, + fpu_foseg = fpu_ds, + fpu_fooff = fpu_dp, +}; + +enum gcc_dwarf_regnums +{ + gcc_dwarf_gpr_rax = 0, + gcc_dwarf_gpr_rdx, + gcc_dwarf_gpr_rcx, + gcc_dwarf_gpr_rbx, + gcc_dwarf_gpr_rsi, + gcc_dwarf_gpr_rdi, + gcc_dwarf_gpr_rbp, + gcc_dwarf_gpr_rsp, + gcc_dwarf_gpr_r8, + gcc_dwarf_gpr_r9, + gcc_dwarf_gpr_r10, + gcc_dwarf_gpr_r11, + gcc_dwarf_gpr_r12, + gcc_dwarf_gpr_r13, + gcc_dwarf_gpr_r14, + gcc_dwarf_gpr_r15, + gcc_dwarf_gpr_rip, + gcc_dwarf_fpu_xmm0, + gcc_dwarf_fpu_xmm1, + gcc_dwarf_fpu_xmm2, + gcc_dwarf_fpu_xmm3, + gcc_dwarf_fpu_xmm4, + gcc_dwarf_fpu_xmm5, + gcc_dwarf_fpu_xmm6, + gcc_dwarf_fpu_xmm7, + gcc_dwarf_fpu_xmm8, + gcc_dwarf_fpu_xmm9, + gcc_dwarf_fpu_xmm10, + gcc_dwarf_fpu_xmm11, + gcc_dwarf_fpu_xmm12, + gcc_dwarf_fpu_xmm13, + gcc_dwarf_fpu_xmm14, + gcc_dwarf_fpu_xmm15, + gcc_dwarf_fpu_stmm0, + gcc_dwarf_fpu_stmm1, + gcc_dwarf_fpu_stmm2, + gcc_dwarf_fpu_stmm3, + gcc_dwarf_fpu_stmm4, + gcc_dwarf_fpu_stmm5, + gcc_dwarf_fpu_stmm6, + gcc_dwarf_fpu_stmm7, + +}; + +enum gdb_regnums +{ + gdb_gpr_rax = 0, + gdb_gpr_rbx = 1, + gdb_gpr_rcx = 2, + gdb_gpr_rdx = 3, + gdb_gpr_rsi = 4, + gdb_gpr_rdi = 5, + gdb_gpr_rbp = 6, + gdb_gpr_rsp = 7, + gdb_gpr_r8 = 8, + gdb_gpr_r9 = 9, + gdb_gpr_r10 = 10, + gdb_gpr_r11 = 11, + gdb_gpr_r12 = 12, + gdb_gpr_r13 = 13, + gdb_gpr_r14 = 14, + gdb_gpr_r15 = 15, + gdb_gpr_rip = 16, + gdb_gpr_rflags = 17, + gdb_gpr_cs = 18, + gdb_gpr_ss = 19, + gdb_gpr_ds = 20, + gdb_gpr_es = 21, + gdb_gpr_fs = 22, + gdb_gpr_gs = 23, + gdb_fpu_stmm0 = 24, + gdb_fpu_stmm1 = 25, + gdb_fpu_stmm2 = 26, + gdb_fpu_stmm3 = 27, + gdb_fpu_stmm4 = 28, + gdb_fpu_stmm5 = 29, + gdb_fpu_stmm6 = 30, + gdb_fpu_stmm7 = 31, + gdb_fpu_fctrl = 32, gdb_fpu_fcw = gdb_fpu_fctrl, + gdb_fpu_fstat = 33, gdb_fpu_fsw = gdb_fpu_fstat, + gdb_fpu_ftag = 34, gdb_fpu_ftw = gdb_fpu_ftag, + gdb_fpu_fiseg = 35, gdb_fpu_cs = gdb_fpu_fiseg, + gdb_fpu_fioff = 36, gdb_fpu_ip = gdb_fpu_fioff, + gdb_fpu_foseg = 37, gdb_fpu_ds = gdb_fpu_foseg, + gdb_fpu_fooff = 38, gdb_fpu_dp = gdb_fpu_fooff, + gdb_fpu_fop = 39, + gdb_fpu_xmm0 = 40, + gdb_fpu_xmm1 = 41, + gdb_fpu_xmm2 = 42, + gdb_fpu_xmm3 = 43, + gdb_fpu_xmm4 = 44, + gdb_fpu_xmm5 = 45, + gdb_fpu_xmm6 = 46, + gdb_fpu_xmm7 = 47, + gdb_fpu_xmm8 = 48, + gdb_fpu_xmm9 = 49, + gdb_fpu_xmm10 = 50, + gdb_fpu_xmm11 = 51, + gdb_fpu_xmm12 = 52, + gdb_fpu_xmm13 = 53, + gdb_fpu_xmm14 = 54, + gdb_fpu_xmm15 = 55, + gdb_fpu_mxcsr = 56, +}; + +RegisterContextMach_x86_64::RegisterContextMach_x86_64 (Thread &thread, StackFrame *frame) : + RegisterContext (thread, frame), + gpr(), + fpu(), + exc() +{ + uint32_t i; + for (i=0; i<kNumErrors; i++) + { + gpr_errs[i] = -1; + fpu_errs[i] = -1; + exc_errs[i] = -1; + } +} + +RegisterContextMach_x86_64::~RegisterContextMach_x86_64() +{ +} + +#define GPR_OFFSET(reg) (offsetof (RegisterContextMach_x86_64::GPR, reg)) +#define FPU_OFFSET(reg) (offsetof (RegisterContextMach_x86_64::FPU, reg) + sizeof (RegisterContextMach_x86_64::GPR)) +#define EXC_OFFSET(reg) (offsetof (RegisterContextMach_x86_64::EXC, reg) + sizeof (RegisterContextMach_x86_64::GPR) + sizeof (RegisterContextMach_x86_64::FPU)) + +// These macros will auto define the register name, alt name, register size, +// register offset, encoding, format and native register. This ensures that +// the register state structures are defined correctly and have the correct +// sizes and offsets. +#define DEFINE_GPR(reg, alt) #reg, alt, sizeof(((RegisterContextMach_x86_64::GPR *)NULL)->reg), GPR_OFFSET(reg), eEncodingUint, eFormatHex, gpr_##reg +#define DEFINE_FPU_UINT(reg) #reg, NULL, sizeof(((RegisterContextMach_x86_64::FPU *)NULL)->reg), FPU_OFFSET(reg), eEncodingUint, eFormatHex, fpu_##reg +#define DEFINE_FPU_VECT(reg, i) #reg#i, NULL, sizeof(((RegisterContextMach_x86_64::FPU *)NULL)->reg[i].bytes), FPU_OFFSET(reg[i]), eEncodingVector, eFormatVectorOfUInt8, fpu_##reg##i, { gcc_dwarf_fpu_##reg##i, gcc_dwarf_fpu_##reg##i, LLDB_INVALID_REGNUM, gdb_fpu_##reg##i } +#define DEFINE_EXC(reg) #reg, NULL, sizeof(((RegisterContextMach_x86_64::EXC *)NULL)->reg), EXC_OFFSET(reg), eEncodingUint, eFormatHex, exc_##reg + +#define REG_CONTEXT_SIZE (sizeof (RegisterContextMach_x86_64::GPR) + sizeof (RegisterContextMach_x86_64::FPU) + sizeof (RegisterContextMach_x86_64::EXC)) + +// General purpose registers for 64 bit +static RegisterInfo g_register_infos[] = +{ +// Macro auto defines most stuff GCC REG KIND NUM DWARF REG KIND NUM GENERIC REG KIND NUM GDB REG KIND NUM +// =============================== ======================= =================== ========================== ========================== + { DEFINE_GPR (rax , NULL) , { gcc_dwarf_gpr_rax , gcc_dwarf_gpr_rax , LLDB_INVALID_REGNUM , gdb_gpr_rax }}, + { DEFINE_GPR (rbx , NULL) , { gcc_dwarf_gpr_rbx , gcc_dwarf_gpr_rbx , LLDB_INVALID_REGNUM , gdb_gpr_rbx }}, + { DEFINE_GPR (rcx , NULL) , { gcc_dwarf_gpr_rcx , gcc_dwarf_gpr_rcx , LLDB_INVALID_REGNUM , gdb_gpr_rcx }}, + { DEFINE_GPR (rdx , NULL) , { gcc_dwarf_gpr_rdx , gcc_dwarf_gpr_rdx , LLDB_INVALID_REGNUM , gdb_gpr_rdx }}, + { DEFINE_GPR (rdi , NULL) , { gcc_dwarf_gpr_rdi , gcc_dwarf_gpr_rdi , LLDB_INVALID_REGNUM , gdb_gpr_rdi }}, + { DEFINE_GPR (rsi , NULL) , { gcc_dwarf_gpr_rsi , gcc_dwarf_gpr_rsi , LLDB_INVALID_REGNUM , gdb_gpr_rsi }}, + { DEFINE_GPR (rbp , "fp") , { gcc_dwarf_gpr_rbp , gcc_dwarf_gpr_rbp , LLDB_REGNUM_GENERIC_FP , gdb_gpr_rbp }}, + { DEFINE_GPR (rsp , "sp") , { gcc_dwarf_gpr_rsp , gcc_dwarf_gpr_rsp , LLDB_REGNUM_GENERIC_SP , gdb_gpr_rsp }}, + { DEFINE_GPR (r8 , NULL) , { gcc_dwarf_gpr_r8 , gcc_dwarf_gpr_r8 , LLDB_INVALID_REGNUM , gdb_gpr_r8 }}, + { DEFINE_GPR (r9 , NULL) , { gcc_dwarf_gpr_r9 , gcc_dwarf_gpr_r9 , LLDB_INVALID_REGNUM , gdb_gpr_r9 }}, + { DEFINE_GPR (r10 , NULL) , { gcc_dwarf_gpr_r10 , gcc_dwarf_gpr_r10 , LLDB_INVALID_REGNUM , gdb_gpr_r10 }}, + { DEFINE_GPR (r11 , NULL) , { gcc_dwarf_gpr_r11 , gcc_dwarf_gpr_r11 , LLDB_INVALID_REGNUM , gdb_gpr_r11 }}, + { DEFINE_GPR (r12 , NULL) , { gcc_dwarf_gpr_r12 , gcc_dwarf_gpr_r12 , LLDB_INVALID_REGNUM , gdb_gpr_r12 }}, + { DEFINE_GPR (r13 , NULL) , { gcc_dwarf_gpr_r13 , gcc_dwarf_gpr_r13 , LLDB_INVALID_REGNUM , gdb_gpr_r13 }}, + { DEFINE_GPR (r14 , NULL) , { gcc_dwarf_gpr_r14 , gcc_dwarf_gpr_r14 , LLDB_INVALID_REGNUM , gdb_gpr_r14 }}, + { DEFINE_GPR (r15 , NULL) , { gcc_dwarf_gpr_r15 , gcc_dwarf_gpr_r15 , LLDB_INVALID_REGNUM , gdb_gpr_r15 }}, + { DEFINE_GPR (rip , "pc") , { gcc_dwarf_gpr_rip , gcc_dwarf_gpr_rip , LLDB_REGNUM_GENERIC_PC , gdb_gpr_rip }}, + { DEFINE_GPR (rflags, "flags") , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_REGNUM_GENERIC_FLAGS , gdb_gpr_rflags}}, + { DEFINE_GPR (cs , NULL) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_gpr_cs }}, + { DEFINE_GPR (fs , NULL) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_gpr_fs }}, + { DEFINE_GPR (gs , NULL) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_gpr_gs }}, + + { DEFINE_FPU_UINT(fcw) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_fpu_fcw }}, + { DEFINE_FPU_UINT(fsw) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_fpu_fsw }}, + { DEFINE_FPU_UINT(ftw) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_fpu_ftw }}, + { DEFINE_FPU_UINT(fop) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_fpu_fop }}, + { DEFINE_FPU_UINT(ip) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_fpu_ip }}, + { DEFINE_FPU_UINT(cs) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_fpu_cs }}, + { DEFINE_FPU_UINT(dp) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_fpu_dp }}, + { DEFINE_FPU_UINT(ds) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_fpu_ds }}, + { DEFINE_FPU_UINT(mxcsr) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , gdb_fpu_mxcsr }}, + { DEFINE_FPU_UINT(mxcsrmask) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM }}, + { DEFINE_FPU_VECT(stmm,0) }, + { DEFINE_FPU_VECT(stmm,1) }, + { DEFINE_FPU_VECT(stmm,2) }, + { DEFINE_FPU_VECT(stmm,3) }, + { DEFINE_FPU_VECT(stmm,4) }, + { DEFINE_FPU_VECT(stmm,5) }, + { DEFINE_FPU_VECT(stmm,6) }, + { DEFINE_FPU_VECT(stmm,7) }, + { DEFINE_FPU_VECT(xmm,0) }, + { DEFINE_FPU_VECT(xmm,1) }, + { DEFINE_FPU_VECT(xmm,2) }, + { DEFINE_FPU_VECT(xmm,3) }, + { DEFINE_FPU_VECT(xmm,4) }, + { DEFINE_FPU_VECT(xmm,5) }, + { DEFINE_FPU_VECT(xmm,6) }, + { DEFINE_FPU_VECT(xmm,7) }, + { DEFINE_FPU_VECT(xmm,8) }, + { DEFINE_FPU_VECT(xmm,9) }, + { DEFINE_FPU_VECT(xmm,10) }, + { DEFINE_FPU_VECT(xmm,11) }, + { DEFINE_FPU_VECT(xmm,12) }, + { DEFINE_FPU_VECT(xmm,13) }, + { DEFINE_FPU_VECT(xmm,14) }, + { DEFINE_FPU_VECT(xmm,15) }, + + { DEFINE_EXC(trapno) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM }}, + { DEFINE_EXC(err) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM }}, + { DEFINE_EXC(faultvaddr) , { LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM , LLDB_INVALID_REGNUM }} +}; + +static size_t k_num_register_infos = (sizeof(g_register_infos)/sizeof(RegisterInfo)); + + +void +RegisterContextMach_x86_64::Invalidate () +{ + InvalidateAllRegisterStates(); +} + + +size_t +RegisterContextMach_x86_64::GetRegisterCount () +{ + assert(k_num_register_infos == k_num_registers); + return k_num_registers; +} + + +const RegisterInfo * +RegisterContextMach_x86_64::GetRegisterInfoAtIndex (uint32_t reg) +{ + assert(k_num_register_infos == k_num_registers); + if (reg < k_num_registers) + return &g_register_infos[reg]; + return NULL; +} + + +size_t +RegisterContextMach_x86_64::GetRegisterInfosCount () +{ + return k_num_register_infos; +} + +const RegisterInfo * +RegisterContextMach_x86_64::GetRegisterInfos () +{ + return g_register_infos; +} + + + +static uint32_t g_gpr_regnums[] = +{ + gpr_rax, + gpr_rbx, + gpr_rcx, + gpr_rdx, + gpr_rdi, + gpr_rsi, + gpr_rbp, + gpr_rsp, + gpr_r8, + gpr_r9, + gpr_r10, + gpr_r11, + gpr_r12, + gpr_r13, + gpr_r14, + gpr_r15, + gpr_rip, + gpr_rflags, + gpr_cs, + gpr_fs, + gpr_gs +}; + +static uint32_t g_fpu_regnums[] = +{ + fpu_fcw, + fpu_fsw, + fpu_ftw, + fpu_fop, + fpu_ip, + fpu_cs, + fpu_dp, + fpu_ds, + fpu_mxcsr, + fpu_mxcsrmask, + fpu_stmm0, + fpu_stmm1, + fpu_stmm2, + fpu_stmm3, + fpu_stmm4, + fpu_stmm5, + fpu_stmm6, + fpu_stmm7, + fpu_xmm0, + fpu_xmm1, + fpu_xmm2, + fpu_xmm3, + fpu_xmm4, + fpu_xmm5, + fpu_xmm6, + fpu_xmm7, + fpu_xmm8, + fpu_xmm9, + fpu_xmm10, + fpu_xmm11, + fpu_xmm12, + fpu_xmm13, + fpu_xmm14, + fpu_xmm15 +}; + +static uint32_t +g_exc_regnums[] = +{ + exc_trapno, + exc_err, + exc_faultvaddr +}; + +// Number of registers in each register set +const size_t k_num_gpr_registers = sizeof(g_gpr_regnums) / sizeof(uint32_t); +const size_t k_num_fpu_registers = sizeof(g_fpu_regnums) / sizeof(uint32_t); +const size_t k_num_exc_registers = sizeof(g_exc_regnums) / sizeof(uint32_t); + +//---------------------------------------------------------------------- +// Register set definitions. The first definitions at register set index +// of zero is for all registers, followed by other registers sets. The +// register information for the all register set need not be filled in. +//---------------------------------------------------------------------- +static const RegisterSet g_reg_sets[] = +{ + { "General Purpose Registers", "gpr", k_num_gpr_registers, g_gpr_regnums, }, + { "Floating Point Registers", "fpu", k_num_fpu_registers, g_fpu_regnums }, + { "Exception State Registers", "exc", k_num_exc_registers, g_exc_regnums } +}; + +const size_t k_num_regsets = sizeof(g_reg_sets) / sizeof(RegisterSet); + + +size_t +RegisterContextMach_x86_64::GetRegisterSetCount () +{ + return k_num_regsets; +} + +const RegisterSet * +RegisterContextMach_x86_64::GetRegisterSet (uint32_t reg_set) +{ + if (reg_set < k_num_regsets) + return &g_reg_sets[reg_set]; + return NULL; +} + +int +RegisterContextMach_x86_64::GetSetForNativeRegNum (int reg_num) +{ + if (reg_num < fpu_fcw) + return GPRRegSet; + else if (reg_num < exc_trapno) + return FPURegSet; + else if (reg_num < k_num_registers) + return EXCRegSet; + return -1; +} + +void +RegisterContextMach_x86_64::LogGPR(Log *log, const char *format, ...) +{ + if (log) + { + if (format) + { + va_list args; + va_start (args, format); + log->VAPrintf (format, args); + va_end (args); + } + for (uint32_t i=0; i<k_num_gpr_registers; i++) + { + uint32_t reg = gpr_rax + i; + log->Printf("%12s = 0x%16.16llx", g_register_infos[reg].name, (&gpr.rax)[reg]); + } + } +} + +kern_return_t +RegisterContextMach_x86_64::ReadGPR (bool force) +{ + int set = GPRRegSet; + if (force || !RegisterSetIsCached(set)) + { + mach_msg_type_number_t count = GPRWordCount; + SetError(GPRRegSet, Read, ::thread_get_state(GetThreadID(), set, (thread_state_t)&gpr, &count)); + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_THREAD); + if (log) + LogGPR (log, "RegisterContextMach_x86_64::ReadGPR(thread = 0x%4.4x)", GetThreadID()); + } + return GetError(GPRRegSet, Read); +} + +kern_return_t +RegisterContextMach_x86_64::ReadFPU (bool force) +{ + int set = FPURegSet; + if (force || !RegisterSetIsCached(set)) + { + mach_msg_type_number_t count = FPUWordCount; + SetError(FPURegSet, Read, ::thread_get_state(GetThreadID(), set, (thread_state_t)&fpu, &count)); + } + return GetError(FPURegSet, Read); +} + +kern_return_t +RegisterContextMach_x86_64::ReadEXC (bool force) +{ + int set = EXCRegSet; + if (force || !RegisterSetIsCached(set)) + { + mach_msg_type_number_t count = EXCWordCount; + SetError(EXCRegSet, Read, ::thread_get_state(GetThreadID(), set, (thread_state_t)&exc, &count)); + } + return GetError(EXCRegSet, Read); +} + +kern_return_t +RegisterContextMach_x86_64::WriteGPR () +{ + int set = GPRRegSet; + if (!RegisterSetIsCached(set)) + { + SetError (set, Write, -1); + return KERN_INVALID_ARGUMENT; + } + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet (PD_LOG_THREAD); + if (log) + LogGPR (log, "RegisterContextMach_x86_64::WriteGPR (thread = 0x%4.4x)", GetThreadID()); + SetError (set, Write, ::thread_set_state(GetThreadID(), set, (thread_state_t)&gpr, GPRWordCount)); + SetError (set, Read, -1); + return GetError (set, Write); +} + +kern_return_t +RegisterContextMach_x86_64::WriteFPU () +{ + int set = FPURegSet; + if (!RegisterSetIsCached(set)) + { + SetError (set, Write, -1); + return KERN_INVALID_ARGUMENT; + } + SetError (set, Write, ::thread_set_state(GetThreadID(), set, (thread_state_t)&fpu, FPUWordCount)); + SetError (set, Read, -1); + return GetError (set, Write); +} + +kern_return_t +RegisterContextMach_x86_64::WriteEXC () +{ + int set = EXCRegSet; + if (!RegisterSetIsCached(set)) + { + SetError (set, Write, -1); + return KERN_INVALID_ARGUMENT; + } + SetError (set, Write, ::thread_set_state(GetThreadID(), set, (thread_state_t)&exc, EXCWordCount)); + SetError (set, Read, -1); + return GetError (set, Write); +} + +kern_return_t +RegisterContextMach_x86_64::ReadRegisterSet(uint32_t set, bool force) +{ + switch (set) + { + case GPRRegSet: return ReadGPR (force); + case FPURegSet: return ReadFPU (force); + case EXCRegSet: return ReadEXC (force); + default: break; + } + return KERN_INVALID_ARGUMENT; +} + +kern_return_t +RegisterContextMach_x86_64::WriteRegisterSet(uint32_t set) +{ + // Make sure we have a valid context to set. + switch (set) + { + case GPRRegSet: return WriteGPR (); + case FPURegSet: return WriteFPU (); + case EXCRegSet: return WriteEXC (); + default: break; + } + return KERN_INVALID_ARGUMENT; +} + + +bool +RegisterContextMach_x86_64::ReadRegisterValue (uint32_t reg, Scalar &value) +{ + int set = RegisterContextMach_x86_64::GetSetForNativeRegNum (reg); + + if (set == -1) + return false; + + if (ReadRegisterSet(set, false) != KERN_SUCCESS) + return false; + + switch (reg) + { + case gpr_rax: + case gpr_rbx: + case gpr_rcx: + case gpr_rdx: + case gpr_rdi: + case gpr_rsi: + case gpr_rbp: + case gpr_rsp: + case gpr_r8: + case gpr_r9: + case gpr_r10: + case gpr_r11: + case gpr_r12: + case gpr_r13: + case gpr_r14: + case gpr_r15: + case gpr_rip: + case gpr_rflags: + case gpr_cs: + case gpr_fs: + case gpr_gs: + value = (&gpr.rax)[reg - gpr_rax]; + break; + + case fpu_fcw: + value = fpu.fcw; + break; + + case fpu_fsw: + value = fpu.fsw; + break; + + case fpu_ftw: + value = fpu.ftw; + break; + + case fpu_fop: + value = fpu.fop; + break; + + case fpu_ip: + value = fpu.ip; + break; + + case fpu_cs: + value = fpu.cs; + break; + + case fpu_dp: + value = fpu.dp; + break; + + case fpu_ds: + value = fpu.ds; + break; + + case fpu_mxcsr: + value = fpu.mxcsr; + break; + + case fpu_mxcsrmask: + value = fpu.mxcsrmask; + break; + + case fpu_stmm0: + case fpu_stmm1: + case fpu_stmm2: + case fpu_stmm3: + case fpu_stmm4: + case fpu_stmm5: + case fpu_stmm6: + case fpu_stmm7: + // These values don't fit into scalar types, + // RegisterContext::ReadRegisterBytes() must be used for these + // registers + //::memcpy (reg_value.value.vector.uint8, fpu.stmm[reg - fpu_stmm0].bytes, 10); + return false; + + case fpu_xmm0: + case fpu_xmm1: + case fpu_xmm2: + case fpu_xmm3: + case fpu_xmm4: + case fpu_xmm5: + case fpu_xmm6: + case fpu_xmm7: + case fpu_xmm8: + case fpu_xmm9: + case fpu_xmm10: + case fpu_xmm11: + case fpu_xmm12: + case fpu_xmm13: + case fpu_xmm14: + case fpu_xmm15: + // These values don't fit into scalar types, RegisterContext::ReadRegisterBytes() + // must be used for these registers + //::memcpy (reg_value.value.vector.uint8, fpu.xmm[reg - fpu_xmm0].bytes, 16); + return false; + + case exc_trapno: + value = exc.trapno; + break; + + case exc_err: + value = exc.err; + break; + + case exc_faultvaddr: + value = exc.faultvaddr; + break; + + default: + return false; + } + return true; +} + + +bool +RegisterContextMach_x86_64::WriteRegisterValue (uint32_t reg, const Scalar &value) +{ + int set = RegisterContextMach_x86_64::GetSetForNativeRegNum (reg); + + if (set == -1) + return false; + + if (ReadRegisterSet(set, false) != KERN_SUCCESS) + return false; + + switch (reg) + { + case gpr_rax: + case gpr_rbx: + case gpr_rcx: + case gpr_rdx: + case gpr_rdi: + case gpr_rsi: + case gpr_rbp: + case gpr_rsp: + case gpr_r8: + case gpr_r9: + case gpr_r10: + case gpr_r11: + case gpr_r12: + case gpr_r13: + case gpr_r14: + case gpr_r15: + case gpr_rip: + case gpr_rflags: + case gpr_cs: + case gpr_fs: + case gpr_gs: + (&gpr.rax)[reg - gpr_rax] = value.ULongLong(0); + break; + + case fpu_fcw: + fpu.fcw = value.UInt(0); + break; + + case fpu_fsw: + fpu.fsw = value.UInt(0); + break; + + case fpu_ftw: + fpu.ftw = value.UInt(0); + break; + + case fpu_fop: + fpu.fop = value.UInt(0); + break; + + case fpu_ip: + fpu.ip = value.UInt(0); + break; + + case fpu_cs: + fpu.cs = value.UInt(0); + break; + + case fpu_dp: + fpu.dp = value.UInt(0); + break; + + case fpu_ds: + fpu.ds = value.UInt(0); + break; + + case fpu_mxcsr: + fpu.mxcsr = value.UInt(0); + break; + + case fpu_mxcsrmask: + fpu.mxcsrmask = value.UInt(0); + break; + + case fpu_stmm0: + case fpu_stmm1: + case fpu_stmm2: + case fpu_stmm3: + case fpu_stmm4: + case fpu_stmm5: + case fpu_stmm6: + case fpu_stmm7: + // These values don't fit into scalar types, RegisterContext::ReadRegisterBytes() + // must be used for these registers + //::memcpy (fpu.stmm[reg - fpu_stmm0].bytes, reg_value.value.vector.uint8, 10); + return false; + + case fpu_xmm0: + case fpu_xmm1: + case fpu_xmm2: + case fpu_xmm3: + case fpu_xmm4: + case fpu_xmm5: + case fpu_xmm6: + case fpu_xmm7: + case fpu_xmm8: + case fpu_xmm9: + case fpu_xmm10: + case fpu_xmm11: + case fpu_xmm12: + case fpu_xmm13: + case fpu_xmm14: + case fpu_xmm15: + // These values don't fit into scalar types, RegisterContext::ReadRegisterBytes() + // must be used for these registers + //::memcpy (fpu.xmm[reg - fpu_xmm0].bytes, reg_value.value.vector.uint8, 16); + return false; + + case exc_trapno: + exc.trapno = value.UInt(0); + break; + + case exc_err: + exc.err = value.UInt(0); + break; + + case exc_faultvaddr: + exc.faultvaddr = value.UInt(0); + break; + + default: + return false; + } + return WriteRegisterSet(set) == KERN_SUCCESS; +} + +bool +RegisterContextMach_x86_64::ReadRegisterBytes (uint32_t reg, DataExtractor &data) +{ + int set = RegisterContextMach_x86_64::GetSetForNativeRegNum (reg); + if (set == -1) + return false; + + if (ReadRegisterSet(set, false) != KERN_SUCCESS) + return false; + + const RegisterInfo * reg_info = GetRegisterInfoAtIndex (reg); + if (reg_info == NULL) + return false; + + switch (reg) + { + case gpr_rax: + case gpr_rbx: + case gpr_rcx: + case gpr_rdx: + case gpr_rdi: + case gpr_rsi: + case gpr_rbp: + case gpr_rsp: + case gpr_r8: + case gpr_r9: + case gpr_r10: + case gpr_r11: + case gpr_r12: + case gpr_r13: + case gpr_r14: + case gpr_r15: + case gpr_rip: + case gpr_rflags: + case gpr_cs: + case gpr_fs: + case gpr_gs: + data.SetData(&gpr.rax + reg - gpr_rax, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_fcw: + data.SetData(&fpu.fcw, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_fsw: + data.SetData(&fpu.fsw, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_ftw: + data.SetData(&fpu.ftw, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_fop: + data.SetData(&fpu.fop, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_ip: + data.SetData(&fpu.ip, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_cs: + data.SetData(&fpu.cs, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_dp: + data.SetData(&fpu.dp, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_ds: + data.SetData(&fpu.ds, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_mxcsr: + data.SetData(&fpu.mxcsr, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_mxcsrmask: + data.SetData(&fpu.mxcsrmask, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_stmm0: + case fpu_stmm1: + case fpu_stmm2: + case fpu_stmm3: + case fpu_stmm4: + case fpu_stmm5: + case fpu_stmm6: + case fpu_stmm7: + data.SetData(fpu.stmm[reg - fpu_stmm0].bytes, reg_info->byte_size, eByteOrderHost); + break; + + case fpu_xmm0: + case fpu_xmm1: + case fpu_xmm2: + case fpu_xmm3: + case fpu_xmm4: + case fpu_xmm5: + case fpu_xmm6: + case fpu_xmm7: + case fpu_xmm8: + case fpu_xmm9: + case fpu_xmm10: + case fpu_xmm11: + case fpu_xmm12: + case fpu_xmm13: + case fpu_xmm14: + case fpu_xmm15: + data.SetData(fpu.xmm[reg - fpu_xmm0].bytes, reg_info->byte_size, eByteOrderHost); + break; + + case exc_trapno: + data.SetData(&exc.trapno, reg_info->byte_size, eByteOrderHost); + break; + + case exc_err: + data.SetData(&exc.err, reg_info->byte_size, eByteOrderHost); + break; + + case exc_faultvaddr: + data.SetData(&exc.faultvaddr, reg_info->byte_size, eByteOrderHost); + break; + + default: + return false; + } + return true; +} + +bool +RegisterContextMach_x86_64::WriteRegisterBytes (uint32_t reg, DataExtractor &data, uint32_t data_offset) +{ + int set = RegisterContextMach_x86_64::GetSetForNativeRegNum (reg); + + if (set == -1) + return false; + + if (ReadRegisterSet(set, false) != KERN_SUCCESS) + return false; + + + const RegisterInfo * reg_info = GetRegisterInfoAtIndex (reg); + if (reg_info == NULL && data.ValidOffsetForDataOfSize(data_offset, reg_info->byte_size)) + return false; + + uint32_t offset = data_offset; + switch (reg) + { + case gpr_rax: + case gpr_rbx: + case gpr_rcx: + case gpr_rdx: + case gpr_rdi: + case gpr_rsi: + case gpr_rbp: + case gpr_rsp: + case gpr_r8: + case gpr_r9: + case gpr_r10: + case gpr_r11: + case gpr_r12: + case gpr_r13: + case gpr_r14: + case gpr_r15: + case gpr_rip: + case gpr_rflags: + case gpr_cs: + case gpr_fs: + case gpr_gs: + (&gpr.rax)[reg - gpr_rax] = data.GetU32 (&offset); + break; + + case fpu_fcw: + fpu.fcw = data.GetU16(&offset); + break; + + case fpu_fsw: + fpu.fsw = data.GetU16(&offset); + break; + + case fpu_ftw: + fpu.ftw = data.GetU8(&offset); + break; + + case fpu_fop: + fpu.fop = data.GetU16(&offset); + break; + + case fpu_ip: + fpu.ip = data.GetU32(&offset); + break; + + case fpu_cs: + fpu.cs = data.GetU16(&offset); + break; + + case fpu_dp: + fpu.dp = data.GetU32(&offset); + break; + + case fpu_ds: + fpu.ds = data.GetU16(&offset); + break; + + case fpu_mxcsr: + fpu.mxcsr = data.GetU32(&offset); + break; + + case fpu_mxcsrmask: + fpu.mxcsrmask = data.GetU32(&offset); + break; + + case fpu_stmm0: + case fpu_stmm1: + case fpu_stmm2: + case fpu_stmm3: + case fpu_stmm4: + case fpu_stmm5: + case fpu_stmm6: + case fpu_stmm7: + ::memcpy (fpu.stmm[reg - fpu_stmm0].bytes, data.PeekData(offset, reg_info->byte_size), reg_info->byte_size); + return false; + + case fpu_xmm0: + case fpu_xmm1: + case fpu_xmm2: + case fpu_xmm3: + case fpu_xmm4: + case fpu_xmm5: + case fpu_xmm6: + case fpu_xmm7: + case fpu_xmm8: + case fpu_xmm9: + case fpu_xmm10: + case fpu_xmm11: + case fpu_xmm12: + case fpu_xmm13: + case fpu_xmm14: + case fpu_xmm15: + // These values don't fit into scalar types, RegisterContext::ReadRegisterBytes() + // must be used for these registers + ::memcpy (fpu.xmm[reg - fpu_xmm0].bytes, data.PeekData(offset, reg_info->byte_size), reg_info->byte_size); + return false; + + case exc_trapno: + exc.trapno = data.GetU32 (&offset); + break; + + case exc_err: + exc.err = data.GetU32 (&offset); + break; + + case exc_faultvaddr: + exc.faultvaddr = data.GetU32 (&offset); + break; + + default: + return false; + } + return WriteRegisterSet(set) == KERN_SUCCESS; +} + +bool +RegisterContextMach_x86_64::ReadAllRegisterValues (lldb::DataBufferSP &data_sp) +{ + data_sp.reset (new DataBufferHeap (REG_CONTEXT_SIZE, 0)); + if (data_sp && + ReadGPR (false) == KERN_SUCCESS && + ReadFPU (false) == KERN_SUCCESS && + ReadEXC (false) == KERN_SUCCESS) + { + uint8_t *dst = data_sp->GetBytes(); + ::memcpy (dst, &gpr, sizeof(gpr)); + dst += sizeof(gpr); + + ::memcpy (dst, &fpu, sizeof(fpu)); + dst += sizeof(gpr); + + ::memcpy (dst, &exc, sizeof(exc)); + return true; + } + return false; +} + +bool +RegisterContextMach_x86_64::WriteAllRegisterValues (const lldb::DataBufferSP &data_sp) +{ + if (data_sp && data_sp->GetByteSize() == REG_CONTEXT_SIZE) + { + const uint8_t *src = data_sp->GetBytes(); + ::memcpy (&gpr, src, sizeof(gpr)); + src += sizeof(gpr); + + ::memcpy (&fpu, src, sizeof(fpu)); + src += sizeof(gpr); + + ::memcpy (&exc, src, sizeof(exc)); + uint32_t success_count = 0; + if (WriteGPR() == KERN_SUCCESS) + ++success_count; + if (WriteFPU() == KERN_SUCCESS) + ++success_count; + if (WriteEXC() == KERN_SUCCESS) + ++success_count; + return success_count == 3; + } + return false; +} + + +uint32_t +RegisterContextMach_x86_64::ConvertRegisterKindToRegisterNumber (uint32_t kind, uint32_t reg) +{ + if (kind == eRegisterKindGeneric) + { + switch (reg) + { + case LLDB_REGNUM_GENERIC_PC: return gpr_rip; + case LLDB_REGNUM_GENERIC_SP: return gpr_rsp; + case LLDB_REGNUM_GENERIC_FP: return gpr_rbp; + case LLDB_REGNUM_GENERIC_FLAGS: return gpr_rflags; + case LLDB_REGNUM_GENERIC_RA: + default: + break; + } + } + else if (kind == eRegisterKindGCC || kind == eRegisterKindDWARF) + { + switch (reg) + { + case gcc_dwarf_gpr_rax: return gpr_rax; + case gcc_dwarf_gpr_rdx: return gpr_rdx; + case gcc_dwarf_gpr_rcx: return gpr_rcx; + case gcc_dwarf_gpr_rbx: return gpr_rbx; + case gcc_dwarf_gpr_rsi: return gpr_rsi; + case gcc_dwarf_gpr_rdi: return gpr_rdi; + case gcc_dwarf_gpr_rbp: return gpr_rbp; + case gcc_dwarf_gpr_rsp: return gpr_rsp; + case gcc_dwarf_gpr_r8: return gpr_r8; + case gcc_dwarf_gpr_r9: return gpr_r9; + case gcc_dwarf_gpr_r10: return gpr_r10; + case gcc_dwarf_gpr_r11: return gpr_r11; + case gcc_dwarf_gpr_r12: return gpr_r12; + case gcc_dwarf_gpr_r13: return gpr_r13; + case gcc_dwarf_gpr_r14: return gpr_r14; + case gcc_dwarf_gpr_r15: return gpr_r15; + case gcc_dwarf_gpr_rip: return gpr_rip; + case gcc_dwarf_fpu_xmm0: return fpu_xmm0; + case gcc_dwarf_fpu_xmm1: return fpu_xmm1; + case gcc_dwarf_fpu_xmm2: return fpu_xmm2; + case gcc_dwarf_fpu_xmm3: return fpu_xmm3; + case gcc_dwarf_fpu_xmm4: return fpu_xmm4; + case gcc_dwarf_fpu_xmm5: return fpu_xmm5; + case gcc_dwarf_fpu_xmm6: return fpu_xmm6; + case gcc_dwarf_fpu_xmm7: return fpu_xmm7; + case gcc_dwarf_fpu_xmm8: return fpu_xmm8; + case gcc_dwarf_fpu_xmm9: return fpu_xmm9; + case gcc_dwarf_fpu_xmm10: return fpu_xmm10; + case gcc_dwarf_fpu_xmm11: return fpu_xmm11; + case gcc_dwarf_fpu_xmm12: return fpu_xmm12; + case gcc_dwarf_fpu_xmm13: return fpu_xmm13; + case gcc_dwarf_fpu_xmm14: return fpu_xmm14; + case gcc_dwarf_fpu_xmm15: return fpu_xmm15; + case gcc_dwarf_fpu_stmm0: return fpu_stmm0; + case gcc_dwarf_fpu_stmm1: return fpu_stmm1; + case gcc_dwarf_fpu_stmm2: return fpu_stmm2; + case gcc_dwarf_fpu_stmm3: return fpu_stmm3; + case gcc_dwarf_fpu_stmm4: return fpu_stmm4; + case gcc_dwarf_fpu_stmm5: return fpu_stmm5; + case gcc_dwarf_fpu_stmm6: return fpu_stmm6; + case gcc_dwarf_fpu_stmm7: return fpu_stmm7; + default: + break; + } + } + else if (kind == eRegisterKindGDB) + { + switch (reg) + { + case gdb_gpr_rax : return gpr_rax; + case gdb_gpr_rbx : return gpr_rbx; + case gdb_gpr_rcx : return gpr_rcx; + case gdb_gpr_rdx : return gpr_rdx; + case gdb_gpr_rsi : return gpr_rsi; + case gdb_gpr_rdi : return gpr_rdi; + case gdb_gpr_rbp : return gpr_rbp; + case gdb_gpr_rsp : return gpr_rsp; + case gdb_gpr_r8 : return gpr_r8; + case gdb_gpr_r9 : return gpr_r9; + case gdb_gpr_r10 : return gpr_r10; + case gdb_gpr_r11 : return gpr_r11; + case gdb_gpr_r12 : return gpr_r12; + case gdb_gpr_r13 : return gpr_r13; + case gdb_gpr_r14 : return gpr_r14; + case gdb_gpr_r15 : return gpr_r15; + case gdb_gpr_rip : return gpr_rip; + case gdb_gpr_rflags : return gpr_rflags; + case gdb_gpr_cs : return gpr_cs; + case gdb_gpr_ss : return gpr_gs; // HACK: For now for "ss", just copy what is in "gs" + case gdb_gpr_ds : return gpr_gs; // HACK: For now for "ds", just copy what is in "gs" + case gdb_gpr_es : return gpr_gs; // HACK: For now for "es", just copy what is in "gs" + case gdb_gpr_fs : return gpr_fs; + case gdb_gpr_gs : return gpr_gs; + case gdb_fpu_stmm0 : return fpu_stmm0; + case gdb_fpu_stmm1 : return fpu_stmm1; + case gdb_fpu_stmm2 : return fpu_stmm2; + case gdb_fpu_stmm3 : return fpu_stmm3; + case gdb_fpu_stmm4 : return fpu_stmm4; + case gdb_fpu_stmm5 : return fpu_stmm5; + case gdb_fpu_stmm6 : return fpu_stmm6; + case gdb_fpu_stmm7 : return fpu_stmm7; + case gdb_fpu_fctrl : return fpu_fctrl; + case gdb_fpu_fstat : return fpu_fstat; + case gdb_fpu_ftag : return fpu_ftag; + case gdb_fpu_fiseg : return fpu_fiseg; + case gdb_fpu_fioff : return fpu_fioff; + case gdb_fpu_foseg : return fpu_foseg; + case gdb_fpu_fooff : return fpu_fooff; + case gdb_fpu_fop : return fpu_fop; + case gdb_fpu_xmm0 : return fpu_xmm0; + case gdb_fpu_xmm1 : return fpu_xmm1; + case gdb_fpu_xmm2 : return fpu_xmm2; + case gdb_fpu_xmm3 : return fpu_xmm3; + case gdb_fpu_xmm4 : return fpu_xmm4; + case gdb_fpu_xmm5 : return fpu_xmm5; + case gdb_fpu_xmm6 : return fpu_xmm6; + case gdb_fpu_xmm7 : return fpu_xmm7; + case gdb_fpu_xmm8 : return fpu_xmm8; + case gdb_fpu_xmm9 : return fpu_xmm9; + case gdb_fpu_xmm10 : return fpu_xmm10; + case gdb_fpu_xmm11 : return fpu_xmm11; + case gdb_fpu_xmm12 : return fpu_xmm12; + case gdb_fpu_xmm13 : return fpu_xmm13; + case gdb_fpu_xmm14 : return fpu_xmm14; + case gdb_fpu_xmm15 : return fpu_xmm15; + case gdb_fpu_mxcsr : return fpu_mxcsr; + default: + break; + } + } + return LLDB_INVALID_REGNUM; +} + +bool +RegisterContextMach_x86_64::HardwareSingleStep (bool enable) +{ + if (ReadGPR(true) != KERN_SUCCESS) + return false; + + const uint64_t trace_bit = 0x100ull; + if (enable) + { + + if (gpr.rflags & trace_bit) + return true; // trace bit is already set, there is nothing to do + else + gpr.rflags |= trace_bit; + } + else + { + if (gpr.rflags & trace_bit) + gpr.rflags &= ~trace_bit; + else + return true; // trace bit is clear, there is nothing to do + } + + return WriteGPR() == KERN_SUCCESS; +} + diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_x86_64.h b/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_x86_64.h new file mode 100644 index 00000000000..4f33bbdc2db --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/RegisterContextMach_x86_64.h @@ -0,0 +1,261 @@ +//===-- RegisterContextMach_x86_64.h ----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_RegisterContextMach_x86_64_h_ +#define liblldb_RegisterContextMach_x86_64_h_ + +// C Includes +// C++ Includes +// Other libraries and framework includes +// Project includes +#include "lldb/lldb-private.h" +#include "lldb/Target/RegisterContext.h" + +class RegisterContextMach_x86_64 : public lldb_private::RegisterContext +{ +public: + RegisterContextMach_x86_64 (lldb_private::Thread &thread, + lldb_private::StackFrame *frame); + + virtual + ~RegisterContextMach_x86_64(); + + virtual void + Invalidate (); + + virtual size_t + GetRegisterCount (); + + virtual const lldb::RegisterInfo * + GetRegisterInfoAtIndex (uint32_t reg); + + virtual size_t + GetRegisterSetCount (); + + virtual const lldb::RegisterSet * + GetRegisterSet (uint32_t set); + + virtual bool + ReadRegisterValue (uint32_t reg, lldb_private::Scalar &value); + + virtual bool + ReadRegisterBytes (uint32_t reg, lldb_private::DataExtractor &data); + + virtual bool + ReadAllRegisterValues (lldb::DataBufferSP &data_sp); + + virtual bool + WriteRegisterValue (uint32_t reg, const lldb_private::Scalar &value); + + virtual bool + WriteRegisterBytes (uint32_t reg, lldb_private::DataExtractor &data, uint32_t data_offset = 0); + + virtual bool + WriteAllRegisterValues (const lldb::DataBufferSP &data_sp); + + virtual uint32_t + ConvertRegisterKindToRegisterNumber (uint32_t kind, uint32_t num); + + virtual bool + HardwareSingleStep (bool enable); + + struct GPR + { + uint64_t rax; + uint64_t rbx; + uint64_t rcx; + uint64_t rdx; + uint64_t rdi; + uint64_t rsi; + uint64_t rbp; + uint64_t rsp; + uint64_t r8; + uint64_t r9; + uint64_t r10; + uint64_t r11; + uint64_t r12; + uint64_t r13; + uint64_t r14; + uint64_t r15; + uint64_t rip; + uint64_t rflags; + uint64_t cs; + uint64_t fs; + uint64_t gs; + }; + + struct MMSReg + { + uint8_t bytes[10]; + uint8_t pad[6]; + }; + + struct XMMReg + { + uint8_t bytes[16]; + }; + + struct FPU + { + uint32_t pad[2]; + uint16_t fcw; // "fctrl" + uint16_t fsw; // "fstat" + uint8_t ftw; // "ftag" + uint8_t pad1; + uint16_t fop; // "fop" + uint32_t ip; // "fioff" + uint16_t cs; // "fiseg" + uint16_t pad2; + uint32_t dp; // "fooff" + uint16_t ds; // "foseg" + uint16_t pad3; + uint32_t mxcsr; + uint32_t mxcsrmask; + MMSReg stmm[8]; + XMMReg xmm[16]; + uint8_t pad4[6*16]; + int pad5; + }; + + struct EXC + { + uint32_t trapno; + uint32_t err; + uint64_t faultvaddr; + }; + +protected: + + typedef enum + { + GPRRegSet = 4, + FPURegSet = 5, + EXCRegSet = 6 + }; + + enum + { + GPRWordCount = sizeof(GPR)/sizeof(uint32_t), + FPUWordCount = sizeof(FPU)/sizeof(uint32_t), + EXCWordCount = sizeof(EXC)/sizeof(uint32_t) + }; + + enum + { + Read = 0, + Write = 1, + kNumErrors = 2 + }; + + GPR gpr; + FPU fpu; + EXC exc; + kern_return_t gpr_errs[2]; // Read/Write errors + kern_return_t fpu_errs[2]; // Read/Write errors + kern_return_t exc_errs[2]; // Read/Write errors + + void + InvalidateAllRegisterStates() + { + SetError (GPRRegSet, Read, -1); + SetError (FPURegSet, Read, -1); + SetError (EXCRegSet, Read, -1); + } + + kern_return_t + GetError (int flavor, uint32_t err_idx) const + { + if (err_idx < kNumErrors) + { + switch (flavor) + { + // When getting all errors, just OR all values together to see if + // we got any kind of error. + case GPRRegSet: return gpr_errs[err_idx]; + case FPURegSet: return fpu_errs[err_idx]; + case EXCRegSet: return exc_errs[err_idx]; + default: break; + } + } + return -1; + } + + bool + SetError (int flavor, uint32_t err_idx, kern_return_t err) + { + if (err_idx < kNumErrors) + { + switch (flavor) + { + case GPRRegSet: + gpr_errs[err_idx] = err; + return true; + + case FPURegSet: + fpu_errs[err_idx] = err; + return true; + + case EXCRegSet: + exc_errs[err_idx] = err; + return true; + + default: break; + } + } + return false; + } + + bool + RegisterSetIsCached (int set) const + { + return GetError(set, Read) == KERN_SUCCESS; + } + + void + LogGPR (lldb_private::Log *log, const char *format, ...); + + kern_return_t + ReadGPR (bool force); + + kern_return_t + ReadFPU (bool force); + + kern_return_t + ReadEXC (bool force); + + kern_return_t + WriteGPR (); + + kern_return_t + WriteFPU (); + + kern_return_t + WriteEXC (); + + kern_return_t + ReadRegisterSet (uint32_t set, bool force); + + kern_return_t + WriteRegisterSet (uint32_t set); + + static uint32_t + GetRegisterNumber (uint32_t reg_kind, uint32_t reg_num); + + static int + GetSetForNativeRegNum (int reg_num); + + static size_t + GetRegisterInfosCount (); + + static const lldb::RegisterInfo * + GetRegisterInfos (); + +}; + +#endif // liblldb_RegisterContextMach_x86_64_h_ diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/ThreadMacOSX.cpp b/lldb/source/Plugins/Process/MacOSX-User/source/ThreadMacOSX.cpp new file mode 100644 index 00000000000..46d84a853d0 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/ThreadMacOSX.cpp @@ -0,0 +1,769 @@ +//===-- ThreadMacOSX.cpp ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + + +#include "ThreadMacOSX.h" + +#include "lldb/Core/ArchSpec.h" +#include "lldb/Core/DataExtractor.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" + +#include "ProcessMacOSX.h" +#include "ProcessMacOSXLog.h" +#include "MachThreadContext.h" +#include "lldb/Target/RegisterContext.h" +#include "lldb/Breakpoint/WatchpointLocation.h" +#include "lldb/Core/StreamString.h" + +using namespace lldb; +using namespace lldb_private; + +//---------------------------------------------------------------------- +// Thread Registers +//---------------------------------------------------------------------- + +ThreadMacOSX::ThreadMacOSX (ProcessMacOSX &process, lldb::tid_t tid) : + Thread(process, tid), + m_fp_pc_pairs(), + m_basic_info(), + m_suspend_count(0), + m_stop_exception(), + m_context() +{ + ProcessMacOSX::CreateArchCalback create_arch_callback = process.GetArchCreateCallback(); + assert(create_arch_callback != NULL); + m_context.reset(create_arch_callback(process.GetArchSpec(), *this)); + assert(m_context.get() != NULL); + m_context->InitializeInstance(); + ::bzero (&m_basic_info, sizeof (m_basic_info)); + ::bzero (&m_ident_info, sizeof (m_ident_info)); + ::bzero (&m_proc_threadinfo, sizeof (m_proc_threadinfo)); + ProcessMacOSXLog::LogIf(PD_LOG_THREAD | PD_LOG_VERBOSE, "ThreadMacOSX::ThreadMacOSX ( pid = %i, tid = 0x%4.4x, )", m_process.GetID(), GetID()); +} + +ThreadMacOSX::~ThreadMacOSX () +{ +} + +#if defined (__i386__) || defined (__x86_64__) + #define MACH_SOFTWARE_BREAKPOINT_DATA_0 EXC_I386_BPT + #define MACH_TRAP_DATA_0 EXC_I386_SGL +#elif defined (__powerpc__) || defined (__ppc__) || defined (__ppc64__) + #define MACH_SOFTWARE_BREAKPOINT_DATA_0 EXC_PPC_BREAKPOINT + +#elif defined (__arm__) + #define MACH_SOFTWARE_BREAKPOINT_DATA_0 EXC_ARM_BREAKPOINT +#endif + + +bool +ThreadMacOSX::GetRawStopReason (Thread::StopInfo *stop_info ) +{ + stop_info->SetThread(this); + + bool success = GetStopException().GetStopInfo(stop_info); + + +#if defined (MACH_SOFTWARE_BREAKPOINT_DATA_0) || defined (MACH_TRAP_DATA_0) + if (stop_info->GetStopReason() == eStopReasonException) + { + if (stop_info->GetExceptionType() == EXC_BREAKPOINT && stop_info->GetExceptionDataCount() == 2) + { + const lldb::addr_t data_0 = stop_info->GetExceptionDataAtIndex(0); +#if defined (MACH_SOFTWARE_BREAKPOINT_DATA_0) + if (data_0 == MACH_SOFTWARE_BREAKPOINT_DATA_0) + { + lldb::addr_t pc = GetRegisterContext()->GetPC(); + lldb::user_id_t break_id = m_process.GetBreakpointSiteList().FindIDByAddress(pc); + if (break_id != LLDB_INVALID_BREAK_ID) + { + stop_info->Clear (); + stop_info->SetStopReasonWithBreakpointSiteID (break_id); + return success; + } + } +#endif +#if defined (MACH_TRAP_DATA_0) + if (data_0 == MACH_TRAP_DATA_0) + { + stop_info->Clear (); + stop_info->SetStopReasonToTrace (); + return success; + } +#endif + } + } +#endif + + if (stop_info->GetStopReason() == eStopReasonException) + { + if (stop_info->GetExceptionType() == EXC_SOFTWARE && + stop_info->GetExceptionDataCount() == 2 && + stop_info->GetExceptionDataAtIndex(0) == EXC_SOFT_SIGNAL) + { + int signo = stop_info->GetExceptionDataAtIndex(1); + stop_info->Clear (); + stop_info->SetStopReasonWithSignal (signo); + } + } + else + { + stop_info->SetStopReasonToNone(); + } + + return success; +} + +const char * +ThreadMacOSX::GetInfo () +{ + return GetBasicInfoAsString(); +} + +bool +ThreadMacOSX::GetIdentifierInfo () +{ +#ifdef THREAD_IDENTIFIER_INFO_COUNT + if (m_ident_info.thread_id == 0) + { + mach_msg_type_number_t count = THREAD_IDENTIFIER_INFO_COUNT; + return ::thread_info (GetID(), THREAD_IDENTIFIER_INFO, (thread_info_t) &m_ident_info, &count) == KERN_SUCCESS; + } +#else + //m_error.SetErrorString("Thread_info doesn't support THREAD_IDENTIFIER_INFO."); +#endif + + return false; +} + +const char * +ThreadMacOSX::GetDispatchQueueName() +{ + if (GetIdentifierInfo ()) + { + if (m_ident_info.dispatch_qaddr == 0) + return NULL; + + uint8_t memory_buffer[8]; + DataExtractor data(memory_buffer, sizeof(memory_buffer), m_process.GetByteOrder(), m_process.GetAddressByteSize()); + ModuleSP module_sp(m_process.GetTarget().GetImages().FindFirstModuleForFileSpec (FileSpec("libSystem.B.dylib"))); + if (module_sp.get() == NULL) + return NULL; + + lldb::addr_t dispatch_queue_offsets_addr = LLDB_INVALID_ADDRESS; + const Symbol *dispatch_queue_offsets_symbol = module_sp->FindFirstSymbolWithNameAndType (ConstString("dispatch_queue_offsets"), eSymbolTypeData); + if (dispatch_queue_offsets_symbol) + dispatch_queue_offsets_addr = dispatch_queue_offsets_symbol->GetValue().GetLoadAddress(&GetProcess()); + + if (dispatch_queue_offsets_addr == LLDB_INVALID_ADDRESS) + return NULL; + + // Excerpt from src/queue_private.h + struct dispatch_queue_offsets_s + { + uint16_t dqo_version; + uint16_t dqo_label; + uint16_t dqo_label_size; + } dispatch_queue_offsets; + + Error error; + if (m_process.ReadMemory (dispatch_queue_offsets_addr, memory_buffer, sizeof(dispatch_queue_offsets), error) == sizeof(dispatch_queue_offsets)) + { + uint32_t data_offset = 0; + if (data.GetU16(&data_offset, &dispatch_queue_offsets.dqo_version, sizeof(dispatch_queue_offsets)/sizeof(uint16_t))) + { + if (m_process.ReadMemory (m_ident_info.dispatch_qaddr, &memory_buffer, data.GetAddressByteSize(), error) == data.GetAddressByteSize()) + { + data_offset = 0; + lldb::addr_t queue_addr = data.GetAddress(&data_offset); + lldb::addr_t label_addr = queue_addr + dispatch_queue_offsets.dqo_label; + const size_t chunk_size = 32; + uint32_t label_pos = 0; + m_dispatch_queue_name.resize(chunk_size, '\0'); + while (1) + { + size_t bytes_read = m_process.ReadMemory (label_addr + label_pos, &m_dispatch_queue_name[label_pos], chunk_size, error); + + if (bytes_read <= 0) + break; + + if (m_dispatch_queue_name.find('\0', label_pos) != std::string::npos) + break; + label_pos += bytes_read; + } + m_dispatch_queue_name.erase(m_dispatch_queue_name.find('\0')); + } + } + } + } + + if (m_dispatch_queue_name.empty()) + return NULL; + return m_dispatch_queue_name.c_str(); +} + +const char * +ThreadMacOSX::GetName () +{ + if (GetIdentifierInfo ()) + ::proc_pidinfo (m_process.GetID(), PROC_PIDTHREADINFO, m_ident_info.thread_handle, &m_proc_threadinfo, sizeof (m_proc_threadinfo)); + + // No thread name, lets return the queue name instead + if (m_proc_threadinfo.pth_name[0] == '\0') + return GetDispatchQueueName(); + + // Return the thread name if there was one + if (m_proc_threadinfo.pth_name[0]) + return m_proc_threadinfo.pth_name; + return NULL; +} + +bool +ThreadMacOSX::WillResume (StateType resume_state) +{ + ThreadWillResume(resume_state); + Thread::WillResume(resume_state); + return true; +} + +void +ThreadMacOSX::RefreshStateAfterStop() +{ + // Invalidate all registers in our register context + GetRegisterContext()->Invalidate(); + + m_context->RefreshStateAfterStop(); + + // We may have suspended this thread so the primary thread could step + // without worrying about race conditions, so lets restore our suspend + // count. + RestoreSuspendCount(); + + // Update the basic information for a thread for suspend count reasons. + ThreadMacOSX::GetBasicInfo(GetID(), &m_basic_info); + m_suspend_count = m_basic_info.suspend_count; + m_basic_info_string.clear(); +} + +uint32_t +ThreadMacOSX::GetStackFrameCount() +{ + if (m_fp_pc_pairs.empty()) + GetStackFrameData(m_fp_pc_pairs); + return m_fp_pc_pairs.size(); +} + +// Make sure that GetStackFrameAtIndex() does NOT call GetStackFrameCount() when +// getting the stack frame at index zero! This way GetStackFrameCount() (via +// GetStackFRameData()) can call this function to get the first frame in order +// to provide the first frame to a lower call for efficiency sake (avoid +// redundant lookups in the frame symbol context). +lldb::StackFrameSP +ThreadMacOSX::GetStackFrameAtIndex (uint32_t idx) +{ + StackFrameSP frame_sp(m_frames.GetFrameAtIndex(idx)); + + if (frame_sp) + return frame_sp; + + // Don't try and fetch a frame while process is running + // Calling IsRunning isn't right here, because IsRunning reads the Public + // state but we need to be able to read the stack frames in the ShouldStop + // methods, which happen before the Public state has been updated. +// if (m_process.IsRunning()) +// return frame_sp; + + // Special case the first frame (idx == 0) so that we don't need to + // know how many stack frames there are to get it. If we need any other + // frames, then we do need to know if "idx" is a valid index. + if (idx == 0) + { + // If this is the first frame, we want to share the thread register + // context with the stack frame at index zero. + GetRegisterContext(); + assert (m_reg_context_sp.get()); + frame_sp.reset (new StackFrame (idx, *this, m_reg_context_sp, m_reg_context_sp->GetFP(), m_reg_context_sp->GetPC())); + } + else if (idx < GetStackFrameCount()) + { + assert (idx < m_fp_pc_pairs.size()); + frame_sp.reset (new StackFrame (idx, *this, m_fp_pc_pairs[idx].first, m_fp_pc_pairs[idx].second)); + } + m_frames.SetFrameAtIndex(idx, frame_sp); + return frame_sp; +} + +void +ThreadMacOSX::ClearStackFrames () +{ + m_fp_pc_pairs.clear(); + Thread::ClearStackFrames(); +} + + + +int32_t +ThreadMacOSX::Suspend() +{ + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_THREAD); + if (log && log->GetMask().IsSet(PD_LOG_VERBOSE)) + log->Printf ("ThreadMacOSX::%s ( )", __FUNCTION__); + lldb::tid_t tid = GetID (); + if (ThreadIDIsValid(tid)) + { + Error err(::thread_suspend (tid), eErrorTypeMachKernel); + if (err.Success()) + m_suspend_count++; + if (log || err.Fail()) + err.PutToLog(log, "::thread_suspend (%4.4x)", tid); + } + return GetSuspendCount(); +} + +int32_t +ThreadMacOSX::Resume() +{ + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_THREAD); + if (log && log->GetMask().IsSet(PD_LOG_VERBOSE)) + log->Printf ("ThreadMacOSX::%s ()", __FUNCTION__); + lldb::tid_t tid = GetID (); + if (ThreadIDIsValid(tid)) + { + while (m_suspend_count > 0) + { + Error err(::thread_resume (tid), eErrorTypeMachKernel); + if (err.Success()) + m_suspend_count--; + if (log || err.Fail()) + err.PutToLog(log, "::thread_resume (%4.4x)", tid); + } + } + return GetSuspendCount(); +} + +bool +ThreadMacOSX::RestoreSuspendCount() +{ + Log *log = ProcessMacOSXLog::GetLogIfAllCategoriesSet(PD_LOG_THREAD); + if (log && log->GetMask().IsSet(PD_LOG_VERBOSE)) + log->Printf ("ThreadMacOSX::%s ( )", __FUNCTION__); + Error err; + lldb::tid_t tid = GetID (); + if (ThreadIDIsValid(tid) == false) + return false; + else if (m_suspend_count > m_basic_info.suspend_count) + { + while (m_suspend_count > m_basic_info.suspend_count) + { + err = ::thread_resume (tid); + if (err.Success()) + --m_suspend_count; + if (log || err.Fail()) + err.PutToLog(log, "::thread_resume (%4.4x)", tid); + } + } + else if (m_suspend_count < m_basic_info.suspend_count) + { + while (m_suspend_count < m_basic_info.suspend_count) + { + err = ::thread_suspend (tid); + if (err.Success()) + --m_suspend_count; + if (log || err.Fail()) + err.PutToLog(log, "::thread_suspend (%4.4x)", tid); + } + } + return m_suspend_count == m_basic_info.suspend_count; +} + + +const char * +ThreadMacOSX::GetBasicInfoAsString () +{ + if (m_basic_info_string.empty()) + { + StreamString sstr; + struct thread_basic_info basicInfo; + + lldb::tid_t tid = GetID (); + if (GetBasicInfo(tid, &basicInfo)) + { +// char run_state_str[32]; +// size_t run_state_str_size = sizeof(run_state_str); +// switch (basicInfo.run_state) +// { +// case TH_STATE_RUNNING: strncpy(run_state_str, "running", run_state_str_size); break; +// case TH_STATE_STOPPED: strncpy(run_state_str, "stopped", run_state_str_size); break; +// case TH_STATE_WAITING: strncpy(run_state_str, "waiting", run_state_str_size); break; +// case TH_STATE_UNINTERRUPTIBLE: strncpy(run_state_str, "uninterruptible", run_state_str_size); break; +// case TH_STATE_HALTED: strncpy(run_state_str, "halted", run_state_str_size); break; +// default: snprintf(run_state_str, run_state_str_size, "%d", basicInfo.run_state); break; // ??? +// } + float user = (float)basicInfo.user_time.seconds + (float)basicInfo.user_time.microseconds / 1000000.0f; + float system = (float)basicInfo.user_time.seconds + (float)basicInfo.user_time.microseconds / 1000000.0f; + sstr.Printf("Thread 0x%4.4x: user=%f system=%f cpu=%d sleep_time=%d", + InferiorThreadID(), + user, + system, + basicInfo.cpu_usage, + basicInfo.sleep_time); + m_basic_info_string.assign (sstr.GetData(), sstr.GetSize()); + } + } + if (m_basic_info_string.empty()) + return NULL; + return m_basic_info_string.c_str(); +} + + +//const uint8_t * +//ThreadMacOSX::SoftwareBreakpointOpcode (size_t break_op_size) const +//{ +// return m_context->SoftwareBreakpointOpcode(break_op_size); +//} + + +lldb::tid_t +ThreadMacOSX::InferiorThreadID() const +{ + mach_msg_type_number_t i; + mach_port_name_array_t names; + mach_port_type_array_t types; + mach_msg_type_number_t ncount, tcount; + lldb::tid_t inferior_tid = LLDB_INVALID_THREAD_ID; + task_t my_task = ::mach_task_self(); + task_t task = GetMacOSXProcess().Task().GetTaskPort(); + + kern_return_t kret = ::mach_port_names (task, &names, &ncount, &types, &tcount); + if (kret == KERN_SUCCESS) + { + lldb::tid_t tid = GetID (); + + for (i = 0; i < ncount; i++) + { + mach_port_t my_name; + mach_msg_type_name_t my_type; + + kret = ::mach_port_extract_right (task, names[i], MACH_MSG_TYPE_COPY_SEND, &my_name, &my_type); + if (kret == KERN_SUCCESS) + { + ::mach_port_deallocate (my_task, my_name); + if (my_name == tid) + { + inferior_tid = names[i]; + break; + } + } + } + // Free up the names and types + ::vm_deallocate (my_task, (vm_address_t) names, ncount * sizeof (mach_port_name_t)); + ::vm_deallocate (my_task, (vm_address_t) types, tcount * sizeof (mach_port_type_t)); + } + return inferior_tid; +} + +bool +ThreadMacOSX::GetBasicInfo(lldb::tid_t thread, struct thread_basic_info *basicInfoPtr) +{ + if (ThreadIDIsValid(thread)) + { + unsigned int info_count = THREAD_BASIC_INFO_COUNT; + kern_return_t err = ::thread_info (thread, THREAD_BASIC_INFO, (thread_info_t) basicInfoPtr, &info_count); + if (err == KERN_SUCCESS) + return true; + } + ::memset (basicInfoPtr, 0, sizeof (struct thread_basic_info)); + return false; +} + + +bool +ThreadMacOSX::ThreadIDIsValid (lldb::tid_t thread) +{ + return thread != 0; +} + +void +ThreadMacOSX::Dump(Log *log, uint32_t index) +{ + const char * thread_run_state = NULL; + + switch (m_basic_info.run_state) + { + case TH_STATE_RUNNING: thread_run_state = "running"; break; // 1 thread is running normally + case TH_STATE_STOPPED: thread_run_state = "stopped"; break; // 2 thread is stopped + case TH_STATE_WAITING: thread_run_state = "waiting"; break; // 3 thread is waiting normally + case TH_STATE_UNINTERRUPTIBLE: thread_run_state = "uninter"; break; // 4 thread is in an uninterruptible wait + case TH_STATE_HALTED: thread_run_state = "halted "; break; // 5 thread is halted at a + default: thread_run_state = "???"; break; + } + + RegisterContext *reg_context = GetRegisterContext(); + log->Printf ("thread[%u] %4.4x (%u): pc: 0x%8.8llx sp: 0x%8.8llx breakID: %d user: %d.%06.6d system: %d.%06.6d cpu: %d policy: %d run_state: %d (%s) flags: %d suspend_count: %d (current %d) sleep_time: %d", + index, + GetID (), + reg_context->GetPC (LLDB_INVALID_ADDRESS), + reg_context->GetSP (LLDB_INVALID_ADDRESS), + m_basic_info.user_time.seconds, m_basic_info.user_time.microseconds, + m_basic_info.system_time.seconds, m_basic_info.system_time.microseconds, + m_basic_info.cpu_usage, + m_basic_info.policy, + m_basic_info.run_state, + thread_run_state, + m_basic_info.flags, + m_basic_info.suspend_count, m_suspend_count, + m_basic_info.sleep_time); + //DumpRegisterState(0); +} + +void +ThreadMacOSX::ThreadWillResume (StateType resume_state) +{ + // Update the thread state to be the state we wanted when the task resumes + SetState (resume_state); + switch (resume_state) + { + case eStateSuspended: + Suspend(); + break; + + case eStateRunning: + case eStateStepping: + Resume(); + break; + } + m_context->ThreadWillResume(); +} + +void +ThreadMacOSX::DidResume () +{ + // TODO: cache current stack frames for next time in case we can match things up?? + ClearStackFrames(); + m_stop_exception.Clear(); + Thread::DidResume(); +} + +bool +ThreadMacOSX::ShouldStop(bool &step_more) +{ +// TODO: REmove this after all is working, Process should be managing this +// for us. +// +// // See if this thread is at a breakpoint? +// lldb::user_id_t breakID = CurrentBreakpoint(); +// +// if (LLDB_BREAK_ID_IS_VALID(breakID)) +// { +// // This thread is sitting at a breakpoint, ask the breakpoint +// // if we should be stopping here. +// if (Process()->Breakpoints().ShouldStop(ProcessID(), ThreadID(), breakID)) +// return true; +// else +// { +// // The breakpoint said we shouldn't stop, but we may have gotten +// // a signal or the user may have requested to stop in some other +// // way. Stop if we have a valid exception (this thread won't if +// // another thread was the reason this process stopped) and that +// // exception, is NOT a breakpoint exception (a common case would +// // be a SIGINT signal). +// if (GetStopException().IsValid() && !GetStopException().IsBreakpoint()) +// return true; +// } +// } +// else +// { + if (m_context->StepNotComplete()) + { + step_more = true; + return false; + } +// // The thread state is used to let us know what the thread was +// // trying to do. ThreadMacOSX::ThreadWillResume() will set the +// // thread state to various values depending if the thread was +// // the current thread and if it was to be single stepped, or +// // resumed. +// if (GetState() == eStateRunning) +// { +// // If our state is running, then we should continue as we are in +// // the process of stepping over a breakpoint. +// return false; +// } +// else +// { +// // Stop if we have any kind of valid exception for this +// // thread. +// if (GetStopException().IsValid()) +// return true; +// } +// } +// return false; + return true; +} + +bool +ThreadMacOSX::NotifyException(MachException::Data& exc) +{ + if (m_stop_exception.IsValid()) + { + // We may have more than one exception for a thread, but we need to + // only remember the one that we will say is the reason we stopped. + // We may have been single stepping and also gotten a signal exception, + // so just remember the most pertinent one. + if (m_stop_exception.IsBreakpoint()) + m_stop_exception = exc; + } + else + { + m_stop_exception = exc; + } +// bool handled = + m_context->NotifyException(exc); +// if (!handled) +// { +// handled = true; +// lldb::addr_t pc = GetPC(); +// lldb::user_id_t breakID = m_process.Breakpoints().FindIDCyAddress(pc); +// SetCurrentBreakpoint(breakID); +// switch (exc.exc_type) +// { +// case EXC_BAD_ACCESS: +// break; +// case EXC_BAD_INSTRUCTION: +// break; +// case EXC_ARITHMETIC: +// break; +// case EXC_EMULATION: +// break; +// case EXC_SOFTWARE: +// break; +// case EXC_BREAKPOINT: +// break; +// case EXC_SYSCALL: +// break; +// case EXC_MACH_SYSCALL: +// break; +// case EXC_RPC_ALERT: +// break; +// } +// } +// return handled; + return true; +} + +RegisterContext * +ThreadMacOSX::GetRegisterContext () +{ + if (m_reg_context_sp.get() == NULL) + m_reg_context_sp.reset (CreateRegisterContextForFrame (NULL)); + return m_reg_context_sp.get(); +} + +RegisterContext * +ThreadMacOSX::CreateRegisterContextForFrame (StackFrame *frame) +{ + return m_context->CreateRegisterContext (frame); +} + +uint32_t +ThreadMacOSX::SetHardwareBreakpoint (const BreakpointSite *bp) +{ + if (bp != NULL) + return GetRegisterContext()->SetHardwareBreakpoint(bp->GetLoadAddress(), bp->GetByteSize()); + return LLDB_INVALID_INDEX32; +} + +uint32_t +ThreadMacOSX::SetHardwareWatchpoint (const WatchpointLocation *wp) +{ + if (wp != NULL) + return GetRegisterContext()->SetHardwareWatchpoint(wp->GetLoadAddress(), wp->GetByteSize(), wp->WatchpointRead(), wp->WatchpointWrite()); + return LLDB_INVALID_INDEX32; +} + + +bool +ThreadMacOSX::SaveFrameZeroState (RegisterCheckpoint &checkpoint) +{ + lldb::StackFrameSP frame_sp(GetStackFrameAtIndex (0)); + if (frame_sp) + { + checkpoint.SetStackID(frame_sp->GetStackID()); + return frame_sp->GetRegisterContext()->ReadAllRegisterValues (checkpoint.GetData()); + } + return false; +} + +bool +ThreadMacOSX::RestoreSaveFrameZero (const RegisterCheckpoint &checkpoint) +{ + lldb::StackFrameSP frame_sp(GetStackFrameAtIndex (0)); + if (frame_sp) + { + bool ret = frame_sp->GetRegisterContext()->WriteAllRegisterValues (checkpoint.GetData()); + + // Clear out all stack frames as our world just changed. + ClearStackFrames(); + frame_sp->GetRegisterContext()->Invalidate(); + + return ret; + } + return false; +} + +bool +ThreadMacOSX::ClearHardwareBreakpoint (const BreakpointSite *bp) +{ + if (bp != NULL && bp->IsHardware()) + return GetRegisterContext()->ClearHardwareBreakpoint(bp->GetHardwareIndex()); + return false; +} + +bool +ThreadMacOSX::ClearHardwareWatchpoint (const WatchpointLocation *wp) +{ + if (wp != NULL && wp->IsHardware()) + return GetRegisterContext()->ClearHardwareWatchpoint(wp->GetHardwareIndex()); + return false; +} + +size_t +ThreadMacOSX::GetStackFrameData(std::vector<std::pair<lldb::addr_t, lldb::addr_t> >& fp_pc_pairs) +{ + lldb::StackFrameSP frame_sp(GetStackFrameAtIndex (0)); + return m_context->GetStackFrameData(frame_sp.get(), fp_pc_pairs); +} + + +//void +//ThreadMacOSX::NotifyBreakpointChanged (const BreakpointSite *bp) +//{ +// if (bp) +// { +// lldb::user_id_t breakID = bp->GetID(); +// if (bp->IsEnabled()) +// { +// if (bp->Address() == GetPC()) +// { +// SetCurrentBreakpoint(breakID); +// } +// } +// else +// { +// if (CurrentBreakpoint() == breakID) +// { +// SetCurrentBreakpoint(LLDB_INVALID_BREAK_ID); +// } +// } +// } +//} +// + + diff --git a/lldb/source/Plugins/Process/MacOSX-User/source/ThreadMacOSX.h b/lldb/source/Plugins/Process/MacOSX-User/source/ThreadMacOSX.h new file mode 100644 index 00000000000..0039f3639f1 --- /dev/null +++ b/lldb/source/Plugins/Process/MacOSX-User/source/ThreadMacOSX.h @@ -0,0 +1,159 @@ +//===-- ThreadMacOSX.h ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_ThreadMacOSX_h_ +#define liblldb_ThreadMacOSX_h_ + +#include <libproc.h> + +#include "lldb/Target/Process.h" +#include "lldb/Target/Thread.h" +#include "MachException.h" + +class ProcessMacOSX; +class MachThreadContext; + +class ThreadMacOSX : public lldb_private::Thread +{ +public: + ThreadMacOSX (ProcessMacOSX &process, lldb::tid_t tid); + + virtual + ~ThreadMacOSX (); + + virtual bool + WillResume (lldb::StateType resume_state); + + virtual void + RefreshStateAfterStop(); + + virtual const char * + GetInfo (); + + virtual const char * + GetName (); + + virtual lldb_private::RegisterContext * + GetRegisterContext (); + + virtual lldb_private::RegisterContext * + CreateRegisterContextForFrame (lldb_private::StackFrame *frame); + + virtual bool + SaveFrameZeroState (RegisterCheckpoint &checkpoint); + + virtual bool + RestoreSaveFrameZero (const RegisterCheckpoint &checkpoint); + + virtual uint32_t + GetStackFrameCount(); + + virtual lldb::StackFrameSP + GetStackFrameAtIndex (uint32_t idx); + + virtual void + ClearStackFrames (); + + ProcessMacOSX & + GetMacOSXProcess () + { + return (ProcessMacOSX &)m_process; + } + + const ProcessMacOSX & + GetMacOSXProcess () const + { + return (ProcessMacOSX &)m_process; + } + + void + Dump (lldb_private::Log *log, uint32_t index); + + lldb::tid_t + InferiorThreadID () const; + + static bool + ThreadIDIsValid (lldb::tid_t thread); + + int32_t + Resume (); + + int32_t + Suspend (); + + int32_t + GetSuspendCount () const { return m_suspend_count; } + + bool + RestoreSuspendCount (); + + uint32_t + SetHardwareBreakpoint (const lldb_private::BreakpointSite *bp); + + uint32_t + SetHardwareWatchpoint (const lldb_private::WatchpointLocation *wp); + + bool + ClearHardwareBreakpoint (const lldb_private::BreakpointSite *bp); + + bool + ClearHardwareWatchpoint (const lldb_private::WatchpointLocation *wp); + + void + ThreadWillResume (lldb::StateType resume_state); + + virtual void + DidResume (); + + bool + ShouldStop (bool &step_more); + + bool + NotifyException (MachException::Data& exc); + + const MachException::Data& + GetStopException () { return m_stop_exception; } + + const char * + GetBasicInfoAsString (); + + size_t + GetStackFrameData (std::vector<std::pair<lldb::addr_t, lldb::addr_t> >& fp_pc_pairs); + + virtual bool + GetRawStopReason (lldb_private::Thread::StopInfo *stop_info); + +protected: + bool + GetIdentifierInfo (); + + const char * + GetDispatchQueueName(); + + static bool + GetBasicInfo (lldb::tid_t threadID, struct thread_basic_info *basic_info); + + //------------------------------------------------------------------ + // Member variables. + //------------------------------------------------------------------ + std::vector<std::pair<lldb::addr_t, lldb::addr_t> > m_fp_pc_pairs; + struct thread_basic_info m_basic_info; // Basic information for a thread used to see if a thread is valid + std::string m_basic_info_string;// Basic thread info as a C string. +#ifdef THREAD_IDENTIFIER_INFO_COUNT + thread_identifier_info_data_t m_ident_info; + struct proc_threadinfo m_proc_threadinfo; + std::string m_dispatch_queue_name; +#endif + int32_t m_suspend_count; // The current suspend count + MachException::Data m_stop_exception; // The best exception that describes why this thread is stopped + std::auto_ptr<MachThreadContext> m_context; // The arch specific thread context for this thread (register state and more) + +}; + +#endif // liblldb_ThreadMacOSX_h_ |