Package vtrace :: Package platforms :: Module linux
[hide private]
[frames] | no frames]

Source Code for Module vtrace.platforms.linux

  1  """ 
  2  Linux Platform Module 
  3  """ 
  4  # Copyright (C) 2007 Invisigoth - See LICENSE file for details 
  5  import os 
  6  import struct 
  7  import signal 
  8  import traceback 
  9  import platform 
 10   
 11  import envi.memory as e_mem 
 12  import envi.registers as e_reg 
 13   
 14  import vtrace 
 15  import vtrace.archs.i386 as v_i386 
 16  import vtrace.archs.amd64 as v_amd64 
 17  import vtrace.platforms.base as v_base 
 18  import vtrace.platforms.posix as v_posix 
 19   
 20  from ctypes import * 
 21  import ctypes.util as cutil 
 22   
 23  libc = CDLL(cutil.find_library("c")) 
 24   
 25  libc.lseek64.restype = c_ulonglong 
 26  libc.lseek64.argtypes = [c_uint, c_ulonglong, c_uint] 
 27  libc.read.restype = c_long 
 28  libc.read.argtypes = [c_uint, c_void_p, c_long] 
 29  libc.write.restype = c_long 
 30  libc.write.argtypes = [c_uint, c_void_p, c_long] 
 31   
 32  O_RDWR = 2 
 33  O_LARGEFILE = 0x8000 
 34   
 35  MAP_ANONYMOUS = 0x20 
 36  MAP_PRIVATE = 0x02 
 37   
 38  # Linux specific ptrace extensions 
 39  PT_GETREGS = 12 
 40  PT_SETREGS = 13 
 41  PT_GETFPREGS = 14 
 42  PT_SETFPREGS = 15 
 43  PT_ATTACH = 16 
 44  PT_DETACH = 17 
 45  PT_GETFPXREGS = 18 
 46  PT_SETFPXREGS = 19 
 47  PT_SYSCALL = 24 
 48  PT_SETOPTIONS = 0x4200 
 49  PT_GETEVENTMSG = 0x4201 
 50  PT_GETSIGINFO = 0x4202 
 51  PT_SETSIGINFO = 0x4203 
 52  # PT set options stuff.  ONLY TRACESYSGOOD may be used in 2.4... 
 53  PT_O_TRACESYSGOOD   = 0x00000001 # add 0x80 to TRAP when generated by syscall 
 54  # For each of the options below, the stop signal is (TRAP | PT_EVENT_FOO << 8) 
 55  PT_O_TRACEFORK      = 0x00000002 # Cause a trap at fork 
 56  PT_O_TRACEVFORK     = 0x00000004 # Cause a trap at vfork 
 57  PT_O_TRACECLONE     = 0x00000008 # Cause a trap at clone 
 58  PT_O_TRACEEXEC      = 0x00000010 # Cause a trap at exec 
 59  PT_O_TRACEVFORKDONE = 0x00000020 # Cause a trap when vfork done 
 60  PT_O_TRACEEXIT      = 0x00000040 # Cause a trap on exit 
 61  PT_O_MASK           = 0x0000007f 
 62  # Ptrace event types (TRAP | PT_EVENT_FOO << 8) means that type 
 63  # when using GETEVENTMSG for most of these, the new pid is the data 
 64  PT_EVENT_FORK       = 1 
 65  PT_EVENT_VFORK      = 2 
 66  PT_EVENT_CLONE      = 3 
 67  PT_EVENT_EXEC       = 4 
 68  PT_EVENT_VFORK_DONE = 5 
 69  PT_EVENT_EXIT       = 6 
 70   
 71  # Used to tell some of the additional events apart 
 72  SIG_LINUX_SYSCALL = signal.SIGTRAP | 0x80 
 73  SIG_LINUX_CLONE = signal.SIGTRAP | (PT_EVENT_CLONE << 8) 
