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