Home | History | Annotate | Download | only in tools
      1 #!/usr/bin/python
      2 #
      3 # this tool is used to generate the syscall assembler templates
      4 # to be placed into arch-{arm,x86,mips}/syscalls, as well as the content
      5 # of arch-{arm,x86,mips}/linux/_syscalls.h
      6 #
      7 
      8 import sys, os.path, glob, re, commands, filecmp, shutil
      9 import getpass
     10 
     11 from bionic_utils import *
     12 
     13 bionic_libc_root = os.environ["ANDROID_BUILD_TOP"] + "/bionic/libc/"
     14 
     15 # temp directory where we store all intermediate files
     16 bionic_temp = "/tmp/bionic_gensyscalls/"
     17 
     18 # all architectures, update as you see fit
     19 all_archs = [ "arm", "mips", "x86" ]
     20 
     21 def make_dir( path ):
     22     path = os.path.abspath(path)
     23     if not os.path.exists(path):
     24         parent = os.path.dirname(path)
     25         if parent:
     26             make_dir(parent)
     27         os.mkdir(path)
     28 
     29 def create_file( relpath ):
     30     dir = os.path.dirname( bionic_temp + relpath )
     31     make_dir(dir)
     32     return open( bionic_temp + relpath, "w" )
     33 
     34 #
     35 # x86 assembler templates for each syscall stub
     36 #
     37 
     38 x86_header = """/* autogenerated by gensyscalls.py */
     39 #include <linux/err.h>
     40 #include <machine/asm.h>
     41 #include <asm/unistd.h>
     42 
     43 ENTRY(%(fname)s)
     44 """
     45 
     46 x86_registers = [ "%ebx", "%ecx", "%edx", "%esi", "%edi", "%ebp" ]
     47 
     48 x86_call = """    movl    $%(idname)s, %%eax
     49     int     $0x80
     50     cmpl    $-MAX_ERRNO, %%eax
     51     jb      1f
     52     negl    %%eax
     53     pushl   %%eax
     54     call    __set_errno
     55     addl    $4, %%esp
     56     orl     $-1, %%eax
     57 1:
     58 """
     59 
     60 x86_return = """    ret
     61 END(%(fname)s)
     62 """
     63 
     64 #
     65 # ARM assembler templates for each syscall stub
     66 #
     67 
     68 arm_header = """/* autogenerated by gensyscalls.py */
     69 #include <asm/unistd.h>
     70 #include <linux/err.h>
     71 #include <machine/asm.h>
     72 
     73 ENTRY(%(fname)s)
     74 """
     75 
     76 arm_eabi_call_default = arm_header + """\
     77     mov     ip, r7
     78     ldr     r7, =%(idname)s
     79     swi     #0
     80     mov     r7, ip
     81     cmn     r0, #(MAX_ERRNO + 1)
     82     bxls    lr
     83     neg     r0, r0
     84     b       __set_errno
     85 END(%(fname)s)
     86 """
     87 
     88 arm_eabi_call_long = arm_header + """\
     89     mov     ip, sp
     90     .save   {r4, r5, r6, r7}
     91     stmfd   sp!, {r4, r5, r6, r7}
     92     ldmfd   ip, {r4, r5, r6}
     93     ldr     r7, =%(idname)s
     94     swi     #0
     95     ldmfd   sp!, {r4, r5, r6, r7}
     96     cmn     r0, #(MAX_ERRNO + 1)
     97     bxls    lr
     98     neg     r0, r0
     99     b       __set_errno
    100 END(%(fname)s)
    101 """
    102 
    103 #
    104 # mips assembler templates for each syscall stub
    105 #
    106 
    107 mips_call = """/* autogenerated by gensyscalls.py */
    108 #include <asm/unistd.h>
    109     .text
    110     .globl %(fname)s
    111     .align 4
    112     .ent %(fname)s
    113 
    114 %(fname)s:
    115     .set noreorder
    116     .cpload $t9
    117     li $v0, %(idname)s
    118     syscall
    119     bnez $a3, 1f
    120     move $a0, $v0
    121     j $ra
    122     nop
    123 1:
    124     la $t9,__set_errno
    125     j $t9
    126     nop
    127     .set reorder
    128     .end %(fname)s
    129 """
    130 
    131 def param_uses_64bits(param):
    132     """Returns True iff a syscall parameter description corresponds
    133        to a 64-bit type."""
    134     param = param.strip()
    135     # First, check that the param type begins with one of the known
    136     # 64-bit types.
    137     if not ( \
    138        param.startswith("int64_t") or param.startswith("uint64_t") or \
    139        param.startswith("loff_t") or param.startswith("off64_t") or \
    140        param.startswith("long long") or param.startswith("unsigned long long") or
    141        param.startswith("signed long long") ):
    142            return False
    143 
    144     # Second, check that there is no pointer type here
    145     if param.find("*") >= 0:
    146             return False
    147 
    148     # Ok
    149     return True
    150 
    151 def count_arm_param_registers(params):
    152     """This function is used to count the number of register used
    153        to pass parameters when invoking an ARM system call.
    154        This is because the ARM EABI mandates that 64-bit quantities
    155        must be passed in an even+odd register pair. So, for example,
    156        something like:
    157 
    158              foo(int fd, off64_t pos)
    159 
    160        would actually need 4 registers:
    161              r0 -> int
    162              r1 -> unused
    163              r2-r3 -> pos
    164    """
    165     count = 0
    166     for param in params:
    167         if param_uses_64bits(param):
    168             if (count & 1) != 0:
    169                 count += 1
    170             count += 2
    171         else:
    172             count += 1
    173     return count
    174 
    175 def count_generic_param_registers(params):
    176     count = 0
    177     for param in params:
    178         if param_uses_64bits(param):
    179             count += 2
    180         else:
    181             count += 1
    182     return count
    183 
    184 # This lets us support regular system calls like __NR_write and also weird
    185 # ones like __ARM_NR_cacheflush, where the NR doesn't come at the start.
    186 def make__NR_name(name):
    187     if name.startswith("__"):
    188         return name
    189     else:
    190         return "__NR_%s" % (name)
    191 
    192 class State:
    193     def __init__(self):
    194         self.old_stubs = []
    195         self.new_stubs = []
    196         self.other_files = []
    197         self.syscalls = []
    198 
    199     def x86_genstub(self, fname, numparams, idname):
    200         t = { "fname"  : fname,
    201               "idname" : idname }
    202 
    203         result     = x86_header % t
    204         stack_bias = 4
    205         for r in range(numparams):
    206             result     += "    pushl   " + x86_registers[r] + "\n"
    207             stack_bias += 4
    208 
    209         for r in range(numparams):
    210             result += "    mov     %d(%%esp), %s" % (stack_bias+r*4, x86_registers[r]) + "\n"
    211 
    212         result += x86_call % t
    213 
    214         for r in range(numparams):
    215             result += "    popl    " + x86_registers[numparams-r-1] + "\n"
    216 
    217         result += x86_return % t
    218         return result
    219 
    220     def x86_genstub_cid(self, fname, numparams, idname, cid):
    221         # We'll ignore numparams here because in reality, if there is a
    222         # dispatch call (like a socketcall syscall) there are actually
    223         # only 2 arguments to the syscall and 2 regs we have to save:
    224         #   %ebx <--- Argument 1 - The call id of the needed vectored
    225         #                          syscall (socket, bind, recv, etc)
    226         #   %ecx <--- Argument 2 - Pointer to the rest of the arguments
    227         #                          from the original function called (socket())
    228         t = { "fname"  : fname,
    229               "idname" : idname }
    230 
    231         result = x86_header % t
    232         stack_bias = 4
    233 
    234         # save the regs we need
    235         result += "    pushl   %ebx" + "\n"
    236         stack_bias += 4
    237         result += "    pushl   %ecx" + "\n"
    238         stack_bias += 4
    239 
    240         # set the call id (%ebx)
    241         result += "    mov     $%d, %%ebx" % (cid) + "\n"
    242 
    243         # set the pointer to the rest of the args into %ecx
    244         result += "    mov     %esp, %ecx" + "\n"
    245         result += "    addl    $%d, %%ecx" % (stack_bias) + "\n"
    246 
    247         # now do the syscall code itself
    248         result += x86_call % t
    249 
    250         # now restore the saved regs
    251         result += "    popl    %ecx" + "\n"
    252         result += "    popl    %ebx" + "\n"
    253 
    254         # epilog
    255         result += x86_return % t
    256         return result
    257 
    258 
    259     def arm_eabi_genstub(self,fname, flags, idname):
    260         t = { "fname"  : fname,
    261               "idname" : idname }
    262         if flags:
    263             numargs = int(flags)
    264             if numargs > 4:
    265                 return arm_eabi_call_long % t
    266         return arm_eabi_call_default % t
    267 
    268 
    269     def mips_genstub(self,fname, idname):
    270         t = { "fname"  : fname,
    271               "idname" : idname }
    272         return mips_call % t
    273 
    274     def process_file(self,input):
    275         parser = SysCallsTxtParser()
    276         parser.parse_file(input)
    277         self.syscalls = parser.syscalls
    278         parser = None
    279 
    280         for t in self.syscalls:
    281             syscall_func   = t["func"]
    282             syscall_params = t["params"]
    283             syscall_name   = t["name"]
    284 
    285             if t["common"] >= 0 or t["armid"] >= 0:
    286                 num_regs = count_arm_param_registers(syscall_params)
    287                 t["asm-arm"] = self.arm_eabi_genstub(syscall_func, num_regs, make__NR_name(syscall_name))
    288 
    289             if t["common"] >= 0 or t["x86id"] >= 0:
    290                 num_regs = count_generic_param_registers(syscall_params)
    291                 if t["cid"] >= 0:
    292                     t["asm-x86"] = self.x86_genstub_cid(syscall_func, num_regs, make__NR_name(syscall_name), t["cid"])
    293                 else:
    294                     t["asm-x86"] = self.x86_genstub(syscall_func, num_regs, make__NR_name(syscall_name))
    295             elif t["cid"] >= 0:
    296                 E("cid for dispatch syscalls is only supported for x86 in "
    297                   "'%s'" % syscall_name)
    298                 return
    299 
    300             if t["common"] >= 0 or t["mipsid"] >= 0:
    301                 t["asm-mips"] = self.mips_genstub(syscall_func, make__NR_name(syscall_name))
    302 
    303 
    304     # Scan a Linux kernel asm/unistd.h file containing __NR_* constants
    305     # and write out equivalent SYS_* constants for glibc source compatibility.
    306     def scan_linux_unistd_h(self, fp, path):
    307         pattern = re.compile(r'^#define __NR_([a-z]\S+) .*')
    308         syscalls = set() # MIPS defines everything three times; work around that.
    309         for line in open(path):
    310             m = re.search(pattern, line)
    311             if m:
    312                 syscalls.add(m.group(1))
    313         for syscall in sorted(syscalls):
    314             fp.write("#define SYS_%s %s\n" % (syscall, make__NR_name(syscall)))
    315 
    316 
    317     def gen_glibc_syscalls_h(self):
    318         # TODO: generate a separate file for each architecture, like glibc's bits/syscall.h.
    319         glibc_syscalls_h_path = "include/sys/glibc-syscalls.h"
    320         D("generating " + glibc_syscalls_h_path)
    321         glibc_fp = create_file(glibc_syscalls_h_path)
    322         glibc_fp.write("/* Auto-generated by gensyscalls.py; do not edit. */\n")
    323         glibc_fp.write("#ifndef _BIONIC_GLIBC_SYSCALLS_H_\n")
    324         glibc_fp.write("#define _BIONIC_GLIBC_SYSCALLS_H_\n")
    325 
    326         glibc_fp.write("#if defined(__arm__)\n")
    327         self.scan_linux_unistd_h(glibc_fp, bionic_libc_root + "/kernel/arch-arm/asm/unistd.h")
    328         glibc_fp.write("#elif defined(__mips__)\n")
    329         self.scan_linux_unistd_h(glibc_fp, bionic_libc_root + "/kernel/arch-mips/asm/unistd.h")
    330         glibc_fp.write("#elif defined(__i386__)\n")
    331         self.scan_linux_unistd_h(glibc_fp, bionic_libc_root + "/kernel/arch-x86/asm/unistd_32.h")
    332         glibc_fp.write("#endif\n")
    333 
    334         glibc_fp.write("#endif /* _BIONIC_GLIBC_SYSCALLS_H_ */\n")
    335         glibc_fp.close()
    336         self.other_files.append(glibc_syscalls_h_path)
    337 
    338 
    339     # now dump the contents of syscalls.mk
    340     def gen_arch_syscalls_mk(self, arch):
    341         path = "arch-%s/syscalls.mk" % arch
    342         D( "generating "+path )
    343         fp = create_file( path )
    344         fp.write( "# auto-generated by gensyscalls.py, do not touch\n" )
    345         fp.write( "syscall_src := \n" )
    346         arch_test = {
    347             "arm": lambda x: x.has_key("asm-arm"),
    348             "x86": lambda x: x.has_key("asm-x86"),
    349             "mips": lambda x: x.has_key("asm-mips")
    350         }
    351 
    352         for sc in self.syscalls:
    353             if arch_test[arch](sc):
    354                 fp.write("syscall_src += arch-%s/syscalls/%s.S\n" %
    355                          (arch, sc["func"]))
    356         fp.close()
    357         self.other_files.append( path )
    358 
    359 
    360     # now generate each syscall stub
    361     def gen_syscall_stubs(self):
    362         for sc in self.syscalls:
    363             if sc.has_key("asm-arm") and 'arm' in all_archs:
    364                 fname = "arch-arm/syscalls/%s.S" % sc["func"]
    365                 D2( ">>> generating "+fname )
    366                 fp = create_file( fname )
    367                 fp.write(sc["asm-arm"])
    368                 fp.close()
    369                 self.new_stubs.append( fname )
    370 
    371             if sc.has_key("asm-x86") and 'x86' in all_archs:
    372                 fname = "arch-x86/syscalls/%s.S" % sc["func"]
    373                 D2( ">>> generating "+fname )
    374                 fp = create_file( fname )
    375                 fp.write(sc["asm-x86"])
    376                 fp.close()
    377                 self.new_stubs.append( fname )
    378 
    379             if sc.has_key("asm-mips") and 'mips' in all_archs:
    380                 fname = "arch-mips/syscalls/%s.S" % sc["func"]
    381                 D2( ">>> generating "+fname )
    382                 fp = create_file( fname )
    383                 fp.write(sc["asm-mips"])
    384                 fp.close()
    385                 self.new_stubs.append( fname )
    386 
    387     def  regenerate(self):
    388         D( "scanning for existing architecture-specific stub files" )
    389 
    390         bionic_libc_root_len = len(bionic_libc_root)
    391 
    392         for arch in all_archs:
    393             arch_path = bionic_libc_root + "arch-" + arch
    394             D( "scanning " + arch_path )
    395             files = glob.glob( arch_path + "/syscalls/*.S" )
    396             for f in files:
    397                 self.old_stubs.append( f[bionic_libc_root_len:] )
    398 
    399         D( "found %d stub files" % len(self.old_stubs) )
    400 
    401         if not os.path.exists( bionic_temp ):
    402             D( "creating %s" % bionic_temp )
    403             make_dir( bionic_temp )
    404 
    405         D( "re-generating stubs and support files" )
    406 
    407         self.gen_glibc_syscalls_h()
    408         for arch in all_archs:
    409             self.gen_arch_syscalls_mk(arch)
    410         self.gen_syscall_stubs()
    411 
    412         D( "comparing files" )
    413         adds    = []
    414         edits   = []
    415 
    416         for stub in self.new_stubs + self.other_files:
    417             if not os.path.exists( bionic_libc_root + stub ):
    418                 # new file, git add it
    419                 D( "new file:     " + stub)
    420                 adds.append( bionic_libc_root + stub )
    421                 shutil.copyfile( bionic_temp + stub, bionic_libc_root + stub )
    422 
    423             elif not filecmp.cmp( bionic_temp + stub, bionic_libc_root + stub ):
    424                 D( "changed file: " + stub)
    425                 edits.append( stub )
    426 
    427         deletes = []
    428         for stub in self.old_stubs:
    429             if not stub in self.new_stubs:
    430                 D( "deleted file: " + stub)
    431                 deletes.append( bionic_libc_root + stub )
    432 
    433 
    434         if adds:
    435             commands.getoutput("git add " + " ".join(adds))
    436         if deletes:
    437             commands.getoutput("git rm " + " ".join(deletes))
    438         if edits:
    439             for file in edits:
    440                 shutil.copyfile( bionic_temp + file, bionic_libc_root + file )
    441             commands.getoutput("git add " +
    442                                " ".join((bionic_libc_root + file) for file in edits))
    443 
    444         commands.getoutput("git add %s%s" % (bionic_libc_root,"SYSCALLS.TXT"))
    445 
    446         if (not adds) and (not deletes) and (not edits):
    447             D("no changes detected!")
    448         else:
    449             D("ready to go!!")
    450 
    451 D_setlevel(1)
    452 
    453 state = State()
    454 state.process_file(bionic_libc_root+"SYSCALLS.TXT")
    455 state.regenerate()
    456