• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#
4# Copyright (C) 2019 The Android Open Source Project
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#      http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17"""Helper tool to generate cross-compiled syscall and constant tables to JSON.
18
19This script takes the LLVM IR of libconstants.gen.c and libsyscalls.gen.c and
20generates the `constants.json` file with that. LLVM IR files are moderately
21architecture-neutral (at least for this case).
22"""
23
24import argparse
25import collections
26import json
27import re
28import sys
29
30_STRING_CONSTANT_RE = re.compile(r'(@[a-zA-Z0-9.]+) = .*c"([^"\\]+)\\00".*')
31_TABLE_ENTRY_RE = re.compile(
32    r'%struct.(?:constant|syscall)_entry\s*{\s*([^}]+)\s*}')
33# This looks something like
34#
35#  i8* getelementptr inbounds ([5 x i8], [5 x i8]* @.str.5, i32 0, i32 0), i32 5
36#
37# For arm-v7a. What we are interested in are the @.str.x and the very last
38# number.
39_TABLE_ENTRY_CONTENTS = re.compile(r'.*?(null|@[a-zA-Z0-9.]+).* (-?\d+)')
40
41ParseResults = collections.namedtuple('ParseResults', ['table_name',
42                                                       'table_entries'])
43
44
45def parse_llvm_ir(ir):
46    """Parses a single LLVM IR file."""
47    string_constants = collections.OrderedDict()
48    table_entries = collections.OrderedDict()
49    table_name = ''
50    for line in ir:
51        string_constant_match = _STRING_CONSTANT_RE.match(line)
52        if string_constant_match:
53            string_constants[string_constant_match.group(
54                1)] = string_constant_match.group(2)
55            continue
56
57        if '@syscall_table' in line or '@constant_table' in line:
58            if '@syscall_table' in line:
59                table_name = 'syscalls'
60            else:
61                table_name = 'constants'
62            for entry in _TABLE_ENTRY_RE.findall(line):
63                groups = _TABLE_ENTRY_CONTENTS.match(entry)
64                if not groups:
65                    raise ValueError('Failed to parse table entry %r' % entry)
66                name, value = groups.groups()
67                if name == 'null':
68                    # This is the end-of-table marker.
69                    break
70                table_entries[string_constants[name]] = int(value)
71
72    return ParseResults(table_name=table_name, table_entries=table_entries)
73
74
75def main(argv=None):
76    """Main entrypoint."""
77
78    if argv is None:
79        argv = sys.argv[1:]
80
81    parser = argparse.ArgumentParser(description=__doc__)
82    parser.add_argument('--output',
83                        help='The path of the generated constants.json file.',
84                        type=argparse.FileType('w'),
85                        required=True)
86    parser.add_argument(
87        'llvm_ir_files',
88        help='An LLVM IR file with one of the {constants,syscall} table.',
89        metavar='llvm_ir_file',
90        nargs='+',
91        type=argparse.FileType('r'))
92    opts = parser.parse_args(argv)
93
94    constants_json = {}
95    for ir in opts.llvm_ir_files:
96        parse_results = parse_llvm_ir(ir)
97        constants_json[parse_results.table_name] = parse_results.table_entries
98
99    # Populate the top-level fields.
100    constants_json['arch_nr'] = constants_json['constants']['MINIJAIL_ARCH_NR']
101    constants_json['bits'] = constants_json['constants']['MINIJAIL_ARCH_BITS']
102
103    # It is a bit more complicated to generate the arch_name, since the constants
104    # can only output numeric values. Use a hardcoded mapping instead.
105    if constants_json['arch_nr'] == 0xC000003E:
106        constants_json['arch_name'] = 'x86_64'
107    elif constants_json['arch_nr'] == 0x40000003:
108        constants_json['arch_name'] = 'x86'
109    elif constants_json['arch_nr'] == 0xC00000B7:
110        constants_json['arch_name'] = 'arm64'
111    elif constants_json['arch_nr'] == 0x40000028:
112        constants_json['arch_name'] = 'arm'
113    else:
114        raise ValueError('Unknown architecture: 0x%08X' %
115                         constants_json['arch_nr'])
116
117    json.dump(constants_json, opts.output, indent='  ')
118    return 0
119
120
121if __name__ == '__main__':
122    sys.exit(main(sys.argv[1:]))
123