diff options
author | Markus Metzger <markus.t.metzger@intel.com> | 2019-11-21 12:01:13 +0100 |
---|---|---|
committer | Markus Metzger <markus.t.metzger@intel.com> | 2019-12-17 16:04:40 +0100 |
commit | 03bad9e43d43bc7382e9487142ee4fa988bc5576 (patch) | |
tree | 4ff3483ee0f96fb3e23091e7e92f678c35b3808f | |
parent | 3e7fa952aa12f56bafe02e0dab47eb9c690e9f47 (diff) | |
download | fsf-binutils-gdb-03bad9e43d43bc7382e9487142ee4fa988bc5576.zip fsf-binutils-gdb-03bad9e43d43bc7382e9487142ee4fa988bc5576.tar.gz fsf-binutils-gdb-03bad9e43d43bc7382e9487142ee4fa988bc5576.tar.bz2 |
gdb, testsuite: test changing FS and GS segment selectors and bases
Signed-off-by: Markus Metzger <markus.t.metzger@intel.com>
testsuite/
* lib/gdb.exp (skip_fsgsbase_tests, skip_arch_set_fs_tests)
(skip_arch_set_gs_tests): New.
* gdb.arch/x86-fsgs.c: New.
* gdb.arch/x86-fsgs.exp: New.
Change-Id: I7da32d4b57fd8b34153e7385263ec82318d32a98
-rw-r--r-- | gdb/testsuite/gdb.arch/x86-fsgs.c | 262 | ||||
-rw-r--r-- | gdb/testsuite/gdb.arch/x86-fsgs.exp | 191 | ||||
-rw-r--r-- | gdb/testsuite/gdb.base/gcore.exp | 16 | ||||
-rw-r--r-- | gdb/testsuite/lib/gdb.exp | 269 |
4 files changed, 738 insertions, 0 deletions
diff --git a/gdb/testsuite/gdb.arch/x86-fsgs.c b/gdb/testsuite/gdb.arch/x86-fsgs.c new file mode 100644 index 0000000..654e687 --- /dev/null +++ b/gdb/testsuite/gdb.arch/x86-fsgs.c @@ -0,0 +1,262 @@ +/* This testcase is part of GDB, the GNU debugger. + + Copyright 2019 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/>. +*/ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> +#include <asm/ldt.h> +#include <sys/types.h> +#include <sys/syscall.h> +#include <sys/mman.h> + +#if HAVE_ARCH_SET_FS || HAVE_ARCH_SET_GS +# include <asm/prctl.h> +# include <sys/prctl.h> +#endif /* HAVE_ARCH_SET_FS || HAVE_ARCH_SET_GS */ + +struct segs { + int initial; + int other; + int twentythree; +}; +static struct segs *segs; + +static unsigned int +setup_ldt (unsigned int entry, void *base, size_t size) +{ + struct user_desc ud; + int errcode; + + memset (&ud, 0, sizeof (ud)); + ud.entry_number = entry; + ud.base_addr = (unsigned int) (unsigned long) base; + ud.limit = (unsigned int) size; + + /* The base is 32-bit. */ + if ((unsigned long) ud.base_addr != (unsigned long) base) + return 0u; + + errcode = syscall(SYS_modify_ldt, 1, &ud, sizeof(ud)); + if (errcode != 0) + return 0u; + + return (ud.entry_number << 3) | 7; +} + +int +read_fs (void) +{ + int value; + + __asm__ volatile ("mov %%fs:0x0, %0" : "=rm"(value) :: "memory"); + + return value; +} + +int +read_gs (void) +{ + int value; + + __asm__ volatile ("mov %%gs:0x0, %0" : "=rm"(value) :: "memory"); + + return value; +} + +int +switch_fs_read (unsigned int fs) +{ + __asm__ volatile ("mov %0, %%fs" :: "rm"(fs) : "memory"); + + return read_fs (); +} + +void +test_fs (unsigned int selector) +{ + int value; + + value = switch_fs_read (selector); /* l.1 */ + value = read_fs (); /* l.2 */ + value = read_fs (); /* l.3 */ +} /* l.4 */ + +int +switch_gs_read (unsigned int gs) +{ + __asm__ volatile ("mov %0, %%gs" :: "rm"(gs) : "memory"); + + return read_gs (); +} + +void +test_gs (unsigned int selector) +{ + int value; + + value = switch_gs_read (selector); /* l.1 */ + value = read_gs (); /* l.2 */ + value = read_gs (); /* l.3 */ +} /* l.4 */ + +#if HAVE_WRFSGSBASE + +int +wrfsbase_read (void *fsbase) +{ + __asm__ volatile ("wrfsbase %0" :: "r"(fsbase) : "memory"); + + return read_fs (); +} + +static void +test_wrfsbase (void *base) +{ + int value; + + value = wrfsbase_read (base); /* l.1 */ + value = read_fs (); /* l.2 */ + value = read_fs (); /* l.3 */ +} /* l.4 */ + +int +wrgsbase_read (void *gsbase) +{ + __asm__ volatile ("wrgsbase %0" :: "r"(gsbase) : "memory"); + + return read_gs (); +} + +static void +test_wrgsbase (void *base) +{ + int value; + + value = wrgsbase_read (base); /* l.1 */ + value = read_gs (); /* l.2 */ + value = read_gs (); /* l.3 */ +} /* l.4 */ + +#endif /* HAVE_WRFSGSBASE */ + +#if HAVE_ARCH_SET_FS + +int +arch_set_fs_read (void *fsbase) +{ + int errcode; + + errcode = syscall (SYS_arch_prctl, ARCH_SET_FS, fsbase); + if (errcode != 0) + return 0; + + return read_fs (); +} + +static void +test_arch_set_fs (void *base) +{ + int value; + + value = arch_set_fs_read (base); /* l.1 */ + value = read_fs (); /* l.2 */ + value = read_fs (); /* l.3 */ +} /* l.4 */ + +#endif /* HAVE_ARCH_SET_FS */ + +#if HAVE_ARCH_SET_GS + +int +arch_set_gs_read (void *gsbase) +{ + int errcode; + + errcode = syscall (SYS_arch_prctl, ARCH_SET_GS, gsbase); + if (errcode != 0) + return 0; + + return read_gs (); +} + +static void +test_arch_set_gs (void *base) +{ + int value; + + value = arch_set_gs_read (base); /* l.1 */ + value = read_gs (); /* l.2 */ + value = read_gs (); /* l.3 */ +} /* l.4 */ + +#endif /* HAVE_ARCH_SET_GS */ + +int +main (void) +{ + unsigned int selector; + + segs = mmap (NULL, sizeof (*segs), PROT_READ | PROT_WRITE, +#ifdef __x86_64__ + MAP_32BIT | +#endif + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (segs == MAP_FAILED) + { + perror ("failed to mmap 32-bit memory"); + abort (); + } + + segs->initial = 42; + segs->other = -42; + segs->twentythree = 23; + + selector = setup_ldt (0xb7 >> 3, &segs->other, sizeof (segs->other)); + if (selector == 0u) + { + perror ("failed to setup LDT[0xb7>>3] = &segs->other"); + abort (); + } + + selector = setup_ldt (0xa7 >> 3, &segs->initial, sizeof (segs->initial)); + if (selector == 0u) + { + perror ("failed to setup LDT[0xa7>>3] = &segs->initial"); + abort (); + } + + test_fs (selector); + test_gs (selector); + +#if HAVE_ARCH_SET_FS + test_arch_set_fs (&segs->initial); +#endif /* HAVE_ARCH_SET_FS */ + +#if HAVE_ARCH_SET_GS + test_arch_set_gs (&segs->initial); +#endif /* HAVE_ARCH_SET_GS */ + +#if HAVE_WRFSGSBASE + test_wrfsbase (&segs->initial); + test_wrgsbase (&segs->initial); +#endif /* HAVE_WRFSGSBASE */ + + return 0; +} diff --git a/gdb/testsuite/gdb.arch/x86-fsgs.exp b/gdb/testsuite/gdb.arch/x86-fsgs.exp new file mode 100644 index 0000000..5c4282d --- /dev/null +++ b/gdb/testsuite/gdb.arch/x86-fsgs.exp @@ -0,0 +1,191 @@ +# Copyright (C) 2019 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 + +if { ![istarget x86_64-*-* ] && ![istarget i?86-*-* ] } { + verbose "Skipping ${testfile}." + return +} + +if { [skip_modify_ldt_tests] } { + untested "cannot setup LDT" + return +} + +set skip_fsgsbase [skip_fsgsbase_tests] +set skip_arch_set_fs [skip_arch_set_fs_tests] +set skip_arch_set_gs [skip_arch_set_gs_tests] + +set flags { debug } +set flags [concat $flags additional_flags=-DHAVE_WRFSGSBASE=!$skip_fsgsbase] +set flags [concat $flags additional_flags=-DHAVE_ARCH_SET_FS=!$skip_arch_set_fs] +set flags [concat $flags additional_flags=-DHAVE_ARCH_SET_GS=!$skip_arch_set_gs] + +if { [prepare_for_testing "failed to prepare" ${testfile} ${srcfile} ${flags}] } { + return -1 +} + +if { ![runto_main] } { + untested "failed to run to main" + return -1 +} + +proc test_switch { seg } { + global skip_fsgsbase skip_arch_set_fs skip_arch_set_gs + + # The inferior provides a new segment selector + gdb_test "p switch_${seg}_read (0xa7)" "= 42" + + # The inferior provides the segment base via WRFS/GSBASE. + if { $skip_fsgsbase } { + untested "FSGSBASE" + } else { + gdb_test "p wr${seg}base_read (&segs->twentythree)" "= 23" + } + + # The inferior provides the segment base via arch_prctl (). + if { (($seg == "fs" && $skip_arch_set_fs) || + ($seg == "gs" && $skip_arch_set_gs)) } { + untested "arch_prctl(ARCH_SET_FS/GS)" + } else { + gdb_test "p arch_set_${seg}_read (&segs->twentythree)" "= 23" + } +} + +proc test { seg } { + global hex gdb_prompt + + # Check that the target overrides any garbage we put into FS/GS and + # FS/GSBASE. + gdb_test "p/x \$${seg} = 0xb7" "= 0xb7" + # On 32-bit kernels, FS/GSBASE are not defined + if { ![istarget i?86-*-* ] } { + gdb_test "p/x \$${seg}_base = &segs->twentythree" "= $hex" + } + gdb_test "next" " l\.2 \\\*/" + + # Since we want to use the same function for different scenarios, we + # don't check the actual register values but we check the effect. + gdb_test "p value" "= 42" + + # Change the segment selector to point to the 'other' segment. + with_test_prefix $seg { + gdb_test "p/x \$${seg} = 0xb7" "= 0xb7" + gdb_test "p/x &segs->other" $hex + + # Some kernels are nice enough to update the base for us. + set testname "${seg}_base" + if { ![istarget "x86_64-*-*"] } { + untested $testname + } else { + gdb_test_multiple "p/x \$${seg}_base" $testname { + -re "= 0x0\r\n$gdb_prompt $" { + pass $testname + } + -re "= $hex.*\r\n$gdb_prompt $" { + gdb_test "p (int *)\$${seg}_base == &segs->other" "= 1" \ + $testname + } + -re "$gdb_prompt $" { + fail $testname + } + } + } + + # Inferior calls will use the 'other' segment. + gdb_test "p read_${seg} ()" "= -42" + + # Inferior calls may switch the segment again. This will be + # undone when we restore the register state after returning from + # the inferior call. Test a few different scenarios. + test_switch $seg + + # When we resume, we will read from the 'other' segment as we did + # in the inferior call above. We do this check at the end to + # check that inferior calls are not able to override the state. + gdb_test "next" " l\.3 \\\*/" + gdb_test "p value" "= -42" + } + + # Only 64-bit kernels provide FS/GSBASE. + if { ![istarget "x86_64-*-*"] } { + untested ${seg}_base + } elseif { [is_ilp32_target] } { + # Even though a 64-bit kernel provides FS/GSBASE for the current + # FS/GS selector, it does not allow changing the base independent + # of the selector. + # + # Trying to do that while setting the selector to zero, as tests + # do below, results in an inferior crash while trying to use that + # zero selector. + untested ${seg}_base + } else { + # Change the segment base to point to 'twentythree'. + with_test_prefix ${seg}_base { + # We also need to set the selector to zero. And we need to do + # so before changing the base. + gdb_test "p/x \$${seg} = 0x0" "= 0x0" + gdb_test "p/x \$${seg}_base = &segs->twentythree" "= $hex" + + # Inferior calls will use the 'twentythree' segment. + gdb_test "p read_${seg} ()" "= 23" + + # Check inferior calls switching the segment again. + test_switch $seg + + # When we resume, we will read from the 'twentythreee' segment + # as we did in the inferior call above. We do this check at + # the end to check that inferior calls are not able to + # override the state. + gdb_test "next" " l\.4 \\\*/" + gdb_test "p value" "= 23" + } + } +} + +proc test_one { function seg } { + global decimal + + gdb_breakpoint $function + gdb_continue_to_breakpoint $function "$function .* l\.1 \\\*/" + + with_test_prefix $function { + test $seg + } +} + +test_one test_fs fs +test_one test_gs gs + +if { $skip_arch_set_fs } { + untested "arch_prctl(ARCH_SET_FS)" +} else { + test_one test_arch_set_fs fs +} + +if { $skip_arch_set_gs } { + untested "arch_prctl(ARCH_SET_GS)" +} else { + test_one test_arch_set_gs gs +} + +if { $skip_fsgsbase } { + untested "FSGSBASE" +} else { + test_one test_wrfsbase fs + test_one test_wrgsbase gs +} diff --git a/gdb/testsuite/gdb.base/gcore.exp b/gdb/testsuite/gdb.base/gcore.exp index 5027005..a9f9aa5 100644 --- a/gdb/testsuite/gdb.base/gcore.exp +++ b/gdb/testsuite/gdb.base/gcore.exp @@ -51,6 +51,22 @@ set pre_corefile_local_array \ set pre_corefile_extern_array \ [capture_command_output "print extern_array" "$print_prefix"] +# On IA, a 64-bit kernel provides fs_base and gs_base for 32-bit inferiors +# via ptrace, yet does not write them into the corefile. Neither does +# GDB. +# +# They will appear '<unavailable>' when reading the corefile back in. +# Adjust the above output to reflect this behaviour. +if { [istarget x86_64-*-* ] && [is_ilp32_target] } { + regsub -all "\(\[fg\]s_base *\)$hex *-?$decimal" $pre_corefile_regs \ + "\\1<unavailable>" pre_corefile_regs + verbose -log "adjusted general regs: $pre_corefile_regs" + + regsub -all "\(\[fg\]s_base *\)$hex *-?$decimal" $pre_corefile_allregs \ + "\\1<unavailable>" pre_corefile_allregs + verbose -log "adjusted all regs: $pre_corefile_allregs" +} + set corefile [standard_output_file gcore.test] set core_supported [gdb_gcore_cmd "$corefile" "save a corefile"] if {!$core_supported} { diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp index b6c5e00..a52e93b 100644 --- a/gdb/testsuite/lib/gdb.exp +++ b/gdb/testsuite/lib/gdb.exp @@ -6837,5 +6837,274 @@ gdb_caching_proc skip_ctf_tests { } executable "additional_flags=-gt"] } +# Run a test on the target to see if it supports WRFSBASE and WRGSBASE. +# Return 0 if so, 1 if it does not. + +gdb_caching_proc skip_fsgsbase_tests { + global srcdir subdir gdb_prompt inferior_exited_re + + set me "skip_fsgsbase_tests" + + # Compile a test program. + set src { + int seg; + int main() { + void *old; + + __asm__ volatile ("rdfsbase %0" : "=rm"(old)); + __asm__ volatile ("wrfsbase %0" :: "r"(&seg)); + __asm__ volatile ("wrfsbase %0" :: "r"(old)); + + __asm__ volatile ("rdgsbase %0" : "=rm"(old)); + __asm__ volatile ("wrgsbase %0" :: "r"(&seg)); + __asm__ volatile ("wrgsbase %0" :: "r"(old)); + + return 0; + } + } + if {![gdb_simple_compile $me $src executable]} { + return 1 + } + + # No error message, compilation succeeded so now run it via gdb. + + gdb_exit + gdb_start + gdb_reinitialize_dir $srcdir/$subdir + gdb_load "$obj" + gdb_run_cmd + gdb_expect { + -re ".*Illegal instruction.*${gdb_prompt} $" { + verbose -log "$me: FSGSBASE support not detected." + set skip_fsgsbase_tests 1 + } + -re ".*$inferior_exited_re normally.*${gdb_prompt} $" { + verbose -log "$me: FSGSBASE support detected." + set skip_fsgsbase_tests 0 + } + default { + warning "\n$me: default case taken." + set skip_fsgsbase_tests 1 + } + } + gdb_exit + remote_file build delete $obj + + verbose "$me: returning $skip_fsgsbase_tests" 2 + return $skip_fsgsbase_tests +} + +# Run a test on the target to see if it supports arch_prctl(ARCH_SET_FS). +# Return 0 if so, 1 if it does not. + +gdb_caching_proc skip_arch_set_fs_tests { + global srcdir subdir gdb_prompt inferior_exited_re + + set me "skip_arch_set_fs_tests" + + # The system call is not available to 32-bit. + if { [is_ilp32_target] } { + return 1 + } + + # Compile a test program. + set src { + #include <stdlib.h> + #include <assert.h> + #include <unistd.h> + #include <sys/syscall.h> + #include <asm/prctl.h> + #include <sys/prctl.h> + + int seg; + int main() { + unsigned long old; + int errcode; + + errcode = syscall (SYS_arch_prctl, ARCH_GET_FS, &old); + assert (errcode == 0); + + errcode = syscall (SYS_arch_prctl, ARCH_SET_FS, &seg); + assert (errcode == 0); + + errcode = syscall (SYS_arch_prctl, ARCH_SET_FS, old); + assert (errcode == 0); + + return 0; + } + } + if {![gdb_simple_compile $me $src executable]} { + return 1 + } + + # No error message, compilation succeeded so now run it via gdb. + + gdb_exit + gdb_start + gdb_reinitialize_dir $srcdir/$subdir + gdb_load "$obj" + gdb_run_cmd + gdb_expect { + -re ".*Assertion `errcode == 0' failed.*${gdb_prompt} $" { + verbose -log "$me: ARCH_SET_FS support not detected." + set skip_arch_set_fs_tests 1 + } + -re ".*$inferior_exited_re normally.*${gdb_prompt} $" { + verbose -log "$me: ARCH_SET_FS support detected." + set skip_arch_set_fs_tests 0 + } + default { + warning "\n$me: default case taken." + set skip_arch_set_fs_tests 1 + } + } + gdb_exit + remote_file build delete $obj + + verbose "$me: returning $skip_arch_set_fs_tests" 2 + return $skip_arch_set_fs_tests +} + +# Run a test on the target to see if it supports arch_prctl(ARCH_SET_GS). +# Return 0 if so, 1 if it does not. + +gdb_caching_proc skip_arch_set_gs_tests { + global srcdir subdir gdb_prompt inferior_exited_re + + set me "skip_arch_set_gs_tests" + + # The system call is not available to 32-bit. + if { [is_ilp32_target] } { + return 1 + } + + # Compile a test program. + set src { + #include <stdlib.h> + #include <assert.h> + #include <unistd.h> + #include <sys/syscall.h> + #include <asm/prctl.h> + #include <sys/prctl.h> + + int seg; + int main() { + unsigned long old; + int errcode; + + errcode = syscall (SYS_arch_prctl, ARCH_GET_GS, &old); + assert (errcode == 0); + + errcode = syscall (SYS_arch_prctl, ARCH_SET_GS, &seg); + assert (errcode == 0); + + errcode = syscall (SYS_arch_prctl, ARCH_SET_GS, old); + assert (errcode == 0); + + return 0; + } + } + if {![gdb_simple_compile $me $src executable]} { + return 1 + } + + # No error message, compilation succeeded so now run it via gdb. + + gdb_exit + gdb_start + gdb_reinitialize_dir $srcdir/$subdir + gdb_load "$obj" + gdb_run_cmd + gdb_expect { + -re ".*Assertion `errcode == 0' failed.*${gdb_prompt} $" { + verbose -log "$me: ARCH_SET_GS support not detected." + set skip_arch_set_gs_tests 1 + } + -re ".*$inferior_exited_re normally.*${gdb_prompt} $" { + verbose -log "$me: ARCH_SET_GS support detected." + set skip_arch_set_gs_tests 0 + } + default { + warning "\n$me: default case taken." + set skip_arch_set_gs_tests 1 + } + } + gdb_exit + remote_file build delete $obj + + verbose "$me: returning $skip_arch_set_gs_tests" 2 + return $skip_arch_set_gs_tests +} + +# Run a test on the target to see if it supports modify_ldt. +# Return 0 if so, 1 if it does not. + +gdb_caching_proc skip_modify_ldt_tests { + global srcdir subdir gdb_prompt inferior_exited_re + + set me "skip_modify_ldt_tests" + + # Compile a test program. + set src { + #include <stdlib.h> + #include <assert.h> + #include <unistd.h> + #include <sys/types.h> + #include <sys/syscall.h> + #include <asm/ldt.h> + + int seg; + int main() { + struct user_desc ud; + int errcode; + + memset (&ud, 0, sizeof (ud)); + ud.entry_number = 0xa7u >> 3; + ud.base_addr = (unsigned int) (unsigned long) &seg; + ud.limit = (unsigned int) sizeof (seg); + + errcode = syscall (SYS_modify_ldt, 1, &ud, sizeof(ud)); + assert (errcode == 0); + + return 0; + } + } + + if {![gdb_simple_compile $me $src executable]} { + return 1 + } + + # No error message, compilation succeeded so now run it via gdb. + + gdb_exit + gdb_start + gdb_reinitialize_dir $srcdir/$subdir + gdb_load "$obj" + gdb_run_cmd + gdb_expect { + -re ".*Assertion `errcode.*${gdb_prompt} $" { + verbose -log "$me: modify_ldt support not detected." + set skip_modify_ldt_tests 1 + } + -re ".*Assertion `ud.base_addr.*${gdb_prompt} $" { + verbose -log "$me: struct user_desc truncates base." + set skip_modify_ldt_tests 1 + } + -re ".*$inferior_exited_re normally.*${gdb_prompt} $" { + verbose -log "$me: modify_ldt working." + set skip_modify_ldt_tests 0 + } + default { + warning "\n$me: default case taken." + set skip_modify_ldt_tests 1 + } + } + gdb_exit + remote_file build delete $obj + + verbose "$me: returning $skip_modify_ldt_tests" 2 + return $skip_modify_ldt_tests +} + # Always load compatibility stuff. load_lib future.exp |