[V3,09/15] unwinder: Add SFrame unwinder tests

Message ID 20221030074450.1956074-10-indu.bhagat@oracle.com
State Not Applicable
Headers
Series Definition and support for SFrame unwind format |

Checks

Context Check Description
snail/binutils-gdb-check fail Git am fail log

Commit Message

Indu Bhagat Oct. 30, 2022, 7:44 a.m. UTC
  From: Weimin Pan <weimin.pan@oracle.com>

[Changes in V3]
  - Added two new tests with attributes -f(no-)omit-frame-pointer.
  - Minor adjustments due to buildsystem changes in libsframe.
[End of changes in V3]

[Changes in V2]
  - minor changes in filenames in the testsuite.
[End of changes in V2]

Add tests for backtracing using SFrame section.

PS: libsframe/configure has NOT been included in the patch.  Please
regenerate.

ChangeLog:

	* libsframe/Makefile.in: Regenerated.
	* libsframe/configure: Regenerated.  <-- [REMOVED FROM THE
	  PATCH.  PLEASE REGENERATE.  ]
	* libsframe/configure.ac: Check for cross compilation.
	* libsframe/testsuite/Makefile.in: Regenerated.
	* libsframe/testsuite/config/default.exp: Load
	  sframe-lib.exp.
	* libsframe/testsuite/libsframe.decode/Makefile.in:
	  Regenerated.
	* libsframe/testsuite/libsframe.encode/Makefile.in:
	  Regenerated.
	* libsframe/testsuite/lib/sframe-lib.exp: New file.  Add
	  procedures for handling unwinder tests.
	* libsframe/testsuite/libsframe.unwind/backtrace.c: New test.
	* libsframe/testsuite/libsframe.unwind/backtrace.lk: New test.
	* libsframe/testsuite/libsframe.unwind/inline-cmds.c: New test.
	* libsframe/testsuite/libsframe.unwind/inline-cmds.lk: New test.
	* libsframe/testsuite/libsframe.unwind/inline.c: New test.
	* libsframe/testsuite/libsframe.unwind/inline.lk: New test.
	* libsframe/testsuite/libsframe.unwind/solib-lib1.c: New test.
	* libsframe/testsuite/libsframe.unwind/solib-lib2.c: New test.
	* libsframe/testsuite/libsframe.unwind/solib-main.c: New test.
	* libsframe/testsuite/libsframe.unwind/solib-main.d: New test.
	* libsframe/testsuite/libsframe.unwind/solib.exp: New file.
	* libsframe/testsuite/libsframe.unwind/solib-lib1.h: New test.
	* libsframe/testsuite/libsframe.unwind/solib-lib2.h: New test.
	* libsframe/testsuite/libsframe.unwind/tailcall.c: New test.
	* libsframe/testsuite/libsframe.unwind/tailcall.lk: New test.
	* libsframe/testsuite/libsframe.unwind/ttest.c: New test.
	* libsframe/testsuite/libsframe.unwind/ttest.lk: New test.
	* libsframe/testsuite/libsframe.unwind/unwind.exp: New file.
	* libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-1.c:
	  Likewise.
	* libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-1.lk:
	  Likewise.
	* libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-2.c:
	  Likewise.
	* libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-2.lk:
	  Likewise.
---
 libsframe/Makefile.in                         |   1 +
 libsframe/configure.ac                        |  11 +
 libsframe/testsuite/config/default.exp        |   3 +
 libsframe/testsuite/lib/sframe-lib.exp        | 180 +++++++++++++++
 .../libsframe.unwind/backtrace-fp-attr-1.c    | 109 ++++++++++
 .../libsframe.unwind/backtrace-fp-attr-1.lk   |   4 +
 .../libsframe.unwind/backtrace-fp-attr-2.c    | 110 ++++++++++
 .../libsframe.unwind/backtrace-fp-attr-2.lk   |   4 +
 .../testsuite/libsframe.unwind/backtrace.c    | 145 +++++++++++++
 .../testsuite/libsframe.unwind/backtrace.lk   |   3 +
 .../testsuite/libsframe.unwind/inline-cmds.c  | 136 ++++++++++++
 .../testsuite/libsframe.unwind/inline-cmds.lk |   3 +
 libsframe/testsuite/libsframe.unwind/inline.c |  97 +++++++++
 .../testsuite/libsframe.unwind/inline.lk      |   3 +
 .../testsuite/libsframe.unwind/solib-lib1.c   |   8 +
 .../testsuite/libsframe.unwind/solib-lib1.h   |   3 +
 .../testsuite/libsframe.unwind/solib-lib2.c   |  51 +++++
 .../testsuite/libsframe.unwind/solib-lib2.h   |   3 +
 .../testsuite/libsframe.unwind/solib-main.c   |  47 ++++
 .../testsuite/libsframe.unwind/solib-main.d   |   3 +
 .../testsuite/libsframe.unwind/solib.exp      |  75 +++++++
 .../testsuite/libsframe.unwind/tailcall.c     | 103 +++++++++
 .../testsuite/libsframe.unwind/tailcall.lk    |   3 +
 libsframe/testsuite/libsframe.unwind/ttest.c  | 127 +++++++++++
 libsframe/testsuite/libsframe.unwind/ttest.lk |   3 +
 .../testsuite/libsframe.unwind/unwind.exp     | 205 ++++++++++++++++++
 26 files changed, 1440 insertions(+)
 create mode 100644 libsframe/testsuite/lib/sframe-lib.exp
 create mode 100644 libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-1.c
 create mode 100644 libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-1.lk
 create mode 100644 libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-2.c
 create mode 100644 libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-2.lk
 create mode 100644 libsframe/testsuite/libsframe.unwind/backtrace.c
 create mode 100644 libsframe/testsuite/libsframe.unwind/backtrace.lk
 create mode 100644 libsframe/testsuite/libsframe.unwind/inline-cmds.c
 create mode 100644 libsframe/testsuite/libsframe.unwind/inline-cmds.lk
 create mode 100644 libsframe/testsuite/libsframe.unwind/inline.c
 create mode 100644 libsframe/testsuite/libsframe.unwind/inline.lk
 create mode 100644 libsframe/testsuite/libsframe.unwind/solib-lib1.c
 create mode 100644 libsframe/testsuite/libsframe.unwind/solib-lib1.h
 create mode 100644 libsframe/testsuite/libsframe.unwind/solib-lib2.c
 create mode 100644 libsframe/testsuite/libsframe.unwind/solib-lib2.h
 create mode 100644 libsframe/testsuite/libsframe.unwind/solib-main.c
 create mode 100644 libsframe/testsuite/libsframe.unwind/solib-main.d
 create mode 100644 libsframe/testsuite/libsframe.unwind/solib.exp
 create mode 100644 libsframe/testsuite/libsframe.unwind/tailcall.c
 create mode 100644 libsframe/testsuite/libsframe.unwind/tailcall.lk
 create mode 100644 libsframe/testsuite/libsframe.unwind/ttest.c
 create mode 100644 libsframe/testsuite/libsframe.unwind/ttest.lk
 create mode 100644 libsframe/testsuite/libsframe.unwind/unwind.exp
  

