diff --git a/gdb/testsuite/gdb.base/tls-common.exp.tcl b/gdb/testsuite/gdb.base/tls-common.exp.tcl
new file mode 100644
index 00000000000..7439e80e3e2
--- /dev/null
+++ b/gdb/testsuite/gdb.base/tls-common.exp.tcl
@@ -0,0 +1,50 @@
+# 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.
+
+# Require statement, variables and procs used by tls-nothreads.exp,
+# tls-multiobj.exp, and tls-dlobj.exp.
+
+# The tests listed above are known to work for the targets listed on
+# the 'require' line, below.
+#
+# At the moment, only the Linux target is listed, but, ideally, these
+# tests should be run on other targets too.  E.g, testing on FreeBSD
+# shows many failures which should be addressed in some fashion before
+# enabling it for that target.
+
+require {is_any_target "*-*-linux*"}
+
+# These are the targets which have support for internal TLS lookup:
+
+set internal_tls_linux_targets {"x86_64-*-linux*" "aarch64-*-linux*"
+				"riscv*-*-linux*" "powerpc64*-*-linux*"
+				"s390x*-*-linux*"}
+
+# The "set force-internal-tls-address-lookup" command is only
+# available for certain Linux architectures.  Don't attempt to force
+# use of internal TLS support for architectures which don't support
+# it.
+
+if [is_any_target {*}$internal_tls_linux_targets] {
+    set internal_tls_iters { false true }
+} else {
+    set internal_tls_iters { false }
+}
+
+# Set up a kfail with message KFAIL_MSG when KFAIL_COND holds, then
+# issue gdb_test with command CMD and regular expression RE.
+
+proc gdb_test_with_kfail {cmd re kfail_cond kfail_msg} {
+    if [uplevel 1 [list expr $kfail_cond]] {
+	setup_kfail $kfail_msg *-*-*
+    }
+    gdb_test $cmd $re
+}
diff --git a/gdb/testsuite/gdb.base/tls-nothreads.c b/gdb/testsuite/gdb.base/tls-nothreads.c
new file mode 100644
index 00000000000..b3aaa3383e7
--- /dev/null
+++ b/gdb/testsuite/gdb.base/tls-nothreads.c
@@ -0,0 +1,57 @@
+/* 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/>.  */
+
+__thread int tls_tbss_1;
+__thread int tls_tbss_2;
+__thread int tls_tbss_3;
+
+__thread int tls_tdata_1 = 21;
+__thread int tls_tdata_2 = 22;
+__thread int tls_tdata_3 = 23;
+
+volatile int data;
+
+void
+use_it (int a)
+{
+  data = a;
+}
+
+int
+main (int argc, char **argv)
+{
+  use_it (-1);
+
+  tls_tbss_1 = 24;	/* main-breakpoint-1 */
+  tls_tbss_2 = 25;
+  tls_tbss_3 = 26;
+
+  tls_tdata_1 = 42;
+  tls_tdata_2 = 43;
+  tls_tdata_3 = 44;
+
+  use_it (tls_tbss_1);
+  use_it (tls_tbss_2);
+  use_it (tls_tbss_3);
+  use_it (tls_tdata_1);
+  use_it (tls_tdata_2);
+  use_it (tls_tdata_3);
+
+  use_it (100);		/* main-breakpoint-2 */
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.base/tls-nothreads.exp b/gdb/testsuite/gdb.base/tls-nothreads.exp
new file mode 100644
index 00000000000..8860c3e753d
--- /dev/null
+++ b/gdb/testsuite/gdb.base/tls-nothreads.exp
@@ -0,0 +1,249 @@
+# 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.
+
+# Using different compilation/linking scenarios, attempt to access
+# thread-local variables in a non-threaded program.  Also test that
+# GDB internal TLS lookup works correctly.
+
+source $srcdir/$subdir/tls-common.exp.tcl
+
+standard_testfile
+
+proc do_tests {force_internal_tls {do_kfail_tls_access 0}} {
+    clean_restart $::binfile
+    if ![runto_main] {
+	return
+    }
+
+    if $force_internal_tls {
+	gdb_test_no_output "set force-internal-tls-address-lookup on"
+    }
+
+    if { $do_kfail_tls_access && [istarget "*-*-linux*"] } {
+	# Turn off do_kfail_tls_access when libthread_db is loaded. 
+	# This can happen for the default case when testing x86_64
+	# w/ -m32 using glibc versions 2.34 or newer.
+	gdb_test_multiple "maint check libthread-db" "Check for loaded libthread_db" {
+	    -re -wrap "libthread_db integrity checks passed." {
+		set do_kfail_tls_access 0
+		pass $gdb_test_name
+	    }
+	    -re -wrap "No libthread_db loaded" {
+		pass $gdb_test_name
+	    }
+	}
+	# Also turn off do_kfail_tls_access when connected to a
+	# gdbserver and we observe that accessing a TLS variable
+	# works.
+	if [target_is_gdbserver] {
+	    gdb_test_multiple "print tls_tbss_1" "Check TLS accessibility when connected to a gdbserver" {
+		-re -wrap "= 0" {
+		    set do_kfail_tls_access 0
+		    pass $gdb_test_name
+		}
+		-re -wrap "Remote target failed to process qGetTLSAddr request" {
+		    pass $gdb_test_name
+		}
+	    }
+	}
+    }
+
+    gdb_breakpoint [gdb_get_line_number "main-breakpoint-1"]
+    gdb_continue_to_breakpoint "main-breakpoint-1"
+
+    set t $do_kfail_tls_access
+    set m "tls not available"
+    with_test_prefix "before assignments" {
+	gdb_test_with_kfail "print tls_tbss_1" ".* = 0" $t $m
+	gdb_test_with_kfail "print tls_tbss_2" ".* = 0" $t $m
+	gdb_test_with_kfail "print tls_tbss_3" ".* = 0" $t $m
+
+	gdb_test_with_kfail "print tls_tdata_1" ".* = 21" $t $m
+	gdb_test_with_kfail "print tls_tdata_2" ".* = 22" $t $m
+	gdb_test_with_kfail "print tls_tdata_3" ".* = 23" $t $m
+    }
+
+    gdb_breakpoint [gdb_get_line_number "main-breakpoint-2"]
+    gdb_continue_to_breakpoint "main-breakpoint-2"
+
+    with_test_prefix "after assignments" {
+	gdb_test_with_kfail "print tls_tbss_1" ".* = 24" $t $m
+	gdb_test_with_kfail "print tls_tbss_2" ".* = 25" $t $m
+	gdb_test_with_kfail "print tls_tbss_3" ".* = 26" $t $m
+
+	gdb_test_with_kfail "print tls_tdata_1" ".* = 42" $t $m
+	gdb_test_with_kfail "print tls_tdata_2" ".* = 43" $t $m
+	gdb_test_with_kfail "print tls_tdata_3" ".* = 44" $t $m
+    }
+
+    # Make a core file now, but save testing using it until the end
+    # in case core files are not supported.
+    set corefile ${::binfile}.core
+    set core_supported 0
+    if { ![is_remote host] } {
+	set core_supported [gdb_gcore_cmd $corefile "save corefile"]
+    }
+
+    # Now continue to end and see what happens when attempting to
+    # access a TLS variable when the program is no longer running.
+    gdb_continue_to_end
+    with_test_prefix "after exit" {
+	gdb_test "print tls_tbss_1" \
+	    "Cannot read `tls_tbss_1' without registers"
+    }
+
+    with_test_prefix "stripped" {
+	set binfile_stripped "${::binfile}.stripped"
+	set objcopy [gdb_find_objcopy]
+	set cmd "$objcopy --strip-debug ${::binfile} $binfile_stripped"
+	if ![catch "exec $cmd" cmd_output] {
+	    clean_restart $binfile_stripped
+	    if ![runto_main] {
+		return
+	    }
+
+	    if $force_internal_tls {
+		gdb_test_no_output "set force-internal-tls-address-lookup on"
+	    }
+
+	    # While there are no debug (e.g.  DWARF) symbols, there
+	    # are minimal symbols, so we should be able to place a
+	    # breakpoint in use_it and continue to it.  Continuing
+	    # twice should put us past the assignments, at which point
+	    # we can see if the TLS variables are still accessible.
+	    gdb_test "break use_it" "Breakpoint 2 at $::hex"
+	    gdb_test "continue" "Breakpoint 2, $::hex in use_it.*"
+	    gdb_test "continue" "Breakpoint 2, $::hex in use_it.*" "continue 2"
+
+	    # Note that a cast has been added in order to avoid the
+	    # "...has unknown type; cast it to its declared type"
+	    # problem.
+	    gdb_test_with_kfail "print (int) tls_tbss_1" ".* = 24" $t $m
+	    gdb_test_with_kfail "print (int) tls_tbss_2" ".* = 25" $t $m
+	    gdb_test_with_kfail "print (int) tls_tbss_3" ".* = 26" $t $m
+
+	    gdb_test_with_kfail "print (int) tls_tdata_1" ".* = 42" $t $m
+	    gdb_test_with_kfail "print (int) tls_tdata_2" ".* = 43" $t $m
+	    gdb_test_with_kfail "print (int) tls_tdata_3" ".* = 44" $t $m
+
+	    # Get rid of the "use_it" breakpoint
+	    gdb_test_no_output "del 2"
+
+	    # Continue to program exit
+	    gdb_continue_to_end
+
+	    # TLS variables should not be accessible after program exit
+	    # (This case initially caused GDB to crash during development
+	    # of GDB-internal TLS lookup support.)
+	    with_test_prefix "after exit" {
+		gdb_test "print (int) tls_tbss_1" \
+		    "Cannot determine address of TLS symbol `tls_tbss_1' without registers"
+	    }
+	}
+    }
+
+    # 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
+    }
+
+    with_test_prefix "core file" {
+	if $force_internal_tls {
+	    gdb_test_no_output "set force-internal-tls-address-lookup on"
+	}
+
+	gdb_test_with_kfail "print tls_tbss_1" ".* = 24" $t $m
+	gdb_test_with_kfail "print tls_tbss_2" ".* = 25" $t $m
+	gdb_test_with_kfail "print tls_tbss_3" ".* = 26" $t $m
+
+	gdb_test_with_kfail "print tls_tdata_1" ".* = 42" $t $m
+	gdb_test_with_kfail "print tls_tdata_2" ".* = 43" $t $m
+	gdb_test_with_kfail "print tls_tdata_3" ".* = 44" $t $m
+    }
+}
+
+# Certain linux target architectures implement support for internal
+# TLS lookup which is used when thread stratum support (via
+# libthread_db) is missing or when the linux-only GDB setting
+# 'force-internal-tls-address-lookup' is 'on'.  Thus for some
+# of the testing scenarios, such as statically linked executables,
+# this internal support will be used.  Set 'do_kfail_tls_access'
+# to 1 for those architectures which don't implement internal
+# TLS support.
+if {[istarget *-*-linux*]
+    && ![is_any_target {*}$internal_tls_linux_targets]} {
+    set do_kfail_tls_access 1
+} elseif {[istarget *-*-linux*] && [is_x86_like_target]} {
+    # This covers the case of x86_64 with -m32:
+    set do_kfail_tls_access 1
+} else {
+    set do_kfail_tls_access 0
+}
+
+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 {
+	foreach_with_prefix force_internal_tls $internal_tls_iters {
+	    # Depending on glibc version, it might not be appropriate
+	    # for do_kfail_tls_access to be set here.  That will be
+	    # handled in 'do_tests', disabling it if necessary.
+	    # 
+	    # Specifically, glibc versions 2.34 and later have the
+	    # thread library (and libthread_db availability) in
+	    # programs not linked against libpthread.so
+	    do_tests $force_internal_tls $do_kfail_tls_access
+	}
+    }
+}
+
+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 {
+	foreach_with_prefix force_internal_tls $internal_tls_iters {
+	    do_tests $force_internal_tls $do_kfail_tls_access
+	}
+    }
+}
+
+with_test_prefix "pthreads" {
+    set binfile $binprefix-pthreads
+    if  { [gdb_compile_pthreads "${srcdir}/${subdir}/${srcfile}" "${binfile}" executable {debug}] != "" } {
+	untested "failed to compile"
+    } else {
+	foreach_with_prefix force_internal_tls $internal_tls_iters {
+	    do_tests $force_internal_tls
+	}
+    }
+}
+
+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 {
+	foreach_with_prefix force_internal_tls $internal_tls_iters {
+	    do_tests $force_internal_tls $do_kfail_tls_access
+	}
+    }
+}
