1#!/usr/bin/env python
2from __future__ import print_function
3'''
4Helper script to print out the raw content of an ELF section.
5Example usages:
6```
7# print out as bits by default
8extract-section.py .text --input-file=foo.o
9```
10```
11# read from stdin and print out in hex
12cat foo.o | extract-section.py -h .text
13```
14This is merely a wrapper around `llvm-readobj` that focuses on the binary
15content as well as providing more formatting options.
16'''
17
18# Unfortunately reading binary from stdin is not so trivial in Python...
19def read_raw_stdin():
20    import sys
21    if sys.version_info >= (3, 0):
22        reading_source = sys.stdin.buffer
23    else:
24        # Windows will always read as string so we need some
25        # special handling
26        if sys.platform == 'win32':
27            import os, msvcrt
28            msvcrt.setformat(sys.stdin.fileno(), os.O_BINARY)
29        reading_source = sys.stdin
30    return reading_source.read()
31
32def get_raw_section_dump(readobj_path, section_name, input_file):
33    import subprocess
34    cmd = [readobj_path, '-elf-output-style=GNU', '--hex-dump={}'.format(section_name),
35            input_file]
36    proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
37
38    if input_file == '-':
39        # From stdin
40        out,_ = proc.communicate(input=read_raw_stdin())
41    else:
42        out,_ = proc.communicate()
43
44    return out.decode('utf-8') if type(out) is not str else out
45
46if __name__ == '__main__':
47    import argparse
48    # The default '-h' (--help) will conflict with our '-h' (hex) format
49    arg_parser = argparse.ArgumentParser(add_help=False)
50    arg_parser.add_argument('--readobj-path', metavar='<executable path>', type=str,
51            help='Path to llvm-readobj')
52    arg_parser.add_argument('--input-file', metavar='<file>', type=str,
53            help='Input object file, or \'-\' to read from stdin')
54    arg_parser.add_argument('section', metavar='<name>', type=str,
55            help='Name of the section to extract')
56    # Output format
57    format_group = arg_parser.add_mutually_exclusive_group()
58    format_group.add_argument('-b', dest='format', action='store_const', const='bits',
59            help='Print out in bits')
60    arg_parser.add_argument('--byte-indicator', action='store_true',
61            help='Whether to print a \'.\' every 8 bits in bits printing mode')
62    format_group.add_argument('-h', dest='format', action='store_const', const='hex',
63            help='Print out in hexadecimal')
64    arg_parser.add_argument('--hex-width', metavar='<# of bytes>', type=int,
65            help='The width (in byte) of every element in hex printing mode')
66
67    arg_parser.add_argument('--help', action='help')
68    arg_parser.set_defaults(format='bits', tool_path='llvm-readobj', input_file='-',
69            byte_indicator=False, hex_width=4)
70    args = arg_parser.parse_args()
71
72    raw_section = get_raw_section_dump(args.tool_path, args.section, args.input_file)
73
74    results = []
75    for line in raw_section.splitlines(False):
76        if line.startswith('Hex dump'):
77            continue
78        parts = line.strip().split(' ')[1:]
79        for part in parts[:4]:
80            # exclude any non-hex dump string
81            try:
82                val = int(part, 16)
83                if args.format == 'bits':
84                    # divided into bytes first
85                    for byte in [(val >> off) & 0xFF for off in (24,16,8,0)]:
86                        for bit in [(byte >> off) & 1 for off in range(7, -1, -1)]:
87                            results.append(str(bit))
88                        if args.byte_indicator:
89                            results.append('.')
90                elif args.format == 'hex':
91                    assert args.hex_width <= 4 and args.hex_width > 0
92                    width_bits = args.hex_width * 8
93                    offsets = [off for off in range(32 - width_bits, -1, -width_bits)]
94                    mask = (1 << width_bits) - 1
95                    format_str = "{:0" + str(args.hex_width * 2) + "x}"
96                    for word in [(val >> i) & mask for i in offsets]:
97                        results.append(format_str.format(word))
98            except:
99                break
100    print(' '.join(results), end='')
101