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