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