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