gdb watchpoint not activated

480 Views Asked by At

Consider the code:

#include <stdio.h>
#include <stdlib.h>

int update (int *arr, int size);

#define SIZE 10

int main() { // <---------------------- Breakpoint 1
  int x[SIZE];

  // Initialize array
  for (int c = 0 ; c < SIZE ; c++) {
    x[c] = c * 2;
  }

  // Do some random updates to an array
  update((int*) &x, SIZE);

  // Print the elements
  for (int c = 0 ; c < SIZE ; c++) {
    printf("%d\n", x[c]);
  }

  return EXIT_SUCCESS;
} //            <----------------------Breakpoint 2

int update (int *arr, int size) {
  for (int i = 0 ; i < size ; i++) {
    arr[i] += i;
    update(arr+i, size-1);
  }
  return 1;
}

Result of running info frame at breakpoint 1:

Stack level 0, frame at 0x7ffc176b2610:
 rip = 0x56434b0d76b8 in main (array.c:12); saved rip = 0x7f8190fb92b1
 source language c.
 Arglist at 0x7ffc176b2600, args:
 Locals at 0x7ffc176b2600, Previous frame's sp is 0x7ffc176b2610
 Saved registers:
  rbp at 0x7ffc176b2600, rip at 0x7ffc176b2608

Result of running info frame at breakpoint 2:

Stack level 0, frame at 0x7ffc176b2610:
 rip = 0x56434b0d771a in main (array.c:24); saved rip = 0x2d28490fd6501
 source language c.
 Arglist at 0x7ffc176b2600, args:
 Locals at 0x7ffc176b2600, Previous frame's sp is 0x7ffc176b2610
 Saved registers:
  rbp at 0x7ffc176b2600, rip at 0x7ffc176b2608

We see that main()'s saved return address rip at 0x7ffc176b2608 is mutated from 0x7f8190fb92b1 to 0x2d28490fd6501 between the two breakpoints.

However, setting a watchpoint on the address of rip with watch * 0x7ffc176b2608 and running the executable anew does not pause execution between the breakpoints as expected.

How can this be?

-----------EDIT-----------

Output of disassemble /s main:

