gensyscalls.py revision 9aceab50155b17741faded1fb22e2daa51a07fb1
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    # now dump the content of linux-syscalls.h
310    def gen_linux_syscalls_h(self):
311        path = "include/sys/linux-syscalls.h"
312        D( "generating "+path )
313        fp = create_file( path )
314        fp.write( "/* auto-generated by gensyscalls.py, do not touch */\n" )
315        fp.write( "#ifndef _BIONIC_LINUX_SYSCALLS_H_\n" )
316        fp.write( "#define _BIONIC_LINUX_SYSCALLS_H_\n\n" )
317        fp.write( "#if !defined __ASM_ARM_UNISTD_H && !defined __ASM_I386_UNISTD_H && !defined __ASM_MIPS_UNISTD_H\n" )
318        fp.write( "#if defined __arm__ && !defined __ARM_EABI__ && !defined __thumb__\n" )
319        fp.write( "  #  define __NR_SYSCALL_BASE 0x900000\n" )
320        fp.write( "#elif defined(__mips__)\n" )
321        fp.write( "  #  define __NR_SYSCALL_BASE 4000\n" )
322        fp.write( "#else\n" )
323        fp.write( "  #  define __NR_SYSCALL_BASE 0\n" )
324        fp.write( "#endif\n\n" )
325
326        # first, all common syscalls
327        for sc in sorted(self.syscalls,key=lambda x:x["common"]):
328            sc_id  = sc["common"]
329            sc_name = sc["name"]
330            if sc_id >= 0:
331                self.gen_NR_syscall( fp, sc_name, sc_id )
332
333        # now, all arm-specific syscalls
334        fp.write( "\n#ifdef __arm__\n" );
335        for sc in self.syscalls:
336            sc_id  = sc["armid"]
337            sc_name = sc["name"]
338            if sc_id >= 0:
339                self.gen_NR_syscall( fp, sc_name, sc_id )
340        fp.write( "#endif\n" );
341
342        gen_syscalls = {}
343        # finally, all i386-specific syscalls
344        fp.write( "\n#ifdef __i386__\n" );
345        for sc in sorted(self.syscalls,key=lambda x:x["x86id"]):
346            sc_id  = sc["x86id"]
347            sc_name = sc["name"]
348            if sc_id >= 0 and sc_name not in gen_syscalls:
349                self.gen_NR_syscall( fp, sc_name, sc_id )
350                gen_syscalls[sc_name] = True
351        fp.write( "#endif\n" );
352
353        # all mips-specific syscalls
354        fp.write( "\n#ifdef __mips__\n" );
355        for sc in sorted(self.syscalls,key=lambda x:x["mipsid"]):
356            sc_id = sc["mipsid"]
357            if sc_id >= 0:
358                self.gen_NR_syscall( fp, sc["name"], sc_id )
359        fp.write( "#endif\n" );
360
361        fp.write( "\n#endif\n" )
362        fp.write( "\n#endif /* _BIONIC_LINUX_SYSCALLS_H_ */\n" );
363        fp.close()
364        self.other_files.append( path )
365
366
367    # now dump the contents of syscalls.mk
368    def gen_arch_syscalls_mk(self, arch):
369        path = "arch-%s/syscalls.mk" % arch
370        D( "generating "+path )
371        fp = create_file( path )
372        fp.write( "# auto-generated by gensyscalls.py, do not touch\n" )
373        fp.write( "syscall_src := \n" )
374        arch_test = {
375            "arm": lambda x: x.has_key("asm-arm"),
376            "x86": lambda x: x.has_key("asm-x86"),
377            "mips": lambda x: x.has_key("asm-mips")
378        }
379
380        for sc in self.syscalls:
381            if arch_test[arch](sc):
382                fp.write("syscall_src += arch-%s/syscalls/%s.S\n" %
383                         (arch, sc["func"]))
384        fp.close()
385        self.other_files.append( path )
386
387
388    # now generate each syscall stub
389    def gen_syscall_stubs(self):
390        for sc in self.syscalls:
391            if sc.has_key("asm-arm") and 'arm' in all_archs:
392                fname = "arch-arm/syscalls/%s.S" % sc["func"]
393                D2( ">>> generating "+fname )
394                fp = create_file( fname )
395                fp.write(sc["asm-arm"])
396                fp.close()
397                self.new_stubs.append( fname )
398
399            if sc.has_key("asm-x86") and 'x86' in all_archs:
400                fname = "arch-x86/syscalls/%s.S" % sc["func"]
401                D2( ">>> generating "+fname )
402                fp = create_file( fname )
403                fp.write(sc["asm-x86"])
404                fp.close()
405                self.new_stubs.append( fname )
406
407            if sc.has_key("asm-mips") and 'mips' in all_archs:
408                fname = "arch-mips/syscalls/%s.S" % sc["func"]
409                D2( ">>> generating "+fname )
410                fp = create_file( fname )
411                fp.write(sc["asm-mips"])
412                fp.close()
413                self.new_stubs.append( fname )
414
415    def  regenerate(self):
416        D( "scanning for existing architecture-specific stub files" )
417
418        bionic_root_len = len(bionic_root)
419
420        for arch in all_archs:
421            arch_path = bionic_root + "arch-" + arch
422            D( "scanning " + arch_path )
423            files = glob.glob( arch_path + "/syscalls/*.S" )
424            for f in files:
425                self.old_stubs.append( f[bionic_root_len:] )
426
427        D( "found %d stub files" % len(self.old_stubs) )
428
429        if not os.path.exists( bionic_temp ):
430            D( "creating %s" % bionic_temp )
431            make_dir( bionic_temp )
432
433        D( "re-generating stubs and support files" )
434
435        self.gen_linux_syscalls_h()
436        for arch in all_archs:
437            self.gen_arch_syscalls_mk(arch)
438        self.gen_syscall_stubs()
439
440        D( "comparing files" )
441        adds    = []
442        edits   = []
443
444        for stub in self.new_stubs + self.other_files:
445            if not os.path.exists( bionic_root + stub ):
446                # new file, git add it
447                D( "new file:     " + stub)
448                adds.append( bionic_root + stub )
449                shutil.copyfile( bionic_temp + stub, bionic_root + stub )
450
451            elif not filecmp.cmp( bionic_temp + stub, bionic_root + stub ):
452                D( "changed file: " + stub)
453                edits.append( stub )
454
455        deletes = []
456        for stub in self.old_stubs:
457            if not stub in self.new_stubs:
458                D( "deleted file: " + stub)
459                deletes.append( bionic_root + stub )
460
461
462        if adds:
463            commands.getoutput("git add " + " ".join(adds))
464        if deletes:
465            commands.getoutput("git rm " + " ".join(deletes))
466        if edits:
467            for file in edits:
468                shutil.copyfile( bionic_temp + file, bionic_root + file )
469            commands.getoutput("git add " +
470                               " ".join((bionic_root + file) for file in edits))
471
472        commands.getoutput("git add %s%s" % (bionic_root,"SYSCALLS.TXT"))
473
474        if (not adds) and (not deletes) and (not edits):
475            D("no changes detected!")
476        else:
477            D("ready to go!!")
478
479D_setlevel(1)
480
481state = State()
482state.process_file(bionic_root+"SYSCALLS.TXT")
483state.regenerate()
484