Comments

Mike Frysinger Oct. 30, 2022, 2:14 p.m. UTC | #1
On 30 Oct 2022 00:44, Indu Bhagat via Binutils wrote:
> --- a/libsframe/configure.ac
> +++ b/libsframe/configure.ac
>  
> +# Determine if we are cross compiling

use `dnl` for comments in .ac files

> +AC_CANONICAL_HOST

this only sets up $host, but you're also checking $target.  you should be
using AC_CANONICAL_TARGET.
https://www.gnu.org/savannah-checkouts/gnu/autoconf/manual/autoconf-2.71/html_node/Canonicalizing.html

> --- /dev/null
> +++ b/libsframe/testsuite/lib/sframe-lib.exp
>
> +load_file $srcdir/../../ld/testsuite/lib/ld-lib.exp

this doesn't seem right to me, but i guess if the ld maintainers are
OK with it ...

> --- /dev/null
> +++ b/libsframe/testsuite/libsframe.unwind/solib.exp
>
> +# Run the tests only if we are not cross compiling.

why ?  people need to test cross-compilers too.  some folks will never test
it with a native compiler because they only ever make cross-compilers, which
means these tests rarely (if ever) get run.  especially for embedded arches
that can't self-host (e.g. nommu).

> --- /dev/null
> +++ b/libsframe/testsuite/libsframe.unwind/unwind.exp
>
> +if {[info exists env(LC_ALL)]} {
> +    set old_lc_all $env(LC_ALL)
> +}
> +set env(LC_ALL) "C"

why ?  weird code like this really needs a comment.  this is usually
a read flag that the test/code is broken.
-mike
  

Patch

diff --git a/libsframe/Makefile.in b/libsframe/Makefile.in
index 3d0b26000db..a96a7102953 100644
--- a/libsframe/Makefile.in
+++ b/libsframe/Makefile.in
@@ -316,6 +316,7 @@  CFLAGS = @CFLAGS@
 COMPAT_DEJAGNU = @COMPAT_DEJAGNU@
 CPP = @CPP@
 CPPFLAGS = @CPPFLAGS@
+CROSS_COMPILE = @CROSS_COMPILE@
 CYGPATH_W = @CYGPATH_W@
 DEFS = @DEFS@
 DEPDIR = @DEPDIR@
diff --git a/libsframe/configure.ac b/libsframe/configure.ac
index 2775fe88b68..038331dba2e 100644
--- a/libsframe/configure.ac
+++ b/libsframe/configure.ac
@@ -59,6 +59,17 @@  AM_CONDITIONAL([HAVE_COMPAT_DEJAGNU], [test "x$ac_cv_dejagnu_compat" = "xyes"])
 COMPAT_DEJAGNU=$ac_cv_dejagnu_compat
 AC_SUBST(COMPAT_DEJAGNU)
 
+# Determine if we are cross compiling
+AC_CANONICAL_HOST
+is_cross_compiler=
+if test x"${host}" = x"${target}" ; then
+  is_cross_compiler=no
+else
+  is_cross_compiler=yes
+fi
+CROSS_COMPILE=$is_cross_compiler
+AC_SUBST([CROSS_COMPILE])
+
 dnl The libsframebt library needs to be built with SFrame info.
 dnl If the build assembler is not capable of generate SFrame then
 dnl the library is not built.
