xref: /kvm-unit-tests/scripts/pretty_print_stacks.py (revision 3c1736b1344b9831f17fbd64f95ea89c279564c6)
1#!/usr/bin/env python3
2
3import re
4import subprocess
5import sys
6import traceback
7import os
8
9config = {}
10
11# Subvert output buffering.
12def puts(string):
13    sys.stdout.write(string)
14    sys.stdout.flush()
15
16def pretty_print_stack(binary, line):
17    addrs = line.split()[1:]
18    # Addresses are return addresses unless preceded by a '@'. We want the
19    # caller address so line numbers are more intuitive. Thus we subtract 1
20    # from the address to get the call code.
21    for i in range(len(addrs)):
22        addr = addrs[i]
23        if addr.startswith('@'):
24            addrs[i] = addr[1:]
25        else:
26            addrs[i] = '%lx' % max((int(addrs[i], 16) - 1), 0)
27
28    # Output like this:
29    #        0x004002be: start64 at path/to/kvm-unit-tests-repo-worktree/x86/cstart64.S:208
30    #         (inlined by) test_ept_violation at path/to/kvm-unit-tests-repo-worktree/x86/vmx_tests.c:1719 (discriminator 1)
31    cmd = [config.get('ADDR2LINE', 'addr2line'), '-e', binary, '-i', '-f', '--pretty', '--address']
32    cmd.extend(addrs)
33
34    p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
35    out, err = p.communicate()
36    if p.returncode != 0:
37        puts(line)
38        return
39
40    for line in out.splitlines():
41        m = re.match(rb'(.*) at (.*):(([0-9]+)|\?)([^:]*)', line)
42        if m is None:
43            puts('%s\n' % line)
44            return
45
46        head, path, maybeline, line, tail = m.groups()
47        path = os.path.relpath(os.path.realpath(path), start=os.path.realpath(os.getcwdb()))
48        puts('%s at %s:%s%s\n' % (head.decode(), path.decode(), maybeline.decode(), tail.decode()))
49        if line:
50            line = int(line)
51            try:
52                lines = open(path).readlines()
53            except IOError:
54                continue
55            if line > 1:
56                puts('        %s\n' % lines[line - 2].rstrip())
57            puts('      > %s\n' % lines[line - 1].rstrip())
58            if line < len(lines):
59                puts('        %s\n' % lines[line].rstrip())
60
61def main():
62    if len(sys.argv) != 2:
63        sys.stderr.write('usage: %s <kernel>\n' % sys.argv[0])
64        sys.exit(1)
65
66    binary = sys.argv[1]
67    if binary.endswith('.flat'):
68        binary = binary.replace('.flat', '.elf')
69    elif binary.endswith('.efi'):
70        binary += '.debug'
71
72    with open("config.mak") as config_file:
73        for line in config_file:
74            name, val = line.partition("=")[::2]
75            config[name.strip()] = val.strip()
76
77    try:
78        while True:
79            # Subvert input buffering.
80            line = sys.stdin.readline()
81            if line == '':
82                break
83
84            puts(line)
85
86            if not line.strip().startswith('STACK:'):
87                continue
88
89            try:
90                pretty_print_stack(binary, line)
91            except Exception:
92                puts('Error pretty printing stack:\n')
93                puts(traceback.format_exc())
94                puts('Continuing without pretty printing...\n')
95                while True:
96                    puts(line)
97                    line = sys.stdin.readline()
98                    if line == '':
99                        break
100    except:
101        sys.exit(1)
102
103if __name__ == '__main__':
104    main()
105