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