diff options
author | Greg Clayton <gclayton@apple.com> | 2012-04-14 01:42:46 +0000 |
---|---|---|
committer | Greg Clayton <gclayton@apple.com> | 2012-04-14 01:42:46 +0000 |
commit | d1cf11a74df14374c8be20dd43e805d2dfb1de07 (patch) | |
tree | 8d93d9fd95aa1825bf1ae394233bee05208c16f4 /lldb | |
parent | f5c87882a01b733a98a0138688d2ff0c9e586848 (diff) | |
download | bcm5719-llvm-d1cf11a74df14374c8be20dd43e805d2dfb1de07.tar.gz bcm5719-llvm-d1cf11a74df14374c8be20dd43e805d2dfb1de07.zip |
Added a new host function that allows us to run shell command and get the output from them along with the status and signal:
Error
Host::RunShellCommand (const char *command,
const char *working_dir,
int *status_ptr,
int *signo_ptr,
std::string *command_output_ptr,
uint32_t timeout_sec);
This will allow us to use this functionality in the host lldb_private::Platform, and also use it in our lldb-platform binary. It leverages the existing code in Host::LaunchProcess and ProcessLaunchInfo.
llvm-svn: 154730
Diffstat (limited to 'lldb')
-rw-r--r-- | lldb/include/lldb/Host/Host.h | 8 | ||||
-rw-r--r-- | lldb/include/lldb/Target/Process.h | 5 | ||||
-rw-r--r-- | lldb/source/Commands/CommandObjectPlatform.cpp | 70 | ||||
-rw-r--r-- | lldb/source/Host/common/Host.cpp | 158 | ||||
-rw-r--r-- | lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp | 7 | ||||
-rw-r--r-- | lldb/source/Target/Platform.cpp | 7 | ||||
-rw-r--r-- | lldb/source/Target/Process.cpp | 66 |
7 files changed, 296 insertions, 25 deletions
diff --git a/lldb/include/lldb/Host/Host.h b/lldb/include/lldb/Host/Host.h index aa1967613b6..e41f15ac3b4 100644 --- a/lldb/include/lldb/Host/Host.h +++ b/lldb/include/lldb/Host/Host.h @@ -414,6 +414,14 @@ public: static Error LaunchProcess (ProcessLaunchInfo &launch_info); + static Error + RunShellCommand (const char *command, // Shouldn't be NULL + const char *working_dir, // Pass NULL to use the current working directory + int *status_ptr, // Pass NULL if you don't want the process exit status + int *signo_ptr, // Pass NULL if you don't want the signal that caused the process to exit + std::string *command_output, // Pass NULL if you don't want the command output + uint32_t timeout_sec); // Timeout in seconds to wait for shell program to finish + static lldb::DataBufferSP GetAuxvData (lldb_private::Process *process); diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index d5804d9c92b..cca72d1043e 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -733,7 +733,10 @@ public: } bool - ConvertArgumentsForLaunchingInShell (Error &error, bool localhost); + ConvertArgumentsForLaunchingInShell (Error &error, + bool localhost, + bool will_debug, + bool first_arg_is_full_shell_command); void SetMonitorProcessCallback (Host::MonitorChildProcessCallback callback, diff --git a/lldb/source/Commands/CommandObjectPlatform.cpp b/lldb/source/Commands/CommandObjectPlatform.cpp index 9618c6cecb4..e4d02733276 100644 --- a/lldb/source/Commands/CommandObjectPlatform.cpp +++ b/lldb/source/Commands/CommandObjectPlatform.cpp @@ -870,6 +870,75 @@ private: DISALLOW_COPY_AND_ASSIGN (CommandObjectPlatformProcess); }; + +class CommandObjectPlatformShell : public CommandObject +{ +public: + CommandObjectPlatformShell (CommandInterpreter &interpreter) : + CommandObject (interpreter, + "platform shell", + "Run a shell command on a the selected platform.", + "platform shell <shell-command>", + 0) + { + } + + virtual + ~CommandObjectPlatformShell () + { + } + + virtual bool + Execute (Args& command, + CommandReturnObject &result) + { + return false; + } + + virtual bool + WantsRawCommandString() { return true; } + + bool + ExecuteRawCommandString (const char *raw_command_line, CommandReturnObject &result) + { + // TODO: Implement "Platform::RunShellCommand()" and switch over to using + // the current platform when it is in the interface. + const char *working_dir = NULL; + std::string output; + int status = -1; + int signo = -1; + Error error (Host::RunShellCommand (raw_command_line, working_dir, &status, &signo, &output, 10)); + if (!output.empty()) + result.GetOutputStream().PutCString(output.c_str()); + if (status > 0) + { + if (signo > 0) + { + const char *signo_cstr = Host::GetSignalAsCString(signo); + if (signo_cstr) + result.GetOutputStream().Printf("error: command returned with status %i and signal %s\n", status, signo_cstr); + else + result.GetOutputStream().Printf("error: command returned with status %i and signal %i\n", status, signo); + } + else + result.GetOutputStream().Printf("error: command returned with status %i\n", status); + } + + if (error.Fail()) + { + result.AppendError(error.AsCString()); + result.SetStatus (eReturnStatusFailed); + } + else + { + result.SetStatus (eReturnStatusSuccessFinishResult); + } + return true; + } + +protected: +}; + //---------------------------------------------------------------------- // CommandObjectPlatform constructor //---------------------------------------------------------------------- @@ -885,6 +954,7 @@ CommandObjectPlatform::CommandObjectPlatform(CommandInterpreter &interpreter) : LoadSubCommand ("connect", CommandObjectSP (new CommandObjectPlatformConnect (interpreter))); LoadSubCommand ("disconnect", CommandObjectSP (new CommandObjectPlatformDisconnect (interpreter))); LoadSubCommand ("process", CommandObjectSP (new CommandObjectPlatformProcess (interpreter))); + LoadSubCommand ("shell", CommandObjectSP (new CommandObjectPlatformShell (interpreter))); } diff --git a/lldb/source/Host/common/Host.cpp b/lldb/source/Host/common/Host.cpp index f1fc24f85fd..aeff1fff659 100644 --- a/lldb/source/Host/common/Host.cpp +++ b/lldb/source/Host/common/Host.cpp @@ -1239,6 +1239,164 @@ Host::GetDummyTarget (lldb_private::Debugger &debugger) return dummy_target; } +struct ShellInfo +{ + ShellInfo () : + process_reaped (false), + can_delete (false), + pid (LLDB_INVALID_PROCESS_ID), + signo(-1), + status(-1) + { + } + + lldb_private::Predicate<bool> process_reaped; + lldb_private::Predicate<bool> can_delete; + lldb::pid_t pid; + int signo; + int status; +}; + +static bool +MonitorShellCommand (void *callback_baton, + lldb::pid_t pid, + bool exited, // True if the process did exit + int signo, // Zero for no signal + int status) // Exit value of process if signal is zero +{ + ShellInfo *shell_info = (ShellInfo *)callback_baton; + shell_info->pid = pid; + shell_info->signo = signo; + shell_info->status = status; + // Let the thread running Host::RunShellCommand() know that the process + // exited and that ShellInfo has been filled in by broadcasting to it + shell_info->process_reaped.SetValue(1, eBroadcastAlways); + // Now wait for a handshake back from that thread running Host::RunShellCommand + // so we know that we can delete shell_info_ptr + shell_info->can_delete.WaitForValueEqualTo(true); + // Sleep a bit to allow the shell_info->can_delete.SetValue() to complete... + usleep(1000); + // Now delete the shell info that was passed into this function + delete shell_info; + return true; +} + +Error +Host::RunShellCommand (const char *command, + const char *working_dir, + int *status_ptr, + int *signo_ptr, + std::string *command_output_ptr, + uint32_t timeout_sec) +{ + Error error; + ProcessLaunchInfo launch_info; + launch_info.SetShell("/bin/bash"); + launch_info.GetArguments().AppendArgument(command); + const bool localhost = true; + const bool will_debug = false; + const bool first_arg_is_full_shell_command = true; + launch_info.ConvertArgumentsForLaunchingInShell (error, + localhost, + will_debug, + first_arg_is_full_shell_command); + + if (working_dir) + launch_info.SetWorkingDirectory(working_dir); + char output_file_path_buffer[L_tmpnam]; + const char *output_file_path = NULL; + if (command_output_ptr) + { + // Create a temporary file to get the stdout/stderr and redirect the + // output of the command into this file. We will later read this file + // if all goes well and fill the data into "command_output_ptr" + output_file_path = ::tmpnam(output_file_path_buffer); + launch_info.AppendSuppressFileAction (STDIN_FILENO, true, false); + launch_info.AppendOpenFileAction(STDOUT_FILENO, output_file_path, false, true); + launch_info.AppendDuplicateFileAction(STDERR_FILENO, STDOUT_FILENO); + } + else + { + launch_info.AppendSuppressFileAction (STDIN_FILENO, true, false); + launch_info.AppendSuppressFileAction (STDOUT_FILENO, false, true); + launch_info.AppendSuppressFileAction (STDERR_FILENO, false, true); + } + + // The process monitor callback will delete the 'shell_info_ptr' below... + std::auto_ptr<ShellInfo> shell_info_ap (new ShellInfo()); + + const bool monitor_signals = false; + launch_info.SetMonitorProcessCallback(MonitorShellCommand, shell_info_ap.get(), monitor_signals); + + error = LaunchProcess (launch_info); + const lldb::pid_t pid = launch_info.GetProcessID(); + if (pid != LLDB_INVALID_PROCESS_ID) + { + // The process successfully launched, so we can defer ownership of + // "shell_info" to the MonitorShellCommand callback function that will + // get called when the process dies. We release the std::auto_ptr as it + // doesn't need to delete the ShellInfo anymore. + ShellInfo *shell_info = shell_info_ap.release(); + TimeValue timeout_time(TimeValue::Now()); + timeout_time.OffsetWithSeconds(timeout_sec); + bool timed_out = false; + shell_info->process_reaped.WaitForValueEqualTo(true, &timeout_time, &timed_out); + if (timed_out) + { + error.SetErrorString("timed out waiting for shell command to complete"); + + // Kill the process since it didn't complete withint the timeout specified + ::kill (pid, SIGKILL); + // Wait for the monitor callback to get the message + timeout_time = TimeValue::Now(); + timeout_time.OffsetWithSeconds(1); + timed_out = false; + shell_info->process_reaped.WaitForValueEqualTo(true, &timeout_time, &timed_out); + } + else + { + if (status_ptr) + *status_ptr = shell_info->status; + + if (signo_ptr) + *signo_ptr = shell_info->signo; + + if (command_output_ptr) + { + command_output_ptr->clear(); + FileSpec file_spec(output_file_path, File::eOpenOptionRead); + uint64_t file_size = file_spec.GetByteSize(); + if (file_size > 0) + { + if (file_size > command_output_ptr->max_size()) + { + error.SetErrorStringWithFormat("shell command output is too large to fit into a std::string"); + } + else + { + command_output_ptr->resize(file_size); + file_spec.ReadFileContents(0, &((*command_output_ptr)[0]), command_output_ptr->size(), &error); + } + } + } + } + shell_info->can_delete.SetValue(true, eBroadcastAlways); + } + else + { + error.SetErrorString("failed to get process ID"); + } + + if (output_file_path) + ::unlink (output_file_path); + // Handshake with the monitor thread, or just let it know in advance that + // it can delete "shell_info" in case we timed out and were not able to kill + // the process... + return error; +} + + + #if !defined (__APPLE__) bool Host::OpenFileInExternalEditor (const FileSpec &file_spec, uint32_t line_no) diff --git a/lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp b/lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp index 58200da5182..c1dc25de277 100644 --- a/lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp +++ b/lldb/source/Plugins/Platform/Linux/PlatformLinux.cpp @@ -330,7 +330,12 @@ PlatformLinux::LaunchProcess (ProcessLaunchInfo &launch_info) if (launch_info.GetFlags().Test (eLaunchFlagLaunchInShell)) { const bool is_localhost = true; - if (!launch_info.ConvertArgumentsForLaunchingInShell (error, is_localhost)) + const bool will_debug = launch_info.GetFlags().Test(eLaunchFlagDebug); + const bool first_arg_is_full_shell_command = false; + if (!launch_info.ConvertArgumentsForLaunchingInShell (error, + is_localhost, + will_debug, + first_arg_is_full_shell_command)) return error; } error = Platform::LaunchProcess (launch_info); diff --git a/lldb/source/Target/Platform.cpp b/lldb/source/Target/Platform.cpp index dcb8ba10c52..03b9d91aed3 100644 --- a/lldb/source/Target/Platform.cpp +++ b/lldb/source/Target/Platform.cpp @@ -574,7 +574,12 @@ Platform::LaunchProcess (ProcessLaunchInfo &launch_info) if (launch_info.GetFlags().Test (eLaunchFlagLaunchInShell)) { const bool is_localhost = true; - if (!launch_info.ConvertArgumentsForLaunchingInShell (error, is_localhost)) + const bool will_debug = launch_info.GetFlags().Test(eLaunchFlagDebug); + const bool first_arg_is_full_shell_command = false; + if (!launch_info.ConvertArgumentsForLaunchingInShell (error, + is_localhost, + will_debug, + first_arg_is_full_shell_command)) return error; } diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 93619b062cd..c32b4b1305f 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -303,7 +303,10 @@ ProcessLaunchInfo::FinalizeFileActions (Target *target, bool default_to_use_pty) bool -ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell (Error &error, bool localhost) +ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell (Error &error, + bool localhost, + bool will_debug, + bool first_arg_is_full_shell_command) { error.Clear(); @@ -334,37 +337,56 @@ ProcessLaunchInfo::ConvertArgumentsForLaunchingInShell (Error &error, bool local Args shell_arguments; std::string safe_arg; shell_arguments.AppendArgument (shell_executable); - StreamString shell_command; shell_arguments.AppendArgument ("-c"); - shell_command.PutCString ("exec"); - if (GetArchitecture().IsValid()) - { - shell_command.Printf(" /usr/bin/arch -arch %s", GetArchitecture().GetArchitectureName()); - // Set the resume count to 2: - // 1 - stop in shell - // 2 - stop in /usr/bin/arch - // 3 - then we will stop in our program - SetResumeCount(2); - } - else + + StreamString shell_command; + if (will_debug) { - // Set the resume count to 1: - // 1 - stop in shell - // 2 - then we will stop in our program - SetResumeCount(1); + shell_command.PutCString ("exec"); + if (GetArchitecture().IsValid()) + { + shell_command.Printf(" /usr/bin/arch -arch %s", GetArchitecture().GetArchitectureName()); + // Set the resume count to 2: + // 1 - stop in shell + // 2 - stop in /usr/bin/arch + // 3 - then we will stop in our program + SetResumeCount(2); + } + else + { + // Set the resume count to 1: + // 1 - stop in shell + // 2 - then we will stop in our program + SetResumeCount(1); + } } const char **argv = GetArguments().GetConstArgumentVector (); if (argv) { - for (size_t i=0; argv[i] != NULL; ++i) + if (first_arg_is_full_shell_command) { - const char *arg = Args::GetShellSafeArgument (argv[i], safe_arg); - shell_command.Printf(" %s", arg); + // There should only be one argument that is the shell command itself to be used as is + if (argv[0] && !argv[1]) + shell_command.Printf("%s", argv[0]); + else + return false; + } + else + { + for (size_t i=0; argv[i] != NULL; ++i) + { + const char *arg = Args::GetShellSafeArgument (argv[i], safe_arg); + shell_command.Printf(" %s", arg); + } } + shell_arguments.AppendArgument (shell_command.GetString().c_str()); } - shell_arguments.AppendArgument (shell_command.GetString().c_str()); - + else + { + return false; + } + m_executable.SetFile(shell_executable, false); m_arguments = shell_arguments; return true; |