diff --git a/pwndbg/commands/__init__.py b/pwndbg/commands/__init__.py index 9cf5eb271..8227e1602 100644 --- a/pwndbg/commands/__init__.py +++ b/pwndbg/commands/__init__.py @@ -339,7 +339,7 @@ class _ArgparsedCommand(Command): class ArgparsedCommand: """Adds documentation and offloads parsing for a Command via argparse""" - def __init__(self, parser_or_desc, aliases=[]): + def __init__(self, parser_or_desc, aliases=[], command_name=None): """ :param parser_or_desc: `argparse.ArgumentParser` instance or `str` """ @@ -348,6 +348,8 @@ class ArgparsedCommand: else: self.parser = parser_or_desc self.aliases = aliases + self._command_name = command_name + # We want to run all integer and otherwise-unspecified arguments # through fix() so that GDB parses it. for action in self.parser._actions: @@ -360,8 +362,8 @@ class ArgparsedCommand: def __call__(self, function): for alias in self.aliases: - _ArgparsedCommand(self.parser, function, alias) - return _ArgparsedCommand(self.parser, function) + _ArgparsedCommand(self.parser, function, command_name=alias) + return _ArgparsedCommand(self.parser, function, command_name=self._command_name) # We use a 64-bit max value literal here instead of pwndbg.arch.current diff --git a/pwndbg/commands/misc.py b/pwndbg/commands/misc.py index fc3a20905..c873bd34e 100644 --- a/pwndbg/commands/misc.py +++ b/pwndbg/commands/misc.py @@ -1,14 +1,14 @@ import argparse -import errno as _errno +import errno + +import gdb -import pwndbg as _pwndbg -import pwndbg.arch as _arch import pwndbg.auxv import pwndbg.commands import pwndbg.regs import pwndbg.symbol -_errno.errorcode[0] = "OK" +errno.errorcode[0] = "OK" parser = argparse.ArgumentParser( description=""" @@ -24,24 +24,25 @@ parser.add_argument( ) -@_pwndbg.commands.ArgparsedCommand(parser) +@pwndbg.commands.ArgparsedCommand(parser, command_name="errno") @pwndbg.commands.OnlyWhenRunning -def errno(err): +def errno_(err): if err is None: - # Dont ask. - errno_location = pwndbg.symbol.get("__errno_location") - err = pwndbg.memory.int(errno_location) - # err = int(gdb.parse_and_eval('*((int *(*) (void)) __errno_location) ()')) - - err = abs(int(err)) - - if err >> 63: - err -= 1 << 64 - elif err >> 31: - err -= 1 << 32 - - msg = _errno.errorcode.get(int(err), "Unknown error code") - print("Errno %i: %s" % (err, msg)) + # Try to get the `errno` variable value + # if it does not exist, get the errno variable from its location + try: + err = int(gdb.parse_and_eval("errno")) + except gdb.error: + try: + err = int(gdb.parse_and_eval("*((int *(*) (void)) __errno_location) ()")) + except gdb.error: + print( + "Could not determine error code automatically: neither `errno` nor `__errno_location` symbols were provided (was libc.so loaded already?)" + ) + return + + msg = errno.errorcode.get(int(err), "Unknown error code") + print("Errno %s: %s" % (err, msg)) parser = argparse.ArgumentParser( @@ -58,8 +59,8 @@ parser.add_argument( ) -@_pwndbg.commands.ArgparsedCommand(parser) -def pwndbg(filter_pattern): +@pwndbg.commands.ArgparsedCommand(parser, command_name="pwndbg") +def pwndbg_(filter_pattern): for name, docs in list_and_filter_commands(filter_pattern): print("%-20s %s" % (name, docs)) @@ -69,19 +70,19 @@ parser.add_argument("a", type=int, help="The first address.") parser.add_argument("b", type=int, help="The second address.") -@_pwndbg.commands.ArgparsedCommand(parser) +@pwndbg.commands.ArgparsedCommand(parser) def distance(a, b): """Print the distance between the two arguments""" - a = int(a) & _arch.ptrmask - b = int(b) & _arch.ptrmask + a = int(a) & pwndbg.arch.ptrmask + b = int(b) & pwndbg.arch.ptrmask distance = b - a - print("%#x->%#x is %#x bytes (%#x words)" % (a, b, distance, distance // _arch.ptrsize)) + print("%#x->%#x is %#x bytes (%#x words)" % (a, b, distance, distance // pwndbg.arch.ptrsize)) def list_and_filter_commands(filter_str): - sorted_commands = list(_pwndbg.commands.commands) + sorted_commands = list(pwndbg.commands.commands) sorted_commands.sort(key=lambda x: x.__name__) if filter_str: diff --git a/tests/test_command_errno.py b/tests/test_command_errno.py new file mode 100644 index 000000000..83da35042 --- /dev/null +++ b/tests/test_command_errno.py @@ -0,0 +1,48 @@ +import gdb + +import pwndbg +import pwndbg.memory +import pwndbg.regs +import tests + +# We use the heap_vis binary as it enforces pthreads and so will have TLS on all distros +REFERENCE_BINARY = tests.binaries.get("heap_vis.out") + + +def test_command_errno(start_binary): + """ + Tests the errno command display + """ + start_binary(REFERENCE_BINARY) + + # Since start_binary does 'starti' which stops on the very first instruction + # the errno is not yet an available symbol, because the libc library it is + # defined in is not yet loaded + result = "".join(gdb.execute("errno", to_string=True).splitlines()) + assert ( + result + == "Could not determine error code automatically: neither `errno` nor `__errno_location` symbols were provided (was libc.so loaded already?)" + ) + + gdb.execute("break main") + gdb.execute("continue") + + result = gdb.execute("errno", to_string=True) + assert result == "Errno 0: OK\n" + + gdb.execute("set *(int*)&errno=11") + result = gdb.execute("errno", to_string=True) + assert result == "Errno 11: EAGAIN\n" + + gdb.execute("set *(int*)&errno=111") + result = gdb.execute("errno", to_string=True) + assert result == "Errno 111: ECONNREFUSED\n" + + result = gdb.execute("errno 8", to_string=True) + assert result == "Errno 8: ENOEXEC\n" + + result = gdb.execute("errno 123", to_string=True) + assert result == "Errno 123: ENOMEDIUM\n" + + result = gdb.execute("errno 250", to_string=True) + assert result == "Errno 250: Unknown error code\n"