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