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