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