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