gensyscalls.py revision 9ce09e423f24823d52f19ab8247e078977100132
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 atexit
8import commands
9import filecmp
10import glob
11import logging
12import os.path
13import re
14import shutil
15import stat
16import string
17import sys
18import tempfile
19
20
21all_arches = [ "arm", "arm64", "mips", "mips64", "x86", "x86_64" ]
22
23
24# temp directory where we store all intermediate files
25bionic_temp = tempfile.mkdtemp(prefix="bionic_gensyscalls");
26# Make sure the directory is deleted when the script exits.
27atexit.register(shutil.rmtree, bionic_temp)
28
29bionic_libc_root = os.path.join(os.environ["ANDROID_BUILD_TOP"], "bionic/libc")
30
31warning = "Generated by gensyscalls.py. Do not edit."
32
33DRY_RUN = False
34
35def make_dir(path):
36    path = os.path.abspath(path)
37    if not os.path.exists(path):
38        parent = os.path.dirname(path)
39        if parent:
40            make_dir(parent)
41        os.mkdir(path)
42
43
44def create_file(relpath):
45    full_path = os.path.join(bionic_temp, relpath)
46    dir = os.path.dirname(full_path)
47    make_dir(dir)
48    return open(full_path, "w")
49
50
51syscall_stub_header = "/* " + warning + " */\n" + \
52"""
53#include <private/bionic_asm.h>
54
55ENTRY(%(func)s)
56"""
57
58
59#
60# ARM assembler templates for each syscall stub
61#
62
63arm_eabi_call_default = syscall_stub_header + """\
64    mov     ip, r7
65    ldr     r7, =%(__NR_name)s
66    swi     #0
67    mov     r7, ip
68    cmn     r0, #(MAX_ERRNO + 1)
69    bxls    lr
70    neg     r0, r0
71    b       __set_errno_internal
72END(%(func)s)
73"""
74
75arm_eabi_call_long = syscall_stub_header + """\
76    mov     ip, sp
77    stmfd   sp!, {r4, r5, r6, r7}
78    .cfi_def_cfa_offset 16
79    .cfi_rel_offset r4, 0
80    .cfi_rel_offset r5, 4
81    .cfi_rel_offset r6, 8
82    .cfi_rel_offset r7, 12
83    ldmfd   ip, {r4, r5, r6}
84    ldr     r7, =%(__NR_name)s
85    swi     #0
86    ldmfd   sp!, {r4, r5, r6, r7}
87    .cfi_def_cfa_offset 0
88    cmn     r0, #(MAX_ERRNO + 1)
89    bxls    lr
90    neg     r0, r0
91    b       __set_errno_internal
92END(%(func)s)
93"""
94
95
96#
97# Arm64 assembler templates for each syscall stub
98#
99
100arm64_call = syscall_stub_header + """\
101    mov     x8, %(__NR_name)s
102    svc     #0
103
104    cmn     x0, #(MAX_ERRNO + 1)
105    cneg    x0, x0, hi
106    b.hi    __set_errno_internal
107
108    ret
109END(%(func)s)
110"""
111
112
113#
114# MIPS assembler templates for each syscall stub
115#
116
117mips_call = syscall_stub_header + """\
118    .set noreorder
119    .cpload t9
120    li v0, %(__NR_name)s
121    syscall
122    bnez a3, 1f
123    move a0, v0
124    j ra
125    nop
1261:
127    la t9,__set_errno_internal
128    j t9
129    nop
130    .set reorder
131END(%(func)s)
132"""
133
134
135#
136# MIPS64 assembler templates for each syscall stub
137#
138
139mips64_call = syscall_stub_header + """\
140    .set push
141    .set noreorder
142    li v0, %(__NR_name)s
143    syscall
144    bnez a3, 1f
145    move a0, v0
146    j ra
147    nop
1481:
149    move t0, ra
150    bal     2f
151    nop
1522:
153    .cpsetup ra, t1, 2b
154    LA t9,__set_errno_internal
155    .cpreturn
156    j t9
157    move ra, t0
158    .set pop
159END(%(func)s)
160"""
161
162
163#
164# x86 assembler templates for each syscall stub
165#
166
167x86_registers = [ "ebx", "ecx", "edx", "esi", "edi", "ebp" ]
168
169x86_call_prepare = """\
170
171    call    __kernel_syscall
172    pushl   %eax
173    .cfi_adjust_cfa_offset 4
174    .cfi_rel_offset eax, 0
175
176"""
177
178x86_call = """\
179    movl    $%(__NR_name)s, %%eax
180    call    *(%%esp)
181    addl    $4, %%esp
182
183    cmpl    $-MAX_ERRNO, %%eax
184    jb      1f
185    negl    %%eax
186    pushl   %%eax
187    call    __set_errno_internal
188    addl    $4, %%esp
1891:
190"""
191
192x86_return = """\
193    ret
194END(%(func)s)
195"""
196
197
198#
199# x86_64 assembler templates for each syscall stub
200#
201
202x86_64_call = """\
203    movl    $%(__NR_name)s, %%eax
204    syscall
205    cmpq    $-MAX_ERRNO, %%rax
206    jb      1f
207    negl    %%eax
208    movl    %%eax, %%edi
209    call    __set_errno_internal
2101:
211    ret
212END(%(func)s)
213"""
214
215
216def param_uses_64bits(param):
217    """Returns True iff a syscall parameter description corresponds
218       to a 64-bit type."""
219    param = param.strip()
220    # First, check that the param type begins with one of the known
221    # 64-bit types.
222    if not ( \
223       param.startswith("int64_t") or param.startswith("uint64_t") or \
224       param.startswith("loff_t") or param.startswith("off64_t") or \
225       param.startswith("long long") or param.startswith("unsigned long long") or
226       param.startswith("signed long long") ):
227           return False
228
229    # Second, check that there is no pointer type here
230    if param.find("*") >= 0:
231            return False
232
233    # Ok
234    return True
235
236
237def count_arm_param_registers(params):
238    """This function is used to count the number of register used
239       to pass parameters when invoking an ARM system call.
240       This is because the ARM EABI mandates that 64-bit quantities
241       must be passed in an even+odd register pair. So, for example,
242       something like:
243
244             foo(int fd, off64_t pos)
245
246       would actually need 4 registers:
247             r0 -> int
248             r1 -> unused
249             r2-r3 -> pos
250   """
251    count = 0
252    for param in params:
253        if param_uses_64bits(param):
254            if (count & 1) != 0:
255                count += 1
256            count += 2
257        else:
258            count += 1
259    return count
260
261
262def count_generic_param_registers(params):
263    count = 0
264    for param in params:
265        if param_uses_64bits(param):
266            count += 2
267        else:
268            count += 1
269    return count
270
271
272def count_generic_param_registers64(params):
273    count = 0
274    for param in params:
275        count += 1
276    return count
277
278
279# This lets us support regular system calls like __NR_write and also weird
280# ones like __ARM_NR_cacheflush, where the NR doesn't come at the start.
281def make__NR_name(name):
282    if name.startswith("__ARM_NR_"):
283        return name
284    else:
285        return "__NR_%s" % (name)
286
287
288def add_footer(pointer_length, stub, syscall):
289    # Add any aliases for this syscall.
290    aliases = syscall["aliases"]
291    for alias in aliases:
292        stub += "\nALIAS_SYMBOL(%s, %s)\n" % (alias, syscall["func"])
293
294    # Use hidden visibility on LP64 for any functions beginning with underscores.
295    # Force hidden visibility for any functions which begin with 3 underscores
296    if (pointer_length == 64 and syscall["func"].startswith("__")) or syscall["func"].startswith("___"):
297        stub += '.hidden ' + syscall["func"] + '\n'
298
299    return stub
300
301
302def arm_eabi_genstub(syscall):
303    num_regs = count_arm_param_registers(syscall["params"])
304    if num_regs > 4:
305        return arm_eabi_call_long % syscall
306    return arm_eabi_call_default % syscall
307
308
309def arm64_genstub(syscall):
310    return arm64_call % syscall
311
312
313def mips_genstub(syscall):
314    return mips_call % syscall
315
316
317def mips64_genstub(syscall):
318    return mips64_call % syscall
319
320
321def x86_genstub(syscall):
322    result     = syscall_stub_header % syscall
323
324    numparams = count_generic_param_registers(syscall["params"])
325    stack_bias = numparams*4 + 8
326    offset = 0
327    mov_result = ""
328    first_push = True
329    for register in x86_registers[:numparams]:
330        result     += "    pushl   %%%s\n" % register
331        if first_push:
332          result   += "    .cfi_def_cfa_offset 8\n"
333          result   += "    .cfi_rel_offset %s, 0\n" % register
334          first_push = False
335        else:
336          result   += "    .cfi_adjust_cfa_offset 4\n"
337          result   += "    .cfi_rel_offset %s, 0\n" % register
338        mov_result += "    mov     %d(%%esp), %%%s\n" % (stack_bias+offset, register)
339        offset += 4
340
341    result += x86_call_prepare
342    result += mov_result
343    result += x86_call % syscall
344
345    for register in reversed(x86_registers[:numparams]):
346        result += "    popl    %%%s\n" % register
347
348    result += x86_return % syscall
349    return result
350
351
352def x86_genstub_socketcall(syscall):
353    #   %ebx <--- Argument 1 - The call id of the needed vectored
354    #                          syscall (socket, bind, recv, etc)
355    #   %ecx <--- Argument 2 - Pointer to the rest of the arguments
356    #                          from the original function called (socket())
357
358    result = syscall_stub_header % syscall
359
360    # save the regs we need
361    result += "    pushl   %ebx\n"
362    result += "    .cfi_def_cfa_offset 8\n"
363    result += "    .cfi_rel_offset ebx, 0\n"
364    result += "    pushl   %ecx\n"
365    result += "    .cfi_adjust_cfa_offset 4\n"
366    result += "    .cfi_rel_offset ecx, 0\n"
367    stack_bias = 16
368
369    result += x86_call_prepare
370
371    # set the call id (%ebx)
372    result += "    mov     $%d, %%ebx\n" % syscall["socketcall_id"]
373
374    # set the pointer to the rest of the args into %ecx
375    result += "    mov     %esp, %ecx\n"
376    result += "    addl    $%d, %%ecx\n" % (stack_bias)
377
378    # now do the syscall code itself
379    result += x86_call % syscall
380
381    # now restore the saved regs
382    result += "    popl    %ecx\n"
383    result += "    popl    %ebx\n"
384
385    # epilog
386    result += x86_return % syscall
387    return result
388
389
390def x86_64_genstub(syscall):
391    result = syscall_stub_header % syscall
392    num_regs = count_generic_param_registers64(syscall["params"])
393    if (num_regs > 3):
394        # rcx is used as 4th argument. Kernel wants it at r10.
395        result += "    movq    %rcx, %r10\n"
396
397    result += x86_64_call % syscall
398    return result
399
400
401class SysCallsTxtParser:
402    def __init__(self):
403        self.syscalls = []
404        self.lineno   = 0
405
406    def E(self, msg):
407        print "%d: %s" % (self.lineno, msg)
408
409    def parse_line(self, line):
410        """ parse a syscall spec line.
411
412        line processing, format is
413           return type    func_name[|alias_list][:syscall_name[:socketcall_id]] ( [paramlist] ) architecture_list
414        """
415        pos_lparen = line.find('(')
416        E          = self.E
417        if pos_lparen < 0:
418            E("missing left parenthesis in '%s'" % line)
419            return
420
421        pos_rparen = line.rfind(')')
422        if pos_rparen < 0 or pos_rparen <= pos_lparen:
423            E("missing or misplaced right parenthesis in '%s'" % line)
424            return
425
426        return_type = line[:pos_lparen].strip().split()
427        if len(return_type) < 2:
428            E("missing return type in '%s'" % line)
429            return
430
431        syscall_func = return_type[-1]
432        return_type  = string.join(return_type[:-1],' ')
433        socketcall_id = -1
434
435        pos_colon = syscall_func.find(':')
436        if pos_colon < 0:
437            syscall_name = syscall_func
438        else:
439            if pos_colon == 0 or pos_colon+1 >= len(syscall_func):
440                E("misplaced colon in '%s'" % line)
441                return
442
443            # now find if there is a socketcall_id for a dispatch-type syscall
444            # after the optional 2nd colon
445            pos_colon2 = syscall_func.find(':', pos_colon + 1)
446            if pos_colon2 < 0:
447                syscall_name = syscall_func[pos_colon+1:]
448                syscall_func = syscall_func[:pos_colon]
449            else:
450                if pos_colon2+1 >= len(syscall_func):
451                    E("misplaced colon2 in '%s'" % line)
452                    return
453                syscall_name = syscall_func[(pos_colon+1):pos_colon2]
454                socketcall_id = int(syscall_func[pos_colon2+1:])
455                syscall_func = syscall_func[:pos_colon]
456
457        alias_delim = syscall_func.find('|')
458        if alias_delim > 0:
459            alias_list = syscall_func[alias_delim+1:].strip()
460            syscall_func = syscall_func[:alias_delim]
461            alias_delim = syscall_name.find('|')
462            if alias_delim > 0:
463                syscall_name = syscall_name[:alias_delim]
464            syscall_aliases = string.split(alias_list, ',')
465        else:
466            syscall_aliases = []
467
468        if pos_rparen > pos_lparen+1:
469            syscall_params = line[pos_lparen+1:pos_rparen].split(',')
470            params         = string.join(syscall_params,',')
471        else:
472            syscall_params = []
473            params         = "void"
474
475        t = {
476              "name"    : syscall_name,
477              "func"    : syscall_func,
478              "aliases" : syscall_aliases,
479              "params"  : syscall_params,
480              "decl"    : "%-15s  %s (%s);" % (return_type, syscall_func, params),
481              "socketcall_id" : socketcall_id
482        }
483
484        # Parse the architecture list.
485        arch_list = line[pos_rparen+1:].strip()
486        if arch_list == "all":
487            for arch in all_arches:
488                t[arch] = True
489        else:
490            for arch in string.split(arch_list, ','):
491                if arch in all_arches:
492                    t[arch] = True
493                else:
494                    E("invalid syscall architecture '%s' in '%s'" % (arch, line))
495                    return
496
497        self.syscalls.append(t)
498
499        logging.debug(t)
500
501
502    def parse_file(self, file_path):
503        logging.debug("parse_file: %s" % file_path)
504        fp = open(file_path)
505        for line in fp.xreadlines():
506            self.lineno += 1
507            line = line.strip()
508            if not line: continue
509            if line[0] == '#': continue
510            self.parse_line(line)
511
512        fp.close()
513
514
515class State:
516    def __init__(self):
517        self.old_stubs = []
518        self.new_stubs = []
519        self.other_files = []
520        self.syscalls = []
521
522
523    def process_file(self, input):
524        parser = SysCallsTxtParser()
525        parser.parse_file(input)
526        self.syscalls = parser.syscalls
527        parser = None
528
529        for syscall in self.syscalls:
530            syscall["__NR_name"] = make__NR_name(syscall["name"])
531
532            if syscall.has_key("arm"):
533                syscall["asm-arm"] = add_footer(32, arm_eabi_genstub(syscall), syscall)
534
535            if syscall.has_key("arm64"):
536                syscall["asm-arm64"] = add_footer(64, arm64_genstub(syscall), syscall)
537
538            if syscall.has_key("x86"):
539                if syscall["socketcall_id"] >= 0:
540                    syscall["asm-x86"] = add_footer(32, x86_genstub_socketcall(syscall), syscall)
541                else:
542                    syscall["asm-x86"] = add_footer(32, x86_genstub(syscall), syscall)
543            elif syscall["socketcall_id"] >= 0:
544                E("socketcall_id for dispatch syscalls is only supported for x86 in '%s'" % t)
545                return
546
547            if syscall.has_key("mips"):
548                syscall["asm-mips"] = add_footer(32, mips_genstub(syscall), syscall)
549
550            if syscall.has_key("mips64"):
551                syscall["asm-mips64"] = add_footer(64, mips64_genstub(syscall), syscall)
552
553            if syscall.has_key("x86_64"):
554                syscall["asm-x86_64"] = add_footer(64, x86_64_genstub(syscall), syscall)
555
556    # Scan a Linux kernel asm/unistd.h file containing __NR_* constants
557    # and write out equivalent SYS_* constants for glibc source compatibility.
558    def scan_linux_unistd_h(self, fp, path):
559        pattern = re.compile(r'^#define __NR_([a-z]\S+) .*')
560        syscalls = set() # MIPS defines everything three times; work around that.
561        for line in open(path):
562            m = re.search(pattern, line)
563            if m:
564                syscalls.add(m.group(1))
565        for syscall in sorted(syscalls):
566            fp.write("#define SYS_%s %s\n" % (syscall, make__NR_name(syscall)))
567
568
569    def gen_glibc_syscalls_h(self):
570        # TODO: generate a separate file for each architecture, like glibc's bits/syscall.h.
571        glibc_syscalls_h_path = "include/sys/glibc-syscalls.h"
572        logging.info("generating " + glibc_syscalls_h_path)
573        glibc_fp = create_file(glibc_syscalls_h_path)
574        glibc_fp.write("/* %s */\n" % warning)
575        glibc_fp.write("#ifndef _BIONIC_GLIBC_SYSCALLS_H_\n")
576        glibc_fp.write("#define _BIONIC_GLIBC_SYSCALLS_H_\n")
577
578        glibc_fp.write("#if defined(__aarch64__)\n")
579        self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-generic/unistd.h"))
580        glibc_fp.write("#elif defined(__arm__)\n")
581        self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-arm/asm/unistd.h"))
582        glibc_fp.write("#elif defined(__mips__)\n")
583        self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-mips/asm/unistd.h"))
584        glibc_fp.write("#elif defined(__i386__)\n")
585        self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-x86/asm/unistd_32.h"))
586        glibc_fp.write("#elif defined(__x86_64__)\n")
587        self.scan_linux_unistd_h(glibc_fp, os.path.join(bionic_libc_root, "kernel/uapi/asm-x86/asm/unistd_64.h"))
588        glibc_fp.write("#endif\n")
589
590        glibc_fp.write("#endif /* _BIONIC_GLIBC_SYSCALLS_H_ */\n")
591        glibc_fp.close()
592        self.other_files.append(glibc_syscalls_h_path)
593
594
595    # Write each syscall stub.
596    def gen_syscall_stubs(self):
597        for syscall in self.syscalls:
598            for arch in all_arches:
599                if syscall.has_key("asm-%s" % arch):
600                    filename = "arch-%s/syscalls/%s.S" % (arch, syscall["func"])
601                    logging.info(">>> generating " + filename)
602                    fp = create_file(filename)
603                    fp.write(syscall["asm-%s" % arch])
604                    fp.close()
605                    self.new_stubs.append(filename)
606
607
608    def regenerate(self):
609        logging.info("scanning for existing architecture-specific stub files...")
610
611        for arch in all_arches:
612            arch_dir = "arch-" + arch
613            logging.info("scanning " + os.path.join(bionic_libc_root, arch_dir))
614            rel_path = os.path.join(arch_dir, "syscalls")
615            for file in os.listdir(os.path.join(bionic_libc_root, rel_path)):
616                if file.endswith(".S"):
617                  self.old_stubs.append(os.path.join(rel_path, file))
618
619        logging.info("found %d stub files" % len(self.old_stubs))
620
621        if not os.path.exists(bionic_temp):
622            logging.info("creating %s..." % bionic_temp)
623            make_dir(bionic_temp)
624
625        logging.info("re-generating stubs and support files...")
626
627        self.gen_glibc_syscalls_h()
628        self.gen_syscall_stubs()
629
630        logging.info("comparing files...")
631        adds    = []
632        edits   = []
633
634        for stub in self.new_stubs + self.other_files:
635            tmp_file = os.path.join(bionic_temp, stub)
636            libc_file = os.path.join(bionic_libc_root, stub)
637            if not os.path.exists(libc_file):
638                # new file, git add it
639                logging.info("new file:     " + stub)
640                adds.append(libc_file)
641                shutil.copyfile(tmp_file, libc_file)
642
643            elif not filecmp.cmp(tmp_file, libc_file):
644                logging.info("changed file: " + stub)
645                edits.append(stub)
646
647        deletes = []
648        for stub in self.old_stubs:
649            if not stub in self.new_stubs:
650                logging.info("deleted file: " + stub)
651                deletes.append(os.path.join(bionic_libc_root, stub))
652
653        if not DRY_RUN:
654            if adds:
655                commands.getoutput("git add " + " ".join(adds))
656            if deletes:
657                commands.getoutput("git rm " + " ".join(deletes))
658            if edits:
659                for file in edits:
660                    shutil.copyfile(os.path.join(bionic_temp, file),
661                                    os.path.join(bionic_libc_root, file))
662                commands.getoutput("git add " + " ".join((os.path.join(bionic_libc_root, file)) for file in edits))
663
664            commands.getoutput("git add %s" % (os.path.join(bionic_libc_root, "SYSCALLS.TXT")))
665
666        if (not adds) and (not deletes) and (not edits):
667            logging.info("no changes detected!")
668        else:
669            logging.info("ready to go!!")
670
671logging.basicConfig(level=logging.INFO)
672
673state = State()
674state.process_file(os.path.join(bionic_libc_root, "SYSCALLS.TXT"))
675state.regenerate()
676