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