Repository: pwndbg/pwndbg
Pull request: Add special handling for `version` in LLDB Pwndbg
Fixes: #2800

`version` is another Pwndbg command that has to alias an LLDB command. This PR adds it to the LLDB Pwndbg CLI, in the same way we handle `set`. This PR also introduces a name component to `pwndbg.dbg.Debugger`, as a means to distinguish between debuggers that's more specific than `is_gdblib_available`, and uses it to gate off installing `version` as a regular command when running under LLDB.

Currently, doing these overrides manually for Pwndbg commands is probably fine, because there's all in all like two of them. But if there end up being more commands like this, we should find a better, more structured way to do it.

Problem statement:
[LLDB] error: cannot add command: can't replace builtin command
pwndbg version: pwndbg-lldb_2025.02.19_x86_64-portable

When lunch ./pwndbg/bin/pwndbg-lldb, there have an error:
`error: cannot add command: can't replace builtin command`

I add a print in `pwndbg-lldb_2025.02.19_x86_64-portable:1808 the function add_command` ,
Found the error becasue command `version`,
hope fix it.

Hints from issue discussion:
Yes, this is an '"error"', but it does not break any functionality in lldb. We have to disable this message or disable `version` command only for lldb.
I think should rename `version` with others, keep the `version` command working.
Why? Wouldn't it be better to override it with ours and return our version + LLDB version?

Commit message: Fix `version` in LLDB Pwndbg

Patch to review:
diff --git a/pwndbg/commands/version.py b/pwndbg/commands/version.py
index e1226ddb2da..0c51b62c7c8 100644
--- a/pwndbg/commands/version.py
+++ b/pwndbg/commands/version.py
@@ -93,13 +93,22 @@ def get_terminal_size():
     return f"Terminal width: {width_info}, height: {height_info}\n"
 
 
-@pwndbg.commands.ArgparsedCommand(
-    "Displays Pwndbg and its important deps versions.", category=CommandCategory.PWNDBG
-)
-def version() -> None:
+def version_impl() -> None:
+    """
+    Implementation of the `version` command.
+    """
     print("\n".join(map(message.system, all_versions())))
 
 
+if not pwndbg.dbg.name() == "lldb":
+    # In LLDB, this command is implemented as part of the Pwndbg CLI.
+    @pwndbg.commands.ArgparsedCommand(
+        "Displays Pwndbg and its important deps versions.", category=CommandCategory.PWNDBG
+    )
+    def version() -> None:
+        version_impl()
+
+
 bugreport_parser = argparse.ArgumentParser(description="Generate a bug report.")
 bugreport_group = bugreport_parser.add_mutually_exclusive_group()
 bugreport_group.add_argument(
diff --git a/pwndbg/dbg/__init__.py b/pwndbg/dbg/__init__.py
index 5442d564029..fe39f1b6dc8 100644
--- a/pwndbg/dbg/__init__.py
+++ b/pwndbg/dbg/__init__.py
@@ -1134,6 +1134,12 @@ def supports_breakpoint_creation_during_stop_handler(self) -> bool:
     # removed or replaced as the porting work continues.
     #
 
+    def name(self) -> Literal["lldb", "gdb"]:
+        """
+        The name of the current debugger.
+        """
+        raise NotImplementedError()
+
     # We'd like to be able to gate some imports off during porting. This aids in
     # that.
     def is_gdblib_available(self) -> bool:
diff --git a/pwndbg/dbg/gdb/__init__.py b/pwndbg/dbg/gdb/__init__.py
index 90fdf8c74a6..bb38f2b3f8e 100644
--- a/pwndbg/dbg/gdb/__init__.py
+++ b/pwndbg/dbg/gdb/__init__.py
@@ -1549,6 +1549,10 @@ def set_sysroot(self, sysroot: str) -> bool:
     def supports_breakpoint_creation_during_stop_handler(self) -> bool:
         return False
 
+    @override
+    def name(self) -> Literal["gdb", "lldb"]:
+        return "gdb"
+
     @override
     def x86_disassembly_flavor(self) -> Literal["att", "intel"]:
         try:
diff --git a/pwndbg/dbg/lldb/__init__.py b/pwndbg/dbg/lldb/__init__.py
index e83ca6b630f..edc63e471e7 100644
--- a/pwndbg/dbg/lldb/__init__.py
+++ b/pwndbg/dbg/lldb/__init__.py
@@ -2029,6 +2029,10 @@ def set_sysroot(self, sysroot: str) -> bool:
     def supports_breakpoint_creation_during_stop_handler(self) -> bool:
         return True
 
+    @override
+    def name(self) -> Literal["lldb", "gdb"]:
+        return "lldb"
+
     @override
     def x86_disassembly_flavor(self) -> Literal["att", "intel"]:
         # Example:
diff --git a/pwndbg/dbg/lldb/repl/__init__.py b/pwndbg/dbg/lldb/repl/__init__.py
index 0220715d63a..b88d0dd6631 100644
--- a/pwndbg/dbg/lldb/repl/__init__.py
+++ b/pwndbg/dbg/lldb/repl/__init__.py
@@ -366,11 +366,17 @@ def exec_repl_command(
         )
         return True
 
+    # Not allowed to have this be a regular command, because of LLDB.
+    if "version".startswith(line) and line.startswith("ve"):
+        pwndbg.commands.version.version_impl()
+        print()
+        # Don't return. We want the LLDB command to also run.
+
     # There are interactive commands that `SBDebugger.HandleCommand` will
     # silently ignore. We have to implement them manually, here.
-    if "quit".startswith(line) and line.startswith("quit"):
+    if "quit".startswith(line) and line.startswith("q"):
         return False
-    if "exit".startswith(line) and line.startswith("exit"):
+    if "exit".startswith(line) and line.startswith("exi"):
         return False
 
     # `script` is a little weird. Unlike with the other commands we're


Code review comments:
diff_hunk: @@ -93,13 +93,22 @@ def get_terminal_size():
     return f"Terminal width: {width_info}, height: {height_info}\n"
 
 
-@pwndbg.commands.ArgparsedCommand(
-    "Displays Pwndbg and its important deps versions.", category=CommandCategory.PWNDBG
-)
-def version() -> None:
+def version_impl() -> None:
+    """
+    Implementation of the `version` command.
+    """
     print("\n".join(map(message.system, all_versions())))
 
 
+if not pwndbg.dbg.name() == "lldb":
path: pwndbg/commands/version.py
text: @user1:
```suggestion
if pwndbg.dbg.name() != "lldb":
```
why not just? anyway lgtm I think 

@author:
That was a total brain fart wow