mib created this revision.
mib added reviewers: JDevlieghere, teemperor.
Herald added subscribers: lldb-commits, mgorny.
Herald added a project: LLDB.

This patch introduces the `shell` command in lldb. It allows the user to run
shell builtins and binaries on the host without putting lldb in the background.

rdar://62856024

Signed-off-by: Med Ismail Bennani <medismail.benn...@gmail.com>


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D79659

Files:
  lldb/source/Commands/CMakeLists.txt
  lldb/source/Commands/CommandObjectShell.cpp
  lldb/source/Commands/CommandObjectShell.h
  lldb/source/Commands/Options.td
  lldb/source/Interpreter/CommandInterpreter.cpp
  lldb/test/API/commands/shell/TestShellCommand.py

Index: lldb/test/API/commands/shell/TestShellCommand.py
===================================================================
--- /dev/null
+++ lldb/test/API/commands/shell/TestShellCommand.py
@@ -0,0 +1,45 @@
+"""
+Test the lldb host shell command.
+"""
+
+
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+
+class ShellCommandTestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    @no_debug_info_test
+    def test_help_platform(self):
+        self.expect("help shell", substrs=["shell <shell-command>",
+                                           "-t <value> ( --timeout <value> )"])
+
+    @expectedFailureAll(oslist=["windows"])
+    @no_debug_info_test
+    def test_shell(self):
+        """ Test that the shell command can invoke ls. """
+        triple = self.dbg.GetSelectedPlatform().GetTriple()
+        if re.match(".*-.*-windows", triple):
+            self.expect("shell dir c:\\", substrs=["Windows", "Program Files"])
+        elif re.match(".*-.*-.*-android", triple):
+            self.expect("shell ls /",
+                substrs=["cache", "dev", "system"])
+        else:
+            self.expect("shell ls /", substrs=["dev", "tmp", "usr"])
+
+    @no_debug_info_test
+    def test_shell_builtin(self):
+        """ Test a shell built-in command (echo) """
+        self.expect("shell echo hello lldb",
+                    substrs=["hello lldb"])
+
+    @no_debug_info_test
+    def test_shell_timeout(self):
+        """ Test a shell built-in command (sleep) that times out """
+        self.expect("shell -t 1 --  sleep 3", error=True, substrs=[
+                    "error: timed out waiting for shell command to complete"])
Index: lldb/source/Interpreter/CommandInterpreter.cpp
===================================================================
--- lldb/source/Interpreter/CommandInterpreter.cpp
+++ lldb/source/Interpreter/CommandInterpreter.cpp
@@ -32,6 +32,7 @@
 #include "Commands/CommandObjectRegister.h"
 #include "Commands/CommandObjectReproducer.h"
 #include "Commands/CommandObjectSettings.h"
+#include "Commands/CommandObjectShell.h"
 #include "Commands/CommandObjectSource.h"
 #include "Commands/CommandObjectStats.h"
 #include "Commands/CommandObjectTarget.h"
@@ -485,6 +486,7 @@
       CommandObjectSP(new CommandObjectScript(*this, script_language));
   m_command_dict["settings"] =
       CommandObjectSP(new CommandObjectMultiwordSettings(*this));
+  m_command_dict["shell"] = CommandObjectSP(new CommandObjectShell(*this));
   m_command_dict["source"] =
       CommandObjectSP(new CommandObjectMultiwordSource(*this));
   m_command_dict["statistics"] = CommandObjectSP(new CommandObjectStats(*this));
Index: lldb/source/Commands/Options.td
===================================================================
--- lldb/source/Commands/Options.td
+++ lldb/source/Commands/Options.td
@@ -701,6 +701,12 @@
     Desc<"Set the synchronicity of this command's executions with regard to "
     "LLDB event system.">;
 }