Dump of assembler code for function main:
array.c:
8   int main() {
   0x000056434b0d76b0 <+0>: push   rbp
   0x000056434b0d76b1 <+1>: mov    rbp,rsp
   0x000056434b0d76b4 <+4>: sub    rsp,0x30

9     int x[SIZE];
10  
11    // Initialize array
12    for (int c = 0 ; c < SIZE ; c++) {
   0x000056434b0d76b8 <+8>: mov    DWORD PTR [rbp-0x4],0x0
   0x000056434b0d76bf <+15>:    jmp    0x56434b0d76d4 <main+36>

13      x[c] = c * 2;
   0x000056434b0d76c1 <+17>:    mov    eax,DWORD PTR [rbp-0x4]
   0x000056434b0d76c4 <+20>:    lea    edx,[rax+rax*1]
   0x000056434b0d76c7 <+23>:    mov    eax,DWORD PTR [rbp-0x4]
   0x000056434b0d76ca <+26>:    cdqe   
   0x000056434b0d76cc <+28>:    mov    DWORD PTR [rbp+rax*4-0x30],edx

12    for (int c = 0 ; c < SIZE ; c++) {
   0x000056434b0d76d0 <+32>:    add    DWORD PTR [rbp-0x4],0x1
   0x000056434b0d76d4 <+36>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x000056434b0d76d8 <+40>:    jle    0x56434b0d76c1 <main+17>

14    }
15  
16    // Do some random updates to an array
17    update((int*) &x, SIZE);
   0x000056434b0d76da <+42>:    lea    rax,[rbp-0x30]
   0x000056434b0d76de <+46>:    mov    esi,0xa
   0x000056434b0d76e3 <+51>:    mov    rdi,rax
   0x000056434b0d76e6 <+54>:    call   0x56434b0d7721 <update>

18  
19    // Print the elements
20    for (int c = 0 ; c < SIZE ; c++) {
   0x000056434b0d76eb <+59>:    mov    DWORD PTR [rbp-0x8],0x0
   0x000056434b0d76f2 <+66>:    jmp    0x56434b0d7714 <main+100>

21      printf("%d\n", x[c]);
   0x000056434b0d76f4 <+68>:    mov    eax,DWORD PTR [rbp-0x8]
   0x000056434b0d76f7 <+71>:    cdqe   
   0x000056434b0d76f9 <+73>:    mov    eax,DWORD PTR [rbp+rax*4-0x30]
   0x000056434b0d76fd <+77>:    mov    esi,eax
   0x000056434b0d76ff <+79>:    lea    rdi,[rip+0x12e]        # 0x56434b0d7834
   0x000056434b0d7706 <+86>:    mov    eax,0x0
   0x000056434b0d770b <+91>:    call   0x56434b0d7560 <printf@plt>

20    for (int c = 0 ; c < SIZE ; c++) {
   0x000056434b0d7710 <+96>:    add    DWORD PTR [rbp-0x8],0x1
   0x000056434b0d7714 <+100>:   cmp    DWORD PTR [rbp-0x8],0x9
   0x000056434b0d7718 <+104>:   jle    0x56434b0d76f4 <main+68>

22    }
23  
24    return EXIT_SUCCESS;
=> 0x000056434b0d771a <+106>:   mov    eax,0x0

25  }
   0x000056434b0d771f <+111>:   leave  
   0x000056434b0d7720 <+112>:   ret    
End of assembler dump.

Output of disassemble /s update:

Dump of assembler code for function update:
array.c:
27  int update (int *arr, int size) {
   0x000056434b0d7721 <+0>: push   rbp
   0x000056434b0d7722 <+1>: mov    rbp,rsp
   0x000056434b0d7725 <+4>: sub    rsp,0x20
   0x000056434b0d7729 <+8>: mov    QWORD PTR [rbp-0x18],rdi
   0x000056434b0d772d <+12>:    mov    DWORD PTR [rbp-0x1c],esi

28    for (int i = 0 ; i < size ; i++) {
   0x000056434b0d7730 <+15>:    mov    DWORD PTR [rbp-0x4],0x0
   0x000056434b0d7737 <+22>:    jmp    0x56434b0d7793 <update+114>

29      arr[i] += i;
   0x000056434b0d7739 <+24>:    mov    eax,DWORD PTR [rbp-0x4]
   0x000056434b0d773c <+27>:    cdqe   
   0x000056434b0d773e <+29>:    lea    rdx,[rax*4+0x0]
   0x000056434b0d7746 <+37>:    mov    rax,QWORD PTR [rbp-0x18]
   0x000056434b0d774a <+41>:    add    rax,rdx
   0x000056434b0d774d <+44>:    mov    edx,DWORD PTR [rbp-0x4]
   0x000056434b0d7750 <+47>:    movsxd rdx,edx
   0x000056434b0d7753 <+50>:    lea    rcx,[rdx*4+0x0]
   0x000056434b0d775b <+58>:    mov    rdx,QWORD PTR [rbp-0x18]
   0x000056434b0d775f <+62>:    add    rdx,rcx
   0x000056434b0d7762 <+65>:    mov    ecx,DWORD PTR [rdx]
   0x000056434b0d7764 <+67>:    mov    edx,DWORD PTR [rbp-0x4]
   0x000056434b0d7767 <+70>:    add    edx,ecx
   0x000056434b0d7769 <+72>:    mov    DWORD PTR [rax],edx

30      update(arr+i, size-1);
   0x000056434b0d776b <+74>:    mov    eax,DWORD PTR [rbp-0x1c]
   0x000056434b0d776e <+77>:    lea    edx,[rax-0x1]
   0x000056434b0d7771 <+80>:    mov    eax,DWORD PTR [rbp-0x4]
   0x000056434b0d7774 <+83>:    cdqe   
   0x000056434b0d7776 <+85>:    lea    rcx,[rax*4+0x0]
   0x000056434b0d777e <+93>:    mov    rax,QWORD PTR [rbp-0x18]
   0x000056434b0d7782 <+97>:    add    rax,rcx
   0x000056434b0d7785 <+100>:   mov    esi,edx
   0x000056434b0d7787 <+102>:   mov    rdi,rax
   0x000056434b0d778a <+105>:   call   0x56434b0d7721 <update>

28    for (int i = 0 ; i < size ; i++) {
   0x000056434b0d778f <+110>:   add    DWORD PTR [rbp-0x4],0x1
   0x000056434b0d7793 <+114>:   mov    eax,DWORD PTR [rbp-0x4]
   0x000056434b0d7796 <+117>:   cmp    eax,DWORD PTR [rbp-0x1c]
   0x000056434b0d7799 <+120>:   jl     0x56434b0d7739 <update+24>

31    }
32    return 1;
   0x000056434b0d779b <+122>:   mov    eax,0x1

33  }
   0x000056434b0d77a0 <+127>:   leave  
   0x000056434b0d77a1 <+128>:   ret    
End of assembler dump.

Contents of ~/.gdbinit

# Security
set auto-load safe-path /

# Misc
set disassembly-flavor intel
set disable-randomization off
set pagination off
set follow-fork-mode child

# History
set history filename ~/.gdbhistory
set history save
set history expansion

disp/10i $pc

handle SIGXCPU SIG33 SIG35 SIGPWR nostop noprint

set tui enable
2

There are 2 best solutions below

3
On BEST ANSWER

It is likely that this line in your .gdbinit is the source of your troubles:

set disable-randomization off

By default, GDB disables address space layout randomization (ASLR). That means that the binary under GDB starts at exactly the same address, with exactly the same stack pointer every time it runs. This is on by default precisely so you can set a watchpoints and breakpoints on a given address, and have it fire on each run.

By setting disable-randomization off, you are asking GDB to run your binary the same way it would run outside of GDB, i.e. with ASLR enabled. Now the location of stack variables (and globals for a PIE binary that you have) will change from run to run, and setting a watchpoint on a given stack address will only work randomly and rarely.

You can confirm that that is the cause by issuing info frame and run several times. You'll observe that the location where registers are saved changes between runs.

TL;DR: Don't put settings you don't completely understand into your .gdbinit.

5
On

the problem with the code is the clobbering of the stack.

The comment by @Mark Plotnick clarifies the problem and includes a suggestion on how to fix it.