pax_global_header00006660000000000000000000000064145536573430014531gustar00rootroot0000000000000052 comment=9d8657e90b918994d7d2bcf6dd2cc7354c35a1b4 uftrace-0.15.2/000077500000000000000000000000001455365734300132475ustar00rootroot00000000000000uftrace-0.15.2/.clang-format000066400000000000000000000100501455365734300156160ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0 # # clang-format configuration file. Intended for clang-format >= 11. # # For more information, see: # # https://clang.llvm.org/docs/ClangFormat.html # https://clang.llvm.org/docs/ClangFormatStyleOptions.html # --- AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Right AlignOperands: true AlignTrailingComments: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: true BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: true AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: true IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: false BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: false ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 8 ContinuationIndentWidth: 8 Cpp11BracedListStyle: false DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: false # Taken from: # git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' utils/ \ # | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \ # | LC_ALL=C sort -u ForEachMacros: - 'elf_for_each_dynamic' - 'elf_for_each_dynamic_symbol' - 'elf_for_each_note' - 'elf_for_each_phdr' - 'elf_for_each_rel' - 'elf_for_each_rela' - 'elf_for_each_shdr' - 'elf_for_each_symbol' - 'for_each_map' - 'list_for_each' - 'list_for_each_entry' - 'list_for_each_entry_continue' - 'list_for_each_entry_continue_reverse' - 'list_for_each_entry_from' - 'list_for_each_entry_reverse' - 'list_for_each_entry_safe' - 'list_for_each_entry_safe_continue' - 'list_for_each_entry_safe_from' - 'list_for_each_entry_safe_reverse' - 'list_for_each_prev' - 'list_for_each_prev_safe' - 'list_for_each_safe' - 'strv_for_each' IncludeBlocks: Preserve IncludeCategories: - Regex: '.*' Priority: 1 IncludeIsMainRegex: '(Test)?$' IndentCaseLabels: false IndentGotoLabels: false IndentPPDirectives: None IndentWidth: 8 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBinPackProtocolList: Auto ObjCBlockIndentWidth: 8 ObjCSpaceAfterProperty: true ObjCSpaceBeforeProtocolList: true # Taken from git's rules PenaltyBreakAssignment: 10 PenaltyBreakBeforeFirstCallParameter: 30 PenaltyBreakComment: 10 PenaltyBreakFirstLessLess: 0 PenaltyBreakString: 10 PenaltyExcessCharacter: 100 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Right ReflowComments: false SortIncludes: true SortUsingDeclarations: false SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeCtorInitializerColon: true SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatementsExceptForEachMacros SpaceBeforeRangeBasedForLoopColon: true SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: false SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp03 TabWidth: 8 UseTab: Always ... uftrace-0.15.2/.codespell_ignore000066400000000000000000000000321455365734300165600ustar00rootroot00000000000000pevent creat mmaped ot te uftrace-0.15.2/.github/000077500000000000000000000000001455365734300146075ustar00rootroot00000000000000uftrace-0.15.2/.github/workflows/000077500000000000000000000000001455365734300166445ustar00rootroot00000000000000uftrace-0.15.2/.github/workflows/nightly-test.yml000066400000000000000000000015401455365734300220220ustar00rootroot00000000000000name: "Nightly test" on: schedule: - cron: '0 11 * * *' jobs: run-uftrace-tests: if: github.repository == 'namhyung/uftrace' strategy: max-parallel: 1 matrix: os: [ubuntu-20.04, ubuntu-22.04] runs-on: ${{ matrix.os }} steps: - name: "checkout" uses: actions/checkout@v3 - name: "install dependencies" run: sudo ./misc/install-deps.sh -y - name: "configure build env" run: ./configure - name: "build the source" run: make DEBUG=1 - name: "setup run-time env" run: | sudo sh -c "echo 1 > /proc/sys/kernel/perf_event_paranoid" sudo sh -c "echo 0 > /proc/sys/kernel/kptr_restrict" sudo sh -c "chmod +x /sys/kernel/tracing" - name: "run tests" run: make test -j1 TESTARG='--color on' RUNTESTARG='--quiet' uftrace-0.15.2/.github/workflows/on-demand-test.yml000066400000000000000000000025571455365734300222170ustar00rootroot00000000000000name: "On-demand test" on: workflow_dispatch: inputs: branch: description: 'Branch or tag to test' required: true default: 'master' type: string args: description: 'Common test options' default: '-d' type: string unit_args: description: 'Unit test options' type: string run_args: description: 'Run test options' type: string python_args: description: 'Python test options' type: string jobs: run-uftrace-tests: if: github.repository == 'namhyung/uftrace' runs-on: ubuntu-latest steps: - name: "checkout" uses: actions/checkout@v3 with: ref: ${{ inputs.branch }} - name: "install dependencies" run: sudo ./misc/install-deps.sh -y - name: "configure build env" run: ./configure - name: "build the source" run: make DEBUG=1 - name: "setup run-time env" run: | sudo sh -c "echo 1 > /proc/sys/kernel/perf_event_paranoid" sudo sh -c "echo 0 > /proc/sys/kernel/kptr_restrict" sudo sh -c "chmod +x /sys/kernel/tracing" - name: "run tests" run: make test -j1 DEBUG=1 TESTARG="${{ inputs.args }}" UNITTESTARG="${{ inputs.unit_args }}" RUNTESTARG="${{ inputs.run_args }}" PYTESTARG="${{ inputs.python_args }}" uftrace-0.15.2/.github/workflows/pr-test.yml000066400000000000000000000013431455365734300207660ustar00rootroot00000000000000name: "PR test" on: pull_request jobs: unit-test-with-asan: runs-on: ubuntu-latest steps: - name: "checkout" uses: actions/checkout@v3 - name: "install dependencies" run: sudo ./misc/install-deps.sh -y - name: "configure build env" run: ./configure - name: "build the source" run: make ASAN=1 DEBUG=1 - name: "run unit tests" run: make unittest ASAN=1 DEBUG=1 TESTARG="-v" pre-commit-check: runs-on: ubuntu-latest steps: - name: "check out uftrace source" uses: actions/checkout@v3 - name: "install dependencies" uses: actions/setup-python@v3 - name: "run pre-commit" uses: pre-commit/action@v3.0.0 uftrace-0.15.2/.github/workflows/push-test.yml000066400000000000000000000013351455365734300213250ustar00rootroot00000000000000name: "Push test" on: push jobs: unit-test-with-asan: runs-on: ubuntu-latest steps: - name: "checkout" uses: actions/checkout@v3 - name: "install dependencies" run: sudo ./misc/install-deps.sh -y - name: "configure build env" run: ./configure - name: "build the source" run: make ASAN=1 DEBUG=1 - name: "run unit tests" run: make unittest ASAN=1 DEBUG=1 TESTARG="-v" pre-commit-check: runs-on: ubuntu-latest steps: - name: "check out uftrace source" uses: actions/checkout@v3 - name: "install dependencies" uses: actions/setup-python@v3 - name: "run pre-commit" uses: pre-commit/action@v3.0.0 uftrace-0.15.2/.gitignore000066400000000000000000000010251455365734300152350ustar00rootroot00000000000000*.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 uftrace_python.so gmon.out tests/t-* tests/arch/*/t-* tests/*.so check-deps/* !check-deps/*.c !check-deps/Makefile* FLAGS version.h .config .cache .clangd/ .vscode .build/ GPATH GRTAGS GTAGS TAGS tags cscope.* compile_commands.json *.pyc *.sw[opn] *.patch *.orig *.gcda *.gcno *.cstr misc/demangler misc/symbols misc/dbginfo misc/bench uftrace-0.15.2/.pre-commit-config.yaml000066400000000000000000000010601455365734300175250ustar00rootroot00000000000000repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.0.1 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/PyCQA/isort rev: 5.11.5 hooks: - id: isort - repo: https://github.com/pre-commit/mirrors-clang-format rev: v14.0.6 hooks: - id: clang-format - repo: https://github.com/codespell-project/codespell rev: v2.1.0 hooks: - id: codespell args: ['-I', '.codespell_ignore'] exclude: "libtraceevent/.*" uftrace-0.15.2/CONTRIBUTING.md000066400000000000000000000103441455365734300155020ustar00rootroot00000000000000Contributing 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 [Linux kernel coding style](https://www.kernel.org/doc/Documentation/process/coding-style.rst) with a few differences. The uftrace repository provides a way to automatically apply formatting with the help of [pre-commit](https://pre-commit.com) and [clang-format](https://clang.llvm.org/docs/ClangFormat.html) so that our source code has a consistent coding style at all times. You need to install pre-commit package but please note that python version 3.7 or higher is required. The installation can be done as follows. $ python3 -m pip install pre-commit Then you can simply install a pre-commit hook inside the uftrace source directory as follows. $ pre-commit install pre-commit installed at .git/hooks/pre-commit After pre-commit installation, coding style check is done automatically whenever you try to create a commit as follows. $ git commit -s ... clang-format.............................................................Failed - hook id: clang-format - files were modified by this hook If your change doesn't follow the coding style, then clang-format check fails and also modifies your code to follow the pre-configured uftrace coding style, which is written at [.clang-format](.clang-format). If the code is modified by clang-format, then please run `git add -u` and create a commit again to include the changes made by clang-format. You can also run coding style check by running pre-commit manually as follows. $ git add -u $ pre-commit run It will check the coding style only for the changes in the git staging area and automatically reformatted if the check fails. 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.15.2/COPYING000066400000000000000000000432541455365734300143120ustar00rootroot00000000000000 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.15.2/INSTALL.md000066400000000000000000000216671455365734300147130ustar00rootroot00000000000000QUICK GUIDE =========== On Linux distros, the 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 on the system to properly detect the dependencies of uftrace. Otherwise, some packages may not be detected even if they are already installed, causing some features of uftrace to be disabled. 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 the 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 uftrace 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). The ncursesw library is used to implement text user interface (TUI) on console. The ncurses(w) library provides terminal handling routines which `uftrace tui` command is built on top of. As it improves user experience of trace data analysis, you should consider installing it if you do things like `uftrace graph` or `uftrace report` frequently. Also uftrace 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 you also need to install dependent packages. Please see DEPENDENCY section for more details. Once you have installed the required software(s), you need to run `configure` to set the install directory and other features. It installs the uftrace under /usr/local by default. If you want install it to some other location, you can set the `prefix` variable (see below). $ ./configure --prefix=/usr It will show the prefix directory and detected features like: uftrace detected system features: ... prefix: /usr ... libelf: [ on ] - more flexible ELF data handling ... libdw: [ on ] - DWARF debug info support ... libpython: [ on ] - python scripting support ... libluajit: [ OFF ] - luajit scripting support ... libncursesw: [ on ] - TUI support ... cxa_demangle: [ on ] - full demangler support with libstdc++ ... perf_event: [ on ] - perf (PMU) event support ... schedule: [ on ] - scheduler event support ... capstone: [ on ] - full dynamic tracing support ... libunwind: [ OFF ] - stacktrace support (optional for debugging) Then you can run `make` to build the source. $ make It builds uftrace, placing the resulting binaries in the current directory. This is good for testing, but you'll want to install it for normal use. $ sudo make install The output of the build looks like linux kernel style, and users can see the original build command lines with V=1 (like kernel). $ make V=1 CONFIGURATION ============= The uftrace implements its own version of configure script to save user preferences. The config file (named `.config`) is created, if it doesn't exist, at 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/uftrace) --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 that depend on external libraries or system behaviors. For example --without-libpython option will disable scripting feature - `uftrace script` command will still exist but won't work. For cross compilation, you may want to setup the toolchain to 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=`. To compile for Android 9+, export the CC environment variable and disable the not yet implemented python and libstdc++ support. E.g. to configure for Android AArch64 do $ export CC=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang $ export LD=$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/ld.lld $ ./configure --arch=aarch64 --cross-compile=aarch64-linux-gnu- --without-libpython --without-libstdc++ It's recommended to compile instrumented program with `-fpatchable-function-entry` on Android. You could also use `-finstrument-functions` or `-pg` but in that case you also need to link program with `-Wl,-z,undefs` because Android runtime does not include `__cyg_profile_func_enter` or `mcount`. Note that Android has been tested on AArch64 and x86\_64 so far. 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 installs both libelf and libdw to prefix directory. The installed libelf and libdw can be found using `--with-elfutils` in the `configure` script. uftrace-0.15.2/Makefile000066400000000000000000000467161455365734300147250ustar00rootroot00000000000000VERSION := 0.15 # 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/uftrace etcdir = $(prefix)/etc mandir = $(prefix)/share/man docdir = $(srcdir)/doc completiondir = $(etcdir)/bash_completion.d 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 := -std=gnu11 -D_GNU_SOURCE $(CFLAGS) $(CPPFLAGS) COMMON_CFLAGS += -iquote $(srcdir) -iquote $(objdir) -iquote $(srcdir)/arch/$(ARCH) COMMON_CFLAGS += -W -Wall -Wno-unused-parameter -Wno-missing-field-initializers COMMON_CFLAGS += -Wdeclaration-after-statement -Wstrict-prototypes COMMON_LDFLAGS := -ldl -pthread -Wl,-z,noexecstack $(LDFLAGS) ifeq ($(ANDROID),) COMMON_LDFLAGS += -lrt else COMMON_LDFLAGS += -landroid endif ifneq ($(elfdir),) COMMON_CFLAGS += -I$(elfdir)/include COMMON_LDFLAGS += -L$(elfdir)/lib endif C_STR_TARGET = utils/mermaid.js utils/mermaid.html C_STR_EXTENSION = cstr C_STR_OBJS := $(patsubst %,$(objdir)/%.$(C_STR_EXTENSION),$(C_STR_TARGET)) # # 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) DBGINFO_CFLAGS = $(COMMON_CFLAGS) $(CFLAGS_$@) $(CFLAGS_dbginfo) BENCH_CFLAGS = -D_GNU_SOURCE -g -pg $(CFLAGS_$@) $(CFLAGS_bench) TRACEEVENT_CFLAGS = $(COMMON_CFLAGS) $(CFLAGS_$@) $(CFLAGS_traceevent) LIB_CFLAGS = $(COMMON_CFLAGS) $(CFLAGS_$@) $(CFLAGS_lib) LIB_CFLAGS += -fPIC -fvisibility=hidden -fno-omit-frame-pointer LIB_CFLAGS += -fno-builtin -fno-tree-vectorize -DLIBMCOUNT TEST_CFLAGS = $(COMMON_CFLAGS) -DUNIT_TEST PYTHON_CFLAGS = $(COMMON_CFLAGS) -fPIC UFTRACE_LDFLAGS = $(COMMON_LDFLAGS) $(LDFLAGS_$@) $(LDFLAGS_uftrace) DEMANGLER_LDFLAGS = $(COMMON_LDFLAGS) $(LDFLAGS_$@) $(LDFLAGS_demangler) SYMBOLS_LDFLAGS = $(COMMON_LDFLAGS) $(LDFLAGS_$@) $(LDFLAGS_symbols) DBGINFO_LDFLAGS = $(COMMON_LDFLAGS) $(LDFLAGS_$@) $(LDFLAGS_dbginfo) BENCH_LDFLAGS = -Wl,-z,noexecstack $(LDFLAGS_$@) $(LDFLAGS_bench) LIB_LDFLAGS = $(COMMON_LDFLAGS) $(LDFLAGS_$@) $(LDFLAGS_lib) -Wl,--no-undefined TEST_LDFLAGS = $(COMMON_LDFLAGS) _DEFAULT_SANITIZERS := address,leak ifeq ($(ARCH), riscv64) DEFAULT_SANITIZERS = $(_DEFAULT_SANITIZERS) else DEFAULT_SANITIZERS = $(_DEFAULT_SANITIZERS),undefined endif ifeq ($(DEBUG), 1) COMMON_CFLAGS += -O0 -g3 -DDEBUG_MODE=1 -Werror else COMMON_CFLAGS += -O2 -g -DDEBUG_MODE=0 endif ifeq ($(TRACE), 1) TRACE_CFLAGS := -pg -fno-omit-frame-pointer UFTRACE_CFLAGS += $(TRACE_CFLAGS) DEMANGLER_CFLAGS += $(TRACE_CFLAGS) SYMBOLS_CFLAGS += $(TRACE_CFLAGS) DBGINFO_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=$(DEFAULT_SANITIZERS) UFTRACE_CFLAGS += $(ASAN_CFLAGS) DEMANGLER_CFLAGS += $(ASAN_CFLAGS) SYMBOLS_CFLAGS += $(ASAN_CFLAGS) DBGINFO_CFLAGS += $(ASAN_CFLAGS) TRACEEVENT_CFLAGS += $(ASAN_CFLAGS) TEST_CFLAGS += $(ASAN_CFLAGS) endif ifneq ($(SAN),) ifeq ($(SAN), all) SAN_CFLAGS := -O0 -g -fsanitize=$(DEFAULT_SANITIZERS) else SAN_CFLAGS := -O0 -g -fsanitize=$(SAN) endif UFTRACE_CFLAGS += $(SAN_CFLAGS) DEMANGLER_CFLAGS += $(SAN_CFLAGS) SYMBOLS_CFLAGS += $(SAN_CFLAGS) DBGINFO_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 $(objdir)/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 python/uftrace_python.so _TARGETS += $(LIBMCOUNT_TARGETS) libmcount/libmcount-nop.so _TARGETS += misc/demangler misc/symbols misc/dbginfo 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 += $(srcdir)/utils/argspec.c SYMBOLS_SRCS += $(wildcard $(srcdir)/utils/symbol*.c) SYMBOLS_OBJS := $(patsubst $(srcdir)/%.c,$(objdir)/%.o,$(SYMBOLS_SRCS)) DBGINFO_SRCS := $(srcdir)/misc/dbginfo.c $(srcdir)/utils/dwarf.c DBGINFO_SRCS += $(srcdir)/utils/auto-args.c $(srcdir)/utils/regs.c DBGINFO_SRCS += $(srcdir)/utils/utils.c $(srcdir)/utils/debug.c DBGINFO_SRCS += $(srcdir)/utils/argspec.c $(srcdir)/utils/rbtree.c DBGINFO_SRCS += $(srcdir)/utils/demangle.c $(srcdir)/utils/filter.c DBGINFO_SRCS += $(wildcard $(srcdir)/utils/symbol*.c) DBGINFO_OBJS := $(patsubst $(srcdir)/%.c,$(objdir)/%.o,$(DBGINFO_SRCS)) BENCH_SRCS := $(srcdir)/misc/bench.c BENCH_OBJS := $(patsubst $(srcdir)/%.c,$(objdir)/%.o,$(BENCH_SRCS)) PYTHON_SRCS := $(srcdir)/python/trace-python.c $(srcdir)/utils/debug.c PYTHON_SRCS += $(srcdir)/utils/utils.c $(srcdir)/utils/rbtree.c $(srcdir)/utils/shmem.c PYTHON_SRCS += $(wildcard $(srcdir)/utils/symbol-*.c) PYTHON_OBJS := $(patsubst $(srcdir)/%.c,$(objdir)/%.op,$(PYTHON_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 += $(srcdir)/utils/hashmap.c $(srcdir)/utils/argspec.c LIBMCOUNT_UTILS_SRCS += $(srcdir)/utils/tracefs.c $(srcdir)/utils/socket.c LIBMCOUNT_UTILS_SRCS += $(srcdir)/utils/shmem.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)"' 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 $(error Please run 'configure' first) config: $(srcdir)/configure $(QUIET_GEN)$(srcdir)/configure --objdir=$(objdir) $(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)/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 $@ $< $(objdir)/misc/dbginfo.o: $(srcdir)/misc/dbginfo.c $(objdir)/version.h $(COMMON_DEPS) $(QUIET_CC)$(CC) $(DBGINFO_CFLAGS) -c -o $@ $< $(objdir)/misc/bench.o: $(srcdir)/misc/bench.c $(QUIET_CC)$(CC) $(BENCH_CFLAGS) -c -o $@ $< $(objdir)/cmds/dump.o: $(C_STR_OBJS) $(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) $(ARCH) $(objdir) $(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) $(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) $(objdir)/misc/dbginfo: $(DBGINFO_OBJS) $(QUIET_LINK)$(CC) $(DBGINFO_CFLAGS) -o $@ $(DBGINFO_OBJS) $(DBGINFO_LDFLAGS) $(objdir)/misc/bench: $(BENCH_OBJS) $(QUIET_LINK)$(CC) $(BENCH_CFLAGS) -o $@ $(BENCH_OBJS) $(BENCH_LDFLAGS) ifneq ($(findstring HAVE_LIBPYTHON, $(COMMON_CFLAGS)), ) $(PYTHON_OBJS): $(objdir)/%.op: $(srcdir)/%.c $(COMMON_DEPS) $(QUIET_CC_FPIC)$(CC) $(PYTHON_CFLAGS) -c -o $@ $< $(objdir)/python/uftrace_python.so: $(PYTHON_OBJS) $(QUIET_LINK)$(CC) -shared $(PYTHON_CFLAGS) -o $@ $(PYTHON_OBJS) $(PYTHON_LDFLAGS) else $(objdir)/python/uftrace_python.so: endif install: all $(Q)$(INSTALL) -d -m 755 $(DESTDIR)$(bindir) $(Q)$(INSTALL) -d -m 755 $(DESTDIR)$(libdir) $(Q)$(INSTALL) -d -m 755 $(DESTDIR)$(completiondir) 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 ifneq ($(findstring HAVE_LIBPYTHON, $(COMMON_CFLAGS)), ) $(call QUIET_INSTALL, uftrace-python) $(Q)$(INSTALL) $(srcdir)/python/uftrace.py $(DESTDIR)$(libdir)/uftrace.py $(Q)$(INSTALL) $(objdir)/python/uftrace_python.so $(DESTDIR)$(libdir)/uftrace_python.so endif $(call QUIET_INSTALL, bash-completion) $(Q)$(INSTALL) -m 644 $(srcdir)/misc/bash-completion.sh $(DESTDIR)$(completiondir)/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 ifneq ($(findstring HAVE_LIBPYTHON, $(COMMON_CFLAGS)), ) $(call QUIET_UNINSTALL, uftrace-python) $(Q)$(RM) $(DESTDIR)$(libdir)/uftrace.py $(Q)$(RM) $(DESTDIR)$(libdir)/uftrace_python.so endif $(call QUIET_UNINSTALL, bash-completion) $(Q)$(RM) $(DESTDIR)$(completiondir)/uftrace @$(MAKE) -sC $(docdir) uninstall DESTDIR=$(DESTDIR)$(mandir) test: all @$(MAKE) -C $(srcdir)/tests TESTARG="$(TESTARG)" UNITTESTARG="$(UNITTESTARG)" RUNTESTARG="$(RUNTESTARG)" PYTESTARG="$(PYTESTARG)" test unittest: all @$(MAKE) -C $(srcdir)/tests TESTARG="$(TESTARG)" UNITTESTARG="$(UNITTESTARG)" test_unit runtest: all @$(MAKE) -C $(srcdir)/tests TESTARG="$(TESTARG)" RUNTESTARG="$(RUNTESTARG)" test_run pytest: all @$(MAKE) -C $(srcdir)/tests TESTARG="$(TESTARG)" PYTESTARG="$(PYTESTARG)" test_python bench: all $(objdir)/misc/bench @cd $(srcdir)/misc && echo && ./bench.sh $(BENCHARG) 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 $(objdir)/python/*.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 $(C_STR_OBJS) @$(MAKE) -sC $(srcdir)/arch/$(ARCH) clean @$(MAKE) -sC $(srcdir)/tests ARCH=$(ARCH) clean @$(MAKE) -sC $(docdir) 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/' help: @echo "Available targets:" @echo " all - Build uftrace (default)" @echo " config - Configure uftrace" @echo " install - Install built uftrace" @echo " uninstall - Uninstall uftrace" @echo " test - Run all tests: unit test, integration test, python test" @echo " unittest - Run unit tests" @echo " runtest - Run integration tests" @echo " pytest - Run Python tests" @echo " bench - Run benchmark tests" @echo " dist - make *.tar file" @echo " doc - Build documentation" @echo " clean - Clean up built object files" @echo " reset-coverage- Reset code coverage data (*.gcda)" @echo " ctags - Generate ctags" @echo " help - Print this help message" @echo "" @echo "Build options:" @echo " make DEBUG=0|1 [targets] - Set flags for debugging (default: 0)" @echo " make TRACE=0|1 [targets] - Set flags for tracing (default: 0)" @echo " make COVERAGE=0|1 [targets] - Set flags for code coverage (default: 0)" @echo " make ASAN=0|1 [targets] - Set flags for AddressSanitizer (default: 0)" @echo " make SAN=all [targets] - Set flags for Sanitizer (default: none)" @echo " make DOCLANG=ko [targets] - Generate documentation in Korean (default: English)" @echo " make V=0|1 [targets] - Set verbose output (default: 0)" @echo " make O=dir [targets] - Set directory as objdir (default: $(srcdir))" @echo "" $(C_STR_OBJS): $(objdir)/%.$(C_STR_EXTENSION): $(srcdir)/% $(QUIET_GEN)sed -e 's#\\#\\\\#g;s#\"#\\"#g;s#$$#\\n\"#;s#^#\"#' $< > $@ .PHONY: all config clean test dist doc ctags help PHONY uftrace-0.15.2/Makefile.include000066400000000000000000000015071455365734300163340ustar00rootroot00000000000000#-*- 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.15.2/NEWS000066400000000000000000000371261455365734300137570ustar00rootroot00000000000000uftrace v0.15 ============= * New architecture support Basic support for RISC-V (RV64G ABI) (#1815) Arguments and return value recording (#1824) PLT hooking for library call tracing (#1853) * Bug fixes Support library call tracing with `-fno-plt` (#1777) Reduce failures on `uftrace recv` test (#1767) Fix tracefs check on qemu (#1753) Skip offline CPUs for kernel tracing (#1849) Ignore unpaired __cyg_profile_func_exit() to avoid crashes * other changes Get rid of old copy of libtraceevent (#1728) Use MAP_FIXED_NOREPLACE for dynamic tracing (#1798) Many documentation updates (#1669, #1744, #1759, #1760, #1783, #1785, #1786, #1788, #1790, #1806) Add misc/wget-pr.sh script to get patch files from github (#1808) Add more unit test cases And many other fixes and improvements. Thanks all contributors: Bernhard Kaindl, ChoKyuWon, Chongyun Lee, Clément Guidi, Gichoel Choi, Honggyu Kim, Jeonghwan In, Jin Jang, JungminKim, Kang Minchul, Michelle Jin, Olivier Dion, Paran Lee, Robert Berger, SangGyuLee, SeokMin Kwon, Seong Jin Kim, Soyeon Park, Stefan Hoffmeister, Yoojung Nam, Yufeng Jin, kang-hyuck uftrace v0.14 ============= * new options --trigger option is restored --trace=(on|off) option to deprecate --disable filters and triggers can be deleted with @clear action (with agent) * new features python tracing (#436, #1640, #1641, #1676) change options at runtime with agent (#1665, #1678, #1643, #1644, #1645, #1646) * bug fixes Lots of fixes for the test infra (#1628) * other changes Android build support (#1605) Update symbol file format to add symbol size (#1616) Improve Rust symbol demangling (#1625) Change default library install path (#1618) Use C11 + GNU extensions for compilation (#1642) And many other fixes and improvements. Thanks all contributors: Bernhard Kaindl, ChoKyuWon, Clément Guidi, Honggyu Kim, Kang Minchul, Khem Raj, Michelle Jin, Namhyung Kim, Sangwon Hong, Yi Hong, Yuri Gribov uftrace v0.13 ============= * new options -L/--loc-filter option to filter by source location (#1395) --mermaid option for uftrace dump (#1511) --no-sched-preempt option to disable preempted schedule events (#1587) * new features make -Z/--size-filter option work in general (#1600) optionally spawn a background agent to talk using a socket (#1543) add "size" field for uftrace report (#1495) * bug fixes handle different tracefs mount points (#1476) fix timestamp parsing for external data (#1549) * other changes add clang-format support change to use Github actions test both GCC and LLVM/clang (#1523) And many other fixes and improvements. Thanks all contributors: Andrew Woo, Clément Guidi, Daero Lee, Euiseo Park, Eunseon Lee, Honggyu Kim, JHH20, JaeSang Yoo, JeongWan Gho, Jia Ha, JongHyeon Hwang, Jonghyeon An, Joonho Seo, Kang Minchul, Namhyung Kim, Paran Lee, Sangwon Hong, Seonghee Jin, Seonghyun Park, Yuri Gribov, Yusun Choi, valdaarhun uftrace v0.12 ============= * new options --no-args to hide arguments and return values --clock option to set clock source other than monotonic * new features support events in uftrace script enable --with-syms for uftrace record adjust sample time for flame graph dump (#1257) * dynamic tracing updates support binaries built with -fpatchable-function-entry (#1238) documentation updates * bug fixes fix segfault in uftrace record on a large machine (#1418) various fixes for ARM build failures * other changes allow -s option to set a sort key for TUI report remove -L option for later use add func-histogram.py script a couple of update in the travis CI setup And many other fixes and improvements. Thanks all contributors: Eunseon Lee, Honggyu Kim, ihsinme, Kang Minchul, Nobuhiro Iwamatsu, Paul Cannon, Sangwon Hong, Thomas Petazzoni uftrace v0.11 ============= * new options --format=html to print a html page directly (#1308) --with-syms to replay/report with existing symbol data (#1228) * new features support note.txt in the data directory to put extra notes (#1307) improve debugging with stacktrace using libunwind (#1177) support customizing output fields in TUI report (#1270) * dynamic tracing updates support -mfentry -mnop-mcount on i386 (#1296) dynamic tracing for clang XRay v2 (#1265) SONAME support for library patterns in dynamic tracing (#1216) * bug fixes fix two similar struct arguments are used (#1281) fix struct argument is used in uftrace script (#1282) fix TUI info mode crash in a large screen fix crash in the USDT event tracing (#1286) * other changes update and fix build with python3 (#1271, #1344) ignore remaining schedule events in replay (#1293) enable undefined-behavior sanitizer (ubsan) with ASAN=1 (#1295) And many other fixes and improvements. Thanks all contributors: Bernhard Kaindl, Gabriel-Andrew Pollo-Guilbert, Hanbum Park Honggyu Kim, Kang Minchul, Kun-Chuan Hsieh, Michael Sartain Sangwon Hong uftrace v0.10 ============= * new options --estimate-return to avoid return address hooking -H/--hide to not display unwanted symbols --no-sched to suppress scheduler events --srcline supports report/replay/graph (by Eunseon) * dynamic tracing update (mostly by Hanbum) convert to hash map to find saved code it can trace functions in libraries loaded by dlopen() support patching MOV instruction in x86 * other changes convert from argparse to getopt_long check binary build-id (for binaries have same name) struct argument/return value (pass-by-value) support support customizing output fields in TUI graph (by Sangwon) * bug fixes fix floating-point register access on AArch64 fix issues with Intel CET technology on x86 handle zero PLT entry size on AArch64 fix crash when C++ object is passed indirectly And many other fixes and improvements. Thanks all contributors: Anas Blaboul, Daniel T. Lee, Dohyung Kim, Eunseon Lee, Hanbum Park, Honggyu Kim, Kang Minchul, Lei Maohui, Nathan Lanza, Sang-Heon Jeon, Sangwon Hong, SeokHoon Yoon, Sungho Bae, Tim Gates uftrace 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.15.2/README.md000066400000000000000000000404071455365734300145330ustar00rootroot00000000000000[![Nightly test](https://github.com/namhyung/uftrace/actions/workflows/nightly-test.yml/badge.svg)](https://github.com/namhyung/uftrace/actions/workflows/nightly-test.yml) [![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?logo=discord&logoColor=white)](https://discord.gg/MENMKaCWqD) uftrace ======= uftrace is a function call graph tracer for C, C++, Rust and Python programs. It hooks into the entry and exit of each function, recording timestamps as well as the function's arguments and return values. uftrace is capable of tracing both user and kernel functions, as well as library functions and system events providing an integrated execution flow in a single timeline. Initially, uftrace only supported function tracing with compiler support. However, it now allows users to trace function calls without recompilation by analyzing instructions in each function prologue and dynamically and selectively patching those instructions. Users can also write and run scripts for each function entry and exit using python/luajit APIs to create custom tools for their specific purposes. uftrace offers various filters to reduce the amount of trace data and provides visualization using Chrome trace viewer and flame graph or call-graph diagrams for graphviz and mermaid, allowing for a big picture view of the execution flow. It was heavily inspired by the ftrace framework of the Linux kernel and the name uftrace stems from the combination of user and ftrace. It can record data from: - User space C/C++/Rust functions, by either dynamically patching functions using `-P.`, or else selective NOP patching using code compiled with `-pg`, `-finstrument-functions` or `-fpatchable-function-entry=N`. - C/C++/Rust Library functions (through PLT hooking) - Python functions (using Python's trace/profile infrastructure) - Kernel functions (using the ftrace framework in Linux kernel) - Kernel trace events (using event tracing framework in Linux kernel) - Task creation, termination and scheduling events (using Linux perf_event) - User space events in the target binary or libraries (using SystemTap SDT ABI) - PMU counter values for given functions (using Linux perf_event) With the recorded data, uftrace can: - Show colored and nested function call graphs. - Show arguments and return values symbolically using libc function prototypes and DWARF debug information. - Apply filters to minimize the amount of trace data in both record and replay time. - Extract metadata from traces. (e.g. system information on which the trace was taken) - Generate symbol tables and memory maps of the traced program and library functions. - Generate task relationship trees (parent and children) of nested programs in traces. It supports many commands and filters such as filtering by function call duration for analysis of 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://discord.gg/MENMKaCWqD (Discord) * Mailing list: [uftrace@googlegroups.com](https://groups.google.com/forum/#!forum/uftrace) * Lightning talk: https://youtu.be/LNav5qvyK7I Features ======== uftrace traces each function in the executable and shows time durations. Usually, for this to be possible, the program needs to be compiled with `-pg` or `-fpatchable-function-entry=5` (`=2` is enough on aarch64). With full dynamic tracing (`-P.`|`--patch=.`), uftrace works on all executables (as long they are not stripped, or symbol information is available from a separate file). uftrace hooks into the PLT in the given executable file to trace library calls and with (`-l`|`--nest-libcall`), it also hooks into the procedure linkage tables (PLTs) of shared libraries. The depth can be limited using `-D`, where 1 is flat call tracing. Using (`-a`|`--auto-args`), uftrace automatically records arguments and return values of known functions. Without extra debug information, this includes the API functions of standard (C language or system) libraries. This can be combined with `-P.` or `-l`: For example, `-la` traces nested library calls, even in stripped executables. In addition, `-a` implies `--srcline`, so it records the source line location info, and this info can be shown by `uftrace replay --srcline` and in `uftrace tui`. Users can directly open the editor at the source location as shown in https://uftrace.github.io/slide/#120. If debug information for the program (`gcc -g`) is available, `--auto-args` works even on functions inside the compiled the user programs. In case argument information is not available, argument specifications like (`-A udev_new@arg1/s`) can be passed on the command line or an options file. Example: ```py $ uftrace record -la -A udev_new@arg1/s lsusb >/dev/null $ uftrace replay -f+module or simply: $ uftrace -la -A udev_new@arg1/s -f+module lsusb # -f+module adds the module name # DURATION TID MODULE NAME FUNCTION 306.339 us [ 23561] lsusb | setlocale(LC_TYPE, "") = "en_US.UTF-8"; 1.163 us [ 23561] lsusb | getopt_long(1, 0x7fff7175f6a8, "D:vtP:p:s:d:Vh") = -1; [ 23561] lsusb | udev_new("POSIXLY_CORRECT") { 0.406 us [ 23561] libudev.so.1.7.2 | malloc(16) = 0x55e07277a7b0; 2.620 us [ 23561] lsusb | } /* udev_new */ [ 23561] lsusb | udev_hwdb_new() { 0.427 us [ 23561] libudev.so.1.7.2 | calloc(1, 200) = 0x55e07277a7d0; 5.829 us [ 23561] libudev.so.1.7.2 | fopen64("/etc/systemd/hwdb/hwdb.bin", "re") = 0; ``` Furthermore, it can show detailed execution flow at function level, and report which functions had the longest execution time. It also shows information about the execution environment. You can set up filters to exclude or include specific functions when tracing. In addition, function arguments and return values can be saved and shown later. It supports multi-process and/or multi-threaded applications. With root privileges and if the kernel was built with `CONFIG_FUNCTION_GRAPH_TRACER=y`, kernel functions can be traced as well. How to build and install uftrace ================================ On Linux distros, [misc/install-deps.sh](misc/install-deps.sh) can be used to install required software(s) for building uftrace. Those are for optional and advanced features, but are highly recommended. $ 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 details about installation and dependencies, please refer to [INSTALL.md](INSTALL.md) How to use uftrace ================== These are the commands supported by uftrace: * [`record`](doc/uftrace-record.md) : runs a program and saves the trace data * [`replay`](doc/uftrace-replay.md) : shows program execution in the trace data * [`report`](doc/uftrace-report.md) : shows performance statistics in the trace data * [`live` ](doc/uftrace-live.md) : does record and replay in a row (default) * [`info` ](doc/uftrace-info.md) : shows system and program info in the trace data * [`dump` ](doc/uftrace-dump.md) : shows low-level trace data * [`recv` ](doc/uftrace-recv.md) : saves the trace data from network * [`graph` ](doc/uftrace-graph.md) : shows function call graph in the trace data * [`script`](doc/uftrace-script.md) : runs a script for recorded trace data * [`tui` ](doc/uftrace-tui.md) : show text user interface for graph and report You can use `-h` or `--help` option to see available [commands and options](doc/uftrace.md). $ uftrace uftrace -- function (graph) tracer for userspace usage: uftrace [COMMAND] [OPTION...] [] COMMAND: record Run a program and saves the trace data replay Show program execution in the trace data report Show performance statistics in the trace data live Do record and replay in a row (default) info Show system and program info in the trace data dump Show low-level trace data recv Save the trace data from network graph Show function call graph in the trace data script Run a script for recorded trace data tui Show text user interface for graph and report Try `uftrace --help' or `man uftrace [COMMAND]' 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, `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 system call handler for write() 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 3, 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 C/C++/Rust/Python application on Linux and Android. - It *cannot* trace an already running process yet. - It was *not* designed for system-wide tracing in mind. - It mainly supports x86_64, AArch64 architectures. It also works on x86 (32-bit), ARM (v6 and v7) but some features like dynamic tracing and automatic argument fetching might not work well. License ======= The uftrace program is released under GPL v2. See [COPYING file](COPYING) for details. uftrace-0.15.2/TODO000066400000000000000000000014431455365734300137410ustar00rootroot00000000000000- 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.15.2/arch/000077500000000000000000000000001455365734300141645ustar00rootroot00000000000000uftrace-0.15.2/arch/aarch64/000077500000000000000000000000001455365734300154145ustar00rootroot00000000000000uftrace-0.15.2/arch/aarch64/Makefile000066400000000000000000000016561455365734300170640ustar00rootroot00000000000000sdir := $(srcdir)/arch/aarch64 odir := $(objdir)/arch/aarch64 LINKFLAGS := -r -z noexecstack 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.15.2/arch/aarch64/cpuinfo.c000066400000000000000000000006641455365734300172310ustar00rootroot00000000000000#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.15.2/arch/aarch64/dynamic.S000066400000000000000000000042521455365734300171670ustar00rootroot00000000000000#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.15.2/arch/aarch64/fentry.S000066400000000000000000000020701455365734300170460ustar00rootroot00000000000000#include "utils/asm.h" .text /* universal stack constraint: (SP mod 16) == 0 */ /* frame pointer was saved in the trampoline */ GLOBAL(__fentry__) /* 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 */ 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 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 */ 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(__fentry__) uftrace-0.15.2/arch/aarch64/mcount-arch.h000066400000000000000000000022541455365734300200100ustar00rootroot00000000000000#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 #define HAVE_MCOUNT_ARCH_CONTEXT struct mcount_arch_context { double d[ARCH_MAX_FLOAT_REGS]; }; #define ARCH_PLT0_SIZE 32 #define ARCH_PLTHOOK_ADDR_OFFSET 0 /* index of module-ID in the PLTGOT table */ #define ARCH_PLTGOT_MOD_ID 1 /* index of resolver address in the PLTGOT table */ #define ARCH_PLTGOT_RESOLVE 2 /* number of reserved entries in the PLTGOT table */ #define ARCH_PLTGOT_OFFSET 3 struct mcount_disasm_engine; struct mcount_dynamic_info; struct mcount_disasm_info; #define NOP_INSN_SIZE 4 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.15.2/arch/aarch64/mcount-dynamic.c000066400000000000000000000175621455365734300205220ustar00rootroot00000000000000#include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "dynamic" #define PR_DOMAIN DBG_DYNAMIC #include "libmcount/dynamic.h" #include "libmcount/internal.h" #include "libmcount/mcount.h" #include "mcount-arch.h" #include "utils/rbtree.h" #include "utils/symbol.h" #include "utils/utils.h" #ifndef MAP_FIXED_NOREPLACE #define MAP_FIXED_NOREPLACE MAP_FIXED #endif #define CODE_SIZE 8 static const unsigned int patchable_nop_patt[] = { 0xd503201f, 0xd503201f }; static void save_orig_code(struct mcount_disasm_info *info) { 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; } /* make sure info.addr same as when called from __dentry__ */ mcount_save_code(info, CODE_SIZE, jmp_insn, jmp_insn_size); } int mcount_setup_trampoline(struct mcount_dynamic_info *mdi) { uintptr_t dentry_addr = (uintptr_t)(void *)&__dentry__; uintptr_t fentry_addr = (uintptr_t)(void *)&__fentry__; unsigned long page_offset; /* * 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, }; if (mdi->type == DYNAMIC_FENTRY_NOP || mdi->type == DYNAMIC_PATCHABLE) { trampoline[3] = fentry_addr; trampoline[4] = fentry_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)) { void *trampoline_check; mdi->trampoline += sizeof(trampoline); mdi->text_size += PAGE_SIZE; pr_dbg("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_NOREPLACE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (trampoline_check != (void *)mdi->trampoline) { pr_err("could not map trampoline at desired location %#lx, got %#lx: %m\n", mdi->trampoline, (uintptr_t)trampoline_check); } } page_offset = mdi->text_addr & (PAGE_SIZE - 1); if (mprotect((void *)(mdi->text_addr - page_offset), mdi->text_size + page_offset, 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 void read_patchable_loc(struct mcount_dynamic_info *mdi, struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter, unsigned long offset) { typeof(iter->shdr) *shdr = &iter->shdr; unsigned i; unsigned long *patchable_loc; unsigned long sh_addr; mdi->nr_patch_target = shdr->sh_size / sizeof(long); mdi->patch_target = xmalloc(shdr->sh_size); patchable_loc = mdi->patch_target; sh_addr = shdr->sh_addr; if (elf->ehdr.e_type == ET_DYN) sh_addr += offset; for (i = 0; i < mdi->nr_patch_target; i++) { unsigned long *entry = (unsigned long *)sh_addr + i; patchable_loc[i] = *entry - offset; } } void mcount_arch_find_module(struct mcount_dynamic_info *mdi, struct uftrace_symtab *symtab) { struct uftrace_elf_data elf; struct uftrace_elf_iter iter; unsigned i = 0; mdi->type = 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, PATCHABLE_SECT)) { mdi->type = DYNAMIC_PATCHABLE; read_patchable_loc(mdi, &elf, &iter, mdi->base_addr); goto out; } } /* * check first few functions have patchable function entry * signature. */ for (i = 0; i < symtab->nr_sym; i++) { struct uftrace_symbol *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; /* don't check special functions */ if (sym->name[0] == '_') continue; /* * there might be some chances of not having patchable section * '__patchable_function_entries' but shows the NOPs pattern. * this can be marked as DYNAMIC_FENTRY_NOP. */ if (!memcmp(code_addr, patchable_nop_patt, CODE_SIZE)) { mdi->type = DYNAMIC_FENTRY_NOP; goto out; } } switch (check_trace_functions(mdi->map->libname)) { case TRACE_MCOUNT: mdi->type = DYNAMIC_PG; break; default: break; } out: pr_dbg("dynamic patch type: %s: %d (%s)\n", basename(mdi->map->libname), mdi->type, mdi_type_names[mdi->type]); elf_finish(&elf); } static unsigned long get_target_addr(struct mcount_dynamic_info *mdi, unsigned long addr) { /* encode the target address of the trampoline */ return (mdi->trampoline - addr - 4) >> 2; } static int patch_code(struct mcount_dynamic_info *mdi, struct uftrace_symbol *sym) { uint32_t push = 0xa9bf7bfd; /* STP x29, x30, [sp, #-0x10]! */ uint32_t call; void *insn = (void *)sym->addr + mdi->map->start; call = get_target_addr(mdi, (unsigned long)insn); 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 int patch_patchable_func(struct mcount_dynamic_info *mdi, struct uftrace_symbol *sym) { void *insn = (void *)sym->addr + mdi->map->start; /* only support calls to 2 NOPs at the beginning */ if (memcmp(insn, patchable_nop_patt, sizeof(patchable_nop_patt))) { pr_dbg4("skip non-applicable functions: %s\n", sym->name); return INSTRUMENT_SKIPPED; } if (patch_code(mdi, sym) < 0) return INSTRUMENT_FAILED; pr_dbg3("update %p for '%s' function dynamically to call __fentry__\n", insn, sym->name); return INSTRUMENT_SUCCESS; } static int patch_normal_func(struct mcount_dynamic_info *mdi, struct uftrace_symbol *sym, struct mcount_disasm_engine *disasm) { struct mcount_disasm_info info = { .sym = sym, .addr = sym->addr + mdi->map->start, }; if (disasm_check_insns(disasm, mdi, &info) < 0) return INSTRUMENT_FAILED; save_orig_code(&info); if (patch_code(mdi, sym) < 0) return INSTRUMENT_FAILED; pr_dbg3("force patch normal func: %s (patch size: %d)\n", sym->name, info.orig_size); return INSTRUMENT_SUCCESS; } int mcount_patch_func(struct mcount_dynamic_info *mdi, struct uftrace_symbol *sym, struct mcount_disasm_engine *disasm, unsigned min_size) { int result = INSTRUMENT_SKIPPED; if (min_size < CODE_SIZE + 1) min_size = CODE_SIZE + 1; if (sym->size < min_size) return result; switch (mdi->type) { case DYNAMIC_PATCHABLE: case DYNAMIC_FENTRY_NOP: result = patch_patchable_func(mdi, sym); break; case DYNAMIC_NONE: result = patch_normal_func(mdi, sym, disasm); break; default: break; } return result; } static void revert_normal_func(struct mcount_dynamic_info *mdi, struct uftrace_symbol *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.15.2/arch/aarch64/mcount-insn.c000066400000000000000000000160221455365734300200330ustar00rootroot00000000000000#include "libmcount/dynamic.h" #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.15.2/arch/aarch64/mcount-support.c000066400000000000000000000151351455365734300206040ustar00rootroot00000000000000#include #include "libmcount/internal.h" #include "utils/filter.h" #include "utils/utils.h" /* FIXME: x0 is overwritten before calling _mcount() */ static 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; } static 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); mcount_memset4(ctx->val.v, 0, sizeof(ctx->val)); return; } addr += offset; if (check_mem_region(ctx, (unsigned long)addr)) mcount_memcpy4(ctx->val.v, addr, spec->size); else { pr_dbg("stack address is not allowed: %p\n", addr); mcount_memset4(ctx->val.v, 0, sizeof(ctx->val)); } } static void mcount_get_struct_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { struct uftrace_arg_spec reg_spec = { .type = ARG_TYPE_REG, }; void *ptr = ctx->val.p; int i; for (i = 0; i < spec->struct_reg_cnt; i++) { reg_spec.reg_idx = spec->struct_regs[i]; mcount_get_register_arg(ctx, ®_spec); mcount_memcpy4(ptr, ctx->val.v, sizeof(long)); ptr += sizeof(long); } if (spec->stack_ofs > 0) { unsigned long *addr = ctx->stack_base + spec->stack_ofs; /* * it cannot call mcount_get_stack_arg() since the struct * might be bigger than the ctx->val. It directly updates * the argument buffer (in the ptr). */ if (check_mem_region(ctx, (unsigned long)addr)) mcount_memcpy4(ptr, addr, spec->size); else { pr_dbg("stack address is not allowed: %p\n", addr); mcount_memset4(ptr, 0, spec->size); } } else if (spec->struct_reg_cnt == 0) { mcount_get_register_arg(ctx, spec); mcount_memcpy4(ptr, ctx->val.v, sizeof(long)); } } void mcount_arch_get_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { if (spec->fmt == ARG_FMT_STRUCT) { mcount_get_struct_arg(ctx, spec); return; } /* 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; if (spec->fmt == ARG_FMT_STRUCT) mcount_memcpy4(ctx->val.v, ctx->retval, sizeof(long)); /* type of return value cannot be FLOAT, so check format instead */ else 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 mcount_memcpy4(ctx->val.v, ctx->retval, spec->size); } unsigned long mcount_arch_plthook_addr(struct plthook_data *pd, int idx) { struct uftrace_symbol *sym; sym = &pd->dsymtab.sym[0]; return sym->addr - ARCH_PLT0_SIZE; } void mcount_save_arch_context(struct mcount_arch_context *ctx) { asm volatile("str d0, %0\n" : "=m"(ctx->d[0])); asm volatile("str d1, %0\n" : "=m"(ctx->d[1])); asm volatile("str d2, %0\n" : "=m"(ctx->d[2])); asm volatile("str d3, %0\n" : "=m"(ctx->d[3])); asm volatile("str d4, %0\n" : "=m"(ctx->d[4])); asm volatile("str d5, %0\n" : "=m"(ctx->d[5])); asm volatile("str d6, %0\n" : "=m"(ctx->d[6])); asm volatile("str d7, %0\n" : "=m"(ctx->d[7])); } void mcount_restore_arch_context(struct mcount_arch_context *ctx) { asm volatile("ldr d0, %0\n" ::"m"(ctx->d[0])); asm volatile("ldr d1, %0\n" ::"m"(ctx->d[1])); asm volatile("ldr d2, %0\n" ::"m"(ctx->d[2])); asm volatile("ldr d3, %0\n" ::"m"(ctx->d[3])); asm volatile("ldr d4, %0\n" ::"m"(ctx->d[4])); asm volatile("ldr d5, %0\n" ::"m"(ctx->d[5])); asm volatile("ldr d6, %0\n" ::"m"(ctx->d[6])); asm volatile("ldr d7, %0\n" ::"m"(ctx->d[7])); } uftrace-0.15.2/arch/aarch64/mcount.S000066400000000000000000000023441455365734300170500ustar00rootroot00000000000000#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 indirect result location */ stp x8, x18, [sp, #-16]! /* 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 indirect result location */ ldp x8, x18, [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]! str q0, [sp, #-16]! /* * save indirect result location register * used in C++ for returning non-trivial objects */ stp x8, x18, [sp, #-16]! add x0, sp, #32 bl mcount_exit mov x16, x0 /* restore indirect result location register */ ldp x8, x18, [sp], #16 /* restore return values */ ldr q0, [sp], #16 ldp x0, x1, [sp], #16 /* restore frame pointer */ ldp x29, x30, [sp], #16 br x16 END(mcount_return) uftrace-0.15.2/arch/aarch64/plthook.S000066400000000000000000000033471455365734300172270ustar00rootroot00000000000000/* * Based on glibc/ports/sysdeps/aarch64/dl-trampoline.S */ #include "utils/asm.h" .text .align 2 .macro save_args stp x8, x18, [sp, #-16]! 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 ldp x8, x18, [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 -= 80 */ add x0, sp, #104 ldr x1, [sp, #96] 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 */ stp x8, x18, [sp, #-16]! add x0, sp, #32 bl plthook_exit mov x16, x0 /* restore indirect result location register */ ldp x8, x18, [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.15.2/arch/arm/000077500000000000000000000000001455365734300147435ustar00rootroot00000000000000uftrace-0.15.2/arch/arm/Makefile000066400000000000000000000016461455365734300164120ustar00rootroot00000000000000sdir := $(srcdir)/arch/arm odir := $(objdir)/arch/arm LINKFLAGS := -r -z noexecstack 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.15.2/arch/arm/cpuinfo.c000066400000000000000000000010631455365734300165520ustar00rootroot00000000000000#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.15.2/arch/arm/mcount-arch.h000066400000000000000000000016631455365734300173420ustar00rootroot00000000000000#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 uftrace_sym_info; #define FIX_PARENT_LOC unsigned long *mcount_arch_parent_location(struct uftrace_sym_info *symtabs, unsigned long *parent_loc, unsigned long child_ip); #define ARCH_PLT0_SIZE 20 #define ARCH_PLTHOOK_ADDR_OFFSET 0 /* index of module-ID in the PLTGOT table */ #define ARCH_PLTGOT_MOD_ID 1 /* index of resolver address in the PLTGOT table */ #define ARCH_PLTGOT_RESOLVE 2 /* number of reserved entries in the PLTGOT table */ #define ARCH_PLTGOT_OFFSET 3 #define NOP_INSN_SIZE 4 #endif /* MCOUNT_ARCH_H */ uftrace-0.15.2/arch/arm/mcount-support.c000066400000000000000000000267401455365734300201370ustar00rootroot00000000000000#include #include #include #ifndef EF_ARM_VFP_FLOAT #define EF_ARM_VFP_FLOAT 0x400 #endif #ifndef EF_ARM_ABI_FLOAT_HARD #define EF_ARM_ABI_FLOAT_HARD EF_ARM_VFP_FLOAT #endif #include "libmcount/internal.h" #include "utils/filter.h" #include "utils/rbtree.h" #include "utils/symbol.h" #include "utils/utils.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 uftrace_sym_info *symtabs, unsigned long *parent_loc, unsigned long child_ip) { struct uftrace_symbol *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_dbg("cannot find a child symbol for %lx\n", child_ip); return parent_loc; } /* 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, d1 registers (64 bit) were saved below the r0 */ long *float_retval = ctx->retval - 4; mcount_memcpy4(ctx->val.v, float_retval, 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.15.2/arch/arm/mcount.S000066400000000000000000000060411455365734300163750ustar00rootroot00000000000000/* 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 .fpu vfpv2 vpush {d0-d1} #endif bl mcount_exit #if HAVE_ARM_HARDFP .fpu vfpv2 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.15.2/arch/arm/plthook.S000066400000000000000000000022631455365734300165520ustar00rootroot00000000000000/* * 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 .fpu vfpv2 vpush {d0-d1} #endif bl plthook_exit #ifdef HAVE_ARM_HARDFP .fpu vfpv2 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.15.2/arch/i386/000077500000000000000000000000001455365734300146555ustar00rootroot00000000000000uftrace-0.15.2/arch/i386/Makefile000066400000000000000000000016641455365734300163240ustar00rootroot00000000000000LINKFLAGS := -r -m elf_i386 -z noexecstack 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.15.2/arch/i386/common.S000066400000000000000000000001271455365734300162710ustar00rootroot00000000000000#include "utils/asm.h" ENTRY(get_pc_thunk) movl 0(%esp), %eax ret END(get_pc_thunk) uftrace-0.15.2/arch/i386/cpuinfo.c000066400000000000000000000005751455365734300164730ustar00rootroot00000000000000#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.15.2/arch/i386/fentry.S000066400000000000000000000015161455365734300163130ustar00rootroot00000000000000#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.15.2/arch/i386/mcount-arch.h000066400000000000000000000020141455365734300172430ustar00rootroot00000000000000#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 uftrace_sym_info *symtabs, unsigned long *parent_loc, unsigned long child_ip); #define ARCH_PLT0_SIZE 16 #define ARCH_PLTHOOK_ADDR_OFFSET 6 /* index of module-ID in the PLTGOT table */ #define ARCH_PLTGOT_MOD_ID 1 /* index of resolver address in the PLTGOT table */ #define ARCH_PLTGOT_RESOLVE 2 /* number of reserved entries in the PLTGOT table */ #define ARCH_PLTGOT_OFFSET 3 #define ARCH_CAN_RESTORE_PLTHOOK 1 #define CALL_INSN_SIZE 5 #define NOP_INSN_SIZE 1 #endif /* __MCOUNT_ARCH_H__ */ uftrace-0.15.2/arch/i386/mcount-dynamic.c000066400000000000000000000106701455365734300177540ustar00rootroot00000000000000#include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "dynamic" #define PR_DOMAIN DBG_DYNAMIC #include "libmcount/dynamic.h" #include "libmcount/internal.h" #include "utils/symbol.h" #include "utils/utils.h" #ifndef MAP_FIXED_NOREPLACE #define MAP_FIXED_NOREPLACE MAP_FIXED #endif static const unsigned char fentry_nop_patt[] = { 0x0f, 0x1f, 0x44, 0x00, 0x00 }; 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_NOREPLACE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (trampoline_check != (void *)mdi->trampoline) { pr_err("could not map trampoline at desired location %#lx, got %#lx: %m\n", mdi->trampoline, (uintptr_t)trampoline_check); } } 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_READ | PROT_EXEC)) pr_err("cannot restore trampoline due to protection"); } void mcount_arch_find_module(struct mcount_dynamic_info *mdi, struct uftrace_symtab *symtab) { unsigned i = 0; mdi->type = DYNAMIC_NONE; /* check first few functions have fentry signature */ for (i = 0; i < symtab->nr_sym; i++) { struct uftrace_symbol *sym = &symtab->sym[i]; void *code_addr = (unsigned char *)((uintptr_t)(sym->addr + mdi->map->start)); if (sym->type != ST_LOCAL_FUNC && sym->type != ST_GLOBAL_FUNC) continue; /* don't check special functions */ if (sym->name[0] == '_') continue; /* only support calls to __fentry__ at the beginning */ if (!memcmp(code_addr, fentry_nop_patt, CALL_INSN_SIZE)) { mdi->type = DYNAMIC_FENTRY_NOP; goto out; } } switch (check_trace_functions(mdi->map->libname)) { case TRACE_MCOUNT: mdi->type = DYNAMIC_PG; break; case TRACE_FENTRY: mdi->type = DYNAMIC_FENTRY; break; default: break; } out: pr_dbg("dynamic patch type: %s: %d (%s)\n", basename(mdi->map->libname), mdi->type, mdi_type_names[mdi->type]); } 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 uftrace_symbol *sym) { unsigned char *insn = (unsigned char *)((uintptr_t)(sym->addr + mdi->map->start)); unsigned int target_addr; /* only support calls to __fentry__ at the beginning */ if (memcmp(insn, fentry_nop_patt, sizeof(fentry_nop_patt))) { 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, (unsigned long)insn); 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 %p for '%s' function dynamically to call __fentry__\n", insn, sym->name); return 0; } int mcount_patch_func(struct mcount_dynamic_info *mdi, struct uftrace_symbol *sym, struct mcount_disasm_engine *disasm, unsigned min_size) { int result = INSTRUMENT_SKIPPED; if (min_size < CALL_INSN_SIZE + 1) min_size = CALL_INSN_SIZE + 1; if (sym->size < min_size) return result; switch (mdi->type) { case DYNAMIC_FENTRY_NOP: result = patch_fentry_func(mdi, sym); break; default: break; } return result; } uftrace-0.15.2/arch/i386/mcount-support.c000066400000000000000000000157231455365734300200500ustar00rootroot00000000000000/* * basic i386 support for uftrace * * Copyright (C) 2017. Hanbum Park * * Released under the GPL v2. */ #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 uftrace_sym_info *symtabs, unsigned long *parent_loc, unsigned long child_ip) { if (!search_main_ret) { struct uftrace_symbol *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; bool found_main_ret = false; int stack_index = 0; 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.. 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.15.2/arch/i386/mcount.S000066400000000000000000000017021455365734300163060ustar00rootroot00000000000000/* 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.15.2/arch/i386/plthook.S000066400000000000000000000021031455365734300164550ustar00rootroot00000000000000#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.15.2/arch/riscv64/000077500000000000000000000000001455365734300154645ustar00rootroot00000000000000uftrace-0.15.2/arch/riscv64/Makefile000066400000000000000000000016561455365734300171340ustar00rootroot00000000000000LINKFLAGS := -r -z noexecstack sdir := $(srcdir)/arch/riscv64 odir := $(objdir)/arch/riscv64 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.15.2/arch/riscv64/cpuinfo.c000066400000000000000000000006061455365734300172750ustar00rootroot00000000000000#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, "isa\t\t: rv64", 11)) { dprintf(fd, "cpuinfo:desc=RISCV64_%s", &buf[12]); ret = 0; break; } } fclose(fp); return ret; } uftrace-0.15.2/arch/riscv64/mcount-arch.h000066400000000000000000000024771455365734300200670ustar00rootroot00000000000000#ifndef MCOUNT_ARCH_H #define MCOUNT_ARCH_H #define mcount_regs mcount_regs struct mcount_regs { unsigned long a0; unsigned long a1; unsigned long a2; unsigned long a3; unsigned long a4; unsigned long a5; unsigned long a6; unsigned long a7; }; #define ARG1(x) ((x)->a0) #define ARG2(x) ((x)->a1) #define ARG3(x) ((x)->a2) #define ARG4(x) ((x)->a3) #define ARG5(x) ((x)->a4) #define ARG6(x) ((x)->a5) #define ARG7(x) ((x)->a6) #define ARG8(x) ((x)->a7) #define ARCH_MAX_REG_ARGS 8 #define ARCH_MAX_FLOAT_REGS 8 #define HAVE_MCOUNT_ARCH_CONTEXT struct mcount_arch_context { double f[ARCH_MAX_FLOAT_REGS]; }; #if defined(__riscv_compressed) #define NOP_INSN_SIZE 2 #else #define NOP_INSN_SIZE 4 #endif #define ARCH_PLT0_SIZE 32 #define ARCH_PLTHOOK_ADDR_OFFSET 0 /* index of module-ID in the PLTGOT table */ #define ARCH_PLTGOT_MOD_ID 1 /* index of resolver address in the PLTGOT table */ #define ARCH_PLTGOT_RESOLVE 0 /* number of reserved entries in the PLTGOT table */ #define ARCH_PLTGOT_OFFSET 2 /* TODO: not implemented yet (Start) */ 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); /* TODO: not implemented yet (End) */ #endif /* MCOUNT_ARCH_H */ uftrace-0.15.2/arch/riscv64/mcount-support.c000066400000000000000000000127241455365734300206550ustar00rootroot00000000000000#include #include "libmcount/internal.h" #include "utils/filter.h" #include "utils/utils.h" static 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_RISCV64_REG_FLOAT_BASE; break; case ARG_TYPE_STACK: default: return -1; } ctx->val.i = 0; switch (reg_idx) { case UFT_RISCV64_REG_A0: ctx->val.i = ARG1(regs); break; case UFT_RISCV64_REG_A1: ctx->val.i = ARG2(regs); break; case UFT_RISCV64_REG_A2: ctx->val.i = ARG3(regs); break; case UFT_RISCV64_REG_A3: ctx->val.i = ARG4(regs); break; case UFT_RISCV64_REG_A4: ctx->val.i = ARG5(regs); break; case UFT_RISCV64_REG_A5: ctx->val.i = ARG6(regs); break; case UFT_RISCV64_REG_A6: ctx->val.i = ARG7(regs); break; case UFT_RISCV64_REG_A7: ctx->val.i = ARG8(regs); break; case UFT_RISCV64_REG_FA0: asm volatile("fsd fa0, %0\n" : "=m"(ctx->val.v)); break; case UFT_RISCV64_REG_FA1: asm volatile("fsd fa1, %0\n" : "=m"(ctx->val.v)); break; case UFT_RISCV64_REG_FA2: asm volatile("fsd fa2, %0\n" : "=m"(ctx->val.v)); break; case UFT_RISCV64_REG_FA3: asm volatile("fsd fa3, %0\n" : "=m"(ctx->val.v)); break; case UFT_RISCV64_REG_FA4: asm volatile("fsd fa4, %0\n" : "=m"(ctx->val.v)); break; case UFT_RISCV64_REG_FA5: asm volatile("fsd fa5, %0\n" : "=m"(ctx->val.v)); break; case UFT_RISCV64_REG_FA6: asm volatile("fsd fa6, %0\n" : "=m"(ctx->val.v)); break; case UFT_RISCV64_REG_FA7: asm volatile("fsd fa7, %0\n" : "=m"(ctx->val.v)); break; default: return -1; } return 0; } static 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); mcount_memset4(ctx->val.v, 0, sizeof(ctx->val)); return; } addr += offset; if (check_mem_region(ctx, (unsigned long)addr)) { /* save long double arguments properly */ mcount_memcpy4(ctx->val.v, addr, ALIGN(spec->size, 4)); } else { pr_dbg("stack address is not allowed: %p\n", addr); mcount_memset4(ctx->val.v, 0, sizeof(ctx->val)); } } static void mcount_get_struct_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { struct uftrace_arg_spec reg_spec = { .type = ARG_TYPE_REG, }; void *ptr = ctx->val.p; int i; for (i = 0; i < spec->struct_reg_cnt; i++) { reg_spec.reg_idx = spec->struct_regs[i]; mcount_get_register_arg(ctx, ®_spec); mcount_memcpy4(ptr, ctx->val.v, sizeof(long)); ptr += sizeof(long); } if (spec->stack_ofs > 0) { unsigned long *addr = ctx->stack_base + spec->stack_ofs; /* * it cannot call mcount_get_stack_arg() since the struct * might be bigger than the ctx->val. It directly updates * the argument buffer (in the ptr). */ if (check_mem_region(ctx, (unsigned long)addr)) mcount_memcpy4(ptr, addr, spec->size); else { pr_dbg("stack address is not allowed: %p\n", addr); mcount_memset4(ptr, 0, spec->size); } } else if (spec->struct_reg_cnt == 0) { mcount_get_register_arg(ctx, spec); mcount_memcpy4(ptr, ctx->val.v, sizeof(long)); } } void mcount_arch_get_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { if (spec->fmt == ARG_FMT_STRUCT) { mcount_get_struct_arg(ctx, spec); return; } 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 (spec->fmt == ARG_FMT_STRUCT) mcount_memcpy4(ctx->val.v, ctx->retval, sizeof(long)); /* type of return value cannot be FLOAT, so check format instead */ else if (spec->fmt == ARG_FMT_FLOAT) { if (spec->size <= 4) { asm volatile("fsw fa0, %0\n" : "=m"(ctx->val.v)); } else { asm volatile("fsd fa0, %0\n" : "=m"(ctx->val.v)); } } else mcount_memcpy4(ctx->val.v, ctx->retval, spec->size); } void mcount_save_arch_context(struct mcount_arch_context *ctx) { asm volatile("fsd fa0, %0\n" : "=m"(ctx->f[0])); asm volatile("fsd fa1, %0\n" : "=m"(ctx->f[1])); asm volatile("fsd fa2, %0\n" : "=m"(ctx->f[2])); asm volatile("fsd fa3, %0\n" : "=m"(ctx->f[3])); asm volatile("fsd fa4, %0\n" : "=m"(ctx->f[4])); asm volatile("fsd fa5, %0\n" : "=m"(ctx->f[5])); asm volatile("fsd fa6, %0\n" : "=m"(ctx->f[6])); asm volatile("fsd fa7, %0\n" : "=m"(ctx->f[7])); } void mcount_restore_arch_context(struct mcount_arch_context *ctx) { asm volatile("fld fa0, %0\n" ::"m"(ctx->f[0])); asm volatile("fld fa1, %0\n" ::"m"(ctx->f[1])); asm volatile("fld fa2, %0\n" ::"m"(ctx->f[2])); asm volatile("fld fa3, %0\n" ::"m"(ctx->f[3])); asm volatile("fld fa4, %0\n" ::"m"(ctx->f[4])); asm volatile("fld fa5, %0\n" ::"m"(ctx->f[5])); asm volatile("fld fa6, %0\n" ::"m"(ctx->f[6])); asm volatile("fld fa7, %0\n" ::"m"(ctx->f[7])); } unsigned long mcount_arch_plthook_addr(struct plthook_data *pd, int idx) { return pd->plt_addr; } uftrace-0.15.2/arch/riscv64/mcount.S000066400000000000000000000023741455365734300171230ustar00rootroot00000000000000#include "utils/asm.h" .text GLOBAL(_mcount) /* setup frame pointer & return address */ addi sp, sp, -80 sd ra, 72(sp) sd fp, 64(sp) addi fp, sp, 80 /* save arguments */ sd a7, 56(sp) sd a6, 48(sp) sd a5, 40(sp) sd a4, 32(sp) sd a3, 24(sp) sd a2, 16(sp) sd a1, 8(sp) sd a0, 0(sp) /* parent location */ ld t1, 64(sp) addi t1, t1, -8 mv a0, t1 /* child addr */ mv a1, ra /* mcount_args */ mv a2, sp /* call mcount_entry func */ call mcount_entry /* restore argunents */ ld a0, 0(sp) ld a1, 8(sp) ld a2, 16(sp) ld a3, 24(sp) ld a4, 32(sp) ld a5, 40(sp) ld a6, 48(sp) ld a7, 56(sp) /* restore frame pointer */ ld fp, 64(sp) ld ra, 72(sp) addi sp, sp, 80 ret END(_mcount) ENTRY(mcount_return) /* setup frame pointer & return address */ addi sp, sp, -48 sd ra, 40(sp) sd fp, 32(sp) addi fp, sp, 48 /* save return values */ fsd fa0, 16(sp) sd a1, 8(sp) sd a0, 0(sp) /* set the first argument of mcount_exit as pointer to return values */ addi a0, sp, 0 /* call mcount_exit func */ call mcount_exit mv t1, a0 /* restore return values */ ld a0, 0(sp) ld a1, 8(sp) fld fa0, 16(sp) /* restore frame pointer */ ld fp, 32(sp) ld ra, 40(sp) addi sp, sp, 48 /* call return address */ jr t1 END(mcount_return) uftrace-0.15.2/arch/riscv64/plthook.S000066400000000000000000000031351455365734300172720ustar00rootroot00000000000000#include "utils/asm.h" .text /* * it gets called with: * t0: module id * t1: PLT index * 8 */ ENTRY(plt_hooker) /* setup frame pointer & return address */ addi sp, sp, -96 sd ra, 88(sp) sd fp, 80(sp) addi fp, sp, 96 /* temporary registers.. maybe t2, t3 too? */ sd t0, 72(sp) sd t1, 64(sp) /* save arguments */ sd a7, 56(sp) sd a6, 48(sp) sd a5, 40(sp) sd a4, 32(sp) sd a3, 24(sp) sd a2, 16(sp) sd a1, 8(sp) sd a0, 0(sp) /* parent location */ addi a0, sp, 88 /* child_index */ srli a1, t1, 3 /* module_id */ mv a2, t0 /* arguments */ mv a3, sp /* call mcount_entry func */ call plthook_entry /* save the actual function address */ mv t3, a0 /* restore argunents */ ld a0, 0(sp) ld a1, 8(sp) ld a2, 16(sp) ld a3, 24(sp) ld a4, 32(sp) ld a5, 40(sp) ld a6, 48(sp) ld a7, 56(sp) /* restore temp registers */ ld t1, 64(sp) ld t0, 72(sp) /* restore frame pointer */ ld fp, 80(sp) ld ra, 88(sp) addi sp, sp, 96 /* if plthook_entry returns 0, call the resolver */ bne t3, x0, .L1 la t3, plthook_resolver_addr ld t3, 0(t3) .L1: jr t3 END(plt_hooker) ENTRY(plthook_return) /* setup frame pointer & return address */ addi sp, sp, -48 sd ra, 40(sp) sd fp, 32(sp) addi fp, sp, 48 /* save return values */ fsd fa0, 16(sp) sd a1, 8(sp) sd a0, 0(sp) /* pass the return values */ mv a0, sp /* call plthook_exit func */ call plthook_exit mv t1, a0 /* restore return values */ ld a0, 0(sp) ld a1, 8(sp) fld fa0, 16(sp) /* restore frame pointer */ ld fp, 32(sp) ld ra, 40(sp) addi sp, sp, 48 /* call return address */ jr t1 END(plthook_return) uftrace-0.15.2/arch/x86_64/000077500000000000000000000000001455365734300151225ustar00rootroot00000000000000uftrace-0.15.2/arch/x86_64/Makefile000066400000000000000000000017161455365734300165670ustar00rootroot00000000000000LINKFLAGS := -r -z noexecstack 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.15.2/arch/x86_64/cpuinfo.c000066400000000000000000000005751455365734300167400ustar00rootroot00000000000000#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.15.2/arch/x86_64/dynamic.S000066400000000000000000000100521455365734300166700ustar00rootroot00000000000000/* * 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 /* align stack pointer to 16-byte */ andq $0xfffffffffffffff0, %rsp sub $16, %rsp /* save original stack pointer */ movq %rdi, (%rsp) /* returns original parent address */ call mcount_exit /* restore original stack pointer */ movq (%rsp), %rsp /* restore original return address in parent */ movq %rax, 88(%rsp) movq 0(%rsp), %rax movq 8(%rsp), %rdx movdqu 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.15.2/arch/x86_64/fentry.S000066400000000000000000000040371455365734300165610ustar00rootroot00000000000000/* 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 /* save scratch registers due to -fipa-ra */ push %r10 push %r11 call mcount_entry pop %r11 pop %r10 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.15.2/arch/x86_64/mcount-arch.h000066400000000000000000000032031455365734300175110ustar00rootroot00000000000000#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_ARGS 8 #define ARCH_NUM_BASE_REGS 8 #define HAVE_MCOUNT_ARCH_CONTEXT struct mcount_arch_context { double xmm[ARCH_MAX_FLOAT_ARGS]; }; #define ARCH_PLT0_SIZE 16 #define ARCH_PLTHOOK_ADDR_OFFSET 6 /* index of module-ID in the PLTGOT table */ #define ARCH_PLTGOT_MOD_ID 1 /* index of resolver address in the PLTGOT table */ #define ARCH_PLTGOT_RESOLVE 2 /* number of reserved entries in the PLTGOT table */ #define ARCH_PLTGOT_OFFSET 3 #define ARCH_SUPPORT_AUTO_RECOVER 1 #define ARCH_CAN_RESTORE_PLTHOOK 1 #define ARCH_TRAMPOLINE_SIZE 16 #define ARCH_BRANCH_ENTRY_SIZE ARCH_TRAMPOLINE_SIZE struct plthook_arch_context { bool has_plt_sec; }; struct mcount_disasm_engine; struct mcount_dynamic_info; struct mcount_disasm_info; #define CALL_INSN_SIZE 5 #define JMP_INSN_SIZE 6 /* indirect jump */ #define JCC8_INSN_SIZE 2 #define JMP32_INSN_SIZE 5 #define MOV_INSN_SIZE 10 /* move 8-byte immediate to reg */ #define ENDBR_INSN_SIZE 4 #define CET_JMP_INSN_SIZE 7 /* indirect jump + prefix */ #define NOP_INSN_SIZE 1 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.15.2/arch/x86_64/mcount-dynamic.c000066400000000000000000000516461455365734300202310ustar00rootroot00000000000000#include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "dynamic" #define PR_DOMAIN DBG_DYNAMIC #include "libmcount/dynamic.h" #include "libmcount/internal.h" #include "mcount-arch.h" #include "utils/symbol.h" #include "utils/utils.h" #ifndef MAP_FIXED_NOREPLACE #define MAP_FIXED_NOREPLACE MAP_FIXED #endif static const unsigned char fentry_nop_patt1[] = { 0x67, 0x0f, 0x1f, 0x04, 0x00 }; static const unsigned char fentry_nop_patt2[] = { 0x0f, 0x1f, 0x44, 0x00, 0x00 }; static const unsigned char patchable_gcc_nop[] = { 0x90, 0x90, 0x90, 0x90, 0x90 }; static const unsigned char patchable_clang_nop[] = { 0x0f, 0x1f, 0x44, 0x00, 0x08 }; static const unsigned char endbr64[] = { 0xf3, 0x0f, 0x1e, 0xfa }; int mcount_setup_trampoline(struct mcount_dynamic_info *mdi) { unsigned char trampoline[] = { 0x3e, 0xff, 0x25, 0x01, 0x00, 0x00, 0x00, 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; size_t trampoline_size = 16; void *trampoline_check; if (mdi->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_NOREPLACE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (trampoline_check != (void *)mdi->trampoline) { pr_err("could not map trampoline at desired location %#lx, got %#lx: %m\n", mdi->trampoline, (uintptr_t)trampoline_check); } } 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 (mdi->type == DYNAMIC_XRAY) { /* jmpq *0x1(%rip) # */ memcpy((void *)mdi->trampoline, trampoline, sizeof(trampoline)); memcpy((void *)mdi->trampoline + sizeof(trampoline), &xray_entry_addr, sizeof(xray_entry_addr)); /* jmpq *0x1(%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 (mdi->type == DYNAMIC_FENTRY_NOP || mdi->type == DYNAMIC_PATCHABLE) { /* jmpq *0x1(%rip) # */ memcpy((void *)mdi->trampoline, trampoline, sizeof(trampoline)); memcpy((void *)mdi->trampoline + sizeof(trampoline), &fentry_addr, sizeof(fentry_addr)); } else if (mdi->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_READ | PROT_EXEC)) pr_err("cannot restore trampoline due to protection"); } static void read_xray_map(struct mcount_dynamic_info *mdi, struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter, unsigned long offset) { struct xray_instr_map *xrmap; unsigned i; typeof(iter->shdr) *shdr = &iter->shdr; mdi->nr_patch_target = shdr->sh_size / sizeof(*xrmap); mdi->patch_target = xmalloc(mdi->nr_patch_target * sizeof(*xrmap)); elf_get_secdata(elf, iter); elf_read_secdata(elf, iter, 0, mdi->patch_target, shdr->sh_size); for (i = 0; i < mdi->nr_patch_target; i++) { xrmap = &((struct xray_instr_map *)mdi->patch_target)[i]; if (xrmap->version == 2) { xrmap->address += offset + (shdr->sh_offset + i * sizeof(*xrmap)); xrmap->function += offset + (shdr->sh_offset + i * sizeof(*xrmap) + 8); } else if (elf->ehdr.e_type == ET_DYN) { xrmap->address += offset; xrmap->function += offset; } } } static void read_mcount_loc(struct mcount_dynamic_info *mdi, struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter, unsigned long offset) { typeof(iter->shdr) *shdr = &iter->shdr; mdi->nr_patch_target = shdr->sh_size / sizeof(long); mdi->patch_target = xmalloc(shdr->sh_size); elf_get_secdata(elf, iter); elf_read_secdata(elf, iter, 0, mdi->patch_target, shdr->sh_size); /* symbol has relative address, fix it to match each other */ if (elf->ehdr.e_type == ET_EXEC) { unsigned long *mcount_loc = mdi->patch_target; unsigned i; for (i = 0; i < mdi->nr_patch_target; i++) { mcount_loc[i] -= offset; } } } static void read_patchable_loc(struct mcount_dynamic_info *mdi, struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter, unsigned long offset) { typeof(iter->shdr) *shdr = &iter->shdr; unsigned i; unsigned long *patchable_loc; unsigned long sh_addr; mdi->nr_patch_target = shdr->sh_size / sizeof(long); mdi->patch_target = xmalloc(shdr->sh_size); patchable_loc = mdi->patch_target; sh_addr = shdr->sh_addr; if (elf->ehdr.e_type == ET_DYN) sh_addr += offset; for (i = 0; i < mdi->nr_patch_target; i++) { unsigned long *entry = (unsigned long *)sh_addr + i; patchable_loc[i] = *entry - offset; } } void mcount_arch_find_module(struct mcount_dynamic_info *mdi, struct uftrace_symtab *symtab) { struct uftrace_elf_data elf; struct uftrace_elf_iter iter; unsigned i = 0; mdi->type = 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, PATCHABLE_SECT)) { mdi->type = DYNAMIC_PATCHABLE; read_patchable_loc(mdi, &elf, &iter, mdi->base_addr); goto out; } if (!strcmp(shstr, XRAY_SECT)) { mdi->type = DYNAMIC_XRAY; read_xray_map(mdi, &elf, &iter, mdi->base_addr); goto out; } if (!strcmp(shstr, MCOUNTLOC_SECT)) { read_mcount_loc(mdi, &elf, &iter, mdi->base_addr); /* still needs to check pg or fentry */ } } /* * check first few functions have fentry or patchable function entry * signature. */ for (i = 0; i < symtab->nr_sym; i++) { struct uftrace_symbol *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; /* don't check special functions */ if (sym->name[0] == '_') continue; /* * there might be some chances of not having patchable section * '__patchable_function_entries' but shows the NOPs pattern. * this can be treated as DYNAMIC_FENTRY_NOP. */ if (!memcmp(code_addr, patchable_gcc_nop, CALL_INSN_SIZE) || !memcmp(code_addr, patchable_clang_nop, CALL_INSN_SIZE)) { mdi->type = DYNAMIC_FENTRY_NOP; goto out; } /* 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)) { mdi->type = DYNAMIC_FENTRY_NOP; goto out; } } switch (check_trace_functions(mdi->map->libname)) { case TRACE_MCOUNT: mdi->type = DYNAMIC_PG; break; case TRACE_FENTRY: mdi->type = DYNAMIC_FENTRY; break; default: break; } out: pr_dbg("dynamic patch type: %s: %d (%s)\n", basename(mdi->map->libname), mdi->type, mdi_type_names[mdi->type]); 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_code(struct mcount_dynamic_info *mdi, struct uftrace_symbol *sym) { unsigned char *insn = (void *)sym->addr + mdi->map->start; unsigned int target_addr; /* skip 'endbr64' instruction, which is inserted by (implicit) -fcf-protection option. */ if (!memcmp(insn, endbr64, sizeof(endbr64))) insn += sizeof(endbr64); /* support patchable function entry and __fentry__ at the beginning */ if (memcmp(insn, patchable_gcc_nop, sizeof(patchable_gcc_nop)) && memcmp(insn, patchable_clang_nop, sizeof(patchable_clang_nop)) && memcmp(insn, fentry_nop_patt1, sizeof(fentry_nop_patt1)) && memcmp(insn, fentry_nop_patt2, sizeof(fentry_nop_patt2))) { pr_dbg4("skip non-applicable functions: %s\n", sym->name); return INSTRUMENT_SKIPPED; } /* 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 %p for '%s' function dynamically to call __fentry__\n", insn, sym->name); return INSTRUMENT_SUCCESS; } static int patch_fentry_func(struct mcount_dynamic_info *mdi, struct uftrace_symbol *sym) { return patch_fentry_code(mdi, sym); } static int patch_patchable_func(struct mcount_dynamic_info *mdi, struct uftrace_symbol *sym) { /* it does the same patch logic with fentry. */ return patch_fentry_code(mdi, sym); } static int update_xray_code(struct mcount_dynamic_info *mdi, struct uftrace_symbol *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->address; union { unsigned long word; char bytes[8]; } patch; if (memcmp(func + 2, pad, sizeof(pad))) return INSTRUMENT_FAILED; if (xrmap->kind == 0) { /* ENTRY */ if (memcmp(func, entry_insn, sizeof(entry_insn))) return INSTRUMENT_FAILED; target_addr = mdi->trampoline - (xrmap->address + 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->address + 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 %p for '%s' function %s dynamically to call xray functions\n", func, sym->name, xrmap->kind == 0 ? "entry" : "exit "); return INSTRUMENT_SUCCESS; } static int patch_xray_func(struct mcount_dynamic_info *mdi, struct uftrace_symbol *sym) { unsigned i; int ret = -2; 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 < mdi->nr_patch_target; i++) { xrmap = &((struct xray_instr_map *)mdi->patch_target)[i]; if (xrmap->address < sym_addr || xrmap->address >= sym_addr + sym->size) continue; while ((ret = update_xray_code(mdi, sym, xrmap)) == 0) { if (i == mdi->nr_patch_target - 1) break; i++; if (xrmap->function != xrmap[1].function) 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, struct mcount_disasm_info *info) { void *origin_code_addr; unsigned char call_insn[] = { 0xe8, 0x00, 0x00, 0x00, 0x00 }; uint32_t target_addr = get_target_addr(mdi, info->addr); /* patch address */ origin_code_addr = (void *)info->addr; if (info->has_intel_cet) { origin_code_addr += ENDBR_INSN_SIZE; target_addr = get_target_addr(mdi, info->addr + ENDBR_INSN_SIZE); } /* 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 */ info->orig_size - CALL_INSN_SIZE); /* flush icache so that cpu can execute the new insn */ __builtin___clear_cache(origin_code_addr, origin_code_addr + info->orig_size); } static int patch_normal_func(struct mcount_dynamic_info *mdi, struct uftrace_symbol *sym, struct mcount_disasm_engine *disasm) { uint8_t jmp_insn[15] = { 0x3e, 0xff, 0x25, }; uint64_t jmp_target; struct mcount_disasm_info info = { .sym = sym, .addr = mdi->map->start + sym->addr, }; unsigned call_offset = CALL_INSN_SIZE; int state; state = disasm_check_insns(disasm, mdi, &info); if (state != INSTRUMENT_SUCCESS) { pr_dbg3(" >> %s: %s\n", state == INSTRUMENT_FAILED ? "FAIL" : "SKIP", sym->name); return state; } pr_dbg2("force 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; if (info.has_intel_cet) { jmp_target += ENDBR_INSN_SIZE; call_offset += ENDBR_INSN_SIZE; } memcpy(jmp_insn + CET_JMP_INSN_SIZE, &jmp_target, sizeof(jmp_target)); if (info.has_jump) mcount_save_code(&info, call_offset, jmp_insn, 0); else mcount_save_code(&info, call_offset, jmp_insn, sizeof(jmp_insn)); patch_code(mdi, &info); 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((void *)insn, (void *)insn + nop_size); return INSTRUMENT_SUCCESS; } static int unpatch_fentry_func(struct mcount_dynamic_info *mdi, struct uftrace_symbol *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 uftrace_symbol *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 uftrace_symbol *sym) { unsigned long *mcount_loc = mdi->patch_target; uintptr_t *loc; if (mdi->nr_patch_target != 0) { loc = bsearch(sym, mcount_loc, mdi->nr_patch_target, sizeof(*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 uftrace_symbol *sym, struct mcount_disasm_engine *disasm, unsigned min_size) { int result = INSTRUMENT_SKIPPED; if (min_size < CALL_INSN_SIZE + 1) min_size = CALL_INSN_SIZE + 1; if (sym->size < min_size) return result; switch (mdi->type) { case DYNAMIC_XRAY: result = patch_xray_func(mdi, sym); break; case DYNAMIC_FENTRY_NOP: result = patch_fentry_func(mdi, sym); break; case DYNAMIC_PATCHABLE: result = patch_patchable_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 uftrace_symbol *sym, struct mcount_disasm_engine *disasm) { int result = INSTRUMENT_SKIPPED; switch (mdi->type) { case DYNAMIC_FENTRY: case DYNAMIC_PATCHABLE: 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 uftrace_symbol *sym, struct mcount_disasm_engine *disasm) { void *addr = (void *)(uintptr_t)sym->addr + mdi->map->start; struct mcount_orig_insn *moi; if (!memcmp(addr, endbr64, sizeof(endbr64))) addr += sizeof(endbr64); 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); } } static bool addr_in_prologue(struct mcount_disasm_info *info, unsigned long addr) { return info->addr <= addr && addr < (info->addr + info->orig_size); } int mcount_arch_branch_table_size(struct mcount_disasm_info *info) { struct cond_branch_info *jcc_info; int count = 0; int i; for (i = 0; i < info->nr_branch; i++) { jcc_info = &info->branch_info[i]; /* no need to allocate entry for jcc that jump directly to prologue */ if (addr_in_prologue(info, jcc_info->branch_target)) continue; count++; } return count * ARCH_BRANCH_ENTRY_SIZE; } void mcount_arch_patch_branch(struct mcount_disasm_info *info, struct mcount_orig_insn *orig) { /* * The first entry in the table starts right after the out-of-line * execution buffer. */ uint64_t entry_offset = orig->insn_size; uint8_t trampoline[ARCH_TRAMPOLINE_SIZE] = { 0x3e, 0xff, 0x25, }; struct cond_branch_info *jcc_info; unsigned long jcc_target; unsigned long jcc_index; uint32_t disp; int i; for (i = 0; i < info->nr_branch; i++) { jcc_info = &info->branch_info[i]; jcc_target = jcc_info->branch_target; jcc_index = jcc_info->insn_index; /* leave the original disp of jcc that target the prologue as it is */ if (addr_in_prologue(info, jcc_target)) { jcc_target -= jcc_info->insn_addr + jcc_info->insn_size; info->insns[jcc_index + 1] = jcc_target; continue; } /* setup the branch entry trampoline */ memcpy(trampoline + CET_JMP_INSN_SIZE, &jcc_target, sizeof(jcc_target)); /* write the entry to the branch table */ memcpy(orig->insn + entry_offset, trampoline, sizeof(trampoline)); /* previously, all jcc32 are downgraded to jcc8 */ disp = entry_offset - (jcc_index + JCC8_INSN_SIZE); if (disp > SCHAR_MAX) { /* should not happen */ pr_err("target is not in reach"); } /* patch jcc displacement to target corresponding entry in the table */ info->insns[jcc_index + 1] = disp; entry_offset += ARCH_BRANCH_ENTRY_SIZE; } } uftrace-0.15.2/arch/x86_64/mcount-event.c000066400000000000000000000027411455365734300177160ustar00rootroot00000000000000#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 #ifndef PAGE_SIZE #define PAGE_SIZE 4096 #endif #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, then it will fall into sdt_handler() above. */ memset((void *)mei->addr, INVALID_OPCODE, 1); if (mprotect(PAGE_ADDR(mei->addr), PAGE_SIZE, PROT_READ | PROT_EXEC)) pr_err("cannot setup event due to protection"); return 0; } uftrace-0.15.2/arch/x86_64/mcount-insn.c000066400000000000000000000742231455365734300175500ustar00rootroot00000000000000/* This should be defined before #include "utils.h" */ #define PR_FMT "dynamic" #define PR_DOMAIN DBG_DYNAMIC #include "libmcount/dynamic.h" #include "libmcount/internal.h" #include "mcount-arch.h" #include "utils/utils.h" #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_NO_DETAIL = (1U << 0), INSTRUMENT_FAIL_RELJMP = (1U << 1), INSTRUMENT_FAIL_RELCALL = (1U << 2), INSTRUMENT_FAIL_PIC = (1U << 3), INSTRUMENT_FAIL_RET = (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_RELJMP) pr_dbg3("prologue has relative jump\n"); if (reason & INSTRUMENT_FAIL_RELCALL) pr_dbg3("prologue has (relative) call\n"); if (reason & INSTRUMENT_FAIL_PIC) pr_dbg3("prologue has PC-relative addressing\n"); if (reason & INSTRUMENT_FAIL_RET) pr_dbg3("prologue has return instruction\n"); } static int x86_reg_index(int capstone_reg) { int x86_regs[] = { X86_REG_RAX, X86_REG_RCX, X86_REG_RDX, X86_REG_RBX, X86_REG_RSP, X86_REG_RBP, X86_REG_RSI, X86_REG_RDI, 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 < ARRAY_SIZE(x86_regs); i++) { if (capstone_reg == x86_regs[i]) return i; } return -1; } /* * Handle relative conditional jumps and relative unconditional jumps. * * This function relocates jcc8 and jcc32 instructions by replacing them with a jcc8 * that has a null offset. The offset will be patched later when the code is saved * in the of line execution buffer. The new jcc8 will bounce (if condition is met) * on a trampoline that jumps to the target of the original instruction. * * The relocation of jmp8 and jmp32 is achieved by replacing them with an absolute * indirect jump to the target. * */ static int handle_rel_jmp(cs_insn *insn, uint8_t insns[], struct mcount_dynamic_info *mdi, struct mcount_disasm_info *info) { cs_x86 *x86 = &insn->detail->x86; uint8_t relocated_insn[ARCH_TRAMPOLINE_SIZE] = { 0xff, 0x25, }; uint8_t opcode = insn->bytes[0]; uint64_t target; struct cond_branch_info *cbi; cs_x86_op *opnd = &x86->operands[0]; #define JMP8_OPCODE 0xEB #define JMP32_OPCODE 0xE9 #define OP 0 #define OFS 1 if (x86->op_count != 1 || opnd->type != X86_OP_IMM) goto out; target = opnd->imm; /* disallow jump to middle of other function */ if (info->addr > target || target >= info->addr + info->sym->size) { /* also mark the target function as invalid */ if (mcount_add_badsym(mdi, insn->address, target)) goto out; } if (opcode == JMP8_OPCODE || opcode == JMP32_OPCODE) { if (strcmp(insn->mnemonic, "jmp") != 0) goto out; memcpy(insns, relocated_insn, JMP_INSN_SIZE); memcpy(insns + JMP_INSN_SIZE, &target, sizeof(target)); info->modified = true; /* * If this jump is the last insn in the prologue, we can ignore * the one in patch_normal_func() */ if (info->orig_size + insn->size >= JMP32_INSN_SIZE) info->has_jump = true; return JMP_INSN_SIZE + sizeof(target); } /* Jump relative 8 if condition is met (except JCXZ, JECXZ and JRCXZ) */ else if ((opcode & 0xF0) == 0x70) { cbi = &info->branch_info[info->nr_branch++]; cbi->insn_index = info->copy_size; cbi->branch_target = target; cbi->insn_addr = insn->address; cbi->insn_size = insn->size; relocated_insn[OP] = opcode; relocated_insn[OFS] = 0x00; memcpy(insns, (void *)relocated_insn, JCC8_INSN_SIZE); info->modified = true; return JCC8_INSN_SIZE; } /* Jump relative 32 if condition is met */ else if (opcode == 0x0F && (insn->bytes[1] & 0xF0) == 0x80) { cbi = &info->branch_info[info->nr_branch++]; cbi->insn_index = info->copy_size; cbi->branch_target = target; cbi->insn_addr = insn->address; cbi->insn_size = insn->size; /* We use the equivalent jcc8 of the original jcc32 */ relocated_insn[OP] = insn->bytes[1] - 0x10; relocated_insn[OFS] = 0x00; memcpy(insns, (void *)relocated_insn, JCC8_INSN_SIZE); info->modified = true; return JCC8_INSN_SIZE; } 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; } /* * handle LEA instruction. * * this function manipulate the instruction like below, * lea rcx, qword ptr [rip + 0x8f3f85] * to this. * mov rcx, [calculated PC + 0x8f3f85] */ static int handle_lea(cs_insn *insn, uint8_t insns[], struct mcount_disasm_info *info) { cs_x86 *x86 = &insn->detail->x86; cs_x86_op *opnd1; cs_x86_op *opnd2; uint64_t target; /* * array for mov instruction: REX + OPCODE + IMM(8-byte) * ex) mov rbx, 0x555556d35690 */ uint8_t mov_insns[MOV_INSN_SIZE] = { 0x48, 0xb8, }; int reg; #define REX 0 #define OPC 1 #define IMM 2 /* according to intel manual, lea instruction takes 2 operand */ opnd1 = &x86->operands[0]; opnd2 = &x86->operands[1]; /* check PC-relative addressing mode */ if (opnd1->type != X86_OP_REG || 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; reg = x86_reg_index(opnd1->reg); if (reg < 0) goto out; /* set register index (high bit) */ if (reg >= ARCH_NUM_BASE_REGS) mov_insns[REX]++; /* set register index (least 3 bits only) */ mov_insns[OPC] |= reg & (ARCH_NUM_BASE_REGS - 1); /* update target address (PC + disp) */ target = insn->address + insn->size + opnd2->mem.disp; memcpy(&mov_insns[IMM], &target, sizeof(target)); memcpy(insns, mov_insns, sizeof(mov_insns)); info->modified = true; return sizeof(mov_insns); out: return -1; } /* * handle MOV instruction (LOAD). * * this function changes addressing mode of the instruction to use 8-byte * immediate value. For now it handles the case when the destination is a * 64-bit register. For example, it'd change the following instruction * * MOV rdi, qword ptr [rip + 0x8f3f85] * * to this. * * MOV rdi, [calculated PC + 0x8f3f85] (= LEA) * MOV rdi, qword ptr [rdi] */ static int handle_mov(cs_insn *insn, uint8_t insns[], struct mcount_disasm_info *info) { cs_x86 *x86 = &insn->detail->x86; uint8_t mov_insn[3] = { 0x48, 0x8b }; int insn_size = sizeof(mov_insn); int reg; if (x86->rex) { /* we only support it when the destination is a register like LEA */ if (handle_lea(insn, insns, info) < 0) goto out; reg = x86_reg_index(x86->operands[0].reg); if (reg < 0) goto out; /* set register index (high bit) */ if (reg >= ARCH_NUM_BASE_REGS) mov_insn[REX] += 5; /* now we only care about the lower 3 bits */ reg &= ARCH_NUM_BASE_REGS - 1; /* not support RSP, RBP, R12 and R13 due to addressing mode constraints */ if (reg == 4 || reg == 5) return -1; /* set register index */ mov_insn[2] = (reg << 3) | reg; /* modrm.{reg,rm}*/ /* skip the part handle_lea() added (= MOV_INSN_SIZE) */ memcpy(insns + MOV_INSN_SIZE, mov_insn, insn_size); } else { uint8_t opcode = insn->bytes[0]; /* this is actually MOVABS but we can think as LEA */ uint8_t lea_insns[MOV_INSN_SIZE] = { 0x48, 0xb8, }; uint64_t target; #define MOV8_OPCODE 0x8a #define MOV32_OPCODE 0x8b /* ignore insns with prefixes */ if (opcode != MOV8_OPCODE && opcode != MOV32_OPCODE) goto out; /* extract modrm.reg */ reg = (insn->bytes[1] >> 3) & 7; /* not support ESP, EBP due to addressing mode constraints */ if (reg == 4 || reg == 5) return -1; lea_insns[OPC] |= reg; /* update target address (PC + disp) */ target = insn->address + insn->size + x86->operands[1].mem.disp; memcpy(&lea_insns[IMM], &target, sizeof(target)); memcpy(insns, lea_insns, sizeof(lea_insns)); /* skip REX prefix */ insn_size--; mov_insn[1] = opcode; /* set register index */ mov_insn[2] = (reg << 3) | reg; /* modrm.{reg,rm}*/ memcpy(insns + sizeof(lea_insns), &mov_insn[1], insn_size); } info->modified = true; return MOV_INSN_SIZE + insn_size; out: return -1; } /* handle position independent code (PIC) */ static int handle_pic(cs_insn *insn, uint8_t insns[], struct mcount_disasm_info *info) { if (insn->id == X86_INS_LEA) return handle_lea(insn, insns, info); if (insn->id == X86_INS_MOV && insn->detail->x86.operands[0].type == X86_OP_REG) return handle_mov(insn, insns, info); return -1; } static int manipulate_insns(cs_insn *insn, uint8_t insns[], int *fail_reason, struct mcount_dynamic_info *mdi, struct mcount_disasm_info *info) { int res; pr_dbg3("manipulate instructions having PC-relative addressing.\n"); switch (*fail_reason) { case INSTRUMENT_FAIL_RELJMP: res = handle_rel_jmp(insn, insns, mdi, info); break; case INSTRUMENT_FAIL_RELCALL: res = handle_call(insn, insns, info); break; case INSTRUMENT_FAIL_PIC: res = handle_pic(insn, insns, info); break; default: res = -1; break; } if (res > 0) *fail_reason = 0; 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; pr_dbg4("check_instrumentable: %s %s (status = %d)\n", insn->mnemonic, insn->op_str, status); /* * 'detail' can be NULL on "data" instruction * if SKIPDATA option is turned ON */ if (insn->detail == NULL) { status = INSTRUMENT_FAIL_NO_DETAIL; goto out; } detail = insn->detail; for (i = 0; i < detail->groups_count; i++) { switch (detail->groups[i]) { case CS_GRP_CALL: check_branch = OP_GROUP_CALL; break; case CS_GRP_JUMP: check_branch = OP_GROUP_JMP; break; case CS_GRP_RET: case CS_GRP_IRET: return INSTRUMENT_FAIL_RET; #if CS_API_MAJOR >= 4 case CS_GRP_PRIVILEGE: case CS_GRP_BRANCH_RELATIVE: return INSTRUMENT_FAIL_NO_DETAIL; #endif default: /* do nothing for legit instructions. */ break; } } 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_PIC; goto out; } continue; default: continue; } } out: 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) { pr_dbg4("jump to prologue: addr=%lx, target=%lx\n", insn->address - mdi->map->start, target - mdi->map->start); 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 */ if (!mcount_add_badsym(mdi, insn->address, target)) { /* it was actually ok (like tail call) */ return true; } pr_dbg4("jump to middle of function: addr=%lx, target=%lx\n", insn->address - mdi->map->start, target - mdi->map->start); return false; } break; 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; unsigned long addr = info->addr; badsym = mcount_find_badsym(mdi, info->addr); if (badsym != NULL) { badsym->reverted = true; return INSTRUMENT_FAILED; } /* * some compilers split cold part of the code into a separate function * and it's likely to have a jump into original function body. We need * to skip those functions and allow the original function. */ size = strlen(info->sym->name); if (size > 5 && !strcmp(info->sym->name + size - 5, ".cold")) return INSTRUMENT_SKIPPED; size = info->sym->size; if (!memcmp((void *)info->addr, endbr64, sizeof(endbr64))) { addr += sizeof(endbr64); size -= sizeof(endbr64); if (size <= CALL_INSN_SIZE) return INSTRUMENT_SKIPPED; info->has_intel_cet = true; } count = cs_disasm(disasm->engine, (void *)addr, size, addr, 0, &insn); if (count == 0) return INSTRUMENT_FAILED; 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, mdi, info); else size = copy_insn_bytes(&insn[i], insns_byte); if (status > 0) { print_instrument_fail_msg(status); status = INSTRUMENT_FAILED; 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; } #ifdef UNIT_TEST #define ORIGINAL_BASE 0x111122220000 #define CODEPAGE_BASE 0x555566660000 TEST_CASE(dynamic_x86_handle_lea) { struct uftrace_symbol sym = { .name = "abc", .addr = 0x3000, .size = 32, }; struct mcount_disasm_engine disasm; struct mcount_disasm_info info = { .sym = &sym, .addr = ORIGINAL_BASE + sym.addr, }; int count; cs_insn *insn = NULL; cs_x86 *x86; uint8_t lea_insn[7] = { 0x48, 0x8d, 0x05, 0x60, }; /* lea 0x60(%rip),%rax */ uint8_t new_insns[16]; int new_size; mcount_disasm_init(&disasm); pr_dbg("running capstone disassemler for LEA instruction\n"); count = cs_disasm(disasm.engine, lea_insn, sizeof(lea_insn), info.addr, 0, &insn); TEST_EQ(count, 1); x86 = &insn->detail->x86; TEST_EQ(insn->id, X86_INS_LEA); TEST_EQ(insn->address, info.addr); TEST_EQ(x86->op_count, 2); TEST_EQ(x86->operands[0].type, X86_OP_REG); TEST_EQ(x86->operands[0].reg, X86_REG_RAX); TEST_EQ(x86->operands[1].type, X86_OP_MEM); TEST_EQ(x86->operands[1].mem.base, X86_REG_RIP); TEST_EQ(x86->operands[1].mem.disp, 0x60); pr_dbg("handling LEA instruction\n"); new_size = handle_pic(insn, new_insns, &info); TEST_EQ(new_size, 10); cs_free(insn, count); pr_dbg("checking modified instruction\n"); count = cs_disasm(disasm.engine, new_insns, new_size, CODEPAGE_BASE, 0, &insn); TEST_EQ(count, 1); x86 = &insn->detail->x86; TEST_EQ(insn->id, X86_INS_MOVABS); TEST_EQ(x86->operands[0].type, X86_OP_REG); TEST_EQ(x86->operands[0].reg, X86_REG_RAX); TEST_EQ(x86->operands[1].type, X86_OP_IMM); TEST_EQ(x86->operands[1].imm, info.addr + sizeof(lea_insn) + 0x60); cs_free(insn, count); mcount_disasm_finish(&disasm); return TEST_OK; } TEST_CASE(dynamic_x86_handle_call) { struct uftrace_symbol sym1 = { .name = "a", .addr = 0x3000, .size = 32, }; struct uftrace_symbol sym2 = { .name = "b", .addr = 0x4000, .size = 32, }; struct mcount_disasm_engine disasm; struct mcount_disasm_info info = { .sym = &sym1, .addr = ORIGINAL_BASE + sym1.addr, }; int count; cs_insn *insn = NULL; cs_x86 *x86; uint8_t call_insn[5] = { 0xe8, 0xfb, 0x0f, }; /* 0xffb + 5 = 0x1000 */ uint8_t new_insns[32]; int new_size; uint64_t target = ORIGINAL_BASE + sym2.addr; mcount_disasm_init(&disasm); pr_dbg("running capstone disassemler for CALL instruction\n"); count = cs_disasm(disasm.engine, call_insn, sizeof(call_insn), info.addr, 0, &insn); TEST_EQ(count, 1); x86 = &insn->detail->x86; TEST_EQ(insn->id, X86_INS_CALL); TEST_EQ(insn->address, info.addr); TEST_EQ(x86->op_count, 1); TEST_EQ(x86->operands[0].type, X86_OP_IMM); TEST_EQ(x86->operands[0].imm, target); pr_dbg("handling CALL instruction\n"); new_size = handle_call(insn, new_insns, &info); TEST_EQ(new_size, 28); cs_free(insn, count); pr_dbg("checking modified instruction\n"); count = cs_disasm(disasm.engine, new_insns, 12 /* actual insn size */, CODEPAGE_BASE, 0, &insn); TEST_EQ(count, 2); TEST_EQ(insn[0].id, X86_INS_PUSH); TEST_EQ(insn[0].address, CODEPAGE_BASE); x86 = &insn[0].detail->x86; TEST_EQ(x86->op_count, 1); TEST_EQ(x86->operands[0].type, X86_OP_MEM); TEST_EQ(x86->operands[0].mem.base, X86_REG_RIP); TEST_EQ(x86->operands[0].mem.disp, 6); memcpy(&target, &new_insns[12], sizeof(target)); TEST_EQ(target, info.addr + CALL_INSN_SIZE); TEST_EQ(insn[1].id, X86_INS_JMP); TEST_EQ(insn[1].address, CODEPAGE_BASE + 6); x86 = &insn[0].detail->x86; TEST_EQ(x86->op_count, 1); TEST_EQ(x86->operands[0].type, X86_OP_MEM); TEST_EQ(x86->operands[0].mem.base, X86_REG_RIP); TEST_EQ(x86->operands[0].mem.disp, 6); memcpy(&target, &new_insns[20], sizeof(target)); TEST_EQ(target, ORIGINAL_BASE + sym2.addr); cs_free(insn, count); mcount_disasm_finish(&disasm); return TEST_OK; } TEST_CASE(dynamic_x86_handle_jmp) { struct uftrace_symbol sym = { .name = "a", .addr = 0x3000, .size = 32, }; struct mcount_disasm_engine disasm; struct mcount_disasm_info info = { .sym = &sym, .addr = ORIGINAL_BASE + sym.addr, }; int count; cs_insn *insn = NULL; cs_x86 *x86; uint8_t jmp8_insn[2] = { 0xeb, 0x0e, }; /* 0xe + 2 = 0x10 */ uint8_t jmp32_insn[5] = { 0xe9, 0x0b, }; /* 0xb + 5 = 0x10 */ uint8_t new_insns[32]; int new_size; uint64_t target = info.addr + 0x10; mcount_disasm_init(&disasm); pr_dbg("running capstone disassemler for JMP8 instruction\n"); count = cs_disasm(disasm.engine, jmp8_insn, sizeof(jmp8_insn), info.addr, 0, &insn); TEST_EQ(count, 1); x86 = &insn->detail->x86; TEST_EQ(insn->id, X86_INS_JMP); TEST_EQ(insn->address, info.addr); TEST_EQ(x86->op_count, 1); TEST_EQ(x86->operands[0].type, X86_OP_IMM); TEST_EQ(x86->operands[0].imm, target); pr_dbg("handling JMP instruction\n"); new_size = handle_rel_jmp(insn, new_insns, NULL, &info); TEST_EQ(new_size, 14); cs_free(insn, count); pr_dbg("checking modified instruction\n"); count = cs_disasm(disasm.engine, new_insns, JMP_INSN_SIZE, CODEPAGE_BASE, 0, &insn); TEST_EQ(count, 1); TEST_EQ(insn->id, X86_INS_JMP); TEST_EQ(insn->address, CODEPAGE_BASE); x86 = &insn->detail->x86; TEST_EQ(x86->op_count, 1); TEST_EQ(x86->operands[0].type, X86_OP_MEM); TEST_EQ(x86->operands[0].mem.base, X86_REG_RIP); TEST_EQ(x86->operands[0].mem.disp, 0); memcpy(&target, &new_insns[6], sizeof(target)); TEST_EQ(target, info.addr + 0x10); cs_free(insn, count); pr_dbg("running capstone disassemler for JMP32 instruction\n"); count = cs_disasm(disasm.engine, jmp32_insn, sizeof(jmp32_insn), info.addr, 0, &insn); TEST_EQ(count, 1); x86 = &insn->detail->x86; TEST_EQ(insn->id, X86_INS_JMP); TEST_EQ(insn->address, info.addr); TEST_EQ(x86->op_count, 1); TEST_EQ(x86->operands[0].type, X86_OP_IMM); TEST_EQ(x86->operands[0].imm, target); pr_dbg("handling JMP instruction\n"); new_size = handle_rel_jmp(insn, new_insns, NULL, &info); TEST_EQ(new_size, 14); cs_free(insn, count); pr_dbg("checking modified instruction\n"); count = cs_disasm(disasm.engine, new_insns, JMP_INSN_SIZE, CODEPAGE_BASE, 0, &insn); TEST_EQ(count, 1); TEST_EQ(insn->id, X86_INS_JMP); TEST_EQ(insn->address, CODEPAGE_BASE); x86 = &insn->detail->x86; TEST_EQ(x86->op_count, 1); TEST_EQ(x86->operands[0].type, X86_OP_MEM); TEST_EQ(x86->operands[0].mem.base, X86_REG_RIP); TEST_EQ(x86->operands[0].mem.disp, 0); memcpy(&target, &new_insns[6], sizeof(target)); TEST_EQ(target, info.addr + 0x10); cs_free(insn, count); mcount_disasm_finish(&disasm); return TEST_OK; } TEST_CASE(dynamic_x86_handle_jcc) { struct uftrace_symbol sym = { .name = "a", .addr = 0x3000, .size = 32, }; struct mcount_disasm_engine disasm; struct mcount_disasm_info info = { .sym = &sym, .addr = ORIGINAL_BASE + sym.addr, }; int count; cs_insn *insn = NULL; cs_x86 *x86; uint8_t jcc8_insn[2] = { 0x74, 0x0e, }; /* 0x0e + 2 = 0x10 */ uint8_t jcc32_insn[6] = { 0x0f, 0x85, 0x0a, }; /* 0x0a + 6 = 0x10 */ uint8_t new_insns[32]; int new_size; uint64_t target = info.addr + 0x10; mcount_disasm_init(&disasm); pr_dbg("running capstone disassemler for Jcc8 instruction\n"); count = cs_disasm(disasm.engine, jcc8_insn, sizeof(jcc8_insn), info.addr, 0, &insn); TEST_EQ(count, 1); x86 = &insn->detail->x86; TEST_EQ(insn->id, X86_INS_JE); TEST_EQ(insn->address, info.addr); TEST_EQ(x86->op_count, 1); TEST_EQ(x86->operands[0].type, X86_OP_IMM); TEST_EQ(x86->operands[0].imm, target); pr_dbg("handling JE instruction\n"); new_size = handle_rel_jmp(insn, new_insns, NULL, &info); TEST_EQ(new_size, 2); cs_free(insn, count); pr_dbg("checking modified instruction\n"); count = cs_disasm(disasm.engine, new_insns, 2, CODEPAGE_BASE, 0, &insn); TEST_EQ(count, 1); TEST_EQ(insn->id, X86_INS_JE); TEST_EQ(insn->address, CODEPAGE_BASE); x86 = &insn->detail->x86; TEST_EQ(x86->op_count, 1); TEST_EQ(x86->operands[0].type, X86_OP_IMM); TEST_EQ(x86->operands[0].imm, CODEPAGE_BASE + 2); TEST_EQ(info.nr_branch, 1); TEST_EQ(info.branch_info[0].insn_index, 0); TEST_EQ(info.branch_info[0].branch_target, target); TEST_EQ(info.branch_info[0].insn_addr, info.addr); TEST_EQ(info.branch_info[0].insn_size, sizeof(jcc8_insn)); cs_free(insn, count); pr_dbg("running capstone disassemler for Jcc32 instruction\n"); count = cs_disasm(disasm.engine, jcc32_insn, sizeof(jcc32_insn), info.addr, 0, &insn); TEST_EQ(count, 1); x86 = &insn->detail->x86; TEST_EQ(insn->id, X86_INS_JNE); TEST_EQ(insn->address, info.addr); TEST_EQ(x86->op_count, 1); TEST_EQ(x86->operands[0].type, X86_OP_IMM); TEST_EQ(x86->operands[0].imm, target); pr_dbg("handling JNE instruction\n"); new_size = handle_rel_jmp(insn, new_insns, NULL, &info); TEST_EQ(new_size, 2); cs_free(insn, count); pr_dbg("checking modified instruction\n"); count = cs_disasm(disasm.engine, new_insns, 2, CODEPAGE_BASE, 0, &insn); TEST_EQ(count, 1); TEST_EQ(insn->id, X86_INS_JNE); TEST_EQ(insn->address, CODEPAGE_BASE); x86 = &insn->detail->x86; TEST_EQ(x86->op_count, 1); TEST_EQ(x86->operands[0].type, X86_OP_IMM); TEST_EQ(x86->operands[0].imm, CODEPAGE_BASE + 2); TEST_EQ(info.nr_branch, 2); TEST_EQ(info.branch_info[1].insn_index, 0); TEST_EQ(info.branch_info[1].branch_target, target); TEST_EQ(info.branch_info[1].insn_addr, info.addr); TEST_EQ(info.branch_info[1].insn_size, sizeof(jcc32_insn)); cs_free(insn, count); mcount_disasm_finish(&disasm); return TEST_OK; } TEST_CASE(dynamic_x86_handle_mov_load) { struct uftrace_symbol sym = { .name = "abc", .addr = 0x3000, .size = 32, }; struct mcount_disasm_engine disasm; struct mcount_disasm_info info = { .sym = &sym, .addr = ORIGINAL_BASE + sym.addr, }; int count; cs_insn *insn = NULL; cs_x86 *x86; uint8_t mov64_insn[7] = { 0x48, 0x8b, 0x3d, 0x64 }; /* mov 0x64(%rip),%rdi */ uint8_t mov32_insn[6] = { 0x8b, 0x0d, 0x32 }; /* mov 0x32(%rip),%ecx */ uint8_t mov8_insn[6] = { 0x8a, 0x05, 0x08 }; /* mov 0x08(%rip),%al */ uint8_t new_insns[16]; int new_size; mcount_disasm_init(&disasm); /* 1. 64-bit MOV (with REX) */ pr_dbg("running capstone disassemler for MOV instruction\n"); count = cs_disasm(disasm.engine, mov64_insn, sizeof(mov64_insn), info.addr, 0, &insn); TEST_EQ(count, 1); x86 = &insn->detail->x86; TEST_EQ(insn->id, X86_INS_MOV); TEST_EQ(insn->address, info.addr); TEST_EQ(x86->op_count, 2); TEST_EQ(x86->operands[0].type, X86_OP_REG); TEST_EQ(x86->operands[0].reg, X86_REG_RDI); TEST_EQ(x86->operands[1].type, X86_OP_MEM); TEST_EQ(x86->operands[1].mem.base, X86_REG_RIP); TEST_EQ(x86->operands[1].mem.disp, 0x64); pr_dbg("handling MOV instruction (64-bit load)\n"); new_size = handle_pic(insn, new_insns, &info); TEST_EQ(new_size, 13); cs_free(insn, count); pr_dbg("checking modified instruction\n"); count = cs_disasm(disasm.engine, new_insns, new_size, CODEPAGE_BASE, 0, &insn); TEST_EQ(count, 2); x86 = &insn[0].detail->x86; TEST_EQ(insn[0].id, X86_INS_MOVABS); TEST_EQ(x86->operands[0].type, X86_OP_REG); TEST_EQ(x86->operands[0].reg, X86_REG_RDI); TEST_EQ(x86->operands[1].type, X86_OP_IMM); TEST_EQ(x86->operands[1].imm, info.addr + sizeof(mov64_insn) + 0x64); x86 = &insn[1].detail->x86; TEST_EQ(insn[1].id, X86_INS_MOV); TEST_EQ(x86->operands[0].type, X86_OP_REG); TEST_EQ(x86->operands[0].reg, X86_REG_RDI); TEST_EQ(x86->operands[1].type, X86_OP_MEM); TEST_EQ(x86->operands[1].mem.base, X86_REG_RDI); TEST_EQ(x86->operands[1].mem.disp, 0); cs_free(insn, count); /* 2. 32-bit MOV (without REX) */ pr_dbg("running capstone disassemler for MOV instruction\n"); count = cs_disasm(disasm.engine, mov32_insn, sizeof(mov32_insn), info.addr, 0, &insn); TEST_EQ(count, 1); x86 = &insn->detail->x86; TEST_EQ(insn->id, X86_INS_MOV); TEST_EQ(insn->address, info.addr); TEST_EQ(x86->op_count, 2); TEST_EQ(x86->operands[0].type, X86_OP_REG); TEST_EQ(x86->operands[0].reg, X86_REG_ECX); TEST_EQ(x86->operands[1].type, X86_OP_MEM); TEST_EQ(x86->operands[1].mem.base, X86_REG_RIP); TEST_EQ(x86->operands[1].mem.disp, 0x32); pr_dbg("handling MOV instruction (32-bit load)\n"); new_size = handle_pic(insn, new_insns, &info); TEST_EQ(new_size, 12); cs_free(insn, count); pr_dbg("checking modified instruction\n"); count = cs_disasm(disasm.engine, new_insns, new_size, CODEPAGE_BASE, 0, &insn); TEST_EQ(count, 2); x86 = &insn[0].detail->x86; TEST_EQ(insn[0].id, X86_INS_MOVABS); TEST_EQ(x86->operands[0].type, X86_OP_REG); TEST_EQ(x86->operands[0].reg, X86_REG_RCX); TEST_EQ(x86->operands[1].type, X86_OP_IMM); TEST_EQ(x86->operands[1].imm, info.addr + sizeof(mov32_insn) + 0x32); x86 = &insn[1].detail->x86; TEST_EQ(insn[1].id, X86_INS_MOV); TEST_EQ(x86->operands[0].type, X86_OP_REG); TEST_EQ(x86->operands[0].reg, X86_REG_ECX); TEST_EQ(x86->operands[1].type, X86_OP_MEM); TEST_EQ(x86->operands[1].mem.base, X86_REG_RCX); TEST_EQ(x86->operands[1].mem.disp, 0); cs_free(insn, count); /* 3. 8-bit MOV (with a different OPCODE) */ pr_dbg("running capstone disassemler for MOV instruction\n"); count = cs_disasm(disasm.engine, mov8_insn, sizeof(mov8_insn), info.addr, 0, &insn); TEST_EQ(count, 1); x86 = &insn->detail->x86; TEST_EQ(insn->id, X86_INS_MOV); TEST_EQ(insn->address, info.addr); TEST_EQ(x86->op_count, 2); TEST_EQ(x86->operands[0].type, X86_OP_REG); TEST_EQ(x86->operands[0].reg, X86_REG_AL); TEST_EQ(x86->operands[1].type, X86_OP_MEM); TEST_EQ(x86->operands[1].mem.base, X86_REG_RIP); TEST_EQ(x86->operands[1].mem.disp, 0x8); pr_dbg("handling MOV instruction (8-bit load)\n"); new_size = handle_pic(insn, new_insns, &info); TEST_EQ(new_size, 12); cs_free(insn, count); pr_dbg("checking modified instruction\n"); count = cs_disasm(disasm.engine, new_insns, new_size, CODEPAGE_BASE, 0, &insn); TEST_EQ(count, 2); x86 = &insn[0].detail->x86; TEST_EQ(insn[0].id, X86_INS_MOVABS); TEST_EQ(x86->operands[0].type, X86_OP_REG); TEST_EQ(x86->operands[0].reg, X86_REG_RAX); TEST_EQ(x86->operands[1].type, X86_OP_IMM); TEST_EQ(x86->operands[1].imm, info.addr + sizeof(mov8_insn) + 0x8); x86 = &insn[1].detail->x86; TEST_EQ(insn[1].id, X86_INS_MOV); TEST_EQ(x86->operands[0].type, X86_OP_REG); TEST_EQ(x86->operands[0].reg, X86_REG_AL); TEST_EQ(x86->operands[1].type, X86_OP_MEM); TEST_EQ(x86->operands[1].mem.base, X86_REG_RAX); TEST_EQ(x86->operands[1].mem.disp, 0); cs_free(insn, count); mcount_disasm_finish(&disasm); return TEST_OK; } #endif /* UNIT_TEST */ #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.15.2/arch/x86_64/mcount-noplt.c000066400000000000000000000065601455365734300177340ustar00rootroot00000000000000#include #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 "libmcount/mcount.h" #include "uftrace.h" #include "utils/symbol.h" #include "utils/utils.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; /* clang-format off */ 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, }; /* clang-format on */ void *plthook_addr = plt_hooker; void *tramp; pd = xzalloc(sizeof(*pd)); pd->module_id = (unsigned long)pd; pd->base_addr = offset; if (arch_load_dynsymtab_noplt(&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 uftrace_symbol *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.15.2/arch/x86_64/mcount-support.c000066400000000000000000000134231455365734300203100ustar00rootroot00000000000000#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/arch.h" #include "utils/filter.h" #define COPY_XMM(xmm) \ do { \ if (spec->size == 8) \ asm volatile("movsd %%" xmm ", %0\n" : "=m"(ctx->val.v)); \ else \ asm volatile("movss %%" xmm ", %0\n" : "=m"(ctx->val.v)); \ } while (0) static 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; } ctx->val.i = 0; 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: COPY_XMM("xmm0"); break; case UFT_X86_64_REG_XMM1: COPY_XMM("xmm1"); break; case UFT_X86_64_REG_XMM2: COPY_XMM("xmm2"); break; case UFT_X86_64_REG_XMM3: COPY_XMM("xmm3"); break; case UFT_X86_64_REG_XMM4: COPY_XMM("xmm4"); break; case UFT_X86_64_REG_XMM5: COPY_XMM("xmm5"); break; case UFT_X86_64_REG_XMM6: COPY_XMM("xmm6"); break; case UFT_X86_64_REG_XMM7: COPY_XMM("xmm7"); break; default: return -1; } return 0; } static 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_ARGS) * 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); mcount_memset4(ctx->val.v, 0, sizeof(ctx->val)); return; } addr += offset; if (check_mem_region(ctx, (unsigned long)addr)) { /* save long double arguments properly */ mcount_memcpy4(ctx->val.v, addr, ALIGN(spec->size, 4)); } else { pr_dbg("stack address is not allowed: %p\n", addr); mcount_memset4(ctx->val.v, 0, sizeof(ctx->val)); } } static void mcount_get_struct_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { struct uftrace_arg_spec reg_spec = { .type = ARG_TYPE_REG, }; void *ptr = ctx->val.p; int i; for (i = 0; i < spec->struct_reg_cnt; i++) { reg_spec.reg_idx = spec->struct_regs[i]; mcount_get_register_arg(ctx, ®_spec); mcount_memcpy4(ptr, ctx->val.v, sizeof(long)); ptr += sizeof(long); } if (spec->stack_ofs > 0) { unsigned long *addr = ctx->stack_base + spec->stack_ofs; /* * it cannot call mcount_get_stack_arg() since the struct * might be bigger than the ctx->val. It directly updates * the argument buffer (in the ptr). */ if (check_mem_region(ctx, (unsigned long)addr)) mcount_memcpy4(ptr, addr, spec->size); else { pr_dbg("stack address is not allowed: %p\n", addr); mcount_memset4(ptr, 0, spec->size); } } else if (spec->struct_reg_cnt == 0) { mcount_get_register_arg(ctx, spec); mcount_memcpy4(ptr, ctx->val.v, sizeof(long)); } } void mcount_arch_get_arg(struct mcount_arg_context *ctx, struct uftrace_arg_spec *spec) { if (spec->fmt == ARG_FMT_STRUCT) { mcount_get_struct_arg(ctx, spec); return; } 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 (spec->fmt == ARG_FMT_STRUCT) mcount_memcpy4(ctx->val.v, ctx->retval, sizeof(long)); /* type of return value cannot be FLOAT, so check format instead */ else if (spec->fmt != ARG_FMT_FLOAT) mcount_memcpy1(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.15.2/arch/x86_64/mcount.S000066400000000000000000000060711455365734300165570ustar00rootroot00000000000000/* 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) /* * Now, we are just returned from the child, RSP points to the right above the * stack address containing the return address (now it is mcount_return), but we * should restore the original address and the RSP to return. */ ENTRY(mcount_return) .cfi_startproc sub $48, %rsp .cfi_def_cfa_offset 48 movq %rdi, 32(%rsp) 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 .cfi_def_cfa_register rdi /* align stack pointer to 16-byte */ andq $0xfffffffffffffff0, %rsp sub $16, %rsp /* save original stack pointer */ movq %rdi, (%rsp) /* returns original parent address */ call mcount_exit /* restore original stack pointer */ movq 0(%rsp), %rsp /* restore original return address in parent */ movq %rax, 40(%rsp) movq 0(%rsp), %rax movq 8(%rsp), %rdx movdqu 16(%rsp), %xmm0 movq 32(%rsp), %rdi add $40, %rsp .cfi_def_cfa_offset 8 retq .cfi_endproc END(mcount_return) uftrace-0.15.2/arch/x86_64/plthook.S000066400000000000000000000037421455365734300167340ustar00rootroot00000000000000#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 $56, %rsp .cfi_def_cfa_offset 56 movq %rdi, 32(%rsp) 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 /* align stack pointer to 16-byte */ andq $0xfffffffffffffff0, %rsp sub $16, %rsp /* save original stack pointer */ movq %rdi, 0(%rsp) call plthook_exit /* restore original stack pointer */ movq 0(%rsp), %rsp /* restore original return address in parent */ movq %rax, 48(%rsp) movq 0(%rsp), %rax movq 8(%rsp), %rdx movdqu 16(%rsp), %xmm0 movq 32(%rsp), %rdi add $48, %rsp .cfi_def_cfa_offset 8 retq .cfi_endproc END(plthook_return) uftrace-0.15.2/arch/x86_64/symbol.c000066400000000000000000000065031455365734300165770ustar00rootroot00000000000000#include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "symbol" #define PR_DOMAIN DBG_SYMBOL #include "libmcount/internal.h" #include "mcount-arch.h" #include "uftrace.h" #include "utils/symbol.h" #include "utils/utils.h" #define R_OFFSET_POS 2 #define JMP_INSN_SIZE 6 #define PLTGOT_SIZE 8 int arch_load_dynsymtab_noplt(struct uftrace_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)); elf_for_each_shdr(elf, &sec_iter) { if (strcmp(elf_get_name(elf, &sec_iter, sec_iter.shdr.sh_name), ".rela.dyn") == 0) { memcpy(&rel_iter, &sec_iter, sizeof(sec_iter)); pr_dbg2("found rela.dyn section with %ld entry.\n", sec_iter.shdr.sh_entsize); 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 uftrace_symbol *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); } sort_dynsymtab(dsymtab); 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 uftrace_symbol *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.15.2/arch/x86_64/xray.S000066400000000000000000000034441455365734300162360ustar00rootroot00000000000000/* 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 movq %rdi, 32(%rsp) /* 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 /* align stack pointer to 16-byte */ andq $0xfffffffffffffff0, %rsp sub $16, %rsp /* save original stack pointer */ movq %rdi, (%rsp) call xray_exit /* restore original stack pointer */ movq 0(%rsp), %rsp /* restore return values */ movq 0(%rsp), %rax movq 8(%rsp), %rdx movdqu 16(%rsp), %xmm0 movq 32(%rsp), %rdi add $40, %rsp retq .cfi_endproc END(__xray_exit) uftrace-0.15.2/check-deps/000077500000000000000000000000001455365734300152555ustar00rootroot00000000000000uftrace-0.15.2/check-deps/Makefile000066400000000000000000000055331455365734300167230ustar00rootroot00000000000000CHECK_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_libunwind CHECK_LIST += have_libcapstone CHECK_LIST += cc_has_minline_all_stringops CHECK_LIST += have_libtraceevent CHECK_LIST += cc_has_mgeneral_regs_only # # This is needed for checking build dependency # CHECK_CFLAGS = $(CFLAGS) $(CFLAGS_$@) -O2 -Werror CHECK_LDFLAGS = $(LDFLAGS) $(LDFLAGS_$@) # libpython3 provides an embed version of pkg-config file since python3.8 ifeq ($(shell pkg-config python3-embed --exists 2> /dev/null; echo $$?), 0) EMBED := -embed endif ifneq ($(O), ) CHKDIR := $(O)/check-deps else CHKDIR := $(CURDIR) endif CFLAGS_cc_has_mfentry = -mfentry LDFLAGS_cxa_demangle = -lstdc++ LDFLAGS_have_libelf = -lelf CFLAGS_cc_has_mno_sse2 = -mno-sse -mno-sse2 LDFLAGS_have_libpython2.7 = -lpython2.7 CFLAGS_have_libpython3 = $(shell pkg-config python3$(EMBED) --cflags 2> /dev/null) LDFLAGS_have_libpython3 = $(shell pkg-config python3$(EMBED) --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) CFLAGS_have_libunwind = $(shell pkg-config --cflags libunwind 2> /dev/null) LDFLAGS_have_libunwind = $(shell pkg-config --libs libunwind 2> /dev/null) CFLAGS_cc_has_minline_all_stringops = -minline-all-stringops CFLAGS_have_libtraceevent = $(shell pkg-config --cflags libtraceevent 2> /dev/null) LDFLAGS_have_libtraceevent = $(shell pkg-config --libs libtraceevent 2> /dev/null) CFLAGS_cc_has_mgeneral_regs_only = -mgeneral-regs-only check-build: $(CHECK_LIST) $(CHECK_LIST): %: __%.c $(CHKDIR)/check-tstamp @$(CC) $(CHECK_CFLAGS) -o $(CHKDIR)/$@ $< $(CHECK_LDFLAGS) > /dev/null 2>&1 $(CHKDIR)/check-tstamp: PHONY @mkdir -p $(dir $@) @touch $@ @if [ `id -u` -eq 0 ]; then chmod 666 $@; fi check-clean: # This is to find and remove all the executable files. # In overlayfs, entire files are matched with -executable option, # so -perm option is used instead. @$(RM) $(shell find $(CHKDIR) -type f -perm -u=x,g=x,o=x 2> /dev/null) $(CHKDIR)/check-tstamp $(CHKDIR)/*.o .PHONY: PHONY; uftrace-0.15.2/check-deps/Makefile.check000066400000000000000000000062301455365734300177720ustar00rootroot00000000000000ifeq ($(wildcard $(objdir)/check-deps/clock_without_librt),) LDFLAGS_libmcount.so += -lrt endif ifneq ($(wildcard $(objdir)/check-deps/cc_has_mfentry),) export HAVE_CC_MFENTRY = 1 endif ifneq ($(wildcard $(objdir)/check-deps/cxa_demangle),) COMMON_CFLAGS += -DHAVE_CXA_DEMANGLE COMMON_LDFLAGS += -lstdc++ endif ifneq ($(wildcard $(objdir)/check-deps/cc_has_mgeneral_regs_only),) LIB_CFLAGS += -mgeneral-regs-only else ifneq ($(wildcard $(objdir)/check-deps/cc_has_mno_sse2),) LIB_CFLAGS += -mno-sse -mno-sse2 endif ifneq ($(wildcard $(objdir)/check-deps/have_libpython3),) # libpython3 provides an embed version of pkg-config file since python3.8 ifeq ($(shell pkg-config python3-embed --exists 2> /dev/null; echo $$?), 0) EMBED := -embed endif COMMON_CFLAGS += -DHAVE_LIBPYTHON3 COMMON_CFLAGS += $(shell pkg-config python3$(EMBED) --cflags) COMMON_CFLAGS += -DLIBPYTHON_VERSION=$(shell pkg-config python3$(EMBED) --modversion) PYTHON_LDFLAGS = $(shell pkg-config python3$(EMBED) --libs) else ifneq ($(wildcard $(objdir)/check-deps/have_libpython2.7),) COMMON_CFLAGS += -DHAVE_LIBPYTHON2 COMMON_CFLAGS += -I/usr/include/python2.7 COMMON_CFLAGS += -DLIBPYTHON_VERSION="2.7" PYTHON_LDFLAGS = -lpython2.7 endif ifneq ($(wildcard $(objdir)/check-deps/have_libluajit),) COMMON_CFLAGS += -DHAVE_LIBLUAJIT COMMON_CFLAGS += $(shell pkg-config --cflags luajit) endif ifneq ($(wildcard $(objdir)/check-deps/perf_clockid),) COMMON_CFLAGS += -DHAVE_PERF_CLOCKID endif ifneq ($(wildcard $(objdir)/check-deps/perf_context_switch),) COMMON_CFLAGS += -DHAVE_PERF_CTXSW endif ifneq ($(wildcard $(objdir)/check-deps/arm_has_hardfp),) COMMON_CFLAGS += -DHAVE_ARM_HARDFP endif ifneq ($(wildcard $(objdir)/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 $(objdir)/check-deps/have_libelf),) COMMON_CFLAGS += -DHAVE_LIBELF COMMON_LDFLAGS += -lelf endif ifneq ($(wildcard $(objdir)/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 $(objdir)/check-deps/have_libunwind),) ifeq ($(DEBUG), 1) COMMON_CFLAGS += -DHAVE_LIBUNWIND COMMON_CFLAGS += $(shell pkg-config --cflags libunwind 2> /dev/null) COMMON_LDFLAGS += $(shell pkg-config --libs libunwind 2> /dev/null) endif endif ifneq ($(wildcard $(objdir)/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 ifneq ($(wildcard $(objdir)/check-deps/cc_has_minline_all_stringops),) LIB_CFLAGS += -minline-all-stringops endif ifneq ($(wildcard $(objdir)/check-deps/have_libtraceevent),) COMMON_CFLAGS += -DHAVE_LIBTRACEEVENT COMMON_CFLAGS += $(shell pkg-config --cflags libtraceevent 2> /dev/null) COMMON_LDFLAGS += $(shell pkg-config --libs libtraceevent 2> /dev/null) endif uftrace-0.15.2/check-deps/__arm_has_hardfp.c000066400000000000000000000001251455365734300206530ustar00rootroot00000000000000int main(void) { float f; asm volatile("vstr %%s0, %0\n" : "=m"(f)); return 0; } uftrace-0.15.2/check-deps/__cc_has_mfentry.c000066400000000000000000000000361455365734300207020ustar00rootroot00000000000000int main(void) { return 0; } uftrace-0.15.2/check-deps/__cc_has_mgeneral_regs_only.c000066400000000000000000000001351455365734300230710ustar00rootroot00000000000000#include #include int main(void) { printf("%f", 123.45); return 0; } uftrace-0.15.2/check-deps/__cc_has_minline_all_stringops.c000066400000000000000000000000361455365734300236110ustar00rootroot00000000000000int main(void) { return 0; } uftrace-0.15.2/check-deps/__cc_has_mno_sse2.c000066400000000000000000000000631455365734300207430ustar00rootroot00000000000000#include int main(void) { return 0; } uftrace-0.15.2/check-deps/__clock_without_librt.c000066400000000000000000000001211455365734300217630ustar00rootroot00000000000000#include int main(void) { return clock_gettime(CLOCK_MONOTONIC, 0); } uftrace-0.15.2/check-deps/__cxa_demangle.c000066400000000000000000000002271455365734300203270ustar00rootroot00000000000000extern 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.15.2/check-deps/__have_libcapstone.c000066400000000000000000000003171455365734300212260ustar00rootroot00000000000000#include #include #include #include #include int main(void) { cs_insn insn; printf("size: %zu\n", sizeof(insn)); return 0; } uftrace-0.15.2/check-deps/__have_libdw.c000066400000000000000000000001751455365734300200260ustar00rootroot00000000000000#include int main(void) { Dwarf *dw; dw = dwarf_begin(0, DWARF_C_READ); dwarf_end(dw); return 0; } uftrace-0.15.2/check-deps/__have_libelf.c000066400000000000000000000001611455365734300201550ustar00rootroot00000000000000#include #include int main(void) { GElf_Ehdr ehdr; elf_version(EV_CURRENT); return 0; } uftrace-0.15.2/check-deps/__have_libluajit.c000066400000000000000000000002151455365734300206770ustar00rootroot00000000000000#include #include #include int main(void) { lua_State *L = luaL_newstate(); luaL_openlibs(L); return 0; } uftrace-0.15.2/check-deps/__have_libncurses.c000066400000000000000000000001131455365734300210660ustar00rootroot00000000000000#include int main(void) { initscr(); endwin(); return 0; } uftrace-0.15.2/check-deps/__have_libpython2.7.c000066400000000000000000000001171455365734300211600ustar00rootroot00000000000000#include int main(void) { Py_Initialize(); return 0; } uftrace-0.15.2/check-deps/__have_libpython3.c000066400000000000000000000002371455365734300210170ustar00rootroot00000000000000#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.15.2/check-deps/__have_libtraceevent.c000066400000000000000000000001671455365734300215550ustar00rootroot00000000000000#include int main(void) { struct tep_handle *tep; tep = tep_alloc(); tep_free(tep); return 0; } uftrace-0.15.2/check-deps/__have_libunwind.c000066400000000000000000000002741455365734300207200ustar00rootroot00000000000000#define UNW_LOCAL_ONLY #include int main(void) { unw_cursor_t cursor; unw_context_t context; unw_getcontext(&context); unw_init_local(&cursor, &context); return 0; } uftrace-0.15.2/check-deps/__perf_clockid.c000066400000000000000000000004341455365734300203440ustar00rootroot00000000000000#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.15.2/check-deps/__perf_context_switch.c000066400000000000000000000005111455365734300217750ustar00rootroot00000000000000#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.15.2/cmds/000077500000000000000000000000001455365734300141755ustar00rootroot00000000000000uftrace-0.15.2/cmds/dump.c000066400000000000000000001433431455365734300153160ustar00rootroot00000000000000#include #include #include #include #include #include #include "uftrace.h" #include "utils/event.h" #include "utils/filter.h" #include "utils/fstack.h" #include "utils/graph.h" #include "utils/kernel-parser.h" #include "utils/kernel.h" #include "utils/list.h" #include "utils/utils.h" #include "version.h" /* target sampling frequency for flame graph */ #define FLAME_GRAPH_SAMPLE_FREQ 1000000 /* this is to skip arguments and return value output */ static bool show_args; struct uftrace_dump_ops { /* this is called at the beginning */ void (*header)(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct uftrace_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 uftrace_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; }; struct uftrace_mermaid_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 uftrace_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 uftrace_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 uftrace_fstack_args *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 uftrace_symbol *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 uftrace_dbg_info *dinfo; char *enum_def; s = find_task_session(sessions, task->t, task->rstack->time); map = find_map(&s->sym_info, 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->type_name, val); pr_out(" args[%d] enum %s: %s (%lld)\n", i, spec->type_name, enum_def, val); free(enum_def); size = spec->size; } else if (spec->fmt == ARG_FMT_STRUCT) { int c; unsigned char *p = ptr; pr_out(" args[%d] struct %s:", i, spec->type_name ?: ""); for (c = 0; c < spec->size; c++) { if ((c % 16) == 0) pr_out("\n\t"); pr_out("%02x ", p[c]); if ((c % 8) == 7) pr_out(" "); } pr_out("\n"); 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 uftrace_fstack_args *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 uftrace_symbol *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 if (spec->fmt == ARG_FMT_STRUCT) { int c; unsigned char *p = ptr; pr_out(" retval struct %s:", spec->type_name ?: ""); for (c = 0; c < spec->size; c++) { if ((c % 16) == 0) pr_out("\n\t"); pr_out("%02x ", p[c]); if ((c % 8) == 7) pr_out(" "); } pr_out("\n"); size = spec->size; } 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(struct uftrace_task_reader *task, unsigned evt_id) { char *evt_name = event_get_name(task->h, evt_id); char *evt_data = event_get_data_str(evt_id, task->args.data, false); pr_out(" %s", evt_name); if (evt_data) pr_out(": %s", evt_data); pr_out("\n"); free(evt_name); free(evt_data); } static void get_header_string(char *buf, size_t sz, uint64_t hdr_mask, const char **hdr_str, int hdr_max) { int i; size_t len; bool first = true; for (i = 0; i < hdr_max; i++) { if (!((1U << i) & hdr_mask)) continue; len = snprintf(buf, sz, "%s%s", first ? "" : " | ", hdr_str[i]); buf += len; sz -= len; first = false; } } static void dump_raw_header(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct uftrace_opts *opts) { int i; char buf[1024]; struct uftrace_raw_dump *raw = container_of(ops, typeof(*raw), ops); const char *feat_str[] = { "PLTHOOK", "TASK_SESSION", "KERNEL", "ARGUMENT", "RETVAL", "SYM_REL_ADDR", "MAX_STACK", "EVENT", "PERF_EVENT", "AUTO_ARGS", "DEBUG_INFO", "ESTIMATE_RETURN", "SYM_SIZE" }; const char *info_str[] = { "EXE_NAME", "EXE_BUILD_ID", "EXIT_STATUS", "CMDLINE", "CPUINFO", "MEMINFO", "OSINFO", "TASKINFO", "USAGEINFO", "LOADINFO", "ARG_SPEC", "RECORD_DATE", "PATTERN_TYPE", "VERSION" }; 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.elf_class, handle->hdr.elf_class == 2 ? "64" : "32"); get_header_string(buf, sizeof(buf), handle->hdr.feat_mask, feat_str, FEAT_BIT_MAX); pr_out("uftrace file header: features = %#" PRIx64 " (%s)\n", handle->hdr.feat_mask, buf); get_header_string(buf, sizeof(buf), handle->hdr.info_mask, info_str, INFO_BIT_MAX); pr_out("uftrace file header: info = %#" PRIx64 " (%s)\n", handle->hdr.info_mask, buf); 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 = event_get_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 && show_args) { 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 = event_get_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 && show_args) { pr_time(frs->time); pr_out("%5d: [%s] length = %d\n", task->tid, "data ", task->args.len); pr_event(task, frs->addr); 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); pr_out("reading kernel-cpu%d.dat\n", cpu); raw->file_offset = 0; raw->kbuf_offset = __kparser_curr_offset(&kernel->parser, cpu); } /* internal kernel tracing ring buffer format for extended timestamp */ #define KERNEL_TRACING_TIME_EXTEND 30 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 uftrace_kernel_parser *kp = &kernel->parser; 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 = __kparser_read_offset(kp, cpu, offset); unsigned char *tmp = data - 12; /* data still returns next record */ if ((*tmp & 0x1f) == KERNEL_TRACING_TIME_EXTEND) { uint32_t upper, lower; int size; size = __kparser_event_size(kp, cpu); 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 (__kparser_next_event(kp, cpu)) 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 = __kparser_read_offset(kp, cpu, raw->kbuf_offset); int size; size = __kparser_event_size(kp, cpu); raw->file_offset = __kparser_curr_offset(kp, cpu); pr_hex(&raw->file_offset, data - 4, size + 4); if (__kparser_next_event(kp, cpu)) 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 uftrace_kernel_parser *kp = &kernel->parser; int tid = kernel->tids[cpu]; void *event; char *event_data; char buf[512]; int size = 0; event = kparser_find_event(kp, frs->addr); if (!event) return; kparser_event_name(kp, event, buf, sizeof(buf)); event_data = read_kernel_event(kernel, cpu, &size); pr_time(frs->time); pr_out("%5d: [%s] %s(%ld) %.*s\n", tid, rstack_type(frs), buf, frs->addr, size, event_data); if (debug) { /* this is only needed for hex dump */ void *data = __kparser_read_offset(kp, cpu, raw->kbuf_offset); int size; size = __kparser_event_size(kp, cpu); raw->file_offset = __kparser_curr_offset(kp, cpu); pr_hex(&raw->file_offset, data - 4, size + 4); if (__kparser_next_event(kp, cpu)) 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 = event_get_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 uftrace_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; } void print_json_escaped_char(char **args, size_t *len, const char c); static void dump_chrome_task_rstack(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task, char *name) { char ph; char spec_buf[2048]; char name_buf[2048]; struct uftrace_record *frs = task->rstack; enum uftrace_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; size_t namelen = strlen(name); char *p = name_buf; size_t len = sizeof(name_buf) - 1; size_t i; 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; } } /* escape the function name */ for (i = 0; i < namelen; i++) print_json_escaped_char(&p, &len, name[i]); *p = '\0'; 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_buf); } 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_buf); } if (frs->more && show_args) { 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_buf); } 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_buf); } if (frs->more && show_args) { 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 uftrace_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 & 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 uftrace_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_dump_ops *ops, struct uftrace_graph_node *node, struct uftrace_opts *opts) { struct uftrace_graph_node *child; struct uftrace_flame_dump *flame = container_of(ops, typeof(*flame), ops); unsigned long sample = node->nr_calls; if (sample && flame->sample_time) sample = (node->time - node->child_time) / flame->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(ops, child, opts); } static void dump_flame_header(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct uftrace_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), NULL); } 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), NULL); } static void dump_flame_footer(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct uftrace_opts *opts) { print_flame_graph(ops, &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 uftrace_opts *opts) { char *exename = basename(handle->info.exename); graphviz_graph.root.name = exename; pr_out("# version\":\"uftrace %s\"\n", UFTRACE_VERSION); if (handle->hdr.info_mask & CMDLINE) pr_out("# command_line \"%s\"\n", handle->info.cmdline); pr_out("\ndigraph "); pr_out("\"%s\"", exename); pr_out(" { \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), NULL); } 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), NULL); } static void print_graph_to_graphviz(struct uftrace_graph_node *node, struct uftrace_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(" "); if (parent != NULL && parent->name != NULL) { pr_out("\"%s\" -> ", parent->name); } pr_out("\"%s\"", node->name); // Edge Attributes pr_out(" [xlabel = \"%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 uftrace_opts *opts) { print_graph_to_graphviz(&graphviz_graph.root, opts); pr_out("}\n"); graph_destroy(&graphviz_graph); graph_remove_task(); } /* mermaid support */ static struct uftrace_graph mermaid_graph = { .root.head = LIST_HEAD_INIT(mermaid_graph.root.head), .special_nodes = LIST_HEAD_INIT(mermaid_graph.special_nodes), }; static void dump_mermaid_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 = &mermaid_graph; mermaid_graph.sess = find_task_session(&task->h->sessions, task->t, frs->time); if (graph->node == NULL) graph->node = &mermaid_graph.root; graph_add_node(graph, frs->type, name, sizeof(struct uftrace_graph_node), NULL); } static void dump_mermaid_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 = &mermaid_graph; mermaid_graph.sess = kernel->handle->sessions.first; if (graph->node == NULL) graph->node = &mermaid_graph.root; graph_add_node(graph, rec->type, name, sizeof(struct uftrace_graph_node), NULL); } static void dump_mermaid_header(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct uftrace_opts *opts) { graph_init_callbacks(NULL, NULL, NULL, ops); mermaid_graph.root.name = basename(handle->info.exename); pr_out("\n"); pr_out("\n"); } static void print_graph_node_mermaid(struct uftrace_graph *graph, struct uftrace_graph_node *node) { char *symname = node->name; struct uftrace_graph_node *child; static int depth = -1; depth++; list_for_each_entry(child, &node->head, list) { pr_out(" %d_%d[\"%s\"] -->|%d| %d_%d[\"%s\"];\n", depth, node->id, symname, child->nr_calls, depth + 1, child->id, child->name); print_graph_node_mermaid(graph, child); } if (list_no_entry(child, &node->head, list)) { depth--; } } static void dump_mermaid_footer(struct uftrace_dump_ops *ops, struct uftrace_data *handle, struct uftrace_opts *opts) { const char *interactive_option_html; const char *interactive_option_js; /* skip empty graph */ if (mermaid_graph.root.time == 0 && mermaid_graph.root.nr_edges == 0) return; pr_out("

Function Call Graph for %s", mermaid_graph.root.name); pr_out("

\n"); interactive_option_html = #include "utils/mermaid.html.cstr" /* This file is converted from mermaid.html at build-time */ ; pr_out("%s", interactive_option_html); pr_out("
\n"); pr_out("flowchart TB\n"); print_graph_node_mermaid(&mermaid_graph, &mermaid_graph.root); pr_out("
\n"); pr_out("\n"); pr_out("\n"); pr_out("\n"); pr_out("\n"); } static void do_dump_file(struct uftrace_dump_ops *ops, struct uftrace_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 uftrace_symbol *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) { fstack_check_filter_done(task); continue; } name = symbol_getname(sym, frs->addr); call_if_nonull(ops->task_rstack, ops, task, name); symbol_putname(sym, name); fstack_check_filter_done(task); } } 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; if (kparser_data_size(&kernel->parser, i) == 0) continue; 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 = kparser_missed_events(&kernel->parser, i); struct uftrace_symbol *sym = NULL; char *name; uint64_t addr; if (losts) { call_if_nonull(ops->lost, ops, frs->time, tid, losts); kparser_clear_missed(&kernel->parser, i); } 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->sym_info, frs->addr); sym = find_symtabs(&fsess->sym_info, addr); name = symbol_getname(sym, addr); call_if_nonull(ops->kernel_func, ops, kernel, i, frs, name); symbol_putname(sym, name); } } perf: if (opts->no_event || !has_perf_data(handle) || uftrace_done) goto footer; for (i = 0; i < handle->nr_perf; i++) { struct uftrace_perf_reader *perf = &handle->perf[i]; struct stat statbuf; if (fstat(fileno(perf->fp), &statbuf) < 0) continue; if (statbuf.st_size == 0) continue; call_if_nonull(ops->perf_start, ops, perf, i); while (!uftrace_done) { struct uftrace_record *rec; /* for re-read perf data from file */ perf->valid = false; rec = get_perf_record(handle, perf); if (rec == NULL) break; if (opts->no_sched && is_sched_event(rec->addr)) continue; call_if_nonull(ops->perf_event, ops, perf, rec); } } footer: call_if_nonull(ops->footer, ops, handle, opts); } static bool check_task_rstack(struct uftrace_task_reader *task, struct uftrace_opts *opts) { struct uftrace_record *frs = task->rstack; if (!fstack_check_opts(task, opts)) return false; if (!check_time_range(&task->h->time_range, frs->time)) return false; if (!fstack_check_filter(task)) return false; return true; } static void dump_replay_func(struct uftrace_dump_ops *ops, struct uftrace_task_reader *task, struct uftrace_opts *opts) { struct uftrace_record *rec = task->rstack; struct uftrace_session_link *sessions = &task->h->sessions; struct uftrace_symbol *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 uftrace_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); fstack_check_filter_done(task); } /* 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 uftrace_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->sym_info, 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); fstack_check_filter_done(task); } } ops->footer(ops, handle, opts); } int command_dump(int argc, char *argv[], struct uftrace_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->show_args) show_args = true; 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, }; if (!opts->sample_time && handle.hdr.info_mask & RECORD_DATE) { uint64_t total_time, sample_time; /* elapsed time is saved in second */ total_time = strtod(handle.info.elapsed_time, NULL) * 1e9; /* start from 1 usec and increase it for the target freq */ for (sample_time = 1000; sample_time * FLAME_GRAPH_SAMPLE_FREQ < total_time; sample_time *= 10) { /* max sample time is 1 sec */ if (sample_time == 1000000000) break; } dump.sample_time = 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 if (opts->mermaid) { struct uftrace_mermaid_dump dump = { .ops = { .header = dump_mermaid_header, .task_rstack = dump_mermaid_task_rstack, .kernel_func = dump_mermaid_kernel_rstack, .footer = dump_mermaid_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; } #ifdef UNIT_TEST TEST_CASE(dump_command1) { struct uftrace_opts opts = { .dirname = "dump-file-test", .exename = read_exename(), .max_stack = 10, .depth = OPT_DEPTH_DEFAULT, }; struct uftrace_data handle; 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, }, }; TEST_EQ(prepare_test_data(&opts, &handle), 0); pr_dbg("dump each file contents\n"); do_dump_file(&dump.ops, &opts, &handle); release_test_data(&opts, &handle); return TEST_OK; } TEST_CASE(dump_command2) { struct uftrace_opts opts = { .dirname = "dump-replay-test", .exename = read_exename(), .max_stack = 10, .depth = OPT_DEPTH_DEFAULT, }; struct uftrace_data handle; struct uftrace_raw_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, }, }; TEST_EQ(prepare_test_data(&opts, &handle), 0); pr_dbg("dump contents in time order\n"); do_dump_replay(&dump.ops, &opts, &handle); release_test_data(&opts, &handle); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/cmds/graph.c000066400000000000000000000607411455365734300154520ustar00rootroot00000000000000#include #include #include #include #include #include "uftrace.h" #include "utils/field.h" #include "utils/filter.h" #include "utils/fstack.h" #include "utils/graph.h" #include "utils/symbol.h" #include "utils/utils.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 uftrace_opts *opts, struct display_field *p_field_table[]) { add_field(fields, field_table[GRAPH_F_TOTAL_TIME]); } static void setup_default_task_field(struct list_head *fields, struct uftrace_opts *opts, struct display_field *p_field_table[]) { 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 uftrace_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->sym_info, 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 uftrace_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 uftrace_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 uftrace_symbol *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->sym_info, 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", node->nr_calls, symname); if (node->loc) pr_gray(" [%s:%d]", node->loc->file->name, node->loc->line); pr_out("\n"); } 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 uftrace_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, false); if (!list_empty(&output_fields)) { if (opts->srcline) pr_gray(" %s", "[SOURCE]"); pr_out("\n"); } 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 uftrace_opts *opts, struct uftrace_task_reader *task, uint64_t time, uint64_t addr, int type, char *func) { struct task_graph *tg; struct uftrace_symbol *sym = NULL; char *name; struct uftrace_dbg_loc *loc = NULL; tg = get_task_graph(task, time, addr); if (unlikely(tg->utg.graph == NULL)) return; sym = find_symtabs(&tg->utg.graph->sess->sym_info, 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) { if (opts->srcline) { loc = task_find_loc_addr(&task->h->sessions, task, time, addr); } graph_add_node(&tg->utg, type, name, sizeof(struct uftrace_graph_node), loc); } /* 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 uftrace_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 && frs->addr != EVENT_ID_PERF_SCHED_OUT_PREEMPT) continue; } if (is_kernel_record(task, frs)) { struct uftrace_session *fsess; fsess = task->h->sessions.first; addr = get_kernel_address(&fsess->sym_info, 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 uftrace_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->sym_info.kernel_base + 1); tg->utg.lost = true; if (tg->enabled && is_kernel_address(&fsess->sym_info, 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); fstack_check_filter_done(task); } /* add duration of remaining functions */ for (i = 0; i < handle->nr_tasks; i++) { uint64_t last_time; struct uftrace_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 uftrace_sym_info *sinfo = &s->sym_info; struct uftrace_mmap *map; for_each_map(sinfo, 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 uftrace_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 uftrace_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: case EVENT_ID_PERF_SCHED_OUT_PREEMPT: 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; } } fstack_check_filter_done(task); } /* 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 uftrace_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 uftrace_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 uftrace_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 uftrace_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, true); 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 uftrace_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 (format_mode == FORMAT_HTML) pr_out(HTML_HEADER); 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: if (format_mode == FORMAT_HTML) pr_out(HTML_FOOTER); close_data_file(opts, &handle); return 0; } #ifdef UNIT_TEST TEST_CASE(graph_command) { struct uftrace_opts opts = { .dirname = "graph-cmd-test", .exename = read_exename(), .max_stack = 10, .depth = OPT_DEPTH_DEFAULT, }; struct uftrace_data handle; struct session_graph *graph; struct graph_backtrace *bt, *btmp; char *func; int ret = 0; func = "_start"; full_graph = true; TEST_EQ(prepare_test_data(&opts, &handle), 0); pr_dbg("construct full function graph\n"); build_graph(&opts, &handle, func); graph = graph_list; while (graph && !uftrace_done) { pr_dbg("print graph for %s\n", graph->func); ret += print_graph(graph, &opts); graph = graph->next; } TEST_NE(ret, 0); while (graph_list) { graph = graph_list; graph_list = graph->next; pr_dbg("destroy graph for %s\n", graph->func); 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(); release_test_data(&opts, &handle); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/cmds/info.c000066400000000000000000000722041455365734300153010ustar00rootroot00000000000000/* * 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 "libmcount/mcount.h" #include "uftrace.h" #include "utils/filter.h" #include "utils/fstack.h" #include "utils/symbol.h" #include "utils/utils.h" #include "version.h" struct read_handler_arg { struct uftrace_data *handle; char buf[PATH_MAX]; }; struct fill_handler_arg { int fd; int exit_status; struct uftrace_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; char buf[BUILD_ID_STR_SIZE]; if (read_build_id(fha->opts->exename, buf, sizeof(buf)) < 0) { pr_dbg("cannot read build-id section\n"); return -1; } return dprintf(fha->fd, "build_id:%s\n", buf); } 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; } #define NUM_CPU_INFO 2 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=%d\n", NUM_CPU_INFO); 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; /* old data might have fewer lines */ if (lines > NUM_CPU_INFO) 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 (!strncmp(info->cpudesc, "RISCV64", 7)) { handle->arch = UFT_CPU_RISCV64; } 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; } #define NUM_OS_INFO 3 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=%d\n", NUM_OS_INFO); 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; /* old data might have fewer lines */ if (lines > NUM_OS_INFO) 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; } #define NUM_TASK_INFO 2 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, 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=%d\n", NUM_TASK_INFO); 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; /* old data might have fewer lines */ if (lines > NUM_TASK_INFO) return -1; 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; } #define NUM_USAGE_INFO 6 static int fill_usageinfo(void *arg) { struct fill_handler_arg *fha = arg; struct rusage *r = fha->rusage; struct rusage zero = {}; if (!r || !memcmp(r, &zero, sizeof(*r))) return -1; dprintf(fha->fd, "usageinfo:lines=%d\n", NUM_USAGE_INFO); 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; /* old data might have fewer lines */ if (lines > NUM_USAGE_INFO) 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; } #define MAX_ARGSPEC_INFO 6 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; if (lines > MAX_ARGSPEC_INFO) return -1; 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 uftrace_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 |= 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 & read_handlers[i].bit)) continue; if (read_handlers[i].handler(&arg) < 0) { pr_dbg("error during read uftrace info (%x)\n", 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 uftrace_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 & VERSION) process(data, fmt, "program version", info->uftrace_version); if (info_mask & RECORD_DATE) process(data, fmt, "recorded on", info->record_date); else process(data, "# %-20s: %s", "recorded on", ctime(&statbuf.st_mtime)); if (info_mask & CMDLINE) process(data, fmt, "cmdline", info->cmdline); if (info_mask & 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 & MEMINFO) process(data, fmt, "memory info", info->meminfo); if (info_mask & LOADINFO) process(data, "# %-20s: %.02f / %.02f / %.02f (1 / 5 / 15 min)\n", "system load", info->load1, info->load5, info->load15); if (info_mask & 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 & 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, 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 & EXE_NAME) process(data, fmt, "exe image", info->exename); if (info_mask & 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 & 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 & PATTERN_TYPE) process(data, fmt, "pattern", get_filter_pattern(info->patt_type)); if (info_mask & 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 & RECORD_DATE) process(data, fmt, "elapsed time", info->elapsed_time); if (info_mask & 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); } snprintf(buf, sizeof(buf), "%s/note.txt", opts->dirname); if (!access(buf, R_OK)) { size_t size; char *note_buf; FILE *fp = fopen(buf, "r"); if (fstat(fileno(fp), &statbuf) < 0) { pr_dbg("failed to get the size of note.txt\n"); goto out; } size = (size_t)statbuf.st_size; note_buf = xmalloc(size + 1); if (fread(note_buf, size, 1, fp) != size) pr_dbg("couldn't read the whole note.txt contents"); note_buf[size] = '\0'; process(data, "#\n"); process(data, "# extra note information\n"); process(data, "# ======================\n"); process(data, "%s", note_buf); free(note_buf); out: fclose(fp); } 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 uftrace_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 uftrace_sym_info sinfo = { .dirname = opts->dirname, .filename = opts->exename, .flags = SYMTAB_FL_USE_SYMFILE | SYMTAB_FL_DEMANGLE, }; struct uftrace_module *mod; char build_id[BUILD_ID_STR_SIZE]; int i; if (!opts->exename) { pr_use("Usage: uftrace info --symbols [COMMAND]\n"); return -1; } for (i = 0; i < BUILD_ID_SIZE; i++) { snprintf(build_id + i * 2, 3, "%02x", handle.info.build_id[i]); } mod = load_module_symtab(&sinfo, sinfo.filename, build_id); 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, 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; } #ifdef UNIT_TEST TEST_CASE(info_command) { struct uftrace_opts opts = { .dirname = "info-cmd-test", .exename = read_exename(), .max_stack = 10, .depth = OPT_DEPTH_DEFAULT, }; struct uftrace_data handle; TEST_EQ(prepare_test_data(&opts, &handle), 0); pr_dbg("process info section in the data\n"); process_uftrace_info(&handle, &opts, print_info, NULL); if (handle.hdr.feat_mask & PERF_EVENT) { if (setup_perf_data(&handle) == 0) update_perf_task_comm(&handle); } pr_dbg("print task info in the data\n"); print_task_info(&handle); release_test_data(&opts, &handle); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/cmds/live.c000066400000000000000000000277031455365734300153110ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "libmcount/mcount.h" #include "uftrace.h" #include "utils/fstack.h" #include "utils/kernel.h" #include "utils/socket.h" #include "utils/utils.h" static char *tmp_dirname; static void cleanup_tempdir(void) { if (!tmp_dirname) return; remove_directory(tmp_dirname); tmp_dirname = NULL; } /* trigger actions that need to be done in replay */ static const struct { const char *action; int len; } replay_triggers[] = { { "backtrace", 9 }, { "color=", 6 }, { "hide", 4 }, { "time=", 5 }, { "trace", 5 }, }; static bool has_replay_triggers(const char *trigger) { unsigned int i; for (i = 0; i < ARRAY_SIZE(replay_triggers); i++) { if (strstr(trigger, replay_triggers[i].action)) return true; } return false; } static bool match_replay_triggers(const char *trigger) { unsigned int i; for (i = 0; i < ARRAY_SIZE(replay_triggers); i++) { if (!strncmp(trigger, replay_triggers[i].action, replay_triggers[i].len)) return true; } return false; } static void reset_live_opts(struct uftrace_opts *opts) { /* this is needed to set display_depth at replay */ live_disabled = (opts->trace == TRACE_STATE_OFF); /* * 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->size_filter = 0; /* * Likewise, most trigger options are ignored, but color settings and * backtrace are effective only in replay. */ if (opts->trigger) { char *new_triggers = NULL; struct strv trs = STRV_INIT; char *s; int i; /* fastpath: these are not used frequently */ if (!has_replay_triggers(opts->trigger)) { free(opts->trigger); opts->trigger = NULL; goto others; } /* * Split trigger options for each function. * Example trigger option string like this: * * a@depth=1;b@backtrace,read=pmu-cycle;c@color=red,time=1us */ strv_split(&trs, opts->trigger, ";"); strv_for_each(&trs, s, i) { struct strv sv = STRV_INIT; char *name, *tmp, *o; bool found = false; int k; /* skip this function if it doesn't have these triggers */ if (!has_replay_triggers(s)) continue; name = xstrdup(s); tmp = strchr(name, '@'); if (tmp == NULL) { pr_dbg("invalid trigger option: %s\n", s); free(name); continue; } *tmp = '\0'; /* split trigger option into actions */ strv_split(&sv, tmp + 1, ","); strv_for_each(&sv, o, k) { if (!match_replay_triggers(o)) continue; if (!found) { /* first action: needs func name first */ tmp = strjoin(name, o, "@"); found = true; continue; } /* second or later actions: just append */ tmp = strjoin(tmp, o, ","); } strv_free(&sv); if (found) { new_triggers = strjoin(new_triggers, tmp, ";"); free(tmp); } } strv_free(&trs); free(opts->trigger); opts->trigger = new_triggers; } others: opts->depth = MCOUNT_DEFAULT_DEPTH; opts->trace = TRACE_STATE_ON; opts->no_event = false; opts->no_sched = false; } static void sigsegv_handler(int sig) { pr_warn("Segmentation fault\n"); cleanup_tempdir(); raise(sig); } static bool can_skip_replay(struct uftrace_opts *opts, int record_result) { if (opts->nop) return true; return false; } static void setup_child_environ(struct uftrace_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("uftrace could not find libmcount.so for live-tracing\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); } /** * query_agent_capabilities - query agent for its supported features * @fd - agent socket fd * @return - agent capabilities */ static int query_agent_capabilities(int fd) { struct uftrace_msg msg; int status; pr_dbg3("query agent capabilities\n"); if (agent_message_send(fd, UFTRACE_MSG_AGENT_QUERY, NULL, 0) < 0) return -1; status = agent_message_read_response(fd, &msg); if (status < 0) return -1; switch (msg.type) { case UFTRACE_MSG_AGENT_OK: pr_dbg2("agent capabilities: %#x\n", status); break; case UFTRACE_MSG_AGENT_ERR: pr_dbg3("agent query error: %s\n", strerror(status)); status = -1; break; default: pr_dbg3("invalid agent response\n"); } return status; } /** * forward_option - forward a given option and its data to the agent * @fd - agent socket file descriptor * @capabilities - features supported by the agent * @opt - option type to send * @value - option data load * @value_size - size of @value * @return - status: -1 on error, 0 on success */ static int forward_option(int fd, int capabilities, int opt, void *value, size_t value_size) { void *data; size_t data_size; struct uftrace_msg ack; int ret = -1; if (!(opt & capabilities)) { pr_warn("option not unsupported by the agent: %#x\n", opt); return 0; } /* The agent needs to know which option to apply, and its associated value. * We pack both information into the data load of a single message, with * type UFTRACE_MSG_AGENT_SET_OPT. * * We use the following data structure: * * PACKED_DATA = OPTION_TYPE | VALUE * ----------- ---------- * sizeof(opt) value_size * * Example: * PACKED_DATA = UFTRACE_AGENT_OPT_FILTER | 'func1,!func2' * ------------------------ --------------- * sizeof(opt) value_size = 12 */ pr_dbg2("send option to agent: %#x\n", opt); data_size = value_size + sizeof(opt); data = malloc(data_size); if (!data) goto cleanup; memcpy(data, &opt, sizeof(opt)); memcpy(data + sizeof(opt), value, value_size); ret = agent_message_send(fd, UFTRACE_MSG_AGENT_SET_OPT, data, data_size); if (ret < 0) { pr_dbg("error sending option to agent\n"); goto cleanup; } ret = agent_message_read_response(fd, &ack); if (ret < 0) { pr_dbg3("error reading agent response\n"); goto cleanup; } switch (ack.type) { case UFTRACE_MSG_AGENT_OK: ret = 0; pr_dbg4("option applied by agent: %#x\n", opt); break; case UFTRACE_MSG_AGENT_ERR: if (ret == ENOTSUP) pr_warn("option not unsupported by the agent: %#x\n", opt); else pr_warn("agent error: %s\n", strerror(ret)); ret = -1; break; default: pr_dbg3("bad agent message type (expected ack)\n"); ret = -1; } cleanup: free(data); return ret; } /* Forward all client options to the agent */ static int forward_options(struct uftrace_opts *opts) { int sfd; struct sockaddr_un addr; struct uftrace_msg ack; int status = 0; int status_close = 0; int capabilities; sfd = agent_socket_create(&addr, opts->pid); if (sfd == -1) return UFTRACE_EXIT_FAILURE; if (agent_connect(sfd, &addr) == -1) goto socket_error; pr_dbg2("connected to agent %d\n", opts->pid); capabilities = query_agent_capabilities(sfd); if (capabilities < 0) goto close; if (opts->trace != TRACE_STATE_NONE) { int trace = (opts->trace == TRACE_STATE_ON); status = forward_option(sfd, capabilities, UFTRACE_AGENT_OPT_TRACE, &trace, sizeof(trace)); } if (opts->depth) { status = forward_option(sfd, capabilities, UFTRACE_AGENT_OPT_DEPTH, &opts->depth, sizeof(opts->depth)); if (status < 0) goto close; } if (opts->threshold) { status = forward_option(sfd, capabilities, UFTRACE_AGENT_OPT_THRESHOLD, &opts->threshold, sizeof(opts->threshold)); if (status < 0) goto close; } /* provide a pattern type for options that need it */ if (opts->filter || opts->caller || opts->trigger) { status = forward_option(sfd, capabilities, UFTRACE_AGENT_OPT_PATTERN, &opts->patt_type, sizeof(opts->patt_type)); if (status < 0) goto close; } if (opts->filter) { status = forward_option(sfd, capabilities, UFTRACE_AGENT_OPT_FILTER, opts->filter, strlen(opts->filter) + 1); if (status < 0) goto close; } if (opts->caller) { status = forward_option(sfd, capabilities, UFTRACE_AGENT_OPT_CALLER, opts->caller, strlen(opts->caller) + 1); if (status < 0) goto close; } if (opts->trigger) { status = forward_option(sfd, capabilities, UFTRACE_AGENT_OPT_TRIGGER, opts->trigger, strlen(opts->trigger) + 1); if (status < 0) goto close; } close: status_close = agent_message_send(sfd, UFTRACE_MSG_AGENT_CLOSE, NULL, 0); if (status_close == 0) { status_close = agent_message_read_response(sfd, &ack); if (status_close == 0) { if (ack.type == UFTRACE_MSG_AGENT_OK) pr_dbg("disconnected from agent\n"); else status_close = -1; } } if (status_close < 0) pr_dbg("agent connection not closed properly\n"); socket_error: if (close(sfd) == -1) pr_dbg2("error closing agent socket\n"); if (status < 0 || status_close < 0) return UFTRACE_EXIT_FAILURE; else return UFTRACE_EXIT_SUCCESS; } int command_live(int argc, char *argv[], struct uftrace_opts *opts) { #define LIVE_NAME "uftrace-live-XXXXXX" char template[32] = "/tmp/" LIVE_NAME; 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) { /* can't reuse first template because it was trashed by mkstemp */ strcpy(template, LIVE_NAME); if (errno != EPERM && errno != ENOENT) pr_err("cannot access to /tmp"); fd = mkstemp(template); if (fd < 0) pr_err("cannot create temp name"); tmp_dirname = template; } 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 (opts->exename && fork() == 0) { setup_child_environ(opts); setenv("UFTRACE_LIST_EVENT", "1", 1); execv(opts->exename, argv); abort(); } return 0; } if (opts->pid) return forward_options(opts); 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; } #ifdef UNIT_TEST TEST_CASE(live_reset_options) { struct uftrace_opts o = { .depth = 3, .trace = TRACE_STATE_OFF, .no_event = true, .no_sched = true, }; pr_dbg("setup live options\n"); o.filter = strjoin(o.filter, "foo", ";"); o.caller = strjoin(o.caller, "bar", ";"); /* add different types of triggers */ o.trigger = strjoin(o.trigger, "foo@filter,depth=1", ";"); o.trigger = strjoin(o.trigger, "bar@time=1us,filter,color=red", ";"); o.trigger = strjoin(o.trigger, "baz@backtrace,trace,read=pmu-cycle", ";"); pr_dbg("reset live options (filter, triggers, ...)\n"); reset_live_opts(&o); TEST_EQ(o.depth, MCOUNT_DEFAULT_DEPTH); TEST_EQ(o.filter, NULL); TEST_EQ(o.caller, NULL); /* it should only have the color trigger */ TEST_STREQ(o.trigger, "bar@time=1us,color=red;baz@backtrace,trace"); TEST_EQ(o.trace, TRACE_STATE_ON); TEST_EQ(o.no_event, false); TEST_EQ(o.no_sched, false); free(o.trigger); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/cmds/record.c000066400000000000000000001473501455365734300156310ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libmcount/mcount.h" #include "uftrace.h" #include "utils/filter.h" #include "utils/kernel.h" #include "utils/list.h" #include "utils/perf.h" #include "utils/shmem.h" #include "utils/symbol.h" #include "utils/utils.h" #ifndef EM_RISCV #define EM_RISCV 243 #endif #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 uftrace_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") || getenv("UFTRACE_AGENT") || getenv("UFTRACE_LOCATION")) 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 uftrace_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 uftrace_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->loc_filter) { char *loc_str = uftrace_clear_kernel(opts->loc_filter); if (loc_str) { setenv("UFTRACE_LOCATION", loc_str, 1); setenv("UFTRACE_SRCLINE", "1", 1); free(loc_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_MIN_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->trace == TRACE_STATE_OFF) setenv("UFTRACE_TRACE_OFF", "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 (opts->estimate_return) setenv("UFTRACE_ESTIMATE_RETURN", "1", 1); if (opts->clock) setenv("UFTRACE_CLOCK", opts->clock, 1); if (opts->with_syms) setenv("UFTRACE_SYMBOL_DIR", opts->with_syms, 1); if (opts->agent) setenv("UFTRACE_AGENT", "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("uftrace could not find libmcount.so for record-tracing\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 uftrace_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; if (opts->estimate_return) features |= ESTIMATE_RETURN; /* symbol file saves size */ features |= SYM_SIZE; xasprintf(&buf, "%s/*.dbg", opts->dirname); if (glob(buf, GLOB_NOSORT, NULL, &g) != GLOB_NOMATCH) features |= DEBUG_INFO; globfree(&g); free(buf); return features; } int fill_file_header(struct uftrace_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.elf_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 uftrace_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 uftrace_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 uftrace_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 uftrace_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 = uftrace_shmem_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 uftrace_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(uftrace_shmem_root(), &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); uftrace_shmem_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 update_session_maps(struct uftrace_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++) { char buf[PATH_MAX]; snprintf(buf, sizeof(buf), "%s/%s", opts->dirname, map_list[i]->d_name); update_session_map(buf); free(map_list[i]); } free(map_list); } static void load_session_symbols(struct uftrace_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 uftrace_sym_info sinfo = { .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, &sinfo, sid); load_module_symtabs(&sinfo); delete_session_map(&sinfo); } 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, char *buf, size_t len) { /* try to find the binary in PATH */ struct strv strv = STRV_INIT; char *env = getenv("PATH"); 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) { snprintf(buf, len, "%s/%s", path, exename); if (is_regular_executable(buf)) { found = true; break; } } if (!found) pr_err_ns(UFTRACE_MSG, exename); strv_free(&strv); } static void check_binary(struct uftrace_opts *opts) { int fd; int chk; size_t i; char elf_ident[EI_NIDENT]; static char altname[PATH_MAX]; // for opts->exename to be persistent uint16_t e_type; uint16_t e_machine; uint16_t supported_machines[] = { EM_X86_64, EM_ARM, EM_AARCH64, EM_386, EM_RISCV }; again: /* if it cannot be found in PATH, then fails inside */ if (!is_regular_executable(opts->exename)) { find_in_path(opts->exename, altname, sizeof(altname)); opts->exename = altname; } 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 = altname; char *p; if (!check_script_file(opts->exename, altname, sizeof(altname))) pr_err_ns(UFTRACE_ELF_MSG, opts->exename); #if defined(HAVE_LIBPYTHON2) || defined(HAVE_LIBPYTHON3) if (strstr(script, "python")) { opts->force = true; /* TODO: disable sched event until it can merge subsequent events */ opts->no_sched = true; } #endif if (!opts->force && !opts->patch) pr_err_ns(SCRIPT_MSG, opts->exename); script = str_ltrim(script); /* 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 uftrace_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; if (opts->no_sched) 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 uftrace_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; kernel->bufsize = opts->kernel_bufsize; kernel->clock = opts->clock; 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) { setup_clock_id(opts->clock); 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 a pipe for writer thread"); } static void start_tracing(struct writer_data *wd, struct uftrace_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 uftrace_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 uftrace_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 copy_data_files(struct uftrace_opts *opts, const char *ext) { char path[PATH_MAX]; glob_t g; size_t i; snprintf(path, sizeof(path), "%s/*%s", opts->with_syms, ext); glob(path, GLOB_NOSORT, NULL, &g); for (i = 0; i < g.gl_pathc; i++) { snprintf(path, sizeof(path), "%s/%s", opts->dirname, basename(g.gl_pathv[i])); copy_file(g.gl_pathv[i], path); } globfree(&g); } static void write_symbol_files(struct writer_data *wd, struct uftrace_opts *opts) { struct dlopen_list *dlib, *tmp; if (opts->nop) return; /* add build-id info map files */ update_session_maps(opts); if (opts->with_syms) { copy_data_files(opts, ".sym"); copy_data_files(opts, ".dbg"); goto after_save; } /* 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 uftrace_sym_info dlib_sinfo = { .dirname = opts->dirname, .flags = SYMTAB_FL_ADJ_OFFSET, }; char build_id[BUILD_ID_STR_SIZE]; read_build_id(dlib->libname, build_id, sizeof(build_id)); load_module_symtab(&dlib_sinfo, dlib->libname, build_id); list_del(&dlib->list); free(dlib->libname); free(dlib); } save_module_symtabs(opts->dirname); unload_module_symtabs(); after_save: 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); } static int do_main_loop(int ready[], struct uftrace_opts *opts, int pid) { int ret; struct writer_data wd; char *channel = NULL; close(ready[0]); if (opts->nop) { setup_writers(&wd, opts); start_tracing(&wd, opts, ready[1]); close(ready[1]); 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[1]); close(ready[1]); 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; } static int do_child_exec(int ready[], struct uftrace_opts *opts, int argc, char *argv[]) { uint64_t dummy; char *shebang = NULL; char dirpath[PATH_MAX]; char exepath[PATH_MAX]; struct strv new_args = STRV_INIT; bool is_python = false; close(ready[1]); 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, dirpath) != NULL) opts->dirname = dirpath; if (access(argv[0], F_OK) == 0) { /* prefer current directory over PATH */ if (check_script_file(argv[0], exepath, sizeof(exepath))) shebang = exepath; } else { struct strv path_names = STRV_INIT; char *path, *dir; int i, ret; strv_split(&path_names, getenv("PATH"), ":"); strv_for_each(&path_names, dir, i) { xasprintf(&path, "%s/%s", dir, argv[0]); ret = access(path, F_OK); if (ret == 0 && check_script_file(path, exepath, sizeof(exepath))) shebang = exepath; free(path); if (ret == 0) break; } strv_free(&path_names); } if (shebang) { char *s, *p; int i; #if defined(HAVE_LIBPYTHON2) || defined(HAVE_LIBPYTHON3) if (strstr(shebang, "python")) is_python = true; #endif s = str_ltrim(shebang); p = strchr(s, ' '); if (p != NULL) *p++ = '\0'; strv_append(&new_args, s); if (p != NULL) strv_append(&new_args, p); if (is_python) { strv_append(&new_args, "-m"); strv_append(&new_args, "uftrace"); if (!opts->libcall) setenv("UFTRACE_PY_LIBCALL", "NONE", 1); if (opts->nest_libcall) setenv("UFTRACE_PY_LIBCALL", "NESTED", 1); /* disable library calls for 'python' interpreter */ opts->libcall = false; } for (i = 0; i < argc; i++) strv_append(&new_args, argv[i]); argc = new_args.nr; argv = new_args.p; } setup_child_environ(opts, argc, argv); /* wait for parent ready */ if (read(ready[0], &dummy, sizeof(dummy)) != (ssize_t)sizeof(dummy)) pr_err("waiting for parent failed"); close(ready[0]); if (is_python) { char *python_path = NULL; if (getenv("PYTHONPATH")) python_path = strdup(getenv("PYTHONPATH")); #ifdef INSTALL_LIB_PATH python_path = strjoin(python_path, INSTALL_LIB_PATH, ":"); #endif python_path = strjoin(python_path, "python", ":"); /* FIXME */ setenv("PYTHONPATH", python_path, 1); free(python_path); /* * prevent from creating .pyc files inside __pycache__. * it makes some script execution failed. */ setenv("PYTHONDONTWRITEBYTECODE", "1", 1); } /* * 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 uftrace_opts *opts) { int pid; int ready[2]; 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); if (pipe(ready) < 0) pr_err("creating pipe failed"); 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.15.2/cmds/recv.c000066400000000000000000000374271455365734300153150ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "uftrace.h" #include "utils/list.h" #include "utils/utils.h" struct client_data { struct list_head list; int sock; char *dirname; }; static LIST_HEAD(client_list); static int server_socket(struct uftrace_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"); if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) != 0) pr_warn("socket setting failed\n"); if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) pr_err("socket bind failed (port: %d)", opts->port); if (listen(sock, 5) < 0) pr_err("socket listen failed"); return sock; } static int signal_fd(struct uftrace_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 uftrace_opts *opts) { struct sockaddr_in addr = { .sin_family = AF_INET, .sin_port = htons(opts->port), }; struct addrinfo *results, hints; int err; int sock; int one = 1; /* * Comment for the migration to use getaddrinfo() * in order to not use the deprecated gethostbyname(): * * Next, we may want to loop over trying all addresses returned * by getaddrinfo(). (like in the example loops linked below) * * After this, the code could be made IPv4/IPv6-agnostic: For this, * we'd change sockaddr_in to sockaddr_storage, check ai_family, * and use AF_UNSPEC in place of AF_INET which will allow getaddrinfo() * to return IPv4 and IPv6 addrs. (and the server would be updated too) * * A very nice example is shown in the accepted answer and answer 8 here: * stackoverflow.com/questions/52727565/client-in-c-use-gethostbyname-or-getaddrinfo */ memset(&hints, 0, sizeof hints); hints.ai_family = addr.sin_family; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; err = getaddrinfo(opts->host, NULL, &hints, &results); if (err) pr_err_ns("Failed to resolve host %s: %s\n", opts->host, gai_strerror(err)); addr.sin_addr = ((struct sockaddr_in *)results->ai_addr)->sin_addr; freeaddrinfo(results); sock = socket(hints.ai_family, hints.ai_socktype, 0); if (sock < 0) pr_err("socket create failed"); if (setsockopt(sock, SOL_TCP, TCP_NODELAY, &one, sizeof(one)) != 0) pr_warn("socket setting failed\n"); if (connect(sock, (const struct sockaddr *)&addr, sizeof(addr)) < 0) pr_err("socket connect failed (host: %s, port: %d)", opts->host, opts->port); 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); if (namelen > len) pr_err_ns("invalid namelen received"); 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) { int pid; if (!argv) return; 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 uftrace_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 uftrace_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.15.2/cmds/replay.c000066400000000000000000000734651455365734300156540ustar00rootroot00000000000000#include #include #include #include #include #include #include "uftrace.h" #include "utils/event.h" #include "utils/field.h" #include "utils/filter.h" #include "utils/fstack.h" #include "utils/kernel.h" #include "utils/list.h" #include "utils/symbol.h" #include "utils/utils.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 uftrace_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 uftrace_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 uftrace_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->sym_info, fstack->addr); if (map == MAP_KERNEL) modname = "[kernel]"; else if (map) modname = basename(map->libname); else if (is_sched_event(fstack->addr)) modname = "[event]"; } 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 uftrace_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 uftrace_opts *opts, struct display_field *p_field_table[]) { 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 uftrace_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 uftrace_symbol *sym; char *name; struct uftrace_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 = event_get_name(task->h, evt_id); char *evt_data = event_get_data_str(evt_id, task->args.data, true); 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 { pr_color(color, "%s", evt_name); if (evt_data) pr_color(color, " (%s)", evt_data); } free(evt_name); free(evt_data); } static int print_flat_rstack(struct uftrace_data *handle, struct uftrace_task_reader *task, struct uftrace_opts *opts) { static int count; struct uftrace_record *rstack = task->rstack; struct uftrace_session_link *sessions = &task->h->sessions; struct uftrace_symbol *sym = NULL; char *name; struct uftrace_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; } static void print_char(char **args, size_t *len, const char c) { **args = c; *args += 1; *len -= 1; } static void print_args(char **args, size_t *len, const char *fmt, ...) { int x; va_list ap; va_start(ap, fmt); x = vsnprintf(*args, *len, fmt, ap); va_end(ap); *args += x; *len -= x; } void print_json_escaped_char(char **args, size_t *len, const char c) { if (c == '\n') print_args(args, len, "\\\\n"); else if (c == '\t') print_args(args, len, "\\\\t"); else if (c == '\\') print_args(args, len, "\\\\"); else if (c == '"') print_args(args, len, "\\\""); else if (isprint(c)) print_char(args, len, c); else print_args(args, len, "\\\\x%02hhx", c); } static void print_escaped_char(char **args, size_t *len, const char c) { if (c == '\0') print_args(args, len, "\\0"); else if (c == '\b') print_args(args, len, "\\b"); else if (c == '\n') print_args(args, len, "\\n"); else print_char(args, len, c); } void get_argspec_string(struct uftrace_task_reader *task, char *args, size_t len, enum uftrace_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(&args, &len, "("); else if (needs_assignment) print_args(&args, &len, " = "); 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(&args, &len, ", "); 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(&args, &len, "NULL"); else if (needs_json) { char *p = str; print_args(&args, &len, "\\\""); while (*p) { char c = *p++; print_json_escaped_char(&args, &len, c); } print_args(&args, &len, "\\\""); } else { char *p = str; print_args(&args, &len, "%s\"", color_string); 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(&args, &len, "%.*s", slen, str); } else { p = str; while (*p) { char c = *p++; print_escaped_char(&args, &len, c); } } print_args(&args, &len, "\"%s", color_reset); } /* std::string can be represented as "TEXT"s from C++14 */ if (spec->fmt == ARG_FMT_STD_STRING) print_args(&args, &len, "s"); free(str); size = slen + 2; } else if (spec->fmt == ARG_FMT_CHAR) { char c; memcpy(&c, data, 1); if (needs_json) { print_args(&args, &len, "'"); print_json_escaped_char(&args, &len, c); print_args(&args, &len, "'"); } else { print_args(&args, &len, "%s", color_string); print_args(&args, &len, "'"); print_escaped_char(&args, &len, c); print_args(&args, &len, "'"); print_args(&args, &len, "%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(&args, &len, fmtstr, val.f); break; case 8: print_args(&args, &len, fmtstr, val.d); break; case 10: print_args(&args, &len, 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 uftrace_symbol *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(&args, &len, "%s", color_symbol); if (format_mode == FORMAT_HTML) print_args(&args, &len, "&%s", sym->name); else print_args(&args, &len, "&%s", sym->name); print_args(&args, &len, "%s", color_reset); } else if (val.p) print_args(&args, &len, "%p", val.p); else print_args(&args, &len, "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 uftrace_dbg_info *dinfo; char *estr; memcpy(val.v, data, spec->size); s = find_task_session(sessions, task->t, task->rstack->time); map = find_map(&s->sym_info, task->rstack->addr); if (map == NULL || map->mod == NULL) { print_args(&args, &len, " %x", (int)val.i); goto next; } dinfo = &map->mod->dinfo; estr = get_enum_string(&dinfo->enums, spec->type_name, 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(&args, &len, "%s", color_enum); if (strlen(estr) >= len) print_args(&args, &len, ""); else print_args(&args, &len, "%s", estr); print_args(&args, &len, "%s", color_reset); free(estr); } else if (spec->fmt == ARG_FMT_STRUCT) { if (spec->type_name) { /* * gcc puts "type_name, "type_name, color_reset); } } if (spec->size) print_args(&args, &len, "{...}"); else print_args(&args, &len, "{}"); } 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(&args, &len, fmtstr, val.ll); else print_args(&args, &len, 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(&args, &len, ")"); } 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 uftrace_opts *opts) { struct uftrace_record *rstack; struct uftrace_session_link *sessions = &handle->sessions; struct uftrace_symbol *sym = NULL; enum uftrace_argspec_string_bits str_mode = 0; char *symname = NULL; char args[1024]; char *libname = ""; struct uftrace_mmap *map = NULL; struct uftrace_dbg_loc *loc = NULL; char *str_loc = 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) { int len = strlen(symname); if (symname[len - 1] != ')' || rstack->more || (len > 10 && !strcmp(symname + len - 10, "operator()"))) 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->sym_info, symname); if (map != NULL) libname = basename(map->libname); } } if (opts->srcline) { loc = task_find_loc_addr(sessions, task, rstack->time, rstack->addr); if (opts->comment && loc) xasprintf(&str_loc, "%s:%d", loc->file->name, loc->line); } if (rstack->type == UFTRACE_ENTRY) { struct uftrace_task_reader *next = NULL; struct uftrace_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 && opts->show_args) 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 && opts->show_args) { 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", args, retval); } else { pr_out("%s%s%s%s%s", symname, *libname ? "@" : "", libname, args, retval); } if (str_loc) pr_gray(" /* %s */", str_loc); pr_out("\n"); /* 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 {", args); } else { pr_out("%s%s%s%s {", symname, *libname ? "@" : "", libname, args); } if (str_loc) pr_gray(" /* %s */", str_loc); pr_out("\n"); fstack_update(UFTRACE_ENTRY, task, fstack); } } else if (rstack->type == UFTRACE_EXIT) { struct uftrace_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 && opts->show_args) { 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 */", symname, *libname ? "@" : "", libname); } pr_out("\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, ""); free(str_loc); return 0; } else if (rstack->type == UFTRACE_EVENT) { int depth; struct uftrace_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 || evt_id == EVENT_ID_PERF_SCHED_OUT_PREEMPT) && !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); if (evt_id == EVENT_ID_PERF_SCHED_OUT) rec.addr = EVENT_ID_PERF_SCHED_BOTH; else if (evt_id == EVENT_ID_PERF_SCHED_OUT_PREEMPT) rec.addr = EVENT_ID_PERF_SCHED_BOTH_PREEMPT; else pr_warn("unexpected event id\n"); 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); free(str_loc); 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 uftrace_opts *opts, struct uftrace_task_reader *task) { struct uftrace_symbol *sym; struct uftrace_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->sym_info, 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 uftrace_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; if (task->stack_count == 1) { struct uftrace_fstack *fstack = fstack_get(task, 0); /* ignore if it only has a schedule event */ if (fstack && (fstack->addr == EVENT_ID_PERF_SCHED_OUT || fstack->addr == EVENT_ID_PERF_SCHED_OUT_PREEMPT)) continue; } /* sometimes it has many 0 entries in the fstack. ignore them */ for (k = 0; k < task->stack_count; k++) { struct uftrace_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 uftrace_fstack *fstack; int zero_count = 0; if (task->stack_count == 0) continue; if (task->stack_count == 1) { fstack = fstack_get(task, 0); /* skip if it only has a schedule event */ if (fstack && (fstack->addr == EVENT_ID_PERF_SCHED_OUT || fstack->addr == EVENT_ID_PERF_SCHED_OUT_PREEMPT)) 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 uftrace_symbol *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 uftrace_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 (format_mode == FORMAT_HTML) pr_out(HTML_HEADER); if (!opts->flat && peek_rstack(&handle, &task) == 0) print_header(&output_fields, "#", "FUNCTION", 1, false); if (!list_empty(&output_fields)) { if (opts->srcline) pr_gray(" [SOURCE]"); pr_out("\n"); } 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); if (format_mode == FORMAT_HTML) pr_out(HTML_FOOTER); close_data_file(opts, &handle); return ret; } #ifdef UNIT_TEST static const char *record_type_str(struct uftrace_record *rec) { switch (rec->type) { case UFTRACE_ENTRY: return "ENTRY"; case UFTRACE_EXIT: return "EXIT"; case UFTRACE_LOST: return "LOST"; case UFTRACE_EVENT: return "EVENT"; default: break; } return "UNKNOWN"; } TEST_CASE(replay_command) { struct uftrace_opts opts = { .dirname = "replay-graph-test", .exename = read_exename(), .max_stack = 10, .depth = OPT_DEPTH_DEFAULT, }; struct uftrace_data handle; struct uftrace_task_reader *task; uint64_t prev_time = 0; TEST_EQ(prepare_test_data(&opts, &handle), 0); setup_field(&output_fields, &opts, &setup_default_field, field_table, ARRAY_SIZE(field_table)); if (peek_rstack(&handle, &task) == 0) print_header(&output_fields, "#", "FUNCTION", 1, false); if (!list_empty(&output_fields)) pr_out("\n"); pr_dbg("replay test data in graph format\n"); while (read_rstack(&handle, &task) == 0) { struct uftrace_record *rstack = task->rstack; uint64_t curr_time = rstack->time; if (!fstack_check_opts(task, &opts)) { pr_dbg("task=%d time=%lu skip\n", task->tid, rstack->time); continue; } pr_dbg("task=%d time=%lu depth=%d type=%s\n", task->tid, rstack->time, rstack->depth, record_type_str(rstack)); /* * 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; } /* this will merge adjacent ENTRY and EXIT */ TEST_EQ(print_graph_rstack(&handle, task, &opts), 0); } print_remaining_stack(&opts, &handle); release_test_data(&opts, &handle); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/cmds/report.c000066400000000000000000000346051455365734300156640ustar00rootroot00000000000000#include #include #include "uftrace.h" #include "utils/field.h" #include "utils/fstack.h" #include "utils/list.h" #include "utils/rbtree.h" #include "utils/report.h" #include "utils/symbol.h" #include "utils/utils.h" enum avg_mode avg_mode = AVG_NONE; /* maximum length of symbol */ static int maxlen = 20; static LIST_HEAD(output_fields); static void print_field(struct uftrace_report_node *node, int space) { struct field_data fd = { .arg = node, }; print_field_data(&output_fields, &fd, space); } static void insert_node(struct rb_root *root, struct uftrace_task_reader *task, char *symname, struct uftrace_dbg_loc *loc) { 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, loc); } static void find_insert_node(struct rb_root *root, struct uftrace_task_reader *task, uint64_t timestamp, uint64_t addr, bool needs_srcline) { struct uftrace_symbol *sym; char *symname; struct uftrace_dbg_loc *loc = NULL; sym = task_find_sym_addr(&task->h->sessions, task, timestamp, addr); if (needs_srcline) loc = task_find_loc_addr(&task->h->sessions, task, timestamp, addr); task->func = sym; symname = symbol_getname(sym, addr); insert_node(root, task, symname, loc); symbol_putname(sym, symname); } static void add_lost_fstack(struct rb_root *root, struct uftrace_task_reader *task, struct uftrace_opts *opts) { struct uftrace_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, opts->srcline); } fstack_exit(task); task->stack_count--; } } static void add_remaining_fstack(struct uftrace_data *handle, struct rb_root *root, struct uftrace_opts *opts) { struct uftrace_task_reader *task; struct uftrace_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, NULL); else find_insert_node(root, task, last_time, fstack->addr, opts->srcline); } } } static void build_function_tree(struct uftrace_data *handle, struct rb_root *root, struct uftrace_opts *opts) { struct uftrace_session_link *sessions = &handle->sessions; struct uftrace_symbol *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) { fstack_check_filter_done(task); continue; } if (rstack->type == UFTRACE_EVENT) { if (rstack->addr == EVENT_ID_PERF_SCHED_IN) { char *name; struct uftrace_fstack *fstack; fstack = fstack_get(task, task->stack_count); if (fstack == NULL) continue; if (fstack->addr == EVENT_ID_PERF_SCHED_OUT) name = sched_sym.name; else if (fstack->addr == EVENT_ID_PERF_SCHED_OUT_PREEMPT) name = sched_preempt_sym.name; else continue; insert_node(root, task, name, NULL); } continue; } if (rstack->type == UFTRACE_LOST) { /* add partial duration of functions before LOST */ add_lost_fstack(root, task, opts); 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->sym_info, 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) { fstack_check_filter_done(task); continue; } find_insert_node(root, task, rstack->time, addr, opts->srcline); fstack_check_filter_done(task); } if (uftrace_done) return; add_remaining_fstack(handle, root, opts); } static void print_and_delete(struct rb_root *root, bool sorted, void *arg, void (*print_func)(struct uftrace_report_node *, void *, int space), int space) { 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, space); free(node->name); free(node); } } static void print_function(struct uftrace_report_node *node, void *unused, int space) { print_field(node, space); pr_out("%*s", space, " "); pr_out("%-s", node->name); if (node->loc) pr_gray(" [%s:%d]", node->loc->file->name, node->loc->line); pr_out("\n"); } static void print_line(struct list_head *output_fields, int space) { struct display_field *field; const char line[] = "================================================="; /* do not print anything if not needed */ if (list_empty(output_fields)) return; list_for_each_entry(field, output_fields, list) { pr_out("%*s", space, ""); pr_out("%-.*s", field->length, line); } pr_out("%*s", space, " "); pr_out("%-.*s\n", maxlen, line); } static void report_functions(struct uftrace_data *handle, struct uftrace_opts *opts) { struct rb_root name_root = RB_ROOT; struct rb_root sort_root = RB_ROOT; const int field_space = 2; build_function_tree(handle, &name_root, opts); report_calc_avg(&name_root); report_sort_nodes(&name_root, &sort_root); if (uftrace_done) return; setup_report_field(&output_fields, opts, avg_mode); print_header_align(&output_fields, " ", "Function", field_space, ALIGN_RIGHT, false); if (!list_empty(&output_fields)) { if (opts->srcline) pr_gray(" [Source]"); pr_out("\n"); } print_line(&output_fields, field_space); print_and_delete(&sort_root, true, NULL, print_function, field_space); } static void add_remaining_task_fstack(struct uftrace_data *handle, struct rb_root *root) { struct uftrace_task_reader *task; struct uftrace_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, NULL); } } } 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_task(struct uftrace_report_node *node, void *arg, int space) { int pid; struct uftrace_task *t; struct uftrace_data *handle = arg; pid = strtol(node->name, NULL, 10); t = find_task(&handle->sessions, pid); print_field(node, space); pr_out("%*s", space, " "); pr_out("%-16s\n", t->comm); } static void report_task(struct uftrace_data *handle, struct uftrace_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; char buf[10]; int field_space = 2; 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, NULL); } 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); setup_report_field(&output_fields, opts, avg_mode); print_header_align(&output_fields, " ", "Task name", field_space, ALIGN_RIGHT, true); print_line(&output_fields, field_space); print_and_delete(&sort_tree, true, handle, print_task, field_space); } struct diff_data { char *dirname; struct rb_root root; struct uftrace_data handle; }; static void report_diff(struct uftrace_data *handle, struct uftrace_opts *opts) { struct uftrace_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; int field_space = 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"); setup_report_field(&output_fields, opts, avg_mode); print_header_align(&output_fields, " ", "Function", field_space, ALIGN_RIGHT, false); if (!list_empty(&output_fields)) { if (opts->srcline) pr_gray(" [Source]"); pr_out("\n"); } print_line(&output_fields, field_space); print_and_delete(&diff_tree, true, NULL, print_function, field_space); out: destroy_diff_nodes(&base_tree, &pair_tree); __close_data_file(&dummy_opts, &data.handle, false); } int command_report(int argc, char *argv[], struct uftrace_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->fields && (opts->avg_self || opts->avg_total)) { pr_warn("--avg-total and --avg-self options are ignored when used with -f option.\n"); } 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, avg_mode); 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, avg_mode); 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 (format_mode == FORMAT_HTML) pr_out(HTML_HEADER); if (opts->show_task) report_task(&handle, opts); else if (opts->diff) report_diff(&handle, opts); else report_functions(&handle, opts); if (format_mode == FORMAT_HTML) pr_out(HTML_FOOTER); close_data_file(opts, &handle); return 0; } #ifdef UNIT_TEST TEST_CASE(report_command1) { struct uftrace_opts opts = { .dirname = "report-func-test", .exename = read_exename(), .max_stack = 10, .depth = OPT_DEPTH_DEFAULT, }; struct uftrace_data handle; char *sort_keys; TEST_EQ(prepare_test_data(&opts, &handle), 0); pr_dbg("report setup sort key\n"); sort_keys = convert_sort_keys(opts.sort_keys, AVG_TOTAL); TEST_EQ(report_setup_sort(sort_keys), 1); pr_dbg("report functions\n"); report_functions(&handle, &opts); release_test_data(&opts, &handle); free(sort_keys); return TEST_OK; } TEST_CASE(report_command2) { struct uftrace_opts opts = { .dirname = "report-task-test", .exename = read_exename(), .max_stack = 10, .depth = OPT_DEPTH_DEFAULT, }; struct uftrace_data handle; char *sort_keys; TEST_EQ(prepare_test_data(&opts, &handle), 0); pr_dbg("report setup sort key\n"); sort_keys = convert_sort_keys("self", AVG_SELF); TEST_EQ(report_setup_sort(sort_keys), 1); pr_dbg("report task\n"); report_task(&handle, &opts); release_test_data(&opts, &handle); free(sort_keys); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/cmds/script.c000066400000000000000000000116771455365734300156610ustar00rootroot00000000000000/* * 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 "utils/event.h" #include "utils/filter.h" #include "utils/fstack.h" #include "utils/script.h" #include "utils/symbol.h" #include "utils/utils.h" #include "version.h" static int run_script_for_rstack(struct uftrace_data *handle, struct uftrace_task_reader *task, struct uftrace_opts *opts) { struct uftrace_record *rstack = task->rstack; struct uftrace_session_link *sessions = &handle->sessions; struct uftrace_symbol *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 script_context sc_ctx = { 0, }; struct uftrace_fstack *fstack; struct uftrace_trigger tr = { .flags = 0, }; int depth; 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; sc_ctx.tid = task->tid; sc_ctx.depth = depth; /* display depth */ sc_ctx.timestamp = rstack->time; sc_ctx.address = rstack->addr; sc_ctx.name = symname; if (tr.flags & TRIGGER_FL_ARGUMENT && opts->show_args) { 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 script_context sc_ctx = { 0, }; struct uftrace_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 */ sc_ctx.tid = task->tid; sc_ctx.depth = rstack->depth; sc_ctx.timestamp = rstack->time; sc_ctx.duration = fstack->total_time; sc_ctx.address = rstack->addr; sc_ctx.name = symname; if (rstack->more && opts->show_args) { 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_EVENT) { struct script_context sc_ctx = { .tid = task->tid, .depth = rstack->depth, .timestamp = rstack->time, .address = rstack->addr, }; sc_ctx.name = event_get_name(handle, rstack->addr); sc_ctx.argbuf = event_get_data_str(rstack->addr, task->args.data, false); script_uftrace_event(&sc_ctx); free(sc_ctx.name); free(sc_ctx.argbuf); } else if (rstack->type == UFTRACE_LOST) { /* Do nothing as of now */ } out: symbol_putname(sym, symname); return 0; } int command_script(int argc, char *argv[], struct uftrace_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) { char *script_file; /* parse in-script record option - "uftrace_options" */ parse_script_opt(opts); 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.15.2/cmds/tui.c000066400000000000000000002217711455365734300151540ustar00rootroot00000000000000#ifdef HAVE_LIBNCURSES #include #include #include #include #include #include #include #include #include #include #include "uftrace.h" #include "utils/dwarf.h" #include "utils/field.h" #include "utils/fstack.h" #include "utils/graph.h" #include "utils/list.h" #include "utils/rbtree.h" #include "utils/report.h" #include "utils/utils.h" #include "version.h" #define KEY_ESCAPE 27 #define BLANK 32 #define TUI_ASSERT(cond) \ do { \ endwin(); \ ASSERT(cond); \ } while (0) #define TUI_DASSERT(cond) \ do { \ endwin(); \ DASSERT(cond); \ } while (0) 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 uftrace_dbg_loc *(*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 LIST_HEAD(report_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", "f Customize fields in graph or report mode", "h/? Show this help", "q Quit", }; #define NUM_GRAPH_FIELD 3 static const char *graph_field_names[NUM_GRAPH_FIELD] = { "TOTAL TIME", "SELF TIME", "ADDRESS", }; #define NUM_REPORT_FIELD 10 static const char *report_field_names[NUM_REPORT_FIELD] = { "TOTAL TIME", "TOTAL AVG", "TOTAL MIN", "TOTAL MAX", "SELF TIME", "SELF AVG", "SELF MIN", "SELF MAX", "CALL", "SIZE", }; static const char *field_help[] = { "DOWN/UP ARROW Move down/up", "j/k Move down/up", "Enter Apply checked fields", "SPACE Check or uncheck a field", "f/q Close the window without any changes", }; enum tui_mode { TUI_MODE_GRAPH, TUI_MODE_REPORT, TUI_MODE_OTHER, }; static char *report_sort_key[] = { OPT_SORT_KEYS, "total_avg", "total_min", "total_max", "self", "self_avg", "self_min", "self_max", "call", "size", }; static char *selected_report_sort_key[NUM_REPORT_FIELD]; 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 graph_field_total = { .id = GRAPH_F_TOTAL_TIME, .name = "total-time", .alias = "total", .header = "TOTAL TIME", .length = 10, .print = print_graph_total, .list = LIST_HEAD_INIT(graph_field_total.list), }; static struct display_field graph_field_self = { .id = GRAPH_F_SELF_TIME, .name = "self-time", .alias = "self", .header = " SELF TIME", .length = 10, .print = print_graph_self, .list = LIST_HEAD_INIT(graph_field_self.list), }; static struct display_field graph_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(graph_field_addr.list), }; /* index of this table should be matched to display_field_id */ static struct display_field *graph_field_table[] = { &graph_field_total, &graph_field_self, &graph_field_addr, }; /* clang-format off */ #define REPORT_FIELD_STRUCT(_id, _name, _func, _header, _length) \ static struct display_field report_field_##_func = { \ .id = _id, \ .name = #_name, \ .header = _header, \ .length = _length, \ .print = print_report_##_func, \ .list = LIST_HEAD_INIT(report_field_##_func.list) \ }; #define REPORT_FIELD_TIME(_id, _name, _field, _func, _header) \ static void print_report_##_func(struct field_data *fd) \ { \ struct uftrace_report_node *node = fd->arg; \ uint64_t d = node->_field; \ printw(" "); \ print_time(d); \ } \ REPORT_FIELD_STRUCT(_id, _name, _func, _header, 11) #define REPORT_FIELD_UINT(_id, _name, _field, _func, _header) \ static void print_report_##_func(struct field_data *fd) \ { \ struct uftrace_report_node *node = fd->arg; \ uint64_t d = node->_field; \ printw(" "); \ printw("%10"PRIu64 "", d); \ } \ REPORT_FIELD_STRUCT(_id, _name, _func, _header, 11) REPORT_FIELD_TIME(REPORT_F_TOTAL_TIME, total, total.sum, total, "TOTAL TIME"); REPORT_FIELD_TIME(REPORT_F_TOTAL_TIME_AVG, total-avg, total.avg, total_avg, "TOTAL AVG"); REPORT_FIELD_TIME(REPORT_F_TOTAL_TIME_MIN, total-min, total.min, total_min, "TOTAL MIN"); REPORT_FIELD_TIME(REPORT_F_TOTAL_TIME_MAX, total-max, total.max, total_max, "TOTAL MAX"); REPORT_FIELD_TIME(REPORT_F_SELF_TIME, self, self.sum, self, "SELF TIME"); REPORT_FIELD_TIME(REPORT_F_SELF_TIME_AVG, self-avg, self.avg, self_avg, "SELF AVG"); REPORT_FIELD_TIME(REPORT_F_SELF_TIME_MIN, self-min, self.min, self_min, "SELF MIN"); REPORT_FIELD_TIME(REPORT_F_SELF_TIME_MAX, self-max, self.max, self_max, "SELF MAX"); REPORT_FIELD_UINT(REPORT_F_CALL, call, call, call, "CALL"); REPORT_FIELD_UINT(REPORT_F_SIZE, size, size, size, "SIZE"); /* clang-format on */ static struct display_field *report_field_table[] = { &report_field_total, &report_field_total_avg, &report_field_total_min, &report_field_total_max, &report_field_self, &report_field_self_avg, &report_field_self_min, &report_field_self_max, &report_field_call, &report_field_size, }; static void setup_default_graph_field(struct list_head *fields, struct uftrace_opts *opts, struct display_field *p_field_table[]) { add_field(fields, p_field_table[GRAPH_F_TOTAL_TIME]); } static void setup_default_report_field(struct list_head *fields, struct uftrace_opts *opts, struct display_field *p_field_table[]) { add_field(fields, p_field_table[REPORT_F_TOTAL_TIME]); add_field(fields, p_field_table[REPORT_F_SELF_TIME]); add_field(fields, p_field_table[REPORT_F_CALL]); } 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 uftrace_opts *opts) { walk_sessions(&handle->sessions, create_data, NULL); tui_report.name_tree = RB_ROOT; if (opts->report) { setup_field(&report_output_fields, opts, setup_default_report_field, report_field_table, ARRAY_SIZE(report_field_table)); setup_default_graph_field(&graph_output_fields, opts, graph_field_table); } else { setup_field(&graph_output_fields, opts, setup_default_graph_field, graph_field_table, ARRAY_SIZE(graph_field_table)); setup_default_report_field(&report_output_fields, opts, report_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->sym_info, 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, NULL); } static int build_tui_node(struct uftrace_task_reader *task, struct uftrace_record *rec, struct uftrace_opts *opts) { struct uftrace_task_graph *tg; struct uftrace_graph *graph; struct tui_graph_node *graph_node; struct uftrace_symbol *sym = NULL; 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->sym_info, 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); task->func = sym; /* 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) { if (addr == EVENT_ID_PERF_SCHED_IN) { struct uftrace_fstack *fstack; fstack = fstack_get(task, task->stack_count); if (!fstack) return -1; if (fstack->addr == EVENT_ID_PERF_SCHED_OUT) sym = &sched_sym; else if (fstack->addr == EVENT_ID_PERF_SCHED_OUT_PREEMPT) sym = &sched_preempt_sym; else return -1; name = symbol_getname(sym, addr); update_report_node(task, name, tg); } else if (addr == EVENT_ID_PERF_SCHED_OUT) { sym = &sched_sym; name = symbol_getname(sym, addr); } else if (addr == EVENT_ID_PERF_SCHED_OUT_PREEMPT) { sym = &sched_preempt_sym; name = symbol_getname(sym, addr); } else return 0; } else /* rec->type == UFTRACE_LOST */ return 0; graph_add_node(tg, rec->type, name, sizeof(struct tui_graph_node), NULL); 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 uftrace_opts *opts, struct uftrace_data *handle) { uint64_t last_time; struct uftrace_fstack *fstack; struct uftrace_task_reader *task; struct uftrace_task_graph *tg; struct uftrace_symbol *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), NULL); 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 uftrace_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 uftrace_dbg_loc *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->sym_info, 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 uftrace_dbg_loc *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->sym_info, 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; } static void report_sort_key_init(void) { int i, j = 0; for (i = 0; i < NUM_REPORT_FIELD; i++) { if (report_field_table[i]->used) selected_report_sort_key[j++] = report_sort_key[i]; } } static int count_selected_report_sort_key(void) { int count = 0; int i; for (i = 0; i < NUM_REPORT_FIELD; i++) { if (report_field_table[i]->used) count++; } return count; } static void curr_sort_key_init(char *sort_keys) { int count = count_selected_report_sort_key(); int i; for (i = 0; i < count; i++) { const char *key = selected_report_sort_key[i]; if (!strncmp(sort_keys, key, strlen(key))) { curr_sort_key = i; break; } } } /* per-window operations for report window */ static struct tui_report *tui_report_init(struct uftrace_opts *opts) { struct tui_window *win = &tui_report.win; char *sort_keys; report_calc_avg(&tui_report.name_tree); report_sort_key_init(); if (count_selected_report_sort_key()) { if (opts->report && opts->sort_keys) { sort_keys = convert_sort_keys(opts->sort_keys, AVG_NONE); curr_sort_key_init(sort_keys); } else { sort_keys = xstrdup(selected_report_sort_key[curr_sort_key]); } report_setup_sort(sort_keys); free(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 = 0, c; char *buf, *p; struct display_field *field; int i = 0; if (list_empty(&report_output_fields)) { printw("%-*.*s", COLS, COLS, "uftrace report TUI"); return; } list_for_each_entry(field, &report_output_fields, list) { w += field->length + 3; } w += strlen(" FUNCTION"); buf = p = xmalloc(w + 1); list_for_each_entry(field, &report_output_fields, list) { char header[field->length + 1]; header[0] = '\0'; if (i == curr_sort_key) strcpy(header, "*"); strcat(header, field->header); c = snprintf(p, w, " %*s", field->length, header); p += c; w -= c; i++; } snprintf(p, w + 1, " %s", "FUNCTION"); printw("%-*.*s", COLS, COLS, buf); free(buf); } 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 uftrace_dbg_loc *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 display_field *field; struct tui_report_node *curr = node; struct field_data fd = { .arg = curr, }; int w = 2; list_for_each_entry(field, &report_output_fields, list) { field->print(&fd); w += field->length + 1; } printw(" "); printw("%-*.*s", COLS - w, COLS - w, curr->n.name); } static struct uftrace_dbg_loc *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 uftrace_dbg_loc *dloc; list_for_each_entry(gnode, &curr->head, link) { sess = gnode->graph->sess; dloc = find_file_line(&sess->sym_info, 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 uftrace_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, sizeof(buf), "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 uftrace_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 uftrace_dbg_loc *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, "nvi", 3) || !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 void display_tui_field(WINDOW *win, int selected_field, bool field_flags[], int num_field, const char *field_names[]) { int i; for (i = 0; i < num_field; i++) { if (i == selected_field) wattron(win, A_REVERSE); mvwprintw(win, i + ARRAY_SIZE(field_help) + 4, 2, "[ %c ] %s", field_flags[i] ? 'x' : ' ', field_names[i]); wattroff(win, A_REVERSE); } } static void update_graph_output_fields(bool graph_field_flags[]) { struct display_field *field, *tmp; int i; list_for_each_entry_safe(field, tmp, &graph_output_fields, list) del_field(field); for (i = 0; i < NUM_GRAPH_FIELD; i++) { if (graph_field_flags[i]) add_field(&graph_output_fields, graph_field_table[i]); } } static void update_report_output_fields(bool report_field_flags[]) { struct display_field *field, *tmp; int i, j = 0; list_for_each_entry_safe(field, tmp, &report_output_fields, list) del_field(field); for (i = 0; i < NUM_REPORT_FIELD; i++) selected_report_sort_key[i] = NULL; for (i = 0; i < NUM_REPORT_FIELD; i++) { if (report_field_flags[i]) { add_field(&report_output_fields, report_field_table[i]); selected_report_sort_key[j++] = report_sort_key[i]; } } } static inline void tui_graph_field_flags_init(bool graph_field_flags[]) { int i; for (i = 0; i < NUM_GRAPH_FIELD; i++) graph_field_flags[i] = graph_field_table[i]->used; } static inline void tui_report_field_flags_init(bool report_field_flags[]) { int i; for (i = 0; i < NUM_REPORT_FIELD; i++) report_field_flags[i] = report_field_table[i]->used; } static void tui_window_field(enum tui_mode tui_mode) { WINDOW *win; int w = 64; int h; bool done = false; unsigned i; bool graph_field_flags[NUM_GRAPH_FIELD] = { false }; bool report_field_flags[NUM_REPORT_FIELD] = { false }; int selected_field = 0; int num_field; if (tui_mode == TUI_MODE_OTHER) return; num_field = tui_mode == TUI_MODE_GRAPH ? NUM_GRAPH_FIELD : NUM_REPORT_FIELD; h = num_field + ARRAY_SIZE(field_help) + 6; tui_graph_field_flags_init(graph_field_flags); tui_report_field_flags_init(report_field_flags); if (w > COLS) w = COLS; if (h > LINES) h = LINES; win = newwin(h, w, (LINES - h) / 2, (COLS - w) / 2); keypad(win, true); wrefresh(win); box(win, 0, 0); if (tui_mode == TUI_MODE_GRAPH) mvwprintw(win, 1, 2, "Customize fields in graph mode"); else mvwprintw(win, 1, 2, "Customize fields in report mode"); for (i = 0; i < ARRAY_SIZE(field_help); i++) mvwprintw(win, i + 3, 2, "%-*.*s", w - 3, w - 3, field_help[i]); if (tui_mode == TUI_MODE_GRAPH) display_tui_field(win, selected_field, graph_field_flags, NUM_GRAPH_FIELD, graph_field_names); else display_tui_field(win, selected_field, report_field_flags, NUM_REPORT_FIELD, report_field_names); mvwprintw(win, h - 1, w - 1, " "); wrefresh(win); while (!done) { int k = wgetch(win); switch (k) { case 'k': case KEY_UP: selected_field--; if (selected_field < 0) selected_field = num_field - 1; if (tui_mode == TUI_MODE_GRAPH) display_tui_field(win, selected_field, graph_field_flags, NUM_GRAPH_FIELD, graph_field_names); else display_tui_field(win, selected_field, report_field_flags, NUM_REPORT_FIELD, report_field_names); break; case 'j': case KEY_DOWN: selected_field++; if (selected_field >= num_field) selected_field = 0; if (tui_mode == TUI_MODE_GRAPH) display_tui_field(win, selected_field, graph_field_flags, NUM_GRAPH_FIELD, graph_field_names); else display_tui_field(win, selected_field, report_field_flags, NUM_REPORT_FIELD, report_field_names); break; case KEY_ENTER: case '\n': if (tui_mode == TUI_MODE_GRAPH) update_graph_output_fields(graph_field_flags); else update_report_output_fields(report_field_flags); done = true; break; case 'f': case 'q': done = true; break; case ' ': if (tui_mode == TUI_MODE_GRAPH) { graph_field_flags[selected_field] ^= 1; display_tui_field(win, selected_field, graph_field_flags, NUM_GRAPH_FIELD, graph_field_names); } else { report_field_flags[selected_field] ^= 1; display_tui_field(win, selected_field, report_field_flags, NUM_REPORT_FIELD, report_field_names); } break; } } delwin(win); } static inline void cancel_search(void) { free(tui_search); tui_search = NULL; } static void tui_main_loop(struct uftrace_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; enum tui_mode tui_mode; int num_sort_key = 3; 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; tui_mode = TUI_MODE_REPORT; } else if (session->nr_node > 1) { win = &session->win; tui_mode = TUI_MODE_OTHER; } else { win = &graph->win; tui_mode = TUI_MODE_GRAPH; } 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); tui_mode = TUI_MODE_REPORT; break; case TUI_SESS_INFO: win = &info->win; tui_window_move_home(win); tui_mode = TUI_MODE_OTHER; break; case TUI_SESS_HELP: tui_window_help(); tui_mode = TUI_MODE_OTHER; break; case TUI_SESS_QUIT: tui_mode = TUI_MODE_OTHER; goto out; default: /* change window for the current graph */ graph = get_current_graph(win->curr, NULL); win = &graph->win; tui_window_move_home(win); tui_mode = TUI_MODE_GRAPH; 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; tui_mode = TUI_MODE_GRAPH; } 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; tui_mode = TUI_MODE_GRAPH; break; case 'R': if (tui_window_change(win, &report->win)) { win = &report->win; tui_window_move_home(win); full_redraw = true; tui_mode = TUI_MODE_REPORT; } 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; tui_mode = TUI_MODE_REPORT; } break; case 's': if (!tui_window_change(win, &report->win)) { num_sort_key = count_selected_report_sort_key(); if (num_sort_key == 0) break; curr_sort_key = (curr_sort_key + 1) % num_sort_key; report_setup_sort(selected_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; tui_mode = TUI_MODE_OTHER; } break; case 'S': if (tui_window_change(win, &session->win)) { win = &session->win; full_redraw = true; tui_mode = TUI_MODE_OTHER; } 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 'f': tui_window_field(tui_mode); if (tui_mode == TUI_MODE_REPORT && count_selected_report_sort_key()) { if (!tui_window_change(win, &report->win)) { report_setup_sort(selected_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 '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(struct uftrace_opts *opts) { char *tuimsg = "Building graph for TUI..."; int row, col; if (opts->report) tuimsg = "Building report for TUI..."; getmaxyx(stdscr, row, col); mvprintw(row / 2, (col - strlen(tuimsg)) / 2, "%s", tuimsg); refresh(); } int command_tui(int argc, char *argv[], struct uftrace_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; } tui_setup(&handle, opts); setlocale(LC_ALL, ""); initscr(); init_colors(); keypad(stdscr, true); curs_set(0); noecho(); atexit(tui_cleanup); /* Print a message before main screen is launched. */ display_loading_msg(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; fstack_check_filter_done(task); } add_remaining_node(opts, &handle); tui_main_loop(opts, &handle); close_data_file(opts, &handle); tui_cleanup(); return 0; } #ifdef UNIT_TEST TEST_CASE(tui_command) { struct uftrace_opts opts = { .dirname = "tui-cmd-test", .exename = read_exename(), .max_stack = 10, .depth = OPT_DEPTH_DEFAULT, }; struct uftrace_data handle; struct uftrace_task_reader *task; TEST_EQ(prepare_test_data(&opts, &handle), 0); pr_dbg("construct data structure for TUI\n"); tui_setup(&handle, &opts); while (read_rstack(&handle, &task) == 0) { struct uftrace_record *rec = task->rstack; TEST_NE(fstack_check_opts(task, &opts), 0); TEST_NE(fstack_check_filter(task), 0); TEST_EQ(build_tui_node(task, rec, &opts), 0); fstack_check_filter_done(task); } add_remaining_node(&opts, &handle); tui_cleanup(); release_test_data(&opts, &handle); return TEST_OK; } #endif /* UNIT_TEST */ #else /* !HAVE_LIBNCURSES */ #include "uftrace.h" #include "utils/utils.h" int command_tui(int argc, char *argv[], struct uftrace_opts *opts) { pr_warn("TUI is unsupported (libncursesw.so is missing)\n"); return 0; } #endif /* HAVE_LIBNCURSES */ uftrace-0.15.2/configure000077500000000000000000000231061455365734300151600ustar00rootroot00000000000000#!/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/uftrace) --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-libunwind build without libunwind (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) --without-libtraceevent build without libtraceevent (even if found on the system) --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 -o output filename 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 "Error: '$arch' is not a supported architecture" >&2 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/uftrace} 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} if $CC --version | grep -q Android; then ANDROID=1 fi # 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 check_command() { if ! command -v $1 &>/dev/null then echo "Error: '$1' command is not found" >&2 exit 1 fi } check_command make check_command ${CC} make -siC ${srcdir}/check-deps O=${objdir} check-clean make -siC ${srcdir}/check-deps O=${objdir} 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 ;; libunwind) TARGET=have_libunwind ;; libstdc++) TARGET=cxa_demangle ;; capstone) TARGET=have_libcapstone ;; perf*) TARGET=perf_clockid ;; sched*) TARGET=perf_context_switch;; libtraceevent) TARGET=have_libtraceevent ;; *) ;; esac if [ ! -z "$TARGET" ]; then rm -f ${objdir}/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 ${objdir}/check-deps/${file} ]; then onoff="\033[32mon \033[0m" else onoff="\033[91mOFF\033[0m" fi else if [ -f ${objdir}/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 ${objdir}/check-deps/${file1} -o -f ${objdir}/check-deps/${file2} ]; then onoff="\033[32mon \033[0m" else onoff="\033[91mOFF\033[0m" fi else if [ -f ${objdir}/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 tracing & 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" print_feature "libtraceevent" "have_libtraceevent" "kernel tracing support" print_feature "libunwind" "have_libunwind" "stacktrace support (optional for debugging)" 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.15.2/doc/ko/000077500000000000000000000000001455365734300144255ustar00rootroot00000000000000uftrace-0.15.2/doc/ko/CONTRIBUTING.md000066400000000000000000000124351455365734300166630ustar00rootroot00000000000000uftraceì— ê¸°ì—¬í•˜ê¸° ================== uftraceì— ê¸°ì—¬í•˜ëŠ” ê²ƒì„ ê³ ë ¤í•´ì£¼ì…”ì„œ ê°ì‚¬í•©ë‹ˆë‹¤. ì•„ëž˜ì˜ ì£¼ì†Œì—서 uftrace 소스코드를 git으로 í´ë¡ í•œ ë’¤ 패치와 함께 PRì„ ë³´ë‚´ì£¼ì‹œë©´ ë©ë‹ˆë‹¤. 패치를 ì§„í–‰í•˜ì‹œê¸°ì— ì•žì„œ, 본 ê¸€ì— ì†Œê°œëœ ê·œì¹™ë“¤ì„ ë¨¼ì € ì½ì–´ì£¼ì‹œê¸°ë¥¼ 권장드립니다. https://github.com/namhyung/uftrace 코드 작성 ìŠ¤íƒ€ì¼ ---------------- uftrace는 C로 작성ë˜ì—ˆê³  몇가지 ì°¨ì´ì ë“¤ì„ 제외하고서는 ê±°ì˜ ëŒ€ë¶€ë¶„ [리눅스 커ë„ì˜ ì½”ë“œ 작성 스타ì¼](https://www.kernel.org/doc/Documentation/process/coding-style.rst)ì„ ë”°ë¥´ê³  있습니다. uftrace 저장소ì—서는 [pre-commit](https://pre-commit.com)ê³¼ [clang-format](https://clang.llvm.org/docs/ClangFormat.html)ì„ í†µí•´ ìžë™ìœ¼ë¡œ 코드 형ì‹ì„ ì ìš©ì‹œì¼œ ì „ë°˜ì ì¸ ì†ŒìŠ¤ì½”ë“œì˜ ì½”ë“œ 작성 스타ì¼ì´ í•­ìƒ ì¼ê´€ì ìœ¼ë¡œ ìœ ì§€ë  ìˆ˜ 있ë„ë¡ í•˜ê³  있습니다. 코드 작성 스타ì¼ì„ ìžë™ìœ¼ë¡œ 검사하기 위해서는 pre-commit 파ì´ì¬ 패키지 (파ì´ì¬ 버전 3.7 ì´ìƒì´ 필요합니다)ê°€ 필요하고, 설치는 다ìŒê³¼ ê°™ì´ ì§„í–‰í•  수 있습니다. $ python3 -m pip install pre-commit 패키지 설치가 완료ë˜ì—ˆë‹¤ë©´, pre-commit hookì„ uftrace 소스코드 디렉토리 ì•ˆì— ì„¤ì¹˜í•  수 있습니다. $ pre-commit install pre-commit installed at .git/hooks/pre-commit 디렉토리 ì•ˆì— pre-commit 설치가 완료ë˜ì—ˆë‹¤ë©´, 새로운 commitì„ ìž‘ì„±í•  때마다 코드 작성 스타ì¼ì´ ìžë™ìœ¼ë¡œ ê²€ì‚¬ë  ê²ƒìž…ë‹ˆë‹¤. $ git commit -s ... clang-format.............................................................Failed - hook id: clang-format - files were modified by this hook 만약, ìž‘ì„±ëœ ì½”ë“œê°€ uftraceì˜ ì½”ë“œ 작성 스타ì¼ê³¼ ë§žì§€ 않는다면 clang-formatì´ ì½”ë”© 스타ì¼ì„ 검사한 결과가 Failed로 나타나고, [.clang-format](.clang-format)ì— ë¯¸ë¦¬ 설정해둔 코드 작성 스타ì¼ì— 맞추어 코드가 ìžë™ìœ¼ë¡œ ìˆ˜ì •ë  ê²ƒìž…ë‹ˆë‹¤. clang-format으로 코드가 수정ë˜ì—ˆë‹¤ë©´, `git add -u` 명령어를 실행한 ë’¤, commitì„ ë‹¤ì‹œ 작성해서 ìˆ˜ì •ëœ ì½”ë“œê°€ ë°˜ì˜ë  수 있ë„ë¡ í•´ì•¼í•©ë‹ˆë‹¤. 다ìŒì˜ 명령어로 pre-commitì„ ì‚¬ìš©í•œ 코드 작성 ìŠ¤íƒ€ì¼ ê²€ì‚¬ë„ ê°€ëŠ¥í•©ë‹ˆë‹¤. $ git add -u $ pre-commit run pre-commitì€ git stagingëœ ì½”ë“œë“¤ì— í•œí•´ì„œ 코드 작성 스타ì¼ì„ 검사하고, 검사 결과가 Failed로 나타나면 ìžë™ìœ¼ë¡œ 코드 스타ì¼ì„ 수정해ì¤ë‹ˆë‹¤. 패치 주제를 메시지 ì œëª©ì— í¬í•¨ì‹œí‚¤ê¸° ------------------------------------ uftraceê°€ í° ê·œëª¨ì˜ í”„ë¡œì íŠ¸ê°€ ì•„ë‹ì§€ë¼ë„, ìŒì (:) ì•žì— íŒ¨ì¹˜ì˜ ì£¼ì œë¥¼ 나타내는 단어를 ì ëŠ” ê²ƒì€ ë‹¤ë¥¸ 개발ìžë“¤ì´ 여러 ì£¼ì œì˜ íŒ¨ì¹˜ë¥¼ 쉽게 구분할 수 있게 해주는 ì¢‹ì€ ê·œì¹™ì´ë¼ê³  ìƒê°ë©ë‹ˆë‹¤. $ 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 |/ ... íŒ¨ì¹˜ì— ì„œëª…í•˜ê¸° --------------- sign-off (서명)ì€ íŒ¨ì¹˜ì— ëŒ€í•œ 설명 마지막 ë¶€ë¶„ì— í•œì¤„ë¡œ 패치를 ìžì‹ ì´ ì§ì ‘ 작성했고, 오픈소스 패치로 ë°°í¬í•  권리가 있다는 ì‚¬ì‹¤ì„ ì•Œë ¤ì£¼ëŠ” 것입니다. íŒ¨ì¹˜ì— sign-off (서명)ì„ í•˜ëŠ” ê·œì¹™ì€ [ë‹¤ìŒ í•­ëª©](https://developercertificate.org/)ë“¤ì— ë™ì˜í•˜ëŠ” 것으로 충분합니다: Developer's Certificate of Origin 1.1 본 프로ì íŠ¸ì˜ ê¸°ì—¬ìžë¡œì¨, 다ìŒì„ ì¦ëª…합니다: (a) 본 프로ì íŠ¸ì— ëŒ€í•œ ì „ì²´ í˜¹ì€ ì¼ë¶€ 기여는 본ì¸ì´ ì§ì ‘ 했으며, ê¸°ì—¬ìž ë³¸ì¸ì€ 파ì¼ì— 명시ë˜ì–´ 있는 오픈소스 ë¼ì´ì„¼ìŠ¤ì— ë”°ë¼ ê¸°ì—¬í•œ ë¶€ë¶„ì„ ì œì¶œí•  권리가 있습니다. (b) 본 프로ì íŠ¸ì— ëŒ€í•œ 기여는, ê¸°ì—¬ìž ë³¸ì¸ì´ 아는 한, ì ì ˆí•œ 오픈소스 ë¼ì´ì„¼ìŠ¤ê°€ ì ìš©ë˜ëŠ” ì´ì „ ìž‘ì—…ë“¤ì— ê¸°ë°˜í•˜ê³  있으며 다른 ë¼ì´ì„¼ìŠ¤ë¥¼ 사용하는 ê²ƒì´ í—ˆìš©ë˜ì§€ ì•Šì€ ì´ìƒ, 파ì¼ì— ëª…ì‹œëœ ë°”ì— ë”°ë¼, ê¸°ì—¬ìž ë³¸ì¸ì€ ë™ì¼í•œ 오픈소스 ë¼ì´ì„¼ìŠ¤ì— ê¸°ë°˜í•˜ì—¬ ì´ì „ ìž‘ì—…ë“¤ì„ ì „ì²´ ë˜ëŠ” ì¼ë¶€ 수정하여 제출할 권리가 있습니다. (c) 본 프로ì íŠ¸ì— ëŒ€í•œ 기여는 ìƒê¸° (a), (b), (c) 중 í•˜ë‚˜ì˜ í•­ëª©ì— í•´ë‹¹ë˜ëŠ” ì´ë¡œë¶€í„° ì§ì ‘ 제공받았으며, 본ì¸ì€ ì œê³µë°›ì€ ìž‘ì—…ë¬¼ì„ ìˆ˜ì •í•˜ì§€ 않았습니다. (d) 본 프로ì íЏ ë° ê¸°ì—¬ìžê°€ 프로ì íŠ¸ì— ê¸°ì—¬í•œ ë¶€ë¶„ì€ ê³µê°œë˜ëŠ” ë°”ì´ë©°, 프로ì íŠ¸ì— ê¸°ì—¬í•œ ë¶€ë¶„ì„ ì œì¶œí•  때 함께 ì œê³µí–ˆë˜ ê°œì¸ì •보와 서명(sign-off)ê³¼ ê°™ì€ ê¸°ë¡ë“¤ì€ 무기한 유지ë˜ì–´ 본 프로ì íŠ¸ë‚˜ 프로ì íŠ¸ì™€ ê´€ë ¨ëœ ì˜¤í”ˆì†ŒìŠ¤ ë¼ì´ì„¼ìŠ¤ì™€ 함께 ì§€ì†ì ìœ¼ë¡œ ìž¬ë°°í¬ ë  ìˆ˜ 있ìŒì„ ì´í•´í•˜ê³  ë™ì˜í•©ë‹ˆë‹¤. 본ì¸ì´ 위 항목 (a), (b), (c) 중 í•˜ë‚˜ì— í•´ë‹¹ë˜ê³  (d)ì— ë™ì˜í•œë‹¤ë©´, 패치 설명 마지막 ë¶€ë¶„ì— ë³¸ì¸ì˜ 실명으로 다ìŒê³¼ ê°™ì´ í•œ ì¤„ì„ ì¶”ê°€í•´ì£¼ì‹œë©´ ë©ë‹ˆë‹¤. (가명ì´ë‚˜ ìµëª…으로 프로ì íŠ¸ì— ê¸°ì—¬í•˜ëŠ” ê²ƒì€ í—ˆìš©í•˜ì§€ 않습니다.) Signed-off-by: Random J Developer uftrace-0.15.2/doc/ko/Makefile000066400000000000000000000016441455365734300160720ustar00rootroot00000000000000all: 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.15.2/doc/ko/README.md000066400000000000000000000447611455365734300157200ustar00rootroot00000000000000[![Build Status](https://app.travis-ci.com/namhyung/uftrace.svg?branch=master)](https://app.travis-ci.com/namhyung/uftrace) [![Coverity scan](https://scan.coverity.com/projects/12421/badge.svg)](https://scan.coverity.com/projects/namhyung-uftrace) uftrace ======= uftrace 는 C, C++, Rust, Python 으로 ìž‘ì„±ëœ í”„ë¡œê·¸ëž¨ì˜ ì‹¤í–‰ íë¦„ì„ ì¶”ì (trace)하며 기ë¡í•˜ê³  ë¶„ì„하는 ë„구ì´ë‹¤. uftrace는 ê° í•¨ìˆ˜ì˜ ì‹œìž‘ê³¼ ëì„ í›„í‚¹í•˜ì—¬ 타임스탬프 ë° í•¨ìˆ˜ ì¸ìž, 반환값 ë“±ì„ ê¸°ë¡í•œë‹¤. uftrace는 유저와 ì»¤ë„ í•¨ìˆ˜ ë¿ ì•„ë‹ˆë¼ ë¼ì´ë¸ŒëŸ¬ë¦¬ 함수 ë° ì‹œìŠ¤í…œ ì´ë²¤íŠ¸ë¥¼ ì¶”ì í•˜ì—¬ 단ì¼í•œ 시간 í름 ìƒì—서 í†µí•©ëœ ì‹¤í–‰ 과정으로 보여줄 수 있다. 초기ì—, uftrace는 컴파ì¼ëŸ¬ ì§€ì›ì„ ì´ìš©í•œ 함수 ì¶”ì ë§Œì„ 제공해 주었다. 그러나, 현재는 ê° í•¨ìˆ˜ í”„ë¡¤ë¡œê·¸ì˜ ëª…ë ¹ì–´ë¥¼ ë¶„ì„하고 ë™ì ì´ê³  ì„ íƒì ìœ¼ë¡œ ëª…ë ¹ì–´ë“¤ì„ íŒ¨ì¹˜í•¨ìœ¼ë¡œì¨, ìž¬ì»´íŒŒì¼ ì—†ì´ í•¨ìˆ˜ í˜¸ì¶œì„ ì¶”ì í•  수 있다. 사용ìžëŠ” Python/Juajit API를 ì´ìš©í•´ í•¨ìˆ˜ì˜ ì‹œìž‘ê³¼ ì¢…ë£Œì— ëŒ€í•œ 스í¬ë¦½íŠ¸ë¥¼ 작성하고, 실행해 특정 ìš©ë„ì— ë§žëŠ” 커스텀 ë„구를 만들 수 있다. uftrace는 ì¶”ì  ë°ì´í„°ì˜ ì–‘ì„ ì¤„ì´ê¸° 위해 다양한 í•„í„° ê¸°ëŠ¥ì„ ì œê³µí•˜ë©°, Chrome trace viewer와 Flame graph, í˜¹ì€ graphviz와 mermaid와 호환ë˜ëŠ” 호출 그래프 다ì´ì–´ê·¸ëž¨ì„ 통한 시ê°í™”를 제공해 실행 íë¦„ì„ í•œ 눈으로 ë³¼ 수 있다. ì´ ë„구는 Linux 커ë„ì˜ ftrace 프레임워í¬ì— í¬ê²Œ ì˜ê°ì„ 받았고, uftrace ì´ë¦„ì˜ ëœ»ì€ user와 ftrace 단어를 í•©ì³ ë§Œë“¤ì—ˆë‹¤. ì´ëŸ¬í•œ í”„ë¡œê·¸ëž¨ë“¤ì„ ê¸°ë¡í•  수 있다: - 유저 스페ì´ìФ C/C++/Rust 함수들 (런타임ì—서 ë™ì ìœ¼ë¡œ 패치가 가능하거나, 코드가 `-pg`, `-finstrument-functions`로 컴파ì¼ë˜ì—ˆê±°ë‚˜, ì„ íƒì  NOP 패치를 위해 `-fpatchable-function-entry=N`로 컴파ì¼ëœ 경우) - C/C++/Rust ë¼ì´ë¸ŒëŸ¬ë¦¬ 함수 (PLT hooking ì´ìš©) - Python 함수 (Pythonì˜ ì¶”ì /프로필 기반 ì´ìš©) - ì»¤ë„ í•¨ìˆ˜ (리눅스 커ë„ì˜ ftrace í”„ë ˆìž„ì›Œí¬ ì´ìš©) - ì»¤ë„ ì¶”ì  ì´ë²¤íЏ (리눅스 커ë„ì˜ ì´ë²¤íЏ 트레ì´ì‹± í”„ë ˆìž„ì›Œí¬ ì´ìš©) - 작업 ìƒì„±, 종료, ìŠ¤ì¼€ì¤„ë§ ì´ë²¤íЏ (ë¦¬ëˆ…ìŠ¤ì˜ perf_event ì´ìš©) - 목표 ë°”ì´ë„ˆë¦¬ í˜¹ì€ ë¼ì´ë¸ŒëŸ¬ë¦¬ì˜ 유저 스페ì´ìФ ì´ë²¤íЏ (SystemTap SDI ABI ì´ìš©) - 주어진 í•¨ìˆ˜ì˜ PMU ì¹´ìš´í„° ê°’ (ë¦¬ëˆ…ìŠ¤ì˜ perf_event ì´ìš©) 기ë¡ëœ ë°ì´í„°ë¥¼ ì´ìš©í•´, uftrace는 다ìŒê³¼ ê°™ì€ ê¸°ëŠ¥ì„ ì œê³µí•œë‹¤: - 중첩 함수 호출 그래프를 시ê°í™”í•´ 준다. - libc 함수 프로토타입과 DWARF 디버그 정보를 ì´ìš©í•´ 함수 ì¸ìžì™€ 반환 ê°’ì„ ì‹¬ë³¼ë¡œ 표시해 준다. - ì¶”ì  ë°ì´í„° ì–‘ì„ ì¤„ì´ê¸° 위해 í•„í„° ê¸°ëŠ¥ì„ ì ìš©í•œë‹¤ (record ë° replay 시 ëª¨ë‘ ê°€ëŠ¥) - ì¶”ì  ë°ì´í„°ì—서 메타ë°ì´í„°ë¥¼ 추출한다. (e.g. ì¶”ì ì´ ìˆ˜í–‰ëœ ì‹œìŠ¤í…œì˜ ì •ë³´) - ì¶”ì ëœ 프로그램 ë° ë¼ì´ë¸ŒëŸ¬ë¦¬ í•¨ìˆ˜ì˜ ì‹¬ë³¼ í…Œì´ë¸” ë° ë©”ëª¨ë¦¬ ë§µì„ ìƒì„±í•œë‹¤. - ì¶”ì  ë°ì´í„°ë¡œë¶€í„° í”„ë¡œê·¸ëž¨ì˜ ìž‘ì—… 관계 트리(부모/ìžì‹ 관계)를 ìƒì„±í•œë‹¤. uftrace는 프로그램 실행 ë° ì„±ëŠ¥ ë¶„ì„ì„ ìœ„í•´ 함수 호출 기간별 í•„í„°ë§ê³¼ ê°™ì€ ë§Žì€ ëª…ë ¹ ë° í•„í„°ë¥¼ ì§€ì›í•œë‹¤. ![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) * 발표 ì˜ìƒ: https://youtu.be/LNav5qvyK7I 기능 ======== uftrace는 ê° ì‹¤í–‰ë˜ëŠ” í•¨ìˆ˜ë“¤ì„ ì¶”ì í•˜ê³  ì†Œìš”ëœ ì‹œê°„ì„ ë³´ì—¬ì¤€ë‹¤. ì¼ë°˜ì ìœ¼ë¡œ, ì´ëŸ° ê³¼ì •ì´ ê°€ëŠ¥í•˜ê¸° 위해선, í”„ë¡œê·¸ëž¨ì´ `-pg` í˜¹ì€ `-fpatchable-function-entry=5` (aarch64 환경ì—ì„  `=2` ë„ ì¶©ë¶„í•¨)로 컴파ì¼ë˜ì–´ì•¼ 한다. ì „ì²´ ë™ì  ì¶”ì  ê¸°ëŠ¥ (`-P.`|`--patch=.`)ì„ ì´ìš©í•œë‹¤ë©´ (디버깅 ì •ë³´ê°€ 있거나 심볼 ì •ë³´ê°€ ë³„ë„ íŒŒì¼ì— 존재하는 경우) uftrace는 모든 실행 파ì¼ì„ ì¶”ì  ê°€ëŠ¥í•˜ë‹¤. uftrace는 ë¼ì´ë¸ŒëŸ¬ë¦¬ ì½œì„ ì¶”ì í•˜ê¸° 위해 주어진 실행 파ì¼ì˜ PLTì— í›…ì„ ê±¸ê³ , (`-l`|`--nest-libcall`)ì˜µì…˜ì„ ì´ìš©í•˜ë©´ 공유 ë¼ì´ë¸ŒëŸ¬ë¦¬ì˜ 프로시저 ì—°ê²° í…Œì´ë¸”(PLT)ì—ë„ í›…ì„ ê±¸ê²Œ ëœë‹¤. 깊ì´ëŠ” `-D`ì„ ì´ìš©í•´ 제한할 수 있다. 1ì¼ ê²½ìš° 첫 단계만 ì¶”ì í•œë‹¤. (`-a`|`--auto-args`) ì˜µì…˜ì„ ì´ìš©í•˜ë©´, uftrace는 ìžë™ìœ¼ë¡œ 알려진 í•¨ìˆ˜ì— ëŒ€í•´ ì¸ìžì™€ 반환 ê°’ì„ ê¸°ë¡í•œë‹¤. 추가ì ì¸ 디버그 ì •ë³´ê°€ 없다면, ì´ê²ƒì€ 표준 (C 언어 í˜¹ì€ ì‹œìŠ¤í…œ) ë¼ì´ë¸ŒëŸ¬ë¦¬ì˜ API 함수를 í¬í•¨í•œë‹¤. ì´ëŠ” `-P.` í˜¹ì€ `-l` 옵션과 함께 사용할 수 있다. 예를 들어, `-la` ì˜µì…˜ì€ ë””ë²„ê¹… ì •ë³´ê°€ 없는 파ì¼ë„ ì¶”ì ì´ 가능하며, ì¤‘ì²©ëœ í•¨ìˆ˜ 호출 ì¶”ì ì„ ì§€ì›í•œë‹¤. 추가로, `-a` ì˜µì…˜ì€ `--srcline`ê³¼ ë™ì¼í•˜ë©° ì†ŒìŠ¤ì˜ ë¼ì¸ 위치 정보를 기ë¡í•œë‹¤. 그리고 ì´ëŠ” `uftrace replay --srcline` í˜¹ì€ `uftrace tui`를 통해 ë³¼ 수 있다. 사용ìžëŠ” 바로 해당 소스 코드를 ì—디터로 ì—´ì–´ë³¼ 수 있다. 참고 : https://uftrace.github.io/slide/#120 í”„ë¡œê·¸ëž¨ì˜ ë””ë²„ê·¸ ì •ë³´ (`gcc -g`)ê°€ 존재한다면, `--auto-args`는 컴파ì¼ëœ ì‚¬ìš©ìž í”„ë¡œê·¸ëž¨ ë‚´ë¶€ì˜ í•¨ìˆ˜ì—ì„œë„ ìž‘ë™í•œë‹¤. ì¸ìž ì •ë³´ê°€ 존재하지 않는 경우, (`-A udev_new@arg1/s`)와 ê°™ì´ ì¸ìž 정보를 명령줄ì´ë‚˜ 옵션 파ì¼ì— 전달할 수 있다. 예: ```py $ uftrace record -la -A udev_new@arg1/s lsusb >/dev/null $ uftrace replay -f+module í˜¹ì€ ê°„ë‹¨ížˆ: $ uftrace -la -A udev_new@arg1/s -f+module lsusb # -f+module adds the module name # DURATION TID MODULE NAME FUNCTION 306.339 us [ 23561] lsusb | setlocale(LC_TYPE, "") = "en_US.UTF-8"; 1.163 us [ 23561] lsusb | getopt_long(1, 0x7fff7175f6a8, "D:vtP:p:s:d:Vh") = -1; [ 23561] lsusb | udev_new("POSIXLY_CORRECT") { 0.406 us [ 23561] libudev.so.1.7.2 | malloc(16) = 0x55e07277a7b0; 2.620 us [ 23561] lsusb | } /* udev_new */ [ 23561] lsusb | udev_hwdb_new() { 0.427 us [ 23561] libudev.so.1.7.2 | calloc(1, 200) = 0x55e07277a7d0; 5.829 us [ 23561] libudev.so.1.7.2 | fopen64("/etc/systemd/hwdb/hwdb.bin", "re") = 0; ``` 추가ì ìœ¼ë¡œ, uftrace는 함수 단계ì—서 구체ì ì¸ 실행 íë¦„ì„ í‘œí˜„í•  수 있으며, ì–´ë–¤ 함수가 가장 긴 수행 ì‹œê°„ì„ ê°€ì§€ëŠ”ì§€ 표현할 수 있다. 그리고 실행 í™˜ê²½ì˜ ì •ë³´ë¥¼ 보여줄 ìˆ˜ë„ ìžˆë‹¤. ë‹¹ì‹ ì€ í•„í„°ë¥¼ ì´ìš©í•´ 특정 함수를 í¬í•¨í•˜ê±°ë‚˜ 제외할 수 있다. 추가로, 함수 ì¸ìžë‚˜ 반환 ê°’ì€ ì €ìž¥í•œ 후 다ìŒì— 출력할 수 있다. uftrace는 멀티프로세스와 멀티스레드 애플리케ì´ì…˜ì„ ì§€ì›í•œë‹¤. root ê¶Œí•œì´ ìžˆê³  `CONFIG_FUNCTION_GRAPH_TRACER=y` ì„¤ì •ì´ ì¼œì§„ ìƒíƒœë¡œ 커ë„ì´ ë¹Œë“œë˜ì–´ 있다면, ì»¤ë„ í•¨ìˆ˜ ë˜í•œ ì¶”ì ì´ 가능하다. uftrace 빌드 ë° ì„¤ì¹˜ 방법 ================================ 리눅스 ë°°í¬íŒì—서, [misc/install-deps.sh](../../misc/install-deps.sh) 스í¬ë¦½íŠ¸ëŠ” uftrace를 빌드하는 ë° í•„ìš”í•œ 소프트웨어를 설치해 준다. ì´ëŠ” 고급 ê¸°ëŠ¥ë“¤ì„ ìœ„í•œ 것ì´ë©° 반드시 설치할 필요는 없지만, 함께 설치하기를 ì ê·¹ 권장한다. $ sudo misc/install-deps.sh 요구ë˜ëŠ” 소프트웨어를 설치한 ë’¤, 다ìŒê³¼ ê°™ì´ ë¹Œë“œ ë° ì„¤ì¹˜ê°€ 가능하다: $ ./configure $ make $ sudo make install ë” ìžì„¸í•œ 설치방법ì€, [INSTALL.md](../../INSTALL.md) 파ì¼ì„ 확ì¸í•˜ë©´ ëœë‹¤. uftrace 사용 방법 ================== uftrace 명령어는 다ìŒê³¼ ê°™ì€ ëª…ë ¹ì–´ë¥¼ 제공한다. * [`record`](./uftrace-record.md) : í”„ë¡œê·¸ëž¨ì„ ì‹¤í–‰í•˜ë©° ì¶”ì  ë°ì´í„°ë¥¼ 저장한다. * [`replay`](./uftrace-replay.md) : ì¶”ì  ë°ì´í„° ë‚´ì˜ í”„ë¡œê·¸ëž¨ ì‹¤í–‰ì„ ë³´ì—¬ì¤€ë‹¤. * [`report`](./uftrace-report.md) : ì¶”ì  ë°ì´í„° ë‚´ì˜ ìˆ˜í–‰ 통계를 보여준다. * [`live` ](./uftrace-live.md) : record 와 replay 를 차례로 수행한다. (기본값) * [`info` ](./uftrace-info.md) : ì¶”ì  ë°ì´í„° ë‚´ì˜ ì‹œìŠ¤í…œ ë° í”„ë¡œê·¸ëž¨ 정보를 보여준다. * [`dump` ](./uftrace-dump.md) : low-levelì˜ ì¶”ì  ë°ì´í„°ë¥¼ 보여준다. * [`recv` ](./uftrace-recv.md) : 네트워í¬ë¡œë¶€í„° ì¶”ì í•œ ë°ì´í„°ë¥¼ 저장한다. * [`graph` ](./uftrace-graph.md) : ì¶”ì  ë°ì´í„° ë‚´ì˜ í•¨ìˆ˜ 호출 그래프를 보여준다. * [`script`](./uftrace-script.md) : ì €ìž¥ëœ ì¶”ì  ë°ì´í„°ì˜ 스í¬ë¦½íŠ¸ë¥¼ 실행한다. * [`tui` ](./uftrace-tui.md) : graph와 report를 위한 í…스트 기반 ì¸í„°íŽ˜ì´ìŠ¤ë¥¼ 보여준다. [사용 가능한 명령어와 옵션](./uftrace.md)ì„ ë³´ê¸° 위해 `-h` í˜¹ì€ `--help` ì˜µì…˜ì„ ì‚¬ìš©í•  수 있다. $ uftrace uftrace -- function (graph) tracer for userspace usage: uftrace [COMMAND] [OPTION...] [] COMMAND: record Run a program and saves the trace data replay Show program execution in the trace data report Show performance statistics in the trace data live Do record and replay in a row (default) info Show system and program info in the trace data dump Show low-level trace data recv Save the trace data from network graph Show function call graph in the trace data script Run a script for recorded trace data tui Show text user interface for graph and report Try `uftrace --help' or `man uftrace [COMMAND]' for more information. ë§Œì¼ í•˜ìœ„ 명령어를 ìƒëžµí•œë‹¤ë©´, 기본ì ìœ¼ë¡œ record 와 replay 를 차례로 ì ìš©í•œ 것과 ë™ì¼í•œ `live` 명령어를 수행한다. (하지만 ì¶”ì  ì •ë³´ë¥¼ 파ì¼ë¡œ 저장하진 않는다) record 명령어로 기ë¡í•˜ê¸° 위해선, 실행 파ì¼ì´ `-pg` (í˜¹ì€ `-finstrument-functions`) ì˜µì…˜ì„ ì´ìš©í•´ 컴파ì¼ë˜ì–´ 프로파ì¼ë§ 코드 (mcount í˜¹ì€ __cyg_profile_func_enter/exit로 불리는)ê°€ ìƒì„±ë˜ì–´ì•¼ 한다. 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 3, Python 2.7 ê³¼ Lua 5.1 ì´ë‹¤. `tui` 명령어는 ncurses 를 ì´ìš©í•œ í…스트 기반 대화형 ì‚¬ìš©ìž ì¸í„°íŽ˜ì´ìŠ¤ë¥¼ 위한 명령어ì´ë‹¤. 현재 `graph`, `report`, `info` ëª…ë ¹ì–´ì˜ ê¸°ë³¸ì ì¸ ê¸°ëŠ¥ì„ ì œê³µí•œë‹¤. 제약사항 =========== - 리눅스와 안드로ì´ë“œì—서 실행ë˜ëŠ” C/C++/Rust/Python 애플리케ì´ì…˜ì— 대해서만 사용 가능하다. - ì´ë¯¸ 실행 ì¤‘ì¸ í”„ë¡œì„¸ìŠ¤ì˜ ì¶”ì ì€ ì•„ì§ *불가능*하다. - ì „ì²´ ì‹œìŠ¤í…œì— ëŒ€í•œ 통합 ë¶„ì„ì€ *불가능*하다. - 현재는 x86_64, AArch64 ë§Œ ì§€ì›í•œë‹¤. x86 (32-bit), ARM (v6, v7) 환경ì—ì„œë„ ìž‘ë™í•˜ì§€ë§Œ, ë™ì  ì¶”ì ì´ë‚˜ ìžë™ ì¸ìž 가져오기와 ê°™ì€ ì¼ë¶€ ê¸°ëŠ¥ì€ ìž˜ ìž‘ë™í•˜ì§€ ì•Šì„ ìˆ˜ 있다. ë¼ì´ì„ ìФ ======= uftrace 는 GPL v2. ë¼ì´ì„ ìФ í•˜ì— ë°°í¬ë˜ë©° ìžì„¸í•œ ë‚´ìš©ì€ [COPYING](../../COPYING) 파ì¼ì—서 확ì¸í•  수 있다. uftrace-0.15.2/doc/ko/uftrace-dump.md000066400000000000000000000210451455365734300173450ustar00rootroot00000000000000% 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 형ì‹ì˜ ê²°ê³¼ë¬¼ì„ í‘œì‹œí•œë‹¤. \--mermaid : 그래프를 오픈소스 mermaidì˜ í”Œë¡œìš°ì°¨íŠ¸ 다ì´ì–´ 그램으로 표시한다. ì¶œë ¥ê°’ì€ ë¸Œë¼ìš°ì €ì—서 ë Œë”ë§ë  수 있다. \--debug : 16진수 ë°ì´í„°ë¥¼ 보여준다. \--sample-time=*시간* : --flame-graph ì˜µì…˜ì˜ ê²°ê³¼ë¬¼ì„ ìƒì„±í•  때 ìƒ˜í”Œë§ ì‹œê°„ì„ ì ìš©í•œë‹¤. 기본으로는 ê° í•¨ìˆ˜ì˜ í˜¸ì¶œ 수가 ì ìš©ëœë‹¤. ì´ ì˜µì…˜ì´ ì‚¬ìš©ë˜ë©´ 주어진 단위로 실행 ì‹œê°„ì„ ê³„ì‚°í•˜ì—¬ 샘플ë§í•œë‹¤. 만약 주어진 ìƒ˜í”Œë§ ì‹œê°„ë³´ë‹¤ ì ê²Œ ìˆ˜í–‰ëœ í•¨ìˆ˜ëŠ” 결과물 ì—서 제외ë˜ì§€ë§Œ, ë” ê¸¸ê²Œ ìˆ˜í–‰ëœ í•¨ìˆ˜ëŠ” 표시ëœë‹¤. \--no-args : 함수 ì¸ìžë¥¼ 표시하지 않고 ê°’ì„ ë°˜í™˜í•œë‹¤. 공통 옵션 ========= -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' 트리거가 ì ìš©ëœ 경우, ê·¸ 함수는 실행 시간과 ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. -Z *SIZE*, \--size-filter=*SIZE* : SIZE ë°”ì´íŠ¸ë³´ë‹¤ ìž‘ì€ í•¨ìˆ˜ë“¤ì„ í‘œì‹œí•˜ì§€ 않게 한다. 만약 ì–´ë–¤ 함수가 명시ì ìœ¼ë¡œ 'trace' 트리거가 ì ìš©ëœ 경우, ê·¸ 함수는 함수 í¬ê¸°ì™€ ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. -L *LOCATION*, \--loc-filter=*LOCATION* : 사용할 í•„í„°ì˜ ê²½ë¡œë¥¼ 지정한다. ì´ ì˜µì…˜ì€ 1번ì´ìƒ 사용할 수 있다. \--no-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì€ í‘œì‹œí•˜ì§€ 않게 한다. \--no-event : ì´ë²¤íŠ¸ë“¤ì„ í‘œì‹œí•˜ì§€ 않게 한다. `--no-sched` ì˜µì…˜ì„ ë‚´í¬í•œë‹¤. \--no-sched : 스케줄 ì´ë²¤íŠ¸ë¥¼ 표시하지 않게 한다. \--match=*TYPE* : 타입(TYPE)으로 ì¼ì¹˜í•˜ëŠ” íŒ¨í„´ì„ ë³´ì—¬ì¤€ë‹¤. 가능한 형태는 `regex`와 `glob`ì´ë‹¤. 기본 ì„¤ì •ì€ `regex`ì´ë‹¤. \--with-syms=*DIR* : DIR ë””ë ‰í† ë¦¬ì˜ .sym 파ì¼ì—서 심볼(symbol) ë°ì´í„°ë¥¼ ì½ëŠ”ë‹¤. ì´ëŠ” 심볼(symbol) ë°ì´í„°ê°€ ì œê±°ëœ ë°”ì´ë„ˆë¦¬ 파ì¼ì„ ë‹¤ë£¨ëŠ”ë° ìœ ìš©í•˜ë‹¤. ë°”ì´ë„ˆë¦¬ íŒŒì¼ ì´ë¦„ì€ ì €ìž¥í•  때와 사용할 때 ë™ì¼í•´ì•¼ 한다. 공통 ë¶„ì„ ì˜µì…˜ ======================= -H *FUNC*, \--hide=*FUNC* : 주어진 FUNC í•¨ìˆ˜ë“¤ì„ ì¶œë ¥ 대ìƒì—서 제외할 수 있다. ì´ëŠ” ì„ íƒëœ í•¨ìˆ˜ì˜ ìžì‹ í•¨ìˆ˜ë“¤ì— ëŒ€í•´ì„œëŠ” ì˜í–¥ì„ 주지 않으며 단지 주어진 함수들만 숨기는 ê¸°ëŠ¥ì„ í•˜ê²Œ ëœë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. \--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.15.2/doc/ko/uftrace-graph.md000066400000000000000000000373511455365734300175100ustar00rootroot00000000000000% 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* 를 참고할 수 있다. \--task : ì¼ë°˜ì ì¸ 함수 그래프 대신 íƒœìŠ¤í¬ ê·¸ëž˜í”„ë¥¼ 출력한다. ì¶œë ¥ëœ ê° ë…¸ë“œë“¤ì€ í”„ë¡œì„¸ìŠ¤ í˜¹ì€ (ì´ˆë¡ìƒ‰ìœ¼ë¡œ 표기ëœ)스레드를 보여준다. \--srcline : 가능한 ê° í•¨ìˆ˜ë“¤ì˜ ì†ŒìŠ¤ 줄번호를 표시한다. \--format=*TYPE* : 형ì‹í™”ëœ ì¶œë ¥ì„ ë³´ì—¬ì¤€ë‹¤. 현재는 'normal' ê³¼ 'html' 형ì‹ì´ ì§€ì›ëœë‹¤. 공통 옵션 ========= -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' 트리거가 ì ìš©ëœ 경우, ê·¸ 함수는 실행 시간과 ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. -Z *SIZE*, \--size-filter=*SIZE* : SIZE ë°”ì´íŠ¸ë³´ë‹¤ ìž‘ì€ í•¨ìˆ˜ë“¤ì„ í‘œì‹œí•˜ì§€ 않게 한다. 만약 ì–´ë–¤ 함수가 명시ì ìœ¼ë¡œ 'trace' 트리거가 ì ìš©ëœ 경우, ê·¸ 함수는 함수 í¬ê¸°ì™€ ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. -L *LOCATION*, \--loc-filter=*LOCATION* : 사용할 í•„í„°ì˜ ê²½ë¡œë¥¼ 지정한다. ì´ ì˜µì…˜ì€ 1번ì´ìƒ 사용할 수 있다. \--no-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì€ í‘œì‹œí•˜ì§€ 않게 한다. \--no-event : ì´ë²¤íŠ¸ë“¤ì„ í‘œì‹œí•˜ì§€ 않게 한다. `--no-sched` ì˜µì…˜ì„ ë‚´í¬í•œë‹¤. \--no-sched : 스케줄 ì´ë²¤íŠ¸ë¥¼ 표시하지 않게 한다. \--no-sched-preempt : ì„ ì  ìŠ¤ì¼€ì¤„ ì´ë²¤íŠ¸ëŠ” 표시하지 않게 하나 ì¼ë°˜(대기) 스케쥴 ì´ë²¤íŠ¸ëŠ” 그대로 표시한다. \--match=*TYPE* : 타입(TYPE)으로 ì¼ì¹˜í•˜ëŠ” íŒ¨í„´ì„ ë³´ì—¬ì¤€ë‹¤. 가능한 형태는 `regex`와 `glob`ì´ë‹¤. 기본 ì„¤ì •ì€ `regex`ì´ë‹¤. \--with-syms=*DIR* : DIR ë””ë ‰í† ë¦¬ì˜ .sym 파ì¼ì—서 심볼(symbol) ë°ì´í„°ë¥¼ ì½ëŠ”ë‹¤. ì´ëŠ” 심볼(symbol) ë°ì´í„°ê°€ ì œê±°ëœ ë°”ì´ë„ˆë¦¬ 파ì¼ì„ ë‹¤ë£¨ëŠ”ë° ìœ ìš©í•˜ë‹¤. ë°”ì´ë„ˆë¦¬ íŒŒì¼ ì´ë¦„ì€ ì €ìž¥í•  때와 사용할 때 ë™ì¼í•´ì•¼ 한다. 공통 ë¶„ì„ ì˜µì…˜ ======================= -H *FUNC*, \--hide=*FUNC* : 주어진 FUNC í•¨ìˆ˜ë“¤ì„ ì¶œë ¥ 대ìƒì—서 제외할 수 있다. ì´ëŠ” ì„ íƒëœ í•¨ìˆ˜ì˜ ìžì‹ í•¨ìˆ˜ë“¤ì— ëŒ€í•´ì„œëŠ” ì˜í–¥ì„ 주지 않으며 단지 주어진 함수들만 숨기는 ê¸°ëŠ¥ì„ í•˜ê²Œ ëœë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. \--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 ìœ„ì˜ ê²°ê³¼ì™€ ê°™ì´ ìŠ¤ë ˆë“œì˜ ë“¤ì—¬ì“°ê¸° 깊ì´ëŠ” 프로세스와는 다르게 표현ëœë‹¤. `graph` ëª…ë ¹ì„ `--srcline` 옵션과 함께 실행한다면 아래와 ê°™ì´ í˜¸ì¶œ í•¨ìˆ˜ì˜ ì†ŒìŠ¤ 줄번호를 보여준다. $ uftrace record --srcline t-abc $ uftrace graph --srcline # Function Call Graph for 't-abc' (session: 60195bac953d8736) ========== FUNCTION CALL GRAPH ========== # TOTAL TIME FUNCTION [SOURCE] 8.909 us : (1) t-abc 1.260 us : +-(1) __monstartup : | 0.179 us : +-(1) __cxa_atexit : | 7.470 us : +-(1) main [tests/s-abc.c:26] 5.522 us : (1) a [tests/s-abc.c:11] 4.912 us : (1) b [tests/s-abc.c:16] 4.176 us : (1) c [tests/s-abc.c:21] 0.794 us : (1) getpid 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.15.2/doc/ko/uftrace-info.md000066400000000000000000000062061455365734300173350ustar00rootroot00000000000000% 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.15.2/doc/ko/uftrace-live.md000066400000000000000000001216401455365734300173410ustar00rootroot00000000000000% 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* 를 참고한다. -Z *SIZE*, \--size-filter=*SIZE* : SIZE ë°”ì´íŠ¸ë³´ë‹¤ ìž‘ì€ í•¨ìˆ˜ë“¤ì„ í‘œì‹œí•˜ì§€ 않게 한다. 만약 ì–´ë–¤ 함수가 명시ì ìœ¼ë¡œ 'trace' 트리거가 ì ìš©ëœ 경우, ê·¸ 함수는 함수 í¬ê¸°ì™€ ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* ì„ ì°¸ê³ í•œë‹¤. -L *LOCATION*, \--loc-filter=*LOCATION* : 사용할 í•„í„°ì˜ ê²½ë¡œë¥¼ 지정한다. ì´ ì˜µì…˜ì€ 1번ì´ìƒ 사용할 수 있다. \--no-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì€ í‘œì‹œí•˜ì§€ 않게 한다. \--no-event : (기본 옵션으로 켜져있는) ì´ë²¤íЏ ë ˆì½”ë”©ì„ í•˜ì§€ 않는다. `--event` 를 통한 명시ì ì¸ ì´ë²¤íЏ ê¸°ë¡ ë°©ì‹ì—는 ì˜í–¥ì„ 주지 않는다. `--no-sched` ì˜µì…˜ì„ ë‚´í¬í•œë‹¤. \--no-sched : (기본 옵션으로 켜져있는) 스케줄 ì´ë²¤íЏ ë ˆì½”ë”©ì„ í•˜ì§€ 않는다. \--match=*TYPE* : 타입(TYPE)으로 ì¼ì¹˜í•˜ëŠ” íŒ¨í„´ì„ ë³´ì—¬ì¤€ë‹¤. 가능한 형태는 `regex`와 `glob`ì´ë‹¤. 기본 ì„¤ì •ì€ `regex`ì´ë‹¤. \--disable : uftrace 를 시작할때 ë°ì´í„°ë¥¼ 기ë¡í•˜ì§€ 않고 시작한다. ì´ê²ƒì€ `trace_on` 트리거와 함께 사용ë˜ì—ˆì„ 때만 ì˜ë¯¸ë¥¼ 가진다. \--with-syms=*DIR* : DIR ë””ë ‰í† ë¦¬ì˜ .sym 파ì¼ì—서 심볼(symbol) ë°ì´í„°ë¥¼ ì½ëŠ”ë‹¤. ì´ëŠ” 심볼(symbol) ë°ì´í„°ê°€ ì œê±°ëœ ë°”ì´ë„ˆë¦¬ 파ì¼ì„ ë‹¤ë£¨ëŠ”ë° ìœ ìš©í•˜ë‹¤. ë°”ì´ë„ˆë¦¬ íŒŒì¼ ì´ë¦„ì€ ì €ìž¥í•  때와 사용할 때 ë™ì¼í•´ì•¼ 한다. 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 ì˜µì…˜ì´ ìžë™ìœ¼ë¡œ ì ìš©ëœë‹¤. \--clock=*CLOCK* : 타임스탬프를 ì½ëŠ” í´ëŸ­ 소스를 설정한다. *CLOCK* ì€ 'mono', 'mono_raw', 'boot' ì¤‘ì˜ í•˜ë‚˜ë¡œ 설정 가능하고, 기본 ì„¤ì •ì€ 'mono' ì´ë‹¤. \--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) 스타ì¼ë¡œ ì‹¤í–‰ì‹œê°„ì„ ì¶œë ¥í•œë‹¤. -e, \--estimate-return : ê° í•¨ìˆ˜ì˜ ì§„ìž… ë°ì´í„°ë§Œì„ 기ë¡í•œë‹¤. ì´ ì˜µì…˜ì€ ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì´ ìŠ¤íƒì„ 다룰 경우 유용하게 ì‚¬ìš©ë  ìˆ˜ 있다. ì¼ë°˜ì ìœ¼ë¡œ uftrace는 í•¨ìˆ˜ì˜ ë°˜í™˜ê°’ì„ í›„í‚¹í•˜ê¸° 위해 작업 대ìƒì˜ 실행 ìŠ¤íƒ í”„ë ˆìž„ì„ ìˆ˜ì •í•œë‹¤. 하지만 ì´ëŠ” 때때로 문제를 ë°œìƒì‹œí‚¤ê³  모든 경우를 다루기 어렵다. ì´ ì˜µì…˜ì€ uftraceê°€ 리턴 주소를 후킹하지 않ë„ë¡ í•˜ì—¬ ì´ëŸ¬í•œ 문제를 예방한다. ë°˜í™˜ëœ ì‹œê°„ì€ ì—°ì†ëœ ë‘ í•¨ìˆ˜ë“¤ì˜ ì‹¤í–‰ ì‹œê°„ì˜ ì ˆë°˜ìœ¼ë¡œ 예측ëœë‹¤. RECORD 설정 옵션 ===================== \--libmcount-path=*PATH* : limcount ë¼ì´ë¸ŒëŸ¬ë¦¬ë¥¼ *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 : 함수 ì´ë¦„ê³¼ 함께 ë¼ì´ë¸ŒëŸ¬ë¦¬ ì´ë¦„ì„ ì¶œë ¥í•œë‹¤. 공통 ë¶„ì„ ì˜µì…˜ ======================= -H *FUNC*, \--hide=*FUNC* : 주어진 FUNC í•¨ìˆ˜ë“¤ì„ ì¶œë ¥ 대ìƒì—서 제외할 수 있다. ì´ëŠ” ì„ íƒëœ í•¨ìˆ˜ì˜ ìžì‹ í•¨ìˆ˜ë“¤ì— ëŒ€í•´ì„œëŠ” ì˜í–¥ì„ 주지 않으며 단지 주어진 함수들만 숨기는 ê¸°ëŠ¥ì„ í•˜ê²Œ ëœë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. \--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 */ `b()` í•¨ìˆ˜ë§Œì„ ìˆ¨ê¸°ê³  ê·¸ì˜ í•˜ìœ„ í•¨ìˆ˜ë“¤ì€ ê·¸ëŒ€ë¡œ ë³´ê³  싶으면 `-H` ì˜µì…˜ì„ ì‚¬ìš©í•  수 있다. $ uftrace -H b ./abc # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { [ 1234] | a() { 3.880 us [ 1234] | c(); 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ ìœ„ì˜ `-H` ì˜µì…˜ì€ íŠ¹ížˆ C++ 프로그램ì—서 `-H ^std::` 와 ê°™ì´ ì‚¬ìš©í•´ì„œ std 네임스페ì´ìŠ¤ì˜ í˜¸ì¶œë“¤ì„ ìˆ¨ê¸¸ë•Œ 유용하다. ë§Œì¼ íŠ¹ì • 함수ì—ë§Œ ê´€ì‹¬ì´ ìžˆê³  ê·¸ 함수가 어떻게 호출ë˜ëŠ”ì§€ë§Œ 알고 싶다면, 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" | "hide" := [ ] := "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` ê°™ì€ íš¨ê³¼ê°€ 있다. `hide` 트리거는 특정 함수를 ë³´ì´ì§€ 않게 하는 `-H`/`--hide` 옵션과 ê°™ì€ íš¨ê³¼ê°€ 있어서 `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" 형ì‹ì€ unsigned 으로 출력한다. ë‘ í˜•ì‹ ëª¨ë‘ 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 */ `-U` ì˜µì…˜ì€ `-P` 옵션과 ë°˜ëŒ€ì˜ íš¨ê³¼ê°€ 있어 사용ìžê°€ ë‘ ì˜µì…˜ 모ë‘를 제어할 수 있다. ë‚˜ì¤‘ì— ëª…ì‹œëœ ì˜µì…˜ì€ ì•žì„  ì˜µì…˜ì„ ë®ì–´ì“´ë‹¤. 예를 들어, ìœ„ì˜ ì˜ˆì‹œì—서 'a'를 제외한 모든 í•¨ìˆ˜ë“¤ì„ ì¶”ì í•˜ê³  싶다면 아래와 ê°™ì´ ì˜µì…˜ì„ ì ìš©í•œë‹¤. $ uftrace --no-libcall -P . -U a abc # 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 -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, Python 3 그리고 Lua 5.1 ì´ë‹¤. 사용ìžëŠ” 네 ê°œì˜ í•¨ìˆ˜ë¥¼ 작성할 수 있다. '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.15.2/doc/ko/uftrace-record.md000066400000000000000000001137621455365734300176660ustar00rootroot00000000000000% 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* : í•¨ìˆ˜ì˜ ì¸ìžë“¤ì„ 기ë¡í•œë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. --srcline ì˜µì…˜ì´ ìžë™ìœ¼ë¡œ ì ìš©ëœë‹¤. ì¸ìžì— 대한 ì„¤ëª…ì€ *ARGUMENTS* 를 참고한다. -R *SPEC*, \--retval=*SPEC* : í•¨ìˆ˜ë“¤ì˜ ë°˜í™˜ê°’ì„ ê¸°ë¡í•œë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. --srcline ì˜µì…˜ì´ ìžë™ìœ¼ë¡œ ì ìš©ëœë‹¤. ë°˜í™˜ê°’ì— ëŒ€í•œ ì„¤ëª…ì€ *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 언어 ë˜ëŠ” ì‹œìŠ¤í…œì˜ í‘œì¤€ ë¼ì´ë¸ŒëŸ¬ë¦¬ í•¨ìˆ˜ë“¤ì— í•´ë‹¹í•˜ì§€ë§Œ, 디버그 정보를 ì´ìš©í•  수 있다면 ì‚¬ìš©ìž í•¨ìˆ˜ë“¤ì—ë„ ì ìš©í•  수 있다. --srcline ì˜µì…˜ì´ ìžë™ìœ¼ë¡œ ì ìš©ëœë‹¤. -l, \--nest-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ë“¤ ê°„ì˜ í•¨ìˆ˜ í˜¸ì¶œë„ í•¨ê»˜ 기ë¡í•œë‹¤. 기본ì ìœ¼ë¡œ uftrace 는 실행파ì¼ì—서 ì§ì ‘ 호출하는 ë¼ì´ë¸ŒëŸ¬ë¦¬ 함수만 기ë¡í•œë‹¤. -k, \--kernel : ì‚¬ìš©ìž í”„ë¡œê·¸ëž¨ì˜ í•¨ìˆ˜ì™€ 함께 ì»¤ë„ í•¨ìˆ˜ë¥¼ ì¶”ì í•œë‹¤. 기본ì ìœ¼ë¡œëŠ” 커ë„ë¡œì˜ ì§„ìž… ë° ë³µê·€ 함수만 기ë¡í•œë‹¤. ì´ë¥¼ 변경하려면 --kernel-depth ì˜µì…˜ì„ ì‚¬ìš©í•  수 있다. -K *DEPTH*, \--kernel-depth=*DEPTH* : ì»¤ë„ ìµœëŒ€ 함수 깊ì´ë¥¼ 설정한다. --kernel ì˜µì…˜ì´ ìžë™ìœ¼ë¡œ ì ìš©ëœë‹¤. \--clock=*CLOCK* : 타임스탬프를 ì½ëŠ” í´ëŸ­ 소스를 설정한다. *CLOCK* ì€ 'mono', 'mono_raw', 'boot' ì¤‘ì˜ í•˜ë‚˜ë¡œ 설정 가능하고, 기본 ì„¤ì •ì€ 'mono' ì´ë‹¤. \--host=*HOST* : 파ì¼ì— ì“°ì§€ 않고, 주어진 호스트ì—게 ì¶”ì  ë°ì´í„°ë¥¼ ë„¤íŠ¸ì›Œí¬ ìƒìœ¼ë¡œ 전송한다. ë°ì´í„°ë¥¼ 받기 위해서 `uftrace recv` 명령어가 목ì ì§€ì—서 실행ë˜ì–´ì•¼ 한다. \--port=*PORT* : `--host` ì˜µì…˜ì„ ì´ìš©í•´ì„œ ë°ì´í„°ë¥¼ 네트워í¬ë¡œ 보낼 때, 기본 í¬íЏ(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) 스타ì¼ë¡œ ì‹¤í–‰ì‹œê°„ì„ ì¶œë ¥í•œë‹¤. -e, \--estimate-return : ê° í•¨ìˆ˜ì˜ ì§„ìž… ë°ì´í„°ë§Œì„ 기ë¡í•œë‹¤. ì´ ì˜µì…˜ì€ ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì´ ìŠ¤íƒì„ 다룰 경우 유용하게 ì‚¬ìš©ë  ìˆ˜ 있다. ì¼ë°˜ì ìœ¼ë¡œ uftrace는 í•¨ìˆ˜ì˜ ë°˜í™˜ê°’ì„ í›„í‚¹í•˜ê¸° 위해 작업 대ìƒì˜ 실행 ìŠ¤íƒ í”„ë ˆìž„ì„ ìˆ˜ì •í•œë‹¤. 하지만 ì´ëŠ” 때때로 문제를 ë°œìƒì‹œí‚¤ê³  모든 경우를 제대로 다루기 어렵다. ì´ ì˜µì…˜ì€ uftraceê°€ 리턴 주소를 후킹하지 않ë„ë¡ í•˜ì—¬ ì´ëŸ¬í•œ 문제를 예방한다. ë°˜í™˜ëœ ì‹œê°„ì€ ì—°ì†ëœ ë‘ í•¨ìˆ˜ë“¤ì˜ ì‹¤í–‰ ì‹œê°„ì˜ ì ˆë°˜ìœ¼ë¡œ 예측ëœë‹¤. 공통 옵션 ============== -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* 를 참고한다. -Z *SIZE*, \--size-filter=*SIZE* : SIZE ë°”ì´íŠ¸ë³´ë‹¤ ìž‘ì€ í•¨ìˆ˜ë“¤ì„ í‘œì‹œí•˜ì§€ 않게 한다. 만약 ì–´ë–¤ 함수가 명시ì ìœ¼ë¡œ 'trace' 트리거가 ì ìš©ëœ 경우, ê·¸ 함수는 함수 í¬ê¸°ì™€ ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* ì„ ì°¸ê³ í•œë‹¤. -L *LOCATION*, \--loc-filter=*LOCATION* : 사용할 í•„í„°ì˜ ê²½ë¡œë¥¼ 지정한다. ì´ ì˜µì…˜ì€ 1번ì´ìƒ 사용할 수 있다. \--no-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì€ í‘œì‹œí•˜ì§€ 않게 한다. \--no-event : (기본 옵션으로 켜져있는) ì´ë²¤íЏ ë ˆì½”ë”©ì„ í•˜ì§€ 않는다. `--event` 를 통한 명시ì ì¸ ì´ë²¤íЏ ê¸°ë¡ ë°©ì‹ì—는 ì˜í–¥ì„ 주지 않는다. `--no-sched` ì˜µì…˜ì„ ë‚´í¬í•œë‹¤. \--no-sched : (기본 옵션으로 켜져있는) 스케줄 ì´ë²¤íЏ ë ˆì½”ë”©ì„ í•˜ì§€ 않는다. \--match=*TYPE* : TYPE으로 ì¼ì¹˜í•˜ëŠ” íŒ¨í„´ì„ ë³´ì—¬ì¤€ë‹¤. 가능한 형태는 `regex`와 `glob`ì´ë‹¤. ê¸°ë³¸ì€ `regex`ì´ë‹¤. \--disable : ì¶”ì ì„ 사용하지 ì•Šì€ ì±„ë¡œ uftrace를 시작한다. ì´ê²ƒì€ `trace_on` 트리거와 함께 사용ë˜ì—ˆì„ 때만 ì˜ë¯¸ë¥¼ 가진다. \--with-syms=*DIR* : DIR ë””ë ‰í† ë¦¬ì˜ .sym 파ì¼ì—서 심볼(symbol) ë°ì´í„°ë¥¼ ì½ëŠ”ë‹¤. ì´ëŠ” 심볼(symbol) ë°ì´í„°ê°€ ì œê±°ëœ ë°”ì´ë„ˆë¦¬ 파ì¼ì„ ë‹¤ë£¨ëŠ”ë° ìœ ìš©í•˜ë‹¤. ë°”ì´ë„ˆë¦¬ íŒŒì¼ ì´ë¦„ì€ ì €ìž¥í•  때와 사용할 때 ë™ì¼í•´ì•¼ 한다. RECORD 설정 옵션 ===================== \--libmcount-path=*PATH* : libmcount ë¼ì´ë¸ŒëŸ¬ë¦¬ë¥¼ *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 */ `-t`/`--time-filter` ì˜µì…˜ì€ ì‚¬ìš©ìž í•¨ìˆ˜ì—게만 ë™ìž‘한다. ì»¤ë„ í•¨ìˆ˜ë“¤ì„ ê¸°ë¡í•˜ì§€ëŠ” 않지만, ì´ê²ƒë“¤ì€ replay, report, dump와 `-t`/`--time-filter` 옵션과 함께 사용한 graph ëª…ë ¹ì˜ ê²°ê³¼ì— ìˆ¨ê²¨ì ¸ ìžˆì„ ìˆ˜ 있다. í•„í„°ë§ëœ í•¨ìˆ˜ì— íŠ¸ë¦¬ê±°ë¥¼ 설정할 ìˆ˜ë„ ìžˆë‹¤. ë” ë§Žì€ ì •ë³´ëŠ” *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=" | "trace" | "trace_on" | "trace_off" | "time=" | "size=" | "read=" | "finish" | "filter" | "notrace" | "recover" := [ ] := "ns" | "nsec" | "us" | "usec" | "ms" | "msec" | "s" | "sec" | "m" | "min" := "proc/statm" | "page-fault" | "pmu-cycle" | "pmu-cache" | "pmu-branch" `depth` 트리거는 함수를 실행하는 ë™ì•ˆ í•„í„°ì˜ ê¹Šì´ë¥¼ 변경한다. 다양한 í•¨ìˆ˜ì— ëŒ€í•´ 서로 다른 í•„í„° 깊ì´ë¥¼ 설정할 수 있다. ë‹¤ìŒ ì˜ˆì œëŠ” 트리거 ìž‘ë™ ë°©ì‹ì„ 보여준다. ì „ì—­ í•„í„° 깊ì´ê°€ 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)ì— ì§ì ‘ 접근하는 ì¼ë¶€ ê²½ìš°ì— ì‚¬ìš©ëœë‹¤. 지금으로서는 uftraceê°€ 해당 ìž‘ì—…ì„ ìžë™ìœ¼ë¡œ ìˆ˜í–‰í•˜ê¸°ì— í˜¸ì¶œí•  필요는 없다. `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" 형ì‹ì€ unsigned 으로 출력한다. ë‘ í˜•ì‹ ëª¨ë‘ 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 =============== FULL DYNAMIC TRACING -------------------- uftrace 는 x86_64, AArch64 í™˜ê²½ì˜ ëŸ°íƒ€ìž„ (정확하게는, 로드 타임) ì—서 ë™ì ì¶”ì (dynamic tracing)ì´ ê°€ëŠ¥í•˜ë‹¤. 함수를 기ë¡í•˜ê¸° ì „ì—, 보통 í”„ë¡œê·¸ëž¨ì„ `-pg` (í˜¹ì€ `-finstrument-functions`으로) 빌드해야 하고, 그렇게 ëœë‹¤ë©´ 모든 í•¨ìˆ˜ë“¤ì´ `mcount()`를 호출하기 ë•Œë¬¸ì— ì–´ëŠ ì •ë„ ì„±ëŠ¥ì— ì˜í–¥ì„ 받게 ë  ê²ƒì´ë‹¤. ë™ì ì¶”ì ì„ í•  때, `-P`/`--patch` ì˜µì…˜ì„ í†µí•´ 특정 í•¨ìˆ˜ë§Œì„ ì¶”ì í•  수 있다. capstone 디스어셈블리 ì—”ì§„ì„ ì‚¬ìš©í•œë‹¤ë©´ 위 ì˜µì…˜ì„ ì§€ì •í•´ì„œ í”„ë¡œê·¸ëž¨ì„ (재)컴파ì¼í•  필요가 없다. ì´ì œ uftrace 는 ëª…ë ¹ì–´ë“¤ì„ ë¶„ì„í•  수 있게 ë˜ê³  (만약 가능하다면) ê·¸ ëª…ë ¹ì–´ë“¤ì„ ë‹¤ë¥¸ ê³³ì— ë³µì‚¬í•˜ì—¬ `mcount()` í•¨ìˆ˜ë“¤ì„ í˜¸ì¶œí•˜ì—¬ uftrace 로 ì¶”ì í•  수 있게 ë°”ì´ë„ˆë¦¬ë¥¼ ì¡°ìž‘ í•  수 있다. ê·¸ ì´í›„ ì œì–´ê¶Œì€ ë³µì‚¬ëœ ëª…ë ¹ì–´ë¡œ 넘어가게 ë˜ê³ , ê·¸ 다ìŒì—야 ë‚¨ì€ ëª…ë ¹ì–´ë“¤ë¡œ 반환하게 ëœë‹¤. uftrace 를 ì•„ëž˜ì˜ ì˜ˆì œì—서 í‰ì†Œì²˜ëŸ¼ 사용할때ì—는 ì—러 메세지를 보여준다. ê·¸ ì´ìœ ëŠ” ë°”ì´ë„ˆë¦¬ê°€ ì–´ë–¤ `mcount()` 와 ê°™ì€ í•¨ìˆ˜ ì¶”ì ì„ 위한 ì½”ë“œë„ í˜¸ì¶œí•˜ì§€ 않기 때문ì´ë‹¤. $ gcc -o abc 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 .` ê°€ 다른 ëª¨ë“ ê²ƒì— ìš°ì„ í•´ 작용해서ì´ë‹¤. GCC FENTRY ---------- capstone ì„ ì‚¬ìš©í•  수 없다면, í”„ë¡œê·¸ëž¨ì„ ë¹Œë“œí•  때 몇몇 컴파ì¼ëŸ¬ (gcc) ì˜µì…˜ë“¤ì„ ì¶”ê°€í•´ì•¼ í•  것ì´ë‹¤. gcc 5.1 버전 ì´ìƒë¶€í„°ëŠ” `-mfentry`와 `-mnop-mcount` ì˜µì…˜ì„ ì œê³µí•˜ëŠ”ë° ì´ ì˜µì…˜ë“¤ì€ í•¨ìˆ˜ 맨 ì•žì— `mcount()` 와 ê°™ì€ í•¨ìˆ˜ ì¶”ì ì„ 위한 코드를 추가하고 ê·¸ 명령어를 NOP 으로 변환한다. 그렇게 ë˜ë©´ ì¼ë°˜ì ì¸ ì¡°ê±´ì—서 실행할 때ì—는 성능 ìƒì˜ 오버헤드가 ê±°ì˜ ì—†ì–´ì§ˆ 것ì´ë‹¤. uftrace 는 `-P` ì˜µì…˜ì„ ì´ìš©í•˜ì—¬ ì„ íƒì ìœ¼ë¡œ `mcount()` 함수를 호출할 수 있ë„ë¡ ì „í™˜í•  수 있다. $ gcc -pg -mfentry -mnop-mcount -o abc-fentry tests/s-abc.c $ uftrace record -P . --no-libcall abc-fentry $ uftrace replay # DURATION TID FUNCTION [ 18973] | main() { [ 18973] | a() { [ 18973] | b() { 0.852 us [ 18973] | c(); 2.378 us [ 18973] | } /* b */ 2.909 us [ 18973] | } /* a */ 3.756 us [ 18973] | } /* main */ CLANG XRAY ---------- 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 */ PATCHABLE FUNCTION ENTRY ------------------------ 최근 gcc와 clang 컴파ì¼ëŸ¬ëŠ” ëª¨ë‘ `-fpatchable-function-entry=N[,M]`ë¼ëŠ” 유용한 ì˜µì…˜ì„ ì œê³µí•˜ëŠ”ë°, ì´ëŠ” 함수 ì§„ìž… ì´ì „ì— M NOP를 ìƒì„±í•˜ê³  함수 ì§„ìž… ì´í›„ì— N-M NOP를 ìƒì„±í•œë‹¤. Mì´ 0ì¸ ê²½ìš° `-fpatchable-function-entry=N`ë§Œìœ¼ë¡œë„ ì¶©ë¶„í•˜ë‹¤. ë™ì ì¶”ì ì„ 위한 NOP 수는 아키í…ì²˜ì— ë”°ë¼ ë‹¤ë¥´ì§€ë§Œ uftrace 기ë¡ì„ 위해 ë™ì ìœ¼ë¡œ 호출 명령어를 패치하려면 x86_64는 5ê°œì˜ NOP를 요구하고 AArch64는 2ê°œì˜ NOP를 요구한다. 예를 들어 x86_64ì—서는, 아래와 ê°™ì´ ëŒ€ìƒ í”„ë¡œê·¸ëž¨ì„ ë¹Œë“œí•˜ê³  ì¶”ì í•  수 있다. $ gcc -fpatchable-function-entry=5 -o abc-fpatchable tests/s-abc.c $ uftrace record -P . abc-fpatchable $ uftrace replay # DURATION TID FUNCTION [ 6818] | main() { [ 6818] | a() { [ 6818] | b() { [ 6818] | c() { 0.926 us [ 6818] | getpid(); 4.158 us [ 6818] | } /* c */ 4.590 us [ 6818] | } /* b */ 4.957 us [ 6818] | } /* a */ 5.593 us [ 6818] | } /* main */ ì´ ê¸°ëŠ¥ì€ `__attribute__ ((patchable_function_entry (N,M)))`로 특정 í•¨ìˆ˜ì— ì»´íŒŒì¼ëŸ¬ ì†ì„±ì„ 추가하여 사용할 ìˆ˜ë„ ìžˆë‹¤. 예를 들어, 'tests/s-abc.c' í”„ë¡œê·¸ëž¨ì€ ì•„ëž˜ì™€ ê°™ì´ ìˆ˜ì •ë  ìˆ˜ 있다. static int c(void) { return 100000; } __attribute__((patchable_function_entry(5))) static int b(void) { return c() + 1; } static int a(void) { return b() - 1; } __attribute__((patchable_function_entry(5))) int main(void) { int ret = 0; ret += a(); return ret ? 0 : 1; } ì´ ì†ì„±ì€ 'main'ê³¼ 'b' 함수ì—ë§Œ 추가ë˜ì—ˆê³  ì´ í”„ë¡œê·¸ëž¨ì€ ë‹¤ë¥¸ 추가ì ì¸ 컴파ì¼ëŸ¬ 옵션 ì—†ì´ë„ ì •ìƒì ìœ¼ë¡œ 컴파ì¼ë˜ì§€ë§Œ, 컴파ì¼ëŸ¬ëŠ” ì†ì„±ì„ ê°ì§€í•˜ê³  'main'ê³¼ 'b' 함수 진입시ì ì— 5ê°œì˜ NOP를 추가한다. $ gcc -o abc tests/s-patchable-abc.c $ uftrace record -P . abc $ uftrace replay # DURATION TID FUNCTION [ 20803] | main() { 0.342 us [ 20803] | b(); 1.608 us [ 20803] | } /* main */ ì´ëŸ° ì‹ìœ¼ë¡œ, uftrace는 사용ìžê°€ ì›í•˜ì—¬ 명시ì ìœ¼ë¡œ ì†ì„±ì„ 추가한 í•¨ìˆ˜ë§Œì„ ì„ ë³„ì ìœ¼ë¡œ ì¶”ì í•  수 있다. ì´ëŸ¬í•œ ì ‘ê·¼ì€ ì»´íŒŒì¼ëŸ¬ 플래그로 í™œì„±í™”ëœ í•¨ìˆ˜ 전체를 ì¶”ì í•˜ëŠ” ë°©ì‹ë³´ë‹¤ ëœ ë²ˆê±°ë¡œìš´ ë°©ì‹ìœ¼ë¡œ ì¶”ì  ê¸°ë¡ì„ 수집할 수 있다. `-fpatchable-function-entry=N[,M]` 옵션과 ê·¸ê²ƒì˜ ì†ì„±ì€ gcc-8.1ê³¼ clang-10부터 ì§€ì›ëœë‹¤. ì´ ë™ì ì¶”ì  ê¸°ëŠ¥ì€ í˜„ìž¬ë¡œì„œ x86_64와 AArch64 모ë‘ì—서 사용 가능하다. SCRIPT EXECUTION ================ uftrace 는 í•¨ìˆ˜ì˜ ì§„ìž…ê³¼ 반환 시ì ì— 스í¬ë¦½íЏ ì‹¤í–‰ì´ ê°€ëŠ¥í•˜ë‹¤. 현재 ì§€ì›ë˜ëŠ” 스í¬ë¦½íЏ íƒ€ìž…ì€ Python 2.7, Python 3 그리고 Lua 5.1 ì´ë‹¤. 사용ìžëŠ” 네 ê°œì˜ í•¨ìˆ˜ë¥¼ 작성할 수 있다. 'uftrace_entry' 와 'uftrace_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 record -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 '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.15.2/doc/ko/uftrace-recv.md000066400000000000000000000046311455365734300173410ustar00rootroot00000000000000% UFTRACE-RECV(1) Uftrace User Manuals % Namhyung Kim % Sep, 2018 ì´ë¦„ ==== uftrace-recv - 네트워í¬ë¥¼ 통해 ë°ì´í„°ë¥¼ 수신하고 파ì¼ë¡œ 저장한다. 사용법 ======== uftrace recv [*옵션*] 설명 =========== uftrace recv 명령어는 네트워í¬ë¥¼ 통해 ë°ì´í„°ë¥¼ 수신하고 파ì¼ë¡œ 저장한다. ë°ì´í„°ëŠ” `uftrace-record` 명령어와 \--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 -d example_data --host localhost --port 1234 example ìœ„ì˜ ëª…ë ¹ì–´ëŠ” `example`í”„ë¡œê·¸ëž¨ì˜ ë°ì´í„°ë¥¼ 기ë¡í•œ ë‹¤ìŒ `-d` ì˜µì…˜ì„ ì‚¬ìš©í•˜ì—¬ `example_data` ë””ë ‰í„°ë¦¬ì— ë°ì´í„°ë¥¼ 저장하고 `--host` ì˜µì…˜ì„ ì‚¬ìš©í•˜ì—¬ 수신할 호스트를 설정하고 전송한다. 최종ì ìœ¼ë¡œ, ìœ„ì˜ ëª…ë ¹ì–´ì˜ í˜¸ìŠ¤íŠ¸ëŠ” `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.15.2/doc/ko/uftrace-replay.md000066400000000000000000000435061455365734300177020ustar00rootroot00000000000000% 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 : 함수 ì´ë¦„ê³¼ 함께 ë¼ì´ë¸ŒëŸ¬ë¦¬ ì´ë¦„ì„ ì¶œë ¥í•œë‹¤. \--format=*TYPE* : 형ì‹í™”ëœ ì¶œë ¥ì„ ë³´ì—¬ì¤€ë‹¤. 현재는 'normal' ê³¼ 'html' 형ì‹ì´ ì§€ì›ëœë‹¤. 공통 옵션 ========= -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* 를 참고한다. -Z *SIZE*, \--size-filter=*SIZE* : SIZE ë°”ì´íŠ¸ë³´ë‹¤ ìž‘ì€ í•¨ìˆ˜ë“¤ì„ í‘œì‹œí•˜ì§€ 않게 한다. 만약 ì–´ë–¤ 함수가 명시ì ìœ¼ë¡œ 'trace' 트리거가 ì ìš©ëœ 경우, ê·¸ 함수는 함수 í¬ê¸°ì™€ ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. í•„í„°ì— ëŒ€í•œ ì„¤ëª…ì€ *FILTERS* ì„ ì°¸ê³ í•œë‹¤. -L *LOCATION*, \--loc-filter=*LOCATION* : 사용할 í•„í„°ì˜ ê²½ë¡œë¥¼ 지정한다. ì´ ì˜µì…˜ì€ 1번ì´ìƒ 사용할 수 있다. \--no-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì€ í‘œì‹œí•˜ì§€ 않게 한다. \--no-event : ì´ë²¤íŠ¸ë“¤ì„ í‘œì‹œí•˜ì§€ 않게 한다. `--no-sched` ì˜µì…˜ì„ ë‚´í¬í•œë‹¤. \--no-sched : 스케줄 ì´ë²¤íŠ¸ë¥¼ 표시하지 않게 한다. \--no-sched-preempt : ì„ ì  ìŠ¤ì¼€ì¤„ ì´ë²¤íŠ¸ëŠ” 표시하지 않게 하나 ì¼ë°˜(대기) 스케쥴 ì´ë²¤íŠ¸ëŠ” 그대로 표시한다. \--match=*TYPE* : 타입(TYPE)으로 ì¼ì¹˜í•˜ëŠ” íŒ¨í„´ì„ ë³´ì—¬ì¤€ë‹¤. 가능한 형태는 `regex`와 `glob`ì´ë‹¤. 기본 ì„¤ì •ì€ `regex`ì´ë‹¤. \--disable : uftrace 를 시작할때 ë°ì´í„°ë¥¼ 기ë¡í•˜ì§€ 않고 시작한다. ì´ê²ƒì€ `trace_on` 트리거와 함께 사용ë˜ì—ˆì„ 때만 ì˜ë¯¸ë¥¼ 가진다. \--with-syms=*DIR* : DIR ë””ë ‰í† ë¦¬ì˜ .sym 파ì¼ì—서 심볼(symbol) ë°ì´í„°ë¥¼ ì½ëŠ”ë‹¤. ì´ëŠ” 심볼(symbol) ë°ì´í„°ê°€ ì œê±°ëœ ë°”ì´ë„ˆë¦¬ 파ì¼ì„ ë‹¤ë£¨ëŠ”ë° ìœ ìš©í•˜ë‹¤. ë°”ì´ë„ˆë¦¬ íŒŒì¼ ì´ë¦„ì€ ì €ìž¥í•  때와 사용할 때 ë™ì¼í•´ì•¼ 한다. 공통 ë¶„ì„ ì˜µì…˜ ======================= -H *FUNC*, \--hide=*FUNC* : 주어진 FUNC í•¨ìˆ˜ë“¤ì„ ì¶œë ¥ 대ìƒì—서 제외할 수 있다. ì´ëŠ” ì„ íƒëœ í•¨ìˆ˜ì˜ ìžì‹ í•¨ìˆ˜ë“¤ì— ëŒ€í•´ì„œëŠ” ì˜í–¥ì„ 주지 않으며 단지 주어진 함수들만 숨기는 ê¸°ëŠ¥ì„ í•˜ê²Œ ëœë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. \--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 */ `b()` í•¨ìˆ˜ë§Œì„ ìˆ¨ê¸°ê³  ê·¸ì˜ í•˜ìœ„ í•¨ìˆ˜ë“¤ì€ ê·¸ëŒ€ë¡œ ë³´ê³  싶으면 `-H` ì˜µì…˜ì„ ì‚¬ìš©í•  수 있다. $ uftrace record ./abc $ uftrace replay -H b # DURATION TID FUNCTION 138.494 us [ 1234] | __cxa_atexit(); [ 1234] | main() { [ 1234] | a() { 3.880 us [ 1234] | c(); 6.448 us [ 1234] | } /* a */ 8.631 us [ 1234] | } /* main */ ìœ„ì˜ `-H` ì˜µì…˜ì€ íŠ¹ížˆ C++ 프로그램ì—서 `-H ^std::` 와 ê°™ì´ ì‚¬ìš©í•´ì„œ std 네임스페ì´ìŠ¤ì˜ í˜¸ì¶œë“¤ì„ ìˆ¨ê¸¸ë•Œ 유용하다. ë§Œì¼ íŠ¹ì • 함수ì—ë§Œ ê´€ì‹¬ì´ ìžˆê³  ê·¸ 함수가 어떻게 호출ë˜ëŠ”ì§€ë§Œ 알고 싶다면, 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=" | "size=" | "filter" | "notrace" | "hide" := [ ] := "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` ê°™ì€ íš¨ê³¼ê°€ 있다. `hide` 트리거는 특정 함수를 ë³´ì´ì§€ 않게 하는 `-H`/`--hide` 옵션과 ê°™ì€ íš¨ê³¼ê°€ 있어서 `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.15.2/doc/ko/uftrace-report.md000066400000000000000000000441331455365734300177160ustar00rootroot00000000000000% UFTRACE-REPORT(1) Uftrace User Manuals % Namhyung Kim % Sep, 2018 ì´ë¦„ ==== uftrace-report - 기ë¡ëœ ë°ì´í„°ì˜ 통계와 요약 정보를 출력한다. 사용법 ====== uftrace report [*options*] 설명 ==== ì´ ëª…ë ¹ì–´ëŠ” 주어진 ë°ì´í„° 파ì¼ì˜ ë°ì´í„°ë¥¼ 모으고 ê·¸ 요약 정보와 통계 ìžë£Œë“¤ì„ 출력한다. 기본ì ìœ¼ë¡œ 함수ìžë£Œë“¤ì„ 보여주는ë°, `--task` ì˜µì…˜ì„ í†µí•´ 실행한 íƒœìŠ¤í¬ ë‹¨ìœ„ì˜ í†µê³„ìžë£Œë¥¼ ë³¼ 수 있고, `--diff` ì˜µì…˜ì€ ì¶”ê°€ ì¸ìžë¡œ ë°ì´í„°ë¥¼ 입력하면, ê·¸ ë°ì´í„°ì™€ ì›ë³¸ ë°ì´í„° ê°„ì˜ ì°¨ì´ì ì„ 보여준다. REPORT 옵션 =========== -f *FIELD*, \--output-fields=*FIELD* : 결과로 보여지는 필드를 사용ìžê°€ 지정한다. 가능한 값들로는 `total`, `total-avg`, `total-min`, `total-max`, `self`, `self-avg`, `self-min`, `self-max`, `size` 그리고 `call`ì´ ìžˆë‹¤. 여러 필드를 갖는 경우 콤마로 구분ëœë‹¤. 모든 필드를 ê°ì¶”기 위한 (단ì¼í•˜ê²Œ 사용ë˜ëŠ”) 'none' 특수 필드가 있으며 기본ì ìœ¼ë¡œ 'total,self,call' ì´ ì‚¬ìš©ëœë‹¤. ìƒì„¸í•œ ì„¤ëª…ì€ *FIELDS* 를 참고한다. -s *KEYS*[,*KEYS*,...], \--sort=*KEYS*[,*KEYS*,...] : 주어진 키를 기반으로 í•¨ìˆ˜ë“¤ì„ ì •ë ¬í•œë‹¤. 여러 í‚¤ë“¤ì„ ì ìš©í•  경우, í‚¤ë“¤ì„ ì‰¼í‘œ(,)로 나누어 표현한다. `total` (time), `total-avg`, `total-min`, `total-max`, `self` (time), `self-avg`, `self-min`, `self-max`, `size`, `call`, `func`를 키로 ì´ìš©í•  수 있다. 그러나 `--avg-total` ë˜ëŠ” `--avg-self` ì˜µì…˜ì´ ì‚¬ìš©ëœ ê²½ìš°, ì´ ì‹œê°„(total time) ë˜ëŠ” ìžì²´ 시간(self timeì—)ì— ì ìš©ë˜ëŠ” `avg`, `min`, `max`를 키로 ì´ìš©í•  수 있다. \--avg-total : ê° í•¨ìˆ˜ì˜ ì´ ì‹œê°„(total time)ì˜ í‰ê· , 최소, 최대 ì‹œê°„ì„ ë³´ì—¬ì¤€ë‹¤. \--avg-self : ê° í•¨ìˆ˜ì˜ ìžì²´ 시간(self time)ì˜ í‰ê· , 최소, 최대 ì‹œê°„ì„ ë³´ì—¬ì¤€ë‹¤. \--task : í•¨ìˆ˜ì˜ í†µê³„ìžë£Œê°€ 아닌 태스í¬ë¥¼ 요약해서 보고한다. -f ì˜µì…˜ì„ ì´ìš©í•´ 출력 필드를 사용ìžê°€ 지정할 수 있다. 가능한 값들로는: `total`, `self`, `func` 그리고 `tid`ê°€ 있다. 여러 필드를 갖는 경우 콤마로 구분ëœë‹¤. 모든 필드를 ê°ì¶”기 위한 (단ì¼í•˜ê²Œ 사용ë˜ëŠ”) 'none' 특수 필드가 있으며 기본ì ìœ¼ë¡œ 'total,self,func,tid' ê°€ 사용ëœë‹¤. ìƒì„¸í•œ ì„¤ëª…ì€ *FIELDS* 를 참고한다. \--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는 ë‘ ë°ì´í„° ê°„ì˜ (백분율) ì°¨ì´ì— 대한 것ì´ë‹¤. \--srcline : 가능한 ê° í•¨ìˆ˜ë“¤ì˜ ì†ŒìŠ¤ 줄번호를 표시한다. \--format=*TYPE* : 형ì‹í™”ëœ ì¶œë ¥ì„ ë³´ì—¬ì¤€ë‹¤. 현재는 'normal' ê³¼ 'html' 형ì‹ì´ ì§€ì›ëœë‹¤. 공통 옵션 ========= -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' 트리거가 ì ìš©ëœ 경우, ê·¸ 함수는 실행 시간과 ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. -Z *SIZE*, \--size-filter=*SIZE* : SIZE ë°”ì´íŠ¸ë³´ë‹¤ ìž‘ì€ í•¨ìˆ˜ë“¤ì„ í‘œì‹œí•˜ì§€ 않게 한다. 만약 ì–´ë–¤ 함수가 명시ì ìœ¼ë¡œ 'trace' 트리거가 ì ìš©ëœ 경우, ê·¸ 함수는 함수 í¬ê¸°ì™€ ìƒê´€ì—†ì´ í•­ìƒ ì¶œë ¥ëœë‹¤. -L *LOCATION*, \--loc-filter=*LOCATION* : 사용할 í•„í„°ì˜ ê²½ë¡œë¥¼ 지정한다. ì´ ì˜µì…˜ì€ 1번ì´ìƒ 사용할 수 있다. \--no-libcall : ë¼ì´ë¸ŒëŸ¬ë¦¬ í˜¸ì¶œì€ í‘œì‹œí•˜ì§€ 않게 한다. \--no-event : ì´ë²¤íŠ¸ë“¤ì„ í‘œì‹œí•˜ì§€ 않게 한다. `--no-sched` ì˜µì…˜ì„ ë‚´í¬í•œë‹¤. \--no-sched : 스케줄 ì´ë²¤íŠ¸ë¥¼ 표시하지 않게 한다. \--no-sched-preempt : ì„ ì  ìŠ¤ì¼€ì¤„ ì´ë²¤íŠ¸ëŠ” 표시하지 않게 하나 ì¼ë°˜(대기) 스케쥴 ì´ë²¤íŠ¸ëŠ” 그대로 표시한다. \--match=*TYPE* : 타입(TYPE)으로 ì¼ì¹˜í•˜ëŠ” íŒ¨í„´ì„ ë³´ì—¬ì¤€ë‹¤. 가능한 형태는 `regex`와 `glob`ì´ë‹¤. 기본 ì„¤ì •ì€ `regex`ì´ë‹¤. \--with-syms=*DIR* : DIR ë””ë ‰í† ë¦¬ì˜ .sym 파ì¼ì—서 심볼(symbol) ë°ì´í„°ë¥¼ ì½ëŠ”ë‹¤. ì´ëŠ” 심볼(symbol) ë°ì´í„°ê°€ ì œê±°ëœ ë°”ì´ë„ˆë¦¬ 파ì¼ì„ ë‹¤ë£¨ëŠ”ë° ìœ ìš©í•˜ë‹¤. ë°”ì´ë„ˆë¦¬ íŒŒì¼ ì´ë¦„ì€ ì €ìž¥í•  때와 사용할 때 ë™ì¼í•´ì•¼ 한다. 공통 ë¶„ì„ ì˜µì…˜ ======================= -H *FUNC*, \--hide=*FUNC* : 주어진 FUNC í•¨ìˆ˜ë“¤ì„ ì¶œë ¥ 대ìƒì—서 제외할 수 있다. ì´ëŠ” ì„ íƒëœ í•¨ìˆ˜ì˜ ìžì‹ í•¨ìˆ˜ë“¤ì— ëŒ€í•´ì„œëŠ” ì˜í–¥ì„ 주지 않으며 단지 주어진 함수들만 숨기는 ê¸°ëŠ¥ì„ í•˜ê²Œ ëœë‹¤. ì´ ì˜µì…˜ì€ í•œë²ˆ ì´ìƒ ì“°ì¼ ìˆ˜ 있다. \--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 --task Total time Self time Num funcs TID Task name ========== ========== ========== ====== ================ 22.178 us 22.178 us 7 29955 t-abc $ uftrace record --srcline abc $ uftrace report --srcline Total time Self time Calls Function [Source] ========== ========== ========== ==================== 17.508 us 2.199 us 1 main [./tests/s-abc.c:26] 15.309 us 2.384 us 1 a [./tests/s-abc.c:11] 12.925 us 2.633 us 1 b [./tests/s-abc.c:16] 10.292 us 5.159 us 1 c [./tests/s-abc.c:21] 5.133 us 5.133 us 1 getpid 3.437 us 3.437 us 1 __monstartup 1.959 us 1.959 us 1 __cxa_atexit ë‘ ë°ì´í„°ì˜ ì°¨ì´ì ì„ 보려면: $ 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 FIELDS ====== uftrace 사용ìžëŠ” report 결과를 ëª‡ëª‡ì˜ í•„ë“œë¡œ ì›í•˜ëŠ” ë°©ì‹ëŒ€ë¡œ 구성할 수 있다. 기본ì ìœ¼ë¡œ total, self와 call 필드를 사용하지만, 다른 í•„ë“œë“¤ë„ ë‹¤ìŒê³¼ ê°™ì´ ìž„ì˜ì˜ 순서로 사용 가능하다. $ uftrace report -f total,total-max,self-min,call Total time Total max Self min Calls Function ========== ========== ========== ========== ==================== 97.234 us 36.033 us 1.073 us 3 lib_a 50.552 us 26.690 us 2.828 us 2 lib_b 46.806 us 46.806 us 3.290 us 1 main 43.516 us 43.516 us 7.483 us 1 foo 32.010 us 20.847 us 9.684 us 2 lib_c ê° í•„ë“œëŠ” 아래와 ê°™ì´ ì •ë ¬ í‚¤ë¡œë„ ì‚¬ìš©ë  ìˆ˜ 있다. $ uftrace report -f total,total-max,self-min,call -s call Total time Total max Self min Calls Function ========== ========== ========== ========== ==================== 97.234 us 36.033 us 1.073 us 3 lib_a 50.552 us 26.690 us 2.828 us 2 lib_b 32.010 us 20.847 us 9.684 us 2 lib_c 43.516 us 43.516 us 7.483 us 1 foo 46.806 us 46.806 us 3.290 us 1 main $ uftrace report -f total,total-max,self-min,total-min,call -s self-min,total-min Total time Total max Self min Total min Calls Function ========== ========== ========== ========== ========== ==================== 32.010 us 20.847 us 9.684 us 11.163 us 2 lib_c 43.516 us 43.516 us 7.483 us 43.516 us 1 foo 46.806 us 46.806 us 3.290 us 46.806 us 1 main 50.552 us 26.690 us 2.828 us 23.862 us 2 lib_b 97.234 us 36.033 us 1.073 us 27.763 us 3 lib_a ê° í•„ë“œëŠ” 아래와 ê°™ì´ --diff 옵션과 함께 ì‚¬ìš©ë  ìˆ˜ 있다. $ uftrace report --diff uftrace.data.old -f total,total-min # # uftrace diff # [0] base: uftrace.data (from uftrace record test/t-lib) # [1] diff: uftrace.data.old (from uftrace record test/t-lib) # Total time Total min Function =========== =========== ==================== +34.560 us +9.884 us lib_a +18.086 us +8.517 us lib_b +16.887 us +16.887 us main +15.479 us +15.479 us foo +10.600 us +3.127 us lib_c $ uftrace report --diff uftrace.data.old -f total,total-min,self-avg --diff-policy full # # uftrace diff # [0] base: uftrace.data (from uftrace record --srcline test/t-lib) # [1] diff: uftrace.data.old (from uftrace record --srcline test/t-lib) # Total time (diff) Total min (diff) Self avg (diff) Function =================================== =================================== =================================== ==================== 14.616 us 13.796 us +0.820 us 4.146 us 3.823 us +0.323 us 0.443 us 0.459 us -0.016 us lib_a 6.529 us 5.957 us +0.572 us 6.529 us 5.957 us +0.572 us 0.436 us 0.356 us +0.080 us main 7.700 us 7.173 us +0.527 us 3.677 us 3.426 us +0.251 us 0.365 us 0.363 us +0.002 us lib_b 6.093 us 5.601 us +0.492 us 6.093 us 5.601 us +0.492 us 0.741 us 0.476 us +0.265 us foo 5.638 us 5.208 us +0.430 us 2.346 us 2.187 us +0.159 us 1.646 us 1.510 us +0.136 us lib_c ê° í•„ë“œëŠ” 다ìŒê³¼ ê°™ì€ ì˜ë¯¸ê°€ 있다. * total: í•¨ìˆ˜ì˜ ì „ì²´ 실행 시간 * total-avg: ê° í•¨ìˆ˜ë“¤ì˜ ì´í•© ì‹œê°„ì˜ í‰ê· ê°’. * total-min: ê° í•¨ìˆ˜ë“¤ì˜ ì´í•© ì‹œê°„ì˜ ìµœì†Œê°’. * total-max: ê° í•¨ìˆ˜ë“¤ì˜ ì´í•© ì‹œê°„ì˜ ìµœëŒ€ê°’. * self: ê° í•¨ìˆ˜ë³„ 소요 시간. * self-avg: ê° í•¨ìˆ˜ë³„ 소요 ì‹œê°„ì˜ í‰ê· ê°’. * self-min: ê° í•¨ìˆ˜ë³„ 소요 ì‹œê°„ì˜ ìµœì†Œê°’. * self-max: ê° í•¨ìˆ˜ë³„ 소요 ì‹œê°„ì˜ ìµœëŒ€ê°’. * call: ê° í•¨ìˆ˜ë“¤ì´ í˜¸ì¶œëœ íšŸìˆ˜. 기본ì ìœ¼ë¡œ ì„¤ì •ëœ í•„ë“œê°’ì€ 'total,self,call'ì´ë‹¤. 만약 주어진 í•„ë“œì˜ ì´ë¦„ì´ "+"로 시작ëœë‹¤ë©´, ê·¸ 필드는 기본 í•„ë“œê°’ì— ì¶”ê°€ë  ê²ƒì´ë‹¤. 즉, "-f +total-avg" 는 "-f total,self,call,total-avg" 와 ê°™ì€ ê²ƒì´ë‹¤. ë˜í•œ 'none'ì´ë¼ëŠ” 특별한 í•„ë“œë„ ë°›ì„ ìˆ˜ 있는ë°, ì´ëŠ” 필드 ì¶œë ¥ì„ í•˜ì§€ 않고 ì˜¤ì§ í•¨ìˆ˜ 실행 ê²°ê³¼ë§Œì„ ë³´ì—¬ì¤€ë‹¤. TASK FIELDS ====== * total: ê° ìž‘ì—…ì˜ ì´ ì†Œìš” 시간. * self: ê° ìž‘ì—…ë³„ 소요 시간. * func: 작업 ë‚´ì˜ í•¨ìˆ˜ 갯수. * tid: 작업 ID. 기본ì ìœ¼ë¡œ ì„¤ì •ëœ í•„ë“œê°’ì€ 'total,self,func,tid'ì´ë‹¤. ìƒì„¸í•œ ì„¤ëª…ì€ *FIELDS* 를 참고한다. 함께 보기 ========= `uftrace`(1), `uftrace-record`(1), `uftrace-replay`(1), `uftrace-tui`(1) ë²ˆì—­ìž ====== ê¹€ì„œì˜ , 강민철 uftrace-0.15.2/doc/ko/uftrace-script.md000066400000000000000000000244571455365734300177160ustar00rootroot00000000000000% UFTRACE-SCRIPT(1) Uftrace User Manuals % Honggyu Kim , Namhyung Kim % Sep, 2018 ì´ë¦„ ==== uftrace-script - 기ë¡ëœ ë°ì´í„°ë¥¼ 대ìƒìœ¼ë¡œ 스í¬ë¦½íŠ¸ë¥¼ 실행한다. 사용법 ====== uftrace script (-S|--script) uftrace-0.15.2/doc/uftrace.md000066400000000000000000000240361455365734300157740ustar00rootroot00000000000000% 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 [//1]: # "This is a portable invisible comment block as outlined in this answer:" [//2]: # "https://stackoverflow.com/questions/4823468/comments-in-markdown" [//3]: # "Most of the command line option help texts have been generated by:" [//4]: # "https://github.com/bernhardkaindl/help2md" [//5]: # "(followed by merging the existing more descriptive option help texts)" [//6]: # "TODO: Group them into command-specific sections for a quick overview" [//7]: # "with a note referring to the command-specific page for each section." [//8]: # "While they are described in the command-specific pages, it" [//9]: # "would be nice to also have one place to check for all options." COMMON OPTIONS ============== These are the common options supported by all uftrace subcommands: -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. SUBCOMMAND-SPECIFIC OPTIONS =========================== These options are listed here for completeness, but are only effective with specific subcommands. Please see the uftrace-<*subcommand*> manual pages for more information: The manual for *uftrace-live*(1) is special: The subcommand `live` does `record` and `replay` internally. Thus, it describes most regular option in detail. \--avg-self : Show average/min/max of self function time \--avg-total : Show average/min/max of total function time -a, \--auto-args : Show arguments and return value of known functions -A, \--argument=*FUNC*@arg[,arg,...] : Show function arguments -b, \--buffer=*SIZE* : Size of tracing buffer (default: 128K) \--chrome : Dump recorded data in chrome trace format \--clock : Set clock source for timestamp (default: mono) \--column-offset=*DEPTH* : Offset of each column (default: 8) \--column-view : Print tasks in separate columns -C, \--caller-filter=*FUNC* : Only trace callers of those FUNCs \--demangle=*TYPE* : C++ symbol demangling: full, simple, no : (default: simple) \--diff=*DATA* : Report differences \--diff-policy=*POLICY* : Control diff report policy : (default: 'abs,compact,no-percent') \--disable : Start with tracing disabled -D, \--depth=*DEPTH* : Trace functions within *DEPTH* -e, \--estimate-return : Use only entry record type for safety \--event-full : Show all events outside of function -E, \--Event=*EVENT* : Enable *EVENT* to save more information \--flame-graph : Dump recorded data in FlameGraph format \--flat : Use flat output format \--force : Trace even if executable is not instrumented \--format=*FORMAT* : Use *FORMAT* for output: normal, html (default: normal) -f, \--output-fields=*FIELD* : Show FIELDs in the replay or graph output -F, \--filter=*FUNC* : Only trace those FUNCs -g, \--agent : Start an agent in mcount to listen to commands \--graphviz : Dump recorded data in *DOT* format -H, \--hide=*FUNC* : Hide FUNCs from trace \--host=*HOST* : Send trace data to *HOST* instead of write to file -k, \--kernel : Trace kernel functions also (if supported) \--keep-pid : Keep same pid during execution of traced program \--kernel-buffer=*SIZE* : Size of kernel tracing buffer (default: 1408K) \--kernel-full : Show kernel functions outside of user \--kernel-only : Dump kernel data only \--kernel-skip-out : Skip kernel functions outside of user (deprecated) -K, \--kernel-depth=*DEPTH* : Trace kernel functions within *DEPTH* \--libmcount-single : Use single thread version of libmcount \--list-event : List available events \--logfile=*FILE* : Save warning and debug messages into this file instead of stderr. -l, \--nest-libcall : Show nested library calls \--libname : Show libname name with symbol name \--libmcount-path=*PATH* : Load libmcount libraries from this *PATH* \--match=*TYPE* : Support pattern match: regex, glob (default: : regex) \--max-stack=*DEPTH* : Set max stack depth to *DEPTH* (default: 65535) \--no-args : Do not show arguments and return value \--no-comment : Don't show comments of returned functions \--no-event : Disable (default) events \--no-sched : Disable schedule events \--no-sched-preempt : Hide pre-emptive schedule event : but show regular(sleeping) schedule event \--no-libcall : Don't trace library function calls \--no-merge : Don't merge leaf functions \--no-pltbind : Do not bind dynamic symbols (*LD_BIND_NOT*) \--no-randomize-addr : Disable *ASLR* (Address Space Layout Randomization) \--nop : No operation (for performance test) \--num-thread=*NUM* : Create *NUM* recorder threads -N, \--notrace=*FUNC* : Don't trace those FUNCs -p, \--pid=*PID* : Connect to the *PID* of an interactive mcount instance \--port=*PORT* : Use *PORT* for network connection (default: 8090) -P, \--patch=*FUNC* : Apply dynamic patching for FUNCs \--record : Record a new trace before running given script \--report : Show a live report before replay \--rt-prio=*PRIO* : Record with real-time (*FIFO*) priority -r, \--time-range=*TIME*~*TIME* : Show output within the *TIME* (timestamp or elapsed time) : range only \--run-cmd=*CMDLINE* : Command line that want to execute after tracing : data received -R, \--retval=*FUNC*[@retspec] : Show function return values for *FUNC*, optionally with given uftrace retspec \--sample-time=*TIME* : Show flame graph with this sampling time \--signal=*SIGNAL*@act[,act,...] : Trigger the given actions when the given *SIGNAL* is received \--sort-column=*INDEX* : Sort diff report on column *INDEX* (default: 2) \--srcline : Enable recording source line info \--symbols : Print symbol table instead of the recorded tracing info -s, \--sort=*KEY*[,*KEY*,...] : Sort reported functions by KEYs (default: 2) -S, \--script=*SCRIPT* : Run a given *SCRIPT* in function entry and exit -t, \--time-filter=*TIME* : Hide small functions run less than the *TIME* \--task : Print task relationship in a tree form instead of the tracing info. \--task-newline : Interleave a newline when task is changed \--tid=*TID*[,*TID*,...] : Only replay those tasks \--time : Print time information -T, \--trigger=*FUNC*@act[,act,...] : Trigger action on those FUNCs -U, \--unpatch=*FUNC* : Don't apply dynamic patching for FUNCs \--with-syms=*DIR* : Use symbol files in the *DIR* -W, \--watch=*POINT* : Watch and report *POINT* if it's changed -Z, \--size-filter=*SIZE* : Apply dynamic patching for functions bigger than *SIZE* For more detail about these command-specific options, please see the more specific manual pages listed below. 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.15.2/gdb/000077500000000000000000000000001455365734300140035ustar00rootroot00000000000000uftrace-0.15.2/gdb/uftrace/000077500000000000000000000000001455365734300154345ustar00rootroot00000000000000uftrace-0.15.2/gdb/uftrace/lists.py000066400000000000000000000071271455365734300171530ustar00rootroot00000000000000# # 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.15.2/gdb/uftrace/mcount.py000066400000000000000000000067171455365734300173260ustar00rootroot00000000000000# # 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 rbtree, trigger, utils 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.15.2/gdb/uftrace/plthook.py000066400000000000000000000030751455365734300174730ustar00rootroot00000000000000# # 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 os import gdb from uftrace import lists, utils 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.15.2/gdb/uftrace/rbtree.py000066400000000000000000000217701455365734300173000ustar00rootroot00000000000000# # 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_color(node): """Return the color of a node. red -> 0 | black -> 1""" if node.address == 0: return 1 if node['rb_parent_color'] % 2 == 0: return 0 else: return 1 def rb_check(node, val_min=-1, val_max=-1, gdbtype=None, val_field="start"): if node.address == 0: return 1 # check order if gdbtype is not None: node_container = utils.container_of(node.address, gdbtype.pointer(), "node").dereference() val = int(node_container[val_field]) if val < val_min: gdb.write(f"node {node.address} is not ordered (val={val} < min={val_min})\n") return -1 if val > val_max and val_max != -1: # use -1 as infinity value for val_max gdb.write(f"node {node.address} is not ordered (val={val} > max={val_max})\n") return -1 else: val = -1 left = node['rb_left'].dereference() right = node['rb_right'].dereference() # check that a red node has black children if rb_color(node) == 0: if rb_color(left) == 0: gdb.write(f"red node {node.address} has red left child {left}\n") return -1 if rb_color(right) == 0: gdb.write(f"red node {node.address} has red right child {right}\n") return -1 # recursively check that paths to NULL leafs have as many black nodes left_black_count = rb_check(left, val_min, val, gdbtype) if left_black_count == -1: return -1 right_black_count = rb_check(right, val, val_max, gdbtype) if right_black_count == -1: return -1 if left_black_count != right_black_count: gdb.write(f"node @ {node.address}: {left_black_count} on left != {right_black_count} on right\n") return -1 else: black_count = left_black_count if rb_color(node) == 1: black_count += 1 return black_count class UftRbtreeCheck(gdb.Command): """Check if a rbtree has a valid structure. A red-black tree is a binary search tree with the following constraints: 1. Every node is either red or black 2. All NULL leafs are defined as black 3. A red node does not have a red child 4. Every path from a given node to any of its descendant NULL leafs goes through the same number of black nodes Source: https://wikipedia.org/wiki/Red%E2%80%93black_tree _ROOT_ / \ Legend: NODE NODE UPPERCASE: BLACK / \ / \ lowercase: red node NULL node NULL / \ / \ NULL NULL NULL NULL """ def __init__(self): super(UftRbtreeCheck, self).__init__("uft-rbtree-check", gdb.COMMAND_DATA) def invoke(self, arg, from_tty): argv = arg.split() if len(argv) == 0: gdb.write("Usage: uft-rbtree-check RBTREE [CONTAINER_TYPE]\n") return tr = utils.gdb_eval_or_none(argv[0]) if tr is None: gdb.write(f"{argv[0]} tree not found\n") return if len(argv) > 1: container_type = utils.CachedType(" ".join(argv[1:])) gdbtype = container_type.get_type() else: gdbtype = None gdb.write("[info] no container type given: skipping order check\n") node = tr['rb_node'].dereference() if rb_check(node, gdbtype=gdbtype) == -1: gdb.write(f"{arg} @ {node.address} is NOT a valid rbtree\n") else: gdb.write(f"{arg} @ {node.address} is a valid rbtree\n") UftRbtreeCheck() def rb_print(node, depth=0, gdbtype=None): if depth > 0: gdb.write(" |") gdb.write(f"{' |'*(depth-1)}") gdb.write("_") if node.address == 0: gdb.write("(b) NULL\n") return gdb.write(f"({'r' if rb_color(node) == 0 else 'b'}) {node.address} ") if gdbtype is not None: node_container = utils.container_of(node.address, gdbtype.pointer(), "node").dereference() gdb.write(f"{node_container}") else: gdb.write(f"{node}") gdb.write("\n") rb_print(node['rb_left'].dereference(), depth+1, gdbtype) rb_print(node['rb_right'].dereference(), depth+1, gdbtype) class UftRbtreePrint(gdb.Command): """Display a textual representation of an rbtree.""" def __init__(self): super(UftRbtreePrint, self).__init__("uft-rbtree-print", gdb.COMMAND_DATA) def invoke(self, arg, from_tty): argv = arg.split() if len(argv) == 0: gdb.write("Usage: uft-rbtree-print RBTREE [CONTAINER_TYPE]\n") return tr = utils.gdb_eval_or_none(argv[0]) if tr is None: gdb.write(f"{argv[0]} tree not found\n") return if len(argv) >= 2: container_type = utils.CachedType(" ".join(argv[1:])) gdbtype = container_type.get_type() else: gdbtype = None node = tr['rb_node'].dereference() gdb.write(f"{argv[0]}\n") rb_print(node, gdbtype=gdbtype) UftRbtreePrint() 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.15.2/gdb/uftrace/trigger.py000066400000000000000000000077401455365734300174610ustar00rootroot00000000000000# # 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 lists, utils 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.15.2/gdb/uftrace/utils.py000066400000000000000000000044551455365734300171560ustar00rootroot00000000000000# # 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(expression): try: return gdb.parse_and_eval(expression) 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.15.2/libmcount/000077500000000000000000000000001455365734300152435ustar00rootroot00000000000000uftrace-0.15.2/libmcount/agent.c000066400000000000000000000237331455365734300165150ustar00rootroot00000000000000#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/internal.h" #include "libmcount/mcount.h" #include "utils/socket.h" #include "utils/utils.h" /* agent thread */ static pthread_t agent; /* state flag for the agent */ static volatile bool agent_run = false; #define MCOUNT_AGENT_CAPABILITIES \ (UFTRACE_AGENT_OPT_TRACE | UFTRACE_AGENT_OPT_DEPTH | UFTRACE_AGENT_OPT_THRESHOLD | \ UFTRACE_AGENT_OPT_PATTERN | UFTRACE_AGENT_OPT_FILTER | UFTRACE_AGENT_OPT_CALLER | \ UFTRACE_AGENT_OPT_TRIGGER) /** * swap_triggers - atomically swap the pointer to a filter rbtree and free the * old one * @old - pointer to the tree to deprecate * @new - new version of the tree to use */ static void swap_triggers(struct uftrace_triggers_info **old, struct uftrace_triggers_info *new) { struct uftrace_triggers_info *tmp; tmp = __sync_val_compare_and_swap(old, *old, new); sleep(1); /* RCU-like grace period */ uftrace_cleanup_triggers(tmp); free(tmp); } /** * agent_setup_filter - update the registered filters from the agent * @filter_str - filters to add or remove * @triggers - rbtree of tracing filters */ static void agent_setup_filter(char *filter_str, struct uftrace_triggers_info *triggers) { uftrace_setup_filter(filter_str, &mcount_sym_info, triggers, &mcount_filter_setting); } /** * agent_setup_caller_filter - update the registered caller filters from the agent * @caller_str - caller filters to add or remove * @triggers - rbtree where the filters are stored */ static void agent_setup_caller_filter(char *caller_str, struct uftrace_triggers_info *triggers) { uftrace_setup_caller_filter(caller_str, &mcount_sym_info, triggers, &mcount_filter_setting); } /** * agent_setup_trigger - update the registered triggers from the agent * @trigger_str - trigger to add or remove * @triggers - rbtree of tracing filters */ static void agent_setup_trigger(char *trigger_str, struct uftrace_triggers_info *triggers) { uftrace_setup_trigger(trigger_str, &mcount_sym_info, triggers, &mcount_filter_setting); } /** * agent_init - initialize the agent * @addr - client socket * @return - socket file descriptor (-1 on error) */ static int agent_init(struct sockaddr_un *addr) { int sfd; if (mkdir(MCOUNT_AGENT_SOCKET_DIR, 0775) == -1) { if (errno != EEXIST) { pr_dbg("error creating run directory %s\n", MCOUNT_AGENT_SOCKET_DIR); return -1; } } sfd = agent_socket_create(addr, getpid()); if (sfd == -1) return sfd; if (access(addr->sun_path, F_OK) == 0) { pr_dbg("agent socket file already exists\n"); goto error; } if (agent_listen(sfd, addr) == -1) goto error; return sfd; error: close(sfd); return -1; } /** * agent_fini - finalize the agent thread execution * @addr - client socket * @sfd - client socket file descriptor */ static void agent_fini(struct sockaddr_un *addr, int sfd) { if (sfd != -1) close(sfd); socket_unlink(addr); pr_dbg("agent terminated\n"); } /** * agent_read_option - fetch option type and value from agent socket * @fd - socket file descriptor * @opt - option type * @value - option value * @read_size - size of data to read * @return - size of data read into @value */ static int agent_read_option(int fd, int *opt, void **value, size_t read_size) { size_t opt_size = sizeof(*opt); size_t value_size = read_size - opt_size; if (read_all(fd, opt, opt_size) < 0) return -1; *value = realloc(*value, value_size); if (!value) return -1; if (read_all(fd, *value, value_size) < 0) return -1; pr_dbg4("read agent option (size=%d)\n", read_size); return value_size; } /** * agent_apply_option - change libmcount parameters at runtime * @opt - option to apply * @value - value for the given option * @size - size of @value * @triggers - triggers definition and counters * @return - 0 on success, -1 on failure */ static int agent_apply_option(int opt, void *value, size_t size, struct uftrace_triggers_info *triggers) { struct uftrace_opts opts; int ret = 0; int trace; switch (opt) { case UFTRACE_AGENT_OPT_TRACE: trace = *((int *)value); if (mcount_enabled != trace) { mcount_enabled = trace; pr_dbg("turn trace %s\n", mcount_enabled ? "on" : "off"); } break; case UFTRACE_AGENT_OPT_DEPTH: opts.depth = *((int *)value); if (opts.depth != mcount_depth) { mcount_depth = opts.depth; pr_dbg3("dynamic depth: %d\n", mcount_depth); } else pr_dbg3("dynamic depth unchanged\n"); break; case UFTRACE_AGENT_OPT_THRESHOLD: opts.threshold = *((uint64_t *)value); if (opts.threshold != mcount_threshold) { mcount_threshold = opts.threshold; pr_dbg3("dynamic time threshold: %lu\n", mcount_threshold); } else pr_dbg3("dynamic time threshold unchanged\n"); break; case UFTRACE_AGENT_OPT_PATTERN: opts.patt_type = *((int *)value); if (opts.patt_type != mcount_filter_setting.ptype) { mcount_filter_setting.ptype = opts.patt_type; pr_dbg3("use pattern type %#x\n", opts.patt_type); } break; case UFTRACE_AGENT_OPT_FILTER: pr_dbg3("apply filter '%s' (size=%d)\n", value, size); agent_setup_filter(value, triggers); break; case UFTRACE_AGENT_OPT_CALLER: pr_dbg3("apply caller filter '%s' (size=%d)\n", value, size); agent_setup_caller_filter(value, triggers); break; case UFTRACE_AGENT_OPT_TRIGGER: pr_dbg3("apply trigger '%s' (size=%d)\n", value, size); agent_setup_trigger(value, triggers); break; default: ret = -1; } return ret; } static bool triggers_needs_copy(int opt) { bool ret; #define MATCHING_OPTIONS \ (UFTRACE_AGENT_OPT_FILTER | UFTRACE_AGENT_OPT_CALLER | UFTRACE_AGENT_OPT_TRIGGER) ret = opt & MATCHING_OPTIONS; #undef MATCHING_OPTIONS return ret; } /* Agent routine, applying instructions from the CLI. */ static void *agent_apply_commands(void *arg) { int sfd, cfd; /* socket fd, connection fd */ bool close_connection; struct uftrace_msg msg; struct sockaddr_un addr; void *value = NULL; size_t size; struct uftrace_triggers_info *triggers_copy = NULL; /* initialize agent */ sfd = agent_init(&addr); if (sfd == -1) { pr_warn("agent cannot start\n"); return NULL; } agent_run = true; pr_dbg("agent started on socket '%s'\n", addr.sun_path); /* handle incoming connections consecutively */ while (agent_run) { cfd = agent_accept(sfd); if (cfd == -1) { pr_dbg2("error accepting socket connection\n"); continue; } pr_dbg3("client connected\n"); /* read client messages */ close_connection = false; while (!close_connection) { int status = 0; int opt; /* read message header to get type */ if (agent_message_read_head(cfd, &msg) == -1) { status = EINVAL; pr_dbg3("error reading client message\n"); agent_message_send(cfd, UFTRACE_MSG_AGENT_ERR, &status, sizeof(status)); continue; } /* parse message body */ switch (msg.type) { case UFTRACE_MSG_AGENT_QUERY: status = MCOUNT_AGENT_CAPABILITIES; pr_dbg3("send capabilities to client\n"); agent_message_send(cfd, UFTRACE_MSG_AGENT_OK, &status, sizeof(status)); break; case UFTRACE_MSG_AGENT_SET_OPT: size = agent_read_option(cfd, &opt, &value, msg.len); if (status < 0) { status = EINVAL; agent_message_send(cfd, UFTRACE_MSG_AGENT_ERR, &status, sizeof(status)); break; } /* deep copy mcount_triggers for each connection (if needed) */ if (triggers_needs_copy(opt) && !triggers_copy) { triggers_copy = xmalloc(sizeof(*triggers_copy)); *triggers_copy = uftrace_deep_copy_triggers(mcount_triggers); } status = agent_apply_option(opt, value, size, triggers_copy); if (status == 0) agent_message_send(cfd, UFTRACE_MSG_AGENT_OK, NULL, 0); else agent_message_send(cfd, UFTRACE_MSG_AGENT_ERR, &status, sizeof(status)); break; case UFTRACE_MSG_AGENT_GET_OPT: /* TODO send data */ agent_message_send(cfd, UFTRACE_MSG_AGENT_OK, NULL, 0); break; case UFTRACE_MSG_AGENT_CLOSE: close_connection = true; agent_message_send(cfd, UFTRACE_MSG_AGENT_OK, NULL, 0); break; default: close_connection = true; pr_dbg3("agent message not recognized\n"); } } if (triggers_copy) { swap_triggers(&mcount_triggers, triggers_copy); triggers_copy = NULL; } if (close(cfd) == -1) pr_dbg3("error closing client socket\n"); else pr_dbg3("client disconnected\n"); } free(value); agent_fini(&addr, sfd); return 0; } int agent_spawn(void) { errno = pthread_create(&agent, NULL, &agent_apply_commands, NULL); if (errno != 0) { pr_warn("cannot start agent: %s\n", strerror((errno))); return -1; } return 0; } /* Check if the agent is up. If so, set its run flag to false, open and close * connection . */ int agent_kill(void) { int sfd; int status; struct sockaddr_un addr; struct uftrace_msg ack; if (!agent_run) return 0; agent_run = false; sfd = agent_socket_create(&addr, getpid()); if (sfd == -1) goto error; if (agent_connect(sfd, &addr) == -1) { if (errno != ENOENT) /* The agent may have ended and deleted the socket */ goto error; } status = agent_message_send(sfd, UFTRACE_MSG_AGENT_CLOSE, NULL, 0); if (status < 0) goto error; status = agent_message_read_response(sfd, &ack); if (status < 0 || ack.type != UFTRACE_MSG_AGENT_OK) goto error; close(sfd); if (pthread_join(agent, NULL) != 0) pr_dbg("agent left in unknown state\n"); return 0; error: pr_dbg2("error terminating agent routine\n"); close(sfd); socket_unlink(&addr); return -1; } #ifdef UNIT_TEST TEST_CASE(mcount_agent) { pr_dbg("starting the agent\n"); TEST_EQ(agent_spawn(), 0); do { usleep(1000); } while (!agent_run); pr_dbg("killing the agent\n"); TEST_EQ(agent_kill(), 0); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/libmcount/dynamic.c000066400000000000000000000535671455365734300170530ustar00rootroot00000000000000/* * INSTRUMENTED CODE LAYOUT * * Func offset | Instrumented code * -------------------------------- * 0x0 | Call Trampoline * 0x6 | nop * 0x7 | nop * * we must use starting address of function when * -. store original code to hashmap * -. find original code from hashmap * -. unpatch function */ #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "dynamic" #define PR_DOMAIN DBG_DYNAMIC #include "libmcount/dynamic.h" #include "libmcount/internal.h" #include "libmcount/mcount.h" #include "utils/filter.h" #include "utils/hashmap.h" #include "utils/list.h" #include "utils/rbtree.h" #include "utils/symbol.h" #include "utils/utils.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; bool frozen; }; static LIST_HEAD(code_pages); static struct Hashmap *code_hmap; /* minimum function size for dynamic update */ static unsigned min_size; /* disassembly engine for dynamic code patch (for capstone) */ static struct mcount_disasm_engine disasm; static struct mcount_orig_insn *create_code(struct Hashmap *map, unsigned long addr) { struct mcount_orig_insn *entry; entry = xmalloc(sizeof *entry); entry->addr = addr; if (hashmap_put(code_hmap, (void *)entry->addr, entry) == NULL) pr_err("code map allocation failed"); return entry; } static struct mcount_orig_insn *lookup_code(struct Hashmap *map, unsigned long addr) { struct mcount_orig_insn *entry; entry = hashmap_get(code_hmap, (void *)addr); return entry; } static struct code_page *alloc_codepage(void) { struct code_page *cp; cp = xzalloc(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"); list_add_tail(&cp->list, &code_pages); return cp; } void mcount_save_code(struct mcount_disasm_info *info, unsigned call_size, 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); int table_size = mcount_arch_branch_table_size(info); patch_size = ALIGN(copy_size + orig_size + table_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 = alloc_codepage(); } orig = create_code(code_hmap, info->addr + call_size); /* * if dynamic patch has been processed before, cp be frozen by * calling freeze_code. so, when reaching here from the * mcount_handle_dlopen, cp unwriteable. */ if (cp->frozen) { /* [Caution] * even if a little memory loss occurs, it can be dangerous * that to re-assigned write and execute permission to exist * codepage, so be sure to allocate new memory. */ cp = alloc_codepage(); } 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); mcount_arch_patch_branch(info, orig); } memcpy(orig->insn, info->insns, info->copy_size); memcpy(orig->insn + info->copy_size, jmp_insn, jmp_size); cp->pos += patch_size; } void mcount_freeze_code(void) { struct code_page *cp; list_for_each_entry(cp, &code_pages, list) { if (cp->frozen) continue; if (mprotect(cp->page, CODE_CHUNK, PROT_READ | PROT_EXEC) < 0) pr_err("mprotect to freeze code page failed"); cp->frozen = true; } } void *mcount_find_code(unsigned long addr) { struct mcount_orig_insn *orig; orig = lookup_code(code_hmap, addr); if (orig == NULL) return NULL; return orig->insn; } struct mcount_orig_insn *mcount_find_insn(unsigned long addr) { return lookup_code(code_hmap, addr); } static bool release_code(void *key, void *value, void *ctx) { hashmap_remove(code_hmap, key); free(value); return true; } /* not actually called for safety reason */ void mcount_release_code(void) { hashmap_for_each(code_hmap, release_code, NULL); hashmap_free(code_hmap); while (!list_empty(&code_pages)) { struct code_page *cp; cp = list_first_entry(&code_pages, struct code_page, list); list_del(&cp->list); munmap(cp->page, CODE_CHUNK); free(cp); } } /* 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 uftrace_symbol *sym, struct mcount_disasm_engine *disasm, unsigned min_size) { return -1; } __weak int mcount_unpatch_func(struct mcount_dynamic_info *mdi, struct uftrace_symbol *sym, struct mcount_disasm_engine *disasm) { return -1; } __weak void mcount_arch_find_module(struct mcount_dynamic_info *mdi, struct uftrace_symtab *symtab) { } __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) { } __weak int mcount_arch_branch_table_size(struct mcount_disasm_info *info) { return 0; } __weak void mcount_arch_patch_branch(struct mcount_disasm_info *info, struct mcount_orig_insn *orig) { } struct find_module_data { struct uftrace_sym_info *sinfo; bool needs_modules; }; static struct mcount_dynamic_info *create_mdi(struct dl_phdr_info *info) { struct mcount_dynamic_info *mdi; 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); return mdi; } /* 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 uftrace_sym_info *sym_info = fmd->sinfo; struct uftrace_mmap *map; bool is_executable = mcount_is_main_executable(info->dlpi_name, sym_info->filename); mdi = create_mdi(info); map = find_map(sym_info, 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 && is_executable; } static void prepare_dynamic_update(struct uftrace_sym_info *sinfo, bool needs_modules) { struct find_module_data fmd = { .sinfo = sinfo, .needs_modules = needs_modules, }; int hash_size = sinfo->exec_map->mod->symtab.nr_sym * 3 / 4; if (needs_modules) hash_size *= 2; code_hmap = hashmap_create(hash_size, hashmap_ptr_hash, hashmap_ptr_equals); 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; } static LIST_HEAD(patterns); struct patt_list { struct list_head list; struct uftrace_pattern patt; char *module; bool positive; }; static bool match_pattern_module(char *pathname) { struct patt_list *pl; bool ret = false; char *libname = basename(pathname); char *soname = get_soname(pathname); list_for_each_entry(pl, &patterns, list) { if (!strncmp(libname, pl->module, strlen(pl->module))) { ret = true; break; } if (soname && !strncmp(soname, pl->module, strlen(pl->module))) { ret = true; break; } } free(soname); return ret; } /** * match_pattern_list - match a symbol name against a pattern list * @map - memory map of the symbol * @soname - name of the module * @sym_name - name of the symbol * @return - -1 if match negative, 1 if match positive, 0 if no match */ static int match_pattern_list(struct uftrace_mmap *map, char *soname, char *sym_name) { struct patt_list *pl; int ret = 0; char *libname = basename(map->libname); list_for_each_entry(pl, &patterns, list) { int len = strlen(pl->module); if (strncmp(libname, pl->module, len) && (!soname || strncmp(soname, pl->module, len))) continue; if (match_filter_pattern(&pl->patt, sym_name)) ret = pl->positive ? 1 : -1; } return ret; } static void parse_pattern_list(char *patch_funcs, char *def_mod, enum uftrace_pattern_type ptype) { struct strv funcs = STRV_INIT; char *name; int j; struct patt_list *pl; 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; 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); } strv_free(&funcs); } static void release_pattern_list(void) { struct patt_list *pl, *tmp; list_for_each_entry_safe(pl, tmp, &patterns, list) { list_del(&pl->list); free_filter_pattern(&pl->patt); free(pl->module); free(pl); } } static bool skip_sym(struct uftrace_symbol *sym, struct mcount_dynamic_info *mdi, struct uftrace_mmap *map, char *soname) { /* skip special startup (csu) functions */ const char *csu_skip_syms[] = { "_start", "__libc_csu_init", "__libc_csu_fini", }; unsigned i; for (i = 0; i < ARRAY_SIZE(csu_skip_syms); i++) { if (!strcmp(sym->name, csu_skip_syms[i])) return true; } if (sym->type != ST_LOCAL_FUNC && sym->type != ST_GLOBAL_FUNC && sym->type != ST_WEAK_FUNC) return true; return false; } static void mcount_patch_func_with_stats(struct mcount_dynamic_info *mdi, struct uftrace_symbol *sym) { 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++; } static void patch_patchable_func_matched(struct mcount_dynamic_info *mdi, struct uftrace_mmap *map) { struct uftrace_symtab *symtab; unsigned long *patchable_loc = mdi->patch_target; unsigned i; struct uftrace_symbol *sym; char namebuf[BUFSIZ]; struct uftrace_symbol fake_sym = { .size = UINT_MAX, .name = namebuf, }; bool found = false; int match; char *soname = get_soname(map->libname); symtab = &map->mod->symtab; /* * If __patchable_function_entries is found, then apply patching * only to the target addresses found at the section. */ for (i = 0; i < mdi->nr_patch_target; i++) { uint64_t rel_addr = patchable_loc[i]; struct uftrace_symbol *searched_sym = find_sym(symtab, rel_addr); if (searched_sym == NULL) { sym = &fake_sym; sym->addr = rel_addr; snprintf(sym->name, sizeof(namebuf), "<%lx>", patchable_loc[i]); } else { sym = searched_sym; if (skip_sym(sym, mdi, map, soname)) continue; } found = true; match = match_pattern_list(map, soname, sym->name); if (!match) continue; else if (match == 1) mcount_patch_func_with_stats(mdi, sym); else mcount_unpatch_func(mdi, sym, NULL); } if (!found) stats.nomatch++; free(soname); } static void patch_normal_func_matched(struct mcount_dynamic_info *mdi, struct uftrace_mmap *map) { struct uftrace_symtab *symtab; unsigned i; struct uftrace_symbol *sym; bool found = false; int match; char *soname = get_soname(map->libname); symtab = &map->mod->symtab; for (i = 0; i < symtab->nr_sym; i++) { sym = &symtab->sym[i]; if (skip_sym(sym, mdi, map, soname)) continue; found = true; match = match_pattern_list(map, soname, sym->name); if (!match) continue; else if (match == 1) mcount_patch_func_with_stats(mdi, sym); else mcount_unpatch_func(mdi, sym, NULL); } if (!found) stats.nomatch++; free(soname); } static void patch_func_matched(struct mcount_dynamic_info *mdi, struct uftrace_mmap *map) { /* * In some cases, the __patchable_function_entries section can be * removed. For example, -Wl,--gc-sections strips this section. * In this case, we try patching in patch_normal_func_matched() by * looping over all the symbols available and check if the function * begins with NOP patterns for patchable function entry. */ if (mdi->type == DYNAMIC_PATCHABLE) patch_patchable_func_matched(mdi, map); else patch_normal_func_matched(mdi, map); } static int do_dynamic_update(struct uftrace_sym_info *sinfo, char *patch_funcs, enum uftrace_pattern_type ptype) { struct uftrace_mmap *map; char *def_mod; if (patch_funcs == NULL) return 0; def_mod = basename(sinfo->exec_map->libname); parse_pattern_list(patch_funcs, def_mod, ptype); for_each_map(sinfo, map) { struct mcount_dynamic_info *mdi; /* TODO: filter out unsuppported libs */ mdi = setup_trampoline(map); if (mdi == NULL) continue; patch_func_matched(mdi, map); } if (stats.failed + stats.skipped + stats.nomatch == 0) { pr_dbg("patched all (%d) functions in '%s'\n", stats.total, basename(sinfo->filename)); } return 0; } static void freeze_dynamic_update(void) { 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_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 uftrace_sym_info *sinfo, char *patch_funcs, enum uftrace_pattern_type ptype) { int ret = 0; char *size_filter; bool needs_modules = !!strchr(patch_funcs, '@'); mcount_disasm_init(&disasm); prepare_dynamic_update(sinfo, needs_modules); size_filter = getenv("UFTRACE_MIN_SIZE"); if (size_filter != NULL) min_size = strtoul(size_filter, NULL, 0); ret = do_dynamic_update(sinfo, patch_funcs, ptype); if (stats.total && (stats.failed || stats.skipped)) { int success = stats.total - stats.failed - stats.skipped; int r, q; pr_dbg("dynamic patch stats for '%s'\n", basename(sinfo->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); } freeze_dynamic_update(); return ret; } void mcount_dynamic_dlopen(struct uftrace_sym_info *sinfo, struct dl_phdr_info *info, char *pathname) { struct mcount_dynamic_info *mdi; struct uftrace_mmap *map; if (!match_pattern_module(pathname)) return; mdi = create_mdi(info); map = xmalloc(sizeof(*map) + strlen(pathname) + 1); map->start = info->dlpi_addr; map->end = map->start + mdi->text_size; map->len = strlen(pathname); strcpy(map->libname, pathname); mcount_memcpy1(map->prot, "r-xp", 4); read_build_id(pathname, map->build_id, sizeof(map->build_id)); map->next = sinfo->maps; sinfo->maps = map; mdi->map = map; map->mod = load_module_symtab(sinfo, map->libname, map->build_id); mcount_arch_find_module(mdi, &map->mod->symtab); if (mcount_setup_trampoline(mdi) < 0) { pr_dbg("setup trampoline to %s failed\n", map->libname); free(mdi); return; } patch_func_matched(mdi, map); mcount_arch_dynamic_recover(mdi, &disasm); mcount_cleanup_trampoline(mdi); free(mdi); mcount_freeze_code(); } void mcount_dynamic_finish(void) { release_pattern_list(); mcount_disasm_finish(&disasm); } struct dynamic_bad_symbol *mcount_find_badsym(struct mcount_dynamic_info *mdi, unsigned long addr) { struct uftrace_symbol *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 uftrace_symbol *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; } void mcount_free_badsym(struct mcount_dynamic_info *mdi) { struct dynamic_bad_symbol *badsym, *tmp; list_for_each_entry_safe(badsym, tmp, &mdi->bad_syms, list) { list_del(&badsym->list); free(badsym); } } #ifdef UNIT_TEST TEST_CASE(dynamic_find_code) { struct mcount_disasm_info info1 = { .addr = 0x1000, .insns = { 0xaa, 0xbb, 0xcc, 0xdd, }, .orig_size = 4, .copy_size = 4, }; struct mcount_disasm_info info2 = { .addr = 0x2000, .insns = { 0xf1, 0xf2, 0xcc, 0xdd, }, .orig_size = 2, .copy_size = 4, }; uint8_t jmp_insn[] = { 0xcc }; uint8_t *insn; pr_dbg("create hash map to search code\n"); code_hmap = hashmap_create(4, hashmap_ptr_hash, hashmap_ptr_equals); pr_dbg("save fake code to the hash\n"); mcount_save_code(&info1, 0, jmp_insn, sizeof(jmp_insn)); mcount_save_code(&info2, 0, jmp_insn, sizeof(jmp_insn)); pr_dbg("freeze the code page\n"); mcount_freeze_code(); pr_dbg("finding the first code\n"); insn = mcount_find_code(info1.addr); TEST_NE(insn, NULL); TEST_MEMEQ(insn, info1.insns, info1.orig_size); pr_dbg("finding the second code\n"); insn = mcount_find_code(info2.addr); TEST_NE(insn, NULL); TEST_MEMEQ(insn, info2.insns, info2.orig_size); pr_dbg("release the code page and hash\n"); mcount_release_code(); return TEST_OK; } TEST_CASE(dynamic_pattern_list) { struct uftrace_mmap *main_map, *other_map; main_map = xzalloc(sizeof(*main_map) + 16); strcpy(main_map->libname, "main"); other_map = xzalloc(sizeof(*other_map) + 16); strcpy(other_map->libname, "other"); pr_dbg("check simple match with default module\n"); parse_pattern_list("abc;!def", "main", PATT_SIMPLE); TEST_EQ(match_pattern_list(main_map, NULL, "abc"), 1); TEST_EQ(match_pattern_list(main_map, NULL, "def"), -1); TEST_EQ(match_pattern_list(other_map, NULL, "xyz"), 0); release_pattern_list(); pr_dbg("check negative regex match with default module\n"); parse_pattern_list("!^a", "main", PATT_REGEX); TEST_EQ(match_pattern_list(main_map, NULL, "abc"), -1); TEST_EQ(match_pattern_list(main_map, NULL, "def"), 0); TEST_EQ(match_pattern_list(other_map, NULL, "xyz"), 0); release_pattern_list(); pr_dbg("check wildcard match with other module\n"); parse_pattern_list("*@other", "main", PATT_GLOB); TEST_EQ(match_pattern_list(main_map, NULL, "abc"), 0); TEST_EQ(match_pattern_list(main_map, NULL, "def"), 0); TEST_EQ(match_pattern_list(other_map, NULL, "xyz"), 1); release_pattern_list(); free(main_map); free(other_map); return TEST_OK; } struct test_map_data { struct mcount_dynamic_info *mdi; }; /* callback for dl_iterate_phdr() */ static int setup_test_map(struct dl_phdr_info *info, size_t sz, void *data) { struct test_map_data *tmd = data; struct mcount_dynamic_info *mdi; struct uftrace_mmap *map; struct uftrace_module *mod; static struct uftrace_symbol syms[] = { { 0x100, 0x100, ST_LOCAL_FUNC, "a" }, { 0x200, 0x100, ST_LOCAL_FUNC, "b" }, { 0x300, 0x100, ST_LOCAL_FUNC, "c" }, }; mdi = create_mdi(info); map = xzalloc(sizeof(*map) + 16); map->start = info->dlpi_addr; map->end = map->start + 0x1000; strcpy(map->libname, "main"); map->len = strlen(map->libname); mcount_memcpy1(map->prot, "r-xp", 4); mod = xzalloc(sizeof(*mod) + 16); strcpy(mod->name, "main"); mod->symtab.sym = syms; mod->symtab.nr_sym = ARRAY_SIZE(syms); map->mod = mod; mdi->map = map; tmd->mdi = mdi; return 1; }; static void cleanup_test_map(struct mcount_dynamic_info *mdi) { struct uftrace_mmap *map = mdi->map; struct uftrace_module *mod = map->mod; free(mod); free(map); free(mdi); } TEST_CASE(dynamic_find_badsym) { struct test_map_data tmd = {}; unsigned long base; pr_dbg("setup test map info\n"); dl_iterate_phdr(setup_test_map, &tmd); base = tmd.mdi->map->start; pr_dbg("adding bad symbols\n"); TEST_EQ(mcount_add_badsym(tmd.mdi, 0, base + 0x100), false); TEST_EQ(mcount_add_badsym(tmd.mdi, 0, base + 0x234), true); pr_dbg("finding bad symbols\n"); TEST_EQ(mcount_find_badsym(tmd.mdi, base + 0x100), NULL); TEST_NE(mcount_find_badsym(tmd.mdi, base + 0x234), NULL); /* found */ TEST_EQ(mcount_find_badsym(tmd.mdi, base + 0x369), NULL); pr_dbg("cleanup test map info\n"); mcount_free_badsym(tmd.mdi); cleanup_test_map(tmd.mdi); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/libmcount/dynamic.h000066400000000000000000000103051455365734300170370ustar00rootroot00000000000000#ifndef UFTRACE_MCOUNT_DYNAMIC_H #define UFTRACE_MCOUNT_DYNAMIC_H #include #include #ifdef HAVE_LIBCAPSTONE #include #endif #include "utils/symbol.h" #ifndef PAGE_SIZE #define PAGE_SIZE 4096 #endif #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 PATCHABLE_SECT "__patchable_function_entries" /* 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 { uint64_t address; uint64_t function; uint8_t kind; uint8_t always_instrument; uint8_t version; uint8_t padding[13]; }; enum mcount_dynamic_type { DYNAMIC_NONE, DYNAMIC_PG, DYNAMIC_FENTRY, DYNAMIC_FENTRY_NOP, DYNAMIC_XRAY, DYNAMIC_PATCHABLE, }; __maybe_unused static const char *mdi_type_names[] = { "none", "pg", "fentry", "fentry-nop", "xray", "fpatchable", }; 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; enum mcount_dynamic_type type; void *patch_target; unsigned nr_patch_target; }; struct mcount_disasm_engine { #ifdef HAVE_LIBCAPSTONE csh engine; #endif }; #define INSTRUMENT_SUCCESS 0 #define INSTRUMENT_FAILED -1 #define INSTRUMENT_SKIPPED -2 /* * Supposing the size of smallest conditional branch is 2 byte. * We can replace, at most, 3 of them by the instrumentation * instruction. */ #define MAX_COND_BRANCH 3 int mcount_dynamic_update(struct uftrace_sym_info *sinfo, char *patch_funcs, enum uftrace_pattern_type ptype); void mcount_dynamic_dlopen(struct uftrace_sym_info *sinfo, struct dl_phdr_info *info, char *path); void mcount_dynamic_finish(void); struct mcount_orig_insn { struct rb_node node; unsigned long addr; void *orig; void *insn; int orig_size; int insn_size; }; struct cond_branch_info { /* where the insn starts in the out-of-line exec buffer*/ unsigned long insn_index; /* the original target address of the branch */ unsigned long branch_target; unsigned long insn_addr; unsigned long 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 uftrace_symbol *sym; unsigned long addr; unsigned char insns[64]; int orig_size; int copy_size; bool modified; bool has_jump; bool has_intel_cet; uint8_t nr_branch; struct cond_branch_info branch_info[MAX_COND_BRANCH]; }; void mcount_save_code(struct mcount_disasm_info *info, unsigned call_size, 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 uftrace_symbol *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); int mcount_arch_branch_table_size(struct mcount_disasm_info *info); void mcount_arch_patch_branch(struct mcount_disasm_info *info, struct mcount_orig_insn *orig); struct dynamic_bad_symbol { struct list_head list; struct uftrace_symbol *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); void mcount_free_badsym(struct mcount_dynamic_info *mdi); #endif /* UFTRACE_MCOUNT_DYNAMIC_H */ uftrace-0.15.2/libmcount/event.c000066400000000000000000000132051455365734300165310ustar00rootroot00000000000000#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/internal.h" #include "libmcount/mcount.h" #include "utils/filter.h" #include "utils/list.h" #include "utils/utils.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); } } #ifdef UNIT_TEST TEST_CASE(mcount_list_event) { pr_dbg("checking event list\n"); mcount_list_events(); TEST_EQ(mcount_lookup_event(0x123), NULL); mcount_finish_events(); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/libmcount/internal.h000066400000000000000000000304411455365734300172320ustar00rootroot00000000000000/* * 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 #include #ifdef HAVE_LIBCAPSTONE #include #endif #include "mcount-arch.h" #include "uftrace.h" #include "utils/compiler.h" #include "utils/filter.h" #include "utils/rbtree.h" #include "utils/symbol.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 #define FILTER_NO_MAX_DEPTH 0xffff #define FILTER_NO_TIME 0xffffffffffffffff struct filter_control { int in_count; int out_count; uint16_t depth; uint16_t saved_depth; uint16_t max_depth; uint16_t saved_max_depth; uint64_t time; uint64_t saved_time; unsigned size; unsigned saved_size; }; #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 unsigned mcount_minsize; extern pthread_key_t mtd_key; extern int shmem_bufsize; extern int pfd; extern int mcount_depth; extern char *mcount_exename; extern int page_size_in_kb; extern bool kernel_pid_update; extern bool mcount_auto_recover; extern bool mcount_estimate_return; extern bool mcount_enabled; extern struct uftrace_sym_info mcount_sym_info; extern struct uftrace_filter_setting mcount_filter_setting; extern struct uftrace_triggers_info *mcount_triggers; 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_source, &ts); return (uint64_t)ts.tv_sec * NSEC_PER_SEC + ts.tv_nsec; } static inline unsigned mcount_getsize(struct uftrace_sym_info *sinfo, uint64_t addr) { struct uftrace_symbol *sym; sym = find_symtabs(sinfo, addr); if (sym != NULL) return sym->size; return 0; } 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 uftrace_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; extern const char *const noplt_skip_syms[]; extern size_t noplt_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 struct uftrace_mmap *new_map(const char *path, uint64_t start, uint64_t end, const char *prot); extern void record_proc_maps(char *dirname, const char *sess_id, struct uftrace_sym_info *sinfo); extern void mcount_rstack_inject_return(struct mcount_thread_data *mtdp, unsigned long *frame_pointer, unsigned long addr); extern const char *uftrace_basename(const char *pathname); #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_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); bool mcount_is_main_executable(const char *filename, const char *exename); int agent_spawn(void); int agent_kill(void); #endif /* UFTRACE_MCOUNT_INTERNAL_H */ uftrace-0.15.2/libmcount/mcount-nop.c000066400000000000000000000013531455365734300175100ustar00rootroot00000000000000/* * 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.15.2/libmcount/mcount.c000066400000000000000000001531041455365734300167200ustar00rootroot00000000000000/* * 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/dynamic.h" #include "libmcount/internal.h" #include "libmcount/mcount.h" #include "mcount-arch.h" #include "utils/filter.h" #include "utils/script.h" #include "utils/socket.h" #include "utils/symbol.h" #include "utils/utils.h" #include "version.h" /* time filter in nsec */ uint64_t mcount_threshold; /* size filter */ unsigned mcount_min_size; /* symbol info for current process */ struct uftrace_sym_info mcount_sym_info = { .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 */ int __maybe_unused mcount_depth = MCOUNT_DEFAULT_DEPTH; /* setting for all filter actions */ struct uftrace_filter_setting mcount_filter_setting = { .ptype = PATT_REGEX, .auto_args = false, .allow_kernel = false, }; /* boolean flag to turn on/off recording */ bool __maybe_unused mcount_enabled = true; /* triggers definition and counters */ struct uftrace_triggers_info __maybe_unused *mcount_triggers; /* bitmask of active watch points */ static unsigned long __maybe_unused mcount_watchpoints; /* address of function will be called when a function returns */ unsigned long mcount_return_fn; /* do not hook return address and inject EXIT record between functions */ bool mcount_estimate_return; __weak void dynamic_return(void) { } #ifdef DISABLE_MCOUNT_FILTER static void mcount_filter_init(struct uftrace_filter_setting *filter_setting, bool force) { if (getenv("UFTRACE_SRCLINE") == NULL) return; load_module_symtabs(&mcount_sym_info); /* use debug info if available */ prepare_debug_info(&mcount_sym_info, filter_setting->ptype, NULL, NULL, false, force); save_debug_info(&mcount_sym_info, mcount_sym_info.dirname); } static void mcount_filter_finish(void) { finish_debug_info(&mcount_sym_info); } #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(); } } /* clang-format off */ #define SIGTABLE_ENTRY(s) { #s, s } /* clang-format on */ 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(struct uftrace_filter_setting *filter_setting, 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"); char *loc_str = getenv("UFTRACE_LOCATION"); bool needs_debug_info = false; filter_setting->lp64 = host_is_lp64(); filter_setting->arch = host_cpu_arch(); load_module_symtabs(&mcount_sym_info); 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(&mcount_sym_info, filter_setting->ptype, argument_str, retval_str, !!autoargs_str, force); save_debug_info(&mcount_sym_info, mcount_sym_info.dirname); } mcount_triggers = xmalloc(sizeof(*mcount_triggers)); memset(mcount_triggers, 0, sizeof(*mcount_triggers)); mcount_triggers->root = RB_ROOT; uftrace_setup_filter(filter_str, &mcount_sym_info, mcount_triggers, filter_setting); uftrace_setup_trigger(trigger_str, &mcount_sym_info, mcount_triggers, filter_setting); uftrace_setup_argument(argument_str, &mcount_sym_info, mcount_triggers, filter_setting); uftrace_setup_retval(retval_str, &mcount_sym_info, mcount_triggers, filter_setting); if (needs_debug_info) { uftrace_setup_loc_filter(loc_str, &mcount_sym_info, mcount_triggers, filter_setting); } if (caller_str) { uftrace_setup_caller_filter(caller_str, &mcount_sym_info, mcount_triggers, filter_setting); } if (autoargs_str) { char *autoarg = "."; char *autoret = "."; if (filter_setting->ptype == PATT_GLOB) autoarg = autoret = "*"; filter_setting->auto_args = true; uftrace_setup_argument(autoarg, &mcount_sym_info, mcount_triggers, filter_setting); uftrace_setup_retval(autoret, &mcount_sym_info, mcount_triggers, filter_setting); } if (getenv("UFTRACE_DEPTH")) mcount_depth = strtol(getenv("UFTRACE_DEPTH"), NULL, 0); if (getenv("UFTRACE_TRACE_OFF")) mcount_enabled = false; } static void mcount_filter_setup(struct mcount_thread_data *mtdp) { mtdp->filter.max_depth = FILTER_NO_MAX_DEPTH; mtdp->filter.depth = 0; mtdp->filter.time = FILTER_NO_TIME; mtdp->filter.size = mcount_min_size; 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_triggers(mcount_triggers); free(mcount_triggers); finish_auto_args(); finish_debug_info(&mcount_sym_info); 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); } static void mcount_rstack_estimate_finish(struct mcount_thread_data *mtdp) { uint64_t ret_time = mcount_gettime(); pr_dbg2("generates EXIT records for task %d (idx = %d)\n", mcount_gettid(mtdp), mtdp->idx); while (mtdp->idx > 0) { mtdp->idx--; ret_time++; /* add fake exit records */ mtdp->rstack[mtdp->idx].end_time = ret_time; mcount_exit_filter_record(mtdp, &mtdp->rstack[mtdp->idx], NULL); } } /* 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; if (mcount_estimate_return) mcount_rstack_estimate_finish(mtdp); 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); } if (!mcount_estimate_return) { pr_warn(" if this happens only with uftrace," " please consider -e/--estimate-return option.\n\n"); } pr_warn("Backtrace from uftrace " UFTRACE_VERSION "\n"); pr_warn("=====================================\n"); while (rstack >= mtdp->rstack) { struct uftrace_symbol *parent, *child; char *pname, *cname; parent = find_symtabs(&mcount_sym_info, rstack->parent_ip); pname = symbol_getname(parent, rstack->parent_ip); child = find_symtabs(&mcount_sym_info, 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--; } pr_out("\n"); pr_red(BUG_REPORT_MSG); 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); if (mcount_estimate_return) { struct mcount_thread_data *mtdp = get_thread_data(); if (!check_thread_data(mtdp)) mcount_rstack_estimate_finish(mtdp); } 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 *); /** * mcount_get_filter_mode - compute the filter mode from the filter count */ static inline enum filter_mode mcount_get_filter_mode(void) { return mcount_triggers->filter_count > 0 ? FILTER_MODE_IN : FILTER_MODE_OUT; } /** * mcount_get_loc_mode - compute the location filter mode from the location count */ static inline enum filter_mode mcount_get_loc_mode(void) { return mcount_triggers->loc_count > 0 ? FILTER_MODE_IN : FILTER_MODE_OUT; } static void mcount_save_filter(struct mcount_thread_data *mtdp) { /* save original depth and time to restore at exit time */ mtdp->filter.saved_depth = mtdp->filter.depth; mtdp->filter.saved_max_depth = mtdp->filter.max_depth; mtdp->filter.saved_time = mtdp->filter.time; mtdp->filter.saved_size = mtdp->filter.size; } /* 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) { int max_depth = mtdp->filter.max_depth; if (max_depth == FILTER_NO_MAX_DEPTH) max_depth = mcount_depth; pr_dbg3("<%d> enter %lx\n", mtdp->idx, child); if (mcount_check_rstack(mtdp)) return FILTER_RSTACK; mcount_save_filter(mtdp); /* already filtered by notrace option */ if (mtdp->filter.out_count > 0) return FILTER_OUT; uftrace_match_filter(child, &mcount_triggers->root, 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 = 0; } else { /* not matched by filter */ if (mcount_get_filter_mode() == FILTER_MODE_IN && mtdp->filter.in_count == 0) return FILTER_OUT; } if (tr->flags & TRIGGER_FL_LOC) { if (tr->lmode == FILTER_MODE_OUT) return FILTER_OUT; } else { if (mcount_get_loc_mode() == FILTER_MODE_IN) return FILTER_OUT; } #define FLAGS_TO_CHECK \ (TRIGGER_FL_DEPTH | TRIGGER_FL_TRACE_ON | TRIGGER_FL_TRACE_OFF | TRIGGER_FL_TIME_FILTER | \ TRIGGER_FL_SIZE_FILTER) if (tr->flags & FLAGS_TO_CHECK) { if (tr->flags & TRIGGER_FL_DEPTH) { mtdp->filter.depth = 0; mtdp->filter.max_depth = max_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; if (tr->flags & TRIGGER_FL_SIZE_FILTER) mtdp->filter.size = tr->size; } #undef FLAGS_TO_CHECK if (mtdp->filter.depth >= max_depth) 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 uftrace_symbol *sym = find_symtabs(&mcount_sym_info, 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 uftrace_symbol *sym = find_symtabs(&mcount_sym_info, 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); } /** * filter_save_to_rstack - save current filter state to rstack * @mtdp - thread data * * The current values can be overwritten by triggers, and will be restored from * @rstack at function exit. */ static void filter_save_to_rstack(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack) { rstack->filter_depth = mtdp->filter.saved_depth; rstack->filter_max_depth = mtdp->filter.saved_max_depth; rstack->filter_time = mtdp->filter.saved_time; rstack->filter_size = mtdp->filter.saved_size; } 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_get_filter_mode() == FILTER_MODE_IN) || (mtdp->filter.size > 0 && mcount_getsize(&mcount_sym_info, rstack->child_ip) < mtdp->filter.size)) rstack->flags |= MCOUNT_FL_NORECORD; filter_save_to_rstack(mtdp, rstack); #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 } /** * filter_restore_from_rstack - restore filters to their value at function entry * @mtdp - thread data */ static void filter_restore_from_rstack(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack) { mtdp->filter.depth = rstack->filter_depth; mtdp->filter.max_depth = rstack->filter_max_depth; mtdp->filter.time = rstack->filter_time; mtdp->filter.size = rstack->filter_size; } void mcount_exit_filter_record(struct mcount_thread_data *mtdp, struct mcount_ret_stack *rstack, long *retval) { uint64_t time_filter = mtdp->filter.time; if (time_filter == FILTER_NO_TIME) time_filter = mcount_threshold; 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 filter_restore_from_rstack(mtdp, rstack); 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->root, &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_triggers->caller_count || 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; if (mcount_min_size > 0 && mcount_getsize(&mcount_sym_info, child) < mcount_min_size) return FILTER_OUT; 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"); } } static void mcount_save_filter(struct mcount_thread_data *mtdp) { } #endif /* DISABLE_MCOUNT_FILTER */ #ifndef FIX_PARENT_LOC static inline unsigned long *mcount_arch_parent_location(struct uftrace_sym_info *sinfo, unsigned long *parent_loc, unsigned long child_ip) { return parent_loc; } #endif bool within_same_module(unsigned long addr1, unsigned long addr2) { return find_map(&mcount_sym_info, addr1) == find_map(&mcount_sym_info, addr2); } void mcount_rstack_inject_return(struct mcount_thread_data *mtdp, unsigned long *frame_pointer, unsigned long addr) { uint64_t estimated_ret_time = 0; if (mtdp->idx > 0) { int idx = mtdp->idx - 1; /* * NOTE: we don't know the exact return time. * estimate it as a half of delta from the previous start. */ estimated_ret_time = mcount_gettime(); estimated_ret_time += mtdp->rstack[idx].start_time; estimated_ret_time /= 2; /* * if previous symbol is a PLT function, and this one came * from same module, we assume these two are siblings and * use same depth even if it has a lower frame pointer. */ if (mtdp->rstack[idx].dyn_idx != MCOUNT_INVALID_DYNIDX && mtdp->rstack[idx].parent_loc > frame_pointer && within_same_module(mtdp->rstack[idx].child_ip, addr)) { /* add a fake exit record for the PLT func */ mtdp->rstack[idx].end_time = estimated_ret_time; mcount_exit_filter_record(mtdp, &mtdp->rstack[idx], NULL); /* make it have a same depth */ mtdp->idx--; mtdp->record_idx = mtdp->idx; mcount_save_filter(mtdp); return; } } while (mtdp->idx > 0) { int below = mtdp->idx - 1; if (mtdp->rstack[below].parent_loc == &mtdp->cygprof_dummy) break; if (mtdp->rstack[below].parent_loc > frame_pointer) break; /* add fake exit records */ mtdp->rstack[below].end_time = estimated_ret_time; mcount_exit_filter_record(mtdp, &mtdp->rstack[below], NULL); mtdp->idx--; estimated_ret_time++; } mtdp->record_idx = mtdp->idx; mcount_save_filter(mtdp); } 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(&mcount_sym_info, parent_loc, child); if (mcount_estimate_return) mcount_rstack_inject_return(mtdp, 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; if (!mcount_estimate_return) { /* 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; } if (mcount_estimate_return) mcount_rstack_inject_return(mtdp, (void *)~0UL, child); /* * 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 = MCOUNT_FL_CYGPROF; } else { rstack->start_time = 0; rstack->flags = MCOUNT_FL_CYGPROF | 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 warn_unpaired_cygprof(void) { pr_warn("unpaired cygprof exit: dropping...\n"); } 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]; /* discard unpaired cygprof exit (due to compiler bug?) */ if (unlikely(!(rstack->flags & MCOUNT_FL_CYGPROF))) { static pthread_once_t warn_once = PTHREAD_ONCE_INIT; pthread_once(&warn_once, warn_unpaired_cygprof); mcount_unguard_recursion(mtdp); return; } 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; } if (mcount_estimate_return) mcount_rstack_inject_return(mtdp, (void *)~0UL, child); /* '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 *channel = NULL; char *logfd_str; char *debug_str; char *bufsize_str; char *maxstack_str; char *threshold_str; char *minsize_str; char *color_str; char *demangle_str; char *plthook_str; char *patch_str; char *event_str; char *dirname; char *pattern_str; char *clock_str; char *symdir_str; struct stat statbuf; bool nest_libcall; 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"); 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"); minsize_str = getenv("UFTRACE_MIN_SIZE"); 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"); clock_str = getenv("UFTRACE_CLOCK"); symdir_str = getenv("UFTRACE_SYMBOL_DIR"); 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; 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(); mcount_sym_info.dirname = dirname; mcount_sym_info.symdir = symdir_str ?: dirname; mcount_sym_info.filename = mcount_exename; if (symdir_str) mcount_sym_info.flags |= SYMTAB_FL_USE_SYMFILE | SYMTAB_FL_SYMS_DIR; record_proc_maps(dirname, mcount_session_name(), &mcount_sym_info); if (pattern_str) mcount_filter_setting.ptype = 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(&mcount_filter_setting, !!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 (minsize_str) mcount_min_size = strtoul(minsize_str, NULL, 0); if (patch_str) mcount_dynamic_update(&mcount_sym_info, patch_str, mcount_filter_setting.ptype); if (event_str) mcount_setup_events(dirname, event_str, mcount_filter_setting.ptype); if (getenv("UFTRACE_KERNEL_PID_UPDATE")) kernel_pid_update = true; if (getenv("UFTRACE_ESTIMATE_RETURN")) mcount_estimate_return = true; if (plthook_str) { /* PLT hook depends on mcount_estimate_return */ mcount_setup_plthook(mcount_exename, nest_libcall); } if (clock_str) setup_clock_id(clock_str); if (getenv("UFTRACE_AGENT")) agent_spawn(); pthread_atfork(atfork_prepare_handler, NULL, atfork_child_handler); mcount_hook_functions(); /* initialize script binding */ if (SCRIPT_ENABLED && script_str) mcount_script_init(mcount_filter_setting.ptype); compiler_barrier(); pr_dbg("mcount setup done\n"); mcount_global_flags &= ~MCOUNT_GFL_SETUP; mtd.recursion_marker = false; } static void mcount_cleanup(void) { agent_kill(); mcount_finish(); destroy_dynsym_indexes(); mcount_dynamic_finish(); #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); bool mcount_is_main_executable(const char *filename, const char *exename) { /* on Linux main executable has empty name whereas on Android we need to compare with exename */ char filename_canonized[PATH_MAX]; char exename_canonized[PATH_MAX]; if (!*filename) return true; if (realpath(filename, filename_canonized) && realpath(exename, exename_canonized)) { return strcmp(filename_canonized, exename_canonized) == 0; } return false; } #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 */ #include static void setup_mcount_test(void) { pr_dbg("init libmcount for testing\n"); mcount_exename = read_exename(); pthread_key_create(&mtd_key, mtd_dtor); mcount_global_flags = 0; mcount_triggers = xmalloc(sizeof(*mcount_triggers)); memset(mcount_triggers, 0, sizeof(*mcount_triggers)); mcount_triggers->root = RB_ROOT; } #define SHMEM_SESSION_FMT "/uftrace-%s-%d-%03d" static void cleanup_thread_data(struct mcount_thread_data *mtdp) { char shm_id[128]; int tid = mcount_gettid(mtdp); int idx; shmem_finish(mtdp); for (idx = 0; idx < 2; idx++) { snprintf(shm_id, sizeof(shm_id), SHMEM_SESSION_FMT, mcount_session_name(), tid, idx); shm_unlink(shm_id); } } TEST_CASE(mcount_thread_data) { struct mcount_thread_data *mtdp; setup_mcount_test(); pr_dbg("try to get thread data - should fail\n"); mtdp = get_thread_data(); TEST_EQ(check_thread_data(mtdp), true); pr_dbg("mcount_prepare() should setup the thread data\n"); mtdp = mcount_prepare(); TEST_EQ(check_thread_data(mtdp), false); TEST_EQ(get_thread_data(), mtdp); TEST_EQ(check_thread_data(mtdp), false); cleanup_thread_data(mtdp); 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; } struct fake_rstack { unsigned long *frame_pointer; unsigned long func_addr; }; TEST_CASE(mcount_estimate_return_depth) { /* dummy frame pointer values - just to check relative values */ unsigned long frame_pointers[8]; /* increase idx/depth when frame pointer goes down */ struct fake_rstack test_scenario[] = { { &frame_pointers[7], 0x1234 }, { &frame_pointers[4], 0x1234 }, { &frame_pointers[0], 0x1234 }, { &frame_pointers[4], 0x1234 }, { &frame_pointers[5], 0x1234 }, }; /* mtdp->idx increased after mcount_entry() */ int depth_check[] = { 0, 1, 2, 1, 1 }; struct mcount_thread_data *mtdp; unsigned i; setup_mcount_test(); mtdp = mcount_prepare(); /* mcount_prepare calls mcount_guard_recursion() internally */ mcount_unguard_recursion(mtdp); mcount_estimate_return = true; for (i = 0; i < ARRAY_SIZE(test_scenario); i++) { TEST_EQ(mcount_entry(test_scenario[i].frame_pointer, test_scenario[i].func_addr, NULL), 0); pr_dbg("[%d] mcount entry: idx = %d, depth = %d\n", i, mtdp->idx, mtdp->rstack[mtdp->idx - 1].depth); TEST_EQ(mtdp->idx, depth_check[i] + 1); TEST_EQ(mtdp->rstack[mtdp->idx - 1].depth, depth_check[i]); } cleanup_thread_data(mtdp); mcount_cleanup(); return TEST_OK; } #define TESTDIR_NAME "testdir" TEST_CASE(mcount_setup) { setenv("UFTRACE_DIR", TESTDIR_NAME, 1); setenv("UFTRACE_FILTER", "mcount.*_init", 1); setenv("UFTRACE_ESTIMATE_RETURN", "1", 1); create_directory(TESTDIR_NAME); TEST_EQ(mcount_global_flags, MCOUNT_GFL_SETUP); TEST_EQ(mcount_return_fn, 0); /* just to detect sanitizer failures */ mcount_startup(); TEST_EQ(mcount_global_flags, 0); TEST_EQ(mcount_estimate_return, true); TEST_NE(mcount_return_fn, 0); mcount_cleanup(); TEST_EQ(mcount_global_flags, MCOUNT_GFL_FINISH); remove_directory(TESTDIR_NAME); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/libmcount/mcount.h000066400000000000000000000042101455365734300167160ustar00rootroot00000000000000/* * 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 #include "utils/rbtree.h" #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), MCOUNT_FL_CYGPROF = (1U << 14), }; 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 filter_size; unsigned short depth; unsigned short filter_depth; unsigned short filter_max_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 "TSDFfsKMpPERWw" #endif /* UFTRACE_MCOUNT_H */ uftrace-0.15.2/libmcount/misc.c000066400000000000000000000157111455365734300163470ustar00rootroot00000000000000#include #include #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 "libmcount/mcount.h" #include "utils/tracefs.h" #include "utils/utils.h" /* old kernel never updates pid filter for a forked child */ void update_kernel_tid(int tid) { char buf[8]; if (!kernel_pid_update) return; snprintf(buf, sizeof(buf), "%d", tid); /* update pid filter for function tracing */ if (append_tracing_file("set_ftrace_pid", buf) < 0) pr_dbg("write to kernel ftrace pid filter failed\n"); /* update pid filter for event tracing */ if (append_tracing_file("set_event_pid", buf) < 0) pr_dbg("write to kernel ftrace pid filter failed\n"); } 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; unsigned long plthook_return_fn = (unsigned long)plthook_return; if (unlikely(mcount_estimate_return)) return; /* 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 == plthook_return_fn) continue; if (!ARCH_CAN_RESTORE_PLTHOOK && rstack->dyn_idx != MCOUNT_INVALID_DYNIDX) { /* * We don't know exact location where the return address * was saved (on ARM/AArch64). But we know that the * return address itself was changed to plthook_return_fn * by the plt_hooker(). So it needs to scan the stack to * look up the value. */ unsigned long *loc, *end; if (idx < mtdp->idx - 1) { struct mcount_ret_stack *next_rstack; next_rstack = rstack + 1; /* skip rstacks for -finstrument-functions */ while (next_rstack->parent_loc == &mtdp->cygprof_dummy && next_rstack < &mtdp->rstack[mtdp->idx]) next_rstack++; if (next_rstack == &mtdp->rstack[mtdp->idx]) goto last_rstack; /* special case: same as tail-call */ if (next_rstack->parent_ip == plthook_return_fn) { rstack->parent_loc = next_rstack->parent_loc; *rstack->parent_loc = rstack->parent_ip; continue; } end = next_rstack->parent_loc; } else { last_rstack: /* just check 32 stack slots */ end = rstack->parent_loc - 32; } for (loc = rstack->parent_loc; loc < end; loc--) { if (*loc != plthook_return_fn) continue; rstack->parent_loc = loc; *loc = rstack->parent_ip; break; } 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; if (unlikely(mcount_estimate_return)) return; 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)); pr_dbg("initially all domains are off\n"); for (i = 0; i < DBG_DOMAIN_MAX; i++) { if (i != PR_DOMAIN) TEST_EQ(dbg_domain[i], 0); } pr_dbg("turn on all domains\n"); 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.15.2/libmcount/plthook.c000066400000000000000000000677411455365734300171060ustar00rootroot00000000000000#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/internal.h" #include "libmcount/mcount.h" #include "mcount-arch.h" #include "utils/filter.h" #include "utils/script.h" #include "utils/symbol.h" #include "utils/utils.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 uftrace_symbol *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; } if (dbg_domain[DBG_PLTHOOK] >= 2) { char *symname = demangle(sym->name); pr_dbg2("resolved addr of %s = %#lx\n", symname, addr); free(symname); } 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); /* * Some compilers generate PLT section even if -fno-plt option is given. * So it cannot simply check if the PLT section is missing for no-plt case. * This is a list of symbols the compilers put in the PLT section regardless * of the option. */ const char *const noplt_skip_syms[] = { /* For GCC/CLANG */ "__stack_chk_fail", "__monstartup", "__cxa_atexit", /* For Rust */ "__tls_get_addr", "_Unwind_Resume", }; size_t noplt_skip_nr = ARRAY_SIZE(noplt_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 uftrace_symtab *dsymtab = &pd->dsymtab; for (i = 0; i < dsymtab->nr_sym; i++) { /* * Typically 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 = ARCH_PLTGOT_OFFSET + i; bool skipped = false; unsigned long plthook_addr; unsigned long resolved_addr; struct uftrace_symbol *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 + %d] to %p (%s)\n", i, ARCH_PLTGOT_OFFSET, 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) { char *symname; /* save already resolved address and hook it */ pd->resolved_addr[i] = resolved_addr; overwrite_pltgot(pd, got_idx, (void *)plthook_addr); if (dbg_domain[DBG_PLTHOOK] < 2) continue; symname = demangle(sym->name); pr_dbg2("restore GOT[%d + %d] from \"%s\"(%#lx) to PLT(base + %#lx)\n", i, ARCH_PLTGOT_OFFSET, symname, resolved_addr, plthook_addr - pd->base_addr); free(symname); } else if (mcount_estimate_return) { /* we can't resolve PLT functions at return. do it now */ resolve_pltgot(pd, i); } } } 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; unsigned long jmprel_addr = 0; struct uftrace_elf_iter sec_iter; size_t jmprel_nr = 0; size_t jmprel_ent_size = 0; struct plthook_data *pd; const char *fname; 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: /* * Depends on compiler, no-plt binary might have a few plt entries, * so we need to traverse them. */ jmprel_addr = (uintptr_t)iter->dyn.d_un.d_ptr + offset; break; case DT_PLTRELSZ: jmprel_nr = (unsigned long)iter->dyn.d_un.d_val; break; case DT_RELENT: case DT_RELAENT: jmprel_ent_size = iter->dyn.d_un.d_val; break; default: break; } } if (jmprel_ent_size == 0) { pr_dbg("cannot find REL(A)ENT size\n"); return 0; } elf_for_each_shdr(elf, &sec_iter) { if (sec_iter.shdr.sh_type == SHT_DYNSYM) { elf_get_strtab(elf, &sec_iter, sec_iter.shdr.sh_link); elf_get_secdata(elf, &sec_iter); break; } } for (size_t i = 0; i < jmprel_nr; i += jmprel_ent_size) { bool found = false; typeof(sec_iter.rel) *rel = (void *)jmprel_addr + i; elf_get_symbol(elf, &sec_iter, elf_rel_symbol(rel)); fname = elf_get_name(elf, &sec_iter, sec_iter.sym.st_name); /* check if PLT has actual functions other than known symbols */ for (size_t k = 0; k < noplt_skip_nr; k++) { if (!strcmp(fname, noplt_skip_syms[k])) found = true; } for (size_t k = 0; k < plt_skip_nr; k++) { if (!strcmp(fname, plt_skip_syms[k].name)) found = true; } if (!found) { plt_found = true; 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[ARCH_PLTGOT_MOD_ID]; 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)); /* do not demangle symbol names since it might call dlsym() */ load_elf_dynsymtab(&pd->dsymtab, elf, pd->base_addr, 0); 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[ARCH_PLTGOT_RESOLVE]; /* * 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, ARCH_PLTGOT_MOD_ID, 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, ARCH_PLTGOT_RESOLVE, 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", "__gxx_personality_v0", "_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", "uftrace_python.so", /* system base libraries */ "libc.so.6", "libc-2.*.so", "libm.so.6", "libm-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", #ifdef __ANDROID__ "linker64", "libc.so", "libm.so", #endif }; 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; if (!mcount_is_main_executable(info->dlpi_name, exename)) return 0; 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 uftrace_symbol *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 = ARCH_PLTGOT_OFFSET + 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 uftrace_symbol *sym; struct mcount_thread_data *mtdp = NULL; struct mcount_ret_stack *rstack; 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; struct uftrace_trigger tr; mcount_memset4(&tr, 0, sizeof(tr)); // 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]; if (dbg_domain[DBG_PLTHOOK] >= 3) { char *symname = demangle(sym->name); pr_dbg3("[idx: %4d] enter %" PRIx64 ": %s@plt (mod: %lx)\n", (int)child_idx, sym->addr, symname, module_id); free(symname); } } else { pr_dbg("invalid function idx found! (idx: %lu/%zu, module: %s)\n", child_idx, pd->dsymtab.nr_sym, pd->mod_name); mcount_unguard_recursion(mtdp); return 0; } 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; if (mcount_estimate_return) goto out; } if (mcount_estimate_return) mcount_rstack_inject_return(mtdp, ret_addr, sym->addr); 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; if (!mcount_estimate_return) { /* 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.15.2/libmcount/pmu.c000066400000000000000000000127451455365734300162210ustar00rootroot00000000000000#include #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 "libmcount/mcount.h" #include "utils/list.h" #include "utils/utils.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); } } #ifdef UNIT_TEST TEST_CASE(mcount_pmu_event) { struct mcount_thread_data mtd; enum uftrace_event_id eid = EVENT_ID_READ_PMU_CYCLE; struct pmu_data *pd; char buf[32]; pr_dbg("checking PMU cycle event\n"); INIT_LIST_HEAD(&mtd.pmu_fds); pd = prepare_pmu_event(&mtd, eid); if (pd == NULL) return TEST_SKIP; TEST_EQ(pd->refcnt, 1); TEST_EQ(read_pmu_event(&mtd, eid, buf), 0); finish_pmu_event(&mtd); pr_dbg("checking PMU cache event\n"); eid = EVENT_ID_READ_PMU_CACHE; pd = prepare_pmu_event(&mtd, eid); if (pd == NULL) return TEST_SKIP; TEST_EQ(pd->refcnt, 1); TEST_EQ(read_pmu_event(&mtd, eid, buf), 0); release_pmu_event(&mtd, eid); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/libmcount/record.c000066400000000000000000000700271455365734300166730ustar00rootroot00000000000000#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/internal.h" #include "libmcount/mcount.h" #include "mcount-arch.h" #include "utils/event.h" #include "utils/filter.h" #include "utils/shmem.h" #include "utils/symbol.h" #include "utils/utils.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 = uftrace_shmem_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); /* prot: rwxp */ p = next + 1; if (p[0] != 'r') continue; 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 (spec->fmt == ARG_FMT_STRUCT) { if (total_size + spec->size > max_size) { /* just to make it fail */ total_size += spec->size; break; } ctx->val.p = ptr; } 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 if (spec->fmt == ARG_FMT_STRUCT) { /* * It already filled the argbuf in the * mcount_arch_get_arg/retval() above. */ size = ALIGN(spec->size, 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; mcount_memset4(&ctx, 0, sizeof(ctx)); ctx.regs = regs; ctx.stack_base = rstack->parent_loc; ctx.regions = &mtdp->mem_regions; ctx.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; mcount_memset4(&ctx, 0, sizeof(ctx)); ctx.retval = retval; ctx.regions = &mtdp->mem_regions; ctx.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; size_t maxsize = (size_t)shmem_bufsize - sizeof(**shmem->buffer); if (unlikely(shmem->curr == -1 || shmem->buffer == NULL)) goto get_buffer; curr_buf = shmem->buffer[shmem->curr]; if (unlikely(curr_buf->size + size > maxsize)) { get_buffer: 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); } struct uftrace_mmap *new_map(const char *path, uint64_t start, uint64_t end, const char *prot) { size_t namelen; struct uftrace_mmap *map; namelen = strlen(path) + 1; map = xzalloc(sizeof(*map) + ALIGN(namelen, 4)); map->start = start; map->end = end; map->len = namelen; mcount_memcpy1(map->prot, prot, 4); mcount_memcpy1(map->libname, path, namelen); read_build_id(path, map->build_id, sizeof(map->build_id)); return map; } void record_proc_maps(char *dirname, const char *sess_id, struct uftrace_sym_info *sinfo) { 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"); sinfo->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]; 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) { sinfo->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; } } map = new_map(path, start, end, prot); /* save map for the executable */ if (!strcmp(path, sinfo->filename)) sinfo->exec_map = map; if (prev_map) prev_map->next = map; else sinfo->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.15.2/libmcount/wrap.c000066400000000000000000000347211455365734300163670ustar00rootroot00000000000000#include #include #include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "wrap" #define PR_DOMAIN DBG_WRAP #include "libmcount/dynamic.h" #include "libmcount/internal.h" #include "libmcount/mcount.h" #include "utils/compiler.h" #include "utils/utils.h" extern struct uftrace_sym_info mcount_sym_info; struct dlopen_base_data { struct mcount_thread_data *mtdp; uint64_t timestamp; }; const char *uftrace_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(&mcount_sym_info, uftrace_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); mcount_dynamic_dlopen(&mcount_sym_info, info, p); return 0; } void mcount_rstack_reset_exception(struct mcount_thread_data *mtdp, unsigned long frame_addr) { int idx; struct mcount_ret_stack *rstack; if (unlikely(mcount_estimate_return)) return; /* it needs to find how much stack frame unwinds */ for (idx = mtdp->idx - 1; idx >= 0; idx--) { rstack = &mtdp->rstack[idx]; pr_dbg3("%s: [%d] parent at %p\n", __func__, 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_dbg3("%s: exception in tail call at [%d]\n", __func__, 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_dbg3("%s: exception returned to [%d]\n", __func__, 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 int count_envp(char *const *env) { int i, n = 0; for (i = 0; env && env[i]; i++) n++; return n; } static char **merge_envp(char *const *env1, char **env2) { int i, n = 0; char **envp; n += count_envp(env1); n += count_envp(env2); 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); pr_dbg("%s is called from [%d]\n", __func__, mtdp->idx); } 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_dbg2("%s: exception thrown from [%d]\n", __func__, 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_dbg2("%s: exception rethrown from [%d]\n", __func__, 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("%s: exception resumed on [%d]\n", __func__, 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; pr_dbg2("%s: exception caught begin on [%d]\n", __func__, mtdp->idx); } return obj; } __visible_default void __cxa_end_catch(void) { if (unlikely(real_cxa_end_catch == NULL)) mcount_hook_functions(); pr_dbg2("%s: exception caught end\n", __func__); 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(); pr_dbg("%s is called for '%s'\n", __func__, filename); 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 (!mcount_estimate_return && !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); } if (!check_thread_data(mtdp)) pr_dbg("%s: pthread exited on [%d]\n", __func__, mtdp->idx); 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); pr_dbg("%s is called for '%s'\n", __func__, path); 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); pr_dbg("%s is called for '%s'\n", __func__, file); 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); pr_dbg("%s is called for '%s'\n", __func__, path); 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); pr_dbg("%s is called for '%s'\n", __func__, file); 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); pr_dbg("%s is called for fd %d\n", __func__, fd); return real_fexecve(fd, argv, new_envp); } #ifdef UNIT_TEST TEST_CASE(mcount_wrap_dlopen) { void *handle; /* In some environment, dlopen() is called already */ if (unlikely(real_dlopen != NULL)) real_dlopen = NULL; pr_dbg("calling %s (%s) should init all the wrappers\n", "dlopen", "or other wrapped function"); handle = dlopen(NULL, RTLD_LAZY); TEST_NE(handle, NULL); TEST_NE(real_dlopen, NULL); return TEST_OK; } TEST_CASE(mcount_env_check) { char **uftrace_envp; char **new_envp; int old1_cnt, old2_cnt, new_cnt; int i; pr_dbg("collecting environ related to uftrace\n"); uftrace_envp = collect_uftrace_envp(); old1_cnt = count_envp(uftrace_envp); old2_cnt = count_envp(environ); pr_dbg("merging uftrace envp to the existing one\n"); new_envp = merge_envp(environ, uftrace_envp); new_cnt = count_envp(new_envp); TEST_EQ(old1_cnt + old2_cnt, new_cnt); for (i = 0; i < old1_cnt; i++) free(uftrace_envp[i]); free(uftrace_envp); free(new_envp); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/misc/000077500000000000000000000000001455365734300142025ustar00rootroot00000000000000uftrace-0.15.2/misc/apply-patch.sh000077500000000000000000000000711455365734300167610ustar00rootroot00000000000000#!/bin/sh unexpand $1 | sed -e "s/^\t/ \t/" | patch -p1 uftrace-0.15.2/misc/bash-completion.sh000066400000000000000000000017621455365734300176300ustar00rootroot00000000000000_uftrace () { local cur prev subcmds options uftrace_comp cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD - 1]} COMPREPLY=() subcmds='record replay report live dump graph info recv script tui' options=$(uftrace -h | awk '$1 ~ /--[a-z]/ { split($1, r, "="); print r[1] } \ $2 ~ /--[a-z]/ { split($2, r, "="); print r[1] }') demangle='full simple no' sort_key='total self call avg min max' uftrace_comp="${subcmds} ${options}" case $prev in -d|--data|--diff|-L|--libmcount-path) # complete directory name COMPREPLY=($(compgen -d -- "${cur}")) ;; --demangle) COMPREPLY=($(compgen -W "${demangle}" -- "${cur}")) ;; -s|--sort) COMPREPLY=($(compgen -W "${sort_key}" -- "${cur}")) ;; *) # complete subcommand, long option or (executable) filename COMPREPLY=($(compgen -f -W "${uftrace_comp}" -- "${cur}")) ;; esac return 0 } complete -o filenames -F _uftrace uftrace uftrace-0.15.2/misc/bench.c000066400000000000000000000011261455365734300154250ustar00rootroot00000000000000#include int foo(volatile int *ptr) { *ptr += 1; return *ptr; } void baz(volatile int *ptr) { *ptr += 1; } int bar(volatile int *ptr) { baz(ptr); return *ptr; } int bench(int count) { volatile int result = 0; int i; for (i = 0; i < count; i++) { if (i % 2 == 0) foo(&result); else bar(&result); } return result; } int main(int argc, char *argv[]) { int n = 1; int i; int loop = 1000000; int result = 0; if (argc > 1) n = atoi(argv[1]); if (argc > 2) loop = atoi(argv[2]); for (i = 0; i < n; i++) result += bench(loop); return result ? 0 : 1; } uftrace-0.15.2/misc/bench.sh000077500000000000000000000034301455365734300156200ustar00rootroot00000000000000#!/bin/bash UFTRACE="../uftrace --libmcount-path=../libmcount" UOPTS= PROG="./bench" DATA=bench.data # setup cpufreq (on a cpu) CPU=3 function msg() { if [ "${VERBOSE}" != "1" ]; then return fi echo $* } function help() { echo "Usage: bench.sh [OPTION]" echo " OPTION -c N Use CPU N during the benchmark. (default: 3)" echo " -p PROG Use program PROG. (default: ./bench)" echo " -u UOPT Use uftrace option UOPT. Please quote it." echo " -v Show verbose messages." echo " -h Show this help and exit." exit 0 } function set_cpufreq() { NEWGOV=$1 CPUFREQ="/sys/devices/system/cpu/cpufreq/policy${CPU}/scaling_governor" if [ ! -e ${CPUFREQ} ]; then msg "Skip setting cpufreq since the file is not found: ${CPUFREQ}" return fi if [ "${ORIG_GOV}" == "" ]; then ORIG_GOV=$(cat ${CPUFREQ}) fi CURGOV=$(cat ${CPUFREQ}) msg "Changing cpufreq governor: ${CURGOV} ==> ${NEWGOV} : ${CPUFREQ}" sudo sh -c "echo ${NEWGOV} > ${CPUFREQ}" } # parse command line options while getopts "c:p:u:vh" arg; do case $arg in c) CPU=$OPTARG ;; p) PROG=$OPTARG ;; u) UOPTS=$OPTARG ;; v) VERBOSE=1 ;; h) help ;; esac done shift $((OPTIND - 1)) ARGS=$* TARGET="${PROG} ${ARGS}" TASKSET="taskset -c ${CPU}" echo "# uftrace bench" # this will set $ORIG_GOV set_cpufreq "performance" sleep 1 # do not use taskset (CPU affinity) when cpufreq is not available if [ "${ORIG_GOV}" == "" ]; then TASKSET= fi msg "running uftrace record ${UOPTS} with ${TARGET}" ${TASKSET} ${UFTRACE} record -d ${DATA} ${UOPTS} ${TARGET} ${UFTRACE} report -d ${DATA} -F 'foo' -F '^ba' -f self-avg,self-min set_cpufreq "${ORIG_GOV}" rm -rf ${DATA}{,.old} uftrace-0.15.2/misc/dbginfo.c000066400000000000000000000043671455365734300157700ustar00rootroot00000000000000#include #include #include #include #include #include "utils/dwarf.h" #include "utils/filter.h" #include "utils/symbol.h" void print_debug_info(struct uftrace_dbg_info *dinfo, bool auto_args) { size_t i; char *argspec = NULL; char *retspec = NULL; /* TODO: print enum definitions */ for (i = 0; i < dinfo->nr_locs; i++) { struct uftrace_dbg_loc *loc = &dinfo->locs[i]; int idx = 0; if (loc->sym == NULL) continue; argspec = get_dwarf_argspec(dinfo, loc->sym->name, loc->sym->addr); retspec = get_dwarf_retspec(dinfo, loc->sym->name, loc->sym->addr); if (argspec == NULL && retspec == NULL && !auto_args) continue; printf("%s [addr: %" PRIx64 "]\n", loc->sym->name, loc->sym->addr); /* skip common parts with compile directory */ if (dinfo->base_dir) { int len = strlen(dinfo->base_dir); if (!strncmp(loc->file->name, dinfo->base_dir, len)) idx = len + 1; } printf(" srcline: %s:%d\n", loc->file->name + idx, loc->line); if (argspec) printf(" argspec: %s\n", argspec); if (retspec) printf(" retspec: %s\n", retspec); } } int main(int argc, char *argv[]) { struct uftrace_mmap *map; struct uftrace_sym_info sinfo = { .dirname = ".", .flags = SYMTAB_FL_DEMANGLE, }; char *argspec = NULL; char *retspec = NULL; char *filename = NULL; bool auto_args = false; enum uftrace_pattern_type ptype = PATT_REGEX; int opt; while ((opt = getopt(argc, argv, "aA:R:v")) != -1) { switch (opt) { case 'a': auto_args = true; break; case 'A': argspec = optarg; break; case 'R': retspec = optarg; break; case 'v': debug++; dbg_domain[DBG_DWARF]++; break; default: printf("dbginfo: unknown option: %c\n", opt); return 1; } } if (optind >= argc) { printf("Usage: dbginfo [-a | -A | -R ] \n"); return 1; } filename = argv[optind]; logfp = stderr; outfp = stdout; map = xzalloc(sizeof(*map) + strlen(filename) + 1); strcpy(map->libname, filename); sinfo.maps = map; load_module_symtabs(&sinfo); prepare_debug_info(&sinfo, ptype, argspec, retspec, auto_args, false); print_debug_info(&map->mod->dinfo, auto_args); finish_debug_info(&sinfo); unload_module_symtabs(); free(map); return 0; } uftrace-0.15.2/misc/debug-mcount.cmd000066400000000000000000000002611455365734300172570ustar00rootroot00000000000000file uftrace set breakpoint pending on #b command_record catch exec commands # set follow-fork-mode child b main continue end r record -L. -d xxx --keep-pid --force argv uftrace-0.15.2/misc/debug.sh000077500000000000000000000001761455365734300156330ustar00rootroot00000000000000#!/bin/sh TMP=$(mktemp) ARGV="${@:-tests/t-abc}" sed "s|argv|$ARGV|g" misc/debug-mcount.cmd > $TMP gdb -x $TMP rm -f $TMP uftrace-0.15.2/misc/demangler.c000066400000000000000000000037771455365734300163220ustar00rootroot00000000000000#include #include #include #include #include "uftrace.h" #include "utils/utils.h" #include "version.h" char *demangle(char *str); extern enum symbol_demangler demangler; enum options { OPT_simple = 301, OPT_full, OPT_no, }; static struct option demangler_options[] = { { "simple", no_argument, 0, OPT_simple }, { "full", no_argument, 0, OPT_full }, { "no", no_argument, 0, OPT_no }, { "verbose", no_argument, 0, 'v' }, }; static const char demangler_usage[] = "demangler " UFTRACE_VERSION "\n" "\n" " OPTION:\n" " --simple Use internal simple demangler (default)\n" " --full Use libstdc++ demangler\n" " --no Do not use demangler\n" " -v, --verbose Be verbose\n" "\n"; struct demangler_opts { int mode; int idx; }; static void parse_option(int argc, char **argv, struct demangler_opts *opts) { bool done = false; while (!done) { int key, tmp; key = getopt_long(argc, argv, "v", demangler_options, &tmp); switch (key) { case OPT_simple: opts->mode = DEMANGLE_SIMPLE; break; case OPT_full: opts->mode = DEMANGLE_FULL; break; case OPT_no: opts->mode = DEMANGLE_NONE; break; case 'v': debug++; dbg_domain[DBG_DEMANGLE]++; break; case -1: done = true; break; default: printf("%s", demangler_usage); exit(1); } } opts->idx = optind; } int main(int argc, char *argv[]) { struct demangler_opts opts = { .mode = DEMANGLE_SIMPLE, }; parse_option(argc, argv, &opts); demangler = opts.mode; outfp = stdout; logfp = stdout; if (opts.idx < argc) { int i; for (i = opts.idx; i < argc; i++) { char *name = demangle(argv[i]); printf("%s\n", name); free(name); } } else { char buf[4096]; while (fgets(buf, sizeof(buf), stdin)) { char *name; char *p; buf[sizeof(buf) - 1] = '\0'; p = strchr(buf, '\n'); if (p) *p = '\0'; name = demangle(buf); printf("%s\n", name); free(name); } } return 0; } uftrace-0.15.2/misc/docker/000077500000000000000000000000001455365734300154515ustar00rootroot00000000000000uftrace-0.15.2/misc/docker/alpine/000077500000000000000000000000001455365734300167215ustar00rootroot00000000000000uftrace-0.15.2/misc/docker/alpine/3.13/000077500000000000000000000000001455365734300173055ustar00rootroot00000000000000uftrace-0.15.2/misc/docker/alpine/3.13/Dockerfile000066400000000000000000000006701455365734300213020ustar00rootroot00000000000000FROM alpine:3.13 ARG test RUN apk update RUN apk add build-base linux-headers git bash libunwind-dev RUN mkdir -p /usr/src RUN git clone https://github.com/namhyung/uftrace /usr/src/uftrace RUN if [ "${test}" == "yes" ] ; then \ cd /usr/src/uftrace && ./misc/install-deps.sh && ./configure && make && make unittest; \ else \ cd /usr/src/uftrace && ./misc/install-deps.sh && ./configure && make && make install; \ fi uftrace-0.15.2/misc/docker/alpine/3.14/000077500000000000000000000000001455365734300173065ustar00rootroot00000000000000uftrace-0.15.2/misc/docker/alpine/3.14/Dockerfile000066400000000000000000000006701455365734300213030ustar00rootroot00000000000000FROM alpine:3.14 ARG test RUN apk update RUN apk add build-base linux-headers git bash libunwind-dev RUN mkdir -p /usr/src RUN git clone https://github.com/namhyung/uftrace /usr/src/uftrace RUN if [ "${test}" == "yes" ] ; then \ cd /usr/src/uftrace && ./misc/install-deps.sh && ./configure && make && make unittest; \ else \ cd /usr/src/uftrace && ./misc/install-deps.sh && ./configure && make && make install; \ fi uftrace-0.15.2/misc/docker/arch/000077500000000000000000000000001455365734300163665ustar00rootroot00000000000000uftrace-0.15.2/misc/docker/arch/Dockerfile000066400000000000000000000006631455365734300203650ustar00rootroot00000000000000FROM archlinux:latest ARG test RUN pacman -Sy RUN yes | pacman -S git gcc make RUN mkdir -p /usr/src RUN git clone https://github.com/namhyung/uftrace /usr/src/uftrace RUN if [ "$test" = "yes" ] ; then \ cd /usr/src/uftrace && ./misc/install-deps.sh -y && ./configure && make ASAN=1 && make ASAN=1 unittest; \ else \ cd /usr/src/uftrace && ./misc/install-deps.sh -y && ./configure && make && make install; \ fi uftrace-0.15.2/misc/docker/centos/000077500000000000000000000000001455365734300167445ustar00rootroot00000000000000uftrace-0.15.2/misc/docker/centos/7/000077500000000000000000000000001455365734300171125ustar00rootroot00000000000000uftrace-0.15.2/misc/docker/centos/7/Dockerfile000066400000000000000000000013471455365734300211110ustar00rootroot00000000000000FROM centos:7 ARG test RUN yum install -y git gcc make epel-release RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY* # update gcc version RUN yum install -y centos-release-scl RUN yum install -y devtoolset-9-gcc* devtoolset-9-libasan-devel devtoolset-9-libubsan-devel RUN echo "source /opt/rh/devtoolset-9/enable" >> /etc/bashrc SHELL ["/bin/bash", "--login", "-c"] RUN mkdir -p /usr/src RUN git clone https://github.com/namhyung/uftrace /usr/src/uftrace RUN if [ "$test" = "yes" ] ; then \ cd /usr/src/uftrace \ && ./misc/install-deps.sh -y \ && ./configure && make ASAN=1 && make ASAN=1 unittest; \ else \ cd /usr/src/uftrace && ./misc/install-deps.sh -y && ./configure && make && make install; \ fi uftrace-0.15.2/misc/docker/fedora/000077500000000000000000000000001455365734300167115ustar00rootroot00000000000000uftrace-0.15.2/misc/docker/fedora/33/000077500000000000000000000000001455365734300171365ustar00rootroot00000000000000uftrace-0.15.2/misc/docker/fedora/33/Dockerfile000066400000000000000000000006601455365734300211320ustar00rootroot00000000000000FROM fedora:33 ARG test RUN yum install -y git gcc make RUN mkdir -p /usr/src RUN git clone https://github.com/namhyung/uftrace /usr/src/uftrace RUN if [ "$test" = "yes" ] ; then \ cd /usr/src/uftrace \ && ./misc/install-deps.sh -y \ && ./configure && make ASAN=1 && make ASAN=1 unittest; \ else \ cd /usr/src/uftrace && ./misc/install-deps.sh -y && ./configure && make && make install; \ fi uftrace-0.15.2/misc/docker/fedora/34/000077500000000000000000000000001455365734300171375ustar00rootroot00000000000000uftrace-0.15.2/misc/docker/fedora/34/Dockerfile000066400000000000000000000006601455365734300211330ustar00rootroot00000000000000FROM fedora:34 ARG test RUN yum install -y git gcc make RUN mkdir -p /usr/src RUN git clone https://github.com/namhyung/uftrace /usr/src/uftrace RUN if [ "$test" = "yes" ] ; then \ cd /usr/src/uftrace \ && ./misc/install-deps.sh -y \ && ./configure && make ASAN=1 && make ASAN=1 unittest; \ else \ cd /usr/src/uftrace && ./misc/install-deps.sh -y && ./configure && make && make install; \ fi uftrace-0.15.2/misc/docker/ubuntu/000077500000000000000000000000001455365734300167735ustar00rootroot00000000000000uftrace-0.15.2/misc/docker/ubuntu/16.04/000077500000000000000000000000001455365734300174435ustar00rootroot00000000000000uftrace-0.15.2/misc/docker/ubuntu/16.04/Dockerfile000066400000000000000000000010561455365734300214370ustar00rootroot00000000000000FROM ubuntu:16.04 ARG test RUN apt-get update \ && apt-get install -y --no-install-recommends git gcc make ca-certificates RUN mkdir -p /usr/src RUN git clone https://github.com/namhyung/uftrace /usr/src/uftrace RUN if [ "$test" = "yes" ] ; then \ cd /usr/src/uftrace \ && ./misc/install-deps.sh -y \ && ./configure && make ASAN=1 && make ASAN=1 unittest; \ else \ cd /usr/src/uftrace && ./misc/install-deps.sh -y && ./configure && make && make install; \ fi RUN apt-get clean \ && rm -rf /var/lib/apt/lists/* uftrace-0.15.2/misc/docker/ubuntu/18.04/000077500000000000000000000000001455365734300174455ustar00rootroot00000000000000uftrace-0.15.2/misc/docker/ubuntu/18.04/Dockerfile000066400000000000000000000010561455365734300214410ustar00rootroot00000000000000FROM ubuntu:18.04 ARG test RUN apt-get update \ && apt-get install -y --no-install-recommends git gcc make ca-certificates RUN mkdir -p /usr/src RUN git clone https://github.com/namhyung/uftrace /usr/src/uftrace RUN if [ "$test" = "yes" ] ; then \ cd /usr/src/uftrace \ && ./misc/install-deps.sh -y \ && ./configure && make ASAN=1 && make ASAN=1 unittest; \ else \ cd /usr/src/uftrace && ./misc/install-deps.sh -y && ./configure && make && make install; \ fi RUN apt-get clean \ && rm -rf /var/lib/apt/lists/* uftrace-0.15.2/misc/gen-autoargs.py000077500000000000000000000245301455365734300171570ustar00rootroot00000000000000#!/usr/bin/env python3 # # Arguments / return type option tables generator for automatic value display # # Copyright (C) 2017, LG Electronics, Honggyu Kim # # Released under the GPL v2. # from __future__ import print_function import re import sys # The syntax of C in Backus-Naur Form # https://cs.wmich.edu/~gupta/teaching/cs4850/sumII06/The%20syntax%20of%20C%20in%20Backus-Naur%20form.htm # generated file name argspec_file = "autoargs.h" storage_class_specifier = ["auto", "register", "static", "extern", "typedef"] type_qualifier = ["const", "volatile"] type_specifier = ["void", "char", "short", "int", "long", "float", "double", \ "signed", "unsigned" ] struct_or_union_specifier = ["struct", "union"] enum_specifier = ["enum"] typedef_name = [ "size_t", "ssize_t", "pid_t", "uid_t", "off_t", "off64_t", "sigset_t", "socklen_t", "intptr_t", "nfds_t", "pthread_t", "pthread_once_t", "pthread_attr_t", "pthread_mutex_t", "pthread_mutexattr_t", "pthread_cond_t", "Lmid_t", "FILE", "in_addr_t", ] artifitial_type = [ "funcptr_t" ] pointer = "*" reference = "&" type_specifier.extend(struct_or_union_specifier) #type_specifier.extend(enum_specifier) type_specifier.extend(typedef_name) type_specifier.extend(["std::string"]) type_specifier.extend(artifitial_type) header = """\ /* * Arguments / return type option tables for automatic value display * * This file is auto-generated by "gen-autoargs.py" based on prototypes.h */ """ verbose = False def parse_return_type(words): global storage_class_specifier global type_qualifier global struct_or_union_specifier global enum_specifier global pointer i = 0 return_type = "" struct_or_union_flag = False for word in words: if word in storage_class_specifier: # skip pass elif word in type_qualifier: # skip pass elif word in struct_or_union_specifier: return_type = word struct_or_union_flag = True elif word in type_specifier: if return_type == "": return_type = word else: return_type += " " + word elif word == pointer: return_type += word elif word == reference: # skip reference pass elif struct_or_union_flag: return_type += " " + word struct_or_union_flag = False elif word == ",": pass else: break i += 1 return (return_type, words[i:]) def parse_func_name(words): funcname = words[0] return (funcname, words[1:]) def parse_args(words): if words[0] != '(' and words[-1] != ')': return [] # fail arg_type = [] enum_flag = False struct_or_union_flag = False for word in words[1:-1]: if word in type_qualifier: # skip pass elif word in struct_or_union_specifier: arg_type.append(word) struct_or_union_flag = True elif word in type_specifier: arg_type.append(word) elif word in enum_specifier: enum_flag = True elif word == pointer: arg_type[-1] += pointer elif word == reference: # skip reference pass elif struct_or_union_flag: struct_or_union_flag = False arg_type[-1] += " " + word elif enum_flag: enum_flag = False arg_type.append("enum " + word) elif word == ",": pass else: struct_or_union_flag = False enum_flag = False return arg_type def parse_func_decl(func): chunks = re.split('[\s,;]+|([*()])', func) words = [x for x in chunks if x] (return_type, words) = parse_return_type(words) (funcname, words) = parse_func_name(words) args = parse_args(words) return (return_type, funcname, args) DECL_TYPE_NONE = 0 DECL_TYPE_FUNC = 1 DECL_TYPE_ENUM = 2 def get_decl_type(line): # function should have parenthesis if line.find('(') >= 0: return DECL_TYPE_FUNC # or it should be enum if line.startswith('enum'): return DECL_TYPE_ENUM # error return DECL_TYPE_NONE def make_uftrace_retval_format(ctype, funcname): retval_format = funcname + "@" if ctype == "void": retval_format = "" pass elif ctype == "int": retval_format += "retval/d32" elif ctype == "short": retval_format += "retval/d16" elif ctype == "char": retval_format += "retval/c" elif ctype == "float": retval_format += "retval/f32" elif ctype == "double": retval_format += "retval/f64" elif ctype == "char*": retval_format += "retval/s" elif ctype == "std::string": retval_format += "retval/S" elif ctype[-1] == "*": retval_format += "retval/p" elif ctype == "pid_t" or ctype == "uid_t": retval_format += "retval/i32" elif "unsigned" in ctype or ctype == "size_t": retval_format += "retval/u" elif ctype == "funcptr_t": retval_format += "retval/p" elif ctype == "off64_t": retval_format += "retval/d64" elif ctype.startswith('enum'): retval_format += "retval/e:%s" % ctype[5:] else: retval_format += "retval" return retval_format def make_uftrace_args_format(args, funcname): args_format = funcname + "@" i = 0 f = 1 for arg in args: i += 1 if (i > 1): args_format += "," if arg == "void": args_format = "" break elif arg == "int": args_format += "arg%d/d32" % i elif arg == "short": args_format += "arg%d/d16" % i elif arg == "char": args_format += "arg%d/c" % i elif arg == "float": args_format += "fparg%d/32" % f f += 1 i -= 1 elif arg == "double": args_format += "fparg%d/64" % f f += 1 i -= 1 elif arg == "char*": args_format += "arg%d/s" % i elif arg == "std::string": args_format += "arg%d/S" % i elif arg[-1] == "*": args_format += "arg%d/p" % i elif arg == "pid_t" or arg == "uid_t": args_format += "arg%d/i32" % i elif "unsigned" in arg or arg == "size_t": args_format += "arg%d/u" % i elif arg == "funcptr_t": args_format += "arg%d/p" % i elif arg == "off64_t": args_format += "arg%d/d64" % i elif arg.startswith('enum'): args_format += "arg%d/e:%s" % (i, arg[5:]) else: args_format += "arg%d" % i return args_format def parse_enum(line): # is this the final line (including semi-colon) if line.find(';') >= 0: return (DECL_TYPE_NONE, ' '.join(line.split())) # continue to parse next line return (DECL_TYPE_ENUM, ' '.join(line.split())) def parse_argument(): import argparse parser = argparse.ArgumentParser() parser.add_argument("-i", "--input-file", dest='infile', default="prototypes.h", help="input prototype header file (default: prototypes.h") parser.add_argument("-o", "--output-file", dest='outfile', default="autoargs.h", help="output uftrace argspec file (default: autoargs.h)") parser.add_argument("-v", "--verbose", dest='verbose', action='store_true', help="show internal command and result for debugging") return parser.parse_args() if __name__ == "__main__": arg = parse_argument() if arg.verbose: print(arg) argspec_file = arg.outfile prototype_file = arg.infile verbose = arg.verbose enum_list = "" args_list = "" retvals_list = "" # operator new and delete and their variations args_list = '\t\"_Znwm@arg1/u;\"\n' \ + '\t\"_Znam@arg1/u;\"\n' \ + '\t\"_ZdlPv@arg1/x;\"\n' \ + '\t\"_ZdaPv@arg1/x;\"\n' # operator new and its variations retvals_list = '\t\"_Znwm@retval/x;\"\n' \ + '\t\"_Znam@retval/x;\"\n' t = DECL_TYPE_NONE with open(prototype_file) as fin: for line in fin: if len(line) <= 1 or line[0] == '#' or line[0:2] == "//" \ or line[0:7] == "typedef": continue if verbose: print(line, end='') if t == DECL_TYPE_ENUM: (t, curr) = parse_enum(line) enum_format += curr if t == DECL_TYPE_NONE: enum_list += '\t"' + enum_format + '"\n' continue t = get_decl_type(line) if t == DECL_TYPE_NONE: continue if t == DECL_TYPE_ENUM: (t, enum_format) = parse_enum(line) if t == DECL_TYPE_NONE: enum_list += '\t"' + enum_format + '"\n' continue (return_type, funcname, args) = parse_func_decl(line) if verbose: print(args) retval_format = make_uftrace_retval_format(return_type, funcname) args_format = make_uftrace_args_format(args, funcname) if verbose: print("ret : " + retval_format) print("arg : " + args_format) print("") if retval_format: retvals_list += '\t"' + retval_format + ';"\n' if args_format: args_list += '\t"' + args_format + ';"\n' if verbose: print(enum_list) print(args_list) print(retvals_list) if argspec_file == "-": fout = sys.stdout else: fout = open(argspec_file, "w") fout.write(header) if len(enum_list) == 0: enum_list="\"\"" fout.write("/* clang-format off */\n\n") fout.write("static char *auto_enum_list =\n") fout.write(enum_list) fout.write(";\n\n") fout.write("static char *auto_args_list =\n") fout.write(args_list) fout.write(";\n\n") fout.write("static char *auto_retvals_list =\n") fout.write(retvals_list) fout.write(";\n\n") fout.write("/* clang-format on */\n") if argspec_file != "-": fout.close() uftrace-0.15.2/misc/install-deps.sh000077500000000000000000000043101455365734300171360ustar00rootroot00000000000000#!/bin/sh install_packages() { case $1 in "ubuntu" | "debian") apt-get install $OPT pandoc libdw-dev python3-dev libncursesw5-dev pkg-config apt-get install $OPT libluajit-5.1-dev || true apt-get install $OPT libcapstone-dev || true apt-get install $OPT libtraceevent-dev || true exit ;; "fedora") dnf install $OPT pandoc elfutils-devel python3-devel ncurses-devel pkgconf-pkg-config dnf install $OPT luajit-devel || true dnf install $OPT capstone-devel || true dnf install $OPT libtraceevent-devel || true exit ;; "rhel" | "centos") rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm yum install $OPT pandoc elfutils-devel python3-devel ncurses-devel pkgconfig yum install $OPT luajit-devel || true yum install $OPT capstone-devel || true dnf install $OPT libtraceevent-devel || true exit ;; "arch" | "manjaro") pacman $OPT -S pandoc libelf python3 ncurses pkgconf pacman $OPT -S luajit || true pacman $OPT -S capstone || true pacman $OPT -S libtraceevent || true exit ;; "alpine") apk add $OPT elfutils-dev python3-dev ncurses-dev pkgconf apk add $OPT luajit-dev || true apk add $OPT capstone-dev || true apk add $OPT libtraceevent-dev || true exit ;; esac } if [ "x$(id -u)" != x0 ]; then echo "You might have to run it as root user." echo "Please run it again with 'sudo'." echo exit fi OPT="${@}" if [ ! -f /etc/os-release ]; then echo "Your distribution is not supported, so please install packages manually." echo exit fi distro=$(grep "^ID=" /etc/os-release | cut -d\= -f2 | sed -e 's/"//g') id_like=$(grep "^ID_LIKE=" /etc/os-release | cut -d\= -f2 | sed -e 's/"//g') install_packages "$distro" for distro_like in $id_like; do install_packages "$distro_like" done echo "\"$distro\" is not supported distro, so please install packages manually." echo uftrace-0.15.2/misc/install-elfutils.sh000077500000000000000000000037301455365734300200370ustar00rootroot00000000000000#!/bin/bash #-*- mode: shell-script; -*- prefix=/usr/local objdir=$(readlink -f ${objdir:-${PWD}}) builddir=${objdir}/.build n_cpus=$(grep -c ^processor /proc/cpuinfo) usage() { echo "Usage: $0 [] --help print this message --prefix= set install root dir as (default: /usr/local) Example usage for host compilation: $ $0 --prefix=./build.host Example usage for cross compilation: $ CROSS_COMPILE=arm-linux-gnueabi- ARCH=arm CFLAGS=\"-march=armv7-a\" \\ $0 --prefix=./build.arm " exit 1 } while getopts ":ho:-:p" opt; do case "$opt" in -) # process --long-options case "$OPTARG" in help) usage ;; *=*) opt=${OPTARG%%=*}; val=${OPTARG#*=} eval "${opt/-/_}='$val'" ;; *) ;; esac ;; *) usage ;; esac done shift $((OPTIND - 1)) mkdir -p ${builddir} && cd ${builddir} ELFUTILS_VERSION=0.164 ELFUTILS_NAME=elfutils-$ELFUTILS_VERSION ELFUTILS_TARBALL=$ELFUTILS_NAME.tar.bz2 ELFUTILS_URL=https://sourceware.org/elfutils/ftp/$ELFUTILS_VERSION/$ELFUTILS_TARBALL if [ ! -d "$ELFUTILS_NAME" ]; then wget -c $ELFUTILS_URL tar xvfj $ELFUTILS_TARBALL ln -sf $ELFUTILS_NAME elfutils fi cd elfutils opt_host_cc="" if [ ! -z $CROSS_COMPILE ]; then HOST=$(basename $CROSS_COMPILE | sed 's/-$//g') opt_host_cc="--host=$HOST CC=${CROSS_COMPILE}gcc" fi configure_cmd="./configure --prefix=$prefix $opt_host_cc" if [ ! -f configure.cmd ] || [ "$configure_cmd" != "$(cat configure.cmd)" ]; then $configure_cmd && echo "$configure_cmd" > configure.cmd fi # build and install libelf first make -j${n_cpus} -C libelf install # build and install libdw later on # libdw requires to build libdwfl, libdwelf, and libebl make -j${n_cpus} -C libdwfl make -j${n_cpus} -C libdwelf make -j${n_cpus} -C libebl CFLAGS="$CFLAGS -Wno-misleading-indentation" make -j${n_cpus} -C libdw install cd .. uftrace-0.15.2/misc/prototypes.h000066400000000000000000000465041455365734300166140ustar00rootroot00000000000000// // Prototype functions for automatic arguments / return value display // // Copyright (C) 2017, LG Electronics, Honggyu Kim // // Released under the GPL v2. // // This file is processed by gen-autoargs.py and it generates autoargs.h to be // used for --auto-args option. Due to the limitation of space, it cannot // contain more function prototypes as of now. // // clang-format off #include int atoi(const char *str); long atol(const char *str); double atof(const char *str); long strtol(const char *str, void *endp, int base); unsigned long strtoul(const char *str, void *endp, int base); double strtod(const char *str, void *endp); float strtof(const char *str, void *endp); void qsort(void *base, size_t nmemb, size_t size, funcptr_t compar); void qsort_r(void *base, size_t nmemb, size_t size, funcptr_t compar, void *arg); void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, funcptr_t compar); void exit(int status); //////////////////////////////////////////////////////////////////////////////// // memory void *malloc(size_t size); void free(void* ptr); void* calloc(size_t nmemb, size_t size); void* realloc(void* ptr, size_t size); #include enum uft_mmap_prot { PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC = 4, }; enum uft_mmap_flag { MAP_SHARED = 0x1, MAP_PRIVATE = 0x2, MAP_FIXED = 0x10, MAP_ANON = 0x20, MAP_GROWSDOWN = 0x100, MAP_DENYWRITE = 0x800, MAP_EXECUTABLE = 0x1000, MAP_LOCKED = 0x2000, MAP_NORESERVE = 0x4000, MAP_POPULATE = 0x8000, MAP_NONBLOCK = 0x10000, MAP_STACK = 0x20000, MAP_HUGETLB = 0x40000, }; void *mmap(void *addr, size_t length, enum uft_mmap_prot prot, enum uft_mmap_flag flags, int fd, off_t offset); void *mmap64(void *addr, size_t length, enum uft_mmap_prot prot, enum uft_mmap_flag flags, int fd, off64_t offset); int munmap(void *addr, size_t length); int mprotect(void *addr, size_t len, enum uft_mmap_prot prot); enum uft_madvise { MADV_NORMAL = 0, MADV_RANDOM = 1, MADV_SEQUENTIAL = 2, MADV_WILLNEED = 3, MADV_DONTNEED = 4, MADV_FREE = 8, MADV_REMOVE = 9, MADV_DONTFORK = 10, MADV_DOFORK = 11, MADV_MERGEABLE = 12, MADV_UNMERGEABLE = 13, MADV_HUGEPAGE = 14, MADV_NOHUGEPAGE = 15, MADV_DONTDUMP = 16, MADV_DODUMP = 17, MADV_HWPOISON = 100, }; int madvise(void *addr, size_t length, enum uft_madvise advice); enum uft_posix_madvise { POSIX_MADV_NORMAL = 0, POSIX_MADV_RANDOM = 1, POSIX_MADV_SEQUENTIAL = 2, POSIX_MADV_WILLNEED = 3, POSIX_MADV_DONTNEED = 4, }; int posix_madvise(void *addr, size_t len, enum uft_posix_madvise advice); enum uft_posix_fadvise { POSIX_FADV_NORMAL = 0, POSIX_FADV_RANDOM = 1, POSIX_FADV_SEQUENTIAL = 2, POSIX_FADV_WILLNEED = 3, POSIX_FADV_DONTNEED = 4, POSIX_FADV_NOREUSE = 5, }; int posix_fadvise(int fd, off_t offset, off_t len, enum uft_posix_fadvise advice); int brk(void *addr); void *sbrk(intptr_t increment); #include void *memalign(size_t alignment, size_t size); void *pvalloc(size_t size); int posix_memalign(void **memptr, size_t alignment, size_t size); void *aligned_alloc(size_t alignment, size_t size); void *valloc(size_t size); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // string #include char* strcat(char *dest, const char *src); char* strncat(char *dest, const char *src, size_t n); void strcpy(void* dest, const char* src); void strncpy(void* dest, const char* src, size_t n); size_t strlen(const char *s); size_t strnlen(const char *s, size_t maxlen); int strcmp(const char *s1, const char *s2); int strncmp(const char *s1, const char *s2, size_t n); int strcasecmp(const char *s1, const char *s2); int strncasecmp(const char *s1, const char *s2, size_t n); char *strdup(const char *s); char *strndup(const char *s, size_t n); char *strdupa(const char *s); char *strndupa(const char *s, size_t n); int strcoll(const char *s1, const char *s2); char *strstr(const char *haystack, const char *needle); char *strcasestr(const char *haystack, const char *needle); char *strchr(const char *s, char c); char *strrchr(const char *s, char c); char *strchrnul(const char *s, char c); char* strtok(char *str, const char *delim); char* strtok_r(char *str, const char *delim, char **saveptr); char* strpbrk(const char *s, const char *accept); size_t strspn(const char *s, const char *accept); size_t strcspn(const char *s, const char *reject); char* strsep(char **stringp, const char *delim); void memcpy(void *dest, const void *src, size_t n); void memset(void *s, int c, size_t n); int memcmp(const void *s1, const void *s2, size_t n); void memmove(void *dest, const void *src, size_t n); void *memchr(const void *s, int c, size_t n); void *memrchr(const void *s, int c, size_t n); void *rawmemchr(const void *s, int c); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // stdio #include int printf(const char *format, ...); int fprintf(FILE *stream, const char *format, ...); int dprintf(int fd, const char *format, ...); int sprintf(void *dest, const char *format, ...); int snprintf(void *dest, size_t size, const char *format, ...); int fputc(char c, FILE *stream); int fputs(const char *s, FILE *stream); int putc(char c, FILE *stream); int putchar(char c); int puts(const char *s); char fgetc(FILE *stream); char *fgets(void *s, int size, FILE *stream); char getc(FILE *stream); char getchar(void); char ungetc(char c, FILE *stream); char *getenv(const char *name); int setenv(const char *name, const char *value, int overwrite); int unsetenv(const char *name); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // file #include #include enum uft_open_flag { O_RDONLY = 00, O_WRONLY = 01, O_RDWR = 02, O_CREAT = 0100, O_EXCL = 0200, O_NOCTTY = 0400, O_TRUNC = 01000, O_APPEND = 02000, O_NONBLOCK = 04000, O_DSYNC = 010000, O_ASYNC = 020000, O_DIRECT = 040000, O_LARGEFILE = 0100000, O_DIRECTORY = 0200000, O_NOFOLLOW = 0400000, O_NOATIME = 01000000, O_CLOEXEC = 02000000, O_SYNC = 04010000, O_PATH = 010000000, }; int open(const char* pathname, enum uft_open_flag flags); int open64(const char* pathname, enum uft_open_flag flags); int openat(int fd, const char* pathname, enum uft_open_flag flags); int open64at(int fd, const char* pathname, enum uft_open_flag flags); int close(int fd); enum uft_fcntl_cmd { F_DUPFD, F_GETFD, F_SETFD, F_GETFL, F_SETFL, F_GETLK, F_SETLK, F_SETLKW, F_SETOWN, F_GETOWN, F_SEGSIG, F_GETSIG, F_GETLK64, F_SETLK64, F_SETLKW64, F_SETOWN_EX, F_GETOWN_EX, }; int fcntl(int fd, enum uft_fcntl_cmd); int fcntl64(int fd, enum uft_fcntl_cmd); enum uft_seek_whence { SEEK_SET, SEEK_CUR, SEEK_END, SEEK_DATA, SEEK_HOLE, }; off_t lseek(int fd, off_t offset, enum uft_seek_whence whence); enum uft_falloc_mde { FALLOC_FL_KEEP_SIZE = 1, FALLOC_FL_PUNCH_HOLE = 2, FALLOC_FL_NO_HIDE_STALE = 4, FALLOC_FL_COLLAPSE_RANGE = 8, FALLOC_FL_ZERO_RANGE = 16, FALLOC_FL_INSERT_RANGE = 32, FALLOC_FL_UNSHARE_RANGE = 64, }; int fallocate(int fd, enum uft_falloc_mode mode, off_t off, off_t len); int fsync(int fd); int fdatasync(int fd); FILE *fopen(const char *path, const char *mode); FILE *fopen64(const char *filename, const char *type); FILE *fdopen(int fd, const char *mode); FILE *freopen(const char *path, const char *mode, FILE *stream); int fclose(FILE *stream); int fseek(FILE *stream, long offset, int whence); long ftell(FILE *stream); int fflush(FILE *stream); ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); int feof(FILE *stream); int ferror(FILE *stream); int fileno(FILE *stream); enum uft_access_flag { F_OK = 0, X_OK = 1, W_OK = 2, R_OK = 4, }; int access(const char *pathname, enum uft_access_flag mode); int unlink(const char *pathname); int unlinkat(int dirfd, const char *pathname, int flags); int mkdir(const char *pathname, mode_t mode); int rmdir(const char *pathname); int chdir(const char *pathname); #include void * opendir(const char *name); int closedir(void *dirp); char * getcwd(void *buf, size_t size); #include char *dirname(char *path); char *basename(char *path); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // fork and exec pid_t fork(void); pid_t vfork(void); int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg, ...); //int execv(const char *path, char *const argv[]); // cannot understand argv type int execv(const char *path, ...); //int execvp(const char *file, char *const argv[]); // cannot understand argv type int execvp(const char *file, ...); //int execve(const char *file, char *const argv[], char *const envp[]); int execve(const char *file, ...); //int execvpe(const char *file, char *const argv[], char *const envp[]); int execvpe(const char *file, ...); #include pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options); pid_t getpid(void); pid_t getppid(void); pid_t gettid(void); #include enum uft_dlopen_flag { RTLD_LOCAL = 0, RTLD_LAZY = 1, RTLD_NOW = 2, RTLD_NOLOAD = 4, RTLD_DEEPBIND = 8, RTLD_GLOBAL = 0x100, RTLD_NODELETE = 0x1000, }; void *dlopen(const char *filename, enum uft_dlopen_flag flags); void *dlmopen (Lmid_t lmid, const char *filename, int flags); void *dlsym(void *handle, const char *symbol); void *dlvsym(void *handle, char *symbol, char *version); int dlclose(void *handle); char *dlerror(void); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // pthread #include //int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); int pthread_create(pthread_t *thread, const pthread_attr_t *attr, funcptr_t start_routine, void *arg); //int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)); int pthread_once(pthread_once_t *once_control, funcptr_t init_routine); int pthread_join(pthread_t thread, void **retval); int pthread_detach(pthread_t thread); int pthread_kill(pthread_t thread, int sig); int pthread_cancel(pthread_t thread); void pthread_exit(void *retval); int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_mutex_destroy(pthread_mutex_t *mutex); int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime); int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // socket #include enum uft_socket_domain { AF_UNSPEC = 0, AF_UNIX, AF_INET, AF_AX25, AF_IPX, AF_APPLETALK, AF_NETROM, AF_BRIDGE, AF_ATMPVC = 8, AF_X25, AF_INET6, AF_ROSE, AF_DECnet, AF_NETBEUI, AF_SECURITY, AF_KEY, AF_NETLINK = 16, AF_PACKET, AF_ASH, AF_ECONET, AF_ATMSVC, AF_RDS, AF_SNA, AF_IRDA, AF_PPPOX = 24, AF_WANPIPE, AF_LLC, AF_IB, AF_MPLS, AF_CAN, AF_TPIC, AF_BLUETOOTH, AF_IUCV = 32, AF_RXRPC, AF_ISDN, AF_PHONET, AF_IEEE802154, AF_CAIF, AF_ALG, AF_NFC, AF_VSOCK = 40, AF_KCM, AF_QIPCRTR, AF_SMC, }; enum uft_socket_type { SOCK_STREAM = 1, SOCK_DGRAM, SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET, SOCK_DCCP, SOCK_PACKET = 10, SOCK_NONBLOCK = 04000, SOCK_CLOEXEC = 02000000, }; enum uft_socket_flag { SOCK_NONBLOCK = 04000, SOCK_CLOEXEC = 02000000, }; int socket(enum uft_socket_domain domain, enum uft_socket_type type, int protocol); int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, enum uft_socket_flag flags); #include struct hostent *gethostbyname(const char *name); struct hostent *gethostbyaddr(const void *addr, socklen_t len, enum uft_socket_domain type); int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); void freeaddrinfo(struct addrinfo *res); #include #include int inet_pton(enum uft_socket_domain af, const char *src, void *dst); const char *inet_ntop(enum uft_socket_domain af, const void *src, char *dst, socklen_t size); int inet_aton(const char *cp, struct in_addr *inp); char *inet_ntoa(struct in_addr in); in_addr_t inet_addr(const char *cp); in_addr_t inet_network(const char *cp); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // signal #include // linux signal number enum uft_signal { SIGNULL = 0, SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGKILL = 9, SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGSTKFLT, SIGCHLD = 17, SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU, SIGXFSZ = 25, SIGVTALRM, SIGPROF, SIGWINCH, SIGPOLL, SIGPWR, SIGSYS, SIGRTMIN = 32, SIGRTMAX = 64, }; int kill(pid_t pid, enum uft_signal sig); int raise(enum uft_signal sig); long signal(enum uft_signal sig, funcptr_t handler); int sigaction(enum uft_signal signum, const struct sigaction *act, struct sigaction *oldact); int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, enum uft_signal signum); int sigdelset(sigset_t *set, enum uft_signal signum); int sigismember(const sigset_t *set, enum uft_signal signum); enum uft_sigmask { SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK }; int sigprocmask(enum uft_sigmask how, const sigset_t *set, sigset_t *oldset); int pthread_sigmask(enum uft_sigmask how, const sigset_t *set, sigset_t *oldset); //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // etc. #include enum uft_prctl_op { PR_SET_PDEATHSIG = 1, PR_GET_PDEATHSIG, PR_GET_DUMPABLE, PR_SET_DUMPABLE, PR_GET_UNALIGN = 5, PR_SET_UNALIGN, PR_GET_KEEPCAPS, PR_SET_KEEPCAPS, PR_GET_FPEMU = 9, PR_SET_FPEMU, PR_GET_FPEXC, PR_SET_FPEXC, PR_GET_TIMING = 13, PR_SET_TIMING, PR_SET_NAME, PR_GET_NAME, PR_GET_ENDIAN = 19, PR_SET_ENDIAN, PR_GET_SECCOMP, PR_SET_SECCOMP, PR_CAPBSET_READ = 23, PR_CAPBSET_DROP, PR_GET_TSC, PR_SET_TSC, PR_GET_SECUREBITS = 27, PR_SET_SECUREBITS, PR_SET_TIMERSLACK, PR_GET_TIMERSLACK, PR_TASK_PERF_EVENTS_DISABLE = 31, PR_TASK_PERF_EVENTS_ENABLE, PR_MCE_KILL = 33, PR_MCE_KILL_GET, PR_SET_MM, PR_SET_CHILD_SUBREAPER = 36, PR_GET_CHILD_SUBREAPER, PR_SET_NO_NEW_PRIVS = 38, PR_GET_NO_NEW_PRIVS, PR_GET_TID_ADDRESS, PR_SET_THP_DISABLE = 41, PR_GET_THP_DISABLE, PR_MPX_ENABLE_MANAGEMENT = 43, PR_MPX_DISABLE_MANAGEMENT, PR_SET_FP_MODE = 45, PR_GET_FP_MODE, PR_CAP_AMBIENT, }; int prctl(enum uft_prctl_op option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); #include int select(int nfds, void *rset, void *wset, void *eset, void *timeout); int pselect(int nfds, void *rset, void *wset, void *eset, void *timeout, sigset_t *mask); #include int poll(struct pollfd *fds, nfds_t nfds, int timeout); int ppoll(struct pollfd *fds, nfds_t nfds, void *timeout, sigset_t *mask); #include enum uft_epoll_op { EPOLL_CTL_ADD = 1, EPOLL_CTL_DEL, EPOLL_CTL_MOD }; int epoll_create(int size); int epoll_create1(int flags); int epoll_ctl(int efd, enum uft_epoll_op op, int fd, void *event); int epoll_wait(int efd, void *events, int max_event, int timeout); int epoll_pwait(int efd, void *events, int max_event, int timeout, sigset_t *mask); #include /* For SYS_xxx definitions */ long syscall(long number, ...); #include int ioctl(int fd, unsigned long request, ...); #include void textdomain(const char * domainname); void bindtextdomain(const char * domainname, const char * dirname); char *gettext (const char * msgid); char *dgettext (const char * domainname, const char * msgid); char *dcgettext (const char * domainname, const char * msgid, int category); #include enum uft_locale { LC_TYPE = 0, LC_NUMERIC, LC_TIME, LC_COLLATE, LC_MONETARY, LC_MESSAGES, LC_ALL, LC_PAPER, LC_NAME, LC_ADDRESS, LC_TELEPHONE, LC_MEASUREMENT, LC_IDENTIFICATION, }; char * setlocale(enum uft_locale category, const char * locale); #include int getopt(int argc, void *argv, const char * optstr); /* ignore struct option for longopts for now */ int getopt_long(int argc, void *argv, const char * optstr); int getopt_long_only(int argc, void *argv, const char * optstr); #include int stat(const char *pathname, void *statbuf); int fstat(int fd, void *statbuf); int lstat(const char *pathname, void *statbuf); enum uft_mode { mod_777 = 0777, mod_755 = 0755, mod_666 = 0666, mod_644 = 0644, mod_400 = 0400, mod_600 = 0600, mod_660 = 0660, mod_640 = 0640, mod_444 = 0444, mod_022 = 0022, mod_440 = 0440, mod_222 = 0222, mod_111 = 0111, mod_011 = 0011, mod_033 = 0033, mod_077 = 0077, }; int chmod(const char *pathname, enum uft_mode mode); int fchmod(int fd, enum uft_mode mode); void umask(enum uft_mode mask); int creat(const char *file, enum uft_mode mode); int creat64(const char *file, enum uft_mode mode); #include int isatty(int fd); uid_t getuid(void); uid_t getgid(void); uid_t geteuid(void); uid_t getegid(void); int setuid(uid_t id); int setgid(uid_t id); int seteuid(uid_t id); int setegid(uid_t id); int setreuid(uid_t ruid, uid_t euid); int setregid(uid_t rgid, uid_t egid); int setresuid(uid_t ruid, uid_t euid, uid_t suid); int setresgid(uid_t rgid, uid_t egid, uid_t sgid); int chown(const char *path, uid_t uid, uid_t gid); int lchown(const char *path, uid_t uid, uid_t gid); int fchown(int fd, uid_t uid, uid_t gid); int dup(int oldfd); int dup2(int oldfd, int newfd); unsigned sleep(unsigned seconds); int usleep(unsigned usec); #include enum uft_clockid_t { CLOCK_REALTIME = 0, CLOCK_MONOTONIC, CLOCK_PROCESS_CPUTIME_ID, CLOCK_THREAD_CPUTIME_ID, CLOCK_MONOTONIC_RAW, CLOCK_REALTIME_COARSE, CLOCK_MONOTONIC_COARSE, CLOCK_BOOTTIME, CLOCK_REALTIME_ALARM, CLOCK_BOOTTIME_ALARM, CLOCK_TAI = 11, }; int clock_getres(enum uft_clockid_t clk_id, struct timespec *res); int clock_gettime(enum uft_clockid_t clk_id, struct timespec *tp); int clock_settime(enum uft_clockid_t clk_id, const struct timespec *tp); //////////////////////////////////////////////////////////////////////////////// // clang-format on uftrace-0.15.2/misc/run-lcov.sh000077500000000000000000000021241455365734300163050ustar00rootroot00000000000000#!/bin/bash info_file=coverage.info report_dir=coverage.html with_branch=0 which lcov > /dev/null lcov_test=$? if [ 0 -ne $lcov_test ]; then echo "Error: cannot find 'lcov': Please install it first." exit $lcov_test fi usage() { echo "Usage: $0 [] -h print this message -o set output report dir as (default: coverage.html) -b generate the report with branch coverage (default: off) " exit 1 } while getopts "o:bh" opt do case "$opt" in o) report_dir=$OPTARG ;; b) with_branch=1 ;; h) usage ;; *) usage ;; esac done shift $((OPTIND - 1)) if [ $with_branch -eq 1 ]; then lcov --capture --rc lcov_branch_coverage=1 --directory . --output-file $info_file genhtml $info_file --branch-coverage --output-directory $report_dir else lcov --capture --directory . --output-file $info_file genhtml $info_file --output-directory $report_dir fi rm -f $info_file rmdir $report_dir 2> /dev/null if [ -d $report_dir ]; then echo -e "\nThe code coverage report is normally generated in $report_dir\n" fi uftrace-0.15.2/misc/symbols.c000066400000000000000000000117201455365734300160370ustar00rootroot00000000000000#include #include #include #include "uftrace.h" #include "utils/arch.h" #include "utils/dwarf.h" #include "utils/symbol.h" #include "utils/utils.h" #include "version.h" /* needs to print session info with symbol */ static bool needs_session; static struct option symbols_options[] = { { "data", required_argument, 0, 'd' }, { "verbose", no_argument, 0, 'v' }, }; static const char symbols_usage[] = "symbols " UFTRACE_VERSION "\n" "\n" " OPTION:\n" " -d, --data Use this DATA instead of uftrace.data\n" " -v, --verbose Be verbose\n" "\n"; struct symbols_opts { char *dirname; int idx; }; static void parse_option(int argc, char **argv, struct symbols_opts *opts) { bool done = false; while (!done) { int key, tmp; key = getopt_long(argc, argv, "d:v", symbols_options, &tmp); switch (key) { case 'd': opts->dirname = xstrdup(optarg); break; case 'v': debug++; dbg_domain[DBG_SYMBOL]++; break; case -1: done = true; break; default: printf("%s", symbols_usage); exit(1); } } opts->idx = optind; } static int print_session_symbol(struct uftrace_session *s, void *arg) { uint64_t addr = *(uint64_t *)arg; struct uftrace_symbol *sym; struct uftrace_dbg_loc *dloc; sym = find_symtabs(&s->sym_info, addr); if (sym == NULL) sym = session_find_dlsym(s, ~0ULL, addr); if (sym == NULL) return 0; printf(" %s", sym->name); dloc = find_file_line(&s->sym_info, addr); if (dloc && dloc->file) printf(" (at %s:%d)", dloc->file->name, dloc->line); if (needs_session) printf(" [in %.*s]", SESSION_ID_LEN, s->sid); return 0; } static int read_sessions(struct uftrace_session_link *link, char *dirname) { FILE *fp; char *fname = NULL; char *line = NULL; size_t sz = 0; unsigned long sec, nsec; struct uftrace_msg_task tmsg; struct uftrace_msg_sess smsg; struct uftrace_msg_dlopen dlop; char *exename, *pos; int count = 0; xasprintf(&fname, "%s/%s", dirname, "task.txt"); fp = fopen(fname, "r"); if (fp == NULL) { free(fname); return -1; } pr_dbg("reading %s file\n", fname); while (getline(&line, &sz, fp) >= 0) { if (!strncmp(line, "TASK", 4)) { sscanf(line + 5, "timestamp=%lu.%lu tid=%d pid=%d", &sec, &nsec, &tmsg.tid, &tmsg.pid); tmsg.time = (uint64_t)sec * NSEC_PER_SEC + nsec; create_task(link, &tmsg, false); } else if (!strncmp(line, "FORK", 4)) { sscanf(line + 5, "timestamp=%lu.%lu pid=%d ppid=%d", &sec, &nsec, &tmsg.tid, &tmsg.pid); tmsg.time = (uint64_t)sec * NSEC_PER_SEC + nsec; create_task(link, &tmsg, true); } else if (!strncmp(line, "SESS", 4)) { sscanf(line + 5, "timestamp=%lu.%lu %*[^i]id=%d sid=%s", &sec, &nsec, &smsg.task.pid, (char *)&smsg.sid); // Get the execname pos = strstr(line, "exename="); if (pos == NULL) pr_err_ns("invalid task.txt format"); exename = pos + 8 + 1; // skip double-quote pos = strrchr(exename, '\"'); if (pos) *pos = '\0'; smsg.task.tid = smsg.task.pid; smsg.task.time = (uint64_t)sec * NSEC_PER_SEC + nsec; smsg.namelen = strlen(exename); create_session(link, &smsg, dirname, dirname, exename, true, true, false); count++; } else if (!strncmp(line, "DLOP", 4)) { struct uftrace_session *s; sscanf(line + 5, "timestamp=%lu.%lu tid=%d sid=%s base=%" PRIx64, &sec, &nsec, &dlop.task.tid, (char *)&dlop.sid, &dlop.base_addr); pos = strstr(line, "libname="); if (pos == NULL) pr_err_ns("invalid task.txt format"); exename = pos + 8 + 1; // skip double-quote pos = strrchr(exename, '\"'); if (pos) *pos = '\0'; dlop.task.pid = dlop.task.tid; dlop.task.time = (uint64_t)sec * NSEC_PER_SEC + nsec; dlop.namelen = strlen(exename); s = get_session_from_sid(link, dlop.sid); session_add_dlopen(s, dlop.task.time, dlop.base_addr, exename); } } if (count > 1) needs_session = true; free(line); fclose(fp); free(fname); return 0; } int main(int argc, char *argv[]) { int ret = 0; uint64_t addr; struct symbols_opts opts = { .dirname = UFTRACE_DIR_NAME, }; struct uftrace_session_link link = { .root = RB_ROOT, .tasks = RB_ROOT, }; outfp = stdout; logfp = stdout; parse_option(argc, argv, &opts); retry: if (read_sessions(&link, opts.dirname) < 0) { if (!strcmp(opts.dirname, UFTRACE_DIR_NAME)) { opts.dirname = "."; goto retry; } printf("read session failed\n"); ret = -1; goto out; } if (opts.idx < argc) { int i; for (i = opts.idx; i < argc; i++) { sscanf(argv[i], "%" PRIx64, &addr); printf("%" PRIx64 ":", addr); if (needs_session) putchar('\n'); walk_sessions(&link, print_session_symbol, &addr); putchar('\n'); } } else { char buf[4096]; while (fgets(buf, sizeof(buf), stdin)) { sscanf(buf, "%" PRIx64, &addr); printf("%" PRIx64 ":", addr); if (needs_session) putchar('\n'); walk_sessions(&link, print_session_symbol, &addr); putchar('\n'); } } out: delete_sessions(&link); return ret; } uftrace-0.15.2/misc/version.sh000077500000000000000000000033031455365734300162250ustar00rootroot00000000000000#!/bin/sh VERSION_FILE=$1 if [ $# -ne 4 ]; then echo "Usage: $0 " exit 1 fi CURR_VERSION=$2 FILE_VERSION= GIT_VERSION= ARCH=$3 SRCDIR=$4 if test -f ${VERSION_FILE}; then FILE_VERSION=$(cut -d'"' -f2 ${VERSION_FILE}) fi if test -d .git -a -n "$(git --version 2>/dev/null)"; then # update current version using git tags GIT_VERSION=$(git describe --tags --abbrev=4 --match="v[0-9].[0-9]*" 2>/dev/null) CURR_VERSION=${GIT_VERSION} fi if test -z "${GIT_VERSION}" -a -n "${FILE_VERSION}"; then # do not update file version if git version is not available exit 0 fi DEPS=" ${ARCH}" if test -f ${SRCDIR}/check-deps/have_libdw; then DEPS="${DEPS} dwarf" fi if test -f ${SRCDIR}/check-deps/have_libpython3; then DEPS="${DEPS} python3" elif test -f ${SRCDIR}/check-deps/have_libpython2.7; then DEPS="${DEPS} python2" fi if test -f ${SRCDIR}/check-deps/have_libluajit; then DEPS="${DEPS} luajit" fi if test -f ${SRCDIR}/check-deps/have_libncurses; then DEPS="${DEPS} tui" fi if test -f ${SRCDIR}/check-deps/perf_clockid; then DEPS="${DEPS} perf" fi if test -f ${SRCDIR}/check-deps/perf_context_switch; then DEPS="${DEPS} sched" fi if test -f ${SRCDIR}/check-deps/have_libcapstone; then DEPS="${DEPS} dynamic" fi if test -f ${SRCDIR}/check-deps/have_libtraceevent; then DEPS="${DEPS} kernel" fi if [ "x${DEPS}" != "x" ]; then DEPS=" (${DEPS} )" fi if test -z "${FILE_VERSION}" -o "${CURR_VERSION}${DEPS}" != "${FILE_VERSION}"; then # update file version only if it's different echo "#define UFTRACE_VERSION \"${CURR_VERSION}${DEPS}\"" > ${VERSION_FILE} echo " GEN " ${VERSION_FILE#${objdir}/} exit 0 fi uftrace-0.15.2/misc/wget-pr.sh000077500000000000000000000006051455365734300161270ustar00rootroot00000000000000#!/bin/sh if [ $# -ne "1" ]; then echo "usage: $0 " exit 1 fi pr=$1 if [ -x "$(command -v wget)" ]; then wget https://github.com/namhyung/uftrace/pull/$pr.patch exit 0 elif [ -x "$(command -v curl)" ]; then curl -L https://github.com/namhyung/uftrace/pull/$pr.patch > $pr.patch exit 0 else echo "You need wget or curl to run this script." exit 1 fi uftrace-0.15.2/pyproject.toml000066400000000000000000000002301455365734300161560ustar00rootroot00000000000000[tool.isort] profile = "black" lines_between_types = 0 lines_after_imports = 1 combine_as_imports = true ignore_whitespace = true skip_gitignore = true uftrace-0.15.2/python/000077500000000000000000000000001455365734300145705ustar00rootroot00000000000000uftrace-0.15.2/python/trace-python.c000066400000000000000000000712511455365734300173570ustar00rootroot00000000000000/* * python extension module to trace python functions for uftrace * * Copyright (C) 2023, Namhyung Kim * * Released under the GPL v2. */ #undef _XOPEN_SOURCE #define PY_SSIZE_T_CLEAN #include #include #include #include #include #include #include "uftrace.h" #include "utils/filter.h" #include "utils/list.h" #include "utils/rbtree.h" #include "utils/shmem.h" #include "utils/symbol.h" #include "utils/utils.h" /* python module state */ struct uftrace_py_state { PyObject *trace_func; }; /* pointer to python tracing function (for libpython2.7) */ static PyObject *uftrace_func __maybe_unused; /* RB tree of python_symbol to map code object to address */ static struct rb_root code_tree = RB_ROOT; /* name of the python script file it's running */ static char *main_file; /* pathname where the main python script is loaded from */ static char *main_dir; /* length of the main_dir (for comparison) */ static int main_dir_len; /* initial size of the symbol table and unit size for increment */ #define UFTRACE_PYTHON_SYMTAB_SIZE (1 * 1024 * 1024) /* size of the symbol table header (including the padding) */ #define UFTRACE_PYTHON_SYMTAB_HDRSZ (48) /* name of the shared memory region for symbol table: /uftrace-python-PID */ static char uftrace_shmem_name[32]; /* name of the shared memory region for debug info : /uftrace-python-dbg-PID */ static char uftrace_shmem_dbg_name[32]; /* file descriptor of the symbol table in a shared memory */ static int uftrace_shmem_fd; /* file descriptor of the debug info table in a shared memory */ static int uftrace_shmem_dbg_fd; /* current symbol table size */ static unsigned int uftrace_symtab_size; /* current debug info table size */ static unsigned int uftrace_dbginfo_size; /* python3 adds a C function frame for builtins.exec() */ static bool skip_first_frame; /* whether it should collect srcline info */ static bool need_dbg_info = true; /* * Symbol table header in a shared memory. * * It consists of count and offset, but they are combined into a val for * atomic update in case of multi-processing. It also has some padding * before the actual data, and it will be converted to comments when it * writes the symtab to a file. * * The rest area in the shared memory is the content of the symbol file. */ union uftrace_python_symtab { uint64_t val; /* for atomic update */ struct { uint32_t count; /* number of symbols */ uint32_t offset; /* next position to write */ }; char padding[UFTRACE_PYTHON_SYMTAB_HDRSZ]; }; /* maintain a symbol table for .sym file */ static union uftrace_python_symtab *symtab; /* just to maintain the same symbols for .dbg file */ static union uftrace_python_symtab *dbg_info; /* symbol table entry to maintain mappings from code to addr */ struct uftrace_python_symbol { struct rb_node node; PyObject *code; char *name; uint32_t addr; uint32_t flag; }; /* struct uftrace_python_symbol flags */ #define UFT_PYSYM_F_LIBCALL (1U << 0) /* linked list of filter names */ static LIST_HEAD(filters); /* filter entry - currently function filters supported only */ struct uftrace_python_filter { struct list_head list; struct uftrace_pattern p; enum filter_mode mode; }; /* track filter state - depth and time filters are handled in libmcount */ struct uftrace_python_filter_state { enum filter_mode mode; int count_in; int count_out; }; /* global filter state - multiprocess will have their own copy */ static struct uftrace_python_filter_state filter_state; /* control tracing of library calls (like python standard library) */ enum uftrace_python_libcall_mode { UFT_PY_LIBCALL_NONE, UFT_PY_LIBCALL_SINGLE, UFT_PY_LIBCALL_NESTED, }; /* global libcall state */ static enum uftrace_python_libcall_mode libcall_mode = UFT_PY_LIBCALL_SINGLE; /* * maintain the depth of library calls - multiprocess will have their own copy, * but it won't work with multi-threaded cases. */ static int libcall_count; /* functions in libmcount.so */ static void (*cygprof_enter)(unsigned long child, unsigned long parent); static void (*cygprof_exit)(unsigned long child, unsigned long parent); /* main trace function to be called from python interpreter */ static PyObject *uftrace_trace_python(PyObject *self, PyObject *args); /* hooking function of os._exit() for proper cleanup */ static PyObject *uftrace_trace_python_exit(PyObject *self, PyObject *obj); static __attribute__((used)) PyMethodDef uftrace_py_methods[] = { { "trace", uftrace_trace_python, METH_VARARGS, PyDoc_STR("trace python function with uftrace.") }, { "exit", uftrace_trace_python_exit, METH_O, PyDoc_STR("exit the target program with cleanup.") }, { NULL, NULL, 0, NULL }, }; static void find_cygprof_funcs(const char *filename, unsigned long base_addr) { struct uftrace_elf_data elf; struct uftrace_elf_iter iter; if (elf_init(filename, &elf) < 0) return; elf_for_each_shdr(&elf, &iter) { if (iter.shdr.sh_type == SHT_SYMTAB) break; } elf_for_each_symbol(&elf, &iter) { char *name = elf_get_name(&elf, &iter, iter.sym.st_name); if (!strcmp(name, "__cyg_profile_func_enter")) cygprof_enter = (void *)(intptr_t)(iter.sym.st_value + base_addr); if (!strcmp(name, "__cyg_profile_func_exit")) cygprof_exit = (void *)(intptr_t)(iter.sym.st_value + base_addr); } elf_finish(&elf); } static void find_libmcount_funcs(void) { char *line = NULL; size_t len = 0; FILE *fp = fopen("/proc/self/maps", "r"); if (fp == NULL) return; while (getline(&line, &len, fp) != -1) { unsigned long start, end; char prot[5]; char path[PATH_MAX]; if (sscanf(line, "%lx-%lx %s %*x %*x:%*x %*d %s\n", &start, &end, prot, path) != 4) continue; if (strncmp(basename(path), "libmcount", 9)) continue; find_cygprof_funcs(path, start); break; } free(line); fclose(fp); } static void init_symtab(void) { snprintf(uftrace_shmem_name, sizeof(uftrace_shmem_name), "/uftrace-python-%d", getpid()); uftrace_shmem_fd = uftrace_shmem_open(uftrace_shmem_name, O_RDWR | O_CREAT | O_TRUNC, 0600); if (uftrace_shmem_fd < 0) pr_err("failed to open shared memory for %s", uftrace_shmem_name); if (ftruncate(uftrace_shmem_fd, UFTRACE_PYTHON_SYMTAB_SIZE) < 0) pr_err("failed to allocate the shared memory for %s", uftrace_shmem_name); symtab = mmap(NULL, UFTRACE_PYTHON_SYMTAB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, uftrace_shmem_fd, 0); if (symtab == MAP_FAILED) pr_err("failed to mmap shared memory for %s", uftrace_shmem_name); symtab->count = 0; symtab->offset = UFTRACE_PYTHON_SYMTAB_HDRSZ; /* reserve some area for the header */ uftrace_symtab_size = UFTRACE_PYTHON_SYMTAB_SIZE; } static uint32_t get_new_sym_addr(const char *name, bool is_libcall) { union uftrace_python_symtab old_hdr, new_hdr, tmp_hdr; char *data = (void *)symtab; int entry_size = strlen(name) + 20; /* addr(16) + spaces(2) + type(1) + newline(1) */ old_hdr.val = symtab->val; /* this loop is needed to handle concurrent updates for multi-processing */ while (true) { new_hdr.count = old_hdr.count + 1; new_hdr.offset = old_hdr.offset + entry_size; /* atomic update of header (count + offset) */ tmp_hdr.val = __sync_val_compare_and_swap(&symtab->val, old_hdr.val, new_hdr.val); if (tmp_hdr.val == old_hdr.val) break; old_hdr.val = tmp_hdr.val; } if (new_hdr.offset >= uftrace_symtab_size) { unsigned new_symtab_size = uftrace_symtab_size + UFTRACE_PYTHON_SYMTAB_SIZE; pr_dbg("try to increase the shared memory for %s (new size=%uMB)\n", uftrace_shmem_name, new_symtab_size / (1024 * 1024)); /* increase the file size */ if (ftruncate(uftrace_shmem_fd, new_symtab_size) < 0) pr_err("failed to resize the shared memory for %s", uftrace_shmem_name); /* remap the symbol table, this might result in a new address */ data = mremap(symtab, uftrace_symtab_size, new_symtab_size, MREMAP_MAYMOVE); if (data == MAP_FAILED) pr_err("failed to mmap shared memory for %s", uftrace_shmem_name); /* update the address and size of the symbol table */ symtab = (void *)data; uftrace_symtab_size = new_symtab_size; } /* add the symbol table contents (in the old format) */ snprintf(data + old_hdr.offset, entry_size + 1, "%016x %c %s\n", new_hdr.count, is_libcall ? 'P' : 'T', name); return new_hdr.count; } static void write_symtab(const char *dirname) { char *filename = NULL; FILE *fp; void *buf = (void *)symtab; unsigned len; xasprintf(&filename, "%s/%s.sym", dirname, UFTRACE_PYTHON_SYMTAB_NAME); fp = fopen(filename, "w"); free(filename); if (fp == NULL) { pr_warn("writing symbol table of python program failed: %m"); return; } pr_dbg("writing the python symbol table (count=%u)\n", symtab->count); /* update the header comment */ len = fprintf(fp, "# symbols: %u\n", symtab->count); len += fprintf(fp, "# path name: %s\n", UFTRACE_PYTHON_SYMTAB_NAME); len += fprintf(fp, "#%*s\n", UFTRACE_PYTHON_SYMTAB_HDRSZ - 2 - len, ""); if (len != UFTRACE_PYTHON_SYMTAB_HDRSZ) pr_warn("symbol header size should be 64: %u", len); /* copy rest of the shmem buffer to the file */ buf += UFTRACE_PYTHON_SYMTAB_HDRSZ; len = symtab->offset - UFTRACE_PYTHON_SYMTAB_HDRSZ; while (len) { int size = fwrite(buf, 1, len, fp); if (size < 0) pr_err("failed to write python symbol file"); len -= size; buf += size; } /* special symbol needed for the old symbol file format */ fprintf(fp, "%016x %c %s\n", symtab->count + 1, '?', "__sym_end"); fclose(fp); munmap(symtab, uftrace_symtab_size); close(uftrace_shmem_fd); uftrace_shmem_unlink(uftrace_shmem_name); } static void init_dbginfo(void) { snprintf(uftrace_shmem_dbg_name, sizeof(uftrace_shmem_dbg_name), "/uftrace-python-dbg-%d", getpid()); uftrace_shmem_dbg_fd = uftrace_shmem_open(uftrace_shmem_dbg_name, O_RDWR | O_CREAT | O_TRUNC, 0600); if (uftrace_shmem_dbg_fd < 0) pr_err("failed to open shared memory for %s", uftrace_shmem_dbg_name); if (ftruncate(uftrace_shmem_dbg_fd, UFTRACE_PYTHON_SYMTAB_SIZE) < 0) pr_err("failed to allocate the shared memory for %s", uftrace_shmem_dbg_name); dbg_info = mmap(NULL, UFTRACE_PYTHON_SYMTAB_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, uftrace_shmem_dbg_fd, 0); if (dbg_info == MAP_FAILED) pr_err("failed to mmap shared memory for %s", uftrace_shmem_dbg_name); dbg_info->count = 0; dbg_info->offset = UFTRACE_PYTHON_SYMTAB_HDRSZ; /* reserve some area for the header */ uftrace_dbginfo_size = UFTRACE_PYTHON_SYMTAB_SIZE; } static void update_dbg_info(const char *name, uint64_t addr, const char *file, int line) { union uftrace_python_symtab old_hdr, new_hdr, tmp_hdr; char *data = (void *)dbg_info; char *buf = NULL; int entry_size = xasprintf(&buf, "F: %" PRIx64 " %s\nL: %d %s\n", addr, name, line, file); old_hdr.val = dbg_info->val; /* this loop is needed to handle concurrent updates for multi-processing */ while (true) { new_hdr.count = old_hdr.count + 1; new_hdr.offset = old_hdr.offset + entry_size; /* atomic update of header (count + offset) */ tmp_hdr.val = __sync_val_compare_and_swap(&dbg_info->val, old_hdr.val, new_hdr.val); if (tmp_hdr.val == old_hdr.val) break; old_hdr.val = tmp_hdr.val; } if (new_hdr.offset >= uftrace_symtab_size) { unsigned new_dbginfo_size = uftrace_dbginfo_size + UFTRACE_PYTHON_SYMTAB_SIZE; pr_dbg("try to increase the shared memory for %s (new size=%uMB)\n", uftrace_shmem_dbg_name, new_dbginfo_size / (1024 * 1024)); /* increase the file size */ if (ftruncate(uftrace_shmem_dbg_fd, new_dbginfo_size) < 0) pr_err("failed to resize the shared memory for %s", uftrace_shmem_dbg_name); /* remap the debug info, this might result in a new address */ data = mremap(dbg_info, uftrace_dbginfo_size, new_dbginfo_size, MREMAP_MAYMOVE); if (data == MAP_FAILED) pr_err("failed to mmap shared memory for %s", uftrace_shmem_dbg_name); /* update the address and size of the debug info table */ dbg_info = (void *)data; uftrace_dbginfo_size = new_dbginfo_size; } /* add the debug info file contents */ snprintf(data + old_hdr.offset, entry_size + 1, "%s", buf); free(buf); } static void write_dbginfo(const char *dirname) { char *filename = NULL; FILE *fp; void *buf = (void *)dbg_info; unsigned len = 0; xasprintf(&filename, "%s/%s.dbg", dirname, UFTRACE_PYTHON_SYMTAB_NAME); fp = fopen(filename, "w"); free(filename); if (fp == NULL) { pr_warn("writing debug info of python program failed: %m"); return; } pr_dbg("writing the python debug info (count=%u)\n", dbg_info->count); /* update the header comment */ len += fprintf(fp, "# path name: %s\n", UFTRACE_PYTHON_SYMTAB_NAME); len += fprintf(fp, "#%*s\n", UFTRACE_PYTHON_SYMTAB_HDRSZ - 2 - len, ""); if (len != UFTRACE_PYTHON_SYMTAB_HDRSZ) pr_warn("debug info header size should be %d: %u", UFTRACE_PYTHON_SYMTAB_HDRSZ, len); /* copy rest of the shmem buffer to the file */ buf += UFTRACE_PYTHON_SYMTAB_HDRSZ; len = dbg_info->offset - UFTRACE_PYTHON_SYMTAB_HDRSZ; while (len) { int size = fwrite(buf, 1, len, fp); if (size < 0) pr_err("failed to write python symbol file"); len -= size; buf += size; } fclose(fp); munmap(dbg_info, uftrace_dbginfo_size); close(uftrace_shmem_dbg_fd); uftrace_shmem_unlink(uftrace_shmem_dbg_name); } static void init_filters(void) { char *filter_str = getenv("UFTRACE_FILTER"); char *pattern_str = getenv("UFTRACE_PATTERN"); enum uftrace_pattern_type ptype = PATT_REGEX; struct strv fsv = STRV_INIT; char *str; int i; if (filter_str == NULL) { filter_state.mode = FILTER_MODE_NONE; return; } if (pattern_str) { if (!strcmp(pattern_str, "glob")) ptype = PATT_GLOB; else if (!strcmp(pattern_str, "simple")) ptype = PATT_SIMPLE; } filter_state.mode = FILTER_MODE_OUT; strv_split(&fsv, filter_str, ";"); strv_for_each(&fsv, str, i) { struct uftrace_python_filter *filter; filter = xmalloc(sizeof(*filter)); if (*str == '!') { filter->mode = FILTER_MODE_OUT; str++; } else { filter->mode = FILTER_MODE_IN; filter_state.mode = FILTER_MODE_IN; } if (strpbrk(str, REGEX_CHARS)) filter->p.type = ptype; else filter->p.type = PATT_SIMPLE; filter->p.patt = xstrdup(str); if (filter->p.type == PATT_REGEX) regcomp(&filter->p.re, filter->p.patt, REG_NOSUB | REG_EXTENDED); list_add_tail(&filter->list, &filters); } strv_free(&fsv); } static bool match_filter(struct uftrace_python_filter *filter, const char *fname) { switch (filter->p.type) { case PATT_SIMPLE: return !strcmp(filter->p.patt, fname); case PATT_REGEX: return !regexec(&filter->p.re, fname, 0, NULL, 0); case PATT_GLOB: return !fnmatch(filter->p.patt, fname, 0); default: return false; } } /* returns true if the current event should be skipped */ static bool apply_filters(const char *event, struct uftrace_python_symbol *sym, bool is_pyfunc) { struct uftrace_python_filter *filter; bool is_entry = !strcmp(event, "call") || !strcmp(event, "c_call"); int delta = is_entry ? 1 : -1; list_for_each_entry(filter, &filters, list) { if (!match_filter(filter, sym->name)) continue; if (filter->mode == FILTER_MODE_IN) filter_state.count_in += delta; else if (filter->mode == FILTER_MODE_OUT) filter_state.count_out += delta; break; } if (list_no_entry(filter, &filters, list)) filter = NULL; if (filter_state.count_out > 0) return true; if (filter_state.mode == FILTER_MODE_IN) { if (filter_state.count_in > 0) return false; if (filter && filter->mode == FILTER_MODE_IN && !is_entry) return false; return true; } if (filter_state.mode == FILTER_MODE_OUT) { if (filter && filter->mode == FILTER_MODE_OUT && !is_entry) return true; } return false; } static void remove_filters(void) { struct uftrace_python_filter *filter, *tmp; list_for_each_entry_safe(filter, tmp, &filters, list) { list_del(&filter->list); if (filter->p.type == PATT_REGEX) regfree(&filter->p.re); free(filter->p.patt); free(filter); } } static bool can_trace(bool is_entry, struct uftrace_python_symbol *sym) { /* always trace functions in the main module */ if ((sym->flag & UFT_PYSYM_F_LIBCALL) == 0) return true; if (libcall_mode == UFT_PY_LIBCALL_NONE) return false; if (libcall_mode == UFT_PY_LIBCALL_NESTED) return true; /* allow single-depth libcalls only */ if (is_entry) { if (libcall_count++ > 0) return false; } else { if (--libcall_count > 0) return false; if (unlikely(libcall_count < 0)) libcall_count = 0; } return true; } static void init_uftrace(void) { const char *libcall = getenv("UFTRACE_PY_LIBCALL"); const char *pymain = getenv("UFTRACE_PYMAIN"); char *p; /* check if it's loaded in a uftrace session */ if (getenv("UFTRACE_SHMEM") == NULL) return; if (getenv("UFTRACE_DEBUG")) { debug = 1; dbg_domain[DBG_UFTRACE] = 1; } if (getenv("UFTRACE_SRCLINE")) need_dbg_info = true; /* UFTRACE_PYMAIN was set in uftrace.py. */ if (pymain != NULL) { main_file = xstrdup(pymain); /* main_dir keeps the dirname of the main file */ if (main_file[0] != '/') main_dir = realpath(main_file, NULL); else main_dir = xstrdup(main_file); /* get dirname of main_file */ p = strrchr(main_dir, '/'); if (p && p != main_dir) *p = '\0'; main_dir_len = strlen(main_dir); pr_dbg2("main module is loaded from: %s\n", main_dir); } if (libcall != NULL) { if (!strcmp(libcall, "NONE")) libcall_mode = UFT_PY_LIBCALL_NONE; if (!strcmp(libcall, "NESTED")) libcall_mode = UFT_PY_LIBCALL_NESTED; } init_symtab(); if (need_dbg_info) init_dbginfo(); find_libmcount_funcs(); init_filters(); } /* due to Python API usage, we need to exclude this part for unit testing. */ #ifndef UNIT_TEST #ifdef HAVE_LIBPYTHON3 /* this is called during GC traversal */ static int uftrace_py_traverse(PyObject *m, visitproc visit, void *arg) { struct uftrace_py_state *state; struct rb_node *node; struct uftrace_python_symbol *sym; state = PyModule_GetState(m); Py_VISIT(state->trace_func); node = rb_first(&code_tree); while (node) { sym = rb_entry(node, struct uftrace_python_symbol, node); Py_VISIT(sym->code); node = rb_next(node); } return 0; } /* this is called before the module is deallocated */ static int uftrace_py_clear(PyObject *m) { struct uftrace_py_state *state; struct rb_node *node; struct uftrace_python_symbol *sym; state = PyModule_GetState(m); Py_CLEAR(state->trace_func); node = rb_first(&code_tree); while (node) { sym = rb_entry(node, struct uftrace_python_symbol, node); Py_CLEAR(sym->code); node = rb_next(node); } return 0; } static void uftrace_py_free(void *arg) { /* do nothing for now */ } static struct PyModuleDef uftrace_module = { PyModuleDef_HEAD_INIT, UFTRACE_PYTHON_MODULE_NAME, PyDoc_STR("C extension module to trace python functions with uftrace"), sizeof(struct uftrace_py_state), uftrace_py_methods, NULL, /* slots */ uftrace_py_traverse, uftrace_py_clear, uftrace_py_free, }; static PyObject *get_trace_function(void) { PyObject *mod; struct uftrace_py_state *state; mod = PyState_FindModule(&uftrace_module); if (mod == NULL) Py_RETURN_NONE; state = PyModule_GetState(mod); Py_INCREF(state->trace_func); return state->trace_func; } static bool is_string_type(PyObject *utf8) { return PyUnicode_Check(utf8); } static char *get_c_string(PyObject *utf8) { return (char *)PyUnicode_AsUTF8(utf8); } /* the name should be 'PyInit_' + */ PyMODINIT_FUNC PyInit_uftrace_python(void) { PyObject *m, *d, *f; struct uftrace_py_state *s; outfp = stdout; logfp = stdout; m = PyModule_Create(&uftrace_module); if (m == NULL) return NULL; d = PyModule_GetDict(m); f = PyDict_GetItemString(d, "trace"); /* keep the pointer to trace function as it's used as a return value */ s = PyModule_GetState(m); s->trace_func = f; skip_first_frame = true; init_uftrace(); return m; } #else /* HAVE_LIBPYTHON2 */ /* the name should be 'init' + */ PyMODINIT_FUNC inituftrace_python(void) { PyObject *m, *d; outfp = stdout; logfp = stdout; m = Py_InitModule(UFTRACE_PYTHON_MODULE_NAME, uftrace_py_methods); if (m == NULL) return; d = PyModule_GetDict(m); /* keep the pointer to trace function as it's used as a return value */ uftrace_func = PyDict_GetItemString(d, "trace"); init_uftrace(); } static PyObject *get_trace_function(void) { Py_INCREF(uftrace_func); return uftrace_func; } static bool is_string_type(PyObject *str) { return PyString_Check(str); } static char *get_c_string(PyObject *str) { return (char *)PyString_AsString(str); } #endif /* HAVE_LIBPYTHON2 */ static char *get_python_funcname(PyObject *frame, PyObject *code, bool *is_main) { PyObject *name, *global; char *func_name = NULL; *is_main = false; if (PyObject_HasAttrString(code, "co_qualname")) name = PyObject_GetAttrString(code, "co_qualname"); else name = PyObject_GetAttrString(code, "co_name"); /* prepend module name if available */ global = PyObject_GetAttrString(frame, "f_globals"); if (global && name) { PyObject *mod = PyDict_GetItemString(global, "__name__"); char *name_str = get_c_string(name); /* 'mod' is a borrowed reference */ if (mod && is_string_type(mod)) { char *mod_str = get_c_string(mod); /* skip __main__. prefix for functions in the main module */ if (!strcmp(mod_str, "__main__")) *is_main = true; if (!*is_main || !strcmp(name_str, "")) xasprintf(&func_name, "%s.%s", mod_str, name_str); } Py_DECREF(global); } if (func_name == NULL && name) func_name = strdup(get_c_string(name)); Py_XDECREF(name); return func_name; } static char *get_c_funcname(PyObject *frame, PyObject *code) { PyObject *name, *mod; PyCFunctionObject *cfunc; char *func_name = NULL; if (!PyCFunction_Check(code)) return NULL; cfunc = (void *)code; if (PyObject_HasAttrString(code, "__qualname__")) name = PyObject_GetAttrString(code, "__qualname__"); else name = PyObject_GetAttrString(code, "__name__"); /* prepend module name if available */ mod = cfunc->m_module; if (mod && is_string_type(mod)) xasprintf(&func_name, "%s.%s", get_c_string(mod), get_c_string(name)); else if (strchr(get_c_string(name), '.')) func_name = xstrdup(get_c_string(name)); else xasprintf(&func_name, "%s.%s", "builtins", get_c_string(name)); Py_XDECREF(name); return func_name; } static struct uftrace_python_symbol *convert_function_addr(PyObject *frame, PyObject *args, bool is_pyfunc) { struct rb_node *parent = NULL; struct rb_node **p = &code_tree.rb_node; struct uftrace_python_symbol *iter, *new_sym; PyObject *code; const char *file_name = NULL; char *func_name; bool is_main = false; if (is_pyfunc) { code = PyObject_GetAttrString(frame, "f_code"); if (code == NULL) return NULL; } else { code = args; Py_INCREF(code); } while (*p) { parent = *p; iter = rb_entry(parent, struct uftrace_python_symbol, node); /* just compare pointers of the code object */ if (iter->code == code) { Py_DECREF(code); return iter; } if (iter->code < code) p = &parent->rb_left; else p = &parent->rb_right; } if (is_pyfunc) func_name = get_python_funcname(frame, code, &is_main); else func_name = get_c_funcname(frame, code); if (func_name == NULL) return NULL; if (main_dir && PyObject_HasAttrString(code, "co_filename")) { PyObject *obj; obj = PyObject_GetAttrString(code, "co_filename"); file_name = get_c_string(obj); Py_DECREF(obj); /* check if this function is from the same directory as the main script */ if (!strncmp(file_name, main_dir, main_dir_len) && file_name[main_dir_len] == '/') is_main = true; } new_sym = xmalloc(sizeof(*new_sym)); new_sym->code = code; new_sym->addr = get_new_sym_addr(func_name, !is_main); new_sym->name = func_name; new_sym->flag = is_main ? 0 : UFT_PYSYM_F_LIBCALL; if (need_dbg_info) { if (file_name && PyObject_HasAttrString(code, "co_firstlineno")) { PyObject *obj; int line; if (!strcmp(file_name, "") && main_file) file_name = main_file; obj = PyObject_GetAttrString(code, "co_firstlineno"); line = PyLong_AsLong(obj); Py_DECREF(obj); update_dbg_info(func_name, new_sym->addr, file_name, line); } } /* keep the refcount of the code object to keep it alive */ rb_link_node(&new_sym->node, parent, p); rb_insert_color(&new_sym->node, &code_tree); return new_sym; } /* * This is the actual trace function to be called for each python event. */ static PyObject *uftrace_trace_python(PyObject *self, PyObject *args) { PyObject *frame, *args_tuple; static PyObject *first_frame; const char *event; struct uftrace_python_symbol *sym; bool is_pyfunc; if (!PyArg_ParseTuple(args, "OsO", &frame, &event, &args_tuple)) Py_RETURN_NONE; if (first_frame == NULL) first_frame = frame; /* skip the first frame: builtins.exec() */ if (skip_first_frame && frame == first_frame) Py_RETURN_NONE; is_pyfunc = !strcmp(event, "call") || !strcmp(event, "return"); sym = convert_function_addr(frame, args_tuple, is_pyfunc); if (sym == NULL) Py_RETURN_NONE; if (filter_state.mode != FILTER_MODE_NONE && apply_filters(event, sym, is_pyfunc)) Py_RETURN_NONE; if (!strcmp(event, "call") || !strcmp(event, "c_call")) { if (can_trace(true, sym)) cygprof_enter(sym->addr, 0); else Py_RETURN_NONE; } else if (!strcmp(event, "return") || !strcmp(event, "c_return")) { if (can_trace(false, sym)) cygprof_exit(0, 0); else Py_RETURN_NONE; } else if (!strcmp(event, "c_exception")) { /* C code exception doesn't generate c_return */ if (can_trace(false, sym)) cygprof_exit(0, 0); else Py_RETURN_NONE; } return get_trace_function(); } static void __attribute__((destructor)) uftrace_trace_python_finish(void) { const char *dirname; dirname = getenv("UFTRACE_DIR"); if (dirname == NULL) dirname = UFTRACE_DIR_NAME; write_symtab(dirname); if (need_dbg_info) write_dbginfo(dirname); remove_filters(); free(main_file); free(main_dir); } static PyObject *uftrace_trace_python_exit(PyObject *self, PyObject *obj) { int n = PyLong_AsLong(obj); uftrace_trace_python_finish(); _exit(n); return NULL; } #else /* UNIT_TEST */ static PyObject *uftrace_trace_python(PyObject *self, PyObject *args) { /* just to suppress compiler warnings */ skip_first_frame = false; code_tree = code_tree; return NULL; } static PyObject *uftrace_trace_python_exit(PyObject *self, PyObject *obj) { return NULL; } TEST_CASE(python_symtab) { char buf[32]; /* should have no effect */ init_uftrace(); pr_dbg("initialize symbol table on a shared memory\n"); init_symtab(); TEST_NE(symtab, MAP_FAILED); TEST_EQ(get_new_sym_addr("a", true), 1); TEST_EQ(get_new_sym_addr("b", true), 2); TEST_EQ(get_new_sym_addr("c", false), 3); write_symtab("."); snprintf(buf, sizeof(buf), "%s.sym", UFTRACE_PYTHON_SYMTAB_NAME); unlink(buf); pr_dbg("unlink the symbol table: %s\n", buf); return TEST_OK; } TEST_CASE(python_dbginfo) { char buf[32]; /* should have no effect */ init_uftrace(); need_dbg_info = true; pr_dbg("initialize debug info on a shared memory\n"); init_dbginfo(); TEST_NE(dbg_info, MAP_FAILED); update_dbg_info("a", 1, __FILE__, __LINE__); TEST_EQ(dbg_info->count, 1); update_dbg_info("b", 2, __FILE__, __LINE__); TEST_EQ(dbg_info->count, 2); update_dbg_info("c", 3, __FILE__, __LINE__); TEST_EQ(dbg_info->count, 3); write_dbginfo("."); snprintf(buf, sizeof(buf), "%s.dbg", UFTRACE_PYTHON_SYMTAB_NAME); unlink(buf); pr_dbg("unlink the debug info: %s\n", buf); return TEST_OK; } TEST_CASE(python_filter) { struct uftrace_python_symbol sym = { .name = "test.sym", }; struct uftrace_python_filter *filter; setenv("UFTRACE_FILTER", "^test", 1); init_filters(); TEST_EQ(list_empty(&filters), false); filter = list_first_entry(&filters, struct uftrace_python_filter, list); pr_dbg("match filter patterns\n"); TEST_EQ(match_filter(filter, "test.abc"), true); TEST_EQ(match_filter(filter, "xyz.test"), false); pr_dbg("apply filters\n"); TEST_EQ(apply_filters("call", &sym, true), false); remove_filters(); return TEST_OK; } TEST_CASE(python_libcall) { struct uftrace_python_symbol syms[] = { { .name = "foo", }, { .name = "bar", .flag = UFT_PYSYM_F_LIBCALL, }, { .name = "baz", .flag = UFT_PYSYM_F_LIBCALL, }, }; int results_none[] = { 1, 0, 0 }; int results_single[] = { 1, 1, 0 }; int results_nested[] = { 1, 1, 1 }; pr_dbg("checking no libcall\n"); libcall_mode = UFT_PY_LIBCALL_NONE; libcall_count = 0; for (unsigned i = 0; i < ARRAY_SIZE(syms); i++) TEST_EQ(can_trace(true, &syms[i]), results_none[i]); pr_dbg("checking single libcall\n"); libcall_mode = UFT_PY_LIBCALL_SINGLE; libcall_count = 0; for (unsigned i = 0; i < ARRAY_SIZE(syms); i++) TEST_EQ(can_trace(true, &syms[i]), results_single[i]); pr_dbg("checking nested libcall\n"); libcall_mode = UFT_PY_LIBCALL_NESTED; libcall_count = 0; for (unsigned i = 0; i < ARRAY_SIZE(syms); i++) TEST_EQ(can_trace(true, &syms[i]), results_nested[i]); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/python/uftrace.py000066400000000000000000000023411455365734300165730ustar00rootroot00000000000000import os import sys sys.argv = sys.argv[1:len(sys.argv)] filename = sys.argv[0] if os.path.exists(filename) or filename[0] == '/': os.environ["UFTRACE_PYMAIN"] = filename if filename[0] == '/': pathname = filename else: pathname = os.getcwd() + '/' + filename else: for dir in os.environ["PATH"].split(":"): pathname = dir + '/' + filename try: f = open(pathname) sys.argv[0] = pathname os.environ["UFTRACE_PYMAIN"] = pathname f.close() break except OSError: continue # UFTRACE_PYMAIN must be set before importing uftrace_python import uftrace_python # Symbol and debug files are finally written at uftrace_trace_python_finish() # when program exits, but os._exit() terminates the program immediately so there # is no chance to write symbol and debug files at the destructor. # The os._exit() is hooked here to prevent this problem. def os_exit(n): uftrace_python.exit(n) os._exit = os_exit new_globals = globals() new_globals["__file__"] = pathname sys.path.insert(0, os.path.dirname(pathname)) code = open(sys.argv[0]).read() sys.setprofile(uftrace_python.trace) exec(code, new_globals) sys.setprofile(None) uftrace-0.15.2/scripts/000077500000000000000000000000001455365734300147365ustar00rootroot00000000000000uftrace-0.15.2/scripts/count.lua000066400000000000000000000002651455365734300165740ustar00rootroot00000000000000local count = 0 function uftrace_begin(ctx) end function uftrace_entry(ctx) count = count + 1 end function uftrace_exit(ctx) end function uftrace_end() print(count) end uftrace-0.15.2/scripts/count.py000066400000000000000000000002531455365734300164400ustar00rootroot00000000000000count = 0 def uftrace_begin(ctx): pass def uftrace_entry(ctx): global count count += 1 def uftrace_exit(ctx): pass def uftrace_end(): print(count) uftrace-0.15.2/scripts/dump.lua000066400000000000000000000037041455365734300164120ustar00rootroot00000000000000-- uftrace-option: --auto-args --nest-libcall -- UFTRACE_FUNCS = [ "foo", "bar" ] function uftrace_begin(ctx) print('uftrace_begin(ctx)') print(string.format(' record : %s', ctx['record'])) print(string.format(' version : %s', ctx['version'])) if ctx['cmds'] ~= nil then print(string.format(' cmds : %s', table.concat(ctx['cmds'], ' '))) end print('') end function uftrace_entry(ctx) local _tid = ctx['tid'] local _depth = ctx['depth'] local _time = ctx['timestamp'] -- _duration = ctx["duration"] -- exit only local _address = ctx['address'] local _name = ctx['name'] local unit = 1000000000 print(string.format('%d.%09d %6d: [entry] %s(%x) depth: %d', _time / unit, _time % unit, _tid, _name, _address, _depth)) if ctx['args'] ~= nil then for i, arg in ipairs(ctx['args']) do print(string.format(' args[%d] %s: %s', i, type(arg), arg)) end end end function uftrace_exit(ctx) local _tid = ctx['tid'] local _depth = ctx['depth'] local _time = ctx['timestamp'] local _duration = ctx["duration"] -- not used here local _address = ctx["address"] local _name = ctx["name"] local unit = 1000000000 print(string.format('%d.%09d %6d: [exit ] %s(%x) depth: %d', _time / unit, _time % unit, _tid, _name, _address, _depth)) if ctx['retval'] ~= nil then local ret = ctx['retval'] print(string.format(' retval %s: %s', type(ret), ret)) end end function uftrace_event(ctx) local _tid = ctx['tid'] local _time = ctx['timestamp'] local _address = ctx["address"] local _name = ctx["name"] local unit = 1000000000 print(string.format('%d.%09d %6d: [event] %s(%x)', _time / unit, _time % unit, _tid, _name, _address)) end function uftrace_end() print('\nuftrace_end()') end uftrace-0.15.2/scripts/dump.py000066400000000000000000000042611455365734300162600ustar00rootroot00000000000000# # dump.py # # Target program is executed with the given uftrace options below. # uftrace-option: --auto-args --nest-libcall # # uftrace_entry and uftrace_exit are executed only for listed functions. # UFTRACE_FUNCS = [ "foo", "bar" ] # uftrace_begin is optional, so can be omitted. def uftrace_begin(ctx): print("uftrace_begin(ctx)") print(" record : %s" % ctx["record"]) print(" version : %s" % ctx["version"]) if "cmds" in ctx: print(" cmds : %s" % " ".join(ctx["cmds"])) print("") # uftrace_entry is executed at the entry of each function. # if UFTRACE_FUNCS is defined, only the listed functions enter here. def uftrace_entry(ctx): _tid = ctx["tid"] _depth = ctx["depth"] _time = ctx["timestamp"] # _duration = ctx["duration"] # exit only _address = ctx["address"] _name = ctx["name"] unit = 10 ** 9 print("%d.%09d %6d: [entry] %s(%x) depth: %d" % (_time / unit, _time % unit, _tid, _name, _address, _depth)) if "args" in ctx: for i in range(len(ctx["args"])): arg = ctx["args"][i] print(" args[%d] %s: %s" % (i, type(arg), arg)) # uftrace_exit is executed at the exit of each function. # if UFTRACE_FUNCS is defined, only the listed functions enter here. def uftrace_exit(ctx): _tid = ctx["tid"] _depth = ctx["depth"] _time = ctx["timestamp"] _duration = ctx["duration"] # not used here _address = ctx["address"] _name = ctx["name"] unit = 10 ** 9 print("%d.%09d %6d: [exit ] %s(%x) depth: %d" % (_time / unit, _time % unit, _tid, _name, _address, _depth)) if "retval" in ctx: ret = ctx["retval"] print(" retval %s: %s" % (type(ret), ret)) # uftrace_event is executed for each event. def uftrace_event(ctx): _tid = ctx["tid"] _time = ctx["timestamp"] _address = ctx["address"] _name = ctx["name"] unit = 10 ** 9 print("%d.%09d %6d: [event] %s(%x)" % (_time / unit, _time % unit, _tid, _name, _address)) # uftrace_end is optional, so can be omitted. def uftrace_end(): print("\nuftrace_end()") uftrace-0.15.2/scripts/func-histogram.py000066400000000000000000000052141455365734300202400ustar00rootroot00000000000000# # func-histogram.py : print histogram of given function's execution time # # Usage: func-histogram.py [-- -u ] # Unit is one of ns, us, ms, s, m # # $ uftrace script -S scripts/func-histogram.py read # histogram of function latency of 'read' # # < 0us : 0 ( 0.0 %) # < 1us : 0 ( 0.0 %) # < 2us : 5 ( 2.3 %) # < 4us : 11 ( 5.1 %) # < 8us : 24 ( 11.1 %) # < 16us : 41 ( 19.0 %) # < 32us : 68 ( 31.5 %) # < 64us : 49 ( 22.7 %) # < 128us : 16 ( 7.4 %) # < 256us : 2 ( 0.9 %) # < 512us : 0 ( 0.0 %) # < 1024us : 0 ( 0.0 %) # >= 1024us : 0 ( 0.0 %) # func = '' unit = 'us' histo = None divider = { 'ns': 1, 'us': 1000, 'ms': 1000000, 's': 1000000000, 'm': 60000000000, } def create_histogram(): h = [] # 1 ~ 1024 (logarithm) = 11 + 2 (= lower/upper bounds) for i in range(13): h.append(0) return h def get_histogram_index(val): if val < 0: return 0 val = int(val / divider[unit]) for i in range(11): if val < (1 << i): return i+1 return 12 def print_histogram(): print("histogram of function latency of '%s'\n" % func) total = sum(histo) if total == 0: print("no value") return print(" < %4d%-2s : %10d (%5.1f %%)" % (0, unit, histo[0], 100.0 * histo[0] / total)) for i in range(11): print(" < %4d%-2s : %10d (%5.1f %%)" % (1 << i, unit, histo[i+1], 100.0 * histo[i+1] / total)) print(" >= %4d%-2s : %10d (%5.1f %%)" % (1024, unit, histo[12], 100.0 * histo[12] / total)) def parse_args(args): global func, unit if args[0] == '-u' or args[0] == '--unit': unit = args[1] func = args[2] else: unit = 'us' func = args[0] # # uftrace interface functions # def uftrace_begin(ctx): global histo if len(ctx["cmds"]) == 0: print("Usage: func-histogram.py [-- -u ] ") print(" Unit is one of ns, us, ms, s or m") return parse_args(ctx["cmds"]) if unit not in divider: print("Usage: invalid unit: %s" % unit) return histo = create_histogram() def uftrace_entry(ctx): pass def uftrace_exit(ctx): global histo if histo is None: return if ctx["name"] != func: return if "duration" not in ctx: return dur = int(ctx["duration"]) idx = get_histogram_index(dur) histo[idx] += 1 def uftrace_end(): if histo is None: return print_histogram() uftrace-0.15.2/scripts/info.lua000066400000000000000000000004141455365734300163730ustar00rootroot00000000000000function uftrace_begin(ctx) print(ctx['record']) print(ctx['version']) io.write('(') for _, cmd in ipairs(ctx['cmds']) do io.write("'" .. cmd .. "', ") end print(')') end function uftrace_entry(ctx) end function uftrace_exit(ctx) end uftrace-0.15.2/scripts/info.py000066400000000000000000000002451455365734300162440ustar00rootroot00000000000000def uftrace_begin(ctx): print(ctx["record"]) print(ctx["version"]) print(ctx["cmds"]) def uftrace_entry(ctx): pass def uftrace_exit(ctx): pass uftrace-0.15.2/scripts/replay.lua000066400000000000000000000025611455365734300167410ustar00rootroot00000000000000function uftrace_begin(ctx) print('# DURATION TID FUNCTION') end function uftrace_entry(ctx) local _tid = ctx['tid'] local _depth = ctx['depth'] local _symname = ctx['name'] local indent = _depth * 2 local space = string.rep(' ', indent) local buf = string.format(' %10s [%6d] | %s%s() {', '', _tid, space, _symname) print(buf) end function uftrace_exit(ctx) local _tid = ctx['tid'] local _depth = ctx['depth'] local _symname = ctx['name'] local _duration = ctx['duration'] local indent = _depth * 2 local space = string.rep(' ', indent) local time_and_unit = get_time_and_unit(_duration) local time = time_and_unit[1] local unit = time_and_unit[2] local buf = string.format(' %7.3f %s [%6d] | %s}', time, unit, _tid, space) local buf = string.format('%s /* %s */', buf, _symname) print(buf) end function uftrace_end() end function get_time_and_unit(duration) local duration = duration local time_unit = '' local divider if duration < 100 then divider = 1 time_unit = 'ns' elseif duration < 1000000 then divider = 1000 time_unit = 'us' elseif duration < 1000000000 then divider = 1000000 time_unit = 'ms' else divider = 1000000000 time_unit = ' s' end return {duration / divider, time_unit} end uftrace-0.15.2/scripts/replay.py000066400000000000000000000021651455365734300166100ustar00rootroot00000000000000def uftrace_begin(ctx): print("# DURATION TID FUNCTION") def uftrace_entry(ctx): # read arguments _tid = ctx["tid"] _depth = ctx["depth"] _symname = ctx["name"] indent = _depth * 2 space = " " * indent buf = " %10s [%6d] | %s%s() {" % ("", _tid, space, _symname) print(buf) def uftrace_exit(ctx): # read arguments _tid = ctx["tid"] _depth = ctx["depth"] _symname = ctx["name"] _duration = ctx["duration"] indent = _depth * 2 space = " " * indent (time, unit) = get_time_and_unit(_duration) buf = " %7.3f %s [%6d] | %s}" % (time, unit, _tid, space) buf = "%s /* %s */" % (buf, _symname) print(buf) def uftrace_end(): pass def get_time_and_unit(duration): duration = float(duration) time_unit = "" if duration < 100: divider = 1 time_unit = "ns" elif duration < 1000000: divider = 1000 time_unit = "us" elif duration < 1000000000: divider = 1000000 time_unit = "ms" else: divider = 1000000000 time_unit = " s" return (duration / divider, time_unit) uftrace-0.15.2/scripts/report-libcall.py000066400000000000000000000015661455365734300202330ustar00rootroot00000000000000# # report-libcall.py # # uftrace-option: --nest-libcall -F .*@plt # import os libcall_map = {} def uftrace_begin(ctx): pass def uftrace_entry(ctx): _name = ctx["name"] if _name in libcall_map: libcall_map[_name] += 1 else: libcall_map[_name] = 1 def uftrace_exit(ctx): pass def uftrace_end(): global libcall_map sorted_dict = sorted(libcall_map.items(), key=lambda k: k[1], reverse=True) pid = os.getpid() with open("/proc/%s/comm" % pid) as proc_comm: comm = proc_comm.read()[:-1] print(" # Library Function Call Report for '%s' (pid: %d)\n" % (comm, pid)) print(" %15s %-#50s" % ("Call Count", "Library Functions")) print(" ================ ================================================") for item in sorted_dict: print(" %15d %-#50s" % (item[1], item[0])) print("\n") uftrace-0.15.2/scripts/retval-histogram.py000066400000000000000000000052111455365734300205770ustar00rootroot00000000000000# # retval-histogram.py : print histogram of given function's return values # # Usage: retval-histogram.py [-- -u ] # Unit is one of b, k, m, g # # $ uftrace script -S scripts/retval-histogram.py strlen # histogram of return value of 'strlen' # # < 0b : 0 ( 0.0 %) # < 1b : 0 ( 0.0 %) # < 2b : 5 ( 2.3 %) # < 4b : 11 ( 5.1 %) # < 8b : 24 ( 11.1 %) # < 16b : 41 ( 19.0 %) # < 32b : 68 ( 31.5 %) # < 64b : 49 ( 22.7 %) # < 128b : 16 ( 7.4 %) # < 256b : 2 ( 0.9 %) # < 512b : 0 ( 0.0 %) # < 1024b : 0 ( 0.0 %) # >= 1024b : 0 ( 0.0 %) # func = '' unit = 'b' histo = None divider = { 'b': 1, 'k': 1000, 'K': 1000, 'm': 1000000, 'M': 1000000, 'g': 1000000000, 'G': 1000000000, } def create_histogram(): h = [] # 1 ~ 1024 (logarithm) = 11 + 2 (= lower/upper bounds) for i in range(13): h.append(0) return h def get_histogram_index(val): if val < 0: return 0 val = int(val / divider[unit]) for i in range(11): if val < (1 << i): return i+1 return 12 def print_histogram(): print("histogram of return value of '%s'\n" % func) total = sum(histo) if total == 0: print("no value") return print(" < %4d%s : %10d (%5.1f %%)" % (0, unit, histo[0], 100.0 * histo[0] / total)) for i in range(11): print(" < %4d%s : %10d (%5.1f %%)" % (1 << i, unit, histo[i+1], 100.0 * histo[i+1] / total)) print(" >= %4d%s : %10d (%5.1f %%)" % (1024, unit, histo[12], 100.0 * histo[12] / total)) def parse_args(args): global func, unit if args[0] == '-u' or args[0] == '--unit': unit = args[1] func = args[2] else: unit = 'b' func = args[0] # # uftrace interface functions # def uftrace_begin(ctx): global histo if len(ctx["cmds"]) == 0: print("Usage: retval-histogram.py [-- -u ] ") print(" Unit is one of b, k, m, g") return parse_args(ctx["cmds"]) if unit not in divider: print("Usage: invalid unit: %s" % unit) return histo = create_histogram() def uftrace_entry(ctx): pass def uftrace_exit(ctx): global histo if histo is None: return if ctx["name"] != func: return if "retval" not in ctx: return retval = int(ctx["retval"]) idx = get_histogram_index(retval) histo[idx] += 1 def uftrace_end(): if histo is None: return print_histogram() uftrace-0.15.2/scripts/simple.lua000066400000000000000000000004671455365734300167410ustar00rootroot00000000000000function uftrace_begin(ctx) print('program begins...') end function uftrace_entry(ctx) func = ctx['name'] print('entry : ' .. func .. '()') end function uftrace_exit(ctx) func = ctx['name'] print('exit : ' .. func .. '()') end function uftrace_end() print('program is finished') end uftrace-0.15.2/scripts/simple.py000066400000000000000000000004231455365734300166000ustar00rootroot00000000000000def 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-0.15.2/scripts/strings.lua000066400000000000000000000020741455365734300171350ustar00rootroot00000000000000-- -- strings.lua - print the unique strings of runtime function arguments and return values. -- -- uftrace-option: --nest-libcall --auto-args -- local strset = {} function uftrace_entry(ctx) if ctx['args'] ~= nil then for i, arg in ipairs(ctx['args']) do if type(arg) == 'string' then arg = string.gsub(arg, '^%s+', '') arg = string.gsub(arg, '%s+$', '') if arg ~= '' and arg:sub(1, 8) ~= 'struct: ' then strset[arg] = true end end end end end function uftrace_exit(ctx) if ctx['retval'] ~= nil then local ret = ctx['retval'] if type(ctx['retval']) == 'string' then ret = string.gsub(ret, '^%s+', '') ret = string.gsub(ret, '%s+$', '') if ret ~= '' and ret:sub(1, 8) ~= 'struct: ' then strset[ret] = true end end end end function uftrace_end() for strval, _ in pairs(strset) do print('"' .. strval .. '"') print("---") end end uftrace-0.15.2/scripts/strings.py000066400000000000000000000014311455365734300170000ustar00rootroot00000000000000# # strings.py - print the unique strings of runtime function arguments and return values. # # uftrace-option: --nest-libcall --auto-args # strset = set() def uftrace_entry(ctx): global strset if "args" in ctx: args = ctx["args"] for arg in args: if isinstance(arg, str): arg = arg.strip() if arg != "" and arg[:8] != "struct: ": strset.add(arg) def uftrace_exit(ctx): global strset if "retval" in ctx: ret = ctx["retval"] if isinstance(ret, str): ret = ret.strip() if ret != "" and ret[:8] != "struct: ": strset.add(ret) def uftrace_end(): global strset for strval in strset: print('"%s"' % strval) print("---") uftrace-0.15.2/scripts/trace-memcpy.lua000066400000000000000000000011141455365734300200240ustar00rootroot00000000000000-- -- trace-memcpy.lua -- -- uftrace-option: --nest-libcall -T memcpy@filter,arg3 -- -- void *memcpy(void *dest, const void *src, size_t n); -- -- Only "memcpy" calls this script and other functions never. UFTRACE_FUNCS = { 'memcpy' } count = 0 total_bytes = 0 function uftrace_begin(ctx) end function uftrace_entry(ctx) count = count + 1 if ctx['args'] ~= nil then total_bytes = total_bytes + ctx['args'][1] end end function uftrace_exit(ctx) end function uftrace_end() print(count .. ' times memcpy called') print(total_bytes .. ' bytes copied') end uftrace-0.15.2/scripts/trace-memcpy.py000066400000000000000000000011101455365734300176670ustar00rootroot00000000000000# # trace-memcpy.py # # uftrace-option: --nest-libcall -T memcpy@filter,arg3 # # void *memcpy(void *dest, const void *src, size_t n); # # Only "memcpy" calls this script and other functions never. UFTRACE_FUNCS = [ "memcpy" ] count = 0 total_bytes = 0 def uftrace_begin(ctx): pass def uftrace_entry(ctx): global count global total_bytes count += 1 total_bytes += ctx["args"][0] def uftrace_exit(ctx): pass def uftrace_end(): global count global total_bytes print("%d times memcpy called" % count) print("%d bytes copied" % total_bytes) uftrace-0.15.2/tests/000077500000000000000000000000001455365734300144115ustar00rootroot00000000000000uftrace-0.15.2/tests/.gitignore000066400000000000000000000001021455365734300163720ustar00rootroot00000000000000*.pyc __pycache__/ unittest failed-tests.txt failed-tests.txt.old uftrace-0.15.2/tests/Makefile000066400000000000000000000042511455365734300160530ustar00rootroot00000000000000TEST_CFLAGS += -g -include $(srcdir)/tests/unittest.h -Wno-sign-compare $(CFLAGS_$@) UNIT_TEST_SRC := $(srcdir)/uftrace.c UNIT_TEST_SRC += $(wildcard $(srcdir)/cmds/*.c) UNIT_TEST_SRC += $(wildcard $(srcdir)/utils/*.c) UNIT_TEST_SRC += $(wildcard $(srcdir)/arch/$(ARCH)/*.c) UNIT_TEST_SRC += $(wildcard $(srcdir)/libmcount/*.c) ifneq ($(findstring HAVE_LIBPYTHON, $(TEST_CFLAGS)), ) UNIT_TEST_SRC += $(wildcard $(srcdir)/python/*.c) endif UNIT_TEST_OBJ := $(patsubst $(srcdir)/%.c,$(objdir)/%.ot,$(UNIT_TEST_SRC)) UNIT_TEST_OBJ := $(filter-out %-nop.ot,$(UNIT_TEST_OBJ)) UNIT_TEST_HDR := $(srcdir)/uftrace.h UNIT_TEST_HDR += $(srcdir)/tests/unittest.h UNIT_TEST_HDR += $(wildcard $(srcdir)/utils/*.h) UNIT_TEST_HDR += $(wildcard $(srcdir)/arch/$(ARCH)/*.h) UNIT_TEST_HDR += $(wildcard $(srcdir)/libmcount/*.h) UNIT_TEST_OBJ_VERSION := $(objdir)/cmds/script.ot $(objdir)/cmds/tui.ot UNIT_TEST_OBJ_VERSION += $(objdir)/cmds/dump.ot $(objdir)/cmds/info.ot UNIT_TEST_OBJ_VERSION += $(objdir)/libmcount/mcount.ot FULL_WORKER := -j $(shell getconf _NPROCESSORS_ONLN || echo 1) # these needs to be recursively expanded JOPT = $(filter -j%, $(MAKEFLAGS)) WORKER = $(if $(JOPT), $(if $(patsubst -j%,%,$(JOPT)), $(JOPT), $(FULL_WORKER)), -j1) include $(srcdir)/Makefile.include test: test_all test_all: unittest $(QUIET_TEST)./unittest $(TESTARG) $(UNITTESTARG) $(QUIET_TEST)./runtest.py $(WORKER) $(TESTARG) $(RUNTESTARG) ifneq ($(findstring HAVE_LIBPYTHON, $(TEST_CFLAGS)), ) $(QUIET_TEST)./runtest.py -P $(WORKER) $(TESTARG) $(PYTESTARG) endif test_run: $(QUIET_TEST)./runtest.py $(WORKER) $(TESTARG) $(RUNTESTARG) test_python: $(QUIET_TEST)./runtest.py -P $(WORKER) $(TESTARG) $(PYTESTARG) test_unit: unittest $(QUIET_TEST)./unittest $(TESTARG) $(UNITTESTARG) unittest: unittest.c unittest.h $(UNIT_TEST_OBJ) $(QUIET_LINK)$(CC) -o $@ $(TEST_CFLAGS) $< $(UNIT_TEST_OBJ) $(TEST_LDFLAGS) $(UNIT_TEST_OBJ_VERSION): $(objdir)/version.h $(UNIT_TEST_OBJ): $(objdir)/%.ot: $(srcdir)/%.c $(objdir)/.config $(UNIT_TEST_HDR) $(QUIET_CC)$(CC) -o $@ -c $(TEST_CFLAGS) $< clean: $(call QUIET_CLEAN, test) @rm -f *.o *.so *.pyc t-* unittest $(UNIT_TEST_OBJ) .PHONY: clean test test_run test_unit test_python uftrace-0.15.2/tests/arch/000077500000000000000000000000001455365734300153265ustar00rootroot00000000000000uftrace-0.15.2/tests/arch/arm/000077500000000000000000000000001455365734300161055ustar00rootroot00000000000000uftrace-0.15.2/tests/arch/arm/Makefile000066400000000000000000000033371455365734300175530ustar00rootroot00000000000000CC = gcc RM = rm -f UFTRACE = ../../../uftrace --libmcount-path=../../.. --flat #CFLAGS = -g -finstrument-functions CFLAGS = -g -pg -fno-omit-frame-pointer TARGETS = t-thumb t-arm-thumb all: $(TARGETS) TARGETS_O1 = $(patsubst %,%_O1,$(TARGETS)) TARGETS_O2 = $(patsubst %,%_O2,$(TARGETS)) TARGETS_O3 = $(patsubst %,%_O3,$(TARGETS)) TARGETS_Os = $(patsubst %,%_Os,$(TARGETS)) ALL_TARGETS = $(TARGETS) $(TARGETS_O1) $(TARGETS_O2) $(TARGETS_O3) $(TARGETS_Os) test_run = $(UFTRACE) $(1) 1 2 3 | sort -k2 | cut -d/ -f2 | cut -d, -f1 | sed -e 's/<[0-9a-f]\+>//' test_and_diff = printf "%20s: " $(1); $(test_run) | diff -U0 $(2) - > $(1)-diff && \ printf [${GREEN}OK${RESET}]"\n" || printf [${RED}NG${RESET}]"\n" all: $(ALL_TARGETS) t-thumb: thumb.c $(CC) $(CFLAGS) -mthumb -o $@ $< t-thumb_%: thumb.c $(CC) $(CFLAGS) -$(@:t-thumb_%=%) -mthumb -o $@ $< t-arm-thumb: a.c t.c $(CC) $(CFLAGS) -marm -c a.c $(CC) $(CFLAGS) -mthumb -c t.c $(CC) $(CFLAGS) -o $@ a.o t.o t-arm-thumb_%: a.c t.c $(CC) $(CFLAGS) -$(@:t-arm-thumb_%=%) -marm -c a.c $(CC) $(CFLAGS) -$(@:t-arm-thumb_%=%) -mthumb -c t.c $(CC) $(CFLAGS) -$(@:t-arm-thumb_%=%) -o $@ a.o t.o test: all @echo testing -O0 @for t in $(TARGETS); do $(call test_run, $${t}) > $${t}_result; done @echo testing -O1 @for t in $(TARGETS_O1); do $(call test_and_diff, $${t}, $${t%_O1}_result); done @echo testing -O2 @for t in $(TARGETS_O2); do $(call test_and_diff, $${t}, $${t%_O2}_result); done @echo testing -O3 @for t in $(TARGETS_O3); do $(call test_and_diff, $${t}, $${t%_O3}_result); done @echo testing -Os @for t in $(TARGETS_Os); do $(call test_and_diff, $${t}, $${t%_Os}_result); done clean: $(RM) $(ALL_TARGETS) *.o uftrace.data* gmon.out *-diff .PHONY: clean test arch uftrace-0.15.2/tests/arch/arm/a.c000066400000000000000000000004421455365734300164710ustar00rootroot00000000000000static volatile int account; extern volatile int tcount; extern void t1(void); extern void t2(void); static void __attribute__((noinline)) a1(void) { t1(); account++; } void __attribute__((noinline)) a2(void) { t2(); account++; } int main(void) { a1(); return account + tcount; } uftrace-0.15.2/tests/arch/arm/t.c000066400000000000000000000002371455365734300165160ustar00rootroot00000000000000volatile int tcount; extern void a2(); void __attribute__((noinline)) t1(void) { a2(); tcount++; } void __attribute__((noinline)) t2(void) { tcount++; } uftrace-0.15.2/tests/arch/arm/thumb.c000066400000000000000000000005701455365734300173720ustar00rootroot00000000000000#include void __attribute__((noinline)) foo(void) { char buf[128]; snprintf(buf, sizeof(buf), "%s", __func__); printf("%s\n", buf); } void __attribute__((noinline)) bar(void) { foo(); printf("%s\n", __func__); } void __attribute__((noinline)) baz(void) { bar(); printf("%s\n", __func__); } int main(void) { printf("%s\n", __func__); baz(); return 0; } uftrace-0.15.2/tests/arch/x86_64/000077500000000000000000000000001455365734300162645ustar00rootroot00000000000000uftrace-0.15.2/tests/arch/x86_64/Makefile000066400000000000000000000026371455365734300177340ustar00rootroot00000000000000CC = gcc RM = rm -f UFTRACE = ../../../uftrace --libmcount-path=../../.. --flat CFLAGS = -g -finstrument-functions TARGETS = ifdef HAVE_CC_MFENTRY TARGETS += t-fentry endif TARGETS_O1 = $(patsubst %,%_O1,$(TARGETS)) TARGETS_O2 = $(patsubst %,%_O2,$(TARGETS)) TARGETS_O3 = $(patsubst %,%_O3,$(TARGETS)) TARGETS_Os = $(patsubst %,%_Os,$(TARGETS)) ALL_TARGETS = $(TARGETS) $(TARGETS_O1) $(TARGETS_O2) $(TARGETS_O3) $(TARGETS_Os) test_run = $(UFTRACE) $(1) 1 2 3 | sort -k2 | cut -d/ -f2 | cut -d, -f1 | sed -e 's/<[0-9a-f]\+>//' test_and_diff = printf "%20s: " $(1); $(test_run) | diff -U0 $(2) - > $(1)-diff && \ printf [${GREEN}OK${RESET}]"\n" || printf [${RED}NG${RESET}]"\n" all: $(ALL_TARGETS) t-fentry: fentry.c $(CC) $(CFLAGS) -mfentry -o $@ $< t-fentry_%: fentry.c $(CC) $(CFLAGS) -$(@:t-fentry_%=%) -mfentry -o $@ $< test: all @echo testing -O0 @for t in $(TARGETS); do $(call test_run, $${t}) > $${t}_result; done @echo testing -O1 @for t in $(TARGETS_O1); do $(call test_and_diff, $${t}, $${t%_O1}_result); done @echo testing -O2 @for t in $(TARGETS_O2); do $(call test_and_diff, $${t}, $${t%_O2}_result); done @echo testing -O3 @for t in $(TARGETS_O3); do $(call test_and_diff, $${t}, $${t%_O3}_result); done @echo testing -Os @for t in $(TARGETS_Os); do $(call test_and_diff, $${t}, $${t%_Os}_result); done clean: $(RM) $(ALL_TARGETS) *.o uftrace.data* gmon.out *-diff .PHONY: clean test arch uftrace-0.15.2/tests/arch/x86_64/fentry.c000066400000000000000000000005001455365734300177320ustar00rootroot00000000000000#include void __attribute__((noinline)) foo(void) { printf("%s\n", __func__); } void __attribute__((noinline)) bar(void) { foo(); printf("%s\n", __func__); } void __attribute__((noinline)) baz(void) { bar(); printf("%s\n", __func__); } int main(void) { printf("%s\n", __func__); baz(); return 0; } uftrace-0.15.2/tests/mymod.py000066400000000000000000000001601455365734300161050ustar00rootroot00000000000000import json def public_func(): internal(1) internal(2) def internal(n): json.dumps({'number': n}) uftrace-0.15.2/tests/p001_basic.py000066400000000000000000000010351455365734300166030ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import PyTestBase class TestCase(PyTestBase): def __init__(self): PyTestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [ 28141] | __main__.() { [ 28141] | a() { [ 28141] | b() { [ 28141] | c() { 0.753 us [ 28141] | posix.getpid(); 1.430 us [ 28141] | } /* c */ 1.915 us [ 28141] | } /* b */ 2.405 us [ 28141] | } /* a */ 3.005 us [ 28141] | } /* __main__. */ """) uftrace-0.15.2/tests/p002_filter_F.py000066400000000000000000000007441455365734300172630ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import PyTestBase class TestCase(PyTestBase): def __init__(self): PyTestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [ 28142] | a() { [ 28142] | b() { [ 28142] | c() { 0.753 us [ 28142] | posix.getpid(); 1.430 us [ 28142] | } /* c */ 1.915 us [ 28142] | } /* b */ 2.405 us [ 28142] | } /* a */ """) def setup(self): self.option = '-F a' uftrace-0.15.2/tests/p003_filter_N.py000066400000000000000000000006611455365734300172720ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import PyTestBase class TestCase(PyTestBase): def __init__(self): PyTestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [ 28143] | __main__.() { [ 28143] | a() { 1.915 us [ 28143] | b(); 2.405 us [ 28143] | } /* a */ 3.005 us [ 28143] | } /* __main__. */ """) def setup(self): self.option = '-N c' uftrace-0.15.2/tests/p004_filter_FN.py000066400000000000000000000006341455365734300174010ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import PyTestBase class TestCase(PyTestBase): def __init__(self): PyTestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [ 28141] | a() { [ 28141] | b() { 1.430 us [ 28141] | c(); 1.915 us [ 28141] | } /* b */ 2.405 us [ 28141] | } /* a */ """) def setup(self): self.option = '-F a -N .getpid' uftrace-0.15.2/tests/p005_srcline.py000066400000000000000000000024131455365734300171660ustar00rootroot00000000000000#!/usr/bin/env python3 import re from runtest import PyTestBase class TestCase(PyTestBase): def __init__(self): PyTestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [ 28141] | __main__.() { /* /home/namhyung/project/uftrace/tests/s-abc.py:1 */ [ 28141] | a() { /* /home/namhyung/project/uftrace/tests/s-abc.py:4 */ [ 28141] | b() { /* /home/namhyung/project/uftrace/tests/s-abc.py:7 */ [ 28141] | c() { /* /home/namhyung/project/uftrace/tests/s-abc.py:10 */ 0.753 us [ 28141] | posix.getpid(); 1.430 us [ 28141] | } /* c */ 1.915 us [ 28141] | } /* b */ 2.405 us [ 28141] | } /* a */ 3.005 us [ 28141] | } /* __main__. */ """) def setup(self): self.option = '--srcline' def sort(self, output, ignored=True): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue line = ln.split('|', 1)[-1] # remove directory path and check the basename only func = re.sub(r'{ .*/s-abc.py:', '{ /* s-abc.py:', line) result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/p006_filter_time.py000066400000000000000000000007031455365734300200330ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import PyTestBase class TestCase(PyTestBase): def __init__(self): PyTestBase.__init__(self, 'sleep', """ # DURATION TID FUNCTION [538338] | __main__.() { [538338] | foo() { 100.152 ms [538338] | time.sleep(); 100.208 ms [538338] | } /* foo */ 100.215 ms [538338] | } /* __main__. */ """) def setup(self): self.option = '-t 80ms' uftrace-0.15.2/tests/p007_filter_depth.py000066400000000000000000000006611455365734300202050ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import PyTestBase class TestCase(PyTestBase): def __init__(self): PyTestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [ 28142] | __main__.() { [ 28142] | a() { 1.430 us [ 28142] | b(); 1.915 us [ 28142] | } /* a */ 2.405 us [ 28142] | } /* __main__. */ """) def setup(self): self.option = '-D 3' uftrace-0.15.2/tests/p008_file_var.py000066400000000000000000000007231455365734300173230ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import PyTestBase class TestCase(PyTestBase): def __init__(self): PyTestBase.__init__(self, 'file-var', """ s-file-var.py # DURATION TID FUNCTION [539546] | __main__.() { [539546] | foo() { 11.602 us [539546] | posixpath.basename(); 9.202 us [539546] | builtins.print(); 25.738 us [539546] | } /* foo */ 28.985 us [539546] | } /* __main__. */ """) uftrace-0.15.2/tests/p009_libcall_single.py000066400000000000000000000014601455365734300204770ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import PyTestBase class TestCase(PyTestBase): def __init__(self): PyTestBase.__init__(self, 'libmain', """ # DURATION TID FUNCTION [ 12698] | __main__.() { [ 12698] | myfunc() { [ 12698] | mymod.public_func() { [ 12698] | mymod.internal() { 17.354 us [ 12698] | json.dumps(); 21.809 us [ 12698] | } /* mymod.internal */ [ 12698] | mymod.internal() { 6.099 us [ 12698] | json.dumps(); 7.196 us [ 12698] | } /* mymod.internal */ 31.716 us [ 12698] | } /* mymod.public_func */ 34.551 us [ 12698] | } /* myfunc */ 8.932 ms [ 12698] | } /* __main__. */ """) def setup(self): self.option = '-N ^importlib' uftrace-0.15.2/tests/p010_libcall_none.py000066400000000000000000000012261455365734300201450ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import PyTestBase class TestCase(PyTestBase): def __init__(self): PyTestBase.__init__(self, 'libmain', """ # DURATION TID FUNCTION [ 12778] | __main__.() { 8.153 ms [ 12778] | mymod.(); [ 12778] | myfunc() { [ 12778] | mymod.public_func() { 22.748 us [ 12778] | mymod.internal(); 6.878 us [ 12778] | mymod.internal(); 32.405 us [ 12778] | } /* mymod.public_func */ 35.086 us [ 12778] | } /* myfunc */ 8.787 ms [ 12778] | } /* __main__. */ """) def setup(self): self.option = '--no-libcall' uftrace-0.15.2/tests/p011_libcall_nested.py000066400000000000000000000023201455365734300204650ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import PyTestBase # json.dumps() might be implemented differently depending on versions class TestCase(PyTestBase): def __init__(self): PyTestBase.__init__(self, 'libmain', """ # DURATION TID FUNCTION [ 12889] | __main__.() { [ 12889] | myfunc() { [ 12889] | mymod.public_func() { [ 12889] | mymod.internal() { [ 12889] | json.dumps() { 16.521 us [ 12889] | json.encoder.JSONEncoder.encode(); 19.719 us [ 12889] | } /* json.dumps */ 23.313 us [ 12889] | } /* mymod.internal */ [ 12889] | mymod.internal() { [ 12889] | json.dumps() { 6.233 us [ 12889] | json.encoder.JSONEncoder.encode(); 7.311 us [ 12889] | } /* json.dumps */ 8.484 us [ 12889] | } /* mymod.internal */ 34.855 us [ 12889] | } /* mymod.public_func */ 37.884 us [ 12889] | } /* myfunc */ 10.610 ms [ 12889] | } /* __main__. */ """) def setup(self): self.option = '--nest-libcall -N ^importlib -D 6' def fixup(self, cflags, result): return result.replace(".JSONEncoder", "") uftrace-0.15.2/tests/p012_os_exit.py000066400000000000000000000012021455365734300171720ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import PyTestBase class TestCase(PyTestBase): def __init__(self): PyTestBase.__init__(self, 'abc-exit', """ # DURATION TID FUNCTION [ 6461] | __main__.() { [ 6461] | a() { [ 6461] | b() { [ 6461] | c() { 1.114 us [ 6461] | posix.getpid(); 4.746 us [ 6461] | } /* c */ 7.101 us [ 6461] | } /* b */ 9.897 us [ 6461] | } /* a */ uftrace stopped tracing with remaining functions ================================================ task: 6461 [0] __main__. """, sort='task') uftrace-0.15.2/tests/runtest.py000077500000000000000000001166221455365734300165020ustar00rootroot00000000000000#!/usr/bin/env python3 import glob import multiprocessing import os import random import re import socket import subprocess as sp import sys import tempfile import time class Elf: EI_NIDENT = 16 @staticmethod def is_32bit(filename): # e_ident[] indexes EI_CLASS = 4 # EI_CLASS: ELFCLASSNONE, ELFCLASS32, ELFCLASS64, ELFCLASSNUM ELFCLASS32 = 1 try: with open(filename, 'rb') as f: elf_ident = list(f.read(Elf.EI_NIDENT)) ei_class = ord(elf_ident[EI_CLASS]) if ei_class == ELFCLASS32: return True except Exception: pass return False @staticmethod def get_elf_machine(filename): # e_machine (architecture) EM_386 = 3 # Intel 80386 EM_ARM = 40 # ARM EM_X86_64 = 62 # AMD x86-64 architecture EM_AARCH64 = 183 # ARM AARCH64 machine = { EM_386: 'i386', EM_ARM: 'arm', EM_X86_64: 'x86_64', EM_AARCH64: 'aarch64', } try: with open(filename, 'rb') as f: # consume elf_ident and e_type f.read(Elf.EI_NIDENT + 2) # read e_machine e_machine = f.read(2)[0] if isinstance(e_machine, str): e_machine = ord(e_machine) return machine[e_machine] except Exception: pass return None class TestBase: supported_lang = { 'C': { 'cc': 'gcc', 'flags': 'CFLAGS', 'ext': '.c' }, 'C++': { 'cc': 'g++', 'flags': 'CXXFLAGS', 'ext': '.cpp' }, } TEST_SUCCESS = 0 TEST_UNSUPP_LANG = -1 TEST_BUILD_FAIL = -2 TEST_ABNORMAL_EXIT = -3 TEST_TIME_OUT = -4 TEST_DIFF_RESULT = -5 TEST_NONZERO_RETURN = -6 TEST_SKIP = -7 TEST_SUCCESS_FIXED = -8 origdir = os.getcwd() basedir = os.path.dirname(origdir) objdir = 'objdir' in os.environ and os.environ['objdir'] or basedir srcdir = 'srcdir' in os.environ and os.environ['srcdir'] or basedir uftrace_cmd = objdir + '/uftrace' default_opt = '--no-pager --no-event --libmcount-path=' + objdir default_cflags = ['-fno-inline', '-fno-builtin', '-fno-ipa-cp', '-fno-omit-frame-pointer', '-D_FORTIFY_SOURCE=0'] feature = set() def __init__(self, name, result, lang='C', cflags='', ldflags='', sort='task', serial=False): _tmp = tempfile.mkdtemp(prefix='test_%s_' % name) self.keep = False os.chdir(_tmp) self.test_dir = _tmp self.name = name self.result = result self.cflags = cflags self.ldflags = ldflags self.lang = lang self.sort_method = sort self.serial = serial self.subcmd = 'live' self.option = '' self.exearg = 't-' + name self.p_flag = '' self.p_libs = [] self.test_feature() if Elf.get_elf_machine(self.uftrace_cmd) == 'i386': self.default_cflags.append('-m32') def set_compiler(self, compiler): if compiler == 'gcc': self.supported_lang['C']['cc'] = 'gcc' self.supported_lang['C++']['cc'] = 'g++' elif compiler == 'clang': self.supported_lang['C']['cc'] = 'clang' self.supported_lang['C++']['cc'] = 'clang++' else: # ignore invalid compiler argument pass def set_debug(self, dbg): self.debug = dbg def pr_debug(self, msg): if self.debug: print(msg) def set_keep(self, keep): self.keep = keep def gen_port(self, start = 40000, end = 50000): for port in random.sample(list(range(start, end + 1)), end - start + 1): try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind(("localhost", port)) self.port = port return except OSError: pass raise Exception("No available port found") def test_feature(self): try: p = sp.Popen(self.uftrace_cmd + ' --version', shell=True, stdout=sp.PIPE, stderr=sp.PIPE) uftrace_version = p.communicate()[0].decode(errors='ignore') s = uftrace_version.split() for i in range(3, len(s) - 1): self.feature.add(s[i]) return True except Exception: return False def convert_abs_path(self, build_cmd): cmd = build_cmd.split() src_idx = [i for i, _cmd in enumerate(cmd) if _cmd.startswith('s-')][0] abs_src = os.path.join(self.basedir, 'tests', cmd[src_idx]) cmd[src_idx] = abs_src return " ".join(cmd) def strip_tracing_flags(self, cmd): cmd = cmd.replace('-pg', '').replace('-finstrument-functions', '') cmd = re.sub(r'-fpatchable-function-entry=[0-9]+', '', cmd) return cmd def build_it(self, build_cmd): build_cmd = self.convert_abs_path(build_cmd) if '-fpatchable-function-entry' in build_cmd: self.p_flag = '-P .' try: p = sp.Popen(build_cmd.split(), stderr=sp.PIPE) if p.wait() != 0: self.pr_debug(p.communicate()[1].decode(errors='ignore')) return TestBase.TEST_BUILD_FAIL return TestBase.TEST_SUCCESS except OSError as e: self.pr_debug(e.strerror) return TestBase.TEST_BUILD_FAIL except Exception: return TestBase.TEST_BUILD_FAIL def build(self, name, cflags='', ldflags=''): if self.lang not in TestBase.supported_lang: self.pr_debug("%s: unsupported language: %s" % (name, self.lang)) return TestBase.TEST_UNSUPP_LANG lang = TestBase.supported_lang[self.lang] prog = 't-' + name src = 's-' + name + lang['ext'] build_cflags = ' '.join(TestBase.default_cflags + [self.cflags, cflags, \ os.getenv(lang['flags'], '')]) build_ldflags = ' '.join([self.ldflags, ldflags, os.getenv('LDFLAGS', '')]) build_cmd = '%s -o %s %s %s %s' % (lang['cc'], prog, build_cflags, src, build_ldflags) self.pr_debug("build command: %s" % build_cmd) return self.build_it(build_cmd) def build_notrace_lib(self, dstname, srcname, cflags='', ldflags =''): lang = TestBase.supported_lang['C'] build_cflags = ' '.join(TestBase.default_cflags + [self.cflags, cflags, \ os.getenv(lang['flags'], '')]) build_ldflags = ' '.join([self.ldflags, ldflags, os.getenv('LDFLAGS', '')]) lib_cflags = build_cflags + ' -shared -fPIC' build_cmd = '%s -o lib%s.so %s s-%s.c %s' % \ (lang['cc'], dstname, lib_cflags, srcname, build_ldflags) build_cmd = self.strip_tracing_flags(build_cmd) self.pr_debug("build command for library: %s" % build_cmd) return self.build_it(build_cmd) def build_libabc(self, cflags='', ldflags=''): lang = TestBase.supported_lang['C'] build_cflags = ' '.join(TestBase.default_cflags + [self.cflags, cflags, \ os.getenv(lang['flags'], '')]) build_ldflags = ' '.join([self.ldflags, ldflags, os.getenv('LDFLAGS', '')]) lib_cflags = build_cflags + ' -shared -fPIC' if '-fpatchable-function-entry' in cflags: self.p_libs.append('libabc_test_lib.so') # build libabc_test_lib.so library build_cmd = '%s -o libabc_test_lib.so %s s-lib.c %s' % (lang['cc'], lib_cflags, build_ldflags) self.pr_debug("build command for library: %s" % build_cmd) return self.build_it(build_cmd) def build_libfoo(self, name, cflags='', ldflags=''): lang = TestBase.supported_lang['C++'] build_cflags = ' '.join(TestBase.default_cflags + [self.cflags, cflags, \ os.getenv(lang['flags'], '')]) build_ldflags = ' '.join([self.ldflags, ldflags, os.getenv('LDFLAGS', '')]) lib_cflags = build_cflags + ' -shared -fPIC' if '-fpatchable-function-entry' in cflags: self.p_libs.append('lib%s.so' % name) # build lib{foo}.so library build_cmd = '%s -o lib%s.so %s s-lib%s%s %s' % \ (lang['cc'], name, lib_cflags, name, lang['ext'], build_ldflags) self.pr_debug("build command for library: %s" % build_cmd) return self.build_it(build_cmd) def build_libmain(self, exename, srcname, libs, cflags='', ldflags='', instrument=True): if self.lang not in TestBase.supported_lang: self.pr_debug("%s: unsupported language: %s" % (self.name, self.lang)) return TestBase.TEST_UNSUPP_LANG lang = TestBase.supported_lang[self.lang] prog = 't-' + exename build_cflags = ' '.join(TestBase.default_cflags + [self.cflags, cflags, os.getenv(lang['flags'], '')]) build_ldflags = ' '.join([self.ldflags, ldflags, os.getenv('LDFLAGS', '')]) exe_ldflags = build_ldflags + ' -Wl,-rpath,$ORIGIN -L. ' if '-fpatchable-function-entry' in cflags: self.p_libs.append(prog) for lib in libs: exe_ldflags += ' -l' + lib[3:-3] build_cmd = '%s -o %s %s %s %s' % (lang['cc'], prog, build_cflags, srcname, exe_ldflags) if not instrument: build_cmd = self.strip_tracing_flags(build_cmd) self.pr_debug("build command for executable: %s" % build_cmd) return self.build_it(build_cmd) def prepare(self): """ This function returns command line need to be run before""" return '' def setup(self): """ This function sets up options to be passed to runcmd""" pass def runcmd(self): """ This function returns (shell) command that runs the test. A test case can extend this to setup a complex configuration. """ if len(self.p_libs) > 0: self.p_flag = '' for lib in self.p_libs: self.p_flag += '-P .@%s ' % lib if '-P' in self.option: self.p_flag = '' # On aarch64, gcc and clang emit calls to memcpy() to pass and return structs. # This depends on a number of factors (gcc, clang, size and optimisation levels). # For now, it affects all tests tracing s-arg.c:pass() and return.c:return_large(). # Any test can be affected by it and it may change from compiler to compiler. # To prevent inconsistencies, filter all calls to memcpy() for all tests: self.option += ' -N memcpy' return '%s %s %s %s %s %s' % (TestBase.uftrace_cmd, self.subcmd, \ TestBase.default_opt, self.p_flag, self.option, self.exearg) def task_sort(self, output, ignore_children=False): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ pids = {} order = 1 before_main = True for ln in output.split('\n'): if ln.find(' | main()') > 0 or ln.find(' | __main__.') > 0: before_main = False if before_main: continue # ignore result of remaining functions which follows a blank line if ln.strip() == '': break pid_patt = re.compile('[^[]+\[ *(\d+)\] |') m = pid_patt.match(ln) try: pid = int(m.group(1)) except Exception: continue func = ln.split('|', 1)[-1] if pid not in pids: pids[pid] = { 'order': order } pids[pid]['result'] = [] order += 1 pids[pid]['result'].append(func) result = '' pid_list = sorted(list(pids), key=lambda p: pids[p]['order']) try: if ignore_children: result += '\n'.join(pids[pid_list[0]]['result']) else: for p in pid_list: result += '\n'.join(pids[p]['result']) + '\n' result = result.strip() except Exception: pass # this leads to a failure with 'NG' return result def simple_sort(self, output, ignored): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue func = ln.split('|', 1)[-1] result.append(func) return '\n'.join(result) def report_sort(self, output, ignored): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] # total_time unit self_time unit called function try: if line[-1].startswith('__'): continue except Exception: pass result.append('%s %s' % (line[-2], line[-1])) return '\n'.join(result) def graph_sort(self, output, ignored): """ This function post-processes output of the test to be compared. It ignores blank and comment (#) lines and header lines. """ result = [] mode = 0 for ln in output.split('\n'): if ln.strip() == '' or ln.startswith('#'): continue # A graph result consists of backtrace and calling functions if ln.startswith('=============== BACKTRACE ==============='): mode = 1 continue if ln.startswith('========== FUNCTION CALL GRAPH =========='): mode = 2 continue if mode == 1: if ln.startswith(' backtrace #'): result.append(ln.split(',')[0]) # remove time part if ln.startswith(' ['): result.append(ln.split('(')[0]) # remove '(addr)' part if mode == 2: if " : " in ln: result.append(ln.split(':')[1]) # remove time part else: result.append(ln) return '\n'.join(result) def dump_sort(self, output, ignored): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] # A (raw) dump result consists of following data # : [] () depth: patt = re.compile(r'[^[]*(?P\[(entry|exit )\]) (?P[_a-z0-9]*)\([0-9a-f]+\) (?P.*)') # A (raw) dump argument patter arg_patt = re.compile(r'^ args') for ln in output.split('\n'): if ln.startswith('uftrace'): #result.append(ln) pass else: m = arg_patt.match(ln) if m is not None: result.append(ln) continue m = patt.match(ln) if m is None: continue # ignore __monstartup and __cxa_atexit if m.group('func').startswith('__'): continue result.append(patt.sub(r'\g \g \g', ln)) return '\n'.join(result) def chrome_sort(self, output, ignored): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ import json # A chrome dump results consists of following JSON object: # {"ts": , "ph": , "pid": , "name": } result = [] try: o = json.loads(output) except Exception: return '' for ln in o['traceEvents']: if ln['name'].startswith('__'): continue if ln['ph'] == "M": if ln['name'] == "process_name" or ln['name'] == "thread_name": args = ln['args'] name = args['name'] m = re.search(r'\[\d+\] (.*)', args['name']) if m: name = m.group(1) result.append("%s %s %s" % (ln['ph'], ln['name'], name)) else: result.append("%s %s" % (ln['ph'], ln['name'])) return '\n'.join(result) def mermaid_sort(self, output, ignore_children=False): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] start_mermaid = False for ln in output.split('\n'): if ln.find('
= 0: start_mermaid = True continue if start_mermaid == False: continue if ln.find('
') >= 0: break m = re.match( r'\s+(?P\d+)_(?P\d+)\[\"(?P\S+)\"\]\s+' + '-->\|(?P\d+)\|\s+(?P\d+)_(?P\d+)\[\"(?P\S+)\"\];', ln) if m: result.append("%s_%s_%s %s> %s_%s_%s" % (m.group('start_depth'), m.group('start_id'), m.group('start_name'), m.group('call_num'), m.group('end_depth'), m.group('end_id'), m.group('end_name'))) else: continue return '\n'.join(result) def sort(self, output, ignore_children=False): if not hasattr(TestBase, self.sort_method + '_sort'): print('cannot find the sort function: %s' % self.sort_method) return '' # this leads to a failure with 'NG' func = TestBase.__dict__[self.sort_method + '_sort'] if callable(func): return func(self, output, ignore_children) else: return '' # this leads to a failure with 'NG' def postrun(self, result): """This function is called after running a testcase""" return result def fixup(self, cflags, result): """This function is called when result is different to expected. But if we know some known difference on some optimization level, apply it and re-test with the modified result.""" return result def check_dependency(self, item): import os.path return os.path.exists('%s/check-deps/' % self.basedir + item) def check_perf_paranoid(self): if not 'perf' in TestBase.feature: return False try: f = open('/proc/sys/kernel/perf_event_paranoid') v = int(f.readline()) f.close() if v >= 3: return False except Exception: pass return True def is_32bit(self): return Elf.is_32bit('t-' + self.name) def get_machine(self): return os.uname()[4] def get_elf_machine(self): return Elf.get_elf_machine('t-' + self.name) def check_arch_full_dynamic_support(self): elf_machine = TestBase.get_elf_machine(self) if elf_machine == 'x86_64' or elf_machine == 'aarch64': return True return False def check_arch_mfentry_mnop_mcount_support(self): machine = TestBase.get_machine(self) if machine == 'x86_64' or machine == 'i386': return True return False def check_arch_sdt_support(self): machine = TestBase.get_machine(self) if machine == 'x86_64': return True return False def prerun(self, timeout): self.subcmd = 'live' self.option = '' self.exearg = 't-' + self.name if self.lang == 'Python': self.exearg = TestBase.srcdir + '/tests/' + 's-' + self.name + '.py' cmd = self.prepare() if cmd == '': return TestBase.TEST_SUCCESS self.pr_debug("prerun command: " + cmd) class Timeout(Exception): pass def timeout_handler(sig, frame): raise Timeout ret = TestBase.TEST_SUCCESS import signal signal.signal(signal.SIGALRM, timeout_handler) try: signal.alarm(timeout) sp.call(cmd.split()) except Timeout: ret = TestBase.TEST_TIME_OUT signal.alarm(0) return ret def run(self, name, cflags, diff, timeout): ret = TestBase.TEST_SUCCESS dif = '' self.setup() test_cmd = self.runcmd() self.pr_debug("test command: %s" % test_cmd) if self.debug: # In verbose mode, stderr is printed as is without redirection. # This will inform error messages to users when something goes wrong. p = sp.Popen(test_cmd, shell=True, stdout=sp.PIPE) else: p = sp.Popen(test_cmd, shell=True, stdout=sp.PIPE, stderr=sp.PIPE) class Timeout(Exception): pass def timeout_handler(sig, frame): try: p.kill() finally: raise Timeout import signal signal.signal(signal.SIGALRM, timeout_handler) timed_out = False try: signal.alarm(timeout) result_origin = p.communicate()[0].decode(errors='ignore') except Timeout: result_origin = '' timed_out = True signal.alarm(0) try: result_tested = self.sort(result_origin) # for python3, may fail! result_expect = self.sort(self.result) except IndexError: result_tested = result_origin result_expect = self.result # strip trailing whitespace for each line. result_expect = '\n'.join([line.rstrip() for line in result_expect.split('\n')]) result_tested = '\n'.join([line.rstrip() for line in result_tested.split('\n')]) ret = p.wait() if ret < 0: if timed_out: return TestBase.TEST_TIME_OUT, '' else: return TestBase.TEST_ABNORMAL_EXIT, '' if ret > 0: if ret == 2: return TestBase.TEST_ABNORMAL_EXIT, '' if diff: dif = "%s: %s returns %d\n" % (name, cflags, ret) return TestBase.TEST_NONZERO_RETURN, dif self.pr_debug("=========== %s ===========\n%s" % ("original", result_origin)) self.pr_debug("=========== %s ===========\n%s" % (" result ", result_tested)) self.pr_debug("=========== %s ===========\n%s" % ("expected", result_expect)) if result_expect.strip() == '': if diff: dif = "%s: has no output for %s\n" % (name, cflags) return TestBase.TEST_DIFF_RESULT, dif if result_expect != result_tested: try: result_expect = self.sort(self.fixup(cflags, self.result)) ret = TestBase.TEST_SUCCESS_FIXED except IndexError: return TestBase.TEST_DIFF_RESULT, "Internal error: Expected more results" if result_expect != result_tested: if diff: f = open('expect', 'w') f.write(result_expect + '\n') f.close() f = open('result', 'w') f.write(result_tested + '\n') f.close() compiler = self.supported_lang['C']['cc'] dif = "%s: diff result of %s %s\n" % (name, compiler, cflags) try: p = sp.Popen(['colordiff', '-U1', 'expect', 'result'], stdout=sp.PIPE) except Exception: p = sp.Popen(['diff', '-U1', 'expect', 'result'], stdout=sp.PIPE) dif += p.communicate()[0].decode(errors='ignore') os.remove('expect') os.remove('result') return TestBase.TEST_DIFF_RESULT, dif return ret, '' def __del__(self): if self.keep: sp.call(['mv', self.test_dir, TestBase.origdir]) else: sp.call(['rm', '-rf', self.test_dir]) class PyTestBase(TestBase): def __init__(self, name, result, lang='Python', cflags='', ldflags='', sort='simple', serial=False): TestBase.__init__(self, name, result, lang, cflags, ldflags, sort, serial) # setup PYTHONPATH to load the new code before the inst orig_path = os.environ["PYTHONPATH"] if "PYTHONPATH" in os.environ else "" os.environ["PYTHONPATH"] = TestBase.objdir + '/python' if TestBase.objdir != TestBase.srcdir: os.environ["PYTHONPATH"] += ':' + TestBase.objdir + '/python' if orig_path != "": os.environ["PYTHONPATH"] += ':' + orig_path RED = '\033[1;31m' GREEN = '\033[1;32m' YELLOW = '\033[1;33m' NORMAL = '\033[0m' colored_result = { TestBase.TEST_SUCCESS: GREEN + 'OK' + NORMAL, TestBase.TEST_UNSUPP_LANG: YELLOW + 'LA' + NORMAL, TestBase.TEST_BUILD_FAIL: YELLOW + 'BI' + NORMAL, TestBase.TEST_ABNORMAL_EXIT: RED + 'SG' + NORMAL, TestBase.TEST_TIME_OUT: RED + 'TM' + NORMAL, TestBase.TEST_DIFF_RESULT: RED + 'NG' + NORMAL, TestBase.TEST_NONZERO_RETURN: RED + 'NZ' + NORMAL, TestBase.TEST_SKIP: YELLOW + 'SK' + NORMAL, TestBase.TEST_SUCCESS_FIXED: YELLOW + 'OK' + NORMAL, } text_result = { TestBase.TEST_SUCCESS: 'OK', TestBase.TEST_UNSUPP_LANG: 'LA', TestBase.TEST_BUILD_FAIL: 'BI', TestBase.TEST_ABNORMAL_EXIT: 'SG', TestBase.TEST_TIME_OUT: 'TM', TestBase.TEST_DIFF_RESULT: 'NG', TestBase.TEST_NONZERO_RETURN: 'NZ', TestBase.TEST_SKIP: 'SK', TestBase.TEST_SUCCESS_FIXED: 'OK', } result_string = { TestBase.TEST_SUCCESS: 'Test succeeded', TestBase.TEST_UNSUPP_LANG: 'Unsupported Language', TestBase.TEST_BUILD_FAIL: 'Build failed', TestBase.TEST_ABNORMAL_EXIT: 'Abnormal exit by signal', TestBase.TEST_TIME_OUT: 'Test ran too long', TestBase.TEST_DIFF_RESULT: 'Different test result', TestBase.TEST_NONZERO_RETURN: 'Non-zero return value', TestBase.TEST_SKIP: 'Skipped', TestBase.TEST_SUCCESS_FIXED: 'Test succeeded (with some fixup)', } def check_serial_case(case): # for python3 _locals = {} exec("import %s; tc = %s.TestCase()" % (case, case), globals(), _locals) tc = _locals['tc'] return tc.serial def run_python_case(T, case, timeout): tc = T.TestCase() tc.set_debug(arg.debug) tc.set_keep(arg.keep) # to load uftrace.py and uftrace_python.so module sys.path.append(TestBase.objdir + '/python') sys.path.append(TestBase.srcdir + '/python') ret = tc.prerun(timeout) dif = '' if ret == TestBase.TEST_SUCCESS: ret, dif = tc.run(case, "", arg.diff, timeout) ret = tc.postrun(ret) return (ret, dif) def run_single_case(case, flags, opts, arg, compilers): result = [] timeout = int(arg.timeout) # for python3 _locals = {} exec("import %s as T" % (case), globals(), _locals) T = _locals['T'] for compiler in compilers: if compiler == 'python': ret, dif = run_python_case(T, case, timeout) result.append((ret, dif)) continue for flag in flags: for opt in opts: tc = T.TestCase() tc.set_debug(arg.debug) tc.set_keep(arg.keep) tc.set_compiler(compiler) cflags = ' '.join(["-" + flag, "-" + opt]) # add -fno-ipa-sra to prevent function renames like foo.isra.0 # this is available on GCC only if compiler == 'gcc': cflags += ' -fno-ipa-sra' dif = '' ret = tc.build(tc.name, cflags) if ret == TestBase.TEST_SUCCESS: ret = tc.prerun(timeout) if ret == TestBase.TEST_SUCCESS: ret, dif = tc.run(case, cflags, arg.diff, timeout) ret = tc.postrun(ret) result.append((ret, dif)) return result def save_test_result(result, case, shared): # save diff before results for print_test_result() to see it shared.diffs[case] = [r[1] for r in result] shared.results[case] = [r[0] for r in result] shared.progress += 1 for r in result: shared.stats[r[0]] += 1 shared.total += 1 def print_test_result(case, result, diffs, color, ftests, nr_compilers): plain_result = [text_result[r] for r in result] if color: result_list = [colored_result[r] for r in result] else: result_list = plain_result for dif in diffs: if dif != '': sys.stdout.write(dif + '\n') output = case[1:4] output += ' %-20s' % case[5:] + ':' result_per_compiler = len(result_list) // nr_compilers for i in range(nr_compilers): output += ' ' if i != 0: output += ' ' begin = result_per_compiler * i end = result_per_compiler * (i + 1) output += ' '.join(result_list[begin:end]) output += '\n' # write abnormal test result to failed-tests.txt normal = [TestBase.TEST_SUCCESS, TestBase.TEST_SUCCESS_FIXED, TestBase.TEST_SKIP] for r in result: if r not in normal: ftests.write(output) ftests.flush() break if arg.quiet: for r in result: if r not in normal: sys.stdout.write(output) break else: sys.stdout.write(output) def print_test_header(opts, flags, ftests, compilers): optslen = len(opts) header1 = '%-24s ' % 'Compiler' header2 = '%-24s ' % 'Runtime test case' header3 = '-' * 24 + ':' empty = ' ' * 100 for i, compiler in enumerate(compilers): if i != 0: header1 += ' ' header2 += ' ' header3 += ' ' for flag in flags: # align with optimization flags header2 += ' ' + flag[:optslen] + empty[len(flag):optslen] header3 += ' ' + opts header1 += ' ' + compiler[:optslen*len(flags)+len(flags)-1] + empty[len(compiler):len(header3)-len(header1)-1] print("") print(header1) print(header2) print(header3) ftests.write(header1 + '\n') ftests.write(header2 + '\n') ftests.write(header3 + '\n') ftests.flush() def print_python_test_header(ftests): header1 = '%-24s %s' % ('Python test case', 'Result') header2 = '-' * 32 print("") print(header1) print(header2) ftests.write(header1 + '\n') ftests.write(header2 + '\n') ftests.flush() def print_test_report(color, shared): success = shared.stats[TestBase.TEST_SUCCESS] + shared.stats[TestBase.TEST_SUCCESS_FIXED] percent = 100.0 * success / shared.total print("") print("runtime test stats") print("====================") print("total %5d Tests executed (success: %.2f%%)" % (shared.total, percent)) for r in res: if color: result = colored_result[r] else: result = text_result[r] print(" %s: %5d %s" % (result, shared.stats[r], result_string[r])) def parse_argument(): import argparse parser = argparse.ArgumentParser() parser.add_argument("-f", "--profile-flags", dest='flags', default="pg finstrument-functions fpatchable-function-entry", help="comma separated list of compiler profiling flags") parser.add_argument("-O", "--optimize-levels", dest='opts', default="0123s", help="compiler optimization levels") parser.add_argument("cases", nargs='?', default="all", help="test cases: 'all' or test number or (partial) name") parser.add_argument("-p", "--profile-pg", dest='pg_flag', action='store_true', help="profiling with -pg option") parser.add_argument("-i", "--instrument-functions", dest='if_flag', action='store_true', help="profiling with -finstrument-functions option") parser.add_argument("-e", "--patchable-function-entry", dest='pfe_flag', action='store_true', help="profiling with -fpatchable-function-entry option") parser.add_argument("-d", "--diff", dest='diff', action='store_true', help="show diff result if not matched") parser.add_argument("-v", "--verbose", dest='debug', action='store_true', help="show internal command and result for debugging") parser.add_argument("-l", "--color", dest='color', default='auto', help="set color in the output. 'auto', 'on' or 'off'") parser.add_argument("-t", "--timeout", dest='timeout', default=5, help="fail test if it runs more than TIMEOUT seconds") parser.add_argument("-j", "--worker", dest='worker', type=int, default=multiprocessing.cpu_count(), help="Parallel worker count; using all core for default") parser.add_argument("-c", "--compiler", dest='compiler', default="all", help="Select compiler gcc or clang. (use both by default)") parser.add_argument("-k", "--keep", dest='keep', action='store_true', help="keep the test directories with compiled binaries") parser.add_argument("-q", "--quiet", dest='quiet', action='store_true', help="Hide normal results and print only abnormal results.") parser.add_argument("-P", "--python", dest='python', action='store_true', help="Run python test cases instead") return parser.parse_args() if __name__ == "__main__": # prevent to create .pyc files (it makes some tests failed) os.environ["PYTHONDONTWRITEBYTECODE"] = "1" arg = parse_argument() testcases = [] if arg.cases == 'all': if arg.python: testcases = glob.glob('p???_*.py') else: testcases = glob.glob('t???_*.py') else: try: cases = arg.cases.split('|') for case in cases: if arg.python: testcases.extend(glob.glob('p*' + case + '*.py')) else: testcases.extend(glob.glob('t*' + case + '*.py')) arg.worker = min(arg.worker, len(testcases)) finally: if len(testcases) == 0: print("cannot find testcase for : %s" % arg.cases) sys.exit(0) # Use multiprocessing pool if the number of workers is greater than 1. use_pool = arg.worker > 1 opts = ' '.join(sorted(['O' + o for o in arg.opts])) patch_size = { 'x86_64' : 5, 'aarch64' : 2, } m = os.uname()[-1] # machine if arg.pg_flag: flags = ['pg'] elif arg.if_flag: flags = ['finstrument-functions'] elif arg.pfe_flag: flags = ['fpatchable-function-entry'] if m not in patch_size: print('fpatchable-function-entry not supported on current platform\n') exit(-1) else: flags = arg.flags.split() if m not in patch_size: flags.remove('fpatchable-function-entry') for i in range(len(flags)): if flags[i] == 'fpatchable-function-entry': flags[i] += '=%d' % patch_size[m] def has_compiler(compiler): installed = os.system('command -v %s > /dev/null' % compiler) == 0 return installed compilers = [] if arg.python: compilers.append('python') elif arg.compiler == 'all': for compiler in ['gcc', 'clang']: if has_compiler(compiler): compilers.append(compiler) else: if has_compiler(arg.compiler): compilers.append(arg.compiler) nr_compilers = len(compilers) if nr_compilers == 0: print('no compilers available for testing\n') sys.exit(-1) from functools import partial class dotdict(dict): """dot.notation access to dictionary attributes""" __getattr__ = dict.get __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__ manager = multiprocessing.Manager() if use_pool else None shared = manager.dict() if use_pool else dotdict() shared.tests_count = len(testcases) shared.progress = 0 shared.results = dict() shared.diffs = dict() shared.total = 0 res = [] res.append(TestBase.TEST_SUCCESS) res.append(TestBase.TEST_SUCCESS_FIXED) res.append(TestBase.TEST_DIFF_RESULT) res.append(TestBase.TEST_NONZERO_RETURN) res.append(TestBase.TEST_ABNORMAL_EXIT) res.append(TestBase.TEST_TIME_OUT) res.append(TestBase.TEST_BUILD_FAIL) res.append(TestBase.TEST_UNSUPP_LANG) res.append(TestBase.TEST_SKIP) failed_tests = "failed-tests.txt" if os.path.exists(failed_tests): os.rename(failed_tests, failed_tests + ".old") ftests = open(failed_tests, "w") shared.stats = dict.fromkeys(res, 0) pool = multiprocessing.Pool(arg.worker) if use_pool else None serial_pool = multiprocessing.Pool(1) if use_pool else None if use_pool: print("Start %s tests with %d worker" % (shared.tests_count, arg.worker)) else: print("Start %s tests without worker pool" % shared.tests_count) if arg.python: print_python_test_header(ftests) else: print_test_header(opts, flags, ftests, compilers) color = True if arg.color == 'auto': if not sys.stdout.isatty(): color = False if 'TERM' in os.environ and os.environ['TERM'] == 'dumb': color = False elif arg.color == 'on': color = True elif arg.color == 'off': color = False else: print("unknown color: %s" % arg.color) sys.exit(-1) for tc in sorted(testcases): name = tc.split('.')[0] # remove '.py' if use_pool: _pool = serial_pool if check_serial_case(name) else pool clbk = partial(save_test_result, case=name, shared=shared) _pool.apply_async(run_single_case, callback=clbk, args=[name, flags, opts.split(), arg, compilers]) else: results = run_single_case(name, flags, opts.split(), arg, compilers) save_test_result(results, case=name, shared=shared) # Print sequentially when executing in serial print_test_result(name, shared.results[name], shared.diffs[name], color, ftests, nr_compilers) if use_pool: # Print via polling when using multiprocessing pool for tc in sorted(testcases): name = tc.split('.')[0] # remove '.py' while name not in shared.results: time.sleep(1) print_test_result(name, shared.results[name], shared.diffs[name], color, ftests, nr_compilers) if use_pool: pool.close() pool.join() ftests.close() sys.stdout.write("\n") sys.stdout.flush() if shared.progress >= 10 or shared.total >= 300: print_test_report(color, shared) uftrace-0.15.2/tests/s-abc-exit.py000077500000000000000000000001671455365734300167260ustar00rootroot00000000000000#!/usr/bin/env python3 import os def a(): b() def b(): c() def c(): return os.getpid() a() os._exit(0) uftrace-0.15.2/tests/s-abc.c000066400000000000000000000006661455365734300155520ustar00rootroot00000000000000/* * This is a basic test to verify whether uftrace works on the system. */ #include #include static int a(void); static int b(void); static int c(void); static int a(void) { return b() - 1; } static int b(void) { return c() + 1; } static int c(void) { return getpid() % 100000; } int main(int argc, char *argv[]) { int ret = 0; if (argc > 1) ret = atoi(argv[1]); ret += a(); return ret ? 0 : 1; } uftrace-0.15.2/tests/s-abc.py000077500000000000000000000001531455365734300157520ustar00rootroot00000000000000#!/usr/bin/env python3 import os def a(): b() def b(): c() def c(): return os.getpid() a() uftrace-0.15.2/tests/s-agent.c000066400000000000000000000030301455365734300161070ustar00rootroot00000000000000/* This target program is intended for use with the agent. It executes a loop * and awaits for external input at each iteration. * * It accepts options so it can adapt its execution to test various agent * features. */ #include #include #include #include static int func(int depth); static int trigger(int depth); static int a(int depth); static int b(int depth); static int c(int depth); #define DELAY 1000 static int use_delay; /* accessed by func and trigger in different ways so they don't get merged by the compiler */ static int dummy; static int func(int depth) { dummy++; if (depth) return a(--depth) + 1; return 0; } static int trigger(int depth) { dummy--; if (depth) return a(--depth) + 1; return 0; } static int a(int depth) { if (use_delay) usleep(DELAY); if (depth) return b(--depth) + 1; return 0; } static int b(int depth) { if (use_delay) usleep(DELAY); if (depth) return c(--depth) + 1; return 0; } static int c(int depth) { if (use_delay) usleep(DELAY); if (getpid()) /* true */ return depth; return -1; /* unreached */ } int main(int argc, char *argv[]) { int i; int depth = 3; int use_trigger = 0; for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--depth")) depth = atoi(argv[++i]); if (!strcmp(argv[i], "--trigger")) use_trigger = 1; if (!strcmp(argv[i], "--delay")) use_delay = 1; } if (depth <= 0) depth = 1; do { func(depth); if (use_trigger) trigger(depth); } while (getchar() != EOF); return 0; } uftrace-0.15.2/tests/s-alloca.c000066400000000000000000000005211455365734300162460ustar00rootroot00000000000000#include #include #include #include int foo(int c) { char *ptr = alloca(c); strncpy(ptr, "hello world\n", c); return c; } int bar(int c) { return foo(c) + foo(c - 2); } int main(int argc, char *argv[]) { int c = 12; if (argc > 1) c = atoi(argv[1]); foo(c); bar(c); return 0; } uftrace-0.15.2/tests/s-allocfree.c000066400000000000000000000020001455365734300167410ustar00rootroot00000000000000#include // block compiler optimizing. static void *alloc1(volatile int); static void *alloc2(volatile int); static void *alloc3(volatile int); static void *alloc4(volatile int); static void *alloc5(volatile int); static void free1(void *ptr); static void free2(void *ptr); static void free3(void *ptr); static void free4(void *ptr); static void free5(void *ptr); static void *alloc1(volatile int one) { return alloc2(one); } static void *alloc2(volatile int one) { return alloc3(one); } static void *alloc3(volatile int one) { return alloc4(one); } static void *alloc4(volatile int one) { return alloc5(one); } static void *alloc5(volatile int one) { return malloc(one); } static void free1(void *ptr) { free2(ptr); } static void free2(void *ptr) { free3(ptr); } static void free3(void *ptr) { free4(ptr); } static void free4(void *ptr) { free5(ptr); } static void free5(void *ptr) { free(ptr); } int main(int argc, char *argv[]) { volatile int one = 1; free1(alloc1(one)); return 0; } uftrace-0.15.2/tests/s-arg.c000066400000000000000000000032241455365734300155670ustar00rootroot00000000000000/* * This is to test uftrace can work well with passing various arguments * to the functions. */ #include #include #include #include const char *strs[] = { "a", "b", "c" }; const unsigned nr_strs = sizeof(strs) / sizeof(strs[0]); const int ints[] = { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144 }; const unsigned nr_ints = sizeof(ints) / sizeof(ints[0]); /* adds some holes/pads in purpose */ struct big { char c; double d; short s; long double ld; int i; unsigned long long ull; long l; }; int bar(unsigned n, const char *str) { if (n >= nr_strs) return strcmp("foo", str); return strcmp(strs[n], str); } int foo(unsigned n) { unsigned int i; while (n-- > 0) { const char *str = n >= nr_strs ? "foo" : strs[n]; if (bar(n, str) != 0) return -1; } return 0; } int many(unsigned argc, ...) { unsigned i; va_list ap; va_start(ap, argc); for (i = 0; i < argc; i++) { if (ints[i] != va_arg(ap, int)) return -1; } va_end(ap); return 0; } int check(struct big val, struct big *ref) { if (val.c != ref->c) return -1; if (val.d != ref->d) return -1; if (val.s != ref->s) return -1; if (val.ld != ref->ld) return -1; if (val.i != ref->i) return -1; if (val.ull != ref->ull) return -1; if (val.l != ref->l) return -1; return 0; } int pass(int n) { struct big b = { 'b', 3.14, -1, 2.71828, n, 987654321ULL, 12345L }; return check(b, &b); } int main(int argc, char *argv[]) { unsigned n = 3; if (argc > 1) n = atoi(argv[1]); if (foo(n) < 0) return 1; if (many(nr_ints, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144) < 0) return 1; if (pass(n) < 0) return 1; return 0; } uftrace-0.15.2/tests/s-autoargs.c000066400000000000000000000005051455365734300166420ustar00rootroot00000000000000#include #include #include char *hello = "hello"; char *msg = "autoargs test"; int main(int argc, char *argv[]) { char buf[1024]; size_t len = strlen(msg); char *ptr = (char *)calloc(1, len + 1); free(ptr); if (!strcmp(hello, argv[1])) puts(hello); else puts(msg); return 0; } uftrace-0.15.2/tests/s-backtrace.cpp000066400000000000000000000005011455365734300172700ustar00rootroot00000000000000#include #include int foo(int count) { void *buf[count]; return backtrace(buf, count); } int c(int n) { return foo(n); } int b(int n) { return c(n); } int a(int n) { return b(n); } int main(int argc, char *argv[]) { int n = 5; if (argc > 1) n = atoi(argv[1]); a(n); return 0; } uftrace-0.15.2/tests/s-chcpu.c000066400000000000000000000007731455365734300161260ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include #include int main(int argc, char *argv[]) { cpu_set_t cpuset; int total_cpus; int c, cpu; total_cpus = sysconf(_SC_NPROCESSORS_ONLN); cpu = sched_getcpu(); CPU_ZERO(&cpuset); for (c = 0; c < total_cpus; c++) { if (c != cpu) CPU_SET(c, &cpuset); } sched_setaffinity(0, sizeof(cpuset), &cpuset); CPU_ZERO(&cpuset); CPU_SET(cpu, &cpuset); sched_setaffinity(0, sizeof(cpuset), &cpuset); return 0; } uftrace-0.15.2/tests/s-daemon.c000066400000000000000000000007201455365734300162570ustar00rootroot00000000000000/* * This is test to trace child task after calling daemon(). */ #include #include static int a(void); static int b(void); static int c(void); static int a(void) { return b() - 1; } static int b(void) { return c() + 1; } static int c(void) { return getpid() % 100000; } int main(int argc, char *argv[]) { int ret = 0; if (argc > 1) ret = atoi(argv[1]); if (daemon(0, 0) < 0) return -1; ret += a(); return ret ? 0 : 1; } uftrace-0.15.2/tests/s-data.c000066400000000000000000000005401455365734300157250ustar00rootroot00000000000000#include static int static_var = 1; int global_var = 2; int __attribute__((weak)) weak_var = 3; int foo(int *p, int n) { *p = n; return n + 1; } int filecmp(FILE *a, FILE *b) { return a == b; } int main(void) { foo(&static_var, static_var); foo(&global_var, global_var); foo(&weak_var, weak_var); return filecmp(stdout, stderr); } uftrace-0.15.2/tests/s-demangle.cpp000066400000000000000000000006711455365734300171350ustar00rootroot00000000000000#include class ABC { private: int n; int bar(void); int baz(void); public: ABC(int n); int foo(void); }; int ABC::foo(void) { return bar() + 1; } int ABC::bar(void) { return baz() - 1; } int ABC::baz(void) { return n; } ABC::ABC(int c) { n = c; } int main(int argc, char *argv[]) { int n = 0; ABC *abc; if (argc > 1) std::cin >> n; abc = new ABC(n); if (n == abc->foo()) return 0; return 1; } uftrace-0.15.2/tests/s-diff.c000066400000000000000000000004411455365734300157240ustar00rootroot00000000000000#include #include int bar(void) { usleep(100); } int foo(void) { usleep(1000); bar(); } int main(int argc, char *argv[]) { int do_sleep = 0; if (argc > 1) do_sleep = atoi(argv[1]); if (do_sleep) { usleep(10); foo(); bar(); } foo(); return 0; } uftrace-0.15.2/tests/s-dlopen.c000066400000000000000000000007161455365734300163020ustar00rootroot00000000000000#include #include #include int main(int argc, char *argv[]) { void *handle; void (*sym)(int); int n = 1; if (argc > 1) n = atoi(argv[1]); handle = dlopen("./libabc_test_lib.so", RTLD_LAZY); if (!handle) return -1; sym = dlsym(handle, "lib_a"); sym(n); dlclose(handle); handle = dlopen("./libfoo.so", RTLD_LAZY); if (!handle) return -1; sym = dlsym(handle, "foo"); sym(n); dlclose(handle); return 0; } uftrace-0.15.2/tests/s-dlopen2.cpp000066400000000000000000000007031455365734300167200ustar00rootroot00000000000000#include #include #include class Parent { public: int virtual bar(int); int virtual func(int); }; int main(int argc, char *argv[]) { void *handle; Parent *(*creat)(); Parent *p; int n = 1; if (argc > 1) n = atoi(argv[1]); handle = dlopen("./libbaz.so", RTLD_LAZY); if (!handle) return -1; creat = (Parent * (*)()) dlsym(handle, "creat"); p = creat(); p->bar(n); dlclose(handle); return 0; } uftrace-0.15.2/tests/s-dwarf1.c000066400000000000000000000006361455365734300162060ustar00rootroot00000000000000#include char *gs; char gc; double gd; enum xx { ONE = 1, TWO, }; void null(char *s) { } int foo(volatile int a, long b) { return a + b; } float bar(char *s, char c, double d, void (*fp)(char *s)) { gs = s; gc = c; gd = d; fp(NULL); return -1.0; } void baz(enum xx x) { foo(x, -1); } int main(void) { foo(-1, 32768); bar("string argument", 'c', 0.00001, null); baz(ONE); return 0; } uftrace-0.15.2/tests/s-dwarf2.cpp000066400000000000000000000010431455365734300165400ustar00rootroot00000000000000#include #include enum xxx { FOO = 3, BAR, }; struct empty {}; class A { public: A(empty e, enum xxx x, long i, const char *s) : E(e) , X(x) , I(i) , S(s) { } private: empty E; enum xxx X; long I; const char *S; }; bool myless(int a, int b) { return a < b; } int main() { int x[5] = { 5, 3, 9, 2, 7 }; empty E; A(E, FOO, BAR, "debug info test"); std::sort(x, x + 5, myless); std::sort(x, x + 5, std::less()); std::sort(x, x + 5, [](int a, int b) { return a < b; }); return 0; } uftrace-0.15.2/tests/s-dwarf3.cpp000066400000000000000000000015231455365734300165440ustar00rootroot00000000000000#include #include template class C { public: C(T v, const char *s) { construct(v, s); } C(const C &t) { copy(t.val, t.str); } void construct(T v, const char *s); void copy(T v, const char *s); T val; const char *str; }; template void C::construct(T val, const char *str) { this->val = val; this->str = str; } template void C::copy(T val, const char *str) { this->val = val; this->str = str; } template C foo(C c1, C &c2, const char *str, float f) { return C(c1.val + c2.val + f, str); } int main() { C c1(1, "debug info"); C c2(2, (const char *)0x1234); /* should not crash */ /* 'c1' is passed by value (for each member) */ C c3 = foo(c1, c2, "passed by value", 0.001f); return c3.val == 3 ? 0 : 1; } uftrace-0.15.2/tests/s-dwarf5.c000066400000000000000000000037171455365734300162150ustar00rootroot00000000000000#include #include int gx; char *gy; struct int1 { int a; }; struct int3 { int a, b, c; }; struct lng1 { long a; }; struct lng3 { long a, b, c; }; struct dbl1 { double a; }; struct dbl3 { double a, b, c; }; struct mix1 { int a; float b; }; struct mix2 { int a; float b; double c; }; struct mix3 { int a; float b; double c; long d; }; int pass_int1(struct int1 a, int x, char *y, double z) { gx = x; gy = y; return (a.a == z) ? 0 : 1; } int pass_int3(struct int3 a, int x, char *y, double z) { gx = x; gy = y; return (a.a + a.b + a.c) == z ? 0 : 1; } long pass_lng1(struct lng1 a, int x, char *y, double z) { gx = x; gy = y; return (a.a == z) ? 0 : 1; } long pass_lng3(struct lng3 a, int x, char *y, double z) { gx = x; gy = y; return (a.a + a.b + a.c) == z ? 0 : 1; } double pass_dbl1(struct dbl1 a, int x, char *y, double z) { gx = x; gy = y; return (a.a == z) ? 0 : 1; } double pass_dbl3(struct dbl3 a, int x, char *y, double z) { gx = x; gy = y; return (a.a + a.b + a.c) == z ? 0 : 1; } float pass_mix1(struct mix1 a, int x, char *y, double z) { gx = x; gy = y; return (a.a + a.b == z) ? 0 : 1; } float pass_mix2(struct mix2 a, int x, char *y, double z) { gx = x; gy = y; return (a.a + a.b + a.c) == z ? 0 : 1; } int pass_mix3(struct mix3 a, int x, char *y, double z) { gx = x; gy = y; return (a.a + a.b + a.c + a.d) == z ? 0 : 1; } int main(int argc, char *argv[]) { double z = 3.0; int s = 0; if (argc > 1) z = atof(argv[1]); s += pass_int1((struct int1){ 1 }, 1, "2", z); s += pass_int3((struct int3){ 1, 2, 3 }, 1, "2", z); s += pass_lng1((struct lng1){ 1 }, 1, "2", z); s += pass_lng3((struct lng3){ 1, 2, 3 }, 1, "2", z); s += pass_dbl1((struct dbl1){ 1 }, 1, "2", z); s += pass_dbl3((struct dbl3){ 1, 2, 3 }, 1, "2", z); s += pass_mix1((struct mix1){ 1, 2 }, 1, "2", z); s += pass_mix2((struct mix2){ 1, 2, 3 }, 1, "2", z); s += pass_mix3((struct mix3){ 1, 2, 3, 4 }, 1, "2", z); return s == 9; } uftrace-0.15.2/tests/s-dwarf6.cpp000066400000000000000000000007561455365734300165560ustar00rootroot00000000000000#include #include std::string addString(std::string dst, const char *src) { return dst + src; } std::vector addItem(std::vector v, int n) { v.push_back(n); return v; } int main(int argc, char *argv[]) { std::vector v = { 1, 2, 3 }; int n = 0; const char *s = "test"; if (argc > 1) n = atoi(argv[1]); if (argc > 2) s = argv[2]; if (addString(" uftrace ", s) != " uftrace test") return 1; if (addItem(v, n)[3] != 0) return 1; return 0; } uftrace-0.15.2/tests/s-dwarf7.cpp000066400000000000000000000003661455365734300165540ustar00rootroot00000000000000#include __attribute__((noinline)) int compare_iters(std::vector::iterator beg, std::vector::iterator end) { return beg == end; } int main() { std::vector v(3, 1); return compare_iters(v.begin(), v.end()); } uftrace-0.15.2/tests/s-dynamic.c000066400000000000000000000005631455365734300164450ustar00rootroot00000000000000#include #include int foo(int n) { volatile int i = 0; int sum = 0; for (i = 0; i < n; i++) sum += i; return sum; } int bar(int n) { int i = 0; volatile int sum = 0; for (i = 0; i < n; i++) sum += i; return sum; } int main(int argc, char *argv[]) { int n = 5; if (argc > 1) n = atoi(argv[1]); foo(n); bar(n); return 0; } uftrace-0.15.2/tests/s-dynmain.c000066400000000000000000000002231455365734300164510ustar00rootroot00000000000000#include extern int lib_a(int i); extern int lib_d(int i); int main(void) { int res = lib_a(1111); res += lib_d(4444); return 0; } uftrace-0.15.2/tests/s-enum.c000066400000000000000000000003131455365734300157560ustar00rootroot00000000000000#include enum foo_enum { XXX = 1, YYY, }; void foo(enum foo_enum a) { static volatile int i; i += a; } int main(int argc, char *argv[]) { kill(0, 0); foo(0); foo(XXX); return 0; } uftrace-0.15.2/tests/s-enum2.c000066400000000000000000000007221455365734300160440ustar00rootroot00000000000000static int cnt; enum memory_order_modifier { zero = 0, memory_order_mask = 0x0ffff, memory_order_modifier_mask = 0xffff0000, memory_order_hle_acquire = 0x10000, memory_order_hle_release = 0x20000 }; __attribute__((noinline)) void foo(enum memory_order_modifier m) { if (m != zero) cnt++; } int main() { foo(memory_order_mask); foo(memory_order_modifier_mask); foo(memory_order_hle_acquire); foo(memory_order_hle_release); return cnt == 4 ? 0 : -1; } uftrace-0.15.2/tests/s-exception.cpp000066400000000000000000000004741455365734300173600ustar00rootroot00000000000000#include using namespace std; static volatile int n; void foo() { n++; } void bar() { n--; } void oops() { throw exception(); } int test() { int r = 0; try { oops(); } catch (exception &e) { r = 1; } return r; } int main() { int r; foo(); r = test(); bar(); return !(r == 1); } uftrace-0.15.2/tests/s-exception2.cpp000066400000000000000000000003411455365734300174330ustar00rootroot00000000000000struct exc {}; void foo() { try { throw exc(); } catch (const exc &e) { throw; } } int bar() { static volatile int n; return n++; } int main() { try { foo(); } catch (const exc &e) { bar(); } return 0; } uftrace-0.15.2/tests/s-exception3.cpp000066400000000000000000000023521455365734300174400ustar00rootroot00000000000000#include #include struct exc {}; class A { volatile int i; public: A(); ~A(); }; // noinline A::A() { i++; } A::~A() { i--; } class B { volatile int i; public: B(); ~B(); }; B::B() { i++; } B::~B() { i--; } class C { volatile int i; public: C(); ~C(); }; C::C() { i++; } C::~C() { i--; } extern void foo(); extern void bar(); extern void baz(); extern void catch_exc(int i); extern void foo1(); extern void foo2(); extern void foo3(); extern void foo4(); extern void foo5(); extern void bar1(); extern void bar2(); extern void bar3(); void foo1() { foo2(); } void foo2() { foo3(); } void foo3() { try { foo4(); } catch (const exc &e) { throw; } } void foo4() { C c; foo5(); } void foo5() { throw exc(); } void foo() { try { foo1(); } catch (const exc &e) { B b; throw; } } void bar1() { bar2(); } void bar2() { bar3(); } void bar3() { C c; throw exc(); } void bar() { try { B b; bar1(); } catch (const exc &e) { catch_exc(2); } } void baz() { static volatile int n; n++; } void catch_exc(int i) { if (i == 1) bar(); else baz(); } int main(int argc, char *argv[]) { try { A a; foo(); } catch (const exc &e) { catch_exc(1); } return 0; } uftrace-0.15.2/tests/s-exception4.cpp000066400000000000000000000002111455365734300174310ustar00rootroot00000000000000int foo(volatile int i) { if (i) throw 1; return 0; } int main() { int i = 1; try { foo(i); } catch (int n) { } return 0; } uftrace-0.15.2/tests/s-exit.c000066400000000000000000000002051455365734300157630ustar00rootroot00000000000000#include static void foo(int status) { exit(status); } int main(int argc, char *argv[]) { foo(argc - 1); return 0; } uftrace-0.15.2/tests/s-exp-char.c000066400000000000000000000005231455365734300165240ustar00rootroot00000000000000#include char ga; char gb; char gc; char gd; char foo(char a, char b, char c) { ga = a; gb = b; gc = c; return 'd'; } char bar(char a, char b, char c, char d) { ga = a; gb = b; gc = c; gd = d; return 0; } int main(void) { char d; d = foo('f', 'o', 'o'); d += bar(0, 'B', 'a', 'r'); return d == 'd' ? 0 : 1; } uftrace-0.15.2/tests/s-exp-float.c000066400000000000000000000006461455365734300167220ustar00rootroot00000000000000float float_add(float a, float b) { return a + b; } float float_sub(float a, double b) { return a - b; } double float_mul(double a, float b) { return a * b; } double float_div(double a, double b) { return a / b; } int main(int argc, char *argv[]) { double a, b, c, d; a = float_add(-0.1, 0.2); b = float_sub(0.1, 0.2); c = float_mul(300, 400); d = float_div(4e10, -0.02); return (a + b + c + d) ? 0 : 1; } uftrace-0.15.2/tests/s-exp-int.c000066400000000000000000000005561455365734300164070ustar00rootroot00000000000000int int_add(int a, long b) { return a + b; } int int_sub(char a, short b) { return a - b; } int int_mul(long long a, int b) { return a * b; } int int_div(int a, long b) { return a / b; } int main(int argc, char *argv[]) { int a, b, c, d; a = int_add(-1, 2); b = int_sub(1, 2); c = int_mul(3, 4); d = int_div(4, -2); return (a + b + c + d) ? 0 : 1; } uftrace-0.15.2/tests/s-exp-mixed.c000066400000000000000000000012601455365734300167140ustar00rootroot00000000000000#include char *p; double mixed_add(int a, float b) { return a + b; } long mixed_sub(void *a, unsigned long b) { return (long)a - b; } long long mixed_mul(double a, long long b) { return a * b; } long double mixed_div(long long a, long double b, int c) { return a / b; } char *mixed_str(char *a, double b) { static char buf[32]; p = a; if (b) snprintf(buf, sizeof(buf), "%.2f", b); return b ? buf : "return"; } int main(int argc, char *argv[]) { int a, b, c, d; a = mixed_add(-1, 0.2); b = mixed_sub((void *)0x400000, 2048); c = mixed_mul(-3, 80000000000LL); d = mixed_div(4, -0.000002, 3); mixed_str("argument", 0); return (a + b + c + d) ? 0 : 1; } uftrace-0.15.2/tests/s-exp-str.c000066400000000000000000000010611455365734300164150ustar00rootroot00000000000000char *str_cat(char *d, char *s) { char *p = d; while (*d) d++; while (*d++ = *s++) continue; return p; } char *str_cpy(char *d, char *s) { char *p = d; while (*d++ = *s++) continue; return p; } char a[32]; char b[32]; int main(void) { char *c; /* * string arguments are aligned in 8-byte boundary including * 2-byte length prefix so checking strings with length of * 5, 6 and 7 will be enough. */ str_cpy(a, "hello"); str_cpy(b, " world"); c = str_cat(a, b); str_cpy(a, "goodbye"); c = str_cat(a, b); return *c ? 0 : 1; } uftrace-0.15.2/tests/s-fibonacci.c000066400000000000000000000003371455365734300167350ustar00rootroot00000000000000#include #include int fib(int n) { if (n <= 2) return 1; return fib(n - 1) + fib(n - 2); } int main(int argc, char *argv[]) { int n = 8; if (argc > 1) n = atoi(argv[1]); return !!fib(n); } uftrace-0.15.2/tests/s-file-var.py000077500000000000000000000001321455365734300167270ustar00rootroot00000000000000#!/usr/bin/env python3 import os def foo(): print(os.path.basename(__file__)) foo() uftrace-0.15.2/tests/s-float-libcall.c000066400000000000000000000001671455365734300175260ustar00rootroot00000000000000#include int main(int argc, char *argv[]) { float e = expf(1.0f); double one = log(e); return one - 1; } uftrace-0.15.2/tests/s-fork.c000066400000000000000000000010541455365734300157560ustar00rootroot00000000000000/* * This is test to trace child task after calling fork(). */ #include #include #include static int a(void); static int b(void); static int c(void); static int a(void) { return b() - 1; } static int b(void) { return c() + 1; } static int c(void) { return getpid() % 100000; } int main(int argc, char *argv[]) { int ret = 0; if (argc > 1) ret = atoi(argv[1]); switch (fork()) { case -1: return 1; default: wait(NULL); /* fall through */ case 0: ret += a(); break; } return ret ? 0 : 1; } uftrace-0.15.2/tests/s-fork2.c000066400000000000000000000004011455365734300160330ustar00rootroot00000000000000#include #include #include #include int main(void) { switch (fork()) { case -1: return 1; default: wait(NULL); /* fall through */ case 0: close(open("/dev/null", O_RDONLY)); break; } return 0; } uftrace-0.15.2/tests/s-forkexec.c000066400000000000000000000007321455365734300166250ustar00rootroot00000000000000/* * This test calls fork() and then exec() to run the t-abc executable. */ #include #include #include #include #include #define TEST_PROG1 "t-abc" #define TEST_PROG2 "t-openclose" int main(int argc, char *argv[]) { int pid; pid = fork(); if (pid == 0) { if (argc == 1) execl(TEST_PROG1, TEST_PROG1, NULL); else execl(TEST_PROG2, TEST_PROG2, NULL); exit(2); } waitpid(pid, NULL, 0); return 0; } uftrace-0.15.2/tests/s-getids.c000066400000000000000000000002431455365734300162730ustar00rootroot00000000000000#include int main(int argc, char *argv[]) { getpid(); getppid(); getpgid(0); getsid(0); getuid(); geteuid(); getgid(); getegid(); return 0; } uftrace-0.15.2/tests/s-hello.c000066400000000000000000000002411455365734300161150ustar00rootroot00000000000000#include int main(int argc, char *argv[]) { const char *whom = "world"; if (argc > 1) whom = argv[1]; printf("Hello %s\n", whom); return 0; } uftrace-0.15.2/tests/s-indirect-return.cpp000066400000000000000000000001211455365734300204650ustar00rootroot00000000000000#include int main() { std::ostringstream ss; ss.str(); return 0; } uftrace-0.15.2/tests/s-lib.c000066400000000000000000000004601455365734300155630ustar00rootroot00000000000000/* * This is test to trace function in a library. */ #include int lib_a(int i); static int lib_b(int i); static int lib_c(int i); int lib_a(int i) { return lib_b(i + 1) - 1; } static int lib_b(int i) { return lib_c(i - 1) + 1; } static int lib_c(int mask) { return getpid() % mask; } uftrace-0.15.2/tests/s-libbar.cpp000066400000000000000000000002531455365734300166100ustar00rootroot00000000000000class Parent { public: virtual int func(int n); virtual int bar(int n); }; int Parent::bar(int n) { return func(n ?: 1); } int Parent::func(int n) { return 0; } uftrace-0.15.2/tests/s-libbaz.cpp000066400000000000000000000003651455365734300166240ustar00rootroot00000000000000class Parent { public: virtual int func(int n); virtual int bar(int n); }; class Child : public Parent { public: virtual int func(int n); }; int Child::func(int n) { return 100; } extern "C" Parent *creat() { return new Child; } uftrace-0.15.2/tests/s-libdyn1.c000066400000000000000000000004601455365734300163570ustar00rootroot00000000000000/* * This is test to trace function in a library. */ #include int lib_a(int i); static int lib_b(int i); static int lib_c(int i); int lib_a(int i) { return lib_b(i + 1) - 1; } static int lib_b(int i) { return lib_c(i - 1) + 1; } static int lib_c(int mask) { return getpid() % mask; } uftrace-0.15.2/tests/s-libdyn2.c000066400000000000000000000004601455365734300163600ustar00rootroot00000000000000/* * This is test to trace function in a library. */ #include int lib_d(int i); static int lib_e(int i); static int lib_f(int i); int lib_d(int i) { return lib_e(i + 1) - 1; } static int lib_e(int i) { return lib_f(i - 1) + 1; } static int lib_f(int mask) { return getpid() % mask; } uftrace-0.15.2/tests/s-libexcept-main.cpp000066400000000000000000000004471455365734300202630ustar00rootroot00000000000000#include "s-libexcept.hpp" #include #include class YYY { public: YYY() { throw std::runtime_error("YYY exception"); } ~YYY() { } }; int main(int argc, char *argv[]) { try { XXX xxx; } catch (...) { } try { YYY yyy; } catch (...) { } return 0; } uftrace-0.15.2/tests/s-libexcept.cpp000066400000000000000000000001501455365734300173300ustar00rootroot00000000000000#include "s-libexcept.hpp" #include XXX::XXX() { throw std::runtime_error("XXX error"); } uftrace-0.15.2/tests/s-libexcept.hpp000066400000000000000000000001501455365734300173350ustar00rootroot00000000000000#ifndef XXX_HPP #define XXX_HPP class XXX { public: XXX(); ~XXX() { } }; #endif /* XXX_HPP */ uftrace-0.15.2/tests/s-libfoo.cpp000066400000000000000000000002211455365734300166220ustar00rootroot00000000000000volatile int a = 0; class AAA { public: static void bar(int n) { a = n; } }; extern "C" { void foo(int n) { a = n; AAA::bar(n); } } uftrace-0.15.2/tests/s-libmain.c000066400000000000000000000006501455365734300164310ustar00rootroot00000000000000/* * This is a test to trace functions in a library. * This file is not built with tracing, but library functions are, * so the result will only contain lib_a() and its children. */ #include #include extern int lib_a(int mask); int foo(void) { return lib_a(0xfff); } int main(int argc, char *argv[]) { int ret = 0; if (argc > 1) ret = atoi(argv[1]); ret += foo(); return ret ? 0 : 1; } uftrace-0.15.2/tests/s-libmain.py000077500000000000000000000001601455365734300166360ustar00rootroot00000000000000#!/usr/bin/python3 import mymod def myfunc(): mymod.public_func() if __name__ == '__main__': myfunc() uftrace-0.15.2/tests/s-longjmp.c000066400000000000000000000003671455365734300164710ustar00rootroot00000000000000#include jmp_buf env; int foo(void) { longjmp(env, 1); return 0; } int bar(int a) { return a - 2; } int main(int argc, char *argv[]) { int ret; if (!setjmp(env)) ret = foo(); else ret = bar(argc); return !(ret == -1); } uftrace-0.15.2/tests/s-longjmp2.c000066400000000000000000000005731455365734300165520ustar00rootroot00000000000000#include jmp_buf env; int foo(void) { longjmp(env, 1); return 1; } int baz(void) { longjmp(env, 2); return 2; } int bar(void) { return baz(); } int main(int argc, char *argv[]) { int ret; switch (setjmp(env)) { case 0: ret = foo(); break; case 1: ret = bar(); break; case 2: ret = 0; break; default: ret = -1; break; } return ret; } uftrace-0.15.2/tests/s-longjmp3.c000066400000000000000000000010231455365734300165420ustar00rootroot00000000000000#include #include jmp_buf env1; jmp_buf env2; int foo(int i) { if (i == 1) longjmp(env1, 1); else longjmp(env2, 3); return 0; } int baz(int i) { if (i == 1) longjmp(env1, 2); else longjmp(env2, 4); return 0; } int bar(int i) { return baz(i); } int main(int argc, char *argv[]) { int ret; ret = setjmp(env1); getpid(); if (ret == 0) ret = setjmp(env2); if (ret == 0) foo(1); else if (ret == 1) bar(1); else if (ret == 2) foo(2); else if (ret == 3) bar(2); return 0; } uftrace-0.15.2/tests/s-malloc-fork.c000066400000000000000000000026321455365734300172260ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include #include void *(*real_malloc)(size_t sz); void *(*real_realloc)(void *ptr, size_t sz); void (*real_free)(void *ptr); #define ALIGN(n, a) (((n) + (a)-1) & ~((a)-1)) #define MALLOC_BUFSIZE (128 * 1024 * 1024) /* this is needed for optimized binaries */ static char buf[MALLOC_BUFSIZE] __attribute__((aligned(8))); void *malloc(size_t sz) { static unsigned alloc_size; void *ptr; if (real_malloc) return real_malloc(sz); sz = ALIGN(sz, 8); if (alloc_size + sz > sizeof(buf)) return NULL; ptr = buf + alloc_size; alloc_size += sz; return ptr; } void *realloc(void *ptr, size_t size) { char *p; if (real_realloc && (ptr < buf || ptr >= &buf[MALLOC_BUFSIZE])) return real_realloc(ptr, size); p = malloc(size); /* using memcpy() caused segfault due to alignment */ if (ptr != NULL) { char *q = ptr; size_t i; for (i = 0; i < size; i++) p[i] = q[i]; } return p; } void free(void *ptr) { char *p = ptr; if (buf <= p && p < &buf[MALLOC_BUFSIZE]) return; if (real_free) real_free(ptr); } static void hook(void) { real_malloc = dlsym(RTLD_NEXT, "malloc"); real_realloc = dlsym(RTLD_NEXT, "realloc"); real_free = dlsym(RTLD_NEXT, "free"); } static __attribute__((section(".preinit_array"))) void (*preinit_func_table[])(void) = { hook, }; int main(void) { fork(); free(malloc(1)); return 0; } uftrace-0.15.2/tests/s-malloc-hook.c000066400000000000000000000025521455365734300172260ustar00rootroot00000000000000#define _GNU_SOURCE #include #include void *(*real_malloc)(size_t sz); void *(*real_realloc)(void *ptr, size_t sz); void (*real_free)(void *ptr); #define ALIGN(n, a) (((n) + (a)-1) & ~((a)-1)) #define MALLOC_BUFSIZE (128 * 1024 * 1024) /* this is needed for optimized binaries */ static char buf[MALLOC_BUFSIZE] __attribute__((aligned(8))); void *malloc(size_t sz) { static unsigned alloc_size; void *ptr; if (real_malloc) return real_malloc(sz); sz = ALIGN(sz, 8); if (alloc_size + sz > sizeof(buf)) return NULL; ptr = buf + alloc_size; alloc_size += sz; return ptr; } void *realloc(void *ptr, size_t size) { char *p; if (real_realloc && (ptr < buf || ptr >= &buf[MALLOC_BUFSIZE])) return real_realloc(ptr, size); p = malloc(size); /* using memcpy() caused segfault due to alignment */ if (ptr != NULL) { char *q = ptr; size_t i; for (i = 0; i < size; i++) p[i] = q[i]; } return p; } void free(void *ptr) { char *p = ptr; if (buf <= p && p < &buf[MALLOC_BUFSIZE]) return; if (real_free) real_free(ptr); } static void hook(void) { real_malloc = dlsym(RTLD_NEXT, "malloc"); real_realloc = dlsym(RTLD_NEXT, "realloc"); real_free = dlsym(RTLD_NEXT, "free"); } static __attribute__((section(".preinit_array"))) void (*preinit_func_table[])(void) = { hook, }; int main(void) { free(malloc(16)); return 0; } uftrace-0.15.2/tests/s-malloc-tsd.c000066400000000000000000000033631455365734300170610ustar00rootroot00000000000000#define _GNU_SOURCE #include #include #include #include static pthread_key_t key; void *(*real_malloc)(size_t sz); void *(*real_realloc)(void *ptr, size_t sz); void (*real_free)(void *ptr); #define ALIGN(n, a) (((n) + (a)-1) & ~((a)-1)) #define MALLOC_BUFSIZE (128 * 1024 * 1024) /* this is needed for optimized binaries */ static char buf[MALLOC_BUFSIZE] __attribute__((aligned(8))); void *malloc(size_t sz) { static unsigned alloc_size; void *ptr; if (real_malloc) return real_malloc(sz); sz = ALIGN(sz, 8); if (alloc_size + sz > sizeof(buf)) return NULL; ptr = buf + alloc_size; alloc_size += sz; return ptr; } void *realloc(void *ptr, size_t size) { char *p; if (real_realloc && (ptr < buf || ptr >= &buf[MALLOC_BUFSIZE])) return real_realloc(ptr, size); p = malloc(size); /* using memcpy() caused segfault due to alignment */ if (ptr != NULL) { char *q = ptr; size_t i; for (i = 0; i < size; i++) p[i] = q[i]; } return p; } void free(void *ptr) { char *p = ptr; if (buf <= p && p < &buf[MALLOC_BUFSIZE]) return; if (real_free) real_free(ptr); } static void hook(void) { real_malloc = dlsym(RTLD_NEXT, "malloc"); real_realloc = dlsym(RTLD_NEXT, "realloc"); real_free = dlsym(RTLD_NEXT, "free"); } static __attribute__((section(".preinit_array"))) void (*preinit_func_table[])(void) = { hook, }; void tsd_dtor(void *data) { free(data); } void *thread(void *arg) { pthread_setspecific(key, malloc(2)); return NULL; } int main(void) { pthread_t t; pthread_key_create(&key, tsd_dtor); pthread_setspecific(key, malloc(1)); pthread_create(&t, NULL, thread, NULL); pthread_join(t, NULL); tsd_dtor(pthread_getspecific(key)); pthread_key_delete(key); return 0; } uftrace-0.15.2/tests/s-malloc.c000066400000000000000000000012271455365734300162660ustar00rootroot00000000000000#include #include #define ALIGN(n, a) (((n) + (a)-1) & ~((a)-1)) #define MALLOC_BUFSIZE (128 * 1024 * 1024) int malloc_count; int free_count; void *malloc(size_t size) { static char buf[MALLOC_BUFSIZE] __attribute__((aligned(16))); static unsigned alloc_size; void *ptr; size = ALIGN(size, 16); if (alloc_size + size > sizeof(buf)) return NULL; ptr = buf + alloc_size; alloc_size += size; malloc_count++; return ptr; } void *realloc(void *ptr, size_t size) { void *p = malloc(size); if (ptr) memcpy(p, ptr, size); return p; } void free(void *ptr) { free_count++; } int main(void) { free(malloc(16)); return 0; } uftrace-0.15.2/tests/s-mmap.c000066400000000000000000000006301455365734300157460ustar00rootroot00000000000000#include #include #include #include int foo(long sz) { int fd; void *ptr; fd = open("/dev/zero", O_RDONLY); ptr = mmap(NULL, sz, PROT_READ, MAP_ANON | MAP_PRIVATE, fd, 0); mprotect(ptr, sz, PROT_NONE); munmap(ptr, sz); close(fd); return 0; } int main(int argc, char *argv[]) { int n = 4096; if (argc > 1) n = atoi(argv[1]); foo(n); return 0; } uftrace-0.15.2/tests/s-namespace.cpp000066400000000000000000000017631455365734300173200ustar00rootroot00000000000000#include namespace ns { namespace ns1 { class foo { private: int size_; void *bar1(void); void *bar2(void); void *bar3(void); public: foo(int size); void bar(void); }; foo::foo(int size) { size_ = size; } void *foo::bar1(void) { return bar2(); } void *foo::bar2(void) { return bar3(); } void *foo::bar3(void) { return malloc(size_); } void foo::bar(void) { free(bar1()); } } namespace ns2 { class foo { private: int size_; void *bar1(void); void *bar2(void); void *bar3(void); public: foo(int size); void bar(void); }; foo::foo(int size) { size_ = size; } void *foo::bar1(void) { return bar2(); } void *foo::bar2(void) { return bar3(); } void *foo::bar3(void) { return malloc(size_); } void foo::bar(void) { free(bar1()); } } } int main(int argc, char *argv[]) { int n = 0; ns::ns1::foo *foo1; ns::ns2::foo *foo2; foo1 = new ns::ns1::foo(1); foo1->bar(); delete foo1; foo2 = new ns::ns2::foo(2); foo2->bar(); delete foo2; return 0; } uftrace-0.15.2/tests/s-nest-libcall.c000066400000000000000000000002721455365734300173670ustar00rootroot00000000000000#include extern int lib_a(int); extern void foo(int); int main(int argc, char *argv[]) { int n = 1; if (argc > 1) n = atoi(argv[1]); lib_a(n); foo(n); return 0; } uftrace-0.15.2/tests/s-nested.c000066400000000000000000000011501455365734300162740ustar00rootroot00000000000000#include int foo(void) { int count = 0; __attribute__((noinline)) int foo_internal(void) { return count++; } return foo_internal(); } int bar(void) { int arr[3] = { 2, 3, 1 }; __attribute__((noinline)) int compar(const void *a, const void *b) { const int *ai = a; const int *bi = b; return *ai - *bi; } qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), compar); if (arr[0] != 1 || arr[1] != 2 || arr[2] != 3) return 1; return 0; } int main(int argc, char *argv[]) { int n = 1; if (argc > 1) n = strtol(argv[1], NULL, 0); while (n--) foo(); return bar(); } uftrace-0.15.2/tests/s-openclose.c000066400000000000000000000001241455365734300170010ustar00rootroot00000000000000#include int main(void) { fclose(fopen("/dev/null", "r")); return 0; } uftrace-0.15.2/tests/s-patchable-abc.c000066400000000000000000000012071455365734300174630ustar00rootroot00000000000000/* * This is a basic test to verify whether uftrace works on the system. */ #include #include #if __x86_64__ #define NR_NOPS 5 #elif __aarch64__ #define NR_NOPS 2 #else #define NR_NOPS 0 #endif #define __patchable __attribute__((patchable_function_entry(NR_NOPS))) static int a(void); static int b(void); static int c(void); __patchable static int a(void) { return b() - 1; } static int b(void) { return c() + 1; } __patchable static int c(void) { return getpid() % 100000; } __patchable int main(int argc, char *argv[]) { int ret = 0; if (argc > 1) ret = atoi(argv[1]); ret += a(); return ret ? 0 : 1; } uftrace-0.15.2/tests/s-pltarg.c000066400000000000000000000003061455365734300163050ustar00rootroot00000000000000#include int main(int argc, char *argv[]) { int num = 3; char *path = getenv("HOME"); void *ptr; if (argc > 1) num = atoi(argv[1]); ptr = malloc(num); free(ptr); return 0; } uftrace-0.15.2/tests/s-posix_spawn.c000066400000000000000000000011101455365734300173600ustar00rootroot00000000000000/* * This test calls posix_spawn() to run the t-abc and t-openclose executables. */ #include #include #include #include #define TEST_PROG1 "t-abc" #define TEST_PROG2 "t-openclose" int main(int argc, char *argv[]) { int pid; char *args1[] = { TEST_PROG1, NULL }; char *args2[] = { TEST_PROG2, NULL }; char *envp[] = { "PATH=.", "HOME=/home/user", NULL }; posix_spawn(&pid, TEST_PROG1, NULL, NULL, args1, envp); waitpid(pid, NULL, 0); posix_spawn(&pid, TEST_PROG2, NULL, NULL, args2, envp); waitpid(pid, NULL, 0); return 0; } uftrace-0.15.2/tests/s-racycount.c000066400000000000000000000010701455365734300170220ustar00rootroot00000000000000#include #define COUNT 10000 static volatile int count; static void racy_count(pthread_barrier_t *bar) { int i; pthread_barrier_wait(bar); for (i = 0; i < COUNT; i++) count++; } static void *thread_fn(void *arg) { racy_count(arg); return NULL; } int main(void) { pthread_t id[2]; pthread_barrier_t bar; pthread_barrier_init(&bar, NULL, 3); pthread_create(&id[0], NULL, thread_fn, &bar); pthread_create(&id[1], NULL, thread_fn, &bar); racy_count(&bar); pthread_join(id[0], NULL); pthread_join(id[1], NULL); return !(count > 0); } uftrace-0.15.2/tests/s-return.c000066400000000000000000000013601455365734300163340ustar00rootroot00000000000000#include #include #include #include struct large { char buf[4096]; }; struct small { unsigned char bit : 1; }; struct large return_large(char patt) { struct large l; memset(l.buf, patt, sizeof(l.buf)); return l; } #if __clang__ __attribute__((optnone)) #endif struct small return_small(void) { struct small s = { .bit = 1 }; return s; } #if __clang__ __attribute__((optnone)) #endif long double return_long_double(void) { return LDBL_MAX; } int main(void) { struct large l; struct small s; long double ld; l = return_large(1); if (l.buf[10] != 1) return 1; s = return_small(); if (s.bit != 1) return 1; ld = return_long_double(); if (ld != LDBL_MAX) return 1; return 0; } uftrace-0.15.2/tests/s-sdt.c000066400000000000000000000003071455365734300156070ustar00rootroot00000000000000#include #include void foo(int n) { STAP_PROBE(uftrace, event); } int main(int argc, char *argv[]) { int n = 1; if (argc > 1) n = atoi(argv[1]); foo(n); return 0; } uftrace-0.15.2/tests/s-signal.c000066400000000000000000000007201455365734300162710ustar00rootroot00000000000000#include #include volatile int dummy; typedef void (*sighandler_t)(int sig); sighandler_t old_handler; int foo(void) { return dummy; } void bar(int n) { dummy = n; } void sighandler(int sig) { bar(sig); if (old_handler != SIG_DFL) old_handler(sig); } int main(int argc, char *argv[]) { int sig = SIGUSR1; if (argc > 1) sig = atoi(argv[1]); foo(); old_handler = signal(sig, sighandler); raise(sig); foo(); return 0; } uftrace-0.15.2/tests/s-signal2.c000066400000000000000000000011711455365734300163540ustar00rootroot00000000000000#include "../utils/compiler.h" #include #include #include void foo(void); void bar(void); volatile int count; volatile int max = 3; void foo(void) { while (count < max) bar(); } void bar(void) { int prev = count; /* wait for signal */ while (prev == count) cpu_relax(); } void sighandler(int sig) { count++; } int main(int argc, char *argv[]) { struct itimerval it = { .it_value = { .tv_usec = 1, }, .it_interval = { .tv_usec = 1, }, }; if (argc > 1) max = atoi(argv[1]); signal(SIGPROF, sighandler); setitimer(ITIMER_PROF, &it, NULL); foo(); return 0; } uftrace-0.15.2/tests/s-sleep.c000066400000000000000000000004141455365734300161240ustar00rootroot00000000000000#include #include void *mem_alloc(void) { return malloc(0); } void mem_free(void *ptr) { free(ptr); } void bar(void) { usleep(2000); } void foo(void) { void *p = mem_alloc(); bar(); mem_free(p); } int main(void) { foo(); return 0; } uftrace-0.15.2/tests/s-sleep.py000077500000000000000000000002471455365734300163410ustar00rootroot00000000000000#!/usr/bin/env python3 import time def foo(): bar() time.sleep(0.1) baz() def bar(): pass def baz(): pass if __name__ == '__main__': foo() uftrace-0.15.2/tests/s-sleep2.c000066400000000000000000000002551455365734300162110ustar00rootroot00000000000000#include #include void bar(void) { usleep(3000); } void foo(void) { usleep(5000); bar(); } int main(void) { usleep(1000); foo(); return 0; } uftrace-0.15.2/tests/s-sort.c000066400000000000000000000010261455365734300160030ustar00rootroot00000000000000#include void loop(void) { int i; for (i = 0; i < 10000; i++) asm volatile("" ::: "memory"); } void foo(void) { loop(); loop(); loop(); } void bar(void) { int i; for (i = 0; i < 50000; i++) asm volatile("" ::: "memory"); usleep(10000); } int main(int argc, char *argv[]) { int i; for (i = 0; i < 50000; i++) asm volatile("" ::: "memory"); foo(); for (i = 0; i < 50000; i++) asm volatile("" ::: "memory"); foo(); for (i = 0; i < 50000; i++) asm volatile("" ::: "memory"); bar(); return 0; } uftrace-0.15.2/tests/s-std-string.cpp000066400000000000000000000006201455365734300174510ustar00rootroot00000000000000#include std::string s[] = { "Hello", "World!", "std::string support is done!" }; __attribute__((noinline)) void std_string_arg(std::string &s) { s = s; } __attribute__((noinline)) std::string std_string_ret(int index) { return s[index]; } int main() { std_string_arg(s[0]); std_string_arg(s[1]); std_string_arg(s[2]); std_string_ret(0); std_string_ret(1); std_string_ret(2); } uftrace-0.15.2/tests/s-struct.c000066400000000000000000000012351455365734300163420ustar00rootroot00000000000000// The sizeof(Option) is 11 in 64-bit, 7 in 32-bit. struct __attribute__((packed)) Option { char m; // 1 long n; // 8 or 4 short k; // 2 }; // empty struct struct StringRef {}; // global variables to suppress compiler optimization struct Option g_opt; struct StringRef g_sr; unsigned g_index; int g_value1; int g_value2; __attribute__((noinline)) void foo(const struct Option Opt, struct StringRef S, unsigned Index, const int Value1, const int Value2) { g_opt = Opt; g_sr = S; g_index = Index; g_value1 = Value1; g_value2 = Value2; } int main(void) { struct Option Opt = { 11, 22, 33 }; struct StringRef S; foo(Opt, S, 44, 55, 66); return 0; } uftrace-0.15.2/tests/s-taskname.c000066400000000000000000000004661455365734300166260ustar00rootroot00000000000000#define _GNU_SOURCE #include #include void task_name1(const char *name) { prctl(PR_SET_NAME, name, 0, 0, 0); } void task_name2(const char *name) { pthread_setname_np(pthread_self(), name); } int main(int argc, char *argv[]) { task_name1("foo"); task_name2("bar"); return 0; } uftrace-0.15.2/tests/s-thread-exec.c000066400000000000000000000004321455365734300172050ustar00rootroot00000000000000#include #include void *thread_func(void *arg) { char *exename = arg; execl(exename, exename, NULL); return NULL; } int main(void) { pthread_t thrd_id; pthread_create(&thrd_id, NULL, thread_func, "t-abc"); pthread_join(thrd_id, NULL); return 0; } uftrace-0.15.2/tests/s-thread-exit.c000066400000000000000000000006341455365734300172360ustar00rootroot00000000000000#include #include #define NUM_THREAD 2 pthread_t threads[NUM_THREAD]; void *thread_main(void *arg) { double result = 1.0; printf("%f\n", result); pthread_exit(NULL); return NULL; } int main(void) { int i; for (i = 0; i < NUM_THREAD; i++) pthread_create(&threads[i], NULL, &thread_main, NULL); for (i = 0; i < NUM_THREAD; i++) pthread_join(threads[i], NULL); return 0; } uftrace-0.15.2/tests/s-thread-name.c000066400000000000000000000014351455365734300172050ustar00rootroot00000000000000#include void foo(void) { int i; for (i = 0; i < 1000; i++) asm volatile("" ::: "memory"); } void *bar(void *arg) { if (arg) arg = NULL; return arg; } static void *thread_first(void *arg) { foo(); return bar(arg); } static void *thread_second(void *arg) { foo(); return bar(arg); } static void *thread_third(void *arg) { foo(); return bar(arg); } static void *thread_fourth(void *arg) { foo(); return bar(arg); } int main(void) { int i; pthread_t thrd_id[4]; pthread_create(&thrd_id[0], NULL, thread_first, NULL); pthread_create(&thrd_id[1], NULL, thread_second, NULL); pthread_create(&thrd_id[2], NULL, thread_third, NULL); pthread_create(&thrd_id[3], NULL, thread_fourth, NULL); for (i = 0; i < 4; i++) pthread_join(thrd_id[i], NULL); return 0; } uftrace-0.15.2/tests/s-thread-tsd.c000066400000000000000000000007171455365734300170610ustar00rootroot00000000000000#include #include #include static pthread_key_t key; void tsd_dtor(void *data) { free(data); } void *thread(void *arg) { pthread_setspecific(key, malloc(2)); return NULL; } int main(void) { pthread_t t; pthread_key_create(&key, tsd_dtor); pthread_setspecific(key, malloc(1)); pthread_create(&t, NULL, thread, NULL); pthread_join(t, NULL); tsd_dtor(pthread_getspecific(key)); pthread_key_delete(key); return 0; } uftrace-0.15.2/tests/s-thread.c000066400000000000000000000014611455365734300162660ustar00rootroot00000000000000/* * This is a basic test that checks uftrace can trace functions * properly within a multi-thread environment. */ #include #include #define NUM_THREAD 4 static int a(void *); static int b(void *); static int c(void *); static int a(void *arg) { return b(arg) - 1; } static int b(void *arg) { return c(arg) + 1; } static int c(void *arg) { return *(int *)arg; } static void *foo(void *arg) { return (void *)(long)a(arg); } int main(int argc, char *argv[]) { int i; int n = 10; int ret = 0; void *v; pthread_t t[NUM_THREAD]; if (argc > 1) n = atoi(argv[1]); for (i = 0; i < NUM_THREAD; i++) pthread_create(&t[i], NULL, foo, &n); for (i = 0; i < NUM_THREAD; i++) { pthread_join(t[i], &v); ret += (long)v; } if (n * NUM_THREAD != ret) return ret; return 0; } uftrace-0.15.2/tests/s-ucontext.c000066400000000000000000000011101455365734300166570ustar00rootroot00000000000000#include #include #include int foo(ucontext_t *old, ucontext_t *new) { swapcontext(old, new); } int bar(int c) { return c + getpid(); } int baz(volatile int c) { return c % 2; } #define STACKSIZE 8192 int main(int argc, char *argv[]) { int n = 10; char stack[STACKSIZE]; ucontext_t curr, new; if (argc > 1) n = atoi(argv[1]); getcontext(&new); new.uc_link = &curr; new.uc_stack.ss_sp = stack; new.uc_stack.ss_size = STACKSIZE; makecontext(&new, (void (*)(void))bar, 1, n); foo(&curr, &new); n = baz(n); return !(n < 2); } uftrace-0.15.2/tests/s-unroll.c000066400000000000000000000015041455365734300163300ustar00rootroot00000000000000#include #include #define LOOP_CNT 4 int big(int n) { char string[] = "a local string variable saved in the stack memory"; volatile unsigned x = n; int i; for (i = 0; i < LOOP_CNT; i++) { x *= 3 + i; x ^= 0xf0f0f0f0; x &= (1 << i) - 1; if (x == 0) x = (unsigned)string[i] << i; } return n; } int small(int n) { return big(n) ? n : 1; } int main(int argc, char *argv[]) { char string[] = "a very long string variable saved in the stack memory"; int n = 123456; int i; if (argc > 1) n = atoi(argv[1]); else if (argc > 2) n = strtol(argv[1], NULL, atoi(argv[2])); small(n ? n : 123456); for (i = 0; i < LOOP_CNT; i++) { static volatile unsigned x = 42; x *= 1 << i; x ^= 0xdeadbeef; x >>= i; if (x == 0) x = (unsigned)string[i] << i; } return n ? (n ^ n) : 0; } uftrace-0.15.2/tests/s-variadic.c000066400000000000000000000005351455365734300166020ustar00rootroot00000000000000#include #include int variadic(const char *fmt, ...) { va_list ap; int ret; char buf[256]; va_start(ap, fmt); ret = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); return ret; } int main(void) { variadic("print %c %s %d %ld %lu %lld %f", 'a', "hello", 100, 1234L, 5678UL, 9876543210ULL, 3.141592); return 0; } uftrace-0.15.2/tests/s-vforkexec.c000066400000000000000000000004511455365734300170110ustar00rootroot00000000000000#include #include #include #include #define TEST_PROG "t-abc" int main(int argc, char *argv[]) { int pid; pid = vfork(); if (pid < 0) return -1; if (pid == 0) { execl(TEST_PROG, TEST_PROG, NULL); return -1; } wait(NULL); return 0; } uftrace-0.15.2/tests/t001_basic.py000066400000000000000000000010231455365734300166040ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION 62.202 us [28141] | __cxa_atexit(); [28141] | main() { [28141] | a() { [28141] | b() { [28141] | c() { 0.753 us [28141] | getpid(); 1.430 us [28141] | } /* c */ 1.915 us [28141] | } /* b */ 2.405 us [28141] | } /* a */ 3.005 us [28141] | } /* main */ """) uftrace-0.15.2/tests/t002_argument.py000066400000000000000000000014201455365734300173470ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'arg', """ # DURATION TID FUNCTION [16325] | main() { [16325] | foo() { [16325] | bar() { 0.567 us [16325] | strcmp(); 1.779 us [16325] | } /* bar */ [16325] | bar() { 0.133 us [16325] | strcmp(); 0.489 us [16325] | } /* bar */ [16325] | bar() { 0.081 us [16325] | strcmp(); 0.381 us [16325] | } /* bar */ 3.515 us [16325] | } /* foo */ 0.235 us [16325] | many(); [16325] | pass() { 0.130 us [16325] | check(); 0.427 us [16325] | } /* pass */ 42.161 us [16325] | } /* main */ """) uftrace-0.15.2/tests/t003_thread.py000066400000000000000000000041001455365734300167730ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'thread', ldflags='-pthread', result=""" # DURATION TID FUNCTION [ 1429] | main() { [ 1429] | pthread_create() { 44.296 us [ 1429] | } /* pthread_create */ [ 1429] | pthread_create() { 24.726 us [ 1429] | } /* pthread_create */ [ 1429] | pthread_create() { 21.086 us [ 1429] | } /* pthread_create */ [ 1429] | pthread_create() { 20.720 us [ 1429] | } /* pthread_create */ [ 1429] | pthread_join() { [ 1430] | foo() { [ 1430] | a() { [ 1430] | b() { [ 1430] | c() { 2.880 us [ 1430] | } /* c */ 3.793 us [ 1430] | } /* b */ 4.620 us [ 1430] | } /* a */ 96.966 us [ 1430] | } /* foo */ 340.217 us [ 1429] | } /* pthread_join */ [ 1429] | pthread_join() { [ 1431] | foo() { [ 1431] | a() { [ 1431] | b() { [ 1431] | c() { 0.444 us [ 1431] | } /* c */ 1.333 us [ 1431] | } /* b */ 2.186 us [ 1431] | } /* a */ 63.205 us [ 1431] | } /* foo */ 100.046 us [ 1429] | } /* pthread_join */ [ 1429] | pthread_join() { [ 1432] | foo() { [ 1432] | a() { [ 1432] | b() { [ 1432] | c() { 0.420 us [ 1432] | } /* c */ 1.210 us [ 1432] | } /* b */ 2.134 us [ 1432] | } /* a */ 169.879 us [ 1432] | } /* foo */ 27.470 us [ 1429] | } /* pthread_join */ [ 1429] | pthread_join() { [ 1433] | foo() { [ 1433] | a() { [ 1433] | b() { [ 1433] | c() { 0.577 us [ 1433] | } /* c */ 1.717 us [ 1433] | } /* b */ 2.860 us [ 1433] | } /* a */ 121.139 us [ 1433] | } /* foo */ 0.390 us [ 1429] | } /* pthread_join */ 658.759 us [ 1429] | } /* main */ """) def setup(self): self.option = '--no-merge' uftrace-0.15.2/tests/t004_filter_F.py000066400000000000000000000007371455365734300172730ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [28141] | a() { [28141] | b() { [28141] | c() { 0.753 us [28141] | getpid(); 1.430 us [28141] | } /* c */ 1.915 us [28141] | } /* b */ 2.405 us [28141] | } /* a */ """, sort='simple') def setup(self): self.option = '-F a' uftrace-0.15.2/tests/t005_filter_N.py000066400000000000000000000006611455365734300173000ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION 62.202 us [28141] | __cxa_atexit(); [28141] | main() { [28141] | a() { 1.915 us [28141] | b(); 2.405 us [28141] | } /* a */ 3.005 us [28141] | } /* main */ """) def setup(self): self.option = '-N c' uftrace-0.15.2/tests/t006_filter_FN.py000066400000000000000000000005271455365734300174100ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [28141] | a() { 1.915 us [28141] | b(); 2.405 us [28141] | } /* a */ """, sort='simple') def setup(self): self.option = '-F a -N c' uftrace-0.15.2/tests/t007_library.py000066400000000000000000000013241455365734300172010ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'lib', """ # DURATION TID FUNCTION [17455] | lib_a() { [17455] | lib_b() { 61.911 us [17455] | lib_c(); 217.279 us [17455] | } /* lib_b */ 566.261 us [17455] | } /* lib_a */ """, sort='simple') def build(self, name, cflags='', ldflags=''): if TestBase.build_libabc(self, cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-libmain.c', ['libabc_test_lib.so']) def setup(self): self.option = '--force --no-libcall' uftrace-0.15.2/tests/t008_daemon.py000066400000000000000000000013421455365734300170010ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'daemon', """ # DURATION TID FUNCTION 71.636 us [28239] | __cxa_atexit(); [28239] | main() { [28239] | daemon() { 393.995 us [28240] | } /* daemon */ [28240] | a() { [28240] | b() { [28240] | c() { 19.127 us [28240] | getpid(); 20.230 us [28240] | } /* c */ 20.870 us [28240] | } /* b */ 21.633 us [28240] | } /* a */ 427.945 us [28240] | } /* main */ ftrace stopped tracing with remaining functions =============================================== task: 28239 [1] daemon [0] main """) uftrace-0.15.2/tests/t009_fork.py000066400000000000000000000022351455365734300165020ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fork', """ # DURATION TID FUNCTION [26125] | __cxa_atexit() { 68.297 us [26125] | } /* __cxa_atexit */ [26125] | main() { [26125] | fork() { 101.456 us [26125] | } /* fork */ [26125] | wait() { 298.356 us [26126] | } /* fork */ [26126] | a() { [26126] | b() { [26126] | c() { [26126] | getpid() { 1.206 us [26126] | } /* getpid */ 1.925 us [26126] | } /* c */ 2.531 us [26126] | } /* b */ 3.151 us [26126] | } /* a */ 333.039 us [26126] | } /* main */ 19.376 us [26125] | } /* wait */ [26125] | a() { [26125] | b() { [26125] | c() { [26125] | getpid() { 5.031 us [26125] | } /* getpid */ 5.934 us [26125] | } /* c */ 6.520 us [26125] | } /* b */ 7.140 us [26125] | } /* a */ 420.059 us [26125] | } /* main */ """) def setup(self): self.option = '--no-merge' uftrace-0.15.2/tests/t010_forkexec.py000066400000000000000000000017401455365734300173370ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'forkexec', """ # DURATION TID FUNCTION [ 9874] | main() { 142.145 us [ 9874] | fork(); [ 9874] | waitpid() { 473.298 us [ 9875] | } /* fork */ [ 9875] | execl() { [ 9875] | main() { [ 9875] | a() { [ 9875] | b() { [ 9875] | c() { 0.976 us [ 9875] | getpid(); 1.992 us [ 9875] | } /* c */ 2.828 us [ 9875] | } /* b */ 3.658 us [ 9875] | } /* a */ 7.713 us [ 9875] | } /* main */ 2.515 ms [ 9874] | } /* waitpid */ 2.708 ms [ 9874] | } /* main */ """) def build(self, name, cflags='', ldflags=''): ret = TestBase.build(self, 'abc', cflags, ldflags) ret += TestBase.build(self, self.name, cflags, ldflags) return ret def setup(self): self.option = '-F main' uftrace-0.15.2/tests/t011_vforkexec.py000066400000000000000000000020031455365734300175170ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'vforkexec', """ # DURATION TID FUNCTION [ 3122] | main() { [ 3122] | vfork() { [ 3124] | } /* vfork */ [ 3124] | execl() { 1.248 ms [ 3122] | } /* vfork */ [ 3122] | wait() { [ 3124] | main() { [ 3124] | a() { [ 3124] | b() { [ 3124] | c() { 1.655 us [ 3124] | getpid(); 3.861 us [ 3124] | } /* c */ 4.393 us [ 3124] | } /* b */ 4.901 us [ 3124] | } /* a */ 7.511 us [ 3124] | } /* main */ 2.706 ms [ 3122] | } /* wait */ 3.959 ms [ 3122] | } /* main */ """) def build(self, name, cflags='', ldflags=''): ret = TestBase.build(self, 'abc', cflags, ldflags) ret += TestBase.build(self, self.name, cflags, ldflags) return ret def setup(self): self.option = '-F main' uftrace-0.15.2/tests/t012_demangle.py000066400000000000000000000020001455365734300172750ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'demangle', lang='C++', result=""" # DURATION TID FUNCTION [31433] | ABC::foo() { [31433] | __static_initialization_and_destruction_0() { 96.867 us [31433] | std::ios_base::Init::Init(); 1.403 us [31433] | __cxa_atexit(); 101.554 us [31433] | } /* __static_initialization_and_destruction_0 */ 171.419 us [31433] | } /* ABC::foo */ [31433] | main() { 2.540 us [31433] | operator new(); 0.146 us [31433] | ABC::ABC(); [31433] | ABC::foo() { [31433] | ABC::bar() { 0.157 us [31433] | ABC::baz(); 0.714 us [31433] | } /* ABC::bar */ 1.323 us [31433] | } /* ABC::foo */ 5.623 us [31433] | } /* main */ 9.223 us [31433] | std::ios_base::Init::~Init(); """) def fixup(self, cflags, result): return result.replace(" std::ios_base::Init::~Init();\n", '') uftrace-0.15.2/tests/t013_signal.py000066400000000000000000000011031455365734300170020ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'signal', """ # DURATION TID FUNCTION 78.933 us [28901] | __cxa_atexit(); [28901] | main() { 0.996 us [28901] | foo(); 1.930 us [28901] | signal(); [28901] | raise() { [28901] | sighandler() { 0.236 us [28901] | bar(); 0.236 us [28901] | } /* sighandler */ 13.464 us [28901] | } /* raise */ 0.102 us [28901] | foo(); 17.113 us [28901] | } /* main */ """) uftrace-0.15.2/tests/t014_ucontext.py000066400000000000000000000012401455365734300174010ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'ucontext', """ # DURATION TID FUNCTION 79.593 us [28383] | __cxa_atexit(); [28383] | main() { 1.606 us [28383] | getcontext(); 1.584 us [28383] | makecontext(); [28383] | foo() { [28383] | swapcontext() { [28383] | bar() { 2.384 us [28383] | getpid(); 5.700 us [28383] | } /* bar */ 8.716 us [28383] | } /* swapcontext */ 9.489 us [28383] | } /* foo */ 0.130 us [28383] | baz(); 15.586 us [28383] | } /* main */ """) uftrace-0.15.2/tests/t015_longjmp.py000066400000000000000000000007231455365734300172040ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'longjmp', """ # DURATION TID FUNCTION 63.615 us [29065] | __cxa_atexit(); [29065] | main() { 0.690 us [29065] | _setjmp(); [29065] | foo() { [29065] | longjmp() { 0.907 us [29065] | } /* _setjmp */ 0.105 us [29065] | bar(); 36.125 us [29065] | } /* main */ """) uftrace-0.15.2/tests/t016_alloca.py000066400000000000000000000012561455365734300167740ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'alloca', """ # DURATION TID FUNCTION' 75.736 us [ 6681] | __cxa_atexit(); [ 6681] | main() { [ 6681] | foo() { 2.153 us [ 6681] | strncpy(); 3.073 us [ 6681] | } /* foo */ [ 6681] | bar() { [ 6681] | foo() { 0.593 us [ 6681] | strncpy(); 1.317 us [ 6681] | } /* foo */ [ 6681] | foo() { 0.700 us [ 6681] | strncpy(); 1.336 us [ 6681] | } /* foo */ 3.723 us [ 6681] | } /* bar */ 8.063 us [ 6681] | } /* main */ """) uftrace-0.15.2/tests/t017_no_libcall.py000066400000000000000000000007311455365734300176350ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [28141] | main() { [28141] | a() { [28141] | b() { 1.430 us [28141] | c(); 1.915 us [28141] | } /* b */ 2.405 us [28141] | } /* a */ 72.252 us [28141] | } /* main */ """) def setup(self): self.option = '--no-libcall' uftrace-0.15.2/tests/t018_filter_regex.py000066400000000000000000000012711455365734300202170ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'allocfree', """ # DURATION TID FUNCTION [11583] | alloc1() { [11583] | alloc2() { [11583] | alloc3() { [11583] | alloc4() { [11583] | alloc5() { 1.873 us [11583] | malloc(); 2.909 us [11583] | } /* alloc5 */ 3.652 us [11583] | } /* alloc4 */ 4.239 us [11583] | } /* alloc3 */ 5.016 us [11583] | } /* alloc2 */ 104.119 us [11583] | } /* alloc1 */ """, sort='simple') def setup(self): self.option = '-F "^alloc.*$"' uftrace-0.15.2/tests/t019_full_depth.py000066400000000000000000000010121455365734300176600ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'allocfree', """ # DURATION TID FUNCTION [30388] | main() { [30388] | alloc1() { 7.794 us [30388] | alloc2(); 9.137 us [30388] | } /* alloc1 */ [30388] | free1() { 3.621 us [30388] | free2(); 4.499 us [30388] | } /* free1 */ 120.407 us [30388] | } /* main */ """) def setup(self): self.option = '-D3' uftrace-0.15.2/tests/t020_filter_depth.py000066400000000000000000000007021455365734300202000ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'allocfree', """ # DURATION TID FUNCTION [20175] | alloc1() { [20175] | alloc3() { 5.792 us [20175] | alloc5(); 7.914 us [20175] | } /* alloc3 */ 114.958 us [20175] | } /* alloc1 */ """, sort='simple') def setup(self): self.option = '-D1 -F "alloc[135]"' uftrace-0.15.2/tests/t021_filter_plt.py000066400000000000000000000006101455365734300176720ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'getids', """ # DURATION TID FUNCTION 1.811 us [18130] | getpid(); 1.776 us [18130] | getsid(); 1.289 us [18130] | getuid(); 1.043 us [18130] | getgid(); """, sort='simple') def setup(self): self.option = '-F "get.?id@plt"' uftrace-0.15.2/tests/t022_filter_kernel.py000066400000000000000000000025741455365734300203670ustar00rootroot00000000000000#!/usr/bin/env python3 import os from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'getids', serial=True, result=""" # DURATION TID FUNCTION [20769] | main() { 0.925 us [20769] | getpid(); 2.089 us [20769] | getppid(); 1.334 us [20769] | getpgid(); 0.881 us [20769] | getsid(); 1.234 us [20769] | getuid(); [20769] | geteuid() { 0.056 us [20769] | sys_geteuid(); 1.178 us [20769] | } /* geteuid */ 0.994 us [20769] | getgid(); [20769] | getegid() { 0.054 us [20769] | sys_getegid(); 0.912 us [20769] | } /* getegid */ 81.933 us [20769] | } /* main */ """) def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def setup(self): self.option = "-k -F sys_gete.*@kernel" def fixup(self, cflags, result): uname = os.uname() # Linux v4.17 (x86_64) changed syscall routines major, minor, release = uname[2].split('.') if uname[0] == 'Linux' and uname[4] == 'x86_64' and \ int(major) >= 5 or (int(major) == 4 and int(minor) >= 17): result = result.replace('sys_gete', '__x64_sys_gete') return result uftrace-0.15.2/tests/t023_replay_filter.py000066400000000000000000000013551455365734300204000ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'allocfree', """ # DURATION TID FUNCTION [ 4629] | alloc3() { 4.671 us [ 4629] | alloc4(); 4.999 us [ 4629] | } /* alloc3 */ [ 4629] | free1() { [ 4629] | free2() { [ 4629] | free5() { 1.057 us [ 4629] | free(); 1.563 us [ 4629] | } /* free5 */ 2.323 us [ 4629] | } /* free2 */ 2.580 us [ 4629] | } /* free1 */ """, sort='simple') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-F alloc3 -D2 -F "free[15]"' uftrace-0.15.2/tests/t024_report_basic.py000066400000000000000000000013501455365734300202070ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sort', """ Total time Self time Calls Function ========== ========== ========== ==================================== 1.152 ms 71.683 us 1 main 1.080 ms 1.813 us 1 bar 1.078 ms 1.078 ms 1 usleep 70.176 us 70.176 us 1 __monstartup 37.525 us 1.137 us 2 foo 36.388 us 36.388 us 6 loop 1.200 us 1.200 us 1 __cxa_atexit """, sort='report') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'report' uftrace-0.15.2/tests/t025_report_s_call.py000066400000000000000000000013751455365734300203730ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sort', """ Total time Self time Calls Function ========== ========== ========== ==================== 187.817 us 187.817 us 6 loop 189.598 us 1.781 us 2 foo 0.657 us 0.657 us 1 __cxa_atexit 1.323 us 1.323 us 1 __monstartup 10.265 ms 157.616 us 1 bar 10.921 ms 466.298 us 1 main 10.107 ms 10.107 ms 1 usleep """, sort='report') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'report' self.option = '-s call,func' uftrace-0.15.2/tests/t026_filter_trigger.py000066400000000000000000000017031455365734300205470ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'allocfree', """ # DURATION TID FUNCTION [ 767] | main() { [ 767] | alloc1() { [ 767] | alloc2() { 4.223 us [ 767] | alloc3(); 4.848 us [ 767] | } /* alloc2 */ 5.417 us [ 767] | } /* alloc1 */ [ 767] | free1() { [ 767] | free2() { [ 767] | free3() { [ 767] | free4() { [ 767] | free5() { 1.104 us [ 767] | free(); 1.974 us [ 767] | } /* free5 */ 2.289 us [ 767] | } /* free4 */ 2.577 us [ 767] | } /* free3 */ 2.857 us [ 767] | } /* free2 */ 3.188 us [ 767] | } /* free1 */ 66.699 us [ 767] | } /* main */ """) def setup(self): self.option = '-F "main" -F "alloc3@depth=1"' uftrace-0.15.2/tests/t027_replay_filter_d.py000066400000000000000000000020661455365734300207070ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'allocfree', """ # DURATION TID FUNCTION [ 4629] | main() { [ 4629] | alloc1() { [ 4629] | alloc2() { 4.999 us [ 4629] | alloc3(); 5.360 us [ 4629] | } /* alloc2 */ 5.811 us [ 4629] | } /* alloc1 */ [ 4629] | free1() { [ 4629] | free2() { [ 4629] | free3() { [ 4629] | free4() { [ 4629] | free5() { 1.057 us [ 4629] | free(); 1.563 us [ 4629] | } /* free5 */ 1.817 us [ 4629] | } /* free4 */ 2.072 us [ 4629] | } /* free3 */ 2.323 us [ 4629] | } /* free2 */ 2.580 us [ 4629] | } /* free1 */ 78.021 us [ 4629] | } /* main */ """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-F "main" -F "alloc3@depth=1"' uftrace-0.15.2/tests/t028_replay_backtrace.py000066400000000000000000000013341455365734300210340ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'allocfree', """ # DURATION TID FUNCTION backtrace [ 4629] | /* [ 0] main */ backtrace [ 4629] | /* [ 1] alloc1 */ backtrace [ 4629] | /* [ 2] alloc2 */ backtrace [ 4629] | /* [ 3] alloc3 */ [ 4629] | alloc4() { [ 4629] | alloc5() { 2.020 us [ 4629] | malloc(); 4.334 us [ 4629] | } /* alloc5 */ 4.671 us [ 4629] | } /* alloc4 */ """, sort='simple') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-T "alloc4@filter,backtrace"' uftrace-0.15.2/tests/t029_trigger_only.py000066400000000000000000000024021455365734300202430ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'allocfree', """ # DURATION TID FUNCTION 101.601 us [31924] | __monstartup(); 2.047 us [31924] | __cxa_atexit(); [31924] | main() { [31924] | alloc1() { [31924] | alloc2() { 3.010 us [31924] | alloc3(); 4.068 us [31924] | } /* alloc2 */ 4.611 us [31924] | } /* alloc1 */ [31924] | free1() { [31924] | free2() { [31924] | free3() { [31924] | free4() { [31924] | free5() { backtrace [31924] | /* [ 0] main */ backtrace [31924] | /* [ 1] free1 */ backtrace [31924] | /* [ 2] free2 */ backtrace [31924] | /* [ 3] free3 */ backtrace [31924] | /* [ 4] free4 */ backtrace [31924] | /* [ 5] free5 */ 1.894 us [31924] | free(); 2.921 us [31924] | } /* free5 */ 3.504 us [31924] | } /* free4 */ 4.059 us [31924] | } /* free3 */ 4.588 us [31924] | } /* free2 */ 5.177 us [31924] | } /* free1 */ 11.175 us [31924] | } /* main */ """) def setup(self): self.option = '-T "alloc3@depth=1" -T "free@backtrace"' uftrace-0.15.2/tests/t030_replay_trigger.py000066400000000000000000000013671455365734300205570ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'allocfree', """ # DURATION TID FUNCTION [12561] | main() { [12561] | alloc1() { 4.499 us [12561] | alloc2(); 4.998 us [12561] | } /* alloc1 */ [12561] | free1() { backtrace [12561] | /* [ 0] main */ backtrace [12561] | /* [ 1] free1 */ 3.905 us [12561] | free2(); 4.392 us [12561] | } /* free1 */ 10.380 us [12561] | } /* main */ """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-T "alloc1@depth=2" -T "free2@depth=1,backtrace"' uftrace-0.15.2/tests/t031_filter_demangle1.py000066400000000000000000000013721455365734300207370ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION [14470] | ns::ns1::foo::bar() { [14470] | ns::ns1::foo::bar1() { [14470] | ns::ns1::foo::bar2() { [14470] | ns::ns1::foo::bar3() { 4.039 us [14470] | malloc(); 5.471 us [14470] | } /* ns::ns1::foo::bar3 */ 6.145 us [14470] | } /* ns::ns1::foo::bar2 */ 6.858 us [14470] | } /* ns::ns1::foo::bar1 */ 2.207 us [14470] | free(); 100.290 us [14470] | } /* ns::ns1::foo::bar */ """, sort='simple') def setup(self): self.option = '-F "ns::ns1::foo::bar"' uftrace-0.15.2/tests/t032_filter_demangle2.py000066400000000000000000000006721455365734300207430ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION 128.411 us [30174] | operator new(); 4.580 us [30174] | operator delete(); 10.248 us [30174] | operator new(); 0.317 us [30174] | operator delete(); """, sort='simple') def setup(self): self.option = '-F "^operator"' uftrace-0.15.2/tests/t033_filter_demangle3.py000066400000000000000000000020141455365734300207350ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION [ 3357] | main() { 2.874 us [ 3357] | operator new(); 3.115 us [ 3357] | operator delete(); 0.456 us [ 3357] | operator new(); 0.386 us [ 3357] | ns::ns2::foo::foo(); [ 3357] | ns::ns2::foo::bar() { [ 3357] | ns::ns2::foo::bar1() { [ 3357] | ns::ns2::foo::bar2() { [ 3357] | ns::ns2::foo::bar3() { 0.346 us [ 3357] | malloc(); 0.732 us [ 3357] | } /* ns::ns2::foo::bar3 */ 0.847 us [ 3357] | } /* ns::ns2::foo::bar2 */ 1.339 us [ 3357] | } /* ns::ns2::foo::bar1 */ 0.411 us [ 3357] | free(); 2.072 us [ 3357] | } /* ns::ns2::foo::bar */ 0.311 us [ 3357] | operator delete(); 105.160 us [ 3357] | } /* main */ """) def setup(self): self.option = '-N ".*ns1::.*"' uftrace-0.15.2/tests/t034_filter_demangle4.py000066400000000000000000000007721455365734300207500ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION 61.419 us [ 1817] | ns::ns1::foo::foo(); [ 1817] | ns::ns1::foo::bar() { 2.585 us [ 1817] | ns::ns1::foo::bar1(); 1.303 us [ 1817] | free(); 4.863 us [ 1817] | } /* ns::ns1::foo::bar */ """, sort='simple') def setup(self): self.option = '-F "ns1::.*" -N "bar2$"' uftrace-0.15.2/tests/t035_filter_demangle5.py000066400000000000000000000021151455365734300207430ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION 66.323 us [ 1845] | ns::ns1::foo::foo(); [ 1845] | ns::ns1::foo::bar() { [ 1845] | ns::ns1::foo::bar1() { [ 1845] | ns::ns1::foo::bar2() { [ 1845] | ns::ns1::foo::bar3() { 1.759 us [ 1845] | malloc(); 2.656 us [ 1845] | } /* ns::ns1::foo::bar3 */ 2.996 us [ 1845] | } /* ns::ns1::foo::bar2 */ 3.346 us [ 1845] | } /* ns::ns1::foo::bar1 */ 1.367 us [ 1845] | free(); 5.499 us [ 1845] | } /* ns::ns1::foo::bar */ [ 1845] | ns::ns2::foo::bar2() { [ 1845] | ns::ns2::foo::bar3() { 0.450 us [ 1845] | malloc(); 0.930 us [ 1845] | } /* ns::ns2::foo::bar3 */ 1.393 us [ 1845] | } /* ns::ns2::foo::bar2 */ """, sort='simple') # test whether filter option preserves the ordering def setup(self): self.option = '-F "ns1::.*" -N "bar2$" -F "bar2$"' uftrace-0.15.2/tests/t036_replay_filter_N.py000066400000000000000000000020411455365734300206520ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION [ 7102] | main() { 2.697 us [ 7102] | operator new(); 0.842 us [ 7102] | ns::ns1::foo::foo(); [ 7102] | ns::ns1::foo::bar() { [ 7102] | ns::ns1::foo::bar1() { 1.926 us [ 7102] | ns::ns1::foo::bar2(); 2.169 us [ 7102] | } /* ns::ns1::foo::bar1 */ 1.215 us [ 7102] | free(); 3.897 us [ 7102] | } /* ns::ns1::foo::bar */ 1.865 us [ 7102] | operator delete(); 0.274 us [ 7102] | operator new(); 0.115 us [ 7102] | ns::ns2::foo::foo(); 1.566 us [ 7102] | ns::ns2::foo::bar(); 0.168 us [ 7102] | operator delete(); 78.921 us [ 7102] | } /* main */ """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-N "bar3$" -Tns::ns2::foo::bar@depth=1' uftrace-0.15.2/tests/t037_trace_onoff.py000066400000000000000000000020101455365734300200160ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION [30192] | main() { 3.210 us [30192] | operator new(); 1.435 us [30192] | ns::ns1::foo::foo(); [30192] | ns::ns1::foo::bar() { [30192] | ns::ns1::foo::bar1() { [30192] | ns::ns2::foo::bar2() { [30192] | ns::ns2::foo::bar3() { 0.988 us [30192] | malloc(); 1.735 us [30192] | } /* ns::ns2::foo::bar3 */ 2.342 us [30192] | } /* ns::ns2::foo::bar2 */ 14.366 us [30192] | } /* ns::ns2::foo::bar1 */ 0.472 us [30192] | free(); 15.807 us [30192] | } /* ns::ns2::foo::bar */ 0.316 us [30192] | operator delete(); 107.604 us [30192] | } /* main */ """) def setup(self): self.option = '-T "ns::ns1::foo::bar2@trace_off" ' self.option += '-T "ns::ns2::foo::bar2@trace-on"' uftrace-0.15.2/tests/t038_trace_disable.py000066400000000000000000000015611455365734300203250ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION [12683] | ns::ns2::foo::bar() { [12683] | ns::ns2::foo::bar1() { [12683] | ns::ns2::foo::bar2() { [12683] | ns::ns2::foo::bar3() { 1.067 us [12683] | malloc(); 2.390 us [12683] | } /* ns::ns2::foo::bar3 */ 3.197 us [12683] | } /* ns::ns2::foo::bar2 */ 4.177 us [12683] | } /* ns::ns2::foo::bar1 */ 0.695 us [12683] | free(); 105.025 us [12683] | } /* ns::ns2::foo::bar */ 0.602 us [12683] | operator delete(); [12683] | } /* main */ """, sort='simple') def setup(self): self.option = '--trace=off -T "ns::ns2::foo::bar@trace_on"' uftrace-0.15.2/tests/t039_trace_onoff_F.py000066400000000000000000000013311455365734300202720ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase # Unlike filters (-F), The trace-on/off trigger preserves the depth. class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION [27770] | ns::ns1::foo::bar3() { 2.725 us [27770] | malloc(); 78.805 us [27770] | } /* ns::ns1::foo::bar3 */ [27770] | } /* ns::ns1::foo::bar2 */ [27770] | } /* ns::ns1::foo::bar1 */ 1.791 us [27770] | free(); [27770] | } /* ns::ns1::foo::bar */ """, sort='simple') def setup(self): self.option = '--trace=off -F "ns::ns1::foo::bar" -T ".*foo::bar3@trace_on"' uftrace-0.15.2/tests/t040_replay_onoff.py000066400000000000000000000017651455365734300202260ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION 4.843 us [29826] | operator new(); 1.846 us [29826] | ns::ns1::foo::foo(); [29826] | ns::ns1::foo::bar() { [29826] | ns::ns1::foo::bar1() { [29826] | ns::ns1::foo::bar2() { [29826] | ns::ns1::foo::bar3() { 0.597 us [29826] | operator new(); 0.317 us [29826] | ns::ns2::foo::foo(); [29826] | ns::ns2::foo::bar() { [29826] | ns::ns2::foo::bar1() { [29826] | ns::ns2::foo::bar2() { [29826] | ns::ns2::foo::bar3() { """, sort='simple') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = "replay" self.option = "--trace=off -T 'operator new@trace_on' -T 'malloc@trace_off'" uftrace-0.15.2/tests/t041_replay_onoff_N.py000066400000000000000000000017721455365734300205020ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase # in this case, malloc() in ns2 was already filtered out, # so 'trace-off' trigger cannot be fired and shows delete() and main exit. class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION 4.843 us [29826] | operator new(); 1.846 us [29826] | ns::ns1::foo::foo(); [29826] | ns::ns1::foo::bar() { [29826] | ns::ns1::foo::bar1() { [29826] | ns::ns1::foo::bar2() { [29826] | ns::ns1::foo::bar3() { 0.597 us [29826] | operator new(); 0.410 us [29826] | operator delete(); 143.705 us [29826] | } /* main */ """, sort='simple') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = "--trace=off -N 'ns2.*' -T 'operator new@trace-on' " self.option += "-T 'malloc@traceoff'" uftrace-0.15.2/tests/t042_live_disable.py000066400000000000000000000015461455365734300201640ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION 53.511 us [ 6624] | ns::ns1::foo::foo(); [ 6624] | ns::ns1::foo::bar2() { [ 6624] | ns::ns1::foo::bar3() { 1.607 us [ 6624] | malloc(); 2.520 us [ 6624] | } /* ns::ns1::foo::bar3 */ 2.982 us [ 6624] | } /* ns::ns1::foo::bar2 */ 0.174 us [ 6624] | ns::ns2::foo::foo(); [ 6624] | ns::ns2::foo::bar2() { [ 6624] | ns::ns2::foo::bar3() { 0.365 us [ 6624] | malloc(); 0.834 us [ 6624] | } /* ns::ns2::foo::bar3 */ 1.200 us [ 6624] | } /* ns::ns2::foo::bar2 */ """, sort='simple') def setup(self): self.option = '--trace=off -F ".*foo::foo" -T .foo::foo@trace_on -F .bar2' uftrace-0.15.2/tests/t043_full_demangle.py000066400000000000000000000025041455365734300203340ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION [22757] | main() { 2.554 us [22757] | operator new(unsigned long); 1.040 us [22757] | ns::ns1::foo::foo(int); [22757] | ns::ns1::foo::bar() { [22757] | ns::ns1::foo::bar1() { [22757] | ns::ns1::foo::bar2() { [22757] | ns::ns1::foo::bar3() { 1.360 us [22757] | malloc(); 1.903 us [22757] | } /* ns::ns1::foo::bar3() */ 2.276 us [22757] | } /* ns::ns1::foo::bar2() */ 2.629 us [22757] | } /* ns::ns1::foo::bar1() */ 1.266 us [22757] | free(); 4.629 us [22757] | } /* ns::ns1::foo::bar() */ 1.927 us [22757] | operator delete(void*); 0.283 us [22757] | operator new(unsigned long); 0.223 us [22757] | operator delete(void*); 76.629 us [22757] | } /* main */ """) def setup(self): self.option = '--demangle=full -N "ns2.*"' def fixup(self, cflags, result): if TestBase.is_32bit(self): return result.replace('unsigned long', 'unsigned int') return result.replace('delete(void*);', 'delete(void*, unsigned long);') uftrace-0.15.2/tests/t044_report_avg_total.py000066400000000000000000000030041455365734300211060ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sort', """ Total avg Total min Total max Function ========== ========== ========== ==================== 11.378 ms 11.378 ms 11.378 ms main 10.537 ms 10.537 ms 10.537 ms bar 10.288 ms 10.288 ms 10.288 ms usleep 120.947 us 120.605 us 121.290 us foo 39.967 us 39.801 us 40.275 us loop 0.701 us 0.701 us 0.701 us __monstartup 0.270 us 0.270 us 0.270 us __cxa_atexit """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'report' self.option = '--avg-total' def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.strip() == '': continue line = ln.split() if line[1] == 'avg': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] [6] # avg_total unit min_total unit max_total unit function if line[-1].startswith('__'): continue result.append(line[-1]) return '\n'.join(result) uftrace-0.15.2/tests/t045_report_avg_self.py000066400000000000000000000027751455365734300207330ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sort', """ Self avg Self min Self max Function ========== ========== ========== ==================== 10.288 ms 10.288 ms 10.288 ms usleep 598.518 us 598.518 us 598.518 us main 249.854 us 249.854 us 249.854 us bar 39.967 us 39.801 us 40.275 us loop 1.044 us 0.884 us 1.205 us foo 0.701 us 0.701 us 0.701 us __monstartup 0.270 us 0.270 us 0.270 us __cxa_atexit """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'report' self.option = '--avg-self' def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.strip() == '': continue line = ln.split() if line[1] == 'avg': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] [6] # avg_self unit min_self unit max_self unit function if line[-1].startswith('__'): continue result.append(line[-1]) return '\n'.join(result) uftrace-0.15.2/tests/t046_report_task.py000066400000000000000000000030301455365734300200710ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'thread-name', """ Total time Self time Num funcs TID Task name ========== ========== ========== ====== ================ 772.936 us 136.238 us 9 2569 t-thread-name 178.218 us 101.132 us 3 2571 t-thread-name 174.537 us 83.934 us 3 2573 t-thread-name 172.378 us 90.137 us 3 2574 t-thread-name 63.568 us 63.568 us 3 2572 t-thread-name """, ldflags='-pthread') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'report' self.option = '--task' def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] [6] # total_time unit self_time unit num_funcs tid task_name if line[4].startswith('__'): continue result.append(line[-1]) return '\n'.join(result) uftrace-0.15.2/tests/t047_signal2.py000066400000000000000000000014521455365734300171020ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'signal2', """ # DURATION TID FUNCTION 3.966 us [24050] | __monstartup(); 0.342 us [24050] | __cxa_atexit(); [24050] | main() { 0.483 us [24050] | signal(); 1.476 us [24050] | setitimer(); [24050] | foo() { [24050] | bar() { 0.401 us [24050] | sighandler(); 4.074 ms [24050] | } /* bar */ [24050] | bar() { 0.086 us [24050] | sighandler(); 3.330 ms [24050] | } /* bar */ [24050] | bar() { 0.099 us [24050] | sighandler(); 3.331 ms [24050] | } /* bar */ 10.736 ms [24050] | } /* foo */ 10.738 ms [24050] | } /* main */ """) uftrace-0.15.2/tests/t048_malloc_impl.py000066400000000000000000000005641455365734300200370ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'malloc', """ # DURATION TID FUNCTION [16726] | main() { 0.426 us [16726] | malloc(); 0.397 us [16726] | free(); 3.074 us [16726] | } /* main */ """) def setup(self): self.option = '-F main' uftrace-0.15.2/tests/t049_column_view.py000066400000000000000000000053401455365734300200740ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'thread', ldflags='-pthread', result=""" # DURATION TID FUNCTION [15156] | main() { [15156] | pthread_create() { 47.998 us [15156] | } /* pthread_create */ [15156] | pthread_create() { 49.997 us [15156] | } /* pthread_create */ [15156] | pthread_create() { 86.359 us [15156] | } /* pthread_create */ [15156] | pthread_create() { 135.596 us [15156] | } /* pthread_create */ [15156] | pthread_join() { [15158] | foo() { [15158] | a() { [15158] | b() { [15158] | c() { 2.563 us [15158] | } /* c */ 2.977 us [15158] | } /* b */ 3.288 us [15158] | } /* a */ 4.093 us [15158] | } /* foo */ [15159] | foo() { [15159] | a() { [15159] | b() { [15159] | c() { 0.256 us [15159] | } /* c */ 0.580 us [15159] | } /* b */ 0.938 us [15159] | } /* a */ 1.540 us [15159] | } /* foo */ 195.074 us [15156] | } /* pthread_join */ [15156] | pthread_join() { 19.243 us [15156] | } /* pthread_join */ [15156] | pthread_join() { [15160] | foo() { [15160] | a() { [15160] | b() { [15160] | c() { 0.226 us [15160] | } /* c */ 0.587 us [15160] | } /* b */ 0.948 us [15160] | } /* a */ 1.429 us [15160] | } /* foo */ 93.036 us [15156] | } /* pthread_join */ [15156] | pthread_join() { [15161] | foo() { [15161] | a() { [15161] | b() { [15161] | c() { 0.196 us [15161] | } /* c */ 0.587 us [15161] | } /* b */ 0.983 us [15161] | } /* a */ 1.796 us [15161] | } /* foo */ 1.358 ms [15156] | } /* pthread_join */ 1.995 ms [15156] | } /* main */ """) def setup(self): self.option = '--column-view --column-offset=4 --no-merge' uftrace-0.15.2/tests/t050_no_pltbind.py000066400000000000000000000037041455365734300176670ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'racycount', ldflags='-pthread', result=""" # DURATION TID FUNCTION [22829] | __monstartup() { 17.753 us [22829] | } /* __monstartup */ [22829] | __cxa_atexit() { 8.038 us [22829] | } /* __cxa_atexit */ [22829] | main() { [22829] | pthread_barrier_init() { 6.095 us [22829] | } /* pthread_barrier_init */ [22829] | pthread_create() { 81.734 us [22829] | } /* pthread_create */ [22829] | pthread_create() { 85.171 us [22829] | } /* pthread_create */ [22829] | racy_count() { [22829] | pthread_barrier_wait() { [22832] | thread_fn() { [22832] | racy_count() { [22831] | thread_fn() { [22832] | pthread_barrier_wait() { [22831] | racy_count() { [22831] | pthread_barrier_wait() { 21.614 us [22831] | } /* pthread_barrier_wait */ 246.706 us [22829] | } /* pthread_barrier_wait */ 78.105 us [22832] | } /* pthread_barrier_wait */ 300.416 us [22831] | } /* racy_count */ 314.149 us [22831] | } /* thread_fn */ 567.337 us [22829] | } /* racy_count */ [22829] | pthread_join() { 370.700 us [22832] | } /* racy_count */ 383.658 us [22832] | } /* thread_fn */ 334.627 us [22829] | } /* pthread_join */ [22829] | pthread_join() { 5.735 us [22829] | } /* pthread_join */ 1.122 ms [22829] | } /* main */ """) def setup(self): self.option = '--no-pltbind --column-view --no-merge' uftrace-0.15.2/tests/t051_return.py000066400000000000000000000007231455365734300170550ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'return', result=""" # DURATION TID FUNCTION [12703] | main() { [12703] | return_large() { 1.440 us [12703] | memset(); 2.533 us [12703] | } /* return_large */ 0.153 us [12703] | return_small(); 0.157 us [12703] | return_long_double(); 4.097 us [12703] | } /* main */ """) uftrace-0.15.2/tests/t052_nested_func.py000066400000000000000000000032701455365734300200340ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'nested', result=""" # DURATION TID FUNCTION [13348] | main() { [13348] | foo() { 0.170 us [13348] | foo_internal.2406(); 0.650 us [13348] | } /* foo */ [13348] | bar() { [13348] | qsort() { 0.120 us [13348] | compar.2414(); 0.093 us [13348] | compar.2414(); 0.092 us [13348] | compar.2414(); 2.479 us [13348] | } /* qsort */ 3.462 us [13348] | } /* bar */ 3.623 us [13348] | } /* main */ """) def build(self, name, cflags='', ldflags=''): if self.supported_lang['C']['cc'] == 'clang': # clang doesn't allow nested function. return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def sort(self, output, ignore_children=False): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ before_main = True funcs = [] for ln in output.split('\n'): if ln.find(' | main()') > 0: before_main = False if before_main: continue # ignore result of remaining functions which follows a blank line if ln.strip() == '': break; try: func = ln.split('|', 1)[-1] # ignore function suffix after '.' funcs.append(func.split('.',1)[0]) except: pass result = '\n'.join(funcs) return result uftrace-0.15.2/tests/t053_filter_time.py000066400000000000000000000007511455365734300200440ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # DURATION TID FUNCTION [18219] | main() { [18219] | foo() { [18219] | bar() { 2.093 ms [18219] | usleep(); 2.095 ms [18219] | } /* bar */ 2.106 ms [18219] | } /* foo */ 2.107 ms [18219] | } /* main */ """) def setup(self): self.option = '-t 1ms' uftrace-0.15.2/tests/t054_filter_time_F.py000066400000000000000000000005551455365734300203140ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # DURATION TID FUNCTION [18229] | bar() { 2.078 ms [18229] | usleep(); 2.080 ms [18229] | } /* bar */ """, sort='simple') def setup(self): self.option = '-t 1ms -F bar' uftrace-0.15.2/tests/t055_filter_time_N.py000066400000000000000000000005351455365734300203230ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # DURATION TID FUNCTION [18224] | main() { 2.083 ms [18224] | foo(); 2.085 ms [18224] | } /* main */ """) def setup(self): self.option = '-t 1ms -N bar' uftrace-0.15.2/tests/t056_filter_time_T.py000066400000000000000000000006061455365734300203310ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # DURATION TID FUNCTION [18260] | main() { 2.088 ms [18260] | foo(); 2.090 ms [18260] | } /* main */ """) def setup(self): self.option = '-t 1ms -T "mem_alloc@trace-off" -T "mem_free@trace-on"' uftrace-0.15.2/tests/t057_filter_time_D.py000066400000000000000000000006401455365734300203100ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # DURATION TID FUNCTION [18270] | main() { [18270] | foo() { 2.071 ms [18270] | bar(); 2.082 ms [18270] | } /* foo */ 2.083 ms [18270] | } /* main */ """) def setup(self): self.option = '-t 1ms -D3' uftrace-0.15.2/tests/t058_arg_int.py000066400000000000000000000016421455365734300171710ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exp-int', result=""" # DURATION TID FUNCTION [18270] | main() { 0.371 ms [18270] | int_add(-1, 2); 0.118 ms [18270] | int_sub(1, 2); 0.711 ms [18270] | int_mul(3, 4); 0.923 ms [18270] | int_div(4, -2); 3.281 ms [18270] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "^int_@arg1,arg2"' if TestBase.is_32bit(self): # int_mul@arg1 is a 'long long', so we should skip arg2 self.option = '-A "int_(add|sub|div)@arg1,arg2" -A "int_mul@arg1/i64,arg3"' uftrace-0.15.2/tests/t059_arg_str.py000066400000000000000000000015111455365734300172030ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exp-str', result=""" # DURATION TID FUNCTION [18141] | main() { 0.271 ms [18141] | str_cpy("", "hello"); 0.205 ms [18141] | str_cpy("", " world"); 0.318 ms [18141] | str_cat("hello", " world"); 0.216 ms [18141] | str_cpy("hello world", "goodbye"); 0.303 ms [18141] | str_cat("goodbye", " world"); 3.134 ms [18141] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "^str_@arg1/s,arg2/s"' uftrace-0.15.2/tests/t060_arg_fmt.py000066400000000000000000000014011455365734300171470ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exp-int', result=""" # DURATION TID FUNCTION [18278] | main() { 0.371 ms [18278] | int_add(-1, 2); 0.118 ms [18278] | int_sub(); 0.711 ms [18278] | int_mul(); 0.923 ms [18278] | int_div(4, 0xfe); 3.281 ms [18278] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "int_add@arg1/i32,arg2/u" -A "int_div@arg1/i16,arg2/x8"' uftrace-0.15.2/tests/t061_arg_plt.py000066400000000000000000000014061455365734300171660ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'pltarg', result=""" # DURATION TID FUNCTION 1.237 us [ 3479] | __monstartup(); 0.897 us [ 3479] | __cxa_atexit(); [ 3479] | main() { 4.886 us [ 3479] | getenv("HOME"); 2.079 us [ 3479] | atoi("100"); 2.139 us [ 3479] | malloc(100); 1.017 us [ 3479] | free(); 12.233 us [ 3479] | } /* main */ """) def setup(self): self.option = '-A "getenv|atoi@arg1/s" -A malloc@arg1' self.exearg = 't-' + self.name + ' 100' def fixup(self, cflags, result): # for some reason, ARM eats up atoi() return result.replace(' 2.079 us [ 3479] | atoi("100");\n', '') uftrace-0.15.2/tests/t062_arg_char.py000066400000000000000000000013551455365734300173100ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exp-char', result=""" # DURATION TID FUNCTION [18279] | main() { 0.371 ms [18279] | foo('f', 'o', 'o'); 0.923 ms [18279] | bar('\\0', 'B', 97, 0x72); 3.281 ms [18279] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "foo@arg1/c,arg2/c,arg3/c" ' self.option += '-A "bar@arg1/c,arg2/c,arg3/i,arg4/x8"' uftrace-0.15.2/tests/t063_retval.py000066400000000000000000000021551455365734300170370ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exp-int', result=""" # DURATION TID FUNCTION 1.498 us [ 3338] | __monstartup(); 1.079 us [ 3338] | __cxa_atexit(); [ 3338] | main() { 3.399 us [ 3338] | int_add(-1, 2) = 1; 0.786 us [ 3338] | int_sub(1, 2) = -1; 0.446 us [ 3338] | int_mul(3, 4) = 12; 0.429 us [ 3338] | int_div(4, -2) = -2; 8.568 us [ 3338] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support return value now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "^int_@arg1,arg2" -R "^int_@retval/i32"' if TestBase.is_32bit(self): # int_mul@arg1 is a 'long long', so we should skip arg2 self.option = '-A "int_(add|sub|div)@arg1,arg2" ' self.option += '-A "int_mul@arg1/i64,arg3" ' self.option += '-R "^int_@retval/i32"' uftrace-0.15.2/tests/t064_trigger_trace.py000066400000000000000000000010451455365734300203610ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # DURATION TID FUNCTION [18219] | main() { [18219] | foo() { 1.866 us [18219] | mem_alloc(); [18219] | bar() { 2.093 ms [18219] | usleep(); 2.095 ms [18219] | } /* bar */ 2.106 ms [18219] | } /* foo */ 2.107 ms [18219] | } /* main */ """) def setup(self): self.option = '-t 1ms -T "mem_alloc@trace"' uftrace-0.15.2/tests/t065_arg_order.py000066400000000000000000000020131455365734300175010ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exp-int', result=""" # DURATION TID FUNCTION [18279] | main() { 0.371 ms [18279] | int_add(-1, 2); 0.118 ms [18279] | int_sub(1, 2); 0.711 ms [18279] | int_mul(0x4, 3); 0.923 ms [18279] | int_div(4, -2); 3.281 ms [18279] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support return value now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "int_mul@arg2/x" -A "^int_@arg1,arg2" -A "int_add@arg1/i32"' if TestBase.is_32bit(self): # int_mul@arg1 is a 'long long', so we should skip arg2 self.option = '-A "int_mul@arg3/x" -A "^int_@arg1" ' self.option += '-A "int_(add|sub|div)@arg2" -A "int_mul@arg1/i64" ' uftrace-0.15.2/tests/t066_no_demangle.py000066400000000000000000000014431455365734300200140ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION [17005] | _ZN2ns3ns13foo3barEv() { [17005] | _ZN2ns3ns13foo4bar1Ev() { [17005] | _ZN2ns3ns13foo4bar2Ev() { [17005] | _ZN2ns3ns13foo4bar3Ev() { 1.350 us [17005] | malloc(); 3.245 us [17005] | } /* _ZN2ns3ns13foo4bar3Ev */ 3.705 us [17005] | } /* _ZN2ns3ns13foo4bar2Ev */ 4.128 us [17005] | } /* _ZN2ns3ns13foo4bar1Ev */ 1.463 us [17005] | free(); 6.702 us [17005] | } /* _ZN2ns3ns13foo3barEv */ """, sort='simple') def setup(self): self.option = '--demangle=no -F "_ZN2ns3ns13foo3barEv"' uftrace-0.15.2/tests/t067_report_diff.py000066400000000000000000000051011455365734300200430ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase XDIR='xxx' YDIR='yyy' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # # uftrace diff # [0] base: xxx (from uftrace record -d xxx tests/t-abc ) # [1] diff: yyy (from uftrace record -d yyy tests/t-abc ) # Total time (diff) Self time (diff) Nr. called (diff) Function ================================ ================================ ================================ ==================================== 6.974 us 6.268 us -10.12% 0.560 us 0.511 us -8.75% 1 1 +0 main 6.414 us 5.757 us -10.24% 0.489 us 0.372 us -23.93% 1 1 +0 a 5.925 us 5.385 us -9.11% 0.786 us 0.554 us -29.52% 1 1 +0 b 5.139 us 4.831 us -5.99% 3.517 us 3.137 us -10.80% 1 1 +0 c 1.622 us 1.694 us +4.44% 1.622 us 1.694 us +4.44% 1 1 +0 getpid """) def prerun(self, timeout): self.subcmd = 'record' self.option = '-d ' + XDIR self.exearg = 't-' + self.name record_cmd = self.runcmd() sp.call(record_cmd.split()) self.option = '-d ' + YDIR record_cmd = self.runcmd() sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'report' self.option = '--sort-column 0 --diff-policy full,percent' # old behavior self.exearg = '-d %s --diff %s' % (XDIR, YDIR) def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.startswith('#') or ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] # tT/0 unit tT/1 unit percent tS/0 unit tS/1 unit percent call/0 call/1 call/diff function if line[-1].startswith('__'): continue result.append('%s %s %s %s' % (line[-4], line[-3], line[-2], line[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t068_filter_time_A.py000066400000000000000000000011001455365734300202770ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # DURATION TID FUNCTION [23157] | main() { [23157] | foo() { [23157] | bar() { 2.093 ms [23157] | usleep(2000); 2.095 ms [23157] | } /* bar */ 2.106 ms [23157] | } /* foo */ 2.107 ms [23157] | } /* main */ """) def setup(self): self.option = '-t 1ms -R mem_alloc@retval ' self.option += '-A mem_free@arg1 -A usleep@plt,arg1' uftrace-0.15.2/tests/t069_graph.py000066400000000000000000000013311455365734300166440ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sort', result=""" # Function Call Graph for 'main' (session: baa921f86e22e0c9) =============== BACKTRACE =============== backtrace #0: hit 1, time 11.460 ms [0] main (0x40069e) ========== FUNCTION CALL GRAPH ========== 11.460 ms : (1) main 311.345 us : +-(2) foo 308.918 us : | (6) loop : | 10.362 ms : +-(1) bar 10.091 ms : (1) usleep """, sort='graph') def prepare(self): self.subcmd = 'record' self.exearg = 't-' + self.name return self.runcmd() def setup(self): self.subcmd = 'graph' self.exearg = 'main' uftrace-0.15.2/tests/t070_graph_backtrace.py000066400000000000000000000012721455365734300206370ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', result=""" # Function Call Graph for 'getpid' (session: adff9f265b25c0d8) =============== BACKTRACE =============== backtrace #0: hit 1, time 2.010 us [0] main (0x400530) [1] a (0x4006f1) [2] b (0x4006c1) [3] c (0x400686) [4] getpid (0x4004d0) ========== FUNCTION CALL GRAPH ========== 2.010 us : (1) getpid """, sort='graph') def prepare(self): self.subcmd = 'record' self.exearg = 't-' + self.name return self.runcmd() def setup(self): self.subcmd = 'graph' self.exearg = 'getpid' uftrace-0.15.2/tests/t071_graph_depth.py000066400000000000000000000025021455365734300200220ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # Function Call Graph for 'main' (session: b508f628ffe7287f) =============== BACKTRACE =============== backtrace #0: hit 1, time 17.931 us [0] main (0x400790) ========== FUNCTION CALL GRAPH ========== 17.931 us : (1) main 2.087 us : +-(2) operator new : | 0.183 us : +-(1) ns::ns1::foo::foo : | 4.816 us : +-(1) ns::ns1::foo::bar 2.810 us : | +-(1) ns::ns1::foo::bar1 2.536 us : | | (1) ns::ns1::foo::bar2 2.240 us : | | (1) ns::ns1::foo::bar3 : | | 1.356 us : | +-(1) free : | 2.624 us : +-(2) operator delete : | 0.093 us : +-(1) ns::ns2::foo::foo : | 1.997 us : +-(1) ns::ns2::foo::bar 1.286 us : +-(1) ns::ns2::foo::bar1 1.017 us : | (1) ns::ns2::foo::bar2 0.740 us : | (1) ns::ns2::foo::bar3 : | 0.187 us : +-(1) free """, sort='graph') def prepare(self): self.subcmd = 'record' self.option = '' self.exearg = 't-' + self.name return self.runcmd() def setup(self): self.subcmd = 'graph' self.option = '-D5' self.exearg = 'main' uftrace-0.15.2/tests/t072_no_comment.py000066400000000000000000000011211455365734300176700ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION 0.508 us [ 772] | __monstartup(); 0.425 us [ 772] | __cxa_atexit(); [ 772] | main() { [ 772] | a() { [ 772] | b() { [ 772] | c() { 0.419 us [ 772] | getpid(); 0.844 us [ 772] | } 1.037 us [ 772] | } 1.188 us [ 772] | } 1.378 us [ 772] | } """) def setup(self): self.option = '--no-comment' uftrace-0.15.2/tests/t073_lib_filter.py000066400000000000000000000012221455365734300176500ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'lib', """ # DURATION TID FUNCTION [17456] | lib_b() { 6.911 us [17456] | lib_c(); 8.279 us [17456] | } /* lib_b */ """, sort='simple') def build(self, name, cflags='', ldflags=''): if TestBase.build_libabc(self, cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-libmain.c', ['libabc_test_lib.so']) def setup(self): self.option = '--force -F lib_b@libabc_test' uftrace-0.15.2/tests/t074_lib_trigger.py000066400000000000000000000012471455365734300200360ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'lib', """ # DURATION TID FUNCTION [17457] | lib_a() { 6.911 us [17457] | lib_b(); 8.279 us [17457] | } /* lib_a */ """, sort='simple') def build(self, name, cflags='', ldflags=''): if TestBase.build_libabc(self, cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-libmain.c', ['libabc_test_lib.so']) def setup(self): self.option = '--force --no-libcall -T lib_b@libabc_test,depth=1' uftrace-0.15.2/tests/t075_lib_arg.py000066400000000000000000000016071455365734300171450ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'lib', """ # DURATION TID FUNCTION [17458] | lib_a(4095) { [17458] | lib_b(4096) { 5.193 us [17458] | lib_c(4095); 6.911 us [17458] | } /* lib_b */ 8.279 us [17458] | } /* lib_a */ """, sort='simple') def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP if TestBase.build_libabc(self, cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-libmain.c', ['libabc_test_lib.so']) def setup(self): self.option = '--force --no-libcall -A ^lib@libabc_test,arg1' uftrace-0.15.2/tests/t076_lib_replay_F.py000066400000000000000000000014351455365734300201350ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'lib', """ # DURATION TID FUNCTION [17459] | lib_b() { 6.911 us [17459] | lib_c(); 8.279 us [17459] | } /* lib_b */ """, sort='simple') def build(self, name, cflags='', ldflags=''): if TestBase.build_libabc(self, cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-libmain.c', ['libabc_test_lib.so']) def prepare(self): self.subcmd = 'record' self.option = '--force' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-F lib_b@libabc_test' uftrace-0.15.2/tests/t077_lib_replay_T.py000066400000000000000000000014621455365734300201540ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'lib', """ # DURATION TID FUNCTION [17460] | lib_a() { 6.911 us [17460] | lib_b(); 8.279 us [17460] | } /* lib_a */ """, sort='simple') def build(self, name, cflags='', ldflags=''): if TestBase.build_libabc(self, cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-libmain.c', ['libabc_test_lib.so']) def prepare(self): self.subcmd = 'record' self.option = '--force --no-libcall' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-T lib_a@libabc_test,depth=2' uftrace-0.15.2/tests/t078_max_stack.py000066400000000000000000000007401455365734300175200ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION 1.138 us [28141] | __monstartup(); 2.202 us [28141] | __cxa_atexit(); [28141] | main() { [28141] | a() { 1.915 us [28141] | b(); 2.405 us [28141] | } /* a */ 3.005 us [28141] | } /* main */ """) def setup(self): self.option = '--max-stack=3' uftrace-0.15.2/tests/t079_replay_kernel_D.py000066400000000000000000000030701455365734300206450ustar00rootroot00000000000000#!/usr/bin/env python3 import os import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'openclose', serial=True, result=""" # DURATION TID FUNCTION 1.088 us [18343] | __monstartup(); 0.640 us [18343] | __cxa_atexit(); [18343] | main() { [18343] | fopen() { 86.790 us [18343] | sys_open(); 89.018 us [18343] | } /* fopen */ [18343] | fclose() { 10.781 us [18343] | sys_close(); 37.325 us [18343] | } /* fclose */ 128.387 us [18343] | } /* main */ """) def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP self.subcmd = 'record' self.option = '-K3 ' self.option += '-N %s@kernel ' % 'exit_to_usermode_loop' self.option += '-N %s@kernel' % '_*do_page_fault' record_cmd = self.runcmd() sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '-k -D3' def fixup(self, cflags, result): uname = os.uname() # Linux v4.17 (x86_64) changed syscall routines major, minor, release = uname[2].split('.') if uname[0] == 'Linux' and uname[4] == 'x86_64' and \ int(major) >= 5 or (int(major) == 4 and int(minor) >= 17): result = result.replace('sys_', '__x64_sys_') return result.replace(' sys_open', ' sys_openat') uftrace-0.15.2/tests/t080_replay_kernel_D2.py000066400000000000000000000017651455365734300207300ustar00rootroot00000000000000#!/usr/bin/env python3 import os import subprocess as sp from runtest import TestBase TDIR='xxx' # there was a problem applying depth filter if it contains kernel functions class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'openclose', serial=True, result=""" # DURATION TID FUNCTION 1.088 us [18343] | __monstartup(); 0.640 us [18343] | __cxa_atexit(); [18343] | main() { 89.018 us [18343] | fopen(); 37.325 us [18343] | fclose(); 128.387 us [18343] | } /* main */ """) def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP self.subcmd = 'record' self.option = '-K3 -N %s@kernel' % 'smp_irq_work_interrupt' record_cmd = self.runcmd() sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '-k -D2' uftrace-0.15.2/tests/t081_kernel_depth.py000066400000000000000000000030661455365734300202100ustar00rootroot00000000000000#!/usr/bin/env python3 import os from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'openclose', serial=True, result=""" # DURATION TID FUNCTION 1.540 us [27711] | __monstartup(); 1.089 us [27711] | __cxa_atexit(); [27711] | main() { [27711] | fopen() { [27711] | sys_open() { 12.732 us [27711] | do_sys_open(); 14.039 us [27711] | } /* sys_open */ 17.193 us [27711] | } /* fopen */ [27711] | fclose() { [27711] | sys_close() { 0.591 us [27711] | __close_fd(); 1.429 us [27711] | } /* sys_close */ 8.028 us [27711] | } /* fclose */ 26.938 us [27711] | } /* main */ """) def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def setup(self): self.option = '-k --kernel-depth=2 --match glob ' self.option += '-N exit_to_usermode_loop@kernel ' self.option += '-N *do_page_fault@kernel' def fixup(self, cflags, result): uname = os.uname() # Linux v4.17 (x86_64) changed syscall routines major, minor, release = uname[2].split('.') if uname[0] == 'Linux' and uname[4] == 'x86_64' and \ int(major) >= 5 or (int(major) == 4 and int(minor) >= 17): result = result.replace(' sys_', ' __x64_sys_') return result.replace(' sys_open', ' sys_openat') uftrace-0.15.2/tests/t082_arg_many.py000066400000000000000000000024371455365734300173430ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'arg', """ # DURATION TID FUNCTION [13476] | main() { [13476] | foo() { [13476] | bar() { 0.567 us [13476] | strcmp(); 1.779 us [13476] | } /* bar */ [13476] | bar() { 0.133 us [13476] | strcmp(); 0.489 us [13476] | } /* bar */ [13476] | bar() { 0.081 us [13476] | strcmp(); 0.381 us [13476] | } /* bar */ 3.515 us [13476] | } /* foo */ 2.235 us [13476] | many(12, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144); [13476] | pass() { 0.130 us [13476] | check(); 0.427 us [13476] | } /* pass */ 18.161 us [13476] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "many@arg1/i32,arg2/i32,arg3/i32,arg4/i32,' self.option += 'arg5/i32,arg6/i32,arg7/i32,arg8/i32,arg9/i32,' self.option += 'arg10/i32,arg11/i32,arg12/i32,arg13/i32"' uftrace-0.15.2/tests/t083_arg_float.py000066400000000000000000000030551455365734300175020ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exp-float', result=""" # DURATION TID FUNCTION [18276] | main() { 0.371 ms [18276] | float_add(-0.100000, 0.200000) = 0.100000; 0.118 ms [18276] | float_sub(0.100000, 0.200000) = -0.100000; 0.711 ms [18276] | float_mul(300.000000, 400.000000) = 120000.000000; 0.923 ms [18276] | float_div(40000000000.000000, -0.020000) = -2000000000000.000000; 3.281 ms [18276] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "float_add@fparg1/32,fparg2/32" -R "float_add@retval/f32" ' self.option += '-A "float_sub@fparg1/32,fparg2" -R "float_sub@retval/f32" ' self.option += '-A "float_mul@fparg1/64,fparg2/32" -R "float_mul@retval/f64" ' self.option += '-A "float_div@fparg1,fparg2" -R "float_div@retval/f"' if TestBase.is_32bit(self): # argument count follows the size of type self.option = self.option.replace('float_mul@fparg1/64,fparg2/32', 'float_mul@fparg1/64,fparg3/32') self.option = self.option.replace('float_div@fparg1,fparg2', 'float_div@fparg1/64,fparg3/64') uftrace-0.15.2/tests/t084_arg_mixed.py000066400000000000000000000041741455365734300175070ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exp-mixed', result=""" # DURATION TID FUNCTION [18276] | main() { 0.371 ms [18276] | mixed_add(-1, 0.200000) = -0.800000; 0.118 ms [18276] | mixed_sub(0x400000, 2048) = 0x3ff800; 0.711 ms [18276] | mixed_mul(-3.000000, 80000000000) = -240000000000; 0.923 ms [18276] | mixed_div(4, -0.000002) = -2000000.000000; 1.257 ms [18276] | mixed_str("argument", 0.000000) = "return"; 4.891 ms [18276] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "mixed_add@arg1/i32,fparg1/32" ' self.option += '-R "mixed_add@retval/f64" ' self.option += '-A "mixed_sub@arg1/x,arg2" ' self.option += '-R "mixed_sub@retval" ' self.option += '-A "mixed_mul@fparg1,arg1/i64" ' self.option += '-R "mixed_mul@retval/i64" ' self.option += '-A "mixed_div@arg1/i64,fparg1/80%stack+1" ' self.option += '-R "mixed_div@retval/f80" ' self.option += '-A "mixed_str@arg1/s,fparg1" ' self.option += '-R "mixed_str@retval/s"' if TestBase.get_elf_machine(self) == 'arm': self.option = self.option.replace('fparg1/80%stack+1', 'fparg1/80') elif TestBase.is_32bit(self): self.option = '-A "mixed_add@arg1/i32,fparg2/32" ' self.option += '-R "mixed_add@retval/f64" ' self.option += '-A "mixed_sub@arg1/x,arg2" ' self.option += '-R "mixed_sub@retval" ' self.option += '-A "mixed_mul@fparg1,arg3/i64" ' self.option += '-R "mixed_mul@retval/i64" ' self.option += '-A "mixed_div@arg1/i64,fparg1/80%stack+3" ' self.option += '-R "mixed_div@retval/f80" ' self.option += '-A "mixed_str@arg1/s,fparg1" ' self.option += '-R "mixed_str@retval/s"' uftrace-0.15.2/tests/t085_arg_reg.py000066400000000000000000000032031455365734300171470ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exp-mixed', result=""" # DURATION TID FUNCTION [18277] | main() { 0.371 ms [18277] | mixed_add(-1, 0.200000); 0.118 ms [18277] | mixed_sub(0x400000, 2048); 0.711 ms [18277] | mixed_mul(-3.000000, 80000000000); 0.923 ms [18277] | mixed_div(4, -0.000002); 1.257 ms [18277] | mixed_str("argument", 0.000000); 4.891 ms [18277] | } /* main */ """) def prerun(self, timeout): if TestBase.get_elf_machine(self) == 'i386': return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "mixed_add@arg1/i32%rdi,fparg1/32%xmm0" ' self.option += '-A "mixed_sub@arg1/x%rdi,arg2%rsi" ' self.option += '-A "mixed_mul@fparg1%xmm0,arg1/i64" ' self.option += '-A "mixed_div@arg1/i64,fparg1/80%stack+1" ' self.option += '-A "mixed_str@arg1/s%rdi,fparg1%xmm0"' if TestBase.get_elf_machine(self) == 'arm': self.option = self.option.replace('%rdi', '%r0') self.option = self.option.replace('%rsi', '%r1') self.option = self.option.replace('32%xmm0', '32%s0') self.option = self.option.replace('%xmm0', '%d0') self.option = self.option.replace('fparg1/80%stack+1', 'fparg1/80') uftrace-0.15.2/tests/t086_arg_stack.py000066400000000000000000000032531455365734300175050ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'arg', """ # DURATION TID FUNCTION [13476] | main() { [13476] | foo() { [13476] | bar() { 0.567 us [13476] | strcmp(); 1.779 us [13476] | } /* bar */ [13476] | bar() { 0.133 us [13476] | strcmp(); 0.489 us [13476] | } /* bar */ [13476] | bar() { 0.081 us [13476] | strcmp(); 0.381 us [13476] | } /* bar */ 3.515 us [13476] | } /* foo */ 2.235 us [13476] | many(8, 13, 21, 34, 55, 89, 144); [13476] | pass() { 0.130 us [13476] | check(); 0.427 us [13476] | } /* pass */ 18.161 us [13476] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "many@arg1/i32%stack+1,arg2/i32%stack+2" ' self.option += '-A "many@arg3/i32%stack+3,arg4/i32%stack+4" ' self.option += '-A "many@arg5/i32%stack5,arg6/i32%stack6,arg7/i32%stack7"' if TestBase.is_32bit(self): # i386 use stack for passing argument. so, change order. self.option = '-A "many@arg1/i32%stack+7,arg2/i32%stack+8" ' self.option += '-A "many@arg3/i32%stack+9,arg4/i32%stack+10" ' self.option += '-A "many@arg5/i32%stack11,arg6/i32%stack12,arg7/i32%stack13"' # FIXME: arm has to be handled differently uftrace-0.15.2/tests/t087_arg_variadic.py000066400000000000000000000022411455365734300201570ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'variadic', result=""" # DURATION TID FUNCTION 1.334 us [ 9624] | __monstartup(); 0.869 us [ 9624] | __cxa_atexit(); [ 9624] | main() { [ 9624] | variadic("print %c %s %d %ld %lu %lld %f", 'a', "hello", 100, 1234, 5678, 9876543210, 3.141592) { 8.979 us [ 9624] | vsnprintf(256, "print %c %s %d %ld %lu %lld %f"); 12.642 us [ 9624] | } /* variadic */ 13.250 us [ 9624] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "variadic@arg1/s,arg2/c,arg3/s,arg4,arg5,arg6,arg7/i64,fparg1" ' self.option += '-A "vsnprintf@arg2,arg3/s" ' if TestBase.is_32bit(self): self.option = '-A "variadic@arg1/s,arg2/c,arg3/s,arg4,arg5,arg6,arg7/i64,fparg9" ' self.option += '-A "vsnprintf@arg2,arg3/s" ' uftrace-0.15.2/tests/t088_graph_session.py000066400000000000000000000041621455365734300204150ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'forkexec', result=""" # Function Call Graph for 'main' (session: 327202376e209585) =============== BACKTRACE =============== backtrace #0: hit 1, time 5.824 us [0] main (0x400530) ========== FUNCTION CALL GRAPH ========== 5.824 us : (1) main 5.411 us : (1) a 5.141 us : (1) b 4.670 us : (1) c 0.967 us : (1) getpid # Function Call Graph for 'main' (session: f34056bd485963b3) =============== BACKTRACE =============== backtrace #0: hit 1, time 3.679 ms [0] main (0x4005b0) ========== FUNCTION CALL GRAPH ========== 3.679 ms : (1) main 127.172 us : +-(1) fork : | 3.527 ms : +-(1) waitpid """) def build(self, name, cflags='', ldflags=''): ret = TestBase.build(self, 'abc', cflags, ldflags) ret += TestBase.build(self, self.name, cflags, ldflags) return ret def prepare(self): self.subcmd = 'record' self.exearg = 't-' + self.name return self.runcmd() def setup(self): self.subcmd = 'graph' self.exearg = 'main' def fixup(self, cflags, result): return result.replace("readlink", """memset : | 9.814 us : +-(1) readlink""") def sort(self, output): """ This function post-processes output of the test to be compared. It ignores blank and comment (#) lines and header lines. """ result = [] mode = 0 for ln in output.split('\n'): if ln.strip() == '' or ln.startswith('#'): continue if ln.startswith('=============== BACKTRACE ==============='): mode = 1 # it seems to be broken in this case continue if ln.startswith('========== FUNCTION CALL GRAPH =========='): mode = 2 continue if mode == 1: pass # compare function graph part only if mode == 2: result.append(ln.split(':')[1]) # remove time part return '\n'.join(result) uftrace-0.15.2/tests/t089_graph_exit.py000066400000000000000000000011461455365734300177030ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exit', result=""" # Function Call Graph for 'main' (session: 095c3a95937bdbae) =============== BACKTRACE =============== backtrace #0: hit 1, time 0.527 us [0] main (0x400480) ========== FUNCTION CALL GRAPH ========== 0.527 us : (1) main 0.387 us : (1) foo : (1) exit """, sort='graph') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'graph' self.exearg = 'main' uftrace-0.15.2/tests/t090_report_recursive.py000066400000000000000000000032461455365734300211460ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase # This test checks total time of recursive calls not exceeds self time. # But the actual time will be different than this example run. # So just use 'same' symbol to indicate it handles recursive calls properly. class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fibonacci', """ 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 """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'report' def sort(self, output): """ This function post-processes output of the test to be compared. It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.strip() == '': continue line = ln.split() if line[0].startswith('='): continue if len(line) <= 5 or line[5] != 'fib': continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] # total_time unit self_time unit calls function if line[0] == line[2]: result.append('same same') else: result.append('%s %s' % (line[0], line[2])) return '\n'.join(result) uftrace-0.15.2/tests/t091_replay_tid.py000066400000000000000000000021141455365734300176720ustar00rootroot00000000000000#!/usr/bin/env python3 import os.path from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fork', """ # DURATION TID FUNCTION [ 1661] | main() { 130.930 us [ 1661] | fork(); 691.873 us [ 1661] | wait(); [ 1661] | a() { [ 1661] | b() { [ 1661] | c() { 4.234 us [ 1661] | getpid(); 5.680 us [ 1661] | } /* c */ 6.094 us [ 1661] | } /* b */ 6.602 us [ 1661] | } /* a */ 849.948 us [ 1661] | } /* main */ """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): t = 0 for ln in open(os.path.join('uftrace.data', 'task.txt')): if not ln.startswith('TASK'): continue try: t = int(ln.split()[2].split('=')[1]) except: pass if t == 0: self.subcmd = 'FAILED TO FIND TID' return self.subcmd = 'replay' self.option = '-F main --tid %d' % t uftrace-0.15.2/tests/t092_report_tid.py000066400000000000000000000023041455365734300177130ustar00rootroot00000000000000#!/usr/bin/env python3 import os.path from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fork', """ Total time Self time Calls Function ========== ========== ========== ==================================== 849.948 us 20.543 us 1 main 691.873 us 691.873 us 1 wait 130.930 us 130.930 us 1 fork 6.602 us 0.508 us 1 a 6.094 us 0.414 us 1 b 5.680 us 1.446 us 1 c 4.234 us 4.234 us 1 getpid 1.568 us 1.568 us 1 __monstartup 1.140 us 1.140 us 1 __cxa_atexit """, sort='report') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): t = 0 for ln in open(os.path.join('uftrace.data', 'task.txt')): if not ln.startswith('TASK'): continue try: t = int(ln.split()[2].split('=')[1]) except: pass if t == 0: self.subcmd = 'FAILED TO FIND TID' return self.subcmd = 'report' self.option = '--tid %d' % t uftrace-0.15.2/tests/t093_report_filter.py000066400000000000000000000012411455365734300204200ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fork', """ Total time Self time Calls Function ========== ========== ========== ==================================== 849.948 us 20.543 us 2 main 691.873 us 691.873 us 1 wait 130.930 us 130.930 us 2 fork 6.602 us 0.508 us 1 a 6.094 us 0.414 us 1 b """, sort='report') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'report' self.option = '-F main -N c' uftrace-0.15.2/tests/t094_report_depth.py000066400000000000000000000014471455365734300202500ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fork', """ Total time Self time Calls Function ========== ========== ========== ==================================== 849.948 us 20.543 us 2 main 691.873 us 691.873 us 1 wait 130.930 us 130.930 us 2 fork 10.942 us 0.880 us 2 a 10.062 us 0.756 us 2 b 3.626 us 1.612 us 1 c 1.568 us 1.568 us 1 __monstartup 1.140 us 1.140 us 1 __cxa_atexit """, sort='report') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'report' self.option = '-D 3' uftrace-0.15.2/tests/t095_graph_tid.py000066400000000000000000000020221455365734300175010ustar00rootroot00000000000000#!/usr/bin/env python3 import os.path from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fork', result=""" # Function Call Graph for 'a' (session: 5eec64959f2b2e87) =============== BACKTRACE =============== backtrace #0: hit 1, time 4.290 us [0] main (0x4005c0) [1] a (0x4007a1) ========== FUNCTION CALL GRAPH ========== 4.290 us : (1) a 3.970 us : (1) b 3.580 us : (1) c 2.616 us : (1) getpid """, sort='graph') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): t = 0 for ln in open(os.path.join('uftrace.data', 'task.txt')): if not ln.startswith('TASK'): continue try: t = int(ln.split()[2].split('=')[1]) except: pass if t == 0: self.subcmd = 'FAILED TO FIND TID' return self.subcmd = 'graph' self.option = '--tid %d' % t self.exearg = 'a' uftrace-0.15.2/tests/t096_graph_filter.py000066400000000000000000000011751455365734300202170ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fork', result=""" # Function Call Graph for 'a' (session: 93175b4bdd9d0ddf) =============== BACKTRACE =============== backtrace #0: hit 1, time 4.217 us [0] main (0x4005c0) [1] a (0x4007a1) ========== FUNCTION CALL GRAPH ========== 4.217 us : (1) a 3.876 us : (1) b """, sort='graph') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'graph' self.option = '-F main -N c' self.exearg = 'a' uftrace-0.15.2/tests/t097_dump_basic.py000066400000000000000000000035051455365734300176570ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ 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 = 0x363 (PLTHOOK | TASK_SESSION | SYM_REL_ADDR | MAX_STACK | PERF_EVENT | AUTO_ARGS) uftrace file header: info = 0x3bff reading 5231.dat 58348.873430946 5231: [entry] __monstartup(4004d0) depth: 0 58348.873433169 5231: [exit ] __monstartup(4004d0) depth: 0 58348.873439477 5231: [entry] __cxa_atexit(4004f0) depth: 0 58348.873440994 5231: [exit ] __cxa_atexit(4004f0) depth: 0 58348.873444506 5231: [entry] main(400512) depth: 0 58348.873444843 5231: [entry] a(4006b2) depth: 1 58348.873445107 5231: [entry] b(4006a0) depth: 2 58348.873445348 5231: [entry] c(400686) depth: 3 58348.873445830 5231: [entry] getpid(4004b0) depth: 4 58348.873447154 5231: [exit ] getpid(4004b0) depth: 4 58348.873448318 5231: [exit ] c(400686) depth: 3 58348.873448707 5231: [exit ] b(4006a0) depth: 2 58348.873448996 5231: [exit ] a(4006b2) depth: 1 58348.873449309 5231: [exit ] main(400512) depth: 0 """, sort='dump') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'dump' def fixup(self, cflags, result): if TestBase.is_32bit(self): result = result.replace("2 (64 bit)", "1 (32 bit)") p = sp.Popen(['file', 't-' + self.name], stdout=sp.PIPE) if 'BuildID' not in p.communicate()[0].decode(errors='ignore'): result = result.replace("0xbff", "0xbfd") return result uftrace-0.15.2/tests/t098_dump_tid.py000066400000000000000000000043031455365734300173540ustar00rootroot00000000000000#!/usr/bin/env python3 import os.path import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fork', """ 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 = 0x363 (PLTHOOK | TASK_SESSION | SYM_REL_ADDR | MAX_STACK | PERF_EVENT | AUTO_ARGS) uftrace file header: info = 0x3bff reading 5186.dat 58071.916834908 5186: [entry] main(400590) depth: 0 58071.916835853 5186: [entry] fork(400580) depth: 1 58071.917056572 5186: [exit ] fork(400580) depth: 1 58071.917091028 5186: [entry] wait(400570) depth: 1 58071.918038822 5186: [exit ] wait(400570) depth: 1 58071.918040938 5186: [entry] a(400774) depth: 1 58071.918041182 5186: [entry] b(400741) depth: 2 58071.918041482 5186: [entry] c(400706) depth: 3 58071.918042306 5186: [entry] getpid(400530) depth: 4 58071.918045615 5186: [exit ] getpid(400530) depth: 4 58071.918048103 5186: [exit ] c(400706) depth: 3 58071.918048457 5186: [exit ] b(400741) depth: 2 58071.918048760 5186: [exit ] a(400774) depth: 1 58071.918049117 5186: [exit ] main(400590) depth: 0 reading 5188.dat """, sort='dump') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): t = 0 for ln in open(os.path.join('uftrace.data', 'task.txt')): if not ln.startswith('TASK'): continue try: t = int(ln.split()[2].split('=')[1]) except: pass if t == 0: self.subcmd = 'FAILED TO FIND TID' return self.subcmd = 'dump' self.option = '--tid %d' % t def fixup(self, cflags, result): if TestBase.is_32bit(self): result = result.replace("2 (64 bit)", "1 (32 bit)") p = sp.Popen(['file', 't-' + self.name], stdout=sp.PIPE) if 'BuildID' not in p.communicate()[0].decode(errors='ignore'): result = result.replace("0xbff", "0xbfd") return result uftrace-0.15.2/tests/t099_dump_filter.py000066400000000000000000000026341455365734300200670ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ 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 = 0x363 (PLTHOOK | TASK_SESSION | SYM_REL_ADDR | MAX_STACK | PERF_EVENT | AUTO_ARGS) uftrace file header: info = 0x3bff reading 5231.dat 58348.873444506 5231: [entry] main(400512) depth: 0 58348.873444843 5231: [entry] a(4006b2) depth: 1 58348.873445107 5231: [entry] b(4006a0) depth: 2 58348.873448707 5231: [exit ] b(4006a0) depth: 2 58348.873448996 5231: [exit ] a(4006b2) depth: 1 58348.873449309 5231: [exit ] main(400512) depth: 0 """, sort='dump') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'dump' self.option = '-F main -N c' def fixup(self, cflags, result): if TestBase.is_32bit(self): result = result.replace("2 (64 bit)", "1 (32 bit)") p = sp.Popen(['file', 't-' + self.name], stdout=sp.PIPE) if 'BuildID' not in p.communicate()[0].decode(errors='ignore'): result = result.replace("0xbff", "0xbfd") return result uftrace-0.15.2/tests/t100_dump_depth.py000066400000000000000000000026341455365734300176650ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ 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 = 0x363 (PLTHOOK | TASK_SESSION | SYM_REL_ADDR | MAX_STACK | PERF_EVENT | AUTO_ARGS) uftrace file header: info = 0x3bff reading 5231.dat 58348.873444506 5231: [entry] main(400512) depth: 0 58348.873444843 5231: [entry] a(4006b2) depth: 1 58348.873445107 5231: [entry] b(4006a0) depth: 2 58348.873448707 5231: [exit ] b(4006a0) depth: 2 58348.873448996 5231: [exit ] a(4006b2) depth: 1 58348.873449309 5231: [exit ] main(400512) depth: 0 """, sort='dump') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'dump' self.option = '-F main -D 3' def fixup(self, cflags, result): if TestBase.is_32bit(self): result = result.replace("2 (64 bit)", "1 (32 bit)") p = sp.Popen(['file', 't-' + self.name], stdout=sp.PIPE) if 'BuildID' not in p.communicate()[0].decode(errors='ignore'): result = result.replace("0xbff", "0xbfd") return result uftrace-0.15.2/tests/t101_dump_chrome.py000066400000000000000000000020431455365734300200310ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ {"traceEvents":[ {"ts":0,"ph":"M","pid":5231,"name":"process_name","args":{"name":"[5231] t-abc"}}, {"ts":0,"ph":"M","pid":5231,"name":"thread_name","args":{"name":"[5231] t-abc"}}, {"ts":58348873444,"ph":"B","pid":5231,"name":"main"}, {"ts":58348873444,"ph":"B","pid":5231,"name":"a"}, {"ts":58348873445,"ph":"B","pid":5231,"name":"b"}, {"ts":58348873445,"ph":"B","pid":5231,"name":"c"}, {"ts":58348873448,"ph":"E","pid":5231,"name":"c"}, {"ts":58348873448,"ph":"E","pid":5231,"name":"b"}, {"ts":58348873448,"ph":"E","pid":5231,"name":"a"}, {"ts":58348873449,"ph":"E","pid":5231,"name":"main"} ], "metadata": { "command_line":"uftrace record -d abc.data t-abc ", "recorded_time":"Sat Oct 1 18:19:06 2016" } } """, sort='chrome') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'dump' self.option = '-F main -D 4 --chrome' uftrace-0.15.2/tests/t102_dump_flamegraph.py000066400000000000000000000013351455365734300206660ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', """ main;foo;bar;usleep 1 """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'dump' self.option = '--flame-graph --sample-time=2ms' def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.strip() == '': continue result.append(ln) return '\n'.join(result) uftrace-0.15.2/tests/t103_dump_kernel.py000066400000000000000000000071161455365734300200440ustar00rootroot00000000000000#!/usr/bin/env python3 import os import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'getids', serial=True, result=""" {"traceEvents":[ {"ts":14510734170,"ph":"M","pid":32687,"name":"process_name","args":{'name': 't-getids'}}, {"ts":14510734170,"ph":"M","pid":32687,"name":"thread_name","args":{'name': 't-getids'}}, {"ts":14510734172,"ph":"B","pid":32687,"name":"main"}, {"ts":14510734172,"ph":"B","pid":32687,"name":"getpid"}, {"ts":14510734173,"ph":"E","pid":32687,"name":"getpid"}, {"ts":14510734176,"ph":"B","pid":32687,"name":"getppid"}, {"ts":14510734177,"ph":"B","pid":32687,"name":"sys_getppid"}, {"ts":14510734177,"ph":"E","pid":32687,"name":"sys_getppid"}, {"ts":14510734178,"ph":"E","pid":32687,"name":"getppid"}, {"ts":14510734178,"ph":"B","pid":32687,"name":"getpgid"}, {"ts":14510734179,"ph":"B","pid":32687,"name":"sys_getpgid"}, {"ts":14510734180,"ph":"E","pid":32687,"name":"sys_getpgid"}, {"ts":14510734180,"ph":"E","pid":32687,"name":"getpgid"}, {"ts":14510734180,"ph":"B","pid":32687,"name":"getsid"}, {"ts":14510734181,"ph":"B","pid":32687,"name":"sys_getsid"}, {"ts":14510734182,"ph":"E","pid":32687,"name":"sys_getsid"}, {"ts":14510734182,"ph":"E","pid":32687,"name":"getsid"}, {"ts":14510734182,"ph":"B","pid":32687,"name":"getuid"}, {"ts":14510734183,"ph":"B","pid":32687,"name":"sys_getuid"}, {"ts":14510734183,"ph":"E","pid":32687,"name":"sys_getuid"}, {"ts":14510734184,"ph":"E","pid":32687,"name":"getuid"}, {"ts":14510734184,"ph":"B","pid":32687,"name":"geteuid"}, {"ts":14510734184,"ph":"B","pid":32687,"name":"sys_geteuid"}, {"ts":14510734185,"ph":"E","pid":32687,"name":"sys_geteuid"}, {"ts":14510734185,"ph":"E","pid":32687,"name":"geteuid"}, {"ts":14510734185,"ph":"B","pid":32687,"name":"getgid"}, {"ts":14510734186,"ph":"B","pid":32687,"name":"sys_getgid"}, {"ts":14510734187,"ph":"E","pid":32687,"name":"sys_getgid"}, {"ts":14510734187,"ph":"E","pid":32687,"name":"getgid"}, {"ts":14510734187,"ph":"B","pid":32687,"name":"getegid"}, {"ts":14510734188,"ph":"B","pid":32687,"name":"sys_getegid"}, {"ts":14510734188,"ph":"E","pid":32687,"name":"sys_getegid"}, {"ts":14510734188,"ph":"E","pid":32687,"name":"getegid"}, {"ts":14510734189,"ph":"E","pid":32687,"name":"main"} ], "metadata": { "command_line":"uftrace record -k -d xxx t-getids ", "recorded_time":"Sun Oct 2 20:52:31 2016" } } """, sort='chrome') def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP self.subcmd = 'record' self.option = '-k' record_cmd = self.runcmd() sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'dump' self.option = '-k --chrome' def fixup(self, cflags, result): result = result.replace("""\ {"ts":14510734172,"ph":"B","pid":32687,"name":"getpid"}, {"ts":14510734173,"ph":"E","pid":32687,"name":"getpid"},\ """, """\ {"ts":14510734172,"ph":"B","pid":32687,"name":"getpid"}, {"ts":14510734172,"ph":"B","pid":32687,"name":"sys_getpid"}, {"ts":14510734172,"ph":"E","pid":32687,"name":"sys_getpid"}, {"ts":14510734173,"ph":"E","pid":32687,"name":"getpid"},\ """) uname = os.uname() # Linux v4.17 (x86_64) changed syscall routines major, minor, release = uname[2].split('.') if uname[0] == 'Linux' and uname[4] == 'x86_64' and \ int(major) >= 5 or (int(major) == 4 and int(minor) >= 17): result = result.replace('sys_get', '__x64_sys_get') return result uftrace-0.15.2/tests/t104_graph_kernel.py000066400000000000000000000037361455365734300202050ustar00rootroot00000000000000#!/usr/bin/env python3 import os import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'getids', serial=True, result=""" # Function Call Graph for 'main' (session: 59268c360e3c1bd6) =============== BACKTRACE =============== backtrace #0: hit 1, time 24.837 us [0] main (0x400893) ========== FUNCTION CALL GRAPH ========== 24.837 us : (1) main 0.860 us : +-(1) getpid : | 3.130 us : +-(1) getppid 1.080 us : | (1) sys_getppid : | 2.926 us : +-(1) getpgid 0.834 us : | (1) sys_getpgid : | 2.393 us : +-(1) getsid 0.750 us : | (1) sys_getsid : | 2.030 us : +-(1) getuid 0.660 us : | (1) sys_getuid : | 2.074 us : +-(1) geteuid 0.510 us : | (1) sys_geteuid : | 4.391 us : +-(1) getgid 0.696 us : | (1) sys_getgid : | 4.223 us : +-(1) getegid 1.710 us : (1) sys_getegid """, sort='graph') def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP self.subcmd = 'record' self.option = '-k' self.exearg = 't-' + self.name record_cmd = self.runcmd() sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'graph' self.option = '-k' self.exearg = 'main' def fixup(self, cflags, result): uname = os.uname() result = result.replace("(1) getpid", """(1) getpid 0.738 us : | (1) sys_getpid""") # Linux v4.17 (x86_64) changed syscall routines major, minor, release = uname[2].split('.') if uname[0] == 'Linux' and uname[4] == 'x86_64' and \ int(major) >= 5 or (int(major) == 4 and int(minor) >= 17): result = result.replace('sys_get', '__x64_sys_get') return result uftrace-0.15.2/tests/t105_replay_time.py000066400000000000000000000011341455365734300200450ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # DURATION TID FUNCTION [32537] | main() { [32537] | foo() { [32537] | bar() { 2.080 ms [32537] | usleep(); 2.084 ms [32537] | } /* bar */ 2.102 ms [32537] | } /* foo */ 2.103 ms [32537] | } /* main */ """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-t 1ms' uftrace-0.15.2/tests/t106_report_time.py000066400000000000000000000011671455365734300200730ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', """ Total time Self time Calls Function ========== ========== ========== ==================================== 2.103 ms 0.910 us 1 main 2.102 ms 18.787 us 1 foo 2.084 ms 4.107 us 1 bar 2.080 ms 2.080 ms 1 usleep """, sort='report') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'report' self.option = '-t 1ms' uftrace-0.15.2/tests/t107_dump_time.py000066400000000000000000000020761455365734300175260ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', """ {"traceEvents":[ {"ts":0,"ph":"M","pid":32537,"name":"process_name","args":{"name":"[32537] t-sleep"}}, {"ts":0,"ph":"M","pid":32537,"name":"thread_name","args":{"name":"[32537] t-sleep"}}, {"ts":56466448731,"ph":"B","pid":32537,"name":"main"}, {"ts":56466448731,"ph":"B","pid":32537,"name":"foo"}, {"ts":56466448742,"ph":"B","pid":32537,"name":"bar"}, {"ts":56466448743,"ph":"B","pid":32537,"name":"usleep"}, {"ts":56466450823,"ph":"E","pid":32537,"name":"usleep"}, {"ts":56466450827,"ph":"E","pid":32537,"name":"bar"}, {"ts":56466450834,"ph":"E","pid":32537,"name":"foo"}, {"ts":56466450835,"ph":"E","pid":32537,"name":"main"} ], "metadata": { "command_line":"uftrace record -d xxx t-sleep ", "recorded_time":"Mon Oct 3 22:45:57 2016" } } """, sort='chrome') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'dump' self.option = '-t 1ms --chrome' uftrace-0.15.2/tests/t108_graph_time.py000066400000000000000000000012361455365734300176600ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # Function Call Graph for 'main' (session: b78e9c27042adaa7) =============== BACKTRACE =============== backtrace #0: hit 1, time 2.109 ms [0] main (0x400570) ========== FUNCTION CALL GRAPH ========== 2.109 ms : (1) main 2.109 ms : (1) foo 2.098 ms : (1) bar 2.096 ms : (1) usleep """, sort='graph') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'graph' self.option = '-t 1ms' self.exearg = 'main' uftrace-0.15.2/tests/t109_replay_time_A.py000066400000000000000000000017101455365734300203110ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # DURATION TID FUNCTION [32537] | main(1) { [32537] | foo() { [32537] | bar() { 2.080 ms [32537] | usleep(2000); 2.084 ms [32537] | } /* bar */ 2.102 ms [32537] | } /* foo */ 2.103 ms [32537] | } /* main */ """, sort='simple') def build(self, name, cflags='', ldflags=''): if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def prepare(self): self.subcmd = "record" self.option = "-A main@arg1 " self.option += "-A (malloc|free|usleep)@plt,arg1 " self.option += "-R malloc@retval" return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-t 1ms' uftrace-0.15.2/tests/t110_replay_time_T.py000066400000000000000000000013461455365734300203310ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # DURATION TID FUNCTION [13256] | main() { [13256] | foo() { [13256] | mem_alloc() { 1.000 us [13256] | malloc(); 1.769 us [13256] | } /* mem_alloc */ [13256] | bar() { 2.073 ms [13256] | usleep(); 2.075 ms [13256] | } /* bar */ 2.084 ms [13256] | } /* foo */ 2.085 ms [13256] | } /* main */ """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-t 1ms -T malloc@trace' uftrace-0.15.2/tests/t111_kernel_tid.py000066400000000000000000000045521455365734300176570ustar00rootroot00000000000000#!/usr/bin/env python3 import os import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fork', serial=True, result=""" # DURATION TID FUNCTION [ 1661] | main() { [ 1661] | fork() { 5.135 us [ 1661] | sys_writev(); 32.391 us [ 1661] | sys_clone(); 130.930 us [ 1661] | } /* fork */ [ 1661] | wait() { 7.074 us [ 1661] | sys_wait4(); 691.873 us [ 1661] | } /* wait */ [ 1661] | a() { [ 1661] | b() { [ 1661] | c() { 4.234 us [ 1661] | getpid(); 5.680 us [ 1661] | } /* c */ 6.094 us [ 1661] | } /* b */ 6.602 us [ 1661] | } /* a */ 849.948 us [ 1661] | } /* main */ """) def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP self.subcmd = 'record' self.option = '-k --match glob ' self.option += '-N *page_fault@kernel' record_cmd = self.runcmd() sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): t = 0 for ln in open(os.path.join('uftrace.data', 'task.txt')): if not ln.startswith('TASK'): continue try: t = int(ln.split()[2].split('=')[1]) except: pass if t == 0: self.subcmd = 'FAILED TO FIND TID' return self.subcmd = 'replay' self.option = '-k --tid %d' % t def fixup(self, cflags, result): result = result.replace(" [ 1661] | fork() {", """\ [ 1661] | fork() { 5.135 us [ 1661] | sys_getpid();""") result = result.replace(" 4.234 us [ 1661] | getpid();", """\ [ 1661] | getpid() { 3.328 us [ 1661] | sys_getpid(); 4.234 us [ 1661] | } /* getpid */""") uname = os.uname() # Linux v4.17 (x86_64) changed syscall routines major, minor, release = uname[2].split('.') if uname[0] == 'Linux' and uname[4] == 'x86_64' and \ int(major) >= 5 or (int(major) == 4 and int(minor) >= 17): result = result.replace('sys_', '__x64_sys_') return result uftrace-0.15.2/tests/t112_replay_skip.py000066400000000000000000000014101455365734300200500ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'thread', ldflags='-pthread', result=""" # DURATION TID FUNCTION [20053] | main() { 81.953 us [20053] | pthread_create(); 53.108 us [20053] | pthread_create(); 188.065 us [20053] | pthread_create(); 184.846 us [20053] | pthread_create(); 779.561 us [20053] | pthread_join(); 1.136 us [20053] | pthread_join(); 0.702 us [20053] | pthread_join(); 0.650 us [20053] | pthread_join(); 1.309 ms [20053] | } /* main */ """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-F main' uftrace-0.15.2/tests/t113_trigger_time.py000066400000000000000000000011671455365734300202210ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # DURATION TID FUNCTION [16873] | main() { [16873] | foo() { [16873] | mem_alloc() { 1.675 us [16873] | malloc(); 6.867 us [16873] | } /* mem_alloc */ [16873] | bar() { 2.068 ms [16873] | usleep(); 2.071 ms [16873] | } /* bar */ 2.085 ms [16873] | } /* foo */ 2.086 ms [16873] | } /* main */ """) def setup(self): self.option = '-t 1ms -T mem_alloc@time=0' uftrace-0.15.2/tests/t114_replay_trg_time.py000066400000000000000000000013521455365734300207230ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # DURATION TID FUNCTION [16873] | main() { [16873] | foo() { [16873] | mem_alloc() { 1.675 us [16873] | malloc(); 6.867 us [16873] | } /* mem_alloc */ [16873] | bar() { 2.068 ms [16873] | usleep(); 2.071 ms [16873] | } /* bar */ 2.085 ms [16873] | } /* foo */ 2.086 ms [16873] | } /* main */ """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-t 1ms -T mem_alloc@time=0' uftrace-0.15.2/tests/t115_replay_field.py000066400000000000000000000015511455365734300201760ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # TIMESTAMP DURATION TID FUNCTION 75691.369083031 [28337] | main() { 75691.369083271 [28337] | a() { 75691.369083411 [28337] | b() { 75691.369083545 [28337] | c() { 75691.369083755 0.776 us [28337] | getpid(); 75691.369085245 1.700 us [28337] | } /* c */ 75691.369085578 2.167 us [28337] | } /* b */ 75691.369085788 2.517 us [28337] | } /* a */ 75691.369085968 2.937 us [28337] | } /* main */ """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-F main -f time,duration,tid' uftrace-0.15.2/tests/t116_field_none.py000066400000000000000000000006171455365734300176440ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """main() { a() { b() { c() { getpid(); } /* c */ } /* b */ } /* a */ } /* main */ """) def setup(self): self.option = '-F main -f none' def sort(self, output, ignore_children=False): return output uftrace-0.15.2/tests/t117_time_range.py000066400000000000000000000022761455365734300176600ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase START=0 class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # TIMESTAMP FUNCTION 74469.340765344 | c() { 74469.340765524 | getpid(); 74469.340766935 | } /* c */ 74469.340767195 | } /* b */ 74469.340767372 | } /* a */ 74469.340767541 | } /* main */ """, sort='simple') def prerun(self, timeout): global START self.subcmd = 'record' record_cmd = self.runcmd() sp.call(record_cmd.split()) # find timestamp of function 'c' self.subcmd = 'replay' self.option = '-f time -F main' replay_cmd = self.runcmd() p = sp.Popen(replay_cmd, shell=True, stdout=sp.PIPE, stderr=sp.PIPE) r = p.communicate()[0].decode(errors='ignore') lines = r.split('\n') if len(lines) < 5: return TestBase.TEST_DIFF_RESULT START = lines[4].split()[0] # skip header, main, a and b (= 4) p.wait() return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '-f time -r %s~' % START uftrace-0.15.2/tests/t118_thread_tsd.py000066400000000000000000000020601455365734300176570ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'thread-tsd', ldflags='-pthread', result=""" # DURATION TID FUNCTION 1.368 us [ 3336] | __monstartup(); 1.142 us [ 3336] | __cxa_atexit(); [ 3336] | main() { 1.019 us [ 3336] | pthread_key_create(); 1.278 us [ 3336] | malloc(); 0.828 us [ 3336] | pthread_setspecific(); 39.549 us [ 3336] | pthread_create(); [ 3336] | pthread_join() { [ 3346] | thread() { 0.804 us [ 3346] | malloc(); 0.128 us [ 3346] | pthread_setspecific(); 2.708 us [ 3346] | } /* thread */ 149.452 us [ 3336] | } /* pthread_join */ 1.684 us [ 3336] | pthread_getspecific(); 0.549 us [ 3336] | tsd_dtor(); 0.861 us [ 3336] | pthread_key_delete(); 199.848 us [ 3336] | } /* main */ """) def fixup(self, cflags, result): return result.replace('tsd_dtor();', """tsd_dtor() { 0.347 us [ 3336] | free(); 0.549 us [ 3336] | } /* tsd_dtor */""") uftrace-0.15.2/tests/t119_malloc_hook.py000066400000000000000000000006201455365734300200260ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'malloc-hook', ldflags='-ldl', result=""" # DURATION TID FUNCTION [ 4408] | main() { 0.470 us [ 4408] | malloc(); 0.390 us [ 4408] | free(); 1.512 us [ 4408] | } /* main */ """) def setup(self): self.option = '-F main' uftrace-0.15.2/tests/t120_malloc_tsd.py000066400000000000000000000017031455365734300176530ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'malloc-tsd', ldflags='-pthread -ldl', result=""" # DURATION TID FUNCTION [ 5078] | main() { 2.139 us [ 5078] | pthread_key_create(); 0.391 us [ 5078] | malloc(); 1.246 us [ 5078] | pthread_setspecific(); 55.151 us [ 5078] | pthread_create(); [ 5078] | pthread_join() { [ 5082] | thread() { 0.803 us [ 5082] | malloc(); 0.449 us [ 5082] | pthread_setspecific(); 3.608 us [ 5082] | } /* thread */ 207.839 us [ 5078] | } /* pthread_join */ 2.997 us [ 5078] | pthread_getspecific(); [ 5078] | tsd_dtor() { 0.646 us [ 5078] | free(); 1.246 us [ 5078] | } /* tsd_dtor */ 1.314 us [ 5078] | pthread_key_delete(); 280.194 us [ 5078] | } /* main */ """) def setup(self): self.option = '-F main -F thread' uftrace-0.15.2/tests/t121_malloc_fork.py000066400000000000000000000016271455365734300200300ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'malloc-fork', ldflags='-ldl', result=""" # DURATION TID FUNCTION [22300] | __cxa_atexit() { 1.328 us [22300] | } /* __cxa_atexit */ [22300] | malloc() { 0.200 us [22300] | } /* malloc */ [22300] | main() { [22300] | fork() { 108.998 us [22300] | } /* fork */ [22300] | malloc() { 0.164 us [22300] | } /* malloc */ [22300] | free() { 0.100 us [22300] | } /* free */ 123.077 us [22300] | } /* main */ [22304] | } /* fork */ [22304] | malloc() { 0.128 us [22304] | } /* malloc */ [22304] | free() { 0.081 us [22304] | } /* free */ [22304] | } /* main */ """) def setup(self): self.option = '--no-merge' uftrace-0.15.2/tests/t122_time_range2.py000066400000000000000000000024301455365734300177260ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase START=0 class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # ELAPSED FUNCTION 4.343 us | c() { 4.447 us | getpid(); 5.137 us | } /* c */ 5.436 us | } /* b */ 5.544 us | } /* a */ 5.626 us | } /* main */ """, sort='simple') def prerun(self, timeout): global START self.subcmd = 'record' record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) sp.call(record_cmd.split()) # find timestamp of function 'c' self.subcmd = 'replay' self.option = '-f elapsed -F main' replay_cmd = self.runcmd() self.pr_debug("prerun command: " + replay_cmd) p = sp.Popen(replay_cmd, shell=True, stdout=sp.PIPE, stderr=sp.PIPE) r = p.communicate()[0].decode(errors='ignore') lines = r.split('\n') if len(lines) < 5: return TestBase.TEST_DIFF_RESULT START, unit = lines[4].split()[0:2] # skip header, main, a and b (= 4) START += unit p.wait() return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '-f elapsed -r %s~' % START uftrace-0.15.2/tests/t123_backtrace.py000066400000000000000000000012011455365734300174450ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'backtrace', lang='C++', result=""" # DURATION TID FUNCTION 2.277 us [11616] | __cxa_atexit(); [11616] | main() { [11616] | a() { [11616] | b() { [11616] | c() { [11616] | foo() { 51.142 us [11616] | backtrace(); 52.363 us [11616] | } /* foo */ 52.735 us [11616] | } /* c */ 53.031 us [11616] | } /* b */ 53.317 us [11616] | } /* a */ 53.703 us [11616] | } /* main */ """) uftrace-0.15.2/tests/t124_exception.py000066400000000000000000000024271455365734300175400ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exception', lang='C++', result=""" # DURATION TID FUNCTION 2.777 us [10827] | __cxa_atexit(); [10827] | _GLOBAL__sub_I__Z3foov() { [10827] | __static_initialization_and_destruction_0() { 108.818 us [10827] | std::ios_base::Init::Init(); 0.350 us [10827] | __cxa_atexit(); 111.039 us [10827] | } /* __static_initialization_and_destruction_0 */ 111.488 us [10827] | } /* foo */ [10827] | main() { 0.078 us [10827] | foo(); [10827] | test() { [10827] | oops() { 1.752 us [10827] | __cxa_allocate_exception(); 0.088 us [10827] | std::exception::exception(); 84.367 us [10827] | } /* oops */ 84.652 us [10827] | } /* test */ 0.090 us [10827] | bar(); 85.590 us [10827] | } /* main */ """) def setup(self): self.option = '-N personality_v.' def fixup(self, cflags, result): r = result.replace("} /* oops */", """} /* oops */ 0.088 us [10827] | std::exception::~exception();""") r = r.replace("} /* main */", """} /* main */ 108.818 us [10827] | std::ios_base::Init::~Init();""") return r uftrace-0.15.2/tests/t125_report_range.py000066400000000000000000000026111455365734300202250ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase START=0 class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ Total time Self time Calls Function ========== ========== ========== ==================================== 2.160 us 0.172 us 1 main 1.988 us 0.126 us 1 a 1.862 us 0.375 us 1 b 1.487 us 0.747 us 1 c 0.740 us 0.740 us 1 getpid """, sort='report') def prerun(self, timeout): global START self.subcmd = 'record' record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) sp.call(record_cmd.split()) # find timestamp of function 'c' self.subcmd = 'replay' self.option = '-f time -F main' replay_cmd = self.runcmd() self.pr_debug("prerun command: " + replay_cmd) p = sp.Popen(replay_cmd, shell=True, stdout=sp.PIPE, stderr=sp.PIPE) r = p.communicate()[0].decode(errors='ignore') lines = r.split('\n') if len(lines) < 7: return TestBase.TEST_DIFF_RESULT START = lines[6].split()[0] # skip header, main, a, b, c and getpid (= 6) p.wait() return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'report' self.option = '-F main -r ~%s' % START uftrace-0.15.2/tests/t126_arg_regex.py000066400000000000000000000030141455365734300175000ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'allocfree', """ # DURATION TID FUNCTION 2.417 us [32130] | __monstartup(); 1.535 us [32130] | __cxa_atexit(); [32130] | main() { [32130] | alloc1(1) { [32130] | alloc2(1) { [32130] | alloc3(1) { [32130] | alloc4(1) { [32130] | alloc5(1) { 1.850 us [32130] | malloc(1); 4.284 us [32130] | } /* alloc5 */ 11.517 us [32130] | } /* alloc4 */ 12.357 us [32130] | } /* alloc3 */ 13.036 us [32130] | } /* alloc2 */ 14.543 us [32130] | } /* alloc1 */ [32130] | free1() { [32130] | free2() { [32130] | free3() { [32130] | free4() { [32130] | free5() { 1.394 us [32130] | free(); 2.970 us [32130] | } /* free5 */ 3.531 us [32130] | } /* free4 */ 4.064 us [32130] | } /* free3 */ 4.641 us [32130] | } /* free2 */ 5.271 us [32130] | } /* free1 */ 21.319 us [32130] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "alloc*@arg1"' uftrace-0.15.2/tests/t127_arg_module.py000066400000000000000000000030301455365734300176520ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'allocfree', """ # DURATION TID FUNCTION 2.417 us [32130] | __monstartup(); 1.535 us [32130] | __cxa_atexit(); [32130] | main() { [32130] | alloc1(1) { [32130] | alloc2(1) { [32130] | alloc3(1) { [32130] | alloc4(1) { [32130] | alloc5(1) { 1.850 us [32130] | malloc(1); 4.284 us [32130] | } /* alloc5 */ 11.517 us [32130] | } /* alloc4 */ 12.357 us [32130] | } /* alloc3 */ 13.036 us [32130] | } /* alloc2 */ 14.543 us [32130] | } /* alloc1 */ [32130] | free1() { [32130] | free2() { [32130] | free3() { [32130] | free4() { [32130] | free5() { 1.394 us [32130] | free(); 2.970 us [32130] | } /* free5 */ 3.531 us [32130] | } /* free4 */ 4.064 us [32130] | } /* free3 */ 4.641 us [32130] | } /* free2 */ 5.271 us [32130] | } /* free1 */ 21.319 us [32130] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "alloc*@t-allocfree,arg1"' uftrace-0.15.2/tests/t128_arg_module2.py000066400000000000000000000030131455365734300177360ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'allocfree', """ # DURATION TID FUNCTION 3.937 us [ 447] | __monstartup(); 1.909 us [ 447] | __cxa_atexit(); [ 447] | main() { [ 447] | alloc1() { [ 447] | alloc2() { [ 447] | alloc3() { [ 447] | alloc4() { [ 447] | alloc5() { 8.408 us [ 447] | malloc(1); 10.642 us [ 447] | } /* alloc5 */ 11.502 us [ 447] | } /* alloc4 */ 12.057 us [ 447] | } /* alloc3 */ 12.780 us [ 447] | } /* alloc2 */ 13.400 us [ 447] | } /* alloc1 */ [ 447] | free1() { [ 447] | free2() { [ 447] | free3() { [ 447] | free4() { [ 447] | free5() { 2.072 us [ 447] | free(); 3.951 us [ 447] | } /* free5 */ 4.561 us [ 447] | } /* free4 */ 5.151 us [ 447] | } /* free3 */ 5.713 us [ 447] | } /* free2 */ 6.341 us [ 447] | } /* free1 */ 21.174 us [ 447] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "alloc*@PLT,arg1"' uftrace-0.15.2/tests/t129_session_tid.py000066400000000000000000000023641455365734300200720ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase # Test that task.txt files with a tid in the SESS line still work class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION 62.202 us [28141] | __cxa_atexit(); [28141] | main() { [28141] | a() { [28141] | b() { [28141] | c() { 0.753 us [28141] | getpid(); 1.430 us [28141] | } /* c */ 1.915 us [28141] | } /* b */ 2.405 us [28141] | } /* a */ 3.005 us [28141] | } /* main */ """) def build(self, name, cflags='', ldflags=''): ret = TestBase.build(self, 'abc', cflags, ldflags) return ret def prerun(self, timeout): self.subcmd = 'record' record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) sp.call(record_cmd.split()) # Replace pid by tid on the SESS line to test backward-compatibility sed_cmd = 'sed -i "/SESS/s/pid/tid/g" uftrace.data/task.txt' self.pr_debug("prerun command: " + sed_cmd) sp.call(sed_cmd, shell=True) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' uftrace-0.15.2/tests/t130_thread_exec.py000066400000000000000000000020251455365734300200040ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'thread-exec', """ # DURATION TID FUNCTION [23290] | main() { 29.452 us [23290] | pthread_create(); [23292] | thread_func() { [23292] | execl() { [23290] | main() { [23290] | a() { [23290] | b() { [23290] | c() { 0.379 us [23290] | getpid(); 0.772 us [23290] | } /* c */ 1.159 us [23290] | } /* b */ 1.289 us [23290] | } /* a */ 1.461 us [23290] | } /* main */ uftrace stopped tracing with remaining functions ================================================ task: 23290 [0] main """) def build(self, name, cflags='', ldflags=''): ret = TestBase.build(self, 'abc', cflags, ldflags) ret += TestBase.build(self, self.name, cflags, ldflags + ' -pthread') return ret def setup(self): self.option = '-N ^__' uftrace-0.15.2/tests/t131_lib_dlopen.py000066400000000000000000000022451455365734300176450ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'dlopen', """1 # DURATION TID FUNCTION 1.404 us [22207] | __cxa_atexit(); [22207] | main() { 70.963 us [22207] | dlopen(); 1.546 us [22207] | dlsym(); [22207] | lib_a() { [22207] | lib_b() { 0.678 us [22207] | lib_c(); 1.301 us [22207] | } /* lib_b */ 2.104 us [22207] | } /* lib_a */ 14.446 us [22207] | dlclose(); [22207] | dlopen(); 0.844 us [22207] | dlsym(); [22207] | foo() { 19.601 us [22207] | AAA::bar(); 19.869 us [22207] | } /* foo */ 13.391 us [22207] | dlclose(); 274.723 us [22207] | } /* main */ """) def build(self, name, cflags='', ldflags=''): if TestBase.build_libabc(self, cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL if TestBase.build_libfoo(self, 'foo', cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-dlopen.c', ['libdl.so'], cflags, ldflags) uftrace-0.15.2/tests/t132_trigger_kernel.py000066400000000000000000000030351455365734300205400ustar00rootroot00000000000000#!/usr/bin/env python3 import os from runtest import TestBase # there was a problem applying depth filter if it contains kernel functions class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'openclose', serial=True, result=""" # DURATION TID FUNCTION 0.714 us [ 4435] | __monstartup(); 0.349 us [ 4435] | __cxa_atexit(); [ 4435] | main() { [ 4435] | fopen() { 6.413 us [ 4435] | sys_open(); 7.037 us [ 4435] | } /* fopen */ [ 4435] | fclose() { 8.389 us [ 4435] | sys_close(); 9.949 us [ 4435] | } /* fclose */ 17.632 us [ 4435] | } /* main */ """) def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def setup(self): self.option = '-K3 ' self.option += '-T ^sys_@kernel,depth=1 ' self.option += '-T ^__x64_@kernel,depth=1 ' self.option += '-N exit_to_usermode_loop@kernel ' self.option += '-N _*do_page_fault@kernel' def fixup(self, cflags, result): uname = os.uname() # Linux v4.17 (x86_64) changed syscall routines major, minor, release = uname[2].split('.') if uname[0] == 'Linux' and uname[4] == 'x86_64' and \ int(major) >= 5 or (int(major) == 4 and int(minor) >= 17): result = result.replace('sys_', '__x64_sys_') return result.replace(' sys_open', ' sys_openat') uftrace-0.15.2/tests/t133_long_string.py000066400000000000000000000014501455365734300200620ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'hello', """ # DURATION TID FUNCTION 62.202 us [28141] | __cxa_atexit(); [28141] | main() { 2.405 us [28141] | printf("Hello %s\\n", "01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234..."); 3.005 us [28141] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A printf@arg1/s,arg2/s' self.exearg += ' ' + "0123456789" * 10 uftrace-0.15.2/tests/t134_pic_pie.py000066400000000000000000000014511455365734300171470ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION 62.202 us [28141] | __cxa_atexit(); [28141] | main() { [28141] | a() { [28141] | b() { [28141] | c() { 0.753 us [28141] | getpid(); 1.430 us [28141] | } /* c */ 1.915 us [28141] | } /* b */ 2.405 us [28141] | } /* a */ 3.005 us [28141] | } /* main */ """) def build(self, name, cflags = '', ldflags = ''): if cflags.find('-pg') < 0: return TestBase.build(self, name, cflags + ' -fPIC', ldflags + ' -pie') else: return TestBase.build(self, name, cflags + ' -fno-PIC', ldflags + ' -no-pie') uftrace-0.15.2/tests/t135_trigger_time2.py000066400000000000000000000031271455365734300203050ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase TDIR='xxx' TIME=0 UNIT='' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', """ # DURATION TID FUNCTION [27437] | main() { [27437] | foo() { 2.241 us [27437] | mem_alloc(); [27437] | bar() { 2.183 ms [27437] | usleep(); 2.185 ms [27437] | } /* bar */ [27437] | mem_free() { 3.086 us [27437] | free(); 3.806 us [27437] | } /* mem_free */ 2.191 ms [27437] | } /* foo */ 2.192 ms [27437] | } /* main */ """, sort='simple') def prerun(self, timeout): global TIME, UNIT self.subcmd = 'record' self.option = '-F main' record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) sp.call(record_cmd.split()) # find timestamp of function 'malloc' self.subcmd = 'replay' self.option = '-F malloc' replay_cmd = self.runcmd() self.pr_debug("prerun command: " + replay_cmd) p = sp.Popen(replay_cmd, shell=True, stdout=sp.PIPE, stderr=sp.PIPE) r = p.communicate()[0].decode(errors='ignore') lines = r.split('\n') if len(lines) < 2: return TestBase.TEST_DIFF_RESULT TIME, UNIT = lines[1].split()[0:2] # skip header TIME = float(TIME) + 0.001 # for time filtering p.wait() return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '-T mem_alloc@time=%.3f%s' % (TIME, UNIT) uftrace-0.15.2/tests/t136_dynamic.py000066400000000000000000000016051455365734300171660ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION 62.202 us [28141] | __cxa_atexit(); [28141] | main() { 2.405 us [28141] | a(); 3.005 us [28141] | } /* main */ """) def build(self, name, cflags='', ldflags=''): if not TestBase.check_arch_mfentry_mnop_mcount_support(self): return TestBase.TEST_SKIP if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP if self.supported_lang['C']['cc'] == 'clang': return TestBase.TEST_SKIP cflags += ' -mfentry -mnop-mcount' cflags += ' -fno-pie -fno-plt' # workaround of build failure return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-P "a.?" --no-libcall' uftrace-0.15.2/tests/t137_kernel_tid_update.py000066400000000000000000000032571455365734300212320ustar00rootroot00000000000000#!/usr/bin/env python3 import os from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fork2', serial=True, result=""" # DURATION TID FUNCTION [19227] | main() { 328.451 us [19227] | fork(); [19227] | wait() { [19231] | } /* fork */ [19231] | open() { 17.068 us [19231] | sys_open(); 21.964 us [19231] | } /* open */ [19231] | close() { 2.537 us [19231] | sys_close(); 7.057 us [19231] | } /* close */ [19231] | } /* main */ 40.601 ms [19227] | } /* wait */ [19227] | open() { 30.832 us [19227] | sys_open(); 37.121 us [19227] | } /* open */ [19227] | close() { 2.950 us [19227] | sys_close(); 9.520 us [19227] | } /* close */ 41.010 ms [19227] | } /* main */ """) def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def setup(self): self.option = '-k -F main ' self.option += '-F sys_open*@kernel ' self.option += '-F sys_close*@kernel' def fixup(self, cflags, result): uname = os.uname() result = result.replace(' sys_open', ' sys_openat') # Linux v4.17 (x86_64) changed syscall routines major, minor, release = uname[2].split('.') if uname[0] == 'Linux' and uname[4] == 'x86_64' and \ int(major) >= 5 or (int(major) == 4 and int(minor) >= 17): result = result.replace('sys_', '__x64_sys_') return result uftrace-0.15.2/tests/t138_kernel_dynamic.py000066400000000000000000000024561455365734300205350ustar00rootroot00000000000000#!/usr/bin/env python3 import os from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'openclose', serial=True, result=""" # DURATION TID FUNCTION [ 9875] | main() { [ 9875] | fopen() { 14.416 us [ 9875] | sys_open(); 19.099 us [ 9875] | } /* fopen */ [ 9875] | fclose() { 3.380 us [ 9875] | sys_close(); 9.720 us [ 9875] | } /* fclose */ 37.051 us [ 9875] | } /* main */ """) def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def setup(self): self.option = '-k -F main ' self.option += '-P sys_open*@kernel ' self.option += '-P sys_close*@kernel' def fixup(self, cflags, result): uname = os.uname() result = result.replace(' sys_open', ' sys_openat') # Linux v4.17 (x86_64) changed syscall routines major, minor, release = uname[2].split('.') if uname[0] == 'Linux' and uname[4] == 'x86_64' and \ int(major) >= 5 or (int(major) == 4 and int(minor) >= 17): result = result.replace('sys_', '__x64_sys_') return result uftrace-0.15.2/tests/t139_kernel_dynamic2.py000066400000000000000000000023151455365734300206120ustar00rootroot00000000000000#!/usr/bin/env python3 import os from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'openclose', serial=True, result=""" # DURATION TID FUNCTION [ 9875] | main() { [ 9875] | fopen() { 14.416 us [ 9875] | sys_open(); 19.099 us [ 9875] | } /* fopen */ 9.720 us [ 9875] | fclose(); 37.051 us [ 9875] | } /* main */ """) def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS # check syscall name would corrected (for SyS_ prefix) def setup(self): self.option = "-k -P '_*sys_open@kernel'" def fixup(self, cflags, result): uname = os.uname() # Linux v4.17 (x86_64) changed syscall routines major, minor, release = uname[2].split('.') if uname[0] == 'Linux' and uname[4] == 'x86_64' and \ int(major) >= 5 or (int(major) == 4 and int(minor) >= 17): return result.replace(' sys_open', ' __x64_sys_openat') else: return result.replace(' sys_open', ' sys_openat') uftrace-0.15.2/tests/t140_dynamic_xray.py000066400000000000000000000016411455365734300202240ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION 62.202 us [28141] | __cxa_atexit(); [28141] | main() { [28141] | a() { 0.753 us [28141] | getpid(); 2.405 us [28141] | } /* a */ 3.005 us [28141] | } /* main */ """) def prerun(self, timeout): if TestBase.get_elf_machine(self) == 'arm': return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def build(self, name, cflags='', ldflags=''): old_cc = TestBase.supported_lang['C']['cc'] TestBase.supported_lang['C']['cc'] = 'clang' r = TestBase.build(self, name, '-fxray-instrument -fxray-instruction-threshold=1', '-lstdc++') TestBase.supported_lang['C']['cc'] = old_cc return r def setup(self): self.option = "-P 'a.?'" uftrace-0.15.2/tests/t141_recv_basic.py000066400000000000000000000026141455365734300176370ustar00rootroot00000000000000#!/usr/bin/env python3 import os.path import subprocess as sp from runtest import TestBase TDIR = 'xxx' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION 62.202 us [28141] | __cxa_atexit(); [28141] | main() { [28141] | a() { [28141] | b() { [28141] | c() { 0.753 us [28141] | getpid(); 1.430 us [28141] | } /* c */ 1.915 us [28141] | } /* b */ 2.405 us [28141] | } /* a */ 3.005 us [28141] | } /* main */ """) def prerun(self, timeout): self.gen_port() self.subcmd = 'recv' self.option = '-d %s --port %s' % (TDIR, self.port) self.exearg = '' recv_cmd = self.runcmd() self.pr_debug("prerun command: " + recv_cmd) self.recv_p = sp.Popen(recv_cmd.split()) self.subcmd = 'record' self.option = '--host %s --port %s' % ('localhost', self.port) self.exearg = 't-' + self.name record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '-d ' + os.path.join(TDIR, 'uftrace.data') self.exearg = '' def postrun(self, ret): self.recv_p.terminate() return ret uftrace-0.15.2/tests/t142_recv_multi.py000066400000000000000000000033701455365734300177110ustar00rootroot00000000000000#!/usr/bin/env python3 import os.path import random import subprocess as sp from runtest import TestBase TDIR = 'xxx' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION 62.202 us [28141] | __cxa_atexit(); [28141] | main() { [28141] | a() { [28141] | b() { [28141] | c() { 0.753 us [28141] | getpid(); 1.430 us [28141] | } /* c */ 1.915 us [28141] | } /* b */ 2.405 us [28141] | } /* a */ 3.005 us [28141] | } /* main */ """) def prerun(self, timeout): self.gen_port() self.subcmd = 'recv' self.option = '-d %s --port %s' % (TDIR, self.port) self.exearg = '' recv_cmd = self.runcmd() self.pr_debug('prerun command: ' + recv_cmd) self.recv_p = sp.Popen(recv_cmd.split()) # recorded but not used self.subcmd = 'record' self.option = '--host %s --port %s' % ('localhost', self.port) self.exearg = 't-' + self.name record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) # use this self.pr_debug('run another record') self.dirname = 'dir-' + str(random.randint(100000, 999999)) self.pr_debug('after randint') self.option += ' -d ' + self.dirname record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '-d %s' % os.path.join(TDIR, self.dirname) def postrun(self, ret): self.recv_p.terminate() return ret uftrace-0.15.2/tests/t143_recv_kernel.py000066400000000000000000000041241455365734300200360ustar00rootroot00000000000000#!/usr/bin/env python3 import os import random import subprocess as sp from runtest import TestBase TDIR = 'xxx' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'openclose', serial=True, result=""" # DURATION TID FUNCTION 1.088 us [18343] | __monstartup(); 0.640 us [18343] | __cxa_atexit(); [18343] | main() { [18343] | fopen() { 86.790 us [18343] | sys_open(); 89.018 us [18343] | } /* fopen */ [18343] | fclose() { 10.781 us [18343] | sys_close(); 37.325 us [18343] | } /* fclose */ 128.387 us [18343] | } /* main */ """) self.recv_p = None def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP self.gen_port() self.subcmd = 'recv' self.option = '-d %s --port %s' % (TDIR, self.port) self.exearg = '' recv_cmd = self.runcmd() self.pr_debug('prerun command: ' + recv_cmd) self.recv_p = sp.Popen(recv_cmd.split()) self.dirname = 'dir-%d' % random.randint(100000, 999999) self.subcmd = 'record' self.option = '--host %s --port %s -d %s' % ('localhost', self.port, self.dirname) self.exearg = 't-' + self.name record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '-d %s' % os.path.join(TDIR, self.dirname) def postrun(self, ret): self.recv_p.terminate() return ret def fixup(self, cflags, result): uname = os.uname() # Linux v4.17 (x86_64) changed syscall routines major, minor, release = uname[2].split('.') if uname[0] == 'Linux' and uname[4] == 'x86_64' and \ int(major) >= 5 or (int(major) == 4 and int(minor) >= 17): result = result.replace('sys_', '__x64_sys_') return result.replace(' sys_open', ' sys_openat') uftrace-0.15.2/tests/t144_longjmp2.py000066400000000000000000000011551455365734300172710ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'longjmp2', """ # DURATION TID FUNCTION 1.517 us [ 3024] | __monstartup(); 0.971 us [ 3024] | __cxa_atexit(); [ 3024] | main() { 3.588 us [ 3024] | _setjmp(); [ 3024] | foo() { [ 3024] | longjmp() { 1.637 us [ 3024] | } /* _setjmp */ [ 3024] | bar() { [ 3024] | baz() { [ 3024] | longjmp() { 0.671 us [ 3024] | } /* _setjmp */ 7.291 us [ 3024] | } /* main */ """) uftrace-0.15.2/tests/t145_longjmp3.py000066400000000000000000000021511455365734300172700ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'longjmp3', """ # DURATION TID FUNCTION 1.164 us [ 4107] | __monstartup(); 0.657 us [ 4107] | __cxa_atexit(); [ 4107] | main() { 0.705 us [ 4107] | _setjmp() = 0; 1.823 us [ 4107] | getpid(); 0.182 us [ 4107] | _setjmp() = 0; [ 4107] | foo() { [ 4107] | longjmp(1) { 8.790 us [ 4107] | } = 1; /* _setjmp */ 0.540 us [ 4107] | getpid(); [ 4107] | bar() { [ 4107] | baz() { [ 4107] | longjmp(2) { 1.282 us [ 4107] | } = 2; /* _setjmp */ 0.540 us [ 4107] | getpid(); [ 4107] | foo() { [ 4107] | longjmp(3) { 0.578 us [ 4107] | } = 3; /* _setjmp */ [ 4107] | bar() { [ 4107] | baz() { [ 4107] | longjmp(4) { 0.642 us [ 4107] | } = 4; /* _setjmp */ 18.019 us [ 4107] | } /* main */ """) def setup(self): self.option = '-A .?longjmp@arg2 -R .?setjmp@retval' uftrace-0.15.2/tests/t146_arg_std_string.py000066400000000000000000000023231455365734300205520ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'std-string', lang='C++', result=""" # DURATION TID FUNCTION [71555] | main() { 7.549 us [71555] | std_string_arg("Hello"s); 0.218 us [71555] | std_string_arg("World!"s); 0.150 us [71555] | std_string_arg("std::string support is done!"s); 0.240 us [71555] | std_string_ret::cxx11() = "Hello"s; 0.124 us [71555] | std_string_ret::cxx11() = "World!"s; 0.110 us [71555] | std_string_ret::cxx11() = "std::string support is done!"s; 10.346 us [71555] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) # To handle g++ 4.xx version def fixup(self, cflags, result): return result.replace("std_string_ret::cxx11()", "std_string_ret()") def setup(self): self.option = '-A ^std_string_arg@arg1/S ' self.option += '-R ^std_string_ret@retval/S ' self.option += '-F main -F ^std_string_ -D 1' uftrace-0.15.2/tests/t147_event_sdt.py000066400000000000000000000015361455365734300175420ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sdt', """ # DURATION TID FUNCTION 9.392 us [28141] | __monstartup(); 12.912 us [28141] | __cxa_atexit(); [28141] | main() { [28141] | foo() { [28141] | /* uftrace:event */ 2.896 us [28141] | } /* foo */ 3.017 us [28141] | } /* main */ """) def build(self, name, cflags='', ldflags=''): if not TestBase.check_arch_sdt_support(self): return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-E uftrace:* --match glob' def runcmd(self): cmd = TestBase.runcmd(self) # change it to glob matching pattern return cmd.replace('-P .', '-P "*"') uftrace-0.15.2/tests/t148_event_kernel.py000066400000000000000000000037131455365734300202300ustar00rootroot00000000000000#!/usr/bin/env python3 import os from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', serial=True, result=""" # DURATION TID FUNCTION [24464] | main() { [24464] | foo() { [24464] | mem_alloc() { 4.976 us [24464] | malloc(); 15.040 us [24464] | } /* mem_alloc */ [24464] | bar() { [24464] | usleep() { [24464] | /* sched:sched_switch (prev_comm=t-sleep ...) */ [24464] | /* sched:sched_switch (prev_comm=swapper/0 ...) */ 2.176 ms [24464] | } /* usleep */ 2.183 ms [24464] | } /* bar */ [24464] | mem_free() { 12.992 us [24464] | free(); 15.400 us [24464] | } /* mem_free */ 2.215 ms [24464] | } /* foo */ 2.216 ms [24464] | } /* main */ """) def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def setup(self): self.option = '-E sched:sched_switch@kernel' def sort(self, output, ignored=''): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] before_main = True for ln in output.split('\n'): if ln.find(' | main()') > 0: before_main = False if before_main: continue # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue # delete event specific info if ln.find('sched:sched_switch') > 0: ln = ' | /* sched:sched_switch */' func = ln.split('|', 1)[-1] result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t149_event_kernel2.py000066400000000000000000000047621455365734300203200ustar00rootroot00000000000000#!/usr/bin/env python3 import os from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fork', serial=True, result=""" # DURATION TID FUNCTION [ 6532] | /* sched:sched_process_exec (filename=t-fork pid=6532 old_pid=6532) */ [ 6532] | main() { [ 6532] | fork() { [ 6532] | /* sched:sched_process_fork (comm=t-fork pid=6532 child_comm=t-fork child_pid=6536) */ 144.501 us [ 6532] | } /* fork */ [ 6532] | wait() { [ 6532] | /* sched:sched_process_wait (comm=t-fork pid=0 prio=120) */ [ 6536] | } /* fork */ [ 6536] | a() { [ 6536] | b() { [ 6536] | c() { 1.674 us [ 6536] | getpid(); 4.648 us [ 6536] | } /* c */ 5.131 us [ 6536] | } /* b */ 5.488 us [ 6536] | } /* a */ 18.724 us [ 6536] | } /* main */ [ 6536] | /* sched:sched_process_exit (comm=t-fork pid=6536 prio=120) */ 50.274 ms [ 6532] | } /* wait */ [ 6532] | a() { [ 6532] | b() { [ 6532] | c() { 3.626 us [ 6532] | getpid(); 5.851 us [ 6532] | } /* c */ 6.217 us [ 6532] | } /* b */ 6.522 us [ 6532] | } /* a */ 50.451 ms [ 6532] | } /* main */ [ 6532] | /* sched:sched_process_exit (comm=t-fork pid=6532 prio=120) */ """) def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def setup(self): self.option = '-E sched:sched_process_*@kernel --kernel-full --event-full' def sort(self, output, ignored=''): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] before_main = True for ln in output.split('\n'): if ln.find(' | main()') > 0: before_main = False if before_main: continue # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue # delete event specific info if ln.find('sched:sched_') > 0: ln = ln.split('(', 1)[0] + '*/' func = ln.split('|', 1)[-1] result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t150_recv_event.py000066400000000000000000000030351455365734300176750ustar00rootroot00000000000000#!/usr/bin/env python3 import os.path import subprocess as sp from runtest import TestBase TDIR = 'xxx' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sdt', """ # DURATION TID FUNCTION 9.392 us [28141] | __monstartup(); 12.912 us [28141] | __cxa_atexit(); [28141] | main() { [28141] | foo() { [28141] | /* uftrace:event */ 2.896 us [28141] | } /* foo */ 3.017 us [28141] | } /* main */ """) def prerun(self, timeout): self.gen_port() self.subcmd = 'recv' self.option = '-d %s --port %s' % (TDIR, self.port) self.exearg = '' recv_cmd = self.runcmd() self.pr_debug('prerun command: ' + recv_cmd) self.recv_p = sp.Popen(recv_cmd.split()) self.subcmd = 'record' self.option = '--host %s --port %s -E uftrace:event' % ('localhost', self.port) self.exearg = 't-' + self.name record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def build(self, name, cflags='', ldflags=''): if not TestBase.check_arch_sdt_support(self): return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.subcmd = 'replay' self.option = '-E uftrace:event -d ' + os.path.join(TDIR, 'uftrace.data') self.exearg = '' def postrun(self, ret): self.recv_p.terminate() return ret uftrace-0.15.2/tests/t151_recv_runcmd.py000066400000000000000000000037241455365734300200520ustar00rootroot00000000000000#!/usr/bin/env python3 import select import subprocess as sp from runtest import TestBase TDIR = 'xxx' TMPF = 'out' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION 62.202 us [28141] | __cxa_atexit(); [28141] | main() { [28141] | a() { [28141] | b() { [28141] | c() { 0.753 us [28141] | getpid(); 1.430 us [28141] | } /* c */ 1.915 us [28141] | } /* b */ 2.405 us [28141] | } /* a */ 3.005 us [28141] | } /* main */ """) def prerun(self, timeout): self.gen_port() self.file_p = open(TMPF, 'w+') recv_cmd = [TestBase.uftrace_cmd, 'recv'] recv_cmd += TestBase.default_opt.split() recv_cmd += ['-d', TDIR, '--port', str(self.port)] recv_cmd += ['--run-cmd', '%s %s' % (TestBase.uftrace_cmd, 'replay')] self.pr_debug('prerun command: ' + ' '.join(recv_cmd)) self.recv_p = sp.Popen(recv_cmd, stdout=sp.PIPE, stderr=sp.PIPE) epolls = select.epoll() epolls.register(self.recv_p.stdout, select.EPOLLIN) record_cmd = [TestBase.uftrace_cmd, 'record'] record_cmd += TestBase.default_opt.split() record_cmd += ['--host', 'localhost', '--port', str(self.port)] if self.p_flag: record_cmd += self.p_flag.split() record_cmd += ['t-' + self.name] self.pr_debug('prerun command: ' + ' '.join(record_cmd)) sp.call(record_cmd, stderr=sp.PIPE) epolls.poll(timeout=timeout) self.recv_p.terminate() out = self.recv_p.communicate()[0].decode(errors='ignore') self.file_p.write(out) self.file_p.flush() self.file_p.close() epolls.close() return TestBase.TEST_SUCCESS def runcmd(self): # run replay at recv time and print the result now return 'cat ' + TMPF def postrun(self, ret): return ret uftrace-0.15.2/tests/t152_read_proc_statm.py000066400000000000000000000024451455365734300207110ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [32417] | main() { [32417] | a() { [32417] | b() { [32417] | /* read:proc/statm (size=6812KB, rss=780KB, shared=716KB) */ [32417] | c() { 0.479 us [32417] | getpid(); 3.014 us [32417] | } /* c */ [32417] | /* diff:proc/statm (size=+0KB, rss=+0KB, shared=+0KB) */ 16.914 us [32417] | } /* b */ 17.083 us [32417] | } /* a */ 17.873 us [32417] | } /* main */ """) def setup(self): self.option = '-F main -T b@read=proc/statm' def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue func = ln.split('|', 1)[-1] # remove actual numbers in proc.statm if func.find('read:proc/statm') > 0: func = ' /* read:proc/statm */' if func.find('diff:proc/statm') > 0: func = ' /* diff:proc/statm */' result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t153_read_page_fault.py000066400000000000000000000024051455365734300206420ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [32766] | main() { [32766] | a() { [32766] | b() { [32766] | /* read:page-fault (major=0, minor=188) */ [32766] | c() { 0.609 us [32766] | getpid(); 13.722 us [32766] | } /* c */ [32766] | /* diff:page-fault (major=+0, minor=+1) */ 24.950 us [32766] | } /* b */ 25.564 us [32766] | } /* a */ 26.963 us [32766] | } /* main */ """) def setup(self): self.option = '-F main -T b@read=page-fault' def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue func = ln.split('|', 1)[-1] # remove actual numbers in page-fault if func.find('read:page-fault') > 0: func = ' /* read:page-fault */' if func.find('diff:page-fault') > 0: func = ' /* diff:page-fault */' result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t154_keep_pid.py000066400000000000000000000013511455365734300173200ustar00rootroot00000000000000from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'daemon', """ # DURATION TID FUNCTION [22067] | main() { [22067] | daemon() { [22072] | } /* daemon */ [22072] | a() { [22072] | b() { [22072] | c() { 1.196 us [22072] | getpid(); 3.268 us [22072] | } /* c */ 3.555 us [22072] | } /* b */ 3.798 us [22072] | } /* a */ 22.759 us [22072] | } /* main */ uftrace stopped tracing with remaining functions ================================================ task: 22067 [1] daemon [0] main """) def setup(self): self.option = '--keep-pid --no-pager' uftrace-0.15.2/tests/t155_trigger_finish.py000066400000000000000000000014751455365734300205530ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [ 4131] | main() { [ 4131] | a() { [ 4131] | b() { [ 4131] | c() { [ 4131] | getpid() { [ 4131] | /* linux:task-exit */ uftrace stopped tracing with remaining functions ================================================ task: 4131 [4] getpid [3] c [2] b [1] a [0] main """) def setup(self): self.option = '-F main -T getpid@finish' def fixup(self, cflags, result): return result.replace(""" getpid() { [ 4131] | /* linux:task-exit */""", " getpid() {") uftrace-0.15.2/tests/t156_trigger_finish2.py000066400000000000000000000020611455365734300206260ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', """ # DURATION TID FUNCTION [ 6565] | main() { 2.234 us [ 6565] | operator new(); 0.897 us [ 6565] | ns::ns1::foo::foo(); [ 6565] | ns::ns1::foo::bar() { [ 6565] | ns::ns1::foo::bar1() { [ 6565] | ns::ns1::foo::bar2() { [ 6565] | ns::ns1::foo::bar3() { [ 6565] | /* linux:task-exit */ uftrace stopped tracing with remaining functions ================================================ task: 6565 [4] ns::ns1::foo::bar3 [3] ns::ns1::foo::bar2 [2] ns::ns1::foo::bar1 [1] ns::ns1::foo::bar [0] main """, lang='C++') def setup(self): self.option = '-F main -T ns::ns1::foo::bar3@finish' def fixup(self, cflags, result): return result.replace("""ns::ns1::foo::bar3() { [ 6565] | /* linux:task-exit */""", "ns::ns1::foo::bar3() {") uftrace-0.15.2/tests/t157_script_python.py000066400000000000000000000014041455365734300204470ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', '5') def prerun(self, timeout): self.subcmd = 'script' self.option = '-S %s/scripts/count.py --record' % self.basedir script_cmd = self.runcmd() self.pr_debug('prerun command: ' + script_cmd) p = sp.Popen(script_cmd.split(), stdout=sp.PIPE, stderr=sp.PIPE) if p.communicate()[1].decode(errors='ignore').startswith('WARN:'): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def setup(self): self.option = '-F main -S %s/scripts/count.py' % self.basedir def sort(self, output): return output.strip() uftrace-0.15.2/tests/t158_report_diff_policy1.py000066400000000000000000000043171455365734300215140ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase XDIR='xxx' YDIR='yyy' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'diff', """ # # uftrace diff # [0] base: xxx (from uftrace record -d xxx -F main tests/t-diff 0 ) # [1] diff: yyy (from uftrace record -d yyy -F main tests/t-diff 1 ) # Total time Self time Calls Function =========== =========== =========== ==================== -1.495 ms -1.495 ms +4 usleep -320.169 us -1.968 us +2 bar -1.270 ms -2.370 us +1 foo +0.359 us +0.359 us +0 atoi -1.499 ms -0.157 us +0 main """) def prerun(self, timeout): self.subcmd = 'record' self.option = '-d %s -F main' % XDIR self.exearg = 't-' + self.name + ' 0' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) self.option = '-d %s -F main' % YDIR self.exearg = 't-' + self.name + ' 1' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'report' self.option = '--diff-policy compact,no-percent,abs -s call' # new default self.exearg = '-d %s --diff %s' % (XDIR, YDIR) def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.startswith('#') or ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] # total unit self unit call function if line[-1].startswith('__'): continue result.append('%s %s' % (line[-2], line[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t159_report_diff_policy2.py000066400000000000000000000054501455365734300215150ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase XDIR='xxx' YDIR='yyy' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'diff', """ # # uftrace diff # [0] base: xxx (from uftrace record -d yyy -F main tests/t-diff 1 ) # [1] diff: yyy (from uftrace record -d xxx -F main tests/t-diff 0 ) # Total time (diff) Self time (diff) Calls (diff) Function =================================== =================================== ================================ ==================== 0.965 us 0.942 us +0.023 us 0.965 us 0.942 us +0.023 us 1 1 +0 atoi 2.735 ms 1.219 ms +1.516 ms 3.370 us 1.528 us +1.842 us 1 1 +0 main 2.501 ms 1.216 ms +1.284 ms 4.159 us 0.950 us +3.209 us 2 1 -1 foo 481.377 us 156.153 us +325.224 us 3.160 us 0.557 us +2.603 us 3 1 -2 bar 2.724 ms 1.215 ms +1.509 ms 2.724 ms 1.215 ms +1.509 ms 6 2 -4 usleep """) def prerun(self, timeout): self.subcmd = 'record' self.option = '-d %s -F main' % XDIR self.exearg = 't-' + self.name + ' 0' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) self.option = '-d %s -F main' % YDIR self.exearg = 't-' + self.name + ' 1' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'report' self.option = '--diff-policy full,no-abs -s call,func' self.exearg = '-d %s --diff %s' % (YDIR, XDIR) def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.startswith('#') or ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] # tT/0 unit tT/1 unit tT/d unit tS/0 unit tS/1 unit tS/d unit call/0 call/1 call/d function if line[-1].startswith('__'): continue result.append('%s %s %s %s' % (line[-4], line[-3], line[-2], line[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t160_report_diff_policy3.py000066400000000000000000000042771455365734300215140ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase XDIR='xxx' YDIR='yyy' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'diff', """ # # uftrace diff # [0] base: xxx (from uftrace record -d xxx -F main tests/t-diff 0 ) # [1] diff: yyy (from uftrace record -d yyy -F main tests/t-diff 1 ) # Total time Self time Calls Function =========== =========== =========== ==================== +2.99% +2.99% +0 atoi +195.87% +104.40% +2 bar +95.74% +16.14% +1 foo +113.47% +88.88% +0 main +113.96% +113.96% +4 usleep """) def prerun(self, timeout): self.subcmd = 'record' self.option = '-d %s -F main' % XDIR self.exearg = 't-' + self.name + ' 0' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) self.option = '-d %s -F main' % YDIR self.exearg = 't-' + self.name + ' 1' record_cmd = self.runcmd() self.pr_debug('prerun command: %s' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'report' self.option = '--diff-policy percent -s func' self.exearg = '-d %s --diff %s' % (XDIR, YDIR) def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.startswith('#') or ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] # total percent self percent call function if line[-1].startswith('__'): continue result.append('%s %s' % (line[-2], line[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t161_pltbind_now.py000066400000000000000000000007151455365734300200600ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'openclose', """ # DURATION TID FUNCTION [15973] | main() { 5.903 us [15973] | fopen(); 4.374 us [15973] | fclose(); 15.262 us [15973] | } /* main */ """) def build(self, name, cflags='', ldflags=''): return TestBase.build(self, name, cflags, ldflags + " -Wl,-z,now -Wl,-z,relro") uftrace-0.15.2/tests/t162_pltbind_now_pie.py000066400000000000000000000007671455365734300207250ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'openclose', """ # DURATION TID FUNCTION [15973] | main() { 5.903 us [15973] | fopen(); 4.374 us [15973] | fclose(); 15.262 us [15973] | } /* main */ """) def build(self, name, cflags='', ldflags=''): return TestBase.build(self, name, cflags + " -fpie", ldflags + " -Wl,-pie,-z,now,-z,relro") uftrace-0.15.2/tests/t163_event_sched.py000066400000000000000000000015771455365734300200410ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', serial=True, result=""" # DURATION TID FUNCTION [ 395] | main() { [ 395] | foo() { [ 395] | bar() { [ 395] | usleep() { 2.088 ms [ 395] | /* linux:schedule */ 2.105 ms [ 395] | } /* usleep */ 2.109 ms [ 395] | } /* bar */ 2.120 ms [ 395] | } /* foo */ 2.121 ms [ 395] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_dependency(self, 'perf_context_switch'): return TestBase.TEST_SKIP if not TestBase.check_perf_paranoid(self): return TestBase.TEST_SKIP return TestCase.TEST_SUCCESS def setup(self): self.option = '-D 2 -F main -F bar -E linux:schedule' uftrace-0.15.2/tests/t164_report_sched.py000066400000000000000000000025661455365734300202330ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sort', serial=True, result=""" Total time Self time Calls Function ========== ========== ========== ==================================== 1.152 ms 71.683 us 1 main 1.080 ms 1.813 us 1 bar 1.078 ms 2.892 us 1 usleep 1.075 ms 1.075 ms 1 linux:schedule 70.176 us 70.176 us 1 __monstartup 37.525 us 1.137 us 2 foo 36.388 us 36.388 us 6 loop 1.200 us 1.200 us 1 __cxa_atexit """, sort='report') def prerun(self, timeout): if not TestBase.check_dependency(self, 'perf_context_switch'): return TestBase.TEST_SKIP if not TestBase.check_perf_paranoid(self): return TestBase.TEST_SKIP self.subcmd = 'record' self.option = '-E linux:schedule' record_cmd = TestBase.runcmd(self) self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'report' self.option = '-E linux:schedule --no-sched-preempt' def runcmd(self): cmd = TestBase.runcmd(self) return cmd.replace('--no-event', '') uftrace-0.15.2/tests/t165_graph_sched.py000066400000000000000000000024711455365734300200150ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase FUNC='main' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sort', serial=True, result=""" # Function Call Graph for 'main' (session: 54047ea45c46ad91) =============== BACKTRACE =============== backtrace #0: hit 1, time 10.329 ms [0] main (0x4004e0) ========== FUNCTION CALL GRAPH ========== 10.329 ms : (1) main 53.100 us : +-(2) foo 50.745 us : | (6) loop : | 10.150 ms : +-(1) bar 10.102 ms : (1) usleep 10.088 ms : (1) linux:schedule """, sort='graph') def prerun(self, timeout): if not TestBase.check_dependency(self, 'perf_context_switch'): return TestBase.TEST_SKIP if not TestBase.check_perf_paranoid(self): return TestBase.TEST_SKIP self.subcmd = 'record' self.option = '-E linux:schedule' self.exearg = 't-' + self.name record_cmd = TestBase.runcmd(self) self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'graph' self.option = '' self.exearg = 'main' def runcmd(self): cmd = TestBase.runcmd(self) return cmd.replace('--no-event', '') uftrace-0.15.2/tests/t166_dump_sched.py000066400000000000000000000050151455365734300176570ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', serial=True, result=""" {"traceEvents":[ {"ts":0,"ph":"M","pid":306,"name":"process_name","args":{"name":"[306] t-sleep"}}, {"ts":0,"ph":"M","pid":306,"name":"thread_name","args":{"name":"[306] t-sleep"}}, {"ts":112150305218.363,"ph":"B","pid":306,"name":"__monstartup"}, {"ts":112150305220.090,"ph":"E","pid":306,"name":"__monstartup"}, {"ts":112150305224.313,"ph":"B","pid":306,"name":"__cxa_atexit"}, {"ts":112150305225.219,"ph":"E","pid":306,"name":"__cxa_atexit"}, {"ts":112150305226.496,"ph":"B","pid":306,"name":"main"}, {"ts":112150305226.752,"ph":"B","pid":306,"name":"foo"}, {"ts":112150305226.825,"ph":"B","pid":306,"name":"mem_alloc"}, {"ts":112150305226.921,"ph":"B","pid":306,"name":"malloc"}, {"ts":112150305227.641,"ph":"E","pid":306,"name":"malloc"}, {"ts":112150305228.173,"ph":"E","pid":306,"name":"mem_alloc"}, {"ts":112150305228.317,"ph":"B","pid":306,"name":"bar"}, {"ts":112150305228.436,"ph":"B","pid":306,"name":"usleep"}, {"ts":112150305241.755,"ph":"B","pid":306,"name":"linux:schedule"}, {"ts":112150307301.727,"ph":"E","pid":306,"name":"linux:schedule"}, {"ts":112150307318.143,"ph":"E","pid":306,"name":"usleep"}, {"ts":112150307321.099,"ph":"E","pid":306,"name":"bar"}, {"ts":112150307322.007,"ph":"B","pid":306,"name":"mem_free"}, {"ts":112150307323.132,"ph":"B","pid":306,"name":"free"}, {"ts":112150307328.403,"ph":"E","pid":306,"name":"free"}, {"ts":112150307328.615,"ph":"E","pid":306,"name":"mem_free"}, {"ts":112150307328.742,"ph":"E","pid":306,"name":"foo"}, {"ts":112150307328.905,"ph":"E","pid":306,"name":"main"} ], "displayTimeUnit": "ns", "metadata": { "command_line":"uftrace record -d xxx -E linux:schedule t-sleep ", "recorded_time":"Fri Aug 25 14:23:29 2017" } } """, sort='chrome') def prerun(self, timeout): if not TestBase.check_dependency(self, 'perf_context_switch'): return TestBase.TEST_SKIP if not TestBase.check_perf_paranoid(self): return TestBase.TEST_SKIP self.subcmd = 'record' self.option = '-E linux:schedule' record_cmd = TestBase.runcmd(self) self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'dump' self.option = '-F main --chrome' def runcmd(self): cmd = TestBase.runcmd(self) return cmd.replace('--no-event', '') uftrace-0.15.2/tests/t167_recv_sched.py000066400000000000000000000040001455365734300176430ustar00rootroot00000000000000#!/usr/bin/env python3 import os.path import subprocess as sp from runtest import TestBase TDIR = 'xxx' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', serial=True, result=""" # DURATION TID FUNCTION [ 395] | main() { [ 395] | foo() { [ 395] | mem_alloc() { 1.328 us [ 395] | malloc(); 1.924 us [ 395] | } /* mem_alloc */ [ 395] | bar() { [ 395] | usleep() { 2.088 ms [ 395] | /* linux:schedule */ 2.105 ms [ 395] | } /* usleep */ 2.109 ms [ 395] | } /* bar */ [ 395] | mem_free() { 3.137 us [ 395] | free(); 3.783 us [ 395] | } /* mem_free */ 2.120 ms [ 395] | } /* foo */ 2.121 ms [ 395] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_dependency(self, 'perf_context_switch'): return TestBase.TEST_SKIP if not TestBase.check_perf_paranoid(self): return TestBase.TEST_SKIP self.gen_port() self.subcmd = 'recv' self.option = '-d %s --port %s' % (TDIR, self.port) self.exearg = '' recv_cmd = TestBase.runcmd(self) self.pr_debug('prerun command: ' + recv_cmd) self.recv_p = sp.Popen(recv_cmd.split()) self.subcmd = 'record' self.option = '--host %s --port %s ' % ('localhost', self.port) self.option += '-E %s' % 'linux:schedule' self.exearg = 't-' + self.name record_cmd = TestBase.runcmd(self) self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '-d ' + os.path.join(TDIR, 'uftrace.data') self.exearg = '' def runcmd(self): cmd = TestBase.runcmd(self) return cmd.replace('--no-event', '') def postrun(self, ret): self.recv_p.terminate() return ret uftrace-0.15.2/tests/t168_lib_nested.py000066400000000000000000000017361455365734300176640ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'nest-libcall', """1 # DURATION TID FUNCTION [ 5363] | main() { [ 5363] | lib_a() { 0.538 us [ 5363] | getpid(); 2.793 us [ 5363] | } /* lib_a */ [ 5363] | foo() { 9.405 us [ 5363] | AAA::bar(); 17.133 us [ 5363] | } /* foo */ 21.093 us [ 5363] | } /* main */ """) def build(self, name, cflags='', ldflags=''): if TestBase.build_libabc(self, '', '') != 0: return TestBase.TEST_BUILD_FAIL if TestBase.build_libfoo(self, 'foo', '', '') != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-nest-libcall.c', ['libabc_test_lib.so', 'libfoo.so'], cflags, ldflags) def setup(self): self.option = '-D3 --nest-libcall' uftrace-0.15.2/tests/t169_script_args.py000066400000000000000000000021171455365734300200670ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase FILE='script.py' script = """ def uftrace_entry(ctx): if "args" in ctx: print("%s(%s)" % (ctx["name"], ctx["args"][0])) def uftrace_exit(ctx): pass """ class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'openclose', 'fopen(/dev/null)') def prerun(self, timeout): script_cmd = '%s script' % (TestBase.uftrace_cmd) p = sp.Popen(script_cmd.split(), stdout=sp.PIPE, stderr=sp.PIPE) if p.communicate()[1].decode(errors='ignore').startswith('WARN:'): return TestBase.TEST_SKIP f = open(FILE, 'w') f.write(script) f.close() self.subcmd = 'record' self.option = '-A fopen@arg1/s' record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) sp.call(record_cmd.split(), stdout=sp.PIPE) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'script' self.option = '-S ' + FILE def sort(self, output): return output.strip() uftrace-0.15.2/tests/t170_script_filter.py000066400000000000000000000022231455365734300204060ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase FILE='script.py' script = """ UFTRACE_FUNCS = [ "a", "b" ] def uftrace_entry(ctx): print("%s enter" % (ctx["name"])) def uftrace_exit(ctx): print("%s exit" % (ctx["name"])) """ class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ a enter b enter b exit a exit """) def prerun(self, timeout): script_cmd = '%s script' % (TestBase.uftrace_cmd) p = sp.Popen(script_cmd.split(), stdout=sp.PIPE, stderr=sp.PIPE) if p.communicate()[1].decode(errors='ignore').startswith('WARN:'): return TestBase.TEST_SKIP f = open(FILE, 'w') f.write(script) f.close() self.subcmd = 'record' self.option = '' self.exearg = 't-' + self.name record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'script' self.option = '-S ' + FILE self.exearg = '' def sort(self, output): return output.strip() uftrace-0.15.2/tests/t171_script_option.py000066400000000000000000000021131455365734300204300ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase FILE='script.py' script = """ # uftrace-option: -A fopen@arg1/s def uftrace_entry(ctx): if "args" in ctx: print("%s(%s)" % (ctx["name"], ctx["args"][0])) def uftrace_exit(ctx): pass """ class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'openclose', 'fopen(/dev/null)') def prerun(self, timeout): script_cmd = '%s script' % (TestBase.uftrace_cmd) p = sp.Popen(script_cmd.split(), stdout=sp.PIPE, stderr=sp.PIPE) if p.communicate()[1].decode(errors='ignore').startswith('WARN:'): return TestBase.TEST_SKIP f = open(FILE, 'w') f.write(script) f.close() return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'script' self.option = '-S %s --record' % FILE def sort(self, output): i = 0 # skip warning for '-finstrument-functions' if output.startswith('uftrace: -A or -R might not work'): i = 1 return output.strip().split('\n')[i] uftrace-0.15.2/tests/t172_trigger_filter.py000066400000000000000000000013601455365734300205500ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION [ 5908] | main() { 7.545 us [ 5908] | operator new(); 0.377 us [ 5908] | ns::ns1::foo::foo(); [ 5908] | ns::ns1::foo::bar() { 2.694 us [ 5908] | ns::ns1::foo::bar1(); 1.834 us [ 5908] | free(); 5.574 us [ 5908] | } /* ns::ns1::foo::bar */ 1.540 us [ 5908] | operator delete(); 0.154 us [ 5908] | operator new(); 0.177 us [ 5908] | operator delete(); 25.925 us [ 5908] | } /* main */ """) def setup(self): self.option = '-T main@filter,depth=3 -T ^ns::ns2@notrace' uftrace-0.15.2/tests/t173_trigger_args.py000066400000000000000000000023141455365734300202200ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'namespace', lang="C++", result=""" # DURATION TID FUNCTION [ 5943] | main(1) { 3.800 us [ 5943] | operator new(); 0.310 us [ 5943] | ns::ns1::foo::foo(); [ 5943] | ns::ns1::foo::bar() { 2.523 us [ 5943] | ns::ns1::foo::bar1(); 1.627 us [ 5943] | free(); 5.152 us [ 5943] | } /* ns::ns1::foo::bar */ 1.240 us [ 5943] | operator delete(); 0.203 us [ 5943] | operator new(); 0.102 us [ 5943] | ns::ns2::foo::foo(); [ 5943] | ns::ns2::foo::bar() { 0.860 us [ 5943] | ns::ns2::foo::bar1(); 0.215 us [ 5943] | free(); 1.895 us [ 5943] | } /* ns::ns2::foo::bar */ 0.237 us [ 5943] | operator delete(); 21.882 us [ 5943] | } /* main */ """, sort='simple') def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-T main@filter,depth=3,arg1' uftrace-0.15.2/tests/t174_replay_filter_kernel.py000066400000000000000000000025561455365734300217530ustar00rootroot00000000000000#!/usr/bin/env python3 import os import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'openclose', serial=True, result=""" # DURATION TID FUNCTION [18343] | main() { [18343] | fopen() { 86.790 us [18343] | sys_open(); 89.018 us [18343] | } /* fopen */ 37.325 us [18343] | fclose(); 128.387 us [18343] | } /* main */ """) def prerun(self, timeout): if os.geteuid() != 0: return TestBase.TEST_SKIP if os.path.exists('/.dockerenv'): return TestBase.TEST_SKIP self.subcmd = 'record' self.option = '-k' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '-F main -D2 -F sys_open*@kernel' def fixup(self, cflags, result): uname = os.uname() # Linux v4.17 (x86_64) changed syscall routines major, minor, release = uname[2].split('.') if uname[0] == 'Linux' and uname[4] == 'x86_64' and \ int(major) >= 5 or (int(major) == 4 and int(minor) >= 17): result = result.replace('sys_', '__x64_sys_') return result.replace(' sys_open', ' sys_openat') uftrace-0.15.2/tests/t175_filter_time_read.py000066400000000000000000000032201455365734300210360ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', """ # DURATION TID FUNCTION [18219] | main() { [18219] | foo() { [18219] | /* read:proc/statm (size=6812KB, rss=784KB, shared=716KB) */ [18219] | bar() { [18219] | /* read:proc/statm (size=6812KB, rss=784KB, shared=716KB) */ 2.093 ms [18219] | usleep(); [18219] | /* diff:proc/statm (size=+0KB, rss=+0KB, shared=+0KB) */ 2.095 ms [18219] | } /* bar */ [18219] | /* diff:proc/statm (size=+0KB, rss=+0KB, shared=+0KB) */ 2.106 ms [18219] | } /* foo */ 2.107 ms [18219] | } /* main */ """) def setup(self): self.option = "-F main -t 1ms -T '(foo|bar)@read=proc/statm' " self.option += "-E read:* -E diff:*" def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue func = ln.split('|', 1)[-1] # remove actual numbers in proc.statm if func.find('read:proc/statm') > 0: func = ' /* read:proc/statm */' if func.find('diff:proc/statm') > 0: func = ' /* diff:proc/statm */' result.append(func) return '\n'.join(result) def fixup(self, cflags, result): return result.replace('usleep();', """usleep() { 2.090 ms [18219] | /* linux:schedule */ 2.093 ms [18219] | } /* usleep */""") uftrace-0.15.2/tests/t176_arg_fptr.py000066400000000000000000000041511455365734300173510ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'thread', ldflags='-pthread', result=""" # DURATION TID FUNCTION [ 1429] | main() { [ 1429] | pthread_create(&foo) { 44.296 us [ 1429] | } /* pthread_create */ [ 1429] | pthread_create(&foo) { 24.726 us [ 1429] | } /* pthread_create */ [ 1429] | pthread_create(&foo) { 21.086 us [ 1429] | } /* pthread_create */ [ 1429] | pthread_create(&foo) { 20.720 us [ 1429] | } /* pthread_create */ [ 1429] | pthread_join() { [ 1430] | foo() { [ 1430] | a() { [ 1430] | b() { [ 1430] | c() { 2.880 us [ 1430] | } /* c */ 3.793 us [ 1430] | } /* b */ 4.620 us [ 1430] | } /* a */ 96.966 us [ 1430] | } /* foo */ 340.217 us [ 1429] | } /* pthread_join */ [ 1429] | pthread_join() { [ 1431] | foo() { [ 1431] | a() { [ 1431] | b() { [ 1431] | c() { 0.444 us [ 1431] | } /* c */ 1.333 us [ 1431] | } /* b */ 2.186 us [ 1431] | } /* a */ 63.205 us [ 1431] | } /* foo */ 100.046 us [ 1429] | } /* pthread_join */ [ 1429] | pthread_join() { [ 1432] | foo() { [ 1432] | a() { [ 1432] | b() { [ 1432] | c() { 0.420 us [ 1432] | } /* c */ 1.210 us [ 1432] | } /* b */ 2.134 us [ 1432] | } /* a */ 169.879 us [ 1432] | } /* foo */ 27.470 us [ 1429] | } /* pthread_join */ [ 1429] | pthread_join() { [ 1433] | foo() { [ 1433] | a() { [ 1433] | b() { [ 1433] | c() { 0.577 us [ 1433] | } /* c */ 1.717 us [ 1433] | } /* b */ 2.860 us [ 1433] | } /* a */ 121.139 us [ 1433] | } /* foo */ 0.390 us [ 1429] | } /* pthread_join */ 658.759 us [ 1429] | } /* main */ """) def setup(self): self.option = '--no-merge -T pthread_create@arg3/p' uftrace-0.15.2/tests/t177_report_diff_policy4.py000066400000000000000000000042571455365734300215230ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase XDIR='xxx' YDIR='yyy' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'diff', """ # # uftrace diff # [0] base: xxx (from uftrace record -d xxx -F main tests/t-diff 0 ) # [1] diff: yyy (from uftrace record -d yyy -F main tests/t-diff 1 ) # Total time Self time Calls Function =========== =========== =========== ==================== -0.092 us -0.092 us +0 atoi -315.716 us -2.259 us +2 bar -1.318 ms -3.505 us +1 foo -1.548 ms -1.632 us +0 main -1.540 ms -1.540 ms +4 usleep """) def prerun(self, timeout): self.subcmd = 'record' self.option = '-d %s -F main' % XDIR self.exearg = 't-' + self.name + ' 0' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) self.option = '-d %s -F main' % YDIR self.exearg = 't-' + self.name + ' 1' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'report' self.option = '--diff-policy compact -s func' self.exearg = '-d %s --diff %s' % (XDIR, YDIR) def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.startswith('#') or ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] # tT unit tS unit call function if line[-1].startswith('__'): continue result.append('%s %s' % (line[-2], line[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t178_arg_auto1.py000066400000000000000000000010771455365734300174350ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'autoargs', result="""hello # DURATION TID FUNCTION [16523] | main() { 2.670 us [16523] | strlen("autoargs test") = 13; 1.353 us [16523] | calloc(); 1.150 us [16523] | free(); 1.336 us [16523] | strcmp("hello", "hello") = 0; 4.017 us [16523] | puts(); 18.574 us [16523] | } /* main */ """) def setup(self): self.option = '-F main -A ^str -R ^str' self.exearg += ' hello' uftrace-0.15.2/tests/t179_arg_auto2.py000066400000000000000000000017361455365734300174410ustar00rootroot00000000000000#!/usr/bin/env python3 import re from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'autoargs', result="""hello # DURATION TID FUNCTION [16523] | main() { 2.670 us [16523] | strlen("autoargs test") = 13; 1.353 us [16523] | calloc(1, 14) = 0xADDR; 1.150 us [16523] | free(0xADDR); 1.336 us [16523] | strcmp("hello", "hello") = 0; 4.017 us [16523] | puts("hello") = 6; 18.574 us [16523] | } /* main */ """) def setup(self): self.option = '-F main --auto-args' self.exearg += ' hello' def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue line = ln.split('|', 1)[-1] func = re.sub(r'0x[0-9a-f]+', '0xADDR', line) result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t180_arg_auto3.py000066400000000000000000000022361455365734300174260ustar00rootroot00000000000000#!/usr/bin/env python3 import re from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'autoargs', result="""hello # DURATION TID FUNCTION [16523] | main() { 2.670 us [16523] | strlen("autoargs test") = 13; 1.353 us [16523] | calloc(14) = 0xADDR; 1.150 us [16523] | free(0xADDR); 1.336 us [16523] | strcmp("hello", "hello") = 0; 4.017 us [16523] | puts("hello") = 6; 18.574 us [16523] | } /* main */ """) # override calloc() to save 2nd argument only def setup(self): self.option = '-F main -A calloc@arg2 --auto-args' self.exearg += ' hello' def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue # ignore uftrace message on -finstrument-functions if ln.startswith('uftrace:'): continue line = ln.split('|', 1)[-1] func = re.sub(r'0x[0-9a-f]+', '0xADDR', line) result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t181_graph_full.py000066400000000000000000000023651455365734300176710ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'forkexec', result=""" # Function Call Graph for 't-abc' (session: 327202376e209585) ========== FUNCTION CALL GRAPH ========== 5.824 us : (1) t-abc 5.824 us : (1) main 5.411 us : (1) a 5.141 us : (1) b 4.670 us : (1) c 0.967 us : (1) getpid # Function Call Graph for 't-forkexec' (session: f34056bd485963b3) ========== FUNCTION CALL GRAPH ========== 3.679 ms : (1) t-forkexec 3.679 ms : (1) main 127.172 us : +-(1) fork : | 3.527 ms : +-(1) waitpid : | : +-(1) execl """, sort='graph') def build(self, name, cflags='', ldflags=''): ret = TestBase.build(self, 'abc', cflags, ldflags) ret += TestBase.build(self, self.name, cflags, ldflags) return ret def prepare(self): self.subcmd = 'record' self.option = '-N __monstartup -N __cxa_atexit' return self.runcmd() def setup(self): self.subcmd = 'graph' self.option = '' self.exearg = '' def fixup(self, cflags, result): return result.replace("readlink", """memset : | 9.814 us : +-(1) readlink""") uftrace-0.15.2/tests/t182_thread_exit.py000066400000000000000000000017711455365734300200470ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'thread-exit', ldflags='-pthread', result=""" 1.000000 1.000000 # DURATION TID FUNCTION [26832] | main() { [26832] | pthread_create() { 51.697 us [26832] | } /* pthread_create */ [26832] | pthread_create() { 32.395 us [26832] | } /* pthread_create */ [26832] | pthread_join() { [26836] | thread_main() { [26836] | printf() { 17.092 us [26836] | } /* printf */ [26836] | pthread_exit() { [26837] | thread_main() { [26837] | printf() { 5.480 us [26837] | } /* printf */ [26837] | pthread_exit() { 362.442 us [26832] | } /* pthread_join */ [26832] | pthread_join() { 1.000 us [26832] | } /* pthread_join */ 457.662 us [26832] | } /* main */ """) def setup(self): self.option = '--no-merge' uftrace-0.15.2/tests/t183_info_quote.py000066400000000000000000000035541455365734300177210ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'hello', """ # system information # ================== # program version : uftrace v0.8.1-133-g7f71 # recorded on : Mon Nov 27 09:40:31 2017 # cmdline : ../uftrace record --no-pager --no-event --libmcount-path=.. t-hello \\"uftrace\\" # cpu info : Intel(R) Core(TM) i7-3930K CPU @ 3.20GHz # number of cpus : 12 / 12 (online / possible) # memory info : 13.2 / 23.5 GB (free / total) # system load : 3.25 / 3.17 / 3.11 (1 / 5 / 15 min) # kernel version : Linux 4.13.11-1-ARCH # hostname : sejong # distro : "Arch Linux" # # process information # =================== # number of tasks : 1 # task list : 10217 # exe image : /home/namhyung/project/uftrace/tests/t-hello # build id : 7fde527c74f398c5f48b5ec30173d2c17366dd90 # exit status : exited with code: 0 # elapsed time : 0.004278080 sec # cpu time : 0.002 / 0.002 sec (sys / user) # context switch : 1 / 0 (voluntary / involuntary) # max rss : 3284 KB # page fault : 0 / 197 (major / minor) # disk iops : 0 / 16 (read / write)""") def prerun(self, timeout): self.subcmd = 'record' self.exearg += ' "uftrace"' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) f = open('/dev/null') sp.call(record_cmd.split(), stdout=f, stderr=f) f.close() return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'info' self.exearg = '' def sort(self, output): for ln in output.split('\n'): if ln.startswith('# cmdline'): return ln.split()[-1] return '' uftrace-0.15.2/tests/t184_arg_enum.py000066400000000000000000000030161455365734300173400ustar00rootroot00000000000000#!/usr/bin/env python3 import re from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'mmap', result=""" # DURATION TID FUNCTION [14885] | main() { [14885] | foo() { 9.397 us [14885] | open("/dev/zero", O_RDONLY) = FD; 3.802 us [14885] | mmap(0, 4096, PROT_READ, MAP_ANON|MAP_PRIVATE, FD, 0) = 0xADDR; 4.119 us [14885] | mprotect(0xADDR, 4096, PROT_NONE) = 0; 6.183 us [14885] | munmap(0xADDR, 4096) = 0; 3.120 us [14885] | close(FD) = 0; 36.529 us [14885] | } /* foo */ 37.849 us [14885] | } /* main */ """) def setup(self): self.option = '-F main --auto-args' def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue line = ln.split('|', 1)[-1] # address might be different, so replace it as 0xADDR for comparison. func = re.sub(r'0x[0-9a-f]+', '0xADDR', line) # fd number might be different, so replace it as FD for comparison. func = re.sub(r'O_RDONLY\) = [0-9]+', 'O_RDONLY) = FD', func) func = re.sub(r'MAP_PRIVATE, [0-9]+, 0', 'MAP_PRIVATE, FD, 0', func) func = re.sub(r'close\([0-9]+\)', 'close(FD)', func) result.append(func) return '\n'.join(result) def fixup(self, cflags, result): return result.replace('5', '4') uftrace-0.15.2/tests/t185_exception2.py000066400000000000000000000014451455365734300176300ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exception2', lang='C++', result=""" # DURATION TID FUNCTION [ 25279] | _GLOBAL__sub_I__Z3foov() { [ 25279] | __static_initialization_and_destruction_0() { 51.124 us [ 25279] | std::ios_base::Init::Init(); 55.299 us [ 25279] | } /* __static_initialization_and_destruction_0 */ 55.864 us [ 25279] | } /* _GLOBAL__sub_I__Z3foov */ [ 25279] | main() { 0.050 us [ 25279] | foo() { 1.513 us [ 25279] | __cxa_allocate_exception(); 22.451 us [ 25279] | } /* foo */ 0.087 us [ 25279] | bar(); 23.092 us [ 25279] | } /* main */ """) def setup(self): self.option = '-N personality_v.' uftrace-0.15.2/tests/t186_exception3.py000066400000000000000000000035331455365734300176320ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exception3', lang='C++', result=""" # DURATION TID FUNCTION [ 16014] | main() { 0.205 us [ 16014] | A::A(); [ 16014] | foo() { [ 16014] | foo1() { [ 16014] | foo2() { [ 16014] | foo3() { [ 16014] | foo4() { 0.130 us [ 16014] | C::C(); [ 16014] | foo5() { 2.142 us [ 16014] | __cxa_allocate_exception(); 34.096 us [ 16014] | } /* foo5 */ 0.087 us [ 16014] | C::~C(); 41.512 us [ 16014] | } /* foo4 */ 56.240 us [ 16014] | } /* foo3 */ 56.506 us [ 16014] | } /* foo2 */ 56.817 us [ 16014] | } /* foo1 */ 0.088 us [ 16014] | B::B(); 0.087 us [ 16014] | B::~B(); 67.679 us [ 16014] | } /* foo */ 0.085 us [ 16014] | A::~A(); [ 16014] | catch_exc() { [ 16014] | bar() { 0.088 us [ 16014] | B::B(); [ 16014] | bar1() { [ 16014] | bar2() { [ 16014] | bar3() { 0.080 us [ 16014] | C::C(); 0.431 us [ 16014] | __cxa_allocate_exception(); 0.092 us [ 16014] | C::~C(); 12.139 us [ 16014] | } /* bar3 */ 12.345 us [ 16014] | } /* bar2 */ 12.543 us [ 16014] | } /* bar1 */ 0.096 us [ 16014] | B::~B(); [ 16014] | catch_exc() { 0.086 us [ 16014] | baz(); 0.461 us [ 16014] | } /* catch_exc */ 14.698 us [ 16014] | } /* bar */ 14.948 us [ 16014] | } /* catch_exc */ 85.226 us [ 16014] | } /* main */ """) def setup(self): self.option = '-N personality_v.' uftrace-0.15.2/tests/t187_graph_field.py000066400000000000000000000016531455365734300200170ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sort', """ # Function Call Graph for 'main' (session: 67af3e650b051216) =============== BACKTRACE =============== backtrace #0: hit 1, time 10.148 ms [0] main (0x560e956bd610) ========== FUNCTION CALL GRAPH ========== # TOTAL TIME SELF TIME ADDRESS FUNCTION 10.148 ms 37.889 us 560e956bd610 : (1) main 15.991 us 0.765 us 560e956bd7ce : +-(2) foo 15.226 us 15.226 us 560e956bd7a0 : | (6) loop : | 10.094 ms 13.365 us 560e956bd802 : +-(1) bar 10.081 ms 10.081 ms 560e956bd608 : (1) usleep """, sort='graph') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'graph' self.option = '-f +self,addr' self.exearg = 'main' uftrace-0.15.2/tests/t188_graph_field_none.py000066400000000000000000000011701455365734300210310ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sort', """ # Function Call Graph for 'main' (session: 6085c5f021e501d0) =============== BACKTRACE =============== backtrace #0: hit 1, time 10.321 ms [0] main (0x4004a0) ========== FUNCTION CALL GRAPH ========== (1) main +-(2) foo | (6) loop | +-(1) bar (1) usleep """, sort='graph') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'graph' self.option = '-f none' self.exearg = 'main' uftrace-0.15.2/tests/t189_replay_field2.py000066400000000000000000000025101455365734300202670ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'taskname', ldflags='-pthread', serial=True, result=""" # TASK NAME FUNCTION t-taskname | main() { t-taskname | task_name1() { t-taskname | prctl() { foo | } /* prctl */ foo | } /* task_name1 */ foo | task_name2() { foo | pthread_self(); foo | pthread_setname_np() { bar | } /* pthread_setname_np */ bar | } /* task_name2 */ bar | } /* main */ """) def prerun(self, timeout): if not TestBase.check_perf_paranoid(self): return TestBase.TEST_SKIP self.subcmd = 'record' self.option = '-E linux:task-name' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '-F main -f task' def sort(self, output, ignore_children=False): result = [] for ln in output.split('\n'): if ln.strip() == '': continue; result.append(ln) return '\n'.join(result) uftrace-0.15.2/tests/t190_trigger_autoargs.py000066400000000000000000000010121455365734300211020ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'autoargs', result="""hello # DURATION TID FUNCTION [16523] | main() { 2.670 us [16523] | strlen(); 1.336 us [16523] | strcmp("hello", "hello") = 0; 4.017 us [16523] | puts(); 18.574 us [16523] | } /* main */ """) def setup(self): self.option = '-F main -T calloc@trace-off -T strcmp@trace-on,auto-args' self.exearg += ' hello' uftrace-0.15.2/tests/t191_posix_spawn.py000066400000000000000000000023351455365734300201160ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'posix_spawn', """ # DURATION TID FUNCTION [ 9874] | main() { 142.145 us [ 9874] | posix_spawn(); [ 9874] | waitpid() { [ 9875] | main() { [ 9875] | a() { [ 9875] | b() { [ 9875] | c() { 0.976 us [ 9875] | getpid(); 1.992 us [ 9875] | } /* c */ 2.828 us [ 9875] | } /* b */ 3.658 us [ 9875] | } /* a */ 7.713 us [ 9875] | } /* main */ 2.515 ms [ 9874] | } /* waitpid */ 142.145 us [ 9874] | posix_spawn(); [ 9874] | waitpid() { [ 9876] | main() { 2.828 us [ 9876] | fopen(); 3.658 us [ 9876] | fclose(); 7.713 us [ 9876] | } /* main */ 2.515 ms [ 9874] | } /* waitpid */ 2.708 ms [ 9874] | } /* main */ """) def build(self, name, cflags='', ldflags=''): ret = TestBase.build(self, 'abc', cflags, ldflags) ret += TestBase.build(self, 'openclose', cflags, ldflags) ret += TestBase.build(self, self.name, cflags, ldflags) return ret def setup(self): self.option = '-F main' uftrace-0.15.2/tests/t192_lib_name.py000066400000000000000000000033351455365734300173140ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'nest-libcall', """1 # DURATION TID MODULE NAME FUNCTION [ 26884] t-nest-libcall | main() { [ 26884] t-nest-libcall | lib_a@libabc_test_lib.so() { 2.320 us [ 26884] libabc_test_lib. | getpid@libc.so.6(); 8.884 us [ 26884] t-nest-libcall | } /* lib_a@libabc_test_lib.so */ [ 26884] t-nest-libcall | foo@libfoo.so() { 0.880 us [ 26884] libfoo.so | AAA::bar@libfoo.so(); 2.423 us [ 26884] t-nest-libcall | } /* foo@libfoo.so */ 13.722 us [ 26884] t-nest-libcall | } /* main */ """) def build(self, name, cflags='', ldflags=''): if TestBase.build_libabc(self, '', '') != 0: return TestBase.TEST_BUILD_FAIL if TestBase.build_libfoo(self, 'foo', '', '') != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-nest-libcall.c', ['libabc_test_lib.so', 'libfoo.so'], cflags, ldflags) def setup(self): self.option = "--nest-libcall --libname -f +module" def fixup(self, cflags, result): import re import subprocess as sp # # use `ldd --version` to get libc version # # $ ldd --version # ldd (GNU libc) 2.26 <-- use this # Copyright (C) 2017 Free Software Foundation, Inc. # ... v = sp.check_output(["ldd", "--version"]).decode(errors='ignore') ver = v.split('\n')[0].split(') ')[1] ver.strip() return re.sub("libc.so.6", "libc-%s.so" % ver, result) uftrace-0.15.2/tests/t193_read_pmu_cycle.py000066400000000000000000000027001455365734300205150ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [32417] | main() { [32417] | a() { [32417] | b() { [32417] | /* read:pmu-cycle (cycle=133314, instructions=74485) */ [32417] | c() { 0.479 us [32417] | getpid(); 3.014 us [32417] | } /* c */ [32417] | /* diff:pmu-cycle (cycle=+5014, instructions=+3514, IPC=0.70) */ 16.914 us [32417] | } /* b */ 17.083 us [32417] | } /* a */ 17.873 us [32417] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_perf_paranoid(self): return TestBase.TEST_SKIP return TestCase.TEST_SUCCESS def setup(self): self.option = '-F main -T b@read=pmu-cycle' def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue func = ln.split('|', 1)[-1] # remove actual numbers in pmu-cycle if func.find('read:pmu-cycle') > 0: func = ' /* read:pmu-cycle */' if func.find('diff:pmu-cycle') > 0: func = ' /* diff:pmu-cycle */' result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t194_read_pmu_cache.py000066400000000000000000000026541455365734300204720ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [32417] | main() { [32417] | a() { [32417] | b() { [32417] | /* read:pmu-cache (refers=2105, misses=271) */ [32417] | c() { 0.479 us [32417] | getpid(); 3.014 us [32417] | } /* c */ [32417] | /* diff:pmu-cache (refers=+23, misses=+5, hit=78%) */ 16.914 us [32417] | } /* b */ 17.083 us [32417] | } /* a */ 17.873 us [32417] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_perf_paranoid(self): return TestBase.TEST_SKIP return TestCase.TEST_SUCCESS def setup(self): self.option = '-F main -T b@read=pmu-cache' def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue func = ln.split('|', 1)[-1] # remove actual numbers in pmu-cache if func.find('read:pmu-cache') > 0: func = ' /* read:pmu-cache */' if func.find('diff:pmu-cache') > 0: func = ' /* diff:pmu-cache */' result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t195_read_pmu_branch.py000066400000000000000000000026741455365734300206670ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [32417] | main() { [32417] | a() { [32417] | b() { [32417] | /* read:pmu-branch (branch=15482, misses=1192) */ [32417] | c() { 0.479 us [32417] | getpid(); 3.014 us [32417] | } /* c */ [32417] | /* diff:pmu-branch (branch=+785, misses=+71, predict=90%) */ 16.914 us [32417] | } /* b */ 17.083 us [32417] | } /* a */ 17.873 us [32417] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_perf_paranoid(self): return TestBase.TEST_SKIP return TestCase.TEST_SUCCESS def setup(self): self.option = '-F main -T b@read=pmu-branch' def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue func = ln.split('|', 1)[-1] # remove actual numbers in pmu-branch if func.find('read:pmu-branch') > 0: func = ' /* read:pmu-branch */' if func.find('diff:pmu-branch') > 0: func = ' /* diff:pmu-branch */' result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t196_chrome_taskname.py000066400000000000000000000043041455365734300207070ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'taskname', ldflags='-pthread', serial=True, result=""" {"traceEvents":[ {"ts":0,"ph":"M","pid":4694,"name":"process_name","args":{"name":"[4694] bar"}}, {"ts":0,"ph":"M","pid":4694,"name":"thread_name","args":{"name":"[4694] bar"}}, {"ts":13453314717.085,"ph":"B","pid":4694,"name":"main"}, {"ts":13453314717.245,"ph":"B","pid":4694,"name":"task_name1"}, {"ts":13453314717.814,"ph":"B","pid":4694,"name":"prctl"}, {"ts":0,"ph":"M","pid":4694,"name":"process_name","args":{"name":"[4694] foo"}}, {"ts":0,"ph":"M","pid":4694,"name":"thread_name","args":{"name":"[4694] foo"}}, {"ts":13453314720.072,"ph":"E","pid":4694,"name":"prctl"}, {"ts":13453314720.665,"ph":"E","pid":4694,"name":"task_name1"}, {"ts":13453314720.793,"ph":"B","pid":4694,"name":"task_name2"}, {"ts":13453314720.920,"ph":"B","pid":4694,"name":"pthread_self"}, {"ts":13453314721.080,"ph":"E","pid":4694,"name":"pthread_self"}, {"ts":13453314721.264,"ph":"B","pid":4694,"name":"pthread_setname_np"}, {"ts":0,"ph":"M","pid":4694,"name":"process_name","args":{"name":"[4694] bar"}}, {"ts":0,"ph":"M","pid":4694,"name":"thread_name","args":{"name":"[4694] bar"}}, {"ts":13453314722.478,"ph":"E","pid":4694,"name":"pthread_setname_np"}, {"ts":13453314722.631,"ph":"E","pid":4694,"name":"task_name2"}, {"ts":13453314722.695,"ph":"E","pid":4694,"name":"main"} ], "displayTimeUnit": "ns", "metadata": { "command_line":"../uftrace record --no-pager --no-event --libmcount-path=.. t-taskname", "recorded_time":"Tue Jan 30 16:05:24 2018" } } """, sort='chrome') def prerun(self, timeout): if not TestBase.check_perf_paranoid(self): return TestBase.TEST_SKIP self.subcmd = 'record' self.option = '-E linux:task-name' record_cmd = TestBase.runcmd(self) self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'dump' self.option = '--chrome -F main' def runcmd(self): cmd = TestBase.runcmd(self) return cmd.replace('--no-event', '') uftrace-0.15.2/tests/t197_filter_glob.py000066400000000000000000000012661455365734300200440ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'allocfree', """ # DURATION TID FUNCTION [11583] | alloc1() { [11583] | alloc2() { [11583] | alloc3() { [11583] | alloc4() { [11583] | alloc5() { 1.873 us [11583] | malloc(); 2.909 us [11583] | } /* alloc5 */ 3.652 us [11583] | } /* alloc4 */ 4.239 us [11583] | } /* alloc3 */ 5.016 us [11583] | } /* alloc2 */ 104.119 us [11583] | } /* alloc1 */ """, sort='simple') def setup(self): self.option = '-F "alloc*"' uftrace-0.15.2/tests/t198_lib_arg_float.py000066400000000000000000000014401455365734300203330ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'float-libcall', result=""" # DURATION TID FUNCTION [18276] | main() { 0.371 ms [18276] | expf(1.000000) = 2.718282; 0.118 ms [18276] | log(2.718282) = 1.000000; 3.281 ms [18276] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP ldflags += " -lm" return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "expf@fparg1/32" -R "expf@retval/f32" ' self.option += '-A "log@fparg1/64" -R "log@retval/f64" ' uftrace-0.15.2/tests/t199_script_info.py000066400000000000000000000021331455365734300200670ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ False v0.8.3-10/gfbfac3 ('foo', 'bar') """) def prerun(self, timeout): self.subcmd = 'script' self.option = '' self.exearg = '' script_cmd = self.runcmd() p = sp.Popen(script_cmd.split(), stdout=sp.PIPE, stderr=sp.PIPE) if p.communicate()[1].decode(errors='ignore').startswith('WARN:'): return TestBase.TEST_SKIP self.subcmd = 'record' self.exearg = 't-' + self.name record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'script' self.option = '-F main -S %s/scripts/info.py' % self.basedir self.exearg = 'foo bar' def sort(self, output): result = output.strip().split('\n') result[1] = 'uftrace version' # overwrite the version number return '\n'.join(result) uftrace-0.15.2/tests/t200_lib_dlopen2.py000066400000000000000000000025771455365734300177340ustar00rootroot00000000000000#!/usr/bin/env python3 import os from runtest import TestBase class TestCase(TestBase): """This tests when dlopen() loads multiple libraries (libbar and libbaz) at once. The Parent code is in libbar while Child is in libbaz.""" def __init__(self): TestBase.__init__(self, 'dlopen2', lang="C++", result=""" # DURATION TID FUNCTION [ 29510] | main() { 398.509 us [ 29510] | dlopen(); 2.324 us [ 29510] | dlsym(); [ 29510] | creat() { [ 29510] | Child::Child() { 0.290 us [ 29510] | Parent::Parent(); 1.703 us [ 29510] | } /* Child::Child */ 6.090 us [ 29510] | } /* creat */ 0.133 us [ 29510] | Child::func(); 48.519 us [ 29510] | dlclose(); 465.432 us [ 29510] | } /* main */ """) os.environ['LD_LIBRARY_PATH'] = "." def build(self, name, cflags='', ldflags=''): if TestBase.build_libfoo(self, 'bar', cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL if TestBase.build_libfoo(self, 'baz', cflags, ldflags + ' -L. -lbar') != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-dlopen2.cpp', ['libdl.so'], cflags, ldflags) def setup(self): self.option = '-F a' def fixup(self, cflags, result): return result.replace('creat', 'creat64') uftrace-0.15.2/tests/t201_arg_dwarf1.py000066400000000000000000000016621455365734300175530ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'dwarf1', """ # DURATION TID FUNCTION [ 29831] | main() { 0.495 us [ 29831] | foo(-1, 32768) = 32767; [ 29831] | bar("string argument", 'c', 0.000010, &null) { 0.660 us [ 29831] | null("NULL"); 442.163 us [ 29831] | } = -1.000000; /* bar */ [ 29831] | baz(ONE) { 0.323 us [ 29831] | foo(1, -1) = 0; 1.291 us [ 29831] | } /* baz */ 449.927 us [ 29831] | } = 0; /* main */ """, cflags='-g') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A . -R. -F main' uftrace-0.15.2/tests/t202_arg_dwarf2.py000066400000000000000000000027561455365734300175620ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'dwarf2', """ # DURATION TID FUNCTION [ 23714] | main() { 482.922 us [ 23714] | A::A(0x7ffecd0fe6e0, empty{}, FOO, 4, "debug info test"); 16.629 us [ 23714] | std::sort(0x7ffecd0fe700, 0x7ffecd0fe714, &myless); 5.713 us [ 23714] | std::sort(0x7ffecd0fe700, 0x7ffecd0fe714, less{...}); 5.853 us [ 23714] | std::sort(0x7ffecd0fe700, 0x7ffecd0fe714, {...}); 515.511 us [ 23714] | } = 0; /* main */ """, lang='C++', cflags='-g -std=c++11') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A . -R . -D2 -F main' def sort(self, output): import re result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue line = ln.split('|', 1)[-1] func = re.sub(r'0x[0-9a-f]+', '0xADDR', line) result.append(func) return '\n'.join(result) def fixup(self, cflags, result): # -O2 makes specialization of std::sort() (without 3rd arg) return result.replace(', 0x7ffecd0fe700)', ')') uftrace-0.15.2/tests/t203_arg_dwarf3.py000066400000000000000000000035201455365734300175520ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'dwarf3', """ # DURATION TID FUNCTION [ 28332] | main() { [ 28332] | C::C(0x7ffce3e4fe30, 1, "debug info") { 0.686 us [ 28332] | C::construct(0x7ffce3e4fe30, 1, "debug info"); 449.541 us [ 28332] | } /* C::C */ [ 28332] | C::C(0x7ffce3e4fe40, 2, "<0x1234>") { 0.340 us [ 28332] | C::construct(0x7ffce3e4fe40, 2, "<0x1234>"); 1.296 us [ 28332] | } /* C::C */ [ 28332] | C::C(0x7ffce3e4fe60, 0x7ffce3e4fe30) { 0.432 us [ 28332] | C::copy(0x7ffce3e4fe60, 1, "debug info"); 1.360 us [ 28332] | } /* C::C */ [ 28332] | foo(C{...}, 0x7ffce3e4fe40, "passed by value", 0.001000) { [ 28332] | C::C(0x7ffce3e4fe50, 3, "passed by value") { 0.346 us [ 28332] | C::construct(0x7ffce3e4fe50, 3, "passed by value"); 1.332 us [ 28332] | } /* C::C */ 2.225 us [ 28332] | } = C{...}; /* foo */ 457.250 us [ 28332] | } = 0; /* main */ """, lang='C++', cflags='-g') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A . -R . -F main' def sort(self, output): import re result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue line = ln.split('|', 1)[-1] func = re.sub(r'0x[0-9a-f]+', '0xADDR', line) result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t204_arg_dwarf4.py000066400000000000000000000022271455365734300175570ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exception4', """ # DURATION TID FUNCTION [ 15296] | main() { [ 15296] | foo(1) { 2.490 us [ 15296] | __cxa_allocate_exception(); 47.390 us [ 15296] | } /* foo */ 51.560 us [ 15296] | } = 0; /* main */ """, lang='C++', cflags='-g') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A . -R . -F main -N personality_v.' def sort(self, output): import re result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue line = ln.split('|', 1)[-1] func = re.sub(r'0x[0-9a-f]+', '0xADDR', line) result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t205_arg_auto4.py000066400000000000000000000025301455365734300174220ustar00rootroot00000000000000#!/usr/bin/env python3 import re from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'autoargs', result="""autoargs test # DURATION TID FUNCTION [ 16523] | main(2, 0xADDR) { 2.670 us [ 16523] | strlen("autoargs test") = 13; 1.353 us [ 16523] | calloc(1, 14) = 0xADDR; 1.150 us [ 16523] | free(0xADDR); 1.336 us [ 16523] | strcmp("hello", "bye") = 6; 4.017 us [ 16523] | puts("autoargs test") = 14; 18.574 us [ 16523] | } = 0; /* main */ """, cflags='-g') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-F main --auto-args' self.exearg += ' bye' def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue line = ln.split('|', 1)[-1] func = re.sub(r'0x[0-9a-f]+', '0xADDR', line) result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t206_arg_enum2.py000066400000000000000000000022231455365734300174140ustar00rootroot00000000000000#!/usr/bin/env python3 import re from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'enum', result=""" # DURATION TID FUNCTION [ 14885] | main() { 6.183 us [ 14885] | kill(0, SIGNULL); 3.120 us [ 14885] | foo(0); 4.095 us [ 14885] | foo(XXX); 17.849 us [ 14885] | } /* main */ """, cflags='-g') def build(self, name, cflags='', ldflags=''): if not "dwarf" in self.feature: return TestBase.TEST_SKIP # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-F main -A kill -A foo' def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue line = ln.split('|', 1)[-1] func = re.sub(r'0x[0-9a-f]+', '0xADDR', line) result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t207_dump_graphviz.py000066400000000000000000000017771455365734300204320ustar00rootroot00000000000000#!/usr/bin/env python3 import os.path from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # Elements digraph "t-abc" { "t-abc" -> "main" [xlabel = "1"] "main" -> "a" [xlabel = "1"] "a" -> "b" [xlabel = "1"] "b" -> "c" [xlabel = "1"] } """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'dump' self.option = '-F main -D 4 --graphviz' def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for line, ln in enumerate(output.split('\n')): # remove all comments ln = ln.strip() if ln.startswith('#'): continue result.append(ln) return '\n'.join(result) uftrace-0.15.2/tests/t208_watch_cpu.py000066400000000000000000000023061455365734300175160ustar00rootroot00000000000000#!/usr/bin/env python3 import re from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'chcpu', serial=True, result=""" # DURATION TID FUNCTION [320248] | main() { [320248] | /* watch:cpu (cpu=1) */ 23.252 us [320248] | sysconf(); 0.667 us [320248] | sched_getcpu(); [320248] | sched_setaffinity() { [320248] | /* watch:cpu (cpu=0) */ 75.337 us [320248] | } /* sched_setaffinity */ [320248] | sched_setaffinity() { [320248] | /* watch:cpu (cpu=1) */ 40.668 us [320248] | } /* sched_setaffinity */ 146.590 us [320248] | } /* main */ """) def setup(self): # ignore unexpected memset on ARM (raspbian) self.option = '-F main -W cpu -N memset --no-sched' def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue line = ln.split('|', 1)[-1] func = re.sub(r'cpu=[0-9a-f]+', 'cpu=N', line) result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t209_filter_caller.py000066400000000000000000000006471455365734300203570ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', """ # DURATION TID FUNCTION [ 30702] | main() { [ 30702] | foo() { 6.933 us [ 30702] | mem_alloc(); 2.139 ms [ 30702] | } /* foo */ 2.141 ms [ 30702] | } /* main */ """) def setup(self): self.option = '-C mem_alloc' uftrace-0.15.2/tests/t210_filter_time_C.py000066400000000000000000000006431455365734300203010ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # DURATION TID FUNCTION [18219] | main() { [18219] | foo() { 2.095 ms [18219] | bar(); 2.106 ms [18219] | } /* foo */ 2.107 ms [18219] | } /* main */ """) def setup(self): self.option = '-t 1ms -C bar' uftrace-0.15.2/tests/t211_replay_filter_C.py000066400000000000000000000010411455365734300206310ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', result=""" # DURATION TID FUNCTION [ 30702] | main() { [ 30702] | foo() { 6.933 us [ 30702] | mem_alloc(); 2.139 ms [ 30702] | } /* foo */ 2.141 ms [ 30702] | } /* main */ """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-C mem_alloc' uftrace-0.15.2/tests/t212_noplt_libcall.py000066400000000000000000000012201455365734300203440ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [ 28141] | main() { [ 28141] | a() { [ 28141] | b() { [ 28141] | c() { 0.753 us [ 28141] | getpid(); 1.430 us [ 28141] | } /* c */ 1.915 us [ 28141] | } /* b */ 2.405 us [ 28141] | } /* a */ 3.005 us [ 28141] | } /* main */ """) def build(self, name, cflags='', ldflags=''): return TestBase.build(self, name, cflags + " -fno-plt", ldflags + " -Wl,-z,now -Wl,-z,relro") uftrace-0.15.2/tests/t213_arg_symbol.py000066400000000000000000000015661455365734300177020ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'data', result=""" # DURATION TID FUNCTION [ 16187] | main() { 0.967 us [ 16187] | foo(&static_var, 1); 0.331 us [ 16187] | foo(&global_var, 2); 0.143 us [ 16187] | foo(&weak_var, 3); 0.166 us [ 16187] | filecmp(&_IO_2_1_stdout_, &_IO_2_1_stderr_); 3.189 us [ 16187] | } /* main */ """) def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A foo@arg1/p,arg2 -A filecmp@arg1/p,arg2/p' uftrace-0.15.2/tests/t214_signal_trigger.py000066400000000000000000000013741455365734300205420ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'signal', """ # DURATION TID FUNCTION [ 11892] | main() { 0.241 us [ 11892] | foo(); 1.611 us [ 11892] | signal(); [ 11892] | raise() { [ 11892] | sighandler() { 0.120 us [ 11892] | bar(); 2.315 us [ 11892] | } /* sighandler */ uftrace stopped tracing with remaining functions ================================================ task: 11892 [2] sighandler [1] raise [0] main """) def setup(self): self.option = "--signal SIGUSR1@finish" def fixup(self, cflags, result): return result.replace(""" } /* sighandler */ """, "") uftrace-0.15.2/tests/t215_no_libcall_replay.py000066400000000000000000000011431455365734300212070ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'signal', """ # DURATION TID FUNCTION [ 73755] | main() { 0.236 us [ 73755] | foo(); [ 73755] | sighandler() { 0.144 us [ 73755] | bar(); 0.734 us [ 73755] | } /* sighandler */ 0.117 us [ 73755] | foo(); 18.227 us [ 73755] | } /* main */ """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '--no-libcall' uftrace-0.15.2/tests/t216_no_libcall_report.py000066400000000000000000000012001455365734300212210ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'signal', """ Total time Self time Calls Function ========== ========== ========== ==================== 0.125 us 0.125 us 2 foo 9.500 us 0.459 us 1 main 0.209 us 0.126 us 1 sighandler 0.083 us 0.083 us 1 bar """, sort='report') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'report' self.option = '--no-libcall -s call,total' uftrace-0.15.2/tests/t217_no_libcall_dump.py000066400000000000000000000024221455365734300206630ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'signal', """ 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 = 0x363 (PLTHOOK | TASK_SESSION | SYM_REL_ADDR | MAX_STACK | PERF_EVENT | AUTO_ARGS) uftrace file header: info = 0x3bff reading 73755.dat 50895.869952000 73755: [entry] main(400787) depth: 0 50895.869952297 73755: [entry] foo(40071f) depth: 1 50895.869952533 73755: [exit ] foo(40071f) depth: 1 50895.869966333 73755: [entry] sighandler(400750) depth: 2 50895.869966473 73755: [entry] bar(400734) depth: 3 50895.869966617 73755: [exit ] bar(400734) depth: 3 50895.869967067 73755: [exit ] sighandler(400750) depth: 2 50895.869969790 73755: [entry] foo(40071f) depth: 1 50895.869969907 73755: [exit ] foo(40071f) depth: 1 50895.869970227 73755: [exit ] main(400787) depth: 0 """, sort='dump') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'dump' self.option = '--no-libcall' uftrace-0.15.2/tests/t218_no_libcall_graph.py000066400000000000000000000012151455365734300210170ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'signal', """ # Function Call Graph for 't-signal' (session: 0fa6f0e678964bde) ========== FUNCTION CALL GRAPH ========== # TOTAL TIME FUNCTION 18.227 us : (1) t-signal 18.227 us : (1) main 0.353 us : +-(2) foo : | 0.734 us : +-(1) sighandler 0.144 us : (1) bar """, sort='graph') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'graph' self.option = '--no-libcall' self.exearg = '' uftrace-0.15.2/tests/t219_no_libcall_script.py000066400000000000000000000031521455365734300212250ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'signal', """ uftrace_begin(ctx) record : False version : v0.9.1-161-g13755 ( dwarf python tui perf sched ) cmds : 50895.869952000 73755: [entry] main(400787) depth: 0 50895.869952297 73755: [entry] foo(40071f) depth: 1 50895.869952533 73755: [exit ] foo(40071f) depth: 1 50895.869966333 73755: [entry] sighandler(400750) depth: 1 50895.869966473 73755: [entry] bar(400734) depth: 2 50895.869966617 73755: [exit ] bar(400734) depth: 2 50895.869967067 73755: [exit ] sighandler(400750) depth: 1 50895.869969790 73755: [entry] foo(40071f) depth: 1 50895.869969907 73755: [exit ] foo(40071f) depth: 1 50895.869970227 73755: [exit ] main(400787) depth: 0 uftrace_end() """, sort='dump') def prerun(self, timeout): self.subcmd = 'script' self.option = '' self.exearg = '' script_cmd = self.runcmd() self.pr_debug('prerun command: ' + script_cmd) p = sp.Popen(script_cmd.split(), stdout=sp.PIPE, stderr=sp.PIPE) if p.communicate()[1].decode(errors='ignore').startswith('WARN:'): return TestBase.TEST_SKIP self.subcmd = 'record' self.exearg = 't-' + self.name record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'script' self.option = '-S %s/scripts/dump.py --no-libcall' % self.basedir self.exearg = '' uftrace-0.15.2/tests/t220_trace_script.py000066400000000000000000000021271455365734300202160ustar00rootroot00000000000000#!/usr/bin/env python3 import os import stat from runtest import TestBase TEST_SCRIPT = "./test-script.sh" class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION 133.697 us [28137] | fork(); [28141] | } /* fork */ [28141] | main() { [28141] | a() { [28141] | b() { [28141] | c() { 0.753 us [28141] | getpid(); 1.430 us [28141] | } /* c */ 1.915 us [28141] | } /* b */ 2.405 us [28141] | } /* a */ 3.005 us [28141] | } /* main */ """, sort='simple') f = open(TEST_SCRIPT, "w") f.write("""#!/bin/sh ./t-abc """) f.close() os.chmod(TEST_SCRIPT, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) def setup(self): self.option = "--force -F fork -F main" self.exearg = TEST_SCRIPT def fixup(self, cflags, result): result = result.replace(' 133.697 us [28137] | fork();\n', '') result = result.replace(' [28141] | } /* fork */\n', '') return result uftrace-0.15.2/tests/t221_taskname_time.py000066400000000000000000000017651455365734300203650ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'taskname', ldflags='-pthread', serial=True, result=""" # TASK NAME FUNCTION taskname | main() { taskname | task_name1() { taskname | prctl() { foo | /* linux:task-name (comm="foo") */ foo | } /* prctl */ foo | } /* task_name1 */ foo | task_name2() { foo | pthread_self(); foo | pthread_setname_np() { bar | /* linux:task-name (comm="bar") */ bar | } /* pthread_setname_np */ bar | } /* task_name2 */ bar | } /* main */ """, sort='simple') def prerun(self, timeout): if not TestBase.check_perf_paranoid(self): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def setup(self): self.option = '-F main -E linux:task-name -t 1' uftrace-0.15.2/tests/t222_external_data.py000066400000000000000000000041121455365734300203450ustar00rootroot00000000000000#!/usr/bin/env python3 import os.path import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [28141] | main() { [28141] | /* external-data: user message */ [28141] | a() { [28141] | b() { [28141] | c() { 0.753 us [28141] | getpid(); 1.430 us [28141] | } /* c */ 1.915 us [28141] | } /* b */ 2.405 us [28141] | } /* a */ 3.005 us [28141] | } /* main */ """) def prerun(self, timeout): self.subcmd = 'record' self.option = '' self.exearg = 't-' + self.name record_cmd = TestBase.runcmd(self) self.pr_debug("prerun command: " + record_cmd) sp.call(record_cmd.split()) self.subcmd = 'replay' self.option = '-F main -f time' self.exearg = '' replay_cmd = TestBase.runcmd(self) self.pr_debug("prerun command: " + replay_cmd) p = sp.Popen(replay_cmd.split(), stdout=sp.PIPE) if p.wait() != 0: return TestBase.TEST_NONZERO_RETURN output = p.communicate()[0].decode(errors='ignore') for l in output.split('\n'): if l.startswith('#'): continue; # parse first line to get the timestamp t = l.split(' | ')[0].strip() point = t.find('.') if point < 0: return TestBase.TEST_DIFF_RESULT nsec = int(t[point+1:point+10]) # add the external data right after the first line msg = '%s.%09d %s\n' % (t[:point], nsec + 1, 'user message') data_file = open(os.path.join('uftrace.data', 'extern.dat'), 'w') data_file.write(msg) data_file.close() break return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '' self.exearg = '' def runcmd(self): cmd = TestBase.runcmd(self) return cmd.replace('--no-event', '') uftrace-0.15.2/tests/t223_dynamic_full.py000066400000000000000000000014251455365734300202050ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'dynamic', """ # DURATION TID FUNCTION [ 63876] | main() { 0.739 us [ 63876] | foo(); 0.833 us [ 63876] | bar(); 1.903 us [ 63876] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_arch_full_dynamic_support(self): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def build(self, name, cflags='', ldflags=''): cflags = self.strip_tracing_flags(cflags) cflags += ' -fno-pie -fno-plt' # workaround of build failure return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-P main -P foo -P bar --no-libcall' uftrace-0.15.2/tests/t224_dynamic_lib.py000066400000000000000000000025501455365734300200120ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'dynmain', """ # DURATION TID FUNCTION [ 26661] | main() { [ 26661] | lib_a() { [ 26661] | lib_b() { 1.187 us [ 26661] | lib_c(); 2.271 us [ 26661] | } /* lib_b */ 2.647 us [ 26661] | } /* lib_a */ [ 26661] | lib_d() { [ 26661] | lib_e() { 0.974 us [ 26661] | lib_f(); 1.266 us [ 26661] | } /* lib_e */ 1.438 us [ 26661] | } /* lib_d */ 7.607 us [ 26661] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_arch_full_dynamic_support(self): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def build(self, name, cflags='', ldflags=''): if TestBase.build_notrace_lib(self, 'dyn1', 'libdyn1', cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL if TestBase.build_notrace_lib(self, 'dyn2', 'libdyn2', cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-dynmain.c', ['libdyn1.so', 'libdyn2.so'], cflags, ldflags, instrument=False) def setup(self): self.option = '-Pmain -P.@libdyn1.so -P.@libdyn2.so --no-libcall' uftrace-0.15.2/tests/t225_dynamic_size.py000066400000000000000000000024331455365734300202170ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase def get_func_size(elfbin, funcname): # expected output # $ readelf -s t-unroll | grep small # 28: 0000000000001185 30 FUNC GLOBAL DEFAULT 15 small cmd1 = "readelf -s %s" % elfbin cmd2 = "grep %s" % funcname size = 0 with sp.Popen(cmd1.split(), stdout=sp.PIPE) as p1: with sp.Popen(cmd2.split(), stdin=p1.stdout, stdout=sp.PIPE) as p2: line = p2.communicate()[0].decode(errors='ignore') size = int(line.split()[2]) return size class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'unroll', """ # DURATION TID FUNCTION [ 72208] | main() { 0.252 us [ 72208] | big(); 1.802 us [ 72208] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_arch_full_dynamic_support(self): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def build(self, name, cflags='', ldflags=''): cflags = self.strip_tracing_flags(cflags) cflags += ' -funroll-loops' return TestBase.build(self, name, cflags, ldflags) def setup(self): size = get_func_size('t-' + self.name, 'small') self.option = '-P. -Z %d' % (size + 1) uftrace-0.15.2/tests/t226_default_opts.py000066400000000000000000000010701455365734300202270ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep2', result=""" # DURATION TID FUNCTION [ 41487] | main() { [ 41487] | foo() { 5.107 ms [ 41487] | usleep(); 8.220 ms [ 41487] | } /* foo */ 9.336 ms [ 41487] | } /* main */ """) def prepare(self): self.subcmd = 'record' self.option = '-t 2ms' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-t 4ms' uftrace-0.15.2/tests/t227_read_pmu_cycle2.py000066400000000000000000000031761455365734300206050ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [ 32417] | main() { [ 32417] | a() { [ 32417] | b() { [ 32417] | /* read:pmu-cycle (cycle=233, instructions=159) */ [ 32417] | c() { [ 32417] | /* read:pmu-cycle (cycle=471, instructions=385) */ 0.479 us [ 32417] | getpid(); [ 32417] | /* diff:pmu-cycle (cycle=+3230, instructions=+2723, IPC=0.84) */ 3.014 us [ 32417] | } /* c */ [ 32417] | /* diff:pmu-cycle (cycle=+5014, instructions=+3514, IPC=0.70) */ 16.914 us [ 32417] | } /* b */ 17.083 us [ 32417] | } /* a */ 17.873 us [ 32417] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_perf_paranoid(self): return TestBase.TEST_SKIP return TestCase.TEST_SUCCESS def setup(self): self.option = "-F main -T '[bc]@read=pmu-cycle'" def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue func = ln.split('|', 1)[-1] # remove actual numbers in pmu-cycle if func.find('read:pmu-cycle') > 0: func = ' /* read:pmu-cycle */' if func.find('diff:pmu-cycle') > 0: func = ' /* diff:pmu-cycle */' result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t228_read_pmu_cycle3.py000066400000000000000000000034761455365734300206120ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fork', """ # DURATION TID FUNCTION [ 3309] | main() { [ 3324] | a() { [ 3324] | b() { [ 3324] | c() { [ 3324] | /* read:pmu-cycle (cycle=688, instructions=30) */ [ 3324] | /* diff:pmu-cycle (cycle=+5739, instructions=+1338, IPC=0.23) */ 99.968 us [ 3324] | } /* c */ 120.909 us [ 3324] | } /* b */ 122.103 us [ 3324] | } /* a */ 122.390 us [ 3324] | } /* main */ [ 3309] | a() { [ 3309] | b() { [ 3309] | c() { [ 3309] | /* read:pmu-cycle (cycle=664, instructions=30) */ [ 3309] | /* diff:pmu-cycle (cycle=+3649, instructions=+1338, IPC=0.37) */ 109.272 us [ 3309] | } /* c */ 124.879 us [ 3309] | } /* b */ 125.839 us [ 3309] | } /* a */ 2.630 ms [ 3309] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_perf_paranoid(self): return TestBase.TEST_SKIP return TestCase.TEST_SUCCESS def setup(self): self.option = '-T c@read=pmu-cycle --no-libcall' def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue func = ln.split('|', 1)[-1] # remove actual numbers in pmu-cycle if func.find('read:pmu-cycle') > 0: func = ' /* read:pmu-cycle */' if func.find('diff:pmu-cycle') > 0: func = ' /* diff:pmu-cycle */' result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t229_info_task.py000066400000000000000000000017501455365734300175230ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fork', """ # TIMESTAMP FLAGS TID TASK DATA SIZE 347768.688479931 FS [ 27364] t-fork 0.000 MB 347768.711664373 F [ 27366] t-fork 0.000 MB """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'info' self.option = '--task' def sort(self, output): import re result = [] for ln in output.split('\n'): # ignore blank lines and comments if ln.strip() == '' or ln.startswith('#'): continue # ignore time part ln = ln[24:] # replace tid part into common string task = re.sub(r'\[ *[0-9]+\]', '[TID]', ln) result.append(task) return '\n'.join(result) uftrace-0.15.2/tests/t230_graph_task.py000066400000000000000000000024721455365734300176630ustar00rootroot00000000000000#!/usr/bin/env python3 import re from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'fork', result=""" ========== TASK GRAPH ========== # TOTAL TIME SELF TIME TID TASK NAME 16.172 ms 356.762 us [129306] : t-fork : | 11.015 us 11.015 us [129317] : +----t-fork """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'graph' self.option = '--task' self.exearg = '' def sort(self, output, ignored=False): """ This function post-processes output of the test to be compared. It ignores blank and comment (#) lines and header lines. """ result = [] for ln in output.split('\n'): if ln.strip() == '' or ln.startswith('#'): continue # A graph result consists of backtrace and calling functions if ln.startswith('========== TASK GRAPH =========='): continue if " : " in ln: line = ln.split(':')[1] # remove time part line = re.sub('\[\d+\]', 'TID', line) result.append(line) else: result.append(ln) return '\n'.join(result) uftrace-0.15.2/tests/t231_arg_bound.py000066400000000000000000000023211455365734300174720ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'arg', """ # DURATION TID FUNCTION 0.647 us [ 17685] | __monstartup(); 0.117 us [ 17685] | __cxa_atexit(); [ 17685] | main() { [ 17685] | foo() { [ 17685] | bar() { 0.107 us [ 17685] | strcmp(); 0.420 us [ 17685] | } /* bar */ [ 17685] | bar() { 0.044 us [ 17685] | strcmp(); 0.181 us [ 17685] | } /* bar */ [ 17685] | bar() { 0.045 us [ 17685] | strcmp(); 0.162 us [ 17685] | } /* bar */ 1.122 us [ 17685] | } /* foo */ 160.575 us [ 17685] | many(12, 0, 1, 0); [ 17685] | pass() { 0.083 us [ 17685] | check(); 0.303 us [ 17685] | } /* pass */ 162.699 us [ 17685] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A "many@arg1,arg9999999,arg2,arg4294967295"' uftrace-0.15.2/tests/t232_dynamic_unpatch.py000066400000000000000000000017101455365734300207020ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION 62.202 us [28141] | __cxa_atexit(); [28141] | main() { [28141] | b() { 0.913 us [28141] | c(); 2.210 us [28141] | } /* b */ 3.005 us [28141] | } /* main */ """) def build(self, name, cflags='', ldflags=''): if not TestBase.check_arch_mfentry_mnop_mcount_support(self): return TestBase.TEST_SKIP if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP if self.supported_lang['C']['cc'] == 'clang': return TestBase.TEST_SKIP cflags += ' -mfentry -mnop-mcount' cflags += ' -fno-pie -fno-plt' # workaround of build failure return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-P . -U a --no-libcall' uftrace-0.15.2/tests/t233_dynamic_unpatch2.py000066400000000000000000000013561455365734300207730ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'dynamic', """ # DURATION TID FUNCTION [ 63876] | main() { 0.739 us [ 63876] | foo(); 1.903 us [ 63876] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_arch_full_dynamic_support(self): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def build(self, name, cflags='', ldflags=''): cflags = self.strip_tracing_flags(cflags) cflags += ' -fno-pie -fno-plt' # workaround of build failure return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-P . -U bar --no-libcall' uftrace-0.15.2/tests/t234_script_luajit.py000066400000000000000000000014061455365734300204140ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', '5') def prerun(self, timeout): self.subcmd = 'script' self.option = '-S %s/scripts/count.lua --record' % self.basedir script_cmd = self.runcmd() self.pr_debug('prerun command: ' + script_cmd) p = sp.Popen(script_cmd.split(), stdout=sp.PIPE, stderr=sp.PIPE) if p.communicate()[1].decode(errors='ignore').startswith('WARN:'): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def setup(self): self.option = '-F main -S %s/scripts/count.lua' % self.basedir def sort(self, output): return output.strip() uftrace-0.15.2/tests/t235_report_srcline.py000066400000000000000000000037541455365734300206030ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ Total time Self time Calls Function [Source] ========== ========== ========== ==================== 2.559 us 0.306 us 1 main [tests/s-abc.c:26] 2.253 us 0.292 us 1 a [tests/s-abc.c:11] 1.961 us 0.342 us 1 b [tests/s-abc.c:16] 1.619 us 0.555 us 1 c [tests/s-abc.c:21] 1.064 us 1.064 us 1 getpid 0.372 us 0.372 us 1 __monstartup 0.186 us 0.186 us 1 __cxa_atexit """, cflags='-g') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def prepare(self): self.subcmd = 'record' self.option = '--srcline' return self.runcmd() def setup(self): self.subcmd = 'report' self.option = '--srcline' def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] [6] # total_time unit self_time unit called function srcline if line[-1].startswith('__'): continue if len(line) < 7 : result.append('%s %s' % (line[-2], line[-1])) else : result.append('%s %s %s' % (line[-3], line[-2], line[-1][1:-1].split('/')[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t236_replay_srcline.py000066400000000000000000000035441455365734300205620ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [SOURCE] 0.678 us [ 19939] | __monstartup(); 0.234 us [ 19939] | __cxa_atexit(); [ 19939] | main() { /* tests/s-abc.c:26 */ [ 19939] | a() { /* tests/s-abc.c:11 */ [ 19939] | b() { /* tests/s-abc.c:16 */ [ 19939] | c() { /* tests/s-abc.c:21 */ 1.120 us [ 19939] | getpid(); 1.697 us [ 19939] | } /* c */ 2.044 us [ 19939] | } /* b */ 2.329 us [ 19939] | } /* a */ 2.644 us [ 19939] | } /* main */ """, cflags='-g') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def prepare(self): self.subcmd = 'record' self.option = '--srcline' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '--srcline' def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] before_main = True for ln in output.split('\n'): if ln.find(' | main()') > 0: before_main = False if before_main: continue # ignore result of remaining functions which follows a blank line if ln.strip() == '': break func = ln.split('|', 1)[-1].split('/*') if len(func) < 2 : result.append('%s' % (func[0])) else : result.append('%s %s' % (func[-2], func[-1][0:-3].split('/')[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t237_report_field1.py000066400000000000000000000026111455365734300203010ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sort', """ Total time Calls Function ========== ========== ==================== 11.173 ms 1 main 10.467 ms 1 bar 10.297 ms 1 usleep 207.139 us 2 foo 204.033 us 6 loop 0.763 us 1 __monstartup 0.299 us 1 __cxa_atexit """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'report' self.option = '-f total,call' def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] # total_time unit called function if line[-1].startswith('__'): continue result.append('%s %s' % (line[-2], line[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t238_report_field2.py000066400000000000000000000033121455365734300203020ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sort', """ Total time Total max Self min Calls Function ========== ========== ========== ========== ==================== 10.297 ms 10.297 ms 10.297 ms 1 usleep 11.173 ms 11.173 ms 499.317 us 1 main 10.467 ms 10.467 ms 169.727 us 1 bar 204.033 us 37.300 us 33.148 us 6 loop 207.139 us 105.930 us 1.190 us 2 foo 0.763 us 0.763 us 0.763 us 1 __monstartup 0.299 us 0.299 us 0.299 us 1 __cxa_atexit """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'report' self.option = '-f total,total-max,self-min,call -s self-min,total-min' def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] [6] [7] # total_time unit total_max unit self_min unit called function if line[-1].startswith('__'): continue result.append('%s %s' % (line[-2], line[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t239_report_diff_field1.py000066400000000000000000000051241455365734300212750ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase XDIR='xxx' YDIR='yyy' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # # uftrace diff # [0] base: xxx (from uftrace record -d xxx tests/t-abc ) # [1] diff: yyy (from uftrace record -d yyy tests/t-abc ) # Total time (diff) Self time (diff) Nr. called (diff) Function ================================ ================================ ================================ ==================================== 6.974 us 6.268 us -10.12% 0.560 us 0.511 us -8.75% 1 1 +0 main 6.414 us 5.757 us -10.24% 0.489 us 0.372 us -23.93% 1 1 +0 a 5.925 us 5.385 us -9.11% 0.786 us 0.554 us -29.52% 1 1 +0 b 5.139 us 4.831 us -5.99% 3.517 us 3.137 us -10.80% 1 1 +0 c 1.622 us 1.694 us +4.44% 1.622 us 1.694 us +4.44% 1 1 +0 getpid """) def prerun(self, timeout): self.subcmd = 'record' self.option = '-d ' + XDIR self.exearg = 't-' + self.name record_cmd = self.runcmd() sp.call(record_cmd.split()) self.option = '-d ' + YDIR record_cmd = self.runcmd() sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'report' self.option = '-f total,self,call --sort-column 0 --diff-policy full,percent' # old behavior self.exearg = '-d %s --diff %s' % (XDIR, YDIR) def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.startswith('#') or ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] # tT/0 unit tT/1 unit percent tS/0 unit tS/1 unit percent call/0 call/1 call/diff function if line[-1].startswith('__'): continue result.append('%s %s %s %s' % (line[-4], line[-3], line[-2], line[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t240_report_diff_field2.py000066400000000000000000000041451455365734300212700ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase XDIR='xxx' YDIR='yyy' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'diff', """ # # uftrace diff # [0] base: xxx (from uftrace record -d xxx -F main tests/t-diff 0 ) # [1] diff: yyy (from uftrace record -d yyy -F main tests/t-diff 1 ) # Total time Calls Function =========== =========== ==================== -1.524 ms +4 usleep -326.342 us +2 bar -1.302 ms +1 foo -0.115 us +0 atoi -1.533 ms +0 main """) def prerun(self, timeout): self.subcmd = 'record' self.option = '-d %s -F main' % XDIR self.exearg = 't-' + self.name + ' 0' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) self.option = '-d %s -F main' % YDIR self.exearg = 't-' + self.name + ' 1' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'report' self.option = '-f total,call --diff-policy compact,no-percent,abs -s call' # new default self.exearg = '-d %s --diff %s' % (XDIR, YDIR) def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.startswith('#') or ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] # total unit call function if line[-1].startswith('__'): continue result.append('%s %s' % (line[-2], line[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t241_report_diff_field3.py000066400000000000000000000054731455365734300212770ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase XDIR='xxx' YDIR='yyy' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'diff', """ # # uftrace diff # [0] base: xxx (from uftrace record -d yyy -F main tests/t-diff 1 ) # [1] diff: yyy (from uftrace record -d xxx -F main tests/t-diff 0 ) # Total time (diff) Self time (diff) Calls (diff) Function =================================== =================================== ================================ ==================== 1.012 us 1.017 us -0.005 us 1.012 us 1.017 us -0.005 us 1 1 +0 atoi 2.796 ms 1.268 ms +1.528 ms 3.031 us 1.644 us +1.387 us 1 1 +0 main 2.563 ms 1.265 ms +1.297 ms 5.972 us 2.706 us +3.266 us 2 1 -1 foo 484.028 us 157.853 us +326.175 us 4.056 us 0.979 us +3.077 us 3 1 -2 bar 2.782 ms 1.261 ms +1.521 ms 2.782 ms 1.261 ms +1.521 ms 6 2 -4 usleep """) def prerun(self, timeout): self.subcmd = 'record' self.option = '-d %s -F main' % XDIR self.exearg = 't-' + self.name + ' 0' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) self.option = '-d %s -F main' % YDIR self.exearg = 't-' + self.name + ' 1' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'report' self.option = '-f total,self,call --diff-policy full,no-abs -s call,func' self.exearg = '-d %s --diff %s' % (YDIR, XDIR) def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.startswith('#') or ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] # tT/0 unit tT/1 unit tT/d unit tS/0 unit tS/1 unit tS/d unit call/0 call/1 call/d function if line[-1].startswith('__'): continue result.append('%s %s %s %s' % (line[-4], line[-3], line[-2], line[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t242_report_diff_field4.py000066400000000000000000000041141455365734300212700ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase XDIR='xxx' YDIR='yyy' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'diff', """ # # uftrace diff # [0] base: xxx (from uftrace record -d xxx -F main tests/t-diff 0 ) # [1] diff: yyy (from uftrace record -d yyy -F main tests/t-diff 1 ) # Self time Calls Function =========== =========== ==================== -1.71% +0 atoi +160.82% +2 bar +63.23% +1 foo +51.53% +0 main +120.44% +4 usleep """) def prerun(self, timeout): self.subcmd = 'record' self.option = '-d %s -F main' % XDIR self.exearg = 't-' + self.name + ' 0' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) self.option = '-d %s -F main' % YDIR self.exearg = 't-' + self.name + ' 1' record_cmd = self.runcmd() self.pr_debug('prerun command: %s' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'report' self.option = '-f self,call --diff-policy percent -s func' self.exearg = '-d %s --diff %s' % (XDIR, YDIR) def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.startswith('#') or ln.strip() == '': continue line = ln.split() if line[0] == 'Self': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] # self percent call function if line[-1].startswith('__'): continue result.append('%s %s' % (line[-2], line[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t243_report_diff_field5.py000066400000000000000000000043021455365734300212710ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase XDIR='xxx' YDIR='yyy' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'diff', """ # # uftrace diff # [0] base: xxx (from uftrace record -d xxx -F main tests/t-diff 0 ) # [1] diff: yyy (from uftrace record -d yyy -F main tests/t-diff 1 ) # Total time Self time Calls Function =========== =========== =========== ==================== +0.089 us +0.089 us +0 atoi -314.912 us -1.346 us +2 bar -1.262 ms -1.819 us +1 foo -1.490 ms -1.005 us +0 main -1.486 ms -1.486 ms +4 usleep """) def prerun(self, timeout): self.subcmd = 'record' self.option = '-d %s -F main' % XDIR self.exearg = 't-' + self.name + ' 0' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) self.option = '-d %s -F main' % YDIR self.exearg = 't-' + self.name + ' 1' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'report' self.option = '-f total,self,call --diff-policy compact -s func' self.exearg = '-d %s --diff %s' % (XDIR, YDIR) def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.startswith('#') or ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] # tT unit tS unit call function if line[-1].startswith('__'): continue result.append('%s %s' % (line[-2], line[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t244_report_task_field.py000066400000000000000000000024231455365734300212410ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'thread', """ TID Num funcs Task name ====== ========== ==================== 36562 1 t-thread 36578 4 t-thread 36577 4 t-thread 36580 4 t-thread 36579 4 t-thread """, ldflags='-pthread') def prepare(self): self.subcmd = 'record' self.option = '--no-libcall' return self.runcmd() def setup(self): self.subcmd = 'report' self.option = '--task -f tid,func' def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.strip() == '': continue line = ln.split() if line[0] == 'TID': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] # tid num_funcs task_name result.append('%s %s' % (line[-2], line[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t245_report_field_none.py000066400000000000000000000006551455365734300212440ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ main a b c getpid """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'report' self.option = '-F main -f none' def sort(self, output, ignore_children=False): return output uftrace-0.15.2/tests/t246_report_srcline2.py000066400000000000000000000043301455365734300206560ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'lib', """ Total time Self time Calls Function [Source] ========== ========== ========== ==================== 4.362 us 0.212 us 1 main [xxx/uftrace/tests/s-libmain.c:16] 4.150 us 1.713 us 1 foo [xxx/uftrace/tests/s-libmain.c:11] 1.804 us 0.186 us 1 lib_a [xxx/uftrace/tests/s-lib.c:10] 1.618 us 0.980 us 1 lib_b [xxx/uftrace/tests/s-lib.c:15] 0.638 us 0.638 us 1 lib_c [xxx/uftrace/tests/s-lib.c:20] """, cflags='-g') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP if TestBase.build_libabc(self, cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-libmain.c', ['libabc_test_lib.so'], cflags, ldflags) def prepare(self): self.subcmd = 'record' self.option = '--srcline --no-libcall' return self.runcmd() def setup(self): self.subcmd = 'report' self.option = '--srcline' def sort(self, output): """ This function post-processes output of the test to be compared . It ignores blank and comment (#) lines and remaining functions. """ result = [] for ln in output.split('\n'): if ln.strip() == '': continue line = ln.split() if line[0] == 'Total': continue if line[0].startswith('='): continue # A report line consists of following data # [0] [1] [2] [3] [4] [5] [6] # total_time unit self_time unit called function srcline if line[-1].startswith('__'): continue if len(line) < 7 : result.append('%s %s' % (line[-2], line[-1])) else : result.append('%s %s %s' % (line[-3], line[-2], line[-1][1:-1].split('/')[-1])) return '\n'.join(result) uftrace-0.15.2/tests/t247_graph_srcline.py000066400000000000000000000050761455365734300203730ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sort', result=""" # Function Call Graph for 'main' (session: de27777d0a966d5a) =============== BACKTRACE =============== backtrace #0: hit 1, time 13.120 ms [0] main (0x56366ebab7fc) ========== FUNCTION CALL GRAPH ========== # TOTAL TIME FUNCTION [SOURCE] 13.120 ms : (1) main 694.492 us : +-(2) foo [/home/eslee/soft/uftrace/tests/s-sort.c:10] 688.800 us : | (6) loop [/home/eslee/soft/uftrace/tests/s-sort.c:3] : | 10.748 ms : +-(1) bar [/home/eslee/soft/uftrace/tests/s-sort.c:17] 10.183 ms : (1) usleep """, sort='graph', cflags='-g') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def prepare(self): self.subcmd = 'record' self.option = '--srcline' self.exearg = 't-' + self.name return self.runcmd() def setup(self): self.subcmd = 'graph' self.option = '--srcline' self.exearg = 'main' def sort(self, output): """ This function post-processes output of the test to be compared. It ignores blank and comment (#) lines and header lines. """ result = [] mode = 0 for ln in output.split('\n'): if ln.strip() == '' or ln.startswith('#'): continue # A graph result consists of backtrace and calling functions if ln.startswith('=============== BACKTRACE ==============='): mode = 1 continue if ln.startswith('========== FUNCTION CALL GRAPH =========='): mode = 2 continue if mode == 1: if ln.startswith(' backtrace #'): result.append(ln.split(',')[0]) # remove time part if ln.startswith(' ['): result.append(ln.split('(')[0]) # remove '(addr)' part if mode == 2: if " : " in ln: func = ln.split(':', 1)[1].split('[') # remove time part if len(func) < 2 : result.append('%s' % (func[-1])) else : # extract basename and line number of source location result.append('%s %s' % (func[-2], func[-1][0:-1].split('/')[-1])) else: result.append(ln) return '\n'.join(result) uftrace-0.15.2/tests/t248_dynamic_dlopen.py000066400000000000000000000027041455365734300205340ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'dlopen', """ # DURATION TID FUNCTION [ 29979] | main() { 401.827 us [ 29979] | dlopen(); 1.339 us [ 29979] | dlsym(); [ 29979] | lib_a() { [ 29979] | lib_b() { 1.509 us [ 29979] | lib_c(); 1.993 us [ 29979] | } /* lib_b */ 2.468 us [ 29979] | } /* lib_a */ 14.949 us [ 29979] | dlclose(); 346.494 us [ 29979] | dlopen(); 0.925 us [ 29979] | dlsym(); [ 29979] | foo() { 0.163 us [ 29979] | AAA::bar(); 1.706 us [ 29979] | } /* foo */ 11.246 us [ 29979] | dlclose(); 788.643 us [ 29979] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_arch_full_dynamic_support(self): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def build(self, name, cflags='', ldflags=''): cflags = self.strip_tracing_flags(cflags) if TestBase.build_libabc(self, cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL if TestBase.build_libfoo(self, 'foo', cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-dlopen.c', ['libdl.so'], cflags, ldflags) def setup(self): self.option = ' -P. -P.@libfoo.so -P.@libabc_test_lib.so' uftrace-0.15.2/tests/t249_estimate_return.py000066400000000000000000000010651455365734300207610ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [28141] | main() { [28141] | a() { [28141] | b() { [28141] | c() { 0.753 us [28141] | getpid(); 1.430 us [28141] | } /* c */ 1.915 us [28141] | } /* b */ 2.405 us [28141] | } /* a */ 3.005 us [28141] | } /* main */ """) def setup(self): self.option = '--estimate-return -F main' uftrace-0.15.2/tests/t250_indirect_return.py000066400000000000000000000011311455365734300207310ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'indirect-return', lang="C++", result=""" # DURATION TID FUNCTION [418122] | main() { 178.233 us [418122] | std::__cxx11::basic_ostringstream::basic_ostringstream(); 3.674 us [418122] | std::__cxx11::basic_ostringstream::str(); 1.476 us [418122] | std::__cxx11::basic_string::~basic_string(); 10.450 us [418122] | std::__cxx11::basic_ostringstream::~basic_ostringstream(); 201.943 us [418122] | } /* main */ """, sort='simple') uftrace-0.15.2/tests/t251_exception4.py000066400000000000000000000020371455365734300176220ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'libexcept-main', lang='C++', result=""" # DURATION TID FUNCTION [423633] | main() { [423633] | XXX::XXX() { 30.679 us [423633] | XXX::XXX(); 31.490 us [423633] | } /* XXX::XXX */ [423633] | YYY::YYY() { 0.509 us [423633] | __cxa_allocate_exception(); 0.541 us [423633] | std::runtime_error::runtime_error(); 5.670 us [423633] | } /* YYY::YYY */ 42.354 us [423633] | } /* main */ """) def build(self, name, cflags='', ldflags=''): if TestBase.build_libfoo(self, 'except', cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-libexcept-main.cpp', ['libexcept.so'], cflags, ldflags) def fixup(self, cflags, result): return result.replace(""" 6.353 us [423633] | std::runtime_error:~runtime_error(); """, '') uftrace-0.15.2/tests/t252_filter_H.py000066400000000000000000000007361455365734300173010ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [ 30217] | main() { [ 30217] | a() { [ 30217] | b() { 2.500 us [ 30217] | getpid(); 8.600 us [ 30217] | } /* b */ 11.500 us [ 30217] | } /* a */ 14.500 us [ 30217] | } /* main */ """) def setup(self): self.option = '-H c' uftrace-0.15.2/tests/t253_trigger_hide.py000066400000000000000000000007431455365734300202000ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [ 30217] | main() { [ 30217] | a() { [ 30217] | b() { 2.500 us [ 30217] | getpid(); 8.600 us [ 30217] | } /* b */ 11.500 us [ 30217] | } /* a */ 14.500 us [ 30217] | } /* main */ """) def setup(self): self.option = '-T c@hide' uftrace-0.15.2/tests/t254_arg_dwarf5.py000066400000000000000000000024031455365734300175610ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'dwarf5', """ # DURATION TID FUNCTION [1167172] | main() { 143.049 us [1167172] | pass_int1(int1{...}, 1, "2", 3.000000) = 1; 0.746 us [1167172] | pass_int3(int3{...}, 1, "2", 3.000000) = 1; 0.538 us [1167172] | pass_lng1(lng1{...}, 1, "2", 3.000000) = 1; 0.492 us [1167172] | pass_lng3(lng3{...}, 1, "2", 3.000000) = 1; 0.501 us [1167172] | pass_dbl1(dbl1{...}, 1, "2", 3.000000) = 1.000000; 0.351 us [1167172] | pass_dbl3(dbl3{...}, 1, "2", 3.000000) = 1.000000; 0.359 us [1167172] | pass_mix1(mix1{...}, 1, "2", 3.000000) = 0.000000; 0.387 us [1167172] | pass_mix2(mix2{...}, 1, "2", 3.000000) = 1.000000; 0.255 us [1167172] | pass_mix3(mix3{...}, 1, "2", 3.000000) = 1; 154.430 us [1167172] | } /* main */ """, cflags='-g') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-A ^pass -R ^pass -F main' uftrace-0.15.2/tests/t255_arg_dwarf6.py000066400000000000000000000014301455365734300175620ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'dwarf6', """ # DURATION TID FUNCTION [152673] | main() { 83.549 us [152673] | addString(" uftrace "s, "test") = " uftrace test"s; 4.597 us [152673] | addItem(vector{...}, 0) = vector{...}; 116.117 us [152673] | } /* main */ """, lang='C++', cflags='-g -std=c++11') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-F main -N ^std -A ^add -R ^add' uftrace-0.15.2/tests/t256_arg_dwarf7.py000066400000000000000000000013431455365734300175670ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'dwarf7', """ # DURATION TID FUNCTION [1279330] | main() { 0.166 us [1279330] | compare_iters(__normal_iterator{...}, __normal_iterator{...}) = 0; 5.959 us [1279330] | } = 0; /* main */ """, lang='C++', cflags='-g -std=c++11') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-a -C compare_iters' uftrace-0.15.2/tests/t257_arg_struct_replay.py000066400000000000000000000013341455365734300212760ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'struct', """ # DURATION TID FUNCTION [ 54277] | main() { 207.329 us [ 54277] | foo(Option{...}, StringRef{}, 44, 55, 66); 208.750 us [ 54277] | } = 0; /* main */ """, cflags='-g') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-a --no-libcall' uftrace-0.15.2/tests/t258_arg_struct_dump.py000066400000000000000000000034141455365734300207510ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'struct', """ 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 = 0x67a (TASK_SESSION | ARGUMENT | RETVAL | SYM_REL_ADDR | MAX_STACK | AUTO_ARGS | DEBUG_INFO) uftrace file header: info = 0x3fff reading 3121.dat 11431966.432931623 3121: [entry] main(400673) depth: 0 11431966.432932393 3121: [entry] foo(40061f) depth: 1 11431966.432932393 3121: [args ] length = 36 args[0] struct Option: 0b 16 00 00 00 00 00 00 00 00 00 args[1] struct StringRef: args[2] d64: 0x000000000000002c args[3] d64: 0x0000000000000037 args[4] d64: 0x0000000000000042 11431966.433134239 3121: [exit ] foo(40061f) depth: 1 11431966.433135754 3121: [exit ] main(400673) depth: 0 11431966.433135754 3121: [retval] length = 8 retval d64: 0x0000000000000000 """, cflags='-g', sort='dump') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def prepare(self): self.subcmd = 'record' self.option = '-a --no-libcall' return self.runcmd() def setup(self): self.subcmd = 'dump' def fixup(self, cflags, result): # 32-bit will show the output differently return result.replace('d64: 0x00000000', 'd32: 0x') uftrace-0.15.2/tests/t259_arg_struct_script.py000066400000000000000000000034511455365734300213120ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'struct', """ uftrace_begin(ctx) record : False version : v0.10-17-g8d1b ( x86_64 dwarf python luajit tui perf sched dynamic ) cmds : t-struct 11332966.196463691 50529: [entry] main(4006ef) depth: 0 11332966.196464540 50529: [entry] foo(40068f) depth: 1 args[0] : struct: Option{} args[1] : struct: StringRef{} args[2] : 44 args[3] : 55 args[4] : 66 11332966.196670289 50529: [exit ] foo(40068f) depth: 1 11332966.196671664 50529: [exit ] main(4006ef) depth: 0 retval : 0 uftrace_end() """, cflags='-g', sort='dump') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature or not 'python' in self.feature: return TestBase.TEST_SKIP # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def prerun(self, timeout): script_cmd = '%s script' % (TestBase.uftrace_cmd) p = sp.Popen(script_cmd.split(), stdout=sp.PIPE, stderr=sp.PIPE) if p.communicate()[1].decode(errors='ignore').startswith('WARN:'): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'script' self.option = '-S %s/scripts/dump.py -a --no-libcall --no-event --record' % self.basedir def fixup(self, cflags, result): # handle the difference between python2 and python3 output result = result.replace(" = 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def prerun(self, timeout): script_cmd = '%s script' % (TestBase.uftrace_cmd) p = sp.Popen(script_cmd.split(), stdout=sp.PIPE, stderr=sp.PIPE) if p.communicate()[1].decode(errors='ignore').startswith('WARN:'): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'script' self.option = '-S %s/scripts/dump.lua -a --no-libcall --no-event --record' % self.basedir uftrace-0.15.2/tests/t261_info_note.py000066400000000000000000000044541455365734300175260ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'hello', """ # system information # ================== # program version : v0.10-52-gca6c ( x86_64 dwarf python luajit tui perf sched dynamic ) # recorded on : Mon Aug 16 07:05:58 2021 # cmdline : uftrace record t-hello note.txt # cpu info : ARM64 (v8) # number of cpus : 2 / 2 (online / possible) # memory info : 0.1 / 1.8 GB (free / total) # system load : 1.21 / 1.12 / 1.13 (1 / 5 / 15 min) # kernel version : Linux 5.10.21-200.fc33.aarch64 # hostname : fedora # distro : "Fedora 33 (Workstation Edition) # # process information # =================== # number of tasks : 1 # task list : 19331(t-hello) # exe image : /home/honggyu/work/uftrace/tests/t-hello # build id : bbe710345ed7b36f7c83085837cbf24b1fba00fb # pattern : regex # exit status : exited with code: 0 # elapsed time : 0.008521454 sec # cpu time : 0.007 / 0.001 sec (sys / user) # context switch : 2 / 1 (voluntary / involuntary) # max rss : 4844 KB # page fault : 0 / 349 (major / minor) # disk iops : 0 / 8 (read / write) # # extra note information # ====================== You can leave a note for the recorded data.""") def prerun(self, timeout): self.subcmd = 'record' self.exearg += ' note.txt' record_cmd = self.runcmd() self.pr_debug('prerun command: ' + record_cmd) f = open('/dev/null') sp.call(record_cmd.split(), stdout=f, stderr=f) f.close() return TestBase.TEST_SUCCESS def setup(self): with open('uftrace.data/note.txt', 'w+') as f: f.write('You can leave a note for the recorded data.') self.subcmd = 'info' self.exearg = '' def sort(self, output): header_match = 0 for ln in output.split('\n'): if header_match == 0: if ln.startswith('# extra note information'): header_match = 1 elif header_match == 1: header_match = 2 elif header_match == 2: return ln return '' uftrace-0.15.2/tests/t262_replay_with_syms.py000066400000000000000000000025431455365734300211460ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase SYMDIR = "abc.syms" class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION 62.202 us [28141] | __cxa_atexit(); [28141] | main() { [28141] | a() { [28141] | b() { [28141] | c() { 0.753 us [28141] | getpid(); 1.430 us [28141] | } /* c */ 1.915 us [28141] | } /* b */ 2.405 us [28141] | } /* a */ 3.005 us [28141] | } /* main */ """) def prerun(self, timeout): # record a data with symbols self.subcmd = 'record' self.option = '-d ' + SYMDIR self.exearg = 't-' + self.name record_cmd = self.runcmd() sp.call(record_cmd.split()) self.pr_debug("prerun command1: " + record_cmd) strip_cmd = 'strip ' + self.exearg sp.call(strip_cmd.split()) self.pr_debug("prerun command2: " + strip_cmd) # record again with the stripped binary self.option = '' record_cmd = self.runcmd() sp.call(record_cmd.split()) self.pr_debug("prerun command3: " + record_cmd) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '--with-syms ' + SYMDIR self.exearg = '' uftrace-0.15.2/tests/t263_patchable_dynamic.py000066400000000000000000000020741455365734300211730ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [ 54963] | main() { [ 54963] | a() { [ 54963] | b() { 1.297 us [ 54963] | c(); 3.772 us [ 54963] | } /* b */ 4.376 us [ 54963] | } /* a */ 5.484 us [ 54963] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_arch_full_dynamic_support(self): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def build(self, name, cflags='', ldflags=''): cflags = self.strip_tracing_flags(cflags) # add patchable function entry option machine = TestBase.get_machine(self) if machine == 'x86_64': cflags += ' -fpatchable-function-entry=5' elif machine == 'aarch64': cflags += ' -fpatchable-function-entry=2' return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-P . --no-libcall' uftrace-0.15.2/tests/t264_patchable_dynamic2.py000066400000000000000000000013621455365734300212550ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'patchable-abc', """ # DURATION TID FUNCTION [ 2331] | main() { [ 2331] | a() { 0.897 us [ 2331] | c(); 2.555 us [ 2331] | } /* a */ 3.468 us [ 2331] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_arch_full_dynamic_support(self): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def build(self, name, cflags='', ldflags=''): cflags = self.strip_tracing_flags(cflags) return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-P . --no-libcall' uftrace-0.15.2/tests/t265_patchable_dynamic3.py000066400000000000000000000012631455365734300212570ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'patchable-abc', """ # DURATION TID FUNCTION [ 6907] | main() { 1.138 us [ 6907] | c(); 4.345 us [ 6907] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_arch_full_dynamic_support(self): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def build(self, name, cflags='', ldflags=''): cflags = self.strip_tracing_flags(cflags) return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-P . -U a --no-libcall' uftrace-0.15.2/tests/t266_patchable_dynamic4.py000066400000000000000000000025051455365734300212610ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'lib', """ # DURATION TID FUNCTION [ 9519] | main() { [ 9519] | foo() { [ 9519] | lib_a() { 0.625 us [ 9519] | lib_c(); 1.455 us [ 9519] | } /* lib_a */ 2.125 us [ 9519] | } /* foo */ 3.114 us [ 9519] | } /* main */ """, sort='simple') def prerun(self, timeout): if not TestBase.check_arch_full_dynamic_support(self): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def build(self, name, cflags='', ldflags=''): cflags = self.strip_tracing_flags(cflags) # add patchable function entry option machine = TestBase.get_machine(self) if machine == 'x86_64': cflags += ' -fpatchable-function-entry=5' elif machine == 'aarch64': cflags += ' -fpatchable-function-entry=2' if TestBase.build_libabc(self, cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-libmain.c', ['libabc_test_lib.so'], cflags) def setup(self): self.option = '-P . -P .@libabc_test_lib.so -U lib_b@libabc_test_lib.so --no-libcall' uftrace-0.15.2/tests/t267_patchable_dynamic5.py000066400000000000000000000014311455365734300212600ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'patchable-abc', """ # DURATION TID FUNCTION [ 2331] | main() { [ 2331] | a() { 0.897 us [ 2331] | c(); 2.555 us [ 2331] | } /* a */ 3.468 us [ 2331] | } /* main */ """) def prerun(self, timeout): if not TestBase.check_arch_full_dynamic_support(self): return TestBase.TEST_SKIP return TestBase.TEST_SUCCESS def build(self, name, cflags='', ldflags=''): cflags = self.strip_tracing_flags(cflags) cflags += ' -Wl,--gc-sections' return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-P . --no-libcall' uftrace-0.15.2/tests/t268_record_with_syms.py000066400000000000000000000024251455365734300211350ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase SYMDIR = "abc.syms" class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ # DURATION TID FUNCTION [ 28143] | a() { [ 28143] | b() { [ 28143] | c() { 0.753 us [ 28143] | getpid(); 1.430 us [ 28143] | } /* c */ 1.915 us [ 28143] | } /* b */ 2.167 us [ 28143] | } /* a */ """, sort='simple') def prerun(self, timeout): # record a data with symbols self.subcmd = 'record' self.option = '-d ' + SYMDIR self.exearg = 't-' + self.name record_cmd = self.runcmd() sp.call(record_cmd.split()) self.pr_debug("prerun command1: " + record_cmd) strip_cmd = 'strip ' + self.exearg sp.call(strip_cmd.split()) self.pr_debug("prerun command2: " + strip_cmd) # record again with the stripped binary + with-syms self.option = '-F a --with-syms ' + SYMDIR record_cmd = self.runcmd() sp.call(record_cmd.split()) self.pr_debug("prerun command3: " + record_cmd) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '' self.exearg = '' uftrace-0.15.2/tests/t269_arg_no_args_replay.py000066400000000000000000000024151455365734300214060ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase TDIR='xxx' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exp-str', result=""" # DURATION TID FUNCTION 1.700 us [114150] | __monstartup(); 0.980 us [114150] | __cxa_atexit(); [114150] | main() { 311.089 us [114150] | str_cpy(); 0.734 us [114150] | str_cpy(); 0.597 us [114150] | str_cat(); 0.537 us [114150] | str_cpy(); 0.454 us [114150] | str_cat(); 317.939 us [114150] | } /* main */ """) def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def prerun(self, timeout): record_cmd = '%s record -d %s %s -A ^str_@arg1/s,arg2/s -R ^str_@retval/s %s %s' \ % (TestBase.uftrace_cmd, TDIR, TestBase.default_opt, self.p_flag, 't-' + self.name) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def runcmd(self): return '%s replay -d %s --no-args' % (TestBase.uftrace_cmd, TDIR) def post(self, ret): sp.call(['rm', '-rf', TDIR]) return ret uftrace-0.15.2/tests/t270_arg_no_args_dump.py000066400000000000000000000045751455365734300210600ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase TDIR='xxx' class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'exp-str', result=""" 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 = 0x77b (PLTHOOK | TASK_SESSION | ARGUMENT | RETVAL | SYM_REL_ADDR | MAX_STACK | PERF_EVENT | AUTO_ARGS | DEBUG_INFO) uftrace file header: info = 0x3fff reading 72944.dat 108370.324774953 72944: [entry] __monstartup(aaaacc4c0750) depth: 0 108370.324775495 72944: [exit ] __monstartup(aaaacc4c0750) depth: 0 108370.324776370 72944: [entry] __cxa_atexit(aaaacc4c0720) depth: 0 108370.324776453 72944: [exit ] __cxa_atexit(aaaacc4c0720) depth: 0 108370.324777203 72944: [entry] main(aaaacc4c0a08) depth: 0 108370.324777286 72944: [entry] str_cpy(aaaacc4c09a4) depth: 1 108370.324850202 72944: [exit ] str_cpy(aaaacc4c09a4) depth: 1 108370.324850910 72944: [entry] str_cpy(aaaacc4c09a4) depth: 1 108370.324851243 72944: [exit ] str_cpy(aaaacc4c09a4) depth: 1 108370.324851493 72944: [entry] str_cat(aaaacc4c0918) depth: 1 108370.324851785 72944: [exit ] str_cat(aaaacc4c0918) depth: 1 108370.324851993 72944: [entry] str_cpy(aaaacc4c09a4) depth: 1 108370.324852243 72944: [exit ] str_cpy(aaaacc4c09a4) depth: 1 108370.324852410 72944: [entry] str_cat(aaaacc4c0918) depth: 1 108370.324852660 72944: [exit ] str_cat(aaaacc4c0918) depth: 1 108370.324852827 72944: [exit ] main(aaaacc4c0a08) depth: 0 """, sort='dump') def build(self, name, cflags='', ldflags=''): # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def prerun(self, timeout): record_cmd = '%s record -d %s -A ^str_@arg1/s,arg2/s -R ^str_@retval/s %s %s %s' \ % (TestBase.uftrace_cmd, TDIR, TestBase.default_opt, self.p_flag, 't-' + self.name) sp.call(record_cmd.split()) return TestBase.TEST_SUCCESS def runcmd(self): return '%s dump -d %s --no-args' % (TestBase.uftrace_cmd, TDIR) def post(self, ret): sp.call(['rm', '-rf', TDIR]) return ret uftrace-0.15.2/tests/t271_script_event.py000066400000000000000000000025011455365734300202430ustar00rootroot00000000000000#!/usr/bin/env python3 import os import subprocess as sp from runtest import TestBase FILE='script.py' script = """ def uftrace_entry(ctx): pass def uftrace_exit(ctx): pass def uftrace_event(ctx): args = '' if "args" in ctx: for kv in ctx["args"].split(" "): args += ' ' + kv.split("=")[0] print(ctx["name"] + args) """ class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """ read:proc/statm vmsize vmrss shared diff:proc/statm vmsize vmrss shared """) def prerun(self, timeout): script_cmd = '%s script' % (TestBase.uftrace_cmd) p = sp.Popen(script_cmd.split(), stdout=sp.PIPE, stderr=sp.PIPE) if p.communicate()[1].decode(errors='ignore').startswith('WARN:'): return TestBase.TEST_SKIP f = open(FILE, 'w') f.write(script) f.close() TestBase.default_opt = TestBase.default_opt.replace('--no-event', '') self.subcmd = 'record' self.option = '-T a@read=proc/statm' record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) sp.call(record_cmd.split(), stdout=sp.PIPE) return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'script' self.option = '-S ' + FILE def sort(self, output): return output.strip() uftrace-0.15.2/tests/t272_dump_mermaid.py000066400000000000000000000015211455365734300202030ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'abc', """

Function Call Graph for t-abc

flowchart TB 0_0["t-abc"] -->|1| 1_1["main"]; 1_1["main"] -->|1| 2_2["a"]; 2_2["a"] -->|1| 3_3["b"]; 3_3["b"] -->|1| 4_4["c"];
""", sort='mermaid') def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'dump' self.option = '-F main -D 4 --mermaid' uftrace-0.15.2/tests/t273_agent_basic.py000066400000000000000000000025531455365734300200060ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from time import sleep from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'agent', """ # DURATION TID FUNCTION 34.330 ms [ 22621] | main(); """) def client_send_command(self, pid, option): self.subcmd = 'live' self.option = '-p %d %s' % (pid, option) self.exearg = '' client_cmd = self.runcmd() self.pr_debug('prerun command: ' + client_cmd) client_p = sp.run(client_cmd.split()) return client_p.returncode def prerun(self, timeout): self.subcmd = 'record' self.option = '--agent' self.option += ' --keep-pid' self.option += ' --no-libcall' self.exearg = 't-' + self.name record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) record_p = sp.Popen(record_cmd.split(), stdin=sp.PIPE, stderr=sp.PIPE, bufsize=0) sleep(.05) # time for the agent to start if self.client_send_command(record_p.pid, '') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') record_p.stdin.close() record_p.wait() return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '-D 1' self.exearg = '' uftrace-0.15.2/tests/t274_filter_loc1.py000066400000000000000000000016711455365734300177530ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'lib', """ # DURATION TID FUNCTION [ 11746] | main() { [ 11746] | foo(); [ 11746] | } /* main */ """, sort='simple', cflags='-g') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP if TestBase.build_libabc(self, cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-libmain.c', ['libabc_test_lib.so'], cflags, ldflags) def prepare(self): self.subcmd = 'record' self.option = '--srcline --no-libcall' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-L s-libmain.c$' uftrace-0.15.2/tests/t275_filter_loc2.py000066400000000000000000000020151455365734300177460ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'lib', """ # DURATION TID FUNCTION [ 5295] | lib_a() { [ 5295] | lib_b() { 1.457 us [ 5295] | lib_c(); 2.401 us [ 5295] | } /* lib_b */ 2.736 us [ 5295] | } /* lib_a */ """, sort='simple', cflags='-g') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP if TestBase.build_libabc(self, cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-libmain.c', ['libabc_test_lib.so'], cflags, ldflags) def prepare(self): self.subcmd = 'record' self.option = '--srcline --no-libcall' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-L s-libmain.c@hide' uftrace-0.15.2/tests/t276_filter_loc3.py000066400000000000000000000016021455365734300177510ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'lib', """ # DURATION TID FUNCTION 2.664 us [ 14444] | lib_b(); """, sort='simple', cflags='-g') def build(self, name, cflags='', ldflags=''): if not 'dwarf' in self.feature: return TestBase.TEST_SKIP if TestBase.build_libabc(self, cflags, ldflags) != 0: return TestBase.TEST_BUILD_FAIL return TestBase.build_libmain(self, name, 's-libmain.c', ['libabc_test_lib.so'], cflags, ldflags) def prepare(self): self.subcmd = 'record' self.option = '--srcline --no-libcall' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-L s-lib.c -F lib_b -N lib_c' uftrace-0.15.2/tests/t277_time_range3.py000066400000000000000000000043511455365734300177460ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase END=999999999.999999999 # when uftrace record used a time filter, it sets a default option to apply it # to replay (mostly for schedule events). But it clashed with a time range # option. This test output should not have other events like 'schedule'. class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'sleep', """ # TIMESTAMP FUNCTION 310743.877593890 | main() { 310743.877593950 | foo() { 310743.877594592 | bar() { 310743.877594652 | usleep() { uftrace stopped tracing with remaining functions ================================================ task: 16676 [3] usleep [2] bar [1] foo [0] main """, sort='simple') def prerun(self, timeout): global END # record with a time filter self.subcmd = 'record' self.option = '-t 1ms' record_cmd = self.runcmd() record_cmd.replace('--no-event', '') sp.call(record_cmd.split()) # find timestamp of function 'usleep' self.subcmd = 'replay' self.option = '-f time' replay_cmd = self.runcmd() p = sp.Popen(replay_cmd, shell=True, stdout=sp.PIPE, stderr=sp.PIPE) r = p.communicate()[0].decode(errors='ignore') lines = r.split('\n') if len(lines) < 7: return TestBase.TEST_DIFF_RESULT END = lines[4].split()[0] # skip header, main, foo and bar (= 4) p.wait() TS1 = lines[6].split()[0] # next, next line after usleep f = open('uftrace.data/extern.dat', 'w') f.write("%s %s\n" % (TS1, 'external message')) f.close() return TestBase.TEST_SUCCESS def setup(self): # replay with time range self.subcmd = 'replay' self.option = '-f time -r ~%s' % END def runcmd(self): cmd = TestBase.runcmd(self) return cmd.replace('--no-event', '') def sort(self, output): result = [] for ln in output.split('\n'): # ignore blank lines and task id if ln.strip() == '' or ln.startswith('task:'): continue func = ln.split('|', 1)[-1] result.append(func) return '\n'.join(result) uftrace-0.15.2/tests/t278_size_filter1.py000066400000000000000000000023471455365734300201550ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from runtest import TestBase SIZE = 100 class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'arg', """ # DURATION TID FUNCTION [162500] | main() { 12.486 us [162500] | foo(); 0.505 us [162500] | many(); [162500] | pass() { 0.283 us [162500] | check(); 1.449 us [162500] | } /* pass */ 18.478 us [162500] | } /* main */ """) def prerun(self, timeout): # run 'readelf' tool to read exact symbol size of 'bar' (the smallest one) # and save its 'size + 1' to exclude the function try: output = sp.check_output(['readelf', '-s', 't-arg']) except: return TestBase.TEST_SKIP for ln in output.decode(errors='ignore').split('\n'): # Num: Value Size Type Bind Vis Ndx Name syms = ln.split() if len(syms) < 8: continue if syms[7] == 'bar': global SIZE SIZE = int(syms[2]) + 1 break return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'live' self.option = '-Z %d' % SIZE self.exearg = 't-' + self.name uftrace-0.15.2/tests/t279_size_filter2.py000066400000000000000000000007561455365734300201610ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'arg', """ # DURATION TID FUNCTION [162500] | main() { 12.486 us [162500] | foo(); 0.505 us [162500] | many(); [162500] | pass() { 0.283 us [162500] | check(); 1.449 us [162500] | } /* pass */ 18.478 us [162500] | } /* main */ """) def setup(self): self.option = '-Z 30 -T bar@size=1000' uftrace-0.15.2/tests/t280_size_filter3.py000066400000000000000000000011411455365734300201370ustar00rootroot00000000000000#!/usr/bin/env python3 from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'arg', """ # DURATION TID FUNCTION [162500] | main() { 12.486 us [162500] | foo(); 0.505 us [162500] | many(); [162500] | pass() { 0.283 us [162500] | check(); 1.449 us [162500] | } /* pass */ 18.478 us [162500] | } /* main */ """) def prepare(self): self.subcmd = 'record' return self.runcmd() def setup(self): self.subcmd = 'replay' self.option = '-Z 30 -T bar@size=1000' uftrace-0.15.2/tests/t281_agent_trace_toggle.py000066400000000000000000000044701455365734300213630ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from time import sleep from runtest import TestBase class TestCase(TestBase): """Run the target with the agent activated. Tracing is disabled by the client before triggering the second loop in the target, and restored just after. That way, func() gets executed 3 times in the target, but is only recorded 2 times by uftrace.""" def __init__(self): TestBase.__init__(self, 'agent', """ # DURATION TID FUNCTION [ 5650] | main() { [ 5650] | func() { [ 5650] | a() { [ 5650] | b() { 0.113 us [ 5650] | c(); 1.618 us [ 5650] | } /* b */ 1.910 us [ 5650] | } /* a */ 2.301 us [ 5650] | } /* func */ [ 5650] | func() { [ 5650] | a() { [ 5650] | b() { 0.430 us [ 5650] | c(); 3.898 us [ 5650] | } /* b */ 4.993 us [ 5650] | } /* a */ 6.843 us [ 5650] | } /* func */ 37.310 ms [ 5650] | } /* main */ """) def client_send_command(self, pid, option): self.subcmd = 'live' self.option = '-p %d %s' % (pid, option) self.exearg = '' client_cmd = self.runcmd() self.pr_debug('prerun command: ' + client_cmd) client_p = sp.run(client_cmd.split()) return client_p.returncode def prerun(self, timeout): self.subcmd = 'record' self.option = '--agent' self.option += ' --keep-pid' self.option += ' --no-libcall' self.exearg = 't-' + self.name record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) record_p = sp.Popen(record_cmd.split(), stdin=sp.PIPE, stderr=sp.PIPE, bufsize=0) sleep(.05) # time for the agent to start if self.client_send_command(record_p.pid, '--trace=off') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') if self.client_send_command(record_p.pid, '--trace=on') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') record_p.stdin.close() record_p.wait() return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '' self.exearg = '' uftrace-0.15.2/tests/t282_agent_depth.py000066400000000000000000000044211455365734300200250ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from time import sleep from runtest import TestBase class TestCase(TestBase): """Test changing the tracing depth in the target from the agent. The target itself has an original depth limit of 4. We decrease the depth to 2 and then increase the depth to 5.""" def __init__(self): TestBase.__init__(self, 'agent', """ # DURATION TID FUNCTION [ 14634] | main() { [ 14634] | func() { [ 14634] | a() { 0.135 us [ 14634] | b(); 0.524 us [ 14634] | } /* a */ 0.732 us [ 14634] | } /* func */ 0.279 us [ 14634] | func(); [ 14634] | func() { [ 14634] | a() { [ 14634] | b() { 0.074 us [ 14634] | c(); 0.674 us [ 14634] | } /* b */ 0.874 us [ 14634] | } /* a */ 1.098 us [ 14634] | } /* func */ 37.807 ms [ 14634] | } /* main */ """) def client_send_command(self, pid, option): self.subcmd = 'live' self.option = '-p %d %s' % (pid, option) self.exearg = '' client_cmd = self.runcmd() self.pr_debug('prerun command: ' + client_cmd) client_p = sp.run(client_cmd.split()) return client_p.returncode def prerun(self, timeout): self.subcmd = 'record' self.option = '--keep-pid' self.option += ' --agent' self.option += ' --no-libcall' self.option += ' -D 4' self.exearg = 't-' + self.name record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) # bufsize=0 to write characters one by one record_p = sp.Popen(record_cmd.split(), stdin=sp.PIPE, stderr=sp.PIPE, bufsize=0) sleep(.05) # time for the agent to start if self.client_send_command(record_p.pid, '--depth=2') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') if self.client_send_command(record_p.pid, '--depth=5') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') record_p.stdin.close() record_p.wait() return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '' self.exearg = '' uftrace-0.15.2/tests/t283_agent_time.py000066400000000000000000000052611455365734300176630ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from time import sleep from runtest import TestBase class TestCase(TestBase): """Test changing the time threshold in the target from the agent. The target is started with the '--delay' option so calls to a(), b() and c() include a 1ms sleep delay. The original threshold is 2ms. We decrease it to 1ms and then increase it to 3ms. """ def __init__(self): TestBase.__init__(self, 'agent', """ # DURATION TID FUNCTION [ 6314] | main() { [ 6314] | func() { [ 6314] | a() { 2.134 ms [ 6314] | b(); 3.203 ms [ 6314] | } /* a */ 3.203 ms [ 6314] | } /* func */ [ 6314] | func() { [ 6314] | a() { [ 6314] | b() { 1.112 ms [ 6314] | c(); 2.121 ms [ 6314] | } /* b */ 3.186 ms [ 6314] | } /* a */ 3.187 ms [ 6314] | } /* func */ [ 6314] | func() { 3.207 ms [ 6314] | a(); 3.209 ms [ 6314] | } /* func */ 51.265 ms [ 6314] | } /* main */ """) def client_send_command(self, pid, option): self.subcmd = 'live' self.option = '-p %d %s' % (pid, option) self.exearg = '' client_cmd = self.runcmd() self.pr_debug('prerun command: ' + client_cmd) client_p = sp.run(client_cmd.split()) return client_p.returncode def prerun(self, timeout): self.subcmd = 'record' self.option = '--keep-pid' self.option += ' --agent' self.option += ' --no-libcall' self.option += ' -t 2ms' self.exearg = 't-' + self.name self.exearg += ' --delay' # add 1ms sleep to inner func calls record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) # bufsize=0 to write characters one by one record_p = sp.Popen(record_cmd.split(), stdin=sp.PIPE, stderr=sp.PIPE, bufsize=0) sleep(.05) # time for the agent to start if self.client_send_command(record_p.pid, '--time-filter=1ms') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') if self.client_send_command(record_p.pid, '--time-filter=3ms') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') record_p.stdin.close() record_p.wait() return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '' # NOTE See https://github.com/namhyung/uftrace/issues/1627 # $uftrace dump shows the right output without the following line self.option += ' -t 1ms' self.exearg = '' uftrace-0.15.2/tests/t284_agent_filter.py000066400000000000000000000061121455365734300202070ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from time import sleep from runtest import TestBase class TestCase(TestBase): """Add and remove opt-in and opt-out filters at runtime from the agent. The target runs a loop, and starts with leaf function c() filtered out. We remove the filter, expecting c() to appear in the trace. Then we apply two filters, and remove them one by one. """ def __init__(self): TestBase.__init__(self, 'agent', """ # DURATION TID FUNCTION [ 24808] | main() { [ 24808] | func() { [ 24808] | a() { 2.115 ms [ 24808] | b(); 3.178 ms [ 24808] | } /* a */ 3.179 ms [ 24808] | } /* func */ [ 24808] | func() { [ 24808] | a() { [ 24808] | b() { 1.058 ms [ 24808] | c(); 2.122 ms [ 24808] | } /* b */ 3.195 ms [ 24808] | } /* a */ 3.196 ms [ 24808] | } /* func */ 2.124 ms [ 24808] | b(); [ 24808] | func() { [ 24808] | a() { 2.135 ms [ 24808] | b(); 3.207 ms [ 24808] | } /* a */ 3.208 ms [ 24808] | } /* func */ [ 24808] | func() { [ 24808] | a() { [ 24808] | b() { 1.068 ms [ 24808] | c(); 2.137 ms [ 24808] | } /* b */ 3.206 ms [ 24808] | } /* a */ 3.206 ms [ 24808] | } /* func */ 58.216 ms [ 24808] | } /* main */ """) def client_send_command(self, pid, option): self.subcmd = 'live' self.option = '-p %d %s' % (pid, option) self.exearg = '' client_cmd = self.runcmd() self.pr_debug('prerun command: ' + client_cmd) client_p = sp.run(client_cmd.split()) return client_p.returncode def prerun(self, timeout): self.subcmd = 'record' self.option = '--keep-pid' self.option += ' --no-libcall' self.option += ' --agent' self.option += ' -N c' self.exearg = 't-' + self.name record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) # bufsize=0 to write characters one by one record_p = sp.Popen(record_cmd.split(), stdin=sp.PIPE, stderr=sp.PIPE, bufsize=0) sleep(.05) # time for the agent to start if self.client_send_command(record_p.pid, '-F c@clear') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') if self.client_send_command(record_p.pid, '-F b -N c') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') if self.client_send_command(record_p.pid, '-F b@clear') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') if self.client_send_command(record_p.pid, '-F c@clear') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') record_p.stdin.close() record_p.wait() return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '' self.exearg = '' uftrace-0.15.2/tests/t285_agent_caller_filter.py000066400000000000000000000055261455365734300215420ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from time import sleep from runtest import TestBase class TestCase(TestBase): """Add and remove caller filters at runtime from the agent. The target runs a loop, and starts with a caller filter on b(). We add one on c() so all functions appear in the trace. Then we proceed to leave only a caller filter on a() so it appears as a leaf in the trace. """ def __init__(self): TestBase.__init__(self, 'agent', """ # DURATION TID FUNCTION [ 30262] | main() { [ 30262] | func() { [ 30262] | a() { 2.092 ms [ 30262] | b(); 3.160 ms [ 30262] | } /* a */ 3.160 ms [ 30262] | } /* func */ [ 30262] | func() { [ 30262] | a() { [ 30262] | b() { 1.075 ms [ 30262] | c(); 2.152 ms [ 30262] | } /* b */ 3.222 ms [ 30262] | } /* a */ 3.223 ms [ 30262] | } /* func */ [ 30262] | func() { [ 30262] | a() { [ 30262] | b() { 1.063 ms [ 30262] | c(); 2.128 ms [ 30262] | } /* b */ 3.200 ms [ 30262] | } /* a */ 3.200 ms [ 30262] | } /* func */ [ 30262] | func() { [ 30262] | a(); 3.198 ms [ 30262] | } /* func */ 37.973 ms [ 30262] | } /* main */ """) def client_send_command(self, pid, option): self.subcmd = 'live' self.option = '-p %d %s' % (pid, option) self.exearg = '' client_cmd = self.runcmd() self.pr_debug('prerun command: ' + client_cmd) client_p = sp.run(client_cmd.split()) return client_p.returncode def prerun(self, timeout): self.subcmd = 'record' self.option = '--keep-pid' self.option += ' --no-libcall' self.option += ' --agent' self.option += ' -C b' self.exearg = 't-' + self.name record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) # bufsize=0 to write characters one by one record_p = sp.Popen(record_cmd.split(), stdin=sp.PIPE, stderr=sp.PIPE, bufsize=0) sleep(.05) # time for the agent to start if self.client_send_command(record_p.pid, '-C c') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') if self.client_send_command(record_p.pid, '-C b@clear -C a') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') if self.client_send_command(record_p.pid, '-C c@clear') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') record_p.stdin.close() record_p.wait() return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '' self.exearg = '' uftrace-0.15.2/tests/t286_agent_trigger.py000066400000000000000000000061721455365734300203750ustar00rootroot00000000000000#!/usr/bin/env python3 import subprocess as sp from time import sleep from runtest import TestBase class TestCase(TestBase): """Add and remove triggers at runtime from the client. The target runs a loop and waits for input from this script. The agent is enabled. Calls to a(), b() and c() include a sleep period so we can test time filters. The client adds then removes a depth filter. Then adds a time filter. And adds then removes an opt-out filter. """ def __init__(self): TestBase.__init__(self, 'agent', """ # DURATION TID FUNCTION [ 6999] | main() { [ 6999] | func() { [ 6999] | a() { [ 6999] | b() { 1.056 ms [ 6999] | c(); 2.113 ms [ 6999] | } /* b */ 3.175 ms [ 6999] | } /* a */ 3.175 ms [ 6999] | } /* func */ [ 6999] | func() { [ 6999] | a() { 2.110 ms [ 6999] | b(); 3.181 ms [ 6999] | } /* a */ 3.182 ms [ 6999] | } /* func */ [ 6999] | func() { 3.178 ms [ 6999] | a(); 3.179 ms [ 6999] | } /* func */ 3.177 ms [ 6999] | func(); [ 6999] | func() { [ 6999] | a() { [ 6999] | b() { 1.057 ms [ 6999] | c(); 2.119 ms [ 6999] | } /* b */ 3.185 ms [ 6999] | } /* a */ 3.185 ms [ 6999] | } /* func */ 55.835 ms [ 6999] | } /* main */ """) def client_send_command(self, pid, option): self.subcmd = 'live' self.option = '-p %d %s' % (pid, option) self.exearg = '' client_cmd = self.runcmd() self.pr_debug('prerun command: ' + client_cmd) client_p = sp.run(client_cmd.split()) return client_p.returncode def prerun(self, timeout): self.subcmd = 'record' self.option = '--keep-pid' self.option += ' --no-libcall' self.option += ' --agent' self.exearg = 't-' + self.name self.exearg += ' --delay' record_cmd = self.runcmd() self.pr_debug("prerun command: " + record_cmd) # bufsize=0 to write characters one by one record_p = sp.Popen(record_cmd.split(), stdin=sp.PIPE, stderr=sp.PIPE, bufsize=0) sleep(.05) # time for the agent to start if self.client_send_command(record_p.pid, '-T func@depth=3') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') if self.client_send_command(record_p.pid, '-T func@clear=depth,time=3ms') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') if self.client_send_command(record_p.pid, '-T a@notrace') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') if self.client_send_command(record_p.pid, '-T a@clear -T func@clear') != 0: return TestBase.TEST_NONZERO_RETURN record_p.stdin.write(b'0') record_p.stdin.close() record_p.wait() return TestBase.TEST_SUCCESS def setup(self): self.subcmd = 'replay' self.option = '' self.exearg = '' uftrace-0.15.2/tests/t287_arg_enum3.py000066400000000000000000000016001455365734300174240ustar00rootroot00000000000000#!/usr/bin/env python3 import re from runtest import TestBase class TestCase(TestBase): def __init__(self): TestBase.__init__(self, 'enum2', result=""" # DURATION TID FUNCTION [ 57041] | main() { 0.535 us [ 57041] | foo(memory_order_mask); 0.109 us [ 57041] | foo(memory_order_modifier_mask); 0.069 us [ 57041] | foo(memory_order_hle_acquire); 0.068 us [ 57041] | foo(memory_order_hle_release); 1.783 us [ 57041] | } = 0; /* main */ """, cflags='-g') def build(self, name, cflags='', ldflags=''): if not "dwarf" in self.feature: return TestBase.TEST_SKIP # cygprof doesn't support arguments now if cflags.find('-finstrument-functions') >= 0: return TestBase.TEST_SKIP return TestBase.build(self, name, cflags, ldflags) def setup(self): self.option = '-F main -a' uftrace-0.15.2/tests/unittest.c000066400000000000000000000210241455365734300164330ustar00rootroot00000000000000#include #include #include #include #include #include #include #include "tests/unittest.h" #include "utils/symbol.h" #include "utils/utils.h" static bool color = true; /* example test case */ TEST_CASE(unittest_framework) { static const char hello[] = "Hello"; pr_dbg("check basic test macro\n"); TEST_EQ(1 + 1, 2); TEST_NE(true, false); TEST_GT(1 * 2, 0 * 2); TEST_GE(1.0, 1); TEST_LT(0 * 2, 1); TEST_LE(0.0, 0); pr_dbg("check string test macro\n"); TEST_STREQ("Hello", hello); TEST_MEMEQ("Hello", hello, sizeof(hello)); return TEST_OK; } static const char *retcodes[] = { TERM_COLOR_GREEN "PASS" TERM_COLOR_RESET, TERM_COLOR_RED "FAIL" TERM_COLOR_RESET, TERM_COLOR_YELLOW "SKIP" TERM_COLOR_RESET, TERM_COLOR_RED "SIG " TERM_COLOR_RESET, TERM_COLOR_RED "BAD " TERM_COLOR_RESET, }; static const char *retcodes_nocolor[] = { "PASS", "FAIL", "SKIP", "SIG ", "BAD ", }; static const char *messages[] = { "ran successfully", "failed", "skipped", "signal caught", "unknown result", }; static void set_debug_domain(struct uftrace_unit_test *test) { #define DOMAIN(x) \ { \ #x, DBG_##x \ } struct { char *name; int domain; } domains[] = { DOMAIN(SYMBOL), DOMAIN(DEMANGLE), DOMAIN(FILTER), DOMAIN(FSTACK), DOMAIN(SESSION), DOMAIN(KERNEL), DOMAIN(MCOUNT), DOMAIN(DYNAMIC), DOMAIN(EVENT), DOMAIN(SCRIPT), DOMAIN(DWARF), /* some fixup domains */ { "task", DBG_SESSION }, { "argspec", DBG_FILTER }, { "trigger", DBG_FILTER }, { "python", DBG_UFTRACE }, }; unsigned int i; int count = 0; for (i = 0; i < ARRAY_SIZE(domains); i++) { if (strcasestr(test->name, domains[i].name)) { dbg_domain[domains[i].domain] = debug; count++; } } if (count == 0) dbg_domain[DBG_UFTRACE] = debug; } static void run_unit_test(struct uftrace_unit_test *test, int *test_stats, char *filter) { static int count; int status; int ret = TEST_BAD; if (filter && !strstr(test->name, filter)) return; if (debug) { printf("Testing %s...\n", test->name); fflush(stdout); } if (!fork()) { set_debug_domain(test); exit(test->func()); } wait(&status); if (WIFSIGNALED(status)) ret = TEST_SIG; else if (WIFEXITED(status)) ret = WEXITSTATUS(status); /* OK or NG */ if (ret < 0 || ret >= TEST_MAX) ret = TEST_BAD; test_stats[ret]++; printf("[%03d] %-30s: %s\n", ++count, test->name, color ? retcodes[ret] : retcodes_nocolor[ret]); if (debug) printf("-------------\n"); fflush(stdout); } static unsigned long load_base; static int find_load_base(struct dl_phdr_info *info, size_t size, void *arg) { unsigned i; if (info->dlpi_name[0] != '\0') return 0; /* not a PIE binary */ if (info->dlpi_addr == 0) return 1; for (i = 0; i < info->dlpi_phnum; i++) { if (info->dlpi_phdr[i].p_type == PT_LOAD) { load_base = info->dlpi_addr - info->dlpi_phdr[i].p_vaddr; break; } } return 1; } static int sort_tests(const void *tc1, const void *tc2) { const struct uftrace_unit_test *test1 = tc1; const struct uftrace_unit_test *test2 = tc2; /* keep unittest_framework first */ if (!strcmp(test1->name, "unittest_framework")) return -1; if (!strcmp(test2->name, "unittest_framework")) return 1; return strcmp(test1->name, test2->name); } static bool free_test_names; /* * RISC-V GCC doesn't set uftrace_unit_test data in the uftrace.test section * (#1833). So it should read the unittest binary to find the symbol table * and fill the data manually. * * Note that the unit test functions are global functions start with "func_" * prefix. Other (global) functions should not start with that. */ static void update_test_cases(struct uftrace_unit_test *tcases, int num, struct uftrace_elf_data *elf) { struct uftrace_elf_iter iter; int len = strlen(TEST_FUNC_PREFIX_STR); int i = 0; printf("update test cases from binary\n"); elf_for_each_shdr(elf, &iter) { if (iter.shdr.sh_type != SHT_SYMTAB) continue; elf_for_each_symbol(elf, &iter) { typeof(iter.sym) *sym = &iter.sym; const char *name = elf_get_name(elf, &iter, sym->st_name); if (elf_symbol_type(sym) != STT_FUNC || elf_symbol_bind(sym) != STB_GLOBAL) continue; if (strncmp(name, TEST_FUNC_PREFIX_STR, len)) continue; tcases[i].func = (void *)sym->st_value + load_base; tcases[i].name = strdup(name + len); i++; } /* * It applied the base address already, make sure not to do it * twice. */ load_base = 0; free_test_names = true; break; } } static int setup_unit_test(struct uftrace_unit_test **test_cases, size_t *test_num, char *filter) { char *exename; struct uftrace_elf_data elf; struct uftrace_elf_iter iter; struct uftrace_unit_test *tcases; bool found_unittest = false; size_t sec_size; unsigned i, num, actual; int ret = -1; exename = read_exename(); if (elf_init(exename, &elf) < 0) { printf("error during load ELF header: %s\n", exename); return -1; } elf_for_each_shdr(&elf, &iter) { char *shstr; shstr = elf_get_name(&elf, &iter, iter.shdr.sh_name); if (strcmp(shstr, "uftrace.unit_test") == 0) { sec_size = iter.shdr.sh_size; found_unittest = true; break; } } if (!found_unittest) { printf("cannot find unit test data\n"); goto out; } dl_iterate_phdr(find_load_base, NULL); tcases = xmalloc(sec_size); num = sec_size / sizeof(*tcases); elf_get_secdata(&elf, &iter); elf_read_secdata(&elf, &iter, 0, tcases, sec_size); /* check if test cases are set properly */ for (i = 0, actual = 0; i < num; i++) { if (tcases[i].func && tcases[i].name) actual++; } if (actual != num) update_test_cases(tcases, num, &elf); /* relocate section symbols in case of PIE */ for (i = 0, actual = 0; i < num && (load_base || filter); i++) { struct uftrace_unit_test *tc = &tcases[i]; unsigned long faddr = (unsigned long)tc->func; unsigned long naddr = (unsigned long)tc->name; if (load_base) { faddr += load_base; naddr += load_base; tc->func = (void *)faddr; tc->name = (void *)naddr; } if (filter && strstr(tc->name, filter)) actual++; } if (!filter) actual = num; printf("Running %u test cases\n======================\n", actual); qsort(tcases, num, sizeof(*tcases), sort_tests); *test_cases = tcases; *test_num = num; ret = 0; out: elf_finish(&elf); return ret; } static int finish_unit_test(struct uftrace_unit_test *test_cases, int test_num, int *test_stats) { int i; printf("\nunit test stats\n====================\n"); for (i = 0; i < TEST_MAX; i++) printf("%3d %s\n", test_stats[i], messages[i]); printf("\n"); if (free_test_names) { for (i = 0; i < test_num; i++) free((void *)test_cases[i].name); } free(test_cases); return test_stats[TEST_NG] + test_stats[TEST_BAD] + test_stats[TEST_SIG] > 0 ? EXIT_FAILURE : EXIT_SUCCESS; } int __attribute__((weak)) arch_fill_cpuinfo_model(int fd) { return 0; } void mcount_return(void) { } void plthook_return(void) { } void dynamic_return(void) { } void __fentry__(void) { } void __dentry__(void) { } void __xray_entry(void) { } void __xray_exit(void) { } #undef main int main(int argc, char *argv[]) { struct uftrace_unit_test *test_cases = NULL; int test_stats[TEST_MAX] = {}; size_t i, test_num = 0; char *filter = NULL; char *term; int c; bool color_set = false; while ((c = getopt(argc, argv, "dvnpiO:f:-:")) != -1) { switch (c) { case 'd': case 'v': debug = 1; break; case 'n': color = false; color_set = true; break; case '-': /* poor man's getopt_long() */ if (!strncmp(optarg, "color", 5)) { char *arg; if (optarg[5] == '=') arg = optarg + 6; else arg = argv[optind++]; if (!strcmp(arg, "on") || !strcmp(arg, "1")) color = true; if (!strcmp(arg, "off") || !strcmp(arg, "0")) color = false; color_set = true; } break; default: break; } } if (optind < argc) filter = argv[optind]; outfp = logfp = stdout; if (setup_unit_test(&test_cases, &test_num, filter) < 0) { printf("Cannot run unit tests - failed to load test cases\n"); return -1; } if (!color_set) { term = getenv("TERM"); if (term && !strcmp(term, "dumb")) color = false; if (!isatty(STDIN_FILENO)) color = false; } for (i = 0; i < test_num; i++) run_unit_test(&test_cases[i], test_stats, filter); return finish_unit_test(test_cases, test_num, test_stats); } uftrace-0.15.2/tests/unittest.h000066400000000000000000000214361455365734300164470ustar00rootroot00000000000000#ifndef UFTRACE_UNIT_TEST_H #define UFTRACE_UNIT_TEST_H #include #include enum { TEST_OK = 0, /* success */ TEST_NG, /* failed */ TEST_SKIP, /* skipped */ TEST_SIG, /* signal caught */ TEST_BAD, /* unknown result */ TEST_MAX, }; #define stringify(s) __stringify(s) #define __stringify(s) #s extern int debug; #define __TEST_NG(file, line, test, name_a, a, name_b, b) \ ({ \ if (debug) { \ printf("test failed at %s:%d: %s\n", file, line, test); \ printf(" %-16s = %ld\n", name_a, (long)a); \ printf(" %-16s = %ld\n", name_b, (long)b); \ fflush(stdout); \ } \ return TEST_NG; \ }) #define __TEST_OP(a, op, b, file, line) \ ({ \ const char *name_a; \ const char *name_b; \ __typeof__(a) __a = (a); \ __typeof__(b) __b = (b); \ \ if (__builtin_constant_p(a)) \ name_a = "value_1"; \ else \ name_a = stringify(a); \ \ if (__builtin_constant_p(b)) \ name_b = "value_2"; \ else \ name_b = stringify(b); \ \ if (!(__a op __b)) \ __TEST_NG(file, line, stringify(a op b), name_a, __a, name_b, __b); \ TEST_OK; \ }) #define TEST_EQ(a, b) __TEST_OP(a, ==, b, __FILE__, __LINE__) #define TEST_NE(a, b) __TEST_OP(a, !=, b, __FILE__, __LINE__) #define TEST_GT(a, b) __TEST_OP(a, >, b, __FILE__, __LINE__) #define TEST_GE(a, b) __TEST_OP(a, >=, b, __FILE__, __LINE__) #define TEST_LT(a, b) __TEST_OP(a, <, b, __FILE__, __LINE__) #define TEST_LE(a, b) __TEST_OP(a, <=, b, __FILE__, __LINE__) #define __TEST_STRNG(file, line, test, name_a, a, name_b, b) \ ({ \ if (debug) { \ printf("test failed at %s:%d: %s\n", file, line, test); \ printf(" %-16s = %s\n", name_a, a); \ printf(" %-16s = %s\n", name_b, b); \ } \ return TEST_NG; \ }) #define __TEST_STREQ(a, b, file, line) \ ({ \ const char *name_a; \ const char *name_b; \ const char *__a = (a); \ const char *__b = (b); \ \ if (__builtin_constant_p(a)) \ name_a = "expected"; \ else \ name_a = stringify(a); \ \ if (__builtin_constant_p(b)) \ name_b = "actual"; \ else \ name_b = stringify(b); \ \ if (strcmp(__a, __b)) \ __TEST_STRNG(file, line, stringify(a == b), name_a, __a, name_b, __b); \ TEST_OK; \ }) #define TEST_STREQ(a, b) __TEST_STREQ(a, b, __FILE__, __LINE__) #define __TEST_MEMEQ(a, b, sz, file, line) \ ({ \ const void *__a = (a); \ const void *__b = (b); \ \ if (memcmp(__a, __b, (sz))) \ __TEST_NG(file, line, stringify(a == b), stringify(a), __a, stringify(b), \ __b); \ TEST_OK; \ }) #define TEST_MEMEQ(a, b, sz) __TEST_MEMEQ((a), (b), (sz), __FILE__, __LINE__) #define TEST_SECTION "uftrace.unit_test" struct uftrace_unit_test { const char *name; int (*func)(void); }; #define TEST_FUNC_PREFIX func_ #define TEST_FUNC_PREFIX_STR stringify(TEST_FUNC_PREFIX) #define __concat(a, b) a##b #define concat(a, b) __concat(a, b) #define TEST_FUNC_NAME(t) concat(TEST_FUNC_PREFIX, t) #ifdef __clang__ #define TEST_CASE(t) \ extern int TEST_FUNC_NAME(t)(void); \ \ __attribute__((section(TEST_SECTION), used, no_sanitize("address"))) \ const struct uftrace_unit_test test_##t = { \ .name = stringify(t), \ .func = TEST_FUNC_NAME(t), \ }; \ \ int TEST_FUNC_NAME(t)(void) #else /* #ifdef __clang__ */ #define TEST_CASE(t) \ extern int TEST_FUNC_NAME(t)(void); \ \ __attribute__((section(TEST_SECTION), used)) const struct uftrace_unit_test test_##t = { \ .name = stringify(t), \ .func = TEST_FUNC_NAME(t), \ }; \ \ int TEST_FUNC_NAME(t)(void) #endif /* #ifdef __clang__ */ #define TERM_COLOR_NORMAL "" #define TERM_COLOR_RESET "\033[0m" #define TERM_COLOR_BOLD "\033[1m" #define TERM_COLOR_RED "\033[91m" #define TERM_COLOR_GREEN "\033[32m" #define TERM_COLOR_YELLOW "\033[33m" #endif /* UFTRACE_UNIT_TEST_H */ uftrace-0.15.2/uftrace-gdb.py000066400000000000000000000012321455365734300160020ustar00rootroot00000000000000# # gdb helper commands and functions for uftrace debugging # copied from the Linux kernel source # # loader module # # Copyright (c) Siemens AG, 2012, 2013 # # Authors: # Jan Kiszka # # This work is licensed under the terms of the GNU GPL version 2. # import os sys.path.insert(0, os.path.dirname(__file__) + "/gdb") try: gdb.parse_and_eval("0") gdb.execute("", to_string=True) except: gdb.write("NOTE: gdb 7.2 or later required for helper scripts to work.\n") else: import uftrace.lists import uftrace.mcount import uftrace.plthook import uftrace.rbtree import uftrace.trigger import uftrace.utils uftrace-0.15.2/uftrace.c000066400000000000000000001314411455365734300150500ustar00rootroot00000000000000/* * uftrace - Function (Graph) Tracer for Userspace * * Copyright (C) 2014-2018 LG Electronics * Author: Namhyung Kim * * 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; version 2 of the License. * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "uftrace" #include "uftrace.h" #include "utils/script.h" #include "utils/utils.h" #include "version.h" static const char uftrace_version[] = "uftrace " UFTRACE_VERSION; static bool dbg_domain_set = false; static bool parsing_default_opts = false; enum uftrace_short_options { OPT_flat = 301, OPT_no_libcall, OPT_symbols, OPT_logfile, OPT_force, OPT_task, OPT_no_merge, OPT_nop, OPT_time, OPT_max_stack, OPT_host, OPT_port, OPT_nopager, OPT_avg_total, OPT_avg_self, OPT_color, OPT_disabled, OPT_trace, OPT_demangle, OPT_dbg_domain, OPT_report, OPT_column_view, OPT_column_offset, OPT_bind_not, OPT_task_newline, OPT_chrome_trace, OPT_flame_graph, OPT_graphviz, OPT_sample_time, OPT_diff, OPT_format, OPT_sort_column, OPT_tid_filter, OPT_num_thread, OPT_no_comment, OPT_libmcount_single, OPT_rt_prio, OPT_kernel_bufsize, OPT_kernel_skip_out, OPT_kernel_full, OPT_kernel_only, OPT_list_event, OPT_run_cmd, OPT_opt_file, OPT_keep_pid, OPT_diff_policy, OPT_event_full, OPT_record, OPT_no_args, OPT_libname, OPT_match_type, OPT_no_randomize_addr, OPT_no_event, OPT_no_sched, OPT_no_sched_preempt, OPT_signal, OPT_srcline, OPT_with_syms, OPT_clock, OPT_usage, OPT_libmcount_path, OPT_mermaid, OPT_library_path, OPT_loc_filter, }; /* clang-format off */ __used static const char uftrace_usage[] = " uftrace -- function (graph) tracer for userspace\n" "\n" " usage: uftrace [COMMAND] [OPTION...] []\n" "\n" " COMMAND:\n" " record Run a program and saves the trace data\n" " replay Show program execution in the trace data\n" " report Show performance statistics in the trace data\n" " live Do record and replay in a row (default)\n" " info Show system and program info in the trace data\n" " dump Show low-level trace data\n" " recv Save the trace data from network\n" " graph Show function call graph in the trace data\n" " script Run a script for recorded trace data\n" " tui Show text user interface for graph and report\n" "\n"; __used static const char uftrace_help[] = " OPTION:\n" " --avg-self Show average/min/max of self function time\n" " --avg-total Show average/min/max of total function time\n" " -a, --auto-args Show arguments and return value of known functions\n" " -A, --argument=FUNC@arg[,arg,...]\n" " Show function arguments\n" " -b, --buffer=SIZE Size of tracing buffer (default: " stringify(SHMEM_BUFFER_SIZE_KB) "K)\n" " --chrome Dump recorded data in chrome trace format\n" " --clock Set clock source for timestamp (default: mono)\n" " --color=SET Use color for output: yes, no, auto (default: auto)\n" " --column-offset=DEPTH Offset of each column (default: " stringify(OPT_COLUMN_OFFSET) ")\n" " --column-view Print tasks in separate columns\n" " -C, --caller-filter=FUNC Only trace callers of those FUNCs\n" " -d, --data=DATA Use this DATA instead of uftrace.data\n" " --debug-domain=DOMAIN Filter debugging domain\n" " --demangle=TYPE C++ symbol demangling: full, simple, no\n" " (default: simple)\n" " --diff=DATA Report differences\n" " --diff-policy=POLICY Control diff report policy\n" " (default: 'abs,compact,no-percent')\n" " --disable Start with tracing disabled (deprecated)\n" " -D, --depth=DEPTH Trace functions within DEPTH\n" " -e, --estimate-return Use only entry record type for safety\n" " --event-full Show all events outside of function\n" " -E, --Event=EVENT Enable EVENT to save more information\n" " --flame-graph Dump recorded data in FlameGraph format\n" " --flat Use flat output format\n" " --force Trace even if executable is not instrumented\n" " --format=FORMAT Use FORMAT for output: normal, html (default: normal)\n" " -f, --output-fields=FIELD Show FIELDs in the replay or graph output\n" " -F, --filter=FUNC Only trace those FUNCs\n" " -g --agent Start an agent in mcount to listen to commands\n" " --graphviz Dump recorded data in DOT format\n" " -H, --hide=FUNC Hide FUNCs from trace\n" " --host=HOST Send trace data to HOST instead of write to file\n" " -k, --kernel Trace kernel functions also (if supported)\n" " --keep-pid Keep same pid during execution of traced program\n" " --kernel-buffer=SIZE Size of kernel tracing buffer (default: 1408K)\n" " --kernel-full Show kernel functions outside of user\n" " --kernel-only Dump kernel data only\n" " --kernel-skip-out Skip kernel functions outside of user (deprecated)\n" " -K, --kernel-depth=DEPTH Trace kernel functions within DEPTH\n" " --libmcount-single Use single thread version of libmcount\n" " --list-event List available events\n" " -L, --loc-filter=LOCATION Only trace functions in the source LOCATION\n" " --logfile=FILE Save log messages to this file\n" " -l, --nest-libcall Show nested library calls\n" " --libname Show libname name with symbol name\n" " --libmcount-path=PATH Load libmcount libraries from this PATH\n" " --match=TYPE Support pattern match: regex, glob (default:\n" " regex)\n" " --max-stack=DEPTH Set max stack depth to DEPTH (default: " stringify(OPT_RSTACK_MAX) ")\n" " --no-args Do not show arguments and return value\n" " --no-comment Don't show comments of returned functions\n" " --no-event Disable (default) events\n" " --no-sched Disable schedule events\n" " --no-sched-preempt Hide pre-emptive schedule event\n" " but show regular(sleeping) schedule event\n" " --no-libcall Don't trace library function calls\n" " --no-merge Don't merge leaf functions\n" " --no-pager Do not use pager\n" " --no-pltbind Do not bind dynamic symbols (LD_BIND_NOT)\n" " --no-randomize-addr Disable ASLR (Address Space Layout Randomization)\n" " --nop No operation (for performance test)\n" " --num-thread=NUM Create NUM recorder threads\n" " -N, --notrace=FUNC Don't trace those FUNCs\n" " --opt-file=FILE Read command-line options from FILE\n" " -p --pid=PID PID of an interactive mcount instance\n" " --port=PORT Use PORT for network connection (default: " stringify(UFTRACE_RECV_PORT) ")\n" " -P, --patch=FUNC Apply dynamic patching for FUNCs\n" " --record Record a new trace data before running command\n" " --report Show live report\n" " --rt-prio=PRIO Record with real-time (FIFO) priority\n" " -r, --time-range=TIME~TIME Show output within the TIME(timestamp or elapsed time)\n" " range only\n" " --run-cmd=CMDLINE Command line that want to execute after tracing\n" " data received\n" " -R, --retval=FUNC@retval Show function return value\n" " --sample-time=TIME Show flame graph with this sampling time\n" " --signal=SIG@act[,act,...] Trigger action on those SIGnal\n" " --sort-column=INDEX Sort diff report on column INDEX (default: 2)\n" " --srcline Enable recording source line info\n" " --symbols Print symbol tables\n" " -s, --sort=KEY[,KEY,...] Sort reported functions by KEYs (default: " stringify(OPT_SORT_COLUMN) ")\n" " -S, --script=SCRIPT Run a given SCRIPT in function entry and exit\n" " -t, --time-filter=TIME Hide small functions run less than the TIME\n" " --task Show task info instead\n" " --task-newline Interleave a newline when task is changed\n" " --tid=TID[,TID,...] Only replay those tasks\n" " --time Print time information\n" " --trace=STATE Set the recording state: on, off (default: on)\n" " -T, --trigger=FUNC@act[,act,...]\n" " Trigger action on those FUNCs\n" " -U, --unpatch=FUNC Don't apply dynamic patching for FUNCs\n" " -v, --debug Print debug messages\n" " --verbose Print verbose (debug) messages\n" " --with-syms=DIR Use symbol files in the DIR\n" " -W, --watch=POINT Watch and report POINT if it's changed\n" " -Z, --size-filter=SIZE Apply dynamic patching for functions bigger than SIZE\n" " -h, --help Give this help list\n" " --usage Give a short usage message\n" " -V, --version Print program version\n" "\n" " Try `man uftrace [COMMAND]' for more information.\n" "\n"; __used static const char uftrace_footer[] = " Try `uftrace --help' or `man uftrace [COMMAND]' for more information.\n" "\n"; static const char uftrace_shopts[] = "+aA:b:C:d:D:eE:f:F:ghH:kK:lL:N:p:P:r:R:s:S:t:T:U:vVW:Z:"; #define REQ_ARG(name, shopt) { #name, required_argument, 0, shopt } #define NO_ARG(name, shopt) { #name, no_argument, 0, shopt } static const struct option uftrace_options[] = { REQ_ARG(libmcount-path, OPT_libmcount_path), REQ_ARG(library-path, OPT_libmcount_path), REQ_ARG(filter, 'F'), REQ_ARG(notrace, 'N'), REQ_ARG(depth, 'D'), REQ_ARG(time-filter, 't'), REQ_ARG(caller-filter, 'C'), REQ_ARG(argument, 'A'), REQ_ARG(trigger, 'T'), REQ_ARG(retval, 'R'), NO_ARG(auto-args, 'a'), NO_ARG(no-args, OPT_no_args), REQ_ARG(patch, 'P'), REQ_ARG(unpatch, 'U'), REQ_ARG(size-filter, 'Z'), NO_ARG(debug, 'v'), NO_ARG(verbose, 'v'), REQ_ARG(debug-domain, OPT_dbg_domain), NO_ARG(force, OPT_force), REQ_ARG(data, 'd'), NO_ARG(flat, OPT_flat), NO_ARG(symbols, OPT_symbols), REQ_ARG(buffer, 'b'), REQ_ARG(logfile, OPT_logfile), NO_ARG(task, OPT_task), REQ_ARG(tid, OPT_tid_filter), NO_ARG(no-merge, OPT_no_merge), NO_ARG(nop, OPT_nop), NO_ARG(time, OPT_time), REQ_ARG(max-stack, OPT_max_stack), REQ_ARG(host, OPT_host), REQ_ARG(port, OPT_port), NO_ARG(no-pager, OPT_nopager), REQ_ARG(sort, 's'), NO_ARG(avg-total, OPT_avg_total), NO_ARG(avg-self, OPT_avg_self), REQ_ARG(color, OPT_color), NO_ARG(disable, OPT_disabled), REQ_ARG(trace, OPT_trace), REQ_ARG(demangle, OPT_demangle), NO_ARG(record, OPT_record), NO_ARG(report, OPT_report), NO_ARG(column-view, OPT_column_view), REQ_ARG(column-offset, OPT_column_offset), NO_ARG(no-pltbind, OPT_bind_not), NO_ARG(task-newline, OPT_task_newline), NO_ARG(chrome, OPT_chrome_trace), NO_ARG(graphviz, OPT_graphviz), NO_ARG(flame-graph, OPT_flame_graph), NO_ARG(mermaid, OPT_mermaid), REQ_ARG(sample-time, OPT_sample_time), REQ_ARG(diff, OPT_diff), REQ_ARG(format, OPT_format), REQ_ARG(sort-column, OPT_sort_column), REQ_ARG(num-thread, OPT_num_thread), NO_ARG(no-comment, OPT_no_comment), NO_ARG(libmcount-single, OPT_libmcount_single), REQ_ARG(rt-prio, OPT_rt_prio), NO_ARG(kernel, 'k'), REQ_ARG(kernel-depth, 'K'), REQ_ARG(kernel-buffer, OPT_kernel_bufsize), NO_ARG(kernel-skip-out, OPT_kernel_skip_out), NO_ARG(kernel-full, OPT_kernel_full), NO_ARG(kernel-only, OPT_kernel_only), REQ_ARG(output-fields, 'f'), REQ_ARG(time-range, 'r'), REQ_ARG(Event, 'E'), NO_ARG(no-event, OPT_no_event), NO_ARG(no-sched, OPT_no_sched), NO_ARG(no-sched-preempt, OPT_no_sched_preempt), NO_ARG(list-event, OPT_list_event), REQ_ARG(run-cmd, OPT_run_cmd), REQ_ARG(opt-file, OPT_opt_file), NO_ARG(keep-pid, OPT_keep_pid), REQ_ARG(script, 'S'), REQ_ARG(diff-policy, OPT_diff_policy), NO_ARG(event-full, OPT_event_full), NO_ARG(no-libcall, OPT_no_libcall), NO_ARG(nest-libcall, 'l'), NO_ARG(libname, OPT_libname), REQ_ARG(match, OPT_match_type), NO_ARG(no-randomize-addr, OPT_no_randomize_addr), REQ_ARG(watch, 'W'), REQ_ARG(signal, OPT_signal), NO_ARG(srcline, OPT_srcline), REQ_ARG(hide, 'H'), REQ_ARG(loc-filter, OPT_loc_filter), REQ_ARG(loc-filter-warning, 'L'), /* the long option is dummy, will change later */ REQ_ARG(clock, OPT_clock), NO_ARG(help, 'h'), NO_ARG(usage, OPT_usage), NO_ARG(version, 'V'), NO_ARG(estimate-return, 'e'), REQ_ARG(with-syms, OPT_with_syms), NO_ARG(agent, 'g'), REQ_ARG(pid, 'p'), { 0 } }; /* clang-format on */ #undef REQ_ARG #undef NO_ARG static unsigned long parse_size(char *str) { unsigned long size; char *unit; size = strtoul(str, &unit, 0); switch (*unit) { case '\0': break; case 'k': case 'K': size <<= 10; break; case 'm': case 'M': size <<= 20; break; case 'g': case 'G': size <<= 30; break; default: pr_use("invalid size: %s\n", str); size = 0; break; } return size; } static char *opt_add_string(char *old_opt, char *new_opt) { return strjoin(old_opt, new_opt, ";"); } static char *opt_add_prefix_string(char *old_opt, char *prefix, char *new_opt) { new_opt = strjoin(xstrdup(prefix), new_opt, ""); if (old_opt) { old_opt = strjoin(old_opt, new_opt, ";"); free(new_opt); new_opt = old_opt; } return new_opt; } static const char *true_str[] = { "true", "yes", "on", "y", "1", }; static const char *false_str[] = { "false", "no", "off", "n", "0", }; static enum color_setting parse_color(char *arg) { size_t i; for (i = 0; i < ARRAY_SIZE(true_str); i++) { if (!strcmp(arg, true_str[i])) return COLOR_ON; } for (i = 0; i < ARRAY_SIZE(false_str); i++) { if (!strcmp(arg, false_str[i])) return COLOR_OFF; } if (!strcmp(arg, "auto")) return COLOR_AUTO; return COLOR_UNKNOWN; } static int parse_demangle(char *arg) { size_t i; if (!strcmp(arg, "simple")) return DEMANGLE_SIMPLE; if (!strcmp(arg, "full")) { if (support_full_demangle()) return DEMANGLE_FULL; return DEMANGLE_NOT_SUPPORTED; } for (i = 0; i < ARRAY_SIZE(false_str); i++) { if (!strcmp(arg, false_str[i])) return DEMANGLE_NONE; } return DEMANGLE_ERROR; } static void parse_debug_domain(char *arg) { struct strv strv = STRV_INIT; char *tok, *tmp; int i; strv_split(&strv, arg, ","); strv_for_each(&strv, tok, i) { int level = -1; tmp = strchr(tok, ':'); if (tmp) { *tmp++ = '\0'; level = strtol(tmp, NULL, 0); } if (!strcmp(tok, "ftrace")) /* for backward compatibility */ dbg_domain[DBG_UFTRACE] = level; else if (!strcmp(tok, "uftrace")) dbg_domain[DBG_UFTRACE] = level; else if (!strcmp(tok, "symbol")) dbg_domain[DBG_SYMBOL] = level; else if (!strcmp(tok, "demangle")) dbg_domain[DBG_DEMANGLE] = level; else if (!strcmp(tok, "filter")) dbg_domain[DBG_FILTER] = level; else if (!strcmp(tok, "fstack")) dbg_domain[DBG_FSTACK] = level; else if (!strcmp(tok, "session")) dbg_domain[DBG_SESSION] = level; else if (!strcmp(tok, "kernel")) dbg_domain[DBG_KERNEL] = level; else if (!strcmp(tok, "mcount")) dbg_domain[DBG_MCOUNT] = level; else if (!strcmp(tok, "plthook")) dbg_domain[DBG_PLTHOOK] = level; else if (!strcmp(tok, "dynamic")) dbg_domain[DBG_DYNAMIC] = level; else if (!strcmp(tok, "event")) dbg_domain[DBG_EVENT] = level; else if (!strcmp(tok, "script")) dbg_domain[DBG_SCRIPT] = level; else if (!strcmp(tok, "dwarf")) dbg_domain[DBG_DWARF] = level; else if (!strcmp(tok, "wrap")) dbg_domain[DBG_WRAP] = level; } dbg_domain_set = true; strv_free(&strv); } static bool has_time_unit(const char *str) { if (isalpha(str[strlen(str) - 1])) return true; else return false; } static uint64_t parse_any_timestamp(char *str, bool *elapsed) { if (*str == '\0') return 0; if (has_time_unit(str)) { *elapsed = true; return parse_time(str, 3); } *elapsed = false; return parse_timestamp(str); } static bool parse_time_range(struct uftrace_time_range *range, char *arg) { char *str, *pos; str = xstrdup(arg); pos = strchr(str, '~'); if (pos == NULL) { free(str); return false; } *pos++ = '\0'; range->start = parse_any_timestamp(str, &range->start_elapsed); range->stop = parse_any_timestamp(pos, &range->stop_elapsed); free(str); return true; } static char *remove_trailing_slash(char *path) { size_t len = strlen(path); if (path[len - 1] == '/') path[len - 1] = '\0'; return path; } static bool is_libmcount_directory(const char *path) { DIR *dp = NULL; struct dirent *ent; int ret = false; dp = opendir(path); if (dp == NULL) return false; while ((ent = readdir(dp)) != NULL) { if ((ent->d_type == DT_DIR && !strcmp(ent->d_name, "libmcount")) || ((ent->d_type == DT_LNK || ent->d_type == DT_REG) && !strcmp(ent->d_name, "libmcount.so"))) { ret = true; break; } } closedir(dp); return ret; } static int parse_option(struct uftrace_opts *opts, int key, char *arg) { char *pos; switch (key) { case 'F': opts->filter = opt_add_string(opts->filter, arg); break; case 'N': opts->filter = opt_add_prefix_string(opts->filter, "!", arg); break; case 'T': opts->trigger = opt_add_string(opts->trigger, arg); break; case 'D': opts->depth = strtol(arg, NULL, 0); if (opts->depth <= 0 || opts->depth >= OPT_DEPTH_MAX) { pr_use("invalid depth given: %s (ignoring..)\n", arg); opts->depth = OPT_DEPTH_DEFAULT; } break; case 'C': opts->caller = opt_add_string(opts->caller, arg); /* * caller filter focuses onto a given function, * displaying sched event with it is annoying. */ opts->no_sched = true; break; case 'H': opts->hide = opt_add_string(opts->hide, arg); break; case 'L': if (is_libmcount_directory(arg)) pr_warn("--libmcount-path option should be used to set libmcount path.\n"); /* fall through */ case OPT_loc_filter: pos = strstr(arg, "@hide"); if (!pos) opts->loc_filter = opt_add_string(opts->loc_filter, arg); else { *pos = '\0'; opts->loc_filter = opt_add_prefix_string(opts->loc_filter, "!", arg); } /* * location filter focuses onto a given location, * displaying sched event with it is annoying. */ opts->no_sched = true; break; case 'v': debug++; break; case 'd': opts->dirname = remove_trailing_slash(arg); break; case 'b': opts->bufsize = parse_size(arg); if (opts->bufsize & (getpagesize() - 1)) { pr_use("buffer size should be multiple of page size\n"); opts->bufsize = ROUND_UP(opts->bufsize, getpagesize()); } break; case 'k': opts->kernel = true; opts->kernel_depth = 1; break; case 'K': opts->kernel = true; opts->kernel_depth = strtol(arg, NULL, 0); if (opts->kernel_depth < 1 || opts->kernel_depth > 50) { pr_use("invalid kernel depth: %s. Set depth to 1.\n", arg); opts->kernel_depth = 1; } break; case 's': opts->sort_keys = opt_add_string(opts->sort_keys, arg); break; case 'S': opts->script_file = arg; break; case 't': /* do not override time-filter or time-range if it's already set */ if (parsing_default_opts) { if (opts->threshold || opts->range.start || opts->range.stop) break; } /* add time-filter to uftrace.data/default.opts */ strv_append(&default_opts, "-t"); strv_append(&default_opts, arg); opts->threshold = parse_time(arg, 3); if (opts->threshold >= OPT_THRESHOLD_MAX) { pr_use("invalid time given: %lu (ignoring..)\n", opts->threshold); opts->threshold = OPT_THRESHOLD_MAX - 1; } if (opts->range.start || opts->range.stop) { pr_use("--time-range cannot be used with --time-filter\n"); opts->range.start = opts->range.stop = 0; } break; case 'A': opts->args = opt_add_string(opts->args, arg); break; case 'R': opts->retval = opt_add_string(opts->retval, arg); break; case 'a': opts->auto_args = true; break; case 'l': /* --nest-libcall implies --force option */ opts->force = true; opts->nest_libcall = true; break; case 'f': opts->fields = arg; break; case 'r': if (!parse_time_range(&opts->range, arg)) pr_use("invalid time range: %s (ignoring...)\n", arg); if (opts->threshold) { pr_use("--time-filter cannot be used with --time-range\n"); opts->threshold = 0; } break; case 'P': opts->patch = opt_add_string(opts->patch, arg); break; case 'U': opts->patch = opt_add_prefix_string(opts->patch, "!", arg); break; case 'Z': opts->size_filter = strtol(arg, NULL, 0); if (opts->size_filter <= 0) { pr_use("--size-filter should be positive\n"); opts->size_filter = 0; } break; case 'E': if (!strcmp(arg, "list")) { pr_use("'-E list' is deprecated, use --list-event instead.\n"); opts->list_event = true; } else opts->event = opt_add_string(opts->event, arg); break; case 'W': opts->watch = opt_add_string(opts->watch, arg); break; case 'e': opts->estimate_return = true; break; case 'V': pr_out("%s\n", uftrace_version); return -1; case 'g': opts->agent = true; break; case 'h': return -3; case 'p': opts->pid = strtol(arg, NULL, 0); opts->exename = ""; break; case OPT_libmcount_path: opts->lib_path = arg; break; case OPT_usage: return -2; case OPT_flat: opts->flat = true; break; case OPT_no_libcall: opts->libcall = false; break; case OPT_symbols: opts->print_symtab = true; break; case OPT_logfile: opts->logfile = arg; break; case OPT_force: opts->force = true; break; case OPT_task: opts->show_task = true; break; case OPT_tid_filter: if (strtol(arg, NULL, 0) <= 0) pr_use("invalid thread id: %s\n", arg); else opts->tid = opt_add_string(opts->tid, arg); break; case OPT_no_merge: opts->no_merge = true; break; case OPT_nop: opts->nop = true; break; case OPT_time: opts->time = true; break; case OPT_max_stack: opts->max_stack = strtol(arg, NULL, 0); if (opts->max_stack <= 0 || opts->max_stack > OPT_RSTACK_MAX) { pr_use("max stack depth should be >0 and <%d\n", OPT_RSTACK_MAX); opts->max_stack = OPT_RSTACK_DEFAULT; } break; case OPT_host: opts->host = arg; break; case OPT_port: opts->port = strtol(arg, NULL, 0); if (opts->port <= 0) { pr_use("invalid port number: %s (ignoring..)\n", arg); opts->port = UFTRACE_RECV_PORT; } break; case OPT_nopager: opts->use_pager = false; break; case OPT_avg_total: opts->avg_total = true; break; case OPT_avg_self: opts->avg_self = true; break; case OPT_color: opts->color = parse_color(arg); if (opts->color == COLOR_UNKNOWN) { pr_use("unknown color setting: %s (ignoring..)\n", arg); opts->color = COLOR_AUTO; } break; case OPT_disabled: pr_use("'--disable' is deprecated, use --trace=off instead.\n"); opts->trace = TRACE_STATE_OFF; break; case OPT_trace: if (!strcmp(arg, "on")) opts->trace = TRACE_STATE_ON; else if (!strcmp(arg, "off")) opts->trace = TRACE_STATE_OFF; else pr_use("unknown tracing state: %s (ignoring..)\n", arg); break; case OPT_demangle: demangler = parse_demangle(arg); if (demangler == DEMANGLE_ERROR) { pr_use("unknown demangle value: %s (ignoring..)\n", arg); demangler = DEMANGLE_SIMPLE; } else if (demangler == DEMANGLE_NOT_SUPPORTED) { pr_use("'%s' demangler is not supported\n", arg); demangler = DEMANGLE_SIMPLE; } break; case OPT_dbg_domain: parse_debug_domain(arg); break; case OPT_report: opts->report = true; break; case OPT_column_view: opts->column_view = true; break; case OPT_column_offset: opts->column_offset = strtol(arg, NULL, 0); if (opts->column_offset < 0) opts->column_offset = OPT_COLUMN_OFFSET; break; case OPT_bind_not: opts->want_bind_not = true; break; case OPT_task_newline: opts->task_newline = true; break; case OPT_chrome_trace: opts->chrome_trace = true; break; case OPT_flame_graph: opts->flame_graph = true; break; case OPT_graphviz: opts->graphviz = true; break; case OPT_diff: opts->diff = arg; break; case OPT_diff_policy: opts->diff_policy = arg; break; case OPT_format: if (!strcmp(arg, "normal")) format_mode = FORMAT_NORMAL; else if (!strcmp(arg, "html")) { format_mode = FORMAT_HTML; if (opts->color == COLOR_AUTO) opts->color = COLOR_ON; } else { pr_use("invalid format argument: %s\n", arg); format_mode = FORMAT_NORMAL; } break; case OPT_sort_column: opts->sort_column = strtol(arg, NULL, 0); if (opts->sort_column < 0 || opts->sort_column > OPT_SORT_COLUMN) { pr_use("invalid column number: %d\n", opts->sort_column); pr_use("force to set it to --sort-column=%d for diff percentage\n", OPT_SORT_COLUMN); opts->sort_column = OPT_SORT_COLUMN; } break; case OPT_num_thread: opts->nr_thread = strtol(arg, NULL, 0); if (opts->nr_thread < 0) { pr_use("invalid thread number: %s\n", arg); opts->nr_thread = 0; } break; case OPT_no_comment: opts->comment = false; break; case OPT_libmcount_single: opts->libmcount_single = true; break; case OPT_rt_prio: opts->rt_prio = strtol(arg, NULL, 0); if (opts->rt_prio < 1 || opts->rt_prio > 99) { pr_use("invalid rt prioity: %d (ignoring...)\n", opts->rt_prio); opts->rt_prio = 0; } break; case OPT_kernel_bufsize: opts->kernel_bufsize = parse_size(arg); if (opts->kernel_bufsize & (getpagesize() - 1)) { pr_use("buffer size should be multiple of page size\n"); opts->kernel_bufsize = ROUND_UP(opts->kernel_bufsize, getpagesize()); } break; case OPT_kernel_skip_out: /* deprecated */ opts->kernel_skip_out = true; break; case OPT_kernel_full: opts->kernel_skip_out = false; /* see setup_kernel_tracing() also */ break; case OPT_kernel_only: opts->kernel_only = true; break; case OPT_sample_time: opts->sample_time = parse_time(arg, 9); break; case OPT_list_event: opts->list_event = true; break; case OPT_run_cmd: if (opts->run_cmd) { pr_warn("intermediate --run-cmd argument is ignored.\n"); free_parsed_cmdline(opts->run_cmd); } opts->run_cmd = parse_cmdline(arg, NULL); break; case OPT_opt_file: opts->opt_file = arg; break; case OPT_keep_pid: opts->keep_pid = true; break; case OPT_event_full: opts->event_skip_out = false; break; case OPT_record: opts->record = true; break; case OPT_no_args: opts->show_args = false; break; case OPT_libname: opts->libname = true; break; case OPT_match_type: opts->patt_type = parse_filter_pattern(arg); if (opts->patt_type == PATT_NONE) { pr_use("invalid match pattern: %s (ignoring...)\n", arg); opts->patt_type = PATT_REGEX; } break; case OPT_no_randomize_addr: opts->no_randomize_addr = true; break; case OPT_no_event: opts->no_event = true; break; case OPT_no_sched: opts->no_sched = true; break; case OPT_no_sched_preempt: opts->no_sched_preempt = true; break; case OPT_signal: opts->sig_trigger = opt_add_string(opts->sig_trigger, arg); break; case OPT_srcline: opts->srcline = true; break; case OPT_with_syms: opts->with_syms = arg; break; case OPT_clock: if (strcmp(arg, "mono") && strcmp(arg, "mono_raw") && strcmp(arg, "boot")) { pr_use("invalid clock source: '%s' " "(force to use 'mono')\n", arg); arg = "mono"; } opts->clock = arg; break; case OPT_mermaid: opts->mermaid = true; break; default: return -1; } return 0; } static void update_subcmd(struct uftrace_opts *opts, char *cmd) { if (!strcmp(cmd, "record")) opts->mode = UFTRACE_MODE_RECORD; else if (!strcmp(cmd, "replay")) opts->mode = UFTRACE_MODE_REPLAY; else if (!strcmp(cmd, "report")) opts->mode = UFTRACE_MODE_REPORT; else if (!strcmp(cmd, "live")) opts->mode = UFTRACE_MODE_LIVE; else if (!strcmp(cmd, "graph")) opts->mode = UFTRACE_MODE_GRAPH; else if (!strcmp(cmd, "info")) opts->mode = UFTRACE_MODE_INFO; else if (!strcmp(cmd, "dump")) opts->mode = UFTRACE_MODE_DUMP; else if (!strcmp(cmd, "recv")) opts->mode = UFTRACE_MODE_RECV; else if (!strcmp(cmd, "script")) opts->mode = UFTRACE_MODE_SCRIPT; else if (!strcmp(cmd, "tui")) opts->mode = UFTRACE_MODE_TUI; else opts->mode = UFTRACE_MODE_INVALID; } static void parse_opt_file(int *argc, char ***argv, char *filename, struct uftrace_opts *opts) { int file_argc; char **file_argv; char *buf; struct stat stbuf; FILE *fp; char *orig_exename = opts->exename; bool has_subcmd = false; if (stat(filename, &stbuf) < 0) { pr_use("Cannot use opt-file: %s: %m\n", filename); exit(0); } /* prepend dummy string since getopt_long cannot process argv[0] */ buf = xmalloc(stbuf.st_size + 9); strncpy(buf, "uftrace ", 9); fp = fopen(filename, "r"); if (fp == NULL) pr_err("Open failed: %s", filename); fread_all(buf + 8, stbuf.st_size, fp); fclose(fp); buf[stbuf.st_size + 8] = '\0'; file_argv = parse_cmdline(buf, &file_argc); /* clear opt_file for error reporting */ opts->opt_file = NULL; /* re-initialize getopt as we start another round */ optind = 0; if (file_argv[1][0] != '-') { int orig_mode = opts->mode; update_subcmd(opts, file_argv[1]); if (opts->mode == UFTRACE_MODE_INVALID) { opts->mode = orig_mode; has_subcmd = true; } else { if (orig_mode != UFTRACE_MODE_INVALID && orig_mode != opts->mode) { pr_use("ignore uftrace command in opt-file\n"); opts->mode = orig_mode; } else { has_subcmd = true; } } } while (true) { int key, tmp = 0; key = getopt_long(file_argc, file_argv, uftrace_shopts, uftrace_options, &tmp); if (key == -1 || key == '?') { if (has_subcmd && optind == 1) optind++; else break; } parse_option(opts, key, optarg); } /* overwrite argv only if it's not given on command line */ if (orig_exename == NULL && optind < file_argc) { *argc = file_argc; *argv = file_argv; opts->idx = optind; opts->exename = file_argv[optind]; /* mark it to free at the end */ opts->opt_file = filename; } else { opts->exename = orig_exename; free_parsed_cmdline(file_argv); } free(buf); } /* * Parse options in a script file header. For example, * * # uftrace-option: -F main -A malloc@arg1 * def uftrace_entry(): * pass * ... * * Note that it only handles some options like filter, trigger, * argument, return values and maybe some more. */ void parse_script_opt(struct uftrace_opts *opts) { FILE *fp; int opt_argc; char **opt_argv; char *line = NULL; size_t len = 0; static const char optname[] = "uftrace-option"; enum script_type_t script_type; const char *comments[SCRIPT_TYPE_COUNT] = { "", "#", "--" }; const char *comment; size_t comment_len; if (opts->script_file == NULL) return; fp = fopen(opts->script_file, "r"); if (fp == NULL) pr_err("cannot open script file: %s", opts->script_file); script_type = get_script_type(opts->script_file); if (script_type == SCRIPT_UNKNOWN) { fclose(fp); pr_err("unknown script type"); } comment = comments[script_type]; comment_len = strlen(comment); while (getline(&line, &len, fp) > 0) { char *pos; if (strncmp(line, comment, comment_len)) continue; pos = line + comment_len; while (isspace(*pos)) pos++; if (strncmp(pos, optname, strlen(optname))) continue; /* extract option value */ pos = strchr(line, ':'); if (pos == NULL) break; pr_dbg("adding record option from script: %s", pos + 1); /* include ':' so that it can start with optind 1 */ opt_argv = parse_cmdline(pos, &opt_argc); /* re-initialize getopt as we start another round */ optind = 0; while (true) { int key, tmp = 0; key = getopt_long(opt_argc, opt_argv, uftrace_shopts, uftrace_options, &tmp); if (key == -1 || key == '?') break; parse_option(opts, key, optarg); } free_parsed_cmdline(opt_argv); break; } free(line); fclose(fp); } static void free_opts(struct uftrace_opts *opts) { free(opts->filter); free(opts->trigger); free(opts->sig_trigger); free(opts->sort_keys); free(opts->args); free(opts->retval); free(opts->tid); free(opts->event); free(opts->patch); free(opts->caller); free(opts->watch); free(opts->hide); free(opts->loc_filter); free_parsed_cmdline(opts->run_cmd); } static int parse_options(int argc, char **argv, struct uftrace_opts *opts) { /* initial option parsing index */ optind = 1; while (true) { int key, tmp = 0; key = getopt_long(argc, argv, uftrace_shopts, uftrace_options, &tmp); if (key == -1 || key == '?') { if (optind < argc && opts->mode == UFTRACE_MODE_INVALID) { update_subcmd(opts, argv[optind]); if (opts->mode != UFTRACE_MODE_INVALID) { optind++; continue; } } break; } tmp = parse_option(opts, key, optarg); if (tmp < 0) return tmp; } if (optind < argc) { opts->idx = optind; opts->exename = argv[optind]; } return 0; } __used static void apply_default_opts(int *argc, char ***argv, struct uftrace_opts *opts) { char *basename = "default.opts"; char opts_file[PATH_MAX]; struct stat stbuf; /* default.opts is only for analysis commands */ if (opts->mode == UFTRACE_MODE_RECORD || opts->mode == UFTRACE_MODE_LIVE || opts->mode == UFTRACE_MODE_RECV) return; /* this is not to override user given time-filter by default opts */ parsing_default_opts = true; snprintf(opts_file, PATH_MAX, "%s/%s", opts->dirname, basename); if (!stat(opts_file, &stbuf) && stbuf.st_size > 0) { pr_dbg("apply '%s' option file\n", opts_file); parse_opt_file(argc, argv, opts_file, opts); } else if (!strcmp(opts->dirname, UFTRACE_DIR_NAME) && !access("./info", F_OK)) { /* try again applying default.opts in the current dir */ if (!stat(basename, &stbuf) && stbuf.st_size > 0) { pr_dbg("apply './%s' option file\n", basename); parse_opt_file(argc, argv, basename, opts); } } } #ifndef UNIT_TEST int main(int argc, char *argv[]) { struct uftrace_opts opts = { .mode = UFTRACE_MODE_INVALID, .dirname = UFTRACE_DIR_NAME, .libcall = true, .bufsize = SHMEM_BUFFER_SIZE, .max_stack = OPT_RSTACK_DEFAULT, .port = UFTRACE_RECV_PORT, .use_pager = true, .color = COLOR_AUTO, /* turn on if terminal */ .column_offset = OPT_COLUMN_OFFSET, .comment = true, .kernel_skip_out = true, .fields = NULL, .sort_column = OPT_SORT_COLUMN, .event_skip_out = true, .patt_type = PATT_REGEX, .show_args = true, .clock = "mono", }; int ret = -1; char *pager = NULL; /* this must be done before calling pr_*() */ logfp = stderr; outfp = stdout; if (argc == 1) { pr_out(uftrace_usage); pr_out(uftrace_footer); return 0; } switch (parse_options(argc, argv, &opts)) { case -1: ret = 0; goto cleanup; case -2: pr_out(uftrace_usage); pr_out(uftrace_footer); ret = 0; goto cleanup; case -3: if (opts.use_pager) start_pager(setup_pager()); pr_out(uftrace_usage); pr_out(uftrace_help); wait_for_pager(); ret = 0; goto cleanup; } if (opts.opt_file) parse_opt_file(&argc, &argv, opts.opt_file, &opts); if (opts.exename == NULL && !opts.list_event) { switch (opts.mode) { case UFTRACE_MODE_RECORD: case UFTRACE_MODE_LIVE: case UFTRACE_MODE_INVALID: pr_out(uftrace_usage); pr_out(uftrace_footer); ret = 1; goto cleanup; } } if (opts.mode == UFTRACE_MODE_INVALID) opts.mode = UFTRACE_MODE_DEFAULT; if (dbg_domain_set && !debug) debug = 1; if (opts.logfile) { logfp = fopen(opts.logfile, "a"); if (logfp == NULL) { logfp = stderr; pr_err("cannot open log file"); } setvbuf(logfp, NULL, _IOLBF, 1024); } else if (debug) { /* ensure normal output is not mixed by debug message */ setvbuf(outfp, NULL, _IOLBF, 1024); } if (debug) { int d; /* set default debug level */ for (d = 0; d < DBG_DOMAIN_MAX; d++) { if (dbg_domain[d] == -1 || !dbg_domain_set) dbg_domain[d] = debug; } } pr_dbg("running %s\n", uftrace_version); opts.range.kernel_skip_out = opts.kernel_skip_out; opts.range.event_skip_out = opts.event_skip_out; if (opts.mode == UFTRACE_MODE_RECORD || opts.mode == UFTRACE_MODE_RECV || opts.mode == UFTRACE_MODE_TUI) opts.use_pager = false; if (opts.nop) opts.use_pager = false; if (opts.use_pager) pager = setup_pager(); if (!opts.pid) { /* Keep uninitialized values in client mode */ if (!opts.depth) opts.depth = OPT_DEPTH_DEFAULT; } setup_color(opts.color, pager); setup_signal(); /* 'live' will start pager at its replay time */ if (opts.use_pager && opts.mode != UFTRACE_MODE_LIVE) start_pager(pager); /* the srcline info is used for TUI status line by default */ if (opts.mode == UFTRACE_MODE_TUI) opts.srcline = true; if (!opts.pid) { /* Keep uninitialized values in client mode */ if (opts.trace == TRACE_STATE_NONE) opts.trace = TRACE_STATE_ON; } /* apply 'default.opts' options for analysis commands */ apply_default_opts(&argc, &argv, &opts); if (opts.idx == 0) opts.idx = argc; argc -= opts.idx; argv += opts.idx; if (!opts.libcall && opts.nest_libcall) pr_err_ns("cannot use --no-libcall and --nest-libcall options together\n"); switch (opts.mode) { case UFTRACE_MODE_RECORD: ret = command_record(argc, argv, &opts); break; case UFTRACE_MODE_REPLAY: ret = command_replay(argc, argv, &opts); break; case UFTRACE_MODE_LIVE: ret = command_live(argc, argv, &opts); break; case UFTRACE_MODE_REPORT: ret = command_report(argc, argv, &opts); break; case UFTRACE_MODE_INFO: ret = command_info(argc, argv, &opts); break; case UFTRACE_MODE_RECV: ret = command_recv(argc, argv, &opts); break; case UFTRACE_MODE_DUMP: ret = command_dump(argc, argv, &opts); break; case UFTRACE_MODE_GRAPH: ret = command_graph(argc, argv, &opts); break; case UFTRACE_MODE_SCRIPT: ret = command_script(argc, argv, &opts); break; case UFTRACE_MODE_TUI: ret = command_tui(argc, argv, &opts); break; case UFTRACE_MODE_INVALID: ret = 1; break; } wait_for_pager(); cleanup: if (opts.logfile) fclose(logfp); if (opts.opt_file) free_parsed_cmdline(argv - opts.idx); free_opts(&opts); return ret; } #else #define OPT_FILE "opt" TEST_CASE(option_parsing1) { char *stropt = NULL; int i; bool elapsed_time; pr_dbg("check parsing size suffix\n"); TEST_EQ(parse_size("1234"), 1234); TEST_EQ(parse_size("10k"), 10240); TEST_EQ(parse_size("100M"), 100 * 1024 * 1024); pr_dbg("check string list addition\n"); stropt = opt_add_string(stropt, "abc"); TEST_STREQ(stropt, "abc"); stropt = opt_add_string(stropt, "def"); TEST_STREQ(stropt, "abc;def"); free(stropt); stropt = NULL; pr_dbg("check string list addition with prefix\n"); stropt = opt_add_prefix_string(stropt, "!", "abc"); TEST_STREQ(stropt, "!abc"); stropt = opt_add_prefix_string(stropt, "?", "def"); TEST_STREQ(stropt, "!abc;?def"); free(stropt); stropt = NULL; pr_dbg("check parsing colors\n"); TEST_EQ(parse_color("1"), COLOR_ON); TEST_EQ(parse_color("true"), COLOR_ON); TEST_EQ(parse_color("off"), COLOR_OFF); TEST_EQ(parse_color("n"), COLOR_OFF); TEST_EQ(parse_color("auto"), COLOR_AUTO); TEST_EQ(parse_color("ok"), COLOR_UNKNOWN); pr_dbg("check parsing demanglers\n"); TEST_EQ(parse_demangle("simple"), DEMANGLE_SIMPLE); TEST_EQ(parse_demangle("no"), DEMANGLE_NONE); TEST_EQ(parse_demangle("0"), DEMANGLE_NONE); /* full demangling might not supported */ TEST_NE(parse_demangle("full"), DEMANGLE_SIMPLE); for (i = 0; i < DBG_DOMAIN_MAX; i++) dbg_domain[i] = 0; pr_dbg("check parsing debug domains\n"); parse_debug_domain("mcount:1,uftrace:2,symbol:3"); TEST_EQ(dbg_domain[DBG_UFTRACE], 2); TEST_EQ(dbg_domain[DBG_MCOUNT], 1); TEST_EQ(dbg_domain[DBG_SYMBOL], 3); TEST_EQ(parse_any_timestamp("1ns", &elapsed_time), 1ULL); TEST_EQ(parse_any_timestamp("2us", &elapsed_time), 2000ULL); TEST_EQ(parse_any_timestamp("3ms", &elapsed_time), 3000000ULL); TEST_EQ(parse_any_timestamp("4s", &elapsed_time), 4000000000ULL); TEST_EQ(parse_any_timestamp("5m", &elapsed_time), 300000000000ULL); return TEST_OK; } TEST_CASE(option_parsing2) { struct uftrace_opts opts = { .mode = UFTRACE_MODE_INVALID, }; char *argv[] = { "uftrace", "replay", "-v", "--data=abc.data", "--kernel", "-t", "1us", "-F", "foo", "-N", "bar", "-Abaz@kernel", }; int argc = ARRAY_SIZE(argv); int saved_debug = debug; pr_dbg("check parsing regular command line options\n"); parse_options(argc, argv, &opts); TEST_EQ(opts.mode, UFTRACE_MODE_REPLAY); TEST_EQ(debug, saved_debug + 1); TEST_EQ(opts.kernel, 1); TEST_EQ(opts.threshold, (uint64_t)1000); TEST_STREQ(opts.dirname, "abc.data"); TEST_STREQ(opts.filter, "foo;!bar"); TEST_STREQ(opts.args, "baz@kernel"); free_opts(&opts); return TEST_OK; } TEST_CASE(option_parsing3) { struct uftrace_opts opts = { .mode = UFTRACE_MODE_INVALID, }; char *argv[] = { "uftrace", "-v", "--opt-file", OPT_FILE, }; int argc = ARRAY_SIZE(argv); char opt_file[] = "-K 2\n" "-b4m\n" "--column-view\n" "--depth=3\n" "t-abc"; int file_argc; char **file_argv; FILE *fp; int saved_debug = debug; /* create opt-file */ fp = fopen(OPT_FILE, "w"); TEST_NE(fp, NULL); fwrite(opt_file, strlen(opt_file), 1, fp); fclose(fp); pr_dbg("check parsing regular command line options\n"); parse_options(argc, argv, &opts); TEST_STREQ(opts.opt_file, OPT_FILE); pr_dbg("check parsing option files\n"); parse_opt_file(&file_argc, &file_argv, opts.opt_file, &opts); TEST_EQ(file_argc, 7); // +1 for dummy prefix unlink(OPT_FILE); TEST_EQ(opts.mode, UFTRACE_MODE_INVALID); TEST_EQ(debug, saved_debug + 1); TEST_EQ(opts.kernel, 1); TEST_EQ(opts.kernel_depth, 2); TEST_EQ(opts.depth, 3); TEST_EQ(opts.bufsize, 4 * 1024 * 1024); TEST_EQ(opts.column_view, 1); TEST_STREQ(opts.exename, "t-abc"); free_parsed_cmdline(file_argv); free_opts(&opts); return TEST_OK; } TEST_CASE(option_parsing4) { struct uftrace_opts opts = { .mode = UFTRACE_MODE_INVALID, }; char *argv[] = { "uftrace", "-v", "--opt-file", OPT_FILE, }; int argc = ARRAY_SIZE(argv); char opt_file[] = "-K 2\n" "# buffer size: 4 MB\n" "-b4m\n" "\n" "## show different thread with different indentation\n" "--column-view\n" "\n" "# limit maximum function call depth to 3\n" "--depth=3 # same as -D3 \n" "\n" "\n" "#test program\n" "t-abc\n" "\n"; int file_argc; char **file_argv; FILE *fp; int saved_debug = debug; /* create opt-file */ fp = fopen(OPT_FILE, "w"); TEST_NE(fp, NULL); fwrite(opt_file, strlen(opt_file), 1, fp); fclose(fp); pr_dbg("check parsing regular command line options\n"); parse_options(argc, argv, &opts); TEST_STREQ(opts.opt_file, OPT_FILE); pr_dbg("check parsing option files\n"); parse_opt_file(&file_argc, &file_argv, opts.opt_file, &opts); TEST_EQ(file_argc, 7); // +1 for dummy prefix unlink(OPT_FILE); pr_dbg("command mode should remain as is\n"); TEST_EQ(opts.mode, UFTRACE_MODE_INVALID); TEST_EQ(debug, saved_debug + 1); TEST_EQ(opts.kernel, 1); TEST_EQ(opts.kernel_depth, 2); TEST_EQ(opts.depth, 3); TEST_EQ(opts.bufsize, 4 * 1024 * 1024); TEST_EQ(opts.column_view, 1); TEST_STREQ(opts.exename, "t-abc"); free_parsed_cmdline(file_argv); free_opts(&opts); return TEST_OK; } TEST_CASE(option_parsing5) { struct uftrace_opts opts = { .mode = UFTRACE_MODE_INVALID, }; char *argv[] = { "uftrace", "-v", "--opt-file", OPT_FILE, "hello" }; int argc = ARRAY_SIZE(argv); char opt_file[] = "record\n" "-F main\n" "--time-filter 1us\n" "--depth=3\n" "t-abc"; int file_argc = argc; char **file_argv = argv; FILE *fp; int saved_debug = debug; /* create opt-file */ fp = fopen(OPT_FILE, "w"); TEST_NE(fp, NULL); fwrite(opt_file, strlen(opt_file), 1, fp); fclose(fp); pr_dbg("check parsing regular command line options\n"); parse_options(argc, argv, &opts); TEST_STREQ(opts.opt_file, OPT_FILE); pr_dbg("check parsing option files\n"); parse_opt_file(&file_argc, &file_argv, opts.opt_file, &opts); unlink(OPT_FILE); pr_dbg("opt file should update command mode\n"); TEST_EQ(opts.mode, UFTRACE_MODE_RECORD); TEST_EQ(debug, saved_debug + 1); /* preserve original arg[cv] if command line is given */ TEST_EQ(file_argc, argc); TEST_EQ(file_argv, (char **)argv); TEST_EQ(opts.threshold, (uint64_t)1000); TEST_EQ(opts.depth, 3); TEST_EQ(opts.idx, 4); TEST_STREQ(opts.filter, "main"); /* it should not update exename to "t-abc" */ TEST_STREQ(opts.exename, "hello"); free_opts(&opts); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/uftrace.h000066400000000000000000000423771455365734300150660ustar00rootroot00000000000000#ifndef UFTRACE_H #define UFTRACE_H #include #include #include #include #include "utils/arch.h" #include "utils/filter.h" #include "utils/list.h" #include "utils/perf.h" #include "utils/rbtree.h" #include "utils/symbol.h" #define UFTRACE_MAGIC_LEN 8 #define UFTRACE_MAGIC_STR "Ftrace!" #define UFTRACE_FILE_VERSION 4 #define UFTRACE_FILE_VERSION_MIN 3 #define UFTRACE_DIR_NAME "uftrace.data" #define UFTRACE_DIR_OLD_NAME "ftrace.dir" #define UFTRACE_RECV_PORT 8090 /* default option values */ #define OPT_RSTACK_MAX 65535 #define OPT_RSTACK_DEFAULT 1024 #define OPT_DEPTH_MAX OPT_RSTACK_MAX #define OPT_THRESHOLD_MAX 0xFFFFFFFFFFFFFFFF /* max uint64 = 2^64-1 */ #define OPT_DEPTH_DEFAULT OPT_RSTACK_DEFAULT #define OPT_COLUMN_OFFSET 8 #define OPT_SORT_COLUMN 2 #define OPT_SORT_KEYS "total" #define KB 1024 #define MB (KB * 1024) struct uftrace_file_header { char magic[UFTRACE_MAGIC_LEN]; uint32_t version; uint16_t header_size; uint8_t endian; uint8_t elf_class; uint64_t feat_mask; uint64_t info_mask; uint16_t max_stack; uint16_t unused1; uint32_t unused2; }; enum uftrace_feat_bits { /* bit index */ PLTHOOK_BIT, TASK_SESSION_BIT, KERNEL_BIT, ARGUMENT_BIT, RETVAL_BIT, SYM_REL_ADDR_BIT, MAX_STACK_BIT, EVENT_BIT, PERF_EVENT_BIT, AUTO_ARGS_BIT, DEBUG_INFO_BIT, ESTIMATE_RETURN_BIT, SYM_SIZE_BIT, FEAT_BIT_MAX, /* bit mask */ PLTHOOK = (1U << PLTHOOK_BIT), TASK_SESSION = (1U << TASK_SESSION_BIT), KERNEL = (1U << KERNEL_BIT), ARGUMENT = (1U << ARGUMENT_BIT), RETVAL = (1U << RETVAL_BIT), SYM_REL_ADDR = (1U << SYM_REL_ADDR_BIT), MAX_STACK = (1U << MAX_STACK_BIT), EVENT = (1U << EVENT_BIT), PERF_EVENT = (1U << PERF_EVENT_BIT), AUTO_ARGS = (1U << AUTO_ARGS_BIT), DEBUG_INFO = (1U << DEBUG_INFO_BIT), ESTIMATE_RETURN = (1U << ESTIMATE_RETURN_BIT), SYM_SIZE = (1U << SYM_SIZE_BIT), }; enum uftrace_info_bits { /* bit index */ EXE_NAME_BIT, EXE_BUILD_ID_BIT, EXIT_STATUS_BIT, CMDLINE_BIT, CPUINFO_BIT, MEMINFO_BIT, OSINFO_BIT, TASKINFO_BIT, USAGEINFO_BIT, LOADINFO_BIT, ARG_SPEC_BIT, RECORD_DATE_BIT, PATTERN_TYPE_BIT, VERSION_BIT, INFO_BIT_MAX, /* bit mask */ EXE_NAME = (1U << EXE_NAME_BIT), EXE_BUILD_ID = (1U << EXE_BUILD_ID_BIT), EXIT_STATUS = (1U << EXIT_STATUS_BIT), CMDLINE = (1U << CMDLINE_BIT), CPUINFO = (1U << CPUINFO_BIT), MEMINFO = (1U << MEMINFO_BIT), OSINFO = (1U << OSINFO_BIT), TASKINFO = (1U << TASKINFO_BIT), USAGEINFO = (1U << USAGEINFO_BIT), LOADINFO = (1U << LOADINFO_BIT), ARG_SPEC = (1U << ARG_SPEC_BIT), RECORD_DATE = (1U << RECORD_DATE_BIT), PATTERN_TYPE = (1U << PATTERN_TYPE_BIT), VERSION = (1U << VERSION_BIT), }; struct uftrace_info { char *exename; unsigned char build_id[20]; int exit_status; char *cmdline; int nr_cpus_online; int nr_cpus_possible; char *cpudesc; char *meminfo; char *kernel; char *hostname; char *distro; char *argspec; char *retspec; char *autoarg; char *autoret; char *autoenum; bool auto_args_enabled; int nr_tid; int *tids; double stime; double utime; char *record_date; char *elapsed_time; long vctxsw; long ictxsw; long maxrss; long major_fault; long minor_fault; long rblock; long wblock; float load1; float load5; float load15; enum uftrace_pattern_type patt_type; char *uftrace_version; }; enum { UFTRACE_EXIT_SUCCESS = 0, UFTRACE_EXIT_FAILURE, UFTRACE_EXIT_SIGNALED, UFTRACE_EXIT_UNKNOWN, UFTRACE_EXIT_FINISHED = 1 << 16, }; struct kbuffer; struct pevent; struct uftrace_record; struct uftrace_rstack_list; struct uftrace_session; struct uftrace_kernel_reader; struct uftrace_perf_reader; struct uftrace_extern_reader; struct uftrace_module; struct uftrace_session_link { struct rb_root root; struct rb_root tasks; struct uftrace_session *first; struct uftrace_task *first_task; }; struct uftrace_data { FILE *fp; int sock; const char *dirname; enum uftrace_cpu_arch arch; struct uftrace_file_header hdr; struct uftrace_info info; struct uftrace_kernel_reader *kernel; struct uftrace_perf_reader *perf; struct uftrace_extern_reader *extn; struct uftrace_task_reader *tasks; struct uftrace_session_link sessions; int nr_tasks; int nr_perf; int last_perf_idx; int depth; bool needs_byte_swap; bool needs_bit_swap; bool perf_event_processed; bool caller_filter; uint64_t time_filter; unsigned size_filter; struct uftrace_time_range time_range; struct list_head events; }; bool data_is_lp64(struct uftrace_data *handle); #define UFTRACE_MODE_INVALID 0 #define UFTRACE_MODE_RECORD 1 #define UFTRACE_MODE_REPLAY 2 #define UFTRACE_MODE_LIVE 3 #define UFTRACE_MODE_REPORT 4 #define UFTRACE_MODE_INFO 5 #define UFTRACE_MODE_RECV 6 #define UFTRACE_MODE_DUMP 7 #define UFTRACE_MODE_GRAPH 8 #define UFTRACE_MODE_SCRIPT 9 #define UFTRACE_MODE_TUI 10 #define UFTRACE_MODE_DEFAULT UFTRACE_MODE_LIVE struct uftrace_opts { char *lib_path; char *filter; char *trigger; char *sig_trigger; char *tid; char *exename; char *dirname; char *logfile; char *host; char *sort_keys; char *args; char *retval; char *diff; char *fields; char *patch; char *event; char *watch; char **run_cmd; char *opt_file; char *script_file; char *diff_policy; char *caller; char *extern_data; char *hide; char *loc_filter; char *with_syms; char *clock; int mode; int idx; int depth; int kernel_depth; int max_stack; int port; int color; int column_offset; int sort_column; int nr_thread; int rt_prio; int size_filter; int pid; unsigned long bufsize; unsigned long kernel_bufsize; uint64_t threshold; uint64_t sample_time; bool flat; bool libcall; bool print_symtab; bool force; bool show_task; bool no_merge; bool nop; bool time; bool backtrace; bool use_pager; bool avg_total; bool avg_self; bool report; bool column_view; bool want_bind_not; bool task_newline; bool chrome_trace; bool comment; bool flame_graph; bool libmcount_single; bool kernel; bool kernel_skip_out; /* also affects VDSO filter */ bool kernel_only; bool keep_pid; bool list_event; bool event_skip_out; bool no_event; bool no_sched; bool no_sched_preempt; bool nest_libcall; bool record; bool auto_args; bool show_args; bool libname; bool no_randomize_addr; bool graphviz; bool srcline; bool estimate_return; bool mermaid; bool agent; struct uftrace_time_range range; enum uftrace_pattern_type patt_type; enum uftrace_trace_state trace; }; extern struct strv default_opts; static inline bool opts_has_filter(struct uftrace_opts *opts) { return opts->filter || opts->trigger || opts->threshold || opts->depth != OPT_DEPTH_DEFAULT; } void parse_script_opt(struct uftrace_opts *opts); int command_record(int argc, char *argv[], struct uftrace_opts *opts); int command_replay(int argc, char *argv[], struct uftrace_opts *opts); int command_live(int argc, char *argv[], struct uftrace_opts *opts); int command_report(int argc, char *argv[], struct uftrace_opts *opts); int command_info(int argc, char *argv[], struct uftrace_opts *opts); int command_recv(int argc, char *argv[], struct uftrace_opts *opts); int command_dump(int argc, char *argv[], struct uftrace_opts *opts); int command_graph(int argc, char *argv[], struct uftrace_opts *opts); int command_script(int argc, char *argv[], struct uftrace_opts *opts); int command_tui(int argc, char *argv[], struct uftrace_opts *opts); extern volatile bool uftrace_done; int open_data_file(struct uftrace_opts *opts, struct uftrace_data *handle); int open_info_file(struct uftrace_opts *opts, struct uftrace_data *handle); void __close_data_file(struct uftrace_opts *opts, struct uftrace_data *handle, bool unload_modules); static inline void close_data_file(struct uftrace_opts *opts, struct uftrace_data *handle) { __close_data_file(opts, handle, true); } int read_task_file(struct uftrace_session_link *sess, char *dirname, bool needs_symtab, bool sym_rel_addr, bool needs_srcline); int read_task_txt_file(struct uftrace_session_link *sess, char *dirname, char *symdir, bool needs_symtab, bool sym_rel_addr, bool needs_srcline); char *get_libmcount_path(struct uftrace_opts *opts); void put_libmcount_path(char *libpath); #define SESSION_ID_LEN 16 #define TASK_COMM_LEN 16 #define TASK_COMM_LAST (TASK_COMM_LEN - 1) struct uftrace_session { struct rb_node node; char sid[SESSION_ID_LEN]; uint64_t start_time; int pid, tid; struct uftrace_sym_info sym_info; struct rb_root filters; struct rb_root fixups; struct list_head dlopen_libs; int namelen; char exename[]; }; struct uftrace_sess_ref { struct uftrace_sess_ref *next; struct uftrace_session *sess; uint64_t start, end; }; struct uftrace_dlopen_list { struct list_head list; uint64_t time; unsigned long base; struct uftrace_module *mod; }; struct uftrace_task { int pid, tid, ppid; char comm[TASK_COMM_LEN]; struct rb_node node; struct uftrace_sess_ref sref; struct uftrace_sess_ref *sref_last; struct list_head children; struct list_head siblings; struct { uint64_t run; uint64_t idle; uint64_t stamp; } time; }; #define UFTRACE_MSG_MAGIC 0xface enum uftrace_msg_type { UFTRACE_MSG_REC_START = 1, UFTRACE_MSG_REC_END, UFTRACE_MSG_TASK_START, UFTRACE_MSG_TASK_END, UFTRACE_MSG_FORK_START, UFTRACE_MSG_FORK_END, UFTRACE_MSG_SESSION, UFTRACE_MSG_LOST, UFTRACE_MSG_DLOPEN, UFTRACE_MSG_FINISH, UFTRACE_MSG_SEND_START = 100, UFTRACE_MSG_SEND_DIR_NAME, UFTRACE_MSG_SEND_DATA, UFTRACE_MSG_SEND_KERNEL_DATA, UFTRACE_MSG_SEND_PERF_DATA, UFTRACE_MSG_SEND_INFO, UFTRACE_MSG_SEND_META_DATA, UFTRACE_MSG_SEND_END, UFTRACE_MSG_AGENT_CLOSE = 200, /* close the connection */ UFTRACE_MSG_AGENT_QUERY, /* perform connection handshake */ UFTRACE_MSG_AGENT_GET_OPT, /* get current option value */ UFTRACE_MSG_AGENT_SET_OPT, /* set new option value */ UFTRACE_MSG_AGENT_OK, /* ack previous message */ UFTRACE_MSG_AGENT_ERR, /* signal error on previous message */ }; /* msg format for communicating by pipe */ struct uftrace_msg { unsigned short magic; /* UFTRACE_MSG_MAGIC */ unsigned short type; /* UFTRACE_MSG_* */ unsigned int len; unsigned char data[]; }; struct uftrace_msg_task { uint64_t time; int32_t pid; int32_t tid; }; struct uftrace_msg_sess { struct uftrace_msg_task task; char sid[16]; int unused; int namelen; char exename[]; }; struct uftrace_msg_dlopen { struct uftrace_msg_task task; uint64_t base_addr; char sid[16]; int unused; int namelen; char exename[]; }; enum uftrace_agent_opt { UFTRACE_AGENT_OPT_TRACE = (1U << 0), /* turn tracing on/off */ UFTRACE_AGENT_OPT_DEPTH = (1U << 1), /* mcount depth filter */ UFTRACE_AGENT_OPT_THRESHOLD = (1U << 2), /* mcount time filter */ UFTRACE_AGENT_OPT_PATTERN = (1U << 3), /* pattern match type */ UFTRACE_AGENT_OPT_FILTER = (1U << 4), /* tracing filters */ UFTRACE_AGENT_OPT_CALLER = (1U << 5), /* tracing caller filters */ UFTRACE_AGENT_OPT_TRIGGER = (1U << 6), /* tracing trigger actions */ }; extern struct uftrace_session *first_session; void create_session(struct uftrace_session_link *sess, struct uftrace_msg_sess *msg, char *dirname, char *symdir, char *exename, bool sym_rel_addr, bool needs_symtab, bool needs_srcline); struct uftrace_session *find_task_session(struct uftrace_session_link *sess, struct uftrace_task *task, uint64_t timestamp); void create_task(struct uftrace_session_link *sess, struct uftrace_msg_task *msg, bool fork); struct uftrace_task *find_task(struct uftrace_session_link *sess, int tid); void read_session_map(char *dirname, struct uftrace_sym_info *sinfo, char *sid); void delete_session_map(struct uftrace_sym_info *sinfo); void update_session_map(const char *filename); struct uftrace_session *get_session_from_sid(struct uftrace_session_link *sess, char sid[]); void session_add_dlopen(struct uftrace_session *sess, uint64_t timestamp, unsigned long base_addr, const char *libname); struct uftrace_symbol *session_find_dlsym(struct uftrace_session *sess, uint64_t timestamp, unsigned long addr); void delete_sessions(struct uftrace_session_link *sess); struct uftrace_record; struct uftrace_symbol *task_find_sym(struct uftrace_session_link *sess, struct uftrace_task_reader *task, struct uftrace_record *rec); struct uftrace_symbol *task_find_sym_addr(struct uftrace_session_link *sess, struct uftrace_task_reader *task, uint64_t time, uint64_t addr); struct uftrace_dbg_loc *task_find_loc_addr(struct uftrace_session_link *sess, struct uftrace_task_reader *task, uint64_t time, uint64_t addr); typedef int (*walk_sessions_cb_t)(struct uftrace_session *session, void *arg); void walk_sessions(struct uftrace_session_link *sess, walk_sessions_cb_t callback, void *arg); typedef int (*walk_tasks_cb_t)(struct uftrace_task *task, void *arg); void walk_tasks(struct uftrace_session_link *sess, walk_tasks_cb_t callback, void *arg); int setup_client_socket(struct uftrace_opts *opts); void send_trace_dir_name(int sock, char *name); void send_trace_data(int sock, int tid, void *data, size_t len); void send_trace_kernel_data(int sock, int cpu, void *data, size_t len); void send_trace_perf_data(int sock, int cpu, void *data, size_t len); void send_trace_metadata(int sock, const char *dirname, char *filename); void send_trace_info(int sock, struct uftrace_file_header *hdr, void *info, int len); void send_trace_end(int sock); void write_task_info(const char *dirname, struct uftrace_msg_task *tmsg); void write_fork_info(const char *dirname, struct uftrace_msg_task *tmsg); void write_session_info(const char *dirname, struct uftrace_msg_sess *smsg, const char *exename); void write_dlopen_info(const char *dirname, struct uftrace_msg_dlopen *dmsg, const char *libname); enum uftrace_record_type { UFTRACE_ENTRY, UFTRACE_EXIT, UFTRACE_LOST, UFTRACE_EVENT, }; #define RECORD_MAGIC_V3 0xa #define RECORD_MAGIC_V4 0x5 #define RECORD_MAGIC RECORD_MAGIC_V4 /* reduced version of mcount_ret_stack */ struct uftrace_record { uint64_t time; uint64_t type : 2; uint64_t more : 1; uint64_t magic : 3; uint64_t depth : 10; uint64_t addr : 48; /* child ip or uftrace_event_id */ }; static inline bool is_v3_compat(struct uftrace_record *urec) { /* (RECORD_MAGIC_V4 << 1 | more) == RECORD_MAGIC_V3 */ return urec->magic == RECORD_MAGIC && urec->more == 0; } struct uftrace_fstack_args { struct list_head *args; unsigned len; void *data; }; struct uftrace_rstack_list { struct list_head read; struct list_head unused; int count; }; struct uftrace_rstack_list_node { struct list_head list; struct uftrace_record rstack; struct uftrace_fstack_args args; }; void setup_rstack_list(struct uftrace_rstack_list *list); void add_to_rstack_list(struct uftrace_rstack_list *list, struct uftrace_record *rstack, struct uftrace_fstack_args *args); struct uftrace_record *get_first_rstack_list(struct uftrace_rstack_list *); void consume_first_rstack_list(struct uftrace_rstack_list *list); void delete_last_rstack_list(struct uftrace_rstack_list *list); void reset_rstack_list(struct uftrace_rstack_list *list); enum uftrace_ext_type { FTRACE_ARGUMENT = 1, }; static inline bool has_perf_data(struct uftrace_data *handle) { return handle->perf != NULL; } static inline bool has_event_data(struct uftrace_data *handle) { return handle->perf_event_processed; } struct rusage; int fill_file_header(struct uftrace_opts *opts, int status, struct rusage *rusage, char *elapsed_time); void fill_uftrace_info(uint64_t *info_mask, int fd, struct uftrace_opts *opts, int status, struct rusage *rusage, char *elapsed_time); int read_uftrace_info(uint64_t info_mask, struct uftrace_data *handle); void process_uftrace_info(struct uftrace_data *handle, struct uftrace_opts *opts, void (*process)(void *data, const char *fmt, ...), void *data); void clear_uftrace_info(struct uftrace_info *info); int arch_fill_cpuinfo_model(int fd); enum uftrace_event_id { EVENT_ID_KERNEL = 0U, /* kernel IDs are read from tracefs */ EVENT_ID_BUILTIN = 100000U, EVENT_ID_READ_PROC_STATM, EVENT_ID_READ_PAGE_FAULT, EVENT_ID_DIFF_PROC_STATM, EVENT_ID_DIFF_PAGE_FAULT, EVENT_ID_READ_PMU_CYCLE, EVENT_ID_DIFF_PMU_CYCLE, EVENT_ID_READ_PMU_CACHE, EVENT_ID_DIFF_PMU_CACHE, EVENT_ID_READ_PMU_BRANCH, EVENT_ID_DIFF_PMU_BRANCH, EVENT_ID_WATCH_CPU, /* supported perf events */ EVENT_ID_PERF = 200000U, EVENT_ID_PERF_SCHED_IN, EVENT_ID_PERF_SCHED_OUT, EVENT_ID_PERF_SCHED_BOTH, EVENT_ID_PERF_TASK, EVENT_ID_PERF_EXIT, EVENT_ID_PERF_COMM, EVENT_ID_PERF_SCHED_OUT_PREEMPT, EVENT_ID_PERF_SCHED_BOTH_PREEMPT, EVENT_ID_USER = 1000000U, EVENT_ID_EXTERN_DATA = 2000000U, }; struct uftrace_event { struct list_head list; enum uftrace_event_id id; char *provider; char *event; }; #define HTML_HEADER \ "\n" \ "\n" \ "\n" \ "
\n"

#define HTML_FOOTER                                                                                \
	"
\n" \ "\n" \ "\n" /* for unit tests */ int prepare_test_data(struct uftrace_opts *opts, struct uftrace_data *handle); int release_test_data(struct uftrace_opts *opts, struct uftrace_data *handle); #endif /* UFTRACE_H */ uftrace-0.15.2/utils/000077500000000000000000000000001455365734300144075ustar00rootroot00000000000000uftrace-0.15.2/utils/arch.h000066400000000000000000000076601455365734300155060ustar00rootroot00000000000000/* * Architecture specific code and data */ #ifndef UFTRACE_ARCH_H #define UFTRACE_ARCH_H #include enum uftrace_cpu_arch { UFT_CPU_NONE, UFT_CPU_X86_64, UFT_CPU_ARM, UFT_CPU_AARCH64, UFT_CPU_I386, UFT_CPU_RISCV64, }; static inline enum uftrace_cpu_arch host_cpu_arch(void) { #if defined(__x86_64__) return UFT_CPU_X86_64; #elif defined(__arm__) return UFT_CPU_ARM; #elif defined(__aarch64__) return UFT_CPU_AARCH64; #elif defined(__i386__) return UFT_CPU_I386; #elif defined(__riscv) && __riscv_xlen == 64 return UFT_CPU_RISCV64; #else return UFT_CPU_NONE; #endif } static inline bool arch_is_lp64(enum uftrace_cpu_arch arch) { switch (arch) { case UFT_CPU_X86_64: case UFT_CPU_AARCH64: case UFT_CPU_RISCV64: return true; default: return false; } } enum uftrace_x86_64_reg_index { UFT_X86_64_REG_INT_BASE = 0, /* integer registers */ UFT_X86_64_REG_RDI, UFT_X86_64_REG_RSI, UFT_X86_64_REG_RDX, UFT_X86_64_REG_RCX, UFT_X86_64_REG_R8, UFT_X86_64_REG_R9, UFT_X86_64_REG_FLOAT_BASE = 100, /* floating-point registers */ UFT_X86_64_REG_XMM0, UFT_X86_64_REG_XMM1, UFT_X86_64_REG_XMM2, UFT_X86_64_REG_XMM3, UFT_X86_64_REG_XMM4, UFT_X86_64_REG_XMM5, UFT_X86_64_REG_XMM6, UFT_X86_64_REG_XMM7, }; enum uftrace_arm_reg_index { UFT_ARM_REG_INT_BASE = 0, /* integer registers */ UFT_ARM_REG_R0, UFT_ARM_REG_R1, UFT_ARM_REG_R2, UFT_ARM_REG_R3, UFT_ARM_REG_FLOAT_BASE = 100, /* (single-precision) floating-point registers */ UFT_ARM_REG_S0, UFT_ARM_REG_S1, UFT_ARM_REG_S2, UFT_ARM_REG_S3, UFT_ARM_REG_S4, UFT_ARM_REG_S5, UFT_ARM_REG_S6, UFT_ARM_REG_S7, UFT_ARM_REG_S8, UFT_ARM_REG_S9, UFT_ARM_REG_S10, UFT_ARM_REG_S11, UFT_ARM_REG_S12, UFT_ARM_REG_S13, UFT_ARM_REG_S14, UFT_ARM_REG_S15, /* double-precision registers */ UFT_ARM_REG_DOUBLE_BASE = 200, UFT_ARM_REG_D0, UFT_ARM_REG_D1, UFT_ARM_REG_D2, UFT_ARM_REG_D3, UFT_ARM_REG_D4, UFT_ARM_REG_D5, UFT_ARM_REG_D6, UFT_ARM_REG_D7, }; enum uftrace_aarch64_reg_index { UFT_AARCH64_REG_INT_BASE = 0, /* integer registers */ UFT_AARCH64_REG_X0, UFT_AARCH64_REG_X1, UFT_AARCH64_REG_X2, UFT_AARCH64_REG_X3, UFT_AARCH64_REG_X4, UFT_AARCH64_REG_X5, UFT_AARCH64_REG_X6, UFT_AARCH64_REG_X7, UFT_AARCH64_REG_FLOAT_BASE = 100, /* (single-precision) floating-point registers */ UFT_AARCH64_REG_S0, UFT_AARCH64_REG_S1, UFT_AARCH64_REG_S2, UFT_AARCH64_REG_S3, UFT_AARCH64_REG_S4, UFT_AARCH64_REG_S5, UFT_AARCH64_REG_S6, UFT_AARCH64_REG_S7, UFT_AARCH64_REG_DOUBLE_BASE = 200, /* (double-precision) floating-point registers */ UFT_AARCH64_REG_D0, UFT_AARCH64_REG_D1, UFT_AARCH64_REG_D2, UFT_AARCH64_REG_D3, UFT_AARCH64_REG_D4, UFT_AARCH64_REG_D5, UFT_AARCH64_REG_D6, UFT_AARCH64_REG_D7, }; enum uftrace_i386_reg_index { UFT_I386_REG_INT_BASE = 0, /* integer registers */ UFT_I386_REG_ECX, UFT_I386_REG_EDX, UFT_I386_REG_FLOAT_BASE = 100, /* floating-point registers */ UFT_I386_REG_XMM0, UFT_I386_REG_XMM1, UFT_I386_REG_XMM2, UFT_I386_REG_XMM3, UFT_I386_REG_XMM4, UFT_I386_REG_XMM5, UFT_I386_REG_XMM6, UFT_I386_REG_XMM7, }; enum uftrace_riscv64_reg_index { UFT_RISCV64_REG_INT_BASE = 0, /* integer argument registers */ UFT_RISCV64_REG_A0, UFT_RISCV64_REG_A1, UFT_RISCV64_REG_A2, UFT_RISCV64_REG_A3, UFT_RISCV64_REG_A4, UFT_RISCV64_REG_A5, UFT_RISCV64_REG_A6, UFT_RISCV64_REG_A7, UFT_RISCV64_REG_FLOAT_BASE = 100, /* floating-point argument registers */ UFT_RISCV64_REG_FA0, UFT_RISCV64_REG_FA1, UFT_RISCV64_REG_FA2, UFT_RISCV64_REG_FA3, UFT_RISCV64_REG_FA4, UFT_RISCV64_REG_FA5, UFT_RISCV64_REG_FA6, UFT_RISCV64_REG_FA7, }; int arch_register_number(enum uftrace_cpu_arch arch, char *reg_name); int arch_register_at(enum uftrace_cpu_arch arch, bool integer, int idx); int arch_register_index(enum uftrace_cpu_arch arch, int idx); const char *arch_register_dwarf_name(enum uftrace_cpu_arch arch, int dwarf_reg); const char *arch_register_argspec_name(enum uftrace_cpu_arch arch, bool integer, int idx); #endif /* UFTRACE_ARCH_H */ uftrace-0.15.2/utils/argspec.c000066400000000000000000000133071455365734300162030ustar00rootroot00000000000000#include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "filter" #define PR_DOMAIN DBG_FILTER #include "uftrace.h" #include "utils/arch.h" #include "utils/argspec.h" #include "utils/filter.h" #include "utils/utils.h" static bool is_arm_machine(struct uftrace_filter_setting *setting) { return setting->arch == UFT_CPU_ARM; } static int check_so_cb(struct dl_phdr_info *info, size_t size, void *data) { const char *soname = data; int so_used = 0; if (!strncmp(basename(info->dlpi_name), soname, strlen(soname))) so_used = 1; return so_used; } /* check whether the given library name is in shared object list */ static int has_shared_object(const char *soname) { static int so_used = -1; if (so_used != -1) return so_used; so_used = dl_iterate_phdr(check_so_cb, (void *)soname); return so_used; } /* argument_spec = arg1/i32,arg2/x64,... */ struct uftrace_arg_spec *parse_argspec(char *str, struct uftrace_filter_setting *setting) { struct uftrace_arg_spec *arg; int fmt = ARG_FMT_AUTO; int size = setting->lp64 ? 8 : 4; int idx; int type; int bit; char *suffix; char *p; if (!strncmp(str, "arg", 3) && isdigit(str[3])) { idx = strtol(str + 3, &suffix, 0); type = ARG_TYPE_INDEX; } else if (!strncmp(str, "retval", 6)) { idx = RETVAL_IDX; type = ARG_TYPE_INDEX; suffix = str + 6; } else if (!strncmp(str, "fparg", 5) && isdigit(str[5])) { idx = strtol(str + 5, &suffix, 0); fmt = ARG_FMT_FLOAT; type = ARG_TYPE_FLOAT; size = sizeof(double); } else { pr_dbg("invalid argspec: %s\n", str); return NULL; } arg = xzalloc(sizeof(*arg)); INIT_LIST_HEAD(&arg->list); if (suffix == NULL || *suffix == '\0') goto out; if (*suffix == '%') goto type; if (*suffix != '/') goto err; suffix++; switch (*suffix) { case 'd': fmt = ARG_FMT_AUTO; break; case 'i': fmt = ARG_FMT_SINT; break; case 'u': fmt = ARG_FMT_UINT; break; case 'x': fmt = ARG_FMT_HEX; break; case 's': fmt = ARG_FMT_STR; break; case 'c': fmt = ARG_FMT_CHAR; size = sizeof(char); break; case 'f': fmt = ARG_FMT_FLOAT; type = ARG_TYPE_FLOAT; size = sizeof(double); break; case 'S': if (has_shared_object("libc++.so")) { static bool warned = false; if (!warned) { pr_warn("std::string display for libc++.so is " "not supported.\n"); warned = true; } goto err; } fmt = ARG_FMT_STD_STRING; break; case 'p': fmt = ARG_FMT_PTR; break; case 'e': fmt = ARG_FMT_ENUM; if (suffix[1] != ':' || (!isalpha(suffix[2]) && suffix[2] != '_')) { pr_use("invalid enum spec: %s\n", suffix); goto err; } arg->type_name = xstrdup(&suffix[2]); p = strchr(arg->type_name, '%'); if (p) *p = '\0'; pr_dbg2("parsing argspec for enum: %s\n", arg->type_name); suffix += strlen(arg->type_name) + 2; goto type; case 't': /* struct/union/class passed-by-value */ fmt = ARG_FMT_STRUCT; size = strtol(&suffix[1], &suffix, 0); arg->struct_reg_cnt = 0; if (*suffix == ':') { arg->type_name = xstrdup(&suffix[1]); p = strchr(arg->type_name, '%'); if (p) *p = '\0'; suffix += strlen(arg->type_name) + 1; } pr_dbg2("parsing argspec for struct: %s\n", arg->type_name ?: "(no name)"); if (*suffix == '%') { if (!strncmp(suffix, "%stack+", 7)) goto type; do { short reg; char *next = strchr(suffix, '+'); if (next) *next = '\0'; reg = arch_register_number(setting->arch, ++suffix); if (reg >= 0) { arg->struct_regs[arg->struct_reg_cnt++] = reg; arg->reg_idx = reg; } suffix = next; } while (suffix); if (arg->struct_reg_cnt) type = ARG_TYPE_REG; } goto out; default: if (fmt == ARG_FMT_FLOAT && isdigit(*suffix)) goto size; pr_use("unsupported argument type: %s\n", str); goto err; } suffix++; if (*suffix == '\0') goto out; if (*suffix == '%') goto type; size: bit = strtol(suffix, &suffix, 10); switch (bit) { case 8: case 16: case 32: case 64: size = bit / 8; break; case 80: if (fmt == ARG_FMT_FLOAT) { size = bit / 8; break; } /* fall through */ default: pr_use("unsupported argument size: %s\n", str); goto err; } type: if (*suffix == '%') { suffix++; if (!strncmp(suffix, "stack", 5)) { arg->stack_ofs = strtol(suffix + 5, NULL, 0); type = ARG_TYPE_STACK; } else { arg->reg_idx = arch_register_number(setting->arch, suffix); type = ARG_TYPE_REG; if (arg->reg_idx < 0) { pr_use("unknown register name: %s\n", str); goto err; } } } else if (*suffix != '\0') goto err; out: /* it seems ARM falls back 'long double' to 'double' */ if (fmt == ARG_FMT_FLOAT && size == 10 && is_arm_machine(setting)) size = 8; arg->idx = idx; arg->fmt = fmt; arg->size = size; arg->type = type; return arg; err: pr_dbg("argspec parse failed: %s\n", str); free_arg_spec(arg); return NULL; } void free_arg_spec(struct uftrace_arg_spec *arg) { free(arg->type_name); free(arg); } #ifdef UNIT_TEST TEST_CASE(argspec_parse_struct) { char *str; struct uftrace_arg_spec *spec; struct uftrace_filter_setting setting = { .arch = UFT_CPU_X86_64 }; /* parse_argspec might change the string, copy it */ str = strdup("arg3/t16:mystruct%RDI+RSI"); pr_dbg("parsing a struct passed by value: %s\n", str); spec = parse_argspec(str, &setting); TEST_NE(spec, NULL); TEST_EQ(spec->idx, 3); TEST_EQ(spec->fmt, ARG_FMT_STRUCT); TEST_EQ(spec->size, 16); TEST_EQ(spec->type, ARG_TYPE_REG); TEST_STREQ(spec->type_name, "mystruct"); TEST_EQ(spec->struct_reg_cnt, 2); TEST_EQ(spec->struct_regs[0], UFT_X86_64_REG_RDI); TEST_EQ(spec->struct_regs[1], UFT_X86_64_REG_RSI); free_arg_spec(spec); free(str); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/argspec.h000066400000000000000000000044361455365734300162130ustar00rootroot00000000000000#ifndef UFTRACE_ARGSPEC_H #define UFTRACE_ARGSPEC_H #include "utils/list.h" #include "utils/rbtree.h" #include #include enum uftrace_arg_format { ARG_FMT_AUTO, ARG_FMT_SINT, ARG_FMT_UINT, ARG_FMT_HEX, ARG_FMT_STR, ARG_FMT_CHAR, ARG_FMT_FLOAT, ARG_FMT_STD_STRING, ARG_FMT_PTR, ARG_FMT_ENUM, ARG_FMT_STRUCT, }; #define ARG_TYPE_INDEX 0 #define ARG_TYPE_FLOAT 1 #define ARG_TYPE_REG 2 #define ARG_TYPE_STACK 3 /* should match with uftrace_arg_format above */ #define ARG_SPEC_CHARS "diuxscfSpet" /** * uftrace_arg_spec contains arguments and return value info. * * If idx is zero, it means the recorded data is return value. * * If idx is not zero, it means the recorded data is arguments * and idx shows the sequence order of arguments. */ #define RETVAL_IDX 0 struct uftrace_arg_spec { struct list_head list; int idx; enum uftrace_arg_format fmt; int size; bool exact; unsigned char type; short struct_reg_cnt; union { short reg_idx; short stack_ofs; }; char *type_name; short struct_regs[4]; }; struct uftrace_filter_setting; struct uftrace_arg_spec *parse_argspec(char *str, struct uftrace_filter_setting *setting); void setup_auto_args(struct uftrace_filter_setting *setting); void setup_auto_args_str(char *args, char *rets, char *enums, struct uftrace_filter_setting *setting); void finish_auto_args(void); void free_arg_spec(struct uftrace_arg_spec *arg); struct uftrace_dbg_info; struct uftrace_filter; struct uftrace_trigger; struct uftrace_filter *find_auto_argspec(struct uftrace_filter *filter, struct uftrace_trigger *tr, struct uftrace_dbg_info *dinfo, struct uftrace_filter_setting *setting); struct uftrace_filter *find_auto_retspec(struct uftrace_filter *filter, struct uftrace_trigger *tr, struct uftrace_dbg_info *dinfo, struct uftrace_filter_setting *setting); char *get_auto_argspec_str(void); char *get_auto_retspec_str(void); char *get_auto_enum_str(void); int extract_trigger_args(char **pargs, char **prets, char *trigger); int parse_enum_string(char *enum_str, struct rb_root *root); char *get_enum_string(struct rb_root *root, char *name, long val); void save_enum_def(struct rb_root *root, FILE *fp); void release_enum_def(struct rb_root *root); extern struct rb_root dwarf_enum; #endif /* UFTRACE_ARGSPEC_H */ uftrace-0.15.2/utils/asm.h000066400000000000000000000007041455365734300153410ustar00rootroot00000000000000#ifndef UFTRACE_ASM_H #define UFTRACE_ASM_H /* clang-format off */ #define GLOBAL(sym) \ .global sym; \ .type sym, %function; \ sym: \ .global uftrace_ ## sym; \ .hidden uftrace_ ## sym; \ .type uftrace_ ## sym, %function; \ uftrace_ ## sym: #define ENTRY(sym) \ .global sym; \ .hidden sym; \ .type sym, %function; \ sym: #define END(sym) \ .size sym, .-sym; /* clang-format on */ #endif /* UFTRACE_ASM_H */ uftrace-0.15.2/utils/auto-args.c000066400000000000000000000503021455365734300164550ustar00rootroot00000000000000#include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "filter" #define PR_DOMAIN DBG_FILTER #include "uftrace.h" #include "utils/argspec.h" #include "utils/auto-args.h" #include "utils/dwarf.h" #include "utils/filter.h" #include "utils/list.h" #include "utils/rbtree.h" #include "utils/symbol.h" #include "utils/utils.h" /* RB-tree maintaining automatic arguments and return value */ static struct rb_root auto_argspec = RB_ROOT; static struct rb_root auto_retspec = RB_ROOT; static struct rb_root auto_enum = RB_ROOT; extern void update_trigger(struct uftrace_filter *filter, struct uftrace_trigger *tr, bool exact_match); extern int setup_trigger_action(char *str, struct uftrace_trigger *tr, char **module, unsigned long orig_flags, struct uftrace_filter_setting *setting); static void add_auto_args(struct rb_root *root, struct uftrace_filter *entry, struct uftrace_trigger *tr) { struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; struct uftrace_filter *iter, *new; int cmp; pr_dbg2("add auto-argument for %s\n", entry->name); while (*p) { parent = *p; iter = rb_entry(parent, struct uftrace_filter, node); cmp = strcmp(iter->name, entry->name); if (cmp == 0) { update_trigger(iter, tr, true); return; } if (cmp < 0) p = &parent->rb_left; else p = &parent->rb_right; } new = xmalloc(sizeof(*new)); memcpy(new, entry, sizeof(*new)); new->trigger.flags = 0; INIT_LIST_HEAD(&new->args); new->trigger.pargs = &new->args; update_trigger(new, tr, true); rb_link_node(&new->node, parent, p); rb_insert_color(&new->node, root); } static void build_auto_args(const char *args_str, struct rb_root *root, unsigned long flag, struct uftrace_filter_setting *setting) { struct strv specs = STRV_INIT; char *name; int j; if (args_str == NULL) return; strv_split(&specs, args_str, ";"); strv_for_each(&specs, name, j) { LIST_HEAD(args); struct uftrace_arg_spec *arg; struct uftrace_trigger tr = { .pargs = &args, }; struct uftrace_filter entry = { .name = NULL, }; char *p = strchr(name, '@'); if (p == NULL) continue; /* * save original spec string in 'end'. * it needs to be done before setup_trigger_action() * splitting the original string. */ entry.end = (unsigned long)xstrdup(p + 1); if (setup_trigger_action(name, &tr, NULL, flag, setting) < 0) goto next; /* * it should be copied after setup_trigger_action() removed * '@' for the arg spec */ entry.name = demangle(name); add_auto_args(root, &entry, &tr); next: while (!list_empty(&args)) { arg = list_first_entry(&args, struct uftrace_arg_spec, list); list_del(&arg->list); free_arg_spec(arg); } } strv_free(&specs); } static struct uftrace_filter *find_auto_args(struct rb_root *root, char *name) { struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; struct uftrace_filter *iter; int cmp; while (*p) { parent = *p; iter = rb_entry(parent, struct uftrace_filter, node); cmp = strcmp(iter->name, name); if (cmp == 0) return iter; if (cmp < 0) p = &parent->rb_left; else p = &parent->rb_right; } return NULL; } static struct uftrace_filter *dwarf_argspec_list; static struct uftrace_filter *find_dwarf_argspec(struct uftrace_filter *filter, struct uftrace_dbg_info *dinfo, bool is_retval, struct uftrace_filter_setting *setting) { LIST_HEAD(dwarf_argspec); struct uftrace_filter *dwarf_filter; struct uftrace_trigger dwarf_tr = { .pargs = &dwarf_argspec, }; char *arg_str; unsigned long flag = is_retval ? TRIGGER_FL_RETVAL : TRIGGER_FL_ARGUMENT; unsigned long addr = filter->start; if (is_retval) arg_str = get_dwarf_retspec(dinfo, filter->name, addr); else arg_str = get_dwarf_argspec(dinfo, filter->name, addr); if (arg_str == NULL) return NULL; arg_str = xstrdup(arg_str); setup_trigger_action(arg_str, &dwarf_tr, NULL, flag, setting); if (list_empty(dwarf_tr.pargs)) { free(arg_str); return NULL; } dwarf_filter = xzalloc(sizeof(*dwarf_filter)); INIT_LIST_HEAD(&dwarf_filter->args); list_splice(dwarf_tr.pargs, &dwarf_filter->args); dwarf_filter->trigger.pargs = &dwarf_filter->args; dwarf_filter->trigger.flags = dwarf_tr.flags; /* XXX: since 'name' was not used here, abuse it as a linked list */ dwarf_filter->name = (void *)dwarf_argspec_list; dwarf_argspec_list = dwarf_filter; free(arg_str); return dwarf_filter; } struct uftrace_filter *find_auto_argspec(struct uftrace_filter *filter, struct uftrace_trigger *tr, struct uftrace_dbg_info *dinfo, struct uftrace_filter_setting *setting) { struct uftrace_filter *auto_arg = NULL; if (debug_info_has_argspec(dinfo)) auto_arg = find_dwarf_argspec(filter, dinfo, false, setting); if (auto_arg == NULL) auto_arg = find_auto_args(&auto_argspec, filter->name); return auto_arg; } struct uftrace_filter *find_auto_retspec(struct uftrace_filter *filter, struct uftrace_trigger *tr, struct uftrace_dbg_info *dinfo, struct uftrace_filter_setting *setting) { struct uftrace_filter *auto_ret = NULL; if (debug_info_has_argspec(dinfo)) auto_ret = find_dwarf_argspec(filter, dinfo, true, setting); if (auto_ret == NULL) auto_ret = find_auto_args(&auto_retspec, filter->name); return auto_ret; } char *get_auto_argspec_str(void) { return auto_args_list; } char *get_auto_retspec_str(void) { return auto_retvals_list; } void setup_auto_args(struct uftrace_filter_setting *setting) { parse_enum_string(auto_enum_list, &auto_enum); build_auto_args(auto_args_list, &auto_argspec, TRIGGER_FL_ARGUMENT, setting); build_auto_args(auto_retvals_list, &auto_retspec, TRIGGER_FL_RETVAL, setting); } void setup_auto_args_str(char *args, char *rets, char *enums, struct uftrace_filter_setting *setting) { parse_enum_string(enums, &auto_enum); build_auto_args(args, &auto_argspec, TRIGGER_FL_ARGUMENT, setting); build_auto_args(rets, &auto_retspec, TRIGGER_FL_RETVAL, setting); } static void release_auto_args(struct rb_root *root) { struct rb_node *p; struct uftrace_filter *entry; struct uftrace_arg_spec *arg, *tmp; while (!RB_EMPTY_ROOT(root)) { p = rb_first(root); entry = rb_entry(p, struct uftrace_filter, node); rb_erase(p, root); list_for_each_entry_safe(arg, tmp, &entry->args, list) { list_del(&arg->list); free_arg_spec(arg); } free(entry->name); free((void *)(uintptr_t)entry->end); free(entry); } } void finish_auto_args(void) { struct uftrace_filter *tmp; struct uftrace_arg_spec *spec; release_enum_def(&auto_enum); release_auto_args(&auto_argspec); release_auto_args(&auto_retspec); while (dwarf_argspec_list) { tmp = (void *)dwarf_argspec_list->name; while (!list_empty(dwarf_argspec_list->trigger.pargs)) { spec = list_first_entry(dwarf_argspec_list->trigger.pargs, typeof(*spec), list); list_del(&spec->list); free_arg_spec(spec); } free(dwarf_argspec_list); dwarf_argspec_list = tmp; } } /** * extract_trigger_args - extract argspec from trigger actions * @pargs: pointer to existing argspec * @prets: pointer to existing retspec * @trigger: trigger string * * This function extracts arg/ret spec from the trigger string (if any) * and append them to pargs or prets respectively so that they can be * saved into the info section. * * It returns 0 if none of arguments or return values are specified, * 1 if only one of them is specified, and 2 if both are given. */ int extract_trigger_args(char **pargs, char **prets, char *trigger) { char *argspec = NULL; char *retspec = NULL; /* extract argspec (and retspec) in trigger action */ if (trigger) { struct strv actions = STRV_INIT; char *pos, *act; int j; strv_split(&actions, trigger, ";"); strv_for_each(&actions, pos, j) { char *name = pos; char *args = NULL; char *rval = NULL; bool auto_args = false; act = strchr(name, '@'); if (act == NULL) continue; *act++ = '\0'; while ((pos = strsep(&act, ",")) != NULL) { if (!strncasecmp(pos, "arg", 3) || !strncasecmp(pos, "fparg", 5)) args = strjoin(args, pos, ","); if (!strncasecmp(pos, "retval", 6)) rval = "retval"; if (!strncasecmp(pos, "auto-args", 9)) auto_args = true; } if (args) { xasprintf(&act, "%s@%s", name, args); argspec = strjoin(argspec, act, ";"); free(act); free(args); } if (rval) { xasprintf(&act, "%s@retval", name); retspec = strjoin(retspec, act, ";"); free(act); } if (auto_args) { argspec = strjoin(argspec, name, ";"); retspec = strjoin(retspec, name, ";"); } } strv_free(&actions); } if (*pargs) argspec = strjoin(argspec, *pargs, ";"); if (*prets) retspec = strjoin(retspec, *prets, ";"); *pargs = argspec; *prets = retspec; return !!argspec + !!retspec; } enum enum_token_ret { TOKEN_INVALID = -1, TOKEN_NULL, TOKEN_STR, TOKEN_SIGN, TOKEN_NUM, }; static char enum_token[256]; static enum enum_token_ret enum_next_token(char **str) { char *pos, *tok; enum enum_token_ret ret; ptrdiff_t len; tok = *str; if (tok == NULL) return TOKEN_NULL; while (isspace(*tok)) tok++; if (*tok == '\0') return TOKEN_NULL; if (ispunct(*tok) && *tok != '_') { enum_token[0] = *tok; enum_token[1] = '\0'; *str = tok + 1; return TOKEN_SIGN; } if (isalpha(*tok) || *tok == '_') ret = TOKEN_STR; else if (isdigit(*tok)) ret = TOKEN_NUM; else return TOKEN_INVALID; pos = strpbrk(tok, " \n\t=,{}"); if (pos != NULL) len = pos - tok; else len = strlen(tok); if ((size_t)len >= sizeof(enum_token)) len = sizeof(enum_token) - 1; strncpy(enum_token, tok, len); enum_token[len] = '\0'; *str = pos; return ret; } struct enum_def { char *name; struct list_head vals; struct rb_node node; }; struct enum_val { struct list_head list; char *str; long val; }; static void free_enum_def(struct enum_def *e_def) { struct enum_val *e_val; if (e_def == NULL) return; while (!list_empty(&e_def->vals)) { e_val = list_first_entry(&e_def->vals, struct enum_val, list); list_del(&e_val->list); free(e_val->str); free(e_val); } free(e_def->name); free(e_def); } static void add_enum_tree(struct rb_root *root, struct enum_def *e_def) { struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; struct enum_def *iter; int cmp; pr_dbg2("add enum definition for %s\n", e_def->name); while (*p) { parent = *p; iter = rb_entry(parent, struct enum_def, node); cmp = strcmp(iter->name, e_def->name); if (cmp == 0) { pr_dbg2("ignore same enum name: %s\n", e_def->name); free_enum_def(e_def); return; } if (cmp < 0) p = &parent->rb_left; else p = &parent->rb_right; } rb_link_node(&e_def->node, parent, p); rb_insert_color(&e_def->node, root); } struct enum_def *find_enum_def(struct rb_root *root, char *name) { struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; struct enum_def *iter; int cmp; while (*p) { parent = *p; iter = rb_entry(parent, struct enum_def, node); cmp = strcmp(iter->name, name); if (cmp == 0) return iter; if (cmp < 0) p = &parent->rb_left; else p = &parent->rb_right; } return NULL; } char *convert_enum_val(struct enum_def *e_def, long val) { struct enum_val *e_val; char *str = NULL; /* exact match? */ list_for_each_entry(e_val, &e_def->vals, list) { if (e_val->val == val) return xstrdup(e_val->str); } /* if not, try OR-ing bit flags */ list_for_each_entry(e_val, &e_def->vals, list) { if (e_val->val <= val) { val -= e_val->val; str = strjoin(str, e_val->str, "|"); } if (val == 0) break; } /* print hex for unknown value */ if (str && val) { char *tmp; xasprintf(&tmp, "%s+%#lx", str, val); free(str); str = tmp; } else if (unlikely(str == NULL)) { if (labs(val) > 100000) xasprintf(&str, "%#lx", val); else xasprintf(&str, "%ld", val); } return str; } /* caller should free the return value */ char *get_enum_string(struct rb_root *root, char *name, long val) { struct enum_def *e_def; char *ret; e_def = find_enum_def(root, name); if (e_def == NULL) e_def = find_enum_def(&auto_enum, name); if (e_def == NULL) xasprintf(&ret, "%ld", val); else ret = convert_enum_val(e_def, val); return ret; } /** * parse_enum_string - parse enum and add it to a tree * @enum_str: string presentation of enum * * This function parses @enum_str and add it to @root so that it can be * used for argument/return later. The syntax of enum is same as C * (except for the 'enum' keyword) but it only accepts a simple integer * constant or other enum constant in RHS. * * For example, following string should be accepted: * * enum number { * ZERO = 0, * ONE, * TWO, * HUNDRED = 100, * }; */ int parse_enum_string(char *enum_str, struct rb_root *root) { char *pos; struct enum_def *e_def = NULL; struct enum_val *e_val, *e; enum enum_token_ret ret; struct strv strv = STRV_INIT; int err = -1; int j; if (enum_str == NULL) return 0; strv_split(&strv, enum_str, ";"); strv_for_each(&strv, pos, j) { long val = 0; ret = enum_next_token(&pos); /* ignore empty string */ if (ret == TOKEN_NULL) continue; if (ret != TOKEN_STR || strcmp(enum_token, "enum")) { pr_dbg("don't have 'enum' prefix\n"); goto out; } /* name is mandatory */ ret = enum_next_token(&pos); if (ret != TOKEN_STR) { pr_dbg("enum name is missing\n"); goto out; } e_def = xmalloc(sizeof(*e_def)); e_def->name = xstrdup(enum_token); INIT_LIST_HEAD(&e_def->vals); ret = enum_next_token(&pos); if (ret != TOKEN_SIGN || strcmp(enum_token, "{")) { pr_dbg("enum start brace is missing\n"); goto out; } pr_dbg2("parse enum %s\n", e_def->name); ret = enum_next_token(&pos); while (ret != TOKEN_NULL && strcmp(enum_token, "}")) { char *name = xstrdup(enum_token); ret = enum_next_token(&pos); if (ret != TOKEN_SIGN) { pr_dbg("invalid enum syntax - sign required\n"); free(name); goto out; } if (!strcmp(enum_token, "=")) { while (isspace(*pos)) pos++; val = strtol(pos, &pos, 0); /* consume ',' after the number */ ret = enum_next_token(&pos); if (ret != TOKEN_SIGN) { pr_dbg("invalid enum syntax - comma needed\n"); free(name); goto out; } } e_val = xmalloc(sizeof(*e_val)); e_val->str = name; e_val->val = val; pr_dbg3(" %s = %ld\n", name, val); /* sort by value, just in case */ list_for_each_entry(e, &e_def->vals, list) { if (e->val <= val) break; } list_add_tail(&e_val->list, &e->list); val++; if (!strcmp(enum_token, ",")) ret = enum_next_token(&pos); } if (!strcmp(enum_token, "}")) { add_enum_tree(root, e_def); e_def = NULL; } else { pr_dbg("invalid enum def: %s\n", enum_token); goto out; } } err = 0; out: free_enum_def(e_def); strv_free(&strv); return err; } static char *get_enum_def_string(struct enum_def *def) { struct enum_val *e_val; int last = -1; char *str = NULL; char *buf = NULL; list_for_each_entry_reverse(e_val, &def->vals, list) { /* simple case */ if (e_val->val == ++last) { str = strjoin(str, e_val->str, ","); continue; } last = e_val->val; xasprintf(&buf, "%s=%ld", e_val->str, e_val->val); str = strjoin(str, buf, ","); } free(buf); return str; } void save_enum_def(struct rb_root *root, FILE *fp) { struct rb_node *node; struct enum_def *e_def; char *str; node = rb_first(root); while (node) { e_def = rb_entry(node, struct enum_def, node); str = get_enum_def_string(e_def); save_debug_file(fp, 'E', e_def->name, (long)str); free(str); node = rb_next(node); } } void release_enum_def(struct rb_root *root) { struct rb_node *node; struct enum_def *e_def; node = rb_first(root); while (node) { e_def = rb_entry(node, struct enum_def, node); node = rb_next(node); rb_erase(&e_def->node, root); free_enum_def(e_def); } } char *get_auto_enum_str(void) { return auto_enum_list; } #ifdef UNIT_TEST TEST_CASE(argspec_auto_args) { char test_auto_args[] = "foo@arg1,arg2/s;bar@fparg1"; struct uftrace_filter *entry; struct uftrace_filter key; struct uftrace_arg_spec *spec; struct uftrace_filter_setting setting = { .lp64 = host_is_lp64(), }; int idx = 1; pr_dbg("build auto args from: %s\n", test_auto_args); build_auto_args(test_auto_args, &auto_argspec, TRIGGER_FL_ARGUMENT, &setting); pr_dbg("'foo' should have two arguments\n"); key.name = "foo"; entry = find_auto_argspec(&key, NULL, NULL, &setting); TEST_NE(entry, NULL); TEST_EQ(entry->trigger.flags, TRIGGER_FL_ARGUMENT); list_for_each_entry(spec, &entry->args, list) { TEST_EQ(spec->idx, idx); TEST_EQ(spec->fmt, idx == 1 ? ARG_FMT_AUTO : ARG_FMT_STR); idx++; } pr_dbg("'foo' should have one FP argument\n"); key.name = "bar"; entry = find_auto_argspec(&key, NULL, NULL, &setting); TEST_NE(entry, NULL); TEST_EQ(entry->trigger.flags, TRIGGER_FL_ARGUMENT); spec = list_first_entry(&entry->args, struct uftrace_arg_spec, list); TEST_EQ(spec->fmt, ARG_FMT_FLOAT); TEST_EQ(spec->idx, 1); pr_dbg("'xxx' should not have arguments\n"); key.name = "xxx"; entry = find_auto_argspec(&key, NULL, NULL, &setting); TEST_EQ(entry, NULL); release_auto_args(&auto_argspec); pr_dbg("'foo' should not have arguments after released\n"); key.name = "foo"; entry = find_auto_argspec(&key, NULL, NULL, &setting); TEST_EQ(entry, NULL); return TEST_OK; } TEST_CASE(argspec_extract) { char test_trigger_str1[] = "foo@arg1,retval"; char test_trigger_str2[] = "foo@trace-on;bar@depth=2,arg1/s,trace-off,arg2/x64"; char test_trigger_str3[] = "foo@libabc,arg3/i32%rax,backtrace"; char *args, *rets; pr_dbg("extracting args/rets from %s\n", test_trigger_str1); args = rets = NULL; extract_trigger_args(&args, &rets, test_trigger_str1); TEST_STREQ(args, "foo@arg1"); TEST_STREQ(rets, "foo@retval"); free(args); free(rets); pr_dbg("extracting args/rets from %s\n", test_trigger_str2); args = rets = NULL; extract_trigger_args(&args, &rets, test_trigger_str2); TEST_STREQ("bar@arg1/s,arg2/x64", args); TEST_EQ(rets, NULL); free(args); free(rets); pr_dbg("extracting args/rets from %s\n", test_trigger_str3); args = rets = NULL; extract_trigger_args(&args, &rets, test_trigger_str3); TEST_STREQ("foo@arg3/i32%rax", args); TEST_EQ(rets, NULL); free(args); free(rets); return TEST_OK; } TEST_CASE(argspec_parse_enum) { char test_enum_str1[] = "enum xxx { ZERO, ONE = 111, TWO };"; char test_enum_str2[] = "enum a { AAA, BBB = 1, CCC }"; char test_enum_str3[] = ";enum uftrace{record=100,replay=-23,report}"; struct rb_root enum_tree = RB_ROOT; struct rb_node *node; struct enum_def *e_def; struct enum_val *e_val, *e_next; char *str; pr_dbg("parse enum string: %s\n", test_enum_str1); TEST_EQ(parse_enum_string(test_enum_str1, &enum_tree), 0); pr_dbg("parse enum string: %s\n", test_enum_str2); TEST_EQ(parse_enum_string(test_enum_str2, &enum_tree), 0); pr_dbg("parse enum string: %s\n", test_enum_str3); TEST_EQ(parse_enum_string(test_enum_str3, &enum_tree), 0); node = rb_first(&enum_tree); while (node) { e_def = rb_entry(node, struct enum_def, node); e_val = list_first_entry(&e_def->vals, struct enum_val, list); e_next = list_next_entry(e_val, list); TEST_GE(e_val->val, e_next->val); e_val = list_next_entry(e_val, list); e_next = list_next_entry(e_next, list); TEST_GE(e_val->val, e_next->val); node = rb_next(node); } e_def = find_enum_def(&enum_tree, "xxx"); TEST_NE(e_def, NULL); pr_dbg("first enum item should have 0 value\n"); e_val = list_last_entry(&e_def->vals, struct enum_val, list); TEST_STREQ(e_val->str, "ZERO"); TEST_EQ(e_val->val, 0L); pr_dbg("check value increments from previous value\n"); e_val = list_first_entry(&e_def->vals, struct enum_val, list); TEST_STREQ(e_val->str, "TWO"); TEST_EQ(e_val->val, 112L); pr_dbg("check value match to multiple bitmask\n"); e_def = find_enum_def(&enum_tree, "a"); str = convert_enum_val(e_def, 3); TEST_STREQ(str, "CCC|BBB"); free(str); pr_dbg("check value increments correctly for negative numbers\n"); e_def = find_enum_def(&enum_tree, "uftrace"); str = convert_enum_val(e_def, -22); TEST_STREQ(str, "report"); free(str); release_enum_def(&enum_tree); pr_dbg("after release, it should not find any definition\n"); TEST_EQ(find_enum_def(&enum_tree, "xxx"), NULL); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/auto-args.h000066400000000000000000000412431455365734300164660ustar00rootroot00000000000000/* * Arguments / return type option tables for automatic value display * * This file is auto-generated by "gen-autoargs.py" based on prototypes.h */ /* clang-format off */ static char *auto_enum_list = "enum uft_mmap_prot { PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC = 4, };" "enum uft_mmap_flag {MAP_SHARED = 0x1,MAP_PRIVATE = 0x2,MAP_FIXED = 0x10,MAP_ANON = 0x20,MAP_GROWSDOWN = 0x100,MAP_DENYWRITE = 0x800,MAP_EXECUTABLE = 0x1000,MAP_LOCKED = 0x2000,MAP_NORESERVE = 0x4000,MAP_POPULATE = 0x8000,MAP_NONBLOCK = 0x10000,MAP_STACK = 0x20000,MAP_HUGETLB = 0x40000,};" "enum uft_madvise {MADV_NORMAL = 0,MADV_RANDOM = 1,MADV_SEQUENTIAL = 2,MADV_WILLNEED = 3,MADV_DONTNEED = 4,MADV_FREE = 8,MADV_REMOVE = 9,MADV_DONTFORK = 10,MADV_DOFORK = 11,MADV_MERGEABLE = 12,MADV_UNMERGEABLE = 13,MADV_HUGEPAGE = 14,MADV_NOHUGEPAGE = 15,MADV_DONTDUMP = 16,MADV_DODUMP = 17,MADV_HWPOISON = 100,};" "enum uft_posix_madvise {POSIX_MADV_NORMAL = 0,POSIX_MADV_RANDOM = 1,POSIX_MADV_SEQUENTIAL = 2,POSIX_MADV_WILLNEED = 3,POSIX_MADV_DONTNEED = 4,};" "enum uft_posix_fadvise {POSIX_FADV_NORMAL = 0,POSIX_FADV_RANDOM = 1,POSIX_FADV_SEQUENTIAL = 2,POSIX_FADV_WILLNEED = 3,POSIX_FADV_DONTNEED = 4,POSIX_FADV_NOREUSE = 5,};" "enum uft_open_flag {O_RDONLY = 00,O_WRONLY = 01,O_RDWR = 02,O_CREAT = 0100,O_EXCL = 0200,O_NOCTTY = 0400,O_TRUNC = 01000,O_APPEND = 02000,O_NONBLOCK = 04000,O_DSYNC = 010000,O_ASYNC = 020000,O_DIRECT = 040000,O_LARGEFILE = 0100000,O_DIRECTORY = 0200000,O_NOFOLLOW = 0400000,O_NOATIME = 01000000,O_CLOEXEC = 02000000,O_SYNC = 04010000,O_PATH = 010000000,};" "enum uft_fcntl_cmd {F_DUPFD, F_GETFD, F_SETFD, F_GETFL, F_SETFL,F_GETLK, F_SETLK, F_SETLKW,F_SETOWN, F_GETOWN, F_SEGSIG, F_GETSIG,F_GETLK64, F_SETLK64, F_SETLKW64,F_SETOWN_EX, F_GETOWN_EX,};" "enum uft_seek_whence { SEEK_SET, SEEK_CUR, SEEK_END, SEEK_DATA, SEEK_HOLE, };" "enum uft_falloc_mde {FALLOC_FL_KEEP_SIZE = 1,FALLOC_FL_PUNCH_HOLE = 2,FALLOC_FL_NO_HIDE_STALE = 4,FALLOC_FL_COLLAPSE_RANGE = 8,FALLOC_FL_ZERO_RANGE = 16,FALLOC_FL_INSERT_RANGE = 32,FALLOC_FL_UNSHARE_RANGE = 64,};" "enum uft_access_flag {F_OK = 0, X_OK = 1, W_OK = 2, R_OK = 4,};" "enum uft_dlopen_flag {RTLD_LOCAL = 0,RTLD_LAZY = 1,RTLD_NOW = 2,RTLD_NOLOAD = 4,RTLD_DEEPBIND = 8,RTLD_GLOBAL = 0x100,RTLD_NODELETE = 0x1000,};" "enum uft_socket_domain {AF_UNSPEC = 0, AF_UNIX, AF_INET, AF_AX25, AF_IPX, AF_APPLETALK, AF_NETROM, AF_BRIDGE,AF_ATMPVC = 8, AF_X25, AF_INET6, AF_ROSE, AF_DECnet, AF_NETBEUI, AF_SECURITY, AF_KEY,AF_NETLINK = 16, AF_PACKET, AF_ASH, AF_ECONET, AF_ATMSVC, AF_RDS, AF_SNA, AF_IRDA,AF_PPPOX = 24, AF_WANPIPE, AF_LLC, AF_IB, AF_MPLS, AF_CAN, AF_TPIC, AF_BLUETOOTH,AF_IUCV = 32, AF_RXRPC, AF_ISDN, AF_PHONET, AF_IEEE802154, AF_CAIF, AF_ALG, AF_NFC,AF_VSOCK = 40, AF_KCM, AF_QIPCRTR, AF_SMC,};" "enum uft_socket_type {SOCK_STREAM = 1, SOCK_DGRAM, SOCK_RAW, SOCK_RDM, SOCK_SEQPACKET, SOCK_DCCP,SOCK_PACKET = 10,SOCK_NONBLOCK = 04000, SOCK_CLOEXEC = 02000000,};" "enum uft_socket_flag {SOCK_NONBLOCK = 04000, SOCK_CLOEXEC = 02000000,};" "enum uft_signal {SIGNULL = 0, SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE,SIGKILL = 9, SIGUSR1, SIGSEGV, SIGUSR2, SIGPIPE, SIGALRM, SIGTERM, SIGSTKFLT,SIGCHLD = 17, SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, SIGURG, SIGXCPU,SIGXFSZ = 25, SIGVTALRM, SIGPROF, SIGWINCH, SIGPOLL, SIGPWR, SIGSYS,SIGRTMIN = 32, SIGRTMAX = 64,};" "enum uft_sigmask { SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK };" "enum uft_prctl_op {PR_SET_PDEATHSIG = 1, PR_GET_PDEATHSIG, PR_GET_DUMPABLE, PR_SET_DUMPABLE,PR_GET_UNALIGN = 5, PR_SET_UNALIGN, PR_GET_KEEPCAPS, PR_SET_KEEPCAPS,PR_GET_FPEMU = 9, PR_SET_FPEMU, PR_GET_FPEXC, PR_SET_FPEXC,PR_GET_TIMING = 13, PR_SET_TIMING, PR_SET_NAME, PR_GET_NAME,PR_GET_ENDIAN = 19, PR_SET_ENDIAN, PR_GET_SECCOMP, PR_SET_SECCOMP,PR_CAPBSET_READ = 23, PR_CAPBSET_DROP, PR_GET_TSC, PR_SET_TSC,PR_GET_SECUREBITS = 27, PR_SET_SECUREBITS, PR_SET_TIMERSLACK, PR_GET_TIMERSLACK,PR_TASK_PERF_EVENTS_DISABLE = 31, PR_TASK_PERF_EVENTS_ENABLE,PR_MCE_KILL = 33, PR_MCE_KILL_GET, PR_SET_MM,PR_SET_CHILD_SUBREAPER = 36, PR_GET_CHILD_SUBREAPER,PR_SET_NO_NEW_PRIVS = 38, PR_GET_NO_NEW_PRIVS, PR_GET_TID_ADDRESS,PR_SET_THP_DISABLE = 41, PR_GET_THP_DISABLE,PR_MPX_ENABLE_MANAGEMENT = 43, PR_MPX_DISABLE_MANAGEMENT,PR_SET_FP_MODE = 45, PR_GET_FP_MODE, PR_CAP_AMBIENT,};" "enum uft_epoll_op { EPOLL_CTL_ADD = 1, EPOLL_CTL_DEL, EPOLL_CTL_MOD };" "enum uft_locale {LC_TYPE = 0, LC_NUMERIC, LC_TIME, LC_COLLATE, LC_MONETARY, LC_MESSAGES,LC_ALL, LC_PAPER, LC_NAME, LC_ADDRESS, LC_TELEPHONE, LC_MEASUREMENT,LC_IDENTIFICATION,};" "enum uft_mode {mod_777 = 0777, mod_755 = 0755, mod_666 = 0666, mod_644 = 0644,mod_400 = 0400, mod_600 = 0600, mod_660 = 0660, mod_640 = 0640,mod_444 = 0444, mod_022 = 0022, mod_440 = 0440, mod_222 = 0222,mod_111 = 0111, mod_011 = 0011, mod_033 = 0033, mod_077 = 0077,};" "enum uft_clockid_t {CLOCK_REALTIME = 0,CLOCK_MONOTONIC,CLOCK_PROCESS_CPUTIME_ID,CLOCK_THREAD_CPUTIME_ID,CLOCK_MONOTONIC_RAW,CLOCK_REALTIME_COARSE,CLOCK_MONOTONIC_COARSE,CLOCK_BOOTTIME,CLOCK_REALTIME_ALARM,CLOCK_BOOTTIME_ALARM,CLOCK_TAI = 11,};" ; static char *auto_args_list = "_Znwm@arg1/u;" "_Znam@arg1/u;" "_ZdlPv@arg1/x;" "_ZdaPv@arg1/x;" "atoi@arg1/s;" "atol@arg1/s;" "atof@arg1/s;" "strtol@arg1/s,arg2/p,arg3/d32;" "strtoul@arg1/s,arg2/p,arg3/d32;" "strtod@arg1/s,arg2/p;" "strtof@arg1/s,arg2/p;" "qsort@arg1/p,arg2/u,arg3/u,arg4/p;" "qsort_r@arg1/p,arg2/u,arg3/u,arg4/p,arg5/p;" "bsearch@arg1/p,arg2/p,arg3/u,arg4/u,arg5/p;" "exit@arg1/d32;" "malloc@arg1/u;" "free@arg1/p;" "calloc@arg1/u,arg2/u;" "realloc@arg1/p,arg2/u;" "mmap@arg1/p,arg2/u,arg3/e:uft_mmap_prot,arg4/e:uft_mmap_flag,arg5/d32,arg6;" "mmap64@arg1/p,arg2/u,arg3/e:uft_mmap_prot,arg4/e:uft_mmap_flag,arg5/d32,arg6/d64;" "munmap@arg1/p,arg2/u;" "mprotect@arg1/p,arg2/u,arg3/e:uft_mmap_prot;" "madvise@arg1/p,arg2/u,arg3/e:uft_madvise;" "posix_madvise@arg1/p,arg2/u,arg3/e:uft_posix_madvise;" "posix_fadvise@arg1/d32,arg2,arg3,arg4/e:uft_posix_fadvise;" "brk@arg1/p;" "sbrk@arg1;" "memalign@arg1/u,arg2/u;" "pvalloc@arg1/u;" "posix_memalign@arg1/p,arg2/u,arg3/u;" "aligned_alloc@arg1/u,arg2/u;" "valloc@arg1/u;" "strcat@arg1/s,arg2/s;" "strncat@arg1/s,arg2/s,arg3/u;" "strcpy@arg1/p,arg2/s;" "strncpy@arg1/p,arg2/s,arg3/u;" "strlen@arg1/s;" "strnlen@arg1/s,arg2/u;" "strcmp@arg1/s,arg2/s;" "strncmp@arg1/s,arg2/s,arg3/u;" "strcasecmp@arg1/s,arg2/s;" "strncasecmp@arg1/s,arg2/s,arg3/u;" "strdup@arg1/s;" "strndup@arg1/s,arg2/u;" "strdupa@arg1/s;" "strndupa@arg1/s,arg2/u;" "strcoll@arg1/s,arg2/s;" "strstr@arg1/s,arg2/s;" "strcasestr@arg1/s,arg2/s;" "strchr@arg1/s,arg2/c;" "strrchr@arg1/s,arg2/c;" "strchrnul@arg1/s,arg2/c;" "strtok@arg1/s,arg2/s;" "strtok_r@arg1/s,arg2/s,arg3/p;" "strpbrk@arg1/s,arg2/s;" "strspn@arg1/s,arg2/s;" "strcspn@arg1/s,arg2/s;" "strsep@arg1/p,arg2/s;" "memcpy@arg1/p,arg2/p,arg3/u;" "memset@arg1/p,arg2/d32,arg3/u;" "memcmp@arg1/p,arg2/p,arg3/u;" "memmove@arg1/p,arg2/p,arg3/u;" "memchr@arg1/p,arg2/d32,arg3/u;" "memrchr@arg1/p,arg2/d32,arg3/u;" "rawmemchr@arg1/p,arg2/d32;" "printf@arg1/s;" "fprintf@arg1/p,arg2/s;" "dprintf@arg1/d32,arg2/s;" "sprintf@arg1/p,arg2/s;" "snprintf@arg1/p,arg2/u,arg3/s;" "fputc@arg1/c,arg2/p;" "fputs@arg1/s,arg2/p;" "putc@arg1/c,arg2/p;" "putchar@arg1/c;" "puts@arg1/s;" "fgetc@arg1/p;" "fgets@arg1/p,arg2/d32,arg3/p;" "getc@arg1/p;" "ungetc@arg1/c,arg2/p;" "getenv@arg1/s;" "setenv@arg1/s,arg2/s,arg3/d32;" "unsetenv@arg1/s;" "open@arg1/s,arg2/e:uft_open_flag;" "open64@arg1/s,arg2/e:uft_open_flag;" "openat@arg1/d32,arg2/s,arg3/e:uft_open_flag;" "open64at@arg1/d32,arg2/s,arg3/e:uft_open_flag;" "close@arg1/d32;" "fcntl@arg1/d32,arg2/e:uft_fcntl_cmd;" "fcntl64@arg1/d32,arg2/e:uft_fcntl_cmd;" "lseek@arg1/d32,arg2,arg3/e:uft_seek_whence;" "fallocate@arg1/d32,arg2/e:uft_falloc_mode,arg3,arg4;" "fsync@arg1/d32;" "fdatasync@arg1/d32;" "fopen@arg1/s,arg2/s;" "fopen64@arg1/s,arg2/s;" "fdopen@arg1/d32,arg2/s;" "freopen@arg1/s,arg2/s,arg3/p;" "fclose@arg1/p;" "fseek@arg1/p,arg2,arg3/d32;" "ftell@arg1/p;" "fflush@arg1/p;" "read@arg1/d32,arg2/p,arg3/u;" "write@arg1/d32,arg2/p,arg3/u;" "fread@arg1/p,arg2/u,arg3/u,arg4/p;" "fwrite@arg1/p,arg2/u,arg3/u,arg4/p;" "feof@arg1/p;" "ferror@arg1/p;" "fileno@arg1/p;" "access@arg1/s,arg2/e:uft_access_flag;" "unlink@arg1/s;" "unlinkat@arg1/d32,arg2/s,arg3/d32;" "mkdir@arg1/s;" "rmdir@arg1/s;" "chdir@arg1/s;" "opendir@arg1/s;" "closedir@arg1/p;" "getcwd@arg1/p,arg2/u;" "dirname@arg1/s;" "basename@arg1/s;" "execl@arg1/s,arg2/s;" "execlp@arg1/s,arg2/s;" "execle@arg1/s,arg2/s;" "execv@arg1/s;" "execvp@arg1/s;" "execve@arg1/s;" "execvpe@arg1/s;" "wait@arg1/p;" "waitpid@arg1/i32,arg2/p,arg3/d32;" "dlopen@arg1/s,arg2/e:uft_dlopen_flag;" "dlmopen@arg1,arg2/s,arg3/d32;" "dlsym@arg1/p,arg2/s;" "dlvsym@arg1/p,arg2/s,arg3/s;" "dlclose@arg1/p;" "pthread_create@arg1/p,arg2/p,arg3/p,arg4/p;" "pthread_once@arg1/p,arg2/p;" "pthread_join@arg1,arg2/p;" "pthread_detach@arg1;" "pthread_kill@arg1,arg2/d32;" "pthread_cancel@arg1;" "pthread_exit@arg1/p;" "pthread_mutex_lock@arg1/p;" "pthread_mutex_trylock@arg1/p;" "pthread_mutex_unlock@arg1/p;" "pthread_mutex_destroy@arg1/p;" "pthread_mutex_init@arg1/p,arg2/p;" "pthread_cond_wait@arg1/p,arg2/p;" "pthread_cond_timedwait@arg1/p,arg2/p,arg3/p;" "pthread_cond_signal@arg1/p;" "pthread_cond_broadcast@arg1/p;" "socket@arg1/e:uft_socket_domain,arg2/e:uft_socket_type,arg3/d32;" "connect@arg1/d32,arg2/p,arg3;" "bind@arg1/d32,arg2/p,arg3;" "accept@arg1/d32,arg2/p,arg3/p;" "accept4@arg1/d32,arg2/p,arg3/p,arg4/e:uft_socket_flag;" "gethostbyname@arg1/s;" "gethostbyaddr@arg1/p,arg2,arg3/e:uft_socket_domain;" "getaddrinfo@arg1/s,arg2/s,arg3/p,arg4/p;" "freeaddrinfo@arg1/p;" "inet_pton@arg1/e:uft_socket_domain,arg2/s,arg3/p;" "inet_ntop@arg1/e:uft_socket_domain,arg2/p,arg3/s,arg4;" "inet_aton@arg1/s,arg2/p;" "inet_ntoa@arg1;" "inet_addr@arg1/s;" "inet_network@arg1/s;" "kill@arg1/i32,arg2/e:uft_signal;" "raise@arg1/e:uft_signal;" "signal@arg1/e:uft_signal,arg2/p;" "sigaction@arg1/e:uft_signal,arg2/p,arg3/p;" "sigemptyset@arg1/p;" "sigfillset@arg1/p;" "sigaddset@arg1/p,arg2/e:uft_signal;" "sigdelset@arg1/p,arg2/e:uft_signal;" "sigismember@arg1/p,arg2/e:uft_signal;" "sigprocmask@arg1/e:uft_sigmask,arg2/p,arg3/p;" "pthread_sigmask@arg1/e:uft_sigmask,arg2/p,arg3/p;" "prctl@arg1/e:uft_prctl_op,arg2/u,arg3,arg4/u,arg5,arg6/u,arg7,arg8/u,arg9;" "select@arg1/d32,arg2/p,arg3/p,arg4/p,arg5/p;" "pselect@arg1/d32,arg2/p,arg3/p,arg4/p,arg5/p,arg6/p;" "poll@arg1/p,arg2,arg3/d32;" "ppoll@arg1/p,arg2,arg3/p,arg4/p;" "epoll_create@arg1/d32;" "epoll_create1@arg1/d32;" "epoll_ctl@arg1/d32,arg2/e:uft_epoll_op,arg3/d32,arg4/p;" "epoll_wait@arg1/d32,arg2/p,arg3/d32,arg4/d32;" "epoll_pwait@arg1/d32,arg2/p,arg3/d32,arg4/d32,arg5/p;" "syscall@arg1;" "ioctl@arg1/d32,arg2/u,arg3;" "textdomain@arg1/s;" "bindtextdomain@arg1/s,arg2/s;" "gettext@arg1/s;" "dgettext@arg1/s,arg2/s;" "dcgettext@arg1/s,arg2/s,arg3/d32;" "setlocale@arg1/e:uft_locale,arg2/s;" "getopt@arg1/d32,arg2/p,arg3/s;" "getopt_long@arg1/d32,arg2/p,arg3/s;" "getopt_long_only@arg1/d32,arg2/p,arg3/s;" "stat@arg1/s,arg2/p;" "fstat@arg1/d32,arg2/p;" "lstat@arg1/s,arg2/p;" "chmod@arg1/s,arg2/e:uft_mode;" "fchmod@arg1/d32,arg2/e:uft_mode;" "umask@arg1/e:uft_mode;" "creat@arg1/s,arg2/e:uft_mode;" "creat64@arg1/s,arg2/e:uft_mode;" "isatty@arg1/d32;" "setuid@arg1/i32;" "setgid@arg1/i32;" "seteuid@arg1/i32;" "setegid@arg1/i32;" "setreuid@arg1/i32,arg2/i32;" "setregid@arg1/i32,arg2/i32;" "setresuid@arg1/i32,arg2/i32,arg3/i32;" "setresgid@arg1/i32,arg2/i32,arg3/i32;" "chown@arg1/s,arg2/i32,arg3/i32;" "lchown@arg1/s,arg2/i32,arg3/i32;" "fchown@arg1/d32,arg2/i32,arg3/i32;" "dup@arg1/d32;" "dup2@arg1/d32,arg2/d32;" "sleep@arg1/u;" "usleep@arg1/u;" "clock_getres@arg1/e:uft_clockid_t,arg2/p;" "clock_gettime@arg1/e:uft_clockid_t,arg2/p;" "clock_settime@arg1/e:uft_clockid_t,arg2/p;" ; static char *auto_retvals_list = "_Znwm@retval/x;" "_Znam@retval/x;" "atoi@retval/d32;" "atol@retval;" "atof@retval/f64;" "strtol@retval;" "strtoul@retval/u;" "strtod@retval/f64;" "strtof@retval/f32;" "bsearch@retval/p;" "malloc@retval/p;" "calloc@retval/p;" "realloc@retval/p;" "mmap@retval/p;" "mmap64@retval/p;" "munmap@retval/d32;" "mprotect@retval/d32;" "madvise@retval/d32;" "posix_madvise@retval/d32;" "posix_fadvise@retval/d32;" "brk@retval/d32;" "sbrk@retval/p;" "memalign@retval/p;" "pvalloc@retval/p;" "posix_memalign@retval/d32;" "aligned_alloc@retval/p;" "valloc@retval/p;" "strcat@retval/s;" "strncat@retval/s;" "strlen@retval/u;" "strnlen@retval/u;" "strcmp@retval/d32;" "strncmp@retval/d32;" "strcasecmp@retval/d32;" "strncasecmp@retval/d32;" "strdup@retval/s;" "strndup@retval/s;" "strdupa@retval/s;" "strndupa@retval/s;" "strcoll@retval/d32;" "strstr@retval/s;" "strcasestr@retval/s;" "strchr@retval/s;" "strrchr@retval/s;" "strchrnul@retval/s;" "strtok@retval/s;" "strtok_r@retval/s;" "strpbrk@retval/s;" "strspn@retval/u;" "strcspn@retval/u;" "strsep@retval/s;" "memcmp@retval/d32;" "memchr@retval/p;" "memrchr@retval/p;" "rawmemchr@retval/p;" "printf@retval/d32;" "fprintf@retval/d32;" "dprintf@retval/d32;" "sprintf@retval/d32;" "snprintf@retval/d32;" "fputc@retval/d32;" "fputs@retval/d32;" "putc@retval/d32;" "putchar@retval/d32;" "puts@retval/d32;" "fgetc@retval/c;" "fgets@retval/s;" "getc@retval/c;" "getchar@retval/c;" "ungetc@retval/c;" "getenv@retval/s;" "setenv@retval/d32;" "unsetenv@retval/d32;" "open@retval/d32;" "open64@retval/d32;" "openat@retval/d32;" "open64at@retval/d32;" "close@retval/d32;" "fcntl@retval/d32;" "fcntl64@retval/d32;" "lseek@retval;" "fallocate@retval/d32;" "fsync@retval/d32;" "fdatasync@retval/d32;" "fopen@retval/p;" "fopen64@retval/p;" "fdopen@retval/p;" "freopen@retval/p;" "fclose@retval/d32;" "fseek@retval/d32;" "ftell@retval;" "fflush@retval/d32;" "read@retval;" "write@retval;" "fread@retval/u;" "fwrite@retval/u;" "feof@retval/d32;" "ferror@retval/d32;" "fileno@retval/d32;" "access@retval/d32;" "unlink@retval/d32;" "unlinkat@retval/d32;" "mkdir@retval/d32;" "rmdir@retval/d32;" "chdir@retval/d32;" "opendir@retval/p;" "closedir@retval/d32;" "getcwd@retval/s;" "dirname@retval/s;" "basename@retval/s;" "fork@retval/i32;" "vfork@retval/i32;" "execl@retval/d32;" "execlp@retval/d32;" "execle@retval/d32;" "execv@retval/d32;" "execvp@retval/d32;" "execve@retval/d32;" "execvpe@retval/d32;" "wait@retval/i32;" "waitpid@retval/i32;" "getpid@retval/i32;" "getppid@retval/i32;" "gettid@retval/i32;" "dlopen@retval/p;" "dlmopen@retval/p;" "dlsym@retval/p;" "dlvsym@retval/p;" "dlclose@retval/d32;" "dlerror@retval/s;" "pthread_create@retval/d32;" "pthread_once@retval/d32;" "pthread_join@retval/d32;" "pthread_detach@retval/d32;" "pthread_kill@retval/d32;" "pthread_cancel@retval/d32;" "pthread_mutex_lock@retval/d32;" "pthread_mutex_trylock@retval/d32;" "pthread_mutex_unlock@retval/d32;" "pthread_mutex_destroy@retval/d32;" "pthread_mutex_init@retval/d32;" "pthread_cond_wait@retval/d32;" "pthread_cond_timedwait@retval/d32;" "pthread_cond_signal@retval/d32;" "pthread_cond_broadcast@retval/d32;" "socket@retval/d32;" "connect@retval/d32;" "bind@retval/d32;" "accept@retval/d32;" "accept4@retval/d32;" "gethostbyname@retval/p;" "gethostbyaddr@retval/p;" "getaddrinfo@retval/d32;" "inet_pton@retval/d32;" "inet_ntop@retval/s;" "inet_aton@retval/d32;" "inet_ntoa@retval/s;" "inet_addr@retval;" "inet_network@retval;" "kill@retval/d32;" "raise@retval/d32;" "signal@retval;" "sigaction@retval/d32;" "sigemptyset@retval/d32;" "sigfillset@retval/d32;" "sigaddset@retval/d32;" "sigdelset@retval/d32;" "sigismember@retval/d32;" "sigprocmask@retval/d32;" "pthread_sigmask@retval/d32;" "prctl@retval/d32;" "select@retval/d32;" "pselect@retval/d32;" "poll@retval/d32;" "ppoll@retval/d32;" "epoll_create@retval/d32;" "epoll_create1@retval/d32;" "epoll_ctl@retval/d32;" "epoll_wait@retval/d32;" "epoll_pwait@retval/d32;" "syscall@retval;" "ioctl@retval/d32;" "gettext@retval/s;" "dgettext@retval/s;" "dcgettext@retval/s;" "setlocale@retval/s;" "getopt@retval/d32;" "getopt_long@retval/d32;" "getopt_long_only@retval/d32;" "stat@retval/d32;" "fstat@retval/d32;" "lstat@retval/d32;" "chmod@retval/d32;" "fchmod@retval/d32;" "creat@retval/d32;" "creat64@retval/d32;" "isatty@retval/d32;" "getuid@retval/i32;" "getgid@retval/i32;" "geteuid@retval/i32;" "getegid@retval/i32;" "setuid@retval/d32;" "setgid@retval/d32;" "seteuid@retval/d32;" "setegid@retval/d32;" "setreuid@retval/d32;" "setregid@retval/d32;" "setresuid@retval/d32;" "setresgid@retval/d32;" "chown@retval/d32;" "lchown@retval/d32;" "fchown@retval/d32;" "dup@retval/d32;" "dup2@retval/d32;" "sleep@retval/u;" "usleep@retval/d32;" "clock_getres@retval/d32;" "clock_gettime@retval/d32;" "clock_settime@retval/d32;" ; /* clang-format on */ uftrace-0.15.2/utils/compiler.h000066400000000000000000000056071455365734300164020ustar00rootroot00000000000000#ifndef UFTRACE_COMPILER_H #define UFTRACE_COMPILER_H #define compiler_barrier() asm volatile("" ::: "memory") #if defined(__i386__) #define cpu_relax() asm volatile("rep; nop" ::: "memory") #define full_memory_barrier() asm volatile("mfence" ::: "memory") #define read_memory_barrier() asm volatile("lfence" ::: "memory") #define write_memory_barrier() asm volatile("sfence" ::: "memory") #endif #if defined(__x86_64__) #define cpu_relax() asm volatile("rep; nop" ::: "memory") #define full_memory_barrier() asm volatile("mfence" ::: "memory") #define read_memory_barrier() asm volatile("lfence" ::: "memory") #define write_memory_barrier() asm volatile("sfence" ::: "memory") #endif #if defined(__aarch64__) #define cpu_relax() asm volatile("yield" ::: "memory") #define full_memory_barrier() asm volatile("dmb ish" ::: "memory") #define read_memory_barrier() asm volatile("dmb ishld" ::: "memory") #define write_memory_barrier() asm volatile("dmb ishst" ::: "memory") #endif #if defined(__arm__) #define cpu_relax() compiler_barrier() #if __ARM_ARCH == 7 #define full_memory_barrier() asm volatile("dmb ish" ::: "memory") #define read_memory_barrier() asm volatile("dmb ish" ::: "memory") #define write_memory_barrier() asm volatile("dmb ishst" ::: "memory") #else #define full_memory_barrier() asm volatile("mcr p15, 0, %0, c7, c10, 5" ::"r"(0) : "memory") #define read_memory_barrier() full_memory_barrier() #define write_memory_barrier() full_memory_barrier() #endif #endif /* TODO: not implemented yet (Start) * * From RISC-V's "The RISC-V Instruction Set Manual, Volume I: User-Level * ISA, Document Version 20191213", the G Extension consists of "I, M, A, * F, D, Zicsr, Zifencei". * * In RISC-V, two instructions exist for memory barriers: the fence * instruction, defined in the I Extension, and the fence.i instruction, * defined in the Zifencei Extension. * * So the memory barrier commands we can use are fence, fence.i, and * we'll have to figure out which one we need later when we implement * the functions that call those macro functions. * */ #if defined(__riscv) #define cpu_relax() asm volatile("nop" ::: "memory") #define full_memory_barrier() asm volatile("nop" ::: "memory") #define read_memory_barrier() asm volatile("nop" ::: "memory") #define write_memory_barrier() asm volatile("nop" ::: "memory") #endif /* TODO: not implemented yet (End) */ /* ignore 'restrict' keyword if not supported (before C99) */ #if !defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L #define restrict #endif #define __weak __attribute__((weak)) #define __visible_default __attribute__((visibility("default"))) #define __alias(func) __attribute__((alias(#func))) #define __maybe_unused __attribute__((unused)) #ifndef __used #define __used __attribute__((used)) #endif #ifndef __noreturn #define __noreturn __attribute__((noreturn)) #endif #define __align(n) __attribute__((aligned(n))) #endif /* UFTRACE_COMPILER_H */ uftrace-0.15.2/utils/data-file.c000066400000000000000000000463771455365734300164220ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "libmcount/mcount.h" #include "uftrace.h" #include "utils/event.h" #include "utils/filter.h" #include "utils/fstack.h" #include "utils/kernel.h" #include "utils/perf.h" #include "utils/symbol.h" #include "utils/utils.h" /** * read_task_file - read 'task' file from data directory * @sess: session link to manage sessions and tasks * @dirname: name of the data directory * @needs_symtab: read session symbol tables too * @sym_rel_addr: whether symbol address is relative * @needs_srcline: whether debug info loading is needed * * This function read the task file in the @dirname and build task * (and session when @needs_session is %true) information. Note that * this functions is for backward compatibility. Recent data * directory contains 'task.txt' file instead. * * It returns 0 for success, -1 for error. */ int read_task_file(struct uftrace_session_link *sess, char *dirname, bool needs_symtab, bool sym_rel_addr, bool needs_srcline) { int fd; char pad[8]; char buf[1024]; struct uftrace_msg msg; struct uftrace_msg_task tmsg; struct uftrace_msg_sess smsg; int ret = -1; snprintf(buf, sizeof(buf), "%s/task", dirname); fd = open(buf, O_RDONLY); if (fd < 0) return -1; pr_dbg("reading task file\n"); while (read_all(fd, &msg, sizeof(msg)) == 0) { if (msg.magic != UFTRACE_MSG_MAGIC) goto out; switch (msg.type) { case UFTRACE_MSG_SESSION: if (read_all(fd, &smsg, sizeof(smsg)) < 0) goto out; if (read_all(fd, buf, smsg.namelen) < 0) goto out; if (smsg.namelen % 8 && read_all(fd, pad, 8 - (smsg.namelen % 8)) < 0) goto out; create_session(sess, &smsg, dirname, dirname, buf, sym_rel_addr, needs_symtab, needs_srcline); break; case UFTRACE_MSG_TASK_START: if (read_all(fd, &tmsg, sizeof(tmsg)) < 0) goto out; create_task(sess, &tmsg, false); break; case UFTRACE_MSG_FORK_END: if (read_all(fd, &tmsg, sizeof(tmsg)) < 0) goto out; create_task(sess, &tmsg, true); break; default: pr_dbg("invalid contents in task file\n"); goto out; } } ret = 0; out: close(fd); return ret; } /** * read_task_txt_file - read 'task.txt' file from data directory * @sess: session link to manage sessions and tasks * @dirname: name of the data directory * @needs_symtab: read session symbol tables too * @sym_rel_addr: whethere symbol address is relative * @needs_srcline: whether debug info loading is needed * * This function read the task.txt file in the @dirname and build task * (and session when @needs_session is %true) information. * * It returns 0 for success, -1 for error. */ int read_task_txt_file(struct uftrace_session_link *sess, char *dirname, char *symdir, bool needs_symtab, bool sym_rel_addr, bool needs_srcline) { FILE *fp; char *fname = NULL; char *line = NULL; size_t sz = 0; unsigned long sec, nsec; struct uftrace_msg_task tmsg; struct uftrace_msg_sess smsg; struct uftrace_msg_dlopen dlop; char *exename, *pos; int ret = -1; int num; xasprintf(&fname, "%s/%s", dirname, "task.txt"); fp = fopen(fname, "r"); if (fp == NULL) { free(fname); return -errno; } pr_dbg("reading %s file\n", fname); while (getline(&line, &sz, fp) >= 0) { if (!strncmp(line, "TASK", 4)) { num = sscanf(line + 5, "timestamp=%lu.%lu tid=%d pid=%d", &sec, &nsec, &tmsg.tid, &tmsg.pid); if (num != 4) goto out; tmsg.time = (uint64_t)sec * NSEC_PER_SEC + nsec; create_task(sess, &tmsg, false); } else if (!strncmp(line, "FORK", 4)) { num = sscanf(line + 5, "timestamp=%lu.%lu pid=%d ppid=%d", &sec, &nsec, &tmsg.tid, &tmsg.pid); if (num != 4) goto out; tmsg.time = (uint64_t)sec * NSEC_PER_SEC + nsec; create_task(sess, &tmsg, true); } else if (!strncmp(line, "SESS", 4)) { num = sscanf(line + 5, "timestamp=%lu.%lu %*[^i]id=%d sid=%s", &sec, &nsec, &smsg.task.pid, (char *)&smsg.sid); if (num != 4) goto out; // Get the execname pos = strstr(line, "exename="); if (pos == NULL) goto out; exename = pos + 8 + 1; // skip double-quote pos = strrchr(exename, '\"'); if (pos) *pos = '\0'; smsg.task.tid = smsg.task.pid; smsg.task.time = (uint64_t)sec * NSEC_PER_SEC + nsec; smsg.namelen = strlen(exename); create_session(sess, &smsg, dirname, symdir, exename, sym_rel_addr, needs_symtab, needs_srcline); } else if (!strncmp(line, "DLOP", 4)) { struct uftrace_session *s; if (!needs_symtab) continue; num = sscanf(line + 5, "timestamp=%lu.%lu tid=%d sid=%s base=%" PRIx64, &sec, &nsec, &dlop.task.tid, (char *)&dlop.sid, &dlop.base_addr); if (num != 5) goto out; pos = strstr(line, "libname="); if (pos == NULL) goto out; exename = pos + 8 + 1; // skip double-quote pos = strrchr(exename, '\"'); if (pos) *pos = '\0'; dlop.task.pid = dlop.task.tid; dlop.task.time = (uint64_t)sec * NSEC_PER_SEC + nsec; dlop.namelen = strlen(exename); s = get_session_from_sid(sess, dlop.sid); ASSERT(s); session_add_dlopen(s, dlop.task.time, dlop.base_addr, exename); } } ret = 0; out: free(line); fclose(fp); free(fname); if (ret != 0) errno = EINVAL; return ret; } static void snprint_timestamp(char *buf, size_t sz, uint64_t timestamp) { snprintf(buf, sz, "%" PRIu64 ".%09" PRIu64, // sec.nsec timestamp / NSEC_PER_SEC, timestamp % NSEC_PER_SEC); } void write_task_info(const char *dirname, struct uftrace_msg_task *tmsg) { FILE *fp; char *fname = NULL; char ts[128]; xasprintf(&fname, "%s/%s", dirname, "task.txt"); fp = fopen(fname, "a"); if (fp == NULL) pr_err("cannot open %s", fname); snprint_timestamp(ts, sizeof(ts), tmsg->time); fprintf(fp, "TASK timestamp=%s tid=%d pid=%d\n", ts, tmsg->tid, tmsg->pid); fclose(fp); free(fname); } void write_fork_info(const char *dirname, struct uftrace_msg_task *tmsg) { FILE *fp; char *fname = NULL; char ts[128]; xasprintf(&fname, "%s/%s", dirname, "task.txt"); fp = fopen(fname, "a"); if (fp == NULL) pr_err("cannot open %s", fname); snprint_timestamp(ts, sizeof(ts), tmsg->time); fprintf(fp, "FORK timestamp=%s pid=%d ppid=%d\n", ts, tmsg->tid, tmsg->pid); fclose(fp); free(fname); } void write_session_info(const char *dirname, struct uftrace_msg_sess *smsg, const char *exename) { FILE *fp; char *fname = NULL; char ts[128]; xasprintf(&fname, "%s/%s", dirname, "task.txt"); fp = fopen(fname, "a"); if (fp == NULL) pr_err("cannot open %s", fname); snprint_timestamp(ts, sizeof(ts), smsg->task.time); fprintf(fp, "SESS timestamp=%s pid=%d sid=%.*s exename=\"%s\"\n", ts, smsg->task.pid, SESSION_ID_LEN, smsg->sid, exename); fclose(fp); free(fname); } void write_dlopen_info(const char *dirname, struct uftrace_msg_dlopen *dmsg, const char *libname) { FILE *fp; char *fname = NULL; char ts[128]; xasprintf(&fname, "%s/%s", dirname, "task.txt"); fp = fopen(fname, "a"); if (fp == NULL) pr_err("cannot open %s", fname); snprint_timestamp(ts, sizeof(ts), dmsg->task.time); fprintf(fp, "DLOP timestamp=%s tid=%d sid=%.*s base=%" PRIx64 " libname=\"%s\"\n", ts, dmsg->task.tid, SESSION_ID_LEN, dmsg->sid, dmsg->base_addr, libname); fclose(fp); free(fname); } static void check_data_order(struct uftrace_data *handle) { union { struct uftrace_record s; uint64_t d[2]; } data; handle->needs_byte_swap = (get_elf_endian() != handle->hdr.endian); if (handle->needs_byte_swap) pr_dbg("byte order is different!\n"); /* the s.magic should be in bit[3:5] in the second word */ data.d[1] = RECORD_MAGIC << 3; handle->needs_bit_swap = (data.s.magic != RECORD_MAGIC); if (handle->needs_bit_swap) pr_dbg("bitfield order is different!\n"); } static bool check_data_file(struct uftrace_data *handle, const char *pattern) { glob_t g; size_t i; bool found = false; if (glob(pattern, 0, NULL, &g) == GLOB_ERR) { pr_dbg("glob matching failed: %s: %m\n", pattern); return false; } for (i = 0; i < g.gl_pathc; i++) { struct stat stbuf; if (stat(g.gl_pathv[i], &stbuf) == 0 && stbuf.st_size) { found = true; break; } } globfree(&g); return found; } bool data_is_lp64(struct uftrace_data *handle) { return handle->hdr.elf_class == ELFCLASS64; } int open_info_file(struct uftrace_opts *opts, struct uftrace_data *handle) { FILE *fp; char buf[PATH_MAX]; int saved_errno = 0; struct stat stbuf; memset(handle, 0, sizeof(*handle)); snprintf(buf, sizeof(buf), "%s/info", opts->dirname); fp = fopen(buf, "rb"); if (fp != NULL) goto ok; saved_errno = errno; /* provide a better error code for empty/invalid directories */ if (stat(opts->dirname, &stbuf) == 0) saved_errno = EINVAL; /* if default dirname is failed */ if (!strcmp(opts->dirname, UFTRACE_DIR_NAME)) { /* try again inside the current directory */ fp = fopen("./info", "rb"); if (fp != NULL) { opts->dirname = "./"; goto ok; } /* retry with old default dirname */ snprintf(buf, sizeof(buf), "%s/info", UFTRACE_DIR_OLD_NAME); fp = fopen(buf, "rb"); if (fp != NULL) { opts->dirname = UFTRACE_DIR_OLD_NAME; goto ok; } saved_errno = errno; /* restore original file name for error reporting */ snprintf(buf, sizeof(buf), "%s/info", opts->dirname); } /* data file loading is failed */ pr_dbg("cannot open %s file\n", buf); return -saved_errno; ok: saved_errno = 0; handle->fp = fp; handle->dirname = opts->dirname; handle->depth = opts->depth; handle->time_filter = opts->threshold; handle->size_filter = opts->size_filter; handle->time_range = opts->range; handle->sessions.root = RB_ROOT; handle->sessions.tasks = RB_ROOT; handle->last_perf_idx = -1; INIT_LIST_HEAD(&handle->events); if (fread(&handle->hdr, sizeof(handle->hdr), 1, fp) != 1) pr_err("cannot read header data"); if (memcmp(handle->hdr.magic, UFTRACE_MAGIC_STR, UFTRACE_MAGIC_LEN)) pr_err_ns("invalid magic string found!\n"); check_data_order(handle); if (handle->needs_byte_swap) { handle->hdr.version = bswap_32(handle->hdr.version); handle->hdr.feat_mask = bswap_64(handle->hdr.feat_mask); handle->hdr.info_mask = bswap_64(handle->hdr.info_mask); handle->hdr.max_stack = bswap_16(handle->hdr.max_stack); } if (handle->hdr.version < UFTRACE_FILE_VERSION_MIN || handle->hdr.version > UFTRACE_FILE_VERSION) pr_err_ns("unsupported file version: %u\n", handle->hdr.version); if (read_uftrace_info(handle->hdr.info_mask, handle) < 0) pr_err_ns("cannot read uftrace header info!\n"); if (opts->exename == NULL) opts->exename = handle->info.exename; fclose(fp); return 0; } int open_data_file(struct uftrace_opts *opts, struct uftrace_data *handle) { int ret; char buf[PATH_MAX]; int saved_errno = 0; ret = open_info_file(opts, handle); if (ret < 0) { errno = -ret; return -1; } if (handle->info.nr_tid == 0) { errno = ENODATA; return -1; } if (handle->hdr.feat_mask & TASK_SESSION) { bool sym_rel = false; struct uftrace_session_link *sessions = &handle->sessions; int i; if (handle->hdr.feat_mask & SYM_REL_ADDR) sym_rel = true; /* read old task file first and then try task.txt file */ if (read_task_file(sessions, opts->dirname, true, sym_rel, opts->srcline) < 0 && read_task_txt_file(sessions, opts->dirname, opts->with_syms ?: opts->dirname, true, sym_rel, opts->srcline | (opts->loc_filter != NULL)) < 0) { if (errno == ENOENT) saved_errno = ENODATA; else saved_errno = errno; goto out; } if (sessions->first == NULL) { saved_errno = EINVAL; goto out; } for (i = 0; i < handle->info.nr_tid; i++) { int tid = handle->info.tids[i]; if (find_task(sessions, tid)) break; } if (i == handle->info.nr_tid) { saved_errno = ENODATA; goto out; } } if (handle->hdr.info_mask & ARG_SPEC) { struct uftrace_filter_setting setting = { .ptype = handle->info.patt_type, .allow_kernel = true, .auto_args = false, .lp64 = data_is_lp64(handle), .arch = handle->arch, }; if (handle->hdr.feat_mask & AUTO_ARGS) { setup_auto_args_str(handle->info.autoarg, handle->info.autoret, handle->info.autoenum, &setting); } setup_fstack_args(handle->info.argspec, handle->info.retspec, handle, &setting); if (handle->info.auto_args_enabled) { char *autoarg = handle->info.autoarg; char *autoret = handle->info.autoret; if (handle->hdr.feat_mask & DEBUG_INFO) { if (handle->info.patt_type == PATT_REGEX) autoarg = autoret = "."; else /* PATT_GLOB */ autoarg = autoret = "*"; } setting.auto_args = true; setup_fstack_args(autoarg, autoret, handle, &setting); } } if (!(handle->hdr.feat_mask & MAX_STACK)) handle->hdr.max_stack = MCOUNT_RSTACK_MAX; if (handle->hdr.feat_mask & KERNEL) { struct uftrace_kernel_reader *kernel; kernel = xzalloc(sizeof(*kernel)); kernel->handle = handle; kernel->dirname = opts->dirname; kernel->skip_out = opts->kernel_skip_out; if (setup_kernel_data(kernel) == 0) { handle->kernel = kernel; load_kernel_symbol(opts->dirname); } else { free(kernel); handle->kernel = NULL; } } if (handle->hdr.feat_mask & EVENT) read_events_file(handle); if (handle->hdr.feat_mask & PERF_EVENT) setup_perf_data(handle); setup_extern_data(handle, opts); /* check there are data files actually */ snprintf(buf, sizeof(buf), "%s/[0-9]*.dat", opts->dirname); if (!check_data_file(handle, buf)) { if (handle->kernel) { snprintf(buf, sizeof(buf), "%s/kernel-*.dat", opts->dirname); if (check_data_file(handle, buf)) goto out; } if (saved_errno == 0) saved_errno = ENODATA; } out: if (saved_errno) { close_data_file(opts, handle); errno = saved_errno; ret = -1; } else ret = 0; return ret; } void __close_data_file(struct uftrace_opts *opts, struct uftrace_data *handle, bool unload_modules) { if (opts->exename == handle->info.exename) opts->exename = NULL; if (has_kernel_data(handle->kernel)) { finish_kernel_data(handle->kernel); free(handle->kernel); } finish_events_file(handle); if (has_perf_data(handle)) finish_perf_data(handle); if (has_extern_data(handle)) finish_extern_data(handle); delete_sessions(&handle->sessions); if (unload_modules) unload_module_symtabs(); if (handle->hdr.feat_mask & AUTO_ARGS) finish_auto_args(); clear_uftrace_info(&handle->info); reset_task_handle(handle); } #ifdef UNIT_TEST #define TEST_SESSION_ID "test-session-id" #define TEST_EXIT_STATUS 37 #define TEST_INFO_MASK (EXE_NAME | EXIT_STATUS | TASKINFO) static int create_test_data(struct uftrace_opts *opts) { struct uftrace_msg_sess sess_msg = { .task = { .time = 100, .pid = 10 }, .sid = TEST_SESSION_ID, }; struct uftrace_msg_task task_msg = { .time = 200, .pid = 10, .tid = 10, }; struct uftrace_msg_task fork_msg = { .time = 300, .pid = 10, .tid = 20, }; struct uftrace_msg_dlopen dlopen_msg = { .task = { .time = 400, .tid = 20 }, .sid = TEST_SESSION_ID, .base_addr = 0x123000, }; struct uftrace_record parent_funcs[] = { /* ignore symbols for now */ { .time = 201, .type = UFTRACE_ENTRY, .magic = RECORD_MAGIC, .depth = 0, }, { .time = 202, .type = UFTRACE_ENTRY, .magic = RECORD_MAGIC, .depth = 1, }, { .time = 203, .type = UFTRACE_ENTRY, .magic = RECORD_MAGIC, .depth = 2, }, { .time = 204, .type = UFTRACE_ENTRY, .magic = RECORD_MAGIC, .depth = 3, }, { .time = 205, .type = UFTRACE_EXIT, .magic = RECORD_MAGIC, .depth = 3, }, { .time = 206, .type = UFTRACE_EXIT, .magic = RECORD_MAGIC, .depth = 2, }, { .time = 207, .type = UFTRACE_EXIT, .magic = RECORD_MAGIC, .depth = 1, }, { .time = 208, .type = UFTRACE_EXIT, .magic = RECORD_MAGIC, .depth = 0, }, }; struct uftrace_record child_funcs[] = { { .time = 301, .type = UFTRACE_ENTRY, .magic = RECORD_MAGIC, .depth = 0, }, { .time = 302, .type = UFTRACE_ENTRY, .magic = RECORD_MAGIC, .depth = 1, }, { .time = 303, .type = UFTRACE_EXIT, .magic = RECORD_MAGIC, .depth = 1, }, { .time = 304, .type = UFTRACE_EXIT, .magic = RECORD_MAGIC, .depth = 0, }, }; char *filename = NULL; FILE *fp; if (create_directory(opts->dirname) < 0) return -1; write_session_info(opts->dirname, &sess_msg, opts->exename); write_task_info(opts->dirname, &task_msg); write_fork_info(opts->dirname, &fork_msg); write_dlopen_info(opts->dirname, &dlopen_msg, "libnothing.so"); /* create_session() requires a map file even if it's empty */ TEST_GE(asprintf(&filename, "%s/sid-%.*s.map", opts->dirname, SESSION_ID_LEN, TEST_SESSION_ID), 0); creat(filename, 0644); free(filename); TEST_GE(asprintf(&filename, "%s/%d.dat", opts->dirname, task_msg.pid), 0); fp = fopen(filename, "w"); fwrite(parent_funcs, sizeof(*parent_funcs), ARRAY_SIZE(parent_funcs), fp); fclose(fp); free(filename); TEST_GE(asprintf(&filename, "%s/%d.dat", opts->dirname, fork_msg.tid), 0); fp = fopen(filename, "w"); fwrite(child_funcs, sizeof(*child_funcs), ARRAY_SIZE(child_funcs), fp); fclose(fp); free(filename); fill_file_header(opts, TEST_EXIT_STATUS, NULL, (char *)"1ms"); return 0; } static int remove_test_data(struct uftrace_opts *opts) { return remove_directory(opts->dirname); } int prepare_test_data(struct uftrace_opts *opts, struct uftrace_data *handle) { FILE *dev_null; if (create_test_data(opts) < 0) return -1; dev_null = fopen("/dev/null", "w+"); if (dev_null == NULL) { remove_test_data(opts); return -1; } /* prevent any output message during the test */ outfp = dev_null; open_data_file(opts, handle); fstack_setup_filters(opts, handle); if (handle->sessions.first == NULL) { close_data_file(opts, handle); remove_test_data(opts); return -1; } handle->sessions.first->sym_info.kernel_base = -1ULL; return 0; } int release_test_data(struct uftrace_opts *opts, struct uftrace_data *handle) { FILE *dev_null = outfp; close_data_file(opts, handle); outfp = stdout; fclose(dev_null); if (remove_test_data(opts) < 0) { pr_dbg("failed to remove data\n"); return -1; } return 0; } TEST_CASE(data_basic) { struct uftrace_opts opts = { .dirname = "data-test", .exename = read_exename(), .max_stack = 10, }; struct uftrace_data handle; struct uftrace_session *s; struct uftrace_task *t; pr_dbg("create test data\n"); if (create_test_data(&opts) < 0) return TEST_SKIP; open_data_file(&opts, &handle); pr_dbg("verify header info\n"); TEST_NE(handle.hdr.feat_mask & TASK_SESSION, 0); TEST_EQ(handle.hdr.info_mask & TEST_INFO_MASK, TEST_INFO_MASK); TEST_EQ(handle.info.exit_status, TEST_EXIT_STATUS); TEST_STREQ(handle.info.exename, opts.exename); pr_dbg("verify session info\n"); TEST_NE(handle.sessions.first, NULL); s = handle.sessions.first; TEST_EQ(s->start_time, 100); TEST_EQ(s->pid, 10); TEST_STREQ(s->sid, TEST_SESSION_ID); TEST_STREQ(s->exename, opts.exename); TEST_EQ(list_empty(&s->dlopen_libs), false); pr_dbg("verify task info\n"); TEST_NE(handle.sessions.first_task, NULL); t = handle.sessions.first_task; TEST_EQ(t->tid, 10); TEST_EQ(t->tid, t->pid); TEST_STREQ(t->comm, basename(opts.exename)); close_data_file(&opts, &handle); pr_dbg("delete test data\n"); if (remove_test_data(&opts) < 0) pr_dbg("failed to remove data\n"); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/debug.c000066400000000000000000000237571455365734300156570ustar00rootroot00000000000000/* * debug routines for uftrace * * Copyright (C) 2014-2017, LG Electronics, Namhyung Kim * * Released under the GPL v2. */ #include #include #include #include #include #include "utils/utils.h" #define TERM_COLOR_NORMAL "" #define TERM_COLOR_RESET "\033[0m" #define TERM_COLOR_BOLD "\033[1m" #define TERM_COLOR_RED "\033[91m" /* bright red */ #define TERM_COLOR_GREEN "\033[32m" #define TERM_COLOR_YELLOW "\033[33m" #define TERM_COLOR_BLUE "\033[94m" /* bright blue */ #define TERM_COLOR_MAGENTA "\033[35m" #define TERM_COLOR_CYAN "\033[36m" #define TERM_COLOR_GRAY "\033[90m" /* bright black */ #define HTML_COLOR_NORMAL "" #define HTML_COLOR_RESET "" #define HTML_COLOR_BOLD "" #define HTML_COLOR_RED "" /* bright red */ #define HTML_COLOR_GREEN "" #define HTML_COLOR_YELLOW "" #define HTML_COLOR_BLUE "" /* bright blue */ #define HTML_COLOR_MAGENTA "" #define HTML_COLOR_CYAN "" #define HTML_COLOR_GRAY "" /* bright black */ int debug; FILE *logfp; FILE *outfp; enum color_setting log_color; enum color_setting out_color; enum format_mode format_mode; int dbg_domain[DBG_DOMAIN_MAX]; /* colored output for argspec display */ const char *color_reset = TERM_COLOR_RESET; const char *color_bold = TERM_COLOR_BOLD; const char *color_string = TERM_COLOR_MAGENTA; const char *color_symbol = TERM_COLOR_CYAN; const char *color_struct = TERM_COLOR_CYAN; const char *color_enum = TERM_COLOR_BLUE; const char *color_enum_or = TERM_COLOR_RESET TERM_COLOR_BOLD "|" TERM_COLOR_RESET TERM_COLOR_BLUE; static const struct color_code { char code; const char *color; const char *html_color; } colors[] = { { COLOR_CODE_NORMAL, TERM_COLOR_NORMAL, HTML_COLOR_NORMAL }, { COLOR_CODE_RESET, TERM_COLOR_RESET, HTML_COLOR_RESET }, { COLOR_CODE_RED, TERM_COLOR_RED, HTML_COLOR_RED }, { COLOR_CODE_GREEN, TERM_COLOR_GREEN, HTML_COLOR_GREEN }, { COLOR_CODE_BLUE, TERM_COLOR_BLUE, HTML_COLOR_BLUE }, { COLOR_CODE_YELLOW, TERM_COLOR_YELLOW, HTML_COLOR_YELLOW }, { COLOR_CODE_MAGENTA, TERM_COLOR_MAGENTA, HTML_COLOR_MAGENTA }, { COLOR_CODE_CYAN, TERM_COLOR_CYAN, HTML_COLOR_CYAN }, { COLOR_CODE_GRAY, TERM_COLOR_GRAY, HTML_COLOR_GRAY }, { COLOR_CODE_BOLD, TERM_COLOR_BOLD, HTML_COLOR_BOLD }, }; static void color(const char *code, FILE *fp) { size_t len = strlen(code); if ((fp == logfp && log_color == COLOR_OFF) || (fp == outfp && out_color == COLOR_OFF)) return; if (fwrite(code, 1, len, fp) == len) return; /* ok */ /* disable color */ log_color = COLOR_OFF; out_color = COLOR_OFF; len = sizeof(TERM_COLOR_RESET) - 1; if (fwrite(TERM_COLOR_RESET, 1, len, fp) != len) pr_dbg("resetting terminal color failed"); } static bool check_busybox(const char *pager) { struct strv path_strv = STRV_INIT; char buf[PATH_MAX]; char *path; int i; bool ret = false; if (pager == NULL) return false; if (pager[0] == '/') goto check; /* search "PATH" env for absolute path */ strv_split(&path_strv, getenv("PATH"), ":"); strv_for_each(&path_strv, path, i) { snprintf(buf, sizeof(buf), "%s/%s", path, pager); if (!access(buf, X_OK)) { pager = buf; break; } } strv_free(&path_strv); check: path = realpath(pager, NULL); if (path) { ret = !strncmp("busybox", basename(path), 7); free(path); } return ret; } void setup_color(enum color_setting color, char *pager) { if (likely(color == COLOR_AUTO)) { char *term = getenv("TERM"); bool dumb = term && !strcmp(term, "dumb"); bool busybox = false; out_color = COLOR_ON; log_color = COLOR_ON; if (pager) { /* less in the busybox doesn't support color */ busybox = check_busybox(pager); } if (!isatty(fileno(outfp)) || dumb || busybox) out_color = COLOR_OFF; if (!isatty(fileno(logfp)) || dumb || busybox) log_color = COLOR_OFF; } else { log_color = color; out_color = color; } if (format_mode == FORMAT_HTML) { color_reset = HTML_COLOR_RESET; color_bold = HTML_COLOR_BOLD; color_string = HTML_COLOR_MAGENTA; color_symbol = HTML_COLOR_CYAN; color_struct = HTML_COLOR_CYAN; color_enum = HTML_COLOR_BLUE; color_enum_or = HTML_COLOR_RESET HTML_COLOR_BOLD "|" HTML_COLOR_RESET HTML_COLOR_BLUE; } if (out_color != COLOR_ON) { color_reset = ""; color_bold = ""; color_string = ""; color_symbol = ""; color_struct = ""; color_enum = ""; color_enum_or = "|"; } } static const char *get_color(char code) { unsigned i; if (out_color != COLOR_ON) return TERM_COLOR_NORMAL; for (i = 0; i < ARRAY_SIZE(colors); i++) { if (code == colors[i].code) { if (format_mode == FORMAT_HTML) return colors[i].html_color; else return colors[i].color; } } return TERM_COLOR_NORMAL; } void __pr_dbg(const char *fmt, ...) { va_list ap; color(TERM_COLOR_GRAY, logfp); va_start(ap, fmt); vfprintf(logfp, fmt, ap); va_end(ap); color(TERM_COLOR_RESET, logfp); } void __pr_err(const char *fmt, ...) { va_list ap; color(TERM_COLOR_RED, logfp); va_start(ap, fmt); vfprintf(logfp, fmt, ap); va_end(ap); color(TERM_COLOR_RESET, logfp); DTRAP(); exit(1); } void __pr_err_s(const char *fmt, ...) { va_list ap; int saved_errno = errno; char buf[512]; color(TERM_COLOR_RED, logfp); va_start(ap, fmt); vfprintf(logfp, fmt, ap); va_end(ap); fprintf(logfp, ": %s\n", uftrace_strerror(saved_errno, buf, sizeof(buf))); color(TERM_COLOR_RESET, logfp); DTRAP(); exit(1); } void __pr_warn(const char *fmt, ...) { va_list ap; color(TERM_COLOR_YELLOW, logfp); va_start(ap, fmt); vfprintf(logfp, fmt, ap); va_end(ap); color(TERM_COLOR_RESET, logfp); } void __pr_out(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(outfp, fmt, ap); va_end(ap); } void __pr_color(char code, const char *fmt, ...) { va_list ap; const char *sc = get_color(code); const char *ec = get_color(COLOR_CODE_RESET); color(sc, outfp); va_start(ap, fmt); vfprintf(outfp, fmt, ap); va_end(ap); color(ec, outfp); } #ifndef LIBMCOUNT static void __print_time_unit(int64_t delta_nsec, bool needs_sign) { uint64_t delta = llabs(delta_nsec); uint64_t delta_small = 0; char *units[] = { "us", "ms", " s", " m", " h", }; char *color_units[] = { "us", TERM_COLOR_GREEN "ms" TERM_COLOR_RESET, TERM_COLOR_YELLOW " s" TERM_COLOR_RESET, TERM_COLOR_RED " m" TERM_COLOR_RESET, TERM_COLOR_RED " h" TERM_COLOR_RESET, }; char *html_color_units[] = { "us", HTML_COLOR_GREEN "ms" HTML_COLOR_RESET, HTML_COLOR_YELLOW " s" HTML_COLOR_RESET, HTML_COLOR_RED " m" HTML_COLOR_RESET, HTML_COLOR_RED " h" HTML_COLOR_RESET, }; char *unit; unsigned limit[] = { 1000, 1000, 1000, 60, 24, INT_MAX, }; unsigned idx; if (delta_nsec == 0UL) { if (needs_sign) pr_out(" "); pr_out("%7s %2s", "", ""); return; } for (idx = 0; idx < ARRAY_SIZE(units); idx++) { delta_small = delta % limit[idx]; delta = delta / limit[idx]; if (delta < limit[idx + 1]) break; } ASSERT(idx < ARRAY_SIZE(units)); /* for some error cases */ if (delta > 999) delta = delta_small = 999; if (out_color == COLOR_ON) { if (format_mode == FORMAT_HTML) unit = html_color_units[idx]; else unit = color_units[idx]; } else unit = units[idx]; if (needs_sign) { const char *signs[] = { "+", "-" }; const char *color_signs[] = { TERM_COLOR_RED "+", TERM_COLOR_MAGENTA "+", TERM_COLOR_NORMAL "+", TERM_COLOR_BLUE "-", TERM_COLOR_CYAN "-", TERM_COLOR_NORMAL "-", }; const char *html_color_signs[] = { HTML_COLOR_RED "+", HTML_COLOR_MAGENTA "+", HTML_COLOR_NORMAL "+", HTML_COLOR_BLUE "-", HTML_COLOR_CYAN "-", HTML_COLOR_NORMAL "-", }; int sign_idx = (delta_nsec > 0); int indent = (delta >= 100) ? 0 : (delta >= 10) ? 1 : 2; const char *sign = signs[sign_idx]; const char *ends = TERM_COLOR_NORMAL; if (out_color == COLOR_ON) { if (delta_nsec >= 100000) sign_idx = 0; else if (delta_nsec >= 5000) sign_idx = 1; else if (delta_nsec > 0) sign_idx = 2; else if (delta_nsec <= -100000) sign_idx = 3; else if (delta_nsec <= -5000) sign_idx = 4; else sign_idx = 5; if (format_mode == FORMAT_HTML) { sign = html_color_signs[sign_idx]; ends = HTML_COLOR_RESET; } else { sign = color_signs[sign_idx]; ends = TERM_COLOR_RESET; } } pr_out("%*s%s%" PRId64 ".%03" PRIu64 "%s %s", indent, "", sign, delta, delta_small, ends, unit); } else pr_out("%3" PRIu64 ".%03" PRIu64 " %s", delta, delta_small, unit); } void print_time_unit(uint64_t delta_nsec) { __print_time_unit(delta_nsec, false); } void print_diff_percent(uint64_t base_nsec, uint64_t pair_nsec) { double percent; const char *sc; const char *ec = get_color(COLOR_CODE_RESET); if (base_nsec == 0) { sc = get_color(COLOR_CODE_RED); pr_out("%s%7s%s ", sc, "N/A", ec); return; } if (pair_nsec == 0) { sc = get_color(COLOR_CODE_BLUE); pr_out("%s%7s%s ", sc, "N/A", ec); return; } percent = 100.0 * (int64_t)(pair_nsec - base_nsec) / base_nsec; /* for some error cases */ if (percent > 999.99) percent = 999.99; else if (percent < -999.99) percent = -999.99; sc = percent > 30 ? get_color(COLOR_CODE_RED) : percent > 3 ? get_color(COLOR_CODE_MAGENTA) : percent < -30 ? get_color(COLOR_CODE_BLUE) : percent < -3 ? get_color(COLOR_CODE_CYAN) : get_color(COLOR_CODE_NORMAL); pr_out("%s%+7.2f%s%%", sc, percent, ec); } void print_diff_time_unit(uint64_t base_nsec, uint64_t pair_nsec) { if (base_nsec == pair_nsec) pr_out("%11s", "0 us"); else __print_time_unit(pair_nsec - base_nsec, true); } void print_diff_count(uint64_t base, uint64_t pair) { char diff_colors[] = { COLOR_CODE_RED, COLOR_CODE_BLUE, }; int sign_idx = (pair < base); int64_t diff = pair - base; const char *sc = get_color(diff_colors[sign_idx]); const char *ec = get_color(COLOR_CODE_RESET); if (diff != 0) pr_out("%s%+9" PRId64 "%s", sc, diff, ec); else pr_out("%9s", "+0"); } #endif uftrace-0.15.2/utils/demangle.c000066400000000000000000001257171455365734300163440ustar00rootroot00000000000000/* * Very simple (and incomplete by design) C++ name demangler. * * Copyright (C) 2015-2019, LG Electronics, Namhyung Kim * * Released under the GPL v2 license (the C++ part). * * See https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling * * Rust demangler referred to https://github.com/alexcrichton/rustc-demangle * which was released by MIT (or Apache) license. * * Copyright (c) 2014 Alex Crichton * * Permission is hereby granted, free of charge, to any * person obtaining a copy of this software and associated * documentation files (the "Software"), to deal in the * Software without restriction, including without * limitation the rights to use, copy, modify, merge, * publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software * is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice * shall be included in all copies or substantial portions * of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF * ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED * TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT * SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include /* This should be defined before #include "utils.h" */ #define PR_FMT "demangle" #define PR_DOMAIN DBG_DEMANGLE #include "utils/symbol.h" #include "utils/utils.h" #define MAX_DEBUG_DEPTH 128 enum symbol_demangler demangler = DEMANGLE_SIMPLE; struct demangle_debug { const char *func; int level; int pos; }; struct demangle_data { char *old; char *new; const char *func; char *expected; int line; int pos; int len; int newpos; int alloc; int level; int type; int nr_dbg; int templates; bool type_info; bool first_name; bool ignore_disc; struct demangle_debug debug[MAX_DEBUG_DEPTH]; }; static char dd_expbuf[2]; static int dd_eof(struct demangle_data *dd) { return dd->pos >= dd->len; } static char dd_peek(struct demangle_data *dd, int lookahead) { if (dd->pos + lookahead > dd->len) return 0; return dd->old[dd->pos + lookahead]; } static char dd_curr(struct demangle_data *dd) { return dd_peek(dd, 0); } static void __dd_add_debug(struct demangle_data *dd, const char *func) { if (dd->nr_dbg < MAX_DEBUG_DEPTH && func) { struct demangle_debug *dbg = &dd->debug[dd->nr_dbg++]; dbg->func = func; dbg->level = dd->level; dbg->pos = dd->pos; } } static char __dd_consume_n(struct demangle_data *dd, int n, const char *dbg) { char c = dd_curr(dd); if (dbg) __dd_add_debug(dd, dbg); if (dd->pos + n > dd->len) return 0; dd->pos += n; return c; } static char __dd_consume(struct demangle_data *dd, const char *dbg) { return __dd_consume_n(dd, 1, dbg); } #define dd_consume(dd) __dd_consume(dd, __func__) #define dd_consume_n(dd, n) __dd_consume_n(dd, n, __func__) #define dd_add_debug(dd) __dd_add_debug(dd, __func__) #define DD_DEBUG(dd, exp, inc) \ ({ \ dd->func = __func__; \ dd->line = __LINE__ - 1; \ dd->pos += inc; \ dd->expected = exp; \ return -1; \ }) #define DD_DEBUG_CONSUME(dd, exp_c) \ ({ \ if (dd_consume(dd) != exp_c) { \ if (!dd->expected) { \ dd->func = __func__; \ dd->line = __LINE__; \ dd->pos--; \ dd->expected = dd_expbuf; \ dd_expbuf[0] = exp_c; \ } \ return -1; \ } \ }) #define __DD_DEBUG_CONSUME(dd, exp_c) \ ({ \ if (__dd_consume(dd, NULL) != exp_c) { \ if (!dd->expected) { \ dd->func = __func__; \ dd->line = __LINE__; \ dd->pos--; \ dd->expected = dd_expbuf; \ dd_expbuf[0] = exp_c; \ } \ return -1; \ } \ }) static void dd_debug_print(struct demangle_data *dd) { int i; const char *expected = dd->expected; if (expected == NULL) { if (dd_eof(dd)) expected = "more input"; else expected = "unknown input"; } if (dd->func == NULL) dd->func = "demangle"; if (dbg_domain[DBG_DEMANGLE] <= 3) { pr_dbg3("demangle failed: %s\n", dd->old); return; } pr_dbg4("simple demangle failed:%s%s\n%s\n%*c\n%s:%d: \"%s\" expected\n", dd_eof(dd) ? " (EOF)" : "", dd->level ? " (not finished)" : "", dd->old, dd->pos + 1, '^', dd->func, dd->line, expected); pr_dbg4("current: %s (pos: %d/%d)\n", dd->new, dd->pos, dd->len); for (i = 0; i < dd->nr_dbg; i++) { struct demangle_debug *dbg = &dd->debug[i]; pr_dbg4(" [%02d] (%03d/%c%c) %*s%s\n", i, dbg->pos, dbg->pos < dd->len ? dd->old[dbg->pos] : ' ', dbg->pos + 1 < dd->len ? dd->old[dbg->pos + 1] : ' ', dbg->level * 2, "", dbg->func); } } static const struct { char op[2]; char *name; } ops[] = { { { 'n', 'w' }, " new" }, { { 'n', 'a' }, " new[]" }, { { 'd', 'l' }, " delete" }, { { 'd', 'a' }, " delete[]" }, { { 'p', 's' }, "+" }, /* unary */ { { 'n', 'g' }, "-" }, /* unary */ { { 'a', 'd' }, "&" }, /* unary */ { { 'd', 'e' }, "*" }, /* unary */ { { 'c', 'o' }, "~" }, { { 'p', 'l' }, "+" }, { { 'm', 'i' }, "-" }, { { 'm', 'l' }, "*" }, { { 'd', 'v' }, "/" }, { { 'r', 'm' }, "%" }, { { 'a', 'n' }, "&" }, { { 'o', 'r' }, "|" }, { { 'e', 'o' }, "^" }, { { 'a', 'S' }, "=" }, { { 'p', 'L' }, "+=" }, { { 'm', 'I' }, "-=" }, { { 'm', 'L' }, "*=" }, { { 'd', 'V' }, "/=" }, { { 'r', 'M' }, "%=" }, { { 'a', 'N' }, "&=" }, { { 'o', 'R' }, "|=" }, { { 'e', 'O' }, "^=" }, { { 'l', 's' }, "<<" }, { { 'r', 's' }, ">>" }, { { 'l', 'S' }, "<<=" }, { { 'r', 'S' }, ">>=" }, { { 'e', 'q' }, "==" }, { { 'n', 'e' }, "!=" }, { { 'l', 't' }, "<" }, { { 'g', 't' }, ">" }, { { 'l', 'e' }, "<=" }, { { 'g', 'e' }, ">=" }, { { 'n', 't' }, "!" }, { { 'a', 'a' }, "&&" }, { { 'o', 'o' }, "||" }, { { 'p', 'p' }, "++" }, { { 'm', 'm' }, "--" }, { { 'c', 'm' }, "," }, { { 'p', 'm' }, "->*" }, { { 'p', 't' }, "->" }, { { 'c', 'l' }, "()" }, { { 'i', 'x' }, "[]" }, { { 'q', 'u' }, "?" }, { { 'c', 'v' }, "(cast)" }, { { 'l', 'i' }, "\"\"" }, }; static const struct { char code; char *name; } types[] = { { 'v', "void" }, { 'w', "wchar_t" }, { 'b', "bool" }, { 'c', "char" }, { 'a', "signed char" }, { 'h', "unsigned char" }, { 's', "short" }, { 't', "unsigned short" }, { 'i', "int" }, { 'j', "unsigned int" }, { 'l', "long" }, { 'm', "unsigned long" }, { 'x', "long long" }, { 'y', "unsigned long long" }, { 'n', "__int128" }, { 'o', "unsigned __int128" }, { 'f', "float" }, { 'd', "double" }, { 'e', "long double" }, { 'g', "__float128" }, { 'z', "..." }, }; static const struct { char code; char *name; } std_abbrevs[] = { { 't', "std" }, { 'a', "std::allocator" }, { 'b', "std::basic_string" }, { 's', "std::basic_string<>" }, { 'i', "std::basic_istream" }, { 'o', "std::basic_ostream" }, { 'd', "std::basic_iostream" }, }; static const struct { char *code; /* surrounded by $..$ */ char *punc; } rust_mappings[] = { { "SP", "@" }, { "BP", "*" }, { "RF", "&" }, { "LT", "<" }, { "GT", ">" }, { "LP", "(" }, { "RP", ")" }, { "C", "," }, /* some selected unicode characters */ { "u20", " " }, { "u22", "\"" }, { "u27", "'" }, { "u2b", "+" }, { "u3b", ";" }, { "u3d", "=" }, { "u5b", "[" }, { "u5d", "]" }, { "u7b", "{" }, { "u7d", "}" }, { "u7e", "~" }, }; static int dd_encoding(struct demangle_data *dd); static int dd_name(struct demangle_data *dd); static int dd_local_name(struct demangle_data *dd); static int dd_source_name(struct demangle_data *dd); static int dd_operator_name(struct demangle_data *dd); static int dd_nested_name(struct demangle_data *dd); static int dd_unqualified_name(struct demangle_data *dd); static int dd_type(struct demangle_data *dd); static int dd_decltype(struct demangle_data *dd); static int dd_expression(struct demangle_data *dd); static int dd_expr_primary(struct demangle_data *dd); static int dd_append_len(struct demangle_data *dd, char *str, int size) { if (dd->newpos + size >= dd->alloc) { dd->alloc = ALIGN(dd->newpos + size + 1, 16); dd->new = xrealloc(dd->new, dd->alloc); } /* copy including the last NUL byte (but usually not) */ strncpy(&dd->new[dd->newpos], str, size + 1); dd->newpos += size; dd->new[dd->newpos] = '\0'; return 0; } static int dd_append(struct demangle_data *dd, char *str) { return dd_append_len(dd, str, strlen(str)); } static int dd_append_separator(struct demangle_data *dd, char *str) { if (!dd->first_name) dd_append(dd, str); dd->first_name = false; return 0; } static int dd_number(struct demangle_data *dd) { char *str = &dd->old[dd->pos]; char *end; int num; if (dd_eof(dd)) return -1; if (*str == 'n') { /* negative number */ str++; dd->pos++; } if (!isdigit(*str)) DD_DEBUG(dd, "digit", 0); num = strtoul(str, &end, 0); dd->pos += end - str; return num; } static int dd_seq_id(struct demangle_data *dd) { char c = dd_curr(dd); if (dd_eof(dd)) return -1; /* just skip for now */ while (isdigit(c) || isupper(c)) { dd_add_debug(dd); c = dd->old[++dd->pos]; } return 0; } static int dd_call_offset(struct demangle_data *dd) { char c = dd_curr(dd); if (dd_eof(dd)) return -1; if (c == 'h') { dd_consume(dd); if (dd_number(dd) < 0) return -1; __DD_DEBUG_CONSUME(dd, '_'); return 0; } if (c == 'v') { dd_consume(dd); if (dd_number(dd) < 0) return -1; __DD_DEBUG_CONSUME(dd, '_'); if (dd_number(dd) < 0) return -1; __DD_DEBUG_CONSUME(dd, '_'); return 0; } return -1; } static int dd_qualifier(struct demangle_data *dd) { char c = dd_curr(dd); char qual[] = "rVKRO"; if (dd_eof(dd)) return -1; /* qualifiers are optional and we ignore them */ if (strchr(qual, c)) dd_consume(dd); return 0; } static int dd_initializer(struct demangle_data *dd) { char c0 = dd_consume(dd); char c1 = __dd_consume(dd, NULL); if (dd_eof(dd)) return -1; if (c0 != 'p' || c1 != 'i') DD_DEBUG(dd, "pi", -2); dd->level++; while (dd_curr(dd) != 'E') { if (dd_expression(dd) < 0) return -1; } __DD_DEBUG_CONSUME(dd, 'E'); dd->level--; return 0; } static int dd_abi_tag(struct demangle_data *dd) { if (dd_eof(dd)) return -1; DD_DEBUG_CONSUME(dd, 'B'); if (dd_source_name(dd) < 0) return -1; return 0; } static int dd_substitution(struct demangle_data *dd) { char c; unsigned i; if (dd_eof(dd)) return -1; DD_DEBUG_CONSUME(dd, 'S'); c = dd_curr(dd); for (i = 0; i < ARRAY_SIZE(std_abbrevs); i++) { if (c == std_abbrevs[i].code) { __dd_consume(dd, NULL); if (dd->type == 0 || dd->type_info) { dd_append_separator(dd, "::"); dd_append(dd, std_abbrevs[i].name); } if (dd_curr(dd) == 'B') dd_abi_tag(dd); return 0; } } dd_seq_id(dd); __DD_DEBUG_CONSUME(dd, '_'); return 0; } static int dd_function_param(struct demangle_data *dd) { char c0 = dd_consume(dd); char c1 = __dd_consume(dd, NULL); if (dd_eof(dd)) return -1; if (c0 != 'f' || (c1 != 'p' && c1 != 'L')) DD_DEBUG(dd, "fp or fL", -2); if (isdigit(dd_curr(dd))) { dd_number(dd); if (c1 == 'L') __DD_DEBUG_CONSUME(dd, 'p'); } dd_qualifier(dd); if (isdigit(dd_curr(dd))) dd_number(dd); __DD_DEBUG_CONSUME(dd, '_'); return 0; } static int dd_template_param(struct demangle_data *dd) { if (dd_eof(dd)) return -1; DD_DEBUG_CONSUME(dd, 'T'); dd_number(dd); __DD_DEBUG_CONSUME(dd, '_'); return 0; } static int dd_template_arg(struct demangle_data *dd) { char c = dd_curr(dd); if (dd_eof(dd)) return -1; if (c == 'X') { dd_consume(dd); dd->level++; dd_expression(dd); __DD_DEBUG_CONSUME(dd, 'E'); dd->level--; } else if (c == 'L') { if (dd_expr_primary(dd) < 0) return -1; } else if (c == 'J') { dd_consume(dd); dd->level++; while (dd_curr(dd) != 'E') { if (dd_template_arg(dd) < 0) return -1; } __DD_DEBUG_CONSUME(dd, 'E'); dd->level--; } else { if (dd_type(dd) < 0) return -1; } return 0; } static int dd_template_args(struct demangle_data *dd) { if (dd_eof(dd)) return -1; DD_DEBUG_CONSUME(dd, 'I'); dd->templates++; dd->level++; while (dd_curr(dd) != 'E') { if (dd_template_arg(dd) < 0) return -1; } __DD_DEBUG_CONSUME(dd, 'E'); dd->level--; dd->templates--; return 0; } static int dd_simple_id(struct demangle_data *dd) { if (dd_eof(dd)) return -1; if (!isdigit(dd_curr(dd))) DD_DEBUG(dd, "digit", -1); if (dd_source_name(dd) < 0) return -1; if (dd_curr(dd) == 'I') return dd_template_args(dd); return 0; } static int dd_unresolved_type(struct demangle_data *dd) { char c = dd_curr(dd); if (dd_eof(dd)) return -1; if (c == 'T') return dd_template_param(dd); if (c == 'D') return dd_decltype(dd); if (c == 'S') { if (dd_substitution(dd) < 0) return -1; if (dd_curr(dd) == 'I') return dd_template_args(dd); if (isdigit(dd_curr(dd))) return dd_unqualified_name(dd); return 0; } return -1; } static int dd_destructor_name(struct demangle_data *dd) { char c = dd_curr(dd); if (dd_eof(dd)) return -1; if (isdigit(c)) return dd_source_name(dd); return dd_unresolved_type(dd); } static int dd_base_unresolved_name(struct demangle_data *dd) { char c0 = dd_curr(dd); char c1 = dd_peek(dd, 1); if (dd_eof(dd)) return -1; if (c0 == 'o' && c1 == 'n') { dd_consume_n(dd, 2); if (dd_operator_name(dd) < 0) return -1; if (dd_curr(dd) == 'I') return dd_template_args(dd); return 0; } if (c0 == 'd' && c1 == 'n') { dd_consume_n(dd, 2); return dd_destructor_name(dd); } return dd_simple_id(dd); } static int dd_unresolved_name(struct demangle_data *dd) { char c0 = dd_curr(dd); char c1 = dd_peek(dd, 1); if (dd_eof(dd)) return -1; if (c0 == 'g' && c1 == 's') { __dd_consume_n(dd, 2, NULL); c0 = dd_curr(dd); c1 = dd_peek(dd, 1); } if (c0 == 's' && c1 == 'r') { dd_consume_n(dd, 2); c0 = dd_curr(dd); if (c0 == 'T' || c0 == 'D' || c0 == 'S') { if (dd_type(dd) < 0) return -1; if (dd_base_unresolved_name(dd) < 0) return -1; if (dd_curr(dd) == 'I') dd_template_args(dd); return 0; } if (c0 == 'N') { __dd_consume(dd, NULL); if (dd_type(dd) < 0) return -1; } c0 = dd_curr(dd); while (c0 != 'E') { if (dd_simple_id(dd) < 0) return 0; c0 = dd_curr(dd); } if (c0 != 'E') pr_dbg("no E\n"); __DD_DEBUG_CONSUME(dd, 'E'); } return dd_base_unresolved_name(dd); } static int dd_expr_primary(struct demangle_data *dd) { if (dd_eof(dd)) return -1; DD_DEBUG_CONSUME(dd, 'L'); dd->type++; dd->level++; if (dd_curr(dd) == '_' && dd_peek(dd, 1) == 'Z') { __dd_consume_n(dd, 2, NULL); if (dd_encoding(dd) < 0) return -1; __DD_DEBUG_CONSUME(dd, 'E'); dd->level--; dd->type--; return 0; } dd_type(dd); dd_number(dd); if (dd_curr(dd) == '_') { __dd_consume(dd, NULL); dd_number(dd); } __DD_DEBUG_CONSUME(dd, 'E'); dd->level--; dd->type--; return 0; } static int dd_expr_list(struct demangle_data *dd) { char c = dd_curr(dd); if (dd_eof(dd)) return -1; dd->level++; while (c != 'E' && c != '_') { if (dd_expression(dd) < 0) return -1; c = dd_curr(dd); } __dd_consume_n(dd, 1, NULL); dd->level--; return 0; } static int dd_expression(struct demangle_data *dd) { unsigned i; char c0 = dd_peek(dd, 0); char c1 = dd_peek(dd, 1); char *exp = &dd->old[dd->pos]; char *unary_ops[] = { "ps", "ng", "ad", "de", "pp_", "mm_", "pp", "mm", "dl", "da", "te", "sz", "az", "nx", "sp", "tw", "nt", }; if (dd_eof(dd)) return -1; dd_add_debug(dd); if (c0 == 'g' && c1 == 's') { __dd_consume_n(dd, 2, NULL); c0 = dd_curr(dd); c1 = dd_peek(dd, 1); } if (c0 == 'L') return dd_expr_primary(dd); for (i = 0; i < ARRAY_SIZE(unary_ops); i++) { /* unary operator */ if (strncmp(unary_ops[i], exp, strlen(unary_ops[i]))) continue; dd_consume_n(dd, strlen(unary_ops[i])); return dd_expression(dd); } if (c0 == 'q' && c1 == 'u') { /* ternary operator */ dd_consume_n(dd, 2); if (dd_expression(dd) < 0) return -1; if (dd_expression(dd) < 0) return -1; return dd_expression(dd); } for (i = 0; i < ARRAY_SIZE(ops); i++) { /* binary operator */ if (c0 != ops[i].op[0] || c1 != ops[i].op[1]) continue; if (c0 == 'c' || c1 == 'v') continue; dd_consume_n(dd, 2); if (dd_expression(dd) < 0) return -1; return dd_expression(dd); } if (c0 == 'c' && c1 == 'l') { dd_consume_n(dd, 2); return dd_expr_list(dd); } if (c0 == 'c' && c1 == 'v') { dd_consume_n(dd, 2); if (dd_type(dd) < 0) return -1; if (dd_curr(dd) == '_') { dd_consume(dd); return dd_expr_list(dd); } return dd_expression(dd); } if (c0 == 't' && c1 == 'l') { dd_consume_n(dd, 2); if (dd_type(dd) < 0) return -1; return dd_expr_list(dd); } if (c0 == 'i' && c1 == 'l') { dd_consume_n(dd, 2); return dd_expr_list(dd); } if (c0 == 'n' && (c1 == 'w' || c1 == 'a')) { if (dd_expr_list(dd) < 0) return -1; if (dd_type(dd) < 0) return -1; if (dd_curr(dd) == 'E') { dd_consume(dd); return 0; } return dd_initializer(dd); } if (strchr("dscr", c0) && c1 == 'c') { dd_consume_n(dd, 2); if (dd_type(dd) < 0) return -1; return dd_expression(dd); } if ((c0 == 't' && c1 == 'i') || ((c0 == 's' || c0 == 'a') && c1 == 't')) { dd_consume_n(dd, 2); return dd_type(dd); } if (c0 == 'T' && (c1 == '_' || isdigit(c1))) { return dd_template_param(dd); } if (c0 == 'f' && (c1 == 'p' || c1 == 'L')) { return dd_function_param(dd); } if ((c0 == 'd' || c0 == 'p') && c1 == 't') { dd_consume_n(dd, 2); if (dd_expression(dd) < 0) return -1; return dd_unresolved_name(dd); } if (c0 == 'd' && c1 == 's') { dd_consume_n(dd, 2); if (dd_expression(dd) < 0) return -1; return dd_expression(dd); } if (c0 == 's' && c1 == 'Z') { dd_consume_n(dd, 2); c0 = dd_curr(dd); if (c0 == 'T') return dd_template_param(dd); if (c0 == 'f') return dd_function_param(dd); return -1; } if (c0 == 's' && c1 == 'P') { dd_consume_n(dd, 2); dd->level++; while (dd_curr(dd) != 'E') { if (dd_template_arg(dd) < 0) return -1; } __DD_DEBUG_CONSUME(dd, 'E'); dd->level--; return 0; } if (c0 == 't' && c1 == 'r') { dd_consume_n(dd, 2); return 0; } return dd_unresolved_name(dd); } static int dd_function_type(struct demangle_data *dd) { char c; if (dd_eof(dd)) return -1; DD_DEBUG_CONSUME(dd, 'F'); c = dd_curr(dd); if (c == 'Y') __dd_consume(dd, NULL); dd->type++; dd->level++; c = dd_curr(dd); while (c != 'E') { int old_pos = dd->pos; if (dd_type(dd) < 0) { dd->pos = old_pos; break; } c = dd_curr(dd); } if (c == 'R' || c == 'O') dd_qualifier(dd); __DD_DEBUG_CONSUME(dd, 'E'); dd->level--; dd->type--; return 0; } static int dd_array_type(struct demangle_data *dd) { char c; if (dd_eof(dd)) return -1; DD_DEBUG_CONSUME(dd, 'A'); c = dd_curr(dd); if (isdigit(c)) dd_number(dd); else if (c != '_') dd_expression(dd); /* optional */ __DD_DEBUG_CONSUME(dd, '_'); return dd_type(dd); } static int dd_ptr_to_member_type(struct demangle_data *dd) { if (dd_eof(dd)) return -1; DD_DEBUG_CONSUME(dd, 'M'); if (dd_type(dd) < 0) /* class */ return -1; return dd_type(dd); /* member */ } static int dd_decltype(struct demangle_data *dd) { char c0 = dd_consume(dd); char c1 = __dd_consume(dd, NULL); if (dd_eof(dd)) return -1; if (c0 != 'D' || (c1 != 'T' && c1 != 't')) DD_DEBUG(dd, "DT or Dt", -2); dd->type++; dd->level++; dd_expression(dd); __DD_DEBUG_CONSUME(dd, 'E'); dd->level--; dd->type--; return 0; } static int dd_vector_type(struct demangle_data *dd) { char c0 = dd_consume(dd); char c1 = __dd_consume(dd, NULL); if (dd_eof(dd)) return -1; if (c0 != 'D' || c1 != 'v') DD_DEBUG(dd, "Dv", -2); dd->type++; c0 = dd_curr(dd); if (c0 == '_') { __dd_consume(dd, NULL); dd_expression(dd); } else if (dd_number(dd) < 0) return -1; __DD_DEBUG_CONSUME(dd, '_'); dd->type--; return 0; } static int dd_type(struct demangle_data *dd) { unsigned i; char cv_qual[] = "rVK"; char prefix[] = "PROCG"; char D_types[] = "defhisacnu"; char scue[] = "sue"; /* struct, class, union, enum */ int done = 0; int ret = -1; if (dd_eof(dd)) return -1; /* ignore type names */ dd->type++; dd_add_debug(dd); dd->level++; while (!done && !dd_eof(dd)) { char c = dd_curr(dd); if (strchr(cv_qual, c)) { dd_qualifier(dd); continue; } else if (strchr(prefix, c)) { dd_consume(dd); continue; } else if (c == 'F') { ret = dd_function_type(dd); done = 1; } else if (c == 'T') { c = dd_peek(dd, 1); if (strchr(scue, c)) { /* struct, class, union, enum */ dd_consume_n(dd, 2); ret = dd_name(dd); } else if (c == '_' || isdigit(c)) { ret = dd_template_param(dd); if (dd_curr(dd) == 'I') ret = dd_template_args(dd); } done = 1; } else if (c == 'A') { ret = dd_array_type(dd); done = 1; } else if (c == 'M') { ret = dd_ptr_to_member_type(dd); done = 1; } else if (c == 'D') { c = dd_peek(dd, 1); if (strchr(D_types, c)) { dd_consume_n(dd, 2); ret = 0; } else if (c == 'p') { /* pack expansion */ dd_consume_n(dd, 2); continue; } else if (c == 'v') { dd_vector_type(dd); continue; } else if (c == 't' || c == 'T') ret = dd_decltype(dd); done = 1; } else if (c == 'S') { c = dd_peek(dd, 1); ret = dd_substitution(dd); if (!ret && c == 't' && isdigit(dd_curr(dd))) ret = dd_unqualified_name(dd); if (dd_curr(dd) == 'I') ret = dd_template_args(dd); done = 1; } else if (c == 'u') { /* vendor extended type */ dd_consume(dd); ret = dd_source_name(dd); done = 1; } else if (c == 'U') { /* vendor extended type qualifier */ dd_consume(dd); ret = dd_source_name(dd); if (ret < 0) done = 1; if (!ret && dd_curr(dd) == 'I') ret = dd_template_args(dd); if (ret < 0) done = 1; } else if (c == 'I') { /* template args?? - not specified in the spec */ ret = dd_template_args(dd); done = 1; } else if (isdigit(c) || c == 'N' || c == 'Z') { /* class or enum name */ ret = dd_name(dd); done = 1; } else { /* builtin types */ for (i = 0; i < ARRAY_SIZE(types); i++) { if (c == types[i].code) { __dd_consume(dd, NULL); ret = 0; break; } } done = 1; } } dd->level--; dd->type--; return ret; } static int dd_discriminator(struct demangle_data *dd) { char c; if (dd_eof(dd)) return -1; DD_DEBUG_CONSUME(dd, '_'); c = dd_curr(dd); if (isdigit(c)) { return dd_number(dd) > 0 ? 0 : -1; } else if (c == '_') { __dd_consume(dd, NULL); if (dd_number(dd) < 0) return -1; __DD_DEBUG_CONSUME(dd, '_'); } return 0; } static int dd_special_name(struct demangle_data *dd) { char c0 = dd_curr(dd); char c1 = dd_peek(dd, 1); char T_type[] = "VTISFJ"; char *T_type_name[] = { "vtable", "VTT", "typeinfo_name", "typeinfo", "typeinfo_fn", "java_class" }; if (dd_eof(dd)) return -1; if (c0 == 'T') { if (strchr(T_type, c1)) { int idx; char *p; dd_consume_n(dd, 2); dd->type_info = true; p = strchr(T_type, c1); idx = p - T_type; /* special name prefix */ dd_append(dd, "__"); dd_append(dd, T_type_name[idx]); dd_append(dd, "__"); return dd_type(dd); } if (c1 == 'h' || c1 == 'v') { dd_consume(dd); if (dd_call_offset(dd) < 0) return -1; return dd_encoding(dd); } if (c1 == 'c') { dd_consume_n(dd, 2); if (dd_call_offset(dd) < 0) return -1; if (dd_call_offset(dd) < 0) return -1; return dd_encoding(dd); } if (c1 == 'C') { dd_consume_n(dd, 2); dd_append(dd, "__construction_vtable__"); /* base type */ dd->type_info = true; if (dd_type(dd) < 0) return -1; if (dd_number(dd) < 0) return -1; if (dd_eof(dd)) return 0; __DD_DEBUG_CONSUME(dd, '_'); /* * ideally it'd be better using this derived type * for the simple name but it requires to support * substitution correctly which is not done yet. * So just use the base type info only. */ dd->type_info = false; return dd_type(dd); } if (c1 == 'H' || c1 == 'W') { /* TLS init and wrapper */ dd_consume_n(dd, 2); dd_append_separator(dd, "::"); dd_append(dd, "TLS_"); dd_append(dd, c1 == 'H' ? "init" : "wrap"); return dd_name(dd); } } if (c0 == 'G') { if (c1 == 'V') { /* guard */ dd_consume_n(dd, 2); dd_append(dd, "__guard_variable__"); return dd_name(dd); } if (c1 == 'R') { /* reftemp */ dd_consume_n(dd, 2); dd_append(dd, "__ref_temp__"); dd->ignore_disc = true; if (dd_name(dd) < 0) return -1; if (dd_curr(dd) != '_') dd_seq_id(dd); __DD_DEBUG_CONSUME(dd, '_'); return 0; } if (c1 == 'A') { /* hidden alias */ dd_consume_n(dd, 2); return dd_encoding(dd); } if (c1 == 'T') { dd_consume_n(dd, 2); c0 = dd_curr(dd); /* (non-)transaction clone */ if (c0 == 't' || c0 == 'n') { dd_consume(dd); return dd_encoding(dd); } return -1; } } DD_DEBUG(dd, "valid special name", 0); return 0; } static int dd_ctor_dtor_name(struct demangle_data *dd) { char c0 = dd_consume(dd); char c1 = __dd_consume(dd, NULL); char *pos; int len; int ret = 0; bool needs_type = false; if (dd_eof(dd)) return -1; if ((c0 != 'C' && c0 != 'D')) DD_DEBUG(dd, "C[0-5] or D[0-5]", -2); /* inheriting constructor */ if (c1 == 'I') { c1 = __dd_consume(dd, NULL); needs_type = true; } if (!isdigit(c1)) DD_DEBUG(dd, "C[0-5] or D[0-5]", -2 - (needs_type ? 1 : 0)); if (needs_type) ret = dd_type(dd); if (dd->type) return ret; /* repeat last name after '::' */ pos = strrchr(dd->new, ':'); if (pos == NULL) pos = dd->new; else pos++; /* pos can be invalidated after dd_apend() below, so copy it */ pos = xstrdup(pos); len = strlen(pos); if (c0 == 'C') dd_append(dd, "::"); else dd_append(dd, "::~"); dd_append_len(dd, pos, len); free(pos); return ret; } static int dd_operator_name(struct demangle_data *dd) { unsigned i; char c0 = dd_consume(dd); char c1 = __dd_consume(dd, NULL); if (dd_eof(dd)) return -1; if (dd->type) { if (c0 == 'c' && c1 == 'v') dd_type(dd); if (c0 == 'l' && c1 == 'i') dd_source_name(dd); return 0; } for (i = 0; i < ARRAY_SIZE(ops); i++) { if (c0 == ops[i].op[0] && c1 == ops[i].op[1]) { dd_append_separator(dd, "::"); dd_append(dd, "operator"); dd_append(dd, ops[i].name); dd->type++; if (c0 == 'c' && c1 == 'v') dd_type(dd); if (c0 == 'l' && c1 == 'i') dd_source_name(dd); dd->type--; return 0; } } if (c0 == 'v' && isdigit(c1)) { /* vendor extended operator */ dd->type++; dd_source_name(dd); dd->type--; } DD_DEBUG(dd, "valid operator name", -2); return 0; } static int dd_source_name(struct demangle_data *dd) { int num = dd_number(dd); char *dollar; char *p, *end; unsigned i; bool add_name = false; if (num < 0) return -1; if (dd_eof(dd) || dd->pos + num > dd->len) DD_DEBUG(dd, "shorter name", 0); dd_add_debug(dd); if (dd->type && !dd->type_info) goto out; if (dd->templates) goto out; /* ignore hash code in a Rust symbol */ if (num == 17 && dd->old[dd->pos] == 'h') { for (i = 1; i < 17; i++) { if (!isxdigit(dd->old[dd->pos + i])) break; } if (i == 17) goto out; } add_name = true; dd_append_separator(dd, "::"); p = dd->old + dd->pos; dollar = strchr(p, '$'); if (dollar == NULL) goto out; end = p + num; if (dollar > end) goto out; /* check special symbol mappings (e.g. '$LT$') for Rust */ while (dollar != NULL && dollar < end) { bool found = false; char *separator = p; num = dollar - p; while (true) { char *update = strstr(separator, ".."); if (!update || update > dollar) break; dd_append_len(dd, separator, update - separator); dd_append_separator(dd, "::"); separator = update + 2; } dd_append_len(dd, separator, dollar - separator); for (i = 0; i < ARRAY_SIZE(rust_mappings); i++) { if (strncmp(rust_mappings[i].code, dollar + 1, strlen(rust_mappings[i].code))) continue; dd_add_debug(dd); /* skip "as TRAIT" */ if (strncmp(dollar, "$u20$as$u20$", 12) == 0) { dd_append(dd, ">"); num += (end - dollar); __dd_consume_n(dd, num, NULL); } else { dd_append(dd, rust_mappings[i].punc); num += strlen(rust_mappings[i].code) + 2; __dd_consume_n(dd, num, NULL); } p += num; found = true; break; } /* treat '$' as a normal symbol */ if (!found) break; dollar = strchr(p, '$'); } num = end - p; out: if (add_name) dd_append_len(dd, p, num); __dd_consume_n(dd, num, NULL); return 0; } static int dd_unqualified_name(struct demangle_data *dd) { char c0 = dd_curr(dd); char c1 = dd_peek(dd, 1); int ret = 0; if (dd_eof(dd)) return -1; if (c0 == 'C' || c0 == 'D') ret = dd_ctor_dtor_name(dd); else if (c0 == 'U') { if (c1 == 't') { /* unnamed type name */ dd->type++; dd_consume_n(dd, 2); dd_number(dd); DD_DEBUG_CONSUME(dd, '_'); dd->type--; } else if (c1 == 'l') { int n = -1; char buf[32]; /* closure type name (or lambda) */ dd_consume_n(dd, 2); dd->level++; while (dd_curr(dd) != 'E') { if (dd_type(dd) < 0) break; } DD_DEBUG_CONSUME(dd, 'E'); dd->level--; if (dd_curr(dd) != '_') { n = dd_number(dd); if (n < 0) return -1; } DD_DEBUG_CONSUME(dd, '_'); if (dd->type) return 0; dd_append_separator(dd, "::"); snprintf(buf, sizeof(buf), "$_%d", n + 1); dd_append(dd, buf); } else { ret = -1; } } else if (islower(c0)) ret = dd_operator_name(dd); else { if (c0 == 'L') dd_consume(dd); /* local-source-name ? */ ret = dd_source_name(dd); } if (dd_curr(dd) == 'B') ret = dd_abi_tag(dd); return ret; } static int dd_nested_name(struct demangle_data *dd) { char qual[] = "rVKRO"; int ret = 0; if (dd_eof(dd)) return -1; DD_DEBUG_CONSUME(dd, 'N'); dd->level++; while (dd_curr(dd) != 'E' && !dd_eof(dd) && !ret) { char c0 = dd_curr(dd); char c1 = dd_peek(dd, 1); if (c0 == 'D' && (c1 == 'T' || c1 == 't')) ret = dd_decltype(dd); else if (c0 == 'C' || c0 == 'D') ret = dd_ctor_dtor_name(dd); else if (c0 == 'U' || islower(c0) || isdigit(c0)) ret = dd_unqualified_name(dd); else if (c0 == 'T') ret = dd_template_param(dd); else if (c0 == 'I') ret = dd_template_args(dd); else if (c0 == 'S') ret = dd_substitution(dd); else if (c0 == 'M') dd_consume(dd); /* assumed data-member-prefix */ else if (c0 == 'L') dd_consume(dd); /* local-source-name ? */ else if (strchr(qual, c0)) dd_qualifier(dd); else break; } __DD_DEBUG_CONSUME(dd, 'E'); dd->level--; return ret; } static int dd_local_name(struct demangle_data *dd) { char c; if (dd_eof(dd)) return -1; DD_DEBUG_CONSUME(dd, 'Z'); dd->level++; dd_encoding(dd); __DD_DEBUG_CONSUME(dd, 'E'); dd->level--; c = dd_curr(dd); if (c == 'd') { __dd_consume(dd, NULL); if (dd_curr(dd) != '_' && dd_number(dd) < 0) return -1; __DD_DEBUG_CONSUME(dd, '_'); if (dd_name(dd) < 0) return -1; return 0; } if (c == 's') __dd_consume(dd, NULL); else dd_name(dd); if (dd_curr(dd) == '_' && !dd->ignore_disc) dd_discriminator(dd); return 0; } static int dd_name(struct demangle_data *dd) { char c = dd_curr(dd); if (dd_eof(dd)) return -1; if (c == 'N') return dd_nested_name(dd); if (c == 'Z') return dd_local_name(dd); if (c == 'S') { if (dd_substitution(dd) < 0) return -1; if (dd_curr(dd) == 'I') return dd_template_args(dd); } if (dd_unqualified_name(dd) < 0) return -1; if (dd_curr(dd) == 'I') return dd_template_args(dd); return 0; } static int dd_encoding(struct demangle_data *dd) { int ret; char c; char end[] = "E.@"; if (dd_eof(dd)) return -1; if (dd->pos == 0) dd_consume_n(dd, 2); /* skip initial "_Z" */ else dd_add_debug(dd); dd->level++; c = dd_curr(dd); if (c == 'T' || c == 'G') { ret = dd_special_name(dd); dd->level--; return ret; } ret = dd_name(dd); if (ret < 0) return ret; while (!dd_eof(dd) && !strchr(end, dd_curr(dd))) { if (dd_type(dd) < 0) break; } /* ignore compiler generated suffix: XXX.part.0 */ if (dd_curr(dd) == '.') dd->len = dd->pos; /* ignore version info in PLT symbols: malloc@GLIBC2.1 */ if (dd_curr(dd) == '@') dd->len = dd->pos; dd->level--; return 0; } /* TODO: implement demangling of Rust v0 mangling */ static char *demangle_rust_v0(char *str) { return xstrdup(str); } static char *demangle_simple(char *str) { struct demangle_data dd = { .old = str, .len = strlen(str), .first_name = true, }; bool has_prefix = false; if (!strncmp(str, "_GLOBAL__sub_I_", 15)) { has_prefix = true; dd.old += 15; dd.len -= 15; } /* a mangled name should start with "_Z" */ if (dd.old[0] != '_' || dd.old[1] != 'Z') { if (dd.old[0] == '_' && dd.old[1] == 'R') return demangle_rust_v0(str); return xstrdup(str); } if (dd_encoding(&dd) < 0 || dd.level != 0) { dd_debug_print(&dd); free(dd.new); return xstrdup(str); } if (!dd_eof(&dd)) { if (!dd.type_info || dd_name(&dd) < 0) { dd_debug_print(&dd); free(dd.new); return xstrdup(str); } } if (has_prefix) { char *p = NULL; xasprintf(&p, "_GLOBAL__sub_I_%s", dd.new); free(dd.new); dd.new = p; } /* caller should free it */ return dd.new; } #ifdef HAVE_CXA_DEMANGLE static char *demangle_full(char *str) { char *symname; size_t len = 64; /* minimum length */ int status; /* str is not mangled C++ symbol */ if (str[0] != '_' || str[1] != 'Z') return xstrdup(str); __cxa_demangle(str, NULL, &len, &status); if (status < 0) return xstrdup(str); symname = xmalloc(len); __cxa_demangle(str, symname, &len, &status); return symname; } #endif /** * demangle - demangle if given @str is a mangled C++ symbol name * @str: symbol name * * This function returns malloc-ed string of demangled name of @str. * If @str is not a C++ symbol or @demangler global variable is set to * #DEMANGLE_NONE, the returned string is same as the input string. * If @demangler is #DEMANGLE_SIMPLE, it'd be demangled using our own * implementation. If @demangler is #DEMANGLE_FULL, it'd be demangled * using libstdc++ (if available). */ char *demangle(char *str) { if (str == NULL) return NULL; switch (demangler) { case DEMANGLE_SIMPLE: return demangle_simple(str); case DEMANGLE_FULL: return demangle_full(str); case DEMANGLE_NONE: return xstrdup(str); default: pr_dbg("demangler error\n"); return xstrdup(str); } } #ifdef UNIT_TEST #define DEMANGLE_TEST(m, d) \ do { \ char *name = demangle_simple(m); \ pr_dbg("%.64s should be converted to %s\n", m, d); \ TEST_STREQ(d, name); \ free(name); \ } while (0) TEST_CASE(demangle_simple1) { DEMANGLE_TEST("normal", "normal"); DEMANGLE_TEST("_ZN3ABC3fooEv", "ABC::foo"); DEMANGLE_TEST("_ZN3ABCC1Ei", "ABC::ABC"); DEMANGLE_TEST("_Znwm", "operator new"); DEMANGLE_TEST("_ZN2ns3ns13foo4bar1Ev", "ns::ns1::foo::bar1"); return TEST_OK; } TEST_CASE(demangle_simple2) { DEMANGLE_TEST("_ZThn8_N13FtraceServiceD0Ev", "FtraceService::~FtraceService"); DEMANGLE_TEST("_ZN2v88internal12ScopedVectorIcEC1Ei", "v8::internal::ScopedVector::ScopedVector"); DEMANGLE_TEST("_ZNSt16allocator_traitsISaISt13_Rb_tree_node" "ISt4pairIKSsN7pbnjson7JSchemaEEEEE9construct" "IS6_IS1_ISsS4_EEEEDTcl12_S_constructfp_fp0_" "spcl7forwardIT0_Efp1_EEERS7_PT_DpOSB_", "std::allocator_traits::construct"); return TEST_OK; } TEST_CASE(demangle_simple3) { DEMANGLE_TEST("_ZN4node8Watchdog7DestroyEv.part.0", "node::Watchdog::Destroy"); DEMANGLE_TEST("_ZN2v88internal8CodeStub6GetKeyEv.constprop.17", "v8::internal::CodeStub::GetKey"); DEMANGLE_TEST("_ZSteqIPN2v88internal8compiler4NodeERKS4_PS5_E" "bRKSt15_Deque_iteratorIT_T0_T1_ESE_", "std::operator=="); DEMANGLE_TEST("_ZN2v84base8internalmlIiiEENS1_14CheckedNumeric" "INS1_19ArithmeticPromotionIT_T0_XqugtsrNS1_" "11MaxExponentIS5_EE5valuesrNS7_IS6_EE5value" "qugtsrS8_5valueL_ZNS7_IiE5valueEELNS1_" "27ArithmeticPromotionCategoryE0ELSB_2E" "qugtsrS9_5valueL_ZNSA_5valueEELSB_1ELSB_2EEE" "4typeEEERKNS3_IS5_EES6_", "v8::base::internal::operator*"); DEMANGLE_TEST("_ZSt3powIidEN9__gnu_cxx11__promote_2IT_T0_NS0_" "9__promoteIS2_XsrSt12__is_integerIS2_E7__valueEE" "6__typeENS4_IS3_XsrS5_IS3_E7__valueEE6__typeEE" "6__typeES2_S3_", "std::pow"); return TEST_OK; } TEST_CASE(demangle_simple4) { DEMANGLE_TEST("_ZSt9__find_ifISt14_List_iteratorISt10shared_ptr" "I16AppLaunchingItemEEZN13MemoryChecker8add_itemE" "S1_I13LaunchingItemEEUlS7_E_ET_S9_S9_T0_" "St18input_iterator_tag", "std::__find_if"); DEMANGLE_TEST("_ZZ19convertToWindowTypeRKSsRSsENUt_D1Ev", "convertToWindowType::~convertToWindowType"); DEMANGLE_TEST("_ZNSt3setISsSt4lessISsESaISsEE5eraseB5cxx11E" "St23_Rb_tree_const_iteratorISsE", "std::set::erase::cxx11"); DEMANGLE_TEST("_ZNSt16allocator_traitsISaISsEE9_S_select" "IKS0_EENSt9enable_ifIXntsrNS1_15__select_helper" "IT_EE5valueES6_E4typeERS6_", "std::allocator_traits::_S_select"); DEMANGLE_TEST("_ZN6icu_5416umtx_loadAcquireERU7_Atomici", "icu_54::umtx_loadAcquire"); return TEST_OK; } TEST_CASE(demangle_simple5) { DEMANGLE_TEST("_ZN2v88internal13RememberedSetILNS0_" "16PointerDirectionE1EE7IterateIZNS3_" "18IterateWithWrapperIPFvPPNS0_10HeapObjectE" "S7_EEEvPNS0_4HeapET_EUlPhE_EEvSC_SD_", "v8::internal::RememberedSet::Iterate"); DEMANGLE_TEST("_ZN2v88internal7SlotSet7Iterate" "IZNS0_13RememberedSetILNS0_16PointerDirectionE" "1EE18IterateWithWrapperIPFvPPNS0_10HeapObjectE" "S8_EEEvPNS0_4HeapET_EUlPhE_EEiSE_", "v8::internal::SlotSet::Iterate"); DEMANGLE_TEST("_ZNSt5tupleIJPbSt14default_deleteIA_bEEEC2Ev", "std::tuple::tuple"); DEMANGLE_TEST("_Z26storageIndexFromLayoutItemRK" "N51_GLOBAL__N_kernel_qformlayout.cpp_C3DE8A26_2E30FA86" "17FixedColumnMatrixIP15QFormLayoutItemLi2EEES2_", "storageIndexFromLayoutItem"); DEMANGLE_TEST("_ZGTtNSt11range_errorD1Ev", "std::range_error::~range_error"); DEMANGLE_TEST("_ZNSi6ignoreEl@@GLIBCXX_3.4.5", "std::basic_istream::ignore"); DEMANGLE_TEST("_ZN4llvm12function_refIFN5clang12ActionResult" "IPNS1_4ExprELb1EEES4_EE11callback_fnIZNS1_4Sema" "25CorrectDelayedTyposInExprES4_PNS1_7VarDeclE" "S7_Ed_NUlS4_E_EEES5_lS4_", "llvm::function_ref::callback_fn"); return TEST_OK; } TEST_CASE(demangle_simple6) { DEMANGLE_TEST("_ZN4base8internal15OptionalStorageImLb1ELb1EE" "CI2NS0_19OptionalStorageBaseImLb1EEEIJRKmEEE" "NS_10in_place_tEDpOT_", "base::internal::OptionalStorage::OptionalStorage"); DEMANGLE_TEST("_ZL18color_lookup_tableILi3EEv" "PK28SkJumper_ColorLookupTableCtx" "RDv4_fS4_S4_S3_Dv4_jS5_", "color_lookup_table"); DEMANGLE_TEST("_ZTWN6__xray19__xray_fdr_internal7RunningE", "TLS_wrap::__xray::__xray_fdr_internal::Running"); return TEST_OK; } TEST_CASE(demangle_simple7) { DEMANGLE_TEST("_ZTSSt12system_error", "__typeinfo__std::system_error"); DEMANGLE_TEST("_ZNSs4nposE", "std::basic_string<>::npos"); DEMANGLE_TEST("_ZNSt14numeric_limitsIoE5radixE", "std::numeric_limits::radix"); DEMANGLE_TEST("_ZGVNSt7__cxx117collateIcE2idE", "__guard_variable__std::__cxx11::collate::id"); DEMANGLE_TEST("_ZNSbIwSt11char_traitsIwESaIwEE4nposE", "std::basic_string::npos"); return TEST_OK; } TEST_CASE(demangle_simple8) { DEMANGLE_TEST("_ZTV23SkCanvasVirtualEnforcerI8SkCanvasE", "__vtable__SkCanvasVirtualEnforcer"); DEMANGLE_TEST("_ZZNK13SkImageShader14onAppendStagesE" "RKN12SkShaderBase8StageRecEENK3$_0clEv", "SkImageShader::onAppendStages::$_0::operator()"); DEMANGLE_TEST("_ZTCN2v88internal12StdoutStreamE0_NS0_8OFStreamE", "__construction_vtable__v8::internal::StdoutStream"); DEMANGLE_TEST("_ZGRZNK5blink8Variable27GetPropertyNameAtomicStringEvE4name_", "__ref_temp__blink::Variable::GetPropertyNameAtomicString::name"); DEMANGLE_TEST("_ZNSt14numeric_limitsIDuE8is_exactE", "std::numeric_limits::is_exact"); DEMANGLE_TEST("_ZTCSt10istrstream0_Si", "__construction_vtable__std::istrstream"); DEMANGLE_TEST( "_ZTCNSt7__cxx1119basic_istringstreamIwSt11char_traitsIwESaIwEEE0_St13basic_istreamIwS2_E", "__construction_vtable__std::__cxx11::basic_istringstream::std::std::allocator"); return TEST_OK; } TEST_CASE(demangle_rust1) { DEMANGLE_TEST("_ZN8$BP$test3fooE", "*test::foo"); DEMANGLE_TEST("_ZN35Bar$LT$$u5b$u32$u3b$$u20$4$u5d$$GT$E", "Bar<[u32; 4]>"); DEMANGLE_TEST("_ZN71_$LT$Test$u20$$u2b$$u20$$u27$static" "$u20$as$u20$foo..Bar$LT$Test$GT$$GT$3barE", "_::bar"); DEMANGLE_TEST("_ZN3foo3bar17h05af221e174051e9E", "foo::bar"); DEMANGLE_TEST( "_ZN61_$LT$$RF$std..io..stdio..Stdout$u20$as$u20$std..io..Write$GT$9write_fmt17h75c561f414a62159E", "_<&std::io::stdio::Stdout>::write_fmt"); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/dwarf.c000066400000000000000000001611751455365734300156710ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "dwarf" #define PR_DOMAIN DBG_DWARF #include "mcount-arch.h" #include "uftrace.h" #include "utils/dwarf.h" #include "utils/filter.h" #include "utils/symbol.h" #include "utils/utils.h" bool debug_info_has_argspec(struct uftrace_dbg_info *dinfo) { if (dinfo == NULL) return false; /* dinfo has some debug entries? */ return !RB_EMPTY_ROOT(&dinfo->args) || !RB_EMPTY_ROOT(&dinfo->rets); } bool debug_info_has_location(struct uftrace_dbg_info *dinfo) { if (dinfo == NULL) return false; /* dinfo has some debug entries? */ return dinfo->nr_locs_used; } struct debug_entry { struct rb_node node; uint64_t offset; char *name; char *spec; }; static int add_debug_entry(struct rb_root *root, char *func, uint64_t offset, char *argspec) { struct debug_entry *entry, *iter; struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; pr_dbg3("add debug entry: %" PRIx64 " %s%s\n", offset, func, argspec); while (*p) { parent = *p; iter = rb_entry(parent, struct debug_entry, node); if (unlikely(iter->offset == offset)) { pr_dbg3("debug entry: conflict!\n"); /* mark it broken by using NULL spec */ free(iter->spec); iter->spec = NULL; return 0; } if (iter->offset > offset) p = &parent->rb_left; else p = &parent->rb_right; } entry = xmalloc(sizeof(*entry)); entry->name = xstrdup(func); entry->spec = xstrdup(argspec); entry->offset = offset; rb_link_node(&entry->node, parent, p); rb_insert_color(&entry->node, root); return 0; } static struct debug_entry *find_debug_entry(struct rb_root *root, uint64_t offset) { struct debug_entry *iter; struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; int ret; while (*p) { parent = *p; iter = rb_entry(parent, struct debug_entry, node); ret = iter->offset - offset; if (ret == 0) { pr_dbg3("found debug entry at %" PRIx64 " (%s%s)\n", offset, iter->name, iter->spec); return iter; } if (ret > 0) p = &parent->rb_left; else p = &parent->rb_right; } return NULL; } static void free_debug_entry(struct rb_root *root) { struct debug_entry *entry; struct rb_node *node; while (!RB_EMPTY_ROOT(root)) { node = rb_first(root); entry = rb_entry(node, typeof(*entry), node); rb_erase(node, root); free(entry->name); free(entry->spec); free(entry); } } static struct uftrace_dbg_file *get_debug_file(struct uftrace_dbg_info *dinfo, const char *filename) { struct uftrace_dbg_file *df; struct rb_node *parent = NULL; struct rb_node **p = &dinfo->files.rb_node; int ret; if (filename == NULL) return NULL; while (*p) { parent = *p; df = rb_entry(parent, struct uftrace_dbg_file, node); ret = strcmp(df->name, filename); if (ret == 0) return df; if (ret < 0) p = &parent->rb_left; else p = &parent->rb_right; } df = xmalloc(sizeof(*df)); df->name = xstrdup(filename); rb_link_node(&df->node, parent, p); rb_insert_color(&df->node, &dinfo->files); return df; } static void release_debug_file(struct rb_root *root) { struct uftrace_dbg_file *df; struct rb_node *node; while (!RB_EMPTY_ROOT(root)) { node = rb_first(root); df = rb_entry(node, typeof(*df), node); rb_erase(node, root); free(df->name); free(df); } } #ifdef HAVE_LIBDW #include #include #include /* * symbol table contains normalized (zero-based) relative address. * but some other info in non-PIE executable has different base * address so it needs to convert back and forth. */ static inline unsigned long sym_to_dwarf_addr(struct uftrace_dbg_info *dinfo, unsigned long addr) { if (dinfo->file_type == ET_EXEC) addr += dinfo->offset; return addr; } static inline unsigned long dwarf_to_sym_addr(struct uftrace_dbg_info *dinfo, unsigned long addr) { if (dinfo->file_type == ET_EXEC) addr -= dinfo->offset; return addr; } struct cu_files { Dwarf_Files *files; size_t num; /* number of files */ }; static int elf_file_type(struct uftrace_dbg_info *dinfo) { GElf_Ehdr ehdr; if (dinfo->dw && gelf_getehdr(dwarf_getelf(dinfo->dw), &ehdr)) return ehdr.e_type; return ET_NONE; } static bool get_attr(Dwarf_Die *die, int attr, bool follow, Dwarf_Attribute *da) { if (follow) { if (!dwarf_hasattr_integrate(die, attr)) return false; dwarf_attr_integrate(die, attr, da); } else { if (!dwarf_hasattr(die, attr)) return false; dwarf_attr(die, attr, da); } return true; } static long int_attr(Dwarf_Die *die, int attr, bool follow) { Dwarf_Attribute da; Dwarf_Sword data; if (!get_attr(die, attr, follow, &da)) return 0; dwarf_formsdata(&da, &data); switch (dwarf_whatform(&da)) { case DW_FORM_data1: data &= 0xff; break; case DW_FORM_data2: data &= 0xffff; break; case DW_FORM_data4: data &= 0xffffffff; break; default: break; } return data; } static char *str_attr(Dwarf_Die *die, int attr, bool follow) { Dwarf_Attribute da; if (!get_attr(die, attr, follow, &da)) return NULL; return (char *)dwarf_formstring(&da); } /* setup dwarf info from filename, return 0 for success */ static int setup_dwarf_info(const char *filename, struct uftrace_dbg_info *dinfo, unsigned long offset, bool force) { int fd; if (force || check_trace_functions(filename) != TRACE_CYGPROF) dinfo->needs_args = true; pr_dbg2("setup dwarf debug info for %s\n", filename); fd = open(filename, O_RDONLY); if (fd < 0) { pr_dbg2("cannot open debug info for %s: %m\n", filename); return -1; } dinfo->dw = dwarf_begin(fd, DWARF_C_READ); close(fd); if (dinfo->dw == NULL) { pr_dbg2("failed to setup debug info: %s\n", dwarf_errmsg(dwarf_errno())); return -1; } pr_dbg2("setup dwarf debug info for %s\n", filename); /* * symbol table already uses relative address but non-PIE * executable needs to use absolute address for DWARF info. * Also as filter entry uses absolute address, it needs to * keep the offset to recover relative address back. */ dinfo->offset = offset; dinfo->file_type = elf_file_type(dinfo); return 0; } static void release_dwarf_info(struct uftrace_dbg_info *dinfo) { if (dinfo->dw == NULL) return; dwarf_end(dinfo->dw); dinfo->dw = NULL; } #define ARGSPEC_MAX_SIZE 256 #define MAX_STRUCT_REGS 4 /* arg_data contains argument passing info for single function */ struct arg_data { /* name of the function (symbol name) */ const char *name; /* (result) argspec, should be freed after used */ char *argspec; /* (normal) argument index */ int idx; /* floating-point argument index */ int fpidx; /* arg format of the last argument */ int last_fmt; /* arg size (in byte) of the last argument */ int last_size; /* number of available core registers */ int reg_max; /* number of available FP registers */ int fpreg_max; /* index of next core register to be used */ int reg_pos; /* index of next FP register to be used */ int fpreg_pos; /* position of next available stack */ int stack_ofs; /* whether we have retspec for this function */ bool has_retspec; /* argument info parsing failed or unsupported */ bool broken; /* struct is passed by value, location needs update */ bool struct_passed; /* struct passed-by-value will be replaced to a pointer */ bool struct_arg_needs_ptr; /* struct passed-by-value will be replaced to a pointer */ bool struct_return_needs_ptr; /* struct containing FP types will use FP registers */ bool struct_uses_fpreg; /* pass class via stack if it has a MEMORY class member */ bool has_mem_class; /* * class containing non-trivial copy constructor or destructor, or * virtual functions will be passed by a invisible reference. */ bool class_via_ptr; /* struct_param_class if argument is struct and passed by value */ char struct_regs[MAX_STRUCT_REGS]; /* number of registers used above */ int struct_reg_cnt; /* uftrace debug info */ struct uftrace_dbg_info *dinfo; }; static void setup_arg_data(struct arg_data *ad, const char *name, struct uftrace_dbg_info *dinfo) { memset(ad, 0, sizeof(*ad)); ad->name = name; ad->dinfo = dinfo; switch (host_cpu_arch()) { case UFT_CPU_X86_64: ad->reg_max = 6; ad->fpreg_max = 8; ad->struct_uses_fpreg = true; ad->struct_return_needs_ptr = true; ad->class_via_ptr = true; ad->has_mem_class = true; break; case UFT_CPU_AARCH64: ad->reg_max = 8; ad->fpreg_max = 8; ad->struct_arg_needs_ptr = true; /* struct return will use 'x8' register */ break; case UFT_CPU_RISCV64: ad->reg_max = 8; ad->fpreg_max = 8; ad->struct_arg_needs_ptr = true; ad->struct_return_needs_ptr = true; ad->struct_uses_fpreg = true; break; default: /* TODO */ ad->broken = true; break; } } /* struct parameter class to determine argument passing method */ enum struct_param_class { PARAM_CLASS_NONE = 0, PARAM_CLASS_MEM = 'm', PARAM_CLASS_INT = 'i', PARAM_CLASS_FP = 'f', PARAM_CLASS_PTR = 'p', }; /* type_data contains info about single argument */ struct type_data { struct arg_data *arg_data; enum uftrace_arg_format fmt; size_t size; /* in bit */ int pointer; bool ignore; bool broken; char *name; }; static char *fill_enum_str(Dwarf_Die *die) { char *str = NULL; Dwarf_Die e_val; if (dwarf_child(die, &e_val) != 0) goto out; do { if (dwarf_tag(&e_val) == DW_TAG_enumerator) { char buf[256]; Dwarf_Sword val; val = int_attr(&e_val, DW_AT_const_value, false); snprintf(buf, sizeof(buf), "%s=%ld", dwarf_diename(&e_val), (long)val); str = strjoin(str, buf, ","); } } while (dwarf_siblingof(&e_val, &e_val) == 0); out: if (str == NULL) pr_dbg2("no enum values\n"); return str; } static char *make_enum_name(Dwarf_Die *die) { Dwarf_Die cudie; const char *cu_name = NULL; unsigned long off; char *enum_name; char *tmp; if (dwarf_diecu(die, &cudie, NULL, NULL)) cu_name = dwarf_diename(&cudie); if (cu_name == NULL) cu_name = "unnamed"; off = dwarf_cuoffset(die); xasprintf(&enum_name, "_%s_%lx", basename(cu_name), off); /* replace forbidden characters */ tmp = enum_name; while ((tmp = strpbrk(tmp, "+-.()<> ")) != NULL) *tmp++ = '_'; return enum_name; } /* returns size in bit */ static size_t type_size(Dwarf_Die *die, size_t default_size) { Dwarf_Word size; /* require >= elfutils 0.144 */ if (dwarf_aggregate_size(die, &size) >= 0) return size * 8; /* just guess it's word size */ size = dwarf_bytesize(die); if ((long)size <= 0) size = default_size; return size * 8; } static bool is_empty_aggregate(Dwarf_Die *die) { Dwarf_Die child; Dwarf_Die parent; bool inherited = false; /* C++ defines size of an empty struct as 1 byte */ if (type_size(die, sizeof(long)) > 1 * 8) return false; retry: if (dwarf_child(die, &child) != 0) return true; /* no child = no member */ do { Dwarf_Attribute type; Dwarf_Die type_die; switch (dwarf_tag(&child)) { case DW_TAG_member: if (dwarf_attr(die, DW_AT_type, &type) == NULL) return false; if (dwarf_formref_die(&type, &type_die) == NULL) return false; return is_empty_aggregate(&type_die); case DW_TAG_subprogram: /* probably a lambda function */ return false; case DW_TAG_inheritance: dwarf_attr(&child, DW_AT_type, &type); dwarf_formref_die(&type, &parent); inherited = true; break; default: break; } } while (dwarf_siblingof(&child, &child) == 0); if (inherited) { inherited = false; die = &parent; goto retry; } return true; } /* param_data contains addition info about a struct passed by value */ struct param_data { /* position in byte, in case two or more fields are merged into one */ int pos; /* maximum size to pass an argument in registers */ unsigned max_struct_size; /* maximum allowed register count */ int reg_max; /* current allocated register count */ int reg_cnt; /* allocated register classes */ char regs[MAX_STRUCT_REGS]; /* if it's set, FP registers are allowed */ bool use_fpregs; /* check member name (for std::string detection) */ bool lookup_string; /* previous (or current) register class in case of merge */ int prev_class; }; static void setup_param_data(struct param_data *data) { memset(data, 0, sizeof(*data)); switch (host_cpu_arch()) { case UFT_CPU_X86_64: /* TODO: check availability of __m256 type */ data->max_struct_size = 16 * 8; data->use_fpregs = true; break; case UFT_CPU_AARCH64: data->max_struct_size = 16 * 8; break; case UFT_CPU_RISCV64: data->max_struct_size = 16 * 8; data->use_fpregs = true; break; default: /* TODO */ break; } data->reg_max = data->max_struct_size / 64; } static int get_param_class(Dwarf_Die *die, struct arg_data *ad, struct param_data *pd) { Dwarf_Die ref; Dwarf_Attribute type; unsigned aform; const char *tname; int size; int this_class; while (dwarf_hasattr(die, DW_AT_type)) { dwarf_attr(die, DW_AT_type, &type); aform = dwarf_whatform(&type); switch (aform) { case DW_FORM_ref1: case DW_FORM_ref2: case DW_FORM_ref4: case DW_FORM_ref8: case DW_FORM_ref_udata: case DW_FORM_ref_addr: case DW_FORM_ref_sig8: dwarf_formref_die(&type, &ref); die = &ref; break; default: pr_dbg2("unhandled type form: %u\n", aform); return PARAM_CLASS_MEM; } switch (dwarf_tag(die)) { case DW_TAG_pointer_type: case DW_TAG_ptr_to_member_type: case DW_TAG_reference_type: case DW_TAG_rvalue_reference_type: /* align start address (TODO: handle packed struct) */ if (pd->pos % sizeof(long)) pd->reg_cnt++; pd->pos = ROUND_UP(pd->pos, sizeof(long)); if (pd->reg_cnt >= pd->reg_max) return PARAM_CLASS_MEM; pd->pos += sizeof(long); pd->regs[pd->reg_cnt++] = PARAM_CLASS_INT; return PARAM_CLASS_INT; case DW_TAG_structure_type: case DW_TAG_union_type: case DW_TAG_class_type: /* TODO */ return PARAM_CLASS_MEM; case DW_TAG_array_type: /* TODO */ break; case DW_TAG_enumeration_type: return PARAM_CLASS_INT; case DW_TAG_base_type: tname = dwarf_diename(die); /* make 'size' in byte (by dividing by 8 ) */ size = type_size(die, sizeof(int)) / 8; if (size == 0) size = 1; /* align start address (TODO: handle packed struct) */ if (pd->pos % size) { if ((pd->pos % sizeof(long)) + size >= sizeof(long)) { pd->reg_cnt++; pd->prev_class = PARAM_CLASS_NONE; } } pd->pos = ROUND_UP(pd->pos, size); if (pd->reg_cnt >= pd->reg_max) return PARAM_CLASS_MEM; if (!strcmp(tname, "double") && pd->use_fpregs) { pd->regs[pd->reg_cnt++] = PARAM_CLASS_FP; pd->pos += sizeof(double); pd->prev_class = PARAM_CLASS_NONE; return PARAM_CLASS_FP; } else if (!strcmp(tname, "float") && pd->use_fpregs) { /* if it's already "int", don't change */ if (pd->prev_class != PARAM_CLASS_INT) pd->prev_class = PARAM_CLASS_FP; } else { /* default to integer class */ pd->prev_class = PARAM_CLASS_INT; } this_class = pd->prev_class; pd->regs[pd->reg_cnt] = this_class; if ((pd->pos % sizeof(long)) + size >= sizeof(long)) { pd->reg_cnt++; pd->prev_class = PARAM_CLASS_NONE; } pd->pos += size; return this_class == PARAM_CLASS_INT ? this_class : PARAM_CLASS_FP; default: break; } } return PARAM_CLASS_MEM; } static void place_struct_members(Dwarf_Die *die, struct arg_data *ad, struct type_data *td) { Dwarf_Die child; int param_class = PARAM_CLASS_NONE; struct param_data pd; int i, reg_cnt = 0, fp_cnt = 0; const char *sname; bool found_mem_class = false; bool check_class_ptr_only = false; setup_param_data(&pd); ad->struct_reg_cnt = 0; ad->struct_passed = true; sname = dwarf_diename(die); if (sname) { char *p; td->name = xstrdup(sname); /* remove long C++ type name to prevent confusion */ p = strpbrk(td->name + 1, "< ({["); if (p) *p = '\0'; if (!strcmp(td->name, "basic_string")) pd.lookup_string = true; } if (dwarf_child(die, &child) != 0) return; /* no child = no member */ if (td->size > pd.max_struct_size && !pd.lookup_string) { if (ad->class_via_ptr) check_class_ptr_only = true; else goto pass_via_stack; } do { switch (dwarf_tag(&child)) { case DW_TAG_member: if (!check_class_ptr_only) { param_class = get_param_class(&child, ad, &pd); if (param_class == PARAM_CLASS_MEM) found_mem_class = true; if (!pd.lookup_string) break; sname = dwarf_diename(&child); if (sname && !strcmp(sname, "_M_dataplus")) { td->fmt = ARG_FMT_STD_STRING; td->size = sizeof(long) * 8; return; } } break; case DW_TAG_inheritance: /* TODO */ break; case DW_TAG_subprogram: /* * FIXME: assume pass via stack if it has a (probably * non-trivial) destructor or a virtual function */ if (!ad->class_via_ptr) break; if (ad->reg_pos >= ad->reg_max) goto pass_via_stack; sname = dwarf_diename(&child); if ((sname && sname[0] == '~') || dwarf_hasattr(die, DW_AT_virtuality)) { pr_dbg3("non-trivial class passed via pointer\n"); ad->struct_regs[0] = PARAM_CLASS_PTR; ad->struct_reg_cnt = 1; return; } break; default: break; } } while (dwarf_siblingof(&child, &child) == 0); if (td->size > pd.max_struct_size) goto pass_via_stack; if (ad->has_mem_class && found_mem_class) goto pass_via_stack; if (pd.pos % sizeof(long)) pd.reg_cnt++; for (i = 0; i < pd.reg_cnt; i++) { if (pd.regs[i] == PARAM_CLASS_FP) fp_cnt++; else reg_cnt++; } if (ad->reg_pos + reg_cnt > ad->reg_max) goto pass_via_stack; if (ad->fpreg_pos + fp_cnt > ad->fpreg_max) goto pass_via_stack; memcpy(ad->struct_regs, pd.regs, sizeof(pd.regs)); ad->struct_reg_cnt = pd.reg_cnt; return; pass_via_stack: pr_dbg3("struct passed via stack: size = %zd bytes\n", td->size / 8); ad->struct_reg_cnt = 0; if (((ad->has_retspec && ad->struct_return_needs_ptr) || (!ad->has_retspec && ad->struct_arg_needs_ptr)) && ad->reg_pos < ad->reg_max) { ad->struct_regs[ad->struct_reg_cnt++] = PARAM_CLASS_PTR; } } static bool resolve_type_info(Dwarf_Die *die, struct arg_data *ad, struct type_data *td) { Dwarf_Die ref; Dwarf_Attribute type; unsigned aform; const char *tname; char *enum_def; char *enum_str; /* * type refers to another type in a chain like: * (pointer) -> (const) -> (char) */ while (dwarf_hasattr(die, DW_AT_type)) { dwarf_attr(die, DW_AT_type, &type); aform = dwarf_whatform(&type); switch (aform) { case DW_FORM_ref1: case DW_FORM_ref2: case DW_FORM_ref4: case DW_FORM_ref8: case DW_FORM_ref_udata: case DW_FORM_ref_addr: case DW_FORM_ref_sig8: dwarf_formref_die(&type, &ref); die = &ref; break; default: pr_dbg2("unhandled type form: %u\n", aform); return false; } switch (dwarf_tag(die)) { case DW_TAG_enumeration_type: enum_str = fill_enum_str(die); if (enum_str == NULL) return false; /* use default format */ td->fmt = ARG_FMT_ENUM; tname = dwarf_diename(die); if (tname && (isalpha(*tname) || *tname == '_')) td->name = xstrdup(tname); else td->name = make_enum_name(die); xasprintf(&enum_def, "enum %s { %s }", td->name, enum_str); pr_dbg3("type: %s\n", enum_str); td->size = type_size(die, sizeof(int)); parse_enum_string(enum_def, &td->arg_data->dinfo->enums); free(enum_def); free(enum_str); return true; case DW_TAG_structure_type: case DW_TAG_union_type: case DW_TAG_class_type: /* ignore struct with no member (when called-by-value) */ if (td->pointer) break; td->fmt = ARG_FMT_STRUCT; if (is_empty_aggregate(die)) td->size = 0; else td->size = type_size(die, sizeof(long)); place_struct_members(die, ad, td); pr_dbg3("type: struct/union/class: %s\n", td->name ?: "(no name)"); return true; case DW_TAG_pointer_type: case DW_TAG_ptr_to_member_type: case DW_TAG_reference_type: case DW_TAG_rvalue_reference_type: td->pointer++; pr_dbg3("type: pointer/reference\n"); break; case DW_TAG_array_type: pr_dbg3("type: array\n"); break; case DW_TAG_const_type: pr_dbg3("type: const\n"); break; case DW_TAG_subroutine_type: if (td->pointer == 1) { td->fmt = ARG_FMT_PTR; pr_dbg3("type: function pointer\n"); /* prevent to look up (return) type more */ return true; } break; default: pr_dbg3("type: %s (tag %d)\n", dwarf_diename(die), dwarf_tag(die)); break; } } tname = dwarf_diename(die); if (td->pointer) { td->size = sizeof(long) * 8; /* treat 'char *' as string */ if (td->pointer == 1 && tname && !strcmp(tname, "char")) td->fmt = ARG_FMT_STR; else td->fmt = ARG_FMT_PTR; return true; } td->size = type_size(die, sizeof(long)); if (dwarf_tag(die) != DW_TAG_base_type) return false; if (!strcmp(tname, "char")) td->fmt = ARG_FMT_CHAR; else if (!strcmp(tname, "float")) td->fmt = ARG_FMT_FLOAT; else if (!strcmp(tname, "double")) td->fmt = ARG_FMT_FLOAT; else if (!strcmp(tname, "long double")) { td->fmt = ARG_FMT_FLOAT; td->size = 80; } return true; } static bool add_type_info(char *spec, size_t len, Dwarf_Die *die, struct arg_data *ad) { struct type_data data = { .fmt = ARG_FMT_AUTO, .arg_data = ad, }; Dwarf_Die origin; if (!dwarf_hasattr(die, DW_AT_type)) { Dwarf_Attribute attr; if (!dwarf_hasattr(die, DW_AT_abstract_origin)) return false; dwarf_attr(die, DW_AT_abstract_origin, &attr); dwarf_formref_die(&attr, &origin); die = &origin; } if (!resolve_type_info(die, ad, &data)) { ad->broken = data.broken; return false; } ad->last_fmt = data.fmt; ad->last_size = data.size / 8; switch (data.fmt) { case ARG_FMT_CHAR: strcat(spec, "/c"); break; case ARG_FMT_STR: if (!ad->broken) strcat(spec, "/s"); break; case ARG_FMT_STD_STRING: strcat(spec, "/S"); break; case ARG_FMT_FLOAT: if (ad->idx) { /* for arguments */ snprintf(spec, len, "fparg%d/%zu", ++ad->fpidx, data.size); /* do not increase index of integer arguments */ --ad->idx; } else { /* for return values */ char sz[16]; snprintf(sz, sizeof(sz), "%d", (int)data.size); strcat(spec, "/f"); strcat(spec, sz); } break; case ARG_FMT_PTR: strcat(spec, "/p"); break; case ARG_FMT_ENUM: strcat(spec, "/e:"); strcat(spec, data.name); break; case ARG_FMT_STRUCT: if (ad->idx) { /* for arguments */ snprintf(spec, len, "arg%d/t%d", ad->idx, ad->last_size); } else { /* for return valus */ char sz[16]; snprintf(sz, sizeof(sz), "/t%d", ad->last_size); strcat(spec, sz); } if (data.name) { int len1 = strlen(spec) + 1; int len2 = strlen(data.name); strcat(spec, ":"); if (len1 + len2 >= ARGSPEC_MAX_SIZE) { strncat(spec, data.name, ARGSPEC_MAX_SIZE - len1 - 1); spec[ARGSPEC_MAX_SIZE - 1] = '\0'; } else { strcat(spec, data.name); } } break; default: break; } free(data.name); return true; } struct location_data { int type; int reg; // DWARF register number int offset; // stack offset }; static bool get_arg_location(Dwarf_Die *die, struct location_data *ld) { Dwarf_Attribute loc; Dwarf_Op *ops = NULL; size_t len = 0; if (!dwarf_hasattr(die, DW_AT_location)) return false; dwarf_attr(die, DW_AT_location, &loc); if (dwarf_getlocation(&loc, &ops, &len) == -1) { int (*get_location_list)(Dwarf_Attribute * loc, Dwarf_Off offset, Dwarf_Addr * base, Dwarf_Addr * start, Dwarf_Addr * end, Dwarf_Op * *ops, size_t * len); Dwarf_Addr base, start, end; get_location_list = dlsym(RTLD_DEFAULT, "dwarf_getlocations"); if (get_location_list == NULL) return false; /* try to get the first entry in the location list */ if (get_location_list(&loc, 0, &base, &start, &end, &ops, &len) == -1) return false; } while (len--) { switch (ops->atom) { case DW_OP_fbreg: /* * ignore minus offsets since it doesn't set the * frame-pointer yet (we're before the prologue). */ if ((int)ops->number >= 0) { ld->type = ARG_TYPE_STACK; ld->offset = DIV_ROUND_UP(ops->number, sizeof(long)) + 1; pr_dbg3("location: stack (%d)\n", ld->offset); } break; case DW_OP_reg0 ... DW_OP_reg31: ld->type = ARG_TYPE_REG; ld->reg = ops->atom; pr_dbg3("location: reg (%d)\n", ld->reg); break; case DW_OP_regx: ld->type = ARG_TYPE_REG; ld->reg = ops->number; pr_dbg3("location: reg (%d)\n", ld->reg); break; default: pr_dbg3("unsupported exprloc (%d)\n", ops->atom); break; } } return true; } static void add_location(char *spec, size_t len, Dwarf_Die *die, struct arg_data *ad) { struct location_data data = { .type = ARG_TYPE_INDEX, }; char buf[32]; const char *reg = NULL; enum uftrace_cpu_arch arch = host_cpu_arch(); int i; get_arg_location(die, &data); /* * If a struct argument was passed by value, all the remaining arguments * need to have specific location info because the index-based location * would be incorrect. */ if (ad->struct_passed && data.type == ARG_TYPE_INDEX) { switch (ad->last_fmt) { case ARG_FMT_STRUCT: if (ad->struct_reg_cnt == 0) break; /* * If struct_reg_cnt is set, it's guaranteed * that enough registers are ready. */ for (i = 0; i < ad->struct_reg_cnt; i++) { int param = ad->struct_regs[i]; if (param == PARAM_CLASS_INT || param == PARAM_CLASS_PTR) { reg = arch_register_argspec_name(arch, true, ad->reg_pos); ad->reg_pos++; } else { reg = arch_register_argspec_name(arch, false, ad->fpreg_pos); ad->fpreg_pos++; } snprintf(buf, sizeof(buf), "%s%s", i ? "+" : "%", reg); strcat(spec, buf); } /* we are done now */ return; case ARG_FMT_FLOAT: if (ad->fpreg_pos < ad->fpreg_max) { reg = arch_register_argspec_name(arch, false, ad->fpreg_pos); ad->fpreg_pos++; } break; default: if (ad->reg_pos < ad->reg_max) { reg = arch_register_argspec_name(arch, true, ad->reg_pos); ad->reg_pos++; } break; } if (reg) { snprintf(buf, sizeof(buf), "%%%s", reg); strcat(spec, buf); } return; } switch (data.type) { case ARG_TYPE_REG: reg = arch_register_dwarf_name(host_cpu_arch(), data.reg); if (strcmp(reg, "invalid register")) { snprintf(buf, sizeof(buf), "%%%s", reg); strcat(spec, buf); if (ad->last_fmt == ARG_FMT_FLOAT) ad->fpreg_pos++; else ad->reg_pos++; } break; case ARG_TYPE_STACK: snprintf(buf, sizeof(buf), "%%stack+%d", data.offset); strcat(spec, buf); ad->stack_ofs = data.offset + ALIGN(ad->last_size, sizeof(long)); break; default: if (ad->last_fmt == ARG_FMT_FLOAT) ad->fpreg_pos++; else ad->reg_pos++; break; } } static int get_retspec(Dwarf_Die *die, void *data, bool found) { struct arg_data *ad = data; char buf[ARGSPEC_MAX_SIZE]; Dwarf_Die spec; ad->has_retspec = true; if (found) pr_dbg2("found '%s' function for retspec\n", ad->name); /* for C++ programs */ if (!dwarf_hasattr(die, DW_AT_type)) { Dwarf_Attribute attr; if (!dwarf_hasattr(die, DW_AT_specification)) return 0; dwarf_attr(die, DW_AT_specification, &attr); dwarf_formref_die(&attr, &spec); die = &spec; if (!dwarf_hasattr(die, DW_AT_type)) return 0; } snprintf(buf, sizeof(buf), "@retval"); add_type_info(buf, sizeof(buf), die, ad); ad->argspec = xstrdup(buf); if (ad->last_fmt == ARG_FMT_STRUCT && ad->struct_return_needs_ptr && ad->struct_reg_cnt == 1 && ad->struct_regs[0] == PARAM_CLASS_PTR) { ad->struct_passed = true; ad->reg_pos = 1; } else if (ad->last_fmt == ARG_FMT_STD_STRING) { ad->struct_passed = true; ad->reg_pos = 1; } return 1; } static int get_argspec(Dwarf_Die *die, void *data) { struct arg_data *ad = data; Dwarf_Die arg; Dwarf_Addr offset = 0; int count = 0; dwarf_lowpc(die, &offset); pr_dbg2("found '%s' function for argspec (%#lx)\n", ad->name, offset); if (!ad->has_retspec) { /* update the return type info first */ get_retspec(die, ad, false); free(ad->argspec); ad->argspec = NULL; } ad->has_retspec = false; if (dwarf_child(die, &arg) != 0) { pr_dbg2("has no argument (children)\n"); return 0; } do { char buf[ARGSPEC_MAX_SIZE]; if (dwarf_tag(&arg) != DW_TAG_formal_parameter) continue; snprintf(buf, sizeof(buf), "arg%d", ++ad->idx); if (!add_type_info(buf, sizeof(buf), &arg, ad)) { /* ignore this argument */ ad->idx--; continue; } add_location(buf, sizeof(buf), &arg, ad); if (ad->argspec == NULL) xasprintf(&ad->argspec, "@%s", buf); else ad->argspec = strjoin(ad->argspec, buf, ","); count++; } while (dwarf_siblingof(&arg, &arg) == 0); return count; } struct build_data { struct uftrace_dbg_info *dinfo; struct uftrace_symtab *symtab; int nr_args; int nr_rets; struct uftrace_pattern *args; struct uftrace_pattern *rets; struct cu_files files; }; /* caller should free the return value */ static char *find_last_component(char *name) { char *tmp, *p, *last; int count = 0; tmp = p = last = xstrdup(name); while (*p) { if (strchr("<(", *p)) *p = '\0', count++; else if (strchr(">)", *p)) count--; if (p[0] == ':' && p[1] == ':' && count == 0) last = p + 2; p++; } p = xstrdup(last); free(tmp); return p; } static bool match_name(struct uftrace_symbol *sym, char *name) { bool ret; if (sym == NULL) return false; if (!strcmp(sym->name, name)) return true; /* name is mangled C++/Rust symbol */ if (name[0] == '_' && name[1] == 'Z') { char *demangled_name = demangle(name); ret = !strcmp(sym->name, demangled_name); free(demangled_name); return ret; } /* name is already (fully) demangled */ if (strpbrk(name, "(<:>)")) { char *demangled_sym = NULL; char *last_sym; char *last_name; if (demangler == DEMANGLE_FULL) return !strcmp(sym->name, name); if (demangler == DEMANGLE_NONE) demangled_sym = demangle(sym->name); last_sym = find_last_component(sym->name); last_name = find_last_component(name); ret = !strcmp(last_sym, last_name); free(last_sym); free(last_name); free(demangled_sym); return ret; } return false; } static void get_source_location(Dwarf_Die *die, struct build_data *bd, struct uftrace_symbol *sym) { ptrdiff_t sym_idx; const char *filename; struct uftrace_dbg_info *dinfo = bd->dinfo; struct uftrace_dbg_file *dfile = NULL; int dline = 0; sym_idx = sym - bd->symtab->sym; if (dwarf_hasattr(die, DW_AT_decl_file)) { if (dwarf_decl_line(die, &dline) == 0) { filename = dwarf_decl_file(die); /* * The dwarf_decl_file() can return 0 for DWARF-5 as it allows * file index of 0 (for default file) which is treated invalid * in libdw. This is unfortunate but we can access the file * table with index 0 directly since we checked the DIE has the * both decl file and decl line. */ if (filename == NULL) filename = dwarf_filesrc(bd->files.files, 0, NULL, NULL); dfile = get_debug_file(dinfo, filename); } } else { Dwarf_Die cudie; Dwarf_Line *line; unsigned long dwarf_addr = sym_to_dwarf_addr(dinfo, sym->addr); unsigned long limit_addr = dwarf_addr + sym->size; int search_limit = 10; dwarf_diecu(die, &cudie, NULL, NULL); /* * This loop is needed because gcc doesn't create dwarf info at NOP * instruction address so dwarf_getsrc_die() returns NULL. * To avoid this problem, we move forward until we see the first actual * instruction address of the function, which has a valid dwarf info. * The search is limited to the function size. */ do { line = dwarf_getsrc_die(&cudie, dwarf_addr); dwarf_addr += NOP_INSN_SIZE; } while (line == NULL && --search_limit > 0 && dwarf_addr < limit_addr); filename = dwarf_linesrc(line, NULL, NULL); dfile = get_debug_file(dinfo, filename); dwarf_lineno(line, &dline); } if (dfile == NULL) return; dinfo->locs[sym_idx].sym = sym; dinfo->locs[sym_idx].file = dfile; dinfo->locs[sym_idx].line = dline; dinfo->nr_locs_used++; } static int get_dwarfspecs_cb(Dwarf_Die *die, void *data) { struct build_data *bd = data; struct arg_data ad; char *name = NULL; Dwarf_Addr offset; struct uftrace_symbol *sym; int i; if (uftrace_done) return DWARF_CB_ABORT; if (dwarf_tag(die) != DW_TAG_subprogram) return DWARF_CB_OK; /* XXX: old libdw might call with decl DIE */ if (dwarf_hasattr(die, DW_AT_declaration)) return DWARF_CB_OK; /* XXX: this assumes symbol->addr is same as the lowpc */ if (!dwarf_hasattr(die, DW_AT_low_pc)) return DWARF_CB_OK; dwarf_lowpc(die, &offset); offset = dwarf_to_sym_addr(bd->dinfo, offset); if (dwarf_hasattr_integrate(die, DW_AT_linkage_name)) name = str_attr(die, DW_AT_linkage_name, true); if (name == NULL) name = (char *)dwarf_diename(die); if (unlikely(name == NULL)) return DWARF_CB_OK; pr_dbg3("func %s (at %lx)\n", name, offset); /* * double-check symbol table has same info. * we add 1 to the offset because of ARM(THUMB) symbols * but DWARF doesn't know about it. */ sym = find_sym(bd->symtab, offset + 1); if (sym == NULL || !match_name(sym, name)) { pr_dbg4("skip unknown debug info: %s / %s (%lx)\n", sym ? sym->name : "no name", name, offset); goto out; } get_source_location(die, bd, sym); setup_arg_data(&ad, sym->name, bd->dinfo); for (i = 0; i < bd->nr_rets; i++) { if (!match_filter_pattern(&bd->rets[i], sym->name)) continue; if (get_retspec(die, &ad, true)) { add_debug_entry(&bd->dinfo->rets, sym->name, sym->addr, ad.argspec); } free(ad.argspec); ad.argspec = NULL; break; } for (i = 0; i < bd->nr_args; i++) { if (!match_filter_pattern(&bd->args[i], sym->name)) continue; if (get_argspec(die, &ad)) { add_debug_entry(&bd->dinfo->args, sym->name, sym->addr, ad.argspec); } free(ad.argspec); ad.argspec = NULL; break; } out: return DWARF_CB_OK; } struct comp_dir_entry { struct rb_node node; char *name; int nr_used; /* number of times comp_dir is used in module */ int nr_locs; /* number of source locations built into comp_dir */ }; static int add_comp_dir(struct rb_root *root, char *name, int nr_locs) { struct comp_dir_entry *entry, *iter; struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; int cmp; pr_dbg3("add dir entry: %s (%d)\n", name, nr_locs); while (*p) { parent = *p; iter = rb_entry(parent, struct comp_dir_entry, node); cmp = strcmp(iter->name, name); if (cmp == 0) { iter->nr_used++; iter->nr_locs += nr_locs; return 0; } if (cmp > 0) p = &parent->rb_left; else p = &parent->rb_right; } entry = xmalloc(sizeof(*entry)); entry->name = xstrdup(name); entry->nr_locs = nr_locs; entry->nr_used = 1; rb_link_node(&entry->node, parent, p); rb_insert_color(&entry->node, root); return 0; } static void free_comp_dir(struct rb_root *root) { struct comp_dir_entry *entry; struct rb_node *node; while (!RB_EMPTY_ROOT(root)) { node = rb_first(root); entry = rb_entry(node, typeof(*entry), node); rb_erase(node, root); free(entry->name); free(entry); } } static struct comp_dir_entry *get_max_comp_dir(struct comp_dir_entry *a, struct comp_dir_entry *b) { if (a->nr_used > b->nr_used || (a->nr_used == b->nr_used && a->nr_locs > b->nr_locs)) return a; else return b; } static char *get_base_comp_dir(struct rb_root *dirs) { struct rb_node *rbnode; struct comp_dir_entry *e; struct comp_dir_entry *prev = NULL; struct comp_dir_entry *max; if (RB_EMPTY_ROOT(dirs)) return NULL; rbnode = rb_first(dirs); max = rb_entry(rbnode, typeof(*e), node); prev = max; rbnode = rb_next(rbnode); while (rbnode != NULL) { e = rb_entry(rbnode, typeof(*e), node); if (!strncmp(e->name, prev->name, strlen(prev->name))) { prev->nr_used += e->nr_used; prev->nr_locs += e->nr_locs; } else { max = get_max_comp_dir(prev, max); prev = e; } rbnode = rb_next(rbnode); } max = get_max_comp_dir(prev, max); return max->name; } static void build_dwarf_info(struct uftrace_dbg_info *dinfo, struct uftrace_symtab *symtab, enum uftrace_pattern_type ptype, struct strv *args, struct strv *rets) { Dwarf_Off curr = 0; Dwarf_Off next = 0; size_t header_sz = 0; struct uftrace_pattern *arg_patt; struct uftrace_pattern *ret_patt; struct rb_root comp_dirs = RB_ROOT; char *dir; char *s; int i; if (dinfo->dw == NULL) return; arg_patt = xcalloc(args->nr, sizeof(*arg_patt)); strv_for_each(args, s, i) init_filter_pattern(ptype, &arg_patt[i], s); ret_patt = xcalloc(rets->nr, sizeof(*ret_patt)); strv_for_each(rets, s, i) init_filter_pattern(ptype, &ret_patt[i], s); dinfo->nr_locs = symtab->nr_sym; dinfo->locs = xcalloc(dinfo->nr_locs, sizeof(*dinfo->locs)); /* traverse every CU to find debug info */ while (dwarf_nextcu(dinfo->dw, curr, &next, &header_sz, NULL, NULL, NULL) == 0) { Dwarf_Die cudie; struct build_data bd = { .dinfo = dinfo, .symtab = symtab, .args = arg_patt, .rets = ret_patt, .nr_args = args->nr, .nr_rets = rets->nr, }; if (dwarf_offdie(dinfo->dw, curr + header_sz, &cudie) == NULL) break; if (dwarf_tag(&cudie) != DW_TAG_compile_unit) break; if (uftrace_done) break; /* do not read arguments when it's not needed */ if (!dinfo->needs_args) { bd.nr_args = 0; bd.nr_rets = 0; } dwarf_getsrcfiles(&cudie, &bd.files.files, &bd.files.num); dwarf_getfuncs(&cudie, get_dwarfspecs_cb, &bd, 0); if (dwarf_hasattr(&cudie, DW_AT_comp_dir)) { dir = str_attr(&cudie, DW_AT_comp_dir, false); add_comp_dir(&comp_dirs, dir, dinfo->nr_locs_used); dinfo->nr_locs_used = 0; } curr = next; } dir = get_base_comp_dir(&comp_dirs); if (dir) { pr_dbg3("base dir: %s\n", dir); dinfo->base_dir = xstrdup(dir); free_comp_dir(&comp_dirs); } else { dinfo->base_dir = NULL; } for (i = 0; i < args->nr; i++) free_filter_pattern(&arg_patt[i]); free(arg_patt); for (i = 0; i < rets->nr; i++) free_filter_pattern(&ret_patt[i]); free(ret_patt); } #else /* !HAVE_LIBDW */ static int setup_dwarf_info(const char *filename, struct uftrace_dbg_info *dinfo, unsigned long offset, bool force) { dinfo->dw = NULL; return 0; } static void build_dwarf_info(struct uftrace_dbg_info *dinfo, struct uftrace_symtab *symtab, enum uftrace_pattern_type ptype, struct strv *args, struct strv *rets) { } static void release_dwarf_info(struct uftrace_dbg_info *dinfo) { } #endif /* !HAVE_LIBDW */ int setup_debug_info(const char *filename, struct uftrace_dbg_info *dinfo, unsigned long offset, bool force) { dinfo->args = RB_ROOT; dinfo->rets = RB_ROOT; dinfo->enums = RB_ROOT; dinfo->files = RB_ROOT; dinfo->loaded = true; return setup_dwarf_info(filename, dinfo, offset, force); } static void release_debug_info(struct uftrace_dbg_info *dinfo) { free_debug_entry(&dinfo->args); free_debug_entry(&dinfo->rets); release_enum_def(&dinfo->enums); release_debug_file(&dinfo->files); free(dinfo->locs); dinfo->locs = NULL; free(dinfo->base_dir); release_dwarf_info(dinfo); dinfo->loaded = false; } /* find argspecs only have function name (pattern) */ static void extract_dwarf_args(char *argspec, char *retspec, struct strv *pargs, struct strv *prets) { if (argspec) { struct strv tmp = STRV_INIT; char *arg; int i; strv_split(&tmp, argspec, ";"); strv_for_each(&tmp, arg, i) { if (strchr(arg, '@')) continue; strv_append(pargs, arg); } strv_free(&tmp); } if (retspec) { struct strv tmp = STRV_INIT; char *ret; int i; strv_split(&tmp, retspec, ";"); strv_for_each(&tmp, ret, i) { if (strchr(ret, '@')) continue; strv_append(prets, ret); } strv_free(&tmp); } } void prepare_debug_info(struct uftrace_sym_info *sinfo, enum uftrace_pattern_type ptype, char *argspec, char *retspec, bool auto_args, bool force) { struct uftrace_mmap *map; struct strv dwarf_args = STRV_INIT; struct strv dwarf_rets = STRV_INIT; if (sinfo->flags & SYMTAB_FL_SYMS_DIR) { load_debug_info(sinfo, true); return; } extract_dwarf_args(argspec, retspec, &dwarf_args, &dwarf_rets); if (auto_args) { if (ptype == PATT_REGEX) { strv_append(&dwarf_args, "."); strv_append(&dwarf_rets, "."); } else { /* PATT_GLOB */ strv_append(&dwarf_args, "*"); strv_append(&dwarf_rets, "*"); } } /* file and line info need be saved regardless of argspec */ pr_dbg("prepare debug info\n"); for_each_map(sinfo, map) { struct uftrace_symtab *stab = &map->mod->symtab; struct uftrace_dbg_info *dinfo = &map->mod->dinfo; if (map->mod == NULL || map->mod->dinfo.loaded) continue; setup_debug_info(map->libname, dinfo, map->start, force); build_dwarf_info(dinfo, stab, ptype, &dwarf_args, &dwarf_rets); } strv_free(&dwarf_args); strv_free(&dwarf_rets); } void finish_debug_info(struct uftrace_sym_info *sinfo) { struct uftrace_mmap *map; for_each_map(sinfo, map) { if (map->mod == NULL || !map->mod->dinfo.loaded) continue; release_debug_info(&map->mod->dinfo); } } static bool match_debug_file(const char *dbgname, const char *pathname, char *build_id) { FILE *fp; bool ret = true; char *line = NULL; size_t len = 0; fp = fopen(dbgname, "r"); if (fp == NULL) return false; while (getline(&line, &len, fp) >= 0) { if (line[0] != '#') break; /* remove trailing newline */ line[strlen(line) - 1] = '\0'; if (!strncmp(line, "# path name: ", 13)) ret = !strcmp(line + 13, pathname); if (!strncmp(line, "# build-id: ", 12)) ret = !strcmp(line + 12, build_id); } free(line); fclose(fp); return ret; } static FILE *create_debug_file(const char *dirname, const char *filename, char *build_id) { FILE *fp; char *tmp; xasprintf(&tmp, "%s/%s.dbg", dirname, basename(filename)); if (match_debug_file(tmp, filename, build_id)) { free(tmp); return NULL; } fp = fopen(tmp, "ax"); if (fp == NULL && errno == EEXIST) { char *dbgfile; int len; dbgfile = make_new_symbol_filename(tmp, filename, build_id); len = strlen(dbgfile); strncpy(dbgfile + len - 3, "dbg", 4); free(tmp); tmp = dbgfile; fp = fopen(tmp, "ax"); } free(tmp); return fp; } static void close_debug_file(FILE *fp, const char *dirname, const char *filename, char *build_id) { bool delete = !ftell(fp); char *tmp; fclose(fp); if (!delete) return; pr_dbg2("delete debug file for %s\n", filename); xasprintf(&tmp, "%s/%s.dbg", dirname, filename); if (!match_debug_file(tmp, filename, build_id)) { char *dbgfile; int len; dbgfile = make_new_symbol_filename(tmp, filename, build_id); len = strlen(dbgfile); strncpy(dbgfile + len - 3, "dbg", 4); free(tmp); tmp = dbgfile; delete = false; fp = fopen(tmp, "r"); if (fp != NULL) { fseek(fp, 0, SEEK_END); delete = !ftell(fp); fclose(fp); } if (!delete) { free(tmp); return; } } unlink(tmp); free(tmp); } void save_debug_file(FILE *fp, char code, char *str, unsigned long val) { fprintf(fp, "%c: ", code); switch (code) { case 'F': /* symbol address and name */ fprintf(fp, "%lx %s\n", val, str); break; case 'A': case 'R': /* argument and return value spec */ fprintf(fp, "%s\n", str); break; case 'E': /* * enum definition: this format is compatible with * parse_enum_string() */ fprintf(fp, "enum %s {%s}\n", str, (char *)val); break; case 'L': /* line number and file name */ fprintf(fp, "%ld %s\n", val, str); break; default: fprintf(fp, "unknown debug info\n"); break; } } static void save_debug_entries(struct uftrace_dbg_info *dinfo, const char *dirname, const char *filename, char *build_id) { size_t i; FILE *fp; int idx; int len; fp = create_debug_file(dirname, filename, build_id); if (fp == NULL) return; /* somebody already did that! */ fprintf(fp, "# path name: %s\n", filename); if (strlen(build_id) > 0) fprintf(fp, "# build-id: %s\n", build_id); save_enum_def(&dinfo->enums, fp); for (i = 0; i < dinfo->nr_locs; i++) { struct uftrace_dbg_loc *loc = &dinfo->locs[i]; struct debug_entry *entry; if (loc->sym == NULL) continue; save_debug_file(fp, 'F', loc->sym->name, loc->sym->addr); idx = 0; if (dinfo->base_dir) { len = strlen(dinfo->base_dir); if (!strncmp(loc->file->name, dinfo->base_dir, len)) idx = len + 1; } /* skip common parts with compile directory */ save_debug_file(fp, 'L', loc->file->name + idx, loc->line); entry = find_debug_entry(&dinfo->args, loc->sym->addr); if (entry && entry->spec) save_debug_file(fp, 'A', entry->spec, 0); entry = find_debug_entry(&dinfo->rets, loc->sym->addr); if (entry && entry->spec) save_debug_file(fp, 'R', entry->spec, 0); } close_debug_file(fp, dirname, basename(filename), build_id); } void save_debug_info(struct uftrace_sym_info *sinfo, const char *dirname) { struct uftrace_mmap *map; for_each_map(sinfo, map) { if (map->mod == NULL || !map->mod->dinfo.loaded) continue; save_debug_entries(&map->mod->dinfo, dirname, map->libname, map->build_id); } } static int load_debug_file(struct uftrace_dbg_info *dinfo, struct uftrace_symtab *symtab, const char *dirname, const char *filename, char *build_id, bool needs_srcline) { char *pathname; FILE *fp; char *line = NULL; size_t len = 0; int ret = -1; char *func = NULL; uint64_t offset = 0; xasprintf(&pathname, "%s/%s.dbg", dirname, basename(filename)); if (!match_debug_file(pathname, filename, build_id)) { char *newfile; newfile = make_new_symbol_filename(pathname, filename, build_id); len = strlen(newfile); strcpy(newfile + len - 3, "dbg"); /* replace pathname */ free(pathname); pathname = newfile; len = 0; } fp = fopen(pathname, "r"); if (fp == NULL) { if (errno == ENOENT) { free(pathname); return -1; } pr_err("failed to open: %s", pathname); } pr_dbg2("load debug info from %s\n", pathname); dinfo->args = RB_ROOT; dinfo->rets = RB_ROOT; dinfo->enums = RB_ROOT; dinfo->files = RB_ROOT; dinfo->loaded = true; if (needs_srcline && dinfo->locs == NULL) { dinfo->nr_locs = symtab->nr_sym; dinfo->locs = xcalloc(dinfo->nr_locs, sizeof(*dinfo->locs)); } while (getline(&line, &len, fp) >= 0) { char *pos; struct rb_root *root = &dinfo->args; struct uftrace_symbol *sym; ptrdiff_t sym_idx; unsigned long lineno; if (line[0] == '#') continue; if (line[1] != ':' || line[2] != ' ') goto out; /* remove trailing newline */ line[strlen(line) - 1] = '\0'; switch (line[0]) { case 'F': offset = strtoul(&line[3], &pos, 16); if (*pos == ' ') pos++; free(func); func = xstrdup(pos); break; case 'A': case 'R': if (line[0] == 'R') root = &dinfo->rets; if (func == NULL) goto out; if (add_debug_entry(root, func, offset, &line[3]) < 0) goto out; break; case 'E': if (parse_enum_string(&line[3], &dinfo->enums)) goto out; break; case 'L': if (!needs_srcline) break; sym = find_sym(symtab, offset); if (sym == NULL) goto out; lineno = strtoul(&line[3], &pos, 0); sym_idx = sym - symtab->sym; dinfo->locs[sym_idx].sym = sym; dinfo->locs[sym_idx].line = lineno; dinfo->locs[sym_idx].file = get_debug_file(dinfo, pos + 1); dinfo->nr_locs_used++; break; default: goto out; } } ret = 0; out: if (ret < 0) { pr_dbg("invalid dbg file: %s: %s\n", pathname, line); free_debug_entry(&dinfo->args); free_debug_entry(&dinfo->rets); } free(line); fclose(fp); free(pathname); free(func); return ret; } void load_debug_info(struct uftrace_sym_info *sinfo, bool needs_srcline) { struct uftrace_mmap *map; for_each_map(sinfo, map) { struct uftrace_module *mod = map->mod; struct uftrace_symtab *stab; struct uftrace_dbg_info *dinfo; if (map->mod == NULL) continue; stab = &mod->symtab; dinfo = &mod->dinfo; if (!debug_info_has_location(dinfo) && !debug_info_has_argspec(dinfo)) { load_debug_file(dinfo, stab, sinfo->symdir, map->libname, map->build_id, needs_srcline); } } } char *get_dwarf_argspec(struct uftrace_dbg_info *dinfo, char *name, unsigned long addr) { struct debug_entry *entry = find_debug_entry(&dinfo->args, addr); return entry ? entry->spec : NULL; } char *get_dwarf_retspec(struct uftrace_dbg_info *dinfo, char *name, unsigned long addr) { struct debug_entry *entry = find_debug_entry(&dinfo->rets, addr); return entry ? entry->spec : NULL; } struct uftrace_dbg_loc *find_file_line(struct uftrace_sym_info *sinfo, uint64_t addr) { struct uftrace_mmap *map; struct uftrace_symtab *symtab; struct uftrace_dbg_info *dinfo; struct uftrace_symbol *sym = NULL; ptrdiff_t idx; map = find_map(sinfo, addr); /* TODO: support kernel debug info */ if (map == MAP_KERNEL) return NULL; if (map == NULL || map->mod == NULL) return NULL; symtab = &map->mod->symtab; dinfo = &map->mod->dinfo; if (debug_info_has_location(dinfo)) sym = find_sym(symtab, addr - map->start); if (sym == NULL) return NULL; idx = sym - symtab->sym; return &dinfo->locs[idx]; } #ifdef UNIT_TEST #ifdef HAVE_LIBDW struct comp_dir { char *name; int nr_loc; }; /* test: same number of compilation unit */ TEST_CASE(dwarf_srcline_prefix1) { struct rb_root dirs = RB_ROOT; int i; static struct comp_dir test_dirs[] = { { "/home/soft/uftrace/cmds", 1 }, { "/home/soft/uftrace/utils", 1 }, { "/home/soft/uftrace/libmcount", 1 }, }; for (i = 0; i < sizeof(test_dirs) / sizeof(struct comp_dir); i++) { pr_dbg("comp_dir=%s (count=%d)\n", test_dirs[i].name, test_dirs[i].nr_loc); add_comp_dir(&dirs, test_dirs[i].name, test_dirs[i].nr_loc); } pr_dbg("selected base_dir=%s\n", "/home/soft/uftrace/cmds"); TEST_STREQ(get_base_comp_dir(&dirs), "/home/soft/uftrace/cmds"); free_comp_dir(&dirs); return TEST_OK; } /* test: number of compilation unit */ TEST_CASE(dwarf_srcline_prefix2) { struct rb_root dirs = RB_ROOT; int i; static struct comp_dir test_dirs[] = { { "/home/a/tests", 1 }, { "/home/soft/uftrace/cmds", 1 }, { "/home/soft/uftrace", 1 }, }; for (i = 0; i < sizeof(test_dirs) / sizeof(struct comp_dir); i++) { pr_dbg("comp_dir=%s (count=%d)\n", test_dirs[i].name, test_dirs[i].nr_loc); add_comp_dir(&dirs, test_dirs[i].name, test_dirs[i].nr_loc); } pr_dbg("selected base_dir=%s\n", "/home/soft/uftrace"); TEST_STREQ(get_base_comp_dir(&dirs), "/home/soft/uftrace"); free_comp_dir(&dirs); return TEST_OK; } /* test: number of debug info of compilation unit */ TEST_CASE(dwarf_srcline_prefix3) { struct rb_root dirs = RB_ROOT; int i; static struct comp_dir test_dirs[] = { { "/home/a/tests", 1 }, { "/home/a/tests", 3 }, { "/home/soft/uftrace/cmds", 4 }, { "/home/soft/uftrace", 1 }, }; for (i = 0; i < sizeof(test_dirs) / sizeof(struct comp_dir); i++) { pr_dbg("comp_dir=%s (count=%d)\n", test_dirs[i].name, test_dirs[i].nr_loc); add_comp_dir(&dirs, test_dirs[i].name, test_dirs[i].nr_loc); } pr_dbg("selected base_dir=%s\n", "/home/soft/uftrace"); TEST_STREQ(get_base_comp_dir(&dirs), "/home/soft/uftrace"); free_comp_dir(&dirs); return TEST_OK; } /* test: no compilation unit */ TEST_CASE(dwarf_srcline_prefix4) { struct rb_root dirs = RB_ROOT; pr_dbg("check empty comp_dir\n"); TEST_EQ(get_base_comp_dir(&dirs), NULL); return TEST_OK; } #endif /* HAVE_LIBDW */ static void setup_test_debug_info(struct uftrace_module *mod) { struct uftrace_dbg_info *dinfo = &mod->dinfo; struct uftrace_dbg_file *file; int i; file = xzalloc(sizeof(*file)); file->name = xstrdup(mod->name); dinfo->files.rb_node = &file->node; dinfo->nr_locs = mod->symtab.nr_sym; dinfo->locs = xcalloc(dinfo->nr_locs, sizeof(*dinfo->locs)); for (i = 0; i < dinfo->nr_locs; i++) { struct uftrace_symbol *sym = &mod->symtab.sym[i]; struct uftrace_dbg_loc *loc = &dinfo->locs[i]; char argspec[32]; loc->sym = sym; loc->file = file; loc->line = (i + 1) * 10; snprintf(argspec, sizeof(argspec), "arg%d", i + 1); add_debug_entry(&dinfo->args, sym->name, sym->addr, argspec); } } static void init_test_module_info(struct uftrace_module **pmod1, struct uftrace_module **pmod2, bool init_debug_info) { struct uftrace_module *mod1, *mod2; const char mod1_name[] = "/some/where/module/name"; const char mod2_name[] = "/different/path/name"; const char mod1_build_id[] = "1234567890abcdef"; const char mod2_build_id[] = "DUMMY-BUILD-ID"; static struct uftrace_symbol mod1_syms[] = { { 0x1000, 0x1000, ST_PLT_FUNC, "func1" }, { 0x2000, 0x1000, ST_LOCAL_FUNC, "func2" }, { 0x3000, 0x1000, ST_GLOBAL_FUNC, "func3" }, }; static struct uftrace_symbol mod2_syms[] = { { 0x5000, 0x1000, ST_PLT_FUNC, "funcA" }, { 0x6000, 0x1000, ST_PLT_FUNC, "funcB" }, { 0x7000, 0x1000, ST_PLT_FUNC, "funcC" }, { 0x8000, 0x1000, ST_GLOBAL_FUNC, "funcD" }, }; mod1 = xzalloc(sizeof(*mod1) + sizeof(mod1_name)); mod2 = xzalloc(sizeof(*mod2) + sizeof(mod2_name)); strcpy(mod1->name, mod1_name); strcpy(mod2->name, mod2_name); strcpy(mod1->build_id, mod1_build_id); strcpy(mod2->build_id, mod2_build_id); mod1->symtab.sym = mod1_syms; mod1->symtab.nr_sym = ARRAY_SIZE(mod1_syms); mod2->symtab.sym = mod2_syms; mod2->symtab.nr_sym = ARRAY_SIZE(mod2_syms); if (init_debug_info) { setup_test_debug_info(mod1); setup_test_debug_info(mod2); } *pmod1 = mod1; *pmod2 = mod2; } static int check_test_debug_info(struct uftrace_dbg_info *dinfo1, struct uftrace_dbg_info *dinfo2) { int i; struct rb_node *node; struct debug_entry *save_entry, *load_entry; TEST_EQ(dinfo1->nr_locs, dinfo2->nr_locs); for (i = 0; i < dinfo1->nr_locs; i++) { struct uftrace_dbg_loc *save_loc = &dinfo1->locs[i]; struct uftrace_dbg_loc *load_loc = &dinfo2->locs[i]; TEST_STREQ(save_loc->sym->name, load_loc->sym->name); TEST_STREQ(save_loc->file->name, load_loc->file->name); TEST_EQ(save_loc->line, load_loc->line); } TEST_EQ(RB_EMPTY_ROOT(&dinfo1->args), false); TEST_EQ(RB_EMPTY_ROOT(&dinfo2->args), false); node = rb_first(&dinfo1->args); save_entry = rb_entry(node, struct debug_entry, node); node = rb_first(&dinfo2->args); load_entry = rb_entry(node, struct debug_entry, node); while (save_entry && load_entry) { TEST_STREQ(save_entry->name, load_entry->name); TEST_STREQ(save_entry->spec, load_entry->spec); TEST_EQ(save_entry->offset, load_entry->offset); node = rb_next(&save_entry->node); save_entry = node ? rb_entry(node, struct debug_entry, node) : NULL; node = rb_next(&load_entry->node); load_entry = node ? rb_entry(node, struct debug_entry, node) : NULL; } TEST_EQ(save_entry == NULL, load_entry == NULL); return TEST_OK; } TEST_CASE(dwarf_same_file_name1) { struct uftrace_module *save_mod[2]; struct uftrace_module *load_mod[2]; int ret; /* recover from earlier failures */ if (system("rm -f name*.dbg")) return TEST_NG; pr_dbg("init debug info and save .dbg files (no build-id)\n"); init_test_module_info(&save_mod[0], &save_mod[1], true); save_debug_entries(&save_mod[0]->dinfo, ".", save_mod[0]->name, ""); save_debug_entries(&save_mod[1]->dinfo, ".", save_mod[1]->name, ""); pr_dbg("load .dbg files\n"); init_test_module_info(&load_mod[0], &load_mod[1], false); ret = load_debug_file(&load_mod[0]->dinfo, &load_mod[0]->symtab, ".", load_mod[0]->name, "", true); TEST_EQ(ret, 0); ret = load_debug_file(&load_mod[1]->dinfo, &load_mod[1]->symtab, ".", load_mod[1]->name, "", true); TEST_EQ(ret, 0); pr_dbg("compare debug info1\n"); ret = check_test_debug_info(&save_mod[0]->dinfo, &load_mod[0]->dinfo); if (ret == TEST_OK) { pr_dbg("compare debug info2\n"); ret = check_test_debug_info(&save_mod[1]->dinfo, &load_mod[1]->dinfo); } pr_dbg("release debug info\n"); release_debug_info(&save_mod[0]->dinfo); release_debug_info(&save_mod[1]->dinfo); release_debug_info(&load_mod[0]->dinfo); release_debug_info(&load_mod[1]->dinfo); free(save_mod[0]); free(save_mod[1]); free(load_mod[0]); free(load_mod[1]); if (system("rm -f name*.dbg")) return TEST_NG; return ret; } TEST_CASE(dwarf_same_file_name2) { struct uftrace_module *save_mod[2]; struct uftrace_module *load_mod[2]; int ret; /* recover from earlier failures */ if (system("rm -f name*.dbg")) return TEST_NG; pr_dbg("init debug info and save .dbg files (with build-id)\n"); init_test_module_info(&save_mod[0], &save_mod[1], true); /* save them in the opposite order */ save_debug_entries(&save_mod[1]->dinfo, ".", save_mod[1]->name, save_mod[1]->build_id); save_debug_entries(&save_mod[0]->dinfo, ".", save_mod[0]->name, save_mod[0]->build_id); pr_dbg("load .dbg files\n"); init_test_module_info(&load_mod[0], &load_mod[1], false); ret = load_debug_file(&load_mod[0]->dinfo, &load_mod[0]->symtab, ".", load_mod[0]->name, load_mod[0]->build_id, true); TEST_EQ(ret, 0); ret = load_debug_file(&load_mod[1]->dinfo, &load_mod[1]->symtab, ".", load_mod[1]->name, load_mod[1]->build_id, true); TEST_EQ(ret, 0); pr_dbg("compare debug info1\n"); ret = check_test_debug_info(&save_mod[0]->dinfo, &load_mod[0]->dinfo); if (ret == TEST_OK) { pr_dbg("compare debug info2\n"); ret = check_test_debug_info(&save_mod[1]->dinfo, &load_mod[1]->dinfo); } pr_dbg("release debug info\n"); release_debug_info(&save_mod[0]->dinfo); release_debug_info(&save_mod[1]->dinfo); release_debug_info(&load_mod[0]->dinfo); release_debug_info(&load_mod[1]->dinfo); free(save_mod[0]); free(save_mod[1]); free(load_mod[0]); free(load_mod[1]); if (system("rm -f name*.dbg")) return TEST_NG; return ret; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/dwarf.h000066400000000000000000000047171455365734300156740ustar00rootroot00000000000000#ifndef UFTRACE_DWARF_H #define UFTRACE_DWARF_H #include #include #include #include "utils/filter.h" #include "utils/rbtree.h" struct uftrace_sym_info; #ifdef HAVE_LIBDW #include #else #define Dwarf void #endif struct uftrace_dbg_file { /* saved in uftrace_dbg_info.files */ struct rb_node node; /* source file name */ char *name; }; /* we only keep the start location of symbol */ struct uftrace_dbg_loc { /* symbol for this location */ struct uftrace_symbol *sym; /* filename info */ struct uftrace_dbg_file *file; /* line number info */ int line; }; struct uftrace_dbg_info { /* opaque DWARF info pointer */ Dwarf *dw; /* start address in memory for this module */ uint64_t offset; /* rb tree of arguments */ struct rb_root args; /* rb tree of return values */ struct rb_root rets; /* rb tree of enum tags/values */ struct rb_root enums; /* rb tree of file info */ struct rb_root files; /* array of location - same order as symbol */ struct uftrace_dbg_loc *locs; /* number of debug location info */ size_t nr_locs; /* number of actually used debug location info */ size_t nr_locs_used; /* ELF file type - EXEC, REL, DYN */ int file_type; /* whether it needs to parse argument info */ bool needs_args; /* whether it's loaded already */ bool loaded; /* name of common directory path for source files (can be %NULL) */ char *base_dir; }; extern void prepare_debug_info(struct uftrace_sym_info *sinfo, enum uftrace_pattern_type ptype, char *argspec, char *retspec, bool auto_args, bool force); extern void finish_debug_info(struct uftrace_sym_info *sinfo); extern bool debug_info_has_argspec(struct uftrace_dbg_info *dinfo); extern bool debug_info_has_location(struct uftrace_dbg_info *dinfo); extern char *get_dwarf_argspec(struct uftrace_dbg_info *dinfo, char *name, unsigned long addr); extern char *get_dwarf_retspec(struct uftrace_dbg_info *dinfo, char *name, unsigned long addr); struct uftrace_dbg_loc *find_file_line(struct uftrace_sym_info *sinfo, uint64_t addr); extern void save_debug_info(struct uftrace_sym_info *sinfo, const char *dirname); extern void load_debug_info(struct uftrace_sym_info *sinfo, bool needs_srcline); extern void save_debug_file(FILE *fp, char code, char *str, unsigned long val); /* only for dummy python module */ extern int setup_debug_info(const char *filename, struct uftrace_dbg_info *dinfo, unsigned long offset, bool force); #endif /* UFTRACE_DWARF_H */ uftrace-0.15.2/utils/event.c000066400000000000000000000237351455365734300157060ustar00rootroot00000000000000#include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "event" #define PR_DOMAIN DBG_EVENT #include "uftrace.h" #include "utils/event.h" #include "utils/fstack.h" #include "utils/kernel.h" #include "utils/utils.h" #define EVENT_FILE_NAME "events.txt" /** * event_get_name - find event name from event id * @handle - handle to uftrace data * @evt_id - event id * * This function returns a string of event name matching to @evt_id. * Callers must free the returned string. This is moved from utils.c * since it needs to call libtraceevent function for kernel events * which is not linked into libmcount. */ char *event_get_name(struct uftrace_data *handle, unsigned evt_id) { char *evt_name = NULL; struct event_format *event; if (evt_id == EVENT_ID_EXTERN_DATA) { evt_name = xstrdup("external-data"); goto out; } if (evt_id >= EVENT_ID_USER) { struct uftrace_event *ev; list_for_each_entry(ev, &handle->events, list) { if (ev->id == evt_id) { xasprintf(&evt_name, "%s:%s", ev->provider, ev->event); goto out; } } xasprintf(&evt_name, "user_event:%u", evt_id); goto out; } if (evt_id >= EVENT_ID_PERF) { const char *event_name; switch (evt_id) { case EVENT_ID_PERF_SCHED_IN: event_name = "sched-in"; break; case EVENT_ID_PERF_SCHED_OUT: event_name = "sched-out"; break; case EVENT_ID_PERF_SCHED_OUT_PREEMPT: event_name = "sched-out (pre-empted)"; break; case EVENT_ID_PERF_SCHED_BOTH: event_name = "schedule"; break; case EVENT_ID_PERF_SCHED_BOTH_PREEMPT: event_name = "schedule (pre-empted)"; break; case EVENT_ID_PERF_TASK: event_name = "task-new"; break; case EVENT_ID_PERF_EXIT: event_name = "task-exit"; break; case EVENT_ID_PERF_COMM: event_name = "task-name"; break; default: event_name = "unknown"; break; } xasprintf(&evt_name, "linux:%s", event_name); goto out; } if (evt_id >= EVENT_ID_BUILTIN) { switch (evt_id) { case EVENT_ID_READ_PROC_STATM: xasprintf(&evt_name, "read:proc/statm"); break; case EVENT_ID_READ_PAGE_FAULT: xasprintf(&evt_name, "read:page-fault"); break; case EVENT_ID_READ_PMU_CYCLE: xasprintf(&evt_name, "read:pmu-cycle"); break; case EVENT_ID_READ_PMU_CACHE: xasprintf(&evt_name, "read:pmu-cache"); break; case EVENT_ID_READ_PMU_BRANCH: xasprintf(&evt_name, "read:pmu-branch"); break; case EVENT_ID_DIFF_PROC_STATM: xasprintf(&evt_name, "diff:proc/statm"); break; case EVENT_ID_DIFF_PAGE_FAULT: xasprintf(&evt_name, "diff:page-fault"); break; case EVENT_ID_DIFF_PMU_CYCLE: xasprintf(&evt_name, "diff:pmu-cycle"); break; case EVENT_ID_DIFF_PMU_CACHE: xasprintf(&evt_name, "diff:pmu-cache"); break; case EVENT_ID_DIFF_PMU_BRANCH: xasprintf(&evt_name, "diff:pmu-branch"); break; case EVENT_ID_WATCH_CPU: xasprintf(&evt_name, "watch:cpu"); break; default: xasprintf(&evt_name, "builtin_event:%u", evt_id); break; } goto out; } /* kernel events */ if (has_kernel_data(handle->kernel)) { char buf[512]; event = kparser_find_event(&handle->kernel->parser, evt_id); kparser_event_name(&handle->kernel->parser, event, buf, sizeof(buf)); evt_name = xstrdup(buf); } out: return evt_name; } /** * event_get_data_str - convert event data to a string * @evt_id - event id * @data - raw event data * @verbose - whether it needs the verbose format * * This function returns a string of event name matching to @evt_id. * Callers must free the returned string. */ char *event_get_data_str(unsigned evt_id, void *data, bool verbose) { char *str = NULL; const char *diff = ""; char vbuf[128]; union { struct uftrace_proc_statm statm; struct uftrace_page_fault pgfault; struct uftrace_pmu_cycle cycle; struct uftrace_pmu_cache cache; struct uftrace_pmu_branch branch; int cpu; } u; switch (evt_id) { case EVENT_ID_EXTERN_DATA: xasprintf(&str, "msg=\"%s\"", (char *)data); break; case EVENT_ID_PERF_COMM: xasprintf(&str, "comm=\"%s\"", (char *)data); break; case EVENT_ID_DIFF_PROC_STATM: if (verbose) diff = "+"; /* fall through */ case EVENT_ID_READ_PROC_STATM: memcpy(&u.statm, data, sizeof(u.statm)); xasprintf(&str, "vmsize=%s%" PRIu64 "KB vmrss=%s%" PRIu64 "KB shared=%s%" PRIu64 "KB", diff, u.statm.vmsize, diff, u.statm.vmrss, diff, u.statm.shared); break; case EVENT_ID_DIFF_PAGE_FAULT: if (verbose) diff = "+"; /* fall through */ case EVENT_ID_READ_PAGE_FAULT: memcpy(&u.pgfault, data, sizeof(u.pgfault)); xasprintf(&str, "major=%s%" PRIu64 " minor=%s%" PRIu64, diff, u.pgfault.major, diff, u.pgfault.minor); break; case EVENT_ID_DIFF_PMU_CYCLE: if (verbose) diff = "+"; /* fall through */ case EVENT_ID_READ_PMU_CYCLE: memcpy(&u.cycle, data, sizeof(u.cycle)); xasprintf(&str, "cycles=%s%" PRIu64 " instructions=%s%" PRIu64, diff, u.cycle.cycles, diff, u.cycle.instrs); if (diff[0] == '+') { snprintf(vbuf, sizeof(vbuf), "IPC=%.2f", (float)u.cycle.instrs / u.cycle.cycles); str = strjoin(str, vbuf, " "); } break; case EVENT_ID_DIFF_PMU_CACHE: if (verbose) diff = "+"; /* fall through */ case EVENT_ID_READ_PMU_CACHE: memcpy(&u.cache, data, sizeof(u.cache)); xasprintf(&str, "refers=%s%" PRIu64 " misses=%s%" PRIu64, diff, u.cache.refers, diff, u.cache.misses); if (diff[0] == '+') { snprintf(vbuf, sizeof(vbuf), "hit=%.2f%%", 100.0 * (u.cache.refers - u.cache.misses) / u.cache.refers); str = strjoin(str, vbuf, " "); } break; case EVENT_ID_DIFF_PMU_BRANCH: if (verbose) diff = "+"; /* fall through */ case EVENT_ID_READ_PMU_BRANCH: memcpy(&u.branch, data, sizeof(u.branch)); xasprintf(&str, "branch=%s%" PRIu64 " misses=%s%" PRIu64, diff, u.branch.branch, diff, u.branch.misses); if (diff[0] == '+') { snprintf(vbuf, sizeof(vbuf), "predict=%.2f%%", 100.0 * (u.branch.branch - u.branch.misses) / u.branch.branch); str = strjoin(str, vbuf, " "); } break; case EVENT_ID_WATCH_CPU: memcpy(&u.cpu, data, sizeof(u.cpu)); xasprintf(&str, "cpu=%d", u.cpu); break; default: /* kernel tracepoints */ if (evt_id < EVENT_ID_BUILTIN) str = xstrdup((char *)data); else pr_dbg3("unexpected event data: %u\n", evt_id); break; } return str; } /** * finish_events_file - cleanup memory for events in the given handle * @handle: uftrace_data data structure */ void finish_events_file(struct uftrace_data *handle) { struct uftrace_event *ev, *tmp; list_for_each_entry_safe(ev, tmp, &handle->events, list) { list_del(&ev->list); free(ev->provider); free(ev->event); free(ev); } } /** * read_events_file - read 'events.txt' file from data directory * @handle: uftrace_data data structure * * This function read the events file in the @handle->dirname and build event * information (for userspace). * * It returns 0 for success, -1 for error. */ int read_events_file(struct uftrace_data *handle) { FILE *fp; char *fname = NULL; char *line = NULL; size_t sz = 0; xasprintf(&fname, "%s/%s", handle->dirname, EVENT_FILE_NAME); fp = fopen(fname, "r"); if (fp == NULL) { /* it might hit no events, so no file is ok */ if (errno == ENOENT) errno = 0; free(fname); return -errno; } pr_dbg("reading %s file\n", fname); while (getline(&line, &sz, fp) >= 0) { char provider[512]; char event[512]; unsigned evt_id; struct uftrace_event *ev; if (!strncmp(line, "EVENT", 5) && sscanf(line + 7, "%u %[^:]:%s", &evt_id, provider, event) == 3) { ev = xmalloc(sizeof(*ev)); ev->id = evt_id; ev->provider = xstrdup(provider); ev->event = xstrdup(event); list_add_tail(&ev->list, &handle->events); } } free(line); fclose(fp); free(fname); return 0; } #ifdef UNIT_TEST TEST_CASE(event_name) { unsigned i; struct uftrace_data handle = {}; struct { unsigned evt_id; char *evt_name; } expected[] = { { EVENT_ID_EXTERN_DATA, "external-data" }, { EVENT_ID_PERF_SCHED_IN, "linux:sched-in" }, { EVENT_ID_PERF_COMM, "linux:task-name" }, { EVENT_ID_READ_PROC_STATM, "read:proc/statm" }, { EVENT_ID_DIFF_PROC_STATM, "diff:proc/statm" }, { EVENT_ID_READ_PMU_CACHE, "read:pmu-cache" }, { EVENT_ID_DIFF_PMU_CACHE, "diff:pmu-cache" }, { EVENT_ID_WATCH_CPU, "watch:cpu" }, }; pr_dbg("testing event name strings\n"); for (i = 0; i < ARRAY_SIZE(expected); i++) { char *got = event_get_name(&handle, expected[i].evt_id); TEST_STREQ(expected[i].evt_name, got); free(got); } return TEST_OK; } TEST_CASE(event_data) { char msg[] = "this is external data."; char comm[] = "taskname"; struct uftrace_page_fault pgfault = { 1977, 1102 }; struct uftrace_pmu_cycle cycle = { 1024, 2048 }; int cpu = 123; unsigned i; struct { unsigned evt_id; void *data; char *str; } expected[] = { { EVENT_ID_EXTERN_DATA, msg, "msg=\"this is external data.\"" }, { EVENT_ID_PERF_COMM, comm, "comm=\"taskname\"" }, { EVENT_ID_READ_PAGE_FAULT, &pgfault, "major=1977 minor=1102" }, { EVENT_ID_DIFF_PMU_CYCLE, &cycle, "cycles=+1024 instructions=+2048 IPC=2.00" }, { EVENT_ID_WATCH_CPU, &cpu, "cpu=123" }, }; pr_dbg("testing event data strings\n"); for (i = 0; i < ARRAY_SIZE(expected); i++) { char *got = event_get_data_str(expected[i].evt_id, expected[i].data, true); TEST_STREQ(expected[i].str, got); free(got); } return TEST_OK; } TEST_CASE(event_read_from_file) { FILE *fp; char *fname = NULL; struct uftrace_event *ev; struct uftrace_data handle = { .dirname = ".", }; INIT_LIST_HEAD(&handle.events); xasprintf(&fname, "%s/%s", handle.dirname, EVENT_FILE_NAME); fp = fopen(fname, "w"); TEST_NE(fp, NULL); fprintf(fp, "EVENT: 1000000 uftrace:event\n"); fclose(fp); pr_dbg("testing event read from file\n"); TEST_EQ(read_events_file(&handle), 0); ev = list_first_entry(&handle.events, typeof(*ev), list); TEST_EQ(ev->id, EVENT_ID_USER); TEST_STREQ(ev->provider, "uftrace"); TEST_STREQ(ev->event, "event"); finish_events_file(&handle); TEST_EQ(unlink(fname), 0); free(fname); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/event.h000066400000000000000000000020571455365734300157050ustar00rootroot00000000000000#ifndef UFTRACE_EVENT_H #define UFTRACE_EVENT_H #include #include struct uftrace_data; /* please see man proc(5) for /proc/[pid]/statm */ struct uftrace_proc_statm { uint64_t vmsize; /* total program size in KB */ uint64_t vmrss; /* resident set size in KB */ uint64_t shared; /* shared rss in KB (Rssfile + RssShmem) */ }; struct uftrace_page_fault { uint64_t major; /* major page faults */ uint64_t minor; /* minor page faults */ }; struct uftrace_pmu_cycle { uint64_t cycles; /* cpu cycles */ uint64_t instrs; /* cpu instructions */ }; struct uftrace_pmu_cache { uint64_t refers; /* cache references */ uint64_t misses; /* cache misses */ }; struct uftrace_pmu_branch { uint64_t branch; /* branch instructions */ uint64_t misses; /* branch misses */ }; char *event_get_name(struct uftrace_data *handle, unsigned evt_id); char *event_get_data_str(unsigned evt_id, void *data, bool verbose); void finish_events_file(struct uftrace_data *handle); int read_events_file(struct uftrace_data *handle); #endif /* UFTRACE_EVENT_H */ uftrace-0.15.2/utils/extern.c000066400000000000000000000126151455365734300160650ustar00rootroot00000000000000/** * external data support * * An external data comes with a text file containing following info: * * # TIMESTAMP MESSAGE * 16414531.193431732 this is random text * 16414631.732980320 next message * 16414706.137491843 3rd message * ... */ #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "fstack" #define PR_DOMAIN DBG_FSTACK #include "uftrace.h" #include "utils/fstack.h" #include "utils/utils.h" #define DEFAULT_FILENAME "extern.dat" int setup_extern_data(struct uftrace_data *handle, struct uftrace_opts *opts) { struct uftrace_extern_reader *extn; char *filename; FILE *fp; handle->extn = NULL; filename = opts->extern_data; if (filename == NULL) xasprintf(&filename, "%s/%s", opts->dirname, DEFAULT_FILENAME); fp = fopen(filename, "r"); if (opts->extern_data == NULL) free(filename); if (fp == NULL) { if (errno == ENOENT) return 0; /* report other error code */ pr_dbg("opening external data filed: %m\n"); return -1; } extn = xzalloc(sizeof(*extn)); extn->fp = fp; handle->extn = extn; return 1; } int read_extern_data(struct uftrace_data *handle, struct uftrace_extern_reader *extn) { char buf[EXTERN_DATA_MAX + 64]; char *pos; int len; if (extn == NULL) return -1; if (extn->valid) return 0; retry: do { pos = fgets(buf, sizeof(buf), extn->fp); if (pos == NULL) return -1; /* end of file */ buf[sizeof(buf) - 1] = '\0'; while (isspace(*pos)) pos++; } while (*pos == '#' || *pos == '\n'); /* ignore comment or blank */ extn->time = parse_timestamp(pos); if (!check_time_range(&handle->time_range, extn->time)) goto retry; pos = strpbrk(pos, "\t "); if (pos == NULL) goto retry; /* invalid data */ while (isspace(*pos)) pos++; len = strlen(pos); if (pos[len - 1] == '\n') pos[len - 1] = '\0'; if (len == 0) goto retry; /* no message */ if (unlikely(len >= EXTERN_DATA_MAX)) len = EXTERN_DATA_MAX - 1; strncpy(extn->msg, pos, len); extn->msg[len] = '\0'; extn->valid = true; return 0; } struct uftrace_record *get_extern_record(struct uftrace_extern_reader *extn, struct uftrace_record *rec) { rec->time = extn->time; rec->type = UFTRACE_EVENT; rec->addr = EVENT_ID_EXTERN_DATA; rec->more = 1; rec->magic = RECORD_MAGIC; return rec; } int finish_extern_data(struct uftrace_data *handle) { struct uftrace_extern_reader *extn = handle->extn; if (extn && extn->fp != NULL) { fclose(extn->fp); extn->fp = NULL; free(extn); handle->extn = NULL; } return 0; } #ifdef UNIT_TEST #include #include #include TEST_CASE(fstack_extern_data1) { int fd; struct uftrace_data handle = { .dirname = "extern.test", }; struct uftrace_opts opts = { .dirname = "extern.test", }; const char extern_data[] = "# test data\n" "1234.987654320\n" /* invalid data */ "1234.987654321 first data\n" "1234.987654322 \n" /* empty data */ "1234.123456789 second data\n"; pr_dbg("creating external data file\n"); mkdir("extern.test", 0755); fd = creat("extern.test/" DEFAULT_FILENAME, 0644); TEST_NE(fd, -1); if (write(fd, extern_data, sizeof(extern_data) - 1) < 0) return TEST_NG; close(fd); setup_extern_data(&handle, &opts); pr_dbg("first read should return first data\n"); read_extern_data(&handle, handle.extn); TEST_EQ(handle.extn->valid, true); TEST_EQ(handle.extn->time, 1234987654321ULL); TEST_STREQ(handle.extn->msg, "first data"); pr_dbg("next read should return same data\n"); read_extern_data(&handle, handle.extn); TEST_EQ(handle.extn->time, 1234987654321ULL); TEST_STREQ(handle.extn->msg, "first data"); pr_dbg("after invalidate, a read should return second data\n"); handle.extn->valid = false; read_extern_data(&handle, handle.extn); TEST_EQ(handle.extn->valid, true); TEST_EQ(handle.extn->time, 1234123456789); TEST_STREQ(handle.extn->msg, "second data"); finish_extern_data(&handle); unlink("extern.test/" DEFAULT_FILENAME); rmdir("extern.test"); return TEST_OK; } TEST_CASE(fstack_extern_data2) { int fd; struct uftrace_data handle = { .dirname = "extern.test", }; struct uftrace_opts opts = { .dirname = "extern.test", }; const char test_data[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"; int msg_size = EXTERN_DATA_MAX * 2; char *extern_data; int i; extern_data = xmalloc(msg_size + 1); for (i = 0; i < msg_size; i += sizeof(test_data) - 1) strcpy(&extern_data[i], test_data); pr_dbg("creating external data file\n"); mkdir("extern.test", 0755); fd = creat("extern.test/" DEFAULT_FILENAME, 0644); TEST_NE(fd, -1); if (write(fd, "1234.987654321 ", 15) < 0) return TEST_NG; if (write(fd, extern_data, msg_size) < 0) return TEST_NG; free(extern_data); close(fd); setup_extern_data(&handle, &opts); pr_dbg("read very very long data\n"); read_extern_data(&handle, handle.extn); TEST_EQ(handle.extn->valid, true); TEST_EQ(handle.extn->time, 1234987654321ULL); /* check the first pattern only */ if (strncmp(handle.extn->msg, test_data, sizeof(test_data) - 1)) { pr_dbg("test failed at %s:%d\n", __FILE__, __LINE__ - 1); pr_dbg(" expected = %s\n", test_data); handle.extn->msg[sizeof(test_data) - 1] = '\0'; pr_dbg(" actual = %s\n", handle.extn->msg); return TEST_NG; } finish_extern_data(&handle); unlink("extern.test/" DEFAULT_FILENAME); rmdir("extern.test"); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/field.c000066400000000000000000000154331455365734300156440ustar00rootroot00000000000000#include "utils/field.h" #include "utils/fstack.h" #include "utils/list.h" void print_header(struct list_head *output_fields, const char *prefix, const char *postfix, int space, bool new_line) { struct display_field *field; bool first = true; /* do not print anything if not needed */ if (list_empty(output_fields)) return; list_for_each_entry(field, output_fields, list) { pr_out("%*s", space, first ? prefix : ""); pr_out("%s", field->header); first = false; } pr_out(" %s", postfix); if (new_line) pr_out("\n"); } void print_header_align(struct list_head *output_fields, const char *prefix, const char *postfix, int space, enum align_pos align, bool new_line) { struct display_field *field; bool first = true; /* do not print anything if not needed */ if (list_empty(output_fields)) return; list_for_each_entry(field, output_fields, list) { pr_out("%*s", space, first ? prefix : ""); if (align == ALIGN_LEFT) pr_out("%-*s", field->length, field->header); else pr_out("%*s", field->length, field->header); first = false; } pr_out("%*s", space, " "); pr_out("%s", postfix); if (new_line) pr_out("\n"); } int print_field_data(struct list_head *output_fields, struct field_data *fd, int space) { struct display_field *field; if (list_empty(output_fields)) return 0; list_for_each_entry(field, output_fields, list) { pr_out("%*s", space, ""); field->print(fd); } return 1; } int print_empty_field(struct list_head *output_fields, int space) { struct display_field *field; if (list_empty(output_fields)) return 0; list_for_each_entry(field, output_fields, list) pr_out("%*s", field->length + space, ""); return 1; } void add_field(struct list_head *output_fields, struct display_field *field) { if (field->used) return; pr_dbg("add field \"%s\"\n", field->name); field->used = true; list_add_tail(&field->list, output_fields); } void del_field(struct display_field *field) { if (!field->used) return; pr_dbg("delete field \"%s\"\n", field->name); field->used = false; list_del(&field->list); } static bool check_field_name(struct display_field *field, const char *name) { if (!strcmp(field->name, name)) return true; if (field->alias && !strcmp(field->alias, name)) return true; return false; } void setup_field(struct list_head *output_fields, struct uftrace_opts *opts, setup_default_field_t setup_default_field, struct display_field *field_table[], size_t field_table_size) { struct display_field *field, *tmp; struct strv strv = STRV_INIT; char *str, *p; unsigned i; int j; bool *field_flags; bool all = false; /* default fields */ if (opts->fields == NULL) { setup_default_field(output_fields, opts, field_table); return; } if (!strcmp(opts->fields, "none")) return; field_flags = xcalloc(field_table_size, sizeof(*field_flags)); str = opts->fields; if (*str == '+') { /* prepend default fields */ setup_default_field(output_fields, opts, field_table); for (i = 0; i < field_table_size; i++) { if (field_table[i]->used) field_flags[i] = true; } str++; } strv_split(&strv, str, ","); strv_for_each(&strv, p, j) { if (!strcmp(p, "all")) { all = true; break; } for (i = 0; i < field_table_size; i++) { field = field_table[i]; pr_dbg2("check field \"%s\"\n", field->name); if (!check_field_name(field, p)) continue; field_flags[i] = true; break; } if (i == field_table_size) { pr_out("uftrace: Unknown field name '%s'\n", p); pr_out("uftrace: Possible fields are:"); for (i = 0; i < field_table_size; i++) pr_out(" %s", field_table[i]->name); pr_out("\n"); exit(1); } } strv_free(&strv); list_for_each_entry_safe(field, tmp, output_fields, list) del_field(field); for (i = 0; i < field_table_size; i++) { if (field_flags[i] || all) add_field(output_fields, field_table[i]); } free(field_flags); } #ifdef UNIT_TEST static void print_nothing(struct field_data *fd) { } static void setup_first_field(struct list_head *head, struct uftrace_opts *opts, struct display_field *p_field_table[]) { add_field(head, p_field_table[0]); } #define DEFINE_FIELD(_id, _name, _alias) \ static struct display_field field##_id = { \ .id = _id, \ .name = _name, \ .header = _name, \ .length = 1, \ .print = print_nothing, \ .alias = _alias, \ } DEFINE_FIELD(1, "foo", "FOO"); DEFINE_FIELD(2, "bar", "baz"); DEFINE_FIELD(3, "abc", "xyz"); static struct display_field *test_field_table[] = { &field1, &field2, &field3, }; TEST_CASE(field_setup_default) { LIST_HEAD(output_fields); struct uftrace_opts opts = { .fields = NULL, }; pr_dbg("calling setup_default_field\n"); setup_field(&output_fields, &opts, setup_first_field, test_field_table, ARRAY_SIZE(test_field_table)); TEST_EQ(output_fields.next, &field1.list); TEST_EQ(field1.used, true); TEST_EQ(field2.used, false); TEST_EQ(field3.used, false); return TEST_OK; } TEST_CASE(field_setup_default_plus) { LIST_HEAD(output_fields); struct uftrace_opts opts = { .fields = "+abc", }; pr_dbg("add 'abc' field after the default\n"); setup_field(&output_fields, &opts, setup_first_field, test_field_table, ARRAY_SIZE(test_field_table)); TEST_EQ(output_fields.next, &field1.list); TEST_EQ(field1.list.next, &field3.list); TEST_EQ(field1.used, true); TEST_EQ(field2.used, false); TEST_EQ(field3.used, true); return TEST_OK; } TEST_CASE(field_setup_list) { LIST_HEAD(output_fields); struct uftrace_opts opts = { .fields = "bar,foo", }; pr_dbg("setup fields in a given order\n"); setup_field(&output_fields, &opts, setup_first_field, test_field_table, ARRAY_SIZE(test_field_table)); TEST_EQ(output_fields.next, &field1.list); TEST_EQ(field1.list.next, &field2.list); TEST_EQ(field1.used, true); TEST_EQ(field2.used, true); TEST_EQ(field3.used, false); return TEST_OK; } TEST_CASE(field_setup_list_alias) { LIST_HEAD(output_fields); struct uftrace_opts opts = { .fields = "baz,xyz", }; pr_dbg("setup fields with alias name\n"); setup_field(&output_fields, &opts, setup_first_field, test_field_table, ARRAY_SIZE(test_field_table)); TEST_EQ(output_fields.next, &field2.list); TEST_EQ(field2.list.next, &field3.list); TEST_EQ(field1.used, false); TEST_EQ(field2.used, true); TEST_EQ(field3.used, true); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/field.h000066400000000000000000000042301455365734300156420ustar00rootroot00000000000000#ifndef UFTRACE_FIELD_H #define UFTRACE_FIELD_H #include "utils/fstack.h" #include "utils/list.h" enum align_pos { ALIGN_LEFT, ALIGN_RIGHT, }; /* data for field display */ struct field_data { struct uftrace_task_reader *task; struct uftrace_fstack *fstack; void *arg; }; enum display_field_id { DISPLAY_F_NONE = -1, REPLAY_F_DURATION = 0, REPLAY_F_TID, REPLAY_F_ADDR, REPLAY_F_TIMESTAMP, REPLAY_F_TIMEDELTA, REPLAY_F_ELAPSED, REPLAY_F_TASK, REPLAY_F_MODULE, GRAPH_F_TOTAL_TIME = 0, GRAPH_F_SELF_TIME, GRAPH_F_ADDR, GRAPH_F_TASK_TOTAL_TIME = 0, GRAPH_F_TASK_SELF_TIME, GRAPH_F_TASK_TID, REPORT_F_TOTAL_TIME = 0, REPORT_F_TOTAL_TIME_AVG, REPORT_F_TOTAL_TIME_MIN, REPORT_F_TOTAL_TIME_MAX, REPORT_F_SELF_TIME, REPORT_F_SELF_TIME_AVG, REPORT_F_SELF_TIME_MIN, REPORT_F_SELF_TIME_MAX, REPORT_F_CALL, REPORT_F_SIZE, REPORT_F_TASK_TOTAL_TIME = 0, REPORT_F_TASK_SELF_TIME, REPORT_F_TASK_TID, REPORT_F_TASK_NR_FUNC, }; struct display_field { struct list_head list; enum display_field_id id; const char *name; const char *header; int length; bool used; void (*print)(struct field_data *fd); const char *alias; }; typedef void (*setup_default_field_t)(struct list_head *fields, struct uftrace_opts *, struct display_field *p_field_table[]); static inline uint64_t effective_addr(uint64_t addr) { /* return 48-bit truncated address info */ return addr & ((1ULL << 48) - 1); } void print_header(struct list_head *output_fields, const char *prefix, const char *postfix, int space, bool new_line); void print_header_align(struct list_head *output_fields, const char *prefix, const char *postfix, int space, enum align_pos align, bool new_line); int print_field_data(struct list_head *output_fields, struct field_data *fd, int space); int print_empty_field(struct list_head *output_fields, int space); void add_field(struct list_head *output_fields, struct display_field *field); void del_field(struct display_field *field); void setup_field(struct list_head *output_fields, struct uftrace_opts *opts, setup_default_field_t setup_default_field, struct display_field *field_table[], size_t field_table_size); #endif /* UFTRACE_FIELD_H */ uftrace-0.15.2/utils/filter.c000066400000000000000000002066411455365734300160510ustar00rootroot00000000000000#include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "filter" #define PR_DOMAIN DBG_FILTER #include "libmcount/mcount.h" #include "uftrace.h" #include "utils/dwarf.h" #include "utils/filter.h" #include "utils/list.h" #include "utils/rbtree.h" #include "utils/symbol.h" #include "utils/utils.h" static void snprintf_trigger_read(char *buf, size_t len, enum trigger_read_type type) { buf[0] = '\0'; if (type == TRIGGER_READ_NONE) snprintf(buf, len, "none"); if (type & TRIGGER_READ_PROC_STATM) snprintf(buf, len, "%s%s", buf[0] ? "|" : "", "proc/statm"); if (type & TRIGGER_READ_PAGE_FAULT) snprintf(buf, len, "%s%s", buf[0] ? "|" : "", "page-fault"); if (type & TRIGGER_READ_PMU_CYCLE) snprintf(buf, len, "%s%s", buf[0] ? "|" : "", "pmu-cycle"); if (type & TRIGGER_READ_PMU_CACHE) snprintf(buf, len, "%s%s", buf[0] ? "|" : "", "pmu-cache"); if (type & TRIGGER_READ_PMU_BRANCH) snprintf(buf, len, "%s%s", buf[0] ? "|" : "", "pmu-branch"); } static void print_trigger(struct uftrace_trigger *tr) { if (tr->flags & TRIGGER_FL_CLEAR) pr_dbg("\ttriggers: clear=%#x\n", tr->clear_flags); if (tr->flags & TRIGGER_FL_DEPTH) pr_dbg("\ttrigger: depth %d\n", tr->depth); if (tr->flags & TRIGGER_FL_FILTER) { if (tr->fmode == FILTER_MODE_IN) pr_dbg("\ttrigger: filter IN\n"); else if (tr->fmode == FILTER_MODE_OUT) pr_dbg("\ttrigger: filter OUT\n"); } if (tr->flags & TRIGGER_FL_LOC) { if (tr->lmode == FILTER_MODE_IN) pr_dbg("\ttrigger: location filter IN\n"); else pr_dbg("\ttrigger: location filter OUT\n"); } if (tr->flags & TRIGGER_FL_BACKTRACE) pr_dbg("\ttrigger: backtrace\n"); if (tr->flags & TRIGGER_FL_TRACE) pr_dbg("\ttrigger: trace\n"); if (tr->flags & TRIGGER_FL_TRACE_ON) pr_dbg("\ttrigger: trace_on\n"); if (tr->flags & TRIGGER_FL_TRACE_OFF) pr_dbg("\ttrigger: trace_off\n"); if (tr->flags & TRIGGER_FL_RECOVER) pr_dbg("\ttrigger: recover\n"); if (tr->flags & TRIGGER_FL_FINISH) pr_dbg("\ttrigger: finish\n"); if (tr->flags & TRIGGER_FL_ARGUMENT) { struct uftrace_arg_spec *arg; pr_dbg("\ttrigger: argument\n"); list_for_each_entry(arg, tr->pargs, list) { if (arg->idx == RETVAL_IDX) continue; pr_dbg("\t\t arg%d: %c%d\n", arg->idx, ARG_SPEC_CHARS[arg->fmt], arg->size * 8); } } if (tr->flags & TRIGGER_FL_RETVAL) { struct uftrace_arg_spec *arg; pr_dbg("\ttrigger: return value\n"); list_for_each_entry(arg, tr->pargs, list) { if (arg->idx != RETVAL_IDX) continue; pr_dbg("\t\t retval%d: %c%d\n", arg->idx, ARG_SPEC_CHARS[arg->fmt], arg->size * 8); } } if (tr->flags & TRIGGER_FL_COLOR) pr_dbg("\ttrigger: color '%c'\n", tr->color); if (tr->flags & TRIGGER_FL_TIME_FILTER) pr_dbg("\ttrigger: time filter %" PRIu64 "\n", tr->time); if (tr->flags & TRIGGER_FL_CALLER) pr_dbg("\ttrigger: caller filter\n"); if (tr->flags & TRIGGER_FL_SIZE_FILTER) pr_dbg("\ttrigger: size filter %u\n", tr->size); if (tr->flags & TRIGGER_FL_READ) { char buf[1024]; snprintf_trigger_read(buf, sizeof(buf), tr->read); pr_dbg("\ttrigger: read (%s)\n", buf); } } /** * uftrace_count_filter - count matching filters in @root * @root - root of rbtree which has filters * @flag - filter flag to match */ int uftrace_count_filter(struct rb_root *root, unsigned long flag) { struct rb_node *entry; struct uftrace_filter *iter; int count = 0; entry = rb_first(root); while (entry) { iter = rb_entry(entry, struct uftrace_filter, node); if (iter->trigger.flags & flag) count++; entry = rb_next(entry); } return count; } static bool match_ip(struct uftrace_filter *filter, uint64_t addr) { return filter->start <= addr && addr < filter->end; } /** * uftrace_match_filter - try to match @ip with filters in @root * @addr - instruction address to match * @root - root of rbtree which has filters * @tr - trigger data */ struct uftrace_filter *uftrace_match_filter(uint64_t addr, struct rb_root *root, struct uftrace_trigger *tr) { struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; struct uftrace_filter *iter; while (*p) { parent = *p; iter = rb_entry(parent, struct uftrace_filter, node); if (match_ip(iter, addr)) { *tr = iter->trigger; pr_dbg2("filter match: %s\n", iter->name); if (dbg_domain[DBG_FILTER] >= 3) print_trigger(tr); return iter; } if (iter->start > addr) p = &parent->rb_left; else p = &parent->rb_right; } return NULL; } static void add_arg_spec(struct list_head *arg_list, struct uftrace_arg_spec *arg, bool exact_match) { bool found = false; struct uftrace_arg_spec *oarg, *narg; list_for_each_entry(oarg, arg_list, list) { if (arg->type != oarg->type) continue; switch (arg->type) { case ARG_TYPE_INDEX: case ARG_TYPE_FLOAT: if (arg->idx == oarg->idx) found = true; break; case ARG_TYPE_REG: if (arg->reg_idx == oarg->reg_idx) found = true; break; case ARG_TYPE_STACK: if (arg->stack_ofs == oarg->stack_ofs) found = true; break; } if (found) break; } if (found) { /* do not overwrite exact match by regex match */ if (exact_match || !oarg->exact) { free(oarg->type_name); oarg->type_name = NULL; oarg->fmt = arg->fmt; oarg->size = arg->size; oarg->exact = exact_match; oarg->type = arg->type; oarg->reg_idx = arg->reg_idx; oarg->struct_reg_cnt = arg->struct_reg_cnt; if (arg->type_name) oarg->type_name = xstrdup(arg->type_name); if (arg->struct_reg_cnt) { memcpy(oarg->struct_regs, arg->struct_regs, sizeof(arg->struct_regs)); } } } else { narg = xmalloc(sizeof(*narg)); narg->idx = arg->idx; narg->fmt = arg->fmt; narg->size = arg->size; narg->exact = exact_match; narg->type = arg->type; narg->reg_idx = arg->reg_idx; narg->struct_reg_cnt = arg->struct_reg_cnt; if (arg->type_name) narg->type_name = xstrdup(arg->type_name); else narg->type_name = NULL; if (arg->struct_reg_cnt) { memcpy(narg->struct_regs, arg->struct_regs, sizeof(arg->struct_regs)); } list_add_tail(&narg->list, &oarg->list); } } /** * update_trigger - update the trigger flags and related filter data * @filter - trigger tree entry holding filter parameters * @tr - trigger flags to apply * @exact_match - if symbol is exact or regex match (exact match has precedence) */ void update_trigger(struct uftrace_filter *filter, struct uftrace_trigger *tr, bool exact_match) { filter->trigger.flags |= tr->flags; if (tr->flags & TRIGGER_FL_CLEAR) { filter->trigger.flags &= ~tr->clear_flags; if (tr->clear_flags & TRIGGER_FL_FILTER) tr->fmode = filter->trigger.fmode; /* read from tree before deleting */ } if (tr->flags & TRIGGER_FL_DEPTH) filter->trigger.depth = tr->depth; if (tr->flags & TRIGGER_FL_FILTER) filter->trigger.fmode = tr->fmode; if (tr->flags & TRIGGER_FL_LOC) filter->trigger.lmode = tr->lmode; if (tr->flags & TRIGGER_FL_TRACE_ON) filter->trigger.flags &= ~TRIGGER_FL_TRACE_OFF; if (tr->flags & TRIGGER_FL_TRACE_OFF) filter->trigger.flags &= ~TRIGGER_FL_TRACE_ON; if (tr->flags & (TRIGGER_FL_ARGUMENT | TRIGGER_FL_RETVAL)) { struct uftrace_arg_spec *arg; list_for_each_entry(arg, tr->pargs, list) add_arg_spec(&filter->args, arg, exact_match); } if (tr->flags & TRIGGER_FL_COLOR) filter->trigger.color = tr->color; if (tr->flags & TRIGGER_FL_TIME_FILTER) filter->trigger.time = tr->time; if (tr->flags & TRIGGER_FL_READ) filter->trigger.read |= tr->read; if (tr->flags & TRIGGER_FL_SIZE_FILTER) filter->trigger.size = tr->size; } /** * prune_void_filter - remove filters without trigger flags from the tree * @node - filter node to check * @root - root of the rbtree */ static void prune_void_filter(struct rb_node *node, struct rb_root *root) { struct uftrace_filter *iter = rb_entry(node, struct uftrace_filter, node); if (!iter->trigger.flags) { rb_erase(node, root); pr_dbg3("prune void filter %s\n", iter->name); } } /** * update_filter - add, change or remove registered filter * @root - registered filters RB tree * @filter - filter tree node to update * @tr - trigger flags and data to apply * @exact_match - if symbol name is exact or regex match (exact precedes) * @dinfo - debug information * @return - status: 1 when update is made, 0 otherwise */ static int update_filter(struct rb_root *root, struct uftrace_filter *filter, struct uftrace_trigger *tr, struct uftrace_mmap *map, bool exact_match, struct uftrace_dbg_info *dinfo, struct uftrace_filter_setting *setting) { struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; struct uftrace_filter *iter, *new; struct uftrace_filter *auto_arg = NULL; struct uftrace_filter *auto_ret = NULL; unsigned long orig_flags = tr->flags; if ((tr->flags & TRIGGER_FL_ARGUMENT) && list_empty(tr->pargs)) { auto_arg = find_auto_argspec(filter, tr, dinfo, setting); if (auto_arg == NULL) tr->flags &= ~TRIGGER_FL_ARGUMENT; } if ((tr->flags & TRIGGER_FL_RETVAL) && list_empty(tr->pargs)) { auto_ret = find_auto_retspec(filter, tr, dinfo, setting); if (auto_ret == NULL) tr->flags &= ~TRIGGER_FL_RETVAL; } /* remove unnecessary filters might be set by --auto-args */ if (tr->flags == TRIGGER_FL_AUTO_ARGS) { /* restored for regex filter */ tr->flags = orig_flags; return 0; } pr_dbg2("add filter for %s (flags = %x)\n", filter->name, tr->flags); if (dbg_domain[DBG_FILTER] >= 3) print_trigger(tr); filter->start += map->start; filter->end += map->start; while (*p) { parent = *p; iter = rb_entry(parent, struct uftrace_filter, node); if (iter->start == filter->start) { unsigned long args_flags = tr->flags; args_flags &= ~TRIGGER_FL_AUTO_ARGS; /* ignore auto-args if it already has argspec */ if ((tr->flags & TRIGGER_FL_AUTO_ARGS) && (iter->trigger.flags & args_flags)) { tr->flags = orig_flags; return 0; } update_trigger(iter, tr, exact_match); if (auto_arg) update_trigger(iter, &auto_arg->trigger, exact_match); if (auto_ret) update_trigger(iter, &auto_ret->trigger, exact_match); tr->flags = orig_flags; if (tr->flags & TRIGGER_FL_CLEAR) prune_void_filter(parent, root); return 1; } if (iter->start > filter->start) p = &parent->rb_left; else p = &parent->rb_right; } new = xmalloc(sizeof(*new)); memcpy(new, filter, sizeof(*new)); new->trigger.flags = 0; new->trigger.read = 0; INIT_LIST_HEAD(&new->args); new->trigger.pargs = &new->args; update_trigger(new, tr, exact_match); if (auto_arg) update_trigger(new, &auto_arg->trigger, exact_match); if (auto_ret) update_trigger(new, &auto_ret->trigger, exact_match); tr->flags = orig_flags; rb_link_node(&new->node, parent, p); rb_insert_color(&new->node, root); return 1; } struct { enum uftrace_pattern_type type; const char *name; } filter_patterns[] = { { PATT_SIMPLE, "simple" }, { PATT_REGEX, "regex" }, { PATT_GLOB, "glob" }, }; void init_locfilter_pattern(enum uftrace_pattern_type type, struct uftrace_pattern *p, char *str) { char *converted; if (strpbrk(str, REGEX_CHARS) == NULL) { /* remove trailing '/' */ if (str[strlen(str) - 1] == '/') str[strlen(str) - 1] = '\0'; /* remove preceding '/' */ if (str[0] == '/') str += 1; xasprintf(&converted, "%s%s%s", "((.*/)*)", str, "($|(/.*))"); p->type = PATT_REGEX; p->patt = converted; } else { /* remaining PATT_REGEX and PATT_GLOB cases */ p->type = type; p->patt = xstrdup(str); } if (p->type == PATT_REGEX) { /* to handle full demangled operator new and delete specially */ const char *str_operator = "operator "; if (!strncmp(p->patt, str_operator, 9)) { p->type = PATT_SIMPLE; } else if (regcomp(&p->re, p->patt, REG_NOSUB | REG_EXTENDED)) { pr_dbg("regex pattern failed: %s\n", p->patt); p->type = PATT_SIMPLE; } } } void init_filter_pattern(enum uftrace_pattern_type type, struct uftrace_pattern *p, char *str) { if (strpbrk(str, REGEX_CHARS) == NULL) type = PATT_SIMPLE; p->type = type; p->patt = xstrdup(str); if (type == PATT_REGEX) { /* to handle full demangled operator new and delete specially */ const char *str_operator = "operator "; if (!strncmp(str, str_operator, 9)) { p->type = PATT_SIMPLE; } else if (regcomp(&p->re, str, REG_NOSUB | REG_EXTENDED)) { pr_dbg("regex pattern failed: %s\n", str); p->type = PATT_SIMPLE; } } } bool match_filter_pattern(struct uftrace_pattern *p, char *name) { switch (p->type) { case PATT_SIMPLE: return !strcmp(p->patt, name); case PATT_REGEX: return !regexec(&p->re, name, 0, NULL, 0); case PATT_GLOB: return !fnmatch(p->patt, name, 0); default: return false; } } bool match_location_filter(struct uftrace_pattern *p, struct uftrace_dbg_info *dinfo, size_t loc_idx) { char *loc; if (!dinfo || loc_idx >= dinfo->nr_locs || !dinfo->locs[loc_idx].file) return false; loc = dinfo->locs[loc_idx].file->name; return match_filter_pattern(p, loc); } void free_filter_pattern(struct uftrace_pattern *p) { free(p->patt); p->patt = NULL; if (p->type == PATT_REGEX) regfree(&p->re); p->type = PATT_NONE; } enum uftrace_pattern_type parse_filter_pattern(const char *str) { size_t i; for (i = 0; i < ARRAY_SIZE(filter_patterns); i++) { if (!strcmp(str, filter_patterns[i].name)) return filter_patterns[i].type; } return PATT_NONE; } const char *get_filter_pattern(enum uftrace_pattern_type ptype) { size_t i; for (i = 0; i < ARRAY_SIZE(filter_patterns); i++) { if (filter_patterns[i].type == ptype) return filter_patterns[i].name; } return "none"; } /* argument_spec = arg1/i32,arg2/x64%reg,arg3%stack+1,... */ static int parse_argument_spec(char *str, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { struct uftrace_arg_spec *arg; if (!isdigit(str[3])) { pr_use("skipping invalid argument: %s\n", str); return -1; } arg = parse_argspec(str, setting); if (arg == NULL) return -1; tr->flags |= TRIGGER_FL_ARGUMENT; list_add_tail(&arg->list, tr->pargs); return 0; } /* argument_spec = retval/i32 or retval/x64 ... */ static int parse_retval_spec(char *str, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { struct uftrace_arg_spec *arg; arg = parse_argspec(str, setting); if (arg == NULL) return -1; tr->flags |= TRIGGER_FL_RETVAL; list_add_tail(&arg->list, tr->pargs); return 0; } /* argument_spec = fparg1/32,fparg2/64%stack+1,... */ static int parse_float_argument_spec(char *str, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { struct uftrace_arg_spec *arg; if (!isdigit(str[5])) { pr_use("skipping invalid argument: %s\n", str); return -1; } arg = parse_argspec(str, setting); if (arg == NULL) return -1; tr->flags |= TRIGGER_FL_ARGUMENT; list_add_tail(&arg->list, tr->pargs); return 0; } static int parse_depth_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { tr->flags |= TRIGGER_FL_DEPTH; tr->depth = strtoul(action + 6, NULL, 10); if (tr->depth < 0 || tr->depth > MCOUNT_RSTACK_MAX) { pr_use("skipping invalid trigger depth: %d\n", tr->depth); return -1; } return 0; } static int parse_time_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { tr->flags |= TRIGGER_FL_TIME_FILTER; tr->time = parse_time(action + 5, 3); return 0; } static int parse_size_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { tr->flags |= TRIGGER_FL_SIZE_FILTER; tr->size = strtoul(action + 5, NULL, 10); return 0; } static int parse_read_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { const char *target = action + 5; if (!strcmp(target, "proc/statm")) tr->read |= TRIGGER_READ_PROC_STATM; if (!strcmp(target, "page-fault")) tr->read |= TRIGGER_READ_PAGE_FAULT; if (!strcmp(target, "pmu-cycle")) tr->read |= TRIGGER_READ_PMU_CYCLE; if (!strcmp(target, "pmu-cache")) tr->read |= TRIGGER_READ_PMU_CACHE; if (!strcmp(target, "pmu-branch")) tr->read |= TRIGGER_READ_PMU_BRANCH; /* set READ flag only if valid type set */ if (tr->read) tr->flags |= TRIGGER_FL_READ; return 0; } static int parse_color_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { const char *color = action + 6; if (!strcmp(color, "red")) tr->color = COLOR_CODE_RED; else if (!strcmp(color, "green")) tr->color = COLOR_CODE_GREEN; else if (!strcmp(color, "blue")) tr->color = COLOR_CODE_BLUE; else if (!strcmp(color, "yellow")) tr->color = COLOR_CODE_YELLOW; else if (!strcmp(color, "magenta")) tr->color = COLOR_CODE_MAGENTA; else if (!strcmp(color, "cyan")) tr->color = COLOR_CODE_CYAN; else if (!strcmp(color, "bold")) tr->color = COLOR_CODE_BOLD; else if (!strcmp(color, "gray")) tr->color = COLOR_CODE_GRAY; else { pr_use("ignoring invalid color: %s\n", color); return 0; } tr->flags |= TRIGGER_FL_COLOR; return 0; } static int parse_trace_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { action += 5; if (*action == '_' || *action == '-') action++; if (*action == '\0') tr->flags |= TRIGGER_FL_TRACE; else if (!strcasecmp(action, "on")) tr->flags |= TRIGGER_FL_TRACE_ON; else if (!strcasecmp(action, "off")) tr->flags |= TRIGGER_FL_TRACE_OFF; else pr_use("skipping invalid trace action: %s\n", action); return 0; } static int parse_backtrace_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { tr->flags |= TRIGGER_FL_BACKTRACE; return 0; } static int parse_recover_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { tr->flags |= TRIGGER_FL_RECOVER; return 0; } static int parse_finish_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { tr->flags |= TRIGGER_FL_FINISH; return 0; } static int parse_filter_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { tr->flags |= TRIGGER_FL_FILTER; tr->fmode = FILTER_MODE_IN; return 0; } static int parse_notrace_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { tr->flags |= TRIGGER_FL_FILTER; tr->fmode = FILTER_MODE_OUT; return 0; } static int parse_auto_args_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { tr->flags |= TRIGGER_FL_ARGUMENT | TRIGGER_FL_RETVAL; return 0; } static int parse_caller_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { tr->flags |= TRIGGER_FL_CALLER; return 0; } static int parse_hide_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { tr->flags |= TRIGGER_FL_HIDE; return 0; } static int parse_clear_action(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting) { struct strv acts = STRV_INIT; char *pos = NULL; int j; tr->flags |= TRIGGER_FL_CLEAR; if (strlen(action) == 5) { tr->clear_flags = ~0; return 0; } if (action[5] != '=') { pr_use("skipping invalid action: %s\n", action); return -1; } /* action = "clear=act1+act2+..." */ pos = action + 6; strv_split(&acts, pos, "+"); strv_for_each(&acts, pos, j) { if (!strcmp(pos, "arg") || !strcmp(pos, "fparg")) tr->clear_flags |= TRIGGER_FL_ARGUMENT; else if (!strcmp(pos, "retval")) tr->clear_flags |= TRIGGER_FL_RETVAL; else if (!strcmp(pos, "filter") || !strcmp(pos, "notrace")) tr->clear_flags |= TRIGGER_FL_FILTER; else if (!strcmp(pos, "depth")) tr->clear_flags |= TRIGGER_FL_DEPTH; else if (!strcmp(pos, "time")) tr->clear_flags |= TRIGGER_FL_TIME_FILTER; else if (!strcmp(pos, "size")) tr->clear_flags |= TRIGGER_FL_SIZE_FILTER; else if (!strcmp(pos, "hide")) tr->clear_flags |= TRIGGER_FL_HIDE; else if (!strcmp(pos, "trace")) tr->clear_flags |= TRIGGER_FL_TRACE | TRIGGER_FL_TRACE_ON | TRIGGER_FL_TRACE_OFF; else if (!strcmp(pos, "finish")) tr->clear_flags |= TRIGGER_FL_FINISH; else if (!strcmp(pos, "read")) tr->clear_flags |= TRIGGER_FL_READ; else if (!strcmp(pos, "color")) tr->clear_flags |= TRIGGER_FL_COLOR; else if (!strcmp(pos, "backtrace")) tr->clear_flags |= TRIGGER_FL_BACKTRACE; else if (!strcmp(pos, "recover")) tr->clear_flags |= TRIGGER_FL_RECOVER; else pr_use("skipping invalid clear argument: %s\n", pos); } strv_free(&acts); return 0; } struct trigger_action_parser { const char *name; int (*parse)(char *action, struct uftrace_trigger *tr, struct uftrace_filter_setting *setting); enum trigger_flag compat_flags; /* flags the action is restricted to */ }; static const struct trigger_action_parser actions[] = { { "arg", parse_argument_spec, TRIGGER_FL_ARGUMENT, }, { "fparg", parse_float_argument_spec, TRIGGER_FL_ARGUMENT, }, { "retval", parse_retval_spec, TRIGGER_FL_RETVAL, }, { "filter", parse_filter_action, TRIGGER_FL_FILTER, }, { "notrace", parse_notrace_action, TRIGGER_FL_FILTER, }, { "depth=", parse_depth_action, TRIGGER_FL_FILTER, }, { "time=", parse_time_action, TRIGGER_FL_FILTER, }, { "size=", parse_size_action, TRIGGER_FL_FILTER, }, { "caller", parse_caller_action, TRIGGER_FL_FILTER, }, { "hide", parse_hide_action, TRIGGER_FL_FILTER, }, { "trace", parse_trace_action, TRIGGER_FL_SIGNAL, }, { "finish", parse_finish_action, TRIGGER_FL_SIGNAL, }, { "read=", parse_read_action, }, { "color=", parse_color_action, }, { "backtrace", parse_backtrace_action, }, { "recover", parse_recover_action, }, { "auto-args", parse_auto_args_action, }, { "clear", parse_clear_action, TRIGGER_FL_FILTER | TRIGGER_FL_CALLER, }, }; int setup_trigger_action(char *str, struct uftrace_trigger *tr, char **module, unsigned long orig_flags, struct uftrace_filter_setting *setting) { char *pos = strchr(str, '@'); struct strv acts = STRV_INIT; int ret = -1; size_t i; int j; if (module != NULL) *module = NULL; if (pos == NULL) return 0; *pos++ = '\0'; strv_split(&acts, pos, ","); strv_for_each(&acts, pos, j) { for (i = 0; i < ARRAY_SIZE(actions); i++) { const struct trigger_action_parser *action = &actions[i]; if (strncasecmp(pos, action->name, strlen(action->name))) continue; if (orig_flags && !(orig_flags & action->compat_flags)) break; /* ignore incompatible actions */ if (action->parse(pos, tr, setting) < 0) goto out; break; } /* if it's not an action, treat it as a module name */ if (i == ARRAY_SIZE(actions) && module != NULL) { if (*module) pr_use("ignoring extra module: %s\n", pos); else *module = xstrdup(pos); } } if (tr->flags & TRIGGER_FL_CLEAR) { if (orig_flags) /* '@clear' suffix for options other then -T/--trigger */ tr->clear_flags = orig_flags; else { /* preserve flag if set and cleared e.g. -T func@act,clear=act applies act */ tr->clear_flags &= ~tr->flags; } } ret = 0; out: if (ret < 0 && module != NULL) free(*module); strv_free(&acts); return ret; } /** * update_trigger_entry - match symbol names to update their filter * @root - RB tree of registered filters * @patt - matching pattern type * @tr - trigger data and flags to apply * @return - status: count of updated filters */ static int update_trigger_entry(struct rb_root *root, struct uftrace_pattern *patt, struct uftrace_trigger *tr, struct uftrace_mmap *map, struct uftrace_filter_setting *setting) { struct uftrace_filter filter; struct uftrace_symtab *symtab = &map->mod->symtab; struct uftrace_dbg_info *dinfo = &map->mod->dinfo; struct uftrace_symbol *sym; size_t i; int ret = 0; for (i = 0; i < symtab->nr_sym; i++) { sym = &symtab->sym[i]; if (tr->flags == TRIGGER_FL_LOC) { if (!match_location_filter(patt, dinfo, i)) continue; } else { if (!match_filter_pattern(patt, sym->name)) continue; } if (setting->plt_only && sym->type != ST_PLT_FUNC) continue; filter.name = sym->name; filter.start = sym->addr; filter.end = sym->addr + sym->size; ret += update_filter(root, &filter, tr, map, patt->type == PATT_SIMPLE, dinfo, setting); } return ret; } /** * setup_trigger - register filter and set trigger data for matching entries * @filter_str - symbol and action specification * @sinfo - symbol information to find symbol address * @triggers - rbtree of registered filters and associated counters * @flags - trigger flags to apply * @setting - filter settings */ static void setup_trigger(char *filter_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, unsigned long flags, struct uftrace_filter_setting *setting) { struct strv filters = STRV_INIT; char *name; int j; if (filter_str == NULL) return; strv_split(&filters, filter_str, ";"); strv_for_each(&filters, name, j) { LIST_HEAD(args); struct uftrace_trigger tr = { .flags = flags, .pargs = &args, }; int ret = 0; char *module = NULL; struct uftrace_arg_spec *arg; struct uftrace_mmap *map; struct uftrace_pattern patt = { .type = PATT_NONE, }; if (setup_trigger_action(name, &tr, &module, flags, setting) < 0) goto next; /* skip unintended kernel symbols */ if (module && has_kernel_opt(module) && !setting->allow_kernel) goto next; if (flags & TRIGGER_FL_FILTER) { if (name[0] == '!') { tr.fmode = FILTER_MODE_OUT; name++; } else tr.fmode = FILTER_MODE_IN; } if (flags & TRIGGER_FL_LOC) { if (name[0] == '!') { tr.lmode = FILTER_MODE_OUT; name++; } else tr.lmode = FILTER_MODE_IN; } /* use demangled name for triggers (some auto-args need it) */ name = demangle(name); if (flags & TRIGGER_FL_LOC) init_locfilter_pattern(setting->ptype, &patt, name); else init_filter_pattern(setting->ptype, &patt, name); free(name); if (module) { if (!strcasecmp(module, "PLT")) { setting->plt_only = true; ret += update_trigger_entry(&triggers->root, &patt, &tr, sinfo->exec_map, setting); setting->plt_only = false; } else if (has_kernel_opt(module)) { struct uftrace_mmap kernel_map = { .mod = get_kernel_module(), }; ret = update_trigger_entry(&triggers->root, &patt, &tr, &kernel_map, setting); } else { map = find_map_by_name(sinfo, module); if (map && map->mod) { ret = update_trigger_entry(&triggers->root, &patt, &tr, map, setting); } } } else { for_each_map(sinfo, map) { /* some modules don't have symbol table */ if (map->mod == NULL) continue; ret += update_trigger_entry(&triggers->root, &patt, &tr, map, setting); } } if (ret > 0 && (tr.flags & TRIGGER_FL_FILTER)) { if (tr.fmode == FILTER_MODE_IN) { if (tr.clear_flags & TRIGGER_FL_FILTER) triggers->filter_count -= ret; else triggers->filter_count += ret; } pr_dbg4("filter IN count: %d\n", triggers->filter_count); } if (ret > 0 && (tr.flags & TRIGGER_FL_LOC)) { if (tr.lmode == FILTER_MODE_IN) triggers->loc_count += ret; } if (ret > 0 && (tr.flags & TRIGGER_FL_CALLER)) { if (tr.clear_flags & TRIGGER_FL_CALLER) triggers->caller_count -= ret; else triggers->caller_count += ret; pr_dbg4("caller filter count: %d\n", triggers->caller_count); } next: free_filter_pattern(&patt); free(module); while (!list_empty(&args)) { arg = list_first_entry(&args, typeof(*arg), list); list_del(&arg->list); free_arg_spec(arg); } } strv_free(&filters); } /** * uftrace_setup_filter - construct rbtree of filters * @filter_str - CSV of filter string * @sinfo - symbol information to find symbol address * @root - root of filters rbtree * @count - opt-in filter count * @setting - filter settings */ void uftrace_setup_filter(char *filter_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, struct uftrace_filter_setting *setting) { setup_trigger(filter_str, sinfo, triggers, TRIGGER_FL_FILTER, setting); } /** * uftrace_setup_trigger - construct rbtree of triggers * @trigger_str - CSV of trigger string (FUNC @ act) * @sinfo - symbol information to find symbol address * @root - root of resulting rbtree * @count - registered opt-in filter count * @setting - filter settings */ void uftrace_setup_trigger(char *trigger_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, struct uftrace_filter_setting *setting) { setup_trigger(trigger_str, sinfo, triggers, 0, setting); } /** * uftrace_setup_argument - construct rbtree of argument * @args_str - CSV of argument string (FUNC @ arg) * @sinfo - symbol information to find symbol address * @root - root of resulting rbtree * @setting - filter settings */ void uftrace_setup_argument(char *args_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, struct uftrace_filter_setting *setting) { unsigned long flags = TRIGGER_FL_ARGUMENT; if (setting->auto_args) flags |= TRIGGER_FL_AUTO_ARGS; setup_trigger(args_str, sinfo, triggers, flags, setting); } /** * uftrace_setup_retval - construct rbtree of retval * @retval_str - CSV of return value string (FUNC @ arg) * @sinfo - symbol information to find symbol address * @root - root of resulting rbtree * @setting - filter settings */ void uftrace_setup_retval(char *retval_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, struct uftrace_filter_setting *setting) { unsigned long flags = TRIGGER_FL_RETVAL; if (setting->auto_args) flags |= TRIGGER_FL_AUTO_ARGS; setup_trigger(retval_str, sinfo, triggers, flags, setting); } /** * uftrace_setup_caller_filter - add caller filters to rbtree * @filter_str - CSV of filter string * @sinfo - symbol information to find symbol address * @root - root of resulting rbtree * @count - counter for registered caller filters * @setting - filter settings */ void uftrace_setup_caller_filter(char *filter_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, struct uftrace_filter_setting *setting) { setup_trigger(filter_str, sinfo, triggers, TRIGGER_FL_CALLER, setting); } /** * uftrace_setup_hide_filter - add hide filters to rbtree * @filter_str - CSV of filter string * @sinfo - symbol information to find symbol address * @root - root of resulting rbtree * @setting - filter settings */ void uftrace_setup_hide_filter(char *filter_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, struct uftrace_filter_setting *setting) { setup_trigger(filter_str, sinfo, triggers, TRIGGER_FL_HIDE, setting); } /** * uftrace_setup_loc_filter - add source location filters to rbtree * @filter_str - CSV of filter string * @sinfo - symbol information to find symbol address * @root - root of resulting rbtree * @count - opt-in loc filter count * @setting - filter settings */ void uftrace_setup_loc_filter(char *filter_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, struct uftrace_filter_setting *setting) { setup_trigger(filter_str, sinfo, triggers, TRIGGER_FL_LOC, setting); } /** * deep_copy_filter - perform a deep of a filter structure * @old - structure to copy * @return - allocated deep copy of @old */ static struct uftrace_filter *deep_copy_filter(struct uftrace_filter *old) { struct uftrace_filter *new; struct uftrace_arg_spec *arg, *arg_copy; new = xmalloc(sizeof(*new)); /* copy the whole structure */ memcpy(new, old, sizeof(*old)); /* deep copy nested argspec list */ INIT_LIST_HEAD(&new->args); list_for_each_entry(arg, &old->args, list) { arg_copy = xmalloc(sizeof(*arg_copy)); memcpy(arg_copy, arg, sizeof(*arg)); if (arg->type_name) arg_copy->type_name = xstrdup(arg->type_name); list_add_tail(&arg_copy->list, &new->args); } /* deep copy nested trigger.pargs */ new->trigger.pargs = &new->args; return new; } /** * deep_copy_triggers - recursively perform a deep copy of a rbtree with * 'struct uftrace_filter' nodes * @dest - pointer to the address of the copy (modified in place) * @src - original rbtree */ static void deep_copy_triggers(struct rb_node **dest, struct rb_node *src) { struct uftrace_filter *old, *new; if (!src) { *dest = NULL; return; } old = rb_entry(src, struct uftrace_filter, node); new = deep_copy_filter(old); *dest = &new->node; if (src->rb_left) { deep_copy_triggers(&(*dest)->rb_left, src->rb_left); (*dest)->rb_left->rb_parent_color = (unsigned long)new & ~1; (*dest)->rb_left->rb_parent_color |= rb_color(src->rb_left); } if (src->rb_right) { deep_copy_triggers(&(*dest)->rb_right, src->rb_right); (*dest)->rb_right->rb_parent_color = (unsigned long)new & ~1; (*dest)->rb_right->rb_parent_color |= rb_color(src->rb_right); } } /** * deep_copy_triggers - deep copy an rbtree containing filters * @src - root of the rbtree to copy * @return - root of the deep copy */ struct uftrace_triggers_info uftrace_deep_copy_triggers(struct uftrace_triggers_info *src) { struct uftrace_triggers_info new = *src; new.root = RB_ROOT; deep_copy_triggers(&new.root.rb_node, src->root.rb_node); return new; } /** * uftrace_cleanup_filter - delete filters in rbtree * @root - root of the filter rbtree */ void uftrace_cleanup_filter(struct rb_root *root) { struct rb_node *node; struct uftrace_filter *filter; struct uftrace_arg_spec *arg, *tmp; while (!RB_EMPTY_ROOT(root)) { node = rb_first(root); filter = rb_entry(node, struct uftrace_filter, node); rb_erase(node, root); list_for_each_entry_safe(arg, tmp, &filter->args, list) { list_del(&arg->list); free_arg_spec(arg); } free(filter); } } /** * uftrace_cleanup_triggers - delete filters and reset counters * @triggers - triggers info */ void uftrace_cleanup_triggers(struct uftrace_triggers_info *triggers) { uftrace_cleanup_filter(&triggers->root); triggers->filter_count = 0; triggers->caller_count = 0; triggers->loc_count = 0; } /** * uftrace_print_filter - print all filters in rbtree * @root - root of the filter rbtree */ void uftrace_print_filter(struct rb_root *root) { struct rb_node *node; struct uftrace_filter *filter; node = rb_first(root); while (node) { filter = rb_entry(node, struct uftrace_filter, node); pr_dbg("%lx-%lx: %s\n", filter->start, filter->end, filter->name); print_trigger(&filter->trigger); node = rb_next(node); } } char *uftrace_clear_kernel(char *filter_str) { struct strv filters = STRV_INIT; char *pos, *ret = NULL; int j; /* check filter string contains a kernel filter */ if (filter_str == NULL) return NULL; if (has_kernel_filter(filter_str) == NULL) return xstrdup(filter_str); strv_split(&filters, filter_str, ";"); strv_for_each(&filters, pos, j) { if (has_kernel_filter(pos) == NULL) ret = strjoin(ret, pos, ";"); } strv_free(&filters); return ret; } #ifdef UNIT_TEST static void filter_test_load_symtabs(struct uftrace_sym_info *sinfo) { static struct uftrace_symbol syms[] = { { 0x1000, 0x1000, ST_GLOBAL_FUNC, "foo::foo" }, { 0x2000, 0x1000, ST_GLOBAL_FUNC, "foo::bar" }, { 0x3000, 0x1000, ST_GLOBAL_FUNC, "foo::baz1" }, { 0x4000, 0x1000, ST_GLOBAL_FUNC, "foo::baz2" }, { 0x5000, 0x1000, ST_GLOBAL_FUNC, "foo::baz3" }, { 0x6000, 0x1000, ST_GLOBAL_FUNC, "foo::~foo" }, { 0x21000, 0x1000, ST_PLT_FUNC, "malloc" }, { 0x22000, 0x1000, ST_PLT_FUNC, "free" }, }; static struct uftrace_module mod = { .symtab = { .sym = syms, .nr_sym = ARRAY_SIZE(syms), }, .dinfo = { .loaded = true, } }; static struct uftrace_mmap map = { .mod = &mod, .start = 0x0, .end = 0x24000, }; mod.symtab.sym = syms; mod.symtab.nr_sym = ARRAY_SIZE(syms); sinfo->maps = ↦ sinfo->exec_map = ↦ sinfo->loaded = true; } enum filter_mode get_filter_mode(int count) { return count > 0 ? FILTER_MODE_IN : FILTER_MODE_OUT; } TEST_CASE(filter_setup_simple) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, }; struct rb_node *node; struct uftrace_filter *filter; struct uftrace_filter_setting setting = { .ptype = PATT_SIMPLE, }; filter_test_load_symtabs(&sinfo); pr_dbg("checking simple match\n"); uftrace_setup_filter("foo::bar", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::bar"); TEST_EQ(filter->start, 0x2000UL); TEST_EQ(filter->end, 0x2000UL + 0x1000UL); uftrace_cleanup_filter(&triggers.root); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); pr_dbg("checking destructor match\n"); uftrace_setup_filter("foo::~foo", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::~foo"); TEST_EQ(filter->start, 0x6000UL); TEST_EQ(filter->end, 0x6000UL + 0x1000UL); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); pr_dbg("checking unknown symbol\n"); uftrace_setup_filter("invalid_name", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } TEST_CASE(filter_setup_regex) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, }; struct rb_node *node; struct uftrace_filter *filter; struct uftrace_filter_setting setting = { .ptype = PATT_REGEX, }; filter_test_load_symtabs(&sinfo); pr_dbg("try to match with regex pattern: ^foo::b\n"); uftrace_setup_filter("^foo::b", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::bar"); TEST_EQ(filter->start, 0x2000UL); TEST_EQ(filter->end, 0x2000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::baz1"); TEST_EQ(filter->start, 0x3000UL); TEST_EQ(filter->end, 0x3000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::baz2"); TEST_EQ(filter->start, 0x4000UL); TEST_EQ(filter->end, 0x4000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::baz3"); TEST_EQ(filter->start, 0x5000UL); TEST_EQ(filter->end, 0x5000UL + 0x1000UL); pr_dbg("found 4 symbols. done\n"); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } TEST_CASE(filter_setup_glob) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, }; struct rb_node *node; struct uftrace_filter *filter; struct uftrace_filter_setting setting = { .ptype = PATT_GLOB, }; filter_test_load_symtabs(&sinfo); pr_dbg("try to match with glob pattern: foo::b*\n"); uftrace_setup_filter("foo::b*", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::bar"); TEST_EQ(filter->start, 0x2000UL); TEST_EQ(filter->end, 0x2000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::baz1"); TEST_EQ(filter->start, 0x3000UL); TEST_EQ(filter->end, 0x3000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::baz2"); TEST_EQ(filter->start, 0x4000UL); TEST_EQ(filter->end, 0x4000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::baz3"); TEST_EQ(filter->start, 0x5000UL); TEST_EQ(filter->end, 0x5000UL + 0x1000UL); pr_dbg("found 4 symbols. done\n"); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } TEST_CASE(filter_setup_notrace) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, .filter_count = 0, }; struct rb_node *node; struct uftrace_filter *filter; struct uftrace_filter_setting setting = { .ptype = PATT_GLOB, }; filter_test_load_symtabs(&sinfo); pr_dbg("setup inclusive filter for foo::*\n"); uftrace_setup_filter("foo::*", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); TEST_EQ(get_filter_mode(triggers.filter_count), FILTER_MODE_IN); pr_dbg("add/replace exclusive filter for foo::foo\n"); uftrace_setup_filter("!foo::foo", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); TEST_EQ(get_filter_mode(triggers.filter_count), FILTER_MODE_IN); /* overall filter mode doesn't change */ pr_dbg("foo:foo should have OUT filter mode\n"); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::foo"); TEST_EQ(filter->trigger.flags, TRIGGER_FL_FILTER); TEST_EQ(filter->trigger.fmode, FILTER_MODE_OUT); pr_dbg("foo:bar should have IN filter mode\n"); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::bar"); TEST_EQ(filter->trigger.flags, TRIGGER_FL_FILTER); TEST_EQ(filter->trigger.fmode, FILTER_MODE_IN); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } TEST_CASE(filter_match) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, .filter_count = 0, }; struct uftrace_trigger tr; struct uftrace_filter_setting setting = { .ptype = PATT_REGEX, }; filter_test_load_symtabs(&sinfo); pr_dbg("check filter address match with foo::foo at 0x1000-0x1fff\n"); uftrace_setup_filter("foo::foo", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); TEST_EQ(get_filter_mode(triggers.filter_count), FILTER_MODE_IN); pr_dbg("check addresses inside the symbol\n"); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x1000, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_FILTER); TEST_EQ(tr.fmode, FILTER_MODE_IN); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x1fff, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_FILTER); TEST_EQ(tr.fmode, FILTER_MODE_IN); pr_dbg("addresses out of the symbol should not have FILTER flags\n"); memset(&tr, 0, sizeof(tr)); TEST_EQ(uftrace_match_filter(0xfff, &triggers.root, &tr), NULL); TEST_NE(tr.flags, TRIGGER_FL_FILTER); memset(&tr, 0, sizeof(tr)); TEST_EQ(uftrace_match_filter(0x2000, &triggers.root, &tr), NULL); TEST_NE(tr.flags, TRIGGER_FL_FILTER); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } TEST_CASE(trigger_setup_actions) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, }; struct uftrace_trigger tr; struct uftrace_filter_setting setting = { .ptype = PATT_REGEX, .lp64 = host_is_lp64(), }; filter_test_load_symtabs(&sinfo); pr_dbg("checking depth trigger\n"); uftrace_setup_trigger("foo::bar@depth=2", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x2500, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_DEPTH); TEST_EQ(tr.depth, 2); pr_dbg("checking backtrace trigger\n"); uftrace_setup_trigger("foo::bar@backtrace", &sinfo, &triggers, &setting); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x2500, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_DEPTH | TRIGGER_FL_BACKTRACE); pr_dbg("checking trace-on trigger\n"); uftrace_setup_trigger("foo::baz1@traceon", &sinfo, &triggers, &setting); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x3000, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_TRACE_ON); pr_dbg("checking trace-off trigger and overwrite the depth\n"); uftrace_setup_trigger("foo::baz3@trace_off,depth=1", &sinfo, &triggers, &setting); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x5000, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_TRACE_OFF | TRIGGER_FL_DEPTH); TEST_EQ(tr.depth, 1); pr_dbg("checking caller trigger\n"); uftrace_setup_trigger("foo::baz2@caller", &sinfo, &triggers, &setting); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x4200, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_CALLER); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } TEST_CASE(trigger_setup_filters) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, .filter_count = 0, }; struct uftrace_trigger tr; struct uftrace_filter_setting setting = { .ptype = PATT_REGEX, .lp64 = host_is_lp64(), }; filter_test_load_symtabs(&sinfo); pr_dbg("setup notrace filter with trigger action\n"); uftrace_setup_trigger("foo::bar@depth=2,notrace", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); TEST_EQ(get_filter_mode(triggers.filter_count), FILTER_MODE_OUT); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x2500, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_DEPTH | TRIGGER_FL_FILTER); TEST_EQ(tr.depth, 2); TEST_EQ(tr.fmode, FILTER_MODE_OUT); pr_dbg("compare regular filter setting with trigger\n"); uftrace_setup_filter("foo::baz1", &sinfo, &triggers, &setting); TEST_EQ(get_filter_mode(triggers.filter_count), FILTER_MODE_IN); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x3000, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_FILTER); TEST_EQ(tr.fmode, FILTER_MODE_IN); uftrace_setup_trigger("foo::baz2@notrace", &sinfo, &triggers, &setting); TEST_EQ(get_filter_mode(triggers.filter_count), FILTER_MODE_IN); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x4100, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_FILTER); TEST_EQ(tr.fmode, FILTER_MODE_OUT); pr_dbg("check caller filter setting\n"); uftrace_setup_caller_filter("foo::baz3", &sinfo, &triggers, &setting); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x5000, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_CALLER); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } /* same node tests as filter_setup_glob */ TEST_CASE(filter_rbtree_deep_copy) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info orig = { .root = RB_ROOT, }; struct uftrace_triggers_info copy = { .root = RB_ROOT, }; struct rb_node *node; struct uftrace_filter *filter; struct uftrace_filter_setting setting = { .ptype = PATT_GLOB, }; filter_test_load_symtabs(&sinfo); uftrace_setup_filter("foo::b*", &sinfo, &orig, &setting); pr_dbg("checking filter deep copy\n"); copy = uftrace_deep_copy_triggers(&orig); uftrace_cleanup_filter(&orig.root); TEST_EQ(RB_EMPTY_ROOT(©.root), false); node = rb_first(©.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::bar"); TEST_EQ(filter->start, 0x2000UL); TEST_EQ(filter->end, 0x2000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::baz1"); TEST_EQ(filter->start, 0x3000UL); TEST_EQ(filter->end, 0x3000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::baz2"); TEST_EQ(filter->start, 0x4000UL); TEST_EQ(filter->end, 0x4000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "foo::baz3"); TEST_EQ(filter->start, 0x5000UL); TEST_EQ(filter->end, 0x5000UL + 0x1000UL); uftrace_cleanup_filter(©.root); TEST_EQ(RB_EMPTY_ROOT(©.root), true); return TEST_OK; } TEST_CASE(trigger_setup_args) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, }; struct uftrace_trigger tr; struct uftrace_arg_spec *spec; struct uftrace_filter_setting setting = { .ptype = PATT_REGEX, .lp64 = host_is_lp64(), .arch = UFT_CPU_X86_64, }; int count; filter_test_load_symtabs(&sinfo); pr_dbg("check regular argument setting\n"); uftrace_setup_argument("foo::bar@arg1", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x2500, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_ARGUMENT); TEST_NE(tr.pargs, NULL); pr_dbg("compare argument setting via trigger\n"); uftrace_setup_trigger("foo::bar@arg2/s", &sinfo, &triggers, &setting); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x2500, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_ARGUMENT); TEST_NE(tr.pargs, NULL); count = 0; list_for_each_entry(spec, tr.pargs, list) { count++; pr_dbg("arg%d: fmt = %d, type = %d\n", spec->idx, spec->fmt, spec->type); if (count == 1) { TEST_EQ(spec->idx, 1); TEST_EQ(spec->fmt, ARG_FMT_AUTO); TEST_EQ(spec->type, ARG_TYPE_INDEX); } else if (count == 2) { TEST_EQ(spec->idx, 2); TEST_EQ(spec->fmt, ARG_FMT_STR); TEST_EQ(spec->type, ARG_TYPE_INDEX); } } TEST_EQ(count, 2); pr_dbg("check argument format, type and size\n"); uftrace_setup_argument("foo::baz1@arg1/i32,arg2/x64,fparg1/32,fparg2", &sinfo, &triggers, &setting); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x3999, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_ARGUMENT); count = 0; list_for_each_entry(spec, tr.pargs, list) { pr_dbg("arg%d: fmt = %d, type = %d, size = %d\n", spec->idx, spec->fmt, spec->type, spec->size); switch (++count) { case 1: TEST_EQ(spec->idx, 1); TEST_EQ(spec->fmt, ARG_FMT_SINT); TEST_EQ(spec->type, ARG_TYPE_INDEX); TEST_EQ(spec->size, 4); break; case 2: TEST_EQ(spec->idx, 2); TEST_EQ(spec->fmt, ARG_FMT_HEX); TEST_EQ(spec->type, ARG_TYPE_INDEX); TEST_EQ(spec->size, 8); break; case 3: TEST_EQ(spec->idx, 1); TEST_EQ(spec->fmt, ARG_FMT_FLOAT); TEST_EQ(spec->type, ARG_TYPE_FLOAT); TEST_EQ(spec->size, 4); break; case 4: TEST_EQ(spec->idx, 2); TEST_EQ(spec->fmt, ARG_FMT_FLOAT); TEST_EQ(spec->type, ARG_TYPE_FLOAT); TEST_EQ(spec->size, 8); break; default: /* should not reach here */ TEST_EQ(spec->idx, -1); break; } } TEST_EQ(count, 4); pr_dbg("check argument location\n"); uftrace_setup_trigger("foo::baz2@arg1/c,arg2/x32%rdi,arg3%stack+4,retval/f64", &sinfo, &triggers, &setting); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x4000, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_ARGUMENT | TRIGGER_FL_RETVAL); count = 0; list_for_each_entry(spec, tr.pargs, list) { pr_dbg("arg%d: fmt = %d, type = %d, size = %d\n", spec->idx, spec->fmt, spec->type, spec->size); switch (++count) { case 1: TEST_EQ(spec->idx, 1); TEST_EQ(spec->fmt, ARG_FMT_CHAR); TEST_EQ(spec->type, ARG_TYPE_INDEX); TEST_EQ(spec->size, 1); break; case 2: TEST_EQ(spec->idx, 2); TEST_EQ(spec->fmt, ARG_FMT_HEX); TEST_EQ(spec->type, ARG_TYPE_REG); TEST_EQ(spec->size, 4); TEST_EQ(spec->reg_idx, 1); break; case 3: TEST_EQ(spec->idx, 3); TEST_EQ(spec->fmt, ARG_FMT_AUTO); TEST_EQ(spec->type, ARG_TYPE_STACK); TEST_EQ(spec->size, (int)sizeof(long)); TEST_EQ(spec->stack_ofs, 4); break; case 4: TEST_EQ(spec->idx, 0); TEST_EQ(spec->fmt, ARG_FMT_FLOAT); TEST_EQ(spec->type, ARG_TYPE_FLOAT); TEST_EQ(spec->size, 8); break; default: /* should not reach here */ TEST_EQ(spec->idx, -1); break; } } TEST_EQ(count, 4); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } static struct uftrace_mmap *locfilter_test_load_mmap(void) { static struct uftrace_symbol syms[] = { { 0x1000, 0x1000, ST_GLOBAL_FUNC, "command_dump" }, { 0x2000, 0x1000, ST_GLOBAL_FUNC, "command_replay" }, { 0x3000, 0x1000, ST_GLOBAL_FUNC, "command_report" }, { 0x4000, 0x1000, ST_GLOBAL_FUNC, "command_foo" }, }; static struct uftrace_dbg_file dfiles[] = { { .name = "uftrace/cmds/dump.c" }, { .name = "uftrace/cmds/replay.c" }, { .name = "uftrace/cmds/report.c" }, { .name = "uftrace/cmds1/foo.c" }, }; static struct uftrace_dbg_loc locs[] = { { .file = &dfiles[0] }, { .file = &dfiles[1] }, { .file = &dfiles[2] }, { .file = &dfiles[3] }, }; static struct uftrace_module mod = { .symtab = { .sym = syms, .nr_sym = ARRAY_SIZE(syms), }, .dinfo = { .locs = locs, .nr_locs = ARRAY_SIZE(locs), .loaded = true, } }; static struct uftrace_mmap map = { .mod = &mod, .start = 0x0, .end = 0x6000, }; mod.symtab.sym = syms; mod.symtab.nr_sym = ARRAY_SIZE(syms); return ↦ } static struct uftrace_mmap *locfilter_test_load_mmap2(void) { static struct uftrace_symbol syms2[] = { { 0xa000, 0x1000, ST_GLOBAL_FUNC, "util_fstack" }, { 0xb000, 0x1000, ST_GLOBAL_FUNC, "util_report" }, }; static struct uftrace_dbg_file dfiles2[] = { { .name = "uftrace/utils/fstack.c" }, { .name = "uftrace/utils/report.c" }, }; static struct uftrace_dbg_loc locs2[] = { { .file = &dfiles2[0] }, { .file = &dfiles2[1] }, }; static struct uftrace_module mod2 = { .symtab = { .sym = syms2, .nr_sym = ARRAY_SIZE(syms2), }, .dinfo = { .locs = locs2, .nr_locs = ARRAY_SIZE(locs2), .loaded = true, } }; static struct uftrace_mmap map2 = { .mod = &mod2, .start = 0x0, .end = 0xe000, }; mod2.symtab.sym = syms2; mod2.symtab.nr_sym = ARRAY_SIZE(syms2); return &map2; } static void locfilter_test_load_symtabs(struct uftrace_sym_info *sinfo) { struct uftrace_mmap *map = locfilter_test_load_mmap(); struct uftrace_mmap *map2 = locfilter_test_load_mmap2(); sinfo->maps = map; sinfo->exec_map = map; sinfo->loaded = true; sinfo->maps->next = map2; } /* Simple pattern arguments are treated as regex after conversion. */ TEST_CASE(locfilter_setup_simple) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, }; struct rb_node *node; struct uftrace_filter *filter; struct uftrace_filter_setting setting = { .ptype = PATT_REGEX, }; locfilter_test_load_symtabs(&sinfo); pr_dbg("checking simple match\n"); uftrace_setup_loc_filter("uftrace/cmds/replay.c", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_replay"); TEST_EQ(filter->start, 0x2000UL); TEST_EQ(filter->end, 0x2000UL + 0x1000UL); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); pr_dbg("checking base name match\n"); uftrace_setup_loc_filter("dump.c", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_dump"); TEST_EQ(filter->start, 0x1000UL); TEST_EQ(filter->end, 0x1000UL + 0x1000UL); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); pr_dbg("checking unknown symbol\n"); uftrace_setup_loc_filter("invalid_name", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } TEST_CASE(locfilter_setup_regex) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, }; struct rb_node *node; struct uftrace_filter *filter; struct uftrace_filter_setting setting = { .ptype = PATT_REGEX, }; locfilter_test_load_symtabs(&sinfo); pr_dbg("try to match with regex pattern: re.*\n"); uftrace_setup_loc_filter("re.*", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_replay"); TEST_EQ(filter->start, 0x2000UL); TEST_EQ(filter->end, 0x2000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_report"); TEST_EQ(filter->start, 0x3000UL); TEST_EQ(filter->end, 0x3000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "util_report"); TEST_EQ(filter->start, 0xb000UL); TEST_EQ(filter->end, 0xb000UL + 0x1000UL); TEST_EQ(rb_next(node), NULL); pr_dbg("found 3 symbols. done\n"); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } TEST_CASE(locfilter_setup_glob) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, }; struct rb_node *node; struct uftrace_filter *filter; struct uftrace_filter_setting setting = { .ptype = PATT_GLOB, }; locfilter_test_load_symtabs(&sinfo); pr_dbg("try to match with glob pattern: *re*\n"); uftrace_setup_loc_filter("*re*", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_replay"); TEST_EQ(filter->start, 0x2000UL); TEST_EQ(filter->end, 0x2000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_report"); TEST_EQ(filter->start, 0x3000UL); TEST_EQ(filter->end, 0x3000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "util_report"); TEST_EQ(filter->start, 0xb000UL); TEST_EQ(filter->end, 0xb000UL + 0x1000UL); TEST_EQ(rb_next(node), NULL); pr_dbg("found 3 symbols. done\n"); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } /* Simple pattern arguments are treated as regex after conversion. */ TEST_CASE(locfilter_setup_dir_simple) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, }; struct rb_node *node; struct uftrace_filter *filter; struct uftrace_filter_setting setting = { .ptype = PATT_REGEX, }; locfilter_test_load_symtabs(&sinfo); pr_dbg("try to match directory with pattern: cmds\n"); uftrace_setup_loc_filter("cmds", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_dump"); TEST_EQ(filter->start, 0x1000UL); TEST_EQ(filter->end, 0x1000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_replay"); TEST_EQ(filter->start, 0x2000UL); TEST_EQ(filter->end, 0x2000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_report"); TEST_EQ(filter->start, 0x3000UL); TEST_EQ(filter->end, 0x3000UL + 0x1000UL); TEST_EQ(rb_next(node), NULL); pr_dbg("found 3 symbols. done\n"); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); pr_dbg("try to match directory with pattern: /cmds\n"); uftrace_setup_loc_filter("/cmds", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_dump"); TEST_EQ(filter->start, 0x1000UL); TEST_EQ(filter->end, 0x1000UL + 0x1000UL); TEST_NE(node = rb_next(node), NULL); TEST_NE(node = rb_next(node), NULL); TEST_EQ(node = rb_next(node), NULL); pr_dbg("found 3 symbols. done\n"); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); pr_dbg("try to match directory with pattern: cmds/\n"); uftrace_setup_loc_filter("cmds/", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_dump"); TEST_EQ(filter->start, 0x1000UL); TEST_EQ(filter->end, 0x1000UL + 0x1000UL); TEST_NE(node = rb_next(node), NULL); TEST_NE(node = rb_next(node), NULL); TEST_EQ(node = rb_next(node), NULL); pr_dbg("found 3 symbols. done\n"); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); pr_dbg("try to match directory with pattern: /uftrace/cmds/\n"); uftrace_setup_loc_filter("/uftrace/cmds/", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_dump"); TEST_EQ(filter->start, 0x1000UL); TEST_EQ(filter->end, 0x1000UL + 0x1000UL); TEST_NE(node = rb_next(node), NULL); TEST_NE(node = rb_next(node), NULL); TEST_EQ(node = rb_next(node), NULL); pr_dbg("found 3 symbols. done\n"); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); pr_dbg("try to match directory with pattern: uftrace/cmds/\n"); uftrace_setup_loc_filter("uftrace/cmds", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_dump"); TEST_EQ(filter->start, 0x1000UL); TEST_EQ(filter->end, 0x1000UL + 0x1000UL); TEST_NE(node = rb_next(node), NULL); TEST_NE(node = rb_next(node), NULL); TEST_EQ(node = rb_next(node), NULL); pr_dbg("found 3 symbols. done\n"); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); pr_dbg("try to match directory with pattern: /uftrace\n"); uftrace_setup_loc_filter("/uftrace", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_dump"); TEST_EQ(filter->start, 0x1000UL); TEST_EQ(filter->end, 0x1000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_replay"); TEST_EQ(filter->start, 0x2000UL); TEST_EQ(filter->end, 0x2000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_report"); TEST_EQ(filter->start, 0x3000UL); TEST_EQ(filter->end, 0x3000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_foo"); TEST_EQ(filter->start, 0x4000UL); TEST_EQ(filter->end, 0x4000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "util_fstack"); TEST_EQ(filter->start, 0xa000UL); TEST_EQ(filter->end, 0xa000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "util_report"); TEST_EQ(filter->start, 0xb000UL); TEST_EQ(filter->end, 0xb000UL + 0x1000UL); pr_dbg("found 6 symbols. done\n"); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); pr_dbg("checking invalid directory match\n"); uftrace_setup_loc_filter("youftrace", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } TEST_CASE(locfilter_setup_dir_regex) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, }; struct rb_node *node; struct uftrace_filter *filter; struct uftrace_filter_setting setting = { .ptype = PATT_REGEX, }; locfilter_test_load_symtabs(&sinfo); pr_dbg("try to match directory with regex pattern: cmds/.*\n"); uftrace_setup_loc_filter("cmds/.*", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_dump"); TEST_EQ(filter->start, 0x1000UL); TEST_EQ(filter->end, 0x1000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_replay"); TEST_EQ(filter->start, 0x2000UL); TEST_EQ(filter->end, 0x2000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_report"); TEST_EQ(filter->start, 0x3000UL); TEST_EQ(filter->end, 0x3000UL + 0x1000UL); TEST_EQ(rb_next(node), NULL); pr_dbg("found 3 symbols. done\n"); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); pr_dbg("checking invalid directory match\n"); uftrace_setup_loc_filter("cmd/.*", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } TEST_CASE(locfilter_setup_dir_glob) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, }; struct rb_node *node; struct uftrace_filter *filter; struct uftrace_filter_setting setting = { .ptype = PATT_GLOB, }; locfilter_test_load_symtabs(&sinfo); pr_dbg("try to match with glob pattern: *cmds/*\n"); uftrace_setup_loc_filter("*cmds/*", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); node = rb_first(&triggers.root); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_dump"); TEST_EQ(filter->start, 0x1000UL); TEST_EQ(filter->end, 0x1000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_replay"); TEST_EQ(filter->start, 0x2000UL); TEST_EQ(filter->end, 0x2000UL + 0x1000UL); node = rb_next(node); filter = rb_entry(node, struct uftrace_filter, node); TEST_STREQ(filter->name, "command_report"); TEST_EQ(filter->start, 0x3000UL); TEST_EQ(filter->end, 0x3000UL + 0x1000UL); TEST_EQ(rb_next(node), NULL); pr_dbg("found 3 symbols. done\n"); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } TEST_CASE(locfilter_match) { struct uftrace_sym_info sinfo = { .loaded = false, }; struct uftrace_triggers_info triggers = { .root = RB_ROOT, .loc_count = 0, }; struct uftrace_trigger tr; struct uftrace_filter_setting setting = { .ptype = PATT_REGEX, }; locfilter_test_load_symtabs(&sinfo); pr_dbg("check filter address match with re.* at 0x2000-0x2fff\n"); uftrace_setup_filter("re.*", &sinfo, &triggers, &setting); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), false); TEST_EQ(get_filter_mode(triggers.filter_count), FILTER_MODE_IN); pr_dbg("check addresses inside the symbol\n"); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x2000, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_FILTER); TEST_EQ(tr.fmode, FILTER_MODE_IN); memset(&tr, 0, sizeof(tr)); TEST_NE(uftrace_match_filter(0x2fff, &triggers.root, &tr), NULL); TEST_EQ(tr.flags, TRIGGER_FL_FILTER); TEST_EQ(tr.fmode, FILTER_MODE_IN); pr_dbg("addresses out of the symbol should not have FILTER flags\n"); memset(&tr, 0, sizeof(tr)); TEST_EQ(uftrace_match_filter(0x1fff, &triggers.root, &tr), NULL); TEST_NE(tr.flags, TRIGGER_FL_FILTER); memset(&tr, 0, sizeof(tr)); TEST_EQ(uftrace_match_filter(0xa000, &triggers.root, &tr), NULL); TEST_NE(tr.flags, TRIGGER_FL_FILTER); uftrace_cleanup_triggers(&triggers); TEST_EQ(RB_EMPTY_ROOT(&triggers.root), true); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/filter.h000066400000000000000000000121701455365734300160460ustar00rootroot00000000000000#ifndef UFTRACE_FILTER_H #define UFTRACE_FILTER_H #include #include #include #include "utils/arch.h" #include "utils/argspec.h" #include "utils/list.h" #include "utils/rbtree.h" /** * REGEX_CHARS: characters for regex matching. * * When one of these characters is in filter strings, * treat them as regex expressions. */ #define REGEX_CHARS ".?*+-^$|()[]{}" enum trigger_flag { TRIGGER_FL_DEPTH = (1U << 0), TRIGGER_FL_FILTER = (1U << 1), TRIGGER_FL_BACKTRACE = (1U << 2), TRIGGER_FL_TRACE = (1U << 3), TRIGGER_FL_TRACE_ON = (1U << 4), TRIGGER_FL_TRACE_OFF = (1U << 5), TRIGGER_FL_ARGUMENT = (1U << 6), TRIGGER_FL_RECOVER = (1U << 7), TRIGGER_FL_RETVAL = (1U << 8), TRIGGER_FL_COLOR = (1U << 9), TRIGGER_FL_TIME_FILTER = (1U << 10), TRIGGER_FL_READ = (1U << 11), TRIGGER_FL_FINISH = (1U << 13), TRIGGER_FL_AUTO_ARGS = (1U << 14), TRIGGER_FL_CALLER = (1U << 15), TRIGGER_FL_SIGNAL = (1U << 16), TRIGGER_FL_HIDE = (1U << 17), TRIGGER_FL_LOC = (1U << 18), TRIGGER_FL_SIZE_FILTER = (1U << 19), TRIGGER_FL_CLEAR = (1U << 20), /* Reverse other flags when set */ }; /** * filter_mode - opt-in or opt-out mode * * When in opt-in mode, only trace functions that have an explicit filter. When * in opt-out mode, trace all but explicitly excluded functions. * @FILTER_MODE_NONE is neutral and is only used for initialization in the * location filter. */ enum filter_mode { FILTER_MODE_NONE, FILTER_MODE_IN, FILTER_MODE_OUT, }; enum trigger_read_type { TRIGGER_READ_NONE = 0, TRIGGER_READ_PROC_STATM = 1, TRIGGER_READ_PAGE_FAULT = 2, TRIGGER_READ_PMU_CYCLE = 4, TRIGGER_READ_PMU_CACHE = 8, TRIGGER_READ_PMU_BRANCH = 16, }; struct uftrace_trigger { enum trigger_flag flags; enum trigger_flag clear_flags; int depth; char color; uint64_t time; unsigned size; enum filter_mode fmode; enum filter_mode lmode; enum trigger_read_type read; struct list_head *pargs; }; struct uftrace_filter { struct rb_node node; char *name; uint64_t start; uint64_t end; struct list_head args; struct uftrace_trigger trigger; }; enum uftrace_pattern_type { PATT_NONE, PATT_SIMPLE, PATT_REGEX, PATT_GLOB, }; struct uftrace_pattern { enum uftrace_pattern_type type; char *patt; regex_t re; }; enum uftrace_trace_state { TRACE_STATE_NONE, TRACE_STATE_OFF, TRACE_STATE_ON, }; struct uftrace_filter_setting { enum uftrace_pattern_type ptype; enum uftrace_cpu_arch arch; bool auto_args; bool allow_kernel; bool lp64; bool plt_only; /* caller-defined data */ void *info_str; }; struct uftrace_triggers_info { /* filters, trigger actions, arg/retval specs */ /* container type: struct uftrace_filter */ struct rb_root root; /* count of registered opt-in filters (-F) */ int filter_count; /* count of registered caller filters */ int caller_count; /* count of registered opt-in location filters (-L) */ int loc_count; }; typedef void (*trigger_fn_t)(struct uftrace_trigger *tr, void *arg); struct uftrace_sym_info; void uftrace_setup_filter(char *filter_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, struct uftrace_filter_setting *setting); void uftrace_setup_trigger(char *trigger_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, struct uftrace_filter_setting *setting); void uftrace_setup_argument(char *args_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, struct uftrace_filter_setting *setting); void uftrace_setup_retval(char *retval_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, struct uftrace_filter_setting *setting); void uftrace_setup_caller_filter(char *filter_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, struct uftrace_filter_setting *setting); void uftrace_setup_hide_filter(char *filter_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, struct uftrace_filter_setting *setting); void uftrace_setup_loc_filter(char *filter_str, struct uftrace_sym_info *sinfo, struct uftrace_triggers_info *triggers, struct uftrace_filter_setting *setting); struct uftrace_triggers_info uftrace_deep_copy_triggers(struct uftrace_triggers_info *src); struct uftrace_filter *uftrace_match_filter(uint64_t ip, struct rb_root *root, struct uftrace_trigger *tr); void uftrace_cleanup_filter(struct rb_root *root); void uftrace_cleanup_triggers(struct uftrace_triggers_info *triggers); void uftrace_print_filter(struct rb_root *root); int uftrace_count_filter(struct rb_root *root, unsigned long flag); void init_filter_pattern(enum uftrace_pattern_type type, struct uftrace_pattern *p, char *str); bool match_filter_pattern(struct uftrace_pattern *p, char *name); void free_filter_pattern(struct uftrace_pattern *p); enum uftrace_pattern_type parse_filter_pattern(const char *str); const char *get_filter_pattern(enum uftrace_pattern_type ptype); char *uftrace_clear_kernel(char *filter_str); int setup_trigger_action(char *str, struct uftrace_trigger *tr, char **module, unsigned long orig_flags, struct uftrace_filter_setting *setting); #endif /* UFTRACE_FILTER_H */ uftrace-0.15.2/utils/fstack.c000066400000000000000000002146111455365734300160330ustar00rootroot00000000000000#include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "fstack" #define PR_DOMAIN DBG_FSTACK #include "libmcount/mcount.h" #include "uftrace.h" #include "utils/arch.h" #include "utils/event.h" #include "utils/filter.h" #include "utils/fstack.h" #include "utils/kernel.h" #include "utils/rbtree.h" #include "utils/utils.h" bool fstack_enabled = true; bool live_disabled = false; static struct uftrace_triggers_info fstack_triggers; static inline int fstack_get_filter_mode(void) { int filter_count = fstack_triggers.filter_count; return filter_count > 0 ? FILTER_MODE_IN : FILTER_MODE_OUT; } static inline int fstack_get_loc_mode(void) { int loc_count = fstack_triggers.loc_count; return loc_count > 0 ? FILTER_MODE_IN : FILTER_MODE_OUT; } static int __read_task_ustack(struct uftrace_task_reader *task); struct uftrace_task_reader *get_task_handle(struct uftrace_data *handle, int tid) { int i; for (i = 0; i < handle->nr_tasks; i++) { if (handle->tasks[i].tid == tid) return &handle->tasks[i]; } return NULL; } static void setup_task_handle(struct uftrace_data *handle, struct uftrace_task_reader *task, int tid) { int i; int max_stack; task->stack_count = 0; task->display_depth = 0; task->column_index = -1; task->filter.depth = handle->depth; task->event_color = DEFAULT_EVENT_COLOR; /* * set display depth to non-zero only when trace-on trigger (with --trace=off * option) or time range is set. */ task->display_depth_set = (fstack_enabled && !live_disabled && !handle->time_range.start); max_stack = handle->hdr.max_stack; task->func_stack = xcalloc(1, sizeof(*task->func_stack) * max_stack); /* FIXME: save filter depth at fork() and restore */ for (i = 0; i < max_stack; i++) task->func_stack[i].orig_depth = handle->depth; } void reset_task_handle(struct uftrace_data *handle) { int i; struct uftrace_task_reader *task; for (i = 0; i < handle->nr_tasks; i++) { task = &handle->tasks[i]; task->done = true; if (task->fp) { fclose(task->fp); task->fp = NULL; } free(task->args.data); task->args.data = NULL; free(task->func_stack); task->func_stack = NULL; reset_rstack_list(&task->rstack_list); reset_rstack_list(&task->event_list); } free(handle->tasks); handle->tasks = NULL; handle->nr_tasks = 0; } static void prepare_task_handle(struct uftrace_data *handle, struct uftrace_task_reader *task, int tid) { char *filename; memset(task, 0, sizeof(*task)); task->tid = tid; task->h = handle; task->t = find_task(&handle->sessions, tid); xasprintf(&filename, "%s/%d.dat", handle->dirname, tid); task->fp = fopen(filename, "rb"); if (task->fp == NULL) { pr_dbg("cannot open task data file: %s: %m\n", filename); task->done = true; } else pr_dbg2("opening %s\n", filename); free(filename); setup_rstack_list(&task->rstack_list); setup_rstack_list(&task->event_list); } static void update_first_timestamp(struct uftrace_data *handle, struct uftrace_task_reader *task, struct uftrace_record *rstack) { uint64_t first = handle->time_range.first; if (task->stack_count == 0 && rstack->type == UFTRACE_EVENT && handle->time_range.event_skip_out) return; if (task->stack_count == 0 && is_kernel_record(task, rstack) && handle->time_range.kernel_skip_out) return; if (first == 0 || first > rstack->time) handle->time_range.first = rstack->time; } /** * fstack_setup_task - setup task filters using tid * @tid_filter - CSV of tid (or possibly separated by ':') * @handle - file handle * * This function sets up task filters using @tid_filter. * Tasks not listed will be ignored. */ void fstack_setup_task(char *tid_filter, struct uftrace_data *handle) { int i, k; int nr_filters = 0; int *filter_tids = NULL; char *p = tid_filter; if (tid_filter == NULL) goto setup; do { int id; if (*p == ',' || *p == ';') p++; id = strtol(p, &p, 10); filter_tids = xrealloc(filter_tids, (nr_filters + 1) * sizeof(int)); filter_tids[nr_filters++] = id; } while (*p); pr_dbg("setup filters for %d task(s)\n", nr_filters); setup: handle->nr_tasks = handle->info.nr_tid; handle->tasks = xmalloc(sizeof(*handle->tasks) * handle->nr_tasks); for (i = 0; i < handle->nr_tasks; i++) { bool found; int tid; struct uftrace_task_reader *task; if (handle->info.tids == NULL) pr_err_ns("The info file is broken: missing tids\n"); found = !tid_filter; tid = handle->info.tids[i]; task = &handle->tasks[i]; prepare_task_handle(handle, task, tid); for (k = 0; k < nr_filters; k++) { if (tid == filter_tids[k]) { found = true; break; } } if (!found) { task->done = true; /* need to read the data to check elapsed time */ if (task->fp) { if (!__read_task_ustack(task)) { update_first_timestamp(handle, task, &task->ustack); } fclose(task->fp); task->fp = NULL; } continue; } setup_task_handle(handle, task, tid); } free(filter_tids); } static int setup_filters(struct uftrace_session *s, void *arg) { struct uftrace_filter_setting *setting = arg; fstack_triggers.root = s->filters; uftrace_setup_filter(setting->info_str, &s->sym_info, &fstack_triggers, setting); s->filters = fstack_triggers.root; return 0; } static int setup_trigger(struct uftrace_session *s, void *arg) { struct uftrace_filter_setting *setting = arg; fstack_triggers.root = s->filters; uftrace_setup_trigger(setting->info_str, &s->sym_info, &fstack_triggers, setting); s->filters = fstack_triggers.root; return 0; } static int setup_callers(struct uftrace_session *s, void *arg) { struct uftrace_filter_setting *setting = arg; fstack_triggers.root = s->filters; uftrace_setup_caller_filter(setting->info_str, &s->sym_info, &fstack_triggers, setting); s->filters = fstack_triggers.root; return 0; } static int setup_hides(struct uftrace_session *s, void *arg) { struct uftrace_filter_setting *setting = arg; fstack_triggers.root = s->filters; uftrace_setup_hide_filter(setting->info_str, &s->sym_info, &fstack_triggers, setting); s->filters = fstack_triggers.root; return 0; } static int setup_locs(struct uftrace_session *s, void *arg) { struct uftrace_filter_setting *setting = arg; fstack_triggers.root = s->filters; uftrace_setup_loc_filter(setting->info_str, &s->sym_info, &fstack_triggers, setting); s->filters = fstack_triggers.root; return 0; } static int count_filters(struct uftrace_session *s, void *arg) { int *count = arg; struct rb_node *node = rb_first(&s->filters); while (node) { (*count)++; node = rb_next(node); } return 0; } static int count_callers(struct uftrace_session *s, void *arg) { *(int *)arg += uftrace_count_filter(&s->filters, TRIGGER_FL_CALLER); return 0; } static int count_hides(struct uftrace_session *s, void *arg) { *(int *)arg += uftrace_count_filter(&s->filters, TRIGGER_FL_HIDE); return 0; } static int count_locs(struct uftrace_session *s, void *arg) { *(int *)arg += uftrace_count_filter(&s->filters, TRIGGER_FL_LOC); return 0; } /** * setup_fstack_filters - setup symbol filters and triggers * @handle - handle for uftrace data * @filter_str - filter symbol names * @trigger_str - trigger definitions * @caller_str - caller filter symbol names * @hide_str - hide filter symbol names * @loc_str - source location filter symbol names * @setting - filter setting * * This function sets up the symbol filters and triggers using following syntax: * filter_strs = filter | filter ";" filter_strs * filter = symbol | symbol "@" trigger * trigger = trigger_def | trigger_def "," trigger * trigger_def = "depth=" NUM | "backtrace" */ static int setup_fstack_filters(struct uftrace_data *handle, char *filter_str, char *trigger_str, char *caller_str, char *hide_str, char *loc_str, struct uftrace_filter_setting *setting) { int count = 0; struct uftrace_session_link *sessions = &handle->sessions; if (filter_str) { setting->info_str = filter_str; walk_sessions(sessions, setup_filters, setting); walk_sessions(sessions, count_filters, &count); if (count == 0) return -1; pr_dbg("setup filters for %d function(s)\n", count); } if (trigger_str) { int prev = count; setting->info_str = trigger_str; walk_sessions(sessions, setup_trigger, setting); walk_sessions(sessions, count_filters, &count); if (prev == count) return -1; pr_dbg("setup triggers for %d function(s)\n", count - prev); } if (caller_str) { int prev = count; setting->info_str = caller_str; walk_sessions(sessions, setup_callers, setting); walk_sessions(sessions, count_callers, &count); if (prev == count) return -1; handle->caller_filter = true; pr_dbg("setup caller filters for %d function(s)\n", count - prev); } if (hide_str) { int prev = count; setting->info_str = hide_str; walk_sessions(sessions, setup_hides, setting); walk_sessions(sessions, count_hides, &count); if (prev == count) return -1; pr_dbg("setup hide filters for %d function(s)\n", count - prev); } if (loc_str) { int prev = count; setting->info_str = loc_str; walk_sessions(sessions, setup_locs, setting); walk_sessions(sessions, count_locs, &count); if (prev == count) return -1; pr_dbg("setup location filters for %d function(s)\n", count - prev); } return 0; } static const char *fixup_syms[] = { "execl", "execlp", "execle", "execv", "execve", "execvp", "execvpe", "setjmp", "_setjmp", "sigsetjmp", "__sigsetjmp", "longjmp", "siglongjmp", "__longjmp_chk", "fork", "vfork", "daemon", "posix.fork", }; static int setjmp_depth; static int setjmp_count; static int build_fixup_filter(struct uftrace_session *s, void *arg) { size_t i; struct uftrace_filter_setting setting = { .ptype = PATT_SIMPLE, .auto_args = false, }; struct uftrace_triggers_info fixups = { .root = s->fixups, }; pr_dbg("fixup for some special functions\n"); for (i = 0; i < ARRAY_SIZE(fixup_syms); i++) { uftrace_setup_trigger((char *)fixup_syms[i], &s->sym_info, &fixups, &setting); } s->fixups = fixups.root; return 0; } /** * fstack_prepare_fixup - setup special filters for fixup routines * @handle: handle for uftrace data * * This function sets up special symbol filter tables which need * special handling like fork/exec, setjmp/longjmp cases. */ static void fstack_prepare_fixup(struct uftrace_data *handle) { walk_sessions(&handle->sessions, build_fixup_filter, NULL); } static int build_arg_spec(struct uftrace_session *s, void *arg) { struct uftrace_filter_setting *setting = arg; struct uftrace_triggers_info triggers = { .root = s->filters, }; if (setting->info_str) { uftrace_setup_argument(setting->info_str, &s->sym_info, &triggers, setting); s->filters = triggers.root; } return 0; } static int build_ret_spec(struct uftrace_session *s, void *arg) { struct uftrace_filter_setting *setting = arg; struct uftrace_triggers_info triggers = { .root = s->filters, }; if (setting->info_str) { uftrace_setup_retval(setting->info_str, &s->sym_info, &triggers, setting); s->filters = triggers.root; } return 0; } /** * setup_fstack_args - setup argument and return value spec * @argspec: spec string describes function arguments * @retspec: spec string describes function return values * @handle: handle for uftrace data * @auto_args: whether current spec is auto-spec * @patt_type: filter match pattern (regex or glob) * * This functions sets up argument and return value information * provided by user at the time of recording. */ void setup_fstack_args(char *argspec, char *retspec, struct uftrace_data *handle, struct uftrace_filter_setting *setting) { if (argspec == NULL && retspec == NULL && !setting->auto_args) return; pr_dbg("setup argspec and/or retspec\n"); setting->info_str = argspec; walk_sessions(&handle->sessions, build_arg_spec, setting); setting->info_str = retspec; walk_sessions(&handle->sessions, build_ret_spec, setting); /* old data does not have separated retspec */ if (argspec && strstr(argspec, "retval")) { setting->info_str = argspec; walk_sessions(&handle->sessions, build_ret_spec, setting); } } /** * fstack_setup_filters - setup necessary filters for processing data * @opts: uftrace user options * @handle: handle for uftrace data * * This function sets up all kind of filters given by user. */ int fstack_setup_filters(struct uftrace_opts *opts, struct uftrace_data *handle) { if (opts->filter || opts->trigger || opts->caller || opts->hide || opts->loc_filter) { struct uftrace_filter_setting setting = { .ptype = opts->patt_type, .allow_kernel = true, .lp64 = data_is_lp64(handle), .arch = handle->arch, }; if (setup_fstack_filters(handle, opts->filter, opts->trigger, opts->caller, opts->hide, opts->loc_filter, &setting) < 0) { char * or = ""; pr_use("failed to set filter or trigger: "); if (opts->filter) { pr_out("%s%s", or, opts->filter); or = " or "; } if (opts->trigger) { pr_out("%s%s", or, opts->trigger); or = " or "; } if (opts->caller) { pr_out("%s%s", or, opts->caller); or = " or "; } if (opts->hide) { pr_out("%s%s", or, opts->hide); or = " or "; } if (opts->loc_filter) { pr_out("%s%s", or, opts->loc_filter); or = " or "; } pr_out("\n"); } } if (opts->trace == TRACE_STATE_OFF) fstack_enabled = false; fstack_setup_task(opts->tid, handle); fstack_prepare_fixup(handle); return 0; } /** * fstack_get - retrieve func_stack entry in a task * @task - tracee task * @idx - stack index * * This function returns a pointer to func_stack in @task or %NULL if it has * no function call stack or @idx is out of the boundary. */ struct uftrace_fstack *fstack_get(struct uftrace_task_reader *task, int idx) { if (task->func_stack == NULL) return NULL; if (task->h && idx >= task->h->hdr.max_stack) { if (!task->fstack_warned) { pr_dbg("call stack overflow (task: %d)\n", task->tid); task->fstack_warned = true; } return NULL; } if (idx < 0) { if (!task->fstack_warned) { pr_dbg("negative call stack count (task: %d)\n", task->tid); task->fstack_warned = true; } return NULL; } return &task->func_stack[idx]; } /** * fstack_entry - function entry handler * @task - tracee task * @rstack - function return stack * @tr - trigger data * * This function should be called when replaying a recorded session. * It updates function stack, filter status, trigger result and * determine how to react. Callers can do whatever they want based * on the trigger result. * * This function returns -1 if it should be skipped, 0 otherwise. */ int fstack_entry(struct uftrace_task_reader *task, struct uftrace_record *rstack, struct uftrace_trigger *tr) { struct uftrace_fstack *fstack; struct uftrace_session_link *sessions = &task->h->sessions; struct uftrace_session *sess; uint64_t addr = rstack->addr; /* stack_count was increased in __read_rstack */ fstack = fstack_get(task, task->stack_count - 1); if (fstack == NULL) return -1; pr_dbg2("ENTRY: [%5d] stack: %d, depth: %d, disp: %d, I: %d, O: %d, D: %d, flags = %lx %s\n", task->tid, task->stack_count - 1, rstack->depth, task->display_depth, task->filter.in_count, task->filter.out_count, task->filter.depth, fstack->flags, rstack->more ? "more" : ""); fstack->orig_depth = task->filter.depth; fstack->flags = 0; if (task->filter.out_count > 0) { fstack->flags |= FSTACK_FL_NORECORD; return -1; } sess = find_task_session(sessions, task->t, rstack->time); if (is_kernel_record(task, rstack)) { if (sess == NULL) sess = sessions->first; addr = get_kernel_address(&sess->sym_info, addr); } if (sess) { struct uftrace_filter *fixup; fixup = uftrace_match_filter(addr, &sess->fixups, tr); if (unlikely(fixup)) { if (!strncmp(fixup->name, "exec", 4)) fstack->flags |= FSTACK_FL_EXEC; else if (strstr(fixup->name, "setjmp")) { setjmp_depth = task->display_depth + 1; setjmp_count = task->stack_count; } else if (strstr(fixup->name, "longjmp")) { fstack->flags |= FSTACK_FL_LONGJMP; } else if (strstr(fixup->name, "fork") || !strcmp(fixup->name, "daemon") || !strcmp(fixup->name, "posix.fork")) { task->fork_display_depth = task->display_depth + 1; } } uftrace_match_filter(addr, &sess->filters, tr); } if (tr->flags & TRIGGER_FL_FILTER) { if (tr->fmode == FILTER_MODE_IN) { task->filter.in_count++; fstack->flags |= FSTACK_FL_FILTERED; } else { task->filter.out_count++; fstack->flags |= FSTACK_FL_NOTRACE | FSTACK_FL_NORECORD; return -1; } /* restore default filter depth */ task->filter.depth = task->h->depth; } else { if (fstack_get_filter_mode() == FILTER_MODE_IN && task->filter.in_count == 0) { fstack->flags |= FSTACK_FL_NORECORD; return -1; } } if (tr->flags & TRIGGER_FL_LOC) { if (tr->lmode == FILTER_MODE_OUT) { fstack->flags |= FSTACK_FL_NORECORD; return -1; } } else { if (fstack_get_loc_mode() == FILTER_MODE_IN) { fstack->flags |= FSTACK_FL_NORECORD; return -1; } } if (tr->flags & TRIGGER_FL_DEPTH) task->filter.depth = tr->depth; if (tr->flags & TRIGGER_FL_TRACE_ON) fstack_enabled = true; if (tr->flags & TRIGGER_FL_TRACE_OFF) { fstack_enabled = false; task->display_depth_set = false; } if (!fstack_enabled) { /* * don't set NORECORD flag so that it can be printed * when trace-on again */ return -1; } if (task->filter.depth <= 0 || tr->flags & TRIGGER_FL_HIDE) { fstack->flags |= FSTACK_FL_NORECORD; return -1; } task->filter.depth--; if (!task->display_depth_set) { task->display_depth = task->stack_count - 1; task->display_depth_set = true; if (unlikely(task->display_depth < 0)) task->display_depth = 0; } return 0; } /** * fstack_exit - function exit handler * @task - tracee task * * This function should be paired with fstack_entry(). */ void fstack_exit(struct uftrace_task_reader *task) { struct uftrace_fstack *fstack; fstack = fstack_get(task, task->stack_count); if (fstack == NULL) return; pr_dbg2("EXIT : [%5d] stack: %d, depth: %d, disp: %d, I: %d, O: %d, D: %d, flags = %lx\n", task->tid, task->stack_count, task->rstack->depth, task->display_depth, task->filter.in_count, task->filter.out_count, task->filter.depth, fstack->flags); if (fstack->flags & FSTACK_FL_FILTERED) task->filter.in_count--; else if (fstack->flags & FSTACK_FL_NOTRACE) task->filter.out_count--; fstack->flags = 0; task->filter.depth = fstack->orig_depth; } /** * fstack_update - Update fstack related info * @type - UFTRACE_ENTRY or UFTRACE_EXIT * @task - tracee task * @fstack - function tracing stack * * This function updates current display depth according to @type and * flags of @fstack, and return a new depth. */ int fstack_update(int type, struct uftrace_task_reader *task, struct uftrace_fstack *fstack) { if (fstack == NULL) return task->display_depth; if (type == UFTRACE_ENTRY) { if (fstack->flags & FSTACK_FL_EXEC) { task->display_depth = 0; task->stack_count = 0; /* these are user functions */ task->user_display_depth = 0; task->user_stack_count = 0; } else if (fstack->flags & FSTACK_FL_LONGJMP) { task->display_depth = setjmp_depth; task->stack_count = setjmp_count; /* these are user functions */ task->user_display_depth = setjmp_depth; task->user_stack_count = setjmp_count; } else { task->display_depth++; if (task->ctx == FSTACK_CTX_USER) task->user_display_depth++; } fstack->flags &= ~(FSTACK_FL_EXEC | FSTACK_FL_LONGJMP); } else if (type == UFTRACE_EXIT) { /* fork'ed child starts with an exit record */ if (!task->display_depth_set) { task->display_depth = task->stack_count + 1; task->display_depth_set = true; } if (task->display_depth > 0) task->display_depth--; else task->display_depth = 0; if (task->ctx == FSTACK_CTX_USER) { if (task->user_display_depth > 0) task->user_display_depth--; else task->user_display_depth = 0; } } else { pr_err_ns("wrong type of fstack entry: %d\n", type); } return task->display_depth; } /* returns -1 if it can skip the rstack */ static int fstack_check_skip(struct uftrace_task_reader *task, struct uftrace_record *rstack) { struct uftrace_session_link *sessions = &task->h->sessions; struct uftrace_session *sess; uint64_t addr = rstack->addr; struct uftrace_trigger tr = { 0 }; int depth = task->filter.depth; struct uftrace_fstack *fstack; if (task->filter.out_count > 0) return -1; if (rstack->type == UFTRACE_EXIT) { if (task->stack_count < 1) return 0; /* fstack_consume() is not called yet */ fstack = fstack_get(task, task->stack_count - 1); if (fstack == NULL) return -1; if (fstack->flags & FSTACK_FL_NORECORD) return -1; return 0; } sess = find_task_session(sessions, task->t, rstack->time); if (sess == NULL) { struct uftrace_session *fsess = sessions->first; if (is_kernel_record(task, rstack)) sess = fsess; else return -1; addr = get_kernel_address(&fsess->sym_info, addr); } uftrace_match_filter(addr, &sess->filters, &tr); if (tr.flags & TRIGGER_FL_FILTER) { if (tr.fmode == FILTER_MODE_OUT) return -1; depth = task->h->depth; } else if (tr.flags & TRIGGER_FL_LOC) { if (tr.fmode == FILTER_MODE_OUT) return -1; } else if ((fstack_get_filter_mode() == FILTER_MODE_IN || fstack_get_loc_mode() == FILTER_MODE_IN) && task->filter.in_count == 0) { return -1; } if (tr.flags & (TRIGGER_FL_DEPTH | TRIGGER_FL_TRACE_ON)) return 1; if (tr.flags & (TRIGGER_FL_TRACE_OFF | TRIGGER_FL_HIDE) || depth <= 0) return -1; return 0; } /** * fstack_skip - Skip filtered record as many as possible * @handle - file handle * @task - tracee task * @curr_depth - current rstack depth * @opts - options * * This function checks next rstack and skip if it's filtered out. * The intention is to merge EXIT record after skipped ones. It * returns updated @task pointer which contains next non-filtered * rstack or NULL if it's the last record. */ struct uftrace_task_reader *fstack_skip(struct uftrace_data *handle, struct uftrace_task_reader *task, int curr_depth, struct uftrace_opts *opts) { struct uftrace_task_reader *next = NULL; struct uftrace_fstack *fstack; struct uftrace_record *curr_stack = task->rstack; struct uftrace_session_link *sessions = &handle->sessions; fstack = fstack_get(task, task->stack_count - 1); if (fstack == NULL) return NULL; if (fstack->flags & (FSTACK_FL_EXEC | FSTACK_FL_LONGJMP)) return NULL; if (peek_rstack(handle, &next) < 0) return NULL; while (true) { struct uftrace_record *next_stack = next->rstack; struct uftrace_trigger tr = { 0 }; struct uftrace_symbol *sym = task_find_sym(sessions, task, next_stack); /* skip filtered entries until current matching EXIT records */ if (next == task && curr_stack == next_stack && curr_depth >= next_stack->depth) break; /* skip kernel functions outside user functions */ if (is_kernel_record(next, next_stack)) { if (has_kernel_data(handle->kernel) && !next->user_stack_count && handle->kernel->skip_out) goto next; } if (next_stack->type == UFTRACE_EVENT) { if (!next->user_stack_count && opts->event_skip_out) goto next; } if (next_stack->type == UFTRACE_LOST) return NULL; /* skip it if --no-libcall is given */ if (!opts->libcall && sym && sym->type == ST_PLT_FUNC) goto next; /* return if it's not filtered */ if (fstack_check_skip(next, next_stack) >= 0) break; next: /* consume the filtered rstack */ fstack_consume(handle, next); /* * call fstack_entry/exit() after read_rstack() so * that it can changes stack_count properly. */ if (next_stack->type == UFTRACE_ENTRY) fstack_entry(next, next_stack, &tr); else if (next_stack->type == UFTRACE_EXIT) fstack_exit(next); if (!fstack_enabled) return NULL; /* and then read next */ if (peek_rstack(handle, &next) < 0) return NULL; } return next; } /** * fstack_check_filter - Check filter for current function * @task - tracee task * * This function checks @task->func_stack and returns whether it * should be filtered out or not. True means it's ok to process * this function and false means it should be skipped. */ bool fstack_check_filter(struct uftrace_task_reader *task) { struct uftrace_fstack *fstack; struct uftrace_trigger tr = {}; if (task->rstack->type == UFTRACE_ENTRY) { fstack = fstack_get(task, task->stack_count - 1); if (fstack == NULL) return false; if (fstack_entry(task, task->rstack, &tr) < 0) return false; } else if (task->rstack->type == UFTRACE_EXIT) { fstack = fstack_get(task, task->stack_count); if (fstack == NULL) return false; if ((fstack->flags & FSTACK_FL_NORECORD) || !fstack_enabled) { fstack_exit(task); return false; } fstack_update(UFTRACE_EXIT, task, fstack); } else if (task->rstack->type == UFTRACE_EVENT) { /* don't change filter state, just check it */ if (task->filter.out_count > 0 || task->filter.depth <= 0 || (fstack_get_filter_mode() == FILTER_MODE_IN && task->filter.in_count == 0)) return false; if (task->rstack->addr == EVENT_ID_PERF_SCHED_IN || task->rstack->addr == EVENT_ID_PERF_SCHED_OUT) { int idx = task->stack_count; const char *sched = "IN"; if (task->rstack->addr == EVENT_ID_PERF_SCHED_OUT) { idx--; sched = "OUT"; } fstack = fstack_get(task, idx); if (fstack == NULL) return false; pr_dbg2("SCHED: [%5d] stack: %d, depth: %d, disp: %d, I: %d, O: %d, D: %d, %s\n", task->tid, idx, fstack->orig_depth, task->display_depth, task->filter.in_count, task->filter.out_count, task->filter.depth, sched); } } return true; } /** * fstack_check_filter_done - post-process filter settings * @task - tracee task * * This function should be called after fstack_check_filter() returned * true and uftrace handled the fstack. */ void fstack_check_filter_done(struct uftrace_task_reader *task) { struct uftrace_fstack *fstack; if (task->rstack->type == UFTRACE_ENTRY) { fstack = fstack_get(task, task->stack_count - 1); if (fstack == NULL) return; fstack_update(UFTRACE_ENTRY, task, fstack); } else if (task->rstack->type == UFTRACE_EXIT) { fstack = fstack_get(task, task->stack_count); if (fstack == NULL) return; fstack_exit(task); } } /** * is_sched_event - check whether the given address is a schedule event * @addr - address to check whether it's a schedule event or now * * This function returns true if the given address is a schedule event otherwise * returns false. */ bool is_sched_event(uint64_t addr) { if (addr == EVENT_ID_PERF_SCHED_IN || addr == EVENT_ID_PERF_SCHED_OUT || addr == EVENT_ID_PERF_SCHED_OUT_PREEMPT || addr == EVENT_ID_PERF_SCHED_BOTH || addr == EVENT_ID_PERF_SCHED_BOTH_PREEMPT) return true; return false; } /** * is_preempt_sched_event - check whether the given task is schedule preempt event * @task - tracee task * @addr - address to check whether it's a schedule event to be filtered or not * * This function returns true if the given address is a schedule preempt event or * given address is a EVENT_ID_PERF_SCHED_IN right after schedule preempt event otherwise * returns false */ bool is_sched_preempt_event(struct uftrace_task_reader *task, uint64_t addr) { if (addr == EVENT_ID_PERF_SCHED_OUT_PREEMPT || addr == EVENT_ID_PERF_SCHED_BOTH_PREEMPT) { task->sched_preempt_seen = true; return true; } if (addr == EVENT_ID_PERF_SCHED_IN && task->sched_preempt_seen) { task->sched_preempt_seen = false; return true; } return false; } /** * fstack_check_opt - Check filter options for current function * @task - tracee task * @opts - options given by user * * This function checks @task->func_stack with @opts and returns * whether it should be filtered out or not. True means it's ok to * process this function and false means it should be skipped. */ bool fstack_check_opts(struct uftrace_task_reader *task, struct uftrace_opts *opts) { struct uftrace_record *rec = task->rstack; /* skip user functions if --kernel-only is set */ if (opts->kernel_only) { if (!is_kernel_record(task, rec) && rec->type != UFTRACE_LOST) return false; } if (opts->kernel_skip_out) { /* skip kernel functions outside user functions */ if (!task->user_stack_count && is_kernel_record(task, rec)) return false; } if (opts->event_skip_out) { /* skip event outside of user functions */ if (!task->user_stack_count && rec->type == UFTRACE_EVENT) return false; } if (opts->no_event && !opts->event && rec->type == UFTRACE_EVENT) return false; if (opts->no_sched && is_sched_event(rec->addr)) return false; if (opts->no_sched_preempt && is_sched_preempt_event(task, rec->addr)) { return false; } return true; } void setup_rstack_list(struct uftrace_rstack_list *list) { INIT_LIST_HEAD(&list->read); INIT_LIST_HEAD(&list->unused); list->count = 0; } void add_to_rstack_list(struct uftrace_rstack_list *list, struct uftrace_record *rstack, struct uftrace_fstack_args *args) { struct uftrace_rstack_list_node *node; if (list_empty(&list->unused)) { node = xmalloc(sizeof(*node)); node->args.data = NULL; } else { node = list_first_entry(&list->unused, typeof(*node), list); list_del(&node->list); } memcpy(&node->rstack, rstack, sizeof(*rstack)); if (rstack->more) { memcpy(&node->args, args, sizeof(*args)); node->args.data = xmalloc(args->len); memcpy(node->args.data, args->data, args->len); } list_add_tail(&node->list, &list->read); list->count++; } struct uftrace_record *get_first_rstack_list(struct uftrace_rstack_list *list) { struct uftrace_rstack_list_node *node; ASSERT(list->count > 0); node = list_first_entry(&list->read, typeof(*node), list); return &node->rstack; } void consume_first_rstack_list(struct uftrace_rstack_list *list) { struct uftrace_rstack_list_node *node; ASSERT(list->count > 0); node = list_first_entry(&list->read, typeof(*node), list); list_move(&node->list, &list->unused); if (node->rstack.more) ASSERT(node->args.data == NULL); list->count--; } void delete_last_rstack_list(struct uftrace_rstack_list *list) { struct uftrace_rstack_list_node *node; ASSERT(list->count > 0); node = list_last_entry(&list->read, typeof(*node), list); if (node->rstack.more) { free(node->args.data); node->args.data = NULL; } list_move(&node->list, &list->unused); list->count--; } void reset_rstack_list(struct uftrace_rstack_list *list) { while (!list_empty(&list->read)) { struct uftrace_rstack_list_node *node; node = list_first_entry(&list->read, typeof(*node), list); list_del(&node->list); free(node); } while (!list_empty(&list->unused)) { struct uftrace_rstack_list_node *node; node = list_first_entry(&list->unused, typeof(*node), list); list_del(&node->list); free(node); } } static void swap_byte_order(struct uftrace_record *rstack) { uint64_t *ptr = (void *)rstack; ptr[0] = bswap_64(ptr[0]); ptr[1] = bswap_64(ptr[1]); } static void swap_bitfields(struct uftrace_record *rstack) { uint64_t *ptr = (void *)rstack; uint64_t data = ptr[1]; rstack->type = (data >> 0) & 0x3; rstack->more = (data >> 2) & 0x1; rstack->magic = (data >> 3) & 0x7; rstack->depth = (data >> 6) & 0x3ff; rstack->addr = (data >> 16) & 0xffffffffffffULL; } static int __read_task_ustack(struct uftrace_task_reader *task) { FILE *fp = task->fp; if (fread(&task->ustack, sizeof(task->ustack), 1, fp) != 1) { if (feof(fp)) return -1; pr_warn("error reading rstack: %s\n", strerror(errno)); return -1; } if (task->h->needs_byte_swap) swap_byte_order(&task->ustack); if (task->h->needs_bit_swap) swap_bitfields(&task->ustack); if (task->ustack.magic != RECORD_MAGIC) { pr_warn("invalid rstack read\n"); return -1; } return 0; } static int read_task_arg(struct uftrace_task_reader *task, struct uftrace_arg_spec *spec) { FILE *fp = task->fp; struct uftrace_fstack_args *args = &task->args; unsigned size = spec->size; int rem; if (spec->size == 0) return 0; if (spec->fmt == ARG_FMT_STR || spec->fmt == ARG_FMT_STD_STRING) { args->data = xrealloc(args->data, args->len + 2); if (fread(args->data + args->len, 2, 1, fp) != 1) { if (feof(fp)) return -1; } size = *(unsigned short *)(args->data + args->len); args->len += 2; } rem = (args->len + size) % 4; if (rem) size += 4 - rem; args->data = xrealloc(args->data, args->len + size); if (fread(args->data + args->len, size, 1, fp) != 1) { if (feof(fp)) return -1; } args->len += size; return 0; } /** * read_task_args - read arguments of current function of the task * @task: tracee task * @rstack: uftrace_record * @is_retval: 0 reads argument, 1 reads return value * * This function reads argument records of @task's current function * according to the @spec. */ int read_task_args(struct uftrace_task_reader *task, struct uftrace_record *rstack, bool is_retval) { struct uftrace_session *sess; struct uftrace_trigger tr = {}; struct uftrace_filter *fl; struct uftrace_arg_spec *arg; int rem; task->args.len = 0; task->args.args = NULL; /* keep args.data for realloc() */ sess = find_task_session(&task->h->sessions, task->t, rstack->time); if (sess == NULL) { pr_dbg("cannot find session\n"); return -1; } fl = uftrace_match_filter(rstack->addr, &sess->filters, &tr); if (fl == NULL) { pr_dbg("cannot find filter: %lx\n", rstack->addr); return -1; } if (!(tr.flags & (TRIGGER_FL_ARGUMENT | TRIGGER_FL_RETVAL))) { pr_dbg("cannot find arg spec\n"); return -1; } task->args.args = &fl->args; list_for_each_entry(arg, &fl->args, list) { /* skip unwanted arguments or retval */ if (is_retval != (arg->idx == RETVAL_IDX)) continue; if (read_task_arg(task, arg) < 0) return -1; } rem = task->args.len % 8; if (rem) fseek(task->fp, 8 - rem, SEEK_CUR); return 0; } static int read_task_event_size(struct uftrace_task_reader *task, void *buf, size_t buflen) { uint16_t len; if (fread(&len, sizeof(len), 1, task->fp) != 1) return -1; ASSERT(len == buflen); if (fread(buf, len, 1, task->fp) != 1) return -1; return 0; } static void save_task_event(struct uftrace_task_reader *task, void *buf, size_t buflen) { int rem; /* abuse task->args */ task->args.args = (void *)1; task->args.len = buflen; task->args.data = xrealloc(task->args.data, buflen); memcpy(task->args.data, buf, buflen); /* ensure 8-byte alignment */ rem = (buflen + 2) % 8; if (rem) fseek(task->fp, 8 - rem, SEEK_CUR); } int read_task_event(struct uftrace_task_reader *task, struct uftrace_record *rec) { union { struct uftrace_proc_statm statm; struct uftrace_page_fault pgfault; struct uftrace_pmu_cycle cycle; struct uftrace_pmu_cache cache; struct uftrace_pmu_branch branch; int cpu; } u; switch (rec->addr) { case EVENT_ID_READ_PROC_STATM: case EVENT_ID_DIFF_PROC_STATM: if (read_task_event_size(task, &u.statm, sizeof(u.statm)) < 0) return -1; if (task->h->needs_byte_swap) { u.statm.vmsize = bswap_64(u.statm.vmsize); u.statm.vmrss = bswap_64(u.statm.vmrss); u.statm.shared = bswap_64(u.statm.shared); } save_task_event(task, &u.statm, sizeof(u.statm)); break; case EVENT_ID_READ_PAGE_FAULT: case EVENT_ID_DIFF_PAGE_FAULT: if (read_task_event_size(task, &u.pgfault, sizeof(u.pgfault)) < 0) return -1; if (task->h->needs_byte_swap) { u.pgfault.major = bswap_64(u.pgfault.major); u.pgfault.minor = bswap_64(u.pgfault.minor); } save_task_event(task, &u.pgfault, sizeof(u.pgfault)); break; case EVENT_ID_READ_PMU_CYCLE: case EVENT_ID_DIFF_PMU_CYCLE: if (read_task_event_size(task, &u.cycle, sizeof(u.cycle)) < 0) return -1; if (task->h->needs_byte_swap) { u.cycle.cycles = bswap_64(u.cycle.cycles); u.cycle.instrs = bswap_64(u.cycle.instrs); } save_task_event(task, &u.cycle, sizeof(u.cycle)); break; case EVENT_ID_READ_PMU_CACHE: case EVENT_ID_DIFF_PMU_CACHE: if (read_task_event_size(task, &u.cache, sizeof(u.cache)) < 0) return -1; if (task->h->needs_byte_swap) { u.cache.refers = bswap_64(u.cache.refers); u.cache.misses = bswap_64(u.cache.misses); } save_task_event(task, &u.cache, sizeof(u.cache)); break; case EVENT_ID_READ_PMU_BRANCH: case EVENT_ID_DIFF_PMU_BRANCH: if (read_task_event_size(task, &u.branch, sizeof(u.branch)) < 0) return -1; if (task->h->needs_byte_swap) { u.branch.branch = bswap_64(u.branch.branch); u.branch.misses = bswap_64(u.branch.misses); } save_task_event(task, &u.branch, sizeof(u.branch)); break; case EVENT_ID_WATCH_CPU: if (read_task_event_size(task, &u.cpu, sizeof(u.cpu)) < 0) return -1; if (task->h->needs_byte_swap) u.cpu = bswap_32(u.cpu); save_task_event(task, &u.cpu, sizeof(u.cpu)); break; default: pr_err_ns("unknown event has data: %u\n", rec->addr); break; } return 0; } /** * read_task_ustack - read user function record for @task * @handle: file handle * @task: tracee task * * This function reads current ftrace record and save it to @task->ustack. * Data file it accesses should be opened already. When @task->valid is * set, it just returns @task->ustack already read, so if you want to force * read from file, the @task->valid should be reset before calling this * function. * * This function returns 0 if succeeded, -1 otherwise. */ int read_task_ustack(struct uftrace_data *handle, struct uftrace_task_reader *task) { if (task->valid) return 0; if (task->done || task->fp == NULL) return -1; if (__read_task_ustack(task) < 0) { task->done = true; return -1; } if (task->ustack.more) { if (task->ustack.type == UFTRACE_ENTRY) read_task_args(task, &task->ustack, false); else if (task->ustack.type == UFTRACE_EXIT) read_task_args(task, &task->ustack, true); else if (task->ustack.type == UFTRACE_EVENT) read_task_event(task, &task->ustack); if (unlikely(task->args.args == NULL || task->args.len == 0)) { struct uftrace_symbol *sym; char *symname; /* there might be zero-length struct as a return value */ if (task->args.args) { int actual_len = 0; struct uftrace_arg_spec *spec; list_for_each_entry(spec, task->args.args, list) { if ((task->ustack.type == UFTRACE_EXIT) != (spec->idx == RETVAL_IDX)) continue; actual_len += spec->size; } if (actual_len == 0) goto out; } sym = task_find_sym(&handle->sessions, task, &task->ustack); symname = symbol_getname(sym, task->ustack.addr); pr_err_ns("record missing argument info for %s\n", symname); symbol_putname(sym, symname); } } out: task->valid = true; return 0; } /** * get_task_ustack - read task's user function record * @handle: file handle * @idx: task index * * This function returns current ftrace record of @idx-th task from * data file in @handle. */ static struct uftrace_record *get_task_ustack(struct uftrace_data *handle, int idx) { struct uftrace_task_reader *task; struct uftrace_record *curr; struct uftrace_rstack_list *rstack_list; struct uftrace_session_link *sessions = &handle->sessions; task = &handle->tasks[idx]; rstack_list = &task->rstack_list; if (rstack_list->count) goto out; /* * read task (user) stack until it found an entry that exceeds * the given time filter (-t option). */ while (read_task_ustack(handle, task) == 0) { struct uftrace_session *sess; struct uftrace_trigger tr = {}; uint64_t time_filter = handle->time_filter; unsigned size_filter = handle->size_filter; curr = &task->ustack; /* prevent ustack from invalid access */ task->valid = false; if (!check_time_range(&handle->time_range, curr->time)) continue; sess = find_task_session(sessions, task->t, curr->time); if (sess && (curr->type == UFTRACE_ENTRY || curr->type == UFTRACE_EXIT)) uftrace_match_filter(curr->addr, &sess->filters, &tr); if (task->filter.stack) { time_filter = task->filter.stack->threshold; size_filter = task->filter.stack->size; } if (tr.flags & TRIGGER_FL_TIME_FILTER) time_filter = tr.time; if (tr.flags & TRIGGER_FL_SIZE_FILTER) size_filter = tr.size; if (curr->type == UFTRACE_ENTRY) { if (size_filter) { struct uftrace_symbol *sym; sym = find_symtabs(&sess->sym_info, curr->addr); if (sym && sym->size >= size_filter) add_to_rstack_list(rstack_list, curr, &task->args); } else { /* it needs to wait until matching exit found */ add_to_rstack_list(rstack_list, curr, &task->args); } if (tr.flags & (TRIGGER_FL_TIME_FILTER | TRIGGER_FL_SIZE_FILTER)) { struct uftrace_task_filter_stack *tfs; tfs = xmalloc(sizeof(*tfs)); tfs->next = task->filter.stack; tfs->depth = curr->depth; tfs->context = FSTACK_CTX_USER; tfs->threshold = time_filter; tfs->size = size_filter; task->filter.stack = tfs; } } else if (curr->type == UFTRACE_EXIT) { struct uftrace_rstack_list_node *last; uint64_t delta; int last_type; bool filtered = false; if (task->filter.stack) { struct uftrace_task_filter_stack *tfs; tfs = task->filter.stack; if (tfs->depth == curr->depth && tfs->context == FSTACK_CTX_USER) { /* discard stale filter */ task->filter.stack = tfs->next; free(tfs); } } if (size_filter) { struct uftrace_symbol *sym; sym = find_symtabs(&sess->sym_info, curr->addr); if (sym && sym->size < size_filter) continue; } list_for_each_entry_reverse(last, &rstack_list->read, list) { if (last->rstack.type == UFTRACE_ENTRY) break; } if (list_no_entry(last, &rstack_list->read, list)) { /* it's already exceeded time filter, just return */ add_to_rstack_list(rstack_list, curr, &task->args); break; } /* time filter is meaningful for functions */ while (last->rstack.type != UFTRACE_ENTRY) last = list_prev_entry(last, list); delta = curr->time - last->rstack.time; if (delta < time_filter) filtered = true; if (handle->caller_filter) filtered |= !(tr.flags & TRIGGER_FL_CALLER); if (filtered) { /* * it might set TRACE trigger, which shows * function even if it's less than the time * filter. */ if (tr.flags & TRIGGER_FL_TRACE) { add_to_rstack_list(rstack_list, curr, &task->args); break; } /* also delete matching entry (at the last) */ do { last = list_last_entry(&rstack_list->read, typeof(*last), list); last_type = last->rstack.type; delete_last_rstack_list(rstack_list); } while (last_type != UFTRACE_ENTRY); } else { /* found! process all existing rstacks in the list */ add_to_rstack_list(rstack_list, curr, &task->args); break; } } else if (curr->type == UFTRACE_EVENT) { add_to_rstack_list(rstack_list, curr, &task->args); /* show user event regardless of time filter */ if (curr->addr >= EVENT_ID_USER) break; } else { /* TODO: handle LOST properly */ add_to_rstack_list(rstack_list, curr, &task->args); break; } } if (task->done && rstack_list->count == 0) return NULL; out: task->valid = true; curr = get_first_rstack_list(rstack_list); memcpy(&task->ustack, curr, sizeof(*task->rstack)); return &task->ustack; } static int read_user_stack(struct uftrace_data *handle, struct uftrace_task_reader **task) { int i, next_i = -1; uint64_t next_time = 0; struct uftrace_record *tmp; for (i = 0; i < handle->info.nr_tid; i++) { tmp = get_task_ustack(handle, i); if (tmp == NULL) continue; if (next_i < 0 || tmp->time < next_time) { next_time = tmp->time; next_i = i; } } if (next_i < 0) return -1; *task = &handle->tasks[next_i]; return next_i; } static int read_event_stack(struct uftrace_data *handle, struct uftrace_task_reader **task) { int i, next_i = -1; uint64_t next_time = 0; struct uftrace_task_reader *t; struct uftrace_record *curr, *next; for (i = 0; i < handle->info.nr_tid; i++) { t = &handle->tasks[i]; if (t->event_list.count == 0) continue; curr = get_first_rstack_list(&t->event_list); if (next_i < 0 || curr->time < next_time) { next_time = curr->time; next_i = i; next = curr; } } if (next_i < 0) return -1; *task = &handle->tasks[next_i]; memcpy(&(*task)->estack, next, sizeof(*next)); return next_i; } /* convert perf sched events to a virtual schedule function */ static bool convert_perf_event(struct uftrace_task_reader *task, struct uftrace_record *orig, struct uftrace_record *dummy) { switch (orig->addr) { case EVENT_ID_PERF_SCHED_IN: case EVENT_ID_PERF_SCHED_OUT: case EVENT_ID_PERF_SCHED_OUT_PREEMPT: if (orig->addr == EVENT_ID_PERF_SCHED_OUT || orig->addr == EVENT_ID_PERF_SCHED_OUT_PREEMPT) { dummy->type = UFTRACE_ENTRY; task->sched_out_seen = true; task->sched_cpu = task->h->last_perf_idx; } else { dummy->type = UFTRACE_EXIT; task->sched_out_seen = false; } dummy->time = orig->time; dummy->magic = RECORD_MAGIC; dummy->addr = orig->addr; dummy->depth = 0; dummy->more = 0; return true; default: return false; } } static void fstack_account_time(struct uftrace_task_reader *task) { struct uftrace_fstack *fstack; struct uftrace_record *rstack = task->rstack; struct uftrace_record dummy_rec; bool is_kernel_func = is_kernel_record(task, rstack); int i; if (rstack->type == UFTRACE_EVENT) { if (!convert_perf_event(task, rstack, &dummy_rec)) return; rstack = &dummy_rec; } if (!task->fstack_set) { /* inherit stack count after [v]fork() or recover from lost */ task->stack_count = rstack->depth; if (rstack->type == UFTRACE_EXIT) task->stack_count++; task->fstack_set = true; if (!task->fork_handled) { struct uftrace_task_reader *parent = NULL; /* inherit display depth from parent (if possible) */ if (task->t) parent = get_task_handle(task->h, task->t->ppid); if (parent && parent->fork_display_depth) { task->display_depth = parent->fork_display_depth; task->display_depth_set = true; /* * cannot update user_stack_count due to the * kernel_skip_out setting. unfortunately, * it'll show 'negative stack count' debug * message when return to user. */ if (is_kernel_func) task->stack_count += task->display_depth; } task->fork_handled = true; } if (is_kernel_func) task->stack_count += task->user_stack_count; /* calculate duration from now on */ for (i = 0; i < task->stack_count; i++) { fstack = fstack_get(task, i); if (fstack != NULL) { fstack->total_time = rstack->time; /* start time */ fstack->child_time = 0; fstack->valid = true; } } task->filter.depth = task->h->depth; } if (task->lost_seen) { uint64_t timestamp_after_lost; if (rstack->type == UFTRACE_LOST) return; task->stack_count = rstack->depth; if (rstack->type == UFTRACE_EXIT) task->stack_count++; if (is_kernel_func) task->stack_count += task->user_stack_count; timestamp_after_lost = rstack->time - 1; task->lost_seen = false; /* XXX: currently LOST can occur in kernel */ for (i = 0; i <= rstack->depth; i++) { fstack = fstack_get(task, i + task->user_stack_count); /* reset timestamp after seeing LOST */ if (fstack != NULL) { fstack->total_time = timestamp_after_lost; fstack->child_time = 0; } } } if (task->ctx == FSTACK_CTX_KERNEL && !is_kernel_func) { /* protect from broken kernel records */ if (rstack->type != UFTRACE_LOST && rstack != &dummy_rec) { task->stack_count = task->user_stack_count; task->filter.depth = task->h->depth - task->stack_count; } } /* if task filter was set, it doesn't have func_stack */ if (task->func_stack == NULL) return; if (rstack->type == UFTRACE_ENTRY) { fstack = fstack_get(task, task->stack_count); if (fstack == NULL) return; fstack->addr = rstack->addr; fstack->total_time = rstack->time; /* start time */ fstack->child_time = 0; fstack->valid = true; if (is_kernel_func) { struct uftrace_sym_info *sinfo; sinfo = &task->h->sessions.first->sym_info; fstack->addr = get_kernel_address(sinfo, rstack->addr); } } else if (rstack->type == UFTRACE_EXIT) { uint64_t delta; int idx = task->stack_count - 1; fstack = fstack_get(task, idx); if (fstack == NULL) return; delta = rstack->time - fstack->total_time; if (!fstack->valid) delta = 0UL; fstack->valid = false; fstack->total_time = delta; if (fstack->child_time > fstack->total_time) fstack->child_time = fstack->total_time; /* add current time to parent's child time */ if (task->stack_count > 1) fstack[-1].child_time += delta; } else if (rstack->type == UFTRACE_LOST) { uint64_t delta; uint64_t lost_time = 0; task->lost_seen = true; task->display_depth_set = false; /* XXX: currently LOST can occur in kernel */ for (i = task->stack_count; i >= task->user_stack_count; i--) { fstack = fstack_get(task, i); if (fstack == NULL) continue; if (lost_time == 0) lost_time = fstack->total_time + 1; /* account time of remaining functions at LOST */ delta = lost_time - fstack->total_time; fstack->total_time = delta; if (fstack->child_time > fstack->total_time) fstack->child_time = fstack->total_time; if (i > 0) fstack[-1].child_time += delta; } } } static void fstack_update_stack_count(struct uftrace_task_reader *task) { struct uftrace_record *rstack = task->rstack; struct uftrace_record dummy_rec; if (rstack->type == UFTRACE_EVENT) { if (!convert_perf_event(task, rstack, &dummy_rec)) return; rstack = &dummy_rec; } if (is_user_record(task, rstack)) task->ctx = FSTACK_CTX_USER; else if (is_kernel_record(task, rstack)) task->ctx = FSTACK_CTX_KERNEL; else task->ctx = FSTACK_CTX_UNKNOWN; if (rstack->type == UFTRACE_ENTRY) task->stack_count++; else if (rstack->type == UFTRACE_EXIT && task->stack_count > 0) task->stack_count--; if (task->ctx == FSTACK_CTX_USER) { if (rstack->type == UFTRACE_ENTRY) task->user_stack_count++; else if (rstack->type == UFTRACE_EXIT && task->user_stack_count > 0) task->user_stack_count--; } } static int find_rstack_cpu(struct uftrace_kernel_reader *kernel, struct uftrace_record *rstack) { int cpu = -1; if (rstack->type == UFTRACE_LOST) { for (cpu = 0; cpu < kernel->nr_cpus; cpu++) { if (rstack->addr == (unsigned)kparser_missed_events(&kernel->parser, cpu) && rstack->depth == kernel->rstacks[cpu].depth) break; } ASSERT(cpu < kernel->nr_cpus); } else { for (cpu = 0; cpu < kernel->nr_cpus; cpu++) { if (rstack->time == kernel->rstacks[cpu].time && rstack->addr == kernel->rstacks[cpu].addr) break; } ASSERT(cpu < kernel->nr_cpus); } return cpu; } static void __fstack_consume(struct uftrace_task_reader *task, struct uftrace_kernel_reader *kernel, int cpu) { struct uftrace_record *rstack = task->rstack; struct uftrace_data *handle = task->h; if (rstack->more) { struct uftrace_rstack_list_node *node; if (is_user_record(task, rstack)) node = list_first_entry(&task->rstack_list.read, typeof(*node), list); else if (is_kernel_record(task, rstack)) node = list_first_entry(&kernel->rstack_list[cpu].read, typeof(*node), list); else if (is_event_record(task, rstack)) node = list_first_entry(&task->event_list.read, typeof(*node), list); else if (is_extern_record(task, rstack)) goto consume_extern_data; else goto consume_perf_event; ASSERT(node->args.data); /* restore args/retval to task */ free(task->args.data); task->args.args = node->args.args; task->args.data = node->args.data; task->args.len = node->args.len; node->args.data = NULL; } if (is_user_record(task, rstack)) { task->valid = false; if (task->rstack_list.count) consume_first_rstack_list(&task->rstack_list); } else if (is_kernel_record(task, rstack)) { kernel->rstack_valid[cpu] = false; if (kernel->rstack_list[cpu].count) consume_first_rstack_list(&kernel->rstack_list[cpu]); } else if (is_event_record(task, rstack)) { if (rstack->addr == EVENT_ID_PERF_COMM) { strncpy(task->t->comm, task->args.data, TASK_COMM_LAST); task->t->comm[TASK_COMM_LAST] = '\0'; } if (task->event_list.count) consume_first_rstack_list(&task->event_list); } else if (rstack->type == UFTRACE_LOST) { kparser_clear_missed(&kernel->parser, cpu); } else if (is_extern_record(task, rstack)) { struct uftrace_extern_reader *extn; consume_extern_data: extn = handle->extn; extn->valid = false; /* restore args/retval to task */ free(task->args.data); task->args.data = xstrdup(extn->msg); task->args.len = strlen(extn->msg); } else { /* must be perf event */ struct uftrace_perf_reader *perf; consume_perf_event: ASSERT(handle->last_perf_idx >= 0); perf = &handle->perf[handle->last_perf_idx]; if (rstack->addr == EVENT_ID_PERF_COMM) { memcpy(task->t->comm, perf->u.comm.comm, sizeof(task->t->comm)); } perf->valid = false; } update_first_timestamp(handle, task, rstack); fstack_account_time(task); fstack_update_stack_count(task); } /** * fstack_consume - consume current rstack read * @handle: file handle * @task: task that holds current rstack * * This function consumes currently read stack by peek_rstack() so that * it can read next rstack in the data file. */ void fstack_consume(struct uftrace_data *handle, struct uftrace_task_reader *task) { struct uftrace_record *rstack = task->rstack; struct uftrace_kernel_reader *kernel = handle->kernel; int cpu = 0; if (is_kernel_record(task, rstack)) cpu = find_rstack_cpu(kernel, rstack); __fstack_consume(task, kernel, cpu); } static bool peek_perf_data(struct uftrace_task_reader *task) { struct uftrace_data *handle = task->h; struct uftrace_perf_reader *perf = NULL; read_perf_data(handle); perf = &handle->perf[task->sched_cpu]; if (perf->tid != task->tid) return false; if (perf->type != PERF_RECORD_SWITCH || perf->u.ctxsw.out) return false; return true; } static bool peek_event_rstack(struct uftrace_task_reader *task) { struct uftrace_record *rec; rec = get_first_rstack_list(&task->event_list); if (rec->type != UFTRACE_EVENT) return false; if (rec->addr != EVENT_ID_PERF_SCHED_IN) return false; return true; } /* delay ustack after this schedule event */ static void adjust_rstack_after_schedule(struct uftrace_data *handle, struct uftrace_task_reader *task) { struct uftrace_record *next_rec; struct uftrace_fstack *prev_fstack; if (task->rstack_list.count == 0) return; next_rec = get_first_rstack_list(&task->rstack_list); if (next_rec->type == UFTRACE_ENTRY) { task->timestamp_next = 0; return; } /* get ENTRY record of the user function */ if (task->rstack->addr == EVENT_ID_PERF_SCHED_IN && task->stack_count >= 2) prev_fstack = fstack_get(task, task->stack_count - 2); else prev_fstack = fstack_get(task, task->stack_count - 1); if (prev_fstack == NULL || prev_fstack->addr != next_rec->addr) return; if (task->rstack->addr == EVENT_ID_PERF_SCHED_OUT || task->rstack->addr == EVENT_ID_PERF_SCHED_OUT_PREEMPT) { if (task->timestamp_next == 0) { /* * Get start time of next real (ENTRY) record as * it's necessary to adjust return time after schedule. * Note that estimated time of an EXIT record is a * half between two ENTRY records. * * So we can next timestamp by adding diff between * ENTRY and EXIT (i.e. half of delta) to EXIT. * * EXIT + (EXIT - ENTRY) = 2*EXIT - ENTRY */ task->timestamp_next = next_rec->time * 2; task->timestamp_next -= prev_fstack->total_time; } pr_dbg3("task[%6d] delay next record after schedule\n", task->tid); next_rec->time = ~0ULL; return; } /* if next record's time is later than schedule, it's fine */ if (task->timestamp_estimate > task->rstack->time) { next_rec->time = task->timestamp_estimate; return; } /* calculate half between sched in and next */ next_rec->time = (task->rstack->time + task->timestamp_next) / 2; /* save it in case it's overwritten by subsequent schedule */ task->timestamp_estimate = next_rec->time; pr_dbg3("task[%6d] estimate next record after schedule\n", task->tid); } static int __read_rstack(struct uftrace_data *handle, struct uftrace_task_reader **taskp, bool consume) { int u, k = -1, p, e, x; struct uftrace_task_reader *task = NULL; struct uftrace_task_reader *utask = NULL; struct uftrace_task_reader *ktask = NULL; struct uftrace_task_reader *etask = NULL; struct uftrace_kernel_reader *kernel = handle->kernel; struct uftrace_perf_reader *perf = NULL; struct uftrace_extern_reader *extn = handle->extn; uint64_t min_timestamp = ~0ULL; enum { NONE, USER, KERNEL, PERF, EVENT, EXTERN } source = NONE; /* keep last selected task for external data */ static struct uftrace_task_reader *last_task = NULL; if (handle->time_filter) process_perf_event(handle); u = read_user_stack(handle, &utask); if (u >= 0) { min_timestamp = utask->ustack.time; source = USER; } if (has_kernel_data(kernel)) { k = read_kernel_stack(handle, &ktask); if (k < 0) { static bool warn = false; if (!warn && consume) { pr_dbg2("no more kernel data\n"); warn = true; } } else if (ktask->kstack.time < min_timestamp) { min_timestamp = ktask->kstack.time; source = KERNEL; } } if (has_perf_data(handle) && !handle->time_filter) { p = read_perf_data(handle); perf = &handle->perf[p]; if (p < 0) { static bool warn = false; if (!warn && consume) { pr_dbg2("no more perf data\n"); warn = true; } } else if (perf->time < min_timestamp) { min_timestamp = perf->time; source = PERF; } } if (has_event_data(handle)) { e = read_event_stack(handle, &etask); if (e >= 0 && etask->estack.time < min_timestamp) { min_timestamp = etask->estack.time; source = EVENT; } } if (has_extern_data(handle)) { x = read_extern_data(handle, extn); if (x >= 0 && extn->time < min_timestamp) { min_timestamp = extn->time; source = EXTERN; } } switch (source) { case USER: utask->rstack = &utask->ustack; task = utask; /* subsequent EXIT records might have inverted timestamp */ if (handle->hdr.feat_mask & ESTIMATE_RETURN && task->timestamp_estimate != 0) { if (task->rstack->type == UFTRACE_EXIT && task->rstack->time <= task->timestamp_estimate) { task->rstack->time = ++task->timestamp_estimate; } else { /* * ENTRY records are always fine since * they have real timestamps. */ task->timestamp_estimate = 0; task->timestamp_next = 0; } } break; case KERNEL: ktask->rstack = get_kernel_record(kernel, ktask, k); task = ktask; /* * deep kernel trace includes 'schedule' function which * mixed with perf sched event. since depth of in/out event * differs, consume sched in early with same depth. */ if (unlikely(task->sched_out_seen)) { if (has_perf_data(handle) && !handle->time_filter && peek_perf_data(task)) { perf = &handle->perf[task->sched_cpu]; task->rstack = get_perf_record(handle, perf); task->rstack->time = min_timestamp - 1; } else if (has_event_data(handle) && handle->time_filter && peek_event_rstack(task)) { task->rstack = &task->estack; task->rstack->time = min_timestamp - 1; } } break; case PERF: task = get_task_handle(handle, perf->tid); if (unlikely(task == NULL)) pr_err_ns("cannot find task %d\n", perf->tid); task->rstack = get_perf_record(handle, perf); if (task->rstack->addr == EVENT_ID_PERF_COMM) { task->rstack->more = 1; /* abuse task->args to save comm */ free(task->args.data); task->args.data = xstrdup(perf->u.comm.comm); task->args.len = strlen(perf->u.comm.comm); } else if (task->rstack->addr == EVENT_ID_PERF_SCHED_IN || task->rstack->addr == EVENT_ID_PERF_SCHED_OUT || task->rstack->addr == EVENT_ID_PERF_SCHED_OUT_PREEMPT) { if (handle->hdr.feat_mask & ESTIMATE_RETURN && task->stack_count > 0) adjust_rstack_after_schedule(handle, task); } break; case EVENT: etask->rstack = &etask->estack; task = etask; break; case EXTERN: task = last_task; if (unlikely(task == NULL)) task = &handle->tasks[0]; task->rstack = get_extern_record(extn, &task->xstack); break; case NONE: default: return -1; } /* update stack count when the rstack is actually used */ if (consume) { last_task = task; __fstack_consume(task, kernel, k); } *taskp = task; return 0; } /** * read_rstack - read and consume the oldest ftrace stack * @handle: file handle * @task: pointer to the oldest task * * This function reads all function trace records of each task, * compares the timestamp, and find the oldest one. After this * function @task will point a task which has the oldest record, and * it can be accessed by @task->rstack. The oldest record will be * consumed, that means it sets another (*@task)->rstack for next * call. * * This function returns 0 if it reads a rstack, -1 if it's done. */ int read_rstack(struct uftrace_data *handle, struct uftrace_task_reader **task) { return __read_rstack(handle, task, true); } /** * peek_rstack - read the oldest ftrace stack * @handle: file handle * @task: pointer to the oldest task * * This function reads all function trace records of each task, * compares the timestamp, and find the oldest one. After this * function @task will point a task which has the oldest record, and * it can be accessed by @task->rstack. The oldest record will *NOT* * be consumed, that means another call to this or @read_rstack will * return same (*@task)->rstack. * * This function returns 0 if it reads a rstack, -1 if it's done. */ int peek_rstack(struct uftrace_data *handle, struct uftrace_task_reader **task) { return __read_rstack(handle, task, false); } #ifdef UNIT_TEST #include #define NUM_TASK 2 #define NUM_RECORD 4 static int test_tids[NUM_TASK] = { 1234, 5678 }; static struct uftrace_task test_tasks[NUM_TASK]; static struct uftrace_record test_record[NUM_TASK][NUM_RECORD] = { { { 100, UFTRACE_ENTRY, false, RECORD_MAGIC, 0, 0x40000 }, { 200, UFTRACE_ENTRY, false, RECORD_MAGIC, 1, 0x41000 }, { 300, UFTRACE_EXIT, false, RECORD_MAGIC, 1, 0x41000 }, { 400, UFTRACE_EXIT, false, RECORD_MAGIC, 0, 0x40000 }, }, { { 150, UFTRACE_ENTRY, false, RECORD_MAGIC, 0, 0x40000 }, { 250, UFTRACE_ENTRY, false, RECORD_MAGIC, 1, 0x41000 }, { 350, UFTRACE_EXIT, false, RECORD_MAGIC, 1, 0x41000 }, { 450, UFTRACE_EXIT, false, RECORD_MAGIC, 0, 0x40000 }, } }; static struct uftrace_record exec_record[] = { { 000, UFTRACE_ENTRY, false, RECORD_MAGIC, 0, 0x40000 }, // main { 100, UFTRACE_ENTRY, false, RECORD_MAGIC, 1, 0x43000 }, // execve { 200, UFTRACE_ENTRY, false, RECORD_MAGIC, 0, 0x40000 }, // main { 300, UFTRACE_ENTRY, false, RECORD_MAGIC, 1, 0x40100 }, // a { 400, UFTRACE_ENTRY, false, RECORD_MAGIC, 2, 0x40200 }, // b { 500, UFTRACE_ENTRY, false, RECORD_MAGIC, 3, 0x40300 }, // c { 600, UFTRACE_EXIT, false, RECORD_MAGIC, 3, 0x40300 }, // c { 700, UFTRACE_EXIT, false, RECORD_MAGIC, 2, 0x40200 }, // b { 800, UFTRACE_EXIT, false, RECORD_MAGIC, 1, 0x40100 }, // a { 900, UFTRACE_EXIT, false, RECORD_MAGIC, 0, 0x40000 }, // main }; static struct uftrace_session test_sess; static struct uftrace_data fstack_test_handle; static void fstack_test_finish_file(void); static int fstack_test_setup_file(struct uftrace_data *handle, int nr_tid, int *tids, int nr_record, struct uftrace_record **records) { int i; char *filename; handle->dirname = "tmp.dir"; handle->info.tids = tids; handle->info.nr_tid = nr_tid; handle->hdr.max_stack = 16; handle->depth = 16; if (handle->sessions.first == NULL) handle->sessions.first = &test_sess; /* it doesn't have kernel functions */ handle->sessions.first->sym_info.kernel_base = -1ULL; if (mkdir(handle->dirname, 0755) < 0) { if (errno != EEXIST) { pr_dbg("cannot create temp dir: %m\n"); return -1; } } for (i = 0; i < handle->info.nr_tid; i++) { FILE *fp; if (asprintf(&filename, "%s/%d.dat", handle->dirname, handle->info.tids[i]) < 0) { pr_dbg("cannot alloc filename: %s/%d.dat", handle->dirname, handle->info.tids[i]); return -1; } fp = fopen(filename, "w"); if (fp == NULL) { pr_dbg("file open failed: %m\n"); free(filename); return -1; } fwrite(records[i], sizeof(**records), nr_record, fp); free(filename); fclose(fp); test_tasks[i].tid = handle->info.tids[i]; } fstack_setup_task(NULL, handle); /* for fstack_entry not to crash */ for (i = 0; i < handle->info.nr_tid; i++) { if (handle->tasks[i].t == NULL) handle->tasks[i].t = &test_tasks[i]; } setup_perf_data(handle); atexit(fstack_test_finish_file); return 0; } static int fstack_test_setup_normal(struct uftrace_data *handle) { struct uftrace_record *normal_tests[] = { test_record[0], test_record[1], }; return fstack_test_setup_file(handle, NUM_TASK, test_tids, NUM_RECORD, normal_tests); } static int fstack_test_setup_single(struct uftrace_data *handle) { struct uftrace_record *single_tests[] = { test_record[0], }; return fstack_test_setup_file(handle, 1, test_tids, NUM_RECORD, single_tests); } static int fstack_test_setup_exec(struct uftrace_data *handle) { struct uftrace_record *exec_tests[] = { exec_record, }; static struct uftrace_symbol exec_sym = { .addr = 0x3000, .size = 16, .name = "execve", .type = ST_PLT_FUNC, }; static struct uftrace_module exec_mod = { .symtab = { .sym = &exec_sym, .nr_sym = 1, }, }; static struct uftrace_mmap map = { .mod = &exec_mod, .start = 0x40000, .end = 0x50000, }; struct uftrace_msg_sess smsg = { .task = { .pid = test_tids[0], .tid = test_tids[0], }, .sid = "uftrace-session", .namelen = 8, /* .name falls through (?) */ }; char name[] = "unittest"; create_session(&handle->sessions, &smsg, "tests", "tests", name, true, false, false); create_task(&handle->sessions, &smsg.task, false); handle->sessions.first->sym_info.maps = ↦ handle->sessions.first->sym_info.exec_map = ↦ return fstack_test_setup_file(handle, 1, test_tids, ARRAY_SIZE(exec_record), exec_tests); } static void fstack_test_finish_file(void) { int i; char *filename; struct uftrace_data *handle = &fstack_test_handle; if (handle->dirname == NULL) return; reset_task_handle(handle); for (i = 0; i < handle->info.nr_tid; i++) { if (asprintf(&filename, "%s/%d.dat", handle->dirname, handle->info.tids[i]) < 0) return; remove(filename); free(filename); } remove(handle->dirname); handle->dirname = NULL; } TEST_CASE(fstack_read) { struct uftrace_data *handle = &fstack_test_handle; struct uftrace_task_reader *task; int i; TEST_EQ(fstack_test_setup_normal(handle), 0); for (i = 0; i < NUM_RECORD; i++) { pr_dbg("[%d] read rstack from task %d\n", i, test_tids[0]); TEST_EQ(read_rstack(handle, &task), 0); TEST_EQ(task->tid, test_tids[0]); TEST_EQ((uint64_t)task->rstack->type, (uint64_t)test_record[0][i].type); TEST_EQ((uint64_t)task->rstack->depth, (uint64_t)test_record[0][i].depth); TEST_EQ((uint64_t)task->rstack->addr, (uint64_t)test_record[0][i].addr); pr_dbg("[%d] peek rstack from task %d\n", i, test_tids[1]); TEST_EQ(peek_rstack(handle, &task), 0); TEST_EQ(task->tid, test_tids[1]); TEST_EQ((uint64_t)task->rstack->type, (uint64_t)test_record[1][i].type); TEST_EQ((uint64_t)task->rstack->depth, (uint64_t)test_record[1][i].depth); TEST_EQ((uint64_t)task->rstack->addr, (uint64_t)test_record[1][i].addr); pr_dbg("[%d] read rstack from task %d\n", i, test_tids[1]); TEST_EQ(read_rstack(handle, &task), 0); TEST_EQ(task->tid, test_tids[1]); TEST_EQ((uint64_t)task->rstack->type, (uint64_t)test_record[1][i].type); TEST_EQ((uint64_t)task->rstack->depth, (uint64_t)test_record[1][i].depth); TEST_EQ((uint64_t)task->rstack->addr, (uint64_t)test_record[1][i].addr); } return TEST_OK; } TEST_CASE(fstack_skip) { struct uftrace_data *handle = &fstack_test_handle; struct uftrace_task_reader *task; struct uftrace_trigger tr = { 0, }; struct uftrace_opts opts = { .event_skip_out = true, .libcall = true, }; TEST_EQ(fstack_test_setup_single(handle), 0); pr_dbg("set depth filter to skip depth 1 records\n"); handle->depth = 1; pr_dbg("read first rstack for task %d\n", test_tids[0]); TEST_EQ(read_rstack(handle, &task), 0); TEST_EQ(fstack_entry(task, task->rstack, &tr), 0); TEST_EQ(task->tid, test_tids[0]); TEST_EQ((uint64_t)task->rstack->type, (uint64_t)test_record[0][0].type); TEST_EQ((uint64_t)task->rstack->depth, (uint64_t)test_record[0][0].depth); TEST_EQ((uint64_t)task->rstack->addr, (uint64_t)test_record[0][0].addr); /* skip filtered records (due to depth) */ pr_dbg("fstack skip to rstack for task %d again\n", test_tids[0]); TEST_EQ(fstack_skip(handle, task, task->rstack->depth, &opts), task); TEST_EQ(task->tid, test_tids[0]); TEST_EQ((uint64_t)task->rstack->type, (uint64_t)test_record[0][3].type); TEST_EQ((uint64_t)task->rstack->depth, (uint64_t)test_record[0][3].depth); TEST_EQ((uint64_t)task->rstack->addr, (uint64_t)test_record[0][3].addr); return TEST_OK; } TEST_CASE(fstack_time) { struct uftrace_data *handle = &fstack_test_handle; struct uftrace_task_reader *task; int i; TEST_EQ(fstack_test_setup_normal(handle), 0); pr_dbg("set time filter to skip depth 1 records\n"); handle->time_filter = 200; for (i = 0; i < NUM_TASK; i++) { pr_dbg("[%d] read rstack from task %d\n", i, test_tids[0]); TEST_EQ(read_rstack(handle, &task), 0); TEST_EQ(task->tid, test_tids[0]); TEST_EQ((uint64_t)task->rstack->type, (uint64_t)test_record[0][i * 3].type); TEST_EQ((uint64_t)task->rstack->depth, (uint64_t)test_record[0][i * 3].depth); TEST_EQ((uint64_t)task->rstack->addr, (uint64_t)test_record[0][i * 3].addr); pr_dbg("[%d] read rstack from task %d\n", i, test_tids[1]); TEST_EQ(read_rstack(handle, &task), 0); TEST_EQ(task->tid, test_tids[1]); TEST_EQ((uint64_t)task->rstack->type, (uint64_t)test_record[1][i * 3].type); TEST_EQ((uint64_t)task->rstack->depth, (uint64_t)test_record[1][i * 3].depth); TEST_EQ((uint64_t)task->rstack->addr, (uint64_t)test_record[1][i * 3].addr); } return TEST_OK; } TEST_CASE(fstack_fixup) { struct uftrace_data *handle = &fstack_test_handle; struct uftrace_task_reader *task; int i; TEST_EQ(fstack_test_setup_exec(handle), 0); pr_dbg("set fixup filter to detect special functions like exec\n"); fstack_prepare_fixup(handle); for (i = 0; i < ARRAY_SIZE(exec_record); i++) { pr_dbg("[%d] read rstack from task %d\n", i, test_tids[0]); TEST_EQ(read_rstack(handle, &task), 0); TEST_EQ(fstack_check_filter(task), true); pr_dbg("check stack count of the task\n"); TEST_EQ(task->tid, test_tids[0]); TEST_EQ((uint64_t)task->rstack->addr, (uint64_t)exec_record[i].addr); if (task->rstack->type == UFTRACE_ENTRY) TEST_EQ(task->stack_count - 1, (int)exec_record[i].depth); else TEST_EQ(task->stack_count, (int)exec_record[i].depth); pr_dbg("adjust stack count after filter check\n"); fstack_check_filter_done(task); } return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/fstack.h000066400000000000000000000122161455365734300160350ustar00rootroot00000000000000#ifndef UFTRACE_FSTACK_H #define UFTRACE_FSTACK_H #include #include #include #include "uftrace.h" #include "utils/filter.h" struct uftrace_symbol; enum uftrace_fstack_flag { FSTACK_FL_FILTERED = (1U << 0), FSTACK_FL_NOTRACE = (1U << 1), FSTACK_FL_NORECORD = (1U << 2), FSTACK_FL_EXEC = (1U << 3), FSTACK_FL_LONGJMP = (1U << 4), }; enum uftrace_fstack_context { FSTACK_CTX_UNKNOWN = 0, FSTACK_CTX_USER = 1, FSTACK_CTX_KERNEL = 2, }; struct uftrace_task_filter_stack { struct uftrace_task_filter_stack *next; uint64_t threshold; int depth; unsigned size; enum uftrace_fstack_context context; }; struct uftrace_task_reader { int tid; bool valid; bool done; bool lost_seen; bool sched_out_seen; bool fork_handled; bool fstack_set; bool display_depth_set; bool fstack_warned; FILE *fp; struct uftrace_symbol *func; struct uftrace_task *t; struct uftrace_data *h; struct uftrace_record ustack; struct uftrace_record kstack; struct uftrace_record estack; struct uftrace_record xstack; struct uftrace_record *rstack; struct uftrace_rstack_list rstack_list; struct uftrace_rstack_list event_list; int stack_count; int lost_count; int user_stack_count; int display_depth; int user_display_depth; int fork_display_depth; int column_index; int event_color; int sched_cpu; enum uftrace_fstack_context ctx; uint64_t timestamp; uint64_t timestamp_last; uint64_t timestamp_next; uint64_t timestamp_estimate; struct { int in_count; int out_count; int depth; struct uftrace_task_filter_stack *stack; } filter; struct uftrace_fstack { uint64_t addr; bool valid; int orig_depth; unsigned long flags; uint64_t total_time; uint64_t child_time; } * func_stack; struct uftrace_fstack_args args; bool sched_preempt_seen; }; enum uftrace_argspec_string_bits { /* bit index */ NEEDS_PAREN_BIT, NEEDS_SEMI_COLON_BIT, HAS_MORE_BIT, IS_RETVAL_BIT, NEEDS_ASSIGNMENT_BIT, NEEDS_JSON_BIT, /* bit mask */ NEEDS_PAREN = (1U << NEEDS_PAREN_BIT), NEEDS_SEMI_COLON = (1U << NEEDS_SEMI_COLON_BIT), HAS_MORE = (1U << HAS_MORE_BIT), IS_RETVAL = (1U << IS_RETVAL_BIT), NEEDS_ASSIGNMENT = (1U << NEEDS_ASSIGNMENT_BIT), NEEDS_JSON = (1U << NEEDS_JSON_BIT), }; extern bool fstack_enabled; extern bool live_disabled; struct uftrace_task_reader *get_task_handle(struct uftrace_data *handle, int tid); void reset_task_handle(struct uftrace_data *handle); void fstack_setup_task(char *tid_filter, struct uftrace_data *handle); int read_rstack(struct uftrace_data *handle, struct uftrace_task_reader **task); int peek_rstack(struct uftrace_data *handle, struct uftrace_task_reader **task); void fstack_consume(struct uftrace_data *handle, struct uftrace_task_reader *task); int read_task_ustack(struct uftrace_data *handle, struct uftrace_task_reader *task); int read_task_args(struct uftrace_task_reader *task, struct uftrace_record *rstack, bool is_retval); static inline bool is_user_record(struct uftrace_task_reader *task, struct uftrace_record *rec) { return rec == &task->ustack; } static inline bool is_kernel_record(struct uftrace_task_reader *task, struct uftrace_record *rec) { return rec == &task->kstack; } static inline bool is_event_record(struct uftrace_task_reader *task, struct uftrace_record *rec) { return rec == &task->estack; } static inline bool is_extern_record(struct uftrace_task_reader *task, struct uftrace_record *rec) { return rec == &task->xstack; } void setup_fstack_args(char *argspec, char *retspec, struct uftrace_data *handle, struct uftrace_filter_setting *setting); int fstack_setup_filters(struct uftrace_opts *opts, struct uftrace_data *handle); struct uftrace_fstack *fstack_get(struct uftrace_task_reader *task, int idx); int fstack_entry(struct uftrace_task_reader *task, struct uftrace_record *rstack, struct uftrace_trigger *tr); void fstack_exit(struct uftrace_task_reader *task); int fstack_update(int type, struct uftrace_task_reader *task, struct uftrace_fstack *fstack); struct uftrace_task_reader *fstack_skip(struct uftrace_data *handle, struct uftrace_task_reader *task, int curr_depth, struct uftrace_opts *opts); bool fstack_check_filter(struct uftrace_task_reader *task); bool fstack_check_opts(struct uftrace_task_reader *task, struct uftrace_opts *opts); void fstack_check_filter_done(struct uftrace_task_reader *task); bool is_sched_event(uint64_t addr); bool is_sched_preempt_event(struct uftrace_task_reader *task, uint64_t addr); void get_argspec_string(struct uftrace_task_reader *task, char *args, size_t len, enum uftrace_argspec_string_bits str_mode); #define EXTERN_DATA_MAX 1024 struct uftrace_extern_reader { FILE *fp; bool valid; uint64_t time; char msg[EXTERN_DATA_MAX]; struct uftrace_record rec; }; int setup_extern_data(struct uftrace_data *handle, struct uftrace_opts *opts); int read_extern_data(struct uftrace_data *handle, struct uftrace_extern_reader *extn); struct uftrace_record *get_extern_record(struct uftrace_extern_reader *extn, struct uftrace_record *rec); int finish_extern_data(struct uftrace_data *handle); static inline bool has_extern_data(struct uftrace_data *handle) { return handle->extn != NULL; } #endif /* UFTRACE_FSTACK_H */ uftrace-0.15.2/utils/graph.c000066400000000000000000000217171455365734300156640ustar00rootroot00000000000000#include "utils/graph.h" #include "utils/filter.h" #include "utils/list.h" #include "utils/rbtree.h" static graph_fn entry_cb; static graph_fn exit_cb; static graph_fn event_cb; static void *cb_arg; static struct rb_root task_graph_root = RB_ROOT; void graph_init(struct uftrace_graph *graph, struct uftrace_session *s) { memset(graph, 0, sizeof(*graph)); graph->sess = s; INIT_LIST_HEAD(&graph->root.head); INIT_LIST_HEAD(&graph->special_nodes); } void graph_init_callbacks(graph_fn entry_fn, graph_fn exit_fn, graph_fn event_fn, void *arg) { entry_cb = entry_fn; exit_cb = exit_fn; event_cb = event_fn; cb_arg = arg; } struct uftrace_task_graph *graph_get_task(struct uftrace_task_reader *task, size_t tg_size) { struct rb_node *parent = NULL; struct rb_node **p = &task_graph_root.rb_node; struct uftrace_task_graph *tg; while (*p) { parent = *p; tg = rb_entry(parent, struct uftrace_task_graph, link); if (tg->task->tid == task->tid) return tg; if (tg->task->tid > task->tid) p = &parent->rb_left; else p = &parent->rb_right; } tg = xzalloc(tg_size); tg->task = task; rb_link_node(&tg->link, parent, p); rb_insert_color(&tg->link, &task_graph_root); return tg; } static int add_graph_entry(struct uftrace_task_graph *tg, char *name, size_t node_size, struct uftrace_dbg_loc *loc) { struct uftrace_graph_node *node = NULL; struct uftrace_graph_node *curr = tg->node; struct uftrace_fstack *fstack; static uint32_t next_id = 1; if (tg->lost) return 1; /* ignore kernel functions after LOST */ if (tg->new_sess) { curr = &tg->graph->root; pr_dbg2("starts new session graph for task %d\n", tg->task->tid); tg->new_sess = false; } fstack = fstack_get(tg->task, tg->task->stack_count - 1); if (curr == NULL || fstack == NULL) return -1; list_for_each_entry(node, &curr->head, list) { if (name && !strcmp(name, node->name)) break; } if (list_no_entry(node, &curr->head, list)) { struct uftrace_trigger tr; struct uftrace_session *sess = tg->graph->sess; node = xzalloc(node_size); node->id = next_id++; node->addr = fstack->addr; node->name = xstrdup(name ?: "none"); INIT_LIST_HEAD(&node->head); node->parent = curr; list_add_tail(&node->list, &node->parent->head); node->parent->nr_edges++; node->loc = loc; if (sess && uftrace_match_filter(fstack->addr, &sess->fixups, &tr)) { struct uftrace_symbol *sym; struct uftrace_special_node *snode; enum uftrace_graph_node_type type = NODE_T_NORMAL; sym = find_symtabs(&sess->sym_info, fstack->addr); if (sym == NULL) goto out; if (!strcmp(sym->name, "fork") || !strcmp(sym->name, "vfork") || !strcmp(sym->name, "daemon")) type = NODE_T_FORK; else if (!strncmp(sym->name, "exec", 4)) type = NODE_T_EXEC; else goto out; snode = xmalloc(sizeof(*snode)); snode->node = node; snode->type = type; snode->pid = tg->task->t->pid; /* find recent one first */ list_add(&snode->list, &tg->graph->special_nodes); } } out: node->nr_calls++; tg->node = node; if (entry_cb) entry_cb(tg, cb_arg); return 0; } static int add_graph_exit(struct uftrace_task_graph *tg) { struct uftrace_fstack *fstack = fstack_get(tg->task, tg->task->stack_count); struct uftrace_graph_node *node = tg->node; if (node == NULL || fstack == NULL) return -1; if (tg->lost) { if (is_kernel_address(&tg->task->h->sessions.first->sym_info, fstack->addr)) return 1; /* * LOST only occurs in kernel, so clear tg->lost * when return to userspace */ tg->lost = false; } if (node->addr != fstack->addr) { struct uftrace_special_node *snode, *tmp; list_for_each_entry_safe(snode, tmp, &tg->graph->special_nodes, list) { if (snode->node->addr == tg->task->rstack->addr && snode->type == NODE_T_FORK && snode->pid == tg->task->t->ppid) { node = snode->node; list_del(&snode->list); free(snode); pr_dbg("recover from fork\n"); goto out; } } pr_dbg("broken graph - addresses not match\n"); } out: node->time += fstack->total_time; node->child_time += fstack->child_time; if (exit_cb) exit_cb(tg, cb_arg); tg->node = node->parent; return 0; } static int add_graph_event(struct uftrace_task_graph *tg, size_t node_size) { struct uftrace_record *rec = tg->task->rstack; if (event_cb) event_cb(tg, cb_arg); if (rec->addr == EVENT_ID_PERF_SCHED_OUT) { /* to match addr with sched-in */ rec->addr = EVENT_ID_PERF_SCHED_IN; return add_graph_entry(tg, sched_sym.name, node_size, NULL); } else if (rec->addr == EVENT_ID_PERF_SCHED_OUT_PREEMPT) { /* to match addr with sched-in */ rec->addr = EVENT_ID_PERF_SCHED_IN; return add_graph_entry(tg, sched_preempt_sym.name, node_size, NULL); } else if (rec->addr == EVENT_ID_PERF_SCHED_IN) { return add_graph_exit(tg); } return -1; } /* graph_add_node is not thread-safe due to static id of uftrace_graph_node */ int graph_add_node(struct uftrace_task_graph *tg, int type, char *name, size_t node_size, struct uftrace_dbg_loc *loc) { if (type == UFTRACE_ENTRY) return add_graph_entry(tg, name, node_size, loc); else if (type == UFTRACE_EXIT) return add_graph_exit(tg); else if (type == UFTRACE_EVENT) return add_graph_event(tg, node_size); else return 0; } struct uftrace_graph_node *graph_find_node(struct uftrace_graph_node *parent, uint64_t addr) { struct uftrace_graph_node *node; list_for_each_entry(node, &parent->head, list) { if (addr == node->addr) return node; } return NULL; } static void graph_destroy_node(struct uftrace_graph_node *node) { struct uftrace_graph_node *child, *tmp; list_for_each_entry_safe(child, tmp, &node->head, list) graph_destroy_node(child); list_del(&node->list); free(node->name); free(node); } void graph_destroy(struct uftrace_graph *graph) { struct uftrace_graph_node *node, *tmp; struct uftrace_special_node *snode, *stmp; list_for_each_entry_safe(node, tmp, &graph->root.head, list) graph_destroy_node(node); list_for_each_entry_safe(snode, stmp, &graph->special_nodes, list) { list_del(&snode->list); free(snode); } } void graph_remove_task(void) { struct rb_node *node; struct uftrace_task_graph *tg; while (!RB_EMPTY_ROOT(&task_graph_root)) { node = rb_first(&task_graph_root); tg = rb_entry(node, struct uftrace_task_graph, link); rb_erase(node, &task_graph_root); free(tg); } } #ifdef UNIT_TEST struct test_data { int type; uint64_t addr; uint64_t total_time; uint64_t child_time; const char *name; }; static void setup_fstack_and_graph(struct uftrace_graph *graph, struct test_data *data, size_t len) { size_t i; struct uftrace_task_graph *tg; struct uftrace_graph_node *node; struct uftrace_task_reader task = { .tid = 1234, }; tg = graph_get_task(&task, sizeof(*tg)); tg->graph = graph; tg->new_sess = true; /* TODO: de-couple graph from fstack */ task.func_stack = xcalloc(sizeof(*task.func_stack), len); for (i = 0; i < len; i++) { struct uftrace_fstack *fstack = NULL; if (data[i].type == UFTRACE_ENTRY) fstack = &task.func_stack[task.stack_count++]; else if (data[i].type == UFTRACE_EXIT) fstack = &task.func_stack[--task.stack_count]; if (fstack) { fstack->addr = data[i].addr; fstack->total_time = data[i].total_time; fstack->child_time = data[i].child_time; } graph_add_node(tg, data[i].type, (char *)data[i].name, sizeof(*node), NULL); } free(task.func_stack); } TEST_CASE(graph_basic) { struct uftrace_graph graph; struct uftrace_graph_node *node; /* * (root) * +-- (1) foo * | * +-- (1) bar * (1) baz */ struct test_data data[] = { { UFTRACE_ENTRY, 0x1000, 0, 0, "foo", }, { UFTRACE_EXIT, 0x1000, 100, 0, "foo", }, { UFTRACE_ENTRY, 0x2000, 0, 0, "bar", }, { UFTRACE_ENTRY, 0x3000, 0, 0, "baz", }, { UFTRACE_EXIT, 0x3000, 200, 0, "baz", }, { UFTRACE_EXIT, 0x2000, 500, 200, "bar", }, }; pr_dbg("init graph and add data\n"); graph_init(&graph, NULL); setup_fstack_and_graph(&graph, data, ARRAY_SIZE(data)); pr_dbg("check graph root\n"); TEST_EQ(graph.root.nr_edges, 2); pr_dbg("check graph node: foo\n"); node = graph_find_node(&graph.root, data[1].addr); TEST_NE(node, NULL); TEST_STREQ(node->name, data[1].name); TEST_EQ(node->time, data[1].total_time); TEST_EQ(node->child_time, data[1].child_time); TEST_EQ(node->nr_calls, 1); pr_dbg("check graph node: bar\n"); node = graph_find_node(&graph.root, data[5].addr); TEST_NE(node, NULL); TEST_STREQ(node->name, data[5].name); TEST_EQ(node->time, data[5].total_time); TEST_EQ(node->child_time, data[5].child_time); TEST_EQ(node->nr_calls, 1); pr_dbg("check graph node: baz\n"); node = graph_find_node(node, data[4].addr); TEST_NE(node, NULL); TEST_STREQ(node->name, data[4].name); TEST_EQ(node->time, data[4].total_time); TEST_EQ(node->child_time, data[4].child_time); TEST_EQ(node->nr_calls, 1); pr_dbg("destroy graph and data\n"); graph_destroy(&graph); graph_remove_task(); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/graph.h000066400000000000000000000031451455365734300156640ustar00rootroot00000000000000#ifndef UFTRACE_GRAPH_H #define UFTRACE_GRAPH_H #include #include #include "uftrace.h" #include "utils/fstack.h" #include "utils/list.h" #include "utils/rbtree.h" struct uftrace_graph_node { uint64_t addr; char *name; int nr_edges; int nr_calls; uint64_t time; uint64_t child_time; uint32_t id; struct list_head head; struct list_head list; struct uftrace_graph_node *parent; struct uftrace_dbg_loc *loc; }; enum uftrace_graph_node_type { NODE_T_NORMAL, NODE_T_FORK, NODE_T_EXEC, }; struct uftrace_special_node { struct list_head list; struct uftrace_graph_node *node; enum uftrace_graph_node_type type; int pid; }; struct uftrace_graph { bool kernel_only; struct uftrace_session *sess; struct list_head special_nodes; struct uftrace_graph_node root; }; struct uftrace_task_graph { bool lost; bool new_sess; struct uftrace_task_reader *task; struct uftrace_graph *graph; struct uftrace_graph_node *node; struct rb_node link; }; typedef void (*graph_fn)(struct uftrace_task_graph *tg, void *arg); void graph_init(struct uftrace_graph *graph, struct uftrace_session *s); void graph_init_callbacks(graph_fn entry, graph_fn exit, graph_fn event, void *arg); void graph_destroy(struct uftrace_graph *graph); struct uftrace_task_graph *graph_get_task(struct uftrace_task_reader *task, size_t tg_size); void graph_remove_task(void); int graph_add_node(struct uftrace_task_graph *tg, int type, char *name, size_t node_size, struct uftrace_dbg_loc *loc); struct uftrace_graph_node *graph_find_node(struct uftrace_graph_node *parent, uint64_t addr); #endif /* UFTRACE_GRAPH_H */ uftrace-0.15.2/utils/hashmap.c000066400000000000000000000214061455365734300161770ustar00rootroot00000000000000/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include "hashmap.h" #include "utils.h" typedef pthread_mutex_t mutex_t; typedef struct Entry Entry; struct Entry { void *key; hash_t hash; void *value; Entry *next; }; struct Hashmap { Entry **buckets; size_t bucket_count; hash_t (*hash)(void *key); bool (*equals)(void *keyA, void *keyB); mutex_t lock; size_t size; }; Hashmap *hashmap_create(size_t initial_capacity, hash_t (*hash)(void *key), bool (*equals)(void *keyA, void *keyB)) { Hashmap *map; size_t minimum_bucket_count; ASSERT(hash != NULL); ASSERT(equals != NULL); map = malloc(sizeof(Hashmap)); if (map == NULL) { return NULL; } // 0.75 load factor. minimum_bucket_count = initial_capacity * 4 / 3; map->bucket_count = 1; while (map->bucket_count <= minimum_bucket_count) { // Bucket count must be power of 2. map->bucket_count <<= 1; } map->buckets = calloc(map->bucket_count, sizeof(Entry *)); if (map->buckets == NULL) { free(map); return NULL; } map->size = 0; map->hash = hash; map->equals = equals; pthread_mutex_init(&map->lock, NULL); return map; } /** * Hashes the given key. */ static hash_t hash_key(Hashmap *map, void *key) { hash_t h = map->hash(key); return h; } size_t hashmap_size(Hashmap *map) { return map->size; } static inline size_t calculate_index(size_t bucket_count, int hash) { return ((size_t)hash) & (bucket_count - 1); } static void expand_if_necessary(Hashmap *map) { // If the load factor exceeds 0.75... if (map->size > (map->bucket_count * 3 / 4)) { // Start off with a 0.33 load factor. size_t new_bucket_count = map->bucket_count << 1; Entry **new_buckets; size_t i; new_buckets = calloc(new_bucket_count, sizeof(Entry *)); if (new_buckets == NULL) { // Abort expansion. return; } // Move over existing entries. for (i = 0; i < map->bucket_count; i++) { Entry *entry = map->buckets[i]; while (entry != NULL) { Entry *next = entry->next; size_t index = calculate_index(new_bucket_count, entry->hash); entry->next = new_buckets[index]; new_buckets[index] = entry; entry = next; } } // Copy over internals. free(map->buckets); map->buckets = new_buckets; map->bucket_count = new_bucket_count; } } void hashmap_lock(Hashmap *map) { pthread_mutex_lock(&map->lock); } void hashmap_unlock(Hashmap *map) { pthread_mutex_unlock(&map->lock); } void hashmap_free(Hashmap *map) { size_t i; for (i = 0; i < map->bucket_count; i++) { Entry *entry = map->buckets[i]; while (entry != NULL) { Entry *next = entry->next; free(entry); entry = next; } } free(map->buckets); pthread_mutex_destroy(&map->lock); free(map); } hash_t hashmap_hash(void *key, size_t key_size) { hash_t h = key_size; char *data = (char *)key; size_t i; for (i = 0; i < key_size; i++) { h = h * 31 + *data; data++; } return h; } static Entry *create_entry(void *key, int hash, void *value) { Entry *entry = malloc(sizeof(Entry)); if (entry == NULL) { return NULL; } entry->key = key; entry->hash = hash; entry->value = value; entry->next = NULL; return entry; } static inline bool equal_keys(void *keyA, int hashA, void *keyB, int hashB, bool (*equals)(void *, void *)) { if (keyA == keyB) { return true; } if (hashA != hashB) { return false; } return equals(keyA, keyB); } void *hashmap_put(Hashmap *map, void *key, void *value) { hash_t hash = hash_key(map, key); size_t index = calculate_index(map->bucket_count, hash); Entry **p = &(map->buckets[index]); while (true) { Entry *current = *p; // Add a new entry. if (current == NULL) { *p = create_entry(key, hash, value); if (*p == NULL) { errno = ENOMEM; return NULL; } map->size++; expand_if_necessary(map); return value; } // Replace existing entry. if (equal_keys(current->key, current->hash, key, hash, map->equals)) { void *oldValue = current->value; current->value = value; return oldValue; } // Move to next entry. p = ¤t->next; } } void *hashmap_get(Hashmap *map, void *key) { hash_t hash = hash_key(map, key); size_t index = calculate_index(map->bucket_count, hash); Entry *entry = map->buckets[index]; while (entry != NULL) { if (equal_keys(entry->key, entry->hash, key, hash, map->equals)) { return entry->value; } entry = entry->next; } return NULL; } bool hashmap_contains_key(Hashmap *map, void *key) { hash_t hash = hash_key(map, key); size_t index = calculate_index(map->bucket_count, hash); Entry *entry = map->buckets[index]; while (entry != NULL) { if (equal_keys(entry->key, entry->hash, key, hash, map->equals)) { return true; } entry = entry->next; } return false; } void *hashmap_memoize(Hashmap *map, void *key, void *(*initial_value)(void *key, void *context), void *context) { hash_t hash = hash_key(map, key); size_t index = calculate_index(map->bucket_count, hash); Entry **p = &(map->buckets[index]); while (true) { Entry *current = *p; // Add a new entry. if (current == NULL) { void *value; *p = create_entry(key, hash, NULL); if (*p == NULL) { errno = ENOMEM; return NULL; } value = initial_value(key, context); (*p)->value = value; map->size++; expand_if_necessary(map); return value; } // Return existing value. if (equal_keys(current->key, current->hash, key, hash, map->equals)) { return current->value; } // Move to next entry. p = ¤t->next; } } void *hashmap_remove(Hashmap *map, void *key) { hash_t hash = hash_key(map, key); size_t index = calculate_index(map->bucket_count, hash); // Pointer to the current entry. Entry **p = &(map->buckets[index]); Entry *current; while ((current = *p) != NULL) { if (equal_keys(current->key, current->hash, key, hash, map->equals)) { void *value = current->value; *p = current->next; free(current); map->size--; return value; } p = ¤t->next; } return NULL; } void hashmap_for_each(Hashmap *map, bool (*callback)(void *key, void *value, void *context), void *context) { size_t i; for (i = 0; i < map->bucket_count; i++) { Entry *entry = map->buckets[i]; while (entry != NULL) { Entry *next = entry->next; if (!callback(entry->key, entry->value, context)) { return; } entry = next; } } } size_t hashmap_current_capacity(Hashmap *map) { size_t bucket_count = map->bucket_count; return bucket_count * 3 / 4; } size_t hashmap_count_collisions(Hashmap *map) { size_t collisions = 0; size_t i; for (i = 0; i < map->bucket_count; i++) { Entry *entry = map->buckets[i]; while (entry != NULL) { if (entry->next != NULL) { collisions++; } entry = entry->next; } } return collisions; } hash_t hashmap_default_hash(void *key) { return *((hash_t *)key); } bool hashmap_default_equals(void *keyA, void *keyB) { hash_t a = *((hash_t *)keyA); hash_t b = *((hash_t *)keyB); return a == b; } hash_t hashmap_ptr_hash(void *key) { return (uintptr_t)key; } bool hashmap_ptr_equals(void *keyA, void *keyB) { hash_t a = (uintptr_t)keyA; hash_t b = (uintptr_t)keyB; return a == b; } #ifdef UNIT_TEST #include "utils/utils.h" TEST_CASE(hashmap_expand) { int orig_size = 3; Hashmap *hmap = hashmap_create(orig_size, hashmap_default_hash, hashmap_default_equals); size_t count = hmap->bucket_count; hash_t keys[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; char str_value[] = "string value longer than the keys"; int i; pr_dbg("add entries not to expand yet\n"); for (i = 0; i < orig_size; i++) { hashmap_put(hmap, &keys[i], &str_value[i]); } TEST_EQ(hmap->bucket_count, count); pr_dbg("add more entries to expand\n"); for (i = 0; i < ARRAY_SIZE(keys); i++) { if (i < orig_size) { TEST_EQ(hashmap_contains_key(hmap, &keys[i]), true); continue; } hashmap_put(hmap, &keys[i], &str_value[i]); } pr_dbg("now hmap should be expanded\n"); TEST_GT(hmap->bucket_count, count); pr_dbg("check keys return correct values\n"); for (i = 0; i < ARRAY_SIZE(keys); i++) { void *val = hashmap_get(hmap, &keys[i]); TEST_NE(val, NULL); TEST_EQ(val, &str_value[i]); } hashmap_free(hmap); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/hashmap.h000066400000000000000000000074611455365734300162110ustar00rootroot00000000000000/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef __HASHMAP_H #define __HASHMAP_H #include #include #include #if defined(__amd64__) || defined(__aarch64__) #define ARCH64 #else #define ARCH32 #endif #if defined(ARCH64) typedef int64_t hash_t; typedef uint64_t uhash_t; #elif defined(ARCH32) typedef int32_t hash_t; typedef uint32_t uhash_t; #endif /** A hash map. */ typedef struct Hashmap Hashmap; /** * Creates a new hash map. Returns NULL if memory allocation fails. * * @param initialCapacity number of expected entries * @param hash function which hashes keys * @param equals function which compares keys for equality */ Hashmap *hashmap_create(size_t initialCapacity, hash_t (*hash)(void *key), bool (*equals)(void *keyA, void *keyB)); /** * Frees the hash map. Does not free the keys or values themselves. */ void hashmap_free(Hashmap *map); /** * Hashes the memory pointed to by key with the given size. Useful for * implementing hash functions. */ hash_t hashmap_hash(void *key, size_t keySize); /** * Puts value for the given key in the map. Returns pre-existing value if * any, otherwise it returns the given value. * * If memory allocation fails, this function returns NULL, the map's size * does not increase, and errno is set to ENOMEM. */ void *hashmap_put(Hashmap *map, void *key, void *value); /** * Gets a value from the map. Returns NULL if no entry for the given key is * found or if the value itself is NULL. */ void *hashmap_get(Hashmap *map, void *key); /** * Returns true if the map contains an entry for the given key. */ bool hashmap_contains_key(Hashmap *map, void *key); /** * Gets the value for a key. If a value is not found, this function gets a * value and creates an entry using the given callback. * * If memory allocation fails, the callback is not called, this function * returns NULL, and errno is set to ENOMEM. */ void *hashmap_memoize(Hashmap *map, void *key, void *(*initialValue)(void *key, void *context), void *context); /** * Removes an entry from the map. Returns the removed value or NULL if no * entry was present. */ void *hashmap_remove(Hashmap *map, void *key); /** * Gets the number of entries in this map. */ size_t hashmap_size(Hashmap *map); /** * Invokes the given callback on each entry in the map. Stops iterating if * the callback returns false. */ void hashmap_for_each(Hashmap *map, bool (*callback)(void *key, void *value, void *context), void *context); /** * Concurrency support. */ /** * Locks the hash map so only the current thread can access it. */ void hashmap_lock(Hashmap *map); /** * Unlocks the hash map so other threads can access it. */ void hashmap_unlock(Hashmap *map); /** * Key utilities. */ hash_t hashmap_default_hash(void *key); /** * Compares two keys for equality. */ bool hashmap_default_equals(void *keyA, void *keyB); /** * Gets current capacity. */ size_t hashmap_current_capacity(Hashmap *map); /** * Counts the number of entry collisions. */ size_t hashmap_count_collisions(Hashmap *map); /** * Key utilities - use pointer as key. */ hash_t hashmap_ptr_hash(void *key); /** * Compares two pointers for equality. */ bool hashmap_ptr_equals(void *keyA, void *keyB); #endif /* __HASHMAP_H */ uftrace-0.15.2/utils/kernel-parser.c000066400000000000000000000225551455365734300173360ustar00rootroot00000000000000#ifdef HAVE_LIBTRACEEVENT #include #include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "kernel" #define PR_DOMAIN DBG_KERNEL #include "uftrace.h" #include "utils/kernel-parser.h" #include "utils/kernel.h" #include "utils/utils.h" /* To check if there's (user) data for the task (tid) after read kernel data */ extern struct uftrace_task_reader *get_task_handle(struct uftrace_data *handle, int tid); int kparser_init(struct uftrace_kernel_parser *kp) { memset(kp, 0, sizeof(*kp)); kp->tep = tep_alloc(); if (kp->tep == NULL) return -1; trace_seq_init(&kp->seqbuf); return 0; } int kparser_exit(struct uftrace_kernel_parser *kp) { tep_free(kp->tep); trace_seq_destroy(&kp->seqbuf); memset(kp, 0, sizeof(*kp)); return 0; } bool kparser_ready(struct uftrace_kernel_parser *kp) { return kp->tep != NULL; } int kparser_strerror(struct uftrace_kernel_parser *kp, int err, char *buf, int len) { return tep_strerror(kp->tep, err, buf, len); } void kparser_set_info(struct uftrace_kernel_parser *kp, int page_size, int long_size, bool is_big_endian) { bool is_host_bigendian = (strcmp(get_endian_str(), "BE") == 0); kp->pagesize = page_size; tep_set_page_size(kp->tep, page_size); tep_set_long_size(kp->tep, long_size); tep_set_file_bigendian(kp->tep, is_big_endian); tep_set_local_bigendian(kp->tep, is_host_bigendian); } int kparser_read_header(struct uftrace_kernel_parser *kp, char *buf, int len) { int long_size = tep_get_long_size(kp->tep); return tep_parse_header_page(kp->tep, buf, len, long_size); } int kparser_read_event(struct uftrace_kernel_parser *kp, const char *sys, char *buf, int len) { return tep_parse_event(kp->tep, buf, len, sys); } int kparser_prepare_buffers(struct uftrace_kernel_parser *kp, int nr_cpus) { kp->kbufs = xcalloc(nr_cpus, sizeof(*kp->kbufs)); kp->fds = xcalloc(nr_cpus, sizeof(*kp->fds)); kp->sizes = xcalloc(nr_cpus, sizeof(*kp->sizes)); kp->mmaps = xcalloc(nr_cpus, sizeof(*kp->mmaps)); kp->offsets = xcalloc(nr_cpus, sizeof(*kp->offsets)); kp->missed_events = xcalloc(nr_cpus, sizeof(*kp->missed_events)); return 0; } int kparser_release_buffers(struct uftrace_kernel_parser *kp, int nr_cpus) { free(kp->kbufs); free(kp->fds); free(kp->sizes); free(kp->mmaps); free(kp->offsets); free(kp->missed_events); return 0; } static int kparser_prepare_kbuffer(struct uftrace_kernel_parser *kp, int cpu) { kp->mmaps[cpu] = mmap(NULL, kp->pagesize, PROT_READ, MAP_PRIVATE, kp->fds[cpu], kp->offsets[cpu]); if (kp->mmaps[cpu] == MAP_FAILED) { pr_dbg("loading kbuffer for cpu %d (fd: %d, offset: %lu, pagesize: %zd) failed\n", cpu, kp->fds[cpu], kp->offsets[cpu], kp->pagesize); return -1; } kbuffer_load_subbuffer(kp->kbufs[cpu], kp->mmaps[cpu]); kp->missed_events[cpu] = kbuffer_missed_events(kp->kbufs[cpu]); return 0; } int kparser_prepare_cpu(struct uftrace_kernel_parser *kp, const char *filename, int cpu) { struct stat stbuf; enum kbuffer_endian endian = KBUFFER_ENDIAN_LITTLE; enum kbuffer_long_size longsize = KBUFFER_LSIZE_8; if (tep_is_file_bigendian(kp->tep)) endian = KBUFFER_ENDIAN_BIG; if (tep_get_long_size(kp->tep) == 4) longsize = KBUFFER_LSIZE_4; kp->fds[cpu] = open(filename, O_RDONLY); if (kp->fds[cpu] < 0) return -1; if (fstat(kp->fds[cpu], &stbuf) < 0) return -1; kp->sizes[cpu] = stbuf.st_size; kp->kbufs[cpu] = kbuffer_alloc(longsize, endian); if (kp->kbufs[cpu] == NULL) return -1; if (tep_is_old_format(kp->tep)) kbuffer_set_old_format(kp->kbufs[cpu]); if (!kp->sizes[cpu]) return 0; return kparser_prepare_kbuffer(kp, cpu); } int kparser_release_cpu(struct uftrace_kernel_parser *kp, int cpu) { close(kp->fds[cpu]); kp->fds[cpu] = -1; munmap(kp->mmaps[cpu], kp->pagesize); kp->mmaps[cpu] = NULL; kbuffer_free(kp->kbufs[cpu]); kp->kbufs[cpu] = NULL; return 0; } /* kernel trace event handlers */ static int funcgraph_entry_handler(struct trace_seq *s, struct tep_record *record, struct tep_event *event, void *context) { struct uftrace_kernel_parser *kp = context; unsigned long long depth; unsigned long long addr; if (tep_get_any_field_val(s, event, "depth", record, &depth, 1)) return -1; if (tep_get_any_field_val(s, event, "func", record, &addr, 1)) return -1; kp->rec.type = UFTRACE_ENTRY; kp->rec.time = record->ts; kp->rec.addr = addr; kp->rec.depth = depth; kp->rec.more = 0; return 0; } static int funcgraph_exit_handler(struct trace_seq *s, struct tep_record *record, struct tep_event *event, void *context) { struct uftrace_kernel_parser *kp = context; unsigned long long depth; unsigned long long addr; if (tep_get_any_field_val(s, event, "depth", record, &depth, 1)) return -1; if (tep_get_any_field_val(s, event, "func", record, &addr, 1)) return -1; kp->rec.type = UFTRACE_EXIT; kp->rec.time = record->ts; kp->rec.addr = addr; kp->rec.depth = depth; kp->rec.more = 0; return 0; } static int generic_event_handler(struct trace_seq *s, struct tep_record *record, struct tep_event *event, void *context) { struct uftrace_kernel_parser *kp = context; kp->rec.type = UFTRACE_EVENT; kp->rec.time = record->ts; kp->rec.addr = event->id; kp->rec.depth = 0; kp->rec.more = 1; /* for trace_seq to be filled according to its print_fmt */ return 1; } void kparser_register_handler(struct uftrace_kernel_parser *kp, const char *sys, const char *event) { if (!strcmp(sys, "ftrace")) { if (!strcmp(event, "funcgraph_entry")) tep_register_event_handler(kp->tep, -1, sys, event, funcgraph_entry_handler, kp); else if (!strcmp(event, "funcgraph_exit")) tep_register_event_handler(kp->tep, -1, sys, event, funcgraph_exit_handler, kp); } else tep_register_event_handler(kp->tep, -1, sys, event, generic_event_handler, kp); } static int kparser_next_page(struct uftrace_kernel_parser *kp, int cpu) { munmap(kp->mmaps[cpu], kp->pagesize); kp->mmaps[cpu] = NULL; kp->offsets[cpu] += kp->pagesize; if (kp->offsets[cpu] >= (loff_t)kp->sizes[cpu]) return 1; return kparser_prepare_kbuffer(kp, cpu); } /* return 0 on success, 1 on EOF or -1 on error */ int kparser_read_data(struct uftrace_kernel_parser *kp, struct uftrace_data *handle, int cpu, int *ptid) { void *data; int type, tid; unsigned long long timestamp; struct tep_record record; struct tep_event *event; struct kbuffer *kbuf = kp->kbufs[cpu]; data = kbuffer_read_event(kbuf, ×tamp); while (!data) { int ret = kparser_next_page(kp, cpu); if (ret) return ret; data = kbuffer_read_event(kbuf, ×tamp); } record.ts = timestamp; record.cpu = cpu; record.data = data; record.offset = kbuffer_curr_offset(kbuf); record.missed_events = kbuffer_missed_events(kbuf); record.size = kbuffer_event_size(kbuf); record.record_size = kbuffer_curr_size(kbuf); // record.ref_count = 1; // record.locked = 1; type = tep_data_type(kp->tep, &record); if (type == 0) return -1; // padding event = tep_find_event(kp->tep, type); if (event == NULL) { pr_dbg("cannot find event for type: %d\n", type); return -1; } trace_seq_reset(&kp->seqbuf); /* this will call event handlers */ tep_print_event(kp->tep, &kp->seqbuf, &record, "%s", TEP_PRINT_INFO); tid = tep_data_pid(kp->tep, &record); /* * some event might be saved for unrelated task. In this case * pid for our child would be in a different field (not common_pid). */ if (kp->rec.type == UFTRACE_EVENT && get_task_handle(handle, tid) == NULL) { unsigned long long try_tid; /* for sched_switch event */ if (tep_get_field_val(NULL, event, "next_pid", &record, &try_tid, 0) == 0 && get_task_handle(handle, try_tid) != NULL) tid = try_tid; /* for sched_wakeup event (or others) */ else if (tep_get_field_val(NULL, event, "pid", &record, &try_tid, 0) == 0 && get_task_handle(handle, try_tid) != NULL) tid = try_tid; } *ptid = tid; kbuffer_next_event(kbuf, NULL); return 0; } int kparser_data_size(struct uftrace_kernel_parser *kp, int cpu) { return kp->sizes[cpu]; } /* This is valid only after kparser_read_data() */ void *kparser_trace_buffer(struct uftrace_kernel_parser *kp) { return kp->seqbuf.buffer; } /* This is valid only after kparser_read_data() */ int kparser_trace_buflen(struct uftrace_kernel_parser *kp) { return kp->seqbuf.len; } int kparser_missed_events(struct uftrace_kernel_parser *kp, int cpu) { return kp->missed_events[cpu]; } void kparser_clear_missed(struct uftrace_kernel_parser *kp, int cpu) { kp->missed_events[cpu] = 0; } void *kparser_find_event(struct uftrace_kernel_parser *kp, int evt_id) { return tep_find_event(kp->tep, evt_id); } char *kparser_event_name(struct uftrace_kernel_parser *kp, void *evt, char *buf, int len) { struct tep_event *event = evt; snprintf(buf, len, "%s:%s", event->system, event->name); buf[len - 1] = '\0'; return buf; } int64_t __kparser_curr_offset(struct uftrace_kernel_parser *kp, int cpu) { return kp->offsets[cpu] + kbuffer_curr_offset(kp->kbufs[cpu]); } void *__kparser_read_offset(struct uftrace_kernel_parser *kp, int cpu, int64_t off) { return kbuffer_read_at_offset(kp->kbufs[cpu], off, NULL); } void *__kparser_next_event(struct uftrace_kernel_parser *kp, int cpu) { return kbuffer_next_event(kp->kbufs[cpu], NULL); } int __kparser_event_size(struct uftrace_kernel_parser *kp, int cpu) { return kbuffer_event_size(kp->kbufs[cpu]); } #endif /* HAVE_LIBTRACEEVENT */ uftrace-0.15.2/utils/kernel-parser.h000066400000000000000000000121351455365734300173340ustar00rootroot00000000000000#ifndef UFTRACE_KERNEL_PARSER_H #define UFTRACE_KERNEL_PARSER_H #include #include #include "uftrace.h" #ifdef HAVE_LIBTRACEEVENT #include #include /* * Wrappers for kernel function/event parser (using libtraceevent). */ struct uftrace_kernel_parser { /* global data */ struct tep_handle *tep; struct trace_seq seqbuf; struct uftrace_record rec; size_t pagesize; /* per-cpu data */ struct kbuffer **kbufs; int *fds; void **mmaps; int64_t *offsets; int64_t *sizes; int *missed_events; }; int kparser_init(struct uftrace_kernel_parser *kp); int kparser_exit(struct uftrace_kernel_parser *kp); bool kparser_ready(struct uftrace_kernel_parser *kp); int kparser_strerror(struct uftrace_kernel_parser *kp, int err, char *buf, int len); void kparser_set_info(struct uftrace_kernel_parser *kp, int page_size, int long_size, bool is_big_endian); int kparser_read_header(struct uftrace_kernel_parser *kp, char *buf, int len); int kparser_read_event(struct uftrace_kernel_parser *kp, const char *sys, char *buf, int len); int kparser_prepare_buffers(struct uftrace_kernel_parser *kp, int nr_cpus); int kparser_release_buffers(struct uftrace_kernel_parser *kp, int nr_cpus); int kparser_prepare_cpu(struct uftrace_kernel_parser *kp, const char *filename, int cpu); int kparser_release_cpu(struct uftrace_kernel_parser *kp, int cpu); void kparser_register_handler(struct uftrace_kernel_parser *kp, const char *sys, const char *event); int kparser_read_data(struct uftrace_kernel_parser *kp, struct uftrace_data *handle, int cpu, int *tid); int kparser_data_size(struct uftrace_kernel_parser *kp, int cpu); int kparser_missed_events(struct uftrace_kernel_parser *kp, int cpu); void kparser_clear_missed(struct uftrace_kernel_parser *kp, int cpu); void *kparser_trace_buffer(struct uftrace_kernel_parser *kp); int kparser_trace_buflen(struct uftrace_kernel_parser *kp); void *kparser_find_event(struct uftrace_kernel_parser *kp, int evt_id); char *kparser_event_name(struct uftrace_kernel_parser *kp, void *evt, char *buf, int len); /* low-level APIs - not encouraged to use */ int64_t __kparser_curr_offset(struct uftrace_kernel_parser *kp, int cpu); void *__kparser_read_offset(struct uftrace_kernel_parser *kp, int cpu, int64_t off); void *__kparser_next_event(struct uftrace_kernel_parser *kp, int cpu); int __kparser_event_size(struct uftrace_kernel_parser *kp, int cpu); #else /* HAVE_LIBTRACEEVENT */ struct uftrace_kernel_parser { struct uftrace_record rec; }; static inline int kparser_init(struct uftrace_kernel_parser *kp) { return -1; } static inline int kparser_exit(struct uftrace_kernel_parser *kp) { return -1; } static inline bool kparser_ready(struct uftrace_kernel_parser *kp) { return false; } static inline int kparser_strerror(struct uftrace_kernel_parser *kp, int err, char *buf, int len) { buf[0] = '\0'; return 0; } static inline void kparser_set_info(struct uftrace_kernel_parser *kp, int page_size, int long_size, bool is_big_endian) { } static inline int kparser_read_header(struct uftrace_kernel_parser *kp, char *buf, int len) { return -1; } static inline int kparser_read_event(struct uftrace_kernel_parser *kp, const char *sys, char *buf, int len) { return -1; } static inline int kparser_prepare_buffers(struct uftrace_kernel_parser *kp, int nr_cpus) { return -1; } static inline int kparser_release_buffers(struct uftrace_kernel_parser *kp, int nr_cpus) { return -1; } static inline int kparser_prepare_cpu(struct uftrace_kernel_parser *kp, const char *filename, int cpu) { return -1; } static inline int kparser_release_cpu(struct uftrace_kernel_parser *kp, int cpu) { return -1; } static inline void kparser_register_handler(struct uftrace_kernel_parser *kp, const char *sys, const char *event) { } static inline int kparser_read_data(struct uftrace_kernel_parser *kp, struct uftrace_data *handle, int cpu, int *tid) { return -1; } static inline int kparser_data_size(struct uftrace_kernel_parser *kp, int cpu) { return 0; } static inline int kparser_missed_events(struct uftrace_kernel_parser *kp, int cpu) { return 0; } static inline void kparser_clear_missed(struct uftrace_kernel_parser *kp, int cpu) { } static inline void *kparser_trace_buffer(struct uftrace_kernel_parser *kp) { return NULL; } static inline int kparser_trace_buflen(struct uftrace_kernel_parser *kp) { return 0; } static inline void *kparser_find_event(struct uftrace_kernel_parser *kp, int evt_id) { return NULL; } static inline char *kparser_event_name(struct uftrace_kernel_parser *kp, void *evt, char *buf, int len) { return NULL; } static inline int64_t __kparser_curr_offset(struct uftrace_kernel_parser *kp, int cpu) { return -1; } static inline void *__kparser_read_offset(struct uftrace_kernel_parser *kp, int cpu, int64_t off) { return NULL; } static inline void *__kparser_next_event(struct uftrace_kernel_parser *kp, int cpu) { return NULL; } static inline int __kparser_event_size(struct uftrace_kernel_parser *kp, int cpu) { return 0; } #endif /* HAVE_LIBTRACEEVENT */ #endif /* UFTRACE_KERNEL_PARSER_H */ uftrace-0.15.2/utils/kernel.c000066400000000000000000001340431455365734300160400ustar00rootroot00000000000000/* * Linux kernel ftrace support code. * * Copyright (c) 2015-2018 LG Electronics, Namhyung Kim * * Released under the GPL v2. */ #include #include #include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "kernel" #define PR_DOMAIN DBG_KERNEL #include "uftrace.h" #include "utils/filter.h" #include "utils/fstack.h" #include "utils/kernel-parser.h" #include "utils/kernel.h" #include "utils/rbtree.h" #include "utils/tracefs.h" #include "utils/utils.h" static bool kernel_tracing_enabled; /* tree of executed kernel functions */ static struct rb_root kfunc_tree = RB_ROOT; static int save_kernel_files(struct uftrace_kernel_writer *kernel); static int load_kernel_files(struct uftrace_kernel_reader *kernel); struct kfilter { struct list_head list; char name[]; }; static int set_filter_file(const char *filter_file, struct list_head *filters) { struct kfilter *pos, *tmp; int fd; fd = open_tracing_file(filter_file, true); if (fd < 0) return -1; list_for_each_entry_safe(pos, tmp, filters, list) { /* * it might fail with non-existing functions added by * add_single_filter() or skip_kernel_functions(). */ __write_tracing_file(fd, filter_file, pos->name, true, true); list_del(&pos->list); free(pos); /* separate filters by space */ if (write(fd, " ", 1) != 1) pr_dbg2("writing filter file failed, but ignoring...\n"); } close(fd); return 0; } static int set_tracing_filter(struct uftrace_kernel_writer *kernel) { if (set_filter_file("set_graph_function", &kernel->filters) < 0) return -1; /* ignore error on old kernel */ set_filter_file("set_graph_notrace", &kernel->notrace); if (set_filter_file("set_ftrace_filter", &kernel->patches) < 0) return -1; if (set_filter_file("set_ftrace_notrace", &kernel->nopatch) < 0) return -1; return 0; } static int set_tracing_depth(struct uftrace_kernel_writer *kernel) { int ret = 0; char buf[32]; snprintf(buf, sizeof(buf), "%d", kernel->depth); ret = write_tracing_file("max_graph_depth", buf); return ret; } static int set_tracing_bufsize(struct uftrace_kernel_writer *kernel) { int ret = 0; char buf[32]; if (kernel->bufsize) { snprintf(buf, sizeof(buf), "%lu", kernel->bufsize >> 10); ret = write_tracing_file("buffer_size_kb", buf); } return ret; } /* check whether the kernel supports pid filter inheritance */ bool check_kernel_pid_filter(void) { bool ret = true; char *filename = get_tracing_file("options/function-fork"); if (filename == NULL) return false; if (!access(filename, F_OK)) ret = false; put_tracing_file(filename); return ret; } static int set_tracing_options(struct uftrace_kernel_writer *kernel) { /* old kernels don't have the options, ignore errors */ if (!write_tracing_file("options/function-fork", "1")) write_tracing_file("options/event-fork", "1"); return 0; } static void add_single_filter(struct list_head *head, char *name) { struct kfilter *kfilter; kfilter = xmalloc(sizeof(*kfilter) + strlen(name) + 1); strcpy(kfilter->name, name); list_add(&kfilter->list, head); } static void add_pattern_filter(struct list_head *head, struct uftrace_pattern *patt) { char *filename; FILE *fp; char buf[1024]; filename = get_tracing_file("available_filter_functions"); fp = fopen(filename, "r"); if (fp == NULL) pr_err("failed to open 'tracing/available_filter_functions' file"); while (fgets(buf, sizeof(buf), fp) != NULL) { /* remove module name part */ char *pos = strchr(buf, '['); size_t len; if (pos) *pos = '\0'; /* remove trailing whitespace */ len = strlen(buf); if (isspace(buf[len - 1])) buf[len - 1] = '\0'; if (match_filter_pattern(patt, buf)) add_single_filter(head, buf); } fclose(fp); put_tracing_file(filename); } static void build_kernel_filter(struct uftrace_kernel_writer *kernel, char *filter_str, enum uftrace_pattern_type ptype, struct list_head *filters, struct list_head *notrace) { struct list_head *head; struct strv strv = STRV_INIT; char *pos, *name; int j; if (filter_str == NULL) return; strv_split(&strv, filter_str, ";"); strv_for_each(&strv, name, j) { struct uftrace_pattern patt; pos = has_kernel_filter(name); if (pos == NULL) continue; *pos = '\0'; if (name[0] == '!') { head = notrace; name++; } else head = filters; init_filter_pattern(ptype, &patt, name); if (patt.type == PATT_SIMPLE) add_single_filter(head, name); else add_pattern_filter(head, &patt); free_filter_pattern(&patt); } strv_free(&strv); } struct kevent { struct list_head list; char name[]; }; static int set_tracing_event(struct uftrace_kernel_writer *kernel) { struct kevent *pos, *tmp; list_for_each_entry_safe(pos, tmp, &kernel->events, list) { if (append_tracing_file("set_event", pos->name) < 0) return -1; list_del(&pos->list); free(pos); } return 0; } static void add_single_event(struct list_head *events, char *name) { struct kevent *kevent; kevent = xmalloc(sizeof(*kevent) + strlen(name) + 1); strcpy(kevent->name, name); list_add_tail(&kevent->list, events); } static void add_pattern_event(struct list_head *events, struct uftrace_pattern *patt) { char *filename; FILE *fp; char buf[1024]; filename = get_tracing_file("available_events"); fp = fopen(filename, "r"); if (fp == NULL) pr_err("failed to open 'tracing/available_events' file"); while (fgets(buf, sizeof(buf), fp) != NULL) { /* it's ok to have a trailing '\n' */ if (match_filter_pattern(patt, buf)) add_single_event(events, buf); } fclose(fp); put_tracing_file(filename); } static void build_kernel_event(struct uftrace_kernel_writer *kernel, char *event_str, enum uftrace_pattern_type ptype, struct list_head *events) { struct strv strv = STRV_INIT; char *pos, *name; int j; if (event_str == NULL) return; strv_split(&strv, event_str, ";"); strv_for_each(&strv, name, j) { struct uftrace_pattern patt; pos = has_kernel_filter(name); if (pos == NULL) continue; *pos = '\0'; init_filter_pattern(ptype, &patt, name); if (patt.type == PATT_SIMPLE) add_single_event(events, name); else add_pattern_event(events, &patt); free_filter_pattern(&patt); } strv_free(&strv); } static int reset_tracing_files(void) { if (write_tracing_file("tracing_on", "1") < 0) return -1; if (write_tracing_file("current_tracer", "nop") < 0) return -1; if (write_tracing_file("trace_clock", "local") < 0) return -1; if (write_tracing_file("set_ftrace_pid", " ") < 0) return -1; if (write_tracing_file("set_graph_function", " ") < 0) return -1; /* ignore error on old kernel */ write_tracing_file("set_event_pid", " "); write_tracing_file("set_graph_notrace", " "); write_tracing_file("options/event-fork", "0"); write_tracing_file("options/function-fork", "0"); if (write_tracing_file("set_ftrace_filter", " ") < 0) return -1; if (write_tracing_file("set_ftrace_notrace", " ") < 0) return -1; if (write_tracing_file("max_graph_depth", "0") < 0) return -1; if (write_tracing_file("set_event", " ") < 0) return -1; /* default kernel buffer size: 16384 * 88 / 1024 = 1408 */ if (write_tracing_file("buffer_size_kb", "1408") < 0) return -1; kernel_tracing_enabled = false; return 0; } static int __setup_kernel_tracing(struct uftrace_kernel_writer *kernel) { if (geteuid() != 0) return -EPERM; if (reset_tracing_files() < 0) { pr_dbg("failed to reset tracing files\n"); return -ENOSYS; } pr_dbg("setting up kernel tracing\n"); /* disable tracing */ if (write_tracing_file("tracing_on", "0") < 0) return -ENOSYS; /* reset ftrace buffer */ if (write_tracing_file("trace", "0") < 0) goto out; if (set_tracing_clock(kernel->clock) < 0) goto out; if (set_tracing_pid(kernel->pid) < 0) goto out; if (set_tracing_filter(kernel) < 0) goto out; if (set_tracing_depth(kernel) < 0) goto out; if (set_tracing_event(kernel) < 0) goto out; if (set_tracing_options(kernel) < 0) goto out; if (set_tracing_bufsize(kernel) < 0) goto out; if (write_tracing_file("current_tracer", kernel->tracer) < 0) goto out; kernel_tracing_enabled = true; return 0; out: reset_tracing_files(); return -EINVAL; } static void check_and_add_list(struct uftrace_kernel_writer *kernel, const char *funcs[], size_t funcs_len, struct list_head *list) { unsigned int i; struct kfilter *kfilter; for (i = 0; i < funcs_len; i++) { bool add = true; const char *name = funcs[i]; struct kfilter *pos; /* Don't skip it if user particularly want to see them*/ list_for_each_entry(pos, &kernel->filters, list) { if (!strcmp(pos->name, name)) { add = false; break; } } list_for_each_entry(pos, &kernel->patches, list) { if (!strcmp(pos->name, name)) { add = false; break; } } if (add) { kfilter = xmalloc(sizeof(*kfilter) + strlen(name) + 1); strcpy(kfilter->name, name); list_add_tail(&kfilter->list, list); } } } static void skip_kernel_functions(struct uftrace_kernel_writer *kernel) { const char *skip_funcs[] = { /* * Some (old) kernel and architecture doesn't support VDSO * so there will be many sys_clock_gettime() in the output * due to internal call in libmcount. It'd be better * ignoring them not to confuse users. I think it does NOT * affect to the output when VDSO is enabled. */ "sys_clock_gettime", /* * Currently kernel tracing seems to wake up uftrace writer * threads too often using the irq_work interrupt. This * messes up the trace output so it'd be better hiding them. */ "smp_irq_work_interrupt", /* Disable syscall tracing in the kernel */ "syscall_trace_enter_phase1", "syscall_slow_exit_work", "exit_to_user_mode_prepare", #ifdef __aarch64__ /* * TTBR is for page table setting and it is needed for security * enhancement against spectre/meltdown attacks. * post_ttbr_update_workaround() is better to be hidden not to * confuse general users unnecessarily. */ "post_ttbr_update_workaround", #endif }; const char *skip_patches[] = { /* kernel 4.17 changed syscall entry on x86_64 */ "do_syscall_64", }; check_and_add_list(kernel, skip_funcs, ARRAY_SIZE(skip_funcs), &kernel->notrace); check_and_add_list(kernel, skip_patches, ARRAY_SIZE(skip_patches), &kernel->nopatch); } /** * setup_kernel_tracing - prepare to record kernel ftrace data (binary) * @kernel : kernel ftrace handle * @opts: option related to kernel tracing * * This function sets up all necessary data structures and configure * kernel ftrace subsystem. */ int setup_kernel_tracing(struct uftrace_kernel_writer *kernel, struct uftrace_opts *opts) { int i, n; int ret; INIT_LIST_HEAD(&kernel->filters); INIT_LIST_HEAD(&kernel->notrace); INIT_LIST_HEAD(&kernel->patches); INIT_LIST_HEAD(&kernel->nopatch); INIT_LIST_HEAD(&kernel->events); build_kernel_filter(kernel, opts->filter, opts->patt_type, &kernel->filters, &kernel->notrace); build_kernel_filter(kernel, opts->patch, opts->patt_type, &kernel->patches, &kernel->nopatch); build_kernel_event(kernel, opts->event, opts->patt_type, &kernel->events); if (opts->kernel) kernel->tracer = KERNEL_GRAPH_TRACER; else kernel->tracer = KERNEL_NOP_TRACER; /* mark kernel tracing is enabled (for event tracing) */ opts->kernel = true; if (opts->kernel_skip_out) skip_kernel_functions(kernel); ret = __setup_kernel_tracing(kernel); if (ret < 0) return ret; kernel->nr_cpus = n = sysconf(_SC_NPROCESSORS_CONF); kernel->traces = xcalloc(n, sizeof(*kernel->traces)); kernel->fds = xcalloc(n, sizeof(*kernel->fds)); for (i = 0; i < kernel->nr_cpus; i++) { kernel->traces[i] = -1; kernel->fds[i] = -1; } return 0; } /** * start_kernel_tracing - prepare to record kernel ftrace data (binary) * @kernel : kernel ftrace handle * * This function sets up all necessary data structures and configure * kernel ftrace subsystem. As this function modifies system ftrace * configuration it should be used in pair with stop_kernel_tracing() * function. * * The kernel ftrace data is captured from per-cpu trace_pipe_raw file * as binary form and saved to kernel-cpuXX.dat file in the ftrace * data directory. */ int start_kernel_tracing(struct uftrace_kernel_writer *kernel) { char *trace_file; char buf[PATH_MAX]; int i; int saved_errno; for (i = 0; i < kernel->nr_cpus; i++) { /* TODO: take an account of (currently) offline cpus */ snprintf(buf, sizeof(buf), "per_cpu/cpu%d/trace_pipe_raw", i); trace_file = get_tracing_file(buf); if (!trace_file) { pr_dbg("failed to open %s: %m\n", buf); goto out; } kernel->traces[i] = open(trace_file, O_RDONLY); saved_errno = errno; put_tracing_file(trace_file); if (kernel->traces[i] < 0) { errno = saved_errno; pr_dbg("failed to open %s: %m\n", buf); goto out; } fcntl(kernel->traces[i], F_SETFL, O_NONBLOCK); snprintf(buf, sizeof(buf), "%s/kernel-cpu%d.dat", kernel->output_dir, i); kernel->fds[i] = open(buf, O_WRONLY | O_TRUNC | O_CREAT, 0644); if (kernel->fds[i] < 0) { pr_dbg("failed to open output file: %s: %m\n", buf); goto out; } } if (write_tracing_file("tracing_on", "1") < 0) { pr_dbg("can't enable tracing\n"); goto out; } pr_dbg("kernel tracing started..\n"); return 0; out: for (i = 0; kernel->nr_cpus; i++) { close(kernel->traces[i]); close(kernel->fds[i]); } free(kernel->traces); free(kernel->fds); reset_tracing_files(); return -1; } /** * record_kernel_trace_pipe - read and save kernel ftrace data for specific cpu * @kernel - kernel ftrace handle * @cpu - cpu to read * @sock - socket descriptor (for network transfer) * * This function read trace data for @cpu and save it to file. */ int record_kernel_trace_pipe(struct uftrace_kernel_writer *kernel, int cpu, int sock) { char buf[PATH_MAX]; ssize_t n; if (cpu < 0 || cpu >= kernel->nr_cpus) return 0; retry: n = read(kernel->traces[cpu], buf, sizeof(buf)); if (n < 0) { if (errno == EINTR) goto retry; if (errno == EAGAIN || errno == ENODEV) return 0; return -errno; } if (n == 0) return 0; if (sock > 0) send_trace_kernel_data(sock, cpu, buf, n); else write_all(kernel->fds[cpu], buf, n); return n; } /** * record_kernel_tracing - read and save kernel ftrace data (binary) * @kernel - kernel ftrace handle * * This function read every (online) per-cpu trace data in a * round-robin fashion and save them to files. */ int record_kernel_tracing(struct uftrace_kernel_writer *kernel) { ssize_t bytes = 0; ssize_t n; int i; if (!kernel_tracing_enabled) return -1; for (i = 0; i < kernel->nr_cpus; i++) { n = record_kernel_trace_pipe(kernel, i, -1); if (n < 0) { pr_warn("record kernel data (cpu %d) failed: %m\n", i); return n; } bytes += n; } pr_dbg3("kernel ftrace record wrote %zd bytes\n", bytes); return bytes; } /** * stop_kernel_tracing - stop recording kernel ftrace data * @kernel - kernel ftrace handle * * This function signals kernel to stop generating trace data. */ int stop_kernel_tracing(struct uftrace_kernel_writer *kernel) { if (!kernel_tracing_enabled) return 0; return write_tracing_file("tracing_on", "0"); } /** * finish_kernel_tracing - finish kernel ftrace data * @kernel - kernel ftrace handle * * This function reads out remaining ftrace data and restores kernel * ftrace configuration. */ int finish_kernel_tracing(struct uftrace_kernel_writer *kernel) { int i; pr_dbg("kernel tracing stopped.\n"); while (record_kernel_tracing(kernel) > 0) continue; for (i = 0; i < kernel->nr_cpus; i++) { close(kernel->traces[i]); close(kernel->fds[i]); } free(kernel->traces); free(kernel->fds); if (kernel_tracing_enabled) { save_kernel_files(kernel); save_kernel_symbol(kernel->output_dir); } reset_tracing_files(); return 0; } void list_kernel_events(void) { char *filename; FILE *fp; char buf[BUFSIZ]; filename = get_tracing_file("available_events"); fp = fopen(filename, "r"); put_tracing_file(filename); if (fp == NULL) { pr_dbg("failed to open 'tracing/available_events"); return; } while (fgets(buf, sizeof(buf), fp) != NULL) pr_out("[kernel event] %s", buf); fclose(fp); } static int save_kernel_file(FILE *fp, const char *name) { ssize_t len; char buf[PATH_MAX]; len = read_tracing_file(name, buf, sizeof(buf)); if (len < 0) return -1; fprintf(fp, "TRACEFS: %s: %zd\n", name, len); fwrite(buf, len, 1, fp); return 0; } static int save_event_files(struct uftrace_kernel_writer *kernel, FILE *fp) { int ret = -1; char buf[PATH_MAX]; char *filename = NULL; DIR *subsys = NULL; DIR *event = NULL; struct dirent *sys, *name; if (read_tracing_file("events/enable", buf, sizeof(buf))) goto out; /* no events enabled: exit */ if (buf[0] == '0') { ret = 0; goto out; } filename = get_tracing_file("events"); if (filename == NULL) goto out; subsys = opendir(filename); if (subsys == NULL) goto out; while ((sys = readdir(subsys)) != NULL) { if (sys->d_name[0] == '.' || sys->d_type != DT_DIR) continue; /* ftrace events are special - skip it */ if (!strcmp(sys->d_name, "ftrace")) continue; snprintf(buf, sizeof(buf), "events/%s/enable", sys->d_name); if (read_tracing_file(buf, buf, sizeof(buf)) < 0) goto out; /* this subsystem has no events enabled */ if (buf[0] == '0') continue; snprintf(buf, sizeof(buf), "events/%s", sys->d_name); event = opendir(buf); if (event == NULL) goto out; while ((name = readdir(event)) != NULL) { if (name->d_name[0] == '.' || name->d_type != DT_DIR) continue; snprintf(buf, sizeof(buf), "events/%s/%s/enable", sys->d_name, name->d_name); if (read_tracing_file(buf, buf, sizeof(buf)) < 0) goto out; /* this event is not enabled */ if (buf[0] == '0') continue; snprintf(buf, sizeof(buf), "events/%s/%s/format", sys->d_name, name->d_name); if (save_kernel_file(fp, buf) < 0) goto out; } closedir(event); event = NULL; } ret = 0; out: if (event) closedir(event); if (subsys) closedir(subsys); if (filename) put_tracing_file(filename); return ret; } static int save_kernel_files(struct uftrace_kernel_writer *kernel) { char *path = NULL; FILE *fp; int ret = -1; xasprintf(&path, "%s/kernel_header", kernel->output_dir); fp = fopen(path, "w"); if (fp == NULL) pr_err("cannot write kernel header"); fprintf(fp, "PAGE_SIZE: %d\n", getpagesize()); fprintf(fp, "LONG_SIZE: %zd\n", sizeof(long)); fprintf(fp, "ENDIAN: %s\n", get_endian_str()); if (save_kernel_file(fp, "events/header_page") < 0) goto out; if (save_kernel_file(fp, "events/ftrace/funcgraph_entry/format") < 0) goto out; if (save_kernel_file(fp, "events/ftrace/funcgraph_exit/format") < 0) goto out; if (save_event_files(kernel, fp) < 0) goto out; ret = 0; out: fclose(fp); free(path); return ret; } /* provided for backward compatibility */ static int load_current_kernel(struct uftrace_kernel_reader *kernel) { int fd; size_t len; char buf[PATH_MAX]; bool is_big_endian = !strcmp(get_endian_str(), "BE"); struct uftrace_kernel_parser *kp = &kernel->parser; kparser_set_info(kp, sizeof(long), getpagesize(), is_big_endian); fd = open_tracing_file("events/header_page", O_RDONLY); if (fd < 0) return -1; len = read(fd, buf, sizeof(buf)); kparser_read_header(kp, buf, len); close(fd); fd = open_tracing_file("events/ftrace/funcgraph_entry/format", O_RDONLY); if (fd < 0) return -1; len = read(fd, buf, sizeof(buf)); kparser_read_event(kp, "ftrace", buf, len); close(fd); fd = open_tracing_file("events/ftrace/funcgraph_exit/format", O_RDONLY); if (fd < 0) return -1; len = read(fd, buf, sizeof(buf)); kparser_read_event(kp, "ftrace", buf, len); close(fd); return 0; } static int load_kernel_files(struct uftrace_kernel_reader *kernel) { char *path = NULL; FILE *fp; char buf[PATH_MAX]; struct uftrace_kernel_parser *kp = &kernel->parser; int page_size = 0, long_size = 0, file_endian = -1; int ret = 0; xasprintf(&path, "%s/kernel_header", kernel->dirname); fp = fopen(path, "r"); free(path); if (fp == NULL) /* old data doesn't have the kernel header */ return load_current_kernel(kernel); while (fgets(buf, sizeof(buf), fp) != NULL) { char name[128]; size_t len = 0; if (strncmp(buf, "TRACEFS:", 8) != 0) { char val[32]; sscanf(buf, "%[^:]: %s\n", name, val); if (!strcmp(name, "PAGE_SIZE")) page_size = strtol(val, NULL, 0); else if (!strcmp(name, "LONG_SIZE")) long_size = strtol(val, NULL, 0); else if (!strcmp(name, "ENDIAN")) file_endian = (strcmp(val, "BE") == 0); if (page_size && long_size && file_endian >= 0) kparser_set_info(kp, page_size, long_size, file_endian); continue; } if (sscanf(buf, "TRACEFS: %[^:]: %zd\n", name, &len) != 2) { ret = -1; break; } if (fread(buf, 1, len, fp) != len) { ret = -1; break; } if (!strcmp(name, "events/header_page")) { kparser_read_header(kp, buf, len); } else if (!strncmp(name, "events/ftrace/", 14)) { ret = kparser_read_event(kp, "ftrace", buf, len); if (ret != 0) { kparser_strerror(kp, ret, buf, len); pr_err_ns("%s: %s\n", name, buf); } } else if (!strncmp(name, "events/", 7) && !strncmp(name + strlen(name) - 7, "/format", 7)) { /* extract subsystem and event names */ char *pos1 = strchr(name + 8, '/'); char *pos2 = strrchr(name, '/'); if (pos1 == NULL || pos2 == NULL) continue; *pos1 = '\0'; /* add event so that we can skip the record */ ret = kparser_read_event(kp, name + 7, buf, len); if (ret != 0) { *pos1 = '/'; kparser_strerror(kp, ret, buf, len); pr_err_ns("%s: %s\n", name, buf); } *pos2 = '\0'; kparser_register_handler(kp, name + 7, pos1 + 1); } else { pr_dbg("unknown data: %s\n", name); ret = -1; break; } } fclose(fp); return ret; } static int scandir_filter(const struct dirent *d) { return !strncmp(d->d_name, "kernel-cpu", 10); } static int scandir_sort(const struct dirent **a, const struct dirent **b) { return strtol((*a)->d_name + sizeof("kernel-cpu") - 1, NULL, 0) - strtol((*b)->d_name + sizeof("kernel-cpu") - 1, NULL, 0); } /** * setup_kernel_data - prepare to read kernel ftrace data from files * @kernel - kernel ftrace handle * * This function initializes necessary data structures for reading * kernel ftrace data files. It should be called in pair with * finish_kernel_data(). */ int setup_kernel_data(struct uftrace_kernel_reader *kernel) { int i; char buf[PATH_MAX]; struct dirent **list; if (kparser_init(&kernel->parser) < 0) return -1; kernel->nr_cpus = scandir(kernel->dirname, &list, scandir_filter, scandir_sort); if (kernel->nr_cpus <= 0) { pr_out("cannot find kernel trace data\n"); goto out; } if (load_kernel_files(kernel) < 0) { pr_out("cannot read kernel header: %m\n"); goto out; } pr_dbg("found kernel ftrace data for %d cpus\n", kernel->nr_cpus); kernel->rstacks = xcalloc(kernel->nr_cpus, sizeof(*kernel->rstacks)); kernel->rstack_list = xcalloc(kernel->nr_cpus, sizeof(*kernel->rstack_list)); kernel->rstack_valid = xcalloc(kernel->nr_cpus, sizeof(*kernel->rstack_valid)); kernel->rstack_done = xcalloc(kernel->nr_cpus, sizeof(*kernel->rstack_done)); kernel->tids = xcalloc(kernel->nr_cpus, sizeof(*kernel->tids)); kparser_prepare_buffers(&kernel->parser, kernel->nr_cpus); for (i = 0; i < kernel->nr_cpus; i++) { snprintf(buf, sizeof(buf), "%s/%s", kernel->dirname, list[i]->d_name); free(list[i]); if (kparser_prepare_cpu(&kernel->parser, buf, i) < 0) break; setup_rstack_list(&kernel->rstack_list[i]); } free(list); if (i != kernel->nr_cpus) { pr_dbg("failed to access to kernel trace data: %s: %m\n", buf); goto out; } kparser_register_handler(&kernel->parser, "ftrace", "funcgraph_entry"); kparser_register_handler(&kernel->parser, "ftrace", "funcgraph_exit"); return 0; out: finish_kernel_data(kernel); return -1; } /** * finish_kernel_data - tear down data structures for kernel ftrace * @kernel - kernel ftrace handle * * This function destroys all data structures created by * setup_kernel_data(). */ int finish_kernel_data(struct uftrace_kernel_reader *kernel) { int i; if (kernel == NULL) return 0; for (i = 0; i < kernel->nr_cpus; i++) { kparser_release_cpu(&kernel->parser, i); reset_rstack_list(&kernel->rstack_list[i]); } free(kernel->rstacks); free(kernel->rstack_list); free(kernel->rstack_valid); free(kernel->rstack_done); free(kernel->tids); kparser_release_buffers(&kernel->parser, kernel->nr_cpus); kparser_exit(&kernel->parser); return 0; } struct uftrace_kfunc { struct rb_node node; uint64_t addr; }; static void add_kfunc_addr(struct rb_root *root, uint64_t addr) { struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; struct uftrace_kfunc *iter, *kfunc; while (*p) { parent = *p; iter = rb_entry(parent, struct uftrace_kfunc, node); if (iter->addr == addr) return; if (iter->addr > addr) p = &parent->rb_left; else p = &parent->rb_right; } kfunc = xmalloc(sizeof(*kfunc)); kfunc->addr = addr; rb_link_node(&kfunc->node, parent, p); rb_insert_color(&kfunc->node, root); } static bool find_kfunc_addr(struct rb_root *root, uint64_t addr) { struct rb_node *node = root->rb_node; struct uftrace_kfunc *iter; while (node) { iter = rb_entry(node, struct uftrace_kfunc, node); if (iter->addr == addr) return true; if (iter->addr > addr) node = node->rb_left; else node = node->rb_right; } return false; } /** * read_kernel_cpu_data - read next kernel tracing data of specific cpu * @kernel - kernel ftrace handle * @cpu - cpu number * * This function reads tracing data from kbuffer and saves it to the * @kernel->rstacks[@cpu]. It returns 0 if succeeded, 1 if there's * no more data or -1 on error. */ int read_kernel_cpu_data(struct uftrace_kernel_reader *kernel, int cpu) { int ret; ret = kparser_read_data(&kernel->parser, kernel->handle, cpu, &kernel->tids[cpu]); if (ret) return ret; memcpy(&kernel->rstacks[cpu], &kernel->parser.rec, sizeof(kernel->parser.rec)); kernel->rstack_valid[cpu] = true; return 0; } static int read_kernel_cpu(struct uftrace_data *handle, int cpu) { struct uftrace_kernel_reader *kernel = handle->kernel; struct uftrace_rstack_list *rstack_list = &kernel->rstack_list[cpu]; struct uftrace_record *curr; int tid, prev_tid = -1; if (rstack_list->count) goto out; /* * read task (kernel) stack until it found an entry that exceeds * the given time filter (-t option). */ while (read_kernel_cpu_data(kernel, cpu) == 0) { struct uftrace_session *sess = handle->sessions.first; struct uftrace_task_reader *task; struct uftrace_trigger tr = {}; uint64_t real_addr; uint64_t time_filter = handle->time_filter; uint64_t size_filter = handle->size_filter; curr = &kernel->rstacks[cpu]; /* prevent ustack from invalid access */ kernel->rstack_valid[cpu] = false; tid = kernel->tids[cpu]; task = get_task_handle(handle, tid); if (task == NULL) continue; if (!check_time_range(&handle->time_range, curr->time)) continue; if (prev_tid == -1) prev_tid = tid; if (task->filter.stack) { time_filter = task->filter.stack->threshold; size_filter = task->filter.stack->size; } /* filter match needs full (64-bit) address */ real_addr = get_kernel_address(&sess->sym_info, curr->addr); /* * it might set TRACE trigger, which shows * function even if it's less than the time filter. */ uftrace_match_filter(real_addr, &sess->filters, &tr); if (curr->type == UFTRACE_ENTRY) { if (size_filter) { struct uftrace_symbol *sym; sym = find_symtabs(&sess->sym_info, curr->addr); if (sym && sym->size >= size_filter) add_to_rstack_list(rstack_list, curr, &task->args); } else { /* it needs to wait until matching exit found */ add_to_rstack_list(rstack_list, curr, NULL); } add_kfunc_addr(&kfunc_tree, real_addr); if (tr.flags & (TRIGGER_FL_TIME_FILTER | TRIGGER_FL_SIZE_FILTER)) { struct uftrace_task_filter_stack *tfs; tfs = xmalloc(sizeof(*tfs)); tfs->next = task->filter.stack; tfs->depth = curr->depth; tfs->context = FSTACK_CTX_KERNEL; tfs->threshold = (tr.flags & TRIGGER_FL_TIME_FILTER) ? tr.time : time_filter; tfs->size = (tr.flags & TRIGGER_FL_SIZE_FILTER) ? tr.size : size_filter; task->filter.stack = tfs; } /* XXX: handle scheduled task properly */ if (tid != prev_tid) break; } else if (curr->type == UFTRACE_EXIT) { struct uftrace_rstack_list_node *last; uint64_t delta; int count; bool filtered = false; if (!find_kfunc_addr(&kfunc_tree, real_addr)) continue; if (task->filter.stack) { struct uftrace_task_filter_stack *tfs; tfs = task->filter.stack; if (tfs->depth == curr->depth && tfs->context == FSTACK_CTX_KERNEL) { /* discard stale filter */ task->filter.stack = tfs->next; free(tfs); } } if (size_filter) { struct uftrace_symbol *sym; sym = find_symtabs(&sess->sym_info, curr->addr); if (sym && sym->size < size_filter) continue; } if (rstack_list->count == 0 || tr.flags & TRIGGER_FL_TRACE) { /* * it's already exceeded time filter or * it might set TRACE trigger, just return. */ add_to_rstack_list(rstack_list, curr, NULL); break; } last = list_last_entry(&rstack_list->read, typeof(*last), list); count = 1; /* skip EVENT records, if any*/ while (last->rstack.type == UFTRACE_EVENT) { last = list_prev_entry(last, list); count++; } delta = curr->time - last->rstack.time; if (delta < time_filter) filtered = true; if (handle->caller_filter) filtered |= !(tr.flags & TRIGGER_FL_CALLER); if (filtered) { /* also delete matching entry (at the last) */ while (count--) delete_last_rstack_list(rstack_list); /* XXX: handle scheduled task properly */ if (tid != prev_tid) break; } else { /* found! process all existing rstacks in the list */ add_to_rstack_list(rstack_list, curr, NULL); break; } } else if (curr->type == UFTRACE_EVENT) { struct uftrace_fstack_args arg = { .data = kparser_trace_buffer(&kernel->parser), .len = kparser_trace_buflen(&kernel->parser) + 1, }; add_to_rstack_list(rstack_list, curr, &arg); /* XXX: handle scheduled task properly */ if (tid != prev_tid) break; } else { /* TODO: handle LOST properly */ add_to_rstack_list(rstack_list, curr, NULL); break; } prev_tid = tid; } if (rstack_list->count == 0) { if (!kernel->rstack_done[cpu]) { pr_dbg("XXX: still has unknown tracepoint?\n"); kernel->rstack_done[cpu] = true; } return -1; } out: kernel->rstack_valid[cpu] = true; curr = get_first_rstack_list(rstack_list); memcpy(&kernel->rstacks[cpu], curr, sizeof(*curr)); return 0; } /** * read_kernel_event - read current kernel event of specific cpu * @handle - uftrace file handle * @cpu - cpu number * @psize - pointer to size * * This function returns current tracepoint event data in trace_seq. * The size of the event data will be saved in @size. It returns a * pointer to event data if succeeded, NULL if current record is not a * tracepoint. */ void *read_kernel_event(struct uftrace_kernel_reader *kernel, int cpu, int *psize) { struct uftrace_record *rstack = &kernel->rstacks[cpu]; if (!rstack->more) return NULL; *psize = kparser_trace_buflen(&kernel->parser); return kparser_trace_buffer(&kernel->parser); } /** * read_kernel_stack - peek next kernel ftrace data * @handle - ftrace file handle * @taskp - pointer to the oldest task * * This function reads all kernel function trace records of each cpu, * compares the timestamp, and find the oldest one. After this * function @task will point a task which has the oldest record, and * it can be accessed by @task->kstack. The oldest record will *NOT* * be consumed, that means another call to this function will give you * same (*@taskp)->kstack. * * This function returns the cpu number (> 0) if it reads a rstack, * -1 if it's done. */ int read_kernel_stack(struct uftrace_data *handle, struct uftrace_task_reader **taskp) { int i; int first_cpu = -1; int first_tid = -1; uint64_t first_timestamp = 0; struct uftrace_kernel_reader *kernel = handle->kernel; struct uftrace_record *first_rstack; retry: first_rstack = NULL; for (i = 0; i < kernel->nr_cpus; i++) { uint64_t timestamp; if (kernel->rstack_done[i] && kernel->rstack_list[i].count == 0) continue; if (!kernel->rstack_valid[i]) { read_kernel_cpu(handle, i); if (!kernel->rstack_valid[i]) continue; } timestamp = kernel->rstacks[i].time; if (!first_rstack || first_timestamp > timestamp) { first_rstack = &kernel->rstacks[i]; first_timestamp = timestamp; first_tid = kernel->tids[i]; first_cpu = i; } } if (first_rstack == NULL) return -1; *taskp = get_task_handle(handle, first_tid); if (*taskp == NULL || (*taskp)->fp == NULL) { /* force re-read on that cpu */ kernel->rstack_valid[first_cpu] = false; if (first_rstack->more) { struct uftrace_rstack_list_node *node; node = list_first_entry(&kernel->rstack_list[first_cpu].read, typeof(*node), list); free(node->args.data); node->args.data = NULL; } consume_first_rstack_list(&kernel->rstack_list[first_cpu]); goto retry; } memcpy(&(*taskp)->kstack, first_rstack, sizeof(*first_rstack)); kernel->last_read_cpu = first_cpu; return first_cpu; } struct uftrace_record *get_kernel_record(struct uftrace_kernel_reader *kernel, struct uftrace_task_reader *task, int cpu) { static struct uftrace_record lost_record; int missed_events = kparser_missed_events(&kernel->parser, cpu); if (!missed_events) return &task->kstack; /* convert to ftrace_rstack */ lost_record.time = 0; lost_record.type = UFTRACE_LOST; lost_record.addr = missed_events; lost_record.depth = task->kstack.depth; lost_record.magic = RECORD_MAGIC; lost_record.more = 0; /* * NOTE: do not consume the kstack since we didn't * read the first record yet. Next read_kernel_stack() * will return the first record. */ return &lost_record; } #ifdef HAVE_LIBTRACEEVENT #ifdef UNIT_TEST #define NUM_CPU 2 #define NUM_TASK 2 #define NUM_RECORD 4 #define NUM_EVENT 2 /* event id */ #define FUNCGRAPH_ENTRY 11 #define FUNCGRAPH_EXIT 10 #define TEST_EXAMPLE 100 static struct uftrace_data test_handle; static struct uftrace_session test_sess; static void kernel_test_finish_file(void); static void kernel_test_finish_handle(void); /* NOTE: assume 64-bit little-endian systems */ static const char test_kernel_header[] = "PAGE_SIZE: 4096\n" "LONG_SIZE: 8\n" "ENDIAN: LE\n" "TRACEFS: events/header_page: 205\n" "\tfield: u64 timestamp;\toffset:0;\tsize:8;\tsigned:0;\n" "\tfield: local_t commit;\toffset:8;\tsize:8;\tsigned:1;\n" "\tfield: int overwrite;\toffset:8;\tsize:1;\tsigned:1;\n" "\tfield: char data;\toffset:16;\tsize:4080;\tsigned:1;\n" "TRACEFS: events/ftrace/funcgraph_entry/format: 438\n" "name: funcgraph_entry\n" "ID: 11\n" "format:\n" "\tfield:unsigned short common_type;\toffset:0;\tsize:2;\tsigned:0;\n" "\tfield:unsigned char common_flags;\toffset:2;\tsize:1;\tsigned:0;\n" "\tfield:unsigned char common_preempt_count;\toffset:3;\tsize:1;\tsigned:0;\n" "\tfield:int common_pid;\toffset:4;\tsize:4;\tsigned:1;\n" "\n" "\tfield:unsigned long func;\toffset:8;\tsize:8;\tsigned:0;\n" "\tfield:int depth;\toffset:16;\tsize:4;\tsigned:1;\n" "\n" "print fmt: \"--> %lx (%d)\", REC->func, REC->depth\n" "TRACEFS: events/ftrace/funcgraph_exit/format: 700\n" "name: funcgraph_exit\n" "ID: 10\n" "format:\n" "\tfield:unsigned short common_type;\toffset:0;\tsize:2;\tsigned:0;\n" "\tfield:unsigned char common_flags;\toffset:2;\tsize:1;\tsigned:0;\n" "\tfield:unsigned char common_preempt_count;\toffset:3;\tsize:1;\tsigned:0;\n" "\tfield:int common_pid;\toffset:4;\tsize:4;\tsigned:1;\n" "\n" "\tfield:unsigned long func;\toffset:8;\tsize:8;\tsigned:0;\n" "\tfield:unsigned long long calltime;\toffset:24;\tsize:8;\tsigned:0;\n" "\tfield:unsigned long long rettime;\toffset:32;\tsize:8;\tsigned:0;\n" "\tfield:unsigned long overrun; offset:16;\tsize:8;\tsigned:0;\n" "\tfield:int depth;\toffset:40;\tsize:4;\tsigned:1;\n" "\n" "print fmt: \"<-- %lx (%d) (start: %llx end: %llx) over: %d\", " "REC->func, REC->depth, REC->calltime, REC->rettime, REC->depth\n"; static const char test_kernel_event[] = "TRACEFS: events/test/example/format: 419\n" "name: example\n" "ID: 100\n" "format:\n" "\tfield:unsigned short common_type;\toffset:0;\tsize:2;\tsigned:0;\n" "\tfield:unsigned char common_flags;\toffset:2;\tsize:1;\tsigned:0;\n" "\tfield:unsigned char common_preempt_count;\toffset:3;\tsize:1;\tsigned:0;\n" "\tfield:int common_pid;\toffset:4;\tsize:4;\tsigned:1;\n" "\n" "\tfield:int foo;\toffset:8;\tsize:4;\tsigned:0;\n" "\tfield:int bar;\toffset:12;\tsize:4;\tsigned:1;\n" "\n" "print fmt: \"foo=%d, bar=0x%x\", REC->foo, REC->bar\n"; struct header_page { uint64_t timestamp; uint64_t commit; }; struct type_len_ts { uint32_t type_len : 5; uint32_t ts : 27; }; struct funcgraph_entry { unsigned short common_type; unsigned char common_flags; unsigned char common_preempt_count; int common_pid; uint64_t func; int depth; }; struct funcgraph_exit { unsigned short common_type; unsigned char common_flags; unsigned char common_preempt_count; int common_pid; uint64_t func; uint64_t calltime; uint64_t rettime; uint64_t overrun; int depth; }; struct test_example { unsigned short common_type; unsigned char common_flags; unsigned char common_preempt_count; int common_pid; int foo; int bar; }; static int test_tids[NUM_TASK] = { 1234, 5678 }; static struct header_page header = { 0, 4096 }; static struct type_len_ts test_len_ts[NUM_CPU][NUM_RECORD] = { { { sizeof(struct funcgraph_entry) / 4, 100 }, { sizeof(struct funcgraph_entry) / 4, 100 }, { sizeof(struct funcgraph_exit) / 4, 100 }, { sizeof(struct funcgraph_exit) / 4, 100 }, }, { { sizeof(struct funcgraph_entry) / 4, 150 }, { sizeof(struct funcgraph_exit) / 4, 100 }, { sizeof(struct funcgraph_entry) / 4, 100 }, { sizeof(struct funcgraph_exit) / 4, 100 }, } }; /* NOTE: it's actually a mix of funcgraph_entry and funcgraph_exit */ static struct funcgraph_exit test_record[NUM_CPU][NUM_RECORD] = { { /* NOTE: entry->depth might not set on big-endian? */ { FUNCGRAPH_ENTRY, 0, 0, 1234, 0xffff1000, 0 }, { FUNCGRAPH_ENTRY, 0, 0, 1234, 0xffff2000, 1 }, { FUNCGRAPH_EXIT, 0, 0, 1234, 0xffff2000, 200, 300, 0, 1 }, { FUNCGRAPH_EXIT, 0, 0, 1234, 0xffff1000, 100, 400, 0, 0 }, }, { { FUNCGRAPH_ENTRY, 0, 0, 1234, 0xffff3000, 0 }, { FUNCGRAPH_EXIT, 0, 0, 1234, 0xffff3000, 150, 250, 0, 0 }, { FUNCGRAPH_ENTRY, 0, 0, 5678, 0xffff4000, 1 }, { FUNCGRAPH_EXIT, 0, 0, 5678, 0xffff4000, 350, 450, 0, 1 }, } }; static struct type_len_ts test_event_len_ts[NUM_CPU][NUM_EVENT] = { { { sizeof(struct test_example) / 4, 1000 }, { sizeof(struct test_example) / 4, 1000 }, }, { { sizeof(struct test_example) / 4, 1500 }, { sizeof(struct test_example) / 4, 1000 }, } }; static struct test_example test_event[NUM_CPU][NUM_EVENT] = { { { TEST_EXAMPLE, 0, 0, 1234, 1024, 1024 }, { TEST_EXAMPLE, 0, 0, 1234, 2048, 2048 }, }, { { TEST_EXAMPLE, 0, 0, 5678, 100, 256 }, { TEST_EXAMPLE, 0, 0, 5678, 200, 512 }, } }; /* NOTE: we used struct funcgraph_exit even for UFTRACE_ENTRY */ static int record_size(struct funcgraph_exit *rec) { return rec->common_type == FUNCGRAPH_ENTRY ? sizeof(struct funcgraph_entry) : sizeof(struct funcgraph_exit); } static int record_type(struct funcgraph_exit *rec) { return rec->common_type == FUNCGRAPH_ENTRY ? UFTRACE_ENTRY : UFTRACE_EXIT; } static int record_depth(struct funcgraph_exit *rec) { return rec->common_type == FUNCGRAPH_ENTRY ? rec->calltime : rec->depth; } /* fwrite with checking return value */ #define cwrite(bf) \ if (fwrite_all(&bf, sizeof(bf), fp) < 0) \ pr_dbg("write failed: %s\n", #bf) #define cwrite2(bf, sz) \ if (fwrite_all(bf, sz, fp) < 0) \ pr_dbg("write failed: %s\n", #bf) static int kernel_test_setup_file(struct uftrace_kernel_reader *kernel, bool event) { int cpu, i; FILE *fp; char *filename; kernel->dirname = "kernel.dir"; kernel->nr_cpus = NUM_CPU; if (mkdir(kernel->dirname, 0755) < 0) { if (errno != EEXIST) { pr_dbg("cannot create temp dir: %m\n"); return -1; } } if (asprintf(&filename, "%s/kernel_header", kernel->dirname) < 0) { pr_dbg("cannot alloc filename: %s/kernel_header", kernel->dirname); return -1; } fp = fopen(filename, "w"); if (fp == NULL) { pr_dbg("file open failed: %m\n"); free(filename); return -1; } cwrite2(test_kernel_header, strlen(test_kernel_header)); if (event) cwrite2(test_kernel_event, strlen(test_kernel_event)); free(filename); fclose(fp); for (cpu = 0; cpu < kernel->nr_cpus; cpu++) { if (asprintf(&filename, "%s/kernel-cpu%d.dat", kernel->dirname, cpu) < 0) { pr_dbg("cannot alloc filename: %s/kernel-cpu%d.dat", kernel->dirname, cpu); return -1; } fp = fopen(filename, "w"); if (fp == NULL) { pr_dbg("file open failed: %m\n"); free(filename); return -1; } cwrite(header); if (event) { for (i = 0; i < NUM_EVENT; i++) { cwrite(test_event_len_ts[cpu][i]); cwrite(test_event[cpu][i]); } } else { for (i = 0; i < NUM_RECORD; i++) { cwrite(test_len_ts[cpu][i]); cwrite2(&test_record[cpu][i], record_size(&test_record[cpu][i])); } } /* pad to page size */ fallocate(fileno(fp), 0, 0, 4096); free(filename); fclose(fp); } kernel->handle = &test_handle; test_handle.kernel = kernel; atexit(kernel_test_finish_file); return setup_kernel_data(kernel); } #undef cwrite #undef cwrite2 static int kernel_test_setup_handle(struct uftrace_kernel_reader *kernel, struct uftrace_data *handle) { int i; handle->nr_tasks = NUM_TASK; handle->tasks = xcalloc(sizeof(*handle->tasks), NUM_TASK); handle->time_range.start = handle->time_range.stop = 0; handle->time_filter = 0; for (i = 0; i < NUM_TASK; i++) { handle->tasks[i].tid = test_tids[i]; handle->tasks[i].fp = (void *)1; /* prevent retry */ } test_sess.sym_info.kernel_base = 0xffff0000UL; handle->sessions.first = &test_sess; atexit(kernel_test_finish_handle); return 0; } static void kernel_test_finish_file(void) { int cpu; char *filename; struct uftrace_kernel_reader *kernel = test_handle.kernel; if (kernel == NULL) return; finish_kernel_data(kernel); for (cpu = 0; cpu < kernel->nr_cpus; cpu++) { if (asprintf(&filename, "%s/kernel-cpu%d.dat", kernel->dirname, cpu) < 0) return; remove(filename); free(filename); } if (asprintf(&filename, "%s/kernel_header", kernel->dirname) < 0) return; remove(filename); free(filename); remove(kernel->dirname); kernel->dirname = NULL; free(kernel); test_handle.kernel = NULL; } static void kernel_test_finish_handle(void) { struct uftrace_data *handle = &test_handle; free(handle->tasks); handle->tasks = NULL; } TEST_CASE(kernel_read) { int cpu, i; int timestamp[NUM_CPU] = {}; struct uftrace_data *handle = &test_handle; struct uftrace_kernel_reader *kernel = xzalloc(sizeof(*kernel)); struct uftrace_task_reader *task; TEST_EQ(kernel_test_setup_file(kernel, false), 0); TEST_EQ(kernel_test_setup_handle(kernel, handle), 0); i = 0; while ((cpu = read_kernel_stack(handle, &task)) != -1) { struct funcgraph_exit *rec = &test_record[cpu][i / 2]; struct uftrace_record *rstack = &task->kstack; timestamp[cpu] += test_len_ts[cpu][i / 2].ts; pr_dbg("[%d] read kernel record: type=%d, depth=%d, addr=%" PRIx64 "\n", i, rstack->type, rstack->depth, (uint64_t)rstack->addr); TEST_EQ((int)rstack->type, record_type(rec)); TEST_EQ((int)rstack->time, timestamp[cpu]); TEST_EQ((uint64_t)rstack->addr, rec->func); TEST_EQ((int)rstack->depth, record_depth(rec)); TEST_EQ(kernel->tids[cpu], rec->common_pid); consume_first_rstack_list(&kernel->rstack_list[cpu]); kernel->rstack_valid[cpu] = false; i++; } TEST_EQ(i, NUM_CPU * NUM_RECORD); return TEST_OK; } TEST_CASE(kernel_cpu_read) { int cpu, i; int timestamp[NUM_CPU] = {}; struct uftrace_kernel_reader *kernel = xzalloc(sizeof(*kernel)); TEST_EQ(kernel_test_setup_file(kernel, false), 0); for (cpu = 0; cpu < NUM_CPU; cpu++) { for (i = 0; i < NUM_RECORD; i++) { struct funcgraph_exit *rec = &test_record[cpu][i]; struct uftrace_record *rstack = &kernel->parser.rec; TEST_EQ(read_kernel_cpu_data(kernel, cpu), 0); timestamp[cpu] += test_len_ts[cpu][i].ts; pr_dbg("[%d] read cpu record: type=%d, depth=%d, addr=%" PRIx64 "\n", i, rstack->type, rstack->depth, (uint64_t)rstack->addr); TEST_EQ((int)rstack->type, record_type(rec)); TEST_EQ((int)rstack->time, timestamp[cpu]); TEST_EQ((uint64_t)rstack->addr, rec->func); TEST_EQ((int)rstack->depth, record_depth(rec)); TEST_EQ(kernel->tids[cpu], rec->common_pid); } } return TEST_OK; } TEST_CASE(kernel_event_read) { int cpu, i; int timestamp[NUM_CPU] = {}; struct uftrace_kernel_reader *kernel = xzalloc(sizeof(*kernel)); pr_dbg("checking custom event format parsing\n"); TEST_EQ(kernel_test_setup_file(kernel, true), 0); for (cpu = 0; cpu < NUM_CPU; cpu++) { for (i = 0; i < NUM_EVENT; i++) { struct test_example *rec = &test_event[cpu][i]; struct uftrace_record *rstack = &kernel->parser.rec; char *data; int size; int foo, bar; TEST_EQ(read_kernel_cpu_data(kernel, cpu), 0); TEST_NE(data = read_kernel_event(kernel, cpu, &size), NULL); timestamp[cpu] += test_event_len_ts[cpu][i].ts; pr_dbg("[%d] read event record: type=%d, data=%s\n", i, rstack->type, data); TEST_EQ((int)rstack->type, UFTRACE_EVENT); TEST_EQ((int)rstack->time, timestamp[cpu]); TEST_EQ((int)rstack->addr, TEST_EXAMPLE); TEST_EQ((int)rstack->depth, 0); TEST_EQ(kernel->tids[cpu], rec->common_pid); TEST_EQ(sscanf(data, "foo=%d, bar=%x", &foo, &bar), 2); TEST_EQ(foo, test_event[cpu][i].foo); TEST_EQ(bar, test_event[cpu][i].bar); } } return TEST_OK; } #endif /* UNIT_TEST */ #endif /* HAVE_LIBTRACEEVENT */ uftrace-0.15.2/utils/kernel.h000066400000000000000000000042201455365734300160360ustar00rootroot00000000000000#ifndef UFTRACE_KERNEL_H #define UFTRACE_KERNEL_H #include "uftrace.h" #include "utils/kernel-parser.h" #include "utils/list.h" #include "utils/utils.h" #define KERNEL_NOP_TRACER "nop" #define KERNEL_GRAPH_TRACER "function_graph" struct uftrace_kernel_writer { int pid; int nr_cpus; int depth; unsigned long bufsize; char *tracer; int *traces; int *fds; char *output_dir; char *clock; struct list_head filters; struct list_head notrace; struct list_head patches; struct list_head nopatch; struct list_head events; }; struct uftrace_kernel_reader { int nr_cpus; int last_read_cpu; bool skip_out; char *dirname; struct uftrace_data *handle; struct uftrace_kernel_parser parser; struct uftrace_record *rstacks; struct uftrace_rstack_list *rstack_list; bool *rstack_valid; bool *rstack_done; int *tids; }; /* these functions will be used at record time */ int setup_kernel_tracing(struct uftrace_kernel_writer *kernel, struct uftrace_opts *opts); int start_kernel_tracing(struct uftrace_kernel_writer *kernel); int record_kernel_tracing(struct uftrace_kernel_writer *kernel); int record_kernel_trace_pipe(struct uftrace_kernel_writer *kernel, int cpu, int sock); int stop_kernel_tracing(struct uftrace_kernel_writer *kernel); int finish_kernel_tracing(struct uftrace_kernel_writer *kernel); void list_kernel_events(void); /* these functions will be used at replay time */ int setup_kernel_data(struct uftrace_kernel_reader *kernel); int read_kernel_stack(struct uftrace_data *handle, struct uftrace_task_reader **taskp); int read_kernel_cpu_data(struct uftrace_kernel_reader *kernel, int cpu); void *read_kernel_event(struct uftrace_kernel_reader *kernel, int cpu, int *psize); struct uftrace_record *get_kernel_record(struct uftrace_kernel_reader *kernel, struct uftrace_task_reader *task, int cpu); int finish_kernel_data(struct uftrace_kernel_reader *kernel); static inline bool has_kernel_data(struct uftrace_kernel_reader *kernel) { return kernel && kparser_ready(&kernel->parser); } static inline bool has_kernel_event(char *events) { return events && has_kernel_filter(events); } bool check_kernel_pid_filter(void); #endif /* UFTRACE_KERNEL_H */ uftrace-0.15.2/utils/list.h000066400000000000000000000445301455365734300155410ustar00rootroot00000000000000/* copied from the Linux kernel source (GPL v2) */ #ifndef _LINUX_LIST_H #define _LINUX_LIST_H /* * Simple doubly linked list implementation. * * Some of the internal functions ("__xxx") are useful when * manipulating whole lists rather than single entries, as * sometimes we already know the next/prev entries and we can * generate better code by using them directly rather than * using the generic single-entry routines. */ struct list_head { struct list_head *prev; struct list_head *next; }; #define LIST_POISON1 ((struct list_head *)0x00100100) #define LIST_POISON2 ((struct list_head *)0x00200200) #define LIST_HEAD_INIT(name) \ { \ &(name), &(name) \ } #define LIST_HEAD(name) struct list_head name = LIST_HEAD_INIT(name) static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; } /* * Insert a new entry between two known consecutive entries. * * This is only for internal list manipulation where we know * the prev/next entries already! */ #ifndef CONFIG_DEBUG_LIST static inline void __list_add(struct list_head *new_entry, struct list_head *prev, struct list_head *next) { next->prev = new_entry; new_entry->next = next; new_entry->prev = prev; prev->next = new_entry; } #else extern void __list_add(struct list_head *new_entry, struct list_head *prev, struct list_head *next); #endif /** * list_add - add a new entry * @new_entry: new entry to be added * @head: list head to add it after * * Insert a new entry after the specified head. * This is good for implementing stacks. */ static inline void list_add(struct list_head *new_entry, struct list_head *head) { __list_add(new_entry, head, head->next); } /** * list_add_tail - add a new entry * @new_entry: new entry to be added * @head: list head to add it before * * Insert a new entry before the specified head. * This is useful for implementing queues. */ static inline void list_add_tail(struct list_head *new_entry, struct list_head *head) { __list_add(new_entry, head->prev, head); } /* * Delete a list entry by making the prev/next entries * point to each other. * * This is only for internal list manipulation where we know * the prev/next entries already! */ static inline void __list_del(struct list_head *prev, struct list_head *next) { next->prev = prev; prev->next = next; } /** * list_del - deletes entry from list. * @entry: the element to delete from the list. * Note: list_empty() on entry does not return true after this, the entry is * in an undefined state. */ #ifndef CONFIG_DEBUG_LIST static inline void __list_del_entry(struct list_head *entry) { __list_del(entry->prev, entry->next); } static inline void list_del(struct list_head *entry) { __list_del(entry->prev, entry->next); entry->next = LIST_POISON1; entry->prev = LIST_POISON2; } #else extern void __list_del_entry(struct list_head *entry); extern void list_del(struct list_head *entry); #endif /** * list_replace - replace old entry by new one * @old_entry : the element to be replaced * @new_entry : the new element to insert * * If @old_entry was empty, it will be overwritten. */ static inline void list_replace(struct list_head *old_entry, struct list_head *new_entry) { new_entry->next = old_entry->next; new_entry->next->prev = new_entry; new_entry->prev = old_entry->prev; new_entry->prev->next = new_entry; } static inline void list_replace_init(struct list_head *old_entry, struct list_head *new_entry) { list_replace(old_entry, new_entry); INIT_LIST_HEAD(old_entry); } /** * list_del_init - deletes entry from list and reinitialize it. * @entry: the element to delete from the list. */ static inline void list_del_init(struct list_head *entry) { __list_del_entry(entry); INIT_LIST_HEAD(entry); } /** * list_move - delete from one list and add as another's head * @list: the entry to move * @head: the head that will precede our entry */ static inline void list_move(struct list_head *list, struct list_head *head) { __list_del_entry(list); list_add(list, head); } /** * list_move_tail - delete from one list and add as another's tail * @list: the entry to move * @head: the head that will follow our entry */ static inline void list_move_tail(struct list_head *list, struct list_head *head) { __list_del_entry(list); list_add_tail(list, head); } /** * list_is_last - tests whether @list is the last entry in list @head * @list: the entry to test * @head: the head of the list */ static inline int list_is_last(const struct list_head *list, const struct list_head *head) { return list->next == head; } /** * list_empty - tests whether a list is empty * @head: the list to test. */ static inline int list_empty(const struct list_head *head) { return head->next == head; } /** * list_empty_careful - tests whether a list is empty and not being modified * @head: the list to test * * Description: * tests whether a list is empty _and_ checks that no other CPU might be * in the process of modifying either member (next or prev) * * NOTE: using list_empty_careful() without synchronization * can only be safe if the only activity that can happen * to the list entry is list_del_init(). Eg. it cannot be used * if another CPU could re-list_add() it. */ static inline int list_empty_careful(const struct list_head *head) { struct list_head *next = head->next; return (next == head) && (next == head->prev); } /** * list_rotate_left - rotate the list to the left * @head: the head of the list */ static inline void list_rotate_left(struct list_head *head) { struct list_head *first; if (!list_empty(head)) { first = head->next; list_move_tail(first, head); } } /** * list_is_singular - tests whether a list has just one entry. * @head: the list to test. */ static inline int list_is_singular(const struct list_head *head) { return !list_empty(head) && (head->next == head->prev); } static inline void __list_cut_position(struct list_head *list, struct list_head *head, struct list_head *entry) { struct list_head *new_first = entry->next; list->next = head->next; list->next->prev = list; list->prev = entry; entry->next = list; head->next = new_first; new_first->prev = head; } /** * list_cut_position - cut a list into two * @list: a new list to add all removed entries * @head: a list with entries * @entry: an entry within head, could be the head itself * and if so we won't cut the list * * This helper moves the initial part of @head, up to and * including @entry, from @head to @list. You should * pass on @entry an element you know is on @head. @list * should be an empty list or a list you do not care about * losing its data. * */ static inline void list_cut_position(struct list_head *list, struct list_head *head, struct list_head *entry) { if (list_empty(head)) return; if (list_is_singular(head) && (head->next != entry && head != entry)) return; if (entry == head) INIT_LIST_HEAD(list); else __list_cut_position(list, head, entry); } static inline void __list_splice(const struct list_head *list, struct list_head *prev, struct list_head *next) { struct list_head *first = list->next; struct list_head *last = list->prev; first->prev = prev; prev->next = first; last->next = next; next->prev = last; } /** * list_splice - join two lists, this is designed for stacks * @list: the new list to add. * @head: the place to add it in the first list. */ static inline void list_splice(const struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head, head->next); } /** * list_splice_tail - join two lists, each list being a queue * @list: the new list to add. * @head: the place to add it in the first list. */ static inline void list_splice_tail(struct list_head *list, struct list_head *head) { if (!list_empty(list)) __list_splice(list, head->prev, head); } /** * list_splice_init - join two lists and reinitialise the emptied list. * @list: the new list to add. * @head: the place to add it in the first list. * * The list at @list is reinitialised */ static inline void list_splice_init(struct list_head *list, struct list_head *head) { if (!list_empty(list)) { __list_splice(list, head, head->next); INIT_LIST_HEAD(list); } } /** * list_splice_tail_init - join two lists and reinitialise the emptied list * @list: the new list to add. * @head: the place to add it in the first list. * * Each of the lists is a queue. * The list at @list is reinitialised */ static inline void list_splice_tail_init(struct list_head *list, struct list_head *head) { if (!list_empty(list)) { __list_splice(list, head->prev, head); INIT_LIST_HEAD(list); } } /** * list_entry - get the struct for this entry * @ptr: the &struct list_head pointer. * @type: the type of the struct this is embedded in. * @member: the name of the list_head within the struct. */ #define list_entry(ptr, type, member) container_of(ptr, type, member) /** * list_first_entry - get the first element from a list * @ptr: the list head to take the element from. * @type: the type of the struct this is embedded in. * @member: the name of the list_head within the struct. * * Note, that list is expected to be not empty. */ #define list_first_entry(ptr, type, member) list_entry((ptr)->next, type, member) /** * list_last_entry - get the last element from a list * @ptr: the list head to take the element from. * @type: the type of the struct this is embedded in. * @member: the name of the list_head within the struct. * * Note, that list is expected to be not empty. */ #define list_last_entry(ptr, type, member) list_entry((ptr)->prev, type, member) /** * list_first_entry_or_null - get the first element from a list * @ptr: the list head to take the element from. * @type: the type of the struct this is embedded in. * @member: the name of the list_head within the struct. * * Note that if the list is empty, it returns NULL. */ #define list_first_entry_or_null(ptr, type, member) \ (!list_empty(ptr) ? list_first_entry(ptr, type, member) : NULL) /** * list_next_entry - get the next element in list * @pos: the type * to cursor * @member: the name of the list_head within the struct. */ #define list_next_entry(pos, member) list_entry((pos)->member.next, typeof(*(pos)), member) /** * list_prev_entry - get the prev element in list * @pos: the type * to cursor * @member: the name of the list_head within the struct. */ #define list_prev_entry(pos, member) list_entry((pos)->member.prev, typeof(*(pos)), member) /** * list_for_each - iterate over a list * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. */ #define list_for_each(pos, head) for (pos = (head)->next; pos != (head); pos = pos->next) /** * list_for_each_prev - iterate over a list backwards * @pos: the &struct list_head to use as a loop cursor. * @head: the head for your list. */ #define list_for_each_prev(pos, head) for (pos = (head)->prev; pos != (head); pos = pos->prev) /** * list_for_each_safe - iterate over a list safe against removal of list entry * @pos: the &struct list_head to use as a loop cursor. * @n: another &struct list_head to use as temporary storage * @head: the head for your list. */ #define list_for_each_safe(pos, n, head) \ for (pos = (head)->next, n = pos->next; pos != (head); pos = n, n = pos->next) /** * list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry * @pos: the &struct list_head to use as a loop cursor. * @n: another &struct list_head to use as temporary storage * @head: the head for your list. */ #define list_for_each_prev_safe(pos, n, head) \ for (pos = (head)->prev, n = pos->prev; pos != (head); pos = n, n = pos->prev) /** * list_for_each_entry - iterate over list of given type * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_head within the struct. */ #define list_for_each_entry(pos, head, member) \ for (pos = list_first_entry(head, typeof(*pos), member); &pos->member != (head); \ pos = list_next_entry(pos, member)) /** * list_for_each_entry_reverse - iterate backwards over list of given type. * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_head within the struct. */ #define list_for_each_entry_reverse(pos, head, member) \ for (pos = list_last_entry(head, typeof(*pos), member); &pos->member != (head); \ pos = list_prev_entry(pos, member)) /** * list_none - found no element after list_for_each() * @pos: the &struct list_head * to use as a loop cursor. * @head: the header for your list. */ #define list_none(pos, head) (pos == head) /** * list_no_entry - found no entry after list_for_each_entry() * @pos: the type * to use as a loop cursor. * @head: the header for your list. * @member: the name of the list_header within the struct. */ #define list_no_entry(pos, head, member) (&(pos)->member == head) /** * list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue() * @pos: the type * to use as a start point * @head: the head of the list * @member: the name of the list_head within the struct. * * Prepares a pos entry for use as a start point in list_for_each_entry_continue(). */ #define list_prepare_entry(pos, head, member) ((pos) ?: list_entry(head, typeof(*pos), member)) /** * list_for_each_entry_continue - continue iteration over list of given type * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_head within the struct. * * Continue to iterate over list of given type, continuing after * the current position. */ #define list_for_each_entry_continue(pos, head, member) \ for (pos = list_next_entry(pos, member); &pos->member != (head); \ pos = list_next_entry(pos, member)) /** * list_for_each_entry_continue_reverse - iterate backwards from the given point * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_head within the struct. * * Start to iterate over list of given type backwards, continuing after * the current position. */ #define list_for_each_entry_continue_reverse(pos, head, member) \ for (pos = list_prev_entry(pos, member); &pos->member != (head); \ pos = list_prev_entry(pos, member)) /** * list_for_each_entry_from - iterate over list of given type from the current point * @pos: the type * to use as a loop cursor. * @head: the head for your list. * @member: the name of the list_head within the struct. * * Iterate over list of given type, continuing from current position. */ #define list_for_each_entry_from(pos, head, member) \ for (; &pos->member != (head); pos = list_next_entry(pos, member)) /** * list_for_each_entry_safe - iterate over list of given type safe against removal of list entry * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_head within the struct. */ #define list_for_each_entry_safe(pos, n, head, member) \ for (pos = list_first_entry(head, typeof(*pos), member), n = list_next_entry(pos, member); \ &pos->member != (head); pos = n, n = list_next_entry(n, member)) /** * list_for_each_entry_safe_continue - continue list iteration safe against removal * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_head within the struct. * * Iterate over list of given type, continuing after current point, * safe against removal of list entry. */ #define list_for_each_entry_safe_continue(pos, n, head, member) \ for (pos = list_next_entry(pos, member), n = list_next_entry(pos, member); \ &pos->member != (head); pos = n, n = list_next_entry(n, member)) /** * list_for_each_entry_safe_from - iterate over list from current point safe against removal * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_head within the struct. * * Iterate over list of given type from current point, safe against * removal of list entry. */ #define list_for_each_entry_safe_from(pos, n, head, member) \ for (n = list_next_entry(pos, member); &pos->member != (head); \ pos = n, n = list_next_entry(n, member)) /** * list_for_each_entry_safe_reverse - iterate backwards over list safe against removal * @pos: the type * to use as a loop cursor. * @n: another type * to use as temporary storage * @head: the head for your list. * @member: the name of the list_head within the struct. * * Iterate backwards over list of given type, safe against removal * of list entry. */ #define list_for_each_entry_safe_reverse(pos, n, head, member) \ for (pos = list_last_entry(head, typeof(*pos), member), n = list_prev_entry(pos, member); \ &pos->member != (head); pos = n, n = list_prev_entry(n, member)) /** * list_safe_reset_next - reset a stale list_for_each_entry_safe loop * @pos: the loop cursor used in the list_for_each_entry_safe loop * @n: temporary storage used in list_for_each_entry_safe * @member: the name of the list_head within the struct. * * list_safe_reset_next is not safe to use in general if the list may be * modified concurrently (eg. the lock is dropped in the loop body). An * exception to this is if the cursor element (pos) is pinned in the list, * and list_safe_reset_next is called after re-taking the lock and before * completing the current iteration of the loop body. */ #define list_safe_reset_next(pos, n, member) n = list_next_entry(pos, member) #endif uftrace-0.15.2/utils/mermaid.html000066400000000000000000000025061455365734300167160ustar00rootroot00000000000000
Show option
Full/Sub Graph Show/Hide Depth
Depth:

uftrace-0.15.2/utils/mermaid.js000066400000000000000000000141301455365734300163620ustar00rootroot00000000000000const GRAPH_TITLE = new String("graph TB\n"); const FULL_DEPTH = document.getElementById("depth-select").options[0]; const COMMENT = new String("%% "); const FUNC_ID_PATTERN = /(?\d+)_(?\d+)/; const COLOR_SUB_DEPTH = new String("#f9f"); const COLOR_PARTIAL_GRAPH = new String("#cab8ff"); const COLOR_STROKE = new String("#333"); let startId = new Array(); let startDepth = new Array(); let startName = new Array(); let callFlow = new Array(); let openNode = new Array(); let baseRootName = new String(); let baseRootId = new String("0"); let baseGraph = new String(); let baseRootDepth = new Number(FULL_DEPTH); let showHideDesc = new String(); let maxDepth = new Number(1); const mermaidTxt = document.getElementById("meraid_graph").innerHTML; var showHideNode = function(nodeId) { const nodeIdResult = nodeId.match(FUNC_ID_PATTERN); if (nodeIdResult == null) { document.getElementById("description").innerHTML = "Invalid Node ID : " + nodeId; return; } const id = nodeIdResult.groups.node_id; const depth = nodeIdResult.groups.node_depth; const open = openNode.includes(id); let start = new Boolean(false); for (let i = 0; i < callFlow.length; i++) { if (start == false && Number(startId[i]) == id) { start = true; } if (start == true) { if (Number(startId[i]) == id || startDepth[i] > depth) { if (open) callFlow[i] = COMMENT + callFlow[i]; else callFlow[i] = callFlow[i].replace(COMMENT, ""); } else { break; } } } if (open) openNode.pop(id); else openNode.push(id); renderGraph("".concat(GRAPH_TITLE, callFlow.join("\n"), "\n", showHideDesc)); }; var selectSubGraph = function(nodeId) { const nodeIdResult = nodeId.match(FUNC_ID_PATTERN); const id = nodeIdResult.groups.node_id; for (let i = 0; i < callFlow.length; i++) { if (Number(startId[i]) == id) { drawBaseGraph(id); break; } } }; var initializeBaseGraph = function() { startId.length = 0; startDepth.length = 0; startName.length = 0; openNode.length = 0; callFlow.length = 0; showHideDesc = ""; baseGraph = ""; }; var getNodeStyle = function(id, fillColor, strokeColor) { return " style " + id + " fill:" + fillColor + " ,stroke:" + strokeColor + ",stroke-width:px;"; }; var drawDepthGraph = function(depth) { mermaid.initialize(config); showHideDesc = ""; openNode.length = 0; const nodeList = new Array(); let id = new String(); for (let i = 0; i < callFlow.length; i++) { callFlow[i] = callFlow[i].replace(COMMENT, ""); if (Number(startDepth[i]) >= depth) callFlow[i] = (COMMENT + callFlow[i]); if (Number(startDepth[i]) == depth) { id = startDepth[i] + "_" + startId[i]; if (nodeList.includes(id) == false) { nodeList.push(id); showHideDesc = showHideDesc.concat( getNodeStyle(id, COLOR_SUB_DEPTH, COLOR_STROKE), "\n"); showHideDesc = showHideDesc.concat(" click " + id + " showHideNode;\n"); } } } renderGraph("".concat(GRAPH_TITLE, callFlow.join("\n"), "\n", showHideDesc)); }; function drawSelectGraph() { let selectionDesc = new String(); let nodeList = new Array(); if (callFlow.length == 0) drawBaseGraph(0); for (let i = 1; i < callFlow.length; i++) { let id = startDepth[i] + "_" + startId[i]; if (nodeList.includes(id) == false) { nodeList.push(id); selectionDesc = selectionDesc.concat( getNodeStyle(id, COLOR_PARTIAL_GRAPH, COLOR_STROKE), "\n"); selectionDesc = selectionDesc.concat(" click " + id + " selectSubGraph;\n"); } } renderGraph(baseGraph.concat("\n", selectionDesc)); document.getElementById("description").innerHTML = "Click on a node to view Sub-Graph"; }; var drawBaseGraph = function(id) { mermaid.initialize(config); initializeBaseGraph(); let start = new Boolean(false); maxDepth = 0; var result = mermaidTxt.split(/\r?\n/); for (let i = 0; i < result.length; i++) { if (result[i].length == 0) continue; let oneCallFlow = result[i].match( /\s+(?\d+)_(?\d+)\[\"(?\S+)\"\]\s+--\S+\|(?\d+)\|\s+(?\d+)_(?\d+)\[\"(?\S+)\"];/); // If call flow is // 3_6["A"] -->|2| 4_9["B"]; // , each matching value is like // start_depth : 3, start_id : 6, start_name : A, call_num : 2, end_depth : 4, end_id : 9, end_name : B if (oneCallFlow == null) continue; if (Number(oneCallFlow.groups.start_id) == id) { start = true; baseRootDepth = Number(oneCallFlow.groups.start_depth); baseRootId = id; } if (start == true) { if (Number(oneCallFlow.groups.start_depth) > baseRootDepth || Number(oneCallFlow.groups.start_id == id)) { callFlow.push(result[i]); startId.push(oneCallFlow.groups.start_id); startDepth.push(oneCallFlow.groups.start_depth); startName.push(oneCallFlow.groups.start_name); } else { break; } } if (Number(oneCallFlow.groups.start_depth) > maxDepth) maxDepth = Number(oneCallFlow.groups.start_depth); } baseGraph = "".concat(GRAPH_TITLE, callFlow.join("\n")); updateDepthList(); renderGraph(baseGraph); }; var updateDepthList = function() { var select = document.getElementById("depth-select"); select.options.length = 0; select.options[0] = new Option("Full", FULL_DEPTH); let limit = new Number(maxDepth - Number(baseRootDepth) + 1); for (let i = 1; i <= limit; i++) { select.options[select.options.length] = new Option(i, i); } } var renderGraph = function(graphText) { var graphDiv = document.getElementById("meraid_graph"); graphText = graphText.replace(/>/g, ">"); var insertSvg = function(svgCode, bindFunctions) { graphDiv.innerHTML = svgCode; if (typeof callback != "undefined") { callback(id); } bindFunctions(graphDiv); }; mermaid.render("callGraph", graphText, insertSvg, graphDiv); document.getElementById("description").innerHTML = ""; }; let selector = document.getElementById("depth-select"); selector.addEventListener("click", () => { selector.addEventListener("change", () => { if (callFlow.length == 0) drawBaseGraph(0); showHideDepth = Number(document.getElementById("depth-select").value) + Number(baseRootDepth); if (showHideDepth != FULL_DEPTH) drawDepthGraph(showHideDepth); }); }); uftrace-0.15.2/utils/pager.c000066400000000000000000000036431455365734300156570ustar00rootroot00000000000000/* * This code is based on Linux perf tools (so in turn git) project * which is released under GPL v2. */ #include #include #include #include #include #include static int pager_pid; static int pager_fd; static int start_command(const char *argv[]) { int fds[2]; if (pipe(fds) < 0) return -1; fflush(NULL); pager_pid = fork(); if (pager_pid < 0) { close(fds[0]); close(fds[1]); return -1; } if (!pager_pid) { fd_set in_set; fd_set ex_set; /* child process */ dup2(fds[0], STDIN_FILENO); close(fds[0]); close(fds[1]); FD_ZERO(&in_set); FD_ZERO(&ex_set); FD_SET(STDIN_FILENO, &in_set); FD_SET(STDIN_FILENO, &ex_set); /* * Work around bug in "less" by not starting it * until we have real input */ select(1, &in_set, NULL, &ex_set, NULL); setenv("LESS", "FRSX", 0); execvp(argv[0], (char *const *)argv); exit(127); } close(fds[0]); pager_fd = fds[1]; return 0; } void wait_for_pager(void) { int status; if (!pager_pid) return; /* signal EOF to pager */ fclose(stdout); fclose(stderr); waitpid(pager_pid, &status, 0); pager_pid = 0; } char *setup_pager(void) { char *pager = getenv("PAGER"); if (!isatty(STDOUT_FILENO)) return NULL; if (!(pager || access("/usr/bin/pager", X_OK))) pager = "/usr/bin/pager"; if (!(pager || access("/usr/bin/less", X_OK))) pager = "/usr/bin/less"; if (!pager) pager = "cat"; if (!*pager || !strcmp(pager, "cat")) return NULL; return pager; } void start_pager(char *pager) { const char *pager_argv[] = { "sh", "-c", NULL, NULL }; if (pager == NULL) return; /* spawn the pager */ pager_argv[2] = pager; if (start_command(pager_argv)) return; /* original process continues, but writes to the pipe */ dup2(pager_fd, STDOUT_FILENO); if (isatty(STDERR_FILENO)) dup2(pager_fd, STDERR_FILENO); close(pager_fd); atexit(wait_for_pager); } uftrace-0.15.2/utils/perf.c000066400000000000000000000351341455365734300155150ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "uftrace.h" #include "utils/compiler.h" #include "utils/fstack.h" #include "utils/perf.h" /* It needs to synchronize records using monotonic clock */ #ifdef HAVE_PERF_CLOCKID #define PERF_PARANOID_CHECK "/proc/sys/kernel/perf_event_paranoid" static bool use_perf = true; static int open_perf_event(int pid, int cpu, int use_ctxsw) { /* use dummy events to get scheduling info (Linux v4.3 or later) */ struct perf_event_attr attr = { .size = sizeof(attr), .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_DUMMY, .sample_type = PERF_SAMPLE_TIME | PERF_SAMPLE_TID, .sample_period = 1, .sample_id_all = 1, .exclude_kernel = 1, .disabled = 1, .enable_on_exec = 1, .inherit = 1, .watermark = 1, .wakeup_watermark = PERF_WATERMARK, .task = 1, .comm = 1, .use_clockid = 1, .clockid = clock_source, #ifdef HAVE_PERF_CTXSW .context_switch = use_ctxsw, #endif }; unsigned long flag = PERF_FLAG_FD_NO_GROUP; return syscall(SYS_perf_event_open, &attr, pid, cpu, -1, flag); } /** * setup_perf_record - prepare recording perf events * @perf: data structure for perf record * @nr_cpu: total number of cpus to record * @pid: process id to record * @dirname: directory name to save perf record data * @use_ctxsw: whether to use context_switch attribute * * This function prepares recording linux perf events. The perf_event * fd should be opened and mmaped for each cpu. * * It returns 0 for success, -1 if failed. Callers should call * finish_perf_record() after recording. */ int setup_perf_record(struct uftrace_perf_writer *perf, int nr_cpu, int pid, const char *dirname, int use_ctxsw) { char filename[PATH_MAX]; int fd, cpu; perf->event_fd = xcalloc(nr_cpu, sizeof(*perf->event_fd)); perf->data_pos = xcalloc(nr_cpu, sizeof(*perf->data_pos)); perf->page = xcalloc(nr_cpu, sizeof(*perf->page)); perf->fp = xcalloc(nr_cpu, sizeof(*perf->fp)); perf->nr_event = nr_cpu; memset(perf->event_fd, -1, nr_cpu * sizeof(fd)); if (!PERF_CTXSW_AVAILABLE && use_ctxsw) { /* Operation not supported */ pr_dbg("linux:schedule event is not supported for this kernel\n"); use_ctxsw = 0; } for (cpu = 0; cpu < nr_cpu; cpu++) { fd = open_perf_event(pid, cpu, use_ctxsw); if (fd < 0) { int saved_errno = errno; pr_dbg("skipping perf event due to error: %m\n"); if (saved_errno == EACCES) pr_dbg("please check %s\n", PERF_PARANOID_CHECK); use_perf = false; break; } perf->event_fd[cpu] = fd; perf->page[cpu] = mmap(NULL, PERF_MMAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (perf->page[cpu] == MAP_FAILED) { pr_warn("failed to mmap perf event: %m\n"); use_perf = false; break; } snprintf(filename, sizeof(filename), "%s/perf-cpu%d.dat", dirname, cpu); perf->fp[cpu] = fopen(filename, "w"); if (perf->fp[cpu] == NULL) { pr_warn("failed to create perf data file: %m\n"); use_perf = false; break; } } if (!use_perf) { finish_perf_record(perf); return -1; } return 0; } /** * finish_perf_record - destroy data structure for perf recording * @perf: data structure for perf record * * This function releases all resources in the @perf. */ void finish_perf_record(struct uftrace_perf_writer *perf) { int cpu; for (cpu = 0; cpu < perf->nr_event; cpu++) { close(perf->event_fd[cpu]); munmap(perf->page[cpu], PERF_MMAP_SIZE); if (perf->fp[cpu]) fclose(perf->fp[cpu]); } free(perf->event_fd); free(perf->page); free(perf->data_pos); free(perf->fp); perf->event_fd = NULL; perf->page = NULL; perf->data_pos = NULL; perf->fp = NULL; perf->nr_event = 0; } /** * record_perf_data - record perf event data to file or socket * @perf: data structure for perf record * @cpu: cpu number for perf event * @sock: socket fd to send perf data * * This function copies contents in the perf ring buffer to a file * or a network socket. */ void record_perf_data(struct uftrace_perf_writer *perf, int cpu, int sock) { struct perf_event_mmap_page *pc; unsigned char *data; volatile uint64_t *ptr; uint64_t mask; uint64_t old, pos, start, end; unsigned long size; unsigned char *buf; /* * it can have invalid cpu index due to rounding. * see cmds/record.c::start_tracing() */ if (cpu < 0) return; pc = perf->page[cpu]; data = perf->page[cpu] + pc->data_offset; ptr = (void *)&pc->data_head; mask = pc->data_size - 1; pos = *ptr; old = perf->data_pos[cpu]; /* ensure reading the data head first */ read_memory_barrier(); if (pos == old) return; size = pos - old; if (size > (unsigned long)(mask) + 1) { static bool once = true; if (once) { pr_warn("failed to keep up with mmap data.\n"); once = false; } pc->data_tail = pos; perf->data_pos[cpu] = pos; return; } start = old; end = pos; /* handle wrap around */ if ((start & mask) + size != (end & mask)) { buf = &data[start & mask]; size = mask + 1 - (start & mask); start += size; if (sock > 0) send_trace_perf_data(sock, cpu, buf, size); else if (fwrite(buf, 1, size, perf->fp[cpu]) != size) { pr_dbg("failed to write perf data: %m\n"); goto out; } } buf = &data[start & mask]; size = end - start; if (sock > 0) send_trace_perf_data(sock, cpu, buf, size); else if (fwrite(buf, 1, size, perf->fp[cpu]) != size) pr_dbg("failed to write perf data: %m\n"); out: /* ensure all reads are done before we write the tail. */ full_memory_barrier(); pc->data_tail = pos; perf->data_pos[cpu] = pos; } #endif /* HAVE_PERF_CLOCKID */ /** * setup_perf_data - prepare reading perf event data * @handle - uftrace data file handle * * This function prepares to read perf event data from perf-cpu*.dat * files. It returns 0 on success which includes that perf event data * already setup, -1 on failure. Callers should call * finish_perf_data() after reading all perf event data. */ int setup_perf_data(struct uftrace_data *handle) { struct uftrace_perf_reader *perf; glob_t globbuf; char *pattern; size_t i; int ret = -1; if (has_perf_data(handle)) return 0; xasprintf(&pattern, "%s/perf-cpu*.dat", handle->dirname); if (glob(pattern, GLOB_ERR, NULL, &globbuf)) { pr_dbg("failed to search perf data file\n"); handle->hdr.feat_mask &= ~PERF_EVENT; handle->nr_perf = 0; goto out; } perf = xcalloc(globbuf.gl_pathc, sizeof(*perf)); for (i = 0; i < globbuf.gl_pathc; i++) { perf[i].fp = fopen(globbuf.gl_pathv[i], "r"); if (perf[i].fp == NULL) pr_err("open failed: %s", globbuf.gl_pathv[i]); } handle->nr_perf = globbuf.gl_pathc; handle->perf = perf; ret = 0; globfree(&globbuf); out: free(pattern); return ret; } /** * finish_perf_data - destroy resources for perf event data * @handle - uftrace data file handle * * This function releases all resources regarding perf event. */ void finish_perf_data(struct uftrace_data *handle) { int i; if (handle->perf == NULL) return; for (i = 0; i < handle->nr_perf; i++) fclose(handle->perf[i].fp); free(handle->perf); handle->perf = NULL; } static int read_perf_event(struct uftrace_data *handle, struct uftrace_perf_reader *perf) { struct perf_event_header h; struct uftrace_task_reader *task; union { struct perf_context_switch_event cs; struct perf_task_event t; struct perf_comm_event c; } u; size_t len; int comm_len; if (perf->done || perf->fp == NULL) return -1; again: if (fread(&h, sizeof(h), 1, perf->fp) != 1) { perf->done = true; return -1; } if (handle->needs_byte_swap) { h.type = bswap_32(h.type); h.misc = bswap_16(h.misc); h.size = bswap_16(h.size); } len = h.size - sizeof(h); switch (h.type) { case PERF_RECORD_SWITCH: if (fread(&u.cs, len, 1, perf->fp) != 1) return -1; if (handle->needs_byte_swap) { u.cs.sample_id.time = bswap_64(u.cs.sample_id.time); u.cs.sample_id.tid = bswap_32(u.cs.sample_id.tid); } perf->u.ctxsw.out = h.misc & PERF_RECORD_MISC_SWITCH_OUT; perf->u.ctxsw.preempt = h.misc & PERF_RECORD_MISC_SWITCH_OUT_PREEMPT; perf->time = u.cs.sample_id.time; perf->tid = u.cs.sample_id.tid; break; case PERF_RECORD_FORK: case PERF_RECORD_EXIT: if (fread(&u.t, len, 1, perf->fp) != 1) return -1; if (handle->needs_byte_swap) { u.t.tid = bswap_32(u.t.tid); u.t.pid = bswap_32(u.t.pid); u.t.ppid = bswap_32(u.t.ppid); u.t.time = bswap_64(u.t.time); } perf->u.task.pid = u.t.pid; perf->u.task.ppid = u.t.ppid; perf->time = u.t.time; perf->tid = u.t.tid; break; case PERF_RECORD_COMM: /* length of comm event is variable */ comm_len = ALIGN(len - sizeof(u.c.sample_id), 8); if (fread(&u.c, comm_len, 1, perf->fp) != 1) return -1; if (fread(&u.c.sample_id, sizeof(u.c.sample_id), 1, perf->fp) != 1) return -1; if (handle->needs_byte_swap) { u.c.tid = bswap_32(u.c.tid); u.c.pid = bswap_32(u.c.pid); u.c.sample_id.time = bswap_64(u.c.sample_id.time); } perf->u.comm.pid = u.c.pid; perf->u.comm.exec = h.misc & PERF_RECORD_MISC_COMM_EXEC; strncpy(perf->u.comm.comm, u.c.comm, sizeof(perf->u.comm.comm)); perf->time = u.c.sample_id.time; perf->tid = u.c.tid; break; default: pr_dbg3("skip unknown event: %u\n", h.type); if (fseek(perf->fp, len, SEEK_CUR) < 0) { pr_warn("skipping perf data failed: %m\n"); perf->done = true; return -1; } goto again; } task = get_task_handle(handle, perf->tid); if (unlikely(task == NULL || task->fp == NULL)) goto again; if (!check_time_range(&handle->time_range, perf->time)) goto again; perf->type = h.type; perf->valid = true; return 0; } /** * read_perf_data - read perf event data * @handle: uftrace data file handle * * This function reads perf events for each cpu data file and returns * the (cpu) index of earliest event. The event info can be found in * @handle->perf[idx]. * * It's important that callers should reset the valid bit after using * the event so that it can read next event for the cpu data file. */ int read_perf_data(struct uftrace_data *handle) { struct uftrace_perf_reader *perf; uint64_t min_time = ~0ULL; int best = -1; int i; for (i = 0; i < handle->nr_perf; i++) { perf = &handle->perf[i]; if (perf->done) continue; if (!perf->valid) { if (read_perf_event(handle, perf) < 0) continue; } if (perf->time < min_time) { min_time = perf->time; best = i; } } handle->last_perf_idx = best; return best; } /** * get_perf_record - convert perf event into uftrace record format * @handle: uftrace data file handle * @perf: data structure for perf event * * This function converts the last perf event into an uftrace record * so that it can be handled in the fstack code like normal function * record. This is useful for schedule event treated as a function. * * Normally this is called after read_perf_data() so it knows current * event. But do_dump_file() calls it directly without the above * function in order to access to the raw file contents. */ struct uftrace_record *get_perf_record(struct uftrace_data *handle, struct uftrace_perf_reader *perf) { static struct uftrace_record rec; if (!perf->valid) { if (read_perf_event(handle, perf) < 0) return NULL; } rec.type = UFTRACE_EVENT; rec.time = perf->time; rec.magic = RECORD_MAGIC; rec.more = 0; switch (perf->type) { case PERF_RECORD_FORK: rec.addr = EVENT_ID_PERF_TASK; break; case PERF_RECORD_EXIT: rec.addr = EVENT_ID_PERF_EXIT; break; case PERF_RECORD_COMM: rec.addr = EVENT_ID_PERF_COMM; break; case PERF_RECORD_SWITCH: if (perf->u.ctxsw.out) { if (perf->u.ctxsw.preempt) rec.addr = EVENT_ID_PERF_SCHED_OUT_PREEMPT; else rec.addr = EVENT_ID_PERF_SCHED_OUT; } else rec.addr = EVENT_ID_PERF_SCHED_IN; break; } return &rec; } /** * update_perf_task_comm - read perf event data and update task's comm * @handle: uftrace data file handle * * This function reads perf events for each cpu data file and updates * task->comm for each PERF_RECORD_COMM. */ void update_perf_task_comm(struct uftrace_data *handle) { struct uftrace_perf_reader *perf; struct uftrace_task *task; int i; for (i = 0; i < handle->nr_perf; i++) { perf = &handle->perf[i]; while (!perf->done) { if (read_perf_event(handle, perf) < 0) continue; task = find_task(&handle->sessions, perf->tid); if (task == NULL) continue; if (task->time.stamp == 0 || task->time.stamp > perf->time) task->time.stamp = perf->time; if (perf->type != PERF_RECORD_COMM) continue; memcpy(task->comm, perf->u.comm.comm, sizeof(task->comm)); } /* reset file position for future processing */ rewind(perf->fp); perf->valid = false; perf->done = false; } } static void remove_event_rstack(struct uftrace_task_reader *task) { struct uftrace_rstack_list_node *last; uint64_t last_addr; /* also delete matching entry (at the last) */ do { last = list_last_entry(&task->event_list.read, typeof(*last), list); last_addr = last->rstack.addr; delete_last_rstack_list(&task->event_list); } while (last_addr != EVENT_ID_PERF_SCHED_OUT && last_addr != EVENT_ID_PERF_SCHED_OUT_PREEMPT); } void process_perf_event(struct uftrace_data *handle) { struct uftrace_perf_reader *perf; struct uftrace_task_reader *task; struct uftrace_record *rec; struct uftrace_fstack_args args = {}; int p; if (handle->perf_event_processed) return; while (1) { p = read_perf_data(handle); if (p < 0) break; perf = &handle->perf[p]; rec = get_perf_record(handle, perf); task = get_task_handle(handle, perf->tid); if (unlikely(task == NULL || task->fp == NULL)) continue; if (perf->type == PERF_RECORD_COMM) { rec->more = 1; args.args = NULL; args.data = xstrdup(perf->u.comm.comm); args.len = strlen(perf->u.comm.comm) + 1; } else if (perf->type == PERF_RECORD_SWITCH && !perf->u.ctxsw.out) { struct uftrace_rstack_list_node *last; uint64_t delta; if (task->event_list.count == 0) goto add_it; last = list_last_entry(&task->event_list.read, typeof(*last), list); /* time filter is meaningful only for schedule events */ while (last->rstack.addr != EVENT_ID_PERF_SCHED_OUT && last->rstack.addr != EVENT_ID_PERF_SCHED_OUT_PREEMPT) { if (last->list.prev == &task->event_list.read) goto add_it; last = list_prev_entry(last, list); } delta = perf->time - last->rstack.time; if (delta < handle->time_filter) { remove_event_rstack(task); perf->valid = false; continue; } } add_it: add_to_rstack_list(&task->event_list, rec, &args); if (args.len) { free(args.data); args.data = NULL; args.len = 0; } perf->valid = false; } handle->perf_event_processed = true; } uftrace-0.15.2/utils/perf.h000066400000000000000000000055321455365734300155210ustar00rootroot00000000000000#ifndef UFTRACE_PERF_H #define UFTRACE_PERF_H #include #include #include #include #define PERF_MMAP_SIZE (132 * 1024) /* 32 + 1 pages */ #define PERF_WATERMARK (8 * 1024) /* 2 pages */ #define COMM_LEN 16 struct uftrace_perf_writer { int *event_fd; void **page; uint64_t *data_pos; FILE **fp; int nr_event; }; struct sample_id { uint32_t pid; uint32_t tid; uint64_t time; }; struct perf_task_event { /* * type: PERF_RECORD_FORK (7) or PERF_RECORD_EXIT (4) */ uint32_t pid, ppid; uint32_t tid, ptid; uint64_t time; struct sample_id sample_id; }; struct perf_comm_event { /* * type: PERF_RECORD_COMM (3) */ uint32_t pid, tid; /* variable length (aligned to 8) */ char comm[COMM_LEN]; /* needs to be read separately */ struct sample_id sample_id; }; struct perf_context_switch_event { /* * type: PERF_RECORD_SWITCH (14) * misc: PERF_RECORD_MISC_SWITCH_OUT (0x2000) * size: 24 */ struct sample_id sample_id; }; struct uftrace_ctxsw_event { bool out; bool preempt; }; struct uftrace_task_event { int pid; int ppid; }; struct uftrace_comm_event { int pid; bool exec; char comm[COMM_LEN]; }; #ifdef HAVE_PERF_CLOCKID int setup_perf_record(struct uftrace_perf_writer *perf, int nr_cpu, int pid, const char *dirname, int use_ctxsw); void finish_perf_record(struct uftrace_perf_writer *perf); void record_perf_data(struct uftrace_perf_writer *perf, int cpu, int sock); #else /* !HAVE_PERF_CLOCKID */ static inline int setup_perf_record(struct uftrace_perf_writer *perf, int nr_cpu, int pid, const char *dirname, int use_ctxsw) { return -1; } static inline void finish_perf_record(struct uftrace_perf_writer *perf) { } static inline void record_perf_data(struct uftrace_perf_writer *perf, int cpu, int sock) { } #endif /* HAVE_PERF_CLOCKID */ #ifndef PERF_RECORD_MISC_COMM_EXEC #define PERF_RECORD_MISC_COMM_EXEC (1 << 13) #endif #ifdef HAVE_PERF_CTXSW #define PERF_CTXSW_AVAILABLE 1 #else /* !HAVE_PERF_CTXSW */ #define PERF_CTXSW_AVAILABLE 0 #define PERF_RECORD_SWITCH 14 #define PERF_RECORD_MISC_SWITCH_OUT (1 << 13) #endif /* HAVE_PERF_CTXSW */ #define PERF_RECORD_MISC_SWITCH_OUT_PREEMPT (1 << 14) struct uftrace_perf_reader { FILE *fp; bool valid; bool done; int type; int tid; uint64_t time; union { struct uftrace_ctxsw_event ctxsw; struct uftrace_task_event task; struct uftrace_comm_event comm; } u; }; struct uftrace_data; struct uftrace_record; int setup_perf_data(struct uftrace_data *handle); void finish_perf_data(struct uftrace_data *handle); int read_perf_data(struct uftrace_data *handle); struct uftrace_record *get_perf_record(struct uftrace_data *handle, struct uftrace_perf_reader *perf); void update_perf_task_comm(struct uftrace_data *handle); void process_perf_event(struct uftrace_data *handle); #endif /* UFTRACE_PERF_H */ uftrace-0.15.2/utils/rbtree.c000066400000000000000000000203571455365734300160450ustar00rootroot00000000000000/* Red Black Trees (C) 1999 Andrea Arcangeli (C) 2002 David Woodhouse 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA linux/lib/rbtree.c */ #include "utils/rbtree.h" static void __rb_rotate_left(struct rb_node *node, struct rb_root *root) { struct rb_node *right = node->rb_right; struct rb_node *parent = rb_parent(node); if ((node->rb_right = right->rb_left)) rb_set_parent(right->rb_left, node); right->rb_left = node; rb_set_parent(right, parent); if (parent) { if (node == parent->rb_left) parent->rb_left = right; else parent->rb_right = right; } else root->rb_node = right; rb_set_parent(node, right); } static void __rb_rotate_right(struct rb_node *node, struct rb_root *root) { struct rb_node *left = node->rb_left; struct rb_node *parent = rb_parent(node); if ((node->rb_left = left->rb_right)) rb_set_parent(left->rb_right, node); left->rb_right = node; rb_set_parent(left, parent); if (parent) { if (node == parent->rb_right) parent->rb_right = left; else parent->rb_left = left; } else root->rb_node = left; rb_set_parent(node, left); } void rb_insert_color(struct rb_node *node, struct rb_root *root) { struct rb_node *parent, *gparent; while ((parent = rb_parent(node)) && rb_is_red(parent)) { gparent = rb_parent(parent); if (parent == gparent->rb_left) { { register struct rb_node *uncle = gparent->rb_right; if (uncle && rb_is_red(uncle)) { rb_set_black(uncle); rb_set_black(parent); rb_set_red(gparent); node = gparent; continue; } } if (parent->rb_right == node) { register struct rb_node *tmp; __rb_rotate_left(parent, root); tmp = parent; parent = node; node = tmp; } rb_set_black(parent); rb_set_red(gparent); __rb_rotate_right(gparent, root); } else { { register struct rb_node *uncle = gparent->rb_left; if (uncle && rb_is_red(uncle)) { rb_set_black(uncle); rb_set_black(parent); rb_set_red(gparent); node = gparent; continue; } } if (parent->rb_left == node) { register struct rb_node *tmp; __rb_rotate_right(parent, root); tmp = parent; parent = node; node = tmp; } rb_set_black(parent); rb_set_red(gparent); __rb_rotate_left(gparent, root); } } rb_set_black(root->rb_node); } static void __rb_erase_color(struct rb_node *node, struct rb_node *parent, struct rb_root *root) { struct rb_node *other; while ((!node || rb_is_black(node)) && node != root->rb_node) { if (parent->rb_left == node) { other = parent->rb_right; if (rb_is_red(other)) { rb_set_black(other); rb_set_red(parent); __rb_rotate_left(parent, root); other = parent->rb_right; } if ((!other->rb_left || rb_is_black(other->rb_left)) && (!other->rb_right || rb_is_black(other->rb_right))) { rb_set_red(other); node = parent; parent = rb_parent(node); } else { if (!other->rb_right || rb_is_black(other->rb_right)) { rb_set_black(other->rb_left); rb_set_red(other); __rb_rotate_right(other, root); other = parent->rb_right; } rb_set_color(other, rb_color(parent)); rb_set_black(parent); rb_set_black(other->rb_right); __rb_rotate_left(parent, root); node = root->rb_node; break; } } else { other = parent->rb_left; if (rb_is_red(other)) { rb_set_black(other); rb_set_red(parent); __rb_rotate_right(parent, root); other = parent->rb_left; } if ((!other->rb_left || rb_is_black(other->rb_left)) && (!other->rb_right || rb_is_black(other->rb_right))) { rb_set_red(other); node = parent; parent = rb_parent(node); } else { if (!other->rb_left || rb_is_black(other->rb_left)) { rb_set_black(other->rb_right); rb_set_red(other); __rb_rotate_left(other, root); other = parent->rb_left; } rb_set_color(other, rb_color(parent)); rb_set_black(parent); rb_set_black(other->rb_left); __rb_rotate_right(parent, root); node = root->rb_node; break; } } } if (node) rb_set_black(node); } void rb_erase(struct rb_node *node, struct rb_root *root) { struct rb_node *child, *parent; int color; if (!node->rb_left) child = node->rb_right; else if (!node->rb_right) child = node->rb_left; else { struct rb_node *old = node, *left; node = node->rb_right; while ((left = node->rb_left) != NULL) node = left; if (rb_parent(old)) { if (rb_parent(old)->rb_left == old) rb_parent(old)->rb_left = node; else rb_parent(old)->rb_right = node; } else root->rb_node = node; child = node->rb_right; parent = rb_parent(node); color = rb_color(node); if (parent == old) { parent = node; } else { if (child) rb_set_parent(child, parent); parent->rb_left = child; node->rb_right = old->rb_right; rb_set_parent(old->rb_right, node); } node->rb_parent_color = old->rb_parent_color; node->rb_left = old->rb_left; rb_set_parent(old->rb_left, node); goto color; } parent = rb_parent(node); color = rb_color(node); if (child) rb_set_parent(child, parent); if (parent) { if (parent->rb_left == node) parent->rb_left = child; else parent->rb_right = child; } else root->rb_node = child; color: if (color == RB_BLACK) __rb_erase_color(child, parent, root); } /* * This function returns the first node (in sort order) of the tree. */ struct rb_node *rb_first(const struct rb_root *root) { struct rb_node *n; n = root->rb_node; if (!n) return NULL; while (n->rb_left) n = n->rb_left; return n; } struct rb_node *rb_last(const struct rb_root *root) { struct rb_node *n; n = root->rb_node; if (!n) return NULL; while (n->rb_right) n = n->rb_right; return n; } struct rb_node *rb_next(const struct rb_node *node) { struct rb_node *parent; if (rb_parent(node) == node) return NULL; /* If we have a right-hand child, go down and then left as far as we can. */ if (node->rb_right) { node = node->rb_right; while (node->rb_left) node = node->rb_left; return (struct rb_node *)node; } /* No right-hand children. Everything down and left is smaller than us, so any 'next' node must be in the general direction of our parent. Go up the tree; any time the ancestor is a right-hand child of its parent, keep going up. First time it's a left-hand child of its parent, said parent is our 'next' node. */ while ((parent = rb_parent(node)) && node == parent->rb_right) node = parent; return parent; } struct rb_node *rb_prev(const struct rb_node *node) { struct rb_node *parent; if (rb_parent(node) == node) return NULL; /* If we have a left-hand child, go down and then right as far as we can. */ if (node->rb_left) { node = node->rb_left; while (node->rb_right) node = node->rb_right; return (struct rb_node *)node; } /* No left-hand children. Go up till we find an ancestor which is a right-hand child of its parent */ while ((parent = rb_parent(node)) && node == parent->rb_left) node = parent; return parent; } void rb_replace_node(struct rb_node *victim, struct rb_node *new_entry, struct rb_root *root) { struct rb_node *parent = rb_parent(victim); /* Set the surrounding nodes to point to the replacement */ if (parent) { if (victim == parent->rb_left) parent->rb_left = new_entry; else parent->rb_right = new_entry; } else { root->rb_node = new_entry; } if (victim->rb_left) rb_set_parent(victim->rb_left, new_entry); if (victim->rb_right) rb_set_parent(victim->rb_right, new_entry); /* Copy the pointers/colour from the victim to the replacement */ *new_entry = *victim; } uftrace-0.15.2/utils/rbtree.h000066400000000000000000000103641455365734300160470ustar00rootroot00000000000000/* Red Black Trees (C) 1999 Andrea Arcangeli 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA linux/include/linux/rbtree.h To use rbtrees you'll have to implement your own insert and search cores. This will avoid us to use callbacks and to drop drammatically performances. I know it's not the cleaner way, but in C (not in C++) to get performances and genericity... */ #ifndef _LINUX_RBTREE_H #define _LINUX_RBTREE_H #include /* NULL */ #ifndef container_of #define container_of(ptr, type, member) \ ({ \ const typeof(((type *)0)->member) *__mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); \ }) #endif struct rb_node { unsigned long rb_parent_color; #define RB_RED 0 #define RB_BLACK 1 struct rb_node *rb_right; struct rb_node *rb_left; } __attribute__((aligned(sizeof(long)))); /* The alignment might seem pointless, but allegedly CRIS needs it */ struct rb_root { struct rb_node *rb_node; }; #define rb_parent(r) ((struct rb_node *)((r)->rb_parent_color & ~3)) #define rb_color(r) ((r)->rb_parent_color & 1) #define rb_is_red(r) (!rb_color(r)) #define rb_is_black(r) rb_color(r) #define rb_set_red(r) \ do { \ (r)->rb_parent_color &= ~1; \ } while (0) #define rb_set_black(r) \ do { \ (r)->rb_parent_color |= 1; \ } while (0) static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p) { rb->rb_parent_color = (rb->rb_parent_color & 3) | (unsigned long)p; } static inline void rb_set_color(struct rb_node *rb, int color) { rb->rb_parent_color = (rb->rb_parent_color & ~1) | color; } #define RB_ROOT \ (struct rb_root) \ { \ NULL, \ } #define rb_entry(ptr, type, member) container_of(ptr, type, member) #define RB_EMPTY_ROOT(root) ((root)->rb_node == NULL) #define RB_EMPTY_NODE(node) (rb_parent(node) == node) #define RB_CLEAR_NODE(node) (rb_set_parent(node, node)) extern void rb_insert_color(struct rb_node *, struct rb_root *); extern void rb_erase(struct rb_node *, struct rb_root *); /* Find logical next and previous nodes in a tree */ extern struct rb_node *rb_next(const struct rb_node *); extern struct rb_node *rb_prev(const struct rb_node *); extern struct rb_node *rb_first(const struct rb_root *); extern struct rb_node *rb_last(const struct rb_root *); /* Fast replacement of a single node without remove/rebalance/add/rebalance */ extern void rb_replace_node(struct rb_node *victim, struct rb_node *new_entry, struct rb_root *root); static inline void rb_link_node(struct rb_node *node, struct rb_node *parent, struct rb_node **rb_link) { node->rb_parent_color = (unsigned long)parent; node->rb_left = node->rb_right = NULL; *rb_link = node; } #endif /* _LINUX_RBTREE_H */ uftrace-0.15.2/utils/regs.c000066400000000000000000000223021455365734300155120ustar00rootroot00000000000000 #include "utils/arch.h" #include "utils/utils.h" struct uftrace_reg_table { const char *name; int reg; }; static const struct uftrace_reg_table uft_x86_64_reg_table[] = { #define X86_REG(_r) \ { \ #_r, UFT_X86_64_REG_##_r \ } /* integer registers */ X86_REG(RDI), X86_REG(RSI), X86_REG(RDX), X86_REG(RCX), X86_REG(R8), X86_REG(R9), /* floating-point registers */ X86_REG(XMM0), X86_REG(XMM1), X86_REG(XMM2), X86_REG(XMM3), X86_REG(XMM4), X86_REG(XMM5), X86_REG(XMM6), X86_REG(XMM7), #undef X86_REG }; static const struct uftrace_reg_table uft_arm_reg_table[] = { #define ARM_REG(_r) \ { \ #_r, UFT_ARM_REG_##_r \ } /* integer registers */ ARM_REG(R0), ARM_REG(R1), ARM_REG(R2), ARM_REG(R3), /* floating-point registers */ ARM_REG(S0), ARM_REG(S1), ARM_REG(S2), ARM_REG(S3), ARM_REG(S4), ARM_REG(S5), ARM_REG(S6), ARM_REG(S7), ARM_REG(S8), ARM_REG(S9), ARM_REG(S10), ARM_REG(S11), ARM_REG(S12), ARM_REG(S13), ARM_REG(S14), ARM_REG(S15), ARM_REG(D0), ARM_REG(D1), ARM_REG(D2), ARM_REG(D3), ARM_REG(D4), ARM_REG(D5), ARM_REG(D6), ARM_REG(D7), #undef ARM_REG }; static const struct uftrace_reg_table uft_aarch64_reg_table[] = { #define ARM64_REG(_r) \ { \ #_r, UFT_AARCH64_REG_##_r \ } /* integer registers */ ARM64_REG(X0), ARM64_REG(X1), ARM64_REG(X2), ARM64_REG(X3), ARM64_REG(X4), ARM64_REG(X5), ARM64_REG(X6), ARM64_REG(X7), /* floating-point registers */ ARM64_REG(S0), ARM64_REG(S1), ARM64_REG(S2), ARM64_REG(S3), ARM64_REG(S4), ARM64_REG(S5), ARM64_REG(S6), ARM64_REG(S7), ARM64_REG(D0), ARM64_REG(D1), ARM64_REG(D2), ARM64_REG(D3), ARM64_REG(D4), ARM64_REG(D5), ARM64_REG(D6), ARM64_REG(D7), #undef ARM64_REG }; static const struct uftrace_reg_table uft_i386_reg_table[] = { #define X86_REG(_r) \ { \ #_r, UFT_I386_REG_##_r \ } /* integer registers */ X86_REG(ECX), X86_REG(EDX), /* floating-point registers */ X86_REG(XMM0), X86_REG(XMM1), X86_REG(XMM2), X86_REG(XMM3), X86_REG(XMM4), X86_REG(XMM5), X86_REG(XMM6), X86_REG(XMM7), #undef X86_REG }; static const struct uftrace_reg_table uft_riscv64_reg_table[] = { #define RISCV64_REG(_r) \ { \ #_r, UFT_RISCV64_REG_##_r \ } /* integer registers */ RISCV64_REG(A0), RISCV64_REG(A1), RISCV64_REG(A2), RISCV64_REG(A3), RISCV64_REG(A4), RISCV64_REG(A5), RISCV64_REG(A6), RISCV64_REG(A7), /* floating-point registers */ RISCV64_REG(FA0), RISCV64_REG(FA1), RISCV64_REG(FA2), RISCV64_REG(FA3), RISCV64_REG(FA4), RISCV64_REG(FA5), RISCV64_REG(FA6), RISCV64_REG(FA7), #undef RISCV64_REG }; static const struct uftrace_reg_table *arch_reg_tables[] = { NULL, uft_x86_64_reg_table, uft_arm_reg_table, uft_aarch64_reg_table, uft_i386_reg_table, uft_riscv64_reg_table, }; static const size_t arch_reg_sizes[] = { 0, ARRAY_SIZE(uft_x86_64_reg_table), ARRAY_SIZE(uft_arm_reg_table), ARRAY_SIZE(uft_aarch64_reg_table), ARRAY_SIZE(uft_i386_reg_table), ARRAY_SIZE(uft_riscv64_reg_table), }; /* number of integer registers */ static const int arch_reg_int_sizes[] = { 0, 6, 4, 8, 2, 8, }; /* returns uftrace register number for the architecture */ int arch_register_number(enum uftrace_cpu_arch arch, char *reg_name) { unsigned i; const struct uftrace_reg_table *table; ASSERT(arch < ARRAY_SIZE(arch_reg_tables)); table = arch_reg_tables[arch]; for (i = 0; i < arch_reg_sizes[arch]; i++) { if (!strcasecmp(reg_name, table[i].name)) return table[i].reg; } return -1; } /* return uftrace register number at the given index (for argspec) */ int arch_register_at(enum uftrace_cpu_arch arch, bool integer, int idx) { int int_regs; const struct uftrace_reg_table *table; ASSERT(arch < ARRAY_SIZE(arch_reg_tables)); int_regs = arch_reg_int_sizes[arch]; table = arch_reg_tables[arch]; if (idx < 0) return -1; if (integer && idx >= int_regs) return -1; if (!integer) idx += int_regs; if (idx >= (int)arch_reg_sizes[arch]) return -1; return table[idx].reg; } /* returns argspec register index from uftrace register number */ int arch_register_index(enum uftrace_cpu_arch arch, int reg) { unsigned i; const struct uftrace_reg_table *table; ASSERT(arch < ARRAY_SIZE(arch_reg_tables)); table = arch_reg_tables[arch]; for (i = 0; i < arch_reg_sizes[arch]; i++) { if (table[i].reg != reg) continue; if (i >= (unsigned)arch_reg_int_sizes[arch]) i -= arch_reg_int_sizes[arch]; return i; } return -1; } const char *arch_register_argspec_name(enum uftrace_cpu_arch arch, bool integer, int idx) { const struct uftrace_reg_table *table; ASSERT(arch < ARRAY_SIZE(arch_reg_tables)); table = arch_reg_tables[arch]; if (!integer) idx += arch_reg_int_sizes[arch]; if ((unsigned)idx >= arch_reg_sizes[arch]) return NULL; return table[idx].name; } #ifdef HAVE_LIBDW #include static const struct uftrace_reg_table uft_x86_64_dwarf_table[] = { /* support registers used for arguments */ { "rdx", DW_OP_reg1, }, { "rcx", DW_OP_reg2, }, { "rsi", DW_OP_reg4, }, { "rdi", DW_OP_reg5, }, { "r8", DW_OP_reg8, }, { "r9", DW_OP_reg9, }, { "xmm0", DW_OP_reg17, }, { "xmm1", DW_OP_reg18, }, { "xmm2", DW_OP_reg19, }, { "xmm3", DW_OP_reg20, }, { "xmm4", DW_OP_reg21, }, { "xmm5", DW_OP_reg22, }, { "xmm6", DW_OP_reg23, }, { "xmm7", DW_OP_reg24, }, }; #define ARM_REG_VFPv3_BASE 256 static const struct uftrace_reg_table uft_arm_dwarf_table[] = { /* support registers used for arguments */ { "r0", DW_OP_reg0, }, { "r1", DW_OP_reg1, }, { "r2", DW_OP_reg2, }, { "r3", DW_OP_reg3, }, { "d0", ARM_REG_VFPv3_BASE + 0, }, { "d1", ARM_REG_VFPv3_BASE + 1, }, { "d2", ARM_REG_VFPv3_BASE + 2, }, { "d3", ARM_REG_VFPv3_BASE + 3, }, { "d4", ARM_REG_VFPv3_BASE + 4, }, { "d5", ARM_REG_VFPv3_BASE + 5, }, { "d6", ARM_REG_VFPv3_BASE + 6, }, { "d7", ARM_REG_VFPv3_BASE + 7, }, }; #define AARCH64_REG_FP_BASE 64 static const struct uftrace_reg_table uft_aarch64_dwarf_table[] = { /* support registers used for arguments */ { "x0", DW_OP_reg0, }, { "x1", DW_OP_reg1, }, { "x2", DW_OP_reg2, }, { "x3", DW_OP_reg3, }, { "x4", DW_OP_reg4, }, { "x5", DW_OP_reg5, }, { "x6", DW_OP_reg6, }, { "x7", DW_OP_reg7, }, { "d0", AARCH64_REG_FP_BASE + 0, }, { "d1", AARCH64_REG_FP_BASE + 1, }, { "d2", AARCH64_REG_FP_BASE + 2, }, { "d3", AARCH64_REG_FP_BASE + 3, }, { "d4", AARCH64_REG_FP_BASE + 4, }, { "d5", AARCH64_REG_FP_BASE + 5, }, { "d6", AARCH64_REG_FP_BASE + 6, }, { "d7", AARCH64_REG_FP_BASE + 7, }, }; static const struct uftrace_reg_table uft_i386_dwarf_table[] = {}; #define RISCV64_REG_FP_BASE 32 static const struct uftrace_reg_table uft_riscv64_dwarf_table[] = { /* support registers used for arguments */ { "a0", DW_OP_reg10, }, { "a1", DW_OP_reg11, }, { "a2", DW_OP_reg12, }, { "a3", DW_OP_reg13, }, { "a4", DW_OP_reg14, }, { "a5", DW_OP_reg15, }, { "a6", DW_OP_reg16, }, { "a7", DW_OP_reg17, }, { "fa0", RISCV64_REG_FP_BASE + 0, }, { "fa1", RISCV64_REG_FP_BASE + 1, }, { "fa2", RISCV64_REG_FP_BASE + 2, }, { "fa3", RISCV64_REG_FP_BASE + 3, }, { "fa4", RISCV64_REG_FP_BASE + 4, }, { "fa5", RISCV64_REG_FP_BASE + 5, }, { "fa6", RISCV64_REG_FP_BASE + 6, }, { "fa7", RISCV64_REG_FP_BASE + 7, }, }; static const struct uftrace_reg_table *arch_dwarf_tables[] = { NULL, uft_x86_64_dwarf_table, uft_arm_dwarf_table, uft_aarch64_dwarf_table, uft_i386_dwarf_table, uft_riscv64_dwarf_table, }; static const size_t arch_dwarf_sizes[] = { 0, ARRAY_SIZE(uft_x86_64_dwarf_table), ARRAY_SIZE(uft_arm_dwarf_table), ARRAY_SIZE(uft_aarch64_dwarf_table), ARRAY_SIZE(uft_i386_dwarf_table), ARRAY_SIZE(uft_riscv64_dwarf_table), }; const char *arch_register_dwarf_name(enum uftrace_cpu_arch arch, int dwarf_reg) { unsigned i; const struct uftrace_reg_table *table; ASSERT(arch < ARRAY_SIZE(arch_dwarf_tables)); table = arch_dwarf_tables[arch]; for (i = 0; i < arch_dwarf_sizes[arch]; i++) { if (dwarf_reg == table[i].reg) return table[i].name; } return "invalid register"; } #endif /* HAVE_LIBDW */ uftrace-0.15.2/utils/report.c000066400000000000000000001241071455365734300160730ustar00rootroot00000000000000#include #include #include #include "uftrace.h" #include "utils/field.h" #include "utils/fstack.h" #include "utils/report.h" #include "utils/utils.h" static void init_time_stat(struct report_time_stat *ts) { ts->min = -1ULL; } static void update_time_stat(struct report_time_stat *ts, uint64_t time_ns, bool recursive) { if (recursive) ts->rec += time_ns; else ts->sum += time_ns; if (ts->min > time_ns) ts->min = time_ns; if (ts->max < time_ns) ts->max = time_ns; } static void finish_time_stat(struct report_time_stat *ts, unsigned long call) { ts->avg = (ts->sum + ts->rec) / call; } static struct uftrace_report_node *find_or_create_node(struct rb_root *root, const char *name, struct uftrace_report_node *node) { struct uftrace_report_node *iter; struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; while (*p) { int cmp; parent = *p; iter = rb_entry(parent, typeof(*iter), name_link); cmp = strcmp(iter->name, name); if (cmp == 0) return iter; if (cmp > 0) p = &parent->rb_left; else p = &parent->rb_right; } if (node == NULL) return NULL; node->name = xstrdup(name); node->loc = NULL; init_time_stat(&node->total); init_time_stat(&node->self); rb_link_node(&node->name_link, parent, p); rb_insert_color(&node->name_link, root); return node; } struct uftrace_report_node *report_find_node(struct rb_root *root, const char *name) { return find_or_create_node(root, name, NULL); } /* NOTE: actual allocation will be done by caller */ void report_add_node(struct rb_root *root, const char *name, struct uftrace_report_node *node) { find_or_create_node(root, name, node); } void report_delete_node(struct rb_root *root, struct uftrace_report_node *node) { rb_erase(&node->name_link, root); free(node->name); free(node); } void report_update_node(struct uftrace_report_node *node, struct uftrace_task_reader *task, struct uftrace_dbg_loc *loc) { struct uftrace_fstack *fstack; uint64_t total_time; uint64_t self_time; bool recursive = false; int i; fstack = fstack_get(task, task->stack_count); if (fstack == NULL) return; for (i = 0; i < task->stack_count; i++) { struct uftrace_fstack *check = fstack_get(task, i); if (check == NULL) break; if (check->addr == fstack->addr) { recursive = true; break; } } total_time = fstack->total_time; self_time = fstack->total_time - fstack->child_time; update_time_stat(&node->total, total_time, recursive); update_time_stat(&node->self, self_time, false); node->call++; node->loc = loc; if (task->func != NULL) node->size = task->func->size; } void report_calc_avg(struct rb_root *root) { struct uftrace_report_node *node; struct rb_node *n = rb_first(root); while (n) { node = rb_entry(n, typeof(*node), name_link); finish_time_stat(&node->total, node->call); finish_time_stat(&node->self, node->call); n = rb_next(n); } } /* sort key support */ struct sort_key { const char *name; int (*cmp)(struct uftrace_report_node *a, struct uftrace_report_node *b); struct list_head list; }; #define SORT_KEY(_name, _field) \ static int cmp_##_name(struct uftrace_report_node *a, struct uftrace_report_node *b) \ { \ if (a->_field == b->_field) \ return 0; \ return a->_field > b->_field ? 1 : -1; \ } \ static struct sort_key sort_##_name = { \ .name = #_name, \ .cmp = cmp_##_name, \ .list = LIST_HEAD_INIT(sort_##_name.list), \ } SORT_KEY(total, total.sum); SORT_KEY(total_avg, total.avg); SORT_KEY(total_min, total.min); SORT_KEY(total_max, total.max); SORT_KEY(self, self.sum); SORT_KEY(self_avg, self.avg); SORT_KEY(self_min, self.min); SORT_KEY(self_max, self.max); SORT_KEY(call, call); SORT_KEY(size, size); static int cmp_func(struct uftrace_report_node *a, struct uftrace_report_node *b) { return strcmp(b->name, a->name); } static struct sort_key sort_func = { .name = "func", .cmp = cmp_func, .list = LIST_HEAD_INIT(sort_func.list), }; static struct sort_key *all_sort_keys[] = { &sort_total, &sort_total_avg, &sort_total_min, &sort_total_max, &sort_self, &sort_self_avg, &sort_self_min, &sort_self_max, &sort_call, &sort_func, &sort_size, }; /* list of used sort keys */ static LIST_HEAD(sort_keys); char *convert_sort_keys(char *sort_keys, enum avg_mode avg_mode) { 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) { char *s; s = new_keys = xstrdup(sort_keys); while (*s) { if (*s == '-') *s = '_'; s++; } return new_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 report_setup_sort(const char *key_str) { struct strv keys = STRV_INIT; char *k; unsigned i; int j; int count = 0; INIT_LIST_HEAD(&sort_keys); strv_split(&keys, key_str, ","); strv_for_each(&keys, k, j) { for (i = 0; i < ARRAY_SIZE(all_sort_keys); i++) { struct sort_key *sort_key = all_sort_keys[i]; if (strcmp(k, sort_key->name)) continue; list_add_tail(&sort_key->list, &sort_keys); count++; break; } if (i == ARRAY_SIZE(all_sort_keys)) { count = -1; break; } } strv_free(&keys); return count; } static int cmp_node(struct uftrace_report_node *a, struct uftrace_report_node *b) { int ret; struct sort_key *key; list_for_each_entry(key, &sort_keys, list) { ret = key->cmp(a, b); if (ret) return ret; } return 0; } static void insert_node(struct rb_root *root, struct uftrace_report_node *node) { struct uftrace_report_node *iter; struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; while (*p) { parent = *p; iter = rb_entry(parent, typeof(*iter), sort_link); if (cmp_node(iter, node) < 0) p = &parent->rb_left; else p = &parent->rb_right; } rb_link_node(&node->sort_link, parent, p); rb_insert_color(&node->sort_link, root); } void report_sort_nodes(struct rb_root *name_root, struct rb_root *sort_root) { struct rb_node *n = rb_first(name_root); *sort_root = RB_ROOT; while (n && !uftrace_done) { struct uftrace_report_node *node; /* keep node in the name tree */ node = rb_entry(n, typeof(*node), name_link); insert_node(sort_root, node); n = rb_next(n); } } /* diff support */ struct uftrace_diff_policy diff_policy = { .absolute = true, }; struct diff_key { const char *name; int (*cmp)(struct uftrace_report_node *a, struct uftrace_report_node *b, int sort_column); struct list_head list; }; #define DIFF_KEY(_name, _field) \ static inline int cmp_field_##_name(struct uftrace_report_node *a, \ struct uftrace_report_node *b) \ { \ if (a->_field == b->_field) \ return 0; \ return a->_field > b->_field ? 1 : -1; \ } \ static inline int _cmp_diff_##_name(struct uftrace_report_node *a, \ struct uftrace_report_node *b) \ { \ int64_t diff_a = a->pair->_field - a->_field; \ int64_t diff_b = b->pair->_field - b->_field; \ \ if (diff_a == diff_b) \ return 0; \ \ if (diff_policy.absolute) { \ diff_a = (diff_a > 0) ? diff_a : -diff_a; \ diff_b = (diff_b > 0) ? diff_b : -diff_b; \ } \ return diff_a > diff_b ? 1 : -1; \ } \ static inline int cmp_pcnt_##_name(struct uftrace_report_node *a, \ struct uftrace_report_node *b) \ { \ int64_t diff_a = a->pair->_field - a->_field; \ int64_t diff_b = b->pair->_field - b->_field; \ double pcnt_a = 0; \ double pcnt_b = 0; \ \ if (a->_field) \ pcnt_a = 100.0 * (int64_t)diff_a / a->_field; \ if (b->_field) \ pcnt_b = 100.0 * (int64_t)diff_b / b->_field; \ \ if (pcnt_a == pcnt_b) \ return 0; \ \ if (diff_policy.absolute) { \ pcnt_a = (pcnt_a > 0) ? pcnt_a : -pcnt_a; \ pcnt_b = (pcnt_b > 0) ? pcnt_b : -pcnt_b; \ } \ return pcnt_a > pcnt_b ? 1 : -1; \ } \ static int cmp_diff_##_name(struct uftrace_report_node *a, struct uftrace_report_node *b, \ int column) \ { \ if (column != 2) \ return cmp_field_##_name(a, b); \ \ if (diff_policy.percent) \ return cmp_pcnt_##_name(a, b); \ \ return _cmp_diff_##_name(a, b); \ } \ static struct diff_key sort_diff_##_name = { .name = #_name, \ .cmp = cmp_diff_##_name, \ .list = LIST_HEAD_INIT( \ sort_diff_##_name.list) } DIFF_KEY(total, total.sum); DIFF_KEY(total_avg, total.avg); DIFF_KEY(total_min, total.min); DIFF_KEY(total_max, total.max); DIFF_KEY(self, self.sum); DIFF_KEY(self_avg, self.avg); DIFF_KEY(self_min, self.min); DIFF_KEY(self_max, self.max); DIFF_KEY(call, call); DIFF_KEY(size, size); static int cmp_diff_func(struct uftrace_report_node *a, struct uftrace_report_node *b, int column) { return strcmp(b->name, a->name); } static struct diff_key sort_diff_func = { .name = "func", .cmp = cmp_diff_func, .list = LIST_HEAD_INIT(sort_diff_func.list), }; static struct diff_key *all_diff_keys[] = { &sort_diff_total, &sort_diff_total_avg, &sort_diff_total_min, &sort_diff_total_max, &sort_diff_self, &sort_diff_self_avg, &sort_diff_self_min, &sort_diff_self_max, &sort_diff_call, &sort_diff_func, &sort_diff_size }; /* list of used sort keys for diff */ static LIST_HEAD(diff_keys); static struct uftrace_report_node dummy_node; int report_setup_diff(const char *key_str) { struct strv keys = STRV_INIT; char *k; unsigned i; int j; int count = 0; INIT_LIST_HEAD(&diff_keys); strv_split(&keys, key_str, ","); strv_for_each(&keys, k, j) { for (i = 0; i < ARRAY_SIZE(all_diff_keys); i++) { struct diff_key *sort_key = all_diff_keys[i]; if (strcmp(k, sort_key->name)) continue; list_add_tail(&sort_key->list, &diff_keys); count++; break; } if (i == ARRAY_SIZE(all_diff_keys)) { count = -1; break; } } strv_free(&keys); return count; } static int cmp_diff(struct uftrace_report_node *a, struct uftrace_report_node *b, int diff_column) { int ret; struct diff_key *key; list_for_each_entry(key, &diff_keys, list) { ret = key->cmp(a, b, diff_column); if (ret) return ret; } return 0; } static void insert_diff(struct rb_root *root, struct uftrace_report_node *node, int diff_column) { struct uftrace_report_node *iter; struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; while (*p) { parent = *p; iter = rb_entry(parent, typeof(*iter), sort_link); if (cmp_diff(iter, node, diff_column) < 0) p = &parent->rb_left; else p = &parent->rb_right; } rb_link_node(&node->sort_link, parent, p); rb_insert_color(&node->sort_link, root); } void report_diff_nodes(struct rb_root *orig_root, struct rb_root *pair_root, struct rb_root *diff_root, int diff_column) { struct rb_node *n = rb_first(orig_root); *diff_root = RB_ROOT; while (n && !uftrace_done) { struct uftrace_report_node *iter, *pair, *node; iter = rb_entry(n, typeof(*iter), name_link); pair = report_find_node(pair_root, iter->name); if (pair == NULL) pair = &dummy_node; /* node->name is swallow-copied, do not free */ node = xzalloc(sizeof(*node)); memcpy(node, iter, sizeof(*node)); node->pair = pair; /* mark used pair */ pair->pair = node; insert_diff(diff_root, node, diff_column); n = rb_next(n); } /* add non-used pair nodes */ n = rb_first(pair_root); while (n && !uftrace_done) { struct uftrace_report_node *iter, *node; iter = rb_entry(n, typeof(*iter), name_link); if (iter->pair == NULL) { /* node->name is swallow-copied, do not free */ node = xzalloc(sizeof(*node)); node->name = iter->name; node->pair = iter; insert_diff(diff_root, node, diff_column); } n = rb_next(n); } } void destroy_diff_nodes(struct rb_root *orig_root, struct rb_root *pair_root) { struct rb_node *n; struct uftrace_report_node *iter; n = rb_first(orig_root); while (n) { iter = rb_entry(n, typeof(*iter), name_link); n = rb_next(n); /* name is already freed in print_and_delete */ rb_erase(&iter->name_link, orig_root); free(iter); } n = rb_first(pair_root); while (n) { iter = rb_entry(n, typeof(*iter), name_link); n = rb_next(n); rb_erase(&iter->name_link, pair_root); /* if it has a pair, only base name was freed */ if (iter->pair) free(iter->name); free(iter); } } void apply_diff_policy(char *policy) { struct strv strv = STRV_INIT; char *p; int i; strv_split(&strv, policy, ","); strv_for_each(&strv, p, i) { bool on = true; if (!strncmp(p, "no-", 3)) { on = false; p += 3; } if (!strncmp(p, "abs", 3)) diff_policy.absolute = on; else if (!strncmp(p, "percent", 7)) diff_policy.percent = on; else if (!strncmp(p, "full", 4)) diff_policy.full = true; else if (!strncmp(p, "compact", 7)) diff_policy.full = false; } strv_free(&strv); } /* task sort key support */ struct sort_task_key { const char *name; int (*cmp)(struct uftrace_data *handle, struct uftrace_report_node *a, struct uftrace_report_node *b); struct list_head list; }; #define TASK_KEY(_name, _field) \ static int task_cmp_##_name(struct uftrace_data *handle, struct uftrace_report_node *a, \ struct uftrace_report_node *b) \ { \ if (a->_field == b->_field) \ return 0; \ return a->_field > b->_field ? 1 : -1; \ } \ static struct sort_task_key task_##_name = { \ .name = #_name, \ .cmp = task_cmp_##_name, \ .list = LIST_HEAD_INIT(task_##_name.list), \ } TASK_KEY(total, total.sum); TASK_KEY(self, self.sum); TASK_KEY(func, call); static int cmp_task_tid(struct uftrace_data *handle, struct uftrace_report_node *a, struct uftrace_report_node *b) { return strcmp(b->name, a->name); } static struct sort_task_key task_tid = { .name = "tid", .cmp = cmp_task_tid, .list = LIST_HEAD_INIT(task_tid.list), }; static int cmp_task_name(struct uftrace_data *handle, struct uftrace_report_node *a, struct uftrace_report_node *b) { int tid_a = strtol(a->name, NULL, 0); int tid_b = strtol(b->name, NULL, 0); struct uftrace_task *task_a = find_task(&handle->sessions, tid_a); struct uftrace_task *task_b = find_task(&handle->sessions, tid_b); if (task_a == NULL || task_b == NULL) return !task_a ? (!task_b ? 0 : 1) : -1; return strcmp(task_b->comm, task_a->comm); } static struct sort_task_key task_name = { .name = "name", .cmp = cmp_task_name, .list = LIST_HEAD_INIT(task_name.list), }; static struct sort_task_key *all_task_keys[] = { &task_total, &task_self, &task_tid, &task_func, &task_name, }; /* list of used sort keys for diff */ static LIST_HEAD(task_keys); int report_setup_task(const char *key_str) { struct strv keys = STRV_INIT; char *k; unsigned i; int j; int count = 0; INIT_LIST_HEAD(&task_keys); strv_split(&keys, key_str, ","); strv_for_each(&keys, k, j) { for (i = 0; i < ARRAY_SIZE(all_task_keys); i++) { struct sort_task_key *sort_key = all_task_keys[i]; if (strcmp(k, sort_key->name)) continue; list_add_tail(&sort_key->list, &task_keys); count++; break; } if (i == ARRAY_SIZE(all_task_keys)) { count = -1; break; } } strv_free(&keys); return count; } static int cmp_task(struct uftrace_data *handle, struct uftrace_report_node *a, struct uftrace_report_node *b) { int ret; struct sort_task_key *key; list_for_each_entry(key, &task_keys, list) { ret = key->cmp(handle, a, b); if (ret) return ret; } return 0; } static void insert_task(struct uftrace_data *handle, struct rb_root *root, struct uftrace_report_node *node) { struct uftrace_report_node *iter; struct rb_node *parent = NULL; struct rb_node **p = &root->rb_node; while (*p) { parent = *p; iter = rb_entry(parent, typeof(*iter), sort_link); if (cmp_task(handle, iter, node) < 0) p = &parent->rb_left; else p = &parent->rb_right; } rb_link_node(&node->sort_link, parent, p); rb_insert_color(&node->sort_link, root); } void report_sort_tasks(struct uftrace_data *handle, struct rb_root *name_root, struct rb_root *sort_root) { struct rb_node *n = rb_first(name_root); *sort_root = RB_ROOT; while (n && !uftrace_done) { struct uftrace_report_node *node; /* keep node in the name tree */ node = rb_entry(n, typeof(*node), name_link); insert_task(handle, sort_root, node); n = rb_next(n); } } #define FIELD_STRUCT(_id, _name, _func, _header, _length) \ static struct display_field field_##_func = { .id = _id, \ .name = #_name, \ .header = _header, \ .length = _length, \ .print = print_##_func, \ .list = LIST_HEAD_INIT( \ field_##_func.list) }; #define FIELD_TIME(_id, _name, _field, _func, _header) \ static void print_##_func(struct field_data *fd) \ { \ struct uftrace_report_node *node = fd->arg; \ print_time_unit(node->_field); \ } \ FIELD_STRUCT(_id, _name, _func, _header, 10) #define FIELD_UINT(_id, _name, _field, _func, _header) \ static void print_##_func(struct field_data *fd) \ { \ struct uftrace_report_node *node = fd->arg; \ pr_out("%10" PRIu64 "", node->_field); \ } \ FIELD_STRUCT(_id, _name, _func, _header, 10) #define FIELD_TIME_DIFF(_id, _name, _field, _func, _header) \ static void print_##_func##_diff(struct field_data *fd) \ { \ struct uftrace_report_node *node = fd->arg; \ struct uftrace_report_node *pair = node->pair; \ if (diff_policy.percent) { \ pr_out(" "); \ print_diff_percent(node->_field, pair->_field); \ } \ else { \ print_diff_time_unit(node->_field, pair->_field); \ } \ } \ FIELD_STRUCT(_id, _name, _func##_diff, _header, 11) #define FIELD_UINT_DIFF(_id, _name, _field, _func, _header) \ static void print_##_func##_diff(struct field_data *fd) \ { \ struct uftrace_report_node *node = fd->arg; \ struct uftrace_report_node *pair = node->pair; \ pr_out(" "); \ print_diff_count(node->_field, pair->_field); \ } \ FIELD_STRUCT(_id, _name, _func##_diff, _header, 11) #define FIELD_TIME_DIFF_FULL(_id, _name, _field, _func, _header) \ static void print_##_func##_diff_full(struct field_data *fd) \ { \ struct uftrace_report_node *node = fd->arg; \ struct uftrace_report_node *pair = node->pair; \ print_time_or_dash(node->_field); \ pr_out(" "); \ print_time_or_dash(pair->_field); \ pr_out(" "); \ print_diff_time_unit(node->_field, pair->_field); \ } \ FIELD_STRUCT(_id, _name, _func##_diff_full, _header, 35) #define FIELD_UINT_DIFF_FULL(_id, _name, _field, _func, _header) \ static void print_##_func(struct field_data *fd) \ { \ struct uftrace_report_node *node = fd->arg; \ struct uftrace_report_node *pair = node->pair; \ pr_out(" %9" PRIu64 " %9" PRIu64, node->_field, pair->_field); \ pr_out(" "); \ print_diff_count(node->_field, pair->_field); \ } \ FIELD_STRUCT(_id, _name, _func, _header, 32) #define FIELD_TIME_DIFF_FULL_PCT(_id, _name, _field, _func, _header) \ static void print_##_func##_diff_full_percent(struct field_data *fd) \ { \ struct uftrace_report_node *node = fd->arg; \ struct uftrace_report_node *pair = node->pair; \ print_time_or_dash(node->_field); \ pr_out(" "); \ print_time_or_dash(pair->_field); \ pr_out(" "); \ print_diff_percent(node->_field, pair->_field); \ } \ FIELD_STRUCT(_id, _name, _func##_diff_full_percent, _header, 32) #define FIELD_TID(_id, _name, _func, _header) \ static void print_##_func(struct field_data *fd) \ { \ struct uftrace_report_node *node = fd->arg; \ pr_out("%6d", strtol(node->name, NULL, 10)); \ } \ FIELD_STRUCT(_id, _name, _func, _header, 6) #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); } /* clang-format off */ FIELD_TIME(REPORT_F_TOTAL_TIME, total, total.sum, total, "Total time"); FIELD_TIME(REPORT_F_TOTAL_TIME_AVG, total-avg, total.avg, total_avg, "Total avg"); FIELD_TIME(REPORT_F_TOTAL_TIME_MIN, total-min, total.min, total_min, "Total min"); FIELD_TIME(REPORT_F_TOTAL_TIME_MAX, total-max, total.max, total_max, "Total max"); FIELD_TIME(REPORT_F_SELF_TIME, self, self.sum, self, "Self time"); FIELD_TIME(REPORT_F_SELF_TIME_AVG, self-avg, self.avg, self_avg, "Self avg"); FIELD_TIME(REPORT_F_SELF_TIME_MIN, self-min, self.min, self_min, "Self min"); FIELD_TIME(REPORT_F_SELF_TIME_MAX, self-max, self.max, self_max, "Self max"); FIELD_UINT(REPORT_F_CALL, call, call, call, "Calls"); FIELD_UINT(REPORT_F_SIZE, size, size, size, "Size"); FIELD_TIME_DIFF(REPORT_F_TOTAL_TIME, total, total.sum, total, "Total time"); FIELD_TIME_DIFF(REPORT_F_TOTAL_TIME_AVG, total-avg, total.avg, total_avg, "Total avg"); FIELD_TIME_DIFF(REPORT_F_TOTAL_TIME_MIN, total-min, total.min, total_min, "Total min"); FIELD_TIME_DIFF(REPORT_F_TOTAL_TIME_MAX, total-max, total.max, total_max, "Total max"); FIELD_TIME_DIFF(REPORT_F_SELF_TIME, self, self.sum, self, "Self time"); FIELD_TIME_DIFF(REPORT_F_SELF_TIME_AVG, self-avg, self.avg, self_avg, "Self avg"); FIELD_TIME_DIFF(REPORT_F_SELF_TIME_MIN, self-min, self.min, self_min, "Self min"); FIELD_TIME_DIFF(REPORT_F_SELF_TIME_MAX, self-max, self.max, self_max, "Self max"); FIELD_UINT_DIFF(REPORT_F_CALL, call, call, call, "Calls"); FIELD_UINT_DIFF(REPORT_F_SIZE, size, size, size, "Size"); FIELD_TIME_DIFF_FULL(REPORT_F_TOTAL_TIME, total, total.sum, total, "Total time (diff)"); FIELD_TIME_DIFF_FULL(REPORT_F_TOTAL_TIME_AVG, total-avg, total.avg, total_avg, "Total avg (diff)"); FIELD_TIME_DIFF_FULL(REPORT_F_TOTAL_TIME_MIN, total-min, total.min, total_min, "Total min (diff)"); FIELD_TIME_DIFF_FULL(REPORT_F_TOTAL_TIME_MAX, total-max, total.max, total_max, "Total max (diff)"); FIELD_TIME_DIFF_FULL(REPORT_F_SELF_TIME, self, self.sum, self, "Self time (diff)"); FIELD_TIME_DIFF_FULL(REPORT_F_SELF_TIME_AVG, self-avg, self.avg, self_avg, "Self avg (diff)"); FIELD_TIME_DIFF_FULL(REPORT_F_SELF_TIME_MIN, self-min, self.min, self_min, "Self min (diff)"); FIELD_TIME_DIFF_FULL(REPORT_F_SELF_TIME_MAX, self-max, self.max, self_max, "Self min (diff)"); FIELD_UINT_DIFF_FULL(REPORT_F_CALL, call, call, call_diff_full, "Calls (diff)"); FIELD_UINT_DIFF_FULL(REPORT_F_SIZE, size, size, size_diff_full, "Size (diff)"); FIELD_TIME_DIFF_FULL_PCT(REPORT_F_TOTAL_TIME, total, total.sum, total, "Total time (diff)"); FIELD_TIME_DIFF_FULL_PCT(REPORT_F_TOTAL_TIME_AVG, total-avg, total.avg, total_avg, "Total avg (diff)"); FIELD_TIME_DIFF_FULL_PCT(REPORT_F_TOTAL_TIME_MIN, total-min, total.min, total_min, "Total min (diff)"); FIELD_TIME_DIFF_FULL_PCT(REPORT_F_TOTAL_TIME_MAX, total-max, total.max, total_max, "Total max (diff)"); FIELD_TIME_DIFF_FULL_PCT(REPORT_F_SELF_TIME, self, self.sum, self, "Self time (diff)"); FIELD_TIME_DIFF_FULL_PCT(REPORT_F_SELF_TIME_AVG, self-avg, self.avg, self_avg, "Self avg (diff)"); FIELD_TIME_DIFF_FULL_PCT(REPORT_F_SELF_TIME_MIN, self-min, self.min, self_min, "Self min (diff)"); FIELD_TIME_DIFF_FULL_PCT(REPORT_F_SELF_TIME_MAX, self-max, self.max, self_max, "Self min (diff)"); FIELD_UINT_DIFF_FULL(REPORT_F_CALL, call, call, call_diff_full_percent, "Calls (diff)"); FIELD_UINT_DIFF_FULL(REPORT_F_SIZE, size, size, size_diff_full_percent, "Size (diff)"); FIELD_TIME(REPORT_F_TASK_TOTAL_TIME, total, total.sum, task_total, "Total time"); FIELD_TIME(REPORT_F_TASK_SELF_TIME, self, self.sum, task_self, "Self time"); FIELD_TID(REPORT_F_TASK_TID, tid, task_tid, "TID"); FIELD_UINT(REPORT_F_TASK_NR_FUNC, func, call, task_nr_func, "Num funcs"); /* clang-format on */ /* index of this table should be matched to display_field_id */ static struct display_field *field_table[] = { &field_total, &field_total_avg, &field_total_min, &field_total_max, &field_self, &field_self_avg, &field_self_min, &field_self_max, &field_call, &field_size, }; /* index of this table should be matched to display_field_id */ static struct display_field *field_diff_table[] = { &field_total_diff, &field_total_avg_diff, &field_total_min_diff, &field_total_max_diff, &field_self_diff, &field_self_avg_diff, &field_self_min_diff, &field_self_max_diff, &field_call_diff, &field_size_diff, }; /* index of this table should be matched to display_field_id */ static struct display_field *field_diff_full_table[] = { &field_total_diff_full, &field_total_avg_diff_full, &field_total_min_diff_full, &field_total_max_diff_full, &field_self_diff_full, &field_self_avg_diff_full, &field_self_min_diff_full, &field_self_max_diff_full, &field_call_diff_full, &field_size_diff_full, }; /* index of this table should be matched to display_field_id */ static struct display_field *field_diff_full_percent_table[] = { &field_total_diff_full_percent, &field_total_avg_diff_full_percent, &field_total_min_diff_full_percent, &field_total_max_diff_full_percent, &field_self_diff_full_percent, &field_self_avg_diff_full_percent, &field_self_min_diff_full_percent, &field_self_max_diff_full_percent, &field_call_diff_full_percent, &field_size_diff_full_percent, }; /* index of this table should be matched to display_field_id */ static struct display_field *field_task_table[] = { &field_task_total, &field_task_self, &field_task_tid, &field_task_nr_func, }; static void setup_default_field(struct list_head *fields, struct uftrace_opts *opts, struct display_field *p_field_table[]) { add_field(fields, p_field_table[REPORT_F_TOTAL_TIME]); add_field(fields, p_field_table[REPORT_F_SELF_TIME]); add_field(fields, p_field_table[REPORT_F_CALL]); } static void setup_avg_total_field(struct list_head *fields, struct uftrace_opts *opts, struct display_field *p_field_table[]) { add_field(fields, p_field_table[REPORT_F_TOTAL_TIME_AVG]); add_field(fields, p_field_table[REPORT_F_TOTAL_TIME_MIN]); add_field(fields, p_field_table[REPORT_F_TOTAL_TIME_MAX]); } static void setup_avg_self_field(struct list_head *fields, struct uftrace_opts *opts, struct display_field *p_field_table[]) { add_field(fields, p_field_table[REPORT_F_SELF_TIME_AVG]); add_field(fields, p_field_table[REPORT_F_SELF_TIME_MIN]); add_field(fields, p_field_table[REPORT_F_SELF_TIME_MAX]); } static void setup_default_task_field(struct list_head *fields, struct uftrace_opts *opts, struct display_field *p_field_table[]) { add_field(fields, p_field_table[REPORT_F_TASK_TOTAL_TIME]); add_field(fields, p_field_table[REPORT_F_TASK_SELF_TIME]); add_field(fields, p_field_table[REPORT_F_TASK_TID]); add_field(fields, p_field_table[REPORT_F_TASK_NR_FUNC]); } void setup_report_field(struct list_head *output_fields, struct uftrace_opts *opts, enum avg_mode avg_mode) { struct display_field **f_table; int table_size; setup_default_field_t fn[] = { &setup_default_field, &setup_avg_total_field, &setup_avg_self_field }; if (opts->show_task) { setup_field(output_fields, opts, setup_default_task_field, field_task_table, ARRAY_SIZE(field_task_table)); return; } if (opts->diff) { if (opts->diff_policy && diff_policy.full) { if (diff_policy.percent) { f_table = field_diff_full_percent_table; table_size = ARRAY_SIZE(field_diff_full_percent_table); } else { f_table = field_diff_full_table; table_size = ARRAY_SIZE(field_diff_full_table); } } else { f_table = field_diff_table; table_size = ARRAY_SIZE(field_diff_table); } } else { f_table = field_table; table_size = ARRAY_SIZE(field_table); } setup_field(output_fields, opts, fn[avg_mode], f_table, table_size); } #ifdef UNIT_TEST #define TEST_NODES 3 TEST_CASE(report_find) { struct rb_root root = RB_ROOT; struct rb_node *rbnode; struct uftrace_report_node *node; const char *test_name[TEST_NODES] = { "abc", "foo", "bar" }; const char *name_sort[TEST_NODES] = { "abc", "bar", "foo" }; int i; pr_dbg("add report node in an arbitrary order\n"); for (i = 0; i < TEST_NODES; i++) { node = xzalloc(sizeof(*node)); report_add_node(&root, test_name[i], node); } pr_dbg("find report node by name\n"); for (i = 0; i < TEST_NODES; i++) { node = report_find_node(&root, test_name[i]); TEST_NE(node, NULL); TEST_STREQ(node->name, test_name[i]); } pr_dbg("check the tree was sorted by name\n"); i = 0; while (!RB_EMPTY_ROOT(&root)) { rbnode = rb_first(&root); node = rb_entry(rbnode, typeof(*node), name_link); TEST_STREQ(node->name, name_sort[i++]); report_delete_node(&root, node); } TEST_EQ(i, 3); return TEST_OK; } TEST_CASE(report_sort) { struct rb_root name_tree = RB_ROOT; struct rb_root sort_tree = RB_ROOT; struct rb_node *rbnode; struct uftrace_report_node *node; static struct uftrace_fstack fstack[TEST_NODES]; struct uftrace_data handle = { .hdr = { .max_stack = TEST_NODES, }, .nr_tasks = 1, }; struct uftrace_task_reader task = { .h = &handle, .func_stack = fstack, }; int i; const char *test_name[] = { "abc", "foo", "bar" }; uint64_t total_times[TEST_NODES] = { 1000, 600, 2300, }; uint64_t child_times[TEST_NODES] = { 700, 0, 2100, }; int total_order[TEST_NODES] = { 2, 0, 1 }; int self_order[TEST_NODES] = { 1, 0, 2 }; pr_dbg("setup fstack manually\n"); for (i = 0; i < TEST_NODES; i++) { fstack[i].addr = i; fstack[i].total_time = total_times[i]; fstack[i].child_time = child_times[i]; } for (i = 0; i < TEST_NODES; i++) { node = xzalloc(sizeof(*node)); report_add_node(&name_tree, test_name[i], node); report_update_node(node, &task, NULL); task.stack_count++; } report_calc_avg(&name_tree); TEST_LT(report_setup_sort("foobar"), 0); TEST_EQ(report_setup_sort("total"), 1); report_sort_nodes(&name_tree, &sort_tree); pr_dbg("sort report result with: total\n"); i = 0; rbnode = rb_first(&sort_tree); while (rbnode != NULL) { node = rb_entry(rbnode, typeof(*node), sort_link); TEST_STREQ(node->name, test_name[total_order[i]]); TEST_EQ(node->total.sum, total_times[total_order[i]]); TEST_EQ(node->call, 1); pr_dbg("[%d] %s: %5" PRIu64 ", %" PRIu64 "\n", i, node->name, node->total.sum, node->call); rbnode = rb_next(rbnode); i++; } TEST_EQ(report_setup_sort("call,self_avg"), 2); report_sort_nodes(&name_tree, &sort_tree); pr_dbg("sort report result with: call, self_avg\n"); i = 0; rbnode = rb_first(&sort_tree); while (rbnode != NULL) { int idx = self_order[i]; uint64_t self_time = total_times[idx] - child_times[idx]; node = rb_entry(rbnode, typeof(*node), sort_link); TEST_STREQ(node->name, test_name[idx]); TEST_EQ(node->self.avg, self_time); TEST_EQ(node->self.min, self_time); TEST_EQ(node->self.max, self_time); pr_dbg("[%d] %s: %" PRIu64 ", %5" PRIu64 "\n", i, node->name, node->call, node->self.avg); rbnode = rb_next(rbnode); i++; } while (!RB_EMPTY_ROOT(&name_tree)) { rbnode = rb_first(&name_tree); node = rb_entry(rbnode, typeof(*node), name_link); rb_erase(&node->sort_link, &sort_tree); report_delete_node(&name_tree, node); } TEST_EQ(RB_EMPTY_ROOT(&sort_tree), true); return TEST_OK; } TEST_CASE(report_diff) { struct rb_root orig_tree = RB_ROOT; struct rb_root pair_tree = RB_ROOT; struct rb_root diff_tree = RB_ROOT; struct rb_node *rbnode; struct uftrace_report_node *node; int i; struct uftrace_data handle = { .hdr = { .max_stack = TEST_NODES, }, .nr_tasks = 2, }; struct uftrace_fstack orig_fstack[TEST_NODES]; struct uftrace_task_reader orig_task = { .h = &handle, .func_stack = orig_fstack, }; struct uftrace_fstack pair_fstack[TEST_NODES]; struct uftrace_task_reader pair_task = { .h = &handle, .func_stack = pair_fstack, }; const char *orig_name[] = { "abc", "foo", "bar" }; uint64_t orig_total_times[TEST_NODES] = { 100, 1600, 2300, }; uint64_t orig_child_times[TEST_NODES] = { 50, 800, 2100, }; const char *pair_name[] = { "xyz", "foo", "bar" }; uint64_t pair_total_times[TEST_NODES] = { 150, 2500, 2000, }; uint64_t pair_child_times[TEST_NODES] = { 70, 1800, 300, }; int diff_order[] = { 1, -1, 0, 2 }; int diff_total[] = { 900, 150, -100, -300 }; TEST_EQ(diff_policy.absolute, true); pr_dbg("diff policy = %s\n", "no-abs, compact, no-percent"); apply_diff_policy("no-abs,compact,no-percent"); TEST_EQ(diff_policy.absolute, false); TEST_EQ(diff_policy.full, false); TEST_EQ(diff_policy.percent, false); TEST_EQ(report_setup_diff("total,self"), 2); pr_dbg("report diff sorted by: total, self\n"); for (i = 0; i < TEST_NODES; i++) { orig_fstack[i].addr = i; orig_fstack[i].total_time = orig_total_times[i]; orig_fstack[i].child_time = orig_child_times[i]; node = xzalloc(sizeof(*node)); report_add_node(&orig_tree, orig_name[i], node); report_update_node(node, &orig_task, NULL); orig_task.stack_count++; } report_calc_avg(&orig_tree); for (i = 0; i < TEST_NODES; i++) { pair_fstack[i].addr = i; pair_fstack[i].total_time = pair_total_times[i]; pair_fstack[i].child_time = pair_child_times[i]; node = xzalloc(sizeof(*node)); report_add_node(&pair_tree, pair_name[i], node); report_update_node(node, &pair_task, NULL); pair_task.stack_count++; } report_calc_avg(&pair_tree); report_diff_nodes(&orig_tree, &pair_tree, &diff_tree, 2); TEST_EQ(RB_EMPTY_ROOT(&diff_tree), false); i = 0; rbnode = rb_first(&diff_tree); while (rbnode != NULL) { int idx = diff_order[i]; node = rb_entry(rbnode, typeof(*node), sort_link); rbnode = rb_next(rbnode); if (idx >= 0) TEST_STREQ(node->name, orig_name[idx]); else TEST_STREQ(node->name, pair_name[-idx - 1]); TEST_EQ(node->pair->total.sum - node->total.sum, diff_total[i]); pr_dbg("[%d] %s, %5" PRId64 "\n", i, node->name, diff_total[i]); rb_erase(&node->sort_link, &diff_tree); free(node->name); free(node); i++; } TEST_EQ(i, 4); destroy_diff_nodes(&orig_tree, &pair_tree); TEST_EQ(RB_EMPTY_ROOT(&orig_tree), true); TEST_EQ(RB_EMPTY_ROOT(&pair_tree), true); TEST_EQ(RB_EMPTY_ROOT(&diff_tree), true); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/report.h000066400000000000000000000040741455365734300161000ustar00rootroot00000000000000#ifndef UFTRACE_REPORT_H #define UFTRACE_REPORT_H #include #include #include "uftrace.h" #include "utils/rbtree.h" enum avg_mode { AVG_NONE, AVG_TOTAL, AVG_SELF, AVG_ANY, }; struct report_time_stat { uint64_t sum; uint64_t rec; /* time in recursive call */ uint64_t avg; uint64_t min; uint64_t max; }; struct uftrace_report_node { char *name; struct report_time_stat total; struct report_time_stat self; struct uftrace_dbg_loc *loc; uint64_t call; struct rb_node name_link; struct rb_node sort_link; unsigned size; /* used by diff */ struct uftrace_report_node *pair; }; struct uftrace_diff_policy { /* show percentage rather than value of diff */ bool percent; /* calculate diff using absolute values */ bool absolute; /* show original data as well as difference */ bool full; }; extern struct uftrace_diff_policy diff_policy; struct uftrace_report_node *report_find_node(struct rb_root *root, const char *name); void report_add_node(struct rb_root *root, const char *name, struct uftrace_report_node *node); void report_update_node(struct uftrace_report_node *node, struct uftrace_task_reader *task, struct uftrace_dbg_loc *loc); void report_calc_avg(struct rb_root *root); void report_delete_node(struct rb_root *root, struct uftrace_report_node *node); char *convert_sort_keys(char *sort_keys, enum avg_mode avg_mode); int report_setup_sort(const char *sort_keys); void report_sort_nodes(struct rb_root *name_root, struct rb_root *sort_root); int report_setup_diff(const char *key_str); void report_diff_nodes(struct rb_root *orig_root, struct rb_root *pair_root, struct rb_root *diff_root, int diff_column); void destroy_diff_nodes(struct rb_root *orig_root, struct rb_root *pair_root); void apply_diff_policy(char *policy); int report_setup_task(const char *key_str); void report_sort_tasks(struct uftrace_data *handle, struct rb_root *name_root, struct rb_root *sort_root); void setup_report_field(struct list_head *output_fields, struct uftrace_opts *opts, enum avg_mode avg_mode); #endif /* UFTRACE_REPORT_H */ uftrace-0.15.2/utils/script-luajit.c000066400000000000000000000251101455365734300173440ustar00rootroot00000000000000#ifdef HAVE_LIBLUAJIT #define PR_FMT "script" #define PR_DOMAIN DBG_SCRIPT #include "utils/script-luajit.h" #include "utils/filter.h" #include "utils/fstack.h" #include "utils/script.h" #include "utils/symbol.h" #include "utils/utils.h" #include #include #include #include static const char *libluajit = "libluajit-5.1.so"; static void *luajit_handle; static lua_State *L; static lua_State *(*dlluaL_newstate)(void); static void (*dlluaL_openlibs)(lua_State *L); static int (*dlluaL_loadfile)(lua_State *L, const char *filename); static void (*dllua_close)(lua_State *L); static int (*dllua_pcall)(lua_State *L, int nargs, int nresults, int errfunc); static int (*dllua_next)(lua_State *L, int index); static void (*dllua_createtable)(lua_State *L, int narr, int nrec); static void (*dllua_gettable)(lua_State *L, int index); static void (*dllua_settable)(lua_State *L, int index); static const char *(*dllua_tolstring)(lua_State *L, int index, size_t *len); static void (*dllua_pushstring)(lua_State *L, const char *s); static void (*dllua_pushinteger)(lua_State *L, lua_Integer n); static void (*dllua_pushnumber)(lua_State *L, lua_Number n); static void (*dllua_pushboolean)(lua_State *L, int b); static void (*dllua_pushnil)(lua_State *L); static void (*dllua_remove)(lua_State *L, int index); static void (*dllua_getfield)(lua_State *L, int index, const char *k); static int (*dllua_type)(lua_State *L, int index); static void (*dllua_settop)(lua_State *L, int index); #define dllua_newtable(L) dllua_createtable(L, 0, 0) #define dllua_pop(L, n) dllua_settop(L, -(n)-1) #define dllua_tostring(L, i) dllua_tolstring(L, (i), NULL) #define dllua_isnil(L, n) (dllua_type(L, (n)) == LUA_TNIL) #define dllua_getglobal(L, s) dllua_getfield(L, LUA_GLOBALSINDEX, (s)) static void setup_common_context(struct script_context *sc_ctx) { dllua_newtable(L); dllua_pushstring(L, "tid"); dllua_pushinteger(L, sc_ctx->tid); dllua_settable(L, -3); dllua_pushstring(L, "depth"); dllua_pushinteger(L, sc_ctx->depth); dllua_settable(L, -3); dllua_pushstring(L, "timestamp"); dllua_pushinteger(L, sc_ctx->timestamp); dllua_settable(L, -3); dllua_pushstring(L, "duration"); dllua_pushinteger(L, sc_ctx->duration); dllua_settable(L, -3); dllua_pushstring(L, "address"); dllua_pushinteger(L, sc_ctx->address); dllua_settable(L, -3); dllua_pushstring(L, "name"); dllua_pushstring(L, sc_ctx->name); dllua_settable(L, -3); } static void setup_argument_context(bool is_retval, struct script_context *sc_ctx) { struct uftrace_arg_spec *spec; void *data = sc_ctx->argbuf; union script_arg_val val; int count = 0; list_for_each_entry(spec, sc_ctx->argspec, list) { /* skip unwanted arguments or retval */ if (is_retval != (spec->idx == RETVAL_IDX)) continue; count++; } if (count == 0) return; if (is_retval) dllua_pushstring(L, "retval"); else dllua_pushstring(L, "args"); dllua_newtable(L); count = 0; list_for_each_entry(spec, sc_ctx->argspec, list) { const int null_str = -1; unsigned short slen; char ch_str[2]; char *str; double dval __maybe_unused; /* skip unwanted arguments or retval */ if (is_retval != (spec->idx == RETVAL_IDX)) continue; /* reset the value */ memset(val.v, 0, sizeof(val)); switch (spec->fmt) { case ARG_FMT_AUTO: case ARG_FMT_SINT: case ARG_FMT_UINT: case ARG_FMT_HEX: case ARG_FMT_PTR: case ARG_FMT_ENUM: memcpy(val.v, data, spec->size); switch (spec->size) { case 1: dllua_pushinteger(L, ++count); dllua_pushinteger(L, val.c); dllua_settable(L, -3); break; case 2: dllua_pushinteger(L, ++count); dllua_pushinteger(L, val.s); dllua_settable(L, -3); break; case 4: dllua_pushinteger(L, ++count); dllua_pushinteger(L, val.i); dllua_settable(L, -3); break; case 8: dllua_pushinteger(L, ++count); dllua_pushinteger(L, val.L); dllua_settable(L, -3); break; default: pr_warn("invalid argument format: %d\n", spec->fmt); break; } data += ALIGN(spec->size, 4); break; case ARG_FMT_FLOAT: memcpy(val.v, data, spec->size); #ifndef LIBMCOUNT switch (spec->size) { case 4: dval = val.f; break; case 8: dval = val.d; break; case 10: dval = (double)val.D; break; default: pr_dbg("invalid floating-point type size %d\n", spec->size); dval = 0; break; } dllua_pushinteger(L, ++count); dllua_pushnumber(L, dval); dllua_settable(L, -3); #endif data += ALIGN(spec->size, 4); break; case ARG_FMT_STR: case ARG_FMT_STD_STRING: /* get string length (2 bytes in the beginning) */ memcpy(&slen, data, 2); str = xmalloc(slen + 1); /* copy real string contents */ memcpy(str, data + 2, slen); str[slen] = '\0'; /* NULL string is encoded as '0xffffffff' */ if (slen == 4 && !memcmp(str, &null_str, sizeof(null_str))) strcpy(str, "NULL"); dllua_pushinteger(L, ++count); dllua_pushstring(L, str); dllua_settable(L, -3); free(str); data += ALIGN(slen + 2, 4); break; case ARG_FMT_CHAR: /* make it a string */ memcpy(ch_str, data, 1); ch_str[1] = '\0'; dllua_pushinteger(L, ++count); dllua_pushstring(L, ch_str); dllua_settable(L, -3); data += 4; break; case ARG_FMT_STRUCT: str = NULL; xasprintf(&str, "struct: %s{}", spec->type_name ? spec->type_name : ""); dllua_pushinteger(L, ++count); dllua_pushstring(L, str); dllua_settable(L, -3); free(str); data += ALIGN(spec->size, 4); break; default: pr_warn("invalid argument format: %d\n", spec->fmt); break; } } if (is_retval) { dllua_pushinteger(L, 1); dllua_gettable(L, -2); dllua_remove(L, -2); } dllua_settable(L, -3); } static int luajit_uftrace_begin(struct script_info *info) { int i; char *s; dllua_getglobal(L, "uftrace_begin"); if (dllua_isnil(L, -1)) { dllua_pop(L, 1); return -1; } dllua_newtable(L); dllua_pushstring(L, "record"); dllua_pushboolean(L, info->record); dllua_settable(L, -3); dllua_pushstring(L, "version"); dllua_pushstring(L, info->version); dllua_settable(L, -3); dllua_pushstring(L, "cmds"); dllua_newtable(L); strv_for_each(&info->cmds, s, i) { dllua_pushinteger(L, i + 1); dllua_pushstring(L, s); dllua_settable(L, -3); } dllua_settable(L, -3); if (dllua_pcall(L, 1, 0, 0) != 0) { pr_dbg("uftrace_begin failed: %s\n", dllua_tostring(L, -1)); dllua_pop(L, 1); return -1; } return 0; } static int luajit_uftrace_entry(struct script_context *sc_ctx) { dllua_getglobal(L, "uftrace_entry"); if (dllua_isnil(L, -1)) { dllua_pop(L, 1); return -1; } setup_common_context(sc_ctx); if (sc_ctx->arglen) setup_argument_context(false, sc_ctx); if (dllua_pcall(L, 1, 0, 0) != 0) { pr_dbg("uftrace_entry failed: %s\n", dllua_tostring(L, -1)); dllua_pop(L, 1); return -1; } return 0; } static int luajit_uftrace_exit(struct script_context *sc_ctx) { dllua_getglobal(L, "uftrace_exit"); if (dllua_isnil(L, -1)) { dllua_pop(L, 1); return -1; } setup_common_context(sc_ctx); if (sc_ctx->arglen) setup_argument_context(true, sc_ctx); if (dllua_pcall(L, 1, 0, 0) != 0) { pr_dbg("uftrace_exit failed: %s\n", dllua_tostring(L, -1)); dllua_pop(L, 1); return -1; } return 0; } static int luajit_uftrace_event(struct script_context *sc_ctx) { dllua_getglobal(L, "uftrace_event"); if (dllua_isnil(L, -1)) { dllua_pop(L, 1); return -1; } setup_common_context(sc_ctx); if (sc_ctx->argbuf) { dllua_pushstring(L, "args"); dllua_pushstring(L, sc_ctx->argbuf); dllua_settable(L, -3); } if (dllua_pcall(L, 1, 0, 0) != 0) { pr_dbg("uftrace_event failed: %s\n", dllua_tostring(L, -1)); dllua_pop(L, 1); return -1; } return 0; } static int luajit_uftrace_end(void) { dllua_getglobal(L, "uftrace_end"); if (dllua_isnil(L, -1)) { dllua_pop(L, 1); return -1; } if (dllua_pcall(L, 0, 0, 0) != 0) { pr_dbg("uftrace_end failed: %s\n", dllua_tostring(L, -1)); dllua_pop(L, 1); return -1; } return 0; } static int luajit_atfork_prepare(void) { return 0; } #define INIT_LUAJIT_API_FUNC(func) \ do { \ dl##func = dlsym(luajit_handle, #func); \ if (!dl##func) { \ pr_err("dlsym for \"" #func "\" is failed!\n"); \ return -1; \ } \ } while (0) static int load_luajit_api_funcs(void) { luajit_handle = dlopen(libluajit, RTLD_LAZY | RTLD_GLOBAL); if (!luajit_handle) { pr_warn("%s cannot be loaded!\n", libluajit); return -1; } pr_dbg("%s is loaded\n", libluajit); INIT_LUAJIT_API_FUNC(luaL_newstate); INIT_LUAJIT_API_FUNC(luaL_openlibs); INIT_LUAJIT_API_FUNC(luaL_loadfile); INIT_LUAJIT_API_FUNC(lua_close); INIT_LUAJIT_API_FUNC(lua_pcall); INIT_LUAJIT_API_FUNC(lua_next); INIT_LUAJIT_API_FUNC(lua_gettable); INIT_LUAJIT_API_FUNC(lua_settable); INIT_LUAJIT_API_FUNC(lua_pushstring); INIT_LUAJIT_API_FUNC(lua_pushinteger); INIT_LUAJIT_API_FUNC(lua_pushnumber); INIT_LUAJIT_API_FUNC(lua_pushboolean); INIT_LUAJIT_API_FUNC(lua_pushnil); INIT_LUAJIT_API_FUNC(lua_remove); INIT_LUAJIT_API_FUNC(lua_getfield); INIT_LUAJIT_API_FUNC(lua_type); INIT_LUAJIT_API_FUNC(lua_createtable); INIT_LUAJIT_API_FUNC(lua_settop); INIT_LUAJIT_API_FUNC(lua_tolstring); return 0; } int script_init_for_luajit(struct script_info *info, enum uftrace_pattern_type ptype) { pr_dbg("%s()\n", __func__); script_uftrace_entry = luajit_uftrace_entry; script_uftrace_exit = luajit_uftrace_exit; script_uftrace_event = luajit_uftrace_event; script_uftrace_end = luajit_uftrace_end; script_atfork_prepare = luajit_atfork_prepare; if (load_luajit_api_funcs() < 0) return -1; L = dlluaL_newstate(); dlluaL_openlibs(L); if (dlluaL_loadfile(L, info->name) != 0) return -1; if (dllua_pcall(L, 0, 0, 0) != 0) { pr_warn("luajit script failed: %s\n", dllua_tostring(L, -1)); dllua_pop(L, 1); return -1; } dllua_getglobal(L, "UFTRACE_FUNCS"); if (!dllua_isnil(L, -1)) { dllua_pushnil(L); while (dllua_next(L, -2) != 0) { char *filter_str = xstrdup(dllua_tostring(L, -1)); script_add_filter(filter_str, ptype); free(filter_str); dllua_pop(L, 1); } } dllua_pop(L, 1); luajit_uftrace_begin(info); return 0; } void script_finish_for_luajit(void) { pr_dbg("%s()\n", __func__); dllua_close(L); dlclose(luajit_handle); luajit_handle = NULL; } #endif uftrace-0.15.2/utils/script-luajit.h000066400000000000000000000011171455365734300173520ustar00rootroot00000000000000#ifndef UFTRACE_SCRIPT_LUAJIT_H #define UFTRACE_SCRIPT_LUAJIT_H #include "utils/filter.h" struct script_info; #ifdef HAVE_LIBLUAJIT #define SCRIPT_LUAJIT_ENABLED 1 int script_init_for_luajit(struct script_info *info, enum uftrace_pattern_type ptype); void script_finish_for_luajit(void); #else /* HAVE_LIBLUAJIT */ #define SCRIPT_LUAJIT_ENABLED 0 static inline int script_init_for_luajit(struct script_info *info, enum uftrace_pattern_type ptype) { return -1; } static inline void script_finish_for_luajit(void) { } #endif /* HAVE_LIBLUAJIT */ #endif /* UFTRACE_SCRIPT_LUAJIT_H */ uftrace-0.15.2/utils/script-python.c000066400000000000000000000513431455365734300174040ustar00rootroot00000000000000/* * Python script binding for function entry and exit * * Copyright (C) 2017, LG Electronics, Honggyu Kim * * Released under the GPL v2. */ #if defined(HAVE_LIBPYTHON2) || defined(HAVE_LIBPYTHON3) /* This should be defined before #include "utils.h" */ #define PR_FMT "script" #define PR_DOMAIN DBG_SCRIPT #include "utils/script-python.h" #include "utils/filter.h" #include "utils/fstack.h" #include "utils/script.h" #include "utils/symbol.h" #include "utils/utils.h" #include #include /* python library name, it should support any version python v2 or v3 */ static const char libpython[] = "libpython" stringify(LIBPYTHON_VERSION) ".so"; /* python library handle returned by dlopen() */ static void *python_handle; /* global mutex for python interpreter */ static pthread_mutex_t python_interpreter_lock = PTHREAD_MUTEX_INITIALIZER; /* whether error in script was reported to user */ static bool python_error_reported = false; /* whether script_init() was done successfully */ static bool python_initialized; static void (*__Py_Initialize)(void); static void (*__Py_Finalize)(void); static void (*__PySys_SetPath)(char *); static PyObject *(*__PyImport_Import)(PyObject *name); static PyObject *(*__PyErr_Occurred)(void); static void (*__PyErr_Print)(void); static void (*__PyErr_Clear)(void); static int (*__PyObject_HasAttrString)(PyObject *, const char *); static PyObject *(*__PyObject_GetAttrString)(PyObject *, const char *); static int (*__PyCallable_Check)(PyObject *); static PyObject *(*__PyObject_CallObject)(PyObject *callable_object, PyObject *args); static int (*__PyRun_SimpleStringFlags)(const char *, PyCompilerFlags *); static PyObject *(*__PyString_FromString)(const char *); static PyObject *(*__PyInt_FromLong)(long); static PyObject *(*__PyLong_FromLong)(long); static PyObject *(*__PyLong_FromUnsignedLongLong)(unsigned PY_LONG_LONG); static PyObject *(*__PyFloat_FromDouble)(double); static PyObject *(*__PyBool_FromLong)(long); static char *(*__PyString_AsString)(PyObject *); static long (*__PyLong_AsLong)(PyObject *); static PyObject *(*__PyTuple_New)(Py_ssize_t size); static int (*__PyTuple_SetItem)(PyObject *, Py_ssize_t, PyObject *); static PyObject *(*__PyTuple_GetItem)(PyObject *, Py_ssize_t); static Py_ssize_t (*__PyList_Size)(PyObject *); static PyObject *(*__PyList_GetItem)(PyObject *, Py_ssize_t); static PyObject *(*__PyDict_New)(void); static int (*__PyDict_SetItem)(PyObject *mp, PyObject *key, PyObject *item); static int (*__PyDict_SetItemString)(PyObject *dp, const char *key, PyObject *item); static PyObject *(*__PyDict_GetItem)(PyObject *mp, PyObject *key); /* for python3.8+ compatibility */ static void (*__Py_Dealloc)(PyObject *); #if PY_VERSION_HEX >= 0x03080000 static inline void __Py_DECREF(PyObject *obj) { if (--obj->ob_refcnt == 0) __Py_Dealloc(obj); } #undef Py_DECREF #define Py_DECREF(obj) __Py_DECREF((PyObject *)obj)) static inline void __Py_XDECREF(PyObject *obj) { if (obj) __Py_DECREF(obj); } #undef Py_XDECREF #define Py_XDECREF(obj) __Py_XDECREF((PyObject *)obj) #endif /* PY_VERSION_HEX >= 0x03080000 */ static PyObject *pModule, *pFuncBegin, *pFuncEntry, *pFuncExit, *pFuncEvent, *pFuncEnd; enum py_context_idx { PY_CTX_TID = 0, PY_CTX_DEPTH, PY_CTX_TIMESTAMP, PY_CTX_DURATION, PY_CTX_ADDRESS, PY_CTX_NAME, PY_CTX_ARGS, PY_CTX_RETVAL, }; /* The order has to be aligned with enum py_args above. */ static const char *py_context_table[] = { "tid", "depth", "timestamp", "duration", "address", "name", "args", "retval", }; #define INIT_PY_API_FUNC(func) \ do { \ __##func = dlsym(python_handle, #func); \ if (!__##func) { \ pr_err("dlsym for \"" #func "\" is failed"); \ return -1; \ } \ } while (0) #define INIT_PY_API_FUNC2(func, name) \ do { \ __##func = dlsym(python_handle, #name); \ if (!__##func) { \ pr_err("dlsym for \"" #name "\" is failed"); \ return -1; \ } \ } while (0) static int load_python_api_funcs(void) { python_handle = dlopen(libpython, RTLD_LAZY | RTLD_GLOBAL); if (!python_handle) { pr_warn("%s cannot be loaded!\n", libpython); return -1; } pr_dbg("%s is loaded\n", libpython); INIT_PY_API_FUNC(Py_Initialize); INIT_PY_API_FUNC(PyImport_Import); INIT_PY_API_FUNC(Py_Finalize); #ifdef HAVE_LIBPYTHON2 INIT_PY_API_FUNC(PySys_SetPath); INIT_PY_API_FUNC(PyString_FromString); INIT_PY_API_FUNC(PyInt_FromLong); INIT_PY_API_FUNC(PyString_AsString); /* just to suppress compiler warning */ __Py_Dealloc = NULL; #else INIT_PY_API_FUNC2(PySys_SetPath, Py_SetPath); INIT_PY_API_FUNC2(PyString_FromString, PyUnicode_FromString); INIT_PY_API_FUNC2(PyInt_FromLong, PyLong_FromLong); INIT_PY_API_FUNC2(PyString_AsString, PyUnicode_AsUTF8); INIT_PY_API_FUNC2(Py_Dealloc, _Py_Dealloc); #endif INIT_PY_API_FUNC(PyErr_Occurred); INIT_PY_API_FUNC(PyErr_Print); INIT_PY_API_FUNC(PyErr_Clear); INIT_PY_API_FUNC(PyObject_HasAttrString); INIT_PY_API_FUNC(PyObject_GetAttrString); INIT_PY_API_FUNC(PyCallable_Check); INIT_PY_API_FUNC(PyObject_CallObject); INIT_PY_API_FUNC(PyRun_SimpleStringFlags); INIT_PY_API_FUNC(PyLong_FromLong); INIT_PY_API_FUNC(PyLong_FromUnsignedLongLong); INIT_PY_API_FUNC(PyFloat_FromDouble); INIT_PY_API_FUNC(PyBool_FromLong); INIT_PY_API_FUNC(PyLong_AsLong); INIT_PY_API_FUNC(PyTuple_New); INIT_PY_API_FUNC(PyTuple_SetItem); INIT_PY_API_FUNC(PyTuple_GetItem); INIT_PY_API_FUNC(PyList_Size); INIT_PY_API_FUNC(PyList_GetItem); INIT_PY_API_FUNC(PyDict_New); INIT_PY_API_FUNC(PyDict_SetItem); INIT_PY_API_FUNC(PyDict_SetItemString); INIT_PY_API_FUNC(PyDict_GetItem); return 0; } static char *remove_py_suffix(char *py_name) { char *ext = strrchr(py_name, '.'); if (!ext) return NULL; *ext = '\0'; return py_name; } static int set_python_path(char *py_pathname) { char py_sysdir[PATH_MAX]; char *old_sysdir = getenv("PYTHONPATH"); char *new_sysdir = NULL; pr_dbg2("%s(\"%s\")\n", __func__, py_pathname); if (absolute_dirname(py_pathname, py_sysdir) == NULL) return -1; if (old_sysdir) xasprintf(&new_sysdir, "%s:%s", old_sysdir, py_sysdir); else new_sysdir = xstrdup(py_sysdir); setenv("PYTHONPATH", new_sysdir, 1); free(new_sysdir); return 0; } /* Import python module that is given by -S option. */ static int import_python_module(char *py_pathname) { PyObject *pName; char *py_basename = xstrdup(basename(py_pathname)); remove_py_suffix(py_basename); pName = __PyString_FromString(py_basename); free(py_basename); pModule = __PyImport_Import(pName); Py_XDECREF(pName); if (pModule == NULL) { __PyErr_Print(); pr_warn("\"%s\" cannot be imported!\n", py_pathname); return -1; } /* import sys by default */ __PyRun_SimpleStringFlags("import sys", NULL); pr_dbg("python module \"%s\" is imported.\n", py_pathname); return 0; } union python_val { long l; unsigned long long ull; char *s; double f; }; static void python_insert_tuple(PyObject *tuple, char type, int idx, union python_val val) { PyObject *obj; switch (type) { case 'l': obj = __PyInt_FromLong(val.l); break; case 'U': obj = __PyLong_FromUnsignedLongLong(val.ull); break; case 's': obj = __PyString_FromString(val.s); if (__PyErr_Occurred()) { Py_XDECREF(obj); obj = __PyString_FromString(""); __PyErr_Clear(); } break; case 'f': obj = __PyFloat_FromDouble(val.f); break; default: pr_warn("unsupported data type was added to tuple\n"); obj = NULL; break; } __PyTuple_SetItem(tuple, idx, obj); } static void python_insert_dict(PyObject *dict, char type, const char *key, union python_val val) { PyObject *obj; switch (type) { case 'l': obj = __PyInt_FromLong(val.l); break; case 'U': obj = __PyLong_FromUnsignedLongLong(val.ull); break; case 's': obj = __PyString_FromString(val.s); if (__PyErr_Occurred()) { Py_XDECREF(obj); obj = __PyString_FromString(""); __PyErr_Clear(); } break; case 'b': obj = __PyBool_FromLong(val.l); break; default: pr_warn("unsupported data type was added to dict\n"); obj = NULL; break; } __PyDict_SetItemString(dict, key, obj); Py_XDECREF(obj); } static void insert_tuple_long(PyObject *tuple, int idx, long v) { union python_val val = { .l = v, }; python_insert_tuple(tuple, 'l', idx, val); } static void insert_tuple_ull(PyObject *tuple, int idx, unsigned long long v) { union python_val val = { .ull = v, }; python_insert_tuple(tuple, 'U', idx, val); } static void insert_tuple_string(PyObject *tuple, int idx, char *v) { union python_val val = { .s = v, }; python_insert_tuple(tuple, 's', idx, val); } static void __maybe_unused insert_tuple_double(PyObject *tuple, int idx, double v) { union python_val val = { .f = v, }; python_insert_tuple(tuple, 'f', idx, val); } static void insert_dict_long(PyObject *dict, const char *key, long v) { union python_val val = { .l = v, }; python_insert_dict(dict, 'l', key, val); } static void insert_dict_ull(PyObject *dict, const char *key, unsigned long long v) { union python_val val = { .ull = v, }; python_insert_dict(dict, 'U', key, val); } static void insert_dict_string(PyObject *dict, const char *key, char *v) { union python_val val = { .s = v, }; python_insert_dict(dict, 's', key, val); } static void insert_dict_bool(PyObject *dict, const char *key, bool v) { union python_val val = { .l = v, }; python_insert_dict(dict, 'b', key, val); } #define PYCTX(_item) py_context_table[PY_CTX_##_item] static void setup_common_context(PyObject **pDict, struct script_context *sc_ctx) { insert_dict_long(*pDict, PYCTX(TID), sc_ctx->tid); insert_dict_long(*pDict, PYCTX(DEPTH), sc_ctx->depth); insert_dict_ull(*pDict, PYCTX(TIMESTAMP), sc_ctx->timestamp); insert_dict_long(*pDict, PYCTX(ADDRESS), sc_ctx->address); insert_dict_string(*pDict, PYCTX(NAME), sc_ctx->name); } static void setup_argument_context(PyObject **pDict, bool is_retval, struct script_context *sc_ctx) { struct uftrace_arg_spec *spec; void *data = sc_ctx->argbuf; PyObject *args; union script_arg_val val; int count = 0; list_for_each_entry(spec, sc_ctx->argspec, list) { /* skip unwanted arguments or retval */ if (is_retval != (spec->idx == RETVAL_IDX)) continue; count++; } if (count == 0) return; args = __PyTuple_New(count); if (args == NULL) pr_err("failed to allocate python tuple for argument"); count = 0; list_for_each_entry(spec, sc_ctx->argspec, list) { const int null_str = -1; unsigned short slen; char ch_str[2]; char *str; double dval __maybe_unused; /* skip unwanted arguments or retval */ if (is_retval != (spec->idx == RETVAL_IDX)) continue; /* reset the value */ memset(val.v, 0, sizeof(val)); switch (spec->fmt) { case ARG_FMT_AUTO: case ARG_FMT_SINT: case ARG_FMT_UINT: case ARG_FMT_HEX: case ARG_FMT_PTR: case ARG_FMT_ENUM: memcpy(val.v, data, spec->size); switch (spec->size) { case 1: insert_tuple_long(args, count++, val.c); break; case 2: insert_tuple_long(args, count++, val.s); break; case 4: insert_tuple_long(args, count++, val.i); break; case 8: insert_tuple_ull(args, count++, val.L); break; default: pr_warn("invalid integer size: %d\n", spec->size); break; } data += ALIGN(spec->size, 4); break; case ARG_FMT_FLOAT: memcpy(val.v, data, spec->size); #ifndef LIBMCOUNT switch (spec->size) { case 4: dval = val.f; break; case 8: dval = val.d; break; case 10: dval = (double)val.D; break; default: pr_dbg("invalid floating-point type size %d\n", spec->size); dval = 0; break; } insert_tuple_double(args, count++, dval); #endif data += ALIGN(spec->size, 4); break; case ARG_FMT_STR: case ARG_FMT_STD_STRING: /* get string length (2 bytes in the beginning) */ memcpy(&slen, data, 2); str = xmalloc(slen + 1); /* copy real string contents */ memcpy(str, data + 2, slen); str[slen] = '\0'; /* NULL string is encoded as '0xffffffff' */ if (slen == 4 && !memcmp(str, &null_str, sizeof(null_str))) strcpy(str, "NULL"); insert_tuple_string(args, count++, str); free(str); data += ALIGN(slen + 2, 4); break; case ARG_FMT_CHAR: /* make it a string */ memcpy(ch_str, data, 1); ch_str[1] = '\0'; insert_tuple_string(args, count++, ch_str); data += 4; break; case ARG_FMT_STRUCT: str = NULL; xasprintf(&str, "struct: %s{}", spec->type_name ? spec->type_name : ""); insert_tuple_string(args, count++, str); free(str); data += ALIGN(spec->size, 4); break; default: pr_warn("invalid argument format: %d\n", spec->fmt); break; } } if (is_retval) { PyObject *retval = __PyTuple_GetItem(args, 0); /* single return value doesn't need a tuple */ __PyDict_SetItemString(*pDict, PYCTX(RETVAL), retval); } else { /* arguments will be returned in a tuple */ __PyDict_SetItemString(*pDict, PYCTX(ARGS), args); } Py_XDECREF(args); } static void setup_event_argument(PyObject *pDict, struct script_context *sc_ctx) { char *data = sc_ctx->argbuf; PyObject *args; if (data == NULL) data = ""; args = __PyString_FromString(data); if (__PyErr_Occurred()) { Py_XDECREF(args); args = __PyString_FromString(""); __PyErr_Clear(); } /* arguments will be returned in a tuple */ __PyDict_SetItemString(pDict, PYCTX(ARGS), args); Py_XDECREF(args); } int python_uftrace_begin(struct script_info *info) { PyObject *dict; PyObject *cmds; PyObject *ctx; int i; char *s; if (unlikely(!pFuncBegin)) return -1; /* python_interpreter_lock is already held */ dict = __PyDict_New(); insert_dict_bool(dict, "record", info->record); insert_dict_string(dict, "version", info->version); cmds = __PyTuple_New(info->cmds.nr); strv_for_each(&info->cmds, s, i) insert_tuple_string(cmds, i, s); __PyDict_SetItemString(dict, "cmds", cmds); Py_XDECREF(cmds); ctx = __PyTuple_New(1); __PyTuple_SetItem(ctx, 0, dict); __PyObject_CallObject(pFuncBegin, ctx); if (debug) { if (__PyErr_Occurred()) { pr_dbg("uftrace_begin failed:\n"); __PyErr_Print(); } } Py_XDECREF(ctx); return 0; } int python_uftrace_entry(struct script_context *sc_ctx) { PyObject *pDict; PyObject *pythonContext; if (unlikely(!pFuncEntry)) return -1; pthread_mutex_lock(&python_interpreter_lock); /* Entire arguments are passed into a single dictionary. */ pDict = __PyDict_New(); /* Setup common info in both entry and exit into a dictionary */ setup_common_context(&pDict, sc_ctx); if (sc_ctx->arglen) setup_argument_context(&pDict, false, sc_ctx); /* Python function arguments must be passed in a tuple. */ pythonContext = __PyTuple_New(1); __PyTuple_SetItem(pythonContext, 0, pDict); /* Call python function "uftrace_entry". */ __PyObject_CallObject(pFuncEntry, pythonContext); if (debug) { if (__PyErr_Occurred() && !python_error_reported) { pr_dbg("uftrace_entry failed:\n"); __PyErr_Print(); python_error_reported = true; } } /* Free PyTuple. */ Py_XDECREF(pythonContext); pthread_mutex_unlock(&python_interpreter_lock); return 0; } int python_uftrace_exit(struct script_context *sc_ctx) { PyObject *pDict; PyObject *pythonContext; if (unlikely(!pFuncExit)) return -1; pthread_mutex_lock(&python_interpreter_lock); /* Entire arguments are passed into a single dictionary. */ pDict = __PyDict_New(); /* Setup common info in both entry and exit into a dictionary */ setup_common_context(&pDict, sc_ctx); /* Add time duration info */ insert_dict_ull(pDict, PYCTX(DURATION), sc_ctx->duration); if (sc_ctx->arglen) setup_argument_context(&pDict, true, sc_ctx); /* Python function arguments must be passed in a tuple. */ pythonContext = __PyTuple_New(1); __PyTuple_SetItem(pythonContext, 0, pDict); /* Call python function "uftrace_exit". */ __PyObject_CallObject(pFuncExit, pythonContext); if (debug) { if (__PyErr_Occurred() && !python_error_reported) { pr_dbg("uftrace_exit failed:\n"); __PyErr_Print(); python_error_reported = true; } } /* Free PyTuple. */ Py_XDECREF(pythonContext); pthread_mutex_unlock(&python_interpreter_lock); return 0; } int python_uftrace_event(struct script_context *sc_ctx) { PyObject *pDict; PyObject *pythonContext; if (unlikely(!pFuncEvent)) return -1; pthread_mutex_lock(&python_interpreter_lock); /* Entire arguments are passed into a single dictionary. */ pDict = __PyDict_New(); /* Setup common info into a dictionary */ setup_common_context(&pDict, sc_ctx); setup_event_argument(pDict, sc_ctx); /* Python function arguments must be passed in a tuple. */ pythonContext = __PyTuple_New(1); __PyTuple_SetItem(pythonContext, 0, pDict); /* Call python function "uftrace_exit". */ __PyObject_CallObject(pFuncEvent, pythonContext); if (debug) { if (__PyErr_Occurred() && !python_error_reported) { pr_dbg("uftrace_event failed:\n"); __PyErr_Print(); python_error_reported = true; } } /* Free PyTuple. */ Py_XDECREF(pythonContext); pthread_mutex_unlock(&python_interpreter_lock); return 0; } int python_uftrace_end(void) { if (unlikely(!pFuncEnd)) return -1; pthread_mutex_lock(&python_interpreter_lock); /* Call python function "uftrace_end". */ __PyObject_CallObject(pFuncEnd, NULL); if (debug) { if (__PyErr_Occurred()) { pr_dbg("uftrace_end failed:\n"); __PyErr_Print(); } } pthread_mutex_unlock(&python_interpreter_lock); return 0; } int python_atfork_prepare(void) { pr_dbg("flush python buffer in %s()\n", __func__); pthread_mutex_lock(&python_interpreter_lock); __PyRun_SimpleStringFlags("sys.stdout.flush()", NULL); pthread_mutex_unlock(&python_interpreter_lock); return 0; } static PyObject *get_python_callback(char *name) { PyObject *func; if (!__PyObject_HasAttrString(pModule, name)) return NULL; func = __PyObject_GetAttrString(pModule, name); if (!func || !__PyCallable_Check(func)) { if (__PyErr_Occurred()) __PyErr_Print(); pr_dbg("%s is not callable!\n", name); func = NULL; } return func; } int script_init_for_python(struct script_info *info, enum uftrace_pattern_type ptype) { char *py_pathname = info->name; pr_dbg("%s(\"%s\")\n", __func__, py_pathname); /* Bind script_uftrace functions to python's. */ script_uftrace_entry = python_uftrace_entry; script_uftrace_exit = python_uftrace_exit; script_uftrace_event = python_uftrace_event; script_uftrace_end = python_uftrace_end; script_atfork_prepare = python_atfork_prepare; if (load_python_api_funcs() < 0) return -1; if (set_python_path(py_pathname) < 0) { dlclose(python_handle); return -1; } pthread_mutex_lock(&python_interpreter_lock); __Py_Initialize(); python_initialized = true; /* Import python module that is passed by -p option. */ if (import_python_module(py_pathname) < 0) { pthread_mutex_unlock(&python_interpreter_lock); /* script_finish() will release resources */ return -1; } /* check if script has its own list of functions to run */ if (__PyObject_HasAttrString(pModule, "UFTRACE_FUNCS")) { int i, len; PyObject *filter_list = __PyObject_GetAttrString(pModule, "UFTRACE_FUNCS"); /* XXX: type checking is hard */ len = __PyList_Size(filter_list); for (i = 0; i < len; i++) { PyObject *func = __PyList_GetItem(filter_list, i); script_add_filter(__PyString_AsString(func), ptype); } } pFuncBegin = get_python_callback("uftrace_begin"); pFuncEntry = get_python_callback("uftrace_entry"); pFuncExit = get_python_callback("uftrace_exit"); pFuncEvent = get_python_callback("uftrace_event"); pFuncEnd = get_python_callback("uftrace_end"); /* Call python function "uftrace_begin" immediately if possible. */ python_uftrace_begin(info); __PyErr_Clear(); pthread_mutex_unlock(&python_interpreter_lock); pr_dbg("python initialization finished\n"); return 0; } void script_finish_for_python(void) { pr_dbg("%s()\n", __func__); if (!python_initialized) return; pthread_mutex_lock(&python_interpreter_lock); __Py_Finalize(); pthread_mutex_unlock(&python_interpreter_lock); dlclose(python_handle); python_handle = NULL; } #endif /* !(HAVE_LIBPYTHON2 || HAVE_LIBPYTHON3) */ uftrace-0.15.2/utils/script-python.h000066400000000000000000000016021455365734300174020ustar00rootroot00000000000000/* * Python script binding for function entry and exit * * Copyright (C) 2017, LG Electronics, Honggyu Kim * * Released under the GPL v2. */ #ifndef UFTRACE_SCRIPT_PYTHON_H #define UFTRACE_SCRIPT_PYTHON_H #include "utils/filter.h" struct script_info; #if defined(HAVE_LIBPYTHON2) || defined(HAVE_LIBPYTHON3) #define PY_SSIZE_T_CLEAN #include #define SCRIPT_PYTHON_ENABLED 1 int script_init_for_python(struct script_info *info, enum uftrace_pattern_type ptype); void script_finish_for_python(void); #else /* HAVE_LIBPYTHON2 */ /* Do nothing if libpython2.7.so is not installed. */ #define SCRIPT_PYTHON_ENABLED 0 static inline int script_init_for_python(struct script_info *info, enum uftrace_pattern_type ptype) { return -1; } static inline void script_finish_for_python(void) { } #endif /* HAVE_LIBPYTHON2 */ #endif /* UFTRACE_SCRIPT_PYTHON_H */ uftrace-0.15.2/utils/script.c000066400000000000000000000113231455365734300160570ustar00rootroot00000000000000/* * Script binding for function entry and exit * * Copyright (C) 2017, LG Electronics, Honggyu Kim * * Released under the GPL v2. */ /* This should be defined before #include "utils.h" */ #define PR_FMT "script" #define PR_DOMAIN DBG_SCRIPT #include "utils/script.h" #include "utils/filter.h" #include "utils/list.h" #include "utils/script-luajit.h" #include "utils/script-python.h" #include "utils/utils.h" #include /* This will be set by getenv("UFTRACE_SCRIPT"). */ char *script_str; enum script_type_t script_lang; /* The below functions are used both in record time and script command. */ script_uftrace_entry_t script_uftrace_entry; script_uftrace_exit_t script_uftrace_exit; script_uftrace_event_t script_uftrace_event; script_uftrace_end_t script_uftrace_end; script_atfork_prepare_t script_atfork_prepare; struct script_filter_item { struct list_head list; struct uftrace_pattern patt; }; static LIST_HEAD(filters); enum script_type_t get_script_type(const char *str) { char *ext = strrchr(str, '.'); if (ext == NULL) return SCRIPT_UNKNOWN; /* * The given script will be detected by the file suffix. * As of now, it only handles ".py" suffix for python. */ if (!strcmp(ext, ".py")) return SCRIPT_PYTHON; else if (!strcmp(ext, ".lua")) return SCRIPT_LUAJIT; else if (!strcmp(ext, ".testing")) return SCRIPT_TESTING; return SCRIPT_UNKNOWN; } void script_add_filter(char *func, enum uftrace_pattern_type ptype) { struct script_filter_item *item; if (func == NULL) return; item = xmalloc(sizeof(*item)); init_filter_pattern(ptype, &item->patt, func); pr_dbg2("add script filter: %s (%s)\n", func, get_filter_pattern(item->patt.type)); list_add_tail(&item->list, &filters); } /* returns 1 on match - script should be run */ int script_match_filter(char *func) { struct script_filter_item *item; /* special case: no filter */ if (list_empty(&filters)) return 1; list_for_each_entry(item, &filters, list) { if (match_filter_pattern(&item->patt, func)) return 1; } return 0; } void script_finish_filter(void) { struct script_filter_item *item, *tmp; list_for_each_entry_safe(item, tmp, &filters, list) { list_del(&item->list); free_filter_pattern(&item->patt); free(item); } } static int script_init_for_testing(struct script_info *info, enum uftrace_pattern_type ptype) { int i; char *name; strv_for_each(&info->cmds, name, i) script_add_filter(name, ptype); return 0; } static void script_finish_for_testing(void) { } int script_init(struct script_info *info, enum uftrace_pattern_type ptype) { char *script_pathname = info->name; pr_dbg2("%s(\"%s\")\n", __func__, script_pathname); if (access(script_pathname, F_OK) < 0) { perror(script_pathname); return -1; } script_lang = get_script_type(script_pathname); switch (script_lang) { case SCRIPT_PYTHON: if (script_init_for_python(info, ptype) < 0) { pr_warn("failed to init python scripting\n"); script_pathname = NULL; } break; case SCRIPT_LUAJIT: if (script_init_for_luajit(info, ptype) < 0) { pr_warn("failed to init luajit scripting\n"); script_pathname = NULL; } break; case SCRIPT_TESTING: if (script_init_for_testing(info, ptype) < 0) { pr_warn("failed to init test scripting\n"); script_pathname = NULL; } break; default: pr_warn("unsupported script type: %s\n", script_pathname); script_pathname = NULL; } if (script_pathname == NULL) return -1; return 0; } void script_finish(void) { pr_dbg2("%s()\n", __func__); switch (script_lang) { case SCRIPT_PYTHON: script_finish_for_python(); break; case SCRIPT_LUAJIT: script_finish_for_luajit(); break; case SCRIPT_TESTING: script_finish_for_testing(); break; default: break; } script_finish_filter(); } #ifdef UNIT_TEST #include #define SCRIPT_FILE "xxx.testing" static int setup_testing_script(struct script_info *info) { FILE *fp; fp = fopen(SCRIPT_FILE, "w+"); if (fp == NULL) return -1; fprintf(fp, "# uftrace script testing\n"); strv_append(&info->cmds, "abc"); strv_append(&info->cmds, "x*z"); fclose(fp); return 0; } static int cleanup_testing_script(struct script_info *info) { unlink(SCRIPT_FILE); strv_free(&info->cmds); return 0; } TEST_CASE(script_init) { struct script_info info = { .version = "UFTRACE_VERSION", .name = SCRIPT_FILE, }; pr_dbg("checking basic script init and finish\n"); TEST_EQ(setup_testing_script(&info), 0); TEST_EQ(script_init(&info, PATT_GLOB), 0); TEST_EQ(list_empty(&filters), false); TEST_EQ(script_match_filter("abc"), 1); TEST_EQ(script_match_filter("xxyyzz"), 1); script_finish(); TEST_EQ(list_empty(&filters), true); TEST_EQ(cleanup_testing_script(&info), 0); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/script.h000066400000000000000000000041561455365734300160720ustar00rootroot00000000000000/* * Script binding for function entry and exit * * Copyright (C) 2017, LG Electronics, Honggyu Kim * * Released under the GPL v2. */ #ifndef UFTRACE_SCRIPT_H #define UFTRACE_SCRIPT_H #include "libmcount/mcount.h" #include "utils/script-luajit.h" #include "utils/script-python.h" #include "utils/utils.h" #define SCRIPT_ENABLED (SCRIPT_LUAJIT_ENABLED || SCRIPT_PYTHON_ENABLED) /* script type */ enum script_type_t { SCRIPT_UNKNOWN = 0, SCRIPT_PYTHON, SCRIPT_LUAJIT, SCRIPT_TESTING, SCRIPT_TYPE_COUNT }; /* informantion passed during initialization */ struct script_info { char *name; char *version; bool record; struct strv cmds; }; /* context information passed to script */ struct script_context { int tid; int depth; uint64_t timestamp; uint64_t duration; /* exit only */ unsigned long address; char *name; /* for arguments and return value */ int arglen; void *argbuf; struct list_head *argspec; }; union script_arg_val { char c; short s; int i; long l; long long L; /* libmcount should not access floating-point types. */ #ifndef LIBMCOUNT float f; double d; long double D; #endif unsigned char v[16]; }; extern char *script_str; typedef int (*script_uftrace_entry_t)(struct script_context *sc_ctx); typedef int (*script_uftrace_exit_t)(struct script_context *sc_ctx); typedef int (*script_uftrace_event_t)(struct script_context *sc_ctx); typedef int (*script_uftrace_end_t)(void); typedef int (*script_atfork_prepare_t)(void); /* The below functions are used both in record time and script command. */ extern script_uftrace_entry_t script_uftrace_entry; extern script_uftrace_exit_t script_uftrace_exit; extern script_uftrace_event_t script_uftrace_event; extern script_uftrace_end_t script_uftrace_end; extern script_atfork_prepare_t script_atfork_prepare; int script_init(struct script_info *info, enum uftrace_pattern_type ptype); void script_finish(void); void script_add_filter(char *func, enum uftrace_pattern_type ptype); int script_match_filter(char *func); void script_finish_filter(void); enum script_type_t get_script_type(const char *str); #endif /* UFTRACE_SCRIPT_H */ uftrace-0.15.2/utils/session.c000066400000000000000000000736371455365734300162560ustar00rootroot00000000000000#include #include #include #define PR_FMT "session" #define PR_DOMAIN DBG_SESSION #include "libmcount/mcount.h" #include "uftrace.h" #include "utils/fstack.h" #include "utils/rbtree.h" #include "utils/symbol.h" #include "utils/utils.h" static void delete_tasks(struct uftrace_session_link *sessions); /** * read_session_map - read memory mappings in a session map file * @dirname: directory name of the session * @addr_space: address space to keep the memory mapping * @sid: session id * * This function reads mapping data from a session map file and * construct the address space for a session to resolve symbols * in libraries. */ void read_session_map(char *dirname, struct uftrace_sym_info *sinfo, char *sid) { FILE *fp; char buf[PATH_MAX]; const char *last_libname = NULL; struct uftrace_mmap **maps = &sinfo->maps; struct uftrace_mmap *last_map = NULL; const char build_id_prefix[] = "build-id:"; snprintf(buf, sizeof(buf), "%s/sid-%.*s.map", dirname, SESSION_ID_LEN, sid); fp = fopen(buf, "rb"); if (fp == NULL) pr_err("cannot open maps file: %s", buf); while (fgets(buf, sizeof(buf), fp) != NULL) { uint64_t start, end; char prot[5]; char path[PATH_MAX]; char build_id[BUILD_ID_STR_SIZE + sizeof(build_id_prefix)]; size_t namelen; struct uftrace_mmap *map; /* prevent to reuse previous iteration's result */ build_id[0] = '\0'; /* skip anon mappings */ if (sscanf(buf, "%" PRIx64 "-%" PRIx64 " %s %*x %*x:%*x %*d %s %s", &start, &end, prot, path, build_id) < 4) { pr_dbg("sscanf failed\n"); continue; } /* skip the [stack] mapping */ if (path[0] == '[') { if (strncmp(path, "[stack", 6) == 0) sinfo->kernel_base = guess_kernel_base(buf); continue; } /* use first mapping only (even if it's non-exec) */ if (last_libname && !strcmp(last_libname, path)) { /* extend last_map to have all segments */ last_map->end = end; continue; } namelen = ALIGN(strlen(path) + 1, 4); map = xzalloc(sizeof(*map) + namelen); map->start = start; map->end = end; map->len = namelen; memcpy(map->prot, prot, 4); memcpy(map->libname, path, namelen); map->libname[strlen(path)] = '\0'; if (!strncmp(build_id, build_id_prefix, strlen(build_id_prefix))) { memcpy(map->build_id, build_id + strlen(build_id_prefix), sizeof(build_id) - sizeof(build_id_prefix)); } /* set mapping of main executable */ if (sinfo->exec_map == NULL && sinfo->filename && !strcmp(path, sinfo->filename)) { sinfo->exec_map = map; } last_libname = map->libname; last_map = map; *maps = map; maps = &map->next; } fclose(fp); } /** * delete_session_map - free memory mappings in an address space * @addr_space: symbol table has the memory mapping * * This function releases mapping data in a symbol table which * was read by read_session_map(). */ void delete_session_map(struct uftrace_sym_info *sinfo) { struct uftrace_mmap *map, *tmp; map = sinfo->maps; while (map) { tmp = map->next; free(map); map = tmp; } sinfo->maps = NULL; sinfo->exec_map = NULL; } /** * update_session_map - rewrite map files to have build-id * @filename - name of map file * * This function updates @filename map file to add build-id at the end * of each line. */ void update_session_map(const char *filename) { FILE *ifp, *ofp; char buf[PATH_MAX]; const char build_id_prefix[] = "build-id:"; ifp = fopen(filename, "r"); if (ifp == NULL) pr_err("cannot open map file: %s", filename); snprintf(buf, sizeof(buf), "%s.tmp", filename); ofp = fopen(buf, "w"); if (ofp == NULL) pr_err("cannot create new map file: %s", buf); while (fgets(buf, sizeof(buf), ifp) != NULL) { char path[PATH_MAX]; char build_id[BUILD_ID_STR_SIZE]; int len; len = strlen(buf); if (len > 0 && buf[len - 1] == '\n') buf[--len] = '\0'; fwrite_all(buf, len, ofp); /* skip anon mappings */ if (sscanf(buf, "%*x-%*x %*s %*x %*x:%*x %*d %s", path) != 1) goto next; /* skip the special mappings like [stack] */ if (path[0] == '[') goto next; if (read_build_id(path, build_id, sizeof(build_id)) == 0) fprintf(ofp, " %s%s", build_id_prefix, build_id); next: fputc('\n', ofp); } fclose(ifp); fclose(ofp); snprintf(buf, sizeof(buf), "%s.tmp", filename); if (rename(buf, filename) < 0) pr_err("cannot rename map file: %s", filename); } /** * create_session - create a new task session from session message * @sessions: session link to manage sessions and tasks * @msg: uftrace session message read from task file * @dirname: uftrace data directory name * @symdir: symbol directory name * @exename: executable name started this session * @sym_rel_addr: whether symbol table uses relative address * @needs_symtab: whether symbol table loading is needed * @needs_srcline: whether debug info loading is needed * * This function allocates a new session started by a task. The new * session will be added to sessions tree sorted by pid and timestamp. * Also it loads symbol table and debug info if needed. */ void create_session(struct uftrace_session_link *sessions, struct uftrace_msg_sess *msg, char *dirname, char *symdir, char *exename, bool sym_rel_addr, bool needs_symtab, bool needs_srcline) { struct uftrace_session *s; struct uftrace_task *t; struct rb_node *parent = NULL; struct rb_node **p = &sessions->root.rb_node; while (*p) { parent = *p; s = rb_entry(parent, struct uftrace_session, node); if (s->pid > msg->task.pid) p = &parent->rb_left; else if (s->pid < msg->task.pid) p = &parent->rb_right; else if (s->start_time > msg->task.time) p = &parent->rb_left; else p = &parent->rb_right; } s = xzalloc(sizeof(*s) + msg->namelen + 1); memcpy(s->sid, msg->sid, sizeof(s->sid)); s->start_time = msg->task.time; s->pid = msg->task.pid; s->tid = msg->task.tid; s->namelen = msg->namelen; memcpy(s->exename, exename, s->namelen); s->exename[s->namelen] = 0; s->filters = RB_ROOT; INIT_LIST_HEAD(&s->dlopen_libs); pr_dbg2("new session: pid = %d, session = %.*s\n", s->pid, SESSION_ID_LEN, s->sid); if (needs_symtab) { s->sym_info.dirname = dirname; s->sym_info.filename = s->exename; s->sym_info.symdir = symdir; s->sym_info.flags = SYMTAB_FL_USE_SYMFILE | SYMTAB_FL_DEMANGLE; if (sym_rel_addr) s->sym_info.flags |= SYMTAB_FL_ADJ_OFFSET; if (strcmp(dirname, symdir)) s->sym_info.flags |= SYMTAB_FL_SYMS_DIR; read_session_map(dirname, &s->sym_info, s->sid); load_module_symtabs(&s->sym_info); load_debug_info(&s->sym_info, needs_srcline); } if (sessions->first == NULL) sessions->first = s; t = find_task(sessions, s->tid); if (t) { strncpy(t->comm, basename(exename), sizeof(t->comm)); t->comm[sizeof(t->comm) - 1] = '\0'; } rb_link_node(&s->node, parent, p); rb_insert_color(&s->node, &sessions->root); } static struct uftrace_session *find_session(struct uftrace_session_link *sessions, int pid, uint64_t timestamp) { struct uftrace_session *iter; struct uftrace_session *s = NULL; struct rb_node *parent = NULL; struct rb_node **p = &sessions->root.rb_node; while (*p) { parent = *p; iter = rb_entry(parent, struct uftrace_session, node); if (iter->pid > pid) p = &parent->rb_left; else if (iter->pid < pid) p = &parent->rb_right; else if (iter->start_time > timestamp) p = &parent->rb_left; else { s = iter; p = &parent->rb_right; } } return s; } /** * walk_sessions - iterates all session and invokes @callback * @sessions: session link to manage sessions and tasks * @callback: function to be called for each task * @arg: argument passed to the @callback * * This function traverses the task tree and invokes @callback with * @arg. As the @callback returns a non-zero value, it'll stop and * return in the middle. */ void walk_sessions(struct uftrace_session_link *sessions, walk_sessions_cb_t callback, void *arg) { struct rb_node *n = rb_first(&sessions->root); struct uftrace_session *s; while (n) { s = rb_entry(n, struct uftrace_session, node); if (callback(s, arg) != 0) break; n = rb_next(n); } } /** * get_session_from_sid - find a session using @sid * @sessions: session link to manage sessions and tasks * @sid: session ID * * This function returns a matching session or %NULL. */ struct uftrace_session *get_session_from_sid(struct uftrace_session_link *sessions, char sid[]) { struct rb_node *n = rb_first(&sessions->root); struct uftrace_session *s; while (n) { s = rb_entry(n, struct uftrace_session, node); if (memcmp(s->sid, sid, sizeof(s->sid)) == 0) return s; n = rb_next(n); } return NULL; } /** * session_add_dlopen - add dlopen'ed library to the mapping table * @sess: pointer to a current session * @timestamp: timestamp at the dlopen call * @base_addr: load address of text segment of the library * @libname: name of the library * * This functions adds the info of a library which was loaded by dlopen. * Instead of creating a new session, it just adds the library information * to the @sess. */ void session_add_dlopen(struct uftrace_session *sess, uint64_t timestamp, unsigned long base_addr, const char *libname) { struct uftrace_dlopen_list *udl, *pos; char build_id[BUILD_ID_STR_SIZE]; udl = xmalloc(sizeof(*udl)); udl->time = timestamp; udl->base = base_addr; read_build_id(libname, build_id, sizeof(build_id)); udl->mod = load_module_symtab(&sess->sym_info, libname, build_id); list_for_each_entry(pos, &sess->dlopen_libs, list) { if (pos->time > timestamp) break; } list_add_tail(&udl->list, &pos->list); } /** * session_find_dlsym - find symbol from dlopen'ed library * @sess: pointer to a current session * @timestamp: timestamp of the address * @addr: instruction address * * This functions find a matching symbol from a dlopen'ed library in * @sess using @addr. The @timestamp is needed to determine which * library should be searched. */ struct uftrace_symbol *session_find_dlsym(struct uftrace_session *sess, uint64_t timestamp, unsigned long addr) { struct uftrace_dlopen_list *pos; struct uftrace_symbol *sym; list_for_each_entry_reverse(pos, &sess->dlopen_libs, list) { if (pos->time > timestamp) continue; if (pos->mod == NULL) continue; sym = find_sym(&pos->mod->symtab, addr - pos->base); if (sym) return sym; } return NULL; } void delete_session(struct uftrace_session *sess) { struct uftrace_dlopen_list *udl, *tmp; list_for_each_entry_safe(udl, tmp, &sess->dlopen_libs, list) { list_del(&udl->list); free(udl); } finish_debug_info(&sess->sym_info); delete_session_map(&sess->sym_info); uftrace_cleanup_filter(&sess->filters); uftrace_cleanup_filter(&sess->fixups); free(sess); } /** * delete_sessions - free all resources in the @sessions * @sessions: session link to manage sessions and tasks * * This function removes all session-related data structure in * @sessions. */ void delete_sessions(struct uftrace_session_link *sessions) { struct uftrace_session *sess; struct rb_node *n; delete_tasks(sessions); while (!RB_EMPTY_ROOT(&sessions->root)) { n = rb_first(&sessions->root); rb_erase(n, &sessions->root); sess = rb_entry(n, struct uftrace_session, node); delete_session(sess); } } static void add_session_ref(struct uftrace_task *task, struct uftrace_session *sess, uint64_t timestamp) { struct uftrace_sess_ref *sref = &task->sref; if (sess == NULL) { pr_dbg("task %d/%d has no session\n", task->tid, task->pid); return; } if (task->sref_last) { task->sref_last->next = sref = xmalloc(sizeof(*sref)); task->sref_last->end = timestamp; } sref->next = NULL; sref->sess = sess; sref->start = timestamp; sref->end = -1ULL; pr_dbg2("task session: tid = %d, session = %.*s\n", task->tid, SESSION_ID_LEN, sess->sid); task->sref_last = sref; } /** * find_task_session - find a matching session using @pid and @timestamp * @sessions: session link to manage sessions and tasks * @task: task to search a session * @timestamp: timestamp of task * * This function searches the sessions tree using @task and @timestamp. * The most recent session that has a smaller than the @timestamp will * be returned. If it didn't find a session tries to search session * list of parent or thread-leader. */ struct uftrace_session *find_task_session(struct uftrace_session_link *sessions, struct uftrace_task *task, uint64_t timestamp) { int parent_id; struct uftrace_sess_ref *ref; while (task != NULL) { ref = &task->sref; while (ref) { if (ref->start <= timestamp && timestamp < ref->end) return ref->sess; ref = ref->next; } /* * if it cannot find its own session, * inherit from parent or leader. */ parent_id = task->ppid ?: task->pid; if (parent_id == 0 || parent_id == task->tid) break; task = find_task(sessions, parent_id); } return NULL; } /** * create_task - create a new task from task message * @sessions: session link to manage sessions and tasks * @msg: ftrace task message read from task file * @fork: whether it's forked or not (i.e. thread) * * This function creates a new task from @msg and add it to task tree. * The newly created task will have a reference to a session if * @needs_session is %true. */ void create_task(struct uftrace_session_link *sessions, struct uftrace_msg_task *msg, bool fork) { struct uftrace_task *t; struct uftrace_task *pt; struct uftrace_session *s; struct rb_node *parent = NULL; struct rb_node **p = &sessions->tasks.rb_node; while (*p) { parent = *p; t = rb_entry(parent, struct uftrace_task, node); if (t->tid > msg->tid) p = &parent->rb_left; else if (t->tid < msg->tid) p = &parent->rb_right; else { /* add new session */ s = find_session(sessions, msg->pid, msg->time); if (s != NULL) add_session_ref(t, s, msg->time); return; } } t = xzalloc(sizeof(*t)); /* msg->pid is a parent pid if forked */ t->pid = fork ? msg->tid : msg->pid; t->tid = msg->tid; t->ppid = fork ? msg->pid : 0; t->time.stamp = msg->time; INIT_LIST_HEAD(&t->children); INIT_LIST_HEAD(&t->siblings); pt = find_task(sessions, msg->pid); if (pt != NULL) list_add_tail(&t->siblings, &pt->children); s = find_session(sessions, msg->pid, msg->time); if (s == NULL) { if (pt && pt->sref_last && pt->sref_last->start < msg->time) s = pt->sref_last->sess; } if (s != NULL) { add_session_ref(t, s, msg->time); strncpy(t->comm, basename(s->exename), sizeof(t->comm)); t->comm[sizeof(t->comm) - 1] = '\0'; } pr_dbg2("new task: tid = %d (%.*s), session = %-.*s\n", t->tid, sizeof(t->comm), s ? t->comm : "unknown", SESSION_ID_LEN, s ? s->sid : "unknown"); if (sessions->first_task == NULL) sessions->first_task = t; rb_link_node(&t->node, parent, p); rb_insert_color(&t->node, &sessions->tasks); } static void delete_task(struct uftrace_task *t) { struct uftrace_sess_ref *sref, *tmp; sref = t->sref.next; while (sref) { tmp = sref->next; free(sref); sref = tmp; } free(t); } static void delete_tasks(struct uftrace_session_link *sessions) { struct uftrace_task *t; struct rb_node *n; while (!RB_EMPTY_ROOT(&sessions->tasks)) { n = rb_first(&sessions->tasks); rb_erase(n, &sessions->tasks); t = rb_entry(n, struct uftrace_task, node); delete_task(t); } } /** * find_task - find a matching task by @tid * @sessions: session link to manage sessions and tasks * @tid: task id */ struct uftrace_task *find_task(struct uftrace_session_link *sessions, int tid) { struct uftrace_task *t; struct rb_node *parent = NULL; struct rb_node **p = &sessions->tasks.rb_node; while (*p) { parent = *p; t = rb_entry(parent, struct uftrace_task, node); if (t->tid > tid) p = &parent->rb_left; else if (t->tid < tid) p = &parent->rb_right; else return t; } return NULL; } /** * walk_tasks - iterates all tasks and invokes @callback * @sess: session link to manage sessions and tasks * @callback: function to be called for each task * @arg: argument passed to the @callback * * This function traverses the task tree and invokes @callback with * @arg. As the @callback returns a non-zero value, it'll stop and * return in the middle. */ void walk_tasks(struct uftrace_session_link *sessions, walk_tasks_cb_t callback, void *arg) { struct rb_node *n = rb_first(&sessions->tasks); struct uftrace_task *t; while (n) { t = rb_entry(n, struct uftrace_task, node); if (callback(t, arg) != 0) break; n = rb_next(n); } } /** * task_find_sym - find a symbol that matches to @rec * @sessions: session link to manage sessions and tasks * @task: handle for functions in a task * @rec: uftrace data record * * This function looks up symbol table in current session. */ struct uftrace_symbol *task_find_sym(struct uftrace_session_link *sessions, struct uftrace_task_reader *task, struct uftrace_record *rec) { struct uftrace_session *sess; struct uftrace_sym_info *sinfo; struct uftrace_symbol *sym = NULL; uint64_t addr = rec->addr; sess = find_task_session(sessions, task->t, rec->time); if (is_kernel_record(task, rec)) { if (sess == NULL) sess = sessions->first; addr = get_kernel_address(&sess->sym_info, addr); } if (sess == NULL) return NULL; sinfo = &sess->sym_info; sym = find_symtabs(sinfo, addr); if (sym == NULL) sym = session_find_dlsym(sess, rec->time, addr); return sym; } /** * task_find_sym_addr - find a symbol that matches to @addr * @sessions: session link to manage sessions and tasks * @task: handle for functions in a task * @time: timestamp of the @addr * @addr: instruction address * * This function looks up symbol table in current session. */ struct uftrace_symbol *task_find_sym_addr(struct uftrace_session_link *sessions, struct uftrace_task_reader *task, uint64_t time, uint64_t addr) { struct uftrace_session *sess; struct uftrace_symbol *sym = NULL; sess = find_task_session(sessions, task->t, time); if (sess == NULL) { struct uftrace_session *fsess = sessions->first; if (is_kernel_address(&fsess->sym_info, addr)) sess = fsess; else return NULL; } sym = find_symtabs(&sess->sym_info, addr); if (sym == NULL) sym = session_find_dlsym(sess, time, addr); if (sym == NULL) { if (EVENT_ID_PERF_SCHED_IN == addr || EVENT_ID_PERF_SCHED_OUT == addr || EVENT_ID_PERF_SCHED_BOTH == addr) return &sched_sym; else if (EVENT_ID_PERF_SCHED_OUT_PREEMPT == addr || EVENT_ID_PERF_SCHED_BOTH_PREEMPT == addr) return &sched_preempt_sym; } return sym; } /** * task_find_loc_addr - find a debug location that matches to @addr * @sessions: session link to manage sessions and tasks * @task: handle for functions in a task * @time: timestamp of the @addr * @addr: instruction address * * This function returns a debug location of symbol * that looked up in symbol table in current session */ struct uftrace_dbg_loc *task_find_loc_addr(struct uftrace_session_link *sessions, struct uftrace_task_reader *task, uint64_t time, uint64_t addr) { struct uftrace_session *sess; struct uftrace_symbol *sym = NULL; struct uftrace_mmap *map; struct uftrace_dbg_info *dinfo; struct uftrace_dbg_loc *loc; ptrdiff_t sym_idx; sess = find_task_session(sessions, task->t, time); if (sess == NULL) { struct uftrace_session *fsess = sessions->first; if (is_kernel_address(&fsess->sym_info, addr)) sess = fsess; else return NULL; } sym = find_symtabs(&sess->sym_info, addr); if (sym == NULL) sym = session_find_dlsym(sess, time, addr); if (sym == NULL) return NULL; if (sym->type == ST_LOCAL_FUNC || sym->type == ST_GLOBAL_FUNC) { map = find_map(&sess->sym_info, addr); if (map == NULL) return NULL; dinfo = &(map->mod->dinfo); if (dinfo == NULL || dinfo->nr_locs_used == 0) return NULL; sym_idx = sym - map->mod->symtab.sym; loc = &dinfo->locs[sym_idx]; if (loc->file != NULL) return loc; } return NULL; } #ifdef UNIT_TEST static struct uftrace_session_link test_sessions; static const char session_map[] = "00400000-00401000 r-xp 00000000 08:03 4096 unittest\n" "bfff0000-bffff000 rw-p 00000000 08:03 4096 [stack]\n"; static const char session_map_with_build_id[] = "00400000-00401000 r-xp 00000000 08:03 4096 unittest build-id:1234567890abcdef\n" "5f98a000-5fa8c000 r-xp 00000000 08:03 4096 libc.so\n" "bfff0000-bffff000 rw-p 00000000 08:03 4096 [stack]\n"; TEST_CASE(session_search) { int i; const int NUM_TEST = 100; TEST_EQ(test_sessions.first, NULL); pr_dbg("create same session %d times\n", NUM_TEST); for (i = 0; i < NUM_TEST; i++) { struct uftrace_msg_sess msg = { .task = { .pid = 1, .tid = 1, .time = i * 100, }, .sid = "test", .namelen = 8, /* = strlen("unittest") */ }; int fd; fd = creat("sid-test.map", 0400); write_all(fd, session_map, sizeof(session_map) - 1); close(fd); create_session(&test_sessions, &msg, ".", ".", "unittest", false, false, false); remove("sid-test.map"); } TEST_NE(test_sessions.first, NULL); TEST_EQ(test_sessions.first->pid, 1); TEST_EQ(test_sessions.first->start_time, 0); pr_dbg("find sessions including random timestamp\n"); for (i = 0; i < NUM_TEST; i++) { int t; struct uftrace_session *s; t = random() % (NUM_TEST * 100); s = find_session(&test_sessions, 1, t); TEST_NE(s, NULL); TEST_EQ(s->pid, 1); TEST_GE(t, s->start_time); TEST_LT(t, s->start_time + 100); } delete_sessions(&test_sessions); TEST_EQ(RB_EMPTY_ROOT(&test_sessions.root), true); return TEST_OK; } TEST_CASE(task_search) { struct uftrace_task *task; struct uftrace_session *sess; int fd; pr_dbg("create initial task\n"); { struct uftrace_msg_sess smsg = { .task = { .pid = 1, .tid = 1, .time = 100, }, .sid = "initial", .namelen = 8, /* = strlen("unittest") */ }; struct uftrace_msg_task tmsg = { .pid = 1, .tid = 1, .time = 100, }; fd = creat("sid-initial.map", 0400); write_all(fd, session_map, sizeof(session_map) - 1); close(fd); create_session(&test_sessions, &smsg, ".", ".", "unittest", false, false, false); create_task(&test_sessions, &tmsg, false); remove("sid-initial.map"); task = find_task(&test_sessions, tmsg.tid); TEST_NE(task, NULL); TEST_EQ(task->tid, tmsg.tid); TEST_EQ(task->sref.sess, test_sessions.first); TEST_NE(test_sessions.first, NULL); sess = find_session(&test_sessions, tmsg.pid, tmsg.time); TEST_NE(sess, NULL); TEST_EQ(sess->pid, task->pid); TEST_EQ(sess->tid, task->tid); } pr_dbg("fork child task\n"); { struct uftrace_msg_task tmsg = { .pid = 1, /* ppid */ .tid = 2, /* pid */ .time = 200, }; create_task(&test_sessions, &tmsg, true); task = find_task(&test_sessions, tmsg.tid); TEST_NE(task, NULL); TEST_EQ(task->tid, tmsg.tid); TEST_EQ(task->sref.sess, test_sessions.first); sess = find_task_session(&test_sessions, task, tmsg.time); TEST_NE(sess, NULL); TEST_EQ(sess->pid, tmsg.pid); TEST_LE(sess->start_time, tmsg.time); } pr_dbg("create parent thread\n"); { struct uftrace_msg_task tmsg = { .pid = 1, .tid = 3, .time = 300, }; create_task(&test_sessions, &tmsg, false); task = find_task(&test_sessions, tmsg.tid); TEST_NE(task, NULL); TEST_EQ(task->tid, tmsg.tid); TEST_EQ(task->sref.sess, test_sessions.first); sess = find_task_session(&test_sessions, task, tmsg.time); TEST_NE(sess, NULL); TEST_EQ(sess->pid, tmsg.pid); TEST_LE(sess->start_time, tmsg.time); } pr_dbg("create child thread\n"); { struct uftrace_msg_task tmsg = { .pid = 2, .tid = 4, .time = 400, }; create_task(&test_sessions, &tmsg, false); task = find_task(&test_sessions, tmsg.tid); TEST_NE(task, NULL); TEST_EQ(task->tid, tmsg.tid); TEST_EQ(task->sref.sess, test_sessions.first); sess = find_task_session(&test_sessions, task, tmsg.time); TEST_NE(sess, NULL); /* it returned a session from parent so pid is not same */ TEST_NE(sess->pid, tmsg.pid); TEST_LE(sess->start_time, tmsg.time); } pr_dbg("exec from child\n"); { struct uftrace_msg_sess smsg = { .task = { .pid = 2, .tid = 4, .time = 500, }, .sid = "after_exec", .namelen = 8, /* = strlen("unittest") */ }; struct uftrace_msg_task tmsg = { .pid = 2, .tid = 4, .time = 500, }; fd = creat("sid-after_exec.map", 0400); write_all(fd, session_map, sizeof(session_map) - 1); close(fd); create_session(&test_sessions, &smsg, ".", ".", "unittest", false, false, false); create_task(&test_sessions, &tmsg, false); remove("sid-after_exec.map"); task = find_task(&test_sessions, tmsg.tid); TEST_NE(task, NULL); TEST_EQ(task->tid, tmsg.tid); sess = find_task_session(&test_sessions, task, tmsg.time); TEST_NE(sess, NULL); TEST_EQ(sess->pid, task->pid); TEST_EQ(sess->tid, task->tid); TEST_LE(sess->start_time, tmsg.time); } pr_dbg("fork grand-child task\n"); { struct uftrace_msg_task tmsg = { .pid = 4, /* ppid */ .tid = 5, /* pid */ .time = 600, }; create_task(&test_sessions, &tmsg, true); task = find_task(&test_sessions, tmsg.tid); TEST_NE(task, NULL); TEST_EQ(task->tid, tmsg.tid); sess = find_task_session(&test_sessions, task, tmsg.time); TEST_NE(sess, NULL); TEST_EQ(sess->tid, tmsg.pid); TEST_LE(sess->start_time, tmsg.time); } pr_dbg("create grand-child thread\n"); { struct uftrace_msg_task tmsg = { .pid = 5, .tid = 6, .time = 700, }; create_task(&test_sessions, &tmsg, false); task = find_task(&test_sessions, tmsg.tid); TEST_NE(task, NULL); TEST_EQ(task->tid, tmsg.tid); sess = find_task_session(&test_sessions, task, tmsg.time); TEST_NE(sess, NULL); /* it returned a session from parent so pid is not same */ TEST_NE(sess->pid, tmsg.pid); TEST_LE(sess->start_time, tmsg.time); } pr_dbg("finding tasks in the initial session\n"); task = find_task(&test_sessions, 1); sess = find_task_session(&test_sessions, task, 100); TEST_NE(sess, NULL); TEST_STREQ(sess->sid, "initial"); task = find_task(&test_sessions, 2); sess = find_task_session(&test_sessions, task, 200); TEST_NE(sess, NULL); TEST_STREQ(sess->sid, "initial"); task = find_task(&test_sessions, 4); sess = find_task_session(&test_sessions, task, 400); TEST_NE(sess, NULL); TEST_STREQ(sess->sid, "initial"); pr_dbg("finding tasks in the session after exec\n"); sess = find_task_session(&test_sessions, task, 500); TEST_NE(sess, NULL); TEST_STREQ(sess->sid, "after_exec"); task = find_task(&test_sessions, 5); sess = find_task_session(&test_sessions, task, 600); TEST_NE(sess, NULL); TEST_STREQ(sess->sid, "after_exec"); task = find_task(&test_sessions, 6); sess = find_task_session(&test_sessions, task, 700); TEST_NE(sess, NULL); TEST_STREQ(sess->sid, "after_exec"); delete_sessions(&test_sessions); TEST_EQ(RB_EMPTY_ROOT(&test_sessions.root), true); TEST_EQ(RB_EMPTY_ROOT(&test_sessions.tasks), true); return TEST_OK; } TEST_CASE(task_symbol) { struct uftrace_symbol *sym; struct uftrace_msg_sess msg = { .task = { .pid = 1, .tid = 1, .time = 100, }, .sid = "test", .namelen = 8, /* = strlen("unittest") */ }; struct uftrace_msg_task tmsg = { .pid = 1, .tid = 1, .time = 100, }; struct uftrace_task_reader task = { .tid = 1, }; FILE *fp; pr_dbg("creating symbol and map files\n"); fp = fopen("sid-test.map", "w"); TEST_NE(fp, NULL); fprintf(fp, "%s", session_map); fclose(fp); fp = fopen("unittest.sym", "w"); TEST_NE(fp, NULL); fprintf(fp, "00000100 P printf\n"); fprintf(fp, "00000200 P __dynsym_end\n"); fprintf(fp, "00000300 T _start\n"); fprintf(fp, "00000400 T main\n"); fprintf(fp, "00000500 T __sym_end\n"); fclose(fp); create_session(&test_sessions, &msg, ".", ".", "unittest", false, true, false); create_task(&test_sessions, &tmsg, false); remove("sid-test.map"); remove("unittest.sym"); TEST_NE(test_sessions.first, NULL); TEST_EQ(test_sessions.first->pid, 1); pr_dbg("try to find a symbol from a mapped address\n"); task.t = find_task(&test_sessions, 1); sym = task_find_sym_addr(&test_sessions, &task, 100, 0x400410); TEST_NE(sym, NULL); TEST_STREQ(sym->name, "main"); delete_sessions(&test_sessions); TEST_EQ(RB_EMPTY_ROOT(&test_sessions.root), true); return TEST_OK; } TEST_CASE(task_symbol_dlopen) { struct uftrace_symbol *sym; struct uftrace_msg_sess msg = { .task = { .pid = 1, .tid = 1, .time = 100, }, .sid = "test", .namelen = 8, /* = strlen("unittest") */ }; FILE *fp; fp = fopen("sid-test.map", "w"); TEST_NE(fp, NULL); fprintf(fp, "%s", session_map); fclose(fp); pr_dbg("creating symbol for the dlopen library\n"); fp = fopen("libuftrace-test.so.0.sym", "w"); TEST_NE(fp, NULL); fprintf(fp, "0100 P __tls_get_addr\n"); fprintf(fp, "0200 P __dynsym_end\n"); fprintf(fp, "0300 T _start\n"); fprintf(fp, "0400 T foo\n"); fprintf(fp, "0500 T __sym_end\n"); fclose(fp); create_session(&test_sessions, &msg, ".", ".", "unittest", false, true, false); remove("sid-test.map"); TEST_NE(test_sessions.first, NULL); TEST_EQ(test_sessions.first->pid, 1); pr_dbg("add dlopen info message\n"); session_add_dlopen(test_sessions.first, 200, 0x7003000, "libuftrace-test.so.0"); remove("libuftrace-test.so.0.sym"); TEST_EQ(list_empty(&test_sessions.first->dlopen_libs), false); pr_dbg("try to find a symbol from the dlopen address\n"); sym = session_find_dlsym(test_sessions.first, 250, 0x7003410); TEST_NE(sym, NULL); TEST_STREQ(sym->name, "foo"); delete_sessions(&test_sessions); TEST_EQ(RB_EMPTY_ROOT(&test_sessions.root), true); return TEST_OK; } TEST_CASE(session_map_build_id) { FILE *fp; struct uftrace_mmap *map; struct uftrace_sym_info test_sinfo = { .loaded = false, }; fp = fopen("sid-test.map", "w"); TEST_NE(fp, NULL); fprintf(fp, "%s", session_map_with_build_id); fclose(fp); pr_dbg("read map file with build-id info\n"); read_session_map(".", &test_sinfo, "test"); pr_dbg("first entry should have a build-id\n"); map = test_sinfo.maps; TEST_NE(map, NULL); TEST_STREQ(map->libname, "unittest"); TEST_STREQ(map->build_id, "1234567890abcdef"); pr_dbg("next entry should not have one\n"); map = map->next; TEST_NE(map, NULL); TEST_STREQ(map->libname, "libc.so"); TEST_STREQ(map->build_id, ""); map = map->next; TEST_EQ(map, NULL); delete_session_map(&test_sinfo); return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/shmem.c000066400000000000000000000031051455365734300156630ustar00rootroot00000000000000#include #include #include #include #include #include #include "utils/shmem.h" #ifdef __ANDROID__ const char *uftrace_shmem_root(void) { static char uftrace_dir[PATH_MAX] = ""; if (uftrace_dir[0] == 0) { const char *tmpdir; tmpdir = getenv("TMPDIR"); if (!tmpdir) tmpdir = "/tmp"; snprintf(uftrace_dir, sizeof(uftrace_dir), "%s/uftrace", tmpdir); } return uftrace_dir; } int uftrace_shmem_open(const char *name, int oflag, mode_t mode) { const char *uftrace_dir; char *fname; int fd; int status; uftrace_dir = uftrace_shmem_root(); status = mkdir(uftrace_dir, mode); if (status < 0 && errno != EEXIST) return -1; if (asprintf(&fname, "%s/%s", uftrace_dir, name) < 0) return -1; fd = open(fname, oflag, mode); if (fd >= 0) { int flags = fcntl(fd, F_GETFD, 0); flags |= FD_CLOEXEC; if (fcntl(fd, F_SETFD, flags) < 0) { int saved_errno = errno; close(fd); fd = -1; errno = saved_errno; } } free(fname); return fd; } int uftrace_shmem_unlink(const char *name) { const char *uftrace_dir; char *fname; int status; uftrace_dir = uftrace_shmem_root(); if (asprintf(&fname, "%s/%s", uftrace_dir, name)) return -1; status = unlink(fname); free(fname); return status; } #else /* ! __ANDROID__ */ #include const char *uftrace_shmem_root(void) { return "/dev/shm"; } int uftrace_shmem_open(const char *name, int oflag, mode_t mode) { return shm_open(name, oflag, mode); } int uftrace_shmem_unlink(const char *name) { return shm_unlink(name); } #endif uftrace-0.15.2/utils/shmem.h000066400000000000000000000003721455365734300156730ustar00rootroot00000000000000#ifndef UFTRACE_SHMEM_H #define UFTRACE_SHMEM_H #include int uftrace_shmem_open(const char *name, int oflag, mode_t mode); int uftrace_shmem_unlink(const char *name); const char *uftrace_shmem_root(void); #endif /* UFTRACE_SHMEM_H */ uftrace-0.15.2/utils/socket.c000066400000000000000000000070671455365734300160550ustar00rootroot00000000000000#include #include #include #include #include "uftrace.h" #include "utils/socket.h" #include "utils/utils.h" /* Unlink socket file if it exists */ void socket_unlink(struct sockaddr_un *addr) { if (unlink(addr->sun_path) == -1) { if (errno != ENOENT) pr_dbg("cannot unlink socket '%s'\n", addr->sun_path); } } /* Create socket for communication between the client and the agent */ int agent_socket_create(struct sockaddr_un *addr, pid_t pid) { int fd; char *channel = NULL; fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd == -1) { pr_warn("socket creation failed: %s\n", strerror(errno)); return fd; } memset(addr, 0, sizeof(struct sockaddr_un)); xasprintf(&channel, "%s/%d.socket", MCOUNT_AGENT_SOCKET_DIR, pid); addr->sun_family = AF_UNIX; strncpy(addr->sun_path, channel, sizeof(addr->sun_path) - 1); free(channel); return fd; } /* Setup socket on agent side so it can accept client connection */ int agent_listen(int fd, struct sockaddr_un *addr) { if (bind(fd, (struct sockaddr *)addr, sizeof(struct sockaddr_un)) == -1) { pr_warn("cannot bind to socket '%s': %s\n", addr->sun_path, strerror(errno)); return -1; } if (listen(fd, 1) == -1) { pr_warn("cannot listen to socket '%s': %s\n", addr->sun_path, strerror(errno)); return -1; } return 0; } /* Client side: connect to an agent socket */ int agent_connect(int fd, struct sockaddr_un *addr) { if (connect(fd, (struct sockaddr *)addr, sizeof(struct sockaddr_un)) == -1) { pr_warn("cannot connect to socket '%s': %s\n", addr->sun_path, strerror(errno)); return -1; } return 0; } /* Agent side: accept incoming client connection */ int agent_accept(int fd) { return accept(fd, NULL, NULL); } /** * agent_message_send - send a message through the agent socket * @fd - socket file descriptor * @type - type of message to send * @data - data load * @size - size of @data * @return - status code, negative for error */ int agent_message_send(int fd, int type, void *data, size_t size) { struct uftrace_msg msg = { .magic = UFTRACE_MSG_MAGIC, .type = type, .len = size, }; struct iovec iov[2] = { { .iov_base = &msg, .iov_len = sizeof(msg), }, { .iov_base = data, .iov_len = size, }, }; pr_dbg4("send agent message [%d] (size=%d)\n", type, size); if (writev_all(fd, iov, ARRAY_SIZE(iov)) < 0) { pr_dbg3("error writing message to agent socket\n"); return -1; } return 0; } /** * agent_message_read_head - read message header from the agent socket * * Fetch the message type and size so the data load can be read somewhere else. * * @fd - socket file descriptor * @msg - received message head * @return - status code, negative for error */ int agent_message_read_head(int fd, struct uftrace_msg *msg) { if (read_all(fd, msg, sizeof(*msg)) < 0) { pr_dbg4("error reading agent message header\n"); return -1; } if (msg->magic != UFTRACE_MSG_MAGIC) { pr_dbg4("invalid agent message received\n"); return -1; } return 0; } /** * agent_message_read_response - read agent ack * @fd - socket file descriptor * @response - ack from agent * @return - status: data or error code, negative on error */ int agent_message_read_response(int fd, struct uftrace_msg *response) { int status = 0; if (agent_message_read_head(fd, response) < 0) return -1; if (response->len > sizeof(status)) return -1; if (read_all(fd, &status, response->len) < 0) { pr_dbg3("error reading agent socket\n"); return -1; } pr_dbg4("read agent response [%d] (size=%d)\n", response->type, response->len); return status; } uftrace-0.15.2/utils/socket.h000066400000000000000000000011441455365734300160500ustar00rootroot00000000000000#ifndef UFTRACE_SOCKET_H #define UFTRACE_SOCKET_H #include #define MCOUNT_AGENT_SOCKET_DIR "/tmp/uftrace" struct uftrace_msg; void socket_unlink(struct sockaddr_un *addr); int agent_socket_create(struct sockaddr_un *addr, pid_t pid); int agent_listen(int fd, struct sockaddr_un *addr); int agent_connect(int fd, struct sockaddr_un *addr); int agent_accept(int fd); int agent_message_send(int fd, int type, void *data, size_t size); int agent_message_read_head(int fd, struct uftrace_msg *msg); int agent_message_read_response(int fd, struct uftrace_msg *response); #endif // UFTRACE_SOCKET_H uftrace-0.15.2/utils/symbol-libelf.c000066400000000000000000000025041455365734300173140ustar00rootroot00000000000000#ifdef HAVE_LIBELF #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "symbol" #define PR_DOMAIN DBG_SYMBOL #include "utils/symbol-libelf.h" #include "utils/utils.h" int elf_init(const char *filename, struct uftrace_elf_data *elf) { elf->fd = open(filename, O_RDONLY); if (elf->fd < 0) { pr_dbg("error during open ELF file: %s: %m\n", filename); goto err; } elf_version(EV_CURRENT); elf->handle = elf_begin(elf->fd, ELF_C_READ_MMAP, NULL); if (elf->handle == NULL) goto err_close; if (gelf_getehdr(elf->handle, &elf->ehdr) == NULL) goto err_end; return 0; err_end: elf_end(elf->handle); err_close: pr_dbg("ELF error when loading symbols: %s\n", elf_errmsg(elf_errno())); close(elf->fd); elf->fd = -1; err: elf->handle = NULL; return -1; } void elf_finish(struct uftrace_elf_data *elf) { if (elf->fd < 0) return; elf_end(elf->handle); elf->handle = NULL; close(elf->fd); elf->fd = -1; } void elf_get_secdata(struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter) { iter->data = elf_getdata((iter)->scn, NULL); } void elf_read_secdata(struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter, unsigned offset, void *buf, size_t len) { memcpy(buf, iter->data->d_buf + offset, len); } #endif /* HAVE_LIBELF */ uftrace-0.15.2/utils/symbol-libelf.h000066400000000000000000000135501455365734300173240ustar00rootroot00000000000000#ifndef UFTRACE_SYMBOL_LIBELF_H #define UFTRACE_SYMBOL_LIBELF_H #include struct uftrace_elf_data { int fd; Elf *handle; GElf_Ehdr ehdr; }; struct uftrace_elf_iter { size_t i; size_t nr; union { GElf_Phdr phdr; GElf_Shdr shdr; GElf_Nhdr nhdr; GElf_Sym sym; GElf_Dyn dyn; GElf_Rel rel; GElf_Rela rela; }; void *note_name; void *note_desc; /* hidden */ int type; size_t str_idx; Elf_Scn *scn; Elf_Data *data; }; #define elf_get_name(elf, iter, name) elf_strptr((elf)->handle, (iter)->str_idx, name) #define elf_get_symbol(elf, iter, idx) gelf_getsym((iter)->data, idx, &(iter)->sym) #define elf_get_strtab(elf, iter, idx) (iter)->str_idx = idx #define elf_symbol_type(sym) GELF_ST_TYPE((sym)->st_info) #define elf_symbol_bind(sym) GELF_ST_BIND((sym)->st_info) #define elf_rel_symbol(rel) GELF_R_SYM((rel)->r_info) #define elf_rel_type(rel) GELF_R_TYPE((rel)->r_info) #define elf_for_each_phdr(elf, iter) \ for ((iter)->i = 0, (iter)->nr = (elf)->ehdr.e_phnum; \ (iter)->i < (iter)->nr && gelf_getphdr((elf)->handle, (iter)->i, &(iter)->phdr); \ (iter)->i++) #define elf_for_each_shdr(elf, iter) \ for (elf_getshdrstrndx((elf)->handle, &(iter)->str_idx), \ (iter)->scn = elf_nextscn((elf)->handle, NULL); \ (iter)->scn && gelf_getshdr((iter)->scn, &(iter)->shdr); \ (iter)->scn = elf_nextscn((elf)->handle, (iter)->scn)) /* iter->scn and iter->shdr must point DYNAMIC section */ #define elf_for_each_dynamic(elf, iter) \ for ((iter)->i = 0, (iter)->nr = (iter)->shdr.sh_size / (iter)->shdr.sh_entsize, \ (iter)->str_idx = (iter)->shdr.sh_link, (iter)->type = (iter)->shdr.sh_type, \ (iter)->data = elf_getdata((iter)->scn, NULL); \ (iter)->type == SHT_DYNAMIC && (iter)->i < (iter)->nr && \ gelf_getdyn((iter)->data, (iter)->i, &(iter)->dyn); \ (iter)->i++) /* iter->scn and iter->shdr must point SYMTAB section */ #define elf_for_each_symbol(elf, iter) \ for ((iter)->i = 0, (iter)->nr = (iter)->shdr.sh_size / (iter)->shdr.sh_entsize, \ (iter)->str_idx = (iter)->shdr.sh_link, (iter)->type = (iter)->shdr.sh_type, \ (iter)->data = elf_getdata((iter)->scn, NULL); \ (iter)->type == SHT_SYMTAB && (iter)->i < (iter)->nr && \ gelf_getsym((iter)->data, (iter)->i, &(iter)->sym); \ (iter)->i++) /* iter->sec and iter->shdr must point DYNSYM section */ #define elf_for_each_dynamic_symbol(elf, iter) \ for ((iter)->i = 0, (iter)->nr = (iter)->shdr.sh_size / (iter)->shdr.sh_entsize, \ (iter)->str_idx = (iter)->shdr.sh_link, (iter)->type = (iter)->shdr.sh_type, \ (iter)->data = elf_getdata((iter)->scn, NULL); \ (iter)->type == SHT_DYNSYM && (iter)->i < (iter)->nr && \ gelf_getsym((iter)->data, (iter)->i, &(iter)->sym); \ (iter)->i++) /* iter->sec and iter->shdr must point REL section */ #define elf_for_each_rel(elf, iter) \ for ((iter)->i = 0, (iter)->nr = (iter)->shdr.sh_size / (iter)->shdr.sh_entsize, \ (iter)->type = (iter)->shdr.sh_type, (iter)->data = elf_getdata((iter)->scn, NULL); \ (iter)->type == SHT_REL && (iter)->i < (iter)->nr && \ gelf_getrel((iter)->data, (iter)->i, &(iter)->rel); \ (iter)->i++) /* iter->sec and iter->shdr must point RELA section */ #define elf_for_each_rela(elf, iter) \ for ((iter)->i = 0, (iter)->nr = (iter)->shdr.sh_size / (iter)->shdr.sh_entsize, \ (iter)->type = (iter)->shdr.sh_type, (iter)->data = elf_getdata((iter)->scn, NULL); \ (iter)->type == SHT_RELA && (iter)->i < (iter)->nr && \ gelf_getrela((iter)->data, (iter)->i, &(iter)->rela); \ (iter)->i++) /* iter->sec and iter->shdr must point NOTE section */ #define elf_for_each_note(elf, iter) \ for ((iter)->i = 0, (iter)->type = (iter)->shdr.sh_type, \ (iter)->data = elf_getdata((iter)->scn, NULL); \ (iter)->type == SHT_NOTE && \ ((iter)->nr = \ gelf_getnote((iter)->data, (iter)->i, &(iter)->nhdr, \ (size_t *)&(iter)->note_name, (size_t *)&(iter)->note_desc)) && \ ((iter)->note_name = (iter)->data->d_buf + (size_t)(iter)->note_name) && \ ((iter)->note_desc = (iter)->data->d_buf + (size_t)(iter)->note_desc); \ (iter)->i = (iter)->nr) int elf_init(const char *filename, struct uftrace_elf_data *elf); void elf_finish(struct uftrace_elf_data *elf); void elf_get_secdata(struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter); void elf_read_secdata(struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter, unsigned offset, void *buf, size_t len); #endif /* UFTRACE_SYMBOL_LIBELF_H */ uftrace-0.15.2/utils/symbol-rawelf.c000066400000000000000000000072221455365734300173410ustar00rootroot00000000000000#ifndef HAVE_LIBELF #include #include #include #include #include /* This should be defined before #include "utils.h" */ #define PR_FMT "symbol" #define PR_DOMAIN DBG_SYMBOL #include "utils/symbol-rawelf.h" #include "utils/utils.h" /* * ELF File Header validation logic. */ int elf_validate(struct uftrace_elf_data *elf) { Elf_Ehdr *ehdr; int eclass, data, version; unsigned long size, offset; ehdr = &elf->ehdr; elf->has_shdr = false; // validate ELF Magic. if (memcmp(ehdr, ELFMAG, SELFMAG)) { pr_dbg2("ELF Signature not matched\n"); return -1; } // validate some field of elf header. eclass = (int)ehdr->e_ident[EI_CLASS]; data = (int)ehdr->e_ident[EI_DATA]; version = (int)ehdr->e_ident[EI_VERSION]; if (eclass != get_elf_class()) { pr_dbg2("Unsupported eclass : [%d]\n", eclass); return -1; } if (data != get_elf_endian()) { pr_dbg2("Unsupported endian : [%d]\n", data); return -1; } if (!(version > EV_NONE && version < EV_NUM)) { pr_dbg2("Invalid ELF version : [%d]\n", version); return -1; } if (ehdr->e_phnum == 0 || ehdr->e_phentsize == 0) { pr_dbg2("Invalid Program header. Num:[%d] Size:[%d]\n", ehdr->e_phnum, ehdr->e_phentsize); return -1; } if (ehdr->e_shnum > 0 && ehdr->e_shentsize == 0) { pr_dbg2("Section Header entry size cannot be 0.\n"); return -1; } // validate program header offset. size = (long)elf->file_size; offset = ehdr->e_phoff + ehdr->e_phnum * ehdr->e_phentsize; if (offset > size) { pr_dbg2("Invalid Program Header offset:[%lu], size:[%lu]\n", offset, size); return -1; } // section header is optional. offset = ehdr->e_shoff + ehdr->e_shnum * ehdr->e_shentsize; if (offset <= size) elf->has_shdr = true; return 0; } int elf_init(const char *filename, struct uftrace_elf_data *elf) { struct stat stbuf; elf->fd = open(filename, O_RDONLY); if (elf->fd < 0) goto err; if (fstat(elf->fd, &stbuf) < 0) goto err_close; elf->file_size = stbuf.st_size; elf->file_map = mmap(NULL, elf->file_size, PROT_READ, MAP_PRIVATE, elf->fd, 0); if (elf->file_map == MAP_FAILED) goto err_close; memcpy(&elf->ehdr, elf->file_map, sizeof(elf->ehdr)); if (elf_validate(elf) < 0) goto err_unmap; return 0; err_unmap: munmap(elf->file_map, elf->file_size); err_close: close(elf->fd); elf->fd = -1; err: elf->file_map = NULL; return -1; } void elf_finish(struct uftrace_elf_data *elf) { if (elf->fd < 0) return; munmap(elf->file_map, elf->file_size); elf->file_map = NULL; close(elf->fd); elf->fd = -1; } void elf_get_strtab(struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter, int shidx) { if (elf->has_shdr) { Elf_Shdr *shdr = elf->file_map + elf->ehdr.e_shoff; iter->strtab = elf->file_map + shdr[shidx].sh_offset; } } void elf_get_secdata(struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter) { iter->ent_size = iter->shdr.sh_entsize; iter->data = elf->file_map + iter->shdr.sh_offset; } void elf_read_secdata(struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter, unsigned offset, void *buf, size_t len) { memcpy(buf, &iter->data[offset], len); } #ifdef UNIT_TEST TEST_CASE(rawelf_validate) { struct uftrace_elf_data elf; struct uftrace_elf_iter iter; Elf_Ehdr *ehdr; unsigned int count; /* elf_init() calls elf_validate() internally */ if (elf_init("/proc/self/exe", &elf) < 0) return TEST_NG; ehdr = &elf.ehdr; count = 0; elf_for_each_phdr(&elf, &iter) count++; TEST_EQ(ehdr->e_phnum, count); count = 0; elf_for_each_shdr(&elf, &iter) count++; TEST_EQ(ehdr->e_shnum, count); elf_finish(&elf); return TEST_OK; } #endif /* UNIT_TEST */ #endif /* HAVE_LIBELF */ uftrace-0.15.2/utils/symbol-rawelf.h000066400000000000000000000167511455365734300173550ustar00rootroot00000000000000#ifndef UFTRACE_SYMBOL_RAWELF_H #define UFTRACE_SYMBOL_RAWELF_H #include #include #include #ifdef __LP64__ #define ELF_SIZE 64 #else #define ELF_SIZE 32 #endif /* re-define Elf64_Addr as Elf_Addr */ #define ElfT(name) ElfT1(ELF_SIZE, name) #define ElfT1(N, name) ElfType(N, name) #define ElfType(N, name) Elf##N##_##name typedef ElfT(Ehdr) Elf_Ehdr; typedef ElfT(Phdr) Elf_Phdr; typedef ElfT(Shdr) Elf_Shdr; typedef ElfT(Nhdr) Elf_Nhdr; typedef ElfT(Sym) Elf_Sym; typedef ElfT(Dyn) Elf_Dyn; typedef ElfT(Rel) Elf_Rel; typedef ElfT(Rela) Elf_Rela; /* re-define ELF32_ST_TYPE() as ELF_ST_TYPE() */ #define ELF_M(ACT) ELF_M1(ELF_SIZE, ACT) #define ELF_M1(N, ACT) ELF_MACRO(N, ACT) #define ELF_MACRO(N, ACT) ELF##N##_##ACT #ifndef ELF_ST_BIND #define ELF_ST_BIND(v) ELF_M(ST_BIND)(v) #endif #ifndef ELF_ST_TYPE #define ELF_ST_TYPE(v) ELF_M(ST_TYPE)(v) #endif #ifndef ELF_R_SYM #define ELF_R_SYM(i) ELF_M(R_SYM)(i) #endif #ifndef ELF_R_TYPE #define ELF_R_TYPE(i) ELF_M(R_TYPE)(i) #endif struct uftrace_elf_data { int fd; void *file_map; size_t file_size; Elf_Ehdr ehdr; unsigned long flags; bool has_shdr; }; struct uftrace_elf_iter { size_t i; size_t nr; union { Elf_Phdr phdr; Elf_Shdr shdr; Elf_Nhdr nhdr; Elf_Sym sym; Elf_Dyn dyn; Elf_Rel rel; Elf_Rela rela; }; void *note_name; void *note_desc; /* hidden */ int type; int ent_size; char *strtab; char *data; }; #define elf_get_name(elf, iter, name) (char *)(iter)->strtab + name #define elf_get_symbol(elf, iter, idx) \ memcpy(&(iter)->sym, &(iter)->data[idx * (iter)->ent_size], (iter)->ent_size) #define elf_symbol_type(sym) ELF_ST_TYPE((sym)->st_info) #define elf_symbol_bind(sym) ELF_ST_BIND((sym)->st_info) #define elf_rel_symbol(rel) ELF_R_SYM((rel)->r_info) #define elf_rel_type(rel) ELF_R_TYPE((rel)->r_info) #define elf_for_each_phdr(elf, iter) \ for ((iter)->i = 0, (iter)->nr = (elf)->ehdr.e_phnum; \ (iter)->i < (iter)->nr && \ memcpy(&(iter)->phdr, \ (elf)->file_map + (elf)->ehdr.e_phoff + (iter)->i * (elf)->ehdr.e_phentsize, \ (elf)->ehdr.e_phentsize); \ (iter)->i++) #define elf_for_each_shdr(elf, iter) \ for (elf_get_strtab((elf), (iter), (elf)->ehdr.e_shstrndx), \ (iter)->i = 0, (iter)->nr = (elf)->ehdr.e_shnum; \ (iter)->i < (iter)->nr && (elf)->has_shdr && \ memcpy(&(iter)->shdr, \ (elf)->file_map + (elf)->ehdr.e_shoff + (iter)->i * (elf)->ehdr.e_shentsize, \ (elf)->ehdr.e_shentsize); \ (iter)->i++) /* iter->shdr must point DYNAMIC section */ #define elf_for_each_dynamic(elf, iter) \ for (elf_get_secdata((elf), (iter)), elf_get_strtab((elf), (iter), (iter)->shdr.sh_link), \ (iter)->type = (iter)->shdr.sh_type, (iter)->ent_size = (iter)->shdr.sh_entsize, \ (iter)->i = 0, (iter)->nr = (iter)->shdr.sh_size / (iter)->ent_size; \ (iter)->type == SHT_DYNAMIC && (iter)->i < (iter)->nr && \ memcpy(&(iter)->dyn, &(iter)->data[(iter)->i * (iter)->ent_size], (iter)->ent_size); \ (iter)->i++) /* iter->shdr must point SYMTAB section */ #define elf_for_each_symbol(elf, iter) \ for (elf_get_secdata((elf), (iter)), elf_get_strtab((elf), (iter), (iter)->shdr.sh_link), \ (iter)->type = (iter)->shdr.sh_type, (iter)->ent_size = (iter)->shdr.sh_entsize, \ (iter)->i = 0, (iter)->nr = (iter)->shdr.sh_size / (iter)->ent_size; \ (iter)->type == SHT_SYMTAB && (iter)->i < (iter)->nr && \ memcpy(&(iter)->sym, &(iter)->data[(iter)->i * (iter)->ent_size], (iter)->ent_size); \ (iter)->i++) /* iter->shdr must point DYNSYM section */ #define elf_for_each_dynamic_symbol(elf, iter) \ for (elf_get_secdata((elf), (iter)), elf_get_strtab((elf), (iter), (iter)->shdr.sh_link), \ (iter)->type = (iter)->shdr.sh_type, (iter)->ent_size = (iter)->shdr.sh_entsize, \ (iter)->i = 0, (iter)->nr = (iter)->shdr.sh_size / (iter)->ent_size; \ (iter)->type == SHT_DYNSYM && (iter)->i < (iter)->nr && \ memcpy(&(iter)->sym, &(iter)->data[(iter)->i * (iter)->ent_size], (iter)->ent_size); \ (iter)->i++) /* iter->shdr must point REL section */ #define elf_for_each_rel(elf, iter) \ for (elf_get_secdata((elf), (iter)), \ (iter)->type = (iter)->shdr.sh_type, (iter)->ent_size = (iter)->shdr.sh_entsize, \ (iter)->i = 0, (iter)->nr = (iter)->shdr.sh_size / (iter)->ent_size; \ (iter)->type == SHT_REL && (iter)->i < (iter)->nr && \ memcpy(&(iter)->rel, &(iter)->data[(iter)->i * (iter)->ent_size], (iter)->ent_size); \ (iter)->i++) /* iter->shdr must point RELA section */ #define elf_for_each_rela(elf, iter) \ for (elf_get_secdata((elf), (iter)), \ (iter)->type = (iter)->shdr.sh_type, (iter)->ent_size = (iter)->shdr.sh_entsize, \ (iter)->i = 0, (iter)->nr = (iter)->shdr.sh_size / (iter)->ent_size; \ (iter)->type == SHT_RELA && (iter)->i < (iter)->nr && \ memcpy(&(iter)->rela, &(iter)->data[(iter)->i * (iter)->ent_size], (iter)->ent_size); \ (iter)->i++) /* iter->shdr must point NOTE section */ #define elf_for_each_note(elf, iter) \ for (elf_get_secdata((elf), (iter)), (iter)->type = (iter)->shdr.sh_type, \ (iter)->ent_size = (iter)->shdr.sh_entsize, \ (iter)->i = 0, (iter)->nr = (iter)->shdr.sh_size; \ (iter)->type == SHT_NOTE && (iter)->i < (iter)->nr - sizeof((iter)->nhdr) && \ memcpy(&(iter)->nhdr, (iter)->data + (iter)->i, sizeof((iter)->nhdr)) && \ ((iter)->note_name = (iter)->data + (iter)->i + sizeof((iter)->nhdr)) && \ ((iter)->note_desc = (iter)->note_name + ALIGN((iter)->nhdr.n_namesz, 4)); \ (iter)->i += sizeof((iter)->nhdr) + ALIGN((iter)->nhdr.n_namesz, 4) + \ ALIGN((iter)->nhdr.n_descsz, 4)) int elf_init(const char *filename, struct uftrace_elf_data *elf); void elf_finish(struct uftrace_elf_data *elf); void elf_get_strtab(struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter, int shidx); void elf_get_secdata(struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter); void elf_read_secdata(struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter, unsigned offset, void *buf, size_t len); #endif /* UFTRACE_SYMBOL_LIBELF_H */ uftrace-0.15.2/utils/symbol.c000066400000000000000000001403561455365734300160710ustar00rootroot00000000000000/* * symbol management routines for uftrace * * Copyright (C) 2014-2018, LG Electronics, Namhyung Kim * * Released under the GPL v2. */ #include #include #include #include #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/filter.h" #include "utils/rbtree.h" #include "utils/symbol.h" #include "utils/utils.h" #ifndef EM_AARCH64 #define EM_AARCH64 183 #endif #ifndef EM_RISCV #define EM_RISCV 243 #endif /* (global) symbol for kernel */ static struct uftrace_module kernel; /* prevent duplicate symbols table loading */ static struct rb_root modules = RB_ROOT; struct uftrace_symbol sched_sym = { .addr = EVENT_ID_PERF_SCHED_BOTH, .size = 1, .type = ST_LOCAL_FUNC, .name = "linux:schedule", }; struct uftrace_symbol sched_preempt_sym = { .addr = EVENT_ID_PERF_SCHED_BOTH_PREEMPT, .size = 1, .type = ST_LOCAL_FUNC, .name = "linux:schedule (pre-empted)", }; static int addrsort(const void *a, const void *b) { const struct uftrace_symbol *syma = a; const struct uftrace_symbol *symb = b; if (syma->addr > symb->addr) return 1; if (syma->addr < symb->addr) return -1; return 0; } static int addrfind(const void *a, const void *b) { uint64_t addr = *(uint64_t *)a; const struct uftrace_symbol *sym = b; if (sym->addr <= addr && addr < sym->addr + sym->size) return 0; if (sym->addr > addr) return -1; return 1; } static int namesort(const void *a, const void *b) { const struct uftrace_symbol *syma = *(const struct uftrace_symbol **)a; const struct uftrace_symbol *symb = *(const struct uftrace_symbol **)b; return strcmp(syma->name, symb->name); } static int namefind(const void *a, const void *b) { const char *name = a; const struct uftrace_symbol *sym = *(const struct uftrace_symbol **)b; return strcmp(name, sym->name); } char *get_soname(const char *filename) { struct uftrace_elf_data elf; struct uftrace_elf_iter iter; char *soname = NULL; if (elf_init(filename, &elf) < 0) { pr_dbg("error during open symbol file: %s: %m\n", filename); return NULL; } elf_for_each_shdr(&elf, &iter) { if (iter.shdr.sh_type == SHT_DYNAMIC) break; } elf_for_each_dynamic(&elf, &iter) { if (iter.dyn.d_tag != DT_SONAME) continue; soname = xstrdup(elf_get_name(&elf, &iter, iter.dyn.d_un.d_ptr)); break; } elf_finish(&elf); return soname; } bool has_dependency(const char *filename, const char *libname) { bool ret = false; struct uftrace_elf_data elf; struct uftrace_elf_iter iter; if (elf_init(filename, &elf) < 0) { pr_dbg("error during open symbol file: %s: %m\n", filename); return false; } elf_for_each_shdr(&elf, &iter) { if (iter.shdr.sh_type == SHT_DYNAMIC) break; } elf_for_each_dynamic(&elf, &iter) { char *soname; if (iter.dyn.d_tag != DT_NEEDED) continue; soname = elf_get_name(&elf, &iter, iter.dyn.d_un.d_ptr); if (!strcmp(soname, libname)) { ret = true; break; } } elf_finish(&elf); return ret; } int check_static_binary(const char *filename) { int ret = 1; struct uftrace_elf_data elf; struct uftrace_elf_iter iter; if (elf_init(filename, &elf) < 0) { pr_dbg("error during open symbol file: %s: %m\n", filename); return -1; } elf_for_each_phdr(&elf, &iter) { if (iter.phdr.p_type == PT_DYNAMIC) { ret = 0; break; } } elf_finish(&elf); return ret; } bool check_script_file(const char *filename, char *buf, size_t len) { char magic[2]; int fd; char *p; bool ret = false; fd = open(filename, O_RDONLY); if (fd < 0) return false; if (read(fd, magic, sizeof(magic)) < 0) goto out; if (magic[0] != '#' || magic[1] != '!') goto out; if (read(fd, buf, len) < 0) { goto out; } buf[len - 1] = '\0'; p = strchr(buf, '\n'); if (p) *p = '\0'; ret = true; out: close(fd); return ret; } static bool is_symbol_end(const char *name) { if (!strcmp(name, "__sym_end") || !strcmp(name, "__dynsym_end") || !strcmp(name, "__func_end")) { return true; } return false; } static void unload_symtab(struct uftrace_symtab *symtab) { size_t i; for (i = 0; i < symtab->nr_sym; i++) { struct uftrace_symbol *sym = symtab->sym + i; free(sym->name); } free(symtab->sym_names); free(symtab->sym); symtab->nr_sym = 0; symtab->sym = NULL; symtab->sym_names = NULL; } static int load_symbol(struct uftrace_symtab *symtab, unsigned long prev_sym_value, unsigned long long offset, unsigned long flags, struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter) { char *name; struct uftrace_symbol *sym; typeof(iter->sym) *elf_sym = &iter->sym; if (elf_sym->st_shndx == STN_UNDEF) return 0; if (elf_sym->st_size == 0) return 0; if (elf_symbol_type(elf_sym) != STT_FUNC && elf_symbol_type(elf_sym) != STT_GNU_IFUNC && elf_symbol_type(elf_sym) != STT_OBJECT) return 0; /* skip aliases */ if (prev_sym_value == elf_sym->st_value) return 0; sym = &symtab->sym[symtab->nr_sym++]; sym->addr = elf_sym->st_value + offset; sym->size = elf_sym->st_size; switch (elf_symbol_bind(elf_sym)) { case STB_LOCAL: if (elf_symbol_type(elf_sym) == STT_OBJECT) sym->type = ST_LOCAL_DATA; else sym->type = ST_LOCAL_FUNC; break; case STB_GLOBAL: if (elf_symbol_type(elf_sym) == STT_OBJECT) sym->type = ST_GLOBAL_DATA; else sym->type = ST_GLOBAL_FUNC; break; case STB_WEAK: if (elf_symbol_type(elf_sym) == STT_OBJECT) sym->type = ST_WEAK_DATA; else sym->type = ST_WEAK_FUNC; break; case STB_GNU_UNIQUE: if (elf_symbol_type(elf_sym) == STT_OBJECT) { sym->type = ST_UNIQUE_DATA; break; } /* fall through */ default: sym->type = ST_UNKNOWN; break; } name = elf_get_name(elf, iter, elf_sym->st_name); if (flags & SYMTAB_FL_DEMANGLE) sym->name = demangle(name); else sym->name = xstrdup(name); pr_dbg4("[%zd] %c %" PRIx64 " + %-5u %s\n", symtab->nr_sym, sym->type, sym->addr, sym->size, sym->name); return 1; } static void sort_symtab(struct uftrace_symtab *symtab) { unsigned i; int dup_syms = 0; qsort(symtab->sym, symtab->nr_sym, sizeof(*symtab->sym), addrsort); /* remove duplicated (overlapped?) symbols */ for (i = 0; i < symtab->nr_sym - 1; i++) { struct uftrace_symbol *curr = &symtab->sym[i]; struct uftrace_symbol *next = &symtab->sym[i + 1]; int count = 0; char *bestname = curr->name; while (curr->addr == next->addr && next < &symtab->sym[symtab->nr_sym]) { /* prefer names not started by '_' (if not mangled) */ if (bestname[0] == '_' && bestname[1] != 'Z' && next->name[0] != '_') bestname = next->name; count++; next++; } if (count) { struct uftrace_symbol *tmp = curr; bestname = xstrdup(bestname); while (tmp < next - 1) { free(tmp->name); tmp++; } memmove(curr, next - 1, (symtab->nr_sym - i - count) * sizeof(*next)); free(curr->name); curr->name = bestname; symtab->nr_sym -= count; dup_syms += count; } } if (dup_syms) pr_dbg4("removed %d duplicates\n", dup_syms); symtab->nr_alloc = symtab->nr_sym; symtab->sym = xrealloc(symtab->sym, symtab->nr_sym * sizeof(*symtab->sym)); symtab->sym_names = xmalloc(sizeof(*symtab->sym_names) * symtab->nr_sym); for (i = 0; i < symtab->nr_sym; i++) symtab->sym_names[i] = &symtab->sym[i]; qsort(symtab->sym_names, symtab->nr_sym, sizeof(*symtab->sym_names), namesort); symtab->name_sorted = true; } static int load_symtab(struct uftrace_symtab *symtab, const char *filename, unsigned long long offset, unsigned long flags) { int ret = -1; unsigned long prev_sym_value = -1; struct uftrace_elf_data elf; struct uftrace_elf_iter iter; if (elf_init(filename, &elf) < 0) { pr_dbg("error during open symbol file: %s: %m\n", filename); return -1; } if (flags & SYMTAB_FL_ADJ_OFFSET) { elf_for_each_phdr(&elf, &iter) { if (iter.phdr.p_type == PT_LOAD) { offset -= iter.phdr.p_vaddr; break; } } } elf_for_each_shdr(&elf, &iter) { if (iter.shdr.sh_type == SHT_SYMTAB) break; } if (iter.shdr.sh_type != SHT_SYMTAB) { /* * fallback to dynamic symbol table when there's no symbol table * (e.g. stripped binary built with -rdynamic option) */ elf_for_each_shdr(&elf, &iter) { if (iter.shdr.sh_type == SHT_DYNSYM) break; } if (iter.shdr.sh_type != SHT_DYNSYM) { pr_dbg("no symbol table was found\n"); goto out; } pr_dbg4("no symtab, using dynsyms instead\n"); } if (iter.shdr.sh_size == 0 || iter.shdr.sh_entsize == 0) goto out; /* pre-allocate enough symbol table entries */ symtab->nr_alloc = iter.shdr.sh_size / iter.shdr.sh_entsize; symtab->sym = xmalloc(symtab->nr_alloc * sizeof(*symtab->sym)); pr_dbg3("loading symbols from %s (offset: %#llx)\n", filename, offset); if (iter.shdr.sh_type == SHT_SYMTAB) { elf_for_each_symbol(&elf, &iter) { if (load_symbol(symtab, prev_sym_value, offset, flags, &elf, &iter)) prev_sym_value = iter.sym.st_value; } } else { elf_for_each_dynamic_symbol(&elf, &iter) { if (load_symbol(symtab, prev_sym_value, offset, flags, &elf, &iter)) prev_sym_value = iter.sym.st_value; } } pr_dbg4("loaded %zd symbols\n", symtab->nr_sym); if (symtab->nr_sym == 0) { free(symtab->sym); symtab->sym = NULL; goto out; } /* also fixup the size of symbol table */ sort_symtab(symtab); ret = 0; out: elf_finish(&elf); return ret; } static int load_dyn_symbol(struct uftrace_symtab *dsymtab, int sym_idx, unsigned long offset, unsigned long flags, unsigned long plt_entsize, unsigned long prev_addr, struct uftrace_elf_data *elf, struct uftrace_elf_iter *iter) { char *name; struct uftrace_symbol *sym; elf_get_symbol(elf, iter, sym_idx); name = elf_get_name(elf, iter, iter->sym.st_name); if (*name == '\0') return 0; sym = &dsymtab->sym[dsymtab->nr_sym++]; if (iter->sym.st_value && iter->sym.st_shndx == STN_UNDEF) sym->addr = iter->sym.st_value + offset; else sym->addr = prev_addr + plt_entsize; sym->size = plt_entsize; sym->type = ST_PLT_FUNC; if (flags & SYMTAB_FL_DEMANGLE) sym->name = demangle(name); else sym->name = xstrdup(name); pr_dbg4("[%zd] %c %" PRIx64 " + %-5u %s\n", dsymtab->nr_sym, sym->type, sym->addr, sym->size, sym->name); return 1; } void sort_dynsymtab(struct uftrace_symtab *dsymtab) { unsigned i, k; if (dsymtab->nr_sym == 0) return; dsymtab->nr_alloc = dsymtab->nr_sym; dsymtab->sym = xrealloc(dsymtab->sym, dsymtab->nr_sym * sizeof(*dsymtab->sym)); /* * abuse ->sym_names[] to save original index */ dsymtab->sym_names = xmalloc(sizeof(*dsymtab->sym_names) * dsymtab->nr_sym); /* save current address for each symbol */ for (i = 0; i < dsymtab->nr_sym; i++) dsymtab->sym_names[i] = (void *)(long)dsymtab->sym[i].addr; /* sort ->sym by address now */ qsort(dsymtab->sym, dsymtab->nr_sym, sizeof(*dsymtab->sym), addrsort); /* find position of sorted symbol */ for (i = 0; i < dsymtab->nr_sym; i++) { for (k = 0; k < dsymtab->nr_sym; k++) { if (dsymtab->sym_names[i] == (void *)(long)dsymtab->sym[k].addr) { dsymtab->sym_names[i] = &dsymtab->sym[k]; break; } } } dsymtab->name_sorted = false; } __weak int arch_load_dynsymtab_noplt(struct uftrace_symtab *dsymtab, struct uftrace_elf_data *elf, unsigned long offset, unsigned long flags) { return 0; } int load_elf_dynsymtab(struct uftrace_symtab *dsymtab, struct uftrace_elf_data *elf, unsigned long offset, unsigned long flags) { int ret = -1; char *shstr; unsigned long plt_addr = 0; unsigned long prev_addr; size_t plt_entsize = 1; int rel_type = SHT_NULL; bool found_dynamic = false; bool found_dynsym = false; bool found_pltsec = false; struct uftrace_elf_iter sec_iter; struct uftrace_elf_iter dyn_iter; struct uftrace_elf_iter rel_iter; unsigned symidx; struct uftrace_symbol *sym; if (flags & SYMTAB_FL_ADJ_OFFSET) { elf_for_each_phdr(elf, &sec_iter) { if (sec_iter.phdr.p_type == PT_LOAD) { offset -= sec_iter.phdr.p_vaddr; break; } } } elf_for_each_shdr(elf, &sec_iter) { typeof(sec_iter.shdr) *shdr = &sec_iter.shdr; shstr = elf_get_name(elf, &sec_iter, shdr->sh_name); if (strcmp(shstr, ".dynsym") == 0) { memcpy(&dyn_iter, &sec_iter, sizeof(sec_iter)); elf_get_strtab(elf, &dyn_iter, shdr->sh_link); elf_get_secdata(elf, &dyn_iter); found_dynsym = true; } else if (strcmp(shstr, ".rela.plt") == 0) { memcpy(&rel_iter, &sec_iter, sizeof(sec_iter)); rel_type = SHT_RELA; } else if (strcmp(shstr, ".rel.plt") == 0) { memcpy(&rel_iter, &sec_iter, sizeof(sec_iter)); rel_type = SHT_REL; } else if (strcmp(shstr, ".plt") == 0) { plt_addr = shdr->sh_addr + offset; plt_entsize = shdr->sh_entsize; } else if (strcmp(shstr, ".plt.sec") == 0) { plt_addr = shdr->sh_addr + offset; plt_entsize = shdr->sh_entsize; found_pltsec = true; } else if (strcmp(shstr, ".dynamic") == 0) { found_dynamic = true; } } if (!found_dynsym || !found_dynamic) { pr_dbg3("cannot find dynamic symbols.. skipping\n"); ret = 0; goto out; } if (rel_type == SHT_NULL) { arch_load_dynsymtab_noplt(dsymtab, elf, offset, flags); goto out_sort; } if (elf->ehdr.e_machine == EM_ARM) { plt_addr += 8; /* ARM PLT0 size is 20 */ plt_entsize = 12; /* size of R_ARM_JUMP_SLOT */ } else if (elf->ehdr.e_machine == EM_AARCH64) { plt_addr += 16; /* AARCH64 PLT0 size is 32 */ if (plt_entsize == 0) plt_entsize = 16; } else if (elf->ehdr.e_machine == EM_386) { plt_entsize += 12; } else if (elf->ehdr.e_machine == EM_X86_64) { plt_entsize = 16; /* lld (of LLVM) seems to miss setting it */ } else if (elf->ehdr.e_machine == EM_RISCV) { plt_addr += 16; /* RISCV64 PLT0 size is 32 */ } prev_addr = plt_addr; if (found_pltsec) prev_addr -= plt_entsize; /* pre-allocate enough symbol table entries */ dsymtab->nr_alloc = rel_iter.shdr.sh_size / rel_iter.shdr.sh_entsize; dsymtab->sym = xmalloc(dsymtab->nr_alloc * sizeof(*dsymtab->sym)); if (rel_type == SHT_REL) { elf_for_each_rel(elf, &rel_iter) { symidx = elf_rel_symbol(&rel_iter.rel); elf_get_symbol(elf, &dyn_iter, symidx); if (load_dyn_symbol(dsymtab, symidx, offset, flags, plt_entsize, prev_addr, elf, &dyn_iter)) { sym = &dsymtab->sym[dsymtab->nr_sym - 1]; prev_addr = sym->addr; } } } else { elf_for_each_rela(elf, &rel_iter) { symidx = elf_rel_symbol(&rel_iter.rela); elf_get_symbol(elf, &dyn_iter, symidx); if (load_dyn_symbol(dsymtab, symidx, offset, flags, plt_entsize, prev_addr, elf, &dyn_iter)) { sym = &dsymtab->sym[dsymtab->nr_sym - 1]; prev_addr = sym->addr; } } } out_sort: pr_dbg4("loaded %zd symbols\n", dsymtab->nr_sym); if (dsymtab->nr_sym == 0) goto out; /* also fixup the size of symbol table */ sort_dynsymtab(dsymtab); ret = 0; out: return ret; } static void merge_symtabs(struct uftrace_symtab *left, struct uftrace_symtab *right) { size_t nr_sym = left->nr_sym + right->nr_sym; struct uftrace_symbol *syms; size_t i; if (right->nr_sym == 0) return; if (left->nr_sym == 0) { *left = *right; right->nr_sym = 0; right->sym = NULL; right->sym_names = NULL; return; } pr_dbg4("merge two symbol tables (left = %lu, right = %lu)\n", left->nr_sym, right->nr_sym); syms = xmalloc(nr_sym * sizeof(*syms)); if (left->sym[0].addr < right->sym[0].addr) { memcpy(&syms[0], left->sym, left->nr_sym * sizeof(*syms)); memcpy(&syms[left->nr_sym], right->sym, right->nr_sym * sizeof(*syms)); } else { memcpy(&syms[0], right->sym, right->nr_sym * sizeof(*syms)); memcpy(&syms[right->nr_sym], left->sym, left->nr_sym * sizeof(*syms)); } free(left->sym); free(right->sym); left->sym = NULL; right->sym = NULL; free(left->sym_names); free(right->sym_names); left->sym_names = NULL; right->sym_names = NULL; left->nr_sym = left->nr_alloc = nr_sym; left->sym = syms; left->sym_names = xmalloc(nr_sym * sizeof(*left->sym_names)); qsort(left->sym, left->nr_sym, sizeof(*left->sym), addrsort); for (i = 0; i < left->nr_sym; i++) left->sym_names[i] = &left->sym[i]; qsort(left->sym_names, left->nr_sym, sizeof(*left->sym_names), namesort); left->name_sorted = true; } static int load_dynsymtab(struct uftrace_symtab *dsymtab, const char *filename, unsigned long offset, unsigned long flags) { struct uftrace_symtab dsymtab_noplt = {}; struct uftrace_elf_data elf; if (elf_init(filename, &elf) < 0) { pr_dbg("error during open symbol file: %s: %m\n", filename); return -1; } pr_dbg3("loading dynamic symbols from %s (offset: %#lx)\n", filename, offset); load_elf_dynsymtab(dsymtab, &elf, offset, flags); arch_load_dynsymtab_noplt(&dsymtab_noplt, &elf, offset, flags); merge_symtabs(dsymtab, &dsymtab_noplt); elf_finish(&elf); return dsymtab->nr_sym; } static int update_symtab_using_dynsym(struct uftrace_symtab *symtab, const char *filename, unsigned long offset, unsigned long flags) { int ret = -1; int count = 0; struct uftrace_elf_data elf; struct uftrace_elf_iter iter; if (elf_init(filename, &elf) < 0) return -1; if (flags & SYMTAB_FL_ADJ_OFFSET) { elf_for_each_phdr(&elf, &iter) { if (iter.phdr.p_type == PT_LOAD) { offset -= iter.phdr.p_vaddr; break; } } } elf_for_each_shdr(&elf, &iter) { if (iter.shdr.sh_type == SHT_DYNSYM) break; } if (iter.shdr.sh_type != SHT_DYNSYM) { ret = 0; goto out; } pr_dbg4("updating symbol name using dynamic symbols\n"); elf_for_each_dynamic_symbol(&elf, &iter) { struct uftrace_symbol *sym; char *name; uint64_t addr; if (iter.sym.st_shndx == SHN_UNDEF) continue; if (elf_symbol_type(&iter.sym) != STT_FUNC && elf_symbol_type(&iter.sym) != STT_GNU_IFUNC && elf_symbol_type(&iter.sym) != STT_OBJECT) continue; addr = iter.sym.st_value + offset; sym = bsearch(&addr, symtab->sym, symtab->nr_sym, sizeof(*sym), addrfind); if (sym == NULL) continue; name = elf_get_name(&elf, &iter, iter.sym.st_name); if (sym->name[0] != '_' && name[0] == '_') continue; if (sym->name[1] == 'Z') continue; pr_dbg4("update symbol name to %s\n", name); free(sym->name); count++; if (flags & SYMTAB_FL_DEMANGLE) sym->name = demangle(name); else sym->name = xstrdup(name); } ret = 1; if (count) pr_dbg4("updated %d symbols\n", count); qsort(symtab->sym_names, symtab->nr_sym, sizeof(*symtab->sym_names), namesort); symtab->name_sorted = true; out: elf_finish(&elf); return ret; } static void load_python_symtab(struct uftrace_sym_info *sinfo) { char *symfile = NULL; struct uftrace_mmap *map; /* try to load python symtab (if exists) */ xasprintf(&symfile, "%s/%s.sym", sinfo->dirname, UFTRACE_PYTHON_SYMTAB_NAME); if (access(symfile, R_OK) < 0) { free(symfile); return; } /* add a fake map for python script */ map = xzalloc(sizeof(*map) + sizeof(UFTRACE_PYTHON_SYMTAB_NAME)); memcpy(map->prot, "rwxp", 4); strcpy(map->libname, UFTRACE_PYTHON_SYMTAB_NAME); map->len = sizeof(UFTRACE_PYTHON_SYMTAB_NAME) - 1; map->mod = load_module_symtab(sinfo, UFTRACE_PYTHON_SYMTAB_NAME, "no-buildid"); map->start = 0; map->end = ALIGN(map->mod->symtab.nr_sym, 4096); setup_debug_info(symfile, &map->mod->dinfo, 0, false); /* add new map to symtabs */ map->next = sinfo->maps; sinfo->maps = map; free(symfile); } enum uftrace_trace_type check_trace_functions(const char *filename) { struct uftrace_elf_data elf; struct uftrace_elf_iter iter; enum uftrace_trace_type ret = TRACE_ERROR; const char *trace_funcs[] = { "__cyg_profile_func_enter", "__fentry__", "mcount", "_mcount", "__gnu_mcount_nc", }; char *name; unsigned i; if (elf_init(filename, &elf) < 0) { pr_dbg("error during open symbol file: %s: %m\n", filename); return ret; } elf_for_each_shdr(&elf, &iter) { if (iter.shdr.sh_type == SHT_DYNSYM) { elf_get_secdata(&elf, &iter); break; } } if (iter.shdr.sh_type != SHT_DYNSYM) { pr_dbg3("cannot find dynamic symbols.. skipping\n"); ret = TRACE_NONE; goto out; } pr_dbg4("check trace functions in %s\n", filename); elf_for_each_dynamic_symbol(&elf, &iter) { elf_get_symbol(&elf, &iter, iter.i); name = elf_get_name(&elf, &iter, iter.sym.st_name); /* undefined function is ok here */ if (elf_symbol_type(&iter.sym) != STT_FUNC && #ifdef __ANDROID__ // Profiling functions are undefined on Android elf_symbol_type(&iter.sym) != STT_NOTYPE && #endif elf_symbol_type(&iter.sym) != STT_GNU_IFUNC) continue; for (i = 0; i < ARRAY_SIZE(trace_funcs); i++) { if (!strcmp(name, trace_funcs[i])) { if (i == 0) ret = TRACE_CYGPROF; else if (i == 1) ret = TRACE_FENTRY; else ret = TRACE_MCOUNT; goto out; } } } ret = TRACE_NONE; out: elf_finish(&elf); return ret; } struct uftrace_mmap *find_map_by_name(struct uftrace_sym_info *sinfo, const char *prefix) { struct uftrace_mmap *map; char *mod_name; for_each_map(sinfo, map) { mod_name = strrchr(map->libname, '/'); if (mod_name == NULL) mod_name = map->libname; else mod_name++; if (!strncmp(mod_name, prefix, strlen(prefix))) return map; } return NULL; } static int load_module_symbol_file(struct uftrace_symtab *symtab, const char *symfile, uint64_t offset) { FILE *fp; char *line = NULL; size_t len = 0; unsigned int i; unsigned int grow = SYMTAB_GROW; char allowed_types[] = "?TtwPKDdvu"; uint64_t prev_addr = -1; char prev_type = 'X'; fp = fopen(symfile, "r"); if (fp == NULL) { pr_dbg("reading %s failed: %m\n", symfile); return -1; } pr_dbg2("loading symbols from %s: offset = %lx\n", symfile, offset); while (getline(&line, &len, fp) > 0) { struct uftrace_symbol *sym; uint64_t addr; uint32_t size; char type; char *name; char *pos; if (*line == '#') { if (!strncmp(line, "# symbols: ", 11)) { size_t nr_syms = strtoul(line + 11, &pos, 10); size_t size_syms = nr_syms * sizeof(*sym); symtab->nr_alloc = nr_syms; symtab->sym = xrealloc(symtab->sym, size_syms); } continue; } pos = strchr(line, '\n'); if (pos) *pos = '\0'; addr = strtoull(line, &pos, 16); size = 0; if (*pos++ != ' ') { pr_dbg4("invalid symbol file format before type\n"); continue; } type = *pos++; if (isdigit(type)) { /* new symbol file has size info */ size = strtoul(pos - 1, &pos, 16); if (*pos++ != ' ') { pr_dbg4("invalid symbol file format for size\n"); continue; } type = *pos++; } if (*pos++ != ' ') { pr_dbg4("invalid symbol file format after type\n"); continue; } name = pos; /* * remove kernel module if any. * ex) btrfs_end_transaction_throttle [btrfs] */ pos = strchr(name, '\t'); if (pos) *pos = '\0'; if (addr == prev_addr && type == prev_type) { sym = &symtab->sym[symtab->nr_sym - 1]; /* for kernel symbols, replace SyS_xxx to sys_xxx */ if (!strncmp(sym->name, "SyS_", 4) && !strncmp(name, "sys_", 4) && !strcmp(sym->name + 4, name + 4)) strncpy(sym->name, name, 4); /* prefer x64 syscall names than 32 bit ones */ if (!strncmp(sym->name, "__ia32", 6) && !strncmp(name, "__x64", 5) && !strcmp(sym->name + 6, name + 5)) strcpy(sym->name, name); pr_dbg4("skip duplicated symbols: %s\n", name); continue; } if (strchr(allowed_types, type) == NULL) continue; /* * it should be updated after the type check * otherwise, it might access invalid sym * in the above. */ prev_addr = addr; prev_type = type; if (type == ST_UNKNOWN || is_symbol_end(name)) { if (symtab->nr_sym > 0) { sym = &symtab->sym[symtab->nr_sym - 1]; if (sym->size == 0) sym->size = addr + offset - sym->addr; } continue; } if (symtab->nr_sym >= symtab->nr_alloc) { if (symtab->nr_alloc >= grow * 4) grow *= 2; symtab->nr_alloc += grow; symtab->sym = xrealloc(symtab->sym, symtab->nr_alloc * sizeof(*sym)); } sym = &symtab->sym[symtab->nr_sym++]; sym->addr = addr + offset; sym->type = type; sym->name = demangle(name); sym->size = size; pr_dbg4("[%zd] %c %lx + %-5u %s\n", symtab->nr_sym, sym->type, sym->addr, sym->size, sym->name); if (symtab->nr_sym > 1 && sym[-1].size == 0) sym[-1].size = sym->addr - sym[-1].addr; } free(line); qsort(symtab->sym, symtab->nr_sym, sizeof(*symtab->sym), addrsort); symtab->sym_names = xmalloc(sizeof(*symtab->sym_names) * symtab->nr_sym); for (i = 0; i < symtab->nr_sym; i++) symtab->sym_names[i] = &symtab->sym[i]; qsort(symtab->sym_names, symtab->nr_sym, sizeof(*symtab->sym_names), namesort); symtab->name_sorted = true; fclose(fp); return 0; } static void load_module_symbol(struct uftrace_sym_info *sinfo, struct uftrace_module *m) { unsigned flags = sinfo->flags; struct uftrace_symtab dsymtab = {}; if (flags & SYMTAB_FL_USE_SYMFILE) { char *symfile = NULL; char buf[PATH_MAX]; char build_id[BUILD_ID_STR_SIZE]; xasprintf(&symfile, "%s/%s.sym", sinfo->symdir, basename(m->name)); if (access(symfile, F_OK) == 0) { if (check_symbol_file(symfile, buf, sizeof(buf), build_id, sizeof(build_id)) > 0 && ((strcmp(buf, m->name) && !(flags & SYMTAB_FL_SYMS_DIR)) || (build_id[0] && m->build_id[0] && strcmp(build_id, m->build_id)))) { char *new_file; new_file = make_new_symbol_filename(symfile, m->name, m->build_id); free(symfile); symfile = new_file; } } if (access(symfile, F_OK) == 0) load_module_symbol_file(&m->symtab, symfile, 0); free(symfile); if (m->symtab.nr_sym) return; } /* * Currently it uses a single symtab for both normal symbols * and dynamic symbols. Maybe it can be changed later to * support more sophisticated symbol handling. */ load_symtab(&m->symtab, m->name, 0, flags); load_dynsymtab(&dsymtab, m->name, 0, flags); merge_symtabs(&m->symtab, &dsymtab); update_symtab_using_dynsym(&m->symtab, m->name, 0, flags); } struct uftrace_module *load_module_symtab(struct uftrace_sym_info *sinfo, const char *mod_name, char *build_id) { struct rb_node *parent = NULL; struct rb_node **p = &modules.rb_node; struct uftrace_module *m; int pos; while (*p) { parent = *p; m = rb_entry(parent, struct uftrace_module, node); pos = strcmp(m->name, mod_name); if (pos == 0) { pos = strcmp(m->build_id, build_id); if (pos == 0) return m; } if (pos < 0) p = &parent->rb_left; else p = &parent->rb_right; } m = xzalloc(sizeof(*m) + strlen(mod_name) + 1); strcpy(m->name, mod_name); strcpy(m->build_id, build_id); load_module_symbol(sinfo, m); rb_link_node(&m->node, parent, p); rb_insert_color(&m->node, &modules); return m; } void unload_module_symtabs(void) { struct rb_node *n; struct uftrace_module *mod; while (!RB_EMPTY_ROOT(&modules)) { n = rb_first(&modules); rb_erase(n, &modules); mod = rb_entry(n, typeof(*mod), node); unload_symtab(&mod->symtab); free(mod); } } void load_module_symtabs(struct uftrace_sym_info *sinfo) { struct uftrace_mmap *map; static const char *const skip_libs[] = { /* uftrace internal libraries */ "libmcount.so", "libmcount-fast.so", "libmcount-single.so", "libmcount-fast-single.so", }; static const char libstdcpp6[] = "libstdc++.so.6"; size_t k; unsigned long flags = sinfo->flags; const char *exec_path = sinfo->filename; bool check_cpp = false; bool needs_cpp = false; if (flags & SYMTAB_FL_USE_SYMFILE) { /* just use the symfile if it's already saved */ check_cpp = true; needs_cpp = true; } for_each_map(sinfo, map) { const char *libname = basename(map->libname); bool skip = false; for (k = 0; k < ARRAY_SIZE(skip_libs); k++) { if (!strcmp(libname, skip_libs[k])) { skip = true; break; } } if (skip) continue; if (exec_path == NULL) exec_path = map->libname; if (!check_cpp) { if (has_dependency(exec_path, libstdcpp6)) needs_cpp = true; check_cpp = true; } /* load symbols from libstdc++.so only if it's written in C++ */ if (!strncmp(libname, libstdcpp6, strlen(libstdcpp6))) { if (!needs_cpp) continue; } map->mod = load_module_symtab(sinfo, map->libname, map->build_id); } load_python_symtab(sinfo); } /* returns the number of matching entries (1 = path only, 2 = build-id) */ int check_symbol_file(const char *symfile, char *pathname, int pathlen, char *build_id, int build_id_len) { FILE *fp; char *line = NULL; size_t len = 0; int ret = 0; fp = fopen(symfile, "r"); if (fp == NULL) { pr_dbg("reading %s failed: %m\n", symfile); return -1; } memset(build_id, 0, build_id_len); while (getline(&line, &len, fp) > 0) { if (*line != '#') break; if (!strncmp(line, "# path name: ", 13)) { strncpy(pathname, line + 13, pathlen); pathlen = strlen(pathname); if (pathname[pathlen - 1] == '\n') pathname[pathlen - 1] = '\0'; ret++; } if (!strncmp(line, "# build-id: ", 12)) { strncpy(build_id, line + 12, build_id_len - 1); build_id[build_id_len - 1] = '\0'; /* in case it has a shorter build-id */ build_id_len = strlen(build_id); if (build_id[build_id_len - 1] == '\n') build_id[build_id_len - 1] = '\0'; ret++; } } free(line); fclose(fp); return ret; } char *make_new_symbol_filename(const char *symfile, const char *pathname, char *build_id) { const char *p; char *newfile = NULL; int len = strlen(symfile); uint16_t csum = 0; if (strlen(build_id) > 0) { xasprintf(&newfile, "%.*s-%.4s.sym", len - 4, symfile, build_id); return newfile; } /* if there's no build-id, calculate checksum using pathname */ p = pathname; while (*p) csum += (int)*p++; xasprintf(&newfile, "%.*s-%04x.sym", len - 4, symfile, csum); return newfile; } static void save_module_symbol_file(struct uftrace_symtab *stab, const char *pathname, char *build_id, const char *symfile, unsigned long offset) { FILE *fp; unsigned i; struct uftrace_symbol *sym; char *newfile = NULL; if (stab->nr_sym == 0) return; fp = fopen(symfile, "wx"); if (fp == NULL) { char buf[PATH_MAX]; char orig_id[BUILD_ID_STR_SIZE]; if (errno != EEXIST) pr_err("cannot open %s file", symfile); /* read path and build-id from the symbol file */ if (check_symbol_file(symfile, buf, sizeof(buf), orig_id, sizeof(orig_id)) <= 0) { pr_dbg("cannot check symbol file\n"); return; } /* check if same file was already saved */ if (!strcmp(buf, pathname) && !strcmp(orig_id, build_id)) return; newfile = make_new_symbol_filename(symfile, pathname, build_id); symfile = newfile; fp = fopen(newfile, "wx"); if (fp == NULL) { free(newfile); return; } } pr_dbg2("saving symbols to %s\n", symfile); fprintf(fp, "# symbols: %zd\n", stab->nr_sym); fprintf(fp, "# path name: %s\n", pathname); if (strlen(build_id) > 0) fprintf(fp, "# build-id: %s\n", build_id); /* PLT + normal symbols (in any order)*/ for (i = 0; i < stab->nr_sym; i++) { sym = &stab->sym[i]; fprintf(fp, "%016" PRIx64 " %08x %c %s\n", sym->addr - offset, sym->size, (char)sym->type, sym->name); } fclose(fp); free(newfile); } void save_module_symtabs(const char *dirname) { struct rb_node *n = rb_first(&modules); struct uftrace_module *mod; char *symfile = NULL; char build_id[BUILD_ID_STR_SIZE]; while (n != NULL) { mod = rb_entry(n, typeof(*mod), node); xasprintf(&symfile, "%s/%s.sym", dirname, basename(mod->name)); read_build_id(mod->name, build_id, sizeof(build_id)); save_module_symbol_file(&mod->symtab, mod->name, build_id, symfile, 0); free(symfile); symfile = NULL; n = rb_next(n); } } int save_kernel_symbol(char *dirname) { char *symfile = NULL; char buf[PATH_MAX]; FILE *ifp, *ofp; ssize_t len; int ret = 0; xasprintf(&symfile, "%s/kallsyms", dirname); ifp = fopen("/proc/kallsyms", "r"); ofp = fopen(symfile, "w"); if (ifp == NULL || ofp == NULL) pr_err("cannot open kernel symbol file"); while ((len = fread(buf, 1, sizeof(buf), ifp)) > 0) fwrite(buf, 1, len, ofp); ret = ferror(ifp) ? -1 : 0; fclose(ifp); fclose(ofp); free(symfile); return ret; } int load_kernel_symbol(char *dirname) { unsigned i; char *symfile = NULL; /* abuse it for checking symbol loading */ if (kernel.node.rb_parent_color) return 0; xasprintf(&symfile, "%s/kallsyms", dirname); if (load_module_symbol_file(&kernel.symtab, symfile, 0) < 0) { free(symfile); return -1; } for (i = 0; i < kernel.symtab.nr_sym; i++) kernel.symtab.sym[i].type = ST_KERNEL_FUNC; kernel.node.rb_parent_color = 1; free(symfile); return 0; } struct uftrace_symtab *get_kernel_symtab(void) { return &kernel.symtab; } struct uftrace_module *get_kernel_module(void) { return &kernel; } void build_dynsym_idxlist(struct uftrace_symtab *dsymtab, struct dynsym_idxlist *idxlist, const char *symlist[], unsigned symcount) { unsigned i, k; unsigned *idx = NULL; unsigned count = 0; for (i = 0; i < dsymtab->nr_sym; i++) { for (k = 0; k < symcount; k++) { if (!strcmp(dsymtab->sym_names[i]->name, symlist[k])) { idx = xrealloc(idx, (count + 1) * sizeof(*idx)); idx[count++] = i; break; } } } idxlist->idx = idx; idxlist->count = count; } void destroy_dynsym_idxlist(struct dynsym_idxlist *idxlist) { free(idxlist->idx); idxlist->idx = NULL; idxlist->count = 0; } bool check_dynsym_idxlist(struct dynsym_idxlist *idxlist, unsigned idx) { unsigned i; for (i = 0; i < idxlist->count; i++) { if (idx == idxlist->idx[i]) return true; } return false; } struct uftrace_mmap *find_map(struct uftrace_sym_info *sinfo, uint64_t addr) { struct uftrace_mmap *map; if (is_kernel_address(sinfo, addr)) return MAP_KERNEL; for_each_map(sinfo, map) { if (map->start <= addr && addr < map->end) return map; } return NULL; } struct uftrace_mmap *find_symbol_map(struct uftrace_sym_info *sinfo, char *name) { struct uftrace_mmap *map; for_each_map(sinfo, map) { struct uftrace_symbol *sym; if (map->mod != NULL) { sym = find_symname(&map->mod->symtab, name); if (sym && sym->type != ST_PLT_FUNC) return map; } } return NULL; } struct uftrace_symbol *find_symtabs(struct uftrace_sym_info *sinfo, uint64_t addr) { struct uftrace_symtab *stab; struct uftrace_mmap *map; struct uftrace_symbol *sym = NULL; map = find_map(sinfo, addr); if (map == MAP_KERNEL) { struct uftrace_symtab *ktab = get_kernel_symtab(); uint64_t kaddr = get_kernel_address(sinfo, addr); if (!ktab) return NULL; sym = bsearch(&kaddr, ktab->sym, ktab->nr_sym, sizeof(*ktab->sym), addrfind); return sym; } if (map != NULL) { if (map->mod == NULL) { map->mod = load_module_symtab(sinfo, map->libname, map->build_id); if (map->mod == NULL) return NULL; } /* * use relative address for module symtab * since mappings can be loaded at any address * for multiple sessions */ addr -= map->start; stab = &map->mod->symtab; sym = bsearch(&addr, stab->sym, stab->nr_sym, sizeof(*sym), addrfind); } if (sym != NULL) { /* these dummy symbols are not part of real symbol table */ if (is_symbol_end(sym->name)) sym = NULL; } return sym; } struct uftrace_symbol *find_sym(struct uftrace_symtab *symtab, uint64_t addr) { struct uftrace_symbol *sym; sym = bsearch(&addr, symtab->sym, symtab->nr_sym, sizeof(struct uftrace_symbol), addrfind); if (sym != NULL) { /* these dummy symbols are not part of real symbol table */ if (is_symbol_end(sym->name)) sym = NULL; } return sym; } struct uftrace_symbol *find_symname(struct uftrace_symtab *symtab, const char *name) { size_t i; if (symtab->name_sorted) { struct uftrace_symbol **psym; psym = bsearch(name, symtab->sym_names, symtab->nr_sym, sizeof(*psym), namefind); if (psym) return *psym; return NULL; } for (i = 0; i < symtab->nr_sym; i++) { struct uftrace_symbol *sym = &symtab->sym[i]; if (!strcmp(name, sym->name)) return sym; } return NULL; } char *symbol_getname(struct uftrace_symbol *sym, uint64_t addr) { char *name; if (sym == NULL) { xasprintf(&name, "<%" PRIx64 ">", addr); return name; } return sym->name; } /* must be used in pair with symbol_getname() */ void symbol_putname(struct uftrace_symbol *sym, char *name) { if (sym != NULL) return; free(name); } char *symbol_getname_offset(struct uftrace_symbol *sym, uint64_t addr) { char *name; if (addr == sym->addr) name = xstrdup(sym->name); else if (sym->addr < addr && addr < sym->addr + sym->size) xasprintf(&name, "%s+%" PRIu64, sym->name, addr - sym->addr); else name = xstrdup(""); /* the return string has to be free-ed after use. */ return name; } void print_symtab(struct uftrace_symtab *symtab) { size_t i; pr_out("Normal symbols\n"); pr_out("==============\n"); for (i = 0; i < symtab->nr_sym; i++) { struct uftrace_symbol *sym = &symtab->sym[i]; if (sym->type == ST_PLT_FUNC) continue; pr_out("[%2zd] %#" PRIx64 ": %s (size: %u)\n", i, sym->addr, sym->name, sym->size); } pr_out("\n\n"); pr_out("Dynamic symbols\n"); pr_out("===============\n"); for (i = 0; i < symtab->nr_sym; i++) { struct uftrace_symbol *sym = &symtab->sym[i]; if (sym->type != ST_PLT_FUNC) continue; pr_out("[%2zd] %#" PRIx64 ": %s (size: %u)\n", i, sym->addr, sym->name, sym->size); } } uint64_t guess_kernel_base(char *str) { uint64_t addr = strtoull(str, NULL, 16); /* * AArch64 has different memory map depending on page size and * level of page table. */ if (addr < 0x40000000UL) /* 1G:3G split */ return 0x40000000UL; else if (addr < 0x80000000UL) /* 2G:2G split */ return 0x80000000UL; else if (addr < 0xB0000000UL) /* 3G:1G split (variant) */ return 0xB0000000UL; else if (addr < 0xC0000000UL) /* 3G:1G split */ return 0xC0000000UL; /* below is for 64-bit systems */ else if (addr < 0x8000000000ULL) /* 512G:512G split */ return 0xFFFFFF8000000000ULL; else if (addr < 0x40000000000ULL) /* 4T:4T split */ return 0xFFFFFC0000000000ULL; else if (addr < 0x800000000000ULL) /* 128T:128T split (x86_64) */ return 0xFFFF800000000000ULL; else return 0xFFFF000000000000ULL; } int read_build_id(const char *filename, char *buf, int len) { struct uftrace_elf_data elf; struct uftrace_elf_iter iter; unsigned char build_id[BUILD_ID_SIZE]; bool found_build_id = false; int offset; memset(buf, 0, len); if (len < BUILD_ID_STR_SIZE) return -1; if (elf_init(filename, &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_dbg2("cannot find build-id section in %s\n", filename); elf_finish(&elf); return -1; } found_build_id = false; 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); found_build_id = true; break; } } elf_finish(&elf); if (!found_build_id) { pr_dbg2("cannot find GNU build-id note in %s\n", filename); return -1; } for (offset = 0; offset < BUILD_ID_SIZE; offset++) { unsigned char c = build_id[offset]; snprintf(buf + offset * 2, len - offset * 2, "%02x", c); } buf[BUILD_ID_STR_SIZE - 1] = '\0'; return 0; } #ifdef UNIT_TEST TEST_CASE(symbol_load_module) { struct uftrace_symtab stab = { .nr_alloc = 0, }; struct uftrace_symbol mixed_sym[] = { { 0x100, 256, ST_PLT_FUNC, "plt1" }, { 0x200, 256, ST_PLT_FUNC, "plt2" }, { 0x300, 256, ST_PLT_FUNC, "plt3" }, { 0x1100, 256, ST_GLOBAL_FUNC, "normal1" }, { 0x1200, 256, ST_LOCAL_FUNC, "normal2" }, { 0x1300, 256, ST_GLOBAL_FUNC, "normal3" }, }; struct uftrace_symtab test = { .nr_sym = 0, }; char symfile[] = "SYM.sym"; int i; /* recover from earlier failures */ unlink(symfile); stab.sym = mixed_sym; stab.nr_sym = ARRAY_SIZE(mixed_sym); pr_dbg("save symbol file and load symbols\n"); save_module_symbol_file(&stab, symfile, "", symfile, 0x400000); TEST_EQ(load_module_symbol_file(&test, symfile, 0x400000), 0); pr_dbg("check PLT symbols first\n"); TEST_EQ(test.nr_sym, ARRAY_SIZE(mixed_sym)); for (i = 0; i < 3; i++) { struct uftrace_symbol *sym = &test.sym[i]; TEST_EQ(sym->addr, stab.sym[i].addr); TEST_EQ(sym->size, stab.sym[i].size); TEST_EQ(sym->type, stab.sym[i].type); TEST_STREQ(sym->name, stab.sym[i].name); } pr_dbg("check normal symbols\n"); for (i = 3; i < 6; i++) { struct uftrace_symbol *sym = &test.sym[i]; TEST_EQ(sym->addr, stab.sym[i].addr); TEST_EQ(sym->size, stab.sym[i].size); TEST_EQ(sym->type, stab.sym[i].type); TEST_STREQ(sym->name, stab.sym[i].name); } unload_symtab(&test); unlink(symfile); return TEST_OK; } #include static int add_map(struct dl_phdr_info *info, size_t sz, void *data) { struct uftrace_sym_info *sym_info = data; struct uftrace_mmap *map; char *exename = NULL; int i; exename = read_exename(); map = xzalloc(sizeof(*map) + strlen(exename) + 1); for (i = 0; i < info->dlpi_phnum; i++) { if (info->dlpi_phdr[i].p_type != PT_LOAD) continue; if (map->start == 0) map->start = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr; /* use last PT_LOAD segment for end address */ map->end = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr + info->dlpi_phdr[i].p_memsz; } map->len = strlen(exename); strcpy(map->libname, exename); sym_info->maps = map; sym_info->exec_map = map; return 1; } TEST_CASE(symbol_load_map) { struct uftrace_sym_info sinfo = { .dirname = "", .symdir = "", .kernel_base = -4096ULL, .flags = SYMTAB_FL_ADJ_OFFSET, }; struct uftrace_mmap *map; struct uftrace_symbol *sym; pr_dbg("load a real map file of the unittest binary\n"); /* just load a map for main executable */ dl_iterate_phdr(add_map, &sinfo); /* load maps and symbols */ load_module_symtabs(&sinfo); pr_dbg("try to find the map using a real symbol: find_map\n"); /* find map by address of a function */ map = find_map(&sinfo, (uintptr_t)&find_map); TEST_NE(map, NULL); /* check symbol table of uftrace binary */ pr_dbg("check specific symbol table to have the address\n"); sym = find_sym(&map->mod->symtab, (uintptr_t)&find_sym - map->start); TEST_NE(sym, NULL); TEST_NE(strstr(sym->name, "find_sym"), NULL); pr_dbg("check the symbol table to have: load_module_symtabs\n"); sym = find_symname(&map->mod->symtab, "load_module_symtabs"); TEST_NE(sym, NULL); TEST_EQ(sym->addr + map->start, (uintptr_t)&load_module_symtabs); pr_dbg("check entire symbol tables to have: add_map\n"); sym = find_symtabs(&sinfo, (uintptr_t)&add_map); TEST_NE(sym, NULL); TEST_NE(strstr(sym->name, "add_map"), NULL); unload_module_symtabs(); TEST_EQ(RB_EMPTY_ROOT(&modules), true); free(map); return TEST_OK; } TEST_CASE(symbol_read_build_id) { char build_id[BUILD_ID_STR_SIZE]; /* non-existing file */ TEST_LT(read_build_id("xxx", build_id, sizeof(build_id)), 0); TEST_STREQ(build_id, ""); /* this should succeed, otherwise it doesn't have one - so skip it */ pr_dbg("reading build-id from %s\n", read_exename()); if (read_build_id(read_exename(), build_id, sizeof(build_id)) < 0) return TEST_SKIP; TEST_NE(build_id[0], '\0'); /* invalid buffer size */ TEST_LT(read_build_id(read_exename(), build_id, 1), 0); TEST_STREQ(build_id, ""); return TEST_OK; } static void init_test_module_info(struct uftrace_module **pmod1, struct uftrace_module **pmod2, bool set_build_id, bool load_symbols) { struct uftrace_module *mod1, *mod2; const char mod1_name[] = "/some/where/module/name"; const char mod2_name[] = "/different/path/name"; const char mod1_build_id[] = "1234567890abcdef"; const char mod2_build_id[] = "DUMMY-BUILD-ID"; static struct uftrace_symbol mod1_syms[] = { { 0x1000, 0x1000, ST_PLT_FUNC, "func1" }, { 0x2000, 0x1000, ST_LOCAL_FUNC, "func2" }, { 0x3000, 0x1000, ST_GLOBAL_FUNC, "func3" }, }; static struct uftrace_symbol mod2_syms[] = { { 0x5000, 0x1000, ST_PLT_FUNC, "funcA" }, { 0x6000, 0x1000, ST_PLT_FUNC, "funcB" }, { 0x7000, 0x1000, ST_PLT_FUNC, "funcC" }, { 0x8000, 0x1000, ST_GLOBAL_FUNC, "funcD" }, }; mod1 = xzalloc(sizeof(*mod1) + sizeof(mod1_name)); mod2 = xzalloc(sizeof(*mod2) + sizeof(mod2_name)); strcpy(mod1->name, mod1_name); strcpy(mod2->name, mod2_name); if (set_build_id) { strcpy(mod1->build_id, mod1_build_id); strcpy(mod2->build_id, mod2_build_id); } if (load_symbols) { mod1->symtab.sym = mod1_syms; mod1->symtab.nr_sym = ARRAY_SIZE(mod1_syms); mod2->symtab.sym = mod2_syms; mod2->symtab.nr_sym = ARRAY_SIZE(mod2_syms); } *pmod1 = mod1; *pmod2 = mod2; } TEST_CASE(symbol_same_file_name1) { struct uftrace_sym_info sinfo = { .dirname = ".", .symdir = ".", .flags = SYMTAB_FL_USE_SYMFILE, }; struct uftrace_module *save_mod[2]; struct uftrace_module *load_mod[2]; size_t i; /* recover from earlier failures */ if (system("rm -f name*.sym")) return TEST_NG; pr_dbg("allocating modules\n"); init_test_module_info(&save_mod[0], &save_mod[1], false, true); init_test_module_info(&load_mod[0], &load_mod[1], false, false); pr_dbg("save symbol files with same name (no build-id)\n"); save_module_symbol_file(&save_mod[0]->symtab, save_mod[0]->name, save_mod[0]->build_id, "name.sym", 0); save_module_symbol_file(&save_mod[1]->symtab, save_mod[1]->name, save_mod[1]->build_id, "name.sym", 0); pr_dbg("load symbol table from the files\n"); load_module_symbol(&sinfo, load_mod[0]); load_module_symbol(&sinfo, load_mod[1]); pr_dbg("check symbol table contents of module1\n"); TEST_EQ(save_mod[0]->symtab.nr_sym, load_mod[0]->symtab.nr_sym); for (i = 0; i < load_mod[0]->symtab.nr_sym; i++) { struct uftrace_symbol *save_sym = &save_mod[0]->symtab.sym[i]; struct uftrace_symbol *load_sym = &load_mod[0]->symtab.sym[i]; TEST_EQ(save_sym->addr, load_sym->addr); TEST_EQ(save_sym->size, load_sym->size); TEST_EQ(save_sym->type, load_sym->type); TEST_STREQ(save_sym->name, load_sym->name); } pr_dbg("check symbol table contents of module2\n"); TEST_EQ(save_mod[1]->symtab.nr_sym, load_mod[1]->symtab.nr_sym); for (i = 0; i < load_mod[1]->symtab.nr_sym; i++) { struct uftrace_symbol *save_sym = &save_mod[1]->symtab.sym[i]; struct uftrace_symbol *load_sym = &load_mod[1]->symtab.sym[i]; TEST_EQ(save_sym->addr, load_sym->addr); TEST_EQ(save_sym->size, load_sym->size); TEST_EQ(save_sym->type, load_sym->type); TEST_STREQ(save_sym->name, load_sym->name); } pr_dbg("releasing modules\n"); free(save_mod[0]); free(save_mod[1]); unload_symtab(&load_mod[0]->symtab); free(load_mod[0]); unload_symtab(&load_mod[1]->symtab); free(load_mod[1]); if (system("rm -f name*.sym")) return TEST_NG; return TEST_OK; } TEST_CASE(symbol_same_file_name2) { struct uftrace_sym_info sinfo = { .dirname = ".", .symdir = ".", .flags = SYMTAB_FL_USE_SYMFILE, }; struct uftrace_module *save_mod[2]; struct uftrace_module *load_mod[2]; size_t i; /* recover from earlier failures */ if (system("rm -f name*.sym")) return TEST_NG; pr_dbg("allocating modules\n"); init_test_module_info(&save_mod[0], &save_mod[1], true, true); init_test_module_info(&load_mod[0], &load_mod[1], true, false); pr_dbg("save symbol files with same name (with build-id)\n"); /* save them in the opposite order */ save_module_symbol_file(&save_mod[1]->symtab, save_mod[1]->name, save_mod[1]->build_id, "name.sym", 0); save_module_symbol_file(&save_mod[0]->symtab, save_mod[0]->name, save_mod[0]->build_id, "name.sym", 0); pr_dbg("load symbol table from the files\n"); load_module_symbol(&sinfo, load_mod[0]); load_module_symbol(&sinfo, load_mod[1]); pr_dbg("check symbol table contents of module1\n"); TEST_EQ(save_mod[0]->symtab.nr_sym, load_mod[0]->symtab.nr_sym); for (i = 0; i < load_mod[0]->symtab.nr_sym; i++) { struct uftrace_symbol *save_sym = &save_mod[0]->symtab.sym[i]; struct uftrace_symbol *load_sym = &load_mod[0]->symtab.sym[i]; TEST_EQ(save_sym->addr, load_sym->addr); TEST_EQ(save_sym->size, load_sym->size); TEST_EQ(save_sym->type, load_sym->type); TEST_STREQ(save_sym->name, load_sym->name); } pr_dbg("check symbol table contents of module2\n"); TEST_EQ(save_mod[1]->symtab.nr_sym, load_mod[1]->symtab.nr_sym); for (i = 0; i < load_mod[1]->symtab.nr_sym; i++) { struct uftrace_symbol *save_sym = &save_mod[1]->symtab.sym[i]; struct uftrace_symbol *load_sym = &load_mod[1]->symtab.sym[i]; TEST_EQ(save_sym->addr, load_sym->addr); TEST_EQ(save_sym->size, load_sym->size); TEST_EQ(save_sym->type, load_sym->type); TEST_STREQ(save_sym->name, load_sym->name); } pr_dbg("releasing modules\n"); free(save_mod[0]); free(save_mod[1]); unload_symtab(&load_mod[0]->symtab); free(load_mod[0]); unload_symtab(&load_mod[1]->symtab); free(load_mod[1]); if (system("rm -f name*.sym")) return TEST_NG; return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/symbol.h000066400000000000000000000161301455365734300160660ustar00rootroot00000000000000/* * symbol management data structures for uftrace * * Copyright (C) 2014-2017, LG Electronics, Namhyung Kim * * Released under the GPL v2. */ #ifndef UFTRACE_SYMBOL_H #define UFTRACE_SYMBOL_H #include #include #include "utils/dwarf.h" #include "utils/list.h" #include "utils/rbtree.h" #include "utils/utils.h" #ifdef HAVE_LIBELF #include "utils/symbol-libelf.h" #else #include "utils/symbol-rawelf.h" #endif #ifndef STT_GNU_IFUNC #define STT_GNU_IFUNC 10 #endif #ifndef STB_GNU_UNIQUE #define STB_GNU_UNIQUE 10 #endif #define BUILD_ID_SIZE 20 #define BUILD_ID_STR_SIZE (BUILD_ID_SIZE * 2 + 1) #define UFTRACE_PYTHON_MODULE_NAME "uftrace_python" #define UFTRACE_PYTHON_SYMTAB_NAME "python.fake" enum uftrace_symtype { ST_UNKNOWN = '?', ST_LOCAL_FUNC = 't', ST_GLOBAL_FUNC = 'T', ST_WEAK_FUNC = 'w', ST_PLT_FUNC = 'P', ST_KERNEL_FUNC = 'K', ST_LOCAL_DATA = 'd', ST_GLOBAL_DATA = 'D', ST_WEAK_DATA = 'v', ST_UNIQUE_DATA = 'u', }; struct uftrace_symbol { uint64_t addr; unsigned size; enum uftrace_symtype type; char *name; }; /* initial factor to resize the symbol table */ #define SYMTAB_GROW 16 struct uftrace_symtab { /* array of symbols sorted by addr */ struct uftrace_symbol *sym; /* * array of symbols sorted by name when name_sorted is %true. * but plthook_data.dsymtab uses this differently so that it keeps * PLT index. In that case name_sorted should be %false. */ struct uftrace_symbol **sym_names; /* number of actual symbols in the array */ size_t nr_sym; /* number of allocated symbols */ size_t nr_alloc; /* indicates whether it's sorted by name */ bool name_sorted; }; struct uftrace_module { struct rb_node node; struct uftrace_symtab symtab; struct uftrace_dbg_info dinfo; char build_id[BUILD_ID_STR_SIZE]; char name[]; }; struct uftrace_mmap { struct uftrace_mmap *next; struct uftrace_module *mod; uint64_t start; uint64_t end; char prot[4]; uint32_t len; char build_id[BUILD_ID_STR_SIZE]; char libname[]; }; enum uftrace_symtab_flag { SYMTAB_FL_DEMANGLE = (1U << 0), SYMTAB_FL_USE_SYMFILE = (1U << 1), SYMTAB_FL_ADJ_OFFSET = (1U << 2), SYMTAB_FL_SKIP_NORMAL = (1U << 3), SYMTAB_FL_SKIP_DYNAMIC = (1U << 4), SYMTAB_FL_SYMS_DIR = (1U << 5), }; struct uftrace_sym_info { /* mmap and symtab info was loaded */ bool loaded; /* name of directory which has data files */ const char *dirname; /* name of the main executable file of this process */ const char *filename; /* * name of directory containing symbol info. * mostly same as dirname, but could be different if --with-sym is given. */ const char *symdir; /* symbol table flags: see above */ enum uftrace_symtab_flag flags; /* start address of kernel address space */ uint64_t kernel_base; /* map for the main executable (cached) */ struct uftrace_mmap *exec_map; /* list of memory mapping info for executable and libraries */ struct uftrace_mmap *maps; }; #define for_each_map(sym_info, map) \ for ((map) = (sym_info)->maps; (map) != NULL; (map) = (map)->next) /* addr should be from fstack or something other than rstack (rec) */ static inline bool is_kernel_address(struct uftrace_sym_info *sinfo, uint64_t addr) { return addr >= sinfo->kernel_base; } /* convert rstack->addr (or rec->addr) to full 64-bit address */ static inline uint64_t get_kernel_address(struct uftrace_sym_info *sinfo, uint64_t addr) { return addr | sinfo->kernel_base; } uint64_t guess_kernel_base(char *str); extern struct uftrace_symbol sched_sym; extern struct uftrace_symbol sched_preempt_sym; struct uftrace_symbol *find_symtabs(struct uftrace_sym_info *sinfo, uint64_t addr); struct uftrace_symbol *find_sym(struct uftrace_symtab *symtab, uint64_t addr); struct uftrace_symbol *find_symname(struct uftrace_symtab *symtab, const char *name); void print_symtab(struct uftrace_symtab *symtab); int arch_load_dynsymtab_noplt(struct uftrace_symtab *dsymtab, struct uftrace_elf_data *elf, unsigned long offset, unsigned long flags); int load_elf_dynsymtab(struct uftrace_symtab *dsymtab, struct uftrace_elf_data *elf, unsigned long offset, unsigned long flags); void load_module_symtabs(struct uftrace_sym_info *sinfo); struct uftrace_module *load_module_symtab(struct uftrace_sym_info *sinfo, const char *mod_name, char *build_id); void save_module_symtabs(const char *dirname); void unload_module_symtabs(void); void sort_dynsymtab(struct uftrace_symtab *dsymtab); enum uftrace_trace_type { TRACE_ERROR = -1, TRACE_NONE, TRACE_MCOUNT, TRACE_CYGPROF, TRACE_FENTRY, }; char *get_soname(const char *filename); bool has_dependency(const char *filename, const char *libname); enum uftrace_trace_type check_trace_functions(const char *filename); int check_static_binary(const char *filename); bool check_script_file(const char *filename, char *buf, size_t len); /* pseudo-map for kernel image */ #define MAP_KERNEL (struct uftrace_mmap *)1 struct uftrace_mmap *find_map(struct uftrace_sym_info *sinfo, uint64_t addr); struct uftrace_mmap *find_map_by_name(struct uftrace_sym_info *sinfo, const char *prefix); struct uftrace_mmap *find_symbol_map(struct uftrace_sym_info *sinfo, char *name); int save_kernel_symbol(char *dirname); int load_kernel_symbol(char *dirname); struct uftrace_symtab *get_kernel_symtab(void); struct uftrace_module *get_kernel_module(void); int load_symbol_file(struct uftrace_sym_info *sinfo, const char *symfile, uint64_t offset); void save_symbol_file(struct uftrace_sym_info *sinfo, const char *dirname, const char *exename); int check_symbol_file(const char *symfile, char *pathname, int pathlen, char *build_id, int build_id_len); char *make_new_symbol_filename(const char *symfile, const char *pathname, char *build_id); char *symbol_getname(struct uftrace_symbol *sym, uint64_t addr); void symbol_putname(struct uftrace_symbol *sym, char *name); char *symbol_getname_offset(struct uftrace_symbol *sym, uint64_t addr); struct dynsym_idxlist { unsigned *idx; unsigned count; }; void build_dynsym_idxlist(struct uftrace_symtab *dsymtab, struct dynsym_idxlist *idxlist, const char *symlist[], unsigned symcount); void destroy_dynsym_idxlist(struct dynsym_idxlist *idxlist); bool check_dynsym_idxlist(struct dynsym_idxlist *idxlist, unsigned idx); void setup_skip_idx(struct uftrace_sym_info *sinfo); void destroy_skip_idx(void); bool should_skip_idx(unsigned idx); enum symbol_demangler { DEMANGLE_ERROR = -2, DEMANGLE_NOT_SUPPORTED, DEMANGLE_NONE, DEMANGLE_SIMPLE, DEMANGLE_FULL, }; extern enum symbol_demangler demangler; char *demangle(char *str); #ifdef HAVE_CXA_DEMANGLE /* copied from /usr/include/c++/4.7.2/cxxabi.h */ extern char *__cxa_demangle(const char *mangled_name, char *output_buffer, size_t *length, int *status); static inline bool support_full_demangle(void) { return true; } #else static inline bool support_full_demangle(void) { return false; } static inline char *demangle_full(char *str) { pr_warn("full demangle is not supported\n"); return str; } #endif /* HAVE_CXA_DEMANGLE */ int read_build_id(const char *filename, char *buf, int len); #endif /* UFTRACE_SYMBOL_H */ uftrace-0.15.2/utils/tracefs.c000066400000000000000000000075471455365734300162170ustar00rootroot00000000000000#include #include #include #include #include #include #include "utils/tracefs.h" #include "utils/utils.h" #ifndef TRACEFS_MAGIC #define TRACEFS_MAGIC 0x74726163 #endif #define PROC_MOUNTS_DIR_PATH "/proc/mounts" #define TRACEFS_DIR_PATH "/sys/kernel/tracing" #define OLD_TRACEFS_DIR_PATH "/sys/kernel/debug/tracing" static char *TRACING_DIR = NULL; static bool find_tracing_dir(void) { FILE *fp; struct mntent *ent; struct statfs fs; if (TRACING_DIR) return true; if (!statfs(TRACEFS_DIR_PATH, &fs) && fs.f_type == TRACEFS_MAGIC) { xasprintf(&TRACING_DIR, "%s", TRACEFS_DIR_PATH); return true; } else if (!statfs(OLD_TRACEFS_DIR_PATH, &fs) && fs.f_type == TRACEFS_MAGIC) { xasprintf(&TRACING_DIR, "%s", OLD_TRACEFS_DIR_PATH); return true; } fp = setmntent(PROC_MOUNTS_DIR_PATH, "r"); if (fp == NULL) return false; while ((ent = getmntent(fp)) != NULL) { if (!strcmp(ent->mnt_fsname, "tracefs")) { xasprintf(&TRACING_DIR, "%s", ent->mnt_dir); break; } } endmntent(fp); if (!TRACING_DIR) { pr_dbg2("No tracefs or debugfs found..!\n"); return false; } return true; } char *get_tracing_file(const char *name) { char *file = NULL; if (!TRACING_DIR && !find_tracing_dir()) return NULL; xasprintf(&file, "%s/%s", TRACING_DIR, name); return file; } void put_tracing_file(char *file) { free(file); } int open_tracing_file(const char *name, int flags) { char *file; int fd; file = get_tracing_file(name); if (!file) { pr_dbg("cannot get tracing file: %s: %m\n", name); return -1; } fd = open(file, flags); if (fd < 0) pr_dbg("cannot open tracing file: %s: %m\n", name); put_tracing_file(file); return fd; } ssize_t read_tracing_file(const char *name, char *buf, size_t len) { ssize_t ret; int fd = open_tracing_file(name, O_RDONLY); if (fd < 0) return -1; ret = read(fd, buf, len); close(fd); return ret; } int __write_tracing_file(int fd, const char *name, const char *val, bool append, bool correct_sys_prefix) { int ret = -1; ssize_t size = strlen(val); if (correct_sys_prefix) { char *newval = (char *)val; if (!strncmp(val, "sys_", 4)) newval[0] = newval[2] = 'S'; else if (!strncmp(val, "compat_sys_", 11)) newval[7] = newval[9] = 'S'; else correct_sys_prefix = false; } pr_dbg2("%s '%s' to tracing/%s\n", append ? "appending" : "writing", val, name); if (write(fd, val, size) == size) ret = 0; if (correct_sys_prefix) { char *newval = (char *)val; if (!strncmp(val, "SyS_", 4)) newval[0] = newval[2] = 's'; else if (!strncmp(val, "compat_SyS_", 11)) newval[7] = newval[9] = 's'; /* write a whitespace to distinguish the previous pattern */ if (write(fd, " ", 1) < 0) ret = -1; pr_dbg2("%s '%s' to tracing/%s\n", append ? "appending" : "writing", val, name); if (write(fd, val, size) == size) ret = 0; } if (ret < 0) pr_dbg("write '%s' to tracing/%s failed: %m\n", val, name); return ret; } int write_tracing_file(const char *name, const char *val) { int ret; int fd = open_tracing_file(name, O_WRONLY | O_TRUNC); if (fd < 0) return -1; ret = __write_tracing_file(fd, name, val, false, false); close(fd); return ret; } int append_tracing_file(const char *name, const char *val) { int ret; int fd = open_tracing_file(name, O_WRONLY | O_APPEND); if (fd < 0) return -1; ret = __write_tracing_file(fd, name, val, true, false); close(fd); return ret; } int set_tracing_pid(int pid) { char buf[16]; snprintf(buf, sizeof(buf), "%d", pid); if (append_tracing_file("set_ftrace_pid", buf) < 0) return -1; /* ignore error on old kernel */ append_tracing_file("set_event_pid", buf); return 0; } int set_tracing_clock(char *clock_str) { /* set to default clock source if not given */ if (clock_str == NULL) clock_str = "mono"; return write_tracing_file("trace_clock", clock_str); } uftrace-0.15.2/utils/tracefs.h000066400000000000000000000012141455365734300162050ustar00rootroot00000000000000#ifndef UFTRACE_TRACEFS_H #define UFTRACE_TRACEFS_H #include #include #include char *get_tracing_file(const char *name); void put_tracing_file(char *file); int open_tracing_file(const char *name, int flags); ssize_t read_tracing_file(const char *name, char *buf, size_t len); int write_tracing_file(const char *name, const char *val); int __write_tracing_file(int fd, const char *name, const char *val, bool append, bool correct_sys_prefix); int append_tracing_file(const char *name, const char *val); int set_tracing_pid(int pid); int set_tracing_clock(char *clock_str); #endif /* UFTRACE_TRACEFS_H */ uftrace-0.15.2/utils/utils.c000066400000000000000000000543141455365734300157220ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBUNWIND #define UNW_LOCAL_ONLY #include #endif #include "uftrace.h" #include "utils/kernel.h" #include "utils/utils.h" volatile bool uftrace_done; /* default uftrace options to be applied for analysis commands */ struct strv default_opts = STRV_INIT; void sighandler(int sig) { uftrace_done = true; } void setup_signal(void) { signal(SIGINT, sighandler); signal(SIGTERM, sighandler); signal(SIGPIPE, sighandler); } int read_all(int fd, void *buf, size_t size) { int ret; while (size) { ret = read(fd, buf, size); if (ret < 0 && errno == EINTR) continue; if (ret <= 0) return -1; buf += ret; size -= ret; } return 0; } int pread_all(int fd, void *buf, size_t size, off_t off) { int ret; while (size) { ret = pread(fd, buf, size, off); if (ret < 0 && errno == EINTR) continue; if (ret <= 0) return -1; buf += ret; size -= ret; off += ret; } return 0; } int fread_all(void *buf, size_t size, FILE *fp) { size_t ret; while (size) { if (feof(fp)) return -1; ret = fread(buf, 1, size, fp); if (ferror(fp)) return -1; buf += ret; size -= ret; } return 0; } int write_all(int fd, const void *buf, size_t size) { int ret; while (size) { ret = write(fd, buf, size); if (ret < 0 && errno == EINTR) continue; if (ret < 0) return -1; buf += ret; size -= ret; } return 0; } int writev_all(int fd, struct iovec *iov, int count) { int i, ret; int size = 0; for (i = 0; i < count; i++) size += iov[i].iov_len; while (size) { ret = writev(fd, iov, count); if (ret < 0 && errno == EINTR) continue; if (ret < 0) return -1; size -= ret; if (size == 0) break; while (ret > (int)iov->iov_len) { ret -= iov->iov_len; if (count == 0) pr_err_ns("invalid iovec count?"); count--; iov++; } iov->iov_base += ret; iov->iov_len -= ret; } return 0; } int fwrite_all(const void *buf, size_t size, FILE *fp) { size_t ret; while (size) { if (feof(fp)) return -1; ret = fwrite(buf, 1, size, fp); if (ferror(fp)) return -1; buf += ret; size -= ret; } return 0; } int remove_directory(const char *dirname) { DIR *dp; struct dirent *ent; struct stat statbuf; char buf[PATH_MAX]; int saved_errno = 0; int ret = 0; dp = opendir(dirname); if (dp == NULL) return -1; pr_dbg("removing %s directory\n", dirname); while ((ent = readdir(dp)) != NULL) { if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) continue; snprintf(buf, sizeof(buf), "%s/%s", dirname, ent->d_name); ret = stat(buf, &statbuf); if (ret < 0) goto failed; if (S_ISDIR(statbuf.st_mode)) ret = remove_directory(buf); else ret = unlink(buf); if (ret < 0) { failed: saved_errno = errno; break; } } closedir(dp); if (rmdir(dirname) < 0 && ret == 0) ret = -1; else errno = saved_errno; return ret; } static bool is_uftrace_directory(const char *path) { int fd; bool ret = false; char *info_path = NULL; char sig[UFTRACE_MAGIC_LEN] = { 0, }; /* ensure that there is "info" file in the recorded directory */ xasprintf(&info_path, "%s/info", path); fd = open(info_path, O_RDONLY); free(info_path); if (fd != -1) { if (read(fd, sig, UFTRACE_MAGIC_LEN) != UFTRACE_MAGIC_LEN) { /* * partial read() will return false anyway * since memcmp() below cannot success. */ } close(fd); return !memcmp(sig, UFTRACE_MAGIC_STR, UFTRACE_MAGIC_LEN); } /* if "info" file is missing, also check that there is "default.opts" */ xasprintf(&info_path, "%s/default.opts", path); if (!access(info_path, F_OK)) ret = true; free(info_path); return ret; } static bool is_empty_directory(const char *path) { DIR *dp = opendir(path); struct dirent *ent; int ret = true; if (dp == NULL) return false; while ((ent = readdir(dp)) != NULL) { if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) continue; ret = false; break; } closedir(dp); return ret; } static bool can_remove_directory(const char *path) { if (access(path, F_OK) != 0) return false; return is_uftrace_directory(path) || is_empty_directory(path); } static bool create_default_opts(const char *dirname) { char *opts_str = strv_join(&default_opts, " "); char opts_filename[PATH_MAX]; FILE *fp; bool ret = false; snprintf(opts_filename, PATH_MAX, "%s/default.opts", dirname); fp = fopen(opts_filename, "w"); if (fp == NULL) { pr_dbg("Open failed: %s\n", opts_filename); goto out; } if (opts_str) fprintf(fp, "%s\n", opts_str); fclose(fp); ret = true; out: strv_free(&default_opts); free(opts_str); return ret; } int create_directory(const char *dirname) { int ret = -1; char *oldname = NULL; xasprintf(&oldname, "%s.old", dirname); if (can_remove_directory(dirname)) { if (can_remove_directory(oldname)) { if (remove_directory(oldname) < 0) { pr_warn("removing old directory failed: %m\n"); goto out; } } if (rename(dirname, oldname) < 0) { pr_warn("rename %s -> %s failed: %m\n", dirname, oldname); goto out; } } ret = mkdir(dirname, 0755); if (ret < 0) pr_warn("creating directory failed: %m\n"); create_default_opts(dirname); out: free(oldname); return ret; } int chown_directory(const char *dirname) { DIR *dp; struct dirent *ent; char buf[PATH_MAX]; char *uidstr; char *gidstr; uid_t uid; gid_t gid; int ret = 0; /* When invoked with sudo, real uid is also 0. Use env instead. */ uidstr = getenv("SUDO_UID"); gidstr = getenv("SUDO_GID"); if (uidstr == NULL || gidstr == NULL) return 0; uid = strtol(uidstr, NULL, 0); gid = strtol(gidstr, NULL, 0); dp = opendir(dirname); if (dp == NULL) return -1; pr_dbg("chown %s directory to (%d:%d)\n", dirname, (int)uid, (int)gid); while ((ent = readdir(dp)) != NULL) { if (ent->d_name[0] == '.') continue; snprintf(buf, sizeof(buf), "%s/%s", dirname, ent->d_name); if (chown(buf, uid, gid) < 0) ret = -1; } closedir(dp); if (chown(dirname, uid, gid) < 0) ret = -1; return ret; } char *read_exename(void) { int len; static char exename[PATH_MAX]; if (!*exename) { len = readlink("/proc/self/exe", exename, sizeof(exename) - 1); if (len < 0) pr_err("cannot read executable name"); exename[len] = '\0'; } return exename; } clockid_t clock_source = CLOCK_MONOTONIC; static const struct { const char *name; clockid_t clock_id; } trace_clocks[] = { { "mono", CLOCK_MONOTONIC }, { "mono_raw", CLOCK_MONOTONIC_RAW }, { "boot", CLOCK_BOOTTIME }, }; void setup_clock_id(const char *clock_str) { size_t i; for (i = 0; i < ARRAY_SIZE(trace_clocks); i++) { if (!strcmp(clock_str, trace_clocks[i].name)) { clock_source = trace_clocks[i].clock_id; break; } } } bool check_time_range(struct uftrace_time_range *range, uint64_t timestamp) { /* maybe it's called before first timestamp set */ if (!range->first) range->first = timestamp; if (range->start) { uint64_t start = range->start; if (range->start_elapsed) start += range->first; if (start > timestamp) return false; } if (range->stop) { uint64_t stop = range->stop; if (range->stop_elapsed) stop += range->first; if (stop < timestamp) return false; } return true; } static int get_digits(uint64_t num) { int digits = 0; do { num /= 10; digits++; } while (num != 0); return digits; } static uint64_t parse_min(uint64_t min, uint64_t decimal, int decimal_places) { uint64_t nsec = min * 60 * NSEC_PER_SEC; if (decimal) { decimal_places += get_digits(decimal); decimal *= 6; /* decide a unit from the number of decimal places */ switch (decimal_places) { case 1: nsec += decimal * NSEC_PER_SEC; break; case 2: decimal *= 10; /* fall through */ case 3: decimal *= 10; nsec += decimal * NSEC_PER_MSEC; break; default: break; } } return nsec; } uint64_t parse_time(char *arg, int limited_digits) { char *unit, *pos; int i, decimal_places = 0, exp = 0; uint64_t limited, decimal = 0; uint64_t val = strtoull(arg, &unit, 10); pos = strchr(arg, '.'); if (pos != NULL) { while (*(++pos) == '0') decimal_places++; decimal = strtoull(pos, &unit, 10); } limited = 10; for (i = 1; i < limited_digits; i++) limited *= 10; if (val >= limited) pr_err_ns("Limited %d digits (before and after decimal point)\n", limited_digits); /* ignore more digits than limited digits before decimal point */ while (decimal >= limited) decimal /= 10; /* * if the unit is omitted, it is regarded as default unit 'ns'. * so ignore it before decimal point. */ if (unit == NULL || *unit == '\0') return val; if (!strcasecmp(unit, "ns") || !strcasecmp(unit, "nsec")) return val; else if (!strcasecmp(unit, "us") || !strcasecmp(unit, "usec")) exp = 3; /* 10^3*/ else if (!strcasecmp(unit, "ms") || !strcasecmp(unit, "msec")) exp = 6; /* 10^6 */ else if (!strcasecmp(unit, "s") || !strcasecmp(unit, "sec")) exp = 9; /* 10^9 */ else if (!strcasecmp(unit, "m") || !strcasecmp(unit, "min")) return parse_min(val, decimal, decimal_places); else pr_warn("The unit '%s' isn't supported\n", unit); for (i = 0; i < exp; i++) val *= 10; if (decimal) { decimal_places += get_digits(decimal); for (i = decimal_places; i < exp; i++) decimal *= 10; val += decimal; } return val; } uint64_t parse_timestamp(char *arg) { char *sep; uint64_t ts, tmp; int len; tmp = strtoull(arg, &sep, 10); ts = tmp * NSEC_PER_SEC; if (*sep == '.') { arg = sep + 1; tmp = strtoull(arg, &sep, 10); len = 0; while (isdigit(*arg)) { arg++; len++; } /* if resolution is lower than nsec */ while (len < 9) { tmp *= 10; len++; } /* if resolution is higher than nsec */ while (len > 9) { tmp /= 10; len--; } ts += tmp; } return ts; } /** * strjoin - join two strings with a delimiter * @left: string buffer to join (dynamic allocated, can be NULL) * @right: string to join * @delim: delimiter inserted between the two * * This function returns a new string that concatenates @left and @right * with @delim. Note that if @left is #NULL, @delim will be omitted and * a copy of @right will be returned. @left must be dynamically allocated * buffer so that it can be passed to realloc. */ char *strjoin(char *left, char *right, const char *delim) { size_t llen = left ? strlen(left) : 0; size_t rlen = strlen(right); size_t dlen = strlen(delim); size_t len = llen + rlen + 1; char *new; if (left) len += dlen; new = xrealloc(left, len); if (left) strcpy(new + llen, delim); strcpy(new + len - rlen - 1, right); return new; } /** * str_ltrim - to trim left spaces * @str: input string * * This function make @str to left trimmed @str */ char *str_ltrim(char *str) { if (!str) return NULL; while (isspace((unsigned char)*str)) { str++; } return str; } /** * str_rtrim - to trim right spaces * @str: input string * * This function make @str to right trimmed @str */ char *str_rtrim(char *str) { char *p = strchr(str, '\0'); while (--p >= str && isspace(*p)) ; *(p + 1) = '\0'; return str; } /** * strv_split - split given string and construct a string vector * @strv: string vector * @str: input string * @delim: delimiter to split the string * * This function builds a string vector using @str split by @delim. */ void strv_split(struct strv *strv, const char *str, const char *delim) { int c = 1; char *saved_str = xstrdup(str); char *tmp, *pos; size_t len = strlen(delim); tmp = saved_str; while ((pos = strstr(tmp, delim)) != NULL) { tmp = pos + len; c++; } strv->nr = c; strv->p = xcalloc(c + 1, sizeof(*strv->p)); /* including NULL at last */ c = 0; tmp = saved_str; while ((pos = strstr(tmp, delim)) != NULL) { *pos = '\0'; strv->p[c++] = xstrdup(tmp); tmp = pos + len; } strv->p[c] = xstrdup(tmp); free(saved_str); } /** * strv_copy - copy argc and argv to string vector * @strv: string vector * @argc: number of input strings * @argv: array of strings * * This function build a string vector using @argc and @argv. */ void strv_copy(struct strv *strv, int argc, char *argv[]) { int i; strv->nr = argc; strv->p = xcalloc(argc + 1, sizeof(*strv->p)); for (i = 0; i < argc; i++) strv->p[i] = xstrdup(argv[i]); } /** * strv_append - add a string to string vector * @strv: string vector * @str: input string * * This function add @str to @strv. */ void strv_append(struct strv *strv, const char *str) { strv->p = xrealloc(strv->p, (strv->nr + 2) * sizeof(*strv->p)); strv->p[strv->nr + 0] = xstrdup(str); strv->p[strv->nr + 1] = NULL; strv->nr++; } /** * str_replace - replace a string to a new one * @strv: string vector * @idx: index of the (old) string * @str: new string to be replaced * * This function replaces @strv[@idx] to @str. Callers should not use the old * string after this function. */ void strv_replace(struct strv *strv, int idx, const char *str) { free(strv->p[idx]); strv->p[idx] = xstrdup(str); } /** * strv_join - make a string with string vector * @strv: string vector * @delim: delimiter inserted between strings * * This function returns a new string that concatenates all strings in * @strv with @delim. Note that if @strv contains a single string, * @delim will be omitted and a copy of @right will be returned. */ char *strv_join(struct strv *strv, const char *delim) { int i; char *s; char *str = NULL; strv_for_each(strv, s, i) str = strjoin(str, s, delim); return str; } /** * strv_free - release strings in string vector * @strv: string vector * * This function resets @strv and releases all memory in it. */ void strv_free(struct strv *strv) { int i; char *s; strv_for_each(strv, s, i) free(s); free(strv->p); strv->p = NULL; strv->nr = 0; } #define QUOTE '\'' #define DQUOTE '"' #define QUOTES "\'\"" /* escape double-quote with backslash - caller should free the returned string */ char *json_quote(char *str, int *len) { char *p = str; int quote = 0; int i, k; int orig_len = *len; /* find number of necessary escape */ while ((p = strchr(p, DQUOTE)) != NULL) { quote++; p++; } p = xmalloc(orig_len + quote + 1); /* escape double-quotes */ for (i = k = 0; i < orig_len; i++, k++) { if (str[i] == DQUOTE) { p[k++] = '\\'; p[k] = DQUOTE; } else p[k] = str[i]; } p[k] = '\0'; *len = k; return p; } static int setargs(char *args, char **argv) { int count = 0; while (*args) { /* ignore spaces */ if (isspace(*args)) { ++args; continue; } /* consider quotes and update argv */ if (*args == QUOTE) { ++args; if (*args == QUOTE) continue; if (argv) argv[count] = args; while (*args && *args != QUOTE) ++args; if (argv && *args) *args = ' '; } else if (*args == DQUOTE) { ++args; if (*args == DQUOTE) continue; if (argv) argv[count] = args; while (*args && *args != DQUOTE) ++args; if (argv && *args) *args = ' '; } else if (*args == '#') { /* ignore comment line */ while (*args != '\n' && *args != '\0') ++args; continue; } else if (argv) { argv[count] = args; } /* read characters until '\0' or space */ while (*args && !isspace(*args)) ++args; /* set '\0' rather than space */ if (argv && *args) *args++ = '\0'; /* count up argument */ count++; } return count; } #undef QUOTE #undef DQUOTE /** * parse_cmdline - parse given string to be executed via execvp(3) * @cmd: full command line * @argc: pointer to number of arguments * * This function parses @cmd and split it into an array of string * to be executed by exec(3) like argv[] in main(). The @argc * will be set to number of argument parsed if it's non-NULL. * The resulting array contains a copy of input string (@cmd) in * the first element which other elements point to. It returns a * pointer to the second element so that it can be used directly * in other functions like exec(3). * * The returned array should be freed by free_parsed_cmdline(). */ char **parse_cmdline(char *cmd, int *argc) { char **argv = NULL; char *cmd_dup = NULL; int argn = 0; if (!cmd || !*cmd) return NULL; /* duplicate cmdline to map to argv with modification */ cmd_dup = xstrdup(cmd); /* get count of arguments */ argn = setargs(cmd_dup, NULL); /* create argv array. +1 for cmd_dup, +1 for the last NULL */ argv = xcalloc(argn + 2, sizeof(char *)); /* remember cmd_dup to free later */ argv[0] = cmd_dup; /* actual assigning of arguments to argv + 1 */ argn = setargs(cmd_dup, &argv[1]); /* set last one as null for execv */ argv[argn + 1] = NULL; /* pass count of arguments */ if (argc) *argc = argn; /* returns +1 addr to hide cmd_dup address */ return &argv[1]; } /** * free_parsed_cmdline - free memory that was allocated by parse_cmdline * @argv: result of parse_cmdline * * The parse_cmdline uses internal allocation logic so, * the pointer should be freed by this function rather than free. */ void free_parsed_cmdline(char **argv) { if (argv) { /* parse_cmdline() passes &argv[1] */ argv--; /* free cmd_dup */ free(argv[0]); /* free original argv */ free(argv); } } /** * absolute_dirname - return the canonicalized absolute dirname * * @path: pathname string that can be either absolute or relative path * @resolved_path: input buffer that will store absolute dirname * * This function parses the @path and sets absolute dirname to @resolved_path. * * Given @path sets @resolved_path as follows: * * @path | @resolved_path * -------------------------+---------------- * mcount.py | $PWD * tests/mcount.py | $PWD/tests * ./tests/mcount.py | $PWD/./tests * /root/uftrace/mcount.py | /root/uftrace */ char *absolute_dirname(const char *path, char *resolved_path) { if (realpath(path, resolved_path) == NULL) return NULL; dirname(resolved_path); return resolved_path; } char *uftrace_strerror(int errnum, char *buf, size_t buflen) { long result = (long)strerror_r(errnum, buf, buflen); if (result == 0) /* XSI-compliant strerror_r succeed */ return buf; else if (result < 4096) { /* XSI-compliant strerror_r failed */ snprintf(buf, buflen, "error: %d", errnum); return buf; } else /* GNU-specific strerror_r */ return (char *)result; } void stacktrace(void) { #ifdef HAVE_LIBUNWIND const int max_depth = 64; int i = 0; bool out = false; unw_cursor_t cursor; unw_context_t context; unw_getcontext(&context); unw_init_local(&cursor, &context); pr_yellow("Stack trace:\n"); while (unw_step(&cursor) && i < max_depth && !out) { char symbol[256] = { "" }; char *name = symbol; unw_word_t ip, off; unw_get_reg(&cursor, UNW_REG_IP, &ip); if (!ip) break; if (!unw_get_proc_name(&cursor, symbol, sizeof(symbol), &off)) name = symbol; pr_yellow(" #%-2d 0x%012" PRIxPTR " %s + 0x%" PRIxPTR "\n", ++i, (uintptr_t)(ip), name, (uintptr_t)(off)); /* * plt_hooker goes into infinite loop with unwinding. * so stop following the stack below. */ if (!strcmp(name, "plt_hooker")) out = true; if (name != symbol) free(name); } #endif pr_out("\n"); } int copy_file(const char *path_in, const char *path_out) { char buf[4096]; FILE *ifp, *ofp; int n; ifp = fopen(path_in, "r"); if (ifp == NULL) { pr_warn("cannot open file: %s: %m\n", path_in); return -1; } ofp = fopen(path_out, "w"); if (ofp == NULL) { pr_warn("cannot create file: %s: %m\n", path_out); fclose(ifp); return -1; } while (true) { n = fread(buf, 1, sizeof(buf), ifp); if (n == 0) break; if (fwrite_all(buf, n, ofp) < 0) { pr_warn("cannot write to file: %m\n"); break; } } fclose(ifp); fclose(ofp); return 0; } #ifdef UNIT_TEST TEST_CASE(utils_parse_cmdline) { char **cmdv; int argc = -1; cmdv = parse_cmdline(NULL, NULL); TEST_EQ(cmdv, NULL); pr_dbg("parse_cmdline() should handle quoted stringss\n"); cmdv = parse_cmdline("uftrace recv --run-cmd 'uftrace replay'", &argc); TEST_NE(cmdv, NULL); TEST_EQ(argc, 4); TEST_STREQ(cmdv[0], "uftrace"); TEST_STREQ(cmdv[1], "recv"); TEST_STREQ(cmdv[2], "--run-cmd"); TEST_STREQ(cmdv[3], "uftrace replay"); free_parsed_cmdline(cmdv); return TEST_OK; } TEST_CASE(utils_strv) { struct strv strv = STRV_INIT; char *s; int i; const char test_str[] = "abc;def;xyz"; const char *test_array[] = { "abc", "def", "xyz" }; TEST_EQ(strv.nr, 0); TEST_EQ(strv.p, NULL); pr_dbg("split string into a vector using ';' delimiter\n"); strv_split(&strv, test_str, ";"); strv_for_each(&strv, s, i) TEST_STREQ(s, test_array[i]); pr_dbg("join the string vector into a single string\n"); s = strv_join(&strv, ";"); TEST_STREQ(s, test_str); free(s); TEST_EQ(strv.nr, 3); strv_free(&strv); TEST_EQ(strv.nr, 0); pr_dbg("append string items to string vector\n"); for (i = 0; i < 3; i++) { strv_append(&strv, test_array[i]); TEST_STREQ(strv.p[i], test_array[i]); TEST_EQ(strv.nr, i + 1); } s = strv_join(&strv, ";"); TEST_STREQ(s, test_str); free(s); TEST_EQ(strv.nr, 3); strv_free(&strv); return TEST_OK; } TEST_CASE(utils_parse_time) { struct TC { char *arg; int limit; uint64_t val; } tc[] = { { "10.123ns", 5, 10 }, { "50.987us", 5, 50987 }, { "100.654ms", 5, 100654000 }, { "123.123s", 5, 123123000000 }, { "17.1m", 5, 1026000000000 }, { "8960070.725168293s", 9, 8960070725168293 }, { "8959832.082682731s", 9, 8959832082682731 }, { "426883.003490100s", 9, 426883003490100 }, { "8107.0641850s", 9, 8107064185000 }, { "8107.006418501s", 9, 8107006418501 }, }; uint64_t time; int i; pr_dbg("parsing time with a unit\n"); for (i = 0; i < sizeof(tc) / sizeof(struct TC); i++) { time = parse_time(tc[i].arg, tc[i].limit); TEST_EQ(time, tc[i].val); } return TEST_OK; } TEST_CASE(utils_parse_timestamp) { struct TC { char *arg; uint64_t val; } tc[] = { { "8960070.725168293", 8960070725168293 }, { "8959832.082682731", 8959832082682731 }, { "426883.003490100", 426883003490100 }, { "8107.0641850", 8107064185000 }, { "8107.006418501234567890", 8107006418501 }, }; uint64_t time; int i; pr_dbg("parsing timestamp in various precisions with leading zeros\n"); for (i = 0; i < sizeof(tc) / sizeof(struct TC); i++) { time = parse_timestamp(tc[i].arg); TEST_EQ(time, tc[i].val); } return TEST_OK; } #endif /* UNIT_TEST */ uftrace-0.15.2/utils/utils.h000066400000000000000000000414551455365734300157310ustar00rootroot00000000000000/* * utiltily functions and macros for uftrace * * Copyright (C) 2014-2017, LG Electronics, Namhyung Kim * * Released under the GPL v2. */ #ifndef UFTRACE_UTILS_H #define UFTRACE_UTILS_H #include #include #include #include #include #include #include #include #include #include #include "compiler.h" #ifndef container_of #define container_of(ptr, type, member) \ ({ \ const typeof(((type *)0)->member) *__mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); \ }) #endif #ifndef ALIGN #define ALIGN(n, a) (((n) + (a)-1) & ~((a)-1)) #endif #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #define MAX(a, b) (((a) > (b)) ? (a) : (b)) #define DIV_ROUND_UP(v, r) (((v) + (r)-1) / (r)) #define ROUND_UP(v, r) (DIV_ROUND_UP((v), (r)) * (r)) #define ROUND_DOWN(v, r) (((v) / (r)) * (r)) #ifndef ARRAY_SIZE #define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) #endif #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) #define NSEC_PER_SEC 1000000000 #define NSEC_PER_MSEC 1000000 #define BUG_REPORT_MSG "Please report this bug to https://github.com/namhyung/uftrace/issues.\n\n" extern int debug; extern FILE *logfp; extern FILE *outfp; /* colored output for argspec display */ extern const char *color_reset; extern const char *color_bold; extern const char *color_string; extern const char *color_symbol; extern const char *color_struct; extern const char *color_enum; extern const char *color_enum_or; /* must change DBG_DOMAIN_STR (in mcount.h) as well */ enum debug_domain { DBG_UFTRACE = 0, DBG_SYMBOL, DBG_DEMANGLE, DBG_FILTER, DBG_FSTACK, DBG_SESSION, DBG_KERNEL, DBG_MCOUNT, DBG_PLTHOOK, DBG_DYNAMIC, DBG_EVENT, DBG_SCRIPT, DBG_DWARF, DBG_WRAP, DBG_DOMAIN_MAX, }; extern int dbg_domain[DBG_DOMAIN_MAX]; enum color_setting { COLOR_UNKNOWN, COLOR_AUTO, COLOR_OFF, COLOR_ON, }; enum format_mode { FORMAT_NORMAL, FORMAT_HTML, }; #define COLOR_CODE_NORMAL '.' #define COLOR_CODE_RESET '-' #define COLOR_CODE_RED 'R' #define COLOR_CODE_GREEN 'G' #define COLOR_CODE_BLUE 'B' #define COLOR_CODE_YELLOW 'Y' #define COLOR_CODE_MAGENTA 'M' #define COLOR_CODE_CYAN 'C' #define COLOR_CODE_GRAY 'g' #define COLOR_CODE_BOLD 'b' #define DEFAULT_EVENT_COLOR COLOR_CODE_GREEN extern void __pr_dbg(const char *fmt, ...); extern void __pr_out(const char *fmt, ...); extern void __pr_err(const char *fmt, ...) __attribute__((noreturn)); extern void __pr_err_s(const char *fmt, ...) __attribute__((noreturn)); extern void __pr_warn(const char *fmt, ...); extern void __pr_color(char code, const char *fmt, ...); extern enum color_setting log_color; extern enum color_setting out_color; extern enum format_mode format_mode; extern void setup_color(enum color_setting color, char *pager); extern void setup_signal(void); #ifndef PR_FMT #define PR_FMT "uftrace" #endif #ifndef PR_DOMAIN #define PR_DOMAIN DBG_UFTRACE #endif #define pr_dbg(fmt, ...) \ ({ \ if (dbg_domain[PR_DOMAIN]) \ __pr_dbg(PR_FMT ": " fmt, ##__VA_ARGS__); \ }) #define pr_dbg2(fmt, ...) \ ({ \ if (dbg_domain[PR_DOMAIN] > 1) \ __pr_dbg(PR_FMT ": " fmt, ##__VA_ARGS__); \ }) #define pr_dbg3(fmt, ...) \ ({ \ if (dbg_domain[PR_DOMAIN] > 2) \ __pr_dbg(PR_FMT ": " fmt, ##__VA_ARGS__); \ }) #define pr_dbg4(fmt, ...) \ ({ \ if (dbg_domain[PR_DOMAIN] > 3) \ __pr_dbg(PR_FMT ": " fmt, ##__VA_ARGS__); \ }) #define pr_err(fmt, ...) \ __pr_err_s(PR_FMT ": %s:%d:%s\n ERROR: " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define pr_err_ns(fmt, ...) \ __pr_err(PR_FMT ": %s:%d:%s\n ERROR: " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) #define pr_warn(fmt, ...) __pr_warn("WARN: " fmt, ##__VA_ARGS__) #define pr_out(fmt, ...) __pr_out(fmt, ##__VA_ARGS__) #define pr_cont(fmt, ...) __pr_out(fmt, ##__VA_ARGS__) #define pr_use(fmt, ...) __pr_out("Usage: " fmt, ##__VA_ARGS__) #define pr_red(fmt, ...) __pr_color(COLOR_CODE_RED, fmt, ##__VA_ARGS__) #define pr_green(fmt, ...) __pr_color(COLOR_CODE_GREEN, fmt, ##__VA_ARGS__) #define pr_blue(fmt, ...) __pr_color(COLOR_CODE_BLUE, fmt, ##__VA_ARGS__) #define pr_yellow(fmt, ...) __pr_color(COLOR_CODE_YELLOW, fmt, ##__VA_ARGS__) #define pr_magenta(fmt, ...) __pr_color(COLOR_CODE_MAGENTA, fmt, ##__VA_ARGS__) #define pr_cyan(fmt, ...) __pr_color(COLOR_CODE_CYAN, fmt, ##__VA_ARGS__) #define pr_bold(fmt, ...) __pr_color(COLOR_CODE_BOLD, fmt, ##__VA_ARGS__) #define pr_gray(fmt, ...) __pr_color(COLOR_CODE_GRAY, fmt, ##__VA_ARGS__) #define pr_color(c, fmt, ...) __pr_color(c, fmt, ##__VA_ARGS__) #define pr_flush() fflush(outfp) #define xmalloc(sz) \ ({ \ void *__ptr = malloc(sz); \ if (__ptr == NULL) { \ pr_err("xmalloc"); \ } \ __ptr; \ }) #define xzalloc(sz) \ ({ \ void *__ptr = calloc(sz, 1); \ if (__ptr == NULL) { \ pr_err("xzalloc"); \ } \ __ptr; \ }) #define xcalloc(sz, n) \ ({ \ void *__ptr = calloc(sz, n); \ if (__ptr == NULL) { \ pr_err("xcalloc"); \ } \ __ptr; \ }) #define xrealloc(p, n) \ ({ \ void *__ptr = realloc(p, n); \ if (__ptr == NULL) { \ pr_err("xrealloc"); \ } \ __ptr; \ }) #define xstrdup(s) \ ({ \ void *__ptr = strdup(s); \ if (__ptr == NULL) { \ pr_err("xstrdup"); \ } \ __ptr; \ }) #define xstrndup(s, sz) \ ({ \ void *__ptr = strndup(s, sz); \ if (__ptr == NULL) { \ pr_err("xstrndup"); \ } \ __ptr; \ }) #define xasprintf(s, fmt, ...) \ ({ \ int __ret = asprintf(s, fmt, ##__VA_ARGS__); \ if (__ret < 0) { \ pr_err("xasprintf"); \ } \ __ret; \ }) #define xvasprintf(s, fmt, ap) \ ({ \ int __ret = vasprintf(s, fmt, ap); \ if (__ret < 0) { \ pr_err("xvasprintf"); \ } \ __ret; \ }) #define call_if_nonull(fptr, ...) \ ({ \ if (fptr != NULL) \ fptr(__VA_ARGS__); \ }) #define stringify(s) __stringify(s) #define __stringify(s) #s #ifndef htonq #define htonq(x) htobe64(x) #endif #ifndef ntohq #define ntohq(x) be64toh(x) #endif /* this comes from /usr/include/elf.h */ #ifndef ELFDATA2LSB #define ELFDATA2LSB 1 /* 2's complement, little endian */ #define ELFDATA2MSB 2 /* 2's complement, big endian */ #endif #ifndef ELFCLASS32 #define ELFCLASSNONE 0 #define ELFCLASS32 1 #define ELFCLASS64 2 #endif static inline int get_elf_endian(void) { #if __BYTE_ORDER == __LITTLE_ENDIAN return ELFDATA2LSB; #else return ELFDATA2MSB; #endif } static inline int get_elf_class(void) { if (sizeof(long) == 4) return ELFCLASS32; else if (sizeof(long) == 8) return ELFCLASS64; else return ELFCLASSNONE; } static inline bool host_is_lp64(void) { return get_elf_class() == ELFCLASS64; } static inline const char *get_endian_str(void) { if (get_elf_endian() == ELFDATA2LSB) return "LE"; else return "BE"; } static inline char *has_kernel_opt(char *buf) { int idx = 0; if (!strncasecmp(buf, "kernel", 6)) idx = 6; else if (!strncasecmp(buf, "k", 1)) idx = 1; if (idx && (buf[idx] == '\0' || buf[idx] == ',')) return buf; return NULL; } static inline char *has_kernel_filter(char *buf) { char *opt = strchr(buf, '@'); if (opt && has_kernel_opt(opt + 1)) return opt; return NULL; } struct uftrace_time_range { uint64_t first; uint64_t start; uint64_t stop; bool start_elapsed; bool stop_elapsed; bool kernel_skip_out; bool event_skip_out; }; struct iovec; int read_all(int fd, void *buf, size_t size); int pread_all(int fd, void *buf, size_t size, off_t off); int fread_all(void *byf, size_t size, FILE *fp); int write_all(int fd, const void *buf, size_t size); int writev_all(int fd, struct iovec *iov, int count); int fwrite_all(const void *buf, size_t size, FILE *fp); int create_directory(const char *dirname); int remove_directory(const char *dirname); int chown_directory(const char *dirname); char *read_exename(void); extern clockid_t clock_source; void setup_clock_id(const char *clock_str); void print_time_unit(uint64_t delta_nsec); void print_diff_percent(uint64_t base_nsec, uint64_t delta_nsec); void print_diff_time_unit(uint64_t base_nsec, uint64_t pair_nsec); void print_diff_count(uint64_t base, uint64_t pair); char *setup_pager(void); void start_pager(char *pager); void wait_for_pager(void); bool check_time_range(struct uftrace_time_range *range, uint64_t timestamp); uint64_t parse_time(char *arg, int limited_digits); uint64_t parse_timestamp(char *arg); char *strjoin(char *left, char *right, const char *delim); char *json_quote(char *str, int *len); /* strv - string vector */ struct strv { int nr; /* actual allocation is (nr + 1) */ char **p; /* terminated by NULL (like argv[]) */ }; #define STRV_INIT \ (struct strv) \ { \ .nr = 0, .p = NULL, \ } #define strv_for_each(strv, s, i) for (i = 0; i < (strv)->nr && ((s) = (strv)->p[i]); i++) void strv_split(struct strv *strv, const char *str, const char *delim); void strv_copy(struct strv *strv, int argc, char *argv[]); void strv_append(struct strv *strv, const char *str); void strv_replace(struct strv *strv, int idx, const char *str); char *strv_join(struct strv *strv, const char *delim); void strv_free(struct strv *strv); char *str_ltrim(char *str); char *str_rtrim(char *str); char **parse_cmdline(char *cmd, int *argc); void free_parsed_cmdline(char **argv); struct uftrace_data; char *absolute_dirname(const char *path, char *resolved_path); char *uftrace_strerror(int errnum, char *buf, size_t buflen); void stacktrace(void); #if defined(__x86_64__) || defined(__i386__) #define TRAP() asm volatile("int $3") #else #define TRAP() raise(SIGTRAP) #endif #define DTRAP() \ if (DEBUG_MODE) { \ TRAP(); \ } #define ASSERT(cond) \ if (unlikely(!(cond))) { \ pr_red("%s:%d: %s: ASSERT `%s' failed.\n", __FILE__, __LINE__, __func__, #cond); \ stacktrace(); \ pr_red(BUG_REPORT_MSG); \ pr_flush(); \ TRAP(); \ } #define DASSERT(cond) \ if (DEBUG_MODE && unlikely(!(cond))) { \ pr_red("%s:%d: %s: DEBUG ASSERT `%s' failed.\n", __FILE__, __LINE__, __func__, \ #cond); \ stacktrace(); \ pr_red(BUG_REPORT_MSG); \ pr_flush(); \ TRAP(); \ } int copy_file(const char *path_in, const char *path_out); #endif /* UFTRACE_UTILS_H */