diff --git a/librarytrader/librarystore.py b/librarytrader/librarystore.py
index 656cd526050c22baeca9ed5918b9e0c1b91e13af..95bb2496d29e049110f5f1dbe015bbfb27c0b575 100644
--- a/librarytrader/librarystore.py
+++ b/librarytrader/librarystore.py
@@ -237,14 +237,23 @@ class LibraryStore(BaseStore):
             worked_on.add(cur_obj)
         return result
 
-    def get_transitive_calls(self, library, function, working_on=None):
+    def get_transitive_calls(self, library, function, working_on=None,
+                             target_depth=None, cur_depth=0):
+
+        if target_depth and cur_depth == target_depth:
+            return set()
+
         if working_on is None:
             working_on = set()
 
         libname = library.fullname
         if libname not in self._callee_cache:
             self._callee_cache[libname] = {}
-        if function in self._callee_cache[libname]:
+        if target_depth is None and function in self._callee_cache[libname]:
+            # Don't access the cache if we're collecting depth-limited call
+            # graphs. Otherwise, successive calls with increasing depths would
+            # return empty sets for functions at the boundary of the previous
+            # depth and lead to too small results.
             return self._callee_cache[libname][function]
 
         # No cache hit, calculate it
@@ -260,7 +269,8 @@ class LibraryStore(BaseStore):
                                   callee, library.fullname)
                     if callee in working_on:
                         continue
-                    subcalls = self.get_transitive_calls(library, callee, working_on)
+                    subcalls = self.get_transitive_calls(library, callee, working_on,
+                                                         target_depth, cur_depth + 1)
                     local_cache.update(subcalls)
                 working_on.remove(function)
 
@@ -293,7 +303,8 @@ class LibraryStore(BaseStore):
                                       callee, intermediate_object, library.fullname)
                         if callee in working_on:
                             continue
-                        subcalls = self.get_transitive_calls(library, callee, working_on)
+                        subcalls = self.get_transitive_calls(library, callee, working_on,
+                                                             target_depth, cur_depth + 1)
                         local_cache.update(subcalls)
 
                 working_on.remove(function)
