├── README.md ├── makefile └── rtl2dot.py /README.md: -------------------------------------------------------------------------------- 1 | # rtl2dot 2 | Create C callgraphs from gcc rtldumps via GraphViz. 3 | 4 | Based on [egypt](http://www.gson.org/egypt/egypt.html), rewritten in python with added support for configurable root nodes and omission by regex. 5 | May or may not work with C++ code. 6 | 7 | ## Usage 8 | Compile your code with `-fdump-rtl-expand` (eg. by running `make CFLAGS=-fdump-rtl-expand`). 9 | This will generate some new files, most commonly with the extension `.expand`. 10 | 11 | Run `rtl2dot myproject.beepbop.expand | dot -Tsvg > myproject.svg` to get a graph of the `main` function of your project. 12 | 13 | Valid options are: 14 | * `--ignore ` Regular expression describing function names to be ignored 15 | * `--root ` Select the function to use as root node of the graph 16 | * `--local` Ignore functions that are not defined within the rtl dump (most likely library functions) 17 | * `--indirect` Draw a dashed line when the address of a function is taken 18 | 19 | Any other arguments are treated as input files. If no input files are given, input is expected on stdin. 20 | 21 | ### Example 22 | `./rtl2dot.py smtpd.expand --root core_loop --ignore "client_send|logprintf|common_*" --local | dot -Tsvg > smtpd.svg` 23 | 24 | ## License 25 | This program is free software. It comes without any warranty, to 26 | the extent permitted by applicable law. You can redistribute it 27 | and/or modify it under the terms of the Do What The Fuck You Want 28 | To Public License, Version 2, as published by Sam Hocevar and 29 | reproduced below. 30 | 31 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 32 | Version 2, December 2004 33 | 34 | Copyright (C) 2004 Sam Hocevar 35 | 36 | Everyone is permitted to copy and distribute verbatim or modified 37 | copies of this license document, and changing it is allowed as long 38 | as the name is changed. 39 | 40 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 41 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 42 | 43 | 0. You just DO WHAT THE FUCK YOU WANT TO. 44 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | .PHONY: install 2 | export PREFIX ?= /usr 3 | 4 | install: 5 | install -m 0755 rtl2dot.py "$(DESTDIR)$(PREFIX)/bin" 6 | -------------------------------------------------------------------------------- /rtl2dot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 3 | # Author: cbdev 4 | # Reference: https://github.com/cbdevnet/rtl2dot 5 | # 6 | #This program is free software. It comes without any warranty, to 7 | #the extent permitted by applicable law. You can redistribute it 8 | #and/or modify it under the terms of the Do What The Fuck You Want 9 | #To Public License, Version 2, as published by Sam Hocevar and 10 | #reproduced below. 11 | # 12 | #DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 13 | #Version 2, December 2004 14 | # 15 | #Copyright (C) 2004 Sam Hocevar 16 | # 17 | # Everyone is permitted to copy and distribute verbatim or modified 18 | # copies of this license document, and changing it is allowed as long 19 | # as the name is changed. 20 | # 21 | #DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 22 | #TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 23 | # 24 | # 0. You just DO WHAT THE FUCK YOU WANT TO. 25 | # 26 | 27 | import fileinput 28 | import re 29 | import sys 30 | 31 | root = "main" 32 | ignore = None 33 | infiles = [] 34 | local = False 35 | indirects = False 36 | 37 | i = 1 38 | # There probably should be sanity checks here, but lets face it: If you cant pass arguments right, this isnt for you 39 | while i < len(sys.argv): 40 | if sys.argv[i] == "--ignore": 41 | ignore = re.compile(sys.argv[i + 1]) 42 | i += 1 43 | elif sys.argv[i] == "--root": 44 | root = sys.argv[i + 1] 45 | i += 1 46 | elif sys.argv[i] == "--local": 47 | local = True 48 | elif sys.argv[i] == "--indirect": 49 | indirects = True 50 | elif sys.argv[i] == "--help" or sys.argv[i] == "-h": 51 | print("Generate call graphs of C programs from gcc rtldumps") 52 | print("Options:") 53 | print("\t--ignore \t\tFunctions to omit from the resulting graph") 54 | print("\t--root \t\tWhich function to use as root node (default: main)") 55 | print("\t--local\t\t\t\tOmit functions not defined in the dump (eg. library calls)") 56 | print("\t--indirect\t\t\tDraw a dashed line when the address of a function is taken") 57 | sys.exit(0) 58 | else: 59 | infiles.append(sys.argv[i]) 60 | i += 1 61 | 62 | current = "" 63 | calls = {} 64 | 65 | func_old = re.compile("^;; Function (?P\S+)\s*$") 66 | func_new = re.compile("^;; Function (?P.*)\s+\((?P\S+)(,.*)?\).*$") 67 | funcall = re.compile("^.*\(call.*\"(?P.*)\".*$") 68 | symref = re.compile("^.*\(symbol_ref.*\"(?P.*)\".*$") 69 | 70 | def enter(func): 71 | global current, calls 72 | current = func 73 | if calls.get(current, None) is not None: 74 | print("Ambiguous function name " + current, file=sys.stderr) 75 | else: 76 | calls[current] = {} 77 | 78 | def call(func, facility): 79 | global calls 80 | if calls[current].get(func, None) is not None and calls[current][func] != facility: 81 | print("Ambiguous calling reference to " + func, file=sys.stderr) 82 | calls[current][func] = facility 83 | 84 | def dump(func): 85 | global calls 86 | if calls.get(func, None) is None: 87 | # edge node 88 | return 89 | for ref in calls[func].keys(): 90 | if calls[func][ref] is not None: 91 | style = "" if calls[func][ref] == "call" else ' [style="dashed"]' 92 | if local and calls.get(ref, None) is None: 93 | # non-local function 94 | continue 95 | if not indirects and calls[func][ref] == "ref": 96 | # indirect reference, but not requested 97 | continue 98 | if ignore is None or re.match(ignore, ref) is None: 99 | # Invalidate the reference to avoid loops 100 | calls[func][ref] = None 101 | print('"' + func + '" -> "' + ref + '"' + style + ';') 102 | dump(ref) 103 | 104 | # Scan the rtl dump into the dict 105 | for line in fileinput.input(infiles): 106 | if re.match(func_old, line) is not None: 107 | # print "OLD", re.match(func_old, line).group("func") 108 | enter(re.match(func_old, line).group("func")) 109 | elif re.match(func_new, line) is not None: 110 | # print "NEW", re.match(func_new, line).group("func"), "Mangled:", re.match(func_new, line).group("mangle") 111 | enter(re.match(func_new, line).group("func")) 112 | elif re.match(funcall, line) is not None: 113 | # print "CALL", re.match(funcall, line).group("target") 114 | call(re.match(funcall, line).group("target"), "call") 115 | elif re.match(symref, line) is not None: 116 | # print "REF", re.match(symref, line).group("target") 117 | call(re.match(symref, line).group("target"), "ref") 118 | 119 | print("digraph callgraph {") 120 | dump(root) 121 | print("}") 122 | --------------------------------------------------------------------------------