Repository: pwndbg/pwndbg
Pull request: Relay parsing error information to argparse, don't except on command argument parse error
Before:
```
pwndbg> hi nothing!
Exception occurred: hi: No symbol "nothing" in current context. (<class 'pwndbg.dbg.Error'>)
For more info invoke `set exception-verbose on` and rerun the command
or debug it by yourself with `set exception-debugger on`
pwndbg> dt struct a
Exception occurred: dt: No symbol "a" in current context. (<class 'pwndbg.dbg.Error'>)
For more info invoke `set exception-verbose on` and rerun the command
or debug it by yourself with `set exception-debugger on`
pwndbg> dt awa vector
Exception occurred: dt: No symbol "vector" in current context. (<class 'pwndbg.dbg.Error'>)
For more info invoke `set exception-verbose on` and rerun the command
or debug it by yourself with `set exception-debugger on`
pwndbg> start
[...]
pwndbg> dt awa a
Exception occurred: dt: Cannot convert value to long. (<class 'pwndbg.dbg.Error'>)
For more info invoke `set exception-verbose on` and rerun the command
or debug it by yourself with `set exception-debugger on`
pwndbg> dt awa vector
Exception occurred: dt: A syntax error in expression, near the end of `vector'. (<class 'pwndbg.dbg.Error'>)
For more info invoke `set exception-verbose on` and rerun the command
or debug it by yourself with `set exception-debugger on`
pwndbg> dt awa awa
Exception occurred: dt: No symbol "awa" in current context. (<class 'pwndbg.dbg.Error'>)
For more info invoke `set exception-verbose on` and rerun the command
or debug it by yourself with `set exception-debugger on`
```
After:
```
pwndbg> hi nothing
usage: hi [-h] [-v] [-s] [-f] addr
hi: error: argument addr: debugger couldn't resolve argument 'nothing': No symbol "nothing" in current context.
pwndbg> dt struct a
usage: dt [-h] typename [address]
dt: error: argument address: debugger couldn't resolve argument 'a': No symbol "a" in current context.
pwndbg> dt awa vector
usage: dt [-h] typename [address]
dt: error: argument address: debugger couldn't resolve argument 'vector': No symbol "vector" in current context.
pwndbg> start
[...]
pwndbg> dt awa a
usage: dt [-h] typename [address]
dt: error: argument address: couldn't convert 'a' (mynumber) to int: Cannot convert value to long.
pwndbg> p a
$1 = {
  i = {0, 1045149306},
  x = 1.2904777690891933e-08,
  d = 1.2904777690891933e-08
}
pwndbg> dt awa vector
usage: dt [-h] typename [address]
dt: error: argument address: debugger couldn't resolve argument 'vector': A syntax error in expression, near the end of `vector'.
pwndbg> dt awa awa
usage: dt [-h] typename [address]
dt: error: argument address: debugger couldn't resolve argument 'awa': No symbol "awa" in current context.
```
I believe this is more clear, as it shows the intended usage of the command and makes debugging the problem easier. For instance it makes it clear that by running `dt struct something`, `something` gets interpreted as the address positional argument. It also makes it clear that at runtime a symbol `a` exists and is of type `mynumber`.

It might be worth looking into subclassing `argparse.ArgumentParser` in the future and overriding the `error` function.

Closes https://github.com/pwndbg/pwndbg/issues/2656

Problem statement:
dt command crashes easily
### Description

The `dt` command throws an exception when given a bit wacky input, it should probably give an error message, similar to `ptype`.

### Steps to reproduce

```cpp
#include <vector>
int main () { return 0; }
```
`g++ -g ./main.cpp -o ./main`
`gdb ./main`
Here are some exceptions, and compared to `ptype`.
```
pwndbg> ptype struct a
No struct type named a.
pwndbg> dt struct a
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ -------------------------pwndbg/pwndbg/dbg/gdb/__init__.py:377 in evaluate_expression            │
│                                                                                                  │
│    374 │   @override                                                                             │
│    375 │   def evaluate_expression(self, expression: str) -> pwndbg.dbg_mod.Value:               │
│    376 │   │   try:                                                                              │
│ ❱  377 │   │   │   return GDBValue(parse_and_eval(expression, global_context=True))              │
│    378 │   │   except gdb.error as e:                                                            │
│    379 │   │   │   raise pwndbg.dbg_mod.Error(e)                                                 │
│    380                                                                                           │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/dbg/gdb/__init__.py:70 in parse_and_eval                  │
│                                                                                                  │
│     67 │   `global_context` was introduced in GDB 14.                                            │
│     68 │   """                                                                                   │
│     69 │   try:                                                                                  │
│ ❱   70 │   │   return gdb.parse_and_eval(expression, global_context)                             │
│     71 │   except TypeError:                                                                     │
│     72 │   │   return gdb.parse_and_eval(expression)                                             │
│     73                                                                                           │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
error: No symbol "a" in current context.

During handling of the above exception, another exception occurred:

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ -------------------------pwndbg/pwndbg/commands/__init__.py:139 in invoke                        │
│                                                                                                  │
│   136 │   │   │   return                                                                         │
│   137 │   │                                                                                      │
│   138 │   │   try:                                                                               │
│ ❱ 139 │   │   │   args, kwargs = self.split_args(argument)                                       │
│   140 │   │   except SystemExit:                                                                 │
│   141 │   │   │   # Raised when the usage is printed by an ArgparsedCommand                      │
│   142 │   │   │   return                                                                         │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:602 in split_args                    │
│                                                                                                  │
│   599 │                                                                                          │
│   600 │   def split_args(self, argument: str):                                                   │
│   601 │   │   argv = pwndbg.dbg.lex_args(argument)                                               │
│ ❱ 602 │   │   return (), vars(self.parser.parse_args(argv))                                      │
│   603                                                                                            │
│   604                                                                                            │
│   605 class ArgparsedCommand:                                                                    │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:1895 in parse_args                                               │
│                                                                                                  │
│   1892 │   # Command line argument parsing methods                                               │
│   1893 │   # =====================================                                               │
│   1894 │   def parse_args(self, args=None, namespace=None):                                      │
│ ❱ 1895 │   │   args, argv = self.parse_known_args(args, namespace)                               │
│   1896 │   │   if argv:                                                                          │
│   1897 │   │   │   msg = _('unrecognized arguments: %s') % ' '.join(argv)                        │
│   1898 │   │   │   if self.exit_on_error:                                                        │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:1931 in parse_known_args                                         │
│                                                                                                  │
│   1928 │   │   # parse the arguments and exit if there are any errors                            │
│   1929 │   │   if self.exit_on_error:                                                            │
│   1930 │   │   │   try:                                                                          │
│ ❱ 1931 │   │   │   │   namespace, args = self._parse_known_args(args, namespace)                 │
│   1932 │   │   │   except ArgumentError as err:                                                  │
│   1933 │   │   │   │   self.error(str(err))                                                      │
│   1934 │   │   else:                                                                             │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2171 in _parse_known_args                                        │
│                                                                                                  │
│   2168 │   │   │   start_index = consume_optional(start_index)                                   │
│   2169 │   │                                                                                     │
│   2170 │   │   # consume any positionals following the last Optional                             │
│ ❱ 2171 │   │   stop_index = consume_positionals(start_index)                                     │
│   2172 │   │                                                                                     │
│   2173 │   │   # if we didn't consume all the argument strings, there were extras                │
│   2174 │   │   extras.extend(arg_strings[stop_index:])                                           │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2127 in consume_positionals                                      │
│                                                                                                  │
│   2124 │   │   │   │   │   │   │   │   │   │   │   │    start_index + arg_count) >= 0):          │
│   2125 │   │   │   │   │   │   args.remove('--')                                                 │
│   2126 │   │   │   │   start_index += arg_count                                                  │
│ ❱ 2127 │   │   │   │   take_action(action, args)                                                 │
│   2128 │   │   │                                                                                 │
│   2129 │   │   │   # slice off the Positionals that we just parsed and return the                │
│   2130 │   │   │   # index at which the Positionals' string args stopped                         │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:1991 in take_action                                              │
│                                                                                                  │
│   1988 │   │                                                                                     │
│   1989 │   │   def take_action(action, argument_strings, option_string=None):                    │
│   1990 │   │   │   seen_actions.add(action)                                                      │
│ ❱ 1991 │   │   │   argument_values = self._get_values(action, argument_strings)                  │
│   1992 │   │   │                                                                                 │
│   1993 │   │   │   # error if this argument is not allowed with other previously                 │
│   1994 │   │   │   # seen arguments                                                              │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2535 in _get_values                                              │
│                                                                                                  │
│   2532 │   │   # single argument or optional argument produces a single value                    │
│   2533 │   │   elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:                  │
│   2534 │   │   │   arg_string, = arg_strings                                                     │
│ ❱ 2535 │   │   │   value = self._get_value(action, arg_string)                                   │
│   2536 │   │   │   self._check_value(action, value)                                              │
│   2537 │   │                                                                                     │
│   2538 │   │   # REMAINDER arguments convert all values, checking none                           │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2568 in _get_value                                               │
│                                                                                                  │
│   2565 │   │                                                                                     │
│   2566 │   │   # convert the value to the appropriate type                                       │
│   2567 │   │   try:                                                                              │
│ ❱ 2568 │   │   │   result = type_func(arg_string)                                                │
│   2569 │   │                                                                                     │
│   2570 │   │   # ArgumentTypeErrors indicate errors                                              │
│   2571 │   │   except ArgumentTypeError as err:                                                  │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:272 in fix_int_reraise               │
│                                                                                                  │
│   269                                                                                            │
│   270                                                                                            │
│   271 def fix_int_reraise(*a, **kw) -> int:                                                      │
│ ❱ 272 │   return fix_int(*a, reraise=True, **kw)                                                 │
│   273                                                                                            │
│   274                                                                                            │
│   275 def OnlyWhenLocal(function: Callable[P, T]) -> Callable[P, Optional[T]]:                   │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:268 in fix_int                       │
│                                                                                                  │
│   265                                                                                            │
│   266                                                                                            │
│   267 def fix_int(*a, **kw) -> int:                                                              │
│ ❱ 268 │   return int(fix(*a, **kw))                                                              │
│   269                                                                                            │
│   270                                                                                            │
│   271 def fix_int_reraise(*a, **kw) -> int:                                                      │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:254 in fix                           │
│                                                                                                  │
│   251 │   │   if not quiet:                                                                      │
│   252 │   │   │   print(ex)                                                                      │
│   253 │   │   if reraise:                                                                        │
│ ❱ 254 │   │   │   raise ex                                                                       │
│   255 │                                                                                          │
│   256 │   if sloppy:                                                                             │
│   257 │   │   return arg                                                                         │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:233 in fix                           │
│                                                                                                  │
│   230 │   │   #                                                                                  │
│   231 │   │   # TODO: Port architecutre functions and `pwndbg.gdblib.regs.fix` to debugger-agn   │
│   232 │   │   arg = pwndbg.aglib.regs.fix(arg)                                                   │
│ ❱ 233 │   │   return target.evaluate_expression(arg)                                             │
│   234 │   except Exception as e:                                                                 │
│   235 │   │   ex = e                                                                             │
│   236                                                                                            │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/dbg/gdb/__init__.py:379 in evaluate_expression            │
│                                                                                                  │
│    376 │   │   try:                                                                              │
│    377 │   │   │   return GDBValue(parse_and_eval(expression, global_context=True))              │
│    378 │   │   except gdb.error as e:                                                            │
│ ❱  379 │   │   │   raise pwndbg.dbg_mod.Error(e)                                                 │
│    380 │                                                                                         │
│    381 │   @override                                                                             │
│    382 │   def vmmap(self) -> pwndbg.dbg_mod.MemoryMap:                                          │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
Error: No symbol "a" in current context.
pwndbg> ptype struct vector
No struct type named vector.
pwndbg> dt struct vector
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ -------------------------pwndbg/pwndbg/dbg/gdb/__init__.py:377 in evaluate_expression            │
│                                                                                                  │
│    374 │   @override                                                                             │
│    375 │   def evaluate_expression(self, expression: str) -> pwndbg.dbg_mod.Value:               │
│    376 │   │   try:                                                                              │
│ ❱  377 │   │   │   return GDBValue(parse_and_eval(expression, global_context=True))              │
│    378 │   │   except gdb.error as e:                                                            │
│    379 │   │   │   raise pwndbg.dbg_mod.Error(e)                                                 │
│    380                                                                                           │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/dbg/gdb/__init__.py:70 in parse_and_eval                  │
│                                                                                                  │
│     67 │   `global_context` was introduced in GDB 14.                                            │
│     68 │   """                                                                                   │
│     69 │   try:                                                                                  │
│ ❱   70 │   │   return gdb.parse_and_eval(expression, global_context)                             │
│     71 │   except TypeError:                                                                     │
│     72 │   │   return gdb.parse_and_eval(expression)                                             │
│     73                                                                                           │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
error: No symbol "vector" in current context.

