Repository: pwndbg/pwndbg

Issue:
'start' command behavior has changed
`git bisect` suggests this was introduced in 8f33ec480f47c038e0077cab67153f3ec3be9181

Prior to this, when debugging programs built with `gcc` using the `-ggdb` flag, pwndbg would break at the program's `main()` function immediately after the `start` command was issued. Since 8f33ec480f47c038e0077cab67153f3ec3be9181 the `start` command breaks at `_start()` the 1st time it's issued, and must be issued a 2nd time before `main()` is reached, after which it consistently stops at `main()`. See below for an example.

A simple program:
```
// main.c
#include <stdio.h>

void main(void) {
    printf("Hello World!\n");
}
```

Built and run with:
```
$ gcc -ggdb main.c
$ gdb a.out
pwndbg> set context-sections code
pwndbg> start
```

The `set context-sections` command is used here to truncate output for the screenshots and isn't part of the bug.
Previously, `main()` was reached immediately:
![start_sucess](https://user-images.githubusercontent.com/16000770/85638764-a1142880-b63b-11ea-83a5-b5257c3eca1e.PNG)

After 8f33ec480f47c038e0077cab67153f3ec3be9181 the `start` command breaks at `_start()` first, then it must be issued a 2nd time before `main()` is reached and the "source" panel appears. After this, every invocation of `start` will break at the `main()` function:
![start_fail](https://user-images.githubusercontent.com/16000770/85638801-bab57000-b63b-11ea-9b98-14832a8690af.PNG)

Perhaps some folk would rather break earlier in their code when issuing the `start` command, but it's odd that when starting the program for the 2nd time a different result is obtained, since `start` tells GDB to run the program over from scratch.


Discussion:
Wow, nice catch!

So the issue comes from the fact we do `if not pwndbg.vmmap.find(address): return None` in `pwndbg.symbol.address` for a given symbol's address and on PIE binaries, the `main` (and similar) functions will first report their offsets instead of rebased address when fetching address from GDB for symbol.

Maybe we should return an information from `pwndbg.symbol.address` whether the address needs to be rebased or not (if it is mapped or not), or, just leave it to the caller?

As of GDB 8.1, `start` is a built-in command and does the "right thing".  It might be worth dropping our own `start` command to not break GDB's.  I think we already avoid registering commands which are already GDB defaults.

EDIT: I'm entirely wrong, the new command is `starti`.  The default `start` command does not work as well as ours, so I recommend keeping it.

```
$ gdb -nx /bin/true
Reading symbols from /bin/true...
(No debugging symbols found in /bin/true)
(gdb) start
Function "main" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Temporary breakpoint 1 (-qualified main) pending.
Starting program: /usr/bin/true
[Inferior 1 (process 229591) exited normally]
```

```
$ gdb /bin/true
pwndbg> start
Temporary breakpoint 1 at 0x555555556610

(works as expected)
```


Resolving patch:
diff --git a/pwndbg/commands/start.py b/pwndbg/commands/start.py
--- a/pwndbg/commands/start.py
+++ b/pwndbg/commands/start.py
@@ -49,7 +49,7 @@ def start(args=None):
                 "_init"]
 
     for symbol in symbols:
-        address = pwndbg.symbol.address(symbol)
+        address = pwndbg.symbol.address(symbol, allow_unmapped=True)
 
         if not address:
             continue
diff --git a/pwndbg/symbol.py b/pwndbg/symbol.py
--- a/pwndbg/symbol.py
+++ b/pwndbg/symbol.py
@@ -190,7 +190,7 @@ def get(address, gdb_only=False):
     return ''
 
 @pwndbg.memoize.reset_on_objfile
-def address(symbol):
+def address(symbol, allow_unmapped=False):
     if isinstance(symbol, int):
         return symbol
 
@@ -215,7 +215,7 @@ def address(symbol):
         # pwndbg> info address tcache
         # Symbol "tcache" is a thread-local variable at offset 0x40
         # in the thread-local storage for `/lib/x86_64-linux-gnu/libc.so.6'.
-        if not pwndbg.vmmap.find(address):
+        if not allow_unmapped and not pwndbg.vmmap.find(address):
             return None
 
         return address


Test patch: