]> git.ipfire.org Git - thirdparty/binutils-gdb.git/commitdiff
gdb, testsuite: test changing FS and GS segment selectors and bases
authorMarkus Metzger <markus.t.metzger@intel.com>
Thu, 21 Nov 2019 11:01:13 +0000 (12:01 +0100)
committerMarkus Metzger <markus.t.metzger@intel.com>
Tue, 17 Dec 2019 15:04:40 +0000 (16:04 +0100)
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

gdb/testsuite/gdb.arch/x86-fsgs.c [new file with mode: 0644]
gdb/testsuite/gdb.arch/x86-fsgs.exp [new file with mode: 0644]
gdb/testsuite/gdb.base/gcore.exp
gdb/testsuite/lib/gdb.exp

diff --git a/gdb/testsuite/gdb.arch/x86-fsgs.c b/gdb/testsuite/gdb.arch/x86-fsgs.c
new file mode 100644 (file)
index 0000000..654e687
--- /dev/null
@@ -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 (file)
index 0000000..5c4282d
--- /dev/null
@@ -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
+}
index 5027005c49149f1273ce81c9b289e8fe23f56fdb..a9f9aa54f1bd08451a1b563bb3a1d7935685ab14 100644 (file)
@@ -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} {
index b6c5e009926ebb2a59e71933222ae7ea6c995813..a52e93b7b86039faaf0c7a8a6a459bd57fbfacd0 100644 (file)
@@ -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