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