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