+
+let Command = "shell" in {
+  def shell_timeout : Option<"timeout", "t">, Arg<"Value">,
+    Desc<"Seconds to wait for the host to finish running the command.">;
+}
+
 let Command = "source info" in {
   def source_info_count : Option<"count", "c">, Arg<"Count">,
     Desc<"The number of line entries to display.">;
Index: lldb/source/Commands/CommandObjectShell.h
===================================================================
--- /dev/null
+++ lldb/source/Commands/CommandObjectShell.h
@@ -0,0 +1,82 @@
+//===-- CommandObjectShell.h ---=--------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_INTERPRETER_COMMANDOBJECTSHELL_H
+#define LLDB_SOURCE_INTERPRETER_COMMANDOBJECTSHELL_H
+
+#include "lldb/Host/OptionParser.h"
+#include "lldb/Interpreter/CommandObject.h"
+#include "lldb/Interpreter/Options.h"
+#include "lldb/Utility/Timeout.h"
+
+namespace lldb_private {
+
+class CommandObjectShell : public CommandObjectRaw {
+public:
+  CommandObjectShell(CommandInterpreter &interpreter);
+
+  ~CommandObjectShell() override;
+
+  void HandleCompletion(CompletionRequest &request) override;
+
+  static void GenerateAdditionalHelpAvenuesMessage(
+      Stream *s, llvm::StringRef command, llvm::StringRef prefix,
+      llvm::StringRef subcommand, bool include_upropos = true,
+      bool include_type_lookup = true);
+
+  class CommandOptions : public Options {
+  public:
+    CommandOptions() : Options() {}
+
+    ~CommandOptions() override {}
+
+    Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
+                          ExecutionContext *execution_context) override {
+      Status error;
+
+      const char short_option = (char)GetDefinitions()[option_idx].short_option;
+
+      switch (short_option) {
+      case 't':
+        uint32_t timeout_sec;
+        if (option_arg.getAsInteger(10, timeout_sec))
+          error.SetErrorStringWithFormat(
+              "could not convert \"%s\" to a numeric value.",
+              option_arg.str().c_str());
+        else
+          m_timeout = std::chrono::seconds(timeout_sec);
+        break;
+      default:
+        llvm_unreachable("Unimplemented option");
+      }
+
+      return error;
+    }
+
+    void OptionParsingStarting(ExecutionContext *execution_context) override {}
+
+    llvm::ArrayRef<OptionDefinition> GetDefinitions() override;
+
+    // Instance variables to hold the values for command options.
+
+    Timeout<std::micro> m_timeout = std::chrono::seconds(10);
+  };
+
+  Options *GetOptions() override { return &m_options; }
+
+protected:
+  bool DoExecute(llvm::StringRef raw_command_line,
+                 CommandReturnObject &result) override;
+
+private:
+  CommandOptions m_options;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_INTERPRETER_COMMANDOBJECTSHELL_H
Index: lldb/source/Commands/CommandObjectShell.cpp
===================================================================
--- /dev/null
+++ lldb/source/Commands/CommandObjectShell.cpp
@@ -0,0 +1,105 @@
+#include "CommandObjectShell.h"
+#include "lldb/Interpreter/CommandInterpreter.h"
+#include "lldb/Interpreter/CommandReturnObject.h"
+
+using namespace lldb;
+using namespace lldb_private;
+
+// CommandObjectShell
+
+CommandObjectShell::CommandObjectShell(CommandInterpreter &interpreter)
+    : CommandObjectRaw(interpreter, "shell", "Run a shell command on the host.",
+                       "shell <shell-command>", 0),
+      m_options() {}
+
+CommandObjectShell::~CommandObjectShell() = default;
+
+// "shell"
+#define LLDB_OPTIONS_shell
+#include "CommandOptions.inc"
+
+llvm::ArrayRef<OptionDefinition>
+CommandObjectShell::CommandOptions::GetDefinitions() {
+  return llvm::makeArrayRef(g_shell_options);
+}
+
+bool CommandObjectShell::DoExecute(llvm::StringRef raw_command_line,
+                                   CommandReturnObject &result) {
+  ExecutionContext exe_ctx = GetCommandInterpreter().GetExecutionContext();
+  m_options.NotifyOptionParsingStarting(&exe_ctx);
+
+  // Print out an usage syntax on an empty command line.
+  if (raw_command_line.empty()) {
+    result.GetOutputStream().Printf("%s\n", this->GetSyntax().str().c_str());
+    return true;
+  }
+
+  OptionsWithRaw args(raw_command_line);
+  const char *expr = args.GetRawPart().c_str();
+
+  if (args.HasArgs())
+    if (!ParseOptions(args.GetArgs(), result))
+      return false;
+
+  PlatformSP platform_sp(Platform::GetHostPlatform());
+  Status error;
+  if (platform_sp) {
+    FileSpec working_dir{};
+    std::string output;
+    int status = -1;
+    int signo = -1;
+    error = (platform_sp->RunShellCommand(expr, working_dir, &status, &signo,
+                                          &output, m_options.m_timeout));
+    if (!output.empty())
+      result.GetOutputStream().PutCString(output);
+    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);
+    }
+  } else {
+    result.GetOutputStream().Printf(
+        "error: cannot run remote shell commands without a platform\n");
+    error.SetErrorString(
+        "error: cannot run remote shell commands without a platform");
+  }
+
+  if (error.Fail()) {
+    result.AppendError(error.AsCString());
+    result.SetStatus(eReturnStatusFailed);
+  } else {
+    result.SetStatus(eReturnStatusSuccessFinishResult);
+  }
+  return true;
+}
+
+void CommandObjectShell::HandleCompletion(CompletionRequest &request) {
+  // Return the completions of the commands in the help system:
+  if (request.GetCursorIndex() == 0) {
+    m_interpreter.HandleCompletionMatches(request);
+    return;
+  }
+  CommandObject *cmd_obj =
+      m_interpreter.GetCommandObject(request.GetParsedLine()[0].ref());
+
+  // The command that they are getting help on might be ambiguous, in which
+  // case we should complete that, otherwise complete with the command the
+  // user is getting help on...
+
+  if (cmd_obj) {
+    request.ShiftArguments();
+    cmd_obj->HandleCompletion(request);
+    return;
+  }
+  m_interpreter.HandleCompletionMatches(request);
+}
Index: lldb/source/Commands/CMakeLists.txt
===================================================================
--- lldb/source/Commands/CMakeLists.txt
+++ lldb/source/Commands/CMakeLists.txt
@@ -13,6 +13,7 @@
   CommandObjectFrame.cpp
   CommandObjectGUI.cpp
   CommandObjectHelp.cpp
+  CommandObjectLanguage.cpp
   CommandObjectLog.cpp
   CommandObjectMemory.cpp
   CommandObjectMultiword.cpp
@@ -23,6 +24,7 @@
   CommandObjectRegister.cpp
   CommandObjectReproducer.cpp
   CommandObjectSettings.cpp
+  CommandObjectShell.cpp
   CommandObjectSource.cpp
   CommandObjectStats.cpp
   CommandObjectTarget.cpp
@@ -31,7 +33,6 @@
   CommandObjectVersion.cpp
   CommandObjectWatchpoint.cpp
   CommandObjectWatchpointCommand.cpp
-  CommandObjectLanguage.cpp
 
   LINK_LIBS
     lldbBase
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to