1#!/usr/bin/env python3
2
3# This tool is used to generate the assembler system call stubs,
4# the header files listing all available system calls, and the
5# makefiles used to build all the stubs.
6
7import atexit
8import filecmp
9import glob
10import re
11import shutil
12import stat
13import string
14import sys
15import tempfile
16
17
18SupportedArchitectures = [ "arm", "arm64", "x86", "x86_64" ]
19
20syscall_stub_header = \
21"""
22ENTRY(%(func)s)
23"""
24
25
26#
27# ARM assembler templates for each syscall stub
28#
29
30arm_eabi_call_default = syscall_stub_header + """\
31    mov     ip, r7
32    .cfi_register r7, ip
33    ldr     r7, =%(__NR_name)s
34    swi     #0
35    mov     r7, ip
36    .cfi_restore r7
37    cmn     r0, #(MAX_ERRNO + 1)
38    bxls    lr
39    neg     r0, r0
40    b       __set_errno_internal
41END(%(func)s)
42"""
43
44arm_eabi_call_long = syscall_stub_header + """\
45    mov     ip, sp
46    stmfd   sp!, {r4, r5, r6, r7}
47    .cfi_def_cfa_offset 16
48    .cfi_rel_offset r4, 0
49    .cfi_rel_offset r5, 4
50    .cfi_rel_offset r6, 8
51    .cfi_rel_offset r7, 12
52    ldmfd   ip, {r4, r5, r6}
53    ldr     r7, =%(__NR_name)s
54    swi     #0
55    ldmfd   sp!, {r4, r5, r6, r7}
56    .cfi_def_cfa_offset 0
57    cmn     r0, #(MAX_ERRNO + 1)
58    bxls    lr
59    neg     r0, r0
60    b       __set_errno_internal
61END(%(func)s)
62"""
63
64
65#
66# Arm64 assembler template for each syscall stub
67#
68
69arm64_call = syscall_stub_header + """\
70    mov     x8, %(__NR_name)s
71    svc     #0
72
73    cmn     x0, #(MAX_ERRNO + 1)
74    cneg    x0, x0, hi
75    b.hi    __set_errno_internal
76
77    ret
78END(%(func)s)
79"""
80
81
82#
83# x86 assembler templates for each syscall stub
84#
85
86x86_registers = [ "ebx", "ecx", "edx", "esi", "edi", "ebp" ]
87
88x86_call_prepare = """\
89
90    call    __kernel_syscall
91    pushl   %eax
92    .cfi_adjust_cfa_offset 4
93    .cfi_rel_offset eax, 0
94
95"""
96
97x86_call = """\
98    movl    $%(__NR_name)s, %%eax
99    call    *(%%esp)
100    addl    $4, %%esp
101
102    cmpl    $-MAX_ERRNO, %%eax
103    jb      1f
104    negl    %%eax
105    pushl   %%eax
106    call    __set_errno_internal
107    addl    $4, %%esp
1081:
109"""
110
111x86_return = """\
112    ret
113END(%(func)s)
114"""
115
116
117#
118# x86_64 assembler template for each syscall stub
119#
120
121x86_64_call = """\
122    movl    $%(__NR_name)s, %%eax
123    syscall
124    cmpq    $-MAX_ERRNO, %%rax
125    jb      1f
126    negl    %%eax
127    movl    %%eax, %%edi
128    call    __set_errno_internal
1291:
130    ret
131END(%(func)s)
132"""
133
134
135def param_uses_64bits(param):
136    """Returns True iff a syscall parameter description corresponds
137       to a 64-bit type."""
138    param = param.strip()
139    # First, check that the param type begins with one of the known
140    # 64-bit types.
141    if not ( \
142       param.startswith("int64_t") or param.startswith("uint64_t") or \
143       param.startswith("loff_t") or param.startswith("off64_t") or \
144       param.startswith("long long") or param.startswith("unsigned long long") or
145       param.startswith("signed long long") ):
146           return False
147
148    # Second, check that there is no pointer type here
149    if param.find("*") >= 0:
150            return False
151
152    # Ok
153    return True
154
155
156def count_arm_param_registers(params):
157    """This function is used to count the number of register used
158       to pass parameters when invoking an ARM system call.
159       This is because the ARM EABI mandates that 64-bit quantities
160       must be passed in an even+odd register pair. So, for example,
161       something like:
162
163             foo(int fd, off64_t pos)
164
165       would actually need 4 registers:
166             r0 -> int
167             r1 -> unused
168             r2-r3 -> pos
169   """
170    count = 0
171    for param in params:
172        if param_uses_64bits(param):
173            if (count & 1) != 0:
174                count += 1
175            count += 2
176        else:
177            count += 1
178    return count
179
180
181def count_generic_param_registers(params):
182    count = 0
183    for param in params:
184        if param_uses_64bits(param):
185            count += 2
186        else:
187            count += 1
188    return count
189
190
191def count_generic_param_registers64(params):
192    count = 0
193    for param in params:
194        count += 1
195    return count
196
197
198# This lets us support regular system calls like __NR_write and also weird
199# ones like __ARM_NR_cacheflush, where the NR doesn't come at the start.
200def make__NR_name(name):
201    if name.startswith("__ARM_NR_"):
202        return name
203    else:
204        return "__NR_%s" % (name)
205
206
207def add_footer(pointer_length, stub, syscall):
208    # Add any aliases for this syscall.
209    aliases = syscall["aliases"]
210    for alias in aliases:
211        stub += "\nALIAS_SYMBOL(%s, %s)\n" % (alias, syscall["func"])
212
213    # Use hidden visibility on LP64 for any functions beginning with underscores.
214    if pointer_length == 64 and syscall["func"].startswith("__"):
215        stub += '.hidden ' + syscall["func"] + '\n'
216
217    return stub
218
219
220def arm_eabi_genstub(syscall):
221    num_regs = count_arm_param_registers(syscall["params"])
222    if num_regs > 4:
223        return arm_eabi_call_long % syscall
224    return arm_eabi_call_default % syscall
225
226
227def arm64_genstub(syscall):
228    return arm64_call % syscall
229
230
231def x86_genstub(syscall):
232    result     = syscall_stub_header % syscall
233
234    numparams = count_generic_param_registers(syscall["params"])
235    stack_bias = numparams*4 + 8
236    offset = 0
237    mov_result = ""
238    first_push = True
239    for register in x86_registers[:numparams]:
240        result     += "    pushl   %%%s\n" % register
241        if first_push:
242          result   += "    .cfi_def_cfa_offset 8\n"
243          result   += "    .cfi_rel_offset %s, 0\n" % register
244          first_push = False
245        else:
246          result   += "    .cfi_adjust_cfa_offset 4\n"
247          result   += "    .cfi_rel_offset %s, 0\n" % register
248        mov_result += "    mov     %d(%%esp), %%%s\n" % (stack_bias+offset, register)
249        offset += 4
250
251    result += x86_call_prepare
252    result += mov_result
253    result += x86_call % syscall
254
255    for register in reversed(x86_registers[:numparams]):
256        result += "    popl    %%%s\n" % register
257
258    result += x86_return % syscall
259    return result
260
261
262def x86_genstub_socketcall(syscall):
263    #   %ebx <--- Argument 1 - The call id of the needed vectored
264    #                          syscall (socket, bind, recv, etc)
265    #   %ecx <--- Argument 2 - Pointer to the rest of the arguments
266    #                          from the original function called (socket())
267
268    result = syscall_stub_header % syscall
269
270    # save the regs we need
271    result += "    pushl   %ebx\n"
272    result += "    .cfi_def_cfa_offset 8\n"
273    result += "    .cfi_rel_offset ebx, 0\n"
274    result += "    pushl   %ecx\n"
275    result += "    .cfi_adjust_cfa_offset 4\n"
276    result += "    .cfi_rel_offset ecx, 0\n"
277    stack_bias = 16
278
279    result += x86_call_prepare
280
281    # set the call id (%ebx)
282    result += "    mov     $%d, %%ebx\n" % syscall["socketcall_id"]
283
284    # set the pointer to the rest of the args into %ecx
285    result += "    mov     %esp, %ecx\n"
286    result += "    addl    $%d, %%ecx\n" % (stack_bias)
287
288    # now do the syscall code itself
289    result += x86_call % syscall
290
291    # now restore the saved regs
292    result += "    popl    %ecx\n"
293    result += "    popl    %ebx\n"
294
295    # epilog
296    result += x86_return % syscall
297    return result
298
299
300def x86_64_genstub(syscall):
301    result = syscall_stub_header % syscall
302    num_regs = count_generic_param_registers64(syscall["params"])
303    if (num_regs > 3):
304        # rcx is used as 4th argument. Kernel wants it at r10.
305        result += "    movq    %rcx, %r10\n"
306
307    result += x86_64_call % syscall
308    return result
309
310
311class SysCallsTxtParser:
312    def __init__(self):
313        self.syscalls = []
314        self.lineno   = 0
315
316    def E(self, msg):
317        print("%d: %s" % (self.lineno, msg))
318
319    def parse_line(self, line):
320        """ parse a syscall spec line.
321
322        line processing, format is
323           return type    func_name[|alias_list][:syscall_name[:socketcall_id]] ( [paramlist] ) architecture_list
324        """
325        pos_lparen = line.find('(')
326        E          = self.E
327        if pos_lparen < 0:
328            E("missing left parenthesis in '%s'" % line)
329            return
330
331        pos_rparen = line.rfind(')')
332        if pos_rparen < 0 or pos_rparen <= pos_lparen:
333            E("missing or misplaced right parenthesis in '%s'" % line)
334            return
335
336        return_type = line[:pos_lparen].strip().split()
337        if len(return_type) < 2:
338            E("missing return type in '%s'" % line)
339            return
340
341        syscall_func = return_type[-1]
342        return_type  = ' '.join(return_type[:-1])
343        socketcall_id = -1
344
345        pos_colon = syscall_func.find(':')
346        if pos_colon < 0:
347            syscall_name = syscall_func
348        else:
349            if pos_colon == 0 or pos_colon+1 >= len(syscall_func):
350                E("misplaced colon in '%s'" % line)
351                return
352
353            # now find if there is a socketcall_id for a dispatch-type syscall
354            # after the optional 2nd colon
355            pos_colon2 = syscall_func.find(':', pos_colon + 1)
356            if pos_colon2 < 0:
357                syscall_name = syscall_func[pos_colon+1:]
358                syscall_func = syscall_func[:pos_colon]
359            else:
360                if pos_colon2+1 >= len(syscall_func):
361                    E("misplaced colon2 in '%s'" % line)
362                    return
363                syscall_name = syscall_func[(pos_colon+1):pos_colon2]
364                socketcall_id = int(syscall_func[pos_colon2+1:])
365                syscall_func = syscall_func[:pos_colon]
366
367        alias_delim = syscall_func.find('|')
368        if alias_delim > 0:
369            alias_list = syscall_func[alias_delim+1:].strip()
370            syscall_func = syscall_func[:alias_delim]
371            alias_delim = syscall_name.find('|')
372            if alias_delim > 0:
373                syscall_name = syscall_name[:alias_delim]
374            syscall_aliases = alias_list.split(',')
375        else:
376            syscall_aliases = []
377
378        if pos_rparen > pos_lparen+1:
379            syscall_params = line[pos_lparen+1:pos_rparen].split(',')
380            params         = ','.join(syscall_params)
381        else:
382            syscall_params = []
383            params         = "void"
384
385        t = {
386              "name"    : syscall_name,
387              "func"    : syscall_func,
388              "aliases" : syscall_aliases,
389              "params"  : syscall_params,
390              "decl"    : "%-15s  %s (%s);" % (return_type, syscall_func, params),
391              "socketcall_id" : socketcall_id
392        }
393
394        # Parse the architecture list.
395        arch_list = line[pos_rparen+1:].strip()
396        if arch_list == "all":
397            for arch in SupportedArchitectures:
398                t[arch] = True
399        else:
400            for arch in arch_list.split(','):
401                if arch == "lp32":
402                    for arch in SupportedArchitectures:
403                        if "64" not in arch:
404                          t[arch] = True
405                elif arch == "lp64":
406                    for arch in SupportedArchitectures:
407                        if "64" in arch:
408                            t[arch] = True
409                elif arch in SupportedArchitectures:
410                    t[arch] = True
411                else:
412                    E("invalid syscall architecture '%s' in '%s'" % (arch, line))
413                    return
414
415        self.syscalls.append(t)
416
417    def parse_open_file(self, fp):
418        for line in fp:
419            self.lineno += 1
420            line = line.strip()
421            if not line: continue
422            if line[0] == '#': continue
423            self.parse_line(line)
424
425    def parse_file(self, file_path):
426        with open(file_path) as fp:
427            self.parse_open_file(fp)
428
429
430def main(arch, syscall_file):
431    parser = SysCallsTxtParser()
432    parser.parse_file(syscall_file)
433
434    for syscall in parser.syscalls:
435        syscall["__NR_name"] = make__NR_name(syscall["name"])
436
437        if "arm" in syscall:
438            syscall["asm-arm"] = add_footer(32, arm_eabi_genstub(syscall), syscall)
439
440        if "arm64" in syscall:
441            syscall["asm-arm64"] = add_footer(64, arm64_genstub(syscall), syscall)
442
443        if "x86" in syscall:
444            if syscall["socketcall_id"] >= 0:
445                syscall["asm-x86"] = add_footer(32, x86_genstub_socketcall(syscall), syscall)
446            else:
447                syscall["asm-x86"] = add_footer(32, x86_genstub(syscall), syscall)
448        elif syscall["socketcall_id"] >= 0:
449            E("socketcall_id for dispatch syscalls is only supported for x86 in '%s'" % t)
450            return
451
452        if "x86_64" in syscall:
453            syscall["asm-x86_64"] = add_footer(64, x86_64_genstub(syscall), syscall)
454
455    print("/* Generated by gensyscalls.py. Do not edit. */\n")
456    print("#include <private/bionic_asm.h>\n")
457    for syscall in parser.syscalls:
458        if ("asm-%s" % arch) in syscall:
459            print(syscall["asm-%s" % arch])
460
461    if arch == 'arm64':
462        print('\nNOTE_GNU_PROPERTY()\n')
463
464if __name__ == "__main__":
465    if len(sys.argv) < 2:
466      print("Usage: gensyscalls.py ARCH SOURCE_FILE")
467      sys.exit(1)
468
469    arch = sys.argv[1]
470    syscall_file = sys.argv[2]
471    main(arch, syscall_file)
472