During handling of the above exception, another exception occurred:

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ -------------------------pwndbg/pwndbg/commands/__init__.py:139 in invoke                        │
│                                                                                                  │
│   136 │   │   │   return                                                                         │
│   137 │   │                                                                                      │
│   138 │   │   try:                                                                               │
│ ❱ 139 │   │   │   args, kwargs = self.split_args(argument)                                       │
│   140 │   │   except SystemExit:                                                                 │
│   141 │   │   │   # Raised when the usage is printed by an ArgparsedCommand                      │
│   142 │   │   │   return                                                                         │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:602 in split_args                    │
│                                                                                                  │
│   599 │                                                                                          │
│   600 │   def split_args(self, argument: str):                                                   │
│   601 │   │   argv = pwndbg.dbg.lex_args(argument)                                               │
│ ❱ 602 │   │   return (), vars(self.parser.parse_args(argv))                                      │
│   603                                                                                            │
│   604                                                                                            │
│   605 class ArgparsedCommand:                                                                    │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:1895 in parse_args                                               │
│                                                                                                  │
│   1892 │   # Command line argument parsing methods                                               │
│   1893 │   # =====================================                                               │
│   1894 │   def parse_args(self, args=None, namespace=None):                                      │
│ ❱ 1895 │   │   args, argv = self.parse_known_args(args, namespace)                               │
│   1896 │   │   if argv:                                                                          │
│   1897 │   │   │   msg = _('unrecognized arguments: %s') % ' '.join(argv)                        │
│   1898 │   │   │   if self.exit_on_error:                                                        │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:1931 in parse_known_args                                         │
│                                                                                                  │
│   1928 │   │   # parse the arguments and exit if there are any errors                            │
│   1929 │   │   if self.exit_on_error:                                                            │
│   1930 │   │   │   try:                                                                          │
│ ❱ 1931 │   │   │   │   namespace, args = self._parse_known_args(args, namespace)                 │
│   1932 │   │   │   except ArgumentError as err:                                                  │
│   1933 │   │   │   │   self.error(str(err))                                                      │
│   1934 │   │   else:                                                                             │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2171 in _parse_known_args                                        │
│                                                                                                  │
│   2168 │   │   │   start_index = consume_optional(start_index)                                   │
│   2169 │   │                                                                                     │
│   2170 │   │   # consume any positionals following the last Optional                             │
│ ❱ 2171 │   │   stop_index = consume_positionals(start_index)                                     │
│   2172 │   │                                                                                     │
│   2173 │   │   # if we didn't consume all the argument strings, there were extras                │
│   2174 │   │   extras.extend(arg_strings[stop_index:])                                           │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2127 in consume_positionals                                      │
│                                                                                                  │
│   2124 │   │   │   │   │   │   │   │   │   │   │   │    start_index + arg_count) >= 0):          │
│   2125 │   │   │   │   │   │   args.remove('--')                                                 │
│   2126 │   │   │   │   start_index += arg_count                                                  │
│ ❱ 2127 │   │   │   │   take_action(action, args)                                                 │
│   2128 │   │   │                                                                                 │
│   2129 │   │   │   # slice off the Positionals that we just parsed and return the                │
│   2130 │   │   │   # index at which the Positionals' string args stopped                         │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:1991 in take_action                                              │
│                                                                                                  │
│   1988 │   │                                                                                     │
│   1989 │   │   def take_action(action, argument_strings, option_string=None):                    │
│   1990 │   │   │   seen_actions.add(action)                                                      │
│ ❱ 1991 │   │   │   argument_values = self._get_values(action, argument_strings)                  │
│   1992 │   │   │                                                                                 │
│   1993 │   │   │   # error if this argument is not allowed with other previously                 │
│   1994 │   │   │   # seen arguments                                                              │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2535 in _get_values                                              │
│                                                                                                  │
│   2532 │   │   # single argument or optional argument produces a single value                    │
│   2533 │   │   elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:                  │
│   2534 │   │   │   arg_string, = arg_strings                                                     │
│ ❱ 2535 │   │   │   value = self._get_value(action, arg_string)                                   │
│   2536 │   │   │   self._check_value(action, value)                                              │
│   2537 │   │                                                                                     │
│   2538 │   │   # REMAINDER arguments convert all values, checking none                           │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2568 in _get_value                                               │
│                                                                                                  │
│   2565 │   │                                                                                     │
│   2566 │   │   # convert the value to the appropriate type                                       │
│   2567 │   │   try:                                                                              │
│ ❱ 2568 │   │   │   result = type_func(arg_string)                                                │
│   2569 │   │                                                                                     │
│   2570 │   │   # ArgumentTypeErrors indicate errors                                              │
│   2571 │   │   except ArgumentTypeError as err:                                                  │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:272 in fix_int_reraise               │
│                                                                                                  │
│   269                                                                                            │
│   270                                                                                            │
│   271 def fix_int_reraise(*a, **kw) -> int:                                                      │
│ ❱ 272 │   return fix_int(*a, reraise=True, **kw)                                                 │
│   273                                                                                            │
│   274                                                                                            │
│   275 def OnlyWhenLocal(function: Callable[P, T]) -> Callable[P, Optional[T]]:                   │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:268 in fix_int                       │
│                                                                                                  │
│   265                                                                                            │
│   266                                                                                            │
│   267 def fix_int(*a, **kw) -> int:                                                              │
│ ❱ 268 │   return int(fix(*a, **kw))                                                              │
│   269                                                                                            │
│   270                                                                                            │
│   271 def fix_int_reraise(*a, **kw) -> int:                                                      │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:254 in fix                           │
│                                                                                                  │
│   251 │   │   if not quiet:                                                                      │
│   252 │   │   │   print(ex)                                                                      │
│   253 │   │   if reraise:                                                                        │
│ ❱ 254 │   │   │   raise ex                                                                       │
│   255 │                                                                                          │
│   256 │   if sloppy:                                                                             │
│   257 │   │   return arg                                                                         │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:233 in fix                           │
│                                                                                                  │
│   230 │   │   #                                                                                  │
│   231 │   │   # TODO: Port architecutre functions and `pwndbg.gdblib.regs.fix` to debugger-agn   │
│   232 │   │   arg = pwndbg.aglib.regs.fix(arg)                                                   │
│ ❱ 233 │   │   return target.evaluate_expression(arg)                                             │
│   234 │   except Exception as e:                                                                 │
│   235 │   │   ex = e                                                                             │
│   236                                                                                            │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/dbg/gdb/__init__.py:379 in evaluate_expression            │
│                                                                                                  │
│    376 │   │   try:                                                                              │
│    377 │   │   │   return GDBValue(parse_and_eval(expression, global_context=True))              │
│    378 │   │   except gdb.error as e:                                                            │
│ ❱  379 │   │   │   raise pwndbg.dbg_mod.Error(e)                                                 │
│    380 │                                                                                         │
│    381 │   @override                                                                             │
│    382 │   def vmmap(self) -> pwndbg.dbg_mod.MemoryMap:                                          │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
Error: No symbol "vector" in current context.
pwndbg> start
Temporary breakpoint 1 at 0x111d: file main.cpp, line 4.
[..snip]

