diff --git a/tool/backtrace b/tool/backtrace
new file mode 100755
index 0000000000..61962fc0ac
--- /dev/null
+++ b/tool/backtrace
@@ -0,0 +1,198 @@
+#!/usr/bin/tclsh
+
+#
+# \brief  Decode 'Genode::backtrace()' output and other memory addresses
+# \author Christian Prochaska
+# \date   2024-01-25
+#
+# Credits: heavily inspired by the Fiasco.OC backtrace tool
+#
+
+
+if {$::argc == 0} {
+	puts "Usage (in Genode debug directory):"
+	puts ""
+	puts {[KERNEL=...] backtrace [binary]}
+	puts ""
+	puts "then paste shared library info and backtrace addresses."
+	puts ""
+	puts "If the KERNEL environment variable is set, the file name "
+	puts "'ld.lib.so' will be replaced by 'ld-KERNEL.lib.so'"
+	puts ""
+	exit 1
+}
+
+
+set genode_tools_dir "/usr/local/genode/tool/23.05/bin"
+
+set arch {}
+
+array set images {}
+array set image_bases {}
+array set sections {}
+array set symbols {}
+
+set sorted_symbols_keys {}
+
+
+proc readelf {} {
+	global genode_tools_dir
+	return "$genode_tools_dir/genode-x86-readelf"
+}
+
+
+proc nm {} {
+	global genode_tools_dir
+	global arch
+	return "$genode_tools_dir/genode-$arch-nm"
+}
+
+
+proc addr2line {} {
+	global genode_tools_dir
+	global arch
+	return "$genode_tools_dir/genode-$arch-addr2line"
+}
+
+
+proc scan_image {img base} {
+
+	global arch
+	global images
+	global image_bases
+	global sections
+	global symbols
+	global sorted_symbols_keys
+
+	if {$arch == ""} {
+		set readelf_output [exec [readelf] -h $img]
+		if {[regexp {AArch64} $readelf_output]} {
+			set arch "aarch64"
+		} elseif {[regexp {ARM} $readelf_output]} {
+			set arch "arm"
+		} elseif {[regexp {RISC-V} $readelf_output]} {
+			set arch "riscv"
+		} elseif {[regexp {80386} $readelf_output]} {
+			set arch "x86"
+		} elseif {[regexp {X86-64} $readelf_output]} {
+			set arch "x86"
+		} else {
+			puts "Error: could not obtain architecture from image file"
+			return
+		}
+	}
+
+	# ignore base address of ld.lib.so
+	if {$img == "ld.lib.so"} {
+		set base 0
+		if {[info exists ::env(KERNEL)]} {
+			set img ld-$::env(KERNEL).lib.so
+		}
+	}
+
+	set nm_output ""
+
+	if {[catch { set nm_output [exec [nm] -C $img.debug] } msg]} {
+		puts "Warning: $msg"
+		return
+	}
+
+	set nm_lines [split $nm_output "\n"]
+
+	foreach nm_line $nm_lines {
+
+		if {[regexp {^([0-9a-fA-F]+)\s+(\S)\s+(.*)$} $nm_line -> addr section sym]} {
+			set key "[format "0x%x" [expr $base + 0x$addr]]"
+			set images($key) $img
+			set image_bases($key) $base
+			set sections($key) $section
+			set symbols($key) $sym
+		}
+	}
+
+	set sorted_symbols_keys [lsort -integer [array names symbols]]
+
+	puts "Scanned image $img"
+}
+
+
+proc print_func {addr} {
+
+	global images
+	global image_bases
+	global sections
+	global symbols
+	global sorted_symbols_keys
+
+	set addr [format "0x%x" $addr]
+
+	set symbol_start_addr 0
+
+	foreach key $sorted_symbols_keys {
+		if {[expr $key > $addr]} {
+			break }
+		set symbol_start_addr $key
+	}
+
+	if {$symbol_start_addr} {
+		set img $images($symbol_start_addr)
+		set local_addr [format "0x%x" [expr $addr - $image_bases($symbol_start_addr)]]
+		set line [exec [addr2line] -e $img $local_addr]
+		puts "$symbols($symbol_start_addr)"
+		puts ""
+		puts "    * $addr: $img:$local_addr $sections($symbol_start_addr)"
+		puts "    * $line"
+	} else {
+		puts "<unknown>"
+		puts "    * $addr"
+	}
+
+	puts ""
+}
+
+
+foreach arg $::argv { scan_image $arg 0 }
+
+while {1} {
+
+	gets stdin line
+
+	# prevent mixing of pasted input and generated output
+	after 10
+
+	puts ""
+
+	if {[regexp {^\[.*\]   (0x[0-9a-f]+).*?: (.*)$} $line -> base img]} {
+
+		# shared library info
+
+		# ignore linker area
+		if {$img == "linker area"} {
+			continue }
+
+		# ignore stack area
+		if {$img == "stack area"} {
+			continue }
+
+		scan_image $img $base
+
+	} elseif {[regexp {^\[.*]\s+[0-9a-f]+\s+([0-9a-f]+)$} $line -> addr]} {
+
+		# backtrace address
+
+		print_func 0x$addr
+
+	} elseif {[regexp {^(0x[0-9a-f]+)$} $line -> addr]} {
+
+		# single hex address prefixed with 0x
+
+		print_func $addr
+
+	} elseif {[regexp {^([0-9a-f]+)$} $line -> addr]} {
+
+		# single hex address not prefixed with 0x
+
+		print_func 0x$addr
+
+	}
+}