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