Repository: pwndbg/pwndbg

Issue:
errno command is throwing errors
```
pwndbg> errno
Traceback (most recent call last):
  File "/home/gsgx/code/pwndbg/pwndbg/commands/__init__.py", line 145, in __call__
    return self.function(*args, **kwargs)
  File "/home/gsgx/code/pwndbg/pwndbg/commands/__init__.py", line 239, in _OnlyWhenRunning
    return function(*a, **kw)
  File "/home/gsgx/code/pwndbg/pwndbg/commands/misc.py", line 32, in errno
    errno_location = pwndbg.symbol.get("__errno_location")
AttributeError: '_ArgparsedCommand' object has no attribute 'symbol'
```

I don't know why the issue is occurring, but it can be fixed with this:
```diff
-        errno_location = pwndbg.symbol.get("__errno_location")
-        err = pwndbg.memory.int(errno_location)
+        errno_location = _pwndbg.symbol.address("__errno_location")
+        print(errno_location)
+        err = _pwndbg.memory.s32(errno_location)
```

But even after fixing that, there's an issue with the following lines:
```python
err = _pwndbg.memory.s32(errno_location)
#err = int(gdb.parse_and_eval('*((int *(*) (void)) __errno_location) ()'))
```

The commented out line is what actually works for me, while the existing code doesn't. I wonder if this was originally due to a change in glibc? If so, we should figure out what version and make this work on both versions.




Discussion:
Fwiw `__errno_location` is a function that will return the `errno` address, so we can't just take its address, we probably want to execute it(?)

<img width="723" alt="image" src="https://user-images.githubusercontent.com/10009354/188022642-e7cc4acc-bced-4f12-a1f8-b05088de4a42.png">

Fwiw, we can just do `gdb.parse_and_eval('errno')` and that will give us the `errno` value lol
> Fwiw __errno_location is a function that will return the errno address, so we can't just take its address, we probably want to execute it(?)

Yea, that's what the commented line I mentioned does:
```
#err = int(gdb.parse_and_eval('*((int *(*) (void)) __errno_location) ()'))
```
Yup, but we can also just print `errno` instead of executing the `__erno_location` I guess?

OTOH, it could be that `errno` would be a local variable in current function etc.
Hmm, I can't replicate that.

```
pwndbg> p __errno_location
$2 = {<text variable, no debug info>} 0x555555558710 <__errno_location@plt>
pwndbg> p errno
No symbol "errno" in current context.
```
Haha, I got into a similar situation with my testing, see https://asciinema.org/a/foERhNehJK7KQfpvAezCFiyyn :)

TL;DR:
- you compiled a binary that uses `__errno_location` so it got a `__erno_location@plt` plt stub - so you can print that plt function address
- the `errno` variable is not defined yet, because you stopped the binary in a place where libc.so is still not loaded; I bet you used `starti` which stopped on the very first instruction of the dynamic loader (ld.so) and so the libc.so is not loaded yet :P 
Fwiw https://github.com/pwndbg/pwndbg/pull/1112 will fix this issue but I have to fix the issue with Ubuntu 20.04 telling us it cannot find TLS...
Seems to work with the PR, but we should make sure that if those symbols aren't resolved the binary doesn't segfault:
```
$ gdb /bin/ls -ex starti -ex errno
pwndbg: loaded 202 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from /bin/ls...
(No debugging symbols found in /bin/ls)
Starting program: /usr/bin/ls

Program stopped.
0x00007ffff7fe32b0 in _start () from /lib64/ld-linux-x86-64.so.2

Program received signal SIGSEGV, Segmentation fault.
0x00000000000040a0 in ?? ()
Could not determine error code automatically: neither `errno` nor `__errno_location` symbols were provided (was libc.so loaded already?)
```

Resolving patch:
diff --git a/pwndbg/commands/misc.py b/pwndbg/commands/misc.py
--- a/pwndbg/commands/misc.py
+++ b/pwndbg/commands/misc.py
@@ -35,10 +35,24 @@ def errno_(err):
             err = int(gdb.parse_and_eval("errno"))
         except gdb.error:
             try:
-                err = int(gdb.parse_and_eval("*((int *(*) (void)) __errno_location) ()"))
+                # We can't simply call __errno_location because its .plt.got entry may be uninitialized
+                # (e.g. if the binary was just started with `starti` command)
+                # So we have to check the got.plt entry first before calling it
+                errno_loc_gotplt = pwndbg.symbol.address("__errno_location@got.plt")
+
+                # If the got.plt entry is not there (is None), it means the symbol is not used by the binary
+                if errno_loc_gotplt is None or pwndbg.vmmap.find(
+                    pwndbg.memory.pvoid(errno_loc_gotplt)
+                ):
+                    err = int(gdb.parse_and_eval("*((int *(*) (void)) __errno_location) ()"))
+                else:
+                    print(
+                        "Could not determine error code automatically: the __errno_location@got.plt has no valid address yet (perhaps libc.so hasn't been loaded yet?)"
+                    )
+                    return
             except gdb.error:
                 print(
-                    "Could not determine error code automatically: neither `errno` nor `__errno_location` symbols were provided (was libc.so loaded already?)"
+                    "Could not determine error code automatically: neither `errno` nor `__errno_location` symbols were provided (perhaps libc.so hasn't been not loaded yet?)"
                 )
                 return
 
diff --git a/pwndbg/lib/tips.py b/pwndbg/lib/tips.py
--- a/pwndbg/lib/tips.py
+++ b/pwndbg/lib/tips.py
@@ -21,6 +21,7 @@
     "Use the `procinfo` command for better process introspection (than the GDB's `info proc` command)",
     "Want to display each context panel in a separate tmux window? See https://github.com/pwndbg/pwndbg/blob/dev/FEATURES.md#splitting--layouting-context",
     "The $heap_base GDB variable can be used to refer to the starting address of the heap after running the `heap` command",
+    "Use the `errno` (or `errno <number>`) command to see the name of the last or provided (libc) error",
 ]
 
 


Test patch:
diff --git a/tests/test_command_errno.py b/tests/test_command_errno.py
--- a/tests/test_command_errno.py
+++ b/tests/test_command_errno.py
@@ -21,7 +21,7 @@ def test_command_errno(start_binary):
     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?)"
+        == "Could not determine error code automatically: neither `errno` nor `__errno_location` symbols were provided (perhaps libc.so hasn't been not loaded yet?)"
     )
 
     gdb.execute("break main")