Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> Debuggers aren't very good at handling this situation. Because once the control flow jumps to the NULL page, you'd need to find a way to rewind execution history to figure out how it got there.

This is one place where reversible debuggers shine. Try rr[1].

[1] https://rr-project.org/



In my experience debuggers handles this fine. Some archs also has a link register (jump and link) which may help finding back. This test is from x86-64 Linux.

  /*
  gcc -g -Wall -o x x.c
  gdb ./x
  (gdb) r
  (gdb) bt
  #0  0x0000000000000000 in ?? ()
  #1  0x0000555555554617 in foo () at x.c:6
  #2  0x0000555555554628 in main () at x.c:10
  (gdb) f 1
  #1  0x0000555555554617 in foo () at x.c:6
  6           bar();
  (gdb) p bar
  $1 = (void (*)(void)) 0x0
  */
  
  #include <stddef.h>
  
  void (*bar)(void) = NULL;
  
  void foo() {
      bar();
  }
  
  int main() {
      foo();
  }


It gets worse if you have a stack smash, e.g:

  #include <string.h>
  
  void foo() {
      int array[1];
      memset(array, 0, 100); /\* Oh no, trash the stack! */
  }
  
  int main() {
      foo();
  }
This gets us:

  Program received signal SIGSEGV, Segmentation fault.
  0x0000000000000000 in ?? ()
  (gdb) bt
  #0  0x0000000000000000 in ?? ()
  #1  0x0000000000000000 in ?? ()
But with a time travel debugger (I'm using UDB because - disclaimer - it's what I work on. `rr` would work just as well):

  Program received signal SIGSEGV, Segmentation fault.
  0x0000000000000000 in ?? ()
  recording 10,617> backtrace
  #0  0x0000000000000000 in ?? ()
  #1  0x0000000000000000 in ?? ()

^ Because we returned to NULL we have segfaulted but the stack is also trashed due to the memset. We don't know how we got here.

  recording 10,617> reverse-stepi
  0x0000000000401146      6       }
  99% 10,616> bt
  #0  0x0000000000401146 in foo () at smash.c:6
  #1  0x0000000000000000 in ?? ()

^ We've stepped back before the return, so we can now see how we got to NULL. Still incomplete stack because it's still trashed.

  99% 10,616> reverse-step
  5           memset(array, 0, 100); /* Oh no, trash the stack! \*/
  99% 10,586> bt
  #0  foo () at smash.c:5
  #1  0x0000000000401155 in main () at smash.c:9
  99% 10,586> 
^ We've gone back before the stack smash happened, so now we get the full backtrace.


ASAN is pretty great with these cases as well. Spent much time looking for the needle in the haystack before ASAN came along.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: