| # Copyright 2026 Free Software Foundation, Inc. |
| |
| # This program is free software; you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation; either version 3 of the License, or |
| # (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| |
| # Test that GDB can unwind using a .debug_frame section generated by |
| # the DWARF assembler. |
| # |
| # This test is amd64-specific, but could be ported to other |
| # architectures if needed. |
| |
| load_lib dwarf.exp |
| |
| require dwarf2_support is_x86_64_m64_target |
| |
| standard_testfile .S -dw.S |
| |
| # AMD64 DWARF register numbers. |
| set rax 0 |
| set rbp 6 |
| set rsp 7 |
| set r12 12 |
| set r13 13 |
| set r14 14 |
| set rip 16 |
| |
| foreach_with_prefix is_64 { false true } { |
| set asm_file [standard_output_file ${testfile}-${is_64}-dw.S] |
| |
| Dwarf::assemble $asm_file { |
| frame { |
| declare_labels cie_label |
| |
| cie_label: CIE { |
| return_address_register $::rip |
| data_alignment_factor -8 |
| is_64 $::is_64 |
| } { |
| DW_CFA_def_cfa $::rsp 8 |
| DW_CFA_offset $::rip 1 |
| } |
| |
| # FDE for main |
| FDE $cie_label main main_len { |
| is_64 $::is_64 |
| } { |
| DW_CFA_set_loc main_after_push_rbp |
| DW_CFA_def_cfa_offset 16 |
| DW_CFA_offset $::rbp 2 |
| DW_CFA_set_loc main_after_set_rbp |
| DW_CFA_def_cfa_register $::rbp |
| } |
| |
| # FDE for caller |
| FDE $cie_label caller caller_len { |
| is_64 $::is_64 |
| } { |
| DW_CFA_set_loc caller_after_push_rbp |
| DW_CFA_def_cfa_offset 16 |
| DW_CFA_offset $::rbp 2 |
| DW_CFA_set_loc caller_after_set_rbp |
| DW_CFA_def_cfa_register $::rbp |
| } |
| |
| # FDE for callee |
| FDE $cie_label callee callee_len { |
| is_64 $::is_64 |
| } { |
| DW_CFA_set_loc callee_after_push_rbp |
| DW_CFA_def_cfa_offset 16 |
| DW_CFA_offset $::rbp 2 |
| DW_CFA_set_loc callee_after_set_rbp |
| DW_CFA_def_cfa_register $::rbp |
| |
| DW_CFA_set_loc callee_body |
| DW_CFA_offset $::r12 3 |
| DW_CFA_register $::r13 $::rax |
| |
| # r14's value is computed by an arbitrary expression. |
| DW_CFA_val_expression $::r14 { |
| DW_OP_constu 0x99aabbcc |
| } |
| } |
| } |
| } |
| |
| if { [prepare_for_testing "failed to prepare" ${testfile}-${is_64} \ |
| [list $srcfile $asm_file] {nodebug}] } { |
| continue |
| } |
| |
| # Stop in caller before the call, to capture rbp. |
| if { ![runto caller_call_callee] } { |
| continue |
| } |
| |
| set caller_rbp [get_hexadecimal_valueof "\$rbp" "UNKNOWN"] |
| |
| # Stop inside callee. |
| gdb_breakpoint callee_body |
| gdb_continue_to_breakpoint "callee_body" |
| |
| # Verify backtrace shows the full call chain. |
| gdb_test "bt" "#0.*callee.*\r\n#1.*caller.*\r\n#2.*main.*" |
| |
| # Select caller's frame and check saved registers. |
| gdb_test "frame 1" "#1.*caller.*" |
| |
| # r12 was saved on the stack by callee. |
| gdb_test "p/x \$r12" "= 0x11223344" |
| |
| # r13 was saved in rax by callee. |
| gdb_test "p/x \$r13" "= 0x55667788" |
| |
| # r14's value is computed by a DWARF expression. |
| gdb_test "p/x \$r14" "= 0x99aabbcc" |
| |
| # rbp should match what caller had. |
| gdb_test "p/x \$rbp" "= ${caller_rbp}" |
| } |