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