Bug 14901 - Breakpoints can end up in the middle of an instruction, causing corruption
Summary: Breakpoints can end up in the middle of an instruction, causing corruption
Status: NEW
Alias: None
Product: gdb
Classification: Unclassified
Component: breakpoints (show other bugs)
Version: 7.5
: P2 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2012-11-30 21:24 UTC by Dima Kogan
Modified: 2024-01-22 17:17 UTC (History)
1 user (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Dima Kogan 2012-11-30 21:24:49 UTC
I'm using gdb 7.5.1, on a more-or-less vanilla amd64 box. I'm seeing that it is
possible to

1. set a breakpoint somewhere
2. load a new version of the executable being debugged
3. observe the breakpoint 0xCC byte end up in the middle of an instruction,
   causing havoc

The 0xCC byte corrupts the instruction it ends up in. Furthermore, unless one
knows that a breakpoint is involved, the observed behavior is extremely
mysterious. The output of "disass /r" doesn't display the 0xCC, so the user
thinks they're stepping through one instruction, while gdb is executing a
completely different one.

To reproduce, I have the following source I named tst.c:

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

static void f(void)
{
#ifdef PAD
  char c = 3;
#endif
  int  a = 5;

  printf("0x%x\n", a);
}

int main(void)
{
  f();
  return 0;
}
==============================

I compile this with and without defining PAD:

gcc-4.7 -DPAD -g -o tst_pad tst.c; gcc-4.7 -g -o tst tst.c

I then load "tst_pad", set the breakpoint on the "a=5" line, then load "tst".
The old breakpoint stays at the same exact address, corrupting the a=5
statement.

This can be shown by running "gdb -x gdb.cmd" with gdb.cmd as

==============================
file /tmp/tst_pad
b main
r
s
n
b
info b
c
file /tmp/tst
r
info b
disass /r f
c
q
==============================

The output I see from this is

==============================
GNU gdb (GDB) 7.5.1
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-unknown-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Breakpoint 1 at 0x400539: file tst.c, line 16.

Breakpoint 1, main () at tst.c:16
16	  f();
f () at tst.c:7
7	  char c = 3;
9	  int  a = 5;
Breakpoint 2 at 0x400518: file tst.c, line 9.
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400539 in main at tst.c:16
	breakpoint already hit 1 time
2       breakpoint     keep y   0x0000000000400518 in f at tst.c:9
0x5
[Inferior 1 (process 17031) exited normally]

Breakpoint 1, main () at tst.c:16
16	  f();
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x0000000000400535 in main at tst.c:16
	breakpoint already hit 1 time
2       breakpoint     keep y   0x0000000000400518 in f at tst.c:9
Dump of assembler code for function f:
   0x000000000040050c <+0>:	55	push   rbp
   0x000000000040050d <+1>:	48 89 e5	mov    rbp,rsp
   0x0000000000400510 <+4>:	48 83 ec 10	sub    rsp,0x10
   0x0000000000400514 <+8>:	c7 45 fc 05 00 00 00	mov    DWORD PTR [rbp-0x4],0x5
   0x000000000040051b <+15>:	8b 45 fc	mov    eax,DWORD PTR [rbp-0x4]
   0x000000000040051e <+18>:	89 c6	mov    esi,eax
   0x0000000000400520 <+20>:	bf fc 05 40 00	mov    edi,0x4005fc
   0x0000000000400525 <+25>:	b8 00 00 00 00	mov    eax,0x0
   0x000000000040052a <+30>:	e8 b1 fe ff ff	call   0x4003e0 <printf@plt>
   0x000000000040052f <+35>:	c9	leave  
   0x0000000000400530 <+36>:	c3	ret    
End of assembler dump.
0xcc05
[Inferior 1 (process 17039) exited normally]
==============================

Note that the breakpoint is at 0x400518 in BOTH executables, and the "a"
variable ends up being 0xCC05 instead of 5. This is extremely perplexing to the
unsuspecting user, since the output of "disass /r" doesn't show the 0xCC. So the
user steps through the instruction "mov DWORD PTR [rbp-0x4],0x5", but observes
something completely different happening!

gdb appears to have some safeguards against this. In the above script the
breakpoint was set with a simple "b" command when gdb was stopped at the
particular location. If it was set with "b 9" instead (breaking on line 9, which
should be the same) then when loading the new executable, the breakpoint is
moved to 0x400514, which is at the start of an instruction, and produces more
predictable behavior.

I think it would be great if the breakpoint reinterpretation was either fixed,
or documented more clearly to indicate when the breakpoint should be moved and
when not. "help b" doesn't make clear that "b" and "b 9" produce very different
behaviors. Also, it would be great if "disass /r" had some sort of indication
that a particular instruction has been overwritten by a breakpoint; this would
make the observed failure much less baffling.

Thanks
Comment 1 Hannes Domani 2024-01-22 17:17:56 UTC
This is a similar issue to PR11273, since 'b' without arguments is used here, which sets the breakpoint on the current address, no matter what.