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