| #!/usr/bin/env python3 |
| |
| import re |
| import subprocess |
| import sys |
| import traceback |
| import os |
| |
| config = {} |
| |
| # Subvert output buffering. |
| def puts(string): |
| sys.stdout.write(string) |
| sys.stdout.flush() |
| |
| def pretty_print_stack(binary, line): |
| addrs = line.split()[1:] |
| # Addresses are return addresses unless preceded by a '@'. We want the |
| # caller address so line numbers are more intuitive. Thus we subtract 1 |
| # from the address to get the call code. |
| for i in range(len(addrs)): |
| addr = addrs[i] |
| if addr.startswith('@'): |
| addrs[i] = addr[1:] |
| else: |
| addrs[i] = '%lx' % max((int(addrs[i], 16) - 1), 0) |
| |
| # Output like this: |
| # 0x004002be: start64 at path/to/kvm-unit-tests-repo-worktree/x86/cstart64.S:208 |
| # (inlined by) test_ept_violation at path/to/kvm-unit-tests-repo-worktree/x86/vmx_tests.c:1719 (discriminator 1) |
| cmd = [config.get('ADDR2LINE', 'addr2line'), '-e', binary, '-i', '-f', '--pretty', '--address'] |
| cmd.extend(addrs) |
| |
| p = subprocess.Popen(cmd, stdout=subprocess.PIPE) |
| out, err = p.communicate() |
| if p.returncode != 0: |
| puts(line) |
| return |
| |
| for line in out.splitlines(): |
| m = re.match(rb'(.*) at (.*):(([0-9]+)|\?)([^:]*)', line) |
| if m is None: |
| puts('%s\n' % line) |
| return |
| |
| head, path, maybeline, line, tail = m.groups() |
| path = os.path.relpath(os.path.realpath(path), start=os.path.realpath(os.getcwdb())) |
| puts('%s at %s:%s%s\n' % (head.decode(), path.decode(), maybeline.decode(), tail.decode())) |
| if line: |
| line = int(line) |
| try: |
| lines = open(path).readlines() |
| except IOError: |
| continue |
| if line > 1: |
| puts(' %s\n' % lines[line - 2].rstrip()) |
| puts(' > %s\n' % lines[line - 1].rstrip()) |
| if line < len(lines): |
| puts(' %s\n' % lines[line].rstrip()) |
| |
| def main(): |
| if len(sys.argv) != 2: |
| sys.stderr.write('usage: %s <kernel>\n' % sys.argv[0]) |
| sys.exit(1) |
| |
| binary = sys.argv[1] |
| if binary.endswith('.flat'): |
| binary = binary.replace('.flat', '.elf') |
| elif binary.endswith('.efi'): |
| binary += '.debug' |
| |
| with open("config.mak") as config_file: |
| for line in config_file: |
| name, val = line.partition("=")[::2] |
| config[name.strip()] = val.strip() |
| |
| try: |
| while True: |
| # Subvert input buffering. |
| line = sys.stdin.readline() |
| if line == '': |
| break |
| |
| puts(line) |
| |
| if not line.strip().startswith('STACK:'): |
| continue |
| |
| try: |
| pretty_print_stack(binary, line) |
| except Exception: |
| puts('Error pretty printing stack:\n') |
| puts(traceback.format_exc()) |
| puts('Continuing without pretty printing...\n') |
| while True: |
| puts(line) |
| line = sys.stdin.readline() |
| if line == '': |
| break |
| except: |
| sys.exit(1) |
| |
| if __name__ == '__main__': |
| main() |