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 re
12import shutil
13import stat
14import string
15import sys
16import tempfile
17
18
19SupportedArchitectures = [ "arm", "arm64", "x86", "x86_64" ]
20
21syscall_stub_header = \
22"""
23ENTRY(%(func)s)
24"""
25
26
27#
28# ARM assembler templates for each syscall stub
29#
30
31arm_eabi_call_default = syscall_stub_header + """\
32    mov     ip, r7
33    .cfi_register r7, ip
34    ldr     r7, =%(__NR_name)s
35    swi     #0
36    mov     r7, ip
37    .cfi_restore r7
38    cmn     r0, #(MAX_ERRNO + 1)
39    bxls    lr
40    neg     r0, r0
41    b       __set_errno_internal
42END(%(func)s)
43"""
44
45arm_eabi_call_long = syscall_stub_header + """\
46    mov     ip, sp
47    stmfd   sp!, {r4, r5, r6, r7}
48    .cfi_def_cfa_offset 16
49    .cfi_rel_offset r4, 0
50    .cfi_rel_offset r5, 4
51    .cfi_rel_offset r6, 8
52    .cfi_rel_offset r7, 12
53    ldmfd   ip, {r4, r5, r6}
54    ldr     r7, =%(__NR_name)s
55    swi     #0
56    ldmfd   sp!, {r4, r5, r6, r7}
57    .cfi_def_cfa_offset 0
58    cmn     r0, #(MAX_ERRNO + 1)
59    bxls    lr
60    neg     r0, r0
61    b       __set_errno_internal
62END(%(func)s)
63"""
64
65
66#
67# Arm64 assembler template for each syscall stub
68#
69
70arm64_call = syscall_stub_header + """\
71    mov     x8, %(__NR_name)s
72    svc     #0
73
74    cmn     x0, #(MAX_ERRNO + 1)
75    cneg    x0, x0, hi
76    b.hi    __set_errno_internal
77
78    ret
79END(%(func)s)
80"""
81
82
83#
84# x86 assembler templates for each syscall stub
85#
86
87x86_registers = [ "ebx", "ecx", "edx", "esi", "edi", "ebp" ]
88
89x86_call_prepare = """\
90
91    call    __kernel_syscall
92    pushl   %eax
93    .cfi_adjust_cfa_offset 4
94    .cfi_rel_offset eax, 0
95
96"""
97
98x86_call = """\
99    movl    $%(__NR_name)s, %%eax
100    call    *(%%esp)
101    addl    $4, %%esp
102
103    cmpl    $-MAX_ERRNO, %%eax
104    jb      1f
105    negl    %%eax
106    pushl   %%eax
107    call    __set_errno_internal
108    addl    $4, %%esp
1091:
110"""
111
112x86_return = """\
113    ret
114END(%(func)s)
115"""
116
117
118#
119# x86_64 assembler template for each syscall stub
120#
121
122x86_64_call = """\
123    movl    $%(__NR_name)s, %%eax
124    syscall
125    cmpq    $-MAX_ERRNO, %%rax
126    jb      1f
127    negl    %%eax
128    movl    %%eax, %%edi
129    call    __set_errno_internal
1301:
131    ret
132END(%(func)s)
133"""
134
135
136def param_uses_64bits(param):
137    """Returns True iff a syscall parameter description corresponds
138       to a 64-bit type."""
139    param = param.strip()
140    # First, check that the param type begins with one of the known
141    # 64-bit types.
142    if not ( \
143       param.startswith("int64_t") or param.startswith("uint64_t") or \
144       param.startswith("loff_t") or param.startswith("off64_t") or \
145       param.startswith("long long") or param.startswith("unsigned long long") or
146       param.startswith("signed long long") ):
147           return False
148
149    # Second, check that there is no pointer type here
150    if param.find("*") >= 0:
151            return False
152
153    # Ok
154    return True
155
156
157def count_arm_param_registers(params):
158    """This function is used to count the number of register used
159       to pass parameters when invoking an ARM system call.
160       This is because the ARM EABI mandates that 64-bit quantities
161       must be passed in an even+odd register pair. So, for example,
162       something like:
163
164             foo(int fd, off64_t pos)
165
166       would actually need 4 registers:
167             r0 -> int
168             r1 -> unused
169             r2-r3 -> pos
170   """
171    count = 0
172    for param in params:
173        if param_uses_64bits(param):
174            if (count & 1) != 0:
175                count += 1
176            count += 2
177        else:
178            count += 1
179    return count
180
181
182def count_generic_param_registers(params):
183    count = 0
184    for param in params:
185        if param_uses_64bits(param):
186            count += 2
187        else:
188            count += 1
189    return count
190
191
192def count_generic_param_registers64(params):
193    count = 0
194    for param in params:
195        count += 1
196    return count
197
198
199# This lets us support regular system calls like __NR_write and also weird
200# ones like __ARM_NR_cacheflush, where the NR doesn't come at the start.
201def make__NR_name(name):
202    if name.startswith("__ARM_NR_"):
203        return name
204    else:
205        return "__NR_%s" % (name)
206
207
208def add_footer(pointer_length, stub, syscall):
209    # Add any aliases for this syscall.
210    aliases = syscall["aliases"]
211    for alias in aliases:
212        stub += "\nALIAS_SYMBOL(%s, %s)\n" % (alias, syscall["func"])
213
214    # Use hidden visibility on LP64 for any functions beginning with underscores.
215    if pointer_length == 64 and syscall["func"].startswith("__"):
216        stub += '.hidden ' + syscall["func"] + '\n'
217
218    return stub
219
220
221def arm_eabi_genstub(syscall):
222    num_regs = count_arm_param_registers(syscall["params"])
223    if num_regs > 4:
224        return arm_eabi_call_long % syscall
225    return arm_eabi_call_default % syscall
226
227
228def arm64_genstub(syscall):
229    return arm64_call % syscall
230
231
232def x86_genstub(syscall):
233    result     = syscall_stub_header % syscall
234
235    numparams = count_generic_param_registers(syscall["params"])
236    stack_bias = numparams*4 + 8
237    offset = 0
238    mov_result = ""
239    first_push = True
240    for register in x86_registers[:numparams]:
241        result     += "    pushl   %%%s\n" % register
242        if first_push:
243          result   += "    .cfi_def_cfa_offset 8\n"
244          result   += "    .cfi_rel_offset %s, 0\n" % register
245          first_push = False
246        else:
247          result   += "    .cfi_adjust_cfa_offset 4\n"
248          result   += "    .cfi_rel_offset %s, 0\n" % register
249        mov_result += "    mov     %d(%%esp), %%%s\n" % (stack_bias+offset, register)
250        offset += 4
251
252    result += x86_call_prepare
253    result += mov_result
254    result += x86_call % syscall
255
256    for register in reversed(x86_registers[:numparams]):
257        result += "    popl    %%%s\n" % register
258
259    result += x86_return % syscall
260    return result
261
262
263def x86_genstub_socketcall(syscall):
264    #   %ebx <--- Argument 1 - The call id of the needed vectored
265    #                          syscall (socket, bind, recv, etc)
266    #   %ecx <--- Argument 2 - Pointer to the rest of the arguments
267    #                          from the original function called (socket())
268
269    result = syscall_stub_header % syscall
270
271    # save the regs we need
272    result += "    pushl   %ebx\n"
273    result += "    .cfi_def_cfa_offset 8\n"
274    result += "    .cfi_rel_offset ebx, 0\n"
275    result += "    pushl   %ecx\n"
276    result += "    .cfi_adjust_cfa_offset 4\n"
277    result += "    .cfi_rel_offset ecx, 0\n"
278    stack_bias = 16
279
280    result += x86_call_prepare
281
282    # set the call id (%ebx)
283    result += "    mov     $%d, %%ebx\n" % syscall["socketcall_id"]
284
285    # set the pointer to the rest of the args into %ecx
286    result += "    mov     %esp, %ecx\n"
287    result += "    addl    $%d, %%ecx\n" % (stack_bias)
288
289    # now do the syscall code itself
290    result += x86_call % syscall
291
292    # now restore the saved regs
293    result += "    popl    %ecx\n"
294    result += "    popl    %ebx\n"
295
296    # epilog
297    result += x86_return % syscall
298    return result
299
300
301def x86_64_genstub(syscall):
302    result = syscall_stub_header % syscall
303    num_regs = count_generic_param_registers64(syscall["params"])
304    if (num_regs > 3):
305        # rcx is used as 4th argument. Kernel wants it at r10.
306        result += "    movq    %rcx, %r10\n"
307
308    result += x86_64_call % syscall
309    return result
310
311
312class SysCallsTxtParser:
313    def __init__(self):
314        self.syscalls = []
315        self.lineno   = 0
316
317    def E(self, msg):
318        print "%d: %s" % (self.lineno, msg)
319
320    def parse_line(self, line):
321        """ parse a syscall spec line.
322
323        line processing, format is
324           return type    func_name[|alias_list][:syscall_name[:socketcall_id]] ( [paramlist] ) architecture_list
325        """
326        pos_lparen = line.find('(')
327        E          = self.E
328        if pos_lparen < 0:
329            E("missing left parenthesis in '%s'" % line)
330            return
331
332        pos_rparen = line.rfind(')')
333        if pos_rparen < 0 or pos_rparen <= pos_lparen:
334            E("missing or misplaced right parenthesis in '%s'" % line)
335            return
336
337        return_type = line[:pos_lparen].strip().split()
338        if len(return_type) < 2:
339            E("missing return type in '%s'" % line)
340            return
341
342        syscall_func = return_type[-1]
343        return_type  = string.join(return_type[:-1],' ')
344        socketcall_id = -1
345
346        pos_colon = syscall_func.find(':')
347        if pos_colon < 0:
348            syscall_name = syscall_func
349        else:
350            if pos_colon == 0 or pos_colon+1 >= len(syscall_func):
351                E("misplaced colon in '%s'" % line)
352                return
353
354            # now find if there is a socketcall_id for a dispatch-type syscall
355            # after the optional 2nd colon
356            pos_colon2 = syscall_func.find(':', pos_colon + 1)
357            if pos_colon2 < 0:
358                syscall_name = syscall_func[pos_colon+1:]
359                syscall_func = syscall_func[:pos_colon]
360            else:
361                if pos_colon2+1 >= len(syscall_func):
362                    E("misplaced colon2 in '%s'" % line)
363                    return
364                syscall_name = syscall_func[(pos_colon+1):pos_colon2]
365                socketcall_id = int(syscall_func[pos_colon2+1:])
366                syscall_func = syscall_func[:pos_colon]
367
368        alias_delim = syscall_func.find('|')
369        if alias_delim > 0:
370            alias_list = syscall_func[alias_delim+1:].strip()
371            syscall_func = syscall_func[:alias_delim]
372            alias_delim = syscall_name.find('|')
373            if alias_delim > 0:
374                syscall_name = syscall_name[:alias_delim]
375            syscall_aliases = string.split(alias_list, ',')
376        else:
377            syscall_aliases = []
378
379        if pos_rparen > pos_lparen+1:
380            syscall_params = line[pos_lparen+1:pos_rparen].split(',')
381            params         = string.join(syscall_params,',')
382        else:
383            syscall_params = []
384            params         = "void"
385
386        t = {
387              "name"    : syscall_name,
388              "func"    : syscall_func,
389              "aliases" : syscall_aliases,
390              "params"  : syscall_params,
391              "decl"    : "%-15s  %s (%s);" % (return_type, syscall_func, params),
392              "socketcall_id" : socketcall_id
393        }
394
395        # Parse the architecture list.
396        arch_list = line[pos_rparen+1:].strip()
397        if arch_list == "all":
398            for arch in SupportedArchitectures:
399                t[arch] = True
400        else:
401            for arch in string.split(arch_list, ','):
402                if arch == "lp32":
403                    for arch in SupportedArchitectures:
404                        if "64" not in arch:
405                          t[arch] = True
406                elif arch == "lp64":
407                    for arch in SupportedArchitectures:
408                        if "64" in arch:
409                            t[arch] = True
410                elif arch in SupportedArchitectures:
411                    t[arch] = True
412                else:
413                    E("invalid syscall architecture '%s' in '%s'" % (arch, line))
414                    return
415
416        self.syscalls.append(t)
417
418    def parse_open_file(self, fp):
419        for line in fp:
420            self.lineno += 1
421            line = line.strip()
422            if not line: continue
423            if line[0] == '#': continue
424            self.parse_line(line)
425
426    def parse_file(self, file_path):
427        with open(file_path) as fp:
428            self.parse_open_file(fp)
429
430
431def main(arch, syscall_file):
432    parser = SysCallsTxtParser()
433    parser.parse_file(syscall_file)
434
435    for syscall in parser.syscalls:
436        syscall["__NR_name"] = make__NR_name(syscall["name"])
437
438        if syscall.has_key("arm"):
439            syscall["asm-arm"] = add_footer(32, arm_eabi_genstub(syscall), syscall)
440
441        if syscall.has_key("arm64"):
442            syscall["asm-arm64"] = add_footer(64, arm64_genstub(syscall), syscall)
443
444        if syscall.has_key("x86"):
445            if syscall["socketcall_id"] >= 0:
446                syscall["asm-x86"] = add_footer(32, x86_genstub_socketcall(syscall), syscall)
447            else:
448                syscall["asm-x86"] = add_footer(32, x86_genstub(syscall), syscall)
449        elif syscall["socketcall_id"] >= 0:
450            E("socketcall_id for dispatch syscalls is only supported for x86 in '%s'" % t)
451            return
452
453        if syscall.has_key("x86_64"):
454            syscall["asm-x86_64"] = add_footer(64, x86_64_genstub(syscall), syscall)
455
456    print("/* Generated by gensyscalls.py. Do not edit. */\n")
457    print("#include <private/bionic_asm.h>\n")
458    for syscall in parser.syscalls:
459        if syscall.has_key("asm-%s" % arch):
460            print(syscall["asm-%s" % arch])
461
462
463if __name__ == "__main__":
464    if len(sys.argv) < 2:
465      print "Usage: gensyscalls.py ARCH SOURCE_FILE"
466      sys.exit(1)
467
468    arch = sys.argv[1]
469    syscall_file = sys.argv[2]
470    main(arch, syscall_file)
471