gensyscalls.py revision 8ecf2258274b6ef2630a503a314573d80517465a
1#!/usr/bin/python 2# 3# this tool is used to generate the syscall assembler templates 4# to be placed into arch-{arm,x86,mips}/syscalls, as well as the content 5# of arch-{arm,x86,mips}/linux/_syscalls.h 6# 7 8import sys, os.path, glob, re, commands, filecmp, shutil 9import getpass 10 11from bionic_utils import * 12 13# get the root Bionic directory, simply this script's dirname 14# 15bionic_root = find_bionic_root() 16if not bionic_root: 17 print "could not find the Bionic root directory. aborting" 18 sys.exit(1) 19 20if bionic_root[-1] != '/': 21 bionic_root += "/" 22 23print "bionic_root is %s" % bionic_root 24 25# temp directory where we store all intermediate files 26bionic_temp = "/tmp/bionic_gensyscalls/" 27 28# all architectures, update as you see fit 29all_archs = [ "arm", "mips", "x86" ] 30 31def make_dir( path ): 32 path = os.path.abspath(path) 33 if not os.path.exists(path): 34 parent = os.path.dirname(path) 35 if parent: 36 make_dir(parent) 37 os.mkdir(path) 38 39def create_file( relpath ): 40 dir = os.path.dirname( bionic_temp + relpath ) 41 make_dir(dir) 42 return open( bionic_temp + relpath, "w" ) 43 44# 45# x86 assembler templates for each syscall stub 46# 47 48x86_header = """/* autogenerated by gensyscalls.py */ 49#include <linux/err.h> 50#include <machine/asm.h> 51#include <sys/linux-syscalls.h> 52 53ENTRY(%(fname)s) 54""" 55 56x86_registers = [ "%ebx", "%ecx", "%edx", "%esi", "%edi", "%ebp" ] 57 58x86_call = """ movl $%(idname)s, %%eax 59 int $0x80 60 cmpl $-MAX_ERRNO, %%eax 61 jb 1f 62 negl %%eax 63 pushl %%eax 64 call __set_errno 65 addl $4, %%esp 66 orl $-1, %%eax 671: 68""" 69 70x86_return = """ ret 71END(%(fname)s) 72""" 73 74# 75# ARM assembler templates for each syscall stub 76# 77 78arm_header = """/* autogenerated by gensyscalls.py */ 79#include <linux/err.h> 80#include <machine/asm.h> 81#include <sys/linux-syscalls.h> 82 83ENTRY(%(fname)s) 84""" 85 86arm_eabi_call_default = arm_header + """\ 87 mov ip, r7 88 ldr r7, =%(idname)s 89 swi #0 90 mov r7, ip 91 cmn r0, #(MAX_ERRNO + 1) 92 bxls lr 93 neg r0, r0 94 b __set_errno 95END(%(fname)s) 96""" 97 98arm_eabi_call_long = arm_header + """\ 99 mov ip, sp 100 .save {r4, r5, r6, r7} 101 stmfd sp!, {r4, r5, r6, r7} 102 ldmfd ip, {r4, r5, r6} 103 ldr r7, =%(idname)s 104 swi #0 105 ldmfd sp!, {r4, r5, r6, r7} 106 cmn r0, #(MAX_ERRNO + 1) 107 bxls lr 108 neg r0, r0 109 b __set_errno 110END(%(fname)s) 111""" 112 113# 114# mips assembler templates for each syscall stub 115# 116 117mips_call = """/* autogenerated by gensyscalls.py */ 118#include <sys/linux-syscalls.h> 119 .text 120 .globl %(fname)s 121 .align 4 122 .ent %(fname)s 123 124%(fname)s: 125 .set noreorder 126 .cpload $t9 127 li $v0, %(idname)s 128 syscall 129 bnez $a3, 1f 130 move $a0, $v0 131 j $ra 132 nop 1331: 134 la $t9,__set_errno 135 j $t9 136 nop 137 .set reorder 138 .end %(fname)s 139""" 140 141def param_uses_64bits(param): 142 """Returns True iff a syscall parameter description corresponds 143 to a 64-bit type.""" 144 param = param.strip() 145 # First, check that the param type begins with one of the known 146 # 64-bit types. 147 if not ( \ 148 param.startswith("int64_t") or param.startswith("uint64_t") or \ 149 param.startswith("loff_t") or param.startswith("off64_t") or \ 150 param.startswith("long long") or param.startswith("unsigned long long") or 151 param.startswith("signed long long") ): 152 return False 153 154 # Second, check that there is no pointer type here 155 if param.find("*") >= 0: 156 return False 157 158 # Ok 159 return True 160 161def count_arm_param_registers(params): 162 """This function is used to count the number of register used 163 to pass parameters when invoking an ARM system call. 164 This is because the ARM EABI mandates that 64-bit quantities 165 must be passed in an even+odd register pair. So, for example, 166 something like: 167 168 foo(int fd, off64_t pos) 169 170 would actually need 4 registers: 171 r0 -> int 172 r1 -> unused 173 r2-r3 -> pos 174 """ 175 count = 0 176 for param in params: 177 if param_uses_64bits(param): 178 if (count & 1) != 0: 179 count += 1 180 count += 2 181 else: 182 count += 1 183 return count 184 185def count_generic_param_registers(params): 186 count = 0 187 for param in params: 188 if param_uses_64bits(param): 189 count += 2 190 else: 191 count += 1 192 return count 193 194class State: 195 def __init__(self): 196 self.old_stubs = [] 197 self.new_stubs = [] 198 self.other_files = [] 199 self.syscalls = [] 200 201 def x86_genstub(self, fname, numparams, idname): 202 t = { "fname" : fname, 203 "idname" : idname } 204 205 result = x86_header % t 206 stack_bias = 4 207 for r in range(numparams): 208 result += " pushl " + x86_registers[r] + "\n" 209 stack_bias += 4 210 211 for r in range(numparams): 212 result += " mov %d(%%esp), %s" % (stack_bias+r*4, x86_registers[r]) + "\n" 213 214 result += x86_call % t 215 216 for r in range(numparams): 217 result += " popl " + x86_registers[numparams-r-1] + "\n" 218 219 result += x86_return % t 220 return result 221 222 def x86_genstub_cid(self, fname, numparams, idname, cid): 223 # We'll ignore numparams here because in reality, if there is a 224 # dispatch call (like a socketcall syscall) there are actually 225 # only 2 arguments to the syscall and 2 regs we have to save: 226 # %ebx <--- Argument 1 - The call id of the needed vectored 227 # syscall (socket, bind, recv, etc) 228 # %ecx <--- Argument 2 - Pointer to the rest of the arguments 229 # from the original function called (socket()) 230 t = { "fname" : fname, 231 "idname" : idname } 232 233 result = x86_header % t 234 stack_bias = 4 235 236 # save the regs we need 237 result += " pushl %ebx" + "\n" 238 stack_bias += 4 239 result += " pushl %ecx" + "\n" 240 stack_bias += 4 241 242 # set the call id (%ebx) 243 result += " mov $%d, %%ebx" % (cid) + "\n" 244 245 # set the pointer to the rest of the args into %ecx 246 result += " mov %esp, %ecx" + "\n" 247 result += " addl $%d, %%ecx" % (stack_bias) + "\n" 248 249 # now do the syscall code itself 250 result += x86_call % t 251 252 # now restore the saved regs 253 result += " popl %ecx" + "\n" 254 result += " popl %ebx" + "\n" 255 256 # epilog 257 result += x86_return % t 258 return result 259 260 261 def arm_eabi_genstub(self,fname, flags, idname): 262 t = { "fname" : fname, 263 "idname" : idname } 264 if flags: 265 numargs = int(flags) 266 if numargs > 4: 267 return arm_eabi_call_long % t 268 return arm_eabi_call_default % t 269 270 271 def mips_genstub(self,fname, idname): 272 t = { "fname" : fname, 273 "idname" : idname } 274 return mips_call % t 275 276 def process_file(self,input): 277 parser = SysCallsTxtParser() 278 parser.parse_file(input) 279 self.syscalls = parser.syscalls 280 parser = None 281 282 for t in self.syscalls: 283 syscall_func = t["func"] 284 syscall_params = t["params"] 285 syscall_name = t["name"] 286 287 if t["common"] >= 0 or t["armid"] >= 0: 288 num_regs = count_arm_param_registers(syscall_params) 289 t["asm-arm"] = self.arm_eabi_genstub(syscall_func,num_regs,"__NR_"+syscall_name) 290 291 if t["common"] >= 0 or t["x86id"] >= 0: 292 num_regs = count_generic_param_registers(syscall_params) 293 if t["cid"] >= 0: 294 t["asm-x86"] = self.x86_genstub_cid(syscall_func, num_regs, "__NR_"+syscall_name, t["cid"]) 295 else: 296 t["asm-x86"] = self.x86_genstub(syscall_func, num_regs, "__NR_"+syscall_name) 297 elif t["cid"] >= 0: 298 E("cid for dispatch syscalls is only supported for x86 in " 299 "'%s'" % syscall_name) 300 return 301 302 if t["common"] >= 0 or t["mipsid"] >= 0: 303 t["asm-mips"] = self.mips_genstub(syscall_func,"__NR_"+syscall_name) 304 305 306 def gen_NR_syscall(self, fp, name, id): 307 fp.write("#define __NR_%-25s (__NR_SYSCALL_BASE + %d)\n" % (name,id)) 308 309 310 def gen_glibc_syscalls_h(self): 311 glibc_syscalls_h_path = "include/sys/glibc-syscalls.h" 312 all_syscall_names = set() 313 for sc in self.syscalls: 314 all_syscall_names.add(sc["name"]) 315 fp = create_file(glibc_syscalls_h_path) 316 fp.write("/* Auto-generated by gensyscalls.py; do not edit. */\n") 317 fp.write("#ifndef _BIONIC_GLIBC_SYSCALLS_H_\n") 318 fp.write("#define _BIONIC_GLIBC_SYSCALLS_H_\n") 319 for syscall_name in sorted(all_syscall_names): 320 fp.write("#define SYS_%-25s __NR_%s\n" % (syscall_name, syscall_name)) 321 fp.write("#endif\n") 322 fp.close() 323 self.other_files.append(glibc_syscalls_h_path) 324 325 326 def gen_linux_syscalls_h(self): 327 linux_syscalls_h_path = "include/sys/linux-syscalls.h" 328 D("generating " + linux_syscalls_h_path) 329 fp = create_file(linux_syscalls_h_path) 330 fp.write( "/* Auto-generated by gensyscalls.py; do not edit. */\n" ) 331 fp.write( "#ifndef _BIONIC_LINUX_SYSCALLS_H_\n" ) 332 fp.write( "#define _BIONIC_LINUX_SYSCALLS_H_\n\n" ) 333 fp.write( "#if !defined __ASM_ARM_UNISTD_H && !defined __ASM_I386_UNISTD_H && !defined __ASM_MIPS_UNISTD_H\n" ) 334 fp.write( "#if defined __arm__ && !defined __ARM_EABI__ && !defined __thumb__\n" ) 335 fp.write( " # define __NR_SYSCALL_BASE 0x900000\n" ) 336 fp.write( "#elif defined(__mips__)\n" ) 337 fp.write( " # define __NR_SYSCALL_BASE 4000\n" ) 338 fp.write( "#else\n" ) 339 fp.write( " # define __NR_SYSCALL_BASE 0\n" ) 340 fp.write( "#endif\n\n" ) 341 342 # first, all common syscalls 343 for sc in sorted(self.syscalls,key=lambda x:x["common"]): 344 sc_id = sc["common"] 345 sc_name = sc["name"] 346 if sc_id >= 0: 347 self.gen_NR_syscall(fp, sc_name, sc_id) 348 349 # now, all arm-specific syscalls 350 fp.write( "\n#ifdef __arm__\n" ); 351 for sc in self.syscalls: 352 sc_id = sc["armid"] 353 sc_name = sc["name"] 354 if sc_id >= 0: 355 self.gen_NR_syscall(fp, sc_name, sc_id) 356 fp.write( "#endif\n" ); 357 358 gen_syscalls = {} 359 # finally, all i386-specific syscalls 360 fp.write( "\n#ifdef __i386__\n" ); 361 for sc in sorted(self.syscalls,key=lambda x:x["x86id"]): 362 sc_id = sc["x86id"] 363 sc_name = sc["name"] 364 if sc_id >= 0 and sc_name not in gen_syscalls: 365 self.gen_NR_syscall(fp, sc_name, sc_id) 366 gen_syscalls[sc_name] = True 367 fp.write( "#endif\n" ); 368 369 # all mips-specific syscalls 370 fp.write( "\n#ifdef __mips__\n" ); 371 for sc in sorted(self.syscalls,key=lambda x:x["mipsid"]): 372 sc_id = sc["mipsid"] 373 if sc_id >= 0: 374 self.gen_NR_syscall(fp, sc["name"], sc_id) 375 fp.write( "#endif\n" ); 376 377 fp.write( "\n#endif\n" ) 378 fp.write( "\n#endif /* _BIONIC_LINUX_SYSCALLS_H_ */\n" ); 379 fp.close() 380 self.other_files.append(linux_syscalls_h_path) 381 382 383 # now dump the contents of syscalls.mk 384 def gen_arch_syscalls_mk(self, arch): 385 path = "arch-%s/syscalls.mk" % arch 386 D( "generating "+path ) 387 fp = create_file( path ) 388 fp.write( "# auto-generated by gensyscalls.py, do not touch\n" ) 389 fp.write( "syscall_src := \n" ) 390 arch_test = { 391 "arm": lambda x: x.has_key("asm-arm"), 392 "x86": lambda x: x.has_key("asm-x86"), 393 "mips": lambda x: x.has_key("asm-mips") 394 } 395 396 for sc in self.syscalls: 397 if arch_test[arch](sc): 398 fp.write("syscall_src += arch-%s/syscalls/%s.S\n" % 399 (arch, sc["func"])) 400 fp.close() 401 self.other_files.append( path ) 402 403 404 # now generate each syscall stub 405 def gen_syscall_stubs(self): 406 for sc in self.syscalls: 407 if sc.has_key("asm-arm") and 'arm' in all_archs: 408 fname = "arch-arm/syscalls/%s.S" % sc["func"] 409 D2( ">>> generating "+fname ) 410 fp = create_file( fname ) 411 fp.write(sc["asm-arm"]) 412 fp.close() 413 self.new_stubs.append( fname ) 414 415 if sc.has_key("asm-x86") and 'x86' in all_archs: 416 fname = "arch-x86/syscalls/%s.S" % sc["func"] 417 D2( ">>> generating "+fname ) 418 fp = create_file( fname ) 419 fp.write(sc["asm-x86"]) 420 fp.close() 421 self.new_stubs.append( fname ) 422 423 if sc.has_key("asm-mips") and 'mips' in all_archs: 424 fname = "arch-mips/syscalls/%s.S" % sc["func"] 425 D2( ">>> generating "+fname ) 426 fp = create_file( fname ) 427 fp.write(sc["asm-mips"]) 428 fp.close() 429 self.new_stubs.append( fname ) 430 431 def regenerate(self): 432 D( "scanning for existing architecture-specific stub files" ) 433 434 bionic_root_len = len(bionic_root) 435 436 for arch in all_archs: 437 arch_path = bionic_root + "arch-" + arch 438 D( "scanning " + arch_path ) 439 files = glob.glob( arch_path + "/syscalls/*.S" ) 440 for f in files: 441 self.old_stubs.append( f[bionic_root_len:] ) 442 443 D( "found %d stub files" % len(self.old_stubs) ) 444 445 if not os.path.exists( bionic_temp ): 446 D( "creating %s" % bionic_temp ) 447 make_dir( bionic_temp ) 448 449 D( "re-generating stubs and support files" ) 450 451 self.gen_glibc_syscalls_h() 452 self.gen_linux_syscalls_h() 453 for arch in all_archs: 454 self.gen_arch_syscalls_mk(arch) 455 self.gen_syscall_stubs() 456 457 D( "comparing files" ) 458 adds = [] 459 edits = [] 460 461 for stub in self.new_stubs + self.other_files: 462 if not os.path.exists( bionic_root + stub ): 463 # new file, git add it 464 D( "new file: " + stub) 465 adds.append( bionic_root + stub ) 466 shutil.copyfile( bionic_temp + stub, bionic_root + stub ) 467 468 elif not filecmp.cmp( bionic_temp + stub, bionic_root + stub ): 469 D( "changed file: " + stub) 470 edits.append( stub ) 471 472 deletes = [] 473 for stub in self.old_stubs: 474 if not stub in self.new_stubs: 475 D( "deleted file: " + stub) 476 deletes.append( bionic_root + stub ) 477 478 479 if adds: 480 commands.getoutput("git add " + " ".join(adds)) 481 if deletes: 482 commands.getoutput("git rm " + " ".join(deletes)) 483 if edits: 484 for file in edits: 485 shutil.copyfile( bionic_temp + file, bionic_root + file ) 486 commands.getoutput("git add " + 487 " ".join((bionic_root + file) for file in edits)) 488 489 commands.getoutput("git add %s%s" % (bionic_root,"SYSCALLS.TXT")) 490 491 if (not adds) and (not deletes) and (not edits): 492 D("no changes detected!") 493 else: 494 D("ready to go!!") 495 496D_setlevel(1) 497 498state = State() 499state.process_file(bionic_root+"SYSCALLS.TXT") 500state.regenerate() 501