diff --git a/gdb/testsuite/gdb.base/errno.c b/gdb/testsuite/gdb.base/errno.c
new file mode 100644
index 00000000000..c6835a866e8
--- /dev/null
+++ b/gdb/testsuite/gdb.base/errno.c
@@ -0,0 +1,37 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2024 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 <errno.h>
+
+static void shadow_errno ();
+
+int main ()
+{
+  errno = 42;
+
+  shadow_errno ();	/* main-breakpoint */
+  return 0;
+}
+
+#undef errno
+static void
+shadow_errno ()
+{
+  int errno = 36;
+
+  return;		/* shadow_errno-breakpoint */
+}
diff --git a/gdb/testsuite/gdb.base/errno.exp b/gdb/testsuite/gdb.base/errno.exp
new file mode 100644
index 00000000000..174eb63bc05
--- /dev/null
+++ b/gdb/testsuite/gdb.base/errno.exp
@@ -0,0 +1,260 @@
+# Copyright 2024 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.
+
+# Check that errno can be accessed by GDB under a variety of
+# circumstances.
+#
+# The challenge with GDB accessing errno is that, on modern systems,
+# errno is a variable in thread-local storage.  So, if GDB's access to
+# thread local storage is broken or unavailable, some of these tests
+# could fail.  On Linux, this is/was known to happen on systems with
+# older versions of glibc as well as when debugging statically linked
+# binaries.
+#
+# Another possibility is that the environment lacks sufficient
+# type information to print errno.  This can happen for the errno
+# variable itself or when the debuginfo contains a macro for errno
+# which refers to a function lacking type information.
+#
+# When debugging core files, access to errno might not be possible
+# both due to the situations described earlier along with the fact
+# that inferior function calls are not possible (for the cases in
+# which errno is a macro which calls a function returning errno's
+# address).
+#
+# It's also possible for a program to declare errno in an inner scope
+# causing the thread-local errno to be shadowed.  GDB should still
+# correctly print the masking errno for this case.
+#
+# At the time that this test was written, on GNU/Linux and on FreeBSD,
+# there were always scenarios in which printing errno was problematic.
+# This test attempts to identify the problem cases and set up xfails
+# for them.  So, hopefully, there should be no actual failures.  But
+# the "expected" failures encountered by running this test do
+# genuinely illustrate problems that a user might encounter while
+# attempting to print errno.
+
+standard_testfile
+
+proc do_tests {{do_xfail_cast 0} {do_xfail 0} {do_xfail_core_test 0}} {
+    clean_restart $::binfile
+    if ![runto_main] {
+	return
+    }
+
+    gdb_breakpoint [gdb_get_line_number "main-breakpoint"]
+    gdb_continue_to_breakpoint "main-breakpoint"
+
+    # Whether or not "print errno" will work often depends on the
+    # debuginfo available.  We can make some inferences about whether
+    # some of the tests should have xfail set-up by looking at the
+    # output of "ptype errno".  This test is set up to always pass
+    # even for less than ideal outputs, because the point is to set up
+    # the xfail(s).
+    gdb_test_multiple "ptype errno" "check errno type availability" {
+	-re -wrap "type = int" {
+	    pass $gdb_test_name
+	}
+	-re -wrap "type = .*no debug info.*" {
+	    pass $gdb_test_name
+	    set do_xfail 1
+	    set do_xfail_core_test 1
+	}
+	-re -wrap "Cannot find thread-local variables on this target.*" {
+	    pass $gdb_test_name
+	    set do_xfail 1
+	    set do_xfail_core_test 1
+	    set do_xfail_cast 1
+	}
+	-re -wrap "Cannot find thread-local storage.*" {
+	    pass $gdb_test_name
+	    set do_xfail 1
+	    set do_xfail_core_test 1
+	    set do_xfail_cast 1
+	}
+	-re -wrap "has unknown return type; cast the call to its declared return type.*" {
+
+	    # On systems which glibc as the C library, using -g3,
+	    # which causes macro information to be included in the
+	    # debuginfo, errno might be defined as follows:
+	    #
+	    #   #define errno (*__errno_location ())
+	    #
+	    # So, when we do "ptype errno", due to macro expansion,
+	    # this ends up being "ptype (*__errno_location ())".  So
+	    # the call to __errno_location (or something similar on
+	    # other OSes) is the call mentioned in the error message.
+
+	    pass $gdb_test_name
+	    set do_xfail 1
+	    set do_xfail_core_test 1
+	    set do_xfail_cast 1
+	}
+    }
+
+    # If errno is defined as a macro that contains an obvious function
+    # call, it won't work when debugging a core file.
+    gdb_test_multiple "info macro errno" "check if errno is a macro" {
+	-re -wrap "Defined at.*\[\r\n\]#define.*\\\(\\\).*" {
+	    set do_xfail_core_test 1
+	    pass $gdb_test_name
+	}
+	-re -wrap "Defined at.*\[\r\n\]#define.*" {
+	    pass $gdb_test_name
+	}
+	-re -wrap "The symbol .errno. has no definition.*" {
+	    pass $gdb_test_name
+	}
+    }
+
+    # Sometimes, "ptype errno" will ferret out that thread local
+    # variables aren't accessible, but sometimes it won't.  Dig deeper
+    # by trying to access memory using the "x/d" command.  Again, the
+    # point here is to set up an xfail for the later tests, so we pass
+    # this test for other known outputs.
+    gdb_test_multiple "x/d &errno" "attempt to access errno memory" {
+	-re -wrap "Cannot find thread-local variables on this target.*" {
+	    pass $gdb_test_name
+	    set do_xfail 1
+	    set do_xfail_core_test 1
+	    set do_xfail_cast 1
+	}
+	-re -wrap "Cannot find thread-local storage.*" {
+	    pass $gdb_test_name
+	    set do_xfail 1
+	    set do_xfail_core_test 1
+	    set do_xfail_cast 1
+	}
+	-re -wrap "has unknown return type; cast the call to its declared return type.*" {
+	    set do_xfail 1
+	    set do_xfail_core_test 1
+	    set do_xfail_cast 1
+	    pass $gdb_test_name
+	}
+	-re -wrap "$::hex.*?:\[\t \]$::decimal" {
+	    pass $gdb_test_name
+	}
+    }
+
+    if $do_xfail {
+	setup_xfail *-*-*
+    }
+    gdb_test "print errno" ".* = 42"
+
+    if $do_xfail_cast {
+	setup_xfail *-*-*
+    }
+    gdb_test "print (int) errno" ".* = 42"
+
+    set corefile ${::binfile}.core
+    set core_supported [gdb_gcore_cmd $corefile "save corefile"]
+    # Normally, we'd check core_supported here and return if it's
+    # not, but we'll defer that until after the shadow test.
+
+    gdb_breakpoint [gdb_get_line_number "shadow_errno-breakpoint"]
+    gdb_continue_to_breakpoint "shadow_errno-breakpoint"
+
+    # This test demonstrates why a simple hack to GDB for printing
+    # errno is a bad idea.  (The hack was to intercept the string
+    # "errno" in process_print_command_args() and replace it with
+    # "*(*(int *(*)(void)) __errno_location) ()".)
+    gdb_test "print errno" ".* = 36" "print masking errno"
+
+    # Finish test early if no core file was made.
+    if !$core_supported {
+	return
+    }
+
+    clean_restart $::binfile
+
+    set core_loaded [gdb_core_cmd $corefile "load corefile"]
+    if { $core_loaded == -1 } {
+	return
+    }
+    if $do_xfail_core_test {
+	setup_xfail *-*-*
+    }
+    gdb_test "print errno" ".* = 42" "check errno value from corefile"
+}
+
+set binprefix $binfile
+
+with_test_prefix "default" {
+    set binfile $binprefix-default
+    if  { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+	untested "failed to compile"
+    } else {
+	do_tests
+    }
+}
+
+with_test_prefix "macros" {
+    set binfile $binprefix-macros
+    if  { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug macros}] != "" } {
+	untested "failed to compile"
+    } else {
+	do_tests
+    }
+}
+
+with_test_prefix "static" {
+    set binfile $binprefix-static
+    if  { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug "additional_flags=-static"}] != "" } {
+	untested "failed to compile"
+    } else {
+	do_tests
+    }
+}
+
+with_test_prefix "static-macros" {
+    set binfile $binprefix-static-macros
+    if  { [gdb_compile "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug macros "additional_flags=-static"}] != "" } {
+	untested "failed to compile"
+    } else {
+	do_tests
+    }
+}
+
+with_test_prefix "pthreads" {
+    set binfile $binprefix-pthreads
+    if  { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+	untested "failed to compile"
+    } else {
+	do_tests
+    }
+}
+
+with_test_prefix "pthreads-macros" {
+    set binfile $binprefix-pthreads-macros
+    if  { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug macros}] != "" } {
+	untested "failed to compile"
+    } else {
+	do_tests
+    }
+}
+
+with_test_prefix "pthreads-static" {
+    set binfile $binprefix-pthreads-static
+    if  { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug "additional_flags=-static"}] != "" } {
+	untested "failed to compile"
+    } else {
+	do_tests
+    }
+}
+
+with_test_prefix "pthreads-static-macros" {
+    set binfile $binprefix-pthreads-static-macros
+    if  { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug macros "additional_flags=-static"}] != "" } {
+	untested "failed to compile"
+    } else {
+	do_tests
+    }
+}
