Home | History | Annotate | Download | only in tools
      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 
      7 import atexit
      8 import commands
      9 import filecmp
     10 import glob
     11 import logging
     12 import os.path
     13 import re
     14 import shutil
     15 import stat
     16 import string
     17 import sys
     18 import tempfile
     19 
     20 
     21 all_arches = [ "arm", "arm64", "mips", "mips64", "x86", "x86_64" ]
     22 
     23 
     24 # temp directory where we store all intermediate files
     25 bionic_temp = tempfile.mkdtemp(prefix="bionic_gensyscalls");
     26 # Make sure the directory is deleted when the script exits.
     27 atexit.register(shutil.rmtree, bionic_temp)
     28 
     29 bionic_libc_root = os.path.join(os.environ["ANDROID_BUILD_TOP"], "bionic/libc")
     30 
     31 warning = "Generated by gensyscalls.py. Do not edit."
     32 
     33 DRY_RUN = False
     34 
     35 def 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 
     44 def 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 
     51 syscall_stub_header = "/* " + warning + " */\n" + \
     52 """
     53 #include <private/bionic_asm.h>
     54 
     55 ENTRY(%(func)s)
     56 """
     57 
     58 
     59 #
     60 # ARM assembler templates for each syscall stub
     61 #
     62 
     63 arm_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
     74 END(%(func)s)
     75 """
     76 
     77 arm_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
     94 END(%(func)s)
     95 """
     96 
     97 
     98 #
     99 # Arm64 assembler templates for each syscall stub
    100 #
    101 
    102 arm64_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
    111 END(%(func)s)
    112 """
    113 
    114 
    115 #
    116 # MIPS assembler templates for each syscall stub
    117 #
    118 
    119 mips_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
    128 1:
    129     la t9,__set_errno_internal
    130     j t9
    131     nop
    132     .set reorder
    133 END(%(func)s)
    134 """
    135 
    136 
    137 #
    138 # MIPS64 assembler templates for each syscall stub
    139 #
    140 
    141 mips64_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
    150 1:
    151     move t0, ra
    152     bal     2f
    153     nop
    154 2:
    155     .cpsetup ra, t1, 2b
    156     LA t9,__set_errno_internal
    157     .cpreturn
    158     j t9
    159     move ra, t0
    160     .set pop
    161 END(%(func)s)
    162 """
    163 
    164 
    165 #
    166 # x86 assembler templates for each syscall stub
    167 #
    168 
    169 x86_registers = [ "ebx", "ecx", "edx", "esi", "edi", "ebp" ]
    170 
    171 x86_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 
    180 x86_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
    191 1:
    192 """
    193 
    194 x86_return = """\
    195     ret
    196 END(%(func)s)
    197 """
    198 
    199 
    200 #
    201 # x86_64 assembler templates for each syscall stub
    202 #
    203 
    204 x86_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
    212 1:
    213     ret
    214 END(%(func)s)
    215 """
    216 
    217 
    218 def 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 
    239 def 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 
    264 def 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 
    274 def 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.
    283 def make__NR_name(name):
    284     if name.startswith("__ARM_NR_"):
    285         return name
    286     else:
    287         return "__NR_%s" % (name)
    288 
    289 
    290 def 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 
    304 def 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 
    311 def arm64_genstub(syscall):
    312     return arm64_call % syscall
    313 
    314 
    315 def mips_genstub(syscall):
    316     return mips_call % syscall
    317 
    318 
    319 def mips64_genstub(syscall):
    320     return mips64_call % syscall
    321 
    322 
    323 def 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 
    354 def 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 
    392 def 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 
    403 class 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     def parse_open_file(self, fp):
    504         for line in fp:
    505             self.lineno += 1
    506             line = line.strip()
    507             if not line: continue
    508             if line[0] == '#': continue
    509             self.parse_line(line)
    510 
    511     def parse_file(self, file_path):
    512         logging.debug("parse_file: %s" % file_path)
    513         with open(file_path) as fp:
    514             parse_open_file(fp)
    515 
    516 
    517 class 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 
    559     # Scan Linux kernel asm/unistd.h files containing __NR_* constants
    560     # and write out equivalent SYS_* constants for glibc source compatibility.
    561     def gen_glibc_syscalls_h(self):
    562         glibc_syscalls_h_path = "include/bits/glibc-syscalls.h"
    563         logging.info("generating " + glibc_syscalls_h_path)
    564         glibc_fp = create_file(glibc_syscalls_h_path)
    565         glibc_fp.write("/* %s */\n" % warning)
    566         glibc_fp.write("#ifndef _BIONIC_BITS_GLIBC_SYSCALLS_H_\n")
    567         glibc_fp.write("#define _BIONIC_BITS_GLIBC_SYSCALLS_H_\n")
    568 
    569         # Collect the set of all syscalls for all architectures.
    570         syscalls = set()
    571         pattern = re.compile(r'^\s*#\s*define\s*__NR_([a-z]\S+)')
    572         for unistd_h in ["kernel/uapi/asm-generic/unistd.h",
    573                          "kernel/uapi/asm-arm/asm/unistd.h",
    574                          "kernel/uapi/asm-arm/asm/unistd-common.h",
    575                          "kernel/uapi/asm-arm/asm/unistd-eabi.h",
    576                          "kernel/uapi/asm-arm/asm/unistd-oabi.h",
    577                          "kernel/uapi/asm-mips/asm/unistd.h",
    578                          "kernel/uapi/asm-x86/asm/unistd_32.h",
    579                          "kernel/uapi/asm-x86/asm/unistd_64.h"]:
    580           for line in open(os.path.join(bionic_libc_root, unistd_h)):
    581             m = re.search(pattern, line)
    582             if m:
    583               nr_name = m.group(1)
    584               if 'reserved' not in nr_name and 'unused' not in nr_name:
    585                 syscalls.add(nr_name)
    586 
    587         # Write out a single file listing them all. Note that the input
    588         # files include #if trickery, so even for a single architecture
    589         # we don't know exactly which ones are available.
    590         # https://code.google.com/p/android/issues/detail?id=215853
    591         for syscall in sorted(syscalls):
    592           nr_name = make__NR_name(syscall)
    593           glibc_fp.write("#if defined(%s)\n" % nr_name)
    594           glibc_fp.write("  #define SYS_%s %s\n" % (syscall, nr_name))
    595           glibc_fp.write("#endif\n")
    596 
    597         glibc_fp.write("#endif /* _BIONIC_BITS_GLIBC_SYSCALLS_H_ */\n")
    598         glibc_fp.close()
    599         self.other_files.append(glibc_syscalls_h_path)
    600 
    601 
    602     # Write each syscall stub.
    603     def gen_syscall_stubs(self):
    604         for syscall in self.syscalls:
    605             for arch in all_arches:
    606                 if syscall.has_key("asm-%s" % arch):
    607                     filename = "arch-%s/syscalls/%s.S" % (arch, syscall["func"])
    608                     logging.info(">>> generating " + filename)
    609                     fp = create_file(filename)
    610                     fp.write(syscall["asm-%s" % arch])
    611                     fp.close()
    612                     self.new_stubs.append(filename)
    613 
    614 
    615     def regenerate(self):
    616         logging.info("scanning for existing architecture-specific stub files...")
    617 
    618         for arch in all_arches:
    619             arch_dir = "arch-" + arch
    620             logging.info("scanning " + os.path.join(bionic_libc_root, arch_dir))
    621             rel_path = os.path.join(arch_dir, "syscalls")
    622             for file in os.listdir(os.path.join(bionic_libc_root, rel_path)):
    623                 if file.endswith(".S"):
    624                   self.old_stubs.append(os.path.join(rel_path, file))
    625 
    626         logging.info("found %d stub files" % len(self.old_stubs))
    627 
    628         if not os.path.exists(bionic_temp):
    629             logging.info("creating %s..." % bionic_temp)
    630             make_dir(bionic_temp)
    631 
    632         logging.info("re-generating stubs and support files...")
    633 
    634         self.gen_glibc_syscalls_h()
    635         self.gen_syscall_stubs()
    636 
    637         logging.info("comparing files...")
    638         adds    = []
    639         edits   = []
    640 
    641         for stub in self.new_stubs + self.other_files:
    642             tmp_file = os.path.join(bionic_temp, stub)
    643             libc_file = os.path.join(bionic_libc_root, stub)
    644             if not os.path.exists(libc_file):
    645                 # new file, git add it
    646                 logging.info("new file:     " + stub)
    647                 adds.append(libc_file)
    648                 shutil.copyfile(tmp_file, libc_file)
    649 
    650             elif not filecmp.cmp(tmp_file, libc_file):
    651                 logging.info("changed file: " + stub)
    652                 edits.append(stub)
    653 
    654         deletes = []
    655         for stub in self.old_stubs:
    656             if not stub in self.new_stubs:
    657                 logging.info("deleted file: " + stub)
    658                 deletes.append(os.path.join(bionic_libc_root, stub))
    659 
    660         if not DRY_RUN:
    661             if adds:
    662                 commands.getoutput("git add " + " ".join(adds))
    663             if deletes:
    664                 commands.getoutput("git rm " + " ".join(deletes))
    665             if edits:
    666                 for file in edits:
    667                     shutil.copyfile(os.path.join(bionic_temp, file),
    668                                     os.path.join(bionic_libc_root, file))
    669                 commands.getoutput("git add " + " ".join((os.path.join(bionic_libc_root, file)) for file in edits))
    670 
    671             commands.getoutput("git add %s" % (os.path.join(bionic_libc_root, "SYSCALLS.TXT")))
    672 
    673         if (not adds) and (not deletes) and (not edits):
    674             logging.info("no changes detected!")
    675         else:
    676             logging.info("ready to go!!")
    677 
    678 logging.basicConfig(level=logging.INFO)
    679 
    680 if __name__ == "__main__":
    681     state = State()
    682     state.process_file(os.path.join(bionic_libc_root, "SYSCALLS.TXT"))
    683     state.regenerate()
    684