diff --git a/scripts/generate_callgraph_depth.py b/scripts/generate_callgraph_depth.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a965bb870a625b8050c8fb9a17feedb014da9bd
--- /dev/null
+++ b/scripts/generate_callgraph_depth.py
@@ -0,0 +1,174 @@
+#!/usr/bin/env python3
+
+import os
+import sys
+
+from librarytrader.librarystore import LibraryStore
+
+def format_node(library, addr):
+    name = '{:x}'.format(addr)
+    options = []
+    # Category of nodes: local/static objects/functions
+    if addr in library.init_functions:
+        if addr in library.local_functions:
+            name = library.local_functions[addr][0]
+        elif addr in library.exported_addrs:
+            name = library.exported_addrs[addr][0]
+        options.append('shape=house')
+    elif addr in library.local_functions:
+        name = library.local_functions[addr][0]
+        options.append('shape=box')
+    elif addr in library.exported_addrs:
+        name = library.exported_addrs[addr][0]
+        options.append('shape=diamond')
+    elif addr in library.local_objs:
+        name = library.local_objs[addr][0]
+        options.append('shape=ellipse')
+    elif addr in library.exported_objs:
+        name = library.exported_objs[addr][0]
+        options.append('shape=hexagon')
+    # Usage:
+    if addr in library.object_users and library.object_users[addr]:
+        options.append('color=green')
+    elif addr in library.local_users and library.local_users[addr]:
+        options.append('color=green')
+    elif addr in library.export_users and library.export_users[addr]:
+        options.append('color=green')
+    options.append('label="{}"'.format(name))
+    return '"{:x}" '.format(addr) + '[' + ', '.join(options) + ']'
+
+def format_edge(library, source, target):
+    retval = '"{:x}" -> "{:x}"'.format(source, target)
+    source_used = False
+    target_used = False
+    if source in library.local_users and library.local_users[source]:
+        source_used = True
+    elif source in library.export_users and library.export_users[source]:
+        source_used = True
+    elif source in library.object_users and library.object_users[source]:
+        source_used = True
+
+    if source_used:
+        if target in library.local_users and library.local_users[target]:
+            target_used = True
+        elif target in library.export_users and library.export_users[target]:
+            target_used = True
+        elif target in library.object_users and library.object_users[target]:
+            target_used = True
+
+    if target_used:
+        retval += ' [color=green]'
+    retval += '\n'
+    return retval
+
+def maybe_print_node(library, addr, seen, outfd):
+    if addr not in seen:
+        seen.add(addr)
+        outfd.write(format_node(library, addr) + '\n')
+
+def print_edges(library, source, targets, seen, seen_edges, outfd):
+    maybe_print_node(library, source, seen, outfd)
+    for target in targets:
+        maybe_print_node(library, target, seen, outfd)
+        if (source, target) not in seen_edges:
+            seen_edges.add((source, target))
+            outfd.write(format_edge(library, source, target))
+
+
+s = LibraryStore()
+s.load(sys.argv[1])
+lname = sys.argv[2]
+addr = int(sys.argv[3])
+depth = int(sys.argv[4])
+
+l = s[lname]
+outname = os.path.basename(l.fullname) + '_' + hex(addr) + '_' + str(depth) + '.dot'
+
+with open(outname, 'w') as outfd:
+    print('writing to {}'.format(outname))
+    seen = set()
+    seen_edges = set()
+    seen_import = set()
+
+    outfd.write('digraph D {' + '\n')
+
+    # Get all function nodes reachable from given address
+    nodes = set(k for k, v in s.get_transitive_calls(l, addr, target_depth=depth) if
+                v.fullname == lname)
+    # Add the initial node itself as it is not part of the transitive call chain
+    nodes.add(addr)
+    # Additionally, extract the visited objects from the LibraryStore to allow
+    # a reconstruction of the dependency flow through these objects.
+    for (src, subl), targets in s._object_cache.items():
+        if subl.fullname != lname:
+            continue
+        nodes.add(src)
+        nodes.update(targets)
+
+    i = 1
+    prev_nodes = set()
+    while nodes != prev_nodes:
+        print('round {}'.format(i))
+        i += 1
+        prev_nodes = nodes.copy()
+
+        # Add nodes to output
+        for addr in nodes:
+            maybe_print_node(l, addr, seen, outfd)
+
+        # Add edges through local calls
+        for source, targets in l.local_calls.items():
+            if source in nodes:
+                for target in targets:
+                    if target not in nodes:
+                        continue
+                    print_edges(l, source, [target], seen, seen_edges, outfd)
+
+        # Add edges through calls to exported functions
+        for source, targets in l.internal_calls.items():
+            if source in nodes:
+                for target in targets:
+                    if target not in nodes:
+                        continue
+                    print_edges(l, source, [target], seen, seen_edges, outfd)
+
+        # Add edges to imported (== external) functions
+        for source, targets in l.external_calls.items():
+            if source not in nodes:
+                continue
+            maybe_print_node(l, source, seen, outfd)
+            for target in targets:
+                if target not in seen_import:
+                    seen_import.add(target)
+                    outfd.write('"{}" [shape=doubleoctagon]\n'.format(target))
+                outfd.write('"{:x}" -> "{}"'.format(source, target))
+                outfd.write('\n')
+
+        # Same for local objects
+        for source, targets in l.local_object_refs.items():
+            if source in nodes:
+                print_edges(l, source, targets, seen, seen_edges, outfd)
+                nodes.update(targets)
+
+        # ... exported objects
+        for source, targets in l.export_object_refs.items():
+            if source in nodes:
+                print_edges(l, source, targets, seen, seen_edges, outfd)
+                nodes.update(targets)
+
+        # ... references between objects themselves
+        for source, targets in l.object_to_objects.items():
+            if source not in nodes:
+                continue
+            print_edges(l, source, targets, seen, seen_edges, outfd)
+            nodes.update(targets)
+
+        # ... and outgoing edges from objects to functions
+        for source, targets in l.object_to_functions.items():
+            if source not in nodes:
+                continue
+            if source in l.exports_plt:
+                continue
+            print_edges(l, source, targets, seen, seen_edges, outfd)
+
+    outfd.write('}' + '\n')