diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + 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. + + + Copyright (C) + + 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. + + , 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. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..653548e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,29 @@ +include AUTHORS +include COPYING +include ChangeLog +include INSTALL +include MANIFEST.in +include README.cptrace +include TODO +include tox.ini +include SYSCALL_PROTOTYPES.codegen.py + +include cptrace/Makefile +include cptrace/cptrace.c +include cptrace/version.py + +include doc/*.rst doc/conf.py doc/make.bat doc/Makefile + +include examples/itrace.py +include examples/simple_dbg.py +include pyflakes.sh +include python3.0.patch +include setup_cptrace.py + +# Tests +include runtests.py +include test_doc.py +include tests/test_*.py +include tests/crash/*.c +include tests/crash/BSDmakefile +include tests/crash/Makefile diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..48eb599 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,42 @@ +Metadata-Version: 2.1 +Name: python-ptrace +Version: 0.9.9 +Summary: python binding of ptrace +Home-page: http://python-ptrace.readthedocs.io/ +Download-URL: http://python-ptrace.readthedocs.io/ +Author: Victor Stinner +License: GNU GPL v2 +Classifier: Intended Audience :: Developers +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Console +Classifier: License :: OSI Approved :: GNU General Public License (GPL) +Classifier: Operating System :: OS Independent +Classifier: Natural Language :: English +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +License-File: COPYING + +============= +python-ptrace +============= + +.. image:: https://img.shields.io/pypi/v/python-ptrace.svg + :alt: Latest release on the Python Cheeseshop (PyPI) + :target: https://pypi.python.org/pypi/python-ptrace + +.. image:: https://github.com/vstinner/python-ptrace/actions/workflows/build.yml/badge.svg + :alt: Build status of python-ptrace on GitHub Actions + :target: https://github.com/vstinner/python-ptrace/actions + +python-ptrace is a debugger using ptrace (Linux, BSD and Darwin system call to +trace processes) written in Python. + +* `python-ptrace documentation + `_ +* `python-ptrace at GitHub + `_ +* `python-ptrace at the Python Cheeseshop (PyPI) + `_ + +python-ptrace is an opensource project written in Python under GNU GPLv2 +license. It supports Python 3.6 and newer. diff --git a/README.md b/README.md deleted file mode 100644 index 9ebb840..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -# template-repository \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..516f663 --- /dev/null +++ b/README.rst @@ -0,0 +1,24 @@ +============= +python-ptrace +============= + +.. image:: https://img.shields.io/pypi/v/python-ptrace.svg + :alt: Latest release on the Python Cheeseshop (PyPI) + :target: https://pypi.python.org/pypi/python-ptrace + +.. image:: https://github.com/vstinner/python-ptrace/actions/workflows/build.yml/badge.svg + :alt: Build status of python-ptrace on GitHub Actions + :target: https://github.com/vstinner/python-ptrace/actions + +python-ptrace is a debugger using ptrace (Linux, BSD and Darwin system call to +trace processes) written in Python. + +* `python-ptrace documentation + `_ +* `python-ptrace at GitHub + `_ +* `python-ptrace at the Python Cheeseshop (PyPI) + `_ + +python-ptrace is an opensource project written in Python under GNU GPLv2 +license. It supports Python 3.6 and newer. diff --git a/SYSCALL_PROTOTYPES.codegen.py b/SYSCALL_PROTOTYPES.codegen.py new file mode 100644 index 0000000..c24db67 --- /dev/null +++ b/SYSCALL_PROTOTYPES.codegen.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +""" +Generates the SYSCALL_PROTOTYPES dictionary from the Linux kernel source +and prints out Python code representing it. +""" + +import urllib2 +import re + +url = "https://raw.githubusercontent.com/torvalds/linux/master/include/linux/syscalls.h" +source = urllib2.urlopen(url).read() + +p1 = re.compile(r"^asmlinkage long sys(?:32)?_(.*?)\((.*?)\)", + re.MULTILINE | re.DOTALL) +p2 = re.compile(r"^(.*?)([^ *]+)$") + +SYSCALL_PROTOTYPES = {} + +for m1 in p1.finditer(source): + call_name = m1.group(1) + args = m1.group(2) + args = args.replace("__user", "") + args = " ".join(args.split()) + args_tuple = () + if args != "void": + for arg in args.split(","): + if arg.endswith(("*", "long", "int", "size_t")): + arg_type = arg.strip() + arg_name = "" + else: + m2 = p2.match(arg) + arg_type = m2.group(1).strip() + arg_name = m2.group(2).strip() + # Workaround for pipe system call + if (call_name == 'pipe' or call_name == 'pipe2') and arg_type == "int *": + arg_type = "int[2]" + args_tuple += ((arg_type, arg_name),) + SYSCALL_PROTOTYPES[call_name] = ("long", args_tuple) + +for call_name in sorted(SYSCALL_PROTOTYPES): + signature = SYSCALL_PROTOTYPES[call_name] + args_tuple = signature[1] + print('"%s": ("%s", (' % (call_name, signature[0])) + for arg in args_tuple: + print((' ("%s", "%s"),' % (arg[0], arg[1]))) + print(')),') diff --git a/cptrace/Makefile b/cptrace/Makefile new file mode 100644 index 0000000..61af83e --- /dev/null +++ b/cptrace/Makefile @@ -0,0 +1,10 @@ +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) diff --git a/cptrace/cptrace.c b/cptrace/cptrace.c new file mode 100644 index 0000000..457997b --- /dev/null +++ b/cptrace/cptrace.c @@ -0,0 +1,107 @@ +#include +#include +#if __APPLE__ +#include +#endif +#include + +#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; + errno = 0; + 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|LLO", 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 module_methods[] = { + {"ptrace", (PyCFunction)cpython_ptrace, METH_VARARGS | METH_KEYWORDS, python_ptrace_DOCSTR}, + {NULL, NULL, 0, NULL} +}; + +PyDoc_STRVAR(module_doc, +"ptrace module written in C"); + +#if PY_MAJOR_VERSION >= 3 +static struct PyModuleDef module_def = { + PyModuleDef_HEAD_INIT, + "cptrace", + module_doc, + 0, + module_methods, + NULL +}; +#endif + +PyMODINIT_FUNC +#if PY_MAJOR_VERSION >= 3 +PyInit_cptrace(void) +#else +initcptrace(void) +#endif +{ +#if PY_MAJOR_VERSION >= 3 + return PyModule_Create(&module_def); +#else + (void)Py_InitModule3("cptrace", module_methods, module_doc); +#endif +} + diff --git a/cptrace/version.py b/cptrace/version.py new file mode 100644 index 0000000..5f35e7d --- /dev/null +++ b/cptrace/version.py @@ -0,0 +1,4 @@ +PACKAGE = "cptrace" +VERSION = "0.6.1" +WEBSITE = "http://python-ptrace.readthedocs.io/" +LICENSE = "GNU GPL v2" diff --git a/debian/changelog b/debian/changelog index bad88e2..72698af 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,5 +1,161 @@ -template-repository (1.0-1) unstable; urgency=medium +python-ptrace (0.9.9-0.1) unstable; urgency=medium - * Initial release + * Non-maintainer upload. + * New upstream release, adding support for riscv64 and merging + python-3.12.patch. - -- Tsic404 Sat, 28 Jan 2023 13:46:49 +0800 + -- Stephen Kitt Sun, 17 Mar 2024 19:06:17 +0100 + +python-ptrace (0.9.8-0.2) unstable; urgency=medium + + * Non-maintainer upload. + * Build with importlib instead of imp. Closes: #1055551. + + -- Stephen Kitt Wed, 06 Dec 2023 07:21:34 +0100 + +python-ptrace (0.9.8-0.1) unstable; urgency=medium + + * Non-maintainer upload. + * Limit the architectures to those which are officially supported. + + -- Stephen Kitt Mon, 15 Aug 2022 14:42:58 +0200 + +python-ptrace (0.9.8-0.1~exp1) experimental; urgency=medium + + * Non-maintainer upload. + * New upstream release, adding support for arm64. + * Since this package only works on a limited number of architectures, + switch to “Architecture: any” and rely on the build-time tests to + filter the architectures. See the discussion starting with + . + Closes: #976468. + * Clean up egg-info changes to allow building twice in a row. + * Build the CPython extension. + + -- Stephen Kitt Thu, 09 Sep 2021 08:42:02 +0200 + +python-ptrace (0.9.7-0.1) unstable; urgency=medium + + * Non-maintainer upload. + * New upstream release, adding ppc64 support, and merging + 20-fix-egginfo-sources.patch. This will help with #976468; arm64 + support will be included in the next upstream release. + * Build-depend on python3 instead of python3-dev since this package + isn’t an extension. + * Clean up whitespace. + * Switch to debhelper compatibility level 13. + + -- Stephen Kitt Mon, 04 Jan 2021 18:19:12 +0100 + +python-ptrace (0.9.3-2.2) unstable; urgency=medium + + * Non-maintainer upload. + * Drop python2 support; Closes: #938052 + + -- Sandro Tosi Fri, 20 Dec 2019 14:03:21 -0500 + +python-ptrace (0.9.3-2.1) unstable; urgency=medium + + * Non-maintainer upload. + * Update debian/docs: Correct name of README, which is now + README.rst and remove TODO as upstream no longer ships a + TODO file. (Closes: #903189) + * Set Rules-Requires-Root to no as python-ptrace does not + require (fake)root for producing the .debs. + + -- Niels Thykier Tue, 07 Aug 2018 08:33:18 +0000 + +python-ptrace (0.9.3-2) unstable; urgency=medium + + * Fix spelling error + * Add python3 package (Closes: #782949) + + -- Pierre Chifflier Sun, 29 Apr 2018 14:41:32 +0200 + +python-ptrace (0.9.3-1) unstable; urgency=medium + + * New upstream version 0.9.3 (Closes: #814262) + * Remove patch 10-set_errno_to_0_if_no_error, merged upstream + * Refreshed quilt patches + * Update homepage (Closes: #888910) + * Change priority from extra to optional + * Bump Standards Version to 4.1.4 + + -- Pierre Chifflier Sun, 29 Apr 2018 14:24:08 +0200 + +python-ptrace (0.7-1) unstable; urgency=medium + + * Imported Upstream version 0.7 + * Remove leftovers from python-support + * Fix python egginfo + * Bump Standards Version to 3.9.5 + + -- Pierre Chifflier Thu, 21 Aug 2014 20:58:02 +0200 + +python-ptrace (0.6.4-2) unstable; urgency=low + + * Build with python support in dehelper (Closes: #671054) + + -- Pierre Chifflier Wed, 02 May 2012 21:35:58 +0200 + +python-ptrace (0.6.4-1) unstable; urgency=low + + * Imported Upstream version 0.6.4 + - This version should work on kfreebsd (Closes: #592637) + * Bump Standards Version to 3.9.3 + * Switch to DH 9 + * Convert debian/rules to dh format + * Fix bad errno handling in ptrace.binding.func.ptrace (Closes: #592648) + Thanks to Jakub Wilk for the patch. + + -- Pierre Chifflier Sat, 28 Apr 2012 16:49:32 +0200 + +python-ptrace (0.6.3-1) unstable; urgency=low + + * Imported Upstream version 0.6.3 + * Bump Standards Version to 3.9.1 + * Fix build-dep (use python-all instead of python-dev-all) + * Update licence for debian packaging + * Switch to dpkg-source 3.0 (quilt) format + + -- Pierre Chifflier Mon, 28 Mar 2011 22:00:45 +0200 + +python-ptrace (0.6.2-1) unstable; urgency=low + + * New upstream release + * Bump standards version to 3.8.3 (no changes) + + -- Pierre Chifflier Thu, 17 Dec 2009 21:44:09 +0100 + +python-ptrace (0.6-2) unstable; urgency=low + + * Set minimum Python version to 2.5 (Closes: #516946) + + -- Pierre Chifflier Tue, 24 Feb 2009 18:17:58 +0100 + +python-ptrace (0.6-1) unstable; urgency=low + + * New Upstream Version + * Upload to unstable + + -- Pierre Chifflier Tue, 24 Feb 2009 14:22:15 +0100 + +python-ptrace (0.5-1) experimental; urgency=low + + * New upstream release + * Update watch file + + -- Pierre Chifflier Thu, 18 Sep 2008 16:57:23 +0200 + +python-ptrace (0.3.2-1) unstable; urgency=low + + * New upstream release + * Add watch file + + -- Pierre Chifflier Mon, 28 Jul 2008 10:38:22 +0200 + +python-ptrace (0.3.1-1) unstable; urgency=low + + * Initial release (Closes: #491060) + + -- Pierre Chifflier Wed, 16 Jul 2008 10:31:47 +0200 diff --git a/debian/clean b/debian/clean new file mode 100644 index 0000000..d7d63a8 --- /dev/null +++ b/debian/clean @@ -0,0 +1 @@ +python_ptrace.egg-info/SOURCES.txt diff --git a/debian/compat b/debian/compat deleted file mode 100644 index b4de394..0000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -11 diff --git a/debian/control b/debian/control index cb7c4a0..5e945be 100644 --- a/debian/control +++ b/debian/control @@ -1,15 +1,30 @@ -Source: template-repository -Section: unknown +Source: python-ptrace +Section: python Priority: optional -Maintainer: Tsic404 -Build-Depends: debhelper (>= 11) -Standards-Version: 4.1.3 -Homepage: https://github.com/deepin-community/template-repository -#Vcs-Browser: https://salsa.debian.org/debian/deepin-community-template-repository -#Vcs-Git: https://salsa.debian.org/debian/deepin-community-template-repository.git +Maintainer: Pierre Chifflier +Build-Depends: debhelper-compat (= 13), + dh-python, + python3-all-dev +Standards-Version: 4.1.4 +Homepage: https://github.com/vstinner/python-ptrace +Rules-Requires-Root: no -Package: template-repository -Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends} -Description: - +Package: python3-ptrace +Architecture: amd64 arm64 i386 ppc64el powerpc ppc64 riscv64 +XB-Python-Version: ${python3:Versions} +Depends: ${python3:Depends}, ${misc:Depends}, ${shlibs:Depends} +Provides: ${python3:Provides} +Description: Python 3 bindings for ptrace + This package provides Python bindings for the ptrace library. It allows + controlling, debugging, or modifying processes using the ptrace syscall. + . + Features: + * High level Python object API + * Able to control multiple processes: catch fork events + * Read/write bytes to arbitrary addresses + * Execution step by step using ptrace_singlestep() or hardware int 3 + * Can use distorm disassembler + * Dump registers, memory mappings, stack, etc. + * Syscall tracer and parser (strace command) + . + This package provides the ptrace Python module for Python 3.x. diff --git a/debian/copyright b/debian/copyright index f5c805e..be24fd3 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,22 +1,34 @@ -Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: template-repository -Source: https://github.com/deepin-community/template-repository - -Files: * -Copyright: 2023 Tsic404 -License: GPL-2+ - This package 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 package 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, see - . - On Debian systems, the complete text of the GNU General - Public License version 2 can be found in "/usr/share/common-licenses/GPL-2". +This package was debianized by Pierre Chifflier on +Wed, 16 Jul 2008 10:31:47 +0200. + +It was downloaded from http://fusil.hachoir.org/trac/wiki/Ptrace + +Upstream Author: Victor Stinner + +Copyright (C) 2006,2007,2008 Victor Stinner + +License: + +This library is released under the GPLv2 + + This package 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; version 2 dated June, 1991. + + This package 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 package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + MA 02110-1301, USA. + +On Debian GNU/Linux systems, the complete text of the GNU General +Public License version 2 can be found in `/usr/share/common-licenses/GPL-2'. + + +The Debian packaging is (C) 2008-2011, Pierre Chifflier and +is licensed under the GPL version 2, see `/usr/share/common-licenses/GPL-2'. + diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..a1320b1 --- /dev/null +++ b/debian/docs @@ -0,0 +1 @@ +README.rst diff --git a/debian/examples b/debian/examples new file mode 100644 index 0000000..6b9abd4 --- /dev/null +++ b/debian/examples @@ -0,0 +1,2 @@ +gdb.py +strace.py diff --git a/debian/gbp.conf b/debian/gbp.conf new file mode 100644 index 0000000..7e38616 --- /dev/null +++ b/debian/gbp.conf @@ -0,0 +1,4 @@ +# Configuration file for git-buildpackage and friends + +[DEFAULT] +pristine-tar = True diff --git a/debian/rules b/debian/rules index 2d33f6a..93a9787 100755 --- a/debian/rules +++ b/debian/rules @@ -1,4 +1,36 @@ #!/usr/bin/make -f +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +include /usr/share/dpkg/pkg-info.mk + +export PYBUILD_NAME=ptrace +PYTHON3_VERSIONS = $(shell py3versions -r) +py3sdo = set -e; $(foreach py, $(PYTHON3_VERSIONS), $(py) $(1);) + +override_dh_auto_clean: + $(call py3sdo, setup_cptrace.py clean) + dh_auto_clean + +override_dh_auto_configure: + dh_auto_configure + $(call py3sdo, setup_cptrace.py config) + +override_dh_auto_build: + dh_auto_build + $(call py3sdo, setup_cptrace.py build) + +override_dh_auto_test: +ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) + $(call py3sdo, -m unittest discover -vv tests) +endif + +override_dh_auto_install: + dh_auto_install + $(call py3sdo, setup_cptrace.py install --root=$(CURDIR)/debian/python3-ptrace --install-layout=deb) + # do not install examples as real executables + rm -rf $(CURDIR)/debian/python3-ptrace/usr/bin + %: - dh $@ + dh $@ --with=python3 --buildsystem=pybuild diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..9ef57ac --- /dev/null +++ b/debian/watch @@ -0,0 +1,9 @@ +# watch control file for uscan +# Run the "uscan" command to check for upstream updates and more. +# See uscan(1) for format + +# Compulsory line, this is a version 3 file +version=3 + +http://pypi.python.org/packages/source/p/python-ptrace/python-ptrace-(.*).tar.gz + diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..6138f30 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-ptrace.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-ptrace.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/python-ptrace" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-ptrace" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/doc/authors.rst b/doc/authors.rst new file mode 100644 index 0000000..c74d052 --- /dev/null +++ b/doc/authors.rst @@ -0,0 +1,21 @@ ++++++++ +Authors ++++++++ + +Contributors +============ + +* Anthony Gelibert - fix cptrace on Apple +* Dimitris Glynos - follow gdb.py commands +* Jakub Wilk - fix for GNU/kFreeBSD +* Mark Seaborn - Create -i option for strace +* Mickaël Guérin aka KAeL - OpenBSD port +* teythoon - convert all classes to new-style classes +* T. Bursztyka aka Tuna - x86_64 port +* Victor Stinner - python-ptrace author and maintainer + +Thanks +====== + +* procps authors (top and uptime programs) + diff --git a/doc/changelog.rst b/doc/changelog.rst new file mode 100644 index 0000000..e055817 --- /dev/null +++ b/doc/changelog.rst @@ -0,0 +1,270 @@ +.. _changelog: + +Changelog +========= + +python-ptrace 0.9.9 (2024-03-13) +-------------------------------- + +* Fix arguments of pipe/pipe2 system calls for proper display in call format. + Patch by José Pereira. +* Introduced support for three-digit minor device IDs in ``PROC_MAP_REGEX``. + Patch by fab1ano. +* Added RISCV (riscv32/riscv64) support. + Patch by Andreas Schwab and vimer/yuzibo. +* Fix stray exception raised by child processes terminating with exit code 255. + Patch by Duane Voth/duanev. +* Removed usage of the ``imp`` module. + Collaboration between Mario Haustein/hamarituc and Stephen Kitt/skitt. + +python-ptrace 0.9.8 (2021-03-17) +-------------------------------- + +* Added Arm 64bit (AArch64) support. +* Implemented ``PTRACE_GETREGSET`` and ``PTRACE_SETREGSET`` required on AArch64 + and available on Linux. +* Issue #66: Fix ``SIGTRAP|0x80`` or ``SIGTRAP`` wait in syscall_state.exit + (``PTRACE_O_TRACESYSGOOD``). +* The development branch ``master`` was renamed to ``main``. + See https://sfconservancy.org/news/2020/jun/23/gitbranchname/ for the + rationale. + +python-ptrace 0.9.7 (2020-08-10) +-------------------------------- + +* Add missing module to install directives +* Update README.rst +* Project back in beta and maintenance + +python-ptrace 0.9.6 (2020-08-10) +-------------------------------- + +* Remove RUNNING_WINDOWS constant: python-ptrace doesn't not support Windows. +* Drop Python 2.7 support. six dependency is no longer needed. +* Add close_fds and pass_fds to createChild() function. + Patch by Jean-Baptiste Skutnik. +* Enhance strace.py output for open flags and open optional parameters. + Patch by Jean-Baptiste Skutnik. +* Add support for PowerPC 64-bit (ppc64). + Patch by Jean-Baptiste Skutnik. + +python-ptrace 0.9.5 (2020-04-13) +-------------------------------- + +* Fix readProcessMappings() for device id on 3 digits. Patch by Cat Stevens. +* Drop Python 2 support. + +python-ptrace 0.9.4 (2019-07-30) +-------------------------------- + +* Issue #36: Fix detaching from process object created without is_attached=True +* The project now requires the six module. +* Project moved to: https://github.com/vstinner/python-ptrace + +python-ptrace 0.9.3 (2017-09-19) +-------------------------------- + +* Issue #42: Fix test_strace.py: tolerate the openat() syscall. + +python-ptrace 0.9.2 (2017-02-12) +-------------------------------- + +* Issue #35: Fix strace.py when tracing multiple processes: use the correct + process. Fix suggested by Daniel Trnka. + +python-ptrace 0.9.1 (2016-10-12) +-------------------------------- + +* Added tracing of processes created with the clone syscall (commonly known as + threads). +* gdb.py: add ``gcore`` command, dump the process memory. +* Allow command names without absolute path. +* Fix ptrace binding: clear errno before calling ptrace(). +* Fix PtraceSyscall.exit() for unknown error code +* Project moved to GitHub: https://github.com/haypo/python-ptrace +* Remove the ``ptrace.ctypes_errno`` module: use directly + the ``ctypes.get_errno()`` function +* Remove the ``ptrace.ctypes_errno`` module: use directly + ``ctypes.c_int8``, ``ctypes.c_uint32``, ... types + +python-ptrace 0.9 (2016-04-23) +------------------------------ + +* Add all Linux syscall prototypes +* Add error symbols (e.g. ENOENT), in addition to error text, for strace +* Fix open mode so O_RDONLY shows if it's the only file access mode +* Python 3: fix formatting of string syscall arguments (ex: filenames), decode + bytes from the locale encoding +* Issue #17: syscall parser now supports O_CLOEXEC and SOCK_CLOEXEC, fix unit + tests on Python 3.4 and newer + +python-ptrace 0.8.1 (2014-10-30) +-------------------------------- + +* Update MANIFEST.in to include all files +* Fix to support Python 3.5 + +python-ptrace 0.8 (2014-10-05) +------------------------------ + +* Issue #9: Rewrite waitProcessEvent() and waitSignals() methods of + PtraceProcess to not call waitpid() with pid=-1. There is a race condition + with waitpid(-1) and fork, the status of the child process may be returned + before the debugger is noticed of the creation of the new child process. +* Issue #10: Fix PtraceProcess.readBytes() for processes not created by the + debugger (ex: fork) if the kernel blocks access to private mappings of + /proc/pid/mem. Fallback to PTRACE_PEEKTEXT which is slower but a debugger + tracing the process is always allowed to use it. + +python-ptrace 0.7 (2013-03-05) +------------------------------ + +* Experimental support of Python 3.3 in the same code base +* Drop support of Python 2.5 +* Remove the ptrace.compatibility module +* Fix Process.readStruct() and Process.readArray() on x86_64 +* Experimental support of ARM architecture (Linux EAPI), + strace.py has been tested on Raspberry Pi (armv6l) + +python-ptrace 0.6.6 (2013-12-16) +-------------------------------- + +* Fix os_tools.RUNNING_LINUX for Python 2.x compiled on Linux kernel 3.x +* Support FreeBSD on x86_64 +* Add missing prototype of the unlinkat() system call. Patch written by + Matthew Fernandez. + +python-ptrace 0.6.5 (2013-06-06) +-------------------------------- + +* syscall: fix parsing socketcall on Linux x86 +* syscall: fix prototype of socket() + +python-ptrace 0.6.4 (2012-02-26) +-------------------------------- + +* Convert all classes to new-style classes, patch written by teythoon +* Fix compilation on Apple, patch written by Anthony Gelibert +* Support GNU/kFreeBSD, patch written by Jakub Wilk +* Support sockaddr_in6 (IPv6 address) + +python-ptrace 0.6.3 (2011-02-16) +-------------------------------- + +* Support distrom3 +* Support Python 3 +* Rename strace.py option --socketcall to --socket, and fix this option for + FreeBSD and Linux/64 bits +* Add MANIFEST.in: include all files in source distribution (tests, cptrace + module, ...) + +python-ptrace 0.6.2 (2009-11-09) +-------------------------------- + +* Fix 64 bits sub registers (set mask for eax, ebx, ecx, edx) + +python-ptrace 0.6.1 (2009-11-07) +-------------------------------- + +* Create follow, showfollow, resetfollow, xray commands in gdb.py. Patch + written by Dimitris Glynos +* Project website moved to: ``http://bitbucket.org/haypo/python-ptrace/`` +* Replace types (u)intXX_t by c_(u)intXX +* Create MemoryMapping.search() method and MemoryMapping now keeps a weak + reference to the process + +python-ptrace 0.6 (2009-02-13) +------------------------------ + +User visible changes: + +* python-ptrace now depends on Python 2.5 +* Invalid memory access: add fault address in the name +* Update Python 3.0 conversion patch +* Create -i (--show-ip) option to strace.py: show instruction pointer +* Add a new example (itrace.py) written by Mark Seaborn and based + on strace.py + +API changes: + +* PtraceSyscall: store the instruction pointer at syscall enter (if the + option instr_pointer=True, disabled by default) +* Remove PROC_DIRNAME and procFilename() from ptrace.linux_proc + +Bugfixes: + +* Fix locateProgram() for relative path +* Fix interpretation of memory fault on MOSVW instruction (source is ESI and + destination is EDI, and not the inverse!) + +python-ptrace 0.5 (2008-09-13) +------------------------------ + +Visible changes: + +* Write an example (the most simple debugger) and begin to document the code +* gdb.py: create "dbginfo" command +* Parse socket syscalls on FreeBSD +* On invalid memory access (SIGSEGV), eval the dereference expression to get + the fault address on OS without siginfo (e.g. FreeBSD) +* Fixes to get minimal Windows support: fix imports, fix locateProgram() + +Other changes: + +* Break the API: + - Rename PtraceDebugger.traceSysgood() to PtraceDebugger.enableSysgood() + - Rename PtraceDebugger.trace_sysgood to PtraceDebugger.use_sysgood + - Remove PtraceProcess.readCode() +* Create createChild() function which close all files except stdin, + stdout and stderr +* On FreeBSD, on process exit recalls waitpid(pid) to avoid zombi process + + +python-ptrace 0.4.2 (2008-08-28) +-------------------------------- + +* BUGFIX: Fix typo in gdb.py (commands => command_str), it wasn't possible to + write more than one command... +* BUGFIX: Fix typo in SignalInfo class (remove "self."). When a process + received a signal SIGCHLD (because of a fork), the debugger exited because + of this bug. +* BUGFIX: Debugger._wait() return abnormal process exit as a normal event, + the event is not raised as an exception +* PtraceSignal: don't clear preformatted arguments (e.g. arguments of execve) + +python-ptrace 0.4.1 (2008-08-23) +-------------------------------- + +* The project has a new dedicated website: http://python-ptrace.hachoir.org/ +* Create cptrace: optional Python binding of ptrace written in C (faster + than ptrace, the Python binding written in Python with ctypes) +* Add name attribute to SignalInfo classes +* Fixes to help Python 3.0 compatibility: don't use sys.exc_clear() + (was useless) in writeBacktrace() +* ProcessState: create utime, stime, starttime attributes + +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 diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..6aa3417 --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,233 @@ +# -*- coding: utf-8 -*- +# +# python-ptrace documentation build configuration file, created by +# sphinx-quickstart on Mon Mar 31 10:08:56 2014. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'python-ptrace' +copyright = u'2014, Victor Stinner' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = release = '0.9.9' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'python-ptracedoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'python-ptrace.tex', u'python-ptrace Documentation', + u'Victor Stinner', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'python-ptrace', u'python-ptrace Documentation', + [u'Victor Stinner'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'python-ptrace', u'python-ptrace Documentation', + u'Victor Stinner', 'python-ptrace', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/doc/cptrace.rst b/doc/cptrace.rst new file mode 100644 index 0000000..53c3c4f --- /dev/null +++ b/doc/cptrace.rst @@ -0,0 +1,18 @@ ++++++++++++++++++++++ +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 "", line 1, in + ValueError: ptrace(request=1, pid=1, 0x(nil), 0x(nil)) error #1: Operation not permitted + + diff --git a/doc/gdb.rst b/doc/gdb.rst new file mode 100644 index 0000000..5336fdb --- /dev/null +++ b/doc/gdb.rst @@ -0,0 +1,108 @@ +++++++ +gdb.py +++++++ + +``gdb.py`` is a command line debugger *similar to gdb*, but with fewer +features: no symbol support, no C language support, no thread support, etc. + +Some commands +============= + +* ``cont``: continue execution +* ``stepi``: execute one instruction +* ``step``: execute one instruction, but don't enter into calls + +Type ``help`` to list all available commands. + + +Features +======== + +* print command displays value as decimal and hexadecimal, but also the related + memory mapping (if any):: + + (gdb) print $eip + Decimal: 3086383120 + Hexadecimal: 0xb7f67810 + Address is part of mapping: 0xb7f67000-0xb7f81000 => /lib/ld-2.6.1.so (r-xp) + +* Nice output of signal: see [[signal|python-ptrace signal handling]] +* Syscall tracer with command "sys": see `python-ptrace system call tracer `. Short example:: + + (gdb) sys + long access(char* filename='/etc/ld.so.nohwcap' at 0xb7f7f35b, int mode=F_OK) = -2 (No such file or directory) + +* Supports multiple processes:: + + (gdb) proclist + (active) + + (gdb) proc + Process ID: 24187 (parent: 24182) + Process state: T (traced) + Process command line: [['tests/fork_execve'] + (...) + (gdb)|switch; proc + Switch to + Process ID: 24188 (parent: 24187) + Process state: T (traced) + Process command line: ['/bin/ls']] + (...) + +* Allow multiple commands on the same line using ";" separator:: + + (gdb) print $eax; set $ax=0xdead; print $eax + Decimal: 0 + Hexadecimal: 0x00000000 + Set $ax to 57005 + Decimal: 57005 + Hexadecimal: 0x0000dead + +* Only written in pure Python code, so it's easy to extend +* Expression parser supports all arithmetic operator (``a+b``, ``a/b``, ``a< + Quit gdb. + diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..78bffaa --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,107 @@ +============= +python-ptrace +============= + +.. image:: http://unmaintained.tech/badge.svg + :target: http://unmaintained.tech/ + :alt: No Maintenance Intended + +**This project is no longer maintained and is looking for a new maintainer.** + +python-ptrace is a debugger using ptrace (Linux, BSD and Darwin system call to +trace processes) written in Python. + +* `python-ptrace documentation + `_ +* `python-ptrace at GitHub + `_ +* `python-ptrace at the Python Cheeseshop (PyPI) + `_ + +python-ptrace is an opensource project written in Python under GNU GPLv2 +license. It supports Python 3.6 and newer. + + +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 `_ disassembler +* Dump registers, memory mappings, stack, etc. +* :ref:`Syscall tracer and parser ` (strace.py command) + +Status: + +* Supported operating systems: Linux, FreeBSD, OpenBSD +* Supported architectures: x86, x86_64 (Linux), PPC (Linux), ARM (Linux EAPI) + +Missing features: + +* Symbols: it's not possible to break on a function or read a variable value +* No C language support: debugger shows assembler code, not your C (C++ or other language) code! +* No thread support + + +Table of Contents +================= + +.. toctree:: + :maxdepth: 2 + + install + usage + syscall + gdb + process_events + ptrace_signal + cptrace + authors + changelog + todo + + +Links +===== + +Project using python-ptrace +--------------------------- + +* `Fusil the fuzzer `_ + + +python-ptrace announces +----------------------- + +* `fuzzing mailing list `_ +* `reverse-engineering.net `_ + +ptrace usage +------------ + +* Sandboxing: `Plash `_ + +Similar projects +---------------- + +* `vtrace `_: Python library (Windows and Linux) supporting threads +* `subterfuge `_ by Mike Coleman: Python library (Linux): contains Python binding of ptrace written in C for Python 2.1/2.2. It doesn't work with Python 2.5 (old project, not maintained since 2002) +* `strace `_ program (Linux, BSD) +* ltrace program (Linux) +* truss program (Solaris and BSD) +* `pytstop `_ by Philippe Biondi: debugger similar to gdb but in very alpha stage (e.g. no disassembler), using ptrace Python binding written in C (from subterfuge) +* `strace.py `_ by Philippe Biondi +* `Fenris `_: suite of tools suitable for code analysis, debugging, protocol analysis, reverse engineering, forensics, diagnostics, security audits, vulnerability research +* `PyDBG `_: Windows debugger written in pure Python + +Interesting articles +----------------------- + +* (fr) `Surveiller les connexions avec auditd `_ (2007) +* `Playing with ptrace() for fun and profit `_ (2006) +* `PTRACE_SETOPTIONS tests `_ (2005) +* `Process Tracing Using Ptrace `_ (2002) + diff --git a/doc/install.rst b/doc/install.rst new file mode 100644 index 0000000..e5652bf --- /dev/null +++ b/doc/install.rst @@ -0,0 +1,91 @@ ++++++++++++++++++++++ +Install python-ptrace ++++++++++++++++++++++ + +python-ptrace supports Python 3.6 and newer. + +Linux packages +============== + +* Debian: `python-ptrace Debian package `_. +* Mandriva: `python-ptrace Mandriva package `_ +* OpenEmbedded: `python-ptrace recipe `_ +* Arch Linux: `python-ptrace Arch Linux package `_ +* Gentoo: `dev-python/python-ptrace `_ + +See also `python-ptrace on Python Package Index (PyPI) `_ + +Install from source +=================== + +Download tarball +---------------- + +Get the latest tarball at the `Python Package Index (PyPI) +`_. + +Download development version +---------------------------- + +Download the development version using Git:: + + git clone https://github.com/vstinner/python-ptrace.git + +`Browse python-ptrace source code +`_. + + +Option dependency +----------------- + +* distorm disassembler (optional) + http://www.ragestorm.net/distorm/ + +Installation +------------ + +Note: pip is strongly recommanded. + +Type as root:: + + python3 setup.py install + +Or using sudo program:: + + sudo python3 setup.py install + + +cptrace +======= + +For faster debug and to avoid ctypes, you can also install cptrace: Python +binding of the ptrace() function written in C:: + + python3 setup_cptrace.py install + + +Run tests +========= + +Run tests with tox +------------------ + +To run all tests, just type:: + + tox + +The `tox project `_ creates a clean virtual +environment to run tests. + + +Run tests manually +------------------ + +Type:: + + python3 runtests.py + python3 test_doc.py + +It's also possible to run a specific test:: + + PYTHONPATH=$PWD python3 tests/test_strace.py diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..60c513d --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-ptrace.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-ptrace.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/doc/process_events.rst b/doc/process_events.rst new file mode 100644 index 0000000..a21fbb0 --- /dev/null +++ b/doc/process_events.rst @@ -0,0 +1,64 @@ +++++++++++++++++++++++++++++ +python-ptrace process events +++++++++++++++++++++++++++++ + +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, e.g. 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. + diff --git a/doc/ptrace_signal.rst b/doc/ptrace_signal.rst new file mode 100644 index 0000000..5c2ae2d --- /dev/null +++ b/doc/ptrace_signal.rst @@ -0,0 +1,147 @@ +.. _signal: + ++++++++++++++++++++++++++++++ +python-ptrace signal handling ++++++++++++++++++++++++++++++ + +Introduction +============ + +PtraceSignal tries to display useful information when a signal is received. +Depending on the signal number, it shows different information. + +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. + +When a process receives a signal, python-ptrace tries to explain why the signal was emitted. + +General information (not shown for all signals, e.g. not for SIGABRT): + +* CPU instruction causing the crash +* CPU registers related to the crash +* Memory mappings of the memory addresses + +Categorize signals: + +* SIGFPE + + - Division by zero + +* SIGSEGV, SIGBUS + + - Invalid memory read + - Invalid memory write + - Stack overflow + - Invalid memory access + +* SIGABRT + + - Program abort + +* SIGCHLD + + - Child process exit + +Examples +======== + +Division by zero (SIGFPE) +------------------------- + +:: + + Signal: SIGFPE + Division by zero + - instruction: IDIV DWORD [[EBP-0x8] + - register ebp=0xbfdc4a98 + +Invalid memory read/write (SIGSEGV) +----------------------------------- + +:: + + Signal: SIGSEGV + Invalid read from 0x00000008 + - instruction: MOV EAX, [EAX+0x8]] + - mapping: 0x00000008 is not mapped in memory + - register eax=0x00000000 + +:: + + PID: 23766 + Signal: SIGSEGV + Invalid write to 0x00000008 (size=4 bytes) + - instruction: MOV DWORD [[EAX+0x8],|0x2a + - mapping: 0x00000008..0x0000000b is not mapped in memory + - register eax=0x00000000 + +Given information: + +* Address of the segmentation fault +* (if possible) Size of the invalid memory read/write +* CPU instruction causing the crash +* CPU registers related to the crash +* Memory mappings of the related memory address + +Stack overflow (SIGSEGV) +------------------------ + +:: + + Signal: SIGSEGV + STACK OVERFLOW! Stack pointer is in 0xbf534000-0xbfd34000 => [stack]] (rw-p) + - instruction: MOV BYTE [[EBP-0x1004],|0x0 + - mapping: 0xbf533430 is not mapped in memory + - register =0xbf533430 + - register ebp=0xbf534448 + +Child exit (SIGCHLD) +-------------------- + +:: + + PID: 24008 + Signal: SIGCHLD + Child process 24009 exited normally + Signal sent by user 1000 + +Information: + +* Child process identifier +* Child process user identifier + +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. + +Source code +=========== + +See: + +* ``ptrace/debugger/ptrace_signal.py`` +* ``ptrace/debugger/signal_reason.py`` + + diff --git a/doc/syscall.rst b/doc/syscall.rst new file mode 100644 index 0000000..0fde61d --- /dev/null +++ b/doc/syscall.rst @@ -0,0 +1,84 @@ +.. _syscall: + ++++++++++++++++++++++++++++++ +Trace system calls (syscalls) ++++++++++++++++++++++++++++++ + +python-ptrace can trace system calls using ``PTRACE_SYSCALL``. + +PtraceSyscall +============= + +ptrace.syscall module contains PtraceSyscall class: it's a parser of Linux +syscalls similar to strace program. + +Example:: + + connect(5, , 28) = 0 + open('/usr/lib/i686/cmov/libcrypto.so.0.9.8', 0, 0 ) = 4 + mmap2(0xb7e87000, 81920, 3, 2066, 4, 297) = 0xb7e87000 + rt_sigaction(SIGWINCH, 0xbfb7d4a8, 0xbfb7d41c, 8) = 0 + +You can get more information: result type, value address, argument types, and +argument names. + +Examples:: + + long open(const char* filename='/usr/lib/i686/cmov/libcrypto.so.0.9.8' at 0xb7efc027, int flags=0, int mode=0 ) = 4 + long fstat64(unsigned long fd=4, struct stat* buf=0xbfa46e2c) = 0 + long set_robust_list(struct robust_list_head* head=0xb7be5710, size_t len_ptr=12) = 0 + + +strace.py +========= + +Program strace.py is very close to strace program: display syscalls of a program. Example: + + +Features +-------- + +* Nice output of signal: see [[signal|python-ptrace signal handling]] +* Supports multiple processes +* Can trace running process +* Can display arguments name, type and address +* Option ``--filename`` to show only syscall using file names +* Option ``--socketcall`` to show only syscall related to network (socket usage) +* Option ``--syscalls`` to list all known syscalls + + +Example +------- + +:: + + $ ./strace.py /bin/ls + execve(/bin/ls, [['/bin/ls'],|[/* 40 vars */]]) = 756 + brk(0) = 0x0805c000 + access('/etc/ld.so.nohwcap', 0) = -2 (No such file or directory) + mmap2(NULL, 8192, 3, 34, -1, 0) = 0xb7f56000 + access('/etc/ld.so.preload', 4) = -2 (No such file or directory) + (...) + close(1) = 0 + munmap(0xb7c5c000, 4096) = 0 + exit_group(0) + ---done--- + + +Options +------- + +The program has many options. Example with ``--socketcall`` (display only +network functions):: + + $ ./strace.py --socketcall nc localhost 8080 + execve(/bin/nc, [['/bin/nc',|'localhost', '8080']], [[/*|40 vars */]]) = 12948 + socket(AF_FILE, SOCK_STREAM, 0) = 3 + connect(3, , 110) = -2 (No such file or directory) + socket(AF_FILE, SOCK_STREAM, 0) = 3 + connect(3, , 110) = -2 (No such file or directory) + socket(AF_INET, SOCK_STREAM, 6) = 3 + setsockopt(3, SOL_SOCKET, SO_REUSEADDR, 3217455272L, 4) = 0 + connect(3, , 16) = -111 (Connection refused) + (...) + diff --git a/doc/todo.rst b/doc/todo.rst new file mode 100644 index 0000000..76781ef --- /dev/null +++ b/doc/todo.rst @@ -0,0 +1,53 @@ +TODO +==== + +Main tasks +---------- + +* Fix strace.py --socketcall: SyscallState.enter() calls ignore_callback + before socketcall are proceed +* 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 + diff --git a/doc/usage.rst b/doc/usage.rst new file mode 100644 index 0000000..7951236 --- /dev/null +++ b/doc/usage.rst @@ -0,0 +1,76 @@ ++++++++++++++++++++ +python-ptrace usage ++++++++++++++++++++ + +Hello World +=========== + +Short example attaching a running process. It gets the instruction pointer, +executes a single step, and gets the new instruction pointer:: + + import ptrace.debugger + import signal + import subprocess + import sys + + def debugger_example(pid): + debugger = ptrace.debugger.PtraceDebugger() + + print("Attach the running process %s" % pid) + process = debugger.addProcess(pid, False) + # process is a PtraceProcess instance + print("IP before: %#x" % process.getInstrPointer()) + + print("Execute a single step") + process.singleStep() + # singleStep() gives back control to the process. We have to wait + # until the process is trapped again to retrieve the control on the + # process. + process.waitSignals(signal.SIGTRAP) + print("IP after: %#x" % process.getInstrPointer()) + + process.detach() + debugger.quit() + + def main(): + args = [sys.executable, '-c', 'import time; time.sleep(60)'] + child_popen = subprocess.Popen(args) + debugger_example(child_popen.pid) + child_popen.kill() + child_popen.wait() + + if __name__ == "__main__": + main() + + +API +=== + +PtraceProcess +------------- + +The PtraceProcess class is an helper to manipulate a traced process. + +Example:: + + tracer = PtraceProcess(pid) # attach the process + tracer.singleStep() # execute one instruction + tracer.cont() # continue execution + tracer.syscall() # break at next syscall + tracer.detach() # detach process + + # Get status + tracer.getreg('al') # get AL register value + regs = tracer.getregs() # read all registers + bytes = tracer.readBytes(regs.ax, 10) # read 10 bytes + tracer.dumpCode() # dump code (as assembler or hexa is the disassembler is missing) + tracer.dumpStack() # dump stack (memory words around ESP) + + # Modify the process + shellcode = '...' + ip = tracer.getInstrPointer() # get EIP/RIP register + bytes = tracer.writeBytes(ip, shellcode) # write some bytes + tracer.setreg('ebx', 0) # set EBX register value to zero + +Read ``ptrace/debugger/process.py`` source code to see more methods. + diff --git a/examples/itrace.py b/examples/itrace.py new file mode 100755 index 0000000..8d8d4c6 --- /dev/null +++ b/examples/itrace.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +""" +Here is a tool which I have been using to debug libc startup code where I +didn't find gdb very helpful. It single steps the process and prints each +instruction pointer address. To go faster, it allows a number of syscalls to +run before starting single-stepping. + +It's possible to pipe the addresses through addr2line to get a very +simple tracing debugger. :-) + +I couldn't see a way to catch syscalls and single step at the same +time. As a consequence the tool can't handle multiple threads. + +Mark +""" + +import signal +from ptrace.debugger import ProcessExit, ProcessSignal +import strace + +class Tracer(strace.SyscallTracer): + def createCommonOptions(self, parser): + parser.add_option( + "-n", dest="syscall_limit", type="int", default=None, + help="Number of syscalls before switching to single step") + super(Tracer, self).createCommonOptions(parser) + + def syscallTrace(self, process): + syscall_limit = self.options.syscall_limit + i = 0 + while i < syscall_limit or syscall_limit is None: + print(i) + i += 1 + process.syscall() + self.debugger.waitSyscall() + i = 0 + while self.debugger: + eip = process.getInstrPointer() + print(i, process.pid, "[%08x]" % eip) + i += 1 + process.singleStep() + event = self.debugger.waitProcessEvent() + if isinstance(event, ProcessExit): + print("process exit") + return + if (isinstance(event, ProcessSignal) and + event.signum & ~128 != signal.SIGTRAP): + print("died with signal %i" % event.signum) + return + +if __name__ == "__main__": + Tracer().main() + diff --git a/examples/simple_dbg.py b/examples/simple_dbg.py new file mode 100755 index 0000000..3444689 --- /dev/null +++ b/examples/simple_dbg.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +from ptrace.debugger.debugger import PtraceDebugger +from ptrace.debugger.child import createChild +from ptrace.tools import locateProgram +from sys import stderr, argv, exit + +def playWithProcess(process): + # Do anything you want with the process here... + print("Dump process registers") + process.dumpRegs() + print("Continue process execution") + process.cont() + print("Wait next process event...") + event = process.waitEvent() + print("New process event: %s" % event) + +def traceProgram(arguments): + # Copy the environment variables + env = None + + # Get the full path of the program + arguments[0] = locateProgram(arguments[0]) + + # Create the child process + return createChild(arguments, False, env) + +def main(): + # Check the command line + if len(argv) < 2: + print("usage: %s program [arg1 arg2 ...]" % argv[0], file=stderr) + print(" or: %s pid" % argv[0], file=stderr) + exit(1) + + # Get the process identifier + is_attached = False + has_pid = False + if len(argv) == 2: + try: + # User asked to attach a process + pid = int(argv[1]) + has_pid = True + except ValueError: + pass + + if not has_pid: + # User asked to create a new program and trace it + arguments = argv[1:] + pid = traceProgram(arguments) + is_attached = True + + # Create the debugger and attach the process + dbg = PtraceDebugger() + process = dbg.addProcess(pid, is_attached) + + # Play with the process and then quit + playWithProcess(process) + dbg.quit() + +if __name__ == "__main__": + main() + diff --git a/gdb.py b/gdb.py new file mode 100755 index 0000000..bcd7cf8 --- /dev/null +++ b/gdb.py @@ -0,0 +1,887 @@ +#!/usr/bin/env python +from ptrace import PtraceError +from ptrace.debugger import (PtraceDebugger, Application, + ProcessExit, NewProcessEvent, ProcessSignal, + ProcessExecution, ProcessError) +from optparse import OptionParser +from os import getpid +from sys import stdout, stderr, exit +from logging import getLogger, info, warning, error +from ptrace.version import __version__ as VERSION, WEBSITE +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, word2bytes) +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 +from ptrace.debugger import ChildError +from ptrace.debugger.memory_mapping import readProcessMappings +from ptrace.os_tools import RUNNING_PYTHON3 +try: + unichr + raw_input +except NameError: + # Python 3 + unichr = chr + raw_input = input + +import re +if stdout.isatty(): + try: + # Use readline for better raw_input() + import readline # noqa + except ImportError: + pass + +# Match a register name: $eax, $gp0, $orig_eax +REGISTER_REGEX = re.compile(r"\$[a-z]+[a-z0-9_]+") + +# BYTES_REGEX = re.compile(r"""(?:'([^'\\]*)'|"([^"\\]*)")""") + +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
)"), + ("set", "set register value (set =)"), + ("sys", "continue execution to next syscall"), + ("signal", "send a signal to the process (signal )"), + ("signals", "display signals"), + + # current process info + ("regs", "display registers"), + ("where", "display true code content (show breakpoints effects on code). e.g. 'where $eip', 'where $eip $eip+20'"), + ("print", "display a value (print )"), + ("hexdump", "dump memory as specified address or address range (hexdump
or hexdump )"), + ("gcore", "generate core file of the process"), + ("where2", "display original code content (don't show effects of breakpoint on code)"), + ("stack", "display stack content"), + ("backtrace", "dump the backtrace"), + ("proc", "display process information"), + ("maps", "display memory mappings"), + + # breakpoints + ("break", "set a breakpoint (break
)"), + ("breakpoints", "display breakpoints"), + ("delete", "delete a breakpoint (delete
)"), + + # processes + ("attach", 'attach a new process (e.g. "attach 2390")'), + ("proclist", "list of traced processes"), + ("switch", "switch active process (switch or switch )"), + + ("follow", r'''follow a term (e.g. "follow '\x12\x14\x27\x13'")'''), + ("showfollow", 'show all "followed" terms'), + ("resetfollow", 'reset all "followed" terms'), + ("xray", 'show addresses of (and possible pointers to) "followed" terms'), + + # other + ("dbginfo", "information about the debugger"), + ("quit", "quit debugger"), + ("help", "display this help"), +) + + +def formatAscii(data): + def asciiChar(byte): + if 32 <= byte <= 126: + return unichr(byte) + else: + return '.' + if RUNNING_PYTHON3: + return u''.join(asciiChar(byte) for byte in data) + else: + return u''.join(asciiChar(ord(byte)) for byte in data) + + +def formatHexa(data): + if RUNNING_PYTHON3: + return u' '.join(u"%02x" % byte for byte in data) + else: + return u' '.join(u"%02x" % ord(byte) for byte in data) + +# finds possible pointer values in process memory space, +# pointing to address + + +def getPointers(process, address): + address = word2bytes(address) + procmaps = readProcessMappings(process) + for pm in procmaps: + for found in pm.search(address): + yield found + + +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 information + self.syscall_options = FunctionCallOptions( + write_types=True, + write_argname=True, + write_address=True, + ) + + # FIXME: Remove self.breaks! + self.breaks = dict() + + self.followterms = [] + + 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 parseBytes(self, text): + # FIXME: Validate input + # if not BYTES_REGEX.match(text): + # raise ValueError('Follow text must be enclosed in quotes!') + text = 'b' + text.lstrip() + value = eval(text) + if not isinstance(value, bytes): + raise TypeError("Input is not a bytes string!") + return value + + def addFollowTerm(self, text): + # Allow terms of the form 'string', "string", '\x04', "\x01\x14" + term = self.parseBytes(text) + self.followterms.append(term) + + def showFollowTerms(self): + print(self.followterms) + + def _xray(self): + for term in self.followterms: + for process in self.debugger: + for procmap in readProcessMappings(process): + for address in procmap.search(term): + yield (process, procmap, address, term) + + # displays the offsets of all terms found in the process memory mappings + # along with possible addresses of pointers pointing to these terms + def xray(self): + for process, procmap, address, term in self._xray(): + pointers = " ".join(formatAddress(ptr_addr) + for ptr_addr in getPointers(process, address)) + print("term[%s] pid[%i] %s %s pointers: %s" % ( + repr(term), process.pid, procmap, + formatAddress(address), + pointers)) + + 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 "): + errmsg = self.attachProcess(command[7:]) + elif command == "regs": + self.process.dumpRegs() + elif command == "stack": + self.process.dumpStack() + elif command == "gcore": + self.gcore(self.process) + 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 == "dbginfo": + self.debuggerInfo() + 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:]) + elif command.startswith("follow "): + errmsg = self.addFollowTerm(command[7:]) + elif command == "showfollow": + self.showFollowTerms() + elif command == "resetfollow": + self.followterms = [] + elif command == "xray": + self.xray() + else: + errmsg = "Unknown command: %r" % command + if errmsg: + print(errmsg, file=stderr) + 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: + raise ValueError("Invalid signal number: %r" % command) + + def signal(self, command): + try: + signum = self.parseSignum(command) + except ValueError as 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 as 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 gcore(self, process): + import re + childPid = str(process).split('#')[-1][:-1] + maps_file = open("/proc/" + childPid + "/maps", 'r') + mem_file = open("/proc/" + childPid + "/mem", 'r', 0) + from sys import argv + dump = open("/vmdump/" + argv[1] + ".dump", "wb") + for line in maps_file.readlines(): # for each mapped region + m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line) + if m.group(3) == 'r': # if this is a readable region + if "/lib" not in line and "/usr" not in line: # for eliminating of shared libs + start = int(m.group(1), 16) + end = int(m.group(2), 16) + mem_file.seek(start) # seek to region start + chunk = mem_file.read(end - start) # read region contents + dump.write(chunk,) # dump contents to standard output + maps_file.close() + mem_file.close() + dump.close() + + 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 as 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 as 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 + memory = self.process.readBytes(address, size) + + # Format bytes + hexa = formatHexa(memory) + hexa = hexa.ljust(width * 3 - 1, ' ') + + ascii = formatAscii(memory) + 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 as 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: + return "Invalid command: %r" % command + try: + value = self.parseInteger(value) + except ValueError as err: + return str(err) + try: + self.process.setreg(key, value) + except ProcessError as 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 as err: + error("Unable to read current instruction: %s" % err) + + def attachProcess(self, text): + try: + pid = self.parseInteger(text) + except ValueError as 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: # noqa: E722 + if breakpoint: + breakpoint.desinstall() + raise + return None + + def newProcess(self, event): + error("New process: %s" % event.process) + + # FIXME: This function doesn't work with 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 as 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 as 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 = next(iter(self.debugger)) + self.switchProcess(process) + except StopIteration: + pass + + def displayBreakpoints(self): + found = False + for process in self.debugger: + for bp in process.breakpoints.values(): + found = True + error("%s:%s" % (process, bp)) + if not found: + error("(no breakpoint)") + + def displaySignals(self): + signals = list(SIGNAMES.items()) + signals.sort(key=lambda key_value: key_value[0]) + 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 as 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 as err: + return str(err) + + # Create breakpoint + size = self.readInstrSize(address) + try: + bp = self.process.createBreakpoint(address, size) + except PtraceError as 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 as 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, and parenthesis in expressions") + error( + 'Use ";" to write multiple commands on the same line (e.g. "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 debuggerInfo(self): + error("Debugger process ID: %s" % getpid()) + error("python-ptrace version %s" % VERSION) + error("Website: %s" % WEBSITE) + + 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 as 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() + try: + ok &= self.execute(command) + except Exception as err: + print("Command error: %s" % err) + raise + ok = False + if not ok: + break + if ok: + self.previous_command = command_str + except KeyboardInterrupt: + self.interrupt() + except NewProcessEvent as event: + self.newProcess(event) + except ProcessSignal as event: + self.processSignal(event) + except ProcessExit as event: + error(event) + self.nextProcess() + except ProcessExecution as event: + self.processExecution(event) + except PtraceError as err: + error("ERROR: %s" % err) + if err.errno == ESRCH: + self.deleteProcess(err.pid) + return False + + def runDebugger(self): + self.setupDebugger() + + # Create new process + try: + self.process = self.createProcess() + except ChildError as err: + writeError(getLogger(), err, "Unable to create child process") + return + if not self.process: + return + + # Trace syscalls + self.invite = '(gdb) ' + self.previous_command = None + while True: + if not self.debugger: + # There is no more process: quit + return + done = self.mainLoop() + if done: + return + + def main(self): + self.debugger = PtraceDebugger() + try: + self.runDebugger() + except KeyboardInterrupt: + error("Interrupt debugger: quit!") + except PTRACE_ERRORS as err: + writeError(getLogger(), err, "Debugger error") + self.process = None + self.debugger.quit() + error("Quit gdb.") + self.restoreTerminal() + + +if __name__ == "__main__": + Gdb().main() diff --git a/ptrace/__init__.py b/ptrace/__init__.py new file mode 100644 index 0000000..69bdc54 --- /dev/null +++ b/ptrace/__init__.py @@ -0,0 +1,3 @@ +from ptrace.signames import SIGNAMES, signalName # noqa +from ptrace.error import PtraceError # noqa +from ptrace.version import VERSION, __version__ # noqa diff --git a/ptrace/binding/__init__.py b/ptrace/binding/__init__.py new file mode 100644 index 0000000..eb0374c --- /dev/null +++ b/ptrace/binding/__init__.py @@ -0,0 +1,28 @@ +from ptrace.binding.func import ( # noqa + HAS_PTRACE_SINGLESTEP, HAS_PTRACE_EVENTS, + HAS_PTRACE_IO, HAS_PTRACE_SIGINFO, HAS_PTRACE_GETREGS, + HAS_PTRACE_GETREGSET, 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, # noqa + 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 # noqa +if HAS_PTRACE_SIGINFO: + from ptrace.binding.func import ptrace_getsiginfo # noqa +if HAS_PTRACE_IO: + from ptrace.binding.func import ptrace_io # noqa + from ptrace.binding.freebsd_struct import ( # noqa + ptrace_io_desc, + PIOD_READ_D, PIOD_WRITE_D, + PIOD_READ_I, PIOD_WRITE_I) +if HAS_PTRACE_GETREGS or HAS_PTRACE_GETREGSET: + from ptrace.binding.func import ptrace_getregs # noqa diff --git a/ptrace/binding/cpu.py b/ptrace/binding/cpu.py new file mode 100644 index 0000000..91c985c --- /dev/null +++ b/ptrace/binding/cpu.py @@ -0,0 +1,79 @@ +from ptrace.cpu_info import ( + CPU_POWERPC, CPU_INTEL, CPU_X86_64, CPU_I386, CPU_ARM32, CPU_AARCH64, CPU_RISCV) + +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_ARM32: + CPU_INSTR_POINTER = 'r15' + CPU_STACK_POINTER = 'r14' + CPU_FRAME_POINTER = 'r11' +elif CPU_AARCH64: + CPU_INSTR_POINTER = 'pc' + CPU_STACK_POINTER = 'sp' + CPU_FRAME_POINTER = 'r29' +elif CPU_RISCV: + CPU_INSTR_POINTER = 'pc' + CPU_STACK_POINTER = 'sp' + CPU_FRAME_POINTER = 's0' +elif CPU_X86_64: + CPU_INSTR_POINTER = "rip" + CPU_STACK_POINTER = "rsp" + CPU_FRAME_POINTER = "rbp" + CPU_SUB_REGISTERS = { + # main register name, shift, mask + '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, None), + 'ebx': ('rbx', 32, None), + 'ecx': ('rcx', 32, None), + 'edx': ('rdx', 32, None), + } +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), + }) diff --git a/ptrace/binding/freebsd_struct.py b/ptrace/binding/freebsd_struct.py new file mode 100644 index 0000000..0a57a9b --- /dev/null +++ b/ptrace/binding/freebsd_struct.py @@ -0,0 +1,75 @@ +from ctypes import (Structure, + c_int, c_uint, c_ulong, c_void_p, + c_uint16, c_uint32, c_size_t) +from ptrace.cpu_info import CPU_X86_64 + +PIOD_READ_D = 1 +PIOD_WRITE_D = 2 +PIOD_READ_I = 3 +PIOD_WRITE_I = 4 + +# /usr/include/machine/reg.h +if CPU_X86_64: + register_t = c_ulong + + class reg(Structure): + _fields_ = ( + ("r15", register_t), + ("r14", register_t), + ("r13", register_t), + ("r12", register_t), + ("r11", register_t), + ("r10", register_t), + ("r9", register_t), + ("r8", register_t), + ("rdi", register_t), + ("rsi", register_t), + ("rbp", register_t), + ("rbx", register_t), + ("rdx", register_t), + ("rcx", register_t), + ("rax", register_t), + ("trapno", c_uint32), + ("fs", c_uint16), + ("gs", c_uint16), + ("err", c_uint32), + ("es", c_uint16), + ("ds", c_uint16), + ("rip", register_t), + ("cs", register_t), + ("rflags", register_t), + ("rsp", register_t), + ("ss", register_t), + ) +else: + 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", c_size_t), + ) diff --git a/ptrace/binding/func.py b/ptrace/binding/func.py new file mode 100644 index 0000000..ca70659 --- /dev/null +++ b/ptrace/binding/func.py @@ -0,0 +1,324 @@ +from os import strerror +from ctypes import addressof, c_int, get_errno, set_errno, sizeof +from ptrace import PtraceError +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, CPU_AARCH64, CPU_RISCV + +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, iovec_struct) + 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 +HAS_PTRACE_GETREGSET = False +HAS_PTRACE_SETREGS = False +HAS_PTRACE_SETREGSET = False + +# Special flags that are required to wait for cloned processes (threads) +# See wait(2) +THREAD_TRACE_FLAGS = 0x00000000 + +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 + if not (CPU_AARCH64 or CPU_RISCV): + HAS_PTRACE_GETREGS = True + HAS_PTRACE_SETREGS = True + PTRACE_GETREGS = 12 + PTRACE_SETREGS = 13 + + HAS_PTRACE_GETREGSET = True + HAS_PTRACE_SETREGSET = True + PTRACE_GETREGSET = 0x4204 + PTRACE_SETREGSET = 0x4205 + NT_PRSTATUS = 1 + + 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 + + # Linux introduces the __WALL flag for wait + THREAD_TRACE_FLAGS = 0x40000000 + +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: + set_errno(0) + result = _ptrace(command, pid, arg1, arg2, check_errno) + except ValueError as 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 + + elif HAS_PTRACE_GETREGSET: + def ptrace_getregs(pid): + regs = ptrace_registers_t() + iov = iovec_struct() + setattr(iov, "buf", addressof(regs)) + setattr(iov, "len", sizeof(regs)) + ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, addressof(iov)) + return regs + + if HAS_PTRACE_SETREGS: + def ptrace_setregs(pid, regs): + ptrace(PTRACE_SETREGS, pid, 0, addressof(regs)) + + elif HAS_PTRACE_SETREGSET: + def ptrace_setregs(pid, regs): + iov = iovec_struct() + setattr(iov, "buf", addressof(regs)) + setattr(iov, "len", sizeof(regs)) + ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, addressof(iov)) + + 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)) diff --git a/ptrace/binding/linux_struct.py b/ptrace/binding/linux_struct.py new file mode 100644 index 0000000..4f6accc --- /dev/null +++ b/ptrace/binding/linux_struct.py @@ -0,0 +1,310 @@ +from ctypes import (Structure, Union, sizeof, + c_char, c_ushort, c_int, c_uint, c_ulong, c_void_p, + c_uint16, c_uint32, c_uint64, c_size_t) +from ptrace.cpu_info import CPU_64BITS, CPU_PPC32, CPU_PPC64, CPU_ARM32, CPU_AARCH64, CPU_RISCV + +pid_t = c_int +uid_t = c_ushort +clock_t = c_uint + +# From /usr/include/asm-i386/user.h +# Also more reliably in the kernel sources: +# arch/$ARCH/include/uapi/asm/ptrace.h + + +class register_structure(Structure): + def __str__(self): + regs = {} + for reg in self.__class__._fields_: + regs.update({reg[0]: getattr(self, reg[0])}) + return str(regs) + + +class user_regs_struct(register_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_PPC64: + _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), + ("softe", c_ulong), + ("trap", c_ulong), + ("dar", c_ulong), + ("dsisr", c_ulong), + ("result", c_ulong), + ) + elif CPU_ARM32: + _fields_ = tuple(("r%i" % reg, c_ulong) for reg in range(18)) + elif CPU_AARCH64: + _fields_ = tuple([*[("r%i" % reg, c_ulong) for reg in range(31)], + ('sp', c_ulong), + ('pc', c_ulong), + ('pstate', c_ulong)] + ) + elif CPU_RISCV: + _fields_ = ( + ("pc", c_ulong), + ("ra", c_ulong), + ("sp", c_ulong), + ("gp", c_ulong), + ("tp", c_ulong), + ("t0", c_ulong), + ("t1", c_ulong), + ("t2", c_ulong), + ("s0", c_ulong), + ("s1", c_ulong), + ("a0", c_ulong), + ("a1", c_ulong), + ("a2", c_ulong), + ("a3", c_ulong), + ("a4", c_ulong), + ("a5", c_ulong), + ("a6", c_ulong), + ("a7", c_ulong), + ("s2", c_ulong), + ("s3", c_ulong), + ("s4", c_ulong), + ("s5", c_ulong), + ("s6", c_ulong), + ("s7", c_ulong), + ("s8", c_ulong), + ("s9", c_ulong), + ("s10", c_ulong), + ("s11", c_ulong), + ("t3", c_ulong), + ("t4", c_ulong), + ("t5", c_ulong), + ("t6", 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(register_structure): + if CPU_64BITS: + _fields_ = ( + ("cwd", c_uint16), + ("swd", c_uint16), + ("ftw", c_uint16), + ("fop", c_uint16), + ("rip", c_uint64), + ("rdp", c_uint64), + ("mxcsr", c_uint32), + ("mxcr_mask", c_uint32), + ("st_space", c_uint32 * 32), + ("xmm_space", c_uint32 * 64), + ("padding", c_uint32 * 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: + class user_fpxregs_struct(register_structure): + _fields_ = ( + ("cwd", c_ushort), + ("swd", c_ushort), + ("twd", c_ushort), + ("fop", c_ushort), + ("fip", c_ulong), + ("fcs", c_ulong), + ("foo", c_ulong), + ("fos", c_ulong), + ("mxcsr", c_ulong), + ("reserved", c_ulong), + ("st_space", c_ulong * 32), + ("xmm_space", c_ulong * 32), + ("padding", c_ulong * 56) + ) + +# From /usr/include/asm-generic/siginfo.h + + +class _sifields_sigfault_t(Union): + _fields_ = ( + ("_addr", c_void_p), + ) + + +class _sifields_sigchld_t(Structure): + _fields_ = ( + ("pid", pid_t), + ("uid", uid_t), + ("status", c_int), + ("utime", clock_t), + ("stime", clock_t), + ) + + +class _sifields_t(Union): + _fields_ = ( + ("pad", c_char * (128 - 3 * sizeof(c_int))), + ("_sigchld", _sifields_sigchld_t), + ("_sigfault", _sifields_sigfault_t), + # ("_kill", _sifields_kill_t), + # ("_timer", _sifields_timer_t), + # ("_rt", _sifields_rt_t), + # ("_sigpoll", _sifields_sigpoll_t), + ) + + +class siginfo(Structure): + _fields_ = ( + ("si_signo", c_int), + ("si_errno", c_int), + ("si_code", c_int), + ("_sifields", _sifields_t) + ) + _anonymous_ = ("_sifields",) + + +class iovec_struct(Structure): + _fields_ = ( + ("buf", c_void_p), + ("len", c_size_t) + ) diff --git a/ptrace/binding/openbsd_struct.py b/ptrace/binding/openbsd_struct.py new file mode 100644 index 0000000..6070490 --- /dev/null +++ b/ptrace/binding/openbsd_struct.py @@ -0,0 +1,47 @@ +from ctypes import Structure, c_int, c_uint, c_ulong, c_void_p, c_char + +PIOD_READ_D = 1 +PIOD_WRITE_D = 2 +PIOD_READ_I = 3 +PIOD_WRITE_I = 4 + +size_t = c_ulong +pid_t = c_int + +# /usr/include/machine/reg.h + + +class reg(Structure): + _fields_ = ( + ("eax", c_uint), + ("ecx", c_uint), + ("edx", c_uint), + ("ebx", c_uint), + ("esp", c_uint), + ("ebp", c_uint), + ("esi", c_uint), + ("edi", c_uint), + ("eip", c_uint), + ("eflags", c_uint), + ("cs", c_uint), + ("ss", c_uint), + ("ds", c_uint), + ("es", c_uint), + ("fs", c_uint), + ("gs", c_uint), + ) + + +class fpreg(Structure): + _fields_ = ( + ("__data", c_char * 116), + ) + + +class ptrace_io_desc(Structure): + _fields_ = ( + ("piod_op", c_int), + ("piod_offs", c_void_p), + ("piod_addr", c_void_p), + ("piod_len", size_t), + ) diff --git a/ptrace/cpu_info.py b/ptrace/cpu_info.py new file mode 100644 index 0000000..85e2e95 --- /dev/null +++ b/ptrace/cpu_info.py @@ -0,0 +1,69 @@ +""" +Constants about the CPU: + + - CPU_BIGENDIAN (bool) + - CPU_64BITS (bool) + - CPU_WORD_SIZE (int) + - CPU_MAX_UINT (int) + - CPU_PPC32 (bool) + - CPU_PPC64 (bool) + - CPU_I386 (bool) + - CPU_X86_64 (bool) + - CPU_INTEL (bool) + - CPU_POWERPC (bool) +""" + +try: + from os import uname + HAS_UNAME = True +except ImportError: + HAS_UNAME = False + from platform import architecture +from sys import byteorder +from ctypes import sizeof, c_void_p + +CPU_BIGENDIAN = (byteorder == 'big') +CPU_64BITS = (sizeof(c_void_p) == 8) + +if CPU_64BITS: + CPU_WORD_SIZE = 8 # bytes + CPU_MAX_UINT = 0xffffffffffffffff +else: + CPU_WORD_SIZE = 4 # bytes + CPU_MAX_UINT = 0xffffffff + +if HAS_UNAME: + # guess machine type using uname() + _machine = uname()[4] + CPU_PPC32 = (_machine == 'ppc') + CPU_PPC64 = (_machine in ('ppc64', 'ppc64le')) + CPU_I386 = (_machine in ("i386", "i686")) # compatible Intel 32 bits + CPU_X86_64 = (_machine in ("x86_64", "amd64")) # compatible Intel 64 bits + CPU_ARM32 = _machine.startswith('arm') + CPU_AARCH64 = (_machine == 'aarch64') + CPU_RISCV32 = (_machine == 'riscv32') + CPU_RISCV64 = (_machine == 'riscv64') + del _machine +else: + # uname() fallback for Windows + # I hope that your Windows doesn't run on PPC32/PPC64 + CPU_PPC32 = False + CPU_PPC64 = False + CPU_I386 = False + CPU_X86_64 = False + CPU_ARM32 = False + CPU_AARCH64 = False + CPU_RISCV32 = False + CPU_RISCV64 = False + bits, linkage = architecture() + if bits == '32bit': + CPU_I386 = True + elif bits == '64bit': + CPU_X86_64 = True + else: + raise ValueError("Unknown architecture bits: %r" % bits) + +CPU_INTEL = (CPU_I386 or CPU_X86_64) +CPU_POWERPC = (CPU_PPC32 or CPU_PPC64) +CPU_ARM = (CPU_ARM32 or CPU_AARCH64) +CPU_RISCV = (CPU_RISCV32 or CPU_RISCV64) diff --git a/ptrace/ctypes_libc.py b/ptrace/ctypes_libc.py new file mode 100644 index 0000000..f99fc1f --- /dev/null +++ b/ptrace/ctypes_libc.py @@ -0,0 +1,11 @@ +""" +Load the system C library. Variables: + - LIBC_FILENAME: the C library filename + - libc: the loaded library +""" + +from ctypes import CDLL +from ctypes.util import find_library + +LIBC_FILENAME = find_library('c') +libc = CDLL(LIBC_FILENAME, use_errno=True) diff --git a/ptrace/ctypes_tools.py b/ptrace/ctypes_tools.py new file mode 100644 index 0000000..209db18 --- /dev/null +++ b/ptrace/ctypes_tools.py @@ -0,0 +1,186 @@ +from struct import pack, unpack +from ptrace.cpu_info import CPU_64BITS +from ctypes import cast, POINTER + + +def int2uint64(value): + """ + Convert a signed 64 bits integer into an unsigned 64 bits integer. + + >>> print(int2uint64(1)) + 1 + >>> print(int2uint64(2**64 + 1)) # ignore bits larger than 64 bits + 1 + >>> print(int2uint64(-1)) + 18446744073709551615 + >>> print(int2uint64(-2)) + 18446744073709551614 + """ + return (value & 0xffffffffffffffff) + + +def uint2int64(value): + """ + Convert an unsigned 64 bits integer into a signed 64 bits integer. + + >>> print(uint2int64(1)) + 1 + >>> print(uint2int64(2**64 + 1)) # ignore bits larger than 64 bits + 1 + >>> print(uint2int64(18446744073709551615)) + -1 + >>> print(uint2int64(18446744073709551614)) + -2 + """ + value = value & 0xffffffffffffffff + if value & 0x8000000000000000: + return value - 0x10000000000000000 + else: + return value + + +def truncateWord32(value): + """ + Truncate an unsigned integer to 32 bits. + """ + return value & 0xFFFFFFFF + + +def truncateWord64(value): + """ + Truncate an unsigned integer to 64 bits. + """ + return value & 0xFFFFFFFFFFFFFFFF + + +def formatUintHex16(value): + """ + Format an 16 bits unsigned integer. + """ + return u"0x%04x" % value + + +def formatUintHex32(value): + """ + Format an 32 bits unsigned integer. + """ + return u"0x%08x" % value + + +def formatUintHex64(value): + """ + Format an 64 bits unsigned integer. + """ + return u"0x%016x" % value + + +def int2uint32(value): + """ + Convert a signed 32 bits integer into an unsigned 32 bits integer. + + >>> print(int2uint32(1)) + 1 + >>> print(int2uint32(2**32 + 1)) # ignore bits larger than 32 bits + 1 + >>> print(int2uint32(-1)) + 4294967295 + """ + return value & 0xffffffff + + +def uint2int32(value): + """ + Convert an unsigned 32 bits integer into a signed 32 bits integer. + + >>> print(uint2int32(1)) + 1 + >>> print(uint2int32(2**32 + 1)) # ignore bits larger than 32 bits + 1 + >>> print(uint2int32(4294967295)) + -1 + >>> print(uint2int32(4294967294)) + -2 + >>> print(uint2int32(18446744073709551615)) + -1 + """ + value = value & 0xffffffff + if value & 0x80000000: + v = value - 0x100000000 + else: + v = value + return v + + +uint2int = uint2int32 +int2uint = int2uint32 +if CPU_64BITS: + ulong2long = uint2int64 + long2ulong = int2uint64 + formatWordHex = formatUintHex64 + truncateWord = truncateWord64 +else: + ulong2long = uint2int32 + long2ulong = int2uint32 + formatWordHex = formatUintHex32 + truncateWord = truncateWord32 + + +def formatAddress(address): + """ + Format an address to hexadecimal. + Return "NULL" for zero. + """ + if address: + return formatWordHex(address) + else: + return u"NULL" + + +def formatAddressRange(start, end): + """ + Format an address range, e.g. "0x80004000-0x8000ffff". + """ + return u"%s-%s" % (formatWordHex(start), formatWordHex(end)) + + +def ntoh_ushort(value): + """ + Convert an unsigned short integer from network endian to host endian. + """ + return unpack("H", value))[0] + + +def ntoh_uint(value): + """ + Convert an unsigned integer from network endian to host endian. + """ + return unpack("I", value))[0] + + +def word2bytes(word): + """ + Convert an unsigned integer (a CPU word) to a bytes string. + """ + return pack("L", word) + + +def bytes2word(bytes): + """ + Convert a bytes string to an unsigned integer (a CPU word). + """ + return unpack("L", bytes)[0] + + +def bytes2type(bytes, type): + """ + Cast a bytes string to an object of the specified type. + """ + return cast(bytes, POINTER(type))[0] + + +def bytes2array(bytes, basetype, size): + """ + Cast a bytes string to an array of objects of the specified type + and size. + """ + return bytes2type(bytes, basetype * size) diff --git a/ptrace/debugger/__init__.py b/ptrace/debugger/__init__.py new file mode 100644 index 0000000..f6574e3 --- /dev/null +++ b/ptrace/debugger/__init__.py @@ -0,0 +1,9 @@ +from ptrace.debugger.breakpoint import Breakpoint # noqa +from ptrace.debugger.process_event import (ProcessEvent, ProcessExit, # noqa + NewProcessEvent, ProcessExecution) +from ptrace.debugger.ptrace_signal import ProcessSignal # noqa +from ptrace.debugger.process_error import ProcessError # noqa +from ptrace.debugger.child import ChildError # noqa +from ptrace.debugger.process import PtraceProcess # noqa +from ptrace.debugger.debugger import PtraceDebugger, DebuggerError # noqa +from ptrace.debugger.application import Application # noqa diff --git a/ptrace/debugger/application.py b/ptrace/debugger/application.py new file mode 100644 index 0000000..c57d458 --- /dev/null +++ b/ptrace/debugger/application.py @@ -0,0 +1,91 @@ +from optparse import OptionGroup +from logging import (getLogger, StreamHandler, + DEBUG, INFO, WARNING, ERROR) +from sys import exit +from ptrace import PtraceError +from logging import error +from ptrace.tools import locateProgram +from ptrace.debugger import ProcessExit, DebuggerError +from errno import EPERM +from ptrace.debugger.child import createChild + + +class Application(object): + + def __init__(self): + pass + + def _setupLog(self, fd): + logger = getLogger() + handler = StreamHandler(fd) + logger.addHandler(handler) + if self.options.debug: + level = DEBUG + elif self.options.verbose: + level = INFO + elif self.options.quiet: + level = ERROR + else: + level = WARNING + logger.setLevel(level) + + def processOptions(self): + if self.program: + self.program[0] = locateProgram(self.program[0]) + + def createLogOptions(self, parser): + log = OptionGroup(parser, "Logging") + log.add_option("--quiet", "-q", help="Be quiet (set log level to ERROR)", + action="store_true", default=False) + log.add_option("--verbose", "-v", help="Debug mode (set log level to INFO)", + action="store_true", default=False) + log.add_option("--debug", help="Debug mode (set log level to DEBUG)", + action="store_true", default=False) + parser.add_option_group(log) + + def createChild(self, arguments, env=None): + return createChild(arguments, self.options.no_stdout, env) + + def setupDebugger(self): + # Set ptrace options + if self.options.fork: + try: + self.debugger.traceFork() + except DebuggerError: + error("--fork option is not supported by your OS, sorry!") + exit(1) + if self.options.trace_exec: + self.debugger.traceExec() + if self.options.trace_clone: + self.debugger.traceClone() + + def createProcess(self): + if self.options.pid: + pid = self.options.pid + is_attached = False + error("Attach process %s" % pid) + else: + pid = self.createChild(self.program) + is_attached = True + try: + return self.debugger.addProcess(pid, is_attached=is_attached) + except (ProcessExit, PtraceError) as err: + if isinstance(err, PtraceError) \ + and err.errno == EPERM: + error( + "ERROR: You are not allowed to trace process %s (permission denied or process already traced)" % pid) + else: + error("ERROR: Process can no be attached! %s" % err) + return None + + def createCommonOptions(self, parser): + parser.add_option("--pid", "-p", help="Attach running process specified by its identifier", + type="int", default=None) + parser.add_option("--fork", "-f", help="Trace fork() event and child processes", + action="store_true", default=False) + parser.add_option("--trace-exec", help="Trace execve() event", + action="store_true", default=False) + parser.add_option("--trace-clone", help="Trace clone() event", + action="store_true", default=False) + parser.add_option("--no-stdout", help="Use /dev/null as stdout/stderr, or close stdout and stderr if /dev/null doesn't exist", + action="store_true", default=False) diff --git a/ptrace/debugger/backtrace.py b/ptrace/debugger/backtrace.py new file mode 100644 index 0000000..3a949ec --- /dev/null +++ b/ptrace/debugger/backtrace.py @@ -0,0 +1,115 @@ +from ptrace.ctypes_tools import formatAddress, formatWordHex +from ptrace.cpu_info import CPU_WORD_SIZE, CPU_MAX_UINT +from ptrace import PtraceError + + +class BacktraceFrame(object): + """ + Backtrace frame. + + Attributes: + - ip: instruction pointer + - name: name of the function + - arguments: value of the arguments + """ + + def __init__(self, ip): + self.ip = ip + self.name = u"???" + self.arguments = [] + + def __str__(self): + arguments = (formatWordHex(arg) for arg in self.arguments) + return u"IP=%s: %s (%s)" % (formatAddress(self.ip), self.name, ", ".join(arguments)) + + +class Backtrace(object): + """ + Backtrace: all process frames since the start function. + """ + + def __init__(self): + self.frames = [] + self.truncated = False + + def append(self, frame): + self.frames.append(frame) + + def __iter__(self): + return iter(self.frames) + + def __len__(self): + return len(self.frames) + + +def getBacktrace(process, max_args=6, max_depth=20): + """ + Get the current backtrace of the specified process: + - max_args: maximum number of arguments in a frame + - max_depth: maximum number of frames + + Return a Backtrace object. + """ + backtrace = Backtrace() + + # Get current instruction and frame pointer + ip = process.getInstrPointer() + fp = process.getFramePointer() + depth = 0 + while True: + # Hit maximum trace depth? + if max_depth <= depth: + backtrace.truncated = True + break + + # Read next frame pointer + try: + nextfp = process.readWord(fp) + except PtraceError: + nextfp = None + + # Guess number of function argument + if fp and nextfp: + nargs = ((nextfp - fp) // CPU_WORD_SIZE) - 2 + nargs = min(nargs, max_args) + else: + nargs = 0 + + # Create frame + frame = getBacktraceFrame(process, ip, fp, nargs) + backtrace.append(frame) + + # End of the stack? + if not nextfp: + break + + # Move to next instruction/frame pointer + ip = process.readWord(fp + CPU_WORD_SIZE) + if ip == CPU_MAX_UINT: + # Linux hack to detect end of the stack + break + fp = nextfp + depth += 1 + return backtrace + + +def getBacktraceFrame(process, ip, fp, nargs): + """ + Get a backtrace frame: + - ip: instruction pointer + - fp: frame pointer + - nargs: number of arguments + + Return a BacktraceFrame object. + """ + frame = BacktraceFrame(ip) + address = fp + CPU_WORD_SIZE + try: + for index in range(nargs): + address += CPU_WORD_SIZE + word = process.readWord(address) + frame.arguments.append(word) + except PtraceError: + # Ignore argument read error + pass + return frame diff --git a/ptrace/debugger/breakpoint.py b/ptrace/debugger/breakpoint.py new file mode 100644 index 0000000..8655740 --- /dev/null +++ b/ptrace/debugger/breakpoint.py @@ -0,0 +1,66 @@ +from ptrace.ctypes_tools import formatAddress +from ptrace import PtraceError +from logging import info +from weakref import ref +from ptrace.cpu_info import CPU_POWERPC, CPU_WORD_SIZE +from ptrace.ctypes_tools import word2bytes + + +class Breakpoint(object): + """ + Software breakpoint. + + Use desinstall() method to remove the breakpoint from the process. + """ + + def __init__(self, process, address, size=None): + self._installed = False + self.process = ref(process) + self.address = address + if CPU_POWERPC: + size = CPU_WORD_SIZE + elif size is None: + size = 1 + self.size = size + + # Store instruction bytes + info("Install %s" % self) + self.old_bytes = process.readBytes(address, size) + + if CPU_POWERPC: + # Replace instruction with "TRAP" + new_bytes = word2bytes(0x0cc00000) + else: + # Replace instruction with "INT 3" + new_bytes = b"\xCC" * size + process.writeBytes(address, new_bytes) + self._installed = True + + def desinstall(self, set_ip=False): + """ + Remove the breakpoint from the associated process. If set_ip is True, + restore the instruction pointer to the address of the breakpoint. + """ + if not self._installed: + return + self._installed = False + info("Desinstall %s" % self) + process = self.process() + if not process: + return + if process.running: + process.writeBytes(self.address, self.old_bytes) + if set_ip: + process.setInstrPointer(self.address) + process.removeBreakpoint(self) + + def __str__(self): + return "" % ( + formatAddress(self.address), + formatAddress(self.address + self.size - 1)) + + def __del__(self): + try: + self.desinstall(False) + except PtraceError: + pass diff --git a/ptrace/debugger/child.py b/ptrace/debugger/child.py new file mode 100644 index 0000000..d9b8be4 --- /dev/null +++ b/ptrace/debugger/child.py @@ -0,0 +1,173 @@ +""" +Error pipe and serialization code comes from Python 2.5 subprocess module. +""" +from os import ( + fork, execvp, execvpe, waitpid, + close, dup2, pipe, + read, write, devnull, sysconf, set_inheritable) +from sys import exc_info +from traceback import format_exception +from ptrace.binding import ptrace_traceme +from ptrace import PtraceError +from sys import exit +from errno import EINTR +import fcntl +import pickle + +try: + MAXFD = sysconf("SC_OPEN_MAX") +except Exception: + MAXFD = 256 + + +class ChildError(RuntimeError): + pass + + +class ChildPtraceError(ChildError): + pass + + +def _set_cloexec_flag(fd): + try: + cloexec_flag = fcntl.FD_CLOEXEC + except AttributeError: + cloexec_flag = 1 + + old = fcntl.fcntl(fd, fcntl.F_GETFD) + fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag) + + +def _waitpid_no_intr(pid, options): + """Like os.waitpid, but retries on EINTR""" + while True: + try: + return waitpid(pid, options) + except OSError as e: + if e.errno == EINTR: + continue + else: + raise + + +def _read_no_intr(fd, buffersize): + """Like os.read, but retries on EINTR""" + while True: + try: + return read(fd, buffersize) + except OSError as e: + if e.errno == EINTR: + continue + else: + raise + + +def _write_no_intr(fd, s): + """Like os.write, but retries on EINTR""" + while True: + try: + return write(fd, s) + except OSError as e: + if e.errno == EINTR: + continue + else: + raise + + +def _createParent(pid, errpipe_read): + # Wait for exec to fail or succeed; possibly raising exception + data = _read_no_intr(errpipe_read, 1048576) # Exceptions limited to 1 MB + close(errpipe_read) + if data: + _waitpid_no_intr(pid, 0) + child_exception = pickle.loads(data) + raise child_exception + + +def _createChild(arguments, + no_stdout, + env, + errpipe_write, + close_fds=True, + pass_fds=()): + # Child code + try: + ptrace_traceme() + except PtraceError as err: + raise ChildError(str(err)) + + for fd in pass_fds: + set_inheritable(fd, True) + + if close_fds: + # Close all files except 0, 1, 2 and errpipe_write + for fd in range(3, MAXFD): + if fd == errpipe_write or (fd in pass_fds): + continue + try: + close(fd) + except OSError: + pass + + try: + _execChild(arguments, no_stdout, env) + except: # noqa: E722 + exc_type, exc_value, tb = exc_info() + # Save the traceback and attach it to the exception object + exc_lines = format_exception(exc_type, exc_value, tb) + exc_value.child_traceback = ''.join(exc_lines) + _write_no_intr(errpipe_write, pickle.dumps(exc_value)) + exit(255) + + +def _execChild(arguments, no_stdout, env): + if no_stdout: + try: + null = open(devnull, 'wb') + dup2(null.fileno(), 1) + dup2(1, 2) + null.close() + except IOError: + close(2) + close(1) + try: + if env is not None: + execvpe(arguments[0], arguments, env) + else: + execvp(arguments[0], arguments) + except Exception as err: + raise ChildError(str(err)) + + +def createChild(arguments, no_stdout, env=None, close_fds=True, pass_fds=()): + """ + Create a child process: + - arguments: list of string where (e.g. ['ls', '-la']) + - no_stdout: if True, use null device for stdout/stderr + - env: environment variables dictionary + + Use: + - env={} to start with an empty environment + - env=None (default) to copy the environment + """ + + if pass_fds and not close_fds: + close_fds = True + + errpipe_read, errpipe_write = pipe() + _set_cloexec_flag(errpipe_write) + + # Fork process + pid = fork() + if pid: + close(errpipe_write) + _createParent(pid, errpipe_read) + return pid + else: + close(errpipe_read) + _createChild(arguments, + no_stdout, + env, + errpipe_write, + close_fds=close_fds, + pass_fds=pass_fds) diff --git a/ptrace/debugger/debugger.py b/ptrace/debugger/debugger.py new file mode 100644 index 0000000..bbf0c3d --- /dev/null +++ b/ptrace/debugger/debugger.py @@ -0,0 +1,293 @@ +from logging import info, warning, error +from ptrace import PtraceError +from os import waitpid, WNOHANG +from signal import SIGTRAP, SIGSTOP +from errno import ECHILD +from ptrace.debugger import PtraceProcess, ProcessSignal +from ptrace.binding import HAS_PTRACE_EVENTS +from time import sleep +if HAS_PTRACE_EVENTS: + from ptrace.binding.func import ( + PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK, + PTRACE_O_TRACEEXEC, PTRACE_O_TRACESYSGOOD, + PTRACE_O_TRACECLONE, THREAD_TRACE_FLAGS) + + +class DebuggerError(PtraceError): + pass + + +class PtraceDebugger(object): + """ + Debugger managing one or multiple processes at the same time. + + Methods + ======= + + * Process list: + - addProcess(): add a new process + - deleteProcess(): remove a process from the debugger + + * Wait for an event: + - waitProcessEvent(): wait for a process event + - waitSignals(): wait for a signal + - waitSyscall(): wait for the next syscall event + + * Options: + - traceFork(): enable fork() tracing + - traceExec(): enable exec() tracing + - traceClone(): enable clone() tracing + - enableSysgood(): enable sysgood option + + * Other: + - quit(): quit the debugger, terminate all processes + + Operations + ========== + + - iterate on all processes: "for process in debugger: ..." + - get a process by its identifier: "process = debugger[pid]" + - get the number of processes: len(debugger) + + Attributes + ========== + + - dict: processes dictionary (pid -> PtraceProcess) + - list: processes list + - options: ptrace options + - trace_fork (bool): fork() tracing is enabled? + - trace_exec (bool): exec() tracing is enabled? + - trace_clone (bool): clone() tracing is enabled? + - use_sysgood (bool): sysgood option is enabled? + """ + + def __init__(self): + self.dict = {} # pid -> PtraceProcess object + self.list = [] + self.options = 0 + self.trace_fork = False + self.trace_exec = False + self.trace_clone = False + self.use_sysgood = False + self.enableSysgood() + + def addProcess(self, pid, is_attached, parent=None, is_thread=False): + """ + Add a new process using its identifier. Use is_attached=False to + attach an existing (running) process, and is_attached=True to trace + a new (stopped) process. + """ + if pid in self.dict: + raise KeyError("The process %s is already registered!" % pid) + process = PtraceProcess(self, pid, is_attached, + parent=parent, is_thread=is_thread) + info("Attach %s to debugger" % process) + self.dict[pid] = process + self.list.append(process) + try: + process.waitSignals(SIGTRAP, SIGSTOP) + except KeyboardInterrupt: + error( + "User interrupt! Force the process %s attach " + "(don't wait for signals)." + % pid) + except ProcessSignal as event: + event.display() + except: # noqa: E722 + process.is_attached = False + process.detach() + raise + if HAS_PTRACE_EVENTS and self.options: + process.setoptions(self.options) + return process + + def quit(self): + """ + Quit the debugger: terminate all processes in reverse order. + """ + info("Quit debugger") + # Terminate processes in reverse order + # to kill children before parents + processes = list(self.list) + for process in reversed(processes): + process.terminate() + process.detach() + + def _waitpid(self, wanted_pid, blocking=True): + """ + Wait for a process event from a specific process (if wanted_pid is + set) or any process (wanted_pid=None). The call is blocking is + blocking option is True. Return the tuple (pid, status). + + See os.waitpid() documentation for explanations about the result. + """ + flags = 0 + if not blocking: + flags |= WNOHANG + if wanted_pid: + if wanted_pid not in self.dict: + raise DebuggerError("Unknown PID: %r" % + wanted_pid, pid=wanted_pid) + + process = self.dict[wanted_pid] + if process.is_thread: + flags |= THREAD_TRACE_FLAGS + + pid, status = waitpid(wanted_pid, flags) + else: + pid, status = waitpid(-1, flags) + if (blocking or pid) and wanted_pid and (pid != wanted_pid): + raise DebuggerError("Unwanted PID: %r (instead of %s)" + % (pid, wanted_pid), pid=pid) + return pid, status + + def _wait_event_pid(self, wanted_pid, blocking=True): + """ + Wait for a process event from the specified process identifier. If + blocking=False, return None if there is no new event, otherwise return + an object based on ProcessEvent. + """ + process = None + while not process: + try: + pid, status = self._waitpid(wanted_pid, blocking) + except OSError as err: + if err.errno == ECHILD: + process = self.dict[wanted_pid] + return process.processTerminated() + else: + raise err + if not blocking and not pid: + return None + try: + process = self.dict[pid] + except KeyError: + warning("waitpid() warning: Unknown PID %r" % pid) + return process.processStatus(status) + + def _wait_event(self, wanted_pid, blocking=True): + if wanted_pid is not None: + return self._wait_event_pid(wanted_pid, blocking) + + pause = 0.001 + while True: + pids = tuple(self.dict) + if len(pids) > 1: + for pid in pids: + process = self._wait_event_pid(pid, False) + if process is not None: + return process + if not blocking: + return None + pause = min(pause * 2, 0.5) + sleep(pause) + else: + return self._wait_event_pid(pids[0], blocking) + + def waitProcessEvent(self, pid=None, blocking=True): + """ + Wait for a process event from a specific process (if pid option is + set) or any process (default). If blocking=False, return None if there + is no new event, otherwise return an object based on ProcessEvent. + """ + return self._wait_event(pid, blocking) + + def waitSignals(self, *signals, **kw): + """ + Wait for any signal or some specific signals (if specified) from a + specific process (if pid keyword is set) or any process (default). + Return a ProcessSignal object or raise an unexpected ProcessEvent. + """ + pid = kw.get('pid', None) + while True: + event = self._wait_event(pid) + if event.__class__ != ProcessSignal: + raise event + signum = event.signum + if signum in signals or not signals: + return event + raise event + + def waitSyscall(self, process=None): + """ + Wait for the next syscall event (enter or exit) for a specific process + (if specified) or any process (default). Return a ProcessSignal object + or raise an unexpected ProcessEvent. + """ + signum = SIGTRAP + if self.use_sysgood: + signum |= 0x80 + if process: + return self.waitSignals(signum, pid=process.pid) + else: + return self.waitSignals(signum) + + def deleteProcess(self, process=None, pid=None): + """ + Delete a process from the process list. + """ + if not process: + try: + process = self.dict[pid] + except KeyError: + return + try: + del self.dict[process.pid] + except KeyError: + pass + try: + self.list.remove(process) + except ValueError: + pass + + def traceFork(self): + """ + Enable fork() tracing. Do nothing if it's not supported. + """ + if not HAS_PTRACE_EVENTS: + raise DebuggerError( + "Tracing fork events is not supported on this architecture or operating system") + self.options |= PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK + self.trace_fork = True + info("Debugger trace forks (options=%s)" % self.options) + + def traceExec(self): + """ + Enable exec() tracing. Do nothing if it's not supported. + """ + if not HAS_PTRACE_EVENTS: + # no effect on OS without ptrace events + return + self.trace_exec = True + self.options |= PTRACE_O_TRACEEXEC + + def traceClone(self): + """ + Enable clone() tracing. Do nothing if it's not supported. + """ + if not HAS_PTRACE_EVENTS: + # no effect on OS without ptrace events + return + self.trace_clone = True + self.options |= PTRACE_O_TRACECLONE + + def enableSysgood(self): + """ + Enable sysgood option: ask the kernel to set bit #7 of the signal + number if the signal comes from the kernel space. If the signal comes + from the user space, the bit is unset. + """ + if not HAS_PTRACE_EVENTS: + # no effect on OS without ptrace events + return + self.use_sysgood = True + self.options |= PTRACE_O_TRACESYSGOOD + + def __getitem__(self, pid): + return self.dict[pid] + + def __iter__(self): + return iter(self.list) + + def __len__(self): + return len(self.list) diff --git a/ptrace/debugger/memory_mapping.py b/ptrace/debugger/memory_mapping.py new file mode 100644 index 0000000..df9c3a3 --- /dev/null +++ b/ptrace/debugger/memory_mapping.py @@ -0,0 +1,139 @@ +from ptrace.os_tools import HAS_PROC +from ptrace.debugger.process_error import ProcessError +from ptrace.ctypes_tools import formatAddress +import re +from weakref import ref + +if HAS_PROC: + from ptrace.linux_proc import openProc, ProcError + +PROC_MAP_REGEX = re.compile( + # Address range: '08048000-080b0000 ' + r'([0-9a-f]+)-([0-9a-f]+) ' + # Permission: 'r-xp ' + r'(.{4}) ' + # Offset: '0804d000' + r'([0-9a-f]+) ' + # Device (major:minor): 'fe:01 ' + r'([0-9a-f]{2,3}):([0-9a-f]{2,3}) ' + # Inode: '3334030' + r'([0-9]+)' + # Filename: ' /usr/bin/synergyc' + r'(?: +(.*))?') + + +class MemoryMapping(object): + """ + Process memory mapping (metadata about the mapping). + + Attributes: + - start (int): first byte address + - end (int): last byte address + 1 + - permissions (str) + - offset (int): for file, offset in bytes from the file start + - major_device / minor_device (int): major / minor device number + - inode (int) + - pathname (str) + - _process: weak reference to the process + + Operations: + - "address in mapping" checks the address is in the mapping. + - "search(somestring)" returns the offsets of "somestring" in the mapping + - "str(mapping)" create one string describing the mapping + - "repr(mapping)" create a string representation of the mapping, + useful in list contexts + """ + + def __init__(self, process, start, end, permissions, offset, major_device, minor_device, inode, pathname): + self._process = ref(process) + self.start = start + self.end = end + self.permissions = permissions + self.offset = offset + self.major_device = major_device + self.minor_device = minor_device + self.inode = inode + self.pathname = pathname + + def __contains__(self, address): + return self.start <= address < self.end + + def __str__(self): + text = "%s-%s" % (formatAddress(self.start), formatAddress(self.end)) + if self.pathname: + text += " => %s" % self.pathname + text += " (%s)" % self.permissions + return text + __repr__ = __str__ + + def search(self, bytestr): + process = self._process() + + bytestr_len = len(bytestr) + buf_len = 64 * 1024 + + if buf_len < bytestr_len: + buf_len = bytestr_len + + remaining = self.end - self.start + covered = self.start + + while remaining >= bytestr_len: + if remaining > buf_len: + requested = buf_len + else: + requested = remaining + + data = process.readBytes(covered, requested) + + if data == "": + break + + offset = data.find(bytestr) + if (offset == -1): + skip = requested - bytestr_len + 1 + else: + yield (covered + offset) + skip = offset + bytestr_len + + covered += skip + remaining -= skip + + +def readProcessMappings(process): + """ + Read all memory mappings of the specified process. + + Return a list of MemoryMapping objects, or empty list if it's not possible + to read the mappings. + + May raise a ProcessError. + """ + maps = [] + if not HAS_PROC: + return maps + try: + mapsfile = openProc("%s/maps" % process.pid) + except ProcError as err: + raise ProcessError(process, "Unable to read process maps: %s" % err) + try: + for line in mapsfile: + line = line.rstrip() + match = PROC_MAP_REGEX.match(line) + if not match: + raise ProcessError( + process, "Unable to parse memory mapping: %r" % line) + map = MemoryMapping( + process, + int(match.group(1), 16), + int(match.group(2), 16), + match.group(3), + int(match.group(4), 16), + int(match.group(5), 16), + int(match.group(6), 16), + int(match.group(7)), + match.group(8)) + maps.append(map) + finally: + mapsfile.close() + return maps diff --git a/ptrace/debugger/parse_expr.py b/ptrace/debugger/parse_expr.py new file mode 100644 index 0000000..82ad6c1 --- /dev/null +++ b/ptrace/debugger/parse_expr.py @@ -0,0 +1,76 @@ +import re + +# Match a register name: $eax, $gp0, $orig_eax +REGISTER_REGEX = re.compile(r"([a-z]+[a-z0-9_]+)") + +# Hexadecimal number (e.g. 0xa) +HEXADECIMAL_REGEX = re.compile(r"0x[0-9a-f]+") + +# Make sure that the expression does not contain invalid characters +# Examples: +# (1-2)<<5 +# 340&91 +EXPR_REGEX = re.compile(r"^[()<>+*/&0-9-]+$") + + +def replaceHexadecimal(regs): + """ + Convert an hexadecimal number to decimal number (as string). + Callback used by parseExpression(). + """ + 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) + + +def parseExpression(process, text): + """ + Parse an expression. Syntax: + - "10": decimal number + - "0x10": hexadecimal number + - "eax": register value + - "a+b", "a-b", "a*b", "a/b", "a**b", "a<>b": operators + + >>> from ptrace.mockup import FakeProcess + >>> process = FakeProcess() + >>> parseExpression(process, "1+1") + 2 + >>> process.setreg("eax", 3) + >>> parseExpression(process, "eax*0x10") + 48 + """ + # Remove spaces and convert to lower case + text = text.strip() + orig_text = text + if " " in text: + raise ValueError("Space are forbidden: %r" % text) + text = text.lower() + + def readRegister(regs): + name = regs.group(1) + value = process.getreg(name) + return str(value) + + # Replace hexadecimal by decimal + text = HEXADECIMAL_REGEX.sub(replaceHexadecimal, text) + + # Replace registers by their value + text = REGISTER_REGEX.sub(readRegister, text) + + # Reject invalid characters + if not EXPR_REGEX.match(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 + try: + value = eval(text) + except SyntaxError: + raise ValueError("Invalid expression: %r" % orig_text) + return value diff --git a/ptrace/debugger/process.py b/ptrace/debugger/process.py new file mode 100644 index 0000000..db4573d --- /dev/null +++ b/ptrace/debugger/process.py @@ -0,0 +1,777 @@ +from ptrace.binding import ( + HAS_PTRACE_SINGLESTEP, HAS_PTRACE_EVENTS, + HAS_PTRACE_SIGINFO, HAS_PTRACE_IO, HAS_PTRACE_GETREGS, + HAS_PTRACE_GETREGSET, + ptrace_attach, ptrace_detach, + ptrace_cont, ptrace_syscall, + ptrace_setregs, + ptrace_peektext, ptrace_poketext, + REGISTER_NAMES) +from ptrace.os_tools import HAS_PROC, RUNNING_BSD, RUNNING_PYTHON3 +from ptrace.tools import dumpRegs +from ptrace.cpu_info import CPU_WORD_SIZE +from ptrace.ctypes_tools import bytes2word, word2bytes, bytes2type, bytes2array +from signal import SIGTRAP, SIGSTOP, SIGKILL +from ptrace.ctypes_tools import formatAddress, formatWordHex +from ctypes import sizeof, c_char_p +from logging import info, warning, error +from ptrace.error import PtraceError +from errno import ESRCH, EACCES +from ptrace.debugger import (Breakpoint, + ProcessExit, ProcessSignal, NewProcessEvent, ProcessExecution) +from os import (kill, + WIFSTOPPED, WSTOPSIG, + WIFSIGNALED, WTERMSIG, + WIFEXITED, WEXITSTATUS) +from ptrace.disasm import HAS_DISASSEMBLER +from ptrace.debugger.backtrace import getBacktrace +from ptrace.debugger.process_error import ProcessError +from ptrace.debugger.memory_mapping import readProcessMappings +from ptrace.binding.cpu import CPU_INSTR_POINTER, CPU_STACK_POINTER, CPU_FRAME_POINTER, CPU_SUB_REGISTERS +from ptrace.debugger.syscall_state import SyscallState + +if HAS_PTRACE_SINGLESTEP: + from ptrace.binding import ptrace_singlestep +if HAS_PTRACE_SIGINFO: + from ptrace.binding import ptrace_getsiginfo +if HAS_PTRACE_IO: + from ctypes import create_string_buffer, addressof + from ptrace.binding import ( + ptrace_io, ptrace_io_desc, + PIOD_READ_D, PIOD_WRITE_D) +if HAS_PTRACE_EVENTS: + from ptrace.binding import ( + ptrace_setoptions, ptrace_geteventmsg, WPTRACEEVENT, + PTRACE_EVENT_FORK, PTRACE_EVENT_VFORK, PTRACE_EVENT_CLONE, + PTRACE_EVENT_EXEC) + NEW_PROCESS_EVENT = ( + PTRACE_EVENT_FORK, PTRACE_EVENT_VFORK, PTRACE_EVENT_CLONE) +if HAS_PTRACE_GETREGS or HAS_PTRACE_GETREGSET: + from ptrace.binding import ptrace_getregs +else: + from ptrace.binding import ptrace_peekuser, ptrace_registers_t +if HAS_DISASSEMBLER: + from ptrace.disasm import disassemble, disassembleOne, MAX_INSTR_SIZE +if HAS_PROC: + from ptrace.linux_proc import readProcessStat + +MIN_CODE_SIZE = 32 +MAX_CODE_SIZE = 1024 +DEFAULT_NB_INSTR = 10 +DEFAULT_CODE_SIZE = 24 + + +class PtraceProcess(object): + """ + Process traced by a PtraceDebugger. + + Methods + ======= + + * control execution: + + - singleStep(): execute one instruction + - cont(): continue the execution + - syscall(): break at next syscall + - setInstrPointer(): change the instruction pointer + - kill(): send a signal to the process + - terminate(): kill the process + + * wait an event: + + - waitEvent(): wait next process event + - waitSignals(): wait a signal + + * get status + + - getreg(): get a register + - getInstrPointer(): get the instruction pointer + - getStackPointer(): get the stack pointer + - getFramePointer(): get the stack pointer + - getregs(): get all registers, e.g. regs=getregs(); print regs.eax + - disassemble(): assembler code of the next instructions + - disassembleOne(): assembler code of the next instruction + - findStack(): get stack memory mapping + - getsiginfo(): get signal information + - getBacktrace(): get the current backtrace + + * set status + + - setreg(): set a register + - setregs(): set all registers + + * memory access: + + - readWord(): read a memory word + - readBytes(): read some bytes + - readStruct(): read a structure + - readArray(): read an array + - readCString(): read a C string + - readMappings(): get all memory mappings + - writeWord(): write a memory word + - writeBytes(): write some bytes + + * display status: + + - dumpCode(): display the next instructions + - dumpStack(): display some memory words around the stack pointer + - dumpMaps(): display memory mappings + - dumpRegs(): display all registers + + * breakpoint: + + - createBreakpoint(): set a breakpoint + - findBreakpoint(): find a breakpoint + - removeBreakpoint(): remove a breakpoint + + * other: + + - setoptions(): set ptrace options + + See each method to get better documentation. You are responsible + to manage the process state: some methods may fail or crash your + processus if they are called when the process is in the wrong + state. + + Attributes + ========== + + * main attributes: + - pid: identifier of the process + - debugger: PtraceDebugger instance + - breakpoints: dictionary of active breakpoints + - parent: parent PtraceProcess (None if process has no parent) + + * state: + - running: if True, the process is alive, otherwise the process + doesn't exist anymore + - exited: if True, the process has exited (attributed only used + on BSD operation systems) + - is_attached: if True, the process is attached by ptrace + - was_attached: if True, the process will be detached at exit + - is_stopped: if True, the process is stopped, otherwise it's + running + - syscall_state: control syscall tracing + + Sometimes, is_stopped value is wrong. You might use isTraced() to + make sure that the process is stopped. + """ + + def __init__(self, debugger, pid, is_attached, parent=None, is_thread=False): + self.debugger = debugger + self.breakpoints = {} + self.pid = pid + self.running = True + self.exited = False + self.parent = parent + self.was_attached = is_attached + self.is_attached = False + self.is_stopped = True + self.is_thread = is_thread + if not is_attached: + self.attach() + else: + self.is_attached = True + if HAS_PROC: + self.read_mem_file = None + self.syscall_state = SyscallState(self) + + def isTraced(self): + if not HAS_PROC: + self.notImplementedError() + stat = readProcessStat(self.pid) + return (stat.state == 'T') + + def attach(self): + if self.is_attached: + return + info("Attach process %s" % self.pid) + ptrace_attach(self.pid) + self.is_attached = True + + def dumpCode(self, start=None, stop=None, manage_bp=False, log=None): + if not log: + log = error + try: + ip = self.getInstrPointer() + except PtraceError as err: + if start is None: + log("Unable to read instruction pointer: %s" % err) + return + ip = None + if start is None: + start = ip + + try: + self._dumpCode(start, stop, ip, manage_bp, log) + except PtraceError as err: + log("Unable to dump code at %s: %s" % ( + formatAddress(start), err)) + + def _dumpCode(self, start, stop, ip, manage_bp, log): + if stop is not None: + stop = max(start, stop) + stop = min(stop, start + MAX_CODE_SIZE - 1) + + if not HAS_DISASSEMBLER: + if stop is not None: + size = stop - start + 1 + else: + size = MIN_CODE_SIZE + code = self.readBytes(start, size) + if RUNNING_PYTHON3: + text = " ".join("%02x" % byte for byte in code) + else: + text = " ".join("%02x" % ord(byte) for byte in code) + log("CODE: %s" % text) + return + + log("CODE:") + if manage_bp: + address = start + for line in range(10): + bp = False + if address in self.breakpoints: + bytes = self.breakpoints[address].old_bytes + instr = disassembleOne(bytes, address) + bp = True + else: + instr = self.disassembleOne(address) + text = "%s| %s (%s)" % (formatAddress( + instr.address), instr.text, instr.hexa) + if instr.address == ip: + text += " <==" + if bp: + text += " * BREAKPOINT *" + log(text) + address = address + instr.size + if stop is not None and stop <= address: + break + else: + for instr in self.disassemble(start, stop): + text = "%s| %s (%s)" % (formatAddress( + instr.address), instr.text, instr.hexa) + if instr.address == ip: + text += " <==" + log(text) + + def disassemble(self, start=None, stop=None, nb_instr=None): + if not HAS_DISASSEMBLER: + self.notImplementedError() + if start is None: + start = self.getInstrPointer() + if stop is not None: + stop = max(start, stop) + size = stop - start + 1 + else: + if nb_instr is None: + nb_instr = DEFAULT_NB_INSTR + size = nb_instr * MAX_INSTR_SIZE + + code = self.readBytes(start, size) + for index, instr in enumerate(disassemble(code, start)): + yield instr + if nb_instr and nb_instr <= (index + 1): + break + + def disassembleOne(self, address=None): + if not HAS_DISASSEMBLER: + self.notImplementedError() + if address is None: + address = self.getInstrPointer() + code = self.readBytes(address, MAX_INSTR_SIZE) + return disassembleOne(code, address) + + def findStack(self): + for map in self.readMappings(): + if map.pathname == "[stack]": + return map + return None + + def detach(self): + if not self.is_attached: + return + self.is_attached = False + if self.running: + info("Detach %s" % self) + ptrace_detach(self.pid) + self.debugger.deleteProcess(process=self) + + def _notRunning(self): + self.running = False + if HAS_PROC and self.read_mem_file: + try: + self.read_mem_file.close() + except IOError: + pass + self.detach() + + def kill(self, signum): + kill(self.pid, signum) + + def terminate(self, wait_exit=True): + if not self.running or not self.was_attached: + return True + warning("Terminate %s" % self) + done = False + try: + if self.is_stopped: + self.cont(SIGKILL) + else: + self.kill(SIGKILL) + except PtraceError as event: + if event.errno == ESRCH: + done = True + else: + raise event + if not done: + if not wait_exit: + return False + self.waitExit() + self._notRunning() + return True + + def waitExit(self): + while True: + # Wait for any process signal + event = self.waitEvent() + event_cls = event.__class__ + + # Process exited: we are done + if event_cls == ProcessExit: + return + + # Event different than a signal? Raise an exception + if event_cls != ProcessSignal: + raise event + + # Send the signal to the process + signum = event.signum + if signum not in (SIGTRAP, SIGSTOP): + self.cont(signum) + else: + self.cont() + + def processStatus(self, status): + # Process exited? + if WIFEXITED(status): + code = WEXITSTATUS(status) + event = self.processExited(code) + + # Process killed by a signal? + elif WIFSIGNALED(status): + signum = WTERMSIG(status) + event = self.processKilled(signum) + + # Invalid process status? + elif not WIFSTOPPED(status): + raise ProcessError(self, "Unknown process status: %r" % status) + + # Ptrace event? + elif HAS_PTRACE_EVENTS and WPTRACEEVENT(status): + event = WPTRACEEVENT(status) + event = self.ptraceEvent(event) + + else: + signum = WSTOPSIG(status) + event = self.processSignal(signum) + return event + + def processTerminated(self): + self._notRunning() + return ProcessExit(self) + + def processExited(self, code): + if RUNNING_BSD and not self.exited: + # on FreeBSD, we have to waitpid() twice + # to avoid zombi process!? + self.exited = True + self.waitExit() + self._notRunning() + return ProcessExit(self, exitcode=code) + + def processKilled(self, signum): + self._notRunning() + return ProcessExit(self, signum=signum) + + def processSignal(self, signum): + self.is_stopped = True + return ProcessSignal(signum, self) + + def ptraceEvent(self, event): + if not HAS_PTRACE_EVENTS: + self.notImplementedError() + if event in NEW_PROCESS_EVENT: + new_pid = ptrace_geteventmsg(self.pid) + is_thread = (event == PTRACE_EVENT_CLONE) + new_process = self.debugger.addProcess( + new_pid, is_attached=True, parent=self, is_thread=is_thread) + return NewProcessEvent(new_process) + elif event == PTRACE_EVENT_EXEC: + return ProcessExecution(self) + else: + raise ProcessError(self, "Unknown ptrace event: %r" % event) + + def getregs(self): + if HAS_PTRACE_GETREGS or HAS_PTRACE_GETREGSET: + return ptrace_getregs(self.pid) + else: + # FIXME: Optimize getreg() when used with this function + words = [] + nb_words = sizeof(ptrace_registers_t) // CPU_WORD_SIZE + for offset in range(nb_words): + word = ptrace_peekuser(self.pid, offset * CPU_WORD_SIZE) + bytes = word2bytes(word) + words.append(bytes) + bytes = ''.join(words) + return bytes2type(bytes, ptrace_registers_t) + + def getreg(self, name): + try: + name, shift, mask = CPU_SUB_REGISTERS[name] + except KeyError: + shift = 0 + mask = None + if name not in REGISTER_NAMES: + raise ProcessError(self, "Unknown register: %r" % name) + regs = self.getregs() + value = getattr(regs, name) + value >>= shift + if mask: + value &= mask + return value + + def setregs(self, regs): + ptrace_setregs(self.pid, regs) + + def setreg(self, name, value): + regs = self.getregs() + if name in CPU_SUB_REGISTERS: + full_name, shift, mask = CPU_SUB_REGISTERS[name] + full_value = getattr(regs, full_name) + full_value &= ~mask + full_value |= ((value & mask) << shift) + value = full_value + name = full_name + if name not in REGISTER_NAMES: + raise ProcessError(self, "Unknown register: %r" % name) + setattr(regs, name, value) + self.setregs(regs) + + def singleStep(self): + if not HAS_PTRACE_SINGLESTEP: + self.notImplementedError() + ptrace_singlestep(self.pid) + + def filterSignal(self, signum): + if signum == SIGTRAP: + # Never transfer SIGTRAP signal + return 0 + else: + return signum + + def syscall(self, signum=0): + signum = self.filterSignal(signum) + ptrace_syscall(self.pid, signum) + self.is_stopped = False + + def setInstrPointer(self, ip): + if CPU_INSTR_POINTER: + self.setreg(CPU_INSTR_POINTER, ip) + else: + raise ProcessError( + self, "Instruction pointer register is not defined") + + def getInstrPointer(self): + if CPU_INSTR_POINTER: + return self.getreg(CPU_INSTR_POINTER) + else: + raise ProcessError( + self, "Instruction pointer register is not defined") + + def getStackPointer(self): + if CPU_STACK_POINTER: + return self.getreg(CPU_STACK_POINTER) + else: + raise ProcessError(self, "Stack pointer register is not defined") + + def getFramePointer(self): + if CPU_FRAME_POINTER: + return self.getreg(CPU_FRAME_POINTER) + else: + raise ProcessError(self, "Stack pointer register is not defined") + + def _readBytes(self, address, size): + offset = address % CPU_WORD_SIZE + if offset: + # Read word + address -= offset + word = self.readWord(address) + bytes = word2bytes(word) + + # Read some bytes from the word + subsize = min(CPU_WORD_SIZE - offset, size) + data = bytes[offset:offset + subsize] # <-- FIXME: Big endian! + + # Move cursor + size -= subsize + address += CPU_WORD_SIZE + else: + data = b'' + + while size: + # Read word + word = self.readWord(address) + bytes = word2bytes(word) + + # Read bytes from the word + if size < CPU_WORD_SIZE: + data += bytes[:size] # <-- FIXME: Big endian! + break + data += bytes + + # Move cursor + size -= CPU_WORD_SIZE + address += CPU_WORD_SIZE + return data + + def readWord(self, address): + """Address have to be aligned!""" + word = ptrace_peektext(self.pid, address) + return word + + if HAS_PTRACE_IO: + def readBytes(self, address, size): + buffer = create_string_buffer(size) + io_desc = ptrace_io_desc( + piod_op=PIOD_READ_D, + piod_offs=address, + piod_addr=addressof(buffer), + piod_len=size) + ptrace_io(self.pid, io_desc) + return buffer.raw + elif HAS_PROC: + def readBytes(self, address, size): + if not self.read_mem_file: + filename = '/proc/%u/mem' % self.pid + try: + self.read_mem_file = open(filename, 'rb', 0) + except IOError as err: + message = "Unable to open %s: fallback to ptrace implementation" % filename + if err.errno != EACCES: + error(message) + else: + info(message) + self.readBytes = self._readBytes + return self.readBytes(address, size) + + try: + mem = self.read_mem_file + mem.seek(address) + data = mem.read(size) + except (IOError, ValueError) as err: + raise ProcessError(self, "readBytes(%s, %s) error: %s" % ( + formatAddress(address), size, err)) + if len(data) == 0 and size: + # Issue #10: If the process was not created by the debugger + # (ex: fork), the kernel may deny reading private mappings of + # /proc/pid/mem to the debugger, depending on the kernel + # version and kernel config (ex: SELinux enabled or not). + # + # Fallback to PTRACE_PEEKTEXT. It is slower but a debugger + # tracing the process is always allowed to use it. + self.readBytes = self._readBytes + return self.readBytes(address, size) + return data + else: + readBytes = _readBytes + + def getsiginfo(self): + if not HAS_PTRACE_SIGINFO: + self.notImplementedError() + return ptrace_getsiginfo(self.pid) + + def writeBytes(self, address, bytes): + if HAS_PTRACE_IO: + size = len(bytes) + bytes = create_string_buffer(bytes) + io_desc = ptrace_io_desc( + piod_op=PIOD_WRITE_D, + piod_offs=address, + piod_addr=addressof(bytes), + piod_len=size) + ptrace_io(self.pid, io_desc) + else: + offset = address % CPU_WORD_SIZE + if offset: + # Write partial word (end) + address -= offset + size = CPU_WORD_SIZE - offset + word = self.readBytes(address, CPU_WORD_SIZE) + if len(bytes) < size: + size = len(bytes) + word = word[:offset] + bytes[:size] + \ + word[offset + size:] # <-- FIXME: Big endian! + else: + # <-- FIXME: Big endian! + word = word[:offset] + bytes[:size] + self.writeWord(address, bytes2word(word)) + bytes = bytes[size:] + address += CPU_WORD_SIZE + + # Write full words + while CPU_WORD_SIZE <= len(bytes): + # Read one word + word = bytes[:CPU_WORD_SIZE] + word = bytes2word(word) + self.writeWord(address, word) + + # Move to next word + bytes = bytes[CPU_WORD_SIZE:] + address += CPU_WORD_SIZE + if not bytes: + return + + # Write partial word (begin) + size = len(bytes) + word = self.readBytes(address, CPU_WORD_SIZE) + # FIXME: Write big endian version of the next line + word = bytes + word[size:] + self.writeWord(address, bytes2word(word)) + + def readStruct(self, address, struct): + bytes = self.readBytes(address, sizeof(struct)) + bytes = c_char_p(bytes) + return bytes2type(bytes, struct) + + def readArray(self, address, basetype, count): + bytes = self.readBytes(address, sizeof(basetype) * count) + bytes = c_char_p(bytes) + return bytes2array(bytes, basetype, count) + + def readCString(self, address, max_size, chunk_length=256): + string = [] + size = 0 + truncated = False + while True: + done = False + data = self.readBytes(address, chunk_length) + pos = data.find(b'\0') + if pos != -1: + done = True + data = data[:pos] + if max_size <= size + chunk_length: + data = data[:(max_size - size)] + string.append(data) + truncated = True + break + string.append(data) + if done: + break + size += chunk_length + address += chunk_length + return b''.join(string), truncated + + def dumpStack(self, log=None): + if not log: + log = error + stack = self.findStack() + if stack: + log("STACK: %s" % stack) + self._dumpStack(log) + + def _dumpStack(self, log): + sp = self.getStackPointer() + displayed = 0 + for index in range(-5, 5 + 1): + delta = index * CPU_WORD_SIZE + try: + value = self.readWord(sp + delta) + log("STACK%+ 3i: %s" % (delta, formatWordHex(value))) + displayed += 1 + except PtraceError: + pass + if not displayed: + log("ERROR: unable to read the stack (SP=%s)" % formatAddress(sp)) + + def readMappings(self): + return readProcessMappings(self) + + def dumpMaps(self, log=None): + if not log: + log = error + for map in self.readMappings(): + log("MAPS: %s" % map) + + def writeWord(self, address, word): + """ + Address have to be aligned! + """ + ptrace_poketext(self.pid, address, word) + + def dumpRegs(self, log=None): + if not log: + log = error + try: + regs = self.getregs() + dumpRegs(log, regs) + except PtraceError as err: + log("Unable to read registers: %s" % err) + + def cont(self, signum=0): + signum = self.filterSignal(signum) + ptrace_cont(self.pid, signum) + self.is_stopped = False + + def setoptions(self, options): + if not HAS_PTRACE_EVENTS: + self.notImplementedError() + info("Set %s options to %s" % (self, options)) + ptrace_setoptions(self.pid, options) + + def waitEvent(self): + return self.debugger.waitProcessEvent(pid=self.pid) + + def waitSignals(self, *signals): + return self.debugger.waitSignals(*signals, pid=self.pid) + + def waitSyscall(self): + self.debugger.waitSyscall(self) + + def findBreakpoint(self, address): + for bp in self.breakpoints.values(): + if bp.address <= address < bp.address + bp.size: + return bp + return None + + def createBreakpoint(self, address, size=1): + bp = self.findBreakpoint(address) + if bp: + raise ProcessError(self, "A breakpoint is already set: %s" % bp) + bp = Breakpoint(self, address, size) + self.breakpoints[address] = bp + return bp + + def getBacktrace(self, max_args=6, max_depth=20): + return getBacktrace(self, max_args=max_args, max_depth=max_depth) + + def removeBreakpoint(self, breakpoint): + del self.breakpoints[breakpoint.address] + + def __del__(self): + try: + self.detach() + except PtraceError: + pass + + def __str__(self): + return self.__repr__() + + def __repr__(self): + return "" % self.pid + + def __hash__(self): + return hash(self.pid) + + def notImplementedError(self): + raise NotImplementedError() diff --git a/ptrace/debugger/process_error.py b/ptrace/debugger/process_error.py new file mode 100644 index 0000000..b912aa7 --- /dev/null +++ b/ptrace/debugger/process_error.py @@ -0,0 +1,8 @@ +from ptrace.error import PtraceError + + +class ProcessError(PtraceError): + + def __init__(self, process, message): + PtraceError.__init__(self, message, pid=process.pid) + self.process = process diff --git a/ptrace/debugger/process_event.py b/ptrace/debugger/process_event.py new file mode 100644 index 0000000..ab04c79 --- /dev/null +++ b/ptrace/debugger/process_event.py @@ -0,0 +1,61 @@ +from ptrace.signames import signalName + + +class ProcessEvent(Exception): + """ + A process event: program exit, program killed by a signal, program + received a signal, etc. + + The attribute "process" contains the related process. + """ + + def __init__(self, process, message): + Exception.__init__(self, message) + self.process = process + + +class ProcessExit(ProcessEvent): + """ + Process exit event: + - process kill by a signal (if signum attribute is not None) + - process exited with a code (if exitcode attribute is not None) + - process terminated abnormally (otherwise) + """ + + def __init__(self, process, signum=None, exitcode=None): + pid = process.pid + if signum: + message = "Process %s killed by signal %s" % ( + pid, signalName(signum)) + elif exitcode is not None: + if not exitcode: + message = "Process %s exited normally" % pid + else: + message = "Process %s exited with code %s" % (pid, exitcode) + else: + message = "Process %s terminated abnormally" % pid + ProcessEvent.__init__(self, process, message) + self.signum = signum + self.exitcode = exitcode + + +class ProcessExecution(ProcessEvent): + """ + Process execution: event send just after the process calls the exec() + syscall if exec() tracing option is enabled. + """ + + def __init__(self, process): + ProcessEvent.__init__( + self, process, "Process %s execution" % process.pid) + + +class NewProcessEvent(ProcessEvent): + """ + New process: event send when a process calls the fork() syscall if fork() + tracing option is enabled. The attribute process contains the new child + process. + """ + + def __init__(self, process): + ProcessEvent.__init__(self, process, "New process %s" % process.pid) diff --git a/ptrace/debugger/ptrace_signal.py b/ptrace/debugger/ptrace_signal.py new file mode 100644 index 0000000..d928e86 --- /dev/null +++ b/ptrace/debugger/ptrace_signal.py @@ -0,0 +1,231 @@ +from logging import error +from ptrace.disasm import HAS_DISASSEMBLER +from signal import SIGFPE, SIGSEGV, SIGABRT +try: + from signal import SIGCHLD +except ImportError: + SIGCHLD = None +try: + from signal import SIGBUS +except ImportError: + SIGBUS = None +from ptrace.os_tools import RUNNING_LINUX +from ptrace.cpu_info import CPU_64BITS +from ptrace.debugger import ProcessEvent +from ptrace.error import PtraceError +from ptrace import signalName +from ptrace.debugger.signal_reason import ( + DivisionByZero, Abort, StackOverflow, + InvalidMemoryAccess, InvalidRead, InvalidWrite, + InstructionError, ChildExit) +from ptrace.debugger.parse_expr import parseExpression +import re + +# Match a pointer dereference (e.g. "DWORD [EDX+0x8]") +DEREF_REGEX = r'(?P(BYTE|WORD|DWORD|DQWORD) )?\[(?P[^]]+)\]' + +NAMED_WORD_SIZE = { + 'BYTE': 1, + 'WORD': 2, + 'DWORD': 4, + 'DQWORD': 8, +} + +# Match any Intel instruction (e.g. "ADD") +INSTR_REGEX = '(?:[A-Z]{3,10})' + + +def findDerefSize(match): + name = match.group("deref_size") + try: + return NAMED_WORD_SIZE[name] + except KeyError: + return None + + +def evalFaultAddress(process, match): + expr = match.group('deref') + if not expr: + return None + try: + return parseExpression(process, expr) + except ValueError as err: + error("err: %s" % err) + return None + + +class ProcessSignal(ProcessEvent): + + def __init__(self, signum, process): + # Initialize attributes + self.name = signalName(signum) + ProcessEvent.__init__(self, process, "Signal %s" % self.name) + self.signum = signum + self.reason = None + + def _analyze(self): + if self.signum in (SIGSEGV, SIGBUS): + self.memoryFault() + elif self.signum == SIGFPE: + self.mathError() + elif self.signum == SIGCHLD: + self.childExit() + elif self.signum == SIGABRT: + self.reason = Abort() + return self.reason + + def getInstruction(self): + if not HAS_DISASSEMBLER: + return None + try: + return self.process.disassembleOne() + except PtraceError: + return None + + def memoryFaultInstr(self, instr, fault_address): + asm = instr.text + + # Invalid write (e.g. "MOV [...], value") + match = re.search(r"^(?:MOV|TEST)[A-Z]* %s," % DEREF_REGEX, asm) + if match: + if fault_address is None: + fault_address = evalFaultAddress(self.process, match) + self.reason = InvalidWrite(fault_address, size=findDerefSize(match), + instr=instr, process=self.process) + return + + # Invalid read (e.g. "CMP BYTE [EAX+EDX-0x1], 0x0") + match = re.search(r"^%s %s," % (INSTR_REGEX, DEREF_REGEX), asm) + if match: + if fault_address is None: + fault_address = evalFaultAddress(self.process, match) + self.reason = InvalidRead(fault_address, size=findDerefSize(match), + instr=instr, process=self.process) + return + + # Invalid read (e.g. "MOV reg, [...]") + match = re.match(r"%s [^,]+, %s" % (INSTR_REGEX, DEREF_REGEX), asm) + if match: + if fault_address is None: + fault_address = evalFaultAddress(self.process, match) + self.reason = InvalidRead(fault_address, size=findDerefSize(match), + instr=instr, process=self.process) + return + + # MOVS* and SCAS* instructions (e.g. "MOVSB" or "REP SCASD") + match = re.search( + r"^(?:REP(?:NZ)? )?(?PMOVS|SCAS)(?P[BWD])?", asm) + if match: + self.reason = self.movsInstr(fault_address, instr, match) + return + + def movsInstr(self, fault_address, instr, match): + operator = match.group("operator") + suffix = match.group("suffix") + size = {'B': 1, 'W': 2, 'D': 4}.get(suffix) + error_cls = InvalidMemoryAccess + try: + process = self.process + if CPU_64BITS: + source_reg = 'rsi' + dest_reg = 'rdi' + else: + source_reg = 'esi' + dest_reg = 'edi' + source_addr = process.getreg(source_reg) + registers = {source_reg: source_addr} + write = (operator == 'MOVS') + if write: + dest_addr = process.getreg(dest_reg) + registers[dest_reg] = dest_addr + + if fault_address is not None: + if fault_address == source_addr: + error_cls = InvalidRead + if write and fault_address == dest_addr: + error_cls = InvalidWrite + else: + if write: + fault_address = (source_addr, dest_addr) + else: + fault_address = (source_addr,) + except PtraceError: + registers = {} + return error_cls(fault_address, size=size, instr=instr, + registers=registers, process=self.process) + + def getSignalInfo(self): + if RUNNING_LINUX: + return self.process.getsiginfo() + else: + return None + + def memoryFault(self): + # Get fault + siginfo = self.getSignalInfo() + if siginfo: + fault_address = siginfo._sigfault._addr + if not fault_address: + fault_address = 0 + else: + fault_address = None + + # Get current instruction + instr = self.getInstruction() + + # Call to invalid address? + if fault_address is not None: + try: + ip = self.process.getInstrPointer() + if ip == fault_address: + self.reason = InstructionError(ip, process=self.process) + return + except PtraceError: + pass + + # Stack overflow? + stack = self.process.findStack() + if stack: + sp = self.process.getStackPointer() + if not (stack.start <= sp <= stack.end): + self.reason = StackOverflow( + sp, stack, instr=instr, process=self.process) + return + + # Guess error type using the assembler instruction + if instr: + self.memoryFaultInstr(instr, fault_address) + if self.reason: + return + + # Last chance: use generic invalid memory access error + self.reason = InvalidMemoryAccess( + fault_address, instr=instr, process=self.process) + + def mathError(self): + instr = self.getInstruction() + if not instr: + return + match = re.match(r"I?DIV (.*)", instr.text) + if not match: + return + self.reason = DivisionByZero(instr=instr, process=self.process) + + def childExit(self): + siginfo = self.getSignalInfo() + if siginfo: + child = siginfo._sigchld + self.reason = ChildExit(child.pid, child.status, child.uid) + else: + self.reason = ChildExit() + + def display(self, log=None): + self._analyze() + if not log: + log = error + log("-" * 60) + log("PID: %s" % self.process.pid) + log("Signal: %s" % self.name) + if self.reason: + self.reason.display(log) + log("-" * 60) diff --git a/ptrace/debugger/signal_reason.py b/ptrace/debugger/signal_reason.py new file mode 100644 index 0000000..df2fb19 --- /dev/null +++ b/ptrace/debugger/signal_reason.py @@ -0,0 +1,184 @@ +from ptrace.ctypes_tools import formatAddress, formatWordHex +from ptrace.error import PtraceError +from ptrace.cpu_info import CPU_I386, CPU_X86_64 +from ptrace.process_tools import formatProcessStatus +import re + +# Find all Intel registers (in upper case) +if CPU_I386: + regex = 'E[A-Z]{2}|[CDEFGS]S|[ABCD]L' +elif CPU_X86_64: + regex = '[ER][A-Z]{2}|[CDEFGS]S|[ABCD]L' +else: + regex = None +if regex: + REGISTER_REGEX = re.compile(r'\b(?:%s)\b' % regex) +else: + REGISTER_REGEX = None + + +def extractRegisters(process, instr): + registers = {} + if not process or not instr or not REGISTER_REGEX: + return registers + asm = instr.text + asm = asm.upper() + # Skip the operator ("MOV CL, [EAX]" => "CL, [EAX]") + asm = asm.split(" ", 1)[1] + for match in REGISTER_REGEX.finditer(asm): + name = match.group(0) + name = name.lower() + try: + value = process.getreg(name) + registers[name] = value + except PtraceError: + pass + return registers + + +def findMappings(addresses, process, size): + mappings = [] + if addresses is None or not process: + return mappings + if not isinstance(addresses, (list, tuple)): + addresses = (addresses,) + if not size: + size = 0 + process_mappings = process.readMappings() + if not process_mappings: + return mappings + for address in addresses: + address_str = formatAddress(address) + if 1 < size: + address_str += "..%s" % formatAddress(address + size - 1) + found = False + for map in process_mappings: + if (map.start <= address < map.end) \ + or (map.start <= (address + size - 1) < map.end): + found = True + mappings.append("%s is part of %s" % (address_str, map)) + if not found: + mappings.append("%s is not mapped in memory" % address_str) + return mappings + + +class SignalInfo(Exception): + + def __init__(self, name, text, + address=None, size=None, instr=None, + process=None, registers=None): + Exception.__init__(self, text) + self.name = name + self.text = text + self.instr = instr + self.registers = extractRegisters(process, instr) + if registers: + self.registers.update(registers) + self.mappings = findMappings(address, process, size) + + def display(self, log): + log(self.text) + self.displayExtra(log) + if self.instr: + log("- instruction: %s" % self.instr) + for mapping in self.mappings: + log("- mapping: %s" % mapping) + for name, value in self.registers.items(): + log("- register %s=%s" % (name, formatWordHex(value))) + + def displayExtra(self, log): + pass + + +class DivisionByZero(SignalInfo): + + def __init__(self, instr=None, process=None): + SignalInfo.__init__(self, "div_by_zero", + "Division by zero", instr=instr, process=process) + + +class Abort(SignalInfo): + + def __init__(self): + SignalInfo.__init__(self, "abort", + "Program received signal SIGABRT, Aborted.") + + +class StackOverflow(SignalInfo): + + def __init__(self, stack_ptr, stack_map, instr=None, process=None): + text = "STACK OVERFLOW! Stack pointer is in %s" % stack_map + SignalInfo.__init__(self, "stack_overflow", text, + address=stack_ptr, registers={ + '': stack_ptr}, + instr=instr, process=process) + self.stack_ptr = stack_ptr + self.stack_map = stack_map + + +class InvalidMemoryAccess(SignalInfo): + NAME = "invalid_mem_access" + PREFIX = "Invalid memory access" + PREFIX_ADDR = "Invalid memory access to %s" + + def __init__(self, address=None, size=None, instr=None, registers=None, process=None): + """ + address is an integer or a list of integer + """ + if address is not None: + if isinstance(address, (list, tuple)): + arguments = " or ".join(formatAddress(addr) + for addr in address) + else: + arguments = formatAddress(address) + message = self.PREFIX_ADDR % arguments + else: + message = self.PREFIX + if size: + message += " (size=%s bytes)" % size + name = self.NAME + if address is not None: + name += "-" + formatAddress(address).lower() + SignalInfo.__init__(self, name, message, + address=address, size=size, instr=instr, + process=process, registers=registers) + + +class InvalidRead(InvalidMemoryAccess): + NAME = "invalid_read" + PREFIX = "Invalid read" + PREFIX_ADDR = "Invalid read from %s" + + +class InvalidWrite(InvalidMemoryAccess): + NAME = "invalid_write" + PREFIX = "Invalid write" + PREFIX_ADDR = "Invalid write to %s" + + +class InstructionError(SignalInfo): + + def __init__(self, address, process=None): + SignalInfo.__init__(self, "instr_error", + "UNABLE TO EXECUTE CODE AT %s (SEGMENTATION FAULT)" % formatAddress( + address), + address=address, + process=process, + registers={'': address}) + + +class ChildExit(SignalInfo): + + def __init__(self, pid=None, status=None, uid=None): + if pid is not None and status is not None: + message = formatProcessStatus(status, "Child process %s" % pid) + else: + message = "Child process exited" + SignalInfo.__init__(self, "child_exit", message) + self.pid = pid + self.status = status + self.uid = uid + + def displayExtra(self, log): + if self.uid is not None: + log("Signal sent by user %s" % self.uid) diff --git a/ptrace/debugger/syscall_state.py b/ptrace/debugger/syscall_state.py new file mode 100644 index 0000000..d96595f --- /dev/null +++ b/ptrace/debugger/syscall_state.py @@ -0,0 +1,48 @@ +from ptrace.syscall import PtraceSyscall +from signal import SIGTRAP + + +class SyscallState(object): + + def __init__(self, process): + self.process = process + self.ignore_exec_trap = True + self.ignore_callback = None + self.clear() + + def event(self, options): + if self.next_event == "exit": + return self.exit() + else: + return self.enter(options) + + def enter(self, options): + # syscall enter + regs = self.process.getregs() + self.syscall = PtraceSyscall(self.process, options, regs) + self.name = self.syscall.name + if (not self.ignore_callback) \ + or (not self.ignore_callback(self.syscall)): + self.syscall.enter(regs) + else: + self.syscall = None + self.next_event = "exit" + return self.syscall + + def exit(self): + if self.syscall: + self.syscall.exit() + if self.ignore_exec_trap \ + and self.name == "execve" \ + and not self.process.debugger.trace_exec: + # Ignore the SIGTRAP after exec() syscall exit + self.process.syscall() + self.process.waitSignals(SIGTRAP, SIGTRAP | 0x80) + syscall = self.syscall + self.clear() + return syscall + + def clear(self): + self.syscall = None + self.name = None + self.next_event = "enter" diff --git a/ptrace/disasm.py b/ptrace/disasm.py new file mode 100644 index 0000000..cfa5d4f --- /dev/null +++ b/ptrace/disasm.py @@ -0,0 +1,76 @@ +""" +Disassembler: only enabled if HAS_DISASSEMBLER is True. +""" + +try: + from ptrace.cpu_info import CPU_I386, CPU_X86_64 + try: + from distorm3 import Decode + if CPU_X86_64: + from distorm3 import Decode64Bits as DecodeBits + MAX_INSTR_SIZE = 11 + elif CPU_I386: + from distorm3 import Decode32Bits as DecodeBits + MAX_INSTR_SIZE = 8 + else: + raise ImportError("CPU not supported") + DISTORM3 = True + except ImportError: + DISTORM3 = False + from ptrace.pydistorm import Decode + if CPU_X86_64: + from ptrace.pydistorm import Decode64Bits as DecodeBits + MAX_INSTR_SIZE = 11 + elif CPU_I386: + from ptrace.pydistorm import Decode32Bits as DecodeBits + MAX_INSTR_SIZE = 8 + else: + raise ImportError("CPU not supported") + from ptrace import PtraceError + + class Instruction(object): + """ + A CPU instruction. + + Attributes: + - address (int): address of the instruction + - size (int): size of the instruction in bytes + - mnemonic (str): name of the instruction + - operands (str): string describing the operands + - hexa (str): bytes of the instruction as an hexadecimal string + - text (str): string representing the whole instruction + """ + + def __init__(self, instr): + if DISTORM3: + self.address, self.size, self.text, self.hexa = instr + else: + self.address = instr.offset + self.size = instr.size + self.hexa = str(instr.instructionHex) + self.text = "%s %s" % (instr.mnemonic, instr.operands) + + def __str__(self): + return self.text + + def disassemble(code, address=0x100): + """ + Disassemble the specified byte string, where address is the + address of the first instruction. + """ + for instr in Decode(address, code, DecodeBits): + yield Instruction(instr) + + def disassembleOne(code, address=0x100): + """ + Disassemble the first instruction of the byte string, where + address is the address of the instruction. + """ + for instr in disassemble(code, address): + return instr + raise PtraceError("Unable to disassemble %r" % code) + + HAS_DISASSEMBLER = True +except (ImportError, OSError): + # OSError if libdistorm64.so doesn't exist + HAS_DISASSEMBLER = False diff --git a/ptrace/error.py b/ptrace/error.py new file mode 100644 index 0000000..b4407f6 --- /dev/null +++ b/ptrace/error.py @@ -0,0 +1,63 @@ +from sys import exc_info +from traceback import format_exception +from logging import ERROR, getLogger +from ptrace.logging_tools import getLogFunc, changeLogLevel + +PTRACE_ERRORS = Exception + + +def writeBacktrace(logger, log_level=ERROR): + """ + Write a backtrace into the logger with the specified log level. + """ + log_func = getLogFunc(logger, log_level) + try: + info = exc_info() + trace = format_exception(*info) + if trace[0] != "None\n": + trace = ''.join(trace).rstrip() + for line in trace.split("\n"): + log_func(line.rstrip()) + return + except Exception: + pass + log_func("Unable to get backtrace") + + +def formatError(error): + """ + Format an error as a string. Write the error type as prefix. + Eg. "[ValueError] invalid value". + """ + return "[%s] %s" % (error.__class__.__name__, error) + + +def writeError(logger, error, title="ERROR", log_level=ERROR): + """ + Write an error into the logger: + - logger: the logger (if None, use getLogger()) + - error: the exception object + - title: error message prefix (e.g. title="Initialization error") + - log_level: log level of the error + + If the exception is a SystemExit or a KeyboardInterrupt, re-emit + (raise) the exception and don't write it. + """ + if not logger: + logger = getLogger() + if error.__class__ in (SystemExit, KeyboardInterrupt): + raise error + log_func = getLogFunc(logger, log_level) + log_func("%s: %s" % (title, formatError(error))) + writeBacktrace(logger, log_level=changeLogLevel(log_level, -1)) + + +class PtraceError(Exception): + """ + Ptrace error: have the optional attributes errno and pid. + """ + + def __init__(self, message, errno=None, pid=None): + Exception.__init__(self, message) + self.errno = errno + self.pid = pid diff --git a/ptrace/func_arg.py b/ptrace/func_arg.py new file mode 100644 index 0000000..448600f --- /dev/null +++ b/ptrace/func_arg.py @@ -0,0 +1,105 @@ +from ptrace.error import PTRACE_ERRORS, writeError +from logging import getLogger +from ptrace.ctypes_tools import formatAddress + + +class FunctionArgument(object): + """ + Description of a function argument. Attributes: + - function: a Function object + - index (int): index of the argument (starting at zero) + - options: a FunctionCallOptions object + - value (int) + - type (str, optional) + - text (str): string describing the argument + + Don't use text attribute directly, use getText() to format the + argument instead. + """ + + def __init__(self, function, index, options, + value=None, type=None, name=None): + self.function = function + self.index = index + self.options = options + self.value = value + self.type = type + self.name = name + self.text = None + + def getText(self): + if not self.text: + try: + text = self.createText() + if text is not None: + self.text = str(text) + elif self.type and self.type.endswith("*"): + self.text = formatAddress(self.value) + else: + self.text = repr(self.value) + except PTRACE_ERRORS as err: + writeError(getLogger(), err, + "Format argument %s of function %s() value error" + % (self.name, self.function.name)) + self.text = repr(self.value) + return self.text + + def format(self): + text = self.getText() + options = self.options + if options.write_argname and self.name: + if options.write_types and self.type: + return "%s %s=%s" % (self.type, self.name, text) + else: + return "%s=%s" % (self.name, text) + elif options.write_types and self.type: + return "(%s)%s" % (self.type, text) + else: + return text + + def createText(self): + return repr(self.value) + + def formatPointer(self, value, address): + if self.options.write_address: + return "%s at %s" % (value, formatAddress(address)) + else: + return value + + def readStruct(self, address, struct): + address = self.value + + struct_name = struct.__name__ + data = self.function.process.readStruct(address, struct) + arguments = [] + for name, argtype in struct._fields_: + value = getattr(data, name) + try: + text = self.formatStructValue(struct_name, name, value) + if text is not None: + text = str(text) + else: + text = repr(value) + except PTRACE_ERRORS as err: + writeError(getLogger(), err, "Format struct value error") + text = repr(value) + arguments.append("%s=%s" % (name, text)) + + data = "<%s %s>" % (struct_name, ", ".join(arguments)) + return self.formatPointer(data, address) + + def formatStructValue(self, struct, name, value): + return None + + def readArray(self, address, basetype, count): + array = self.function.process.readArray(address, basetype, count) + arguments = [] + for index in range(count): + value = array[index] + value = str(value) + arguments.append(value) + arguments = ", ".join(arguments) + return self.formatPointer("<(%s)>" % arguments, address) + + def __repr__(self): + return "argument %s of %s()" % (self.name, self.function.name) diff --git a/ptrace/func_call.py b/ptrace/func_call.py new file mode 100644 index 0000000..188e542 --- /dev/null +++ b/ptrace/func_call.py @@ -0,0 +1,77 @@ +from ptrace.func_arg import FunctionArgument + + +class FunctionCallOptions(object): + """ + Options to format a function call and its arguments. + """ + + def __init__(self, + write_types=False, write_argname=False, + replace_socketcall=True, string_max_length=300, + write_address=False, max_array_count=20): + self.write_address = write_address + self.write_types = write_types + self.write_argname = write_argname + self.replace_socketcall = replace_socketcall + self.string_max_length = string_max_length + self.max_array_count = max_array_count + self.instr_pointer = False + + +class FunctionCall(object): + """ + A function call. Attributes: + - name (str): function name + - arguments: list of FunctionArgument objects + - restype (str, optional): result type + - resvalue (optional): result value + - argument_class: class used to build the new arguments + + Methods: + - format(): create a string representation of the call + - addArgument(): add a new argument + - clearArguments(): remove all arguments + """ + + def __init__(self, name, options, argument_class=FunctionArgument): + self.name = name + self.options = options + self.arguments = [] + self.restype = None + self.resvalue = None + self.argument_class = argument_class + + def addArgument(self, value=None, name=None, type=None): + arg = self.argument_class( + self, len(self.arguments), self.options, value, type, name) + self.arguments.append(arg) + + def clearArguments(self): + self.arguments = [] + + def __getitem__(self, key): + if isinstance(key, str): + for arg in self.arguments: + if arg.name == key: + return arg + raise KeyError("%r has no argument called %r" % (self, key)) + else: + # Integer key + return self.arguments[key] + + def format(self): + arguments = [arg.format() for arg in self.arguments] + + # Remove empty optionnal arguments + while arguments and not arguments[-1]: + arguments.pop(-1) + + arguments = ", ".join(arguments) + if self.restype and self.options.write_types: + return "%s %s(%s)" % (self.restype, self.name, arguments) + else: + return "%s(%s)" % (self.name, arguments) + + def __repr__(self): + return "" % self.name diff --git a/ptrace/linux_proc.py b/ptrace/linux_proc.py new file mode 100644 index 0000000..84adc7c --- /dev/null +++ b/ptrace/linux_proc.py @@ -0,0 +1,245 @@ +""" +Functions and variables to access to Linux proc directory. + +Constant: + + - PAGE_SIZE: size of a memory page +""" +from os import readlink, listdir +from resource import getpagesize +from ptrace.tools import timestampUNIX +from datetime import timedelta + +PAGE_SIZE = getpagesize() + + +class ProcError(Exception): + """ + Linux proc directory error. + """ + pass + + +def openProc(path): + """ + Open a proc entry in read only mode. + """ + filename = "/proc/%s" % path + try: + return open(filename) + except IOError as err: + raise ProcError("Unable to open %r: %s" % (filename, err)) + + +def readProc(path): + """ + Read the content of a proc entry. + Eg. readProc("stat") to read /proc/stat. + """ + with openProc(path) as procfile: + return procfile.read() + + +def readProcessProc(pid, key): + """ + Read the content of a process entry in the proc directory. + Eg. readProcessProc(pid, "status") to read /proc/pid/status. + """ + try: + filename = "/proc/%s/%s" % (pid, key) + with open(filename) as proc: + return proc.read() + except IOError as err: + raise ProcError("Process %s doesn't exist: %s" % (pid, err)) + + +class ProcessState(object): + """ + Processus state. Attributes: + - state (str): process status ('R', 'S', 'T', ...) + - program (str): program name + - pid (int): process identifier + - ppid (int): parent process identifier + - pgrp (int): process group + - session (int): session identifier + - tty_nr (int): tty number + - tpgid (int) + - utime (int): user space time (jiffies) + - stime (int): kernel space time (jiffies) + - starttime (int): start time + """ + STATE_NAMES = { + "R": "running", + "S": "sleeping", + "D": "disk", + "Z": "zombie", + "T": "traced", + "W": "paging", + } + + def __init__(self, stat): + # pid (program) ... => "pid (program", "..." + part, stat = stat.rsplit(')', 1) + self.pid, self.program = part.split('(', 1) + self.pid = int(self.pid) + + # "state ..." => state, "..." + stat = stat.split() + self.state = stat[0] + stat = [int(item) for item in stat[1:]] + + # Read next numbers + self.ppid = stat[0] + self.pgrp = stat[1] + self.session = stat[2] + self.tty_nr = stat[3] + self.tpgid = stat[4] + self.utime = stat[10] + self.stime = stat[11] + self.starttime = stat[18] + + +def readProcessStat(pid): + """ + Read the process state ('stat') as a ProcessState object. + """ + stat = readProcessProc(pid, 'stat') + return ProcessState(stat) + + +def readProcessStatm(pid): + """ + Read the process memory status ('statm') as a list of integers. + Values are in bytes (and not in pages). + """ + statm = readProcessProc(pid, 'statm') + statm = [int(item) * PAGE_SIZE for item in statm.split()] + return statm + + +def readProcessProcList(pid, key): + """ + Read a process entry as a list of strings. + """ + data = readProcessProc(pid, key) + if not data: + # Empty file: empty list + return [] + data = data.split("\0") + if not data[-1]: + del data[-1] + return data + + +def readProcessLink(pid, key): + """ + Read a process link. + """ + try: + filename = "/proc/%s/%s" % (pid, key) + return readlink(filename) + except OSError as err: + raise ProcError("Unable to read proc link %r: %s" % (filename, err)) + + +def readProcesses(): + """ + Read all processes identifiers. The function is a generator, + use it with: :: + + for pid in readProcesses(): ... + """ + for filename in listdir('/proc'): + try: + yield int(filename) + except ValueError: + # Filename is not an integer (e.g. "stat" from /proc/stat) + continue + + +def readProcessCmdline(pid, escape_stat=True): + """ + Read the process command line. If escape_stat is True, format program name + with "[%s]" if the process has no command line, e.g. "[khelper]". + """ + # Try /proc/42/cmdline + try: + cmdline = readProcessProcList(pid, 'cmdline') + if cmdline: + return cmdline + except ProcError: + pass + + # Try /proc/42/stat + try: + stat = readProcessStat(pid) + program = stat.program + if escape_stat: + program = "[%s]" % program + return [program] + except ProcError: + return None + + +def searchProcessesByName(process_name): + """ + Find all processes matching the program name pattern. + Eg. pattern "ssh" will find the program "/usr/bin/ssh". + + This function is a generator yielding the process identifier, + use it with: :: + + for pid in searchProcessByName(pattern): + ... + """ + suffix = '/' + process_name + for pid in readProcesses(): + cmdline = readProcessCmdline(pid) + if not cmdline: + continue + program = cmdline[0] + if program == process_name or program.endswith(suffix): + yield pid + + +def searchProcessByName(process_name): + """ + Function similar to searchProcessesByName() but only return the identifier + of the first matching process. Raise a ProcError if there is no matching + process. + """ + for pid in searchProcessesByName(process_name): + return pid + raise ProcError("Unable to find process: %r" % process_name) + + +def getUptime(): + """ + Get the system uptime as a datetime.timedelta object. + """ + uptime = readProc('uptime') + uptime = uptime.strip().split() + uptime = float(uptime[0]) + return timedelta(seconds=uptime) + + +def getSystemBoot(): + """ + Get the system boot date as a datetime.datetime object. + """ + if getSystemBoot.value is None: + stat_file = openProc('stat') + for line in stat_file: + if not line.startswith("btime "): + continue + seconds = int(line[6:]) + btime = timestampUNIX(seconds, True) + getSystemBoot.value = btime + break + stat_file.close() + if getSystemBoot.value is None: + raise ProcError("Unable to read system boot time!") + return getSystemBoot.value + + +getSystemBoot.value = None diff --git a/ptrace/logging_tools.py b/ptrace/logging_tools.py new file mode 100644 index 0000000..6fbd80b --- /dev/null +++ b/ptrace/logging_tools.py @@ -0,0 +1,30 @@ +from ptrace.tools import minmax +from logging import ERROR, WARNING, INFO, DEBUG + + +def getLogFunc(logger, level): + """ + Get the logger function for the specified logging level. + """ + if level == ERROR: + return logger.error + elif level == WARNING: + return logger.warning + elif level == INFO: + return logger.info + elif level == DEBUG: + return logger.debug + else: + return logger.error + + +def changeLogLevel(level, delta): + """ + Compute log level and make sure that the result is in DEBUG..ERROR. + + >>> changeLogLevel(ERROR, -1) == WARNING + True + >>> changeLogLevel(DEBUG, 1) == INFO + True + """ + return minmax(DEBUG, level + delta * 10, ERROR) diff --git a/ptrace/mockup.py b/ptrace/mockup.py new file mode 100644 index 0000000..5800f89 --- /dev/null +++ b/ptrace/mockup.py @@ -0,0 +1,15 @@ +""" +Mockup classes used in unit tests. +""" + + +class FakeProcess(object): + + def __init__(self): + self.regs = {} + + def setreg(self, name, value): + self.regs[name] = value + + def getreg(self, name): + return self.regs[name] diff --git a/ptrace/os_tools.py b/ptrace/os_tools.py new file mode 100644 index 0000000..289c9d8 --- /dev/null +++ b/ptrace/os_tools.py @@ -0,0 +1,26 @@ +""" +Constants about the operating system: + + - RUNNING_PYPY (bool) + - RUNNING_LINUX (bool) + - RUNNING_FREEBSD (bool) + - RUNNING_OPENBSD (bool) + - RUNNING_MACOSX (bool) + - RUNNING_BSD (bool) + - HAS_PROC (bool) + - HAS_PTRACE (bool) +""" + +from sys import platform, version, version_info + +RUNNING_PYTHON3 = version_info[0] == 3 +RUNNING_PYPY = ("pypy" in version.lower()) +RUNNING_LINUX = platform.startswith('linux') +RUNNING_FREEBSD = (platform.startswith('freebsd') + or platform.startswith('gnukfreebsd')) +RUNNING_OPENBSD = platform.startswith('openbsd') +RUNNING_MACOSX = (platform == 'darwin') +RUNNING_BSD = RUNNING_FREEBSD or RUNNING_MACOSX or RUNNING_OPENBSD + +HAS_PROC = RUNNING_LINUX +HAS_PTRACE = (RUNNING_BSD or RUNNING_LINUX) diff --git a/ptrace/process_tools.py b/ptrace/process_tools.py new file mode 100644 index 0000000..79cbabd --- /dev/null +++ b/ptrace/process_tools.py @@ -0,0 +1,124 @@ +from ptrace.os_tools import RUNNING_LINUX +if RUNNING_LINUX: + from ptrace.linux_proc import ( + ProcError, + openProc, + readProcessLink, + readProcessProcList, + readProcessStat, + ) +from ptrace.signames import signalName # noqa +from os import ( + WCOREDUMP, + WEXITSTATUS, + WIFSIGNALED, + WIFSTOPPED, + WSTOPSIG, + WTERMSIG, +) + + +def dumpProcessInfo(log, pid, max_length=None): + """ + Dump all information about a process: + - log: callback to write display one line + - pid: process identifier + - max_length (default: None): maximum number of environment variables + """ + if not RUNNING_LINUX: + log("Process ID: %s" % pid) + return + try: + stat = readProcessStat(pid) + except ProcError: + # Permission denied + stat = None + text = "Process ID: %s" % pid + if stat: + text += " (parent: %s)" % stat.ppid + log(text) + if stat: + state = stat.state + try: + state = "%s (%s)" % (state, stat.STATE_NAMES[state]) + except KeyError: + pass + log("Process state: %s" % state) + try: + log("Process command line: %r" % readProcessProcList(pid, 'cmdline')) + except ProcError: + # Permission denied + pass + try: + env = readProcessProcList(pid, 'environ') + if max_length: + # Truncate environment if it's too long + length = 0 + removed = 0 + index = 0 + while index < len(env): + var = env[index] + if max_length < length + len(var): + del env[index] + removed += 1 + else: + length += len(var) + index += 1 + env = ', '.join("%s=%r" % tuple(item.split("=", 1)) + for item in env) + if removed: + env += ', ... (skip %s vars)' % removed + log("Process environment: %s" % env) + except ProcError: + # Permission denied + pass + try: + log("Process working directory: %s" % readProcessLink(pid, 'cwd')) + except ProcError: + # Permission denied + pass + + try: + user = None + group = None + status_file = openProc("%s/status" % pid) + for line in status_file: + if line.startswith("Uid:"): + user = [int(id) for id in line[5:].split("\t")] + if line.startswith("Gid:"): + group = [int(id) for id in line[5:].split("\t")] + status_file.close() + if user: + text = "User identifier: %s" % user[0] + if user[0] != user[1]: + text += " (effective: %s)" % user[1] + log(text) + if group: + text = "Group identifier: %s" % group[0] + if group[0] != group[1]: + text += " (effective: %s)" % group[1] + log(text) + except ProcError: + # Permission denied + pass + + +def formatProcessStatus(status, title="Process"): + """ + Format a process status (integer) as a string. + """ + if WIFSTOPPED(status): + signum = WSTOPSIG(status) + text = "%s stopped by signal %s" % (title, signalName(signum)) + elif WIFSIGNALED(status): + signum = WTERMSIG(status) + text = "%s killed by signal %s" % (title, signalName(signum)) + else: + exitcode = WEXITSTATUS(status) + if exitcode: + text = "%s exited with code %s" % (title, exitcode) + else: + text = "%s exited normally" % title + if WCOREDUMP(status): + text += " (core dumped)" + return text diff --git a/ptrace/profiler.py b/ptrace/profiler.py new file mode 100644 index 0000000..cc40b00 --- /dev/null +++ b/ptrace/profiler.py @@ -0,0 +1,45 @@ +from profile import Profile +from os import unlink +from io import StringIO +import pstats + + +def calibrate(n): + """ + https://docs.python.org/3/library/profile.html#calibration + """ + if n > 0: + pr = Profile() + magics = [] + for i in range(n): + magics.append(pr.calibrate(10000)) + return sum(magics) / n + + +def runProfiler(logger, func, args=tuple(), kw={}, + verbose=True, nb_func=25, + sort_by=('time',), nb_cal=0): + """ + Run a function in a profiler and then display the functions sorted by time. + """ + profile_filename = "/tmp/profiler" + prof = Profile(bias=calibrate(nb_cal)) + try: + logger.warning("Run profiler") + result = prof.runcall(func, *args, **kw) + logger.error("Profiler: Process data...") + prof.dump_stats(profile_filename) + stat = pstats.Stats(prof) + stat.strip_dirs() + stat.sort_stats(*sort_by) + + logger.error("Profiler: Result:") + log = StringIO() + stat.stream = log + stat.print_stats(nb_func) + log.seek(0) + for line in log: + logger.error(line.rstrip()) + return result + finally: + unlink(profile_filename) diff --git a/ptrace/pydistorm.py b/ptrace/pydistorm.py new file mode 100644 index 0000000..877109b --- /dev/null +++ b/ptrace/pydistorm.py @@ -0,0 +1,127 @@ +""" +:[diStorm64 1.7.27}: +Copyright RageStorm (C) 2007, Gil Dabah + +diStorm is licensed under the BSD license. +http://ragestorm.net/distorm/ +--- +Python binding of diStorm64 library written by Victor Stinner +""" + +from ctypes import cdll, c_long, c_ulong, c_int, c_uint, c_char, POINTER, Structure, addressof, byref, c_void_p, create_string_buffer, sizeof, cast + +# Define (u)int32_t and (u)int64_t types +int32_t = c_int +uint32_t = c_uint +if sizeof(c_ulong) == 8: + int64_t = c_long + uint64_t = c_ulong +else: + from ctypes import c_longlong, c_ulonglong + assert sizeof(c_longlong) == 8 + assert sizeof(c_ulonglong) == 8 + int64_t = c_longlong + uint64_t = c_ulonglong + +SUPPORT_64BIT_OFFSET = True +if SUPPORT_64BIT_OFFSET: + _OffsetType = uint64_t +else: + _OffsetType = uint32_t + +LIB_FILENAME = 'libdistorm64.so' +distorm = cdll.LoadLibrary(LIB_FILENAME) +Decode16Bits = 0 +Decode32Bits = 1 +Decode64Bits = 2 +DECODERS = (Decode16Bits, Decode32Bits, Decode64Bits) + +internal_decode = distorm.internal_decode + +DECRES_NONE = 0 +DECRES_SUCCESS = 1 +DECRES_MEMORYERR = 2 +DECRES_INPUTERR = 3 + +MAX_INSTRUCTIONS = 100 +MAX_TEXT_SIZE = 60 + + +class _WString(Structure): + _fields_ = ( + ("pos", c_uint), + ("p", c_char * MAX_TEXT_SIZE), + ) + + def __str__(self): + # FIXME: Use pos? + return self.p + + +class _DecodedInst(Structure): + _fields_ = ( + ("mnemonic", _WString), + ("operands", _WString), + ("instructionHex", _WString), + ("size", c_uint), + ("offset", _OffsetType), + ) + + def __str__(self): + return "%s %s" % (self.mnemonic, self.operands) + + +internal_decode.argtypes = (_OffsetType, c_void_p, + c_int, c_int, c_void_p, c_uint, POINTER(c_uint)) + + +def Decode(codeOffset, code, dt=Decode32Bits): + """ + Errors: TypeError, IndexError, MemoryError, ValueError + """ + # Check arguments + if not isinstance(codeOffset, int): + raise TypeError("codeOffset have to be an integer") + if not isinstance(code, bytes): + raise TypeError("code have to be a bytes, not %s" + % (type(code).__name__,)) + if dt not in DECODERS: + raise IndexError( + "Decoding-type must be either Decode16Bits, Decode32Bits or Decode64Bits.") + + # Allocate memory for decoder + code_buffer = create_string_buffer(code) + decodedInstructionsCount = c_uint() + result = create_string_buffer(sizeof(_DecodedInst) * MAX_INSTRUCTIONS) + + # Prepare arguments + codeLen = len(code) + code = addressof(code_buffer) + while codeLen: + # Call internal decoder + res = internal_decode(codeOffset, code, codeLen, dt, result, + MAX_INSTRUCTIONS, byref(decodedInstructionsCount)) + + # Check for errors + if res == DECRES_INPUTERR: + raise ValueError("Invalid argument") + count = decodedInstructionsCount.value + if res == DECRES_MEMORYERR and not count: + raise MemoryError() + + # No more instruction + if not count: + break + + # Yield instruction and compute decoded size + size = 0 + instr_array = cast(result, POINTER(_DecodedInst)) + for index in range(count): + instr = instr_array[index] + size += instr.size + yield instr + + # Update parameters to move to next instructions + code += size + codeOffset += size + codeLen -= size diff --git a/ptrace/signames.py b/ptrace/signames.py new file mode 100644 index 0000000..f42c2b7 --- /dev/null +++ b/ptrace/signames.py @@ -0,0 +1,60 @@ +""" +Name of process signals. + +SIGNAMES contains a dictionary mapping a signal number to it's name. But you +should better use signalName() instead of SIGNAMES since it returns a string +even if the signal is unknown. +""" + +PREFERRED_NAMES = ("SIGABRT", "SIGHUP", "SIGCHLD", "SIGPOLL") + + +def getSignalNames(): + """ + Create signal names dictionary (e.g. 9 => 'SIGKILL') using dir(signal). + If multiple signal names have the same number, use the first matching name + in PREFERRED_NAME to select preferred name (e.g. SIGIOT=SIGABRT=17). + """ + import signal + allnames = {} + for name in dir(signal): + if not name.startswith("SIG"): + continue + signum = getattr(signal, name) + try: + allnames[signum].append(name) + except KeyError: + allnames[signum] = [name] + signames = {} + for signum, names in allnames.items(): + if not signum: + # Skip signal 0 + continue + name = None + for preferred in PREFERRED_NAMES: + if preferred in names: + name = preferred + break + if not name: + name = names[0] + signames[signum] = name + return signames + + +SIGNAMES = getSignalNames() + + +def signalName(signum): + """ + Get the name of a signal + + >>> from signal import SIGINT + >>> signalName(SIGINT) + 'SIGINT' + >>> signalName(404) + 'signal<404>' + """ + try: + return SIGNAMES[signum] + except KeyError: + return "signal<%s>" % signum diff --git a/ptrace/syscall/__init__.py b/ptrace/syscall/__init__.py new file mode 100644 index 0000000..ca5bc4f --- /dev/null +++ b/ptrace/syscall/__init__.py @@ -0,0 +1,4 @@ +from ptrace.syscall.names import SYSCALL_NAMES, SOCKET_SYSCALL_NAMES # noqa +from ptrace.syscall.prototypes import SYSCALL_PROTOTYPES, FILENAME_ARGUMENTS # noqa +from ptrace.syscall.syscall_argument import SyscallArgument # noqa +from ptrace.syscall.ptrace_syscall import PtraceSyscall, SYSCALL_REGISTER, RETURN_VALUE_REGISTER # noqa diff --git a/ptrace/syscall/freebsd_constants.py b/ptrace/syscall/freebsd_constants.py new file mode 100644 index 0000000..4d8c739 --- /dev/null +++ b/ptrace/syscall/freebsd_constants.py @@ -0,0 +1,24 @@ +from ptrace.syscall.posix_constants import SYSCALL_ARG_DICT + +RLIMIT_RESOURCE = { + 0: "RLIMIT_CPU", + 1: "RLIMIT_FSIZE", + 2: "RLIMIT_DATA", + 3: "RLIMIT_STACK", + 4: "RLIMIT_CORE", + 5: "RLIMIT_RSS", + 6: "RLIMIT_MEMLOCK", + 7: "RLIMIT_NPROC", + 8: "RLIMIT_NOFILE", + 9: "RLIMIT_SBSIZE", + 10: "RLIMIT_VMEM", +} + +SIGPROCMASK_HOW = {1: " SIG_BLOCK", 2: "SIG_UNBLOCK", 3: "SIG_SETMASK"} + +SYSCALL_ARG_DICT.update({ + "getrlimit": {"resource": RLIMIT_RESOURCE}, + "setrlimit": {"resource": RLIMIT_RESOURCE}, + "sigprocmask": {"how": SIGPROCMASK_HOW}, + "rt_sigprocmask": {"how": SIGPROCMASK_HOW}, +}) diff --git a/ptrace/syscall/freebsd_syscall.py b/ptrace/syscall/freebsd_syscall.py new file mode 100644 index 0000000..976b405 --- /dev/null +++ b/ptrace/syscall/freebsd_syscall.py @@ -0,0 +1,409 @@ +# FreeBSD kernel syscall list from FreeBSD 7.0RC1 for i386 +# +# List extracted from: +# /usr/include/sys/syscall.h +SYSCALL_NAMES = { + 0: "syscall", + 1: "exit", + 2: "fork", + 3: "read", + 4: "write", + 5: "open", + 6: "close", + 7: "wait4", + # 8: old creat + 9: "link", + 10: "unlink", + # 11: obsolete execv + 12: "chdir", + 13: "fchdir", + 14: "mknod", + 15: "chmod", + 16: "chown", + 17: "break", + 18: "freebsd4_getfsstat", + # 19: old lseek + 20: "getpid", + 21: "mount", + 22: "unmount", + 23: "setuid", + 24: "getuid", + 25: "geteuid", + 26: "ptrace", + 27: "recvmsg", + 28: "sendmsg", + 29: "recvfrom", + 30: "accept", + 31: "getpeername", + 32: "getsockname", + 33: "access", + 34: "chflags", + 35: "fchflags", + 36: "sync", + 37: "kill", + # 38: old stat + 39: "getppid", + # 40: old lstat + 41: "dup", + 42: "pipe", + 43: "getegid", + 44: "profil", + 45: "ktrace", + # 46: old sigaction + 47: "getgid", + # 48: old sigprocmask + 49: "getlogin", + 50: "setlogin", + 51: "acct", + # 52: old sigpending + 53: "sigaltstack", + 54: "ioctl", + 55: "reboot", + 56: "revoke", + 57: "symlink", + 58: "readlink", + 59: "execve", + 60: "umask", + 61: "chroot", + # 62: old fstat + # 63: old getkerninfo + # 64: old getpagesize + 65: "msync", + 66: "vfork", + # 67: obsolete vread + # 68: obsolete vwrite + 69: "sbrk", + 70: "sstk", + # 71: old mmap + 72: "vadvise", + 73: "munmap", + 74: "mprotect", + 75: "madvise", + # 76: obsolete vhangup + # 77: obsolete vlimit + 78: "mincore", + 79: "getgroups", + 80: "setgroups", + 81: "getpgrp", + 82: "setpgid", + 83: "setitimer", + # 84: old wait + 85: "swapon", + 86: "getitimer", + # 87: old gethostname + # 88: old sethostname + 89: "getdtablesize", + 90: "dup2", + 92: "fcntl", + 93: "select", + 95: "fsync", + 96: "setpriority", + 97: "socket", + 98: "connect", + # 99: old accept + 100: "getpriority", + # 101: old send + # 102: old recv + # 103: old sigreturn + 104: "bind", + 105: "setsockopt", + 106: "listen", + # 107: obsolete vtimes + # 108: old sigvec + # 109: old sigblock + # 110: old sigsetmask + # 111: old sigsuspend + # 112: old sigstack + # 113: old recvmsg + # 114: old sendmsg + # 115: obsolete vtrace + 116: "gettimeofday", + 117: "getrusage", + 118: "getsockopt", + 120: "readv", + 121: "writev", + 122: "settimeofday", + 123: "fchown", + 124: "fchmod", + # 125: old recvfrom + 126: "setreuid", + 127: "setregid", + 128: "rename", + # 129: old truncate + # 130: old ftruncate + 131: "flock", + 132: "mkfifo", + 133: "sendto", + 134: "shutdown", + 135: "socketpair", + 136: "mkdir", + 137: "rmdir", + 138: "utimes", + # 139: obsolete 4.2 sigreturn + 140: "adjtime", + # 141: old getpeername + # 142: old gethostid + # 143: old sethostid + # 144: old getrlimit + # 145: old setrlimit + # 146: old killpg + 147: "setsid", + 148: "quotactl", + # 149: old quota + # 150: old getsockname + 155: "nfssvc", + # 156: old getdirentries + 157: "freebsd4_statfs", + 158: "freebsd4_fstatfs", + 160: "lgetfh", + 161: "getfh", + 162: "getdomainname", + 163: "setdomainname", + 164: "uname", + 165: "sysarch", + 166: "rtprio", + 169: "semsys", + 170: "msgsys", + 171: "shmsys", + 173: "freebsd6_pread", + 174: "freebsd6_pwrite", + 176: "ntp_adjtime", + 181: "setgid", + 182: "setegid", + 183: "seteuid", + 188: "stat", + 189: "fstat", + 190: "lstat", + 191: "pathconf", + 192: "fpathconf", + 194: "getrlimit", + 195: "setrlimit", + 196: "getdirentries", + 197: "freebsd6_mmap", + 198: "__syscall", + 199: "freebsd6_lseek", + 200: "freebsd6_truncate", + 201: "freebsd6_ftruncate", + 202: "__sysctl", + 203: "mlock", + 204: "munlock", + 205: "undelete", + 206: "futimes", + 207: "getpgid", + 209: "poll", + 220: "__semctl", + 221: "semget", + 222: "semop", + 224: "msgctl", + 225: "msgget", + 226: "msgsnd", + 227: "msgrcv", + 228: "shmat", + 229: "shmctl", + 230: "shmdt", + 231: "shmget", + 232: "clock_gettime", + 233: "clock_settime", + 234: "clock_getres", + 235: "ktimer_create", + 236: "ktimer_delete", + 237: "ktimer_settime", + 238: "ktimer_gettime", + 239: "ktimer_getoverrun", + 240: "nanosleep", + 248: "ntp_gettime", + 250: "minherit", + 251: "rfork", + 252: "openbsd_poll", + 253: "issetugid", + 254: "lchown", + 255: "aio_read", + 256: "aio_write", + 257: "lio_listio", + 272: "getdents", + 274: "lchmod", + 275: "netbsd_lchown", + 276: "lutimes", + 277: "netbsd_msync", + 278: "nstat", + 279: "nfstat", + 280: "nlstat", + 289: "preadv", + 290: "pwritev", + 297: "freebsd4_fhstatfs", + 298: "fhopen", + 299: "fhstat", + 300: "modnext", + 301: "modstat", + 302: "modfnext", + 303: "modfind", + 304: "kldload", + 305: "kldunload", + 306: "kldfind", + 307: "kldnext", + 308: "kldstat", + 309: "kldfirstmod", + 310: "getsid", + 311: "setresuid", + 312: "setresgid", + # 313: obsolete signanosleep + 314: "aio_return", + 315: "aio_suspend", + 316: "aio_cancel", + 317: "aio_error", + 318: "oaio_read", + 319: "oaio_write", + 320: "olio_listio", + 321: "yield", + # 322: obsolete thr_sleep + # 323: obsolete thr_wakeup + 324: "mlockall", + 325: "munlockall", + 326: "__getcwd", + 327: "sched_setparam", + 328: "sched_getparam", + 329: "sched_setscheduler", + 330: "sched_getscheduler", + 331: "sched_yield", + 332: "sched_get_priority_max", + 333: "sched_get_priority_min", + 334: "sched_rr_get_interval", + 335: "utrace", + 336: "freebsd4_sendfile", + 337: "kldsym", + 338: "jail", + 340: "sigprocmask", + 341: "sigsuspend", + 342: "freebsd4_sigaction", + 343: "sigpending", + 344: "freebsd4_sigreturn", + 345: "sigtimedwait", + 346: "sigwaitinfo", + 347: "__acl_get_file", + 348: "__acl_set_file", + 349: "__acl_get_fd", + 350: "__acl_set_fd", + 351: "__acl_delete_file", + 352: "__acl_delete_fd", + 353: "__acl_aclcheck_file", + 354: "__acl_aclcheck_fd", + 355: "extattrctl", + 356: "extattr_set_file", + 357: "extattr_get_file", + 358: "extattr_delete_file", + 359: "aio_waitcomplete", + 360: "getresuid", + 361: "getresgid", + 362: "kqueue", + 363: "kevent", + 371: "extattr_set_fd", + 372: "extattr_get_fd", + 373: "extattr_delete_fd", + 374: "__setugid", + 375: "nfsclnt", + 376: "eaccess", + 378: "nmount", + 379: "kse_exit", + 380: "kse_wakeup", + 381: "kse_create", + 382: "kse_thr_interrupt", + 383: "kse_release", + 384: "__mac_get_proc", + 385: "__mac_set_proc", + 386: "__mac_get_fd", + 387: "__mac_get_file", + 388: "__mac_set_fd", + 389: "__mac_set_file", + 390: "kenv", + 391: "lchflags", + 392: "uuidgen", + 393: "sendfile", + 394: "mac_syscall", + 395: "getfsstat", + 396: "statfs", + 397: "fstatfs", + 398: "fhstatfs", + 400: "ksem_close", + 401: "ksem_post", + 402: "ksem_wait", + 403: "ksem_trywait", + 404: "ksem_init", + 405: "ksem_open", + 406: "ksem_unlink", + 407: "ksem_getvalue", + 408: "ksem_destroy", + 409: "__mac_get_pid", + 410: "__mac_get_link", + 411: "__mac_set_link", + 412: "extattr_set_link", + 413: "extattr_get_link", + 414: "extattr_delete_link", + 415: "__mac_execve", + 416: "sigaction", + 417: "sigreturn", + 421: "getcontext", + 422: "setcontext", + 423: "swapcontext", + 424: "swapoff", + 425: "__acl_get_link", + 426: "__acl_set_link", + 427: "__acl_delete_link", + 428: "__acl_aclcheck_link", + 429: "sigwait", + 430: "thr_create", + 431: "thr_exit", + 432: "thr_self", + 433: "thr_kill", + 434: "_umtx_lock", + 435: "_umtx_unlock", + 436: "jail_attach", + 437: "extattr_list_fd", + 438: "extattr_list_file", + 439: "extattr_list_link", + 440: "kse_switchin", + 441: "ksem_timedwait", + 442: "thr_suspend", + 443: "thr_wake", + 444: "kldunloadf", + 445: "audit", + 446: "auditon", + 447: "getauid", + 448: "setauid", + 449: "getaudit", + 450: "setaudit", + 451: "getaudit_addr", + 452: "setaudit_addr", + 453: "auditctl", + 454: "_umtx_op", + 455: "thr_new", + 456: "sigqueue", + 457: "kmq_open", + 458: "kmq_setattr", + 459: "kmq_timedreceive", + 460: "kmq_timedsend", + 461: "kmq_notify", + 462: "kmq_unlink", + 463: "abort2", + 464: "thr_set_name", + 465: "aio_fsync", + 466: "rtprio_thread", + 471: "sctp_peeloff", + 472: "sctp_generic_sendmsg", + 473: "sctp_generic_sendmsg_iov", + 474: "sctp_generic_recvmsg", + 475: "pread", + 476: "pwrite", + 477: "mmap", + 478: "lseek", + 479: "truncate", + 480: "ftruncate", + 481: "thr_kill2", +} + +SOCKET_SYSCALL_NAMES = set(( + "socket", "socketpair", "connect", + "sendto", "recvfrom", "sendmsg", "recvmsg", + "bind", "listen", "accept", + "getpeername", "getsockname", "getsockopt", "setsockopt", + "shutdown", +)) diff --git a/ptrace/syscall/linux/__init__.py b/ptrace/syscall/linux/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/ptrace/syscall/linux/aarch64.py b/ptrace/syscall/linux/aarch64.py new file mode 100644 index 0000000..8e522fb --- /dev/null +++ b/ptrace/syscall/linux/aarch64.py @@ -0,0 +1,317 @@ +# From https://github.com/hrw/syscalls-table/ + +SYSCALL_NAMES = { + 0: "io_setup", + 1: "io_destroy", + 2: "io_submit", + 3: "io_cancel", + 4: "io_getevents", + 5: "setxattr", + 6: "lsetxattr", + 7: "fsetxattr", + 8: "getxattr", + 9: "lgetxattr", + 10: "fgetxattr", + 11: "listxattr", + 12: "llistxattr", + 13: "flistxattr", + 14: "removexattr", + 15: "lremovexattr", + 16: "fremovexattr", + 17: "getcwd", + 18: "lookup_dcookie", + 19: "eventfd2", + 20: "epoll_create1", + 21: "epoll_ctl", + 22: "epoll_pwait", + 23: "dup", + 24: "dup3", + 25: "fcntl", + 26: "inotify_init1", + 27: "inotify_add_watch", + 28: "inotify_rm_watch", + 29: "ioctl", + 30: "ioprio_set", + 31: "ioprio_get", + 32: "flock", + 33: "mknodat", + 34: "mkdirat", + 35: "unlinkat", + 36: "symlinkat", + 37: "linkat", + 38: "renameat", + 39: "umount2", + 40: "mount", + 41: "pivot_root", + 42: "nfsservctl", + 43: "statfs", + 44: "fstatfs", + 45: "truncate", + 46: "ftruncate", + 47: "fallocate", + 48: "faccessat", + 49: "chdir", + 50: "fchdir", + 51: "chroot", + 52: "fchmod", + 53: "fchmodat", + 54: "fchownat", + 55: "fchown", + 56: "openat", + 57: "close", + 58: "vhangup", + 59: "pipe2", + 60: "quotactl", + 61: "getdents64", + 62: "lseek", + 63: "read", + 64: "write", + 65: "readv", + 66: "writev", + 67: "pread64", + 68: "pwrite64", + 69: "preadv", + 70: "pwritev", + 71: "sendfile", + 72: "pselect6", + 73: "ppoll", + 74: "signalfd4", + 75: "vmsplice", + 76: "splice", + 77: "tee", + 78: "readlinkat", + 79: "newfstatat", + 80: "fstat", + 81: "sync", + 82: "fsync", + 83: "fdatasync", + 84: "sync_file_range", + 85: "timerfd_create", + 86: "timerfd_settime", + 87: "timerfd_gettime", + 88: "utimensat", + 89: "acct", + 90: "capget", + 91: "capset", + 92: "personality", + 93: "exit", + 94: "exit_group", + 95: "waitid", + 96: "set_tid_address", + 97: "unshare", + 98: "futex", + 99: "set_robust_list", + 100: "get_robust_list", + 101: "nanosleep", + 102: "getitimer", + 103: "setitimer", + 104: "kexec_load", + 105: "init_module", + 106: "delete_module", + 107: "timer_create", + 108: "timer_gettime", + 109: "timer_getoverrun", + 110: "timer_settime", + 111: "timer_delete", + 112: "clock_settime", + 113: "clock_gettime", + 114: "clock_getres", + 115: "clock_nanosleep", + 116: "syslog", + 117: "ptrace", + 118: "sched_setparam", + 119: "sched_setscheduler", + 120: "sched_getscheduler", + 121: "sched_getparam", + 122: "sched_setaffinity", + 123: "sched_getaffinity", + 124: "sched_yield", + 125: "sched_get_priority_max", + 126: "sched_get_priority_min", + 127: "sched_rr_get_interval", + 128: "restart_syscall", + 129: "kill", + 130: "tkill", + 131: "tgkill", + 132: "sigaltstack", + 133: "rt_sigsuspend", + 134: "rt_sigaction", + 135: "rt_sigprocmask", + 136: "rt_sigpending", + 137: "rt_sigtimedwait", + 138: "rt_sigqueueinfo", + 139: "rt_sigreturn", + 140: "setpriority", + 141: "getpriority", + 142: "reboot", + 143: "setregid", + 144: "setgid", + 145: "setreuid", + 146: "setuid", + 147: "setresuid", + 148: "getresuid", + 149: "setresgid", + 150: "getresgid", + 151: "setfsuid", + 152: "setfsgid", + 153: "times", + 154: "setpgid", + 155: "getpgid", + 156: "getsid", + 157: "setsid", + 158: "getgroups", + 159: "setgroups", + 160: "uname", + 161: "sethostname", + 162: "setdomainname", + 163: "getrlimit", + 164: "setrlimit", + 165: "getrusage", + 166: "umask", + 167: "prctl", + 168: "getcpu", + 169: "gettimeofday", + 170: "settimeofday", + 171: "adjtimex", + 172: "getpid", + 173: "getppid", + 174: "getuid", + 175: "geteuid", + 176: "getgid", + 177: "getegid", + 178: "gettid", + 179: "sysinfo", + 180: "mq_open", + 181: "mq_unlink", + 182: "mq_timedsend", + 183: "mq_timedreceive", + 184: "mq_notify", + 185: "mq_getsetattr", + 186: "msgget", + 187: "msgctl", + 188: "msgrcv", + 189: "msgsnd", + 190: "semget", + 191: "semctl", + 192: "semtimedop", + 193: "semop", + 194: "shmget", + 195: "shmctl", + 196: "shmat", + 197: "shmdt", + 198: "socket", + 199: "socketpair", + 200: "bind", + 201: "listen", + 202: "accept", + 203: "connect", + 204: "getsockname", + 205: "getpeername", + 206: "sendto", + 207: "recvfrom", + 208: "setsockopt", + 209: "getsockopt", + 210: "shutdown", + 211: "sendmsg", + 212: "recvmsg", + 213: "readahead", + 214: "brk", + 215: "munmap", + 216: "mremap", + 217: "add_key", + 218: "request_key", + 219: "keyctl", + 220: "clone", + 221: "execve", + 222: "mmap", + 223: "fadvise64", + 224: "swapon", + 225: "swapoff", + 226: "mprotect", + 227: "msync", + 228: "mlock", + 229: "munlock", + 230: "mlockall", + 231: "munlockall", + 232: "mincore", + 233: "madvise", + 234: "remap_file_pages", + 235: "mbind", + 236: "get_mempolicy", + 237: "set_mempolicy", + 238: "migrate_pages", + 239: "move_pages", + 240: "rt_tgsigqueueinfo", + 241: "perf_event_open", + 242: "accept4", + 243: "recvmmsg", + 260: "wait4", + 261: "prlimit64", + 262: "fanotify_init", + 263: "fanotify_mark", + 264: "name_to_handle_at", + 265: "open_by_handle_at", + 266: "clock_adjtime", + 267: "syncfs", + 268: "setns", + 269: "sendmmsg", + 270: "process_vm_readv", + 271: "process_vm_writev", + 272: "kcmp", + 273: "finit_module", + 274: "sched_setattr", + 275: "sched_getattr", + 276: "renameat2", + 277: "seccomp", + 278: "getrandom", + 279: "memfd_create", + 280: "bpf", + 281: "execveat", + 282: "userfaultfd", + 283: "membarrier", + 284: "mlock2", + 285: "copy_file_range", + 286: "preadv2", + 287: "pwritev2", + 288: "pkey_mprotect", + 289: "pkey_alloc", + 290: "pkey_free", + 291: "statx", + 292: "io_pgetevents", + 293: "rseq", + 294: "kexec_file_load", + 424: "pidfd_send_signal", + 425: "io_uring_setup", + 426: "io_uring_enter", + 427: "io_uring_register", + 428: "open_tree", + 429: "move_mount", + 430: "fsopen", + 431: "fsconfig", + 432: "fsmount", + 433: "fspick", + 434: "pidfd_open", + 435: "clone3", + 436: "close_range", + 437: "openat2", + 438: "pidfd_getfd", + 439: "faccessat2", +} + +SOCKET_SYSCALL_NAMES = set(( + "socket", + "socketpair", + "connect", + "sendto", + "recvfrom", + "sendmsg", + "recvmsg", + "bind", + "listen", + "accept", + "getsockname", + "getpeername", + "getsockopt", + "setsockopt", + "shutdown", +)) diff --git a/ptrace/syscall/linux/i386.py b/ptrace/syscall/linux/i386.py new file mode 100644 index 0000000..0400f0d --- /dev/null +++ b/ptrace/syscall/linux/i386.py @@ -0,0 +1,329 @@ +# Linux kernel syscall list from Linux 2.6.21 for i386 +# +# List extracted from Linux kernel source code, see: +# arch/i386/kernel/syscall_table.S +SYSCALL_NAMES = { + 0: "restart_syscall", + 1: "exit", + 2: "fork", + 3: "read", + 4: "write", + 5: "open", + 6: "close", + 7: "waitpid", + 8: "creat", + 9: "link", + 10: "unlink", + 11: "execve", + 12: "chdir", + 13: "time", + 14: "mknod", + 15: "chmod", + 16: "lchown16", + # 17: - + 18: "stat", + 19: "lseek", + 20: "getpid", + 21: "mount", + 22: "oldumount", + 23: "setuid16", + 24: "getuid16", + 25: "stime", + 26: "ptrace", + 27: "alarm", + 28: "fstat", + 29: "pause", + 30: "utime", + # 31: - + # 32: - + 33: "access", + 34: "nice", + # 35: - + 36: "sync", + 37: "kill", + 38: "rename", + 39: "mkdir", + 40: "rmdir", + 41: "dup", + 42: "pipe", + 43: "times", + # 44: - + 45: "brk", + 46: "setgid16", + 47: "getgid16", + 48: "signal", + 49: "geteuid16", + 50: "getegid16", + 51: "acct", + 52: "umount", + # 53: - + 54: "ioctl", + 55: "fcntl", + # 56: - + 57: "setpgid", + # 58: - + 59: "oldolduname", + 60: "umask", + 61: "chroot", + 62: "ustat", + 63: "dup2", + 64: "getppid", + 65: "getpgrp", + 66: "setsid", + 67: "sigaction", + 68: "sgetmask", + 69: "ssetmask", + 70: "setreuid16", + 71: "setregid16", + 72: "sigsuspend", + 73: "sigpending", + 74: "sethostname", + 75: "setrlimit", + 76: "old_getrlimit", + 77: "getrusage", + 78: "gettimeofday", + 79: "settimeofday", + 80: "getgroups16", + 81: "setgroups16", + 82: "old_select", + 83: "symlink", + 84: "lstat", + 85: "readlink", + 86: "uselib", + 87: "swapon", + 88: "reboot", + 89: "old_readdir", + 90: "old_mmap", + 91: "munmap", + 92: "truncate", + 93: "ftruncate", + 94: "fchmod", + 95: "fchown16", + 96: "getpriority", + 97: "setpriority", + # 98: - + 99: "statfs", + 100: "fstatfs", + 101: "ioperm", + 102: "socketcall", + 103: "syslog", + 104: "setitimer", + 105: "getitimer", + 106: "newstat", + 107: "newlstat", + 108: "newfstat", + 109: "olduname", + 110: "iopl", + 111: "vhangup", + # 112: old "idle" + 113: "vm86old", + 114: "wait4", + 115: "swapoff", + 116: "sysinfo", + 117: "ipc", + 118: "fsync", + 119: "sigreturn", + 120: "clone", + 121: "setdomainname", + 122: "uname", + 123: "modify_ldt", + 124: "adjtimex", + 125: "mprotect", + 126: "sigprocmask", + # 127: old "create_module" + 128: "init_module", + 129: "delete_module", + # 130: old "get_kernel_syms" + 131: "quotactl", + 132: "getpgid", + 133: "fchdir", + 134: "bdflush", + 135: "sysfs", + 136: "personality", + # 137: reserved for afs_syscall + 138: "setfsuid16", + 139: "setfsgid16", + 140: "llseek", + 141: "getdents", + 142: "select", + 143: "flock", + 144: "msync", + 145: "readv", + 146: "writev", + 147: "getsid", + 148: "fdatasync", + 149: "sysctl", + 150: "mlock", + 151: "munlock", + 152: "mlockall", + 153: "munlockall", + 154: "sched_setparam", + 155: "sched_getparam", + 156: "sched_setscheduler", + 157: "sched_getscheduler", + 158: "sched_yield", + 159: "sched_get_priority_max", + 160: "sched_get_priority_min", + 161: "sched_rr_get_interval", + 162: "nanosleep", + 163: "mremap", + 164: "setresuid16", + 165: "getresuid16", + 166: "vm86", + # 167: old "query_module" + 168: "poll", + 169: "nfsservctl", + 170: "setresgid16", + 171: "getresgid16", + 172: "prctl", + 173: "rt_sigreturn", + 174: "rt_sigaction", + 175: "rt_sigprocmask", + 176: "rt_sigpending", + 177: "rt_sigtimedwait", + 178: "rt_sigqueueinfo", + 179: "rt_sigsuspend", + 180: "pread64", + 181: "pwrite64", + 182: "chown16", + 183: "getcwd", + 184: "capget", + 185: "capset", + 186: "sigaltstack", + 187: "sendfile", + # 188: (reserved) + # 189: (reserved) + 190: "vfork", + 191: "getrlimit", + 192: "mmap2", + 193: "truncate64", + 194: "ftruncate64", + 195: "stat64", + 196: "lstat64", + 197: "fstat64", + 198: "lchown", + 199: "getuid", + 200: "getgid", + 201: "geteuid", + 202: "getegid", + 203: "setreuid", + 204: "setregid", + 205: "getgroups", + 206: "setgroups", + 207: "fchown", + 208: "setresuid", + 209: "getresuid", + 210: "setresgid", + 211: "getresgid", + 212: "chown", + 213: "setuid", + 214: "setgid", + 215: "setfsuid", + 216: "setfsgid", + # ------------------------------- + 217: "pivot_root", + 218: "mincore", + 219: "madvise", + 220: "getdents64", + 221: "fcntl64", + # 222: - + # 223: - + 224: "gettid", + 225: "readahead", + 226: "setxattr", + 227: "lsetxattr", + 228: "fsetxattr", + 229: "getxattr", + 230: "lgetxattr", + 231: "fgetxattr", + 232: "listxattr", + 233: "llistxattr", + 234: "flistxattr", + 235: "removexattr", + 236: "lremovexattr", + 237: "fremovexattr", + 238: "tkill", + 239: "sendfile64", + 240: "futex", + 241: "sched_setaffinity", + 242: "sched_getaffinity", + 243: "set_thread_area", + 244: "get_thread_area", + 245: "io_setup", + 246: "io_destroy", + 247: "io_getevents", + 248: "io_submit", + 249: "io_cancel", + 250: "fadvise64", + # 251: - + 252: "exit_group", + 253: "lookup_dcookie", + 254: "epoll_create", + 255: "epoll_ctl", + 256: "epoll_wait", + 257: "remap_file_pages", + 258: "set_tid_address", + 259: "timer_create", + 260: "timer_settime", + 261: "timer_gettime", + 262: "timer_getoverrun", + 263: "timer_delete", + 264: "clock_settime", + 265: "clock_gettime", + 266: "clock_getres", + 267: "clock_nanosleep", + 268: "statfs64", + 269: "fstatfs64", + 270: "tgkill", + 271: "utimes", + 272: "fadvise64_64", + # 273: - + 274: "mbind", + 275: "get_mempolicy", + 276: "set_mempolicy", + 277: "mq_open", + 278: "mq_unlink", + 279: "mq_timedsend", + 280: "mq_timedreceive", + 281: "mq_notify", + 282: "mq_getsetattr", + 283: "kexec_load", + 284: "waitid", + # 285: - + 286: "add_key", + 287: "request_key", + 288: "keyctl", + 289: "ioprio_set", + 290: "ioprio_get", + 291: "inotify_init", + 292: "inotify_add_watch", + 293: "inotify_rm_watch", + 294: "migrate_pages", + 295: "openat", + 296: "mkdirat", + 297: "mknodat", + 298: "fchownat", + 299: "futimesat", + 300: "fstatat64", + 301: "unlinkat", + 302: "renameat", + 303: "linkat", + 304: "symlinkat", + 305: "readlinkat", + 306: "fchmodat", + 307: "faccessat", + 308: "pselect6", + 309: "ppoll", + 310: "unshare", + 311: "set_robust_list", + 312: "get_robust_list", + 313: "splice", + 314: "sync_file_range", + 315: "tee", + 316: "vmsplice", + 317: "move_pages", + 318: "getcpu", + 319: "epoll_pwait", +} + +SOCKET_SYSCALL_NAMES = set(("socketcall",)) diff --git a/ptrace/syscall/linux/powerpc32.py b/ptrace/syscall/linux/powerpc32.py new file mode 100644 index 0000000..68d0944 --- /dev/null +++ b/ptrace/syscall/linux/powerpc32.py @@ -0,0 +1,439 @@ +# From the kernel sources, arch/powerpc/kernel/syscalls/syscall.tbl + +SYSCALL_NAMES = { + 0: "restart_syscall", + 1: "exit", + 2: "fork", + 3: "read", + 4: "write", + 5: "open", + 6: "close", + 7: "waitpid", + 8: "creat", + 9: "link", + 10: "unlink", + 11: "execve", + 12: "chdir", + 13: "time", + 14: "mknod", + 15: "chmod", + 16: "lchown", + 17: "break", + 18: "oldstat", + 19: "lseek", + 20: "getpid", + 21: "mount", + 22: "umount", + 23: "setuid", + 24: "getuid", + 25: "stime", + 26: "ptrace", + 27: "alarm", + 28: "oldfstat", + 29: "pause", + 30: "utime", + 31: "stty", + 32: "gtty", + 33: "access", + 34: "nice", + 35: "ftime", + 36: "sync", + 37: "kill", + 38: "rename", + 39: "mkdir", + 40: "rmdir", + 41: "dup", + 42: "pipe", + 43: "times", + 44: "prof", + 45: "brk", + 46: "setgid", + 47: "getgid", + 48: "signal", + 49: "geteuid", + 50: "getegid", + 51: "acct", + 52: "umount2", + 53: "lock", + 54: "ioctl", + 55: "fcntl", + 56: "mpx", + 57: "setpgid", + 58: "ulimit", + 59: "oldolduname", + 60: "umask", + 61: "chroot", + 62: "ustat", + 63: "dup2", + 64: "getppid", + 65: "getpgrp", + 66: "setsid", + 67: "sigaction", + 68: "sgetmask", + 69: "ssetmask", + 70: "setreuid", + 71: "setregid", + 72: "sigsuspend", + 73: "sigpending", + 74: "sethostname", + 75: "setrlimit", + 76: "getrlimit", + 77: "getrusage", + 78: "gettimeofday", + 79: "settimeofday", + 80: "getgroups", + 81: "setgroups", + 82: "select", + 83: "symlink", + 84: "oldlstat", + 85: "readlink", + 86: "uselib", + 87: "swapon", + 88: "reboot", + 89: "readdir", + 90: "mmap", + 91: "munmap", + 92: "truncate", + 93: "ftruncate", + 94: "fchmod", + 95: "fchown", + 96: "getpriority", + 97: "setpriority", + 98: "profil", + 99: "statfs", + 100: "fstatfs", + 101: "ioperm", + 102: "socketcall", + 103: "syslog", + 104: "setitimer", + 105: "getitimer", + 106: "stat", + 107: "lstat", + 108: "fstat", + 109: "olduname", + 110: "iopl", + 111: "vhangup", + 112: "idle", + 113: "vm86", + 114: "wait4", + 115: "swapoff", + 116: "sysinfo", + 117: "ipc", + 118: "fsync", + 119: "sigreturn", + 120: "clone", + 121: "setdomainname", + 122: "uname", + 123: "modify_ldt", + 124: "adjtimex", + 125: "mprotect", + 126: "sigprocmask", + 127: "create_module", + 128: "init_module", + 129: "delete_module", + 130: "get_kernel_syms", + 131: "quotactl", + 132: "getpgid", + 133: "fchdir", + 134: "bdflush", + 135: "sysfs", + 136: "personality", + 137: "afs_syscall", + 138: "setfsuid", + 139: "setfsgid", + 140: "_llseek", + 141: "getdents", + 142: "_newselect", + 143: "flock", + 144: "msync", + 145: "readv", + 146: "writev", + 147: "getsid", + 148: "fdatasync", + 149: "_sysctl", + 150: "mlock", + 151: "munlock", + 152: "mlockall", + 153: "munlockall", + 154: "sched_setparam", + 155: "sched_getparam", + 156: "sched_setscheduler", + 157: "sched_getscheduler", + 158: "sched_yield", + 159: "sched_get_priority_max", + 160: "sched_get_priority_min", + 161: "sched_rr_get_interval", + 162: "nanosleep", + 163: "mremap", + 164: "setresuid", + 165: "getresuid", + 166: "query_module", + 167: "poll", + 168: "nfsservctl", + 169: "setresgid", + 170: "getresgid", + 171: "prctl", + 172: "rt_sigreturn", + 173: "rt_sigaction", + 174: "rt_sigprocmask", + 175: "rt_sigpending", + 176: "rt_sigtimedwait", + 177: "", + 178: "", + 179: "pread64", + 180: "pwrite64", + 181: "chown", + 182: "getcwd", + 183: "capget", + 184: "capset", + 185: "sigaltstack", + 186: "sendfile", + 187: "getpmsg", + 188: "", + 189: "vfork", + 190: "ugetrlimit", + 191: "readahead", + 192: "mmap2", + 193: "truncate64", + 194: "ftruncate64", + 195: "stat64", + 196: "lstat64", + 197: "fstat64", + 198: "", + 199: "", + 200: "", + 201: "", + 202: "getdents64", + 203: "pivot_root", + 204: "fcntl64", + 205: "madvise", + 206: "mincore", + 207: "gettid", + 208: "tkill", + 209: "setxattr", + 210: "lsetxattr", + 211: "fsetxattr", + 212: "getxattr", + 213: "lgetxattr", + 214: "fgetxattr", + 215: "listxattr", + 216: "llistxattr", + 217: "flistxattr", + 218: "removexattr", + 219: "lremovexattr", + 220: "fremovexattr", + 221: "futex", + 222: "sched_setaffinity", + 223: "sched_getaffinity", + 225: "tuxcall", + 226: "sendfile64", + 227: "io_setup", + 228: "io_destroy", + 229: "io_getevents", + 230: "io_submit", + 231: "io_cancel", + 232: "set_tid_address", + 233: "fadvise64", + 234: "exit_group", + 235: "lookup_dcookie", + 236: "epoll_create", + 237: "epoll_ctl", + 238: "epoll_wait", + 239: "remap_file_pages", + 240: "timer_create", + 241: "timer_settime", + 242: "timer_gettime", + 243: "timer_getoverrun", + 244: "timer_delete", + 245: "clock_settime", + 246: "clock_gettime", + 247: "clock_getres", + 248: "clock_nanosleep", + 249: "swapcontext", + 250: "tgkill", + 251: "utimes", + 252: "statfs64", + 253: "fstatfs64", + 254: "fadvise64_64", + 255: "rtas", + 256: "sys_debug_setcontext", + 258: "migrate_pages", + 259: "mbind", + 260: "get_mempolicy", + 261: "set_mempolicy", + 262: "mq_open", + 263: "mq_unlink", + 264: "mq_timedsend", + 265: "mq_timedreceive", + 266: "mq_notify", + 267: "mq_getsetattr", + 268: "kexec_load", + 269: "add_key", + 270: "request_key", + 271: "keyctl", + 272: "waitid", + 273: "ioprio_set", + 274: "ioprio_get", + 275: "inotify_init", + 276: "inotify_add_watch", + 277: "inotify_rm_watch", + 280: "pselect6", + 281: "ppoll", + 282: "unshare", + 283: "splice", + 284: "tee", + 285: "vmsplice", + 286: "openat", + 287: "mkdirat", + 288: "mknodat", + 289: "fchownat", + 290: "futimesat", + 291: "fstatat64", + 292: "unlinkat", + 293: "renameat", + 294: "linkat", + 295: "symlinkat", + 296: "readlinkat", + 297: "fchmodat", + 298: "faccessat", + 299: "get_robust_list", + 300: "set_robust_list", + 301: "move_pages", + 302: "getcpu", + 303: "epoll_pwait", + 304: "utimensat", + 305: "signalfd", + 306: "timerfd_create", + 307: "eventfd", + 308: "sync_file_range2", + 309: "fallocate", + 310: "subpage_prot", + 311: "timerfd_settime", + 312: "timerfd_gettime", + 313: "signalfd4", + 314: "eventfd2", + 315: "epoll_create1", + 316: "dup3", + 317: "pipe2", + 318: "inotify_init1", + 319: "perf_event_open", + 320: "preadv", + 321: "pwritev", + 322: "rt_tgsigqueueinfo", + 323: "fanotify_init", + 324: "fanotify_mark", + 325: "prlimit64", + 326: "socket", + 327: "bind", + 328: "connect", + 329: "listen", + 330: "accept", + 331: "getsockname", + 332: "getpeername", + 333: "socketpair", + 334: "send", + 335: "sendto", + 336: "recv", + 337: "recvfrom", + 338: "shutdown", + 339: "setsockopt", + 340: "getsockopt", + 341: "sendmsg", + 342: "recvmsg", + 343: "recvmmsg", + 344: "accept4", + 345: "name_to_handle_at", + 346: "open_by_handle_at", + 347: "clock_adjtime", + 348: "syncfs", + 349: "sendmmsg", + 350: "setns", + 351: "process_vm_readv", + 352: "process_vm_writev", + 353: "finit_module", + 354: "kcmp", + 355: "sched_setattr", + 356: "sched_getattr", + 357: "renameat2", + 358: "seccomp", + 359: "getrandom", + 360: "memfd_create", + 361: "bpf", + 362: "execveat", + 363: "switch_endian", + 364: "userfaultfd", + 365: "membarrier", + 378: "mlock2", + 379: "copy_file_range", + 380: "preadv2", + 381: "pwritev2", + 382: "kexec_file_load", + 383: "statx", + 384: "pkey_alloc", + 385: "pkey_free", + 386: "pkey_mprotect", + 387: "rseq", + 388: "io_pgetevents", + 393: "semget", + 394: "semctl", + 395: "shmget", + 396: "shmctl", + 397: "shmat", + 398: "shmdt", + 399: "msgget", + 400: "msgsnd", + 401: "msgrcv", + 402: "msgctl", + 403: "clock_gettime64", + 404: "clock_settime64", + 405: "clock_adjtime64", + 406: "clock_getres_time64", + 407: "clock_nanosleep_time64", + 408: "timer_gettime64", + 409: "timer_settime64", + 410: "timerfd_gettime64", + 411: "timerfd_settime64", + 412: "utimensat_time64", + 413: "pselect6_time64", + 414: "ppoll_time64", + 416: "io_pgetevents_time64", + 417: "recvmmsg_time64", + 418: "mq_timedsend_time64", + 419: "mq_timedreceive_time64", + 420: "semtimedop_time64", + 421: "rt_sigtimedwait_time64", + 422: "futex_time64", + 423: "sched_rr_get_interval_time64", + 424: "pidfd_send_signal", + 425: "io_uring_setup", + 426: "io_uring_enter", + 427: "io_uring_register", + 428: "open_tree", + 429: "move_mount", + 430: "fsopen", + 431: "fsconfig", + 432: "fsmount", + 433: "fspick", + 434: "pidfd_open", + 435: "clone3", + 437: "openat2", + 438: "pidfd_getfd" +} + +SOCKET_SYSCALL_NAMES = set(( + "socket", + "socketpair", + "connect", + "sendto", + "recvfrom", + "sendmsg", + "recvmsg", + "bind", + "listen", + "accept", + "getsockname", + "getpeername", + "getsockopt", + "setsockopt", + "shutdown", +)) diff --git a/ptrace/syscall/linux/powerpc64.py b/ptrace/syscall/linux/powerpc64.py new file mode 100644 index 0000000..f08162c --- /dev/null +++ b/ptrace/syscall/linux/powerpc64.py @@ -0,0 +1,411 @@ +# From the kernel sources, arch/powerpc/kernel/syscalls/syscall.tbl + +SYSCALL_NAMES = { + 0: "restart_syscall", + 1: "exit", + 2: "fork", + 3: "read", + 4: "write", + 5: "open", + 6: "close", + 7: "waitpid", + 8: "creat", + 9: "link", + 10: "unlink", + 11: "execve", + 12: "chdir", + 13: "time", + 14: "mknod", + 15: "chmod", + 16: "lchown", + 17: "break", + 18: "oldstat", + 19: "lseek", + 20: "getpid", + 21: "mount", + 22: "umount", + 23: "setuid", + 24: "getuid", + 25: "stime", + 26: "ptrace", + 27: "alarm", + 28: "oldfstat", + 29: "pause", + 30: "utime", + 31: "stty", + 32: "gtty", + 33: "access", + 34: "nice", + 35: "ftime", + 36: "sync", + 37: "kill", + 38: "rename", + 39: "mkdir", + 40: "rmdir", + 41: "dup", + 42: "pipe", + 43: "times", + 44: "prof", + 45: "brk", + 46: "setgid", + 47: "getgid", + 48: "signal", + 49: "geteuid", + 50: "getegid", + 51: "acct", + 52: "umount2", + 53: "lock", + 54: "ioctl", + 55: "fcntl", + 56: "mpx", + 57: "setpgid", + 58: "ulimit", + 59: "oldolduname", + 60: "umask", + 61: "chroot", + 62: "ustat", + 63: "dup2", + 64: "getppid", + 65: "getpgrp", + 66: "setsid", + 67: "sigaction", + 68: "sgetmask", + 69: "ssetmask", + 70: "setreuid", + 71: "setregid", + 72: "sigsuspend", + 73: "sigpending", + 74: "sethostname", + 75: "setrlimit", + 76: "getrlimit", + 77: "getrusage", + 78: "gettimeofday", + 79: "settimeofday", + 80: "getgroups", + 81: "setgroups", + 82: "select", + 83: "symlink", + 84: "oldlstat", + 85: "readlink", + 86: "uselib", + 87: "swapon", + 88: "reboot", + 89: "readdir", + 90: "mmap", + 91: "munmap", + 92: "truncate", + 93: "ftruncate", + 94: "fchmod", + 95: "fchown", + 96: "getpriority", + 97: "setpriority", + 98: "profil", + 99: "statfs", + 100: "fstatfs", + 101: "ioperm", + 102: "socketcall", + 103: "syslog", + 104: "setitimer", + 105: "getitimer", + 106: "stat", + 107: "lstat", + 108: "fstat", + 109: "olduname", + 110: "iopl", + 111: "vhangup", + 112: "idle", + 113: "vm86", + 114: "wait4", + 115: "swapoff", + 116: "sysinfo", + 117: "ipc", + 118: "fsync", + 119: "sigreturn", + 120: "clone", + 121: "setdomainname", + 122: "uname", + 123: "modify_ldt", + 124: "adjtimex", + 125: "mprotect", + 126: "sigprocmask", + 127: "create_module", + 128: "init_module", + 129: "delete_module", + 130: "get_kernel_syms", + 131: "quotactl", + 132: "getpgid", + 133: "fchdir", + 134: "bdflush", + 135: "sysfs", + 136: "personality", + 137: "afs_syscall", + 138: "setfsuid", + 139: "setfsgid", + 140: "_llseek", + 141: "getdents", + 142: "_newselect", + 143: "flock", + 144: "msync", + 145: "readv", + 146: "writev", + 147: "getsid", + 148: "fdatasync", + 149: "_sysctl", + 150: "mlock", + 151: "munlock", + 152: "mlockall", + 153: "munlockall", + 154: "sched_setparam", + 155: "sched_getparam", + 156: "sched_setscheduler", + 157: "sched_getscheduler", + 158: "sched_yield", + 159: "sched_get_priority_max", + 160: "sched_get_priority_min", + 161: "sched_rr_get_interval", + 162: "nanosleep", + 163: "mremap", + 164: "setresuid", + 165: "getresuid", + 166: "query_module", + 167: "poll", + 168: "nfsservctl", + 169: "setresgid", + 170: "getresgid", + 171: "prctl", + 172: "rt_sigreturn", + 173: "rt_sigaction", + 174: "rt_sigprocmask", + 175: "rt_sigpending", + 176: "rt_sigtimedwait", + 177: "rt_sigqueueinfo", + 178: "rt_sigsuspend", + 179: "pread64", + 180: "pwrite64", + 181: "chown", + 182: "getcwd", + 183: "capget", + 184: "capset", + 185: "sigaltstack", + 186: "sendfile", + 187: "getpmsg", + 188: "putpmsg", + 189: "vfork", + 190: "ugetrlimit", + 191: "readahead", + 198: "pciconfig_read", + 199: "pciconfig_write", + 200: "pciconfig_iobase", + 201: "multiplexer", + 202: "getdents64", + 203: "pivot_root", + 205: "madvise", + 206: "mincore", + 207: "gettid", + 208: "tkill", + 209: "setxattr", + 210: "lsetxattr", + 211: "fsetxattr", + 212: "getxattr", + 213: "lgetxattr", + 214: "fgetxattr", + 215: "listxattr", + 216: "llistxattr", + 217: "flistxattr", + 218: "removexattr", + 219: "lremovexattr", + 220: "fremovexattr", + 221: "futex", + 222: "sched_setaffinity", + 223: "sched_getaffinity", + 225: "tuxcall", + 227: "io_setup", + 228: "io_destroy", + 229: "io_getevents", + 230: "io_submit", + 231: "io_cancel", + 232: "set_tid_address", + 233: "fadvise64", + 234: "exit_group", + 235: "lookup_dcookie", + 236: "epoll_create", + 237: "epoll_ctl", + 238: "epoll_wait", + 239: "remap_file_pages", + 240: "timer_create", + 241: "timer_settime", + 242: "timer_gettime", + 243: "timer_getoverrun", + 244: "timer_delete", + 245: "clock_settime", + 246: "clock_gettime", + 247: "clock_getres", + 248: "clock_nanosleep", + 249: "swapcontext", + 250: "tgkill", + 251: "utimes", + 252: "statfs64", + 253: "fstatfs64", + 255: "rtas", + 256: "sys_debug_setcontext", + 258: "migrate_pages", + 259: "mbind", + 260: "get_mempolicy", + 261: "set_mempolicy", + 262: "mq_open", + 263: "mq_unlink", + 264: "mq_timedsend", + 265: "mq_timedreceive", + 266: "mq_notify", + 267: "mq_getsetattr", + 268: "kexec_load", + 269: "add_key", + 270: "request_key", + 271: "keyctl", + 272: "waitid", + 273: "ioprio_set", + 274: "ioprio_get", + 275: "inotify_init", + 276: "inotify_add_watch", + 277: "inotify_rm_watch", + 280: "pselect6", + 281: "ppoll", + 282: "unshare", + 283: "splice", + 284: "tee", + 285: "vmsplice", + 286: "openat", + 287: "mkdirat", + 288: "mknodat", + 289: "fchownat", + 290: "futimesat", + 291: "newfstatat", + 292: "unlinkat", + 293: "renameat", + 294: "linkat", + 295: "symlinkat", + 296: "readlinkat", + 297: "fchmodat", + 298: "faccessat", + 299: "get_robust_list", + 300: "set_robust_list", + 301: "move_pages", + 302: "getcpu", + 303: "epoll_pwait", + 304: "utimensat", + 305: "signalfd", + 306: "timerfd_create", + 307: "eventfd", + 308: "sync_file_range2", + 309: "fallocate", + 310: "subpage_prot", + 311: "timerfd_settime", + 312: "timerfd_gettime", + 313: "signalfd4", + 314: "eventfd2", + 315: "epoll_create1", + 316: "dup3", + 317: "pipe2", + 318: "inotify_init1", + 319: "perf_event_open", + 320: "preadv", + 321: "pwritev", + 322: "rt_tgsigqueueinfo", + 323: "fanotify_init", + 324: "fanotify_mark", + 325: "prlimit64", + 326: "socket", + 327: "bind", + 328: "connect", + 329: "listen", + 330: "accept", + 331: "getsockname", + 332: "getpeername", + 333: "socketpair", + 334: "send", + 335: "sendto", + 336: "recv", + 337: "recvfrom", + 338: "shutdown", + 339: "setsockopt", + 340: "getsockopt", + 341: "sendmsg", + 342: "recvmsg", + 343: "recvmmsg", + 344: "accept4", + 345: "name_to_handle_at", + 346: "open_by_handle_at", + 347: "clock_adjtime", + 348: "syncfs", + 349: "sendmmsg", + 350: "setns", + 351: "process_vm_readv", + 352: "process_vm_writev", + 353: "finit_module", + 354: "kcmp", + 355: "sched_setattr", + 356: "sched_getattr", + 357: "renameat2", + 358: "seccomp", + 359: "getrandom", + 360: "memfd_create", + 361: "bpf", + 362: "execveat", + 363: "switch_endian", + 364: "userfaultfd", + 365: "membarrier", + 378: "mlock2", + 379: "copy_file_range", + 380: "preadv2", + 381: "pwritev2", + 382: "kexec_file_load", + 383: "statx", + 384: "pkey_alloc", + 385: "pkey_free", + 386: "pkey_mprotect", + 387: "rseq", + 388: "io_pgetevents", + 392: "semtimedop", + 393: "semget", + 394: "semctl", + 395: "shmget", + 396: "shmctl", + 397: "shmat", + 398: "shmdt", + 399: "msgget", + 400: "msgsnd", + 401: "msgrcv", + 402: "msgctl", + 424: "pidfd_send_signal", + 425: "io_uring_setup", + 426: "io_uring_enter", + 427: "io_uring_register", + 428: "open_tree", + 429: "move_mount", + 430: "fsopen", + 431: "fsconfig", + 432: "fsmount", + 433: "fspick", + 434: "pidfd_open", + 435: "clone3", + 437: "openat2", + 438: "pidfd_getfd" +} + +SOCKET_SYSCALL_NAMES = set(( + "socket", + "socketpair", + "connect", + "sendto", + "recvfrom", + "sendmsg", + "recvmsg", + "bind", + "listen", + "accept", + "getsockname", + "getpeername", + "getsockopt", + "setsockopt", + "shutdown", +)) diff --git a/ptrace/syscall/linux/riscv32.py b/ptrace/syscall/linux/riscv32.py new file mode 100644 index 0000000..dd7927c --- /dev/null +++ b/ptrace/syscall/linux/riscv32.py @@ -0,0 +1,316 @@ +SYSCALL_NAMES = { + 0: "io_setup", + 1: "io_destroy", + 2: "io_submit", + 3: "io_cancel", + 5: "setxattr", + 6: "lsetxattr", + 7: "fsetxattr", + 8: "getxattr", + 9: "lgetxattr", + 10: "fgetxattr", + 11: "listxattr", + 12: "llistxattr", + 13: "flistxattr", + 14: "removexattr", + 15: "lremovexattr", + 16: "fremovexattr", + 17: "getcwd", + 18: "lookup_dcookie", + 19: "eventfd2", + 20: "epoll_create1", + 21: "epoll_ctl", + 22: "epoll_pwait", + 23: "dup", + 24: "dup3", + 25: "fcntl64", + 26: "inotify_init1", + 27: "inotify_add_watch", + 28: "inotify_rm_watch", + 29: "ioctl", + 30: "ioprio_set", + 31: "ioprio_get", + 32: "flock", + 33: "mknodat", + 34: "mkdirat", + 35: "unlinkat", + 36: "symlinkat", + 37: "linkat", + 39: "umount2", + 40: "mount", + 41: "pivot_root", + 42: "nfsservctl", + 43: "statfs64", + 44: "fstatfs64", + 45: "truncate64", + 46: "ftruncate64", + 47: "fallocate", + 48: "faccessat", + 49: "chdir", + 50: "fchdir", + 51: "chroot", + 52: "fchmod", + 53: "fchmodat", + 54: "fchownat", + 55: "fchown", + 56: "openat", + 57: "close", + 58: "vhangup", + 59: "pipe2", + 60: "quotactl", + 61: "getdents64", + 62: "llseek", + 63: "read", + 64: "write", + 65: "readv", + 66: "writev", + 67: "pread64", + 68: "pwrite64", + 69: "preadv", + 70: "pwritev", + 71: "sendfile64", + 74: "signalfd4", + 75: "vmsplice", + 76: "splice", + 77: "tee", + 78: "readlinkat", + 81: "sync", + 82: "fsync", + 83: "fdatasync", + 84: "sync_file_range", + 85: "timerfd_create", + 89: "acct", + 90: "capget", + 91: "capset", + 92: "personality", + 93: "exit", + 94: "exit_group", + 95: "waitid", + 96: "set_tid_address", + 97: "unshare", + 99: "set_robust_list", + 100: "get_robust_list", + 102: "getitimer", + 103: "setitimer", + 104: "kexec_load", + 105: "init_module", + 106: "delete_module", + 107: "timer_create", + 109: "timer_getoverrun", + 111: "timer_delete", + 116: "syslog", + 117: "ptrace", + 118: "sched_setparam", + 119: "sched_setscheduler", + 120: "sched_getscheduler", + 121: "sched_getparam", + 122: "sched_setaffinity", + 123: "sched_getaffinity", + 124: "sched_yield", + 125: "sched_get_priority_max", + 126: "sched_get_priority_min", + 128: "restart_syscall", + 129: "kill", + 130: "tkill", + 131: "tgkill", + 132: "sigaltstack", + 133: "rt_sigsuspend", + 134: "rt_sigaction", + 135: "rt_sigprocmask", + 136: "rt_sigpending", + 138: "rt_sigqueueinfo", + 139: "rt_sigreturn", + 140: "setpriority", + 141: "getpriority", + 142: "reboot", + 143: "setregid", + 144: "setgid", + 145: "setreuid", + 146: "setuid", + 147: "setresuid", + 148: "getresuid", + 149: "setresgid", + 150: "getresgid", + 151: "setfsuid", + 152: "setfsgid", + 153: "times", + 154: "setpgid", + 155: "getpgid", + 156: "getsid", + 157: "setsid", + 158: "getgroups", + 159: "setgroups", + 160: "uname", + 161: "sethostname", + 162: "setdomainname", + 165: "getrusage", + 166: "umask", + 167: "prctl", + 168: "getcpu", + 172: "getpid", + 173: "getppid", + 174: "getuid", + 175: "geteuid", + 176: "getgid", + 177: "getegid", + 178: "gettid", + 179: "sysinfo", + 180: "mq_open", + 181: "mq_unlink", + 184: "mq_notify", + 185: "mq_getsetattr", + 186: "msgget", + 187: "msgctl", + 188: "msgrcv", + 189: "msgsnd", + 190: "semget", + 191: "semctl", + 193: "semop", + 194: "shmget", + 195: "shmctl", + 196: "shmat", + 197: "shmdt", + 198: "socket", + 199: "socketpair", + 200: "bind", + 201: "listen", + 202: "accept", + 203: "connect", + 204: "getsockname", + 205: "getpeername", + 206: "sendto", + 207: "recvfrom", + 208: "setsockopt", + 209: "getsockopt", + 210: "shutdown", + 211: "sendmsg", + 212: "recvmsg", + 213: "readahead", + 214: "brk", + 215: "munmap", + 216: "mremap", + 217: "add_key", + 218: "request_key", + 219: "keyctl", + 220: "clone", + 221: "execve", + 222: "mmap2", + 223: "fadvise64_64", + 224: "swapon", + 225: "swapoff", + 226: "mprotect", + 227: "msync", + 228: "mlock", + 229: "munlock", + 230: "mlockall", + 231: "munlockall", + 232: "mincore", + 233: "madvise", + 234: "remap_file_pages", + 235: "mbind", + 236: "get_mempolicy", + 237: "set_mempolicy", + 238: "migrate_pages", + 239: "move_pages", + 240: "rt_tgsigqueueinfo", + 241: "perf_event_open", + 242: "accept4", + 259: "riscv_flush_icache", + 261: "prlimit64", + 262: "fanotify_init", + 263: "fanotify_mark", + 264: "name_to_handle_at", + 265: "open_by_handle_at", + 267: "syncfs", + 268: "setns", + 269: "sendmmsg", + 270: "process_vm_readv", + 271: "process_vm_writev", + 272: "kcmp", + 273: "finit_module", + 274: "sched_setattr", + 275: "sched_getattr", + 276: "renameat2", + 277: "seccomp", + 278: "getrandom", + 279: "memfd_create", + 280: "bpf", + 281: "execveat", + 282: "userfaultfd", + 283: "membarrier", + 284: "mlock2", + 285: "copy_file_range", + 286: "preadv2", + 287: "pwritev2", + 288: "pkey_mprotect", + 289: "pkey_alloc", + 290: "pkey_free", + 291: "statx", + 293: "rseq", + 294: "kexec_file_load", + 403: "clock_gettime64", + 404: "clock_settime64", + 405: "clock_adjtime64", + 406: "clock_getres_time64", + 407: "clock_nanosleep_time64", + 408: "timer_gettime64", + 409: "timer_settime64", + 410: "timerfd_gettime64", + 411: "timerfd_settime64", + 412: "utimensat_time64", + 413: "pselect6_time64", + 414: "ppoll_time64", + 416: "io_pgetevents_time64", + 417: "recvmmsg_time64", + 418: "mq_timedsend_time64", + 419: "mq_timedreceive_time64", + 420: "semtimedop_time64", + 421: "rt_sigtimedwait_time64", + 422: "futex_time64", + 423: "sched_rr_get_interval_time64", + 424: "pidfd_send_signal", + 425: "io_uring_setup", + 426: "io_uring_enter", + 427: "io_uring_register", + 428: "open_tree", + 429: "move_mount", + 430: "fsopen", + 431: "fsconfig", + 432: "fsmount", + 433: "fspick", + 434: "pidfd_open", + 435: "clone3", + 436: "close_range", + 437: "openat2", + 438: "pidfd_getfd", + 439: "faccessat2", + 440: "process_madvise", + 441: "epoll_pwait2", + 442: "mount_setattr", + 443: "quotactl_fd", + 444: "landlock_create_ruleset", + 445: "landlock_add_rule", + 446: "landlock_restrict_self", + 447: "memfd_secret", + 448: "process_mrelease", + 449: "futex_waitv", + 450: "set_mempolicy_home_node", +} + +SOCKET_SYSCALL_NAMES = set(( + "socket", + "socketpair", + "connect", + "sendto", + "recvfrom", + "sendmsg", + "recvmsg", + "bind", + "listen", + "accept", + "getsockname", + "getpeername", + "getsockopt", + "setsockopt", + "shutdown", +)) diff --git a/ptrace/syscall/linux/riscv64.py b/ptrace/syscall/linux/riscv64.py new file mode 100644 index 0000000..40f4f1f --- /dev/null +++ b/ptrace/syscall/linux/riscv64.py @@ -0,0 +1,327 @@ +SYSCALL_NAMES = { + 0: "io_setup", + 1: "io_destroy", + 2: "io_submit", + 3: "io_cancel", + 4: "io_getevents", + 5: "setxattr", + 6: "lsetxattr", + 7: "fsetxattr", + 8: "getxattr", + 9: "lgetxattr", + 10: "fgetxattr", + 11: "listxattr", + 12: "llistxattr", + 13: "flistxattr", + 14: "removexattr", + 15: "lremovexattr", + 16: "fremovexattr", + 17: "getcwd", + 18: "lookup_dcookie", + 19: "eventfd2", + 20: "epoll_create1", + 21: "epoll_ctl", + 22: "epoll_pwait", + 23: "dup", + 24: "dup3", + 25: "fcntl", + 26: "inotify_init1", + 27: "inotify_add_watch", + 28: "inotify_rm_watch", + 29: "ioctl", + 30: "ioprio_set", + 31: "ioprio_get", + 32: "flock", + 33: "mknodat", + 34: "mkdirat", + 35: "unlinkat", + 36: "symlinkat", + 37: "linkat", + 38: "renameat", + 39: "umount2", + 40: "mount", + 41: "pivot_root", + 42: "nfsservctl", + 43: "statfs", + 44: "fstatfs", + 45: "truncate", + 46: "ftruncate", + 47: "fallocate", + 48: "faccessat", + 49: "chdir", + 50: "fchdir", + 51: "chroot", + 52: "fchmod", + 53: "fchmodat", + 54: "fchownat", + 55: "fchown", + 56: "openat", + 57: "close", + 58: "vhangup", + 59: "pipe2", + 60: "quotactl", + 61: "getdents64", + 62: "lseek", + 63: "read", + 64: "write", + 65: "readv", + 66: "writev", + 67: "pread64", + 68: "pwrite64", + 69: "preadv", + 70: "pwritev", + 71: "sendfile", + 72: "pselect6", + 73: "ppoll", + 74: "signalfd4", + 75: "vmsplice", + 76: "splice", + 77: "tee", + 78: "readlinkat", + 79: "newfstatat", + 80: "fstat", + 81: "sync", + 82: "fsync", + 83: "fdatasync", + 84: "sync_file_range", + 85: "timerfd_create", + 86: "timerfd_settime", + 87: "timerfd_gettime", + 88: "utimensat", + 89: "acct", + 90: "capget", + 91: "capset", + 92: "personality", + 93: "exit", + 94: "exit_group", + 95: "waitid", + 96: "set_tid_address", + 97: "unshare", + 98: "futex", + 99: "set_robust_list", + 100: "get_robust_list", + 101: "nanosleep", + 102: "getitimer", + 103: "setitimer", + 104: "kexec_load", + 105: "init_module", + 106: "delete_module", + 107: "timer_create", + 108: "timer_gettime", + 109: "timer_getoverrun", + 110: "timer_settime", + 111: "timer_delete", + 112: "clock_settime", + 113: "clock_gettime", + 114: "clock_getres", + 115: "clock_nanosleep", + 116: "syslog", + 117: "ptrace", + 118: "sched_setparam", + 119: "sched_setscheduler", + 120: "sched_getscheduler", + 121: "sched_getparam", + 122: "sched_setaffinity", + 123: "sched_getaffinity", + 124: "sched_yield", + 125: "sched_get_priority_max", + 126: "sched_get_priority_min", + 127: "sched_rr_get_interval", + 128: "restart_syscall", + 129: "kill", + 130: "tkill", + 131: "tgkill", + 132: "sigaltstack", + 133: "rt_sigsuspend", + 134: "rt_sigaction", + 135: "rt_sigprocmask", + 136: "rt_sigpending", + 137: "rt_sigtimedwait", + 138: "rt_sigqueueinfo", + 139: "rt_sigreturn", + 140: "setpriority", + 141: "getpriority", + 142: "reboot", + 143: "setregid", + 144: "setgid", + 145: "setreuid", + 146: "setuid", + 147: "setresuid", + 148: "getresuid", + 149: "setresgid", + 150: "getresgid", + 151: "setfsuid", + 152: "setfsgid", + 153: "times", + 154: "setpgid", + 155: "getpgid", + 156: "getsid", + 157: "setsid", + 158: "getgroups", + 159: "setgroups", + 160: "uname", + 161: "sethostname", + 162: "setdomainname", + 163: "getrlimit", + 164: "setrlimit", + 165: "getrusage", + 166: "umask", + 167: "prctl", + 168: "getcpu", + 169: "gettimeofday", + 170: "settimeofday", + 171: "adjtimex", + 172: "getpid", + 173: "getppid", + 174: "getuid", + 175: "geteuid", + 176: "getgid", + 177: "getegid", + 178: "gettid", + 179: "sysinfo", + 180: "mq_open", + 181: "mq_unlink", + 182: "mq_timedsend", + 183: "mq_timedreceive", + 184: "mq_notify", + 185: "mq_getsetattr", + 186: "msgget", + 187: "msgctl", + 188: "msgrcv", + 189: "msgsnd", + 190: "semget", + 191: "semctl", + 192: "semtimedop", + 193: "semop", + 194: "shmget", + 195: "shmctl", + 196: "shmat", + 197: "shmdt", + 198: "socket", + 199: "socketpair", + 200: "bind", + 201: "listen", + 202: "accept", + 203: "connect", + 204: "getsockname", + 205: "getpeername", + 206: "sendto", + 207: "recvfrom", + 208: "setsockopt", + 209: "getsockopt", + 210: "shutdown", + 211: "sendmsg", + 212: "recvmsg", + 213: "readahead", + 214: "brk", + 215: "munmap", + 216: "mremap", + 217: "add_key", + 218: "request_key", + 219: "keyctl", + 220: "clone", + 221: "execve", + 222: "mmap", + 223: "fadvise64", + 224: "swapon", + 225: "swapoff", + 226: "mprotect", + 227: "msync", + 228: "mlock", + 229: "munlock", + 230: "mlockall", + 231: "munlockall", + 232: "mincore", + 233: "madvise", + 234: "remap_file_pages", + 235: "mbind", + 236: "get_mempolicy", + 237: "set_mempolicy", + 238: "migrate_pages", + 239: "move_pages", + 240: "rt_tgsigqueueinfo", + 241: "perf_event_open", + 242: "accept4", + 259: "riscv_flush_icache", + 243: "recvmmsg", + 260: "wait4", + 261: "prlimit64", + 262: "fanotify_init", + 263: "fanotify_mark", + 264: "name_to_handle_at", + 265: "open_by_handle_at", + 266: "clock_adjtime", + 267: "syncfs", + 268: "setns", + 269: "sendmmsg", + 270: "process_vm_readv", + 271: "process_vm_writev", + 272: "kcmp", + 273: "finit_module", + 274: "sched_setattr", + 275: "sched_getattr", + 276: "renameat2", + 277: "seccomp", + 278: "getrandom", + 279: "memfd_create", + 280: "bpf", + 281: "execveat", + 282: "userfaultfd", + 283: "membarrier", + 284: "mlock2", + 285: "copy_file_range", + 286: "preadv2", + 287: "pwritev2", + 288: "pkey_mprotect", + 289: "pkey_alloc", + 290: "pkey_free", + 291: "statx", + 292: "io_pgetevents", + 293: "rseq", + 294: "kexec_file_load", + 424: "pidfd_send_signal", + 425: "io_uring_setup", + 426: "io_uring_enter", + 427: "io_uring_register", + 428: "open_tree", + 429: "move_mount", + 430: "fsopen", + 431: "fsconfig", + 432: "fsmount", + 433: "fspick", + 434: "pidfd_open", + 435: "clone3", + 436: "close_range", + 437: "openat2", + 438: "pidfd_getfd", + 439: "faccessat2", + 440: "process_madvise", + 441: "epoll_pwait2", + 442: "mount_setattr", + 443: "quotactl_fd", + 444: "landlock_create_ruleset", + 445: "landlock_add_rule", + 446: "landlock_restrict_self", + 447: "memfd_secret", + 448: "process_mrelease", + 449: "futex_waitv", + 450: "set_mempolicy_home_node", +} + +SOCKET_SYSCALL_NAMES = set(( + "socket", + "socketpair", + "connect", + "sendto", + "recvfrom", + "sendmsg", + "recvmsg", + "bind", + "listen", + "accept", + "getsockname", + "getpeername", + "getsockopt", + "setsockopt", + "shutdown", +)) diff --git a/ptrace/syscall/linux/x86_64.py b/ptrace/syscall/linux/x86_64.py new file mode 100644 index 0000000..bee5419 --- /dev/null +++ b/ptrace/syscall/linux/x86_64.py @@ -0,0 +1,399 @@ +# Linux kernel 5.8.0 on Intel Xeon E5-2670 +SYSCALL_NAMES = { + 0: "read", + 1: "write", + 2: "open", + 3: "close", + 4: "stat", + 5: "fstat", + 6: "lstat", + 7: "poll", + 8: "lseek", + 9: "mmap", + 10: "mprotect", + 11: "munmap", + 12: "brk", + 13: "rt_sigaction", + 14: "rt_sigprocmask", + 15: "rt_sigreturn", + 16: "ioctl", + 17: "pread64", + 18: "pwrite64", + 19: "readv", + 20: "writev", + 21: "access", + 22: "pipe", + 23: "select", + 24: "sched_yield", + 25: "mremap", + 26: "msync", + 27: "mincore", + 28: "madvise", + 29: "shmget", + 30: "shmat", + 31: "shmctl", + 32: "dup", + 33: "dup2", + 34: "pause", + 35: "nanosleep", + 36: "getitimer", + 37: "alarm", + 38: "setitimer", + 39: "getpid", + 40: "sendfile", + 41: "socket", + 42: "connect", + 43: "accept", + 44: "sendto", + 45: "recvfrom", + 46: "sendmsg", + 47: "recvmsg", + 48: "shutdown", + 49: "bind", + 50: "listen", + 51: "getsockname", + 52: "getpeername", + 53: "socketpair", + 54: "setsockopt", + 55: "getsockopt", + 56: "clone", + 57: "fork", + 58: "vfork", + 59: "execve", + 60: "exit", + 61: "wait4", + 62: "kill", + 63: "uname", + 64: "semget", + 65: "semop", + 66: "semctl", + 67: "shmdt", + 68: "msgget", + 69: "msgsnd", + 70: "msgrcv", + 71: "msgctl", + 72: "fcntl", + 73: "flock", + 74: "fsync", + 75: "fdatasync", + 76: "truncate", + 77: "ftruncate", + 78: "getdents", + 79: "getcwd", + 80: "chdir", + 81: "fchdir", + 82: "rename", + 83: "mkdir", + 84: "rmdir", + 85: "creat", + 86: "link", + 87: "unlink", + 88: "symlink", + 89: "readlink", + 90: "chmod", + 91: "fchmod", + 92: "chown", + 93: "fchown", + 94: "lchown", + 95: "umask", + 96: "gettimeofday", + 97: "getrlimit", + 98: "getrusage", + 99: "sysinfo", + 100: "times", + 101: "ptrace", + 102: "getuid", + 103: "syslog", + 104: "getgid", + 105: "setuid", + 106: "setgid", + 107: "geteuid", + 108: "getegid", + 109: "setpgid", + 110: "getppid", + 111: "getpgrp", + 112: "setsid", + 113: "setreuid", + 114: "setregid", + 115: "getgroups", + 116: "setgroups", + 117: "setresuid", + 118: "getresuid", + 119: "setresgid", + 120: "getresgid", + 121: "getpgid", + 122: "setfsuid", + 123: "setfsgid", + 124: "getsid", + 125: "capget", + 126: "capset", + 127: "rt_sigpending", + 128: "rt_sigtimedwait", + 129: "rt_sigqueueinfo", + 130: "rt_sigsuspend", + 131: "sigaltstack", + 132: "utime", + 133: "mknod", + 134: "uselib", + 135: "personality", + 136: "ustat", + 137: "statfs", + 138: "fstatfs", + 139: "sysfs", + 140: "getpriority", + 141: "setpriority", + 142: "sched_setparam", + 143: "sched_getparam", + 144: "sched_setscheduler", + 145: "sched_getscheduler", + 146: "sched_get_priority_max", + 147: "sched_get_priority_min", + 148: "sched_rr_get_interval", + 149: "mlock", + 150: "munlock", + 151: "mlockall", + 152: "munlockall", + 153: "vhangup", + 154: "modify_ldt", + 155: "pivot_root", + 156: "_sysctl", + 157: "prctl", + 158: "arch_prctl", + 159: "adjtimex", + 160: "setrlimit", + 161: "chroot", + 162: "sync", + 163: "acct", + 164: "settimeofday", + 165: "mount", + 166: "umount2", + 167: "swapon", + 168: "swapoff", + 169: "reboot", + 170: "sethostname", + 171: "setdomainname", + 172: "iopl", + 173: "ioperm", + 174: "create_module", + 175: "init_module", + 176: "delete_module", + 177: "get_kernel_syms", + 178: "query_module", + 179: "quotactl", + 180: "nfsservctl", + 181: "getpmsg", + 182: "putpmsg", + 183: "afs_syscall", + 184: "tuxcall", + 185: "security", + 186: "gettid", + 187: "readahead", + 188: "setxattr", + 189: "lsetxattr", + 190: "fsetxattr", + 191: "getxattr", + 192: "lgetxattr", + 193: "fgetxattr", + 194: "listxattr", + 195: "llistxattr", + 196: "flistxattr", + 197: "removexattr", + 198: "lremovexattr", + 199: "fremovexattr", + 200: "tkill", + 201: "time", + 202: "futex", + 203: "sched_setaffinity", + 204: "sched_getaffinity", + 205: "set_thread_area", + 206: "io_setup", + 207: "io_destroy", + 208: "io_getevents", + 209: "io_submit", + 210: "io_cancel", + 211: "get_thread_area", + 212: "lookup_dcookie", + 213: "epoll_create", + 214: "epoll_ctl_old", + 215: "epoll_wait_old", + 216: "remap_file_pages", + 217: "getdents64", + 218: "set_tid_address", + 219: "restart_syscall", + 220: "semtimedop", + 221: "fadvise64", + 222: "timer_create", + 223: "timer_settime", + 224: "timer_gettime", + 225: "timer_getoverrun", + 226: "timer_delete", + 227: "clock_settime", + 228: "clock_gettime", + 229: "clock_getres", + 230: "clock_nanosleep", + 231: "exit_group", + 232: "epoll_wait", + 233: "epoll_ctl", + 234: "tgkill", + 235: "utimes", + 236: "vserver", + 237: "mbind", + 238: "set_mempolicy", + 239: "get_mempolicy", + 240: "mq_open", + 241: "mq_unlink", + 242: "mq_timedsend", + 243: "mq_timedreceive", + 244: "mq_notify", + 245: "mq_getsetattr", + 246: "kexec_load", + 247: "waitid", + 248: "add_key", + 249: "request_key", + 250: "keyctl", + 251: "ioprio_set", + 252: "ioprio_get", + 253: "inotify_init", + 254: "inotify_add_watch", + 255: "inotify_rm_watch", + 256: "migrate_pages", + 257: "openat", + 258: "mkdirat", + 259: "mknodat", + 260: "fchownat", + 261: "futimesat", + 262: "newfstatat", + 263: "unlinkat", + 264: "renameat", + 265: "linkat", + 266: "symlinkat", + 267: "readlinkat", + 268: "fchmodat", + 269: "faccessat", + 270: "pselect6", + 271: "ppoll", + 272: "unshare", + 273: "set_robust_list", + 274: "get_robust_list", + 275: "splice", + 276: "tee", + 277: "sync_file_range", + 278: "vmsplice", + 279: "move_pages", + 280: "utimensat", + 281: "epoll_pwait", + 282: "signalfd", + 283: "timerfd", + 284: "eventfd", + 285: "fallocate", + 286: "timerfd_settime", + 287: "timerfd_gettime", + 288: "accept4", + 289: "signalfd4", + 290: "eventfd2", + 291: "epoll_create1", + 292: "dup3", + 293: "pipe2", + 294: "inotify_init1", + 295: "preadv", + 296: "pwritev", + 297: "rt_tgsigqueueinfo", + 298: "perf_event_open", + 299: "recvmmsg", + 300: "fanotify_init", + 301: "fanotify_mark", + 302: "prlimit64", + 303: "name_to_handle_at", + 304: "open_by_handle_at", + 305: "clock_adjtime", + 306: "syncfs", + 307: "sendmmsg", + 308: "setns", + 309: "getcpu", + 310: "process_vm_readv", + 311: "process_vm_writev", + 312: "kcmp", + 313: "finit_module", + 314: "sched_setattr", + 315: "sched_getattr", + 316: "renameat2", + 317: "seccomp", + 318: "getrandom", + 319: "memfd_create", + 320: "kexec_file_load", + 321: "bpf", + 322: "execveat", + 323: "userfaultfd", + 324: "membarrier", + 325: "mlock2", + 326: "copy_file_range", + 327: "preadv2", + 328: "pwritev2", + 329: "pkey_mprotect", + 330: "pkey_alloc", + 331: "pkey_free", + 332: "statx", + 333: "io_pgetevents", + 334: "rseq", + 424: "pidfd_send_signal", + 425: "io_uring_setup", + 426: "io_uring_enter", + 427: "io_uring_register", + 428: "open_tree", + 429: "move_mount", + 430: "fsopen", + 431: "fsconfig", + 432: "fsmount", + 433: "fspick", + 434: "pidfd_open", + 435: "clone3", + 436: "close_range", + 437: "openat2", + 438: "pidfd_getfd", + 439: "faccessat2", + 440: "process_madvise", + 512: "rt_sigaction", + 513: "rt_sigreturn", + 514: "ioctl", + 515: "readv", + 516: "writev", + 517: "recvfrom", + 518: "sendmsg", + 519: "recvmsg", + 520: "execve", + 521: "ptrace", + 522: "rt_sigpending", + 523: "rt_sigtimedwait", + 524: "rt_sigqueueinfo", + 525: "sigaltstack", + 526: "timer_create", + 527: "mq_notify", + 528: "kexec_load", + 529: "waitid", + 530: "set_robust_list", + 531: "get_robust_list", + 532: "vmsplice", + 533: "move_pages", + 534: "preadv", + 535: "pwritev", + 536: "rt_tgsigqueueinfo", + 537: "recvmmsg", + 538: "sendmmsg", + 539: "process_vm_readv", + 540: "process_vm_writev", + 541: "setsockopt", + 542: "getsockopt", + 543: "io_setup", + 544: "io_submit", + 545: "execveat", + 546: "preadv2", + 547: "pwritev2", +} + +SOCKET_SYSCALL_NAMES = set(( + "socket", "socketpair", "connect", + "sendto", "recvfrom", "sendmsg", "recvmsg", + "bind", "listen", "accept", + "getsockname", "getpeername", "getsockopt", "setsockopt", + "shutdown", +)) diff --git a/ptrace/syscall/linux_constants.py b/ptrace/syscall/linux_constants.py new file mode 100644 index 0000000..af521bf --- /dev/null +++ b/ptrace/syscall/linux_constants.py @@ -0,0 +1,32 @@ +from ptrace.syscall.posix_constants import SYSCALL_ARG_DICT + +SIGSET_SIZE = 64 +FD_SETSIZE = 1024 + +RLIMIT_RESOURCE = { + 0: "RLIMIT_CPU", + 1: "RLIMIT_FSIZE", + 2: "RLIMIT_DATA", + 3: "RLIMIT_STACK", + 4: "RLIMIT_CORE", + 5: "RLIMIT_RSS", + 6: "RLIMIT_NPROC", + 7: "RLIMIT_NOFILE", + 8: "RLIMIT_MEMLOCK", + 9: "RLIMIT_AS", + 10: "RLIMIT_LOCKS", + 11: "RLIMIT_SIGPENDING", + 12: "RLIMIT_MSGQUEUE", + 13: "RLIMIT_NICE", + 14: "RLIMIT_RTPRIO", + 15: "RLIMIT_NLIMITS", +} + +SIGPROCMASK_HOW = {0: "SIG_BLOCK", 1: "SIG_UNBLOCK", 2: "SIG_SETMASK"} + +SYSCALL_ARG_DICT.update({ + "getrlimit": {"resource": RLIMIT_RESOURCE}, + "setrlimit": {"resource": RLIMIT_RESOURCE}, + "sigprocmask": {"how": SIGPROCMASK_HOW}, + "rt_sigprocmask": {"how": SIGPROCMASK_HOW}, +}) diff --git a/ptrace/syscall/linux_struct.py b/ptrace/syscall/linux_struct.py new file mode 100644 index 0000000..8390be1 --- /dev/null +++ b/ptrace/syscall/linux_struct.py @@ -0,0 +1,63 @@ +from ctypes import (Structure, + c_char, c_short, c_int, c_uint, c_long, c_ulong) + +time_t = c_long +suseconds_t = c_long +rlim_t = c_long + + +class timeval(Structure): + _fields_ = ( + ("tv_sec", time_t), + ("tv_usec", suseconds_t), + ) + + +class timespec(Structure): + _fields_ = ( + ("tv_sec", time_t), + ("tv_nsec", c_long), + ) + + +class pollfd(Structure): + _fields_ = ( + ("fd", c_int), + ("events", c_short), + ("revents", c_short), + ) + + +class rlimit(Structure): + _fields_ = ( + ("rlim_cur", rlim_t), + ("rlim_max", rlim_t), + ) + + +class new_utsname(Structure): + _fields_ = ( + ("sysname", c_char * 65), + ("nodename", c_char * 65), + ("release", c_char * 65), + ("version", c_char * 65), + ("machine", c_char * 65), + ("domainname", c_char * 65), + ) + +# Arch depend + + +class user_desc(Structure): + _fields_ = ( + ("entry_number", c_uint), + ("base_addr", c_ulong), + ("limit", c_uint), + ("_bits_", c_char), + # unsigned int seg_32bit:1; + # unsigned int contents:2; + # unsigned int read_exec_only:1; + # unsigned int limit_in_pages:1; + # unsigned int seg_not_present:1; + # unsigned int useable:1; + ) diff --git a/ptrace/syscall/names.py b/ptrace/syscall/names.py new file mode 100644 index 0000000..b970989 --- /dev/null +++ b/ptrace/syscall/names.py @@ -0,0 +1,25 @@ +from ptrace.cpu_info import CPU_X86_64, CPU_I386, CPU_PPC64, CPU_PPC32, CPU_AARCH64, CPU_RISCV32, CPU_RISCV64 +from ptrace.os_tools import RUNNING_LINUX, RUNNING_FREEBSD +if RUNNING_LINUX: + if CPU_X86_64: + from ptrace.syscall.linux.x86_64 import SYSCALL_NAMES, SOCKET_SYSCALL_NAMES + elif CPU_I386: + from ptrace.syscall.linux.i386 import SYSCALL_NAMES, SOCKET_SYSCALL_NAMES + elif CPU_PPC64: + from ptrace.syscall.linux.powerpc64 import SYSCALL_NAMES, SOCKET_SYSCALL_NAMES + elif CPU_PPC32: + from ptrace.syscall.linux.powerpc32 import SYSCALL_NAMES, SOCKET_SYSCALL_NAMES + elif CPU_AARCH64: + from ptrace.syscall.linux.aarch64 import SYSCALL_NAMES, SOCKET_SYSCALL_NAMES + elif CPU_RISCV32: + from ptrace.syscall.linux.riscv32 import SYSCALL_NAMES, SOCKET_SYSCALL_NAMES + elif CPU_RISCV64: + from ptrace.syscall.linux.riscv64 import SYSCALL_NAMES, SOCKET_SYSCALL_NAMES + else: + raise NotImplementedError("Unsupported CPU architecture") + +elif RUNNING_FREEBSD: + from ptrace.syscall.freebsd_syscall import SYSCALL_NAMES, SOCKET_SYSCALL_NAMES +else: + SYSCALL_NAMES = {} + SOCKET_SYSCALL_NAMES = set() diff --git a/ptrace/syscall/posix_arg.py b/ptrace/syscall/posix_arg.py new file mode 100644 index 0000000..f13d3d1 --- /dev/null +++ b/ptrace/syscall/posix_arg.py @@ -0,0 +1,134 @@ +from ptrace.tools import readBits, formatBits +from ptrace.signames import signalName +from ptrace.ctypes_tools import uint2int + +# From /usr/include/bits/mman.h (Ubuntu Feisty, i386) +MMAP_PROT_BITMASK = ( + (1, "PROT_READ"), + (2, "PROT_WRITE"), + (4, "PROT_EXEC"), + (0x01000000, "PROT_GROWSDOWN"), + (0x02000000, "PROT_GROWSUP"), +) + + +def formatMmapProt(argument): + return formatBits(argument.value, MMAP_PROT_BITMASK, "PROT_NONE") + + +# From /usr/include/bits/mman.h (Ubuntu Feisty, i386) +ACCESS_MODE_BITMASK = ( + (1, "X_OK"), + (2, "W_OK"), + (4, "R_OK"), +) + + +def formatAccessMode(argument): + return formatBits(argument.value, ACCESS_MODE_BITMASK, "F_OK") + + +# From /usr/include/bits/fcntl.h (Ubuntu Feisty, i386) +OPEN_FLAGS_BITMASK = [ + (0o1, "O_WRONLY"), + (0o2, "O_RDWR"), + (0o100, "O_CREAT"), + (0o200, "O_EXCL"), + (0o400, "O_NOCTTY"), + (0o1000, "O_TRUNC"), + (0o2000, "O_APPEND"), + (0o4000, "O_NONBLOCK"), + (0o10000, "O_SYNC"), + (0o20000, "O_ASYNC"), + (0o40000, "O_DIRECT"), + (0o100000, "O_LARGEFILE"), + (0o200000, "O_DIRECTORY"), + (0o400000, "O_NOFOLLOW"), + (0o1000000, "O_NOATIME"), + (0o2000000, "O_CLOEXEC"), + (0o10000000, "O_PATH"), # Linux 2.6.39 + (0o20200000, "O_TMPFILE"), # Linux 3.11 +] + + +def formatOpenFlags(argument): + value = argument.value + flags = readBits(int(value), OPEN_FLAGS_BITMASK) + + # Add default access mode if neither of the others are present. + if not flags or flags[0] not in ("O_WRONLY", "O_RDWR"): + flags.insert(0, "O_RDONLY") + + text = "|".join(flags) + if value: + text = "%s (%s)" % (text, oct(argument.value)) + return text + + +OPEN_MODE_BITMASK = [ + (0o0400, "S_IRUSR"), + (0o0200, "S_IWUSR"), + (0o0100, "S_IXUSR"), + (0o0040, "S_IRGRP"), + (0o0020, "S_IWGRP"), + (0o0010, "S_IXGRP"), + (0o0004, "S_IROTH"), + (0o0002, "S_IWOTH"), + (0o0001, "S_IXOTH"), +] + + +def formatOpenMode(argument): + value = argument.value + flags = readBits(int(value), OPEN_MODE_BITMASK) + + text = "|".join(flags) + + # Overwrite text, add it depending on verbosity later + if value: + text = "%s" % oct(argument.value) + return text + + +CLONE_FLAGS_BITMASK = ( + (0x00000100, "CLONE_VM"), + (0x00000200, "CLONE_FS"), + (0x00000400, "CLONE_FILES"), + (0x00000800, "CLONE_SIGHAND"), + (0x00002000, "CLONE_PTRACE"), + (0x00004000, "CLONE_VFORK"), + (0x00008000, "CLONE_PARENT"), + (0x00010000, "CLONE_THREAD"), + (0x00020000, "CLONE_NEWNS"), + (0x00040000, "CLONE_SYSVSEM"), + (0x00080000, "CLONE_SETTLS"), + (0x00100000, "CLONE_PARENT_SETTID"), + (0x00200000, "CLONE_CHILD_CLEARTID"), + (0x00400000, "CLONE_DETACHED"), + (0x00800000, "CLONE_UNTRACED"), + (0x01000000, "CLONE_CHILD_SETTID"), + (0x02000000, "CLONE_STOPPED"), + (0x04000000, "CLONE_NEWUTS"), + (0x08000000, "CLONE_NEWIPC"), +) + + +def formatCloneFlags(argument): + flags = argument.value + bits = readBits(flags, CLONE_FLAGS_BITMASK) + signum = flags & 0xFF + if signum: + bits.insert(0, signalName(signum)) + if bits: + bits = "%s" % ("|".join(bits)) + return "<%s> (%s)" % (bits, str(flags)) + else: + return str(flags) + + +AT_FDCWD = -100 + + +def formatDirFd(argument): + value = uint2int(argument.value) + return "AT_FDCWD" if value == AT_FDCWD else str(value) diff --git a/ptrace/syscall/posix_constants.py b/ptrace/syscall/posix_constants.py new file mode 100644 index 0000000..ef77368 --- /dev/null +++ b/ptrace/syscall/posix_constants.py @@ -0,0 +1,62 @@ +from ptrace.syscall.socketcall_constants import ( + SOCKET_FAMILY, SOCKET_PROTOCOL, + SETSOCKOPT_LEVEL, SETSOCKOPT_OPTNAME) + +SYSCALL_ARG_DICT = { + "lseek": { + "origin": {0: "SEEK_SET", 1: "SEEK_CUR", 2: "SEEK_END"}, + }, + "futex": { + "op": { + 0: "FUTEX_WAIT", + 1: "FUTEX_WAKE", + 2: "FUTEX_FD", + 3: "FUTEX_REQUEUE", + 4: "FUTEX_CMP_REQUEUE", + 5: "FUTEX_WAKE_OP", + 6: "FUTEX_LOCK_PI", + 7: "FUTEX_UNLOCK_PI", + 8: "FUTEX_TRYLOCK_PI", + }, + }, + "fcntl": { + "cmd": { + 0: "F_DUPFD", + 1: "F_GETFD", + 2: "F_SETFD", + 3: "F_GETFL", + 4: "F_SETFL", + 5: "F_GETOWN", + 6: "F_SETOWN", + 7: "F_GETLK", + 8: "F_SETLK", + 9: "F_SETLKW", + }, + }, + "ipc": { + "call": { + 1: "SEMOP", + 2: "SEMGET", + 3: "SEMCTL", + 4: "SEMTIMEDOP", + 11: "MSGSND", + 12: "MSGRCV", + 13: "MSGGET", + 14: "MSGCTL", + 21: "SHMAT", + 22: "SHMDT", + 23: "SHMGET", + 24: "SHMCTL", + }, + }, + "socket": { + "domain": SOCKET_FAMILY, + "protocol": SOCKET_PROTOCOL, + }, + "getsockopt": { + "level": SETSOCKOPT_LEVEL, + "optname": SETSOCKOPT_OPTNAME, + }, +} + +SYSCALL_ARG_DICT["setsockopt"] = SYSCALL_ARG_DICT["getsockopt"] diff --git a/ptrace/syscall/prototypes.py b/ptrace/syscall/prototypes.py new file mode 100644 index 0000000..2e617da --- /dev/null +++ b/ptrace/syscall/prototypes.py @@ -0,0 +1,1772 @@ +# From Linux kernel source code +# include/linux/syscalls.h +# arch/i386/kernel/syscall_table.S +# arch/um/include/sysdep-i386/syscalls.h +# arch/um/sys-i386/sys_call_table.S + +ALIASES = { + "brk": ("break",), + "fadvise64": ("posix_fadvise",), + "fstatat64": ("fstatat",), + "getcwd": ("__getcwd",), + "mmap_pgoff": ("mmap", "mmap2",), + "pread64": ("pread",), + "prlimit64": ("prlimit",), + "pselect6": ("pselect",), + "pwrite64": ("pwrite",), +} + +# Name of arguments containing a filename or a path +FILENAME_ARGUMENTS = set( + ("filename", "pathname", "path", "oldname", "newname", "old", "new")) + +SYSCALL_PROTOTYPES = { + "accept": ("long", ( + ("int", "sockfd"), + ("struct sockaddr *", "addr"), + ("int *", "addrlen"), + )), + "accept4": ("long", ( + ("int", "sockfd"), + ("struct sockaddr *", "addr"), + ("int *", "addrlen"), + ("int", "flags"), + )), + "access": ("long", ( + ("const char *", "filename"), + ("int", "mode"), + )), + "acct": ("long", ( + ("const char *", "name"), + )), + "add_key": ("long", ( + ("const char *", "_type"), + ("const char *", "_description"), + ("const void *", "_payload"), + ("size_t", "plen"), + ("key_serial_t", "destringid"), + )), + "adjtimex": ("long", ( + ("struct timex *", "txc_p"), + )), + "alarm": ("long", ( + ("unsigned int", "seconds"), + )), + "bdflush": ("long", ( + ("int", "func"), + ("long", "data"), + )), + "bind": ("long", ( + ("int", "sockfd"), + ("struct sockaddr *", "addr"), + ("int", "addrlen"), + )), + "bpf": ("long", ( + ("int", "cmd"), + ("union bpf_attr *", "attr"), + ("unsigned int", "size"), + )), + "brk": ("long", ( + ("unsigned long", "brk"), + )), + "capget": ("long", ( + ("cap_user_header_t", "header"), + ("cap_user_data_t", "dataptr"), + )), + "capset": ("long", ( + ("cap_user_header_t", "header"), + ("const cap_user_data_t", "data"), + )), + "chdir": ("long", ( + ("const char *", "filename"), + )), + "chmod": ("long", ( + ("const char *", "filename"), + ("umode_t", "mode"), + )), + "chown": ("long", ( + ("const char *", "filename"), + ("uid_t", "user"), + ("gid_t", "group"), + )), + "chown16": ("long", ( + ("const char *", "filename"), + ("old_uid_t", "user"), + ("old_gid_t", "group"), + )), + "chroot": ("long", ( + ("const char *", "filename"), + )), + "clock_adjtime": ("long", ( + ("clockid_t", "which_clock"), + ("struct timex *", "tx"), + )), + "clock_getres": ("long", ( + ("clockid_t", "which_clock"), + ("struct timespec *", "tp"), + )), + "clock_gettime": ("long", ( + ("clockid_t", "which_clock"), + ("struct timespec *", "tp"), + )), + "clock_nanosleep": ("long", ( + ("clockid_t", "which_clock"), + ("int", "flags"), + ("const struct timespec *", "rqtp"), + ("struct timespec *", "rmtp"), + )), + "clock_settime": ("long", ( + ("clockid_t", "which_clock"), + ("const struct timespec *", "tp"), + )), + "clone": ("long", ( + ("unsigned long", "flags"), + ("unsigned long", "child_stack"), + ("int *", "ptid"), + ("int *", "ctid"), + ("unsigned long", "regs"), + )), + "close": ("long", ( + ("unsigned int", "fd"), + )), + "connect": ("long", ( + ("int", "sockfd"), + ("struct sockaddr *", "addr"), + ("int", "addrlen"), + )), + "copy_file_range": ("long", ( + ("int", "fd_in"), + ("loff_t *", "off_in"), + ("int", "fd_out"), + ("loff_t *", "off_out"), + ("size_t", "len"), + ("unsigned int", "flags"), + )), + "creat": ("long", ( + ("const char *", "pathname"), + ("umode_t", "mode"), + )), + "delete_module": ("long", ( + ("const char *", "name_user"), + ("unsigned int", "flags"), + )), + "dup": ("long", ( + ("unsigned int", "fildes"), + )), + "dup2": ("long", ( + ("unsigned int", "oldfd"), + ("unsigned int", "newfd"), + )), + "dup3": ("long", ( + ("unsigned int", "oldfd"), + ("unsigned int", "newfd"), + ("int", "flags"), + )), + "epoll_create": ("long", ( + ("int", "size"), + )), + "epoll_create1": ("long", ( + ("int", "flags"), + )), + "epoll_ctl": ("long", ( + ("int", "epfd"), + ("int", "op"), + ("int", "fd"), + ("struct epoll_event *", "event"), + )), + "epoll_pwait": ("long", ( + ("int", "epfd"), + ("struct epoll_event *", "events"), + ("int", "maxevents"), + ("int", "timeout"), + ("const sigset_t *", "sigmask"), + ("size_t", "sigsetsize"), + )), + "epoll_wait": ("long", ( + ("int", "epfd"), + ("struct epoll_event *", "events"), + ("int", "maxevents"), + ("int", "timeout"), + )), + "eventfd": ("long", ( + ("unsigned int", "count"), + )), + "eventfd2": ("long", ( + ("unsigned int", "count"), + ("int", "flags"), + )), + "execve": ("long", ( + ("const char *", "filename"), + ("const char *const *", "argv"), + ("const char *const *", "envp"), + )), + "execveat": ("long", ( + ("int", "dfd"), + ("const char *", "filename"), + ("const char *const *", "argv"), + ("const char *const *", "envp"), + ("int", "flags"), + )), + "exit": ("long", ( + ("int", "error_code"), + )), + "exit_group": ("long", ( + ("int", "error_code"), + )), + "faccessat": ("long", ( + ("int", "dfd"), + ("const char *", "filename"), + ("int", "mode"), + )), + "fadvise64": ("long", ( + ("int", "fd"), + ("loff_t", "offset"), + ("size_t", "len"), + ("int", "advice"), + )), + "fadvise64_64": ("long", ( + ("int", "fd"), + ("loff_t", "offset"), + ("loff_t", "len"), + ("int", "advice"), + )), + "fallocate": ("long", ( + ("int", "fd"), + ("int", "mode"), + ("loff_t", "offset"), + ("loff_t", "len"), + )), + "fanotify_init": ("long", ( + ("unsigned int", "flags"), + ("unsigned int", "event_f_flags"), + )), + "fanotify_mark": ("long", ( + ("int", "fanotify_fd"), + ("unsigned int", "flags"), + ("u64", "mask"), + ("int", "fd"), + ("const char *", "pathname"), + )), + "fchdir": ("long", ( + ("unsigned int", "fd"), + )), + "fchmod": ("long", ( + ("unsigned int", "fd"), + ("umode_t", "mode"), + )), + "fchmodat": ("long", ( + ("int", "dfd"), + ("const char *", "filename"), + ("umode_t", "mode"), + )), + "fchown": ("long", ( + ("unsigned int", "fd"), + ("uid_t", "user"), + ("gid_t", "group"), + )), + "fchown16": ("long", ( + ("unsigned int", "fd"), + ("old_uid_t", "user"), + ("old_gid_t", "group"), + )), + "fchownat": ("long", ( + ("int", "dfd"), + ("const char *", "filename"), + ("uid_t", "user"), + ("gid_t", "group"), + ("int", "flag"), + )), + "fcntl": ("long", ( + ("unsigned int", "fd"), + ("unsigned int", "cmd"), + ("unsigned long", "arg"), + )), + "fcntl64": ("long", ( + ("unsigned int", "fd"), + ("unsigned int", "cmd"), + ("unsigned long", "arg"), + )), + "fdatasync": ("long", ( + ("unsigned int", "fd"), + )), + "fgetxattr": ("long", ( + ("int", "fd"), + ("const char *", "name"), + ("void *", "value"), + ("size_t", "size"), + )), + "finit_module": ("long", ( + ("int", "fd"), + ("const char *", "uargs"), + ("int", "flags"), + )), + "flistxattr": ("long", ( + ("int", "fd"), + ("char *", "list"), + ("size_t", "size"), + )), + "flock": ("long", ( + ("unsigned int", "fd"), + ("unsigned int", "cmd"), + )), + "fork": ("long", ( + )), + "fremovexattr": ("long", ( + ("int", "fd"), + ("const char *", "name"), + )), + "fsetxattr": ("long", ( + ("int", "fd"), + ("const char *", "name"), + ("const void *", "value"), + ("size_t", "size"), + ("int", "flags"), + )), + "fstat": ("long", ( + ("unsigned int", "fd"), + ("struct __old_kernel_stat *", "statbuf"), + )), + "fstat64": ("long", ( + ("unsigned long", "fd"), + ("struct stat64 *", "statbuf"), + )), + "fstatat64": ("long", ( + ("int", "dfd"), + ("const char *", "filename"), + ("struct stat64 *", "statbuf"), + ("int", "flag"), + )), + "fstatfs": ("long", ( + ("unsigned int", "fd"), + ("struct statfs *", "buf"), + )), + "fstatfs64": ("long", ( + ("unsigned int", "fd"), + ("size_t", "sz"), + ("struct statfs64 *", "buf"), + )), + "fsync": ("long", ( + ("unsigned int", "fd"), + )), + "ftruncate": ("long", ( + ("unsigned int", "fd"), + ("unsigned long", "length"), + )), + "ftruncate64": ("long", ( + ("unsigned int", "fd"), + ("loff_t", "length"), + )), + "futex": ("long", ( + ("u32 *", "uaddr"), + ("int", "op"), + ("u32", "val"), + ("struct timespec *", "utime"), + ("u32 *", "uaddr2"), + ("u32", "val3"), + )), + "futimesat": ("long", ( + ("int", "dfd"), + ("const char *", "filename"), + ("struct timeval *", "utimes"), + )), + "get_mempolicy": ("long", ( + ("int *", "policy"), + ("unsigned long *", "nmask"), + ("unsigned long", "maxnode"), + ("unsigned long", "addr"), + ("unsigned long", "flags"), + )), + "get_robust_list": ("long", ( + ("int", "pid"), + ("struct robust_list_head * *", "head_ptr"), + ("size_t *", "len_ptr"), + )), + "getcpu": ("long", ( + ("unsigned *", "cpu"), + ("unsigned *", "node"), + ("struct getcpu_cache *", "cache"), + )), + "getcwd": ("long", ( + ("char *", "pathname"), + ("unsigned long", "size"), + )), + "getdents": ("long", ( + ("unsigned int", "fd"), + ("struct linux_dirent *", "dirent"), + ("unsigned int", "count"), + )), + "getdents64": ("long", ( + ("unsigned int", "fd"), + ("struct linux_dirent64 *", "dirent"), + ("unsigned int", "count"), + )), + "getegid": ("long", ( + )), + "getegid16": ("long", ( + )), + "geteuid": ("long", ( + )), + "geteuid16": ("long", ( + )), + "getgid": ("long", ( + )), + "getgid16": ("long", ( + )), + "getgroups": ("long", ( + ("int", "gidsetsize"), + ("gid_t *", "grouplist"), + )), + "getgroups16": ("long", ( + ("int", "gidsetsize"), + ("old_gid_t *", "grouplist"), + )), + "gethostname": ("long", ( + ("char *", "name"), + ("int", "len"), + )), + "getitimer": ("long", ( + ("int", "which"), + ("struct itimerval *", "value"), + )), + "getpeername": ("long", ( + ("int", "sockfd"), + ("struct sockaddr *", "addr"), + ("int *", "addrlen"), + )), + "getpgid": ("long", ( + ("pid_t", "pid"), + )), + "getpgrp": ("long", ( + )), + "getpid": ("long", ( + )), + "getppid": ("long", ( + )), + "getpriority": ("long", ( + ("int", "which"), + ("int", "who"), + )), + "getrandom": ("long", ( + ("char *", "buf"), + ("size_t", "count"), + ("unsigned int", "flags"), + )), + "getresgid": ("long", ( + ("gid_t *", "rgid"), + ("gid_t *", "egid"), + ("gid_t *", "sgid"), + )), + "getresgid16": ("long", ( + ("old_gid_t *", "rgid"), + ("old_gid_t *", "egid"), + ("old_gid_t *", "sgid"), + )), + "getresuid": ("long", ( + ("uid_t *", "ruid"), + ("uid_t *", "euid"), + ("uid_t *", "suid"), + )), + "getresuid16": ("long", ( + ("old_uid_t *", "ruid"), + ("old_uid_t *", "euid"), + ("old_uid_t *", "suid"), + )), + "getrlimit": ("long", ( + ("unsigned int", "resource"), + ("struct rlimit *", "rlim"), + )), + "getrusage": ("long", ( + ("int", "who"), + ("struct rusage *", "ru"), + )), + "getsid": ("long", ( + ("pid_t", "pid"), + )), + "getsockname": ("long", ( + ("int", "sockfd"), + ("struct sockaddr *", "addr"), + ("int *", "addrlen"), + )), + "getsockopt": ("long", ( + ("int", "fd"), + ("int", "level"), + ("int", "optname"), + ("char *", "optval"), + ("int *", "optlen"), + )), + "gettid": ("long", ( + )), + "gettimeofday": ("long", ( + ("struct timeval *", "tv"), + ("struct timezone *", "tz"), + )), + "getuid": ("long", ( + )), + "getuid16": ("long", ( + )), + "getxattr": ("long", ( + ("const char *", "path"), + ("const char *", "name"), + ("void *", "value"), + ("size_t", "size"), + )), + "init_module": ("long", ( + ("void *", "umod"), + ("unsigned long", "len"), + ("const char *", "uargs"), + )), + "inotify_add_watch": ("long", ( + ("int", "fd"), + ("const char *", "path"), + ("u32", "mask"), + )), + "inotify_init": ("long", ( + )), + "inotify_init1": ("long", ( + ("int", "flags"), + )), + "inotify_rm_watch": ("long", ( + ("int", "fd"), + ("__s32", "wd"), + )), + "io_cancel": ("long", ( + ("aio_context_t", "ctx_id"), + ("struct iocb *", "iocb"), + ("struct io_event *", "result"), + )), + "io_destroy": ("long", ( + ("aio_context_t", "ctx"), + )), + "io_getevents": ("long", ( + ("aio_context_t", "ctx_id"), + ("long", "min_nr"), + ("long", "nr"), + ("struct io_event *", "events"), + ("struct timespec *", "timeout"), + )), + "io_setup": ("long", ( + ("unsigned", "nr_reqs"), + ("aio_context_t *", "ctx"), + )), + "io_submit": ("long", ( + ("aio_context_t", "ctx_id"), + ("long", "nr"), + ("struct iocb * *", "iocbpp"), + )), + "ioctl": ("long", ( + ("unsigned int", "fd"), + ("unsigned int", "cmd"), + ("unsigned long", "arg"), + )), + "ioperm": ("long", ( + ("unsigned long", "from"), + ("unsigned long", "num"), + ("int", "on"), + )), + "ioprio_get": ("long", ( + ("int", "which"), + ("int", "who"), + )), + "ioprio_set": ("long", ( + ("int", "which"), + ("int", "who"), + ("int", "ioprio"), + )), + "ipc": ("long", ( + ("unsigned int", "call"), + ("int", "first"), + ("unsigned long", "second"), + ("unsigned long", "third"), + ("void *", "ptr"), + ("long", "fifth"), + )), + "kcmp": ("long", ( + ("pid_t", "pid1"), + ("pid_t", "pid2"), + ("int", "type"), + ("unsigned long", "idx1"), + ("unsigned long", "idx2"), + )), + "kexec_file_load": ("long", ( + ("int", "kernel_fd"), + ("int", "initrd_fd"), + ("unsigned long", "cmdline_len"), + ("const char *", "cmdline_ptr"), + ("unsigned long", "flags"), + )), + "kexec_load": ("long", ( + ("unsigned long", "entry"), + ("unsigned long", "nr_segments"), + ("struct kexec_segment *", "segments"), + ("unsigned long", "flags"), + )), + "keyctl": ("long", ( + ("int", "cmd"), + ("unsigned long", "arg2"), + ("unsigned long", "arg3"), + ("unsigned long", "arg4"), + ("unsigned long", "arg5"), + )), + "kill": ("long", ( + ("int", "pid"), + ("int", "sig"), + )), + "lchown": ("long", ( + ("const char *", "filename"), + ("uid_t", "user"), + ("gid_t", "group"), + )), + "lchown16": ("long", ( + ("const char *", "filename"), + ("old_uid_t", "user"), + ("old_gid_t", "group"), + )), + "lgetxattr": ("long", ( + ("const char *", "path"), + ("const char *", "name"), + ("void *", "value"), + ("size_t", "size"), + )), + "link": ("long", ( + ("const char *", "oldname"), + ("const char *", "newname"), + )), + "linkat": ("long", ( + ("int", "olddfd"), + ("const char *", "oldname"), + ("int", "newdfd"), + ("const char *", "newname"), + ("int", "flags"), + )), + "listen": ("long", ( + ("int", "sockfd"), + ("int", "backlog"), + )), + "listxattr": ("long", ( + ("const char *", "path"), + ("char *", "list"), + ("size_t", "size"), + )), + "llistxattr": ("long", ( + ("const char *", "path"), + ("char *", "list"), + ("size_t", "size"), + )), + "llseek": ("long", ( + ("unsigned int", "fd"), + ("unsigned long", "offset_high"), + ("unsigned long", "offset_low"), + ("loff_t *", "result"), + ("unsigned int", "whence"), + )), + "lookup_dcookie": ("long", ( + ("u64", "cookie64"), + ("char *", "buf"), + ("size_t", "len"), + )), + "lremovexattr": ("long", ( + ("const char *", "path"), + ("const char *", "name"), + )), + "lseek": ("long", ( + ("unsigned int", "fd"), + ("off_t", "offset"), + ("unsigned int", "whence"), + )), + "lsetxattr": ("long", ( + ("const char *", "path"), + ("const char *", "name"), + ("const void *", "value"), + ("size_t", "size"), + ("int", "flags"), + )), + "lstat": ("long", ( + ("const char *", "filename"), + ("struct __old_kernel_stat *", "statbuf"), + )), + "lstat64": ("long", ( + ("const char *", "filename"), + ("struct stat64 *", "statbuf"), + )), + "madvise": ("long", ( + ("unsigned long", "start"), + ("size_t", "len"), + ("int", "behavior"), + )), + "mbind": ("long", ( + ("unsigned long", "start"), + ("unsigned long", "len"), + ("unsigned long", "mode"), + ("const unsigned long *", "nmask"), + ("unsigned long", "maxnode"), + ("unsigned", "flags"), + )), + "membarrier": ("long", ( + ("int", "cmd"), + ("int", "flags"), + )), + "memfd_create": ("long", ( + ("const char *", "uname_ptr"), + ("unsigned int", "flags"), + )), + "migrate_pages": ("long", ( + ("pid_t", "pid"), + ("unsigned long", "maxnode"), + ("const unsigned long *", "from"), + ("const unsigned long *", "to"), + )), + "mincore": ("long", ( + ("unsigned long", "start"), + ("size_t", "len"), + ("unsigned char *", "vec"), + )), + "mkdir": ("long", ( + ("const char *", "pathname"), + ("umode_t", "mode"), + )), + "mkdirat": ("long", ( + ("int", "dfd"), + ("const char *", "pathname"), + ("umode_t", "mode"), + )), + "mknod": ("long", ( + ("const char *", "filename"), + ("umode_t", "mode"), + ("unsigned", "dev"), + )), + "mknodat": ("long", ( + ("int", "dfd"), + ("const char *", "filename"), + ("umode_t", "mode"), + ("unsigned", "dev"), + )), + "mlock": ("long", ( + ("unsigned long", "start"), + ("size_t", "len"), + )), + "mlock2": ("long", ( + ("unsigned long", "start"), + ("size_t", "len"), + ("int", "flags"), + )), + "mlockall": ("long", ( + ("int", "flags"), + )), + "mmap_pgoff": ("long", ( + ("unsigned long", "addr"), + ("unsigned long", "len"), + ("unsigned long", "prot"), + ("unsigned long", "flags"), + ("unsigned long", "fd"), + ("unsigned long", "pgoff"), + )), + "mount": ("long", ( + ("char *", "dev_name"), + ("char *", "dir_name"), + ("char *", "type"), + ("unsigned long", "flags"), + ("void *", "data"), + )), + "move_pages": ("long", ( + ("pid_t", "pid"), + ("unsigned long", "nr_pages"), + ("const void * *", "pages"), + ("const int *", "nodes"), + ("int *", "status"), + ("int", "flags"), + )), + "mprotect": ("long", ( + ("unsigned long", "start"), + ("size_t", "len"), + ("unsigned long", "prot"), + )), + "mq_getsetattr": ("long", ( + ("mqd_t", "mqdes"), + ("const struct mq_attr *", "mqstat"), + ("struct mq_attr *", "omqstat"), + )), + "mq_notify": ("long", ( + ("mqd_t", "mqdes"), + ("const struct sigevent *", "notification"), + )), + "mq_open": ("long", ( + ("const char *", "name"), + ("int", "oflag"), + ("umode_t", "mode"), + ("struct mq_attr *", "attr"), + )), + "mq_timedreceive": ("long", ( + ("mqd_t", "mqdes"), + ("char *", "msg_ptr"), + ("size_t", "msg_len"), + ("unsigned int *", "msg_prio"), + ("const struct timespec *", "abs_timeout"), + )), + "mq_timedsend": ("long", ( + ("mqd_t", "mqdes"), + ("const char *", "msg_ptr"), + ("size_t", "msg_len"), + ("unsigned int", "msg_prio"), + ("const struct timespec *", "abs_timeout"), + )), + "mq_unlink": ("long", ( + ("const char *", "name"), + )), + "mremap": ("long", ( + ("unsigned long", "addr"), + ("unsigned long", "old_len"), + ("unsigned long", "new_len"), + ("unsigned long", "flags"), + ("unsigned long", "new_addr"), + )), + "msgctl": ("long", ( + ("int", "msqid"), + ("int", "cmd"), + ("struct msqid_ds *", "buf"), + )), + "msgget": ("long", ( + ("key_t", "key"), + ("int", "msgflg"), + )), + "msgrcv": ("long", ( + ("int", "msqid"), + ("struct msgbuf *", "msgp"), + ("size_t", "msgsz"), + ("long", "msgtyp"), + ("int", "msgflg"), + )), + "msgsnd": ("long", ( + ("int", "msqid"), + ("struct msgbuf *", "msgp"), + ("size_t", "msgsz"), + ("int", "msgflg"), + )), + "msync": ("long", ( + ("unsigned long", "start"), + ("size_t", "len"), + ("int", "flags"), + )), + "munlock": ("long", ( + ("unsigned long", "start"), + ("size_t", "len"), + )), + "munlockall": ("long", ( + )), + "munmap": ("long", ( + ("unsigned long", "addr"), + ("size_t", "len"), + )), + "name_to_handle_at": ("long", ( + ("int", "dirfd"), + ("const char *", "name"), + ("struct file_handle *", "handle"), + ("int *", "mnt_id"), + ("int", "flag"), + )), + "nanosleep": ("long", ( + ("struct timespec *", "rqtp"), + ("struct timespec *", "rmtp"), + )), + "newfstat": ("long", ( + ("unsigned int", "fd"), + ("struct stat *", "statbuf"), + )), + "newfstatat": ("long", ( + ("int", "dfd"), + ("const char *", "filename"), + ("struct stat *", "statbuf"), + ("int", "flag"), + )), + "newlstat": ("long", ( + ("const char *", "filename"), + ("struct stat *", "statbuf"), + )), + "newstat": ("long", ( + ("const char *", "filename"), + ("struct stat *", "statbuf"), + )), + "newuname": ("long", ( + ("struct new_utsname *", "name"), + )), + "ni_syscall": ("long", ( + )), + "nice": ("long", ( + ("int", "increment"), + )), + "old_getrlimit": ("long", ( + ("unsigned int", "resource"), + ("struct rlimit *", "rlim"), + )), + "old_mmap": ("long", ( + ("struct mmap_arg_struct *", "arg"), + )), + "old_readdir": ("long", ( + ("unsigned int", "fd"), + ("struct old_linux_dirent *", "dirp"), + ("unsigned int", "count"), + )), + "old_select": ("long", ( + ("struct sel_arg_struct *", "arg"), + )), + "oldumount": ("long", ( + ("char *", "name"), + )), + "olduname": ("long", ( + ("struct oldold_utsname *", "buf"), + )), + "open": ("long", ( + ("const char *", "filename"), + ("int", "flags"), + ("umode_t", "mode"), + )), + "open_by_handle_at": ("long", ( + ("int", "mount_fd"), + ("struct file_handle *", "handle"), + ("int", "flags"), + )), + "openat": ("long", ( + ("int", "dirfd"), + ("const char *", "filename"), + ("int", "flags"), + ("umode_t", "mode"), + )), + "pause": ("long", ( + )), + "pciconfig_iobase": ("long", ( + ("long", "which"), + ("unsigned long", "bus"), + ("unsigned long", "devfn"), + )), + "pciconfig_read": ("long", ( + ("unsigned long", "bus"), + ("unsigned long", "dfn"), + ("unsigned long", "off"), + ("unsigned long", "len"), + ("void *", "buf"), + )), + "pciconfig_write": ("long", ( + ("unsigned long", "bus"), + ("unsigned long", "dfn"), + ("unsigned long", "off"), + ("unsigned long", "len"), + ("void *", "buf"), + )), + "perf_event_open": ("long", ( + ("struct perf_event_attr *", "attr_uptr"), + ("pid_t", "pid"), + ("int", "cpu"), + ("int", "group_fd"), + ("unsigned long", "flags"), + )), + "personality": ("long", ( + ("unsigned int", "personality"), + )), + "pipe": ("long", ( + ("int[2]", "fildes"), + )), + "pipe2": ("long", ( + ("int[2]", "fildes"), + ("int", "flags"), + )), + "pivot_root": ("long", ( + ("const char *", "new_root"), + ("const char *", "put_old"), + )), + "poll": ("long", ( + ("struct pollfd *", "ufds"), + ("unsigned int", "nfds"), + ("int", "timeout"), + )), + "ppoll": ("long", ( + ("struct pollfd *", "fds"), + ("unsigned int", "nfds"), + ("struct timespec *", "tmo_p"), + ("const sigset_t *", "sigmask"), + ("size_t", ""), + )), + "prctl": ("long", ( + ("int", "option"), + ("unsigned long", "arg2"), + ("unsigned long", "arg3"), + ("unsigned long", "arg4"), + ("unsigned long", "arg5"), + )), + "pread64": ("long", ( + ("unsigned int", "fd"), + ("char *", "buf"), + ("size_t", "count"), + ("loff_t", "pos"), + )), + "preadv": ("long", ( + ("unsigned long", "fd"), + ("const struct iovec *", "vec"), + ("unsigned long", "vlen"), + ("unsigned long", "pos_l"), + ("unsigned long", "pos_h"), + )), + "prlimit64": ("long", ( + ("pid_t", "pid"), + ("unsigned int", "resource"), + ("const struct rlimit64 *", "new_rlim"), + ("struct rlimit64 *", "old_rlim"), + )), + "process_vm_readv": ("long", ( + ("pid_t", "pid"), + ("const struct iovec *", "lvec"), + ("unsigned long", "liovcnt"), + ("const struct iovec *", "rvec"), + ("unsigned long", "riovcnt"), + ("unsigned long", "flags"), + )), + "process_vm_writev": ("long", ( + ("pid_t", "pid"), + ("const struct iovec *", "lvec"), + ("unsigned long", "liovcnt"), + ("const struct iovec *", "rvec"), + ("unsigned long", "riovcnt"), + ("unsigned long", "flags"), + )), + "pselect6": ("long", ( + ("int", "nfds"), + ("fd_set *", "readfds"), + ("fd_set *", "writefds"), + ("fd_set *", "exceptfds"), + ("struct timespec *", "timeout"), + ("void *", "sigmask"), + )), + "ptrace": ("long", ( + ("long", "request"), + ("long", "pid"), + ("unsigned long", "addr"), + ("unsigned long", "data"), + )), + "pwrite64": ("long", ( + ("unsigned int", "fd"), + ("const char *", "buf"), + ("size_t", "count"), + ("loff_t", "pos"), + )), + "pwritev": ("long", ( + ("unsigned long", "fd"), + ("const struct iovec *", "vec"), + ("unsigned long", "vlen"), + ("unsigned long", "pos_l"), + ("unsigned long", "pos_h"), + )), + "quotactl": ("long", ( + ("unsigned int", "cmd"), + ("const char *", "special"), + ("qid_t", "id"), + ("void *", "addr"), + )), + "read": ("long", ( + ("unsigned int", "fd"), + ("char *", "buf"), + ("size_t", "count"), + )), + "readahead": ("long", ( + ("int", "fd"), + ("loff_t", "offset"), + ("size_t", "count"), + )), + "readlink": ("long", ( + ("const char *", "path"), + ("char *", "buf"), + ("int", "bufsiz"), + )), + "readlinkat": ("long", ( + ("int", "dfd"), + ("const char *", "path"), + ("char *", "buf"), + ("int", "bufsiz"), + )), + "readv": ("long", ( + ("unsigned long", "fd"), + ("const struct iovec *", "vec"), + ("unsigned long", "vlen"), + )), + "reboot": ("long", ( + ("int", "magic1"), + ("int", "magic2"), + ("unsigned int", "cmd"), + ("void *", "arg"), + )), + "recv": ("long", ( + ("int", "sockfd"), + ("void *", "buf"), + ("size_t", "len"), + ("unsigned", "flags"), + )), + "recvfrom": ("long", ( + ("int", "sockfd"), + ("void *", "buf"), + ("size_t", "len"), + ("unsigned", "flags"), + ("struct sockaddr *", "src_addr"), + ("int *", "addrlen"), + )), + "recvmmsg": ("long", ( + ("int", "fd"), + ("struct mmsghdr *", "msg"), + ("unsigned int", "vlen"), + ("unsigned", "flags"), + ("struct timespec *", "timeout"), + )), + "recvmsg": ("long", ( + ("int", "fd"), + ("struct user_msghdr *", "msg"), + ("unsigned", "flags"), + )), + "remap_file_pages": ("long", ( + ("unsigned long", "start"), + ("unsigned long", "size"), + ("unsigned long", "prot"), + ("unsigned long", "pgoff"), + ("unsigned long", "flags"), + )), + "removexattr": ("long", ( + ("const char *", "path"), + ("const char *", "name"), + )), + "rename": ("long", ( + ("const char *", "oldname"), + ("const char *", "newname"), + )), + "renameat": ("long", ( + ("int", "olddfd"), + ("const char *", "oldname"), + ("int", "newdfd"), + ("const char *", "newname"), + )), + "renameat2": ("long", ( + ("int", "olddfd"), + ("const char *", "oldname"), + ("int", "newdfd"), + ("const char *", "newname"), + ("unsigned int", "flags"), + )), + "request_key": ("long", ( + ("const char *", "_type"), + ("const char *", "_description"), + ("const char *", "_callout_info"), + ("key_serial_t", "destringid"), + )), + "restart_syscall": ("long", ( + )), + "rmdir": ("long", ( + ("const char *", "pathname"), + )), + "rt_sigaction": ("long", ( + ("int", "signum"), + ("const struct sigaction *", "act"), + ("struct sigaction *", "oldact"), + ("size_t", ""), + )), + "rt_sigpending": ("long", ( + ("sigset_t *", "set"), + ("size_t", "sigsetsize"), + )), + "rt_sigprocmask": ("long", ( + ("int", "how"), + ("sigset_t *", "set"), + ("sigset_t *", "oset"), + ("size_t", "sigsetsize"), + )), + "rt_sigqueueinfo": ("long", ( + ("int", "pid"), + ("int", "sig"), + ("siginfo_t *", "uinfo"), + )), + "rt_sigsuspend": ("long", ( + ("sigset_t *", "unewset"), + ("size_t", "sigsetsize"), + )), + "rt_sigtimedwait": ("long", ( + ("const sigset_t *", "uthese"), + ("siginfo_t *", "uinfo"), + ("const struct timespec *", "uts"), + ("size_t", "sigsetsize"), + )), + "rt_tgsigqueueinfo": ("long", ( + ("pid_t", "tgid"), + ("pid_t", "pid"), + ("int", "sig"), + ("siginfo_t *", "uinfo"), + )), + "sched_get_priority_max": ("long", ( + ("int", "policy"), + )), + "sched_get_priority_min": ("long", ( + ("int", "policy"), + )), + "sched_getaffinity": ("long", ( + ("pid_t", "pid"), + ("unsigned int", "len"), + ("unsigned long *", "user_mask_ptr"), + )), + "sched_getattr": ("long", ( + ("pid_t", "pid"), + ("struct sched_attr *", "attr"), + ("unsigned int", "size"), + ("unsigned int", "flags"), + )), + "sched_getparam": ("long", ( + ("pid_t", "pid"), + ("struct sched_param *", "param"), + )), + "sched_getscheduler": ("long", ( + ("pid_t", "pid"), + )), + "sched_rr_get_interval": ("long", ( + ("pid_t", "pid"), + ("struct timespec *", "interval"), + )), + "sched_setaffinity": ("long", ( + ("pid_t", "pid"), + ("unsigned int", "len"), + ("unsigned long *", "user_mask_ptr"), + )), + "sched_setattr": ("long", ( + ("pid_t", "pid"), + ("struct sched_attr *", "attr"), + ("unsigned int", "flags"), + )), + "sched_setparam": ("long", ( + ("pid_t", "pid"), + ("struct sched_param *", "param"), + )), + "sched_setscheduler": ("long", ( + ("pid_t", "pid"), + ("int", "policy"), + ("struct sched_param *", "param"), + )), + "sched_yield": ("long", ( + )), + "seccomp": ("long", ( + ("unsigned int", "op"), + ("unsigned int", "flags"), + ("const char *", "uargs"), + )), + "select": ("long", ( + ("int", "nfds"), + ("fd_set *", "readfds"), + ("fd_set *", "writefds"), + ("fd_set *", "errorfds"), + ("struct timeval *", "timeout"), + )), + "semctl": ("long", ( + ("int", "semid"), + ("int", "semnum"), + ("int", "cmd"), + ("unsigned long", "arg"), + )), + "semget": ("long", ( + ("key_t", "key"), + ("int", "nsems"), + ("int", "semflg"), + )), + "semop": ("long", ( + ("int", "semid"), + ("struct sembuf *", "sops"), + ("unsigned", "nsops"), + )), + "semtimedop": ("long", ( + ("int", "semid"), + ("struct sembuf *", "sops"), + ("unsigned", "nsops"), + ("const struct timespec *", "timeout"), + )), + "send": ("long", ( + ("int", "sockfd"), + ("void *", "buf"), + ("size_t", "len"), + ("unsigned", "flags"), + )), + "sendfile": ("long", ( + ("int", "out_fd"), + ("int", "in_fd"), + ("off_t *", "offset"), + ("size_t", "count"), + )), + "sendfile64": ("long", ( + ("int", "out_fd"), + ("int", "in_fd"), + ("loff_t *", "offset"), + ("size_t", "count"), + )), + "sendmmsg": ("long", ( + ("int", "fd"), + ("struct mmsghdr *", "msg"), + ("unsigned int", "vlen"), + ("unsigned", "flags"), + )), + "sendmsg": ("long", ( + ("int", "fd"), + ("struct user_msghdr *", "msg"), + ("unsigned", "flags"), + )), + "sendto": ("long", ( + ("int", "sockfd"), + ("void *", "buf"), + ("size_t", "len"), + ("unsigned", "flags"), + ("struct sockaddr *", "dest_addr"), + ("int", "addrlen"), + )), + "set_mempolicy": ("long", ( + ("int", "mode"), + ("const unsigned long *", "nmask"), + ("unsigned long", "maxnode"), + )), + "set_robust_list": ("long", ( + ("struct robust_list_head *", "head"), + ("size_t", "len"), + )), + "set_tid_address": ("long", ( + ("int *", "tidptr"), + )), + "setdomainname": ("long", ( + ("char *", "name"), + ("int", "len"), + )), + "setfsgid": ("long", ( + ("gid_t", "gid"), + )), + "setfsgid16": ("long", ( + ("old_gid_t", "gid"), + )), + "setfsuid": ("long", ( + ("uid_t", "uid"), + )), + "setfsuid16": ("long", ( + ("old_uid_t", "uid"), + )), + "setgid": ("long", ( + ("gid_t", "gid"), + )), + "setgid16": ("long", ( + ("old_gid_t", "gid"), + )), + "setgroups": ("long", ( + ("int", "gidsetsize"), + ("gid_t *", "grouplist"), + )), + "setgroups16": ("long", ( + ("int", "gidsetsize"), + ("old_gid_t *", "grouplist"), + )), + "sethostname": ("long", ( + ("char *", "name"), + ("int", "len"), + )), + "setitimer": ("long", ( + ("int", "which"), + ("struct itimerval *", "value"), + ("struct itimerval *", "ovalue"), + )), + "setns": ("long", ( + ("int", "fd"), + ("int", "nstype"), + )), + "setpgid": ("long", ( + ("pid_t", "pid"), + ("pid_t", "pgid"), + )), + "setpriority": ("long", ( + ("int", "which"), + ("int", "who"), + ("int", "niceval"), + )), + "setregid": ("long", ( + ("gid_t", "rgid"), + ("gid_t", "egid"), + )), + "setregid16": ("long", ( + ("old_gid_t", "rgid"), + ("old_gid_t", "egid"), + )), + "setresgid": ("long", ( + ("gid_t", "rgid"), + ("gid_t", "egid"), + ("gid_t", "sgid"), + )), + "setresgid16": ("long", ( + ("old_gid_t", "rgid"), + ("old_gid_t", "egid"), + ("old_gid_t", "sgid"), + )), + "setresuid": ("long", ( + ("uid_t", "ruid"), + ("uid_t", "euid"), + ("uid_t", "suid"), + )), + "setresuid16": ("long", ( + ("old_uid_t", "ruid"), + ("old_uid_t", "euid"), + ("old_uid_t", "suid"), + )), + "setreuid": ("long", ( + ("uid_t", "ruid"), + ("uid_t", "euid"), + )), + "setreuid16": ("long", ( + ("old_uid_t", "ruid"), + ("old_uid_t", "euid"), + )), + "setrlimit": ("long", ( + ("unsigned int", "resource"), + ("struct rlimit *", "rlim"), + )), + "setsid": ("long", ( + )), + "setsockopt": ("long", ( + ("int", "fd"), + ("int", "level"), + ("int", "optname"), + ("char *", "optval"), + ("int", "optlen"), + )), + "settimeofday": ("long", ( + ("struct timeval *", "tv"), + ("struct timezone *", "tz"), + )), + "setuid": ("long", ( + ("uid_t", "uid"), + )), + "setuid16": ("long", ( + ("old_uid_t", "uid"), + )), + "setxattr": ("long", ( + ("const char *", "path"), + ("const char *", "name"), + ("const void *", "value"), + ("size_t", "size"), + ("int", "flags"), + )), + "sgetmask": ("long", ( + )), + "shmat": ("long", ( + ("int", "shmid"), + ("char *", "shmaddr"), + ("int", "shmflg"), + )), + "shmctl": ("long", ( + ("int", "shmid"), + ("int", "cmd"), + ("struct shmid_ds *", "buf"), + )), + "shmdt": ("long", ( + ("char *", "shmaddr"), + )), + "shmget": ("long", ( + ("key_t", "key"), + ("size_t", "size"), + ("int", "flag"), + )), + "shutdown": ("long", ( + ("int", "sockfd"), + ("int", "how"), + )), + "sigaction": ("long", ( + ("int", "signum"), + ("const struct old_sigaction *", "act"), + ("struct old_sigaction *", "oldact"), + )), + "sigaltstack": ("long", ( + ("const struct sigaltstack *", "uss"), + ("struct sigaltstack *", "uoss"), + )), + "signal": ("long", ( + ("int", "sig"), + ("__sighandler_t", "handler"), + )), + "signalfd": ("long", ( + ("int", "ufd"), + ("sigset_t *", "user_mask"), + ("size_t", "sizemask"), + )), + "signalfd4": ("long", ( + ("int", "ufd"), + ("sigset_t *", "user_mask"), + ("size_t", "sizemask"), + ("int", "flags"), + )), + "sigpending": ("long", ( + ("old_sigset_t *", "set"), + )), + "sigprocmask": ("long", ( + ("int", "how"), + ("old_sigset_t *", "set"), + ("old_sigset_t *", "oset"), + )), + "sigsuspend": ("long", ( + ("int", "unused1"), + ("int", "unused2"), + ("old_sigset_t", "mask"), + )), + "socket": ("long", ( + ("int", "domain"), + ("int", "type"), + ("int", "protocol"), + )), + "socketcall": ("long", ( + ("int", "call"), + ("unsigned long *", "args"), + )), + "socketpair": ("long", ( + ("int", "domain"), + ("int", "type"), + ("int", "protocol"), + ("int *", "sv"), + )), + "splice": ("long", ( + ("int", "fd_in"), + ("loff_t *", "off_in"), + ("int", "fd_out"), + ("loff_t *", "off_out"), + ("size_t", "len"), + ("unsigned int", "flags"), + )), + "spu_create": ("long", ( + ("const char *", "name"), + ("unsigned int", "flags"), + ("umode_t", "mode"), + ("int", "fd"), + )), + "spu_run": ("long", ( + ("int", "fd"), + ("__u32 *", "unpc"), + ("__u32 *", "ustatus"), + )), + "ssetmask": ("long", ( + ("int", "newmask"), + )), + "stat": ("long", ( + ("const char *", "filename"), + ("struct __old_kernel_stat *", "statbuf"), + )), + "stat64": ("long", ( + ("const char *", "filename"), + ("struct stat64 *", "statbuf"), + )), + "statfs": ("long", ( + ("const char *", "path"), + ("struct statfs *", "buf"), + )), + "statfs64": ("long", ( + ("const char *", "path"), + ("size_t", "sz"), + ("struct statfs64 *", "buf"), + )), + "statx": ("long", ( + ("int", "dirfd"), + ("const char *", "pathname"), + ("int", "flags"), + ("unsigned int", "mask"), + ("struct statx *", "statxbuf"), + )), + "stime": ("long", ( + ("time_t *", "tptr"), + )), + "swapoff": ("long", ( + ("const char *", "specialfile"), + )), + "swapon": ("long", ( + ("const char *", "specialfile"), + ("int", "swap_flags"), + )), + "symlink": ("long", ( + ("const char *", "old"), + ("const char *", "new"), + )), + "symlinkat": ("long", ( + ("const char *", "oldname"), + ("int", "newdfd"), + ("const char *", "newname"), + )), + "sync": ("long", ( + )), + "sync_file_range": ("long", ( + ("int", "fd"), + ("loff_t", "offset"), + ("loff_t", "nbytes"), + ("unsigned int", "flags"), + )), + "sync_file_range2": ("long", ( + ("int", "fd"), + ("unsigned int", "flags"), + ("loff_t", "offset"), + ("loff_t", "nbytes"), + )), + "syncfs": ("long", ( + ("int", "fd"), + )), + "sysctl": ("long", ( + ("struct __sysctl_args *", "args"), + )), + "sysfs": ("long", ( + ("int", "option"), + ("unsigned long", "arg1"), + ("unsigned long", "arg2"), + )), + "sysinfo": ("long", ( + ("struct sysinfo *", "info"), + )), + "syslog": ("long", ( + ("int", "type"), + ("char *", "buf"), + ("int", "len"), + )), + "tee": ("long", ( + ("int", "fdin"), + ("int", "fdout"), + ("size_t", "len"), + ("unsigned int", "flags"), + )), + "tgkill": ("long", ( + ("int", "tgid"), + ("int", "pid"), + ("int", "sig"), + )), + "time": ("long", ( + ("time_t *", "tloc"), + )), + "timer_create": ("long", ( + ("clockid_t", "which_clock"), + ("struct sigevent *", "timer_event_spec"), + ("timer_t *", "created_timer_id"), + )), + "timer_delete": ("long", ( + ("timer_t", "timer_id"), + )), + "timer_getoverrun": ("long", ( + ("timer_t", "timer_id"), + )), + "timer_gettime": ("long", ( + ("timer_t", "timer_id"), + ("struct itimerspec *", "setting"), + )), + "timer_settime": ("long", ( + ("timer_t", "timer_id"), + ("int", "flags"), + ("const struct itimerspec *", "new_setting"), + ("struct itimerspec *", "old_setting"), + )), + "timerfd_create": ("long", ( + ("int", "clockid"), + ("int", "flags"), + )), + "timerfd_gettime": ("long", ( + ("int", "ufd"), + ("struct itimerspec *", "otmr"), + )), + "timerfd_settime": ("long", ( + ("int", "ufd"), + ("int", "flags"), + ("const struct itimerspec *", "utmr"), + ("struct itimerspec *", "otmr"), + )), + "times": ("long", ( + ("struct tms *", "tbuf"), + )), + "tkill": ("long", ( + ("int", "pid"), + ("int", "sig"), + )), + "truncate": ("long", ( + ("const char *", "path"), + ("long", "length"), + )), + "truncate64": ("long", ( + ("const char *", "path"), + ("loff_t", "length"), + )), + "umask": ("long", ( + ("int", "mask"), + )), + "umount": ("long", ( + ("char *", "name"), + ("int", "flags"), + )), + "uname": ("long", ( + ("struct old_utsname *", "buf"), + )), + "unlink": ("long", ( + ("const char *", "pathname"), + )), + "unlinkat": ("long", ( + ("int", "dfd"), + ("const char *", "pathname"), + ("int", "flag"), + )), + "unshare": ("long", ( + ("unsigned long", "unshare_flags"), + )), + "uselib": ("long", ( + ("const char *", "library"), + )), + "userfaultfd": ("long", ( + ("int", "flags"), + )), + "ustat": ("long", ( + ("unsigned", "dev"), + ("struct ustat *", "ubuf"), + )), + "utime": ("long", ( + ("char *", "filename"), + ("struct utimbuf *", "times"), + )), + "utimensat": ("long", ( + ("int", "dfd"), + ("const char *", "filename"), + ("struct timespec *", "utimes"), + ("int", "flags"), + )), + "utimes": ("long", ( + ("char *", "filename"), + ("struct timeval *", "utimes"), + )), + "vfork": ("long", ( + )), + "vhangup": ("long", ( + )), + "vmsplice": ("long", ( + ("int", "fd"), + ("const struct iovec *", "iov"), + ("unsigned long", "nr_segs"), + ("unsigned int", "flags"), + )), + "wait4": ("long", ( + ("pid_t", "pid"), + ("int *", "stat_addr"), + ("int", "options"), + ("struct rusage *", "ru"), + )), + "waitid": ("long", ( + ("int", "which"), + ("pid_t", "pid"), + ("struct siginfo *", "infop"), + ("int", "options"), + ("struct rusage *", "ru"), + )), + "waitpid": ("long", ( + ("pid_t", "pid"), + ("int *", "stat_addr"), + ("int", "options"), + )), + "write": ("long", ( + ("unsigned int", "fd"), + ("const char *", "buf"), + ("size_t", "count"), + )), + "writev": ("long", ( + ("unsigned long", "fd"), + ("const struct iovec *", "vec"), + ("unsigned long", "vlen"), + )), +} + +for orig, copies in ALIASES.items(): + orig = SYSCALL_PROTOTYPES[orig] + for copy in copies: + SYSCALL_PROTOTYPES[copy] = orig diff --git a/ptrace/syscall/ptrace_syscall.py b/ptrace/syscall/ptrace_syscall.py new file mode 100644 index 0000000..afa4637 --- /dev/null +++ b/ptrace/syscall/ptrace_syscall.py @@ -0,0 +1,156 @@ +from os import strerror +from errno import errorcode + +from ptrace.cpu_info import CPU_X86_64, CPU_POWERPC, CPU_I386, CPU_ARM32, CPU_AARCH64, CPU_RISCV +from ptrace.ctypes_tools import ulong2long, formatAddress, formatWordHex +from ptrace.func_call import FunctionCall +from ptrace.syscall import SYSCALL_NAMES, SYSCALL_PROTOTYPES, SyscallArgument +from ptrace.syscall.socketcall import setupSocketCall +from ptrace.os_tools import RUNNING_LINUX, RUNNING_BSD +from ptrace.cpu_info import CPU_WORD_SIZE +from ptrace.binding.cpu import CPU_INSTR_POINTER + +if CPU_POWERPC: + SYSCALL_REGISTER = "gpr0" +elif CPU_ARM32: + SYSCALL_REGISTER = "r7" +elif CPU_AARCH64: + SYSCALL_REGISTER = "r8" +elif CPU_RISCV: + SYSCALL_REGISTER = "a7" +elif RUNNING_LINUX: + if CPU_X86_64: + SYSCALL_REGISTER = "orig_rax" + else: + SYSCALL_REGISTER = "orig_eax" +else: + if CPU_X86_64: + SYSCALL_REGISTER = "rax" + else: + SYSCALL_REGISTER = "eax" + +if CPU_ARM32: + RETURN_VALUE_REGISTER = "r0" +elif CPU_AARCH64: + RETURN_VALUE_REGISTER = "r0" +elif CPU_I386: + RETURN_VALUE_REGISTER = "eax" +elif CPU_X86_64: + RETURN_VALUE_REGISTER = "rax" +elif CPU_POWERPC: + RETURN_VALUE_REGISTER = "result" +elif CPU_RISCV: + RETURN_VALUE_REGISTER = "a0" +else: + raise NotImplementedError("Unsupported CPU architecture") + +PREFORMAT_ARGUMENTS = { + "select": (1, 2, 3), + "execve": (0, 1, 2), + "clone": (0, 1), +} + + +class PtraceSyscall(FunctionCall): + + def __init__(self, process, options, regs=None): + FunctionCall.__init__(self, "syscall", options, SyscallArgument) + self.process = process + self.restype = "long" + self.result = None + self.result_text = None + self.instr_pointer = None + if not regs: + regs = self.process.getregs() + self.readSyscall(regs) + + def enter(self, regs=None): + if not regs: + regs = self.process.getregs() + argument_values = self.readArgumentValues(regs) + self.readArguments(argument_values) + + if self.name == "socketcall" and self.options.replace_socketcall: + setupSocketCall(self, self.process, self[0], self[1].value) + + # Some arguments are lost after the syscall, so format them now + if self.name in PREFORMAT_ARGUMENTS: + for index in PREFORMAT_ARGUMENTS[self.name]: + argument = self.arguments[index] + argument.format() + + if self.options.instr_pointer: + self.instr_pointer = getattr(regs, CPU_INSTR_POINTER) + + def readSyscall(self, regs): + # Read syscall number + self.syscall = getattr(regs, SYSCALL_REGISTER) + # Get syscall variables + self.name = SYSCALL_NAMES.get( + self.syscall, "syscall<%s>" % self.syscall) + + def readArgumentValues(self, regs): + if CPU_X86_64: + return (regs.rdi, regs.rsi, regs.rdx, regs.r10, regs.r8, regs.r9) + if CPU_ARM32: + return (regs.r0, regs.r1, regs.r2, regs.r3, regs.r4, regs.r5, regs.r6) + if CPU_AARCH64: + return (regs.r0, regs.r1, regs.r2, regs.r3, regs.r4, regs.r5, regs.r6, regs.r7) + if CPU_RISCV: + return (regs.a0, regs.a1, regs.a2, regs.a3, regs.a4, regs.a5, regs.a6) + if RUNNING_BSD: + sp = self.process.getStackPointer() + return [self.process.readWord(sp + index * CPU_WORD_SIZE) + for index in range(1, 6 + 1)] + if CPU_I386: + return (regs.ebx, regs.ecx, regs.edx, regs.esi, regs.edi, regs.ebp) + if CPU_POWERPC: + return (regs.gpr3, regs.gpr4, regs.gpr5, regs.gpr6, regs.gpr7, regs.gpr8) + raise NotImplementedError() + + def readArguments(self, argument_values): + if self.name in SYSCALL_PROTOTYPES: + self.restype, formats = SYSCALL_PROTOTYPES[self.name] + for value, format in zip(argument_values, formats): + argtype, argname = format + self.addArgument(value=value, name=argname, type=argtype) + else: + for value in argument_values: + self.addArgument(value=value) + + def exit(self): + if self.name in PREFORMAT_ARGUMENTS: + preformat = set(PREFORMAT_ARGUMENTS[self.name]) + else: + preformat = set() + + # Data pointed by arguments may have changed during the syscall + # e.g. uname() syscall + for index, argument in enumerate(self.arguments): + if index in preformat: + # Don't lose preformatted arguments + continue + if argument.type and not argument.type.endswith("*"): + continue + argument.text = None + + self.result = self.process.getreg(RETURN_VALUE_REGISTER) + + if self.restype.endswith("*"): + text = formatAddress(self.result) + else: + uresult = self.result + self.result = ulong2long(self.result) + if self.result < 0 and (-self.result) in errorcode: + errcode = -self.result + text = "%s %s (%s)" % ( + self.result, errorcode[errcode], strerror(errcode)) + elif not (0 <= self.result <= 9): + text = "%s (%s)" % (self.result, formatWordHex(uresult)) + else: + text = str(self.result) + self.result_text = text + return text + + def __str__(self): + return "" % self.name diff --git a/ptrace/syscall/socketcall.py b/ptrace/syscall/socketcall.py new file mode 100644 index 0000000..06598a8 --- /dev/null +++ b/ptrace/syscall/socketcall.py @@ -0,0 +1,72 @@ +from ptrace.cpu_info import CPU_WORD_SIZE +from ptrace.ctypes_tools import ntoh_ushort, ntoh_uint # noqa +from ptrace.syscall import SYSCALL_PROTOTYPES +from ptrace.syscall.socketcall_constants import SOCKETCALL, SOCKET_FAMILY # noqa +from ptrace.syscall.socketcall_struct import sockaddr, sockaddr_in, sockaddr_in6, sockaddr_un +from ctypes import c_int +from ptrace.os_tools import RUNNING_LINUX +from socket import AF_INET, AF_INET6, inet_ntoa # noqa +if RUNNING_LINUX: + from socket import AF_NETLINK + from ptrace.syscall.socketcall_struct import sockaddr_nl + +AF_FILE = 1 + + +def formatOptVal(argument): + function = argument.function + optlen = function["optlen"].value + if optlen == 4: + addr = argument.value + text = function.process.readStruct(addr, c_int) + return argument.formatPointer("<%s>" % text, addr) + else: + return None + + +def formatSockaddr(argument, argtype): + address = argument.value + value = argument.function.process.readStruct(address, sockaddr) + family = value.family + if family == AF_INET: + return argument.readStruct(address, sockaddr_in) + if family == AF_INET6: + return argument.readStruct(address, sockaddr_in6) + if family == AF_FILE: + return argument.readStruct(address, sockaddr_un) + if RUNNING_LINUX: + if family == AF_NETLINK: + return argument.readStruct(address, sockaddr_nl) + family = SOCKET_FAMILY.get(family, family) + return argument.formatPointer("" % family, address) + + +def setupSocketCall(function, process, socketcall, address): + # Reset function call + function.clearArguments() +# function.argument_class = SocketCallArgument + + # Setup new function call + function.process = process + function.name = socketcall.getText() + + # Create arguments + function.restype, formats = SYSCALL_PROTOTYPES[function.name] + for argtype, argname in formats: + value = process.readWord(address) + function.addArgument(value, argname, argtype) + address += CPU_WORD_SIZE + + +def formatSockaddrInStruct(argument, name, value): + if name == "sin_port": + return ntoh_ushort(value) + return None + + +def formatSockaddrIn6Struct(argument, name, value): + if name == "sin6_port": + return ntoh_ushort(value) + # if name == "sin6_addr": + # FIXME: ... + return None diff --git a/ptrace/syscall/socketcall_constants.py b/ptrace/syscall/socketcall_constants.py new file mode 100644 index 0000000..216bcd2 --- /dev/null +++ b/ptrace/syscall/socketcall_constants.py @@ -0,0 +1,94 @@ +import socket + +SOCKETCALL = { + 1: "socket", + 2: "bind", + 3: "connect", + 4: "listen", + 5: "accept", + 6: "getsockname", + 7: "getpeername", + 8: "socketpair", + 9: "send", + 10: "recv", + 11: "sendto", + 12: "recvfrom", + 13: "shutdown", + 14: "setsockopt", + 15: "getsockopt", + 16: "sendmsg", + 17: "recvmsg", +} + +SOCKET_FAMILY = { + 0: "AF_UNSPEC", + 1: "AF_FILE", + 2: "AF_INET", + 3: "AF_AX25", + 4: "AF_IPX", + 5: "AF_APPLETALK", + 6: "AF_NETROM", + 7: "AF_BRIDGE", + 8: "AF_ATMPVC", + 9: "AF_X25", + 10: "AF_INET6", + 11: "AF_ROSE", + 12: "AF_DECnet", + 13: "AF_NETBEUI", + 14: "AF_SECURITY", + 15: "AF_KEY", + 16: "AF_NETLINK", + 17: "AF_PACKET", + 18: "AF_ASH", + 19: "AF_ECONET", + 20: "AF_ATMSVC", + 22: "AF_SNA", + 23: "AF_IRDA", + 24: "AF_PPPOX", + 25: "AF_WANPIPE", + 31: "AF_BLUETOOTH", +} + +SOCKET_TYPE = { + 1: "SOCK_STREAM", + 2: "SOCK_DGRAM", + 3: "SOCK_RAW", + 4: "SOCK_RDM", + 5: "SOCK_SEQPACKET", + 10: "SOCK_PACKET", +} + + +def formatSocketType(argument): + value = argument.value + text = [] + if hasattr(socket, 'SOCK_CLOEXEC'): + cloexec = value & socket.SOCK_CLOEXEC + value &= ~socket.SOCK_CLOEXEC + else: + cloexec = False + text = SOCKET_TYPE.get(value, str(value)) + if cloexec: + text += '|SOCK_CLOEXEC' + return text + + +SOCKET_PROTOCOL = { + 1: "IPPROTO_ICMP", + 58: "IPPROTO_ICMPV6", +} + +SETSOCKOPT_LEVEL = { + 0: "SOL_IP", + 1: "SOL_SOCKET", +} + +SETSOCKOPT_OPTNAME = { + # level 0 (SOL_IP) + 1: "IP_TOS", + # level 1 (SOL_SOCKET) + 2: "SO_REUSEADDR", + 9: "SO_KEEPALIVE", + 20: "SO_RCVTIMEO", + 21: "SO_SNDTIMEO", +} diff --git a/ptrace/syscall/socketcall_struct.py b/ptrace/syscall/socketcall_struct.py new file mode 100644 index 0000000..7452fce --- /dev/null +++ b/ptrace/syscall/socketcall_struct.py @@ -0,0 +1,113 @@ +from ctypes import Structure, Union, c_char, c_ushort, c_ubyte, c_uint16, c_uint32 +from ptrace.os_tools import RUNNING_BSD, RUNNING_LINUX +from socket import inet_ntoa +from struct import pack +from ptrace.ctypes_tools import ntoh_uint, ntoh_ushort + + +def ip_int2str(ip): + """ + Convert an IP address (as an integer) to a string. + + >>> ip_int2str(0x7f000001) + '127.0.0.1' + """ + ip_bytes = pack("!I", ip) + return inet_ntoa(ip_bytes) + + +if RUNNING_BSD: + sa_family_t = c_ubyte +else: + sa_family_t = c_ushort + + +class sockaddr(Structure): + if RUNNING_BSD: + _fields_ = ( + ("len", c_ubyte), + ("family", sa_family_t), + ) + else: + _fields_ = ( + ("family", sa_family_t), + ) + + +class in_addr(Structure): + _fields_ = ( + ("s_addr", c_uint32), + ) + + def __repr__(self): + ip = ntoh_uint(self.s_addr) + return ip_int2str(ip) + + +class in6_addr(Union): + _fields_ = ( + ("addr8", c_ubyte * 16), + ("addr16", c_uint16 * 8), + ("addr32", c_uint32 * 4), + ) + + def __repr__(self): + text = ':'.join(("%04x" % ntoh_ushort(part)) for part in self.addr16) + return "" % text + +# INET socket + + +class sockaddr_in(Structure): + if RUNNING_BSD: + _fields_ = ( + ("sin_len", c_ubyte), + ("sin_family", sa_family_t), + ("sin_port", c_uint16), + ("sin_addr", in_addr), + ) + else: + _fields_ = ( + ("sin_family", sa_family_t), + ("sin_port", c_uint16), + ("sin_addr", in_addr), + ) + + +class sockaddr_in6(Structure): + if RUNNING_BSD: + _fields_ = ( + ("sin6_len", c_ubyte), + ("sin6_family", sa_family_t), + ("sin6_port", c_uint16), + ("sin6_flowinfo", c_uint32), + ("sin6_addr", in6_addr), + ) + else: + _fields_ = ( + ("sin6_family", sa_family_t), + ("sin6_port", c_uint16), + ("sin6_flowinfo", c_uint32), + ("sin6_addr", in6_addr), + ("sin6_scope_ip", c_uint32), + ) + +# UNIX socket + + +class sockaddr_un(Structure): + _fields_ = ( + ("sun_family", sa_family_t), + ("sun_path", c_char * 108), + ) + + +# Netlink socket +if RUNNING_LINUX: + class sockaddr_nl(Structure): + _fields_ = ( + ("nl_family", sa_family_t), + ("nl_pad", c_ushort), + ("nl_pid", c_uint32), + ("nl_groups", c_uint32), + ) diff --git a/ptrace/syscall/syscall_argument.py b/ptrace/syscall/syscall_argument.py new file mode 100644 index 0000000..1f99060 --- /dev/null +++ b/ptrace/syscall/syscall_argument.py @@ -0,0 +1,248 @@ +from ptrace.cpu_info import CPU_WORD_SIZE +from ptrace.ctypes_tools import uint2int, formatWordHex, formatAddress +from ptrace.signames import signalName +from ctypes import c_int +from ptrace.error import PTRACE_ERRORS, writeError +from logging import getLogger, INFO +from ptrace.func_arg import FunctionArgument +from ptrace.syscall.posix_arg import ( + formatMmapProt, formatAccessMode, formatOpenFlags, formatCloneFlags, formatDirFd, formatOpenMode) +from ptrace.func_call import FunctionCall +from ptrace.syscall.socketcall import (setupSocketCall, + formatOptVal, formatSockaddr, formatSockaddrInStruct, formatSockaddrIn6Struct) +from ptrace.syscall.socketcall_constants import SOCKETCALL +import os +import re + +from ptrace.os_tools import RUNNING_LINUX, RUNNING_FREEBSD +from ptrace.syscall import FILENAME_ARGUMENTS +from ptrace.syscall.socketcall_constants import formatSocketType +if RUNNING_LINUX: + from ptrace.syscall.linux_struct import ( + timeval, timespec, pollfd, rlimit, new_utsname, user_desc) + from ptrace.syscall.linux_constants import SYSCALL_ARG_DICT, FD_SETSIZE +elif RUNNING_FREEBSD: + from ptrace.syscall.freebsd_constants import SYSCALL_ARG_DICT +else: + SYSCALL_ARG_DICT = {} + + +KNOWN_STRUCTS = [] +if RUNNING_LINUX: + KNOWN_STRUCTS.extend( + (timeval, timespec, pollfd, rlimit, new_utsname, user_desc)) +KNOWN_STRUCTS = dict((struct.__name__, struct) for struct in KNOWN_STRUCTS) + +ARGUMENT_CALLBACK = { + # Prototype: callback(argument) -> str + "access": {"mode": formatAccessMode}, + "open": {"flags": formatOpenFlags, "mode": formatOpenMode}, + "openat": {"dirfd": formatDirFd, "flags": formatOpenFlags, "mode": formatOpenMode}, + "name_to_handle_at": {"dirfd": formatDirFd}, + "mmap": {"prot": formatMmapProt}, + "mmap2": {"prot": formatMmapProt}, + "clone": {"flags": formatCloneFlags}, + "socket": {"type": formatSocketType}, + "setsockopt": {"optval": formatOptVal}, +} + +POINTER_CALLBACK = { + # Prototype: callback(argument, argtype) -> str + "sockaddr": formatSockaddr, +} + +STRUCT_CALLBACK = { + # Prototype: callback(argument, attr_name, attr_value) -> str + "sockaddr_in": formatSockaddrInStruct, + "sockaddr_in6": formatSockaddrIn6Struct, +} + +INTEGER_TYPES = set(( + "int", "size_t", "clockid_t", "long", + "socklen_t", "pid_t", "uid_t", "gid_t", +)) + + +def iterBits(data): + for char in data: + byte = ord(chr(char)) + for index in range(8): + yield ((byte >> index) & 1) == 1 + + +class SyscallArgument(FunctionArgument): + + def createText(self): + value = self.value + argtype = self.type + name = self.name + if not argtype or not name: + return formatWordHex(self.value) + + syscall = self.function.name + + # Special cases + try: + return SYSCALL_ARG_DICT[syscall][name][value] + except KeyError: + pass + try: + callback = ARGUMENT_CALLBACK[syscall][name] + except KeyError: + callback = None + if callback: + return callback(self) + if syscall == "execve": + if name in ("argv", "envp"): + return self.readCStringArray(value) + if syscall == "socketcall": + if name == "call": + try: + return SOCKETCALL[value] + except KeyError: + return str(value) + if name == "args": + func_call = FunctionCall("socketcall", self.options) + setupSocketCall(func_call, self.function.process, + self.function[0], self.value) + text = "<%s>" % func_call.format() + return self.formatPointer(text, self.value) + if syscall == "write" and name == "buf": + fd = self.function[0].value + if fd < 3: + length = self.function[2].value + return self.readString(value, length) + if name == "signum": + return signalName(value) + + # Remove "const " prefix + if argtype.startswith("const "): + argtype = argtype[6:] + + if name in FILENAME_ARGUMENTS and argtype == "char *": + return self.readCString(value) + + # Format depending on the type + if argtype.endswith("*"): + try: + # Strip in case there is a space between the name and '*' + text = self.formatValuePointer(argtype[:-1].strip()) + if text: + return text + except PTRACE_ERRORS as err: + writeError( + getLogger(), err, "Warning: Format %r value error" % self, log_level=INFO) + return formatAddress(self.value) + + # Array like "int[2]" + match = re.match(r"(.*)\[([0-9])+\]", argtype) + if match: + basetype = match.group(1) + count = int(match.group(2)) + if basetype == "int": + return self.readArray(self.value, c_int, count) + + # Simple types + if argtype in ("unsigned int", "unsigned long", "u32"): + return str(self.value) + if argtype in INTEGER_TYPES: + return str(uint2int(self.value)) + + # Default formatter: hexadecimal + return formatWordHex(self.value) + + def formatValuePointer(self, argtype): + address = self.value + + if not address: + return "NULL" + if argtype.startswith("struct "): + argtype = argtype[7:] + + # Try a callback + try: + callback = POINTER_CALLBACK[argtype] + except KeyError: + callback = None + if callback: + return callback(self, argtype) + + if argtype == "int": + pointee = self.function.process.readStruct(address, c_int) + return self.formatPointer("<%s>" % pointee, address) + if argtype in KNOWN_STRUCTS: + struct = KNOWN_STRUCTS[argtype] + return self.readStruct(address, struct) + if RUNNING_LINUX and argtype == "fd_set": + # The function is either select or pselect, so arg[0] is nfds + nfds = self.function.arguments[0].value + fd_set = filter(lambda x: int(x) < nfds, self.readBits(address, FD_SETSIZE)) + return self.formatPointer("[%s]" % " ".join(fd_set), address) + + syscall = self.function.name + if syscall == "rt_sigprocmask" and argtype == "sigset_t": + size = self.function["sigsetsize"].value * 8 + + def formatter(key): + key += 1 + return signalName(key) + fd_set = self.readBits(address, size, format=formatter) + return self.formatPointer("" % " ".join(fd_set), address) + return None + + def readBits(self, address, count, format=str): + bytes = self.function.process.readBytes(address, count // 8) + fd_set = [format(index) + for index, bit in enumerate(iterBits(bytes)) if bit] + return fd_set + + def readCString(self, address): + if address: + max_size = self.options.string_max_length + data, truncated = self.function.process.readCString( + address, max_size) + text = os.fsdecode(data) + text = repr(text) + if truncated: + text += "..." + else: + text = "NULL" + return self.formatPointer(text, address) + + def readString(self, address, size): + if address: + max_len = self.options.string_max_length + truncated = (max_len < size) + size = min(size, max_len) + data = self.function.process.readBytes(address, size) + text = os.fsdecode(data) + text = repr(text) + if truncated: + text += "..." + else: + text = "NULL" + return self.formatPointer(text, address) + + def readCStringArray(self, address): + if not address: + return "NULL" + address0 = address + max_count = self.options.max_array_count + text = [] + while True: + str_addr = self.function.process.readWord(address) + address += CPU_WORD_SIZE + text.append(self.readCString(str_addr)) + if not str_addr: + break + if max_count <= len(text): + text.append("(... more than %s strings ...)" % max_count) + break + text = "<(%s)>" % ", ".join(text) + return self.formatPointer(text, address0) + + def formatStructValue(self, struct, name, value): + if struct in STRUCT_CALLBACK: + callback = STRUCT_CALLBACK[struct] + return callback(self, name, value) + return None diff --git a/ptrace/terminal.py b/ptrace/terminal.py new file mode 100644 index 0000000..53da550 --- /dev/null +++ b/ptrace/terminal.py @@ -0,0 +1,41 @@ +""" +Terminal functions. +""" + +from termios import tcgetattr, tcsetattr, ECHO, TCSADRAIN, TIOCGWINSZ +from sys import stdin, stdout +from fcntl import ioctl +from struct import unpack +import os + +TERMIO_LFLAGS = 3 + + +def _terminalSize(): + fd = stdout.fileno() + size = ioctl(fd, TIOCGWINSZ, '1234') + height, width = unpack('hh', size) + return (width, height) + + +def terminalWidth(): + """ + Get the terminal width in characters. + """ + return _terminalSize()[0] + + +def enableEchoMode(): + """ + Enable echo mode in the terminal. Return True if the echo mode is set + correctly, or False if the mode was already set. + """ + fd = stdin.fileno() + if not os.isatty(fd): + return False + state = tcgetattr(fd) + if state[TERMIO_LFLAGS] & ECHO: + return False + state[TERMIO_LFLAGS] = state[TERMIO_LFLAGS] | ECHO + tcsetattr(fd, TCSADRAIN, state) + return True diff --git a/ptrace/tools.py b/ptrace/tools.py new file mode 100644 index 0000000..6680d48 --- /dev/null +++ b/ptrace/tools.py @@ -0,0 +1,167 @@ +from ctypes import sizeof +from ptrace.ctypes_tools import formatUintHex16, formatUintHex32, formatWordHex +from datetime import datetime, timedelta +from os import getenv, access, X_OK, pathsep, getcwd +from os.path import join as path_join, isabs, dirname, normpath + + +def dumpRegs(log, regs): + """ + Dump all registers using log callback (write one line). + """ + width = max(len(name) for name, type in regs._fields_) + name_format = "%% %us" % width + for name, type in regs._fields_: + value = getattr(regs, name) + name = name_format % name + if sizeof(type) == 32: + value = formatUintHex32(value) + elif sizeof(type) == 16: + value = formatUintHex16(value) + else: + value = formatWordHex(value) + log("%s = %s" % (name, value)) + + +def readBits(value, bitmasks): + """ + Extract bits from the integer value using a list of bit masks. + bitmasks is a list of tuple (mask, text). + + >>> bitmask = ( + ... (1, "exec"), + ... (2, "write"), + ... (4, "read")) + ... + >>> readBits(5, bitmask) + ['exec', 'read'] + >>> readBits(12, bitmask) + ['read', '8'] + """ + bitset = [] + for mask, item in bitmasks: + if not value & mask: + continue + bitset.append(item) + value = value & ~mask + if value: + bitset.append(str(value)) + return bitset + + +def formatBits(value, bitmasks, empty_text=None, format_value=str): + """ + Format a value using a bitmask: see readBits() functions. + + >>> bitmask = ( + ... (1, "exec"), + ... (2, "write"), + ... (4, "read")) + ... + >>> formatBits(5, bitmask, empty_text="no permission") + ' (5)' + >>> formatBits(0, bitmask, empty_text="no permission") + 'no permission' + """ + + orig_value = value + text = readBits(value, bitmasks) + if text: + text = "%s" % ("|".join(text)) + if value: + text = "<%s> (%s)" % (text, format_value(orig_value)) + return text + else: + if empty_text: + return empty_text + else: + return str(value) + + +LOCAL_TIMEZONE_OFFSET = datetime.fromtimestamp( + 0) - datetime.utcfromtimestamp(0) + +# Start of UNIX timestamp (Epoch): 1st January 1970 at 00:00 +UNIX_TIMESTAMP_T0 = datetime(1970, 1, 1) + + +def timestampUNIX(value, is_local): + """ + Convert an UNIX (32-bit) timestamp to datetime object. Timestamp value + is the number of seconds since the 1st January 1970 at 00:00. Maximum + value is 2147483647: 19 january 2038 at 03:14:07. + + May raise ValueError for invalid value: value have to be in 0..2147483647. + + >>> timestampUNIX(0, False) + datetime.datetime(1970, 1, 1, 0, 0) + >>> timestampUNIX(1154175644.37, False) + datetime.datetime(2006, 7, 29, 12, 20, 44, 370000) + """ + timestamp = UNIX_TIMESTAMP_T0 + timedelta(seconds=value) + if is_local: + timestamp += LOCAL_TIMEZONE_OFFSET + return timestamp + + +def locateProgram(program): + """ + Locate a program using the PATH environment variable. Return the + unchanged program value if it's not possible to get the full + program path. + """ + if isabs(program): + return program + if dirname(program): + # ./test => $PWD/./test + # ../python => $PWD/../python + program = path_join(getcwd(), program) + program = normpath(program) + return program + paths = getenv('PATH') + if not paths: + return program + for path in paths.split(pathsep): + filename = path_join(path, program) + if access(filename, X_OK): + return filename + return program + + +def minmax(min_value, value, max_value): + """ + Restrict value to [min_value; max_value] + + >>> minmax(-2, -3, 10) + -2 + >>> minmax(-2, 27, 10) + 10 + >>> minmax(-2, 0, 10) + 0 + """ + return min(max(min_value, value), max_value) + + +def inverseDict(data): + """ + Inverse a dictionary. + + >>> inverseDict({"0x10": 16, "0x20": 32}) == {32: '0x20', 16: '0x10'} + True + """ + result = {} + for key, value in data.items(): + result[value] = key + return result + + +def signal_to_exitcode(signum): + """ + Converts a signal number to an exit code. + UNIX: https://www.gnu.org/software/bash/manual/html_node/Exit-Status.html + + >>> import signal + >>> signal_to_exitcode(signal.SIGQUIT) + 131 + """ + return 128 + signum diff --git a/ptrace/version.py b/ptrace/version.py new file mode 100644 index 0000000..fa2070b --- /dev/null +++ b/ptrace/version.py @@ -0,0 +1,5 @@ +PACKAGE = "python-ptrace" +VERSION = (0, 9, 9) +__version__ = '.'.join(map(str, VERSION)) +WEBSITE = "http://python-ptrace.readthedocs.io/" +LICENSE = "GNU GPL v2" diff --git a/python_ptrace.egg-info/PKG-INFO b/python_ptrace.egg-info/PKG-INFO new file mode 100644 index 0000000..48eb599 --- /dev/null +++ b/python_ptrace.egg-info/PKG-INFO @@ -0,0 +1,42 @@ +Metadata-Version: 2.1 +Name: python-ptrace +Version: 0.9.9 +Summary: python binding of ptrace +Home-page: http://python-ptrace.readthedocs.io/ +Download-URL: http://python-ptrace.readthedocs.io/ +Author: Victor Stinner +License: GNU GPL v2 +Classifier: Intended Audience :: Developers +Classifier: Development Status :: 4 - Beta +Classifier: Environment :: Console +Classifier: License :: OSI Approved :: GNU General Public License (GPL) +Classifier: Operating System :: OS Independent +Classifier: Natural Language :: English +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +License-File: COPYING + +============= +python-ptrace +============= + +.. image:: https://img.shields.io/pypi/v/python-ptrace.svg + :alt: Latest release on the Python Cheeseshop (PyPI) + :target: https://pypi.python.org/pypi/python-ptrace + +.. image:: https://github.com/vstinner/python-ptrace/actions/workflows/build.yml/badge.svg + :alt: Build status of python-ptrace on GitHub Actions + :target: https://github.com/vstinner/python-ptrace/actions + +python-ptrace is a debugger using ptrace (Linux, BSD and Darwin system call to +trace processes) written in Python. + +* `python-ptrace documentation + `_ +* `python-ptrace at GitHub + `_ +* `python-ptrace at the Python Cheeseshop (PyPI) + `_ + +python-ptrace is an opensource project written in Python under GNU GPLv2 +license. It supports Python 3.6 and newer. diff --git a/python_ptrace.egg-info/SOURCES.txt b/python_ptrace.egg-info/SOURCES.txt new file mode 100644 index 0000000..452ece0 --- /dev/null +++ b/python_ptrace.egg-info/SOURCES.txt @@ -0,0 +1,110 @@ +COPYING +MANIFEST.in +README.rst +SYSCALL_PROTOTYPES.codegen.py +gdb.py +runtests.py +setup.cfg +setup.py +setup_cptrace.py +strace.py +test_doc.py +tox.ini +cptrace/Makefile +cptrace/cptrace.c +cptrace/version.py +doc/Makefile +doc/authors.rst +doc/changelog.rst +doc/conf.py +doc/cptrace.rst +doc/gdb.rst +doc/index.rst +doc/install.rst +doc/make.bat +doc/process_events.rst +doc/ptrace_signal.rst +doc/syscall.rst +doc/todo.rst +doc/usage.rst +examples/itrace.py +examples/simple_dbg.py +ptrace/__init__.py +ptrace/cpu_info.py +ptrace/ctypes_libc.py +ptrace/ctypes_tools.py +ptrace/disasm.py +ptrace/error.py +ptrace/func_arg.py +ptrace/func_call.py +ptrace/linux_proc.py +ptrace/logging_tools.py +ptrace/mockup.py +ptrace/os_tools.py +ptrace/process_tools.py +ptrace/profiler.py +ptrace/pydistorm.py +ptrace/signames.py +ptrace/terminal.py +ptrace/tools.py +ptrace/version.py +ptrace/binding/__init__.py +ptrace/binding/cpu.py +ptrace/binding/freebsd_struct.py +ptrace/binding/func.py +ptrace/binding/linux_struct.py +ptrace/binding/openbsd_struct.py +ptrace/debugger/__init__.py +ptrace/debugger/application.py +ptrace/debugger/backtrace.py +ptrace/debugger/breakpoint.py +ptrace/debugger/child.py +ptrace/debugger/debugger.py +ptrace/debugger/memory_mapping.py +ptrace/debugger/parse_expr.py +ptrace/debugger/process.py +ptrace/debugger/process_error.py +ptrace/debugger/process_event.py +ptrace/debugger/ptrace_signal.py +ptrace/debugger/signal_reason.py +ptrace/debugger/syscall_state.py +ptrace/syscall/__init__.py +ptrace/syscall/freebsd_constants.py +ptrace/syscall/freebsd_syscall.py +ptrace/syscall/linux_constants.py +ptrace/syscall/linux_struct.py +ptrace/syscall/names.py +ptrace/syscall/posix_arg.py +ptrace/syscall/posix_constants.py +ptrace/syscall/prototypes.py +ptrace/syscall/ptrace_syscall.py +ptrace/syscall/socketcall.py +ptrace/syscall/socketcall_constants.py +ptrace/syscall/socketcall_struct.py +ptrace/syscall/syscall_argument.py +ptrace/syscall/linux/__init__.py +ptrace/syscall/linux/aarch64.py +ptrace/syscall/linux/i386.py +ptrace/syscall/linux/powerpc32.py +ptrace/syscall/linux/powerpc64.py +ptrace/syscall/linux/riscv32.py +ptrace/syscall/linux/riscv64.py +ptrace/syscall/linux/x86_64.py +python_ptrace.egg-info/PKG-INFO +python_ptrace.egg-info/SOURCES.txt +python_ptrace.egg-info/dependency_links.txt +python_ptrace.egg-info/top_level.txt +tests/test_gdb.py +tests/test_strace.py +tests/crash/BSDmakefile +tests/crash/Makefile +tests/crash/abort.c +tests/crash/call_null.c +tests/crash/div_zero.c +tests/crash/execve.c +tests/crash/fork.c +tests/crash/invalid_read.c +tests/crash/invalid_write.c +tests/crash/pthread.c +tests/crash/socket_ipv4_tcp.c +tests/crash/stack_overflow.c \ No newline at end of file diff --git a/python_ptrace.egg-info/dependency_links.txt b/python_ptrace.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/python_ptrace.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/python_ptrace.egg-info/top_level.txt b/python_ptrace.egg-info/top_level.txt new file mode 100644 index 0000000..d61c513 --- /dev/null +++ b/python_ptrace.egg-info/top_level.txt @@ -0,0 +1 @@ +ptrace diff --git a/runtests.py b/runtests.py new file mode 100755 index 0000000..5de73c9 --- /dev/null +++ b/runtests.py @@ -0,0 +1,281 @@ +#!/usr/bin/env python3 +"""Run Tulip unittests. + +Usage: + python3 runtests.py [flags] [pattern] ... + +Patterns are matched against the fully qualified name of the test, +including package, module, class and method, +e.g. 'tests.test_events.PolicyTests.testPolicy'. + +For full help, try --help. + +runtests.py --coverage is equivalent of: + + $(COVERAGE) run --branch runtests.py -v + $(COVERAGE) html $(list of files) + $(COVERAGE) report -m $(list of files) + +""" + +# Originally written by Beech Horn (for NDB). + +import gc +import importlib.machinery +import logging +import optparse +import os +import re +import sys +import textwrap +try: + import coverage +except ImportError: + coverage = None + +try: + import unittest + from unittest.signals import installHandler +except ImportError: + import unittest2 as unittest + from unittest2.signals import installHandler + +ARGS = optparse.OptionParser(description="Run all unittests.", usage="%prog") +ARGS.add_option( + '-v', '--verbose', action="store_true", dest='verbose', + default=0, help='verbose') +ARGS.add_option( + '-x', action="store_true", dest='exclude', help='exclude tests') +ARGS.add_option( + '-f', '--failfast', action="store_true", default=False, + dest='failfast', help='Stop on first fail or error') +ARGS.add_option( + '-c', '--catch', action="store_true", default=False, + dest='catchbreak', help='Catch control-C and display results') +ARGS.add_option( + '--forever', action="store_true", dest='forever', default=False, + help='run tests forever to catch sporadic errors') +ARGS.add_option( + '--findleaks', action='store_true', dest='findleaks', + help='detect tests that leak memory') +ARGS.add_option( + '-q', action="store_true", dest='quiet', help='quiet') +ARGS.add_option( + '--tests', action="store", dest='testsdir', default='tests', + help='tests directory') +ARGS.add_option( + '--coverage', action="store_true", dest='coverage', + help='enable html coverage report') +ARGS.add_option( + '--pattern', action="append", + help='optional regex patterns to match test ids (default all tests)') + + +def load_module(modname, sourcefile): + loader = importlib.machinery.SourceFileLoader(modname, sourcefile) + return loader.load_module() + + +def load_modules(basedir, suffix='.py'): + def list_dir(prefix, dir): + files = [] + + modpath = os.path.join(dir, '__init__.py') + if os.path.isfile(modpath): + mod = os.path.split(dir)[-1] + files.append(('{0}{1}'.format(prefix, mod), modpath)) + + prefix = '{0}{1}.'.format(prefix, mod) + + for name in os.listdir(dir): + path = os.path.join(dir, name) + + if os.path.isdir(path): + files.extend(list_dir('{0}{1}.'.format(prefix, name), path)) + else: + if (name != '__init__.py' + and name.endswith(suffix) + and not name.startswith(('.', '_'))): + files.append(('{0}{1}'.format(prefix, name[:-3]), path)) + + return files + + mods = [] + for modname, sourcefile in list_dir('', basedir): + if modname == 'runtests': + continue + try: + mod = load_module(modname, sourcefile) + mods.append((mod, sourcefile)) + except SyntaxError: + raise + except Exception as err: + print("Skipping '{0}': {1}".format(modname, err), file=sys.stderr) + + return mods + + +class TestsFinder: + + def __init__(self, testsdir, includes=(), excludes=()): + self._testsdir = testsdir + self._includes = includes + self._excludes = excludes + self.find_available_tests() + + def find_available_tests(self): + """ + Find available test classes without instantiating them. + """ + self._test_factories = [] + mods = [mod for mod, _ in load_modules(self._testsdir)] + for mod in mods: + for name in set(dir(mod)): + if 'Test' in name: + self._test_factories.append(getattr(mod, name)) + + def load_tests(self): + """ + Load test cases from the available test classes and apply + optional include / exclude filters. + """ + loader = unittest.TestLoader() + suite = unittest.TestSuite() + for test_factory in self._test_factories: + tests = loader.loadTestsFromTestCase(test_factory) + if self._includes: + tests = [test + for test in tests + if any(re.search(pat, test.id()) + for pat in self._includes)] + if self._excludes: + tests = [test + for test in tests + if not any(re.search(pat, test.id()) + for pat in self._excludes)] + suite.addTests(tests) + return suite + + +class TestResult(unittest.TextTestResult): + + def __init__(self, stream, descriptions, verbosity): + super().__init__(stream, descriptions, verbosity) + self.leaks = [] + + def startTest(self, test): + super().startTest(test) + gc.collect() + + def addSuccess(self, test): + super().addSuccess(test) + gc.collect() + if gc.garbage: + if self.showAll: + self.stream.writeln( + " Warning: test created {} uncollectable " + "object(s).".format(len(gc.garbage))) + # move the uncollectable objects somewhere so we don't see + # them again + self.leaks.append((self.getDescription(test), gc.garbage[:])) + del gc.garbage[:] + + +class TestRunner(unittest.TextTestRunner): + resultclass = TestResult + + def run(self, test): + result = super().run(test) + if result.leaks: + self.stream.writeln("{0} tests leaks:".format(len(result.leaks))) + for name, leaks in result.leaks: + self.stream.writeln(' ' * 4 + name + ':') + for leak in leaks: + self.stream.writeln(' ' * 8 + repr(leak)) + return result + + +def runtests(): + args, commands = ARGS.parse_args() + + if args.coverage and coverage is None: + URL = "bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py" + print(textwrap.dedent(""" + coverage package is not installed. + + To install coverage3 for Python 3, you need: + - Setuptools (https://pypi.python.org/pypi/setuptools) + + What worked for me: + - download {0} + * curl -O https://{0} + - python3 ez_setup.py + - python3 -m easy_install coverage + """.format(URL)).strip()) + sys.exit(1) + + testsdir = os.path.abspath(args.testsdir) + if not os.path.isdir(testsdir): + print("Tests directory is not found: {0}\n".format(testsdir)) + ARGS.print_help() + return + + excludes = includes = [] + if args.exclude: + excludes = args.pattern + else: + includes = args.pattern + + v = 0 if args.quiet else args.verbose + 1 + failfast = args.failfast + catchbreak = args.catchbreak + findleaks = args.findleaks + runner_factory = TestRunner if findleaks else unittest.TextTestRunner + + if args.coverage: + cov = coverage.coverage(branch=True, + source=['asyncio'], + ) + cov.start() + + finder = TestsFinder(args.testsdir, includes, excludes) + logger = logging.getLogger() + if v == 0: + logger.setLevel(logging.CRITICAL) + elif v == 1: + logger.setLevel(logging.ERROR) + elif v == 2: + logger.setLevel(logging.WARNING) + elif v == 3: + logger.setLevel(logging.INFO) + elif v >= 4: + logger.setLevel(logging.DEBUG) + if catchbreak: + installHandler() + try: + if args.forever: + while True: + tests = finder.load_tests() + result = runner_factory(verbosity=v, + failfast=failfast).run(tests) + if not result.wasSuccessful(): + sys.exit(1) + else: + tests = finder.load_tests() + result = runner_factory(verbosity=v, + failfast=failfast).run(tests) + sys.exit(not result.wasSuccessful()) + finally: + if args.coverage: + cov.stop() + cov.save() + cov.html_report(directory='htmlcov') + print("\nCoverage report:") + cov.report(show_missing=False) + here = os.path.dirname(os.path.abspath(__file__)) + print("\nFor html report:") + print("open file://{0}/htmlcov/index.html".format(here)) + + +if __name__ == '__main__': + runtests() diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..adf5ed7 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,7 @@ +[bdist_wheel] +universal = 1 + +[egg_info] +tag_build = +tag_date = 0 + diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..ad54f9a --- /dev/null +++ b/setup.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +# Prepare a release: +# +# - git pull --rebase # check that there is no incoming changesets +# - check version in ptrace/version.py and doc/conf.py +# - set release date in doc/changelog.rst +# - check that "python3 setup.py sdist" contains all files tracked by +# the SCM (Git): update MANIFEST.in if needed +# - git commit -a -m "prepare release VERSION" +# - Remove untracked files/dirs: git clean -fdx +# - run tests, type: tox --parallel auto +# - git push +# - check GitHub Actions status: +# https://github.com/vstinner/python-ptrace/actions +# +# Release a new version: +# +# - git tag VERSION +# - Remove untracked files/dirs: git clean -fdx +# - python3 setup.py sdist bdist_wheel +# - git push --tags +# - twine upload dist/* +# +# After the release: +# +# - increment version in ptrace/version.py and doc/conf.py +# - git commit -a -m "post-release" +# - git push + +import importlib.util +from os import path +try: + # setuptools supports bdist_wheel + from setuptools import setup +except ImportError: + from distutils.core import setup + + +MODULES = ["ptrace", "ptrace.binding", "ptrace.syscall", "ptrace.syscall.linux", "ptrace.debugger"] + +SCRIPTS = ("strace.py", "gdb.py") + +CLASSIFIERS = [ + 'Intended Audience :: Developers', + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Operating System :: OS Independent', + 'Natural Language :: English', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', +] + +with open('README.rst') as fp: + LONG_DESCRIPTION = fp.read() + +ptrace_spec = importlib.util.spec_from_file_location("version", path.join("ptrace", "version.py")) +ptrace = importlib.util.module_from_spec(ptrace_spec) +ptrace_spec.loader.exec_module(ptrace) + +PACKAGES = {} +for name in MODULES: + PACKAGES[name] = name.replace(".", "/") + +install_options = { + "name": ptrace.PACKAGE, + "version": ptrace.__version__, + "url": ptrace.WEBSITE, + "download_url": ptrace.WEBSITE, + "author": "Victor Stinner", + "description": "python binding of ptrace", + "long_description": LONG_DESCRIPTION, + "classifiers": CLASSIFIERS, + "license": ptrace.LICENSE, + "packages": list(PACKAGES.keys()), + "package_dir": PACKAGES, + "scripts": SCRIPTS, +} + +setup(**install_options) diff --git a/setup_cptrace.py b/setup_cptrace.py new file mode 100755 index 0000000..f06b792 --- /dev/null +++ b/setup_cptrace.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python + +import importlib.util + +SOURCES = ['cptrace/cptrace.c'] + +CLASSIFIERS = [ + 'Intended Audience :: Developers', + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Operating System :: OS Independent', + 'Natural Language :: English', + 'Programming Language :: C', + 'Programming Language :: Python', +] + +LONG_DESCRIPTION = open('doc/cptrace.rst').read() + + +def main(): + from os import path + from sys import argv + + if "--setuptools" in argv: + argv.remove("--setuptools") + from setuptools import setup, Extension + else: + from distutils.core import setup, Extension + + cptrace_ext = Extension('cptrace', sources=SOURCES) + + cptrace_spec = importlib.util.spec_from_file_location("version", + path.join("cptrace", "version.py")) + cptrace = importlib.util.module_from_spec(cptrace_spec) + cptrace_spec.loader.exec_module(cptrace) + + install_options = { + "name": cptrace.PACKAGE, + "version": cptrace.VERSION, + "url": cptrace.WEBSITE, + "download_url": cptrace.WEBSITE, + "license": cptrace.LICENSE, + "author": "Victor Stinner", + "description": "python binding of ptrace written in C", + "long_description": LONG_DESCRIPTION, + "classifiers": CLASSIFIERS, + "ext_modules": [cptrace_ext], + } + setup(**install_options) + + +if __name__ == "__main__": + main() diff --git a/strace.py b/strace.py new file mode 100755 index 0000000..8bac6dc --- /dev/null +++ b/strace.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python +from ptrace import PtraceError +from ptrace.debugger import (PtraceDebugger, Application, + ProcessExit, ProcessSignal, NewProcessEvent, ProcessExecution) +from ptrace.syscall import (SYSCALL_NAMES, SYSCALL_PROTOTYPES, + FILENAME_ARGUMENTS, SOCKET_SYSCALL_NAMES) +from ptrace.func_call import FunctionCallOptions +from sys import stderr, exit +from optparse import OptionParser +from logging import getLogger, error +from ptrace.error import PTRACE_ERRORS, writeError +from ptrace.ctypes_tools import formatAddress +from ptrace.tools import signal_to_exitcode +import sys +import re + + +class SyscallTracer(Application): + + def __init__(self): + Application.__init__(self) + + # Parse self.options + self.parseOptions() + + # Setup output (log) + self.setupLog() + + def setupLog(self): + if self.options.output: + fd = open(self.options.output, 'w') + self._output = fd + else: + fd = stderr + self._output = None + self._setupLog(fd) + + def parseOptions(self): + parser = OptionParser( + usage="%prog [options] -- program [arg1 arg2 ...]") + self.createCommonOptions(parser) + parser.add_option("--enter", help="Show system call enter and exit", + action="store_true", default=False) + parser.add_option("--profiler", help="Use profiler", + action="store_true", default=False) + parser.add_option("--type", help="Display arguments type and result type (default: no)", + action="store_true", default=False) + parser.add_option("--name", help="Display argument name (default: no)", + action="store_true", default=False) + parser.add_option("--string-length", "-s", help="String max length (default: 300)", + type="int", default=300) + parser.add_option("--array-count", help="Maximum number of array items (default: 20)", + type="int", default=20) + parser.add_option("--raw-socketcall", help="Raw socketcall form", + action="store_true", default=False) + parser.add_option("--output", "-o", help="Write output to specified log file", + type="str") + parser.add_option("--ignore-regex", help="Regex used to filter syscall names (e.g. --ignore='^(gettimeofday|futex|f?stat)')", + type="str") + parser.add_option("--address", help="Display structure address", + action="store_true", default=False) + parser.add_option("--syscalls", '-e', help="Comma separated list of shown system calls (other will be skipped)", + type="str", default=None) + parser.add_option("--socket", help="Show only socket functions", + action="store_true", default=False) + parser.add_option("--filename", help="Show only syscall using filename", + action="store_true", default=False) + parser.add_option("--show-pid", + help="Prefix line with process identifier", + action="store_true", default=False) + parser.add_option("--list-syscalls", + help="Display system calls and exit", + action="store_true", default=False) + parser.add_option("-i", "--show-ip", + help="print instruction pointer at time of syscall", + action="store_true", default=False) + + self.createLogOptions(parser) + + self.options, self.program = parser.parse_args() + + if self.options.list_syscalls: + syscalls = list(SYSCALL_NAMES.items()) + syscalls.sort(key=lambda data: data[0]) + for num, name in syscalls: + print("% 3s: %s" % (num, name)) + exit(0) + + if self.options.pid is None and not self.program: + parser.print_help() + exit(1) + + # Create "only" filter + only = set() + if self.options.syscalls: + # split by "," and remove spaces + for item in self.options.syscalls.split(","): + item = item.strip() + if not item or item in only: + continue + ok = True + valid_names = list(SYSCALL_NAMES.values()) + for name in only: + if name not in valid_names: + print("ERROR: unknown syscall %r" % name, file=stderr) + ok = False + if not ok: + print(file=stderr) + print( + "Use --list-syscalls options to get system calls list", file=stderr) + exit(1) + # remove duplicates + only.add(item) + if self.options.filename: + for syscall, format in SYSCALL_PROTOTYPES.items(): + restype, arguments = format + if any(argname in FILENAME_ARGUMENTS for argtype, argname in arguments): + only.add(syscall) + if self.options.socket: + only |= SOCKET_SYSCALL_NAMES + self.only = only + if self.options.ignore_regex: + try: + self.ignore_regex = re.compile(self.options.ignore_regex) + except Exception as err: + print("Invalid regular expression! %s" % err) + print("(regex: %r)" % self.options.ignore_regex) + exit(1) + else: + self.ignore_regex = None + + if self.options.fork: + self.options.show_pid = True + + self.processOptions() + + def ignoreSyscall(self, syscall): + name = syscall.name + if self.only and (name not in self.only): + return True + if self.ignore_regex and self.ignore_regex.match(name): + return True + return False + + def displaySyscall(self, syscall): + text = syscall.format() + if syscall.result is not None: + text = "%-40s = %s" % (text, syscall.result_text) + prefix = [] + if self.options.show_pid: + prefix.append("[%s]" % syscall.process.pid) + if self.options.show_ip: + prefix.append("[%s]" % formatAddress(syscall.instr_pointer)) + if prefix: + text = ''.join(prefix) + ' ' + text + error(text) + + def syscallTrace(self, process): + # First query to break at next syscall + self.prepareProcess(process) + exitcode = 0 + while True: + # No more process? Exit + if not self.debugger: + break + + # Wait until next syscall enter + try: + event = self.debugger.waitSyscall() + except ProcessExit as event: + self.processExited(event) + if event.exitcode is not None: + exitcode = event.exitcode + continue + except ProcessSignal as event: + event.display() + event.process.syscall(event.signum) + exitcode = signal_to_exitcode(event.signum) + continue + except NewProcessEvent as event: + self.newProcess(event) + continue + except ProcessExecution as event: + self.processExecution(event) + continue + + # Process syscall enter or exit + self.syscall(event.process) + return exitcode + + def syscall(self, process): + state = process.syscall_state + syscall = state.event(self.syscall_options) + if syscall and (syscall.result is not None or self.options.enter): + self.displaySyscall(syscall) + + # Break at next syscall + process.syscall() + + def processExited(self, event): + # Display syscall which has not exited + state = event.process.syscall_state + if (state.next_event == "exit") \ + and (not self.options.enter) \ + and state.syscall: + self.displaySyscall(state.syscall) + + # Display exit message + error("*** %s ***" % event) + + def prepareProcess(self, process): + process.syscall() + process.syscall_state.ignore_callback = self.ignoreSyscall + + def newProcess(self, event): + process = event.process + error("*** New process %s ***" % process.pid) + self.prepareProcess(process) + process.parent.syscall() + + def processExecution(self, event): + process = event.process + error("*** Process %s execution ***" % process.pid) + process.syscall() + + def runDebugger(self): + # Create debugger and traced process + self.setupDebugger() + process = self.createProcess() + if not process: + return + + self.syscall_options = FunctionCallOptions( + write_types=self.options.type, + write_argname=self.options.name, + string_max_length=self.options.string_length, + replace_socketcall=not self.options.raw_socketcall, + write_address=self.options.address, + max_array_count=self.options.array_count, + ) + self.syscall_options.instr_pointer = self.options.show_ip + + return self.syscallTrace(process) + + def main(self): + if self.options.profiler: + from ptrace.profiler import runProfiler + exitcode = runProfiler(getLogger(), self._main) + else: + exitcode = self._main() + if self._output is not None: + self._output.close() + sys.exit(exitcode) + + def _main(self): + self.debugger = PtraceDebugger() + exitcode = 0 + try: + exitcode = self.runDebugger() + except ProcessExit as event: + self.processExited(event) + if event.exitcode is not None: + exitcode = event.exitcode + except PtraceError as err: + error("ptrace() error: %s" % err) + if err.errno is not None: + exitcode = err.errno + except KeyboardInterrupt: + error("Interrupted.") + exitcode = 1 + except PTRACE_ERRORS as err: + writeError(getLogger(), err, "Debugger error") + exitcode = 1 + self.debugger.quit() + return exitcode + + def createChild(self, program): + pid = Application.createChild(self, program) + error("execve(%s, %s, [/* 40 vars */]) = %s" % ( + program[0], program, pid)) + return pid + + +if __name__ == "__main__": + SyscallTracer().main() diff --git a/test_doc.py b/test_doc.py new file mode 100755 index 0000000..aef5cac --- /dev/null +++ b/test_doc.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +from doctest import testfile, ELLIPSIS, testmod +from sys import exit, path as sys_path +from os.path import dirname + + +def testDoc(filename, name=None): + print("--- %s: Run tests" % filename) + failure, nb_test = testfile( + filename, optionflags=ELLIPSIS, name=name) + if failure: + exit(1) + print("--- %s: End of tests" % filename) + + +def importModule(name): + mod = __import__(name) + components = name.split('.') + for comp in components[1:]: + mod = getattr(mod, comp) + return mod + + +def testModule(name): + print("--- Test module %s" % name) + module = importModule(name) + failure, nb_test = testmod(module) + if failure: + exit(1) + print("--- End of test") + + +def main(): + ptrace_dir = dirname(__file__) + sys_path.append(ptrace_dir) + + # Test documentation in doc/*.rst files + # testDoc('doc/c_tools.rst') + + # Test documentation of some functions/classes + testModule("ptrace.ctypes_tools") + testModule("ptrace.debugger.parse_expr") + testModule("ptrace.logging_tools") + testModule("ptrace.signames") + testModule("ptrace.syscall.socketcall") + testModule("ptrace.tools") + + +if __name__ == "__main__": + main() diff --git a/tests/crash/BSDmakefile b/tests/crash/BSDmakefile new file mode 100644 index 0000000..d5edbd6 --- /dev/null +++ b/tests/crash/BSDmakefile @@ -0,0 +1,13 @@ +SOURCES=stack_overflow.c div_zero.c call_null.c abort.c fork.c invalid_write.c invalid_read.c +PROGRAMS=${SOURCES:S/.c//g} pthread +CFLAGS=-Wall -O0 -g + +all: $(PROGRAMS) + +pthread: pthread.c + $(CC) -o $@ $(CFLAGS) pthread.c -lpthread + + +clean: + rm -f $(PROGRAMS) + diff --git a/tests/crash/Makefile b/tests/crash/Makefile new file mode 100644 index 0000000..b7eeb58 --- /dev/null +++ b/tests/crash/Makefile @@ -0,0 +1,13 @@ +CC=gcc +SOURCES=stack_overflow.c div_zero.c call_null.c abort.c fork.c execve.c invalid_write.c invalid_read.c socket_ipv4_tcp.c +PROGRAMS=$(patsubst %.c,%,$(SOURCES)) pthread +CFLAGS=-Wall -Wextra -Werror -O0 -g + +all: $(PROGRAMS) + +pthread: pthread.c + $(CC) -o $@ $(CFLAGS) pthread.c -lpthread + +clean: + rm -f $(PROGRAMS) + diff --git a/tests/crash/abort.c b/tests/crash/abort.c new file mode 100644 index 0000000..6b13ecd --- /dev/null +++ b/tests/crash/abort.c @@ -0,0 +1,6 @@ +#include +int main() +{ + abort(); + return 0; +} diff --git a/tests/crash/call_null.c b/tests/crash/call_null.c new file mode 100644 index 0000000..d6056c5 --- /dev/null +++ b/tests/crash/call_null.c @@ -0,0 +1,6 @@ +int main() +{ + void (*func) (void) = 0; + func(); + return 0; +} diff --git a/tests/crash/div_zero.c b/tests/crash/div_zero.c new file mode 100644 index 0000000..44f9254 --- /dev/null +++ b/tests/crash/div_zero.c @@ -0,0 +1,7 @@ +int main() +{ + int x = 1; + int y = 0; + x /= y; + return 0; +} diff --git a/tests/crash/execve.c b/tests/crash/execve.c new file mode 100644 index 0000000..e0f8bee --- /dev/null +++ b/tests/crash/execve.c @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +int main() +{ + char *arg[]= {"/bin/ls", NULL }; + int pid; + + pid = fork(); + + if (pid) { + waitpid(pid, NULL, 0); + exit(EXIT_SUCCESS); + } else { + (void)uname(NULL); + execve(arg[0], arg, NULL); + exit(EXIT_FAILURE); + } +} + diff --git a/tests/crash/fork.c b/tests/crash/fork.c new file mode 100644 index 0000000..6a05f25 --- /dev/null +++ b/tests/crash/fork.c @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include + +int parent_pid; + +void parent(pid_t child_pid) +{ + int status; + int result; + + printf("[parent:%i] wait for child exit\n", parent_pid); + result = waitpid(child_pid, &status, 0); + if (result != child_pid) { + perror("waitpid"); + exit(1); + } + + printf("[parent:%i] child exited with status %i\n", + parent_pid, status); +} + +void child() +{ + struct utsname buf; + int err; + int pid = getpid(); + + printf("[child:%i] uname()\n", pid); + + err = uname(&buf); + if (err) { + perror("uname"); + exit(1); + } + + printf("[child:%i] done, exit.\n", pid); +} + +int main() +{ + pid_t pid; + + /* fork process */ + parent_pid = getpid(); + printf("[parent:%i] fork\n", parent_pid); + pid = fork(); + if (pid < 0) { + perror("fork"); + exit(1); + } + + if (pid) { + parent(pid); + } else { + child(); + } + return 0; +} diff --git a/tests/crash/invalid_read.c b/tests/crash/invalid_read.c new file mode 100644 index 0000000..615ef58 --- /dev/null +++ b/tests/crash/invalid_read.c @@ -0,0 +1,14 @@ +typedef struct +{ + int x; + int y; + int z; +} point_t; + +int main() +{ + point_t *point = 0; + int z; + z = point->z; + return z; +} diff --git a/tests/crash/invalid_write.c b/tests/crash/invalid_write.c new file mode 100644 index 0000000..fc52444 --- /dev/null +++ b/tests/crash/invalid_write.c @@ -0,0 +1,13 @@ +typedef struct +{ + int x; + int y; + int z; +} point_t; + +int main() +{ + point_t *point = 0; + point->z = 42; + return 0; +} diff --git a/tests/crash/pthread.c b/tests/crash/pthread.c new file mode 100644 index 0000000..ff7c6bf --- /dev/null +++ b/tests/crash/pthread.c @@ -0,0 +1,27 @@ +#include +#include + +#define UNUSED(x) x __attribute__((unused)) + +pthread_t thread; +pthread_mutex_t mutex; +int global_counter; + +void* nothing(void *UNUSED(data)) +{ + printf("[thread] exit thread\n"); + pthread_exit(NULL); +} + +int main() +{ + global_counter = 1; + + printf("[main] create thread\n"); + pthread_create(&thread, NULL, nothing, NULL); + printf("[main] join thread\n"); + pthread_join(thread, NULL); + printf("[main] exit\n"); + pthread_mutex_destroy(&mutex); + return 0; +} diff --git a/tests/crash/socket_ipv4_tcp.c b/tests/crash/socket_ipv4_tcp.c new file mode 100644 index 0000000..cc8fc41 --- /dev/null +++ b/tests/crash/socket_ipv4_tcp.c @@ -0,0 +1,31 @@ +#include +#include /* close() */ +#include /* perror() */ +#include /* struct sockaddr_in */ + +int main() +{ + int fd; + int val; + int ret; + struct sockaddr_in addr; + + fd = socket(PF_INET, SOCK_STREAM, 0); + if (fd < 0) + perror("socket"); + + val = 1; + (void)setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + + addr.sin_family = AF_INET; + addr.sin_port = htons(80); + addr.sin_addr.s_addr = 0x7f000001; + ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr)); + if (ret < 0) + perror("connect"); + + ret = close(fd); + if (ret) + perror("close"); + return 0; +} diff --git a/tests/crash/stack_overflow.c b/tests/crash/stack_overflow.c new file mode 100644 index 0000000..45f26a9 --- /dev/null +++ b/tests/crash/stack_overflow.c @@ -0,0 +1,13 @@ +char toto() +{ + char buffer[4096]; + buffer[0] = 0; + toto(); + return buffer[0] + buffer[sizeof(buffer)-1]; +} + +int main() +{ + char c = toto(); + return c; +} diff --git a/tests/test_gdb.py b/tests/test_gdb.py new file mode 100755 index 0000000..6a4f1cc --- /dev/null +++ b/tests/test_gdb.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +import os +import re +import subprocess +import sys +import unittest + +GDB = os.path.normpath(os.path.join(os.path.dirname(__file__), '..', 'gdb.py')) + + +class TestGdb(unittest.TestCase): + + def run_command(self, command): + if isinstance(command, str): + command = command.encode('ascii') + command = command + b'\n' + args = [sys.executable, GDB, '--', sys.executable, '-c', 'pass'] + proc = subprocess.Popen(args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + stdout, _ = proc.communicate(command) + exitcode = proc.wait() + self.assertEqual(exitcode, 0) + if stdout.startswith(b'(gdb) '): + stdout = stdout[6:] + pos = stdout.rfind(b'(gdb) ') + if pos: + stdout = stdout[:pos] + stdout = stdout.rstrip() + if b'Traceback' in stdout: + self.fail('Traceback in output: %r' % stdout) + return stdout + + def check_stdout(self, pattern, stdout): + self.assertTrue(re.search(pattern, stdout, re.MULTILINE), + (pattern, stdout)) + + def test_proc(self): + stdout = self.run_command('proc') + for pattern in ( + b'^Process ID: [0-9]+', + b'^Process state: ', + b'^Process environment: ', + b'^User identifier: [0-9]+', + b'^Group identifier: [0-9]+', + ): + self.assertTrue(re.search(pattern, stdout, re.MULTILINE), + (pattern, stdout)) + + def test_print(self): + stdout = self.run_command('print 1+2') + self.check_stdout(b'^Decimal: 3\n', stdout) + + def test_where(self): + stdout = self.run_command('where') + self.check_stdout(b'^CODE:', stdout) + + def test_regs(self): + # Just check that the command doesn't raise an exception + self.run_command('regs') + + def test_backtrace(self): + # Just check that the command doesn't raise an exception + self.run_command('backtrace') + + def test_maps(self): + stdout = self.run_command('maps') + self.check_stdout(b'^MAPS: ', stdout) + + def test_dbginfo(self): + stdout = self.run_command('dbginfo') + self.check_stdout(b'^Debugger process ID: [0-9]+', stdout) + self.check_stdout(b'^python-ptrace version [0-9]+\\.[0-9]+', stdout) + self.check_stdout(b'^Website: ', stdout) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_strace.py b/tests/test_strace.py new file mode 100755 index 0000000..2020a3b --- /dev/null +++ b/tests/test_strace.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python +import os +import re +import subprocess +import sys +import tempfile +import unittest +import signal + +STRACE = os.path.normpath( + os.path.join(os.path.dirname(__file__), '..', 'strace.py')) + +AARCH64 = (getattr(os.uname(), 'machine', None) == 'aarch64') +RISCV = (getattr(os.uname(), 'machine', None).startswith('riscv')) + + +class TestStrace(unittest.TestCase): + def strace(self, *args): + """ Strace the given command and return the strace output. """ + with tempfile.NamedTemporaryFile(mode='wb+') as temp: + args = (sys.executable, STRACE, '-o', temp.name, '--') + args + with open(os.devnull, "wb") as devnull: + proc = subprocess.Popen(args, + stdout=devnull, + stderr=subprocess.STDOUT) + exitcode = proc.wait() + + temp.seek(0) + strace = temp.readlines() + strace = b''.join(strace) + self.assertIsNone(re.match(b'^Traceback', strace), strace) + return strace, exitcode + + def assert_syscall(self, code, regex): + """ + Strace the given python code and match the strace output against the + given regular expression. + """ + stdout, _ = self.strace(sys.executable, '-c', code) + pattern = re.compile(regex, re.MULTILINE) + self.assertTrue(pattern.search(stdout), stdout) + + def test_basic(self): + stdout, _ = self.strace(sys.executable, '-c', 'pass') + for syscall in (b'exit', b'mmap', b'open'): + pattern = re.compile(b'^' + syscall, re.MULTILINE) + self.assertTrue(pattern.search(stdout), stdout) + + def test_exitcode(self): + for ec in range(2): + stdout, exitcode = self.strace(sys.executable, '-c', 'exit(%d)' % ec) + self.assertEqual(exitcode, ec) + + def test_exitsignal(self): + signum = int(signal.SIGQUIT) + stdout, exitcode = self.strace(sys.executable, '-c', 'import os; os.kill(os.getpid(), %d)' % signum) + self.assertEqual(exitcode, (127 + 1) + signum) + + def test_getcwd(self): + cwd = os.getcwd() + stdout, _ = self.strace(sys.executable, '-c', 'import os; os.getcwd()') + pattern = re.compile(b'^getcwd\\((.*),', re.MULTILINE) + match = pattern.search(stdout) + self.assertTrue(match, stdout) + expected = repr(cwd) + expected = os.fsencode(expected) + self.assertEqual(match.group(1), expected) + + def test_open(self): + code = 'open(%a).close()' % __file__ + self.assert_syscall( + code, br"^open(at)?\(.*test_strace\.pyc?', O_RDONLY(\|O_CLOEXEC)?") + + def test_chdir(self): + self.assert_syscall("import os; os.chdir('directory')", + br"^chdir\('directory'\)\s+= -2 ENOENT") + + def test_rename(self): + pattern = br"^rename\('oldpath', 'newpath'\)" + if AARCH64: + pattern = br"^renameat\(.*'oldpath'.*'newpath'\)" + if RISCV: + pattern = br"^renameat2\(.*'oldpath'.*'newpath', 0\)" + self.assert_syscall("import os; os.rename('oldpath', 'newpath')", + pattern) + + def test_link(self): + pattern = br"^link\('oldpath', 'newpath'\)" + if AARCH64 or RISCV: + pattern = br"^linkat\(.*'oldpath'.*'newpath'.*\)" + self.assert_syscall("import os; os.link('oldpath', 'newpath')", + pattern) + + def test_symlink(self): + pattern = br"^symlink\('target', 'linkpath'\)" + if AARCH64 or RISCV: + pattern = br"^symlinkat\(.*'target'.*'linkpath'\)" + try: + self.assert_syscall("import os; os.symlink('target', 'linkpath')", + pattern) + finally: + try: + os.unlink('linkpath') + except OSError: + pass + + def test_socket(self): + self.assert_syscall( + "import socket; socket.socket(socket.AF_INET,socket.SOCK_STREAM).close()", + br'^socket\(AF_INET, SOCK_STREAM(\|SOCK_CLOEXEC)?') + + +if __name__ == "__main__": + unittest.main() diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..a15647a --- /dev/null +++ b/tox.ini @@ -0,0 +1,21 @@ +[tox] +envlist = py3, pep8 + +[testenv] +basepython = python3 +commands= + python test_doc.py + python runtests.py -v + +[testenv:py3] +basepython = python3 + +[testenv:pep8] +deps = flake8 +commands = + flake8 ptrace/ tests/ gdb.py runtests.py setup_cptrace.py setup.py strace.py SYSCALL_PROTOTYPES.codegen.py test_doc.py + +[flake8] +# E501 line too long (88 > 79 characters) +# W503 line break before binary operator +ignore = E501,W503