pwndbg> ptype struct a
No struct type named a.
pwndbg> dt struct a
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ -------------------------pwndbg/pwndbg/dbg/gdb/__init__.py:1138 in __int__                       │
│                                                                                                  │
│   1135 │   @override                                                                             │
│   1136 │   def __int__(self) -> int:                                                             │
│   1137 │   │   try:                                                                              │
│ ❱ 1138 │   │   │   return int(self.inner)                                                        │
│   1139 │   │   except gdb.error as e:                                                            │
│   1140 │   │   │   raise pwndbg.dbg_mod.Error(e)                                                 │
│   1141                                                                                           │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
error: Cannot convert value to long.

During handling of the above exception, another exception occurred:

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ -------------------------pwndbg/pwndbg/commands/__init__.py:139 in invoke                        │
│                                                                                                  │
│   136 │   │   │   return                                                                         │
│   137 │   │                                                                                      │
│   138 │   │   try:                                                                               │
│ ❱ 139 │   │   │   args, kwargs = self.split_args(argument)                                       │
│   140 │   │   except SystemExit:                                                                 │
│   141 │   │   │   # Raised when the usage is printed by an ArgparsedCommand                      │
│   142 │   │   │   return                                                                         │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:602 in split_args                    │
│                                                                                                  │
│   599 │                                                                                          │
│   600 │   def split_args(self, argument: str):                                                   │
│   601 │   │   argv = pwndbg.dbg.lex_args(argument)                                               │
│ ❱ 602 │   │   return (), vars(self.parser.parse_args(argv))                                      │
│   603                                                                                            │
│   604                                                                                            │
│   605 class ArgparsedCommand:                                                                    │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:1895 in parse_args                                               │
│                                                                                                  │
│   1892 │   # Command line argument parsing methods                                               │
│   1893 │   # =====================================                                               │
│   1894 │   def parse_args(self, args=None, namespace=None):                                      │
│ ❱ 1895 │   │   args, argv = self.parse_known_args(args, namespace)                               │
│   1896 │   │   if argv:                                                                          │
│   1897 │   │   │   msg = _('unrecognized arguments: %s') % ' '.join(argv)                        │
│   1898 │   │   │   if self.exit_on_error:                                                        │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:1931 in parse_known_args                                         │
│                                                                                                  │
│   1928 │   │   # parse the arguments and exit if there are any errors                            │
│   1929 │   │   if self.exit_on_error:                                                            │
│   1930 │   │   │   try:                                                                          │
│ ❱ 1931 │   │   │   │   namespace, args = self._parse_known_args(args, namespace)                 │
│   1932 │   │   │   except ArgumentError as err:                                                  │
│   1933 │   │   │   │   self.error(str(err))                                                      │
│   1934 │   │   else:                                                                             │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2171 in _parse_known_args                                        │
│                                                                                                  │
│   2168 │   │   │   start_index = consume_optional(start_index)                                   │
│   2169 │   │                                                                                     │
│   2170 │   │   # consume any positionals following the last Optional                             │
│ ❱ 2171 │   │   stop_index = consume_positionals(start_index)                                     │
│   2172 │   │                                                                                     │
│   2173 │   │   # if we didn't consume all the argument strings, there were extras                │
│   2174 │   │   extras.extend(arg_strings[stop_index:])                                           │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2127 in consume_positionals                                      │
│                                                                                                  │
│   2124 │   │   │   │   │   │   │   │   │   │   │   │    start_index + arg_count) >= 0):          │
│   2125 │   │   │   │   │   │   args.remove('--')                                                 │
│   2126 │   │   │   │   start_index += arg_count                                                  │
│ ❱ 2127 │   │   │   │   take_action(action, args)                                                 │
│   2128 │   │   │                                                                                 │
│   2129 │   │   │   # slice off the Positionals that we just parsed and return the                │
│   2130 │   │   │   # index at which the Positionals' string args stopped                         │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:1991 in take_action                                              │
│                                                                                                  │
│   1988 │   │                                                                                     │
│   1989 │   │   def take_action(action, argument_strings, option_string=None):                    │
│   1990 │   │   │   seen_actions.add(action)                                                      │
│ ❱ 1991 │   │   │   argument_values = self._get_values(action, argument_strings)                  │
│   1992 │   │   │                                                                                 │
│   1993 │   │   │   # error if this argument is not allowed with other previously                 │
│   1994 │   │   │   # seen arguments                                                              │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2535 in _get_values                                              │
│                                                                                                  │
│   2532 │   │   # single argument or optional argument produces a single value                    │
│   2533 │   │   elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:                  │
│   2534 │   │   │   arg_string, = arg_strings                                                     │
│ ❱ 2535 │   │   │   value = self._get_value(action, arg_string)                                   │
│   2536 │   │   │   self._check_value(action, value)                                              │
│   2537 │   │                                                                                     │
│   2538 │   │   # REMAINDER arguments convert all values, checking none                           │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2568 in _get_value                                               │
│                                                                                                  │
│   2565 │   │                                                                                     │
│   2566 │   │   # convert the value to the appropriate type                                       │
│   2567 │   │   try:                                                                              │
│ ❱ 2568 │   │   │   result = type_func(arg_string)                                                │
│   2569 │   │                                                                                     │
│   2570 │   │   # ArgumentTypeErrors indicate errors                                              │
│   2571 │   │   except ArgumentTypeError as err:                                                  │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:272 in fix_int_reraise               │
│                                                                                                  │
│   269                                                                                            │
│   270                                                                                            │
│   271 def fix_int_reraise(*a, **kw) -> int:                                                      │
│ ❱ 272 │   return fix_int(*a, reraise=True, **kw)                                                 │
│   273                                                                                            │
│   274                                                                                            │
│   275 def OnlyWhenLocal(function: Callable[P, T]) -> Callable[P, Optional[T]]:                   │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:268 in fix_int                       │
│                                                                                                  │
│   265                                                                                            │
│   266                                                                                            │
│   267 def fix_int(*a, **kw) -> int:                                                              │
│ ❱ 268 │   return int(fix(*a, **kw))                                                              │
│   269                                                                                            │
│   270                                                                                            │
│   271 def fix_int_reraise(*a, **kw) -> int:                                                      │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/dbg/gdb/__init__.py:1140 in __int__                       │
│                                                                                                  │
│   1137 │   │   try:                                                                              │
│   1138 │   │   │   return int(self.inner)                                                        │
│   1139 │   │   except gdb.error as e:                                                            │
│ ❱ 1140 │   │   │   raise pwndbg.dbg_mod.Error(e)                                                 │
│   1141 │                                                                                         │
│   1142 │   @override                                                                             │
│   1143 │   def cast(self, type: pwndbg.dbg_mod.Type | Any) -> pwndbg.dbg_mod.Value:              │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
Error: Cannot convert value to long.
pwndbg> ptype struct vector
A syntax error in expression, near the end of `struct vector'.
pwndbg> dt struct vector
╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ -------------------------pwndbg/pwndbg/dbg/gdb/__init__.py:125 in evaluate_expression            │
│                                                                                                  │
│    122 │   │   with do_lock_scheduler() if lock_scheduler else nullcontext():                    │
│    123 │   │   │   with selection(self.inner, lambda: gdb.selected_frame(), lambda f: f.select(  │
│    124 │   │   │   │   try:                                                                      │
│ ❱  125 │   │   │   │   │   value = parse_and_eval(expression, global_context=False)              │
│    126 │   │   │   │   except gdb.error as e:                                                    │
│    127 │   │   │   │   │   raise pwndbg.dbg_mod.Error(e)                                         │
│    128                                                                                           │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/dbg/gdb/__init__.py:70 in parse_and_eval                  │
│                                                                                                  │
│     67 │   `global_context` was introduced in GDB 14.                                            │
│     68 │   """                                                                                   │
│     69 │   try:                                                                                  │
│ ❱   70 │   │   return gdb.parse_and_eval(expression, global_context)                             │
│     71 │   except TypeError:                                                                     │
│     72 │   │   return gdb.parse_and_eval(expression)                                             │
│     73                                                                                           │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
error: A syntax error in expression, near the end of `vector'.