74 75 -class user_regs_i386(Structure):
76 _fields_ = ( 77 ("ebx", c_ulong), 78 ("ecx", c_ulong), 79 ("edx", c_ulong), 80 ("esi", c_ulong), 81 ("edi", c_ulong), 82 ("ebp", c_ulong), 83 ("eax", c_ulong), 84 ("ds", c_ushort), 85 ("__ds", c_ushort), 86 ("es", c_ushort), 87 ("__es", c_ushort), 88 ("fs", c_ushort), 89 ("__fs", c_ushort), 90 ("gs", c_ushort), 91 ("__gs", c_ushort), 92 ("orig_eax", c_ulong), 93 ("eip", c_ulong), 94 ("cs", c_ushort), 95 ("__cs", c_ushort), 96 ("eflags", c_ulong), 97 ("esp", c_ulong), 98 ("ss", c_ushort), 99 ("__ss", c_ushort), 100 )
101
102 103 -class USER_i386(Structure):
104 _fields_ = ( 105 # NOTE: Expand out the user regs struct so 106 # we can make one call to _rctx_Import 107 ("regs", user_regs_i386), 108 ("u_fpvalid", c_ulong), 109 ("u_tsize", c_ulong), 110 ("u_dsize", c_ulong), 111 ("u_ssize", c_ulong), 112 ("start_code", c_ulong), 113 ("start_stack",c_ulong), 114 ("signal", c_ulong), 115 ("reserved", c_ulong), 116 ("u_ar0", c_void_p), 117 ("u_fpstate", c_void_p), 118 ("magic", c_ulong), 119 ("u_comm", c_char*32), 120 ("debug0", c_ulong), 121 ("debug1", c_ulong), 122 ("debug2", c_ulong), 123 ("debug3", c_ulong), 124 ("debug4", c_ulong), 125 ("debug5", c_ulong), 126 ("debug6", c_ulong), 127 ("debug7", c_ulong), 128 )
129
130 -class user_regs_amd64(Structure):
131 _fields_ = [ 132 ('r15', c_uint64), 133 ('r14', c_uint64), 134 ('r13', c_uint64), 135 ('r12', c_uint64), 136 ('rbp', c_uint64), 137 ('rbx', c_uint64), 138 ('r11', c_uint64), 139 ('r10', c_uint64), 140 ('r9', c_uint64), 141 ('r8', c_uint64), 142 ('rax', c_uint64), 143 ('rcx', c_uint64), 144 ('rdx', c_uint64), 145 ('rsi', c_uint64), 146 ('rdi', c_uint64), 147 ('orig_rax', c_uint64), 148 ('rip', c_uint64), 149 ('cs', c_uint64), 150 ('eflags', c_uint64), 151 ('rsp', c_uint64), 152 ('ss', c_uint64), 153 ('fs_base', c_uint64), 154 ('gs_base', c_uint64), 155 ('ds', c_uint64), 156 ('es', c_uint64), 157 ('fs', c_uint64), 158 ('gs', c_uint64), 159 ]
160 161 # Modern linux only lets us write to these... 162 dbgregs = (0,1,2,3,6,7)
163 164 -class LinuxMixin(v_posix.PtraceMixin, v_posix.PosixMixin):
165 """ 166 The mixin to take care of linux specific platform traits. 167 (mostly proc) 168 """ 169
170 - def __init__(self):
171 # Wrap reads from proc in our worker thread 172 v_posix.PtraceMixin.__init__(self) 173 v_posix.PosixMixin.__init__(self) 174 self.nptlinit = False 175 self.memfd = None 176 177 self.fireTracerThread() 178 179 self.initMode("Syscall", False, "Break On Syscalls")
180 181 @v_base.threadwrap
182 - def platformExec(self, cmdline):
183 print 'FIXME: known bug with thread create events from execd linux trace!' 184 pid = v_posix.PtraceMixin.platformExec(self, cmdline) 185 self.pthreads = [pid,] 186 self.setMeta("ExeName",self._findExe(pid)) 187 return pid
188
189 - def setupMemFile(self, offset):
190 """ 191 A utility to open (if necissary) and seek the memfile 192 """ 193 if self.memfd == None: 194 self.memfd = libc.open("/proc/%d/mem" % self.pid, O_RDWR | O_LARGEFILE, 0755) 195 196 x = libc.lseek64(self.memfd, offset, 0)
197 198 @v_base.threadwrap
199 - def platformAllocateMemory(self, size, perms=e_mem.MM_RWX, suggestaddr=0):
200 #FIXME this is intel specific and should probably go in with the regs 201 sp = self.getStackCounter() 202 pc = self.getProgramCounter() 203 204 # Xlate perms (mmap is backward) 205 realperm = 0 206 if perms & e_mem.MM_READ: 207 realperm |= 1 208 if perms & e_mem.MM_WRITE: 209 realperm |= 2 210 if perms & e_mem.MM_EXEC: 211 realperm |= 4 212 213 #mma is struct of mmap args for linux syscall 214 mma = struct.pack("<6L", suggestaddr, size, realperm, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0) 215 216 regsave = self.getRegisters() 217 218 stacksave = self.readMemory(sp, len(mma)) 219 ipsave = self.readMemory(pc, 2) 220 221 SYS_mmap = 90 222 223 self.writeMemory(sp, mma) 224 self.writeMemory(pc, "\xcd\x80") 225 self.setRegisterByName("eax", SYS_mmap) 226 self.setRegisterByName("ebx", sp) 227 self._syncRegs() 228 229 try: 230 # Step over our syscall instruction 231 tid = self.getMeta("ThreadId", 0) 232 self.platformStepi() 233 os.waitpid(tid, 0) 234 eax = self.getRegisterByName("eax") 235 if eax & 0x80000000: 236 raise Exception("Linux mmap syscall error: %d" % eax) 237 return eax 238 239 finally: 240 # Clean up all our fux0ring 241 self.writeMemory(sp, stacksave) 242 self.writeMemory(pc, ipsave) 243 self.setRegisters(regsave)
244
245 - def posixCreateThreadHack(self):
246 for tid in self.threadsForPid(self.pid): 247 if tid == self.pid: 248 continue 249 self.attachThread(tid) 250 v_posix.PosixMixin.posixCreateThreadHack(self)
251 252 @v_base.threadwrap
253 - def platformReadMemory(self, address, size):
254 """ 255 A *much* faster way of reading memory that the 4 bytes 256 per syscall allowed by ptrace 257 """ 258 self.setupMemFile(address) 259 # Use ctypes cause python implementation is teh ghey 260 buf = create_string_buffer(size) 261 x = libc.read(self.memfd, addressof(buf), size) 262 if x != size: 263 #libc.perror('libc.read %d (size: %d)' % (x,size)) 264 raise Exception("reading from invalid memory %s (%d returned)" % (hex(address), x)) 265 # We have to slice cause ctypes "helps" us by adding a null byte... 266 return buf.raw
267 268 @v_base.threadwrap
269 - def whynot_platformWriteMemory(self, address, data):
270 """ 271 A *much* faster way of writting memory that the 4 bytes 272 per syscall allowed by ptrace 273 """ 274 self.setupMemFile(address) 275 buf = create_string_buffer(data) 276 size = len(data) 277 x = libc.write(self.memfd, addressof(buf), size) 278 if x != size: 279 libc.perror('write mem failed: 0x%.8x (%d)' % (address, size)) 280 raise Exception("write memory failed: %d" % x) 281 return x
282
283 - def _findExe(self, pid):
284 exe = os.readlink("/proc/%d/exe" % pid) 285 if "(deleted)" in exe: 286 if "#prelink#" in exe: 287 exe = exe.split(".#prelink#")[0] 288 elif ";" in exe: 289 exe = exe.split(";")[0] 290 else: 291 exe = exe.split("(deleted)")[0].strip() 292 return exe
293 294 @v_base.threadwrap
295 - def platformAttach(self, pid):
296 self.pthreads = [pid,] 297 self.setMeta("ThreadId", pid) 298 if v_posix.ptrace(PT_ATTACH, pid, 0, 0) != 0: 299 raise Exception("PT_ATTACH failed!") 300 self.setupPtraceOptions(pid) 301 self.setMeta("ExeName", self._findExe(pid))
302
303 - def platformPs(self):
304 pslist = [] 305 for dname in os.listdir("/proc/"): 306 try: 307 if not dname.isdigit(): 308 continue 309 cmdline = file("/proc/%s/cmdline" % dname).read() 310 cmdline = cmdline.replace("\x00"," ") 311 if len(cmdline) > 0: 312 pslist.append((int(dname),cmdline)) 313 except: 314 pass # Permissions... quick process... whatev. 315 return pslist
316
317 - def attachThread(self, tid, attached=False):
318 self.doAttachThread(tid,attached=attached) 319 self.setMeta("ThreadId", tid) 320 self.fireNotifiers(vtrace.NOTIFY_CREATE_THREAD)
321 322 @v_base.threadwrap
323 - def platformWait(self):
324 # Blocking wait once... 325 pid, status = os.waitpid(-1, 0x40000002) 326 self.setMeta("ThreadId", pid) 327 # Stop the rest of the threads... 328 # why is linux debugging so Ghetto?!?! 329 if not self.stepping: # If we're stepping, only do the one 330 for tid in self.pthreads: 331 if tid == pid: 332 continue 333 try: 334 # We use SIGSTOP here because they can't mask it. 335 os.kill(tid, signal.SIGSTOP) 336 os.waitpid(tid, 0x40000002) 337 except Exception, e: 338 print "WARNING TID is invalid %d %s" % (tid,e) 339 return status
340 341 # If it's linux 2.4 we must threadwrap wait... 342 if platform.release().startswith("2.4"): 343 platformWait = v_base.threadwrap(platformWait) 344 345 @v_base.threadwrap
346 - def platformContinue(self):
347 cmd = v_posix.PT_CONTINUE 348 if self.getMode("Syscall", False): 349 cmd = PT_SYSCALL 350 pid = self.getPid() 351 sig = self.getCurrentSignal() 352 if sig == None: 353 sig = 0 354 # Only deliver signals to the main thread 355 if v_posix.ptrace(cmd, pid, 0, sig) != 0: 356 raise Exception("ERROR ptrace failed for tid %d" % pid) 357 358 for tid in self.pthreads: 359 if tid == pid: 360 continue 361 if v_posix.ptrace(cmd, tid, 0, 0) != 0: 362 pass
363 364 @v_base.threadwrap
365 - def platformStepi(self):
366 self.stepping = True 367 tid = self.getMeta("ThreadId", 0) 368 if v_posix.ptrace(v_posix.PT_STEP, tid, 0, 0) != 0: 369 raise Exception("ERROR ptrace failed!")
370 371 @v_base.threadwrap
372 - def platformDetach(self):
373 libc.close(self.memfd) 374 for tid in self.pthreads: 375 tid,v_posix.ptrace(PT_DETACH, tid, 0, 0)
376 377 @v_base.threadwrap
378 - def doAttachThread(self, tid, attached=False):
379 """ 380 Do the work for attaching a thread. This must be *under* 381 attachThread() so callers in notifiers may call it (because 382 it's also gotta be thread wrapped). 383 """ 384 if not attached: 385 if v_posix.ptrace(PT_ATTACH, tid, 0, 0) != 0: 386 raise Exception("ERROR ptrace attach failed for thread %d" % tid) 387 os.waitpid(tid, 0x40000002) 388 self.setupPtraceOptions(tid) 389 self.pthreads.append(tid)
390
391 - def setupPtraceOptions(self, tid):
392 """ 393 Called by doAttachThread to setup ptrace related options. 394 """ 395 opts = PT_O_TRACESYSGOOD 396 if platform.release().startswith("2.6"): 397 opts |= PT_O_TRACECLONE 398 x = v_posix.ptrace(PT_SETOPTIONS, tid, 0, opts) 399 if x != 0: 400 libc.perror('ptrace PT_SETOPTION failed for thread %d' % tid)
401 #print "WARNING ptrace SETOPTIONS failed for thread %d (%d)" % (tid,x) 402
403 - def threadsForPid(self, pid):
404 ret = [] 405 tpath = "/proc/%s/task" % pid 406 if os.path.exists(tpath): 407 for pidstr in os.listdir(tpath): 408 ret.append(int(pidstr)) 409 return ret
410
411 - def platformProcessEvent(self, status):
412 # Skim some linux specific events before passing to posix 413 tid = self.getMeta("ThreadId", -1) 414 if os.WIFSTOPPED(status): 415 sig = status >> 8 416 if sig == SIG_LINUX_SYSCALL: 417 self.fireNotifiers(vtrace.NOTIFY_SYSCALL) 418 419 elif sig == SIG_LINUX_CLONE: 420 # Handle a new thread here! 421 newtid = self.getPtraceEvent() 422 self.attachThread(newtid, attached=True) 423 424 #FIXME eventually implement child catching! 425 else: 426 self.handlePosixSignal(sig) 427 428 return 429 430 v_posix.PosixMixin.platformProcessEvent(self, status)
431 432 @v_base.threadwrap
433 - def getPtraceEvent(self):
434 """ 435 This *thread wrapped* function will get any pending GETEVENTMSG 436 msgs. 437 """ 438 p = c_ulong(0) 439 tid = self.getMeta("ThreadId", -1) 440 if v_posix.ptrace(PT_GETEVENTMSG, tid, 0, addressof(p)) != 0: 441 raise Exception('ptrace PT_GETEVENTMSG failed!') 442 return p.value
443
444 - def platformGetThreads(self):
445 ret = {} 446 for tid in self.pthreads: 447 ret[tid] = tid #FIXME make this pthread struct or stackbase soon 448 return ret
449
450 - def platformGetMaps(self):
451 maps = [] 452 mapfile = file("/proc/%d/maps" % self.pid) 453 for line in mapfile: 454 455 perms = 0 456 sline = line.split(" ") 457 addrs = sline[0] 458 permstr = sline[1] 459 fname = sline[-1].strip() 460 addrs = addrs.split("-") 461 base = long(addrs[0],16) 462 max = long(addrs[1],16) 463 mlen = max-base 464 465 if "r" in permstr: 466 perms |= e_mem.MM_READ 467 if "w" in permstr: 468 perms |= e_mem.MM_WRITE 469 if "x" in permstr: 470 perms |= e_mem.MM_EXEC 471 #if "p" in permstr: 472 #pass 473 474 maps.append((base,mlen,perms,fname)) 475 return maps
476
477 - def platformGetFds(self):
478 fds = [] 479 for name in os.listdir("/proc/%d/fd/" % self.pid): 480 try: 481 fdnum = int(name) 482 fdtype = vtrace.FD_UNKNOWN 483 link = os.readlink("/proc/%d/fd/%s" % (self.pid,name)) 484 if "socket:" in link: 485 fdtype = vtrace.FD_SOCKET 486 elif "pipe:" in link: 487 fdtype = vtrace.FD_PIPE 488 elif "/" in link: 489 fdtype = vtrace.FD_FILE 490 491 fds.append((fdnum,fdtype,link)) 492 except: 493 traceback.print_exc() 494 495 return fds
496 497 ############################################################################ 498 # 499 # NOTE: Both of these use class locals set by the i386/amd64 variants 500 # 501 @v_base.threadwrap
502 - def platformGetRegCtx(self, tid):
503 ctx = self.archGetRegCtx() 504 u = self.user_reg_struct() 505 if v_posix.ptrace(PT_GETREGS, tid, 0, addressof(u)) == -1: 506 raise Exception("Error: ptrace(PT_GETREGS...) failed!") 507 508 ctx._rctx_Import(u) 509 510 for i in dbgregs: 511 offset = self.user_dbg_offset + (self.psize * i) 512 r = v_posix.ptrace(v_posix.PT_READ_U, tid, offset, 0) 513 ctx.setRegister(self.dbgidx+i, r & self.reg_val_mask) 514 515 return ctx
516 517 @v_base.threadwrap
518 - def platformSetRegCtx(self, tid, ctx):
519 u = self.user_reg_struct() 520 # Populate the reg struct with the current values (to allow for 521 # any regs in that struct that we don't track... *fs_base*ahem* 522 if v_posix.ptrace(PT_GETREGS, tid, 0, addressof(u)) == -1: 523 raise Exception("Error: ptrace(PT_GETREGS...) failed!") 524 525 ctx._rctx_Export(u) 526 if v_posix.ptrace(PT_SETREGS, tid, 0, addressof(u)) == -1: 527 raise Exception("Error: ptrace(PT_SETREGS...) failed!") 528 529 for i in dbgregs: 530 val = ctx.getRegister(self.dbgidx + i) 531 offset = self.user_dbg_offset + (self.psize * i) 532 if v_posix.ptrace(v_posix.PT_WRITE_U, tid, offset, val) != 0: 533 libc.perror('PT_WRITE_U failed for debug%d' % i)
534 #raise Exception("PT_WRITE_U for debug%d failed!" % i)
535 536 -class Linuxi386Trace( 537 vtrace.Trace, 538 LinuxMixin, 539 v_i386.i386Mixin, 540 v_posix.ElfMixin, 541 v_base.TracerBase):
542 543 544 user_reg_struct = user_regs_i386 545 user_dbg_offset = 252 546 reg_val_mask = 0xffffffff 547
548 - def __init__(self):
549 vtrace.Trace.__init__(self) 550 v_base.TracerBase.__init__(self) 551 v_posix.ElfMixin.__init__(self) 552 v_i386.i386Mixin.__init__(self) 553 LinuxMixin.__init__(self) 554 555 # Pre-calc the index of the debug regs 556 self.dbgidx = self.archGetRegCtx().getRegisterIndex("debug0")
557
558 -class LinuxAmd64Trace( 559 vtrace.Trace, 560 LinuxMixin, 561 v_amd64.Amd64Mixin, 562 v_posix.ElfMixin, 563 v_base.TracerBase):
564 565 user_reg_struct = user_regs_amd64 566 user_dbg_offset = 848 567 reg_val_mask = 0xffffffffffffffff 568
569 - def __init__(self):
570 vtrace.Trace.__init__(self) 571 v_base.TracerBase.__init__(self) 572 v_posix.ElfMixin.__init__(self) 573 v_amd64.Amd64Mixin.__init__(self) 574 LinuxMixin.__init__(self) 575 576 self.dbgidx = self.archGetRegCtx().getRegisterIndex("debug0")
577