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