During handling of the above exception, another exception occurred:

╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮
│ -------------------------pwndbg/pwndbg/commands/__init__.py:139 in invoke                        │
│                                                                                                  │
│   136 │   │   │   return                                                                         │
│   137 │   │                                                                                      │
│   138 │   │   try:                                                                               │
│ ❱ 139 │   │   │   args, kwargs = self.split_args(argument)                                       │
│   140 │   │   except SystemExit:                                                                 │
│   141 │   │   │   # Raised when the usage is printed by an ArgparsedCommand                      │
│   142 │   │   │   return                                                                         │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:602 in split_args                    │
│                                                                                                  │
│   599 │                                                                                          │
│   600 │   def split_args(self, argument: str):                                                   │
│   601 │   │   argv = pwndbg.dbg.lex_args(argument)                                               │
│ ❱ 602 │   │   return (), vars(self.parser.parse_args(argv))                                      │
│   603                                                                                            │
│   604                                                                                            │
│   605 class ArgparsedCommand:                                                                    │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:1895 in parse_args                                               │
│                                                                                                  │
│   1892 │   # Command line argument parsing methods                                               │
│   1893 │   # =====================================                                               │
│   1894 │   def parse_args(self, args=None, namespace=None):                                      │
│ ❱ 1895 │   │   args, argv = self.parse_known_args(args, namespace)                               │
│   1896 │   │   if argv:                                                                          │
│   1897 │   │   │   msg = _('unrecognized arguments: %s') % ' '.join(argv)                        │
│   1898 │   │   │   if self.exit_on_error:                                                        │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:1931 in parse_known_args                                         │
│                                                                                                  │
│   1928 │   │   # parse the arguments and exit if there are any errors                            │
│   1929 │   │   if self.exit_on_error:                                                            │
│   1930 │   │   │   try:                                                                          │
│ ❱ 1931 │   │   │   │   namespace, args = self._parse_known_args(args, namespace)                 │
│   1932 │   │   │   except ArgumentError as err:                                                  │
│   1933 │   │   │   │   self.error(str(err))                                                      │
│   1934 │   │   else:                                                                             │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2171 in _parse_known_args                                        │
│                                                                                                  │
│   2168 │   │   │   start_index = consume_optional(start_index)                                   │
│   2169 │   │                                                                                     │
│   2170 │   │   # consume any positionals following the last Optional                             │
│ ❱ 2171 │   │   stop_index = consume_positionals(start_index)                                     │
│   2172 │   │                                                                                     │
│   2173 │   │   # if we didn't consume all the argument strings, there were extras                │
│   2174 │   │   extras.extend(arg_strings[stop_index:])                                           │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2127 in consume_positionals                                      │
│                                                                                                  │
│   2124 │   │   │   │   │   │   │   │   │   │   │   │    start_index + arg_count) >= 0):          │
│   2125 │   │   │   │   │   │   args.remove('--')                                                 │
│   2126 │   │   │   │   start_index += arg_count                                                  │
│ ❱ 2127 │   │   │   │   take_action(action, args)                                                 │
│   2128 │   │   │                                                                                 │
│   2129 │   │   │   # slice off the Positionals that we just parsed and return the                │
│   2130 │   │   │   # index at which the Positionals' string args stopped                         │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:1991 in take_action                                              │
│                                                                                                  │
│   1988 │   │                                                                                     │
│   1989 │   │   def take_action(action, argument_strings, option_string=None):                    │
│   1990 │   │   │   seen_actions.add(action)                                                      │
│ ❱ 1991 │   │   │   argument_values = self._get_values(action, argument_strings)                  │
│   1992 │   │   │                                                                                 │
│   1993 │   │   │   # error if this argument is not allowed with other previously                 │
│   1994 │   │   │   # seen arguments                                                              │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2535 in _get_values                                              │
│                                                                                                  │
│   2532 │   │   # single argument or optional argument produces a single value                    │
│   2533 │   │   elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:                  │
│   2534 │   │   │   arg_string, = arg_strings                                                     │
│ ❱ 2535 │   │   │   value = self._get_value(action, arg_string)                                   │
│   2536 │   │   │   self._check_value(action, value)                                              │
│   2537 │   │                                                                                     │
│   2538 │   │   # REMAINDER arguments convert all values, checking none                           │
│                                                                                                  │
│ /usr/lib/python3.12/argparse.py:2568 in _get_value                                               │
│                                                                                                  │
│   2565 │   │                                                                                     │
│   2566 │   │   # convert the value to the appropriate type                                       │
│   2567 │   │   try:                                                                              │
│ ❱ 2568 │   │   │   result = type_func(arg_string)                                                │
│   2569 │   │                                                                                     │
│   2570 │   │   # ArgumentTypeErrors indicate errors                                              │
│   2571 │   │   except ArgumentTypeError as err:                                                  │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:272 in fix_int_reraise               │
│                                                                                                  │
│   269                                                                                            │
│   270                                                                                            │
│   271 def fix_int_reraise(*a, **kw) -> int:                                                      │
│ ❱ 272 │   return fix_int(*a, reraise=True, **kw)                                                 │
│   273                                                                                            │
│   274                                                                                            │
│   275 def OnlyWhenLocal(function: Callable[P, T]) -> Callable[P, Optional[T]]:                   │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:268 in fix_int                       │
│                                                                                                  │
│   265                                                                                            │
│   266                                                                                            │
│   267 def fix_int(*a, **kw) -> int:                                                              │
│ ❱ 268 │   return int(fix(*a, **kw))                                                              │
│   269                                                                                            │
│   270                                                                                            │
│   271 def fix_int_reraise(*a, **kw) -> int:                                                      │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:254 in fix                           │
│                                                                                                  │
│   251 │   │   if not quiet:                                                                      │
│   252 │   │   │   print(ex)                                                                      │
│   253 │   │   if reraise:                                                                        │
│ ❱ 254 │   │   │   raise ex                                                                       │
│   255 │                                                                                          │
│   256 │   if sloppy:                                                                             │
│   257 │   │   return arg                                                                         │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/commands/__init__.py:233 in fix                           │
│                                                                                                  │
│   230 │   │   #                                                                                  │
│   231 │   │   # TODO: Port architecutre functions and `pwndbg.gdblib.regs.fix` to debugger-agn   │
│   232 │   │   arg = pwndbg.aglib.regs.fix(arg)                                                   │
│ ❱ 233 │   │   return target.evaluate_expression(arg)                                             │
│   234 │   except Exception as e:                                                                 │
│   235 │   │   ex = e                                                                             │
│   236                                                                                            │
│                                                                                                  │
│ -------------------------pwndbg/pwndbg/dbg/gdb/__init__.py:127 in evaluate_expression            │
│                                                                                                  │
│    124 │   │   │   │   try:                                                                      │
│    125 │   │   │   │   │   value = parse_and_eval(expression, global_context=False)              │
│    126 │   │   │   │   except gdb.error as e:                                                    │
│ ❱  127 │   │   │   │   │   raise pwndbg.dbg_mod.Error(e)                                         │
│    128 │   │                                                                                     │
│    129 │   │   return GDBValue(value)                                                            │
│    130                                                                                           │
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
Error: A syntax error in expression, near the end of `vector'.
pwndbg> 
```

