Repository: pwndbg/pwndbg
Pull request: Add --shell argument to pwndbg command
```
pwndbg: loaded 157 pwndbg commands and 49 shell commands. Type pwndbg [--shell | --all] [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
------- tip of the day (disable with set show-tips off) -------
Use GDB's dprintf command to print all calls to given function. E.g. dprintf malloc, "malloc(%p)\n", (void*)$rdi will print all malloc calls
pwndbg> pwndbg
address              Print virtual memory map pages. Results can be filtered by providing address/module name.
arena                Print the contents of an arena, default to the current thread's arena.
...
pwndbg> pwndbg --shell
asm                  Invokes `asm` shell command
awk                  Invokes `awk` shell command (deprecated)
...
pwndbg> pwndbg --all
address              Print virtual memory map pages. Results can be filtered by providing address/module name.
arena                Print the contents of an arena, default to the current thread's arena.
arenas               List this process's arenas.
argc                 Prints out the number of arguments.
args                 Prints out the contents of argv.
argv                 Prints out the contents of argv.
aslr                 Check the current ASLR status, or turn it on/off.
asm                  Invokes `asm` shell command
```

Fixes #1211 

Problem statement:
Hide shell commands from the `pwndbg` output by default
We should hide shell commands from the `pwndbg` output by default, and only display them with `pwndbg --all` or `pwndbg --shell` or something like that. This would make it easier to find actual pwndbg commands.

Hints from issue discussion:

Commit message: Add --shell argument to pwndbg command

Patch to review:
diff --git a/pwndbg/commands/__init__.py b/pwndbg/commands/__init__.py
index 227619cf075..4e7a2b1b1b7 100644
--- a/pwndbg/commands/__init__.py
+++ b/pwndbg/commands/__init__.py
@@ -54,7 +54,9 @@ class Command(gdb.Command):
     builtin_override_whitelist = {"up", "down", "search", "pwd", "start"}
     history = {}  # type: Dict[int,str]
 
-    def __init__(self, function, prefix=False, command_name=None):
+    def __init__(self, function, prefix=False, command_name=None, shell=False):
+        self.shell = shell
+
         if command_name is None:
             command_name = function.__name__
 
diff --git a/pwndbg/commands/misc.py b/pwndbg/commands/misc.py
index 2e09b926f2f..a3c8889b16f 100644
--- a/pwndbg/commands/misc.py
+++ b/pwndbg/commands/misc.py
@@ -64,6 +64,11 @@ def errno_(err):
 Prints out a list of all pwndbg commands. The list can be optionally filtered if filter_pattern is passed.
 """
 )
+
+group = parser.add_mutually_exclusive_group()
+group.add_argument("--shell", action="store_true", help="Only display shell commands")
+group.add_argument("--all", dest="all_", action="store_true", help="Only display shell commands")
+
 parser.add_argument(
     "filter_pattern",
     type=str,
@@ -74,8 +79,16 @@ def errno_(err):
 
 
 @pwndbg.commands.ArgparsedCommand(parser, command_name="pwndbg")
-def pwndbg_(filter_pattern):
-    for name, docs in list_and_filter_commands(filter_pattern):
+def pwndbg_(filter_pattern, shell, all_):
+    if all_:
+        shell = True
+        non_shell = True
+    elif shell:
+        non_shell = False
+    else:
+        non_shell = True
+
+    for name, docs in list_and_filter_commands(filter_pattern, non_shell, shell):
         print("%-20s %s" % (name, docs))
 
 
@@ -98,7 +111,7 @@ def distance(a, b):
     )
 
 
-def list_and_filter_commands(filter_str):
+def list_and_filter_commands(filter_str, non_shell=True, shell=False):
     sorted_commands = list(pwndbg.commands.commands)
     sorted_commands.sort(key=lambda x: x.__name__)
 
@@ -108,6 +121,14 @@ def list_and_filter_commands(filter_str):
     results = []
 
     for c in sorted_commands:
+        # If this is a shell command and we didn't ask for shell commands, skip it
+        if c.shell and not shell:
+            continue
+
+        # If this is a normal command and we didn't ask for normal commands, skip it
+        if not c.shell and not non_shell:
+            continue
+
         name = c.__name__
         docs = c.__doc__
 
diff --git a/pwndbg/commands/shell.py b/pwndbg/commands/shell.py
index 3eb9cd7856a..e63b6093f31 100644
--- a/pwndbg/commands/shell.py
+++ b/pwndbg/commands/shell.py
@@ -81,7 +81,7 @@ def handler(*a):
     handler.__name__ = str(cmd)
     handler.__doc__ = doc
 
-    pwndbg.commands.Command(handler, False)
+    pwndbg.commands.Command(handler, shell=True)
 
 
 for cmd in pwncmds:
diff --git a/pwndbg/gdblib/prompt.py b/pwndbg/gdblib/prompt.py
index 9f5caab195c..cd87f419c32 100644
--- a/pwndbg/gdblib/prompt.py
+++ b/pwndbg/gdblib/prompt.py
@@ -14,9 +14,11 @@
     message.notice("$" + f.name) for f in pwndbg.gdbutils.functions.functions
 )
 
+num_pwndbg_cmds = sum(1 for _ in filter(lambda c: not c.shell, pwndbg.commands.commands))
+num_shell_cmds = sum(1 for _ in filter(lambda c: c.shell, pwndbg.commands.commands))
 hint_lines = (
-    "loaded %i commands. Type %s for a list."
-    % (len(pwndbg.commands.commands), message.notice("pwndbg [filter]")),
+    "loaded %i pwndbg commands and %i shell commands. Type %s for a list."
+    % (num_pwndbg_cmds, num_shell_cmds, message.notice("pwndbg [--shell | --all] [filter]")),
     "created %s gdb functions (can be used with print/break)" % funcs_list_str,
 )
 


Code review comments:
diff_hunk: @@ -74,8 +79,16 @@ def errno_(err):
 
 
 @pwndbg.commands.ArgparsedCommand(parser, command_name="pwndbg")
-def pwndbg_(filter_pattern):
-    for name, docs in list_and_filter_commands(filter_pattern):
+def pwndbg_(filter_pattern, shell, all_):
+    if all_:
+        shell = True
+        non_shell = True
path: pwndbg/commands/misc.py
text: @user1:
Hmm maybe we can find a better name for `non_shell`? Like `other`, `rest`, `standard`?

Or: `pwndbg_commands` and `shell_commands` or `shell_aliases`?