gensyscalls.py revision 1b91c6c11f6b4b8c082da41339b861981570bb4a
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 <asm/unistd.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 <asm/unistd.h>
80#include <linux/err.h>
81#include <machine/asm.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 <asm/unistd.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
194# This lets us support regular system calls like __NR_write and also weird
195# ones like __ARM_NR_cacheflush, where the NR doesn't come at the start.
196def make__NR_name(name):
197    if name.startswith("__"):
198        return name
199    else:
200        return "__NR_%s" % (name)
201
202class State:
203    def __init__(self):
204        self.old_stubs = []
205        self.new_stubs = []
206        self.other_files = []
207        self.syscalls = []
208
209    def x86_genstub(self, fname, numparams, idname):
210        t = { "fname"  : fname,
211              "idname" : idname }
212
213        result     = x86_header % t
214        stack_bias = 4
215        for r in range(numparams):
216            result     += "    pushl   " + x86_registers[r] + "\n"
217            stack_bias += 4
218
219        for r in range(numparams):
220            result += "    mov     %d(%%esp), %s" % (stack_bias+r*4, x86_registers[r]) + "\n"
221
222        result += x86_call % t
223
224        for r in range(numparams):
225            result += "    popl    " + x86_registers[numparams-r-1] + "\n"
226
227        result += x86_return % t
228        return result
229
230    def x86_genstub_cid(self, fname, numparams, idname, cid):
231        # We'll ignore numparams here because in reality, if there is a
232        # dispatch call (like a socketcall syscall) there are actually
233        # only 2 arguments to the syscall and 2 regs we have to save:
234        #   %ebx <--- Argument 1 - The call id of the needed vectored
235        #                          syscall (socket, bind, recv, etc)
236        #   %ecx <--- Argument 2 - Pointer to the rest of the arguments
237        #                          from the original function called (socket())
238        t = { "fname"  : fname,
239              "idname" : idname }
240
241        result = x86_header % t
242        stack_bias = 4
243
244        # save the regs we need
245        result += "    pushl   %ebx" + "\n"
246        stack_bias += 4
247        result += "    pushl   %ecx" + "\n"
248        stack_bias += 4
249
250        # set the call id (%ebx)
251        result += "    mov     $%d, %%ebx" % (cid) + "\n"
252
253        # set the pointer to the rest of the args into %ecx
254        result += "    mov     %esp, %ecx" + "\n"
255        result += "    addl    $%d, %%ecx" % (stack_bias) + "\n"
256
257        # now do the syscall code itself
258        result += x86_call % t
259
260        # now restore the saved regs
261        result += "    popl    %ecx" + "\n"
262        result += "    popl    %ebx" + "\n"
263
264        # epilog
265        result += x86_return % t
266        return result
267
268
269    def arm_eabi_genstub(self,fname, flags, idname):
270        t = { "fname"  : fname,
271              "idname" : idname }
272        if flags:
273            numargs = int(flags)
274            if numargs > 4:
275                return arm_eabi_call_long % t
276        return arm_eabi_call_default % t
277
278
279    def mips_genstub(self,fname, idname):
280        t = { "fname"  : fname,
281              "idname" : idname }
282        return mips_call % t
283
284    def process_file(self,input):
285        parser = SysCallsTxtParser()
286        parser.parse_file(input)
287        self.syscalls = parser.syscalls
288        parser = None
289
290        for t in self.syscalls:
291            syscall_func   = t["func"]
292            syscall_params = t["params"]
293            syscall_name   = t["name"]
294
295            if t["common"] >= 0 or t["armid"] >= 0:
296                num_regs = count_arm_param_registers(syscall_params)
297                t["asm-arm"] = self.arm_eabi_genstub(syscall_func, num_regs, make__NR_name(syscall_name))
298
299            if t["common"] >= 0 or t["x86id"] >= 0:
300                num_regs = count_generic_param_registers(syscall_params)
301                if t["cid"] >= 0:
302                    t["asm-x86"] = self.x86_genstub_cid(syscall_func, num_regs, make__NR_name(syscall_name), t["cid"])
303                else:
304                    t["asm-x86"] = self.x86_genstub(syscall_func, num_regs, make__NR_name(syscall_name))
305            elif t["cid"] >= 0:
306                E("cid for dispatch syscalls is only supported for x86 in "
307                  "'%s'" % syscall_name)
308                return
309
310            if t["common"] >= 0 or t["mipsid"] >= 0:
311                t["asm-mips"] = self.mips_genstub(syscall_func, make__NR_name(syscall_name))
312
313
314    # Scan a Linux kernel asm/unistd.h file containing __NR_* constants
315    # and write out equivalent SYS_* constants for glibc source compatibility.
316    def scan_linux_unistd_h(self, fp, path):
317        pattern = re.compile(r'^#define __NR_([a-z]\S+) .*')
318        syscalls = set() # MIPS defines everything three times; work around that.
319        for line in open(path):
320            m = re.search(pattern, line)
321            if m:
322                syscalls.add(m.group(1))
323        for syscall in sorted(syscalls):
324            fp.write("#define SYS_%s %s\n" % (syscall, make__NR_name(syscall)))
325
326
327    def gen_glibc_syscalls_h(self):
328        # TODO: generate a separate file for each architecture, like glibc's bits/syscall.h.
329        glibc_syscalls_h_path = "include/sys/glibc-syscalls.h"
330        D("generating " + glibc_syscalls_h_path)
331        glibc_fp = create_file(glibc_syscalls_h_path)
332        glibc_fp.write("/* Auto-generated by gensyscalls.py; do not edit. */\n")
333        glibc_fp.write("#ifndef _BIONIC_GLIBC_SYSCALLS_H_\n")
334        glibc_fp.write("#define _BIONIC_GLIBC_SYSCALLS_H_\n")
335
336        glibc_fp.write("#if defined(__arm__)\n")
337        self.scan_linux_unistd_h(glibc_fp, "libc/kernel/arch-arm/asm/unistd.h")
338        glibc_fp.write("#elif defined(__mips__)\n")
339        self.scan_linux_unistd_h(glibc_fp, "libc/kernel/arch-mips/asm/unistd.h")
340        glibc_fp.write("#elif defined(__i386__)\n")
341        self.scan_linux_unistd_h(glibc_fp, "libc/kernel/arch-x86/asm/unistd_32.h")
342        glibc_fp.write("#endif\n")
343
344        glibc_fp.write("#endif /* _BIONIC_GLIBC_SYSCALLS_H_ */\n")
345        glibc_fp.close()
346        self.other_files.append(glibc_syscalls_h_path)
347
348
349    # now dump the contents of syscalls.mk
350    def gen_arch_syscalls_mk(self, arch):
351        path = "arch-%s/syscalls.mk" % arch
352        D( "generating "+path )
353        fp = create_file( path )
354        fp.write( "# auto-generated by gensyscalls.py, do not touch\n" )
355        fp.write( "syscall_src := \n" )
356        arch_test = {
357            "arm": lambda x: x.has_key("asm-arm"),
358            "x86": lambda x: x.has_key("asm-x86"),
359            "mips": lambda x: x.has_key("asm-mips")
360        }
361
362        for sc in self.syscalls:
363            if arch_test[arch](sc):
364                fp.write("syscall_src += arch-%s/syscalls/%s.S\n" %
365                         (arch, sc["func"]))
366        fp.close()
367        self.other_files.append( path )
368
369
370    # now generate each syscall stub
371    def gen_syscall_stubs(self):
372        for sc in self.syscalls:
373            if sc.has_key("asm-arm") and 'arm' in all_archs:
374                fname = "arch-arm/syscalls/%s.S" % sc["func"]
375                D2( ">>> generating "+fname )
376                fp = create_file( fname )
377                fp.write(sc["asm-arm"])
378                fp.close()
379                self.new_stubs.append( fname )
380
381            if sc.has_key("asm-x86") and 'x86' in all_archs:
382                fname = "arch-x86/syscalls/%s.S" % sc["func"]
383                D2( ">>> generating "+fname )
384                fp = create_file( fname )
385                fp.write(sc["asm-x86"])
386                fp.close()
387                self.new_stubs.append( fname )
388
389            if sc.has_key("asm-mips") and 'mips' in all_archs:
390                fname = "arch-mips/syscalls/%s.S" % sc["func"]
391                D2( ">>> generating "+fname )
392                fp = create_file( fname )
393                fp.write(sc["asm-mips"])
394                fp.close()
395                self.new_stubs.append( fname )
396
397    def  regenerate(self):
398        D( "scanning for existing architecture-specific stub files" )
399
400        bionic_root_len = len(bionic_root)
401
402        for arch in all_archs:
403            arch_path = bionic_root + "arch-" + arch
404            D( "scanning " + arch_path )
405            files = glob.glob( arch_path + "/syscalls/*.S" )
406            for f in files:
407                self.old_stubs.append( f[bionic_root_len:] )
408
409        D( "found %d stub files" % len(self.old_stubs) )
410
411        if not os.path.exists( bionic_temp ):
412            D( "creating %s" % bionic_temp )
413            make_dir( bionic_temp )
414
415        D( "re-generating stubs and support files" )
416
417        self.gen_glibc_syscalls_h()
418        for arch in all_archs:
419            self.gen_arch_syscalls_mk(arch)
420        self.gen_syscall_stubs()
421
422        D( "comparing files" )
423        adds    = []
424        edits   = []
425
426        for stub in self.new_stubs + self.other_files:
427            if not os.path.exists( bionic_root + stub ):
428                # new file, git add it
429                D( "new file:     " + stub)
430                adds.append( bionic_root + stub )
431                shutil.copyfile( bionic_temp + stub, bionic_root + stub )
432
433            elif not filecmp.cmp( bionic_temp + stub, bionic_root + stub ):
434                D( "changed file: " + stub)
435                edits.append( stub )
436
437        deletes = []
438        for stub in self.old_stubs:
439            if not stub in self.new_stubs:
440                D( "deleted file: " + stub)
441                deletes.append( bionic_root + stub )
442
443
444        if adds:
445            commands.getoutput("git add " + " ".join(adds))
446        if deletes:
447            commands.getoutput("git rm " + " ".join(deletes))
448        if edits:
449            for file in edits:
450                shutil.copyfile( bionic_temp + file, bionic_root + file )
451            commands.getoutput("git add " +
452                               " ".join((bionic_root + file) for file in edits))
453
454        commands.getoutput("git add %s%s" % (bionic_root,"SYSCALLS.TXT"))
455
456        if (not adds) and (not deletes) and (not edits):
457            D("no changes detected!")
458        else:
459            D("ready to go!!")
460
461D_setlevel(1)
462
463state = State()
464state.process_file(bionic_root+"SYSCALLS.TXT")
465state.regenerate()
466