diff --git a/lldb/bindings/interface/SBPlatform.i b/lldb/bindings/interface/SBPlatform.i --- a/lldb/bindings/interface/SBPlatform.i +++ b/lldb/bindings/interface/SBPlatform.i @@ -45,6 +45,7 @@ class SBPlatformShellCommand { public: + SBPlatformShellCommand (const char *shell_interpreter, const char *shell_command); SBPlatformShellCommand (const char *shell_command); SBPlatformShellCommand (const SBPlatformShellCommand &rhs); diff --git a/lldb/include/lldb/API/SBPlatform.h b/lldb/include/lldb/API/SBPlatform.h --- a/lldb/include/lldb/API/SBPlatform.h +++ b/lldb/include/lldb/API/SBPlatform.h @@ -51,6 +51,8 @@ class LLDB_API SBPlatformShellCommand { public: + SBPlatformShellCommand(const char *shell_interpreter, + const char *shell_command); SBPlatformShellCommand(const char *shell_command); SBPlatformShellCommand(const SBPlatformShellCommand &rhs); diff --git a/lldb/include/lldb/Target/Platform.h b/lldb/include/lldb/Target/Platform.h --- a/lldb/include/lldb/Target/Platform.h +++ b/lldb/include/lldb/Target/Platform.h @@ -629,7 +629,8 @@ // the process to exit std::string *command_output, // Pass nullptr if you don't want the command output - const Timeout &timeout); + const Timeout &timeout, + const bool run_in_default_shell = true); virtual void SetLocalCacheDirectory(const char *local); diff --git a/lldb/include/lldb/Target/RemoteAwarePlatform.h b/lldb/include/lldb/Target/RemoteAwarePlatform.h --- a/lldb/include/lldb/Target/RemoteAwarePlatform.h +++ b/lldb/include/lldb/Target/RemoteAwarePlatform.h @@ -71,7 +71,8 @@ Status RunShellCommand(const char *command, const FileSpec &working_dir, int *status_ptr, int *signo_ptr, std::string *command_output, - const Timeout &timeout) override; + const Timeout &timeout, + const bool run_in_default_shell = true) override; const char *GetHostname() override; UserIDResolver &GetUserIDResolver() override; diff --git a/lldb/source/API/SBPlatform.cpp b/lldb/source/API/SBPlatform.cpp --- a/lldb/source/API/SBPlatform.cpp +++ b/lldb/source/API/SBPlatform.cpp @@ -50,8 +50,31 @@ // PlatformShellCommand struct PlatformShellCommand { + PlatformShellCommand(const char *command_interpreter, + const char *shell_command) + : m_command(), m_working_dir(), m_status(0), m_signo(0), + m_run_in_default_shell(false) { + std::string full_command; + + if (command_interpreter && command_interpreter[0]) { + full_command += command_interpreter; + full_command += " -c "; + } + + if (shell_command && shell_command[0]) { + full_command += " \""; + full_command += shell_command; + full_command += "\""; + } + + if (!full_command.empty()) { + m_command = full_command.c_str(); + } + } + PlatformShellCommand(const char *shell_command = nullptr) - : m_command(), m_working_dir(), m_status(0), m_signo(0) { + : m_command(), m_working_dir(), m_status(0), m_signo(0), + m_run_in_default_shell(true) { if (shell_command && shell_command[0]) m_command = shell_command; } @@ -64,6 +87,7 @@ int m_status; int m_signo; Timeout> m_timeout = llvm::None; + bool m_run_in_default_shell; }; // SBPlatformConnectOptions SBPlatformConnectOptions::SBPlatformConnectOptions(const char *url) @@ -163,6 +187,13 @@ } // SBPlatformShellCommand +SBPlatformShellCommand::SBPlatformShellCommand(const char *shell_interpreter, + const char *shell_command) + : m_opaque_ptr(new PlatformShellCommand(shell_interpreter, shell_command)) { + LLDB_RECORD_CONSTRUCTOR(SBPlatformShellCommand, (const char *, const char *), + shell_interpreter, shell_command); +} + SBPlatformShellCommand::SBPlatformShellCommand(const char *shell_command) : m_opaque_ptr(new PlatformShellCommand(shell_command)) { LLDB_RECORD_CONSTRUCTOR(SBPlatformShellCommand, (const char *), @@ -557,11 +588,12 @@ if (working_dir) shell_command.SetWorkingDirectory(working_dir); } - return platform_sp->RunShellCommand(command, FileSpec(working_dir), - &shell_command.m_opaque_ptr->m_status, - &shell_command.m_opaque_ptr->m_signo, - &shell_command.m_opaque_ptr->m_output, - shell_command.m_opaque_ptr->m_timeout); + return platform_sp->RunShellCommand( + command, FileSpec(working_dir), &shell_command.m_opaque_ptr->m_status, + &shell_command.m_opaque_ptr->m_signo, + &shell_command.m_opaque_ptr->m_output, + shell_command.m_opaque_ptr->m_timeout, + shell_command.m_opaque_ptr->m_run_in_default_shell); })); } diff --git a/lldb/source/Commands/CommandObjectPlatform.cpp b/lldb/source/Commands/CommandObjectPlatform.cpp --- a/lldb/source/Commands/CommandObjectPlatform.cpp +++ b/lldb/source/Commands/CommandObjectPlatform.cpp @@ -1611,6 +1611,29 @@ else m_timeout = std::chrono::seconds(timeout_sec); break; + case 'i': { + if (option_arg.empty()) { + error.SetErrorStringWithFormat( + "missing shell interpreter path for option -i|--interpreter."); + return error; + } + + if (!FileSystem::Instance().Exists(option_arg)) { + error.SetErrorStringWithFormat("File \"%s\" doesn't exist.", + option_arg.str().c_str()); + return error; + } + + if (!FileSystem::Instance().Readable(option_arg)) { + error.SetErrorStringWithFormat("File \"%s\" is not readable.", + option_arg.str().c_str()); + return error; + } + + m_shell_interpreter.SetFile(option_arg, FileSpec::Style::native); + + break; + } default: llvm_unreachable("Unimplemented option"); } @@ -1621,10 +1644,12 @@ void OptionParsingStarting(ExecutionContext *execution_context) override { m_timeout.reset(); m_use_host_platform = false; + m_shell_interpreter.Clear(); } Timeout m_timeout = std::chrono::seconds(10); bool m_use_host_platform; + FileSpec m_shell_interpreter; }; CommandObjectPlatformShell(CommandInterpreter &interpreter) @@ -1650,7 +1675,6 @@ const bool is_alias = !raw_command_line.contains("platform"); OptionsWithRaw args(raw_command_line); - const char *expr = args.GetRawPart().c_str(); if (args.HasArgs()) if (!ParseOptions(args.GetArgs(), result)) @@ -1662,6 +1686,14 @@ return false; } + std::string expr; + bool use_default_shell = true; + if (m_options.m_shell_interpreter) { + expr += m_options.m_shell_interpreter.GetPath() + " -c "; + use_default_shell = false; + } + expr += args.GetRawPart(); + PlatformSP platform_sp( m_options.m_use_host_platform ? Platform::GetHostPlatform() @@ -1672,8 +1704,9 @@ std::string output; int status = -1; int signo = -1; - error = (platform_sp->RunShellCommand(expr, working_dir, &status, &signo, - &output, m_options.m_timeout)); + error = (platform_sp->RunShellCommand( + expr.c_str(), working_dir, &status, &signo, &output, + m_options.m_timeout, use_default_shell)); if (!output.empty()) result.GetOutputStream().PutCString(output); if (status > 0) { diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -631,6 +631,8 @@ Desc<"Run the commands on the host shell when enabled.">; def platform_shell_timeout : Option<"timeout", "t">, Arg<"Value">, Desc<"Seconds to wait for the remote host to finish running the command.">; + def platform_shell_interpreter : Option<"interpreter", "i">, Arg<"Path">, + Desc<"Shell interpreter path. This is the binary used to run the command.">; } let Command = "process attach" in { diff --git a/lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.h b/lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.h --- a/lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.h +++ b/lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.h @@ -148,7 +148,8 @@ // process to exit std::string *command_output, // Pass NULL if you don't want the command output - const lldb_private::Timeout &timeout) override; + const lldb_private::Timeout &timeout, + const bool run_in_default_shell = true) override; void CalculateTrapHandlerSymbolNames() override; diff --git a/lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp b/lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp --- a/lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp +++ b/lldb/source/Plugins/Platform/gdb-server/PlatformRemoteGDBServer.cpp @@ -719,9 +719,10 @@ // process to exit std::string *command_output, // Pass NULL if you don't want the command output - const Timeout &timeout) { + const Timeout &timeout, const bool run_in_default_shell) { return m_gdb_client.RunShellCommand(command, working_dir, status_ptr, - signo_ptr, command_output, timeout); + signo_ptr, command_output, timeout, + run_in_default_shell); } void PlatformRemoteGDBServer::CalculateTrapHandlerSymbolNames() { diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -407,7 +407,8 @@ // the process to exit std::string *command_output, // Pass nullptr if you don't want the command output - const Timeout &timeout); + const Timeout &timeout, + const bool run_in_default_shell = true); bool CalculateMD5(const FileSpec &file_spec, uint64_t &high, uint64_t &low); diff --git a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp --- a/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -2825,7 +2825,7 @@ // process to exit std::string *command_output, // Pass NULL if you don't want the command output - const Timeout &timeout) { + const Timeout &timeout, const bool run_in_default_shell) { lldb_private::StreamString stream; stream.PutCString("qPlatform_shell:"); stream.PutBytesAsRawHex8(command, strlen(command)); diff --git a/lldb/source/Target/Platform.cpp b/lldb/source/Target/Platform.cpp --- a/lldb/source/Target/Platform.cpp +++ b/lldb/source/Target/Platform.cpp @@ -1327,10 +1327,10 @@ // process to exit std::string *command_output, // Pass nullptr if you don't want the command output - const Timeout &timeout) { + const Timeout &timeout, const bool run_in_default_shell) { if (IsHost()) return Host::RunShellCommand(command, working_dir, status_ptr, signo_ptr, - command_output, timeout); + command_output, timeout, run_in_default_shell); else return Status("unimplemented"); } diff --git a/lldb/source/Target/RemoteAwarePlatform.cpp b/lldb/source/Target/RemoteAwarePlatform.cpp --- a/lldb/source/Target/RemoteAwarePlatform.cpp +++ b/lldb/source/Target/RemoteAwarePlatform.cpp @@ -170,16 +170,19 @@ return error; } -Status RemoteAwarePlatform::RunShellCommand( - const char *command, const FileSpec &working_dir, int *status_ptr, - int *signo_ptr, std::string *command_output, - const Timeout &timeout) { +Status RemoteAwarePlatform::RunShellCommand(const char *command, + const FileSpec &working_dir, + int *status_ptr, int *signo_ptr, + std::string *command_output, + const Timeout &timeout, + const bool run_in_default_shell) { if (IsHost()) return Host::RunShellCommand(command, working_dir, status_ptr, signo_ptr, - command_output, timeout); + command_output, timeout, run_in_default_shell); if (m_remote_platform_sp) return m_remote_platform_sp->RunShellCommand( - command, working_dir, status_ptr, signo_ptr, command_output, timeout); + command, working_dir, status_ptr, signo_ptr, command_output, timeout, + run_in_default_shell); return Status("unable to run a remote command without a platform"); } diff --git a/lldb/test/API/commands/platform/basic/TestPlatformCommand.py b/lldb/test/API/commands/platform/basic/TestPlatformCommand.py --- a/lldb/test/API/commands/platform/basic/TestPlatformCommand.py +++ b/lldb/test/API/commands/platform/basic/TestPlatformCommand.py @@ -92,3 +92,24 @@ "error: timed out waiting for shell command to complete"]) self.expect("shell -t 1 -- sleep 3", error=True, substrs=[ "error: timed out waiting for shell command to complete"]) + + @expectedFailureAll(oslist=["windows"]) + @no_debug_info_test + def test_shell_interpreter(self): + """ Test a shell with a different interpreter """ + platform = self.dbg.GetSelectedPlatform() + self.assertTrue(platform.IsValid()) + + sh_cmd = lldb.SBPlatformShellCommand('/bin/sh', 'echo $0') + self.assertIn('/bin/sh', sh_cmd.GetCommand()) + self.assertIn('echo $0', sh_cmd.GetCommand()) + + err = platform.Run(sh_cmd) + self.assertTrue(err.Success()) + self.assertIn("/bin/sh", sh_cmd.GetOutput()) + + @expectedFailureAll(oslist=["windows"]) + @no_debug_info_test + def test_host_shell_interpreter(self): + """ Test the host platform shell with a different interpreter """ + self.expect("platform shell -h -i /bin/sh -- 'echo $0'", substrs=['/bin/sh'])