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