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 <asm/unistd.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 <asm/unistd.h> 80#include <linux/err.h> 81#include <machine/asm.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 <asm/unistd.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 194# This lets us support regular system calls like __NR_write and also weird 195# ones like __ARM_NR_cacheflush, where the NR doesn't come at the start. 196def make__NR_name(name): 197 if name.startswith("__"): 198 return name 199 else: 200 return "__NR_%s" % (name) 201 202class State: 203 def __init__(self): 204 self.old_stubs = [] 205 self.new_stubs = [] 206 self.other_files = [] 207 self.syscalls = [] 208 209 def x86_genstub(self, fname, numparams, idname): 210 t = { "fname" : fname, 211 "idname" : idname } 212 213 result = x86_header % t 214 stack_bias = 4 215 for r in range(numparams): 216 result += " pushl " + x86_registers[r] + "\n" 217 stack_bias += 4 218 219 for r in range(numparams): 220 result += " mov %d(%%esp), %s" % (stack_bias+r*4, x86_registers[r]) + "\n" 221 222 result += x86_call % t 223 224 for r in range(numparams): 225 result += " popl " + x86_registers[numparams-r-1] + "\n" 226 227 result += x86_return % t 228 return result 229 230 def x86_genstub_cid(self, fname, numparams, idname, cid): 231 # We'll ignore numparams here because in reality, if there is a 232 # dispatch call (like a socketcall syscall) there are actually 233 # only 2 arguments to the syscall and 2 regs we have to save: 234 # %ebx <--- Argument 1 - The call id of the needed vectored 235 # syscall (socket, bind, recv, etc) 236 # %ecx <--- Argument 2 - Pointer to the rest of the arguments 237 # from the original function called (socket()) 238 t = { "fname" : fname, 239 "idname" : idname } 240 241 result = x86_header % t 242 stack_bias = 4 243 244 # save the regs we need 245 result += " pushl %ebx" + "\n" 246 stack_bias += 4 247 result += " pushl %ecx" + "\n" 248 stack_bias += 4 249 250 # set the call id (%ebx) 251 result += " mov $%d, %%ebx" % (cid) + "\n" 252 253 # set the pointer to the rest of the args into %ecx 254 result += " mov %esp, %ecx" + "\n" 255 result += " addl $%d, %%ecx" % (stack_bias) + "\n" 256 257 # now do the syscall code itself 258 result += x86_call % t 259 260 # now restore the saved regs 261 result += " popl %ecx" + "\n" 262 result += " popl %ebx" + "\n" 263 264 # epilog 265 result += x86_return % t 266 return result 267 268 269 def arm_eabi_genstub(self,fname, flags, idname): 270 t = { "fname" : fname, 271 "idname" : idname } 272 if flags: 273 numargs = int(flags) 274 if numargs > 4: 275 return arm_eabi_call_long % t 276 return arm_eabi_call_default % t 277 278 279 def mips_genstub(self,fname, idname): 280 t = { "fname" : fname, 281 "idname" : idname } 282 return mips_call % t 283 284 def process_file(self,input): 285 parser = SysCallsTxtParser() 286 parser.parse_file(input) 287 self.syscalls = parser.syscalls 288 parser = None 289 290 for t in self.syscalls: 291 syscall_func = t["func"] 292 syscall_params = t["params"] 293 syscall_name = t["name"] 294 295 if t["common"] >= 0 or t["armid"] >= 0: 296 num_regs = count_arm_param_registers(syscall_params) 297 t["asm-arm"] = self.arm_eabi_genstub(syscall_func, num_regs, make__NR_name(syscall_name)) 298 299 if t["common"] >= 0 or t["x86id"] >= 0: 300 num_regs = count_generic_param_registers(syscall_params) 301 if t["cid"] >= 0: 302 t["asm-x86"] = self.x86_genstub_cid(syscall_func, num_regs, make__NR_name(syscall_name), t["cid"]) 303 else: 304 t["asm-x86"] = self.x86_genstub(syscall_func, num_regs, make__NR_name(syscall_name)) 305 elif t["cid"] >= 0: 306 E("cid for dispatch syscalls is only supported for x86 in " 307 "'%s'" % syscall_name) 308 return 309 310 if t["common"] >= 0 or t["mipsid"] >= 0: 311 t["asm-mips"] = self.mips_genstub(syscall_func, make__NR_name(syscall_name)) 312 313 314 # Scan a Linux kernel asm/unistd.h file containing __NR_* constants 315 # and write out equivalent SYS_* constants for glibc source compatibility. 316 def scan_linux_unistd_h(self, fp, path): 317 pattern = re.compile(r'^#define __NR_([a-z]\S+) .*') 318 syscalls = set() # MIPS defines everything three times; work around that. 319 for line in open(path): 320 m = re.search(pattern, line) 321 if m: 322 syscalls.add(m.group(1)) 323 for syscall in sorted(syscalls): 324 fp.write("#define SYS_%s %s\n" % (syscall, make__NR_name(syscall))) 325 326 327 def gen_glibc_syscalls_h(self): 328 # TODO: generate a separate file for each architecture, like glibc's bits/syscall.h. 329 glibc_syscalls_h_path = "include/sys/glibc-syscalls.h" 330 D("generating " + glibc_syscalls_h_path) 331 glibc_fp = create_file(glibc_syscalls_h_path) 332 glibc_fp.write("/* Auto-generated by gensyscalls.py; do not edit. */\n") 333 glibc_fp.write("#ifndef _BIONIC_GLIBC_SYSCALLS_H_\n") 334 glibc_fp.write("#define _BIONIC_GLIBC_SYSCALLS_H_\n") 335 336 glibc_fp.write("#if defined(__arm__)\n") 337 self.scan_linux_unistd_h(glibc_fp, "libc/kernel/arch-arm/asm/unistd.h") 338 glibc_fp.write("#elif defined(__mips__)\n") 339 self.scan_linux_unistd_h(glibc_fp, "libc/kernel/arch-mips/asm/unistd.h") 340 glibc_fp.write("#elif defined(__i386__)\n") 341 self.scan_linux_unistd_h(glibc_fp, "libc/kernel/arch-x86/asm/unistd_32.h") 342 glibc_fp.write("#endif\n") 343 344 glibc_fp.write("#endif /* _BIONIC_GLIBC_SYSCALLS_H_ */\n") 345 glibc_fp.close() 346 self.other_files.append(glibc_syscalls_h_path) 347 348 349 # now dump the contents of syscalls.mk 350 def gen_arch_syscalls_mk(self, arch): 351 path = "arch-%s/syscalls.mk" % arch 352 D( "generating "+path ) 353 fp = create_file( path ) 354 fp.write( "# auto-generated by gensyscalls.py, do not touch\n" ) 355 fp.write( "syscall_src := \n" ) 356 arch_test = { 357 "arm": lambda x: x.has_key("asm-arm"), 358 "x86": lambda x: x.has_key("asm-x86"), 359 "mips": lambda x: x.has_key("asm-mips") 360 } 361 362 for sc in self.syscalls: 363 if arch_test[arch](sc): 364 fp.write("syscall_src += arch-%s/syscalls/%s.S\n" % 365 (arch, sc["func"])) 366 fp.close() 367 self.other_files.append( path ) 368 369 370 # now generate each syscall stub 371 def gen_syscall_stubs(self): 372 for sc in self.syscalls: 373 if sc.has_key("asm-arm") and 'arm' in all_archs: 374 fname = "arch-arm/syscalls/%s.S" % sc["func"] 375 D2( ">>> generating "+fname ) 376 fp = create_file( fname ) 377 fp.write(sc["asm-arm"]) 378 fp.close() 379 self.new_stubs.append( fname ) 380 381 if sc.has_key("asm-x86") and 'x86' in all_archs: 382 fname = "arch-x86/syscalls/%s.S" % sc["func"] 383 D2( ">>> generating "+fname ) 384 fp = create_file( fname ) 385 fp.write(sc["asm-x86"]) 386 fp.close() 387 self.new_stubs.append( fname ) 388 389 if sc.has_key("asm-mips") and 'mips' in all_archs: 390 fname = "arch-mips/syscalls/%s.S" % sc["func"] 391 D2( ">>> generating "+fname ) 392 fp = create_file( fname ) 393 fp.write(sc["asm-mips"]) 394 fp.close() 395 self.new_stubs.append( fname ) 396 397 def regenerate(self): 398 D( "scanning for existing architecture-specific stub files" ) 399 400 bionic_root_len = len(bionic_root) 401 402 for arch in all_archs: 403 arch_path = bionic_root + "arch-" + arch 404 D( "scanning " + arch_path ) 405 files = glob.glob( arch_path + "/syscalls/*.S" ) 406 for f in files: 407 self.old_stubs.append( f[bionic_root_len:] ) 408 409 D( "found %d stub files" % len(self.old_stubs) ) 410 411 if not os.path.exists( bionic_temp ): 412 D( "creating %s" % bionic_temp ) 413 make_dir( bionic_temp ) 414 415 D( "re-generating stubs and support files" ) 416 417 self.gen_glibc_syscalls_h() 418 for arch in all_archs: 419 self.gen_arch_syscalls_mk(arch) 420 self.gen_syscall_stubs() 421 422 D( "comparing files" ) 423 adds = [] 424 edits = [] 425 426 for stub in self.new_stubs + self.other_files: 427 if not os.path.exists( bionic_root + stub ): 428 # new file, git add it 429 D( "new file: " + stub) 430 adds.append( bionic_root + stub ) 431 shutil.copyfile( bionic_temp + stub, bionic_root + stub ) 432 433 elif not filecmp.cmp( bionic_temp + stub, bionic_root + stub ): 434 D( "changed file: " + stub) 435 edits.append( stub ) 436 437 deletes = [] 438 for stub in self.old_stubs: 439 if not stub in self.new_stubs: 440 D( "deleted file: " + stub) 441 deletes.append( bionic_root + stub ) 442 443 444 if adds: 445 commands.getoutput("git add " + " ".join(adds)) 446 if deletes: 447 commands.getoutput("git rm " + " ".join(deletes)) 448 if edits: 449 for file in edits: 450 shutil.copyfile( bionic_temp + file, bionic_root + file ) 451 commands.getoutput("git add " + 452 " ".join((bionic_root + file) for file in edits)) 453 454 commands.getoutput("git add %s%s" % (bionic_root,"SYSCALLS.TXT")) 455 456 if (not adds) and (not deletes) and (not edits): 457 D("no changes detected!") 458 else: 459 D("ready to go!!") 460 461D_setlevel(1) 462 463state = State() 464state.process_file(bionic_root+"SYSCALLS.TXT") 465state.regenerate() 466