### My setup
Gdb:      15.2
Python:   3.12.7 
Pwndbg:   2024.08.29 build: ad90ec1a
Capstone: 5.0.1280
Unicorn:  2.1.0

Hints from issue discussion:
Hi @k4lizen, hope you're doing well!
I'm new to the community and would like to try solving this issue.
Could you please guide me on how to contribute and set up the environment?
Also, let me know more this bug 
@alguiguilo098 Hi, please read the https://github.com/pwndbg/pwndbg/blob/dev/DEVELOPING.md documentation.

Let us know if u have any troubles with what is described there.
Regarding the bug, I think the issue is rather self explanatory. We do not want to raise an exception, we want to show a nice error to the user...

Regarding the `ptype` command mentioned in the description: this is a GDB command. You can read its documentation on https://ftp.gnu.org/old-gnu/Manuals/gdb/html_node/gdb_109.html
thanks 
@alguiguilo098 
> Hi k4lizen, hope you're doing well!
> I'm new to the community and would like to try solving this issue.

Great! Glad to see you're interested in pwndbg.

> Could you please guide me on how to contribute and set up the environment?
> Also, let me know more this bug

I don't have anything to add to what disconnect3d said :p 
@k4lizen  kkkkk thanks 
@disconnect3d  @k4lizen what up ?

