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