• 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    bgtu    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
231    # Use hidden visibility on LP64 for any functions beginning with underscores.
232    if pointer_length == 64 and syscall["func"].startswith("__"):
233        stub += '.hidden ' + syscall["func"] + '\n'
234
235    return stub
236
237
238def arm_eabi_genstub(syscall):
239    num_regs = count_arm_param_registers(syscall["params"])
240    if num_regs > 4:
241        return arm_eabi_call_long % syscall
242    return arm_eabi_call_default % syscall
243
244
245def arm64_genstub(syscall):
246    return arm64_call % syscall
247
248
249def riscv64_genstub(syscall):
250    return riscv64_call % syscall
251
252
253def x86_genstub(syscall):
254    result     = syscall_stub_header % syscall
255
256    numparams = count_generic_param_registers(syscall["params"])
257    stack_bias = numparams*4 + 8
258    offset = 0
259    mov_result = ""
260    first_push = True
261    for register in x86_registers[:numparams]:
262        result     += "    pushl   %%%s\n" % register
263        if first_push:
264          result   += "    .cfi_def_cfa_offset 8\n"
265          result   += "    .cfi_rel_offset %s, 0\n" % register
266          first_push = False
267        else:
268          result   += "    .cfi_adjust_cfa_offset 4\n"
269          result   += "    .cfi_rel_offset %s, 0\n" % register
270        mov_result += "    mov     %d(%%esp), %%%s\n" % (stack_bias+offset, register)
271        offset += 4
272
273    result += x86_call_prepare
274    result += mov_result
275    result += x86_call % syscall
276
277    for register in reversed(x86_registers[:numparams]):
278        result += "    popl    %%%s\n" % register
279
280    result += x86_return % syscall
281    return result
282
283
284def x86_genstub_socketcall(syscall):
285    #   %ebx <--- Argument 1 - The call id of the needed vectored
286    #                          syscall (socket, bind, recv, etc)
287    #   %ecx <--- Argument 2 - Pointer to the rest of the arguments
288    #                          from the original function called (socket())
289
290    result = syscall_stub_header % syscall
291
292    # save the regs we need
293    result += "    pushl   %ebx\n"
294    result += "    .cfi_def_cfa_offset 8\n"
295    result += "    .cfi_rel_offset ebx, 0\n"
296    result += "    pushl   %ecx\n"
297    result += "    .cfi_adjust_cfa_offset 4\n"
298    result += "    .cfi_rel_offset ecx, 0\n"
299    stack_bias = 16
300
301    result += x86_call_prepare
302
303    # set the call id (%ebx)
304    result += "    mov     $%d, %%ebx\n" % syscall["socketcall_id"]
305
306    # set the pointer to the rest of the args into %ecx
307    result += "    mov     %esp, %ecx\n"
308    result += "    addl    $%d, %%ecx\n" % (stack_bias)
309
310    # now do the syscall code itself
311    result += x86_call % syscall
312
313    # now restore the saved regs
314    result += "    popl    %ecx\n"
315    result += "    popl    %ebx\n"
316
317    # epilog
318    result += x86_return % syscall
319    return result
320
321
322def x86_64_genstub(syscall):
323    result = syscall_stub_header % syscall
324    num_regs = count_generic_param_registers64(syscall["params"])
325    if (num_regs > 3):
326        # rcx is used as 4th argument. Kernel wants it at r10.
327        result += "    movq    %rcx, %r10\n"
328
329    result += x86_64_call % syscall
330    return result
331
332
333class SysCallsTxtParser:
334    def __init__(self):
335        self.syscalls = []
336        self.lineno = 0
337        self.errors = False
338
339    def E(self, msg):
340        print("%d: %s" % (self.lineno, msg))
341        self.errors = True
342
343    def parse_line(self, line):
344        """ parse a syscall spec line.
345
346        line processing, format is
347           return type    func_name[|alias_list][:syscall_name[:socketcall_id]] ( [paramlist] ) architecture_list
348        """
349        pos_lparen = line.find('(')
350        E          = self.E
351        if pos_lparen < 0:
352            E("missing left parenthesis in '%s'" % line)
353            return
354
355        pos_rparen = line.rfind(')')
356        if pos_rparen < 0 or pos_rparen <= pos_lparen:
357            E("missing or misplaced right parenthesis in '%s'" % line)
358            return
359
360        return_type = line[:pos_lparen].strip().split()
361        if len(return_type) < 2:
362            E("missing return type in '%s'" % line)
363            return
364
365        syscall_func = return_type[-1]
366        return_type  = ' '.join(return_type[:-1])
367        socketcall_id = -1
368
369        pos_colon = syscall_func.find(':')
370        if pos_colon < 0:
371            syscall_name = syscall_func
372        else:
373            if pos_colon == 0 or pos_colon+1 >= len(syscall_func):
374                E("misplaced colon in '%s'" % line)
375                return
376
377            # now find if there is a socketcall_id for a dispatch-type syscall
378            # after the optional 2nd colon
379            pos_colon2 = syscall_func.find(':', pos_colon + 1)
380            if pos_colon2 < 0:
381                syscall_name = syscall_func[pos_colon+1:]
382                syscall_func = syscall_func[:pos_colon]
383            else:
384                if pos_colon2+1 >= len(syscall_func):
385                    E("misplaced colon2 in '%s'" % line)
386                    return
387                syscall_name = syscall_func[(pos_colon+1):pos_colon2]
388                socketcall_id = int(syscall_func[pos_colon2+1:])
389                syscall_func = syscall_func[:pos_colon]
390
391        alias_delim = syscall_func.find('|')
392        if alias_delim > 0:
393            alias_list = syscall_func[alias_delim+1:].strip()
394            syscall_func = syscall_func[:alias_delim]
395            alias_delim = syscall_name.find('|')
396            if alias_delim > 0:
397                syscall_name = syscall_name[:alias_delim]
398            syscall_aliases = alias_list.split(',')
399        else:
400            syscall_aliases = []
401
402        if pos_rparen > pos_lparen+1:
403            syscall_params = line[pos_lparen+1:pos_rparen].split(',')
404            params         = ','.join(syscall_params)
405        else:
406            syscall_params = []
407            params         = "void"
408
409        t = {
410              "name"    : syscall_name,
411              "func"    : syscall_func,
412              "aliases" : syscall_aliases,
413              "params"  : syscall_params,
414              "decl"    : "%-15s  %s (%s);" % (return_type, syscall_func, params),
415              "socketcall_id" : socketcall_id
416        }
417
418        # Parse the architecture list.
419        arch_list = line[pos_rparen+1:].strip()
420        if arch_list == "all":
421            for arch in SupportedArchitectures:
422                t[arch] = True
423        else:
424            for arch in arch_list.split(','):
425                if arch == "lp32":
426                    for arch in SupportedArchitectures:
427                        if "64" not in arch:
428                          t[arch] = True
429                elif arch == "lp64":
430                    for arch in SupportedArchitectures:
431                        if "64" in arch:
432                            t[arch] = True
433                elif arch in SupportedArchitectures:
434                    t[arch] = True
435                else:
436                    E("invalid syscall architecture '%s' in '%s'" % (arch, line))
437                    return
438
439        self.syscalls.append(t)
440
441    def parse_open_file(self, fp):
442        for line in fp:
443            self.lineno += 1
444            line = line.strip()
445            if not line: continue
446            if line[0] == '#': continue
447            self.parse_line(line)
448        if self.errors:
449            sys.exit(1)
450
451    def parse_file(self, file_path):
452        with open(file_path) as fp:
453            self.parse_open_file(fp)
454
455
456def main(arch, syscall_file):
457    parser = SysCallsTxtParser()
458    parser.parse_file(syscall_file)
459
460    for syscall in parser.syscalls:
461        syscall["__NR_name"] = make__NR_name(syscall["name"])
462
463        if "arm" in syscall:
464            syscall["asm-arm"] = add_footer(32, arm_eabi_genstub(syscall), syscall)
465
466        if "arm64" in syscall:
467            syscall["asm-arm64"] = add_footer(64, arm64_genstub(syscall), syscall)
468
469        if "riscv64" in syscall:
470            syscall["asm-riscv64"] = add_footer(64, riscv64_genstub(syscall), syscall)
471
472        if "x86" in syscall:
473            if syscall["socketcall_id"] >= 0:
474                syscall["asm-x86"] = add_footer(32, x86_genstub_socketcall(syscall), syscall)
475            else:
476                syscall["asm-x86"] = add_footer(32, x86_genstub(syscall), syscall)
477        elif syscall["socketcall_id"] >= 0:
478            E("socketcall_id for dispatch syscalls is only supported for x86 in '%s'" % t)
479            return
480
481        if "x86_64" in syscall:
482            syscall["asm-x86_64"] = add_footer(64, x86_64_genstub(syscall), syscall)
483
484    print("/* Generated by gensyscalls.py. Do not edit. */\n")
485    print("#include <private/bionic_asm.h>\n")
486    for syscall in parser.syscalls:
487        if ("asm-%s" % arch) in syscall:
488            print(syscall["asm-%s" % arch])
489
490    if arch == 'arm64':
491        print('\nNOTE_GNU_PROPERTY()\n')
492
493if __name__ == "__main__":
494    if len(sys.argv) < 2:
495      print("Usage: gensyscalls.py ARCH SOURCE_FILE")
496      sys.exit(1)
497
498    arch = sys.argv[1]
499    syscall_file = sys.argv[2]
500    main(arch, syscall_file)
501