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