| Trees | Indices | Help |
|---|
|
|
1 """
2 FreeBSD support...
3 """
4
5 import os
6 import ctypes
7 import ctypes.util as cutil
8
9 import envi.memory as e_mem
10 import envi.cli as e_cli
11
12 import vtrace
13 import vtrace.archs.i386 as v_i386
14 import vtrace.archs.amd64 as v_amd64
15 import vtrace.platforms.base as v_base
16 import vtrace.platforms.posix as v_posix
17 import vtrace.util as v_util
18
19 libkvm = ctypes.CDLL(cutil.find_library("kvm"))
20
21 # kvm_getprocs cmds
22 KERN_PROC_ALL = 0 # everything
23 KERN_PROC_PID = 1 # by process id
24 KERN_PROC_PGRP = 2 # by process group id
25 KERN_PROC_SESSION = 3 # by session of pid
26 KERN_PROC_TTY = 4 # by controlling tty
27 KERN_PROC_UID = 5 # by effective uid
28 KERN_PROC_RUID = 6 # by real uid
29 KERN_PROC_ARGS = 7 # get/set arguments/proctitle
30 KERN_PROC_PROC = 8 # only return procs
31 KERN_PROC_SV_NAME = 9 # get syscall vector name
32 KERN_PROC_RGID = 10 # by real group id
33 KERN_PROC_GID = 11 # by effective group id
34 KERN_PROC_PATHNAME = 12 # path to executable
35 KERN_PROC_INC_THREAD = 0x10 # Include threads in filtered results
36
37 pid_t = ctypes.c_int32
38 lwpid_t = ctypes.c_int32
39 void_p = ctypes.c_void_p
40 dev_t = ctypes.c_uint32
41 sigset_t = ctypes.c_uint32*4
42 uid_t = ctypes.c_uint32
43 gid_t = ctypes.c_uint32
44 fixpt_t = ctypes.c_uint32
45 caddr_t = ctypes.c_void_p
46 vm_size_t = ctypes.c_ulong
47 segsz_t = ctypes.c_ulong
48
49 # Could go crazy and grep headers for this stuff ;)
50 KI_NGROUPS = 16
51 OCOMMLEN = 16
52 WMESGLEN = 8
53 LOGNAMELEN = 17
54 LOCKNAMELEN = 8
55 COMMLEN = 19
56 KI_EMULNAMELEN = 16
57 KI_NSPARE_INT = 10
58 KI_NSPARE_PTR = 7
59 KI_NSPARE_LONG = 12
64
66 _fields_ = (
67 ("pri_class", ctypes.c_ubyte),
68 ("pri_level", ctypes.c_ubyte),
69 ("pri_native", ctypes.c_ubyte),
70 ("pri_user", ctypes.c_ubyte)
71 )
72
78
80 _fields_ = (
81 ("ru_utime", TIMEVAL), # user time used
82 ("ru_stime", TIMEVAL), # system time used
83 ("ru_maxrss", ctypes.c_long), #
84 ("ru_ixrss", ctypes.c_long), # (j) integral shared memory size
85 ("ru_idrss", ctypes.c_long), # (j) integral unshared data
86 ("ru_isrss", ctypes.c_long), # (j) integral unshared stack
87 ("ru_minflt", ctypes.c_long), # (c) page reclaims
88 ("ru_majflt", ctypes.c_long), # (c) page faults
89 ("ru_nswap", ctypes.c_long), # (c + j) swaps
90 ("ru_inblock", ctypes.c_long), # (n) block input operations
91 ("ru_oublock", ctypes.c_long), # (n) block output operations
92 ("ru_msgsnd", ctypes.c_long), # (n) messages sent
93 ("ru_msgrcv", ctypes.c_long), # (n) messages received
94 ("ru_nsignals", ctypes.c_long), # (c) signals received
95 ("ru_nvcsw", ctypes.c_long), # (j) voluntary context switches
96 ("ru_nivcsw", ctypes.c_long), # (j) involuntary
97 )
98
101 _fields_ = (
102 ("ki_structsize", ctypes.c_int),# size of this structure
103 ("ki_layout", ctypes.c_int), # reserved: layout identifier
104 ("ki_args", void_p), # address of command arguments (struct pargs*)
105 ("ki_paddr", void_p), # address of proc (struct proc*)
106 ("ki_addr", void_p), # kernel virtual addr of u-area (struct user*)
107 ("ki_tracep", void_p), # pointer to trace file (struct vnode *)
108 ("ki_textvp", void_p), # pointer to executable file (struct vnode *)
109 ("ki_fd", void_p), # pointer to open file info (struct filedesc *)
110 ("ki_vmspace", void_p), # pointer to kernel vmspace struct (struct vmspace *)
111 ("ki_wchan", void_p), # sleep address (void*)
112 ("ki_pid", pid_t), # Process identifier
113 ("ki_ppid", pid_t), # parent process id
114 ("ki_pgid", pid_t), # process group id
115 ("ki_tpgid", pid_t), # tty process group id
116 ("ki_sid", pid_t), # Process session ID
117 ("ki_tsid", pid_t), # Terminal session ID
118 ("ki_jobc", ctypes.c_short), # job control counter
119 ("ki_spare_short1", ctypes.c_short), #
120 ("ki_tdev", dev_t), # controlling tty dev
121 ("ki_siglist", sigset_t), # Signals arrived but not delivered
122 ("ki_sigmask", sigset_t), # Current signal mask
123 ("ki_sigignore", sigset_t), # Signals being ignored
124 ("ki_sigcatch", sigset_t), # Signals being caught by user
125 ("ki_uid", uid_t), # effective user id
126 ("ki_ruid", uid_t), # Real user id
127 ("ki_svuid", uid_t), # Saved effective user id
128 ("ki_rgid", gid_t), # Real group id
129 ("ki_svgid", gid_t), # Saved effective group id
130 ("ki_ngroups", ctypes.c_short), # number of groups
131 ("ki_spare_short2", ctypes.c_short),
132 ("ki_groups", gid_t * KI_NGROUPS), # groups
133 ("ki_size", vm_size_t), # virtual size
134 ("ki_rssize", segsz_t), # current resident set size in pages
135 ("ki_swrss", segsz_t), # resident set size before last swap
136 ("ki_tsize", segsz_t), # text size (pages) XXX
137 ("ki_dsize", segsz_t), # data size (pages) XXX
138 ("ki_ssize", segsz_t), # stack size (pages)
139 ("ki_xstat", ctypes.c_ushort), # Exit status for wait and stop signal
140 ("ki_acflag", ctypes.c_ushort), # Accounting flags
141 ("ki_pctcpu", fixpt_t), # %cpu for process during ki_swtime
142 ("ki_estcpu", ctypes.c_uint), # Time averaged value of ki_cpticks
143 ("ki_slptime", ctypes.c_uint), # Time since last blocked
144 ("ki_swtime", ctypes.c_uint), # Time swapped in or out
145 ("ki_spareint1", ctypes.c_int), # unused (just here for alignment)
146 ("ki_runtime", ctypes.c_uint64),# Real time in microsec
147 ("ki_start", TIMEVAL), # starting time
148 ("ki_childtime", TIMEVAL), # time used by process children
149 ("ki_flag", ctypes.c_long), # P_* flags
150 ("ki_kiflag", ctypes.c_long), # KI_* flags
151 ("ki_traceflag", ctypes.c_int), # kernel trace points
152 ("ki_stat", ctypes.c_char), # S* process status
153 ("ki_nice", ctypes.c_ubyte), # Process "nice" value
154 ("ki_lock", ctypes.c_char), # Process lock (prevent swap) count
155 ("ki_rqindex", ctypes.c_char), # Run queue index
156 ("ki_oncpu", ctypes.c_char), # Which cpu we are on
157 ("ki_lastcpu", ctypes.c_char), # Last cpu we were on
158 ("ki_ocomm", c_buf(OCOMMLEN+1)), # command name
159 ("ki_wmesg", c_buf(WMESGLEN+1)), # wchan message
160 ("ki_login", c_buf(LOGNAMELEN+1)), # setlogin name
161 ("ki_lockname", c_buf(LOCKNAMELEN+1)),# lock name
162 ("ki_comm", c_buf(COMMLEN+1)), # command name
163 ("ki_emul", c_buf(KI_EMULNAMELEN+1)), # emulation name
164 ("ki_sparestrings",c_buf(68)), # spare string space
165 ("ki_spareints", ctypes.c_int*KI_NSPARE_INT),
166 ("ki_jid", ctypes.c_int), # Process jail ID
167 ("ki_numthreads", ctypes.c_int),# KSE number of total threads
168 ("ki_tid", lwpid_t), # thread id
169 ("ki_pri", PRIORITY), # process priority
170 ("ki_rusage", RUSAGE), # process rusage statistics
171 # XXX - most fields in ki_rusage_ch are not (yet) filled in
172 ("ki_rusage_ch", RUSAGE), # rusage of children processes
173 ("ki_pcb", void_p), # kernel virtual addr of pcb
174 ("ki_kstack", void_p), # kernel virtual addr of stack
175 ("ki_udata", void_p), # User convenience pointer
176 ("ki_spareptrs", void_p*KI_NSPARE_PTR),
177 ("ki_sparelongs", ctypes.c_long*KI_NSPARE_LONG),
178 ("ki_sflag", ctypes.c_long), # PS_* flags
179 ("ki_tdflags", ctypes.c_long), # KSE kthread flag
180 )
181
182 libkvm.kvm_getprocs.argtypes = [caddr_t, ctypes.c_int, ctypes.c_int, caddr_t]
183 libkvm.kvm_getprocs.restype = ctypes.POINTER(KINFO_PROC)
184
185 libkvm.kvm_open.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_char_p]
186 libkvm.kvm_open.restype = caddr_t
187
188 # All the FreeBSD ptrace defines
189 PT_TRACE_ME = 0 #/* child declares it's being traced */
190 PT_READ_I = 1 #/* read word in child's I space */
191 PT_READ_D = 2 #/* read word in child's D space */
192 PT_WRITE_I = 4 #/* write word in child's I space */
193 PT_WRITE_D = 5 #/* write word in child's D space */
194 PT_CONTINUE = 7 #/* continue the child */
195 PT_KILL = 8 #/* kill the child process */
196 PT_STEP = 9 #/* single step the child */
197 PT_ATTACH = 10 #/* trace some running process */
198 PT_DETACH = 11 #/* stop tracing a process */
199 PT_IO = 12 #/* do I/O to/from stopped process. */
200 PT_LWPINFO = 13 #/* Info about the LWP that stopped. */
201 PT_GETNUMLWPS = 14 #/* get total number of threads */
202 PT_GETLWPLIST = 15 #/* get thread list */
203 PT_CLEARSTEP = 16 #/* turn off single step */
204 PT_SETSTEP = 17 #/* turn on single step */
205 PT_SUSPEND = 18 #/* suspend a thread */
206 PT_RESUME = 19 #/* resume a thread */
207 PT_TO_SCE = 20 # Stop on syscall entry
208 PT_TO_SCX = 21 # Stop on syscall exit
209 PT_SYSCALL = 22 # Stop on syscall entry and exit
210 PT_GETREGS = 33 #/* get general-purpose registers */
211 PT_SETREGS = 34 #/* set general-purpose registers */
212 PT_GETFPREGS = 35 #/* get floating-point registers */
213 PT_SETFPREGS = 36 #/* set floating-point registers */
214 PT_GETDBREGS = 37 #/* get debugging registers */
215 PT_SETDBREGS = 38 #/* set debugging registers */
216 #PT_FIRSTMACH = 64 #/* for machine-specific requests */
217
218 # On PT_IO addr is a pointer to a struct
219
220 -class PTRACE_IO_DESC(ctypes.Structure):
221 _fields_ = [
222 ("piod_op", ctypes.c_int), # I/O operation
223 ("piod_offs", ctypes.c_void_p), # Child offset
224 ("piod_addr", ctypes.c_void_p), # Parent Offset
225 ("piod_len", ctypes.c_uint) # Size
226 ]
227
228 # Operations in piod_op.
229 PIOD_READ_D = 1 # Read from D space
230 PIOD_WRITE_D = 2 # Write to D space
231 PIOD_READ_I = 3 # Read from I space
232 PIOD_WRITE_I = 4 # Write to I space
235 _fields_ = (
236 ("pl_lwpid", lwpid_t),
237 ("pl_event", ctypes.c_int),
238 ("pl_flags", ctypes.c_int),
239 ("pl_sigmask", sigset_t),
240 ("pl_siglist", sigset_t),
241 )
242
243 PL_EVENT_NONE = 0
244 PL_EVENT_SIGNAL = 1
245
246 PL_FLAGS_SA = 0
247 PL_FLAGS_BOUND = 1
250
252 self.initMode("Syscall", False, "Break on Syscalls")
253 self.kvmh = libkvm.kvm_open(None, None, None, 0, "vtrace")
254 if not os.path.exists('/proc/curproc/file'):
255 raise Exception("VDB needs /proc! (use: mount -t procfs procfs /proc)")
256
258 print "FIXME I DON'T THINK THIS IS BEING CALLED"
259 if self.kvmh != None:
260 libkvm.kvm_close(self.kvmh)
261
263 #FIXME optimize for speed!
264 iod = PTRACE_IO_DESC()
265 buf = ctypes.create_string_buffer(size)
266
267 iod.piod_op = PIOD_READ_D
268 iod.piod_addr = ctypes.addressof(buf)
269 iod.piod_offs = address
270 iod.piod_len = size
271
272 if v_posix.ptrace(PT_IO, self.pid, ctypes.addressof(iod), 0) != 0:
273 raise Exception("ptrace PT_IO failed to read 0x%.8x" % address)
274
275 return buf.raw
276
278 #FIXME optimize for speed!
279 iod = PTRACE_IO_DESC()
280
281 cbuf = ctypes.create_string_buffer(buf)
282
283 iod.piod_op = PIOD_WRITE_D
284 iod.piod_addr = ctypes.addressof(cbuf)
285 iod.piod_offs = address
286 iod.piod_len = len(buf)
287
288 if v_posix.ptrace(PT_IO, self.pid, ctypes.addressof(iod), 0) != 0:
289 raise Exception("ptrace PT_IO failed to read 0x%.8x" % address)
290
291 @v_base.threadwrap
295
298
299 #@v_base.threadwrap
301 # Basically just like the one in the Ptrace mixin...
302 self.execing = True
303 cmdlist = e_cli.splitargs(cmdline)
304 os.stat(cmdlist[0])
305 pid = os.fork()
306 if pid == 0:
307 v_posix.ptrace(PT_TRACE_ME, 0, 0, 0)
308 os.execv(cmdlist[0], cmdlist)
309 sys.exit(-1)
310 return pid
311
313 self.setMeta('ExeName', self._getExeName(self.pid))
314 return v_posix.PosixMixin.handleAttach(self)
315
316 @v_base.threadwrap
318 status = v_posix.PosixMixin.platformWait(self)
319 # Get the thread id from the ptrace interface
320
321 info = PTRACE_LWPINFO()
322 size = ctypes.sizeof(info)
323 if v_posix.ptrace(PT_LWPINFO, self.pid, ctypes.addressof(info), size) == 0:
324 self.setMeta("ThreadId", info.pl_lwpid)
325 else:
326 #FIXME this is because posix wait is linux specific and broke
327 self.setMeta("ThreadId", self.pid)
328
329 return status
330
331 @v_base.threadwrap
333 self.stepping = True
334 if v_posix.ptrace(PT_STEP, self.pid, 1, 0) != 0:
335 raise Exception("ptrace PT_STEP failed!")
336
337 @v_base.threadwrap
339 cmd = PT_CONTINUE
340 if self.getMode("Syscall"):
341 cmd = PT_SYSCALL
342
343 sig = self.getCurrentSignal()
344 if sig == None:
345 sig = 0
346 # In freebsd address is the place to continue from
347 # but 1 means use existing EIP
348 if v_posix.ptrace(cmd, self.pid, 1, sig) != 0:
349 raise Exception("ptrace PT_CONTINUE/PT_SYSCALL failed")
350
351 @v_base.threadwrap
355
356 @v_base.threadwrap
358 ret = {}
359 cnt = self._getThreadCount()
360 buf = (ctypes.c_int * cnt)()
361 if v_posix.ptrace(PT_GETLWPLIST, self.pid, ctypes.addressof(buf), cnt) != cnt:
362 raise Exception("ptrace PW_GETLWPLIST failed")
363 for x in buf:
364 ret[x] = x
365 return ret
366
367 @v_base.threadwrap
371
372 @v_base.threadwrap
376
379
382
384 # FIXME make this not need proc
385 ret = []
386 mpath = "/proc/%d/map" % self.pid
387
388 mapfile = file(mpath, "rb")
389 for line in mapfile:
390 perms = 0
391 fname = ""
392 maptup = line.split(None)
393 base = int(maptup[0], 16)
394 max = int(maptup[1], 16)
395 permstr = maptup[5]
396
397 if maptup[11] == "vnode":
398 fname = maptup[12].strip()
399
400 if permstr[0] == 'r':
401 perms |= e_mem.MM_READ
402
403 if permstr[1] == 'w':
404 perms |= e_mem.MM_WRITE
405
406 if permstr[2] == 'x':
407 perms |= e_mem.MM_EXEC
408
409 ret.append((base, max-base, perms, fname))
410
411 return ret
412
414 ret = []
415 cnt = ctypes.c_uint(0)
416
417 p = libkvm.kvm_getprocs(self.kvmh, KERN_PROC_PROC, 0, ctypes.addressof(cnt))
418 for i in xrange(cnt.value):
419 kinfo = p[i]
420 if kinfo.ki_structsize != ctypes.sizeof(KINFO_PROC):
421 print "WARNING: KINFO_PROC CHANGED SIZE, Trying to account for it... good luck"
422 ret.append((kinfo.ki_pid, kinfo.ki_comm))
423
424 ret.reverse()
425 return ret
426
428 _fields_ = (
429 ("fs", ctypes.c_ulong),
430 ("es", ctypes.c_ulong),
431 ("ds", ctypes.c_ulong),
432 ("edi", ctypes.c_ulong),
433 ("esi", ctypes.c_ulong),
434 ("ebp", ctypes.c_ulong),
435 ("isp", ctypes.c_ulong),
436 ("ebx", ctypes.c_ulong),
437 ("edx", ctypes.c_ulong),
438 ("ecx", ctypes.c_ulong),
439 ("eax", ctypes.c_ulong),
440 ("trapno", ctypes.c_ulong),
441 ("err", ctypes.c_ulong),
442 ("eip", ctypes.c_ulong),
443 ("cs", ctypes.c_ulong),
444 ("eflags", ctypes.c_ulong),
445 ("esp", ctypes.c_ulong),
446 ("ss", ctypes.c_ulong),
447 ("gs", ctypes.c_ulong),
448 ("debug0", ctypes.c_ulong),
449 ("debug1", ctypes.c_ulong),
450 ("debug2", ctypes.c_ulong),
451 ("debug3", ctypes.c_ulong),
452 ("debug4", ctypes.c_ulong),
453 ("debug5", ctypes.c_ulong),
454 ("debug6", ctypes.c_ulong),
455 ("debug7", ctypes.c_ulong),
456 )
457
458 i386_DBG_OFF = (19*4)
461 _fields_ = (
462 ("r15", ctypes.c_ulonglong),
463 ("r14", ctypes.c_ulonglong),
464 ("r13", ctypes.c_ulonglong),
465 ("r12", ctypes.c_ulonglong),
466 ("r11", ctypes.c_ulonglong),
467 ("r10", ctypes.c_ulonglong),
468 ("r9", ctypes.c_ulonglong),
469 ("r8", ctypes.c_ulonglong),
470 ("rdi", ctypes.c_ulonglong),
471 ("rsi", ctypes.c_ulonglong),
472 ("rbp", ctypes.c_ulonglong),
473 ("rbx", ctypes.c_ulonglong),
474 ("rdx", ctypes.c_ulonglong),
475 ("rcx", ctypes.c_ulonglong),
476 ("rax", ctypes.c_ulonglong),
477 ("trapno", ctypes.c_ulonglong),
478 ("err", ctypes.c_ulonglong),
479 ("rip", ctypes.c_ulonglong),
480 ("cs", ctypes.c_ulonglong),
481 ("rflags", ctypes.c_ulonglong),
482 ("rsp", ctypes.c_ulonglong),
483 ("ss", ctypes.c_ulonglong),
484 ("debug0", ctypes.c_ulonglong),
485 ("debug1", ctypes.c_ulonglong),
486 ("debug2", ctypes.c_ulonglong),
487 ("debug3", ctypes.c_ulonglong),
488 ("debug4", ctypes.c_ulonglong),
489 ("debug5", ctypes.c_ulonglong),
490 ("debug6", ctypes.c_ulonglong),
491 ("debug7", ctypes.c_ulonglong),
492 ("debug8", ctypes.c_ulonglong),
493 ("debug9", ctypes.c_ulonglong),
494 ("debug10", ctypes.c_ulonglong),
495 ("debug11", ctypes.c_ulonglong),
496 ("debug12", ctypes.c_ulonglong),
497 ("debug13", ctypes.c_ulonglong),
498 ("debug14", ctypes.c_ulonglong),
499 ("debug15", ctypes.c_ulonglong),
500 )
501
502 amd64_DBG_OFF = (22*ctypes.sizeof(ctypes.c_uint64))
503
504 -class FreeBSDi386Trace(
505 vtrace.Trace,
506 FreeBSDMixin,
507 v_i386.i386Mixin,
508 v_posix.ElfMixin,
509 v_posix.PosixMixin,
510 v_base.TracerBase):
511
513 vtrace.Trace.__init__(self)
514 v_base.TracerBase.__init__(self)
515 v_posix.ElfMixin.__init__(self)
516 v_posix.PosixMixin.__init__(self)
517 v_i386.i386Mixin.__init__(self)
518 FreeBSDMixin.__init__(self)
519
520 @v_base.threadwrap
522 ctx = self.archGetRegCtx()
523 u = bsd_regs_i386()
524
525 addr = ctypes.addressof(u)
526 if v_posix.ptrace(PT_GETREGS, tid, addr, 0) != 0:
527 raise Exception("ptrace PT_GETREGS failed!")
528 if v_posix.ptrace(PT_GETDBREGS, tid, addr+i386_DBG_OFF, 0) != 0:
529 raise Exception("ptrace PT_GETDBREGS failed!")
530
531 ctx._rctx_Import(u)
532
533 return ctx
534
535 @v_base.threadwrap
537 u = bsd_regs_i386()
538 ctx._rctx_Export(u)
539 addr = ctypes.addressof(u)
540 if v_posix.ptrace(PT_SETREGS, self.pid, addr, 0) != 0:
541 raise Exception("ptrace PT_SETREGS failed!")
542 if v_posix.ptrace(PT_SETDBREGS, self.pid, addr+i386_DBG_OFF, 0) != 0:
543 raise Exception("ptrace PT_SETDBREGS failed!")
544
545
546 -class FreeBSDAmd64Trace(
547 vtrace.Trace,
548 FreeBSDMixin,
549 v_amd64.Amd64Mixin,
550 v_posix.ElfMixin,
551 v_posix.PosixMixin,
552 v_base.TracerBase):
553
555 vtrace.Trace.__init__(self)
556 v_base.TracerBase.__init__(self)
557 v_posix.ElfMixin.__init__(self)
558 v_posix.PosixMixin.__init__(self)
559 v_amd64.Amd64Mixin.__init__(self)
560 FreeBSDMixin.__init__(self)
561
563 '''
564 Get (and populate) a register structure
565 (even set regs needs to get it first...)
566 '''
567 u = bsd_regs_amd64()
568 addr = ctypes.addressof(u)
569 if v_posix.ptrace(PT_GETREGS, tid, addr, 0) != 0:
570 raise Exception("ptrace PT_GETREGS failed!")
571 if v_posix.ptrace(PT_GETDBREGS, tid, addr+amd64_DBG_OFF, 0) != 0:
572 raise Exception("ptrace PT_GETDBREGS failed!")
573 return u
574
575 @v_base.threadwrap
577 ctx = self.archGetRegCtx()
578 u = self._getAmdRegsStruct(tid)
579 ctx._rctx_Import(u)
580 return ctx
581
582 @v_base.threadwrap
584 u = self._getAmdRegsStruct(tid)
585 ctx._rctx_Export(u)
586 addr = ctypes.addressof(u)
587 if v_posix.ptrace(PT_SETREGS, tid, addr, 0) != 0:
588 raise Exception("ptrace PT_SETREGS failed!")
589 if v_posix.ptrace(PT_SETDBREGS, tid, addr+amd64_DBG_OFF, 0) != 0:
590 raise Exception("ptrace PT_SETDBREGS failed!")
591
| Trees | Indices | Help |
|---|
| Generated by Epydoc 3.0.1 on Fri Nov 16 18:22:19 2012 | http://epydoc.sourceforge.net |