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