1#!/usr/bin/python
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 commands
9import filecmp
10import glob
11import logging
12import os.path
13import re
14import shutil
15import stat
16import string
17import sys
18import tempfile
19
20
21all_arches = [ "arm", "arm64", "mips", "mips64", "x86", "x86_64" ]
22
23
24# temp directory where we store all intermediate files
25bionic_temp = tempfile.mkdtemp(prefix="bionic_gensyscalls");
26# Make sure the directory is deleted when the script exits.
27atexit.register(shutil.rmtree, bionic_temp)
28
29bionic_libc_root = os.path.join(os.environ["ANDROID_BUILD_TOP"], "bionic/libc")
30
31warning = "Generated by gensyscalls.py. Do not edit."
32
33DRY_RUN = False
34
35def make_dir(path):
36    path = os.path.abspath(path)
37    if not os.path.exists(path):
38        parent = os.path.dirname(path)
39        if parent:
40            make_dir(parent)
41        os.mkdir(path)
42
43
44def create_file(relpath):
45    full_path = os.path.join(bionic_temp, relpath)
46    dir = os.path.dirname(full_path)
47    make_dir(dir)
48    return open(full_path, "w")
49
50
51syscall_stub_header = "/* " + warning + " */\n" + \
52"""
53#include <private/bionic_asm.h>
54
55ENTRY(%(func)s)
56"""
57
58
59#
60# ARM assembler templates for each syscall stub
61#
62
63arm_eabi_call_default = syscall_stub_header + """\
64    mov     ip, r7
65    .cfi_register r7, ip
66    ldr     r7, =%(__NR_name)s
67    swi     #0
68    mov     r7, ip
69    .cfi_restore r7
70    cmn     r0, #(MAX_ERRNO + 1)
71    bxls    lr
72    neg     r0, r0
73    b       __set_errno_internal
74END(%(func)s)
75"""
76
77arm_eabi_call_long = syscall_stub_header + """\
78    mov     ip, sp
79    stmfd   sp!, {r4, r5, r6, r7}
80    .cfi_def_cfa_offset 16
81    .cfi_rel_offset r4, 0
82    .cfi_rel_offset r5, 4
83    .cfi_rel_offset r6, 8
84    .cfi_rel_offset r7, 12
85    ldmfd   ip, {r4, r5, r6}
86    ldr     r7, =%(__NR_name)s
87    swi     #0
88    ldmfd   sp!, {r4, r5, r6, r7}
89    .cfi_def_cfa_offset 0
90    cmn     r0, #(MAX_ERRNO + 1)
91    bxls    lr
92    neg     r0, r0
93    b       __set_errno_internal
94END(%(func)s)
95"""
96
97
98#
99# Arm64 assembler templates for each syscall stub
100#
101
102arm64_call = syscall_stub_header + """\
103    mov     x8, %(__NR_name)s
104    svc     #0
105
106    cmn     x0, #(MAX_ERRNO + 1)
107    cneg    x0, x0, hi
108    b.hi    __set_errno_internal
109
110    ret
111END(%(func)s)
112"""
113
114
115#
116# MIPS assembler templates for each syscall stub
117#
118
119mips_call = syscall_stub_header + """\
120    .set noreorder
121    .cpload t9
122    li v0, %(__NR_name)s
123    syscall
124    bnez a3, 1f
125    move a0, v0
126    j ra
127    nop
1281:
129    la t9,__set_errno_internal
130    j t9
131    nop
132    .set reorder
133END(%(func)s)
134"""
135
136
137#
138# MIPS64 assembler templates for each syscall stub
139#
140
141mips64_call = syscall_stub_header + """\
142    .set push
143    .set noreorder
144    li v0, %(__NR_name)s
145    syscall
146    bnez a3, 1f
147    move a0, v0
148    j ra
149    nop
1501:
151    move t0, ra
152    bal     2f
153    nop
1542:
155    .cpsetup ra, t1, 2b
156    LA t9,__set_errno_internal
157    .cpreturn
158    j t9
159    move ra, t0
160    .set pop
161END(%(func)s)
162"""
163
164
165#
166# x86 assembler templates for each syscall stub
167#
168
169x86_registers = [ "ebx", "ecx", "edx", "esi", "edi", "ebp" ]
170
171x86_call_prepare = """\
172
173    call    __kernel_syscall
174    pushl   %eax
175    .cfi_adjust_cfa_offset 4
176    .cfi_rel_offset eax, 0
177
178"""
179
180x86_call = """\
181    movl    $%(__NR_name)s, %%eax
182    call    *(%%esp)
183    addl    $4, %%esp
184
185    cmpl    $-MAX_ERRNO, %%eax
186    jb      1f
187    negl    %%eax
188    pushl   %%eax
189    call    __set_errno_internal
190    addl    $4, %%esp
1911:
192"""
193
194x86_return = """\
195    ret
196END(%(func)s)
197"""
198
199
200#
201# x86_64 assembler templates for each syscall stub
202#
203
204x86_64_call = """\
205    movl    $%(__NR_name)s, %%eax
206    syscall
207    cmpq    $-MAX_ERRNO, %%rax
208    jb      1f
209    negl    %%eax
210    movl    %%eax, %%edi
211    call    __set_errno_internal
2121:
213    ret
214END(%(func)s)
215"""
216
217
218def param_uses_64bits(param):
219    """Returns True iff a syscall parameter description corresponds
220       to a 64-bit type."""
221    param = param.strip()
222    # First, check that the param type begins with one of the known
223    # 64-bit types.
224    if not ( \
225       param.startswith("int64_t") or param.startswith("uint64_t") or \
226       param.startswith("loff_t") or param.startswith("off64_t") or \
227       param.startswith("long long") or param.startswith("unsigned long long") or
228       param.startswith("signed long long") ):
229           return False
230
231    # Second, check that there is no pointer type here
232    if param.find("*") >= 0:
233            return False
234
235    # Ok
236    return True
237
238
239def count_arm_param_registers(params):
240    """This function is used to count the number of register used
241       to pass parameters when invoking an ARM system call.
242       This is because the ARM EABI mandates that 64-bit quantities
243       must be passed in an even+odd register pair. So, for example,
244       something like:
245
246             foo(int fd, off64_t pos)
247
248       would actually need 4 registers:
249             r0 -> int
250             r1 -> unused
251             r2-r3 -> pos
252   """
253    count = 0
254    for param in params:
255        if param_uses_64bits(param):
256            if (count & 1) != 0:
257                count += 1
258            count += 2
259        else:
260            count += 1
261    return count
262
263
264def count_generic_param_registers(params):
265    count = 0
266    for param in params:
267        if param_uses_64bits(param):
268            count += 2
269        else:
270            count += 1
271    return count
272
273
274def count_generic_param_registers64(params):
275    count = 0
276    for param in params:
277        count += 1
278    return count
279
280
281# This lets us support regular system calls like __NR_write and also weird
282# ones like __ARM_NR_cacheflush, where the NR doesn't come at the start.
283def make__NR_name(name):
284    if name.startswith("__ARM_NR_"):
285        return name
286    else:
287        return "__NR_%s" % (name)
288
289
290def add_footer(pointer_length, stub, syscall):
291    # Add any aliases for this syscall.
292    aliases = syscall["aliases"]
293    for alias in aliases:
294        stub += "\nALIAS_SYMBOL(%s, %s)\n" % (alias, syscall["func"])
295
296    # Use hidden visibility on LP64 for any functions beginning with underscores.
297    # Force hidden visibility for any functions which begin with 3 underscores
298    if (pointer_length == 64 and syscall["func"].startswith("__")) or syscall["func"].startswith("___"):
299        stub += '.hidden ' + syscall["func"] + '\n'
300
301    return stub
302
303
304def arm_eabi_genstub(syscall):
305    num_regs = count_arm_param_registers(syscall["params"])
306    if num_regs > 4:
307        return arm_eabi_call_long % syscall
308    return arm_eabi_call_default % syscall
309
310
311def arm64_genstub(syscall):
312    return arm64_call % syscall
313
314
315def mips_genstub(syscall):
316    return mips_call % syscall
317
318
319def mips64_genstub(syscall):
320    return mips64_call % syscall
321
322
323def x86_genstub(syscall):
324    result     = syscall_stub_header % syscall
325
326    numparams = count_generic_param_registers(syscall["params"])
327    stack_bias = numparams*4 + 8
328    offset = 0
329    mov_result = ""
330    first_push = True
331    for register in x86_registers[:numparams]:
332        result     += "    pushl   %%%s\n" % register
333        if first_push:
334          result   += "    .cfi_def_cfa_offset 8\n"
335          result   += "    .cfi_rel_offset %s, 0\n" % register
336          first_push = False
337        else:
338          result   += "    .cfi_adjust_cfa_offset 4\n"
339          result   += "    .cfi_rel_offset %s, 0\n" % register
340        mov_result += "    mov     %d(%%esp), %%%s\n" % (stack_bias+offset, register)
341        offset += 4
342
343    result += x86_call_prepare
344    result += mov_result
345    result += x86_call % syscall
346
347    for register in reversed(x86_registers[:numparams]):
348        result += "    popl    %%%s\n" % register
349
350    result += x86_return % syscall
351    return result
352
353
354def x86_genstub_socketcall(syscall):
355    #   %ebx <--- Argument 1 - The call id of the needed vectored
356    #                          syscall (socket, bind, recv, etc)
357    #   %ecx <--- Argument 2 - Pointer to the rest of the arguments
358    #                          from the original function called (socket())
359
360    result = syscall_stub_header % syscall
361
362    # save the regs we need
363    result += "    pushl   %ebx\n"
364    result += "    .cfi_def_cfa_offset 8\n"
365    result += "    .cfi_rel_offset ebx, 0\n"
366    result += "    pushl   %ecx\n"
367    result += "    .cfi_adjust_cfa_offset 4\n"
368    result += "    .cfi_rel_offset ecx, 0\n"
369    stack_bias = 16
370
371    result += x86_call_prepare
372
373    # set the call id (%ebx)
374    result += "    mov     $%d, %%ebx\n" % syscall["socketcall_id"]
375
376    # set the pointer to the rest of the args into %ecx
377    result += "    mov     %esp, %ecx\n"
378    result += "    addl    $%d, %%ecx\n" % (stack_bias)
379
380    # now do the syscall code itself
381    result += x86_call % syscall
382
383    # now restore the saved regs
384    result += "    popl    %ecx\n"
385    result += "    popl    %ebx\n"
386
387    # epilog
388    result += x86_return % syscall
389    return result
390
391
392def x86_64_genstub(syscall):
393    result = syscall_stub_header % syscall
394    num_regs = count_generic_param_registers64(syscall["params"])
395    if (num_regs > 3):
396        # rcx is used as 4th argument. Kernel wants it at r10.
397        result += "    movq    %rcx, %r10\n"
398
399    result += x86_64_call % syscall
400    return result
401
402
403class SysCallsTxtParser:
404    def __init__(self):
405        self.syscalls = []
406        self.lineno   = 0
407
408    def E(self, msg):
409        print "%d: %s" % (self.lineno, msg)
410
411    def parse_line(self, line):
412        """ parse a syscall spec line.
413
414        line processing, format is
415           return type    func_name[|alias_list][:syscall_name[:socketcall_id]] ( [paramlist] ) architecture_list
416        """
417        pos_lparen = line.find('(')
418        E          = self.E
419        if pos_lparen < 0:
420            E("missing left parenthesis in '%s'" % line)
421            return
422
423        pos_rparen = line.rfind(')')
424        if pos_rparen < 0 or pos_rparen <= pos_lparen:
425            E("missing or misplaced right parenthesis in '%s'" % line)
426            return
427
428        return_type = line[:pos_lparen].strip().split()
429        if len(return_type) < 2:
430            E("missing return type in '%s'" % line)
431            return
432
433        syscall_func = return_type[-1]
434        return_type  = string.join(return_type[:-1],' ')
435        socketcall_id = -1
436
437        pos_colon = syscall_func.find(':')
438        if pos_colon < 0:
439            syscall_name = syscall_func
440        else:
441            if pos_colon == 0 or pos_colon+1 >= len(syscall_func):
442                E("misplaced colon in '%s'" % line)
443                return
444
445            # now find if there is a socketcall_id for a dispatch-type syscall
446            # after the optional 2nd colon
447            pos_colon2 = syscall_func.find(':', pos_colon + 1)
448            if pos_colon2 < 0:
449                syscall_name = syscall_func[pos_colon+1:]
450                syscall_func = syscall_func[:pos_colon]
451            else:
452                if pos_colon2+1 >= len(syscall_func):
453                    E("misplaced colon2 in '%s'" % line)
454                    return
455                syscall_name = syscall_func[(pos_colon+1):pos_colon2]
456                socketcall_id = int(syscall_func[pos_colon2+1:])
457                syscall_func = syscall_func[:pos_colon]
458
459        alias_delim = syscall_func.find('|')
460        if alias_delim > 0:
461            alias_list = syscall_func[alias_delim+1:].strip()
462            syscall_func = syscall_func[:alias_delim]
463            alias_delim = syscall_name.find('|')
464            if alias_delim > 0:
465                syscall_name = syscall_name[:alias_delim]
466            syscall_aliases = string.split(alias_list, ',')
467        else:
468            syscall_aliases = []
469
470        if pos_rparen > pos_lparen+1:
471            syscall_params = line[pos_lparen+1:pos_rparen].split(',')
472            params         = string.join(syscall_params,',')
473        else:
474            syscall_params = []
475            params         = "void"
476
477        t = {
478              "name"    : syscall_name,
479              "func"    : syscall_func,
480              "aliases" : syscall_aliases,
481              "params"  : syscall_params,
482              "decl"    : "%-15s  %s (%s);" % (return_type, syscall_func, params),
483              "socketcall_id" : socketcall_id
484        }
485
486        # Parse the architecture list.
487        arch_list = line[pos_rparen+1:].strip()
488        if arch_list == "all":
489            for arch in all_arches:
490                t[arch] = True
491        else:
492            for arch in string.split(arch_list, ','):
493                if arch in all_arches:
494                    t[arch] = True
495                else:
496                    E("invalid syscall architecture '%s' in '%s'" % (arch, line))
497                    return
498
499        self.syscalls.append(t)
500
501        logging.debug(t)
502
503
504    def parse_file(self, file_path):
505        logging.debug("parse_file: %s" % file_path)
506        fp = open(file_path)
507        for line in fp.xreadlines():
508            self.lineno += 1
509            line = line.strip()
510            if not line: continue
511            if line[0] == '#': continue
512            self.parse_line(line)
513
514        fp.close()
515
516
517class State:
518    def __init__(self):
519        self.old_stubs = []
520        self.new_stubs = []
521        self.other_files = []
522        self.syscalls = []
523
524
525    def process_file(self, input):
526        parser = SysCallsTxtParser()
527        parser.parse_file(input)
528        self.syscalls = parser.syscalls
529        parser = None
530
531        for syscall in self.syscalls:
532            syscall["__NR_name"] = make__NR_name(syscall["name"])
533
534            if syscall.has_key("arm"):
535                syscall["asm-arm"] = add_footer(32, arm_eabi_genstub(syscall), syscall)
536
537            if syscall.has_key("arm64"):
538                syscall["asm-arm64"] = add_footer(64, arm64_genstub(syscall), syscall)
539
540            if syscall.has_key("x86"):
541                if syscall["socketcall_id"] >= 0:
542                    syscall["asm-x86"] = add_footer(32, x86_genstub_socketcall(syscall), syscall)
543                else:
544                    syscall["asm-x86"] = add_footer(32, x86_genstub(syscall), syscall)
545            elif syscall["socketcall_id"] >= 0:
546                E("socketcall_id for dispatch syscalls is only supported for x86 in '%s'" % t)
547                return
548
549            if syscall.has_key("mips"):
550                syscall["asm-mips"] = add_footer(32, mips_genstub(syscall), syscall)
551
552            if syscall.has_key("mips64"):
553                syscall["asm-mips64"] = add_footer(64, mips64_genstub(syscall), syscall)
554
555            if syscall.has_key("x86_64"):
556                syscall["asm-x86_64"] = add_footer(64, x86_64_genstub(syscall), syscall)
557
558    # Scan a Linux kernel asm/unistd.h file containing __NR_* constants
559    # and write out equivalent SYS_* constants for glibc source compatibility.
560    def scan_linux_unistd_h(self, fp, path):
561        pattern = re.compile(r'^#define __NR_([a-z]\S+) .*')
562        syscalls = set() # MIPS defines everything three times; work around that.
563        for line in open(path):
564            m = re.search(pattern, line)
565            if m:
566                syscalls.add(m.group(1))
567        for syscall in sorted(syscalls):
568            fp.write("#define SYS_%s %s\n" % (syscall, make__NR_name(syscall)))
569
570
571    def gen_glibc_syscalls_h(self):
572        # TODO: generate a separate file for each architecture, like glibc's bits/syscall.h.
573        glibc_syscalls_h_path = "include/sys/glibc-syscalls.h"
574        logging.info("generating " + glibc_syscalls_h_path)
575        glibc_fp = create_file(glibc_syscalls_h_path)
576        glibc_fp.write("/* %s */\n" % warning)
577        glibc_fp.write("#ifndef _BIONIC_GLIBC_SYSCALLS_H_\n")
578        glibc_fp.write("#define _BIONIC_GLIBC_SYSCALLS_H_\n")
579
580        glibc_fp.write("#if defined(__aarch64__)\n")
581        self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-generic/unistd.h"))
582        glibc_fp.write("#elif defined(__arm__)\n")
583        self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-arm/asm/unistd.h"))
584        glibc_fp.write("#elif defined(__mips__)\n")
585        self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-mips/asm/unistd.h"))
586        glibc_fp.write("#elif defined(__i386__)\n")
587        self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-x86/asm/unistd_32.h"))
588        glibc_fp.write("#elif defined(__x86_64__)\n")
589        self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-x86/asm/unistd_64.h"))
590        glibc_fp.write("#endif\n")
591
592        glibc_fp.write("#endif /* _BIONIC_GLIBC_SYSCALLS_H_ */\n")
593        glibc_fp.close()
594        self.other_files.append(glibc_syscalls_h_path)
595
596
597    # Write each syscall stub.
598    def gen_syscall_stubs(self):
599        for syscall in self.syscalls:
600            for arch in all_arches:
601                if syscall.has_key("asm-%s" % arch):
602                    filename = "arch-%s/syscalls/%s.S" % (arch, syscall["func"])
603                    logging.info(">>> generating " + filename)
604                    fp = create_file(filename)
605                    fp.write(syscall["asm-%s" % arch])
606                    fp.close()
607                    self.new_stubs.append(filename)
608
609
610    def regenerate(self):
611        logging.info("scanning for existing architecture-specific stub files...")
612
613        for arch in all_arches:
614            arch_dir = "arch-" + arch
615            logging.info("scanning " + os.path.join(bionic_libc_root, arch_dir))
616            rel_path = os.path.join(arch_dir, "syscalls")
617            for file in os.listdir(os.path.join(bionic_libc_root, rel_path)):
618                if file.endswith(".S"):
619                  self.old_stubs.append(os.path.join(rel_path, file))
620
621        logging.info("found %d stub files" % len(self.old_stubs))
622
623        if not os.path.exists(bionic_temp):
624            logging.info("creating %s..." % bionic_temp)
625            make_dir(bionic_temp)
626
627        logging.info("re-generating stubs and support files...")
628
629        self.gen_glibc_syscalls_h()
630        self.gen_syscall_stubs()
631
632        logging.info("comparing files...")
633        adds    = []
634        edits   = []
635
636        for stub in self.new_stubs + self.other_files:
637            tmp_file = os.path.join(bionic_temp, stub)
638            libc_file = os.path.join(bionic_libc_root, stub)
639            if not os.path.exists(libc_file):
640                # new file, git add it
641                logging.info("new file:     " + stub)
642                adds.append(libc_file)
643                shutil.copyfile(tmp_file, libc_file)
644
645            elif not filecmp.cmp(tmp_file, libc_file):
646                logging.info("changed file: " + stub)
647                edits.append(stub)
648
649        deletes = []
650        for stub in self.old_stubs:
651            if not stub in self.new_stubs:
652                logging.info("deleted file: " + stub)
653                deletes.append(os.path.join(bionic_libc_root, stub))
654
655        if not DRY_RUN:
656            if adds:
657                commands.getoutput("git add " + " ".join(adds))
658            if deletes:
659                commands.getoutput("git rm " + " ".join(deletes))
660            if edits:
661                for file in edits:
662                    shutil.copyfile(os.path.join(bionic_temp, file),
663                                    os.path.join(bionic_libc_root, file))
664                commands.getoutput("git add " + " ".join((os.path.join(bionic_libc_root, file)) for file in edits))
665
666            commands.getoutput("git add %s" % (os.path.join(bionic_libc_root, "SYSCALLS.TXT")))
667
668        if (not adds) and (not deletes) and (not edits):
669            logging.info("no changes detected!")
670        else:
671            logging.info("ready to go!!")
672
673logging.basicConfig(level=logging.INFO)
674
675state = State()
676state.process_file(os.path.join(bionic_libc_root, "SYSCALLS.TXT"))
677state.regenerate()
678