Repository: pwndbg/pwndbg

Issue:
[Bug] pwndbg shows incorrect stack context
### Environment
* Ubuntu Linux 16.04 64 bit
* latest version of pwndbg (`1.0.0 build: 824dd96`) (edit: add pwndbg version)

--------------------------

### Details
In some binaries, pwndbg will show the incorrect stack context, for example (sorry for not providing the testing binary, it requires a lot of settings to run this binary and the VM is pretty large) :  
![1](https://user-images.githubusercontent.com/4478350/39750107-f58848c0-52e6-11e8-8759-8629cb40e315.PNG)

I've print out the value of `pwndbg.regs.sp`, and found that it did not match the value of the current esp.  
I couldn't find where pwndbg update the value of `pwndbg.regs.sp` though, so I have no idea where the bug exist.  
 
Strangely, if we modify the value of a register, pwndbg will update the `pwndbg.regs.sp` to the correct value, thus showing the correct stack context:  
![2](https://user-images.githubusercontent.com/4478350/39750278-6d7ba110-52e7-11e8-8a7a-193f10bea6c8.PNG)

--------------------------  

This happened several times, both in x86/x64 architecture. It usually happened while I was debugging a large/complicated binary. The example above was an x86 binary, which is a [libFuzzer](https://llvm.org/docs/LibFuzzer.html) compiled with [ASAN](https://github.com/google/sanitizers/wiki/AddressSanitizer) enabled.  It also happened while I was debugging an x64 version of [SpiderMonkey](https://developer.mozilla.org/zh-TW/docs/Mozilla/Projects/SpiderMonkey).  

I think the way to fix this issue is to update all the registers before showing the context? 




Discussion:
Hey, thanks for reporting the bug!

It would be good to be able to reproduce it somehow :/. Any chance to work this out together using a TeamViewer or other screen sharing? If so, ping me at freenode irc (same nick).

Maybe it is due to caching something that shouldn't be cached. Could you check if problem exists after disabling cache? This can be done by:
```
pwndbg> python import pwndbg; pwndbg.memoize.memoize.caching = False
```

> latest version of pwndbg

Is it latest `dev` branch? Its best to show the result of `version` command.

> I think the way to fix this issue is to update all the registers before showing the context?

I believe this is what we do already. I think this is the code that is executed when you get register value (e.g. `pwndbg.regs.sp`):
https://github.com/pwndbg/pwndbg/blob/824dd9645cd06cbb5b63f208ee2604cb8ef28b4a/pwndbg/regs.py#L269-L284

PS: Did you change some theme settings? Mind sharing it to others on https://github.com/pwndbg/pwndbg-themes ? :D (if its not @anthraxx's theme ;P).
Hello @disconnect3d , 

> Any chance to work this out together using a TeamViewer or other screen sharing?  
  
Due to our company policy I'm afraid I can't do that :(  

>Could you check if problem exists after disabling cache?   

**Disabling the cache actually fixes the issue ! But this slows pwndbg down (  a lot !  )**

> Is it latest `dev` branch?  

Yes

>  Its best to show the result of version command.

Here it is:
```
pwndbg> version
Gdb:      GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Python:   3.5.2 (default, Nov 23 2017, 16:37:01)  [GCC 5.4.0 20160609]
Pwndbg:   1.0.0 build: 824dd96
Capstone: 4.0.1024
Unicorn:  1.0.1
```

> Did you change some theme settings? Mind sharing it to others on https://github.com/pwndbg/pwndbg-themes ? :D

Here's my color-theme setting ( from [here](https://github.com/bruce30262/CTF/blob/408406a2ad90c406910359f5d80c77f71a8e42d1/debugger/.gdbinit_pwndbg#L19-L27) ):  
```
# customize color-theme
set banner-color cyan,bold
set context-register-changed-color red,bold
set context-register-color green
set disasm-branch-color yellow
set memory-data-color blue
set memory-heap-color purple,bold
set memory-rodata-color green
set memory-stack-color purple
```
Feel free to copy that and push it to https://github.com/pwndbg/pwndbg-themes :)  
( I'm too lazy to do that :P  )  

Since now we know it's a caching problem, I'll keep tracing the caching part  and see what I can do to fix this issue .
@disconnect3d I've been debugging for a while, and found that this issue is related to **multi-thread** problem.

-----------------------------
testing binary ( arch : x64 ): 
![test_thread](https://user-images.githubusercontent.com/4478350/39852986-2f197bec-5452-11e8-9d89-4f38be38d338.jpg)

source code: 
```c

/* gcc -o t t.c -lpthread*/

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define NUM_THREADS 2

void *thr_func1() {
    printf("thr_func1\n");
    int ooo=100000000;
    /* in gdb, press ctrl-c while doing the following operation */
    while(ooo--);
    pthread_exit(NULL);
}

void *thr_func2() {
    printf("thr_func2\n");
    int ooo=100000000;
    /* in gdb, press ctrl-c while doing the following operation */
    while(ooo--);
    pthread_exit(NULL);
}

int main(int argc, char **argv) {
    pthread_t thr[NUM_THREADS];
    int i, rc;

    for (i = 0; i < NUM_THREADS; ++i) {
        if (i == 0)
        {
            if ((rc = pthread_create(&thr[i], NULL, thr_func1, NULL))) {
                fprintf(stderr, "error: pthread_create, rc: %d\n", rc);
                return -1;
            }
        }
        else
        {
            if ((rc = pthread_create(&thr[i], NULL, thr_func2, NULL))) {
                fprintf(stderr, "error: pthread_create, rc: %d\n", rc);
                return -1;
            }
        }
    }
    for (i = 0; i < NUM_THREADS; ++i) {
        pthread_join(thr[i], NULL);
    }

    return 0;
}


``` 

While doing the `while(ooo--);` operation, I stopped pwndbg ( ctrl-c ) and take a look at the stack frame of each thread:  

```
pwndbg> info thread
  Id   Target Id         Frame
* 1    Thread 0x7ffff7fe6700 (LWP 13529) "t" 0x00007ffff7bc298d in pthread_join (threadid=140737345681152, thread_return=0x0) at pthread_join.c:90
  2    Thread 0x7ffff77ef700 (LWP 13533) "t" 0x00000000004007f0 in thr_func1 ()
  3    Thread 0x7ffff6fee700 (LWP 13534) "t" 0x0000000000400824 in thr_func2 ()

```
In thread 1, I check the value of `pwndbg.regs.rsp` & `pwndbg.regs.sp`:

![sss](https://user-images.githubusercontent.com/4478350/39853068-94a70f06-5452-11e8-8f21-6706fc4713bd.PNG)

You can see that they aren't the same value.   
`pwndbg.regs.rsp` always point to the current thread's stack address, but `pwndbg.regs.sp` seems to point to the **newest** thread stack address.  

If we switch to thread 3 and check the value of those regs, we'll see that they become the same value:  

![s2](https://user-images.githubusercontent.com/4478350/39853172-2548f95c-5453-11e8-81d6-f531c5764c49.PNG)

----------------------------------------

I haven't check the code so I don't know how pwndbg handle the multi-thread situation.  
To fix this issue, we'll have to make sure that `pwndbg.regs.sp` is always point to the current thread's stack's top address.
Thanks a lot for debugging this. The problem probably comes from the fact on how we fetch registers. From regs.py :
```python
@pwndbg.proc.OnlyWhenRunning
def gdb77_get_register(name):
    return gdb.parse_and_eval('$' + name)

@pwndbg.proc.OnlyWhenRunning
def gdb79_get_register(name):
    return gdb.newest_frame().read_register(name)
```

I guess that we have to either use only the old way (parse and eval) or find out if GDB API has something for retrieving frame from current thread and not just 'newest' (I can't see it right now as I am writing from my phone :p).
Hm or it probably comes from the fact we cache registers not to fetch them multiple times. We do invalidate the cache on a stop event, we probably should also check if there is some thread changed event OR we could make the thread id a cache key (not sure how to do that atm tho).
I was expecting caching to be the issue. I don’t think GDB has a built-in
event for detecting thread swaps — I think we can check every time the
prompt shows?

We’ll likely need another cache / event for threading. I’m honestly sad /
surprised that switching threads doesn’t issue a stop event.
On Thu, May 10, 2018 at 12:45 AM Disconnect3d <notifications@github.com>
wrote:

> Hm or it probably comes from the fact we cache registers not to fetch them
> multiple times. We do invalidate the cache on a stop event, we probably
> should also check if there is some thread changed event OR we could make
> the thread id a cache key (not sure how to do that atm tho).
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/pwndbg/pwndbg/issues/460#issuecomment-387958326>, or mute
> the thread
> <https://github.com/notifications/unsubscribe-auth/AAG0GACh2NrUGfGiCNJDTajo37U132Fhks5tw9QTgaJpZM4T2Xne>
> .
>

> I think we can check every time the prompt shows?  

Sounds good. `events.before_prompt` should do the trick.

Resolving patch:
diff --git a/pwndbg/events.py b/pwndbg/events.py
--- a/pwndbg/events.py
+++ b/pwndbg/events.py
@@ -10,8 +10,9 @@
 from __future__ import print_function
 from __future__ import unicode_literals
 
-import functools
 import sys
+from functools import partial
+from functools import wraps
 
 import gdb
 
@@ -65,11 +66,14 @@ def on_stop(self):
 
 # In order to support reloading, we must be able to re-fire
 # all 'objfile' and 'stop' events.
-registered = {gdb.events.exited: [],
-              gdb.events.cont: [],
-              gdb.events.new_objfile: [],
-              gdb.events.stop: [],
-              gdb.events.start: []}
+registered = {
+    gdb.events.exited: [],
+    gdb.events.cont: [],
+    gdb.events.new_objfile: [],
+    gdb.events.stop: [],
+    gdb.events.start: [],
+    gdb.events.before_prompt: []
+}
 
 # GDB 7.9 and above only
 try:
@@ -96,7 +100,7 @@ def connect(func, event_handler, name=''):
     if debug:
         print("Connecting", func.__name__, event_handler)
 
-    @functools.wraps(func)
+    @wraps(func)
     def caller(*a):
         if debug:
             sys.stdout.write('%r %s.%s %r\n' % (name, func.__module__, func.__name__, a))
@@ -131,6 +135,9 @@ def cont(func):        return connect(func, gdb.events.cont, 'cont')
 def new_objfile(func): return connect(func, gdb.events.new_objfile, 'obj')
 def stop(func):        return connect(func, gdb.events.stop, 'stop')
 def start(func):       return connect(func, gdb.events.start, 'start')
+
+before_prompt = partial(connect, event_handler=gdb.events.before_prompt, name='before_prompt')
+
 def reg_changed(func):
     try:
         return connect(func, gdb.events.register_changed, 'reg_changed')
diff --git a/pwndbg/memoize.py b/pwndbg/memoize.py
--- a/pwndbg/memoize.py
+++ b/pwndbg/memoize.py
@@ -96,6 +96,19 @@ def __reset_on_stop():
     _reset = __reset_on_stop
 
 
+class reset_on_prompt(memoize):
+    caches = []
+    kind   = 'prompt'
+
+    @staticmethod
+    @pwndbg.events.before_prompt
+    def __reset_on_prompt():
+        for obj in reset_on_prompt.caches:
+            obj.cache.clear()
+
+    _reset = __reset_on_prompt
+
+
 class reset_on_exit(memoize):
     caches = []
     kind   = 'exit'
diff --git a/pwndbg/regs.py b/pwndbg/regs.py
--- a/pwndbg/regs.py
+++ b/pwndbg/regs.py
@@ -266,7 +266,7 @@ def gdb79_get_register(name):
 class module(ModuleType):
     last = {}
 
-    @pwndbg.memoize.reset_on_stop
+    @pwndbg.memoize.reset_on_prompt
     def __getattr__(self, attr):
         attr = attr.lstrip('$')
         try:


Test patch: