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