Commits

haypo  committed 0ea3ac9

Import tags and trunk from Fusil svn

  • Participants
  • Tags python-ptrace-0.4.1

Comments (0)

Files changed (82)

+ptrace authors
+==============
+
+Mickaël Guérin aka KAeL <mickael.guerin AT crocobox.org> - OpenBSD port
+T. Bursztyka aka Tuna <tuna AT lyua.org> - x86_64 port
+Victor Stinner <victor.stinner AT haypocalc.com>
+
+
+Thanks
+======
+
+ * procps authors (top and uptime programs)
+
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
+Changelog
+=========
+
+python-ptrace 0.4.0 (2008-08-19)
+--------------------------------
+
+Visible changes:
+
+ * Rename the project to "python-ptrace" (old name was "Ptrace)
+ * strace.py: create --ignore-regex option
+ * PtraceSignal: support SIGBUS, display the related registers and
+   the instruction
+ * Support execve() syscall tracing
+
+Developer changes:
+
+ * New API is incompatible with 0.3.2
+ * PtraceProcess.waitProcessEvent() accepts optional blocking=False argument
+ * PtraceProcess.getreg()/setreg() are able to read/write i386 and x86-64
+   "sub-registers" like al or bx
+ * Remove iterProc() function, replaced by openProc() with explicit
+   call to .close() to make sure that files are closed
+ * Create searchProcessesByName()
+ * Replace CPU_PPC constant by CPU_POWERPC and create CPU_PPC32 and CPU_PPC64
+ * Create MemoryMapping object, used by readMappings() and findStack() methods
+   of PtraceProcess
+ * Always define all PtraceProcess methods but raise an error if the function
+   is not implemented
+
+Version 0.3.2 (2008-07-25)
+--------------------------
+
+ * Rewrite ip_int2str() using inet_ntoa() to avoid IPy dependency
+ * Add kill() and unlink() syscall prototypes
+ * Fix sign conversion error in ptrace() to fix error detection
+ * Catch OSError in ptrace.disasm (unable to find libdistorm64.so)
+ * PtraceDebugger.addProcess(): detach the process on exception
+ * Breakpoint: don't store bytes if the process is not running anymore
+ * writeError() now re-raise KeyboardInterrupt
+ * PtraceProcess: don't detach or terminate process if it is was running
+ * PtraceProcess: never send SIGTRAP signal to a process!
+
+Version 0.3.1 (2008-07-08)
+--------------------------
+
+Minor update:
+
+ * ptrace.ctypes_errno: use ctypes_support.get_errno() when it's available
+ * Create RUNNING_PYPY constant is ptrace.os_tools
+ * Remove ptrace dependency from ptrace.pydistorm to be able to use
+   it outside ptrace
+
+Version 0.3 (2008-03-26)
+------------------------
+
+ * Support OpenBSD i386
+ * Use ptrace_io() on FreeBSD for faster readBytes()/writeBytes() methods
+ * Use ptrace_peekuser() to read registers on OS without ptrace_getregs()
+   (eg. Linux 2.4 on PPC)
+ * Breakpoint works on PPC CPU (use TRAP instruction)
+ * Delete process and raise ProcessExit on abnormal process death
+   (eg. detected by waitpid(pid))
+ * Write new Python binding to distorm64 library
+ * gdb.py: create "backtrace" command
+ * gdb.py: support operators in expressions (eg. $eip+4)
+
+
+Version 0.2 (2008-02-14)
+------------------------
+
+ * Able to trace multiple processes
+ * Many new gdb.py commands: hexdump, signal, print, etc.
+ * Support i386 (Linux, FreeBSD), x86_64 (Linux) and PPC (Linux)
+ * Guess reason why a signal is sent: invalid memory read, stack
+   overflow, division by zero, etc.
+ * Create simple C program to test strace.py and gdb.py
+ * Move files to three main modules: ptrace.binding, ptrace.syscall
+   and ptrace.debugger
+
+Version 0.1 (2008-02-08)
+------------------------
+
+ * First public release
+
+ptrace dependencies
+===================
+
+ * Python 2.4:
+   http://python.org/
+ * ctypes Python module (part of Python 2.5+)
+   (Debian package: python-ctypes)
+ * distorm disassembler (optional)
+   http://www.ragestorm.net/distorm/
+
+
+Installation
+============
+
+Type as root:
+
+   ./setup.py install
+
+Or using sudo program:
+
+   sudo python setup.py install
+
+python-ptrace is a Python binding of ptrace library.
+
+The binding works on:
+
+ * Linux version 2.6.20 on i386, x86_64, PPC (may works on Linux 2.4.x and 2.6.x)
+ * Linux version 2.4 on PPC
+ * FreeBSD version 7.0RC1 on i386 (may works on FreeBSD 5.x/6.x)
+ * OpenBSD version 4.2 on i386
+
+Features:
+
+ * High level Python object API : !PtraceDebugger and !PtraceProcess
+ * Able to control multiple processes: catch fork events on Linux
+ * Read/write bytes to arbitrary address: take care of memory alignment and split bytes to cpu word
+ * Execution step by step using ptrace_singlestep() or hardware interruption 3
+ * Can use distorm (http://www.ragestorm.net/distorm/) disassembler
+ * Dump registers, memory mappings, stack, etc.
+ * Syscall tracer and parser (strace command)
+
+Website: http://fusil.hachoir.org/trac/wiki/python-ptrace
+
+
+Installation
+============
+
+Read INSTALL documentation file.
+

File README.cptrace

++++++++++++++++++++++
+cptrace Python module
++++++++++++++++++++++
+
+Python binding for ptrace written in C.
+
+Example
+=======
+
+Dummy example: ::
+
+    >>> import cptrace
+    >>> cptrace.ptrace(1, 1)
+    Traceback (most recent call last):
+      File "<stdin>", line 1, in <module>
+    ValueError: ptrace(request=1, pid=1, 0x(nil), 0x(nil)) error #1: Operation not permitted
+
+
+TODO
+====
+
+Main tasks
+----------
+
+ * Support other backends:
+
+    - GDB MI: http://code.google.com/p/pygdb/
+    - ktrace: (FreeBSD and Darwin): would help Darwin support
+    - utrace (new Linux debugger): http://sourceware.org/systemtap/wiki/utrace
+    - vtrace?
+    - PyDBG: works on Windows
+
+ * Backtrace symbols:
+
+   - GNU BFD?
+   - elfsh?
+   - addr2line program?
+   - See dl_iterate_phdr() function of libdl
+
+ * Support other disassemblers (than distorm), and so both Intel syntax (Intel and AT&T)
+
+   - BFD
+   - http://www.ragestorm.net/distorm/
+   - libasm (ERESI)
+   - libdisasm (bastard)
+   - http://www.woodmann.com/collaborative/tools/index.php/Category:X86_Disassembler_Libraries
+
+ * Support threads: other backends (than python-ptrace) already support threads
+
+Minor tasks
+-----------
+
+ * Fix gdb.py "step" command on a jump. Example where step will never stop: ::
+
+(gdb) where
+ASM 0xb7e3b917: JMP 0xb7e3b8c4 (eb ab)
+ASM 0xb7e3b919: LEA ESI, [ESI+0x0] (8db426 00000000)
+
+ * Remove gdb.py "except PtraceError, err: if err.errno == ESRCH" hack,
+   process death detection should be done by PtraceProcess or PtraceDebugger
+ * Use Intel hardware breakpoints: set vtrace source code
+ * Support Darwin:
+
+   - ktrace? need to recompile Darwin kernel with KTRACE option
+   - get registers: http://unixjunkie.blogspot.com/2006/01/darwin-ptrace-and-registers.html
+   - PT_DENY_ATTACH: http://steike.com/code/debugging-itunes-with-gdb/
+   - PT_DENY_ATTACH: http://landonf.bikemonkey.org/code/macosx/ptrace_deny_attach.20041010201303.11809.mojo.html
+

File cptrace/Makefile

+CC=gcc
+CFLAGS=-fPIC -shared -Wall -Wextra -Wextra $(shell python-config --cflags)
+LIBS=$(shell python-config --libs)
+LIBRARY=cptrace.so
+
+$(LIBRARY): cptrace.c
+	$(CC) -o $@ $< $(CFLAGS) $(LIBS)
+
+clean:
+	rm -f $(LIBRARY)

File cptrace/cptrace.c

+#include <Python.h>
+#include <stdbool.h>
+#include <sys/ptrace.h>
+#include <linux/user.h>
+
+#define UNUSED(arg) arg __attribute__((unused))
+
+char python_ptrace_DOCSTR[] =
+"ptrace(command: int, pid: int, arg1=0, arg2=0, check_errno=False): call ptrace syscall.\r\n"
+"Raise a ValueError on error.\r\n"
+"Returns an unsigned integer.\r\n";
+
+static bool cpython_cptrace(
+    unsigned int request,
+    pid_t pid,
+    void *arg1,
+    void *arg2,
+    bool check_errno,
+    unsigned long *result)
+{
+    unsigned long ret;
+    ret = ptrace(request, pid, arg1, arg2);
+    if ((long)ret == -1) {
+        /**
+         * peek operations may returns -1 with errno=0: it's not an error.
+         * For other operations, -1 is always an error
+         */
+        if (!check_errno || errno) {
+            PyErr_Format(
+                PyExc_ValueError,
+                "ptrace(request=%u, pid=%i, %p, %p) "
+                "error #%i: %s",
+                request, pid, arg1, arg2,
+                errno, strerror(errno));
+            return false;
+        }
+    }
+    if (result)
+        *result = ret;
+    return true;
+}
+
+static PyObject* cpython_ptrace(PyObject* UNUSED(self), PyObject *args, PyObject *keywds)
+{
+    unsigned long result;
+    unsigned int request;
+    pid_t pid;
+    unsigned long arg1 = 0;
+    unsigned long arg2 = 0;
+    bool check_errno = false;
+    PyObject* check_errno_p = NULL;
+    static char *kwlist[] = {"request", "pid", "arg1", "arg2", "check_errno", NULL};
+
+    if (!PyArg_ParseTupleAndKeywords(args, keywds,
+        "Ii|IIO", kwlist,
+        &request, &pid, &arg1, &arg2, &check_errno_p
+    ))
+    {
+        return NULL;
+    }
+
+    if (check_errno_p) {
+        check_errno = PyObject_IsTrue(check_errno_p);
+    }
+
+    if (cpython_cptrace(request, pid, (void*)arg1, (void*)arg2, check_errno, &result))
+        return PyLong_FromUnsignedLong(result);
+    else
+        return NULL;
+}
+
+static PyMethodDef moduleMethods[] = {
+    {"ptrace", (PyCFunction)cpython_ptrace, METH_VARARGS | METH_KEYWORDS, python_ptrace_DOCSTR},
+    {NULL, NULL, 0, NULL}
+};
+
+PyMODINIT_FUNC initcptrace(void)
+{
+    (void)Py_InitModule3("cptrace", moduleMethods, "ptrace module written in C");
+}
+

File cptrace/version.py

+PACKAGE = "ptrace"
+VERSION = "0.4"
+WEBSITE = "http://fusil.hachoir.org/trac/wiki/Ptrace"
+LICENSE = "GNU GPL v2"

File doc/process_events.rst

+Process events
+==============
+
+All process events are based on ProcessEvent class.
+
+ * ProcessExit: process exited with an exitcode, killed by a signal
+   or exited abnormally
+ * ProcessSignal: process received a signal
+ * NewProcessEvent: new process created, eg. after a fork() syscall
+
+Attributes:
+
+ * All events have a "process" attribute
+ * ProcessExit has "exitcode" and "signum" attributes (both can be None)
+ * ProcessSignal has "signum" and "name" attributes
+
+For NewProcessEvent, use process.parent attribute to get the parent process.
+
+Note: ProcessSignal has a display() method to display its content. Use it
+just after receiving the message because it reads process memory to analyze
+the reasons why the signal was sent.
+
+
+Wait for any process event
+==========================
+
+The most generic function is waitProcessEvent(): it waits for any process
+event (exit, signal or new process): ::
+
+   event = debugger.waitProcessEvent()
+
+To wait one or more signals, use waitSignals() methods. With no argument,
+it waits for any signal. Events different than signal are raised as
+Python exception. Examples: ::
+
+   signal = debugger.waitSignals()
+   signal = debugger.waitSignals(SIGTRAP)
+   signal = debugger.waitSignals(SIGINT, SIGTERM)
+
+Note: signal is a ProcessSignal object, use signal.signum to get
+the signal number.
+
+
+Wait for a specific process events
+==================================
+
+To wait any event from a process, use waitEvent() method: ::
+
+   event = process.waitEvent()
+
+To wait one or more signals, use waitSignals() method. With no argument,
+it waits for any signal. Other process events are raised as Python
+exception. Examples: ::
+
+   signal = process.waitSignals()
+   signal = process.waitSignals(SIGTRAP)
+   signal = process.waitSignals(SIGINT, SIGTERM)
+
+Note: As debugger.waitSignals(), signal is a ProcessSignal object.
+

File doc/ptrace_signal.rst

+++++++++++++
+PtraceSignal
+++++++++++++
+
+
+Introduction
+============
+
+PtraceSignal tries to display useful informations when a signal is received.
+Depending on the signal number, it show different informations.
+
+It uses the current instruction decoded as assembler code to understand why
+the signal is raised.
+
+Only Intel x86 (i386, maybe x86_64) is supported now.
+
+
+Examples
+========
+
+Invalid read: ::
+
+    Signal: SIGSEGV
+    Invalid read from 0x00000008
+    - instruction: MOV EAX, [EAX+0x8]
+    - mapping: (no memory mapping)
+    - register eax=0x00000000
+
+Invalid write (MOV): ::
+
+    Signal: SIGSEGV
+    Invalid write to 0x00000008 (size=4 bytes)
+    - instruction: MOV DWORD [EAX+0x8], 0x2a
+    - mapping: (no memory mapping)
+    - register eax=0x00000000
+
+abort(): ::
+
+    Signal: SIGABRT
+    Program received signal SIGABRT, Aborted.
+
+#!/usr/bin/env python
+from ptrace import PtraceError
+from ptrace.debugger import (PtraceDebugger, Application,
+    ProcessExit, NewProcessEvent, ProcessSignal,
+    ProcessExecution, ProcessError)
+from optparse import OptionParser
+from sys import stdout, stderr, exit
+from logging import getLogger, info, warning, error
+from ptrace.error import PTRACE_ERRORS, writeError
+from ptrace.binding import HAS_PTRACE_SINGLESTEP
+from ptrace.disasm import HAS_DISASSEMBLER
+from ptrace.ctypes_tools import (truncateWord,
+    formatWordHex, formatAddress, formatAddressRange)
+from ptrace.process_tools import dumpProcessInfo
+from ptrace.tools import inverseDict
+from ptrace.func_call import FunctionCallOptions
+from ptrace.signames import signalName, SIGNAMES
+from signal import SIGTRAP, SIGINT
+from ptrace.terminal import enableEchoMode, terminalWidth
+from errno import ESRCH
+from ptrace.cpu_info import CPU_POWERPC
+import re
+try:
+    # Use readline for better raw_input()
+    import readline
+except ImportError:
+    pass
+
+# Match a register name: $eax, $gp0, $orig_eax
+REGISTER_REGEX = re.compile(r"\$[a-z]+[a-z0-9_]+")
+
+SIGNALS = inverseDict(SIGNAMES)   # name -> signum
+
+COMMANDS = (
+    # trace instructions
+    ("cont", "continue execution"),
+    ("step", "execute one instruction (do not enter in a call)"),
+    ("stepi", "execute one instruction (enter the call)"),
+    ("until", "execute code until specified address (until <address>)"),
+    ("set", "set register value (set <register>=<value>)"),
+    ("sys", "continue execution to next syscall"),
+    ("signal", "send a signal to the process (signal <signum>)"),
+    ("signals", "display signals"),
+
+    # current process info
+    ("regs", "display registers"),
+    ("where", "display true code content (show breakpoints effects on code). eg. 'where $eip', 'where $eip $eip+20'"),
+    ("print", "display a value (print <value>)"),
+    ("hexdump", "dump memory as specified address or address range (hexdump <address> or hexdump <start> <stop>)"),
+    ("where2", "display original code content (don't show effects of breakpoint on code)"),
+    ("stack", "display stack content"),
+    ("backtrace", "dump the bakctrace"),
+    ("proc", "display process informations"),
+    ("maps", "display memory mappings"),
+
+    # breakpoints
+    ("break", "set a breakpoint (break <address>)"),
+    ("breakpoints", "display breakpoints"),
+    ("delete", "delete a breakpoint (delete <address>)"),
+
+    # processes
+    ("attach", 'attach a new process (eg. "attach 2390")'),
+    ("proclist", "list of traced processes"),
+    ("switch", "switch active process (switch or switch <pid>)"),
+
+    # other
+    ("quit", "quit debugger"),
+    ("help", "display this help"),
+)
+
+class Gdb(Application):
+    def __init__(self):
+        Application.__init__(self)
+
+        # Parse self.options
+        self.parseOptions()
+
+        # Setup output (log)
+        self.setupLog()
+
+        self.last_signal = {}
+
+        # We assume user wants all possible informations
+        self.syscall_options = FunctionCallOptions(
+            write_types=True,
+            write_argname=True,
+            write_address=True,
+        )
+
+        # FIXME: Remove self.breaks!
+        self.breaks = dict()
+
+    def setupLog(self):
+        self._setupLog(stdout)
+
+    def parseOptions(self):
+        parser = OptionParser(usage="%prog [options] -- program [arg1 arg2 ...]")
+        self.createCommonOptions(parser)
+        self.createLogOptions(parser)
+        self.options, self.program = parser.parse_args()
+
+        if self.options.pid is None and not self.program:
+            parser.print_help()
+            exit(1)
+
+        self.processOptions()
+        self.show_pid = self.options.fork
+
+    def _continueProcess(self, process, signum=None):
+        if not signum and process in self.last_signal:
+            signum = self.last_signal[process]
+
+        if signum:
+            error("Send %s to %s" % (signalName(signum), process))
+            process.cont(signum)
+            try:
+                del self.last_signal[process]
+            except KeyError:
+                pass
+        else:
+            process.cont()
+
+    def cont(self, signum=None):
+        for process in self.debugger:
+            process.syscall_state.clear()
+            if process == self.process:
+                self._continueProcess(process, signum)
+            else:
+                self._continueProcess(process)
+
+        # Wait for a process signal
+        signal = self.debugger.waitSignals()
+        process = signal.process
+
+        # Hit breakpoint?
+        if signal.signum == SIGTRAP:
+            ip = self.process.getInstrPointer()
+            if not CPU_POWERPC:
+                # Go before "INT 3" instruction
+                ip -= 1
+            breakpoint = self.process.findBreakpoint(ip)
+            if breakpoint:
+                error("Stopped at %s" % breakpoint)
+                breakpoint.desinstall(set_ip=True)
+        else:
+            self.processSignal(signal)
+        return None
+
+    def readRegister(self, regs):
+        name = regs.group(0)[1:]
+        value = self.process.getreg(name)
+        return str(value)
+
+    def parseInteger(self, text):
+        # Remove spaces and convert to lower case
+        text = text.strip()
+        if " " in text:
+            raise ValueError("Space are forbidden: %r" % text)
+        text = text.lower()
+
+        # Replace registers by their value
+        orig_text = text
+        text = REGISTER_REGEX.sub(self.readRegister, text)
+
+        # Replace hexadecimal numbers by decimal numbers
+        def readHexadecimal(regs):
+            text = regs.group(0)
+            if text.startswith("0x"):
+                text = text[2:]
+            elif not re.search("[a-f]", text):
+                return text
+            value = int(text, 16)
+            return str(value)
+        text = re.sub(r"(?:0x)?[0-9a-f]+", readHexadecimal, text)
+
+        # Reject invalid characters
+        if not re.match(r"^[()<>+*/0-9-]+$", text):
+            raise ValueError("Invalid expression: %r" % orig_text)
+
+        # Use integer division (a//b) instead of float division (a/b)
+        text = text.replace("/", "//")
+
+        # Finally, evaluate the expression
+        is_pointer = text.startswith("*")
+        if is_pointer:
+            text = text[1:]
+        try:
+            value = eval(text)
+            value = truncateWord(value)
+        except SyntaxError:
+            raise ValueError("Invalid expression: %r" % orig_text)
+        if is_pointer:
+            value = self.process.readWord(value)
+        return value
+
+    def parseIntegers(self, text):
+        values = []
+        for item in text.split():
+            item = item.strip()
+            value = self.parseInteger(item)
+            values.append(value)
+        return values
+
+    def execute(self, command):
+        errmsg = None
+        if command == "cont":
+            errmsg = self.cont()
+        elif command == "proc":
+            self.procInfo()
+        elif command == "proclist":
+            self.procList()
+        elif command.startswith("attach "):
+            self.attachProcess(command[7:])
+        elif command == "regs":
+            self.process.dumpRegs()
+        elif command == "stack":
+            self.process.dumpStack()
+        elif command == "backtrace":
+            errmsg = self.backtrace()
+        elif command == "where" or command.startswith("where "):
+            errmsg = self.where(command[6:])
+        elif command == "where2" or command.startswith("where2 "):
+            errmsg = self.where(command[7:], manage_bp=True)
+        elif command == "maps":
+            self.process.dumpMaps()
+        elif command == "step":
+            errmsg = self.step(False)
+        elif command == "stepi":
+            errmsg = self.step(True)
+        elif command == "sys":
+            errmsg = self.syscallTrace()
+        elif command == "help":
+            self.help()
+        elif command.startswith("set "):
+            errmsg = self.set(command)
+        elif command.startswith("until "):
+            errmsg = self.until(command[6:])
+        elif command.startswith("switch") or command == "switch":
+            errmsg = self.switch(command[6:])
+        elif command.startswith("break "):
+            errmsg = self.breakpoint(command[6:])
+        elif command.startswith("breakpoints"):
+            self.displayBreakpoints()
+        elif command.startswith("signals"):
+            self.displaySignals()
+        elif command.startswith("delete "):
+            errmsg = self.delete(command[7:])
+        elif command.startswith("hexdump "):
+            errmsg = self.hexdump(command[8:])
+        elif command.startswith("signal "):
+            errmsg = self.signal(command[7:])
+        elif command.startswith("print "):
+            errmsg = self.print_(command[6:])
+        else:
+            errmsg = "Unknown command: %r" % command
+        if errmsg:
+            print >>stderr, errmsg
+            return False
+        return True
+
+    def parseSignum(self, command):
+        try:
+            return SIGNALS[command]
+        except KeyError:
+            pass
+        try:
+            return SIGNALS["SIG"+command]
+        except KeyError:
+            pass
+        try:
+            return self.parseInteger(command)
+        except ValueError, err:
+            raise ValueError("Invalid signal number: %r" % command)
+
+    def signal(self, command):
+        try:
+            signum = self.parseSignum(command)
+        except ValueError, err:
+            return str(err)
+        last_process = self.process
+        try:
+            errmsg = self.cont(signum)
+            return errmsg
+        finally:
+            try:
+                del self.last_signal[last_process]
+            except KeyError:
+                pass
+
+    def print_(self, command):
+        try:
+            value = self.parseInteger(command)
+        except ValueError, err:
+            return str(err)
+        error("Decimal: %s" % value)
+        error("Hexadecimal: %s" % formatWordHex(value))
+        for map in self.process.readMappings():
+            if value not in map:
+                continue
+            error("Address is part of mapping: %s" % map)
+        return None
+
+    def hexdump(self, command):
+        max_line = 20
+        width = (terminalWidth() - len(formatAddress(1)) - 3) // 4
+        width = max(width, 1)
+
+        limited = None
+        parts = command.split(" ", 1)
+        if 1 < len(parts):
+            try:
+                start_address = self.parseInteger(parts[0])
+                end_address = self.parseInteger(parts[1])
+                if end_address <= start_address:
+                    raise ValueError('End address (%s) is smaller than start address(%s)!'
+                        % (formatAddress(end_address), formatAddress(start_address)))
+            except ValueError, err:
+                return str(err)
+            size = end_address - start_address
+            max_size = width*max_line
+            if max_size < size:
+                limited = max_size
+                end_address = start_address + max_size
+        else:
+            try:
+                start_address = self.parseInteger(command)
+            except ValueError, err:
+                return str(err)
+            end_address = start_address + 5*width
+
+        read_error = None
+        address = start_address
+        while address < end_address:
+            size = min(end_address - address, width)
+            try:
+                # Read bytes
+                bytes = self.process.readBytes(address, size)
+
+                # Format bytes
+                hexa = ' '.join( "%02x" % ord(byte) for byte in bytes )
+                def toAscii(byte):
+                    if 32 <= ord(byte) <= 127:
+                        return byte
+                    else:
+                        return '.'
+                ascii = ''.join(toAscii(byte) for byte in bytes)
+                hexa = hexa.ljust(width*3-1, ' ')
+                ascii = ascii.ljust(width, ' ')
+
+                # Display previous read error, if any
+                if read_error:
+                    warning("Warning: Unable to read memory %s" % (
+                        formatAddressRange(*read_error)))
+                    read_error = None
+
+                # Display line
+                error("%s| %s| %s" % (formatAddress(address), hexa, ascii))
+            except PtraceError:
+                if not read_error:
+                    read_error = [address, address + size]
+                else:
+                    read_error[1] = address + size
+            address += size
+
+        # Display last read error, if any
+        if read_error:
+            warning("Warning: Unable to read memory %s" % (
+                formatAddressRange(*read_error)))
+        if limited:
+            warning("(limit to %s bytes)" % max_size)
+        return None
+
+    def backtrace(self):
+        trace = self.process.getBacktrace()
+        for func in trace:
+            error(func)
+        if trace.truncated:
+            error("--limited to depth %s--" % len(trace))
+        return None
+
+    def where(self, command, manage_bp=False):
+        start = None
+        stop = None
+        try:
+            values = self.parseIntegers(command)
+        except ValueError, err:
+            return str(err)
+        if 1 <= len(values):
+            start = values[0]
+        if 2 <= len(values):
+            stop = values[1]
+        self.process.dumpCode(start, stop, manage_bp=manage_bp)
+        return None
+
+    def procInfo(self):
+        dumpProcessInfo(error, self.process.pid, max_length=160)
+
+    def procList(self):
+        for process in self.debugger:
+            text = str(process)
+            if self.process == process:
+                text += " (active)"
+            error(text)
+
+    def set(self, command):
+        try:
+            key, value = command[4:].split("=", 1)
+            key = key.strip().lower()
+            if not key.startswith("$"):
+                return 'Register name (%s) have to start with "$"' % key
+            key = key[1:]
+        except ValueError, err:
+             return "Invalid command: %r" % command
+        try:
+            value = self.parseInteger(value)
+        except ValueError, err:
+            return str(err)
+        try:
+            self.process.setreg(key, value)
+        except ProcessError, err:
+            return "Unable to set $%s=%s: %s" % (key, value, err)
+        error("Set $%s to %s" % (key, value))
+        return None
+
+    def displayInstr(self, prefix):
+        try:
+            if HAS_DISASSEMBLER:
+                instr = self.process.disassembleOne()
+                error("%s %s: %s" % (
+                    prefix, formatAddress(instr.address), instr.text))
+            else:
+                self.process.dumpCode()
+        except PtraceError, err:
+            error("Unable to read current instruction: %s" % err)
+
+    def attachProcess(self, text):
+        try:
+            pid = self.parseInteger(text)
+        except ValueError, err:
+             return str(err)
+        process = self.debugger.addProcess(pid, False)
+        self.switchProcess(process)
+
+    def step(self, enter_call, address=None):
+        if address is None:
+            self.displayInstr("Execute")
+        if (not HAS_PTRACE_SINGLESTEP) or (not enter_call):
+            if address is None:
+                address = self.process.getInstrPointer()
+                size = self.readInstrSize(address, default_size=None)
+                if not size:
+                    return "Unable to read instruction size at %s" \
+                        % formatAddress(address)
+                address += size
+            size = self.readInstrSize(address)
+
+            # Set a breakpoint
+            breakpoint = self.process.createBreakpoint(address, size)
+
+            # Continue the process
+            self.process.cont()
+        else:
+            # Use ptrace single step command
+            self.process.singleStep()
+            breakpoint = None
+
+        # Execute processus until next TRAP
+        try:
+            self.process.waitSignals(SIGTRAP)
+            if breakpoint:
+                breakpoint.desinstall(set_ip=True)
+        except:
+            if breakpoint:
+                breakpoint.desinstall()
+            raise
+        return None
+
+    def newProcess(self, event):
+        error("New process: %s" % event.process)
+
+    # FIXME: This function doesn't work multiple multiple processes
+    # especially when a parent waits for a child
+    def syscallTrace(self):
+        # Trace until syscall enter
+        self.process.syscall()
+        self.process.waitSyscall()
+
+        # Process the syscall event
+        state = self.process.syscall_state
+        syscall = state.event(self.syscall_options)
+
+        # Display syscall
+        if syscall:
+            if syscall.result is not None:
+                text = "%s = %s" % (syscall.format(), syscall.result_text)
+                if self.show_pid:
+                    text = "Process %s exits %s" % (syscall.process.pid, text)
+                error(text)
+            else:
+                text = syscall.format()
+                if self.show_pid:
+                    text = "Process %s enters %s" % (syscall.process.pid, text)
+                error(text)
+        return None
+
+    def until(self, command):
+        try:
+            address = self.parseInteger(command)
+        except ValueError, err:
+             return str(err)
+        errmsg = self.step(False, address)
+        if errmsg:
+            return errmsg
+        self.displayInstr("Current")
+        return None
+
+    def switch(self, command):
+        if not command:
+            process_list = self.debugger.list
+            if len(process_list) == 1:
+                return "There is only one process!"
+            index = process_list.index(self.process)
+            index = (index + 1) % len(process_list)
+            process = process_list[index]
+            self.switchProcess(process)
+            return
+        try:
+            pid = self.parseInteger(command)
+        except ValueError, err:
+             return str(err)
+        try:
+            process = self.debugger[pid]
+            self.switchProcess(process)
+        except KeyError:
+            return "There is not process %s" % pid
+        return None
+
+    def switchProcess(self, process):
+        if self.process == process:
+            return
+        error("Switch to %s" % process)
+        self.process = process
+
+    def nextProcess(self):
+        try:
+            process = iter(self.debugger).next()
+            self.switchProcess(process)
+        except StopIteration:
+            pass
+
+    def displayBreakpoints(self):
+        found = False
+        for process in self.debugger:
+            for bp in process.breakpoints.itervalues():
+                found = True
+                error("%s:%s" % (process, bp))
+        if not found:
+            error("(no breakpoint)")
+
+    def displaySignals(self):
+        signals = SIGNAMES.items()
+        signals.sort(key=lambda (key, value): key)
+        for signum, name in signals:
+            error("% 2s: %s" % (signum, name))
+
+    def readInstrSize(self, address, default_size=None):
+        if not HAS_DISASSEMBLER:
+            return default_size
+        try:
+            # Get address and size of instruction at specified address
+            instr = self.process.disassembleOne(address)
+            return instr.size
+        except PtraceError, err:
+            warning("Warning: Unable to read instruction size at %s: %s" % (
+                formatAddress(address), err))
+            return default_size
+
+    def breakpoint(self, command):
+        try:
+            address = self.parseInteger(command)
+        except ValueError, err:
+            return str(err)
+
+        # Create breakpoint
+        size = self.readInstrSize(address)
+        try:
+            bp = self.process.createBreakpoint(address, size)
+        except PtraceError, err:
+            return "Unable to set breakpoint at %s: %s" % (
+                formatAddress(address), err)
+        error("New breakpoint: %s" % bp)
+        return None
+
+    def delete(self, command):
+        try:
+            address = self.parseInteger(command)
+        except ValueError, err:
+            return str(err)
+
+        breakpoint = self.process.findBreakpoint(address)
+        if not breakpoint:
+            return "No breakpoint at %s " % formatAddress(address)
+        breakpoint.desinstall()
+        error("%s deleted" % breakpoint)
+        return None
+
+    def help(self):
+        for command, description in COMMANDS:
+            error("%s: %s" % (command, description))
+        error('')
+        error("Value can be an hexadecimal/decimal number or a register name ($reg)")
+        error("You can use operators a+b, a-b, a*b, a/b, a<<b, a>>b, a**b, and parenthesis in expressions")
+        error('Use ";" to write multiple commands on the same line (eg. "step; print $eax")')
+
+    def processSignal(self, event):
+        event.display()
+        self.switchProcess(event.process)
+        self.last_signal[self.process] = event.signum
+        error("%s interrupted by %s" % (self.process, event.name))
+
+    def processExecution(self, event):
+        error(event)
+        self.switchProcess(event.process)
+        self.interrupt()
+
+    def interrupt(self):
+        waitlist = []
+        for process in self.debugger:
+            if process.is_stopped:
+                continue
+            try:
+                if process.isTraced():
+                    continue
+            except NotImplementedError:
+                pass
+            warning("Interrupt %s (send SIGINT)" % process)
+            process.kill(SIGINT)
+            waitlist.append(process)
+        for process in waitlist:
+            info("Wait %s interruption" % process)
+            try:
+                process.waitSignals(SIGINT)
+            except ProcessSignal, event:
+                event.display()
+            except KeyboardInterrupt:
+                pass
+
+    def deleteProcess(self, pid):
+        try:
+            process = self.debugger[pid]
+        except KeyError:
+            return
+        event = process.processTerminated()
+        error(str(event))
+        if process == self.process:
+            self.nextProcess()
+
+    def restoreTerminal(self):
+        if enableEchoMode():
+            error("Terminal: restore echo mode")
+
+    def mainLoop(self):
+        # Read command
+        try:
+            self.restoreTerminal()
+            command = raw_input(self.invite).strip()
+        except EOFError:
+            print
+            return True
+        except KeyboardInterrupt:
+            error("User interrupt!")
+            self.interrupt()
+            return False
+
+        # If command is empty, reuse previous command
+        if not command:
+            if self.previous_command:
+                command = self.previous_command
+                info("Replay previous command: %s" % command)
+            else:
+                return False
+        self.previous_command = None
+
+        # User wants to quit?
+        if command == "quit":
+            return True
+
+        # Execute the user command
+        try:
+            command_str = command
+            ok = True
+            for command in command_str.split(";"):
+                command = command.strip()
+                ok &= self.execute(command)
+            if ok:
+                self.previous_command = commands
+        except KeyboardInterrupt:
+            self.interrupt()
+        except NewProcessEvent, event:
+            self.newProcess(event)
+        except ProcessSignal, event:
+            self.processSignal(event)
+        except ProcessExit, event:
+            error(event)
+            self.nextProcess()
+        except ProcessExecution, event:
+            self.processExecution(event)
+        except PtraceError, err:
+            error("ERROR: %s" % err)
+            if err.errno == ESRCH:
+                self.deleteProcess(err.pid)
+        return False
+
+    def main(self):
+        self.debugger = PtraceDebugger()
+        try:
+            self.setupDebugger()
+
+            # Create new process
+            self.process = self.createProcess()
+            if not self.process:
+                return
+
+            # Trace syscalls
+            self.invite = '(gdb) '
+            self.previous_command = None
+            done = False
+            while not done:
+                if not self.debugger:
+                    # There is no more process: quit
+                    break
+                done = self.mainLoop()
+        except KeyboardInterrupt:
+            error("Interrupt debugger: quit!")
+        except PTRACE_ERRORS, err:
+            writeError(getLogger(), err, "Debugger error")
+        self.process = None
+        self.debugger.quit()
+        error("Quit gdb.")
+        self.restoreTerminal()
+
+if __name__ == "__main__":
+    Gdb().main()
+

File ptrace/__init__.py

+from ptrace.signames import SIGNAMES, signalName
+from ptrace.error import PtraceError
+

File ptrace/binding/__init__.py

+from ptrace.binding.func import (
+    HAS_PTRACE_SINGLESTEP, HAS_PTRACE_EVENTS,
+    HAS_PTRACE_IO, HAS_PTRACE_SIGINFO, HAS_PTRACE_GETREGS,
+    REGISTER_NAMES,
+    ptrace_attach, ptrace_traceme,
+    ptrace_detach, ptrace_kill,
+    ptrace_cont, ptrace_syscall,
+    ptrace_setregs,
+    ptrace_peektext, ptrace_poketext,
+    ptrace_peekuser,
+    ptrace_registers_t)
+if HAS_PTRACE_EVENTS:
+    from ptrace.binding.func import (WPTRACEEVENT,
+        PTRACE_EVENT_FORK, PTRACE_EVENT_VFORK, PTRACE_EVENT_CLONE,
+        PTRACE_EVENT_EXEC,
+        ptrace_setoptions, ptrace_geteventmsg)
+if HAS_PTRACE_SINGLESTEP:
+    from ptrace.binding.func import ptrace_singlestep
+if HAS_PTRACE_SIGINFO:
+    from ptrace.binding.func import ptrace_getsiginfo
+if HAS_PTRACE_IO:
+    from ptrace.binding.func import ptrace_io
+    from ptrace.binding.freebsd_struct import (
+        ptrace_io_desc,
+        PIOD_READ_D, PIOD_WRITE_D,
+        PIOD_READ_I,PIOD_WRITE_I)
+if HAS_PTRACE_GETREGS:
+    from ptrace.binding.func import ptrace_getregs
+

File ptrace/binding/cpu.py

+from ptrace.cpu_info import CPU_POWERPC, CPU_INTEL, CPU_X86_64, CPU_I386
+
+CPU_INSTR_POINTER = None
+CPU_STACK_POINTER = None
+CPU_FRAME_POINTER = None
+CPU_SUB_REGISTERS = {}
+
+if CPU_POWERPC:
+    CPU_INSTR_POINTER = "nip"
+    # FIXME: Is it the right register?
+    CPU_STACK_POINTER = 'gpr1'
+elif CPU_X86_64:
+    CPU_INSTR_POINTER = "rip"
+    CPU_STACK_POINTER = "rsp"
+    CPU_FRAME_POINTER = "rbp"
+    CPU_SUB_REGISTERS = {
+        'al':  ('rax', 0, 0xff),
+        'bl':  ('rbx', 0, 0xff),
+        'cl':  ('rcx', 0, 0xff),
+        'dl':  ('rdx', 0, 0xff),
+        'ah':  ('rax', 8, 0xff),
+        'bh':  ('rbx', 8, 0xff),
+        'ch':  ('rcx', 8, 0xff),
+        'dh':  ('rdx', 8, 0xff),
+        'ax':  ('rax', 0, 0xffff),
+        'bx':  ('rbx', 0, 0xffff),
+        'cx':  ('rcx', 0, 0xffff),
+        'dx':  ('rdx', 0, 0xffff),
+        'eax': ('rax', 32),
+        'ebx': ('rbx', 32),
+        'ecx': ('rcx', 32),
+        'edx': ('rdx', 32),
+    }
+elif CPU_I386:
+    CPU_INSTR_POINTER = "eip"
+    CPU_STACK_POINTER = "esp"
+    CPU_FRAME_POINTER = "ebp"
+    CPU_SUB_REGISTERS = {
+        'al': ('eax', 0, 0xff),
+        'bl': ('ebx', 0, 0xff),
+        'cl': ('ecx', 0, 0xff),
+        'dl': ('edx', 0, 0xff),
+        'ah': ('eax', 8, 0xff),
+        'bh': ('ebx', 8, 0xff),
+        'ch': ('ecx', 8, 0xff),
+        'dh': ('edx', 8, 0xff),
+        'ax': ('eax', 0, 0xffff),
+        'bx': ('ebx', 0, 0xffff),
+        'cx': ('ecx', 0, 0xffff),
+        'dx': ('edx', 0, 0xffff),
+    }
+
+if CPU_INTEL:
+    CPU_SUB_REGISTERS.update({
+        'cf': ('eflags', 0, 1),
+        'pf': ('eflags', 2, 1),
+        'af': ('eflags', 4, 1),
+        'zf': ('eflags', 6, 1),
+        'sf': ('eflags', 7, 1),
+        'tf': ('eflags', 8, 1),
+        'if': ('eflags', 9, 1),
+        'df': ('eflags', 10, 1),
+        'of': ('eflags', 11, 1),
+        'iopl': ('eflags', 12, 2),
+    })
+

File ptrace/binding/freebsd_struct.py

+from ctypes import Structure, c_int, c_uint, c_ulong, c_void_p
+
+PIOD_READ_D = 1
+PIOD_WRITE_D = 2
+PIOD_READ_I = 3
+PIOD_WRITE_I = 4
+
+size_t = c_ulong
+
+# /usr/include/machine/reg.h
+class reg(Structure):
+    _fields_ = (
+        ("fs", c_uint),
+        ("es", c_uint),
+        ("ds", c_uint),
+        ("edi", c_uint),
+        ("esi", c_uint),
+        ("ebp", c_uint),
+        ("isp", c_uint),
+        ("ebx", c_uint),
+        ("edx", c_uint),
+        ("ecx", c_uint),
+        ("eax", c_uint),
+        ("trapno", c_uint),
+        ("err", c_uint),
+        ("eip", c_uint),
+        ("cs", c_uint),
+        ("eflags", c_uint),
+        ("esp", c_uint),
+        ("ss", c_uint),
+        ("gs", c_uint),
+    )
+
+class ptrace_io_desc(Structure):
+    _fields_ = (
+        ("piod_op", c_int),
+        ("piod_offs", c_void_p),
+        ("piod_addr", c_void_p),
+        ("piod_len", size_t),
+    )
+

File ptrace/binding/func.py

+from os import strerror
+from ctypes import addressof, c_int
+from ptrace.ctypes_tools import bytes2type
+from ptrace import PtraceError
+from ptrace.ctypes_errno import get_errno
+from ptrace.ctypes_tools import formatAddress
+from ptrace.os_tools import RUNNING_LINUX, RUNNING_BSD, RUNNING_OPENBSD
+from ptrace.cpu_info import CPU_64BITS, CPU_WORD_SIZE, CPU_POWERPC
+
+if RUNNING_OPENBSD:
+    from ptrace.binding.openbsd_struct import (
+        reg as ptrace_registers_t,
+        fpreg as user_fpregs_struct)
+
+elif RUNNING_BSD:
+    from ptrace.binding.freebsd_struct import (
+        reg as ptrace_registers_t)
+
+elif RUNNING_LINUX:
+    from ptrace.binding.linux_struct import (
+        user_regs_struct as ptrace_registers_t,
+        user_fpregs_struct, siginfo)
+    if not CPU_64BITS:
+        from ptrace.binding.linux_struct import user_fpxregs_struct
+else:
+    raise NotImplementedError("Unknown OS!")
+REGISTER_NAMES = tuple( name for name, type in ptrace_registers_t._fields_ )
+
+HAS_PTRACE_SINGLESTEP = True
+HAS_PTRACE_EVENTS = False
+HAS_PTRACE_IO = False
+HAS_PTRACE_SIGINFO = False
+HAS_PTRACE_GETREGS = False
+
+pid_t = c_int
+
+# PTRACE_xxx constants from /usr/include/sys/ptrace.h
+# (Linux 2.6.21 Ubuntu Feisty i386)
+PTRACE_TRACEME = 0
+PTRACE_PEEKTEXT = 1
+PTRACE_PEEKDATA = 2
+PTRACE_PEEKUSER = 3
+PTRACE_POKETEXT = 4
+PTRACE_POKEDATA = 5
+PTRACE_POKEUSER = 6
+PTRACE_CONT = 7
+PTRACE_KILL = 8
+if HAS_PTRACE_SINGLESTEP:
+    PTRACE_SINGLESTEP = 9
+
+if RUNNING_OPENBSD:
+    # OpenBSD 4.2 i386
+    PTRACE_ATTACH = 9
+    PTRACE_DETACH = 10
+    HAS_PTRACE_GETREGS = True
+    PTRACE_GETREGS = 33
+    PTRACE_SETREGS = 34
+    PTRACE_GETFPREGS = 35
+    PTRACE_SETFPREGS = 36
+    HAS_PTRACE_IO = True
+    PTRACE_IO = 11
+    HAS_PTRACE_SINGLESTEP = True
+    PTRACE_SINGLESTEP = 32 # PT_STEP
+    #HAS_PTRACE_EVENTS = True
+    #PTRACE_SETOPTIONS = 12 # PT_SET_EVENT_MASK
+    #PTRACE_GETEVENTMSG = 14 # PT_GET_PROCESS_STATE
+elif RUNNING_BSD:
+    # FreeBSD 7.0RC1 i386
+    PTRACE_ATTACH = 10
+    PTRACE_DETACH = 11
+    PTRACE_SYSCALL = 22
+    if not CPU_POWERPC:
+        HAS_PTRACE_GETREGS = True
+        PTRACE_GETREGS = 33
+    PTRACE_SETREGS = 34
+    HAS_PTRACE_IO = True
+    PTRACE_IO = 12
+else:
+    # Linux
+    HAS_PTRACE_GETREGS = True
+    PTRACE_GETREGS = 12
+    PTRACE_SETREGS = 13
+    PTRACE_ATTACH = 16
+    PTRACE_DETACH = 17
+    PTRACE_SYSCALL = 24
+if RUNNING_LINUX:
+    PTRACE_GETFPREGS = 14
+    PTRACE_SETFPREGS = 15
+    if not CPU_64BITS:
+        PTRACE_GETFPXREGS = 18
+        PTRACE_SETFPXREGS = 19
+    HAS_PTRACE_SIGINFO = True
+    PTRACE_GETSIGINFO = 0x4202
+    PTRACE_SETSIGINFO = 0x4203
+
+    HAS_PTRACE_EVENTS = True
+    PTRACE_SETOPTIONS = 0x4200
+    PTRACE_GETEVENTMSG = 0x4201
+
+PTRACE_O_TRACESYSGOOD   = 0x00000001
+PTRACE_O_TRACEFORK      = 0x00000002
+PTRACE_O_TRACEVFORK     = 0x00000004
+PTRACE_O_TRACECLONE     = 0x00000008
+PTRACE_O_TRACEEXEC      = 0x00000010
+PTRACE_O_TRACEVFORKDONE = 0x00000020
+PTRACE_O_TRACEEXIT      = 0x00000040
+
+# Wait extended result codes for the above trace options
+PTRACE_EVENT_FORK       = 1
+PTRACE_EVENT_VFORK      = 2
+PTRACE_EVENT_CLONE      = 3
+PTRACE_EVENT_EXEC       = 4
+PTRACE_EVENT_VFORK_DONE = 5
+PTRACE_EVENT_EXIT       = 6
+
+try:
+    from cptrace import ptrace as _ptrace
+    HAS_CPTRACE = True
+except ImportError:
+    HAS_CPTRACE = False
+    from ctypes import c_long, c_ulong
+    from ptrace.ctypes_libc import libc
+
+    # Load ptrace() function from the system C library
+    _ptrace = libc.ptrace
+    _ptrace.argtypes = (c_ulong, c_ulong, c_ulong, c_ulong)
+    _ptrace.restype = c_ulong
+
+def ptrace(command, pid=0, arg1=0, arg2=0, check_errno=False):
+    if HAS_CPTRACE:
+        try:
+            result = _ptrace(command, pid, arg1, arg2)
+        except ValueError, errobj:
+            message = str(errobj)
+            errno = get_errno()
+            raise PtraceError(message, errno=errno, pid=pid)
+    else:
+        result = _ptrace(command, pid, arg1, arg2)
+        result_signed = c_long(result).value
+        if result_signed == -1:
+            errno = get_errno()
+            # peek operations may returns -1 with errno=0:
+            # it's not an error. For other operations, -1
+            # is always an error
+            if not(check_errno) or errno:
+                message = "ptrace(cmd=%s, pid=%s, %r, %r) error #%s: %s" % (
+                    command, pid, arg1, arg2,
+                    errno, strerror(errno))
+                raise PtraceError(message, errno=errno, pid=pid)
+    return result
+
+def ptrace_traceme():
+    ptrace(PTRACE_TRACEME)
+
+def ptrace_attach(pid):
+    ptrace(PTRACE_ATTACH, pid)
+
+def ptrace_detach(pid, signal=0):
+    ptrace(PTRACE_DETACH, pid, 0, signal);
+
+def _peek(command, pid, address):
+    if address % CPU_WORD_SIZE:
+        raise PtraceError(
+            "ptrace can't read a word from an unaligned address (%s)!"
+            % formatAddress(address), pid=pid)
+    return ptrace(command, pid, address, check_errno=True)
+
+def _poke(command, pid, address, word):
+    if address % CPU_WORD_SIZE:
+        raise PtraceError(
+            "ptrace can't write a word to an unaligned address (%s)!"
+            % formatAddress(address), pid=pid)
+    ptrace(command, pid, address, word)
+
+def ptrace_peektext(pid, address):
+    return _peek(PTRACE_PEEKTEXT, pid, address)
+
+def ptrace_peekdata(pid, address):
+    return _peek(PTRACE_PEEKDATA, pid, address)
+
+def ptrace_peekuser(pid, address):
+    return _peek(PTRACE_PEEKUSER, pid, address)
+
+def ptrace_poketext(pid, address, word):
+    _poke(PTRACE_POKETEXT, pid, address, word)
+
+def ptrace_pokedata(pid, address, word):
+    _poke(PTRACE_POKEDATA, pid, address, word)
+
+def ptrace_pokeuser(pid, address, word):
+    _poke(PTRACE_POKEUSER, pid, address, word)
+
+def ptrace_kill(pid):
+    ptrace(PTRACE_KILL, pid)
+
+if HAS_PTRACE_EVENTS:
+    def WPTRACEEVENT(status):
+        return status >> 16
+
+    def ptrace_setoptions(pid, options):
+        ptrace(PTRACE_SETOPTIONS, pid, 0, options)
+
+    def ptrace_geteventmsg(pid):
+        new_pid = pid_t()
+        ptrace(PTRACE_GETEVENTMSG, pid, 0, addressof(new_pid))
+        return new_pid.value
+
+if RUNNING_LINUX:
+    def ptrace_syscall(pid, signum=0):
+        ptrace(PTRACE_SYSCALL, pid, 0, signum)
+
+    def ptrace_cont(pid, signum=0):
+        ptrace(PTRACE_CONT, pid, 0, signum)
+
+    def ptrace_getsiginfo(pid):
+        info = siginfo()
+        ptrace(PTRACE_GETSIGINFO, pid, 0, addressof(info))
+        return info
+
+    def ptrace_setsiginfo(pid, info):
+        ptrace(PTRACE_SETSIGINFO, pid, 0, addressof(info))
+
+    def ptrace_getfpregs(pid):
+        fpregs = user_fpregs_struct()
+        ptrace(PTRACE_GETFPREGS, pid, 0, addressof(fpregs))
+        return fpregs
+
+    def ptrace_setfpregs(pid, fpregs):
+        ptrace(PTRACE_SETFPREGS, pid, 0, addressof(fpregs))
+
+    if not CPU_64BITS:
+        def ptrace_getfpxregs(pid):
+            fpxregs = user_fpxregs_struct()
+            ptrace(PTRACE_GETFPXREGS, pid, 0, addressof(fpxregs))
+            return fpxregs
+
+        def ptrace_setfpxregs(pid, fpxregs):
+            ptrace(PTRACE_SETFPXREGS, pid, 0, addressof(fpxregs))
+
+    if HAS_PTRACE_GETREGS:
+        def ptrace_getregs(pid):
+            regs = ptrace_registers_t()
+            ptrace(PTRACE_GETREGS, pid, 0, addressof(regs))
+            return regs
+
+    def ptrace_setregs(pid, regs):
+        ptrace(PTRACE_SETREGS, pid, 0, addressof(regs))
+
+    if HAS_PTRACE_SINGLESTEP:
+        def ptrace_singlestep(pid):
+            ptrace(PTRACE_SINGLESTEP, pid)
+
+else:
+    def ptrace_syscall(pid, signum=0):
+        ptrace(PTRACE_SYSCALL, pid, 1, signum)
+
+    def ptrace_cont(pid, signum=0):
+        ptrace(PTRACE_CONT, pid, 1, signum)
+
+    if HAS_PTRACE_GETREGS:
+        def ptrace_getregs(pid):
+            regs = ptrace_registers_t()
+            ptrace(PTRACE_GETREGS, pid, addressof(regs))
+            return regs
+
+    def ptrace_setregs(pid, regs):
+        ptrace(PTRACE_SETREGS, pid, addressof(regs))
+
+    if HAS_PTRACE_SINGLESTEP:
+        def ptrace_singlestep(pid):
+            ptrace(PTRACE_SINGLESTEP, pid, 1)
+
+if HAS_PTRACE_IO:
+    def ptrace_io(pid, io_desc):
+        ptrace(PTRACE_IO, pid, addressof(io_desc))
+

File ptrace/binding/linux_struct.py

+from ctypes import (Structure, Union, sizeof,
+    c_char, c_ushort, c_int, c_uint, c_ulong, c_void_p)
+from ptrace.cpu_info import CPU_64BITS, CPU_PPC32
+from ptrace.ctypes_stdint import uint16_t, uint32_t, uint64_t
+
+pid_t = c_int
+uid_t = c_ushort
+clock_t = c_uint
+
+# From /usr/include/asm-i386/user.h
+class user_regs_struct(Structure):
+    if CPU_PPC32:
+        _fields_ = (
+            ("gpr0", c_ulong),
+            ("gpr1", c_ulong),
+            ("gpr2", c_ulong),
+            ("gpr3", c_ulong),
+            ("gpr4", c_ulong),
+            ("gpr5", c_ulong),
+            ("gpr6", c_ulong),
+            ("gpr7", c_ulong),
+            ("gpr8", c_ulong),
+            ("gpr9", c_ulong),
+            ("gpr10", c_ulong),
+            ("gpr11", c_ulong),
+            ("gpr12", c_ulong),
+            ("gpr13", c_ulong),
+            ("gpr14", c_ulong),
+            ("gpr15", c_ulong),
+            ("gpr16", c_ulong),
+            ("gpr17", c_ulong),
+            ("gpr18", c_ulong),
+            ("gpr19", c_ulong),
+            ("gpr20", c_ulong),
+            ("gpr21", c_ulong),
+            ("gpr22", c_ulong),
+            ("gpr23", c_ulong),
+            ("gpr24", c_ulong),
+            ("gpr25", c_ulong),
+            ("gpr26", c_ulong),
+            ("gpr27", c_ulong),
+            ("gpr28", c_ulong),
+            ("gpr29", c_ulong),
+            ("gpr30", c_ulong),
+            ("gpr31", c_ulong),
+            ("nip", c_ulong),
+            ("msr", c_ulong),
+            ("orig_gpr3", c_ulong),
+            ("ctr", c_ulong),
+            ("link", c_ulong),
+            ("xer", c_ulong),
+            ("ccr", c_ulong),
+            ("mq", c_ulong), # FIXME: ppc64 => softe
+            ("trap", c_ulong),
+            ("dar", c_ulong),
+            ("dsisr", c_ulong),
+            ("result", c_ulong),
+        )
+    elif CPU_64BITS:
+        _fields_ = (
+            ("r15", c_ulong),
+            ("r14", c_ulong),
+            ("r13", c_ulong),
+            ("r12", c_ulong),
+            ("rbp", c_ulong),
+            ("rbx", c_ulong),
+            ("r11", c_ulong),
+            ("r10", c_ulong),
+            ("r9", c_ulong),
+            ("r8", c_ulong),
+            ("rax", c_ulong),
+            ("rcx", c_ulong),
+            ("rdx", c_ulong),
+            ("rsi", c_ulong),
+            ("rdi", c_ulong),
+            ("orig_rax", c_ulong),
+            ("rip", c_ulong),
+            ("cs", c_ulong),
+            ("eflags", c_ulong),
+            ("rsp", c_ulong),
+            ("ss", c_ulong),
+            ("fs_base", c_ulong),
+            ("gs_base", c_ulong),
+            ("ds", c_ulong),
+            ("es", c_ulong),
+            ("fs", c_ulong),
+            ("gs", c_ulong)
+            )
+    else:
+        _fields_ = (
+            ("ebx", c_ulong),
+            ("ecx", c_ulong),
+            ("edx", c_ulong),
+            ("esi", c_ulong),
+            ("edi", c_ulong),
+            ("ebp", c_ulong),
+            ("eax", c_ulong),
+            ("ds", c_ushort),
+            ("__ds", c_ushort),
+            ("es", c_ushort),
+            ("__es", c_ushort),
+            ("fs", c_ushort),
+            ("__fs", c_ushort),
+            ("gs", c_ushort),
+            ("__gs", c_ushort),
+            ("orig_eax", c_ulong),
+            ("eip", c_ulong),
+            ("cs", c_ushort),
+            ("__cs", c_ushort),
+            ("eflags", c_ulong),
+            ("esp", c_ulong),
+            ("ss", c_ushort),
+            ("__ss", c_ushort),
+            )
+
+class user_fpregs_struct(Structure):
+    if CPU_64BITS:
+        _fields_ = (
+            ("cwd", uint16_t),
+            ("swd", uint16_t),
+            ("ftw", uint16_t),
+            ("fop", uint16_t),
+            ("rip", uint64_t),
+            ("rdp", uint64_t),
+            ("mxcsr", uint32_t),
+            ("mxcr_mask", uint32_t),
+            ("st_space", uint32_t * 32),
+            ("xmm_space", uint32_t * 64),
+            ("padding", uint32_t * 24)
+            )
+    else:
+        _fields_ = (
+            ("cwd", c_ulong),
+            ("swd", c_ulong),
+            ("twd", c_ulong),
+            ("fip", c_ulong),
+            ("fcs", c_ulong),
+            ("foo", c_ulong),
+            ("fos", c_ulong),
+            ("st_space", c_ulong * 20)
+            )
+
+if not CPU_64BITS: