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