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