diff --git a/libsframe/testsuite/config/default.exp b/libsframe/testsuite/config/default.exp
index c45e25d3357..29dacbc2917 100644
--- a/libsframe/testsuite/config/default.exp
+++ b/libsframe/testsuite/config/default.exp
@@ -52,3 +52,6 @@  if {![info exists CFLAGS]} {
 if {![info exists CFLAGS_FOR_TARGET]} {
     set CFLAGS_FOR_TARGET $CFLAGS
 }
+
+# load the utility procedures
+load_lib sframe-lib.exp
diff --git a/libsframe/testsuite/lib/sframe-lib.exp b/libsframe/testsuite/lib/sframe-lib.exp
new file mode 100644
index 00000000000..d686d27f371
--- /dev/null
+++ b/libsframe/testsuite/lib/sframe-lib.exp
@@ -0,0 +1,180 @@ 
+# Support routines for libsframe testsuite.
+#   Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of the GNU Binutils.
+#
+# This file 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+load_file $srcdir/../../ld/testsuite/lib/ld-lib.exp
+
+set unwind_test_file_name ""
+
+proc run_native_host_cmd { command } {
+    global link_output
+    global ld
+
+    verbose -log "$command"
+    set run_output ""
+    try {
+	set run_output [exec "sh" "-c" "$command" "2>@1"]
+	set status 0
+    } trap CHILDSTATUS {results options} {
+	set status [lindex [dict get $options -errorcode] 2]
+	set run_output $results
+    }
+    regsub "\n$" $run_output "" run_output
+    if { [lindex $status 0] != 0 && [string match "" $run_output] } then {
+	append run_output "child process exited abnormally"
+    }
+
+    if [string match "" $run_output] then {
+	return ""
+    }
+
+    verbose -log "$run_output"
+    return "$run_output"
+}
+
+# Compile and link a C source file for execution on the host.
+proc compile_link_one_host_cc { src output additional_args } {
+    global CC
+    global CFLAGS
+
+    return [run_native_host_cmd "./libtool --quiet --tag=CC --mode=link $CC $CFLAGS $src -o $output $additional_args" ]
+}
+
+proc make_unwind_parallel_path { args } {
+    global objdir
+    set joiner [list "file" "join" $objdir]
+    set joiner [concat $joiner $args]
+    return [eval $joiner]
+}
+
+proc standard_output_file {basename} {
+    global objdir subdir unwind_test_file_name
+
+    set dir [make_unwind_parallel_path outputs $subdir $unwind_test_file_name]
+    file mkdir $dir
+    return [file join $dir $basename]
+}
+
+proc standard_testfile {args} {
+    global unwind_test_file_name
+    global subdir
+    global unwind_test_file_last_vars
+
+    # Outputs.
+    global testfile binfile
+
+    set testfile $unwind_test_file_name
+    set binfile [standard_output_file ${testfile}]
+
+    if {[llength $args] == 0} {
+	set args .c
+    }
+
+    # Unset our previous output variables.
+    # This can help catch hidden bugs.
+    if {[info exists unwind_test_file_last_vars]} {
+	foreach varname $unwind_test_file_last_vars {
+	    global $varname
+	    catch {unset $varname}
+	}
+    }
+    # 'executable' is often set by tests.
+    set unwind_test_file_last_vars {executable}
+
+    set suffix ""
+    foreach arg $args {
+	set varname srcfile$suffix
+	global $varname
+
+	# Handle an extension.
+	if {$arg == ""} {
+	    set arg $testfile.c
+	} else {
+	    set first [string range $arg 0 0]
+	    if { $first == "." || $first == "-" } {
+		set arg $testfile$arg
+	    }
+	}
+
+	set $varname $arg
+	lappend unwind_test_file_last_vars $varname
+
+	if {$suffix == ""} {
+	    set suffix 2
+	} else {
+	    incr suffix
+	}
+    }
+}
+
+# Build a shared object DEST from SOURCES.
+proc unwind_compile_so {sources dest} {
+    global CFLAGS
+    set obj_options $CFLAGS
+    lappend obj_options "additional_flags=-fPIC -Wa,--gsframe"
+
+    set outdir [file dirname $dest]
+    set objects ""
+    foreach source $sources {
+	set sourcebase [file tail $source]
+	set object ${outdir}/${sourcebase}.o
+
+	if {[target_compile $source $object object \
+		  $obj_options] != ""} {
+	    return -1
+	}
+
+	lappend objects $object
+    }
+
+    set link_options "additional_flags=-shared"
+
+    set destbase [file tail $dest]
+    lappend link_options "additional_flags=-Wl,-soname,$destbase"
+
+    if {[target_compile "${objects}" "${dest}" executable $link_options] != ""} {
+	catch "exec rm ${objects}" status
+	return -1
+    }
+    catch "exec rm ${objects}" status
+    return ""
+}
+
+# Build a binary of TYPE from SOURCE at path DEST.
+proc unwind_compile {source dest type options} {
+    set new_options ""
+
+    foreach opt $options {
+	if {[regexp {^shlib=(.*)} $opt dummy_var shlib_name]
+	    && $type == "executable"} {
+	    lappend source "-Wl,$shlib_name"
+	} else {
+	    lappend new_options $opt
+	}
+    }
+    set options $new_options
+
+    verbose "options are $options"
+    verbose "source is $source $dest $type $options"
+
+    lappend options "additional_flags=-rdynamic -Wa,--gsframe ./.libs/libsframebt.a ./.libs/libsframe.a"
+    set result [target_compile $source $dest $type $options]
+
+    return $result
+}
diff --git a/libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-1.c b/libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-1.c
new file mode 100644
index 00000000000..d02e916da7b
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-1.c
@@ -0,0 +1,109 @@ 
+/* Copyright (C) 2022 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/>.  */
+
+/* This is a revised version of gdb/testsuite/gdb.base/backtrace.c.  */
+
+#ifdef __has_attribute
+# if !__has_attribute (noclone)
+#  define ATTRIBUTE_NOCLONE
+# endif
+#endif
+#ifndef ATTRIBUTE_NOCLONE
+# define ATTRIBUTE_NOCLONE __attribute__((noclone))
+#endif
+
+#include <execinfo.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "sframe-backtrace-api.h"
+
+#define BT_BUF_SIZE 100
+
+#define BT_EXPECTED_NPTRS 5
+/* Expected funclist.  */
+static const char *const func_list[] =
+{
+  "show_bt",
+  "baz",
+  "bar",
+  "foo",
+  "main"
+};
+
+void __attribute__((__noinline__,__optimize__("omit-frame-pointer"))) ATTRIBUTE_NOCLONE
+show_bt ()
+{
+  void *buffer[BT_BUF_SIZE];
+  int j, nptrs, err;
+  char **strings;
+
+  /* Call the unwinder to get an array of return addresses.  */
+  nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (err)
+    {
+      printf ("SFrame error: %s (%d)\n", sframe_bt_errmsg (err), nptrs);
+      return;
+    }
+  if (nptrs != BT_EXPECTED_NPTRS)
+    {
+      printf ("Backtace nptrs mismatch: expected = %d, generated = %d \n",
+	      BT_EXPECTED_NPTRS, nptrs);
+      return;
+    }
+
+  /* Get these addresses symbolically.  */
+  strings = backtrace_symbols (buffer, nptrs);
+  if (strings == NULL) {
+      perror ("backtrace_symbols");
+      exit (EXIT_FAILURE);
+  }
+
+  /* Verify the results.  */
+  for (j = 0; j < nptrs; j++)
+    if (!strstr (strings[j], func_list[j]))
+      break;
+
+  free(strings);
+
+  printf ("%s: backtrace with omit-frame-pointer attr\n",
+	  (j == nptrs) ? "PASS" : "FAIL");
+}
+
+int __attribute__((__noinline__)) ATTRIBUTE_NOCLONE
+baz ()
+{
+  show_bt ();
+  return 0;
+}
+
+int __attribute__((__noinline__)) ATTRIBUTE_NOCLONE
+bar ()
+{
+  return baz ();
+}
+
+int __attribute__((__noinline__)) ATTRIBUTE_NOCLONE
+foo ()
+{
+  return bar ();
+}
+
+int __attribute__((__noinline__)) ATTRIBUTE_NOCLONE
+main ()
+{
+  return foo ();
+}
diff --git a/libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-1.lk b/libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-1.lk
new file mode 100644
index 00000000000..a26ef7e2d2e
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-1.lk
@@ -0,0 +1,4 @@ 
+# source: backtrace-fp-attr-1.c
+# cflags: -fno-omit-frame-pointer
+# link: on
+PASS: backtrace with omit-frame-pointer attr
diff --git a/libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-2.c b/libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-2.c
new file mode 100644
index 00000000000..6ac80b73b5d
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-2.c
@@ -0,0 +1,110 @@ 
+/* Copyright (C) 2022 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/>.  */
+
+/* This is a revised version of gdb/testsuite/gdb.base/backtrace.c.  */
+
+#ifdef __has_attribute
+# if !__has_attribute (noclone)
+#  define ATTRIBUTE_NOCLONE
+# endif
+#endif
+#ifndef ATTRIBUTE_NOCLONE
+# define ATTRIBUTE_NOCLONE __attribute__((noclone))
+#endif
+
+#include <execinfo.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "sframe-backtrace-api.h"
+
+#define BT_BUF_SIZE 100
+
+#define BT_EXPECTED_NPTRS 5
+
+/* Expected funclist.  */
+static const char *const func_list[] =
+{
+  "show_bt",
+  "baz",
+  "bar",
+  "foo",
+  "main"
+};
+
+void __attribute__((__noinline__,__optimize__("no-omit-frame-pointer"))) ATTRIBUTE_NOCLONE
+show_bt ()
+{
+  void *buffer[BT_BUF_SIZE];
+  int j, nptrs, err;
+  char **strings;
+
+  /* Call the unwinder to get an array of return addresses.  */
+  nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (err)
+    {
+      printf ("SFrame error: %s (%d)\n", sframe_bt_errmsg (err), nptrs);
+      return;
+    }
+  if (nptrs != BT_EXPECTED_NPTRS)
+    {
+      printf ("Backtace nptrs mismatch: expected = %d, generated = %d \n",
+	      BT_EXPECTED_NPTRS, nptrs);
+      return;
+    }
+
+  /* Get these addresses symbolically.  */
+  strings = backtrace_symbols (buffer, nptrs);
+  if (strings == NULL) {
+      perror ("backtrace_symbols");
+      exit (EXIT_FAILURE);
+  }
+
+  /* Verify the results.  */
+  for (j = 0; j < nptrs; j++)
+    if (!strstr (strings[j], func_list[j]))
+      break;
+
+  free(strings);
+
+  printf ("%s: backtrace with no-omit-frame-pointer attr\n",
+	  (j == nptrs) ? "PASS" : "FAIL");
+}
+
+int __attribute__((__noinline__)) ATTRIBUTE_NOCLONE
+baz ()
+{
+  show_bt ();
+  return 0;
+}
+
+int __attribute__((__noinline__)) ATTRIBUTE_NOCLONE
+bar ()
+{
+  return baz ();
+}
+
+int __attribute__((__noinline__)) ATTRIBUTE_NOCLONE
+foo ()
+{
+  return bar ();
+}
+
+int __attribute__((__noinline__)) ATTRIBUTE_NOCLONE
+main ()
+{
+  return foo ();
+}
diff --git a/libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-2.lk b/libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-2.lk
new file mode 100644
index 00000000000..97f49ef8c43
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/backtrace-fp-attr-2.lk
@@ -0,0 +1,4 @@ 
+# source: backtrace-fp-attr-2.c
+# cflags: -fomit-frame-pointer
+# link: on
+PASS: backtrace with no-omit-frame-pointer attr
diff --git a/libsframe/testsuite/libsframe.unwind/backtrace.c b/libsframe/testsuite/libsframe.unwind/backtrace.c
new file mode 100644
index 00000000000..072cb2f8f08
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/backtrace.c
@@ -0,0 +1,145 @@ 
+/* Copyright (C) 2022 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/>.  */
+
+/* This is a revised version of gdb/testsuite/gdb.base/backtrace.c.  */
+
+#ifdef __has_attribute
+# if !__has_attribute (noclone)
+#  define ATTRIBUTE_NOCLONE
+# endif
+#endif
+#ifndef ATTRIBUTE_NOCLONE
+# define ATTRIBUTE_NOCLONE __attribute__((noclone))
+#endif
+
+#include <execinfo.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "sframe-backtrace-api.h"
+
+#define BT_BUF_SIZE 100
+
+/* Expected funclist.  */
+static const char *const func_list[] =
+{
+  "show_bt",
+  "baz",
+  "bar",
+  "foo",
+  "main"
+};
+
+void __attribute__((__noinline__,__noclone__))
+show_bt ()
+{
+  void *buffer[BT_BUF_SIZE];
+  int j, nptrs, err;
+  char **strings;
+
+  /* Call the unwinder to get an array of return addresses.  */
+  nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (nptrs != 5)
+    {
+      printf ("SFrame error: %s (%d)\n", sframe_bt_errmsg (err), nptrs);
+      return;
+    }
+
+  /* Get these addresses symbolically.  */
+  strings = backtrace_symbols (buffer, nptrs);
+  if (strings == NULL) {
+      perror("backtrace_symbols");
+      exit(EXIT_FAILURE);
+  }
+
+  /* Verify the results.  */
+  for (j = 0; j < nptrs; j++)
+    if (!strstr (strings[j], func_list[j]))
+      break;
+
+  free(strings);
+
+  printf ("%s: backtrace test\n", j == nptrs ? "PASS" : "FAIL");
+}
+
+int __attribute__((__noinline__,__noclone__))
+baz ()
+{
+  void *buffer[BT_BUF_SIZE];
+  int nptrs, err;
+
+  /* Call the unwinder to get an array of return addresses.  */
+  nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (nptrs == -1)
+    {
+      printf ("SFrame error: %s (%d)\n", sframe_bt_errmsg (err), nptrs);
+      return -1;
+    }
+
+  show_bt ();
+  return 0;
+}
+
+int __attribute__((__noinline__,__noclone__))
+bar ()
+{
+  void *buffer[BT_BUF_SIZE];
+  int nptrs, err;
+
+  /* Call the unwinder to get an array of return addresses.  */
+  nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (nptrs == -1)
+    {
+      printf ("SFrame error: %s (%d)\n", sframe_bt_errmsg (err), nptrs);
+      return -1;
+    }
+
+  return baz ();
+}
+
+int __attribute__((__noinline__,__noclone__))
+foo ()
+{
+  void *buffer[BT_BUF_SIZE];
+  int nptrs, err;
+
+  /* Call the unwinder to get an array of return addresses.  */
+  nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (nptrs == -1)
+    {
+      printf ("SFrame error: %s (%d)\n", sframe_bt_errmsg (err), nptrs);
+      return -1;
+    }
+
+  return bar ();
+}
+
+int __attribute__((__noinline__,__noclone__))
+main ()
+{
+  void *buffer[BT_BUF_SIZE];
+  int nptrs, err;
+
+  /* Call the unwinder to get an array of return addresses.  */
+  nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (nptrs == -1)
+    {
+      printf ("SFrame error: %s (%d)\n", sframe_bt_errmsg (err), nptrs);
+      return -1;
+    }
+
+  return foo ();
+}
diff --git a/libsframe/testsuite/libsframe.unwind/backtrace.lk b/libsframe/testsuite/libsframe.unwind/backtrace.lk
new file mode 100644
index 00000000000..fdc78ebe34d
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/backtrace.lk
@@ -0,0 +1,3 @@ 
+# source: backtrace.c
+# link: on
+PASS: backtrace test
diff --git a/libsframe/testsuite/libsframe.unwind/inline-cmds.c b/libsframe/testsuite/libsframe.unwind/inline-cmds.c
new file mode 100644
index 00000000000..a6ceb50a956
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/inline-cmds.c
@@ -0,0 +1,136 @@ 
+/* Copyright (C) 2022 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/>.  */
+
+/* This is only ever run if it is compiled with a new-enough GCC, but
+   we don't want the compilation to fail if compiled by some other
+   compiler.  */
+
+/* This is a revised version of gdb/testsuite/gdb.opt/inline-cmds.c.  */
+
+#ifdef __GNUC__
+#define ATTR __attribute__((always_inline))
+#else
+#define ATTR
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <execinfo.h>
+#include "sframe-backtrace-api.h"
+
+#define BT_BUF_SIZE 10
+
+int x, y;
+volatile int z = 0;
+volatile int result;
+
+void bar(void);
+void marker(void);
+void noinline(void);
+
+inline ATTR int func1(void)
+{
+  bar ();
+  return x * y;
+}
+
+inline ATTR int func2(void)
+{
+  return x * func1 ();
+}
+
+inline ATTR void func3(void)
+{
+  bar ();
+}
+
+inline ATTR void outer_inline1(void)
+{
+  noinline ();
+}
+
+inline ATTR void outer_inline2(void)
+{
+  outer_inline1 ();
+}
+
+int main (void)
+{ /* start of main */
+  int val;
+
+  x = 7;
+  y = 8;
+
+  outer_inline2 ();
+
+  return 0;
+}
+
+
+/* funclist for inline-cmds.  */
+const char *const func_list[] =
+{
+  "noinline",
+  "main"
+};
+
+void bar(void)
+{
+  x += y;
+}
+
+void marker(void)
+{
+  x += y - z;
+}
+
+inline ATTR void inlined_fn(void)
+{
+  x += y + z;
+
+  void *buffer[BT_BUF_SIZE];
+  char **strings;
+  /* Call the unwinder to get an array of return addresses.  */
+  int j, err;
+  int nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (nptrs == -1 || nptrs != 2)
+    {
+      printf ("SFrame error: %s (%d)\n", sframe_bt_errmsg (err), nptrs);
+      return;
+    }
+
+  /* Get these addresses symbolically.  */
+  strings = backtrace_symbols (buffer, nptrs);
+  if (strings == NULL)
+    {
+      perror("backtrace_symbols");
+      exit(EXIT_FAILURE);
+    }
+
+  /* Verify the results.  */
+  for (j = 0; j < nptrs; j++)
+    if (!strstr (strings[j], func_list[j]))
+      break;
+
+    free(strings);
+    printf ("%s: inline-cmds test\n", j == nptrs ? "PASS" : "FAIL");
+}
+
+void noinline(void)
+{
+  inlined_fn (); /* inlined */
+}
diff --git a/libsframe/testsuite/libsframe.unwind/inline-cmds.lk b/libsframe/testsuite/libsframe.unwind/inline-cmds.lk
new file mode 100644
index 00000000000..053b66bd683
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/inline-cmds.lk
@@ -0,0 +1,3 @@ 
+# source: inline-cmds.c
+# link: on
+PASS: inline-cmds test
diff --git a/libsframe/testsuite/libsframe.unwind/inline.c b/libsframe/testsuite/libsframe.unwind/inline.c
new file mode 100644
index 00000000000..86c93136b9a
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/inline.c
@@ -0,0 +1,97 @@ 
+/* Copyright (C) 2022 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/>.  */
+
+/* This is a revised version of gdb/testsuite/gdb.opt/inline-bt.c.  */
+
+#include <execinfo.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include "sframe-backtrace-api.h"
+
+#define ATTR __attribute__((always_inline))
+
+#define BT_BUF_SIZE 32
+
+int x, y;
+volatile int z = 0;
+volatile int result;
+
+/* funclist.  */
+const char *const flist[] =
+{
+  "main"
+};
+
+void bar(void)
+{
+  x += y;
+}
+
+inline ATTR int func1(void)
+{
+  bar ();
+  return x * y;
+}
+
+inline ATTR int func2(void)
+{
+  void *buffer[BT_BUF_SIZE];
+  int ok = 0, nptrs, err;
+  char **strings;
+
+  /* Call the unwinder to get an array of return addresses.  */
+  nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (nptrs == -1)
+    {
+      printf ("SFrame error: %s\n", sframe_bt_errmsg (err));
+      return -1;
+    }
+
+  /* Get these addresses symbolically.  */
+  strings = backtrace_symbols (buffer, nptrs);
+  if (strings == NULL) {
+      perror("backtrace_symbols");
+      exit(EXIT_FAILURE);
+  }
+
+  /* Verify the results.  */
+  if (nptrs == 1 && strstr (strings[0], flist[0]))
+    ok = 1;
+
+  free(strings);
+
+  printf ("%s: unwind test\n", ok == 1 ? "PASS" : "FAIL");
+
+  return x * func1 ();
+}
+
+int main (void)
+{
+  int val;
+
+  x = 7;
+  y = 8;
+  bar ();
+
+  val = func1 ();
+  result = val;
+
+  val = func2 ();
+  result = val;
+
+  return 0;
+}
diff --git a/libsframe/testsuite/libsframe.unwind/inline.lk b/libsframe/testsuite/libsframe.unwind/inline.lk
new file mode 100644
index 00000000000..88f846b0fce
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/inline.lk
@@ -0,0 +1,3 @@ 
+# source: inline.c
+# link: on
+PASS: unwind test
diff --git a/libsframe/testsuite/libsframe.unwind/solib-lib1.c b/libsframe/testsuite/libsframe.unwind/solib-lib1.c
new file mode 100644
index 00000000000..16c77ca53c4
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/solib-lib1.c
@@ -0,0 +1,8 @@ 
+#include "solib-lib1.h"
+
+unsigned int
+adder(unsigned int a, unsigned int b, int (*call)(int))
+{
+  (void)(*call)(a+b);
+  return (a+b);
+}
diff --git a/libsframe/testsuite/libsframe.unwind/solib-lib1.h b/libsframe/testsuite/libsframe.unwind/solib-lib1.h
new file mode 100644
index 00000000000..d40eac0769c
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/solib-lib1.h
@@ -0,0 +1,3 @@ 
+#include<stdio.h>
+
+extern unsigned int adder(unsigned int a, unsigned int b, int (*call)(int));
diff --git a/libsframe/testsuite/libsframe.unwind/solib-lib2.c b/libsframe/testsuite/libsframe.unwind/solib-lib2.c
new file mode 100644
index 00000000000..310b9c0ab89
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/solib-lib2.c
@@ -0,0 +1,51 @@ 
+#include <execinfo.h>
+#include <stdlib.h>
+#include <string.h>
+#include "sframe-backtrace-api.h"
+#include "solib-lib2.h"
+
+#define BT_BUF_SIZE 100
+
+/* funclist for running "ttest.x 3".  */
+static const char *const bt_list[] =
+{
+  "adder2",
+  "bar",
+  "adder",
+  "main"
+};
+
+unsigned int
+adder2 (unsigned int a, unsigned int b, int (*call)(int))
+{
+  void *buffer[BT_BUF_SIZE];
+  int i, nptrs, err;
+  char **strings;
+
+  nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (nptrs == -1 || nptrs != 4)
+    {
+/* printf ("bcktrace failed: %d %d\n", nptrs, err); */
+      printf ("SFrame error: %s\n", sframe_bt_errmsg (err));
+      return(-1);
+    }
+
+  strings = backtrace_symbols (buffer, nptrs);
+  if (strings == NULL)
+    {
+       perror("backtrace_symbols");
+       return(-1);
+    }
+
+  /* Verify the results.  */
+  for (i = 0; i < nptrs; i++)
+    if (!strstr (strings[i], bt_list[i]))
+      break;
+
+  free (strings);
+
+  printf ("%s: unwind solib test\n", i == nptrs ? "PASS" : "FAIL");
+
+  (void)(*call) (a+b);
+  return (a+b);
+}
diff --git a/libsframe/testsuite/libsframe.unwind/solib-lib2.h b/libsframe/testsuite/libsframe.unwind/solib-lib2.h
new file mode 100644
index 00000000000..61b72122771
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/solib-lib2.h
@@ -0,0 +1,3 @@ 
+#include<stdio.h>
+
+extern unsigned int adder2(unsigned int a, unsigned int b, int (*call)(int));
diff --git a/libsframe/testsuite/libsframe.unwind/solib-main.c b/libsframe/testsuite/libsframe.unwind/solib-main.c
new file mode 100644
index 00000000000..9215b95f759
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/solib-main.c
@@ -0,0 +1,47 @@ 
+/* Copyright (C) 2022 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 <execinfo.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "sframe-backtrace-api.h"
+#include "solib-lib1.h"
+#include "solib-lib2.h"
+
+#define BT_BUF_SIZE 100
+
+int foo (int x)
+{
+  return ++x;
+}
+
+int bar (int x)
+{
+  x = adder2 (x, x+1, foo);
+
+  return ++x;
+}
+
+int main (void)
+{
+  unsigned int a = 1;
+  unsigned int b = 2;
+  unsigned int result = 0;
+
+  result = adder (a,b, bar);
+
+  return 0;
+}
diff --git a/libsframe/testsuite/libsframe.unwind/solib-main.d b/libsframe/testsuite/libsframe.unwind/solib-main.d
new file mode 100644
index 00000000000..483ded5a1e5
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/solib-main.d
@@ -0,0 +1,3 @@ 
+# source: solib-main.c
+# link: on
+PASS: unwind solib test
diff --git a/libsframe/testsuite/libsframe.unwind/solib.exp b/libsframe/testsuite/libsframe.unwind/solib.exp
new file mode 100644
index 00000000000..2690ab6ae17
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/solib.exp
@@ -0,0 +1,75 @@ 
+# Copyright 2022 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/>.
+
+# Run the test only if sframebt library exists.
+if [catch "exec ls $objdir/.libs/libsframebt.la" status] then {
+  return;
+}
+
+# Run the tests only if we are not cross compiling.
+if [string equal $CROSS_COMPILE "yes"] {
+    return;
+}
+
+set experimental ""
+
+# Shared object files.
+set libname1 "solib-lib1"
+set srcfile_lib1 ${srcdir}/${subdir}/${libname1}.c
+set binfile_lib1 ${objdir}/${libname1}.so
+set libname2 "solib-lib2"
+set srcfile_lib2 ${srcdir}/${subdir}/${libname2}.c
+set binfile_lib2 ${objdir}/${libname2}.so
+
+# Binary file.
+set testfile "solib-main"
+set srcfile ${srcdir}/${subdir}/${testfile}.c
+set binfile [standard_output_file ${testfile}]
+set bin_flags [list debug shlib=${binfile_lib1} shlib=${binfile_lib2}]
+
+if { [unwind_compile_so ${srcfile_lib1} ${binfile_lib1}] != ""
+     || [unwind_compile_so ${srcfile_lib2} ${binfile_lib2}] != ""
+     || [unwind_compile ${srcfile} ${binfile} executable $bin_flags] != "" } {
+    untested "failed to compile"
+    return -1
+}
+
+if {[info exists env(LD_LIBRARY_PATH)]} {
+    set old_ld_lib $env(LD_LIBRARY_PATH)
+}
+set env(LD_LIBRARY_PATH) "${objdir}"
+
+set solib_output "${binfile} ${binfile_lib1} ${binfile_lib2}"
+set results [run_host_cmd ${binfile} $solib_output]
+
+set f [open "tmpdir/solib.out" "w"]
+puts $f $results
+close $f
+
+if { [regexp_diff "tmpdir/solib.out" "${srcdir}/${subdir}/${testfile}.d"] } then {
+    fail "$test_name"
+} else {
+    pass "$test_name"
+}
+
+catch "exec rm ${binfile_lib1}" status
+catch "exec rm ${binfile_lib2}" status
+catch "exec rm tmpdir/solib.out" status
+
+if {[info exists old_ld_lib]} {
+    set env(LD_LIBRARY_PATH) $old_ld_lib
+} else {
+    unset env(LD_LIBRARY_PATH)
+}
diff --git a/libsframe/testsuite/libsframe.unwind/tailcall.c b/libsframe/testsuite/libsframe.unwind/tailcall.c
new file mode 100644
index 00000000000..7ffa728a27e
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/tailcall.c
@@ -0,0 +1,103 @@ 
+/* Copyright (C) 2022 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 <execinfo.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "sframe-backtrace-api.h"
+
+#ifdef __has_attribute
+# if !__has_attribute (noclone)
+#  define ATTRIBUTE_NOCLONE
+# endif
+#endif
+#ifndef ATTRIBUTE_NOCLONE
+# define ATTRIBUTE_NOCLONE __attribute__((noclone))
+#endif
+
+#define BT_BUF_SIZE 16
+
+/* funclist for running tailcall.  */
+const char *const func_list[] =
+{
+  "show_bt",
+  "dec",
+  "dec",
+  "main"
+};
+
+void show_bt ()
+{
+  void *buffer[BT_BUF_SIZE];
+  int j, nptrs, err;
+  char **strings;
+
+  /* Call the unwinder to get an array of return addresses.  */
+  nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (nptrs == -1 || nptrs != 4)
+    {
+      printf ("SFrame error: %s (%d)\n", sframe_bt_errmsg (err), nptrs);
+      return;
+    }
+
+  /* Get these addresses symbolically.  */
+  strings = backtrace_symbols (buffer, nptrs);
+  if (strings == NULL) {
+      perror("backtrace_symbols");
+      exit(EXIT_FAILURE);
+  }
+
+  /* Verify the results.  */
+  for (j = 0; j < nptrs; j++)
+    if (!strstr (strings[j], func_list[j]))
+      break;
+
+  free(strings);
+
+  printf ("%s: tailcall test\n", j == nptrs ? "PASS" : "FAIL");
+}
+
+/* An example of tail recursive function.  */
+void __attribute__((__noinline__,__noclone__))
+dec (int n)
+{
+  void *buffer[BT_BUF_SIZE];
+  int nptrs, err;
+
+  /* Call the unwinder to get an array of return addresses.  */
+  nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (nptrs == -1)
+    {
+      printf ("SFrame error: %s (%d)\n", sframe_bt_errmsg (err), nptrs);
+      return;
+    }
+
+  if (n < 0)
+     return;
+
+  if (n == 2)
+    show_bt ();
+
+  /* The last executed statement is recursive call.  */
+  dec (n-1);
+}
+
+int
+main (void)
+{
+  dec (3);
+}
diff --git a/libsframe/testsuite/libsframe.unwind/tailcall.lk b/libsframe/testsuite/libsframe.unwind/tailcall.lk
new file mode 100644
index 00000000000..3d7ab98ddba
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/tailcall.lk
@@ -0,0 +1,3 @@ 
+# source: tailcall.c
+# link: on
+PASS: tailcall test
diff --git a/libsframe/testsuite/libsframe.unwind/ttest.c b/libsframe/testsuite/libsframe.unwind/ttest.c
new file mode 100644
index 00000000000..5e245db93c9
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/ttest.c
@@ -0,0 +1,127 @@ 
+/* Copyright (C) 2022 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/>.  */
+
+/* This is the revised version of the example in "man backtrace".  */
+
+#include <execinfo.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include "sframe-backtrace-api.h"
+
+#ifdef __has_attribute
+# if !__has_attribute (noclone)
+#  define ATTRIBUTE_NOCLONE
+# endif
+#endif
+#ifndef ATTRIBUTE_NOCLONE
+# define ATTRIBUTE_NOCLONE __attribute__((noclone))
+#endif
+
+#define BT_BUF_SIZE 100
+
+/* funclist  */
+static const char *const func_list[] =
+{
+  "myfunc3",
+  "()",
+  "myfunc",
+  "myfunc",
+  "myfunc",
+  "main"
+};
+
+void myfunc3 (void)
+{
+  void *buffer[BT_BUF_SIZE];
+  int j, nptrs, err;
+  char **strings;
+
+  /* Call the unwinder to get an array of return addresses.  */
+  nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (nptrs == -1 || nptrs != 6)
+    {
+      printf ("SFrame error: %s (%d)\n", sframe_bt_errmsg (err), nptrs);
+      return;
+    }
+
+  /* Get these addresses symbolically.  */
+  strings = backtrace_symbols (buffer, nptrs);
+  if (strings == NULL) {
+    perror("backtrace_symbols");
+    exit(EXIT_FAILURE);
+  }
+
+  /* Verify the results.  */
+  for (j = 0; j < nptrs; j++)
+    if (!strstr (strings[j], func_list[j]))
+      break;
+
+  free(strings);
+
+  printf ("%s: unwind test\n", j == nptrs ? "PASS" : "FAIL");
+}
+
+static void __attribute__((__noinline__,__noclone__))
+/* "static" means don't export the symbol.  */
+myfunc2 (void)
+{
+  void *buffer[BT_BUF_SIZE];
+  int nptrs, err;
+
+  /* Call the unwinder to get an array of return addresses.  */
+  nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (nptrs == -1)
+    {
+      printf ("SFrame error: %s (%d)\n", sframe_bt_errmsg (err), nptrs);
+      return;
+    }
+
+  myfunc3 ();
+}
+
+void __attribute__((__noinline__,__noclone__))
+myfunc (int ncalls)
+{
+  void *buffer[BT_BUF_SIZE];
+  int nptrs, err;
+
+  /* Call the unwinder to get an array of return addresses.  */
+  nptrs = sframe_backtrace (buffer, BT_BUF_SIZE, &err);
+  if (nptrs == -1)
+    {
+      printf ("SFrame error: %s (%d)\n", sframe_bt_errmsg (err), nptrs);
+      return;
+    }
+
+  if (ncalls > 1)
+    myfunc (ncalls - 1);
+  else
+    myfunc2 ();
+}
+
+int
+main (int argc, char *argv[])
+{
+  int cnt;
+  if (argc != 2) {
+    cnt = 3;
+  }
+  else
+    cnt = atoi(argv[1]);
+  myfunc (cnt);
+  exit (EXIT_SUCCESS);
+}
diff --git a/libsframe/testsuite/libsframe.unwind/ttest.lk b/libsframe/testsuite/libsframe.unwind/ttest.lk
new file mode 100644
index 00000000000..80aa2241402
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/ttest.lk
@@ -0,0 +1,3 @@ 
+# source: ttest.c
+# link: on
+PASS: unwind test
diff --git a/libsframe/testsuite/libsframe.unwind/unwind.exp b/libsframe/testsuite/libsframe.unwind/unwind.exp
new file mode 100644
index 00000000000..4169eef1f09
--- /dev/null
+++ b/libsframe/testsuite/libsframe.unwind/unwind.exp
@@ -0,0 +1,205 @@ 
+# Copyright (C) 2022 Free Software Foundation, Inc.
+#
+# This file is part of the GNU Binutils.
+#
+# 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, write to the Free Software
+# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
+# MA 02110-1301, USA.
+#
+
+# Run the tests only if sframebt library exists.
+
+if [catch "exec ls $objdir/.libs/libsframebt.la" status] then {
+  verbose -log "$objdir/.libs/libsframebt.la not found.";
+  verbose -log "Skipping SFrame unwind tests";
+  return;
+}
+
+# Run the tests only if we are not cross compiling.
+if [string equal $CROSS_COMPILE "yes"] {
+    return;
+}
+
+if {[info exists env(LC_ALL)]} {
+    set old_lc_all $env(LC_ALL)
+}
+set env(LC_ALL) "C"
+
+proc run_unwind_test { name } {
+    global CC
+    global CFLAGS
+    global copyfile env runtests srcdir subdir verbose
+
+    # Append additional flags for unwinder to work properly
+    set unwind_cflags "-Wa,--gsframe -rdynamic "
+
+    if ![runtest_file_p $runtests $name] then {
+	return
+    }
+
+    if [string match "*/*" $name] {
+	set file $name
+	set name [file tail $name]
+    } else {
+	set file "$srcdir/$subdir/$name"
+    }
+
+    set opt_array [slurp_options "${file}.lk"]
+    if { $opt_array == -1 } {
+	perror "error reading options from $file.lk"
+	unresolved $subdir/$name
+	return
+    }
+    set run_ld 0
+    set shared "-shared"
+    set opts(cflags) {}
+    set opts(link) {}
+    set opts(link_flags) {}
+    set opts(nonshared) {}
+    set opts(unwind) {}
+    set opts(name) {}
+    set opts(source) {}
+    set opts(xfail) {}
+
+    foreach i $opt_array {
+	set opt_name [lindex $i 0]
+	set opt_val [lindex $i 1]
+	if { $opt_name == "" } {
+	    set in_extra 1
+	    continue
+	}
+	if ![info exists opts($opt_name)] {
+	    perror "unknown option $opt_name in file $file.lk"
+	    unresolved $subdir/$name
+	    return
+	}
+
+	set opts($opt_name) [concat $opts($opt_name) $opt_val]
+    }
+
+    if { [llength $opts(unwind)] == 0 } {
+	set opts(unwind) "$file.c"
+    } else {
+	set opts(unwind) "[file dirname $file]/$opts(unwind)"
+    }
+
+    if { [llength $opts(name)] == 0 } {
+	set opts(name) $opts(unwind)
+    }
+
+    if { [llength $opts(cflags)] != 0 } {
+	append unwind_cflags $opts(cflags)
+    }
+
+    if { [llength $opts(link)] != 0
+	 || [llength $opts(source)] > 1 } {
+	set run_ld 1
+    }
+
+    if { [llength $opts(nonshared)] != 0 } {
+	set shared ""
+    }
+
+    set testname $opts(name)
+    if { $opts(name) == "" } {
+	set testname "$subdir/$name"
+    }
+
+    # Compile and link the unwind program.
+    set comp_output [compile_link_one_host_cc $opts(unwind) "tmpdir/test_x" "./.libs/libsframebt.la ./.libs/libsframe.la"]
+
+    if { $comp_output != ""} {
+	send_log "compilation of unwind program $opts(unwind) failed with <$comp_output>"
+	perror "compilation of unwind program $opts(unwind) failed"
+	fail $testname
+	return 0
+    }
+
+    # Compile the inputs and posibly link them together.
+
+    set unwind ""
+    if { [llength $opts(source)] > 0 } {
+	set unwind ""
+	if { $run_ld } {
+	    set unwind_output "tmpdir/test_x ./.libs/libsframebt.a ./.libs/libsframe.a"
+	    # set unwind_output "tmpdir/out.so"
+	    # set unwind_flags "-fPIC $shared $opts(link_flags)"
+	} else {
+	    set unwind_output "tmpdir/out.o"
+	    # set unwind_flags "-fPIC -c"
+	}
+	if [board_info [target_info name] exists cflags] {
+	    append unwind_flags " [board_info [target_info name] cflags]"
+	}
+	if [board_info [target_info name] exists ldflags] {
+	    append unwind_flags " [board_info [target_info name] ldflags]"
+	}
+	set src {}
+	foreach sfile $opts(source) {
+	    if [is_remote host] {
+		lappend src [remote_download host [file join [file dirname $file] $sfile]]
+	    } else {
+		lappend src [file join [file dirname $file] $sfile]
+	    }
+	}
+
+	set comp_output [run_host_cmd "$CC" "$CFLAGS $unwind_cflags [concat $src] -o $unwind_output"]
+
+	if { $comp_output != ""} {
+	    send_log "compilation of SFrame test program [concat $src] failed with <$comp_output>"
+	    fail $testname
+	    return 0
+	}
+    }
+
+    # Time to setup xfailures.
+    foreach targ $opts(xfail) {
+	if [match_target $targ] {
+	    setup_xfail "*-*-*"
+	    break
+	}
+    }
+
+    # Invoke the unwind program on the outputs.
+
+    verbose -log "$srcdir"
+    set results [run_host_cmd tmpdir/test_x $unwind_output]
+
+    set f [open "tmpdir/test_x.out" "w"]
+    puts $f $results
+    close $f
+
+    if { [regexp_diff "tmpdir/test_x.out" "${file}.lk"] } then {
+	fail $testname
+	if { $verbose == 2 } then { verbose "output is [file_contents tmpdir/test_x.out]" 2 }
+	return 0
+    }
+
+    pass $testname
+    return 0
+}
+
+set sframe_test_list [lsort [glob -nocomplain $srcdir/$subdir/*.lk]]
+
+foreach sframe_test $sframe_test_list {
+    verbose [file rootname $sframe_test]
+    verbose running unwind test on $sframe_test
+    run_unwind_test [file rootname $sframe_test]
+}
+
+if {[info exists old_lc_all]} {
+    set env(LC_ALL) $old_lc_all
+} else {
+    unset env(LC_ALL)
+}