gensyscalls.py revision 9aceab50155b17741faded1fb22e2daa51a07fb1
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 # now dump the content of linux-syscalls.h 310 def gen_linux_syscalls_h(self): 311 path = "include/sys/linux-syscalls.h" 312 D( "generating "+path ) 313 fp = create_file( path ) 314 fp.write( "/* auto-generated by gensyscalls.py, do not touch */\n" ) 315 fp.write( "#ifndef _BIONIC_LINUX_SYSCALLS_H_\n" ) 316 fp.write( "#define _BIONIC_LINUX_SYSCALLS_H_\n\n" ) 317 fp.write( "#if !defined __ASM_ARM_UNISTD_H && !defined __ASM_I386_UNISTD_H && !defined __ASM_MIPS_UNISTD_H\n" ) 318 fp.write( "#if defined __arm__ && !defined __ARM_EABI__ && !defined __thumb__\n" ) 319 fp.write( " # define __NR_SYSCALL_BASE 0x900000\n" ) 320 fp.write( "#elif defined(__mips__)\n" ) 321 fp.write( " # define __NR_SYSCALL_BASE 4000\n" ) 322 fp.write( "#else\n" ) 323 fp.write( " # define __NR_SYSCALL_BASE 0\n" ) 324 fp.write( "#endif\n\n" ) 325 326 # first, all common syscalls 327 for sc in sorted(self.syscalls,key=lambda x:x["common"]): 328 sc_id = sc["common"] 329 sc_name = sc["name"] 330 if sc_id >= 0: 331 self.gen_NR_syscall( fp, sc_name, sc_id ) 332 333 # now, all arm-specific syscalls 334 fp.write( "\n#ifdef __arm__\n" ); 335 for sc in self.syscalls: 336 sc_id = sc["armid"] 337 sc_name = sc["name"] 338 if sc_id >= 0: 339 self.gen_NR_syscall( fp, sc_name, sc_id ) 340 fp.write( "#endif\n" ); 341 342 gen_syscalls = {} 343 # finally, all i386-specific syscalls 344 fp.write( "\n#ifdef __i386__\n" ); 345 for sc in sorted(self.syscalls,key=lambda x:x["x86id"]): 346 sc_id = sc["x86id"] 347 sc_name = sc["name"] 348 if sc_id >= 0 and sc_name not in gen_syscalls: 349 self.gen_NR_syscall( fp, sc_name, sc_id ) 350 gen_syscalls[sc_name] = True 351 fp.write( "#endif\n" ); 352 353 # all mips-specific syscalls 354 fp.write( "\n#ifdef __mips__\n" ); 355 for sc in sorted(self.syscalls,key=lambda x:x["mipsid"]): 356 sc_id = sc["mipsid"] 357 if sc_id >= 0: 358 self.gen_NR_syscall( fp, sc["name"], sc_id ) 359 fp.write( "#endif\n" ); 360 361 fp.write( "\n#endif\n" ) 362 fp.write( "\n#endif /* _BIONIC_LINUX_SYSCALLS_H_ */\n" ); 363 fp.close() 364 self.other_files.append( path ) 365 366 367 # now dump the contents of syscalls.mk 368 def gen_arch_syscalls_mk(self, arch): 369 path = "arch-%s/syscalls.mk" % arch 370 D( "generating "+path ) 371 fp = create_file( path ) 372 fp.write( "# auto-generated by gensyscalls.py, do not touch\n" ) 373 fp.write( "syscall_src := \n" ) 374 arch_test = { 375 "arm": lambda x: x.has_key("asm-arm"), 376 "x86": lambda x: x.has_key("asm-x86"), 377 "mips": lambda x: x.has_key("asm-mips") 378 } 379 380 for sc in self.syscalls: 381 if arch_test[arch](sc): 382 fp.write("syscall_src += arch-%s/syscalls/%s.S\n" % 383 (arch, sc["func"])) 384 fp.close() 385 self.other_files.append( path ) 386 387 388 # now generate each syscall stub 389 def gen_syscall_stubs(self): 390 for sc in self.syscalls: 391 if sc.has_key("asm-arm") and 'arm' in all_archs: 392 fname = "arch-arm/syscalls/%s.S" % sc["func"] 393 D2( ">>> generating "+fname ) 394 fp = create_file( fname ) 395 fp.write(sc["asm-arm"]) 396 fp.close() 397 self.new_stubs.append( fname ) 398 399 if sc.has_key("asm-x86") and 'x86' in all_archs: 400 fname = "arch-x86/syscalls/%s.S" % sc["func"] 401 D2( ">>> generating "+fname ) 402 fp = create_file( fname ) 403 fp.write(sc["asm-x86"]) 404 fp.close() 405 self.new_stubs.append( fname ) 406 407 if sc.has_key("asm-mips") and 'mips' in all_archs: 408 fname = "arch-mips/syscalls/%s.S" % sc["func"] 409 D2( ">>> generating "+fname ) 410 fp = create_file( fname ) 411 fp.write(sc["asm-mips"]) 412 fp.close() 413 self.new_stubs.append( fname ) 414 415 def regenerate(self): 416 D( "scanning for existing architecture-specific stub files" ) 417 418 bionic_root_len = len(bionic_root) 419 420 for arch in all_archs: 421 arch_path = bionic_root + "arch-" + arch 422 D( "scanning " + arch_path ) 423 files = glob.glob( arch_path + "/syscalls/*.S" ) 424 for f in files: 425 self.old_stubs.append( f[bionic_root_len:] ) 426 427 D( "found %d stub files" % len(self.old_stubs) ) 428 429 if not os.path.exists( bionic_temp ): 430 D( "creating %s" % bionic_temp ) 431 make_dir( bionic_temp ) 432 433 D( "re-generating stubs and support files" ) 434 435 self.gen_linux_syscalls_h() 436 for arch in all_archs: 437 self.gen_arch_syscalls_mk(arch) 438 self.gen_syscall_stubs() 439 440 D( "comparing files" ) 441 adds = [] 442 edits = [] 443 444 for stub in self.new_stubs + self.other_files: 445 if not os.path.exists( bionic_root + stub ): 446 # new file, git add it 447 D( "new file: " + stub) 448 adds.append( bionic_root + stub ) 449 shutil.copyfile( bionic_temp + stub, bionic_root + stub ) 450 451 elif not filecmp.cmp( bionic_temp + stub, bionic_root + stub ): 452 D( "changed file: " + stub) 453 edits.append( stub ) 454 455 deletes = [] 456 for stub in self.old_stubs: 457 if not stub in self.new_stubs: 458 D( "deleted file: " + stub) 459 deletes.append( bionic_root + stub ) 460 461 462 if adds: 463 commands.getoutput("git add " + " ".join(adds)) 464 if deletes: 465 commands.getoutput("git rm " + " ".join(deletes)) 466 if edits: 467 for file in edits: 468 shutil.copyfile( bionic_temp + file, bionic_root + file ) 469 commands.getoutput("git add " + 470 " ".join((bionic_root + file) for file in edits)) 471 472 commands.getoutput("git add %s%s" % (bionic_root,"SYSCALLS.TXT")) 473 474 if (not adds) and (not deletes) and (not edits): 475 D("no changes detected!") 476 else: 477 D("ready to go!!") 478 479D_setlevel(1) 480 481state = State() 482state.process_file(bionic_root+"SYSCALLS.TXT") 483state.regenerate() 484