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