xref: /aosp_15_r20/trusty/kernel/lib/syscall/stubgen/stubgen.py (revision 344aa361028b423587d4ef3fa52a23d194628137)
1#!/bin/sh
2"." "`dirname $0`/../../../../../trusty/vendor/google/aosp/scripts/envsetup.sh"
3"exec" "$PY3" "$0" "$@"
4
5# Copyright 2013-2017 Google Inc. +All rights reserved.
6#
7# Permission is hereby granted, free of charge, to any person obtaining
8# a copy of this software and associated documentation files
9# (the "Software"), to deal in the Software without restriction,
10# including without limitation the rights to use, copy, modify, merge,
11# publish, distribute, sublicense, and/or sell copies of the Software,
12# and to permit persons to whom the Software is furnished to do so,
13# subject to the following conditions:
14#
15# The above copyright notice and this permission notice shall be
16# included in all copies or substantial portions of the Software.
17#
18# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
21# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
23# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
24# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
26import re
27import sys
28from optparse import OptionParser
29
30"""
31Script to generate syscall stubs from a syscall table file
32with definitions like this:
33
34DEF_SYSCALL(nr_syscall, syscall_name, return_type, nr_args, arg_list...)
35
36For e.g.,
37DEF_SYSCALL(0x3, read, 3, int fd, void *buf, int size)
38DEF_SYSCALL(0x4, write, 4, int fd, void *buf, int size)
39
40FUNCTION(read)
41    ldr     r12, =__NR_read
42    swi     #0
43    bx      lr
44
45FUNCTION(write)
46    ldr     r12, =__NR_write
47    swi     #0
48    bx      lr
49
50Another file with a enumeration of all syscalls is also generated:
51
52#define __NR_read  0x3
53#define __NR_write 0x4
54...
55
56
57Only syscalls with 4 or less arguments are supported.
58"""
59
60copyright_header = """/*
61 * Copyright (c) 2012-2018 LK Trusty Authors. All Rights Reserved.
62 *
63 * Permission is hereby granted, free of charge, to any person obtaining
64 * a copy of this software and associated documentation files
65 * (the "Software"), to deal in the Software without restriction,
66 * including without limitation the rights to use, copy, modify, merge,
67 * publish, distribute, sublicense, and/or sell copies of the Software,
68 * and to permit persons to whom the Software is furnished to do so,
69 * subject to the following conditions:
70 *
71 * The above copyright notice and this permission notice shall be
72 * included in all copies or substantial portions of the Software.
73 *
74 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
75 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
76 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
77 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
78 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
79 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
80 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
81 */
82"""
83
84autogen_header = """
85/* This file is auto-generated. !!! DO NOT EDIT !!! */
86
87"""
88clang_format_off = "/* clang-format off */\n\n"
89
90
91includes_header = "#include <%s>\n"
92
93
94class Architecture:
95    def __init__(self, syscall_stub, footer=""):
96        self.syscall_stub = syscall_stub
97        self.footer = footer
98
99arch_dict = {
100    "arm" : Architecture (
101        syscall_stub = """
102.section .text._trusty_%(sys_fn)s
103.arm
104.balign 4
105FUNCTION(_trusty_%(sys_fn)s)
106    ldr     r12, =__NR_%(sys_fn)s
107    svc     #0
108    bx      lr
109.size _trusty_%(sys_fn)s,.-_trusty_%(sys_fn)s
110"""),
111    "arm64" : Architecture (
112        # Note: for arm64 we're using "mov" to set the syscall number instead of
113        # "ldr" because of an assembler bug. The ldr instruction would always
114        # load from a constant pool instead of encoding the constant in the
115        # instruction. For arm64, "mov" should work for the range of constants
116        # we care about.
117        syscall_stub = """
118.section .text._trusty_%(sys_fn)s
119.balign 4
120FUNCTION(_trusty_%(sys_fn)s)
121    mov     x12, #__NR_%(sys_fn)s
122    svc     #0
123    ret
124.size _trusty_%(sys_fn)s,.-_trusty_%(sys_fn)s
125""",
126        footer = """
127SECTION_GNU_NOTE_PROPERTY_AARCH64_FEATURES(GNU_NOTE_FEATURE_AARCH64_BTI)
128"""),
129    "x86" : Architecture (
130        syscall_stub = """
131.global _trusty_%(sys_fn)s
132.type _trusty_%(sys_fn)s,STT_FUNC
133_trusty_%(sys_fn)s:
134    pushq %%r15
135    movq $__NR_%(sys_fn)s, %%rax
136    movq %%rcx, %%r10
137    movq %%rsp, %%r15
138    syscall
139    movq %%r15, %%rsp
140    popq %%r15
141    ret
142.size _trusty_%(sys_fn)s,.-_trusty_%(sys_fn)s
143"""),
144}
145
146syscall_define = "#define __NR_%(sys_fn)s\t\t%(sys_nr)s\n"
147
148syscall_proto = "%(sys_rt)s _trusty_%(sys_fn)s(%(sys_args)s);\n"
149
150asm_ifdef = "\n#ifndef ASSEMBLY\n"
151asm_endif = "\n#endif\n"
152
153beg_cdecls = "\n__BEGIN_CDECLS\n"
154end_cdecls = "\n__END_CDECLS\n"
155
156syscall_def = "DEF_SYSCALL"
157
158syscall_pat = (
159    r'DEF_SYSCALL\s*\('
160    r'\s*(?P<sys_nr>\d+|0x[\da-fA-F]+)\s*,'      # syscall nr
161    r'\s*(?P<sys_fn>\w+)\s*,'                    # syscall name
162    r'\s*(?P<sys_rt>[\w*\s]+)\s*,'               # return type
163    r'\s*(?P<sys_nr_args>\d+)\s*'                # nr ags
164    r'('
165    r'\)\s*$|'                                   # empty arg list or
166    r',\s*(?P<sys_args>[\w,*\s]+)'               # arg list
167    r'\)\s*$'
168    r')')
169
170syscall_re = re.compile(syscall_pat)
171
172syscall_rust_proto = '    pub fn _trusty_%(sys_fn)s(%(rust_args)s) -> %(rust_rt)s;\n'
173beg_rust = 'extern "C" {\n'
174end_rust = '}\n'
175
176def fatal_parse_error(line, err_str):
177    sys.stderr.write("Error processing line %r:\n%s\n" % (line, err_str))
178    sys.exit(2)
179
180
181BUILTIN_TYPES = set(['char', 'int', 'long', 'void'])
182for i in [8, 16, 32, 64]:
183    BUILTIN_TYPES.add('int%d_t' % i)
184    BUILTIN_TYPES.add('uint%d_t' % i)
185
186def reformat_c_to_rust(arg):
187    """Reformat a C-style argument into corresponding Rust argument
188
189    Raises:
190        NotImplementedError: If argument type was too complex to reformat.
191    """
192    m = re.match(r"(const )?(struct )?(.*?)\s*( ?\* ?)?$", arg)
193    is_const = m.group(1) is not None
194    ty = m.group(3)
195    is_ptr = m.group(4) is not None
196    rust_arg = ''
197    if '*' in ty:
198        raise NotImplementedError("Rust arg reformatting needs to be extended "
199                                  f"to handle double indirection in arg: {arg}")
200    if is_ptr:
201        rust_arg += '*%s ' % ('const' if is_const else 'mut')
202    rust_arg += ty
203    return rust_arg
204
205def parse_check_def(line, struct_types):
206    """
207    Parse a DEF_SYSCALL line and check for errors
208    Returns various components from the line.
209    """
210
211    m = syscall_re.match(line)
212    if m is None:
213        fatal_parse_error(line, "Line did not match expected pattern.")
214    gd = m.groupdict()
215
216    sys_nr_args = int(gd['sys_nr_args'])
217    sys_args = gd['sys_args']
218    sys_args_list = re.split(r'\s*,\s*', sys_args) if sys_args else []
219
220    if sys_nr_args > 4:
221        fatal_parse_error(line, "Only syscalls with up to 4 arguments are "
222                          "supported.")
223
224    if sys_nr_args != len(sys_args_list):
225        fatal_parse_error(line, "Expected %d syscall arguments, got %d." %
226                          (sys_nr_args, len(sys_args_list)))
227
228    # Find struct types in the arguments.
229    for arg in sys_args_list:
230        # Remove arg name.
231        arg = re.sub(r"\s*\w+$", "", arg)
232        # Remove trailing pointer.
233        arg = re.sub(r"\s*\*$", "", arg)
234        # Remove initial const.
235        arg = re.sub(r"^const\s+", "", arg)
236        # Ignore the type if it's obviously not a struct.
237        if arg in BUILTIN_TYPES:
238            continue
239        # Require explicit struct declarations, because forward declaring
240        # typedefs is tricky.
241        if not arg.startswith("struct "):
242            fatal_parse_error(line, "Not an integer type or explicit struct "
243                              "type: %r. Don't use typedefs." % arg)
244        struct_types.add(arg)
245
246    # Reformat arguments into Rust syntax
247    rust_args = []
248    try:
249        for arg in sys_args_list:
250            m = re.match(r"(.*?)(\w+)$", arg)
251            ty = m.group(1)
252            name = m.group(2)
253            rust_args.append('%s: %s' % (name, reformat_c_to_rust(ty)))
254        gd['rust_args'] = ', '.join(rust_args)
255        gd['rust_rt'] = reformat_c_to_rust(gd['sys_rt'])
256    except NotImplementedError as err:
257        # reformat_c_to_rust failed to convert argument or return type
258        fatal_parse_error(line, err)
259
260    # In C, a forward declaration with an empty list of arguments has an
261    # unknown number of arguments. Set it to 'void' to declare there are
262    # zero arguments.
263    if sys_nr_args == 0:
264        gd['sys_args'] = 'void'
265
266    return gd
267
268
269def process_table(table_file, std_file, stubs_file, rust_file, verify, arch):
270    """
271    Process a syscall table and generate:
272    1. A sycall stubs file
273    2. A trusty_std.h header file with syscall definitions
274       and function prototypes
275    """
276    define_lines = ""
277    proto_lines = "\n"
278    stub_lines = ""
279    rust_lines = ""
280
281    struct_types = set()
282
283    tbl = open(table_file, "r")
284    for line in tbl:
285        line = line.strip()
286
287        # skip all lines that don't start with a syscall definition
288        # multi-line defintions are not supported.
289        if not line.startswith(syscall_def):
290            continue
291
292        params = parse_check_def(line, struct_types)
293
294        if not verify:
295            define_lines += syscall_define % params
296            proto_lines += syscall_proto % params
297            stub_lines += arch.syscall_stub % params
298            rust_lines += syscall_rust_proto % params
299
300
301    tbl.close()
302
303    if verify:
304        return
305
306    if std_file is not None:
307        with open(std_file, "w") as std:
308            std.writelines(copyright_header + autogen_header)
309            std.writelines(clang_format_off)
310            std.writelines(define_lines + asm_ifdef)
311            std.writelines("\n")
312            std.writelines(includes_header % "lk/compiler.h")
313            std.writelines(includes_header % "stdint.h")
314            std.writelines(beg_cdecls)
315            # Forward declare the struct types.
316            std.writelines("\n")
317            std.writelines([t + ";\n" for t in sorted(struct_types)])
318            std.writelines(proto_lines + end_cdecls + asm_endif)
319
320    if stubs_file is not None:
321        with open(stubs_file, "w") as stubs:
322            stubs.writelines(copyright_header + autogen_header)
323            stubs.writelines(includes_header % "lk/asm.h")
324            stubs.writelines(includes_header % "trusty_syscalls.h")
325            stubs.writelines(stub_lines)
326            stubs.writelines(arch.footer)
327
328    if rust_file is not None:
329        with open(rust_file, "w") as rust:
330            rust.writelines(copyright_header + autogen_header)
331            rust.writelines(beg_rust)
332            rust.writelines(rust_lines)
333            rust.writelines(end_rust)
334
335
336def main():
337
338    usage = "usage:  %prog [options] <syscall-table>"
339
340    op = OptionParser(usage=usage)
341    op.add_option("-v", "--verify", action="store_true",
342            dest="verify", default=False,
343            help="Check syscall table. Do not generate any files.")
344    op.add_option("-d", "--std-header", type="string",
345            dest="std_file", default=None,
346            help="path to syscall defintions header file.")
347    op.add_option("-s", "--stubs-file", type="string",
348            dest="stub_file", default=None,
349            help="path to syscall assembly stubs file.")
350    op.add_option("-r", "--rust-file", type="string",
351            dest="rust_file", default=None,
352            help="path to rust declarations file")
353    op.add_option("-a", "--arch", type="string",
354            dest="arch", default="arm",
355            help="arch of stub assembly files: " + str(arch_dict.keys()))
356
357    (opts, args) = op.parse_args()
358
359    if len(args) == 0:
360        op.print_help()
361        sys.exit(1)
362
363    if not opts.verify:
364        if opts.std_file is None and opts.stub_file is None:
365            op.print_help()
366            sys.exit(1)
367
368    process_table(args[0], opts.std_file, opts.stub_file, opts.rust_file,
369                  opts.verify, arch_dict[opts.arch])
370
371
372if __name__ == '__main__':
373    main()
374