# Copyright 2012-2021 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/>.

standard_testfile jit-reader-host.c

if { (![istarget x86_64-*-*] && ![istarget i?86-*-*]) || ![is_lp64_target] } {
    return -1;
}

if {[skip_shlib_tests]} {
    return -1
}

if { ![isnative] } {
    return -1
}

if {[get_compiler_info]} {
    untested "could not get compiler info"
    return 1
}


# Increase this to see more detail.
set test_verbose 0

set jit_host_src $srcfile
set jit_host_bin $binfile

# We inject the complete path to jit-reader.h into the source file
# lest we end up (incorrectly) building against a system-installed
# version.
set jit_reader_header [standard_output_file "../../../../../gdb/jit-reader.h"]
set jit_reader_flag "-DJIT_READER_H=\"$jit_reader_header\""

if  { [gdb_compile "${srcdir}/${subdir}/${jit_host_src}" "${jit_host_bin}" \
       executable  [list debug additional_flags=$jit_reader_flag]] != "" } {
    untested "failed to compile"
    return -1
}

set jit_reader jit-reader
set jit_reader_src ${jit_reader}.c
set jit_reader_bin [standard_output_file ${jit_reader}.so]

if { [gdb_compile_shlib "${srcdir}/${subdir}/${jit_reader_src}" "${jit_reader_bin}" \
	  [list debug additional_flags=$jit_reader_flag]] != "" } {
    untested "failed to compile"
    return -1
}

# Test "info registers" in the current frame, expecting RSP's value to
# be SP.

proc info_registers_current_frame {sp} {
    global hex decimal

    set any "\[^\r\n\]*"

    set neg_decimal "-?$decimal"

    set expected \
	[multi_line \
	     "rax            $hex +$neg_decimal" \
	     "rbx            $hex +$neg_decimal" \
	     "rcx            $hex +$neg_decimal" \
	     "rdx            $hex +$neg_decimal" \
	     "rsi            $hex +$neg_decimal" \
	     "rdi            $hex +$neg_decimal" \
	     "rbp            $hex +$hex" \
	     "rsp            $sp +$sp" \
	     "r8             $hex +$neg_decimal" \
	     "r9             $hex +$neg_decimal" \
	     "r10            $hex +$neg_decimal" \
	     "r11            $hex +$neg_decimal" \
	     "r12            $hex +$neg_decimal" \
	     "r13            $hex +$neg_decimal" \
	     "r14            $hex +$neg_decimal" \
	     "r15            $hex +$neg_decimal" \
	     "rip            $hex +$hex$any" \
	     "eflags         $hex +\\\[$any\\\]" \
	     "cs             $hex +$neg_decimal" \
	     "ss             $hex +$neg_decimal" \
	     "ds             $hex +$neg_decimal" \
	     "es             $hex +$neg_decimal" \
	     "fs             $hex +$neg_decimal" \
	     "gs             $hex +$neg_decimal" \
	    ]

    # There may be more registers.
    append expected ".*"

    gdb_test "info registers" $expected
}

