Skip to content

Commit 8768f94

Browse files
pawel-kupczaklgaver2
authored andcommitted
gdb: correct endbr64 instruction handling in amd64_analyze_prologue
Compilers can put a sequence aligning the stack at the entry of a function. However with -fcf-protection enabled, "endbr64" is generated before. Current implementation of amd64 prologue analyzer first checks for stack alignment and then for "endbr64", which is not correct. This behavior was introduced with patch "gdb: handle endbr64 instruction in amd64_analyze_prologue". In case both are generated, prologue will not be skipped. This patch swaps the order so that "endbr64" is checked first and adds a regression test. i386-tdep implementation also already had those checked in the correct order, that is stack alignment is after endbr64. Given such source compiled with gcc 11.4.0 via: gcc -O0 main.c -o main ``` #include <alloca.h> void foo (int id) { volatile __attribute__ ((__aligned__ (64))) int a; volatile char *p = (char *) alloca (id * 12); p[2] = 'b'; } int main (int argc, char **argv) { foo (argc + 1); return 1; } ``` we get such function entry for foo (generated with objdump -d): ``` 0000000000001149 <foo>: 1149: f3 0f 1e fa endbr64 114d: 4c 8d 54 24 08 lea 0x8(%rsp),%r10 1152: 48 83 e4 c0 and $0xffffffffffffffc0,%rsp 1156: 41 ff 72 f8 push -0x8(%r10) 115a: 55 push %rbp 115b: 48 89 e5 mov %rsp,%rbp 115e: 41 52 push %r10 1160: 48 81 ec a8 00 00 00 sub $0xa8,%rsp 1167: 89 7d 8c mov %edi,-0x74(%rbp) ... ``` The 3 instructions following endbr64 align the stack. If we were to set a breakpoint on foo, gdb would set it at function's entry: ``` (gdb) b foo Breakpoint 1 at 0x1149 (gdb) r ... Breakpoint 1, 0x0000555555555149 in foo () (gdb) disassemble Dump of assembler code for function foo: => 0x0000555555555149 <+0>: endbr64 0x000055555555514d <+4>: lea 0x8(%rsp),%r10 0x0000555555555152 <+9>: and $0xffffffffffffffc0,%rsp 0x0000555555555156 <+13>: push -0x8(%r10) 0x000055555555515a <+17>: push %rbp 0x000055555555515b <+18>: mov %rsp,%rbp 0x000055555555515e <+21>: push %r10 0x0000555555555160 <+23>: sub $0xa8,%rsp 0x0000555555555167 <+30>: mov %edi,-0x74(%rbp) ... ``` With this patch fixing the order of checked instructions, gdb can properly analyze the prologue: ``` (gdb) b foo Breakpoint 1 at 0x115e (gdb) r ... Breakpoint 1, 0x000055555555515e in foo () (gdb) disassemble Dump of assembler code for function foo: 0x0000555555555149 <+0>: endbr64 0x000055555555514d <+4>: lea 0x8(%rsp),%r10 0x0000555555555152 <+9>: and $0xffffffffffffffc0,%rsp 0x0000555555555156 <+13>: push -0x8(%r10) 0x000055555555515a <+17>: push %rbp 0x000055555555515b <+18>: mov %rsp,%rbp => 0x000055555555515e <+21>: push %r10 0x0000555555555160 <+23>: sub $0xa8,%rsp 0x0000555555555167 <+30>: mov %edi,-0x74(%rbp) ... ``` Approved-By: Andrew Burgess <[email protected]>
1 parent ac7bc96 commit 8768f94

File tree

3 files changed

+79
-27
lines changed

3 files changed

+79
-27
lines changed

