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