proc jit_reader_test {} {
    global jit_host_bin
    global jit_reader_bin
    global test_verbose
    global hex decimal

    set any "\[^\r\n\]*"

    clean_restart $jit_host_bin
    gdb_load_shlib $jit_reader_bin

    if {$test_verbose > 0} {
	gdb_test_no_output "set debug jit 1"
    }

    gdb_test_no_output "jit-reader-load ${jit_reader_bin}" "jit-reader-load"
    gdb_run_cmd
    gdb_test "" "Program received signal SIGTRAP, .*" "expect SIGTRAP"

    # Test the JIT reader unwinder.
    with_test_prefix "with jit-reader" {

	with_test_prefix "before mangling" {
	    gdb_test "bt" \
		[multi_line \
		     "#0 ${any} in jit_function_stack_mangle ${any}" \
		     "#1 ${any} in main ${any}" \
		    ] \
		"bt works"

	    set sp_before_mangling \
		[get_hexadecimal_valueof "\$sp" 0 "get sp"]

	    gdb_test "up" "#1  $any in main $any\r\n$any  function_stack_mangle $any" \
		"move up to caller"

	    set caller_sp \
		[get_hexadecimal_valueof "\$sp" 0 "get caller sp"]
	}

	# Step over the instruction that mangles the stack pointer.
	# While that confuses GDB's built-in unwinder, the JIT
	# reader's unwinder understands the mangling and should thus
	# be able to unwind at that location.
	with_test_prefix "after mangling" {
	    gdb_test "si" "in jit_function_stack_mangle .*" "step over stack mangling"

	    set sp_after_mangling \
		[get_hexadecimal_valueof "\$sp" 0 "get sp"]

	    gdb_assert {$sp_before_mangling != $sp_after_mangling} \
		"sp is mangled"

	    # Check that the jit unwinder manages to backtrace through
	    # the mangled stack pointer.
	    gdb_test "bt" \
		[multi_line \
		     "#0 ${any} in jit_function_stack_mangle ${any}" \
		     "#1 ${any} in main ${any}" \
		    ] \
		"bt works"

	    with_test_prefix "current frame" {
		info_registers_current_frame $sp_after_mangling

		gdb_test "info frame" \
		    "Stack level 0, frame at $sp_before_mangling.*in jit_function_stack_mangle.*"
	    }

	    with_test_prefix "caller frame" {
		gdb_test "up" "#1  $any in main $any\r\n$any  function_stack_mangle $any" \
		    "up to caller"

		# Since the JIT unwinder only provides RIP/RSP/RBP,
		# all other registers should show as "<not saved>".

		set expected \
		    [multi_line \
			 "rax            <not saved>" \
			 "rbx            <not saved>" \
			 "rcx            <not saved>" \
			 "rdx            <not saved>" \
			 "rsi            <not saved>" \
			 "rdi            <not saved>" \
			 "rbp            $hex +$hex" \
			 "rsp            $caller_sp +$caller_sp" \
			 "r8             <not saved>" \
			 "r9             <not saved>" \
			 "r10            <not saved>" \
			 "r11            <not saved>" \
			 "r12            <not saved>" \
			 "r13            <not saved>" \
			 "r14            <not saved>" \
			 "r15            <not saved>" \
			 "rip            $hex +$hex $any" \
			 "eflags         <not saved>" \
			 "cs             <not saved>" \
			 "ss             <not saved>" \
			 "ds             <not saved>" \
			 "es             <not saved>" \
			 "fs             <not saved>" \
			 "gs             <not saved>" \
			]

		# There may be more registers.
		append expected ".*"

		gdb_test "info registers" $expected

		# Make sure that "info frame" doesn't crash.
		gdb_test "info frame" "Stack level 1, .*in main.*"

		# ... and that neither does printing a pseudo
		# register.
		gdb_test "print /x \$ebp" " = $hex" "print pseudo register"

		# There's no way for the JIT reader API to support
		# modifyiable values.
		gdb_test "print \$rbp = -1" \
		    "Attempt to assign to an unmodifiable value\." \
		    "cannot assign to register"
	    }
	}
    }

    # Now unload the jit reader, and ensure that backtracing really
    # doesn't work without it.
    with_test_prefix "without jit-reader" {
	gdb_test_no_output "jit-reader-unload ${jit_reader_bin}" \
	    "jit-reader-unload"

	# Check that we're no longer using the JIT unwinder, and that
	# the built-in unwinder cannot backtrace through the mangled
	# stack pointer.
	gdb_test "bt" \
	    "Backtrace stopped: Cannot access memory at address $sp_after_mangling" \
	    "bt shows error"

	gdb_test "info frame" "Cannot access memory at address.*" \
	    "info frame shows error"
	info_registers_current_frame $sp_after_mangling
	gdb_test "up" "Initial frame selected; you cannot go up\\." \
	    "cannot go up"
    }

    with_test_prefix "with jit-reader again" {
	gdb_test_no_output "jit-reader-load ${jit_reader_bin}" "jit-reader-load"

	# Check that the jit unwinder manages to backtrace through
	# the mangled stack pointer.
	gdb_test "bt" \
	    [multi_line \
		 "#0 ${any} in jit_function_stack_mangle ${any}" \
		 "#1 ${any} in main ${any}" \
		]
    }
}

jit_reader_test