gdb/amd64-tdep.c

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2581,15 +2581,16 @@ amd64_analyze_prologue (gdbarch *gdbarch, CORE_ADDR pc, CORE_ADDR current_pc,
25812581
if (current_pc <= pc)
25822582
return current_pc;
25832583

2584+
/* If generated, 'endbr64' will be placed before stack alignment too. */
2585+
pc = amd64_skip_endbr (gdbarch, pc);
2586+
if (current_pc <= pc)
2587+
return current_pc;
2588+
25842589
if (gdbarch_ptr_bit (gdbarch) == 32)
25852590
pc = amd64_x32_analyze_stack_align (pc, current_pc, cache);
25862591
else
25872592
pc = amd64_analyze_stack_align (pc, current_pc, cache);
25882593

2589-
pc = amd64_skip_endbr (gdbarch, pc);
2590-
if (current_pc <= pc)
2591-
return current_pc;
2592-
25932594
return amd64_analyze_frame_setup (gdbarch, pc, current_pc, cache);
25942595
}
25952596

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/* This testcase is part of GDB, the GNU debugger.
2+
3+
Copyright 2025 Free Software Foundation, Inc.
4+
5+
This program is free software; you can redistribute it and/or modify
6+
it under the terms of the GNU General Public License as published by
7+
the Free Software Foundation; either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU General Public License
16+
along with this program. If not, see <http://www.gnu.org/licenses/>. */
17+
18+
#include <alloca.h>
19+
20+
int
21+
main (int argc, char **argv)
22+
{
23+
volatile __attribute__ ((__aligned__ (64))) int a;
24+
volatile char *p = (char *) alloca (argc * 12);
25+
p[2] = 'b';
26+
return 1;
27+
}

gdb/testsuite/gdb.arch/i386-prologue-skip-cf-protection.exp

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,41 +19,65 @@
1919
# This option places an `endbr32`/`endbr64` instruction at the start of
2020
# all functions, which can interfere with prologue analysis.
2121

22-
standard_testfile .c
23-
set binfile ${binfile}
22+
standard_testfile .c -stackalign.c
2423

2524
require {is_any_target x86_64-*-* i?86-*-*}
26-
2725
require supports_fcf_protection
2826

29-
set opts {debug additional_flags=-fcf-protection=full}
27+
# Tests if breakpoint set on main is placed past main's entry.
28+
proc test_run {} {
29+
# Get start address of function main.
30+
set main_addr [get_integer_valueof &main -1]
31+
gdb_assert {$main_addr != -1}
3032

31-
if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable $opts] != "" } {
32-
untested "failed to compile"
33-
return
34-
}
33+
set bp_addr -1
3534

36-
clean_restart ${binfile}
35+
# Put breakpoint on main, get the address where the breakpoint was installed.
36+
gdb_test_multiple "break -q main" "break on main, get address" {
37+
-re -wrap "Breakpoint $::decimal at ($::hex).*" {
38+
set bp_addr $expect_out(1,string)
3739

38-
# Get start address of function main.
39-
set main_addr [get_integer_valueof &main -1]
40-
gdb_assert {$main_addr != -1}
40+
# Convert to decimal.
41+
set bp_addr [expr $bp_addr]
4142

42-
set bp_addr -1
43+
pass $gdb_test_name
44+
}
45+
}
4346

44-
# Put breakpoint on main, get the address where the breakpoint was installed.
45-
gdb_test_multiple "break -q main" "break on main, get address" {
46-
-re -wrap "Breakpoint $decimal at ($hex).*" {
47-
set bp_addr $expect_out(1,string)
47+
# Make sure some prologue was skipped.
48+
gdb_assert {$bp_addr != -1 && $bp_addr > $main_addr} \
49+
"breakpoint placed past main's entry"
50+
}
4851

49-
# Convert to decimal.
50-
set bp_addr [expr $bp_addr]
52+
with_test_prefix "skip-cf-protection" {
53+
set opts {debug additional_flags=-fcf-protection=full}
5154

52-
pass $gdb_test_name
55+
if { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable \
56+
$opts] != "" } {
57+
untested "failed to compile"
58+
return
5359
}
60+
61+
clean_restart ${binfile}
62+
63+
test_run
5464
}
5565

56-
if { $bp_addr != -1 } {
57-
# Make sure some prologue was skipped.
58-
gdb_assert {$bp_addr > $main_addr}
66+
# Now, make sure that the prologue analysis does not end up at function's entry
67+
# when stack alignment sequence is generated right after 'endbr64'/'endbr32'.
68+
# That could happen if GDB handled those incorrectly - there was a bug that
69+
# checked for those two in incorrect order, which caused such issue.
70+
with_test_prefix "skip-cf-protection-stackalign" {
71+
# gcc is easier to make it produce the sequence of interest.
72+
if { ![is_c_compiler_gcc] } {
73+
unsupported "stackalign test part requires gcc compiler"
74+
return
75+
}
76+
77+
if { [prepare_for_testing "failed to prepare" "${testfile}-stackalign" \
78+
$srcfile2 [list optimize=-O0 additional_flags=-fcf-protection=full]] } {
79+
return
80+
}
81+
82+
test_run
5983
}

0 commit comments

Comments
 (0)