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