pax_global_header00006660000000000000000000000064136205252330014513gustar00rootroot0000000000000052 comment=5e422c0ec87b41d14e9ce8527983406718ef64e0 uftrace-0.9.4/000077500000000000000000000000001362052523300131565ustar00rootroot00000000000000uftrace-0.9.4/.gitignore000066400000000000000000000006661362052523300151560ustar00rootroot00000000000000*.o *.op *.ot a.out ftrace uftrace ftrace.dir ftrace.dir.old ftrace.data ftrace.data.old uftrace.data uftrace.data.old !gdb/uftrace/*.py libmcount.so libmcount-*.so libcygprof.so libcygprof-nop.so gmon.out tests/t-* tests/arch/*/t-* tests/*.so check-deps/* !check-deps/*.c !check-deps/Makefile* FLAGS version.h .config .build/ GPATH GRTAGS GTAGS TAGS tags cscope.* *.pyc *.sw[opn] *.patch *.orig *.gcda *.gcno misc/demangler misc/symbols uftrace-0.9.4/.travis.yml000066400000000000000000000030031362052523300152630ustar00rootroot00000000000000language: c # Ubuntu 16.04 LTS dist: xenial sudo: required install: - sudo apt-get -qq update - sudo misc/install-deps.sh -y script: ./configure && make ASAN=1 && make ASAN=1 unittest env: global: # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created # via the "travis encrypt" command using the project repo's public key - secure: "zl83nEBIlkt3inI+jtGCDmrA3Lzdylf7Ez81soIPEWlirR2nZGfwhnHzfV8BCl+vaxaDIZYhHcHNF90Qt2WEOZ980WfXS0PPMslEWsL57N1oh2/5/dWHANL/+hlw11EiwtWNm6DEY4E2WyrCRinzxoVkH+UgKMV0cLPs5y5qMLd+/oqfDKD1eMp91GsEB+mzK7/qoS1H8TbubbyS3nFpUj9ZqsZwBGatL62vZYiTnaw+8M9BANFl/ctE9dchNNhKUYxwXMLKmE/ET7Ryk0Ikf1QojG0NI7xUyoZsVDifipYTWOIKJQWVxJwlyUE9QZsrYIVM/5qbeOlDKOwTtvOXW3joSlYrgNv5Bp3UJJAUxIrH5X45e1bi7TTiJ/KhT48+jTLtKp619PUuw23hlHKMK6p9niXcA/tNHCEnQTd8GuKOHV3ebAipeIBcuNTTmR4AnnzwB870UAFF9cS1YMAeW1wDXxO9w8CiwKnOlFRfko+YkC7H+DhuEn7Fg7nB6VoNCgCle5SUnTAbMIuH4TqlPI6ix9SHUU+jIWn+hS9Ck5vecGzvEglsOsiXsZ/dx3NzkEO35JFc4XhmCk9VQsOgJFgufC3vB2sG4cQML09PdbyPQ4XMuAgDB3EmjRwa6IiUuUuBqYqBSmZyhZOp3xBtAebg5MTz+fRQ+2ku/RM54gE=" before_install: - echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- addons: coverity_scan: project: name: "namhyung/uftrace" description: "Build submitted via Travis CI" notification_email: namhyung@gmail.com build_command_prepend: "./configure; make clean" build_command: "make -j 4" branch_pattern: master uftrace-0.9.4/CONTRIBUTING.md000066400000000000000000000061621362052523300154140ustar00rootroot00000000000000Contributing to uftrace ======================= Thanks for considering contribution to uftrace. You can git clone the uftrace source on the following address and send PR with your patch. But, before doing that, I recommend you to read this to follow the conventions. https://github.com/namhyung/uftrace Coding style ------------ The uftrace is written in C and mostly follows the coding style of the Linux kernel [1]. The only different is where to put the closing brace and start of subsequent block. I prefer to put it at a separate line for readability. For example: if (cond == A) { do_some_thing(); } else if (cond == B) { do_other_thing(); } Please note that the position of the "else if" line. For python programs (for tests or scripts), use 4 spaces to indent. [1] https://www.kernel.org/doc/Documentation/process/coding-style.rst Include subject word in message header -------------------------------------- Although uftrace has a small codebase, I believe it's a good convention to prefix your subject line with colon. This lets me and other developers more easily distinguish patches from other subject. $ git log --oneline --graph * fef4226 Merge branch 'misc-fix' |\ | * 54a4ef0 test: Fix to be able to call runtest.py directly | * 6bbe4a0 graph: Skip kernel functions outside of user | * a76c7cb kernel: Use real address for filter match |/ ... Signing your patch ------------------ The sign-off is a simple line at the end of the explanation for the patch, which certifies that you wrote it or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify the below: Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. then you just add a line saying Signed-off-by: Random J Developer using your real name (sorry, no pseudonyms or anonymous contributions.) uftrace-0.9.4/COPYING000066400000000000000000000432541362052523300142210ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, 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 Lesser 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 Street, 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 Lesser General Public License instead of this License. uftrace-0.9.4/INSTALL.md000066400000000000000000000164751362052523300146230ustar00rootroot00000000000000QUICK GUIDE =========== On Linux distros, following commands will build and install uftrace from source. $ sudo misc/install-deps.sh # optional for advanced features $ ./configure # --prefix can be used to change install dir $ make $ sudo make install For more information, please see below. GETTING THE SOURCE ================== The latest version of uftrace is available at Github. https://github.com/namhyung/uftrace DEPENDENCY ========== The uftrace is written in C and tried to minimize external dependencies. Currently, uftrace can be built without any external libraries. But in order to use more advanced features, it'd be better to install them like below. Firstly, please make sure `pkg-config` is installed in the system to properly detect the dependencies of uftrace. Otherwise, some packages may not be detected even if they are already installed and it disables some features of uftrace. Historically uftrace depended on the `libelf` from elfutils project for ELF file manipulation. While it's not mandatory anymore, we recommend you to install it for better handling of ELF binaries. Also `libdw` library is recommended to be installed in order to process DWARF debug information. The libdw itself depends on the `libelf`, so you can just install `libdw`. On debian based systems (like Ubuntu), `libdw-dev` package will provide required libraries/files. $ sudo apt-get install libdw-dev On redhat based systems (like Fedora, RHEL), it'll be `elfutils-devel`. $ sudo dnf install elfutils-devel It also uses libstdc++ library to demangle C++ symbols in full detail. But it's not mandatory as uftrace has its own demangler for shorter symbol name (it omits arguments, templates and so on). And ncursesw library is to implement text user interface (TUI) on console. The ncurses(w) library provides terminal handling routines so `uftrace tui` command is built on top of them. As it improves user experience of trace data analysis, you need to consider install it if you do things like `uftrace graph` or `uftrace report` frequently. Also it needs `pandoc` to build man pages from the markdown document. BUILD ===== To build uftrace, you need to install basic software development tools first - like gcc and make. And also you need to install dependent softwares, please see DEPENDENCY section for details. Once you installed required software(s), you can run `make` to build it. $ make It builds uftrace and resulting binary exists in the current directory. This is good for testing, but you'll want to install it for normal use. $ sudo make install It installs the uftrace under /usr/local by default, if you want install it to other location, you can set the `prefix` variable when invoking the configure before running make. (see below). $ ./configure --prefix=/usr $ make $ sudo make install The output of build looks like linux kernel style, users can see original build command lines with V=1 (like kernel). $ make V=1 CONFIGURATION ============= The uftrace implements own version of configure script to save user preferences. The config file (named `.config`) is created if not exist on build time with default options. User can set custom installation directories or build directory with this script. $ ./configure --help Usage: ./configure [] --help print this message --prefix= set install root dir as (default: /usr/local) --bindir= set executable install dir as (default: ${prefix}/bin) --libdir= set library install dir as (default: ${prefix}/lib) --mandir= set manual doc install dir as (default: ${prefix}/share/man) --objdir= set build dir as (default: ${PWD}) --sysconfdir= override the etc dir as --with-elfutils= search for elfutils in /include and /lib --without-libelf build without libelf (and libdw) (even if found on the system) --without-libdw build without libdw (even if found on the system) --without-libstdc++ build without libstdc++ (even if found on the system) --without-libpython build without libpython (even if found on the system) --without-libluajit build without libluajit (even if found on the system) --without-libncurses build without libncursesw (even if found on the system) --without-capstone build without libcapstone (even if found on the system) --without-perf build without perf event (even if available) --without-schedule build without scheduler event (even if available) --arch= set target architecture (default: system default arch) e.g. x86_64, aarch64, i386, or arm --cross-compile= Specify the compiler prefix during compilation e.g. CC is overridden by $(CROSS_COMPILE)gcc --cflags= pass extra C compiler flags --ldflags= pass extra linker flags -p preserve old setting Some influential environment variables: ARCH Target architecture e.g. x86_64, aarch64, i386, or arm CROSS_COMPILE Specify the compiler prefix during compilation e.g. CC is overridden by $(CROSS_COMPILE)gcc CFLAGS C compiler flags LDFLAGS linker flags Also you can set the target architecture and compiler options like CC, CFLAGS. It's also possible to disable some features depending on external libraries or system behaviors. For example --without-libpython option will make scripting feature disabled - `uftrace script` command will still exist but won't work. For cross compile, you may want to setup the toolchain something like below: $ export CROSS_COMPILE=/path/to/cross/toolchain/arm-unknown-linux-gnueabihf- $ ARCH=arm CFLAGS='--sysroot /path/to/sysroot' ./configure or $ ./configure --arch=arm --cflags='--sysroot /path/to/sysroot' \ --cross-compile=/path/to/cross/toolchain/arm-unknown-linux-gnueabihf- This assumes you already installed the cross-built `libelf` on the sysroot directory. Otherwise, you can also build it from source (please see below) or use it on a different path using `--with-elfutils=`. BUILD WITH ELFUTILS (libelf) ============================ It may be useful to manually compile libelf/libdw for uftrace build if the target system doesn't have them installed. `misc/install-elfutils.sh` provides a way to download and build libelf and libdw, which are libraries in elfutils. The below is the way to compile uftrace together with libelf/libdw. $ export CROSS_COMPILE=arm-linux-gnueabi- $ export ARCH=arm $ export CFLAGS="-march=armv7-a" $ ./misc/install-elfutils.sh --prefix=/path/to/install $ ./configure --prefix=/path/to/install --with-elfutils=/path/to/install $ make $ make install `misc/install-elfutils.sh` downloads and builds elfutils and install both libelf and libdw to prefix directory. The installed libelf and libdw can be found using `--with-elfutils` in `configure` script. uftrace-0.9.4/Makefile000066400000000000000000000356011362052523300146230ustar00rootroot00000000000000VERSION := 0.9.4 # Makefiles suck: This macro sets a default value of $(2) for the # variable named by $(1), unless the variable has been set by # environment or command line. This is necessary for CC and AR # because make sets default values, so the simpler ?= approach # won't work as expected. define allow-override $(if $(or $(findstring environment,$(origin $(1))),\ $(findstring command line,$(origin $(1)))),,\ $(eval $(1) = $(2))) endef # Allow setting CC and AR and LD, or setting CROSS_COMPILE as a prefix. $(call allow-override,CC,$(CROSS_COMPILE)gcc) $(call allow-override,AR,$(CROSS_COMPILE)ar) $(call allow-override,LD,$(CROSS_COMPILE)ld) uname_M := $(shell uname -m 2>/dev/null || echo not) ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/ -e s/arm.*/arm/ ) ifneq ($(findstring x86,$(ARCH)),) ifneq ($(findstring m32,$(CC) $(CFLAGS)),) override ARCH := i386 endif endif prefix ?= /usr/local bindir = $(prefix)/bin libdir = $(prefix)/lib etcdir = $(prefix)/etc mandir = $(prefix)/share/man docdir = $(srcdir)/doc ifeq ($(DOCLANG), ko) docdir = $(srcdir)/doc/ko endif srcdir = $(CURDIR) # set objdir to $(O) by default (if any) ifeq ($(objdir),) ifneq ($(O),) objdir = $(O) else objdir = $(CURDIR) endif endif ifneq ($(wildcard $(objdir)/.config),) include $(objdir)/.config endif RM = rm -f INSTALL = install export ARCH CC AR LD RM srcdir objdir LDFLAGS COMMON_CFLAGS := -D_GNU_SOURCE $(CFLAGS) $(CPPFLAGS) COMMON_CFLAGS += -iquote $(srcdir) -iquote $(objdir) -iquote $(srcdir)/arch/$(ARCH) #CFLAGS-DEBUG = -g -D_GNU_SOURCE $(CFLAGS_$@) COMMON_LDFLAGS := -lrt -ldl -pthread -Wl,-z,noexecstack $(LDFLAGS) ifneq ($(elfdir),) COMMON_CFLAGS += -I$(elfdir)/include COMMON_LDFLAGS += -L$(elfdir)/lib endif COMMON_CFLAGS += -W -Wall -Wno-unused-parameter -Wno-missing-field-initializers # # Note that the plain CFLAGS and LDFLAGS can be changed # by config/Makefile later but *_*FLAGS can not. # UFTRACE_CFLAGS = $(COMMON_CFLAGS) $(CFLAGS_$@) $(CFLAGS_uftrace) DEMANGLER_CFLAGS = $(COMMON_CFLAGS) $(CFLAGS_$@) $(CFLAGS_demangler) SYMBOLS_CFLAGS = $(COMMON_CFLAGS) $(CFLAGS_$@) $(CFLAGS_symbols) TRACEEVENT_CFLAGS = $(COMMON_CFLAGS) $(CFLAGS_$@) $(CFLAGS_traceevent) LIB_CFLAGS = $(COMMON_CFLAGS) $(CFLAGS_$@) $(CFLAGS_lib) LIB_CFLAGS += -fPIC -fvisibility=hidden -fno-omit-frame-pointer TEST_CFLAGS = $(COMMON_CFLAGS) -DUNIT_TEST UFTRACE_LDFLAGS = $(COMMON_LDFLAGS) $(LDFLAGS_$@) $(LDFLAGS_uftrace) DEMANGLER_LDFLAGS = $(COMMON_LDFLAGS) $(LDFLAGS_$@) $(LDFLAGS_demangler) SYMBOLS_LDFLAGS = $(COMMON_LDFLAGS) $(LDFLAGS_$@) $(LDFLAGS_symbols) LIB_LDFLAGS = $(COMMON_LDFLAGS) $(LDFLAGS_$@) $(LDFLAGS_lib) -Wl,--no-undefined TEST_LDFLAGS = $(COMMON_LDFLAGS) -L$(objdir)/libtraceevent -ltraceevent ifeq ($(DEBUG), 1) COMMON_CFLAGS += -O0 -g else COMMON_CFLAGS += -O2 -g endif ifeq ($(TRACE), 1) TRACE_CFLAGS := -pg -fno-omit-frame-pointer UFTRACE_CFLAGS += $(TRACE_CFLAGS) DEMANGLER_CFLAGS += $(TRACE_CFLAGS) SYMBOLS_CFLAGS += $(TRACE_CFLAGS) TRACEEVENT_CFLAGS += $(TRACE_CFLAGS) TEST_CFLAGS += $(TRACE_CFLAGS) # cannot add -pg to LIB_CFLAGS because mcount() is not reentrant endif ifeq ($(COVERAGE), 1) COVERAGE_CFLAGS := -O0 -g --coverage -U_FORTIFY_SOURCE COMMON_CFLAGS += $(COVERAGE_CFLAGS) LIB_CFLAGS += $(COVERAGE_CFLAGS) TEST_CFLAGS += $(COVERAGE_CFLAGS) LIB_LDFLAGS += --coverage endif ifeq ($(ASAN), 1) ASAN_CFLAGS := -O0 -g -fsanitize=address,leak UFTRACE_CFLAGS += $(ASAN_CFLAGS) DEMANGLER_CFLAGS += $(ASAN_CFLAGS) SYMBOLS_CFLAGS += $(ASAN_CFLAGS) TRACEEVENT_CFLAGS += $(ASAN_CFLAGS) TEST_CFLAGS += $(ASAN_CFLAGS) endif ifneq ($(SAN),) ifeq ($(SAN), all) SAN_CFLAGS := -O0 -g -fsanitize=address,leak,undefined else SAN_CFLAGS := -O0 -g -fsanitize=$(SAN) endif UFTRACE_CFLAGS += $(SAN_CFLAGS) DEMANGLER_CFLAGS += $(SAN_CFLAGS) SYMBOLS_CFLAGS += $(SAN_CFLAGS) TRACEEVENT_CFLAGS += $(SAN_CFLAGS) TEST_CFLAGS += $(SAN_CFLAGS) endif export UFTRACE_CFLAGS LIB_CFLAGS TEST_CFLAGS TEST_LDFLAGS VERSION_GIT := $(shell git describe --tags 2> /dev/null || echo v$(VERSION)) all: ifneq ($(wildcard $(srcdir)/check-deps/check-tstamp),) include $(srcdir)/check-deps/Makefile.check endif include $(srcdir)/Makefile.include LIBMCOUNT_TARGETS := libmcount/libmcount.so libmcount/libmcount-fast.so LIBMCOUNT_TARGETS += libmcount/libmcount-single.so libmcount/libmcount-fast-single.so _TARGETS := uftrace libtraceevent/libtraceevent.a _TARGETS += $(LIBMCOUNT_TARGETS) libmcount/libmcount-nop.so _TARGETS += misc/demangler misc/symbols TARGETS := $(patsubst %,$(objdir)/%,$(_TARGETS)) UFTRACE_SRCS := $(srcdir)/uftrace.c $(wildcard $(srcdir)/cmds/*.c $(srcdir)/utils/*.c) UFTRACE_OBJS := $(patsubst $(srcdir)/%.c,$(objdir)/%.o,$(UFTRACE_SRCS)) UFTRACE_OBJS_VERSION := $(objdir)/cmds/script.o $(objdir)/cmds/tui.o UFTRACE_OBJS_VERSION += $(objdir)/cmds/dump.o $(objdir)/cmds/info.o DEMANGLER_SRCS := $(srcdir)/misc/demangler.c $(srcdir)/utils/demangle.c DEMANGLER_SRCS += $(srcdir)/utils/debug.c $(srcdir)/utils/utils.c DEMANGLER_OBJS := $(patsubst $(srcdir)/%.c,$(objdir)/%.o,$(DEMANGLER_SRCS)) SYMBOLS_SRCS := $(srcdir)/misc/symbols.c $(srcdir)/utils/session.c SYMBOLS_SRCS += $(srcdir)/utils/demangle.c $(srcdir)/utils/rbtree.c SYMBOLS_SRCS += $(srcdir)/utils/utils.c $(srcdir)/utils/debug.c SYMBOLS_SRCS += $(srcdir)/utils/filter.c $(srcdir)/utils/dwarf.c SYMBOLS_SRCS += $(srcdir)/utils/auto-args.c $(srcdir)/utils/regs.c SYMBOLS_SRCS += $(wildcard $(srcdir)/utils/symbol*.c) SYMBOLS_OBJS := $(patsubst $(srcdir)/%.c,$(objdir)/%.o,$(SYMBOLS_SRCS)) UFTRACE_ARCH_OBJS := $(objdir)/arch/$(ARCH)/uftrace.o UFTRACE_HDRS := $(filter-out $(srcdir)/version.h,$(wildcard $(srcdir)/*.h $(srcdir)/utils/*.h)) UFTRACE_HDRS += $(srcdir)/libmcount/mcount.h $(wildcard $(srcdir)/arch/$(ARCH)/*.h) LIBMCOUNT_SRCS := $(filter-out %-nop.c,$(wildcard $(srcdir)/libmcount/*.c)) LIBMCOUNT_OBJS := $(patsubst $(srcdir)/%.c,$(objdir)/%.op,$(LIBMCOUNT_SRCS)) LIBMCOUNT_FAST_OBJS := $(patsubst $(objdir)/%.op,$(objdir)/%-fast.op,$(LIBMCOUNT_OBJS)) LIBMCOUNT_SINGLE_OBJS := $(patsubst $(objdir)/%.op,$(objdir)/%-single.op,$(LIBMCOUNT_OBJS)) LIBMCOUNT_FAST_SINGLE_OBJS := $(patsubst $(objdir)/%.op,$(objdir)/%-fast-single.op,$(LIBMCOUNT_OBJS)) LIBMCOUNT_UTILS_SRCS += $(srcdir)/utils/debug.c $(srcdir)/utils/regs.c LIBMCOUNT_UTILS_SRCS += $(srcdir)/utils/rbtree.c $(srcdir)/utils/filter.c LIBMCOUNT_UTILS_SRCS += $(srcdir)/utils/demangle.c $(srcdir)/utils/utils.c LIBMCOUNT_UTILS_SRCS += $(srcdir)/utils/script.c $(srcdir)/utils/script-python.c $(srcdir)/utils/script-luajit.c LIBMCOUNT_UTILS_SRCS += $(srcdir)/utils/auto-args.c $(srcdir)/utils/dwarf.c LIBMCOUNT_UTILS_SRCS += $(wildcard $(srcdir)/utils/symbol*.c) LIBMCOUNT_UTILS_OBJS := $(patsubst $(srcdir)/utils/%.c,$(objdir)/libmcount/%.op,$(LIBMCOUNT_UTILS_SRCS)) LIBMCOUNT_NOP_SRCS := $(srcdir)/libmcount/mcount-nop.c LIBMCOUNT_NOP_OBJS := $(patsubst $(srcdir)/%.c,$(objdir)/%.op,$(LIBMCOUNT_NOP_SRCS)) LIBMCOUNT_ARCH_OBJS := $(objdir)/arch/$(ARCH)/mcount-entry.op COMMON_DEPS := $(objdir)/.config $(UFTRACE_HDRS) LIBMCOUNT_DEPS := $(COMMON_DEPS) $(srcdir)/libmcount/internal.h CFLAGS_$(objdir)/mcount.op = -pthread CFLAGS_$(objdir)/cmds/record.o = -DINSTALL_LIB_PATH='"$(libdir)"' CFLAGS_$(objdir)/cmds/live.o = -DINSTALL_LIB_PATH='"$(libdir)"' LDFLAGS_$(objdir)/uftrace = -L$(objdir)/libtraceevent -ltraceevent -ldl LIBMCOUNT_FAST_CFLAGS := -DDISABLE_MCOUNT_FILTER LIBMCOUNT_SINGLE_CFLAGS := -DSINGLE_THREAD LIBMCOUNT_FAST_SINGLE_CFLAGS := -DDISABLE_MCOUNT_FILTER -DSINGLE_THREAD CFLAGS_$(objdir)/utils/demangle.o = -Wno-unused-value CFLAGS_$(objdir)/utils/demangle.op = -Wno-unused-value MAKEFLAGS += --no-print-directory all: $(objdir)/.config $(TARGETS) $(objdir)/.config: $(srcdir)/configure $(srcdir)/check-deps/Makefile $(QUIET_GEN)$(srcdir)/configure -p -o $@ $(MAKEOVERRIDES) @$(MAKE) -C $(objdir) # The above recursive make will handle all build procedure with # updated dependency. So just abort the current build. $(error) config: $(srcdir)/configure $(QUIET_GEN)$(srcdir)/configure -o $(objdir)/.config $(MAKEOVERRIDES) $(LIBMCOUNT_UTILS_OBJS): $(objdir)/libmcount/%.op: $(srcdir)/utils/%.c $(LIBMCOUNT_DEPS) $(QUIET_CC_FPIC)$(CC) $(LIB_CFLAGS) -c -o $@ $< $(objdir)/libmcount/mcount.op: $(objdir)/version.h $(LIBMCOUNT_OBJS): $(objdir)/%.op: $(srcdir)/%.c $(LIBMCOUNT_DEPS) $(QUIET_CC_FPIC)$(CC) $(LIB_CFLAGS) -c -o $@ $< $(LIBMCOUNT_FAST_OBJS): $(objdir)/%-fast.op: $(srcdir)/%.c $(LIBMCOUNT_DEPS) $(QUIET_CC_FPIC)$(CC) $(LIB_CFLAGS) $(LIBMCOUNT_FAST_CFLAGS) -c -o $@ $< $(LIBMCOUNT_SINGLE_OBJS): $(objdir)/%-single.op: $(srcdir)/%.c $(LIBMCOUNT_DEPS) $(QUIET_CC_FPIC)$(CC) $(LIB_CFLAGS) $(LIBMCOUNT_SINGLE_CFLAGS) -c -o $@ $< $(LIBMCOUNT_FAST_SINGLE_OBJS): $(objdir)/%-fast-single.op: $(srcdir)/%.c $(LIBMCOUNT_DEPS) $(QUIET_CC_FPIC)$(CC) $(LIB_CFLAGS) $(LIBMCOUNT_FAST_SINGLE_CFLAGS) -c -o $@ $< $(LIBMCOUNT_NOP_OBJS): $(objdir)/%.op: $(srcdir)/%.c $(LIBMCOUNT_DEPS) $(QUIET_CC_FPIC)$(CC) $(LIB_CFLAGS) -c -o $@ $< $(objdir)/libmcount/libmcount.so: $(LIBMCOUNT_OBJS) $(LIBMCOUNT_UTILS_OBJS) $(LIBMCOUNT_ARCH_OBJS) $(QUIET_LINK)$(CC) -shared -o $@ $^ $(LIB_LDFLAGS) $(objdir)/libmcount/libmcount-fast.so: $(LIBMCOUNT_FAST_OBJS) $(LIBMCOUNT_UTILS_OBJS) $(LIBMCOUNT_ARCH_OBJS) $(QUIET_LINK)$(CC) -shared -o $@ $^ $(LIB_LDFLAGS) $(objdir)/libmcount/libmcount-single.so: $(LIBMCOUNT_SINGLE_OBJS) $(LIBMCOUNT_UTILS_OBJS) $(LIBMCOUNT_ARCH_OBJS) $(QUIET_LINK)$(CC) -shared -o $@ $^ $(LIB_LDFLAGS) $(objdir)/libmcount/libmcount-fast-single.so: $(LIBMCOUNT_FAST_SINGLE_OBJS) $(LIBMCOUNT_UTILS_OBJS) $(LIBMCOUNT_ARCH_OBJS) $(QUIET_LINK)$(CC) -shared -o $@ $^ $(LIB_LDFLAGS) $(objdir)/libmcount/libmcount-nop.so: $(LIBMCOUNT_NOP_OBJS) $(QUIET_LINK)$(CC) -shared -o $@ $^ $(LIB_LDFLAGS) $(LIBMCOUNT_ARCH_OBJS): $(wildcard $(srcdir)/arch/$(ARCH)/*.[cS]) $(LIBMCOUNT_DEPS) @$(MAKE) -B -C $(srcdir)/arch/$(ARCH) $@ $(UFTRACE_ARCH_OBJS): $(wildcard $(srcdir)/arch/$(ARCH)/*.[cS]) $(COMMON_DEPS) @$(MAKE) -B -C $(srcdir)/arch/$(ARCH) $@ $(objdir)/libtraceevent/libtraceevent.a: $(wildcard $(srcdir)/libtraceevent/*.[ch]) $(objdir)/.config @$(MAKE) -C $(srcdir)/libtraceevent BUILD_SRC=$(srcdir)/libtraceevent BUILD_OUTPUT=$(objdir)/libtraceevent CONFIG_FLAGS="$(TRACEEVENT_CFLAGS)" $(objdir)/uftrace.o: $(srcdir)/uftrace.c $(objdir)/version.h $(COMMON_DEPS) $(QUIET_CC)$(CC) $(UFTRACE_CFLAGS) -c -o $@ $< $(objdir)/misc/demangler.o: $(srcdir)/misc/demangler.c $(objdir)/version.h $(COMMON_DEPS) $(QUIET_CC)$(CC) $(DEMANGLER_CFLAGS) -c -o $@ $< $(objdir)/misc/symbols.o: $(srcdir)/misc/symbols.c $(objdir)/version.h $(COMMON_DEPS) $(QUIET_CC)$(CC) $(SYMBOLS_CFLAGS) -c -o $@ $< $(UFTRACE_OBJS_VERSION): $(objdir)/version.h $(filter-out $(objdir)/uftrace.o, $(UFTRACE_OBJS)): $(objdir)/%.o: $(srcdir)/%.c $(COMMON_DEPS) $(QUIET_CC)$(CC) $(UFTRACE_CFLAGS) -c -o $@ $< $(objdir)/version.h: PHONY @$(srcdir)/misc/version.sh $@ $(VERSION_GIT) $(srcdir) $(srcdir)/utils/auto-args.h: $(srcdir)/misc/prototypes.h $(srcdir)/misc/gen-autoargs.py $(QUIET_GEN)$(srcdir)/misc/gen-autoargs.py -i $< -o $@ $(objdir)/uftrace: $(UFTRACE_OBJS) $(UFTRACE_ARCH_OBJS) $(objdir)/libtraceevent/libtraceevent.a $(QUIET_LINK)$(CC) $(UFTRACE_CFLAGS) -o $@ $(UFTRACE_OBJS) $(UFTRACE_ARCH_OBJS) $(UFTRACE_LDFLAGS) $(objdir)/misc/demangler: $(DEMANGLER_OBJS) $(QUIET_LINK)$(CC) $(DEMANGLER_CFLAGS) -o $@ $(DEMANGLER_OBJS) $(DEMANGLER_LDFLAGS) $(objdir)/misc/symbols: $(SYMBOLS_OBJS) $(QUIET_LINK)$(CC) $(SYMBOLS_CFLAGS) -o $@ $(SYMBOLS_OBJS) $(SYMBOLS_LDFLAGS) install: all $(Q)$(INSTALL) -d -m 755 $(DESTDIR)$(bindir) $(Q)$(INSTALL) -d -m 755 $(DESTDIR)$(libdir) $(Q)$(INSTALL) -d -m 755 $(DESTDIR)$(etcdir)/bash_completion.d ifneq ($(wildcard $(elfdir)/lib/libelf.so),) ifeq ($(wildcard $(prefix)/lib/libelf.so),) # install libelf only when it's not in the install directory. $(call QUIET_INSTALL, libelf) $(Q)$(INSTALL) $(elfdir)/lib/libelf.so $(DESTDIR)$(libdir)/libelf.so endif endif $(call QUIET_INSTALL, uftrace) $(Q)$(INSTALL) $(objdir)/uftrace $(DESTDIR)$(bindir)/uftrace $(call QUIET_INSTALL, libmcount) $(Q)$(INSTALL) $(objdir)/libmcount/libmcount.so $(DESTDIR)$(libdir)/libmcount.so $(Q)$(INSTALL) $(objdir)/libmcount/libmcount-nop.so $(DESTDIR)$(libdir)/libmcount-nop.so $(Q)$(INSTALL) $(objdir)/libmcount/libmcount-fast.so $(DESTDIR)$(libdir)/libmcount-fast.so $(Q)$(INSTALL) $(objdir)/libmcount/libmcount-single.so $(DESTDIR)$(libdir)/libmcount-single.so $(Q)$(INSTALL) $(objdir)/libmcount/libmcount-fast-single.so $(DESTDIR)$(libdir)/libmcount-fast-single.so $(call QUIET_INSTALL, bash-completion) $(Q)$(INSTALL) -m 644 $(srcdir)/misc/bash-completion.sh $(DESTDIR)$(etcdir)/bash_completion.d/uftrace @$(MAKE) -sC $(docdir) install DESTDIR=$(DESTDIR)$(mandir) @if [ `id -u` = 0 ]; then ldconfig $(DESTDIR)$(libdir) || echo "ldconfig failed"; fi uninstall: $(call QUIET_UNINSTALL, uftrace) $(Q)$(RM) $(DESTDIR)$(bindir)/uftrace $(call QUIET_UNINSTALL, libmcount) $(Q)$(RM) $(DESTDIR)$(libdir)/libmcount.so $(call QUIET_UNINSTALL, libmcount-nop) $(Q)$(RM) $(DESTDIR)$(libdir)/libmcount-nop.so $(call QUIET_UNINSTALL, libmcount-fast) $(Q)$(RM) $(DESTDIR)$(libdir)/libmcount-fast.so $(call QUIET_UNINSTALL, libmcount-single) $(Q)$(RM) $(DESTDIR)$(libdir)/libmcount-single.so $(call QUIET_UNINSTALL, libmcount-fast-single) $(Q)$(RM) $(DESTDIR)$(libdir)/libmcount-fast-single.so $(call QUIET_UNINSTALL, bash-completion) $(Q)$(RM) $(DESTDIR)$(etcdir)/bash_completion.d/uftrace @$(MAKE) -sC $(docdir) uninstall DESTDIR=$(DESTDIR)$(mandir) test: all @$(MAKE) -C $(srcdir)/tests TESTARG="$(TESTARG)" test unittest: all @$(MAKE) -C $(srcdir)/tests TESTARG="$(TESTARG)" test_unit runtest: all @$(MAKE) -C $(srcdir)/tests TESTARG="$(TESTARG)" test_run dist: @git archive --prefix=uftrace-$(VERSION)/ $(VERSION_GIT) -o $(objdir)/uftrace-$(VERSION).tar @tar rf $(objdir)/uftrace-$(VERSION).tar --transform="s|^|uftrace-$(VERSION)/|" $(objdir)/version.h @gzip $(objdir)/uftrace-$(VERSION).tar doc: @$(MAKE) -C $(docdir) clean: $(call QUIET_CLEAN, uftrace) $(Q)$(RM) $(objdir)/*.o $(objdir)/*.op $(objdir)/*.so $(objdir)/*.a $(Q)$(RM) $(objdir)/cmds/*.o $(objdir)/utils/*.o $(objdir)/misc/*.o $(Q)$(RM) $(objdir)/utils/*.op $(objdir)/libmcount/*.op $(Q)$(RM) $(objdir)/gmon.out $(srcdir)/scripts/*.pyc $(TARGETS) $(Q)$(RM) $(objdir)/uftrace-*.tar.gz $(objdir)/version.h $(Q)find -name "*\.gcda" -o -name "*\.gcno" | xargs $(RM) $(Q)$(RM) coverage.info @$(MAKE) -sC $(srcdir)/arch/$(ARCH) clean @$(MAKE) -sC $(srcdir)/tests ARCH=$(ARCH) clean @$(MAKE) -sC $(docdir) clean @$(MAKE) -sC $(srcdir)/libtraceevent BUILD_SRC=$(srcdir)/libtraceevent BUILD_OUTPUT=$(objdir)/libtraceevent CONFIG_FLAGS="$(TRACEEVENT_CFLAGS)" clean reset-coverage: $(Q)find -name "*\.gcda" | xargs $(RM) $(Q)$(RM) coverage.info ctags: @find . -name "*\.[chS]" -o -path ./tests -prune -o -path ./check-deps -prune \ | xargs ctags --regex-asm='/^(GLOBAL|ENTRY|END)\(([^)]*)\).*/\2/' .PHONY: all config clean test dist doc ctags PHONY uftrace-0.9.4/Makefile.include000066400000000000000000000015071362052523300162430ustar00rootroot00000000000000#-*- mode: makefile -*- ifneq ($(findstring $(MAKEFLAGS),s),s) ifneq ($(V),1) QUIET_CC = @echo ' CC '$(patsubst $(objdir)/%,%,$@); QUIET_CC_FPIC = @echo ' CC FPIC '$(patsubst $(objdir)/%,%,$@); QUIET_AR = @echo ' AR '$(patsubst $(objdir)/%,%,$@); QUIET_ASM = @echo ' ASM '$(patsubst $(objdir)/%,%,$@); QUIET_LINK = @echo ' LINK '$(patsubst $(objdir)/%,%,$@); QUIET_MKDIR = @echo ' MKDIR '$(patsubst $(objdir)/%,%,$@); QUIET_GEN = @echo ' GEN '$(patsubst $(objdir)/%,%,$@); QUIET_FLEX = @echo ' FLEX '$@; QUIET_BISON = @echo ' BISON '$@; QUIET_TEST = @echo ' TEST '$@; QUIET_CLEAN = @printf ' CLEAN %s\n' $1; QUIET_INSTALL = @printf ' INSTALL %s\n' $1; QUIET_UNINSTALL= @printf ' REMOVE %s\n' $1; Q = @ endif endif uftrace-0.9.4/NEWS000066400000000000000000000231151362052523300136570ustar00rootroot00000000000000uftrace v0.9.4 -------------- * dynamic tracing update improve success rate on x86_64 dynamic unpatch on x86_64 (for -mfentry or -mrecord-mcount) add -U/--unpatch option for dynamic tracing experimental support for aarch64 * script update luaJIT (lua 5.1) support by Byeonggon python3 support * build change update configure script for better compatibility handle common cross compile settings * tui change add 's' key to sort column in tui report mode by Hyoungjong use --report option to start tui with report mode * other changes task level analysis for graph and info command add Korean documentation a lot of memory leak fixes And many bug fixes and improvements. Thanks for all contributors: Anas Balboul, Byeonggon Lee, Colin Lord, George Karlos, GwanYeong Kim, Haeun Jeon, Hanbum Park, Handong Choi, Honggyu Kim, Hyoungjong Kim, Jeesoo Min, Joonho Ryu, Jungkeun Cho, Jungwoo Jo, Junil Kim, Minchul Kang, MinJeong Kim, Sang-Heon Jeon, Sangyun Han, SeoYoung Kim, Sungho Yoon, Yeomin Nam uftrace v0.9.3 -------------- * dynamic tracing update add (optional) dependency of capstone disassembly engine support tracing executables w/o instrumentation on x86_64 add -Z/--size-filter option not to select small functions * external event support support display user-defined events in uftrace.data/extern.dat it's a text file which has timestamp and message for each line * other changes allow tracing (system) binaries in the PATH add --srcline option to save debug info only if necessary apply --time-filter for analysis commands by default allow tracing execution of shell (interpreter) And many bug fixes and improvements. Thanks for all contributors: Daniel T. Lee, Hanbum Park, Honggyu Kim, Taeung Song uftrace v0.9.2 -------------- * trigger update add --signal option to support trigger by signal * TUI update add C/E key to collapse/expand all child nodes make R/r key to go to report window separately add z key to align screen to center * other changes display data symbols in argument/return value trace library calls even without PLT add -l short option for --nest-libcall rudimentary support for Rust programs And many bug fixes and improvements. Thanks for all contributors: Anas Balboul, Claudia J. Kang, Daniel T. Lee, Honggyu Kim uftrace v0.9.1 -------------- * filter update add --caller-filter option * script changes rename context in uftrace_begin: "args" -> "cmds" rename context in uftrace_begin: "recording" -> "record" * other changes add --watch option to trace cpu task is running add --graphviz option to produce output in DOT format filter 'do_syscall_64' kernel function by default And many bug fixes and improvements. Thanks for all contributors: Ahn Seung-rye, Claudia J. Kang, Daniel T. Lee, GwanYeong Kim, Hanbum Park, Honggyu Kim, Leah Neukirchen, Rikard Falkeborn uftrace v0.9 ============ * argument update automatic argument using DWARF debug info display enum constants properly add -a short option for --auto-args * TUI implementation graph, report and info commands using ncurses redraw graph for a selected function fold/unfold and search nodes in graph * build changes configure script shows status of dependencies add --without-XXX option to the configure script allow build without libelf * filter changes add --match option to select pattern matching method: regex or glob add --no-event option to disable default events apply recover trigger for every function automatically * other changes pass runtime info to script add -h short option for help message add --no-randomize-addr option to disable ASLR enable task scheduling events by default use gray color for comments and green for events add basic gdb (python) script to help debugging add misc/symbols tool to show symbol name from address And many bug fixes and improvements. Thanks for all contributors: GwanYeong Kim, Hanbum Park, Honggyu Kim, Taeguk Kwon, Taeung Song, Khem Raj uftrace v0.8.3 -------------- * i386 arch support (by Hanbum Park): support arguments and dynamic tracing * graph update (by Honggyu Kim): add -f/--output-fields option to control output share common code/behavior with replay command * event update: add task events (fork/comm/exit) using Linux perf subsystem enable task events always if supported * trigger change: change 'read' trigger action to read events twice (at entry and exit) support some pmu-related events using read trigger (with perf syscall) allow 'd' format specifier for default behavior with different size * other changes: add misc/demangler to test demangling easily add --libname option to show library names for PLT functions And many bug fixes and improvements. Thanks for all contributors: Hanbum Park, Honggyu Kim, Taeung Song uftrace v0.8.2 -------------- * trigger update add 'p' format for function pointer add --auto-args option for automatic argument/return value support enum type for auto-args * diff change add 'compact' policy and make it default old behavior is supported on 'full' policy * graph change show full graph when no function given support fork+exec properly * script change flush stdout buffer before fork serialize execution using a mutex And many bug fixes and improvements. Thanks for all contributors: Andrew Slough, Hansuk Hong, Hanbum Park, Honggyu Kim, JangSoJin, Myungjin Ko, MyungSik Ji, Sangwon Hong, Taeung Song, Vincent LE GARREC, Yujeong Kim uftrace v0.8.1 -------------- * trigger update apply filter/trigger to all libraries by default save symbol tables of all libraries -T/--trigger option supports filtering and argument/return value * other changes make --nest-libcall option imply --force option add --record option to script command replay show 's' suffix for std::string arguments allow reading data in current directory And many bug fixes and improvements. Thanks for all contributors: Honggyu Kim, Taeung Song uftrace v0.8 ============ * event tracing support: enable tracing events as well as functions. following events are supported using -E/--event option - SystemTap SDT (x86_64 only) - kernel tracepoint - scheduler (using perf syscall) list available events using --list-event option new read trigger also creates 'proc/statm' and 'page-fault' events * libcall tracing update: fix to trace already resolved functions too trace nested calls from other libraries using --nest-libcall option handle BIND-NOW + PIE properly * python scripting support: add new 'script' command with -S/--script option also support record-time scripting support additional filter for script execution allow to specify options for recording * report diff change: sort by (absolute) diff add --diff-policy option to control behavior add 'func' sort key change color setting * other changes: std::string argument display add elapsed time info "uftrace recv --run-cmd" can execute user-given command add "finish" trigger action to tracing add --opt-file option to allow reading options from file add --keep-pid option to preserve pid when running program And many bug fixes and improvements. Thanks for all contributors: Changhyeok Bae, Honggyu Kim, JeongBong Seo, SeongJae Park, Taeung Song, Paul Cannon uftrace v0.7 ============ * dynamic tracing support (x86_64 only) enable tracing for selected functions with -P option it needs some compiler support though - gcc with -mnop-mcount option - clang (X-ray) with -fxray-instrument option * AArch64 support add preliminary support for ARM v8 (64-bit) first integer argument is missing * kernel tracing change show recorded kernel functions by default partial support for event tracing fix to send/receive kernel data via network filter out sys_clock_gettime() for non-VDSO systems * dump change: show arguments and return values properly show more kernel tracing info fix file offset printing * build change: fix various problems on GCC 7 update configure script for better distro packaging And many bug fixes and improvements. Thanks for all contributors: Dridi Boukelmoune, Honggyu Kim, Taeung Song, Wonseok Ko uftrace v0.6.2 -------------- * dlopen() support: can show functions from dynamic loaded library using dlopen() * kernel tracing update: save kernel metadata so that it can be viewed from a different machine adjust tracer settings to reduce lost kernel data * filter change: remove '+' sign in elapsed time for --time-range option allow to use 'm' or 'min' to specify elapsed time And many bug fixes and improvements. Thanks for all contributors: Abder Benbachir, Geneviève Bastien, Honggyu Kim, Taeung Song, Wonseok Ko uftrace v0.6.1 -------------- * kernel option change: The -K option is same as --kernel-depth The --kernel-skip-out is deprecated and use The --kernel-full is to show kernel functions outside of user functions The --kernel-only option was added * replay change: add --output-fields option to customize the info on the left side currently time, delta, elapsed, duration, tid and addr fields are supported * filter change: apply time filter on replay add --time-range option to limit data analysis report, graph and dump honors same filter on replay add 'time' trigger to set a different threshold on specific functions * flame-graph support: "uftrace dump --flame-graph" creates a SVG file use --sample-time option to control sampling frequency in the output * build change: improve build process to facilitate distro packaging configure script checks dependency and shows warning uftrace v0.6 ============ * project open!uftrace-0.9.4/README.md000066400000000000000000000256031362052523300144430ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/namhyung/uftrace.svg?branch=master)](https://travis-ci.org/namhyung/uftrace) [![Coverity scan](https://scan.coverity.com/projects/12421/badge.svg)](https://scan.coverity.com/projects/namhyung-uftrace) uftrace ======= The uftrace tool is to trace and analyze execution of a program written in C/C++. It was heavily inspired by the ftrace framework of the Linux kernel (especially function graph tracer) and supports userspace programs. It supports various kind of commands and filters to help analysis of the program execution and performance. ![uftrace-live-demo](doc/uftrace-live-demo.gif) * Homepage: https://github.com/namhyung/uftrace * Tutorial: https://github.com/namhyung/uftrace/wiki/Tutorial * Chat: https://gitter.im/uftrace/uftrace * Mailing list: [uftrace@googlegroups.com](https://groups.google.com/forum/#!forum/uftrace) Features ======== It traces each function in the executable and shows time duration. It can also trace external library calls - but usually entry and exit are supported. Optionally it's possible to trace other (nested) external library calls and/or internal function calls in the library call. It can show detailed execution flow at function level, and report which function has the highest overhead. And it also shows various information related the execution environment. You can setup filters to exclude or include specific functions when tracing. In addition, it can save and show function arguments and return value. It supports multi-process and/or multi-threaded applications. With root privilege, it can also trace kernel functions as well( with `-k` option) if the system enables the function graph tracer in the kernel (`CONFIG_FUNCTION_GRAPH_TRACER=y`). How to build and install uftrace ================================ On Linux distros, [misc/install-deps.sh](misc/install-deps.sh) installs required software(s) on your system. Those are for optional advanced features but highly recommend to install them together. $ sudo misc/install-deps.sh Once you installed required software(s) on your system, it can be built and installed like following: $ ./configure $ make $ sudo make install For more advanced setup, please refer [INSTALL.md](INSTALL.md) file. How to use uftrace ================== The uftrace command has following subcommands: * `record` : runs a program and saves the trace data * `replay` : shows program execution in the trace data * `report` : shows performance statistics in the trace data * `live` : does record and replay in a row (default) * `info` : shows system and program info in the trace data * `dump` : shows low-level trace data * `recv` : saves the trace data from network * `graph` : shows function call graph in the trace data * `script` : runs a script for recorded trace data * `tui` : show text user interface for graph and report You can use `-h`, `-?` or `--help` option to see available commands and options. $ uftrace Usage: uftrace [OPTION...] [record|replay|live|report|info|dump|recv|graph|script|tui] [] Try `uftrace --help' or `uftrace --usage' for more information. If omitted, it defaults to the `live` command which is almost same as running record and replay subcommand in a row (but does not record the trace info to files). For recording, the executable needs to be compiled with the `-pg` (or `-finstrument-functions`) option which generates profiling code (calling mcount or __cyg_profile_func_enter/exit) for each function. Note that, there's an experimental support for dynamic tracing on x86_64 and AArch64(ARM64) which doesn't require such (re-)compilations. Also recent compilers have some options to help uftrace to reduce tracing overhead with similar way (although it still needs recompilation of your program). Please see [dynamic tracing](doc/uftrace-record.md#dynamic-tracing) section for more details. $ uftrace tests/t-abc # DURATION TID FUNCTION 16.134 us [ 1892] | __monstartup(); 223.736 us [ 1892] | __cxa_atexit(); [ 1892] | main() { [ 1892] | a() { [ 1892] | b() { [ 1892] | c() { 2.579 us [ 1892] | getpid(); 3.739 us [ 1892] | } /* c */ 4.376 us [ 1892] | } /* b */ 4.962 us [ 1892] | } /* a */ 5.769 us [ 1892] | } /* main */ For more analysis, you'd be better recording it first so that it can run analysis commands like replay, report, graph, dump and/or info multiple times. $ uftrace record tests/t-abc It'll create uftrace.data directory that contains trace data files. Other analysis commands expect the directory exists in the current directory, but one can use another using `-d` option. The `replay` command shows execution information like above. As you can see, the t-abc is a very simple program merely calls a, b and c functions. In the c function it called getpid() which is a library function implemented in the C library (glibc) on normal systems - the same goes to __cxa_atexit(). Users can use various filter options to limit functions it records/prints. The depth filter (`-D` option) is to omit functions under the given call depth. The time filter (`-t` option) is to omit functions running less than the given time. And the function filters (`-F` and `-N` options) are to show/hide functions under the given function. The `-k` option enables to trace kernel functions as well (needs root access). With the classic hello world program, the output would look like below (Note, I changed it to use fprintf() with stderr rather than the plain printf() to make it invoke system call directly): $ sudo uftrace -k tests/t-hello Hello world # DURATION TID FUNCTION 1.365 us [21901] | __monstartup(); 0.951 us [21901] | __cxa_atexit(); [21901] | main() { [21901] | fprintf() { 3.569 us [21901] | __do_page_fault(); 10.127 us [21901] | sys_write(); 20.103 us [21901] | } /* fprintf */ 21.286 us [21901] | } /* main */ You can see the page fault handler and the write syscall handler were called inside the fprintf() call. Also it can record and show function arguments and return value with `-A` and `-R` options respectively. The following example records first argument and return value of 'fib' (fibonacci number) function. $ uftrace record -A fib@arg1 -R fib@retval tests/t-fibonacci 5 $ uftrace replay # DURATION TID FUNCTION 2.853 us [22080] | __monstartup(); 2.194 us [22080] | __cxa_atexit(); [22080] | main() { 2.706 us [22080] | atoi(); [22080] | fib(5) { [22080] | fib(4) { [22080] | fib(3) { 7.473 us [22080] | fib(2) = 1; 0.419 us [22080] | fib(1) = 1; 11.452 us [22080] | } = 2; /* fib */ 0.460 us [22080] | fib(2) = 1; 13.823 us [22080] | } = 3; /* fib */ [22080] | fib(3) { 0.424 us [22080] | fib(2) = 1; 0.437 us [22080] | fib(1) = 1; 2.860 us [22080] | } = 2; /* fib */ 19.600 us [22080] | } = 5; /* fib */ 25.024 us [22080] | } /* main */ The `report` command lets you know which function spends the longest time including its children (total time). $ uftrace report Total time Self time Calls Function ========== ========== ========== ==================================== 25.024 us 2.718 us 1 main 19.600 us 19.600 us 9 fib 2.853 us 2.853 us 1 __monstartup 2.706 us 2.706 us 1 atoi 2.194 us 2.194 us 1 __cxa_atexit The `graph` command shows function call graph of given function. In the above example, function graph of function 'main' looks like below: $ uftrace graph main # Function Call Graph for 'main' (session: 073f1e84aa8b09d3) =============== BACKTRACE =============== backtrace #0: hit 1, time 25.024 us [0] main (0x40066b) ========== FUNCTION CALL GRAPH ========== 25.024 us : (1) main 2.706 us : +-(1) atoi : | 19.600 us : +-(1) fib 16.683 us : (2) fib 12.773 us : (4) fib 7.892 us : (2) fib The `dump` command shows raw output of each trace record. You can see the result in the chrome browser, once the data is processed with `uftrace dump --chrome`. Below is a trace of clang (LLVM) compiling a small C++ template metaprogram. [![uftrace-chrome-dump](doc/uftrace-chrome.png)](https://uftrace.github.io/dump/clang.tmp.fib.html) It also supports flame-graph output as well. The data can be processed with `uftrace dump --flame-graph` and passed to [flamegraph.pl](https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl). Below is a flame graph result of gcc compiling a simple C program. [![uftrace-flame-graph-dump](https://uftrace.github.io/dump/gcc.svg)](https://uftrace.github.io/dump/gcc.svg) The `info` command shows system and program information when recorded. $ uftrace info # system information # ================== # program version : uftrace v0.8.1 # recorded on : Tue May 24 11:21:59 2016 # cmdline : uftrace record tests/t-abc # cpu info : Intel(R) Core(TM) i7-3930K CPU @ 3.20GHz # number of cpus : 12 / 12 (online / possible) # memory info : 20.1 / 23.5 GB (free / total) # system load : 0.00 / 0.06 / 0.06 (1 / 5 / 15 min) # kernel version : Linux 4.5.4-1-ARCH # hostname : sejong # distro : "Arch Linux" # # process information # =================== # number of tasks : 1 # task list : 5098 # exe image : /home/namhyung/project/uftrace/tests/t-abc # build id : a3c50d25f7dd98dab68e94ef0f215edb06e98434 # exit status : exited with code: 0 # elapsed time : 0.003219479 sec # cpu time : 0.000 / 0.003 sec (sys / user) # context switch : 1 / 1 (voluntary / involuntary) # max rss : 3072 KB # page fault : 0 / 172 (major / minor) # disk iops : 0 / 24 (read / write) The `script` command allows user to run a custom script on a data recorded. The supported script types are Python 2.7 and Lua 5.1 as of now. The `tui` command is for interactive text-based user interface using ncurses. It provides basic functionality of `graph`, `report` and `info` commands as of now. Limitations =========== - It can trace a native C/C++ application on Linux. - It *cannot* trace already running process. - It *cannot* be used for system-wide tracing. - It supports x86 (32 and 64 bit), ARM (v6 or later) and AArch64 for now. License ======= The uftrace program is released under GPL v2. See [COPYING file](COPYING) for details. uftrace-0.9.4/TODO000066400000000000000000000014431362052523300136500ustar00rootroot00000000000000- cleanup task management - more trigger action - trigger filtering (ignore, count, at return, ...) - improve documentation - report w/ multi-thread - config file support - filter by argument value - filter by source location - SDT argument support - LTT-ng event (tracepoint) support - reading/watching external data (global variable, cpu, ...) - show kernel function argument and return value (with dynamic events) - different clock support (TSC on x86, ...) - generic field support for report - field and sort support for TUI - filtering on TUI - replay on TUI - python function tracing - write useful script examples - attach to existing process - process and display multiple data together - graph diff support - full demangling support - improve rust demangling - dynamic object naming (tagging?) uftrace-0.9.4/arch/000077500000000000000000000000001362052523300140735ustar00rootroot00000000000000uftrace-0.9.4/arch/aarch64/000077500000000000000000000000001362052523300153235ustar00rootroot00000000000000uftrace-0.9.4/arch/aarch64/Makefile000066400000000000000000000016371362052523300167720ustar00rootroot00000000000000sdir := $(srcdir)/arch/aarch64 odir := $(objdir)/arch/aarch64 LINKFLAGS := -r include $(srcdir)/Makefile.include ARCH_ENTRY_SRC = $(wildcard $(sdir)/*.S) ARCH_MCOUNT_SRC = $(wildcard $(sdir)/mcount-*.c) ARCH_UFTRACE_SRC = $(sdir)/cpuinfo.c ARCH_MCOUNT_OBJS = $(patsubst $(sdir)/%.S,$(odir)/%.op,$(ARCH_ENTRY_SRC)) ARCH_MCOUNT_OBJS += $(patsubst $(sdir)/%.c,$(odir)/%.op,$(ARCH_MCOUNT_SRC)) ARCH_UFTRACE_OBJS = $(patsubst $(sdir)/%.c,$(odir)/%.o,$(ARCH_UFTRACE_SRC)) all: $(odir)/entry.op $(odir)/mcount-entry.op: $(ARCH_MCOUNT_OBJS) $(QUIET_LINK)$(LD) $(LINKFLAGS) -o $@ $^ $(odir)/uftrace.o: $(ARCH_UFTRACE_OBJS) $(QUIET_LINK)$(LD) $(LINKFLAGS) -o $@ $^ $(odir)/%.op: $(sdir)/%.S $(QUIET_ASM)$(CC) $(LIB_CFLAGS) -c -o $@ $< $(odir)/%.op: $(sdir)/%.c $(QUIET_CC_FPIC)$(CC) $(LIB_CFLAGS) -c -o $@ $< $(odir)/%.o: $(sdir)/%.c $(QUIET_CC)$(CC) $(UFTRACE_CFLAGS) -c -o $@ $< clean: $(RM) $(odir)/*.op $(odir)/*.o uftrace-0.9.4/arch/aarch64/cpuinfo.c000066400000000000000000000006651362052523300171410ustar00rootroot00000000000000#include #include int arch_fill_cpuinfo_model(int fd) { char buf[1024]; FILE *fp; int ret = -1; fp = fopen("/proc/cpuinfo", "r"); if (fp == NULL) return -1; while (fgets(buf, sizeof(buf), fp) != NULL) { if (!strncmp(buf, "CPU architecture:", 17)) { int v = 8; sscanf(&buf[18], "%d", &v); dprintf(fd, "cpuinfo:desc=ARM64 (v%d)\n", v); ret = 0; break; } } fclose(fp); return ret; } uftrace-0.9.4/arch/aarch64/dynamic.S000066400000000000000000000042531362052523300170770ustar00rootroot00000000000000#include "utils/asm.h" .text /* universal stack constraint: (SP mod 16) == 0 */ /* frame pointer was saved in the trampoline */ GLOBAL(__dentry__) /* save all caller-saved registers due to -fipa-ra */ stp x14, x15, [sp, #-16]! stp x12, x13, [sp, #-16]! stp x10, x11, [sp, #-16]! /* platform register and/or scratch registers */ stp x8, x9, [sp, #-16]! /* also save original child address for mcount_find_code */ stp x29, x30, [sp, #-16]! /* save arguments */ stp x6, x7, [sp, #-16]! stp x4, x5, [sp, #-16]! stp x2, x3, [sp, #-16]! stp x0, x1, [sp, #-16]! stp d0, d1, [sp, #-16]! add x0, x29, #8 mov x1, x30 add x2, sp, #16 bl mcount_entry ldr x0, [sp, #88] bl mcount_find_code str x0, [sp, #88] ldp d0, d1, [sp], #16 /* restore arguments */ ldp x0, x1, [sp], #16 ldp x2, x3, [sp], #16 ldp x4, x5, [sp], #16 ldp x6, x7, [sp], #16 /* actual return address from mcount_find_code() */ ldp x16, x17, [sp], #16 ldp x8, x9, [sp], #16 /* caller-saved registers */ ldp x10, x11, [sp], #16 ldp x12, x13, [sp], #16 ldp x14, x15, [sp], #16 /* restore frame pointer */ ldp x29, x30, [sp], #16 /* jump to the saved insn */ br x17 END(__dentry__) ENTRY(dynamic_return) /* setup frame pointer */ stp x29, x30, [sp, #-16]! /* save all caller-saved registers due to -fipa-ra */ stp x14, x15, [sp, #-16]! stp x12, x13, [sp, #-16]! stp x10, x11, [sp, #-16]! /* * save indirect result location register * used in C++ for returning non-trivial objects */ stp x8, x9, [sp, #-16]! /* caller-saved registers again */ stp x6, x7, [sp, #-16]! stp x4, x5, [sp, #-16]! stp x2, x3, [sp, #-16]! /* save return values */ stp x0, x1, [sp, #-16]! stp d0, d1, [sp, #-16]! add x0, sp, #16 bl mcount_exit mov x16, x0 /* restore return values */ ldp d0, d1, [sp], #16 ldp x0, x1, [sp], #16 /* restore caller-saved registers */ ldp x2, x3, [sp], #16 ldp x4, x5, [sp], #16 ldp x6, x7, [sp], #16 /* restore indirect result location register */ ldp x8, x9, [sp], #16 /* caller-saved registers again */ ldp x10, x11, [sp], #16 ldp x12, x13, [sp], #16 ldp x14, x15, [sp], #16 /* restore frame pointer */ ldp x29, x30, [sp], #16 br x16 END(dynamic_return) uftrace-0.9.4/arch/aarch64/mcount-arch.h000066400000000000000000000016111362052523300177130ustar00rootroot00000000000000#ifndef MCOUNT_ARCH_H #define MCOUNT_ARCH_H #define mcount_regs mcount_regs struct mcount_regs { unsigned long x0; unsigned long x1; unsigned long x2; unsigned long x3; unsigned long x4; unsigned long x5; unsigned long x6; unsigned long x7; }; #define ARG1(a) ((a)->x0) #define ARG2(a) ((a)->x1) #define ARG3(a) ((a)->x2) #define ARG4(a) ((a)->x3) #define ARG5(a) ((a)->x4) #define ARG6(a) ((a)->x5) #define ARG7(a) ((a)->x6) #define ARG8(a) ((a)->x7) #define ARCH_MAX_REG_ARGS 8 #define ARCH_MAX_FLOAT_REGS 8 struct mcount_arch_context { }; #define ARCH_PLT0_SIZE 32 #define ARCH_PLTHOOK_ADDR_OFFSET 0 struct mcount_disasm_engine; struct mcount_dynamic_info; struct mcount_disasm_info; int disasm_check_insns(struct mcount_disasm_engine *disasm, struct mcount_dynamic_info *mdi, struct mcount_disasm_info *info); #endif /* MCOUNT_ARCH_H */ uftrace-0.9.4/arch/aarch64/mcount-dynamic.c000066400000000000000000000100511362052523300204130ustar00rootroot00000000000000#include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "dynamic" #define PR_DOMAIN DBG_DYNAMIC #include "libmcount/mcount.h" #include "libmcount/internal.h" #include "mcount-arch.h" #include "utils/utils.h" #include "utils/symbol.h" #include "utils/rbtree.h" #define PAGE_SIZE 4096 #define CODE_SIZE 8 /* target instrumentation function it needs to call */ extern void __dentry__(void); static void save_orig_code(struct mcount_disasm_info *info) { struct mcount_orig_insn *orig; uint32_t jmp_insn[6] = { 0x58000050, /* LDR ip0, addr */ 0xd61f0200, /* BR ip0 */ info->addr + 8, (info->addr + 8) >> 32, }; size_t jmp_insn_size = 16; if (info->modified) { memcpy(&jmp_insn[4], &info->insns[24], 8); jmp_insn_size += 8; } orig = mcount_save_code(info, jmp_insn, jmp_insn_size); /* make sure orig->addr same as when called from __dentry__ */ orig->addr += CODE_SIZE; } int mcount_setup_trampoline(struct mcount_dynamic_info *mdi) { uintptr_t dentry_addr = (uintptr_t)(void *)&__dentry__; /* * trampoline assumes {x29,x30} was pushed but x29 was not updated. * make sure stack is 8-byte aligned. */ uint32_t trampoline[] = { 0x910003fd, /* MOV x29, sp */ 0x58000050, /* LDR ip0, &__dentry__ */ 0xd61f0200, /* BR ip0 */ dentry_addr, dentry_addr >> 32, }; /* find unused 16-byte at the end of the code segment */ mdi->trampoline = ALIGN(mdi->text_addr + mdi->text_size, PAGE_SIZE); mdi->trampoline -= sizeof(trampoline); if (unlikely(mdi->trampoline < mdi->text_addr + mdi->text_size)) { mdi->trampoline += sizeof(trampoline); mdi->text_size += PAGE_SIZE; pr_dbg("adding a page for fentry trampoline at %#lx\n", mdi->trampoline); mmap((void *)mdi->trampoline, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); } if (mprotect((void *)mdi->text_addr, mdi->text_size, PROT_READ | PROT_WRITE | PROT_EXEC)) { pr_dbg("cannot setup trampoline due to protection: %m\n"); return -1; } memcpy((void *)mdi->trampoline, trampoline, sizeof(trampoline)); return 0; } static unsigned long get_target_addr(struct mcount_dynamic_info *mdi, unsigned long addr) { return (mdi->trampoline - addr - 4) >> 2; } int mcount_patch_func(struct mcount_dynamic_info *mdi, struct sym *sym, struct mcount_disasm_engine *disasm, unsigned min_size) { uint32_t push = 0xa9bf7bfd; /* STP x29, x30, [sp, #-0x10]! */ uint32_t call; struct mcount_disasm_info info = { .sym = sym, .addr = sym->addr + mdi->map->start, }; void *insn = (void *)info.addr; if (min_size < CODE_SIZE) min_size = CODE_SIZE; if (sym->size <= min_size) return INSTRUMENT_SKIPPED; if (disasm_check_insns(disasm, mdi, &info) < 0) return INSTRUMENT_FAILED; save_orig_code(&info); call = get_target_addr(mdi, info.addr); if ((call & 0xfc000000) != 0) return INSTRUMENT_FAILED; /* make a "BL" insn with 26-bit offset */ call |= 0x94000000; /* hopefully we're not patching 'memcpy' itself */ memcpy(insn, &push, sizeof(push)); memcpy(insn+4, &call, sizeof(call)); /* flush icache so that cpu can execute the new code */ __builtin___clear_cache(insn, insn + CODE_SIZE); return INSTRUMENT_SUCCESS; } static void revert_normal_func(struct mcount_dynamic_info *mdi, struct sym *sym, struct mcount_disasm_engine *disasm) { void *addr = (void *)(uintptr_t)sym->addr + mdi->map->start; void *saved_insn; saved_insn = mcount_find_code((uintptr_t)addr + CODE_SIZE); if (saved_insn == NULL) return; memcpy(addr, saved_insn, CODE_SIZE); __builtin___clear_cache(addr, addr + CODE_SIZE); } void mcount_arch_dynamic_recover(struct mcount_dynamic_info *mdi, struct mcount_disasm_engine *disasm) { struct dynamic_bad_symbol *badsym, *tmp; list_for_each_entry_safe(badsym, tmp, &mdi->bad_syms, list) { if (!badsym->reverted) revert_normal_func(mdi, badsym->sym, disasm); list_del(&badsym->list); free(badsym); } } uftrace-0.9.4/arch/aarch64/mcount-insn.c000066400000000000000000000160561362052523300177510ustar00rootroot00000000000000#include "libmcount/internal.h" #include "mcount-arch.h" #define INSN_SIZE 8 #ifdef HAVE_LIBCAPSTONE #include #include void mcount_disasm_init(struct mcount_disasm_engine *disasm) { if (cs_open(CS_ARCH_ARM64, CS_MODE_ARM, &disasm->engine) != CS_ERR_OK) { pr_dbg("failed to init Capstone disasm engine\n"); return; } if (cs_option(disasm->engine, CS_OPT_DETAIL, CS_OPT_ON) != CS_ERR_OK) pr_dbg("failed to set detail option\n"); } void mcount_disasm_finish(struct mcount_disasm_engine *disasm) { cs_close(&disasm->engine); } /* return 0 if it's ok, -1 if not supported, 1 if modifiable */ static int check_prologue(struct mcount_disasm_engine *disasm, cs_insn *insn) { int i; cs_arm64 *arm64; cs_detail *detail; bool branch = false; int status = -1; /* * 'detail' can be NULL on "data" instruction * if SKIPDATA option is turned ON */ if (insn->detail == NULL) return -1; /* try to fix some PC-relative instructions */ if (insn->id == ARM64_INS_ADR || insn->id == ARM64_INS_ADRP) return 1; if (insn->id == ARM64_INS_LDR && (insn->bytes[3] & 0x3b) == 0x18) return -1; detail = insn->detail; for (i = 0; i < detail->groups_count; i++) { // BL instruction uses PC for return address */ switch (detail->groups[i]) { case CS_GRP_JUMP: branch = true; break; case CS_GRP_CALL: case CS_GRP_RET: case CS_GRP_IRET: #if CS_API_MAJOR >= 4 case CS_GRP_BRANCH_RELATIVE: #endif return -1; default: break; } } arm64 = &insn->detail->arm64; if (!arm64->op_count) return 0; for (i = 0; i < arm64->op_count; i++) { cs_arm64_op *op = &arm64->operands[i]; switch (op->type) { case ARM64_OP_REG: status = 0; break; case ARM64_OP_IMM: if (branch) return -1; status = 0; break; case ARM64_OP_MEM: status = 0; break; default: break; } } return status; } /* return true if it's ok for dynamic tracing */ static bool check_body(struct mcount_disasm_engine *disasm, cs_insn *insn, struct mcount_dynamic_info *mdi, struct mcount_disasm_info *info) { int i; cs_arm64 *arm64; cs_detail *detail = insn->detail; unsigned long target; bool jump = false; /* we cannot investigate, not supported */ if (detail == NULL) return false; detail = insn->detail; /* assume there's no call into the middle of function */ for (i = 0; i < detail->groups_count; i++) { if (detail->groups[i] == CS_GRP_JUMP) jump = true; } if (!jump) return true; arm64 = &insn->detail->arm64; for (i = 0; i < arm64->op_count; i++) { cs_arm64_op *op = &arm64->operands[i]; switch (op->type) { case ARM64_OP_IMM: /* capstone seems already calculate target address */ target = op->imm; /* disallow (back) jump to the prologue */ if (info->addr < target && target < info->addr + info->copy_size) return false; /* disallow jump to middle of other function */ if (info->addr > target || target >= info->addr + info->sym->size) { /* also mark the target function as invalid */ return !mcount_add_badsym(mdi, insn->address, target); } break; case ARM64_OP_MEM: /* indirect jumps are not allowed */ return false; case ARM64_OP_REG: /* * WARN: it should be disallowed too, but many of functions * use branch with register so this would drop the success * rate significantly. Allowing it for now. */ return true; default: break; } } return true; } static int opnd_reg(int capstone_reg) { const uint8_t arm64_regs[] = { ARM64_REG_X0, ARM64_REG_X1, ARM64_REG_X2, ARM64_REG_X3, ARM64_REG_X4, ARM64_REG_X5, ARM64_REG_X6, ARM64_REG_X7, ARM64_REG_X8, ARM64_REG_X9, ARM64_REG_X10, ARM64_REG_X11, ARM64_REG_X12, ARM64_REG_X13, ARM64_REG_X14, ARM64_REG_X15, ARM64_REG_X16, ARM64_REG_X17, ARM64_REG_X18, ARM64_REG_X19, ARM64_REG_X20, ARM64_REG_X21, ARM64_REG_X22, ARM64_REG_X23, ARM64_REG_X24, ARM64_REG_X25, ARM64_REG_X26, ARM64_REG_X27, ARM64_REG_X28, ARM64_REG_X29, ARM64_REG_X30, ARM64_REG_NZCV, }; size_t i; for (i = 0; i < sizeof(arm64_regs); i++) { if (capstone_reg == arm64_regs[i]) return i; } return -1; } #define REG_SHIFT 5 static bool modify_instruction(struct mcount_disasm_engine *disasm, cs_insn *insn, struct mcount_dynamic_info *mdi, struct mcount_disasm_info *info) { if (insn->id == ARM64_INS_ADR || insn->id == ARM64_INS_ADRP) { uint32_t ldr_insn = 0x580000c0; uint64_t target_addr; cs_arm64_op *op1 = &insn->detail->arm64.operands[0]; cs_arm64_op *op2 = &insn->detail->arm64.operands[1]; /* handle the first ADRP instruction only (for simplicity) */ if (info->copy_size != 0) return false; if (op1->type != ARM64_OP_REG || op2->type != ARM64_OP_IMM) return false; /* * craft LDR instruction to load addr to op1->reg. * the actual 'addr' is located after 24 byte from the insn. */ ldr_insn += opnd_reg(op1->reg); target_addr = op2->imm; memcpy(info->insns, &ldr_insn, sizeof(ldr_insn)); /* 24 = 8 (orig_insn) + 16 (br insn + address) */ memcpy(info->insns + 24, &target_addr, sizeof(target_addr)); info->copy_size += sizeof(ldr_insn); info->modified = true; return true; } return false; } int disasm_check_insns(struct mcount_disasm_engine *disasm, struct mcount_dynamic_info *mdi, struct mcount_disasm_info *info) { cs_insn *insn = NULL; uint32_t count, i; int ret = INSTRUMENT_FAILED; struct dynamic_bad_symbol *badsym; badsym = mcount_find_badsym(mdi, info->addr); if (badsym != NULL) { badsym->reverted = true; return INSTRUMENT_FAILED; } count = cs_disasm(disasm->engine, (void *)info->addr, info->sym->size, info->addr, 0, &insn); for (i = 0; i < count; i++) { int state = check_prologue(disasm, &insn[i]); if (state < 0) { pr_dbg3("instruction not supported: %s\t %s\n", insn[i].mnemonic, insn[i].op_str); goto out; } if (state) { if (!modify_instruction(disasm, &insn[i], mdi, info)) goto out; } else { memcpy(info->insns + info->copy_size, insn[i].bytes, insn[i].size); info->copy_size += insn[i].size; } info->orig_size += insn[i].size; if (info->orig_size >= INSN_SIZE) { ret = INSTRUMENT_SUCCESS; break; } } while (++i < count) { if (!check_body(disasm, &insn[i], mdi, info)) { ret = INSTRUMENT_FAILED; break; } } out: if (count) cs_free(insn, count); return ret; } #else /* HAVE_LIBCAPSTONE */ static bool disasm_check_insn(uint8_t *insn) { // LDR (literal) if ((*insn & 0x3b) == 0x18) return false; // ADR or ADRP if ((*insn & 0x1f) == 0x10) return false; // Branch & system instructions if ((*insn & 0x1c) == 0x14) return false; return true; } int disasm_check_insns(struct mcount_disasm_engine *disasm, struct mcount_dynamic_info *mdi, struct mcount_disasm_info *info) { uint8_t *insn = (void *)info->addr; if (!disasm_check_insn(&insn[3]) || !disasm_check_insn(&insn[7])) return INSTRUMENT_FAILED; memcpy(info->insns, insn, INSN_SIZE); info->orig_size = INSN_SIZE; info->copy_size = INSN_SIZE; return INSTRUMENT_SUCCESS; } #endif /* HAVE_LIBCAPSTONE */ uftrace-0.9.4/arch/aarch64/mcount-support.c000066400000000000000000000110631362052523300205070ustar00rootroot00000000000000#include #include #include "libmcount/internal.h" #include "utils/utils.h" #include "utils/filter.h" /* FIXME: x0 is overwritten before calling _mcount() */ int mcount_get_register_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { struct mcount_regs *regs = ctx->regs; int reg_idx; switch (spec->type) { case ARG_TYPE_REG: reg_idx = spec->reg_idx; break; case ARG_TYPE_FLOAT: if (spec->size <= 4) reg_idx = spec->idx + UFT_AARCH64_REG_FLOAT_BASE; else reg_idx = spec->idx + UFT_AARCH64_REG_DOUBLE_BASE; break; case ARG_TYPE_INDEX: reg_idx = spec->idx; /* for integer arguments */ break; case ARG_TYPE_STACK: default: return -1; } switch (reg_idx) { case UFT_AARCH64_REG_X0: ctx->val.i = ARG1(regs); break; case UFT_AARCH64_REG_X1: ctx->val.i = ARG2(regs); break; case UFT_AARCH64_REG_X2: ctx->val.i = ARG3(regs); break; case UFT_AARCH64_REG_X3: ctx->val.i = ARG4(regs); break; case UFT_AARCH64_REG_X4: ctx->val.i = ARG5(regs); break; case UFT_AARCH64_REG_X5: ctx->val.i = ARG6(regs); break; case UFT_AARCH64_REG_X6: ctx->val.i = ARG7(regs); break; case UFT_AARCH64_REG_X7: ctx->val.i = ARG8(regs); break; case UFT_AARCH64_REG_S0: asm volatile ("str s0, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_S1: asm volatile ("str s1, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_S2: asm volatile ("str s2, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_S3: asm volatile ("str s3, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_S4: asm volatile ("str s4, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_S5: asm volatile ("str s5, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_S6: asm volatile ("str s6, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_S7: asm volatile ("str s7, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_D0: asm volatile ("str d0, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_D1: asm volatile ("str d1, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_D2: asm volatile ("str d2, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_D3: asm volatile ("str d3, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_D4: asm volatile ("str d4, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_D5: asm volatile ("str d5, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_D6: asm volatile ("str d6, %0\n" : "=m" (ctx->val.v)); break; case UFT_AARCH64_REG_D7: asm volatile ("str d7, %0\n" : "=m" (ctx->val.v)); break; default: return -1; } return 0; } void mcount_get_stack_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { int offset = 1; unsigned long *addr = ctx->stack_base; switch (spec->type) { case ARG_TYPE_STACK: offset = spec->stack_ofs; break; case ARG_TYPE_FLOAT: offset = spec->idx - ARCH_MAX_FLOAT_REGS; break; case ARG_TYPE_INDEX: offset = spec->idx - ARCH_MAX_REG_ARGS; break; case ARG_TYPE_REG: default: /* should not reach here */ pr_err_ns("invalid stack access for arguments\n"); break; } if (offset < 1 || offset > 100) { pr_dbg("invalid stack offset: %d\n", offset); memset(ctx->val.v, 0, sizeof(ctx->val)); return; } addr += offset; if (check_mem_region(ctx, (unsigned long)addr)) memcpy(ctx->val.v, addr, spec->size); else { pr_dbg("stack address is not allowed: %p\n", addr); memset(ctx->val.v, 0, sizeof(ctx->val)); } } void mcount_arch_get_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { /* don't support long double, treat it as double */ if (unlikely(spec->size == 10)) spec->size = 8; if (mcount_get_register_arg(ctx, spec) < 0) mcount_get_stack_arg(ctx, spec); } void mcount_arch_get_retval(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { /* don't support long double, treat it as double */ if (unlikely(spec->size == 10)) spec->size = 8; /* type of return value cannot be FLOAT, so check format instead */ if (spec->fmt == ARG_FMT_FLOAT) { long *float_retval = ctx->retval - 2; if (spec->size <= 4) { asm volatile ("ldr s0, %1\n" "str s0, %0\n" : "=m" (ctx->val.v) : "m" (float_retval)); } else { asm volatile ("ldr d0, %1\n" "str d0, %0\n" : "=m" (ctx->val.v) : "m" (float_retval)); } } else memcpy(ctx->val.v, ctx->retval, spec->size); } unsigned long mcount_arch_plthook_addr(struct plthook_data *pd, int idx) { struct sym *sym; sym = &pd->dsymtab.sym[0]; return sym->addr - ARCH_PLT0_SIZE; } uftrace-0.9.4/arch/aarch64/mcount.S000066400000000000000000000021241362052523300167530ustar00rootroot00000000000000#include "utils/asm.h" .text /* universal stack constraint: (SP mod 16) == 0 */ GLOBAL(_mcount) /* setup frame pointer */ stp x29, x30, [sp, #-16]! mov x29, sp /* save arguments */ stp x6, x7, [sp, #-16]! stp x4, x5, [sp, #-16]! stp x2, x3, [sp, #-16]! stp x0, x1, [sp, #-16]! ldr x0, [x29] add x0, x0, #8 mov x1, x30 mov x2, sp bl mcount_entry /* restore arguments */ ldp x0, x1, [sp], #16 ldp x2, x3, [sp], #16 ldp x4, x5, [sp], #16 ldp x6, x7, [sp], #16 /* restore frame pointer */ ldp x29, x30, [sp], #16 ret END(_mcount) ENTRY(mcount_return) /* setup frame pointer */ stp x29, x30, [sp, #-16]! /* save return values */ stp x0, x1, [sp, #-16]! stp d0, d1, [sp, #-16]! /* * save indirect result location register * used in C++ for returning non-trivial objects */ str x8, [sp, #-16]! add x0, sp, #32 bl mcount_exit mov x16, x0 /* restore indirect result location register */ ldr x8, [sp], #16 /* restore return values */ ldp d0, d1, [sp], #16 ldp x0, x1, [sp], #16 /* restore frame pointer */ ldp x29, x30, [sp], #16 br x16 END(mcount_return) uftrace-0.9.4/arch/aarch64/plthook.S000066400000000000000000000032521362052523300171310ustar00rootroot00000000000000/* * Based on glibc/ports/sysdeps/aarch64/dl-trampoline.S */ #include "utils/asm.h" .text .align 2 .macro save_args stp x6, x7, [sp, #-16]! stp x4, x5, [sp, #-16]! stp x2, x3, [sp, #-16]! stp x0, x1, [sp, #-16]! .endm .macro restore_args ldp x0, x1, [sp], #16 ldp x2, x3, [sp], #16 ldp x4, x5, [sp], #16 ldp x6, x7, [sp], #16 .endm ENTRY(plt_hooker) /* * it gets called with: * [sp, #8] : lr * [sp, #0] : &PLTGOT[n] * x16 (ip0): &PLTGOT[2] * x17 (ip1): address of dl resolver */ stp x16, x17, [sp, #-16]! save_args /* sp -= 64 */ add x0, sp, #88 ldr x1, [sp, #80] sub x1, x1, x16 lsr x1, x1, #3 sub x1, x1, #1 ldr x2, [x16, #-8] mov x3, sp bl plthook_entry cmp x0, #0 b.eq .L1 mov x16, x0 restore_args /* if we skip the resolver, it also needs to pop stacks */ add sp, sp, #32 /* restore original LR */ ldr x30, [sp, #-8] br x16 .L1: restore_args /* restore original stack layout */ add sp, sp, #16 adrp x17, plthook_resolver_addr ldr x17, [x17, #:lo12:plthook_resolver_addr] /* restore original contents */ ldr x16, [sp, #-16] ldr x30, [sp, #8] br x17 END(plt_hooker) ENTRY(plthook_return) /* setup frame pointer */ stp x29, x30, [sp, #-16]! /* save return values */ stp x0, x1, [sp, #-16]! stp d0, d1, [sp, #-16]! /* * save indirect result location register * used in C++ for returning non-trivial objects */ str x8, [sp, #-16]! add x0, sp, #32 bl plthook_exit mov x16, x0 /* restore indirect result location register */ ldr x8, [sp], #16 /* restore return values */ ldp d0, d1, [sp], #16 ldp x0, x1, [sp], #16 /* restore frame pointer */ ldp x29, x30, [sp], #16 br x16 END(plthook_return) uftrace-0.9.4/arch/arm/000077500000000000000000000000001362052523300146525ustar00rootroot00000000000000uftrace-0.9.4/arch/arm/Makefile000066400000000000000000000016271362052523300163200ustar00rootroot00000000000000sdir := $(srcdir)/arch/arm odir := $(objdir)/arch/arm LINKFLAGS := -r include $(srcdir)/Makefile.include ARCH_ENTRY_SRC = $(wildcard $(sdir)/*.S) ARCH_MCOUNT_SRC = $(wildcard $(sdir)/mcount-*.c) ARCH_UFTRACE_SRC = $(sdir)/cpuinfo.c ARCH_MCOUNT_OBJS = $(patsubst $(sdir)/%.S,$(odir)/%.op,$(ARCH_ENTRY_SRC)) ARCH_MCOUNT_OBJS += $(patsubst $(sdir)/%.c,$(odir)/%.op,$(ARCH_MCOUNT_SRC)) ARCH_UFTRACE_OBJS = $(patsubst $(sdir)/%.c,$(odir)/%.o,$(ARCH_UFTRACE_SRC)) all: $(odir)/entry.op $(odir)/mcount-entry.op: $(ARCH_MCOUNT_OBJS) $(QUIET_LINK)$(LD) $(LINKFLAGS) -o $@ $^ $(odir)/uftrace.o: $(ARCH_UFTRACE_OBJS) $(QUIET_LINK)$(LD) $(LINKFLAGS) -o $@ $^ $(odir)/%.op: $(sdir)/%.S $(QUIET_ASM)$(CC) $(LIB_CFLAGS) -c -o $@ $< $(odir)/%.op: $(sdir)/%.c $(QUIET_CC_FPIC)$(CC) $(LIB_CFLAGS) -c -o $@ $< $(odir)/%.o: $(sdir)/%.c $(QUIET_CC)$(CC) $(UFTRACE_CFLAGS) -c -o $@ $< clean: $(RM) $(odir)/*.op $(odir)/*.o uftrace-0.9.4/arch/arm/cpuinfo.c000066400000000000000000000010611362052523300164570ustar00rootroot00000000000000#include #include int arch_fill_cpuinfo_model(int fd) { char buf[1024]; FILE *fp; int ret = -1; fp = fopen("/proc/cpuinfo", "r"); if (fp == NULL) return -1; while (fgets(buf, sizeof(buf), fp) != NULL) { if (!strncmp(buf, "Processor\t:", 11)) { dprintf(fd, "cpuinfo:desc=%s", &buf[12]); ret = 0; break; } else if (!strncmp(buf, "model name\t:", 12)) { dprintf(fd, "cpuinfo:desc=%s", &buf[13]); ret = 0; break; } } if (ret < 0) dprintf(fd, "cpuinfo:desc=ARM (unknown)\n"); fclose(fp); return ret; } uftrace-0.9.4/arch/arm/mcount-arch.h000066400000000000000000000012671362052523300172510ustar00rootroot00000000000000#ifndef MCOUNT_ARCH_H #define MCOUNT_ARCH_H #define mcount_regs mcount_regs struct mcount_regs { unsigned long r0; unsigned long r1; unsigned long r2; unsigned long r3; }; #define ARG1(a) ((a)->r0) #define ARG2(a) ((a)->r1) #define ARG3(a) ((a)->r2) #define ARG4(a) ((a)->r3) #define ARCH_MAX_REG_ARGS 4 #define ARCH_MAX_FLOAT_REGS 16 #define ARCH_MAX_DOUBLE_REGS 8 struct mcount_arch_context { }; struct symtabs; #define FIX_PARENT_LOC unsigned long * mcount_arch_parent_location(struct symtabs *symtabs, unsigned long *parent_loc, unsigned long child_ip); #define ARCH_PLT0_SIZE 20 #define ARCH_PLTHOOK_ADDR_OFFSET 0 #endif /* MCOUNT_ARCH_H */ uftrace-0.9.4/arch/arm/mcount-support.c000066400000000000000000000267001362052523300200420ustar00rootroot00000000000000#include #include #include #include #ifndef EF_ARM_ABI_FLOAT_HARD # define EF_ARM_ABI_FLOAT_HARD EF_ARM_VFP_FLOAT #endif #include "libmcount/internal.h" #include "utils/utils.h" #include "utils/symbol.h" #include "utils/rbtree.h" #include "utils/filter.h" struct lr_offset { int offset; // 4-byte unit bool pushed; }; #define REG_SP 13 /* whether current machine supports hardfp */ static bool use_hard_float = false; #ifdef HAVE_ARM_HARDFP /* need to check hardfp at runtime */ static bool float_abi_checked = false; #else /* disable hardfp as it's not supported */ static bool float_abi_checked = true; #endif struct offset_entry { struct rb_node node; unsigned long addr; unsigned long offset; }; static unsigned rotate_right(unsigned val, unsigned bits, unsigned shift) { return (val >> shift) | (val << (bits - shift)); } /* * This function implements ThumbExpandImm() in ARM ARM A6.3.2 * "Modified immediate constants in Thumb instructions". */ static unsigned expand_thumb_imm(unsigned short opcode1, unsigned short opcode2) { unsigned imm_upper = ((opcode1 & 0x0400) >> 7) | ((opcode2 & 0x7000) >> 12); unsigned imm_lower = opcode2 & 0xff; unsigned imm; if ((imm_upper & 0xc) == 0) { switch (imm_upper & 0x3) { case 0: imm = imm_lower; break; case 1: imm = (imm_lower << 16) | imm_lower; break; case 2: imm = (imm_lower << 24) | (imm_lower << 8); break; case 3: imm = (imm_lower << 24) | (imm_lower << 16) | (imm_lower << 8) | imm_lower; break; } } else { unsigned shift = (imm_upper << 1) | (imm_lower >> 7); imm = rotate_right(imm_lower | 0x80, 32, shift); } pr_dbg3("imm: %u (%x/%x)\n", imm, imm_upper, imm_lower); return imm; } static int analyze_mcount_insn(unsigned short *insn, struct lr_offset *lr) { int bit_size = 16; unsigned short opcode = *insn; if (opcode >= 0xe800) bit_size = 32; if (opcode == 0xb500 && (((insn[1] & 0xf800) == 0xf000) && ((insn[2] & 0xc000) == 0xc000))) { /* PUSH $LR + BLX mcount */ if (lr->pushed) lr->offset++; else lr->offset = 0; /* tailcall (use LR directly) */ /* done! */ return 0; } else if ((opcode & 0xfe00) == 0xb400) { /* PUSH (reg mask) */ int i; if ((opcode & 0x100) || lr->pushed) { lr->pushed = true; for (i = 0; i < 8; i++) { if (opcode & (1 << i)) lr->offset++; } } } else if (opcode == 0xe92d) { /* PUSH (reg mask) : 32 bit insn */ int i; unsigned short opcode2 = insn[1]; if ((opcode2 & 0x4000) || lr->pushed) { lr->pushed = true; for (i = 0; i < 13; i++) { if (opcode2 & (1 << i)) lr->offset++; } } } else if ((opcode & 0xff80) == 0xb080) { /* SUB (SP - imm) */ if (lr->pushed) lr->offset += opcode & 0x7f; } else if ((opcode & 0xfbef) == 0xf1ad) { /* SUB (SP - imm) : 32 bit insn */ unsigned short opcode2 = insn[1]; int target = (opcode2 & 0xf00) >> 8; if (lr->pushed && target == REG_SP) { unsigned imm = expand_thumb_imm(opcode, opcode2); lr->offset += imm >> 2; } } else if ((opcode & 0xfbff) == 0xf2ad) { /* SUB (SP - imm) : 32 bit insn */ unsigned short opcode2 = insn[1]; int target = (opcode2 & 0xf00) >> 8; if (lr->pushed && target == REG_SP) { unsigned imm = opcode2 & 0xff; imm |= (opcode2 & 0x7000) >> 4; imm |= (opcode & 0x400) << 1; lr->offset += imm >> 2; } } else if ((opcode & 0xf800) == 0xa800) { /* ADD (SP + imm) */ int target = (opcode & 0x380) >> 7; if (lr->pushed && target == REG_SP) lr->offset -= opcode & 0xff; } else if ((opcode & 0xff80) == 0xb000) { /* ADD (SP + imm) */ if (lr->pushed) lr->offset -= opcode & 0x3f; } else if ((opcode & 0xfbef) == 0xf10d) { /* ADD (SP + imm) : 32 bit insn */ unsigned short opcode2 = insn[1]; int target = (opcode & 0xf00) >> 8; if (lr->pushed && target == REG_SP) { unsigned imm = expand_thumb_imm(opcode, opcode2); lr->offset -= imm >> 2; } } else if (opcode == 0xf84d) { /* STR [SP + imm]! */ unsigned short opcode2 = insn[1]; if (lr->pushed && (opcode2 & 0xfff) == 0xd04) lr->offset++; } else if ((opcode & 0xffbf) == 0xed2d) { /* VPUSH (VFP/NEON reg list) */ unsigned short opcode2 = insn[1]; unsigned imm = opcode2 & 0xff; if (lr->pushed) lr->offset += imm; } else if ((opcode & 0xf800) == 0x4800) { /* LDR [PC + imm] */ } else if ((opcode & 0xfff0) == 0xf8d0) { /* LDR.W (reg + imm) */ } else { pr_err_ns("cannot analyze insn: %hx\n", opcode); } return bit_size == 16 ? 1 : 2; } #define MAX_ANALYSIS_COUNT 16 static void analyze_mcount_instructions(unsigned short *insn, struct lr_offset *lr) { int ret; int count = 0; do { ret = analyze_mcount_insn(insn, lr); insn += ret; } while (ret && count++ < MAX_ANALYSIS_COUNT); if (count > MAX_ANALYSIS_COUNT) { pr_dbg("stopping analysis on a long function prologue\n"); return; } pr_dbg2("%s: return address offset = %+d\n", __func__, lr->offset); } /* This code is only meaningful on THUMB2 mode: @loc = $sp + 4 */ unsigned long *mcount_arch_parent_location(struct symtabs *symtabs, unsigned long *parent_loc, unsigned long child_ip) { struct sym *sym; unsigned short buf[MAX_ANALYSIS_COUNT]; struct lr_offset lr = { .offset = 0, }; struct uftrace_mmap *map; uint64_t map_start_addr = 0; uint64_t load_addr; sym = find_symtabs(symtabs, child_ip); if (sym == NULL) pr_err_ns("cannot find symbol for %lx\n", child_ip); // on ARM mode, return as is if ((sym->addr & 1) == 0) return parent_loc; map = find_map(symtabs, child_ip); if (map != NULL && map != MAP_KERNEL) map_start_addr = map->start; load_addr = sym->addr + map_start_addr; pr_dbg2("copying instructions of %s from %#x\n", sym->name, load_addr); memcpy(buf, (void *)(long)(load_addr & ~1), sizeof(buf)); analyze_mcount_instructions(buf, &lr); return parent_loc + lr.offset; } int check_float_abi_cb(struct dl_phdr_info *info, size_t size, void *data) { unsigned i; for (i = 0; i < info->dlpi_phnum; i++) { const Elf32_Phdr *phdr = info->dlpi_phdr + i; if (phdr->p_type == PT_LOAD) { Elf32_Ehdr *ehdr = (void *)info->dlpi_addr + phdr->p_vaddr; use_hard_float = ehdr->e_flags & EF_ARM_ABI_FLOAT_HARD; break; } } float_abi_checked = true; return 1; } void check_float_abi(void) { dl_iterate_phdr(check_float_abi_cb, NULL); } int mcount_get_register_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { struct mcount_regs *regs = ctx->regs; int reg_idx; switch (spec->type) { case ARG_TYPE_REG: reg_idx = spec->reg_idx; break; case ARG_TYPE_FLOAT: if (use_hard_float) { if (spec->size <= 4) reg_idx = spec->idx + UFT_ARM_REG_FLOAT_BASE; else reg_idx = spec->idx + UFT_ARM_REG_DOUBLE_BASE; break; } /* fall through */ case ARG_TYPE_INDEX: reg_idx = spec->idx; /* for integer arguments */ if (spec->size == 8 && (reg_idx & 1) == 0) reg_idx++; break; case ARG_TYPE_STACK: default: return -1; } switch (reg_idx) { case UFT_ARM_REG_R0: ctx->val.i = ARG1(regs); if (spec->size == 8) ctx->val.ll.hi = ARG2(regs); break; case UFT_ARM_REG_R1: ctx->val.i = ARG2(regs); break; case UFT_ARM_REG_R2: ctx->val.i = ARG3(regs); if (spec->size == 8) ctx->val.ll.hi = ARG4(regs); break; case UFT_ARM_REG_R3: ctx->val.i = ARG4(regs); break; #ifdef HAVE_ARM_HARDFP case UFT_ARM_REG_S0: asm volatile ("vstr %%s0, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S1: asm volatile ("vstr %%s1, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S2: asm volatile ("vstr %%s2, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S3: asm volatile ("vstr %%s3, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S4: asm volatile ("vstr %%s4, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S5: asm volatile ("vstr %%s5, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S6: asm volatile ("vstr %%s6, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S7: asm volatile ("vstr %%s7, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S8: asm volatile ("vstr %%s8, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S9: asm volatile ("vstr %%s9, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S10: asm volatile ("vstr %%s10, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S11: asm volatile ("vstr %%s11, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S12: asm volatile ("vstr %%s12, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S13: asm volatile ("vstr %%s13, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S14: asm volatile ("vstr %%s14, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_S15: asm volatile ("vstr %%s15, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_D0: asm volatile ("vstr %%d0, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_D1: asm volatile ("vstr %%d1, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_D2: asm volatile ("vstr %%d2, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_D3: asm volatile ("vstr %%d3, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_D4: asm volatile ("vstr %%d4, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_D5: asm volatile ("vstr %%d5, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_D6: asm volatile ("vstr %%d6, %0\n" : "=m" (ctx->val.v)); break; case UFT_ARM_REG_D7: asm volatile ("vstr %%d7, %0\n" : "=m" (ctx->val.v)); break; #endif /* HAVE_ARM_HARDFP */ default: return -1; } return 0; } void mcount_get_stack_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { int offset = 1; unsigned long *addr = ctx->stack_base; switch (spec->type) { case ARG_TYPE_STACK: offset = spec->stack_ofs; break; case ARG_TYPE_FLOAT: if (use_hard_float) { if (spec->size <= 4) offset = spec->idx - ARCH_MAX_FLOAT_REGS; else offset = (spec->idx - ARCH_MAX_DOUBLE_REGS) * 2 - 1; break; } /* fall through */ case ARG_TYPE_INDEX: offset = spec->idx - ARCH_MAX_REG_ARGS; if (spec->size == 8 && (offset & 1) == 0) offset++; break; case ARG_TYPE_REG: default: /* should not reach here */ pr_err_ns("invalid stack access for arguments\n"); break; } if (offset < 1 || offset > 100) { pr_dbg("invalid stack offset: %d\n", offset); memset(ctx->val.v, 0, sizeof(ctx->val)); return; } addr += offset; if (check_mem_region(ctx, (unsigned long)addr)) memcpy(ctx->val.v, addr, spec->size); else { pr_dbg("stack address is not allowed: %p\n", addr); memset(ctx->val.v, 0, sizeof(ctx->val)); } } void mcount_arch_get_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { if (!float_abi_checked) check_float_abi(); /* don't support long double, treat it as double */ if (unlikely(spec->size == 10)) spec->size = 8; if (mcount_get_register_arg(ctx, spec) < 0) mcount_get_stack_arg(ctx, spec); } void mcount_arch_get_retval(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { if (!float_abi_checked) check_float_abi(); /* don't support long double, treat it as double */ if (unlikely(spec->size == 10)) spec->size = 8; /* type of return value cannot be FLOAT, so check format instead */ #ifdef HAVE_ARM_HARDFP if (spec->fmt == ARG_FMT_FLOAT && use_hard_float) { /* d0 register (64 bit) was saved below the r0 */ memcpy(ctx->val.v, ctx->retval - 2, spec->size); } else #endif /* HAVE_ARM_HARDFP */ memcpy(ctx->val.v, ctx->retval, spec->size); } unsigned long mcount_arch_plthook_addr(struct plthook_data *pd, int idx) { return pd->plt_addr; } uftrace-0.9.4/arch/arm/mcount.S000066400000000000000000000060111362052523300163010ustar00rootroot00000000000000/* Shamelessly copied from linux/arch/arm/kernel/entry-common.S */ /* * When compiling with -pg, gcc inserts a call to the mcount routine at the * start of every function. In mcount, apart from the function's address (in * lr), we need to get hold of the function's caller's address. * * Older GCCs (pre-4.4) inserted a call to a routine called mcount like this: * * bl mcount * * These versions have the limitation that in order for the mcount routine to * be able to determine the function's caller's address, an APCS-style frame * pointer (which is set up with something like the code below) is required. * * mov ip, sp * push {fp, ip, lr, pc} * sub fp, ip, #4 * * With EABI, these frame pointers are not available unless -mapcs-frame is * specified, and if building as Thumb-2, not even then. * * Newer GCCs (4.4+) solve this problem by introducing a new version of mcount, * with call sites like: * * push {lr} * bl __gnu_mcount_nc * * With these compilers, frame pointers are not necessary. * * mcount can be thought of as a function called in the middle of a subroutine * call. As such, it needs to be transparent for both the caller and the * callee: the original lr needs to be restored when leaving mcount, and no * registers should be clobbered. (In the __gnu_mcount_nc implementation, we * clobber the ip register. This is OK because the ARM calling convention * allows it to be clobbered in subroutines and doesn't use it to hold * parameters.) * * Also recent clang generates following code to call mcount. It saves the * fp and lr registers before calling the function: * * push {fp, lr} * mov fp, sp * sub sp, sp, #8 * bl mcount * * I'm not sure subtracting sp by 8 is guaranteed, but anyway it could use * fp register to find the return address (lr) of the parent function. * */ #include "utils/asm.h" .text .align 2 GLOBAL(__gnu_mcount_nc) push {r0-r3, lr} /* note that caller already pushed lr */ ands r3, lr, #1 /* check lr for ARM/THUMB detection */ add r0, sp, #20 /* r0 points to pushed LR */ bne 1f ldr r1, [fp] /* fp (=r11) might point to return address on THUMB */ ldr r2, [r0] cmp r1, r2 moveq r0, fp 1: mov r1, lr /* child ip */ mov r2, sp /* mcount_args */ bl mcount_entry pop {r0-r3, ip, lr} bx ip END(__gnu_mcount_nc) GLOBAL(mcount) push {r0-r3, fp, lr} /* ensure 8-byte alignment */ ands r3, lr, #1 /* check lr for ARM/THUMB detection */ add r0, fp, #4 /* r0 points to pushed LR */ bne 1f ldr r1, [fp] /* fp (=r11) might point to return address on THUMB */ ldr r2, [r0] cmp r1, r2 moveq r0, lr 1: mov r1, lr /* child ip */ mov r2, sp /* mcount_args */ bl mcount_entry pop {r0-r3, fp, lr} bx lr END(mcount) ENTRY(mcount_return) push {r0-r3, lr, pc} /* ensure 8-byte alignment */ mov r0, sp #ifdef HAVE_ARM_HARDFP vpush {d0-d1} #endif bl mcount_exit #if HAVE_ARM_HARDFP vpop {d0-d1} #endif /* update return address (pc) in the stack */ str r0, [sp, #20] pop {r0-r3, lr, pc} END(mcount_return) uftrace-0.9.4/arch/arm/plthook.S000066400000000000000000000022341362052523300164570ustar00rootroot00000000000000/* * Based on glibc/ports/sysdeps/arm/dl-trampoline.S */ #include "utils/asm.h" .text .align 2 ENTRY(plt_hooker) @ we get called with @ stack[0] contains the return address from this call @ ip contains &GOT[n+3] (pointer to function) @ lr points to &GOT[2] push {r0-r3,ip,lr,pc} add r0, sp, #28 sub r2, ip, lr sub r2, r2, #4 lsr r1, r2, #2 ldr r2, [lr, #-4] mov r3, sp bl plthook_entry cmp r0, $0 beq 1f /* * if we skip the resolver, we also need to pop stack[0] * which saves the original 'lr'. */ str r0, [sp, #24] pop {r0-r3,ip,lr} add sp, sp, #8 ldr lr, [sp, #-4] ldr pc, [sp, #-8] /* return */ 1: ldr r2, .L2 .LPIC0: add r2, pc, r2 ldr r3, .L2+4 ldr r1, [r2, r3] ldr r2, [r1] str r2, [sp, #24] pop {r0-r3,ip,lr,pc} .L3: .align 2 .L2: .word _GLOBAL_OFFSET_TABLE_-(.LPIC0+8) .word plthook_resolver_addr(GOT) END(plt_hooker) ENTRY(plthook_return) push {r0-r3, lr, pc} /* ensure 8-byte alignment */ mov r0, sp #ifdef HAVE_ARM_HARDFP vpush {d0-d1} #endif bl plthook_exit #ifdef HAVE_ARM_HARDFP vpop {d0-d1} #endif /* update return address (pc) in the stack */ str r0, [sp, #20] pop {r0-r3, lr, pc} END(plthook_return) uftrace-0.9.4/arch/i386/000077500000000000000000000000001362052523300145645ustar00rootroot00000000000000uftrace-0.9.4/arch/i386/Makefile000066400000000000000000000016451362052523300162320ustar00rootroot00000000000000LINKFLAGS := -r -m elf_i386 sdir := $(srcdir)/arch/i386 odir := $(objdir)/arch/i386 include $(srcdir)/Makefile.include ARCH_ENTRY_SRC = $(wildcard $(sdir)/*.S) ARCH_MCOUNT_SRC = $(wildcard $(sdir)/mcount-*.c) ARCH_UFTRACE_SRC = $(sdir)/cpuinfo.c ARCH_MCOUNT_OBJS = $(patsubst $(sdir)/%.S,$(odir)/%.op,$(ARCH_ENTRY_SRC)) ARCH_MCOUNT_OBJS += $(patsubst $(sdir)/%.c,$(odir)/%.op,$(ARCH_MCOUNT_SRC)) ARCH_UFTRACE_OBJS = $(patsubst $(sdir)/%.c,$(odir)/%.o,$(ARCH_UFTRACE_SRC)) all: $(odir)/entry.op $(odir)/mcount-entry.op: $(ARCH_MCOUNT_OBJS) $(QUIET_LINK)$(LD) $(LINKFLAGS) -o $@ $^ $(odir)/uftrace.o: $(ARCH_UFTRACE_OBJS) $(QUIET_LINK)$(LD) $(LINKFLAGS) -o $@ $^ $(odir)/%.op: $(sdir)/%.S $(QUIET_ASM)$(CC) $(LIB_CFLAGS) -c -o $@ $< $(odir)/%.op: $(sdir)/%.c $(QUIET_CC_FPIC)$(CC) $(LIB_CFLAGS) -c -o $@ $< $(odir)/%.o: $(sdir)/%.c $(QUIET_CC)$(CC) $(UFTRACE_CFLAGS) -c -o $@ $< clean: $(RM) $(odir)/*.op $(odir)/*.o uftrace-0.9.4/arch/i386/common.S000066400000000000000000000001271362052523300162000ustar00rootroot00000000000000#include "utils/asm.h" ENTRY(get_pc_thunk) movl 0(%esp), %eax ret END(get_pc_thunk) uftrace-0.9.4/arch/i386/cpuinfo.c000066400000000000000000000005751362052523300164020ustar00rootroot00000000000000#include #include int arch_fill_cpuinfo_model(int fd) { char buf[1024]; FILE *fp; int ret = -1; fp = fopen("/proc/cpuinfo", "r"); if (fp == NULL) return -1; while (fgets(buf, sizeof(buf), fp) != NULL) { if (!strncmp(buf, "model name\t:", 12)) { dprintf(fd, "cpuinfo:desc=%s", &buf[13]); ret = 0; break; } } fclose(fp); return ret; } uftrace-0.9.4/arch/i386/fentry.S000066400000000000000000000015161362052523300162220ustar00rootroot00000000000000#include "utils/asm.h" GLOBAL(__fentry__) sub $32, %esp movl %edx, 28(%esp) movl %ecx, 24(%esp) movl %eax, 20(%esp) movl $0, 16(%esp) /* parent location */ leal 36(%esp), %eax movl %eax, 0(%esp) /* child addr */ movl 32(%esp), %eax movl %eax, 4(%esp) /* mcount_args */ leal 16(%esp), %eax movl %eax, 8(%esp) call mcount_entry cmpl $0, %eax jne 1f /* hijack return address */ call get_pc_thunk addl $_GLOBAL_OFFSET_TABLE_, %eax addl $fentry_return@GOTOFF, %eax movl %eax, 36(%esp) 1: movl 20(%esp), %eax movl 24(%esp), %ecx movl 28(%esp), %edx add $32, %esp ret END(__fentry__) ENTRY(fentry_return) sub $16, %esp movl %edx, 8(%esp) movl %eax, 4(%esp) leal 4(%esp), %eax movl %eax, 0(%esp) call mcount_exit movl %eax, 12(%esp) movl 4(%esp), %eax movl 8(%esp), %edx add $12, %esp ret END(fentry_return) uftrace-0.9.4/arch/i386/mcount-arch.h000066400000000000000000000013761362052523300171640ustar00rootroot00000000000000#include "../../utils/symbol.h" #ifndef __MCOUNT_ARCH_H__ #define __MCOUNT_ARCH_H__ #define mcount_regs mcount_regs struct mcount_regs { unsigned long stack1; unsigned long ecx; unsigned long edx; }; #define ARG1(a) ((a)->stack1) #define ARG_REG1(a) ((a)->ecx) #define ARG_REG2(a) ((a)->edx) #define ARCH_MAX_REG_ARGS 3 #define ARCH_MAX_FLOAT_REGS 8 #define HAVE_MCOUNT_ARCH_CONTEXT struct mcount_arch_context { double xmm[ARCH_MAX_FLOAT_REGS]; }; #define FIX_PARENT_LOC unsigned long * mcount_arch_parent_location(struct symtabs *symtabs, unsigned long *parent_loc, unsigned long child_ip); #define ARCH_PLT0_SIZE 16 #define ARCH_PLTHOOK_ADDR_OFFSET 6 #define ARCH_CAN_RESTORE_PLTHOOK 1 #endif /* __MCOUNT_ARCH_H__ */ uftrace-0.9.4/arch/i386/mcount-dynamic.c000066400000000000000000000061741362052523300176670ustar00rootroot00000000000000#include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "dynamic" #define PR_DOMAIN DBG_DYNAMIC #include "libmcount/internal.h" #include "utils/utils.h" #include "utils/symbol.h" #define PAGE_SIZE 4096 /* target instrumentation function it needs to call */ extern void __fentry__(void); int mcount_setup_trampoline(struct mcount_dynamic_info *mdi) { unsigned char trampoline[] = { 0xe8, 0x00, 0x00, 0x00, 0x00, 0x58, 0xff, 0x60, 0x04 }; unsigned long fentry_addr = (unsigned long)__fentry__; size_t trampoline_size = 16; void *trampoline_check; /* find unused 16-byte at the end of the code segment */ mdi->trampoline = ALIGN(mdi->text_addr + mdi->text_size, PAGE_SIZE) - trampoline_size; if (unlikely(mdi->trampoline < mdi->text_addr + mdi->text_size)) { mdi->trampoline += trampoline_size; mdi->text_size += PAGE_SIZE; pr_dbg2("adding a page for fentry trampoline at %#lx\n", mdi->trampoline); trampoline_check = mmap((void *)mdi->trampoline, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (trampoline_check == MAP_FAILED) pr_err("failed to mmap trampoline for setup"); } if (mprotect((void *)mdi->text_addr, mdi->text_size, PROT_READ | PROT_WRITE)) { pr_dbg("cannot setup trampoline due to protection: %m\n"); return -1; } /* jmpq *0x2(%rip) # */ memcpy((void *)mdi->trampoline, trampoline, sizeof(trampoline)); memcpy((void *)mdi->trampoline + sizeof(trampoline), &fentry_addr, sizeof(fentry_addr)); return 0; } void mcount_cleanup_trampoline(struct mcount_dynamic_info *mdi) { if (mprotect((void *)mdi->text_addr, mdi->text_size, PROT_EXEC)) pr_err("cannot restore trampoline due to protection"); } #define CALL_INSN_SIZE 5 static unsigned long get_target_addr(struct mcount_dynamic_info *mdi, unsigned long addr) { while (mdi) { if (mdi->text_addr <= addr && addr < mdi->text_addr + mdi->text_size) return mdi->trampoline - (addr + CALL_INSN_SIZE); mdi = mdi->next; } return 0; } static int patch_fentry_func(struct mcount_dynamic_info *mdi, struct sym *sym) { // In case of "gcc" which is not patched because of old version, // it may not create 5 byte nop. unsigned char nop[] = { 0x0f, 0x1f, 0x44, 0x00, 0x00 }; unsigned char *insn = (unsigned char *)((uintptr_t)sym->addr); unsigned int target_addr; /* only support calls to __fentry__ at the beginning */ if (memcmp(insn, nop, sizeof(nop))) { pr_dbg2("skip non-applicable functions: %s\n", sym->name); return -2; } /* get the jump offset to the trampoline */ target_addr = get_target_addr(mdi, sym->addr); if (target_addr == 0) return -2; /* make a "call" insn with 4-byte offset */ insn[0] = 0xe8; /* hopefully we're not patching 'memcpy' itself */ memcpy(&insn[1], &target_addr, sizeof(target_addr)); pr_dbg3("update function '%s' dynamically to call __fentry__\n", sym->name); return 0; } int mcount_patch_func(struct mcount_dynamic_info *mdi, struct sym *sym, struct mcount_disasm_engine *disasm, unsigned min_size) { return patch_fentry_func(mdi, sym); } uftrace-0.9.4/arch/i386/mcount-support.c000066400000000000000000000163071362052523300177560ustar00rootroot00000000000000/* * basic i386 support for uftrace * * Copyright (C) 2017. Hanbum Park * * Released under the GPL v2. */ #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "mcount" #define PR_DOMAIN DBG_MCOUNT // a max number that retrieves the stack to find the location of // the real return address of the main function for i386. #define MAX_SEARCH_STACK 5 #include "libmcount/internal.h" #include "utils/filter.h" static bool search_main_ret = false; int mcount_get_register_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { struct mcount_regs *regs = ctx->regs; int reg_idx; switch (spec->type) { case ARG_TYPE_REG: reg_idx = spec->reg_idx; break; default: return -1; } switch (reg_idx) { case UFT_I386_REG_ECX: ctx->val.i = ARG_REG1(regs); break; case UFT_I386_REG_EDX: ctx->val.i = ARG_REG2(regs); break; case UFT_I386_REG_XMM0: asm volatile ("movsd %%xmm0, %0\n" : "=m" (ctx->val.v)); break; case UFT_I386_REG_XMM1: asm volatile ("movsd %%xmm1, %0\n" : "=m" (ctx->val.v)); break; case UFT_I386_REG_XMM2: asm volatile ("movsd %%xmm2, %0\n" : "=m" (ctx->val.v)); break; case UFT_I386_REG_XMM3: asm volatile ("movsd %%xmm3, %0\n" : "=m" (ctx->val.v)); break; case UFT_I386_REG_XMM4: asm volatile ("movsd %%xmm4, %0\n" : "=m" (ctx->val.v)); break; case UFT_I386_REG_XMM5: asm volatile ("movsd %%xmm5, %0\n" : "=m" (ctx->val.v)); break; case UFT_I386_REG_XMM6: asm volatile ("movsd %%xmm6, %0\n" : "=m" (ctx->val.v)); break; case UFT_I386_REG_XMM7: asm volatile ("movsd %%xmm7, %0\n" : "=m" (ctx->val.v)); break; default: /* should not reach here */ pr_err_ns("invalid register access for arguments\n"); break; } return 0; } void mcount_get_stack_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { int offset; unsigned long *addr = ctx->stack_base; switch (spec->type) { case ARG_TYPE_STACK: offset = spec->stack_ofs; break; case ARG_TYPE_INDEX: offset = spec->idx; break; case ARG_TYPE_FLOAT: offset = spec->idx; break; default: /* should not reach here */ pr_err_ns("invalid stack access for arguments\n"); break; } if (offset < 1 || offset > 100) { pr_dbg("invalid stack offset: %d\n", offset); memset(ctx->val.v, 0, sizeof(ctx->val)); return; } addr += offset; if (check_mem_region(ctx, (unsigned long)addr)) memcpy(ctx->val.v, addr, spec->size); else { pr_dbg("stack address is not allowed: %p\n", addr); memset(ctx->val.v, 0, sizeof(ctx->val)); } } void mcount_arch_get_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { if (mcount_get_register_arg(ctx, spec) < 0) mcount_get_stack_arg(ctx, spec); } void mcount_arch_get_retval(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { /* type of return value cannot be FLOAT, so check format instead */ if (spec->fmt != ARG_FMT_FLOAT) memcpy(ctx->val.v, ctx->retval, spec->size); else if (spec->size == 4) asm volatile ("fstps %0\n\tflds %0" : "=m" (ctx->val.v)); else if (spec->size == 8) asm volatile ("fstpl %0\n\tfldl %0" : "=m" (ctx->val.v)); else if (spec->size == 10) asm volatile ("fstpt %0\n\tfldt %0" : "=m" (ctx->val.v)); } void mcount_save_arch_context(struct mcount_arch_context *ctx) { asm volatile ("movsd %%xmm0, %0\n" : "=m" (ctx->xmm[0])); asm volatile ("movsd %%xmm1, %0\n" : "=m" (ctx->xmm[1])); asm volatile ("movsd %%xmm2, %0\n" : "=m" (ctx->xmm[2])); asm volatile ("movsd %%xmm3, %0\n" : "=m" (ctx->xmm[3])); asm volatile ("movsd %%xmm4, %0\n" : "=m" (ctx->xmm[4])); asm volatile ("movsd %%xmm5, %0\n" : "=m" (ctx->xmm[5])); asm volatile ("movsd %%xmm6, %0\n" : "=m" (ctx->xmm[6])); asm volatile ("movsd %%xmm7, %0\n" : "=m" (ctx->xmm[7])); } void mcount_restore_arch_context(struct mcount_arch_context *ctx) { asm volatile ("movsd %0, %%xmm0\n" :: "m" (ctx->xmm[0])); asm volatile ("movsd %0, %%xmm1\n" :: "m" (ctx->xmm[1])); asm volatile ("movsd %0, %%xmm2\n" :: "m" (ctx->xmm[2])); asm volatile ("movsd %0, %%xmm3\n" :: "m" (ctx->xmm[3])); asm volatile ("movsd %0, %%xmm4\n" :: "m" (ctx->xmm[4])); asm volatile ("movsd %0, %%xmm5\n" :: "m" (ctx->xmm[5])); asm volatile ("movsd %0, %%xmm6\n" :: "m" (ctx->xmm[6])); asm volatile ("movsd %0, %%xmm7\n" :: "m" (ctx->xmm[7])); } /* For 16-byte stack-alignment, the main function stores the return address in its stack scope at prologue. When the time comes for the main function to return, 1. restore the saved return address from stack. 2. After cleaning up the stack. 3. Put the return address at the top of the stack and return. 4. will be returned. 080485f8
: 80485f8: 8d 4c 24 04 lea 0x4(%esp),%ecx 80485fc: 83 e4 f0 and $0xfffffff0,%esp 80485ff: ff 71 fc pushl -0x4(%ecx) 8048602: 55 push %ebp 8048603: 89 e5 mov %esp,%ebp 8048605: 51 push %ecx 8048606: 83 ec 14 sub $0x14,%esp 8048609: e8 02 fe ff ff call 8048410 ... ... 8048645: 8b 4d fc mov -0x4(%ebp),%ecx 8048648: c9 leave 8048649: 8d 61 fc lea -0x4(%ecx),%esp 804864c: c3 ret So, in this case. The return address we want to replace with mcount_exit is in the stack scope of the main function. Non a parent located. we search stack for that address. we will look for it. we will find it, and we will replace it. GOOD LUCK! */ unsigned long *mcount_arch_parent_location(struct symtabs *symtabs, unsigned long *parent_loc, unsigned long child_ip) { if (!search_main_ret) { struct sym *parent_sym, *child_sym; char *parent_name, *child_name; const char *find_main[] = { "__libc_start_main", "main" }; unsigned long ret_addr; unsigned long search_ret_addr; ret_addr = *parent_loc; parent_sym = find_symtabs(symtabs, ret_addr); parent_name = symbol_getname(parent_sym, ret_addr); child_sym = find_symtabs(symtabs, child_ip); child_name = symbol_getname(child_sym, child_ip); // Assuming that this happens only in main.. bool found_main_ret = false; int stack_index = 0; if (!(strcmp(find_main[0], parent_name) || strcmp(find_main[1], child_name))) { ret_addr = *parent_loc; for (stack_index = 1; stack_index < MAX_SEARCH_STACK; stack_index++) { search_ret_addr = *(unsigned long *)(parent_loc + stack_index); if (search_ret_addr == ret_addr) { parent_loc = parent_loc + stack_index; found_main_ret = true; } } // if we couldn't found correct position of return address, // maybe this approach is not available anymore. if (!found_main_ret) { pr_dbg2("cannot find ret address of main\n"); } search_main_ret = true; } } return parent_loc; } // in i386, the idx value is set to a multiple of 8 unlike other. unsigned long mcount_arch_child_idx(unsigned long child_idx) { if (child_idx > 0) { if (child_idx % 8) { pr_err_ns("the malformed child idx : %lx\n", child_idx); } child_idx = child_idx / 8; } return child_idx; } uftrace-0.9.4/arch/i386/mcount.S000066400000000000000000000017031362052523300162160ustar00rootroot00000000000000/* in i386, generally used stack for argument passing. */ /* use register for return : %eax */ /* no need save registers */ /* stack frame (with -pg): parent addr = 4(%ebp) */ /* child addr = (%esp) */ #include "utils/asm.h" GLOBAL(mcount) sub $32, %esp /* save registers */ movl %edx, 28(%esp) movl %ecx, 24(%esp) movl %eax, 20(%esp) movl $0, 16(%esp) /* parent location */ leal 4(%ebp), %eax movl %eax, 0(%esp) /* child addr */ movl 32(%esp), %eax movl %eax, 4(%esp) /* mcount_regs */ leal 16(%esp), %eax movl %eax, 8(%esp) call mcount_entry /* restore registers */ movl 20(%esp), %eax movl 24(%esp), %ecx movl 28(%esp), %edx add $32, %esp ret END(mcount) ENTRY(mcount_return) sub $16, %esp movl %edx, 8(%esp) movl %eax, 4(%esp) leal 4(%esp), %eax movl %eax, 0(%esp) /* returns original parent address */ call mcount_exit movl %eax, 12(%esp) movl 4(%esp), %eax movl 8(%esp), %edx add $12, %esp ret END(mcount_return) uftrace-0.9.4/arch/i386/plthook.S000066400000000000000000000021041362052523300163650ustar00rootroot00000000000000#include "utils/asm.h" .hidden plthook_resolver_addr ENTRY(plt_hooker) sub $32, %esp /* save registers */ movl %edx, 24(%esp) movl %ecx, 20(%esp) /* this is for ARG1 that using in jmp */ movl 44(%esp), %eax movl %eax, 16(%esp) /* stack address contain parent location */ leal 40(%esp), %eax movl %eax, 0(%esp) /* child_idx */ movl 36(%esp), %eax movl %eax, 4(%esp) /* module_id */ movl 32(%esp), %eax movl %eax, 8(%esp) /* mcount_args */ leal 16(%esp), %eax movl %eax, 12(%esp) call plthook_entry /* restore registers */ movl 20(%esp), %ecx movl 24(%esp), %edx add $32, %esp cmpl $0, %eax jnz 1f /* get address of plthook_resolver_addr */ call get_pc_thunk addl $_GLOBAL_OFFSET_TABLE_, %eax leal plthook_resolver_addr@GOTOFF(%eax), %eax movl (%eax), %eax jmp *%eax 1: add $8, %esp jmp *%eax END(plt_hooker) ENTRY(plthook_return) sub $16, %esp movl %edx, 8(%esp) movl %eax, 4(%esp) leal 4(%esp), %eax movl %eax, 0(%esp) call plthook_exit movl %eax, 12(%esp) movl 4(%esp), %eax movl 8(%esp), %edx add $12, %esp ret END(plthook_return) uftrace-0.9.4/arch/x86_64/000077500000000000000000000000001362052523300150315ustar00rootroot00000000000000uftrace-0.9.4/arch/x86_64/Makefile000066400000000000000000000016771362052523300165040ustar00rootroot00000000000000LINKFLAGS := -r sdir := $(srcdir)/arch/x86_64 odir := $(objdir)/arch/x86_64 include $(srcdir)/Makefile.include ARCH_ENTRY_SRC = $(wildcard $(sdir)/*.S) ARCH_MCOUNT_SRC = $(wildcard $(sdir)/mcount-*.c) $(sdir)/symbol.c ARCH_UFTRACE_SRC = $(sdir)/cpuinfo.c $(sdir)/symbol.c ARCH_MCOUNT_OBJS = $(patsubst $(sdir)/%.S,$(odir)/%.op,$(ARCH_ENTRY_SRC)) ARCH_MCOUNT_OBJS += $(patsubst $(sdir)/%.c,$(odir)/%.op,$(ARCH_MCOUNT_SRC)) ARCH_UFTRACE_OBJS = $(patsubst $(sdir)/%.c,$(odir)/%.o,$(ARCH_UFTRACE_SRC)) all: $(odir)/entry.op $(odir)/mcount-entry.op: $(ARCH_MCOUNT_OBJS) $(QUIET_LINK)$(LD) $(LINKFLAGS) -o $@ $^ $(odir)/uftrace.o: $(ARCH_UFTRACE_OBJS) $(QUIET_LINK)$(LD) $(LINKFLAGS) -o $@ $^ $(odir)/%.op: $(sdir)/%.S $(QUIET_ASM)$(CC) $(LIB_CFLAGS) -c -o $@ $< $(odir)/%.op: $(sdir)/%.c $(QUIET_CC_FPIC)$(CC) $(LIB_CFLAGS) -c -o $@ $< $(odir)/%.o: $(sdir)/%.c $(QUIET_CC)$(CC) $(UFTRACE_CFLAGS) -c -o $@ $< clean: $(RM) $(odir)/*.op $(odir)/*.o uftrace-0.9.4/arch/x86_64/cpuinfo.c000066400000000000000000000005751362052523300166470ustar00rootroot00000000000000#include #include int arch_fill_cpuinfo_model(int fd) { char buf[1024]; FILE *fp; int ret = -1; fp = fopen("/proc/cpuinfo", "r"); if (fp == NULL) return -1; while (fgets(buf, sizeof(buf), fp) != NULL) { if (!strncmp(buf, "model name\t:", 12)) { dprintf(fd, "cpuinfo:desc=%s", &buf[13]); ret = 0; break; } } fclose(fp); return ret; } uftrace-0.9.4/arch/x86_64/dynamic.S000066400000000000000000000074401362052523300166060ustar00rootroot00000000000000/* * argument passing: %rdi, %rsi, %rdx, %rcx, %r8, %r9 * * if %rax have value bigger than 0, it means return address * to the function have patched for dynamic tracing. * otherwise, it must be 0 that means error occurred. * stack frame : parent addr = 8(%rsp), child addr = (%rsp) * * For example: * * Parent(caller): main() * Child(callee): Hello() * * Dump of assembler code for function main: * 0x00000000004005b6 <+0>: callq *0x20043c(%rip) # 0x6009f8 * 0x00000000004005bc <+6>: nop * 0x00000000004005bd <+7>: nop * 0x00000000004005be <+8>: nop * 0x00000000004005bf <+9>: mov $0x400678,%edi * 0x00000000004005c4 <+14>: callq 0x4004a0 * 0x00000000004005c9 <+19>: mov $0x0,%eax * 0x00000000004005ce <+24>: callq 0x400597 * parent => 0x00000000004005d3 <+29>: mov $0x0,%eax * 0x00000000004005d8 <+34>: pop %rbp * 0x00000000004005d9 <+35>: retq * * Dump of assembler code for function Hello: * 0x0000000000400597 <+0>: callq *0x20045b(%rip) # 0x6009f8 * child => 0x000000000040059d <+6>: nop * 0x000000000040059e <+7>: nop * 0x000000000040059f <+8>: movq $0x400668,-0x8(%rbp) * 0x00000000004005a7 <+16>: mov -0x8(%rbp),%rax * 0x00000000004005ab <+20>: mov %rax,%rdi * 0x00000000004005ae <+23>: callq 0x400480 * 0x00000000004005b3 <+28>: nop * 0x00000000004005b4 <+29>: leaveq * 0x00000000004005b5 <+30>: retq * */ #include "utils/asm.h" GLOBAL(__dentry__) .cfi_startproc sub $48, %rsp .cfi_adjust_cfa_offset 48 movq %rdi, 40(%rsp) movq %rsi, 32(%rsp) movq %rdx, 24(%rsp) movq %rcx, 16(%rsp) movq %r8, 8(%rsp) movq %r9, 0(%rsp) /* child addr */ movq 48(%rsp), %rsi /* parent location */ lea 56(%rsp), %rdi /* mcount_args */ movq %rsp, %rdx .cfi_def_cfa_register rdx /* align stack pointer to 16-byte */ andq $0xfffffffffffffff0, %rsp push %rdx /* save rax (implicit argument for variadic functions) */ push %rax /* save scratch registers due to -fipa-ra */ push %r10 push %r11 call mcount_entry /* original stack pointer */ movq 24(%rsp), %rdx /* child addr */ movq 48(%rdx), %rdi /* find location that has the original code */ call mcount_find_code /* original stack pointer */ movq 24(%rsp), %rdx /* overwrite return address */ movq %rax, 48(%rdx) pop %r11 pop %r10 pop %rax movq %rdx, %rsp /* restore mcount_args */ movq 0(%rsp), %r9 movq 8(%rsp), %r8 movq 16(%rsp), %rcx movq 24(%rsp), %rdx movq 32(%rsp), %rsi movq 40(%rsp), %rdi add $48, %rsp .cfi_adjust_cfa_offset -48 retq .cfi_endproc END(__dentry__) ENTRY(dynamic_return) .cfi_startproc sub $96, %rsp .cfi_def_cfa_offset 96 /* save all caller-saved registers due to -fipa-ra */ movq %r11, 80(%rsp) movq %r10, 72(%rsp) movq %r9, 64(%rsp) movq %r8, 56(%rsp) movq %rdi, 48(%rsp) movq %rsi, 40(%rsp) movq %rcx , 32(%rsp) /* below are used to carry return value */ movdqu %xmm0, 16(%rsp) movq %rdx, 8(%rsp) movq %rax, 0(%rsp) /* set the first argument of mcount_exit as pointer to return values */ movq %rsp, %rdi /* returns original parent address */ call mcount_exit movq %rax, 88(%rsp) movq 0(%rsp), %rax movq 8(%rsp), %rdx movq 16(%rsp), %xmm0 movq 32(%rsp), %rcx movq 40(%rsp), %rsi movq 48(%rsp), %rdi movq 56(%rsp), %r8 movq 64(%rsp), %r9 movq 72(%rsp), %r10 movq 80(%rsp), %r11 add $88, %rsp .cfi_def_cfa_offset 8 retq .cfi_endproc END(dynamic_return) uftrace-0.9.4/arch/x86_64/fentry.S000066400000000000000000000037041362052523300164700ustar00rootroot00000000000000/* argument passing: %rdi, %rsi, %rdx, %rcx, %r8, %r9 */ /* return value: %rax */ /* callee saved: %rbx, %rbp, %rsp, %r12-r15 */ /* stack frame (with -pg -mfentry): parent addr = 8(%rsp), child addr = (%rsp) */ /* * For example: Parent(caller): main() Child(callee): hello() Dump of assembler code for function main: 0x00000000004006bc <+0>: callq 0x400550 <__fentry__@plt> 0x00000000004006c1 <+5>: push %rbp 0x00000000004006c2 <+6>: mov %rsp,%rbp 0x00000000004006c5 <+9>: mov $0x0,%eax 0x00000000004006ca <+14>: callq 0x4006a6 parent addr => 0x00000000004006cf <+19>: nop 0x00000000004006d0 <+20>: pop %rbp 0x00000000004006d1 <+21>: retq Dump of assembler code for function hello: 0x00000000004006a6 <+0>: callq 0x400550 <__fentry__@plt> child addr => 0x00000000004006ab <+5>: push %rbp 0x00000000004006ac <+6>: mov %rsp,%rbp */ #include "utils/asm.h" GLOBAL(__fentry__) .cfi_startproc sub $48, %rsp .cfi_adjust_cfa_offset 48 /* save register arguments in mcount_args */ movq %rdi, 40(%rsp) movq %rsi, 32(%rsp) movq %rdx, 24(%rsp) movq %rcx, 16(%rsp) movq %r8, 8(%rsp) movq %r9, 0(%rsp) /* child addr */ movq 48(%rsp), %rsi /* parent location */ lea 56(%rsp), %rdi /* mcount_args */ movq %rsp, %rdx .cfi_def_cfa_register rdx /* align stack pointer to 16-byte */ andq $0xfffffffffffffff0, %rsp push %rdx /* save rax (implicit argument for variadic functions) */ push %rax call mcount_entry pop %rax /* restore original stack pointer */ pop %rdx movq %rdx, %rsp .cfi_def_cfa_register rsp /* restore mcount_args */ movq 0(%rsp), %r9 movq 8(%rsp), %r8 movq 16(%rsp), %rcx movq 24(%rsp), %rdx movq 32(%rsp), %rsi movq 40(%rsp), %rdi add $48, %rsp .cfi_adjust_cfa_offset -48 retq .cfi_endproc END(__fentry__) uftrace-0.9.4/arch/x86_64/mcount-arch.h000066400000000000000000000020611362052523300174210ustar00rootroot00000000000000#ifndef MCOUNT_ARCH_H #define MCOUNT_ARCH_H #include "utils/arch.h" #include "utils/list.h" #define mcount_regs mcount_regs struct mcount_regs { unsigned long r9; unsigned long r8; unsigned long rcx; unsigned long rdx; unsigned long rsi; unsigned long rdi; }; #define ARG1(a) ((a)->rdi) #define ARG2(a) ((a)->rsi) #define ARG3(a) ((a)->rdx) #define ARG4(a) ((a)->rcx) #define ARG5(a) ((a)->r8) #define ARG6(a) ((a)->r9) #define ARCH_MAX_REG_ARGS 6 #define ARCH_MAX_FLOAT_REGS 8 #define HAVE_MCOUNT_ARCH_CONTEXT struct mcount_arch_context { double xmm[ARCH_MAX_FLOAT_REGS]; }; #define ARCH_PLT0_SIZE 16 #define ARCH_PLTHOOK_ADDR_OFFSET 6 #define ARCH_SUPPORT_AUTO_RECOVER 1 #define ARCH_CAN_RESTORE_PLTHOOK 1 struct plthook_arch_context { bool has_plt_sec; }; struct mcount_disasm_engine; struct mcount_dynamic_info; struct mcount_disasm_info; struct sym; int disasm_check_insns(struct mcount_disasm_engine *disasm, struct mcount_dynamic_info *mdi, struct mcount_disasm_info *info); #endif /* MCOUNT_ARCH_H */ uftrace-0.9.4/arch/x86_64/mcount-dynamic.c000066400000000000000000000422061362052523300201300ustar00rootroot00000000000000#include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "dynamic" #define PR_DOMAIN DBG_DYNAMIC #include "mcount-arch.h" #include "libmcount/internal.h" #include "utils/utils.h" #include "utils/symbol.h" #define PAGE_SIZE 4096 #define PAGE_ADDR(a) ((void *)((a) & ~(PAGE_SIZE - 1))) #define PAGE_LEN(a, l) (a + l - (unsigned long)PAGE_ADDR(a)) #define XRAY_SECT "xray_instr_map" #define MCOUNTLOC_SECT "__mcount_loc" #define CALL_INSN_SIZE 5 #define JMP_INSN_SIZE 6 /* target instrumentation function it needs to call */ extern void __fentry__(void); extern void __dentry__(void); extern void __xray_entry(void); extern void __xray_exit(void); struct xray_instr_map { unsigned long addr; unsigned long entry; unsigned long type; unsigned long count; }; enum mcount_x86_dynamic_type { DYNAMIC_NONE, DYNAMIC_PG, DYNAMIC_FENTRY, DYNAMIC_FENTRY_NOP, DYNAMIC_XRAY, }; static const char *adi_type_names[] = { "none", "pg", "fentry", "fentry-nop", "xray", }; struct arch_dynamic_info { enum mcount_x86_dynamic_type type; struct xray_instr_map *xrmap; unsigned long *mcount_loc; unsigned xrmap_count; unsigned nr_mcount_loc; }; int mcount_setup_trampoline(struct mcount_dynamic_info *mdi) { unsigned char trampoline[] = { 0xff, 0x25, 0x02, 0x00, 0x00, 0x00, 0xcc, 0xcc }; unsigned long fentry_addr = (unsigned long)__fentry__; unsigned long xray_entry_addr = (unsigned long)__xray_entry; unsigned long xray_exit_addr = (unsigned long)__xray_exit; struct arch_dynamic_info *adi = mdi->arch; size_t trampoline_size = 16; void *trampoline_check; if (adi->type == DYNAMIC_XRAY) trampoline_size *= 2; /* find unused 16-byte at the end of the code segment */ mdi->trampoline = ALIGN(mdi->text_addr + mdi->text_size, PAGE_SIZE); mdi->trampoline -= trampoline_size; if (unlikely(mdi->trampoline < mdi->text_addr + mdi->text_size)) { mdi->trampoline += trampoline_size; mdi->text_size += PAGE_SIZE; pr_dbg2("adding a page for fentry trampoline at %#lx\n", mdi->trampoline); trampoline_check = mmap((void *)mdi->trampoline, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (trampoline_check == MAP_FAILED) pr_err("failed to mmap trampoline for setup"); } if (mprotect(PAGE_ADDR(mdi->text_addr), PAGE_LEN(mdi->text_addr, mdi->text_size), PROT_READ | PROT_WRITE | PROT_EXEC)) { pr_dbg("cannot setup trampoline due to protection: %m\n"); return -1; } if (adi->type == DYNAMIC_XRAY) { /* jmpq *0x2(%rip) # */ memcpy((void *)mdi->trampoline, trampoline, sizeof(trampoline)); memcpy((void *)mdi->trampoline + sizeof(trampoline), &xray_entry_addr, sizeof(xray_entry_addr)); /* jmpq *0x2(%rip) # */ memcpy((void *)mdi->trampoline + 16, trampoline, sizeof(trampoline)); memcpy((void *)mdi->trampoline + 16 + sizeof(trampoline), &xray_exit_addr, sizeof(xray_exit_addr)); } else if (adi->type == DYNAMIC_FENTRY_NOP) { /* jmpq *0x2(%rip) # */ memcpy((void *)mdi->trampoline, trampoline, sizeof(trampoline)); memcpy((void *)mdi->trampoline + sizeof(trampoline), &fentry_addr, sizeof(fentry_addr)); } else if (adi->type == DYNAMIC_NONE) { #ifdef HAVE_LIBCAPSTONE unsigned long dentry_addr = (unsigned long)__dentry__; /* jmpq *0x2(%rip) # */ memcpy((void *)mdi->trampoline, trampoline, sizeof(trampoline)); memcpy((void *)mdi->trampoline + sizeof(trampoline), &dentry_addr, sizeof(dentry_addr)); #endif } return 0; } void mcount_cleanup_trampoline(struct mcount_dynamic_info *mdi) { if (mprotect(PAGE_ADDR(mdi->text_addr), PAGE_LEN(mdi->text_addr, mdi->text_size), PROT_EXEC)) pr_err("cannot restore trampoline due to protection"); } static void read_xray_map(struct arch_dynamic_info *adi, struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter, unsigned long offset) { typeof(iter->shdr) *shdr = &iter->shdr; adi->xrmap_count = shdr->sh_size / sizeof(*adi->xrmap); adi->xrmap = xmalloc(adi->xrmap_count * sizeof(*adi->xrmap)); elf_get_secdata(elf, iter); elf_read_secdata(elf, iter, 0, adi->xrmap, shdr->sh_size); /* handle position independent code */ if (elf->ehdr.e_type == ET_DYN) { struct xray_instr_map *xrmap; unsigned i; for (i = 0; i < adi->xrmap_count; i++) { xrmap = &adi->xrmap[i]; xrmap->addr += offset; xrmap->entry += offset; } } } static void read_mcount_loc(struct arch_dynamic_info *adi, struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter, unsigned long offset) { typeof(iter->shdr) *shdr = &iter->shdr; adi->nr_mcount_loc = shdr->sh_size / sizeof(long); adi->mcount_loc = xmalloc(shdr->sh_size); elf_get_secdata(elf, iter); elf_read_secdata(elf, iter, 0, adi->mcount_loc, shdr->sh_size); /* symbol has relative address, fix it to match each other */ if (elf->ehdr.e_type == ET_EXEC) { unsigned i; for (i = 0; i < adi->nr_mcount_loc; i++) { adi->mcount_loc[i] -= offset; } } } void mcount_arch_find_module(struct mcount_dynamic_info *mdi, struct symtab *symtab) { struct uftrace_elf_data elf; struct uftrace_elf_iter iter; struct arch_dynamic_info *adi; unsigned char fentry_nop_patt1[] = { 0x67, 0x0f, 0x1f, 0x04, 0x00 }; unsigned char fentry_nop_patt2[] = { 0x0f, 0x1f, 0x44, 0x00, 0x00 }; unsigned i = 0; adi = xzalloc(sizeof(*adi)); /* DYNAMIC_NONE */ if (elf_init(mdi->map->libname, &elf) < 0) goto out; elf_for_each_shdr(&elf, &iter) { char *shstr = elf_get_name(&elf, &iter, iter.shdr.sh_name); if (!strcmp(shstr, XRAY_SECT)) { adi->type = DYNAMIC_XRAY; read_xray_map(adi, &elf, &iter, mdi->base_addr); goto out; } if (!strcmp(shstr, MCOUNTLOC_SECT)) { read_mcount_loc(adi, &elf, &iter, mdi->base_addr); /* still needs to check pg or fentry */ } } /* check first few functions have fentry signature */ for (i = 0; i < symtab->nr_sym; i++) { struct sym *sym = &symtab->sym[i]; void *code_addr = (void *)sym->addr + mdi->map->start; if (sym->type != ST_LOCAL_FUNC && sym->type != ST_GLOBAL_FUNC) continue; /* dont' check special functions */ if (sym->name[0] == '_') continue; /* only support calls to __fentry__ at the beginning */ if (!memcmp(code_addr, fentry_nop_patt1, CALL_INSN_SIZE) || !memcmp(code_addr, fentry_nop_patt2, CALL_INSN_SIZE)) { adi->type = DYNAMIC_FENTRY_NOP; goto out; } } switch (check_trace_functions(mdi->map->libname)) { case TRACE_MCOUNT: adi->type = DYNAMIC_PG; break; case TRACE_FENTRY: adi->type = DYNAMIC_FENTRY; break; default: break; } out: pr_dbg("dynamic patch type: %s: %d (%s)\n", basename(mdi->map->libname), adi->type, adi_type_names[adi->type]); mdi->arch = adi; elf_finish(&elf); } static unsigned long get_target_addr(struct mcount_dynamic_info *mdi, unsigned long addr) { return mdi->trampoline - (addr + CALL_INSN_SIZE); } static int patch_fentry_func(struct mcount_dynamic_info *mdi, struct sym *sym) { unsigned char nop1[] = { 0x67, 0x0f, 0x1f, 0x04, 0x00 }; unsigned char nop2[] = { 0x0f, 0x1f, 0x44, 0x00, 0x00 }; unsigned char *insn = (void *)sym->addr + mdi->map->start; unsigned int target_addr; /* only support calls to __fentry__ at the beginning */ if (memcmp(insn, nop1, sizeof(nop1)) && /* old pattern */ memcmp(insn, nop2, sizeof(nop2))) { /* new pattern */ pr_dbg("skip non-applicable functions: %s\n", sym->name); return INSTRUMENT_FAILED; } /* get the jump offset to the trampoline */ target_addr = get_target_addr(mdi, (unsigned long)insn); if (target_addr == 0) return INSTRUMENT_SKIPPED; /* make a "call" insn with 4-byte offset */ insn[0] = 0xe8; /* hopefully we're not patching 'memcpy' itself */ memcpy(&insn[1], &target_addr, sizeof(target_addr)); pr_dbg3("update function '%s' dynamically to call __fentry__\n", sym->name); return INSTRUMENT_SUCCESS; } static int update_xray_code(struct mcount_dynamic_info *mdi, struct sym *sym, struct xray_instr_map *xrmap) { unsigned char entry_insn[] = { 0xeb, 0x09 }; unsigned char exit_insn[] = { 0xc3, 0x2e }; unsigned char pad[] = { 0x66, 0x0f, 0x1f, 0x84, 0x00, 0x00, 0x02, 0x00, 0x00 }; unsigned char nop6[] = { 0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00 }; unsigned char nop4[] = { 0x0f, 0x1f, 0x40, 0x00 }; unsigned int target_addr; unsigned char *func = (void *)xrmap->addr; union { unsigned long word; char bytes[8]; } patch; if (memcmp(func + 2, pad, sizeof(pad))) return INSTRUMENT_FAILED; if (xrmap->type == 0) { /* ENTRY */ if (memcmp(func, entry_insn, sizeof(entry_insn))) return INSTRUMENT_FAILED; target_addr = mdi->trampoline - (xrmap->addr + 5); memcpy(func + 5, nop6, sizeof(nop6)); /* need to write patch_word atomically */ patch.bytes[0] = 0xe8; /* "call" insn */ memcpy(&patch.bytes[1], &target_addr, sizeof(target_addr)); memcpy(&patch.bytes[5], nop6, 3); memcpy(func, patch.bytes, sizeof(patch)); } else { /* EXIT */ if (memcmp(func, exit_insn, sizeof(exit_insn))) return INSTRUMENT_FAILED; target_addr = mdi->trampoline + 16 - (xrmap->addr + 5); memcpy(func + 5, nop4, sizeof(nop4)); /* need to write patch_word atomically */ patch.bytes[0] = 0xe9; /* "jmp" insn */ memcpy(&patch.bytes[1], &target_addr, sizeof(target_addr)); memcpy(&patch.bytes[5], nop4, 3); memcpy(func, patch.bytes, sizeof(patch)); } pr_dbg3("update function '%s' dynamically to call xray functions\n", sym->name); return INSTRUMENT_SUCCESS; } static int patch_xray_func(struct mcount_dynamic_info *mdi, struct sym *sym) { unsigned i; int ret = -2; struct arch_dynamic_info *adi = mdi->arch; struct xray_instr_map *xrmap; uint64_t sym_addr = sym->addr + mdi->map->start; /* xray provides a pair of entry and exit (or more) */ for (i = 0; i < adi->xrmap_count; i++) { xrmap = &adi->xrmap[i]; if (xrmap->addr < sym_addr || xrmap->addr >= sym_addr + sym->size) continue; while ((ret = update_xray_code(mdi, sym, xrmap)) == 0) { if (i == adi->xrmap_count - 1) break; i++; if (xrmap->entry != xrmap[1].entry) break; xrmap++; } break; } return ret; } /* * we overwrite instructions over 5bytes from start of function * to call '__dentry__' that seems similar like '__fentry__'. * * while overwriting, After adding the generated instruction which * returns to the address of the original instruction end, * save it in the heap. * * for example: * * 4005f0: 31 ed xor %ebp,%ebp * 4005f2: 49 89 d1 mov %rdx,%r9 * 4005f5: 5e pop %rsi * * will changed like this : * * 4005f0 call qword ptr [rip + 0x200a0a] # 0x601000 * * and keeping original instruction : * * Original Instructions--------------- * f1cff0: xor ebp, ebp * f1cff2: mov r9, rdx * f1cff5: pop rsi * Generated Instruction to return----- * f1cff6: jmp qword ptr [rip] * f1cffc: QW 0x00000000004005f6 * * In the original case, address 0x601000 has a dynamic symbol * start address. It is also the first element in the GOT array. * while initializing the mcount library, we will replace it with * the address of the function '__dentry__'. so, the changed * instruction will be calling '__dentry__'. * * '__dentry__' has a similar function like '__fentry__'. * the other thing is that it returns to original instructions * we keeping. it makes it possible to execute the original * instructions and return to the address at the end of the original * instructions. Thus, the execution will goes on. * */ /* * Patch the instruction to the address as given for arguments. */ static void patch_code(struct mcount_dynamic_info *mdi, uintptr_t addr, uint32_t origin_code_size) { void *origin_code_addr; unsigned char call_insn[] = { 0xe8, 0x00, 0x00, 0x00, 0x00 }; uint32_t target_addr = get_target_addr(mdi, addr); /* patch address */ origin_code_addr = (void *)addr; /* build the instrumentation instruction */ memcpy(&call_insn[1], &target_addr, CALL_INSN_SIZE - 1); /* * we need 5-bytes at least to instrumentation. however, * if instructions is not fit 5-bytes, we will overwrite the * 5-bytes and fill the remaining part of the last * instruction with nop. * * [example] * In this example, we overwrite 9-bytes to use 5-bytes. * * dynamic: 0x19e98b0[01]:push rbp * dynamic: 0x19e98b1[03]:mov rbp, rsp * dynamic: 0x19e98b4[05]:mov edi, 0x4005f4 * * dynamic: 0x40054c[05]:call 0x400ff0 * dynamic: 0x400551[01]:nop * dynamic: 0x400552[01]:nop * dynamic: 0x400553[01]:nop * dynamic: 0x400554[01]:nop */ memcpy(origin_code_addr, call_insn, CALL_INSN_SIZE); memset(origin_code_addr + CALL_INSN_SIZE, 0x90, /* NOP */ origin_code_size - CALL_INSN_SIZE); /* flush icache so that cpu can execute the new insn */ __builtin___clear_cache(origin_code_addr, origin_code_addr + origin_code_size); } static int patch_normal_func(struct mcount_dynamic_info *mdi, struct sym *sym, struct mcount_disasm_engine *disasm) { uint8_t jmp_insn[14] = { 0xff, 0x25, }; uint64_t jmp_target; struct mcount_orig_insn *orig; struct mcount_disasm_info info = { .sym = sym, .addr = mdi->map->start + sym->addr, }; int state; state = disasm_check_insns(disasm, mdi, &info); if (state != INSTRUMENT_SUCCESS) return state; pr_dbg2("patch normal func: %s (patch size: %d)\n", sym->name, info.orig_size); /* * stored origin instruction block: * ---------------------- * | [origin_code_size] | * ---------------------- * | [jmpq *0x0(rip) | * ---------------------- * | [Return address] | * ---------------------- */ jmp_target = info.addr + info.orig_size; memcpy(jmp_insn + JMP_INSN_SIZE, &jmp_target, sizeof(jmp_target)); if (info.has_jump) orig = mcount_save_code(&info, jmp_insn, 0); else orig = mcount_save_code(&info, jmp_insn, sizeof(jmp_insn)); /* make sure orig->addr same as when called from __dentry__ */ orig->addr += CALL_INSN_SIZE; patch_code(mdi, info.addr, info.orig_size); return INSTRUMENT_SUCCESS; } static int unpatch_func(uint8_t *insn, char *name) { uint8_t nop5[] = { 0x0f, 0x1f, 0x44, 0x00, 0x00 }; uint8_t nop6[] = { 0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00 }; uint8_t *nop_insn; size_t nop_size; if (*insn == 0xe8) { nop_insn = nop5; nop_size = sizeof(nop5); } else if (insn[0] == 0xff && insn[1] == 0x15) { nop_insn = nop6; nop_size = sizeof(nop6); } else { return INSTRUMENT_SKIPPED; } pr_dbg3("unpatch fentry: %s\n", name); memcpy(insn, nop_insn, nop_size); __builtin___clear_cache(insn, insn + nop_size); return INSTRUMENT_SUCCESS; } static int unpatch_fentry_func(struct mcount_dynamic_info *mdi, struct sym *sym) { uint64_t sym_addr = sym->addr + mdi->map->start; return unpatch_func((void *)sym_addr, sym->name); } static int cmp_loc(const void *a, const void *b) { const struct sym *sym = a; uintptr_t loc = *(uintptr_t *)b; if (sym->addr <= loc && loc < sym->addr + sym->size) return 0; return sym->addr > loc ? 1 : -1; } static int unpatch_mcount_func(struct mcount_dynamic_info *mdi, struct sym *sym) { struct arch_dynamic_info *adi = mdi->arch; uintptr_t *loc; if (adi->nr_mcount_loc != 0) { loc = bsearch(sym, adi->mcount_loc, adi->nr_mcount_loc, sizeof(*adi->mcount_loc), cmp_loc); if (loc != NULL) { uint8_t *insn = (uint8_t*) *loc; return unpatch_func(insn + mdi->map->start, sym->name); } } return INSTRUMENT_SKIPPED; } int mcount_patch_func(struct mcount_dynamic_info *mdi, struct sym *sym, struct mcount_disasm_engine *disasm, unsigned min_size) { struct arch_dynamic_info *adi = mdi->arch; int result = INSTRUMENT_SKIPPED; if (min_size < CALL_INSN_SIZE) min_size = CALL_INSN_SIZE; if (sym->size < min_size) return result; switch (adi->type) { case DYNAMIC_XRAY: result = patch_xray_func(mdi, sym); break; case DYNAMIC_FENTRY_NOP: result = patch_fentry_func(mdi, sym); break; case DYNAMIC_NONE: result = patch_normal_func(mdi, sym, disasm); break; default: break; } return result; } int mcount_unpatch_func(struct mcount_dynamic_info *mdi, struct sym *sym, struct mcount_disasm_engine *disasm) { struct arch_dynamic_info *adi = mdi->arch; int result = INSTRUMENT_SKIPPED; switch (adi->type) { case DYNAMIC_FENTRY: result = unpatch_fentry_func(mdi, sym); break; case DYNAMIC_PG: result = unpatch_mcount_func(mdi, sym); break; default: break; } return result; } static void revert_normal_func(struct mcount_dynamic_info *mdi, struct sym *sym, struct mcount_disasm_engine *disasm) { void *addr = (void *)(uintptr_t)sym->addr + mdi->map->start; struct mcount_orig_insn *moi; moi = mcount_find_insn((uintptr_t)addr + CALL_INSN_SIZE); if (moi == NULL) return; memcpy(addr, moi->orig, moi->orig_size); __builtin___clear_cache(addr, addr + moi->orig_size); } void mcount_arch_dynamic_recover(struct mcount_dynamic_info *mdi, struct mcount_disasm_engine *disasm) { struct dynamic_bad_symbol *badsym, *tmp; list_for_each_entry_safe(badsym, tmp, &mdi->bad_syms, list) { if (!badsym->reverted) revert_normal_func(mdi, badsym->sym, disasm); list_del(&badsym->list); free(badsym); } } uftrace-0.9.4/arch/x86_64/mcount-event.c000066400000000000000000000026611362052523300176260ustar00rootroot00000000000000#include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "event" #define PR_DOMAIN DBG_EVENT #include "libmcount/internal.h" #define INVALID_OPCODE 0xce #define PAGE_SIZE 4096 #define PAGE_ADDR(a) ((void *)((a) & ~(PAGE_SIZE - 1))) static void sdt_handler(int sig, siginfo_t *info, void *arg) { ucontext_t *ctx = arg; unsigned long addr = ctx->uc_mcontext.gregs[REG_RIP]; struct mcount_event_info * mei; mei = mcount_lookup_event(addr); assert(mei != NULL); /* TODO: collect and write arguments */ mcount_save_event(mei); /* skip the invalid insn and continue */ ctx->uc_mcontext.gregs[REG_RIP] = addr + 1; } int mcount_arch_enable_event(struct mcount_event_info *mei) { static bool sdt_handler_set = false; if (!sdt_handler_set) { struct sigaction act = { .sa_flags = SA_SIGINFO, .sa_sigaction = sdt_handler, }; sigemptyset(&act.sa_mask); sigaction(SIGILL, &act, NULL); sdt_handler_set = true; } if (mprotect(PAGE_ADDR(mei->addr), PAGE_SIZE, PROT_READ | PROT_WRITE)) { pr_dbg("cannot enable event due to protection: %m\n"); return -1; } /* replace NOP to an invalid OP so that it can catch SIGILL */ memset((void *)mei->addr, INVALID_OPCODE, 1); if (mprotect(PAGE_ADDR(mei->addr), PAGE_SIZE, PROT_EXEC)) pr_err("cannot setup event due to protection"); return 0; } uftrace-0.9.4/arch/x86_64/mcount-insn.c000066400000000000000000000243571362052523300174620ustar00rootroot00000000000000#include "libmcount/internal.h" #include "mcount-arch.h" #define CALL_INSN_SIZE 5 #ifdef HAVE_LIBCAPSTONE #include #include struct disasm_check_data { uintptr_t addr; uint32_t func_size; uint32_t patch_size; uint32_t copy_size; uint32_t size; }; void mcount_disasm_init(struct mcount_disasm_engine *disasm) { if (cs_open(CS_ARCH_X86, CS_MODE_64, &disasm->engine) != CS_ERR_OK) { pr_dbg("failed to init Capstone disasm engine\n"); return; } if (cs_option(disasm->engine, CS_OPT_DETAIL, CS_OPT_ON) != CS_ERR_OK) pr_dbg("failed to set detail option\n"); } void mcount_disasm_finish(struct mcount_disasm_engine *disasm) { cs_close(&disasm->engine); } enum fail_reason { INSTRUMENT_FAIL_NODETAIL = (1U << 0), INSTRUMENT_FAIL_NOOPRND = (1U << 1), INSTRUMENT_FAIL_RELJMP = (1U << 2), INSTRUMENT_FAIL_RELCALL = (1U << 3), INSTRUMENT_FAIL_PICCODE = (1U << 4), }; enum branch_group { OP_GROUP_NOBRANCH = 0, OP_GROUP_JMP, OP_GROUP_CALL, }; void print_instrument_fail_msg(int reason) { if (reason & INSTRUMENT_FAIL_NOOPRND) { pr_dbg3("Not supported opcode without operand\n"); } if (reason & INSTRUMENT_FAIL_RELJMP) { pr_dbg3("Not supported opcode that jump to relative address\n"); } if (reason & INSTRUMENT_FAIL_RELCALL) { pr_dbg3("Not supported opcode that call to relative address\n"); } if (reason & INSTRUMENT_FAIL_PICCODE) { pr_dbg3("Not supported Position Independent Code\n"); } } static int opnd_reg(int capstone_reg) { uint8_t x86_regs[] = { X86_REG_RAX, X86_REG_RBX, X86_REG_RCX, X86_REG_RDX, X86_REG_RDI, X86_REG_RSI, X86_REG_RBP, X86_REG_RSP, X86_REG_R8, X86_REG_R9, X86_REG_R10, X86_REG_R11, X86_REG_R12, X86_REG_R13, X86_REG_R14, X86_REG_R15, }; size_t i; for (i = 0; i < sizeof(x86_regs); i++) { if (capstone_reg == x86_regs[i]) return i; } return -1; } /* * handle PIC code. * for currently, this function targeted specific type of instruction. * * this function manipulate the instruction like below, * lea rcx, qword ptr [rip + 0x8f3f85] * to this. * mov rcx, [calculated PC + 0x8f3f85] */ static int handle_pic(cs_insn *insn, uint8_t insns[], struct mcount_disasm_info *info) { cs_x86 *x86 = &insn->detail->x86; #define REX 0 #define OPND 1 #define IMM 2 /* * array for mov instruction: REX + OPND + IMM(8-byte) * ex) mov rbx, 0x555556d35690 */ uint8_t mov_insns[10]; const uint8_t mov_operands[] = { /* rax, rbx, rcx, rdx, rdi, rsi, rbp, rsp */ 0xb8, 0xbb, 0xb9, 0xba, 0xbf, 0xbe, 0xbd, 0xbc, /* r8, r9, r10, r11, r12, r13, r14, r15 */ 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, }; /* for now, support LEA instruction only */ if (strcmp(insn->mnemonic, "lea") != 0) goto out; /* according to intel manual, lea instruction takes 2 operand */ cs_x86_op *opnd1 = &x86->operands[0]; cs_x86_op *opnd2 = &x86->operands[1]; /* check PC-relative addressing mode */ if (opnd2->type != X86_OP_MEM || opnd2->mem.base != X86_REG_RIP) goto out; /* the SIB addressing is not supported yet */ if (opnd2->mem.scale > 1 || opnd2->mem.disp == 0) goto out; if (X86_REG_RAX <= opnd1->reg && opnd1->reg <= X86_REG_RSP) { mov_insns[REX] = 0x48; } else if (X86_REG_R8 <= opnd1->reg && opnd1->reg <= X86_REG_R15) { mov_insns[REX] = 0x49; } else { goto out; } /* convert LEA to MOV instruction */ mov_insns[OPND] = mov_operands[opnd_reg(opnd1->reg)]; uint64_t PC_base = insn->address + insn->size + opnd2->mem.disp; memcpy(&mov_insns[IMM], &PC_base, sizeof(PC_base)); memcpy(insns, mov_insns, sizeof(mov_insns)); info->modified = true; return sizeof(mov_insns); out: return -1; } /* * handle CALL instructions. * it's basically PUSH + JMP instructions and we already add JMP * at the end of copied instructions so reuse the JMP. * But the pushed return address should be after the JMP instruction * so it needs to change the offset in the instruction opcode. * Therefore we added JMP here and ignore JMP in patch_normal_func(). * The info->has_jump indicates this situation. * * this function manipulate the instruction like below, * CALL * to this. * PUSH +6 (return address : 6 = sizeof JMP) * JMP +8 (target address : 8 = sizeof RETURN-ADDR) * * */ static int handle_call(cs_insn *insn, uint8_t insns[], struct mcount_disasm_info *info) { cs_x86 *x86 = &insn->detail->x86; cs_x86_op *op = &x86->operands[0]; uint8_t push[6] = { 0xff, 0x35, 0x06, }; uint8_t jump[6] = { 0xff, 0x25, 0x08, }; uint64_t ret_addr; uint64_t target; if (x86->op_count != 1 || op->type != X86_OP_IMM) return -1; target = op->imm; ret_addr = insn->address + insn->size; memcpy(&insns[0], push, sizeof(push)); memcpy(&insns[6], jump, sizeof(jump)); memcpy(&insns[12], &ret_addr, sizeof(ret_addr)); memcpy(&insns[20], &target, sizeof(target)); info->modified = true; info->has_jump = true; return sizeof(push) + sizeof(jump) + 16; } static int manipulate_insns(cs_insn *insn, uint8_t insns[], int* fail_reason, struct mcount_disasm_info *info) { int res = -1; pr_dbg3("Manipulate instructions having PC-relative addressing.\n"); switch (*fail_reason) { case INSTRUMENT_FAIL_PICCODE: res = handle_pic(insn, insns, info); if (res > 0) *fail_reason = 0; break; case INSTRUMENT_FAIL_RELCALL: res = handle_call(insn, insns, info); if (res > 0) *fail_reason = 0; break; default: break; } return res; } static int copy_insn_bytes(cs_insn *insn, uint8_t insns[]) { int res = insn->size; memcpy(insns, insn->bytes, res); return res; } /* * check whether the instruction can be executed regardless of its location. * returns false when instructions are not suitable for dynamic patch. * * TODO: this function is incomplete and need more classification. */ static int check_instrumentable(struct mcount_disasm_engine *disasm, cs_insn *insn) { int i; cs_x86 *x86; cs_detail *detail; int check_branch = OP_GROUP_NOBRANCH; int status = 0; /* * 'detail' can be NULL on "data" instruction * if SKIPDATA option is turned ON */ if (insn->detail == NULL) { status = INSTRUMENT_FAIL_NODETAIL; goto out; } detail = insn->detail; for (i = 0; i < detail->groups_count; i++) { if (detail->groups[i] == CS_GRP_CALL) check_branch = OP_GROUP_CALL; else if (detail->groups[i] == CS_GRP_JUMP) check_branch = OP_GROUP_JMP; } x86 = &insn->detail->x86; if (!x86->op_count) goto out; for (i = 0; i < x86->op_count; i++) { cs_x86_op *op = &x86->operands[i]; switch (op->type) { case X86_OP_REG: continue; case X86_OP_IMM: if (check_branch == OP_GROUP_NOBRANCH) continue; if (check_branch == OP_GROUP_CALL) status |= INSTRUMENT_FAIL_RELCALL; else if (check_branch == OP_GROUP_JMP) status |= INSTRUMENT_FAIL_RELJMP; goto out; case X86_OP_MEM: if (op->mem.base == X86_REG_RIP || op->mem.index == X86_REG_RIP) { status |= INSTRUMENT_FAIL_PICCODE; goto out; } continue; default: continue; } } out: if (status > 0) print_instrument_fail_msg(status); return status; } static bool check_unsupported(struct mcount_disasm_engine *disasm, cs_insn *insn, struct mcount_dynamic_info *mdi, struct mcount_disasm_info *info) { int i; cs_x86 *x86; cs_detail *detail = insn->detail; unsigned long target; bool jump = false; if (detail == NULL) return false; detail = insn->detail; /* assume there's no call into the middle of function */ for (i = 0; i < detail->groups_count; i++) { if (detail->groups[i] == CS_GRP_JUMP) jump = true; } if (!jump) return true; x86 = &insn->detail->x86; for (i = 0; i < x86->op_count; i++) { cs_x86_op *op = &x86->operands[i]; switch((int)op->type) { case X86_OP_IMM: /* capstone seems already calculate target address */ target = op->imm; /* disallow (back) jump to the prologue */ if (info->addr < target && target < info->addr + info->orig_size) return false; /* disallow jump to middle of other function */ if (info->addr > target || target >= info->addr + info->sym->size) { /* also mark the target function as invalid */ return !mcount_add_badsym(mdi, insn->address, target); } break; case X86_OP_MEM: case X86_OP_REG: /* indirect jumps are not allowed */ return false; default: break; } } return true; } int disasm_check_insns(struct mcount_disasm_engine *disasm, struct mcount_dynamic_info *mdi, struct mcount_disasm_info *info) { int status; cs_insn *insn = NULL; uint32_t count, i, size; uint8_t endbr64[] = { 0xf3, 0x0f, 0x1e, 0xfa }; struct dynamic_bad_symbol *badsym; badsym = mcount_find_badsym(mdi, info->addr); if (badsym != NULL) { badsym->reverted = true; return INSTRUMENT_FAILED; } count = cs_disasm(disasm->engine, (void *)info->addr, info->sym->size, info->addr, 0, &insn); if (count == 0 && !memcmp((void *)info->addr, endbr64, sizeof(endbr64))) { /* old version of capstone doesn't recognize ENDBR64 insn */ unsigned long addr = info->addr + sizeof(endbr64); info->orig_size += sizeof(endbr64); info->copy_size += sizeof(endbr64); count = cs_disasm(disasm->engine, (void *)addr, info->sym->size - sizeof(endbr64), addr, 0, &insn); } for (i = 0; i < count; i++) { uint8_t insns_byte[32] = { 0, }; status = check_instrumentable(disasm, &insn[i]); if (status > 0) size = manipulate_insns(&insn[i], insns_byte, &status, info); else size = copy_insn_bytes(&insn[i], insns_byte); if (status > 0) { status = INSTRUMENT_FAILED; pr_dbg3("not supported instruction found at %s : %s\t %s\n", info->sym->name, insn[i].mnemonic, insn[i].op_str); goto out; } memcpy(info->insns + info->copy_size, insns_byte, size); info->copy_size += size; info->orig_size += insn[i].size; if (info->orig_size >= CALL_INSN_SIZE) break; } while (++i < count) { if (!check_unsupported(disasm, &insn[i], mdi, info)) { status = INSTRUMENT_FAILED; break; } } out: if (count) cs_free(insn, count); return status; } #else /* HAVE_LIBCAPSTONE */ int disasm_check_insns(struct mcount_disasm_engine *disasm, struct mcount_dynamic_info *mdi, struct mcount_disasm_info *info) { return INSTRUMENT_FAILED; } #endif /* HAVE_LIBCAPSTONE */ uftrace-0.9.4/arch/x86_64/mcount-noplt.c000066400000000000000000000065221362052523300176410ustar00rootroot00000000000000#include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "mcount" #define PR_DOMAIN DBG_MCOUNT #include "uftrace.h" #include "libmcount/mcount.h" #include "libmcount/internal.h" #include "utils/utils.h" #include "utils/symbol.h" #define TRAMP_ENT_SIZE 16 /* size of trampoilne for each entry */ #define TRAMP_PLT0_SIZE 32 /* module id + address of plthook_addr() */ #define TRAMP_PCREL_JMP 10 /* PC_relative offset for JMP */ #define TRAMP_IDX_OFFSET 1 #define TRAMP_JMP_OFFSET 6 extern void __weak plt_hooker(void); struct plthook_data * mcount_arch_hook_no_plt(struct uftrace_elf_data *elf, const char *modname, unsigned long offset) { struct plthook_data *pd; void *trampoline; size_t tramp_len; uint32_t i; const uint8_t tramp_plt0[] = { /* followed by module_id + plthook_addr */ /* PUSH module_id */ 0xff, 0x35, 0xa, 0, 0, 0, /* JMP plthook_addr */ 0xff, 0x25, 0xc, 0, 0, 0, 0xcc, 0xcc, 0xcc, 0xcc, }; const uint8_t tramp_insns[] = { /* make stack what plt_hooker expect */ /* PUSH child_idx */ 0x68, 0, 0, 0, 0, /* JMP plt0 */ 0xe9, 0, 0, 0, 0, /* should never reach here */ 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, }; void *plthook_addr = plt_hooker; void *tramp; pd = xzalloc(sizeof(*pd)); pd->module_id = (unsigned long)pd; pd->base_addr = offset; if (load_elf_dynsymtab(&pd->dsymtab, elf, offset, 0) < 0 || pd->dsymtab.nr_sym == 0) { free(pd); return NULL; } tramp_len = TRAMP_PLT0_SIZE + pd->dsymtab.nr_sym * TRAMP_ENT_SIZE; trampoline = mmap(NULL, tramp_len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); if (trampoline == MAP_FAILED) { pr_dbg("mmap failed: %m: ignore libcall hooking\n"); free(pd); return NULL; } pd->pltgot_ptr = trampoline; pd->resolved_addr = xcalloc(pd->dsymtab.nr_sym, sizeof(long)); /* add trampoline - save orig addr and replace GOT */ pr_dbg2("module: %s (id: %lx), addr = %lx, TRAMPOLINE = %p\n", pd->mod_name, pd->module_id, pd->base_addr, pd->pltgot_ptr); /* setup PLT0 */ memcpy(trampoline, tramp_plt0, sizeof(tramp_plt0)); tramp = trampoline + sizeof(tramp_plt0); memcpy(tramp, &pd->module_id, sizeof(pd->module_id)); tramp += sizeof(long); memcpy(tramp, &plthook_addr, sizeof(plthook_addr)); tramp += sizeof(long); for (i = 0; i < pd->dsymtab.nr_sym; i++) { uint32_t pcrel; Elf64_Rela *rela; struct sym *sym; unsigned k; bool skip = false; sym = &pd->dsymtab.sym[i]; for (k = 0; k < plt_skip_nr; k++) { if (!strcmp(sym->name, plt_skip_syms[k].name)) { skip = true; break; } } if (skip) continue; /* copy trampoline instructions */ memcpy(tramp, tramp_insns, TRAMP_ENT_SIZE); /* update offset (child id) */ memcpy(tramp + TRAMP_IDX_OFFSET, &i, sizeof(i)); /* update jump offset */ pcrel = trampoline - (tramp + TRAMP_PCREL_JMP); memcpy(tramp + TRAMP_JMP_OFFSET, &pcrel, sizeof(pcrel)); rela = (void*)sym->addr; /* save resolved address in GOT */ memcpy(&pd->resolved_addr[i], (void *)rela->r_offset + offset, sizeof(long)); /* update GOT to point the trampoline */ memcpy((void *)rela->r_offset + offset, &tramp, sizeof(long)); tramp += TRAMP_ENT_SIZE; } mprotect(trampoline, tramp_len, PROT_READ|PROT_EXEC); pd->mod_name = xstrdup(modname); return pd; } uftrace-0.9.4/arch/x86_64/mcount-support.c000066400000000000000000000105561362052523300202230ustar00rootroot00000000000000#include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "mcount" #define PR_DOMAIN DBG_MCOUNT #include "libmcount/internal.h" #include "utils/filter.h" #include "utils/arch.h" int mcount_get_register_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { struct mcount_regs *regs = ctx->regs; int reg_idx; switch (spec->type) { case ARG_TYPE_REG: reg_idx = spec->reg_idx; break; case ARG_TYPE_INDEX: reg_idx = spec->idx; /* for integer arguments */ break; case ARG_TYPE_FLOAT: reg_idx = spec->idx + UFT_X86_64_REG_FLOAT_BASE; break; case ARG_TYPE_STACK: default: return -1; } switch (reg_idx) { case UFT_X86_64_REG_RDI: ctx->val.i = ARG1(regs); break; case UFT_X86_64_REG_RSI: ctx->val.i = ARG2(regs); break; case UFT_X86_64_REG_RDX: ctx->val.i = ARG3(regs); break; case UFT_X86_64_REG_RCX: ctx->val.i = ARG4(regs); break; case UFT_X86_64_REG_R8: ctx->val.i = ARG5(regs); break; case UFT_X86_64_REG_R9: ctx->val.i = ARG6(regs); break; case UFT_X86_64_REG_XMM0: asm volatile ("movsd %%xmm0, %0\n" : "=m" (ctx->val.v)); break; case UFT_X86_64_REG_XMM1: asm volatile ("movsd %%xmm1, %0\n" : "=m" (ctx->val.v)); break; case UFT_X86_64_REG_XMM2: asm volatile ("movsd %%xmm2, %0\n" : "=m" (ctx->val.v)); break; case UFT_X86_64_REG_XMM3: asm volatile ("movsd %%xmm3, %0\n" : "=m" (ctx->val.v)); break; case UFT_X86_64_REG_XMM4: asm volatile ("movsd %%xmm4, %0\n" : "=m" (ctx->val.v)); break; case UFT_X86_64_REG_XMM5: asm volatile ("movsd %%xmm5, %0\n" : "=m" (ctx->val.v)); break; case UFT_X86_64_REG_XMM6: asm volatile ("movsd %%xmm6, %0\n" : "=m" (ctx->val.v)); break; case UFT_X86_64_REG_XMM7: asm volatile ("movsd %%xmm7, %0\n" : "=m" (ctx->val.v)); break; default: return -1; } return 0; } void mcount_get_stack_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { int offset; unsigned long *addr = ctx->stack_base; switch (spec->type) { case ARG_TYPE_STACK: offset = spec->stack_ofs; break; case ARG_TYPE_INDEX: offset = spec->idx - ARCH_MAX_REG_ARGS; break; case ARG_TYPE_FLOAT: offset = (spec->idx - ARCH_MAX_FLOAT_REGS) * 2 - 1; break; case ARG_TYPE_REG: default: /* should not reach here */ pr_err_ns("invalid stack access for arguments\n"); break; } if (offset < 1 || offset > 100) { pr_dbg("invalid stack offset: %d\n", offset); memset(ctx->val.v, 0, sizeof(ctx->val)); return; } addr += offset; if (check_mem_region(ctx, (unsigned long)addr)) memcpy(ctx->val.v, addr, spec->size); else { pr_dbg("stack address is not allowed: %p\n", addr); memset(ctx->val.v, 0, sizeof(ctx->val)); } } void mcount_arch_get_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { if (mcount_get_register_arg(ctx, spec) < 0) mcount_get_stack_arg(ctx, spec); } void mcount_arch_get_retval(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { /* type of return value cannot be FLOAT, so check format instead */ if (spec->fmt != ARG_FMT_FLOAT) memcpy(ctx->val.v, ctx->retval, spec->size); else if (spec->size == 10) /* for long double type */ asm volatile ("fstpt %0\n\tfldt %0" : "=m" (ctx->val.v)); else asm volatile ("movsd %%xmm0, %0\n" : "=m" (ctx->val.v)); } void mcount_save_arch_context(struct mcount_arch_context *ctx) { asm volatile ("movsd %%xmm0, %0\n" : "=m" (ctx->xmm[0])); asm volatile ("movsd %%xmm1, %0\n" : "=m" (ctx->xmm[1])); asm volatile ("movsd %%xmm2, %0\n" : "=m" (ctx->xmm[2])); asm volatile ("movsd %%xmm3, %0\n" : "=m" (ctx->xmm[3])); asm volatile ("movsd %%xmm4, %0\n" : "=m" (ctx->xmm[4])); asm volatile ("movsd %%xmm5, %0\n" : "=m" (ctx->xmm[5])); asm volatile ("movsd %%xmm6, %0\n" : "=m" (ctx->xmm[6])); asm volatile ("movsd %%xmm7, %0\n" : "=m" (ctx->xmm[7])); } void mcount_restore_arch_context(struct mcount_arch_context *ctx) { asm volatile ("movsd %0, %%xmm0\n" :: "m" (ctx->xmm[0])); asm volatile ("movsd %0, %%xmm1\n" :: "m" (ctx->xmm[1])); asm volatile ("movsd %0, %%xmm2\n" :: "m" (ctx->xmm[2])); asm volatile ("movsd %0, %%xmm3\n" :: "m" (ctx->xmm[3])); asm volatile ("movsd %0, %%xmm4\n" :: "m" (ctx->xmm[4])); asm volatile ("movsd %0, %%xmm5\n" :: "m" (ctx->xmm[5])); asm volatile ("movsd %0, %%xmm6\n" :: "m" (ctx->xmm[6])); asm volatile ("movsd %0, %%xmm7\n" :: "m" (ctx->xmm[7])); } uftrace-0.9.4/arch/x86_64/mcount.S000066400000000000000000000050021362052523300164570ustar00rootroot00000000000000/* argument passing: %rdi, %rsi, %rdx, %rcx, %r8, %r9 */ /* return value: %rax */ /* callee saved: %rbx, %rbp, %rsp, %r12-r15 */ /* stack frame (with -pg): parent addr = 8(%rbp), child addr = (%rsp) */ /* * For example: Parent(caller): main() Child(callee): hello() Dump of assembler code for function main: 0x00000000004006b1 <+0>: push %rbp 0x00000000004006b2 <+1>: mov %rsp,%rbp 0x00000000004006b5 <+4>: callq 0x400520 0x00000000004006ba <+9>: mov $0x0,%eax 0x00000000004006bf <+14>: callq 0x400686 parent addr => 0x00000000004006c4 <+19>: nop 0x00000000004006c5 <+20>: pop %rbp 0x00000000004006c6 <+21>: retq Dump of assembler code for function hello: 0x0000000000400686 <+0>: push %rbp 0x0000000000400687 <+1>: mov %rsp,%rbp 0x000000000040068a <+4>: sub $0x10,%rsp 0x000000000040068e <+8>: callq 0x400520 child addr => 0x0000000000400693 <+13>: movl $0x1,-0x4(%rbp) */ #include "utils/asm.h" GLOBAL(mcount) .cfi_startproc sub $48, %rsp .cfi_adjust_cfa_offset 48 /* save register arguments in mcount_args */ movq %rdi, 40(%rsp) movq %rsi, 32(%rsp) movq %rdx, 24(%rsp) movq %rcx, 16(%rsp) movq %r8, 8(%rsp) movq %r9, 0(%rsp) /* child addr */ movq 48(%rsp), %rsi /* parent location */ lea 8(%rbp), %rdi /* mcount_args */ movq %rsp, %rdx .cfi_def_cfa_register rdx /* align stack pointer to 16-byte */ andq $0xfffffffffffffff0, %rsp push %rdx /* save rax (implicit argument for variadic functions) */ push %rax call mcount_entry pop %rax /* restore original stack pointer */ pop %rdx movq %rdx, %rsp .cfi_def_cfa_register rsp /* restore mcount_args */ movq 0(%rsp), %r9 movq 8(%rsp), %r8 movq 16(%rsp), %rcx movq 24(%rsp), %rdx movq 32(%rsp), %rsi movq 40(%rsp), %rdi add $48, %rsp .cfi_adjust_cfa_offset -48 retq .cfi_endproc END(mcount) ENTRY(mcount_return) .cfi_startproc sub $48, %rsp .cfi_def_cfa_offset 48 movdqu %xmm0, 16(%rsp) movq %rdx, 8(%rsp) movq %rax, 0(%rsp) /* set the first argument of mcount_exit as pointer to return values */ movq %rsp, %rdi /* returns original parent address */ call mcount_exit movq %rax, 40(%rsp) movq 0(%rsp), %rax movq 8(%rsp), %rdx movdqu 16(%rsp), %xmm0 add $40, %rsp .cfi_def_cfa_offset 8 retq .cfi_endproc END(mcount_return) uftrace-0.9.4/arch/x86_64/plthook.S000066400000000000000000000032501362052523300166350ustar00rootroot00000000000000#include "utils/asm.h" .hidden plthook_resolver_addr ENTRY(plt_hooker) .cfi_startproc /* PLT code already pushed symbol and module indices */ .cfi_adjust_cfa_offset 16 sub $48, %rsp .cfi_adjust_cfa_offset 48 /* save register arguments in mcount_args */ movq %rdi, 40(%rsp) movq %rsi, 32(%rsp) movq %rdx, 24(%rsp) movq %rcx, 16(%rsp) movq %r8, 8(%rsp) movq %r9, 0(%rsp) /* module id */ movq 48(%rsp), %rdx /* child idx */ movq 56(%rsp), %rsi /* parent location */ leaq 64(%rsp), %rdi /* mcount_args */ movq %rsp, %rcx .cfi_def_cfa_register rcx /* align stack pointer to 16-byte */ andq $0xfffffffffffffff0, %rsp push %rcx /* save rax (implicit argument for variadic functions) */ push %rax call plthook_entry movq %rax, %r11 pop %rax /* restore original stack pointer */ pop %rcx movq %rcx, %rsp .cfi_def_cfa_register rsp /* restore mcount_args */ movq 0(%rsp), %r9 movq 8(%rsp), %r8 movq 16(%rsp), %rcx movq 24(%rsp), %rdx movq 32(%rsp), %rsi movq 40(%rsp), %rdi add $48, %rsp .cfi_adjust_cfa_offset -48 cmpq $0, %r11 cmovz plthook_resolver_addr(%rip), %r11 jz 1f add $16, %rsp /* resolver function needs 2 entries on stack */ .cfi_adjust_cfa_offset -16 1: jmp *%r11 .cfi_endproc END(plt_hooker) ENTRY(plthook_return) .cfi_startproc sub $48, %rsp .cfi_def_cfa_offset 48 movdqu %xmm0, 16(%rsp) movq %rdx, 8(%rsp) movq %rax, 0(%rsp) /* set the first argument of plthook_exit as pointer to return values */ movq %rsp, %rdi call plthook_exit movq %rax, 40(%rsp) movq 0(%rsp), %rax movq 8(%rsp), %rdx movdqu 16(%rsp), %xmm0 add $40, %rsp .cfi_def_cfa_offset 8 retq .cfi_endproc END(plthook_return) uftrace-0.9.4/arch/x86_64/symbol.c000066400000000000000000000065251362052523300165120ustar00rootroot00000000000000#include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "symbol" #define PR_DOMAIN DBG_SYMBOL #include "uftrace.h" #include "utils/utils.h" #include "utils/symbol.h" #include "libmcount/internal.h" #include "mcount-arch.h" #define R_OFFSET_POS 2 #define JMP_INSN_SIZE 6 #define PLTGOT_SIZE 8 int arch_load_dynsymtab_noplt(struct symtab *dsymtab, struct uftrace_elf_data *elf, unsigned long offset, unsigned long flags) { struct uftrace_elf_iter sec_iter; struct uftrace_elf_iter rel_iter; struct uftrace_elf_iter sym_iter; unsigned grow = SYMTAB_GROW; unsigned long reloc_start = 0; size_t reloc_entsize = 0; memset(dsymtab, 0, sizeof(*dsymtab)); /* assumes there's only one RELA section (rela.dyn) for no-plt binary */ elf_for_each_shdr(elf, &sec_iter) { if (sec_iter.shdr.sh_type == SHT_RELA) { memcpy(&rel_iter, &sec_iter, sizeof(sec_iter)); pr_dbg2("found RELA section: %s\n", elf_get_name(elf, &sec_iter, sec_iter.shdr.sh_name)); reloc_start = rel_iter.shdr.sh_addr + offset; reloc_entsize = rel_iter.shdr.sh_entsize; } else if (sec_iter.shdr.sh_type == SHT_DYNSYM) { memcpy(&sym_iter, &sec_iter, sizeof(sec_iter)); elf_get_strtab(elf, &sym_iter, sec_iter.shdr.sh_link); elf_get_secdata(elf, &sym_iter); } } if (reloc_start == 0) return 0; elf_for_each_rela(elf, &rel_iter) { struct sym *sym; int symidx; char *name; symidx = elf_rel_symbol(&rel_iter.rela); if (symidx == 0) continue; if (elf_rel_type(&rel_iter.rela) != R_X86_64_GLOB_DAT) continue; elf_get_symbol(elf, &sym_iter, symidx); if (elf_symbol_type(&sym_iter.sym) != STT_FUNC && elf_symbol_type(&sym_iter.sym) != STT_GNU_IFUNC) continue; if (sym_iter.sym.st_shndx != STN_UNDEF) continue; if (dsymtab->nr_sym >= dsymtab->nr_alloc) { if (dsymtab->nr_alloc >= grow * 4) grow *= 2; dsymtab->nr_alloc += grow; dsymtab->sym = xrealloc(dsymtab->sym, dsymtab->nr_alloc * sizeof(*sym)); } sym = &dsymtab->sym[dsymtab->nr_sym++]; /* use reloc address as symbol address as it's in the map */ sym->addr = reloc_start + rel_iter.i * reloc_entsize; sym->size = reloc_entsize; sym->type = ST_PLT_FUNC; name = elf_get_name(elf, &sym_iter, sym_iter.sym.st_name); if (flags & SYMTAB_FL_DEMANGLE) sym->name = demangle(name); else sym->name = xstrdup(name); pr_dbg3("[%zd] %c %lx + %-5u %s\n", dsymtab->nr_sym, sym->type, sym->addr, sym->size, sym->name); } return dsymtab->nr_sym; } void mcount_arch_plthook_setup(struct plthook_data *pd, struct uftrace_elf_data *elf) { struct plthook_arch_context *ctx; struct uftrace_elf_iter iter; char *secname; ctx = xzalloc(sizeof(*ctx)); elf_for_each_shdr(elf, &iter) { secname = elf_get_name(elf, &iter, iter.shdr.sh_name); if (strcmp(secname, ".plt.sec") == 0) { ctx->has_plt_sec = true; break; } } pd->arch = ctx; } unsigned long mcount_arch_plthook_addr(struct plthook_data *pd, int idx) { struct plthook_arch_context *ctx = pd->arch; struct sym *sym; if (ctx->has_plt_sec) { unsigned long sym_addr; /* symbol has .plt.sec address, so return .plt address */ sym_addr = pd->plt_addr + (idx + 1) * 16; return sym_addr; } sym = &pd->dsymtab.sym[idx]; return sym->addr + ARCH_PLTHOOK_ADDR_OFFSET; } uftrace-0.9.4/arch/x86_64/xray.S000066400000000000000000000030411362052523300161360ustar00rootroot00000000000000/* argument passing: %rdi, %rsi, %rdx, %rcx, %r8, %r9 */ /* return value: %rax */ /* callee saved: %rbx, %rbp, %rsp, %r12-r15 */ /* stack frame: child addr = 0(%rsp), return addr = 8(%rbp) */ #include "utils/asm.h" GLOBAL(__xray_entry) .cfi_startproc sub $48, %rsp .cfi_adjust_cfa_offset 48 movq %rdi, 40(%rsp) movq %rsi, 32(%rsp) movq %rdx, 24(%rsp) movq %rcx, 16(%rsp) movq %r8, 8(%rsp) movq %r9, 0(%rsp) /* child ip */ movq 48(%rsp), %rsi /* parent location */ lea 56(%rsp), %rdi /* mcount_args */ movq %rsp, %rdx .cfi_def_cfa_register rdx /* align stack pointer to 16-byte */ andq $0xfffffffffffffff0, %rsp push %rdx /* save rax (implicit argument for variadic functions) */ push %rax call xray_entry pop %rax /* restore original stack pointer */ pop %rdx movq %rdx, %rsp .cfi_def_cfa_register rsp /* restore mcount_args */ movq 0(%rsp), %r9 movq 8(%rsp), %r8 movq 16(%rsp), %rcx movq 24(%rsp), %rdx movq 32(%rsp), %rsi movq 40(%rsp), %rdi add $48, %rsp .cfi_adjust_cfa_offset -48 retq .cfi_endproc END(__xray_entry) ENTRY(__xray_exit) .cfi_startproc sub $40, %rsp /* return address already consume 8 byte */ .cfi_def_cfa_offset 40 /* save original return values */ movdqu %xmm0, 16(%rsp) movq %rdx, 8(%rsp) movq %rax, 0(%rsp) /* set the first argument of mcount_exit as pointer to return values */ movq %rsp, %rdi call xray_exit /* restore return values */ movq 0(%rsp), %rax movq 8(%rsp), %rdx movdqu 16(%rsp), %xmm0 add $40, %rsp retq .cfi_endproc END(__xray_exit) uftrace-0.9.4/check-deps/000077500000000000000000000000001362052523300151645ustar00rootroot00000000000000uftrace-0.9.4/check-deps/Makefile000066400000000000000000000033561362052523300166330ustar00rootroot00000000000000CHECK_LIST := clock_without_librt CHECK_LIST += cc_has_mfentry CHECK_LIST += cxa_demangle CHECK_LIST += have_libelf CHECK_LIST += cc_has_mno_sse2 CHECK_LIST += have_libpython2.7 CHECK_LIST += have_libpython3 CHECK_LIST += have_libluajit CHECK_LIST += perf_clockid CHECK_LIST += perf_context_switch CHECK_LIST += arm_has_hardfp CHECK_LIST += have_libncurses CHECK_LIST += have_libdw CHECK_LIST += have_libcapstone # # This is needed for checking build dependency # CHECK_CFLAGS = $(CFLAGS) $(CFLAGS_$@) -Werror CHECK_LDFLAGS = $(LDFLAGS) $(LDFLAGS_$@) CFLAGS_cc_has_mfentry = -mfentry LDFLAGS_cxa_demangle = -lstdc++ LDFLAGS_have_libelf = -lelf CFLAGS_cc_has_mno_sse2 = -mno-sse2 LDFLAGS_have_libpython2.7 = -lpython2.7 CFLAGS_have_libpython3 = $(shell python3-config --cflags 2> /dev/null) LDFLAGS_have_libpython3 = $(shell python3-config --libs 2> /dev/null) CFLAGS_have_libluajit = $(shell pkg-config --cflags luajit 2> /dev/null) LDFLAGS_have_libluajit = $(shell pkg-config --libs luajit 2> /dev/null) CFLAGS_have_libncurses = $(shell pkg-config --cflags ncursesw 2> /dev/null) LDFLAGS_have_libncurses = $(shell pkg-config --libs ncursesw 2> /dev/null) CFLAGS_have_libdw = $(shell pkg-config --cflags libdw 2> /dev/null) LDFLAGS_have_libdw = $(shell pkg-config --libs libdw 2> /dev/null || echo "-ldw") CFLAGS_have_libcapstone = $(shell pkg-config --cflags capstone 2> /dev/null) LDFLAGS_have_libcapstone = $(shell pkg-config --libs capstone 2> /dev/null) check-build: check-tstamp $(CHECK_LIST) $(CHECK_LIST): %: __%.c @$(CC) $(CHECK_CFLAGS) -o $@ $< $(CHECK_LDFLAGS) > /dev/null 2>&1 check-tstamp: PHONY @touch $@ @if [ `id -u` -eq 0 ]; then chmod 666 $@; fi check-clean: @$(RM) $(shell find -type f -executable) check-tstamp *.o .PHONY: PHONY; uftrace-0.9.4/check-deps/Makefile.check000066400000000000000000000042621362052523300177040ustar00rootroot00000000000000ifeq ($(wildcard $(srcdir)/check-deps/clock_without_librt),) LDFLAGS_libmcount.so += -lrt endif ifneq ($(wildcard $(srcdir)/check-deps/cc_has_mfentry),) export HAVE_CC_MFENTRY = 1 endif ifneq ($(wildcard $(srcdir)/check-deps/cxa_demangle),) COMMON_CFLAGS += -DHAVE_CXA_DEMANGLE COMMON_LDFLAGS += -lstdc++ endif ifneq ($(wildcard $(srcdir)/check-deps/cc_has_mno_sse2),) LIB_CFLAGS += -mno-sse2 endif ifneq ($(wildcard $(srcdir)/check-deps/have_libpython3),) COMMON_CFLAGS += -DHAVE_LIBPYTHON3 COMMON_CFLAGS += -I $(shell python3 -c 'import sysconfig; print(sysconfig.get_config_vars()["INCLUDEPY"])') COMMON_CFLAGS += -DLIBPYTHON3_VERSION=$(shell python3 -c 'import sysconfig; print(sysconfig.get_config_vars()["LDVERSION"])') else ifneq ($(wildcard $(srcdir)/check-deps/have_libpython2.7),) COMMON_CFLAGS += -DHAVE_LIBPYTHON2 COMMON_CFLAGS += -I/usr/include/python2.7 endif ifneq ($(wildcard $(srcdir)/check-deps/have_libluajit),) COMMON_CFLAGS += -DHAVE_LIBLUAJIT COMMON_CFLAGS += $(shell pkg-config --cflags luajit) endif ifneq ($(wildcard $(srcdir)/check-deps/perf_clockid),) COMMON_CFLAGS += -DHAVE_PERF_CLOCKID endif ifneq ($(wildcard $(srcdir)/check-deps/perf_context_switch),) COMMON_CFLAGS += -DHAVE_PERF_CTXSW endif ifneq ($(wildcard $(srcdir)/check-deps/arm_has_hardfp),) COMMON_CFLAGS += -DHAVE_ARM_HARDFP endif ifneq ($(wildcard $(srcdir)/check-deps/have_libncurses),) COMMON_CFLAGS += -DHAVE_LIBNCURSES $(shell pkg-config --cflags ncursesw) UFTRACE_LDFLAGS += $(shell pkg-config --libs ncursesw) TEST_LDFLAGS += $(shell pkg-config --libs ncursesw) endif ifneq ($(wildcard $(srcdir)/check-deps/have_libelf),) COMMON_CFLAGS += -DHAVE_LIBELF COMMON_LDFLAGS += -lelf endif ifneq ($(wildcard $(srcdir)/check-deps/have_libdw),) COMMON_CFLAGS += -DHAVE_LIBDW COMMON_CFLAGS += $(shell pkg-config --cflags libdw 2> /dev/null) COMMON_LDFLAGS += $(shell pkg-config --libs libdw 2> /dev/null || echo "-ldw") endif ifneq ($(wildcard $(srcdir)/check-deps/have_libcapstone),) COMMON_CFLAGS += -DHAVE_LIBCAPSTONE COMMON_CFLAGS += $(shell pkg-config --cflags capstone 2> /dev/null) COMMON_LDFLAGS += $(shell pkg-config --libs capstone 2> /dev/null) endif uftrace-0.9.4/check-deps/__arm_has_hardfp.c000066400000000000000000000001271362052523300205640ustar00rootroot00000000000000int main(void) { float f; asm volatile ("vstr %%s0, %0\n" : "=m" (f)); return 0; } uftrace-0.9.4/check-deps/__cc_has_mfentry.c000066400000000000000000000000361362052523300206110ustar00rootroot00000000000000int main(void) { return 0; } uftrace-0.9.4/check-deps/__cc_has_mno_sse2.c000066400000000000000000000000361362052523300206520ustar00rootroot00000000000000int main(void) { return 0; } uftrace-0.9.4/check-deps/__clock_without_librt.c000066400000000000000000000001211362052523300216720ustar00rootroot00000000000000#include int main(void) { return clock_gettime(CLOCK_MONOTONIC, 0); } uftrace-0.9.4/check-deps/__cxa_demangle.c000066400000000000000000000002361362052523300202360ustar00rootroot00000000000000extern char *__cxa_demangle(const char *name, char *output, long *len, int *status); int main(void) { __cxa_demangle("_Z1fv", 0, 0, 0); return 0; } uftrace-0.9.4/check-deps/__have_libcapstone.c000066400000000000000000000003141362052523300211320ustar00rootroot00000000000000#include #include #include #include #include int main() { cs_insn insn; printf("size: %zu\n", sizeof(insn)); return 0; } uftrace-0.9.4/check-deps/__have_libdw.c000066400000000000000000000001751362052523300177350ustar00rootroot00000000000000#include int main(void) { Dwarf *dw; dw = dwarf_begin(0, DWARF_C_READ); dwarf_end(dw); return 0; } uftrace-0.9.4/check-deps/__have_libelf.c000066400000000000000000000001611362052523300200640ustar00rootroot00000000000000#include #include int main(void) { GElf_Ehdr ehdr; elf_version(EV_CURRENT); return 0; } uftrace-0.9.4/check-deps/__have_libluajit.c000066400000000000000000000002151362052523300206060ustar00rootroot00000000000000#include #include #include int main(void) { lua_State *L = luaL_newstate(); luaL_openlibs(L); return 0; } uftrace-0.9.4/check-deps/__have_libncurses.c000066400000000000000000000001131362052523300207750ustar00rootroot00000000000000#include int main(void) { initscr(); endwin(); return 0; } uftrace-0.9.4/check-deps/__have_libpython2.7.c000066400000000000000000000001331362052523300210650ustar00rootroot00000000000000#include int main(void) { Py_Initialize(); return 0; } uftrace-0.9.4/check-deps/__have_libpython3.c000066400000000000000000000002531362052523300207240ustar00rootroot00000000000000#define PY_SSIZE_T_CLEAN #include #if PY_MAJOR_VERSION != 3 #error python version is not 3 #endif int main(void) { Py_Initialize(); return 0; } uftrace-0.9.4/check-deps/__perf_clockid.c000066400000000000000000000004341362052523300202530ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include #include int main(void) { struct perf_event_attr attr = { .use_clockid = 1, .clockid = CLOCK_MONOTONIC, }; syscall(SYS_perf_event_open, &attr, 0, -1, -1, 0); return 0; } uftrace-0.9.4/check-deps/__perf_context_switch.c000066400000000000000000000005161362052523300217110ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include int main(void) { struct perf_event_attr attr = { .context_switch = 1, }; syscall(SYS_perf_event_open, &attr, 0, -1, -1, 0); if (PERF_RECORD_SWITCH != 14 || PERF_RECORD_MISC_SWITCH_OUT != (1 << 13)) return -1; return 0; } uftrace-0.9.4/cmds/000077500000000000000000000000001362052523300141045ustar00rootroot00000000000000uftrace-0.9.4/cmds/dump.c000066400000000000000000001250211362052523300152160ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "uftrace.h" #include "version.h" #include "utils/list.h" #include "utils/utils.h" #include "utils/fstack.h" #include "utils/filter.h" #include "utils/kernel.h" #include "utils/graph.h" #include "libtraceevent/kbuffer.h" #include "libtraceevent/event-parse.h" struct uftrace_dump_ops { /* this is called at the beginning */ void (*header)(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct opts *opts); /* this is called when a task starts */ void (*task_start)(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task); /* this is called when a record's time is before the previous */ void (*inverted_time)(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task); /* this is called for each user-level function entry/exit */ void (*task_rstack)(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task, char *name); /* this is called for each user-level event */ void (*task_event)(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task); /* this is called when kernel data starts */ void (*kernel_start)(struct uftrace_dump_ops *ops, struct uftrace_kernel_reader *kernel); /* this is called when a cpu data start */ void (*cpu_start)(struct uftrace_dump_ops *ops, struct uftrace_kernel_reader *kernel, int cpu); /* this is called for each kernel-level function entry/exit */ void (*kernel_func)(struct uftrace_dump_ops *ops, struct uftrace_kernel_reader *kernel, int cpu, struct uftrace_record *frs, char *name); /* this is called for each kernel event (tracepoint) */ void (*kernel_event)(struct uftrace_dump_ops *ops, struct uftrace_kernel_reader *kernel, int cpu, struct uftrace_record *frs); /* this is called when there's a lost record (usually in kernel) */ void (*lost)(struct uftrace_dump_ops *ops, uint64_t time, int tid, int losts); /* this is called when a perf data (for each cpu) starts */ void (*perf_start)(struct uftrace_dump_ops *ops, struct uftrace_perf_reader *perf, int cpu); /* this is called for each perf event (except for schedule) */ void (*perf_event)(struct uftrace_dump_ops *ops, struct uftrace_perf_reader *perf, struct uftrace_record *frs); /* this is called at the end */ void (*footer)(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct opts *opts); }; struct uftrace_raw_dump { struct uftrace_dump_ops ops; uint64_t file_offset; uint64_t kbuf_offset; }; struct uftrace_chrome_dump { struct uftrace_dump_ops ops; unsigned lost_event_cnt; bool last_comma; }; struct uftrace_flame_dump { struct uftrace_dump_ops ops; struct rb_root tasks; struct fg_node *node; uint64_t sample_time; }; struct uftrace_graphviz_dump { struct uftrace_dump_ops ops; }; static const char * rstack_type(struct uftrace_record *frs) { return frs->type == UFTRACE_EXIT ? "exit " : frs->type == UFTRACE_ENTRY ? "entry" : frs->type == UFTRACE_EVENT ? "event" : "lost "; } static void pr_time(uint64_t timestamp) { unsigned sec = timestamp / NSEC_PER_SEC; unsigned nsec = timestamp % NSEC_PER_SEC; pr_out("%u.%09u ", sec, nsec); } static int pr_task(struct opts *opts) { FILE *fp; char buf[PATH_MAX]; struct uftrace_msg msg; struct uftrace_msg_task tmsg; struct uftrace_msg_sess smsg; char *exename = NULL; snprintf(buf, sizeof(buf), "%s/task", opts->dirname); fp = fopen(buf, "r"); if (fp == NULL) return -1; while (fread(&msg, sizeof(msg), 1, fp) == 1) { if (msg.magic != UFTRACE_MSG_MAGIC) { pr_red("invalid message magic: %hx\n", msg.magic); goto out; } switch (msg.type) { case UFTRACE_MSG_TASK_START: if (fread(&tmsg, sizeof(tmsg), 1, fp) != 1) { pr_red("cannot read task message: %m\n"); goto out; } pr_time(tmsg.time); pr_out("task tid %d (pid %d)\n", tmsg.tid, tmsg.pid); break; case UFTRACE_MSG_FORK_END: if (fread(&tmsg, sizeof(tmsg), 1, fp) != 1) { pr_red("cannot read task message: %m\n"); goto out; } pr_time(tmsg.time); pr_out("fork pid %d (ppid %d)\n", tmsg.tid, tmsg.pid); break; case UFTRACE_MSG_SESSION: if (fread(&smsg, sizeof(smsg), 1, fp) != 1) { pr_red("cannot read session message: %m\n"); goto out; } free(exename); exename = xmalloc(ALIGN(smsg.namelen, 8)); if (fread(exename, ALIGN(smsg.namelen, 8), 1,fp) != 1 ) { pr_red("cannot read executable name: %m\n"); goto out; } pr_time(smsg.task.time); pr_out("session of task %d: %.*s (%s)\n", smsg.task.tid, SESSION_ID_LEN, smsg.sid, exename); break; default: pr_out("unknown message type: %u\n", msg.type); break; } } out: free(exename); fclose(fp); return 0; } static int pr_task_txt(struct opts *opts) { FILE *fp; char buf[PATH_MAX]; char *ptr, *end; char *timestamp; int pid, tid; char sid[20]; snprintf(buf, sizeof(buf), "%s/task.txt", opts->dirname); fp = fopen(buf, "r"); if (fp == NULL) return -1; while (fgets(buf, sizeof(buf), fp)) { if (!strncmp(buf, "TASK", 4)) { ptr = strstr(buf, "timestamp="); if (ptr == NULL) { pr_red("invalid task timestamp\n"); goto out; } timestamp = ptr + 10; end = strchr(ptr, ' '); if (end == NULL) { pr_red("invalid task timestamp\n"); goto out; } *end++ = '\0'; sscanf(end, "tid=%d pid=%d", &tid, &pid); pr_out("%s task tid %d (pid %d)\n", timestamp, tid, pid); } else if (!strncmp(buf, "FORK", 4)) { ptr = strstr(buf, "timestamp="); if (ptr == NULL) { pr_red("invalid task timestamp\n"); goto out; } timestamp = ptr + 10; end = strchr(ptr, ' '); if (end == NULL) { pr_red("invalid task timestamp\n"); goto out; } *end++ = '\0'; sscanf(end, "pid=%d ppid=%d", &tid, &pid); pr_out("%s fork pid %d (ppid %d)\n", timestamp, tid, pid); } else if (!strncmp(buf, "SESS", 4)) { char *exename; ptr = strstr(buf, "timestamp="); if (ptr == NULL) { pr_red("invalid session timestamp\n"); goto out; } timestamp = ptr + 10; end = strchr(ptr, ' '); if (end == NULL) { pr_red("invalid session timestamp\n"); goto out; } *end++ = '\0'; if (sscanf(end, "pid=%d sid=%s", &tid, sid) != 2) sscanf(end, "tid=%d sid=%s", &tid, sid); ptr = strstr(end, "exename="); if (ptr == NULL) { pr_red("invalid session exename\n"); goto out; } exename = ptr + 8 + 1; // skip double-quote end = strrchr(ptr, '\"'); if (end == NULL) { pr_red("invalid session exename\n"); goto out; } *end++ = '\0'; pr_out("%s session of task %d: %.*s (%s)\n", timestamp, tid, 16, sid, exename); } } out: fclose(fp); return 0; } static void pr_hex(uint64_t *offset, void *data, size_t len) { size_t i; unsigned char *h = data; uint64_t ofs = *offset; if (!debug) return; while (len >= 16) { pr_green(" <%016"PRIx64">:", ofs); pr_green(" %02x %02x %02x %02x %02x %02x %02x %02x " " %02x %02x %02x %02x %02x %02x %02x %02x\n", h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7], h[8], h[9], h[10], h[11], h[12], h[13], h[14], h[15]); ofs += 16; len -= 16; h += 16; } if (len) { pr_green(" <%016"PRIx64">:", ofs); if (len > 8) { pr_green(" %02x %02x %02x %02x %02x %02x %02x %02x ", h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]); ofs += 8; len -= 8; h += 8; } for (i = 0; i < len; i++) pr_green(" %02x", *h++); pr_green("\n"); ofs += len; } *offset = ofs; } static void pr_args(struct fstack_arguments *args) { struct uftrace_arg_spec *spec; struct uftrace_task_reader *task; struct uftrace_session_link *sessions; void *ptr = args->data; size_t size; int i = 0; task = container_of(args, typeof(*task), args); sessions = &task->h->sessions; list_for_each_entry(spec, args->args, list) { /* skip return value info */ if (spec->idx == RETVAL_IDX) continue; if (spec->fmt == ARG_FMT_STR || spec->fmt == ARG_FMT_STD_STRING) { char *buf; const int null_str = -1; size = *(unsigned short *)ptr; buf = xmalloc(size + 1); strncpy(buf, ptr + 2, size); buf[size] = '\0'; if (!memcmp(buf, &null_str, 4)) strcpy(buf, "NULL"); if (spec->fmt == ARG_FMT_STD_STRING) pr_out(" args[%d] std::string: %s\n", i , buf); else pr_out(" args[%d] str: %s\n", i , buf); free(buf); size += 2; } else if (spec->fmt == ARG_FMT_PTR) { struct sym *sym; unsigned long val = 0; memcpy(&val, ptr, spec->size); size = spec->size; sym = task_find_sym_addr(sessions, task, task->rstack->time, (uint64_t)val); if (sym) pr_out(" args[%d] p: %lx (&%s)\n", i, val, sym->name); else if (val) pr_out(" args[%d] p: %p\n", i, (void *)val); else pr_out(" args[%d] p: 0\n", i); } else if (spec->fmt == ARG_FMT_ENUM) { long long val = 0; struct uftrace_mmap *map; struct uftrace_session *s; struct debug_info *dinfo; char *enum_def; s = find_task_session(sessions, task->t, task->rstack->time); map = find_map(&s->symtabs, task->rstack->addr); if (!map || !map->mod) goto print_raw; dinfo = &map->mod->dinfo; memcpy(&val, ptr, spec->size); enum_def = get_enum_string(&dinfo->enums, spec->enum_str, val); pr_out(" args[%d] enum %s: %s (%lld)\n", i, spec->enum_str, enum_def, val); free(enum_def); size = spec->size; } else { long long val = 0; print_raw: memcpy(&val, ptr, spec->size); pr_out(" args[%d] %c%d: 0x%0*llx\n", i, ARG_SPEC_CHARS[spec->fmt], spec->size * 8, spec->size * 2, val); size = spec->size; } ptr += ALIGN(size, 4); i++; } } static void pr_retval(struct fstack_arguments *args) { struct uftrace_arg_spec *spec; struct uftrace_task_reader *task; struct uftrace_session_link *sessions; void *ptr = args->data; size_t size; task = container_of(args, typeof(*task), args); sessions = &task->h->sessions; list_for_each_entry(spec, args->args, list) { /* skip argument info */ if (spec->idx != RETVAL_IDX) continue; if (spec->fmt == ARG_FMT_STR || spec->fmt == ARG_FMT_STD_STRING) { char *buf; const int null_str = -1; size = *(unsigned short *)ptr; buf = xmalloc(size + 1); strncpy(buf, ptr + 2, size); buf[size] = '\0'; if (!memcmp(buf, &null_str, 4)) strcpy(buf, "NULL"); if (spec->fmt == ARG_FMT_STD_STRING) pr_out(" retval std::string: %s\n", buf); else pr_out(" retval str: %s\n", buf); free(buf); size += 2; } else if (spec->fmt == ARG_FMT_PTR) { struct sym *sym; unsigned long val = 0; memcpy(&val, ptr, spec->size); size = spec->size; sym = task_find_sym_addr(sessions, task, task->rstack->time, (uint64_t)val); if (sym) pr_out(" retval p: %lx (&%s)\n", val, sym->name); else pr_out(" retval p: %p\n", (void *)val); } else { long long val = 0; memcpy(&val, ptr, spec->size); pr_out(" retval %c%d: 0x%0*llx\n", ARG_SPEC_CHARS[spec->fmt], spec->size * 8, spec->size * 2, val); size = spec->size; } ptr += ALIGN(size, 4); } } static void pr_event(int eid, void *ptr, int len) { union { struct uftrace_proc_statm *statm; struct uftrace_page_fault *pgfault; } d; /* built-in events */ switch (eid) { case EVENT_ID_READ_PROC_STATM: d.statm = ptr; pr_out(" proc/statm: vmsize=%"PRIu64"K vmrss=%"PRIu64"K shared=%"PRIu64"K\n", d.statm->vmsize, d.statm->vmrss, d.statm->shared); break; case EVENT_ID_READ_PAGE_FAULT: d.pgfault = ptr; pr_out(" page-fault: major=%"PRIu64" minor=%"PRIu64"\n", d.pgfault->major, d.pgfault->minor); break; case EVENT_ID_DIFF_PROC_STATM: d.statm = ptr; pr_out(" proc/statm: vmsize=%+"PRId64"K vmrss=%+"PRId64"K shared=%+"PRId64"K\n", d.statm->vmsize, d.statm->vmrss, d.statm->shared); break; case EVENT_ID_DIFF_PAGE_FAULT: d.pgfault = ptr; pr_out(" page-fault: major=%+"PRId64" minor=%+"PRId64"\n", d.pgfault->major, d.pgfault->minor); break; default: break; } /* user events */ } static void get_feature_string(char *buf, size_t sz, uint64_t feature_mask) { int i; size_t len; bool first = true; const char *feat_str[] = { "PLTHOOK", "TASK_SESSION", "KERNEL", "ARGUMENT", "RETVAL", "SYM_REL_ADDR", "MAX_STACK", "EVENT", "PERF_EVENT", "AUTO_ARGS", "DEBUG_INFO" }; /* feat_str should match to enum uftrace_feat_bits */ for (i = 0; i < FEAT_BIT_MAX; i++) { if (!((1U << i) & feature_mask)) continue; len = snprintf(buf, sz, "%s%s", first ? "" : " | ", feat_str[i]); buf += len; sz -= len; first = false; } } static void dump_raw_header(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct opts *opts) { int i; char buf[1024]; struct uftrace_raw_dump *raw = container_of(ops, typeof(*raw), ops); pr_out("uftrace file header: magic = "); for (i = 0; i < UFTRACE_MAGIC_LEN; i++) pr_out("%02x", handle->hdr.magic[i]); pr_out("\n"); pr_out("uftrace file header: version = %u\n", handle->hdr.version); pr_out("uftrace file header: header size = %u\n", handle->hdr.header_size); pr_out("uftrace file header: endian = %u (%s)\n", handle->hdr.endian, handle->hdr.endian == 1 ? "little" : "big"); pr_out("uftrace file header: class = %u (%s bit)\n", handle->hdr.class, handle->hdr.class == 2 ? "64" : "32"); get_feature_string(buf, sizeof(buf), handle->hdr.feat_mask); pr_out("uftrace file header: features = %#"PRIx64" (%s)\n", handle->hdr.feat_mask, buf); pr_out("uftrace file header: info = %#"PRIx64"\n", handle->hdr.info_mask); pr_hex(&raw->file_offset, &handle->hdr, handle->hdr.header_size); pr_out("\n"); if (debug) { pr_out("%d tasks found\n", handle->info.nr_tid); /* try to read task.txt first */ if (pr_task_txt(opts) < 0 && pr_task(opts) < 0) pr_red("cannot open task file\n"); pr_out("\n"); } } static void dump_raw_task_start(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task) { struct uftrace_raw_dump *raw = container_of(ops, typeof(*raw), ops); pr_out("reading %d.dat\n", task->tid); raw->file_offset = 0; setup_rstack_list(&task->rstack_list); } static void dump_raw_inverted_time(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task) { pr_red("\n"); pr_red("*************************************\n"); pr_red("* inverted time - data seems broken *\n"); pr_red("*************************************\n"); pr_red("\n"); } static void dump_raw_task_rstack(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task, char *name) { struct uftrace_record *frs = task->rstack; struct uftrace_raw_dump *raw = container_of(ops, typeof(*raw), ops); if (frs->type == UFTRACE_EVENT) name = get_event_name(task->h, frs->addr); pr_time(frs->time); pr_out("%5d: [%s] %s(%"PRIx64") depth: %u\n", task->tid, rstack_type(frs), name, frs->addr, frs->depth); pr_hex(&raw->file_offset, frs, sizeof(*frs)); if (frs->type == UFTRACE_EVENT) free(name); if (frs->more) { if (frs->type == UFTRACE_ENTRY) { pr_time(frs->time); pr_out("%5d: [%s] length = %d\n", task->tid, "args ", task->args.len); pr_args(&task->args); pr_hex(&raw->file_offset, task->args.data, ALIGN(task->args.len, 8)); } else if (frs->type == UFTRACE_EXIT) { pr_time(frs->time); pr_out("%5d: [%s] length = %d\n", task->tid, "retval", task->args.len); pr_retval(&task->args); pr_hex(&raw->file_offset, task->args.data, ALIGN(task->args.len, 8)); } else abort(); } } static void dump_raw_task_event(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task) { struct uftrace_record *frs = task->rstack; struct uftrace_raw_dump *raw = container_of(ops, typeof(*raw), ops); char *name = get_event_name(task->h, frs->addr); pr_time(frs->time); pr_out("%5d: [%s] %s(%"PRIx64") depth: %u\n", task->tid, rstack_type(frs), name, frs->addr, frs->depth); pr_hex(&raw->file_offset, frs, sizeof(*frs)); if (frs->more) { pr_time(frs->time); pr_out("%5d: [%s] length = %d\n", task->tid, "data ", task->args.len); pr_event(frs->addr, task->args.data, task->args.len); pr_hex(&raw->file_offset, task->args.data, ALIGN(task->args.len, 8)); } free(name); } static void dump_raw_kernel_start(struct uftrace_dump_ops *ops, struct uftrace_kernel_reader *kernel) { pr_out("\n"); } static void dump_raw_cpu_start(struct uftrace_dump_ops *ops, struct uftrace_kernel_reader *kernel, int cpu) { struct uftrace_raw_dump *raw = container_of(ops, typeof(*raw), ops); struct kbuffer *kbuf = kernel->kbufs[cpu]; pr_out("reading kernel-cpu%d.dat\n", cpu); raw->file_offset = 0; raw->kbuf_offset = kbuffer_curr_offset(kbuf); } static void dump_raw_kernel_rstack(struct uftrace_dump_ops *ops, struct uftrace_kernel_reader *kernel, int cpu, struct uftrace_record *frs, char *name) { int tid = kernel->tids[cpu]; struct kbuffer *kbuf = kernel->kbufs[cpu]; struct uftrace_raw_dump *raw = container_of(ops, typeof(*raw), ops); /* check dummy 'time extend' record at the beginning */ if (raw->kbuf_offset == 0x18) { uint64_t offset = 0x10; uint64_t timestamp = 0; void *data = kbuffer_read_at_offset(kbuf, offset, NULL); unsigned char *tmp = data - 12; /* data still returns next record */ if ((*tmp & 0x1f) == KBUFFER_TYPE_TIME_EXTEND) { uint32_t upper, lower; int size; size = kbuffer_event_size(kbuf); memcpy(&lower, tmp, 4); memcpy(&upper, tmp + 4, 4); timestamp = ((uint64_t)upper << 27) + (lower >> 5); pr_time(frs->time - timestamp); pr_out("%5d: [%s] %s (+%"PRIu64" nsec)\n", tid, "time ", "extend", timestamp); if (debug) pr_hex(&offset, tmp, 8); else if (kbuffer_next_event(kbuf, NULL)) raw->kbuf_offset += size + 4; // 4 = event header size else raw->kbuf_offset = 0; } } pr_time(frs->time); pr_out("%5d: [%s] %s(%"PRIx64") depth: %u\n", tid, rstack_type(frs), name, frs->addr, frs->depth); if (debug) { /* this is only needed for hex dump */ void *data = kbuffer_read_at_offset(kbuf, raw->kbuf_offset, NULL); int size; size = kbuffer_event_size(kbuf); raw->file_offset = kernel->offsets[cpu] + kbuffer_curr_offset(kbuf); pr_hex(&raw->file_offset, data - 4, size + 4); if (kbuffer_next_event(kbuf, NULL)) raw->kbuf_offset += size + 4; // 4 = event header size else raw->kbuf_offset = 0; } } static void dump_raw_kernel_event(struct uftrace_dump_ops *ops, struct uftrace_kernel_reader *kernel, int cpu, struct uftrace_record *frs) { struct uftrace_raw_dump *raw = container_of(ops, typeof(*raw), ops); struct event_format *event; int tid = kernel->tids[cpu]; char *event_data; int size = 0; event = pevent_find_event(kernel->pevent, frs->addr); event_data = read_kernel_event(kernel, cpu, &size); pr_time(frs->time); pr_out("%5d: [%s] %s:%s(%ld) %.*s\n", tid, rstack_type(frs), event->system, event->name, frs->addr, size, event_data); if (debug) { /* this is only needed for hex dump */ struct kbuffer *kbuf = kernel->kbufs[cpu]; void *data = kbuffer_read_at_offset(kbuf, raw->kbuf_offset, NULL); int size; size = kbuffer_event_size(kbuf); raw->file_offset = kernel->offsets[cpu] + kbuffer_curr_offset(kbuf); pr_hex(&raw->file_offset, data - 4, size + 4); if (kbuffer_next_event(kbuf, NULL)) raw->kbuf_offset += size + 4; // 4 = event header size else raw->kbuf_offset = 0; } } static void dump_raw_kernel_lost(struct uftrace_dump_ops *ops, uint64_t time, int tid, int losts) { pr_time(time); pr_red("%5d: [%s ]: %d events\n", tid, "lost", losts); } static void dump_raw_perf_start(struct uftrace_dump_ops *ops, struct uftrace_perf_reader *perf, int cpu) { struct uftrace_raw_dump *raw = container_of(ops, typeof(*raw), ops); if (cpu == 0) pr_out("\n"); pr_out("reading perf-cpu%d.dat\n", cpu); raw->file_offset = 0; } static void dump_raw_perf_event(struct uftrace_dump_ops *ops, struct uftrace_perf_reader *perf, struct uftrace_record *frs) { struct uftrace_raw_dump *raw = container_of(ops, typeof(*raw), ops); char *evt_name = get_event_name(NULL, frs->addr); pr_time(frs->time); pr_out("%5d: [%s] %s(%"PRIu64")\n", perf->tid, rstack_type(frs), evt_name, frs->addr); if (debug) { /* XXX: this is different from file contents */ switch (frs->addr) { case EVENT_ID_PERF_SCHED_IN: case EVENT_ID_PERF_SCHED_OUT: pr_hex(&raw->file_offset, &perf->u.ctxsw, sizeof(perf->u.ctxsw)); break; case EVENT_ID_PERF_TASK: case EVENT_ID_PERF_EXIT: pr_hex(&raw->file_offset, &perf->u.task, sizeof(perf->u.task)); break; case EVENT_ID_PERF_COMM: pr_hex(&raw->file_offset, &perf->u.comm, sizeof(perf->u.comm)); break; default: break; } } free(evt_name); } /* chrome support */ static void dump_chrome_header(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct opts *opts) { struct uftrace_chrome_dump *chrome = container_of(ops, typeof(*chrome), ops); struct uftrace_info *info = &handle->info; struct uftrace_task *task; int tid; int i; if (handle->hdr.feat_mask & PERF_EVENT) update_perf_task_comm(handle); pr_out("{\"traceEvents\":[\n"); for (i = 0; i < info->nr_tid; i++) { tid = info->tids[i]; task = find_task(&handle->sessions, tid); pr_out("{\"ts\":0,\"ph\":\"M\",\"pid\":%d," "\"name\":\"process_name\"," "\"args\":{\"name\":\"[%d] %s\"}},\n", tid, tid, task->comm); pr_out("{\"ts\":0,\"ph\":\"M\",\"pid\":%d," "\"name\":\"thread_name\"," "\"args\":{\"name\":\"[%d] %s\"}},\n", tid, tid, task->comm); } chrome->last_comma = false; } static void dump_chrome_task_rstack(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task, char *name) { char ph; char spec_buf[1024]; struct uftrace_record *frs = task->rstack; enum argspec_string_bits str_mode = NEEDS_JSON; struct uftrace_chrome_dump *chrome = container_of(ops, typeof(*chrome), ops); bool is_process = task->t->pid == task->tid; int rec_type = frs->type; if (rec_type == UFTRACE_EVENT) { switch (frs->addr) { case EVENT_ID_PERF_SCHED_IN: /* * new thread starts with a sched-in event * which should be ignored */ if (task->timestamp_last == 0) return; rec_type = UFTRACE_EXIT; break; case EVENT_ID_PERF_SCHED_OUT: rec_type = UFTRACE_ENTRY; break; default: return; } } if (chrome->last_comma) pr_out(",\n"); chrome->last_comma = true; if (rec_type == UFTRACE_ENTRY) { ph = 'B'; if (is_process) { /* no need to add "tid" field */ pr_out("{\"ts\":%"PRIu64".%03d,\"ph\":\"%c\",\"pid\":%d,\"name\":\"%s\"", frs->time / 1000, (int)(frs->time % 1000), ph, task->tid, name); } else { pr_out("{\"ts\":%"PRIu64".%03d,\"ph\":\"%c\",\"pid\":%d,\"tid\":%d,\"name\":\"%s\"", frs->time / 1000, (int)(frs->time % 1000), ph, task->t->pid, task->tid, name); } if (frs->more) { str_mode |= NEEDS_PAREN | HAS_MORE; get_argspec_string(task, spec_buf, sizeof(spec_buf), str_mode); pr_out(",\"args\":{\"arguments\":\"%s\"}}", spec_buf); } else pr_out("}"); } else if (rec_type == UFTRACE_EXIT) { ph = 'E'; if (is_process) { /* no need to add "tid" field */ pr_out("{\"ts\":%"PRIu64".%03d,\"ph\":\"%c\",\"pid\":%d,\"name\":\"%s\"", frs->time / 1000, (int)(frs->time % 1000), ph, task->tid, name); } else { pr_out("{\"ts\":%"PRIu64".%03d,\"ph\":\"%c\",\"pid\":%d,\"tid\":%d,\"name\":\"%s\"", frs->time / 1000, (int)(frs->time % 1000), ph, task->t->pid, task->tid, name); } if (frs->more) { str_mode |= IS_RETVAL | HAS_MORE; get_argspec_string(task, spec_buf, sizeof(spec_buf), str_mode); pr_out(",\"args\":{\"retval\":\"%s\"}}", spec_buf); } else pr_out("}"); } else if (rec_type == UFTRACE_LOST) chrome->lost_event_cnt++; } static void dump_chrome_kernel_rstack(struct uftrace_dump_ops *ops, struct uftrace_kernel_reader *kernel, int cpu, struct uftrace_record *rec, char *name) { int tid; struct uftrace_task_reader *task; tid = kernel->tids[cpu]; task = get_task_handle(kernel->handle, tid); dump_chrome_task_rstack(ops, task, name); } static void dump_chrome_perf_event(struct uftrace_dump_ops *ops, struct uftrace_perf_reader *perf, struct uftrace_record *frs) { uint64_t evt_id = frs->addr; bool is_process = perf->u.comm.pid == perf->tid; switch (evt_id) { case EVENT_ID_PERF_COMM: if (is_process) { pr_out(",\n{\"ts\":0,\"ph\":\"M\",\"pid\":%d," "\"name\":\"process_name\"," "\"args\":{\"name\":\"%s\"}}", perf->tid, perf->u.comm.comm); pr_out(",\n{\"ts\":0,\"ph\":\"M\",\"pid\":%d," "\"name\":\"thread_name\"," "\"args\":{\"name\":\"%s\"}}", perf->tid, perf->u.comm.comm); } else { pr_out(",\n{\"ts\":0,\"ph\":\"M\",\"pid\":%d,\"tid\":%d," "\"name\":\"thread_name\"," "\"args\":{\"name\":\"[%d] %s\"}}", perf->u.comm.pid, perf->tid, perf->tid, perf->u.comm.comm); } break; default: break; }; } static void dump_chrome_footer(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct opts *opts) { char buf[PATH_MAX]; struct stat statbuf; struct uftrace_chrome_dump *chrome = container_of(ops, typeof(*chrome), ops); /* read recorded date and time */ snprintf(buf, sizeof(buf), "%s/info", opts->dirname); if (stat(buf, &statbuf) < 0) return; ctime_r(&statbuf.st_mtime, buf); buf[strlen(buf) - 1] = '\0'; pr_out("\n], \"displayTimeUnit\": \"ns\", \"metadata\": {\n"); pr_out("\"version\":\"uftrace %s\",\n", UFTRACE_VERSION); pr_out("\"recorded_time\":\"%s\",\n", buf); if (handle->hdr.info_mask & (1UL << CMDLINE)) pr_out("\"command_line\":\"%s\"\n", handle->info.cmdline); pr_out("} }\n"); /* * Chrome trace format requires to have both entry and exit records so * that it can identify the range of function call and return. * However, if there are some lost records, it cannot match the entry * and exit of some functions. It may show some of functions do not * return until the program is finished or vice versa. * * Since it's very difficult to generate fake records for lost data to * match entry and exit of some lost functions, we just inform the fact * to users as of now. */ if (chrome->lost_event_cnt) { pr_warn("Some of function trace records are lost. " "(%d times shown)\n", chrome->lost_event_cnt); pr_warn("The output json format may not show the correct view " "in chrome browser.\n"); } } /* flamegraph support */ static struct uftrace_graph flame_graph = { .root.head = LIST_HEAD_INIT(flame_graph.root.head), .special_nodes = LIST_HEAD_INIT(flame_graph.special_nodes), }; static void adjust_fg_time(struct uftrace_task_graph *tg, void *arg) { struct uftrace_dump_ops *ops = arg; struct uftrace_flame_dump *flame = container_of(ops, typeof(*flame), ops); struct uftrace_graph_node *node = tg->node; struct fstack *fstack; uint64_t curr_time; uint64_t sample_time; uint64_t accounted_time; if (flame->sample_time == 0) return; if (tg->node->parent == NULL) return; fstack = fstack_get(tg->task, tg->task->stack_count); if (fstack == NULL) return; curr_time = fstack->total_time; sample_time = flame->sample_time; /* * it needs to track the child time separately * since child time not accounted due to sample time * should be accounted to parent. * * For example, with 1us sample time: * * # DURATION TID FUNCTION * [12345] | main() { * 4.789 us [12345] | foo(); * 4.987 us [12345] | bar(); * 10.567 us [12345] | } // main * * In this case, main's total time is more than 10us * so 10 samples should be shown, but after accounting * foo and bar (4 samples each), its time would be * 10.567 - 4.789 - 4.987 = 0.791 so no samples for main. * But it actually needs to get 2 samples. * * So add the accounted child time only, not real time. */ accounted_time = (curr_time / sample_time) * sample_time; node->parent->child_time -= curr_time; node->parent->child_time += accounted_time; } static void print_flame_graph(struct uftrace_graph_node *node, struct opts *opts) { struct uftrace_graph_node *child; unsigned long sample = node->nr_calls; if (sample && opts->sample_time) sample = (node->time - node->child_time) / opts->sample_time; if (sample) { struct uftrace_graph_node *parent = node; char *names[opts->max_stack]; char *buf, *ptr; int i = 0; size_t len = 0; while (parent != NULL && parent->name != NULL) { names[i++] = parent->name; len += strlen(parent->name) + 1; parent = parent->parent; } buf = ptr = xmalloc(len + 32); while (--i >= 0) ptr += snprintf(ptr, len, "%s;", names[i]); ptr[-1] = ' '; snprintf(ptr, len, "%lu", sample); pr_out("%s\n", buf); free(buf); } list_for_each_entry(child, &node->head, list) print_flame_graph(child, opts); } static void dump_flame_header(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct opts *opts) { graph_init_callbacks(NULL, adjust_fg_time, NULL, ops); } static void dump_flame_task_rstack(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task, char *name) { struct uftrace_record *frs = task->rstack; struct uftrace_task_graph *graph; graph = graph_get_task(task, sizeof(*graph)); graph->graph = &flame_graph; flame_graph.sess = find_task_session(&task->h->sessions, task->t, frs->time); if (graph->node == NULL) graph->node = &flame_graph.root; graph_add_node(graph, frs->type, name, sizeof(struct uftrace_graph_node)); } static void dump_flame_kernel_rstack(struct uftrace_dump_ops *ops, struct uftrace_kernel_reader *kernel, int cpu, struct uftrace_record *rec, char *name) { int tid; struct uftrace_task_reader *task; struct uftrace_task_graph *graph; tid = kernel->tids[cpu]; task = get_task_handle(kernel->handle, tid); graph = graph_get_task(task, sizeof(*graph)); graph->graph = &flame_graph; flame_graph.sess = kernel->handle->sessions.first; if (graph->node == NULL) graph->node = &flame_graph.root; graph_add_node(graph, rec->type, name, sizeof(struct uftrace_graph_node)); } static void dump_flame_footer(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct opts *opts) { print_flame_graph(&flame_graph.root, opts); graph_destroy(&flame_graph); graph_remove_task(); } /* graphviz support */ static struct uftrace_graph graphviz_graph = { .root.head = LIST_HEAD_INIT(graphviz_graph.root.head), .special_nodes = LIST_HEAD_INIT(graphviz_graph.special_nodes), }; static void dump_graphviz_header(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct opts *opts) { pr_out("# version\":\"uftrace %s\",\n", UFTRACE_VERSION); if (handle->hdr.info_mask & (1UL << CMDLINE)) pr_out("# command_line \"%s\"\n", handle->info.cmdline); pr_out("digraph \""); pr_out("%s", (&handle->info)->exename); pr_out("\" { \n"); pr_out("\n\t# Attributes \n"); pr_out("\tsplines=ortho;\n"); pr_out("\tconcentrate=true;\n"); pr_out("\tnode [shape=\"rect\",fontsize=\"7\",style=\"filled\"];\n"); pr_out("\tedge [fontsize=\"7\"];\n\n"); graph_init_callbacks(NULL, NULL, NULL, ops); } static void dump_graphviz_task_rstack(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task, char *name) { struct uftrace_record *frs = task->rstack; struct uftrace_task_graph *graph; graph = graph_get_task(task, sizeof(*graph)); graph->graph = &graphviz_graph; graphviz_graph.sess = find_task_session(&task->h->sessions, task->t, frs->time); if (graph->node == NULL) graph->node = &graphviz_graph.root; graph_add_node(graph, frs->type, name, sizeof(struct uftrace_graph_node)); } static void dump_graphviz_kernel_rstack(struct uftrace_dump_ops *ops, struct uftrace_kernel_reader *kernel, int cpu, struct uftrace_record *rec, char *name) { int tid; struct uftrace_task_reader *task; struct uftrace_task_graph *graph; tid = kernel->tids[cpu]; task = get_task_handle(kernel->handle, tid); graph = graph_get_task(task, sizeof(*graph)); graph->graph = &graphviz_graph; graphviz_graph.sess = kernel->handle->sessions.first; if (graph->node == NULL) graph->node = &graphviz_graph.root; graph_add_node(graph, rec->type, name, sizeof(struct uftrace_graph_node)); } static void print_graph_to_graphviz(struct uftrace_graph_node *node, struct opts *opts) { struct uftrace_graph_node *child; unsigned long n_calls = node->nr_calls; if (n_calls) { struct uftrace_graph_node *parent = node->parent; pr_out("\t"); if (parent != NULL && parent->name != NULL) { pr_out("\"%s\" -> ", parent->name); } pr_out("\"%s\"", node->name); // Edge Attributes pr_out(" [xlabel = \"Calls : %lu\"]\n", n_calls); } list_for_each_entry(child, &node->head, list) print_graph_to_graphviz(child, opts); } static void dump_graphviz_footer(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct opts *opts) { pr_out("\t# Elements \n"); print_graph_to_graphviz(&graphviz_graph.root, opts); pr_out("}\n"); graph_destroy(&graphviz_graph); graph_remove_task(); } static void do_dump_file(struct uftrace_dump_ops *ops, struct opts *opts, struct uftrace_data *handle) { int i; uint64_t prev_time; struct uftrace_task_reader *task; struct uftrace_session_link *sessions = &handle->sessions; call_if_nonull(ops->header, ops, handle, opts); for (i = 0; i < handle->info.nr_tid; i++) { if (opts->kernel && opts->kernel_only) continue; task = &handle->tasks[i]; task->rstack = &task->ustack; prev_time = 0; call_if_nonull(ops->task_start, ops, task); while (!read_task_ustack(handle, task) && !uftrace_done) { struct uftrace_record *frs = &task->ustack; struct sym *sym; char *name; if (frs->more) { add_to_rstack_list(&task->rstack_list, frs, &task->args); } /* consume the rstack as it didn't call read_rstack() */ fstack_consume(handle, task); if (!check_time_range(&handle->time_range, frs->time)) continue; if (prev_time > frs->time) call_if_nonull(ops->inverted_time, ops, task); prev_time = frs->time; if (!fstack_check_filter(task)) continue; if (frs->type == UFTRACE_EVENT) { if (!opts->no_event) call_if_nonull(ops->task_event, ops, task); continue; } sym = task_find_sym(sessions, task, frs); /* skip it if --no-libcall is given */ if (!opts->libcall && sym && sym->type == ST_PLT_FUNC) continue; name = symbol_getname(sym, frs->addr); call_if_nonull(ops->task_rstack, ops, task, name); symbol_putname(sym, name); } } if (!has_kernel_data(handle->kernel) || uftrace_done) goto perf; call_if_nonull(ops->kernel_start, ops, handle->kernel); for (i = 0; i < handle->kernel->nr_cpus; i++) { struct uftrace_kernel_reader *kernel = handle->kernel; struct uftrace_record *frs = &kernel->rstacks[i]; struct uftrace_session *fsess = handle->sessions.first; call_if_nonull(ops->cpu_start, ops, kernel, i); while (!read_kernel_cpu_data(kernel, i) && !uftrace_done) { int tid = kernel->tids[i]; int losts = kernel->missed_events[i]; struct sym *sym = NULL; char *name; uint64_t addr; if (losts) { call_if_nonull(ops->lost, ops, frs->time, tid, losts); kernel->missed_events[i] = 0; } if (!check_time_range(&handle->time_range, frs->time)) continue; if (frs->type == UFTRACE_EVENT) { if (!opts->no_event) call_if_nonull(ops->kernel_event, ops, kernel, i, frs); continue; } addr = get_kernel_address(&fsess->symtabs, frs->addr); sym = find_symtabs(&fsess->symtabs, addr); name = symbol_getname(sym, addr); call_if_nonull(ops->kernel_func, ops, kernel, i, frs, name); symbol_putname(sym, name); } } perf: if (!has_perf_data(handle) || uftrace_done) goto footer; for (i = 0; i < handle->nr_perf; i++) { struct uftrace_perf_reader *perf = &handle->perf[i]; call_if_nonull(ops->perf_start, ops, perf, i); while (!uftrace_done) { struct uftrace_record *rec; rec = get_perf_record(handle, perf); if (rec == NULL) break; call_if_nonull(ops->perf_event, ops, perf, rec); /* for re-read perf data from file */ perf->valid = false; } } footer: call_if_nonull(ops->footer, ops, handle, opts); } static bool check_task_rstack(struct uftrace_task_reader *task, struct opts *opts) { struct uftrace_record *frs = task->rstack; if (!fstack_check_opts(task, opts)) return false; if (!fstack_check_filter(task)) return false; if (!check_time_range(&task->h->time_range, frs->time)) return false; return true; } static void dump_replay_func(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task, struct opts *opts) { struct uftrace_record *rec = task->rstack; struct uftrace_session_link *sessions = &task->h->sessions; struct sym *sym; char *name; sym = task_find_sym(sessions, task, rec); /* skip it if --no-libcall is given */ if (!opts->libcall && sym && sym->type == ST_PLT_FUNC) return; name = symbol_getname(sym, rec->addr); if (is_kernel_record(task, rec)) { struct uftrace_kernel_reader *kernel = task->h->kernel; call_if_nonull(ops->kernel_func, ops, kernel, kernel->last_read_cpu, rec, name); } else { call_if_nonull(ops->task_rstack, ops, task, name); } symbol_putname(sym, name); } static void dump_replay_event(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task) { struct uftrace_record *rec = task->rstack; /* handle schedule events as if functions */ if (rec->addr == EVENT_ID_PERF_SCHED_IN || rec->addr == EVENT_ID_PERF_SCHED_OUT) { call_if_nonull(ops->task_rstack, ops, task, "linux:schedule"); return; } if (is_user_record(task, rec)) { call_if_nonull(ops->task_event, ops, task); } else if (is_kernel_record(task, rec)) { struct uftrace_kernel_reader *kernel = task->h->kernel; call_if_nonull(ops->kernel_event, ops, kernel, kernel->last_read_cpu, rec); } else if (is_event_record(task, rec)) { struct uftrace_perf_reader perf = { .tid = task->tid, .time = rec->time, }; if (rec->addr == EVENT_ID_PERF_COMM) { strncpy(perf.u.comm.comm, task->args.data, TASK_COMM_LAST); perf.u.comm.comm[TASK_COMM_LAST] = '\0'; perf.u.comm.pid = task->t->pid; } call_if_nonull(ops->perf_event, ops, &perf, rec); } else if (is_extern_record(task, rec)) { call_if_nonull(ops->task_event, ops, task); } else { struct uftrace_perf_reader *perf; assert(task->h->last_perf_idx >= 0); perf = &task->h->perf[task->h->last_perf_idx]; call_if_nonull(ops->perf_event, ops, perf, rec); } } static void do_dump_replay(struct uftrace_dump_ops *ops, struct opts *opts, struct uftrace_data *handle) { uint64_t prev_time = 0; struct uftrace_task_reader *task; int i; ops->header(ops, handle, opts); while (!read_rstack(handle, &task) && !uftrace_done) { struct uftrace_record *frs = task->rstack; task->timestamp_last = frs->time; if (!check_task_rstack(task, opts)) continue; if (prev_time > frs->time) call_if_nonull(ops->inverted_time, ops, task); prev_time = frs->time; if (task->rstack->type == UFTRACE_EVENT) dump_replay_event(ops, task); else dump_replay_func(ops, task, opts); } /* add duration of remaining functions */ for (i = 0; i < handle->nr_tasks; i++) { uint64_t last_time; task = &handle->tasks[i]; if (task->stack_count == 0) continue; last_time = task->timestamp_last; if (handle->time_range.stop && handle->time_range.stop < last_time) last_time = handle->time_range.stop; while (--task->stack_count >= 0) { struct fstack *fstack; struct uftrace_session *fsess = handle->sessions.first; fstack = fstack_get(task, task->stack_count); if (fstack == NULL) continue; if (fstack->addr == 0) continue; if (fstack->total_time > last_time) continue; fstack->total_time = last_time - fstack->total_time; if (fstack->child_time > fstack->total_time) fstack->total_time = fstack->child_time; if (task->stack_count > 0) fstack[-1].child_time += fstack->total_time; /* make sure is_kernel_record() working correctly */ if (is_kernel_address(&fsess->symtabs, fstack->addr)) task->rstack = &task->kstack; else task->rstack = &task->ustack; task->rstack->time = last_time; task->rstack->type = UFTRACE_EXIT; task->rstack->addr = fstack->addr; task->rstack->more = 0; if (!check_task_rstack(task, opts)) continue; if (task->rstack->type == UFTRACE_EVENT) dump_replay_event(ops, task); else dump_replay_func(ops, task, opts); } } ops->footer(ops, handle, opts); } int command_dump(int argc, char *argv[], struct opts *opts) { int ret; struct uftrace_data handle; ret = open_data_file(opts, &handle); if (ret < 0) { pr_warn("cannot open record data: %s: %m\n", opts->dirname); return -1; } fstack_setup_filters(opts, &handle); if (opts->chrome_trace) { struct uftrace_chrome_dump dump = { .ops = { .header = dump_chrome_header, .task_rstack = dump_chrome_task_rstack, .kernel_func = dump_chrome_kernel_rstack, .perf_event = dump_chrome_perf_event, .footer = dump_chrome_footer, }, }; do_dump_replay(&dump.ops, opts, &handle); } else if (opts->flame_graph) { struct uftrace_flame_dump dump = { .ops = { .header = dump_flame_header, .task_rstack = dump_flame_task_rstack, .kernel_func = dump_flame_kernel_rstack, .footer = dump_flame_footer, }, .tasks = RB_ROOT, .sample_time = opts->sample_time, }; do_dump_replay(&dump.ops, opts, &handle); } else if (opts->graphviz) { struct uftrace_graphviz_dump dump = { .ops = { .header = dump_graphviz_header, .task_rstack = dump_graphviz_task_rstack, .kernel_func = dump_graphviz_kernel_rstack, .footer = dump_graphviz_footer, }, }; do_dump_replay(&dump.ops, opts, &handle); } else { struct uftrace_raw_dump dump = { .ops = { .header = dump_raw_header, .task_start = dump_raw_task_start, .inverted_time = dump_raw_inverted_time, .task_rstack = dump_raw_task_rstack, .task_event = dump_raw_task_event, .kernel_start = dump_raw_kernel_start, .cpu_start = dump_raw_cpu_start, .kernel_func = dump_raw_kernel_rstack, .kernel_event = dump_raw_kernel_event, .lost = dump_raw_kernel_lost, .perf_start = dump_raw_perf_start, .perf_event = dump_raw_perf_event, }, }; do_dump_file(&dump.ops, opts, &handle); } close_data_file(opts, &handle); return ret; } uftrace-0.9.4/cmds/graph.c000066400000000000000000000555271362052523300153670ustar00rootroot00000000000000#include #include #include #include #include #include #include "uftrace.h" #include "utils/utils.h" #include "utils/symbol.h" #include "utils/filter.h" #include "utils/fstack.h" #include "utils/field.h" #include "utils/graph.h" static LIST_HEAD(output_fields); static LIST_HEAD(output_task_fields); struct graph_backtrace { struct list_head list; int len; int hit; uint64_t time; uint64_t addr[]; }; struct session_graph { struct uftrace_graph ug; struct graph_backtrace *bt_curr; struct list_head bt_list; struct session_graph *next; char *func; }; struct task_graph { struct uftrace_task_graph utg; struct graph_backtrace *bt_curr; int enabled; }; static bool full_graph = false; static struct session_graph *graph_list = NULL; static void print_total_time(struct field_data *fd) { struct uftrace_graph_node *node = fd->arg; uint64_t d; d = node->time; print_time_unit(d); } static void print_self_time(struct field_data *fd) { struct uftrace_graph_node *node = fd->arg; uint64_t d; d = node->time - node->child_time; print_time_unit(d); } static void print_addr(struct field_data *fd) { struct uftrace_graph_node *node = fd->arg; /* uftrace records (truncated) 48-bit addresses */ int width = sizeof(long) == 4 ? 8 : 12; pr_out("%*"PRIx64, width, effective_addr(node->addr)); } static struct display_field field_total_time= { .id = GRAPH_F_TOTAL_TIME, .name = "total-time", .alias = "total", .header = "TOTAL TIME", .length = 10, .print = print_total_time, .list = LIST_HEAD_INIT(field_total_time.list), }; static struct display_field field_self_time= { .id = GRAPH_F_SELF_TIME, .name = "self-time", .alias = "self", .header = " SELF TIME", .length = 10, .print = print_self_time, .list = LIST_HEAD_INIT(field_self_time.list), }; static struct display_field field_addr = { .id = GRAPH_F_ADDR, .name = "address", .alias = "addr", #if __SIZEOF_LONG == 4 .header = " ADDR ", .length = 8, #else .header = " ADDRESS ", .length = 12, #endif .print = print_addr, .list = LIST_HEAD_INIT(field_addr.list), }; static void print_task_total_time(struct field_data *fd) { struct uftrace_task *node = fd->arg; uint64_t d; d = node->time.run; print_time_unit(d); } static void print_task_self_time(struct field_data *fd) { struct uftrace_task *node = fd->arg; uint64_t d; d = node->time.run - node->time.idle; print_time_unit(d); } static void print_task_tid(struct field_data *fd) { struct uftrace_task *task = fd->arg; pr_out("[%6d]", task->tid); } static struct display_field field_task_total_time = { .id = GRAPH_F_TASK_TOTAL_TIME, .name = "total-time", .alias = "total", .header = "TOTAL TIME", .length = 10, .print = print_task_total_time, .list = LIST_HEAD_INIT(field_task_total_time.list), }; static struct display_field field_task_self_time = { .id = GRAPH_F_TASK_SELF_TIME, .name = "self-time", .alias = "self", .header = " SELF TIME", .length = 10, .print = print_task_self_time, .list = LIST_HEAD_INIT(field_task_self_time.list), }; static struct display_field field_task_tid = { .id = GRAPH_F_TASK_TID, .name = "tid", .header = " TID ", .length = 8, .print = print_task_tid, .list = LIST_HEAD_INIT(field_task_tid.list), }; /* index of this table should be matched to display_field_id */ static struct display_field *field_table[] = { &field_total_time, &field_self_time, &field_addr, }; /* index of this task table should be matched to display_field_id */ static struct display_field *field_task_table[] = { &field_task_total_time, &field_task_self_time, &field_task_tid, }; static void setup_default_field(struct list_head *fields, struct opts *opts) { add_field(fields, field_table[GRAPH_F_TOTAL_TIME]); } static void setup_default_task_field(struct list_head *fields, struct opts *opts) { add_field(fields, field_task_table[GRAPH_F_TASK_TOTAL_TIME]); add_field(fields, field_task_table[GRAPH_F_TASK_SELF_TIME]); add_field(fields, field_task_table[GRAPH_F_TASK_TID]); } static void print_field(struct uftrace_graph_node *node) { struct field_data fd = { .arg = node, }; if (print_field_data(&output_fields, &fd, 2)) pr_out(" : "); } static void print_task_field(struct uftrace_task *node) { struct field_data fd = { .arg = node, }; if (print_field_data(&output_task_fields, &fd, 2)) pr_out(" : "); } static int create_graph(struct uftrace_session *sess, void *func) { struct session_graph *graph = xzalloc(sizeof(*graph)); pr_dbg("create graph for session %.*s (%s)\n", SESSION_ID_LEN, sess->sid, sess->exename); graph->func = xstrdup(full_graph ? basename(sess->exename) : func); INIT_LIST_HEAD(&graph->bt_list); graph_init(&graph->ug, sess); graph->ug.root.name = graph->func; graph->next = graph_list; graph_list = graph; return 0; } static void setup_graph_list(struct uftrace_data *handle, struct opts *opts, char *func) { struct session_graph *graph; walk_sessions(&handle->sessions, create_graph, func); graph = graph_list; while (graph) { graph->ug.kernel_only = opts->kernel_only; graph = graph->next; } } static struct uftrace_graph * get_graph(struct uftrace_task_reader *task, uint64_t time, uint64_t addr) { struct session_graph *graph; struct uftrace_session_link *sessions = &task->h->sessions; struct uftrace_session *sess; sess = find_task_session(sessions, task->t, time); if (sess == NULL) { struct uftrace_session *fsess = sessions->first; if (is_kernel_address(&fsess->symtabs, addr)) sess = fsess; else return NULL; } graph = graph_list; while (graph) { if (graph->ug.sess == sess) return &graph->ug; graph = graph->next; } return NULL; } static int start_graph(struct task_graph *tg); static struct task_graph * get_task_graph(struct uftrace_task_reader *task, uint64_t time, uint64_t addr) { struct task_graph *tg; struct uftrace_graph *graph; tg = (struct task_graph *)graph_get_task(task, sizeof(*tg)); graph = get_graph(task, time, addr); if (tg->utg.graph && tg->utg.graph != graph) { pr_dbg("detect new session: %.*s\n", SESSION_ID_LEN, graph->sess->sid); tg->utg.new_sess = true; } tg->utg.graph = graph; if (full_graph && tg->utg.node == NULL) start_graph(tg); return tg; } static int save_backtrace_addr(struct task_graph *tg) { int i; int skip = 0; struct graph_backtrace *bt; struct uftrace_task_reader *task = tg->utg.task; struct session_graph *graph = (struct session_graph *)tg->utg.graph; int len = task->stack_count; uint64_t addrs[len]; if (graph->ug.kernel_only) { skip = task->user_stack_count; len -= skip; } if (len == 0) return 0; for (i = len - 1; i >= 0; i--) { struct fstack *fstack = fstack_get(task, i + skip); if (fstack != NULL) addrs[i] = fstack->addr; else addrs[i] = 0; } list_for_each_entry(bt, &graph->bt_list, list) { if (len == bt->len && !memcmp(addrs, bt->addr, len * sizeof(*addrs))) goto found; } bt = xmalloc(sizeof(*bt) + len * sizeof(*addrs)); bt->len = len; bt->hit = 0; bt->time = 0; memcpy(bt->addr, addrs, len * sizeof(*addrs)); list_add(&bt->list, &graph->bt_list); found: bt->hit++; tg->bt_curr = bt; return 0; } static void save_backtrace_time(struct task_graph *tg) { struct uftrace_task_reader *task = tg->utg.task; struct fstack *fstack = fstack_get(task, task->stack_count); if (tg->bt_curr != NULL && fstack != NULL) tg->bt_curr->time += fstack->total_time; tg->bt_curr = NULL; } static int print_backtrace(struct session_graph *graph) { int i = 0, k; struct graph_backtrace *bt; struct sym *sym; char *symname; list_for_each_entry(bt, &graph->bt_list, list) { pr_out(" backtrace #%d: hit %d, time ", i++, bt->hit); print_time_unit(bt->time); pr_out("\n"); for (k = 0; k < bt->len; k++) { sym = find_symtabs(&graph->ug.sess->symtabs, bt->addr[k]); if (sym == NULL) sym = session_find_dlsym(graph->ug.sess, bt->time, bt->addr[k]); symname = symbol_getname(sym, bt->addr[k]); pr_out(" [%d] %s (%#lx)\n", k, symname, bt->addr[k]); symbol_putname(sym, symname); } pr_out("\n"); } return 0; } static int start_graph(struct task_graph *tg) { if (tg->utg.graph && !tg->enabled++) { save_backtrace_addr(tg); pr_dbg("start graph for task %d\n", tg->utg.task->tid); tg->utg.node = &tg->utg.graph->root; tg->utg.node->addr = tg->utg.task->rstack->addr; tg->utg.node->nr_calls++; } return 0; } static int end_graph(struct task_graph *tg) { if (!tg->enabled) return 0; if (!--tg->enabled) { save_backtrace_time(tg); tg->utg.lost = false; pr_dbg("end graph for task %d\n", tg->utg.task->tid); } return 0; } static void pr_indent(bool *indent_mask, int indent, bool line) { int i; int last = -1; for (i = 0; i < indent; i++) { if (line && indent_mask[i]) last = i; } for (i = 0; i < indent; i++) { if (!line || i < last) { if (indent_mask[i]) pr_out(" | "); else pr_out(" "); } else { if (i == last) pr_out(" +-"); else pr_out("---"); } } } static void print_graph_node(struct uftrace_graph *graph, struct uftrace_graph_node *node, bool *indent_mask, int indent, bool needs_line) { char *symname = node->name; struct uftrace_graph_node *parent = node->parent; struct uftrace_graph_node *child; int orig_indent = indent; /* XXX: what if it clashes with existing function address */ if (node->addr == EVENT_ID_PERF_SCHED_IN) symname = "linux:schedule"; print_field(node); pr_indent(indent_mask, indent, needs_line); /* FIXME: it should count fork+exec properly */ if (full_graph && node == &graph->root) pr_out("(%d) %s\n", 1, symname); else pr_out("(%d) %s\n", node->nr_calls, symname); if (node->nr_edges > 1) { pr_dbg2("add mask (%d) for %s\n", indent, symname); indent_mask[indent++] = true; } /* clear parent indent mask at the last node */ if (parent && parent->nr_edges > 1 && orig_indent > 0 && parent->head.prev == &node->list) indent_mask[orig_indent - 1] = false; needs_line = (node->nr_edges > 1); list_for_each_entry(child, &node->head, list) { print_graph_node(graph, child, indent_mask, indent, needs_line); if (&child->list != node->head.prev) { /* print blank line between siblings */ if (print_empty_field(&output_fields, 2)) pr_out(" : "); pr_indent(indent_mask, indent, false); pr_out("\n"); } } indent_mask[orig_indent] = false; pr_dbg2("del mask (%d) for %s\n", orig_indent, symname); } static int print_graph(struct session_graph *graph, struct opts *opts) { bool *indent_mask; /* skip empty graph */ if (list_empty(&graph->bt_list) && graph->ug.root.time == 0 && graph->ug.root.nr_edges == 0) return 0; pr_out("# Function Call Graph for '%s' (session: %.16s)\n", graph->func, graph->ug.sess->sid); if (!full_graph && !list_empty(&graph->bt_list)) { pr_out("=============== BACKTRACE ===============\n"); print_backtrace(graph); } setup_field(&output_fields, opts, &setup_default_field, field_table, ARRAY_SIZE(field_table)); if (graph->ug.root.time || graph->ug.root.nr_edges) { pr_out("========== FUNCTION CALL GRAPH ==========\n"); print_header(&output_fields, "# ", "FUNCTION", 2); indent_mask = xcalloc(opts->max_stack, sizeof(*indent_mask)); print_graph_node(&graph->ug, &graph->ug.root, indent_mask, 0, graph->ug.root.nr_edges > 1); free(indent_mask); pr_out("\n"); } return 1; } static void build_graph_node(struct opts *opts, struct uftrace_task_reader *task, uint64_t time, uint64_t addr, int type, char *func) { struct task_graph *tg; struct sym *sym = NULL; char *name; tg = get_task_graph(task, time, addr); if (unlikely(tg->utg.graph == NULL)) return; sym = find_symtabs(&tg->utg.graph->sess->symtabs, addr); if (sym == NULL) sym = session_find_dlsym(tg->utg.graph->sess, time, addr); name = symbol_getname(sym, addr); /* skip it if --no-libcall is given */ if (!opts->libcall && sym && sym->type == ST_PLT_FUNC) goto out; if (tg->enabled) { graph_add_node(&tg->utg, type, name, sizeof(struct uftrace_graph_node)); } /* cannot find a session for this record */ if (tg->utg.graph == NULL) goto out; if (type == UFTRACE_EVENT) goto out; if (full_graph) goto out; if (!strcmp(name, func)) { if (type == UFTRACE_ENTRY) start_graph(tg); else if (type == UFTRACE_EXIT) end_graph(tg); } out: symbol_putname(sym, name); } static void build_graph(struct opts *opts, struct uftrace_data *handle, char *func) { struct uftrace_task_reader *task; struct session_graph *graph; uint64_t prev_time = 0; int i; setup_graph_list(handle, opts, func); while (!read_rstack(handle, &task) && !uftrace_done) { struct uftrace_record *frs = task->rstack; uint64_t addr = frs->addr; if (!fstack_check_opts(task, opts)) continue; if (!fstack_check_filter(task)) continue; if (frs->type == UFTRACE_EVENT) { if (frs->addr != EVENT_ID_PERF_SCHED_IN && frs->addr != EVENT_ID_PERF_SCHED_OUT) continue; } if (is_kernel_record(task, frs)) { struct uftrace_session *fsess; fsess = task->h->sessions.first; addr = get_kernel_address(&fsess->symtabs, addr); } if (frs->type == UFTRACE_LOST) { struct task_graph *tg; struct uftrace_session *fsess; if (opts->kernel_skip_out && !task->user_stack_count) continue; pr_dbg("*** LOST ***\n"); /* add partial duration of kernel functions before LOST */ while (task->stack_count >= task->user_stack_count) { struct fstack *fstack; fstack = fstack_get(task, task->stack_count); if (fstack_enabled && fstack && fstack->valid && !(fstack->flags & FSTACK_FL_NORECORD)) { build_graph_node(opts, task, prev_time, fstack->addr, UFTRACE_EXIT, func); } fstack_exit(task); task->stack_count--; } /* force to find a session for kernel function */ fsess = task->h->sessions.first; tg = get_task_graph(task, prev_time, fsess->symtabs.kernel_base + 1); tg->utg.lost = true; if (tg->enabled && is_kernel_address(&fsess->symtabs, tg->utg.node->addr)) pr_dbg("not returning to user after LOST\n"); continue; } if (prev_time > frs->time) { pr_warn("inverted time: broken data?\n"); return; } prev_time = frs->time; build_graph_node(opts, task, frs->time, addr, frs->type, func); } /* add duration of remaining functions */ for (i = 0; i < handle->nr_tasks; i++) { uint64_t last_time; struct fstack *fstack; task = &handle->tasks[i]; if (task->stack_count == 0) continue; last_time = task->rstack->time; if (handle->time_range.stop) last_time = handle->time_range.stop; while (--task->stack_count >= 0) { fstack = fstack_get(task, task->stack_count); if (fstack == NULL) continue; if (fstack->addr == 0) continue; if (fstack->total_time > last_time) continue; fstack->total_time = last_time - fstack->total_time; if (fstack->child_time > fstack->total_time) fstack->total_time = fstack->child_time; if (task->stack_count > 0) fstack[-1].child_time += fstack->total_time; build_graph_node(opts, task, last_time, fstack->addr, UFTRACE_EXIT, func); } } if (!full_graph || uftrace_done) return; /* account execution time of each graph */ graph = graph_list; while (graph) { struct uftrace_graph_node *node; list_for_each_entry(node, &graph->ug.root.head, list) { graph->ug.root.time += node->time; graph->ug.root.child_time += node->time; } graph = graph->next; } } struct find_func_data { char *name; bool found; }; static int find_func(struct uftrace_session *s, void *arg) { struct find_func_data *data = arg; struct symtabs *symtabs = &s->symtabs; struct uftrace_mmap *map; for_each_map(symtabs, map) { if (map->mod == NULL) continue; if (find_symname(&map->mod->symtab, data->name)) { data->found = true; break; } } return data->found; } static void synthesize_depth_trigger(struct opts *opts, struct uftrace_data *handle, char *func) { size_t old_len = opts->trigger ? strlen(opts->trigger) : 0; size_t new_len = strlen(func) + 32; struct find_func_data ffd = { .name = func, }; walk_sessions(&handle->sessions, find_func, &ffd); opts->trigger = xrealloc(opts->trigger, old_len + new_len); snprintf(opts->trigger + old_len, new_len, "%s%s@%sdepth=%d", old_len ? ";" : "", func, ffd.found ? "" : "kernel,", opts->depth); } static void reset_task_runtime(struct uftrace_data *handle) { struct uftrace_task *t; struct rb_node *n = rb_first(&handle->sessions.tasks); while (n != NULL) { t = rb_entry(n, struct uftrace_task, node); n = rb_next(n); t->time.run = 0; t->time.idle = 0; t->time.stamp = 0; } } static void graph_build_task(struct opts *opts, struct uftrace_data *handle) { struct uftrace_task_reader *task; struct uftrace_task *t; int i; /* * we need to know entire runtime, so not apply time filter now * and it will be filtered when it's printed. */ handle->time_filter = 0; reset_task_runtime(handle); while (!read_rstack(handle, &task) && !uftrace_done) { struct uftrace_record *frs = task->rstack; if (!fstack_check_opts(task, opts)) continue; if (!fstack_check_filter(task)) continue; if (task->timestamp == 0) task->timestamp = frs->time; task->timestamp_last = frs->time; t = task->t; if (frs->type == UFTRACE_EVENT) { switch (frs->addr) { case EVENT_ID_PERF_SCHED_OUT: t->time.stamp = frs->time; break; case EVENT_ID_PERF_SCHED_IN: if (t->time.stamp) t->time.idle += frs->time - t->time.stamp; t->time.stamp = 0; break; } } } /* update task time if some records were missing */ for (i = 0; i < handle->nr_tasks; i++) { task = &handle->tasks[i]; t = task->t; /* update idle time if last SCHED_IN event was missing */ if (t->time.stamp) t->time.idle += task->timestamp_last - t->time.stamp; t->time.run = task->timestamp_last - task->timestamp; } handle->time_filter = opts->threshold; } /* returns true if any of child has more runtime than the filter */ static bool check_time_filter(struct uftrace_task *task, struct opts *opts) { struct uftrace_task *child; list_for_each_entry(child, &task->children, siblings) { if (child->time.run >= opts->threshold) return true; if (check_time_filter(child, opts)) return true; } return false; } static bool is_last_child(struct uftrace_task *task, struct uftrace_task *parent, struct opts *opts) { if (list_is_singular(&parent->children) || parent->children.prev == &task->siblings) return true; /* any sibling satisfies the time filter? */ list_for_each_entry_continue(task, &parent->children, siblings) { if (task->time.run >= opts->threshold || check_time_filter(task, opts)) return false; } return true; } static bool print_task_node(struct uftrace_task *task, struct uftrace_task *parent, bool *indent_mask, int indent, struct opts *opts) { char *name = task->comm; struct uftrace_task *child; int orig_indent = indent; bool blank = false; if (uftrace_done) return false; print_task_field(task); pr_indent(indent_mask, indent, true); if (parent && parent->pid == task->pid) { /* print thread name in green color */ pr_green("%s\n", name); } else { /* print process name */ pr_out("%s\n", name); } if (list_empty(&task->children) || !check_time_filter(task, opts)) return false; /* clear parent indent mask at the last node */ if (parent && is_last_child(task, parent, opts)) { int parent_indent = orig_indent - 1; if (task->pid != parent->pid) parent_indent--; indent_mask[parent_indent] = false; } list_for_each_entry(child, &task->children, siblings) { /* * Filter out if total time is less than time-filter. * Note that child might live longer than parent. * In that case we should print the parent even if it's * shorter than the time filter to show a correct tree. */ if (opts->threshold > child->time.run && !check_time_filter(child, opts)) continue; indent = orig_indent; indent_mask[indent++] = true; if (child->pid != task->pid) { /* print blank line before forked child */ blank = true; indent++; } if (blank) { /* print blank line between siblings */ if (print_empty_field(&output_task_fields, 2)) pr_out(" : "); pr_indent(indent_mask, indent, false); pr_out("\n"); blank = false; } blank |= print_task_node(child, task, indent_mask, indent, opts); if (&child->siblings != task->children.prev && child->pid != task->pid) { /* print blank line after forked child */ blank = true; } } indent_mask[orig_indent] = false; return blank; } static int graph_print_task(struct uftrace_data *handle, struct opts *opts) { bool *indent_mask; struct uftrace_task *task; if (uftrace_done) return 0; if (handle->nr_tasks <= 0) return 0; task = handle->sessions.first_task; setup_field(&output_task_fields, opts, &setup_default_task_field, field_task_table, ARRAY_SIZE(field_task_table)); pr_out("========== TASK GRAPH ==========\n"); print_header(&output_task_fields, "# ", "TASK NAME", 2); indent_mask = xcalloc(handle->nr_tasks, sizeof(*indent_mask)); /* filter out if total time is less than time-filter */ if (opts->threshold <= task->time.run) print_task_node(task, NULL, indent_mask, 0, opts); free(indent_mask); pr_out("\n"); return 1; } int command_graph(int argc, char *argv[], struct opts *opts) { int ret; struct uftrace_data handle; struct session_graph *graph; char *func; struct graph_backtrace *bt, *btmp; __fsetlocking(outfp, FSETLOCKING_BYCALLER); __fsetlocking(logfp, FSETLOCKING_BYCALLER); if (argc > 0) func = argv[0]; else { func = "_start"; full_graph = true; } ret = open_data_file(opts, &handle); if (ret < 0) { pr_warn("cannot open record data: %s: %m\n", opts->dirname); return -1; } if (opts->depth != OPT_DEPTH_DEFAULT) { /* * Applying depth filter before the function might * lead to undesired result. Set a synthetic depth * trigger to prevent the function from filtering out. */ synthesize_depth_trigger(opts, &handle, func); } fstack_setup_filters(opts, &handle); if (opts->show_task) { graph_build_task(opts, &handle); graph_print_task(&handle, opts); goto out; } build_graph(opts, &handle, func); graph = graph_list; while (graph && !uftrace_done) { ret += print_graph(graph, opts); graph = graph->next; } if (!ret && !uftrace_done) { pr_out("uftrace: cannot find graph for '%s'\n", func); if (opts_has_filter(opts)) pr_out("\t please check your filter settings.\n"); } while (graph_list) { graph = graph_list; graph_list = graph->next; free(graph->func); list_for_each_entry_safe(bt, btmp, &graph->bt_list, list) { list_del(&bt->list); free(bt); } graph_destroy(&graph->ug); free(graph); } graph_remove_task(); out: close_data_file(opts, &handle); return 0; } uftrace-0.9.4/cmds/info.c000066400000000000000000000703231362052523300152100ustar00rootroot00000000000000/* * uftrace info command related routines * * Copyright (C) 2014-2018, LG Electronics, Namhyung Kim * * Released under the GPL v2. */ #include #include #include #include #include #include #include #include #include #include #include "uftrace.h" #include "libmcount/mcount.h" #include "utils/utils.h" #include "utils/filter.h" #include "utils/symbol.h" #include "utils/fstack.h" #include "version.h" #define BUILD_ID_SIZE 20 #define BUILD_ID_STR_SIZE (BUILD_ID_SIZE * 2 + 1) struct read_handler_arg { struct uftrace_data *handle; char buf[PATH_MAX]; }; struct fill_handler_arg { int fd; int exit_status; struct opts *opts; struct rusage *rusage; char *elapsed_time; char buf[PATH_MAX]; }; static char *copy_info_str(char *src) { char *dst = xstrdup(src); size_t len = strlen(dst); if (dst[len-1] == '\n') dst[len-1] = '\0'; return dst; } static int fill_exe_name(void *arg) { struct fill_handler_arg *fha = arg; char *exename; exename = realpath(fha->opts->exename, fha->buf); if (exename == NULL) exename = fha->opts->exename; return dprintf(fha->fd, "exename:%s\n", exename); } static int read_exe_name(void *arg) { struct read_handler_arg *rha = arg; struct uftrace_data *handle = rha->handle; struct uftrace_info *info = &handle->info; char *buf = rha->buf; if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "exename:", 8)) return -1; info->exename = copy_info_str(&buf[8]); return 0; } static int fill_exe_build_id(void *arg) { struct fill_handler_arg *fha = arg; unsigned char build_id[BUILD_ID_SIZE]; char build_id_str[BUILD_ID_STR_SIZE]; struct uftrace_elf_data elf; struct uftrace_elf_iter iter; bool found_build_id = false; int offset; if (elf_init(fha->opts->exename, &elf) < 0) return -1; elf_for_each_shdr(&elf, &iter) { char *str; if (iter.shdr.sh_type != SHT_NOTE) continue; /* there can be more than one note sections */ str = elf_get_name(&elf, &iter, iter.shdr.sh_name); if (!strcmp(str, ".note.gnu.build-id")) { found_build_id = true; break; } } if (!found_build_id) { pr_dbg("cannot find build-id section\n"); return -1; } elf_for_each_note(&elf, &iter) { if (iter.nhdr.n_type != NT_GNU_BUILD_ID) continue; if (!strcmp(iter.note_name, "GNU")) { memcpy(build_id, iter.note_desc, BUILD_ID_SIZE); break; } } elf_finish(&elf); for (offset = 0; offset < BUILD_ID_SIZE; offset++) { unsigned char c = build_id[offset]; sprintf(&build_id_str[offset*2], "%02x", c); } build_id_str[BUILD_ID_STR_SIZE - 1] = '\0'; return dprintf(fha->fd, "build_id:%s\n", build_id_str); } static int convert_to_int(unsigned char hex) { if (!isxdigit(hex)) return -1; if (hex >= '0' && hex <= '9') return hex - '0'; if (hex >= 'a' && hex <= 'f') return hex - 'a' + 10; if (hex >= 'A' && hex <= 'F') return hex - 'A' + 10; return -1; } static int read_exe_build_id(void *arg) { struct read_handler_arg *rha = arg; struct uftrace_data *handle = rha->handle; struct uftrace_info *info = &handle->info; char build_id_str[BUILD_ID_STR_SIZE]; char *buf = rha->buf; int i; if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "build_id:", 9)) return -1; memcpy(build_id_str, &buf[9], BUILD_ID_STR_SIZE - 1); build_id_str[BUILD_ID_STR_SIZE - 1] = '\0'; for (i = 0; i < BUILD_ID_SIZE; i++) { int c1 = convert_to_int(build_id_str[i*2]); int c2 = convert_to_int(build_id_str[i*2 + 1]); if (c1 < 0 || c2 < 0) return -1; info->build_id[i] = c1 << 4 | c2; } return 0; } static int fill_exit_status(void *arg) { struct fill_handler_arg *fha = arg; return dprintf(fha->fd, "exit_status:%d\n", fha->exit_status); } static int read_exit_status(void *arg) { struct read_handler_arg *rha = arg; struct uftrace_data *handle = rha->handle; struct uftrace_info *info = &handle->info; char *buf = rha->buf; if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "exit_status:", 12)) return -1; sscanf(&buf[12], "%d", &info->exit_status); return 0; } static int fill_cmdline(void *arg) { struct fill_handler_arg *fha = arg; char *buf = fha->buf; FILE *fp; int err; int ret; int i; char *p; fp = fopen("/proc/self/cmdline", "r"); if (fp == NULL) return -1; ret = fread(buf, 1, sizeof(fha->buf), fp); err = ferror(fp); fclose(fp); if (!ret && err) return -1; /* cmdline separated by NUL character - convert to space */ for (i = 0, p = buf; i < ret; i++, p++) { if (*p == '\0' || *p == '\n') *p = ' '; } buf[sizeof(fha->buf) - 1] = '\0'; p = json_quote(buf, &ret); p[ret - 1] = '\n'; if ((write(fha->fd, "cmdline:", 8) < 8) || (write(fha->fd, p, ret) < ret)) { free(p); return -1; } free(p); return ret; } static int read_cmdline(void *arg) { struct read_handler_arg *rha = arg; struct uftrace_data *handle = rha->handle; struct uftrace_info *info = &handle->info; char *buf = rha->buf; if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "cmdline:", 8)) return -1; info->cmdline = copy_info_str(&buf[8]); return 0; } static int fill_cpuinfo(void *arg) { struct fill_handler_arg *fha = arg; unsigned long nr_possible; unsigned long nr_online; nr_possible = sysconf(_SC_NPROCESSORS_CONF); nr_online = sysconf(_SC_NPROCESSORS_ONLN); dprintf(fha->fd, "cpuinfo:lines=2\n"); dprintf(fha->fd, "cpuinfo:nr_cpus=%lu / %lu (online/possible)\n", nr_online, nr_possible); return arch_fill_cpuinfo_model(fha->fd); } static int read_cpuinfo(void *arg) { struct read_handler_arg *rha = arg; struct uftrace_data *handle = rha->handle; struct uftrace_info *info = &handle->info; char *buf = rha->buf; int i, lines; if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "cpuinfo:", 8)) return -1; if (sscanf(&buf[8], "lines=%d\n", &lines) == EOF) return -1; for (i = 0; i < lines; i++) { if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "cpuinfo:", 8)) return -1; if (!strncmp(&buf[8], "nr_cpus=", 8)) { sscanf(&buf[8], "nr_cpus=%d / %d\n", &info->nr_cpus_online, &info->nr_cpus_possible); } else if (!strncmp(&buf[8], "desc=", 5)) { info->cpudesc = copy_info_str(&buf[13]); /* guess CPU arch from the description */ if (!strncmp(info->cpudesc, "ARMv6", 5) || !strncmp(info->cpudesc, "ARMv7", 5)) { handle->arch = UFT_CPU_ARM; } else if (!strncmp(info->cpudesc, "ARM64", 5)) { handle->arch = UFT_CPU_AARCH64; } else if (data_is_lp64(handle)) { handle->arch = UFT_CPU_X86_64; } else { handle->arch = UFT_CPU_I386; } } } return 0; } static int fill_meminfo(void *arg) { struct fill_handler_arg *fha = arg; long mem_total = 0; long mem_total_small; long mem_free = 0; long mem_free_small; char *units[] = { "KB", "MB", "GB", "TB" }; char *unit; char *buf = fha->buf; size_t i; FILE *fp; fp = fopen("/proc/meminfo", "r"); if (fp == NULL) return -1; while (fgets(buf, sizeof(fha->buf), fp) != NULL) { if (!strncmp(buf, "MemTotal:", 9)) sscanf(&buf[10], "%ld", &mem_total); else if (!strncmp(buf, "MemFree:", 8)) sscanf(&buf[9], "%ld", &mem_free); else break; } fclose(fp); mem_total_small = (mem_total % 1024) / 103; /* 103 ~= 1024 / 10 */ mem_free_small = (mem_free % 1024) / 103; for (i = 0; i < ARRAY_SIZE(units); i++) { unit = units[i]; if (mem_total < 1024) break; mem_total_small = (mem_total % 1024) / 103; mem_free_small = (mem_free % 1024) / 103; mem_total >>= 10; mem_free >>= 10; } dprintf(fha->fd, "meminfo:%ld.%ld / %ld.%ld %s (free / total)\n", mem_free, mem_free_small, mem_total, mem_total_small, unit); return 0; } static int read_meminfo(void *arg) { struct read_handler_arg *rha = arg; struct uftrace_data *handle = rha->handle; struct uftrace_info *info = &handle->info; char *buf = rha->buf; if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "meminfo:", 8)) return -1; info->meminfo = copy_info_str(&buf[8]); return 0; } static int fill_osinfo(void *arg) { struct fill_handler_arg *fha = arg; struct utsname uts; char *buf = fha->buf; FILE *fp; int ret = -1; uname(&uts); dprintf(fha->fd, "osinfo:lines=3\n"); dprintf(fha->fd, "osinfo:kernel=%s %s\n", uts.sysname, uts.release); dprintf(fha->fd, "osinfo:hostname=%s\n", uts.nodename); fp = fopen("/etc/os-release", "r"); if (fp != NULL) { while (fgets(buf, sizeof(fha->buf), fp) != NULL) { if (!strncmp(buf, "PRETTY_NAME=", 12)) { dprintf(fha->fd, "osinfo:distro=%s", &buf[12]); ret = 0; break; } } fclose(fp); return ret; } fp = fopen("/etc/lsb-release", "r"); if (fp != NULL) { while (fgets(buf, sizeof(fha->buf), fp) != NULL) { if (!strncmp(buf, "DISTRIB_DESCRIPTION=", 20)) { dprintf(fha->fd, "osinfo:distro=%s", &buf[20]); ret = 0; break; } } fclose(fp); return ret; } dprintf(fha->fd, "osinfo:distro=\"Unknown\"\n"); return 0; } static int read_osinfo(void *arg) { struct read_handler_arg *rha = arg; struct uftrace_data *handle = rha->handle; struct uftrace_info *info = &handle->info; char *buf = rha->buf; int i, lines; if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "osinfo:", 7)) return -1; if (sscanf(&buf[7], "lines=%d\n", &lines) == EOF) return -1; for (i = 0; i < lines; i++) { if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "osinfo:", 7)) return -1; if (!strncmp(&buf[7], "kernel=", 7)) { info->kernel = copy_info_str(&buf[14]); } else if (!strncmp(&buf[7], "hostname=", 9)) { info->hostname = copy_info_str(&buf[16]); } else if (!strncmp(&buf[7], "distro=", 7)) { info->distro = copy_info_str(&buf[14]); } } return 0; } struct tid_list { int nr; int *tid; }; static int build_tid_list(struct uftrace_task *t, void *arg) { struct tid_list *list = arg; list->nr++; list->tid = xrealloc(list->tid, list->nr * sizeof(*list->tid)); list->tid[list->nr - 1] = t->tid; return 0; } static int fill_taskinfo(void *arg) { struct fill_handler_arg *fha = arg; bool first = true; struct tid_list tlist = { .nr = 0, }; struct uftrace_session_link link = { .root = RB_ROOT, .tasks = RB_ROOT, }; int i; if (read_task_txt_file(&link, fha->opts->dirname, false, false, false) < 0 && read_task_file(&link, fha->opts->dirname, false, false, false) < 0) return -1; walk_tasks(&link, build_tid_list, &tlist); dprintf(fha->fd, "taskinfo:lines=2\n"); dprintf(fha->fd, "taskinfo:nr_tid=%d\n", tlist.nr); dprintf(fha->fd, "taskinfo:tids="); for (i = 0; i < tlist.nr; i++) { dprintf(fha->fd, "%s%d", first ? "" : ",", tlist.tid[i]); first = false; } dprintf(fha->fd, "\n"); delete_sessions(&link); free(tlist.tid); return 0; } static int read_taskinfo(void *arg) { struct read_handler_arg *rha = arg; struct uftrace_data *handle = rha->handle; struct uftrace_info *info = &handle->info; int i, lines; int ret = -1; char *buf = NULL; size_t len = 0; if (getline(&buf, &len, handle->fp) < 0) goto out; if (strncmp(buf, "taskinfo:", 9)) goto out; if (sscanf(&buf[9], "lines=%d\n", &lines) == EOF) goto out; for (i = 0; i < lines; i++) { if (getline(&buf, &len, handle->fp) < 0) goto out; if (strncmp(buf, "taskinfo:", 9)) goto out; if (!strncmp(&buf[9], "nr_tid=", 7)) { info->nr_tid = strtol(&buf[16], NULL, 10); } else if (!strncmp(&buf[9], "tids=", 5)) { char *tids_str = &buf[14]; char *endp = tids_str; int *tids = xcalloc(sizeof(*tids), info->nr_tid); int nr_tid = 0; while (*endp != '\n') { int tid = strtol(tids_str, &endp, 10); tids[nr_tid++] = tid; if (*endp != ',' && *endp != '\n') { free(tids); goto out; } tids_str = endp + 1; } info->tids = tids; assert(nr_tid == info->nr_tid); } else goto out; } ret = 0; out: free(buf); return ret; } static int fill_usageinfo(void *arg) { struct fill_handler_arg *fha = arg; struct rusage *r = fha->rusage; struct rusage zero = {}; if (!memcmp(r, &zero, sizeof(*r))) return -1; dprintf(fha->fd, "usageinfo:lines=6\n"); dprintf(fha->fd, "usageinfo:systime=%lu.%06lu\n", r->ru_stime.tv_sec, r->ru_stime.tv_usec); dprintf(fha->fd, "usageinfo:usrtime=%lu.%06lu\n", r->ru_utime.tv_sec, r->ru_utime.tv_usec); dprintf(fha->fd, "usageinfo:ctxsw=%ld / %ld (voluntary / involuntary)\n", r->ru_nvcsw, r->ru_nivcsw); dprintf(fha->fd, "usageinfo:maxrss=%ld\n", r->ru_maxrss); dprintf(fha->fd, "usageinfo:pagefault=%ld / %ld (major / minor)\n", r->ru_majflt, r->ru_minflt); dprintf(fha->fd, "usageinfo:iops=%ld / %ld (read / write)\n", r->ru_inblock, r->ru_oublock); return 0; } static int read_usageinfo(void *arg) { struct read_handler_arg *rha = arg; struct uftrace_data *handle = rha->handle; struct uftrace_info *info = &handle->info; char *buf = rha->buf; int i, lines; if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "usageinfo:", 10)) return -1; if (sscanf(&buf[10], "lines=%d\n", &lines) == EOF) return -1; for (i = 0; i < lines; i++) { if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "usageinfo:", 10)) return -1; if (!strncmp(&buf[10], "systime=", 8)) sscanf(&buf[18], "%lf", &info->stime); else if (!strncmp(&buf[10], "usrtime=", 8)) sscanf(&buf[18], "%lf", &info->utime); else if (!strncmp(&buf[10], "ctxsw=", 6)) sscanf(&buf[16], "%ld / %ld", &info->vctxsw, &info->ictxsw); else if (!strncmp(&buf[10], "maxrss=", 7)) sscanf(&buf[17], "%ld", &info->maxrss); else if (!strncmp(&buf[10], "pagefault=", 10)) sscanf(&buf[20], "%ld / %ld", &info->major_fault, &info->minor_fault); else if (!strncmp(&buf[10], "iops=", 5)) sscanf(&buf[15], "%ld / %ld", &info->rblock, &info->wblock); } return 0; } static int fill_loadinfo(void *arg) { struct fill_handler_arg *fha = arg; FILE *fp = fopen("/proc/loadavg", "r"); float loadavg[3]; if (fp == NULL) return -1; if (fscanf(fp, "%f %f %f", &loadavg[0], &loadavg[1], &loadavg[2]) != 3) { fclose(fp); return -1; } dprintf(fha->fd, "loadinfo:%.02f / %.02f / %.02f\n", loadavg[0], loadavg[1], loadavg[2]); fclose(fp); return 0; } static int read_loadinfo(void *arg) { struct read_handler_arg *rha = arg; struct uftrace_data *handle = rha->handle; struct uftrace_info *info = &handle->info; char *buf = rha->buf; if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "loadinfo:", 9)) return -1; sscanf(&buf[9], "%f / %f / %f", &info->load1, &info->load5, &info->load15); return 0; } static int fill_arg_spec(void *arg) { struct fill_handler_arg *fha = arg; char *argspec = fha->opts->args; char *retspec = fha->opts->retval; int n; n = extract_trigger_args(&argspec, &retspec, fha->opts->trigger); if (n == 0 && !fha->opts->auto_args) return -1; dprintf(fha->fd, "argspec:lines=%d\n", n + 3 + !!fha->opts->auto_args); if (argspec) { dprintf(fha->fd, "argspec:%s\n", argspec); free(argspec); } if (retspec) { dprintf(fha->fd, "retspec:%s\n", retspec); free(retspec); } dprintf(fha->fd, "argauto:%s\n", get_auto_argspec_str()); dprintf(fha->fd, "retauto:%s\n", get_auto_retspec_str()); dprintf(fha->fd, "enumauto:%s\n", get_auto_enum_str()); if (fha->opts->auto_args) dprintf(fha->fd, "auto-args:1\n"); return 0; } static int read_arg_spec(void *arg) { struct read_handler_arg *rha = arg; struct uftrace_data *handle = rha->handle; struct uftrace_info *info = &handle->info; int i, lines; int ret = -1; char *buf = NULL; size_t len = 0; if (getline(&buf, &len, handle->fp) < 0) goto out; if (strncmp(buf, "argspec:", 8)) goto out; /* old format only has argspec */ if (strncmp(&buf[8], "lines", 5)) { info->argspec = copy_info_str(&buf[8]); ret = 0; goto out; } if (sscanf(&buf[8], "lines=%d\n", &lines) == EOF) goto out; for (i = 0; i < lines; i++) { if (getline(&buf, &len, handle->fp) < 0) goto out; if (!strncmp(buf, "argspec:", 8)) info->argspec = copy_info_str(&buf[8]); else if (!strncmp(buf, "retspec:", 8)) info->retspec = copy_info_str(&buf[8]); else if (!strncmp(buf, "argauto:", 8)) info->autoarg = copy_info_str(&buf[8]); else if (!strncmp(buf, "retauto:", 8)) info->autoret = copy_info_str(&buf[8]); else if (!strncmp(buf, "enumauto:", 9)) info->autoenum = copy_info_str(&buf[9]); else if (!strncmp(buf, "auto-args:1", 11)) info->auto_args_enabled = 1; else goto out; } ret = 0; out: free(buf); return ret; } static int fill_record_date(void *arg) { struct fill_handler_arg *fha = arg; time_t current_time; time(¤t_time); dprintf(fha->fd, "record_date:%s", ctime(¤t_time)); dprintf(fha->fd, "elapsed_time:%s\n", fha->elapsed_time); return 0; } static int read_record_date(void *arg) { struct read_handler_arg *rha = arg; struct uftrace_data *handle = rha->handle; struct uftrace_info *info = &handle->info; char *buf = rha->buf; if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "record_date:", 12)) return -1; info->record_date = copy_info_str(&buf[12]); if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "elapsed_time:", 13)) return -1; info->elapsed_time = copy_info_str(&buf[13]); return 0; } static int fill_pattern_type(void *arg) { struct fill_handler_arg *fha = arg; dprintf(fha->fd, "pattern_type:%s\n", get_filter_pattern(fha->opts->patt_type)); return 0; } static int read_pattern_type(void *arg) { struct read_handler_arg *rha = arg; struct uftrace_data *handle = rha->handle; struct uftrace_info *info = &handle->info; char *buf = rha->buf; size_t len; if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "pattern_type:", 13)) return -1; len = strlen(&buf[13]); if (buf[13 + len - 1] == '\n') buf[13 + len - 1] = '\0'; info->patt_type = parse_filter_pattern(&buf[13]); return 0; } static int fill_uftrace_version(void *arg) { struct fill_handler_arg *fha = arg; return dprintf(fha->fd, "uftrace_version:%s\n", UFTRACE_VERSION); } static int read_uftrace_version(void *arg) { struct read_handler_arg *rha = arg; struct uftrace_data *handle = rha->handle; struct uftrace_info *info = &handle->info; char *buf = rha->buf; if (fgets(buf, sizeof(rha->buf), handle->fp) == NULL) return -1; if (strncmp(buf, "uftrace_version:", 16)) return -1; info->uftrace_version = copy_info_str(&buf[16]); return 0; } struct uftrace_info_handler { enum uftrace_info_bits bit; int (*handler)(void *arg); }; void fill_uftrace_info(uint64_t *info_mask, int fd, struct opts *opts, int status, struct rusage *rusage, char *elapsed_time) { size_t i; off_t offset; struct fill_handler_arg arg = { .fd = fd, .opts = opts, .exit_status = status, .rusage = rusage, .elapsed_time = elapsed_time, }; struct uftrace_info_handler fill_handlers[] = { { EXE_NAME, fill_exe_name }, { EXE_BUILD_ID, fill_exe_build_id }, { EXIT_STATUS, fill_exit_status }, { CMDLINE, fill_cmdline }, { CPUINFO, fill_cpuinfo }, { MEMINFO, fill_meminfo }, { OSINFO, fill_osinfo }, { TASKINFO, fill_taskinfo }, { USAGEINFO, fill_usageinfo }, { LOADINFO, fill_loadinfo }, { ARG_SPEC, fill_arg_spec }, { RECORD_DATE, fill_record_date }, { PATTERN_TYPE, fill_pattern_type }, { VERSION, fill_uftrace_version }, }; for (i = 0; i < ARRAY_SIZE(fill_handlers); i++) { errno = 0; offset = lseek(fd, 0, SEEK_CUR); if (offset == (off_t)-1 && errno) { pr_dbg("skip info due to failed lseek: %m\n"); continue; } if (fill_handlers[i].handler(&arg) < 0) { /* ignore failed info */ errno = 0; if (lseek(fd, offset, SEEK_SET) == (off_t)-1 && errno) pr_warn("fail to reset uftrace info: %m\n"); continue; } *info_mask |= (1UL << fill_handlers[i].bit); } } int read_uftrace_info(uint64_t info_mask, struct uftrace_data *handle) { size_t i; struct read_handler_arg arg = { .handle = handle, }; struct uftrace_info_handler read_handlers[] = { { EXE_NAME, read_exe_name }, { EXE_BUILD_ID, read_exe_build_id }, { EXIT_STATUS, read_exit_status }, { CMDLINE, read_cmdline }, { CPUINFO, read_cpuinfo }, { MEMINFO, read_meminfo }, { OSINFO, read_osinfo }, { TASKINFO, read_taskinfo }, { USAGEINFO, read_usageinfo }, { LOADINFO, read_loadinfo }, { ARG_SPEC, read_arg_spec }, { RECORD_DATE, read_record_date }, { PATTERN_TYPE, read_pattern_type }, { VERSION, read_uftrace_version }, }; memset(&handle->info, 0, sizeof(handle->info)); for (i = 0; i < ARRAY_SIZE(read_handlers); i++) { if (!(info_mask & (1UL << read_handlers[i].bit))) continue; if (read_handlers[i].handler(&arg) < 0) { pr_dbg("error during read uftrace info (%x)\n", (1U << read_handlers[i].bit)); return -1; } } return 0; } void clear_uftrace_info(struct uftrace_info *info) { free(info->exename); free(info->cmdline); free(info->cpudesc); free(info->meminfo); free(info->kernel); free(info->hostname); free(info->distro); free(info->tids); free(info->argspec); free(info->record_date); free(info->elapsed_time); free(info->uftrace_version); free(info->retspec); free(info->autoarg); free(info->autoret); free(info->autoenum); } static void print_info(void *unused, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(outfp, fmt, ap); va_end(ap); } void process_uftrace_info(struct uftrace_data *handle, struct opts *opts, void (*process)(void *data, const char *fmt, ...), void *data) { char buf[PATH_MAX]; struct stat statbuf; const char *fmt = "# %-20s: %s\n"; uint64_t info_mask = handle->hdr.info_mask; struct uftrace_info *info = &handle->info; if (info_mask == 0) return; snprintf(buf, sizeof(buf), "%s/info", opts->dirname); if (stat(buf, &statbuf) < 0) return; process(data, "# system information\n"); process(data, "# ==================\n"); if (info_mask & (1UL << VERSION)) process(data, fmt, "program version", info->uftrace_version); if (info_mask & (1UL << RECORD_DATE)) process(data, fmt, "recorded on", info->record_date); else process(data, "# %-20s: %s", "recorded on", ctime(&statbuf.st_mtime)); if (info_mask & (1UL << CMDLINE)) process(data, fmt, "cmdline", info->cmdline); if (info_mask & (1UL << CPUINFO)) { process(data, fmt, "cpu info", info->cpudesc); process(data, "# %-20s: %d / %d (online / possible)\n", "number of cpus", info->nr_cpus_online, info->nr_cpus_possible); } if (info_mask & (1UL << MEMINFO)) process(data, fmt, "memory info", info->meminfo); if (info_mask & (1UL << LOADINFO)) process(data, "# %-20s: %.02f / %.02f / %.02f (1 / 5 / 15 min)\n", "system load", info->load1, info->load5, info->load15); if (info_mask & (1UL << OSINFO)) { process(data, fmt, "kernel version", info->kernel); process(data, fmt, "hostname", info->hostname); process(data, fmt, "distro", info->distro); } process(data, "#\n"); process(data, "# process information\n"); process(data, "# ===================\n"); if (info_mask & (1UL << TASKINFO)) { int i; int nr = info->nr_tid; bool first = true; struct uftrace_task *task; char *task_list; int sz, len; char *p; /* ignore errors */ read_task_txt_file(&handle->sessions, opts->dirname, false, false, false); process(data, "# %-20s: %d\n", "number of tasks", nr); if (handle->hdr.feat_mask & PERF_EVENT) { if (setup_perf_data(handle) == 0) update_perf_task_comm(handle); } sz = nr * 32; /* 32 > strlen("tid (comm)") */ len = 0; p = task_list = xmalloc(sz); for (i = 0; i < nr; i++) { int tid = info->tids[i]; task = find_task(&handle->sessions, tid); len = snprintf(p, sz, "%s%d(%s)", first ? "" : ", ", tid, task ? task->comm : ""); p += len; sz -= len; first = false; } process(data, "# %-20s: %s\n", "task list", task_list); free(task_list); } if (info_mask & (1UL << EXE_NAME)) process(data, fmt, "exe image", info->exename); if (info_mask & (1UL << EXE_BUILD_ID)) { int i; char bid[BUILD_ID_SIZE * 2 + 1]; for (i = 0; i < BUILD_ID_SIZE; i++) snprintf(bid + i * 2, 3, "%02x", info->build_id[i]); process(data, "# %-20s: %s\n", "build id", bid); } if (info_mask & (1UL << ARG_SPEC)) { if (info->argspec) process(data, fmt, "arguments", info->argspec); if (info->retspec) process(data, fmt, "return values", info->retspec); if (info->auto_args_enabled) process(data, fmt, "auto-args", "true"); } if (info_mask & (1UL << PATTERN_TYPE)) process(data, fmt, "pattern", get_filter_pattern(info->patt_type)); if (info_mask & (1UL << EXIT_STATUS)) { int status = info->exit_status; if (status == UFTRACE_EXIT_FINISHED) { snprintf(buf, sizeof(buf), "terminated by finish trigger"); } else if (WIFEXITED(status)) { snprintf(buf, sizeof(buf), "exited with code: %d", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { snprintf(buf, sizeof(buf), "terminated by signal: %d (%s)", WTERMSIG(status), strsignal(WTERMSIG(status))); } else { snprintf(buf, sizeof(buf), "unknown exit status: %d", status); } process(data, fmt, "exit status", buf); } if (info_mask & (1UL << RECORD_DATE)) process(data, fmt, "elapsed time", info->elapsed_time); if (info_mask & (1UL << USAGEINFO)) { process(data, "# %-20s: %.3lf / %.3lf sec (sys / user)\n", "cpu time", info->stime, info->utime); process(data, "# %-20s: %ld / %ld (voluntary / involuntary)\n", "context switch", info->vctxsw, info->ictxsw); process(data, "# %-20s: %ld KB\n", "max rss", info->maxrss); process(data, "# %-20s: %ld / %ld (major / minor)\n", "page fault", info->major_fault, info->minor_fault); process(data, "# %-20s: %ld / %ld (read / write)\n", "disk iops", info->rblock, info->wblock); } process(data, "\n"); } static void print_task(struct uftrace_data *handle, struct uftrace_task *t) { char flags[6] = " "; struct stat stbuf; char *filename = NULL; struct uftrace_sess_ref *sref = &t->sref; xasprintf(&filename, "%s/%d.dat", handle->dirname, t->tid); if (stat(filename, &stbuf) < 0) stbuf.st_size = 0; if (t->tid == t->pid) flags[0] = 'F'; /* FORK */ while (sref != NULL) { if (sref->sess->tid == t->tid) { flags[1] = 'S'; /* SESSION */ break; } sref = sref->next; } /* TIMESTAMP */ pr_out(" %13lu.%09lu ", t->time.stamp / NSEC_PER_SEC, t->time.stamp % NSEC_PER_SEC); /* FLAGS TID COMM */ pr_out(" %s [%6d] %-16s ", flags, t->tid, t->comm); /* DATA SIZE */ if (stbuf.st_size) { uint64_t size_kb = stbuf.st_size / 1024; uint64_t size_rem = size_kb % 1024; pr_out(" %5"PRIu64".%03"PRIu64" MB\n", size_kb / 1024, size_rem >= 1000 ? 999 : size_rem); } else pr_out("\n"); free(filename); } static void print_task_info(struct uftrace_data *handle) { struct rb_node *n; struct uftrace_task *t; pr_out("#%23s %5s %8s %-16s %s\n", "TIMESTAMP ", "FLAGS", " TID ", "TASK", "DATA SIZE"); n = rb_first(&handle->sessions.tasks); while (n != NULL) { t = rb_entry(n, struct uftrace_task, node); n = rb_next(n); print_task(handle, t); } } int command_info(int argc, char *argv[], struct opts *opts) { int ret; struct uftrace_data handle; ret = open_info_file(opts, &handle); if (ret < 0) { pr_warn("cannot open record data: %s: %m\n", opts->dirname); return -1; } if (opts->print_symtab) { struct symtabs symtabs = { .dirname = opts->dirname, .filename = opts->exename, .flags = SYMTAB_FL_USE_SYMFILE | SYMTAB_FL_DEMANGLE, }; struct uftrace_module *mod; if (!opts->exename) { pr_use("Usage: uftrace info --symbols [COMMAND]\n"); return -1; } mod = load_module_symtab(&symtabs, symtabs.filename); if (mod == NULL) goto out; print_symtab(&mod->symtab); unload_module_symtabs(); goto out; } fstack_setup_task(opts->tid, &handle); if (opts->show_task) { /* ignore errors */ read_task_txt_file(&handle.sessions, opts->dirname, false, false, false); if (handle.hdr.feat_mask & PERF_EVENT) { if (setup_perf_data(&handle) == 0) update_perf_task_comm(&handle); } print_task_info(&handle); goto out; } process_uftrace_info(&handle, opts, print_info, NULL); out: close_data_file(opts, &handle); return 0; } uftrace-0.9.4/cmds/live.c000066400000000000000000000064701362052523300152160ustar00rootroot00000000000000#include #include #include #include #include #include #include "uftrace.h" #include "utils/utils.h" #include "utils/fstack.h" #include "utils/kernel.h" #include "libmcount/mcount.h" static char *tmp_dirname; static void cleanup_tempdir(void) { if (!tmp_dirname) return; remove_directory(tmp_dirname); tmp_dirname = NULL; } static void reset_live_opts(struct opts *opts) { /* this is needed to set display_depth at replay */ live_disabled = opts->disabled; /* * These options are handled in record and no need to do it in * replay again. */ free(opts->filter); opts->filter = NULL; free(opts->caller); opts->caller = NULL; opts->depth = MCOUNT_DEFAULT_DEPTH; opts->disabled = false; opts->no_event = false; } static void sigsegv_handler(int sig) { pr_warn("Segmentation fault\n"); cleanup_tempdir(); raise(sig); } static bool can_skip_replay(struct opts *opts, int record_result) { if (opts->nop) return true; return false; } static void setup_child_environ(struct opts *opts) { char *old_preload, *libpath; #ifdef INSTALL_LIB_PATH if (!opts->lib_path) { char *envbuf = getenv("LD_LIBRARY_PATH"); if (envbuf) { envbuf = xstrdup(envbuf); libpath = strjoin(envbuf, INSTALL_LIB_PATH, ":"); setenv("LD_LIBRARY_PATH", libpath, 1); free(libpath); } else { setenv("LD_LIBRARY_PATH", INSTALL_LIB_PATH, 1); } } #endif libpath = get_libmcount_path(opts); if (libpath == NULL) pr_err_ns("cannot found libmcount.so\n"); old_preload = getenv("LD_PRELOAD"); if (old_preload) { size_t len = strlen(libpath) + strlen(old_preload) + 2; char *preload = xmalloc(len); snprintf(preload, len, "%s:%s", libpath, old_preload); setenv("LD_PRELOAD", preload, 1); free(preload); } else setenv("LD_PRELOAD", libpath, 1); free(libpath); } int command_live(int argc, char *argv[], struct opts *opts) { char template[32] = "/tmp/uftrace-live-XXXXXX"; int fd; struct sigaction sa = { .sa_flags = SA_RESETHAND, }; int ret; if (!opts->record) { tmp_dirname = template; umask(022); fd = mkstemp(template); if (fd < 0) { if (errno != EPERM) pr_err("cannot access to /tmp"); fd = mkstemp(template + sizeof("/tmp/") - 1); if (fd < 0) pr_err("cannot create temp name"); tmp_dirname += sizeof("/tmp/") - 1; } close(fd); unlink(tmp_dirname); atexit(cleanup_tempdir); sa.sa_handler = sigsegv_handler; sigfillset(&sa.sa_mask); sigaction(SIGSEGV, &sa, NULL); opts->dirname = tmp_dirname; } if (opts->list_event) { if (geteuid() == 0) list_kernel_events(); if (fork() == 0) { setup_child_environ(opts); setenv("UFTRACE_LIST_EVENT", "1", 1); execv(opts->exename, argv); abort(); } return 0; } ret = command_record(argc, argv, opts); if (!can_skip_replay(opts, ret)) { int ret2; reset_live_opts(opts); if (opts->use_pager) start_pager(setup_pager()); pr_dbg("live-record finished.. \n"); if (opts->report) { pr_out("#\n# uftrace report\n#\n"); ret2 = command_report(argc, argv, opts); if (ret == UFTRACE_EXIT_SUCCESS) ret = ret2; pr_out("\n#\n# uftrace replay\n#\n"); } pr_dbg("start live-replaying...\n"); ret2 = command_replay(argc, argv, opts); if (ret == UFTRACE_EXIT_SUCCESS) ret = ret2; } cleanup_tempdir(); return ret; } uftrace-0.9.4/cmds/record.c000066400000000000000000001366551362052523300155460ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "uftrace.h" #include "libmcount/mcount.h" #include "utils/utils.h" #include "utils/symbol.h" #include "utils/list.h" #include "utils/filter.h" #include "utils/kernel.h" #include "utils/perf.h" #ifndef EFD_SEMAPHORE # define EFD_SEMAPHORE (1 << 0) #endif #define SHMEM_NAME_SIZE (64 - (int)sizeof(struct list_head)) struct shmem_list { struct list_head list; char id[SHMEM_NAME_SIZE]; }; static LIST_HEAD(shmem_list_head); static LIST_HEAD(shmem_need_unlink); struct buf_list { struct list_head list; int tid; void *shmem_buf; }; static LIST_HEAD(buf_free_list); static LIST_HEAD(buf_write_list); /* currently active writers */ static LIST_HEAD(writer_list); static pthread_mutex_t free_list_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_mutex_t write_list_lock = PTHREAD_MUTEX_INITIALIZER; static bool buf_done; static int thread_ctl[2]; static bool has_perf_event; static bool has_sched_event; static bool finish_received; static bool can_use_fast_libmcount(struct opts *opts) { if (debug) return false; if (opts->depth != MCOUNT_DEFAULT_DEPTH) return false; if (getenv("UFTRACE_FILTER") || getenv("UFTRACE_TRIGGER") || getenv("UFTRACE_ARGUMENT") || getenv("UFTRACE_RETVAL") || getenv("UFTRACE_PATCH") || getenv("UFTRACE_SCRIPT") || getenv("UFTRACE_AUTO_ARGS") || getenv("UFTRACE_WATCH") || getenv("UFTRACE_CALLER") || getenv("UFTRACE_SIGNAL")) return false; return true; } static char *build_debug_domain_string(void) { int i, d; static char domain[2*DBG_DOMAIN_MAX + 1]; for (i = 0, d = 0; d < DBG_DOMAIN_MAX; d++) { if (dbg_domain[d]) { domain[i++] = DBG_DOMAIN_STR[d]; domain[i++] = dbg_domain[d] + '0'; } } domain[i] = '\0'; return domain; } char * get_libmcount_path(struct opts *opts) { char *libmcount, *lib = xmalloc(PATH_MAX); bool must_use_multi_thread = has_dependency(opts->exename, "libpthread.so.0"); if (opts->nop) { libmcount = "libmcount-nop.so"; } else if (opts->libmcount_single && !must_use_multi_thread) { if (can_use_fast_libmcount(opts)) libmcount = "libmcount-fast-single.so"; else libmcount = "libmcount-single.so"; } else { if (must_use_multi_thread && opts->libmcount_single) pr_dbg("--libmcount-single is off because it uses pthread\n"); if (can_use_fast_libmcount(opts)) libmcount = "libmcount-fast.so"; else libmcount = "libmcount.so"; } if (opts->lib_path) { snprintf(lib, PATH_MAX, "%s/libmcount/%s", opts->lib_path, libmcount); if (access(lib, F_OK) == 0) { return lib; } else if (errno == ENOENT) { snprintf(lib, PATH_MAX, "%s/%s", opts->lib_path, libmcount); if (access(lib, F_OK) == 0) return lib; } free(lib); return NULL; } #ifdef INSTALL_LIB_PATH /* try first to load libmcount from the installation path */ snprintf(lib, PATH_MAX, "%s/%s", INSTALL_LIB_PATH, libmcount); if (access(lib, F_OK) == 0) return lib; #endif strncpy(lib, libmcount, PATH_MAX); return lib; } void put_libmcount_path(char *libpath) { free(libpath); } static void setup_child_environ(struct opts *opts, int argc, char *argv[]) { char buf[PATH_MAX]; char *old_preload, *libpath; #ifdef INSTALL_LIB_PATH if (!opts->lib_path) { char *envbuf = getenv("LD_LIBRARY_PATH"); if (envbuf) { envbuf = xstrdup(envbuf); libpath = strjoin(envbuf, INSTALL_LIB_PATH, ":"); setenv("LD_LIBRARY_PATH", libpath, 1); free(libpath); } else { setenv("LD_LIBRARY_PATH", INSTALL_LIB_PATH, 1); } } #endif if (opts->filter) { char *filter_str = uftrace_clear_kernel(opts->filter); if (filter_str) { setenv("UFTRACE_FILTER", filter_str, 1); free(filter_str); } } if (opts->trigger) { char *trigger_str = uftrace_clear_kernel(opts->trigger); if (trigger_str) { setenv("UFTRACE_TRIGGER", trigger_str, 1); free(trigger_str); } } if (opts->args) { char *arg_str = uftrace_clear_kernel(opts->args); if (arg_str) { setenv("UFTRACE_ARGUMENT", arg_str, 1); free(arg_str); } } if (opts->retval) { char *retval_str = uftrace_clear_kernel(opts->retval); if (retval_str) { setenv("UFTRACE_RETVAL", retval_str, 1); free(retval_str); } } if (opts->auto_args) setenv("UFTRACE_AUTO_ARGS", "1", 1); if (opts->patch) { char *patch_str = uftrace_clear_kernel(opts->patch); if (patch_str) { setenv("UFTRACE_PATCH", patch_str, 1); free(patch_str); } if (opts->size_filter) { snprintf(buf, sizeof(buf), "%d", opts->size_filter); setenv("UFTRACE_PATCH_SIZE", buf, 1); } } if (opts->event) { char *event_str = uftrace_clear_kernel(opts->event); if (event_str) { setenv("UFTRACE_EVENT", event_str, 1); free(event_str); } } if (opts->watch) setenv("UFTRACE_WATCH", opts->watch, 1); if (opts->depth != OPT_DEPTH_DEFAULT) { snprintf(buf, sizeof(buf), "%d", opts->depth); setenv("UFTRACE_DEPTH", buf, 1); } if (opts->max_stack != OPT_RSTACK_DEFAULT) { snprintf(buf, sizeof(buf), "%d", opts->max_stack); setenv("UFTRACE_MAX_STACK", buf, 1); } if (opts->threshold) { snprintf(buf, sizeof(buf), "%"PRIu64, opts->threshold); setenv("UFTRACE_THRESHOLD", buf, 1); } if (opts->caller) { char *caller_str = uftrace_clear_kernel(opts->caller); if (caller_str) { setenv("UFTRACE_CALLER", caller_str, 1); free(caller_str); } } if (opts->libcall) { setenv("UFTRACE_PLTHOOK", "1", 1); if (opts->want_bind_not) { /* do not update GOT/PLT after resolving symbols */ setenv("LD_BIND_NOT", "1", 1); } if (opts->nest_libcall) setenv("UFTRACE_NEST_LIBCALL", "1", 1); } if (strcmp(opts->dirname, UFTRACE_DIR_NAME)) setenv("UFTRACE_DIR", opts->dirname, 1); if (opts->bufsize != SHMEM_BUFFER_SIZE) { snprintf(buf, sizeof(buf), "%lu", opts->bufsize); setenv("UFTRACE_BUFFER", buf, 1); } if (opts->logfile) { snprintf(buf, sizeof(buf), "%d", fileno(logfp)); setenv("UFTRACE_LOGFD", buf, 1); } setenv("UFTRACE_SHMEM", "1", 1); if (debug) { snprintf(buf, sizeof(buf), "%d", debug); setenv("UFTRACE_DEBUG", buf, 1); setenv("UFTRACE_DEBUG_DOMAIN", build_debug_domain_string(), 1); } if (opts->disabled) setenv("UFTRACE_DISABLED", "1", 1); if (log_color == COLOR_ON) { snprintf(buf, sizeof(buf), "%d", log_color); setenv("UFTRACE_COLOR", buf, 1); } snprintf(buf, sizeof(buf), "%d", demangler); setenv("UFTRACE_DEMANGLE", buf, 1); if ((opts->kernel || has_kernel_event(opts->event)) && check_kernel_pid_filter()) setenv("UFTRACE_KERNEL_PID_UPDATE", "1", 1); if (opts->script_file) setenv("UFTRACE_SCRIPT", opts->script_file, 1); if (opts->patt_type != PATT_REGEX) setenv("UFTRACE_PATTERN", get_filter_pattern(opts->patt_type), 1); if (opts->sig_trigger) setenv("UFTRACE_SIGNAL", opts->sig_trigger, 1); if (opts->srcline) setenv("UFTRACE_SRCLINE", "1", 1); if (argc > 0) { char *args = NULL; int i; for (i = 0; i < argc; i++) args = strjoin(args, argv[i], "\n"); setenv("UFTRACE_ARGS", args, 1); free(args); } /* * ----- end of option processing ----- */ libpath = get_libmcount_path(opts); if (libpath == NULL) pr_err_ns("cannot found libmcount.so\n"); pr_dbg("using %s library for tracing\n", libpath); old_preload = getenv("LD_PRELOAD"); if (old_preload) { size_t len = strlen(libpath) + strlen(old_preload) + 2; char *preload = xmalloc(len); snprintf(preload, len, "%s:%s", libpath, old_preload); setenv("LD_PRELOAD", preload, 1); free(preload); } else setenv("LD_PRELOAD", libpath, 1); put_libmcount_path(libpath); setenv("XRAY_OPTIONS", "patch_premain=false", 1); setenv("GLIBC_TUNABLES", "glibc.cpu.hwcaps=-IBT,-SHSTK", 1); } static uint64_t calc_feat_mask(struct opts *opts) { uint64_t features = 0; char *buf = NULL; glob_t g; /* mcount code creates task and sid-XXX.map files */ features |= TASK_SESSION; /* symbol file saves relative address */ features |= SYM_REL_ADDR; /* save mcount_max_stack */ features |= MAX_STACK; /* provide automatic argument/return value spec */ features |= AUTO_ARGS; if (has_perf_event) features |= PERF_EVENT; if (opts->libcall) features |= PLTHOOK; if (opts->kernel) features |= KERNEL; if (opts->args || opts->auto_args) features |= ARGUMENT; if (opts->retval || opts->auto_args) features |= RETVAL; if (opts->event) features |= EVENT; xasprintf(&buf, "%s/*.dbg", opts->dirname); if (glob(buf, GLOB_NOSORT, NULL, &g) != GLOB_NOMATCH) features |= DEBUG_INFO; globfree(&g); free(buf); return features; } static int fill_file_header(struct opts *opts, int status, struct rusage *rusage, char *elapsed_time) { int fd, efd; int ret = -1; char *filename = NULL; struct uftrace_file_header hdr; char elf_ident[EI_NIDENT]; xasprintf(&filename, "%s/info", opts->dirname); pr_dbg3("fill header (metadata) info in %s\n", filename); fd = open(filename, O_WRONLY | O_CREAT| O_TRUNC, 0644); if (fd < 0) pr_err("cannot open info file"); efd = open(opts->exename, O_RDONLY); if (efd < 0) goto close_fd; if (read(efd, elf_ident, sizeof(elf_ident)) < 0) goto close_efd; strncpy(hdr.magic, UFTRACE_MAGIC_STR, UFTRACE_MAGIC_LEN); hdr.version = UFTRACE_FILE_VERSION; hdr.header_size = sizeof(hdr); hdr.endian = elf_ident[EI_DATA]; hdr.class = elf_ident[EI_CLASS]; hdr.feat_mask = calc_feat_mask(opts); hdr.info_mask = 0; hdr.max_stack = opts->max_stack; hdr.unused1 = 0; hdr.unused2 = 0; if (write(fd, &hdr, sizeof(hdr)) != (int)sizeof(hdr)) pr_err("writing header info failed"); fill_uftrace_info(&hdr.info_mask, fd, opts, status, rusage, elapsed_time); try_write: ret = pwrite(fd, &hdr, sizeof(hdr), 0); if (ret != (int)sizeof(hdr)) { static int retry = 0; if (ret > 0 && retry++ < 3) goto try_write; pr_dbg("writing header info failed.\n"); goto close_efd; } ret = 0; close_efd: close(efd); close_fd: close(fd); free(filename); return ret; } /* size including NUL at the end */ #define MSG_ID_SIZE 36 static void parse_msg_id(char *id, uint64_t *sid, int *tid, int *seq) { uint64_t _sid; unsigned _tid; unsigned _seq; /* * parse message id of "/uftrace-SESSION-TID-SEQ". */ if (sscanf(id, "/uftrace-%016"SCNx64"-%u-%03u", &_sid, &_tid, &_seq) != 3) pr_err("parse msg id failed"); if (sid) *sid = _sid; if (tid) *tid = _tid; if (seq) *seq = _seq; } static char *make_disk_name(const char *dirname, int tid) { char *filename = NULL; xasprintf(&filename, "%s/%d.dat", dirname, tid); return filename; } static void write_buffer_file(const char *dirname, struct buf_list *buf) { int fd; char *filename; struct mcount_shmem_buffer *shmbuf = buf->shmem_buf; filename = make_disk_name(dirname, buf->tid); fd = open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644); if (fd < 0) pr_err("open disk file"); if (write_all(fd, shmbuf->data, shmbuf->size) < 0) pr_err("write shmem buffer"); close(fd); free(filename); } static void write_buffer(struct buf_list *buf, struct opts *opts, int sock) { struct mcount_shmem_buffer *shmbuf = buf->shmem_buf; if (!opts->host) write_buffer_file(opts->dirname, buf); else send_trace_data(sock, buf->tid, shmbuf->data, shmbuf->size); shmbuf->size = 0; } struct writer_arg { struct list_head list; struct list_head bufs; struct opts *opts; struct uftrace_kernel_writer *kern; struct uftrace_perf_writer *perf; int sock; int idx; int tid; int nr_cpu; int cpus[]; }; static void write_buf_list(struct list_head *buf_head, struct opts *opts, struct writer_arg *warg) { struct buf_list *buf; list_for_each_entry(buf, buf_head, list) { struct mcount_shmem_buffer *shmbuf = buf->shmem_buf; write_buffer(buf, opts, warg->sock); /* * Now it has consumed all contents in the shmem buffer, * make it so that mcount can reuse it. * This is paired with get_new_shmem_buffer(). */ __sync_synchronize(); shmbuf->flag = SHMEM_FL_WRITTEN; munmap(shmbuf, opts->bufsize); buf->shmem_buf = NULL; } pthread_mutex_lock(&free_list_lock); while (!list_empty(buf_head)) { struct list_head *l = buf_head->next; list_move(l, &buf_free_list); } pthread_mutex_unlock(&free_list_lock); } static int setup_pollfd(struct pollfd **pollfd, struct writer_arg *warg, bool setup_perf, bool setup_kernel) { int nr_poll = 1; struct pollfd *p; int i; if (setup_perf) nr_poll += warg->nr_cpu; if (setup_kernel) nr_poll += warg->nr_cpu; p = xcalloc(nr_poll, sizeof(*p)); p[0].fd = thread_ctl[0]; p[0].events = POLLIN; nr_poll = 1; if (setup_perf) { for (i = 0; i < warg->nr_cpu; i++) { p[i + nr_poll].fd = warg->perf->event_fd[warg->cpus[i]]; p[i + nr_poll].events = POLLIN; } nr_poll += warg->nr_cpu; } if (setup_kernel) { for (i = 0; i < warg->nr_cpu; i++) { p[i + nr_poll].fd = warg->kern->traces[warg->cpus[i]]; p[i + nr_poll].events = POLLIN; } nr_poll += warg->nr_cpu; } *pollfd = p; return nr_poll; } static bool handle_pollfd(struct pollfd *pollfd, struct writer_arg *warg, bool trace_task, bool trace_perf, bool trace_kernel, int timeout) { int start = trace_task ? 0 : 1; int nr_poll = trace_task ? 1 : 0; bool check_task = false; int i; if (trace_perf) nr_poll += warg->nr_cpu; if (trace_kernel) nr_poll += warg->nr_cpu; if (poll(&pollfd[start], nr_poll, timeout) < 0) return false; for (i = start; i < nr_poll; i++) { if (!(pollfd[i].revents & POLLIN)) continue; if (i == 0) check_task = true; else if (trace_perf && i < (warg->nr_cpu + 1)) { record_perf_data(warg->perf, warg->cpus[i - 1], warg->sock); } else if (trace_kernel) { int idx = i - (nr_poll - warg->nr_cpu); record_kernel_trace_pipe(warg->kern, warg->cpus[idx], warg->sock); } } return check_task; } static void finish_pollfd(struct pollfd *pollfd) { free(pollfd); } void *writer_thread(void *arg) { struct buf_list *buf, *pos; struct writer_arg *warg = arg; struct opts *opts = warg->opts; struct pollfd *pollfd; int i, dummy; sigset_t sigset; pthread_setname_np(pthread_self(), "WriterThread"); if (opts->rt_prio) { struct sched_param param = { .sched_priority = opts->rt_prio, }; if (sched_setscheduler(0, SCHED_FIFO, ¶m) < 0) pr_warn("set scheduling param failed\n"); } sigfillset(&sigset); pthread_sigmask(SIG_BLOCK, &sigset, NULL); setup_pollfd(&pollfd, warg, has_perf_event, opts->kernel); pr_dbg2("start writer thread %d\n", warg->idx); while (!buf_done) { LIST_HEAD(head); bool check_list = false; check_list = handle_pollfd(pollfd, warg, true, has_perf_event, opts->kernel, 1000); if (!check_list) continue; if (read(thread_ctl[0], &dummy, sizeof(dummy)) < 0) { if (errno == EAGAIN || errno == EINTR) continue; /* other errors are problematic */ break; } pthread_mutex_lock(&write_list_lock); if (!list_empty(&buf_write_list)) { /* pick first unhandled buf */ buf = list_first_entry(&buf_write_list, struct buf_list, list); list_move(&buf->list, &head); warg->tid = buf->tid; list_add(&warg->list, &writer_list); } list_for_each_entry_safe(buf, pos, &buf_write_list, list) { /* list may have multiple buf for this task */ if (buf->tid == warg->tid) list_move_tail(&buf->list, &head); } pthread_mutex_unlock(&write_list_lock); while (!list_empty(&head)) { write_buf_list(&head, opts, warg); pthread_mutex_lock(&write_list_lock); /* check someone sends bufs for me directly */ list_splice_tail_init(&warg->bufs, &head); if (list_empty(&head)) { /* I'm done with this tid */ warg->tid = -1; list_del_init(&warg->list); } pthread_mutex_unlock(&write_list_lock); if (!has_perf_event && !opts->kernel) continue; handle_pollfd(pollfd, warg, false, has_perf_event, opts->kernel, 0); } } pr_dbg2("stop writer thread %d\n", warg->idx); if (has_perf_event) { for (i = 0; i < warg->nr_cpu; i++) record_perf_data(warg->perf, warg->cpus[i], warg->sock); } finish_pollfd(pollfd); free(warg); return NULL; } static struct buf_list *make_write_buffer(void) { struct buf_list *buf; buf = malloc(sizeof(*buf)); if (buf == NULL) return NULL; INIT_LIST_HEAD(&buf->list); return buf; } static void copy_to_buffer(struct mcount_shmem_buffer *shm, char *sess_id) { struct buf_list *buf = NULL; struct writer_arg *writer; pthread_mutex_lock(&free_list_lock); if (!list_empty(&buf_free_list)) { buf = list_first_entry(&buf_free_list, struct buf_list, list); list_del(&buf->list); } pthread_mutex_unlock(&free_list_lock); if (buf == NULL) { buf = make_write_buffer(); if (buf == NULL) pr_err_ns("not enough memory!\n"); pr_dbg3("make a new write buffer\n"); } buf->shmem_buf = shm; parse_msg_id(sess_id, NULL, &buf->tid, NULL); pthread_mutex_lock(&write_list_lock); /* check some writers work for this tid */ list_for_each_entry(writer, &writer_list, list) { if (buf->tid == writer->tid) { /* if so, pass the buf directly */ list_add_tail(&buf->list, &writer->bufs); break; } } if (list_no_entry(writer, &writer_list, list)) { int kick = 1; /* no writer is dealing with the tid */ list_add_tail(&buf->list, &buf_write_list); if (write(thread_ctl[1], &kick, sizeof(kick)) < 0 && !buf_done) pr_err("copying to buffer failed"); } pthread_mutex_unlock(&write_list_lock); } static void record_mmap_file(const char *dirname, char *sess_id, int bufsize) { int fd; struct shmem_list *sl; struct mcount_shmem_buffer *shmem_buf; /* write (append) it to disk */ fd = shm_open(sess_id, O_RDWR, 0600); if (fd < 0) { pr_dbg("open shmem buffer failed: %s: %m\n", sess_id); return; } shmem_buf = mmap(NULL, bufsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (shmem_buf == MAP_FAILED) pr_err("mmap shmem buffer"); close(fd); if (shmem_buf->flag & SHMEM_FL_RECORDING) { if (shmem_buf->flag & SHMEM_FL_NEW) { bool found = false; if (!list_empty(&shmem_need_unlink)) { sl = list_last_entry(&shmem_need_unlink, struct shmem_list, list); /* length of "uftrace--" is 25 */ if (!strncmp(sl->id, sess_id, 25)) found = true; } if (!found) { sl = xmalloc(sizeof(*sl)); memcpy(sl->id, sess_id, sizeof(sl->id)); /* link to shmem_list */ list_add_tail(&sl->list, &shmem_need_unlink); } } if (shmem_buf->size) { /* shmem_buf will be unmapped */ copy_to_buffer(shmem_buf, sess_id); return; } } munmap(shmem_buf, bufsize); } static void stop_all_writers(void) { buf_done = true; close(thread_ctl[1]); thread_ctl[1] = -1; } static void record_remaining_buffer(struct opts *opts, int sock) { struct buf_list *buf; /* called after all writers gone, no lock is needed */ while (!list_empty(&buf_write_list)) { buf = list_first_entry(&buf_write_list, struct buf_list, list); write_buffer(buf, opts, sock); munmap(buf->shmem_buf, opts->bufsize); list_del(&buf->list); free(buf); } while (!list_empty(&buf_free_list)) { buf = list_first_entry(&buf_free_list, struct buf_list, list); list_del(&buf->list); free(buf); } } static void flush_shmem_list(const char *dirname, int bufsize) { struct shmem_list *sl, *tmp; /* flush remaining list (due to abnormal termination) */ list_for_each_entry_safe(sl, tmp, &shmem_list_head, list) { pr_dbg("flushing %s\n", sl->id); list_del(&sl->list); record_mmap_file(dirname, sl->id, bufsize); free(sl); } } static char shmem_session[20]; static int filter_shmem(const struct dirent *de) { /* compare session ID after the "uftrace-" part */ return !memcmp(&de->d_name[8], shmem_session, 16); } static void unlink_shmem_list(void) { struct shmem_list *sl, *tmp; /* unlink shmem list (not used anymore) */ list_for_each_entry_safe(sl, tmp, &shmem_need_unlink, list) { char sid[128]; struct dirent **shmem_bufs; int i, num; list_del(&sl->list); sscanf(sl->id, "/uftrace-%[^-]-%*d-%*d", shmem_session); pr_dbg2("unlink for session: %s\n", shmem_session); num = scandir("/dev/shm/", &shmem_bufs, filter_shmem, alphasort); for (i = 0; i < num; i++) { sid[0] = '/'; memcpy(&sid[1], shmem_bufs[i]->d_name, MSG_ID_SIZE); pr_dbg3("unlink %s\n", sid); shm_unlink(sid); free(shmem_bufs[i]); } free(shmem_bufs); free(sl); } } static void flush_old_shmem(const char *dirname, int tid, int bufsize) { struct shmem_list *sl; /* flush remaining list (due to abnormal termination) */ list_for_each_entry(sl, &shmem_list_head, list) { int sl_tid; sscanf(sl->id, "/uftrace-%*x-%d-%*d", &sl_tid); if (tid == sl_tid) { pr_dbg3("flushing %s\n", sl->id); list_del(&sl->list); record_mmap_file(dirname, sl->id, bufsize); free(sl); return; } } } static int shmem_lost_count; struct tid_list { struct list_head list; int pid; int tid; bool exited; }; static LIST_HEAD(tid_list_head); static bool child_exited; static void sigchld_handler(int sig, siginfo_t *sainfo, void *context) { int tid = sainfo->si_pid; struct tid_list *tl; list_for_each_entry(tl, &tid_list_head, list) { if (tl->tid == tid) { tl->exited = true; break; } } child_exited = true; } static void add_tid_list(int pid, int tid) { struct tid_list *tl; tl = xmalloc(sizeof(*tl)); tl->pid = pid; tl->tid = tid; tl->exited = false; /* link to tid_list */ list_add(&tl->list, &tid_list_head); } static void free_tid_list(void) { struct tid_list *tl, *tmp; list_for_each_entry_safe(tl, tmp, &tid_list_head, list) { list_del(&tl->list); free(tl); } } static bool check_tid_list(void) { struct tid_list *tl; char buf[128]; list_for_each_entry(tl, &tid_list_head, list) { int fd, len; char state; char line[PATH_MAX]; if (tl->exited || tl->tid < 0) continue; snprintf(buf, sizeof(buf), "/proc/%d/stat", tl->tid); fd = open(buf, O_RDONLY); if (fd < 0) { tl->exited = true; continue; } len = read(fd, line, sizeof(line) - 1); if (len < 0) { tl->exited = true; close(fd); continue; } line[len] = '\0'; sscanf(line, "%*d %*s %c", &state); if (state == 'Z') tl->exited = true; close(fd); } list_for_each_entry(tl, &tid_list_head, list) { if (!tl->exited) return false; } pr_dbg2("all process/thread exited\n"); child_exited = true; return true; } struct dlopen_list { struct list_head list; char *libname; }; static LIST_HEAD(dlopen_libs); static void read_record_mmap(int pfd, const char *dirname, int bufsize) { char buf[128]; struct shmem_list *sl, *tmp; struct tid_list *tl, *pos; struct uftrace_msg msg; struct uftrace_msg_task tmsg; struct uftrace_msg_sess sess; struct uftrace_msg_dlopen dmsg; struct dlopen_list *dlib; char *exename; int lost; if (read_all(pfd, &msg, sizeof(msg)) < 0) pr_err("reading pipe failed:"); if (msg.magic != UFTRACE_MSG_MAGIC) pr_err_ns("invalid message received: %x\n", msg.magic); switch (msg.type) { case UFTRACE_MSG_REC_START: if (msg.len >= SHMEM_NAME_SIZE) pr_err_ns("invalid message length\n"); sl = xmalloc(sizeof(*sl)); if (read_all(pfd, sl->id, msg.len) < 0) pr_err("reading pipe failed"); sl->id[msg.len] = '\0'; pr_dbg2("MSG START: %s\n", sl->id); /* link to shmem_list */ list_add_tail(&sl->list, &shmem_list_head); break; case UFTRACE_MSG_REC_END: if (msg.len >= SHMEM_NAME_SIZE) pr_err_ns("invalid message length\n"); if (read_all(pfd, buf, msg.len) < 0) pr_err("reading pipe failed"); buf[msg.len] = '\0'; pr_dbg2("MSG END : %s\n", buf); /* remove from shmem_list */ list_for_each_entry_safe(sl, tmp, &shmem_list_head, list) { if (!memcmp(sl->id, buf, msg.len)) { list_del(&sl->list); free(sl); break; } } record_mmap_file(dirname, buf, bufsize); break; case UFTRACE_MSG_TASK_START: if (msg.len != sizeof(tmsg)) pr_err_ns("invalid message length\n"); if (read_all(pfd, &tmsg, sizeof(tmsg)) < 0) pr_err("reading pipe failed"); pr_dbg2("MSG TASK_START : %d/%d\n", tmsg.pid, tmsg.tid); /* check existing tid (due to exec) */ list_for_each_entry(pos, &tid_list_head, list) { if (pos->tid == tmsg.tid) { flush_old_shmem(dirname, tmsg.tid, bufsize); break; } } if (list_no_entry(pos, &tid_list_head, list)) add_tid_list(tmsg.pid, tmsg.tid); write_task_info(dirname, &tmsg); break; case UFTRACE_MSG_TASK_END: if (msg.len != sizeof(tmsg)) pr_err_ns("invalid message length\n"); if (read_all(pfd, &tmsg, sizeof(tmsg)) < 0) pr_err("reading pipe failed"); pr_dbg2("MSG TASK_END : %d/%d\n", tmsg.pid, tmsg.tid); /* mark test exited */ list_for_each_entry(pos, &tid_list_head, list) { if (pos->tid == tmsg.tid) { pos->exited = true; break; } } break; case UFTRACE_MSG_FORK_START: if (msg.len != sizeof(tmsg)) pr_err_ns("invalid message length\n"); if (read_all(pfd, &tmsg, sizeof(tmsg)) < 0) pr_err("reading pipe failed"); pr_dbg2("MSG FORK1: %d/%d\n", tmsg.pid, -1); add_tid_list(tmsg.pid, -1); break; case UFTRACE_MSG_FORK_END: if (msg.len != sizeof(tmsg)) pr_err_ns("invalid message length\n"); if (read_all(pfd, &tmsg, sizeof(tmsg)) < 0) pr_err("reading pipe failed"); list_for_each_entry(tl, &tid_list_head, list) { if (tl->pid == tmsg.pid && tl->tid == -1) break; } if (list_no_entry(tl, &tid_list_head, list)) { /* * daemon process has no guarantee that having parent * pid of 1 anymore due to the systemd, just pick a * first task which has tid of -1. */ list_for_each_entry(tl, &tid_list_head, list) { if (tl->tid == -1) { pr_dbg3("override parent of daemon to %d\n", tl->pid); tmsg.pid = tl->pid; break; } } } if (list_no_entry(tl, &tid_list_head, list)) pr_err("cannot find fork pid: %d\n", tmsg.pid); tl->tid = tmsg.tid; pr_dbg2("MSG FORK2: %d/%d\n", tl->pid, tl->tid); write_fork_info(dirname, &tmsg); break; case UFTRACE_MSG_SESSION: if (msg.len < sizeof(sess)) pr_err_ns("invalid message length\n"); if (read_all(pfd, &sess, sizeof(sess)) < 0) pr_err("reading pipe failed"); exename = xmalloc(sess.namelen + 1); if (read_all(pfd, exename, sess.namelen) < 0) pr_err("reading pipe failed"); exename[sess.namelen] = '\0'; memcpy(buf, sess.sid, 16); buf[16] = '\0'; pr_dbg2("MSG SESSION: %d: %s (%s)\n", sess.task.tid, exename, buf); write_session_info(dirname, &sess, exename); free(exename); break; case UFTRACE_MSG_LOST: if (msg.len < sizeof(lost)) pr_err_ns("invalid message length\n"); if (read_all(pfd, &lost, sizeof(lost)) < 0) pr_err("reading pipe failed"); shmem_lost_count += lost; break; case UFTRACE_MSG_DLOPEN: if (msg.len < sizeof(dmsg)) pr_err_ns("invalid message length\n"); if (read_all(pfd, &dmsg, sizeof(dmsg)) < 0) pr_err("reading pipe failed"); exename = xmalloc(dmsg.namelen + 1); if (read_all(pfd, exename, dmsg.namelen) < 0) pr_err("reading pipe failed"); exename[dmsg.namelen] = '\0'; pr_dbg2("MSG DLOPEN: %d: %#lx %s\n", dmsg.task.tid, dmsg.base_addr, exename); dlib = xmalloc(sizeof(*dlib)); dlib->libname = exename; list_add_tail(&dlib->list, &dlopen_libs); write_dlopen_info(dirname, &dmsg, exename); /* exename will be freed with the dlib */ break; case UFTRACE_MSG_FINISH: pr_dbg2("MSG FINISH\n"); finish_received = true; break; default: pr_warn("Unknown message type: %u\n", msg.type); break; } } static void send_task_file(int sock, const char *dirname) { send_trace_metadata(sock, dirname, "task.txt"); } /* find "sid-XXX.map" file */ static int filter_map(const struct dirent *de) { size_t len = strlen(de->d_name); return !strncmp("sid-", de->d_name, 4) && !strncmp(".map", de->d_name + len - 4, 4); } static void send_map_files(int sock, const char *dirname) { int i, maps; struct dirent **map_list; maps = scandir(dirname, &map_list, filter_map, alphasort); if (maps < 0) pr_err("cannot scan map files"); for (i = 0; i < maps; i++) { send_trace_metadata(sock, dirname, map_list[i]->d_name); free(map_list[i]); } free(map_list); } /* find "XXX.sym" file */ static int filter_sym(const struct dirent *de) { size_t len = strlen(de->d_name); return !strncmp(".sym", de->d_name + len - 4, 4); } static void send_sym_files(int sock, const char *dirname) { int i, syms; struct dirent **sym_list; syms = scandir(dirname, &sym_list, filter_sym, alphasort); if (syms < 0) pr_err("cannot scan sym files"); for (i = 0; i < syms; i++) { send_trace_metadata(sock, dirname, sym_list[i]->d_name); free(sym_list[i]); } free(sym_list); } /* find "XXX.dbg" file */ static int filter_dbg(const struct dirent *de) { size_t len = strlen(de->d_name); return !strncmp(".dbg", de->d_name + len - 4, 4); } static void send_dbg_files(int sock, const char *dirname) { int i, dbgs; struct dirent **dbg_list; dbgs = scandir(dirname, &dbg_list, filter_dbg, alphasort); if (dbgs < 0) pr_err("cannot scan dbg files"); for (i = 0; i < dbgs; i++) { send_trace_metadata(sock, dirname, dbg_list[i]->d_name); free(dbg_list[i]); } free(dbg_list); } static void send_info_file(int sock, const char *dirname) { int fd; char *filename = NULL; struct uftrace_file_header hdr; struct stat stbuf; void *info; int len; xasprintf(&filename, "%s/info", dirname); fd = open(filename, O_RDONLY); if (fd < 0) pr_err("open info failed"); if (fstat(fd, &stbuf) < 0) pr_err("stat info failed"); if (read_all(fd, &hdr, sizeof(hdr)) < 0) pr_err("read file header failed"); len = stbuf.st_size - sizeof(hdr); info = xmalloc(len); if (read_all(fd, info, len) < 0) pr_err("read info failed"); send_trace_info(sock, &hdr, info, len); close(fd); free(info); free(filename); } static void send_kernel_metadata(int sock, const char *dirname) { send_trace_metadata(sock, dirname, "kernel_header"); send_trace_metadata(sock, dirname, "kallsyms"); } static void send_event_file(int sock, const char *dirname) { char buf[PATH_MAX]; /* kernel events doesn't create the events file */ snprintf(buf, sizeof(buf), "%s/events.txt", dirname); if (access(buf, F_OK) != 0) return; send_trace_metadata(sock, dirname, "events.txt"); } static void send_log_file(int sock, const char *logfile) { if (access(logfile, F_OK) != 0) return; send_trace_metadata(sock, NULL, (char*)logfile); } static void load_session_symbols(struct opts *opts) { struct dirent **map_list; int i, maps; maps = scandir(opts->dirname, &map_list, filter_map, alphasort); if (maps <= 0) { if (maps == 0) errno = ENOENT; pr_err("cannot find map files"); } for (i = 0; i < maps; i++) { struct symtabs symtabs = { .dirname = opts->dirname, .flags = SYMTAB_FL_ADJ_OFFSET, }; char sid[20]; sscanf(map_list[i]->d_name, "sid-%[^.].map", sid); free(map_list[i]); pr_dbg2("reading symbols for session %s\n", sid); read_session_map(opts->dirname, &symtabs, sid); load_module_symtabs(&symtabs); delete_session_map(&symtabs); } free(map_list); } static char *get_child_time(struct timespec *ts1, struct timespec *ts2) { #define SEC_TO_NSEC (1000000000ULL) char *elapsed_time = NULL; uint64_t sec = ts2->tv_sec - ts1->tv_sec; uint64_t nsec = ts2->tv_nsec - ts1->tv_nsec; if (nsec > SEC_TO_NSEC) { nsec += SEC_TO_NSEC; sec--; } xasprintf(&elapsed_time, "%"PRIu64".%09"PRIu64" sec", sec, nsec); return elapsed_time; } static void print_child_time(char *elapsed_time) { pr_out("elapsed time: %20s\n", elapsed_time); } static void print_child_usage(struct rusage *ru) { pr_out(" system time: %6lu.%06lu000 sec\n", ru->ru_stime.tv_sec, ru->ru_stime.tv_usec); pr_out(" user time: %6lu.%06lu000 sec\n", ru->ru_utime.tv_sec, ru->ru_utime.tv_usec); } #define UFTRACE_MSG "Cannot trace '%s': No such executable file.\n" #define MCOUNT_MSG "Can't find '%s' symbol in the '%s'.\n" \ "\tIt seems not to be compiled with -pg or -finstrument-functions flag.\n" \ "\tYou can rebuild your program with it or use -P option for dynamic tracing.\n" #define UFTRACE_ELF_MSG "Cannot trace '%s': Invalid file\n" \ "\tThis file doesn't look like an executable ELF file.\n" \ "\tPlease check whether it's a kind of script or shell functions.\n" #define MACHINE_MSG "Cannot trace '%s': Unsupported machine\n" \ "\tThis machine type (%u) is not supported currently.\n" \ "\tSorry about that!\n" #define ARGUMENT_MSG "uftrace: -A or -R might not work for binaries" \ " with -finstrument-functions\n" #define STATIC_MSG "Cannot trace static binary: %s\n" \ "\tIt seems to be compiled with -static, rebuild the binary without it.\n" #define SCRIPT_MSG "Cannot trace script file: %s\n" \ "\tTo trace binaries run by the script, use --force option.\n" #ifndef EM_AARCH64 # define EM_AARCH64 183 #endif static bool is_regular_executable(const char *pathname) { struct stat sb; if (!stat(pathname, &sb)) { if (S_ISREG(sb.st_mode) && (sb.st_mode & S_IXUSR)) return true; } return false; } static void find_in_path(char **exename) { /* try to find the binary in PATH */ struct strv strv = STRV_INIT; char *env = getenv("PATH"); char *pathname = NULL; char *path; bool found = false; int i; if (!env || *exename[0] == '/') pr_err_ns(UFTRACE_MSG, *exename); /* search opts->exename in PATH one by one */ strv_split(&strv, env, ":"); strv_for_each(&strv, path, i) { xasprintf(&pathname, "%s/%s", path, *exename); if (is_regular_executable(pathname)) { found = true; break; } free(pathname); } if (!found) pr_err_ns(UFTRACE_MSG, *exename); *exename = pathname; strv_free(&strv); } static void check_binary(struct opts *opts) { int fd; int chk; size_t i; char elf_ident[EI_NIDENT]; uint16_t e_type; uint16_t e_machine; uint16_t supported_machines[] = { EM_X86_64, EM_ARM, EM_AARCH64, EM_386 }; again: /* if it cannot be found in PATH, then fails inside */ if (!is_regular_executable(opts->exename)) find_in_path(&opts->exename); pr_dbg("checking binary %s\n", opts->exename); fd = open(opts->exename, O_RDONLY); if (fd < 0) pr_err("Cannot open '%s'", opts->exename); if (read(fd, elf_ident, sizeof(elf_ident)) < 0) pr_err("Cannot read '%s'", opts->exename); if (memcmp(elf_ident, ELFMAG, SELFMAG)) { char *script = check_script_file(opts->exename); char *p; if (script == NULL) pr_err_ns(UFTRACE_ELF_MSG, opts->exename); if (!opts->force && !opts->patch) pr_err_ns(SCRIPT_MSG, opts->exename); /* ignore options */ p = strchr(script, ' '); if (p) *p = '\0'; opts->exename = script; close(fd); goto again; } if (read(fd, &e_type, sizeof(e_type)) < 0) pr_err("Cannot read '%s'", opts->exename); if (e_type != ET_EXEC && e_type != ET_DYN) pr_err_ns(UFTRACE_ELF_MSG, opts->exename); if (read(fd, &e_machine, sizeof(e_machine)) < 0) pr_err("Cannot read '%s'", opts->exename); for (i = 0; i < ARRAY_SIZE(supported_machines); i++) { if (e_machine == supported_machines[i]) break; } if (i == ARRAY_SIZE(supported_machines)) pr_err_ns(MACHINE_MSG, opts->exename, e_machine); chk = check_static_binary(opts->exename); if (chk) { if (chk < 0) pr_err_ns("Cannot check '%s'\n", opts->exename); else pr_err_ns(STATIC_MSG, opts->exename); } if (!opts->force) { enum uftrace_trace_type chk_type; chk_type = check_trace_functions(opts->exename); if (chk_type == TRACE_NONE && !opts->patch) { /* there's no function to trace */ pr_err_ns(MCOUNT_MSG, "mcount", opts->exename); } else if (chk_type == TRACE_CYGPROF && (opts->args || opts->retval)) { /* arg/retval doesn't support -finstrument-functions */ pr_out(ARGUMENT_MSG); } else if (chk_type == TRACE_ERROR) { pr_err_ns("Cannot check '%s'\n", opts->exename); } } close(fd); } static void check_perf_event(struct opts *opts) { struct strv strv = STRV_INIT; char *evt; int i; bool found = false; enum uftrace_pattern_type ptype = opts->patt_type; has_perf_event = has_sched_event = !opts->no_event; /* * caller filter focuses onto a given function, * displaying sched event with it is annoying. */ if (opts->caller != NULL) has_sched_event = false; if (opts->event == NULL) return; strv_split(&strv, opts->event, ";"); strv_for_each(&strv, evt, i) { struct uftrace_pattern patt; init_filter_pattern(ptype, &patt, evt); if (match_filter_pattern(&patt, "linux:task-new") || match_filter_pattern(&patt, "linux:task-exit") || match_filter_pattern(&patt, "linux:task-name")) found = true; if (match_filter_pattern(&patt, "linux:sched-in") || match_filter_pattern(&patt, "linux:sched-out") || match_filter_pattern(&patt, "linux:schedule")) { has_sched_event = true; found = true; } free_filter_pattern(&patt); if (found && has_sched_event) break; } strv_free(&strv); has_perf_event = found; } struct writer_data { int pid; int pipefd; int sock; int nr_cpu; int status; pthread_t *writers; struct timespec ts1, ts2; struct rusage usage; struct uftrace_kernel_writer kernel; struct uftrace_perf_writer perf; }; static void setup_writers(struct writer_data *wd, struct opts *opts) { struct uftrace_kernel_writer *kernel = &wd->kernel; struct uftrace_perf_writer *perf = &wd->perf; struct sigaction sa = { .sa_flags = 0, }; if (opts->nop) { opts->nr_thread = 0; opts->kernel = false; has_perf_event = false; wd->nr_cpu = 0; goto out; } sigfillset(&sa.sa_mask); sa.sa_handler = NULL; sa.sa_sigaction = sigchld_handler; sa.sa_flags = SA_NOCLDSTOP | SA_SIGINFO; sigaction(SIGCHLD, &sa, NULL); if (opts->host) { wd->sock = setup_client_socket(opts); send_trace_dir_name(wd->sock, opts->dirname); } else wd->sock = -1; wd->nr_cpu = sysconf(_SC_NPROCESSORS_ONLN); if (unlikely(wd->nr_cpu <= 0)) { wd->nr_cpu = sysconf(_SC_NPROCESSORS_CONF); if (wd->nr_cpu <= 0) pr_err("cannot know number of cpu"); } if (opts->kernel || has_kernel_event(opts->event)) { int err; kernel->pid = wd->pid; kernel->output_dir = opts->dirname; kernel->depth = opts->kernel_depth ?: 1; kernel->bufsize = opts->kernel_bufsize; if (!opts->nr_thread) { if (opts->kernel_depth >= 4) opts->nr_thread = wd->nr_cpu; else if (opts->kernel_depth >= 2) opts->nr_thread = wd->nr_cpu / 2; } if (!opts->kernel_bufsize) { if (opts->kernel_depth >= 8) kernel->bufsize = PATH_MAX * 1024; else if (opts->kernel_depth >= 4) kernel->bufsize = 3072 * 1024; else if (opts->kernel_depth >= 2) kernel->bufsize = 2048 * 1024; } err = setup_kernel_tracing(kernel, opts); if (err) { if (err == -EPERM) pr_warn("kernel tracing requires root privilege\n"); else pr_warn("kernel tracing disabled due to an error\n" "is CONFIG_FUNCTION_GRAPH_TRACER enabled in the kernel?\n"); opts->kernel = false; } } if (!opts->nr_thread) opts->nr_thread = DIV_ROUND_UP(wd->nr_cpu, 4); else if (opts->nr_thread > wd->nr_cpu) opts->nr_thread = wd->nr_cpu; if (has_perf_event) { if (setup_perf_record(perf, wd->nr_cpu, wd->pid, opts->dirname, has_sched_event) < 0) has_perf_event = false; } out: pr_dbg("creating %d thread(s) for recording\n", opts->nr_thread); wd->writers = xmalloc(opts->nr_thread * sizeof(*wd->writers)); if (pipe(thread_ctl) < 0) pr_err("cannot create an eventfd for writer thread"); } static void start_tracing(struct writer_data *wd, struct opts *opts, int ready_fd) { int i, k; uint64_t go = 1; clock_gettime(CLOCK_MONOTONIC, &wd->ts1); if (opts->kernel && start_kernel_tracing(&wd->kernel) < 0) { opts->kernel = false; pr_warn("kernel tracing disabled due to an error\n"); } for (i = 0; i < opts->nr_thread; i++) { struct writer_arg *warg; int cpu_per_thread = DIV_ROUND_UP(wd->nr_cpu, opts->nr_thread); size_t sizeof_warg = sizeof(*warg) + sizeof(int) * cpu_per_thread; warg = xzalloc(sizeof_warg); warg->opts = opts; warg->idx = i; warg->sock = wd->sock; warg->kern = &wd->kernel; warg->perf = &wd->perf; warg->nr_cpu = 0; INIT_LIST_HEAD(&warg->list); INIT_LIST_HEAD(&warg->bufs); if (opts->kernel || has_perf_event) { warg->nr_cpu = cpu_per_thread; for (k = 0; k < cpu_per_thread; k++) { if (i * cpu_per_thread + k < wd->nr_cpu) warg->cpus[k] = i * cpu_per_thread + k; else warg->cpus[k] = -1; } } pthread_create(&wd->writers[i], NULL, writer_thread, warg); } /* signal child that I'm ready */ if (write(ready_fd, &go, sizeof(go)) != (ssize_t)sizeof(go)) pr_err("signal to child failed"); } static int stop_tracing(struct writer_data *wd, struct opts *opts) { int status = -1; int ret = UFTRACE_EXIT_SUCCESS; /* child finished, read remaining data in the pipe */ while (!uftrace_done) { int remaining = 0; if (ioctl(wd->pipefd, FIONREAD, &remaining) < 0) break; if (remaining) { read_record_mmap(wd->pipefd, opts->dirname, opts->bufsize); continue; } /* wait for SIGCHLD or FORK_END */ usleep(1000); /* * It's possible to receive a remaining FORK_START message. * In this case, we need to wait FORK_END message also in * order to get proper pid. Otherwise replay will fail with * pid of -1. */ if (check_tid_list()) break; if (finish_received) { status = UFTRACE_EXIT_FINISHED; break; } pr_dbg2("waiting for FORK2\n"); } if (child_exited) { wait4(wd->pid, &status, 0, &wd->usage); if (WIFEXITED(status)) { pr_dbg("child terminated with exit code: %d\n", WEXITSTATUS(status)); if (WEXITSTATUS(status)) ret = UFTRACE_EXIT_FAILURE; else ret = UFTRACE_EXIT_SUCCESS; } else if (WIFSIGNALED(status)) { pr_warn("child terminated by signal: %d: %s\n", WTERMSIG(status), strsignal(WTERMSIG(status))); ret = UFTRACE_EXIT_SIGNALED; } else { pr_warn("child terminated with unknown reason: %d\n", status); memset(&wd->usage, 0, sizeof(wd->usage)); ret = UFTRACE_EXIT_UNKNOWN; } } else if (opts->keep_pid) memset(&wd->usage, 0, sizeof(wd->usage)); else getrusage(RUSAGE_CHILDREN, &wd->usage); stop_all_writers(); if (opts->kernel) stop_kernel_tracing(&wd->kernel); clock_gettime(CLOCK_MONOTONIC, &wd->ts2); wd->status = status; return ret; } static void finish_writers(struct writer_data *wd, struct opts *opts) { int i; char *elapsed_time = get_child_time(&wd->ts1, &wd->ts2); if (opts->time) { print_child_time(elapsed_time); print_child_usage(&wd->usage); } if (opts->nop) { free(elapsed_time); return; } if (fill_file_header(opts, wd->status, &wd->usage, elapsed_time) < 0) pr_err("cannot generate data file"); free(elapsed_time); if (shmem_lost_count) pr_warn("LOST %d records\n", shmem_lost_count); for (i = 0; i < opts->nr_thread; i++) pthread_join(wd->writers[i], NULL); free(wd->writers); close(thread_ctl[0]); flush_shmem_list(opts->dirname, opts->bufsize); record_remaining_buffer(opts, wd->sock); unlink_shmem_list(); free_tid_list(); if (opts->kernel) finish_kernel_tracing(&wd->kernel); if (has_perf_event) finish_perf_record(&wd->perf); } static void write_symbol_files(struct writer_data *wd, struct opts *opts) { struct dlopen_list *dlib, *tmp; if (opts->nop) return; /* main executable and shared libraries */ load_session_symbols(opts); /* dynamically loaded libraries using dlopen() */ list_for_each_entry_safe(dlib, tmp, &dlopen_libs, list) { struct symtabs dlib_symtabs = { .dirname = opts->dirname, .flags = SYMTAB_FL_ADJ_OFFSET, }; load_module_symtab(&dlib_symtabs, dlib->libname); list_del(&dlib->list); free(dlib->libname); free(dlib); } save_module_symtabs(opts->dirname); unload_module_symtabs(); if (opts->host) { int sock = wd->sock; send_task_file(sock, opts->dirname); send_map_files(sock, opts->dirname); send_sym_files(sock, opts->dirname); send_dbg_files(sock, opts->dirname); send_info_file(sock, opts->dirname); if (opts->kernel) send_kernel_metadata(sock, opts->dirname); if (opts->event) send_event_file(sock, opts->dirname); if (opts->logfile) send_log_file(sock, opts->logfile); send_trace_end(sock); close(sock); remove_directory(opts->dirname); } else if (geteuid() == 0) chown_directory(opts->dirname); } int do_main_loop(int ready, struct opts *opts, int pid) { int ret; struct writer_data wd; char *channel = NULL; if (opts->nop) { setup_writers(&wd, opts); start_tracing(&wd, opts, ready); close(ready); wait(NULL); uftrace_done = true; ret = stop_tracing(&wd, opts); finish_writers(&wd, opts); return ret; } xasprintf(&channel, "%s/%s", opts->dirname, ".channel"); wd.pid = pid; wd.pipefd = open(channel, O_RDONLY | O_NONBLOCK); free(channel); if (wd.pipefd < 0) pr_err("cannot open pipe"); if (opts->sig_trigger) pr_out("uftrace: install signal handlers to task %d\n", pid); setup_writers(&wd, opts); start_tracing(&wd, opts, ready); close(ready); while (!uftrace_done) { struct pollfd pollfd = { .fd = wd.pipefd, .events = POLLIN, }; ret = poll(&pollfd, 1, 1000); if (ret < 0 && errno == EINTR) continue; if (ret < 0) pr_err("error during poll"); if (pollfd.revents & POLLIN) read_record_mmap(wd.pipefd, opts->dirname, opts->bufsize); if (pollfd.revents & (POLLERR | POLLHUP)) break; } ret = stop_tracing(&wd, opts); finish_writers(&wd, opts); write_symbol_files(&wd, opts); return ret; } int do_child_exec(int ready, struct opts *opts, int argc, char *argv[]) { uint64_t dummy; char *shebang; char fullpath[PATH_MAX]; struct strv new_args = STRV_INIT; if (opts->no_randomize_addr) { /* disable ASLR (Address Space Layout Randomization) */ if (personality(ADDR_NO_RANDOMIZE) < 0) pr_dbg("disabling ASLR failed\n"); } /* * The current working directory can be changed by calling chdir. * So dirname has to be converted to an absolute path to avoid unexpected problems. */ if (realpath(opts->dirname, fullpath) != NULL) opts->dirname = fullpath; shebang = check_script_file(argv[0]); if (shebang) { char *s, *p; int i; s = shebang; while (isspace(*s)) s++; p = strchr(s, ' '); if (p != NULL) *p++ = '\0'; strv_append(&new_args, s); if (p != NULL) strv_append(&new_args, p); for (i = 0; i < argc; i++) strv_append(&new_args, argv[i]); argc = new_args.nr; argv = new_args.p; free(shebang); } setup_child_environ(opts, argc, argv); /* wait for parent ready */ if (read(ready, &dummy, sizeof(dummy)) != (ssize_t)sizeof(dummy)) pr_err("waiting for parent failed"); /* * The traced binary is already resolved into absolute pathname. * So plain 'execv' is enough and no need to use 'execvp'. */ execv(opts->exename, argv); abort(); } int command_record(int argc, char *argv[], struct opts *opts) { int pid; int ready; int ret = -1; char *channel = NULL; /* apply script-provided options */ if (opts->script_file) parse_script_opt(opts); check_binary(opts); check_perf_event(opts); if (!opts->nop) { if (create_directory(opts->dirname) < 0) return -1; xasprintf(&channel, "%s/%s", opts->dirname, ".channel"); if (mkfifo(channel, 0600) < 0) pr_err("cannot create a communication channel"); } fflush(stdout); ready = eventfd(0, EFD_CLOEXEC | EFD_SEMAPHORE); if (ready < 0) pr_dbg("creating eventfd failed: %d\n", ready); pid = fork(); if (pid < 0) pr_err("cannot start child process"); if (pid == 0) { if (opts->keep_pid) ret = do_main_loop(ready, opts, getppid()); else do_child_exec(ready, opts, argc, argv); if (channel) { unlink(channel); free(channel); } return ret; } if (opts->keep_pid) do_child_exec(ready, opts, argc, argv); else ret = do_main_loop(ready, opts, pid); if (channel) { unlink(channel); free(channel); } return ret; } uftrace-0.9.4/cmds/recv.c000066400000000000000000000350721362052523300152160ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "uftrace.h" #include "utils/utils.h" #include "utils/list.h" struct client_data { struct list_head list; int sock; char *dirname; }; static LIST_HEAD(client_list); static int server_socket(struct opts *opts) { int sock; int on = 1; struct sockaddr_in addr = { .sin_family = AF_INET, .sin_addr = { .s_addr = htonl(INADDR_ANY), }, .sin_port = htons(opts->port), }; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) pr_err("socket creation failed"); setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) pr_err("socket bind failed"); if (listen(sock, 5) < 0) pr_err("socket listen failed"); return sock; } static int signal_fd(struct opts *opts) { int fd; sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGCHLD); if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) pr_err("signal block failed"); fd = signalfd(-1, &mask, SFD_CLOEXEC | SFD_NONBLOCK); if (fd < 0) pr_err("signalfd failed"); return fd; } /* client (record) side API */ int setup_client_socket(struct opts *opts) { struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(opts->port), }; struct hostent *hostinfo; int sock; int one = 1; sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) pr_err("socket create failed"); setsockopt(sock, SOL_TCP, TCP_NODELAY, &one, sizeof(one)); hostinfo = gethostbyname(opts->host); if (hostinfo == NULL) pr_err("cannot find host: %s", opts->host); addr.sin_addr = *(struct in_addr *) hostinfo->h_addr; if (connect(sock, (const struct sockaddr *)&addr, sizeof(addr)) < 0) pr_err("socket connect failed"); return sock; } void send_trace_dir_name(int sock, char *name) { ssize_t len = strlen(name); struct uftrace_msg msg = { .magic = htons(UFTRACE_MSG_MAGIC), .type = htons(UFTRACE_MSG_SEND_DIR_NAME), .len = htonl(len), }; struct iovec iov[] = { { .iov_base = &msg, .iov_len = sizeof(msg), }, { .iov_base = name, .iov_len = len, }, }; pr_dbg2("send UFTRACE_MSG_SEND_HDR\n"); if (writev_all(sock, iov, ARRAY_SIZE(iov)) < 0) pr_err("send header failed"); } void send_trace_data(int sock, int tid, void *data, size_t len) { int32_t msg_tid = htonl(tid); struct uftrace_msg msg = { .magic = htons(UFTRACE_MSG_MAGIC), .type = htons(UFTRACE_MSG_SEND_DATA), .len = htonl(sizeof(msg_tid) + len), }; struct iovec iov[] = { { .iov_base = &msg, .iov_len = sizeof(msg), }, { .iov_base = &msg_tid, .iov_len = sizeof(msg_tid), }, { .iov_base = data, .iov_len = len, }, }; pr_dbg2("send UFTRACE_MSG_SEND_DATA\n"); if (writev_all(sock, iov, ARRAY_SIZE(iov)) < 0) pr_err("send data failed"); } void send_trace_kernel_data(int sock, int cpu, void *data, size_t len) { int32_t msg_cpu = htonl(cpu); struct uftrace_msg msg = { .magic = htons(UFTRACE_MSG_MAGIC), .type = htons(UFTRACE_MSG_SEND_KERNEL_DATA), .len = htonl(sizeof(msg_cpu) + len), }; struct iovec iov[] = { { .iov_base = &msg, .iov_len = sizeof(msg), }, { .iov_base = &msg_cpu, .iov_len = sizeof(msg_cpu), }, { .iov_base = data, .iov_len = len, }, }; pr_dbg2("send UFTRACE_MSG_SEND_KERNEL_DATA\n"); if (writev_all(sock, iov, ARRAY_SIZE(iov)) < 0) pr_err("send kernel data failed"); } void send_trace_perf_data(int sock, int cpu, void *data, size_t len) { int32_t msg_cpu = htonl(cpu); struct uftrace_msg msg = { .magic = htons(UFTRACE_MSG_MAGIC), .type = htons(UFTRACE_MSG_SEND_PERF_DATA), .len = htonl(sizeof(msg_cpu) + len), }; struct iovec iov[] = { { .iov_base = &msg, .iov_len = sizeof(msg), }, { .iov_base = &msg_cpu, .iov_len = sizeof(msg_cpu), }, { .iov_base = data, .iov_len = len, }, }; pr_dbg2("send UFTRACE_MSG_SEND_PERF_DATA\n"); if (writev_all(sock, iov, ARRAY_SIZE(iov)) < 0) pr_err("send kernel data failed"); } void send_trace_metadata(int sock, const char *dirname, char *filename) { int fd; void *buf; size_t len; char *pathname = NULL; struct stat stbuf; int32_t namelen = strlen(filename); struct uftrace_msg msg = { .magic = htons(UFTRACE_MSG_MAGIC), .type = htons(UFTRACE_MSG_SEND_META_DATA), .len = sizeof(namelen) + namelen, }; struct iovec iov[4] = { { .iov_base = &msg, .iov_len = sizeof(msg), }, { .iov_base = &namelen, .iov_len = sizeof(namelen), }, { .iov_base = filename, .iov_len = namelen, }, { /* to be filled */ }, }; if (dirname) xasprintf(&pathname, "%s/%s", dirname, filename); else pathname = xstrdup(filename); fd = open(pathname, O_RDONLY); if (fd < 0) pr_err("open %s failed", pathname); if (fstat(fd, &stbuf) < 0) pr_err("stat %s failed", pathname); len = stbuf.st_size; buf = xmalloc(len); msg.len = htonl(msg.len + len); iov[3].iov_base = buf; iov[3].iov_len = len; if (read_all(fd, buf, len) < 0) pr_err("map read failed"); namelen = htonl(namelen); pr_dbg2("send UFTRACE_MSG_SEND_META_DATA: %s\n", filename); if (writev_all(sock, iov, ARRAY_SIZE(iov)) < 0) pr_err("send metadata failed"); free(pathname); free(buf); close(fd); } void send_trace_info(int sock, struct uftrace_file_header *hdr, void *info, int len) { struct uftrace_msg msg = { .magic = htons(UFTRACE_MSG_MAGIC), .type = htons(UFTRACE_MSG_SEND_INFO), .len = htonl(sizeof(*hdr) + len), }; struct iovec iov[] = { { .iov_base = &msg, .iov_len = sizeof(msg), }, { .iov_base = hdr, .iov_len = sizeof(*hdr), }, { .iov_base = info, .iov_len = len, }, }; hdr->version = htonl(hdr->version); hdr->header_size = htons(hdr->header_size); hdr->feat_mask = htonq(hdr->feat_mask); hdr->info_mask = htonq(hdr->info_mask); hdr->max_stack = htons(hdr->max_stack); pr_dbg2("send UFTRACE_MSG_SEND_INFO\n"); if (writev_all(sock, iov, ARRAY_SIZE(iov)) < 0) pr_err("send metadata failed"); } void send_trace_end(int sock) { struct uftrace_msg msg = { .magic = htons(UFTRACE_MSG_MAGIC), .type = htons(UFTRACE_MSG_SEND_END), }; pr_dbg2("send UFTRACE_MSG_SEND_END\n"); if (write_all(sock, &msg, sizeof(msg)) < 0) pr_err("send end failed"); } /* server (recv) side API */ static struct client_data *find_client(int sock) { struct client_data *c; list_for_each_entry(c, &client_list, list) { if (c->sock == sock) return c; } return NULL; } #define O_CLIENT_FLAGS (O_WRONLY | O_APPEND | O_CREAT) static void write_client_file(struct client_data *c, char *filename, int nr, ...) { int i, fd; va_list ap; struct iovec iov[nr]; char buf[PATH_MAX]; snprintf(buf, sizeof(buf), "%s/%s", c->dirname, filename); fd = open(buf, O_CLIENT_FLAGS, 0644); if (fd < 0) pr_err("file open failed: %s", buf); va_start(ap, nr); for (i = 0; i < nr; i++) { iov[i].iov_base = va_arg(ap, void *); iov[i].iov_len = va_arg(ap, int); } va_end(ap); if (writev_all(fd, iov, nr) < 0) pr_err("write client data failed on %s", buf); close(fd); } static void recv_trace_dir_name(int sock, int len) { char dirname[len + 1]; struct client_data *client; if (read_all(sock, dirname, len) < 0) pr_err("recv header failed"); dirname[len] = '\0'; client = xmalloc(sizeof(*client)); client->sock = sock; client->dirname = xstrdup(dirname); INIT_LIST_HEAD(&client->list); create_directory(dirname); pr_dbg3("create directory: %s\n", dirname); list_add(&client->list, &client_list); } static void recv_trace_data(int sock, int len) { struct client_data *client; int32_t tid; char *filename = NULL; void *buffer; client = find_client(sock); if (client == NULL) pr_err_ns("no client on this socket\n"); if (read_all(sock, &tid, sizeof(tid)) < 0) pr_err("recv tid failed"); tid = ntohl(tid); xasprintf(&filename, "%d.dat", tid); len -= sizeof(tid); buffer = xmalloc(len); if (read_all(sock, buffer, len) < 0) pr_err("recv buffer failed"); write_client_file(client, filename, 1, buffer, len); free(buffer); free(filename); } static void recv_trace_kernel_data(int sock, int len) { struct client_data *client; int32_t cpu; char *filename = NULL; void *buffer; client = find_client(sock); if (client == NULL) pr_err_ns("no client on this socket\n"); if (read_all(sock, &cpu, sizeof(cpu)) < 0) pr_err("recv cpu failed"); cpu = ntohl(cpu); xasprintf(&filename, "kernel-cpu%d.dat", cpu); len -= sizeof(cpu); buffer = xmalloc(len); if (read_all(sock, buffer, len) < 0) pr_err("recv buffer failed"); write_client_file(client, filename, 1, buffer, len); free(buffer); free(filename); } static void recv_trace_perf_data(int sock, int len) { struct client_data *client; int32_t cpu; char *filename = NULL; void *buffer; client = find_client(sock); if (client == NULL) pr_err_ns("no client on this socket\n"); if (read_all(sock, &cpu, sizeof(cpu)) < 0) pr_err("recv cpu failed"); cpu = ntohl(cpu); xasprintf(&filename, "perf-cpu%d.dat", cpu); len -= sizeof(cpu); buffer = xmalloc(len); if (read_all(sock, buffer, len) < 0) pr_err("recv buffer failed"); write_client_file(client, filename, 1, buffer, len); free(buffer); free(filename); } static void recv_trace_metadata(int sock, int len) { struct client_data *client; int32_t namelen; char *filename = NULL; void *filedata; client = find_client(sock); if (client == NULL) pr_err_ns("no client on this socket\n"); if (read_all(sock, &namelen, sizeof(namelen)) < 0) pr_err("recv symfile name length failed"); namelen = ntohl(namelen); filename = xmalloc(namelen + 1); if (read_all(sock, filename, namelen) < 0) pr_err("recv file name failed"); filename[namelen] = '\0'; len -= sizeof(namelen) + namelen; filedata = xmalloc(len); pr_dbg2("reading %s (%d bytes)\n", filename, len); if (read_all(sock, filedata, len) < 0) pr_err("recv symfile failed"); write_client_file(client, filename, 1, filedata, len); free(filedata); free(filename); } static void recv_trace_info(int sock, int len) { struct client_data *client; struct uftrace_file_header hdr; void *info; client = find_client(sock); if (client == NULL) pr_err_ns("no client on this socket\n"); if (read_all(sock, &hdr, sizeof(hdr)) < 0) pr_err("recv file header failed"); hdr.version = ntohl(hdr.version); hdr.header_size = ntohs(hdr.header_size); hdr.feat_mask = ntohq(hdr.feat_mask); hdr.info_mask = ntohq(hdr.info_mask); hdr.max_stack = ntohs(hdr.max_stack); len -= sizeof(hdr); info = xmalloc(len); if (read_all(sock, info, len) < 0) pr_err("recv info failed"); write_client_file(client, "info", 2, &hdr, sizeof(hdr), info, len); free(info); } static void recv_trace_end(int sock, int efd) { struct client_data *client; client = find_client(sock); if (client) { list_del(&client->list); pr_dbg("wrote client data to %s\n", client->dirname); free(client->dirname); free(client); } if (epoll_ctl(efd, EPOLL_CTL_DEL, sock, NULL) < 0) pr_err("epoll del failed"); close(sock); } static void execute_run_cmd(char **argv) { if (!argv) return; int pid = fork(); if (pid < 0) pr_err("cannot start child process"); if (pid == 0) { execvp(argv[0], argv); pr_err("Failed to execute '%s'", argv[0]); } } static void epoll_add(int efd, int fd, unsigned event) { struct epoll_event ev = { .events = event, .data = { .fd = fd, }, }; if (epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ev) < 0) pr_err("epoll add failed"); } static void handle_server_sock(struct epoll_event *ev, int efd) { int client; int sock = ev->data.fd; struct sockaddr_in addr; socklen_t len = sizeof(addr); char hbuf[NI_MAXHOST]; client = accept(sock, (struct sockaddr *)&addr, &len); if (client < 0) pr_err("socket accept failed"); getnameinfo((struct sockaddr *)&addr, len, hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST); epoll_add(efd, client, EPOLLIN); pr_dbg("new connection added from %s\n", hbuf); } static void handle_client_sock(struct epoll_event *ev, int efd, struct opts *opts) { int sock = ev->data.fd; struct uftrace_msg msg; if (ev->events & (EPOLLERR | EPOLLHUP)) { pr_dbg("client socket closed\n"); recv_trace_end(sock, efd); return; } if (read_all(sock, &msg, sizeof(msg)) < 0) pr_err("message recv failed"); msg.magic = ntohs(msg.magic); msg.type = ntohs(msg.type); msg.len = ntohl(msg.len); if (msg.magic != UFTRACE_MSG_MAGIC) pr_err_ns("invalid message\n"); switch (msg.type) { case UFTRACE_MSG_SEND_DIR_NAME: pr_dbg2("receive UFTRACE_MSG_SEND_DIR_NAME\n"); recv_trace_dir_name(sock, msg.len); break; case UFTRACE_MSG_SEND_DATA: pr_dbg2("receive UFTRACE_MSG_SEND_DATA\n"); recv_trace_data(sock, msg.len); break; case UFTRACE_MSG_SEND_KERNEL_DATA: pr_dbg2("receive UFTRACE_MSG_SEND_KERNEL_DATA\n"); recv_trace_kernel_data(sock, msg.len); break; case UFTRACE_MSG_SEND_PERF_DATA: pr_dbg2("receive UFTRACE_MSG_SEND_PERF_DATA\n"); recv_trace_perf_data(sock, msg.len); break; case UFTRACE_MSG_SEND_INFO: pr_dbg2("receive UFTRACE_MSG_SEND_INFO\n"); recv_trace_info(sock, msg.len); break; case UFTRACE_MSG_SEND_META_DATA: pr_dbg2("receive UFTRACE_MSG_SEND_META_DATA\n"); recv_trace_metadata(sock, msg.len); break; case UFTRACE_MSG_SEND_END: pr_dbg2("receive UFTRACE_MSG_SEND_END\n"); recv_trace_end(sock, efd); execute_run_cmd(opts->run_cmd); break; default: pr_dbg("unknown message: %d\n", msg.type); break; } } int command_recv(int argc, char *argv[], struct opts *opts) { struct signalfd_siginfo si; int sock; int sigfd; int efd; if (strcmp(opts->dirname, UFTRACE_DIR_NAME)) { char *dirname = "current"; if ((mkdir(opts->dirname, 0755) == 0 || errno == EEXIST) && chdir(opts->dirname) == 0) dirname = opts->dirname; pr_dbg("saving to %s directory\n", dirname); } sock = server_socket(opts); sigfd = signal_fd(opts); efd = epoll_create1(EPOLL_CLOEXEC); if (efd < 0) pr_err("epoll create failed"); epoll_add(efd, sock, EPOLLIN); epoll_add(efd, sigfd, EPOLLIN); while (!uftrace_done) { struct epoll_event ev[10]; int i, len; len = epoll_wait(efd, ev, 10, -1); if (len < 0) pr_err("epoll wait failed"); for (i = 0; i < len; i++) { if (ev[i].data.fd == sigfd) { int nr = read(sigfd, &si, sizeof si); if (nr > 0 && si.ssi_signo == SIGCHLD) waitpid(-1, NULL, WNOHANG); else uftrace_done = true; } else if (ev[i].data.fd == sock) handle_server_sock(&ev[i], efd); else handle_client_sock(&ev[i], efd, opts); } } close(efd); close(sigfd); close(sock); return 0; } uftrace-0.9.4/cmds/replay.c000066400000000000000000000705611362052523300155550ustar00rootroot00000000000000#include #include #include #include #include #include #include "uftrace.h" #include "utils/utils.h" #include "utils/symbol.h" #include "utils/filter.h" #include "utils/fstack.h" #include "utils/list.h" #include "utils/kernel.h" #include "utils/field.h" #include "libtraceevent/event-parse.h" static int column_index; static int prev_tid = -1; static LIST_HEAD(output_fields); #define NO_TIME (void *)1 /* to suppress duration */ static void print_duration(struct field_data *fd) { struct fstack *fstack = fd->fstack; void *arg = fd->arg; uint64_t d = 0; /* any non-NULL argument suppresses the output */ if (fstack && arg == NULL) d = fstack->total_time; print_time_unit(d); } static void print_tid(struct field_data *fd) { struct uftrace_task_reader *task = fd->task; pr_out("[%6d]", task->tid); } static void print_addr(struct field_data *fd) { struct fstack *fstack = fd->fstack; /* uftrace records (truncated) 48-bit addresses */ int width = sizeof(long) == 4 ? 8 : 12; if (fstack == NULL) /* LOST */ pr_out("%*s", width, ""); else pr_out("%*"PRIx64, width, effective_addr(fstack->addr)); } static void print_timestamp(struct field_data *fd) { struct uftrace_task_reader *task = fd->task; uint64_t sec = task->timestamp / NSEC_PER_SEC; uint64_t nsec = task->timestamp % NSEC_PER_SEC; pr_out("%8"PRIu64".%09"PRIu64, sec, nsec); } static void print_timedelta(struct field_data *fd) { struct uftrace_task_reader *task = fd->task; uint64_t delta = 0; if (task->timestamp_last) delta = task->timestamp - task->timestamp_last; print_time_unit(delta); } static void print_elapsed(struct field_data *fd) { struct uftrace_task_reader *task = fd->task; uint64_t elapsed = task->timestamp - task->h->time_range.first; print_time_unit(elapsed); } static void print_task(struct field_data *fd) { struct uftrace_task_reader *task = fd->task; pr_out("%*s", 15, task->t->comm); } static void print_module(struct field_data *fd) { struct uftrace_task_reader *task = fd->task; struct fstack *fstack = fd->fstack; uint64_t timestamp = task->timestamp; struct uftrace_session *s; struct uftrace_mmap *map; char *modname = "[unknown]"; /* for EVENT or LOST record */ if (fstack == NULL) { pr_out("%*s", 16, ""); return; } s = find_task_session(&task->h->sessions, task->t, timestamp); if (s) { map = find_map(&s->symtabs, fstack->addr); if (map == MAP_KERNEL) modname = "[kernel]"; else if (map) modname = basename(map->libname); } pr_out("%*.*s", 16, 16, modname); } static struct display_field field_duration = { .id = REPLAY_F_DURATION, .name = "duration", .header = " DURATION ", .length = 10, .print = print_duration, .list = LIST_HEAD_INIT(field_duration.list), }; static struct display_field field_tid = { .id = REPLAY_F_TID, .name = "tid", .header = " TID ", .length = 8, .print = print_tid, .list = LIST_HEAD_INIT(field_tid.list), }; static struct display_field field_addr = { .id = REPLAY_F_ADDR, .name = "addr", #if __SIZEOF_LONG__ == 4 .header = " ADDRESS", .length = 8, #else .header = " ADDRESS ", .length = 12, #endif .print = print_addr, .list = LIST_HEAD_INIT(field_addr.list), }; static struct display_field field_time = { .id = REPLAY_F_TIMESTAMP, .name = "time", .header = " TIMESTAMP ", .length = 18, .print = print_timestamp, .list = LIST_HEAD_INIT(field_time.list), }; static struct display_field field_delta = { .id = REPLAY_F_TIMEDELTA, .name = "delta", .header = " TIMEDELTA", .length = 10, .print = print_timedelta, .list = LIST_HEAD_INIT(field_delta.list), }; static struct display_field field_elapsed = { .id = REPLAY_F_ELAPSED, .name = "elapsed", .header = " ELAPSED ", .length = 10, .print = print_elapsed, .list = LIST_HEAD_INIT(field_elapsed.list), }; static struct display_field field_task = { .id = REPLAY_F_TASK, .name = "task", .header = " TASK NAME", .length = 15, .print = print_task, .list = LIST_HEAD_INIT(field_task.list), }; static struct display_field field_module = { .id = REPLAY_F_MODULE, .name = "module", .header = " MODULE NAME", .length = 16, .print = print_module, .list = LIST_HEAD_INIT(field_module.list), }; /* index of this table should be matched to display_field_id */ static struct display_field *field_table[] = { &field_duration, &field_tid, &field_addr, &field_time, &field_delta, &field_elapsed, &field_task, &field_module, }; static void print_field(struct uftrace_task_reader *task, struct fstack *fstack, void *arg) { struct field_data fd = { .task = task, .fstack = fstack, .arg = arg, }; if (print_field_data(&output_fields, &fd, 1)) pr_out(" | "); } static void setup_default_field(struct list_head *fields, struct opts *opts) { if (opts->range.start > 0 || opts->range.stop > 0) { if (opts->range.start_elapsed || opts->range.stop_elapsed) add_field(fields, field_table[REPLAY_F_ELAPSED]); else add_field(fields, field_table[REPLAY_F_TIMESTAMP]); } add_field(fields, field_table[REPLAY_F_DURATION]); add_field(fields, field_table[REPLAY_F_TID]); } static int task_column_depth(struct uftrace_task_reader *task, struct opts *opts) { if (!opts->column_view) return 0; if (task->column_index == -1) task->column_index = column_index++; return task->column_index * opts->column_offset; } static void print_backtrace(struct uftrace_task_reader *task) { struct uftrace_session_link *sessions = &task->h->sessions; int i; for (i = 0; i < task->stack_count - 1; i++) { struct display_field *field; struct sym *sym; char *name; struct fstack *fstack = fstack_get(task, i); struct field_data fd = { .task = task, .fstack = fstack, }; if (fstack == NULL) continue; sym = task_find_sym_addr(sessions, task, fstack->total_time, fstack->addr); pr_out(" "); list_for_each_entry(field, &output_fields, list) { if (field->id == REPLAY_F_DURATION) pr_out("%*s", field->length, "backtrace"); else field->print(&fd); pr_out(" "); } if (!list_empty(&output_fields)) pr_out("| "); name = symbol_getname(sym, fstack->addr); pr_gray("/* [%2d] %s */\n", i, name); symbol_putname(sym, name); } } static void print_event(struct uftrace_task_reader *task, struct uftrace_record *urec, int color) { unsigned evt_id = urec->addr; char *evt_name = get_event_name(task->h, evt_id); if (evt_id == EVENT_ID_EXTERN_DATA) { pr_color(color, "%s: %s", evt_name, (char *)task->args.data); } else if (evt_id >= EVENT_ID_USER) { /* TODO: some events might have arguments */ pr_color(color, "%s", evt_name); } else if (evt_id >= EVENT_ID_PERF) { pr_color(color, "%s", evt_name); if (evt_id == EVENT_ID_PERF_COMM) pr_color(color, " (name=%s)", (char *)task->args.data); } else if (evt_id >= EVENT_ID_BUILTIN) { union { struct uftrace_proc_statm *statm; struct uftrace_page_fault *page_fault; struct uftrace_pmu_cycle *cycle; struct uftrace_pmu_cache *cache; struct uftrace_pmu_branch *branch; int *cpu; } u; switch (evt_id) { case EVENT_ID_READ_PROC_STATM: u.statm = task->args.data; pr_color(color, "%s (size=%"PRIu64"KB, rss=%"PRIu64"KB, shared=%"PRIu64"KB)", evt_name, u.statm->vmsize, u.statm->vmrss, u.statm->shared); break; case EVENT_ID_READ_PAGE_FAULT: u.page_fault = task->args.data; pr_color(color, "%s (major=%"PRIu64", minor=%"PRIu64")", evt_name, u.page_fault->major, u.page_fault->minor); break; case EVENT_ID_READ_PMU_CYCLE: u.cycle = task->args.data; pr_color(color, "%s (cycle=%"PRIu64", instructions=%"PRIu64")", evt_name, u.cycle->cycles, u.cycle->instrs); break; case EVENT_ID_READ_PMU_CACHE: u.cache = task->args.data; pr_color(color, "%s (refers=%"PRIu64", misses=%"PRIu64")", evt_name, u.cache->refers, u.cache->misses); break; case EVENT_ID_READ_PMU_BRANCH: u.branch = task->args.data; pr_color(color, "%s (branch=%"PRIu64", misses=%"PRIu64")", evt_name, u.branch->branch, u.branch->misses); break; case EVENT_ID_DIFF_PROC_STATM: u.statm = task->args.data; pr_color(color, "%s (size=%+"PRId64"KB, rss=%+"PRId64"KB, shared=%+"PRId64"KB)", evt_name, u.statm->vmsize, u.statm->vmrss, u.statm->shared); break; case EVENT_ID_DIFF_PAGE_FAULT: u.page_fault = task->args.data; pr_color(color, "%s (major=%+"PRId64", minor=%+"PRId64")", evt_name, u.page_fault->major, u.page_fault->minor); break; case EVENT_ID_DIFF_PMU_CYCLE: u.cycle = task->args.data; pr_color(color, "%s (cycle=%+"PRId64", instructions=%+"PRId64", IPC=%.2f)", evt_name, u.cycle->cycles, u.cycle->instrs, (float)u.cycle->instrs / u.cycle->cycles); break; case EVENT_ID_DIFF_PMU_CACHE: u.cache = task->args.data; pr_color(color, "%s (refers=%+"PRId64", misses=%+"PRId64", hit=%"PRId64"%%)", evt_name, u.cache->refers, u.cache->misses, (u.cache->refers - u.cache->misses) * 100 / u.cache->refers); break; case EVENT_ID_DIFF_PMU_BRANCH: u.branch = task->args.data; pr_color(color, "%s (branch=%+"PRId64", misses=%+"PRId64", predict=%"PRId64"%%)", evt_name, u.branch->branch, u.branch->misses, (u.branch->branch - u.branch->misses) * 100 / u.branch->branch); break; case EVENT_ID_WATCH_CPU: u.cpu = task->args.data; pr_color(color, "%s (cpu=%d)", evt_name, *u.cpu); break; default: pr_color(color, "%s", evt_name); } } else { /* kernel events */ pr_color(color, "%s (%.*s)", evt_name, task->args.len, task->args.data); } free(evt_name); } static int print_flat_rstack(struct uftrace_data *handle, struct uftrace_task_reader *task, struct opts *opts) { static int count; struct uftrace_record *rstack = task->rstack; struct uftrace_session_link *sessions = &task->h->sessions; struct sym *sym = NULL; char *name; struct fstack *fstack; sym = task_find_sym(sessions, task, rstack); name = symbol_getname(sym, rstack->addr); fstack = fstack_get(task, rstack->depth); if (fstack == NULL) goto out; /* skip it if --no-libcall is given */ if (!opts->libcall && sym && sym->type == ST_PLT_FUNC) goto out; switch (rstack->type) { case UFTRACE_ENTRY: pr_out("[%d] ==> %d/%d: ip (%s), time (%"PRIu64")\n", count++, task->tid, rstack->depth, name, rstack->time); break; case UFTRACE_EXIT: pr_out("[%d] <== %d/%d: ip (%s), time (%"PRIu64":%"PRIu64")\n", count++, task->tid, rstack->depth, name, rstack->time, fstack->total_time); break; case UFTRACE_LOST: pr_out("[%d] XXX %d: lost %d records\n", count++, task->tid, (int)rstack->addr); break; case UFTRACE_EVENT: pr_out("[%d] !!! %d: ", count++, task->tid); print_event(task, rstack, task->event_color); pr_out(" time (%"PRIu64")\n", rstack->time); break; } out: symbol_putname(sym, name); return 0; } static void print_task_newline(int current_tid) { if (prev_tid != -1 && current_tid != prev_tid) { if (print_empty_field(&output_fields, 1)) pr_out(" | "); pr_out("\n"); } prev_tid = current_tid; } #define print_char(c) \ ({ args[n] = c; n++; len--; }) #define print_args(fmt, ...) \ ({ int _x = snprintf(args + n, len, fmt, ##__VA_ARGS__); n += _x; len -= _x; }) #define print_json_escaped_char(c) \ do { \ if (c == '\n') \ print_args("\\\\n"); \ else if (c == '\t') \ print_args("\\\\t"); \ else if (c == '\\') \ print_args("\\\\"); \ else if (c == '"') \ print_args("\\\""); \ else if (isprint(c)) \ print_char(c); \ else \ print_args("\\\\x%02hhx", c); \ } while (0) #define print_escaped_char(c) \ do { \ if (c == '\0') \ print_args("\\0"); \ else if (c == '\b') \ print_args("\\b"); \ else if (c == '\n') \ print_args("\\n"); \ else \ print_char(c); \ } while (0) void get_argspec_string(struct uftrace_task_reader *task, char *args, size_t len, enum argspec_string_bits str_mode) { int i = 0, n = 0; char *str = NULL; const int null_str = -1; void *data = task->args.data; struct list_head *arg_list = task->args.args; struct uftrace_arg_spec *spec; union { long i; void *p; float f; double d; long long ll; long double D; unsigned char v[16]; } val; bool needs_paren = !!(str_mode & NEEDS_PAREN); bool needs_semi_colon = !!(str_mode & NEEDS_SEMI_COLON); bool has_more = !!(str_mode & HAS_MORE); bool is_retval = !!(str_mode & IS_RETVAL); bool needs_assignment = !!(str_mode & NEEDS_ASSIGNMENT); bool needs_json = !!(str_mode & NEEDS_JSON); if (!has_more) { if (needs_paren) strcpy(args, "()"); else { if (is_retval && needs_semi_colon) args[n++] = ';'; args[n] = '\0'; } return; } assert(arg_list && !list_empty(arg_list)); if (needs_paren) print_args("%s(%s", color_bold, color_reset); else if (needs_assignment) print_args("%s = %s", color_bold, color_reset); list_for_each_entry(spec, arg_list, list) { char fmtstr[16]; char *len_mod[] = { "hh", "h", "", "ll" }; char fmt, *lm; unsigned idx; size_t size = spec->size; /* skip unwanted arguments or retval */ if (is_retval != (spec->idx == RETVAL_IDX)) continue; if (i > 0) print_args("%s, %s", color_bold, color_reset); memset(val.v, 0, sizeof(val)); fmt = ARG_SPEC_CHARS[spec->fmt]; switch (spec->fmt) { case ARG_FMT_AUTO: memcpy(val.v, data, spec->size); if (val.i > 100000L || val.i < -100000L) { fmt = 'x'; /* * Show small negative integers naturally * on 64-bit systems. The conversion is * required to avoid compiler warnings * on 32-bit systems. */ if (sizeof(long) == sizeof(uint64_t)) { uint64_t val64 = val.i; if (val64 > 0xffff0000 && val64 <= 0xffffffff) { fmt = 'd'; idx = 2; break; } } } /* fall through */ case ARG_FMT_SINT: case ARG_FMT_HEX: idx = ffs(spec->size) - 1; break; case ARG_FMT_UINT: memcpy(val.v, data, spec->size); if ((unsigned long)val.i > 100000UL) fmt = 'x'; idx = ffs(spec->size) - 1; break; default: idx = 2; break; } if (spec->fmt == ARG_FMT_STR || spec->fmt == ARG_FMT_STD_STRING) { unsigned short slen; memcpy(&slen, data, 2); str = xmalloc(slen + 1); memcpy(str, data + 2, slen); str[slen] = '\0'; if (slen == 4 && !memcmp(str, &null_str, sizeof(null_str))) print_args("NULL"); else if (needs_json) { char *p = str; print_args("\\\""); while (*p) { char c = *p++; print_json_escaped_char(c); } print_args("\\\""); } else { print_args("%s", color_string); print_args("\""); char *p = str; while (*p) { char c = *p++; if (c & 0x80) { break; } } /* * if value of character is over than 128(0x80), * then it will be UTF-8 string */ if (*p) { print_args("%.*s", slen, str); } else { p = str; while (*p) { char c = *p++; print_escaped_char(c); } } print_args("\""); print_args("%s", color_reset); } /* std::string can be represented as "TEXT"s from C++14 */ if (spec->fmt == ARG_FMT_STD_STRING) print_args("s"); free(str); size = slen + 2; } else if (spec->fmt == ARG_FMT_CHAR) { char c; memcpy(&c, data, 1); if (needs_json) { print_args("'"); print_json_escaped_char(c); print_args("'"); } else { print_args("%s", color_string); print_args("'"); print_escaped_char(c); print_args("'"); print_args("%s", color_reset); } size = 1; } else if (spec->fmt == ARG_FMT_FLOAT) { if (spec->size == 10) lm = "L"; else lm = len_mod[idx]; memcpy(val.v, data, spec->size); snprintf(fmtstr, sizeof(fmtstr), "%%#%s%c", lm, fmt); switch (spec->size) { case 4: print_args(fmtstr, val.f); break; case 8: print_args(fmtstr, val.d); break; case 10: print_args(fmtstr, val.D); break; default: pr_dbg("invalid floating-point type size %d\n", spec->size); break; } } else if (spec->fmt == ARG_FMT_PTR) { struct uftrace_session_link *sessions = &task->h->sessions; struct sym *sym; memcpy(val.v, data, spec->size); sym = task_find_sym_addr(sessions, task, task->rstack->time, (uint64_t)val.i); if (sym) { print_args("%s", color_symbol); print_args("&%s", sym->name); print_args("%s", color_reset); } else if (val.p) print_args("%p", val.p); else print_args("0"); } else if (spec->fmt == ARG_FMT_ENUM) { struct uftrace_session_link *sessions = &task->h->sessions; struct uftrace_session *s; struct uftrace_mmap *map; struct debug_info *dinfo; char *estr; memcpy(val.v, data, spec->size); s = find_task_session(sessions, task->t, task->rstack->time); map = find_map(&s->symtabs, task->rstack->addr); if (map == NULL || map->mod == NULL) { print_args(" %lx", val.i); goto next; } dinfo = &map->mod->dinfo; estr = get_enum_string(&dinfo->enums, spec->enum_str, val.i); if (strstr(estr, "|") && strcmp("|", color_enum_or)) { struct strv enum_vals = STRV_INIT; strv_split(&enum_vals, estr, "|"); free(estr); estr = strv_join(&enum_vals, color_enum_or); strv_free(&enum_vals); } print_args("%s", color_enum); if (strlen(estr) >= len) print_args(""); else print_args("%s", estr); print_args("%s", color_reset); free(estr); } else { if (spec->fmt != ARG_FMT_AUTO) memcpy(val.v, data, spec->size); assert(idx < ARRAY_SIZE(len_mod)); lm = len_mod[idx]; snprintf(fmtstr, sizeof(fmtstr), "%%#%s%c", lm, fmt); if (spec->size == 8) print_args(fmtstr, val.ll); else print_args(fmtstr, val.i); } next: i++; data += ALIGN(size, 4); if (len <= 2) break; /* read only the first match for retval */ if (is_retval) break; } if (needs_paren) { print_args("%s)%s", color_bold, color_reset); } else { if (needs_semi_colon) args[n++] = ';'; args[n] = '\0'; } } static int print_graph_rstack(struct uftrace_data *handle, struct uftrace_task_reader *task, struct opts *opts) { struct uftrace_record *rstack; struct uftrace_session_link *sessions = &handle->sessions; struct sym *sym = NULL; enum argspec_string_bits str_mode = 0; char *symname = NULL; char args[1024]; char *libname = ""; struct uftrace_mmap *map = NULL; if (task == NULL) return 0; rstack = task->rstack; if (rstack->type == UFTRACE_LOST) goto lost; sym = task_find_sym(sessions, task, rstack); symname = symbol_getname(sym, rstack->addr); /* skip it if --no-libcall is given */ if (!opts->libcall && sym && sym->type == ST_PLT_FUNC) goto out; if (rstack->type == UFTRACE_ENTRY) { if (symname[strlen(symname) - 1] != ')' || rstack->more) str_mode |= NEEDS_PAREN; } task->timestamp_last = task->timestamp; task->timestamp = rstack->time; if (opts->libname && sym && sym->type == ST_PLT_FUNC) { struct uftrace_session *s; s = find_task_session(sessions, task->t, rstack->time); if (s != NULL) { map = find_symbol_map(&s->symtabs, symname); if (map != NULL) libname = basename(map->libname); } } if (rstack->type == UFTRACE_ENTRY) { struct uftrace_task_reader *next = NULL; struct fstack *fstack; int rstack_depth = rstack->depth; int depth; struct uftrace_trigger tr = { .flags = 0, }; int ret; ret = fstack_entry(task, rstack, &tr); if (ret < 0) goto out; /* display depth is set in fstack_entry() */ depth = task->display_depth; /* give a new line when tid is changed */ if (opts->task_newline) print_task_newline(task->tid); if (tr.flags & TRIGGER_FL_BACKTRACE) print_backtrace(task); if (tr.flags & TRIGGER_FL_COLOR) task->event_color = tr.color; else task->event_color = DEFAULT_EVENT_COLOR; depth += task_column_depth(task, opts); if (rstack->more) str_mode |= HAS_MORE; get_argspec_string(task, args, sizeof(args), str_mode); fstack = fstack_get(task, task->stack_count - 1); if (!opts->no_merge) next = fstack_skip(handle, task, rstack_depth, opts); if (task == next && next->rstack->depth == rstack_depth && next->rstack->type == UFTRACE_EXIT) { char retval[1024]; /* leaf function - also consume return record */ fstack_consume(handle, next); str_mode = IS_RETVAL | NEEDS_SEMI_COLON; if (next->rstack->more) { str_mode |= HAS_MORE; str_mode |= NEEDS_ASSIGNMENT; } get_argspec_string(task, retval, sizeof(retval), str_mode); print_field(task, fstack, NULL); pr_out("%*s", depth * 2, ""); if (tr.flags & TRIGGER_FL_COLOR) { pr_color(tr.color, "%s", symname); if (*libname) pr_color(tr.color, "@%s", libname); pr_out("%s%s\n", args, retval); } else { pr_out("%s%s%s%s%s\n", symname, *libname ? "@" : "", libname, args, retval); } /* fstack_update() is not needed here */ fstack_exit(task); } else { /* function entry */ print_field(task, fstack, NO_TIME); pr_out("%*s", depth * 2, ""); if (tr.flags & TRIGGER_FL_COLOR) { pr_color(tr.color, "%s", symname); if (*libname) pr_color(tr.color, "@%s", libname); pr_out("%s {\n", args); } else { pr_out("%s%s%s%s {\n", symname, *libname ? "@" : "", libname, args); } fstack_update(UFTRACE_ENTRY, task, fstack); } } else if (rstack->type == UFTRACE_EXIT) { struct fstack *fstack; /* function exit */ fstack = fstack_get(task, task->stack_count); if (fstack_enabled && fstack != NULL && !(fstack->flags & FSTACK_FL_NORECORD)) { int depth = fstack_update(UFTRACE_EXIT, task, fstack); char *retval = args; depth += task_column_depth(task, opts); str_mode = IS_RETVAL; if (rstack->more) { str_mode |= HAS_MORE; str_mode |= NEEDS_ASSIGNMENT; str_mode |= NEEDS_SEMI_COLON; } get_argspec_string(task, retval, sizeof(args), str_mode); /* give a new line when tid is changed */ if (opts->task_newline) print_task_newline(task->tid); print_field(task, fstack, NULL); pr_out("%*s}%s", depth * 2, "", retval); if (opts->comment) pr_gray(" /* %s%s%s */\n", symname, *libname ? "@" : "", libname); else pr_gray("\n"); } fstack_exit(task); } else if (rstack->type == UFTRACE_LOST) { int depth, losts; lost: depth = task->display_depth + 1; losts = (int)rstack->addr; /* skip kernel lost messages outside of user functions */ if (opts->kernel_skip_out && task->user_stack_count == 0) return 0; /* give a new line when tid is changed */ if (opts->task_newline) print_task_newline(task->tid); print_field(task, NULL, NO_TIME); if (losts > 0) pr_red("%*s/* LOST %d records!! */\n", depth * 2, "", losts); else /* kernel sometimes have unknown count */ pr_red("%*s/* LOST some records!! */\n", depth * 2, ""); return 0; } else if (rstack->type == UFTRACE_EVENT) { int depth; struct fstack *fstack; struct uftrace_task_reader *next = NULL; struct uftrace_record rec = *rstack; uint64_t evt_id = rstack->addr; depth = task->display_depth; if (!fstack_check_filter(task)) goto out; /* give a new line when tid is changed */ if (opts->task_newline) print_task_newline(task->tid); depth += task_column_depth(task, opts); /* * try to merge a subsequent sched-in event: * it might overwrite rstack - use (saved) rec for printing. */ if (evt_id == EVENT_ID_PERF_SCHED_OUT && !opts->no_merge) next = fstack_skip(handle, task, 0, opts); if (task == next && next->rstack->addr == EVENT_ID_PERF_SCHED_IN) { /* consume the matching sched-in record */ fstack_consume(handle, next); rec.addr = sched_sym.addr; evt_id = EVENT_ID_PERF_SCHED_IN; } /* show external data regardless of display depth */ if (evt_id == EVENT_ID_EXTERN_DATA) depth = 0; /* for sched-in to show schedule duration */ fstack = fstack_get(task, task->stack_count); if (fstack_enabled && fstack != NULL && !(fstack->flags & FSTACK_FL_NORECORD)) { if (evt_id == EVENT_ID_PERF_SCHED_IN && fstack->total_time) print_field(task, fstack, NULL); else print_field(task, NULL, NO_TIME); pr_color(task->event_color, "%*s/* ", depth * 2, ""); print_event(task, &rec, task->event_color); pr_color(task->event_color, " */\n"); } } out: symbol_putname(sym, symname); return 0; } static void print_warning(struct uftrace_task_reader *task) { if (print_empty_field(&output_fields, 1)) pr_out(" | "); pr_red(" %*s/* inverted time: broken data? */\n", (task->display_depth + 1) * 2, ""); } static bool skip_sys_exit(struct opts *opts, struct uftrace_task_reader *task) { struct sym *sym; struct fstack *fstack; fstack = fstack_get(task, 0); if (fstack == NULL) return true; /* skip 'sys_exit[_group] at last for kernel tracing */ if (!has_kernel_data(task->h->kernel) || task->user_stack_count != 0) return false; sym = find_symtabs(&task->h->sessions.first->symtabs, fstack->addr); if (sym == NULL) return false; /* Linux 4.17 added __x64_sys_exit, __ia32_sys_exit and so on */ if (strstr(sym->name, "sys_exit")) return true; if (!strcmp(sym->name, "do_syscall_64")) return true; return false; } static void print_remaining_stack(struct opts *opts, struct uftrace_data *handle) { int i, k; int total = 0; struct uftrace_session_link *sessions = &handle->sessions; for (i = 0; i < handle->nr_tasks; i++) { struct uftrace_task_reader *task = &handle->tasks[i]; int zero_count = 0; if (skip_sys_exit(opts, task)) continue; for (k = 0; k < task->stack_count; k++) { struct fstack *fstack; fstack = fstack_get(task, k); if (fstack != NULL && fstack->addr != 0) break; zero_count++; } total += task->stack_count - zero_count; } if (total == 0) return; pr_out("\nuftrace stopped tracing with remaining functions"); pr_out("\n================================================\n"); for (i = 0; i < handle->nr_tasks; i++) { struct uftrace_task_reader *task = &handle->tasks[i]; struct fstack *fstack; int zero_count = 0; if (task->stack_count == 0) continue; for (k = 0; k < task->stack_count; k++) { fstack = fstack_get(task, k); if (fstack != NULL && fstack->addr != 0) break; zero_count++; } if (zero_count == task->stack_count) continue; if (skip_sys_exit(opts, task)) continue; pr_out("task: %d\n", task->tid); while (task->stack_count-- > 0) { uint64_t time; uint64_t ip; struct sym *sym; char *symname; fstack = fstack_get(task, task->stack_count); if (fstack == NULL) continue; time = fstack->total_time; ip = fstack->addr; sym = task_find_sym_addr(sessions, task, time, ip); symname = symbol_getname(sym, ip); pr_out("[%d] %s\n", task->stack_count - zero_count, symname); symbol_putname(sym, symname); if (task->stack_count == zero_count) break; } pr_out("\n"); } } int command_replay(int argc, char *argv[], struct opts *opts) { int ret; uint64_t prev_time = 0; struct uftrace_data handle; struct uftrace_task_reader *task; __fsetlocking(outfp, FSETLOCKING_BYCALLER); __fsetlocking(logfp, FSETLOCKING_BYCALLER); ret = open_data_file(opts, &handle); if (ret < 0) { pr_warn("cannot open record data: %s: %m\n", opts->dirname); return -1; } fstack_setup_filters(opts, &handle); setup_field(&output_fields, opts, &setup_default_field, field_table, ARRAY_SIZE(field_table)); if (!opts->flat && peek_rstack(&handle, &task) == 0) print_header(&output_fields, "#", "FUNCTION", 1); while (read_rstack(&handle, &task) == 0 && !uftrace_done) { struct uftrace_record *rstack = task->rstack; uint64_t curr_time = rstack->time; if (!fstack_check_opts(task, opts)) continue; /* * data sanity check: timestamp should be ordered. * But print_graph_rstack() may change task->rstack * during fstack_skip(). So check the timestamp here. */ if (curr_time) { if (prev_time > curr_time) print_warning(task); prev_time = rstack->time; } if (opts->flat) ret = print_flat_rstack(&handle, task, opts); else ret = print_graph_rstack(&handle, task, opts); if (ret) break; } print_remaining_stack(opts, &handle); close_data_file(opts, &handle); return ret; } uftrace-0.9.4/cmds/report.c000066400000000000000000000407651362052523300155770ustar00rootroot00000000000000#include #include #include #include "uftrace.h" #include "utils/utils.h" #include "utils/rbtree.h" #include "utils/symbol.h" #include "utils/list.h" #include "utils/fstack.h" #include "utils/report.h" enum { AVG_NONE, AVG_TOTAL, AVG_SELF, AVG_ANY, } avg_mode = AVG_NONE; /* maximum length of symbol */ static int maxlen = 20; static void insert_node(struct rb_root *root, struct uftrace_task_reader *task, char *symname) { struct uftrace_report_node *node; node = report_find_node(root, symname); if (node == NULL) { node = xzalloc(sizeof(*node)); report_add_node(root, symname, node); } report_update_node(node, task); } static void find_insert_node(struct rb_root *root, struct uftrace_task_reader *task, uint64_t timestamp, uint64_t addr) { struct sym *sym; char *symname; sym = task_find_sym_addr(&task->h->sessions, task, timestamp, addr); symname = symbol_getname(sym, addr); insert_node(root, task, symname); symbol_putname(sym, symname); } static void add_lost_fstack(struct rb_root *root, struct uftrace_task_reader *task) { struct fstack *fstack; while (task->stack_count >= task->user_stack_count) { fstack = fstack_get(task, task->stack_count); if (fstack_enabled && fstack && fstack->valid && !(fstack->flags & FSTACK_FL_NORECORD)) { find_insert_node(root, task, task->timestamp_last, fstack->addr); } fstack_exit(task); task->stack_count--; } } static void add_remaining_fstack(struct uftrace_data *handle, struct rb_root *root) { struct uftrace_task_reader *task; struct fstack *fstack; int i; for (i = 0; i < handle->nr_tasks; i++) { uint64_t last_time; task = &handle->tasks[i]; if (task->stack_count == 0) continue; last_time = task->rstack->time; if (handle->time_range.stop) last_time = handle->time_range.stop; while (--task->stack_count >= 0) { fstack = fstack_get(task, task->stack_count); if (fstack == NULL) continue; if (fstack->total_time > last_time) continue; fstack->total_time = last_time - fstack->total_time; if (fstack->child_time > fstack->total_time) fstack->total_time = fstack->child_time; if (task->stack_count > 0) fstack[-1].child_time += fstack->total_time; if (fstack->addr == EVENT_ID_PERF_SCHED_IN) insert_node(root, task, sched_sym.name); else find_insert_node(root, task, last_time, fstack->addr); } } } static void build_function_tree(struct uftrace_data *handle, struct rb_root *root, struct opts *opts) { struct uftrace_session_link *sessions = &handle->sessions; struct sym *sym = NULL; struct uftrace_record *rstack; struct uftrace_task_reader *task; uint64_t addr; while (read_rstack(handle, &task) >= 0 && !uftrace_done) { rstack = task->rstack; if (rstack->type != UFTRACE_LOST) task->timestamp_last = rstack->time; if (!fstack_check_opts(task, opts)) continue; if (!fstack_check_filter(task)) continue; if (rstack->type == UFTRACE_ENTRY) continue; if (rstack->type == UFTRACE_EVENT) { if (rstack->addr == EVENT_ID_PERF_SCHED_IN) insert_node(root, task, sched_sym.name); continue; } if (rstack->type == UFTRACE_LOST) { /* add partial duration of functions before LOST */ add_lost_fstack(root, task); continue; } /* rstack->type == UFTRACE_EXIT */ addr = rstack->addr; if (is_kernel_record(task, rstack)) { struct uftrace_session *fsess; fsess = sessions->first; addr = get_kernel_address(&fsess->symtabs, rstack->addr); } /* skip it if --no-libcall is given */ sym = task_find_sym(sessions, task, rstack); if (!opts->libcall && sym && sym->type == ST_PLT_FUNC) continue; find_insert_node(root, task, rstack->time, addr); } if (uftrace_done) return; add_remaining_fstack(handle, root); } static void print_and_delete(struct rb_root *root, bool sorted, void *arg, void (*print_func)(struct uftrace_report_node *, void *)) { while (!RB_EMPTY_ROOT(root)) { struct rb_node *n; struct uftrace_report_node *node; n = rb_first(root); rb_erase(n, root); if (sorted) node = rb_entry(n, typeof(*node), sort_link); else node = rb_entry(n, typeof(*node), name_link); print_func(node, arg); free(node->name); free(node); } } static void print_function(struct uftrace_report_node *node, void *unused) { if (avg_mode == AVG_NONE) { pr_out(" "); print_time_unit(node->total.sum); pr_out(" "); print_time_unit(node->self.sum); pr_out(" %10"PRIu64 " %-s\n", node->call, node->name); } else { uint64_t time_avg, time_min, time_max; if (avg_mode == AVG_TOTAL) { time_avg = node->total.avg; time_min = node->total.min; time_max = node->total.max; } else { time_avg = node->self.avg; time_min = node->self.min; time_max = node->self.max; } pr_out(" "); print_time_unit(time_avg); pr_out(" "); print_time_unit(time_min); pr_out(" "); print_time_unit(time_max); pr_out(" %-s\n", node->name); } } static void report_functions(struct uftrace_data *handle, struct opts *opts) { struct rb_root name_root = RB_ROOT; struct rb_root sort_root = RB_ROOT; const char f_format[] = " %10.10s %10.10s %10.10s %-.*s\n"; const char line[] = "================================================="; build_function_tree(handle, &name_root, opts); report_calc_avg(&name_root); report_sort_nodes(&name_root, &sort_root); if (uftrace_done) return; if (avg_mode == AVG_NONE) pr_out(f_format, "Total time", "Self time", "Calls", maxlen, "Function"); else if (avg_mode == AVG_TOTAL) pr_out(f_format, "Avg total", "Min total", "Max total", maxlen, "Function"); else if (avg_mode == AVG_SELF) pr_out(f_format, "Avg self", "Min self", "Max self", maxlen, "Function"); pr_out(f_format, line, line, line, maxlen, line); print_and_delete(&sort_root, true, NULL, print_function); } static void add_remaining_task_fstack(struct uftrace_data *handle, struct rb_root *root) { struct uftrace_task_reader *task; struct fstack *fstack; char buf[10]; int i; for (i = 0; i < handle->nr_tasks; i++) { uint64_t last_time; task = &handle->tasks[i]; if (task->stack_count == 0) continue; last_time = task->timestamp_last; if (handle->time_range.stop) last_time = handle->time_range.stop; while (--task->stack_count >= 0) { fstack = fstack_get(task, task->stack_count); if (fstack == NULL) continue; if (fstack->addr == 0) continue; if (fstack->total_time > last_time) continue; if (fstack->addr == EVENT_ID_PERF_SCHED_IN) { if (task->t->time.stamp) { task->t->time.idle += last_time - fstack->total_time; } task->t->time.stamp = 0; } fstack->total_time = last_time - fstack->total_time; if (fstack->child_time > fstack->total_time) fstack->total_time = fstack->child_time; if (task->stack_count > 0) fstack[-1].child_time += fstack->total_time; snprintf(buf, sizeof(buf), "%d", task->tid); insert_node(root, task, buf); } } } static void adjust_task_runtime(struct uftrace_data *handle, struct rb_root *root) { struct uftrace_task *t; struct uftrace_report_node *node; struct rb_node *n = rb_first(root); int tid; while (n != NULL) { node = rb_entry(n, struct uftrace_report_node, name_link); n = rb_next(n); tid = strtol(node->name, NULL, 0); t = find_task(&handle->sessions, tid); /* total = runtime, self = cputime (= total - idle) */ memcpy(&node->total, &node->self, sizeof(node->self)); memset(&node->self, 0, sizeof(node->self)); node->self.sum = node->total.sum - t->time.idle; } } static void print_thread(struct uftrace_report_node *node, void *arg) { int pid; struct uftrace_task *t; struct uftrace_data *handle = arg; pid = strtol(node->name, NULL, 10); t = find_task(&handle->sessions, pid); pr_out(" "); print_time_unit(node->total.sum); pr_out(" "); print_time_unit(node->self.sum); pr_out(" %10"PRIu64 " %6d %-s\n", node->call, pid, t->comm); } static void report_task(struct uftrace_data *handle, struct opts *opts) { struct uftrace_record *rstack; struct rb_root task_tree = RB_ROOT; struct rb_root sort_tree = RB_ROOT; struct uftrace_task_reader *task; const char t_format[] = " %10.10s %10.10s %10.10s %6.6s %-16.16s\n"; const char line[] = "================================================="; char buf[10]; while (read_rstack(handle, &task) >= 0 && !uftrace_done) { rstack = task->rstack; if (rstack->type == UFTRACE_ENTRY || rstack->type == UFTRACE_LOST) continue; if (!fstack_check_opts(task, opts)) continue; if (!fstack_check_filter(task)) continue; task->timestamp_last = rstack->time; if (rstack->type == UFTRACE_EVENT) { if (rstack->addr == EVENT_ID_PERF_SCHED_OUT) { task->t->time.stamp = rstack->time; continue; } else if (rstack->addr == EVENT_ID_PERF_SCHED_IN) { if (task->t->time.stamp) { task->t->time.idle += rstack->time - task->t->time.stamp; } task->t->time.stamp = 0; } else { continue; } } /* UFTRACE_EXIT */ snprintf(buf, sizeof(buf), "%d", task->tid); insert_node(&task_tree, task, buf); } if (uftrace_done) return; add_remaining_task_fstack(handle, &task_tree); adjust_task_runtime(handle, &task_tree); report_sort_tasks(handle, &task_tree, &sort_tree); pr_out(t_format, "Total time", "Self time", "Num funcs", "TID", "Task name"); pr_out(t_format, line, line, line, line, line); print_and_delete(&sort_tree, true, handle, print_thread); } struct diff_data { char *dirname; struct rb_root root; struct uftrace_data handle; }; #define NODATA "-" static void print_time_or_dash(uint64_t time_nsec) { if (time_nsec) print_time_unit(time_nsec); else pr_out("%10s", NODATA); } static void print_function_diff(struct uftrace_report_node *node, void *arg) { struct uftrace_report_node *pair = node->pair; if (avg_mode == AVG_NONE) { pr_out(" "); if (diff_policy.full) { print_time_or_dash(node->total.sum); pr_out(" "); print_time_or_dash(pair->total.sum); pr_out(" "); } else if (diff_policy.percent) pr_out(" "); if (diff_policy.percent) print_diff_percent(node->total.sum, pair->total.sum); else print_diff_time_unit(node->total.sum, pair->total.sum); pr_out(" "); if (diff_policy.full) { print_time_or_dash(node->self.sum); pr_out(" "); print_time_or_dash(pair->self.sum); pr_out(" "); } else if (diff_policy.percent) pr_out(" "); if (diff_policy.percent) print_diff_percent(node->self.sum, pair->self.sum); else print_diff_time_unit(node->self.sum, pair->self.sum); pr_out(" "); if (diff_policy.full) pr_out(" %9"PRIu64" %9"PRIu64, node->call, pair->call); pr_out(" "); print_diff_count(node->call, pair->call); pr_out(" %-s\n", node->name); } else { uint64_t time_avg, time_min, time_max; uint64_t pair_avg, pair_min, pair_max; if (avg_mode == AVG_TOTAL) { time_avg = node->total.avg; time_min = node->total.min; time_max = node->total.max; pair_avg = pair->total.avg; pair_min = pair->total.min; pair_max = pair->total.max; } else { time_avg = node->self.avg; time_min = node->self.min; time_max = node->self.max; pair_avg = pair->self.avg; pair_min = pair->self.min; pair_max = pair->self.max; } pr_out(" "); if (diff_policy.full) { print_time_unit(time_avg); pr_out(" "); print_time_unit(time_avg); pr_out(" "); } else if (diff_policy.percent) pr_out(" "); if (diff_policy.percent) print_diff_percent(time_avg, pair_avg); else print_diff_time_unit(time_avg, pair_avg); pr_out(" "); if (diff_policy.full) { print_time_unit(time_min); pr_out(" "); print_time_unit(pair_min); pr_out(" "); } else if (diff_policy.percent) pr_out(" "); if (diff_policy.percent) print_diff_percent(time_min, pair_min); else print_diff_time_unit(time_min, pair_min); pr_out(" "); if (diff_policy.full) { print_time_unit(time_max); pr_out(" "); print_time_unit(pair_max); pr_out(" "); } else if (diff_policy.percent) pr_out(" "); if (diff_policy.percent) print_diff_percent(time_max, pair_max); else print_diff_time_unit(time_max, pair_max); pr_out(" %-s\n", node->name); } } static void report_diff(struct uftrace_data *handle, struct opts *opts) { struct opts dummy_opts = { .dirname = opts->diff, .kernel = opts->kernel, .depth = opts->depth, .libcall = opts->libcall, }; struct diff_data data = { .dirname = opts->diff, .root = RB_ROOT, }; struct rb_root base_tree = RB_ROOT; struct rb_root pair_tree = RB_ROOT; struct rb_root diff_tree = RB_ROOT; const char *formats[] = { " %35.35s %35.35s %32.32s %-.*s\n", /* diff numbers */ " %32.32s %32.32s %32.32s %-.*s\n", /* diff percent */ " %35.35s %35.35s %35.35s %-.*s\n", /* diff avg numbers */ " %11.11s %11.11s %11.11s %-.*s\n", /* diff compact */ }; const char line[] = "================================================="; const char *headers[][3] = { { "Total time (diff)", "Self time (diff)", "Calls (diff)" }, { "Avg total (diff)", "Min total (diff)", "Max total (diff)" }, { "Avg self (diff)", "Min self (diff)", "Max self (diff)" }, { "Total time", "Self time", "Calls" }, { "Avg total", "Min total", "Max total" }, { "Avg self", "Min self", "Max self" }, }; int h_idx = (avg_mode == AVG_NONE) ? 0 : (avg_mode == AVG_TOTAL) ? 1 : 2; int f_idx = diff_policy.percent ? 1 : (avg_mode == AVG_NONE) ? 0 : 2; if (!diff_policy.full) { h_idx += 3; f_idx = 3; } build_function_tree(handle, &base_tree, opts); report_calc_avg(&base_tree); if (open_data_file(&dummy_opts, &data.handle) < 0) { pr_warn("cannot open record data: %s: %m\n", opts->diff); goto out; } fstack_setup_filters(&dummy_opts, &data.handle); build_function_tree(&data.handle, &pair_tree, &dummy_opts); report_calc_avg(&pair_tree); report_diff_nodes(&base_tree, &pair_tree, &diff_tree, opts->sort_column); if (uftrace_done) goto out; pr_out("#\n"); pr_out("# uftrace diff\n"); pr_out("# [%d] base: %s\t(from %s)\n", 0, handle->dirname, handle->info.cmdline); pr_out("# [%d] diff: %s\t(from %s)\n", 1, opts->diff, data.handle.info.cmdline); pr_out("#\n"); pr_out(formats[f_idx], headers[h_idx][0], headers[h_idx][1], headers[h_idx][2], maxlen, "Function"); pr_out(formats[f_idx], line, line, line, maxlen, line); print_and_delete(&diff_tree, true, NULL, print_function_diff); out: destroy_diff_nodes(&diff_tree); close_data_file(&dummy_opts, &data.handle); } char * convert_sort_keys(char *sort_keys) { const char *default_sort_key[] = { OPT_SORT_KEYS, "total_avg", "self_avg" }; struct strv keys = STRV_INIT; char *new_keys; char *k; int i; if (sort_keys == NULL) return xstrdup(default_sort_key[avg_mode]); if (avg_mode == AVG_NONE) return xstrdup(sort_keys); strv_split(&keys, sort_keys, ","); strv_for_each(&keys, k, i) { if (!strcmp(k, "avg")) { strv_replace(&keys, i, avg_mode == AVG_TOTAL ? "total_avg" : "self_avg"); } else if (!strcmp(k, "min")) { strv_replace(&keys, i, avg_mode == AVG_TOTAL ? "total_min" : "self_min"); } else if (!strcmp(k, "max")) { strv_replace(&keys, i, avg_mode == AVG_TOTAL ? "total_max" : "self_max"); } } new_keys = strv_join(&keys, ","); strv_free(&keys); return new_keys; } int command_report(int argc, char *argv[], struct opts *opts) { int ret; char *sort_keys; struct uftrace_data handle; if (opts->avg_total && opts->avg_self) { pr_use("--avg-total and --avg-self options should not be used together.\n"); exit(1); } else if (opts->avg_total) avg_mode = AVG_TOTAL; else if (opts->avg_self) avg_mode = AVG_SELF; ret = open_data_file(opts, &handle); if (ret < 0) { pr_warn("cannot open record data: %s: %m\n", opts->dirname); return -1; } fstack_setup_filters(opts, &handle); if (opts->diff) { sort_keys = convert_sort_keys(opts->sort_keys); ret = report_setup_diff(sort_keys); } else if (opts->show_task) { if (opts->sort_keys == NULL) sort_keys = xstrdup(OPT_SORT_KEYS); else sort_keys = xstrdup(opts->sort_keys); ret = report_setup_task(sort_keys); } else { sort_keys = convert_sort_keys(opts->sort_keys); ret = report_setup_sort(sort_keys); } free(sort_keys); if (ret < 0) { pr_use("invalid sort key: %s\n", opts->sort_keys); return -1; } if (opts->diff_policy) apply_diff_policy(opts->diff_policy); if (opts->show_task) report_task(&handle, opts); else if (opts->diff) report_diff(&handle, opts); else report_functions(&handle, opts); close_data_file(opts, &handle); return 0; } uftrace-0.9.4/cmds/script.c000066400000000000000000000111241362052523300155530ustar00rootroot00000000000000/* * Python script binding for function entry and exit * * Copyright (C) 2017-2018, LG Electronics, Honggyu Kim * * Released under the GPL v2. */ #include #include #include #include "uftrace.h" #include "version.h" #include "utils/utils.h" #include "utils/symbol.h" #include "utils/filter.h" #include "utils/fstack.h" #include "utils/script.h" #include "libtraceevent/event-parse.h" static int run_script_for_rstack(struct uftrace_data *handle, struct uftrace_task_reader *task, struct opts *opts) { struct uftrace_record *rstack = task->rstack; struct uftrace_session_link *sessions = &handle->sessions; struct sym *sym = NULL; char *symname = NULL; sym = task_find_sym(sessions, task, rstack); symname = symbol_getname(sym, rstack->addr); /* skip it if --no-libcall is given */ if (!opts->libcall && sym && sym->type == ST_PLT_FUNC) goto out; task->timestamp_last = task->timestamp; task->timestamp = rstack->time; if (rstack->type == UFTRACE_ENTRY) { struct fstack *fstack; int depth; struct uftrace_trigger tr = { .flags = 0, }; int ret; ret = fstack_entry(task, rstack, &tr); if (ret < 0) goto out; /* display depth is set in fstack_entry() */ depth = task->display_depth; fstack = fstack_get(task ,task->stack_count - 1); fstack_update(UFTRACE_ENTRY, task, fstack); if (!script_match_filter(symname)) goto out; /* setup context for script execution */ struct script_context sc_ctx = { .tid = task->tid, .depth = depth, /* display depth */ .timestamp = rstack->time, .address = rstack->addr, .name = symname, }; if (tr.flags & TRIGGER_FL_ARGUMENT) { sc_ctx.argbuf = task->args.data; sc_ctx.arglen = task->args.len; sc_ctx.argspec = task->args.args; } /* script hooking for function entry */ script_uftrace_entry(&sc_ctx); } else if (rstack->type == UFTRACE_EXIT) { struct fstack *fstack; /* function exit */ fstack = fstack_get(task, task->stack_count); if (fstack_enabled && fstack && !(fstack->flags & FSTACK_FL_NORECORD)) { int depth = fstack_update(UFTRACE_EXIT, task, fstack); if (!script_match_filter(symname)) { fstack_exit(task); goto out; } /* display depth is set before passing rstack */ rstack->depth = depth; /* setup context for script execution */ struct script_context sc_ctx = { .tid = task->tid, .depth = rstack->depth, .timestamp = rstack->time, .duration = fstack->total_time, .address = rstack->addr, .name = symname, }; if (rstack->more) { sc_ctx.argbuf = task->args.data; sc_ctx.arglen = task->args.len; sc_ctx.argspec = task->args.args; } /* script hooking for function exit */ script_uftrace_exit(&sc_ctx); } fstack_exit(task); } else if (rstack->type == UFTRACE_LOST) { /* Do nothing as of now */ } else if (rstack->type == UFTRACE_EVENT) { /* TODO: event handling */ } out: symbol_putname(sym, symname); return 0; } int command_script(int argc, char *argv[], struct opts *opts) { int ret; struct uftrace_data handle; struct uftrace_task_reader *task; struct script_info info = { .name = opts->script_file, .version = UFTRACE_VERSION, }; if (!SCRIPT_ENABLED) { pr_warn("script command is not supported due to missing libpython2.7.so\n"); return -1; } if (!opts->script_file) { pr_out("Usage: uftrace script (-S|--script) \n"); return -1; } if (opts->record) { /* parse in-script record option - "uftrace_options" */ parse_script_opt(opts); char *script_file = opts->script_file; opts->script_file = NULL; pr_dbg("start recording before running a script\n"); ret = command_record(argc, argv, opts); if (ret < 0) { pr_warn("cannot record data: %m\n"); return -1; } opts->script_file = script_file; } __fsetlocking(outfp, FSETLOCKING_BYCALLER); __fsetlocking(logfp, FSETLOCKING_BYCALLER); ret = open_data_file(opts, &handle); if (ret < 0) { pr_warn("cannot open record data: %s: %m\n", opts->dirname); return -1; } fstack_setup_filters(opts, &handle); strv_copy(&info.cmds, argc, argv); /* initialize script */ if (script_init(&info, opts->patt_type) < 0) { ret = -1; goto out; } while (read_rstack(&handle, &task) == 0 && !uftrace_done) { if (!fstack_check_opts(task, opts)) continue; ret = run_script_for_rstack(&handle, task, opts); if (ret) break; } /* dtor for script support */ script_uftrace_end(); out: script_finish(); close_data_file(opts, &handle); strv_free(&info.cmds); return ret; } uftrace-0.9.4/cmds/tui.c000066400000000000000000001662741362052523300150710ustar00rootroot00000000000000#ifdef HAVE_LIBNCURSES #include #include #include #include #include #include #include #include #include #include #include "uftrace.h" #include "version.h" #include "utils/utils.h" #include "utils/fstack.h" #include "utils/graph.h" #include "utils/report.h" #include "utils/list.h" #include "utils/rbtree.h" #include "utils/field.h" #include "utils/dwarf.h" #define KEY_ESCAPE 27 #define BLANK 32 static bool tui_finished; static bool tui_debug; struct tui_graph_node { struct uftrace_graph_node n; struct uftrace_graph *graph; struct list_head link; // for tui_report_node.head bool folded; }; struct tui_report_node { struct uftrace_report_node n; struct list_head head; // links tui_graph_node.link }; struct tui_list_node { struct list_head list; void *data; }; struct tui_window; struct tui_window_ops { void * (*prev)(struct tui_window *win, void *node, bool update); void * (*next)(struct tui_window *win, void *node, bool update); void * (*top)(struct tui_window *win, bool update); void * (*parent)(struct tui_window *win, void *node); void * (*sibling_prev)(struct tui_window *win, void *node); void * (*sibling_next)(struct tui_window *win, void *node); bool (*needs_blank)(struct tui_window *win, void *prev, void *next); bool (*enter)(struct tui_window *win, void *node); bool (*collapse)(struct tui_window *win, void *node, bool all, int depth); bool (*expand)(struct tui_window *win, void *node, bool all, int depth); void (*header)(struct tui_window *win, struct uftrace_data *handle); void (*footer)(struct tui_window *win, struct uftrace_data *handle); void (*display)(struct tui_window *win, void *node); bool (*search)(struct tui_window *win, void *node, char *str); bool (*longest_child)(struct tui_window *win, void *node); struct debug_location * (*location)(struct tui_window *win, void *node); }; struct tui_window { const struct tui_window_ops *ops; void *top; void *curr; void *old; int top_index; int curr_index; int last_index; int search_count; }; struct tui_report { struct tui_window win; struct list_head list; struct rb_root name_tree; struct rb_root sort_tree; int nr_sess; int nr_func; }; struct tui_graph { struct tui_window win; struct uftrace_graph ug; struct list_head list; struct tui_graph_node *disp; int top_depth; int curr_depth; int disp_depth; int width; bool *top_mask; bool *disp_mask; size_t mask_size; bool disp_update; }; struct tui_list { struct tui_window win; struct list_head head; int nr_node; }; static LIST_HEAD(tui_graph_list); static LIST_HEAD(graph_output_fields); static struct tui_report tui_report; static struct tui_graph partial_graph; static struct tui_list tui_info; static struct tui_list tui_session; static char *tui_search; static const struct tui_window_ops graph_ops; static const struct tui_window_ops report_ops; static const struct tui_window_ops info_ops; static const struct tui_window_ops session_ops; static void tui_window_move_down(struct tui_window *win); #define FIELD_SPACE 2 #define FIELD_SEP " :" #define POS_SIZE 5 #define C_NORMAL 0 #define C_HEADER 1 #define C_GREEN 2 #define C_YELLOW 3 #define C_RED 4 static const char *help[] = { "ARROW Navigation", "PgUp/Dn", "Home/End", "Enter Fold/unfold graph or Select session", "G Show (full) call graph", "g Show call graph for this function", "R Show uftrace report", "r Show uftrace report for this function", "s Sort by the next column in report", "I Show uftrace info", "S Change session", "O Open editor", "c/e Collapse/Expand direct children graph", "C/E Collapse/Expand all descendant graph", "n/p Next/Prev sibling", "u Move up to parent", "l Move to the longest executed child", "j/k Move down/up", "z Set current line to the center of screen", "/ Search", "/N/P Search next/prev", "v Show debug message", "h/? Show this help", "q Quit", }; static const char *report_sort_key[] = { OPT_SORT_KEYS, "self", "call" }; static int curr_sort_key = 0; static void init_colors(void) { if (!has_colors()) return; start_color(); /* C_NORMAL uses the default color pair */ init_pair(C_HEADER, COLOR_WHITE, COLOR_BLUE); init_pair(C_GREEN, COLOR_GREEN, COLOR_BLACK); init_pair(C_YELLOW, COLOR_YELLOW, COLOR_BLACK); init_pair(C_RED, COLOR_RED, COLOR_BLACK); } static void print_time(uint64_t ntime) { char *units[] = { "us", "ms", " s", " m", " h", }; int pairs[] = { C_NORMAL, C_GREEN, C_YELLOW, C_RED, C_RED }; unsigned limit[] = { 1000, 1000, 1000, 60, 24, INT_MAX, }; uint64_t fract; unsigned idx; if (ntime == 0UL) { printw("%7s %2s", "", ""); return; } for (idx = 0; idx < ARRAY_SIZE(units); idx++) { fract = ntime % limit[idx]; ntime = ntime / limit[idx]; if (ntime < limit[idx+1]) break; } /* for some error cases */ if (ntime > 999) ntime = fract = 999; printw("%3"PRIu64".%03"PRIu64" ", ntime, fract); attron(COLOR_PAIR(pairs[idx])); printw("%2s", units[idx]); attroff(COLOR_PAIR(pairs[idx])); } static void print_graph_total(struct field_data *fd) { struct uftrace_graph_node *node = fd->arg; uint64_t d; d = node->time; print_time(d); } static void print_graph_self(struct field_data *fd) { struct uftrace_graph_node *node = fd->arg; uint64_t d; d = node->time - node->child_time; print_time(d); } static void print_graph_addr(struct field_data *fd) { struct uftrace_graph_node *node = fd->arg; /* uftrace records (truncated) 48-bit addresses */ int width = sizeof(long) == 4 ? 8 : 12; printw("%*"PRIx64, width, effective_addr(node->addr)); } static struct display_field field_total_time= { .id = GRAPH_F_TOTAL_TIME, .name = "total-time", .alias = "total", .header = "TOTAL TIME", .length = 10, .print = print_graph_total, .list = LIST_HEAD_INIT(field_total_time.list), }; static struct display_field field_self_time= { .id = GRAPH_F_SELF_TIME, .name = "self-time", .alias = "self", .header = " SELF TIME", .length = 10, .print = print_graph_self, .list = LIST_HEAD_INIT(field_self_time.list), }; static struct display_field field_addr = { .id = GRAPH_F_ADDR, .name = "address", .alias = "addr", #if __SIZEOF_LONG == 4 .header = " ADDR ", .length = 8, #else .header = " ADDRESS ", .length = 12, #endif .print = print_graph_addr, .list = LIST_HEAD_INIT(field_addr.list), }; /* index of this table should be matched to display_field_id */ static struct display_field *graph_field_table[] = { &field_total_time, &field_self_time, &field_addr, }; static void setup_default_graph_field(struct list_head *fields, struct opts *opts) { add_field(fields, graph_field_table[GRAPH_F_TOTAL_TIME]); } static inline bool is_first_child(struct tui_graph_node *prev, struct tui_graph_node *next) { return prev->n.head.next == &next->n.list; } static inline bool is_last_child(struct tui_graph_node *prev, struct tui_graph_node *next) { return prev->n.head.prev == &next->n.list; } static int create_data(struct uftrace_session *sess, void *arg) { struct tui_graph *graph = xzalloc(sizeof(*graph)); pr_dbg("create graph for session %.*s (%s)\n", SESSION_ID_LEN, sess->sid, sess->exename); graph_init(&graph->ug, sess); list_add_tail(&graph->list, &tui_graph_list); tui_report.nr_sess++; return 0; } static void tui_setup(struct uftrace_data *handle, struct opts *opts) { walk_sessions(&handle->sessions, create_data, NULL); tui_report.name_tree = RB_ROOT; setup_field(&graph_output_fields, opts, setup_default_graph_field, graph_field_table, ARRAY_SIZE(graph_field_table)); } static void tui_cleanup(void) { struct tui_graph *graph; if (!tui_finished) endwin(); tui_finished = true; while (!list_empty(&tui_graph_list)) { graph = list_first_entry(&tui_graph_list, typeof(*graph), list); list_del(&graph->list); free(graph); } graph_remove_task(); } static struct uftrace_graph * get_graph(struct uftrace_task_reader *task, uint64_t time, uint64_t addr) { struct tui_graph *graph; struct uftrace_session_link *sessions = &task->h->sessions; struct uftrace_session *sess; sess = find_task_session(sessions, task->t, time); if (sess == NULL) { struct uftrace_session *fsess = sessions->first; if (is_kernel_address(&fsess->symtabs, addr)) sess = fsess; else return NULL; } list_for_each_entry(graph, &tui_graph_list, list) { if (graph->ug.sess == sess) return &graph->ug; } return NULL; } static bool list_is_none(struct list_head *list) { return list->next == NULL && list->prev == NULL; } static void update_report_node(struct uftrace_task_reader *task, char *symname, struct uftrace_task_graph *tg) { struct tui_report_node *node; struct tui_graph_node *graph_node; /* graph is not set probably due to filters (or error?) */ if (tg->node == NULL) return; node = (struct tui_report_node *)report_find_node(&tui_report.name_tree, symname); if (node == NULL) { node = xzalloc(sizeof(*node)); INIT_LIST_HEAD(&node->head); report_add_node(&tui_report.name_tree, symname, (void *)node); tui_report.nr_func++; } graph_node = (struct tui_graph_node *)tg->node; if (list_is_none(&graph_node->link)) list_add_tail(&graph_node->link, &node->head); report_update_node(&node->n, task); } static int build_tui_node(struct uftrace_task_reader *task, struct uftrace_record *rec, struct opts *opts) { struct uftrace_task_graph *tg; struct uftrace_graph *graph; struct tui_graph_node *graph_node; struct sym *sym; char *name; uint64_t addr = rec->addr; if (is_kernel_record(task, rec)) { struct uftrace_session *fsess; fsess = task->h->sessions.first; addr = get_kernel_address(&fsess->symtabs, addr); } tg = graph_get_task(task, sizeof(*tg)); graph = get_graph(task, rec->time, addr); if (tg->node == NULL || tg->graph != graph) tg->node = &graph->root; tg->graph = graph; if (rec->type == UFTRACE_ENTRY || rec->type == UFTRACE_EXIT) { sym = task_find_sym_addr(&task->h->sessions, task, rec->time, addr); /* skip it if --no-libcall is given */ if (!opts->libcall && sym && sym->type == ST_PLT_FUNC) return 0; name = symbol_getname(sym, addr); if (rec->type == UFTRACE_EXIT) update_report_node(task, name, tg); } else if (rec->type == UFTRACE_EVENT) { sym = &sched_sym; name = symbol_getname(sym, addr); if (addr == EVENT_ID_PERF_SCHED_IN) update_report_node(task, name, tg); else if (addr != EVENT_ID_PERF_SCHED_OUT) return 0; } else /* rec->type == UFTRACE_LOST */ return 0; graph_add_node(tg, rec->type, name, sizeof(struct tui_graph_node)); if (tg->node && tg->node != &graph->root) { graph_node = (struct tui_graph_node *)tg->node; graph_node->graph = graph; } symbol_putname(sym, name); return 0; } static void add_remaining_node(struct opts *opts, struct uftrace_data *handle) { uint64_t last_time; struct fstack *fstack; struct uftrace_task_reader *task; struct uftrace_task_graph *tg; struct sym *sym; char *name; int i; for (i = 0; i < handle->nr_tasks; i++) { task = &handle->tasks[i]; if (task->stack_count == 0) continue; if (opts->kernel_skip_out && task->user_stack_count == 0) continue; last_time = task->rstack->time; if (handle->time_range.stop) last_time = handle->time_range.stop; while (--task->stack_count >= 0) { fstack = fstack_get(task, task->stack_count); if (fstack == NULL) continue; if (fstack->addr == 0) continue; if (fstack->total_time > last_time) continue; tg = graph_get_task(task, sizeof(*tg)); sym = task_find_sym_addr(&handle->sessions, task, fstack->total_time, fstack->addr); name = symbol_getname(sym, fstack->addr); fstack->total_time = last_time - fstack->total_time; if (fstack->child_time > fstack->total_time) fstack->total_time = fstack->child_time; if (task->stack_count > 0) fstack[-1].child_time += fstack->total_time; update_report_node(task, name, tg); graph_add_node(tg, UFTRACE_EXIT, name, sizeof(struct tui_graph_node)); symbol_putname(sym, name); } } } static struct tui_graph_node * append_graph_node(struct uftrace_graph_node *dst, struct tui_graph *graph, char *name) { struct tui_graph_node *node; node = xzalloc(sizeof(*node)); node->n.name = xstrdup(name); INIT_LIST_HEAD(&node->n.head); node->n.parent = dst; node->graph = &graph->ug; list_add_tail(&node->n.list, &dst->head); dst->nr_edges++; return node; } static void copy_graph_node(struct uftrace_graph_node *dst, struct uftrace_graph_node *src) { struct uftrace_graph_node *child; struct tui_graph_node *node; list_for_each_entry(child, &src->head, list) { list_for_each_entry(node, &dst->head, n.list) { if (!strcmp(child->name, node->n.name)) break; } if (list_no_entry(node, &dst->head, n.list)) { struct tui_graph *graph; node = (struct tui_graph_node *)src; graph = container_of(node->graph, typeof(*graph), ug); node = append_graph_node(dst, graph, child->name); } node->n.addr = child->addr; node->n.time += child->time; node->n.child_time += child->child_time; node->n.nr_calls += child->nr_calls; copy_graph_node(&node->n, child); } } static int tui_last_index(struct tui_window *win) { int count = win->curr_index; void *next, *prev = win->curr; while (true) { next = win->ops->next(win, prev, false); if (next == NULL) return count; count++; if (win->ops->needs_blank(win, prev, next)) count++; prev = next; } } static void tui_window_init(struct tui_window *win, const struct tui_window_ops *ops) { void *top = ops->top(win, true); win->ops = ops; win->top = top; win->curr = win->old = top; win->top_index = win->curr_index = 0; win->last_index = tui_last_index(win); } static struct tui_graph * tui_graph_init(struct opts *opts) { struct tui_graph *graph; struct uftrace_graph_node *top, *node; list_for_each_entry(graph, &tui_graph_list, list) { /* top (root) is an artificial node, fill the info */ top = &graph->ug.root; top->name = basename(graph->ug.sess->exename); top->nr_calls = 1; list_for_each_entry(node, &graph->ug.root.head, list) { top->time += node->time; top->child_time += node->time; } tui_window_init(&graph->win, &graph_ops); graph->mask_size = sizeof(*graph->top_mask) * opts->max_stack; graph->top_mask = xzalloc(graph->mask_size); graph->disp_mask = xmalloc(graph->mask_size); } graph = list_first_entry(&tui_graph_list, typeof(*graph), list); partial_graph.mask_size = graph->mask_size; partial_graph.top_mask = xzalloc(graph->mask_size); partial_graph.disp_mask = xmalloc(graph->mask_size); INIT_LIST_HEAD(&partial_graph.ug.root.head); INIT_LIST_HEAD(&partial_graph.ug.special_nodes); tui_window_init(&partial_graph.win, &graph_ops); /* select first session */ partial_graph.ug.sess = graph->ug.sess; return graph; } static void tui_graph_finish(void) { struct tui_graph *graph; list_for_each_entry(graph, &tui_graph_list, list) { graph_destroy(&graph->ug); free(graph->top_mask); free(graph->disp_mask); } graph_destroy(&partial_graph.ug); free(partial_graph.top_mask); free(partial_graph.disp_mask); } static void build_partial_graph(struct tui_report_node *root_node, struct tui_graph *target) { struct tui_graph *graph = &partial_graph; struct tui_graph_node *root, *node; char *str; graph_destroy(&graph->ug); graph->ug.sess = target->ug.sess; xasprintf(&str, "=== Function Call Graph for '%s' ===", root_node->n.name); root = (struct tui_graph_node*) &graph->ug.root; root->n.name = str; root->n.parent = NULL; root->n.time = 0; root->n.child_time = 0; root->n.nr_calls = 0; /* special node */ root = append_graph_node(&graph->ug.root, target, "========== Back-trace =========="); list_for_each_entry(node, &root_node->head, link) { struct tui_graph_node *tmp, *parent; int n = 0; if (node->graph != &target->ug) continue; tmp = root; parent = node; while (parent->n.parent) { tmp = append_graph_node(&tmp->n, target, parent->n.name); tmp->n.addr = parent->n.addr; tmp->n.time = node->n.time; tmp->n.child_time = node->n.child_time; tmp->n.nr_calls = node->n.nr_calls; /* fold backtrace at the first child */ if (n++ == 1) tmp->folded = true; parent = (void *)parent->n.parent; } /* but, unfolded it if it's the last child */ if (n == 2) tmp->folded = false; } /* special node */ root = append_graph_node(&graph->ug.root, target, "========== Call Graph =========="); root = append_graph_node(&root->n, target, root_node->n.name); list_for_each_entry(node, &root_node->head, link) { if (node->graph != &target->ug) continue; root->n.addr = node->n.addr; root->n.time += node->n.time; root->n.child_time += node->n.child_time; root->n.nr_calls += node->n.nr_calls; copy_graph_node(&root->n, &node->n); } tui_window_init(&graph->win, &graph_ops); memset(graph->top_mask, 0, graph->mask_size); } static inline bool is_special_node(struct uftrace_graph_node *node) { return node->name[0] == '='; } static struct tui_graph_node * graph_prev_node(struct tui_graph_node *node, int *depth, bool *indent_mask) { struct uftrace_graph_node *n = &node->n; struct tui_graph_node *parent = (void *)n->parent; /* root node */ if (parent == NULL) { *depth = 0; return NULL; } /* simple case: if it's the first child, move to the parent */ if (is_first_child(parent, node)) { if (!list_is_singular(&n->parent->head) && *depth > 0) { *depth -= 1; if (indent_mask) indent_mask[*depth] = false; } n = n->parent; goto out; } /* move to sibling */ n = list_prev_entry(n, list); node = (struct tui_graph_node *)n; /* if it has children, move to the last child */ while (!list_empty(&n->head) && !node->folded) { if (!list_is_singular(&n->head)) { if (indent_mask) indent_mask[*depth] = false; *depth += 1; } n = list_last_entry(&n->head, typeof(*n), list); node = (struct tui_graph_node *)n; } out: if (n->parent && !list_is_singular(&n->parent->head)) { if (indent_mask && *depth > 0) indent_mask[*depth - 1] = true; } return (struct tui_graph_node *)n; } static struct tui_graph_node * graph_next_node(struct tui_graph_node *node, int *depth, bool *indent_mask) { struct uftrace_graph_node *n = &node->n; struct tui_graph_node *parent = (void *)n->parent; if (parent && !list_is_singular(&n->parent->head) && is_last_child(parent, node) && indent_mask && *depth > 0) indent_mask[*depth - 1] = false; /* simple case: if it has children, move to it */ if (!list_empty(&n->head) && (parent == NULL || !node->folded)) { if (!list_is_singular(&n->head)) { if (indent_mask) indent_mask[*depth] = true; *depth += 1; } n = list_first_entry(&n->head, typeof(*n), list); if (is_special_node(n)) *depth = 0; return (struct tui_graph_node *)n; } /* parent should not be folded */ while (n->parent != NULL) { parent = (struct tui_graph_node *)n->parent; /* move to sibling if possible */ if (!is_last_child(parent, (void *)n)) { n = list_next_entry(n, list); if (is_special_node(n)) *depth = 0; return (struct tui_graph_node *)n; } /* otherwise look up parent */ n = n->parent; if (!list_is_singular(&n->head) && *depth > 0) { *depth -= 1; if (indent_mask) indent_mask[*depth] = false; } } return NULL; } /* per-window operations for graph window */ static void * win_top_graph(struct tui_window *win, bool update) { struct tui_graph *graph = (struct tui_graph *)win; if (update) graph->top_depth = 0; return &graph->ug.root; } static void * win_prev_graph(struct tui_window *win, void *node, bool update) { void *prev; int depth; struct tui_graph *graph = (struct tui_graph *)win; if (update) prev = graph_prev_node(node, &graph->top_depth, graph->top_mask); else prev = graph_prev_node(node, &depth, NULL); return prev; } static void * win_next_graph(struct tui_window *win, void *node, bool update) { void *next; int depth; struct tui_graph *graph = (struct tui_graph *)win; if (update) { /* update top node for new page */ next = graph_next_node(node, &graph->top_depth, graph->top_mask); } else if (graph->disp_update) { /* update display node for next */ next = graph_next_node(node, &graph->disp_depth, graph->disp_mask); graph->disp = next; } else { next = graph_next_node(node, &depth, NULL); } return next; } static bool win_needs_blank_graph(struct tui_window *win, void *prev, void *next) { return !is_first_child(prev, next); } static void * win_sibling_prev_graph(struct tui_window *win, void *node) { struct uftrace_graph_node *curr = node; struct uftrace_graph_node *parent = curr->parent; if (parent == NULL) return NULL; if (list_first_entry(&parent->head, typeof(*curr), list) == curr) return NULL; return list_prev_entry(curr, list); } static void * win_sibling_next_graph(struct tui_window *win, void *node) { struct uftrace_graph_node *curr = node; struct uftrace_graph_node *parent = curr->parent; if (parent == NULL) return NULL; if (list_last_entry(&parent->head, typeof(*curr), list) == curr) return NULL; return list_next_entry(curr, list); } static void * win_parent_graph(struct tui_window *win, void *node) { struct uftrace_graph_node *curr = node; return curr->parent; } static bool win_enter_graph(struct tui_window *win, void *node) { struct tui_graph_node *curr = node; /* root node is not foldable */ if (curr->n.parent == NULL) return false; if (list_empty(&curr->n.head)) return false; curr->folded = !curr->folded; win->last_index = tui_last_index(win); return true; } static int fold_graph_node(struct tui_graph_node *node, bool fold, bool all, int depth) { struct tui_graph_node *child; int count = 0; bool curr_fold = fold; if (!all && depth < 0) return 0; else if (depth > 0) curr_fold = false; /* do not fold leaf nodes - it's meaningless but confusing */ if (list_empty(&node->n.head)) return 0; if (node->folded != curr_fold) { node->folded = curr_fold; count++; } list_for_each_entry(child, &node->n.head, n.list) count += fold_graph_node(child, fold, all, depth - 1); return count; } static bool win_collapse_graph(struct tui_window *win, void *node, bool all, int depth) { bool result = fold_graph_node(node, true, all, depth); win->last_index = tui_last_index(win); return result; } static bool win_expand_graph(struct tui_window *win, void *node, bool all, int depth) { bool result = fold_graph_node(node, false, all, depth); win->last_index = tui_last_index(win); return result; } static void win_header_graph(struct tui_window *win, struct uftrace_data *handle) { int w = 0, c; char *buf, *p; struct tui_graph *graph = (struct tui_graph *)win; struct display_field *field; /* calculate width for fields */ list_for_each_entry(field, &graph_output_fields, list) { w += field->length + FIELD_SPACE; } if (!list_empty(&graph_output_fields)) w += strlen(FIELD_SEP); graph->width = w; w += strlen(" FUNCTION"); if (list_empty(&graph_output_fields)) { printw("%-*.*s", COLS, COLS, "uftrace graph TUI"); goto out; } buf = p = xmalloc(w + 1); list_for_each_entry(field, &graph_output_fields, list) { c = snprintf(p, w, "%*s%*s", FIELD_SPACE, "", field->length, field->header); p += c; w -= c; } snprintf(p, w+1, "%s %s", FIELD_SEP, "FUNCTION"); printw("%-*.*s", COLS, COLS, buf); free(buf); out: /* start with same make as top */ graph->disp = graph->win.top; graph->disp_depth = graph->top_depth; graph->disp_update = true; memcpy(graph->disp_mask, graph->top_mask, graph->mask_size); } static int win_pos_percent(struct tui_window *win) { return win->curr_index * 100.0 / win->last_index; } static void win_footer(struct tui_window *win, char *msg) { int pos_start = COLS - POS_SIZE; int msg_len = strlen(msg); char footer[COLS + 1]; memset(footer, BLANK, sizeof(footer)); memcpy(footer, msg, COLS < msg_len ? COLS : msg_len); if (pos_start > msg_len) snprintf(footer + pos_start, POS_SIZE, "%3d%%", win_pos_percent(win)); footer[COLS] = '\0'; printw("%-*s", COLS, footer); } static void win_footer_graph(struct tui_window *win, struct uftrace_data *handle) { char buf[COLS + 1]; struct tui_graph *graph = (struct tui_graph *)win; struct tui_graph_node *node = win->curr; struct uftrace_session *sess = graph->ug.sess; if (tui_debug) { snprintf(buf, COLS, "uftrace graph: top: %d depth: %d, curr: %d depth: %d last: %d", graph->win.top_index, graph->top_depth, graph->win.curr_index, graph->curr_depth, graph->win.last_index); } else if (tui_search) { snprintf(buf, COLS, "uftrace graph: searching \"%s\" (%d match, %s)", tui_search, graph->win.search_count, "use '<' and '>' keys to navigate"); } else { struct debug_location *dloc; dloc = win->ops->location(win, win->curr); if (dloc != NULL && dloc->file != NULL) { snprintf(buf, COLS, "uftrace graph: %s [line:%d]", dloc->file->name, dloc->line); } else if (find_symtabs(&sess->symtabs, node->n.addr) != NULL) { /* some symbols don't have source location */ snprintf(buf, COLS, "uftrace graph: %s [at %#"PRIx64"]", "source location is not available", node->n.addr); } else { snprintf(buf, COLS, "uftrace graph: session %.*s (%s)", SESSION_ID_LEN, sess->sid, sess->exename); } } win_footer(win, buf); graph->disp_update = false; } static void print_graph_field(struct uftrace_graph_node *node, int width) { struct display_field *field; struct field_data fd = { .arg = node, }; if (list_empty(&graph_output_fields)) return; list_for_each_entry(field, &graph_output_fields, list) { if (width >= FIELD_SPACE) { printw("%*s", FIELD_SPACE, ""); width -= FIELD_SPACE; } if (width >= field->length) { field->print(&fd); width -= field->length; } } if (width >= FIELD_SPACE) printw(FIELD_SEP); } static void print_graph_empty(struct tui_graph *graph, int width) { struct display_field *field; if (list_empty(&graph_output_fields)) return; if (graph->width > width) return; list_for_each_entry(field, &graph_output_fields, list) printw("%*s", field->length + FIELD_SPACE, ""); printw(FIELD_SEP); } static void print_graph_indent(struct tui_graph *graph, struct tui_graph_node *node, int width, int depth, bool single_child) { int i; struct tui_graph_node *parent = (void *)node->n.parent; for (i = 0; i < depth; i++) { if (width < 3) { printw("%*.*s", width, width, " "); break; } width -= 3; if (!graph->disp_mask[i]) { printw(" "); continue; } if (i < depth - 1 || single_child) printw(" │"); else if (is_last_child(parent, node)) printw(" â””"); else printw(" ├"); } } static void win_display_graph(struct tui_window *win, void *node) { struct tui_graph *graph = (struct tui_graph *)win; struct tui_graph_node *curr = node; struct tui_graph_node *parent; int d = graph->disp_depth; int w = graph->width; const char *fold_sign; bool single_child = false; int width; if (node == NULL) { print_graph_empty(graph, COLS); print_graph_indent(graph, graph->disp, COLS - w, d, true); return; } fold_sign = curr->folded ? "â–¶" : "─"; parent = win_parent_graph(win, node); if (parent == NULL) fold_sign = " "; else if (list_is_singular(&parent->n.head)) { single_child = true; if (!curr->folded) fold_sign = " "; } print_graph_field(&curr->n, COLS); print_graph_indent(graph, curr, COLS - w, d, single_child); width = d * 3 + w; if (is_special_node(&curr->n)) { width = COLS - width; if (width > 0) printw("%-*.*s", width, width, curr->n.name); } else { char buf[32]; if (width < COLS) { w = COLS - width; width += snprintf(buf, sizeof(buf), "%s(%d) ", fold_sign, curr->n.nr_calls); /* handle UTF-8 character length */ if (strcmp(fold_sign, " ")) { width -= 2; w += 2; } printw("%.*s", w, buf); } if (width < COLS) { w = COLS - width; printw("%-*.*s", w, w, curr->n.name); } } } static bool win_search_graph(struct tui_window *win, void *node, char *str) { struct tui_graph_node *curr = node; return strstr(curr->n.name, str); } static bool win_longest_child_graph(struct tui_window *win, void *node) { struct tui_graph_node *curr = node; struct tui_graph_node *child; struct tui_graph_node *longest_child = NULL; uint64_t longest_child_time = 0; curr->folded = false; list_for_each_entry(child, &curr->n.head, n.list) { fold_graph_node(child, true, false, 0); if (longest_child_time < child->n.time) { longest_child_time = child->n.time; longest_child = child; } } if (longest_child == NULL) return false; longest_child->folded = false; while (win->curr != longest_child) tui_window_move_down(win); win->last_index = tui_last_index(win); return true; } static struct debug_location *win_location_graph(struct tui_window *win, void *node) { struct tui_graph *graph = (struct tui_graph *)win; struct tui_graph_node *curr = node; struct uftrace_session *sess = graph->ug.sess; return find_file_line(&sess->symtabs, curr->n.addr); } static const struct tui_window_ops graph_ops = { .prev = win_prev_graph, .next = win_next_graph, .top = win_top_graph, .parent = win_parent_graph, .sibling_prev = win_sibling_prev_graph, .sibling_next = win_sibling_next_graph, .needs_blank = win_needs_blank_graph, .enter = win_enter_graph, .collapse = win_collapse_graph, .expand = win_expand_graph, .header = win_header_graph, .footer = win_footer_graph, .display = win_display_graph, .search = win_search_graph, .longest_child = win_longest_child_graph, .location = win_location_graph, }; /* some default (no-op) window operations */ static bool win_needs_blank_no(struct tui_window *win, void *prev, void *next) { return false; } static void * win_sibling_prev_no(struct tui_window *win, void *node) { return win->ops->prev(win, node, false); } static void * win_sibling_next_no(struct tui_window *win, void *node) { return win->ops->next(win, node, false); } static void * win_parent_no(struct tui_window *win, void *node) { return NULL; } /* per-window operations for report window */ static struct tui_report * tui_report_init(struct opts *opts) { struct tui_window *win = &tui_report.win; report_setup_sort(OPT_SORT_KEYS); report_sort_nodes(&tui_report.name_tree, &tui_report.sort_tree); tui_window_init(win, &report_ops); return &tui_report; } static void tui_report_finish(void) { } static void * win_top_report(struct tui_window *win, bool update) { struct tui_report *report = (struct tui_report *)win; struct rb_node *node = rb_first(&report->sort_tree); return rb_entry(node, struct tui_report_node, n.sort_link); } static void * win_prev_report(struct tui_window *win, void *node, bool update) { struct tui_report_node *curr = node; struct rb_node *rbnode = rb_prev(&curr->n.sort_link); if (rbnode == NULL) return NULL; return rb_entry(rbnode, struct tui_report_node, n.sort_link); } static void * win_next_report(struct tui_window *win, void *node, bool update) { struct tui_report_node *curr = node; struct rb_node *rbnode = rb_next(&curr->n.sort_link); if (rbnode == NULL) return NULL; return rb_entry(rbnode, struct tui_report_node, n.sort_link); } static bool win_search_report(struct tui_window *win, void *node, char *str) { struct tui_report_node *curr = node; return strstr(curr->n.name, str); } static void win_header_report(struct tui_window *win, struct uftrace_data *handle) { int w = 46; char header[][32] = { " Total Time", " Self Time", " Calls" }; header[curr_sort_key][0] = '*'; printw(" %11s %11s %11s %s", header[0], header[1], header[2], "Function"); if (COLS > w) printw("%*s", COLS - w, ""); } static void win_footer_report(struct tui_window *win, struct uftrace_data *handle) { char buf[COLS + 1]; if (tui_debug) { snprintf(buf, COLS, "uftrace report: top: %d, curr: %d", win->top_index, win->curr_index); } else if (tui_search) { snprintf(buf, COLS, "uftrace report: searching \"%s\" (%d match, %s)", tui_search, win->search_count, "use '<' and '>' keys to navigate"); } else { struct debug_location *dloc; dloc = win->ops->location(win, win->curr); if (dloc != NULL && dloc->file != NULL) { snprintf(buf, COLS, "uftrace report: %s [line:%d]", dloc->file->name, dloc->line); } else { struct tui_report *report = (struct tui_report *)win; snprintf(buf, COLS, "uftrace report: %s (%d sessions, %d functions)", handle->dirname, report->nr_sess, report->nr_func); } } win_footer(win, buf); } static void win_display_report(struct tui_window *win, void *node) { struct tui_report_node *curr = node; int width = 38; /* 3 output fields and spaces */ printw(" "); print_time(curr->n.total.sum); printw(" "); print_time(curr->n.self.sum); printw(" "); printw("%10lu", curr->n.call); printw(" "); printw("%-*.*s", COLS - width, COLS - width, curr->n.name); } static struct debug_location *win_location_report(struct tui_window *win, void *node) { struct tui_report_node *curr = node; struct tui_graph_node *gnode; struct uftrace_session *sess; struct debug_location *dloc; list_for_each_entry(gnode, &curr->head, link) { sess = gnode->graph->sess; dloc = find_file_line(&sess->symtabs, gnode->n.addr); if (dloc != NULL && dloc->file != NULL) return dloc; } return NULL; } static const struct tui_window_ops report_ops = { .prev = win_prev_report, .next = win_next_report, .top = win_top_report, .parent = win_parent_no, .sibling_prev = win_sibling_prev_no, .sibling_next = win_sibling_next_no, .needs_blank = win_needs_blank_no, .header = win_header_report, .footer = win_footer_report, .display = win_display_report, .search = win_search_report, .location = win_location_report, }; /* per-window operations for list window */ static void * win_top_list(struct tui_window *win, bool update) { struct tui_list *list = (struct tui_list *)win; return list_first_entry(&list->head, struct tui_list_node, list); } static void * win_prev_list(struct tui_window *win, void *node, bool update) { struct tui_list *list = (struct tui_list *)win; if (list_first_entry(&list->head, struct tui_list_node, list) == node) return NULL; return list_prev_entry((struct tui_list_node *)node, list); } static void * win_next_list(struct tui_window *win, void *node, bool update) { struct tui_list *list = (struct tui_list *)win; if (list_last_entry(&list->head, struct tui_list_node, list) == node) return NULL; return list_next_entry((struct tui_list_node *)node, list); } /* per-window operations for info window */ static void build_info_node(void *data, const char *fmt, ...) { va_list ap; struct tui_list *info = data; struct tui_list_node *node; char *str = NULL; node = xmalloc(sizeof(*node)); va_start(ap, fmt); xvasprintf(&str, fmt, ap); va_end(ap); /* remove trailing newline */ str[strlen(str) - 1] = '\0'; node->data = str; list_add_tail(&node->list, &info->head); } static struct tui_list * tui_info_init(struct opts *opts, struct uftrace_data *handle) { INIT_LIST_HEAD(&tui_info.head); process_uftrace_info(handle, opts, build_info_node, &tui_info); tui_window_init(&tui_info.win, &info_ops); return &tui_info; } static void tui_info_finish(void) { struct tui_list_node *node, *tmp; list_for_each_entry_safe(node, tmp, &tui_info.head, list) { list_del(&node->list); free(node->data); free(node); } } static void win_header_info(struct tui_window *win, struct uftrace_data *handle) { printw("%-*.*s", COLS, COLS, "uftrace info"); } #define print_buf(fmt, ...) \ ({ int _x = snprintf(buf + len, sz - len, fmt, ## __VA_ARGS__); len += _x; }) static void win_footer_info(struct tui_window *win, struct uftrace_data *handle) { char buf[256]; snprintf(buf, COLS, "uftrace version: %s", UFTRACE_VERSION); win_footer(win, buf); } static void win_display_info(struct tui_window *win, void *node) { struct tui_list_node *curr = node; printw("%-*.*s", COLS, COLS, (char *)curr->data); } static const struct tui_window_ops info_ops = { .prev = win_prev_list, .next = win_next_list, .top = win_top_list, .parent = win_parent_no, .sibling_prev = win_sibling_prev_no, .sibling_next = win_sibling_next_no, .needs_blank = win_needs_blank_no, .header = win_header_info, .footer = win_footer_info, .display = win_display_info, }; #define TUI_SESS_REPORT 1 #define TUI_SESS_INFO 2 #define TUI_SESS_HELP 3 #define TUI_SESS_QUIT 4 #define TUI_SESS_DUMMY_NR 4 /* per-window operations for session window */ static struct tui_list * tui_session_init(struct opts *opts) { struct tui_graph *graph; struct tui_list_node *node; int i; INIT_LIST_HEAD(&tui_session.head); tui_session.nr_node = 0; list_for_each_entry(graph, &tui_graph_list, list) { node = xmalloc(sizeof(*node)); node->data = graph->ug.sess; list_add_tail(&node->list, &tui_session.head); tui_session.nr_node++; } for (i = 1; i <= TUI_SESS_DUMMY_NR; i++) { node = xmalloc(sizeof(*node)); node->data = (void *)(long)i; list_add_tail(&node->list, &tui_session.head); } tui_window_init(&tui_session.win, &session_ops); return &tui_session; } static void tui_session_finish(void) { struct tui_list_node *node, *tmp; list_for_each_entry_safe(node, tmp, &tui_session.head, list) { list_del(&node->list); free(node); } } static void win_header_session(struct tui_window *win, struct uftrace_data *handle) { printw("%s %-*s", "Key", COLS - 4, "uftrace command"); } static void win_footer_session(struct tui_window *win, struct uftrace_data *handle) { char buf[256]; struct tui_list *s_list = (struct tui_list *)win; struct tui_list_node *node = win->curr; struct uftrace_session *s = node->data; switch ((long)node->data) { case TUI_SESS_REPORT: case TUI_SESS_INFO: case TUI_SESS_HELP: case TUI_SESS_QUIT: snprintf(buf, sizeof(buf), "uftrace: %d session(s)", s_list->nr_node); break; default: snprintf(buf, sizeof(buf), "session %.*s: exe image: %s", SESSION_ID_LEN, s->sid, s->exename); break; } win_footer(win, buf); } static struct tui_graph * get_current_graph(struct tui_list_node *node, int *count) { struct tui_graph *graph; int n = 1; list_for_each_entry(graph, &tui_graph_list, list) { if (graph->ug.sess == node->data) { if (count) *count = n; return graph; } n++; } if (count) *count = 0; return NULL; } static void win_display_session(struct tui_window *win, void *node) { int len = 0; char buf[1024]; size_t sz = sizeof(buf); struct tui_list_node *curr = node; struct uftrace_session *s = curr->data; struct uftrace_session *curr_sess = NULL; int count = 0; switch ((long)s) { case TUI_SESS_REPORT: print_buf(" R Report functions"); break; case TUI_SESS_INFO: print_buf(" I uftrace Info"); break; case TUI_SESS_HELP: print_buf(" h Help message"); break; case TUI_SESS_QUIT: print_buf(" q quit"); break; default: curr_sess = partial_graph.ug.sess; get_current_graph(node, &count); print_buf(" %c %s #%d: %s", s == curr_sess ? 'G' : ' ', "call Graph for session", count, basename(s->exename)); break; } printw("%-*.*s", COLS, COLS, buf); } static bool win_enter_session(struct tui_window *win, void *node) { /* update partial graph for different session */ struct tui_list_node *curr = node; struct uftrace_session *old = partial_graph.ug.sess; struct uftrace_session *new = curr->data; struct uftrace_graph_node *ugnode; struct tui_report_node *func; if ((unsigned long)curr->data <= TUI_SESS_DUMMY_NR) return true; if (old == new) return false; partial_graph.ug.sess = curr->data; /* get root node */ ugnode = &partial_graph.ug.root; if (list_empty(&ugnode->head)) return true; /* get function call node */ ugnode = list_last_entry(&ugnode->head, typeof(*ugnode), list); /* get first child (= actual function) */ ugnode = list_first_entry(&ugnode->head, typeof(*ugnode), list); func = (void *)report_find_node(&tui_report.name_tree, ugnode->name); build_partial_graph(func, get_current_graph(node, NULL)); return true; } static const struct tui_window_ops session_ops = { .prev = win_prev_list, .next = win_next_list, .top = win_top_list, .parent = win_parent_no, .sibling_prev = win_sibling_prev_no, .sibling_next = win_sibling_next_no, .needs_blank = win_needs_blank_no, .enter = win_enter_session, .header = win_header_session, .footer = win_footer_session, .display = win_display_session, }; /* common window operations */ static void tui_window_move_up(struct tui_window *win) { void *node; node = win->ops->prev(win, win->curr, false); if (node == NULL) return; win->curr_index--; if (win->ops->needs_blank(win, node, win->curr)) win->curr_index--; if (win->curr_index < win->top_index) { win->top = win->ops->prev(win, win->top, true); win->top_index = win->curr_index; } win->curr = node; } static void tui_window_move_down(struct tui_window *win) { void *node; node = win->ops->next(win, win->curr, false); if (node == NULL) return; win->curr_index++; if (win->ops->needs_blank(win, win->curr, node)) win->curr_index++; win->curr = node; while (win->curr_index - win->top_index >= LINES - 2) { node = win->ops->next(win, win->top, true); win->top_index++; if (win->ops->needs_blank(win, win->top, node)) win->top_index++; win->top = node; } } static void tui_window_page_up(struct tui_window *win) { void *node; if (win->curr != win->top) { win->curr = win->top; win->curr_index = win->top_index; return; } while (win->top_index - win->curr_index < LINES - 2) { node = win->ops->prev(win, win->top, true); if (node == NULL) break; win->curr_index--; if (win->ops->needs_blank(win, node, win->top)) win->curr_index--; win->top = node; } win->curr = win->top; win->top_index = win->curr_index; } static void tui_window_page_down(struct tui_window *win) { int orig_index; int next_index; void *node; orig_index = win->top_index; next_index = win->curr_index; node = win->ops->next(win, win->curr, false); if (node == NULL) return; next_index++; if (win->ops->needs_blank(win, win->curr, node)) next_index++; if (next_index - win->top_index >= LINES - 2) { /* we're already at the end of page - move to next page */ orig_index = next_index; } do { /* move curr to the bottom from orig_index */ win->curr = node; win->curr_index = next_index; node = win->ops->next(win, win->curr, false); if (node == NULL) break; next_index++; if (win->ops->needs_blank(win, win->curr, node)) next_index++; } while (next_index - orig_index < LINES - 2); /* move top if page was moved */ while (win->curr_index - win->top_index >= LINES - 2) { node = win->ops->next(win, win->top, true); win->top_index++; if (win->ops->needs_blank(win, win->top, node)) win->top_index++; win->top = node; } } static void tui_window_move_home(struct tui_window *win) { win->top = win->curr = win->ops->top(win, true); win->top_index = win->curr_index = 0; } static void tui_window_move_end(struct tui_window *win) { void *node; /* move to the last node */ while (true) { node = win->ops->next(win, win->curr, false); if (node == NULL) break; win->curr_index++; if (win->ops->needs_blank(win, win->curr, node)) win->curr_index++; win->curr = node; } /* move top if page was moved */ while (win->curr_index - win->top_index >= LINES - 2) { node = win->ops->next(win, win->top, true); win->top_index++; if (win->ops->needs_blank(win, win->top, node)) win->top_index++; win->top = node; } } /* move to the previous sibling */ static bool tui_window_move_prev(struct tui_window *win) { void *prev = win->ops->sibling_prev(win, win->curr); int count = 0; if (prev == NULL) return false; if (win->ops->collapse == NULL) { while (win->curr != prev) tui_window_move_up(win); return false; } /* fold the current node before moving to the previous sibling */ count = win->ops->collapse(win, win->curr, false, 0); while (win->curr != prev) tui_window_move_up(win); /* collapse the current node after moving to the previous sibling */ count += win->ops->collapse(win, win->curr, false, 1); return count; } /* move to the next sibling */ static bool tui_window_move_next(struct tui_window *win) { void *next = win->ops->sibling_next(win, win->curr); int count = 0; if (next == NULL) return false; if (win->ops->collapse == NULL) { while (win->curr != next) tui_window_move_down(win); return false; } /* fold the current node before moving to the next sibling */ count = win->ops->collapse(win, win->curr, false, 0); while (win->curr != next) tui_window_move_down(win); /* collapse the current node after moving to the next sibling */ count += win->ops->collapse(win, win->curr, false, 1); return count; } static void tui_window_display(struct tui_window *win, bool full_redraw, struct uftrace_data *handle) { int count; void *node = win->top; /* too small screen */ if (LINES <= 2) return; move(0, 0); attron(COLOR_PAIR(C_HEADER) | A_BOLD); win->ops->header(win, handle); attroff(COLOR_PAIR(C_HEADER) | A_BOLD); for (count = 0; count < LINES - 2; count++) { void *next; if (!full_redraw && node != win->curr && node != win->old) goto next; if (node == win->curr) attron(A_REVERSE); move(count + 1, 0); win->ops->display(win, node); if (node == win->curr) attroff(A_REVERSE); next: next = win->ops->next(win, node, false); if (next == NULL) break; if (win->ops->needs_blank(win, node, next)) { count++; move(count + 1, 0); win->ops->display(win, NULL); } node = next; } move(LINES - 1, 0); attron(COLOR_PAIR(C_HEADER) | A_BOLD); win->ops->footer(win, handle); attroff(COLOR_PAIR(C_HEADER) | A_BOLD); } static void tui_window_set_middle_prev(struct tui_window *win, void *target) { void *prev; while (win->curr != target) tui_window_move_up(win); while (win->curr_index - win->top_index < LINES / 2) { prev = win->ops->prev(win, win->top, false); if (prev == NULL) break; if (win->ops->needs_blank(win, prev, win->top)) win->top_index--; win->top = win->ops->prev(win, win->top, true); win->top_index--; } } static void tui_window_set_middle_next(struct tui_window *win, void *target) { void *old, *next; int next_index; while (win->curr != target) tui_window_move_down(win); /* move next to the end of the page */ old = next = win->curr; next_index = win->curr_index; while (next_index - win->top_index < LINES - 2) { next = win->ops->next(win, old, false); if (next == NULL) return; next_index++; if (win->ops->needs_blank(win, old, next)) next_index++; old = next; } next = win->ops->prev(win, old, false); /* move the top down only if there's node at the end */ while (win->curr_index - win->top_index >= LINES / 2) { next = win->ops->next(win, next, false); if (next == NULL) break; old = win->top; win->top = win->ops->next(win, old, true); win->top_index++; if (win->ops->needs_blank(win, old, win->top)) win->top_index++; } } static void tui_window_set_middle(struct tui_window *win) { int offset_from_top = win->curr_index - win->top_index; int offset_half = LINES / 2; if (offset_from_top < offset_half - 2) tui_window_set_middle_prev(win, win->curr); else if (offset_from_top > offset_half + 1) tui_window_set_middle_next(win, win->curr); } static bool tui_window_can_search(struct tui_window *win) { return win->ops->search != NULL; } static char * tui_search_start(void) { WINDOW *win; int w = COLS / 2; int h = 8; char buf[512]; int n = 0; char *str = NULL; struct tui_graph *graph; win = newwin(h, w, (LINES - h) / 2, (COLS - w) / 2); box(win, 0, 0); mvwprintw(win, 1, 1, "Search function:"); mvwprintw(win, 2, 2, "(press ESC to exit)"); wrefresh(win); wmove(win, 5, 3); wrefresh(win); buf[0] = '\0'; while (true) { int k = wgetch(win); switch (k) { case KEY_ESCAPE: goto out; case KEY_BACKSPACE: case KEY_DC: case 127: case '\b': if (n > 0) { mvwprintw(win, 5, 3, "%*s", n, ""); buf[--n] = '\0'; } break; case KEY_ENTER: case '\n': str = xstrdup(buf); goto out; default: if (isprint(k)) buf[n++] = k; buf[n] = '\0'; break; } mvwprintw(win, 5, 3, "%-.*s", w - 5, buf); wmove(win, 5, 3 + n); wrefresh(win); } out: list_for_each_entry(graph, &tui_graph_list, list) graph->win.search_count = -1; partial_graph.win.search_count = -1; tui_report.win.search_count = -1; delwin(win); return str; } static void tui_window_search_count(struct tui_window *win) { void *node; if (tui_search == NULL || win->ops->search == NULL) return; if (win->search_count != -1) return; win->search_count = 0; node = win->ops->top(win, false); while (node) { if (win->ops->search(win, node, tui_search)) win->search_count++; node = win->ops->next(win, node, false); } } static void tui_window_search_prev(struct tui_window *win) { void *node = win->curr; if (tui_search == NULL || win->ops->search == NULL) return; while (true) { node = win->ops->prev(win, node, false); if (node == NULL) return; if (win->ops->search(win, node, tui_search)) break; } tui_window_set_middle_prev(win, node); } static void tui_window_search_next(struct tui_window *win) { void *node = win->curr; if (tui_search == NULL || win->ops->search == NULL) return; while (true) { node = win->ops->next(win, node, false); if (node == NULL) return; if (win->ops->search(win, node, tui_search)) break; } tui_window_set_middle_next(win, node); } static bool tui_window_change(struct tui_window *win, struct tui_window *new_win) { if (win == new_win) return false; tui_window_search_count(new_win); return true; } static bool tui_window_enter(struct tui_window *win, struct tui_window *prev_win) { if (win->ops->enter == NULL) return false; return win->ops->enter(win, win->curr); } static bool tui_window_collapse(struct tui_window *win, bool all) { if (win->ops->collapse == NULL) return false; /* fold all the directly children */ return win->ops->collapse(win, win->curr, all, 1); } static bool tui_window_expand(struct tui_window *win, bool all) { if (win->ops->expand == NULL) return false; /* unfold all the directly children */ return win->ops->expand(win, win->curr, all, 1); } static bool tui_window_move_parent(struct tui_window *win) { void *parent = win->ops->parent(win, win->curr); if (parent == NULL) return false; while (win->curr != parent) tui_window_move_up(win); return tui_window_collapse(win, false); } static bool tui_window_longest_child(struct tui_window *win) { if (win->ops->longest_child == NULL) return false; return win->ops->longest_child(win, win->curr); } static bool tui_window_open_editor(struct tui_window *win) { struct debug_location *dloc; const char *editor = getenv("EDITOR"); struct strv editor_strv; int pid, status; int ret; if (win->ops->location == NULL) return false; dloc = win->ops->location(win, win->curr); if (dloc == NULL || dloc->file == NULL) return false; /* can read file? */ if (access(dloc->file->name, R_OK) < 0) return false; if (editor == NULL) editor = "vi"; endwin(); strv_split(&editor_strv, editor, " "); if (!strncmp(editor, "vi", 2) || !strncmp(editor, "emacs", 5)) { char buf[16]; /* run 'vi +line file' */ snprintf(buf, sizeof(buf), "+%d", dloc->line); strv_append(&editor_strv, buf); strv_append(&editor_strv, dloc->file->name); } else { /* I don't know what to do */ strv_append(&editor_strv, dloc->file->name); } pid = fork(); if (pid < 0) { int saved_errno = errno; endwin(); errno = saved_errno; pr_err("forking editor failed"); } if (pid == 0) { execvp(editor_strv.p[0], editor_strv.p); exit(1); } strv_free(&editor_strv); do { /* can return early by signal (e.g. SIGWINCH) */ ret = waitpid(pid, &status, 0); } while (ret < 0 && errno == EINTR); refresh(); return true; } static void tui_window_help(void) { WINDOW *win; int w = 64; int h = ARRAY_SIZE(help) + 5; unsigned i; if (w > COLS) w = COLS; if (h > LINES) h = LINES; win = newwin(h, w, (LINES - h) / 2, (COLS - w) / 2); box(win, 0, 0); mvwprintw(win, 1, 1, "Help: (press any key to exit)"); for (i = 0; i < ARRAY_SIZE(help); i++) mvwprintw(win, i + 3, 2, "%-*.*s", w-3, w-3, help[i]); mvwprintw(win, h-1, w-1, ""); wrefresh(win); /* wait for key press */ wgetch(win); delwin(win); } static inline void cancel_search() { free(tui_search); tui_search = NULL; } static void tui_main_loop(struct opts *opts, struct uftrace_data *handle) { int key = 0; bool full_redraw = true; struct tui_graph *graph; struct tui_report *report; struct tui_list *info; struct tui_list *session; struct tui_window *win; void *old_top; graph = tui_graph_init(opts); report = tui_report_init(opts); info = tui_info_init(opts, handle); session = tui_session_init(opts); /* start with graph only if there's one session */ if (opts->report) win = &report->win; else if (session->nr_node > 1) win = &session->win; else win = &graph->win; old_top = win->top; while (true) { switch (key) { case KEY_RESIZE: full_redraw = true; break; case KEY_UP: case 'k': cancel_search(); tui_window_move_up(win); break; case KEY_DOWN: case 'j': cancel_search(); tui_window_move_down(win); break; case KEY_PPAGE: cancel_search(); tui_window_page_up(win); break; case KEY_NPAGE: cancel_search(); tui_window_page_down(win); break; case KEY_HOME: cancel_search(); tui_window_move_home(win); break; case KEY_END: cancel_search(); tui_window_move_end(win); break; case KEY_ENTER: case '\n': full_redraw = tui_window_enter(win, win->curr); if (win == &session->win) { struct tui_list_node *cmd = win->curr; switch ((long)cmd->data) { case TUI_SESS_REPORT: win = &report->win; tui_window_move_home(win); break; case TUI_SESS_INFO: win = &info->win; tui_window_move_home(win); break; case TUI_SESS_HELP: tui_window_help(); break; case TUI_SESS_QUIT: goto out; default: /* change window for the current graph */ graph = get_current_graph(win->curr, NULL); win = &graph->win; tui_window_move_home(win); break; } } break; case KEY_ESCAPE: cancel_search(); break; case 'G': if (tui_window_change(win, &graph->win)) { /* full graph mode */ win = &graph->win; full_redraw = true; } break; case 'g': if (win == &graph->win || win == &partial_graph.win) { struct tui_report_node *func; struct tui_graph_node *curr = win->curr; func = (void *)report_find_node(&report->name_tree, curr->n.name); if (func == NULL) break; build_partial_graph(func, graph); } else if (win == &report->win) { build_partial_graph(win->curr, graph); } else { break; } win = &partial_graph.win; tui_window_move_home(win); tui_window_search_count(win); full_redraw = true; break; case 'R': if (tui_window_change(win, &report->win)) { win = &report->win; tui_window_move_home(win); full_redraw = true; } break; case 'r': if (tui_window_change(win, &report->win)) { struct tui_report_node *func; struct tui_graph_node *graph_curr = win->curr; func = (void *)report_find_node(&report->name_tree, graph_curr->n.name); if (func == NULL) break; /* change to report window */ win = &report->win; /* move focus on the same function */ tui_window_move_home(win); tui_window_set_middle_next(win, func); full_redraw = true; } break; case 's': if (!tui_window_change(win, &report->win)) { curr_sort_key = (curr_sort_key + 1) % ARRAY_SIZE(report_sort_key); report_setup_sort(report_sort_key[curr_sort_key]); report_sort_nodes(&tui_report.name_tree, &tui_report.sort_tree); tui_window_move_home(win); full_redraw = true; } break; case 'I': if (tui_window_change(win, &info->win)) { win = &info->win; full_redraw = true; } break; case 'S': if (tui_window_change(win, &session->win)) { win = &session->win; full_redraw = true; } break; case 'O': full_redraw = tui_window_open_editor(win); break; case 'c': full_redraw = tui_window_collapse(win, false); break; case 'e': full_redraw = tui_window_expand(win, false); break; case 'C': full_redraw = tui_window_collapse(win, true); break; case 'E': full_redraw = tui_window_expand(win, true); break; case 'p': full_redraw = tui_window_move_prev(win); break; case 'n': full_redraw = tui_window_move_next(win); break; case 'u': full_redraw = tui_window_move_parent(win); break; case 'l': full_redraw = tui_window_longest_child(win); break; case 'z': tui_window_set_middle(win); break; case '/': if (tui_window_can_search(win)) { free(tui_search); tui_search = tui_search_start(); tui_window_search_count(win); /* move to the next match if found */ if (win->search_count > 0) tui_window_search_next(win); full_redraw = true; } break; case '<': case 'P': tui_window_search_prev(win); break; case '>': case 'N': tui_window_search_next(win); break; case 'v': tui_debug = !tui_debug; break; case 'h': case '?': tui_window_help(); full_redraw = true; break; case 'q': goto out; default: break; } if (win->top != old_top) full_redraw = true; if (full_redraw) clear(); tui_window_display(win, full_redraw, handle); refresh(); full_redraw = false; win->old = win->curr; old_top = win->top; move(LINES-1, COLS-1); key = getch(); } out: tui_graph_finish(); tui_report_finish(); tui_info_finish(); tui_session_finish(); } static void display_loading_msg() { char *tuimsg = "Building graph for TUI..."; int row, col; getmaxyx(stdscr, row, col); mvprintw(row / 2, (col - strlen(tuimsg)) / 2, "%s", tuimsg); refresh(); } int command_tui(int argc, char *argv[], struct opts *opts) { int ret; struct uftrace_data handle; struct uftrace_task_reader *task; ret = open_data_file(opts, &handle); if (ret < 0) { pr_warn("cannot open record data: %s: %m\n", opts->dirname); return -1; } setlocale(LC_ALL, ""); initscr(); init_colors(); keypad(stdscr, true); noecho(); atexit(tui_cleanup); /* Print a message before main screen is launched. */ display_loading_msg(); tui_setup(&handle, opts); fstack_setup_filters(opts, &handle); while (read_rstack(&handle, &task) == 0 && !uftrace_done) { struct uftrace_record *rec = task->rstack; if (!fstack_check_opts(task, opts)) continue; if (!fstack_check_filter(task)) continue; ret = build_tui_node(task, rec, opts); if (ret) break; } add_remaining_node(opts, &handle); tui_main_loop(opts, &handle); close_data_file(opts, &handle); tui_cleanup(); return 0; } #else /* !HAVE_LIBNCURSES */ #include "uftrace.h" #include "utils/utils.h" int command_tui(int argc, char *argv[], struct opts *opts) { pr_warn("TUI is unsupported (libncursesw.so is missing)\n"); return 0; } #endif /* HAVE_LIBNCURSES */ uftrace-0.9.4/configure000077500000000000000000000213511362052523300150670ustar00rootroot00000000000000#!/usr/bin/env bash #-*- mode: shell-script; -*- if [ $(uname -s) != "Linux" ]; then echo "uftrace is only supported on Linux" exit fi prefix=/usr/local srcdir=$(readlink -f $(dirname $0)) objdir=$(readlink -f ${objdir:-${PWD}}) output=${output:-${objdir}/.config} usage() { echo "Usage: $0 [] --help print this message --prefix= set install root dir as (default: /usr/local) --bindir= set executable install dir as (default: \${prefix}/bin) --libdir= set library install dir as (default: \${prefix}/lib) --mandir= set manual doc install dir as (default: \${prefix}/share/man) --objdir= set build dir as (default: \${PWD}) --sysconfdir= override the etc dir as --with-elfutils= search for elfutils in /include and /lib --without-libelf build without libelf (and libdw) (even if found on the system) --without-libdw build without libdw (even if found on the system) --without-libstdc++ build without libstdc++ (even if found on the system) --without-libpython build without libpython (even if found on the system) --without-libluajit build without libluajit (even if found on the system) --without-libncurses build without libncursesw (even if found on the system) --without-capstone build without libcapstone (even if found on the system) --without-perf build without perf event (even if available) --without-schedule build without scheduler event (even if available) --arch= set target architecture (default: system default arch) e.g. x86_64, aarch64, i386, or arm --cross-compile= Specify the compiler prefix during compilation e.g. CC is overridden by \$(CROSS_COMPILE)gcc --cflags= pass extra C compiler flags --ldflags= pass extra linker flags -p preserve old setting Some influential environment variables: ARCH Target architecture e.g. x86_64, aarch64, i386, or arm CROSS_COMPILE Specify the compiler prefix during compilation e.g. CC is overridden by \$(CROSS_COMPILE)gcc CFLAGS C compiler flags LDFLAGS linker flags " exit 1 } # preserve old settings preserve() { if [ -f ${output} ]; then while read pre opt op val; do # do not change directory settings (to prevent confusion) if [ "${opt:3}" = "dir" ]; then continue fi if [ "$op" = ":=" -o "$op" = "=" ]; then eval "$opt=\"$val\"" fi done < ${output} fi } IGNORE= while getopts ":ho:-:p" opt; do case "$opt" in -) # process --long-options case "$OPTARG" in help) usage ;; without-libelf) IGNORE="${IGNORE} libelf libdw" ;; without-*) IGNORE="${IGNORE} ${OPTARG#*-}" ;; *=*) opt=${OPTARG%%=*}; val=${OPTARG#*=} eval "${opt/-/_}='$val'" ;; *) ;; esac ;; o) output=$OPTARG ;; p) preserve ;; *) usage ;; esac done shift $((OPTIND - 1)) for arg; do opt=${arg%%=*} val=${arg#*=} eval "$opt='$val'" done if [ -z "$ARCH" ]; then uname_M=$(uname -m 2>/dev/null || echo not) ARCH=$(echo $uname_M | sed -e s/i.86/i386/ -e s/arm.*/arm/ ) fi if [ "$ARCH" = "x86_64" -o "$ARCH" = "x86" ]; then if echo "$CC $CFLAGS" | grep -w "\-m32" > /dev/null; then ARCH=i386 fi fi # # Support --arch, --cross-compile, --cflags and --ldflags options # if [ ! -z "$arch" ]; then export ARCH=$arch if [ "$arch" = "x86_64" ] || [ "$arch" = "arm" ] || [ "$arch" = "aarch64" ]; then export ARCH=$arch elif [ "$arch" = "i386" ]; then export ARCH="i386" export CFLAGS="-m32 $CFLAGS" export LDFLAGS="-m32 $LDFLAGS" else echo "$arch is not a supported architecture" exit 1 fi fi if [ ! -z "$cross_compile" ]; then export CROSS_COMPILE=$cross_compile fi if [ ! -z "$cflags" ]; then export CFLAGS="$cflags $CFLAGS" fi if [ ! -z "$ldflags" ]; then export LDFLAGS="$ldflags $LDFLAGS" fi bindir=${bindir:-${prefix}/bin} libdir=${libdir:-${prefix}/lib} etcdir=${etcdir:-${prefix}/etc} mandir=${mandir:-${prefix}/share/man} if [ "$etcdir" = /usr/etc ]; then etcdir=/etc fi if [ -n "$sysconfdir" ]; then etcdir=$sysconfdir fi CC=${CC:-${CROSS_COMPILE}gcc} LD=${LD:-${CROSS_COMPILE}ld} # objdir can be changed, reset output objdir=$(readlink -f ${objdir}) output=${output:-${objdir}/.config} # # this is needed to suppress warning from make below. # otherwise it'll get the following warning # when called from make -jN. # # warning: jobserver unavailable: using -j1. Add '+' to parent make rule. # MAKEFLAGS= MAKEOVERRIDES= export CC CFLAGS LD LDFLAGS make -siC ${srcdir}/check-deps check-clean make -siC ${srcdir}/check-deps check-build for dep in $IGNORE; do TARGET= case "$dep" in libelf) TARGET=have_libelf ;; libdw) TARGET=have_libdw ;; libpython*) TARGET='have_libpython*' ;; libluajit*) TARGET=have_libluajit ;; libncurse*) TARGET=have_libncurses ;; libstdc++) TARGET=cxa_demangle ;; capstone) TARGET=have_libcapstone ;; perf*) TARGET=perf_clockid ;; sched*) TARGET=perf_context_switch;; *) ;; esac if [ ! -z "$TARGET" ]; then rm -f ${srcdir}/check-deps/$TARGET fi done echo "uftrace detected system features:" print_feature() { item=$1 file=$2 description=$3 if [ -t 1 -a "$TERM" != "dumb" ]; then # use colored output only when stdout is tty if [ -f ${srcdir}/check-deps/${file} ]; then onoff="\033[32mon \033[0m" else onoff="\033[91mOFF\033[0m" fi else if [ -f ${srcdir}/check-deps/${file} ]; then onoff="on " else onoff="OFF" fi fi printf "...%15s: [ ${onoff} ] - %s\n" "${item}" "${description}" } print_feature2() { item=$1 file1=$2 file2=$3 description=$4 if [ -t 1 -a "$TERM" != "dumb" ]; then # use colored output only when stdout is tty if [ -f ${srcdir}/check-deps/${file1} -o -f ${srcdir}/check-deps/${file2} ]; then onoff="\033[32mon \033[0m" else onoff="\033[91mOFF\033[0m" fi else if [ -f ${srcdir}/check-deps/${file} ]; then onoff="on " else onoff="OFF" fi fi printf "...%15s: [ ${onoff} ] - %s\n" "${item}" "${description}" } printf "...%15s: %s\n" "prefix" "${prefix}" print_feature "libelf" "have_libelf" "more flexible ELF data handling" print_feature "libdw" "have_libdw" "DWARF debug info support" print_feature2 "libpython" "have_libpython2.7" "have_libpython3" "python scripting support" print_feature "libluajit" "have_libluajit" "luajit scripting support" print_feature "libncursesw" "have_libncurses" "TUI support" print_feature "cxa_demangle" "cxa_demangle" "full demangler support with libstdc++" print_feature "perf_event" "perf_clockid" "perf (PMU) event support" print_feature "schedule" "perf_context_switch" "scheduler event support" print_feature "capstone" "have_libcapstone" "full dynamic tracing support" cat >$output <> $output fi cat >>$output < $objdir/Makefile < /dev/null 2>&1 && echo yes || echo no) RM := rm -f INSTALL = install include ../Makefile.include COMMANDS = record replay live report recv info dump graph script tui MANPAGES = uftrace.1 $(patsubst %,uftrace-%.1,$(COMMANDS)) ifeq ($(has_pandoc),yes) all: $(MANPAGES) %.1: %.md $(QUIET_GEN)pandoc -s $< -t man -o $@ # $(DESTDIR) already contains $(mandir) by ../Makefile install: all $(call QUIET_INSTALL, man-pages) $(Q)$(INSTALL) -d -m 755 $(DESTDIR)/man1 $(Q)for F in $(MANPAGES); do $(INSTALL) -m 644 $${F} $(DESTDIR)/man1; done uninstall: $(call QUIET_UNINSTALL, man-pages) $(Q)for F in $(MANPAGES); do ${RM} $(DESTDIR)/man1/$${F}; done clean: $(call QUIET_CLEAN, man-pages) $(Q)$(RM) *.1 else ifneq ($(MAKECMDGOALS),clean) $(warning To install man pages, please install 'pandoc'.) endif install: uninstall: clean: endif .PHONY: all clean PHONY uftrace-0.9.4/doc/ko/000077500000000000000000000000001362052523300143345ustar00rootroot00000000000000uftrace-0.9.4/doc/ko/Makefile000066400000000000000000000016441362052523300160010ustar00rootroot00000000000000all: has_pandoc := $(shell pandoc -v > /dev/null 2>&1 && echo yes || echo no) RM := rm -f INSTALL = install include ../../Makefile.include COMMANDS = record replay live report recv info dump graph script tui MANPAGES = uftrace.1 $(patsubst %,uftrace-%.1,$(COMMANDS)) ifeq ($(has_pandoc),yes) all: $(MANPAGES) %.1: %.md $(QUIET_GEN)pandoc -s $< -t man -o $@ # $(DESTDIR) already contains $(mandir) by ../Makefile install: all $(call QUIET_INSTALL, man-pages) $(Q)$(INSTALL) -d -m 755 $(DESTDIR)/ko/man1 $(Q)for F in $(MANPAGES); do $(INSTALL) -m 644 $${F} $(DESTDIR)/ko/man1; done uninstall: $(call QUIET_UNINSTALL, man-pages) $(Q)for F in $(MANPAGES); do ${RM} $(DESTDIR)/ko/man1/$${F}; done clean: $(call QUIET_CLEAN, man-pages) $(Q)$(RM) *.1 else ifneq ($(MAKECMDGOALS),clean) $(warning To install man pages, please install 'pandoc'.) endif install: uninstall: clean: endif .PHONY: all clean PHONY uftrace-0.9.4/doc/ko/README.md000066400000000000000000000310041362052523300156110ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/namhyung/uftrace.svg?branch=master)](https://travis-ci.org/namhyung/uftrace) [![Coverity scan](https://scan.coverity.com/projects/12421/badge.svg)](https://scan.coverity.com/projects/namhyung-uftrace) uftrace ======= uftrace 는 C/C++ 로 ìž‘ì„±ëœ í”„ë¡œê·¸ëž¨ì˜ ì‹¤í–‰ íë¦„ì„ ì¶”ì (trace)하며 기ë¡í•˜ê³  ë¶„ì„하는 ë„구ì´ë‹¤. ì´ ë„구는 Linux 커ë„ì˜ ftrace 프레임워í¬(특히 함수 그래프 ì¶”ì )ì— í¬ê²Œ ì˜ê°ì„ 받았고 ì‚¬ìš©ìž ê³µê°„ì˜ í”„ë¡œê·¸ëž¨ì„ ì§€ì›í•œë‹¤. 그리고 프로그램 실행과 성능 ë¶„ì„ì— ë„ì›€ì„ ì£¼ëŠ” 다양한 명령어와 필터를 ì§€ì›í•œë‹¤. ![uftrace-live-demo](../uftrace-live-demo.gif) * 홈페ì´ì§€: https://github.com/namhyung/uftrace * 튜토리얼: https://github.com/namhyung/uftrace/wiki/Tutorial * 채팅방: https://gitter.im/uftrace/ko * ë©”ì¼ë§ 리스트: [uftrace@googlegroups.com](https://groups.google.com/forum/#!forum/uftrace) 기능 ======== 프로그램 실행 중 ê° í•¨ìˆ˜ë“¤ê³¼ ê·¸ 실행 ì†Œìš”ì‹œê°„ì„ ì¶”ì í•œë‹¤. 외부 ë¼ì´ë¸ŒëŸ¬ë¦¬ì˜ 호출 ë˜í•œ ì§€ì›í•˜ì§€ë§Œ, 보통 ì§„ìž…ê³¼ 종료만 ì§€ì›í•œë‹¤. 다른 (중첩ëœ) 외부 ë¼ì´ë¸ŒëŸ¬ë¦¬ì˜ 호출 그리고 (혹ì€) ë¼ì´ë¸ŒëŸ¬ë¦¬ 호출ì—서 ë‚´ë¶€ í•¨ìˆ˜ì˜ í˜¸ì¶œì„ ì¶”ì í•˜ëŠ” ê²ƒë„ ê°€ëŠ¥í•˜ë‹¤. 함수 ë ˆë²¨ì˜ êµ¬ì²´ì ì¸ 실행 íë¦„ì„ ë³´ì—¬ì£¼ê³ , ì–´ë–¤ 함수가 가장 ë§Žì€ ì˜¤ë²„í—¤ë“œë¥¼ ë°œìƒì‹œí‚¤ëŠ”ì§€ 알려줄 수 있다. 그리고 실행 환경과 ê´€ë ¨ëœ ë‹¤ì–‘í•œ ì •ë³´ ë˜í•œ 보여준다. ì¶”ì í•  때 특정 함수를 í¬í•¨/제외시키기 위해 필터를 설정할 수 있다. ë˜í•œ, í•¨ìˆ˜ì˜ ì¸ìžì™€ ë°˜í™˜ê°’ì„ ì €ìž¥í•˜ê³  출력할 수 있다. 다중 프로세스와 (혹ì€) 다중 스레드 애플리케ì´ì…˜ì„ ì§€ì›í•œë‹¤. ë§Œì¼ ì‹œìŠ¤í…œì—서 ì»¤ë„ ë‚´ë¶€ì˜ í•¨ìˆ˜ 그래프 ì¶”ì (`CONFIG_FUNCTION_GRAPH_TRACER=y`)ê¸°ëŠ¥ì´ í™œì„±í™”ë˜ì–´ 있다면, 루트 권한 ìƒì—서 (`-k`ì˜µì…˜ì„ í†µí•´) ì»¤ë„ í•¨ìˆ˜ ë˜í•œ ì¶”ì ì´ 가능하다. uftrace 빌드 ë° ì„¤ì¹˜ 방법 ================================ 리눅스 ë°°í¬íŒì—서, [misc/install-deps.sh](../../misc/install-deps.sh) 스í¬ë¦½íŠ¸ëŠ” 추가ì ìœ¼ë¡œ 필요한 소프트웨어를 설치해준다. ì´ëŠ” 고급 ê¸°ëŠ¥ë“¤ì„ ìœ„í•œ 것ì´ë©° 반드시 설치할 필요는 없지만 함께 설치하기를 ì ê·¹ 권장한다. $ sudo misc/install-deps.sh 요구ë˜ëŠ” 소프트웨어를 설치한 ë’¤, 다ìŒê³¼ ê°™ì´ ë¹Œë“œ ë° ì„¤ì¹˜ê°€ 가능하다: $ ./configure $ make $ sudo make install ë” ìžì„¸í•œ 설치방법ì€, [INSTALL.md](../../INSTALL.md) 파ì¼ì„ 참조할 수 있다. uftrace 사용 방법 ================== uftrace 명령어는 다ìŒê³¼ ê°™ì€ í•˜ìœ„ 명령어들로 구성ëœë‹¤. * `record` : í”„ë¡œê·¸ëž¨ì„ ì‹¤í–‰í•˜ë©° ì¶”ì  ë°ì´í„°ë¥¼ 저장한다. * `replay` : ì¶”ì  ë°ì´í„° ë‚´ì˜ í”„ë¡œê·¸ëž¨ ì‹¤í–‰ì„ ë³´ì—¬ì¤€ë‹¤. * `report` : ì¶”ì  ë°ì´í„° ë‚´ì˜ ìˆ˜í–‰ 통계를 보여준다. * `live` : record와 replay를 차례로 수행한다. (기본값) * `info` : ì¶”ì  ë°ì´í„° ë‚´ì˜ ì‹œìŠ¤í…œê³¼ 프로그램 정보를 보여준다. * `dump` : low-levelì˜ ì¶”ì  ë°ì´í„°ë¥¼ 보여준다. * `recv` : 네트워í¬ë¡œë¶€í„° ì¶”ì í•œ ë°ì´í„°ë¥¼ 저장한다. * `graph` : ì¶”ì  ë°ì´í„° ë‚´ì˜ í•¨ìˆ˜ 호출 그래프를 보여준다. * `script` : ì €ìž¥ëœ ì¶”ì  ë°ì´í„°ì˜ 스í¬ë¦½íŠ¸ë¥¼ 실행한다. * `tui` : graph와 report를 위한 í…스트 기반 ì¸í„°íŽ˜ì´ìŠ¤ë¥¼ 보여준다. 사용 가능한 명령어와 ì˜µì…˜ì„ ë³´ê¸° 위해 `-h`, `-?` í˜¹ì€ `--help` ì˜µì…˜ì„ ì‚¬ìš©í•  수 있다. $ uftrace Usage: uftrace [OPTION...] [record|replay|live|report|info|dump|recv|graph|script|tui] [] Try `uftrace --help' or `uftrace --usage' for more information. ë§Œì¼ í•˜ìœ„ 명령어를 ìƒëžµí•œë‹¤ë©´, 기본ì ìœ¼ë¡œ record 와 replay 를 차례로 ì ìš©í•œ 것과 ë™ì¼í•œ `live` 명령어를 수행한다. (하지만 ì¶”ì  ì •ë³´ë¥¼ 파ì¼ë¡œ 저장하지 않는다) record 명령어로 기ë¡ì„ 하려면, ê° í•¨ìˆ˜ë§ˆë‹¤ mcount 나 __cyg_profile_func_enter/exit 함수를 호출하는 프로파ì¼ë§ 코드를 ìƒì„±í•˜ê¸° 위해 실행 파ì¼ì„ `-pg`(í˜¹ì€ `-finstrument-functions`) 옵션으로 컴파ì¼í•´ì•¼ 한다. x86_64 와 AArch64(ARM64) 아키í…처ì—서 (재)ì»´íŒŒì¼ ê³¼ì •ì´ í•„ìš”í•˜ì§€ ì•Šì€ ë™ì  ì¶”ì  ê¸°ëŠ¥ì´ ì‹¤í—˜ì ìœ¼ë¡œ ì§€ì›ë˜ê³  있다. ë˜í•œ 최근 컴파ì¼ëŸ¬ë“¤ 중 (여전히 ì‚¬ìš©ìž í”„ë¡œê·¸ëž¨ì„ ìž¬ì»´íŒŒì¼í•´ì•¼ 하긴 하지만) 비슷한 ë°©ì‹ìœ¼ë¡œ uftraceì˜ ì¶”ì  ê³¼ì •ì—서 ìƒê¸°ëŠ” 오버헤드를 줄ì´ê¸° 위한 ì˜µì…˜ë“¤ì„ ì œê³µí•˜ê³  있다. ë” ìžì„¸í•œ ë‚´ìš©ì€ [dynamic tracing](uftrace-record.md#dynamic-tracing) ì—서 확ì¸í•´ ë³¼ 수 있다. $ uftrace tests/t-abc # DURATION TID FUNCTION 16.134 us [ 1892] | __monstartup(); 223.736 us [ 1892] | __cxa_atexit(); [ 1892] | main() { [ 1892] | a() { [ 1892] | b() { [ 1892] | c() { 2.579 us [ 1892] | getpid(); 3.739 us [ 1892] | } /* c */ 4.376 us [ 1892] | } /* b */ 4.962 us [ 1892] | } /* a */ 5.769 us [ 1892] | } /* main */ ë” ìƒì„¸í•œ ë¶„ì„ì„ í•˜ë ¤ë©´, record를 통해 ìš°ì„  ë°ì´í„°ë¥¼ 기ë¡í•˜ê³  replay, report, graph, dump, info와 ê°™ì€ ë¶„ì„ ëª…ë ¹ì–´ë¥¼ 여러 번 사용하는 ê²ƒì´ ì¢‹ë‹¤. $ uftrace record tests/t-abc record 명령어는 ì¶”ì  ë°ì´í„° 파ì¼ì„ í¬í•¨í•˜ëŠ” uftrace.data 디렉터리를 만든다. 다른 ë¶„ì„ ëª…ë ¹ì–´ë“¤ì€ ê·¸ 디렉터리가 현재 ê²½ë¡œì— ìžˆì„ ê²ƒìœ¼ë¡œ 예ìƒí•˜ì§€ë§Œ, 다른 디렉터리를 쓰기 위해서는 `-d` ì˜µì…˜ì„ ì‚¬ìš©í•˜ë©´ ëœë‹¤. `replay` 명령어는 위 실행 결과를 보여준다. 보다시피, t-abc는 그저 a, b, c 함수를 호출하는 단순한 프로그램ì´ë‹¤. c 함수ì—서, ì¼ë°˜ì ì¸ ì‹œìŠ¤í…œì˜ C ë¼ì´ë¸ŒëŸ¬ë¦¬ (glibc)ì— ë‚´ìž¥ëœ ë¼ì´ë¸ŒëŸ¬ë¦¬ 함수 getpid()를 호출한다. (__cxa_atexit()ë„ ë§ˆì°¬ê°€ì§€ 경우ì´ë‹¤.) 사용ìžë“¤ì€ í•¨ìˆ˜ë“¤ì˜ ë ˆì½”ë“œ/ì¶œë ¥ì„ ì œí•œí•˜ê¸° 위해 다양한 필터를 ì´ìš©í•  수 있다. ê¹Šì´ í•„í„° (`-D` 옵션)ì€ ì£¼ì–´ì§„ 호출 깊ì´ë³´ë‹¤ ë” ê¹Šê²Œ í˜¸ì¶œëœ í•¨ìˆ˜ë“¤ì„ ìƒëžµí•˜ëŠ” í•„í„°ì´ë‹¤. 시간 í•„í„° (`-t` 옵션)ì€ ì£¼ì–´ì§„ 시간보다 ë” ìž‘ì€ ì‹œê°„ë™ì•ˆ ì‹¤í–‰ëœ í•¨ìˆ˜ë“¤ì„ ìƒëžµí•˜ëŠ” í•„í„°ì´ë‹¤. 그리고 함수 í•„í„°(`-F`와 `-N` 옵션)ì€ ì£¼ì–´ì§„ í•¨ìˆ˜ì˜ í•˜ìœ„ í•¨ìˆ˜ë“¤ì„ ë³´ì—¬ì£¼ê³ /ìƒëžµí•˜ëŠ” í•„í„°ì´ë‹¤. `-k` 옵션으로 ì»¤ë„ í•¨ìˆ˜ë“¤ ë˜í•œ ì¶”ì ì´ 가능하다 (루트 권한 í•„ìš”). 보통 'hello world' í”„ë¡œê·¸ëž¨ì— ëŒ€í•œ 출력 결과는 아래와 같다 (시스템 í˜¸ì¶œì„ ì§ì ‘ 호출하기 위해, ì¼ë°˜ì ì¸ printf()ê°€ 아닌 stderr와 fprintf()를 사용하기로 한 ê²ƒì— ìœ ì˜í•˜ë¼): $ sudo uftrace -k tests/t-hello Hello world # DURATION TID FUNCTION 1.365 us [21901] | __monstartup(); 0.951 us [21901] | __cxa_atexit(); [21901] | main() { [21901] | fprintf() { 3.569 us [21901] | __do_page_fault(); 10.127 us [21901] | sys_write(); 20.103 us [21901] | } /* fprintf */ 21.286 us [21901] | } /* main */ fprintf()호출 ë‚´ë¶€ì—서 page fault 핸들러와 write syscall 핸들러가 호출ë˜ì—ˆìŒì„ 확ì¸í•  수 있다. ë˜í•œ í•¨ìˆ˜ì˜ ì¸ìžì™€ 반환 ê°’ì„ ê°ê° `-A`와 `-R`옵션으로 기ë¡í•˜ê³  보여줄 수 있다. ì´í•˜ 예제ì—서는 'fib'(피보나치 숫ìž) í•¨ìˆ˜ì˜ ì²« 번째 ì¸ìžì™€ ë¦¬í„´ê°’ì„ ê¸°ë¡í•œë‹¤. $ uftrace record -A fib@arg1 -R fib@retval tests/t-fibonacci 5 $ uftrace replay # DURATION TID FUNCTION 2.853 us [22080] | __monstartup(); 2.194 us [22080] | __cxa_atexit(); [22080] | main() { 2.706 us [22080] | atoi(); [22080] | fib(5) { [22080] | fib(4) { [22080] | fib(3) { 7.473 us [22080] | fib(2) = 1; 0.419 us [22080] | fib(1) = 1; 11.452 us [22080] | } = 2; /* fib */ 0.460 us [22080] | fib(2) = 1; 13.823 us [22080] | } = 3; /* fib */ [22080] | fib(3) { 0.424 us [22080] | fib(2) = 1; 0.437 us [22080] | fib(1) = 1; 2.860 us [22080] | } = 2; /* fib */ 19.600 us [22080] | } = 5; /* fib */ 25.024 us [22080] | } /* main */ `report` 명령어는 ì–´ë–¤ 함수가 ê·¸ ìžì‹ì„ í¬í•¨í•´ì„œ 가장 오랫ë™ì•ˆ 실행ë˜ì—ˆëŠ”ì§€, ì´ ì‹œê°„ì„ ì•Œë ¤ì¤€ë‹¤. $ uftrace report Total time Self time Calls Function ========== ========== ========== ==================================== 25.024 us 2.718 us 1 main 19.600 us 19.600 us 9 fib 2.853 us 2.853 us 1 __monstartup 2.706 us 2.706 us 1 atoi 2.194 us 2.194 us 1 __cxa_atexit `graph` 명령어는 주어진 í•¨ìˆ˜ì˜ í˜¸ì¶œ 그래프를 보여준다. ìœ„ì˜ ì˜ˆì œì—서, main í•¨ìˆ˜ì˜ í˜¸ì¶œ 그래프는 아래와 같다: $ uftrace graph main # Function Call Graph for 'main' (session: 073f1e84aa8b09d3) =============== BACKTRACE =============== backtrace #0: hit 1, time 25.024 us [0] main (0x40066b) ========== FUNCTION CALL GRAPH ========== 25.024 us : (1) main 2.706 us : +-(1) atoi : | 19.600 us : +-(1) fib 16.683 us : (2) fib 12.773 us : (4) fib 7.892 us : (2) fib `dump` ëª…ë ¹ì€ ê¸°ë¡ëœ ë°ì´í„°ë¥¼ 그대로(raw) 출력하여 보여준다. `uftrace dump --chrome` ëª…ë ¹ì„ ì‚¬ìš©í•˜ë©´ í¬ë¡¬ 브ë¼ìš°ì €ì—서 결과를 확ì¸í•  수 있다. ì´í•˜ëŠ” ìž‘ì€ C++ template metaprogramì„ ì»´íŒŒì¼í•˜ëŠ” clang (LLVM)ì˜ ì‹¤í–‰ ê³¼ì •ì„ ë³´ì—¬ì¤€ë‹¤. [![uftrace-chrome-dump](../uftrace-chrome.png)](https://uftrace.github.io/dump/clang.tmp.fib.html) flame-graph 형ì‹ì˜ ê²°ê³¼ ë˜í•œ ì§€ì›í•œë‹¤. 해당 ë°ì´í„°ëŠ” `uftrace dump --flame-graph`로 실행ë˜ì–´ [flamegraph.pl](https://github.com/brendangregg/FlameGraph/blob/master/flamegraph.pl)로 넘겨질 수 있다. ì´í•˜ëŠ” 간단한 C í”„ë¡œê·¸ëž¨ì„ gcc로 컴파ì¼í•œ ê²°ê³¼ì— ëŒ€í•œ flame graphì´ë‹¤. [![uftrace-flame-graph-dump](https://uftrace.github.io/dump/gcc.svg)](https://uftrace.github.io/dump/gcc.svg) `info` 명령어는 기ë¡ì´ ë˜ì—ˆì„ ë•Œì˜ ì‹œìŠ¤í…œê³¼ 프로그램 정보를 보여준다. $ uftrace info # system information # ================== # program version : uftrace v0.8.1 # recorded on : Tue May 24 11:21:59 2016 # cmdline : uftrace record tests/t-abc # cpu info : Intel(R) Core(TM) i7-3930K CPU @ 3.20GHz # number of cpus : 12 / 12 (online / possible) # memory info : 20.1 / 23.5 GB (free / total) # system load : 0.00 / 0.06 / 0.06 (1 / 5 / 15 min) # kernel version : Linux 4.5.4-1-ARCH # hostname : sejong # distro : "Arch Linux" # # process information # =================== # number of tasks : 1 # task list : 5098 # exe image : /home/namhyung/project/uftrace/tests/t-abc # build id : a3c50d25f7dd98dab68e94ef0f215edb06e98434 # exit status : exited with code: 0 # elapsed time : 0.003219479 sec # cpu time : 0.000 / 0.003 sec (sys / user) # context switch : 1 / 1 (voluntary / involuntary) # max rss : 3072 KB # page fault : 0 / 172 (major / minor) # disk iops : 0 / 24 (read / write) `script` 명령어는 기ë¡ëœ ë°ì´í„°ì— ì‚¬ìš©ìž ì •ì˜ ìŠ¤í¬ë¦½íŠ¸ë¥¼ 실행할 수 있게 한다. 현재까지 ì§€ì›ë˜ëŠ” 스í¬ë¦½íŠ¸ëŠ” Python 2.7 ê³¼ Lua 5.1 ì´ë‹¤. `tui` 명령어는 ncurses 를 ì´ìš©í•œ í…스트 기반 대화형 ì‚¬ìš©ìž ì¸í„°íŽ˜ì´ìŠ¤ë¥¼ 위한 명령어ì´ë‹¤. 현재 `graph`, `report`, `info` ëª…ë ¹ì–´ì˜ ê¸°ë³¸ì ì¸ ê¸°ëŠ¥ì„ ì œê³µí•œë‹¤. 제약사항 =========== - 리눅스ì—서 실행ë˜ëŠ” 네ì´í‹°ë¸Œ C/C++ ì‚¬ìš©ìž í”„ë¡œê·¸ëž¨ì— ëŒ€í•´ì„œë§Œ 사용 가능하다. - ì´ë¯¸ 실행 ì¤‘ì¸ í”„ë¡œì„¸ìŠ¤ì˜ ì¶”ì ì€ *불가능*하다. - ì „ì²´ ì‹œìŠ¤í…œì— ëŒ€í•œ 통합 ë¶„ì„ì€ *불가능*하다. - 현재는 x86 (32 와 64 비트), ARM (v6 ì´í›„ 버전), AArch64 ë§Œ ì§€ì›í•œë‹¤. ë¼ì´ì„ ìФ ======= uftrace 는 GPL v2. ë¼ì´ì„ ìФ í•˜ì— ë°°í¬ë˜ë©° ìžì„¸í•œ ë‚´ìš©ì€ [COPYING](../../COPYING) 파ì¼ì—서 확ì¸í•  수 있다.uftrace-0.9.4/doc/ko/uftrace-dump.md000066400000000000000000000163511362052523300172600ustar00rootroot00000000000000% UFTRACE-DUMP(1) Uftrace User Manuals % Namhyung Kim % Sep, 2018 ì´ë¦„ ==== uftrace-dump - 기ë¡ëœ ë°ì´í„°ë¥¼ 다양한 형ì‹ìœ¼ë¡œ 출력한다. 사용법 ====== uftrace dump [*options*] 설명 ==== ë°ì´í„° 파ì¼ì— 기ë¡ëœ ë°ì´í„°ë¥¼ 보여주는 명령어ì´ë‹¤. 출력 형ì‹ì€ --chrome, --flame-graph ë˜ëŠ” --graphviz 와 ê°™ì€ ì˜µì…˜ìœ¼ë¡œ 설정할 수 있다. DUMP 옵션 ========= \--chrome : 구글 í¬ë¡¬ ì¶”ì  ê¸°ëŠ¥ì—서 사용ë˜ëŠ” JSON 형ì‹ì˜ ê²°ê³¼ë¬¼ì„ í‘œì‹œí•œë‹¤. \--flame-graph : 최신 웹 브ë¼ìš°ì €ì—서 ë³¼ 수 있는 FlameGraph 형ì‹ìœ¼ë¡œ 표시한다. (FlameGraph 툴로 처리 í•„ìš”) \--graphviz : Graphviz 툴킷ì—서 사용ë˜ëŠ” DOT 형ì‹ì˜ ê²°ê³¼ë¬¼ì„ í‘œì‹œí•œë‹¤. \--debug : 16진수 ë°ì´í„°ë¥¼ 보여준다. \--sample-time=*시간* : --flame-graph ì˜µì…˜ì˜ ê²°ê³¼ë¬¼ì„ ìƒì„±í•  때 ìƒ˜í”Œë§ ì‹œê°„ì„ ì ìš©í•œë‹¤. 기본으로는 ê° í•¨ìˆ˜ì˜ í˜¸ì¶œ 수가 ì ìš©ëœë‹¤. ì´ ì˜µì…˜ì´ ì‚¬ìš©ë˜ë©´ 주어진 단위로 실행 ì‹œê°„ì„ ê³„ì‚°í•˜ì—¬ 샘플ë§í•œë‹¤. 만약 주어진 ìƒ˜í”Œë§ ì‹œê°„ë³´ë‹¤ ì ê²Œ ìˆ˜í–‰ëœ í•¨ìˆ˜ëŠ” 결과물 ì—서 제외ë˜ì§€ë§Œ, ë” ê¸¸ê²Œ ìˆ˜í–‰ëœ í•¨ìˆ˜ëŠ” 표시ëœë‹¤. 공통 옵션 ========= -F *FUNC*, \--filter=*FUNC* : ì„ íƒëœ 함수들(그리고 ê·¸ ë‚´ë¶€ì˜ í•¨ìˆ˜ë“¤)ë§Œ 출력하ë„ë¡ í•„í„°ë¥¼ 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ `uftrace-replay`(1) 를 참고한다. -N *FUNC*, \--notrace=*FUNC* : ì„ íƒëœ 함수들 (ë˜ëŠ” ê·¸ 아래 함수들)ì„ ì¶œë ¥ì—서 제외하ë„ë¡ ì„¤ì •í•˜ëŠ” 옵션ì´ë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ `uftrace-replay`(1) 를 참고한다. -C *FUNC*, \--caller-filter=*FUNC* : ì„ íƒëœ í•¨ìˆ˜ì˜ í˜¸ì¶œìžë¥¼ 출력하는 필터를 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ `uftrace-replay`(1) 를 참고한다. -T *TRG*, \--trigger=*TRG* : ì„ íƒëœ í•¨ìˆ˜ì˜ íŠ¸ë¦¬ê±°ë¥¼ 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. íŠ¸ë¦¬ê±°ì— ëŒ€í•œ ì„¤ëª…ì€ `uftrace-replay`(1) 를 참고한다. -D *DEPTH*, \--depth *DEPTH* : 함수가 ì¤‘ì²©ë  ìˆ˜ 있는 최대 깊ì´ë¥¼ 설정한다. (ì´ë¥¼ 넘어서는 ìƒì„¸í•œ 함수 ì‹¤í–‰ê³¼ì •ì€ ë¬´ì‹œí•œë‹¤.) -t *TIME*, \--time-filter=*TIME* : 설정한 시간 ì´í•˜ë¡œ ìˆ˜í–‰ëœ í•¨ìˆ˜ëŠ” 표시하지 않게 한다. 만약 ì–´ë–¤ 함수가 명시ì ìœ¼ë¡œ 'trace' 트리거가 ì ìš©ëœ 경우, ê·¸ 함수는 실행 시간과 ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. \--no-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì€ í‘œì‹œí•˜ì§€ 않게 한다. \--no-event : ì´ë²¤íŠ¸ëŠ” 표시하지 않게 한다. \--match=*TYPE* : 타입(TYPE)으로 ì¼ì¹˜í•˜ëŠ” íŒ¨í„´ì„ ë³´ì—¬ì¤€ë‹¤. 가능한 형태는 `regex`와 `glob`ì´ë‹¤. 기본 ì„¤ì •ì€ `regex`ì´ë‹¤. 공통 ë¶„ì„ ì˜µì…˜ ======================= \--kernel-full : ì‚¬ìš©ìž í•¨ìˆ˜ ë°–ì—서 í˜¸ì¶œëœ ëª¨ë“  ì»¤ë„ í•¨ìˆ˜ë¥¼ 출력한다. ì´ ì˜µì…˜ì€ --chrome, --flame-graph ë˜ëŠ” --graphviz 옵션과 함께 ì‚¬ìš©ë  ë•Œë§Œ ì˜ë¯¸ê°€ 있다. \--kernel-only : ì‚¬ìš©ìž í•¨ìˆ˜ë¥¼ 제외한 ì»¤ë„ í•¨ìˆ˜ë§Œ 출력한다. \--event-full : ì‚¬ìš©ìž í•¨ìˆ˜ ë°–ì˜ ëª¨ë“  (사용ìž) ì´ë²¤íŠ¸ë¥¼ 출력한다. ì´ ì˜µì…˜ì€ --chrome, --flame-graph ë˜ëŠ” --graphviz 옵션과 함께 ì‚¬ìš©ë  ë•Œë§Œ ì˜ë¯¸ê°€ 있다. \--tid=*TID*[,*TID*,...] : 주어진 태스í¬ì— ì˜í•´ í˜¸ì¶œëœ í•¨ìˆ˜ë“¤ë§Œ 출력한다. `uftrace report --task` ë˜ëŠ” `uftrace info` 를 ì´ìš©í•´ ë°ì´í„° íŒŒì¼ ë‚´ì˜ íƒœìŠ¤í¬ ëª©ë¡ì„ ë³¼ 수 있다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. \--demangle=*TYPE* : í•„í„°, 트리거, 함수ì¸ìžì™€ (ë˜ëŠ”) 반환 ê°’ì„ ë””ë§¹ê¸€(demangle)ëœ C++ 심볼 ì´ë¦„으로 사용한다. "full", "simple", "no" ê°’ì„ ì‚¬ìš©í•  수 있다. 기본 ì„¤ì •ì€ "simple"ì´ë©°, 템플릿 파ë¼ë¯¸í„°ì™€ 함수 ì¸ìžë¥¼ 무시한다. -r *RANGE*, \--time-range=*RANGE* : 시간 범위 RANGE ë‚´ì— ì‹¤í–‰ëœ í•¨ìˆ˜ë“¤ë§Œ 출력한다. RANGE 는 \<시작\>~\<ë\> ("~"로 구분) ì´ê³  \<시작\>ê³¼ \<ë\> 중 하나는 ìƒëžµí•  수 있다. \<시작\>ê³¼ \<ë\>ì€ íƒ€ìž„ìŠ¤íƒ¬í”„ ë˜ëŠ” '100us'와 ê°™ì€ \<시간단위\>ê°€ 있는 경과시간ì´ë‹¤. `uftrace replay`(1) ì—서 `-f time` ë˜ëŠ” `-f elapsed` 를 ì´ìš©í•´ 타임스탬프 ë˜ëŠ” ê²½ê³¼ì‹œê°„ì„ í™•ì¸í•  수 있다. 예제 ==== ì´ ëª…ë ¹ì–´ëŠ” 아래와 ê°™ì€ ê²°ê³¼ë¥¼ 출력한다. $ uftrace record abc $ uftrace dump uftrace file header: magic = 4674726163652100 uftrace file header: version = 4 uftrace file header: header size = 40 uftrace file header: endian = 1 (little) uftrace file header: class = 2 (64 bit) uftrace file header: features = 0x63 (PLTHOOK | TASK_SESSION | SYM_REL_ADDR | MAX_STACK) uftrace file header: info = 0x3ff reading 23043.dat 105430.415350255 23043: [entry] __monstartup(4004d0) depth: 0 105430.415351178 23043: [exit ] __monstartup(4004d0) depth: 0 105430.415351932 23043: [entry] __cxa_atexit(4004f0) depth: 0 105430.415352687 23043: [exit ] __cxa_atexit(4004f0) depth: 0 105430.415353833 23043: [entry] main(400512) depth: 0 105430.415353992 23043: [entry] a(4006b2) depth: 1 105430.415354112 23043: [entry] b(4006a0) depth: 2 105430.415354230 23043: [entry] c(400686) depth: 3 105430.415354425 23043: [entry] getpid(4004b0) depth: 4 105430.415355035 23043: [exit ] getpid(4004b0) depth: 4 105430.415355549 23043: [exit ] c(400686) depth: 3 105430.415355761 23043: [exit ] b(4006a0) depth: 2 105430.415355943 23043: [exit ] a(4006b2) depth: 1 105430.415356109 23043: [exit ] main(400512) depth: 0 $ uftrace dump --chrome -F main {"traceEvents":[ {"ts":105430415353,"ph":"B","pid":23043,"name":"main"}, {"ts":105430415353,"ph":"B","pid":23043,"name":"a"}, {"ts":105430415354,"ph":"B","pid":23043,"name":"b"}, {"ts":105430415354,"ph":"B","pid":23043,"name":"c"}, {"ts":105430415354,"ph":"B","pid":23043,"name":"getpid"}, {"ts":105430415355,"ph":"E","pid":23043,"name":"getpid"}, {"ts":105430415355,"ph":"E","pid":23043,"name":"c"}, {"ts":105430415355,"ph":"E","pid":23043,"name":"b"}, {"ts":105430415355,"ph":"E","pid":23043,"name":"a"}, {"ts":105430415356,"ph":"E","pid":23043,"name":"main"} ], "metadata": { "command_line":"uftrace record abc ", "recorded_time":"Tue May 24 19:44:54 2016" } } $ uftrace dump --flame-graph --sample-time 1us main 1 main;a;b;c 1 $ uftrace dump --graphviz \# command_line "uftrace record tests/t-abc" digraph "/home/m/git/uftrace/tests/t-abc" { \# Attributes splines=ortho; concentrate=true; node [shape="rect",fontsize="7",style="filled"]; edge [fontsize="7"]; \# Elements main[xlabel = "Calls : 1"] main->a[xlabel = "Calls : 1"] a->b[xlabel = "Calls : 1"] b->c[xlabel = "Calls : 1"] c->getpid[xlabel = "Calls : 1"] } 함께 보기 ========= `uftrace`(1), `uftrace-record`(1), `uftrace-replay`(1) ë²ˆì—­ìž ====== 민지수 uftrace-0.9.4/doc/ko/uftrace-graph.md000066400000000000000000000326651362052523300174220ustar00rootroot00000000000000% UFTRACE-GRAPH(1) Uftrace User Manuals % Namhyung Kim % Sep, 2018 ì´ë¦„ ==== uftrace-graph - 기ë¡ëœ ë°ì´í„°ì˜ 함수 호출 그래프를 출력한다. 사용법 ====== uftrace graph [*options*] [*FUNCTION*] 설명 ==== ì´ ëª…ë ¹ì–´ëŠ” ëŒ€ìƒ ë°”ì´ë„ˆë¦¬ ë˜ëŠ” uftrace 형ì‹ìœ¼ë¡œ 기ë¡ëœ ë°ì´í„°ì— 있는 í•¨ìˆ˜ë“¤ì— ëŒ€í•œ 함수 호출 그래프를 출력한다. 만약 함수 ì´ë¦„ì„ ìƒëžµí•˜ë©´ ì „ì²´ 함수 호출 그래프가 보여지고, 함수 ì´ë¦„ì´ í•˜ë‚˜ 주어지면 ëŒ€ìƒ í•¨ìˆ˜ì— ëŒ€í•œ 백트레ì´ìФ(backtrace) 들과 ê·¸ 함수가 호출하는 í•¨ìˆ˜ë“¤ì— ëŒ€í•œ 호출 그래프를 보여준다. ê²°ê³¼ì—서 ë³´ì´ëŠ” ê° í•¨ìˆ˜ë“¤ì˜ ì •ë³´ì—는 호출 횟수와 ê·¸ 함수를 ì‹¤í–‰í•˜ëŠ”ë° ì†Œìš”ëœ ì „ì²´ ì‹œê°„ì´ í•¨ê»˜ 보여진다. GRAPH 옵션 ========= -f *FIELD*, \--output-fields=*FIELD* : 출력 필드를 ì‚¬ìš©ìž ì§€ì •ìœ¼ë¡œ 설정한다. 설정 가능한 ê°’ì€ total, self, addr ì´ë©° 쉼표를 사용하여 여러 필드를 설정할 수 있다. 'none' ê³¼ ê°™ì€ íŠ¹ìˆ˜ 필드를 사용하여 모든 필드를 숨길 수 있으며 기본 ì„¤ì •ì€ 'total' ì´ë‹¤. í•„ë“œì— ëŒ€í•œ ìƒì„¸í•œ ë‚´ìš©ì€ *FIELDS* 를 참고할 수 있다. 공통 옵션 ========= -F *FUNC*, \--filter=*FUNC* : ì„ íƒëœ 함수들(그리고 ê·¸ ë‚´ë¶€ì˜ í•¨ìˆ˜ë“¤)ë§Œ 출력하ë„ë¡ í•„í„°ë¥¼ 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ `uftrace-replay`(1) 를 참고한다. -N *FUNC*, \--notrace=*FUNC* : ì„ íƒëœ 함수들 (ë˜ëŠ” ê·¸ 아래 함수들)ì„ ì¶œë ¥ì—서 제외하ë„ë¡ ì„¤ì •í•˜ëŠ” 옵션ì´ë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ `uftrace-replay`(1) 를 참고한다. -C *FUNC*, \--caller-filter=*FUNC* : ì„ íƒëœ í•¨ìˆ˜ì˜ í˜¸ì¶œìžë¥¼ 출력하는 필터를 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ `uftrace-replay`(1) 를 참고한다. -T *TRG*, \--trigger=*TRG* : ì„ íƒëœ í•¨ìˆ˜ì˜ íŠ¸ë¦¬ê±°ë¥¼ 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. íŠ¸ë¦¬ê±°ì— ëŒ€í•œ ì„¤ëª…ì€ `uftrace-replay`(1) 를 참고한다. -D *DEPTH*, \--depth *DEPTH* : 함수가 ì¤‘ì²©ë  ìˆ˜ 있는 최대 깊ì´ë¥¼ 설정한다. (ì´ë¥¼ 넘어서는 ìƒì„¸í•œ 함수 ì‹¤í–‰ê³¼ì •ì€ ë¬´ì‹œí•œë‹¤.) -t *TIME*, \--time-filter=*TIME* : 설정한 시간 ì´í•˜ë¡œ ìˆ˜í–‰ëœ í•¨ìˆ˜ëŠ” 표시하지 않게 한다. 만약 ì–´ë–¤ 함수가 명시ì ìœ¼ë¡œ 'trace' 트리거가 ì ìš©ëœ 경우, ê·¸ 함수는 실행 시간과 ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. \--no-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì€ í‘œì‹œí•˜ì§€ 않게 한다. \--no-event : ì´ë²¤íŠ¸ëŠ” 표시하지 않게 한다. \--match=*TYPE* : 타입(TYPE)으로 ì¼ì¹˜í•˜ëŠ” íŒ¨í„´ì„ ë³´ì—¬ì¤€ë‹¤. 가능한 형태는 `regex`와 `glob`ì´ë‹¤. 기본 ì„¤ì •ì€ `regex`ì´ë‹¤. 공통 ë¶„ì„ ì˜µì…˜ ======================= \--kernel-full : ì‚¬ìš©ìž í•¨ìˆ˜ ë°–ì—서 í˜¸ì¶œëœ ëª¨ë“  ì»¤ë„ í•¨ìˆ˜ë¥¼ 출력한다. \--kernel-only : ì‚¬ìš©ìž í•¨ìˆ˜ë¥¼ 제외한 ì»¤ë„ í•¨ìˆ˜ë§Œ 출력한다. \--event-full : ì‚¬ìš©ìž í•¨ìˆ˜ ë°–ì˜ ëª¨ë“  (사용ìž) ì´ë²¤íŠ¸ë¥¼ 출력한다. \--tid=*TID*[,*TID*,...] : 주어진 태스í¬ì— ì˜í•´ í˜¸ì¶œëœ í•¨ìˆ˜ë“¤ë§Œ 출력한다. `uftrace report --task` ë˜ëŠ” `uftrace info` 를 ì´ìš©í•´ ë°ì´í„° íŒŒì¼ ë‚´ì˜ íƒœìŠ¤í¬ ëª©ë¡ì„ ë³¼ 수 있다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. \--demangle=*TYPE* : í•„í„°, 트리거, 함수ì¸ìžì™€ (ë˜ëŠ”) 반환 ê°’ì„ ë””ë§¹ê¸€(demangle)ëœ C++ 심볼 ì´ë¦„으로 사용한다. "full", "simple", "no" ê°’ì„ ì‚¬ìš©í•  수 있다. 기본 ì„¤ì •ì€ "simple"ì´ë©°, 템플릿 파ë¼ë¯¸í„°ì™€ 함수 ì¸ìžë¥¼ 무시한다. -r *RANGE*, \--time-range=*RANGE* : 시간 범위 RANGE ë‚´ì— ì‹¤í–‰ëœ í•¨ìˆ˜ë“¤ë§Œ 출력한다. RANGE 는 \<시작\>~\<ë\> ("~"로 구분) ì´ê³  \<시작\>ê³¼ \<ë\> 중 하나는 ìƒëžµí•  수 있다. \<시작\>ê³¼ \<ë\>ì€ íƒ€ìž„ìŠ¤íƒ¬í”„ ë˜ëŠ” '100us'와 ê°™ì€ \<시간단위\>ê°€ 있는 경과시간ì´ë‹¤. `uftrace replay`(1) ì—서 `-f time` ë˜ëŠ” `-f elapsed` 를 ì´ìš©í•´ 타임스탬프 ë˜ëŠ” ê²½ê³¼ì‹œê°„ì„ í™•ì¸í•  수 있다. 예제 ==== ì´ ëª…ë ¹ì–´ëŠ” 아래와 ê°™ì€ ê²°ê³¼ë¥¼ 출력한다. $ uftrace record loop $ uftrace replay # DURATION TID FUNCTION [24447] | main() { [24447] | foo() { 8.134 us [24447] | loop(); 7.296 us [24447] | loop(); 7.234 us [24447] | loop(); 24.324 us [24447] | } /* foo */ [24447] | foo() { 7.234 us [24447] | loop(); 7.231 us [24447] | loop(); 7.231 us [24447] | loop(); 22.302 us [24447] | } /* foo */ [24447] | bar() { 10.100 ms [24447] | usleep(); 10.138 ms [24447] | } /* bar */ 10.293 ms [24447] | } /* main */ `graph` 명령어를 실행하면 다ìŒê³¼ ê°™ì€ í•¨ìˆ˜ 호출 그래프를 출력한다. $ uftrace graph # Function Call Graph for 'loop' (session: 073f1e84aa8b09d3) ========== FUNCTION CALL GRAPH ========== 10.293 ms : (1) loop 10.293 ms : (1) main 46.626 us : +-(2) foo 44.360 us : | (6) loop : | 10.138 ms : +-(1) bar 10.100 ms : (1) usleep 가장 최ìƒë‹¨ì— 있는 노드는 실제 함수가 ì•„ë‹ˆë¼ ì‹¤í–‰ ì´ë¯¸ì§€ì˜ ì´ë¦„ì„ ë‚˜íƒ€ë‚¸ë‹¤. ì™¼ìª½ì— ìžˆëŠ” ì‹œê°„ì€ ì˜¤ë¥¸ìª½ì— ìžˆëŠ” í•¨ìˆ˜ì˜ ì´ ì‹¤í–‰ ì‹œê°„ì„ ë‚˜íƒ€ë‚¸ë‹¤. 함수 ì´ë¦„ ì•žì˜ ê´„í˜¸ ì•ˆì˜ ìˆ«ìžëŠ” 호출 횟수를 ì˜ë¯¸í•œë‹¤. 위ì—서 `main` 함수는 단 한번 호출ë˜ì–´ 약 10 밀리초(msec) ë™ì•ˆ 실행ë˜ì—ˆê³ , `foo` 함수는 ë‘번 í˜¸ì¶œëœ ë‹¤ìŒ ê·¸ 안ì—서 `loop` 함수를 ì´ 6 번 호출 하였다. ë˜í•œ, `main` 함수는 `bar` 함수를 한번 호출하고, `bar` 함수는 다시 `usleep` 함수를 호출한 ê²ƒì„ ì•Œ 수 있다. ì´ëŸ¬í•œ ë¶„ì„ ê²°ê³¼ë¥¼ 통해 `usleep` 함수는 `main` 함수ì—서 ì§ì ‘ 호출 ëœê²ƒì´ ì•„ë‹˜ì„ ì•Œ 수 있다. `graph` 명령어를 실행하고 `main` í•¨ìˆ˜ëª…ì„ ì§€ì •í•˜ë©´ 아래와 ê°™ì´ í•´ë‹¹ í•¨ìˆ˜ì˜ í˜¸ì¶œ 그래프와 함께 백트레ì´ìФ 정보를 ê°™ì´ ë³´ì—¬ì¤€ë‹¤. $ uftrace graph main # Function Call Graph for 'main' (session: 073f1e84aa8b09d3) =============== BACKTRACE =============== backtrace #0: hit 1, time 10.293 ms [0] main (0x4004f0) ========== FUNCTION CALL GRAPH ========== # TOTAL TIME FUNCTION 10.293 ms : (1) main 46.626 us : +-(2) foo 44.360 us : | (6) loop : | 10.138 ms : +-(1) bar 10.100 ms : (1) usleep 'main' 함수는 최ìƒìœ„ 함수ì´ë¯€ë¡œ 백트레ì´ìФ 결과가 없지만 `loop` 함수를 지정하면 다ìŒê³¼ ê°™ì´ ê²°ê³¼ë¥¼ ë³¼ 수 있다. $ uftrace graph loop # Function Call Graph for 'loop' (session: 073f1e84aa8b09d3) =============== BACKTRACE =============== backtrace #0: hit 6, time 44.360 us [0] main (0x4004b0) [1] foo (0x400622) [2] loop (0x400f5f6) ========== FUNCTION CALL GRAPH ========== # TOTAL TIME FUNCTION 44.360 us : (6) loop ì´ ë°±íŠ¸ë ˆì´ìФ ê²°ê³¼ì—서 `loop` 함수는 `foo` 함수ì—서 호출ë˜ì—ˆê³  다시 `foo` 함수는 `main` 함수ì—서 호출ëœê²ƒì„ 알 수 있다. `loop` 함수는 다른 함수를 호출하지 않는다. ì´ ê²½ìš°ì— `loop` 함수는 단 í•˜ë‚˜ì˜ ê²½ë¡œë¥¼ 통해서만 호출ë˜ì—ˆê¸° ë•Œë¬¸ì— backtrace #0 ì˜ í˜¸ì¶œ 횟수는 6 ì´ëœë‹¤. graph 명령어는 함수 ë‹¨ìœ„ì˜ í˜¸ì¶œ 그래프를 보여주지만, --task ì˜µì…˜ì„ ì‚¬ìš©í•˜ë©´ 어떻게 프로세스와 ìŠ¤ë ˆë“œë“¤ì´ ìƒì„±ë˜ì—ˆëŠ”ì§€ë¥¼ 보여주는 íƒœìŠ¤í¬ ë‹¨ìœ„ 그래프를 보여줄 수 있다. 예를 들면, GCC 컴파ì¼ëŸ¬ì˜ ì‹¤í–‰ì— ëŒ€í•œ íƒœìŠ¤í¬ ê·¸ëž˜í”„ëŠ” 다ìŒê³¼ 같다. $ uftrace record --force /usr/bin/gcc hello.c $ uftrace graph --task ========== TASK GRAPH ========== # TOTAL TIME SELF TIME TID TASK NAME 159.854 ms 4.440 ms [ 82723] : gcc : | 90.951 ms 90.951 ms [ 82734] : +----cc1 : | 17.150 ms 17.150 ms [ 82735] : +----as : | 45.183 ms 6.076 ms [ 82736] : +----collect2 : | 38.880 ms 38.880 ms [ 82737] : +----ld ìœ„ì˜ ì¶œë ¥ ê²°ê³¼ì—서 ë³´ì´ëŠ” 것과 ê°™ì´ `gcc` 는 `cc1`, `as` 그리고 `collect2` 프로세스를 ìƒì„±í•˜ì˜€ê³ , `collect2` 는 ë‚´ë¶€ì ìœ¼ë¡œ `ld` 프로세스를 ìƒì„±í•œ ê²ƒì„ í™•ì¸ í•  수 있다. `TOTAL TIME` ì€ íƒœìŠ¤í¬ì˜ ìƒì„±ì—서부터 ì†Œë©¸ê¹Œì§€ì˜ ì´ ì‹œê°„ì„ ë‚˜íƒ€ë‚´ê³ , `SELF TIME` ì€ ì—­ì‹œ ê°™ì€ ë°©ì‹ì˜ ì´ ì‹œê°„ì„ ë‚˜íƒ€ë‚´ì§€ë§Œ ë‚´ë¶€ì ìœ¼ë¡œ 유휴(idle) ì‹œê°„ì€ ì œì™¸ë¥¼ 한 ì‹œê°„ì„ ë‚˜íƒ€ë‚¸ë‹¤. `TID` 는 해당 태스í¬ì˜ 스레드 ë²ˆí˜¸ì¸ tid 를 보여준다. ì•„ëž˜ì˜ ê²°ê³¼ëŠ” uftrace ê°€ record 하는 ì‹¤í–‰ì— ìžì²´ì— 대한 ë‚´ë¶€ì ì¸ íƒœìŠ¤í¬ ê·¸ëž˜í”„ë¥¼ 보여준다. ê²°ê³¼ì—서는 uftrace ê°€ `t-abc` 프로세스를 ìƒì„±í–ˆê³ , ë˜í•œ `WriterThread` ë¼ëŠ” ì´ë¦„ì„ ê°–ëŠ” ë‹¤ìˆ˜ì˜ ìŠ¤ë ˆë“œë“¤ì„ ìƒì„±í•œ ê²ƒì„ í™•ì¸ ê°€ëŠ¥í•˜ë‹¤. $ uftrace record -P. ./uftrace record -d uftrace.data.abc t-abc $ uftrace graph --task ========== TASK GRAPH ========== # TOTAL TIME SELF TIME TID TASK NAME 404.929 ms 321.692 ms [ 4230] : uftrace : | 278.662 us 278.662 us [ 4241] : +----t-abc : | 33.754 ms 4.061 ms [ 4242] : +-WriterThread 27.415 ms 120.992 us [ 4244] : +-WriterThread 27.212 ms 8.119 ms [ 4245] : +-WriterThread 26.754 ms 6.616 ms [ 4248] : +-WriterThread 26.859 ms 8.154 ms [ 4247] : +-WriterThread 26.509 ms 1.645 ms [ 4243] : +-WriterThread 25.320 ms 57.350 us [ 4246] : +-WriterThread 24.757 ms 4.391 ms [ 4249] : +-WriterThread 26.040 ms 3.707 ms [ 4250] : +-WriterThread 24.004 ms 3.999 ms [ 4251] : +-WriterThread ìœ„ì˜ ê²°ê³¼ì™€ ê°™ì´ ìŠ¤ë ˆë“œì˜ ë“¤ì—¬ì“°ê¸° 깊ì´ëŠ” 프로세스와는 다르게 표현ëœë‹¤. FIELDS ====== uftrace 사용ìžëŠ” graph 결과를 ëª‡ëª‡ì˜ í•„ë“œë¡œ ì›í•˜ëŠ” ë°©ì‹ëŒ€ë¡œ 구성할 수 있다. 여기서 필드란 콜론 ë¬¸ìž (:) ì™¼ìª½ì— ë‚˜íƒ€ë‚˜ëŠ” 정보를 뜻한다. 기본ì ìœ¼ë¡œ 전체실행시간 total ë§Œì„ í•„ë“œë¡œ 사용하지만, 다른 í•„ë“œë“¤ë„ ë‹¤ìŒê³¼ ê°™ì´ ìž„ì˜ì˜ 순서로 사용 가능하다. $ uftrace record tests/t-abc $ uftrace graph -f total,self,addr # Function Call Graph for 't-sort' (session: b007f4b7cf792878) ========== FUNCTION CALL GRAPH ========== # TOTAL TIME SELF TIME ADDRESS FUNCTION 10.145 ms 561f652cd610 : (1) t-sort 10.145 ms 39.890 us 561f652cd610 : (1) main 16.773 us 0.734 us 561f652cd7ce : +-(2) foo 16.039 us 16.039 us 561f652cd7a0 : | (6) loop : | 10.088 ms 14.740 us 561f652cd802 : +-(1) bar 10.073 ms 10.073 ms 561f652cd608 : (1) usleep ê° í•„ë“œëŠ” 다ìŒê³¼ ê°™ì€ ì˜ë¯¸ê°€ 있다. * total: í•¨ìˆ˜ì˜ ì „ì²´ 실행 시간 * self : ìžì‹ 함수를 제외한 í•¨ìˆ˜ì˜ ì‹¤í–‰ 시간 * addr : í•¨ìˆ˜ì˜ ì£¼ì†Œ 기본ì ìœ¼ë¡œëŠ” 'total' 필드가 사용ëœë‹¤. 주어진 í•„ë“œì˜ ì´ë¦„ì´ "+"로 시작하면 기본 í•„ë“œì— ì¶”ê°€í•˜ëŠ”ê²ƒì„ ì˜ë¯¸í•œë‹¤. ë”°ë¼ì„œ "-f +addr" 는 "-f total,addr" 와 같다. ë˜í•œ 특별한 í•„ë“œì¸ 'none' ì„ ì‚¬ìš©í•˜ë©´ 아무런 í•„ë“œë„ ì¶œë ¥í•˜ì§€ 않게 í•  수 있다. $ uftrace graph -f none # Function Call Graph for 't-sort' (session: b007f4b7cf792878) ========== FUNCTION CALL GRAPH ========== (1) t-sort (1) main +-(2) foo | (6) loop | +-(1) bar (1) usleep ì´ëŸ° ë°©ì‹ì˜ ì¶œë ¥ì€ diff ë„구를 사용하여 ë‘ ê°œì˜ ì„œë¡œ 다른 그래프 ì¶œë ¥ì„ ë¹„êµí•  때 유용하게 ì‚¬ìš©ë  ìˆ˜ 있다. ê°™ì€ ë°©ì‹ìœ¼ë¡œ íƒœìŠ¤í¬ ê·¸ëž˜í”„ì— ëŒ€í•´ì„œë„ ì¶œë ¥ 필드를 ì›í•˜ëŠ” ë°©ì‹ëŒ€ë¡œ 구성할 수 있다. 기본ì ì¸ 필드 ì„¤ì •ì€ `total,self,tid` ì´ì§€ë§Œ 필드 ì˜µì…˜ì€ ì•„ëž˜ì™€ ê°™ì´ ì‚¬ìš©ë  ìˆ˜ë„ ìžˆë‹¤. $ uftrace graph --task -f tid,self ========== TASK GRAPH ========== # TID SELF TIME TASK NAME [ 82723] 4.440 ms : gcc : | [ 82734] 90.951 ms : +----cc1 : | [ 82735] 17.150 ms : +----as : | [ 82736] 6.076 ms : +----collect2 : | [ 82737] 38.880 ms : +----ld ê° í•„ë“œëŠ” 다ìŒê³¼ ê°™ì€ ì˜ë¯¸ê°€ 있다. * total: 태스í¬ì˜ ìƒì„±ë¶€í„° ì†Œë©¸ê¹Œì§€ì˜ ì´ ì‹œê°„ * self : 태스í¬ì˜ ì´ ì‹œê°„ì—서 유휴(idle) ì‹œê°„ì„ ì œì™¸í•œ 시간 * tid : task id (gettid(2)로 ì–»ì„ ìˆ˜ 있다.) ë˜í•œ 특별한 í•„ë“œì¸ 'none' ì„ ì‚¬ìš©í•˜ë©´ ì™¼ìª½ì— ì•„ë¬´ëŸ° í•„ë“œë„ ì¶œë ¥í•˜ì§€ 않게 í•  수 있다. $ uftrace graph --task -f none ========== TASK GRAPH ========== gcc | +----cc1 | +----as | +----collect2 | +----ld 함께 보기 ========= `uftrace`(1), `uftrace-record`(1), `uftrace-replay`(1), `uftrace-tui`(1) ë²ˆì—­ìž ====== ê¹€ê´€ì˜ uftrace-0.9.4/doc/ko/uftrace-info.md000066400000000000000000000062061362052523300172440ustar00rootroot00000000000000% UFTRACE-INFO(1) Uftrace User Manuals % Namhyung Kim % Sep, 2018 ì´ë¦„ ==== uftrace-info - 기ë¡ëœ ë°ì´í„°ì— 대한 정보를 출력한다. 사용법 ======== uftrace info [*options*] [*COMMAND*] 설명 =========== ì´ ëª…ë ¹ì–´ëŠ” 주어진 ë°ì´í„° 파ì¼ì˜ í—¤ë”ì— ê¸°ë¡ëœ 메타 ë°ì´í„°ë¥¼ 출력한다. 옵션 ======= \--symbols : 기ë¡ëœ ì •ë³´ ëŒ€ì‹ ì— ì‹¬ë³¼(symbol) í…Œì´ë¸”ì„ ì¶œë ¥í•œë‹¤. 심볼 정보는 ì¼ë°˜ 심볼들과 ë™ì  심볼들로 분류ë˜ëŠ”ë° ì¼ë°˜ ì‹¬ë³¼ë“¤ì€ ì‹¤í–‰ ì´ë¯¸ì§€ì— 있는 ì •ë³´ì´ê³ , ë™ì  ì‹¬ë³¼ì€ ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì„ ìœ„í•´ 사용ëœë‹¤. COMMAND ê°€ 주어지면 실행 ì´ë¯¸ì§€ë¡œë¶€í„° 심볼 정보를 추출해서 출력한다. \--task : ë°ì´í„° ì •ë³´ 대신 태스í¬ì˜ 관계를 트리 형태로 출력한다. 예시 ======= ì´ ëª…ë ¹ì–´ëŠ” 아래와 ê°™ì€ ì •ë³´ë¥¼ 출력한다. $ uftrace record abc $ uftrace info # system information # ================== # program version : v0.9 ( dwarf python tui perf sched ) # recorded on : Wed Sep 19 17:30:39 2018 # cmdline : uftrace record abc # cpu info : Intel(R) Core(TM) i7-3930K CPU @ 3.20GHz # number of cpus : 12 / 12 (online / possible) # memory info : 19.8 / 23.5 GB (free / total) # system load : 0.02 / 0.07 / 0.11 (1 / 5 / 15 min) # kernel version : Linux 4.5.4-1-ARCH # hostname : sejong # distro : "Arch Linux" # # process information # =================== # number of tasks : 1 # task list : 8284(abc) # exe image : /home/namhyung/tmp/abc # build id : a3c50d25f7dd98dab68e94ef0f215edb06e98434 # pattern : regex # exit status : exited with code: 0 # elapsed time : 0.003219479 sec # cpu time : 0.003 / 0.000 sec (sys / user) # context switch : 1 / 1 (voluntary / involuntary) # max rss : 3104 KB # page fault : 0 / 169 (major / minor) # disk iops : 0 / 24 (read / write) '--symbols' ì˜µì…˜ì„ ì‚¬ìš©í•´ì„œ 심볼 í…Œì´ë¸”ì„ ë³¼ 수 있다. $ uftrace info --symbols Normal symbols ============== [ 0] _start (0x400590) size: 42 [ 1] __gmon_start__ (0x4005c0) size: 59 [ 2] a (0x4006c6) size: 19 [ 3] b (0x4006d9) size: 19 [ 4] c (0x4006ec) size: 49 [ 5] main (0x40071d) size: 19 [ 6] __libc_csu_init (0x400730) size: 101 [ 7] __libc_csu_fini (0x4007a0) size: 2 [ 8] atexit (0x4007b0) size: 41 Dynamic symbols =============== [ 0] getpid (0x400530) size: 16 [ 1] _mcleanup (0x400540) size: 16 [ 2] __libc_start_main (0x400550) size: 16 [ 3] __monstartup (0x400560) size: 16 [ 4] mcount (0x400570) size: 16 [ 5] __cxa_atexit (0x400580) size: 16 `--task` ì˜µì…˜ì€ íƒœìŠ¤í¬ë“¤ì˜ 계층 관계를 보여준다. $ uftrace info --task [166399] parent [166401] child 함께 보기 ======== `uftrace`(1), `uftrace-record`(1), `uftrace-tui`(1) ë²ˆì—­ìž ======== ì¡°ì •ìš° uftrace-0.9.4/doc/ko/uftrace-live.md000066400000000000000000001122241362052523300172460ustar00rootroot00000000000000% UFTRACE-LIVE(1) Uftrace User Manuals % Namhyung Kim % Sep, 2018 ì´ë¦„ ==== uftrace-live - ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì˜ í•¨ìˆ˜ 실행 íë¦„ì„ ê¸°ë¡í•˜ê³  출력한다. 사용법 ====== uftrace [live] [*options*] COMMAND [*command-options*] 설명 ==== ì´ ëª…ë ¹ì–´ëŠ” COMMAND 로 ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì„ ì‹¤í–‰í•˜ê³  í•¨ìˆ˜ë“¤ì˜ ì‹¤í–‰ íë¦„ì„ ê¸°ë¡í•˜ê³  실행시간 ë“±ì˜ ì •ë³´ë¥¼ 출력한다. ì´ëŠ” 기본ì ìœ¼ë¡œ `uftrace record` 와 `uftrace replay` 를 차례로 실행하는 것과 같지만, ë°ì´í„° 파ì¼ì„ 저장하지는 않는다. ì´ ëª…ë ¹ì–´ëŠ” `record` 와 `replay` ëª…ë ¹ì–´ì— ì‚¬ìš©ë˜ëŠ” ëŒ€ë¶€ë¶„ì˜ ì¸ìžë“¤ì„ 받는다. 공통 옵션 ========= -F *FUNC*, \--filter=*FUNC* : ì„ íƒëœ 함수들(그리고 ê·¸ ë‚´ë¶€ì˜ í•¨ìˆ˜ë“¤)ë§Œ 출력하ë„ë¡ í•„í„°ë¥¼ 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. -N *FUNC*, \--notrace=*FUNC* : ì„ íƒëœ 함수들 (ë˜ëŠ” ê·¸ 아래 함수들)ì„ ì¶œë ¥ì—서 제외하ë„ë¡ ì„¤ì •í•˜ëŠ” 옵션ì´ë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. -C *FUNC*, \--caller-filter=*FUNC* : ì„ íƒëœ í•¨ìˆ˜ì˜ í˜¸ì¶œìžë¥¼ 출력하는 필터를 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. -T *TRG*, \--trigger=*TRG* : ì„ íƒëœ í•¨ìˆ˜ì˜ íŠ¸ë¦¬ê±°ë¥¼ 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. íŠ¸ë¦¬ê±°ì— ëŒ€í•œ ì„¤ëª…ì€ *TRIGGERS* 를 참고한다. -D *DEPTH*, \--depth=*DEPTH* : 함수가 ì¤‘ì²©ë  ìˆ˜ 있는 최대 깊ì´ë¥¼ 설정한다. (ì´ë¥¼ 넘어서는 ìƒì„¸í•œ 함수 ì‹¤í–‰ê³¼ì •ì€ ë¬´ì‹œí•œë‹¤.) í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. -t *TIME*, \--time-filter=*TIME* : 설정한 시간 ì´í•˜ë¡œ ìˆ˜í–‰ëœ í•¨ìˆ˜ëŠ” 표시하지 않게 한다. 만약 ì–´ë–¤ 함수가 명시ì ìœ¼ë¡œ 'trace' 트리거가 ì ìš©ëœ 경우, ê·¸ 함수는 실행 시간과 ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. \--no-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì€ í‘œì‹œí•˜ì§€ 않게 한다. \--no-event : ì´ë²¤íŠ¸ëŠ” 표시하지 않게 한다. \--match=*TYPE* : 타입(TYPE)으로 ì¼ì¹˜í•˜ëŠ” íŒ¨í„´ì„ ë³´ì—¬ì¤€ë‹¤. 가능한 형태는 `regex`와 `glob`ì´ë‹¤. 기본 ì„¤ì •ì€ `regex`ì´ë‹¤. \--disable : uftrace 를 시작할때 ë°ì´í„°ë¥¼ 기ë¡í•˜ì§€ 않고 시작한다. ì´ê²ƒì€ `trace_on` 트리거와 함께 사용ë˜ì—ˆì„ 때만 ì˜ë¯¸ë¥¼ 가진다. LIVE 옵션 ============ \--list-event : ì‹¤í–‰ì¤‘ì— ì‚¬ìš©ê°€ëŠ¥í•œ ì´ë²¤íŠ¸ë“¤ì„ ì¶œë ¥í•œë‹¤. \--report : replay 출력 ì „ live-report 를 함께 출력한다. \--record : 기ë¡ëœ ë°ì´í„°ë¥¼ 향후 ë¶„ì„ì„ ìœ„í•´ 저장한다. RECORD 옵션 ============== -A *SPEC*, \--argument=*SPEC* : í•¨ìˆ˜ì˜ ì¸ìžë“¤ì„ 기ë¡í•œë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. ì¸ìžì— 대한 ì„¤ëª…ì€ *ARGUMENTS* 를 참고한다. -R *SPEC*, \--retval=*SPEC* : í•¨ìˆ˜ë“¤ì˜ ë°˜í™˜ê°’ì„ ê¸°ë¡í•œë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. ë°˜í™˜ê°’ì— ëŒ€í•œ ì„¤ëª…ì€ *ARGUMENTS* 를 참고한다. -P *FUNC*, \--patch=*FUNC* : 주어진 FUNC 함수를 ë™ì ìœ¼ë¡œ 패치하여 ì¶”ì í•˜ê³  기ë¡í•œë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. 관련 ì„¤ëª…ì€ *DYNAMIC TRACING* ì„ ì°¸ê³ í•œë‹¤. -U *FUNC*, \--unpatch=*FUNC* : 주어진 FUNC í•¨ìˆ˜ì— ëŒ€í•´ ë™ì  패치를 ì ìš©í•˜ì§€ 않는다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. 관련 ì„¤ëª…ì€ *DYNAMIC TRACING* ì„ ì°¸ê³ í•œë‹¤. -Z *SIZE*, \--size-filter=*SIZE* : SIZE ë°”ì´íŠ¸ë³´ë‹¤ í° í•¨ìˆ˜ë“¤ì„ ë™ì ìœ¼ë¡œ 패치한다. ë™ì ì¶”ì ì— 대해서는 *DYNAMIC TRACING* ì„ ì°¸ê³ í•œë‹¤. -E *EVENT*, \--event=*EVENT* : ì´ë²¤íЏ ì¶”ì ì„ 활성화한다. 시스템 ë‚´ì—서 사용 가능한 ì´ë²¤íŠ¸ì—¬ì•¼ 한다. -S *SCRIPT_PATH*, \--script=*SCRIPT_PATH* : ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì´ ì‹¤í–‰í•˜ëŠ” ë™ì•ˆ í•¨ìˆ˜ì˜ ì§„ìž…ê³¼ 반환 시ì ì— 주어진 스í¬ë¦½íŠ¸ë¥¼ 활용해서 추가ì ì¸ ìž‘ì—…ì„ í•œë‹¤. 스í¬ë¦½íЏ 언어 종류는 파ì¼ì˜ 확장ìžë¥¼ 통해 ì •í•´ì§€ëŠ”ë° íŒŒì´ì¬ì˜ 경우 ".py" ì´ë‹¤. 스í¬ë¦½íЏ 실행 ì„¤ëª…ì€ *SCRIPT EXECUTION* ì„ ì°¸ê³ í•œë‹¤. -W, \--watch=*POINT* : 특정한 ê°’ì´ ë³€ê²½ë˜ì—ˆì„ 경우 ì´ë¥¼ 보여주기 위해 watch point 를 추가한다. ìžì„¸í•œ ì‚¬í•­ì€ *WATCH POINT* 를 참고한다. -a, \--auto-args : 알려진 í•¨ìˆ˜ì˜ ì¸ìžì™€ ë°˜í™˜ê°’ë“¤ì„ ìžë™ìœ¼ë¡œ 기ë¡í•œë‹¤. ë³´í†µì˜ ê²½ìš° C 언어 ë˜ëŠ” ì‹œìŠ¤í…œì˜ í‘œì¤€ ë¼ì´ë¸ŒëŸ¬ë¦¬ í•¨ìˆ˜ë“¤ì— í•´ë‹¹í•˜ì§€ë§Œ, 디버그 정보를 ì´ìš©í•  수 있다면 ì‚¬ìš©ìž í•¨ìˆ˜ë“¤ì—ë„ ì ìš©í•  수 있다. -l, \--nest-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ë“¤ ê°„ì˜ í•¨ìˆ˜ í˜¸ì¶œë„ í•¨ê»˜ 기ë¡í•œë‹¤. 기본ì ìœ¼ë¡œ uftrace 는 실행파ì¼ì—서 ì§ì ‘ 호출하는 ë¼ì´ë¸ŒëŸ¬ë¦¬ 함수만 기ë¡í•œë‹¤. -k, \--kernel : ì‚¬ìš©ìž í”„ë¡œê·¸ëž¨ì˜ í•¨ìˆ˜ì™€ 함께 ì»¤ë„ í•¨ìˆ˜ë¥¼ ì¶”ì í•œë‹¤. 기본ì ìœ¼ë¡œëŠ” 커ë„ë¡œì˜ ì§„ìž… ë° ë³µê·€ 함수만 기ë¡í•œë‹¤. ì´ë¥¼ 변경하려면 --kernel-depth ì˜µì…˜ì„ ì‚¬ìš©í•  수 있다. -K *DEPTH*, \--kernel-depth=*DEPTH* : ì»¤ë„ ìµœëŒ€ 함수 깊ì´ë¥¼ 설정한다. --kernel ì˜µì…˜ì´ ìžë™ìœ¼ë¡œ ì ìš©ëœë‹¤. \--signal=*TRG* : 함수가 아닌 ì„ íƒí•œ 시그ë„ì— íŠ¸ë¦¬ê±°ë¥¼ 설정한다. 하지만 제한 사항들로 ì¸í•˜ì—¬ 소수 트리거 ê¸°ëŠ¥ë§Œì„ ì§€ì›í•˜ê³  있다. 사용 가능한 작업: : trace_on, trace_off, finish. ì´ ì˜µì…˜ì€ ë‘번 ì´ìƒ 사용할 수 있다. 트리거 ì„¤ëª…ì€ *TRIGGERS* 를 참고한다. \--nop : ì–´ë–¤ í•¨ìˆ˜ë„ record 하거나 replay하지 않는다. ì´ëŠ” 아무 ì¼ë„ 하지 않는 명령어로, 성능 비êµì—서만 ì˜ë¯¸ë¥¼ 가진다. \--force : ì•½ê°„ì˜ ë¬¸ì œê°€ ìžˆì–´ë„ uftrace ê°€ 실행ëœë‹¤. `uftrace record` 는 실행파ì¼ì—서 컴파ì¼ëŸ¬ì— ì˜í•´ ìƒì„±ë˜ëŠ” mcount 를 ì°¾ì„ ìˆ˜ ì—†ì„ ë•Œ uftrace ê°€ í”„ë¡œê·¸ëž¨ì„ ì¶”ì í•  수 없으므로 오류 메시지와 함께 종료ëœë‹¤. 단, 사용ìžëŠ” ë™ì ìœ¼ë¡œ ì—°ê²°ëœ ë¼ì´ë¸ŒëŸ¬ë¦¬ ë‚´ì˜ ê¸°ëŠ¥ì—ë§Œ ê´€ì‹¬ì´ ìžˆì„ ìˆ˜ 있으며, ì´ ê²½ìš° `--force` ì˜µì…˜ì„ ì‚¬ìš©í•˜ì—¬ uftrace 를 실행시킬 수 있다. ë˜í•œ `-A`/`--argument` ë° `-R`/`--retval` ì˜µì…˜ì€ -pg 로 컴파ì¼ëœ ë°”ì´ë„ˆë¦¬ì— 대해서만 ìž‘ë™í•˜ë¯€ë¡œ, uftrace 는 ê·¸ 옵션 ì—†ì´ ë¹Œë“œëœ ë°”ì´ë„ˆë¦¬ë¥¼ 실행하려고 í•  때ì—ë„ ì¢…ë£Œëœë‹¤. ì´ ì˜µì…˜ì€ ê²½ê³ ë¥¼ 무시하고 ì¸ìˆ˜ ë° ë°˜í™˜ ê°’ ì—†ì´ uftrace 를 실행시키ë„ë¡ í•œë‹¤. \--time : time(1) 스타ì¼ë¡œ ì‹¤í–‰ì‹œê°„ì„ ì¶œë ¥í•œë‹¤. RECORD 설정 옵션 ===================== -L *PATH*, \--library-path=*PATH* : 필요한 ë‚´ë¶€ ë¼ì´ë¸ŒëŸ¬ë¦¬ë¥¼ *PATH* ì—서 먼저 찾는다. ì´ ì˜µì…˜ì€ ëŒ€ë¶€ë¶„ 테스트 목ì ìœ¼ë¡œ 사용ëœë‹¤. -b *SIZE*, \--buffer=*SIZE* : 저장할 ë°ì´í„°ì˜ ë‚´ë¶€ ë²„í¼ í¬ê¸°ë¥¼ 설정한다. 기본 사ì´ì¦ˆëŠ” 128k ì´ë‹¤. \--kernel-buffer=*SIZE* : 저장할 ì»¤ë„ ë°ì´í„°ì˜ ë‚´ë¶€ ë²„í¼ í¬ê¸°ë¥¼ 설정한다. ì»¤ë„ ë‚´ë¶€ì˜ ê¸°ë³¸ ì„¤ì •ì€ 1408k ì´ë‹¤. \--no-pltbind : ë™ì  심볼 주소를 ë°”ì¸ë”©í•˜ì§€ 않는다. ì´ ì˜µì…˜ì€ `LD_BIND_NOT` 환경 변수를 사용하여 ë™ì‹œì ìœ¼ë¡œ ë°œìƒí•˜ëŠ” (첫 번째) 접근으로 ì¸í•´ 누ë½ë  수 있는 ë¼ì´ë¸ŒëŸ¬ë¦¬ 함수를 ì¶”ì í•œë‹¤. `--no-libcall` 옵션과 함께 ì´ ì˜µì…˜ì„ ì‚¬ìš©í•˜ëŠ” ê²ƒì€ ì˜ë¯¸ê°€ 없다. \--max-stack=*DEPTH* : ë‚´ë¶€ì ìœ¼ë¡œ 기ë¡í•˜ëŠ” 함수 호출 스íƒì˜ 최대 깊ì´ë¥¼ 설정한다. ê¸°ë³¸ê°’ì€ 1024 ì´ë‹¤. \--num-thread=*NUM* : ë°ì´í„°ë¥¼ 저장하기 위해 *NUM* ê°œì˜ ì“°ë ˆë“œë¥¼ 사용한다. 기본ì ìœ¼ë¡œëŠ” 사용 가능한 CPU ì˜ 1/4 으로 설정한다. (하지만 커ë„ì„ í¬í•¨í•´ 전체를 기ë¡í•˜ëŠ” 경우, 최대로 사용 가능한 CPU ì˜ ìˆ˜ë¡œ 설정한다.) \--libmcount-single : 빠른 ë°ì´í„° 기ë¡ì„ 위해서 libmcount ì˜ ë‹¨ì¼ ì“°ë ˆë“œ ë²„ì „ì„ ì‚¬ìš©í•œë‹¤. ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì´ pthread ë¼ì´ë¸ŒëŸ¬ë¦¬ë¥¼ 사용하는 경우ì—는 무시ëœë‹¤. \--rt-prio=*PRIO* : ë°ì´í„° 기ë¡ì„ 하는 스레드를 *PRIO* 를 우선순위로 갖는 실시간(FIFO)로 í–¥ìƒì‹œí‚¨ë‹¤. ì´ ì˜µì…˜ì€ íŠ¹ížˆ 대규모 ë°ì´í„°ë¥¼ 기ë¡í•˜ëŠ” ì „ì²´ ì»¤ë„ ì¶”ì ê³¼ ê°™ì€ í™˜ê²½ì—서 유용하다. \--keep-pid : í”„ë¡œê·¸ëž¨ì„ ì¶”ì í•  때 ë™ì¼í•œ pid ê°’ì„ ìœ ì§€í•˜ê²Œ 해준다. ì¼ë¶€ ë°ëª¬ í”„ë¡œì„¸ìŠ¤ì˜ ê²½ìš° 분기 í•  ë–„ ë™ì¼í•œ pid 를 ê°–ëŠ”ê²ƒì´ ì¤‘ìš”í•˜ë‹¤. ì¼ë°˜ì ìœ¼ë¡œ uftrace 를 실행하면 fork() 를 ë‚´ë¶€ì ìœ¼ë¡œ 다시 호출하므로 pid ê°€ 변경ëœë‹¤. ì´ ì˜µì…˜ì„ ì‚¬ìš©í•  경우 í„°ë¯¸ë„ ì„¤ì •ì´ ì†ìƒë˜ëŠ” 경우가 있기 ë–„ë¬¸ì— `--no-pager` 옵션과 함께 사용하는 ê²ƒì´ ì¢‹ë‹¤. \--no-randomize-addr : ASLR(Address Space Layout Randomization)ì„ ë¹„í™œì„±í™” 한다. ì´ëŠ” í”„ë¡œì„¸ìŠ¤ì˜ ë¼ì´ë¸ŒëŸ¬ë¦¬ 로딩 주소가 매번 변경ë˜ì§€ 않ë„ë¡ ë§‰ì•„ì¤€ë‹¤. REPLAY 옵션 ============== -f *FIELD*, \--output-fields=*FIELD* : 결과로 보여지는 필드를 사용ìžê°€ 지정한다. 가능한 값들로는 duration, tid, time, delta, elapsed, addr ê°€ 있다. 여러 필드를 갖는 경우 콤마로 구분ëœë‹¤. 모든 필드를 ê°ì¶”기 위한 (단ì¼í•˜ê²Œ 사용ë˜ëŠ”) 'none' 특수 필드가 있으며 기본ì ìœ¼ë¡œ 'duration,tid' ê°€ 사용ëœë‹¤. ìƒì„¸í•œ ì„¤ëª…ì€ *FIELDS* 를 참고한다. \--flat : C 와 ê°™ì´ í˜¸ì¶œ 깊ì´ê°€ ë³´ì´ëŠ” ë°©ì‹ì´ 아닌 í‰í‰í•œ(flat) 형ì‹ìœ¼ë¡œ 출력한다. ì´ ì˜µì…˜ì€ ì£¼ë¡œ 디버깅ì´ë‚˜ 테스트 ìš©ë„로 사용ëœë‹¤. \--column-view : ì—´(column) 별로 분리하여 ê°ê°ì˜ 태스í¬ë¥¼ 출력한다. 서로 다른 태스í¬ì—서 실행하는 í•¨ìˆ˜ì˜ êµ¬ë¶„ì„ ì‰½ê²Œí•œë‹¤. \--column-offset=*DEPTH* : `--column-view` ì˜µì…˜ì´ ì‚¬ìš©ë˜ì—ˆì„ 때, ì´ ì˜µì…˜ì€ ê° íƒœìŠ¤í¬ ì‚¬ì´ì˜ 간격(offset) í¬ê¸°ë¥¼ 명시한다. 기본 ê°„ê²©ì€ 8 ì´ë‹¤. \--task-newline : 태스í¬ê°€ 변경ë˜ë©´ 빈 공백 í•œì¤„ì„ ì¶”ê°€í•œë‹¤. ì´ë¥¼ 통해 여러 태스í¬ì—서 ë™ìž‘하는 í•¨ìˆ˜ë“¤ì„ ì‰½ê²Œ 구별 í•  수 있다. \--no-comment : 함수가 반환ë˜ëŠ” ê³³ì— ì£¼ì„ì„ ì¶œë ¥í•˜ì§€ 않는다. \--libname : 함수 ì´ë¦„ê³¼ 함께 ë¼ì´ë¸ŒëŸ¬ë¦¬ ì´ë¦„ì„ ì¶œë ¥í•œë‹¤. 공통 ë¶„ì„ ì˜µì…˜ ======================= \--kernel-full : ì‚¬ìš©ìž í•¨ìˆ˜ ë°–ì—서 í˜¸ì¶œëœ ëª¨ë“  ì»¤ë„ í•¨ìˆ˜ë¥¼ 출력한다. \--kernel-only : ì‚¬ìš©ìž í•¨ìˆ˜ë¥¼ 제외한 ì»¤ë„ í•¨ìˆ˜ë§Œ 출력한다. \--event-full : ì‚¬ìš©ìž í•¨ìˆ˜ ë°–ì˜ ëª¨ë“  (사용ìž) ì´ë²¤íŠ¸ë¥¼ 출력한다. \--demangle=*TYPE* : í•„í„°, 트리거, 함수ì¸ìžì™€ (ë˜ëŠ”) 반환 ê°’ì„ ë””ë§¹ê¸€(demangle)ëœ C++ 심볼 ì´ë¦„으로 사용한다. "full", "simple", "no" ê°’ì„ ì‚¬ìš©í•  수 있다. 기본 ì„¤ì •ì€ "simple"ì´ë©°, 템플릿 파ë¼ë¯¸í„°ì™€ 함수 ì¸ìžë¥¼ 무시한다. -r *RANGE*, \--time-range=*RANGE* : 시간 범위 RANGE ë‚´ì— ì‹¤í–‰ëœ í•¨ìˆ˜ë“¤ë§Œ 출력한다. RANGE 는 \<시작\>~\<ë\> ("~"로 구분) ì´ê³  \<시작\>ê³¼ \<ë\> 중 하나는 ìƒëžµí•  수 있다. \<시작\>ê³¼ \<ë\>ì€ íƒ€ìž„ìŠ¤íƒ¬í”„ ë˜ëŠ” '100us'와 ê°™ì€ \<시간단위\>ê°€ 있는 경과시간ì´ë‹¤. `uftrace replay`(1) ì—서 `-f time` ë˜ëŠ” `-f elapsed` 를 ì´ìš©í•´ 타임스탬프 ë˜ëŠ” ê²½ê³¼ì‹œê°„ì„ í™•ì¸í•  수 있다. FILTERS ======= uftrace 는 관심 있는 대ìƒì´ 아닌 í•¨ìˆ˜ë“¤ì„ ê°ì¶”는 í•„í„°ë§ì„ í•  수 있다. í•„í„°ë§ì€ 사용ìžë“¤ì´ 관심 있는 함수들ì—ë§Œ 집중할 수 있게 하고, 기ë¡ë˜ëŠ” ë°ì´í„°ì˜ í¬ê¸°ë¥¼ ì¤„ì¼ ìˆ˜ ë•Œë¬¸ì— ì‚¬ìš©í•˜ê¸°ë¥¼ 권장한다. uftrace ê°€ 호출ë˜ë©´, ë‘ ì¢…ë¥˜ì˜ í•¨ìˆ˜ 필터를 갖게 ë˜ëŠ”ë° ì´ë“¤ì€ ëŒ€ìƒ í•¨ìˆ˜ë¥¼ ì„ íƒí•˜ëŠ” ë°©ì‹(opt-in)ì˜ í•„í„°ë¡œ `-F`/`--filter` 와 ì„ íƒí•˜ì§€ 않는 ë°©ì‹(opt-out)ì˜ í•„í„°ì¸ `-N`/`--notrace` ê°€ 있다. ì´ í•„í„°ë“¤ì€ ê¸°ë¡(record)하거나 재ìƒ(replay)í•  때 ëª¨ë‘ ì ìš©ë  수 있다. 첫번째 í•„í„° 종류는 ì„ íƒí•˜ëŠ” ë°©ì‹ì˜ í•„í„°ì´ë‹¤. 기본ì ìœ¼ë¡œ, ì´ê²ƒì€ ì•„ë¬´ê²ƒë„ ì¶”ì í•˜ì§€ 않는다. 하지만 ì–´ë–¤ ëª…ì‹œëœ í•¨ìˆ˜ì— ì§„ìž…í•˜ë©´, 함수 í˜¸ì¶œì— ëŒ€í•œ ì¶”ì ì„ 시작한다. 그러다가 ê·¸ 함수가 반환하게 ë˜ë©´, 함수 호출 ì¶”ì ì„ 중단한다. 예를 들어, `a()`, `b()` 와 `c()`를 차례로 호출하는 간단한 í”„ë¡œê·¸ëž¨ì„ ìƒê°í•´ë³´ìž. $ cat abc.c void c(void) { /* do nothing */ } void b(void) { c(); } void a(void) { b(); } int main(void) { a(); return 0; } $ gcc -pg -o abc abc.c ì¼ë°˜ì ì¸ 경우 uftrace 는 `main()`부터 `c()`ê¹Œì§€ì˜ ëª¨ë“  í•¨ìˆ˜ë“¤ì„ ì¶”ì í•  것ì´ë‹¤. $ uftrace live ./abc # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { [ 1234] | a() { [ 1234] | b() { 3.880 us [ 1234] | c(); 5.475 us [ 1234] | } /* b */ 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ 위 예시ì—서는 명시ì ìœ¼ë¡œ `live` 명령어가 쓰였다. 하지만 `live` 명령어는 기본 명령어ì´ê¸° ë•Œë¬¸ì— ìƒëžµí•  수 있다. ë”°ë¼ì„œ ìœ„ì˜ ëª…ë ¹ì–´ëŠ” 간단하게 `uftrace ./abc` 를 ì‹¤í–‰í•´ë„ ê°™ì€ ê²°ê³¼ë¥¼ ì–»ì„ ìˆ˜ 있다. 하지만 `-F b` í•„í„° ì˜µì…˜ì´ ì‚¬ìš©ë˜ì—ˆì„ 때는, `main()`ê³¼ `a()` 함수는 ë³´ì´ì§€ 않고 ì˜¤ì§ `b()`와 `c()`ë§Œì´ í¬í•¨ëœ ì¶”ì  ê²°ê³¼ë¥¼ ë³´ì¼ê²ƒì´ë‹¤. $ uftrace -F b ./abc # DURATION TID FUNCTION [ 1234] | b() { 3.880 us [ 1234] | c(); 5.475 us [ 1234] | } /* b */ ë‘번째 í•„í„° 종류는 ì„ íƒí•˜ì§€ 않는 ë°©ì‹ì˜ í•„í„°ì´ë‹¤. 기본ì ìœ¼ë¡œ, 모든 ê²ƒì´ ì¶”ì ë˜ì§€ë§Œ, ëª…ì‹œëœ í•¨ìˆ˜ì— ì§„ìž…í•˜ê²Œ ë˜ë©´, ì¶”ì ì„ 멈춘다. ì œì™¸ëœ í•¨ìˆ˜ê°€ 반환하게 ë˜ë©´, ì¶”ì ì„ 재개한다. 위 예시ì—서, `b()` 함수와 ê·¸ì˜ ëª¨ë“  í˜¸ì¶œì€ `-N` 옵션으로 ìƒëžµí•  수 있다. $ uftrace -N b ./abc # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { 6.448 us [ 1234] | a(); 8.631 us [ 1234] | } /* main */ ë§Œì¼ íŠ¹ì • 함수ì—ë§Œ ê´€ì‹¬ì´ ìžˆê³  ê·¸ 함수가 어떻게 호출ë˜ëŠ”ì§€ë§Œ 알고 싶다면, caller filter 를 사용하면 ë  ê²ƒì´ë‹¤. ê·¸ 함수를 마지막(leaf) 노드로 만들고, ê·¸ í•¨ìˆ˜ì˜ ëª¨ë“  부모 í•¨ìˆ˜ë“¤ì„ ê¸°ë¡í•œë‹¤. $ uftrace -C b ./abc # DURATION TID FUNCTION [ 1234] | main() { [ 1234] | a() { 5.475 us [ 1234] | b(); 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ 위 예시ì—서, 호출 ê²½ë¡œì— ì—†ëŠ” í•¨ìˆ˜ë“¤ì„ ì¶œë ¥ë˜ì§€ 않았다. ë˜í•œ, 함수 `b()`ì˜ ìžì‹ í•¨ìˆ˜ì¸ í•¨ìˆ˜ `c()` ë˜í•œ 출력ë˜ì§€ 않았다. ë˜í•œ, `-D` 옵션으로 í•¨ìˆ˜ì˜ ì¤‘ì²© 깊ì´ì„ 제한할 ìˆ˜ë„ ìžˆë‹¤. $ uftrace -D 3 ./abc # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { [ 1234] | a() { 5.475 us [ 1234] | b(); 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ 위 예시ì—서, uftrace 는 함수 호출 깊ì´ë¥¼ 최대 3 으로 제한하여 출력했기 때문ì—, 마지막 í•¨ìˆ˜ì¸ `c()`는 ìƒëžµë˜ì—ˆë‹¤. `-D` ì˜µì…˜ì€ `-F` 옵션과 함께 ì“°ì¼ ìˆ˜ 있다. 때로는, 오랜 시간 실행ë˜ëŠ” í•¨ìˆ˜ë“¤ì„ íŠ¹ë³„í•˜ê²Œ 관찰하는 ê²ƒì´ ìœ ìš©í•˜ë‹¤. ì´ëŠ” ìž‘ì€ (ì‹¤í–‰ì‹œê°„ì„ ê°€ì§€ëŠ”) 함수들 중ì—는 관심 대ìƒì´ 아닌 ê²ƒë“¤ì´ ë§Žê¸° 때문ì´ë‹¤. `-t`/`--time-filter` ì˜µì…˜ì€ ëª…ì‹œëœ ìž„ê³„ì‹œê°„ë³´ë‹¤ 오래 ì‹¤í–‰ëœ í•¨ìˆ˜ë“¤ë§Œ ë³¼ 수 있게 하는 시간 ê¸°ë°˜ì˜ í•„í„°ì´ë‹¤. 위 예시ì—서는, 사용ìžëŠ” 대부분 아래와 ê°™ì´ 5 마ì´í¬ë¡œ(us) ì´ˆ ì´ìƒ 걸려서 실행ë˜ëŠ” 함수를 ë³´ê³  ì‹¶ì–´í•  것ì´ë‹¤. $ uftrace -t 5us ./abc # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { [ 1234] | a() { 5.475 us [ 1234] | b(); 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ í•„í„°ë§ëœ í•¨ìˆ˜ì— íŠ¸ë¦¬ê±°ë¥¼ 설정할 ìˆ˜ë„ ìžˆë‹¤. ë” ë§Žì€ ì •ë³´ëŠ” *TRIGGERS* 항목ì—서 참고할 수 있다. ì»¤ë„ í•¨ìˆ˜ ì¶”ì ì„ 설정하면, `@kernel` ì‹ë³„ìžë¥¼ 통해 ì»¤ë„ í•¨ìˆ˜ì— ëŒ€í•œ 필터를 ì ìš©í•  수 있다. ì´í•˜ 예시ì—서는 모든 ì‚¬ìš©ìž í•¨ìˆ˜ì™€ (ì»¤ë„ ë ˆë²¨ì˜) page fault í•¸ë“¤ëŸ¬ë“¤ì„ ë³´ì—¬ì¤€ë‹¤. $ sudo uftrace -k -F '.*page_fault@kernel' ./abc # DURATION TID FUNCTION [14721] | main() { 7.713 us [14721] | __do_page_fault(); 6.600 us [14721] | __do_page_fault(); 6.544 us [14721] | __do_page_fault(); [14721] | a() { [14721] | b() { [14721] | c() { 0.860 us [14721] | getpid(); 2.346 us [14721] | } /* c */ 2.956 us [14721] | } /* b */ 3.340 us [14721] | } /* a */ 79.086 us [14721] | } /* main */ TRIGGERS ======== uftrace 는 (í•„í„°ê°€ 있든 없든) ì„ íƒëœ 함수 호출과 시그ë„ì— ëŒ€í•œ 트리거 ë™ìž‘ì„ ì§€ì›í•œë‹¤. 현재 ì§€ì›ë˜ëŠ” 트리거와 ì‚¬ì–‘ì— ëŒ€í•œ BNF 는 다ìŒê³¼ 같다. := "@" := | "," := "depth=" | "backtrace" | "trace" | "trace_on" | "trace_off" | "recover" | "color=" | "time=" | "read=" | "finish" | "filter" | "notrace" := [ ] := "ns" | "nsec" | "us" | "usec" | "ms" | "msec" | "s" | "sec" | "m" | "min" := "proc/statm" | "page-fault" | "pmu-cycle" | "pmu-cache" | "pmu-branch" `depth` 트리거는 함수를 실행하는 ë™ì•ˆ í•„í„°ì˜ ê¹Šì´ë¥¼ 변경한다. 다양한 í•¨ìˆ˜ì— ëŒ€í•´ 서로 다른 í•„í„° 깊ì´ë¥¼ 설정할 수 있다. 그리고 `backtrace` 트리거는 replay 시 ìŠ¤íƒ ë°±íŠ¸ë ˆì´ìŠ¤ë¥¼ 출력한다. `color` 트리거는 replay 명령어ì—서 색ìƒì„ 변경한다. ì§€ì›ë˜ëŠ” 색ìƒì€ `red`, `green`, `blue`, `yellow`, `magenta`, `cyan`, `bold`, `gray` ê°€ 있다. ë‹¤ìŒ ì˜ˆì œëŠ” 트리거 ìž‘ë™ ë°©ì‹ì„ 보여준다. ì „ì—­ í•„í„° 깊ì´ê°€ 5 로 설정ë˜ì–´ 있지만 `b()` í•¨ìˆ˜ì— `depth` 트리거를 설정하여 `b()` 아래 함수는 ë³´ì´ì§€ 않게ëœë‹¤. $ uftrace -D 5 -T 'b@depth=1' ./abc # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { [ 1234] | a() { 5.475 us [ 1234] | b(); 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ `backtrace` 트리거는 replay ì—서만 사용할 수 있다. `trace_on`ê³¼ `trace_off` 트리거는 uftrace ê°€ ì§€ì •ëœ í•¨ìˆ˜ë¥¼ 기ë¡í• ì§€ 여부를 관리한다. ë˜í•œ, `_` ë¬¸ìž ì—†ì´ `traceon` ê³¼ `traceoff` ë¡œë„ ì‚¬ìš©í•  수 있다. `recover` 트리거는 프로세스가 호출 스íƒ(call stack)ì— ì§ì ‘ 접근하는 ì¼ë¶€ ê²½ìš°ì— ì‚¬ìš©ëœë‹¤. 예를들어, v8 ìžë°”스í¬ë¦½íЏ ì—”ì§„ì„ ì¶”ì í•˜ëŠ” ë™ì•ˆ 가비지 컬렉션 단계ì—서 세그멘테ì´ì…˜ í´íЏ 문제가 ë°œìƒëœë‹¤ë©´ ì´ëŠ” v8 ì´ (변경ëœ) 반환 주소를 통해 컴파ì¼ëœ 코드 ê°ì²´ì— 접근하려 하기 때문ì´ë‹¤. `recover` 트리거는 함수 ì§„ìž…ì ì— ì›ëž˜ 반환 주소를 ë³µì›í•˜ê³  함수 반환ì ì—서 다시 uftrace ì—서 조작한 반환 주소로 재설정한다. (특히 v8 ìžë°”스í¬ë¦½íЏ 엔진 사례ì—서 `ExitFrame::Iterate` 함수와 ê°™ì´ ë¬¸ì œë¥¼ ë°œìƒì‹œí‚¤ëŠ” ìƒí™©ì—서 `recover` 트리거를 사용하면 문제를 í•´ê²°í•  수 있다.) `time` 트리거는 함수를 실행하는 ë™ì•ˆ 시간 í•„í„°(time-filter) ì„¤ì •ì„ ë³€ê²½í•œë‹¤. 다른 í•¨ìˆ˜ë“¤ì— ëŒ€í•´ì„œ 서로 다른 시간 필터를 ì ìš©í•  ë–„ 사용할 수 있다. `read` 트리거는 실행 ì‹œì— ì¼ë¶€ 정보를 ì½ì„ 수 있다. 결과는 주어진 í•¨ìˆ˜ì˜ ì‹œìž‘ê³¼ ëì— (내장) ì´ë²¤íŠ¸ì˜ í˜•íƒœë¡œ 기ë¡ëœë‹¤. 현재 다ìŒê³¼ ê°™ì€ ì´ë²¤íŠ¸ê°€ ì§€ì›ë˜ê³  있다. * "proc/statm": /proc ìœ¼ë¡œë¶€í„°ì˜ ë©”ëª¨ë¦¬ 사용량 ì •ë³´ * "page-fault": getrusage(2)를 사용한 페ì´ì§€ í´íЏ(page fault) 횟수 * "pmu-cycle": perf-event ì‹œìŠ¤í…œì½œì„ í†µí•œ cpu í´ëŸ­ 사ì´í´ ë° ëª…ë ¹ì–´ 실행 횟수 * "pmu-cache": perf-event ì‹œìŠ¤í…œì½œì„ í†µí•œ ìºì‹œ 참조(reference) ë° ì‹¤íŒ¨(miss) * "pmu-branch": Perf-event ì‹œìŠ¤í…œì½œì„ ì‚¬ìš©í•œ 분기예측(branch prediction) ë° ì‹¤íŒ¨(miss) 결과는 아래와 ê°™ì´ ì£¼ì„ì˜ í˜•íƒœë¡œ ì´ë²¤íЏ ì •ë³´ê°€ 출력ëœë‹¤. $ uftrace -T a@read=proc/statm ./abc # DURATION TID FUNCTION [ 1234] | main() { [ 1234] | a() { [ 1234] | /* read:proc/statm (size=6808KB, rss=776KB, shared=712KB) */ [ 1234] | b() { [ 1234] | c() { 1.448 us [ 1234] | getpid(); 10.270 us [ 1234] | } /* c */ 11.250 us [ 1234] | } /* b */ [ 1234] | /* diff:proc/statm (size=+4KB, rss=+0KB, shared=+0KB) */ 18.380 us [ 1234] | } /* a */ 19.537 us [ 1234] | } /* main */ `finish` 트리거는 기ë¡(record)ì„ ì¢…ë£Œí•  ë–„ 사용한다. ë°ëª¬ê³¼ ê°™ì´ ì¢…ë£Œë˜ì§€ 않는 프로세스를 ì¶”ì í•˜ëŠ” ë° ìœ ìš©í•  수 있다. `filter` 와 `notrace` 트리거는 ê°ê° `-F`/`--filter` 와 `-N` /`--notrace` ê°™ì€ íš¨ê³¼ê°€ 있다. 트리거는 현재 ì»¤ë„ í•¨ìˆ˜ë¥¼ 제외한 ì‚¬ìš©ìž í•¨ìˆ˜ë“¤ì—서만 ë™ìž‘한다. 트리거는 시그ë„ë¡œë„ ì‚¬ìš©í•  수 있다. ì´ëŠ” `signal` íŠ¸ë¦¬ê±°ì— ì˜í•´ 수행ë˜ë©° 함수 트리거와 비슷하지만 현재는 "trace_on", "trace_off" ë° "finish" 트리거만 ì§€ì›ë˜ê³  있다. $ uftrace --signal 'SIGUSR1@finish' ./some-daemon ARGUMENTS ========= uftrace 는 í•¨ìˆ˜ì˜ ì¸ìžì™€ ë°˜í™˜ê°’ì„ ê°ê° `-A`/`\--argument` 와 `-R`/`\--retval` 로 기ë¡í•  수 있다. ì´ì— 대한 문법체계는 트리거와 매우 유사하다. := [ "@" ] := | "," := ( | | ) := "arg" N [ "/" [ ] ] [ "%" ( | ) ] := "fparg" N [ "/" ( | "80" ) ] [ "%" ( | ) ] := "retval" [ "/" [ ] ] := "d" | "i" | "u" | "x" | "s" | "c" | "f" | "S" | "p" := "8" | "16" | "32" | "64" := # "rdi", "xmm0", "r0", ... := "stack" [ "+" ] `-A`/`\--argument` ì˜µì…˜ì€ symbol ì˜ ì´ë¦„ê³¼ ê·¸ê²ƒì˜ spec ë“¤ì„ ì„ íƒì ìœ¼ë¡œ 받는다. spec ì€ argN 으로 시작ë˜ëŠ”ë° ì—¬ê¸°ì„œ N ì€ ì¸ìžì˜ ì¸ë±ìŠ¤ê°’ì´ë‹¤. ì¸ë±ìŠ¤ëŠ” 1 부터 시작ë˜ë©°, 순서는 함수호출규약(calling convention)ì˜ ì¸ìž 전달 순서와 대ì‘ëœë‹¤. ì¸ìžì˜ ì¸ë±ìŠ¤ëŠ” 정수형 (í˜¹ì€ í¬ì¸í„°í˜•) ê³¼ ë¶€ë™ì†Œìˆ˜ì í˜• ê°ê° 따로 관리ëœë‹¤ëŠ” ì , 그리고 ì´ë“¤ì€ í•¨ìˆ˜í˜¸ì¶œê·œì•½ì— ë”°ë¼ ê°ê¸° ê°„ì„­ì„ ì¼ìœ¼í‚¬ 수 있다는 ì ì— 유ì˜í•˜ë¼. argN ì€ ì •ìˆ˜í˜• ì¸ìžë¥¼, fpargN ì€ ë¶€ë™ì†Œìˆ˜ì í˜• ì¸ìžë¥¼ 위한 표기ì´ë‹¤. "d" í˜•ì‹ í˜¹ì€ ì•„ë¬´ 형ì‹ë„ 주지 ì•Šì„ ê²½ìš°, uftrace 는 ì •ìˆ˜í˜•ì€ 'long int'형으로 간주하고 소수ì í˜•ì— ëŒ€í•´ì„œëŠ” 'double'형으로 간주한다. "i" 형ì‹ì€ signed 정수형으로, "u" 형ì‹ì€ unsinged 으로 출력한다. ë‘ í˜•ì‹ ëª¨ë‘ 10 진수가 출력ë˜ëŠ” 한편 "x" 형ì‹ì€ 16 진수로 출력ë˜ê²Œ 한다. "s" 는 null ì„ ì œì™¸í•œ 문ìžì—´ ì¶œë ¥ì„ ìœ„í•œ 형ì‹ì´ê³ , "c" 는 ë‹¨ì¼ ë¬¸ìžë¥¼ 위한 형ì‹ì´ë‹¤. "f" 형ì‹ì€ ë¶€ë™ ì†Œìˆ˜ì ì„ 출력하는ë°, (ì¼ë°˜ì ìœ¼ë¡œ) 반환값ì—서만 ì˜ë¯¸ë¥¼ 가진다. fpargN ì€ í•­ìƒ ì†Œìˆ˜ì  ë°©ì‹ì´ê¸° ë•Œë¬¸ì— ì–´ë–¤ í˜•ì‹ í•„ë“œë„ ì—†ìŒì— 유ì˜í•˜ë¼. "S" 형ì‹ì€ std::string ì„ ìœ„í•œ 형ì‹ì´ì§€ë§Œ, ì•„ì§ê¹Œì§€ëŠ” libstdc++ ë¼ì´ë¸ŒëŸ¬ë¦¬ë§Œ ì§€ì›ê°€ëŠ¥í•˜ë‹¤. 마지막으로, "p" 형ì‹ì€ 함수í¬ì¸í„° 형ì‹ì´ë‹¤. ì¶”ì  ëŒ€ìƒì˜ 주소가 기ë¡ë˜ë©´, 언제나 함수 ì´ë¦„으로 출력ëœë‹¤. 문ìžì—´ íƒ€ìž…ì˜ ì¸ìžë¥¼ 사용할 때 (í¬ì¸í„°) ê°’ì´ ìœ íš¨í•˜ì§€ ì•Šì„ ê²½ìš° í”„ë¡œê·¸ëž¨ì„ ë¹„ì •ìƒ ì¢…ë£Œì‹œí‚¬ 수 있ìŒì— 주ì˜í•˜ë¼. 사실 uftrace 는 유효한 프로세스 주소 ê³µê°„ì˜ ë²”ìœ„ë¥¼ ì§€ì†ì ìœ¼ë¡œ ì ê²€í•˜ë ¤ê³  노력하지만, ëª‡ëª‡ì˜ ê²½ìš°ë“¤ì„ ë†“ì¹  수 있다. ë˜í•œ 특정 ë ˆì§€ìŠ¤í„°ì˜ ì´ë¦„ì´ë‚˜ ìŠ¤íƒ ì˜¤í”„ì…‹(offset)ìœ¼ë¡œë„ ì¸ìžë¡œ 명시할 수 있다. (ë°˜í™˜ê°’ì€ ë¶ˆê°€í•˜ë‹¤) 아래는 ì¸ìžë¡œ ì‚¬ìš©ë  ìˆ˜ 있는 레지스터 ì´ë¦„들ì´ë‹¤. * x86: rdi, rsi, rdx, rcx, r8, r9 (for integer), xmm[0-7] (for floating-point) * arm: r[0-3] (for integer), s[0-15] or d[0-7] (for floating-point) 예시는 아래와 같다. $ uftrace -A main@arg1/x -R main@retval/i32 ./abc # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main(0x1) { [ 1234] | a() { [ 1234] | b() { 3.880 us [ 1234] | c(); 5.475 us [ 1234] | } /* b */ 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } = 0; /* main */ $ uftrace -A puts@arg1/s -R puts@retval ./hello Hello world # DURATION TID FUNCTION 1.457 us [21534] | __monstartup(); 0.997 us [21534] | __cxa_atexit(); [21534] | main() { 7.226 us [21534] | puts("Hello world") = 12; 8.708 us [21534] | } /* main */ ì´ ì¸ìžë“¤ê³¼ ë°˜í™˜ê°’ë“¤ì€ ì‹¤í–‰íŒŒì¼ì´ `-pg` 옵션으로 빌드ë˜ì—ˆì„ 때ì—ë§Œ 기ë¡ë¨ì— 유ì˜í•˜ë¼. `-finstrument-functions` 로 만들어진 실행파ì¼ë“¤ì€ ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì„ ì œì™¸í•˜ê³ ëŠ” 무시ëœë‹¤. ì¸ìžì™€ ë°˜í™˜ê°’ì˜ ê¸°ë¡ì€ ì•„ì§ê¹Œì§„ ì‚¬ìš©ìž ì •ì˜ í•¨ìˆ˜ì—서만 ë™ìž‘한다. ë§Œì¼ í”„ë¡œê·¸ëž¨ì´ DWARF 와 ê°™ì€ ë””ë²„ê·¸ 정보와 함께 빌드ë˜ì—ˆë‹¤ë©´, (libdw 와 함께 빌드ëœ) uftrace 는 ìžë™ìœ¼ë¡œ ì¸ìžë“¤ì˜ 갯수와 ìžë£Œí˜•ë“¤ì„ ì‹ë³„í•  수 있다. ë˜í•œ 디버그 정보를 사용하지 않ë”ë¼ë„, 몇몇 잘 알려진 ë¼ì´ë¸ŒëŸ¬ë¦¬ í•¨ìˆ˜ë“¤ì˜ ì¸ìžë“¤ê³¼ ë°˜í™˜ê°’ì€ ê¸°ë³¸ì ìœ¼ë¡œ 제공ëœë‹¤. ì´ ê²½ìš° 사용ìžëŠ” ì¸ìžë“¤ì˜ spec ê³¼ ë°˜í™˜ê°’ì„ ìˆ˜ë™ì ìœ¼ë¡œ 명시할 필요가 ì—†ì´ í•¨ìˆ˜ì˜ ì´ë¦„ (ë˜ëŠ” 패턴) ë§Œ 주는 ê²ƒìœ¼ë¡œë„ ì¶©ë¶„í•˜ë‹¤. 사실, 명시ì ìœ¼ë¡œ argspec ì„ ì§€ì •í•˜ë©´ ìžë™ argspec ì„ í‘œì‹œë˜ì§€ 않게 한다. 예를 들어, ìœ„ì˜ ì˜ˆì‹œëŠ” 아래와 ê°™ì´ ìž‘ì„±í•  수 있다. $ uftrace -A . -R main -F main ./hello Hello world # DURATION TID FUNCTION [ 18948] | main(1, 0x7ffeeb7590b8) { 7.183 us [ 18948] | puts("Hello world"); 9.832 us [ 18948] | } = 0; /* main */ ì¸ìž 패턴 (".")ì€ ëª¨ë“  문ìžì— 대ì‘ë˜ê¸° ë•Œë¬¸ì— ëª¨ë“  (ì§€ì›ë˜ëŠ”) í•¨ìˆ˜ë“¤ì´ ê¸°ë¡ë˜ì—ˆìŒì— 유ì˜í•˜ë¼. 위ì—서는 "main" í•¨ìˆ˜ì˜ ë‘ ì¸ìžë“¤ê³¼ "puts" ì˜ í•œ 문ìžì—´ ì¸ìžë¥¼ 보여준다. ë§Œì¼ (ì§€ì›ë˜ëŠ”) í•¨ìˆ˜ì˜ ëª¨ë“  ì¸ìžë“¤ê³¼ ë°˜í™˜ê°’ë“¤ì„ ë³´ê³  싶다면, `-a`/`\--auto-args` ì˜µì…˜ì„ ì‚¬ìš©í•˜ë¼. FIELDS ====== uftrace 사용ìžëŠ” replay 결과를 ëª‡ëª‡ì˜ í•„ë“œë¡œ ì›í•˜ëŠ” ë°©ì‹ëŒ€ë¡œ 구성할 수 있다. 여기서 필드란 파ì´í”„ ë¬¸ìž (|) ì™¼ìª½ì— ë‚˜íƒ€ë‚˜ëŠ” 정보를 뜻한다. 기본ì ìœ¼ë¡œ ì§€ì†ì‹œê°„ duration ê³¼ tid 필드를 사용하지만, 다른 í•„ë“œë“¤ë„ ë‹¤ìŒê³¼ ê°™ì´ ìž„ì˜ì˜ 순서로 사용 가능하다. $ uftrace -f time,delta,duration,tid,addr ./abc # TIMESTAMP TIMEDELTA DURATION TID ADDRESS FUNCTION 75059.205379813 1.374 us [27804] 4004d0 | __monstartup(); 75059.205384184 4.371 us 0.737 us [27804] 4004f0 | __cxa_atexit(); 75059.205386655 2.471 us [27804] 4006b1 | main() { 75059.205386838 0.183 us [27804] 400656 | a() { 75059.205386961 0.123 us [27804] 400669 | b() { 75059.205387078 0.117 us [27804] 40067c | c() { 75059.205387264 0.186 us 0.643 us [27804] 4004b0 | getpid(); 75059.205388501 1.237 us 1.423 us [27804] 40067c | } /* c */ 75059.205388724 0.223 us 1.763 us [27804] 400669 | } /* b */ 75059.205388878 0.154 us 2.040 us [27804] 400656 | } /* a */ 75059.205389030 0.152 us 2.375 us [27804] 4006b1 | } /* main */ ê° í•„ë“œë“¤ì€ ë‹¤ìŒê³¼ ê°™ì€ ì˜ë¯¸ë¥¼ 가진다. * tid: task id (gettid(2)로 ì–»ì„ ìˆ˜ 있다.) * duration: 함수 실행 시간 * time: 타임스탬프 ì •ë³´ * delta: ì–´ë–¤ 작업 ë‚´ ë‘ íƒ€ìž„ìŠ¤íƒ¬í”„ì˜ ì°¨ì´ * elapsed: 첫 íƒ€ìž„ìŠ¤íƒ¬í”„ë¡œë¶€í„°ì˜ ê²½ê³¼ 시간 * addr: 해당 í•¨ìˆ˜ì˜ ì£¼ì†Œ * task: íƒœìŠ¤í¬ ì´ë¦„ (comm) * module: ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¹ì€ ì‹¤í–‰ 가능한 í•¨ìˆ˜ì˜ ì´ë¦„ 기본ì ìœ¼ë¡œ ì„¤ì •ëœ í•„ë“œê°’ì€ 'duration,tid'ì´ë‹¤. 만약 주어진 í•„ë“œì˜ ì´ë¦„ì´ "+"로 시작ëœë‹¤ë©´, ê·¸ 필드는 기본 í•„ë“œê°’ì— ì¶”ê°€ë  ê²ƒì´ë‹¤. 즉, "-f +time" 는 "-f duration,tid,time" 와 ê°™ì€ ê²ƒì´ë‹¤. ë˜í•œ 'none'ì´ë¼ëŠ” 특별한 í•„ë“œë„ ë°›ì„ ìˆ˜ 있는ë°, ì´ëŠ” 필드 ì¶œë ¥ì„ í•˜ì§€ 않고 ì˜¤ì§ í•¨ìˆ˜ 실행 ê²°ê³¼ë§Œì„ ë³´ì—¬ì¤€ë‹¤. DYNAMIC TRACING =============== uftrace 는 x86_64, AArch64 í™˜ê²½ì˜ ëŸ°íƒ€ìž„ (정확하게는, 로드 타임) ì—서 ë™ì ì¶”ì (dynamic tracing)ì´ ê°€ëŠ¥í•˜ë‹¤. 함수를 기ë¡í•˜ê¸° ì „ì—, 보통 í”„ë¡œê·¸ëž¨ì„ `-pg` (í˜¹ì€ `-finstrument-functions`으로) 빌드해야 하고, 그렇게 ëœë‹¤ë©´ 모든 í•¨ìˆ˜ë“¤ì´ `mcount()`를 호출하기 ë•Œë¬¸ì— ì–´ëŠ ì •ë„ ì„±ëŠ¥ì— ì˜í–¥ì„ 받게 ë  ê²ƒì´ë‹¤. ë™ì ì¶”ì ì„ í•  때, `-P`/`--patch` ì˜µì…˜ì„ í†µí•´ 특정 í•¨ìˆ˜ë§Œì„ ì¶”ì í•  수 있다. capstone 디스어셈블리 ì—”ì§„ì„ ì‚¬ìš©í•œë‹¤ë©´ 위 ì˜µì…˜ì„ ì§€ì •í•´ì„œ í”„ë¡œê·¸ëž¨ì„ (재)컴파ì¼í•  필요가 없다. ì´ì œ uftrace 는 ëª…ë ¹ì–´ë“¤ì„ ë¶„ì„í•  수 있게 ë˜ê³  (만약 가능하다면) ê·¸ ëª…ë ¹ì–´ë“¤ì„ ë‹¤ë¥¸ ê³³ì— ë³µì‚¬í•˜ì—¬ `mcount()` í•¨ìˆ˜ë“¤ì„ í˜¸ì¶œí•˜ì—¬ uftrace 로 ì¶”ì í•  수 있게 ë°”ì´ë„ˆë¦¬ë¥¼ ì¡°ìž‘ í•  수 있다. ê·¸ ì´í›„ ì œì–´ê¶Œì€ ë³µì‚¬ëœ ëª…ë ¹ì–´ë¡œ 넘어가게 ë˜ê³ , ê·¸ 다ìŒì—야 ë‚¨ì€ ëª…ë ¹ì–´ë“¤ë¡œ 반환하게 ëœë‹¤. capstone ì„ ì‚¬ìš©í•  수 없다면, í”„ë¡œê·¸ëž¨ì„ ë¹Œë“œí•  때 몇몇 컴파ì¼ëŸ¬ (gcc) ì˜µì…˜ë“¤ì„ ì¶”ê°€í•´ì•¼ í•  것ì´ë‹¤. gcc 5.1 버전 ì´ìƒë¶€í„°ëŠ” `-mfentry`와 `-mnop-mcount` ì˜µì…˜ì„ ì œê³µí•˜ëŠ”ë° ì´ ì˜µì…˜ë“¤ì€ í•¨ìˆ˜ 맨 ì•žì— `mcount()` 와 ê°™ì€ í•¨ìˆ˜ ì¶”ì ì„ 위한 코드를 추가하고 ê·¸ 명령어를 NOP 으로 변환한다. 그렇게 ë˜ë©´ ì¼ë°˜ì ì¸ ì¡°ê±´ì—서 실행할 때ì—는 성능 ìƒì˜ 오버헤드가 ê±°ì˜ ì—†ì–´ì§ˆ 것ì´ë‹¤. uftrace 는 `-P` ì˜µì…˜ì„ ì´ìš©í•˜ì—¬ ì„ íƒì ìœ¼ë¡œ `mcount()` 함수를 호출할 수 있ë„ë¡ ì „í™˜í•  수 있다. uftrace 를 ì•„ëž˜ì˜ ì˜ˆì œì—서 í‰ì†Œì²˜ëŸ¼ 사용할때ì—는 ì—러 메세지를 보여준다. ê·¸ ì´ìœ ëŠ” ë°”ì´ë„ˆë¦¬ê°€ ì–´ë–¤ `mcount()` 와 ê°™ì€ í•¨ìˆ˜ ì¶”ì ì„ 위한 ì½”ë“œë„ í˜¸ì¶œí•˜ì§€ 않기 때문ì´ë‹¤. $ gcc -o abc -pg -mfentry -mnop-mcount tests/s-abc.c $ uftrace abc uftrace: /home/namhyung/project/uftrace/cmd-record.c:1305:check_binary ERROR: Can't find 'mcount' symbol in the 'abc'. It seems not to be compiled with -pg or -finstrument-functions flag which generates traceable code. Please check your binary file. 하지만 `-P a` 패치 ì˜µì…˜ì„ ì ìš©í•œë‹¤ë©´, ë™ì ìœ¼ë¡œ `a()` í•¨ìˆ˜ë§Œì„ ì¶”ì í•  것ì´ë‹¤. $ uftrace --no-libcall -P a abc # DURATION TID FUNCTION 0.923 us [19379] | a(); 추가로, '.'ì„ ì´ìš©í•´ (globì€, '*') `P`옵션과 함께 정규표현ì‹ìœ¼ë¡œ ì“°ì¸ ë¬¸ìžì— 대해 하나ë¼ë„ 매칭ë˜ëŠ” 모든 í•¨ìˆ˜ë“¤ì— ëŒ€í•´ì„œë„ ì ìš©ì‹œí‚¬ 수 있다. $ uftrace --no-libcall -P . abc # DURATION TID FUNCTION [19387] | main() { [19387] | a() { [19387] | b() { 0.940 us [19387] | c(); 2.030 us [19387] | } /* b */ 2.451 us [19387] | } /* a */ 3.289 us [19387] | } /* main */ Clang/LLVM 4.0ì€ [X-ray](http://llvm.org/docs/XRay.html)ë¼ëŠ” ê¸°ìˆ ì„ ì œê³µí•œë‹¤. ì´ëŠ” `gcc -mfentry -mnop-mcount` 와 `-finstrument-functions` 를 결합한 ê²ƒê³¼ë„ ìœ ì‚¬í•˜ë‹¤. uftrace는 `X-ray`로 ë¹Œë“œëœ ì‹¤í–‰íŒŒì¼ì— ëŒ€í•´ì„œë„ ë™ì ì¶”ì ì„ ì§€ì›í•œë‹¤. 예를 들어, ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì„ clang 으로 ì•„ëž˜ì˜ ì˜µì…˜ìœ¼ë¡œ 빌드할 ìˆ˜ë„ ìžˆì§€ë§Œ, 그와 ë™ì¼í•˜ê²Œ ë™ì ì¶”ì ì„ 위해 아래와 ê°™ì´ `-P` ì˜µì…˜ì„ ì‚¬ìš©í•  ìˆ˜ë„ ìžˆì„ ê²ƒì´ë‹¤. $ clang -fxray-instrument -fxray-instruction-threshold=1 -o abc-xray tests/s-abc.c $ uftrace -P main abc-xray # DURATION TID FUNCTION [11093] | main() { 1.659 us [11093] | getpid(); 5.963 us [11093] | } /* main */ $ uftrace -P . abc-xray # DURATION TID FUNCTION [11098] | main() { [11098] | a() { [11098] | b() { [11098] | c() { 0.753 us [11098] | getpid(); 1.430 us [11098] | } /* c */ 1.915 us [11098] | } /* b */ 2.405 us [11098] | } /* a */ 3.005 us [11098] | } /* main */ SCRIPT EXECUTION ================ uftrace 는 í•¨ìˆ˜ì˜ ì§„ìž…ê³¼ 반환 시ì ì— 스í¬ë¦½íЏ ì‹¤í–‰ì´ ê°€ëŠ¥í•˜ë‹¤. ì§€ì›ë˜ëŠ” 스í¬ë¦½íŠ¸ëŠ” ì•„ì§ê¹Œì§€ëŠ” Python 2.7 ë¿ì´ë‹¤. 사용ìžëŠ” 네 ê°œì˜ í•¨ìˆ˜ë¥¼ 작성할 수 있다. 'uftrace_entry' 와 'uftracce_exit' ì€ ê° í•¨ìˆ˜ì˜ ì§„ìž…ì‹œì ê³¼ 반환시ì ì— í•­ìƒ ì‹¤í–‰ëœë‹¤. 하지만 'uftrace_begin' ê³¼ 'uftrace_end' 는 ë¶„ì„ ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì´ ì´ˆê¸°í™”ë˜ê³  종료ë ë•Œ 한 번씩만 실행ëœë‹¤. $ cat scripts/simple.py def uftrace_begin(ctx): print("program begins...") def uftrace_entry(ctx): func = ctx["name"] print("entry : " + func + "()") def uftrace_exit(ctx): func = ctx["name"] print("exit : " + func + "()") def uftrace_end(): print("program is finished") 위 스í¬ë¦½íŠ¸ëŠ” 아래와 ê°™ì´ ê¸°ë¡ëœ 시간 순으로 ì‹¤í–‰ë  ìˆ˜ 있다: $ uftrace -S scripts/simple.py -F main tests/t-abc program begins... entry : main() entry : a() entry : b() entry : c() entry : getpid() exit : getpid() exit : c() exit : b() exit : a() exit : main() program is finished # DURATION TID FUNCTION [10929] | main() { [10929] | a() { [10929] | b() { [10929] | c() { 4.293 us [10929] | getpid(); 19.017 us [10929] | } /* c */ 27.710 us [10929] | } /* b */ 37.007 us [10929] | } /* a */ 55.260 us [10929] | } /* main */ 'ctx' 변수는 ì•„ëž˜ì˜ ì •ë³´ë¥¼ í¬í•¨í•˜ëŠ” 사전타입(dictionary type)ì˜ ë³€ìˆ˜ì´ë‹¤. /* context information passed to uftrace_entry(ctx) and uftrace_exit(ctx) */ script_context = { int tid; int depth; long timestamp; long duration; # exit only long address; string name; list args; # entry only (if available) value retval; # exit only (if available) }; /* context information passed to uftrace_begin(ctx) */ script_context = { bool record; # True if it runs at record time, otherwise False string version; # uftrace version info list cmds; # execution commands }; 'script_context' ì— ìžˆëŠ” ê° í•­ëª©ë“¤ì€ ìŠ¤í¬ë¦½íЏ ë‚´ì—서 ì½ì„ 수 있다. 스í¬ë¦½íŒ…ì— ëŒ€í•œ ìžì„¸í•œ ì‚¬í•­ì€ `uftrace-script`(1)를 참고할 수 있다. WATCH POINT =========== uftrace ì˜ watch point 는 특정 ê°’ì˜ ë³€ê²½ì‚¬í•­ì„ ì¶œë ¥í•œë‹¤. ê°œë…ì ìœ¼ë¡œëŠ” ì¼ë°˜ì ì¸ ë””ë²„ê±°ì˜ watch point 와 같지만, í•¨ìˆ˜ì˜ ì§„ìž…ê³¼ 종료ì—ë§Œ ì ìš©ë˜ê¸° ë•Œë¬¸ì— ëª‡ëª‡ ë³€ê²½ì‚¬í•­ë“¤ì€ ë†“ì¹  ìˆ˜ë„ ìžˆë‹¤. ì•„ì§ê¹Œì§€ëŠ”, ì•„ëž˜ì˜ watch point ë“¤ë§Œì´ ì§€ì›ëœë‹¤. * "cpu" : 현재 ìž‘ì—…ì„ ìˆ˜í–‰í•˜ëŠ” cpu 번호 트리거를 ì½ì„ 때처럼, 결과는 다ìŒê³¼ ê°™ì´ ì£¼ì„ í˜•ì‹ì˜ ì´ë²¤íŠ¸ë¡œ 출력ëœë‹¤. $ uftrace -W cpu tests/t-abc # DURATION TID FUNCTION [ 19060] | main() { [ 19060] | /* watch:cpu (cpu=8) */ [ 19060] | a() { [ 19060] | b() { [ 19060] | c() { 2.365 us [ 19060] | getpid(); 8.002 us [ 19060] | } /* c */ 8.690 us [ 19060] | } /* b */ 9.350 us [ 19060] | } /* a */ 12.479 us [ 19060] | } /* main */ 함께 보기 ========= `uftrace-record`(1), `uftrace-replay`(1), `uftrace-report`(1), `uftrace-script`(1) ë²ˆì—­ìž ====== 강민철 , ê¹€ê´€ì˜ , ê¹€í™ê·œ uftrace-0.9.4/doc/ko/uftrace-record.md000066400000000000000000001036251362052523300175720ustar00rootroot00000000000000% UFTRACE-RECORD(1) Uftrace User Manuals % Namhyung Kim % Sep, 2018 ì´ë¦„ ==== uftrace-record - ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì˜ í•¨ìˆ˜ 실행 íë¦„ì„ ê¸°ë¡í•œë‹¤. 사용법 ====== uftrace record [*options*] COMMAND [*command-options*] 설명 ==== ì´ ëª…ë ¹ì–´ëŠ” COMMAND 로 ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì„ ì‹¤í–‰í•˜ê³  í•¨ìˆ˜ë“¤ì˜ ì‹¤í–‰ íë¦„ì„ ê¸°ë¡í•œë‹¤. ì´ ê³¼ì •ì—서 ì•„ë¬´ê²ƒë„ ì¶œë ¥í•˜ì§€ 않고 uftrace.data ë””ë ‰í† ë¦¬ì— ë°ì´í„°ë¥¼ 저장한다. ì´ ë°ì´í„°ëŠ” ì´í›„ì— `uftrace replay` 나 `uftrace report` ë“±ì„ í†µí•´ ë¶„ì„ë  ìˆ˜ 있다. RECORD 옵션 ============== -A *SPEC*, \--argument=*SPEC* : í•¨ìˆ˜ì˜ ì¸ìžë“¤ì„ 기ë¡í•œë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. ì¸ìžì— 대한 ì„¤ëª…ì€ *ARGUMENTS* 를 참고한다. -R *SPEC*, \--retval=*SPEC* : í•¨ìˆ˜ë“¤ì˜ ë°˜í™˜ê°’ì„ ê¸°ë¡í•œë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. ë°˜í™˜ê°’ì— ëŒ€í•œ ì„¤ëª…ì€ *ARGUMENTS* 를 참고한다. -P *FUNC*, \--patch=*FUNC* : 주어진 FUNC 함수를 ë™ì ìœ¼ë¡œ 패치하여 ì¶”ì í•˜ê³  기ë¡í•œë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. 관련 ì„¤ëª…ì€ *DYNAMIC TRACING* ì„ ì°¸ê³ í•œë‹¤. -U *FUNC*, \--unpatch=*FUNC* : 주어진 FUNC í•¨ìˆ˜ì— ëŒ€í•´ ë™ì  패치를 ì ìš©í•˜ì§€ 않는다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. 관련 ì„¤ëª…ì€ *DYNAMIC TRACING* ì„ ì°¸ê³ í•œë‹¤. -Z *SIZE*, \--size-filter=*SIZE* : SIZE ë°”ì´íŠ¸ë³´ë‹¤ í° í•¨ìˆ˜ë“¤ì„ ë™ì ìœ¼ë¡œ 패치한다. ë™ì ì¶”ì ì— 대해서는 *DYNAMIC TRACING* ì„ ì°¸ê³ í•œë‹¤. -E *EVENT*, \--event=*EVENT* : ì´ë²¤íЏ ì¶”ì ì„ 활성화한다. 시스템 ë‚´ì—서 사용 가능한 ì´ë²¤íŠ¸ì—¬ì•¼ 한다. -S *SCRIPT_PATH*, \--script=*SCRIPT_PATH* : ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì´ ì‹¤í–‰í•˜ëŠ” ë™ì•ˆ í•¨ìˆ˜ì˜ ì§„ìž…ê³¼ 반환 시ì ì— 주어진 스í¬ë¦½íŠ¸ë¥¼ 활용해서 추가ì ì¸ ìž‘ì—…ì„ í•œë‹¤. 스í¬ë¦½íЏ 언어 종류는 파ì¼ì˜ 확장ìžë¥¼ 통해 ì •í•´ì§€ëŠ”ë° íŒŒì´ì¬ì˜ 경우 ".py" ì´ë‹¤. 스í¬ë¦½íЏ 실행 ì„¤ëª…ì€ *SCRIPT EXECUTION* ì„ ì°¸ê³ í•œë‹¤. -W, \--watch=*POINT* : 특정한 ê°’ì´ ë³€ê²½ë˜ì—ˆì„ 경우 ì´ë¥¼ 보여주기 위해 watch point 를 추가한다. ìžì„¸í•œ ì‚¬í•­ì€ *WATCH POINT* 를 참고한다. -a, \--auto-args : 알려진 í•¨ìˆ˜ì˜ ì¸ìžì™€ ë°˜í™˜ê°’ë“¤ì„ ìžë™ìœ¼ë¡œ 기ë¡í•œë‹¤. ë³´í†µì˜ ê²½ìš° C 언어 ë˜ëŠ” ì‹œìŠ¤í…œì˜ í‘œì¤€ ë¼ì´ë¸ŒëŸ¬ë¦¬ í•¨ìˆ˜ë“¤ì— í•´ë‹¹í•˜ì§€ë§Œ, 디버그 정보를 ì´ìš©í•  수 있다면 ì‚¬ìš©ìž í•¨ìˆ˜ë“¤ì—ë„ ì ìš©í•  수 있다. -l, \--nest-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ë“¤ ê°„ì˜ í•¨ìˆ˜ í˜¸ì¶œë„ í•¨ê»˜ 기ë¡í•œë‹¤. 기본ì ìœ¼ë¡œ uftrace 는 실행파ì¼ì—서 ì§ì ‘ 호출하는 ë¼ì´ë¸ŒëŸ¬ë¦¬ 함수만 기ë¡í•œë‹¤. -k, \--kernel : ì‚¬ìš©ìž í”„ë¡œê·¸ëž¨ì˜ í•¨ìˆ˜ì™€ 함께 ì»¤ë„ í•¨ìˆ˜ë¥¼ ì¶”ì í•œë‹¤. 기본ì ìœ¼ë¡œëŠ” 커ë„ë¡œì˜ ì§„ìž… ë° ë³µê·€ 함수만 기ë¡í•œë‹¤. ì´ë¥¼ 변경하려면 --kernel-depth ì˜µì…˜ì„ ì‚¬ìš©í•  수 있다. -K *DEPTH*, \--kernel-depth=*DEPTH* : ì»¤ë„ ìµœëŒ€ 함수 깊ì´ë¥¼ 설정한다. --kernel ì˜µì…˜ì´ ìžë™ìœ¼ë¡œ ì ìš©ëœë‹¤. -H *HOST*, \--host=*HOST* : 파ì¼ì— ì“°ì§€ 않고, 주어진 호스트ì—게 ì¶”ì  ë°ì´í„°ë¥¼ ë„¤íŠ¸ì›Œí¬ ìƒìœ¼ë¡œ 전송한다. ë°ì´í„°ë¥¼ 받기 위해서 `uftrace recv` 명령어가 목ì ì§€ì—서 실행ë˜ì–´ì•¼ 한다. \--port=*PORT* : (`-H`를 ì´ìš©í•´)ë°ì´í„°ë¥¼ 네트워í¬ë¡œ 보낼 때, 기본 í¬íЏ (8090)ê°€ 아닌 다른 í¬íŠ¸ë¥¼ 사용한다. \--signal=*TRG* : 함수가 아닌 ì„ íƒí•œ 시그ë„ì— íŠ¸ë¦¬ê±°ë¥¼ 설정한다. 하지만 제한 사항들로 ì¸í•˜ì—¬ 소수 트리거 ê¸°ëŠ¥ë§Œì„ ì§€ì›í•˜ê³  있다. 사용 가능한 작업: : trace_on, trace_off, finish. ì´ ì˜µì…˜ì€ ë‘번 ì´ìƒ 사용할 수 있다. 트리거 ì„¤ëª…ì€ *TRIGGERS* 를 참고한다. \--nop : ì–´ë–¤ í•¨ìˆ˜ë„ record 하거나 replay하지 않는다. ì´ëŠ” 아무 ì¼ë„ 하지 않는 명령어로, 성능 비êµì—서만 ì˜ë¯¸ë¥¼ 가진다. \--force : ì•½ê°„ì˜ ë¬¸ì œê°€ ìžˆì–´ë„ uftrace ê°€ 실행ëœë‹¤. `uftrace record` 는 실행파ì¼ì—서 컴파ì¼ëŸ¬ì— ì˜í•´ ìƒì„±ë˜ëŠ” mcount 를 ì°¾ì„ ìˆ˜ ì—†ì„ ë•Œ uftrace ê°€ í”„ë¡œê·¸ëž¨ì„ ì¶”ì í•  수 없으므로 오류 메시지와 함께 종료ëœë‹¤. 단, 사용ìžëŠ” ë™ì ìœ¼ë¡œ ì—°ê²°ëœ ë¼ì´ë¸ŒëŸ¬ë¦¬ ë‚´ì˜ ê¸°ëŠ¥ì—ë§Œ ê´€ì‹¬ì´ ìžˆì„ ìˆ˜ 있으며, ì´ ê²½ìš° `--force` ì˜µì…˜ì„ ì‚¬ìš©í•˜ì—¬ uftrace 를 실행시킬 수 있다. ë˜í•œ `-A`/`--argument` ë° `-R`/`--retval` ì˜µì…˜ì€ -pg 로 컴파ì¼ëœ ë°”ì´ë„ˆë¦¬ì— 대해서만 ìž‘ë™í•˜ë¯€ë¡œ, uftrace 는 ê·¸ 옵션 ì—†ì´ ë¹Œë“œëœ ë°”ì´ë„ˆë¦¬ë¥¼ 실행하려고 í•  때ì—ë„ ì¢…ë£Œëœë‹¤. ì´ ì˜µì…˜ì€ ê²½ê³ ë¥¼ 무시하고 ì¸ìˆ˜ ë° ë°˜í™˜ ê°’ ì—†ì´ uftrace 를 실행시키ë„ë¡ í•œë‹¤. \--time : time(1) 스타ì¼ë¡œ ì‹¤í–‰ì‹œê°„ì„ ì¶œë ¥í•œë‹¤. 공통 옵션 ============== -F *FUNC*, \--filter=*FUNC* : ì„ íƒëœ 함수들 (그리고 ê·¸ ë‚´ë¶€ì˜ í•¨ìˆ˜ë“¤)ë§Œ 출력하ë„ë¡ í•„í„°ë¥¼ 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. -N *FUNC*, \--notrace=*FUNC* : ì„ íƒëœ 함수들 (ë˜ëŠ” ê·¸ 아래 함수들)ì„ ì¶œë ¥ì—서 제외하ë„ë¡ ì„¤ì •í•˜ëŠ” 옵션ì´ë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. -C *FUNC*, \--caller-filter=*FUNC* : ì„ íƒëœ í•¨ìˆ˜ì˜ í˜¸ì¶œìžë¥¼ 출력하는 필터를 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있 다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. -T *TRG*, \--trigger=*TRG* : ì„ íƒëœ í•¨ìˆ˜ì˜ íŠ¸ë¦¬ê±°ë¥¼ 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. íŠ¸ë¦¬ê±°ì— ëŒ€í•œ ì„¤ëª…ì€ *TRIGGERS* 를 참고한다. -D *DEPTH*, \--depth=*DEPTH* : 함수가 ì¤‘ì²©ë  ìˆ˜ 있는 최대 깊ì´ë¥¼ 설정한다. (ì´ë¥¼ 넘어서는 ìƒì„¸í•œ 함수 실행과정 ì€ ë¬´ì‹œí•œë‹¤.) í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. -t *TIME*, \--time-filter=*TIME* : 설정한 시간 ì´í•˜ë¡œ ìˆ˜í–‰ëœ í•¨ìˆ˜ëŠ” 표시하지 않게 한다. 만약 ì–´ë–¤ 함수가 ëª…ì‹œì  ìœ¼ë¡œ 'ì¶”ì ' triggerê°€ ì ìš©ëœ 경우, ê·¸ 함수는 실행 시간과 ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. \--no-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì€ í‘œì‹œí•˜ì§€ 않게 한다. \--no-event : ì´ë²¤íŠ¸ëŠ” 표시하지 않게 한다. \--match=*TYPE* : TYPE으로 ì¼ì¹˜í•˜ëŠ” íŒ¨í„´ì„ ë³´ì—¬ì¤€ë‹¤. 가능한 형태는 `regex`와 `glob`ì´ë‹¤. ê¸°ë³¸ì€ `regex`ì´ë‹¤. \--disable : ì¶”ì ì„ 사용하지 ì•Šì€ ì±„ë¡œ uftrace를 시작한다. ì´ê²ƒì€ `trace_on` 트리거와 함께 사용ë˜ì—ˆì„ 때만 ì˜ë¯¸ë¥¼ 가진다. RECORD 설정 옵션 ===================== -L *PATH*, \--library-path=*PATH* : 필요한 ë‚´ë¶€ ë¼ì´ë¸ŒëŸ¬ë¦¬ë¥¼ *PATH* ì—서 먼저 찾는다. ì´ ì˜µì…˜ì€ ëŒ€ë¶€ë¶„ 테스트 목ì ìœ¼ë¡œ 사용ëœë‹¤. -b *SIZE*, \--buffer=*SIZE* : 저장할 ë°ì´í„°ì˜ ë‚´ë¶€ ë²„í¼ í¬ê¸°ë¥¼ 설정한다. 기본 사ì´ì¦ˆëŠ” 128k ì´ë‹¤. \--kernel-buffer=*SIZE* : 저장할 ì»¤ë„ ë°ì´í„°ì˜ ë‚´ë¶€ ë²„í¼ í¬ê¸°ë¥¼ 설정한다. ì»¤ë„ ë‚´ë¶€ì˜ ê¸°ë³¸ ì„¤ì •ì€ 1408k ì´ë‹¤. \--no-pltbind : ë™ì  심볼 주소를 ë°”ì¸ë”©í•˜ì§€ 않는다. ì´ ì˜µì…˜ì€ `LD_BIND_NOT` 환경 변수를 사용하여 ë™ì‹œì ìœ¼ë¡œ ë°œìƒí•˜ëŠ” (첫 번째) 접근으로 ì¸í•´ 누ë½ë  수 있는 ë¼ì´ë¸ŒëŸ¬ë¦¬ 함수를 ì¶”ì í•œë‹¤. `--no-libcall` 옵션과 함께 ì´ ì˜µì…˜ì„ ì‚¬ìš©í•˜ëŠ” ê²ƒì€ ì˜ë¯¸ê°€ 없다. \--max-stack=*DEPTH* : ë‚´ë¶€ì ìœ¼ë¡œ 기ë¡í•˜ëŠ” 함수 호출 스íƒì˜ 최대 깊ì´ë¥¼ 설정한다. ê¸°ë³¸ê°’ì€ 1024 ì´ë‹¤. \--num-thread=*NUM* : ë°ì´í„°ë¥¼ 저장하기 위해 *NUM* ê°œì˜ ì“°ë ˆë“œë¥¼ 사용한다. 기본ì ìœ¼ë¡œëŠ” 사용 가능한 CPU ì˜ 1/4 으로 설정한다. (하지만 커ë„ì„ í¬í•¨í•´ 전체를 기ë¡í•˜ëŠ” 경우, 최대로 사용 가능한 CPU ì˜ ìˆ˜ë¡œ 설정한다.) \--libmcount-single : 빠른 ë°ì´í„° 기ë¡ì„ 위해서 libmcount ì˜ ë‹¨ì¼ ì“°ë ˆë“œ ë²„ì „ì„ ì‚¬ìš©í•œë‹¤. ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì´ pthread ë¼ì´ë¸ŒëŸ¬ë¦¬ë¥¼ 사용하는 경우ì—는 무시ëœë‹¤. \--rt-prio=*PRIO* : ë°ì´í„° 기ë¡ì„ 하는 스레드를 *PRIO* 를 우선순위로 갖는 실시간(FIFO)로 í–¥ìƒì‹œí‚¨ë‹¤. ì´ ì˜µì…˜ì€ íŠ¹ížˆ 대규모 ë°ì´í„°ë¥¼ 기ë¡í•˜ëŠ” ì „ì²´ ì»¤ë„ ì¶”ì ê³¼ ê°™ì€ í™˜ê²½ì—서 유용하다. \--keep-pid : í”„ë¡œê·¸ëž¨ì„ ì¶”ì í•  때 ë™ì¼í•œ pid ê°’ì„ ìœ ì§€í•˜ê²Œ 해준다. ì¼ë¶€ ë°ëª¬ í”„ë¡œì„¸ìŠ¤ì˜ ê²½ìš° 분기 í•  ë–„ ë™ì¼í•œ pid 를 ê°–ëŠ”ê²ƒì´ ì¤‘ìš”í•˜ë‹¤. ì¼ë°˜ì ìœ¼ë¡œ uftrace 를 실행하면 fork() 를 ë‚´ë¶€ì ìœ¼ë¡œ 다시 호출하므로 pid ê°€ 변경ëœë‹¤. ì´ ì˜µì…˜ì„ ì‚¬ìš©í•  경우 í„°ë¯¸ë„ ì„¤ì •ì´ ì†ìƒë˜ëŠ” 경우가 있기 ë–„ë¬¸ì— `--no-pager` 옵션과 함께 사용하는 ê²ƒì´ ì¢‹ë‹¤. \--no-randomize-addr : ASLR(Address Space Layout Randomization)ì„ ë¹„í™œì„±í™” 한다. ì´ëŠ” í”„ë¡œì„¸ìŠ¤ì˜ ë¼ì´ë¸ŒëŸ¬ë¦¬ 로딩 주소가 매번 변경ë˜ì§€ 않ë„ë¡ ë§‰ì•„ì¤€ë‹¤. \--srcline : 디버그 ì •ë³´ì— ë ˆì½”ë“œí•œ 소스 줄번호를 표시한다. FILTERS ======= uftrace 는 관심 있는 대ìƒì´ 아닌 í•¨ìˆ˜ë“¤ì„ ê°ì¶”는 í•„í„°ë§ì„ í•  수 있다. í•„í„°ë§ì€ 사용ìžë“¤ì´ 관심 있는 함수들ì—ë§Œ 집중할 수 있게 하고, 기ë¡ë˜ëŠ” ë°ì´í„°ì˜ í¬ê¸°ë¥¼ ì¤„ì¼ ìˆ˜ ë•Œë¬¸ì— ì‚¬ìš©í•˜ê¸°ë¥¼ 권장한다. uftrace ê°€ 호출ë˜ë©´, ë‘ ì¢…ë¥˜ì˜ í•¨ìˆ˜ 필터를 갖게 ë˜ëŠ”ë° ì´ë“¤ì€ ëŒ€ìƒ í•¨ìˆ˜ë¥¼ ì„ íƒí•˜ëŠ” ë°©ì‹(opt-in)ì˜ í•„í„°ë¡œ `-F`/`--filter` 와 ì„ íƒí•˜ì§€ 않는 ë°©ì‹(opt-out)ì˜ í•„í„°ì¸ `-N`/`--notrace` ê°€ 있다. ì´ í•„í„°ë“¤ì€ ê¸°ë¡(record)하거나 재ìƒ(replay)í•  때 ëª¨ë‘ ì ìš©ë  수 있다. 첫번째 í•„í„° 종류는 ì„ íƒí•˜ëŠ” ë°©ì‹ì˜ í•„í„°ì´ë‹¤. 기본ì ìœ¼ë¡œ, ì´ê²ƒì€ ì•„ë¬´ê²ƒë„ ì¶”ì í•˜ì§€ 않는다. 하지만 ì–´ë–¤ ëª…ì‹œëœ í•¨ìˆ˜ì— ì§„ìž…í•˜ë©´, 함수 í˜¸ì¶œì— ëŒ€í•œ ì¶”ì ì„ 시작한다. 그러다가 ê·¸ 함수가 반환하게 ë˜ë©´, 함수 호출 ì¶”ì ì„ 중단한다. 예를 들어, `a()`, `b()` 와 `c()`를 차례로 호출하는 간단한 í”„ë¡œê·¸ëž¨ì„ ìƒê°í•´ë³´ìž. $ cat abc.c void c(void) { /* do nothing */ } void b(void) { c(); } void a(void) { b(); } int main(void) { a(); return 0; } $ gcc -pg -o abc abc.c ì¼ë°˜ì ì¸ 경우 uftrace 는 `main()`부터 `c()`ê¹Œì§€ì˜ ëª¨ë“  í•¨ìˆ˜ë“¤ì„ ì¶”ì í•  것ì´ë‹¤. $ uftrace record ./abc $ uftrace replay # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { [ 1234] | a() { [ 1234] | b() { 3.880 us [ 1234] | c(); 5.475 us [ 1234] | } /* b */ 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ 하지만 `-F b` í•„í„° ì˜µì…˜ì´ ì‚¬ìš©ë˜ì—ˆì„ 때는, `main()`ê³¼ `a()` 함수는 ë³´ì´ì§€ 않고 ì˜¤ì§ `b()`와 `c()`ë§Œì´ í¬í•¨ëœ ì¶”ì  ê²°ê³¼ë¥¼ ë³´ì¼ê²ƒì´ë‹¤. $ uftrace record -F b ./abc $ uftrace replay # DURATION TID FUNCTION [ 1234] | b() { 3.880 us [ 1234] | c(); 5.475 us [ 1234] | } /* b */ ë‘번째 í•„í„° 종류는 ì„ íƒí•˜ì§€ 않는 ë°©ì‹ì˜ í•„í„°ì´ë‹¤. 기본ì ìœ¼ë¡œ, 모든 ê²ƒì´ ì¶”ì ë˜ì§€ë§Œ, ëª…ì‹œëœ í•¨ìˆ˜ì— ì§„ìž…í•˜ê²Œ ë˜ë©´, ì¶”ì ì„ 멈춘다. ì œì™¸ëœ í•¨ìˆ˜ê°€ 반환하게 ë˜ë©´, ì¶”ì ì„ 재개한다. 위 예시ì—서, `b()` 함수와 ê·¸ì˜ ëª¨ë“  í˜¸ì¶œì€ `-N` 옵션으로 ìƒëžµí•  수 있다. $ uftrace record -N b ./abc $ uftrace replay # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { 6.448 us [ 1234] | a(); 8.631 us [ 1234] | } /* main */ ë§Œì¼ íŠ¹ì • 함수ì—ë§Œ ê´€ì‹¬ì´ ìžˆê³  ê·¸ 함수가 어떻게 호출ë˜ëŠ”ì§€ë§Œ 알고 싶다면, caller filter 를 사용하면 ë  ê²ƒì´ë‹¤. ê·¸ 함수를 마지막(leaf) 노드로 만들고, ê·¸ í•¨ìˆ˜ì˜ ëª¨ë“  부모 í•¨ìˆ˜ë“¤ì„ ê¸°ë¡í•œë‹¤. $ uftrace record -C b ./abc $ uftrace replay # DURATION TID FUNCTION [ 1234] | main() { [ 1234] | a() { 5.475 us [ 1234] | b(); 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ 위 예시ì—서, 호출 ê²½ë¡œì— ì—†ëŠ” í•¨ìˆ˜ë“¤ì„ ì¶œë ¥ë˜ì§€ 않았다. ë˜í•œ, 함수 `b()`ì˜ ìžì‹ í•¨ìˆ˜ì¸ í•¨ìˆ˜ `c()` ë˜í•œ 출력ë˜ì§€ 않았다. ë˜í•œ, `-D` 옵션으로 í•¨ìˆ˜ì˜ ì¤‘ì²© 깊ì´ì„ 제한할 ìˆ˜ë„ ìžˆë‹¤. $ uftrace record -D 3 ./abc $ uftrace replay # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { [ 1234] | a() { 5.475 us [ 1234] | b(); 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ 위 예시ì—서, uftrace 는 함수 호출 깊ì´ë¥¼ 최대 3 으로 제한하여 출력했기 때문ì—, 마지막 í•¨ìˆ˜ì¸ `c()`는 ìƒëžµë˜ì—ˆë‹¤. `-D` ì˜µì…˜ì€ `-F` 옵션과 함께 ì“°ì¼ ìˆ˜ 있다. 때로는, 오랜 시간 실행ë˜ëŠ” í•¨ìˆ˜ë“¤ì„ íŠ¹ë³„í•˜ê²Œ 관찰하는 ê²ƒì´ ìœ ìš©í•˜ë‹¤. ì´ëŠ” ìž‘ì€ (ì‹¤í–‰ì‹œê°„ì„ ê°€ì§€ëŠ”) 함수들 중ì—는 관심 대ìƒì´ 아닌 ê²ƒë“¤ì´ ë§Žê¸° 때문ì´ë‹¤. `-t`/`--time-filter` ì˜µì…˜ì€ ëª…ì‹œëœ ìž„ê³„ì‹œê°„ë³´ë‹¤ 오래 ì‹¤í–‰ëœ í•¨ìˆ˜ë“¤ë§Œ ë³¼ 수 있게 하는 시간 ê¸°ë°˜ì˜ í•„í„°ì´ë‹¤. 위 예시ì—서는, 사용ìžëŠ” 대부분 아래와 ê°™ì´ 5 마ì´í¬ë¡œ(us) ì´ˆ ì´ìƒ 걸려서 실행ë˜ëŠ” 함수를 ë³´ê³  ì‹¶ì–´í•  것ì´ë‹¤. $ uftrace record -t 5us ./abc $ uftrace replay # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { [ 1234] | a() { 5.475 us [ 1234] | b(); 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ í•„í„°ë§ëœ í•¨ìˆ˜ì— íŠ¸ë¦¬ê±°ë¥¼ 설정할 ìˆ˜ë„ ìžˆë‹¤. ë” ë§Žì€ ì •ë³´ëŠ” *TRIGGERS* 항목ì—서 참고할 수 있다. ì»¤ë„ í•¨ìˆ˜ ì¶”ì ì„ 설정하면, `@kernel` ì‹ë³„ìžë¥¼ 통해 ì»¤ë„ í•¨ìˆ˜ì— ëŒ€í•œ 필터를 ì ìš©í•  수 있다. ì´í•˜ 예시ì—서는 모든 ì‚¬ìš©ìž í•¨ìˆ˜ì™€ (ì»¤ë„ ë ˆë²¨ì˜) page fault í•¸ë“¤ëŸ¬ë“¤ì„ ë³´ì—¬ì¤€ë‹¤. $ sudo uftrace -k -F '.*page_fault@kernel' ./abc # DURATION TID FUNCTION [14721] | main() { 7.713 us [14721] | __do_page_fault(); 6.600 us [14721] | __do_page_fault(); 6.544 us [14721] | __do_page_fault(); [14721] | a() { [14721] | b() { [14721] | c() { 0.860 us [14721] | getpid(); 2.346 us [14721] | } /* c */ 2.956 us [14721] | } /* b */ 3.340 us [14721] | } /* a */ 79.086 us [14721] | } /* main */ TRIGGERS ======== uftrace 는 (í•„í„°ê°€ 있든 없든) ì„ íƒëœ 함수 호출과 시그ë„ì— ëŒ€í•œ 트리거 ë™ìž‘ì„ ì§€ì›í•œë‹¤. 현재 ì§€ì›ë˜ëŠ” 트리거와 ì‚¬ì–‘ì— ëŒ€í•œ BNF 는 다ìŒê³¼ 같다. := "@" := | "," := "depth=" | "backtrace" | "trace" | "trace_on" | "trace_off" | "recover" | "color=" | "time=" | "read=" | "finish" | "filter" | "notrace" := [ ] := "ns" | "nsec" | "us" | "usec" | "ms" | "msec" | "s" | "sec" | "m" | "min" := "proc/statm" | "page-fault" | "pmu-cycle" | "pmu-cache" | "pmu-branch" `depth` 트리거는 함수를 실행하는 ë™ì•ˆ í•„í„°ì˜ ê¹Šì´ë¥¼ 변경한다. 다양한 í•¨ìˆ˜ì— ëŒ€í•´ 서로 다른 í•„í„° 깊ì´ë¥¼ 설정할 수 있다. 그리고 `backtrace` 트리거는 replay 시 ìŠ¤íƒ ë°±íŠ¸ë ˆì´ìŠ¤ë¥¼ 출력한다. `color` 트리거는 replay 명령어ì—서 색ìƒì„ 변경한다. ì§€ì›ë˜ëŠ” 색ìƒì€ `red`, `green`, `blue`, `yellow`, `magenta`, `cyan`, `bold`, `gray` ê°€ 있다. ë‹¤ìŒ ì˜ˆì œëŠ” 트리거 ìž‘ë™ ë°©ì‹ì„ 보여준다. ì „ì—­ í•„í„° 깊ì´ê°€ 5 로 설정ë˜ì–´ 있지만 `b()` í•¨ìˆ˜ì— `depth` 트리거를 설정하여 `b()` 아래 함수는 ë³´ì´ì§€ 않게ëœë‹¤. $ uftrace record -D 5 -T 'b@depth=1' ./abc $ uftrace replay # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { [ 1234] | a() { 5.475 us [ 1234] | b(); 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ `backtrace` 트리거는 replay ì—서만 사용할 수 있다. `trace_on`ê³¼ `trace_off` 트리거는 uftrace ê°€ ì§€ì •ëœ í•¨ìˆ˜ë¥¼ 기ë¡í• ì§€ 여부를 관리한다. ë˜í•œ, `_` ë¬¸ìž ì—†ì´ `traceon` ê³¼ `traceoff` ë¡œë„ ì‚¬ìš©í•  수 있다. `recover` 트리거는 프로세스가 호출 스íƒ(call stack)ì— ì§ì ‘ 접근하는 ì¼ë¶€ ê²½ìš°ì— ì‚¬ìš©ëœë‹¤. 예를들어, v8 ìžë°”스í¬ë¦½íЏ ì—”ì§„ì„ ì¶”ì í•˜ëŠ” ë™ì•ˆ 가비지 컬렉션 단계ì—서 세그멘테ì´ì…˜ í´íЏ 문제가 ë°œìƒëœë‹¤ë©´ ì´ëŠ” v8 ì´ (변경ëœ) 반환 주소를 통해 컴파ì¼ëœ 코드 ê°ì²´ì— 접근하려 하기 때문ì´ë‹¤. `recover` 트리거는 함수 ì§„ìž…ì ì— ì›ëž˜ 반환 주소를 ë³µì›í•˜ê³  함수 반환ì ì—서 다시 uftrace ì—서 조작한 반환 주소로 재설정한다. (특히 v8 ìžë°”스í¬ë¦½íЏ 엔진 사례ì—서 `ExitFrame::Iterate` 함수와 ê°™ì´ ë¬¸ì œë¥¼ ë°œìƒì‹œí‚¤ëŠ” ìƒí™©ì—서 `recover` 트리거를 사용하면 문제를 í•´ê²°í•  수 있다.) `time` 트리거는 함수를 실행하는 ë™ì•ˆ 시간 í•„í„°(time-filter) ì„¤ì •ì„ ë³€ê²½í•œë‹¤. 다른 í•¨ìˆ˜ë“¤ì— ëŒ€í•´ì„œ 서로 다른 시간 필터를 ì ìš©í•  ë–„ 사용할 수 있다. `read` 트리거는 실행 ì‹œì— ì¼ë¶€ 정보를 ì½ì„ 수 있다. 결과는 주어진 í•¨ìˆ˜ì˜ ì‹œìž‘ê³¼ ëì— (내장) ì´ë²¤íŠ¸ì˜ í˜•íƒœë¡œ 기ë¡ëœë‹¤. 현재 다ìŒê³¼ ê°™ì€ ì´ë²¤íŠ¸ê°€ ì§€ì›ë˜ê³  있다. * "proc/statm": /proc ìœ¼ë¡œë¶€í„°ì˜ ë©”ëª¨ë¦¬ 사용량 ì •ë³´ * "page-fault": getrusage(2)를 사용한 페ì´ì§€ í´íЏ(page fault) 횟수 * "pmu-cycle": perf-event ì‹œìŠ¤í…œì½œì„ í†µí•œ cpu í´ëŸ­ 사ì´í´ ë° ëª…ë ¹ì–´ 실행 횟수 * "pmu-cache": perf-event ì‹œìŠ¤í…œì½œì„ í†µí•œ ìºì‹œ 참조(reference) ë° ì‹¤íŒ¨(miss) * "pmu-branch": Perf-event ì‹œìŠ¤í…œì½œì„ ì‚¬ìš©í•œ 분기예측(branch prediction) ë° ì‹¤íŒ¨(miss) 결과는 아래와 ê°™ì´ ì£¼ì„ì˜ í˜•íƒœë¡œ ì´ë²¤íЏ ì •ë³´ê°€ 출력ëœë‹¤. $ uftrace record -T a@read=proc/statm ./abc $ uftrace replay # DURATION TID FUNCTION [ 1234] | main() { [ 1234] | a() { [ 1234] | /* read:proc/statm (size=6808KB, rss=776KB, shared=712KB) */ [ 1234] | b() { [ 1234] | c() { 1.448 us [ 1234] | getpid(); 10.270 us [ 1234] | } /* c */ 11.250 us [ 1234] | } /* b */ [ 1234] | /* diff:proc/statm (size=+4KB, rss=+0KB, shared=+0KB) */ 18.380 us [ 1234] | } /* a */ 19.537 us [ 1234] | } /* main */ `finish` 트리거는 기ë¡(record)ì„ ì¢…ë£Œí•  ë–„ 사용한다. ë°ëª¬ê³¼ ê°™ì´ ì¢…ë£Œë˜ì§€ 않는 프로세스를 ì¶”ì í•˜ëŠ” ë° ìœ ìš©í•  수 있다. `filter` 와 `notrace` 트리거는 ê°ê° `-F`/`--filter` 와 `-N` /`--notrace` ê°™ì€ íš¨ê³¼ê°€ 있다. 트리거는 현재 ì»¤ë„ í•¨ìˆ˜ë¥¼ 제외한 ì‚¬ìš©ìž í•¨ìˆ˜ë“¤ì—서만 ë™ìž‘한다. 트리거는 시그ë„ë¡œë„ ì‚¬ìš©í•  수 있다. ì´ëŠ” `signal` íŠ¸ë¦¬ê±°ì— ì˜í•´ 수행ë˜ë©° 함수 트리거와 비슷하지만 현재는 "trace_on", "trace_off" ë° "finish" 트리거만 ì§€ì›ë˜ê³  있다. $ uftrace record --signal 'SIGUSR1@finish' ./some-daemon ARGUMENTS ========= uftrace 는 í•¨ìˆ˜ì˜ ì¸ìžì™€ ë°˜í™˜ê°’ì„ ê°ê° `-A`/`\--argument` 와 `-R`/`\--retval` 로 기ë¡í•  수 있다. ì´ì— 대한 문법체계는 트리거와 매우 유사하다. := [ "@" ] := | "," := ( | | ) := "arg" N [ "/" [ ] ] [ "%" ( | ) ] := "fparg" N [ "/" ( | "80" ) ] [ "%" ( | ) ] := "retval" [ "/" [ ] ] := "d" | "i" | "u" | "x" | "s" | "c" | "f" | "S" | "p" := "8" | "16" | "32" | "64" := # "rdi", "xmm0", "r0", ... := "stack" [ "+" ] `-A`/`\--argument` ì˜µì…˜ì€ symbol ì˜ ì´ë¦„ê³¼ ê·¸ê²ƒì˜ spec ë“¤ì„ ì„ íƒì ìœ¼ë¡œ 받는다. spec ì€ argN 으로 시작ë˜ëŠ”ë° ì—¬ê¸°ì„œ N ì€ ì¸ìžì˜ ì¸ë±ìŠ¤ê°’ì´ë‹¤. ì¸ë±ìŠ¤ëŠ” 1 부터 시작ë˜ë©°, 순서는 함수호출규약(calling convention)ì˜ ì¸ìž 전달 순서와 대ì‘ëœë‹¤. ì¸ìžì˜ ì¸ë±ìŠ¤ëŠ” 정수형 (í˜¹ì€ í¬ì¸í„°í˜•) ê³¼ ë¶€ë™ì†Œìˆ˜ì í˜• ê°ê° 따로 관리ëœë‹¤ëŠ” ì , 그리고 ì´ë“¤ì€ í•¨ìˆ˜í˜¸ì¶œê·œì•½ì— ë”°ë¼ ê°ê¸° ê°„ì„­ì„ ì¼ìœ¼í‚¬ 수 있다는 ì ì— 유ì˜í•˜ë¼. argN ì€ ì •ìˆ˜í˜• ì¸ìžë¥¼, fpargN ì€ ë¶€ë™ì†Œìˆ˜ì í˜• ì¸ìžë¥¼ 위한 표기ì´ë‹¤. "d" í˜•ì‹ í˜¹ì€ ì•„ë¬´ 형ì‹ë„ 주지 ì•Šì„ ê²½ìš°, uftrace 는 ì •ìˆ˜í˜•ì€ 'long int'형으로 간주하고 소수ì í˜•ì— ëŒ€í•´ì„œëŠ” 'double'형으로 간주한다. "i" 형ì‹ì€ signed 정수형으로, "u" 형ì‹ì€ unsinged 으로 출력한다. ë‘ í˜•ì‹ ëª¨ë‘ 10 진수가 출력ë˜ëŠ” 한편 "x" 형ì‹ì€ 16 진수로 출력ë˜ê²Œ 한다. "s" 는 null ì„ ì œì™¸í•œ 문ìžì—´ ì¶œë ¥ì„ ìœ„í•œ 형ì‹ì´ê³ , "c" 는 ë‹¨ì¼ ë¬¸ìžë¥¼ 위한 형ì‹ì´ë‹¤. "f" 형ì‹ì€ ë¶€ë™ ì†Œìˆ˜ì ì„ 출력하는ë°, (ì¼ë°˜ì ìœ¼ë¡œ) 반환값ì—서만 ì˜ë¯¸ë¥¼ 가진다. fpargN ì€ í•­ìƒ ì†Œìˆ˜ì  ë°©ì‹ì´ê¸° ë•Œë¬¸ì— ì–´ë–¤ í˜•ì‹ í•„ë“œë„ ì—†ìŒì— 유ì˜í•˜ë¼. "S" 형ì‹ì€ std::string ì„ ìœ„í•œ 형ì‹ì´ì§€ë§Œ, ì•„ì§ê¹Œì§€ëŠ” libstdc++ ë¼ì´ë¸ŒëŸ¬ë¦¬ë§Œ ì§€ì›ê°€ëŠ¥í•˜ë‹¤. 마지막으로, "p" 형ì‹ì€ 함수í¬ì¸í„° 형ì‹ì´ë‹¤. ì¶”ì  ëŒ€ìƒì˜ 주소가 기ë¡ë˜ë©´, 언제나 함수 ì´ë¦„으로 출력ëœë‹¤. 문ìžì—´ íƒ€ìž…ì˜ ì¸ìžë¥¼ 사용할 때 (í¬ì¸í„°) ê°’ì´ ìœ íš¨í•˜ì§€ ì•Šì„ ê²½ìš° í”„ë¡œê·¸ëž¨ì„ ë¹„ì •ìƒ ì¢…ë£Œì‹œí‚¬ 수 있ìŒì— 주ì˜í•˜ë¼. 사실 uftrace 는 유효한 프로세스 주소 ê³µê°„ì˜ ë²”ìœ„ë¥¼ ì§€ì†ì ìœ¼ë¡œ ì ê²€í•˜ë ¤ê³  노력하지만, ëª‡ëª‡ì˜ ê²½ìš°ë“¤ì„ ë†“ì¹  수 있다. ë˜í•œ 특정 ë ˆì§€ìŠ¤í„°ì˜ ì´ë¦„ì´ë‚˜ ìŠ¤íƒ ì˜¤í”„ì…‹(offset)ìœ¼ë¡œë„ ì¸ìžë¡œ 명시할 수 있다. (ë°˜í™˜ê°’ì€ ë¶ˆê°€í•˜ë‹¤) 아래는 ì¸ìžë¡œ ì‚¬ìš©ë  ìˆ˜ 있는 레지스터 ì´ë¦„들ì´ë‹¤. * x86: rdi, rsi, rdx, rcx, r8, r9 (for integer), xmm[0-7] (for floating-point) * arm: r[0-3] (for integer), s[0-15] or d[0-7] (for floating-point) 예시는 아래와 같다. $ uftrace record -A main@arg1/x -R main@retval/i32 ./abc $ uftrace replay # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main(0x1) { [ 1234] | a() { [ 1234] | b() { 3.880 us [ 1234] | c(); 5.475 us [ 1234] | } /* b */ 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } = 0; /* main */ $ uftrace record -A puts@arg1/s -R puts@retval ./hello Hello world $ uftrace replay # DURATION TID FUNCTION 1.457 us [21534] | __monstartup(); 0.997 us [21534] | __cxa_atexit(); [21534] | main() { 7.226 us [21534] | puts("Hello world") = 12; 8.708 us [21534] | } /* main */ ì´ ì¸ìžë“¤ê³¼ ë°˜í™˜ê°’ë“¤ì€ ì‹¤í–‰íŒŒì¼ì´ `-pg` 옵션으로 빌드ë˜ì—ˆì„ 때ì—ë§Œ 기ë¡ë¨ì— 유ì˜í•˜ë¼. `-finstrument-functions` 로 만들어진 실행파ì¼ë“¤ì€ ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì„ ì œì™¸í•˜ê³ ëŠ” 무시ëœë‹¤. ì¸ìžì™€ ë°˜í™˜ê°’ì˜ ê¸°ë¡ì€ ì•„ì§ê¹Œì§„ ì‚¬ìš©ìž ì •ì˜ í•¨ìˆ˜ì—서만 ë™ìž‘한다. ë§Œì¼ í”„ë¡œê·¸ëž¨ì´ DWARF 와 ê°™ì€ ë””ë²„ê·¸ 정보와 함께 빌드ë˜ì—ˆë‹¤ë©´, (libdw 와 함께 빌드ëœ) uftrace 는 ìžë™ìœ¼ë¡œ ì¸ìžë“¤ì˜ 갯수와 ìžë£Œí˜•ë“¤ì„ ì‹ë³„í•  수 있다. ë˜í•œ 디버그 정보를 사용하지 않ë”ë¼ë„, 몇몇 잘 알려진 ë¼ì´ë¸ŒëŸ¬ë¦¬ í•¨ìˆ˜ë“¤ì˜ ì¸ìžë“¤ê³¼ ë°˜í™˜ê°’ì€ ê¸°ë³¸ì ìœ¼ë¡œ 제공ëœë‹¤. ì´ ê²½ìš° 사용ìžëŠ” ì¸ìžë“¤ì˜ spec ê³¼ ë°˜í™˜ê°’ì„ ìˆ˜ë™ì ìœ¼ë¡œ 명시할 필요가 ì—†ì´ í•¨ìˆ˜ì˜ ì´ë¦„ (ë˜ëŠ” 패턴) ë§Œ 주는 ê²ƒìœ¼ë¡œë„ ì¶©ë¶„í•˜ë‹¤. 사실, 명시ì ìœ¼ë¡œ argspec ì„ ì§€ì •í•˜ë©´ ìžë™ argspec ì„ í‘œì‹œë˜ì§€ 않게 한다. 예를 들어, ìœ„ì˜ ì˜ˆì‹œëŠ” 아래와 ê°™ì´ ìž‘ì„±í•  수 있다. $ uftrace record -A . -R main ./hello Hello world $ uftrace replay -F main # DURATION TID FUNCTION [ 18948] | main(1, 0x7ffeeb7590b8) { 7.183 us [ 18948] | puts("Hello world"); 9.832 us [ 18948] | } = 0; /* main */ ì¸ìž 패턴 (".")ì€ ëª¨ë“  문ìžì— 대ì‘ë˜ê¸° ë•Œë¬¸ì— ëª¨ë“  (ì§€ì›ë˜ëŠ”) í•¨ìˆ˜ë“¤ì´ ê¸°ë¡ë˜ì—ˆìŒì— 유ì˜í•˜ë¼. 위ì—서는 "main" í•¨ìˆ˜ì˜ ë‘ ì¸ìžë“¤ê³¼ "puts" ì˜ í•œ 문ìžì—´ ì¸ìžë¥¼ 보여준다. ë§Œì¼ (ì§€ì›ë˜ëŠ”) í•¨ìˆ˜ì˜ ëª¨ë“  ì¸ìžë“¤ê³¼ ë°˜í™˜ê°’ë“¤ì„ ë³´ê³  싶다면, `-a`/`\--auto-args` ì˜µì…˜ì„ ì‚¬ìš©í•˜ë¼. DYNAMIC TRACING =============== uftrace 는 x86_64, AArch64 í™˜ê²½ì˜ ëŸ°íƒ€ìž„ (정확하게는, 로드 타임) ì—서 ë™ì ì¶”ì (dynamic tracing)ì´ ê°€ëŠ¥í•˜ë‹¤. 함수를 기ë¡í•˜ê¸° ì „ì—, 보통 í”„ë¡œê·¸ëž¨ì„ `-pg` (í˜¹ì€ `-finstrument-functions`으로) 빌드해야 하고, 그렇게 ëœë‹¤ë©´ 모든 í•¨ìˆ˜ë“¤ì´ `mcount()`를 호출하기 ë•Œë¬¸ì— ì–´ëŠ ì •ë„ ì„±ëŠ¥ì— ì˜í–¥ì„ 받게 ë  ê²ƒì´ë‹¤. ë™ì ì¶”ì ì„ í•  때, `-P`/`--patch` ì˜µì…˜ì„ í†µí•´ 특정 í•¨ìˆ˜ë§Œì„ ì¶”ì í•  수 있다. capstone 디스어셈블리 ì—”ì§„ì„ ì‚¬ìš©í•œë‹¤ë©´ 위 ì˜µì…˜ì„ ì§€ì •í•´ì„œ í”„ë¡œê·¸ëž¨ì„ (재)컴파ì¼í•  필요가 없다. ì´ì œ uftrace 는 ëª…ë ¹ì–´ë“¤ì„ ë¶„ì„í•  수 있게 ë˜ê³  (만약 가능하다면) ê·¸ ëª…ë ¹ì–´ë“¤ì„ ë‹¤ë¥¸ ê³³ì— ë³µì‚¬í•˜ì—¬ `mcount()` í•¨ìˆ˜ë“¤ì„ í˜¸ì¶œí•˜ì—¬ uftrace 로 ì¶”ì í•  수 있게 ë°”ì´ë„ˆë¦¬ë¥¼ ì¡°ìž‘ í•  수 있다. ê·¸ ì´í›„ ì œì–´ê¶Œì€ ë³µì‚¬ëœ ëª…ë ¹ì–´ë¡œ 넘어가게 ë˜ê³ , ê·¸ 다ìŒì—야 ë‚¨ì€ ëª…ë ¹ì–´ë“¤ë¡œ 반환하게 ëœë‹¤. capstone ì„ ì‚¬ìš©í•  수 없다면, í”„ë¡œê·¸ëž¨ì„ ë¹Œë“œí•  때 몇몇 컴파ì¼ëŸ¬ (gcc) ì˜µì…˜ë“¤ì„ ì¶”ê°€í•´ì•¼ í•  것ì´ë‹¤. gcc 5.1 버전 ì´ìƒë¶€í„°ëŠ” `-mfentry`와 `-mnop-mcount` ì˜µì…˜ì„ ì œê³µí•˜ëŠ”ë° ì´ ì˜µì…˜ë“¤ì€ í•¨ìˆ˜ 맨 ì•žì— `mcount()` 와 ê°™ì€ í•¨ìˆ˜ ì¶”ì ì„ 위한 코드를 추가하고 ê·¸ 명령어를 NOP 으로 변환한다. 그렇게 ë˜ë©´ ì¼ë°˜ì ì¸ ì¡°ê±´ì—서 실행할 때ì—는 성능 ìƒì˜ 오버헤드가 ê±°ì˜ ì—†ì–´ì§ˆ 것ì´ë‹¤. uftrace 는 `-P` ì˜µì…˜ì„ ì´ìš©í•˜ì—¬ ì„ íƒì ìœ¼ë¡œ `mcount()` 함수를 호출할 수 있ë„ë¡ ì „í™˜í•  수 있다. uftrace 를 ì•„ëž˜ì˜ ì˜ˆì œì—서 í‰ì†Œì²˜ëŸ¼ 사용할때ì—는 ì—러 메세지를 보여준다. ê·¸ ì´ìœ ëŠ” ë°”ì´ë„ˆë¦¬ê°€ ì–´ë–¤ `mcount()` 와 ê°™ì€ í•¨ìˆ˜ ì¶”ì ì„ 위한 ì½”ë“œë„ í˜¸ì¶œí•˜ì§€ 않기 때문ì´ë‹¤. $ gcc -o abc -pg -mfentry -mnop-mcount tests/s-abc.c $ uftrace abc uftrace: /home/namhyung/project/uftrace/cmd-record.c:1305:check_binary ERROR: Can't find 'mcount' symbol in the 'abc'. It seems not to be compiled with -pg or -finstrument-functions flag which generates traceable code. Please check your binary file. 하지만 `-P a` 패치 ì˜µì…˜ì„ ì ìš©í•œë‹¤ë©´, ë™ì ìœ¼ë¡œ `a()` í•¨ìˆ˜ë§Œì„ ì¶”ì í•  것ì´ë‹¤. $ uftrace record --no-libcall -P a abc $ uftrace replay # DURATION TID FUNCTION 0.923 us [19379] | a(); 추가로, '.'ì„ ì´ìš©í•´ (globì€, '*') `P`옵션과 함께 정규표현ì‹ìœ¼ë¡œ ì“°ì¸ ë¬¸ìžì— 대해 하나ë¼ë„ 매칭ë˜ëŠ” 모든 í•¨ìˆ˜ë“¤ì— ëŒ€í•´ì„œë„ ì ìš©ì‹œí‚¬ 수 있다. $ uftrace record --no-libcall -P . abc $ uftrace replay # DURATION TID FUNCTION [19387] | main() { [19387] | a() { [19387] | b() { 0.940 us [19387] | c(); 2.030 us [19387] | } /* b */ 2.451 us [19387] | } /* a */ 3.289 us [19387] | } /* main */ `-U` ì˜µì…˜ì€ `-P` 옵션과 반대로 작용한다. ì´ ì˜µì…˜ë“¤ì´ ê°™ì´ ì“°ì´ë©´ ë‚˜ì¤‘ì— ì“°ì—¬ì§„ ì˜µì…˜ì´ ê·¸ ì´ì „ì˜ ì˜µì…˜ì„ ëŒ€ì²´í•˜ëŠ” 효과를 갖는다. 예를 들면 만약 ë‹¹ì‹ ì´ 'a' 를 제외한 모든 함수를 ì¶”ì í•˜ê³  ì‹¶ì€ ê²½ìš°ëŠ” 아래와 ê°™ì´ ì‚¬ìš©í•  수 있다. $ uftrace record --no-libcall -P . -U a abc $ uftrace replay # DURATION TID FUNCTION [19390] | main() { [19390] | b() { 0.983 us [19390] | c(); 2.012 us [19390] | } /* b */ 3.373 us [19390] | } /* main */ 여기서 순서가 ì¤‘ìš”í•œë° ë§Œì•½ 순서를 `-U a -P .` 와 ê°™ì´ ì‚¬ìš©í•˜ë©´ 모든 í•¨ìˆ˜ë“¤ì„ ê¸°ë¡í•˜ëŠ” 결과를 ë³´ì´ëŠ”ë° ì´ëŠ” `-P .` ê°€ 다른 ëª¨ë“ ê²ƒì— ìš°ì„ í•´ 작용해서ì´ë‹¤. 추가ì ìœ¼ë¡œ, `-U` ì˜µì…˜ì€ `-pg`(그리고 `-mfentry ë˜ëŠ” `-mrecord-mcount`)로 컴파ì¼ëœ ë°”ì´ë„ˆë¦¬ì— ëŒ€í•´ì„œë„ ì‚¬ìš© 가능하다. ì´ ê¸°ëŠ¥ì— ëŒ€í•´ì„œëŠ” capstone ì´ ëª…ë ¹ì–´ë¥¼ ë¶„ì„í•  수 있어야 한다. Clang/LLVM 4.0ì€ [X-ray](http://llvm.org/docs/XRay.html)ë¼ëŠ” ê¸°ìˆ ì„ ì œê³µí•œë‹¤. ì´ëŠ” `gcc -mfentry -mnop-mcount` 와 `-finstrument-functions` 를 결합한 ê²ƒê³¼ë„ ìœ ì‚¬í•˜ë‹¤. uftrace는 `X-ray`로 ë¹Œë“œëœ ì‹¤í–‰íŒŒì¼ì— ëŒ€í•´ì„œë„ ë™ì ì¶”ì ì„ ì§€ì›í•œë‹¤. 예를 들어, ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì„ clang 으로 ì•„ëž˜ì˜ ì˜µì…˜ìœ¼ë¡œ 빌드할 ìˆ˜ë„ ìžˆì§€ë§Œ, 그와 ë™ì¼í•˜ê²Œ ë™ì ì¶”ì ì„ 위해 아래와 ê°™ì´ `-P` ì˜µì…˜ì„ ì‚¬ìš©í•  ìˆ˜ë„ ìžˆì„ ê²ƒì´ë‹¤. $ clang -fxray-instrument -fxray-instruction-threshold=1 -o abc-xray tests/s-abc.c $ uftrace record -P main abc-xray $ uftrace replay # DURATION TID FUNCTION [11093] | main() { 1.659 us [11093] | getpid(); 5.963 us [11093] | } /* main */ $ uftrace record -P . abc-xray $ uftrace replay # DURATION TID FUNCTION [11098] | main() { [11098] | a() { [11098] | b() { [11098] | c() { 0.753 us [11098] | getpid(); 1.430 us [11098] | } /* c */ 1.915 us [11098] | } /* b */ 2.405 us [11098] | } /* a */ 3.005 us [11098] | } /* main */ SCRIPT EXECUTION ================ uftrace 는 í•¨ìˆ˜ì˜ ì§„ìž…ê³¼ 반환 시ì ì— 스í¬ë¦½íЏ ì‹¤í–‰ì´ ê°€ëŠ¥í•˜ë‹¤. ì§€ì›ë˜ëŠ” 스í¬ë¦½íŠ¸ëŠ” ì•„ì§ê¹Œì§€ëŠ” Python 2.7 ë¿ì´ë‹¤. 사용ìžëŠ” 네 ê°œì˜ í•¨ìˆ˜ë¥¼ 작성할 수 있다. 'uftrace_entry' 와 'uftracce_exit' ì€ ê° í•¨ìˆ˜ì˜ ì§„ìž…ì‹œì ê³¼ 반환시ì ì— í•­ìƒ ì‹¤í–‰ëœë‹¤. 하지만 'uftrace_begin' ê³¼ 'uftrace_end' 는 ë¶„ì„ ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì´ ì´ˆê¸°í™”ë˜ê³  종료ë ë•Œ 한 번씩만 실행ëœë‹¤. $ cat scripts/simple.py def uftrace_begin(ctx): print("program begins...") def uftrace_entry(ctx): func = ctx["name"] print("entry : " + func + "()") def uftrace_exit(ctx): func = ctx["name"] print("exit : " + func + "()") def uftrace_end(): print("program is finished") 위 스í¬ë¦½íŠ¸ëŠ” 아래와 ê°™ì´ ê¸°ë¡ëœ 시간 순으로 ì‹¤í–‰ë  ìˆ˜ 있다: $ uftrace -S scripts/simple.py -F main tests/t-abc program begins... entry : main() entry : a() entry : b() entry : c() entry : getpid() exit : getpid() exit : c() exit : b() exit : a() exit : main() program is finished # DURATION TID FUNCTION [10929] | main() { [10929] | a() { [10929] | b() { [10929] | c() { 4.293 us [10929] | getpid(); 19.017 us [10929] | } /* c */ 27.710 us [10929] | } /* b */ 37.007 us [10929] | } /* a */ 55.260 us [10929] | } /* main */ 'ctx' 변수는 ì•„ëž˜ì˜ ì •ë³´ë¥¼ í¬í•¨í•˜ëŠ” 사전타입(dictionary type)ì˜ ë³€ìˆ˜ì´ë‹¤. /* context information passed to uftrace_entry(ctx) and uftrace_exit(ctx) */ script_context = { int tid; int depth; long timestamp; long duration; # exit only long address; string name; list args; # entry only (if available) value retval; # exit only (if available) }; /* context information passed to uftrace_begin(ctx) */ script_context = { bool record; # True if it runs at record time, otherwise False string version; # uftrace version info list cmds; # execution commands }; 'script_context' ì— ìžˆëŠ” ê° í•­ëª©ë“¤ì€ ìŠ¤í¬ë¦½íЏ ë‚´ì—서 ì½ì„ 수 있다. 스í¬ë¦½íŒ…ì— ëŒ€í•œ ìžì„¸í•œ ì‚¬í•­ì€ `uftrace-script`(1)를 참고할 수 있다. WATCH POINT =========== uftrace ì˜ watch point 는 특정 ê°’ì˜ ë³€ê²½ì‚¬í•­ì„ ì¶œë ¥í•œë‹¤. ê°œë…ì ìœ¼ë¡œëŠ” ì¼ë°˜ì ì¸ ë””ë²„ê±°ì˜ watch point 와 같지만, í•¨ìˆ˜ì˜ ì§„ìž…ê³¼ 종료ì—ë§Œ ì ìš©ë˜ê¸° ë•Œë¬¸ì— ëª‡ëª‡ ë³€ê²½ì‚¬í•­ë“¤ì€ ë†“ì¹  ìˆ˜ë„ ìžˆë‹¤. ì•„ì§ê¹Œì§€ëŠ”, ì•„ëž˜ì˜ watch point ë“¤ë§Œì´ ì§€ì›ëœë‹¤. * "cpu" : 현재 ìž‘ì—…ì„ ìˆ˜í–‰í•˜ëŠ” cpu 번호 트리거를 ì½ì„ 때처럼, 결과는 다ìŒê³¼ ê°™ì´ ì£¼ì„ í˜•ì‹ì˜ ì´ë²¤íŠ¸ë¡œ 출력ëœë‹¤. $ uftrace -W cpu tests/t-abc # DURATION TID FUNCTION [ 19060] | main() { [ 19060] | /* watch:cpu (cpu=8) */ [ 19060] | a() { [ 19060] | b() { [ 19060] | c() { 2.365 us [ 19060] | getpid(); 8.002 us [ 19060] | } /* c */ 8.690 us [ 19060] | } /* b */ 9.350 us [ 19060] | } /* a */ 12.479 us [ 19060] | } /* main */ 함께 보기 ========= `uftrace`(1), `uftrace-replay`(1), `uftrace-report`(1), `uftrace-recv`(1), `uftrace-graph`(1), `uftrace-script`(1), `uftrace-tui`(1) ë²ˆì—­ìž ====== 강민철 , ê¹€í™ê·œ uftrace-0.9.4/doc/ko/uftrace-recv.md000066400000000000000000000046341362052523300172530ustar00rootroot00000000000000% UFTRACE-RECV(1) Uftrace User Manuals % Namhyung Kim % Sep, 2018 ì´ë¦„ ==== uftrace-recv - 네트워í¬ë¥¼ 통해 ë°ì´í„°ë¥¼ 수신하고 파ì¼ë¡œ 저장한다. 사용법 ======== uftrace recv [*옵션*] 설명 =========== uftrace recv 명령어는 네트워í¬ë¥¼ 통해 ë°ì´í„°ë¥¼ 수신하고 파ì¼ë¡œ 저장한다. ë°ì´í„°ëŠ” `uftrace-record` 명령어와 -H/\--host ì˜µì…˜ì„ ì‚¬ìš©í•˜ì—¬ 전송ëœë‹¤. 옵션 ======= -d *DATA*, \--data=*DATA* : ìˆ˜ì‹ ëœ ë°ì´í„°ë¥¼ 저장할 디렉터리 ì´ë¦„ì„ ì§€ì •í•œë‹¤. \--port=*PORT* : 기본 í¬íЏ(8090) 대신 사용할 í¬íЏ 번호를 지정한다. \--run-cmd=*COMMAND* : ë°ì´í„°ë¥¼ 수신한 다ìŒì— 주어진(쉘) 명령어를 바로 실행한다. 예를 들면, ìˆ˜ì‹ ëœ ë°ì´í„°ì— 대해 uftrace replay를 실행할 수 있다. 예제 ======= uftrace recv ëª…ë ¹ì€ `uftrace-record` 명령어로 ë°ì´í„°ë¥¼ 전송하기 ì „ì— ë¨¼ì € 실행ë˜ì–´ì•¼ 한다. # 호스트 $ uftrace recv -d recv_data --port 1234 ìœ„ì˜ ëª…ë ¹ì–´ëŠ” `-d` ì˜µì…˜ì„ ì‚¬ìš©í•˜ì—¬ ë°ì´í„°ë¥¼ 저장할 디렉터리 ì´ë¦„ì„ ì§€ì •í•˜ê³  í¬íЏ `1234`를 사용하여 ë°ì´í„° ìˆ˜ì‹ ì„ ëŒ€ê¸°í•œë‹¤. # í´ë¼ì´ì–¸íЏ : $ uftrace record -H localhost -d example_data --port 1234 example ìœ„ì˜ ëª…ë ¹ì–´ëŠ” `example`í”„ë¡œê·¸ëž¨ì˜ ë°ì´í„°ë¥¼ 기ë¡í•œ ë‹¤ìŒ `-d` ì˜µì…˜ì„ ì‚¬ìš©í•˜ì—¬ `example_data` ë””ë ‰í„°ë¦¬ì— ë°ì´í„°ë¥¼ 저장하고 `-H` ì˜µì…˜ì„ ì‚¬ìš©í•˜ì—¬ 수신할 호스트를 설정하고 전송한다. 최종ì ìœ¼ë¡œ, ìœ„ì˜ ëª…ë ¹ì–´ì˜ í˜¸ìŠ¤íŠ¸ëŠ” `localhost`ì´ê³  í¬íŠ¸ë²ˆí˜¸ëŠ” `1234`ì´ë©°, í´ë¼ì´ì–¸íЏì—서 호스트로 ì „ì†¡ë  ë°ì´í„°ì˜ 디렉터리 ì´ë¦„ì€ `example_data`ì´ ëœë‹¤. # HOST : Check received data $ uftrace replay -d recv_data/example_data # DURATION TID FUNCTION [17308] | main() { [17308] | a() { [17308] | b() { [17308] | c() { 1.058 us [17308] | getpid(); 4.356 us [17308] | } /* c */ 4.664 us [17308] | } /* b */ 4.845 us [17308] | } /* a */ 5.076 us [17308] | } /* main */ 호스트ì—서 지정한 `recv_data` ë””ë ‰í„°ë¦¬ì˜ í•˜ìœ„ 디렉터리ì—서 `example_data` ë°ì´í„°ë¥¼ ì°¾ì„ ìˆ˜ 있다. 함께 보기 ======== `uftrace`(1), `uftrace-record`(1) ë²ˆì—­ìž ======== ê¹€ê´€ì˜ uftrace-0.9.4/doc/ko/uftrace-replay.md000066400000000000000000000371601362052523300176100ustar00rootroot00000000000000% UFTRACE-REPLAY(1) Uftrace User Manuals % Namhyung Kim % Sep, 2018 ì´ë¦„ ==== uftrace-replay - 기ë¡ëœ ë°ì´í„°ì˜ 함수 실행 íë¦„ì„ ì¶œë ¥í•œë‹¤. 사용법 ====== uftrace replay [*options*] 설명 ==== ì´ ëª…ë ¹ì–´ëŠ” `uftrace-record`(1) 명령어를 통해 기ë¡ëœ ë°ì´í„°ì˜ 함수 실행 íë¦„ì„ ì¶œë ¥í•œë‹¤. 출력ë˜ëŠ” í•¨ìˆ˜ë“¤ì€ C 프로그램과 유사한 형ì‹ìœ¼ë¡œ 시간 순서대로 출력ëœë‹¤. REPLAY 옵션 ============== -f *FIELD*, \--output-fields=*FIELD* : 결과로 보여지는 필드를 사용ìžê°€ 지정한다. 가능한 값들로는 duration, tid, time, delta, elapsed, addr ê°€ 있다. 여러 필드를 갖는 경우 콤마로 구분ëœë‹¤. 모든 필드를 ê°ì¶”기 위한 (단ì¼í•˜ê²Œ 사용ë˜ëŠ”) 'none' 특수 필드가 있으며 기본ì ìœ¼ë¡œ 'duration,tid' ê°€ 사용ëœë‹¤. ìƒì„¸í•œ ì„¤ëª…ì€ *FIELDS* 를 참고한다. \--flat : C 와 ê°™ì´ í˜¸ì¶œ 깊ì´ê°€ ë³´ì´ëŠ” ë°©ì‹ì´ 아닌 í‰í‰í•œ(flat) 형ì‹ìœ¼ë¡œ 출력한다. ì´ ì˜µì…˜ì€ ì£¼ë¡œ 디버깅ì´ë‚˜ 테스트 ìš©ë„로 사용ëœë‹¤. \--column-view : ì—´(column) 별로 분리하여 ê°ê°ì˜ 태스í¬ë¥¼ 출력한다. 서로 다른 태스í¬ì—서 실행하는 í•¨ìˆ˜ì˜ êµ¬ë¶„ì„ ì‰½ê²Œí•œë‹¤. \--column-offset=*DEPTH* : `--column-view` ì˜µì…˜ì´ ì‚¬ìš©ë˜ì—ˆì„ 때, ì´ ì˜µì…˜ì€ ê° íƒœìŠ¤í¬ ì‚¬ì´ì˜ 간격(offset) í¬ê¸°ë¥¼ 명시한다. 기본 ê°„ê²©ì€ 8 ì´ë‹¤. \--task-newline : 태스í¬ê°€ 변경ë˜ë©´ 빈 공백 í•œì¤„ì„ ì¶”ê°€í•œë‹¤. ì´ë¥¼ 통해 여러 태스í¬ì—서 ë™ìž‘하는 í•¨ìˆ˜ë“¤ì„ ì‰½ê²Œ 구별 í•  수 있다. \--no-comment : 함수가 반환ë˜ëŠ” ê³³ì— ì£¼ì„ì„ ì¶œë ¥í•˜ì§€ 않는다. \--libname : 함수 ì´ë¦„ê³¼ 함께 ë¼ì´ë¸ŒëŸ¬ë¦¬ ì´ë¦„ì„ ì¶œë ¥í•œë‹¤. 공통 옵션 ========= -F *FUNC*, \--filter=*FUNC* : ì„ íƒëœ 함수들(그리고 ê·¸ ë‚´ë¶€ì˜ í•¨ìˆ˜ë“¤)ë§Œ 출력하ë„ë¡ í•„í„°ë¥¼ 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. -N *FUNC*, \--notrace=*FUNC* : ì„ íƒëœ 함수들 (ë˜ëŠ” ê·¸ 아래 함수들)ì„ ì¶œë ¥ì—서 제외하ë„ë¡ ì„¤ì •í•˜ëŠ” 옵션ì´ë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. -C *FUNC*, \--caller-filter=*FUNC* : ì„ íƒëœ í•¨ìˆ˜ì˜ í˜¸ì¶œìžë¥¼ 출력하는 필터를 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. -T *TRG*, \--trigger=*TRG* : ì„ íƒëœ í•¨ìˆ˜ì˜ íŠ¸ë¦¬ê±°ë¥¼ 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. íŠ¸ë¦¬ê±°ì— ëŒ€í•œ ì„¤ëª…ì€ *TRIGGERS* 를 참고한다. -D *DEPTH*, \--depth=*DEPTH* : 함수가 ì¤‘ì²©ë  ìˆ˜ 있는 최대 깊ì´ë¥¼ 설정한다. (ì´ë¥¼ 넘어서는 ìƒì„¸í•œ 함수 ì‹¤í–‰ê³¼ì •ì€ ë¬´ì‹œí•œë‹¤.) í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. -t *TIME*, \--time-filter=*TIME* : 설정한 시간 ì´í•˜ë¡œ ìˆ˜í–‰ëœ í•¨ìˆ˜ëŠ” 표시하지 않게 한다. 만약 ì–´ë–¤ 함수가 명시ì ìœ¼ë¡œ 'trace' 트리거가 ì ìš©ëœ 경우, ê·¸ 함수는 실행 시간과 ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* 를 참고한다. \--no-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì€ í‘œì‹œí•˜ì§€ 않게 한다. \--no-event : ì´ë²¤íŠ¸ëŠ” 표시하지 않게 한다. \--match=*TYPE* : 타입(TYPE)으로 ì¼ì¹˜í•˜ëŠ” íŒ¨í„´ì„ ë³´ì—¬ì¤€ë‹¤. 가능한 형태는 `regex`와 `glob`ì´ë‹¤. 기본 ì„¤ì •ì€ `regex`ì´ë‹¤. \--disable : uftrace 를 시작할때 ë°ì´í„°ë¥¼ 기ë¡í•˜ì§€ 않고 시작한다. ì´ê²ƒì€ `trace_on` 트리거와 함께 사용ë˜ì—ˆì„ 때만 ì˜ë¯¸ë¥¼ 가진다. 공통 ë¶„ì„ ì˜µì…˜ ======================= \--kernel-full : ì‚¬ìš©ìž í•¨ìˆ˜ ë°–ì—서 í˜¸ì¶œëœ ëª¨ë“  ì»¤ë„ í•¨ìˆ˜ë¥¼ 출력한다. \--kernel-only : ì‚¬ìš©ìž í•¨ìˆ˜ë¥¼ 제외한 ì»¤ë„ í•¨ìˆ˜ë§Œ 출력한다. \--event-full : ì‚¬ìš©ìž í•¨ìˆ˜ ë°–ì˜ ëª¨ë“  (사용ìž) ì´ë²¤íŠ¸ë¥¼ 출력한다. \--tid=*TID*[,*TID*,...] : 주어진 태스í¬ì— ì˜í•´ í˜¸ì¶œëœ í•¨ìˆ˜ë“¤ë§Œ 출력한다. `uftrace report --task` ë˜ëŠ” `uftrace info` 를 ì´ìš©í•´ ë°ì´í„° íŒŒì¼ ë‚´ì˜ íƒœìŠ¤í¬ ëª©ë¡ì„ ë³¼ 수 있다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. \--demangle=*TYPE* : í•„í„°, 트리거, 함수ì¸ìžì™€ (ë˜ëŠ”) 반환 ê°’ì„ ë””ë§¹ê¸€(demangle)ëœ C++ 심볼 ì´ë¦„으로 사용한다. "full", "simple", "no" ê°’ì„ ì‚¬ìš©í•  수 있다. 기본 ì„¤ì •ì€ "simple"ì´ë©°, 템플릿 파ë¼ë¯¸í„°ì™€ 함수 ì¸ìžë¥¼ 무시한다. -r *RANGE*, \--time-range=*RANGE* : 시간 범위 RANGE ë‚´ì— ì‹¤í–‰ëœ í•¨ìˆ˜ë“¤ë§Œ 출력한다. RANGE 는 \<시작\>~\<ë\> ("~"로 구분) ì´ê³  \<시작\>ê³¼ \<ë\> 중 하나는 ìƒëžµí•  수 있다. \<시작\>ê³¼ \<ë\>ì€ íƒ€ìž„ìŠ¤íƒ¬í”„ ë˜ëŠ” '100us'와 ê°™ì€ \<시간단위\>ê°€ 있는 경과시간ì´ë‹¤. `uftrace replay`(1) ì—서 `-f time` ë˜ëŠ” `-f elapsed` 를 ì´ìš©í•´ 타임스탬프 ë˜ëŠ” ê²½ê³¼ì‹œê°„ì„ í™•ì¸í•  수 있다. FILTERS ======= uftrace 는 관심 있는 대ìƒì´ 아닌 í•¨ìˆ˜ë“¤ì„ ê°ì¶”는 í•„í„°ë§ì„ í•  수 있다. í•„í„°ë§ì€ 사용ìžë“¤ì´ 관심 있는 함수들ì—ë§Œ 집중할 수 있게 하고, 기ë¡ë˜ëŠ” ë°ì´í„°ì˜ í¬ê¸°ë¥¼ ì¤„ì¼ ìˆ˜ ë•Œë¬¸ì— ì‚¬ìš©í•˜ê¸°ë¥¼ 권장한다. uftrace ê°€ 호출ë˜ë©´, ë‘ ì¢…ë¥˜ì˜ í•¨ìˆ˜ 필터를 갖게 ë˜ëŠ”ë° ì´ë“¤ì€ ëŒ€ìƒ í•¨ìˆ˜ë¥¼ ì„ íƒí•˜ëŠ” ë°©ì‹(opt-in)ì˜ í•„í„°ë¡œ `-F`/`--filter` 와 ì„ íƒí•˜ì§€ 않는 ë°©ì‹(opt-out)ì˜ í•„í„°ì¸ `-N`/`--notrace` ê°€ 있다. ì´ í•„í„°ë“¤ì€ ê¸°ë¡(record)하거나 재ìƒ(replay)í•  때 ëª¨ë‘ ì ìš©ë  수 있다. 첫번째 í•„í„° 종류는 ì„ íƒí•˜ëŠ” ë°©ì‹ì˜ í•„í„°ì´ë‹¤. 기본ì ìœ¼ë¡œ, ì´ê²ƒì€ ì•„ë¬´ê²ƒë„ ì¶”ì í•˜ì§€ 않는다. 하지만 ì–´ë–¤ ëª…ì‹œëœ í•¨ìˆ˜ì— ì§„ìž…í•˜ë©´, 함수 í˜¸ì¶œì— ëŒ€í•œ ì¶”ì ì„ 시작한다. 그러다가 ê·¸ 함수가 반환하게 ë˜ë©´, 함수 호출 ì¶”ì ì„ 중단한다. 예를 들어, `a()`, `b()` 와 `c()`를 차례로 호출하는 간단한 í”„ë¡œê·¸ëž¨ì„ ìƒê°í•´ë³´ìž. $ cat abc.c void c(void) { /* do nothing */ } void b(void) { c(); } void a(void) { b(); } int main(void) { a(); return 0; } $ gcc -pg -o abc abc.c ì¼ë°˜ì ì¸ 경우 uftrace 는 `main()`부터 `c()`ê¹Œì§€ì˜ ëª¨ë“  í•¨ìˆ˜ë“¤ì„ ì¶”ì í•  것ì´ë‹¤. $ uftrace ./abc # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { [ 1234] | a() { [ 1234] | b() { 3.880 us [ 1234] | c(); 5.475 us [ 1234] | } /* b */ 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ 하지만 `-F b` í•„í„° ì˜µì…˜ì´ ì‚¬ìš©ë˜ì—ˆì„ 때는, `main()`ê³¼ `a()` 함수는 ë³´ì´ì§€ 않고 ì˜¤ì§ `b()`와 `c()`ë§Œì´ í¬í•¨ëœ ì¶”ì  ê²°ê³¼ë¥¼ ë³´ì¼ê²ƒì´ë‹¤. ì•„ëž˜ì˜ ì˜ˆëŠ” í•„í„°ê°€ `uftrace replay`를 할때 ì ìš©ë˜ì—ˆìŒì— 유ì˜í•˜ë¼. $ uftrace record ./abc $ uftrace replay -F b # DURATION TID FUNCTION [ 1234] | b() { 3.880 us [ 1234] | c(); 5.475 us [ 1234] | } /* b */ ë‘번째 í•„í„° 종류는 ì„ íƒí•˜ì§€ 않는 ë°©ì‹ì˜ í•„í„°ì´ë‹¤. 기본ì ìœ¼ë¡œ, 모든 ê²ƒì´ ì¶”ì ë˜ì§€ë§Œ, ëª…ì‹œëœ í•¨ìˆ˜ì— ì§„ìž…í•˜ê²Œ ë˜ë©´, ì¶”ì ì„ 멈춘다. ì œì™¸ëœ í•¨ìˆ˜ê°€ 반환하게 ë˜ë©´, ì¶”ì ì„ 재개한다. 위 예시ì—서, `b()` 함수와 ê·¸ì˜ ëª¨ë“  í˜¸ì¶œì€ `-N` 옵션으로 ìƒëžµí•  수 있다. $ uftrace record ./abc $ uftrace replay -N b # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { 6.448 us [ 1234] | a(); 8.631 us [ 1234] | } /* main */ ë§Œì¼ íŠ¹ì • 함수ì—ë§Œ ê´€ì‹¬ì´ ìžˆê³  ê·¸ 함수가 어떻게 호출ë˜ëŠ”ì§€ë§Œ 알고 싶다면, caller filter 를 사용하면 ë  ê²ƒì´ë‹¤. ê·¸ 함수를 마지막(leaf) 노드로 만들고, ê·¸ í•¨ìˆ˜ì˜ ëª¨ë“  부모 í•¨ìˆ˜ë“¤ì„ ê¸°ë¡í•œë‹¤. $ uftrace record ./abc $ uftrace replay -C b # DURATION TID FUNCTION [ 1234] | main() { [ 1234] | a() { 5.475 us [ 1234] | b(); 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ 위 예시ì—서, 호출 ê²½ë¡œì— ì—†ëŠ” í•¨ìˆ˜ë“¤ì„ ì¶œë ¥ë˜ì§€ 않았다. ë˜í•œ, 함수 `b()`ì˜ ìžì‹ í•¨ìˆ˜ì¸ í•¨ìˆ˜ `c()` ë˜í•œ 출력ë˜ì§€ 않았다. ë˜í•œ, `-D` 옵션으로 í•¨ìˆ˜ì˜ ì¤‘ì²© 깊ì´ì„ 제한할 ìˆ˜ë„ ìžˆë‹¤. $ uftrace record ./abc $ uftrace replay -D 3 # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { [ 1234] | a() { 5.475 us [ 1234] | b(); 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ 위 예시ì—서, uftrace 는 함수 호출 깊ì´ë¥¼ 최대 3 으로 제한하여 출력했기 때문ì—, 마지막 í•¨ìˆ˜ì¸ `c()`는 ìƒëžµë˜ì—ˆë‹¤. `-D` ì˜µì…˜ì€ `-F` 옵션과 함께 ì“°ì¼ ìˆ˜ 있다. 때로는, 오랜 시간 실행ë˜ëŠ” í•¨ìˆ˜ë“¤ì„ íŠ¹ë³„í•˜ê²Œ 관찰하는 ê²ƒì´ ìœ ìš©í•˜ë‹¤. ì´ëŠ” ìž‘ì€ (ì‹¤í–‰ì‹œê°„ì„ ê°€ì§€ëŠ”) 함수들 중ì—는 관심 대ìƒì´ 아닌 ê²ƒë“¤ì´ ë§Žê¸° 때문ì´ë‹¤. `-t`/`--time-filter` ì˜µì…˜ì€ ëª…ì‹œëœ ìž„ê³„ì‹œê°„ë³´ë‹¤ 오래 ì‹¤í–‰ëœ í•¨ìˆ˜ë“¤ë§Œ ë³¼ 수 있게 하는 시간 ê¸°ë°˜ì˜ í•„í„°ì´ë‹¤. 위 예시ì—서는, 사용ìžëŠ” 대부분 아래와 ê°™ì´ 5 마ì´í¬ë¡œ(us) ì´ˆ ì´ìƒ 걸려서 실행ë˜ëŠ” 함수를 ë³´ê³  ì‹¶ì–´í•  것ì´ë‹¤. $ uftrace record ./abc $ uftrace replay -t 5us # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { [ 1234] | a() { 5.475 us [ 1234] | b(); 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ ë™ì¼í•œ ë°ì´í„°ì— 대해 다른 시간 í•„í„° ê°’ì„ ì„¤ì •í•´ replay 결과를 확ì¸í•  수 있다. $ uftrace replay -t 6us # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { 6.448 us [ 1234] | a(); 8.631 us [ 1234] | } /* main */ ë˜í•œ, `-r` ì˜µì…˜ì€ ì£¼ì–´ì§„ 시간 범위 ë™ì•ˆ ì‹¤í–‰ëœ í•¨ìˆ˜ë“¤ì„ ë³´ì—¬ì¤€ë‹¤. ì´ ì˜µì…˜ì„ ì‚¬ìš©í•  때, ê²°ê³¼ê°’ì„ TIMESTAMP, ELAPSED, DURATION, TID 필드들과 함께 ë³¼ 수 있다. $ uftrace replay -r 502716.387320101~502716.387322389 # TIMESTAMP DURATION TID FUNCTION 502716.387320101 0.289 us [ 6126] | fgets(); 502716.387320584 [ 6126] | get_values_from() { 502716.387320709 0.245 us [ 6126] | strdup(); 502716.387321172 0.144 us [ 6126] | strsep(); 502716.387321542 0.223 us [ 6126] | atoi(); 502716.387321983 0.239 us [ 6126] | atoi(); 502716.387322389 1.805 us [ 6126] | } /* get_values_from */ $ uftrace replay -r 40us~ | head -10 # ELAPSED DURATION TID FUNCTION 40.141 us [ 6126] | get_values_from() { 40.269 us 0.249 us [ 6126] | strdup(); 40.756 us 0.149 us [ 6126] | strsep(); 41.119 us 0.235 us [ 6126] | atoi(); 41.578 us 0.211 us [ 6126] | atoi(); 41.957 us 1.816 us [ 6126] | } /* get_values_from */ 42.124 us 0.220 us [ 6126] | fgets(); 42.529 us [ 6126] | get_values_from() { 42.645 us 0.236 us [ 6126] | strdup(); í•„í„°ë§ëœ í•¨ìˆ˜ì— íŠ¸ë¦¬ê±°ë¥¼ 설정할 ìˆ˜ë„ ìžˆë‹¤. ë” ë§Žì€ ì •ë³´ëŠ” *TRIGGERS* 항목ì—서 참고할 수 있다. TRIGGERS ======== uftrace 는 (í•„í„°ê°€ 있든 없든) ì„ íƒëœ 함수 í˜¸ì¶œì— ëŒ€í•œ 트리거 ë™ìž‘ì„ ì§€ì›í•œë‹¤. 현재 ì§€ì›ë˜ëŠ” 트리거와 ì‚¬ì–‘ì— ëŒ€í•œ BNF 는 다ìŒê³¼ 같다. := "@" := | "," := "depth=" | "backtrace" | "trace_on" | "trace_off" | "color=" | "time=" | "filter" | "notrace" := [ ] := "ns" | "nsec" | "us" | "usec" | "ms" | "msec" | "s" | "sec" | "m" | "min" `depth` 트리거는 함수를 실행하는 ë™ì•ˆ í•„í„°ì˜ ê¹Šì´ë¥¼ 변경한다. 다양한 í•¨ìˆ˜ì— ëŒ€í•´ 서로 다른 í•„í„° 깊ì´ë¥¼ 설정할 수 있다. 그리고 `backtrace` 트리거는 replay 시 ìŠ¤íƒ ë°±íŠ¸ë ˆì´ìŠ¤ë¥¼ 출력한다. `color` 트리거는 색ìƒì„ 변경한다. ì§€ì›ë˜ëŠ” 색ìƒì€ `red`, `green`, `blue`, `yellow`, `magenta`, `cyan`, `bold`, `gray` ê°€ 있다. ë‹¤ìŒ ì˜ˆì œëŠ” íŠ¸ë¦¬ê±°ì˜ ìž‘ë™ ë°©ì‹ì„ 보여준다. 함수 `b()`ì— `backtrace` action ì„ ë„£ê³  í•„í„° 깊ì´ë¥¼ 2 로 설정한다. $ uftrace record ./abc $ uftrace replay -T 'b@filter,backtrace,depth=2' # DURATION TID FUNCTION backtrace [ 1234] | /* [ 0] main */ backtrace [ 1234] | /* [ 1] a */ [ 1234] | b() { 3.880 us [ 1234] | c(); 5.475 us [ 1234] | } /* b */ `trace_on`ê³¼ `trace_off` 트리거는 uftrace ê°€ ì§€ì •ëœ í•¨ìˆ˜ë¥¼ 기ë¡í• ì§€ 여부를 관리한다. ë˜í•œ, `_` ë¬¸ìž ì—†ì´ `traceon` ê³¼ `traceoff` ë¡œë„ ì‚¬ìš©í•  수 있다. `time` 트리거는 함수를 실행하는 ë™ì•ˆ 시간 í•„í„°(time-filter) ì„¤ì •ì„ ë³€ê²½í•œë‹¤. 다른 í•¨ìˆ˜ë“¤ì— ëŒ€í•´ì„œ 서로 다른 시간 필터를 ì ìš©í•  ë–„ 사용할 수 있다. `filter` 와 `notrace` 트리거는 ê°ê° `-F`/`--filter` 와 `-N` /`--notrace` ê°™ì€ íš¨ê³¼ê°€ 있다. FIELDS ====== uftrace 사용ìžëŠ” replay 결과를 ëª‡ëª‡ì˜ í•„ë“œë¡œ ì›í•˜ëŠ” ë°©ì‹ëŒ€ë¡œ 구성할 수 있다. 여기서 필드란 파ì´í”„ ë¬¸ìž (|) ì™¼ìª½ì— ë‚˜íƒ€ë‚˜ëŠ” 정보를 뜻한다. 기본ì ìœ¼ë¡œ ì§€ì†ì‹œê°„ duration ê³¼ tid 필드를 사용하지만, 다른 í•„ë“œë“¤ë„ ë‹¤ìŒê³¼ ê°™ì´ ìž„ì˜ì˜ 순서로 사용 가능하다. $ uftrace replay -f time,delta,duration,addr # TIMESTAMP TIMEDELTA DURATION ADDRESS FUNCTION 74469.340757350 1.583 us 4004d0 | __monstartup(); 74469.340762221 4.871 us 0.766 us 4004f0 | __cxa_atexit(); 74469.340764847 2.626 us 4006b1 | main() { 74469.340765061 0.214 us 400656 | a() { 74469.340765195 0.134 us 400669 | b() { 74469.340765344 0.149 us 40067c | c() { 74469.340765524 0.180 us 0.742 us 4004b0 | getpid(); 74469.340766935 1.411 us 1.591 us 40067c | } /* c */ 74469.340767195 0.260 us 2.000 us 400669 | } /* b */ 74469.340767372 0.177 us 2.311 us 400656 | } /* a */ 74469.340767541 0.169 us 2.694 us 4006b1 | } /* main */ ê° í•„ë“œë“¤ì€ ë‹¤ìŒê³¼ ê°™ì€ ì˜ë¯¸ë¥¼ 가진다. * tid: task id (gettid(2)로 ì–»ì„ ìˆ˜ 있다.) * duration: 함수 실행 시간 * time: 타임스탬프 ì •ë³´ * delta: ì–´ë–¤ 작업 ë‚´ ë‘ íƒ€ìž„ìŠ¤íƒ¬í”„ì˜ ì°¨ì´ * elapsed: 첫 íƒ€ìž„ìŠ¤íƒ¬í”„ë¡œë¶€í„°ì˜ ê²½ê³¼ 시간 * addr: 해당 í•¨ìˆ˜ì˜ ì£¼ì†Œ * task: íƒœìŠ¤í¬ ì´ë¦„ (comm) * module: ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¹ì€ ì‹¤í–‰ 가능한 í•¨ìˆ˜ì˜ ì´ë¦„ 기본ì ìœ¼ë¡œ ì„¤ì •ëœ í•„ë“œê°’ì€ 'duration,tid'ì´ë‹¤. 만약 주어진 í•„ë“œì˜ ì´ë¦„ì´ "+"로 시작ëœë‹¤ë©´, ê·¸ 필드는 기본 í•„ë“œê°’ì— ì¶”ê°€ë  ê²ƒì´ë‹¤. 즉, "-f +time" 는 "-f duration,tid,time" 와 ê°™ì€ ê²ƒì´ë‹¤. ë˜í•œ 'none'ì´ë¼ëŠ” 특별한 í•„ë“œë„ ë°›ì„ ìˆ˜ 있는ë°, ì´ëŠ” 필드 ì¶œë ¥ì„ í•˜ì§€ 않고 ì˜¤ì§ í•¨ìˆ˜ 실행 ê²°ê³¼ë§Œì„ ë³´ì—¬ì¤€ë‹¤. 함께 보기 ========= `uftrace`(1), `uftrace-record`(1), `uftrace-report`(1), `uftrace-info`(1) ë²ˆì—­ìž ====== 강민철 , ê¹€í™ê·œ uftrace-0.9.4/doc/ko/uftrace-report.md000066400000000000000000000245111362052523300176230ustar00rootroot00000000000000% UFTRACE-REPORT(1) Uftrace User Manuals % Namhyung Kim % Sep, 2018 ì´ë¦„ ==== uftrace-report - 기ë¡ëœ ë°ì´í„°ì˜ 통계와 요약 정보를 출력한다. 사용법 ====== uftrace report [*options*] 설명 ==== ì´ ëª…ë ¹ì–´ëŠ” 주어진 ë°ì´í„° 파ì¼ì˜ ë°ì´í„°ë¥¼ 모으고 ê·¸ 요약 정보와 통계 ìžë£Œë“¤ì„ 출력한다. 기본ì ìœ¼ë¡œ 함수ìžë£Œë“¤ì„ 보여주는ë°, `--task` ì˜µì…˜ì„ í†µí•´ 실행한 íƒœìŠ¤í¬ ë‹¨ìœ„ì˜ í†µê³„ìžë£Œë¥¼ ë³¼ 수 있고, `--diff` ì˜µì…˜ì€ ì¶”ê°€ ì¸ìžë¡œ ë°ì´í„°ë¥¼ 입력하면, ê·¸ ë°ì´í„°ì™€ ì›ë³¸ ë°ì´í„° ê°„ì˜ ì°¨ì´ì ì„ 보여준다. REPORT 옵션 =========== -s *KEYS*[,*KEYS*,...], \--sort=*KEYS*[,*KEYS*,...] : 주어진 키를 ì´ìš©í•´ í•¨ìˆ˜ë“¤ì„ ë¶„ë¥˜í•œë‹¤. ë‹¤ìˆ˜ì˜ í‚¤ë“¤ì€ ì‰¼í‘œ(,)로 분리할 수 있고, 가능한 키들ì—는 `total` (time), `self` (time), `call`, `func`, `avg`, `min`, `max`ê°€ 있다. ì²˜ìŒ ì„¸ê°œì˜ í‚¤ëŠ” `--avg-total` 와 `--avg-self` ì˜µì…˜ì´ ì£¼ì–´ì§€ì§€ ì•Šì€ ê²½ìš°ì—ë§Œ 사용할 수 있다. ì´ëŸ¬í•œ 옵션 중 하나를 사용하는 경우ì—는 마지막 ì„¸ê°œì˜ í‚¤ì™€ 함께 ì´ìš©í•´ì•¼í•œë‹¤. \--avg-total : ê° í•¨ìˆ˜ì˜ ì´ ì‹œê°„(total time)ì˜ í‰ê· , 최소, 최대 ì‹œê°„ì„ ë³´ì—¬ì¤€ë‹¤. \--avg-self : ê° í•¨ìˆ˜ì˜ ìžì²´ 시간(self time)ì˜ í‰ê· , 최소, 최대 ì‹œê°„ì„ ë³´ì—¬ì¤€ë‹¤. \--task : í•¨ìˆ˜ì˜ í†µê³„ìžë£Œê°€ 아닌 태스í¬ë¥¼ 요약해서 보고한다. \--diff=*DATA* : 입력한 ì¶”ì  ë°ì´í„°ì™€ 주어진 ë°ì´í„°ì˜ ì°¨ì´ì ì„ 보고한다. ë‘ ë°ì´í„°ëŠ” uftrace 로 record 한 ë°ì´í„°ì´ë©°, ë°ì´í„°ë¥¼ ë‹´ì€ ë””ë ‰í† ë¦¬ë¥¼ ì¸ìžë¡œ 넘겨야한다. \--diff-policy=*POLICY* : `--diff`ì˜µì…˜ì„ ì‚¬ìš©í•  때, 사용ìžê°€ 지정한 diff ì •ì±…ì„ ì ìš©í•œë‹¤. 사용가능한 값으로는 "abs", "no-abs", "percent", "no-percent", "compact" "full"ì´ ìžˆë‹¤. "abs"는 ì ˆëŒ€ê°’ì„ ì‚¬ìš©í•˜ì—¬ diff 결과를 정렬하며 양수와 ìŒìˆ˜ í•­ëª©ì„ í•¨ê»˜ 표시할 수 있다. "no-abs"는 먼저 양수 í•­ëª©ì„ í‘œì‹œí•œ ë‹¤ìŒ ìŒìˆ˜ í•­ëª©ì„ í‘œì‹œí•œë‹¤. "percent"는 diff를 백분율로 표시하고 "no-percent"는 값으로 표시한다. "full"ì€ ê¸°ì¤€, 새 ë°ì´í„°, ì°¨ì´ì  ì´ ì„¸ ì—´ì„ ëª¨ë‘ í‘œì‹œí•˜ëŠ” 반면 "compact"는 ì°¨ì´ì ë§Œ 표시한다. ê¸°ë³¸ê°’ì€ "abs", "compact", "no-percent"다. \--sort-column=*IDX* : `--diff`를 "full" ì •ì±…ê³¼ 함께 사용할 때, ì´ ì‹œê°„, ìžì²´ 시간, 호출 횟수 ì´ 3ê°œì˜ ì—´ì´ í‘œì‹œëœë‹¤. ì´ ì˜µì…˜ì€ ì •ë ¬ 키로 사용할 ì—´ ì¸ë±ìŠ¤ë¥¼ ì„ íƒí•œë‹¤. ì¸ë±ìФ 0ì€ `--data`옵션으로 제공ë˜ëŠ” ì›ë³¸ ë°ì´í„°ì— 대한 것ì´ê³ , ì¸ë±ìФ 1ì€ `--diff`옵션으로 제공ë˜ëŠ” ë°ì´í„°ì— 대한 것, ì¸ë±ìФ 2는 ë‘ ë°ì´í„° ê°„ì˜ (백분율) ì°¨ì´ì— 대한 것ì´ë‹¤. 공통 옵션 ========= -F *FUNC*, \--filter=*FUNC* : ì„ íƒëœ 함수들(그리고 ê·¸ ë‚´ë¶€ì˜ í•¨ìˆ˜ë“¤)ë§Œ 출력하ë„ë¡ í•„í„°ë¥¼ 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ `uftrace-replay`(1) 를 참고한다. -N *FUNC*, \--notrace=*FUNC* : ì„ íƒëœ 함수들 (ë˜ëŠ” ê·¸ 아래 함수들)ì„ ì¶œë ¥ì—서 제외하ë„ë¡ ì„¤ì •í•˜ëŠ” 옵션ì´ë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ `uftrace-replay`(1) 를 참고한다. -C *FUNC*, \--caller-filter=*FUNC* : ì„ íƒëœ í•¨ìˆ˜ì˜ í˜¸ì¶œìžë¥¼ 출력하는 필터를 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ `uftrace-replay`(1) 를 참고한다. -T *TRG*, \--trigger=*TRG* : ì„ íƒëœ í•¨ìˆ˜ì˜ íŠ¸ë¦¬ê±°ë¥¼ 설정한다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. íŠ¸ë¦¬ê±°ì— ëŒ€í•œ ì„¤ëª…ì€ `uftrace-replay`(1) 를 참고한다. -D *DEPTH*, \--depth *DEPTH* : 함수가 ì¤‘ì²©ë  ìˆ˜ 있는 최대 깊ì´ë¥¼ 설정한다. (ì´ë¥¼ 넘어서는 ìƒì„¸í•œ 함수 ì‹¤í–‰ê³¼ì •ì€ ë¬´ì‹œí•œë‹¤.) -t *TIME*, \--time-filter=*TIME* : 설정한 시간 ì´í•˜ë¡œ ìˆ˜í–‰ëœ í•¨ìˆ˜ëŠ” 표시하지 않게 한다. 만약 ì–´ë–¤ 함수가 명시ì ìœ¼ë¡œ 'trace' 트리거가 ì ìš©ëœ 경우, ê·¸ 함수는 실행 시간과 ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. \--no-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì€ í‘œì‹œí•˜ì§€ 않게 한다. \--no-event : ì´ë²¤íŠ¸ëŠ” 표시하지 않게 한다. \--match=*TYPE* : 타입(TYPE)으로 ì¼ì¹˜í•˜ëŠ” íŒ¨í„´ì„ ë³´ì—¬ì¤€ë‹¤. 가능한 형태는 `regex`와 `glob`ì´ë‹¤. 기본 ì„¤ì •ì€ `regex`ì´ë‹¤. 공통 ë¶„ì„ ì˜µì…˜ ======================= \--kernel-full : ì‚¬ìš©ìž í•¨ìˆ˜ ë°–ì—서 í˜¸ì¶œëœ ëª¨ë“  ì»¤ë„ í•¨ìˆ˜ë¥¼ 출력한다. \--kernel-only : ì‚¬ìš©ìž í•¨ìˆ˜ë¥¼ 제외한 ì»¤ë„ í•¨ìˆ˜ë§Œ 출력한다. \--event-full : ì‚¬ìš©ìž í•¨ìˆ˜ ë°–ì˜ ëª¨ë“  (사용ìž) ì´ë²¤íŠ¸ë¥¼ 출력한다. \--tid=*TID*[,*TID*,...] : 주어진 태스í¬ì— ì˜í•´ í˜¸ì¶œëœ í•¨ìˆ˜ë“¤ë§Œ 출력한다. `uftrace report --task` ë˜ëŠ” `uftrace info` 를 ì´ìš©í•´ ë°ì´í„° íŒŒì¼ ë‚´ì˜ íƒœìŠ¤í¬ ëª©ë¡ì„ ë³¼ 수 있다. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. \--demangle=*TYPE* : í•„í„°, 트리거, 함수ì¸ìžì™€ (ë˜ëŠ”) 반환 ê°’ì„ ë””ë§¹ê¸€(demangle)ëœ C++ 심볼 ì´ë¦„으로 사용한다. "full", "simple", "no" ê°’ì„ ì‚¬ìš©í•  수 있다. 기본 ì„¤ì •ì€ "simple"ì´ë©°, 템플릿 파ë¼ë¯¸í„°ì™€ 함수 ì¸ìžë¥¼ 무시한다. -r *RANGE*, \--time-range=*RANGE* : 시간 범위 RANGE ë‚´ì— ì‹¤í–‰ëœ í•¨ìˆ˜ë“¤ë§Œ 출력한다. RANGE 는 \<시작\>~\<ë\> ("~"로 구분) ì´ê³  \<시작\>ê³¼ \<ë\> 중 하나는 ìƒëžµí•  수 있다. \<시작\>ê³¼ \<ë\>ì€ íƒ€ìž„ìŠ¤íƒ¬í”„ ë˜ëŠ” '100us'와 ê°™ì€ \<시간단위\>ê°€ 있는 경과시간ì´ë‹¤. `uftrace replay`(1) ì—서 `-f time` ë˜ëŠ” `-f elapsed` 를 ì´ìš©í•´ 타임스탬프 ë˜ëŠ” ê²½ê³¼ì‹œê°„ì„ í™•ì¸í•  수 있다. 예제 ==== ì´ ëª…ë ¹ì–´ëŠ” 아래와 ê°™ì€ ì •ë³´ë“¤ì„ ì¶œë ¥í•œë‹¤. $ uftrace record abc $ uftrace report Total time Self time Calls Function ========== ========== ========== ==================== 150.829 us 150.829 us 1 __cxa_atexit 27.289 us 1.243 us 1 main 26.046 us 0.939 us 1 a 25.107 us 0.934 us 1 b 24.173 us 1.715 us 1 c 22.458 us 22.458 us 1 getpid $ uftrace report -s call,self Total time Self time Calls Function ========== ========== ========== ==================== 150.829 us 150.829 us 1 __cxa_atexit 22.458 us 22.458 us 1 getpid 24.173 us 1.715 us 1 c 27.289 us 1.243 us 1 main 26.046 us 0.939 us 1 a 25.107 us 0.934 us 1 b $ uftrace report --avg-self Avg self Min self Max self Function ========== ========== ========== ==================== 150.829 us 150.829 us 150.829 us __cxa_atexit 22.458 us 22.458 us 22.458 us getpid 1.715 us 1.715 us 1.715 us c 1.243 us 1.243 us 1.243 us main 0.939 us 0.939 us 0.939 us a 0.934 us 0.934 us 0.934 us b $ uftrace report --threads TID Run time Num funcs Start function ===== ========== ========== ========================= 21959 178.118 us 6 main ë‘ ë°ì´í„°ì˜ ì°¨ì´ì ì„ 보려면: $ uftrace record abc $ uftrace report --diff uftrace.data.old # # uftrace diff # [0] base: uftrace.data (from uftrace record abc ) # [1] diff: uftrace.data.old (from uftrace record abc ) # Total time Self time Calls Function ========== ========== ========== ==================== -0.301 us -0.038 us +0 main -0.263 us -0.070 us +0 a -0.193 us -0.042 us +0 b -0.151 us -0.090 us +0 c -0.131 us -0.131 us +0 __cxa_atexit -0.061 us -0.061 us +0 getpid ìœ„ì˜ ì˜ˆì œëŠ” ì´ ì‹œê°„ì˜ ì ˆëŒ€ê°’ìœ¼ë¡œ 정렬한 ë‘ ë°ì´í„°ì˜ ì°¨ì´ì ë“¤ì„ 보여준다. ì•„ëž˜ì˜ ì˜ˆì œëŠ” ìžì²´ ì‹œê°„ì˜ (부호가 있는) ê°’ì„ ì´ìš©í•´ 정렬했다. $ uftrace report --diff uftrace.data.old -s self --diff-policy no-abs # # uftrace diff # [0] base: uftrace.data (from uftrace record abc ) # [1] diff: uftrace.data.old (from uftrace record abc ) # Total time Self time Calls Function ========== ========== ========== ==================== -0.301 us -0.038 us +0 main -0.193 us -0.042 us +0 b -0.061 us -0.061 us +0 getpid -0.263 us -0.070 us +0 a -0.151 us -0.090 us +0 c -0.131 us -0.131 us +0 __cxa_atexit "full" ì •ì±…ì„ ì‚¬ìš©í•˜ë©´ 사용ìžëŠ” 아래와 ê°™ì€ ì›ì‹œ(raw) ë°ì´í„°ë¥¼ ë³¼ 수 있다. ë˜í•œ (ì›ì‹œ ë°ì´í„°ì˜ 경우) 다른 열로 ì •ë ¬ë„ ê°€ëŠ¥í•˜ë‹¤. ë°‘ì˜ ì˜ˆì œëŠ” base ë°ì´í„°ì˜ ì´ ì‹œê°„ì„ ê¸°ì¤€ìœ¼ë¡œ 결과를 정렬한다. $ uftrace report --diff uftrace.data.old --sort-column 0 --diff-policy full,percent # # uftrace diff # [0] base: uftrace.data (from uftrace record abc ) # [1] diff: uftrace.data.old (from uftrace record abc ) # Total time (diff) Self time (diff) Nr. called (diff) Function ================================ ================================ ================================ ==================== 2.812 us 2.511 us -10.70% 0.403 us 0.365 us -9.43% 1 1 +0 main 2.409 us 2.146 us -10.92% 0.342 us 0.272 us -20.47% 1 1 +0 a 2.067 us 1.874 us -9.34% 0.410 us 0.368 us -10.24% 1 1 +0 b 1.657 us 1.506 us -9.11% 0.890 us 0.800 us -10.11% 1 1 +0 c 0.920 us 0.789 us -14.24% 0.920 us 0.789 us -14.24% 1 1 +0 __cxa_atexit 0.767 us 0.706 us -7.95% 0.767 us 0.706 us -7.95% 1 1 +0 getpid 함께 보기 ========= `uftrace`(1), `uftrace-record`(1), `uftrace-replay`(1), `uftrace-tui`(1) ë²ˆì—­ìž ====== ê¹€ì„œì˜ uftrace-0.9.4/doc/ko/uftrace-script.md000066400000000000000000000207011362052523300176110ustar00rootroot00000000000000% UFTRACE-SCRIPT(1) Uftrace User Manuals % Honggyu Kim , Namhyung Kim % Sep, 2018 ì´ë¦„ ==== uftrace-script - 기ë¡ëœ ë°ì´í„°ë¥¼ 대ìƒìœ¼ë¡œ 스í¬ë¦½íŠ¸ë¥¼ 실행한다. 사용법 ====== uftrace script (-S|--script) uftrace-0.9.4/doc/uftrace.md000066400000000000000000000073541362052523300157070ustar00rootroot00000000000000% UFTRACE(1) Uftrace User Manuals % Namhyung Kim % Sep, 2018 NAME ==== uftrace - Function graph tracer for userspace SYNOPSIS ======== uftrace [*record*|*replay*|*live*|*report*|*info*|*dump*|*recv*|*graph*|*script*|*tui*] [*options*] COMMAND [*command-options*] DESCRIPTION =========== The uftrace tool is a function tracer that traces the execution of given `COMMAND` at the function level. `COMMAND` should be a C or C++ executable built with compiler instrumentation (`-pg` or `-finstrument-functions`). COMMAND needs to have an ELF symbol table (i.e. not be `strip`(1)-ed) in order for the names of traced functions to be available. The uftrace command consists of a number of sub-commands, in the manner of `git`(1) or `perf`(1). Below is a short description of each sub-command. For more detailed information, see the respective manual pages. The options in this page can be given to any sub-command also. For convenience, if no sub-command is given, uftrace acts as though the `live` sub-command was specified, which runs the `record` and `replay` sub-commands in turn. See `uftrace-live`(1) for options belonging to the `live` sub-command. For more detailed analysis, it is better to use `uftrace-record`(1) to save trace data, and then analyze it with other uftrace commands like `uftrace-replay`(1), `uftrace-report`(1), `uftrace-info`(1), `uftrace-dump`(1), `uftrace-script`(1) or `uftrace-tui`(1). SUB-COMMANDS ============ record : Run a given command and save trace data in a data file or directory. replay : Print recorded function trace data with time durations. live : Do live tracing. Print function trace of the given command. report : Print various statistics and summary of the recorded trace data. info : Print side-band information like OS version, CPU info, command line and so on. dump : Print raw tracing data in the data files. recv : Save tracing data sent to network graph : Print function call graph script : Run a script for recorded function trace tui : Show text user interface for graph and report OPTIONS ======= -?, \--help : Print help message and list of options with description -h, \--help : Print help message and list of options with description \--usage : Print usage string -V, \--version : Print program version -v, \--verbose : Print verbose messages. This option increases a debug level and can be used at most 3 times. \--debug : Print debug messages. This option is same as `-v`/`--verbose` and is provided only for backward compatibility. \--debug-domain=*DOMAIN*[,*DOMAIN*, ...] : Limit the printing of debug messages to those belonging to one of the DOMAINs specified. Available domains are: uftrace, symbol, demangle, filter, fstack, session, kernel, mcount, dynamic, event, script and dwarf. The domains can have an their own debug level optionally (preceded by a colon). For example, `-v --debug-domain=filter:2` will apply debug level of 2 to the "filter" domain and apply debug level of 1 to others. -d *DATA*, \--data=*DATA* : Specify name of trace data (directory). Default is `uftrace.data`. \--logfile=*FILE* : Save warning and debug messages into this file instead of stderr. \--color=*VAL* : Enable or disable color on the output. Possible values are "yes"(= "true" | "1" | "on" ), "no"(= "false" | "0" | "off" ) and "auto". The "auto" value is default and turns on coloring if stdout is a terminal. \--no-pager : Do not use a pager. \--opt-file=*FILE* : Read command-line options from the FILE. SEE ALSO ======== `uftrace-live`(1), `uftrace-record`(1), `uftrace-replay`(1), `uftrace-report`(1), `uftrace-info`(1), `uftrace-dump`(1), `uftrace-recv`(1), `uftrace-graph`(1), `uftrace-script`(1), `uftrace-tui(1)` uftrace-0.9.4/gdb/000077500000000000000000000000001362052523300137125ustar00rootroot00000000000000uftrace-0.9.4/gdb/uftrace/000077500000000000000000000000001362052523300153435ustar00rootroot00000000000000uftrace-0.9.4/gdb/uftrace/lists.py000066400000000000000000000071271362052523300170620ustar00rootroot00000000000000# # gdb helper commands and functions for uftrace debugging # copied from the Linux kernel source # # list tools # # Copyright (c) Thiebaud Weksteen, 2015 # # Authors: # Thiebaud Weksteen # # This work is licensed under the terms of the GNU GPL version 2. # import gdb from uftrace import utils list_head = utils.CachedType("struct list_head") def list_for_each(head): if head.type == list_head.get_type().pointer(): head = head.dereference() elif head.type != list_head.get_type(): raise gdb.GdbError("Must be struct list_head not {}" .format(head.type)) node = head['next'].dereference() while node.address != head.address: yield node.address node = node['next'].dereference() def list_for_each_entry(head, gdbtype, member): for node in list_for_each(head): if node.type != list_head.get_type().pointer(): raise TypeError("Type {} found. Expected struct list_head *." .format(node.type)) yield utils.container_of(node, gdbtype, member) def list_check(head): nb = 0 if (head.type == list_head.get_type().pointer()): head = head.dereference() elif (head.type != list_head.get_type()): raise gdb.GdbError('argument must be of type (struct list_head [*])') c = head try: gdb.write("Starting with: {}\n".format(c)) except gdb.MemoryError: gdb.write('head is not accessible\n') return while True: p = c['prev'].dereference() n = c['next'].dereference() try: if p['next'] != c.address: gdb.write('prev.next != current: ' 'current@{current_addr}={current} ' 'prev@{p_addr}={p}\n'.format( current_addr=c.address, current=c, p_addr=p.address, p=p, )) return except gdb.MemoryError: gdb.write('prev is not accessible: ' 'current@{current_addr}={current}\n'.format( current_addr=c.address, current=c )) return try: if n['prev'] != c.address: gdb.write('next.prev != current: ' 'current@{current_addr}={current} ' 'next@{n_addr}={n}\n'.format( current_addr=c.address, current=c, n_addr=n.address, n=n, )) return except gdb.MemoryError: gdb.write('next is not accessible: ' 'current@{current_addr}={current}\n'.format( current_addr=c.address, current=c )) return c = n nb += 1 if c == head: gdb.write("list is consistent: {} node(s)\n".format(nb)) return class UftListChk(gdb.Command): """Verify a list consistency""" def __init__(self): super(UftListChk, self).__init__("uft-list-check", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION) def invoke(self, arg, from_tty): argv = gdb.string_to_argv(arg) if len(argv) != 1: raise gdb.GdbError("uft-list-check takes one argument") list_check(gdb.parse_and_eval(argv[0])) UftListChk() uftrace-0.9.4/gdb/uftrace/mcount.py000066400000000000000000000067661362052523300172410ustar00rootroot00000000000000# # gdb helper commands and functions for uftrace debugging # # mcount tools # # Copyright (c) LG Electronics, 2018 # # Authors: # Namhyung Kim # # This work is licensed under the terms of the GNU GPL version 2. # import gdb from uftrace import utils from uftrace import rbtree from uftrace import trigger filter_type = utils.CachedType("struct uftrace_filter") def get_symbol_name(addr): try: block = gdb.block_for_pc(int(addr)) except: try: return gdb.execute('info symbol ' + hex(addr), False, True).split(' ')[0] except: return '' while block and not block.function: block = block.superblock if block is None: return '' return block.function.print_name class UftMcountData(gdb.Command): """Find mcount thread data of current thread and show return stacks.""" def __init__(self): super(UftMcountData, self).__init__("uft-mcount-data", gdb.COMMAND_DATA) def invoke(self, arg, from_tty): mtd = utils.gdb_eval_or_none("mtd") if mtd is None: gdb.write("no mtd found\n") return mtd_idx = mtd['idx'] gdb.write("mtd: tid = {tid}, idx = {idx}\n".format( tid=mtd['tid'], idx=mtd_idx)) rstack = mtd['rstack'] for i in range(0, mtd_idx): cip = rstack[i]['child_ip'] pip = rstack[i]['parent_ip'] csym = get_symbol_name(cip) psym = get_symbol_name(pip) gdb.write("[{ind}] {child} <== {parent}\n".format( ind=i, child=csym, parent=psym)) UftMcountData() class UftMcountFilter(gdb.Command): """List mcount filters.""" def __init__(self): super(UftMcountFilter, self).__init__("uft-mcount-filters", gdb.COMMAND_DATA) def invoke(self, arg, from_tty): tr = utils.gdb_eval_or_none("mcount_triggers") if tr is None: gdb.write("no filter/trigger found\n") return filter_ptr_type = filter_type.get_type().pointer() trigger.filter_print(None) for filt in rbtree.rb_for_each_entry(tr, filter_ptr_type, "node"): trigger.filter_print(filt) UftMcountFilter() class UftMcountTrigger(gdb.Command): """List mcount triggers.""" def __init__(self): super(UftMcountTrigger, self).__init__("uft-mcount-triggers", gdb.COMMAND_DATA) def invoke(self, arg, from_tty): tr = utils.gdb_eval_or_none("mcount_triggers") if tr is None: gdb.write("no filter/trigger found\n") return verbose = len(arg) > 0 filter_ptr_type = filter_type.get_type().pointer() trigger.trigger_print(None, False) for filt in rbtree.rb_for_each_entry(tr, filter_ptr_type, "node"): trigger.trigger_print(filt, verbose) UftMcountTrigger() class UftMcountArgspec(gdb.Command): """List mcount arguments and return values.""" def __init__(self): super(UftMcountArgspec, self).__init__("uft-mcount-args", gdb.COMMAND_DATA) def invoke(self, arg, from_tty): tr = utils.gdb_eval_or_none("mcount_triggers") if tr is None: gdb.write("no filter/trigger found\n") return verbose = len(arg) > 0 filter_ptr_type = filter_type.get_type().pointer() trigger.argspec_print(None, False) for filt in rbtree.rb_for_each_entry(tr, filter_ptr_type, "node"): trigger.argspec_print(filt, verbose) UftMcountArgspec() uftrace-0.9.4/gdb/uftrace/plthook.py000066400000000000000000000030751362052523300174020ustar00rootroot00000000000000# # gdb helper commands and functions for uftrace debugging # copied from the Linux kernel source (module tools) # # plthook tools # # Copyright (c) Siemens AG, 2013 # Copyright (c) LG Electronics, 2018 # # Authors: # Jan Kiszka # Namhyung Kim # # This work is licensed under the terms of the GNU GPL version 2. # import gdb import os from uftrace import utils, lists plthook_data_type = utils.CachedType("struct plthook_data") def plthook_list(): plthook_modules = utils.gdb_eval_or_none("plthook_modules") if plthook_modules is None: return pd_ptr_type = plthook_data_type.get_type().pointer() for module in lists.list_for_each_entry(plthook_modules, pd_ptr_type, "list"): yield module def find_module_by_name(name): for module in plthook_list(): if os.path.basename(module['mod_name'].string()) == name: return module return None class UftPlthookData(gdb.Command): """List currently loaded plthook modules.""" def __init__(self): super(UftPlthookData, self).__init__("uft-plthook-data", gdb.COMMAND_DATA) def invoke(self, arg, from_tty): gdb.write("{id:>16} {addr:>16} {name:<32}\n".format( id="Module Id", name="Name", addr="Base Address")) for module in plthook_list(): gdb.write("{id:>16} {addr:>16} {name:<32}\n".format( id=hex(module['module_id']), addr=hex(module['base_addr']), name=os.path.basename(module['mod_name'].string()))) UftPlthookData() uftrace-0.9.4/gdb/uftrace/rbtree.py000066400000000000000000000076401362052523300172070ustar00rootroot00000000000000# # gdb helper commands and functions for uftrace debugging # # rbtree tools # # Copyright (c) LG Electronics, 2018 # # Authors: # Namhyung Kim # # This work is licensed under the terms of the GNU GPL version 2. # import gdb from uftrace import utils rb_root = utils.CachedType("struct rb_root") rb_node = utils.CachedType("struct rb_node") def rb_first(root): if root.type == rb_root.get_type().pointer(): root = root.dereference() elif root.type != rb_root.get_type(): raise gdb.GdbError("Must be struct rb_root not {}" .format(root.type)) node = root['rb_node'].dereference() if node.address == 0: return None if node.type != rb_node.get_type(): raise gdb.GdbError("Must be struct rb_ndoe not {}" .format(node.type)) left = node['rb_left'].dereference() while left.address != 0: node = left left = node['rb_left'].dereference() return node def rb_last(root): if root.type == rb_root.get_type().pointer(): root = root.dereference() elif root.type != rb_root.get_type(): raise gdb.GdbError("Must be struct rb_root not {}" .format(root.type)) node = root['rb_node'].dereference() if node.address == 0: return None right = node['rb_right'].dereference() while right.address != 0: node = right right = node['rb_right'].dereference() return node def rb_parent(node): addr = int(node['rb_parent_color']) addr &= ~3 # clear color bit if addr == 0: return None # Value.address is read-only, just create a new value # using Value.cast() after changing its address node = gdb.Value(addr) p = node.cast(rb_node.get_type().pointer()) return p.dereference() def rb_next(node): if node.type == rb_node.get_type().pointer(): node = node.dereference() elif node.type != rb_node.get_type(): raise gdb.GdbError("Must be struct rb_node not {}" .format(node.type)) parent = rb_parent(node) if parent is not None and parent.address == node.address: return None r = node['rb_right'].dereference() if r.address != 0: node = r left = node['rb_left'].dereference() while left.address != 0: node = left left = node['rb_left'].dereference() return node while parent is not None: right_child = parent['rb_right'].dereference() if node.address != right_child.address: break node = parent parent = rb_parent(node) return parent def rb_prev(node): if node.type == rb_node.get_type().pointer(): node = node.dereference() elif node.type != rb_node.get_type(): raise gdb.GdbError("Must be struct rb_node not {}" .format(node.type)) parent = rb_parent(node) if parent is not None and parent.address == node.address: return None l = node['rb_left'].dereference() if l.address != 0: node = l right = node['rb_right'].dereference() while right.address != 0: node = right right = node['rb_right'].dereference() return node while parent is not None: left_child = parent['rb_left'].dereference() if node.address != left_child.address: break node = parent parent = rb_parent(node) return parent def rb_for_each(root): node = rb_first(root) while node is not None: yield node.address node = rb_next(node) def rb_for_each_entry(head, gdbtype, member): for node in rb_for_each(head): if node.type != rb_node.get_type().pointer(): raise TypeError("Type {} found. Expected struct rb_node *." .format(node.type)) yield utils.container_of(node, gdbtype, member) uftrace-0.9.4/gdb/uftrace/trigger.py000066400000000000000000000077631362052523300173750ustar00rootroot00000000000000# # gdb helper commands and functions for uftrace debugging # # filter and trigger tools # # Copyright (c) LG Electronics, 2018 # # Authors: # Namhyung Kim # # This work is licensed under the terms of the GNU GPL version 2. # import gdb from uftrace import utils from uftrace import lists filter_type = utils.CachedType("struct uftrace_filter") trigger_type = utils.CachedType("struct uftrace_trigger") argspec_type = utils.CachedType("struct uftrace_arg_spec") TRIGGER_FLAGS = [ "DEPTH", "FILTER", "BACKTRACE", "TRACE", "TRACE_ON", "TRACE_OFF", "ARGUMENT", "RECOVER", "RETVAL", "COLOR", "TIME_FILTER", "READ", "FINISH", "AUTO_ARGS" ] TR_FLAG_FILTERS = 1 + 2 + 1024 # DEPTH | FILTER | TIME_FILTER TR_FLAG_ARGS = 64 + 256 + 8192 # ARGUMENT | RETVAL | AUTO_ARGS TR_FLAG_READ = 2048 ARG_TYPE_INDEX = 0 ARG_TYPE_FLOAT = 1 ARG_TYPE_REG = 2 ARG_TYPE_STACK = 3 ARG_FMT_AUTO = 0 ARG_FMT_STR = "diuxscfSpe" def filter_flag(tr): flag = tr['flags'] fmode = tr['fmode'] f = 'D' if flag & 1 else ' ' if flag & 2: f += 'F' if fmode == 1 else 'N' f += 't' if flag >= 1024 else ' ' return f def filter_print(filt): if filt is None: gdb.write("{start:>16} {end:<16} {flag:4} {name}\n". format(start="Start", end="End", flag="Flag", name="Name")) return tr = filt['trigger'] flags = tr['flags'] if (flags & TR_FLAG_FILTERS) == 0: return gdb.write("{start:>16} - {end:<16} : {flag:4} {name}\n". format(start=hex(filt['start']), end=hex(filt['end']), flag=filter_flag(tr), name=filt['name'].string())) def trigger_flag(tr): flags = tr['flags'] s = [] for bit, flag in enumerate(TRIGGER_FLAGS): if flags & (1 << bit): s.append(flag) return '|'.join(s) def trigger_print(filt, verbose): if filt is None: gdb.write("{start:>16} {end:<16} {flag:>6} {name}\n". format(start="Start", end="End", flag="Flags", name="Name")) return tr = filt['trigger'] gdb.write("{start:>16} - {end:<16} : {flag:>6} {name}\n". format(start=hex(filt['start']), end=hex(filt['end']), flag=hex(tr['flags']), name=filt['name'].string())) if verbose: gdb.write(" triggers = {flags}\n".format(flags=trigger_flag(tr))) def trigger_argspec(tr): argspec_ptr_type = argspec_type.get_type().pointer() s = [] for arg in lists.list_for_each_entry(tr['pargs'], argspec_ptr_type, 'list'): t = arg['type'] if t == ARG_TYPE_INDEX: idx = int(arg['idx']) if idx == 0: a = 'retval' else: a = 'arg{i}'.format(i=int(arg['idx'])) elif t == ARG_TYPE_FLOAT: a = 'fparg{i}'.format(i=int(arg['idx'])) elif t == ARG_TYPE_REG: a = 'reg{i}'.format(i=int(arg['reg_idx'])) elif t == ARG_TYPE_STACK: a = 'stack+{i}'.format(i=int(arg['stack_ofs'])) f = arg['fmt'] if f != ARG_FMT_AUTO: a += "/{fmt}{sz}".format(fmt=ARG_FMT_STR[f], sz=arg['size']*8) s.append(a) return ','.join(s) def argspec_flag(flags): if flags >= 8192: # AUTO_ARGS return "AA" f = 'A' if flags & 64 else ' ' f += 'R' if flags & 256 else ' ' return f def argspec_print(filt, verbose): if filt is None: gdb.write("{start:>16} {end:<16} {flag:4} {name}\n". format(start="Start", end="End", flag="Flag", name="Name")) return tr = filt['trigger'] flags = tr['flags'] if (flags & TR_FLAG_ARGS) == 0: return gdb.write("{start:>16} - {end:<16} : {flag:4} {name}\n". format(start=hex(filt['start']), end=hex(filt['end']), flag=argspec_flag(flags), name=filt['name'].string())) if verbose: gdb.write(" argspec = {spec}\n".format(spec=trigger_argspec(tr))) uftrace-0.9.4/gdb/uftrace/utils.py000066400000000000000000000044571362052523300170670ustar00rootroot00000000000000# # gdb helper commands and functions for uftrace debugging # copied from the Linux kernel source # # common utilities # # Copyright (c) Siemens AG, 2011-2013 # # Authors: # Jan Kiszka # # This work is licensed under the terms of the GNU GPL version 2. # import gdb class CachedType: def __init__(self, name): self._type = None self._name = name def _new_objfile_handler(self, event): self._type = None gdb.events.new_objfile.disconnect(self._new_objfile_handler) def get_type(self): if self._type is None: self._type = gdb.lookup_type(self._name) if self._type is None: raise gdb.GdbError( "cannot resolve type '{0}'".format(self._name)) if hasattr(gdb, 'events') and hasattr(gdb.events, 'new_objfile'): gdb.events.new_objfile.connect(self._new_objfile_handler) return self._type long_type = CachedType("long") def get_long_type(): global long_type return long_type.get_type() def offset_of(typeobj, field): element = gdb.Value(0).cast(typeobj) return int(str(element[field].address).split()[0], 16) def container_of(ptr, typeobj, member): return (ptr.cast(get_long_type()) - offset_of(typeobj, member)).cast(typeobj) class ContainerOf(gdb.Function): """Return pointer to containing data structure. $container_of(PTR, "TYPE", "ELEMENT"): Given PTR, return a pointer to the data structure of the type TYPE in which PTR is the address of ELEMENT. Note that TYPE and ELEMENT have to be quoted as strings.""" def __init__(self): super(ContainerOf, self).__init__("container_of") def invoke(self, ptr, typename, elementname): return container_of(ptr, gdb.lookup_type(typename.string()).pointer(), elementname.string()) ContainerOf() def gdb_eval_or_none(expresssion): try: return gdb.parse_and_eval(expresssion) except: return None class UftTest(gdb.Command): """test gdb python scripting""" def __init__(self): super(UftTest, self).__init__("uft-test", gdb.COMMAND_DATA, gdb.COMPLETE_EXPRESSION) def invoke(self, arg, from_tty): gdb.write("python scripting test\n") UftTest() uftrace-0.9.4/libmcount/000077500000000000000000000000001362052523300151525ustar00rootroot00000000000000uftrace-0.9.4/libmcount/dynamic.c000066400000000000000000000276621362052523300167570ustar00rootroot00000000000000#include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "dynamic" #define PR_DOMAIN DBG_DYNAMIC #include "libmcount/mcount.h" #include "libmcount/internal.h" #include "utils/utils.h" #include "utils/symbol.h" #include "utils/filter.h" #include "utils/rbtree.h" #include "utils/list.h" static struct mcount_dynamic_info *mdinfo; static struct mcount_dynamic_stats { int total; int failed; int skipped; int nomatch; int unpatch; } stats; #define PAGE_SIZE 4096 #define CODE_CHUNK (PAGE_SIZE * 8) struct code_page { struct list_head list; void *page; int pos; }; static LIST_HEAD(code_pages); static struct rb_root code_tree = RB_ROOT; static struct mcount_orig_insn *lookup_code(struct rb_root *root, unsigned long addr, bool create) { struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; struct mcount_orig_insn *iter; while (*p) { parent = *p; iter = rb_entry(parent, struct mcount_orig_insn, node); if (iter->addr == addr) return iter; if (iter->addr > addr) p = &parent->rb_left; else p = &parent->rb_right; } if (!create) return NULL; iter = xmalloc(sizeof(*iter)); iter->addr = addr; rb_link_node(&iter->node, parent, p); rb_insert_color(&iter->node, root); return iter; } struct mcount_orig_insn *mcount_save_code(struct mcount_disasm_info *info, void *jmp_insn, unsigned jmp_size) { struct code_page *cp = NULL; struct mcount_orig_insn *orig; int patch_size; if (unlikely(info->modified)) { /* it needs to save original instructions as well */ int orig_size = ALIGN(info->orig_size, 16); int copy_size = ALIGN(info->copy_size + jmp_size, 16); patch_size = ALIGN(copy_size + orig_size, 32); } else { patch_size = ALIGN(info->copy_size + jmp_size, 32); } if (!list_empty(&code_pages)) cp = list_last_entry(&code_pages, struct code_page, list); if (cp == NULL || (cp->pos + patch_size > CODE_CHUNK)) { cp = xmalloc(sizeof(*cp)); cp->page = mmap(NULL, CODE_CHUNK, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (cp->page == MAP_FAILED) pr_err("mmap code page failed"); cp->pos = 0; list_add_tail(&cp->list, &code_pages); } orig = lookup_code(&code_tree, info->addr, true); orig->insn = cp->page + cp->pos; orig->orig = orig->insn; orig->orig_size = info->orig_size; orig->insn_size = info->copy_size + jmp_size; if (info->modified) { /* save original instructions before modification */ orig->orig = orig->insn + patch_size - ALIGN(info->orig_size, 16); memcpy(orig->orig, (void *)info->addr, info->orig_size); } memcpy(orig->insn, info->insns, info->copy_size); memcpy(orig->insn + info->copy_size, jmp_insn, jmp_size); cp->pos += patch_size; return orig; } void mcount_freeze_code(void) { struct code_page *cp; list_for_each_entry(cp, &code_pages, list) mprotect(cp->page, CODE_CHUNK, PROT_READ|PROT_EXEC); } void *mcount_find_code(unsigned long addr) { struct mcount_orig_insn *orig; orig = lookup_code(&code_tree, addr, false); if (orig == NULL) return NULL; return orig->insn; } struct mcount_orig_insn * mcount_find_insn(unsigned long addr) { return lookup_code(&code_tree, addr, false); } /* dummy functions (will be overridden by arch-specific code) */ __weak int mcount_setup_trampoline(struct mcount_dynamic_info *mdi) { return -1; } __weak void mcount_cleanup_trampoline(struct mcount_dynamic_info *mdi) { } __weak int mcount_patch_func(struct mcount_dynamic_info *mdi, struct sym *sym, struct mcount_disasm_engine *disasm, unsigned min_size) { return -1; } __weak int mcount_unpatch_func(struct mcount_dynamic_info *mdi, struct sym *sym, struct mcount_disasm_engine *disasm) { return -1; } __weak void mcount_arch_find_module(struct mcount_dynamic_info *mdi, struct symtab *symtab) { mdi->arch = NULL; } __weak void mcount_arch_dynamic_recover(struct mcount_dynamic_info *mdi, struct mcount_disasm_engine *disasm) { } __weak void mcount_disasm_init(struct mcount_disasm_engine *disasm) { } __weak void mcount_disasm_finish(struct mcount_disasm_engine *disasm) { } struct find_module_data { struct symtabs *symtabs; bool needs_modules; }; /* callback for dl_iterate_phdr() */ static int find_dynamic_module(struct dl_phdr_info *info, size_t sz, void *data) { struct mcount_dynamic_info *mdi; struct find_module_data *fmd = data; struct symtabs *symtabs = fmd->symtabs; struct uftrace_mmap *map; bool base_addr_set = false; unsigned i; mdi = xzalloc(sizeof(*mdi)); for (i = 0; i < info->dlpi_phnum; i++) { if (info->dlpi_phdr[i].p_type != PT_LOAD) continue; if (!base_addr_set) { mdi->base_addr = info->dlpi_phdr[i].p_vaddr; base_addr_set = true; } if (!(info->dlpi_phdr[i].p_flags & PF_X)) continue; /* find address and size of code segment */ mdi->text_addr = info->dlpi_phdr[i].p_vaddr; mdi->text_size = info->dlpi_phdr[i].p_memsz; break; } mdi->base_addr += info->dlpi_addr; mdi->text_addr += info->dlpi_addr; INIT_LIST_HEAD(&mdi->bad_syms); map = find_map(symtabs, mdi->base_addr); if (map && map->mod) { mdi->map = map; mcount_arch_find_module(mdi, &map->mod->symtab); mdi->next = mdinfo; mdinfo = mdi; } else { free(mdi); } return !fmd->needs_modules; } static void prepare_dynamic_update(struct mcount_disasm_engine *disasm, struct symtabs *symtabs, bool needs_modules) { struct find_module_data fmd = { .symtabs = symtabs, .needs_modules = needs_modules, }; mcount_disasm_init(disasm); dl_iterate_phdr(find_dynamic_module, &fmd); } struct mcount_dynamic_info *setup_trampoline(struct uftrace_mmap *map) { struct mcount_dynamic_info *mdi; for (mdi = mdinfo; mdi != NULL; mdi = mdi->next) { if (map == mdi->map) break; } if (mdi != NULL && mdi->trampoline == 0) { if (mcount_setup_trampoline(mdi) < 0) mdi = NULL; } return mdi; } struct patt_list { struct list_head list; struct uftrace_pattern patt; char *module; bool positive; }; static bool match_pattern_list(struct list_head *patterns, struct uftrace_mmap *map, char *sym_name) { struct patt_list *pl; bool ret = false; list_for_each_entry(pl, patterns, list) { char *libname = basename(map->libname); if (strncmp(libname, pl->module, strlen(pl->module))) continue; if (match_filter_pattern(&pl->patt, sym_name)) ret = pl->positive; } return ret; } static int do_dynamic_update(struct symtabs *symtabs, char *patch_funcs, enum uftrace_pattern_type ptype, struct mcount_disasm_engine *disasm, unsigned min_size) { struct uftrace_mmap *map; struct symtab *symtab; struct strv funcs = STRV_INIT; char *def_mod; char *name; int j; /* skip special startup (csu) functions */ const char *csu_skip_syms[] = { "_start", "__libc_csu_init", "__libc_csu_fini", }; LIST_HEAD(patterns); struct patt_list *pl; bool all_negative = true; if (patch_funcs == NULL) return 0; def_mod = basename(symtabs->exec_map->libname); strv_split(&funcs, patch_funcs, ";"); strv_for_each(&funcs, name, j) { char *delim; pl = xzalloc(sizeof(*pl)); if (name[0] == '!') name++; else { pl->positive = true; all_negative = false; } delim = strchr(name, '@'); if (delim == NULL) { pl->module = xstrdup(def_mod); } else { *delim = '\0'; pl->module = xstrdup(++delim); } init_filter_pattern(ptype, &pl->patt, name); list_add_tail(&pl->list, &patterns); } /* prepend match-all pattern, if all patterns are negative */ if (all_negative) { pl = xzalloc(sizeof(*pl)); pl->positive = true; pl->module = xstrdup(def_mod); if (ptype == PATT_REGEX) init_filter_pattern(ptype, &pl->patt, "."); else init_filter_pattern(PATT_GLOB, &pl->patt, "*"); list_add(&pl->list, &patterns); } for_each_map(symtabs, map) { bool found = false; bool csu_skip; unsigned i, k; struct sym *sym; struct mcount_dynamic_info *mdi; /* TODO: filter out unsuppported libs */ mdi = setup_trampoline(map); if (mdi == NULL) continue; symtab = &map->mod->symtab; for (i = 0; i < symtab->nr_sym; i++) { sym = &symtab->sym[i]; csu_skip = false; for (k = 0; k < ARRAY_SIZE(csu_skip_syms); k++) { if (!strcmp(sym->name, csu_skip_syms[k])) { csu_skip = true; break; } } if (csu_skip) continue; if (sym->type != ST_LOCAL_FUNC && sym->type != ST_GLOBAL_FUNC) continue; if (!match_pattern_list(&patterns, map, sym->name)) { if (mcount_unpatch_func(mdi, sym, disasm) == 0) stats.unpatch++; continue; } found = true; switch (mcount_patch_func(mdi, sym, disasm, min_size)) { case INSTRUMENT_FAILED: stats.failed++; break; case INSTRUMENT_SKIPPED: stats.skipped++; break; case INSTRUMENT_SUCCESS: default: break; } stats.total++; } if (!found) stats.nomatch++; } if (stats.failed + stats.skipped + stats.nomatch == 0) { pr_dbg("patched all (%d) functions in '%s'\n", stats.total, basename(symtabs->filename)); } while (!list_empty(&patterns)) { struct patt_list *pl; pl = list_first_entry(&patterns, struct patt_list, list); list_del(&pl->list); free(pl->module); free(pl); } strv_free(&funcs); return 0; } static void finish_dynamic_update(struct mcount_disasm_engine *disasm) { struct mcount_dynamic_info *mdi, *tmp; mdi = mdinfo; while (mdi) { tmp = mdi->next; mcount_arch_dynamic_recover(mdi, disasm); mcount_cleanup_trampoline(mdi); free(mdi); mdi = tmp; } mcount_disasm_finish(disasm); mcount_freeze_code(); } /* do not use floating-point in libmcount */ static int calc_percent(int n, int total, int *rem) { int quot = 100 * n / total; *rem = (100 * n - quot * total) * 100 / total; return quot; } int mcount_dynamic_update(struct symtabs *symtabs, char *patch_funcs, enum uftrace_pattern_type ptype, struct mcount_disasm_engine *disasm) { int ret = 0; char *size_filter; unsigned min_size = 0; bool needs_modules = !!strchr(patch_funcs, '@'); prepare_dynamic_update(disasm, symtabs, needs_modules); size_filter = getenv("UFTRACE_PATCH_SIZE"); if (size_filter != NULL) min_size = strtoul(size_filter, NULL, 0); ret = do_dynamic_update(symtabs, patch_funcs, ptype, disasm, min_size); if (stats.total && stats.failed) { int success = stats.total - stats.failed - stats.skipped; int r, q; pr_dbg("dynamic patch stats for '%s'\n", basename(symtabs->filename)); pr_dbg(" total: %8d\n", stats.total); q = calc_percent(success, stats.total, &r); pr_dbg(" patched: %8d (%2d.%02d%%)\n", success, q, r); q = calc_percent(stats.failed, stats.total, &r); pr_dbg(" failed: %8d (%2d.%02d%%)\n", stats.failed, q, r); q = calc_percent(stats.skipped, stats.total, &r); pr_dbg(" skipped: %8d (%2d.%02d%%)\n", stats.skipped, q, r); pr_dbg("no match: %8d\n", stats.nomatch); } finish_dynamic_update(disasm); return ret; } struct dynamic_bad_symbol * mcount_find_badsym(struct mcount_dynamic_info *mdi, unsigned long addr) { struct sym *sym; struct dynamic_bad_symbol *badsym; sym = find_sym(&mdi->map->mod->symtab, addr - mdi->map->start); if (sym == NULL) return NULL; list_for_each_entry(badsym, &mdi->bad_syms, list) { if (badsym->sym == sym) return badsym; } return NULL; } bool mcount_add_badsym(struct mcount_dynamic_info *mdi, unsigned long callsite, unsigned long target) { struct sym *sym; struct dynamic_bad_symbol *badsym; if (mcount_find_badsym(mdi, target)) return true; sym = find_sym(&mdi->map->mod->symtab, target - mdi->map->start); if (sym == NULL) return true; /* only care about jumps to the middle of a function */ if (sym->addr + mdi->map->start == target) return false; pr_dbg2("bad jump: %s:%lx to %lx\n", sym ? sym->name : "", callsite - mdi->map->start, target - mdi->map->start); badsym = xmalloc(sizeof(*badsym)); badsym->sym = sym; badsym->reverted = false; list_add_tail(&badsym->list, &mdi->bad_syms); return true; } uftrace-0.9.4/libmcount/event.c000066400000000000000000000127261362052523300164470ustar00rootroot00000000000000#include #include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "event" #define PR_DOMAIN DBG_EVENT #include "libmcount/mcount.h" #include "libmcount/internal.h" #include "utils/utils.h" #include "utils/list.h" #include "utils/filter.h" #define SDT_SECT ".note.stapsdt" #define SDT_NAME "stapsdt" #define SDT_TYPE 3 /* systemtap SDT data structure */ struct stapsdt { unsigned long probe_addr; unsigned long link_addr; unsigned long sema_addr; char vea[]; /* vendor + event + arguments */ }; /* user-given event specifier (may contains patterns) */ struct event_spec { struct list_head list; struct uftrace_pattern provider; struct uftrace_pattern event; }; /* list of event spec */ static LIST_HEAD(events); /* event id which is allocated dynamically */ static unsigned event_id = EVENT_ID_USER; __weak int mcount_arch_enable_event(struct mcount_event_info *mei) { return 0; } static int search_sdt_event(struct dl_phdr_info *info, size_t sz, void *data) { const char *name = info->dlpi_name; struct mcount_event_info *mei; struct list_head *spec_list = data; struct uftrace_elf_data elf; struct uftrace_elf_iter iter; bool found_sdt = false; int ret = -1; if (name[0] == '\0') name = read_exename(); if (elf_init(name, &elf) < 0) { pr_dbg("error during open file: %s: %m\n", name); return -1; } elf_for_each_shdr(&elf, &iter) { char *shstr; if (iter.shdr.sh_type != SHT_NOTE) continue; /* there can be more than one note sections */ shstr = elf_get_name(&elf, &iter, iter.shdr.sh_name); if (strcmp(shstr, SDT_SECT) == 0) { found_sdt = true; break; } } if (!found_sdt) { ret = 0; goto out; } pr_dbg2("loading sdt notes from %s\n", name); elf_for_each_note(&elf, &iter) { struct stapsdt *sdt; struct event_spec *spec; char *vendor, *event, *args; if (strncmp(iter.note_name, SDT_NAME, iter.nhdr.n_namesz)) continue; if (iter.nhdr.n_type != SDT_TYPE) continue; sdt = iter.note_desc; vendor = sdt->vea; event = vendor + strlen(vendor) + 1; args = event + strlen(event) + 1; if (list_empty(spec_list)) { /* just listing available events */ pr_out("[SDT event] %s:%s %s\n", vendor, event, args); continue; } list_for_each_entry(spec, spec_list, list) { if (!match_filter_pattern(&spec->provider, vendor)) continue; if (!match_filter_pattern(&spec->event, event)) continue; break; } if (list_no_entry(spec, spec_list, list)) continue; mei = xmalloc(sizeof(*mei)); mei->id = event_id++; mei->addr = info->dlpi_addr + sdt->probe_addr; mei->module = xstrdup(name); mei->provider = xstrdup(vendor); mei->event = xstrdup(event); mei->arguments = xstrdup(args); pr_dbg("adding SDT event (%s:%s) from %s at %#lx\n", mei->provider, mei->event, mei->module, mei->addr); list_add_tail(&mei->list, &events); } ret = 0; out: elf_finish(&elf); return ret; } int mcount_setup_events(char *dirname, char *event_str, enum uftrace_pattern_type ptype) { int ret = 0; FILE *fp; char *filename = NULL; struct mcount_event_info *mei; struct strv strv = STRV_INIT; LIST_HEAD(specs); struct event_spec *es, *tmp; char *spec; int i; strv_split(&strv, event_str, ";"); strv_for_each(&strv, spec, i) { char *sep = strchr(spec, ':'); char *kernel; if (sep) { *sep++ = '\0'; kernel = has_kernel_filter(sep); if (kernel) continue; es = xmalloc(sizeof(*es)); init_filter_pattern(ptype, &es->provider, spec); init_filter_pattern(ptype, &es->event, sep); list_add_tail(&es->list, &specs); } else { pr_dbg("ignore invalid event spec: %s\n", spec); } } dl_iterate_phdr(search_sdt_event, &specs); list_for_each_entry_safe(es, tmp, &specs, list) { list_del(&es->list); free_filter_pattern(&es->provider); free_filter_pattern(&es->event); free(es); } strv_free(&strv); if (list_empty(&events)) { pr_dbg("cannot find any event for %s\n", event_str); goto out; } xasprintf(&filename, "%s/events.txt", dirname); fp = fopen(filename, "w"); if (fp == NULL) pr_err("cannot open file: %s", filename); list_for_each_entry(mei, &events, list) { fprintf(fp, "EVENT: %u %s:%s\n", mei->id, mei->provider, mei->event); } fclose(fp); free(filename); list_for_each_entry(mei, &events, list) { /* ignore failures */ mcount_arch_enable_event(mei); } out: return ret; } struct mcount_event_info * mcount_lookup_event(unsigned long addr) { struct mcount_event_info *mei; list_for_each_entry(mei, &events, list) { if (mei->addr == addr) return mei; } return NULL; } void mcount_list_events(void) { LIST_HEAD(list); dl_iterate_phdr(search_sdt_event, &list); } /* save an asynchronous event */ int mcount_save_event(struct mcount_event_info *mei) { struct mcount_thread_data *mtdp; if (unlikely(mcount_should_stop())) return -1; mtdp = get_thread_data(); if (unlikely(check_thread_data(mtdp))) return -1; if (mtdp->nr_events < MAX_EVENT) { int i = mtdp->nr_events++; mtdp->event[i].id = mei->id; mtdp->event[i].time = mcount_gettime(); mtdp->event[i].dsize = 0; mtdp->event[i].idx = ASYNC_IDX; } return 0; } void mcount_finish_events(void) { struct mcount_event_info *mei, *tmp; list_for_each_entry_safe(mei, tmp, &events, list) { list_del(&mei->list); free(mei->module); free(mei->provider); free(mei->event); free(mei->arguments); free(mei); } } uftrace-0.9.4/libmcount/internal.h000066400000000000000000000332351362052523300171450ustar00rootroot00000000000000/* * internal routines and data structures for handling mcount records * * Copyright (C) 2014-2018, LG Electronics, Namhyung Kim * * Released under the GPL v2. */ #ifndef UFTRACE_MCOUNT_INTERNAL_H #define UFTRACE_MCOUNT_INTERNAL_H #include #include #include #include #include #include #include #ifdef HAVE_LIBCAPSTONE # include #endif #include "uftrace.h" #include "mcount-arch.h" #include "utils/rbtree.h" #include "utils/symbol.h" #include "utils/filter.h" #include "utils/compiler.h" /* could be defined in mcount-arch.h */ #ifndef ARCH_SUPPORT_AUTO_RECOVER # define ARCH_SUPPORT_AUTO_RECOVER 0 #endif /* plt_hooker has return address to hook */ #ifndef ARCH_CAN_RESTORE_PLTHOOK # define ARCH_CAN_RESTORE_PLTHOOK 0 #endif enum filter_result { FILTER_RSTACK = -1, FILTER_OUT, FILTER_IN, }; #ifndef DISABLE_MCOUNT_FILTER struct filter_control { int in_count; int out_count; uint16_t depth; uint16_t saved_depth; uint64_t time; uint64_t saved_time; }; #else struct filter_control {}; #endif struct mcount_shmem { unsigned seqnum; int losts; int curr; int nr_buf; int max_buf; bool done; struct mcount_shmem_buffer **buffer; }; /* first 4 byte saves the actual size of the argbuf */ #define ARGBUF_SIZE 1024 #define EVTBUF_SIZE (ARGBUF_SIZE - 16) #define EVTBUF_HDR (offsetof(struct mcount_event, data)) struct mcount_event { uint64_t time; uint32_t id; uint16_t dsize; uint16_t idx; uint8_t data[EVTBUF_SIZE]; }; #define ASYNC_IDX 0xffff #define MAX_EVENT 4 enum mcount_watch_item { MCOUNT_WATCH_NONE = 0, MCOUNT_WATCH_CPU = (1 << 0), }; struct mcount_watchpoint { bool inited; /* per-thread watch points */ int cpu; /* global watch points */ }; #ifndef DISABLE_MCOUNT_FILTER struct mcount_mem_regions { struct rb_root root; unsigned long heap; unsigned long brk; }; void finish_mem_region(struct mcount_mem_regions *regions); #else struct mcount_mem_regions {}; static inline void finish_mem_region(struct mcount_mem_regions *regions) {} #endif /* * The idx and record_idx are to save current index of the rstack. * In general, both will have same value but in case of cygprof * functions, it may differ if filters applied. * * This is because how cygprof handles filters - cygprof_exit() should * be called for filtered functions while mcount_exit() is not. The * mcount_record_idx is only increased/decreased when the function is * not filtered out so that we can keep proper depth in the output. */ struct mcount_thread_data { int tid; int idx; int record_idx; bool recursion_marker; bool in_exception; bool dead; bool warned; unsigned long cygprof_dummy; struct mcount_ret_stack *rstack; void *argbuf; struct filter_control filter; bool enable_cached; struct mcount_shmem shmem; struct mcount_event event[MAX_EVENT]; int nr_events; struct mcount_mem_regions mem_regions; struct mcount_watchpoint watch; struct mcount_arch_context arch; struct list_head pmu_fds; }; #ifdef HAVE_MCOUNT_ARCH_CONTEXT extern void mcount_save_arch_context(struct mcount_arch_context *ctx); extern void mcount_restore_arch_context(struct mcount_arch_context *ctx); #else static inline void mcount_save_arch_context(struct mcount_arch_context *ctx) {} static inline void mcount_restore_arch_context(struct mcount_arch_context *ctx) {} #endif #ifdef SINGLE_THREAD # define TLS # define get_thread_data() &mtd # define check_thread_data(mtdp) (mtdp->rstack == NULL) #else # define TLS __thread # define get_thread_data() pthread_getspecific(mtd_key) # define check_thread_data(mtdp) (mtdp == NULL) #endif extern TLS struct mcount_thread_data mtd; void __mcount_guard_recursion(struct mcount_thread_data *mtdp); void __mcount_unguard_recursion(struct mcount_thread_data *mtdp); bool mcount_guard_recursion(struct mcount_thread_data *mtdp); void mcount_unguard_recursion(struct mcount_thread_data *mtdp); extern uint64_t mcount_threshold; /* nsec */ extern pthread_key_t mtd_key; extern int shmem_bufsize; extern int pfd; extern char *mcount_exename; extern int page_size_in_kb; extern bool kernel_pid_update; extern bool mcount_auto_recover; enum mcount_global_flag { MCOUNT_GFL_SETUP = (1U << 0), MCOUNT_GFL_FINISH = (1U << 1), }; extern unsigned long mcount_global_flags; static inline bool mcount_should_stop(void) { return mcount_global_flags != 0UL; } #ifdef DISABLE_MCOUNT_FILTER static inline void mcount_filter_setup(struct mcount_thread_data *mtdp) {} static inline void mcount_filter_release(struct mcount_thread_data *mtdp) {} static inline void mcount_watch_init(void) {} static inline void mcount_watch_setup(struct mcount_thread_data *mtdp) {} static inline void mcount_watch_release(struct mcount_thread_data *mtdp) {} #endif /* DISABLE_MCOUNT_FILTER */ static inline uint64_t mcount_gettime(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec; } static inline int mcount_gettid(struct mcount_thread_data *mtdp) { if (!mtdp->tid) mtdp->tid = syscall(SYS_gettid); return mtdp->tid; } /* * calling memcpy or memset in libmcount might clobber some registers. */ static inline void mcount_memset1(void *dst, unsigned char d, int len) { unsigned char *p = dst; while (len-- > 0) *p++ = d; } static inline void mcount_memcpy1(void * restrict dst, const void * restrict src, int len) { unsigned char * restrict p = dst; const unsigned char * restrict q = src; while (len-- > 0) *p++ = *q++; } static inline void mcount_memset4(void *dst, unsigned int d, int len) { unsigned int *p = dst; int len4 = len / 4; while (len4-- > 0) *p++ = d; } static inline void mcount_memcpy4(void * restrict dst, const void * restrict src, int len) { unsigned int * restrict p = dst; const unsigned int * restrict q = src; int len4 = len / 4; while (len4-- > 0) *p++ = *q++; } extern void mcount_return(void); extern void dynamic_return(void); extern unsigned long plthook_return(void); extern unsigned long mcount_return_fn; extern struct mcount_thread_data * mcount_prepare(void); extern void update_kernel_tid(int tid); extern const char *mcount_session_name(void); extern void uftrace_send_message(int type, void *data, size_t len); extern void build_debug_domain(char *dbg_domain_str); extern void mcount_rstack_restore(struct mcount_thread_data *mtdp); extern void mcount_rstack_reset(struct mcount_thread_data *mtdp); extern void mcount_rstack_reset_exception(struct mcount_thread_data *mtdp, unsigned long frame_addr); extern void mcount_auto_restore(struct mcount_thread_data *mtdp); extern void mcount_auto_reset(struct mcount_thread_data *mtdp); extern bool mcount_rstack_has_plthook(struct mcount_thread_data *mtdp); extern void prepare_shmem_buffer(struct mcount_thread_data *mtdp); extern void clear_shmem_buffer(struct mcount_thread_data *mtdp); extern void shmem_finish(struct mcount_thread_data *mtdp); enum plthook_special_action { PLT_FL_SKIP = 1U << 0, PLT_FL_LONGJMP = 1U << 1, PLT_FL_SETJMP = 1U << 2, PLT_FL_VFORK = 1U << 3, PLT_FL_FLUSH = 1U << 4, PLT_FL_EXCEPT = 1U << 5, PLT_FL_RESOLVE = 1U << 6, PLT_FL_DLSYM = 1U << 7, }; struct plthook_special_func { unsigned idx; unsigned flags; /* enum plthook_special_action */ }; struct plthook_skip_symbol { const char *name; void *addr; }; struct plthook_data { /* links to the 'plthook_modules' list */ struct list_head list; /* full path of this module */ const char *mod_name; /* used by dynamic linker (PLT resolver), saved in GOT[1] */ unsigned long module_id; /* base address where this module is loaded */ unsigned long base_addr; /* start address of PLT code (PLT0) */ unsigned long plt_addr; /* symbol table for PLT functions */ struct symtab dsymtab; /* address of global offset table (GOT) used for PLT */ unsigned long *pltgot_ptr; /* original address of each function (resolved by dynamic linker) */ unsigned long *resolved_addr; /* array of function that needs special care (see above) */ struct plthook_special_func *special_funcs; int nr_special; /* architecture-specific info */ void *arch; }; unsigned long setup_pltgot(struct plthook_data *pd, int got_idx, int sym_idx, void *data); extern void mcount_setup_plthook(char *exename, bool nest_libcall); extern void setup_dynsym_indexes(struct plthook_data *pd); extern void destroy_dynsym_indexes(void); extern unsigned long mcount_arch_plthook_addr(struct plthook_data *pd, int idx); extern unsigned long plthook_resolver_addr; extern const struct plthook_skip_symbol plt_skip_syms[]; extern size_t plt_skip_nr; struct uftrace_trigger; struct uftrace_arg_spec; struct mcount_regs; struct mcount_arg_context { struct mcount_regs *regs; unsigned long *stack_base; long *retval; union { unsigned long i; void *p; double f; struct { long lo; long hi; } ll; unsigned char v[16]; } __align(16) val; struct mcount_mem_regions *regions; struct mcount_arch_context *arch; }; extern void mcount_arch_get_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec); extern void mcount_arch_get_retval(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec); extern enum filter_result mcount_entry_filter_check(struct mcount_thread_data *mtdp, unsigned long child, struct uftrace_trigger *tr); extern void mcount_entry_filter_record(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, struct uftrace_trigger *tr, struct mcount_regs *regs); extern void mcount_exit_filter_record(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, long *retval); extern int record_trace_data(struct mcount_thread_data *mtdp, struct mcount_ret_stack *mrstack, long *retval); extern void record_proc_maps(char *dirname, const char *sess_id, struct symtabs *symtabs); #ifndef DISABLE_MCOUNT_FILTER extern void save_argument(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, struct list_head *args_spec, struct mcount_regs *regs); void save_retval(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, long *retval); void save_trigger_read(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, enum trigger_read_type type, bool diff); #endif /* DISABLE_MCOUNT_FILTER */ bool check_mem_region(struct mcount_arg_context *ctx, unsigned long addr); void save_watchpoint(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, unsigned long watchpoints); struct mcount_dynamic_info { struct mcount_dynamic_info *next; struct uftrace_mmap *map; unsigned long base_addr; unsigned long text_addr; int text_size; unsigned long trampoline; struct list_head bad_syms; void *arch; }; struct mcount_disasm_engine { #ifdef HAVE_LIBCAPSTONE csh engine; #endif }; #define INSTRUMENT_SUCCESS 0 #define INSTRUMENT_FAILED -1 #define INSTRUMENT_SKIPPED -2 int mcount_dynamic_update(struct symtabs *symtabs, char *patch_funcs, enum uftrace_pattern_type ptype, struct mcount_disasm_engine *disasm); struct mcount_orig_insn { struct rb_node node; unsigned long addr; void *orig; void *insn; int orig_size; int insn_size; }; /* * mcount_disasm_info - information for dynamic patch * @sym : symbol for the function * @addr : currently targeted function address. * @insns : byte array to store instruction. * @orig_size : size of original instructions * @copy_size : size of copied instructions (may be modified) * @modified : whether instruction is changed * @has_jump : whether jump_target should be added */ struct mcount_disasm_info { struct sym *sym; unsigned long addr; unsigned char insns[64]; int orig_size; int copy_size; bool modified; bool has_jump; }; struct mcount_orig_insn *mcount_save_code(struct mcount_disasm_info *info, void *jmp_insn, unsigned jmp_size); void *mcount_find_code(unsigned long addr); struct mcount_orig_insn * mcount_find_insn(unsigned long addr); void mcount_freeze_code(void); /* these should be implemented for each architecture */ int mcount_setup_trampoline(struct mcount_dynamic_info *adi); void mcount_cleanup_trampoline(struct mcount_dynamic_info *mdi); int mcount_patch_func(struct mcount_dynamic_info *mdi, struct sym *sym, struct mcount_disasm_engine *disasm, unsigned min_size); void mcount_disasm_init(struct mcount_disasm_engine *disasm); void mcount_disasm_finish(struct mcount_disasm_engine *disasm); struct dynamic_bad_symbol { struct list_head list; struct sym *sym; bool reverted; }; struct dynamic_bad_symbol * mcount_find_badsym(struct mcount_dynamic_info *mdi, unsigned long addr); bool mcount_add_badsym(struct mcount_dynamic_info *mdi, unsigned long callsite, unsigned long target); struct mcount_event_info { char *module; char *provider; char *event; char *arguments; unsigned id; unsigned long addr; struct list_head list; }; int mcount_setup_events(char *dirname, char *event_str, enum uftrace_pattern_type ptype); struct mcount_event_info * mcount_lookup_event(unsigned long addr); int mcount_save_event(struct mcount_event_info *mei); void mcount_finish_events(void); void mcount_list_events(void); int mcount_arch_enable_event(struct mcount_event_info *mei); void mcount_hook_functions(void); int read_pmu_event(struct mcount_thread_data *mtdp, enum uftrace_event_id id, void *buf); void release_pmu_event(struct mcount_thread_data *mtdp, enum uftrace_event_id id); void finish_pmu_event(struct mcount_thread_data *mtdp); #endif /* UFTRACE_MCOUNT_INTERNAL_H */ uftrace-0.9.4/libmcount/mcount-nop.c000066400000000000000000000013541362052523300174200ustar00rootroot00000000000000/* * dummy mcount() routine for uftrace * * Copyright (C) 2015-2017, LG Electronics, Namhyung Kim * * Released under the GPL v2. */ #include "utils/compiler.h" void __visible_default mcount(void) { } void __visible_default _mcount(void) { } void __visible_default __gnu_mcount_nc(void) { } void __visible_default __fentry__(void) { } void __visible_default __cyg_profile_func_enter(void *child, void *parent) { } void __visible_default __cyg_profile_func_exit(void *child, void *parent) { } void __visible_default __monstartup(unsigned long low, unsigned long high) { } void __visible_default _mcleanup(void) { } void __visible_default mcount_restore(void) { } void __visible_default mcount_reset(void) { } uftrace-0.9.4/libmcount/mcount.c000066400000000000000000001312051362052523300166250ustar00rootroot00000000000000/* * mcount() handling routines for uftrace * * Copyright (C) 2014-2018, LG Electronics, Namhyung Kim * * Released under the GPL v2. */ #include #include #include #include #include #include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "mcount" #define PR_DOMAIN DBG_MCOUNT #include "libmcount/mcount.h" #include "libmcount/internal.h" #include "mcount-arch.h" #include "version.h" #include "utils/utils.h" #include "utils/symbol.h" #include "utils/filter.h" #include "utils/script.h" /* time filter in nsec */ uint64_t mcount_threshold; /* symbol table of main executable */ struct symtabs symtabs = { .flags = SYMTAB_FL_DEMANGLE | SYMTAB_FL_ADJ_OFFSET, }; /* size of shmem buffer to save uftrace_record */ int shmem_bufsize = SHMEM_BUFFER_SIZE; /* recover return address of parent automatically */ bool mcount_auto_recover = ARCH_SUPPORT_AUTO_RECOVER; /* global flag to control mcount behavior */ unsigned long mcount_global_flags = MCOUNT_GFL_SETUP; /* TSD key to save mtd below */ pthread_key_t mtd_key = (pthread_key_t)-1; /* thread local data to trace function execution */ TLS struct mcount_thread_data mtd; /* pipe file descriptor to communite to uftrace */ int pfd = -1; /* maximum depth of mcount rstack */ static int mcount_rstack_max = MCOUNT_RSTACK_MAX; /* name of main executable */ char *mcount_exename; /* whether it should update pid filter manually */ bool kernel_pid_update; /* system page size */ int page_size_in_kb; /* call depth to filter */ static int __maybe_unused mcount_depth = MCOUNT_DEFAULT_DEPTH; /* boolean flag to turn on/off recording */ static bool __maybe_unused mcount_enabled = true; /* function filtering mode - inclusive or exclusive */ static enum filter_mode __maybe_unused mcount_filter_mode = FILTER_MODE_NONE; /* tree of trigger actions */ static struct rb_root __maybe_unused mcount_triggers = RB_ROOT; /* bitmask of active watch points */ static unsigned long __maybe_unused mcount_watchpoints; /* whether caller filter is activated */ static bool __maybe_unused mcount_has_caller; /* address of function will be called when a function returns */ unsigned long mcount_return_fn; /* disassembly engine for dynamic code patch */ static struct mcount_disasm_engine disasm; __weak void dynamic_return(void) { } #ifdef DISABLE_MCOUNT_FILTER static void mcount_filter_init(enum uftrace_pattern_type ptype, char *dirname, bool force) { if (getenv("UFTRACE_SRCLINE") == NULL) return; symtabs.exec_map->mod = load_module_symtab(&symtabs, symtabs.exec_map->libname); /* use debug info if available */ prepare_debug_info(&symtabs, ptype, NULL, NULL, false, force); save_debug_info(&symtabs, dirname); } static void mcount_filter_finish(void) { finish_debug_info(&symtabs); } #else /* be careful: this can be called from signal handler */ static void mcount_finish_trigger(void) { if (mcount_global_flags & MCOUNT_GFL_FINISH) return; /* mark other threads can see the finish flag */ mcount_global_flags |= MCOUNT_GFL_FINISH; } static LIST_HEAD(siglist); struct signal_trigger_item { struct list_head list; int sig; struct uftrace_trigger tr; }; static struct uftrace_trigger * get_signal_trigger(int sig) { struct signal_trigger_item *item; list_for_each_entry(item, &siglist, list) { if (item->sig == sig) return &item->tr; } return NULL; } static void add_signal_trigger(int sig, const char *name, struct uftrace_trigger *tr) { struct signal_trigger_item *item; item = xmalloc(sizeof(*item)); item->sig = sig; memcpy(&item->tr, tr, sizeof(*tr)); pr_dbg("add signal trigger: %s (%d), flags = %lx\n", name, sig, (unsigned long)tr->flags); list_add(&item->list, &siglist); } static void mcount_signal_trigger(int sig) { struct uftrace_trigger *tr; tr = get_signal_trigger(sig); if (tr == NULL) return; pr_dbg("got signal %d\n", sig); if (tr->flags & TRIGGER_FL_TRACE_ON) { mcount_enabled = true; } if (tr->flags & TRIGGER_FL_TRACE_OFF) { mcount_enabled = false; } if (tr->flags & TRIGGER_FL_FINISH) { mcount_finish_trigger(); } } #define SIGTABLE_ENTRY(s) { #s, s } static const struct sigtable { const char *name; int sig; } sigtable[] = { SIGTABLE_ENTRY(SIGHUP), SIGTABLE_ENTRY(SIGINT), SIGTABLE_ENTRY(SIGQUIT), SIGTABLE_ENTRY(SIGILL), SIGTABLE_ENTRY(SIGTRAP), SIGTABLE_ENTRY(SIGABRT), SIGTABLE_ENTRY(SIGBUS), SIGTABLE_ENTRY(SIGFPE), SIGTABLE_ENTRY(SIGKILL), SIGTABLE_ENTRY(SIGUSR1), SIGTABLE_ENTRY(SIGSEGV), SIGTABLE_ENTRY(SIGUSR2), SIGTABLE_ENTRY(SIGPIPE), SIGTABLE_ENTRY(SIGALRM), SIGTABLE_ENTRY(SIGTERM), SIGTABLE_ENTRY(SIGSTKFLT), SIGTABLE_ENTRY(SIGCHLD), SIGTABLE_ENTRY(SIGCONT), SIGTABLE_ENTRY(SIGSTOP), SIGTABLE_ENTRY(SIGTSTP), SIGTABLE_ENTRY(SIGTTIN), SIGTABLE_ENTRY(SIGTTOU), SIGTABLE_ENTRY(SIGURG), SIGTABLE_ENTRY(SIGXCPU), SIGTABLE_ENTRY(SIGXFSZ), SIGTABLE_ENTRY(SIGVTALRM), SIGTABLE_ENTRY(SIGPROF), SIGTABLE_ENTRY(SIGWINCH), SIGTABLE_ENTRY(SIGIO), SIGTABLE_ENTRY(SIGPWR), SIGTABLE_ENTRY(SIGSYS), }; #undef SIGTABLE_ENTRY static int parse_sigspec(char *spec, struct uftrace_filter_setting *setting) { char *pos, *tmp; unsigned i; int sig = -1; int off = 0; const char *signame = NULL; bool num_spec = false; char num_spec_str[16]; struct uftrace_trigger tr = { .flags = 0, }; struct sigaction old_sa; struct sigaction sa = { .sa_handler = mcount_signal_trigger, .sa_flags = SA_RESTART, }; const char *sigrtm = "SIGRTM"; const char *sigrtmin = "SIGRTMIN"; const char *sigrtmax = "SIGRTMAX"; pos = strchr(spec, '@'); if (pos == NULL) return -1; *pos = '\0'; if (isdigit(spec[0])) num_spec = true; else if (strncmp(spec, "SIG", 3)) off = 3; /* skip "SIG" prefix */ for (i = 0; i < ARRAY_SIZE(sigtable); i++) { if (num_spec) { int num = strtol(spec, &tmp, 0); if (num == sigtable[i].sig) { sig = num; signame = sigtable[i].name; break; } continue; } if (!strcmp(sigtable[i].name + off, spec)) { sig = sigtable[i].sig; signame = sigtable[i].name; break; } } /* real-time signals */ if (!strncmp(spec, sigrtm + off, 6 - off)) { if (!strncmp(spec, sigrtmin + off, 8 - off)) sig = SIGRTMIN + strtol(&spec[8 - off], NULL, 0); if (!strncmp(spec, sigrtmax + off, 8 - off)) sig = SIGRTMAX + strtol(&spec[8 - off], NULL, 0); signame = spec; } if (sig == -1 && num_spec) { int sigrtmid = (SIGRTMIN + SIGRTMAX) / 2; sig = strtol(spec, &tmp, 0); /* SIGRTMIN/MAX might not be constant, avoid switch/case */ if (sig == SIGRTMIN) { strcpy(num_spec_str, "SIGRTMIN"); } else if (SIGRTMIN < sig && sig <= sigrtmid) { snprintf(num_spec_str, sizeof(num_spec_str), "%s+%d", "SIGRTMIN", sig - SIGRTMIN); } else if (sigrtmid < sig && sig < SIGRTMAX) { snprintf(num_spec_str, sizeof(num_spec_str), "%s-%d", "SIGRTMAX", SIGRTMAX - sig); } else if (sig == SIGRTMAX) { strcpy(num_spec_str, "SIGRTMAX"); } else { sig = -1; } signame = num_spec_str; } if (sig == -1) { pr_use("failed to parse signal: %s\n", spec); return -1; } /* setup_trigger_action() requires the '@' sign */ *pos = '@'; tmp = NULL; if (setup_trigger_action(spec, &tr, &tmp, TRIGGER_FL_SIGNAL, setting) < 0) return -1; if (tmp != NULL) { pr_warn("invalid signal action: %s\n", tmp); free(tmp); return -1; } add_signal_trigger(sig, signame, &tr); if (sigaction(sig, &sa, &old_sa) < 0) { pr_warn("cannot overwrite signal handler for %s\n", spec); sigaction(sig, &old_sa, NULL); return -1; } return 0; } static int mcount_signal_init(char *sigspec, struct uftrace_filter_setting *setting) { struct strv strv = STRV_INIT; char *spec; int i; int ret = 0; if (sigspec == NULL) return 0; strv_split(&strv, sigspec, ";"); strv_for_each(&strv, spec, i) { if (parse_sigspec(spec, setting) < 0) ret = -1; } strv_free(&strv); return ret; } static void mcount_signal_finish(void) { struct signal_trigger_item *item; while (!list_empty(&siglist)) { item = list_first_entry(&siglist, typeof(*item), list); list_del(&item->list); free(item); } } static void mcount_filter_init(enum uftrace_pattern_type ptype, char *dirname, bool force) { char *filter_str = getenv("UFTRACE_FILTER"); char *trigger_str = getenv("UFTRACE_TRIGGER"); char *argument_str = getenv("UFTRACE_ARGUMENT"); char *retval_str = getenv("UFTRACE_RETVAL"); char *autoargs_str = getenv("UFTRACE_AUTO_ARGS"); char *caller_str = getenv("UFTRACE_CALLER"); struct uftrace_filter_setting filter_setting = { .ptype = ptype, .auto_args = false, .allow_kernel = false, .lp64 = host_is_lp64(), .arch = host_cpu_arch(), }; bool needs_debug_info = false; load_module_symtabs(&symtabs); mcount_signal_init(getenv("UFTRACE_SIGNAL"), &filter_setting); /* setup auto-args only if argument/return value is used */ if (argument_str || retval_str || autoargs_str || (trigger_str && (strstr(trigger_str, "arg") || strstr(trigger_str, "retval")))) { setup_auto_args(&filter_setting); needs_debug_info = true; } if (getenv("UFTRACE_SRCLINE")) needs_debug_info = true; /* use debug info if available */ if (needs_debug_info) { prepare_debug_info(&symtabs, ptype, argument_str, retval_str, !!autoargs_str, force); save_debug_info(&symtabs, dirname); } uftrace_setup_filter(filter_str, &symtabs, &mcount_triggers, &mcount_filter_mode, &filter_setting); uftrace_setup_trigger(trigger_str, &symtabs, &mcount_triggers, &mcount_filter_mode, &filter_setting); uftrace_setup_argument(argument_str, &symtabs, &mcount_triggers, &filter_setting); uftrace_setup_retval(retval_str, &symtabs, &mcount_triggers, &filter_setting); if (caller_str) { uftrace_setup_caller_filter(caller_str, &symtabs, &mcount_triggers, &filter_setting); if (uftrace_count_filter(&mcount_triggers, TRIGGER_FL_CALLER) != 0) mcount_has_caller = true; } if (autoargs_str) { char *autoarg = "."; char *autoret = "."; if (ptype == PATT_GLOB) autoarg = autoret = "*"; filter_setting.auto_args = true; uftrace_setup_argument(autoarg, &symtabs, &mcount_triggers, &filter_setting); uftrace_setup_retval(autoret, &symtabs, &mcount_triggers, &filter_setting); } if (getenv("UFTRACE_DEPTH")) mcount_depth = strtol(getenv("UFTRACE_DEPTH"), NULL, 0); if (getenv("UFTRACE_DISABLED")) mcount_enabled = false; } static void mcount_filter_setup(struct mcount_thread_data *mtdp) { mtdp->filter.depth = mcount_depth; mtdp->filter.time = mcount_threshold; mtdp->enable_cached = mcount_enabled; mtdp->argbuf = xmalloc(mcount_rstack_max * ARGBUF_SIZE); INIT_LIST_HEAD(&mtdp->pmu_fds); } static void mcount_filter_release(struct mcount_thread_data *mtdp) { free(mtdp->argbuf); mtdp->argbuf = NULL; finish_pmu_event(mtdp); } static void mcount_filter_finish(void) { uftrace_cleanup_filter(&mcount_triggers); finish_auto_args(); finish_debug_info(&symtabs); mcount_signal_finish(); } static void mcount_watch_init(void) { char *watch_str = getenv("UFTRACE_WATCH"); struct strv watch = STRV_INIT; char *str; int i; if (watch_str == NULL) return; strv_split(&watch, watch_str, ";"); strv_for_each(&watch, str, i) { if (!strcasecmp(str, "cpu")) mcount_watchpoints = MCOUNT_WATCH_CPU; } strv_free(&watch); } static void mcount_watch_setup(struct mcount_thread_data *mtdp) { mtdp->watch.cpu = -1; } static void mcount_watch_release(struct mcount_thread_data *mtdp) { } #endif /* DISABLE_MCOUNT_FILTER */ static void send_session_msg(struct mcount_thread_data *mtdp, const char *sess_id) { struct uftrace_msg_sess sess = { .task = { .time = mcount_gettime(), .pid = getpid(), .tid = mcount_gettid(mtdp), }, .namelen = strlen(mcount_exename), }; struct uftrace_msg msg = { .magic = UFTRACE_MSG_MAGIC, .type = UFTRACE_MSG_SESSION, .len = sizeof(sess) + sess.namelen, }; struct iovec iov[3] = { { .iov_base = &msg, .iov_len = sizeof(msg), }, { .iov_base = &sess, .iov_len = sizeof(sess), }, { .iov_base = mcount_exename, .iov_len = sess.namelen, }, }; int len = sizeof(msg) + msg.len; if (pfd < 0) return; mcount_memcpy4(sess.sid, sess_id, sizeof(sess.sid)); if (writev(pfd, iov, 3) != len) { if (!mcount_should_stop()) pr_err("write tid info failed"); } } static void mcount_trace_finish(bool send_msg) { static pthread_mutex_t finish_lock = PTHREAD_MUTEX_INITIALIZER; static bool trace_finished = false; pthread_mutex_lock(&finish_lock); if (trace_finished) goto unlock; /* dtor for script support */ if (SCRIPT_ENABLED && script_str) script_uftrace_end(); /* notify to uftrace that we're finished */ if (send_msg) uftrace_send_message(UFTRACE_MSG_FINISH, NULL, 0); if (pfd != -1) { close(pfd); pfd = -1; } trace_finished = true; pr_dbg("mcount trace finished\n"); unlock: pthread_mutex_unlock(&finish_lock); } /* to be used by pthread_create_key() */ void mtd_dtor(void *arg) { struct mcount_thread_data *mtdp = arg; struct uftrace_msg_task tmsg; if (mtdp->dead) return; if (mcount_should_stop()) mcount_trace_finish(true); /* this thread is done, do not enter anymore */ mtdp->recursion_marker = true; mtdp->dead = true; mcount_rstack_restore(mtdp); if (ARCH_CAN_RESTORE_PLTHOOK || !mcount_rstack_has_plthook(mtdp)) { free(mtdp->rstack); mtdp->rstack = NULL; mtdp->idx = 0; } mcount_filter_release(mtdp); mcount_watch_release(mtdp); finish_mem_region(&mtdp->mem_regions); shmem_finish(mtdp); tmsg.pid = getpid(), tmsg.tid = mcount_gettid(mtdp), tmsg.time = mcount_gettime(); uftrace_send_message(UFTRACE_MSG_TASK_END, &tmsg, sizeof(tmsg)); } void __mcount_guard_recursion(struct mcount_thread_data *mtdp) { mtdp->recursion_marker = true; } void __mcount_unguard_recursion(struct mcount_thread_data *mtdp) { mtdp->recursion_marker = false; } bool mcount_guard_recursion(struct mcount_thread_data *mtdp) { if (unlikely(mtdp->recursion_marker)) return false; if (unlikely(mcount_should_stop())) { mtd_dtor(mtdp); return false; } mtdp->recursion_marker = true; return true; } void mcount_unguard_recursion(struct mcount_thread_data *mtdp) { mtdp->recursion_marker = false; if (unlikely(mcount_should_stop())) mtd_dtor(mtdp); } static struct sigaction old_sigact[2]; static const struct { int code; char *msg; } sigsegv_codes[] = { { SEGV_MAPERR, "address not mapped" }, { SEGV_ACCERR, "invalid permission" }, #ifdef SEGV_BNDERR { SEGV_BNDERR, "bound check failed" }, #endif #ifdef SEGV_PKUERR { SEGV_PKUERR, "protection key check failed" }, #endif }; static void segv_handler(int sig, siginfo_t *si, void *ctx) { struct mcount_thread_data *mtdp; struct mcount_ret_stack *rstack; int idx; int i; /* set line buffer mode not to discard crash message */ setlinebuf(outfp); mtdp = get_thread_data(); if (check_thread_data(mtdp)) goto out; if (mtdp->idx <= 0) goto out; mcount_rstack_restore(mtdp); idx = mtdp->idx - 1; /* flush current rstack on crash */ rstack = &mtdp->rstack[idx]; record_trace_data(mtdp, rstack, NULL); /* print backtrace */ for (i = 0; i < (int)ARRAY_SIZE(sigsegv_codes); i++) { if (sig != SIGSEGV) break; if (si->si_code == sigsegv_codes[i].code) { pr_warn("Segmentation fault: %s (addr: %p)\n", sigsegv_codes[i].msg, si->si_addr); break; } } if (sig != SIGSEGV || i == (int)ARRAY_SIZE(sigsegv_codes)) { pr_warn("process crashed by signal %d: %s (si_code: %d)\n", sig, strsignal(sig), si->si_code); } pr_warn("Backtrace from uftrace:\n"); pr_warn("=====================================\n"); while (rstack >= mtdp->rstack) { struct sym *parent, *child; char *pname, *cname; parent = find_symtabs(&symtabs, rstack->parent_ip); pname = symbol_getname(parent, rstack->parent_ip); child = find_symtabs(&symtabs, rstack->child_ip); cname = symbol_getname(child, rstack->child_ip); pr_warn("[%d] (%s[%lx] <= %s[%lx])\n", idx--, cname, rstack->child_ip, pname, rstack->parent_ip); symbol_putname(parent, pname); symbol_putname(child, cname); rstack--; } out: sigaction(sig, &old_sigact[(sig == SIGSEGV)], NULL); raise(sig); } static void mcount_init_file(void) { struct sigaction sa = { .sa_sigaction = segv_handler, .sa_flags = SA_SIGINFO, }; send_session_msg(&mtd, mcount_session_name()); pr_dbg("new session started: %.*s: %s\n", SESSION_ID_LEN, mcount_session_name(), basename(mcount_exename)); sigemptyset(&sa.sa_mask); sigaction(SIGABRT, &sa, &old_sigact[0]); sigaction(SIGSEGV, &sa, &old_sigact[1]); } struct mcount_thread_data * mcount_prepare(void) { static pthread_once_t once_control = PTHREAD_ONCE_INIT; struct mcount_thread_data *mtdp = &mtd; struct uftrace_msg_task tmsg; if (unlikely(mcount_should_stop())) return NULL; /* * If an executable implements its own malloc(), * following recursion could occur * * mcount_entry -> mcount_prepare -> xmalloc -> mcount_entry -> ... */ if (!mcount_guard_recursion(mtdp)) return NULL; compiler_barrier(); mcount_filter_setup(mtdp); mcount_watch_setup(mtdp); mtdp->rstack = xmalloc(mcount_rstack_max * sizeof(*mtd.rstack)); pthread_once(&once_control, mcount_init_file); prepare_shmem_buffer(mtdp); pthread_setspecific(mtd_key, mtdp); /* time should be get after session message sent */ tmsg.pid = getpid(), tmsg.tid = mcount_gettid(mtdp), tmsg.time = mcount_gettime(); uftrace_send_message(UFTRACE_MSG_TASK_START, &tmsg, sizeof(tmsg)); update_kernel_tid(tmsg.tid); return mtdp; } static void mcount_finish(void) { if (!mcount_should_stop()) mcount_trace_finish(false); mcount_global_flags |= MCOUNT_GFL_FINISH; } static bool mcount_check_rstack(struct mcount_thread_data *mtdp) { if (mtdp->idx >= mcount_rstack_max) { if (!mtdp->warned) { struct mcount_ret_stack *rstack; pr_warn("call depth beyond %d is not recorded.\n" " (use --max-stack=DEPTH to record more)\n", mtdp->idx); /* flush current rstack */ rstack = &mtdp->rstack[mcount_rstack_max - 1]; record_trace_data(mtdp, rstack, NULL); mtdp->warned = true; } return true; } mtdp->warned = false; return false; } #ifndef DISABLE_MCOUNT_FILTER extern void * get_argbuf(struct mcount_thread_data *, struct mcount_ret_stack *); /* update filter state from trigger result */ enum filter_result mcount_entry_filter_check(struct mcount_thread_data *mtdp, unsigned long child, struct uftrace_trigger *tr) { pr_dbg3("<%d> enter %lx\n", mtdp->idx, child); if (mcount_check_rstack(mtdp)) return FILTER_RSTACK; /* save original depth and time to restore at exit time */ mtdp->filter.saved_depth = mtdp->filter.depth; mtdp->filter.saved_time = mtdp->filter.time; /* already filtered by notrace option */ if (mtdp->filter.out_count > 0) return FILTER_OUT; uftrace_match_filter(child, &mcount_triggers, tr); pr_dbg3(" tr->flags: %x, filter mode: %d, count: %d/%d, depth: %d\n", tr->flags, tr->fmode, mtdp->filter.in_count, mtdp->filter.out_count, mtdp->filter.depth); if (tr->flags & TRIGGER_FL_FILTER) { if (tr->fmode == FILTER_MODE_IN) mtdp->filter.in_count++; else if (tr->fmode == FILTER_MODE_OUT) mtdp->filter.out_count++; /* apply default filter depth when match */ mtdp->filter.depth = mcount_depth; } else { /* not matched by filter */ if (mcount_filter_mode == FILTER_MODE_IN && mtdp->filter.in_count == 0) return FILTER_OUT; } #define FLAGS_TO_CHECK (TRIGGER_FL_DEPTH | TRIGGER_FL_TRACE_ON | \ TRIGGER_FL_TRACE_OFF | TRIGGER_FL_TIME_FILTER) if (tr->flags & FLAGS_TO_CHECK) { if (tr->flags & TRIGGER_FL_DEPTH) mtdp->filter.depth = tr->depth; if (tr->flags & TRIGGER_FL_TRACE_ON) mcount_enabled = true; if (tr->flags & TRIGGER_FL_TRACE_OFF) mcount_enabled = false; if (tr->flags & TRIGGER_FL_TIME_FILTER) mtdp->filter.time = tr->time; } #undef FLAGS_TO_CHECK if (mtdp->filter.depth == 0) return FILTER_OUT; mtdp->filter.depth--; return FILTER_IN; } static int script_save_context(struct script_context *sc_ctx, struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, char *symname, bool has_arg_retval, struct list_head *pargs) { if (!script_match_filter(symname)) return -1; sc_ctx->tid = mcount_gettid(mtdp); sc_ctx->depth = rstack->depth; sc_ctx->address = rstack->child_ip; sc_ctx->name = symname; sc_ctx->timestamp = rstack->start_time; if (rstack->end_time) sc_ctx->duration = rstack->end_time - rstack->start_time; if (has_arg_retval) { unsigned *argbuf = get_argbuf(mtdp, rstack); sc_ctx->arglen = argbuf[0]; sc_ctx->argbuf = &argbuf[1]; sc_ctx->argspec = pargs; } else { /* prevent access to arguments */ sc_ctx->arglen = 0; } return 0; } static void script_hook_entry(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, struct uftrace_trigger *tr) { struct script_context sc_ctx; unsigned long entry_addr = rstack->child_ip; struct sym *sym = find_symtabs(&symtabs, entry_addr); char *symname = symbol_getname(sym, entry_addr); if (script_save_context(&sc_ctx, mtdp, rstack, symname, tr->flags & TRIGGER_FL_ARGUMENT, tr->pargs) < 0) goto skip; /* accessing argument in script might change arch-context */ mcount_save_arch_context(&mtdp->arch); script_uftrace_entry(&sc_ctx); mcount_restore_arch_context(&mtdp->arch); skip: symbol_putname(sym, symname); } static void script_hook_exit(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack) { struct script_context sc_ctx; unsigned long entry_addr = rstack->child_ip; struct sym *sym = find_symtabs(&symtabs, entry_addr); char *symname = symbol_getname(sym, entry_addr); if (script_save_context(&sc_ctx, mtdp, rstack, symname, rstack->flags & MCOUNT_FL_RETVAL, rstack->pargs) < 0) goto skip; /* accessing argument in script might change arch-context */ mcount_save_arch_context(&mtdp->arch); script_uftrace_exit(&sc_ctx); mcount_restore_arch_context(&mtdp->arch); skip: symbol_putname(sym, symname); } /* save current filter state to rstack */ void mcount_entry_filter_record(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, struct uftrace_trigger *tr, struct mcount_regs *regs) { if (mtdp->filter.out_count > 0 || (mtdp->filter.in_count == 0 && mcount_filter_mode == FILTER_MODE_IN)) rstack->flags |= MCOUNT_FL_NORECORD; rstack->filter_depth = mtdp->filter.saved_depth; rstack->filter_time = mtdp->filter.saved_time; #define FLAGS_TO_CHECK (TRIGGER_FL_FILTER | TRIGGER_FL_RETVAL | \ TRIGGER_FL_TRACE | TRIGGER_FL_FINISH | \ TRIGGER_FL_CALLER) if (tr->flags & FLAGS_TO_CHECK) { if (tr->flags & TRIGGER_FL_FILTER) { if (tr->fmode == FILTER_MODE_IN) rstack->flags |= MCOUNT_FL_FILTERED; else rstack->flags |= MCOUNT_FL_NOTRACE; } /* check if it has to keep arg_spec for retval */ if (tr->flags & TRIGGER_FL_RETVAL) { rstack->pargs = tr->pargs; rstack->flags |= MCOUNT_FL_RETVAL; } if (tr->flags & TRIGGER_FL_TRACE) rstack->flags |= MCOUNT_FL_TRACE; if (tr->flags & TRIGGER_FL_CALLER) rstack->flags |= MCOUNT_FL_CALLER; if (tr->flags & TRIGGER_FL_FINISH) { record_trace_data(mtdp, rstack, NULL); mcount_finish_trigger(); return; } } #undef FLAGS_TO_CHECK if (!(rstack->flags & MCOUNT_FL_NORECORD)) { mtdp->record_idx++; if (!mcount_enabled) { rstack->flags |= MCOUNT_FL_DISABLED; /* * Flush existing rstack when mcount_enabled is off * (i.e. disabled). Note that changing to enabled is * already handled in record_trace_data() on exit path * using the MCOUNT_FL_DISABLED flag. */ if (unlikely(mtdp->enable_cached)) record_trace_data(mtdp, rstack, NULL); } else { if (tr->flags & TRIGGER_FL_ARGUMENT) save_argument(mtdp, rstack, tr->pargs, regs); if (tr->flags & TRIGGER_FL_READ) { save_trigger_read(mtdp, rstack, tr->read, false); rstack->flags |= MCOUNT_FL_READ; } if (mcount_watchpoints) save_watchpoint(mtdp, rstack, mcount_watchpoints); if (mtdp->nr_events) { bool flush = false; int i; /* * Flush rstacks if async event was recorded * as it only has limited space for the events. */ for (i = 0; i < mtdp->nr_events; i++) if (mtdp->event[i].idx == ASYNC_IDX) flush = true; if (flush) record_trace_data(mtdp, rstack, NULL); } } /* script hooking for function entry */ if (SCRIPT_ENABLED && script_str) script_hook_entry(mtdp, rstack, tr); #define FLAGS_TO_CHECK (TRIGGER_FL_RECOVER | TRIGGER_FL_TRACE_ON | TRIGGER_FL_TRACE_OFF) if (tr->flags & FLAGS_TO_CHECK) { if (tr->flags & TRIGGER_FL_RECOVER) { mcount_rstack_restore(mtdp); *rstack->parent_loc = mcount_return_fn; rstack->flags |= MCOUNT_FL_RECOVER; } if (tr->flags & (TRIGGER_FL_TRACE_ON | TRIGGER_FL_TRACE_OFF)) mtdp->enable_cached = mcount_enabled; } } #undef FLAGS_TO_CHECK } /* restore filter state from rstack */ void mcount_exit_filter_record(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, long *retval) { uint64_t time_filter = mtdp->filter.time; pr_dbg3("<%d> exit %lx\n", mtdp->idx, rstack->child_ip); #define FLAGS_TO_CHECK (MCOUNT_FL_FILTERED | MCOUNT_FL_NOTRACE | MCOUNT_FL_RECOVER) if (rstack->flags & FLAGS_TO_CHECK) { if (rstack->flags & MCOUNT_FL_FILTERED) mtdp->filter.in_count--; else if (rstack->flags & MCOUNT_FL_NOTRACE) mtdp->filter.out_count--; if (rstack->flags & MCOUNT_FL_RECOVER) mcount_rstack_reset(mtdp); } #undef FLAGS_TO_CHECK mtdp->filter.depth = rstack->filter_depth; mtdp->filter.time = rstack->filter_time; if (!(rstack->flags & MCOUNT_FL_NORECORD)) { if (mtdp->record_idx > 0) mtdp->record_idx--; if (!mcount_enabled) return; if (!(rstack->flags & MCOUNT_FL_RETVAL)) retval = NULL; if (rstack->flags & MCOUNT_FL_READ) { struct uftrace_trigger tr; /* there's a possibility of overwriting by return value */ uftrace_match_filter(rstack->child_ip, &mcount_triggers, &tr); save_trigger_read(mtdp, rstack, tr.read, true); } if (mcount_watchpoints) save_watchpoint(mtdp, rstack, mcount_watchpoints); if (((rstack->end_time - rstack->start_time > time_filter) && (!mcount_has_caller || rstack->flags & MCOUNT_FL_CALLER)) || rstack->flags & (MCOUNT_FL_WRITTEN | MCOUNT_FL_TRACE)) { if (record_trace_data(mtdp, rstack, retval) < 0) pr_err("error during record"); } else if (mtdp->nr_events) { bool flush = false; int i, k; /* * Record rstacks if async event was recorded * in the middle of the function. Otherwise * update event count to drop filtered ones. */ for (i = 0, k = 0; i < mtdp->nr_events; i++) { if (mtdp->event[i].idx == ASYNC_IDX) flush = true; if (mtdp->event[i].idx < mtdp->idx) k = i + 1; } if (flush) record_trace_data(mtdp, rstack, retval); else mtdp->nr_events = k; /* invalidate sync events */ } /* script hooking for function exit */ if (SCRIPT_ENABLED && script_str) script_hook_exit(mtdp, rstack); } } #else /* DISABLE_MCOUNT_FILTER */ enum filter_result mcount_entry_filter_check(struct mcount_thread_data *mtdp, unsigned long child, struct uftrace_trigger *tr) { if (mcount_check_rstack(mtdp)) return FILTER_RSTACK; return FILTER_IN; } void mcount_entry_filter_record(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, struct uftrace_trigger *tr, struct mcount_regs *regs) { mtdp->record_idx++; } void mcount_exit_filter_record(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, long *retval) { mtdp->record_idx--; if (rstack->end_time - rstack->start_time > mcount_threshold || rstack->flags & MCOUNT_FL_WRITTEN) { if (record_trace_data(mtdp, rstack, NULL) < 0) pr_err("error during record"); } } #endif /* DISABLE_MCOUNT_FILTER */ #ifndef FIX_PARENT_LOC static inline unsigned long * mcount_arch_parent_location(struct symtabs *symtabs, unsigned long *parent_loc, unsigned long child_ip) { return parent_loc; } #endif static int __mcount_entry(unsigned long *parent_loc, unsigned long child, struct mcount_regs *regs) { enum filter_result filtered; struct mcount_thread_data *mtdp; struct mcount_ret_stack *rstack; struct uftrace_trigger tr; /* Access the mtd through TSD pointer to reduce TLS overhead */ mtdp = get_thread_data(); if (unlikely(check_thread_data(mtdp))) { mtdp = mcount_prepare(); if (mtdp == NULL) return -1; } else { if (!mcount_guard_recursion(mtdp)) return -1; } tr.flags = 0; filtered = mcount_entry_filter_check(mtdp, child, &tr); if (filtered != FILTER_IN) { mcount_unguard_recursion(mtdp); return -1; } if (unlikely(mtdp->in_exception)) { unsigned long frame_addr; /* same as __builtin_frame_addr(2) but avoid warning */ frame_addr = parent_loc[-1]; /* basic sanity check */ if (frame_addr < (unsigned long)parent_loc) frame_addr = (unsigned long)(parent_loc - 1); mcount_rstack_reset_exception(mtdp, frame_addr); mtdp->in_exception = false; } /* fixup the parent_loc in an arch-dependant way (if needed) */ parent_loc = mcount_arch_parent_location(&symtabs, parent_loc, child); rstack = &mtdp->rstack[mtdp->idx++]; rstack->depth = mtdp->record_idx; rstack->dyn_idx = MCOUNT_INVALID_DYNIDX; rstack->parent_loc = parent_loc; rstack->parent_ip = *parent_loc; rstack->child_ip = child; rstack->start_time = mcount_gettime(); rstack->end_time = 0; rstack->flags = 0; rstack->nr_events = 0; rstack->event_idx = ARGBUF_SIZE; /* hijack the return address of child */ *parent_loc = mcount_return_fn; /* restore return address of parent */ if (mcount_auto_recover) mcount_auto_restore(mtdp); mcount_entry_filter_record(mtdp, rstack, &tr, regs); mcount_unguard_recursion(mtdp); return 0; } int mcount_entry(unsigned long *parent_loc, unsigned long child, struct mcount_regs *regs) { int saved_errno = errno; int ret = __mcount_entry(parent_loc, child, regs); errno = saved_errno; return ret; } static unsigned long __mcount_exit(long *retval) { struct mcount_thread_data *mtdp; struct mcount_ret_stack *rstack; unsigned long *ret_loc; unsigned long retaddr; mtdp = get_thread_data(); assert(mtdp != NULL); assert(!mtdp->dead); /* * it's only called when mcount_entry() was succeeded and * no need to check recursion here. But still needs to * prevent recursion during this call. */ __mcount_guard_recursion(mtdp); rstack = &mtdp->rstack[mtdp->idx - 1]; rstack->end_time = mcount_gettime(); mcount_exit_filter_record(mtdp, rstack, retval); ret_loc = rstack->parent_loc; retaddr = rstack->parent_ip; /* re-hijack return address of parent */ if (mcount_auto_recover) mcount_auto_reset(mtdp); __mcount_unguard_recursion(mtdp); if (unlikely(mcount_should_stop())) { mtd_dtor(mtdp); /* * mtd_dtor() will free rstack but current ret_addr * might be plthook_return() when it was a tail call. * Reload the return address after mtd_dtor() restored * all the parent locations. */ retaddr = *ret_loc; } compiler_barrier(); mtdp->idx--; return retaddr; } unsigned long mcount_exit(long *retval) { int saved_errno = errno; unsigned long ret = __mcount_exit(retval); errno = saved_errno; return ret; } static int __cygprof_entry(unsigned long parent, unsigned long child) { enum filter_result filtered; struct mcount_thread_data *mtdp; struct mcount_ret_stack *rstack; struct uftrace_trigger tr = { .flags = 0, }; /* Access the mtd through TSD pointer to reduce TLS overhead */ mtdp = get_thread_data(); if (unlikely(check_thread_data(mtdp))) { mtdp = mcount_prepare(); if (mtdp == NULL) return -1; } else { if (!mcount_guard_recursion(mtdp)) return -1; } filtered = mcount_entry_filter_check(mtdp, child, &tr); if (unlikely(mtdp->in_exception)) { unsigned long *frame_ptr; unsigned long frame_addr; frame_ptr = __builtin_frame_address(0); frame_addr = *frame_ptr; /* XXX: probably dangerous */ /* basic sanity check */ if (frame_addr < (unsigned long)frame_ptr) frame_addr = (unsigned long)frame_ptr; mcount_rstack_reset_exception(mtdp, frame_addr); mtdp->in_exception = false; } /* * recording arguments and return value is not supported. * also 'recover' trigger is only work for -pg entry. */ tr.flags &= ~(TRIGGER_FL_ARGUMENT | TRIGGER_FL_RETVAL | TRIGGER_FL_RECOVER); rstack = &mtdp->rstack[mtdp->idx++]; /* * even if it already exceeds the rstack max, it needs to increase idx * since the cygprof_exit() will be called anyway */ if (filtered == FILTER_RSTACK) { mcount_unguard_recursion(mtdp); return 0; } rstack->depth = mtdp->record_idx; rstack->dyn_idx = MCOUNT_INVALID_DYNIDX; rstack->parent_loc = &mtdp->cygprof_dummy; rstack->parent_ip = parent; rstack->child_ip = child; rstack->end_time = 0; rstack->nr_events = 0; rstack->event_idx = ARGBUF_SIZE; if (filtered == FILTER_IN) { rstack->start_time = mcount_gettime(); rstack->flags = 0; } else { rstack->start_time = 0; rstack->flags = MCOUNT_FL_NORECORD; } mcount_entry_filter_record(mtdp, rstack, &tr, NULL); mcount_unguard_recursion(mtdp); return 0; } static int cygprof_entry(unsigned long parent, unsigned long child) { int saved_errno = errno; int ret = __cygprof_entry(parent, child); errno = saved_errno; return ret; } static void __cygprof_exit(unsigned long parent, unsigned long child) { struct mcount_thread_data *mtdp; struct mcount_ret_stack *rstack; mtdp = get_thread_data(); if (unlikely(check_thread_data(mtdp))) return; if (!mcount_guard_recursion(mtdp)) return; /* * cygprof_exit() can be called beyond rstack max. * It cannot use mcount_check_rstack() here * since we didn't decrease the idx yet. */ if (mtdp->idx > mcount_rstack_max) goto out; rstack = &mtdp->rstack[mtdp->idx - 1]; if (!(rstack->flags & MCOUNT_FL_NORECORD)) rstack->end_time = mcount_gettime(); mcount_exit_filter_record(mtdp, rstack, NULL); out: mcount_unguard_recursion(mtdp); compiler_barrier(); mtdp->idx--; } static void cygprof_exit(unsigned long parent, unsigned long child) { int saved_errno = errno; __cygprof_exit(parent, child); errno = saved_errno; } static void _xray_entry(unsigned long parent, unsigned long child, struct mcount_regs *regs) { enum filter_result filtered; struct mcount_thread_data *mtdp; struct mcount_ret_stack *rstack; struct uftrace_trigger tr = { .flags = 0, }; /* Access the mtd through TSD pointer to reduce TLS overhead */ mtdp = get_thread_data(); if (unlikely(check_thread_data(mtdp))) { mtdp = mcount_prepare(); if (mtdp == NULL) return; } else { if (!mcount_guard_recursion(mtdp)) return; } filtered = mcount_entry_filter_check(mtdp, child, &tr); if (unlikely(mtdp->in_exception)) { unsigned long *frame_ptr; unsigned long frame_addr; frame_ptr = __builtin_frame_address(0); frame_addr = *frame_ptr; /* XXX: probably dangerous */ /* basic sanity check */ if (frame_addr < (unsigned long)frame_ptr) frame_addr = (unsigned long)frame_ptr; mcount_rstack_reset_exception(mtdp, frame_addr); mtdp->in_exception = false; } /* 'recover' trigger is only for -pg entry */ tr.flags &= ~TRIGGER_FL_RECOVER; rstack = &mtdp->rstack[mtdp->idx++]; rstack->depth = mtdp->record_idx; rstack->dyn_idx = MCOUNT_INVALID_DYNIDX; rstack->parent_loc = &mtdp->cygprof_dummy; rstack->parent_ip = parent; rstack->child_ip = child; rstack->end_time = 0; rstack->nr_events = 0; rstack->event_idx = ARGBUF_SIZE; if (filtered == FILTER_IN) { rstack->start_time = mcount_gettime(); rstack->flags = 0; } else { rstack->start_time = 0; rstack->flags = MCOUNT_FL_NORECORD; } mcount_entry_filter_record(mtdp, rstack, &tr, regs); mcount_unguard_recursion(mtdp); } void xray_entry(unsigned long parent, unsigned long child, struct mcount_regs *regs) { int saved_errno = errno; _xray_entry(parent, child, regs); errno = saved_errno; } static void _xray_exit(long *retval) { struct mcount_thread_data *mtdp; struct mcount_ret_stack *rstack; mtdp = get_thread_data(); if (unlikely(check_thread_data(mtdp))) return; if (!mcount_guard_recursion(mtdp)) return; /* * cygprof_exit() can be called beyond rstack max. * It cannot use mcount_check_rstack() here * since we didn't decrease the idx yet. */ if (mtdp->idx > mcount_rstack_max) goto out; rstack = &mtdp->rstack[mtdp->idx - 1]; if (!(rstack->flags & MCOUNT_FL_NORECORD)) rstack->end_time = mcount_gettime(); mcount_exit_filter_record(mtdp, rstack, retval); out: mcount_unguard_recursion(mtdp); compiler_barrier(); mtdp->idx--; } void xray_exit(long *retval) { int saved_errno = errno; _xray_exit(retval); errno = saved_errno; } static void atfork_prepare_handler(void) { struct uftrace_msg_task tmsg = { .time = mcount_gettime(), .pid = getpid(), }; /* call script atfork preparation routine */ if (SCRIPT_ENABLED && script_str) script_atfork_prepare(); uftrace_send_message(UFTRACE_MSG_FORK_START, &tmsg, sizeof(tmsg)); /* flush remaining contents in the stream */ fflush(outfp); fflush(logfp); } static void atfork_child_handler(void) { struct mcount_thread_data *mtdp; struct uftrace_msg_task tmsg = { .time = mcount_gettime(), .pid = getppid(), .tid = getpid(), }; int i; mtdp = get_thread_data(); if (unlikely(check_thread_data(mtdp))) { mtdp = mcount_prepare(); if (mtdp == NULL) return; } else { if (!mcount_guard_recursion(mtdp)) return; } /* update tid cache */ mtdp->tid = tmsg.tid; /* flush event data */ mtdp->nr_events = 0; clear_shmem_buffer(mtdp); prepare_shmem_buffer(mtdp); uftrace_send_message(UFTRACE_MSG_FORK_END, &tmsg, sizeof(tmsg)); update_kernel_tid(tmsg.tid); /* do not record parent's functions */ for (i = 0; i < mtdp->idx; i++) mtdp->rstack[i].flags |= MCOUNT_FL_WRITTEN; mcount_unguard_recursion(mtdp); } static void mcount_script_init(enum uftrace_pattern_type patt_type) { struct script_info info = { .name = script_str, .version = UFTRACE_VERSION, .record = true, }; char *cmds_str; cmds_str = getenv("UFTRACE_ARGS"); if (cmds_str) strv_split(&info.cmds, cmds_str, "\n"); if (script_init(&info, patt_type) < 0) script_str = NULL; strv_free(&info.cmds); } static __used void mcount_startup(void) { char *pipefd_str; char *logfd_str; char *debug_str; char *bufsize_str; char *maxstack_str; char *threshold_str; char *color_str; char *demangle_str; char *plthook_str; char *patch_str; char *event_str; char *dirname; char *pattern_str; struct stat statbuf; bool nest_libcall; enum uftrace_pattern_type patt_type = PATT_REGEX; if (!(mcount_global_flags & MCOUNT_GFL_SETUP)) return; mtd.recursion_marker = true; outfp = stdout; logfp = stderr; if (pthread_key_create(&mtd_key, mtd_dtor)) pr_err("cannot create mtd key"); pipefd_str = getenv("UFTRACE_PIPE"); logfd_str = getenv("UFTRACE_LOGFD"); debug_str = getenv("UFTRACE_DEBUG"); bufsize_str = getenv("UFTRACE_BUFFER"); maxstack_str = getenv("UFTRACE_MAX_STACK"); color_str = getenv("UFTRACE_COLOR"); threshold_str = getenv("UFTRACE_THRESHOLD"); demangle_str = getenv("UFTRACE_DEMANGLE"); plthook_str = getenv("UFTRACE_PLTHOOK"); patch_str = getenv("UFTRACE_PATCH"); event_str = getenv("UFTRACE_EVENT"); script_str = getenv("UFTRACE_SCRIPT"); nest_libcall = !!getenv("UFTRACE_NEST_LIBCALL"); pattern_str = getenv("UFTRACE_PATTERN"); page_size_in_kb = getpagesize() / KB; if (logfd_str) { int fd = strtol(logfd_str, NULL, 0); /* minimal sanity check */ if (!fstat(fd, &statbuf)) { logfp = fdopen(fd, "a"); if (logfp == NULL) pr_err("opening log file failed"); setvbuf(logfp, NULL, _IOLBF, 1024); } } if (debug_str) { debug = strtol(debug_str, NULL, 0); build_debug_domain(getenv("UFTRACE_DEBUG_DOMAIN")); } if (demangle_str) demangler = strtol(demangle_str, NULL, 0); if (color_str) setup_color(strtol(color_str, NULL, 0), NULL); else setup_color(COLOR_AUTO, NULL); pr_dbg("initializing mcount library\n"); dirname = getenv("UFTRACE_DIR"); if (dirname == NULL) dirname = UFTRACE_DIR_NAME; if (pipefd_str) { pfd = strtol(pipefd_str, NULL, 0); /* minimal sanity check */ if (fstat(pfd, &statbuf) < 0 || !S_ISFIFO(statbuf.st_mode)) { pr_dbg("ignore invalid pipe fd: %d\n", pfd); pfd = -1; } } else { char *channel = NULL; xasprintf(&channel, "%s/%s", dirname, ".channel"); pfd = open(channel, O_WRONLY); free(channel); } if (getenv("UFTRACE_LIST_EVENT")) { mcount_list_events(); exit(0); } if (bufsize_str) shmem_bufsize = strtol(bufsize_str, NULL, 0); mcount_exename = read_exename(); symtabs.dirname = dirname; symtabs.filename = mcount_exename; record_proc_maps(dirname, mcount_session_name(), &symtabs); if (pattern_str) patt_type = parse_filter_pattern(pattern_str); if (patch_str) mcount_return_fn = (unsigned long)dynamic_return; else mcount_return_fn = (unsigned long)mcount_return; mcount_filter_init(patt_type, dirname, !!patch_str); mcount_watch_init(); if (maxstack_str) mcount_rstack_max = strtol(maxstack_str, NULL, 0); if (threshold_str) mcount_threshold = strtoull(threshold_str, NULL, 0); if (patch_str) mcount_dynamic_update(&symtabs, patch_str, patt_type, &disasm); if (event_str) mcount_setup_events(dirname, event_str, patt_type); if (plthook_str) mcount_setup_plthook(mcount_exename, nest_libcall); if (getenv("UFTRACE_KERNEL_PID_UPDATE")) kernel_pid_update = true; pthread_atfork(atfork_prepare_handler, NULL, atfork_child_handler); mcount_hook_functions(); /* initialize script binding */ if (SCRIPT_ENABLED && script_str) mcount_script_init(patt_type); compiler_barrier(); pr_dbg("mcount setup done\n"); mcount_global_flags &= ~MCOUNT_GFL_SETUP; mtd.recursion_marker = false; } static void mcount_cleanup(void) { mcount_finish(); destroy_dynsym_indexes(); #if 0 /* * This mtd_key deletion sometimes makes other thread get crashed * because they may try to get mtdp based on this mtd_key after being * deleted. Since this key deletion is not mandatory, it'd be better * not to delete it until we find a better solution. */ pthread_key_delete(mtd_key); mtd_key = -1; #endif mcount_filter_finish(); if (SCRIPT_ENABLED && script_str) script_finish(); script_str = NULL; unload_module_symtabs(); pr_dbg("exit from libmcount\n"); } /* * external interfaces */ #define UFTRACE_ALIAS(_func) void uftrace_##_func(void*, void*) __alias(_func) void __visible_default __monstartup(unsigned long low, unsigned long high) { } void __visible_default _mcleanup(void) { } void __visible_default mcount_restore(void) { struct mcount_thread_data *mtdp; mtdp = get_thread_data(); if (unlikely(check_thread_data(mtdp))) return; mcount_rstack_restore(mtdp); } void __visible_default mcount_reset(void) { struct mcount_thread_data *mtdp; mtdp = get_thread_data(); if (unlikely(check_thread_data(mtdp))) return; mcount_rstack_reset(mtdp); } void __visible_default __cyg_profile_func_enter(void *child, void *parent) { cygprof_entry((unsigned long)parent, (unsigned long)child); } UFTRACE_ALIAS(__cyg_profile_func_enter); void __visible_default __cyg_profile_func_exit(void *child, void *parent) { cygprof_exit((unsigned long)parent, (unsigned long)child); } UFTRACE_ALIAS(__cyg_profile_func_exit); #ifndef UNIT_TEST /* * Initializer and Finalizer */ static void __attribute__((constructor)) mcount_init(void) { mcount_startup(); } static void __attribute__((destructor)) mcount_fini(void) { mcount_cleanup(); } #else /* UNIT_TEST */ static void setup_mcount_test(void) { mcount_exename = read_exename(); pthread_key_create(&mtd_key, mtd_dtor); mcount_global_flags = 0; } TEST_CASE(mcount_thread_data) { struct mcount_thread_data *mtdp; setup_mcount_test(); mtdp = get_thread_data(); TEST_EQ(check_thread_data(mtdp), true); mtdp = mcount_prepare(); TEST_EQ(check_thread_data(mtdp), false); TEST_EQ(get_thread_data(), mtdp); TEST_EQ(check_thread_data(mtdp), false); mcount_cleanup(); return TEST_OK; } TEST_CASE(mcount_signal_setup) { struct signal_trigger_item *item; struct uftrace_filter_setting setting = { .ptype = PATT_NONE, }; /* it signal triggers are maintained in a stack (LIFO) */ mcount_signal_init("SIGUSR1@traceon;USR2@traceoff;RTMIN+3@finish", &setting); item = list_first_entry(&siglist, typeof(*item), list); TEST_EQ(item->sig, SIGRTMIN + 3); TEST_EQ(item->tr.flags, TRIGGER_FL_FINISH); item = list_next_entry(item, list); TEST_EQ(item->sig, SIGUSR2); TEST_EQ(item->tr.flags, TRIGGER_FL_TRACE_OFF); item = list_next_entry(item, list); TEST_EQ(item->sig, SIGUSR1); TEST_EQ(item->tr.flags, TRIGGER_FL_TRACE_ON); mcount_signal_finish(); TEST_EQ(list_empty(&siglist), true); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.9.4/libmcount/mcount.h000066400000000000000000000040451362052523300166330ustar00rootroot00000000000000/* * data structures for handling mcount records * * Copyright (C) 2014-2018, LG Electronics, Namhyung Kim * * Released under the GPL v2. */ #ifndef UFTRACE_MCOUNT_H #define UFTRACE_MCOUNT_H #include #include #include #define UFTRACE_DIR_NAME "uftrace.data" #define MCOUNT_RSTACK_MAX OPT_RSTACK_DEFAULT #define MCOUNT_DEFAULT_DEPTH OPT_DEPTH_DEFAULT #define MCOUNT_NOTRACE_IDX 0x10000 #define MCOUNT_INVALID_DYNIDX 0xefefefef enum mcount_rstack_flag { MCOUNT_FL_SETJMP = (1U << 0), MCOUNT_FL_LONGJMP = (1U << 1), MCOUNT_FL_NORECORD = (1U << 2), MCOUNT_FL_NOTRACE = (1U << 3), MCOUNT_FL_FILTERED = (1U << 4), MCOUNT_FL_VFORK = (1U << 5), MCOUNT_FL_WRITTEN = (1U << 6), MCOUNT_FL_DISABLED = (1U << 7), MCOUNT_FL_RECOVER = (1U << 8), MCOUNT_FL_RETVAL = (1U << 9), MCOUNT_FL_TRACE = (1U << 10), MCOUNT_FL_ARGUMENT = (1U << 11), MCOUNT_FL_READ = (1U << 12), MCOUNT_FL_CALLER = (1U << 13), }; struct plthook_data; struct list_head; struct mcount_ret_stack { unsigned long *parent_loc; unsigned long parent_ip; unsigned long child_ip; enum mcount_rstack_flag flags; /* time in nsec (CLOCK_MONOTONIC) */ uint64_t start_time; uint64_t end_time; int tid; unsigned dyn_idx; uint64_t filter_time; unsigned short depth; unsigned short filter_depth; unsigned short nr_events; unsigned short event_idx; struct plthook_data *pd; /* set arg_spec at function entry and use it at exit */ struct list_head *pargs; }; void __monstartup(unsigned long low, unsigned long high); void _mcleanup(void); void mcount_restore(void); void mcount_reset(void); #define SHMEM_BUFFER_SIZE_KB 128 #define SHMEM_BUFFER_SIZE (SHMEM_BUFFER_SIZE_KB * KB) enum shmem_buffer_flags { SHMEM_FL_NEW = (1U << 0), SHMEM_FL_WRITTEN = (1U << 1), SHMEM_FL_RECORDING = (1U << 2), }; struct mcount_shmem_buffer { unsigned size; unsigned flag; unsigned unused[2]; char data[]; }; /* must be in sync with enum debug_domain (bits) */ #define DBG_DOMAIN_STR "TSDFfsKMpPERW" #endif /* UFTRACE_MCOUNT_H */ uftrace-0.9.4/libmcount/misc.c000066400000000000000000000137531362052523300162620ustar00rootroot00000000000000#include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "mcount" #define PR_DOMAIN DBG_MCOUNT #include "libmcount/mcount.h" #include "libmcount/internal.h" #include "utils/utils.h" /* old kernel never updates pid filter for a forked child */ void update_kernel_tid(int tid) { static const char TRACING_DIR[] = "/sys/kernel/debug/tracing"; char *filename = NULL; char buf[8]; int fd; ssize_t len; if (!kernel_pid_update) return; /* update pid filter for function tracing */ xasprintf(&filename, "%s/set_ftrace_pid", TRACING_DIR); fd = open(filename, O_WRONLY | O_APPEND); if (fd < 0) return; snprintf(buf, sizeof(buf), "%d", tid); len = strlen(buf); if (write(fd, buf, len) != len) pr_dbg("update kernel ftrace tid filter failed\n"); close(fd); free(filename); /* update pid filter for event tracing */ xasprintf(&filename, "%s/set_event_pid", TRACING_DIR); fd = open(filename, O_WRONLY | O_APPEND); if (fd < 0) return; snprintf(buf, sizeof(buf), "%d", tid); len = strlen(buf); if (write(fd, buf, len) != len) pr_dbg("update kernel ftrace tid filter failed\n"); close(fd); free(filename); } const char *mcount_session_name(void) { static char session[SESSION_ID_LEN + 1]; static uint64_t session_id; int fd; if (!session_id) { fd = open("/dev/urandom", O_RDONLY); if (fd >= 0) { if (read(fd, &session_id, sizeof(session_id)) != 8) pr_err("reading from urandom"); close(fd); } else { srandom(time(NULL)); session_id = random(); session_id <<= 32; session_id |= random(); } snprintf(session, sizeof(session), "%0*"PRIx64, SESSION_ID_LEN, session_id); } return session; } void uftrace_send_message(int type, void *data, size_t len) { struct uftrace_msg msg = { .magic = UFTRACE_MSG_MAGIC, .type = type, .len = len, }; struct iovec iov[2] = { { .iov_base = &msg, .iov_len = sizeof(msg), }, { .iov_base = data, .iov_len = len, }, }; if (pfd < 0) return; len += sizeof(msg); if (writev(pfd, iov, 2) != (ssize_t)len) { if (!mcount_should_stop()) pr_err("writing shmem name to pipe"); } } void build_debug_domain(char *dbg_domain_str) { int i, len; if (dbg_domain_str == NULL) return; len = strlen(dbg_domain_str); for (i = 0; i < len; i += 2) { const char *pos; char domain = dbg_domain_str[i]; int level = dbg_domain_str[i+1] - '0'; int d; pos = strchr(DBG_DOMAIN_STR, domain); if (pos == NULL) continue; d = pos - DBG_DOMAIN_STR; dbg_domain[d] = level; } } bool mcount_rstack_has_plthook(struct mcount_thread_data *mtdp) { int idx; for (idx = 0; idx < mtdp->idx; idx++) { if (mtdp->rstack[idx].dyn_idx != MCOUNT_INVALID_DYNIDX) return true; } return false; } /* restore saved original return address */ void mcount_rstack_restore(struct mcount_thread_data *mtdp) { int idx; struct mcount_ret_stack *rstack; /* reverse order due to tail calls */ for (idx = mtdp->idx - 1; idx >= 0; idx--) { rstack = &mtdp->rstack[idx]; if (rstack->parent_ip == mcount_return_fn || rstack->parent_ip == (unsigned long)plthook_return) continue; if (!ARCH_CAN_RESTORE_PLTHOOK && rstack->dyn_idx != MCOUNT_INVALID_DYNIDX) continue; *rstack->parent_loc = rstack->parent_ip; } } /* hook return address again (used after mcount_rstack_restore) */ void mcount_rstack_reset(struct mcount_thread_data *mtdp) { int idx; struct mcount_ret_stack *rstack; for (idx = mtdp->idx - 1; idx >= 0; idx--) { rstack = &mtdp->rstack[idx]; if (rstack->dyn_idx == MCOUNT_INVALID_DYNIDX) *rstack->parent_loc = mcount_return_fn; else if (ARCH_CAN_RESTORE_PLTHOOK) *rstack->parent_loc = (unsigned long)plthook_return; } } void mcount_auto_restore(struct mcount_thread_data *mtdp) { struct mcount_ret_stack *curr_rstack; struct mcount_ret_stack *prev_rstack; /* auto recover is meaningful only if parent rstack is hooked */ if (mtdp->idx < 2) return; if (mtdp->in_exception) return; curr_rstack = &mtdp->rstack[mtdp->idx - 1]; prev_rstack = &mtdp->rstack[mtdp->idx - 2]; if (!ARCH_CAN_RESTORE_PLTHOOK && prev_rstack->dyn_idx != MCOUNT_INVALID_DYNIDX) return; /* ignore tail calls */ if (curr_rstack->parent_loc == prev_rstack->parent_loc) return; while (prev_rstack >= mtdp->rstack) { unsigned long parent_ip = prev_rstack->parent_ip; /* parent also can be tail-called; skip */ if (parent_ip == mcount_return_fn || parent_ip == (unsigned long)plthook_return) { prev_rstack--; continue; } *prev_rstack->parent_loc = parent_ip; return; } } void mcount_auto_reset(struct mcount_thread_data *mtdp) { struct mcount_ret_stack *curr_rstack; struct mcount_ret_stack *prev_rstack; /* auto recover is meaningful only if parent rstack is hooked */ if (mtdp->idx < 2) return; if (mtdp->in_exception) return; curr_rstack = &mtdp->rstack[mtdp->idx - 1]; prev_rstack = &mtdp->rstack[mtdp->idx - 2]; if (!ARCH_CAN_RESTORE_PLTHOOK && prev_rstack->dyn_idx != MCOUNT_INVALID_DYNIDX) return; /* ignore tail calls */ if (curr_rstack->parent_loc == prev_rstack->parent_loc) return; if (prev_rstack->dyn_idx == MCOUNT_INVALID_DYNIDX) *prev_rstack->parent_loc = mcount_return_fn; else *prev_rstack->parent_loc = (unsigned long)plthook_return; } #ifdef UNIT_TEST TEST_CASE(mcount_debug_domain) { int i; char dbg_str[DBG_DOMAIN_MAX * 2 + 1]; /* ensure domain string matches to current domain bit */ TEST_EQ(DBG_DOMAIN_MAX, (int)strlen(DBG_DOMAIN_STR)); for (i = 0; i < DBG_DOMAIN_MAX; i++) { if (i != PR_DOMAIN) TEST_EQ(dbg_domain[i], 0); } for (i = 0; i < DBG_DOMAIN_MAX; i++) { dbg_str[i * 2] = DBG_DOMAIN_STR[i]; dbg_str[i * 2 + 1] = '1'; } dbg_str[i * 2] = '\0'; build_debug_domain(dbg_str); for (i = 0; i < DBG_DOMAIN_MAX; i++) TEST_EQ(dbg_domain[i], 1); /* increase mcount debug domain to 2 */ strcpy(dbg_str, "M2"); build_debug_domain(dbg_str); TEST_EQ(dbg_domain[PR_DOMAIN], 2); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.9.4/libmcount/plthook.c000066400000000000000000000620741362052523300170070ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "plthook" #define PR_DOMAIN DBG_PLTHOOK #include "libmcount/mcount.h" #include "libmcount/internal.h" #include "mcount-arch.h" #include "utils/utils.h" #include "utils/filter.h" #include "utils/script.h" #include "utils/symbol.h" #ifndef PT_GNU_RELRO # define PT_GNU_RELRO 0x6474e552 /* Read-only after relocation */ #endif /* global symbol tables for libmcount */ extern struct symtabs symtabs; /* address of dynamic linker's resolver routine (copied from GOT[2]) */ unsigned long plthook_resolver_addr; /* referenced by arch/.../plthook.S */ /* list of plthook_data for each library (module) */ static LIST_HEAD(plthook_modules); /* check getenv("LD_BIND_NOT") */ static bool plthook_no_pltbind; static void overwrite_pltgot(struct plthook_data *pd, int idx, void *data) { /* overwrite it - might be write-protected */ pd->pltgot_ptr[idx] = (unsigned long)data; } unsigned long setup_pltgot(struct plthook_data *pd, int got_idx, int sym_idx, void *data) { unsigned long real_addr = pd->pltgot_ptr[got_idx]; pd->resolved_addr[sym_idx] = real_addr; overwrite_pltgot(pd, got_idx, data); return real_addr; } static void resolve_pltgot(struct plthook_data *pd, int idx) { if (pd->resolved_addr[idx] == 0) { unsigned long addr; struct sym *sym; sym = &pd->dsymtab.sym[idx]; addr = (unsigned long) dlsym(RTLD_DEFAULT, sym->name); /* On ARM dlsym(DEFAULT) returns the address of PLT */ if (unlikely(pd->base_addr <= addr && addr < sym->addr + sym->size)) { void *real_addr = dlsym(RTLD_NEXT, sym->name); if (real_addr) addr = (unsigned long)real_addr; } pr_dbg2("resolved addr of %s = %#lx\n", sym->name, addr); pd->resolved_addr[idx] = addr; } } /* use weak reference for non-defined (arch-dependent) symbols */ #define ALIAS_DECL(_sym) extern __weak void (*uftrace_##_sym)(void); ALIAS_DECL(mcount); ALIAS_DECL(_mcount); ALIAS_DECL(__fentry__); ALIAS_DECL(__gnu_mcount_nc); ALIAS_DECL(__cyg_profile_func_enter); ALIAS_DECL(__cyg_profile_func_exit); #define SKIP_SYM(func) { #func, &uftrace_ ## func } const struct plthook_skip_symbol plt_skip_syms[] = { SKIP_SYM(mcount), SKIP_SYM(_mcount), SKIP_SYM(__fentry__), SKIP_SYM(__gnu_mcount_nc), SKIP_SYM(__cyg_profile_func_enter), SKIP_SYM(__cyg_profile_func_exit), }; size_t plt_skip_nr = ARRAY_SIZE(plt_skip_syms); #undef SKIP_SYM #undef ALIAS_DECL /* * The `mcount` (and its friends) are part of uftrace itself, * so no need to use PLT hook for them. */ static void restore_plt_functions(struct plthook_data *pd) { unsigned i, k; struct symtab *dsymtab = &pd->dsymtab; for (i = 0; i < dsymtab->nr_sym; i++) { /* * GOT[0], GOT[1], and GOT[2] are reserved. * GOT[2] initially points to the runtime resolver, but updated * to plt_hooker for library tracing by uftrace. * The addresses from GOT[3] are supposed to point the resolved * addresses for each library function. */ int got_idx = 3 + i; bool skipped = false; unsigned long plthook_addr; unsigned long resolved_addr; struct sym *sym = dsymtab->sym_names[i]; for (k = 0; k < plt_skip_nr; k++) { const struct plthook_skip_symbol *skip_sym; skip_sym = &plt_skip_syms[k]; if (strcmp(sym->name, skip_sym->name)) continue; overwrite_pltgot(pd, got_idx, skip_sym->addr); pr_dbg2("overwrite GOT[%d] to %p (%s)\n", got_idx, skip_sym->addr, skip_sym->name); skipped = true; break; } if (skipped) continue; resolved_addr = pd->pltgot_ptr[got_idx]; plthook_addr = mcount_arch_plthook_addr(pd, i); if (resolved_addr != plthook_addr) { /* save already resolved address and hook it */ pd->resolved_addr[i] = resolved_addr; overwrite_pltgot(pd, got_idx, (void *)plthook_addr); pr_dbg2("restore GOT[%d] from \"%s\"(%#lx) to PLT(base + %#lx)\n", got_idx, sym->name, resolved_addr, plthook_addr - pd->base_addr); } } } extern void __weak plt_hooker(void); extern unsigned long plthook_return(void); __weak struct plthook_data *mcount_arch_hook_no_plt(struct uftrace_elf_data *elf, const char *modname, unsigned long offset) { return NULL; } __weak void mcount_arch_plthook_setup(struct plthook_data *pd, struct uftrace_elf_data *elf) { pd->arch = NULL; } static int find_got(struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter, const char *modname, unsigned long offset) { bool plt_found = false; unsigned long pltgot_addr = 0; unsigned long plt_addr = 0; struct plthook_data *pd; elf_for_each_shdr(elf, iter) { if (iter->shdr.sh_type == SHT_DYNAMIC) break; } elf_for_each_dynamic(elf, iter) { switch (iter->dyn.d_tag) { case DT_PLTGOT: pltgot_addr = (unsigned long)iter->dyn.d_un.d_val + offset; break; case DT_JMPREL: plt_found = true; break; default: break; } } if (!plt_found) { pd = mcount_arch_hook_no_plt(elf, modname, offset); if (pd == NULL) pr_dbg2("no PLTGOT found.. ignoring...\n"); else list_add_tail(&pd->list, &plthook_modules); return 0; } elf_for_each_shdr(elf, iter) { char *shstr = elf_get_name(elf, iter, iter->shdr.sh_name); if (strcmp(shstr, ".plt") == 0) { plt_addr = iter->shdr.sh_addr + offset; break; } } if (plt_addr == 0) { pr_dbg("cannot find PLT address\n"); return 0; } pd = xmalloc(sizeof(*pd)); pd->mod_name = xstrdup(modname); pd->pltgot_ptr = (void *)pltgot_addr; pd->module_id = pd->pltgot_ptr[1]; pd->base_addr = offset; pd->plt_addr = plt_addr; pr_dbg2("\"%s\" is loaded at %#lx\n", basename(pd->mod_name), pd->base_addr); memset(&pd->dsymtab, 0, sizeof(pd->dsymtab)); load_elf_dynsymtab(&pd->dsymtab, elf, pd->base_addr, SYMTAB_FL_DEMANGLE); pd->resolved_addr = xcalloc(pd->dsymtab.nr_sym, sizeof(long)); pd->special_funcs = NULL; pd->nr_special = 0; mcount_arch_plthook_setup(pd, elf); list_add_tail(&pd->list, &plthook_modules); if (plthook_resolver_addr == 0) plthook_resolver_addr = pd->pltgot_ptr[2]; /* * BIND_NOW (+ RELRO) makes module id not used and resets to 0. * but we still need it to find pd from plthook_enter(). */ if (pd->module_id == 0) { pr_dbg2("update module id to %p\n", pd); overwrite_pltgot(pd, 1, pd); pd->module_id = (unsigned long)pd; } pr_dbg2("found GOT at %p (base_addr + %#lx)\n", pd->pltgot_ptr, (unsigned long)pd->pltgot_ptr - pd->base_addr); pr_dbg2("module id = %#lx, PLT resolver = %#lx\n", pd->module_id, plthook_resolver_addr); restore_plt_functions(pd); overwrite_pltgot(pd, 2, plt_hooker); if (getenv("LD_BIND_NOT")) plthook_no_pltbind = true; return 0; } static int hook_pltgot(const char *modname, unsigned long offset) { int ret = -1; bool relro = false; unsigned long relro_start = 0; unsigned long relro_size = 0; unsigned long page_size; struct uftrace_elf_data elf; struct uftrace_elf_iter iter; bool found_dynamic = false; pr_dbg2("opening executable image: %s\n", modname); if (elf_init(modname, &elf) < 0) return -1; elf_for_each_phdr(&elf, &iter) { if (iter.phdr.p_type == PT_DYNAMIC) found_dynamic = true; if (iter.phdr.p_type == PT_GNU_RELRO) { relro_start = iter.phdr.p_vaddr + offset; relro_size = iter.phdr.p_memsz; page_size = getpagesize(); relro_start &= ~(page_size - 1); relro_size = ALIGN(relro_size, page_size); relro = true; } } if (found_dynamic) { if (relro) { mprotect((void *)relro_start, relro_size, PROT_READ | PROT_WRITE); } ret = find_got(&elf, &iter, modname, offset); if (relro) mprotect((void *)relro_start, relro_size, PROT_READ); } elf_finish(&elf); return ret; } /* functions should skip PLT hooking */ static const char *skip_syms[] = { "_mcleanup", "__libc_start_main", "__cxa_throw", "__cxa_rethrow", "__cxa_begin_catch", "__cxa_end_catch", "__cxa_finalize", "_Unwind_Resume", }; static const char *setjmp_syms[] = { "setjmp", "_setjmp", "sigsetjmp", "__sigsetjmp", }; static const char *longjmp_syms[] = { "longjmp", "siglongjmp", "__longjmp_chk", }; static const char *vfork_syms[] = { "vfork", }; static const char *dlsym_syms[] = { "dlsym", "dlvsym", }; static const char *flush_syms[] = { "fork", "vfork", "daemon", "exit", "longjmp", "siglongjmp", "__longjmp_chk", "execl", "execlp", "execle", "execv", "execve", "execvp", "execvpe", "fexecve", "posix_spawn", "posix_spawnp", }; static const char *except_syms[] = { "_Unwind_RaiseException", }; static const char *resolve_syms[] = { "execl", "execlp", "execle", "execv", "execve", "execvp", "execvpe", "fexecve", "posix_spawn", "posix_spawnp", "pthread_exit", }; static void add_special_func(struct plthook_data *pd, unsigned idx, unsigned flags) { int i; struct plthook_special_func *func; for (i = 0; i < pd->nr_special; i++) { func = &pd->special_funcs[i]; if (func->idx == idx) { func->flags |= flags; return; } } pd->special_funcs = xrealloc(pd->special_funcs, (pd->nr_special + 1) * sizeof(*func)); func = &pd->special_funcs[pd->nr_special++]; func->idx = idx; func->flags = flags; } static void build_special_funcs(struct plthook_data *pd, const char *syms[], unsigned nr_sym, unsigned flag) { unsigned i; struct dynsym_idxlist idxlist; build_dynsym_idxlist(&pd->dsymtab, &idxlist, syms, nr_sym); for (i = 0; i < idxlist.count; i++) add_special_func(pd, idxlist.idx[i], flag); destroy_dynsym_idxlist(&idxlist); } static int idxsort(const void *a, const void *b) { const struct plthook_special_func *func_a = a; const struct plthook_special_func *func_b = b; if (func_a->idx > func_b->idx) return 1; if (func_a->idx < func_b->idx) return -1; return 0; } static int idxfind(const void *a, const void *b) { unsigned idx = (unsigned long) a; const struct plthook_special_func *func = b; if (func->idx == idx) return 0; return (idx > func->idx) ? 1 : -1; } void setup_dynsym_indexes(struct plthook_data *pd) { build_special_funcs(pd, skip_syms, ARRAY_SIZE(skip_syms), PLT_FL_SKIP); build_special_funcs(pd, longjmp_syms, ARRAY_SIZE(longjmp_syms), PLT_FL_LONGJMP); build_special_funcs(pd, setjmp_syms, ARRAY_SIZE(setjmp_syms), PLT_FL_SETJMP); build_special_funcs(pd, vfork_syms, ARRAY_SIZE(vfork_syms), PLT_FL_VFORK); build_special_funcs(pd, dlsym_syms, ARRAY_SIZE(dlsym_syms), PLT_FL_DLSYM); build_special_funcs(pd, flush_syms, ARRAY_SIZE(flush_syms), PLT_FL_FLUSH); build_special_funcs(pd, except_syms, ARRAY_SIZE(except_syms), PLT_FL_EXCEPT); build_special_funcs(pd, resolve_syms, ARRAY_SIZE(resolve_syms), PLT_FL_RESOLVE); /* built all table, now sorting */ qsort(pd->special_funcs, pd->nr_special, sizeof(*pd->special_funcs), idxsort); } void destroy_dynsym_indexes(void) { struct plthook_data *pd; pr_dbg2("destroy plthook special function index\n"); list_for_each_entry(pd, &plthook_modules, list) { free(pd->special_funcs); pd->special_funcs = NULL; pd->nr_special = 0; } } static int setup_mod_plthook_data(struct dl_phdr_info *info, size_t sz, void *arg) { const char *exename = info->dlpi_name; unsigned long offset = info->dlpi_addr; static const char * const skip_libs[] = { /* uftrace internal libraries */ "libmcount.so", "libmcount-fast.so", "libmcount-single.so", "libmcount-fast-single.so", /* system base libraries */ "libc.so.6", "libc-2.*.so", "libgcc_s.so.1", "libpthread.so.0", "libpthread-2.*.so", "linux-vdso.so.1", "linux-gate.so.1", "ld-linux-*.so.*", "libdl.so.2", "libdl-2.*.so", }; size_t k; static bool exe_once = true; if (exename[0] == '\0') { if (!exe_once) return 0; exename = arg; exe_once = false; } for (k = 0; k < ARRAY_SIZE(skip_libs); k++) { if (!fnmatch(skip_libs[k], basename(exename), 0)) return 0; } pr_dbg2("setup plthook data for %s (offset: %lx)\n", exename, offset); if (hook_pltgot(exename, offset) < 0) pr_dbg("error when hooking plt: skipping...\n"); return 0; } static int setup_exe_plthook_data(struct dl_phdr_info *info, size_t sz, void *arg) { const char *exename = arg; unsigned long offset = info->dlpi_addr; pr_dbg2("setup plthook data for %s (offset: %lx)\n", exename, offset); hook_pltgot(exename, offset); return 1; } void mcount_setup_plthook(char *exename, bool nest_libcall) { struct plthook_data *pd; pr_dbg("setup %sPLT hooking \"%s\"\n", nest_libcall ? "nested " : "", exename); if (!nest_libcall) dl_iterate_phdr(setup_exe_plthook_data, exename); else dl_iterate_phdr(setup_mod_plthook_data, exename); list_for_each_entry(pd, &plthook_modules, list) setup_dynsym_indexes(pd); } struct mcount_jmpbuf_rstack { struct list_head list; unsigned long addr; int count; int record_idx; struct mcount_ret_stack rstack[MCOUNT_RSTACK_MAX]; }; static LIST_HEAD(jmpbuf_list); static void setup_jmpbuf_rstack(struct mcount_thread_data *mtdp, unsigned long addr) { int i; struct mcount_jmpbuf_rstack *jbstack; list_for_each_entry(jbstack, &jmpbuf_list, list) { if (jbstack->addr == addr) break; } if (list_no_entry(jbstack, &jmpbuf_list, list)) { jbstack = xmalloc(sizeof(*jbstack)); jbstack->addr = addr; list_add(&jbstack->list, &jmpbuf_list); } pr_dbg2("setup jmpbuf rstack at %lx (%d entries)\n", addr, mtdp->idx); /* currently, only saves a single jmpbuf */ jbstack->count = mtdp->idx; jbstack->record_idx = mtdp->record_idx; for (i = 0; i < jbstack->count; i++) jbstack->rstack[i] = mtdp->rstack[i]; } static void restore_jmpbuf_rstack(struct mcount_thread_data *mtdp, unsigned long addr) { int i; struct mcount_jmpbuf_rstack *jbstack; list_for_each_entry(jbstack, &jmpbuf_list, list) { if (jbstack->addr == addr) break; } assert(!list_no_entry(jbstack, &jmpbuf_list, list)); pr_dbg2("restore jmpbuf rstack at %lx (%d entries)\n", addr, jbstack->count); mtdp->idx = jbstack->count; mtdp->record_idx = jbstack->record_idx; for (i = 0; i < jbstack->count; i++) { mtdp->rstack[i] = jbstack->rstack[i]; /* setjmp() already wrote rstacks */ mtdp->rstack[i].flags |= MCOUNT_FL_WRITTEN; } } /* it's crazy to call vfork() concurrently */ static int vfork_parent; static int vfork_rstack_idx; static int vfork_record_idx; static struct mcount_ret_stack vfork_rstack; static struct mcount_shmem vfork_shmem; static void prepare_vfork(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack) { /* save original parent info */ vfork_parent = getpid(); vfork_rstack_idx = mtdp->idx; vfork_record_idx = mtdp->record_idx; mcount_memcpy4(&vfork_rstack, rstack, sizeof(*rstack)); /* it will be force flushed */ vfork_rstack.flags |= MCOUNT_FL_WRITTEN; } /* this function will be called in child */ static void setup_vfork(struct mcount_thread_data *mtdp) { struct uftrace_msg_task tmsg = { .pid = getppid(), .tid = getpid(), .time = mcount_gettime(), }; /* update tid cache */ mtdp->tid = tmsg.tid; mcount_memcpy4(&vfork_shmem, &mtdp->shmem, sizeof(vfork_shmem)); /* setup new shmem buffer for child */ mcount_memset4(&mtdp->shmem, 0, sizeof(mtdp->shmem)); prepare_shmem_buffer(mtdp); uftrace_send_message(UFTRACE_MSG_FORK_START, &tmsg, sizeof(tmsg)); uftrace_send_message(UFTRACE_MSG_FORK_END, &tmsg, sizeof(tmsg)); update_kernel_tid(tmsg.tid); } /* this function detects whether child is finished */ static struct mcount_ret_stack * restore_vfork(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack) { /* * On vfork, parent sleeps until child is exec'ed or exited. * So if it sees parent pid, that means child was done. */ if (getpid() == vfork_parent) { /* flush tid cache */ mtdp->tid = 0; mtdp->idx = vfork_rstack_idx; mtdp->record_idx = vfork_record_idx; rstack = &mtdp->rstack[mtdp->idx - 1]; vfork_parent = 0; mcount_memcpy4(&mtdp->shmem, &vfork_shmem, sizeof(vfork_shmem)); mcount_memcpy4(rstack, &vfork_rstack, sizeof(*rstack)); } return rstack; } /* * mcount_arch_plthook_addr() returns the address of GOT entry. * The initial value for each GOT entry redirects the execution to * the runtime resolver. (_dl_runtime_resolve in ld-linux.so) * * The GOT entry is updated by the runtime resolver to the resolved address of * the target library function for later reference. * * However, uftrace gets this address to update it back to the initial value. * Even if the GOT entry is resolved by runtime resolver, uftrace restores the * address back to the initial value to watch library function calls. * * Before doing this work, GOT[2] is updated from the address of runtime * resolver(_dl_runtime_resolve) to uftrace hooking routine(plt_hooker). * * This address depends on the PLT structure of each architecture so this * function is implemented differently for each architecture. */ __weak unsigned long mcount_arch_plthook_addr(struct plthook_data *pd, int idx) { struct sym *sym; sym = &pd->dsymtab.sym[idx]; return sym->addr + ARCH_PLTHOOK_ADDR_OFFSET; } static void update_pltgot(struct mcount_thread_data *mtdp, struct plthook_data *pd, int dyn_idx) { if (unlikely(plthook_no_pltbind)) return; if (!pd->resolved_addr[dyn_idx]) { unsigned long plthook_addr; #ifndef SINGLE_THREAD static pthread_mutex_t resolver_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&resolver_mutex); #endif if (!pd->resolved_addr[dyn_idx]) { int got_idx = 3 + dyn_idx; plthook_addr = mcount_arch_plthook_addr(pd, dyn_idx); setup_pltgot(pd, got_idx, dyn_idx, (void*)plthook_addr); } #ifndef SINGLE_THREAD pthread_mutex_unlock(&resolver_mutex); #endif } } __weak unsigned long mcount_arch_child_idx(unsigned long child_idx) { return child_idx; } static unsigned long __plthook_entry(unsigned long *ret_addr, unsigned long child_idx, unsigned long module_id, struct mcount_regs *regs) { struct sym *sym; struct mcount_thread_data *mtdp = NULL; struct mcount_ret_stack *rstack; struct uftrace_trigger tr = { .flags = 0, }; bool skip = false; bool recursion = true; enum filter_result filtered; struct plthook_data *pd; struct plthook_special_func *func; unsigned long special_flag = 0; unsigned long real_addr = 0; // if necessary, implement it by architecture. child_idx = mcount_arch_child_idx(child_idx); list_for_each_entry(pd, &plthook_modules, list) { if (module_id == pd->module_id) break; } if (list_no_entry(pd, &plthook_modules, list)) { pr_dbg("cannot find pd for module id: %lx\n", module_id); pd = NULL; goto out; } mtdp = get_thread_data(); if (unlikely(check_thread_data(mtdp))) { mtdp = mcount_prepare(); if (mtdp == NULL) goto out; } else { if (!mcount_guard_recursion(mtdp)) goto out; } recursion = false; func = bsearch((void *)child_idx, pd->special_funcs, pd->nr_special, sizeof(*func), idxfind); if (func) special_flag |= func->flags; if (unlikely(special_flag & PLT_FL_SKIP)) goto out; if (likely(child_idx < pd->dsymtab.nr_sym)) { sym = &pd->dsymtab.sym[child_idx]; pr_dbg3("[idx: %4d] enter %"PRIx64": %s@plt (mod: %lx)\n", (int)child_idx, sym->addr, sym->name, module_id); } else { sym = NULL; pr_err_ns("invalid function idx found! (idx: %lu/%zu, module: %s)\n", child_idx, pd->dsymtab.nr_sym, pd->mod_name); } filtered = mcount_entry_filter_check(mtdp, sym->addr, &tr); if (filtered != FILTER_IN) { /* * Skip recording but still hook the return address, * otherwise it cannot trace further invocations due to * the overwritten PLT entry by the resolver function. */ skip = true; /* but if we don't have rstack, just bail out */ if (filtered == FILTER_RSTACK) goto out; } rstack = &mtdp->rstack[mtdp->idx++]; rstack->depth = mtdp->record_idx; rstack->pd = pd; rstack->dyn_idx = child_idx; rstack->parent_loc = ret_addr; rstack->parent_ip = *ret_addr; rstack->child_ip = sym->addr; rstack->start_time = skip ? 0 : mcount_gettime(); rstack->end_time = 0; rstack->flags = skip ? MCOUNT_FL_NORECORD : 0; rstack->nr_events = 0; rstack->event_idx = ARGBUF_SIZE; /* hijack the return address of child */ *ret_addr = (unsigned long)plthook_return; /* restore return address of parent */ if (mcount_auto_recover) mcount_auto_restore(mtdp); mcount_entry_filter_record(mtdp, rstack, &tr, regs); if (unlikely(special_flag)) { /* force flush rstack on some special functions */ if (special_flag & PLT_FL_FLUSH) { record_trace_data(mtdp, rstack, NULL); } if (special_flag & PLT_FL_SETJMP) { setup_jmpbuf_rstack(mtdp, ARG1(regs)); } else if (special_flag & PLT_FL_LONGJMP) { rstack->flags |= MCOUNT_FL_LONGJMP; /* abuse end-time for the jmpbuf addr */ rstack->end_time = ARG1(regs); } else if (special_flag & PLT_FL_VFORK) { rstack->flags |= MCOUNT_FL_VFORK; prepare_vfork(mtdp, rstack); } else if (special_flag & PLT_FL_DLSYM) { /* * Using RTLD_NEXT in a shared library caused * an infinite loop since libdl thinks it's * called from libmcount due to the return address. */ if (ARG1(regs) == (unsigned long)RTLD_NEXT && strcmp(pd->mod_name, mcount_exename)) { *ret_addr = rstack->parent_ip; if (mcount_auto_recover) mcount_auto_reset(mtdp); /* * as its return address was recovered, * we need to manually resolve the function * not to overwrite PLT entry by the linker. */ special_flag |= PLT_FL_RESOLVE; if (!(rstack->flags & MCOUNT_FL_NORECORD)) rstack->end_time = mcount_gettime(); mcount_exit_filter_record(mtdp, rstack, NULL); mtdp->idx--; } } else if (special_flag & PLT_FL_EXCEPT) { /* exception handling requires stack unwind */ mcount_rstack_restore(mtdp); } if (special_flag & PLT_FL_RESOLVE) { /* some functions don't have a chance to resolve */ resolve_pltgot(pd, child_idx); } } out: if (likely(pd && child_idx < pd->dsymtab.nr_sym) && pd->resolved_addr[child_idx] != 0) real_addr = pd->resolved_addr[child_idx]; if (!recursion) mcount_unguard_recursion(mtdp); return real_addr; } unsigned long plthook_entry(unsigned long *ret_addr, unsigned long child_idx, unsigned long module_id, struct mcount_regs *regs) { int saved_errno = errno; unsigned long ret; ret = __plthook_entry(ret_addr, child_idx, module_id, regs); errno = saved_errno; return ret; } void mtd_dtor(void *arg); static unsigned long __plthook_exit(long *retval) { unsigned dyn_idx; struct mcount_thread_data *mtdp; struct mcount_ret_stack *rstack; unsigned long *ret_loc; unsigned long ret_addr = 0; mtdp = get_thread_data(); assert(!check_thread_data(mtdp)); /* * it's only called when mcount_entry() was succeeded and * no need to check recursion here. But still needs to * prevent recursion during this call. */ __mcount_guard_recursion(mtdp); again: if (likely(mtdp->idx > 0)) rstack = &mtdp->rstack[mtdp->idx - 1]; else rstack = restore_vfork(mtdp, NULL); /* FIXME! */ if (unlikely(rstack->flags & (MCOUNT_FL_LONGJMP | MCOUNT_FL_VFORK))) { if (rstack->flags & MCOUNT_FL_LONGJMP) { update_pltgot(mtdp, rstack->pd, rstack->dyn_idx); rstack->flags &= ~MCOUNT_FL_LONGJMP; restore_jmpbuf_rstack(mtdp, rstack->end_time); goto again; } if (rstack->flags & MCOUNT_FL_VFORK) setup_vfork(mtdp); } if (unlikely(vfork_parent)) rstack = restore_vfork(mtdp, rstack); dyn_idx = rstack->dyn_idx; if (unlikely(dyn_idx == MCOUNT_INVALID_DYNIDX || dyn_idx >= rstack->pd->dsymtab.nr_sym)) pr_err_ns("<%d> invalid dynsym idx: %d\n", mtdp->idx, dyn_idx); if (!ARCH_CAN_RESTORE_PLTHOOK && unlikely(mtdp->dead)) { ret_addr = rstack->parent_ip; /* make sure it doesn't have plthook below */ mtdp->idx--; if (!mcount_rstack_has_plthook(mtdp)) { free(mtdp->rstack); mtdp->rstack = NULL; mtdp->idx = 0; } return ret_addr; } if (!(rstack->flags & MCOUNT_FL_NORECORD)) rstack->end_time = mcount_gettime(); mcount_exit_filter_record(mtdp, rstack, retval); /* * Since dynamic linker calls fixup routine to patch this GOT entry * to the resolved address, it needs to restore GOT entry back to the * initial value so that it can go to plt_hooker again. * Otherwise, it will directly jump to the resolved address and there's * no way to trace it in the next reference. */ update_pltgot(mtdp, rstack->pd, dyn_idx); ret_loc = rstack->parent_loc; ret_addr = rstack->parent_ip; pr_dbg3("[idx: %4d] exit %lx: %s (resolved addr: %lx)\n", dyn_idx, ret_addr, rstack->pd->dsymtab.sym[dyn_idx].name, rstack->pd->resolved_addr[dyn_idx]); /* re-hijack return address of parent */ if (mcount_auto_recover) mcount_auto_reset(mtdp); __mcount_unguard_recursion(mtdp); if (unlikely(mcount_should_stop())) { mtd_dtor(mtdp); /* * mtd_dtor() will free rstack but current ret_addr * might be plthook_return() when it was a tail call. * Reload the return address after mtd_dtor() restored * all the parent locations. */ if (ARCH_CAN_RESTORE_PLTHOOK) ret_addr = *ret_loc; } compiler_barrier(); mtdp->idx--; return ret_addr; } unsigned long plthook_exit(long *retval) { int saved_errno = errno; unsigned long ret = __plthook_exit(retval); errno = saved_errno; return ret; } uftrace-0.9.4/libmcount/pmu.c000066400000000000000000000115131362052523300161200ustar00rootroot00000000000000#include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "mcount" #define PR_DOMAIN DBG_MCOUNT #include "libmcount/mcount.h" #include "libmcount/internal.h" #include "utils/utils.h" #include "utils/list.h" /* PMU management data for given event */ struct pmu_data { struct list_head list; enum uftrace_event_id evt_id; int n_members; int refcnt; int fd[]; }; /* attribute for perf_event_open(2) */ struct pmu_config { uint32_t type; uint64_t config; char *name; }; static const struct pmu_config cycle[] = { { PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES, "cycles", }, { PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS, "instructions", }, }; static const struct pmu_config cache[] = { { PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES, "cache-references", }, { PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES, "cache-misses", }, }; static const struct pmu_config branch[] = { { PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS, "branches", }, { PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES, "branch-misses", }, }; static const struct pmu_info { enum uftrace_event_id event_id; unsigned n_members; const struct pmu_config *const setting; } pmu_configs[] = { { EVENT_ID_READ_PMU_CYCLE, ARRAY_SIZE(cycle), cycle }, { EVENT_ID_READ_PMU_CACHE, ARRAY_SIZE(cache), cache }, { EVENT_ID_READ_PMU_BRANCH, ARRAY_SIZE(branch), branch }, }; #ifndef PERF_FLAG_FD_CLOEXEC # define PERF_FLAG_FD_CLOEXEC 0 #endif static int open_perf_event(uint32_t type, uint64_t config, int group_fd) { struct perf_event_attr attr = { .size = sizeof(attr), .type = type, .config = config, .exclude_kernel = 1, .read_format = PERF_FORMAT_GROUP, }; unsigned long flag = PERF_FLAG_FD_CLOEXEC; int fd; fd = syscall(SYS_perf_event_open, &attr, 0, -1, group_fd, flag); if (fd >= 0 && flag == 0) { if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) pr_dbg("setting FD_CLOEXEC failed: %m\n"); } return fd; } static void read_perf_event(int fd, void *buf, ssize_t len) { if (read(fd, buf, len) != len) pr_dbg("reading perf_event failed: %m\n"); } static struct pmu_data * prepare_pmu_event(struct mcount_thread_data *mtdp, enum uftrace_event_id id) { struct pmu_data *pd; const struct pmu_info *info; unsigned i, k; int group_fd; list_for_each_entry(pd, &mtdp->pmu_fds, list) { if (pd->evt_id == id) { pd->refcnt++; return pd; } } pr_dbg("setup PMU event (%d) using perf syscall\n", id); for (i = 0; i < ARRAY_SIZE(pmu_configs); i++) { info = &pmu_configs[i]; if (id != info->event_id) continue; pd = xmalloc(sizeof(*pd) + info->n_members * sizeof(int)); pd->evt_id = id; group_fd = open_perf_event(info->setting[0].type, info->setting[0].config, -1); if (group_fd < 0) { pr_warn("failed to open '%s' perf event: %m\n", info->setting[0].name); free(pd); return NULL; } pd->fd[0] = group_fd; for (k = 1; k < info->n_members; k++) { pd->fd[k] = open_perf_event(info->setting[k].type, info->setting[k].config, group_fd); if (pd->fd[k] < 0) { pr_warn("failed to open '%s' perf event: %m\n", info->setting[k].name); free(pd); return NULL; } } pd->n_members = info->n_members; break; } pd->refcnt = 1; if (i == ARRAY_SIZE(pmu_configs)) pr_dbg("unknown pmu event: %d - ignoring\n", id); else list_add_tail(&pd->list, &mtdp->pmu_fds); return pd; } int read_pmu_event(struct mcount_thread_data *mtdp, enum uftrace_event_id id, void *buf) { struct pmu_data *pd; struct { uint64_t nr_members; uint64_t data[2]; } read_buf; pd = prepare_pmu_event(mtdp, id); if (pd == NULL) return -1; /* read group events at once */ read_perf_event(pd->fd[0], &read_buf, sizeof(read_buf)); mcount_memcpy4(buf, read_buf.data, sizeof(*read_buf.data) * read_buf.nr_members); return 0; } void finish_pmu_event(struct mcount_thread_data *mtdp) { struct pmu_data *pd, *tmp; list_for_each_entry_safe(pd, tmp, &mtdp->pmu_fds, list) { list_del(&pd->list); switch (pd->evt_id) { case EVENT_ID_READ_PMU_CYCLE: case EVENT_ID_READ_PMU_CACHE: case EVENT_ID_READ_PMU_BRANCH: close(pd->fd[0]); close(pd->fd[1]); break; default: break; } free(pd); } } void release_pmu_event(struct mcount_thread_data *mtdp, enum uftrace_event_id id) { struct pmu_data *pd, *tmp; list_for_each_entry_safe(pd, tmp, &mtdp->pmu_fds, list) { if (pd->evt_id != id) continue; /* -2 because read and diff pass will increase it separately */ pd->refcnt -= 2; if (pd->refcnt > 0) continue; list_del(&pd->list); switch (pd->evt_id) { case EVENT_ID_READ_PMU_CYCLE: case EVENT_ID_READ_PMU_CACHE: case EVENT_ID_READ_PMU_BRANCH: close(pd->fd[0]); close(pd->fd[1]); break; default: break; } free(pd); } } uftrace-0.9.4/libmcount/record.c000066400000000000000000000667471362052523300166200ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "mcount" #define PR_DOMAIN DBG_MCOUNT #include "libmcount/mcount.h" #include "libmcount/internal.h" #include "mcount-arch.h" #include "utils/utils.h" #include "utils/filter.h" #define SHMEM_SESSION_FMT "/uftrace-%s-%d-%03d" /* session-id, tid, seq */ #define ARG_STR_MAX 98 static struct mcount_shmem_buffer *allocate_shmem_buffer(char *sess_id, size_t size, int tid, int idx) { int fd; int saved_errno = 0; struct mcount_shmem_buffer *buffer = NULL; snprintf(sess_id, size, SHMEM_SESSION_FMT, mcount_session_name(), tid, idx); fd = shm_open(sess_id, O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd < 0) { saved_errno = errno; pr_dbg("failed to open shmem buffer: %s\n", sess_id); goto out; } if (ftruncate(fd, shmem_bufsize) < 0) { saved_errno = errno; pr_dbg("failed to resizing shmem buffer: %s\n", sess_id); goto out; } buffer = mmap(NULL, shmem_bufsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (buffer == MAP_FAILED) { saved_errno = errno; pr_dbg("failed to mmap shmem buffer: %s\n", sess_id); buffer = NULL; goto out; } close(fd); out: errno = saved_errno; return buffer; } void prepare_shmem_buffer(struct mcount_thread_data *mtdp) { char buf[128]; int idx; int tid = mcount_gettid(mtdp); struct mcount_shmem *shmem = &mtdp->shmem; pr_dbg2("preparing shmem buffers: tid = %d\n", tid); shmem->nr_buf = 2; shmem->max_buf = 2; shmem->buffer = xcalloc(sizeof(*shmem->buffer), 2); for (idx = 0; idx < shmem->nr_buf; idx++) { shmem->buffer[idx] = allocate_shmem_buffer(buf, sizeof(buf), tid, idx); if (shmem->buffer[idx] == NULL) pr_err("mmap shmem buffer"); } /* set idx 0 as current buffer */ snprintf(buf, sizeof(buf), SHMEM_SESSION_FMT, mcount_session_name(), tid, 0); uftrace_send_message(UFTRACE_MSG_REC_START, buf, strlen(buf)); shmem->done = false; shmem->curr = 0; shmem->buffer[0]->flag = SHMEM_FL_RECORDING | SHMEM_FL_NEW; } static void get_new_shmem_buffer(struct mcount_thread_data *mtdp) { char buf[128]; struct mcount_shmem *shmem = &mtdp->shmem; struct mcount_shmem_buffer *curr_buf = NULL; struct mcount_shmem_buffer **new_buffer; int idx; /* always use first buffer available */ for (idx = 0; idx < shmem->nr_buf; idx++) { curr_buf = shmem->buffer[idx]; if (!(curr_buf->flag & SHMEM_FL_RECORDING)) goto reuse; } new_buffer = realloc(shmem->buffer, sizeof(*new_buffer) * (idx + 1)); if (new_buffer) { /* * it already free'd the old buffer, keep the new buffer * regardless of allocation failure. */ shmem->buffer = new_buffer; curr_buf = allocate_shmem_buffer(buf, sizeof(buf), mcount_gettid(mtdp), idx); } if (new_buffer == NULL || curr_buf == NULL) { shmem->losts++; shmem->curr = -1; return; } shmem->buffer[idx] = curr_buf; shmem->nr_buf++; if (shmem->nr_buf > shmem->max_buf) shmem->max_buf = shmem->nr_buf; reuse: /* * Start a new buffer and mark its recording data. * See cmd-record.c::writer_thread(). */ __sync_fetch_and_or(&curr_buf->flag, SHMEM_FL_RECORDING); shmem->seqnum++; shmem->curr = idx; curr_buf->size = 0; /* shrink unused buffers */ if (idx + 3 <= shmem->nr_buf) { int i; int count = 0; struct mcount_shmem_buffer *b; for (i = idx + 1; i < shmem->nr_buf; i++) { b = shmem->buffer[i]; if (b->flag == SHMEM_FL_WRITTEN) count++; } /* if 3 or more buffers are unused, free the last one */ if (count >= 3 && b->flag == SHMEM_FL_WRITTEN) { shmem->nr_buf--; munmap(b, shmem_bufsize); } } snprintf(buf, sizeof(buf), SHMEM_SESSION_FMT, mcount_session_name(), mcount_gettid(mtdp), idx); pr_dbg2("new buffer: [%d] %s\n", idx, buf); uftrace_send_message(UFTRACE_MSG_REC_START, buf, strlen(buf)); if (shmem->losts) { struct uftrace_record *frstack = (void *)curr_buf->data; frstack->time = 0; frstack->type = UFTRACE_LOST; frstack->magic = RECORD_MAGIC; frstack->more = 0; frstack->addr = shmem->losts; uftrace_send_message(UFTRACE_MSG_LOST, &shmem->losts, sizeof(shmem->losts)); curr_buf->size = sizeof(*frstack); shmem->losts = 0; } } static void finish_shmem_buffer(struct mcount_thread_data *mtdp, int idx) { char buf[64]; snprintf(buf, sizeof(buf), SHMEM_SESSION_FMT, mcount_session_name(), mcount_gettid(mtdp), idx); uftrace_send_message(UFTRACE_MSG_REC_END, buf, strlen(buf)); } void clear_shmem_buffer(struct mcount_thread_data *mtdp) { struct mcount_shmem *shmem = &mtdp->shmem; int i; pr_dbg2("releasing all shmem buffers for task %d\n", mcount_gettid(mtdp)); for (i = 0; i < shmem->nr_buf; i++) munmap(shmem->buffer[i], shmem_bufsize); free(shmem->buffer); shmem->buffer = NULL; shmem->nr_buf = 0; } void shmem_finish(struct mcount_thread_data *mtdp) { struct mcount_shmem *shmem = &mtdp->shmem; struct mcount_shmem_buffer *curr_buf; int curr = shmem->curr; if (curr >= 0 && shmem->buffer) { curr_buf = shmem->buffer[curr]; if (curr_buf->flag & SHMEM_FL_RECORDING) finish_shmem_buffer(mtdp, curr); } shmem->done = true; shmem->curr = -1; pr_dbg("%s: tid: %d seqnum = %u curr = %d, nr_buf = %d max_buf = %d\n", __func__, mcount_gettid(mtdp), shmem->seqnum, curr, shmem->nr_buf, shmem->max_buf); clear_shmem_buffer(mtdp); } static struct mcount_event * get_event_pointer(void *base, unsigned idx) { size_t len = 0; struct mcount_event *event = base; while (idx--) { len += EVTBUF_HDR + event->dsize; event = base + len; } return event; } #ifndef DISABLE_MCOUNT_FILTER void *get_argbuf(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack) { ptrdiff_t idx = rstack - mtdp->rstack; return mtdp->argbuf + (idx * ARGBUF_SIZE); } #define HEAP_REGION_UNIT 128*MB #define STACK_REGION_UNIT 8*MB struct mem_region { struct rb_node node; unsigned long start; unsigned long end; }; static void add_mem_region(struct rb_root *root, unsigned long start, unsigned long end, bool update_end) { struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; struct mem_region *iter, *entry; while (*p) { parent = *p; iter = rb_entry(parent, struct mem_region, node); if (update_end) { if (iter->start == start) { if (iter->end != end) iter->end = end; return; } } else { if (iter->end == end) { if (iter->start != start) iter->start = start; return; } } if (iter->start > start) p = &parent->rb_left; else p = &parent->rb_right; } entry = xmalloc(sizeof(*entry)); entry->start = start; entry->end = end; pr_dbg3("mem region: %lx - %lx\n", start, end); rb_link_node(&entry->node, parent, p); rb_insert_color(&entry->node, root); } static void update_mem_regions(struct mcount_mem_regions *regions) { FILE *fp; char buf[PATH_MAX]; fp = fopen("/proc/self/maps", "r"); if (fp == NULL) return; while (fgets(buf, sizeof(buf), fp) != NULL) { char *p = buf, *next; unsigned long start, end; bool is_stack = false; /* XXX: cannot use *scanf() due to crash (SSE alignment?) */ start = strtoul(p, &next, 16); if (*next != '-') pr_warn("invalid /proc/map format\n"); p = next + 1; end = strtoul(p, &next, 16); if (strstr(next, "[heap]")) { end = ROUND_UP(end, HEAP_REGION_UNIT); if (end > regions->brk) regions->brk = end; regions->heap = start; } if (strstr(next, "[stack")) { start = ROUND_DOWN(start, STACK_REGION_UNIT); is_stack = true; } add_mem_region(®ions->root, start, end, !is_stack); } fclose(fp); } static bool find_mem_region(struct rb_root *root, unsigned long addr) { struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; struct mem_region *iter; while (*p) { parent = *p; iter = rb_entry(parent, struct mem_region, node); if (iter->start <= addr && addr < iter->end) return true; if (iter->start > addr) p = &parent->rb_left; else p = &parent->rb_right; } pr_dbg2("cannot find mem region: %lx\n", addr); return false; } bool check_mem_region(struct mcount_arg_context *ctx, unsigned long addr) { bool update = true; struct mcount_mem_regions *regions = ctx->regions; retry: if (regions->heap <= addr && addr < regions->brk) return true; if (find_mem_region(®ions->root, addr)) return true; if (update) { mcount_save_arch_context(ctx->arch); update_mem_regions(regions); mcount_restore_arch_context(ctx->arch); update = false; goto retry; } return false; } void finish_mem_region(struct mcount_mem_regions *regions) { struct rb_root *root = ®ions->root; struct rb_node *node; struct mem_region *mr; while (!RB_EMPTY_ROOT(root)) { node = rb_first(root); mr = rb_entry(node, typeof(*mr), node); rb_erase(node, root); free(mr); } } static unsigned save_to_argbuf(void *argbuf, struct list_head *args_spec, struct mcount_arg_context *ctx) { struct uftrace_arg_spec *spec; unsigned size, total_size = 0; unsigned max_size = ARGBUF_SIZE - sizeof(size); bool is_retval = !!ctx->retval; void *ptr; ptr = argbuf + sizeof(total_size); list_for_each_entry(spec, args_spec, list) { if (is_retval != (spec->idx == RETVAL_IDX)) continue; if (is_retval) mcount_arch_get_retval(ctx, spec); else mcount_arch_get_arg(ctx, spec); if (spec->fmt == ARG_FMT_STR || spec->fmt == ARG_FMT_STD_STRING) { unsigned short len; char *str = ctx->val.p; if (spec->fmt == ARG_FMT_STD_STRING) { /* * This is libstdc++ implementation dependent. * So doesn't work on others such as libc++. */ long *base = ctx->val.p; long *_M_string_length = base + 1; if (check_mem_region(ctx, (unsigned long)base)) { char *_M_dataplus = (char*)(*base); len = *_M_string_length; str = _M_dataplus; } } if (str) { unsigned i; char *dst = ptr + 2; char buf[32]; if (!check_mem_region(ctx, (unsigned long)str)) { len = snprintf(buf, sizeof(buf), "<%p>", str); str = buf; } /* * Calling strlen() might clobber floating-point * registers (on x86) depends on the internal * implementation. Do it manually. */ len = 0; for (i = 0; i < max_size - total_size; i++) { dst[i] = str[i]; /* truncate long string */ if (i == ARG_STR_MAX) { dst[i-3] = '.'; dst[i-2] = '.'; dst[i-1] = '.'; dst[i] = '\0'; } if (!dst[i]) break; len++; } /* store 2-byte length before string */ *(unsigned short *)ptr = len; } else { const char null_str[4] = { 'N', 'U', 'L', 'L' }; len = sizeof(null_str); mcount_memcpy1(ptr, &len, sizeof(len)); mcount_memcpy1(ptr + 2, null_str, len); } size = ALIGN(len + 2, 4); } else { size = ALIGN(spec->size, 4); mcount_memcpy4(ptr, ctx->val.v, size); } ptr += size; total_size += size; } if (total_size > max_size) return -1U; return total_size; } void save_argument(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, struct list_head *args_spec, struct mcount_regs *regs) { void *argbuf = get_argbuf(mtdp, rstack); unsigned size; struct mcount_arg_context ctx = { .regs = regs, .stack_base = rstack->parent_loc, .regions = &mtdp->mem_regions, .arch = &mtdp->arch, }; size = save_to_argbuf(argbuf, args_spec, &ctx); if (size == -1U) { pr_warn("argument data is too big\n"); return; } *(unsigned *)argbuf = size; rstack->flags |= MCOUNT_FL_ARGUMENT; } void save_retval(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, long *retval) { struct list_head *args_spec = rstack->pargs; void *argbuf = get_argbuf(mtdp, rstack); unsigned size; struct mcount_arg_context ctx = { .retval = retval, .regions = &mtdp->mem_regions, .arch = &mtdp->arch, }; size = save_to_argbuf(argbuf, args_spec, &ctx); if (size == -1U) { pr_warn("retval data is too big\n"); rstack->flags &= ~MCOUNT_FL_RETVAL; return; } *(uint32_t *)argbuf = size; } static int save_proc_statm(void *ctx, void *buf) { FILE *fp; struct uftrace_proc_statm *statm = buf; fp = fopen("/proc/self/statm", "r"); if (fp == NULL) pr_err("failed to open /proc/self/statm"); if (fscanf(fp, "%"SCNu64" %"SCNu64" %"SCNu64, &statm->vmsize, &statm->vmrss, &statm->shared) != 3) pr_err("failed to scan /proc/self/statm"); /* * Since /proc/[pid]/statm prints the number of pages for each field, * it'd be better to keep the memory size in KB. */ statm->vmsize *= page_size_in_kb; statm->vmrss *= page_size_in_kb; statm->shared *= page_size_in_kb; fclose(fp); return 0; } static void diff_proc_statm(void *ctx, void *dst, void *src) { struct uftrace_proc_statm *dst_statm = dst; struct uftrace_proc_statm *src_statm = src; dst_statm->vmsize -= src_statm->vmsize; dst_statm->vmrss -= src_statm->vmrss; dst_statm->shared -= src_statm->shared; } static int save_page_fault(void *ctx, void *buf) { struct rusage ru; struct uftrace_page_fault *page_fault = buf; /* getrusage provides faults info in a single syscall */ if (getrusage(RUSAGE_SELF, &ru) < 0) return -1; page_fault->major = ru.ru_majflt; page_fault->minor = ru.ru_minflt; return 0; } static void diff_page_fault(void *ctx, void *dst, void *src) { struct uftrace_page_fault *dst_pgflt = dst; struct uftrace_page_fault *src_pgflt = src; dst_pgflt->major -= src_pgflt->major; dst_pgflt->minor -= src_pgflt->minor; } static int save_pmu_cycle(void *ctx, void *buf) { return read_pmu_event(ctx, EVENT_ID_READ_PMU_CYCLE, buf); } static void diff_pmu_cycle(void *ctx, void *dst, void *src) { struct uftrace_pmu_cycle *dst_cycle = dst; struct uftrace_pmu_cycle *src_cycle = src; dst_cycle->cycles -= src_cycle->cycles; dst_cycle->instrs -= src_cycle->instrs; release_pmu_event(ctx, EVENT_ID_READ_PMU_CYCLE); } static int save_pmu_cache(void *ctx, void *buf) { return read_pmu_event(ctx, EVENT_ID_READ_PMU_CACHE, buf); } static void diff_pmu_cache(void *ctx, void *dst, void *src) { struct uftrace_pmu_cache *dst_cache = dst; struct uftrace_pmu_cache *src_cache = src; dst_cache->refers -= src_cache->refers; dst_cache->misses -= src_cache->misses; release_pmu_event(ctx, EVENT_ID_READ_PMU_CACHE); } static int save_pmu_branch(void *ctx, void *buf) { return read_pmu_event(ctx, EVENT_ID_READ_PMU_BRANCH, buf); } static void diff_pmu_branch(void *ctx, void *dst, void *src) { struct uftrace_pmu_branch *dst_branch = dst; struct uftrace_pmu_branch *src_branch = src; dst_branch->branch -= src_branch->branch; dst_branch->misses -= src_branch->misses; release_pmu_event(ctx, EVENT_ID_READ_PMU_BRANCH); } /* above functions should follow the name convention to use below macro */ #define TR_ID(_evt) TRIGGER_READ_##_evt, EVENT_ID_READ_##_evt, EVENT_ID_DIFF_##_evt #define TR_DS(_evt) sizeof(struct uftrace_##_evt) #define TR_FN(_evt) save_##_evt, diff_##_evt static struct read_event_data { enum trigger_read_type type; enum uftrace_event_id id_read; enum uftrace_event_id id_diff; size_t size; int (*save)(void *ctx, void *buf); void (*diff)(void *ctx, void *dst, void *src); } read_events[] = { { TR_ID(PROC_STATM), TR_DS(proc_statm), TR_FN(proc_statm) }, { TR_ID(PAGE_FAULT), TR_DS(page_fault), TR_FN(page_fault) }, { TR_ID(PMU_CYCLE), TR_DS(pmu_cycle), TR_FN(pmu_cycle) }, { TR_ID(PMU_CACHE), TR_DS(pmu_cache), TR_FN(pmu_cache) }, { TR_ID(PMU_BRANCH), TR_DS(pmu_branch), TR_FN(pmu_branch) }, }; #undef TR_ID #undef TR_DS #undef TR_FN void save_trigger_read(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, enum trigger_read_type type, bool diff) { void *ptr = get_argbuf(mtdp, rstack) + rstack->event_idx; struct mcount_event *event; unsigned short evsize; void *arg_data = get_argbuf(mtdp, rstack); size_t i; if (rstack->flags & (MCOUNT_FL_ARGUMENT | MCOUNT_FL_RETVAL)) arg_data += *(uint32_t *)ptr; for (i = 0; i < ARRAY_SIZE(read_events); i++) { struct read_event_data *red = &read_events[i]; if (!(type & red->type)) continue; evsize = EVTBUF_HDR + red->size; event = ptr - evsize; /* do not overwrite argument data */ if ((void *)event < arg_data) continue; event->id = red->id_read; event->time = rstack->end_time ?: rstack->start_time; event->dsize = red->size; event->idx = mtdp->idx; if (red->save(mtdp, event->data) < 0) continue; if (diff) { struct mcount_event *old_event = NULL; unsigned idx; for (idx = 0; idx < rstack->nr_events; idx++) { old_event = get_event_pointer(ptr, idx); if (old_event->id == event->id) break; old_event = NULL; } if (old_event) { event->id = red->id_diff; red->diff(mtdp, event->data, old_event->data); } } ptr = event; rstack->nr_events++; rstack->event_idx -= evsize; } } void save_watchpoint(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, unsigned long watchpoints) { uint64_t timestamp; ptrdiff_t rstack_idx; bool init_watch; timestamp = rstack->end_time ?: rstack->start_time; rstack_idx = rstack - mtdp->rstack; init_watch = !mtdp->watch.inited; if (init_watch) { /* * Normally watch point event comes before the rstack (record) * in order to indicate where it's changed precisely. * But first watch point event needs to come after the first * record otherwise it'd not show since 'event-skip' mechanism. * Therefore, add 2(nsec) so that it can be 1 nsec later. */ timestamp += 2; mtdp->watch.inited = true; } /* save watch event before normal record */ timestamp -= 1; if (watchpoints & MCOUNT_WATCH_CPU) { int cpu = sched_getcpu(); if ((mtdp->watch.cpu != cpu || init_watch) && mtdp->nr_events < MAX_EVENT) { struct mcount_event *event; event = &mtdp->event[mtdp->nr_events++]; event->id = EVENT_ID_WATCH_CPU; event->time = timestamp; event->idx = rstack_idx; event->dsize = sizeof(cpu); mcount_memcpy4(event->data, &cpu, sizeof(cpu)); } mtdp->watch.cpu = cpu; } } #else void *get_argbuf(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack) { return NULL; } void save_retval(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, long *retval) { } void save_trigger_read(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, enum trigger_read_type type) { } void save_watchpoint(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, unsigned long watchpoints) { } bool check_mem_region(struct mcount_arg_context *ctx, unsigned long addr) { return true; } #endif static struct mcount_shmem_buffer * get_shmem_buffer(struct mcount_thread_data *mtdp, size_t size) { struct mcount_shmem *shmem = &mtdp->shmem; struct mcount_shmem_buffer *curr_buf = shmem->buffer[shmem->curr]; size_t maxsize = (size_t)shmem_bufsize - sizeof(**shmem->buffer); if (unlikely(shmem->curr == -1 || curr_buf->size + size > maxsize)) { if (shmem->done) return NULL; if (shmem->curr > -1) finish_shmem_buffer(mtdp, shmem->curr); get_new_shmem_buffer(mtdp); if (shmem->curr == -1) { shmem->losts++; return NULL; } curr_buf = shmem->buffer[shmem->curr]; } return curr_buf; } static int record_event(struct mcount_thread_data *mtdp, struct mcount_event *event) { struct mcount_shmem_buffer *curr_buf; struct { uint64_t time; uint64_t data; } *rec; size_t size = sizeof(*rec); uint16_t data_size = event->dsize; if (data_size) size += ALIGN(data_size + 2, 8); curr_buf = get_shmem_buffer(mtdp, size); if (curr_buf == NULL) return mtdp->shmem.done ? 0 : -1; rec = (void *)(curr_buf->data + curr_buf->size); /* * instead of set bit fields, do the bit operations manually. * this would be good for both performance and portability. */ rec->data = UFTRACE_EVENT | RECORD_MAGIC << 3; rec->data += (uint64_t)event->id << 16; rec->time = event->time; if (data_size) { void *ptr = rec + 1; rec->data += 4; /* set 'more' bit in uftrace_record */ *(uint16_t *)ptr = data_size; memcpy(ptr + 2, event->data, data_size); } curr_buf->size += size; return 0; } static int record_ret_stack(struct mcount_thread_data *mtdp, enum uftrace_record_type type, struct mcount_ret_stack *mrstack) { struct uftrace_record *frstack; uint64_t timestamp = mrstack->start_time; struct mcount_shmem_buffer *curr_buf; size_t size = sizeof(*frstack); void *argbuf = NULL; uint64_t *buf; uint64_t rec; if (type == UFTRACE_EXIT) timestamp = mrstack->end_time; if (unlikely(mtdp->nr_events)) { /* save async events first (if any) */ while (mtdp->nr_events && mtdp->event[0].time < timestamp) { record_event(mtdp, &mtdp->event[0]); mtdp->nr_events--; mcount_memcpy4(&mtdp->event[0], &mtdp->event[1], sizeof(*mtdp->event) * mtdp->nr_events); } } if (type == UFTRACE_EXIT && unlikely(mrstack->nr_events)) { int i; unsigned evidx; struct mcount_event *event; argbuf = get_argbuf(mtdp, mrstack) + mrstack->event_idx; for (i = 0; i < mrstack->nr_events; i++) { evidx = mrstack->nr_events - i - 1; event = get_event_pointer(argbuf, evidx); if (event->time != timestamp) continue; /* save read2 trigger before exit record */ record_event(mtdp, event); } mrstack->nr_events = 0; argbuf = NULL; } if ((type == UFTRACE_ENTRY && mrstack->flags & MCOUNT_FL_ARGUMENT) || (type == UFTRACE_EXIT && mrstack->flags & MCOUNT_FL_RETVAL)) { argbuf = get_argbuf(mtdp, mrstack); if (argbuf) size += *(unsigned *)argbuf; } curr_buf = get_shmem_buffer(mtdp, size); if (curr_buf == NULL) return mtdp->shmem.done ? 0 : -1; #if 0 frstack = (void *)(curr_buf->data + curr_buf->size); frstack->time = timestamp; frstack->type = type; frstack->magic = RECORD_MAGIC; frstack->more = !!argbuf; frstack->depth = mrstack->depth; frstack->addr = mrstack->child_ip; #else /* * instead of set bit fields, do the bit operations manually. * this would be good for both performance and portability. */ rec = type | RECORD_MAGIC << 3; rec += argbuf ? 4 : 0; rec += mrstack->depth << 6; rec += (uint64_t)mrstack->child_ip << 16; buf = (void *)(curr_buf->data + curr_buf->size); buf[0] = timestamp; buf[1] = rec; #endif curr_buf->size += sizeof(*frstack); mrstack->flags |= MCOUNT_FL_WRITTEN; if (argbuf) { unsigned int *ptr = (void *)curr_buf->data + curr_buf->size; size -= sizeof(*frstack); mcount_memcpy4(ptr, argbuf + 4, size); curr_buf->size += ALIGN(size, 8); } pr_dbg3("rstack[%d] %s %lx\n", mrstack->depth, type == UFTRACE_ENTRY? "ENTRY" : "EXIT ", mrstack->child_ip); if (unlikely(mrstack->nr_events) && type == UFTRACE_ENTRY) { int i; unsigned evidx; struct mcount_event *event; argbuf = get_argbuf(mtdp, mrstack) + mrstack->event_idx; for (i = 0; i < mrstack->nr_events; i++) { evidx = mrstack->nr_events - i - 1; event = get_event_pointer(argbuf, evidx); if (event->time != timestamp) break; /* save read trigger after entry record */ record_event(mtdp, event); } } return 0; } int record_trace_data(struct mcount_thread_data *mtdp, struct mcount_ret_stack *mrstack, long *retval) { struct mcount_ret_stack *non_written_mrstack = NULL; struct uftrace_record *frstack; size_t size = 0; int count = 0; #define SKIP_FLAGS (MCOUNT_FL_NORECORD | MCOUNT_FL_DISABLED) if (mrstack < mtdp->rstack) return 0; if (!(mrstack->flags & MCOUNT_FL_WRITTEN)) { non_written_mrstack = mrstack; if (!(non_written_mrstack->flags & SKIP_FLAGS)) count++; while (non_written_mrstack > mtdp->rstack) { struct mcount_ret_stack *prev = non_written_mrstack - 1; if (prev->flags & MCOUNT_FL_WRITTEN) break; if (!(prev->flags & SKIP_FLAGS)) { count++; if (prev->flags & MCOUNT_FL_ARGUMENT) { unsigned *argbuf_size; argbuf_size = get_argbuf(mtdp, prev); if (argbuf_size) size += *argbuf_size; } } non_written_mrstack = prev; } } if (mrstack->end_time) count++; /* for exit */ size += count * sizeof(*frstack); pr_dbg3("task %d recorded %zd bytes (record count = %d)\n", mcount_gettid(mtdp), size, count); while (non_written_mrstack && non_written_mrstack < mrstack) { if (!(non_written_mrstack->flags & SKIP_FLAGS)) { if (record_ret_stack(mtdp, UFTRACE_ENTRY, non_written_mrstack)) { mtdp->shmem.losts += count - 1; return 0; } count--; } non_written_mrstack++; } if (!(mrstack->flags & (MCOUNT_FL_WRITTEN | SKIP_FLAGS))) { if (record_ret_stack(mtdp, UFTRACE_ENTRY, mrstack)) return 0; count--; } if (mrstack->end_time) { if (retval) save_retval(mtdp, mrstack, retval); else mrstack->flags &= ~MCOUNT_FL_RETVAL; if (record_ret_stack(mtdp, UFTRACE_EXIT, mrstack)) return 0; count--; } assert(count == 0); return 0; } static void write_map(FILE *out, struct uftrace_mmap *map, unsigned char major, unsigned char minor, uint32_t ino, uint64_t off) { /* write prev_map when it finds a new map */ fprintf(out, "%"PRIx64"-%"PRIx64" %.4s %08"PRIx64" %02x:%02x %-26u %s\n", map->start, map->end, map->prot, off, major, minor, ino, map->libname); } void record_proc_maps(char *dirname, const char *sess_id, struct symtabs *symtabs) { FILE *ifp, *ofp; char buf[PATH_MAX]; struct uftrace_mmap *prev_map = NULL; bool prev_written = false; ifp = fopen("/proc/self/maps", "r"); if (ifp == NULL) pr_err("cannot open proc maps file"); snprintf(buf, sizeof(buf), "%s/sid-%s.map", dirname, sess_id); ofp = fopen(buf, "w"); if (ofp == NULL) pr_err("cannot open for writing maps file"); symtabs->kernel_base = -1ULL; while (fgets(buf, sizeof(buf), ifp)) { unsigned long start, end; char prot[5]; unsigned char major, minor; unsigned char prev_major = 0, prev_minor = 0; uint32_t ino, prev_ino = 0; uint64_t off, prev_off = 0; char path[PATH_MAX]; size_t namelen; struct uftrace_mmap *map; /* skip anon mappings */ if (sscanf(buf, "%lx-%lx %s %"SCNx64" %hhx:%hhx %u %s\n", &start, &end, prot, &off, &major, &minor, &ino, path) != 8) continue; /* * skip special mappings like [heap], [vdso] etc. * but [stack] is still needed to get kernel base address. */ if (path[0] == '[') { if (prev_map && !prev_written) { write_map(ofp, prev_map, prev_major, prev_minor, prev_ino, prev_off); prev_written = true; } if (strncmp(path, "[stack", 6) == 0) { symtabs->kernel_base = guess_kernel_base(buf); fprintf(ofp, "%s", buf); } continue; } if (prev_map != NULL) { /* extend prev_map to have all segments */ if (!strcmp(path, prev_map->libname)) { prev_map->end = end; if (prot[2] == 'x') mcount_memcpy1(prev_map->prot, prot, 4); continue; } /* write prev_map when it finds a new map */ if (!prev_written) { write_map(ofp, prev_map, prev_major, prev_minor, prev_ino, prev_off); prev_written = true; } } /* save map for the executable */ namelen = ALIGN(strlen(path) + 1, 4); map = xzalloc(sizeof(*map) + namelen); map->start = start; map->end = end; map->len = namelen; mcount_memcpy1(map->prot, prot, 4); mcount_memcpy1(map->libname, path, namelen); map->libname[strlen(path)] = '\0'; /* still need to write the map for executable */ if (!strcmp(path, symtabs->filename)) { symtabs->exec_map = map; } if (prev_map) prev_map->next = map; else symtabs->maps = map; map->next = NULL; prev_map = map; prev_off = off; prev_ino = ino; prev_major = major; prev_minor = minor; prev_written = false; } fclose(ifp); fclose(ofp); } uftrace-0.9.4/libmcount/wrap.c000066400000000000000000000314701362052523300162740ustar00rootroot00000000000000#include #include #include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "mcount" #define PR_DOMAIN DBG_MCOUNT #include "libmcount/mcount.h" #include "libmcount/internal.h" #include "utils/utils.h" #include "utils/compiler.h" extern struct symtabs symtabs; struct dlopen_base_data { struct mcount_thread_data *mtdp; uint64_t timestamp; }; static const char *simple_basename(const char *pathname) { const char *p = strrchr(pathname, '/'); return p ? p + 1 : pathname; } static void send_dlopen_msg(struct mcount_thread_data *mtdp, const char *sess_id, uint64_t timestamp, uint64_t base_addr, const char *libname) { struct uftrace_msg_dlopen dlop = { .task = { .time = timestamp, .pid = getpid(), .tid = mcount_gettid(mtdp), }, .base_addr = base_addr, .namelen = strlen(libname), }; struct uftrace_msg msg = { .magic = UFTRACE_MSG_MAGIC, .type = UFTRACE_MSG_DLOPEN, .len = sizeof(dlop) + dlop.namelen, }; struct iovec iov[3] = { { .iov_base = &msg, .iov_len = sizeof(msg), }, { .iov_base = &dlop, .iov_len = sizeof(dlop), }, { .iov_base = (void *)libname, .iov_len = dlop.namelen, }, }; int len = sizeof(msg) + msg.len; if (pfd < 0) return; mcount_memcpy4(dlop.sid, sess_id, sizeof(dlop.sid)); if (writev(pfd, iov, 3) != len) { if (!mcount_should_stop()) pr_err("write tid info failed"); } } static int dlopen_base_callback(struct dl_phdr_info *info, size_t size, void *arg) { struct dlopen_base_data *data = arg; char buf[PATH_MAX]; char *p; if (info->dlpi_name[0] == '\0') return 0; if (!strcmp("linux-vdso.so.1", info->dlpi_name)) return 0; p = realpath(info->dlpi_name, buf); if (p == NULL) p = buf; if (find_map_by_name(&symtabs, simple_basename(p))) return 0; /* report a library not found in the session maps */ send_dlopen_msg(data->mtdp, mcount_session_name(), data->timestamp, info->dlpi_addr, info->dlpi_name); return 0; } void mcount_rstack_reset_exception(struct mcount_thread_data *mtdp, unsigned long frame_addr) { int idx; struct mcount_ret_stack *rstack; /* it needs to find how much stack frame unwinds */ for (idx = mtdp->idx - 1; idx >= 0; idx--) { rstack = &mtdp->rstack[idx]; pr_dbg2("[%d] parent at %p\n", idx, rstack->parent_loc); if (rstack->parent_loc == &mtdp->cygprof_dummy) break; if ((unsigned long)rstack->parent_loc > frame_addr) { /* * there might be tail call optimizations in the * middle of the exception handling path. * In that case, we need to keep the original * mtdp->idx but update parent address of the * first rstack of the tail call chain. */ int orig_idx = idx; while (idx > 0) { struct mcount_ret_stack *tail_call; tail_call = &mtdp->rstack[idx - 1]; if (rstack->parent_loc != tail_call->parent_loc) break; idx--; rstack = tail_call; pr_dbg2("exception in tail call at [%d]\n", idx + 1); } idx = orig_idx; /* do not overwrite current return address */ rstack->parent_ip = *rstack->parent_loc; break; } /* record unwound functions */ if (!(rstack->flags & MCOUNT_FL_NORECORD)) rstack->end_time = mcount_gettime(); mcount_exit_filter_record(mtdp, rstack, NULL); } /* we're in ENTER state, so add 1 to the index */ mtdp->idx = idx + 1; pr_dbg2("exception returned to [%d]\n", mtdp->idx); mcount_rstack_reset(mtdp); } static char ** collect_uftrace_envp(void) { size_t n = 0; size_t i, k; char **envp; #define ENV(_name) "UFTRACE_" #_name const char *const uftrace_env[] = { ENV(FILTER), ENV(TRIGGER), ENV(ARGUMENT), ENV(RETVAL), ENV(AUTO_ARGS), ENV(DEPTH), ENV(DISABLED), ENV(PIPE), ENV(LOGFD), ENV(DEBUG), ENV(BUFFER), ENV(MAX_STACK), ENV(COLOR), ENV(THRESHOLD), ENV(DEMANGLE), ENV(PLTHOOK), ENV(PATCH), ENV(EVENT), ENV(SCRIPT), ENV(NEST_LIBCALL), ENV(DEBUG_DOMAIN), ENV(LIST_EVENT), ENV(DIR), ENV(KERNEL_PID_UPDATE), ENV(PATTERN), /* not uftrace-specific, but necessary to run */ "LD_PRELOAD", "LD_LIBRARY_PATH", }; #undef ENV for (i = 0; i < ARRAY_SIZE(uftrace_env); i++) { if (getenv(uftrace_env[i])) n++; } envp = xcalloc(sizeof(*envp), n + 2); for (i = k = 0; i < ARRAY_SIZE(uftrace_env); i++) { char *env_str; char *env_val; env_val = getenv(uftrace_env[i]); if (env_val == NULL) continue; xasprintf(&env_str, "%s=%s", uftrace_env[i], env_val); envp[k++] = env_str; } return envp; } static char ** merge_envp(char *const *env1, char **env2) { int i, n = 0; char **envp; for (i = 0; env1 && env1[i]; i++) n++; for (i = 0; env2 && env2[i]; i++) n++; envp = xcalloc(sizeof(*envp), n + 1); n = 0; for (i = 0; env1 && env1[i]; i++) envp[n++] = env1[i]; for (i = 0; env2 && env2[i]; i++) envp[n++] = env2[i]; return envp; } /* * hooking functions */ static int (*real_backtrace)(void **buffer, int sz); static void (*real_cxa_throw)(void *exc, void *type, void *dest); static void (*real_cxa_rethrow)(void); static void * (*real_cxa_begin_catch)(void *exc); static void (*real_cxa_end_catch)(void); static void * (*real_dlopen)(const char *filename, int flags); static __noreturn void (*real_pthread_exit)(void *retval); static void (*real_unwind_resume)(void *exc); static int (*real_posix_spawn)(pid_t *pid, const char *path, const posix_spawn_file_actions_t *actions, const posix_spawnattr_t *attr, char *const argv[], char *const envp[]); static int (*real_posix_spawnp)(pid_t *pid, const char *file, const posix_spawn_file_actions_t *actions, const posix_spawnattr_t *attr, char *const argv[], char *const envp[]); /* TODO: support execle() */ static int (*real_execve)(const char *path, char *const argv[], char *const envp[]); static int (*real_execvpe)(const char *file, char *const argv[], char *const envp[]); static int (*real_fexecve)(int fd, char *const argv[], char *const envp[]); void mcount_hook_functions(void) { real_backtrace = dlsym(RTLD_NEXT, "backtrace"); real_cxa_throw = dlsym(RTLD_NEXT, "__cxa_throw"); real_cxa_rethrow = dlsym(RTLD_NEXT, "__cxa_rethrow"); real_cxa_begin_catch = dlsym(RTLD_NEXT, "__cxa_begin_catch"); real_cxa_end_catch = dlsym(RTLD_NEXT, "__cxa_end_catch"); real_dlopen = dlsym(RTLD_NEXT, "dlopen"); real_pthread_exit = dlsym(RTLD_NEXT, "pthread_exit"); real_unwind_resume = dlsym(RTLD_NEXT, "_Unwind_Resume"); real_posix_spawn = dlsym(RTLD_NEXT, "posix_spawn"); real_posix_spawnp = dlsym(RTLD_NEXT, "posix_spawnp"); real_execve = dlsym(RTLD_NEXT, "execve"); real_execvpe = dlsym(RTLD_NEXT, "execvpe"); real_fexecve = dlsym(RTLD_NEXT, "fexecve"); } __visible_default int backtrace(void **buffer, int sz) { int ret; struct mcount_thread_data *mtdp; if (unlikely(real_backtrace == NULL)) mcount_hook_functions(); mtdp = get_thread_data(); if (!check_thread_data(mtdp)) mcount_rstack_restore(mtdp); ret = real_backtrace(buffer, sz); if (!check_thread_data(mtdp)) mcount_rstack_reset(mtdp); return ret; } __visible_default void __cxa_throw(void *exception, void *type, void *dest) { struct mcount_thread_data *mtdp; if (unlikely(real_cxa_throw == NULL)) mcount_hook_functions(); mtdp = get_thread_data(); if (!check_thread_data(mtdp)) { pr_dbg("exception thrown from [%d]\n", mtdp->idx); mtdp->in_exception = true; /* * restore return addresses so that it can unwind stack * frames safely during the exception handling. * It pairs to mcount_rstack_reset_exception(). */ mcount_rstack_restore(mtdp); } real_cxa_throw(exception, type, dest); } __visible_default void __cxa_rethrow(void) { struct mcount_thread_data *mtdp; if (unlikely(real_cxa_rethrow == NULL)) mcount_hook_functions(); mtdp = get_thread_data(); if (!check_thread_data(mtdp)) { pr_dbg("exception rethrown from [%d]\n", mtdp->idx); mtdp->in_exception = true; /* * restore return addresses so that it can unwind stack * frames safely during the exception handling. * It pairs to mcount_rstack_reset_exception() */ mcount_rstack_restore(mtdp); } real_cxa_rethrow(); } __visible_default void _Unwind_Resume(void *exception) { struct mcount_thread_data *mtdp; if (unlikely(real_unwind_resume == NULL)) mcount_hook_functions(); mtdp = get_thread_data(); if (!check_thread_data(mtdp)) { pr_dbg2("exception resumed on [%d]\n", mtdp->idx); mtdp->in_exception = true; /* * restore return addresses so that it can unwind stack * frames safely during the exception handling. * It pairs to mcount_rstack_reset_exception(). */ mcount_rstack_restore(mtdp); } real_unwind_resume(exception); } __visible_default void * __cxa_begin_catch(void *exception) { struct mcount_thread_data *mtdp; void *obj; if (unlikely(real_cxa_begin_catch == NULL)) mcount_hook_functions(); obj = real_cxa_begin_catch(exception); mtdp = get_thread_data(); if (!check_thread_data(mtdp) && unlikely(mtdp->in_exception)) { unsigned long *frame_ptr; unsigned long frame_addr; frame_ptr = __builtin_frame_address(0); frame_addr = *frame_ptr; /* XXX: probably dangerous */ /* basic sanity check */ if (frame_addr < (unsigned long)frame_ptr) frame_addr = (unsigned long)frame_ptr; mcount_rstack_reset_exception(mtdp, frame_addr); mtdp->in_exception = false; } return obj; } __visible_default void __cxa_end_catch(void) { if (unlikely(real_cxa_end_catch == NULL)) mcount_hook_functions(); real_cxa_end_catch(); } __visible_default void * dlopen(const char *filename, int flags) { struct mcount_thread_data *mtdp; struct dlopen_base_data data = { .timestamp = mcount_gettime(), }; void *ret; /* * get timestamp before calling dlopen() so that * it can have symbols in static initializers which * called during the dlopen. */ if (unlikely(real_dlopen == NULL)) mcount_hook_functions(); ret = real_dlopen(filename, flags); if (filename == NULL) return ret; mtdp = get_thread_data(); if (unlikely(check_thread_data(mtdp))) { mtdp = mcount_prepare(); if (mtdp == NULL) return ret; } else { if (!mcount_guard_recursion(mtdp)) return ret; } data.mtdp = mtdp; dl_iterate_phdr(dlopen_base_callback, &data); mcount_unguard_recursion(mtdp); return ret; } __visible_default __noreturn void pthread_exit(void *retval) { struct mcount_thread_data *mtdp; struct mcount_ret_stack *rstack; if (unlikely(real_pthread_exit == NULL)) mcount_hook_functions(); mtdp = get_thread_data(); if (!check_thread_data(mtdp)) { rstack = &mtdp->rstack[mtdp->idx - 1]; /* record the final call */ mcount_exit_filter_record(mtdp, rstack, NULL); /* * it won't return to the caller ("noreturn"), * do not try to restore the address.. */ mtdp->idx--; mcount_rstack_restore(mtdp); } real_pthread_exit(retval); } __visible_default int posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *actions, const posix_spawnattr_t *attr, char *const argv[], char *const envp[]) { char **uftrace_envp; char **new_envp; if (unlikely(real_posix_spawn == NULL)) mcount_hook_functions(); uftrace_envp = collect_uftrace_envp(); new_envp = merge_envp(envp, uftrace_envp); return real_posix_spawn(pid, path, actions, attr, argv, new_envp); } __visible_default int posix_spawnp(pid_t *pid, const char *file, const posix_spawn_file_actions_t *actions, const posix_spawnattr_t *attr, char *const argv[], char *const envp[]) { char **uftrace_envp; char **new_envp; if (unlikely(real_posix_spawnp == NULL)) mcount_hook_functions(); uftrace_envp = collect_uftrace_envp(); new_envp = merge_envp(envp, uftrace_envp); return real_posix_spawnp(pid, file, actions, attr, argv, new_envp); } __visible_default int execve(const char *path, char *const argv[], char *const envp[]) { char **uftrace_envp; char **new_envp; if (unlikely(real_execve == NULL)) mcount_hook_functions(); uftrace_envp = collect_uftrace_envp(); new_envp = merge_envp(envp, uftrace_envp); return real_execve(path, argv, new_envp); } __visible_default int execvpe(const char *file, char *const argv[], char *const envp[]) { char **uftrace_envp; char **new_envp; if (unlikely(real_execvpe == NULL)) mcount_hook_functions(); uftrace_envp = collect_uftrace_envp(); new_envp = merge_envp(envp, uftrace_envp); return real_execvpe(file, argv, new_envp); } __visible_default int fexecve(int fd, char *const argv[], char *const envp[]) { char **uftrace_envp; char **new_envp; if (unlikely(real_fexecve == NULL)) mcount_hook_functions(); uftrace_envp = collect_uftrace_envp(); new_envp = merge_envp(envp, uftrace_envp); return real_fexecve(fd, argv, new_envp); } #ifdef UNIT_TEST TEST_CASE(mcount_wrap_dlopen) { void *handle; TEST_EQ(real_dlopen, NULL); handle= dlopen(NULL, RTLD_LAZY); TEST_NE(handle, NULL); TEST_NE(real_dlopen, NULL); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.9.4/libtraceevent/000077500000000000000000000000001362052523300160055ustar00rootroot00000000000000uftrace-0.9.4/libtraceevent/.gitignore000066400000000000000000000000631362052523300177740ustar00rootroot00000000000000TRACEEVENT-CFLAGS libtraceevent.a *.so .*.cmd .*.d uftrace-0.9.4/libtraceevent/Makefile000066400000000000000000000147211362052523300174520ustar00rootroot00000000000000# trace-cmd version EP_VERSION = 1 EP_PATCHLEVEL = 1 EP_EXTRAVERSION = 0 # file format version FILE_VERSION = 6 MAKEFLAGS += --no-print-directory # Makefiles suck: This macro sets a default value of $(2) for the # variable named by $(1), unless the variable has been set by # environment or command line. This is necessary for CC and AR # because make sets default values, so the simpler ?= approach # won't work as expected. define allow-override $(if $(or $(findstring environment,$(origin $(1))),\ $(findstring command line,$(origin $(1)))),,\ $(eval $(1) = $(2))) endef # Allow setting CC and AR, or setting CROSS_COMPILE as a prefix. $(call allow-override,CC,$(CROSS_COMPILE)gcc) $(call allow-override,AR,$(CROSS_COMPILE)ar) EXT = -std=gnu99 INSTALL = install # Use DESTDIR for installing into a different root directory. # This is useful for building a package. The program will be # installed in this directory as if it was the root directory. # Then the build tool can move it later. DESTDIR ?= DESTDIR_SQ = '$(subst ','\'',$(DESTDIR))' prefix ?= /usr/local bindir_relative = bin bindir = $(prefix)/$(bindir_relative) man_dir = $(prefix)/share/man man_dir_SQ = '$(subst ','\'',$(man_dir))' export man_dir man_dir_SQ INSTALL export DESTDIR DESTDIR_SQ include $(if $(BUILD_SRC),$(BUILD_SRC)/)../Makefile.include # copy a bit from Linux kbuild ifeq ("$(origin V)", "command line") VERBOSE = $(V) endif ifndef VERBOSE VERBOSE = 0 endif ifeq ("$(origin O)", "command line") BUILD_OUTPUT := $(O) endif ifeq ($(BUILD_SRC),) ifneq ($(BUILD_OUTPUT),) define build_output $(if $(VERBOSE:1=),@)+$(MAKE) -C $(BUILD_OUTPUT) \ BUILD_SRC=$(CURDIR)/ -f $(CURDIR)/Makefile $1 endef all: sub-make $(MAKECMDGOALS): sub-make sub-make: force $(call build_output, $(MAKECMDGOALS)) # Leave processing to above invocation of make skip-makefile := 1 endif # BUILD_OUTPUT endif # BUILD_SRC # We process the rest of the Makefile if this is the final invocation of make ifeq ($(skip-makefile),) srctree := $(if $(BUILD_SRC),$(BUILD_SRC),$(CURDIR)) objtree := $(if $(BUILD_OUTPUT),$(BUILD_OUTPUT),$(CURDIR)) src := $(srctree) obj := $(objtree) export prefix bindir src obj # Shell quotes bindir_SQ = $(subst ','\'',$(bindir)) bindir_relative_SQ = $(subst ','\'',$(bindir_relative)) LIB_FILE = $(obj)/libtraceevent.a # $(obj)/libtraceevent.so CONFIG_INCLUDES = CONFIG_LIBS = CONFIG_FLAGS = VERSION = $(EP_VERSION) PATCHLEVEL = $(EP_PATCHLEVEL) EXTRAVERSION = $(EP_EXTRAVERSION) OBJ = $@ N = export Q VERBOSE EVENT_PARSE_VERSION = $(EP_VERSION).$(EP_PATCHLEVEL).$(EP_EXTRAVERSION) INCLUDES = -I $(srctree)/include $(CONFIG_INCLUDES) # Set compile option CFLAGS if not set elsewhere CFLAGS ?= -g -Wall # Append required CFLAGS override CFLAGS += $(CONFIG_FLAGS) $(INCLUDES) override CFLAGS += $(udis86-flags) -D_GNU_SOURCE ifeq ($(VERBOSE),1) Q = else Q = @ endif do_compile_shared_library = \ ($(print_shared_lib_compile) \ $(CC) --shared $^ -o $@) do_build_static_lib = \ ($(print_static_lib_build) \ $(RM) $@; $(AR) rcs $@ $^) do_compile = $(QUIET_CC)$(CC) -c $(CFLAGS) $(EXT) $< -o $@; $(obj)/%.o: $(src)/%.c $(call do_compile) PEVENT_LIB_OBJS = $(obj)/event-parse.o PEVENT_LIB_OBJS += $(obj)/event-plugin.o PEVENT_LIB_OBJS += $(obj)/trace-seq.o PEVENT_LIB_OBJS += $(obj)/parse-filter.o PEVENT_LIB_OBJS += $(obj)/parse-utils.o PEVENT_LIB_OBJS += $(obj)/kbuffer-parse.o ALL_OBJS = $(PEVENT_LIB_OBJS) $(PLUGIN_OBJS) CMD_TARGETS = $(LIB_FILE) $(PLUGINS) TARGETS = $(CMD_TARGETS) all: all_cmd all_cmd: $(CMD_TARGETS) $(obj)/libtraceevent.so: $(PEVENT_LIB_OBJS) $(QUIET_LINK)$(CC) --shared $^ -o $@ $(obj)/libtraceevent.a: $(PEVENT_LIB_OBJS) $(QUIET_LINK)$(RM) $@; $(AR) rcs $@ $^ $(PEVENT_LIB_OBJS): $(obj)/%.o: $(src)/%.c $(obj)/TRACEEVENT-CFLAGS $(QUIET_CC_FPIC)$(CC) -c $(CFLAGS) $(EXT) $< -o $@ define make_version.h (echo '/* This file is automatically generated. Do not modify. */'; \ echo \#define VERSION_CODE $(shell \ expr $(VERSION) \* 256 + $(PATCHLEVEL)); \ echo '#define EXTRAVERSION ' $(EXTRAVERSION); \ echo '#define VERSION_STRING "'$(VERSION).$(PATCHLEVEL).$(EXTRAVERSION)'"'; \ echo '#define FILE_VERSION '$(FILE_VERSION); \ ) > $1 endef define update_version.h ($(call make_version.h, $@.tmp); \ if [ -r $@ ] && cmp -s $@ $@.tmp; then \ rm -f $@.tmp; \ else \ echo ' UPDATE $@'; \ mv -f $@.tmp $@; \ fi); endef ep_version.h: force $(Q)$(N)$(call update_version.h) VERSION_FILES = ep_version.h define update_dir (echo $1 > $@.tmp; \ if [ -r $@ ] && cmp -s $@ $@.tmp; then \ rm -f $@.tmp; \ else \ echo ' UPDATE $@'; \ mv -f $@.tmp $@; \ fi); endef ## make deps all_objs := $(sort $(ALL_OBJS)) all_deps := $(all_objs:$(obj)/%.o=$(obj)/.%.d) # let .d file also depends on the source and header files define check_deps @set -e; $(RM) $@; \ $(CC) -MM $(CFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ $(RM) $@.$$$$ endef $(all_deps): $(obj)/.%.d: $(src)/%.c $(Q)$(call check_deps) $(all_objs) : $(obj)/%.o : $(obj)/.%.d dep_includes := $(wildcard $(all_deps)) ifneq ($(dep_includes),) include $(dep_includes) endif ### Detect environment changes TRACK_CFLAGS = $(subst ','\'',$(CFLAGS)):$(ARCH):$(CROSS_COMPILE) $(obj)/TRACEEVENT-CFLAGS: force @FLAGS='$(TRACK_CFLAGS)'; \ if test x"$$FLAGS" != x"`cat $(obj)/TRACEEVENT-CFLAGS 2>/dev/null`" ; then \ echo 1>&2 " FLAGS: * new build flags or cross compiler"; \ echo "$$FLAGS" > $(obj)/TRACEEVENT-CFLAGS; \ fi tags: force $(RM) tags find . -name '*.[ch]' | xargs ctags --extra=+f --c-kinds=+px \ --regex-c++='/_PE\(([^,)]*).*/PEVENT_ERRNO__\1/' TAGS: force $(RM) TAGS find . -name '*.[ch]' | xargs etags \ --regex='/_PE(\([^,)]*\).*/PEVENT_ERRNO__\1/' define do_install if [ ! -d '$(DESTDIR_SQ)$2' ]; then \ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$2'; \ fi; \ $(INSTALL) $1 '$(DESTDIR_SQ)$2' endef install_lib: all_cmd $(call QUIET_INSTALL, $(LIB_FILE)) \ $(call do_install,$(LIB_FILE),$(bindir_SQ)) install: install_lib clean: $(call QUIET_CLEAN, libtraceevent) \ $(RM) $(obj)/*.o $(obj)/*~ $(TARGETS) $(obj)/*.a $(obj)/*.so \ $(VERSION_FILES) $(obj)/.*.d $(RM) $(obj)/TRACEEVENT-CFLAGS tags TAGS endif # skip-makefile PHONY += force force: # Declare the contents of the .PHONY variable as phony. We keep that # information in a variable so we can use it in if_changed and friends. .PHONY: $(PHONY) uftrace-0.9.4/libtraceevent/event-parse.c000066400000000000000000004004331362052523300204060ustar00rootroot00000000000000/* * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License (not later!) * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * The parts for function graph printing was taken and modified from the * Linux Kernel that were written by * - Copyright (C) 2009 Frederic Weisbecker, * Frederic Weisbecker gave his permission to relicense the code to * the Lesser General Public License. */ #include #include #include #include #include #include #include #include #include "event-parse.h" #include "event-utils.h" static const char *input_buf; static unsigned long long input_buf_ptr; static unsigned long long input_buf_siz; static int is_flag_field; static int is_symbolic_field; static int show_warning = 1; #define do_warning(fmt, ...) \ do { \ if (show_warning) \ warning(fmt, ##__VA_ARGS__); \ } while (0) #define do_warning_event(event, fmt, ...) \ do { \ if (!show_warning) \ continue; \ \ if (event) \ warning("[%s:%s] " fmt, event->system, \ event->name, ##__VA_ARGS__); \ else \ warning(fmt, ##__VA_ARGS__); \ } while (0) static void init_input_buf(const char *buf, unsigned long long size) { input_buf = buf; input_buf_siz = size; input_buf_ptr = 0; } const char *pevent_get_input_buf(void) { return input_buf; } unsigned long long pevent_get_input_buf_ptr(void) { return input_buf_ptr; } struct event_handler { struct event_handler *next; int id; const char *sys_name; const char *event_name; pevent_event_handler_func func; void *context; }; struct pevent_func_params { struct pevent_func_params *next; enum pevent_func_arg_type type; }; struct pevent_function_handler { struct pevent_function_handler *next; enum pevent_func_arg_type ret_type; char *name; pevent_func_handler func; struct pevent_func_params *params; int nr_args; }; static unsigned long long process_defined_func(struct trace_seq *s, void *data, int size, struct event_format *event, struct print_arg *arg); static void free_func_handle(struct pevent_function_handler *func); /** * pevent_buffer_init - init buffer for parsing * @buf: buffer to parse * @size: the size of the buffer * * For use with pevent_read_token(), this initializes the internal * buffer that pevent_read_token() will parse. */ void pevent_buffer_init(const char *buf, unsigned long long size) { init_input_buf(buf, size); } void breakpoint(void) { static int x; x++; } struct print_arg *alloc_arg(void) { return calloc(1, sizeof(struct print_arg)); } struct cmdline { char *comm; int pid; }; static int cmdline_cmp(const void *a, const void *b) { const struct cmdline *ca = a; const struct cmdline *cb = b; if (ca->pid < cb->pid) return -1; if (ca->pid > cb->pid) return 1; return 0; } struct cmdline_list { struct cmdline_list *next; char *comm; int pid; }; static int cmdline_init(struct pevent *pevent) { struct cmdline_list *cmdlist = pevent->cmdlist; struct cmdline_list *item; struct cmdline *cmdlines; int i; cmdlines = malloc(sizeof(*cmdlines) * pevent->cmdline_count); if (!cmdlines) return -1; i = 0; while (cmdlist) { cmdlines[i].pid = cmdlist->pid; cmdlines[i].comm = cmdlist->comm; i++; item = cmdlist; cmdlist = cmdlist->next; free(item); } qsort(cmdlines, pevent->cmdline_count, sizeof(*cmdlines), cmdline_cmp); pevent->cmdlines = cmdlines; pevent->cmdlist = NULL; return 0; } static const char *find_cmdline(struct pevent *pevent, int pid) { const struct cmdline *comm; struct cmdline key; if (!pid) return ""; if (!pevent->cmdlines && cmdline_init(pevent)) return ""; key.pid = pid; comm = bsearch(&key, pevent->cmdlines, pevent->cmdline_count, sizeof(*pevent->cmdlines), cmdline_cmp); if (comm) return comm->comm; return "<...>"; } /** * pevent_pid_is_registered - return if a pid has a cmdline registered * @pevent: handle for the pevent * @pid: The pid to check if it has a cmdline registered with. * * Returns 1 if the pid has a cmdline mapped to it * 0 otherwise. */ int pevent_pid_is_registered(struct pevent *pevent, int pid) { const struct cmdline *comm; struct cmdline key; if (!pid) return 1; if (!pevent->cmdlines && cmdline_init(pevent)) return 0; key.pid = pid; comm = bsearch(&key, pevent->cmdlines, pevent->cmdline_count, sizeof(*pevent->cmdlines), cmdline_cmp); if (comm) return 1; return 0; } /* * If the command lines have been converted to an array, then * we must add this pid. This is much slower than when cmdlines * are added before the array is initialized. */ static int add_new_comm(struct pevent *pevent, const char *comm, int pid) { struct cmdline *cmdlines = pevent->cmdlines; const struct cmdline *cmdline; struct cmdline key; if (!pid) return 0; /* avoid duplicates */ key.pid = pid; cmdline = bsearch(&key, pevent->cmdlines, pevent->cmdline_count, sizeof(*pevent->cmdlines), cmdline_cmp); if (cmdline) { errno = EEXIST; return -1; } cmdlines = realloc(cmdlines, sizeof(*cmdlines) * (pevent->cmdline_count + 1)); if (!cmdlines) { errno = ENOMEM; return -1; } cmdlines[pevent->cmdline_count].comm = strdup(comm); if (!cmdlines[pevent->cmdline_count].comm) { free(cmdlines); errno = ENOMEM; return -1; } cmdlines[pevent->cmdline_count].pid = pid; if (cmdlines[pevent->cmdline_count].comm) pevent->cmdline_count++; qsort(cmdlines, pevent->cmdline_count, sizeof(*cmdlines), cmdline_cmp); pevent->cmdlines = cmdlines; return 0; } /** * pevent_register_comm - register a pid / comm mapping * @pevent: handle for the pevent * @comm: the command line to register * @pid: the pid to map the command line to * * This adds a mapping to search for command line names with * a given pid. The comm is duplicated. */ int pevent_register_comm(struct pevent *pevent, const char *comm, int pid) { struct cmdline_list *item; if (pevent->cmdlines) return add_new_comm(pevent, comm, pid); item = malloc(sizeof(*item)); if (!item) return -1; item->comm = strdup(comm); if (!item->comm) { free(item); return -1; } item->pid = pid; item->next = pevent->cmdlist; pevent->cmdlist = item; pevent->cmdline_count++; return 0; } void pevent_register_trace_clock(struct pevent *pevent, char *trace_clock) { pevent->trace_clock = trace_clock; } struct func_map { unsigned long long addr; char *func; char *mod; }; struct func_list { struct func_list *next; unsigned long long addr; char *func; char *mod; }; static int func_cmp(const void *a, const void *b) { const struct func_map *fa = a; const struct func_map *fb = b; if (fa->addr < fb->addr) return -1; if (fa->addr > fb->addr) return 1; return 0; } /* * We are searching for a record in between, not an exact * match. */ static int func_bcmp(const void *a, const void *b) { const struct func_map *fa = a; const struct func_map *fb = b; if ((fa->addr == fb->addr) || (fa->addr > fb->addr && fa->addr < (fb+1)->addr)) return 0; if (fa->addr < fb->addr) return -1; return 1; } static int func_map_init(struct pevent *pevent) { struct func_list *funclist; struct func_list *item; struct func_map *func_map; int i; func_map = malloc(sizeof(*func_map) * (pevent->func_count + 1)); if (!func_map) return -1; funclist = pevent->funclist; i = 0; while (funclist) { func_map[i].func = funclist->func; func_map[i].addr = funclist->addr; func_map[i].mod = funclist->mod; i++; item = funclist; funclist = funclist->next; free(item); } qsort(func_map, pevent->func_count, sizeof(*func_map), func_cmp); /* * Add a special record at the end. */ func_map[pevent->func_count].func = NULL; func_map[pevent->func_count].addr = 0; func_map[pevent->func_count].mod = NULL; pevent->func_map = func_map; pevent->funclist = NULL; return 0; } static struct func_map * find_func(struct pevent *pevent, unsigned long long addr) { struct func_map *func; struct func_map key; if (!pevent->func_map) func_map_init(pevent); key.addr = addr; func = bsearch(&key, pevent->func_map, pevent->func_count, sizeof(*pevent->func_map), func_bcmp); return func; } /** * pevent_find_function - find a function by a given address * @pevent: handle for the pevent * @addr: the address to find the function with * * Returns a pointer to the function stored that has the given * address. Note, the address does not have to be exact, it * will select the function that would contain the address. */ const char *pevent_find_function(struct pevent *pevent, unsigned long long addr) { struct func_map *map; map = find_func(pevent, addr); if (!map) return NULL; return map->func; } /** * pevent_find_function_address - find a function address by a given address * @pevent: handle for the pevent * @addr: the address to find the function with * * Returns the address the function starts at. This can be used in * conjunction with pevent_find_function to print both the function * name and the function offset. */ unsigned long long pevent_find_function_address(struct pevent *pevent, unsigned long long addr) { struct func_map *map; map = find_func(pevent, addr); if (!map) return 0; return map->addr; } /** * pevent_register_function - register a function with a given address * @pevent: handle for the pevent * @function: the function name to register * @addr: the address the function starts at * @mod: the kernel module the function may be in (NULL for none) * * This registers a function name with an address and module. * The @func passed in is duplicated. */ int pevent_register_function(struct pevent *pevent, char *func, unsigned long long addr, char *mod) { struct func_list *item = malloc(sizeof(*item)); if (!item) return -1; item->next = pevent->funclist; item->func = strdup(func); if (!item->func) goto out_free; if (mod) { item->mod = strdup(mod); if (!item->mod) goto out_free_func; } else item->mod = NULL; item->addr = addr; pevent->funclist = item; pevent->func_count++; return 0; out_free_func: free(item->func); item->func = NULL; out_free: free(item); errno = ENOMEM; return -1; } /** * pevent_print_funcs - print out the stored functions * @pevent: handle for the pevent * * This prints out the stored functions. */ void pevent_print_funcs(struct pevent *pevent) { int i; if (!pevent->func_map) func_map_init(pevent); for (i = 0; i < (int)pevent->func_count; i++) { printf("%016llx %s", pevent->func_map[i].addr, pevent->func_map[i].func); if (pevent->func_map[i].mod) printf(" [%s]\n", pevent->func_map[i].mod); else printf("\n"); } } struct printk_map { unsigned long long addr; char *printk; }; struct printk_list { struct printk_list *next; unsigned long long addr; char *printk; }; static int printk_cmp(const void *a, const void *b) { const struct printk_map *pa = a; const struct printk_map *pb = b; if (pa->addr < pb->addr) return -1; if (pa->addr > pb->addr) return 1; return 0; } static int printk_map_init(struct pevent *pevent) { struct printk_list *printklist; struct printk_list *item; struct printk_map *printk_map; int i; printk_map = malloc(sizeof(*printk_map) * (pevent->printk_count + 1)); if (!printk_map) return -1; printklist = pevent->printklist; i = 0; while (printklist) { printk_map[i].printk = printklist->printk; printk_map[i].addr = printklist->addr; i++; item = printklist; printklist = printklist->next; free(item); } qsort(printk_map, pevent->printk_count, sizeof(*printk_map), printk_cmp); pevent->printk_map = printk_map; pevent->printklist = NULL; return 0; } static struct printk_map * find_printk(struct pevent *pevent, unsigned long long addr) { struct printk_map *printk; struct printk_map key; if (!pevent->printk_map && printk_map_init(pevent)) return NULL; key.addr = addr; printk = bsearch(&key, pevent->printk_map, pevent->printk_count, sizeof(*pevent->printk_map), printk_cmp); return printk; } /** * pevent_register_print_string - register a string by its address * @pevent: handle for the pevent * @fmt: the string format to register * @addr: the address the string was located at * * This registers a string by the address it was stored in the kernel. * The @fmt passed in is duplicated. */ int pevent_register_print_string(struct pevent *pevent, const char *fmt, unsigned long long addr) { struct printk_list *item = malloc(sizeof(*item)); char *p; if (!item) return -1; item->next = pevent->printklist; item->addr = addr; /* Strip off quotes and '\n' from the end */ if (fmt[0] == '"') fmt++; item->printk = strdup(fmt); if (!item->printk) goto out_free; p = item->printk + strlen(item->printk) - 1; if (*p == '"') *p = 0; p -= 2; if (strcmp(p, "\\n") == 0) *p = 0; pevent->printklist = item; pevent->printk_count++; return 0; out_free: free(item); errno = ENOMEM; return -1; } /** * pevent_print_printk - print out the stored strings * @pevent: handle for the pevent * * This prints the string formats that were stored. */ void pevent_print_printk(struct pevent *pevent) { int i; if (!pevent->printk_map) printk_map_init(pevent); for (i = 0; i < (int)pevent->printk_count; i++) { printf("%016llx %s\n", pevent->printk_map[i].addr, pevent->printk_map[i].printk); } } static struct event_format *alloc_event(void) { return calloc(1, sizeof(struct event_format)); } static int add_event(struct pevent *pevent, struct event_format *event) { int i; struct event_format **events = realloc(pevent->events, sizeof(event) * (pevent->nr_events + 1)); if (!events) return -1; pevent->events = events; for (i = 0; i < pevent->nr_events; i++) { if (pevent->events[i]->id > event->id) break; } if (i < pevent->nr_events) memmove(&pevent->events[i + 1], &pevent->events[i], sizeof(event) * (pevent->nr_events - i)); pevent->events[i] = event; pevent->nr_events++; event->pevent = pevent; return 0; } static int event_item_type(enum event_type type) { switch (type) { case EVENT_ITEM ... EVENT_SQUOTE: return 1; case EVENT_ERROR ... EVENT_DELIM: default: return 0; } } static void free_flag_sym(struct print_flag_sym *fsym) { struct print_flag_sym *next; while (fsym) { next = fsym->next; free(fsym->value); free(fsym->str); free(fsym); fsym = next; } } static void free_arg(struct print_arg *arg) { struct print_arg *farg; if (!arg) return; switch (arg->type) { case PRINT_ATOM: free(arg->atom.atom); break; case PRINT_FIELD: free(arg->field.name); break; case PRINT_FLAGS: free_arg(arg->flags.field); free(arg->flags.delim); free_flag_sym(arg->flags.flags); break; case PRINT_SYMBOL: free_arg(arg->symbol.field); free_flag_sym(arg->symbol.symbols); break; case PRINT_HEX: free_arg(arg->hex.field); free_arg(arg->hex.size); break; case PRINT_TYPE: free(arg->typecast.type); free_arg(arg->typecast.item); break; case PRINT_STRING: case PRINT_BSTRING: free(arg->string.string); break; case PRINT_BITMASK: free(arg->bitmask.bitmask); break; case PRINT_DYNAMIC_ARRAY: free(arg->dynarray.index); break; case PRINT_OP: free(arg->op.op); free_arg(arg->op.left); free_arg(arg->op.right); break; case PRINT_FUNC: while (arg->func.args) { farg = arg->func.args; arg->func.args = farg->next; free_arg(farg); } break; case PRINT_NULL: default: break; } free(arg); } static enum event_type get_type(int ch) { if (ch == '\n') return EVENT_NEWLINE; if (isspace(ch)) return EVENT_SPACE; if (isalnum(ch) || ch == '_') return EVENT_ITEM; if (ch == '\'') return EVENT_SQUOTE; if (ch == '"') return EVENT_DQUOTE; if (!isprint(ch)) return EVENT_NONE; if (ch == '(' || ch == ')' || ch == ',') return EVENT_DELIM; return EVENT_OP; } static int __read_char(void) { if (input_buf_ptr >= input_buf_siz) return -1; return input_buf[input_buf_ptr++]; } static int __peek_char(void) { if (input_buf_ptr >= input_buf_siz) return -1; return input_buf[input_buf_ptr]; } /** * pevent_peek_char - peek at the next character that will be read * * Returns the next character read, or -1 if end of buffer. */ int pevent_peek_char(void) { return __peek_char(); } static int extend_token(char **tok, char *buf, int size) { char *newtok = realloc(*tok, size); if (!newtok) { free(*tok); *tok = NULL; return -1; } if (!*tok) strcpy(newtok, buf); else strcat(newtok, buf); *tok = newtok; return 0; } static enum event_type force_token(const char *str, char **tok); static enum event_type __read_token(char **tok) { char buf[BUFSIZ]; int ch, last_ch, quote_ch, next_ch; int i = 0; int tok_size = 0; enum event_type type; *tok = NULL; ch = __read_char(); if (ch < 0) return EVENT_NONE; type = get_type(ch); if (type == EVENT_NONE) return type; buf[i++] = ch; switch (type) { case EVENT_NEWLINE: case EVENT_DELIM: if (asprintf(tok, "%c", ch) < 0) return EVENT_ERROR; return type; case EVENT_OP: switch (ch) { case '-': next_ch = __peek_char(); if (next_ch == '>') { buf[i++] = __read_char(); break; } /* fall through */ case '+': case '|': case '&': case '>': case '<': last_ch = ch; ch = __peek_char(); if (ch != last_ch) goto test_equal; buf[i++] = __read_char(); switch (last_ch) { case '>': case '<': goto test_equal; default: break; } break; case '!': case '=': goto test_equal; default: /* what should we do instead? */ break; } buf[i] = 0; *tok = strdup(buf); return type; test_equal: ch = __peek_char(); if (ch == '=') buf[i++] = __read_char(); goto out; case EVENT_DQUOTE: case EVENT_SQUOTE: /* don't keep quotes */ i--; quote_ch = ch; last_ch = 0; concat: do { if (i == (BUFSIZ - 1)) { buf[i] = 0; tok_size += BUFSIZ; if (extend_token(tok, buf, tok_size) < 0) return EVENT_NONE; i = 0; } last_ch = ch; ch = __read_char(); buf[i++] = ch; /* the '\' '\' will cancel itself */ if (ch == '\\' && last_ch == '\\') last_ch = 0; } while (ch != quote_ch || last_ch == '\\'); /* remove the last quote */ i--; /* * For strings (double quotes) check the next token. * If it is another string, concatinate the two. */ if (type == EVENT_DQUOTE) { unsigned long long save_input_buf_ptr = input_buf_ptr; do { ch = __read_char(); } while (isspace(ch)); if (ch == '"') goto concat; input_buf_ptr = save_input_buf_ptr; } goto out; case EVENT_ERROR ... EVENT_SPACE: case EVENT_ITEM: default: break; } while (get_type(__peek_char()) == type) { if (i == (BUFSIZ - 1)) { buf[i] = 0; tok_size += BUFSIZ; if (extend_token(tok, buf, tok_size) < 0) return EVENT_NONE; i = 0; } ch = __read_char(); buf[i++] = ch; } out: buf[i] = 0; if (extend_token(tok, buf, tok_size + i + 1) < 0) return EVENT_NONE; if (type == EVENT_ITEM) { /* * Older versions of the kernel has a bug that * creates invalid symbols and will break the mac80211 * parsing. This is a work around to that bug. * * See Linux kernel commit: * 811cb50baf63461ce0bdb234927046131fc7fa8b */ if (strcmp(*tok, "LOCAL_PR_FMT") == 0) { free(*tok); *tok = NULL; return force_token("\"\%s\" ", tok); } else if (strcmp(*tok, "STA_PR_FMT") == 0) { free(*tok); *tok = NULL; return force_token("\" sta:%pM\" ", tok); } else if (strcmp(*tok, "VIF_PR_FMT") == 0) { free(*tok); *tok = NULL; return force_token("\" vif:%p(%d)\" ", tok); } } return type; } static enum event_type force_token(const char *str, char **tok) { const char *save_input_buf; unsigned long long save_input_buf_ptr; unsigned long long save_input_buf_siz; enum event_type type; /* save off the current input pointers */ save_input_buf = input_buf; save_input_buf_ptr = input_buf_ptr; save_input_buf_siz = input_buf_siz; init_input_buf(str, strlen(str)); type = __read_token(tok); /* reset back to original token */ input_buf = save_input_buf; input_buf_ptr = save_input_buf_ptr; input_buf_siz = save_input_buf_siz; return type; } static void free_token(char *tok) { if (tok) free(tok); } static enum event_type read_token(char **tok) { enum event_type type; for (;;) { type = __read_token(tok); if (type != EVENT_SPACE) return type; free_token(*tok); } /* not reached */ *tok = NULL; return EVENT_NONE; } /** * pevent_read_token - access to utilites to use the pevent parser * @tok: The token to return * * This will parse tokens from the string given by * pevent_init_data(). * * Returns the token type. */ enum event_type pevent_read_token(char **tok) { return read_token(tok); } /** * pevent_free_token - free a token returned by pevent_read_token * @token: the token to free */ void pevent_free_token(char *token) { free_token(token); } /* no newline */ static enum event_type read_token_item(char **tok) { enum event_type type; for (;;) { type = __read_token(tok); if (type != EVENT_SPACE && type != EVENT_NEWLINE) return type; free_token(*tok); *tok = NULL; } /* not reached */ *tok = NULL; return EVENT_NONE; } static int test_type(enum event_type type, enum event_type expect) { if (type != expect) { do_warning("Error: expected type %d but read %d", expect, type); return -1; } return 0; } static int test_type_token(enum event_type type, const char *token, enum event_type expect, const char *expect_tok) { if (type != expect) { do_warning("Error: expected type %d but read %d", expect, type); return -1; } if (strcmp(token, expect_tok) != 0) { do_warning("Error: expected '%s' but read '%s'", expect_tok, token); return -1; } return 0; } static int __read_expect_type(enum event_type expect, char **tok, int newline_ok) { enum event_type type; if (newline_ok) type = read_token(tok); else type = read_token_item(tok); return test_type(type, expect); } static int read_expect_type(enum event_type expect, char **tok) { return __read_expect_type(expect, tok, 1); } static int __read_expected(enum event_type expect, const char *str, int newline_ok) { enum event_type type; char *token; int ret; if (newline_ok) type = read_token(&token); else type = read_token_item(&token); ret = test_type_token(type, token, expect, str); free_token(token); return ret; } static int read_expected(enum event_type expect, const char *str) { return __read_expected(expect, str, 1); } static int read_expected_item(enum event_type expect, const char *str) { return __read_expected(expect, str, 0); } static char *event_read_name(void) { char *token; if (read_expected(EVENT_ITEM, "name") < 0) return NULL; if (read_expected(EVENT_OP, ":") < 0) return NULL; if (read_expect_type(EVENT_ITEM, &token) < 0) goto fail; return token; fail: free_token(token); return NULL; } static int event_read_id(void) { char *token; int id; if (read_expected_item(EVENT_ITEM, "ID") < 0) return -1; if (read_expected(EVENT_OP, ":") < 0) return -1; if (read_expect_type(EVENT_ITEM, &token) < 0) goto fail; id = strtoul(token, NULL, 0); free_token(token); return id; fail: free_token(token); return -1; } static int field_is_string(struct format_field *field) { if ((field->flags & FIELD_IS_ARRAY) && (strstr(field->type, "char") || strstr(field->type, "u8") || strstr(field->type, "s8"))) return 1; return 0; } static int field_is_dynamic(struct format_field *field) { if (strncmp(field->type, "__data_loc", 10) == 0) return 1; return 0; } static int field_is_long(struct format_field *field) { /* includes long long */ if (strstr(field->type, "long")) return 1; return 0; } static unsigned int type_size(const char *name) { /* This covers all FIELD_IS_STRING types. */ static struct { const char *type; unsigned int size; } table[] = { { "u8", 1 }, { "u16", 2 }, { "u32", 4 }, { "u64", 8 }, { "s8", 1 }, { "s16", 2 }, { "s32", 4 }, { "s64", 8 }, { "char", 1 }, { }, }; int i; for (i = 0; table[i].type; i++) { if (!strcmp(table[i].type, name)) return table[i].size; } return 0; } static int event_read_fields(struct event_format *event, struct format_field **fields) { struct format_field *field = NULL; enum event_type type; char *token; char *last_token; int count = 0; do { unsigned int size_dynamic = 0; type = read_token(&token); if (type == EVENT_NEWLINE) { free_token(token); return count; } count++; if (test_type_token(type, token, EVENT_ITEM, "field")) goto fail; free_token(token); type = read_token(&token); /* * The ftrace fields may still use the "special" name. * Just ignore it. */ if (event->flags & EVENT_FL_ISFTRACE && type == EVENT_ITEM && strcmp(token, "special") == 0) { free_token(token); type = read_token(&token); } if (test_type_token(type, token, EVENT_OP, ":") < 0) goto fail; free_token(token); if (read_expect_type(EVENT_ITEM, &token) < 0) goto fail; last_token = token; field = calloc(1, sizeof(*field)); if (!field) goto fail; field->event = event; /* read the rest of the type */ for (;;) { type = read_token(&token); if (type == EVENT_ITEM || (type == EVENT_OP && strcmp(token, "*") == 0) || /* * Some of the ftrace fields are broken and have * an illegal "." in them. */ (event->flags & EVENT_FL_ISFTRACE && type == EVENT_OP && strcmp(token, ".") == 0)) { if (strcmp(token, "*") == 0) field->flags |= FIELD_IS_POINTER; if (field->type) { char *new_type; new_type = realloc(field->type, strlen(field->type) + strlen(last_token) + 2); if (!new_type) { free(last_token); goto fail; } field->type = new_type; strcat(field->type, " "); strcat(field->type, last_token); free(last_token); } else field->type = last_token; last_token = token; continue; } break; } if (!field->type) { do_warning_event(event, "%s: no type found", __func__); goto fail; } field->name = last_token; if (test_type(type, EVENT_OP)) goto fail; if (strcmp(token, "[") == 0) { enum event_type last_type = type; char *brackets = token; char *new_brackets; int len; field->flags |= FIELD_IS_ARRAY; type = read_token(&token); if (type == EVENT_ITEM) field->arraylen = strtoul(token, NULL, 0); else field->arraylen = 0; while (strcmp(token, "]") != 0) { if (last_type == EVENT_ITEM && type == EVENT_ITEM) len = 2; else len = 1; last_type = type; new_brackets = realloc(brackets, strlen(brackets) + strlen(token) + len); if (!new_brackets) { free(brackets); goto fail; } brackets = new_brackets; if (len == 2) strcat(brackets, " "); strcat(brackets, token); /* We only care about the last token */ field->arraylen = strtoul(token, NULL, 0); free_token(token); type = read_token(&token); if (type == EVENT_NONE) { do_warning_event(event, "failed to find token"); goto fail; } } free_token(token); new_brackets = realloc(brackets, strlen(brackets) + 2); if (!new_brackets) { free(brackets); goto fail; } brackets = new_brackets; strcat(brackets, "]"); /* add brackets to type */ type = read_token(&token); /* * If the next token is not an OP, then it is of * the format: type [] item; */ if (type == EVENT_ITEM) { char *new_type; new_type = realloc(field->type, strlen(field->type) + strlen(field->name) + strlen(brackets) + 2); if (!new_type) { free(brackets); goto fail; } field->type = new_type; strcat(field->type, " "); strcat(field->type, field->name); size_dynamic = type_size(field->name); free_token(field->name); strcat(field->type, brackets); field->name = token; type = read_token(&token); } else { char *new_type; new_type = realloc(field->type, strlen(field->type) + strlen(brackets) + 1); if (!new_type) { free(brackets); goto fail; } field->type = new_type; strcat(field->type, brackets); } free(brackets); } if (field_is_string(field)) field->flags |= FIELD_IS_STRING; if (field_is_dynamic(field)) field->flags |= FIELD_IS_DYNAMIC; if (field_is_long(field)) field->flags |= FIELD_IS_LONG; if (test_type_token(type, token, EVENT_OP, ";")) goto fail; free_token(token); if (read_expected(EVENT_ITEM, "offset") < 0) goto fail_expect; if (read_expected(EVENT_OP, ":") < 0) goto fail_expect; if (read_expect_type(EVENT_ITEM, &token)) goto fail; field->offset = strtoul(token, NULL, 0); free_token(token); if (read_expected(EVENT_OP, ";") < 0) goto fail_expect; if (read_expected(EVENT_ITEM, "size") < 0) goto fail_expect; if (read_expected(EVENT_OP, ":") < 0) goto fail_expect; if (read_expect_type(EVENT_ITEM, &token)) goto fail; field->size = strtoul(token, NULL, 0); free_token(token); if (read_expected(EVENT_OP, ";") < 0) goto fail_expect; type = read_token(&token); if (type != EVENT_NEWLINE) { /* newer versions of the kernel have a "signed" type */ if (test_type_token(type, token, EVENT_ITEM, "signed")) goto fail; free_token(token); if (read_expected(EVENT_OP, ":") < 0) goto fail_expect; if (read_expect_type(EVENT_ITEM, &token)) goto fail; if (strtoul(token, NULL, 0)) field->flags |= FIELD_IS_SIGNED; free_token(token); if (read_expected(EVENT_OP, ";") < 0) goto fail_expect; if (read_expect_type(EVENT_NEWLINE, &token)) goto fail; } free_token(token); if (field->flags & FIELD_IS_ARRAY) { if (field->arraylen) field->elementsize = field->size / field->arraylen; else if (field->flags & FIELD_IS_DYNAMIC) field->elementsize = size_dynamic; else if (field->flags & FIELD_IS_STRING) field->elementsize = 1; else if (field->flags & FIELD_IS_LONG) field->elementsize = event->pevent ? event->pevent->long_size : (int)sizeof(long); } else field->elementsize = field->size; *fields = field; fields = &field->next; } while (1); return 0; fail: free_token(token); fail_expect: if (field) { free(field->type); free(field->name); free(field); } return -1; } static int event_read_format(struct event_format *event) { char *token; int ret; if (read_expected_item(EVENT_ITEM, "format") < 0) return -1; if (read_expected(EVENT_OP, ":") < 0) return -1; if (read_expect_type(EVENT_NEWLINE, &token)) goto fail; free_token(token); ret = event_read_fields(event, &event->format.common_fields); if (ret < 0) return ret; event->format.nr_common = ret; ret = event_read_fields(event, &event->format.fields); if (ret < 0) return ret; event->format.nr_fields = ret; return 0; fail: free_token(token); return -1; } static enum event_type process_arg_token(struct event_format *event, struct print_arg *arg, char **tok, enum event_type type); static enum event_type process_arg(struct event_format *event, struct print_arg *arg, char **tok) { enum event_type type; char *token; type = read_token(&token); *tok = token; return process_arg_token(event, arg, tok, type); } static enum event_type process_op(struct event_format *event, struct print_arg *arg, char **tok); /* * For __print_symbolic() and __print_flags, we need to completely * evaluate the first argument, which defines what to print next. */ static enum event_type process_field_arg(struct event_format *event, struct print_arg *arg, char **tok) { enum event_type type; type = process_arg(event, arg, tok); while (type == EVENT_OP) { type = process_op(event, arg, tok); } return type; } static enum event_type process_cond(struct event_format *event, struct print_arg *top, char **tok) { struct print_arg *arg, *left, *right; enum event_type type; char *token = NULL; arg = alloc_arg(); left = alloc_arg(); right = alloc_arg(); if (!arg || !left || !right) { do_warning_event(event, "%s: not enough memory!", __func__); /* arg will be freed at out_free */ free_arg(left); free_arg(right); goto out_free; } arg->type = PRINT_OP; arg->op.left = left; arg->op.right = right; *tok = NULL; type = process_arg(event, left, &token); again: /* Handle other operations in the arguments */ if (type == EVENT_OP && strcmp(token, ":") != 0) { type = process_op(event, left, &token); goto again; } if (test_type_token(type, token, EVENT_OP, ":")) goto out_free; arg->op.op = token; type = process_arg(event, right, &token); top->op.right = arg; *tok = token; return type; out_free: /* Top may point to itself */ top->op.right = NULL; free_token(token); free_arg(arg); return EVENT_ERROR; } static enum event_type process_array(struct event_format *event, struct print_arg *top, char **tok) { struct print_arg *arg; enum event_type type; char *token = NULL; arg = alloc_arg(); if (!arg) { do_warning_event(event, "%s: not enough memory!", __func__); /* '*tok' is set to top->op.op. No need to free. */ *tok = NULL; return EVENT_ERROR; } *tok = NULL; type = process_arg(event, arg, &token); if (test_type_token(type, token, EVENT_OP, "]")) goto out_free; top->op.right = arg; free_token(token); type = read_token_item(&token); *tok = token; return type; out_free: free_token(token); free_arg(arg); return EVENT_ERROR; } static int get_op_prio(char *op) { if (!op[1]) { switch (op[0]) { case '~': case '!': return 4; case '*': case '/': case '%': return 6; case '+': case '-': return 7; /* '>>' and '<<' are 8 */ case '<': case '>': return 9; /* '==' and '!=' are 10 */ case '&': return 11; case '^': return 12; case '|': return 13; case '?': return 16; default: do_warning("unknown op '%c'", op[0]); return -1; } } else { if (strcmp(op, "++") == 0 || strcmp(op, "--") == 0) { return 3; } else if (strcmp(op, ">>") == 0 || strcmp(op, "<<") == 0) { return 8; } else if (strcmp(op, ">=") == 0 || strcmp(op, "<=") == 0) { return 9; } else if (strcmp(op, "==") == 0 || strcmp(op, "!=") == 0) { return 10; } else if (strcmp(op, "&&") == 0) { return 14; } else if (strcmp(op, "||") == 0) { return 15; } else { do_warning("unknown op '%s'", op); return -1; } } } static int set_op_prio(struct print_arg *arg) { /* single ops are the greatest */ if (!arg->op.left || arg->op.left->type == PRINT_NULL) arg->op.prio = 0; else arg->op.prio = get_op_prio(arg->op.op); return arg->op.prio; } /* Note, *tok does not get freed, but will most likely be saved */ static enum event_type process_op(struct event_format *event, struct print_arg *arg, char **tok) { struct print_arg *left, *right = NULL; enum event_type type; char *token; /* the op is passed in via tok */ token = *tok; if (arg->type == PRINT_OP && !arg->op.left) { /* handle single op */ if (token[1]) { do_warning_event(event, "bad op token %s", token); goto out_free; } switch (token[0]) { case '~': case '!': case '+': case '-': break; default: do_warning_event(event, "bad op token %s", token); goto out_free; } /* make an empty left */ left = alloc_arg(); if (!left) goto out_warn_free; left->type = PRINT_NULL; arg->op.left = left; right = alloc_arg(); if (!right) goto out_warn_free; arg->op.right = right; /* do not free the token, it belongs to an op */ *tok = NULL; type = process_arg(event, right, tok); } else if (strcmp(token, "?") == 0) { left = alloc_arg(); if (!left) goto out_warn_free; /* copy the top arg to the left */ *left = *arg; arg->type = PRINT_OP; arg->op.op = token; arg->op.left = left; arg->op.prio = 0; /* it will set arg->op.right */ type = process_cond(event, arg, tok); } else if (strcmp(token, ">>") == 0 || strcmp(token, "<<") == 0 || strcmp(token, "&") == 0 || strcmp(token, "|") == 0 || strcmp(token, "&&") == 0 || strcmp(token, "||") == 0 || strcmp(token, "-") == 0 || strcmp(token, "+") == 0 || strcmp(token, "*") == 0 || strcmp(token, "^") == 0 || strcmp(token, "/") == 0 || strcmp(token, "<") == 0 || strcmp(token, ">") == 0 || strcmp(token, "<=") == 0 || strcmp(token, ">=") == 0 || strcmp(token, "==") == 0 || strcmp(token, "!=") == 0) { left = alloc_arg(); if (!left) goto out_warn_free; /* copy the top arg to the left */ *left = *arg; arg->type = PRINT_OP; arg->op.op = token; arg->op.left = left; arg->op.right = NULL; if (set_op_prio(arg) == -1) { event->flags |= EVENT_FL_FAILED; /* arg->op.op (= token) will be freed at out_free */ arg->op.op = NULL; goto out_free; } type = read_token_item(&token); *tok = token; /* could just be a type pointer */ if ((strcmp(arg->op.op, "*") == 0) && type == EVENT_DELIM && (strcmp(token, ")") == 0)) { char *new_atom; if (left->type != PRINT_ATOM) { do_warning_event(event, "bad pointer type"); goto out_free; } new_atom = realloc(left->atom.atom, strlen(left->atom.atom) + 3); if (!new_atom) goto out_warn_free; left->atom.atom = new_atom; strcat(left->atom.atom, " *"); free(arg->op.op); *arg = *left; free(left); return type; } right = alloc_arg(); if (!right) goto out_warn_free; type = process_arg_token(event, right, tok, type); arg->op.right = right; } else if (strcmp(token, "[") == 0) { left = alloc_arg(); if (!left) goto out_warn_free; *left = *arg; arg->type = PRINT_OP; arg->op.op = token; arg->op.left = left; arg->op.prio = 0; /* it will set arg->op.right */ type = process_array(event, arg, tok); } else { do_warning_event(event, "unknown op '%s'", token); event->flags |= EVENT_FL_FAILED; /* the arg is now the left side */ goto out_free; } if (type == EVENT_OP && strcmp(*tok, ":") != 0) { int prio; /* higher prios need to be closer to the root */ prio = get_op_prio(*tok); if (prio > arg->op.prio) return process_op(event, arg, tok); return process_op(event, right, tok); } return type; out_warn_free: do_warning_event(event, "%s: not enough memory!", __func__); out_free: free_token(token); *tok = NULL; return EVENT_ERROR; } static enum event_type process_entry(struct event_format *event __maybe_unused, struct print_arg *arg, char **tok) { enum event_type type; char *field; char *token; if (read_expected(EVENT_OP, "->") < 0) goto out_err; if (read_expect_type(EVENT_ITEM, &token) < 0) goto out_free; field = token; arg->type = PRINT_FIELD; arg->field.name = field; if (is_flag_field) { arg->field.field = pevent_find_any_field(event, arg->field.name); arg->field.field->flags |= FIELD_IS_FLAG; is_flag_field = 0; } else if (is_symbolic_field) { arg->field.field = pevent_find_any_field(event, arg->field.name); arg->field.field->flags |= FIELD_IS_SYMBOLIC; is_symbolic_field = 0; } type = read_token(&token); *tok = token; return type; out_free: free_token(token); out_err: *tok = NULL; return EVENT_ERROR; } static char *arg_eval (struct print_arg *arg); static unsigned long long eval_type_str(unsigned long long val, const char *type, int pointer) { int sign = 0; char *ref; int len; len = strlen(type); if (pointer) { if (type[len-1] != '*') { do_warning("pointer expected with non pointer type"); return val; } ref = malloc(len); if (!ref) { do_warning("%s: not enough memory!", __func__); return val; } memcpy(ref, type, len); /* chop off the " *" */ ref[len - 2] = 0; val = eval_type_str(val, ref, 0); free(ref); return val; } /* check if this is a pointer */ if (type[len - 1] == '*') return val; /* Try to figure out the arg size*/ if (strncmp(type, "struct", 6) == 0) /* all bets off */ return val; if (strcmp(type, "u8") == 0) return val & 0xff; if (strcmp(type, "u16") == 0) return val & 0xffff; if (strcmp(type, "u32") == 0) return val & 0xffffffff; if (strcmp(type, "u64") == 0 || strcmp(type, "s64")) return val; if (strcmp(type, "s8") == 0) return (unsigned long long)(char)val & 0xff; if (strcmp(type, "s16") == 0) return (unsigned long long)(short)val & 0xffff; if (strcmp(type, "s32") == 0) return (unsigned long long)(int)val & 0xffffffff; if (strncmp(type, "unsigned ", 9) == 0) { sign = 0; type += 9; } if (strcmp(type, "char") == 0) { if (sign) return (unsigned long long)(char)val & 0xff; else return val & 0xff; } if (strcmp(type, "short") == 0) { if (sign) return (unsigned long long)(short)val & 0xffff; else return val & 0xffff; } if (strcmp(type, "int") == 0) { if (sign) return (unsigned long long)(int)val & 0xffffffff; else return val & 0xffffffff; } return val; } /* * Try to figure out the type. */ static unsigned long long eval_type(unsigned long long val, struct print_arg *arg, int pointer) { if (arg->type != PRINT_TYPE) { do_warning("expected type argument"); return 0; } return eval_type_str(val, arg->typecast.type, pointer); } static int arg_num_eval(struct print_arg *arg, long long *val) { long long left, right; int ret = 1; switch (arg->type) { case PRINT_ATOM: *val = strtoll(arg->atom.atom, NULL, 0); break; case PRINT_TYPE: ret = arg_num_eval(arg->typecast.item, val); if (!ret) break; *val = eval_type(*val, arg, 0); break; case PRINT_OP: switch (arg->op.op[0]) { case '|': ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; if (arg->op.op[1]) *val = left || right; else *val = left | right; break; case '&': ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; if (arg->op.op[1]) *val = left && right; else *val = left & right; break; case '<': ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; switch (arg->op.op[1]) { case 0: *val = left < right; break; case '<': *val = left << right; break; case '=': *val = left <= right; break; default: do_warning("unknown op '%s'", arg->op.op); ret = 0; } break; case '>': ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; switch (arg->op.op[1]) { case 0: *val = left > right; break; case '>': *val = left >> right; break; case '=': *val = left >= right; break; default: do_warning("unknown op '%s'", arg->op.op); ret = 0; } break; case '=': ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; if (arg->op.op[1] != '=') { do_warning("unknown op '%s'", arg->op.op); ret = 0; } else *val = left == right; break; case '!': ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; switch (arg->op.op[1]) { case '=': *val = left != right; break; default: do_warning("unknown op '%s'", arg->op.op); ret = 0; } break; case '-': /* check for negative */ if (arg->op.left->type == PRINT_NULL) left = 0; else ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; *val = left - right; break; case '+': if (arg->op.left->type == PRINT_NULL) left = 0; else ret = arg_num_eval(arg->op.left, &left); if (!ret) break; ret = arg_num_eval(arg->op.right, &right); if (!ret) break; *val = left + right; break; default: do_warning("unknown op '%s'", arg->op.op); ret = 0; } break; case PRINT_NULL: case PRINT_FIELD ... PRINT_SYMBOL: case PRINT_STRING: case PRINT_BSTRING: case PRINT_BITMASK: default: do_warning("invalid eval type %d", arg->type); ret = 0; } return ret; } static char *arg_eval (struct print_arg *arg) { long long val; static char buf[20]; switch (arg->type) { case PRINT_ATOM: return arg->atom.atom; case PRINT_TYPE: return arg_eval(arg->typecast.item); case PRINT_OP: if (!arg_num_eval(arg, &val)) break; sprintf(buf, "%lld", val); return buf; case PRINT_NULL: case PRINT_FIELD ... PRINT_SYMBOL: case PRINT_STRING: case PRINT_BSTRING: case PRINT_BITMASK: default: do_warning("invalid eval type %d", arg->type); break; } return NULL; } static enum event_type process_fields(struct event_format *event, struct print_flag_sym **list, char **tok) { enum event_type type; struct print_arg *arg = NULL; struct print_flag_sym *field; char *token = *tok; char *value; do { free_token(token); type = read_token_item(&token); if (test_type_token(type, token, EVENT_OP, "{")) break; arg = alloc_arg(); if (!arg) goto out_free; free_token(token); type = process_arg(event, arg, &token); if (type == EVENT_OP) type = process_op(event, arg, &token); if (type == EVENT_ERROR) goto out_free; if (test_type_token(type, token, EVENT_DELIM, ",")) goto out_free; field = calloc(1, sizeof(*field)); if (!field) goto out_free; value = arg_eval(arg); if (value == NULL) goto out_free_field; field->value = strdup(value); if (field->value == NULL) goto out_free_field; free_arg(arg); arg = alloc_arg(); if (!arg) goto out_free; free_token(token); type = process_arg(event, arg, &token); if (test_type_token(type, token, EVENT_OP, "}")) goto out_free_field; value = arg_eval(arg); if (value == NULL) goto out_free_field; field->str = strdup(value); if (field->str == NULL) goto out_free_field; free_arg(arg); arg = NULL; *list = field; list = &field->next; free_token(token); type = read_token_item(&token); } while (type == EVENT_DELIM && strcmp(token, ",") == 0); *tok = token; return type; out_free_field: free_flag_sym(field); out_free: free_arg(arg); free_token(token); *tok = NULL; return EVENT_ERROR; } static enum event_type process_flags(struct event_format *event, struct print_arg *arg, char **tok) { struct print_arg *field; enum event_type type; char *token = NULL; memset(arg, 0, sizeof(*arg)); arg->type = PRINT_FLAGS; field = alloc_arg(); if (!field) { do_warning_event(event, "%s: not enough memory!", __func__); goto out_free; } type = process_field_arg(event, field, &token); /* Handle operations in the first argument */ while (type == EVENT_OP) type = process_op(event, field, &token); if (test_type_token(type, token, EVENT_DELIM, ",")) goto out_free_field; free_token(token); arg->flags.field = field; type = read_token_item(&token); if (event_item_type(type)) { arg->flags.delim = token; type = read_token_item(&token); } if (test_type_token(type, token, EVENT_DELIM, ",")) goto out_free; type = process_fields(event, &arg->flags.flags, &token); if (test_type_token(type, token, EVENT_DELIM, ")")) goto out_free; free_token(token); type = read_token_item(tok); return type; out_free_field: free_arg(field); out_free: free_token(token); *tok = NULL; return EVENT_ERROR; } static enum event_type process_symbols(struct event_format *event, struct print_arg *arg, char **tok) { struct print_arg *field; enum event_type type; char *token = NULL; memset(arg, 0, sizeof(*arg)); arg->type = PRINT_SYMBOL; field = alloc_arg(); if (!field) { do_warning_event(event, "%s: not enough memory!", __func__); goto out_free; } type = process_field_arg(event, field, &token); if (test_type_token(type, token, EVENT_DELIM, ",")) goto out_free_field; arg->symbol.field = field; type = process_fields(event, &arg->symbol.symbols, &token); if (test_type_token(type, token, EVENT_DELIM, ")")) goto out_free; free_token(token); type = read_token_item(tok); return type; out_free_field: free_arg(field); out_free: free_token(token); *tok = NULL; return EVENT_ERROR; } static enum event_type process_hex(struct event_format *event, struct print_arg *arg, char **tok) { struct print_arg *field; enum event_type type; char *token = NULL; memset(arg, 0, sizeof(*arg)); arg->type = PRINT_HEX; field = alloc_arg(); if (!field) { do_warning_event(event, "%s: not enough memory!", __func__); goto out_free; } type = process_arg(event, field, &token); if (test_type_token(type, token, EVENT_DELIM, ",")) goto out_free; arg->hex.field = field; free_token(token); field = alloc_arg(); if (!field) { do_warning_event(event, "%s: not enough memory!", __func__); *tok = NULL; return EVENT_ERROR; } type = process_arg(event, field, &token); if (test_type_token(type, token, EVENT_DELIM, ")")) goto out_free; arg->hex.size = field; free_token(token); type = read_token_item(tok); return type; out_free: free_arg(field); free_token(token); *tok = NULL; return EVENT_ERROR; } static enum event_type process_dynamic_array(struct event_format *event, struct print_arg *arg, char **tok) { struct format_field *field; enum event_type type; char *token; memset(arg, 0, sizeof(*arg)); arg->type = PRINT_DYNAMIC_ARRAY; /* * The item within the parenthesis is another field that holds * the index into where the array starts. */ type = read_token(&token); *tok = token; if (type != EVENT_ITEM) goto out_free; /* Find the field */ field = pevent_find_field(event, token); if (!field) goto out_free; arg->dynarray.field = field; arg->dynarray.index = 0; if (read_expected(EVENT_DELIM, ")") < 0) goto out_free; free_token(token); type = read_token_item(&token); *tok = token; if (type != EVENT_OP || strcmp(token, "[") != 0) return type; free_token(token); arg = alloc_arg(); if (!arg) { do_warning_event(event, "%s: not enough memory!", __func__); *tok = NULL; return EVENT_ERROR; } type = process_arg(event, arg, &token); if (type == EVENT_ERROR) goto out_free_arg; if (!test_type_token(type, token, EVENT_OP, "]")) goto out_free_arg; free_token(token); type = read_token_item(tok); return type; out_free_arg: free_arg(arg); out_free: free_token(token); *tok = NULL; return EVENT_ERROR; } static enum event_type process_paren(struct event_format *event, struct print_arg *arg, char **tok) { struct print_arg *item_arg; enum event_type type; char *token; type = process_arg(event, arg, &token); if (type == EVENT_ERROR) goto out_free; if (type == EVENT_OP) type = process_op(event, arg, &token); if (type == EVENT_ERROR) goto out_free; if (test_type_token(type, token, EVENT_DELIM, ")")) goto out_free; free_token(token); type = read_token_item(&token); /* * If the next token is an item or another open paren, then * this was a typecast. */ if (event_item_type(type) || (type == EVENT_DELIM && strcmp(token, "(") == 0)) { /* make this a typecast and contine */ /* prevous must be an atom */ if (arg->type != PRINT_ATOM) { do_warning_event(event, "previous needed to be PRINT_ATOM"); goto out_free; } item_arg = alloc_arg(); if (!item_arg) { do_warning_event(event, "%s: not enough memory!", __func__); goto out_free; } arg->type = PRINT_TYPE; arg->typecast.type = arg->atom.atom; arg->typecast.item = item_arg; type = process_arg_token(event, item_arg, &token, type); } *tok = token; return type; out_free: free_token(token); *tok = NULL; return EVENT_ERROR; } static enum event_type process_str(struct event_format *event __maybe_unused, struct print_arg *arg, char **tok) { enum event_type type; char *token; if (read_expect_type(EVENT_ITEM, &token) < 0) goto out_free; arg->type = PRINT_STRING; arg->string.string = token; arg->string.offset = -1; if (read_expected(EVENT_DELIM, ")") < 0) goto out_err; type = read_token(&token); *tok = token; return type; out_free: free_token(token); out_err: *tok = NULL; return EVENT_ERROR; } static enum event_type process_bitmask(struct event_format *event __maybe_unused, struct print_arg *arg, char **tok) { enum event_type type; char *token; if (read_expect_type(EVENT_ITEM, &token) < 0) goto out_free; arg->type = PRINT_BITMASK; arg->bitmask.bitmask = token; arg->bitmask.offset = -1; if (read_expected(EVENT_DELIM, ")") < 0) goto out_err; type = read_token(&token); *tok = token; return type; out_free: free_token(token); out_err: *tok = NULL; return EVENT_ERROR; } static struct pevent_function_handler * find_func_handler(struct pevent *pevent, char *func_name) { struct pevent_function_handler *func; if (!pevent) return NULL; for (func = pevent->func_handlers; func; func = func->next) { if (strcmp(func->name, func_name) == 0) break; } return func; } static void remove_func_handler(struct pevent *pevent, char *func_name) { struct pevent_function_handler *func; struct pevent_function_handler **next; next = &pevent->func_handlers; while ((func = *next)) { if (strcmp(func->name, func_name) == 0) { *next = func->next; free_func_handle(func); break; } next = &func->next; } } static enum event_type process_func_handler(struct event_format *event, struct pevent_function_handler *func, struct print_arg *arg, char **tok) { struct print_arg **next_arg; struct print_arg *farg; enum event_type type; char *token; int i; arg->type = PRINT_FUNC; arg->func.func = func; *tok = NULL; next_arg = &(arg->func.args); for (i = 0; i < func->nr_args; i++) { farg = alloc_arg(); if (!farg) { do_warning_event(event, "%s: not enough memory!", __func__); return EVENT_ERROR; } type = process_arg(event, farg, &token); if (i < (func->nr_args - 1)) { if (type != EVENT_DELIM || strcmp(token, ",") != 0) { do_warning_event(event, "Error: function '%s()' expects %d arguments but event %s only uses %d", func->name, func->nr_args, event->name, i + 1); goto err; } } else { if (type != EVENT_DELIM || strcmp(token, ")") != 0) { do_warning_event(event, "Error: function '%s()' only expects %d arguments but event %s has more", func->name, func->nr_args, event->name); goto err; } } *next_arg = farg; next_arg = &(farg->next); free_token(token); } type = read_token(&token); *tok = token; return type; err: free_arg(farg); free_token(token); return EVENT_ERROR; } static enum event_type process_function(struct event_format *event, struct print_arg *arg, char *token, char **tok) { struct pevent_function_handler *func; if (strcmp(token, "__print_flags") == 0) { free_token(token); is_flag_field = 1; return process_flags(event, arg, tok); } if (strcmp(token, "__print_symbolic") == 0) { free_token(token); is_symbolic_field = 1; return process_symbols(event, arg, tok); } if (strcmp(token, "__print_hex") == 0) { free_token(token); return process_hex(event, arg, tok); } if (strcmp(token, "__get_str") == 0) { free_token(token); return process_str(event, arg, tok); } if (strcmp(token, "__get_bitmask") == 0) { free_token(token); return process_bitmask(event, arg, tok); } if (strcmp(token, "__get_dynamic_array") == 0) { free_token(token); return process_dynamic_array(event, arg, tok); } func = find_func_handler(event->pevent, token); if (func) { free_token(token); return process_func_handler(event, func, arg, tok); } do_warning_event(event, "function %s not defined", token); free_token(token); return EVENT_ERROR; } static enum event_type process_arg_token(struct event_format *event, struct print_arg *arg, char **tok, enum event_type type) { char *token; char *atom; token = *tok; switch (type) { case EVENT_ITEM: if (strcmp(token, "REC") == 0) { free_token(token); type = process_entry(event, arg, &token); break; } atom = token; /* test the next token */ type = read_token_item(&token); /* * If the next token is a parenthesis, then this * is a function. */ if (type == EVENT_DELIM && strcmp(token, "(") == 0) { free_token(token); token = NULL; /* this will free atom. */ type = process_function(event, arg, atom, &token); break; } /* atoms can be more than one token long */ while (type == EVENT_ITEM) { char *new_atom; new_atom = realloc(atom, strlen(atom) + strlen(token) + 2); if (!new_atom) { free(atom); *tok = NULL; free_token(token); return EVENT_ERROR; } atom = new_atom; strcat(atom, " "); strcat(atom, token); free_token(token); type = read_token_item(&token); } arg->type = PRINT_ATOM; arg->atom.atom = atom; break; case EVENT_DQUOTE: case EVENT_SQUOTE: arg->type = PRINT_ATOM; arg->atom.atom = token; type = read_token_item(&token); break; case EVENT_DELIM: if (strcmp(token, "(") == 0) { free_token(token); type = process_paren(event, arg, &token); break; } /* fall through */ case EVENT_OP: /* handle single ops */ arg->type = PRINT_OP; arg->op.op = token; arg->op.left = NULL; type = process_op(event, arg, &token); /* On error, the op is freed */ if (type == EVENT_ERROR) arg->op.op = NULL; /* return error type if errored */ break; case EVENT_ERROR ... EVENT_NEWLINE: default: do_warning_event(event, "unexpected type %d", type); return EVENT_ERROR; } *tok = token; return type; } static int event_read_print_args(struct event_format *event, struct print_arg **list) { enum event_type type = EVENT_ERROR; struct print_arg *arg; char *token; int args = 0; do { if (type == EVENT_NEWLINE) { type = read_token_item(&token); continue; } arg = alloc_arg(); if (!arg) { do_warning_event(event, "%s: not enough memory!", __func__); return -1; } type = process_arg(event, arg, &token); if (type == EVENT_ERROR) { free_token(token); free_arg(arg); return -1; } *list = arg; args++; if (type == EVENT_OP) { type = process_op(event, arg, &token); free_token(token); if (type == EVENT_ERROR) { *list = NULL; free_arg(arg); return -1; } list = &arg->next; continue; } if (type == EVENT_DELIM && strcmp(token, ",") == 0) { free_token(token); *list = arg; list = &arg->next; continue; } break; } while (type != EVENT_NONE); if (type != EVENT_NONE && type != EVENT_ERROR) free_token(token); return args; } static int event_read_print(struct event_format *event) { enum event_type type; char *token; int ret; if (read_expected_item(EVENT_ITEM, "print") < 0) return -1; if (read_expected(EVENT_ITEM, "fmt") < 0) return -1; if (read_expected(EVENT_OP, ":") < 0) return -1; if (read_expect_type(EVENT_DQUOTE, &token) < 0) goto fail; concat: event->print_fmt.format = token; event->print_fmt.args = NULL; /* ok to have no arg */ type = read_token_item(&token); if (type == EVENT_NONE) return 0; /* Handle concatenation of print lines */ if (type == EVENT_DQUOTE) { char *cat; if (asprintf(&cat, "%s%s", event->print_fmt.format, token) < 0) goto fail; free_token(token); free_token(event->print_fmt.format); event->print_fmt.format = NULL; token = cat; goto concat; } if (test_type_token(type, token, EVENT_DELIM, ",")) goto fail; free_token(token); ret = event_read_print_args(event, &event->print_fmt.args); if (ret < 0) return -1; return ret; fail: free_token(token); return -1; } /** * pevent_find_common_field - return a common field by event * @event: handle for the event * @name: the name of the common field to return * * Returns a common field from the event by the given @name. * This only searchs the common fields and not all field. */ struct format_field * pevent_find_common_field(struct event_format *event, const char *name) { struct format_field *format; for (format = event->format.common_fields; format; format = format->next) { if (strcmp(format->name, name) == 0) break; } return format; } /** * pevent_find_field - find a non-common field * @event: handle for the event * @name: the name of the non-common field * * Returns a non-common field by the given @name. * This does not search common fields. */ struct format_field * pevent_find_field(struct event_format *event, const char *name) { struct format_field *format; for (format = event->format.fields; format; format = format->next) { if (strcmp(format->name, name) == 0) break; } return format; } /** * pevent_find_any_field - find any field by name * @event: handle for the event * @name: the name of the field * * Returns a field by the given @name. * This searchs the common field names first, then * the non-common ones if a common one was not found. */ struct format_field * pevent_find_any_field(struct event_format *event, const char *name) { struct format_field *format; format = pevent_find_common_field(event, name); if (format) return format; return pevent_find_field(event, name); } /** * pevent_read_number - read a number from data * @pevent: handle for the pevent * @ptr: the raw data * @size: the size of the data that holds the number * * Returns the number (converted to host) from the * raw data. */ unsigned long long pevent_read_number(struct pevent *pevent, const void *ptr, int size) { switch (size) { case 1: return *(unsigned char *)ptr; case 2: return data2host2(pevent, ptr); case 4: return data2host4(pevent, ptr); case 8: return data2host8(pevent, ptr); default: /* BUG! */ return 0; } } /** * pevent_read_number_field - read a number from data * @field: a handle to the field * @data: the raw data to read * @value: the value to place the number in * * Reads raw data according to a field offset and size, * and translates it into @value. * * Returns 0 on success, -1 otherwise. */ int pevent_read_number_field(struct format_field *field, const void *data, unsigned long long *value) { if (!field) return -1; switch (field->size) { case 1: case 2: case 4: case 8: *value = pevent_read_number(field->event->pevent, data + field->offset, field->size); return 0; default: return -1; } } static int get_common_info(struct pevent *pevent, const char *type, int *offset, int *size) { struct event_format *event; struct format_field *field; /* * All events should have the same common elements. * Pick any event to find where the type is; */ if (!pevent->events) { do_warning("no event_list!"); return -1; } event = pevent->events[0]; field = pevent_find_common_field(event, type); if (!field) return -1; *offset = field->offset; *size = field->size; return 0; } static int __parse_common(struct pevent *pevent, void *data, int *size, int *offset, const char *name) { int ret; if (!*size) { ret = get_common_info(pevent, name, offset, size); if (ret < 0) return ret; } return pevent_read_number(pevent, data + *offset, *size); } static int trace_parse_common_type(struct pevent *pevent, void *data) { return __parse_common(pevent, data, &pevent->type_size, &pevent->type_offset, "common_type"); } static int parse_common_pid(struct pevent *pevent, void *data) { return __parse_common(pevent, data, &pevent->pid_size, &pevent->pid_offset, "common_pid"); } static int parse_common_pc(struct pevent *pevent, void *data) { return __parse_common(pevent, data, &pevent->pc_size, &pevent->pc_offset, "common_preempt_count"); } static int parse_common_flags(struct pevent *pevent, void *data) { return __parse_common(pevent, data, &pevent->flags_size, &pevent->flags_offset, "common_flags"); } static int parse_common_lock_depth(struct pevent *pevent, void *data) { return __parse_common(pevent, data, &pevent->ld_size, &pevent->ld_offset, "common_lock_depth"); } static int parse_common_migrate_disable(struct pevent *pevent, void *data) { return __parse_common(pevent, data, &pevent->ld_size, &pevent->ld_offset, "common_migrate_disable"); } static int events_id_cmp(const void *a, const void *b); /** * pevent_find_event - find an event by given id * @pevent: a handle to the pevent * @id: the id of the event * * Returns an event that has a given @id. */ struct event_format *pevent_find_event(struct pevent *pevent, int id) { struct event_format **eventptr; struct event_format key; struct event_format *pkey = &key; /* Check cache first */ if (pevent->last_event && pevent->last_event->id == id) return pevent->last_event; key.id = id; eventptr = bsearch(&pkey, pevent->events, pevent->nr_events, sizeof(*pevent->events), events_id_cmp); if (eventptr) { pevent->last_event = *eventptr; return *eventptr; } return NULL; } /** * pevent_find_event_by_name - find an event by given name * @pevent: a handle to the pevent * @sys: the system name to search for * @name: the name of the event to search for * * This returns an event with a given @name and under the system * @sys. If @sys is NULL the first event with @name is returned. */ struct event_format * pevent_find_event_by_name(struct pevent *pevent, const char *sys, const char *name) { struct event_format *event = NULL; int i; if (pevent->last_event && strcmp(pevent->last_event->name, name) == 0 && (!sys || strcmp(pevent->last_event->system, sys) == 0)) return pevent->last_event; for (i = 0; i < pevent->nr_events; i++) { event = pevent->events[i]; if (strcmp(event->name, name) == 0) { if (!sys) break; if (strcmp(event->system, sys) == 0) break; } } if (i == pevent->nr_events) event = NULL; pevent->last_event = event; return event; } static unsigned long long eval_num_arg(void *data, int size, struct event_format *event, struct print_arg *arg) { struct pevent *pevent = event->pevent; unsigned long long val = 0; unsigned long long left, right; struct print_arg *typearg = NULL; struct print_arg *larg; unsigned long offset; unsigned int field_size; switch (arg->type) { case PRINT_NULL: /* ?? */ return 0; case PRINT_ATOM: return strtoull(arg->atom.atom, NULL, 0); case PRINT_FIELD: if (!arg->field.field) { arg->field.field = pevent_find_any_field(event, arg->field.name); if (!arg->field.field) goto out_warning_field; } /* must be a number */ val = pevent_read_number(pevent, data + arg->field.field->offset, arg->field.field->size); break; case PRINT_FLAGS: case PRINT_SYMBOL: case PRINT_HEX: break; case PRINT_TYPE: val = eval_num_arg(data, size, event, arg->typecast.item); return eval_type(val, arg, 0); case PRINT_STRING: case PRINT_BSTRING: case PRINT_BITMASK: return 0; case PRINT_FUNC: { struct trace_seq s; trace_seq_init(&s); val = process_defined_func(&s, data, size, event, arg); trace_seq_destroy(&s); return val; } case PRINT_OP: if (strcmp(arg->op.op, "[") == 0) { /* * Arrays are special, since we don't want * to read the arg as is. */ right = eval_num_arg(data, size, event, arg->op.right); /* handle typecasts */ larg = arg->op.left; while (larg->type == PRINT_TYPE) { if (!typearg) typearg = larg; larg = larg->typecast.item; } /* Default to long size */ field_size = pevent->long_size; switch (larg->type) { case PRINT_DYNAMIC_ARRAY: offset = pevent_read_number(pevent, data + larg->dynarray.field->offset, larg->dynarray.field->size); if (larg->dynarray.field->elementsize) field_size = larg->dynarray.field->elementsize; /* * The actual length of the dynamic array is stored * in the top half of the field, and the offset * is in the bottom half of the 32 bit field. */ offset &= 0xffff; offset += right; break; case PRINT_FIELD: if (!larg->field.field) { larg->field.field = pevent_find_any_field(event, larg->field.name); if (!larg->field.field) { arg = larg; goto out_warning_field; } } field_size = larg->field.field->elementsize; offset = larg->field.field->offset + right * larg->field.field->elementsize; break; default: goto default_op; /* oops, all bets off */ } val = pevent_read_number(pevent, data + offset, field_size); if (typearg) val = eval_type(val, typearg, 1); break; } else if (strcmp(arg->op.op, "?") == 0) { left = eval_num_arg(data, size, event, arg->op.left); arg = arg->op.right; if (left) val = eval_num_arg(data, size, event, arg->op.left); else val = eval_num_arg(data, size, event, arg->op.right); break; } default_op: left = eval_num_arg(data, size, event, arg->op.left); right = eval_num_arg(data, size, event, arg->op.right); switch (arg->op.op[0]) { case '!': switch (arg->op.op[1]) { case 0: val = !right; break; case '=': val = left != right; break; default: goto out_warning_op; } break; case '~': val = ~right; break; case '|': if (arg->op.op[1]) val = left || right; else val = left | right; break; case '&': if (arg->op.op[1]) val = left && right; else val = left & right; break; case '<': switch (arg->op.op[1]) { case 0: val = left < right; break; case '<': val = left << right; break; case '=': val = left <= right; break; default: goto out_warning_op; } break; case '>': switch (arg->op.op[1]) { case 0: val = left > right; break; case '>': val = left >> right; break; case '=': val = left >= right; break; default: goto out_warning_op; } break; case '=': if (arg->op.op[1] != '=') goto out_warning_op; val = left == right; break; case '-': val = left - right; break; case '+': val = left + right; break; case '/': val = left / right; break; case '*': val = left * right; break; default: goto out_warning_op; } break; case PRINT_DYNAMIC_ARRAY: /* Without [], we pass the address to the dynamic data */ offset = pevent_read_number(pevent, data + arg->dynarray.field->offset, arg->dynarray.field->size); /* * The actual length of the dynamic array is stored * in the top half of the field, and the offset * is in the bottom half of the 32 bit field. */ offset &= 0xffff; val = (unsigned long long)((unsigned long)data + offset); break; default: /* not sure what to do there */ return 0; } return val; out_warning_op: do_warning_event(event, "%s: unknown op '%s'", __func__, arg->op.op); return 0; out_warning_field: do_warning_event(event, "%s: field %s not found", __func__, arg->field.name); return 0; } struct flag { const char *name; unsigned long long value; }; static const struct flag flags[] = { { "HI_SOFTIRQ", 0 }, { "TIMER_SOFTIRQ", 1 }, { "NET_TX_SOFTIRQ", 2 }, { "NET_RX_SOFTIRQ", 3 }, { "BLOCK_SOFTIRQ", 4 }, { "BLOCK_IOPOLL_SOFTIRQ", 5 }, { "TASKLET_SOFTIRQ", 6 }, { "SCHED_SOFTIRQ", 7 }, { "HRTIMER_SOFTIRQ", 8 }, { "RCU_SOFTIRQ", 9 }, { "HRTIMER_NORESTART", 0 }, { "HRTIMER_RESTART", 1 }, }; static unsigned long long eval_flag(const char *flag) { int i; /* * Some flags in the format files do not get converted. * If the flag is not numeric, see if it is something that * we already know about. */ if (isdigit(flag[0])) return strtoull(flag, NULL, 0); for (i = 0; i < (int)(sizeof(flags)/sizeof(flags[0])); i++) if (strcmp(flags[i].name, flag) == 0) return flags[i].value; return 0; } static void print_str_to_seq(struct trace_seq *s, const char *format, int len_arg, const char *str) { if (len_arg >= 0) trace_seq_printf(s, format, len_arg, str); else trace_seq_printf(s, format, str); } static void print_bitmask_to_seq(struct pevent *pevent, struct trace_seq *s, const char *format, int len_arg, const void *data, int size) { int nr_bits = size * 8; int str_size = (nr_bits + 3) / 4; int len = 0; char buf[3]; char *str; int index; int i; /* * The kernel likes to put in commas every 32 bits, we * can do the same. */ str_size += (nr_bits - 1) / 32; str = malloc(str_size + 1); if (!str) { do_warning("%s: not enough memory!", __func__); return; } str[str_size] = 0; /* Start out with -2 for the two chars per byte */ for (i = str_size - 2; i >= 0; i -= 2) { /* * data points to a bit mask of size bytes. * In the kernel, this is an array of long words, thus * endianess is very important. */ if (pevent->file_bigendian) index = size - (len + 1); else index = len; snprintf(buf, 3, "%02x", *((unsigned char *)data + index)); memcpy(str + i, buf, 2); len++; if (!(len & 3) && i > 0) { i--; str[i] = ','; } } if (len_arg >= 0) trace_seq_printf(s, format, len_arg, str); else trace_seq_printf(s, format, str); free(str); } static void print_str_arg(struct trace_seq *s, void *data, int size, struct event_format *event, const char *format, int len_arg, struct print_arg *arg) { struct pevent *pevent = event->pevent; struct print_flag_sym *flag; struct format_field *field; struct printk_map *printk; unsigned long long val, fval; unsigned long addr; char *str; unsigned char *hex; int print; int i, len; switch (arg->type) { case PRINT_NULL: /* ?? */ return; case PRINT_ATOM: print_str_to_seq(s, format, len_arg, arg->atom.atom); return; case PRINT_FIELD: field = arg->field.field; if (!field) { field = pevent_find_any_field(event, arg->field.name); if (!field) { str = arg->field.name; goto out_warning_field; } arg->field.field = field; } /* Zero sized fields, mean the rest of the data */ len = field->size ? : size - field->offset; /* * Some events pass in pointers. If this is not an array * and the size is the same as long_size, assume that it * is a pointer. */ if (!(field->flags & FIELD_IS_ARRAY) && field->size == pevent->long_size) { addr = *(unsigned long *)(data + field->offset); /* Check if it matches a print format */ printk = find_printk(pevent, addr); if (printk) trace_seq_puts(s, printk->printk); else trace_seq_printf(s, "%lx", addr); break; } str = malloc(len + 1); if (!str) { do_warning_event(event, "%s: not enough memory!", __func__); return; } memcpy(str, data + field->offset, len); str[len] = 0; print_str_to_seq(s, format, len_arg, str); free(str); break; case PRINT_FLAGS: val = eval_num_arg(data, size, event, arg->flags.field); print = 0; for (flag = arg->flags.flags; flag; flag = flag->next) { fval = eval_flag(flag->value); if (!val && !fval) { print_str_to_seq(s, format, len_arg, flag->str); break; } if (fval && (val & fval) == fval) { if (print && arg->flags.delim) trace_seq_puts(s, arg->flags.delim); print_str_to_seq(s, format, len_arg, flag->str); print = 1; val &= ~fval; } } break; case PRINT_SYMBOL: val = eval_num_arg(data, size, event, arg->symbol.field); for (flag = arg->symbol.symbols; flag; flag = flag->next) { fval = eval_flag(flag->value); if (val == fval) { print_str_to_seq(s, format, len_arg, flag->str); break; } } break; case PRINT_HEX: if (arg->hex.field->type == PRINT_DYNAMIC_ARRAY) { unsigned long offset; offset = pevent_read_number(pevent, data + arg->hex.field->dynarray.field->offset, arg->hex.field->dynarray.field->size); hex = data + (offset & 0xffff); } else { field = arg->hex.field->field.field; if (!field) { str = arg->hex.field->field.name; field = pevent_find_any_field(event, str); if (!field) goto out_warning_field; arg->hex.field->field.field = field; } hex = data + field->offset; } len = eval_num_arg(data, size, event, arg->hex.size); for (i = 0; i < len; i++) { if (i) trace_seq_putc(s, ' '); trace_seq_printf(s, "%02x", hex[i]); } break; case PRINT_TYPE: break; case PRINT_STRING: { int str_offset; if (arg->string.offset == -1) { struct format_field *f; f = pevent_find_any_field(event, arg->string.string); arg->string.offset = f->offset; } str_offset = data2host4(pevent, data + arg->string.offset); str_offset &= 0xffff; print_str_to_seq(s, format, len_arg, ((char *)data) + str_offset); break; } case PRINT_BSTRING: print_str_to_seq(s, format, len_arg, arg->string.string); break; case PRINT_BITMASK: { int bitmask_offset; int bitmask_size; if (arg->bitmask.offset == -1) { struct format_field *f; f = pevent_find_any_field(event, arg->bitmask.bitmask); arg->bitmask.offset = f->offset; } bitmask_offset = data2host4(pevent, data + arg->bitmask.offset); bitmask_size = bitmask_offset >> 16; bitmask_offset &= 0xffff; print_bitmask_to_seq(pevent, s, format, len_arg, data + bitmask_offset, bitmask_size); break; } case PRINT_OP: /* * The only op for string should be ? : */ if (arg->op.op[0] != '?') return; val = eval_num_arg(data, size, event, arg->op.left); if (val) print_str_arg(s, data, size, event, format, len_arg, arg->op.right->op.left); else print_str_arg(s, data, size, event, format, len_arg, arg->op.right->op.right); break; case PRINT_FUNC: process_defined_func(s, data, size, event, arg); break; default: /* well... */ break; } return; out_warning_field: do_warning_event(event, "%s: field %s not found", __func__, arg->field.name); } static unsigned long long process_defined_func(struct trace_seq *s, void *data, int size, struct event_format *event, struct print_arg *arg) { struct pevent_function_handler *func_handle = arg->func.func; struct pevent_func_params *param; unsigned long long *args; unsigned long long ret; struct print_arg *farg; struct trace_seq str; struct save_str { struct save_str *next; char *str; } *strings = NULL, *string; int i; if (!func_handle->nr_args) { ret = (*func_handle->func)(s, NULL); goto out; } farg = arg->func.args; param = func_handle->params; ret = ULLONG_MAX; args = malloc(sizeof(*args) * func_handle->nr_args); if (!args) goto out; for (i = 0; i < func_handle->nr_args; i++) { switch (param->type) { case PEVENT_FUNC_ARG_INT: case PEVENT_FUNC_ARG_LONG: case PEVENT_FUNC_ARG_PTR: args[i] = eval_num_arg(data, size, event, farg); break; case PEVENT_FUNC_ARG_STRING: trace_seq_init(&str); print_str_arg(&str, data, size, event, "%s", -1, farg); trace_seq_terminate(&str); string = malloc(sizeof(*string)); if (!string) { do_warning_event(event, "%s(%d): malloc str", __func__, __LINE__); goto out_free; } string->next = strings; string->str = strdup(str.buffer); if (!string->str) { free(string); do_warning_event(event, "%s(%d): malloc str", __func__, __LINE__); goto out_free; } args[i] = (uintptr_t)string->str; strings = string; trace_seq_destroy(&str); break; default: /* * Something went totally wrong, this is not * an input error, something in this code broke. */ do_warning_event(event, "Unexpected end of arguments\n"); goto out_free; } farg = farg->next; param = param->next; } ret = (*func_handle->func)(s, args); out_free: free(args); while (strings) { string = strings; strings = string->next; free(string->str); free(string); } out: /* TBD : handle return type here */ return ret; } static void free_args(struct print_arg *args) { struct print_arg *next; while (args) { next = args->next; free_arg(args); args = next; } } static struct print_arg *make_bprint_args(char *fmt, void *data, int size, struct event_format *event) { struct pevent *pevent = event->pevent; struct format_field *field, *ip_field; struct print_arg *args, *arg, **next; unsigned long long ip, val; char *ptr; void *bptr; int vsize; field = pevent->bprint_buf_field; ip_field = pevent->bprint_ip_field; if (!field) { field = pevent_find_field(event, "buf"); if (!field) { do_warning_event(event, "can't find buffer field for binary printk"); return NULL; } ip_field = pevent_find_field(event, "ip"); if (!ip_field) { do_warning_event(event, "can't find ip field for binary printk"); return NULL; } pevent->bprint_buf_field = field; pevent->bprint_ip_field = ip_field; } ip = pevent_read_number(pevent, data + ip_field->offset, ip_field->size); /* * The first arg is the IP pointer. */ args = alloc_arg(); if (!args) { do_warning_event(event, "%s(%d): not enough memory!", __func__, __LINE__); return NULL; } arg = args; arg->next = NULL; next = &arg->next; arg->type = PRINT_ATOM; if (asprintf(&arg->atom.atom, "%lld", ip) < 0) goto out_free; /* skip the first "%pf: " */ for (ptr = fmt + 5, bptr = data + field->offset; bptr < data + size && *ptr; ptr++) { int ls = 0; if (*ptr == '%') { process_again: ptr++; switch (*ptr) { case '%': break; case 'l': ls++; goto process_again; case 'L': ls = 2; goto process_again; case '0' ... '9': goto process_again; case '.': goto process_again; case 'p': ls = 1; /* fall through */ case 'd': case 'u': case 'x': case 'i': switch (ls) { case 0: vsize = 4; break; case 1: vsize = pevent->long_size; break; case 2: vsize = 8; break; default: vsize = ls; /* ? */ break; } /* fall through */ case '*': if (*ptr == '*') vsize = 4; /* the pointers are always 4 bytes aligned */ bptr = (void *)(((unsigned long)bptr + 3) & ~3); val = pevent_read_number(pevent, bptr, vsize); bptr += vsize; arg = alloc_arg(); if (!arg) { do_warning_event(event, "%s(%d): not enough memory!", __func__, __LINE__); goto out_free; } arg->next = NULL; arg->type = PRINT_ATOM; if (asprintf(&arg->atom.atom, "%lld", val) < 0) { free(arg); goto out_free; } *next = arg; next = &arg->next; /* * The '*' case means that an arg is used as the length. * We need to continue to figure out for what. */ if (*ptr == '*') goto process_again; break; case 's': arg = alloc_arg(); if (!arg) { do_warning_event(event, "%s(%d): not enough memory!", __func__, __LINE__); goto out_free; } arg->next = NULL; arg->type = PRINT_BSTRING; arg->string.string = strdup(bptr); if (!arg->string.string) goto out_free; bptr += strlen(bptr) + 1; *next = arg; next = &arg->next; default: break; } } } return args; out_free: free_args(args); return NULL; } static char * get_bprint_format(void *data, int size __maybe_unused, struct event_format *event) { struct pevent *pevent = event->pevent; unsigned long long addr; struct format_field *field; struct printk_map *printk; char *format; field = pevent->bprint_fmt_field; if (!field) { field = pevent_find_field(event, "fmt"); if (!field) { do_warning_event(event, "can't find format field for binary printk"); return NULL; } pevent->bprint_fmt_field = field; } addr = pevent_read_number(pevent, data + field->offset, field->size); printk = find_printk(pevent, addr); if (!printk) { if (asprintf(&format, "%%pf: (NO FORMAT FOUND at %llx)\n", addr) < 0) return NULL; return format; } if (asprintf(&format, "%s: %s", "%pf", printk->printk) < 0) return NULL; return format; } static void print_mac_arg(struct trace_seq *s, int mac, void *data, int size, struct event_format *event, struct print_arg *arg) { unsigned char *buf; const char *fmt = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x"; if (arg->type == PRINT_FUNC) { process_defined_func(s, data, size, event, arg); return; } if (arg->type != PRINT_FIELD) { trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d", arg->type); return; } if (mac == 'm') fmt = "%.2x%.2x%.2x%.2x%.2x%.2x"; if (!arg->field.field) { arg->field.field = pevent_find_any_field(event, arg->field.name); if (!arg->field.field) { do_warning_event(event, "%s: field %s not found", __func__, arg->field.name); return; } } if (arg->field.field->size != 6) { trace_seq_printf(s, "INVALIDMAC"); return; } buf = data + arg->field.field->offset; trace_seq_printf(s, fmt, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]); } static int is_printable_array(char *p, unsigned int len) { unsigned int i; for (i = 0; i < len && p[i]; i++) if (!isprint(p[i]) && !isspace(p[i])) return 0; return 1; } static void print_event_fields(struct trace_seq *s, void *data, int size __maybe_unused, struct event_format *event) { struct format_field *field; unsigned long long val; unsigned int offset, len, i; field = event->format.fields; while (field) { trace_seq_printf(s, " %s=", field->name); if (field->flags & FIELD_IS_ARRAY) { offset = field->offset; len = field->size; if (field->flags & FIELD_IS_DYNAMIC) { val = pevent_read_number(event->pevent, data + offset, len); offset = val; len = offset >> 16; offset &= 0xffff; } if (field->flags & FIELD_IS_STRING && is_printable_array(data + offset, len)) { trace_seq_printf(s, "%s", (char *)data + offset); } else { trace_seq_puts(s, "ARRAY["); for (i = 0; i < len; i++) { if (i) trace_seq_puts(s, ", "); trace_seq_printf(s, "%02x", *((unsigned char *)data + offset + i)); } trace_seq_putc(s, ']'); field->flags &= ~FIELD_IS_STRING; } } else { val = pevent_read_number(event->pevent, data + field->offset, field->size); if (field->flags & FIELD_IS_POINTER) { trace_seq_printf(s, "0x%llx", val); } else if (field->flags & FIELD_IS_SIGNED) { switch (field->size) { case 4: /* * If field is long then print it in hex. * A long usually stores pointers. */ if (field->flags & FIELD_IS_LONG) trace_seq_printf(s, "0x%x", (int)val); else trace_seq_printf(s, "%d", (int)val); break; case 2: trace_seq_printf(s, "%2d", (short)val); break; case 1: trace_seq_printf(s, "%1d", (char)val); break; default: trace_seq_printf(s, "%lld", val); } } else { if (field->flags & FIELD_IS_LONG) trace_seq_printf(s, "0x%llx", val); else trace_seq_printf(s, "%llu", val); } } field = field->next; } } static void pretty_print(struct trace_seq *s, void *data, int size, struct event_format *event) { struct pevent *pevent = event->pevent; struct print_fmt *print_fmt = &event->print_fmt; struct print_arg *arg = print_fmt->args; struct print_arg *args = NULL; const char *ptr = print_fmt->format; unsigned long long val; struct func_map *func; const char *saveptr; struct trace_seq p; char *bprint_fmt = NULL; char format[32]; int show_func; int len_as_arg; int len_arg = 0; int len; int ls; if (event->flags & EVENT_FL_FAILED) { trace_seq_printf(s, "[FAILED TO PARSE]"); print_event_fields(s, data, size, event); return; } if (event->flags & EVENT_FL_ISBPRINT) { bprint_fmt = get_bprint_format(data, size, event); args = make_bprint_args(bprint_fmt, data, size, event); arg = args; ptr = bprint_fmt; } for (; *ptr; ptr++) { ls = 0; if (*ptr == '\\') { ptr++; switch (*ptr) { case 'n': trace_seq_putc(s, '\n'); break; case 't': trace_seq_putc(s, '\t'); break; case 'r': trace_seq_putc(s, '\r'); break; case '\\': trace_seq_putc(s, '\\'); break; default: trace_seq_putc(s, *ptr); break; } } else if (*ptr == '%') { saveptr = ptr; show_func = 0; len_as_arg = 0; cont_process: ptr++; switch (*ptr) { case '%': trace_seq_putc(s, '%'); break; case '#': /* FIXME: need to handle properly */ goto cont_process; case 'h': ls--; goto cont_process; case 'l': ls++; goto cont_process; case 'L': ls = 2; goto cont_process; case '*': /* The argument is the length. */ if (!arg) { do_warning_event(event, "no argument match"); event->flags |= EVENT_FL_FAILED; goto out_failed; } len_arg = eval_num_arg(data, size, event, arg); len_as_arg = 1; arg = arg->next; goto cont_process; case '.': case 'z': case 'Z': case '0' ... '9': goto cont_process; case 'p': if (pevent->long_size == 4) ls = 1; else ls = 2; if (*(ptr+1) == 'F' || *(ptr+1) == 'f') { ptr++; show_func = *ptr; } else if (*(ptr+1) == 'M' || *(ptr+1) == 'm') { print_mac_arg(s, *(ptr+1), data, size, event, arg); ptr++; arg = arg->next; break; } /* fall through */ case 'd': case 'i': case 'x': case 'X': case 'u': if (!arg) { do_warning_event(event, "no argument match"); event->flags |= EVENT_FL_FAILED; goto out_failed; } len = ((unsigned long)ptr + 1) - (unsigned long)saveptr; /* should never happen */ if (len > 31) { do_warning_event(event, "bad format!"); event->flags |= EVENT_FL_FAILED; len = 31; } memcpy(format, saveptr, len); format[len] = 0; val = eval_num_arg(data, size, event, arg); arg = arg->next; if (show_func) { func = find_func(pevent, val); if (func) { trace_seq_puts(s, func->func); if (show_func == 'F') trace_seq_printf(s, "+0x%llx", val - func->addr); break; } } if (pevent->long_size == 8 && ls && sizeof(long) != 8) { char *p; ls = 2; /* make %l into %ll */ p = strchr(format, 'l'); if (p) memmove(p+1, p, strlen(p)+1); else if (strcmp(format, "%p") == 0) strcpy(format, "0x%llx"); } switch (ls) { case -2: if (len_as_arg) trace_seq_printf(s, format, len_arg, (char)val); else trace_seq_printf(s, format, (char)val); break; case -1: if (len_as_arg) trace_seq_printf(s, format, len_arg, (short)val); else trace_seq_printf(s, format, (short)val); break; case 0: if (len_as_arg) trace_seq_printf(s, format, len_arg, (int)val); else trace_seq_printf(s, format, (int)val); break; case 1: if (len_as_arg) trace_seq_printf(s, format, len_arg, (long)val); else trace_seq_printf(s, format, (long)val); break; case 2: if (len_as_arg) trace_seq_printf(s, format, len_arg, (long long)val); else trace_seq_printf(s, format, (long long)val); break; default: do_warning_event(event, "bad count (%d)", ls); event->flags |= EVENT_FL_FAILED; } break; case 's': if (!arg) { do_warning_event(event, "no matching argument"); event->flags |= EVENT_FL_FAILED; goto out_failed; } len = ((unsigned long)ptr + 1) - (unsigned long)saveptr; /* should never happen */ if (len > 31) { do_warning_event(event, "bad format!"); event->flags |= EVENT_FL_FAILED; len = 31; } memcpy(format, saveptr, len); format[len] = 0; if (!len_as_arg) len_arg = -1; /* Use helper trace_seq */ trace_seq_init(&p); print_str_arg(&p, data, size, event, format, len_arg, arg); trace_seq_terminate(&p); trace_seq_puts(s, p.buffer); trace_seq_destroy(&p); arg = arg->next; break; default: trace_seq_printf(s, ">%c<", *ptr); } } else trace_seq_putc(s, *ptr); } if (event->flags & EVENT_FL_FAILED) { out_failed: trace_seq_printf(s, "[FAILED TO PARSE]"); } if (args) { free_args(args); free(bprint_fmt); } } /** * pevent_data_lat_fmt - parse the data for the latency format * @pevent: a handle to the pevent * @s: the trace_seq to write to * @record: the record to read from * * This parses out the Latency format (interrupts disabled, * need rescheduling, in hard/soft interrupt, preempt count * and lock depth) and places it into the trace_seq. */ void pevent_data_lat_fmt(struct pevent *pevent, struct trace_seq *s, struct pevent_record *record) { static int check_lock_depth = 1; static int check_migrate_disable = 1; static int lock_depth_exists; static int migrate_disable_exists; unsigned int lat_flags; unsigned int pc; int lock_depth = -1; int migrate_disable = 0; int hardirq; int softirq; void *data = record->data; lat_flags = parse_common_flags(pevent, data); pc = parse_common_pc(pevent, data); /* lock_depth may not always exist */ if (lock_depth_exists) lock_depth = parse_common_lock_depth(pevent, data); else if (check_lock_depth) { lock_depth = parse_common_lock_depth(pevent, data); if (lock_depth < 0) check_lock_depth = 0; else lock_depth_exists = 1; } /* migrate_disable may not always exist */ if (migrate_disable_exists) migrate_disable = parse_common_migrate_disable(pevent, data); else if (check_migrate_disable) { migrate_disable = parse_common_migrate_disable(pevent, data); if (migrate_disable < 0) check_migrate_disable = 0; else migrate_disable_exists = 1; } hardirq = lat_flags & TRACE_FLAG_HARDIRQ; softirq = lat_flags & TRACE_FLAG_SOFTIRQ; trace_seq_printf(s, "%c%c%c", (lat_flags & TRACE_FLAG_IRQS_OFF) ? 'd' : (lat_flags & TRACE_FLAG_IRQS_NOSUPPORT) ? 'X' : '.', (lat_flags & TRACE_FLAG_NEED_RESCHED) ? 'N' : '.', (hardirq && softirq) ? 'H' : hardirq ? 'h' : softirq ? 's' : '.'); if (pc) trace_seq_printf(s, "%x", pc); else trace_seq_putc(s, '.'); if (migrate_disable_exists) { if (migrate_disable < 0) trace_seq_putc(s, '.'); else trace_seq_printf(s, "%d", migrate_disable); } if (lock_depth_exists) { if (lock_depth < 0) trace_seq_putc(s, '.'); else trace_seq_printf(s, "%d", lock_depth); } trace_seq_terminate(s); } /** * pevent_data_type - parse out the given event type * @pevent: a handle to the pevent * @rec: the record to read from * * This returns the event id from the @rec. */ int pevent_data_type(struct pevent *pevent, struct pevent_record *rec) { return trace_parse_common_type(pevent, rec->data); } /** * pevent_data_event_from_type - find the event by a given type * @pevent: a handle to the pevent * @type: the type of the event. * * This returns the event form a given @type; */ struct event_format *pevent_data_event_from_type(struct pevent *pevent, int type) { return pevent_find_event(pevent, type); } /** * pevent_data_pid - parse the PID from raw data * @pevent: a handle to the pevent * @rec: the record to parse * * This returns the PID from a raw data. */ int pevent_data_pid(struct pevent *pevent, struct pevent_record *rec) { return parse_common_pid(pevent, rec->data); } /** * pevent_data_comm_from_pid - return the command line from PID * @pevent: a handle to the pevent * @pid: the PID of the task to search for * * This returns a pointer to the command line that has the given * @pid. */ const char *pevent_data_comm_from_pid(struct pevent *pevent, int pid) { const char *comm; comm = find_cmdline(pevent, pid); return comm; } /** * pevent_data_comm_from_pid - parse the data into the print format * @s: the trace_seq to write to * @event: the handle to the event * @record: the record to read from * * This parses the raw @data using the given @event information and * writes the print format into the trace_seq. */ void pevent_event_info(struct trace_seq *s, struct event_format *event, struct pevent_record *record) { int print_pretty = 1; if (event->pevent->print_raw || (event->flags & EVENT_FL_PRINTRAW)) print_event_fields(s, record->data, record->size, event); else { if (event->handler && !(event->flags & EVENT_FL_NOHANDLE)) print_pretty = event->handler(s, record, event, event->context); if (print_pretty) pretty_print(s, record->data, record->size, event); } trace_seq_terminate(s); } static bool is_timestamp_in_us(char *trace_clock, bool use_trace_clock) { if (!use_trace_clock) return true; if (!strcmp(trace_clock, "local") || !strcmp(trace_clock, "global") || !strcmp(trace_clock, "uptime") || !strcmp(trace_clock, "perf")) return true; /* trace_clock is setting in tsc or counter mode */ return false; } void pevent_print_event(struct pevent *pevent, struct trace_seq *s, struct pevent_record *record, bool use_trace_clock) { static const char *spaces = " "; /* 20 spaces */ struct event_format *event; unsigned long secs; unsigned long usecs; unsigned long nsecs; const char *comm; void *data = record->data; int type; int pid; int len; int p; bool use_usec_format; use_usec_format = is_timestamp_in_us(pevent->trace_clock, use_trace_clock); if (use_usec_format) { secs = record->ts / NSECS_PER_SEC; nsecs = record->ts - secs * NSECS_PER_SEC; } if (record->size < 0) { do_warning("ug! negative record size %d", record->size); return; } type = trace_parse_common_type(pevent, data); event = pevent_find_event(pevent, type); if (!event) { do_warning("ug! no event found for type %d", type); return; } pid = parse_common_pid(pevent, data); comm = find_cmdline(pevent, pid); if (pevent->latency_format) { trace_seq_printf(s, "%8.8s-%-5d %3d", comm, pid, record->cpu); pevent_data_lat_fmt(pevent, s, record); } else trace_seq_printf(s, "%16s-%-5d [%03d]", comm, pid, record->cpu); if (use_usec_format) { if (pevent->flags & PEVENT_NSEC_OUTPUT) { usecs = nsecs; p = 9; } else { usecs = (nsecs + 500) / NSECS_PER_USEC; p = 6; } trace_seq_printf(s, " %5lu.%0*lu: %s: ", secs, p, usecs, event->name); } else trace_seq_printf(s, " %12llu: %s: ", record->ts, event->name); /* Space out the event names evenly. */ len = strlen(event->name); if (len < 20) trace_seq_printf(s, "%.*s", 20 - len, spaces); pevent_event_info(s, event, record); } static int events_id_cmp(const void *a, const void *b) { struct event_format * const * ea = a; struct event_format * const * eb = b; if ((*ea)->id < (*eb)->id) return -1; if ((*ea)->id > (*eb)->id) return 1; return 0; } static int events_name_cmp(const void *a, const void *b) { struct event_format * const * ea = a; struct event_format * const * eb = b; int res; res = strcmp((*ea)->name, (*eb)->name); if (res) return res; res = strcmp((*ea)->system, (*eb)->system); if (res) return res; return events_id_cmp(a, b); } static int events_system_cmp(const void *a, const void *b) { struct event_format * const * ea = a; struct event_format * const * eb = b; int res; res = strcmp((*ea)->system, (*eb)->system); if (res) return res; res = strcmp((*ea)->name, (*eb)->name); if (res) return res; return events_id_cmp(a, b); } struct event_format **pevent_list_events(struct pevent *pevent, enum event_sort_type sort_type) { struct event_format **events; int (*sort)(const void *a, const void *b); events = pevent->sort_events; if (events && pevent->last_type == sort_type) return events; if (!events) { events = malloc(sizeof(*events) * (pevent->nr_events + 1)); if (!events) return NULL; memcpy(events, pevent->events, sizeof(*events) * pevent->nr_events); events[pevent->nr_events] = NULL; pevent->sort_events = events; /* the internal events are sorted by id */ if (sort_type == EVENT_SORT_ID) { pevent->last_type = sort_type; return events; } } switch (sort_type) { case EVENT_SORT_ID: sort = events_id_cmp; break; case EVENT_SORT_NAME: sort = events_name_cmp; break; case EVENT_SORT_SYSTEM: sort = events_system_cmp; break; default: return events; } qsort(events, pevent->nr_events, sizeof(*events), sort); pevent->last_type = sort_type; return events; } static struct format_field ** get_event_fields(const char *type, const char *name, int count, struct format_field *list) { struct format_field **fields; struct format_field *field; int i = 0; fields = malloc(sizeof(*fields) * (count + 1)); if (!fields) return NULL; for (field = list; field; field = field->next) { fields[i++] = field; if (i == count + 1) { do_warning("event %s has more %s fields than specified", name, type); i--; break; } } if (i != count) do_warning("event %s has less %s fields than specified", name, type); fields[i] = NULL; return fields; } /** * pevent_event_common_fields - return a list of common fields for an event * @event: the event to return the common fields of. * * Returns an allocated array of fields. The last item in the array is NULL. * The array must be freed with free(). */ struct format_field **pevent_event_common_fields(struct event_format *event) { return get_event_fields("common", event->name, event->format.nr_common, event->format.common_fields); } /** * pevent_event_fields - return a list of event specific fields for an event * @event: the event to return the fields of. * * Returns an allocated array of fields. The last item in the array is NULL. * The array must be freed with free(). */ struct format_field **pevent_event_fields(struct event_format *event) { return get_event_fields("event", event->name, event->format.nr_fields, event->format.fields); } static void print_fields(struct trace_seq *s, struct print_flag_sym *field) { trace_seq_printf(s, "{ %s, %s }", field->value, field->str); if (field->next) { trace_seq_puts(s, ", "); print_fields(s, field->next); } } /* for debugging */ static void print_args(struct print_arg *args) { int print_paren = 1; struct trace_seq s; switch (args->type) { case PRINT_NULL: printf("null"); break; case PRINT_ATOM: printf("%s", args->atom.atom); break; case PRINT_FIELD: printf("REC->%s", args->field.name); break; case PRINT_FLAGS: printf("__print_flags("); print_args(args->flags.field); printf(", %s, ", args->flags.delim); trace_seq_init(&s); print_fields(&s, args->flags.flags); trace_seq_do_printf(&s); trace_seq_destroy(&s); printf(")"); break; case PRINT_SYMBOL: printf("__print_symbolic("); print_args(args->symbol.field); printf(", "); trace_seq_init(&s); print_fields(&s, args->symbol.symbols); trace_seq_do_printf(&s); trace_seq_destroy(&s); printf(")"); break; case PRINT_HEX: printf("__print_hex("); print_args(args->hex.field); printf(", "); print_args(args->hex.size); printf(")"); break; case PRINT_STRING: case PRINT_BSTRING: printf("__get_str(%s)", args->string.string); break; case PRINT_BITMASK: printf("__get_bitmask(%s)", args->bitmask.bitmask); break; case PRINT_TYPE: printf("(%s)", args->typecast.type); print_args(args->typecast.item); break; case PRINT_OP: if (strcmp(args->op.op, ":") == 0) print_paren = 0; if (print_paren) printf("("); print_args(args->op.left); printf(" %s ", args->op.op); print_args(args->op.right); if (print_paren) printf(")"); break; default: /* we should warn... */ return; } if (args->next) { printf("\n"); print_args(args->next); } } static void parse_header_field(const char *field, int *offset, int *size, int mandatory) { unsigned long long save_input_buf_ptr; unsigned long long save_input_buf_siz; char *token; int type; save_input_buf_ptr = input_buf_ptr; save_input_buf_siz = input_buf_siz; if (read_expected(EVENT_ITEM, "field") < 0) return; if (read_expected(EVENT_OP, ":") < 0) return; /* type */ if (read_expect_type(EVENT_ITEM, &token) < 0) goto fail; free_token(token); /* * If this is not a mandatory field, then test it first. */ if (mandatory) { if (read_expected(EVENT_ITEM, field) < 0) return; } else { if (read_expect_type(EVENT_ITEM, &token) < 0) goto fail; if (strcmp(token, field) != 0) goto discard; free_token(token); } if (read_expected(EVENT_OP, ";") < 0) return; if (read_expected(EVENT_ITEM, "offset") < 0) return; if (read_expected(EVENT_OP, ":") < 0) return; if (read_expect_type(EVENT_ITEM, &token) < 0) goto fail; *offset = atoi(token); free_token(token); if (read_expected(EVENT_OP, ";") < 0) return; if (read_expected(EVENT_ITEM, "size") < 0) return; if (read_expected(EVENT_OP, ":") < 0) return; if (read_expect_type(EVENT_ITEM, &token) < 0) goto fail; *size = atoi(token); free_token(token); if (read_expected(EVENT_OP, ";") < 0) return; type = read_token(&token); if (type != EVENT_NEWLINE) { /* newer versions of the kernel have a "signed" type */ if (type != EVENT_ITEM) goto fail; if (strcmp(token, "signed") != 0) goto fail; free_token(token); if (read_expected(EVENT_OP, ":") < 0) return; if (read_expect_type(EVENT_ITEM, &token)) goto fail; free_token(token); if (read_expected(EVENT_OP, ";") < 0) return; if (read_expect_type(EVENT_NEWLINE, &token)) goto fail; } fail: free_token(token); return; discard: input_buf_ptr = save_input_buf_ptr; input_buf_siz = save_input_buf_siz; *offset = 0; *size = 0; free_token(token); } /** * pevent_parse_header_page - parse the data stored in the header page * @pevent: the handle to the pevent * @buf: the buffer storing the header page format string * @size: the size of @buf * @long_size: the long size to use if there is no header * * This parses the header page format for information on the * ring buffer used. The @buf should be copied from * * /sys/kernel/debug/tracing/events/header_page */ int pevent_parse_header_page(struct pevent *pevent, char *buf, unsigned long size, int long_size) { int ignore; if (!size) { /* * Old kernels did not have header page info. * Sorry but we just use what we find here in user space. */ pevent->header_page_ts_size = sizeof(long long); pevent->header_page_size_size = long_size; pevent->header_page_data_offset = sizeof(long long) + long_size; pevent->old_format = 1; return -1; } init_input_buf(buf, size); parse_header_field("timestamp", &pevent->header_page_ts_offset, &pevent->header_page_ts_size, 1); parse_header_field("commit", &pevent->header_page_size_offset, &pevent->header_page_size_size, 1); parse_header_field("overwrite", &pevent->header_page_overwrite, &ignore, 0); parse_header_field("data", &pevent->header_page_data_offset, &pevent->header_page_data_size, 1); return 0; } static int event_matches(struct event_format *event, int id, const char *sys_name, const char *event_name) { if (id >= 0 && id != event->id) return 0; if (event_name && (strcmp(event_name, event->name) != 0)) return 0; if (sys_name && (strcmp(sys_name, event->system) != 0)) return 0; return 1; } static void free_handler(struct event_handler *handle) { free((void *)handle->sys_name); free((void *)handle->event_name); free(handle); } static int find_event_handle(struct pevent *pevent, struct event_format *event) { struct event_handler *handle, **next; for (next = &pevent->handlers; *next; next = &(*next)->next) { handle = *next; if (event_matches(event, handle->id, handle->sys_name, handle->event_name)) break; } if (!(*next)) return 0; // pr_stat("overriding event (%d) %s:%s with new print handler", // event->id, event->system, event->name); event->handler = handle->func; event->context = handle->context; *next = handle->next; free_handler(handle); return 1; } /** * __pevent_parse_format - parse the event format * @buf: the buffer storing the event format string * @size: the size of @buf * @sys: the system the event belongs to * * This parses the event format and creates an event structure * to quickly parse raw data for a given event. * * These files currently come from: * * /sys/kernel/debug/tracing/events/.../.../format */ enum pevent_errno __pevent_parse_format(struct event_format **eventp, struct pevent *pevent, const char *buf, unsigned long size, const char *sys) { struct event_format *event; int ret; init_input_buf(buf, size); *eventp = event = alloc_event(); if (!event) return PEVENT_ERRNO__MEM_ALLOC_FAILED; event->name = event_read_name(); if (!event->name) { /* Bad event? */ ret = PEVENT_ERRNO__MEM_ALLOC_FAILED; goto event_alloc_failed; } if (strcmp(sys, "ftrace") == 0) { event->flags |= EVENT_FL_ISFTRACE; if (strcmp(event->name, "bprint") == 0) event->flags |= EVENT_FL_ISBPRINT; } event->id = event_read_id(); if (event->id < 0) { ret = PEVENT_ERRNO__READ_ID_FAILED; /* * This isn't an allocation error actually. * But as the ID is critical, just bail out. */ goto event_alloc_failed; } event->system = strdup(sys); if (!event->system) { ret = PEVENT_ERRNO__MEM_ALLOC_FAILED; goto event_alloc_failed; } /* Add pevent to event so that it can be referenced */ event->pevent = pevent; ret = event_read_format(event); if (ret < 0) { ret = PEVENT_ERRNO__READ_FORMAT_FAILED; goto event_parse_failed; } /* * If the event has an override, don't print warnings if the event * print format fails to parse. */ if (pevent && find_event_handle(pevent, event)) show_warning = 0; ret = event_read_print(event); show_warning = 1; if (ret < 0) { ret = PEVENT_ERRNO__READ_PRINT_FAILED; goto event_parse_failed; } if (!ret && (event->flags & EVENT_FL_ISFTRACE)) { struct format_field *field; struct print_arg *arg, **list; /* old ftrace had no args */ list = &event->print_fmt.args; for (field = event->format.fields; field; field = field->next) { arg = alloc_arg(); if (!arg) { event->flags |= EVENT_FL_FAILED; return PEVENT_ERRNO__OLD_FTRACE_ARG_FAILED; } arg->type = PRINT_FIELD; arg->field.name = strdup(field->name); if (!arg->field.name) { event->flags |= EVENT_FL_FAILED; free_arg(arg); return PEVENT_ERRNO__OLD_FTRACE_ARG_FAILED; } arg->field.field = field; *list = arg; list = &arg->next; } return 0; } return 0; event_parse_failed: event->flags |= EVENT_FL_FAILED; return ret; event_alloc_failed: free(event->system); free(event->name); free(event); *eventp = NULL; return ret; } static enum pevent_errno __pevent_parse_event(struct pevent *pevent, struct event_format **eventp, const char *buf, unsigned long size, const char *sys) { int ret = __pevent_parse_format(eventp, pevent, buf, size, sys); struct event_format *event = *eventp; if (event == NULL) return ret; if (pevent && add_event(pevent, event)) { ret = PEVENT_ERRNO__MEM_ALLOC_FAILED; goto event_add_failed; } #define PRINT_ARGS 0 if (PRINT_ARGS && event->print_fmt.args) print_args(event->print_fmt.args); return 0; event_add_failed: pevent_free_format(event); return ret; } /** * pevent_parse_format - parse the event format * @pevent: the handle to the pevent * @eventp: returned format * @buf: the buffer storing the event format string * @size: the size of @buf * @sys: the system the event belongs to * * This parses the event format and creates an event structure * to quickly parse raw data for a given event. * * These files currently come from: * * /sys/kernel/debug/tracing/events/.../.../format */ enum pevent_errno pevent_parse_format(struct pevent *pevent, struct event_format **eventp, const char *buf, unsigned long size, const char *sys) { return __pevent_parse_event(pevent, eventp, buf, size, sys); } /** * pevent_parse_event - parse the event format * @pevent: the handle to the pevent * @buf: the buffer storing the event format string * @size: the size of @buf * @sys: the system the event belongs to * * This parses the event format and creates an event structure * to quickly parse raw data for a given event. * * These files currently come from: * * /sys/kernel/debug/tracing/events/.../.../format */ enum pevent_errno pevent_parse_event(struct pevent *pevent, const char *buf, unsigned long size, const char *sys) { struct event_format *event = NULL; return __pevent_parse_event(pevent, &event, buf, size, sys); } #undef _PE #define _PE(code, str) str static const char * const pevent_error_str[] = { PEVENT_ERRORS }; #undef _PE int pevent_strerror(struct pevent *pevent __maybe_unused, enum pevent_errno errnum, char *buf, size_t buflen) { int idx; const char *msg; if (errnum >= 0) { msg = strerror_r(errnum, buf, buflen); if (msg != buf) { size_t len = strlen(msg); memcpy(buf, msg, min(buflen - 1, len)); *(buf + min(buflen - 1, len)) = '\0'; } return 0; } if (errnum <= __PEVENT_ERRNO__START || errnum >= __PEVENT_ERRNO__END) return -1; idx = errnum - __PEVENT_ERRNO__START - 1; msg = pevent_error_str[idx]; snprintf(buf, buflen, "%s", msg); return 0; } int get_field_val(struct trace_seq *s, struct format_field *field, const char *name, struct pevent_record *record, unsigned long long *val, int err) { if (!field) { if (err) trace_seq_printf(s, "", name); return -1; } if (pevent_read_number_field(field, record->data, val)) { if (err) trace_seq_printf(s, " %s=INVALID", name); return -1; } return 0; } /** * pevent_get_field_raw - return the raw pointer into the data field * @s: The seq to print to on error * @event: the event that the field is for * @name: The name of the field * @record: The record with the field name. * @len: place to store the field length. * @err: print default error if failed. * * Returns a pointer into record->data of the field and places * the length of the field in @len. * * On failure, it returns NULL. */ void *pevent_get_field_raw(struct trace_seq *s, struct event_format *event, const char *name, struct pevent_record *record, int *len, int err) { struct format_field *field; void *data = record->data; unsigned offset; int dummy; if (!event) return NULL; field = pevent_find_field(event, name); if (!field) { if (err) trace_seq_printf(s, "", name); return NULL; } /* Allow @len to be NULL */ if (!len) len = &dummy; offset = field->offset; if (field->flags & FIELD_IS_DYNAMIC) { offset = pevent_read_number(event->pevent, data + offset, field->size); *len = offset >> 16; offset &= 0xffff; } else *len = field->size; return data + offset; } /** * pevent_get_field_val - find a field and return its value * @s: The seq to print to on error * @event: the event that the field is for * @name: The name of the field * @record: The record with the field name. * @val: place to store the value of the field. * @err: print default error if failed. * * Returns 0 on success -1 on field not found. */ int pevent_get_field_val(struct trace_seq *s, struct event_format *event, const char *name, struct pevent_record *record, unsigned long long *val, int err) { struct format_field *field; if (!event) return -1; field = pevent_find_field(event, name); return get_field_val(s, field, name, record, val, err); } /** * pevent_get_common_field_val - find a common field and return its value * @s: The seq to print to on error * @event: the event that the field is for * @name: The name of the field * @record: The record with the field name. * @val: place to store the value of the field. * @err: print default error if failed. * * Returns 0 on success -1 on field not found. */ int pevent_get_common_field_val(struct trace_seq *s, struct event_format *event, const char *name, struct pevent_record *record, unsigned long long *val, int err) { struct format_field *field; if (!event) return -1; field = pevent_find_common_field(event, name); return get_field_val(s, field, name, record, val, err); } /** * pevent_get_any_field_val - find a any field and return its value * @s: The seq to print to on error * @event: the event that the field is for * @name: The name of the field * @record: The record with the field name. * @val: place to store the value of the field. * @err: print default error if failed. * * Returns 0 on success -1 on field not found. */ int pevent_get_any_field_val(struct trace_seq *s, struct event_format *event, const char *name, struct pevent_record *record, unsigned long long *val, int err) { struct format_field *field; if (!event) return -1; field = pevent_find_any_field(event, name); return get_field_val(s, field, name, record, val, err); } /** * pevent_print_num_field - print a field and a format * @s: The seq to print to * @fmt: The printf format to print the field with. * @event: the event that the field is for * @name: The name of the field * @record: The record with the field name. * @err: print default error if failed. * * Returns: 0 on success, -1 field not found, or 1 if buffer is full. */ int pevent_print_num_field(struct trace_seq *s, const char *fmt, struct event_format *event, const char *name, struct pevent_record *record, int err) { struct format_field *field = pevent_find_field(event, name); unsigned long long val; if (!field) goto failed; if (pevent_read_number_field(field, record->data, &val)) goto failed; return trace_seq_printf(s, fmt, val); failed: if (err) trace_seq_printf(s, "CAN'T FIND FIELD \"%s\"", name); return -1; } /** * pevent_print_func_field - print a field and a format for function pointers * @s: The seq to print to * @fmt: The printf format to print the field with. * @event: the event that the field is for * @name: The name of the field * @record: The record with the field name. * @err: print default error if failed. * * Returns: 0 on success, -1 field not found, or 1 if buffer is full. */ int pevent_print_func_field(struct trace_seq *s, const char *fmt, struct event_format *event, const char *name, struct pevent_record *record, int err) { struct format_field *field = pevent_find_field(event, name); struct pevent *pevent = event->pevent; unsigned long long val; struct func_map *func; char tmp[128]; if (!field) goto failed; if (pevent_read_number_field(field, record->data, &val)) goto failed; func = find_func(pevent, val); if (func) snprintf(tmp, 128, "%s/0x%llx", func->func, func->addr - val); else sprintf(tmp, "0x%08llx", val); return trace_seq_printf(s, fmt, tmp); failed: if (err) trace_seq_printf(s, "CAN'T FIND FIELD \"%s\"", name); return -1; } static void free_func_handle(struct pevent_function_handler *func) { struct pevent_func_params *params; free(func->name); while (func->params) { params = func->params; func->params = params->next; free(params); } free(func); } /** * pevent_register_print_function - register a helper function * @pevent: the handle to the pevent * @func: the function to process the helper function * @ret_type: the return type of the helper function * @name: the name of the helper function * @parameters: A list of enum pevent_func_arg_type * * Some events may have helper functions in the print format arguments. * This allows a plugin to dynamically create a way to process one * of these functions. * * The @parameters is a variable list of pevent_func_arg_type enums that * must end with PEVENT_FUNC_ARG_VOID. */ int pevent_register_print_function(struct pevent *pevent, pevent_func_handler func, enum pevent_func_arg_type ret_type, char *name, ...) { struct pevent_function_handler *func_handle; struct pevent_func_params **next_param; struct pevent_func_params *param; enum pevent_func_arg_type type; va_list ap; int ret; func_handle = find_func_handler(pevent, name); if (func_handle) { /* * This is most like caused by the users own * plugins updating the function. This overrides the * system defaults. */ pr_stat("override of function helper '%s'", name); remove_func_handler(pevent, name); } func_handle = calloc(1, sizeof(*func_handle)); if (!func_handle) { do_warning("Failed to allocate function handler"); return PEVENT_ERRNO__MEM_ALLOC_FAILED; } func_handle->ret_type = ret_type; func_handle->name = strdup(name); func_handle->func = func; if (!func_handle->name) { do_warning("Failed to allocate function name"); free(func_handle); return PEVENT_ERRNO__MEM_ALLOC_FAILED; } next_param = &(func_handle->params); va_start(ap, name); for (;;) { type = va_arg(ap, enum pevent_func_arg_type); if (type == PEVENT_FUNC_ARG_VOID) break; if (type >= PEVENT_FUNC_ARG_MAX_TYPES) { do_warning("Invalid argument type %d", type); ret = PEVENT_ERRNO__INVALID_ARG_TYPE; goto out_free; } param = malloc(sizeof(*param)); if (!param) { do_warning("Failed to allocate function param"); ret = PEVENT_ERRNO__MEM_ALLOC_FAILED; goto out_free; } param->type = type; param->next = NULL; *next_param = param; next_param = &(param->next); func_handle->nr_args++; } va_end(ap); func_handle->next = pevent->func_handlers; pevent->func_handlers = func_handle; return 0; out_free: va_end(ap); free_func_handle(func_handle); return ret; } /** * pevent_unregister_print_function - unregister a helper function * @pevent: the handle to the pevent * @func: the function to process the helper function * @name: the name of the helper function * * This function removes existing print handler for function @name. * * Returns 0 if the handler was removed successfully, -1 otherwise. */ int pevent_unregister_print_function(struct pevent *pevent, pevent_func_handler func, char *name) { struct pevent_function_handler *func_handle; func_handle = find_func_handler(pevent, name); if (func_handle && func_handle->func == func) { remove_func_handler(pevent, name); return 0; } return -1; } static struct event_format *pevent_search_event(struct pevent *pevent, int id, const char *sys_name, const char *event_name) { struct event_format *event; if (id >= 0) { /* search by id */ event = pevent_find_event(pevent, id); if (!event) return NULL; if (event_name && (strcmp(event_name, event->name) != 0)) return NULL; if (sys_name && (strcmp(sys_name, event->system) != 0)) return NULL; } else { event = pevent_find_event_by_name(pevent, sys_name, event_name); if (!event) return NULL; } return event; } /** * pevent_register_event_handler - register a way to parse an event * @pevent: the handle to the pevent * @id: the id of the event to register * @sys_name: the system name the event belongs to * @event_name: the name of the event * @func: the function to call to parse the event information * @context: the data to be passed to @func * * This function allows a developer to override the parsing of * a given event. If for some reason the default print format * is not sufficient, this function will register a function * for an event to be used to parse the data instead. * * If @id is >= 0, then it is used to find the event. * else @sys_name and @event_name are used. */ int pevent_register_event_handler(struct pevent *pevent, int id, const char *sys_name, const char *event_name, pevent_event_handler_func func, void *context) { struct event_format *event; struct event_handler *handle; event = pevent_search_event(pevent, id, sys_name, event_name); if (event == NULL) goto not_found; // pr_stat("overriding event (%d) %s:%s with new print handler", // event->id, event->system, event->name); event->handler = func; event->context = context; return 0; not_found: /* Save for later use. */ handle = calloc(1, sizeof(*handle)); if (!handle) { do_warning("Failed to allocate event handler"); return PEVENT_ERRNO__MEM_ALLOC_FAILED; } handle->id = id; if (event_name) handle->event_name = strdup(event_name); if (sys_name) handle->sys_name = strdup(sys_name); if ((event_name && !handle->event_name) || (sys_name && !handle->sys_name)) { do_warning("Failed to allocate event/sys name"); free((void *)handle->event_name); free((void *)handle->sys_name); free(handle); return PEVENT_ERRNO__MEM_ALLOC_FAILED; } handle->func = func; handle->next = pevent->handlers; pevent->handlers = handle; handle->context = context; return -1; } static int handle_matches(struct event_handler *handler, int id, const char *sys_name, const char *event_name, pevent_event_handler_func func, void *context) { if (id >= 0 && id != handler->id) return 0; if (event_name && (strcmp(event_name, handler->event_name) != 0)) return 0; if (sys_name && (strcmp(sys_name, handler->sys_name) != 0)) return 0; if (func != handler->func || context != handler->context) return 0; return 1; } /** * pevent_unregister_event_handler - unregister an existing event handler * @pevent: the handle to the pevent * @id: the id of the event to unregister * @sys_name: the system name the handler belongs to * @event_name: the name of the event handler * @func: the function to call to parse the event information * @context: the data to be passed to @func * * This function removes existing event handler (parser). * * If @id is >= 0, then it is used to find the event. * else @sys_name and @event_name are used. * * Returns 0 if handler was removed successfully, -1 if event was not found. */ int pevent_unregister_event_handler(struct pevent *pevent, int id, const char *sys_name, const char *event_name, pevent_event_handler_func func, void *context) { struct event_format *event; struct event_handler *handle; struct event_handler **next; event = pevent_search_event(pevent, id, sys_name, event_name); if (event == NULL) goto not_found; if (event->handler == func && event->context == context) { pr_stat("removing override handler for event (%d) %s:%s. Going back to default handler.", event->id, event->system, event->name); event->handler = NULL; event->context = NULL; return 0; } not_found: for (next = &pevent->handlers; *next; next = &(*next)->next) { handle = *next; if (handle_matches(handle, id, sys_name, event_name, func, context)) break; } if (!(*next)) return -1; *next = handle->next; free_handler(handle); return 0; } /** * pevent_alloc - create a pevent handle */ struct pevent *pevent_alloc(void) { struct pevent *pevent = calloc(1, sizeof(*pevent)); if (pevent) pevent->ref_count = 1; return pevent; } void pevent_ref(struct pevent *pevent) { pevent->ref_count++; } static void free_format_fields(struct format_field *field) { struct format_field *next; while (field) { next = field->next; free(field->type); free(field->name); free(field); field = next; } } static void free_formats(struct format *format) { free_format_fields(format->common_fields); free_format_fields(format->fields); } void pevent_free_format(struct event_format *event) { free(event->name); free(event->system); free_formats(&event->format); free(event->print_fmt.format); free_args(event->print_fmt.args); free(event); } /** * pevent_free - free a pevent handle * @pevent: the pevent handle to free */ void pevent_free(struct pevent *pevent) { struct cmdline_list *cmdlist, *cmdnext; struct func_list *funclist, *funcnext; struct printk_list *printklist, *printknext; struct pevent_function_handler *func_handler; struct event_handler *handle; int i; if (!pevent) return; cmdlist = pevent->cmdlist; funclist = pevent->funclist; printklist = pevent->printklist; pevent->ref_count--; if (pevent->ref_count) return; if (pevent->cmdlines) { for (i = 0; i < pevent->cmdline_count; i++) free(pevent->cmdlines[i].comm); free(pevent->cmdlines); } while (cmdlist) { cmdnext = cmdlist->next; free(cmdlist->comm); free(cmdlist); cmdlist = cmdnext; } if (pevent->func_map) { for (i = 0; i < (int)pevent->func_count; i++) { free(pevent->func_map[i].func); free(pevent->func_map[i].mod); } free(pevent->func_map); } while (funclist) { funcnext = funclist->next; free(funclist->func); free(funclist->mod); free(funclist); funclist = funcnext; } while (pevent->func_handlers) { func_handler = pevent->func_handlers; pevent->func_handlers = func_handler->next; free_func_handle(func_handler); } if (pevent->printk_map) { for (i = 0; i < (int)pevent->printk_count; i++) free(pevent->printk_map[i].printk); free(pevent->printk_map); } while (printklist) { printknext = printklist->next; free(printklist->printk); free(printklist); printklist = printknext; } for (i = 0; i < pevent->nr_events; i++) pevent_free_format(pevent->events[i]); while (pevent->handlers) { handle = pevent->handlers; pevent->handlers = handle->next; free_handler(handle); } free(pevent->events); free(pevent->sort_events); free(pevent); } void pevent_unref(struct pevent *pevent) { pevent_free(pevent); } uftrace-0.9.4/libtraceevent/event-parse.h000066400000000000000000000576231362052523300204240ustar00rootroot00000000000000/* * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License (not later!) * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #ifndef _PARSE_EVENTS_H #define _PARSE_EVENTS_H #include #include #include #include #ifndef __maybe_unused #define __maybe_unused __attribute__((unused)) #endif /* ----------------------- trace_seq ----------------------- */ #ifndef TRACE_SEQ_BUF_SIZE #define TRACE_SEQ_BUF_SIZE 4096 #endif #ifndef DEBUG_RECORD #define DEBUG_RECORD 0 #endif struct pevent_record { unsigned long long ts; unsigned long long offset; long long missed_events; /* buffer dropped events before */ int record_size; /* size of binary record */ int size; /* size of data */ void *data; int cpu; int ref_count; int locked; /* Do not free, even if ref_count is zero */ void *priv; #if DEBUG_RECORD struct pevent_record *prev; struct pevent_record *next; long alloc_addr; #endif }; enum trace_seq_fail { TRACE_SEQ__GOOD, TRACE_SEQ__BUFFER_POISONED, TRACE_SEQ__MEM_ALLOC_FAILED, }; /* * Trace sequences are used to allow a function to call several other functions * to create a string of data to use (up to a max of PAGE_SIZE). */ struct trace_seq { char *buffer; unsigned int buffer_size; unsigned int len; unsigned int readpos; enum trace_seq_fail state; }; void trace_seq_init(struct trace_seq *s); void trace_seq_reset(struct trace_seq *s); void trace_seq_destroy(struct trace_seq *s); extern int trace_seq_printf(struct trace_seq *s, const char *fmt, ...) __attribute__ ((format (printf, 2, 3))); extern int trace_seq_vprintf(struct trace_seq *s, const char *fmt, va_list args) __attribute__ ((format (printf, 2, 0))); extern int trace_seq_puts(struct trace_seq *s, const char *str); extern int trace_seq_putc(struct trace_seq *s, unsigned char c); extern void trace_seq_terminate(struct trace_seq *s); extern int trace_seq_do_printf(struct trace_seq *s); /* ----------------------- pevent ----------------------- */ struct pevent; struct event_format; typedef int (*pevent_event_handler_func)(struct trace_seq *s, struct pevent_record *record, struct event_format *event, void *context); typedef int (*pevent_plugin_load_func)(struct pevent *pevent); typedef int (*pevent_plugin_unload_func)(struct pevent *pevent); struct pevent_plugin_option { struct pevent_plugin_option *next; void *handle; char *file; char *name; char *plugin_alias; char *description; char *value; void *priv; int set; }; /* * Plugin hooks that can be called: * * PEVENT_PLUGIN_LOADER: (required) * The function name to initialized the plugin. * * int PEVENT_PLUGIN_LOADER(struct pevent *pevent) * * PEVENT_PLUGIN_UNLOADER: (optional) * The function called just before unloading * * int PEVENT_PLUGIN_UNLOADER(struct pevent *pevent) * * PEVENT_PLUGIN_OPTIONS: (optional) * Plugin options that can be set before loading * * struct pevent_plugin_option PEVENT_PLUGIN_OPTIONS[] = { * { * .name = "option-name", * .plugin_alias = "overide-file-name", (optional) * .description = "description of option to show users", * }, * { * .name = NULL, * }, * }; * * Array must end with .name = NULL; * * * .plugin_alias is used to give a shorter name to access * the vairable. Useful if a plugin handles more than one event. * * PEVENT_PLUGIN_ALIAS: (optional) * The name to use for finding options (uses filename if not defined) */ #define PEVENT_PLUGIN_LOADER pevent_plugin_loader #define PEVENT_PLUGIN_UNLOADER pevent_plugin_unloader #define PEVENT_PLUGIN_OPTIONS pevent_plugin_options #define PEVENT_PLUGIN_ALIAS pevent_plugin_alias #define _MAKE_STR(x) #x #define MAKE_STR(x) _MAKE_STR(x) #define PEVENT_PLUGIN_LOADER_NAME MAKE_STR(PEVENT_PLUGIN_LOADER) #define PEVENT_PLUGIN_UNLOADER_NAME MAKE_STR(PEVENT_PLUGIN_UNLOADER) #define PEVENT_PLUGIN_OPTIONS_NAME MAKE_STR(PEVENT_PLUGIN_OPTIONS) #define PEVENT_PLUGIN_ALIAS_NAME MAKE_STR(PEVENT_PLUGIN_ALIAS) #define NSECS_PER_SEC 1000000000ULL #define NSECS_PER_USEC 1000ULL enum format_flags { FIELD_IS_ARRAY = 1, FIELD_IS_POINTER = 2, FIELD_IS_SIGNED = 4, FIELD_IS_STRING = 8, FIELD_IS_DYNAMIC = 16, FIELD_IS_LONG = 32, FIELD_IS_FLAG = 64, FIELD_IS_SYMBOLIC = 128, }; struct format_field { struct format_field *next; struct event_format *event; char *type; char *name; int offset; int size; unsigned int arraylen; unsigned int elementsize; unsigned long flags; }; struct format { int nr_common; int nr_fields; struct format_field *common_fields; struct format_field *fields; }; struct print_arg_atom { char *atom; }; struct print_arg_string { char *string; int offset; }; struct print_arg_bitmask { char *bitmask; int offset; }; struct print_arg_field { char *name; struct format_field *field; }; struct print_flag_sym { struct print_flag_sym *next; char *value; char *str; }; struct print_arg_typecast { char *type; struct print_arg *item; }; struct print_arg_flags { struct print_arg *field; char *delim; struct print_flag_sym *flags; }; struct print_arg_symbol { struct print_arg *field; struct print_flag_sym *symbols; }; struct print_arg_hex { struct print_arg *field; struct print_arg *size; }; struct print_arg_dynarray { struct format_field *field; struct print_arg *index; }; struct print_arg; struct print_arg_op { char *op; int prio; struct print_arg *left; struct print_arg *right; }; struct pevent_function_handler; struct print_arg_func { struct pevent_function_handler *func; struct print_arg *args; }; enum print_arg_type { PRINT_NULL, PRINT_ATOM, PRINT_FIELD, PRINT_FLAGS, PRINT_SYMBOL, PRINT_HEX, PRINT_TYPE, PRINT_STRING, PRINT_BSTRING, PRINT_DYNAMIC_ARRAY, PRINT_OP, PRINT_FUNC, PRINT_BITMASK, }; struct print_arg { struct print_arg *next; enum print_arg_type type; union { struct print_arg_atom atom; struct print_arg_field field; struct print_arg_typecast typecast; struct print_arg_flags flags; struct print_arg_symbol symbol; struct print_arg_hex hex; struct print_arg_func func; struct print_arg_string string; struct print_arg_bitmask bitmask; struct print_arg_op op; struct print_arg_dynarray dynarray; }; }; struct print_fmt { char *format; struct print_arg *args; }; struct event_format { struct pevent *pevent; char *name; int id; int flags; struct format format; struct print_fmt print_fmt; char *system; pevent_event_handler_func handler; void *context; }; enum { EVENT_FL_ISFTRACE = 0x01, EVENT_FL_ISPRINT = 0x02, EVENT_FL_ISBPRINT = 0x04, EVENT_FL_ISFUNCENT = 0x10, EVENT_FL_ISFUNCRET = 0x20, EVENT_FL_NOHANDLE = 0x40, EVENT_FL_PRINTRAW = 0x80, EVENT_FL_FAILED = 0x80000000 }; enum event_sort_type { EVENT_SORT_ID, EVENT_SORT_NAME, EVENT_SORT_SYSTEM, }; enum event_type { EVENT_ERROR, EVENT_NONE, EVENT_SPACE, EVENT_NEWLINE, EVENT_OP, EVENT_DELIM, EVENT_ITEM, EVENT_DQUOTE, EVENT_SQUOTE, }; typedef unsigned long long (*pevent_func_handler)(struct trace_seq *s, unsigned long long *args); enum pevent_func_arg_type { PEVENT_FUNC_ARG_VOID, PEVENT_FUNC_ARG_INT, PEVENT_FUNC_ARG_LONG, PEVENT_FUNC_ARG_STRING, PEVENT_FUNC_ARG_PTR, PEVENT_FUNC_ARG_MAX_TYPES }; enum pevent_flag { PEVENT_NSEC_OUTPUT = 1, /* output in NSECS */ PEVENT_DISABLE_SYS_PLUGINS = 1 << 1, PEVENT_DISABLE_PLUGINS = 1 << 2, }; #define PEVENT_ERRORS \ _PE(MEM_ALLOC_FAILED, "failed to allocate memory"), \ _PE(PARSE_EVENT_FAILED, "failed to parse event"), \ _PE(READ_ID_FAILED, "failed to read event id"), \ _PE(READ_FORMAT_FAILED, "failed to read event format"), \ _PE(READ_PRINT_FAILED, "failed to read event print fmt"), \ _PE(OLD_FTRACE_ARG_FAILED,"failed to allocate field name for ftrace"),\ _PE(INVALID_ARG_TYPE, "invalid argument type"), \ _PE(INVALID_EXP_TYPE, "invalid expression type"), \ _PE(INVALID_OP_TYPE, "invalid operator type"), \ _PE(INVALID_EVENT_NAME, "invalid event name"), \ _PE(EVENT_NOT_FOUND, "no event found"), \ _PE(SYNTAX_ERROR, "syntax error"), \ _PE(ILLEGAL_RVALUE, "illegal rvalue"), \ _PE(ILLEGAL_LVALUE, "illegal lvalue for string comparison"), \ _PE(INVALID_REGEX, "regex did not compute"), \ _PE(ILLEGAL_STRING_CMP, "illegal comparison for string"), \ _PE(ILLEGAL_INTEGER_CMP,"illegal comparison for integer"), \ _PE(REPARENT_NOT_OP, "cannot reparent other than OP"), \ _PE(REPARENT_FAILED, "failed to reparent filter OP"), \ _PE(BAD_FILTER_ARG, "bad arg in filter tree"), \ _PE(UNEXPECTED_TYPE, "unexpected type (not a value)"), \ _PE(ILLEGAL_TOKEN, "illegal token"), \ _PE(INVALID_PAREN, "open parenthesis cannot come here"), \ _PE(UNBALANCED_PAREN, "unbalanced number of parenthesis"), \ _PE(UNKNOWN_TOKEN, "unknown token"), \ _PE(FILTER_NOT_FOUND, "no filter found"), \ _PE(NOT_A_NUMBER, "must have number field"), \ _PE(NO_FILTER, "no filters exists"), \ _PE(FILTER_MISS, "record does not match to filter") #undef _PE #define _PE(__code, __str) PEVENT_ERRNO__ ## __code enum pevent_errno { PEVENT_ERRNO__SUCCESS = 0, PEVENT_ERRNO__FILTER_MATCH = PEVENT_ERRNO__SUCCESS, /* * Choose an arbitrary negative big number not to clash with standard * errno since SUS requires the errno has distinct positive values. * See 'Issue 6' in the link below. * * http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html */ __PEVENT_ERRNO__START = -100000, PEVENT_ERRORS, __PEVENT_ERRNO__END, }; #undef _PE struct plugin_list; #define INVALID_PLUGIN_LIST_OPTION ((char **)((unsigned long)-1)) struct plugin_list *traceevent_load_plugins(struct pevent *pevent); void traceevent_unload_plugins(struct plugin_list *plugin_list, struct pevent *pevent); char **traceevent_plugin_list_options(void); void traceevent_plugin_free_options_list(char **list); int traceevent_plugin_add_options(const char *name, struct pevent_plugin_option *options); void traceevent_plugin_remove_options(struct pevent_plugin_option *options); void traceevent_print_plugins(struct trace_seq *s, const char *prefix, const char *suffix, const struct plugin_list *list); struct cmdline; struct cmdline_list; struct func_map; struct func_list; struct event_handler; struct pevent { int ref_count; int header_page_ts_offset; int header_page_ts_size; int header_page_size_offset; int header_page_size_size; int header_page_data_offset; int header_page_data_size; int header_page_overwrite; int file_bigendian; int host_bigendian; int latency_format; int old_format; int cpus; int long_size; int page_size; struct cmdline *cmdlines; struct cmdline_list *cmdlist; int cmdline_count; struct func_map *func_map; struct func_list *funclist; unsigned int func_count; struct printk_map *printk_map; struct printk_list *printklist; unsigned int printk_count; struct event_format **events; int nr_events; struct event_format **sort_events; enum event_sort_type last_type; int type_offset; int type_size; int pid_offset; int pid_size; int pc_offset; int pc_size; int flags_offset; int flags_size; int ld_offset; int ld_size; int print_raw; int test_filters; int flags; struct format_field *bprint_ip_field; struct format_field *bprint_fmt_field; struct format_field *bprint_buf_field; struct event_handler *handlers; struct pevent_function_handler *func_handlers; /* cache */ struct event_format *last_event; char *trace_clock; }; static inline void pevent_set_flag(struct pevent *pevent, int flag) { pevent->flags |= flag; } static inline unsigned short __data2host2(struct pevent *pevent, unsigned short data) { unsigned short swap; if (pevent->host_bigendian == pevent->file_bigendian) return data; swap = ((data & 0xffULL) << 8) | ((data & (0xffULL << 8)) >> 8); return swap; } static inline unsigned int __data2host4(struct pevent *pevent, unsigned int data) { unsigned int swap; if (pevent->host_bigendian == pevent->file_bigendian) return data; swap = ((data & 0xffULL) << 24) | ((data & (0xffULL << 8)) << 8) | ((data & (0xffULL << 16)) >> 8) | ((data & (0xffULL << 24)) >> 24); return swap; } static inline unsigned long long __data2host8(struct pevent *pevent, unsigned long long data) { unsigned long long swap; if (pevent->host_bigendian == pevent->file_bigendian) return data; swap = ((data & 0xffULL) << 56) | ((data & (0xffULL << 8)) << 40) | ((data & (0xffULL << 16)) << 24) | ((data & (0xffULL << 24)) << 8) | ((data & (0xffULL << 32)) >> 8) | ((data & (0xffULL << 40)) >> 24) | ((data & (0xffULL << 48)) >> 40) | ((data & (0xffULL << 56)) >> 56); return swap; } #define data2host2(pevent, ptr) __data2host2(pevent, *(unsigned short *)(ptr)) #define data2host4(pevent, ptr) __data2host4(pevent, *(unsigned int *)(ptr)) #define data2host8(pevent, ptr) \ ({ \ unsigned long long __val; \ \ memcpy(&__val, (ptr), sizeof(unsigned long long)); \ __data2host8(pevent, __val); \ }) static inline int traceevent_host_bigendian(void) { unsigned char str[] = { 0x1, 0x2, 0x3, 0x4 }; unsigned int val; memcpy(&val, str, 4); return val == 0x01020304; } /* taken from kernel/trace/trace.h */ enum trace_flag_type { TRACE_FLAG_IRQS_OFF = 0x01, TRACE_FLAG_IRQS_NOSUPPORT = 0x02, TRACE_FLAG_NEED_RESCHED = 0x04, TRACE_FLAG_HARDIRQ = 0x08, TRACE_FLAG_SOFTIRQ = 0x10, }; int pevent_register_comm(struct pevent *pevent, const char *comm, int pid); void pevent_register_trace_clock(struct pevent *pevent, char *trace_clock); int pevent_register_function(struct pevent *pevent, char *name, unsigned long long addr, char *mod); int pevent_register_print_string(struct pevent *pevent, const char *fmt, unsigned long long addr); int pevent_pid_is_registered(struct pevent *pevent, int pid); void pevent_print_event(struct pevent *pevent, struct trace_seq *s, struct pevent_record *record, bool use_trace_clock); int pevent_parse_header_page(struct pevent *pevent, char *buf, unsigned long size, int long_size); enum pevent_errno pevent_parse_event(struct pevent *pevent, const char *buf, unsigned long size, const char *sys); enum pevent_errno pevent_parse_format(struct pevent *pevent, struct event_format **eventp, const char *buf, unsigned long size, const char *sys); void pevent_free_format(struct event_format *event); void *pevent_get_field_raw(struct trace_seq *s, struct event_format *event, const char *name, struct pevent_record *record, int *len, int err); int pevent_get_field_val(struct trace_seq *s, struct event_format *event, const char *name, struct pevent_record *record, unsigned long long *val, int err); int pevent_get_common_field_val(struct trace_seq *s, struct event_format *event, const char *name, struct pevent_record *record, unsigned long long *val, int err); int pevent_get_any_field_val(struct trace_seq *s, struct event_format *event, const char *name, struct pevent_record *record, unsigned long long *val, int err); int pevent_print_num_field(struct trace_seq *s, const char *fmt, struct event_format *event, const char *name, struct pevent_record *record, int err); int pevent_print_func_field(struct trace_seq *s, const char *fmt, struct event_format *event, const char *name, struct pevent_record *record, int err); int pevent_register_event_handler(struct pevent *pevent, int id, const char *sys_name, const char *event_name, pevent_event_handler_func func, void *context); int pevent_unregister_event_handler(struct pevent *pevent, int id, const char *sys_name, const char *event_name, pevent_event_handler_func func, void *context); int pevent_register_print_function(struct pevent *pevent, pevent_func_handler func, enum pevent_func_arg_type ret_type, char *name, ...); int pevent_unregister_print_function(struct pevent *pevent, pevent_func_handler func, char *name); struct format_field *pevent_find_common_field(struct event_format *event, const char *name); struct format_field *pevent_find_field(struct event_format *event, const char *name); struct format_field *pevent_find_any_field(struct event_format *event, const char *name); const char *pevent_find_function(struct pevent *pevent, unsigned long long addr); unsigned long long pevent_find_function_address(struct pevent *pevent, unsigned long long addr); unsigned long long pevent_read_number(struct pevent *pevent, const void *ptr, int size); int pevent_read_number_field(struct format_field *field, const void *data, unsigned long long *value); struct event_format *pevent_find_event(struct pevent *pevent, int id); struct event_format * pevent_find_event_by_name(struct pevent *pevent, const char *sys, const char *name); void pevent_data_lat_fmt(struct pevent *pevent, struct trace_seq *s, struct pevent_record *record); int pevent_data_type(struct pevent *pevent, struct pevent_record *rec); struct event_format *pevent_data_event_from_type(struct pevent *pevent, int type); int pevent_data_pid(struct pevent *pevent, struct pevent_record *rec); const char *pevent_data_comm_from_pid(struct pevent *pevent, int pid); void pevent_event_info(struct trace_seq *s, struct event_format *event, struct pevent_record *record); int pevent_strerror(struct pevent *pevent, enum pevent_errno errnum, char *buf, size_t buflen); struct event_format **pevent_list_events(struct pevent *pevent, enum event_sort_type); struct format_field **pevent_event_common_fields(struct event_format *event); struct format_field **pevent_event_fields(struct event_format *event); static inline int pevent_get_cpus(struct pevent *pevent) { return pevent->cpus; } static inline void pevent_set_cpus(struct pevent *pevent, int cpus) { pevent->cpus = cpus; } static inline int pevent_get_long_size(struct pevent *pevent) { return pevent->long_size; } static inline void pevent_set_long_size(struct pevent *pevent, int long_size) { pevent->long_size = long_size; } static inline int pevent_get_page_size(struct pevent *pevent) { return pevent->page_size; } static inline void pevent_set_page_size(struct pevent *pevent, int _page_size) { pevent->page_size = _page_size; } static inline int pevent_is_file_bigendian(struct pevent *pevent) { return pevent->file_bigendian; } static inline void pevent_set_file_bigendian(struct pevent *pevent, int endian) { pevent->file_bigendian = endian; } static inline int pevent_is_host_bigendian(struct pevent *pevent) { return pevent->host_bigendian; } static inline void pevent_set_host_bigendian(struct pevent *pevent, int endian) { pevent->host_bigendian = endian; } static inline int pevent_is_latency_format(struct pevent *pevent) { return pevent->latency_format; } static inline void pevent_set_latency_format(struct pevent *pevent, int lat) { pevent->latency_format = lat; } struct pevent *pevent_alloc(void); void pevent_free(struct pevent *pevent); void pevent_ref(struct pevent *pevent); void pevent_unref(struct pevent *pevent); /* access to the internal parser */ void pevent_buffer_init(const char *buf, unsigned long long size); enum event_type pevent_read_token(char **tok); void pevent_free_token(char *token); int pevent_peek_char(void); const char *pevent_get_input_buf(void); unsigned long long pevent_get_input_buf_ptr(void); /* for debugging */ void pevent_print_funcs(struct pevent *pevent); void pevent_print_printk(struct pevent *pevent); /* ----------------------- filtering ----------------------- */ enum filter_boolean_type { FILTER_FALSE, FILTER_TRUE, }; enum filter_op_type { FILTER_OP_AND = 1, FILTER_OP_OR, FILTER_OP_NOT, }; enum filter_cmp_type { FILTER_CMP_NONE, FILTER_CMP_EQ, FILTER_CMP_NE, FILTER_CMP_GT, FILTER_CMP_LT, FILTER_CMP_GE, FILTER_CMP_LE, FILTER_CMP_MATCH, FILTER_CMP_NOT_MATCH, FILTER_CMP_REGEX, FILTER_CMP_NOT_REGEX, }; enum filter_exp_type { FILTER_EXP_NONE, FILTER_EXP_ADD, FILTER_EXP_SUB, FILTER_EXP_MUL, FILTER_EXP_DIV, FILTER_EXP_MOD, FILTER_EXP_RSHIFT, FILTER_EXP_LSHIFT, FILTER_EXP_AND, FILTER_EXP_OR, FILTER_EXP_XOR, FILTER_EXP_NOT, }; enum filter_arg_type { FILTER_ARG_NONE, FILTER_ARG_BOOLEAN, FILTER_ARG_VALUE, FILTER_ARG_FIELD, FILTER_ARG_EXP, FILTER_ARG_OP, FILTER_ARG_NUM, FILTER_ARG_STR, }; enum filter_value_type { FILTER_NUMBER, FILTER_STRING, FILTER_CHAR }; struct fliter_arg; struct filter_arg_boolean { enum filter_boolean_type value; }; struct filter_arg_field { struct format_field *field; }; struct filter_arg_value { enum filter_value_type type; union { char *str; unsigned long long val; }; }; struct filter_arg_op { enum filter_op_type type; struct filter_arg *left; struct filter_arg *right; }; struct filter_arg_exp { enum filter_exp_type type; struct filter_arg *left; struct filter_arg *right; }; struct filter_arg_num { enum filter_cmp_type type; struct filter_arg *left; struct filter_arg *right; }; struct filter_arg_str { enum filter_cmp_type type; struct format_field *field; char *val; char *buffer; regex_t reg; }; struct filter_arg { enum filter_arg_type type; union { struct filter_arg_boolean boolean; struct filter_arg_field field; struct filter_arg_value value; struct filter_arg_op op; struct filter_arg_exp exp; struct filter_arg_num num; struct filter_arg_str str; }; }; struct filter_type { int event_id; struct event_format *event; struct filter_arg *filter; }; #define PEVENT_FILTER_ERROR_BUFSZ 1024 struct event_filter { struct pevent *pevent; int filters; struct filter_type *event_filters; char error_buffer[PEVENT_FILTER_ERROR_BUFSZ]; }; struct event_filter *pevent_filter_alloc(struct pevent *pevent); /* for backward compatibility */ #define FILTER_NONE PEVENT_ERRNO__NO_FILTER #define FILTER_NOEXIST PEVENT_ERRNO__FILTER_NOT_FOUND #define FILTER_MISS PEVENT_ERRNO__FILTER_MISS #define FILTER_MATCH PEVENT_ERRNO__FILTER_MATCH enum filter_trivial_type { FILTER_TRIVIAL_FALSE, FILTER_TRIVIAL_TRUE, FILTER_TRIVIAL_BOTH, }; enum pevent_errno pevent_filter_add_filter_str(struct event_filter *filter, const char *filter_str); enum pevent_errno pevent_filter_match(struct event_filter *filter, struct pevent_record *record); int pevent_filter_strerror(struct event_filter *filter, enum pevent_errno err, char *buf, size_t buflen); int pevent_event_filtered(struct event_filter *filter, int event_id); void pevent_filter_reset(struct event_filter *filter); int pevent_filter_clear_trivial(struct event_filter *filter, enum filter_trivial_type type); void pevent_filter_free(struct event_filter *filter); char *pevent_filter_make_string(struct event_filter *filter, int event_id); int pevent_filter_remove_event(struct event_filter *filter, int event_id); int pevent_filter_event_has_trivial(struct event_filter *filter, int event_id, enum filter_trivial_type type); int pevent_filter_copy(struct event_filter *dest, struct event_filter *source); int pevent_update_trivial(struct event_filter *dest, struct event_filter *source, enum filter_trivial_type type); int pevent_filter_compare(struct event_filter *filter1, struct event_filter *filter2); #endif /* _PARSE_EVENTS_H */ uftrace-0.9.4/libtraceevent/event-plugin.c000066400000000000000000000222521362052523300205710ustar00rootroot00000000000000/* * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License (not later!) * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, see * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ #include #include #include #include #include #include #include #include #include "event-parse.h" #include "event-utils.h" #define LOCAL_PLUGIN_DIR ".traceevent/plugins" static struct registered_plugin_options { struct registered_plugin_options *next; struct pevent_plugin_option *options; } *registered_options; static struct trace_plugin_options { struct trace_plugin_options *next; char *plugin; char *option; char *value; } *trace_plugin_options; struct plugin_list { struct plugin_list *next; char *name; void *handle; }; /** * traceevent_plugin_list_options - get list of plugin options * * Returns an array of char strings that list the currently registered * plugin options in the format of :