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