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