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