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
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
.