![image](https://github.com/user-attachments/assets/c1528900-2162-4800-a3ee-66c3faf8a903)

o command dt not found, they know why ?
pwndbg seems to not have been sourced, check your `~/.gdbinit`
Hello, @k4lizen 

I'm encountering an issue when trying to import the gbd module in my Python project. The error message I receive is:

ModuleNotFoundError: No module named 'gbd'

![image](https://github.com/user-attachments/assets/3885cda3-9e44-4ddd-88af-2d11c0fa03c6)


It seems that the module is not available in the current environment. Could you please guide me on how to resolve this issue?
Since this is a bit off-topic to the actual issue, it might be better to ask something like this on the discord. In any case, did you run `./setup.sh` and `./setup-dev.sh` as stated in the documentation?

Commit message: relay parsing error information to argparse

Patch to review:
diff --git a/pwndbg/commands/__init__.py b/pwndbg/commands/__init__.py
index cfde47451c8..2dc56c2f239 100644
--- a/pwndbg/commands/__init__.py
+++ b/pwndbg/commands/__init__.py
@@ -123,6 +123,9 @@ def _handler(_debugger, arguments, is_interactive):
     def split_args(self, argument: str) -> Tuple[List[str], Dict[Any, Any]]:
         """Split a command-line string from the user into arguments.
 
+        This is only used by pwndbg/commands/shell.py which is deprecated.
+        Usually _ArgparsedCommand.split_args is called.
+
         Returns:
             A ``(tuple, dict)``, in the form of ``*args, **kwargs``.
             The contents of the tuple/dict are undefined.
@@ -139,6 +142,7 @@ def invoke(self, argument: str, from_tty: bool) -> None:
             args, kwargs = self.split_args(argument)
         except SystemExit:
             # Raised when the usage is printed by an ArgparsedCommand
+            # because of an error in argument parsing
             return
         except (TypeError, pwndbg.dbg_mod.Error):
             pwndbg.exception.handle(self.function.__name__)
@@ -264,6 +268,18 @@ def fix_reraise(*a, **kw) -> str | pwndbg.dbg_mod.Value | None:
     return fix(*a, reraise=True, **kw)  # type: ignore[misc]
 
 
+def fix_reraise_arg(arg) -> pwndbg.dbg_mod.Value:
+    """fix_reraise wrapper for evaluating command arguments"""
+    try:
+        # Will always return pwndbg.gdb_mod.Value because
+        # sloppy=False (not str) and reraise=True (not None)
+        fixed = fix(arg, sloppy=False, quiet=True, reraise=True)
+        assert isinstance(fixed, pwndbg.dbg_mod.Value)
+        return fixed
+    except pwndbg.dbg_mod.Error as dbge:
+        raise argparse.ArgumentTypeError(f"debugger couldn't resolve argument '{arg}': {dbge}")
+
+
 def fix_int(*a, **kw) -> int:
     return int(fix(*a, **kw))
 
@@ -272,6 +288,17 @@ def fix_int_reraise(*a, **kw) -> int:
     return fix_int(*a, reraise=True, **kw)
 
 
+def fix_int_reraise_arg(arg) -> int:
+    """fix_int_reraise wrapper for evaluating command arguments"""
+    try:
+        fixed = fix_reraise_arg(arg)
+        return int(fixed)
+    except pwndbg.dbg_mod.Error as e:
+        raise argparse.ArgumentTypeError(
+            f"couldn't convert '{arg}' ({fixed.type.name_to_human_readable}) to int: {e}"
+        )
+
+
 def OnlyWhenLocal(function: Callable[P, T]) -> Callable[P, Optional[T]]:
     @functools.wraps(function)
     def _OnlyWhenLocal(*a: P.args, **kw: P.kwargs) -> Optional[T]:
@@ -604,10 +631,10 @@ def __init__(
                 action.type = str
             if action.dest == "help":
                 continue
-            if action.type == int:
-                action.type = fix_int_reraise
+            if action.type is int:
+                action.type = fix_int_reraise_arg
             if action.type is None:
-                action.type = fix_reraise
+                action.type = fix_reraise_arg
             if action.default is not None:
                 action.help += " (default: %(default)s)"
 


Code review comments:
diff_hunk: @@ -264,6 +268,18 @@ def fix_reraise(*a, **kw) -> str | pwndbg.dbg_mod.Value | None:
     return fix(*a, reraise=True, **kw)  # type: ignore[misc]
 
 
+def fix_reraise_arg(arg) -> pwndbg.dbg_mod.Value:
+    """fix_reraise wrapper for evaluating command arguments"""
+    try:
+        # Will always return pwndbg.gdb_mod.Value because
path: pwndbg/commands/__init__.py
text: @user1:
Shouldn't we say pwndbg.dbg_mod.Value? I guess in LLDB this won't return gdb_mod :P
```suggestion
        # Will always return pwndbg.dbg_mod.Value because
```

@user2:
Probably a typo. `gdb_mod` doesn't exist lol.

@author:
Oh oops, yup it's a typo