diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-03-15 05:42:58 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2021-03-15 05:42:58 +0000 |
commit | 1ad264274db5ff6927d3f4de2e356f6680c43b40 (patch) | |
tree | 66d6ed1ee34b681d59bba29caccccad3db79bbb5 | |
parent | Initial commit. (diff) | |
download | dwarves-dfsg-1ad264274db5ff6927d3f4de2e356f6680c43b40.tar.xz dwarves-dfsg-1ad264274db5ff6927d3f4de2e356f6680c43b40.zip |
Adding upstream version 1.19.upstream/1.19
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
150 files changed, 152068 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96e05c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/build +/config.h diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6be99dc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/bpf"] + path = lib/bpf + url = https://github.com/libbpf/libbpf diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..857487a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,166 @@ +project(pahole C) +cmake_minimum_required(VERSION 2.8.8) +cmake_policy(SET CMP0005 NEW) + +INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/lib/bpf/include/uapi) + +# Try to parse this later, Helio just showed me a KDE4 example to support +# x86-64 builds. +# the following are directories where stuff will be installed to +set(__LIB "" CACHE STRING "Define suffix of directory name (32/64)" ) + +macro(_set_fancy _var _value _comment) + if (NOT DEFINED ${_var}) + set(${_var} ${_value}) + else (NOT DEFINED ${_var}) + set(${_var} "${${_var}}" CACHE PATH "${_comment}") + endif (NOT DEFINED ${_var}) +endmacro(_set_fancy) + +# where to look first for cmake modules, +# before ${CMAKE_ROOT}/Modules/ is checked +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules") + +if (NOT CMAKE_BUILD_TYPE) + set (CMAKE_BUILD_TYPE Debug CACHE STRING + "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." + FORCE) +endif (NOT CMAKE_BUILD_TYPE) + +set(CMAKE_C_FLAGS_DEBUG "-Wall -Werror -ggdb -O0") +set(CMAKE_C_FLAGS_RELEASE "-Wall -O2") + +# Just for grepping, DWARVES_VERSION isn't used anywhere anymore +# add_definitions(-D_GNU_SOURCE -DDWARVES_VERSION="v1.19") +add_definitions(-D_GNU_SOURCE -DDWARVES_MAJOR_VERSION=1) +add_definitions(-D_GNU_SOURCE -DDWARVES_MINOR_VERSION=19) +find_package(DWARF REQUIRED) +find_package(ZLIB REQUIRED) + +# make sure git submodule(s) are checked out +find_package(Git QUIET) +if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") + # Update submodules as needed + option(GIT_SUBMODULE "Check submodules during build" ON) + if(GIT_SUBMODULE) + message(STATUS "Submodule update") + execute_process(COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_SUBMOD_RESULT) + if(NOT GIT_SUBMOD_RESULT EQUAL "0") + message(FATAL_ERROR "git submodule update --init failed with ${GIT_SUBMOD_RESULT}, please checkout submodules") + else() + message(STATUS "Submodule update - done") + endif() + endif() +endif() +if(NOT EXISTS "${PROJECT_SOURCE_DIR}/lib/bpf/src/btf.h") + message(FATAL_ERROR "The submodules were not downloaded! GIT_SUBMODULE was turned off or failed. Please update submodules and try again.") +endif() + +_set_fancy(LIB_INSTALL_DIR "${EXEC_INSTALL_PREFIX}${CMAKE_INSTALL_PREFIX}/${__LIB}" "libdir") + +# libbpf uses reallocarray, which is not available in all versions of glibc +# libbpf's include/tools/libc_compat.h provides implementation, but needs +# COMPACT_NEED_REALLOCARRAY to be set +INCLUDE(CheckCSourceCompiles) +CHECK_C_SOURCE_COMPILES( +" +#define _GNU_SOURCE +#include <stdlib.h> +int main(void) +{ + return !!reallocarray(NULL, 1, 1); +} +" HAVE_REALLOCARRAY_SUPPORT) +if (NOT HAVE_REALLOCARRAY_SUPPORT) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DCOMPAT_NEED_REALLOCARRAY") +endif() + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64") + +file(GLOB libbpf_sources "lib/bpf/src/*.c") +add_library(bpf OBJECT ${libbpf_sources}) +set_property(TARGET bpf PROPERTY POSITION_INDEPENDENT_CODE 1) +target_include_directories(bpf PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/lib/bpf/include + ${CMAKE_CURRENT_SOURCE_DIR}/lib/bpf/include/uapi) + +set(dwarves_LIB_SRCS dwarves.c dwarves_fprintf.c gobuffer strings + ctf_encoder.c ctf_loader.c libctf.c btf_encoder.c btf_loader.c libbtf.c + dwarf_loader.c dutil.c elf_symtab.c rbtree.c) +add_library(dwarves SHARED ${dwarves_LIB_SRCS} $<TARGET_OBJECTS:bpf>) +set_target_properties(dwarves PROPERTIES VERSION 1.0.0 SOVERSION 1) +set_target_properties(dwarves PROPERTIES INTERFACE_LINK_LIBRARIES "") +target_include_directories(dwarves PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/lib/bpf/include/uapi) +target_link_libraries(dwarves ${DWARF_LIBRARIES} ${ZLIB_LIBRARIES}) + +set(dwarves_emit_LIB_SRCS dwarves_emit.c) +add_library(dwarves_emit SHARED ${dwarves_emit_LIB_SRCS}) +set_target_properties(dwarves_emit PROPERTIES VERSION 1.0.0 SOVERSION 1) +target_link_libraries(dwarves_emit dwarves) + +set(dwarves_reorganize_LIB_SRCS dwarves_reorganize.c) +add_library(dwarves_reorganize SHARED ${dwarves_reorganize_LIB_SRCS}) +set_target_properties(dwarves_reorganize PROPERTIES VERSION 1.0.0 SOVERSION 1) +target_link_libraries(dwarves_reorganize dwarves) + +set(codiff_SRCS codiff.c) +add_executable(codiff ${codiff_SRCS}) +target_link_libraries(codiff dwarves) + +set(ctracer_SRCS ctracer.c) +add_executable(ctracer ${ctracer_SRCS}) +target_link_libraries(ctracer dwarves dwarves_emit dwarves_reorganize ${ELF_LIBRARY}) + +set(dtagnames_SRCS dtagnames.c) +add_executable(dtagnames ${dtagnames_SRCS}) +target_link_libraries(dtagnames dwarves) + +set(pahole_SRCS pahole.c) +add_executable(pahole ${pahole_SRCS}) +target_link_libraries(pahole dwarves dwarves_reorganize) + +set(pdwtags_SRCS pdwtags.c) +add_executable(pdwtags ${pdwtags_SRCS}) +target_link_libraries(pdwtags dwarves) + +set(pglobal_SRCS pglobal.c) +add_executable(pglobal ${pglobal_SRCS}) +target_link_libraries(pglobal dwarves) + +set(pfunct_SRCS pfunct.c) +add_executable(pfunct ${pfunct_SRCS}) +target_link_libraries(pfunct dwarves dwarves_emit ${ELF_LIBRARY}) + +set(prefcnt_SRCS prefcnt.c) +add_executable(prefcnt ${prefcnt_SRCS}) +target_link_libraries(prefcnt dwarves) + +set(scncopy_SRCS scncopy.c elfcreator.c) +add_executable(scncopy ${scncopy_SRCS}) +target_link_libraries(scncopy dwarves ${ELF_LIBRARY}) + +set(syscse_SRCS syscse.c) +add_executable(syscse ${syscse_SRCS}) +target_link_libraries(syscse dwarves) + +install(TARGETS codiff ctracer dtagnames pahole pdwtags + pfunct pglobal prefcnt scncopy syscse RUNTIME DESTINATION + ${CMAKE_INSTALL_PREFIX}/bin) +install(TARGETS dwarves LIBRARY DESTINATION ${LIB_INSTALL_DIR}) +install(TARGETS dwarves dwarves_emit dwarves_reorganize LIBRARY DESTINATION ${LIB_INSTALL_DIR}) +install(FILES dwarves.h dwarves_emit.h dwarves_reorganize.h + dutil.h gobuffer.h list.h rbtree.h pahole_strings.h + btf_encoder.h config.h ctf_encoder.h ctf.h + elfcreator.h elf_symtab.h hash.h libbtf.h libctf.h + DESTINATION ${CMAKE_INSTALL_PREFIX}/include/dwarves/) +install(FILES man-pages/pahole.1 DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man1/) +install(PROGRAMS ostra/ostra-cg DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +install(PROGRAMS btfdiff fullcircle DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) +install(FILES ostra/python/ostra.py DESTINATION ${CMAKE_INSTALL_PREFIX}/share/dwarves/runtime/python) +install(FILES lib/Makefile lib/ctracer_relay.c lib/ctracer_relay.h lib/linux.blacklist.cu + DESTINATION ${CMAKE_INSTALL_PREFIX}/share/dwarves/runtime) @@ -0,0 +1,339 @@ + 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. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin 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. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..57a721a --- /dev/null +++ b/MANIFEST @@ -0,0 +1,71 @@ +config.h.cmake +btfdiff +btf_encoder.c +btf_encoder.h +btf_loader.c +ctf_encoder.c +ctf_encoder.h +ctf_loader.c +dwarf_loader.c +dwarves.c +dwarves.h +dwarves_emit.c +dwarves_emit.h +dwarves_fprintf.c +dwarves_reorganize.c +dwarves_reorganize.h +cmake/modules/FindDWARF.cmake +CMakeLists.txt +codiff.c +ctracer.c +dtagnames.c +elfcreator.c +elfcreator.h +elf_symtab.c +elf_symtab.h +fullcircle +gobuffer.c +gobuffer.h +hash.h +libbtf.c +libbtf.h +list.h +MANIFEST +man-pages/pahole.1 +pahole.c +pdwtags.c +pfunct.c +pglobal.c +prefcnt.c +rbtree.c +rbtree.h +scncopy.c +syscse.c +strings.c +pahole_strings.h +dutil.c +dutil.h +changes-v1.13 +changes-v1.16 +changes-v1.17 +changes-v1.18 +changes-v1.19 +COPYING +NEWS +README +README.DEBUG +README.btf +README.ctracer +README.tarball +rpm/SPECS/dwarves.spec +lib/Makefile +lib/ctracer_relay.c +lib/ctracer_relay.h +lib/linux.blacklist.cu +ostra/ostra-cg +ostra/python/ostra.py +ctf.h +libctf.c +libctf.h +regtest +lib/bpf/ @@ -0,0 +1,668 @@ +v1.19: + +Fri Nov 20 2020 + +bf21da407593f104 fprintf: Make typedef__fprintf print anonymous enums +9c4bdf9331bf06a7 fprintf: Align enumerators +89cf28228a8ab55e fprintf: Add enumeration__max_entry_name_len() +932b84eb45a9b8ba fprintf: Make typedef__fprintf print anonymous structs +4a1479305b53933d pahole: Add heuristic to auto-add --btf_base for /sys/kernel/btf/ prefixed files +e1d01045828a5c4c btf: Fallback to raw BTF mode if the header magic matches +24cea890abedb15b pahole: Force '-F btf' with --btf_base +cfad738682506ce4 libbtf: Assume its raw_btf if filename starts with "/sys/kernel/btf/" +7293c7fceac36844 pahole: The --btf_base option receives a PATH, not a SIZE +b3dd4f3c3d5ea59e btf_encoder: Use better fallback message +d06048c53094d9d2 btf_encoder: Move btf_elf__verbose/btf_elf__force setup +8156bec8aedb685b btf_encoder: Fix function generation +d0cd007339ee509e btf_encoder: Generate also .init functions +25753e0396abea25 pfunct: Use load stealer to speed up --class +aa8fb8c091446467 man-pages: Add entry for -J/--btf_encode to pahole's man page +ace05ba9414c1fe4 btf: Add support for split BTF loading and encoding +7290d08b4a6e5876 libbpf: Update libbpf submodule reference to latest master +344f2483cfcd4952 libbtf: Improve variable naming and error reporting when writing out BTF +94a7535939f92e91 btf_encoder: Fix array index type numbering +9fa3a100f71e7a13 pfunct: Use a load stealer to stop as soon as a function is found +de18bd5fe3515358 pfunct: Try sole argument as a function name, just like pahole +bc1afd458562f21e pahole: Introduce --numeric_version for use in scripts and Makefiles +784c3dfbd63d5bcf dwarves: Switch from a string based version to major/minor numbers +fc06ee1b6e9dc14a pahole: Check if the sole arg is a file, not considering it a type if so +f47b3a2df3622204 dwarf_loader: Fix partial unit warning +5a22c2de79fb9edf btf_encoder: Change functions check due to broken dwarf +7b1af3f4844b36b9 btf_encoder: Move find_all_percpu_vars in generic collect_symbols +863e6f0f2cc08592 btf_encoder: Check var type after checking var addr. +5e7ab5b9e064a3eb btf_loader: Handle union forward declaration properly +ec3f944102a71241 cmake: Make libbpf's Linux UAPI headers available to all binaries +8cac1c54c83c346b btf_encoder: Ignore zero-sized ELF symbols +040fd7f585c9b9fc btf_encoder: Support cross-compiled ELF binaries with different endianness +29fce8dc8571c6af strings: use BTF's string APIs for strings management +75f3520fedf6afdb strings: Rename strings.h to avoid clashing with /usr/include/strings.h +bba7151e0fd2fb3a dwarf_loader: increase the size of lookup hash map +2e719cca66721284 btf_encoder: revamp how per-CPU variables are encoded +0258a47ef99500db btf_encoder: Discard CUs after BTF encoding +3c913e18b2702a9e btf_encoder: Fix emitting __ARRAY_SIZE_TYPE__ as index range type +48efa92933e81d28 btf_encoder: Use libbpf APIs to encode BTF type info +5d863aa7ce539e86 btf_loader: Use libbpf to load BTF +0a9b89910e711951 dwarves: Expose and maintain active debug info loader operations +7bc2dd07d51cb5ee btf_encoder: detect BTF encoding errors and exit +c35b7fa52cb8112a libbpf: Update to latest libbpf version +ef4f971a9cf745fc dwarf_loader: Conditionally define DW_AT_alignment +cc3f9dce3378280f pahole: Implement --packed +08f49262f474370a man-pages: Fix 'coimbine' typo + +v1.18: + +Fri 02 Oct 2020 + +aee6808c478b760f btf_loader: Initialize function->lexblock.tags to fix segfault in pdwtags +c815d26689313d8d btf_encoder: Handle DW_TAG_variable that has DW_AT_specification +b8068e7373cc1592 pahole: Only try using a single file name as a type name if not encoding BTF or CTF +8b1c63283194ebe1 libctf: Make can't get header message to appear only in verbose mode +63e11400e80f6ac4 libbtf: Make can't get header message to appear only in verbose mode +fc2b317db0bdc1a9 dwarf_loader: Check for unsupported_tag return in last two missing places +2b5f4895e8968e83 dwarf_loader: Warn user about unsupported TAGs +010a71e181b450d7 dwarf_loader: Handle unsupported_tag return in die__process_class() +3d616609ee0fd6df dwarf_loader: Add minimal handling of DW_TAG_subrange_type +2e8cd6a435d96335 dwarf_loader: Ignore DW_TAG_variant_part for now to fix a segfault +e9e6285fd0d63ed0 dwarf_loader: Skip empty CUs +1abc001417b579b8 btf_encoder: Introduce option '--btf_encode_force' +da4ad2f65028e321 btf_encoder: Allow disabling BTF var encoding. +f5847773d94d4875 fprintf: Support DW_TAG_string_type +8b00a5cd677df778 dwarf_loader: Support DW_TAG_string_type +0d9c3c9835659fb7 dwarves: Check if a member type wasn't found and avoid a NULL deref +2ecc308518edfe05 dwarf_loader: Bail out at DW_TAG_imported_unit tags +8c92fd298101171d dwarf_loader: Ignore entries in a DW_TAG_partial_unit, for now +4cfd420f7ef13af4 README: Add instructions to do a cross build +9e495f68c683574f dwarf_loader: Move vaddr to conditional where it is used +69fce762074a5483 pahole: Use "%s" in a snprintf call +22f93766cf02f4e0 pahole: Support multiple types for pretty printing +78f2177d904902c6 pahole: Print the evaluated range= per class +5c32b9d5c7ac8fff pahole: Count the total number of bytes read from stdin +e3e5a4626c94d9c5 pahole: Make sure the header is read only once +208bcd9873442013 pahole: Introduce 'range=member' as a class argument for pretty printing +b9e406311990c2d5 pahole: Cache the type_enum lookups into struct enumerator +fda1825f0b9f1c9f dwarves: Introduce tag_cu_node, so that we can have the leaner tag_cu +47d4dd4c8a2cf9fd pahole: Optimize --header processing by keeping the first successfull instance +fdfc64ec44f4ad53 pahole: Introduce --range +f63d3677e31d88a3 pahole: Support multiple enums in type_enum= +c50b6d37e99fddec pahole: Add infrastructure to have multiple concatenated type_enum +671ea22ca18dd41f pahole: Move finding type_enum to a separate function +dd3c0e9eb0cbc77f dwarves: Move the common initialization of fields for 'struct type' +aa7ab3e8755a4c12 pahole: Allow for more compact enum filters by suppressing common prefix +4ece15c37b028a80 dwarves: Find common enumerators prefix +d28bc97b5c12736b man-pages: Document pretty printing capabilities and provide examples +a345691f08f04f19 pahole: Make --header without -C to be equivalent to -C header-arg --count=1 +eba4ac6b2c1dcb11 pahole: Fallback to pretty printing using types in multiple CUs +7c12b234ee30f6d2 dwarves: Introduce cus__find_type_by_name() +c6f3386e3364486d pahole: Make the type_instance constructor receive the looked up type + its CU +30297256e1f27de5 pahole: Pass the header type_instance to tag__stdio_fprintf_value() +ead8084d3ffcda1f pahole: Store the CU in the type_instance struct +2c886fff0af13510 pahole: Store the CU where type_enum was found +526b116bfec71054 pahole: If pretty printing, don't discard CUs, keep them +a1b8fddb4dcd90d9 pahole: Show which classes were not processed and why +bc6a3cac50cdbdf6 pahole: Fixup the --class_name parsing wrt class args (type=, sizeof=, etc) +363c37340521debe pahole: Remope pretty printed classes from the prototypes list +9f675e7fdbf525fb cmake: Use -O0 for debug builds +4e5b02beea24656d pahole: Don't stop when not finding the type_enum +823739b56f2b5230 pahole: Convert class_names into a list of struct prototypes +0a97d6c143fcc92a pahole: Factor out parsing class prototypes +80af0fbbf3a3f1f6 dutils: Allow for having a priv area per strlist +04b6547e7343410e pahole: Honour --hex_fmt when pretty printing +266c0255984ddbe7 pahole: Support filters without 'filter=' +6c683ce0e11d6c1c pahole: Allow for a 'type' boolean class arg meaning 'type=type' +1c68f41bb87f7718 pahole: Allow for a 'sizeof' boolean class arg meaning 'sizeof=size' +b4b3fb0a3aeb9686 pahole: First look for ',' then '=' to allow for boolean args +446b85f7276bda4c pahole: Add support for --size_bytes, accepts header variables +0f10fa7529d4a9c7 pahole: Move reading the header to outside the --seek_bytes code +9310b04854a8b709 pahole: Add support for referencing header variables when pretty printing +611f5e8bd7eb760d pahole: Add == class member filtering +f2987224beb2b496 pahole: Do the 'type_enum' class argument validation as soon as we parse it +02ca176c6290ff96 pahole: Do the 'type' class argument validation earlier +172d743d9ce3f3a1 pahole: Do the 'sizeof' class argument validation earlier +9a30c101d7c019ee pahole: As soon as a attribute is found, check if the type is a struct or class +a38ccf47237298dc pahole: Allow filter=expression as a class argument for pretty printing +3c4ee1d91f5311ac pahole: Pretty print bitfields +451d66ec02e94492 pahole: Pretty print unions too, cope with unnamed ones +48c7704b49ffa6b1 pahole: Check if the type with arguments is present in the current CU +472519ac2c49d340 pahole: Support nested structs +ae43029363ee2c53 dwarves_fprintf: Export the 'tabs' variable +ab714acec86841b7 pahole: Support zero sized base type arrays +ff7815a0f823a676 pahole: Add missing space before '}' in array__fprintf_base_type_value() +5f2502274e1097db pahole: Support zero sized arrays in array__fprintf_base_type_value() +3aadfbdd72e32016 pahole: Follow array type typedefs to find real sizeof(entry) +7309039aa7510eec pahole: Make 'type' + 'type_enum' select a type to cast a variable sized record +42b7a759f37e5d45 dutil: Add a strlwr() helper to lowercase a string, returning it +a5bb31b86fbe5362 pahole: Fix --skip for variable sized records +f4df384d77312867 pahole: Decouple reading ->sizeof_member from printing +78cdde5cb7200555 pahole: Introduce 'type_enum' class argument +163c330d3138e56f dwarves: Introduce cu__find_enumeration_by_name() +20051b7157fce20c pahole: Add the 'type' modifier to make a struct member be used to find a cast type +210dffe0d13d171b pahole: Iterate classes in the order specified in the command line: +37a5c7c5ba0e1e7d strlist: Allow iterating a strlist in the original order +6b5d938d99b2e457 pahole: Support multiple class/struct args +affbebf04bcaa1ec pahole: Make the class splitter take into account per-class parameters +84387de4a5cf141d pahole: Allow specifying a struct member based sizeof() override +e0773683fa3edd61 dwarves: Allow setting a struct/class member as the source of sizeof() +f399db09a0d5b069 pahole: Allow simple parser for arguments to classes +04d957ba3cdf1047 pahole: Add variable for class name when traversing list of classes +f3d9054ba8ff1df0 btf_encoder: Teach pahole to store percpu variables in vmlinux BTF. +fe284221448c950d pahole: Introduce --seek_bytes +44af7c02b5d0a7f2 pahole: Implement --skip, just like dd +5631fdcea1bbe2bc pahole: Introduce --count, just like dd's +272bc75024b4f08c man-pages: Add information about stdin raw data pretty printing +66835f9e190ce77e pahole: Add support for base type arrays +1a67d6e70090953d pahole: Factor out base_type__fprintf_value() +6fb98aa1209f02b5 pahole: Support char arrays when dumping from stdin +a231d00f8d08825e pahole: Print comma at the end of field name + field value +1b2cdda38c0a6f5e dwarves: Introduce tag__is_array() +a83313fb22520d8d pahole: Pretty print base types in structs from stdin +cc65946e3068f7b6 dwarves: Adopt tag__is_base_type() from ctrace.c +d8079c6d373a5754 pahole: Hex dump a type from stdio when it isn't a tty +38109ab45fe0307d spec: Fix date + +v1.17: + +Fri 13 Mar 2020 + +f14426e41046 docs: Add command line to generate tarball with a prefix +0b2621d426ea dwarves: Avoid truncation when concatenating paths for dir entries +d7b351079583 dwarves: Don't use conf if its NULL in cus__load_running_kernel() +dde3eb086dd3 dwarves: Make list__for_all_tags() more robust +081f3618a795 dwarves: Add -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 to build libbpf +e8f43a4963bf libbpf: Get latest libbpf +de84ae36c738 cmake: libebl was merged into libdw +290b8fdbdab7 pahole: Improve --contains --recursive a bit +3d5be866e55f pahole: Fill 'tab' with spaces earlier +88674f039551 gobuffer: Do not crash on object without any public symbols +cb17f94f46a0 Add changes-v1.16 to the MANIFEST file +32a19e044c93 pahole: Fix changelog date in dwarves.spec +9b09f578b7d1 pahole: Initialize var to silence -Werror=maybe-uninitialized on gcc version 4.8.5 20150623 +277c2b3d1b4a man-pages: Add section about --hex + -E to locate offsets deep into sub structs +812c980b3f76 man-pages: Update some info, expand BTF info, add some EXAMPLES +e0942c7b031f pahole: Add support for finding pointers to void +6a326e93088e pahole: Make --contains and --find_pointers_to to work with base types +3c1a9a3329d9 pahole: Make --contains look for more than just unions, structs +ded5d36f9cf5 dwarves: Introduce cu__find_type_by_name +88d99562e55c pahole: Add --structs to ask just for structs, counterpart of --unions +0b444cf1118c pahole: Make --contains and --find_pointers_to honour --unions +a8a7e605bb45 pahole: Make --find_pointers_to consider unions +0dc327e382f8 pahole: Consider unions when looking for classes containing some class +3661f17d0b2c pahole: Introduce --unions to consider just unions +2dd09c617101 pahole: union have no holes, fix --sizes formatter +6c29cca8c28f pahole: Don't consider unions for options that only make sense for structs +dcba200367f7 pahole: When the sole argument passed isn't a file, take it as a class name +1944de0c93e6 pahole: Use function__for_each_parameter() +66e640508e4d dwarves: Make function__for_each_parameter receive 'cu' arg +252b0fcc91cc pahole: Fix -m/--nr_methods - Number of functions operating on a type pointer +202c8d5f5bd5 pahole: Do not require a class name to operate without a file name +617f5ac2e6c9 dwarves: Move BTF loader ahead of the CTF one +cdd5e1399b9c btf loader: Support raw BTF as available in /sys/kernel/btf/vmlinux + +v1.16: + +Mon Dec 16 2019 + +69df73444c69 (HEAD -> master, acme.korg/master) dwarves: Add -O2 to CFLAGS +6d11dd157f75 elfcreator: Address initialization warning emitted by 'gcc -O6' +1bc63a4cffa7 fprintf: Fixup truncation possibility pointed out by gcc -O2 +5c590fc29d06 ptr_table: Zero out new id ranges +644466dce77f fprintf: Remove extraneous sizeof operator +a59459bb80e7 fprintf: Account inline type __aligned__ member types for spacing +56547f133a5b fprintf: Fix alignment of class members that are structs/enums/unions +be37b64aef38 dwarves: Ignore static members for alignment +cca018e495a8 SPEC: Add dwarves dependency on libdwarves1 +ccf3eebfcd9c btf_loader: Add support for BTF_KIND_FUNC +f95fd85f7b53 pfunct: type->type == 0 is void, fix --compile for that +3e8f09e304ba (origin/master, origin/HEAD) pdwtags: Print DW_TAG_subroutine_type as well +3c5f2a224aa1 btf_encoder: Preserve and encode exported functions as BTF_KIND_FUNC +910e060b5c4b btf_loader: Skip BTF_KIND_DATASEC entries +96235a74a34d btf_loader: Replace if/else series with a proper switch {} +a4ba2234ff58 btf_loader: Support BTF_KIND_VAR +5965ce015eab dwarves: Fix ptr_table__add_with_id() handling of pt->nr_entries +173911ac38ea btf_loader: Show the unknown kind numbers +0d8e11e8568e pglobal: Allow passing the format path specifier, to use with BTF +ba27df9f2f94 CMakeLists: Use lib/bpf/include/uapi when building libdwarves +95517e1d995e libbpf: Get latest libbpf +ca294eebfc47 MANIFEST: Add missing files +d5e01d10e515 fprintf: Set tconf.type_spacing earlier +c6a9a0eb6ac7 fprintf: Fix up decrementing recursivity level in type__fprintf() +93c3cdf89715 cmake: Add -Wall to CFLAGS +7360f0528ff3 elfcreator: Remove unused 'machine' variable +0f52b11f911c prefcnt: Avoid ambiguous else with for_each macro +608813d0affa pglobal: Avoid ambiguous else +77d06c2d305d reorganize: Enclose bitfield routines under ifdef +2221ae30bce9 reorganize: Ditch unused variable +c419c513eae5 libbtf: Enlarge the 'cmd' buffer to not possibly truncate the pathname +092fffe56701 btf_loader: Enum values are s32, cast before calling btf_elf__get32() +7bfb1aa80f9a libctf: Enlarge the 'cmd' buffer to not possibly truncate the pathname +f67c281f9841 fprintf: Correct the type for the 'cacheline' variable, it should be uint32_t +7b36fab5a8df fprintf: Add guard against unlikely overlapping copy +e737976c09d9 fprintf: Do not scrub type when looking up its type +e95dacb704bf fprintf: Remove unused printf arg when printing enumerations +71c4f83f2828 emit: Remove unused 'is_pointer' variable +fe87354c31bb dwarves: Ditch unused asprintf() function +60c73a769882 dwarves: We need to consistently check if 'conf was specified +5fdfd09a6bbe dwarves: Fix check in type__find_first_biggest_size_base_type_member() +24ced5be8a75 dwarf_loader: Fix array overrun access +33e2d7aa35a7 ctf_loader: Plug leak when bailing out due to unknown tag +aefa9303818b codiff: Remove unused filenames variable +de5e72bc15fb btf_loader: Plug leak when bailing out due to unknown tag +511a79129494 dwarves: Remove unused variable +b1412a88bb61 fprintf: Fixup handling classes with no members +[acme@quaco pahole]$ + +v1.15 + +Thu Jun 27 2019 + +3ed9a67967cf fprintf: Avoid null dereference with NULL configs +568dae4bd498 printf: Fixup printing "const" early with "const void" +68f261d8dfff fprintf: Fix recursively printing named structs in --expand_types +139a3b337381 ostra: Initial python3 conversion +01276a7e8966 spec: Sync spec with fedora's +9f1f0628b9ad rpm: Add missing devel headers +989dc3f1ba0d cmake: Install missing devel headers + +v1.13 + +Tue Apr 16 2019 + +See changes-v1.13 for a more verbose description of the changes. + +0fb727166a0e pfunct: Strip inlines in the code generated for --compile +7b967744db7b emit: Emit the types for inline structs defined in pointer members +30526e2848db fprintf: Print relative offsets for inner pointer structs +cfa377c238f8 emit: Do not emit a forward declararion to a nameless struct +cf459ca16fb2 fprintf: Pretty print struct members that are pointers to nameless structs +09ed2e78befe pfunct: Emit definitions for pointers inside pointer to function args +e9fc2f647026 fullcircle: Check that we found the CFLAGS +05921c47f557 emit: Handle typedefs that are a pointer to typedefs +87af9839bf63 fullcircle: Try building from pfunct --compile and check types +c7fd9cc1fe97 pfunct: Fixup more return types +56c50b8dbe9b emit: Make find_fwd_decl a bit more robust +2bcb01fc2f9d fprintf: Handle single zero sized array member structs +0987266cd9e2 fprintf: Deal with zero sized arrays in the midle of a union +1101337a7450 fprintf: Deal with zero sized arrays in the middle of a struct +13aa13eb0ca4 fprintf: Don't reuse 'type' in multiple scopes in the same function +c8fc6f5a7a46 core: Use unnatural alignment of struct embedded in another to infer __packed__ +6f0f9a881589 fprintf: Fixup multi-dimensional zero sized arrays const handling +9a4d71930467 fprintf: Allow suppressing the inferred __attribute__((__packed__)) +ec935ee422b0 fprintf: Allow suppressing the output of force paddings at the end of structs +8471736f3c6c core: Cope with zero sized types when looking for natural alignment +986a3b58a869 fprintf: Only add bitfield forced paddings when alignment info available +49c27bdd6663 core: Allow the loaders to advertise features they have +dc6b9437a3a0 emit: Handle structs with DW_AT_alignment=1 meaning __packed__ +f78633cfb949 core: Infer __packed__ for union struct members +75c52de9c6df core: Move packed_attribute_inferred from 'class' to 'type' class +1bb4527220f4 fprintf: Fixup const pointers +dc3d44196103 core: Improve the natural alignment calculation +ac32e5e908ba codiff: Fix comparision of multi-cu against single-cu files +f2641ce169d6 core: Take arrays into account when inferring if a struct is packed +85c99369631a fprintf: Do not add explicit padding when struct has __aligned__ attr +b5e8fab596d3 emit: Cover void ** as a function parameter +28a3bc7addad fprintf: Support packed enums +f77a442f09e3 fprintf: Do not print the __aligned__ attribute if asked +ea583dac52f0 fprintf: Print zero sized flat arrays as [], not [0] +f909f13dd724 fprintf: Fixup handling of unnamed bitfields +3247a777dcfc core: Infer if a struct is packed by the offsets/natural alignments +13e5b9fc00ee fprintf: Add unnamed bitfield padding at the end to rebuild original type +ccd67bdb205b fprintf: Print "const" for class members more early, in type__fprintf() +b42d77b0bbda fprintf: Print __attribute__((__aligned__(N))) for structs/classes +1c9c1d6bbd45 dwarf_loader: Store DW_AT_alignment if available in DW_TAG_{structure,union,class}_type +41c55858daf4 codiff: Add --quiet option +a104eb1ea11d fprintf: Notice explicit bitfield alignment modifications +75f32a24c7c7 codiff: Improve the comparision of anonymous struct members +6b1e43f2c1ac codiff: When comparing against a file with just one CU don't bother finding by name +15a754f224f7 core: Add nr_entries member to 'struct cus' +99750f244cb8 pfunct: Generate a valid return type for the --compile bodies +881aabd6fc22 reorganize: Introduce class__for_each_member_from_safe() +1b2e3389f304 reorganize: Introduce class__for_each_member_reverse() +10fef2916dce reorganize: Introduce class__for_each_member_continue() +e7a56ee8cc69 reorganize: Introduce class__for_each_member_from() +9a79bb6ced23 tag: Introduce tag__is_pointer_to() +45ad54594442 tag: Introduce tag__is_pointer() +89ce57a02e3a pdwtags: Find holes in structs +ce6f393bc9ea fprintf: Fixup the printing of const parameters +7aec7dd6c29c pfunct: Do not reconstruct external functions +163b873f81c8 pfunct: Do not reconstruct inline expansions of functions +ea83b780eca0 pfunct: Handle unnamed struct typedefs +e7ebc05d12e1 emit: Unwind the definitions for typedefs in type__emit_definitions() +093135b0bfd5 pfunct: Do not emit a type multiple times +3ce2c5216612 pfunct: Ask for generating compilable output that generates DWARF for types +e7a786540d83 pfunct: Make --expand_types/-b without -f expand types for all functions +9b2eadf97b44 pfunct: Follow const, restrict, volatile in --expand_types +f3f86f2f89b0 pfunct: Reconstruct function return types for --expand_types +a7d9c58cb81a fprintf: Add missing closing parens to the align attribute +d83d9f578fa0 dwarf_loader: Handle DW_TAG_label in inline expansions +73e545b144b4 dwarf_loader: Handle unsupported_tag in die__process_inline_expansion +fe590758cb3f class__find_holes: Zero out bit_hole/hole on member +863c2af6e9d7 reorganize: Disable the bitfield coalescing/moving steps +b95961db697a fprintf: Show statistics about holes due to forced alignments +ec772f21f681 fprintf: Show the number of forced alignments in a class +52d1c75ea437 btfdiff: Use --suppress_aligned_attribute with -F dwarf +6cd6a6bd8787 dwarves_fprintf: Allow suppressing the __attribute__((__aligned__(N)) +f31ea292e3cb dwarf_loader: Store the DW_AT_alignment if available +c002873c4479 dwarves_fprintf: Move invariant printing of ; to outside if block +8ce85a1ad7f0 reorganize: Use class__find_holes() to recalculate holes +5d1c4029bd45 dwarves: Fix classification of byte/bit hole for aligned bitfield +78c110a7ea24 dwarves: Revert semantics of member bit/byte hole +b56fed297e5f dwarves_fprintf: Count bitfield member sizes separately +c0fdc5e685e9 dwarf_loader: Use DWARF recommended uniform bit offset scheme +5104d1bef384 loaders: Record CU's endianness in dwarf/btf/ctf loaders +975757bc8867 dwarves: Use bit sizes and bit/byte hole info in __class__fprintf +1838d3d7623e dwarves: Revamp bit/byte holes detection logic +03d9b6ebcac7 dwarf_loader: Fix bitfield fixup logic for DWARF +4abc59553918 btf_loader: Adjust negative bitfield offsets early on +41cf0e3cba0c dwarf_loader: Don't recode enums and use real enum size in calculations +55c96aaed8ce loaders: Strip away volatile/const/restrict when fixing bitfields +7005757fd573 libbpf: Sync in latest libbpf sources +69970fc77ec5 pahole: Filter out unions when looking for packable structs +fa963e1a8698 dwarves_fprintf: Print the bit_offset for inline enum bitfield class members +bb8350acf52f dwarves: Switch type_id_t from uint16_t to uint32_t +5375d06faf26 dwarves: Introduce type_id_t for use with the type IDs +f601f6725890 libctf: The type_ids returned are uint32_t fixup where it was uint16_t +c9b2ef034f89 dwarf: Add cu__add_tag_with_id() to stop using id == -1 to allocate id +762e7b58f447 dwarves: Change ptr_table__add() signature to allow for uint32_t returns +079e6890b788 dwarf_loader: Mark tag__recode_dwarf_bitfield() static +3526ebebd3ab pahole: Use 32-bit integers for type ID iterations within CU +3bd8da5202e4 libbpf: update reference to bring in btf_dedup fixes +a9afcc65fc8f btf_encoder: Don't special case packed enums +8f4f280163b7 btf_loader: Simplify fixup code by relying on BTF data more +be5173b4df0f dwarves: Fixup sizeof(long double) in bits in base_type_name_to_size table +5081ed507095 dwarves: Add _Float128 base_type +d52c9f9b9455 dwarf_loader: Fixup bitfield entry with same number of bits as its base_type +6586e423d4fa btf_loader: Fix bitfield fixup code +7daa4300d230 pahole: Complete list of base type names +6bcf0bd70305 btfdiff: Support specifying custom pahole location +88028b5d0c32 btfdiff: Use --show_private_classes with DWARF +e6c59bd11d3d libbpf: Build as PIC and statically link into libdwarves +cf4f3e282d64 cmake: Bump miminum required version to use OBJECT feature +5148be53dc65 btfdiff: Rename tmp files to contain the format used +dd3a7d3ab3e8 btf_encoder: run BTF deduplication before writing out to ELF +54106025cd14 libbtf: Fixup temp filename to .btf, not .btfe +e6dfd10bcbf3 libbpf: Build as shared lib +c234b6ca6e55 libbpf: Pull latest libbpf +fe4e1f799c55 btf_elf: Rename btf_elf__free() to btf_elf__delete() +6780c4334d55 btf: Rename 'struct btf' to 'struct btf_elf' +ca86e9416b8b pahole: use btf.h directly from libbpf +21507cd3e97b pahole: add libbpf as submodule under lib/bpf +c25ada500ddc pahole: Add build dir, config.h to .gitignore +a58c746c4c7e Fixup copyright notices for BTF files authored by Facebook engineers +e714d2eaa150 Adopt SPDX-License-Identifier +c86960dce55d btf_loader: We can set class_member->type_offset earlier +278b64c3eee0 btfdiff: Use diff's -p option to show the struct/union +1182664d6aa6 dwarves_fprintf: Handle negative bit_offsets in packed structs with bitfields +b0cf845e02c6 dwarves: Change type of bitfield_offset from uint8_t to int8_t +06e364bc62e7 btfdiff: Add utility to compare pahole output produced from DWARF and BTF +b79db4cab41c dwarves: add __int128 types in base_type_name_to_size +de3459cc0ebe btf_loader: BTF encodes the size of enums as bytes not bits +693347f8def7 btf_encoder: Fix void handling in FUNC_PROTO. +2d0b70664f3e dwarves_fprintf: Separate basic type stats into separate type__fprintf() method +18f5910f96e0 dwarves: Add type to tag helper +f2092f56586a btf: recognize BTF_KIND_FUNC in btf_loader +11766614096c btf: Fix kind_flag usage in btf_loader +68b93e6858ae dutil: Add missing string.h header include +851ef335e328 dutil: Drop 'noreturn' attribute for ____ilog2_NaN() +ab0cb33e54e8 btf_loader: Fixup class_member->bit_offset for !big_endian files +b24718fe27d3 dwarves: Fix documentation for class_memer->bitfield_size +3ffe5ba93b63 pahole: Do not apply 'struct class' filters to 'struct type' +da18bb340bee dwarves: Check if the tag is a 'struct class' in class__find_holes() +2a82d593be81 btf: Add kind_flag support for btf_loader +472256d3c57b btf_loader: Introduce a loader for the BTF format +93d6d0016523 dwarves: No need to print the "signed ", the name has it already +0a9bac9a3e8e dwarves: Relookup when searching for signed base types +a2cdc6c2a0a3 dutil: Adopt strstart() from the linux perf tools sources +3aa3fd506e6c btf: add func_proto support +8630ce404287 btf: fix struct/union/fwd types with kind_flag +65bd17abc72c btf: Allow multiple cu's in dwarf->btf conversion +d843945ba514 pahole: Search for unions as well with '-C' +da632a36862c dwarves: Introduce {cu,cus}__find_struct_or_union_by_name() methods +31664d60ad41 pahole: Show tagged enums as well when no class is specified +b18354f64cc2 btf: Generate correct struct bitfield member types +70ef8c7f07ff dwarves_fprintf: Set conf.cachelinep in union__fprintf() too +bfdea37668c6 dwarves_fprintf: Print the scope of variables +465110ec99d3 dwarves: Add the DWARF location to struct variable +c65f2cf4361e dwarves: Rename variable->location to ->scope +0d2511fd1d8e btf: Fix bitfield encoding +92417082aad3 MANIFEST: Add missing COPYING file +eb6bd05766f5 dwarf_loader: Process DW_AT_count in DW_TAG_subrange_type + +v1.12 + +Thu Aug 16 2018 + +1ca2e351dfa1 README.btf: Add section on validating the .BTF section via the kernel +9eda5e8163ce README.btf: No need to use 'llvm.opts = -mattr=dwarfris' with elfutils >= 0.173 +7818af53f64a dwarves: Add a README.btf file with steps to test the BTF encoder +f727c22191d0 dwarf_loader: Initial support for DW_TAG_partial_unit +e975ff247aa8 dwarves_fprintf: Print cacheline boundaries in multiple union members +68645f7facc2 btf: Add BTF support +81466af0d4f8 pahole: Show the file where a struct was used +2dd87be78bb2 dwarves_fprintf: Show offsets at union members +66cf3983e1ac README.DEBUG: Add an extra step to make the instructions cut'n'exec +2a092d61453c dwarves: Fix cus__load_files() success return value +02a456f5f54c pahole: Search and use running kernel vmlinux when no file is passed +5f057919a0c0 man-pages: Add entry for --hex + +v1.11 + +Wed Jun 2017 + +5a57eb074170 man-pages: Update URL to the dwarves paper +b52386d041fa dwarves_fprintf: Find holes when expanding types +103e89bb257d dwarves_fprintf: Find holes on structs embedded in other structs +ab97c07a7ebe dwarves_fprintf: Fixup cacheline boundary printing on expanded structs +046ad67af383 dwarves_fprintf: Shorten class__fprintf() sig +44130bf70e1c dwarves: Update e-mail address +327757975b94 dwarf_loader: Add URL for template tags description +f4d5e21fd1b2 dwarf_loader: Tidy up template tags usage +e12bf9999944 dwarf_loader: Do not hash unsupported tags +3afcfbec9e08 dwarf_loader: Add DW_TAG_GNU_formal_parameter_pack stub in process_function +55d9b20dbaf6 dwarf_loader: Ignore DW_TAG_dwarf_procedure when processing functions +45618c7ec122 dwarf_loader: Initial support for DW_TAG_unspecified_type +658a238b9890 dwarf_loader: Stop emitting warnings about DW_TAG_call_site +0fbb39291d59 dwarf_loader: Add support for DW_TAG_restrict_type +9df42c68265d dwarves: Initial support for rvalue_reference_type +8af5ccd86d21 dwarves: Use cus__fprintf_load_files_err() in the remaining tools +10515a7c4db7 dwarves: Introduce cus__fprintf_load_files_err() +0e6463635082 pahole: Show more informative message when errno is properly set on error +2566cc2c8715 pdwtags: Show proper error messages for files it can't handle +9f3f67e78679 dwarves: Fix cus__load_files() error return +ae3a2720c3d3 dutil: Add ____ilog2_NaN declaration to silence compiler warning +0b81b5ad4743 Update version in CMakeLists.txt +79536f4f9587 cmake: Use INTERFACE_LINK_LIBRARIES +1decb1bc4a41 dwarf_loader: Check cu__find_type_by_ref result +956343d05a41 Add instructions on how to build with debug info +e71353c3fa0a dwarf_loader: Ignore DW_TAG_dwarf_procedure +189695907242 dwarves_fprintf: Add the missing GNU_ suffix to DWARF_TAG_ created by the GNU project +d973b1d5daf0 dwarf_fprintf: Handle DW_TAG_GNU_call_site{_parameter} +c23eab4b1253 dwarf_loader: Print unknown tags as an hex number +943a0de0679a dwarves_fprintf: DW_TAG_mutable_type doesn't exist. +a8e562a15767 dwarf_loader: Use obstack_zalloc when allocating tag +fd3838ae9aa3 dwarves: Stop using 'self' +5ecf1aab9e10 dwarf_loader: Support DW_FORM_data{4,8} for reading class member offsets +c4ccdd5ae63b dwarves_reorganize: Fix member type fixup +e31fda3063e3 dwarves_reorganize: Fixup calculation of bytes needed for bitfield +1e461ec7e0e8 dwarves_fprintf: Fix printf types on 64bit linux +222f0067a9c3 dwarves_fprintf: Don't ignore virtual data members +e512e3f9b36b dwarves: Update git url +8c6378fd8834 dwarves: Support static class data members +a54515fa6ee4 dwarves: Stop using 'self' +6035b0d91f19 rpm: Add missing BuildRequires: zlib-devel +be7b691756ff dwarf_loader: Don't stop processing after finding unsupported tag + +v1.10 + +Wed May 30 2012 + +. Initial DWARF4 support, by Tom Tromey + +. Add stubs for some new GNU Tags + +. Fix build on older systems + +. Fix a crash when pahole is called with -R -S, from Tal Kelrich + +v1.9: + +Ignore DW_TAG_template_{type,value}_parameter, fixing a bug reported at: + +https://bugzilla.redhat.com/show_bug.cgi?id=654471 + +More work is needed to properly support these tags. + +----------------------------------------- + +After a long time without a new release because I was trying to get the CTF +support completed, and due to the very strong gravity force in the Linux kernel +perf tools, here it is 1.8, with lots of performance improvements, bug fixes +and changes to better use these tools in scripts. + +For full details please take a look at the git changesets, repo available at: + +http://git.kernel.org/cgit/devel/pahole/pahole.git + +- Arnaldo + +pahole: + +. Allow list of structs to be passed to pahole. + + E.g.: 'pahole -C str_node,strings' + + Suggested by Zack Weinberg <zweinberg@mozilla.com>, for scripting. + +. Introduce --hex to print offsets and sizes in hexadecimal + +codiff: + +. Improve detection of removal and addition of members in structs + +. Detect changes in padding and the number of holes/bit_holes + +pfunct: + +. --no_parm_names + + Because CTF doesn't encodes the names of the parameters and I want to + test the upcoming CTF function section code in ctftwdiff. + +. pfunct --addr + + Using an rbtree to find in which function the given addr is. + +libdwarves: + +. Greatly reduce the data structures footprint and lookup by recoding + the IDs as short integers, that was done to facilitate support for CTF + but benefited the core libraries greatly. + +. Handle GCC support for vector instructions + + So now it recognizes, as printed by pdwtags: + + 908 typedef int __m64 __attribute__ ((__vector_size__ (8))); size: 8 + 909 int array __attribute__ ((__vector_size__ (8))); size: 8 + 910 int array __attribute__ ((__vector_size__ (4))); size: 4 + 911 short int array __attribute__ ((__vector_size__ (2))); size: 2 + 912 char array __attribute__ ((__vector_size__ (1))); size: 1 + +. Destructors were added so that no leaks are left if this library is to + be used in other tools that don't end the program when done using this lib. + +. Allow the tools to pass a callback that is used after loading each object + file (CU/Compile Unit), so that we can more quickly find tags and stop + the processing sooner, or at least delete the CU if it doesn't have anything + needed by the tool. This _greatly_ speeded up most of the tools. + +. Tools now can pass a debug format "path", specifying the order it wants to + try, so that if a file have both DWARF and CTF, specifying 'ctf,dwarf' will + use the CTF info. + +. Now the formatting routines are in a separate file, dwarves_fprintf.c. This + was done for organizational purposes but also to pave the way for multiple + formatting backends, so that we can print, for instance, in CSV the structs, + for easier scripting like done by several folks out there. + +. Handle volatile typedef bitfields, like: + + struct _GClosure { + volatile guint ref_count:15; /* 0:17 4 */ + volatile guint meta_marshal:1; /* 0:16 4 */ + volatile guint n_guards:1; /* 0:15 4 */ + +. Load java 'interfaces' as a struct/class. + +. Fix buffer expansion bug, detected thanks to boost that provided things like: + + virtual int undefine(class + grammar_helper<boost::spirit::grammar<boost::detail::graph::dot_skipper, + boost::spirit::parser_context<boost::spirit::nil, class + grammar<boost::detail::graph::dot_skipper, + boost::spirit::parser_context<boost::spirit::nil_t> > *); /* + linkage=_ZN5boost6spirit4impl14grammar_helperINS0_7grammarINS_6detail5graph11dot_skipperENS0_14parser_contextINS0_5nil_tEEEEES6_NS0_7scannerINS0_10multi_passISt16istream_i + */ + +. Allow optional addr information loading, speeding up some apps that don't + use such addresses (or in modes where addrs aren't used) such as pahole. + +. Use a obstacks, speeding up apps as measured with the perf tools. + +. Support zero sized arrays in the middle of a struct. + +. Fix padding calculation in the reorganize routines. + +. Fix bitfield demotion in the reorganize routines. + +. Support "using" pointing to data members (C++). + +. Properly support pointers to const, reported by Jan Engelhardt <jengelh@medozas.de>: + +. Support more compact DW_AT_data_member_location form, pointed out by Mark + Wielaard <mjw@redhat.com> and reported by Mike Snitzer <snitzer@redhat.com> + +Experimental CTF support: + +libdwarves was reorganized so that it can support multiple debugging formats, +with the first one being supported being the Compact C Type Format that comes +from the OpenSolaris world. + +David S. Miller contributed an initial CTF decoder and from there I wrote an +encoder. + +To test this a regression testing harness (regtest in the sources) that will +take files with DWARF info and from there encode its contents in CTF in another +ELF section in the same file (.SUN_ctf). Then it will decode both the DWARF +and CTF sections and compare the results for pahole running with some new +flags that cope with some subtleties in the way CTF encodes things. + + --flat_arrays + + We have just one dimension in CTF, with the total number of entries, + in DWARF we can express this, but not in CTF: + + __u8 addr[0][6]; /* 4 0 */ + + So --flat_arrays will show it as: + + __u8 addr[0]; /* 4 0 */ + + --show_private_classes + --fixup_silly_bitfields + + To cope with things like 'char foo:8' that since CTF has only the + number of bits, can't be expressed as we don't know if it is a + bitfield or just a char without the ':8' suffix. + + --first_obj_only + + Look only at the first object file in a file with multiple object + files, like vmlinux. This is because the CTF support is not complete yet, + needing the merging of types in multiple object files to be done. + + --classes_as_structs + + CTF has only a type for structs, not for classes like DWARF (DW_TAG_class_type + is not present in CTF), so print them all as 'struct'. + +Running with the above limitations produce just a few mismatches, related to +packed structs and enumerations and bitfields. @@ -0,0 +1,27 @@ +Build instructions: + +1. install cmake +2. mkdir build +3. cd build +4. cmake -D__LIB=lib .. +5. make install + +Default is to be installed on /usr/local, see rpm spec file for +installing on other places. + +Known to work scenarios: + +Mandriva Cooker: + +cmake 2.4.5-1mdv2007.1 +libelfutils1-devel 0.123-1mdv2007.1 + +Debian Unstable: + +cmake 2.4.5-1 +libdw-dev 0.123-2 + +Fedora Core 6: + +cmake 2.4.5-2.fc6 +elfutils-devel 0.126-1.fc6 diff --git a/README.DEBUG b/README.DEBUG new file mode 100644 index 0000000..4432d45 --- /dev/null +++ b/README.DEBUG @@ -0,0 +1,6 @@ +rm -rf build +mkdir build +cd build +cmake -DCMAKE_BUILD_TYPE=Debug .. +cd .. +make -C build diff --git a/README.btf b/README.btf new file mode 100644 index 0000000..c81734e --- /dev/null +++ b/README.btf @@ -0,0 +1,598 @@ +We'll test the BTF encoder using perf's eBPF integration, but really we can plain +use clang directly, setting up all its options. + +Using perf's integration will save some time here, to see all it does, use +'perf trace -vv' plus the options used below, then all the steps will be shown. + +Build perf from the latest kernel sources, use it with clang/llvm like: + + [root@seventh ~]# clang --version + clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566eefef9c3777bd780ec4cbb9efa764633b76c) + Target: x86_64-unknown-linux-gnu + Thread model: posix + InstalledDir: /usr/local/bin + [root@seventh ~]# llc --version | head -17 + LLVM (http://llvm.org/): + LLVM version 8.0.0svn + DEBUG build with assertions. + Default target: x86_64-unknown-linux-gnu + Host CPU: skylake + + Registered Targets: + aarch64 - AArch64 (little endian) + aarch64_be - AArch64 (big endian) + amdgcn - AMD GCN GPUs + arm - ARM + arm64 - ARM64 (little endian) + armeb - ARM (big endian) + bpf - BPF (host endian) + bpfeb - BPF (big endian) + bpfel - BPF (little endian) + hexagon - Hexagon + [root@seventh ~]# + +Then enable saving the object file build as part of perf's handling of foo.c type +events, i.e. eBPF programs that will be compiled with clang and then loaded with +sys_bpf() to possibly insert events in perf's ring buffer via bpf_perf_event_output(), +or interact with the system via bpf_trace_printk() or just work as filters, etc: + + # cat ~/.perfconfig + [llvm] + dump-obj = true + +Then run a simple example, found in the kernel sources: + + # perf trace -e tools/perf/examples/bpf/hello.c cat /etc/passwd > /dev/null + LLVM: dumping tools/perf/examples/bpf/hello.o + 0.000 __bpf_stdout__:Hello, world + 0.028 __bpf_stdout__:Hello, world + 0.291 __bpf_stdout__:Hello, world + # + +Notice that "LLVM: dumping..." line, look at the ELF sections in that file: + + [root@seventh perf]# readelf -SW tools/perf/examples/bpf/hello.o + There are 11 section headers, starting at offset 0x220: + + Section Headers: + [Nr] Name Type Address Off Size ES Flg Lk Inf Al + [ 0] NULL 0000000000000000 000000 000000 00 0 0 0 + [ 1] .strtab STRTAB 0000000000000000 00018c 00008d 00 0 0 1 + [ 2] .text PROGBITS 0000000000000000 000040 000000 00 AX 0 0 4 + [ 3] syscalls:sys_enter_openat PROGBITS 0000000000000000 000040 000088 00 AX 0 0 8 + [ 4] .relsyscalls:sys_enter_openat REL 0000000000000000 000178 000010 10 10 3 8 + [ 5] maps PROGBITS 0000000000000000 0000c8 00001c 00 WA 0 0 4 + [ 6] .rodata.str1.1 PROGBITS 0000000000000000 0000e4 00000e 01 AMS 0 0 1 + [ 7] license PROGBITS 0000000000000000 0000f2 000004 00 WA 0 0 1 + [ 8] version PROGBITS 0000000000000000 0000f8 000004 00 WA 0 0 4 + [ 9] .llvm_addrsig LOOS+0xfff4c03 0000000000000000 000188 000004 00 E 10 0 1 + [10] .symtab SYMTAB 0000000000000000 000100 000078 18 1 1 8 + Key to Flags: + W (write), A (alloc), X (execute), M (merge), S (strings), I (info), + L (link order), O (extra OS processing required), G (group), T (TLS), + C (compressed), x (unknown), o (OS specific), E (exclude), + p (processor specific) + [root@seventh perf]# + +No DWARF debugging info, so we need to further customize ~/.perfconfig LLVM section: + + [root@seventh perf]# cat ~/.perfconfig + [llvm] + dump-obj = true + clang-opt = -g + [root@seventh perf]# perf trace -e tools/perf/examples/bpf/hello.c cat /etc/passwd > /dev/null + LLVM: dumping tools/perf/examples/bpf/hello.o + 0.000 __bpf_stdout__:Hello, world + 0.015 __bpf_stdout__:Hello, world + 0.184 __bpf_stdout__:Hello, world + [root@seventh perf]# + [root@seventh perf]# readelf -SW tools/perf/examples/bpf/hello.o + There are 26 section headers, starting at offset 0xe20: + + Section Headers: + [Nr] Name Type Address Off Size ES Flg Lk Inf Al + [ 0] NULL 0000000000000000 000000 000000 00 0 0 0 + [ 1] .strtab STRTAB 0000000000000000 000cf4 000127 00 0 0 1 + [ 2] .text PROGBITS 0000000000000000 000040 000000 00 AX 0 0 4 + [ 3] syscalls:sys_enter_openat PROGBITS 0000000000000000 000040 000088 00 AX 0 0 8 + [ 4] .relsyscalls:sys_enter_openat REL 0000000000000000 000a80 000010 10 25 3 8 + [ 5] maps PROGBITS 0000000000000000 0000c8 00001c 00 WA 0 0 4 + [ 6] .rodata.str1.1 PROGBITS 0000000000000000 0000e4 00000e 01 AMS 0 0 1 + [ 7] license PROGBITS 0000000000000000 0000f2 000004 00 WA 0 0 1 + [ 8] version PROGBITS 0000000000000000 0000f8 000004 00 WA 0 0 4 + [ 9] .debug_str PROGBITS 0000000000000000 0000fc 0001d2 01 MS 0 0 1 + [10] .debug_loc PROGBITS 0000000000000000 0002ce 000023 00 0 0 1 + [11] .debug_abbrev PROGBITS 0000000000000000 0002f1 0000e3 00 0 0 1 + [12] .debug_info PROGBITS 0000000000000000 0003d4 000182 00 0 0 1 + [13] .rel.debug_info REL 0000000000000000 000a90 000210 10 25 12 8 + [14] .debug_ranges PROGBITS 0000000000000000 000556 000030 00 0 0 1 + [15] .debug_macinfo PROGBITS 0000000000000000 000586 000001 00 0 0 1 + [16] .debug_pubnames PROGBITS 0000000000000000 000587 00006e 00 0 0 1 + [17] .rel.debug_pubnames REL 0000000000000000 000ca0 000010 10 25 16 8 + [18] .debug_pubtypes PROGBITS 0000000000000000 0005f5 000056 00 0 0 1 + [19] .rel.debug_pubtypes REL 0000000000000000 000cb0 000010 10 25 18 8 + [20] .debug_frame PROGBITS 0000000000000000 000650 000028 00 0 0 8 + [21] .rel.debug_frame REL 0000000000000000 000cc0 000020 10 25 20 8 + [22] .debug_line PROGBITS 0000000000000000 000678 0000a7 00 0 0 1 + [23] .rel.debug_line REL 0000000000000000 000ce0 000010 10 25 22 8 + [24] .llvm_addrsig LOOS+0xfff4c03 0000000000000000 000cf0 000004 00 E 25 0 1 + [25] .symtab SYMTAB 0000000000000000 000720 000360 18 1 32 8 + Key to Flags: + W (write), A (alloc), X (execute), M (merge), S (strings), I (info), + L (link order), O (extra OS processing required), G (group), T (TLS), + C (compressed), x (unknown), o (OS specific), E (exclude), + p (processor specific) + [root@seventh perf]# + +Now lets use 'pahole --btf_encode' (or 'pahole -J') to add an ELF section to that object +file with the conversion from the DWARF sections to a new one, for BTF: + + [root@seventh perf]# pahole --btf_encode tools/perf/examples/bpf/hello.o + + [root@seventh perf]# readelf -SW tools/perf/examples/bpf/hello.o + There are 27 section headers, starting at offset 0x1080: + + Section Headers: + [Nr] Name Type Address Off Size ES Flg Lk Inf Al + [ 0] NULL 0000000000000000 000000 000000 00 0 0 0 + [ 1] .text PROGBITS 0000000000000000 000040 000000 00 AX 0 0 4 + [ 2] syscalls:sys_enter_openat PROGBITS 0000000000000000 000040 000088 00 AX 0 0 8 + [ 3] maps PROGBITS 0000000000000000 0000c8 00001c 00 WA 0 0 4 + [ 4] .rodata.str1.1 PROGBITS 0000000000000000 0000e4 00000e 01 AMS 0 0 1 + [ 5] license PROGBITS 0000000000000000 0000f2 000004 00 WA 0 0 1 + [ 6] version PROGBITS 0000000000000000 0000f8 000004 00 WA 0 0 4 + [ 7] .debug_str PROGBITS 0000000000000000 0000fc 0001d2 01 MS 0 0 1 + [ 8] .debug_loc PROGBITS 0000000000000000 0002ce 000023 00 0 0 1 + [ 9] .debug_abbrev PROGBITS 0000000000000000 0002f1 0000e3 00 0 0 1 + [10] .debug_info PROGBITS 0000000000000000 0003d4 000182 00 0 0 1 + [11] .debug_ranges PROGBITS 0000000000000000 000556 000030 00 0 0 1 + [12] .debug_macinfo PROGBITS 0000000000000000 000586 000001 00 0 0 1 + [13] .debug_pubnames PROGBITS 0000000000000000 000587 00006e 00 0 0 1 + [14] .debug_pubtypes PROGBITS 0000000000000000 0005f5 000056 00 0 0 1 + [15] .debug_frame PROGBITS 0000000000000000 000650 000028 00 0 0 8 + [16] .debug_line PROGBITS 0000000000000000 000678 0000a7 00 0 0 1 + [17] .symtab SYMTAB 0000000000000000 000720 000360 18 25 32 8 + [18] .relsyscalls:sys_enter_openat REL 0000000000000000 000a80 000010 10 17 2 8 + [19] .rel.debug_info REL 0000000000000000 000a90 000210 10 17 10 8 + [20] .rel.debug_pubnames REL 0000000000000000 000ca0 000010 10 17 13 8 + [21] .rel.debug_pubtypes REL 0000000000000000 000cb0 000010 10 17 14 8 + [22] .rel.debug_frame REL 0000000000000000 000cc0 000020 10 17 15 8 + [23] .rel.debug_line REL 0000000000000000 000ce0 000010 10 17 16 8 + [24] .llvm_addrsig LOOS+0xfff4c03 0000000000000000 000cf0 000004 00 E 0 0 1 + [25] .strtab STRTAB 0000000000000000 000cf4 00019c 00 0 0 1 + [26] .BTF PROGBITS 0000000000000000 000e90 0001ea 00 0 0 1 + Key to Flags: + W (write), A (alloc), X (execute), M (merge), S (strings), I (info), + L (link order), O (extra OS processing required), G (group), T (TLS), + C (compressed), x (unknown), o (OS specific), E (exclude), + p (processor specific) + readelf: tools/perf/examples/bpf/hello.o: Warning: possibly corrupt ELF header - it has a non-zero program header offset, but no program headers + [root@seventh perf]# + +That new ".BTF" section should then be parseable by the kernel, that has a BTF +decoder, something not available for pahole at this time, but that will come in +a later version. + +When pahole tries to read the DWARF info in that BPF ELF file, hello.o, we can se +a problem that will require us to add another option to the .perfconfig llvm section: + + # pahole tools/perf/examples/bpf/hello.o + struct clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566eefef9c3777bd780ec4cbb9efa764633b76c) { + clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566ec377 clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566eefef9c3777bd780ec4cbb9efa764633b76c); /* 0 4 */ + clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566ec377 clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566eefef9c3777bd780ec4cbb9efa764633b76c); /* 4 4 */ + clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566ec377 clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566eefef9c3777bd780ec4cbb9efa764633b76c); /* 8 4 */ + clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566ec377 clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566eefef9c3777bd780ec4cbb9efa764633b76c); /* 12 4 */ + clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566ec377 clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566eefef9c3777bd780ec4cbb9efa764633b76c); /* 16 4 */ + clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566ec377 clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566eefef9c3777bd780ec4cbb9efa764633b76c); /* 20 4 */ + clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566ec377 clang version 8.0.0 (http://llvm.org/git/clang.git 8587270a739ee30c926a76d5657e65e85b560f6e) (http://llvm.org/git/llvm.git 0566eefef9c3777bd780ec4cbb9efa764633b76c); /* 24 4 */ + + /* size: 28, cachelines: 1, members: 7 */ + /* last cacheline: 28 bytes */ + }; +# + +We need to pass some options to llvm, via the llvm.opts variable in ~/.perfconfig: + + [root@seventh perf]# cat ~/.perfconfig + [llvm] + dump-obj = true + clang-opt = -g + opts = -mattr=dwarfris + [root@seventh perf]# perf trace -e tools/perf/examples/bpf/hello.c cat /etc/passwd > /dev/null + LLVM: dumping tools/perf/examples/bpf/hello.o + 0.000 __bpf_stdout__:Hello, world + 0.018 __bpf_stdout__:Hello, world + 0.209 __bpf_stdout__:Hello, world + [root@seventh perf]# pahole tools/perf/examples/bpf/hello.o + struct bpf_map { + unsigned int type; /* 0 4 */ + unsigned int key_size; /* 4 4 */ + unsigned int value_size; /* 8 4 */ + unsigned int max_entries; /* 12 4 */ + unsigned int map_flags; /* 16 4 */ + unsigned int inner_map_idx; /* 20 4 */ + unsigned int numa_node; /* 24 4 */ + + /* size: 28, cachelines: 1, members: 7 */ + /* last cacheline: 28 bytes */ + }; + [root@seventh perf]# + +This is not needed when using elfutils >= 0.173, pahole will just work as above. + +Now we need to go test the kernel, and to load that file with a BTF section we +can also use perf, passing the .o file instead of the .c one, skipping the +compilation phase and using the modified .o file, we will also run in system +wide mode, so taht we can keep that BPF object loaded and attached to the +tracepoint, so that we can use the kernel facilities to inspect the BTF file as +read and processed by the kernel: + + # perf trace -e tools/perf/examples/bpf/hello.c 2> /dev/null + +Now to look if the kernel has the bpf filesystem: + + [acme@jouet perf]$ grep bpf /proc/filesystems + nodev bpf + [acme@jouet perf]$ + [root@jouet ~]# mount -t bpf nodev /sys/fs/bpf + [root@jouet ~]# mount | grep bpf + nodev on /sys/fs/bpf type bpf (rw,relatime) + [root@jouet ~]# cd /sys/fs/bpf + [root@jouet bpf]# ls -la + total 0 + drwxrwxrwt. 2 root root 0 Aug 15 17:42 . + drwxr-xr-x. 10 root root 0 Aug 13 15:04 .. + [root@jouet bpf]# + +Work is planned to allow using BTF info to pretty print from the bpf fs, see: + + https://www.spinics.net/lists/netdev/msg518606.html + Date: Sat, 11 Aug 2018 + +<quote> +For bpftool, BTF pretty print support is missing +for per-cpu maps. bpffs print for per-cpu hash/array maps +need to be added as well. Will add them later. + +Acked-by: Yonghong Song <yhs@xxxxxx> +</quote> + +To see what libbpf and its users, like perf, does when a ".BTF" ELF section is +found in a BPF object being loaded via sys_bpf(), we can use 'perf ftrace' to +show the sequence of events inside the kernel to load, validade and initialize +data structures related to the request: + + # perf ftrace -G *btf* perf trace -e tools/perf/examples/bpf/hello.o cat /etc/passwd + 3) | bpf_btf_load() { + 3) | capable() { + 3) | ns_capable_common() { + 3) | security_capable() { + 3) 0.048 us | cap_capable(); + 3) | selinux_capable() { + 3) 0.092 us | cred_has_capability(); + 3) 0.444 us | } + 3) 1.387 us | } + 3) 1.764 us | } + 3) 2.168 us | } + 3) | btf_new_fd() { + 3) | kmem_cache_alloc_trace() { + 3) | _cond_resched() { + 3) 0.041 us | rcu_all_qs(); + 3) 0.407 us | } + 3) 0.040 us | should_failslab(); + 3) 0.161 us | prefetch_freepointer(); + 3) 0.097 us | memcg_kmem_put_cache(); + 3) 2.719 us | } + 3) | kmem_cache_alloc_trace() { + 3) | _cond_resched() { + 3) 0.040 us | rcu_all_qs(); + 3) 0.409 us | } + 3) 0.040 us | should_failslab(); + 3) 0.110 us | prefetch_freepointer(); + 3) 0.099 us | memcg_kmem_put_cache(); + 3) 2.296 us | } + 3) 0.054 us | bpf_check_uarg_tail_zero(); + 3) | __check_object_size() { + 3) 0.152 us | __virt_addr_valid(); + 3) 0.047 us | __check_heap_object(); + 3) 0.040 us | check_stack_object(); + 3) 1.465 us | } + 3) 0.041 us | btf_sec_info_cmp(); + 3) | kvmalloc_node() { + 3) | __kmalloc_node() { + 3) 0.051 us | kmalloc_slab(); + 3) | _cond_resched() { + 3) 0.042 us | rcu_all_qs(); + 3) 0.401 us | } + 3) 0.038 us | should_failslab(); + 3) 0.040 us | memcg_kmem_put_cache(); + 3) 2.168 us | } + 3) 2.591 us | } + 3) | __check_object_size() { + 3) 0.108 us | __virt_addr_valid(); + 3) 0.050 us | __check_heap_object(); + 3) 0.039 us | check_stack_object(); + 3) 1.469 us | } + 3) | btf_struct_check_meta() { + 3) 0.057 us | __btf_verifier_log_type(); + 3) 0.057 us | btf_verifier_log_member(); + 3) 0.043 us | btf_verifier_log_member(); + 3) 0.042 us | btf_verifier_log_member(); + 3) 0.043 us | btf_verifier_log_member(); + 3) 0.043 us | btf_verifier_log_member(); + 3) | btf_verifier_log_member() { + 3) ==========> | + 3) | smp_irq_work_interrupt() { + 3) | irq_enter() { + 3) | rcu_irq_enter() { + 3) 0.038 us | rcu_nmi_enter(); + 3) 0.412 us | } + 3) 0.054 us | irqtime_account_irq(); + 3) 1.409 us | } + 3) | __wake_up() { + 3) | __wake_up_common_lock() { + 3) 0.040 us | _raw_spin_lock_irqsave(); + 3) 0.051 us | __wake_up_common(); + 3) 0.044 us | _raw_spin_unlock_irqrestore(); + 3) 1.155 us | } + 3) 1.508 us | } + 3) | irq_exit() { + 3) 0.062 us | irqtime_account_irq(); + 3) 0.038 us | idle_cpu(); + 3) | rcu_irq_exit() { + 3) 0.038 us | rcu_nmi_exit(); + 3) 0.419 us | } + 3) 1.601 us | } + 3) 6.230 us | } + 3) <========== | + 3) 0.088 us | } /* btf_verifier_log_member */ + 3) 0.041 us | btf_verifier_log_member(); + 3) + 10.759 us | } + 3) | kvmalloc_node() { + 3) | __kmalloc_node() { + 3) 0.043 us | kmalloc_slab(); + 3) | _cond_resched() { + 3) 0.037 us | rcu_all_qs(); + 3) 0.455 us | } + 3) 0.040 us | should_failslab(); + 3) 0.037 us | memcg_kmem_put_cache(); + 3) 2.227 us | } + 3) 2.624 us | } /* kvmalloc_node */ + 3) | kvfree() { + 3) 0.048 us | kfree(); + 3) 0.662 us | } + 3) | btf_int_check_meta() { + 3) 0.043 us | __btf_verifier_log_type(); + 3) 0.457 us | } + 3) | btf_array_check_meta() { + 3) 0.041 us | __btf_verifier_log_type(); + 3) 0.393 us | } + 3) | btf_int_check_meta() { + 3) 0.094 us | __btf_verifier_log_type(); + 3) 0.447 us | } + 3) | btf_int_check_meta() { + 3) 0.043 us | __btf_verifier_log_type(); + 3) 0.573 us | } + 3) | btf_int_check_meta() { + 3) 0.085 us | __btf_verifier_log_type(); + 3) 0.446 us | } + 3) | btf_ref_type_check_meta() { + 3) 0.042 us | __btf_verifier_log_type(); + 3) 0.451 us | } + 3) | btf_ref_type_check_meta() { + 3) 0.042 us | __btf_verifier_log_type(); + 3) 0.427 us | } + 3) | btf_ref_type_check_meta() { + 3) 0.042 us | __btf_verifier_log_type(); + 3) 0.397 us | } + 3) | btf_ref_type_check_meta() { + 3) 0.041 us | __btf_verifier_log_type(); + 3) 0.399 us | } + 3) | btf_int_check_meta() { + 3) 0.043 us | __btf_verifier_log_type(); + 3) 0.602 us | } + 3) | btf_ref_type_check_meta() { + 3) 0.040 us | __btf_verifier_log_type(); + 3) 0.733 us | } + 3) | btf_array_check_meta() { + 3) 0.094 us | __btf_verifier_log_type(); + 3) 0.452 us | } + 3) | kvmalloc_node() { + 3) | __kmalloc_node() { + 3) 0.039 us | kmalloc_slab(); + 3) | _cond_resched() { + 3) 0.041 us | rcu_all_qs(); + 3) 0.579 us | } + 3) 0.039 us | should_failslab(); + 3) 0.042 us | memcg_kmem_put_cache(); + 3) 2.538 us | } + 3) 2.886 us | } + 3) | kvmalloc_node() { + 3) | __kmalloc_node() { + 3) 0.041 us | kmalloc_slab(); + 3) | _cond_resched() { + 3) 0.038 us | rcu_all_qs(); + 3) 0.708 us | } + 3) 0.038 us | should_failslab(); + 3) 0.040 us | memcg_kmem_put_cache(); + 3) 2.483 us | } + 3) 2.829 us | } + 3) | kvmalloc_node() { + 3) | __kmalloc_node() { + 3) 0.057 us | kmalloc_slab(); + 3) | _cond_resched() { + 3) 0.040 us | rcu_all_qs(); + 3) 0.533 us | } + 3) 0.039 us | should_failslab(); + 3) 0.038 us | memcg_kmem_put_cache(); + 3) 2.680 us | } + 3) 3.171 us | } + 3) 0.054 us | env_stack_push(); + 3) | btf_struct_resolve() { + 3) 0.051 us | env_type_is_resolve_sink.isra.19(); + 3) 0.039 us | btf_int_check_member(); + 3) 0.039 us | env_type_is_resolve_sink.isra.19(); + 3) 0.039 us | btf_int_check_member(); + 3) 0.040 us | env_type_is_resolve_sink.isra.19(); + 3) 0.040 us | btf_int_check_member(); + 3) 0.039 us | env_type_is_resolve_sink.isra.19(); + 3) 0.099 us | btf_int_check_member(); + 3) 0.040 us | env_type_is_resolve_sink.isra.19(); + 3) 0.042 us | btf_int_check_member(); + 3) 0.040 us | env_type_is_resolve_sink.isra.19(); + 3) 0.038 us | btf_int_check_member(); + 3) 0.038 us | env_type_is_resolve_sink.isra.19(); + 3) 0.039 us | btf_int_check_member(); + 3) 6.545 us | } + 3) 0.053 us | env_stack_push(); + 3) | btf_array_resolve() { + 3) 0.039 us | env_type_is_resolve_sink.isra.19(); + 3) 0.090 us | btf_type_id_size(); + 3) 0.060 us | btf_type_int_is_regular(); + 3) 0.058 us | env_type_is_resolve_sink.isra.19(); + 3) 0.051 us | btf_type_id_size(); + 3) 0.055 us | btf_type_int_is_regular(); + 3) 3.414 us | } + 3) 0.041 us | btf_type_id_size(); + 3) 0.057 us | env_stack_push(); + 3) | btf_ptr_resolve() { + 3) 0.056 us | env_type_is_resolve_sink.isra.19(); + 3) 0.054 us | env_stack_push(); + 3) 1.056 us | } + 3) 0.063 us | btf_ptr_resolve(); + 3) | btf_ptr_resolve() { + 3) 0.049 us | env_type_is_resolve_sink.isra.19(); + 3) 0.086 us | btf_type_id_size(); + 3) 1.052 us | } + 3) 0.045 us | env_stack_push(); + 3) 0.060 us | btf_ptr_resolve(); + 3) 0.045 us | env_stack_push(); + 3) | btf_ptr_resolve() { + 3) 0.039 us | env_type_is_resolve_sink.isra.19(); + 3) 0.062 us | btf_type_id_size(); + 3) 1.325 us | } + 3) 0.054 us | env_stack_push(); + 3) | btf_modifier_resolve() { + 3) 0.061 us | env_type_is_resolve_sink.isra.19(); + 3) 0.043 us | btf_type_id_size(); + 3) 0.877 us | } + 3) 0.052 us | env_stack_push(); + 3) | btf_array_resolve() { + 3) 0.060 us | env_type_is_resolve_sink.isra.19(); + 3) 0.051 us | btf_type_id_size(); + 3) 0.042 us | btf_type_int_is_regular(); + 3) 0.040 us | env_type_is_resolve_sink.isra.19(); + 3) 0.042 us | btf_type_id_size(); + 3) 0.041 us | btf_type_int_is_regular(); + 3) 2.822 us | } + 3) 0.048 us | btf_type_id_size(); + 3) | kvfree() { + 3) 0.148 us | kfree(); + 3) 0.685 us | } + 3) 0.287 us | kfree(); + 3) 0.042 us | _raw_spin_lock_bh(); + 3) | kmem_cache_alloc() { + 3) 0.040 us | should_failslab(); + 3) 0.111 us | prefetch_freepointer(); + 3) 0.094 us | memcg_kmem_put_cache(); + 3) 2.139 us | } + 3) | _raw_spin_unlock_bh() { + 3) 0.079 us | __local_bh_enable_ip(); + 3) 0.460 us | } + 3) | anon_inode_getfd() { + 3) | get_unused_fd_flags() { + 3) | __alloc_fd() { + 3) 0.040 us | _raw_spin_lock(); + 3) 0.041 us | expand_files(); + 3) 1.374 us | } + 3) 1.759 us | } + 3) | anon_inode_getfile() { + 3) | d_alloc_pseudo() { + 3) | __d_alloc() { + 3) | kmem_cache_alloc() { + 3) | _cond_resched() { + 3) 0.035 us | rcu_all_qs(); + 3) 0.507 us | } + 3) 0.040 us | should_failslab(); + 3) | memcg_kmem_get_cache() { + 3) 0.091 us | get_mem_cgroup_from_mm(); + 3) 0.633 us | } + 3) 0.111 us | prefetch_freepointer(); + 3) 0.082 us | memcg_kmem_put_cache(); + 3) 4.178 us | } + 3) 0.162 us | d_set_d_op(); + 3) 5.545 us | } + 3) 6.270 us | } + 3) 0.112 us | mntget(); + 3) 0.125 us | ihold(); + 3) | d_instantiate() { + 3) 0.120 us | security_d_instantiate(); + 3) 0.106 us | _raw_spin_lock(); + 3) | __d_instantiate() { + 3) 0.069 us | d_flags_for_inode(); + 3) 0.090 us | _raw_spin_lock(); + 3) 1.483 us | } + 3) 2.767 us | } + 3) | alloc_file() { + 3) | get_empty_filp() { + 3) | kmem_cache_alloc() { + 3) | _cond_resched() { + 3) 0.039 us | rcu_all_qs(); + 3)root:x:0:0:root:/root:/bin/bash + bin:x:1:1:bin:/bin:/sbin/nologin + daemon:x:2:2:daemon:/sbin:/sbin/nologin + adm:x:3:4:adm:/var/adm:/sbin/nologin + <SNIP rest of /proc/passwd contents> + 0.382 us | } + 3) 0.040 us | should_failslab(); + 3) | memcg_kmem_get_cache() { + 3) 0.039 us | get_mem_cgroup_from_mm(); + 3) 0.626 us | } + 3) 0.050 us | prefetch_freepointer(); + 3) 0.059 us | memcg_kmem_put_cache(); + 3) 3.280 us | } + 3) | security_file_alloc() { + 3) | selinux_file_alloc_security() { + 3) | kmem_cache_alloc() { + 3) | _cond_resched() { + 3) 0.038 us | rcu_all_qs(); + 3) 0.422 us | } + 3) 0.040 us | should_failslab(); + 3) 0.051 us | prefetch_freepointer(); + 3) 0.054 us | memcg_kmem_put_cache(); + 3) 2.660 us | } + 3) 3.062 us | } + 3) 3.548 us | } + 3) 0.039 us | __mutex_init(); + 3) 8.091 us | } + 3) 8.617 us | } + 3) + 20.810 us | } + 3) | fd_install() { + 3) 0.054 us | __fd_install(); + 3) 0.723 us | } + 3) + 24.438 us | } + 3) ! 109.639 us | } + 3) ! 112.925 us | } + 3) | btf_release() { + 3) | btf_put() { + 3) 0.145 us | _raw_spin_lock_irqsave(); + 3) | call_rcu_sched() { + 3) | __call_rcu() { + 3) 0.082 us | rcu_segcblist_enqueue(); + 3) 1.323 us | } + 3) 1.782 us | } + 3) 0.069 us | _raw_spin_unlock_irqrestore(); + 3) | call_rcu_sched() { + 3) | __call_rcu() { + 3) 0.069 us | rcu_segcblist_enqueue(); + 3) 0.541 us | } + 3) 0.984 us | } + 3) 5.210 us | } + 3) 5.954 us | } + +This should be enough for us to validate pahole's BTF encoder, and now one can +use 'pahole -F btf' to obtain mostly the same results as with the default use +of '-F dwarf', modulo things like explicit alignments that are not present in +BTF and need some work to be inferred from existing non-natural alignment holes. + +- Arnaldo diff --git a/README.cross b/README.cross new file mode 100644 index 0000000..1faa0e9 --- /dev/null +++ b/README.cross @@ -0,0 +1,7 @@ +CC=s390x-linux-gnu-gcc \ + cmake -DDWARF_INCLUDE_DIR=/usr/s390x-linux-gnu/include/ \ + -DLIBDW_INCLUDE_DIR=/usr/s390x-linux-gnu/include/ \ + -DDWARF_LIBRARY=/usr/s390x-linux-gnu/lib/libdw.so.1 \ + -DELF_LIBRARY=/usr/s390x-linux-gnu/lib/libelf.so.1 \ + -DZLIB_LIBRARY=/usr/s390x-linux-gnu/lib/libz.so.1 \ + -DZLIB_INCLUDE_DIR=/usr/s390x-linux-gnu/include/ .. diff --git a/README.ctracer b/README.ctracer new file mode 100644 index 0000000..33c4014 --- /dev/null +++ b/README.ctracer @@ -0,0 +1,57 @@ +Basic instructions to use ctracer: + +1. Install dwarves, if you are not that excited about building it I'm + keeping rpms for Fedora Core 6 here: + + http://oops.ghostprotocols.net:81/acme/dwarves/rpm/ + + The .src.rpm is there in case you want to rebuild it for another + rpm based distro. + + Since fedora 9 you just have to run: + + yum install dwarves + +2. build the kernel with CONFIG_DEBUG_INFO=y, i.e. gcc -g, that will + insert the DWARF info needed by all the pahole tools, ctracer, etc, or + just install the kernel-debuginfo rpm package on FC6, other distros + have it with a different name, its just the kernel built with debug + info. + +3. Assuming you installed the kernel-debuginfo package, to run ctracer + on your workstation, just do the following steps: + +mkdir foo +cd foo +ln -s /usr/share/dwarves/runtime/* . +make CLASS=sock # to trace struct sock methods, this one is safe, try others + # and tell me your horror (or success :-) ) story. + +(kbuild gurus, send suggestions to simplify this procedure! :-) ) + +4. load the resulting module: + +insmod ctracer.ko + +dmesg will show how many probes were successfully installed + +5. Do some related activity (ssh, in the above example should do) + +6. Make sure debugfs is mounted + +[root@filo ~]# mount -t debugfs none_debugfs /sys/kernel/debug/ + +7. Get the log: + +cat /sys/kernel/debug/ctracer0 > /tmp/ctracer.log + +8. Generate the callgraph! + +make callgraph + +9. rmmod ctracer + +Change the shipped Makefile accordingly to build a module for qemu or another test +machine. + +The relay transport is mostly ready and will be included in the upcoming changesets. diff --git a/README.tarball b/README.tarball new file mode 100644 index 0000000..a3db289 --- /dev/null +++ b/README.tarball @@ -0,0 +1 @@ +tar cvfJ ~/rpmbuild/SOURCES/dwarves-1.17.tar.xz --transform 's,^,dwarves-1.17/,' `cat MANIFEST` diff --git a/btf_encoder.c b/btf_encoder.c new file mode 100644 index 0000000..c40f059 --- /dev/null +++ b/btf_encoder.c @@ -0,0 +1,721 @@ +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2019 Facebook + + Derived from ctf_encoder.c, which is: + + Copyright (C) Arnaldo Carvalho de Melo <acme@redhat.com> + Copyright (C) Red Hat Inc + */ + +#include "dwarves.h" +#include "libbtf.h" +#include "lib/bpf/include/uapi/linux/btf.h" +#include "lib/bpf/src/libbpf.h" +#include "hash.h" +#include "elf_symtab.h" +#include "btf_encoder.h" + +#include <ctype.h> /* for isalpha() and isalnum() */ +#include <stdlib.h> /* for qsort() and bsearch() */ +#include <inttypes.h> + +/* + * This corresponds to the same macro defined in + * include/linux/kallsyms.h + */ +#define KSYM_NAME_LEN 128 + +struct funcs_layout { + unsigned long mcount_start; + unsigned long mcount_stop; + unsigned long mcount_sec_idx; +}; + +struct elf_function { + const char *name; + unsigned long addr; + bool generated; +}; + +static struct elf_function *functions; +static int functions_alloc; +static int functions_cnt; + +static int functions_cmp(const void *_a, const void *_b) +{ + const struct elf_function *a = _a; + const struct elf_function *b = _b; + + return strcmp(a->name, b->name); +} + +static void delete_functions(void) +{ + free(functions); + functions_alloc = functions_cnt = 0; + functions = NULL; +} + +#ifndef max +#define max(x, y) ((x) < (y) ? (y) : (x)) +#endif + +static int collect_function(struct btf_elf *btfe, GElf_Sym *sym) +{ + struct elf_function *new; + + if (elf_sym__type(sym) != STT_FUNC) + return 0; + if (!elf_sym__value(sym)) + return 0; + + if (functions_cnt == functions_alloc) { + functions_alloc = max(1000, functions_alloc * 3 / 2); + new = realloc(functions, functions_alloc * sizeof(*functions)); + if (!new) { + /* + * The cleanup - delete_functions is called + * in cu__encode_btf error path. + */ + return -1; + } + functions = new; + } + + functions[functions_cnt].name = elf_sym__name(sym, btfe->symtab); + functions[functions_cnt].addr = elf_sym__value(sym); + functions[functions_cnt].generated = false; + functions_cnt++; + return 0; +} + +static int addrs_cmp(const void *_a, const void *_b) +{ + const unsigned long *a = _a; + const unsigned long *b = _b; + + if (*a == *b) + return 0; + return *a < *b ? -1 : 1; +} + +static int filter_functions(struct btf_elf *btfe, struct funcs_layout *fl) +{ + unsigned long *addrs, count, offset, i; + int functions_valid = 0; + Elf_Data *data; + GElf_Shdr shdr; + Elf_Scn *sec; + + /* + * Find mcount addressed marked by __start_mcount_loc + * and __stop_mcount_loc symbols and load them into + * sorted array. + */ + sec = elf_getscn(btfe->elf, fl->mcount_sec_idx); + if (!sec || !gelf_getshdr(sec, &shdr)) { + fprintf(stderr, "Failed to get section(%lu) header.\n", + fl->mcount_sec_idx); + return -1; + } + + offset = fl->mcount_start - shdr.sh_addr; + count = (fl->mcount_stop - fl->mcount_start) / 8; + + data = elf_getdata(sec, 0); + if (!data) { + fprintf(stderr, "Failed to get section(%lu) data.\n", + fl->mcount_sec_idx); + return -1; + } + + addrs = malloc(count * sizeof(addrs[0])); + if (!addrs) { + fprintf(stderr, "Failed to allocate memory for ftrace addresses.\n"); + return -1; + } + + memcpy(addrs, data->d_buf + offset, count * sizeof(addrs[0])); + qsort(addrs, count, sizeof(addrs[0]), addrs_cmp); + + /* + * Let's got through all collected functions and filter + * out those that are not in ftrace. + */ + for (i = 0; i < functions_cnt; i++) { + struct elf_function *func = &functions[i]; + + /* Make sure function is within ftrace addresses. */ + if (bsearch(&func->addr, addrs, count, sizeof(addrs[0]), addrs_cmp)) { + /* + * We iterate over sorted array, so we can easily skip + * not valid item and move following valid field into + * its place, and still keep the 'new' array sorted. + */ + if (i != functions_valid) + functions[functions_valid] = functions[i]; + functions_valid++; + } + } + + functions_cnt = functions_valid; + free(addrs); + return 0; +} + +static struct elf_function *find_function(const struct btf_elf *btfe, + const char *name) +{ + struct elf_function key = { .name = name }; + + return bsearch(&key, functions, functions_cnt, sizeof(functions[0]), + functions_cmp); +} + +static bool btf_name_char_ok(char c, bool first) +{ + if (c == '_' || c == '.') + return true; + + return first ? isalpha(c) : isalnum(c); +} + +/* Check whether the given name is valid in vmlinux btf. */ +static bool btf_name_valid(const char *p) +{ + const char *limit; + + if (!btf_name_char_ok(*p, true)) + return false; + + /* set a limit on identifier length */ + limit = p + KSYM_NAME_LEN; + p++; + while (*p && p < limit) { + if (!btf_name_char_ok(*p, false)) + return false; + p++; + } + + return !*p; +} + +static void dump_invalid_symbol(const char *msg, const char *sym, + int verbose, bool force) +{ + if (force) { + if (verbose) + fprintf(stderr, "PAHOLE: Warning: %s, ignored (sym: '%s').\n", + msg, sym); + return; + } + + fprintf(stderr, "PAHOLE: Error: %s (sym: '%s').\n", msg, sym); + fprintf(stderr, "PAHOLE: Error: Use '--btf_encode_force' to ignore such symbols and force emit the btf.\n"); +} + +extern struct debug_fmt_ops *dwarves__active_loader; + +static int tag__check_id_drift(const struct tag *tag, + uint32_t core_id, uint32_t btf_type_id, + uint32_t type_id_off) +{ + if (btf_type_id != (core_id + type_id_off)) { + fprintf(stderr, + "%s: %s id drift, core_id: %u, btf_type_id: %u, type_id_off: %u\n", + __func__, dwarf_tag_name(tag->tag), + core_id, btf_type_id, type_id_off); + return -1; + } + + return 0; +} + +static int32_t structure_type__encode(struct btf_elf *btfe, struct cu *cu, struct tag *tag, uint32_t type_id_off) +{ + struct type *type = tag__type(tag); + struct class_member *pos; + const char *name; + int32_t type_id; + uint8_t kind; + + kind = (tag->tag == DW_TAG_union_type) ? + BTF_KIND_UNION : BTF_KIND_STRUCT; + + name = dwarves__active_loader->strings__ptr(cu, type->namespace.name); + type_id = btf_elf__add_struct(btfe, kind, name, type->size); + if (type_id < 0) + return type_id; + + type__for_each_data_member(type, pos) { + /* + * dwarf_loader uses DWARF's recommended bit offset addressing + * scheme, which conforms to BTF requirement, so no conversion + * is required. + */ + name = dwarves__active_loader->strings__ptr(cu, pos->name); + if (btf_elf__add_member(btfe, name, type_id_off + pos->tag.type, pos->bitfield_size, pos->bit_offset)) + return -1; + } + + return type_id; +} + +static uint32_t array_type__nelems(struct tag *tag) +{ + int i; + uint32_t nelem = 1; + struct array_type *array = tag__array_type(tag); + + for (i = array->dimensions - 1; i >= 0; --i) + nelem *= array->nr_entries[i]; + + return nelem; +} + +static int32_t enumeration_type__encode(struct btf_elf *btfe, struct cu *cu, struct tag *tag) +{ + struct type *etype = tag__type(tag); + struct enumerator *pos; + const char *name; + int32_t type_id; + + name = dwarves__active_loader->strings__ptr(cu, etype->namespace.name); + type_id = btf_elf__add_enum(btfe, name, etype->size); + if (type_id < 0) + return type_id; + + type__for_each_enumerator(etype, pos) { + name = dwarves__active_loader->strings__ptr(cu, pos->name); + if (btf_elf__add_enum_val(btfe, name, pos->value)) + return -1; + } + + return type_id; +} + +static bool need_index_type; + +static int tag__encode_btf(struct cu *cu, struct tag *tag, uint32_t core_id, struct btf_elf *btfe, + uint32_t array_index_id, uint32_t type_id_off) +{ + /* single out type 0 as it represents special type "void" */ + uint32_t ref_type_id = tag->type == 0 ? 0 : type_id_off + tag->type; + const char *name; + + switch (tag->tag) { + case DW_TAG_base_type: + name = dwarves__active_loader->strings__ptr(cu, tag__base_type(tag)->name); + return btf_elf__add_base_type(btfe, tag__base_type(tag), name); + case DW_TAG_const_type: + return btf_elf__add_ref_type(btfe, BTF_KIND_CONST, ref_type_id, NULL, false); + case DW_TAG_pointer_type: + return btf_elf__add_ref_type(btfe, BTF_KIND_PTR, ref_type_id, NULL, false); + case DW_TAG_restrict_type: + return btf_elf__add_ref_type(btfe, BTF_KIND_RESTRICT, ref_type_id, NULL, false); + case DW_TAG_volatile_type: + return btf_elf__add_ref_type(btfe, BTF_KIND_VOLATILE, ref_type_id, NULL, false); + case DW_TAG_typedef: + name = dwarves__active_loader->strings__ptr(cu, tag__namespace(tag)->name); + return btf_elf__add_ref_type(btfe, BTF_KIND_TYPEDEF, ref_type_id, name, false); + case DW_TAG_structure_type: + case DW_TAG_union_type: + case DW_TAG_class_type: + name = dwarves__active_loader->strings__ptr(cu, tag__namespace(tag)->name); + if (tag__type(tag)->declaration) + return btf_elf__add_ref_type(btfe, BTF_KIND_FWD, 0, name, tag->tag == DW_TAG_union_type); + else + return structure_type__encode(btfe, cu, tag, type_id_off); + case DW_TAG_array_type: + /* TODO: Encode one dimension at a time. */ + need_index_type = true; + return btf_elf__add_array(btfe, ref_type_id, array_index_id, array_type__nelems(tag)); + case DW_TAG_enumeration_type: + return enumeration_type__encode(btfe, cu, tag); + case DW_TAG_subroutine_type: + return btf_elf__add_func_proto(btfe, cu, tag__ftype(tag), type_id_off); + default: + fprintf(stderr, "Unsupported DW_TAG_%s(0x%x)\n", + dwarf_tag_name(tag->tag), tag->tag); + return -1; + } +} + +static struct btf_elf *btfe; +static uint32_t array_index_id; +static bool has_index_type; + +int btf_encoder__encode() +{ + int err; + + if (gobuffer__size(&btfe->percpu_secinfo) != 0) + btf_elf__add_datasec_type(btfe, PERCPU_SECTION, &btfe->percpu_secinfo); + + err = btf_elf__encode(btfe, 0); + delete_functions(); + btf_elf__delete(btfe); + btfe = NULL; + + return err; +} + +#define MAX_PERCPU_VAR_CNT 4096 + +struct var_info { + uint64_t addr; + uint32_t sz; + const char *name; +}; + +static struct var_info percpu_vars[MAX_PERCPU_VAR_CNT]; +static int percpu_var_cnt; + +static int percpu_var_cmp(const void *_a, const void *_b) +{ + const struct var_info *a = _a; + const struct var_info *b = _b; + + if (a->addr == b->addr) + return 0; + return a->addr < b->addr ? -1 : 1; +} + +static bool percpu_var_exists(uint64_t addr, uint32_t *sz, const char **name) +{ + const struct var_info *p; + struct var_info key = { .addr = addr }; + + p = bsearch(&key, percpu_vars, percpu_var_cnt, + sizeof(percpu_vars[0]), percpu_var_cmp); + + if (!p) + return false; + + *sz = p->sz; + *name = p->name; + return true; +} + +static int collect_percpu_var(struct btf_elf *btfe, GElf_Sym *sym) +{ + const char *sym_name; + uint64_t addr; + uint32_t size; + + /* compare a symbol's shndx to determine if it's a percpu variable */ + if (elf_sym__section(sym) != btfe->percpu_shndx) + return 0; + if (elf_sym__type(sym) != STT_OBJECT) + return 0; + + addr = elf_sym__value(sym); + /* + * Store only those symbols that have allocated space in the percpu section. + * This excludes the following three types of symbols: + * + * 1. __ADDRESSABLE(sym), which are forcely emitted as symbols. + * 2. __UNIQUE_ID(prefix), which are introduced to generate unique ids. + * 3. __exitcall(fn), functions which are labeled as exit calls. + * + * In addition, the variables defined using DEFINE_PERCPU_FIRST are + * also not included, which currently includes: + * + * 1. fixed_percpu_data + */ + if (!addr) + return 0; + + size = elf_sym__size(sym); + if (!size) + return 0; /* ignore zero-sized symbols */ + + sym_name = elf_sym__name(sym, btfe->symtab); + if (!btf_name_valid(sym_name)) { + dump_invalid_symbol("Found symbol of invalid name when encoding btf", + sym_name, btf_elf__verbose, btf_elf__force); + if (btf_elf__force) + return 0; + return -1; + } + + if (btf_elf__verbose) + printf("Found per-CPU symbol '%s' at address 0x%lx\n", sym_name, addr); + + if (percpu_var_cnt == MAX_PERCPU_VAR_CNT) { + fprintf(stderr, "Reached the limit of per-CPU variables: %d\n", + MAX_PERCPU_VAR_CNT); + return -1; + } + percpu_vars[percpu_var_cnt].addr = addr; + percpu_vars[percpu_var_cnt].sz = size; + percpu_vars[percpu_var_cnt].name = sym_name; + percpu_var_cnt++; + + return 0; +} + +static void collect_symbol(GElf_Sym *sym, struct funcs_layout *fl) +{ + if (!fl->mcount_start && + !strcmp("__start_mcount_loc", elf_sym__name(sym, btfe->symtab))) { + fl->mcount_start = sym->st_value; + fl->mcount_sec_idx = sym->st_shndx; + } + + if (!fl->mcount_stop && + !strcmp("__stop_mcount_loc", elf_sym__name(sym, btfe->symtab))) + fl->mcount_stop = sym->st_value; +} + +static int has_all_symbols(struct funcs_layout *fl) +{ + return fl->mcount_start && fl->mcount_stop; +} + +static int collect_symbols(struct btf_elf *btfe, bool collect_percpu_vars) +{ + struct funcs_layout fl = { }; + uint32_t core_id; + GElf_Sym sym; + + /* cache variables' addresses, preparing for searching in symtab. */ + percpu_var_cnt = 0; + + /* search within symtab for percpu variables */ + elf_symtab__for_each_symbol(btfe->symtab, core_id, sym) { + if (collect_percpu_vars && collect_percpu_var(btfe, &sym)) + return -1; + if (collect_function(btfe, &sym)) + return -1; + collect_symbol(&sym, &fl); + } + + if (collect_percpu_vars) { + if (percpu_var_cnt) + qsort(percpu_vars, percpu_var_cnt, sizeof(percpu_vars[0]), percpu_var_cmp); + + if (btf_elf__verbose) + printf("Found %d per-CPU variables!\n", percpu_var_cnt); + } + + if (functions_cnt && has_all_symbols(&fl)) { + qsort(functions, functions_cnt, sizeof(functions[0]), functions_cmp); + if (filter_functions(btfe, &fl)) { + fprintf(stderr, "Failed to filter dwarf functions\n"); + return -1; + } + if (btf_elf__verbose) + printf("Found %d functions!\n", functions_cnt); + } else { + if (btf_elf__verbose) + printf("ftrace symbols not detected, falling back to DWARF data\n"); + delete_functions(); + } + + return 0; +} + +static bool has_arg_names(struct cu *cu, struct ftype *ftype) +{ + struct parameter *param; + const char *name; + + ftype__for_each_parameter(ftype, param) { + name = dwarves__active_loader->strings__ptr(cu, param->name); + if (name == NULL) + return false; + } + return true; +} + +int cu__encode_btf(struct cu *cu, int verbose, bool force, + bool skip_encoding_vars) +{ + uint32_t type_id_off; + uint32_t core_id; + struct variable *var; + struct function *fn; + struct tag *pos; + int err = 0; + + btf_elf__verbose = verbose; + btf_elf__force = force; + + if (btfe && strcmp(btfe->filename, cu->filename)) { + err = btf_encoder__encode(); + if (err) + goto out; + + /* Finished one file, add one empty line */ + if (verbose) + printf("\n"); + } + + if (!btfe) { + btfe = btf_elf__new(cu->filename, cu->elf, base_btf); + if (!btfe) + return -1; + + err = collect_symbols(btfe, !skip_encoding_vars); + if (err) + goto out; + + has_index_type = false; + need_index_type = false; + array_index_id = 0; + + if (verbose) + printf("File %s:\n", btfe->filename); + } + + type_id_off = btf__get_nr_types(btfe->btf); + + if (!has_index_type) { + /* cu__find_base_type_by_name() takes "type_id_t *id" */ + type_id_t id; + if (cu__find_base_type_by_name(cu, "int", &id)) { + has_index_type = true; + array_index_id = type_id_off + id; + } else { + has_index_type = false; + array_index_id = type_id_off + cu->types_table.nr_entries; + } + } + + cu__for_each_type(cu, core_id, pos) { + int32_t btf_type_id = tag__encode_btf(cu, pos, core_id, btfe, array_index_id, type_id_off); + + if (btf_type_id < 0 || + tag__check_id_drift(pos, core_id, btf_type_id, type_id_off)) { + err = -1; + goto out; + } + } + + if (need_index_type && !has_index_type) { + struct base_type bt = {}; + + bt.name = 0; + bt.bit_size = 32; + btf_elf__add_base_type(btfe, &bt, "__ARRAY_SIZE_TYPE__"); + has_index_type = true; + } + + cu__for_each_function(cu, core_id, fn) { + int btf_fnproto_id, btf_fn_id; + const char *name; + + /* + * Skip functions that: + * - are marked as declarations + * - do not have full argument names + * - are not in ftrace list (if it's available) + * - are not external (in case ftrace filter is not available) + */ + if (fn->declaration) + continue; + if (!has_arg_names(cu, &fn->proto)) + continue; + if (functions_cnt) { + struct elf_function *func; + + func = find_function(btfe, function__name(fn, cu)); + if (!func || func->generated) + continue; + func->generated = true; + } else { + if (!fn->external) + continue; + } + + btf_fnproto_id = btf_elf__add_func_proto(btfe, cu, &fn->proto, type_id_off); + name = dwarves__active_loader->strings__ptr(cu, fn->name); + btf_fn_id = btf_elf__add_ref_type(btfe, BTF_KIND_FUNC, btf_fnproto_id, name, false); + if (btf_fnproto_id < 0 || btf_fn_id < 0) { + err = -1; + printf("error: failed to encode function '%s'\n", function__name(fn, cu)); + goto out; + } + } + + if (skip_encoding_vars) + goto out; + + if (btfe->percpu_shndx == 0 || !btfe->symtab) + goto out; + + if (verbose) + printf("search cu '%s' for percpu global variables.\n", cu->name); + + cu__for_each_variable(cu, core_id, pos) { + uint32_t size, type, linkage, offset; + const char *name; + uint64_t addr; + int id; + + var = tag__variable(pos); + if (var->declaration && !var->spec) + continue; + /* percpu variables are allocated in global space */ + if (variable__scope(var) != VSCOPE_GLOBAL && !var->spec) + continue; + + /* addr has to be recorded before we follow spec */ + addr = var->ip.addr; + if (var->spec) + var = var->spec; + + if (!percpu_var_exists(addr, &size, &name)) + continue; /* not a per-CPU variable */ + + if (var->ip.tag.type == 0) { + fprintf(stderr, "error: found variable '%s' in CU '%s' that has void type\n", + name, cu->name); + if (force) + continue; + err = -1; + break; + } + + type = var->ip.tag.type + type_id_off; + linkage = var->external ? BTF_VAR_GLOBAL_ALLOCATED : BTF_VAR_STATIC; + + if (btf_elf__verbose) { + printf("Variable '%s' from CU '%s' at address 0x%lx encoded\n", + name, cu->name, addr); + } + + /* add a BTF_KIND_VAR in btfe->types */ + id = btf_elf__add_var_type(btfe, type, name, linkage); + if (id < 0) { + err = -1; + fprintf(stderr, "error: failed to encode variable '%s' at addr 0x%lx\n", + name, addr); + break; + } + + /* + * add a BTF_VAR_SECINFO in btfe->percpu_secinfo, which will be added into + * btfe->types later when we add BTF_VAR_DATASEC. + */ + offset = addr - btfe->percpu_base_addr; + id = btf_elf__add_var_secinfo(&btfe->percpu_secinfo, id, offset, size); + if (id < 0) { + err = -1; + fprintf(stderr, "error: failed to encode section info for variable '%s' at addr 0x%lx\n", + name, addr); + break; + } + } + +out: + if (err) { + delete_functions(); + btf_elf__delete(btfe); + btfe = NULL; + } + return err; +} diff --git a/btf_encoder.h b/btf_encoder.h new file mode 100644 index 0000000..46fb231 --- /dev/null +++ b/btf_encoder.h @@ -0,0 +1,19 @@ +#ifndef _BTF_ENCODER_H_ +#define _BTF_ENCODER_H_ 1 +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2019 Facebook + + Derived from ctf_encoder.h, which is: + Copyright (C) Arnaldo Carvalho de Melo <acme@redhat.com> + */ + +struct cu; + +int btf_encoder__encode(); + +int cu__encode_btf(struct cu *cu, int verbose, bool force, + bool skip_encoding_vars); + +#endif /* _BTF_ENCODER_H_ */ diff --git a/btf_loader.c b/btf_loader.c new file mode 100644 index 0000000..ec286f4 --- /dev/null +++ b/btf_loader.c @@ -0,0 +1,579 @@ +/* + * btf_loader.c + * + * Copyright (C) 2018 Arnaldo Carvalho de Melo <acme@kernel.org> + * + * Based on ctf_loader.c that, in turn, was based on ctfdump.c: CTF dumper. + * + * Copyright (C) 2008 David S. Miller <davem@davemloft.net> + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <stddef.h> +#include <malloc.h> +#include <string.h> +#include <limits.h> +#include <libgen.h> +#include <zlib.h> + +#include <gelf.h> + +#include "libbtf.h" +#include "lib/bpf/include/uapi/linux/btf.h" +#include "dutil.h" +#include "dwarves.h" + +/* + * FIXME: We should just get the table from the BTF ELF section + * and use it directly + */ +extern struct strings *strings; + +static void *tag__alloc(const size_t size) +{ + struct tag *tag = zalloc(size); + + if (tag != NULL) + tag->top_level = 1; + + return tag; +} + +static int btf_elf__load_ftype(struct btf_elf *btfe, struct ftype *proto, uint32_t tag, + const struct btf_type *tp, uint32_t id) +{ + const struct btf_param *param = btf_params(tp); + int i, vlen = btf_vlen(tp); + + proto->tag.tag = tag; + proto->tag.type = tp->type; + INIT_LIST_HEAD(&proto->parms); + + for (i = 0; i < vlen; ++i, param++) { + if (param->type == 0) + proto->unspec_parms = 1; + else { + struct parameter *p = tag__alloc(sizeof(*p)); + + if (p == NULL) + goto out_free_parameters; + p->tag.tag = DW_TAG_formal_parameter; + p->tag.type = param->type; + p->name = param->name_off; + ftype__add_parameter(proto, p); + } + } + + cu__add_tag_with_id(btfe->priv, &proto->tag, id); + + return 0; +out_free_parameters: + ftype__delete(proto, btfe->priv); + return -ENOMEM; +} + +static int create_new_function(struct btf_elf *btfe, const struct btf_type *tp, uint32_t id) +{ + struct function *func = tag__alloc(sizeof(*func)); + + if (func == NULL) + return -ENOMEM; + + // for BTF this is not really the type of the return of the function, + // but the prototype, the return type is the one in type_id + func->btf = 1; + func->proto.tag.tag = DW_TAG_subprogram; + func->proto.tag.type = tp->type; + func->name = tp->name_off; + INIT_LIST_HEAD(&func->lexblock.tags); + cu__add_tag_with_id(btfe->priv, &func->proto.tag, id); + + return 0; +} + +static struct base_type *base_type__new(strings_t name, uint32_t attrs, + uint8_t float_type, size_t size) +{ + struct base_type *bt = tag__alloc(sizeof(*bt)); + + if (bt != NULL) { + bt->name = name; + bt->bit_size = size; + bt->is_signed = attrs & BTF_INT_SIGNED; + bt->is_bool = attrs & BTF_INT_BOOL; + bt->name_has_encoding = false; + bt->float_type = float_type; + } + return bt; +} + +static void type__init(struct type *type, uint32_t tag, + strings_t name, size_t size) +{ + __type__init(type); + INIT_LIST_HEAD(&type->namespace.tags); + type->size = size; + type->namespace.tag.tag = tag; + type->namespace.name = name; + type->namespace.sname = 0; +} + +static struct type *type__new(uint16_t tag, strings_t name, size_t size) +{ + struct type *type = tag__alloc(sizeof(*type)); + + if (type != NULL) + type__init(type, tag, name, size); + + return type; +} + +static struct class *class__new(strings_t name, size_t size, bool is_union) +{ + struct class *class = tag__alloc(sizeof(*class)); + uint32_t tag = is_union ? DW_TAG_union_type : DW_TAG_structure_type; + + if (class != NULL) { + type__init(&class->type, tag, name, size); + INIT_LIST_HEAD(&class->vtable); + } + + return class; +} + +static struct variable *variable__new(strings_t name, uint32_t linkage) +{ + struct variable *var = tag__alloc(sizeof(*var)); + + if (var != NULL) { + var->external = linkage == BTF_VAR_GLOBAL_ALLOCATED; + var->name = name; + var->ip.tag.tag = DW_TAG_variable; + } + + return var; +} + +static int create_new_base_type(struct btf_elf *btfe, const struct btf_type *tp, uint32_t id) +{ + uint32_t attrs = btf_int_encoding(tp); + strings_t name = tp->name_off; + struct base_type *base = base_type__new(name, attrs, 0, btf_int_bits(tp)); + + if (base == NULL) + return -ENOMEM; + + base->tag.tag = DW_TAG_base_type; + cu__add_tag_with_id(btfe->priv, &base->tag, id); + + return 0; +} + +static int create_new_array(struct btf_elf *btfe, const struct btf_type *tp, uint32_t id) +{ + struct btf_array *ap = btf_array(tp); + struct array_type *array = tag__alloc(sizeof(*array)); + + if (array == NULL) + return -ENOMEM; + + /* FIXME: where to get the number of dimensions? + * it it flattened? */ + array->dimensions = 1; + array->nr_entries = malloc(sizeof(uint32_t)); + + if (array->nr_entries == NULL) { + free(array); + return -ENOMEM; + } + + array->nr_entries[0] = ap->nelems; + array->tag.tag = DW_TAG_array_type; + array->tag.type = ap->type; + + cu__add_tag_with_id(btfe->priv, &array->tag, id); + + return 0; +} + +static int create_members(struct btf_elf *btfe, const struct btf_type *tp, + struct type *class) +{ + struct btf_member *mp = btf_members(tp); + int i, vlen = btf_vlen(tp); + + for (i = 0; i < vlen; i++) { + struct class_member *member = zalloc(sizeof(*member)); + + if (member == NULL) + return -ENOMEM; + + member->tag.tag = DW_TAG_member; + member->tag.type = mp[i].type; + member->name = mp[i].name_off; + member->bit_offset = btf_member_bit_offset(tp, i); + member->bitfield_size = btf_member_bitfield_size(tp, i); + member->byte_offset = member->bit_offset / 8; + /* sizes and offsets will be corrected at class__fixup_btf_bitfields */ + type__add_member(class, member); + } + + return 0; +} + +static int create_new_class(struct btf_elf *btfe, const struct btf_type *tp, uint32_t id) +{ + struct class *class = class__new(tp->name_off, tp->size, false); + int member_size = create_members(btfe, tp, &class->type); + + if (member_size < 0) + goto out_free; + + cu__add_tag_with_id(btfe->priv, &class->type.namespace.tag, id); + + return 0; +out_free: + class__delete(class, btfe->priv); + return -ENOMEM; +} + +static int create_new_union(struct btf_elf *btfe, const struct btf_type *tp, uint32_t id) +{ + struct type *un = type__new(DW_TAG_union_type, tp->name_off, tp->size); + int member_size = create_members(btfe, tp, un); + + if (member_size < 0) + goto out_free; + + cu__add_tag_with_id(btfe->priv, &un->namespace.tag, id); + + return 0; +out_free: + type__delete(un, btfe->priv); + return -ENOMEM; +} + +static struct enumerator *enumerator__new(strings_t name, uint32_t value) +{ + struct enumerator *en = tag__alloc(sizeof(*en)); + + if (en != NULL) { + en->name = name; + en->value = value; + en->tag.tag = DW_TAG_enumerator; + } + + return en; +} + +static int create_new_enumeration(struct btf_elf *btfe, const struct btf_type *tp, uint32_t id) +{ + struct btf_enum *ep = btf_enum(tp); + uint16_t i, vlen = btf_vlen(tp); + struct type *enumeration = type__new(DW_TAG_enumeration_type, + tp->name_off, + tp->size ? tp->size * 8 : (sizeof(int) * 8)); + + if (enumeration == NULL) + return -ENOMEM; + + for (i = 0; i < vlen; i++) { + strings_t name = ep[i].name_off; + uint32_t value = ep[i].val; + struct enumerator *enumerator = enumerator__new(name, value); + + if (enumerator == NULL) + goto out_free; + + enumeration__add(enumeration, enumerator); + } + + cu__add_tag_with_id(btfe->priv, &enumeration->namespace.tag, id); + + return 0; +out_free: + enumeration__delete(enumeration, btfe->priv); + return -ENOMEM; +} + +static int create_new_subroutine_type(struct btf_elf *btfe, const struct btf_type *tp, uint32_t id) +{ + struct ftype *proto = tag__alloc(sizeof(*proto)); + + if (proto == NULL) + return -ENOMEM; + + return btf_elf__load_ftype(btfe, proto, DW_TAG_subroutine_type, tp, id); +} + +static int create_new_forward_decl(struct btf_elf *btfe, const struct btf_type *tp, uint32_t id) +{ + struct class *fwd = class__new(tp->name_off, 0, btf_kflag(tp)); + + if (fwd == NULL) + return -ENOMEM; + fwd->type.declaration = 1; + cu__add_tag_with_id(btfe->priv, &fwd->type.namespace.tag, id); + return 0; +} + +static int create_new_typedef(struct btf_elf *btfe, const struct btf_type *tp, uint32_t id) +{ + struct type *type = type__new(DW_TAG_typedef, tp->name_off, 0); + + if (type == NULL) + return -ENOMEM; + + type->namespace.tag.type = tp->type; + cu__add_tag_with_id(btfe->priv, &type->namespace.tag, id); + + return 0; +} + +static int create_new_variable(struct btf_elf *btfe, const struct btf_type *tp, uint32_t id) +{ + struct btf_var *bvar = btf_var(tp); + struct variable *var = variable__new(tp->name_off, bvar->linkage); + + if (var == NULL) + return -ENOMEM; + + var->ip.tag.type = tp->type; + cu__add_tag_with_id(btfe->priv, &var->ip.tag, id); + return 0; +} + +static int create_new_datasec(struct btf_elf *btfe, const struct btf_type *tp, uint32_t id) +{ + //strings_t name = btf_elf__get32(btfe, &tp->name_off); + + //cu__add_tag_with_id(btfe->priv, &datasec->tag, id); + + /* + * FIXME: this will not be used to reconstruct some original C code, + * its about runtime placement of variables so just ignore this for now + */ + return 0; +} + +static int create_new_tag(struct btf_elf *btfe, int type, const struct btf_type *tp, uint32_t id) +{ + struct tag *tag = zalloc(sizeof(*tag)); + + if (tag == NULL) + return -ENOMEM; + + switch (type) { + case BTF_KIND_CONST: tag->tag = DW_TAG_const_type; break; + case BTF_KIND_PTR: tag->tag = DW_TAG_pointer_type; break; + case BTF_KIND_RESTRICT: tag->tag = DW_TAG_restrict_type; break; + case BTF_KIND_VOLATILE: tag->tag = DW_TAG_volatile_type; break; + default: + free(tag); + printf("%s: Unknown type %d\n\n", __func__, type); + return 0; + } + + tag->type = tp->type; + cu__add_tag_with_id(btfe->priv, tag, id); + + return 0; +} + +static int btf_elf__load_types(struct btf_elf *btfe) +{ + uint32_t type_index; + int err; + + for (type_index = 1; type_index <= btf__get_nr_types(btfe->btf); type_index++) { + const struct btf_type *type_ptr = btf__type_by_id(btfe->btf, type_index); + uint32_t type = btf_kind(type_ptr); + + switch (type) { + case BTF_KIND_INT: + err = create_new_base_type(btfe, type_ptr, type_index); + break; + case BTF_KIND_ARRAY: + err = create_new_array(btfe, type_ptr, type_index); + break; + case BTF_KIND_STRUCT: + err = create_new_class(btfe, type_ptr, type_index); + break; + case BTF_KIND_UNION: + err = create_new_union(btfe, type_ptr, type_index); + break; + case BTF_KIND_ENUM: + err = create_new_enumeration(btfe, type_ptr, type_index); + break; + case BTF_KIND_FWD: + err = create_new_forward_decl(btfe, type_ptr, type_index); + break; + case BTF_KIND_TYPEDEF: + err = create_new_typedef(btfe, type_ptr, type_index); + break; + case BTF_KIND_VAR: + err = create_new_variable(btfe, type_ptr, type_index); + break; + case BTF_KIND_DATASEC: + err = create_new_datasec(btfe, type_ptr, type_index); + break; + case BTF_KIND_VOLATILE: + case BTF_KIND_PTR: + case BTF_KIND_CONST: + case BTF_KIND_RESTRICT: + err = create_new_tag(btfe, type, type_ptr, type_index); + break; + case BTF_KIND_UNKN: + cu__table_nullify_type_entry(btfe->priv, type_index); + fprintf(stderr, "BTF: idx: %d, Unknown kind %d\n", type_index, type); + fflush(stderr); + err = 0; + break; + case BTF_KIND_FUNC_PROTO: + err = create_new_subroutine_type(btfe, type_ptr, type_index); + break; + case BTF_KIND_FUNC: + // BTF_KIND_FUNC corresponding to a defined subprogram. + err = create_new_function(btfe, type_ptr, type_index); + break; + default: + fprintf(stderr, "BTF: idx: %d, Unknown kind %d\n", type_index, type); + fflush(stderr); + err = 0; + break; + } + + if (err < 0) + return err; + } + return 0; +} + +static int btf_elf__load_sections(struct btf_elf *btfe) +{ + return btf_elf__load_types(btfe); +} + +static int class__fixup_btf_bitfields(struct tag *tag, struct cu *cu, struct btf_elf *btfe) +{ + struct class_member *pos; + struct type *tag_type = tag__type(tag); + + type__for_each_data_member(tag_type, pos) { + struct tag *type = tag__strip_typedefs_and_modifiers(&pos->tag, cu); + + if (type == NULL) /* FIXME: C++ BTF... */ + continue; + + pos->bitfield_offset = 0; + pos->byte_size = tag__size(type, cu); + pos->bit_size = pos->byte_size * 8; + + /* bitfield fixup is needed for enums and base types only */ + if (type->tag != DW_TAG_base_type && type->tag != DW_TAG_enumeration_type) + continue; + + /* if BTF data is incorrect and has size == 0, skip field, + * instead of crashing */ + if (pos->byte_size == 0) { + continue; + } + + if (pos->bitfield_size) { + /* bitfields seem to be always aligned, no matter the packing */ + pos->byte_offset = pos->bit_offset / pos->bit_size * pos->bit_size / 8; + pos->bitfield_offset = pos->bit_offset - pos->byte_offset * 8; + /* re-adjust bitfield offset if it is negative */ + if (pos->bitfield_offset < 0) { + pos->bitfield_offset += pos->bit_size; + pos->byte_offset -= pos->byte_size; + pos->bit_offset = pos->byte_offset * 8 + pos->bitfield_offset; + } + } else { + pos->byte_offset = pos->bit_offset / 8; + } + } + + return 0; +} + +static int cu__fixup_btf_bitfields(struct cu *cu, struct btf_elf *btfe) +{ + int err = 0; + struct tag *pos; + + list_for_each_entry(pos, &cu->tags, node) + if (tag__is_struct(pos) || tag__is_union(pos)) { + err = class__fixup_btf_bitfields(pos, cu, btfe); + if (err) + break; + } + + return err; +} + +static void btf_elf__cu_delete(struct cu *cu) +{ + btf_elf__delete(cu->priv); + cu->priv = NULL; +} + +static const char *btf_elf__strings_ptr(const struct cu *cu, strings_t s) +{ + return btf_elf__string(cu->priv, s); +} + +struct debug_fmt_ops btf_elf__ops; + +int btf_elf__load_file(struct cus *cus, struct conf_load *conf, const char *filename) +{ + int err; + struct btf_elf *btfe = btf_elf__new(filename, NULL, base_btf); + + if (btfe == NULL) + return -1; + + struct cu *cu = cu__new(filename, btfe->wordsize, NULL, 0, filename); + if (cu == NULL) + return -1; + + cu->language = LANG_C; + cu->uses_global_strings = false; + cu->little_endian = !btfe->is_big_endian; + cu->dfops = &btf_elf__ops; + cu->priv = btfe; + btfe->priv = cu; + if (btf_elf__load(btfe) != 0) + return -1; + + err = btf_elf__load_sections(btfe); + + if (err != 0) { + cu__delete(cu); + return err; + } + + err = cu__fixup_btf_bitfields(cu, btfe); + /* + * The app stole this cu, possibly deleting it, + * so forget about it + */ + if (conf && conf->steal && conf->steal(cu, conf)) + return 0; + + cus__add(cus, cu); + return err; +} + +struct debug_fmt_ops btf_elf__ops = { + .name = "btf", + .load_file = btf_elf__load_file, + .strings__ptr = btf_elf__strings_ptr, + .cu__delete = btf_elf__cu_delete, +}; @@ -0,0 +1,33 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0-only +# Copyright © 2019 Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com> +# Use pahole to produce output from BTF and from DWARF, then do a diff +# Use --flat_arrays with DWARF as BTF, like CTF, flattens arrays. +# Use --show_private_classes as BTF shows all structs, while pahole knows +# if some struct is defined only inside another struct/class or in a function, +# this information is not available when loading from BTF. + +if [ $# -eq 0 ] ; then + echo "Usage: btfdiff <filename_with_BTF_and_DWARF_info>" + exit 1 +fi + +file=$1 +btf_output=$(mktemp /tmp/btfdiff.btf.XXXXXX) +dwarf_output=$(mktemp /tmp/btfdiff.dwarf.XXXXXX) +pahole_bin=${PAHOLE-"pahole"} + +${pahole_bin} -F dwarf \ + --flat_arrays \ + --suppress_aligned_attribute \ + --suppress_force_paddings \ + --suppress_packed \ + --show_private_classes $file > $dwarf_output +${pahole_bin} -F btf \ + --suppress_packed \ + $file > $btf_output + +diff -up $dwarf_output $btf_output + +rm -f $btf_output $dwarf_output +exit 0 diff --git a/changes-v1.13 b/changes-v1.13 new file mode 100644 index 0000000..b41ad7e --- /dev/null +++ b/changes-v1.13 @@ -0,0 +1,209 @@ +Here is a summary of changes for the 1.13 version of pahole and its friends: + +- BTF + + - Use of the recently introduced BTF deduplication algorithm present in the + Linux kernel's libbpf library, which allows for all the types in a multi + compile unit binary such as vmlinux to be compactly stored, without duplicates. + + E.g.: from roughly: + + $ readelf -SW ../build/v5.1-rc4+/vmlinux | grep .debug_info.*PROGBITS + [63] .debug_info PROGBITS 0000000000000000 1d80be0 c3c18b9 00 0 0 1 + $ + 195 MiB + + to: + + $ time pahole --btf_encode ../build/v5.1-rc4+/vmlinux + real 0m19.168s + user 0m17.707s # On a Lenovo t480s (i7-8650U) SSD + sys 0m1.337s + $ + + $ readelf -SW ../build/v5.1-rc4+/vmlinux | grep .BTF.*PROGBITS + [78] .BTF PROGBITS 0000000000000000 27b49f61 1e23c3 00 0 0 1 + $ + ~2 MiB + + - Introduce a 'btfdiff' utility that prints the output from DWARF and from + BTF, comparing the pretty printed outputs, running it on various linux + kernel images, such as an allyesconfig for ppc64. + + Running it on the above 5.1-rc4+ vmlinux: + + $ btfdiff ../build/v5.1-rc4+/vmlinux + $ + + No differences from the types generated from the DWARF ELF sections to the + ones generated from the BTF ELF section. + + - Add a BTF loader, i.e. 'pahole -F btf' allows pretty printing of structs + and unions in the same fashion as with DWARF info, and since BTF is way + more compact, using it is much faster than using DWARF. + + $ cat ../build/v5.1-rc4+/vmlinux > /dev/null + $ perf stat -e cycles pahole -F btf ../build/v5.1-rc4+/vmlinux > /dev/null + + Performance counter stats for 'pahole -F btf ../build/v5.1-rc4+/vmlinux': + + 229,712,692 cycles:u + 0.063379597 seconds time elapsed + 0.056265000 seconds user + 0.006911000 seconds sys + + $ perf stat -e cycles pahole -F dwarf ../build/v5.1-rc4+/vmlinux > /dev/null + + Performance counter stats for 'pahole -F dwarf ../build/v5.1-rc4+/vmlinux': + + 49,579,679,466 cycles:u + 13.063487352 seconds time elapsed + 12.612512000 seconds user + 0.426226000 seconds sys + $ + +- Better union support: + + - Allow unions to be specified in pahole in the same fashion as structs + + $ pahole -C thread_union ../build/v5.1-rc4+/net/ipv4/tcp.o + union thread_union { + struct task_struct task __attribute__((__aligned__(64))); /* 0 11008 */ + long unsigned int stack[2048]; /* 0 16384 */ + }; + $ + +- Infer __attribute__((__packed__)) when structs have no alignment holes + and violate basic types (integer, longs, short integer) natural alignment + requirements. Several heuristics are used to infer the __packed__ + attribute, see the changeset log for descriptions. + + $ pahole -F btf -C boot_e820_entry ../build/v5.1-rc4+/vmlinux + struct boot_e820_entry { + __u64 addr; /* 0 8 */ + __u64 size; /* 8 8 */ + __u32 type; /* 16 4 */ + + /* size: 20, cachelines: 1, members: 3 */ + /* last cacheline: 20 bytes */ + } __attribute__((__packed__)); + $ + + $ pahole -F btf -C lzma_header ../build/v5.1-rc4+/vmlinux + struct lzma_header { + uint8_t pos; /* 0 1 */ + uint32_t dict_size; /* 1 4 */ + uint64_t dst_size; /* 5 8 */ + + /* size: 13, cachelines: 1, members: 3 */ + /* last cacheline: 13 bytes */ + } __attribute__((__packed__)); + +- Support DWARF5's DW_AT_alignment, which, together with the __packed__ + attribute inference algorithms produce output that, when compiled, should + produce structures with layouts that match the original source code. + + See it in action with 'struct task_struct', which will also show some of the + new information at the struct summary, at the end of the struct: + + $ pahole -C task_struct ../build/v5.1-rc4+/vmlinux | tail -19 + /* --- cacheline 103 boundary (6592 bytes) --- */ + struct vm_struct * stack_vm_area; /* 6592 8 */ + refcount_t stack_refcount; /* 6600 4 */ + + /* XXX 4 bytes hole, try to pack */ + + void * security; /* 6608 8 */ + + /* XXX 40 bytes hole, try to pack */ + + /* --- cacheline 104 boundary (6656 bytes) --- */ + struct thread_struct thread __attribute__((__aligned__(64))); /* 6656 4352 */ + + /* size: 11008, cachelines: 172, members: 207 */ + /* sum members: 10902, holes: 16, sum holes: 98 */ + /* sum bitfield members: 10 bits, bit holes: 2, sum bit holes: 54 bits */ + /* paddings: 3, sum paddings: 14 */ + /* forced alignments: 6, forced holes: 1, sum forced holes: 40 */ + } __attribute__((__aligned__(64))); + $ + +- Add a '--compile' option to 'pfunct' that produces compileable output for the + function prototypes in an object file. There are still some bugs but the vast + majority of the kernel single compilation unit files the ones produced from a + single .c file are working, see the new 'fullcircle' utility that uses this + feature. + + Example of it in action: + + $ pfunct --compile=static_key_false ../build/v5.1-rc4+/net/ipv4/tcp.o + typedef _Bool bool; + typedef struct { + int counter; /* 0 4 */ + + /* size: 4, cachelines: 1, members: 1 */ + /* last cacheline: 4 bytes */ + } atomic_t; + + struct jump_entry; + + struct static_key_mod; + + + struct static_key { + atomic_t enabled; /* 0 4 */ + + /* XXX 4 bytes hole, try to pack */ + + union { + long unsigned int type; /* 8 8 */ + struct jump_entry * entries; /* 8 8 */ + struct static_key_mod * next; /* 8 8 */ + }; /* 8 8 */ + + /* size: 16, cachelines: 1, members: 2 */ + /* sum members: 12, holes: 1, sum holes: 4 */ + /* last cacheline: 16 bytes */ + }; + + bool static_key_false(struct static_key * key) + { + return *(bool *)1; + } + + $ + +The generation of compilable code from the type information and its use in the +new tool 'fullcircle, helps validate all the parts of this codebase, finding +bugs that were lurking forever, go read the csets to find all sorts of curious +C language features that are rarely seen, like unnamed zero sized bitfields and +the way people have been using it over the years in a codebase like the linux +kernel. + +Certainly there are several other features, changes and fixes that I forgot to +mention! Now lemme release this version so that we can use it more extensively +together with a recent patch merged for 5.2: + + [PATCH bpf-next] kbuild: add ability to generate BTF type info for vmlinux + +With it BTF will be always available for all the types of the kernel, which will +open a pandora box of cool new features that are in the works, and, for people +already using pahole, will greatly speed up its usage. + +Please try to alias it to use btf, i.e. + + alias pahole='pahole -F btf' + +Please report any problems you may find with this new version or with the BTF +loader or any errors in the layout generated/pretty printed. + +Thanks to the fine BTF guys at Facebook for the patches and help in testing, +fixing bugs and getting this out of the door, the stats for this release are: + + Changesets: 157 + + 113 Arnaldo Carvalho de Melo Red Hat + 32 Andrii Nakryiko Facebook + 10 Yonghong Song Facebook + 1 Martin Lau Facebook + 1 Domenico Andreoli diff --git a/changes-v1.16 b/changes-v1.16 new file mode 100644 index 0000000..0954157 --- /dev/null +++ b/changes-v1.16 @@ -0,0 +1,104 @@ +v1.16 changes: + +BTF encoder: + + Andrii Nakryiko <andriin@fb.com>: + + - Preserve and encode exported functions as BTF_KIND_FUNC. + + Add encoding of DWARF's DW_TAG_subprogram_type into BTF's BTF_KIND_FUNC + (plus corresponding BTF_KIND_FUNC_PROTO). Only exported functions are converted + for now. This allows to capture all the exported kernel functions, same subset + that's exposed through /proc/kallsyms. + +BTF loader: + + Arnaldo Carvalho de Melo <acme@redhat.com> + + - Add support for BTF_KIND_FUNC + + Some changes to the fprintf routines were needed, as BTF has as the + function type just a BTF_KIND_FUNC_PROTO, while DWARF has as the type for a + function its return value type. With a function->btf flag this was overcome and + all the other goodies in pfunct are present. + +Pretty printer: + + Arnaldo Carvalho de Melo: + + - Account inline type __aligned__ member types for spacing: + + union { + refcount_t rcu_users; /* 2568 4 */ + struct callback_head rcu __attribute__((__aligned__(8))); /* 2568 16 */ + - } __attribute__((__aligned__(8))); /* 2568 16 */ + + } __attribute__((__aligned__(8))); /* 2568 16 */ + struct pipe_inode_info * splice_pipe; /* 2584 8 */ + + - Fix alignment of class members that are structs/enums/unions + + E.g. look at that 'completion' member in this struct: + + struct cpu_stop_done { + atomic_t nr_todo; /* 0 4 */ + int ret; /* 4 4 */ + - struct completion completion; /* 8 32 */ + + struct completion completion; /* 8 32 */ + + /* size: 40, cachelines: 1, members: 3 */ + /* last cacheline: 40 bytes */ + + - Fixup handling classes with no members, solving a NULL deref. + + Gareth Lloyd <gareth.lloyd@uk.ibm.com>: + + - Avoid infinite loop trying to determine type with static data member of its own type. + +RPM spec file. + +Jiri Olsa <jolsa@redhat.com> + + Add dwarves dependency on libdwarves1. + +pfunct: + + Arnaldo Carvalho de Melo <acme@redhat.com> + + - type->type == 0 is void, fix --compile for that + + We were using the fall back for that, i.e. 'return 0;' was being emitted + for a function returning void, noticed with using BTF as the format. + +pdwtags: + + - Print DW_TAG_subroutine_type as well + + So that we can see at least via pdwtags those tags, be it from DWARF of BTF. + +core: + + Arnaldo Carvalho de Melo <acme@redhat.com> + + Fix ptr_table__add_with_id() handling of pt->nr_entries, covering how + BTF variables IDs are encoded. + + +pglobal: + + Arnaldo Carvalho de Melo <acme@redhat.com>: + + - Allow passing the format path specifier, to use with BTF + + I.e. now we can, just like with pahole, use: + + pglobal -F btf --variable foo.o + + To get the global variables. + +Tree wide: + + Arnaldo Carvalho de Melo <acme@redhat.com>: + + - Fixup issues pointed out by various coverity reports. + +Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com> diff --git a/changes-v1.17 b/changes-v1.17 new file mode 100644 index 0000000..8999bdf --- /dev/null +++ b/changes-v1.17 @@ -0,0 +1,540 @@ +v1.17 changes: + +tl;dr: + +BTF loader: + + - Support raw BTF as available in /sys/kernel/btf/vmlinux. + +pahole: + + - When the sole argument passed isn't a file, take it as a class name: + + $ pahole sk_buff + + - Do not require a class name to operate without a file name. + + $ pahole # is equivalent to: + $ pahole vmlinux + + - Make --find_pointers_to consider unions: + + $ pahole --find_pointers_to ehci_qh + + - Make --contains and --find_pointers_to honour --unions + + $ pahole --unions --contains inet_sock + + - Add support for finding pointers to void: + + $ pahole --find_pointers_to void + + - Make --contains and --find_pointers_to to work with base types: + + $ pahole --find_pointers_to 'short unsigned int' + + - Make --contains look for more than just unions, structs: + + $ pahole --contains raw_spinlock_t + + - Consider unions when looking for classes containing some class: + + $ pahole --contains tpacket_req + + - Introduce --unions to consider just unions: + + $ pahole --unions --sizes + $ pahole --unions --prefix tcp + $ pahole --unions --nr_members + + - Fix -m/--nr_methods - Number of functions operating on a type pointer + + $ pahole --nr_methods + +man-pages: + + - Add section about --hex + -E to locate offsets deep into sub structs. + + - Add more information about BTF. + + - Add some examples. + +---------------------------------- + +I want the details: + +btf loader: + + - Support raw BTF as available in /sys/kernel/btf/vmlinux + + Be it automatically when no -F option is passed and + /sys/kernel/btf/vmlinux is available, or when /sys/kernel/btf/vmlinux is + passed as the filename to the tool, i.e.: + + $ pahole -C list_head + struct list_head { + struct list_head * next; /* 0 8 */ + struct list_head * prev; /* 8 8 */ + + /* size: 16, cachelines: 1, members: 2 */ + /* last cacheline: 16 bytes */ + }; + $ strace -e openat pahole -C list_head |& grep /sys/kernel/btf/ + openat(AT_FDCWD, "/sys/kernel/btf/vmlinux", O_RDONLY) = 3 + $ + $ pahole -C list_head /sys/kernel/btf/vmlinux + struct list_head { + struct list_head * next; /* 0 8 */ + struct list_head * prev; /* 8 8 */ + + /* size: 16, cachelines: 1, members: 2 */ + /* last cacheline: 16 bytes */ + }; + $ + + If one wants to grab the matching vmlinux to use its DWARF info instead, + which is useful to compare the results with what we have from BTF, for + instance, its just a matter of using '-F dwarf'. + + This in turn shows something that at first came as a surprise, but then + has a simple explanation: + + For very common data structures, that will probably appear in all of the + DWARF CUs (Compilation Units), like 'struct list_head', using '-F dwarf' + is faster: + + $ perf stat -e cycles pahole -F btf -C list_head > /dev/null + + Performance counter stats for 'pahole -F btf -C list_head': + + 45,722,518 cycles:u + + 0.023717300 seconds time elapsed + + 0.016474000 seconds user + 0.007212000 seconds sys + + $ perf stat -e cycles pahole -F dwarf -C list_head > /dev/null + + Performance counter stats for 'pahole -F dwarf -C list_head': + + 14,170,321 cycles:u + + 0.006668904 seconds time elapsed + + 0.005562000 seconds user + 0.001109000 seconds sys + + $ + + But for something that is more specific to a subsystem, the DWARF loader + will have to process way more stuff till it gets to that struct: + + $ perf stat -e cycles pahole -F dwarf -C tcp_sock > /dev/null + + Performance counter stats for 'pahole -F dwarf -C tcp_sock': + + 31,579,795,238 cycles:u + + 8.332272930 seconds time elapsed + + 8.032124000 seconds user + 0.286537000 seconds sys + + $ + + While using the BTF loader the time should be constant, as it loads + everything from /sys/kernel/btf/vmlinux: + + $ perf stat -e cycles pahole -F btf -C tcp_sock > /dev/null + + Performance counter stats for 'pahole -F btf -C tcp_sock': + + 48,823,488 cycles:u + + 0.024102760 seconds time elapsed + + 0.012035000 seconds user + 0.012046000 seconds sys + + $ + + Above I used '-F btf' just to show that it can be used, but its not + really needed, i.e. those are equivalent: + + $ strace -e openat pahole -F btf -C list_head |& grep /sys/kernel/btf/vmlinux + openat(AT_FDCWD, "/sys/kernel/btf/vmlinux", O_RDONLY) = 3 + $ strace -e openat pahole -C list_head |& grep /sys/kernel/btf/vmlinux + openat(AT_FDCWD, "/sys/kernel/btf/vmlinux", O_RDONLY) = 3 + $ + + The btf_raw__load() function that ends up being grafted into the + preexisting btf_elf routines was based on libbpf's btf_load_raw(). + +pahole: + + - When the sole argument passed isn't a file, take it as a class name. + + With that it becomes as compact as it gets for kernel data structures, + just state the name of the struct and it'll try to find that as a file, + not being a file it'll use /sys/kernel/btf/vmlinux and the argument as a + list of structs, i.e.: + + $ pahole skb_ext,list_head + struct list_head { + struct list_head * next; /* 0 8 */ + struct list_head * prev; /* 8 8 */ + + /* size: 16, cachelines: 1, members: 2 */ + /* last cacheline: 16 bytes */ + }; + struct skb_ext { + refcount_t refcnt; /* 0 4 */ + u8 offset[3]; /* 4 3 */ + u8 chunks; /* 7 1 */ + char data[]; /* 8 0 */ + + /* size: 8, cachelines: 1, members: 4 */ + /* last cacheline: 8 bytes */ + }; + $ pahole hlist_node + struct hlist_node { + struct hlist_node * next; /* 0 8 */ + struct hlist_node * * pprev; /* 8 8 */ + + /* size: 16, cachelines: 1, members: 2 */ + /* last cacheline: 16 bytes */ + }; + $ + + Of course -C continues to work: + + $ pahole -C inode | tail + __u32 i_fsnotify_mask; /* 556 4 */ + struct fsnotify_mark_connector * i_fsnotify_marks; /* 560 8 */ + struct fscrypt_info * i_crypt_info; /* 568 8 */ + /* --- cacheline 9 boundary (576 bytes) --- */ + struct fsverity_info * i_verity_info; /* 576 8 */ + void * i_private; /* 584 8 */ + + /* size: 592, cachelines: 10, members: 53 */ + /* last cacheline: 16 bytes */ + }; + $ + + - Add support for finding pointers to void, e.g.: + + $ pahole --find_pointers_to void --prefix tcp + tcp_md5sig_pool: scratch + $ pahole tcp_md5sig_pool + struct tcp_md5sig_pool { + struct ahash_request * md5_req; /* 0 8 */ + void * scratch; /* 8 8 */ + + /* size: 16, cachelines: 1, members: 2 */ + /* last cacheline: 16 bytes */ + }; + $ + + - Make --contains and --find_pointers_to to work with base types + + I.e. with plain 'int', 'long', 'short int', etc: + + $ pahole --find_pointers_to 'short unsigned int' + uv_hub_info_s: socket_to_node + uv_hub_info_s: socket_to_pnode + uv_hub_info_s: pnode_to_socket + vc_data: vc_screenbuf + vc_data: vc_translate + filter_pred: ops + ext4_sb_info: s_mb_offsets + $ pahole ext4_sb_info | 'sort unsigned int' + bash: sort unsigned int: command not found... + ^[^C + $ + $ pahole ext4_sb_info | grep 'sort unsigned int' + $ pahole ext4_sb_info | grep 'short unsigned int' + short unsigned int s_mount_state; /* 160 2 */ + short unsigned int s_pad; /* 162 2 */ + short unsigned int * s_mb_offsets; /* 664 8 */ + $ pahole --contains 'short unsigned int' + apm_info + desc_ptr + thread_struct + mpc_table + mpc_intsrc + fsnotify_mark_connector + <SNIP> + sock_fprog + blk_mq_hw_ctx + skb_shared_info + $ + + - Make --contains look for more than just unions, structs, look for + typedefs, enums and types that descend from 'struct type': + + So now we can do more interesting queries, lets see, what are the data + structures that embed a raw spinlock in the linux kernel? + + $ pahole --contains raw_spinlock_t + task_struct + rw_semaphore + hrtimer_cpu_base + prev_cputime + percpu_counter + ratelimit_state + perf_event_context + task_delay_info + <SNIP> + lpm_trie + bpf_queue_stack + $ + + Look at the csets comments to see more examples. + + - Make --contains and --find_pointers_to honour --unions + + I.e. when looking for unions or structs that contains/embeds or looking + for unions/structs that have pointers to a given type. + + E.g: + + $ pahole --contains inet_sock + sctp_sock + inet_connection_sock + raw_sock + udp_sock + raw6_sock + $ pahole --unions --contains inet_sock + $ + + We have structs embedding 'struct inet_sock', but no unions doing that. + + - Make --find_pointers_to consider unions + + I.e.: + + $ pahole --find_pointers_to ehci_qh + ehci_hcd: qh_scan_next + ehci_hcd: async + ehci_hcd: dummy + $ + + Wasn't considering: + + $ pahole -C ehci_shadow + union ehci_shadow { + struct ehci_qh * qh; /* 0 8 */ + struct ehci_itd * itd; /* 0 8 */ + struct ehci_sitd * sitd; /* 0 8 */ + struct ehci_fstn * fstn; /* 0 8 */ + __le32 * hw_next; /* 0 8 */ + void * ptr; /* 0 8 */ + }; + $ + + Fix it: + + $ pahole --find_pointers_to ehci_qh + ehci_hcd: qh_scan_next + ehci_hcd: async + ehci_hcd: dummy + ehci_shadow: qh + $ + + - Consider unions when looking for classes containing some class: + + I.e.: + + $ pahole --contains tpacket_req + tpacket_req_u + $ + + Wasn't working, but should be considered with --contains/-i: + + $ pahole -C tpacket_req_u + union tpacket_req_u { + struct tpacket_req req; /* 0 16 */ + struct tpacket_req3 req3; /* 0 28 */ + }; + $ + + - Introduce --unions to consider just unions + + Most filters can be used together with it, for instance to see the + biggest unions in the kernel: + + $ pahole --unions --sizes | sort -k2 -nr | head + thread_union 16384 0 + swap_header 4096 0 + fpregs_state 4096 0 + autofs_v5_packet_union 304 0 + autofs_packet_union 272 0 + pptp_ctrl_union 208 0 + acpi_parse_object 200 0 + acpi_descriptor 200 0 + bpf_attr 120 0 + phy_configure_opts 112 0 + $ + + Or just some unions that have some specific prefix: + + $ pahole --unions --prefix tcp + union tcp_md5_addr { + struct in_addr a4; /* 0 4 */ + struct in6_addr a6; /* 0 16 */ + }; + union tcp_word_hdr { + struct tcphdr hdr; /* 0 20 */ + __be32 words[5]; /* 0 20 */ + }; + union tcp_cc_info { + struct tcpvegas_info vegas; /* 0 16 */ + struct tcp_dctcp_info dctcp; /* 0 16 */ + struct tcp_bbr_info bbr; /* 0 20 */ + }; + $ + + What are the biggest unions in terms of number of members? + + $ pahole --unions --nr_members | sort -k2 -nr | head + security_list_options 218 + aml_resource 36 + acpi_resource_data 29 + acpi_operand_object 26 + iwreq_data 18 + sctp_params 15 + ib_flow_spec 14 + ethtool_flow_union 14 + pptp_ctrl_union 13 + bpf_attr 12 + $ + + If you want to script most of the queries can change the separator: + + $ pahole --unions --nr_members -t, | sort -t, -k2 -nr | head + security_list_options,218 + aml_resource,36 + acpi_resource_data,29 + acpi_operand_object,26 + iwreq_data,18 + sctp_params,15 + ib_flow_spec,14 + ethtool_flow_union,14 + pptp_ctrl_union,13 + bpf_attr,12 + $ + + - Fix -m/--nr_methods - Number of functions operating on a type pointer + + We had to use the same hack as in pfunct, as implemented in ccf3eebfcd9c + ("btf_loader: Add support for BTF_KIND_FUNC"), will hide that 'struct + ftype' (aka function prototype) indirection behind the parameter + iterator (function__for_each_parameter). + + For now, here is the top 10 Linux kernel data structures in terms of + number of functions receiving as one of its parameters a pointer to it, + using /sys/kernel/btf/vmlinux to look at all the vmlinux types and + functions (the ones visible in kallsyms, but with the parameters and its + types): + + $ pahole -m | sort -k2 -nr | head + device 955 + sock 568 + sk_buff 541 + task_struct 437 + inode 421 + pci_dev 390 + page 351 + net_device 347 + file 315 + net 312 + $ + $ pahole --help |& grep -- -m + -m, --nr_methods show number of methods + $ + + - Do not require a class name to operate without a file name + + Since we default to operating on the running kernel data structures, we + should make the default to, with no options passed, to pretty print all + the running kernel data structures, or do what was asked in terms of + number of members, size of structs, etc, i.e.: + + # pahole --help |& head + Usage: pahole [OPTION...] FILE + + -a, --anon_include include anonymous classes + -A, --nested_anon_include include nested (inside other structs) anonymous + classes + -B, --bit_holes=NR_HOLES Show only structs at least NR_HOLES bit holes + -c, --cacheline_size=SIZE set cacheline size to SIZE + --classes_as_structs Use 'struct' when printing classes + -C, --class_name=CLASS_NAME Show just this class + -d, --recursive recursive mode, affects several other flags + # + + Continues working as before, but if you do: + + pahole + + It will work just as if you did: + + pahole vmlinux + + and that vmlinux file is the running kernel vmlinux. + + And since the default now is to read BTF info, then it will do all its + operations on /sys/kernel/btf/vmlinux, when present, i.e. want to know + what are the fattest data structures in the running kernel: + + # pahole -s | sort -k2 -nr | head + cmp_data 290904 1 + dec_data 274520 1 + cpu_entry_area 217088 0 + pglist_data 172928 4 + saved_cmdlines_buffer 131104 1 + debug_store_buffers 131072 0 + hid_parser 110848 1 + hid_local 110608 0 + zonelist 81936 0 + e820_table 64004 0 + # + + How many data structures in the running kernel vmlinux area embbed + 'struct list_head'? + + # pahole -i list_head | wc -l + 260 + # + + Lets see some of those? + + # pahole -C fsnotify_event + struct fsnotify_event { + struct list_head list; /* 0 16 */ + struct inode * inode; /* 16 8 */ + + /* size: 24, cachelines: 1, members: 2 */ + /* last cacheline: 24 bytes */ + }; + # pahole -C audit_chunk + struct audit_chunk { + struct list_head hash; /* 0 16 */ + long unsigned int key; /* 16 8 */ + struct fsnotify_mark * mark; /* 24 8 */ + struct list_head trees; /* 32 16 */ + int count; /* 48 4 */ + + /* XXX 4 bytes hole, try to pack */ + + atomic_long_t refs; /* 56 8 */ + /* --- cacheline 1 boundary (64 bytes) --- */ + struct callback_head head; /* 64 16 */ + struct node owners[]; /* 80 0 */ + + /* size: 80, cachelines: 2, members: 8 */ + /* sum members: 76, holes: 1, sum holes: 4 */ + /* last cacheline: 16 bytes */ + }; + # diff --git a/changes-v1.18 b/changes-v1.18 new file mode 100644 index 0000000..9b52efa --- /dev/null +++ b/changes-v1.18 @@ -0,0 +1,117 @@ +v1.18: + +- Use type information to pretty print raw data from stdin, all + documented in the man pages, further information in the csets. + + TLDRish: this almost completely deciphers a perf.data file: + + $ pahole ~/bin/perf --header=perf_file_header \ + -C 'perf_file_attr(range=attrs),perf_event_header(range=data,sizeof,type,type_enum=perf_event_type+perf_user_event_type)' < perf.data + + What the above command line does: + + This will state that a 'struct perf_file_header' is available in BTF or DWARF + in the ~/bin/perf file and that at the start of stdin it should be used to decode + sizeof(struct perf_file_header) bytes, pretty printing it according to its members. + + Furthermore, that header can be referenced later in the command line, for instance + that 'range=data' means that in the header, it expects a 'range' member in + 'struct perf_file_header' to be used: + + $ pahole ~/bin/perf --header=perf_file_header < perf.data + { + .magic = 3622385352885552464, + .size = 104, + .attr_size = 136, + .attrs = { + .offset = 296, + .size = 136, + }, + .data = { + .offset = 432, + .size = 14688, + }, + .event_types = { + .offset = 0, + .size = 0, + }, + .adds_features = { 376537084, 0, 0, 0 }, + }, + $ + + That 'range' field is expected to have 'offset' and 'size' fields, so that + it can go on decoding a number of 'struct perf_event_header' entries. + + That 'sizeof' in turn expects that in 'struct perf_event_header' there is a + 'size' field stating how long that particular record is, one can also use + 'sizeof=some_other_member_name'. + + This supports variable sized records and then the 'type' field expects there + is a 'struct perf_event_type' member named 'type' (again, type=something_else + may be used. Finally, the value in the 'type' field is used to lookup an entry + in the set formed by the two enumerations specified in the 'type_enum=' argument. + + If we look at these enums we'll see that its entries have names that can be, + when lowercased, point to structs containing the layout for the variable sized + record, which allows it to cast and produce the right pretty printed output. + + I.e. using the kernel BTF info we get: + + $ pahole perf_event_type + enum perf_event_type { + PERF_RECORD_MMAP = 1, + PERF_RECORD_LOST = 2, + PERF_RECORD_COMM = 3, + PERF_RECORD_EXIT = 4, + PERF_RECORD_THROTTLE = 5, + PERF_RECORD_UNTHROTTLE = 6, + PERF_RECORD_FORK = 7, + PERF_RECORD_READ = 8, + PERF_RECORD_SAMPLE = 9, + PERF_RECORD_MMAP2 = 10, + PERF_RECORD_AUX = 11, + PERF_RECORD_ITRACE_START = 12, + PERF_RECORD_LOST_SAMPLES = 13, + PERF_RECORD_SWITCH = 14, + PERF_RECORD_SWITCH_CPU_WIDE = 15, + PERF_RECORD_NAMESPACES = 16, + PERF_RECORD_KSYMBOL = 17, + PERF_RECORD_BPF_EVENT = 18, + PERF_RECORD_CGROUP = 19, + PERF_RECORD_TEXT_POKE = 20, + PERF_RECORD_MAX = 21, + }; + $ + + That is the same as in ~/bin/perf, and, if we get one of these and ask for + that struct: + + $ pahole -C perf_record_mmap ~/bin/perf + struct perf_record_mmap { + struct perf_event_header header; /* 0 8 */ + __u32 pid; /* 8 4 */ + __u32 tid; /* 12 4 */ + __u64 start; /* 16 8 */ + __u64 len; /* 24 8 */ + __u64 pgoff; /* 32 8 */ + char filename[4096]; /* 40 4096 */ + + /* size: 4136, cachelines: 65, members: 7 */ + /* last cacheline: 40 bytes */ + }; + $ + + Many other options were introduced to work with this, including --count, + --skip, etc, look at the man page for details. + +- Store percpu variables in vmlinux BTF. This can be disabled when debugging + kernel features being developed to use it. + +- pahole now should be segfault free when handling gdb test suit DWARF + files, including ADA, FORTRAN, rust and dwp compressed files, the + later being just flatly refused, that got left for v1.19. + +- Bail out on partial units for now, avoiding segfaults and providing warning + to user, hopefully will be addressed in v1.19. + +Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com> diff --git a/changes-v1.19 b/changes-v1.19 new file mode 100644 index 0000000..9861654 --- /dev/null +++ b/changes-v1.19 @@ -0,0 +1,134 @@ +v1.19: + +- Support split BTF, where a main BTF file, vmlinux, can be used to find types + and then a kernel module, for instance, can have just what is unique to it. + + For instance, looking for a type in the main vmlinux BTF info: + + $ pahole wmi_notify_handler + pahole: type 'wmi_notify_handler' not found + $ + + If we look at the 'wmi' module BTF info that is in: + + $ ls -la /sys/kernel/btf/wmi + -r--r--r--. 1 root root 2866 Nov 18 13:35 /sys/kernel/btf/wmi + $ + + $ pahole /sys/kernel/btf/wmi -C wmi_notify_handler + typedef void (*wmi_notify_handler)(u32, void *); + $ + + '--btf_base=/sys/kernel/btf/vmlinux' was automatically added in this last + example, an option that was also introduced in this version where types used in + the wmi.ko module but present in vmlinux can be found so that there is no + duplicity of types. + +- Update libbpf to get the split BTF support and use some of its functions to + load BTF and speed up DWARF loading and BTF encoding. + +- Support cross-compiled ELF binaries with different endianness + +- Support showing typedefs for anonymous types, like structs, unions and enums, + see the "Align enumerators" entry below for an example, another: + + $ pahole rwlock_t + typedef struct { + arch_rwlock_t raw_lock; /* 0 8 */ + + /* size: 8, cachelines: 1, members: 1 */ + /* last cacheline: 8 bytes */ + } rwlock_t; + $ + +- Align enumerators: + + $ pahole ZSTD_strategy + typedef enum { + ZSTD_fast = 0, + ZSTD_dfast = 1, + ZSTD_greedy = 2, + ZSTD_lazy = 3, + ZSTD_lazy2 = 4, + ZSTD_btlazy2 = 5, + ZSTD_btopt = 6, + ZSTD_btopt2 = 7, + } ZSTD_strategy; + $ + +- Workaround bugs in the generation of DWARF records for functions in some gcc + versions that were causing breakage in the encoding of BTF: + + https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97060 "Missing DW_AT_declaration=1 in dwarf data" + +- Ignore zero-sized ELF symbols instead of erroring out. + +- Handle union forward declaration properly in the BTF loader. + +- Introduce --numeric_version for use in scripts and Makefiles: + + $ pahole --version + v1.19 + $ pahole --numeric_version + 119 + $ + + To avoid things like this in the kernel's scripts/link-vmlinux.sh: + + pahole_ver=$(${PAHOLE} --version | sed -E 's/v([0-9]+)\.([0-9]+)/\1\2/') + +- Try sole pfunct argument as a function name, just like pahole with type names: + + $ pfunct tcp_v4_rcv + int tcp_v4_rcv(struct sk_buff * skb); + $ + +- Speed up pfunct using some of the load techniques used in pahole. + +- Discard CUs after BTF encoding as they're not used anymore, greatly reducing + memory usage and speeding up vmlinux BTF encoding. + +- Revamp how per-CPU variables are encoded in BTF. + +- Include BTF info for static functions. + +- Use BTF's string APIs for strings management, greatly improving performance + over the tsearch(). + +- Increase size of DWARF lookup hash table, shaving off about 1 second out of + about 20 seconds total for Linux BTF dedup. + +- Stop BTF encoding when errors are found in some DWARF CU. + +- Implement --packed, to show just packed structures, for instance, here are + the top 5 packed data structures in the Linux kernel: + + $ pahole --sizes --packed | sort -k2 -nr | head -5 + e820_table 64004 0 + boot_params 4096 0 + efi_variable 2084 0 + snd_soc_tplg_pcm 912 0 + ntb_info_regs 800 0 + $ + + And here is one of them: + + $ pahole efi_variable + struct efi_variable { + efi_char16_t VariableName[512]; /* 0 1024 */ + /* --- cacheline 16 boundary (1024 bytes) --- */ + efi_guid_t VendorGuid; /* 1024 16 */ + long unsigned int DataSize; /* 1040 8 */ + __u8 Data[1024]; /* 1048 1024 */ + /* --- cacheline 32 boundary (2048 bytes) was 24 bytes ago --- */ + efi_status_t Status; /* 2072 8 */ + __u32 Attributes; /* 2080 4 */ + + /* size: 2084, cachelines: 33, members: 6 */ + /* last cacheline: 36 bytes */ + } __attribute__((__packed__)); + $ + +- Fix bug in distros such as OpenSUSE:15.2 where DW_AT_alignment isn't defined. + +Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com> diff --git a/cmake/modules/FindDWARF.cmake b/cmake/modules/FindDWARF.cmake new file mode 100644 index 0000000..027d06e --- /dev/null +++ b/cmake/modules/FindDWARF.cmake @@ -0,0 +1,101 @@ +# - Find Dwarf +# Find the dwarf.h header from elf utils +# +# DWARF_INCLUDE_DIR - where to find dwarf.h, etc. +# DWARF_LIBRARIES - List of libraries when using elf utils. +# DWARF_FOUND - True if fdo found. + +message(STATUS "Checking availability of DWARF and ELF development libraries") + +INCLUDE(CheckLibraryExists) + +if (DWARF_INCLUDE_DIR AND LIBDW_INCLUDE_DIR AND DWARF_LIBRARY AND ELF_LIBRARY) + # Already in cache, be silent + set(DWARF_FIND_QUIETLY TRUE) +endif (DWARF_INCLUDE_DIR AND LIBDW_INCLUDE_DIR AND DWARF_LIBRARY AND ELF_LIBRARY) + +find_path(DWARF_INCLUDE_DIR dwarf.h + /usr/include + /usr/local/include + /usr/include/libdwarf + ~/usr/local/include +) + +find_path(LIBDW_INCLUDE_DIR elfutils/libdw.h + /usr/include + /usr/local/include + ~/usr/local/include +) + +find_library(DWARF_LIBRARY + NAMES dw dwarf + PATHS /usr/lib /usr/local/lib /usr/lib64 /usr/local/lib64 ~/usr/local/lib ~/usr/local/lib64 +) + +find_library(ELF_LIBRARY + NAMES elf + PATHS /usr/lib /usr/local/lib /usr/lib64 /usr/local/lib64 ~/usr/local/lib ~/usr/local/lib64 +) + +if (DWARF_INCLUDE_DIR AND LIBDW_INCLUDE_DIR AND DWARF_LIBRARY AND ELF_LIBRARY) + set(DWARF_FOUND TRUE) + set(DWARF_LIBRARIES ${DWARF_LIBRARY} ${ELF_LIBRARY}) + + set(CMAKE_REQUIRED_LIBRARIES ${DWARF_LIBRARIES}) + # check if libdw have the dwfl_module_build_id routine, i.e. if it supports the buildid + # mechanism to match binaries to detached debug info sections (the -debuginfo packages + # in distributions such as fedora). We do it against libelf because, IIRC, some distros + # include libdw linked statically into libelf. + check_library_exists(elf dwfl_module_build_id "" HAVE_DWFL_MODULE_BUILD_ID) +else (DWARF_INCLUDE_DIR AND LIBDW_INCLUDE_DIR AND DWARF_LIBRARY AND ELF_LIBRARY) + set(DWARF_FOUND FALSE) + set(DWARF_LIBRARIES) +endif (DWARF_INCLUDE_DIR AND LIBDW_INCLUDE_DIR AND DWARF_LIBRARY AND ELF_LIBRARY) + +if (DWARF_FOUND) + if (NOT DWARF_FIND_QUIETLY) + message(STATUS "Found dwarf.h header: ${DWARF_INCLUDE_DIR}") + message(STATUS "Found elfutils/libdw.h header: ${LIBDW_INCLUDE_DIR}") + message(STATUS "Found libdw library: ${DWARF_LIBRARY}") + message(STATUS "Found libelf library: ${ELF_LIBRARY}") + endif (NOT DWARF_FIND_QUIETLY) +else (DWARF_FOUND) + if (DWARF_FIND_REQUIRED) + # Check if we are in a Red Hat (RHEL) or Fedora system to tell + # exactly which packages should be installed. Please send + # patches for other distributions. + find_path(FEDORA fedora-release /etc) + find_path(REDHAT redhat-release /etc) + if (FEDORA OR REDHAT) + if (NOT DWARF_INCLUDE_DIR OR NOT LIBDW_INCLUDE_DIR) + message(STATUS "Please install the elfutils-devel package") + endif (NOT DWARF_INCLUDE_DIR OR NOT LIBDW_INCLUDE_DIR) + if (NOT DWARF_LIBRARY) + message(STATUS "Please install the elfutils-libs package") + endif (NOT DWARF_LIBRARY) + if (NOT ELF_LIBRARY) + message(STATUS "Please install the elfutils-libelf package") + endif (NOT ELF_LIBRARY) + else (FEDORA OR REDHAT) + if (NOT DWARF_INCLUDE_DIR) + message(STATUS "Could NOT find dwarf include dir") + endif (NOT DWARF_INCLUDE_DIR) + if (NOT LIBDW_INCLUDE_DIR) + message(STATUS "Could NOT find libdw include dir") + endif (NOT LIBDW_INCLUDE_DIR) + if (NOT DWARF_LIBRARY) + message(STATUS "Could NOT find libdw library") + endif (NOT DWARF_LIBRARY) + if (NOT ELF_LIBRARY) + message(STATUS "Could NOT find libelf library") + endif (NOT ELF_LIBRARY) + endif (FEDORA OR REDHAT) + message(FATAL_ERROR "Could NOT find some ELF and DWARF libraries, please install the missing packages") + endif (DWARF_FIND_REQUIRED) +endif (DWARF_FOUND) + +mark_as_advanced(DWARF_INCLUDE_DIR LIBDW_INCLUDE_DIR DWARF_LIBRARY ELF_LIBRARY) +include_directories(${DWARF_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR}) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_SOURCE_DIR}/config.h) + +message(STATUS "Checking availability of DWARF and ELF development libraries - done") diff --git a/codiff.c b/codiff.c new file mode 100644 index 0000000..264b0af --- /dev/null +++ b/codiff.c @@ -0,0 +1,859 @@ +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2006 Mandriva Conectiva S.A. + Copyright (C) 2006 Arnaldo Carvalho de Melo <acme@mandriva.com> +*/ + +#include <argp.h> +#include <assert.h> +#include <dwarf.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "dwarves.h" +#include "dutil.h" + +static int show_struct_diffs; +static int show_function_diffs; +static int verbose; +static int quiet; +static int show_terse_type_changes; + +static struct conf_load conf_load = { + .get_addr_info = true, +}; + +static struct strlist *structs_printed; + +#define TCHANGEF__SIZE (1 << 0) +#define TCHANGEF__NR_MEMBERS (1 << 1) +#define TCHANGEF__TYPE (1 << 2) +#define TCHANGEF__OFFSET (1 << 3) +#define TCHANGEF__BIT_OFFSET (1 << 4) +#define TCHANGEF__BIT_SIZE (1 << 5) +#define TCHANGEF__PADDING (1 << 6) +#define TCHANGEF__NR_HOLES (1 << 7) +#define TCHANGEF__NR_BIT_HOLES (1 << 8) + +static uint32_t terse_type_changes; + +static uint32_t total_cus_changed; +static uint32_t total_nr_functions_changed; +static uint32_t total_function_bytes_added; +static uint32_t total_function_bytes_removed; + +struct diff_info { + const struct tag *tag; + const struct cu *cu; + int32_t diff; +}; + +static struct diff_info *diff_info__new(const struct tag *twin, + const struct cu *cu, + int32_t diff) +{ + struct diff_info *dinfo = malloc(sizeof(*dinfo)); + + if (dinfo == NULL) { + puts("out of memory!"); + exit(1); + } + dinfo->tag = twin; + dinfo->cu = cu; + dinfo->diff = diff; + return dinfo; +} + +static void cu__check_max_len_changed_item(struct cu *cu, const char *name, + uint8_t addend) +{ + const uint32_t len = strlen(name) + addend; + + if (len > cu->max_len_changed_item) + cu->max_len_changed_item = len; +} + +static void diff_function(const struct cu *new_cu, struct function *function, + struct cu *cu) +{ + struct tag *new_tag; + const char *name; + + if (function->inlined || function->abstract_origin != 0) + return; + + name = function__name(function, cu); + new_tag = cu__find_function_by_name(new_cu, name); + if (new_tag != NULL) { + struct function *new_function = tag__function(new_tag); + int32_t diff = (function__size(new_function) - + function__size(function)); + if (diff != 0) { + function->priv = diff_info__new(&new_function->proto.tag, new_cu, + diff); + cu__check_max_len_changed_item(cu, name, 0); + + ++cu->nr_functions_changed; + if (diff > 0) + cu->function_bytes_added += diff; + else + cu->function_bytes_removed += -diff; + } else { + char proto[1024], twin_proto[1024]; + + if (strcmp(function__prototype(function, cu, + proto, sizeof(proto)), + function__prototype(new_function, new_cu, + twin_proto, + sizeof(twin_proto))) != 0) { + ++cu->nr_functions_changed; + function->priv = diff_info__new(function__tag(new_function), + new_cu, 0); + } + } + } else { + const uint32_t diff = -function__size(function); + + cu__check_max_len_changed_item(cu, name, 0); + function->priv = diff_info__new(NULL, NULL, diff); + ++cu->nr_functions_changed; + cu->function_bytes_removed += -diff; + } +} + +static int check_print_change(const struct class_member *old, + const struct cu *old_cu, + const struct class_member *new, + const struct cu *new_cu, + int print) +{ + size_t old_size, new_size; + char old_type_name[128], new_type_name[128]; + const struct tag *old_type = cu__type(old_cu, old->tag.type); + const struct tag *new_type = cu__type(new_cu, new->tag.type); + int changes = 0; + + if (old_type == NULL || new_type == NULL) + return 0; + + old_size = old->byte_size; + new_size = new->byte_size; + if (old_size != new_size) + changes = 1; + + if (old->byte_offset != new->byte_offset) { + changes = 1; + terse_type_changes |= TCHANGEF__OFFSET; + } + + if (old->bitfield_offset != new->bitfield_offset) { + changes = 1; + terse_type_changes |= TCHANGEF__BIT_OFFSET; + } + + if (old->bitfield_size != new->bitfield_size) { + changes = 1; + terse_type_changes |= TCHANGEF__BIT_SIZE; + } + + if (strcmp(tag__name(old_type, old_cu, old_type_name, + sizeof(old_type_name), NULL), + tag__name(new_type, new_cu, new_type_name, + sizeof(new_type_name), NULL)) != 0) { + changes = 1; + terse_type_changes |= TCHANGEF__TYPE; + } + + if (changes && print && !show_terse_type_changes) + printf(" %s\n" + " from: %-21s /* %5u(%2u) %5zd(%2d) */\n" + " to: %-21s /* %5u(%2u) %5zd(%2u) */\n", + class_member__name(old, old_cu), + old_type_name, old->byte_offset, old->bitfield_offset, + old_size, old->bitfield_size, + new_type_name, new->byte_offset, new->bitfield_offset, + new_size, new->bitfield_size); + + return changes; +} + +static struct class_member *class__find_pair_member(const struct class *structure, const struct cu *cu, + const struct class_member *pair_member, const struct cu *pair_cu, + int *nr_anonymousp) +{ + const char *member_name = class_member__name(pair_member, pair_cu); + struct class_member *member; + + if (member_name) + return class__find_member_by_name(structure, cu, member_name); + + int nr_anonymous = ++*nr_anonymousp; + + /* Unnamed struct or union, lets look for the first unammed matchin tag.type */ + + type__for_each_member(&structure->type, member) { + if (member->tag.tag == pair_member->tag.tag && /* Both are class/union/struct (unnamed) */ + class_member__name(member, cu) == member_name && /* Both are NULL? */ + --nr_anonymous == 0) + return member; + } + + return NULL; +} + +static int check_print_members_changes(const struct class *structure, + const struct cu *cu, + const struct class *new_structure, + const struct cu *new_cu, + int print) +{ + int changes = 0, nr_anonymous = 0; + struct class_member *member; + uint16_t nr_twins_found = 0; + + type__for_each_member(&structure->type, member) { + struct class_member *twin = class__find_pair_member(new_structure, new_cu, member, cu, &nr_anonymous); + if (twin != NULL) { + twin->tag.visited = 1; + ++nr_twins_found; + if (check_print_change(member, cu, twin, new_cu, print)) + changes = 1; + } else { + changes = 1; + if (print) { + char name[128]; + struct tag *type; + type = cu__type(cu, member->tag.type); + printf(" %s\n" + " removed: %-21s /* %5u(%2u) %5zd(%2d) */\n", + class_member__name(member, cu), + tag__name(type, cu, name, sizeof(name), NULL), + member->byte_offset, member->bitfield_offset, + member->byte_size, member->bitfield_size); + } + } + } + + if (nr_twins_found == (new_structure->type.nr_members + + new_structure->type.nr_static_members)) + goto out; + + changes = 1; + if (!print) + goto out; + + type__for_each_member(&new_structure->type, member) { + if (!member->tag.visited) { + char name[128]; + struct tag *type; + type = cu__type(new_cu, member->tag.type); + printf(" %s\n" + " added: %-21s /* %5u(%2u) %5zd(%2d) */\n", + class_member__name(member, new_cu), + tag__name(type, new_cu, name, sizeof(name), NULL), + member->byte_offset, member->bitfield_offset, + member->byte_size, member->bitfield_size); + } + } +out: + return changes; +} + +static void diff_struct(const struct cu *new_cu, struct class *structure, + struct cu *cu) +{ + struct tag *new_tag; + struct class *new_structure = NULL; + int32_t diff; + + assert(class__is_struct(structure)); + + if (class__size(structure) == 0 || class__name(structure, cu) == NULL) + return; + + new_tag = cu__find_struct_by_name(new_cu, + class__name(structure, cu), 0, NULL); + if (new_tag == NULL) + return; + + new_structure = tag__class(new_tag); + if (class__size(new_structure) == 0) + return; + + assert(class__is_struct(new_structure)); + + diff = class__size(structure) != class__size(new_structure) || + class__nr_members(structure) != class__nr_members(new_structure) || + check_print_members_changes(structure, cu, + new_structure, new_cu, 0) || + structure->padding != new_structure->padding || + structure->nr_holes != new_structure->nr_holes || + structure->nr_bit_holes != new_structure->nr_bit_holes; + + if (diff == 0) + return; + + ++cu->nr_structures_changed; + cu__check_max_len_changed_item(cu, class__name(structure, cu), + sizeof("struct")); + structure->priv = diff_info__new(class__tag(new_structure), + new_cu, diff); +} + +static struct cu *cus__find_pair(struct cus *cus, const char *name) +{ + if (cus->nr_entries == 1) + return list_first_entry(&cus->cus, struct cu, node); + + return cus__find_cu_by_name(cus, name); +} + +static int cu_find_new_tags_iterator(struct cu *new_cu, void *old_cus) +{ + struct cu *old_cu = cus__find_pair(old_cus, new_cu->name); + + if (old_cu != NULL && cu__same_build_id(old_cu, new_cu)) + return 0; + + struct function *function; + uint32_t id; + cu__for_each_function(new_cu, id, function) { + /* + * We're not interested in aliases, just real function definitions, + * where we'll know if the kind of inlining + */ + if (function->abstract_origin || function->inlined) + continue; + + const char *name = function__name(function, new_cu); + struct tag *old_function = cu__find_function_by_name(old_cu, + name); + if (old_function != NULL && !tag__function(old_function)->inlined) + continue; + + const int32_t diff = function__size(function); + + cu__check_max_len_changed_item(new_cu, name, 0); + ++new_cu->nr_functions_changed; + new_cu->function_bytes_added += diff; + function->priv = diff_info__new(old_function, new_cu, diff); + } + + struct class *class; + cu__for_each_struct(new_cu, id, class) { + const char *name = class__name(class, new_cu); + if (name == NULL || class__size(class) == 0 || + cu__find_struct_by_name(old_cu, name, 0, NULL)) + continue; + + class->priv = diff_info__new(NULL, NULL, 1); + ++new_cu->nr_structures_changed; + + cu__check_max_len_changed_item(new_cu, name, sizeof("struct")); + } + + return 0; +} + +static int cu_diff_iterator(struct cu *cu, void *new_cus) +{ + struct cu *new_cu = cus__find_pair(new_cus, cu->name); + + if (new_cu != NULL && cu__same_build_id(cu, new_cu)) + return 0; + + uint32_t id; + struct class *class; + cu__for_each_struct(cu, id, class) + diff_struct(new_cu, class, cu); + + struct function *function; + cu__for_each_function(cu, id, function) + diff_function(new_cu, function, cu); + + return 0; +} + +static void show_diffs_function(struct function *function, const struct cu *cu, + const void *cookie) +{ + const struct diff_info *di = function->priv; + + printf(" %-*.*s | %+4d", + (int)cu->max_len_changed_item, (int)cu->max_len_changed_item, + function__name(function, cu), di->diff); + + if (!verbose) { + putchar('\n'); + return; + } + + if (di->tag == NULL) + puts(cookie ? " (added)" : " (removed)"); + else { + struct function *twin = tag__function(di->tag); + + if (twin->inlined) + puts(cookie ? " (uninlined)" : " (inlined)"); + else if (strcmp(function__name(function, cu), + function__name(twin, di->cu)) != 0) + printf("%s: BRAIN FART ALERT: comparing %s to %s, " + "should be the same name\n", __FUNCTION__, + function__name(function, cu), + function__name(twin, di->cu)); + else { + char proto[1024], twin_proto[1024]; + + printf(" # %d -> %d", function__size(function), + function__size(twin)); + if (function->lexblock.nr_lexblocks != + twin->lexblock.nr_lexblocks) + printf(", lexblocks: %d -> %d", + function->lexblock.nr_lexblocks, + twin->lexblock.nr_lexblocks); + if (function->lexblock.nr_inline_expansions != + twin->lexblock.nr_inline_expansions) + printf(", # inlines: %d -> %d", + function->lexblock.nr_inline_expansions, + twin->lexblock.nr_inline_expansions); + if (function->lexblock.size_inline_expansions != + twin->lexblock.size_inline_expansions) + printf(", size inlines: %d -> %d", + function->lexblock.size_inline_expansions, + twin->lexblock.size_inline_expansions); + + if (strcmp(function__prototype(function, cu, + proto, sizeof(proto)), + function__prototype(twin, di->cu, + twin_proto, sizeof(twin_proto))) != 0) + printf(", prototype: %s -> %s", proto, twin_proto); + putchar('\n'); + } + } +} + +static void show_changed_member(char change, const struct class_member *member, + const struct cu *cu) +{ + const struct tag *type = cu__type(cu, member->tag.type); + char bf[128]; + + tag__assert_search_result(type); + printf(" %c%-26s %-21s /* %5u %5zd */\n", + change, tag__name(type, cu, bf, sizeof(bf), NULL), + class_member__name(member, cu), + member->byte_offset, member->byte_size); +} + +static void show_nr_members_changes(const struct class *structure, + const struct cu *cu, + const struct class *new_structure, + const struct cu *new_cu) +{ + struct class_member *member; + int nr_anonymous = 0; + + /* Find the removed ones */ + type__for_each_member(&structure->type, member) { + struct class_member *twin = class__find_pair_member(new_structure, new_cu, member, cu, &nr_anonymous); + if (twin == NULL) + show_changed_member('-', member, cu); + } + + nr_anonymous = 0; + /* Find the new ones */ + type__for_each_member(&new_structure->type, member) { + struct class_member *twin = class__find_pair_member(structure, cu, member, new_cu, &nr_anonymous); + if (twin == NULL) + show_changed_member('+', member, new_cu); + } +} + +static void print_terse_type_changes(struct class *structure, + const struct cu *cu) +{ + const char *sep = ""; + + printf("struct %s: ", class__name(structure, cu)); + + if (terse_type_changes & TCHANGEF__SIZE) { + fputs("size", stdout); + sep = ", "; + } + if (terse_type_changes & TCHANGEF__NR_MEMBERS) { + printf("%snr_members", sep); + sep = ", "; + } + if (terse_type_changes & TCHANGEF__TYPE) { + printf("%stype", sep); + sep = ", "; + } + if (terse_type_changes & TCHANGEF__OFFSET) { + printf("%soffset", sep); + sep = ", "; + } + if (terse_type_changes & TCHANGEF__BIT_OFFSET) { + printf("%sbit_offset", sep); + sep = ", "; + } + if (terse_type_changes & TCHANGEF__BIT_SIZE) { + printf("%sbit_size", sep); + sep = ", "; + } + if (terse_type_changes & TCHANGEF__PADDING) { + printf("%spadding", sep); + sep = ", "; + } + if (terse_type_changes & TCHANGEF__NR_HOLES) { + printf("%snr_holes", sep); + sep = ", "; + } + if (terse_type_changes & TCHANGEF__NR_BIT_HOLES) + printf("%snr_bit_holes", sep); + + putchar('\n'); +} + +static void show_diffs_structure(struct class *structure, + const struct cu *cu) +{ + const struct diff_info *di = structure->priv; + const struct class *new_structure; + int diff; + /* + * This is when the struct was not present in the new object file. + * Meaning that it either was not referenced or that it was completely + * removed. + */ + if (di == NULL) + return; + + new_structure = tag__class(di->tag); + /* + * If there is a diff_info but its di->tag is NULL we have a new structure, + * one that didn't appears in the old object. See find_new_classes_iterator. + */ + if (new_structure == NULL) + diff = class__size(structure); + else + diff = class__size(new_structure) - class__size(structure); + + terse_type_changes = 0; + + if (!show_terse_type_changes) + printf(" struct %-*.*s | %+4d\n", + (int)(cu->max_len_changed_item - sizeof("struct")), + (int)(cu->max_len_changed_item - sizeof("struct")), + class__name(structure, cu), diff); + + if (diff != 0) + terse_type_changes |= TCHANGEF__SIZE; + + if (!verbose && !show_terse_type_changes) + return; + + if (new_structure == NULL) + diff = -class__nr_members(structure); + else + diff = (class__nr_members(new_structure) - + class__nr_members(structure)); + if (diff != 0) { + terse_type_changes |= TCHANGEF__NR_MEMBERS; + if (!show_terse_type_changes) { + printf(" nr_members: %+d\n", diff); + if (new_structure != NULL) + show_nr_members_changes(structure, cu, + new_structure, di->cu); + } + } + if (new_structure != NULL) { + diff = (int)new_structure->padding - (int)structure->padding; + if (diff) { + terse_type_changes |= TCHANGEF__PADDING; + if (!show_terse_type_changes) + printf(" padding: %+d\n", diff); + } + diff = (int)new_structure->nr_holes - (int)structure->nr_holes; + if (diff) { + terse_type_changes |= TCHANGEF__NR_HOLES; + if (!show_terse_type_changes) + printf(" nr_holes: %+d\n", diff); + } + diff = ((int)new_structure->nr_bit_holes - + (int)structure->nr_bit_holes); + if (structure->nr_bit_holes != new_structure->nr_bit_holes) { + terse_type_changes |= TCHANGEF__NR_BIT_HOLES; + if (!show_terse_type_changes) + printf(" nr_bit_holes: %+d\n", diff); + } + check_print_members_changes(structure, cu, + new_structure, di->cu, 1); + } + if (show_terse_type_changes) + print_terse_type_changes(structure, cu); +} + +static void show_structure_diffs_iterator(struct class *class, struct cu *cu) +{ + if (class->priv != NULL) { + const char *name = class__name(class, cu); + if (!strlist__has_entry(structs_printed, name)) { + show_diffs_structure(class, cu); + strlist__add(structs_printed, name); + } + } +} + +static int cu_show_diffs_iterator(struct cu *cu, void *cookie) +{ + static int first_cu_printed; + + if (cu->nr_functions_changed == 0 && + cu->nr_structures_changed == 0) + return 0; + + if (first_cu_printed) { + if (!quiet) + putchar('\n'); + } else { + first_cu_printed = 1; + } + + ++total_cus_changed; + + if (!quiet) + printf("%s:\n", cu->name); + + uint32_t id; + struct class *class; + + if (show_terse_type_changes) { + cu__for_each_struct(cu, id, class) + show_structure_diffs_iterator(class, cu); + return 0; + } + + if (cu->nr_structures_changed != 0 && show_struct_diffs) { + cu__for_each_struct(cu, id, class) + show_structure_diffs_iterator(class, cu); + printf(" %u struct%s changed\n", cu->nr_structures_changed, + cu->nr_structures_changed > 1 ? "s" : ""); + } + + if (cu->nr_functions_changed != 0 && show_function_diffs) { + total_nr_functions_changed += cu->nr_functions_changed; + + struct function *function; + cu__for_each_function(cu, id, function) { + if (function->priv != NULL) + show_diffs_function(function, cu, cookie); + } + + printf(" %u function%s changed", cu->nr_functions_changed, + cu->nr_functions_changed > 1 ? "s" : ""); + if (cu->function_bytes_added != 0) { + total_function_bytes_added += cu->function_bytes_added; + printf(", %zd bytes added", cu->function_bytes_added); + } + if (cu->function_bytes_removed != 0) { + total_function_bytes_removed += cu->function_bytes_removed; + printf(", %zd bytes removed", + cu->function_bytes_removed); + } + printf(", diff: %+zd", + cu->function_bytes_added - cu->function_bytes_removed); + putchar('\n'); + } + return 0; +} + +static int cu_delete_priv(struct cu *cu, void *cookie __unused) +{ + struct class *c; + struct function *f; + uint32_t id; + + cu__for_each_struct(cu, id, c) + free(c->priv); + + cu__for_each_function(cu, id, f) + free(f->priv); + + return 0; +} + +static void print_total_function_diff(const char *filename) +{ + printf("\n%s:\n", filename); + + printf(" %u function%s changed", total_nr_functions_changed, + total_nr_functions_changed > 1 ? "s" : ""); + + if (total_function_bytes_added != 0) + printf(", %u bytes added", total_function_bytes_added); + + if (total_function_bytes_removed != 0) + printf(", %u bytes removed", total_function_bytes_removed); + + printf(", diff: %+d", + (total_function_bytes_added - + total_function_bytes_removed)); + putchar('\n'); +} + +/* Name and version of program. */ +ARGP_PROGRAM_VERSION_HOOK_DEF = dwarves_print_version; + +static const struct argp_option codiff__options[] = { + { + .key = 's', + .name = "structs", + .doc = "show struct diffs", + }, + { + .key = 'f', + .name = "functions", + .doc = "show function diffs", + }, + { + .name = "format_path", + .key = 'F', + .arg = "FORMAT_LIST", + .doc = "List of debugging formats to try" + }, + { + .key = 't', + .name = "terse_type_changes", + .doc = "show terse type changes", + }, + { + .key = 'V', + .name = "verbose", + .doc = "show diffs details", + }, + { + .key = 'q', + .name = "quiet", + .doc = "Show only differences, no difference? No output", + }, + { + .name = NULL, + } +}; + +static error_t codiff__options_parser(int key, char *arg __unused, + struct argp_state *state __unused) +{ + switch (key) { + case 'f': show_function_diffs = 1; break; + case 'F': conf_load.format_path = arg; break; + case 's': show_struct_diffs = 1; break; + case 't': show_terse_type_changes = 1; break; + case 'V': verbose = 1; break; + case 'q': quiet = 1; break; + default: return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static const char codiff__args_doc[] = "OLD_FILE NEW_FILE"; + +static struct argp codiff__argp = { + .options = codiff__options, + .parser = codiff__options_parser, + .args_doc = codiff__args_doc, +}; + +int main(int argc, char *argv[]) +{ + int remaining, err, rc = EXIT_FAILURE; + char *old_filename, *new_filename; + struct stat st; + + if (argp_parse(&codiff__argp, argc, argv, 0, &remaining, NULL) || + remaining < argc) { + switch (argc - remaining) { + case 2: old_filename = argv[remaining++]; + new_filename = argv[remaining++]; break; + case 1: + default: goto failure; + } + } else { +failure: + argp_help(&codiff__argp, stderr, ARGP_HELP_SEE, argv[0]); + goto out; + } + + if (dwarves__init(0)) { + fputs("codiff: insufficient memory\n", stderr); + goto out; + } + + if (show_function_diffs == 0 && show_struct_diffs == 0 && + show_terse_type_changes == 0) + show_function_diffs = show_struct_diffs = 1; + + structs_printed = strlist__new(false); + struct cus *old_cus = cus__new(), + *new_cus = cus__new(); + if (old_cus == NULL || new_cus == NULL || structs_printed == NULL) { + fputs("codiff: insufficient memory\n", stderr); + goto out_cus_delete; + } + + if (stat(old_filename, &st) != 0) { + fprintf(stderr, "codiff: %s (%s)\n", strerror(errno), old_filename); + goto out_cus_delete; + } + + /* If old_file is a character device, leave its cus empty */ + if (!S_ISCHR(st.st_mode)) { + err = cus__load_file(old_cus, &conf_load, old_filename); + if (err != 0) { + cus__print_error_msg("codiff", old_cus, old_filename, err); + goto out_cus_delete_priv; + } + } + + if (stat(new_filename, &st) != 0) { + fprintf(stderr, "codiff: %s (%s)\n", strerror(errno), new_filename); + goto out_cus_delete_priv; + } + + /* If old_file is a character device, leave its cus empty */ + if (!S_ISCHR(st.st_mode)) { + err = cus__load_file(new_cus, &conf_load, new_filename); + if (err != 0) { + cus__print_error_msg("codiff", new_cus, new_filename, err); + goto out_cus_delete_priv; + } + } + + cus__for_each_cu(old_cus, cu_diff_iterator, new_cus, NULL); + cus__for_each_cu(new_cus, cu_find_new_tags_iterator, old_cus, NULL); + cus__for_each_cu(old_cus, cu_show_diffs_iterator, NULL, NULL); + if (new_cus->nr_entries > 1) + cus__for_each_cu(new_cus, cu_show_diffs_iterator, (void *)1, NULL); + + if (total_cus_changed > 1) { + if (show_function_diffs) + print_total_function_diff(new_filename); + } + + rc = EXIT_SUCCESS; +out_cus_delete_priv: + cus__for_each_cu(old_cus, cu_delete_priv, NULL, NULL); + cus__for_each_cu(new_cus, cu_delete_priv, NULL, NULL); +out_cus_delete: + cus__delete(old_cus); + cus__delete(new_cus); + strlist__delete(structs_printed); + dwarves__exit(); +out: + return rc; +} diff --git a/config.h.cmake b/config.h.cmake new file mode 100644 index 0000000..1b57acd --- /dev/null +++ b/config.h.cmake @@ -0,0 +1,9 @@ +/* + Copyright (C) 2007 Arnaldo Carvalho de Melo <acme@redhat.com> + + This program is free software; you can redistribute it and/or modify it + under the terms of version 2 of the GNU General Public License as + published by the Free Software Foundation. +*/ + +#cmakedefine HAVE_DWFL_MODULE_BUILD_ID @@ -0,0 +1,159 @@ +#ifndef _CTF_H +#define _CTF_H +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2019 Arnaldo Carvalho de Melo <acme@redhat.com> + */ + +#include <stdint.h> + +struct ctf_header { + uint16_t ctf_magic; /* Header magic value */ +#define CTF_MAGIC 0xcff1 +#define CTF_MAGIC_SWAP 0xf1cf + + uint8_t ctf_version; /* Header version */ +#define CTF_VERSION 2 + + uint8_t ctf_flags; /* Header flags */ +#define CTF_FLAGS_COMPR 0x01 + + uint32_t ctf_parent_label; /* Label of parent CTF object */ + uint32_t ctf_parent_name; /* Name of parent CTF object */ + + /* All offsets are in bytes are relative to the end of + * this header. + */ + uint32_t ctf_label_off; /* Offset of label section */ + uint32_t ctf_object_off; /* Offset of data object section */ + uint32_t ctf_func_off; /* Offset of function section */ + uint32_t ctf_type_off; /* Offset of type section */ + uint32_t ctf_str_off; /* Offset of string section */ + uint32_t ctf_str_len; /* Length of string section */ +}; + +#define CTF_REF_OFFSET(REF) ((REF) & 0x7fffffff) +#define CTF_REF_TBL_ID(REF) (((REF) >> 31) & 0x1) +#define CTF_STR_TBL_ID_0 0 +#define CTF_STR_TBL_ID_1 1 + +#define CTF_REF_ENCODE(TBL, OFF) (((TBL) << 31) | (OFF)) + +struct ctf_label_ent { + uint32_t ctf_label_ref; + uint32_t ctf_type_index; +}; + +/* Types are encoded with ctf_short_type so long as the ctf_size + * field can be fully represented in a uint16_t. If not, then + * the ctf_size is given the value 0xffff and ctf_full_type is + * used. + */ +struct ctf_short_type { + uint32_t ctf_name; + uint16_t ctf_info; + union { + uint16_t ctf_size; + uint16_t ctf_type; + }; +}; + +struct ctf_full_type { + struct ctf_short_type base; + uint32_t ctf_size_high; + uint32_t ctf_size_low; +}; + +#define CTF_GET_KIND(VAL) (((VAL) >> 11) & 0x1f) +#define CTF_GET_VLEN(VAL) ((VAL) & 0x3ff) +#define CTF_ISROOT(VAL) (((VAL) & 0x400) != 0) + +#define CTF_INFO_ENCODE(KIND, VLEN, ISROOT) \ + (((ISROOT) ? 0x400 : 0) | ((KIND) << 11) | (VLEN)) + +#define CTF_TYPE_KIND_UNKN 0 /* Unknown */ +#define CTF_TYPE_KIND_INT 1 /* Integer */ +#define CTF_TYPE_KIND_FLT 2 /* Float */ +#define CTF_TYPE_KIND_PTR 3 /* Pointer */ +#define CTF_TYPE_KIND_ARR 4 /* Array */ +#define CTF_TYPE_KIND_FUNC 5 /* Function */ +#define CTF_TYPE_KIND_STR 6 /* Struct */ +#define CTF_TYPE_KIND_UNION 7 /* Union */ +#define CTF_TYPE_KIND_ENUM 8 /* Enumeration */ +#define CTF_TYPE_KIND_FWD 9 /* Forward */ +#define CTF_TYPE_KIND_TYPDEF 10 /* Typedef */ +#define CTF_TYPE_KIND_VOLATILE 11 /* Volatile */ +#define CTF_TYPE_KIND_CONST 12 /* Const */ +#define CTF_TYPE_KIND_RESTRICT 13 /* Restrict */ +#define CTF_TYPE_KIND_MAX 31 + +#define CTF_TYPE_INT_ATTRS(VAL) ((VAL) >> 24) +#define CTF_TYPE_INT_OFFSET(VAL) (((VAL) >> 16) & 0xff) +#define CTF_TYPE_INT_BITS(VAL) ((VAL) & 0xffff) + +#define CTF_TYPE_INT_ENCODE(ATTRS, OFF, BITS) \ + (((ATTRS) << 24) | ((OFF) << 16) | (BITS)) + +/* Integer type attributes */ +#define CTF_TYPE_INT_SIGNED 0x1 +#define CTF_TYPE_INT_CHAR 0x2 +#define CTF_TYPE_INT_BOOL 0x4 +#define CTF_TYPE_INT_VARARGS 0x8 + +#define CTF_TYPE_FP_ATTRS(VAL) ((VAL) >> 24) +#define CTF_TYPE_FP_OFFSET(VAL) (((VAL) >> 16) & 0xff) +#define CTF_TYPE_FP_BITS(VAL) ((VAL) & 0xffff) + +#define CTF_TYPE_FP_ENCODE(ATTRS, OFF, BITS) \ + (((ATTRS) << 24) | ((OFF) << 16) | (BITS)) + +/* Possible values for the float type attribute field */ +#define CTF_TYPE_FP_SINGLE 1 +#define CTF_TYPE_FP_DOUBLE 2 +#define CTF_TYPE_FP_CMPLX 3 +#define CTF_TYPE_FP_CMPLX_DBL 4 +#define CTF_TYPE_FP_CMPLX_LDBL 5 +#define CTF_TYPE_FP_LDBL 6 +#define CTF_TYPE_FP_INTVL 7 +#define CTF_TYPE_FP_INTVL_DBL 8 +#define CTF_TYPE_FP_INTVL_LDBL 9 +#define CTF_TYPE_FP_IMGRY 10 +#define CTF_TYPE_FP_IMGRY_DBL 11 +#define CTF_TYPE_FP_IMGRY_LDBL 12 +#define CTF_TYPE_FP_MAX 12 + +struct ctf_enum { + uint32_t ctf_enum_name; + uint32_t ctf_enum_val; +}; + +struct ctf_array { + uint16_t ctf_array_type; + uint16_t ctf_array_index_type; + uint32_t ctf_array_nelems; +}; + +/* Struct members are encoded with either ctf_short_member or + * ctf_full_member, depending upon the 'size' of the struct or + * union being defined. If it is less than CTF_SHORT_MEMBER_LIMIT + * then ctf_short_member objects are used to encode, else + * ctf_full_member is used. + */ +#define CTF_SHORT_MEMBER_LIMIT 8192 + +struct ctf_short_member { + uint32_t ctf_member_name; + uint16_t ctf_member_type; + uint16_t ctf_member_offset; +}; + +struct ctf_full_member { + uint32_t ctf_member_name; + uint16_t ctf_member_type; + uint16_t ctf_member_unused; + uint32_t ctf_member_offset_high; + uint32_t ctf_member_offset_low; +}; + +#endif /* _CTF_H */ diff --git a/ctf_encoder.c b/ctf_encoder.c new file mode 100644 index 0000000..b761287 --- /dev/null +++ b/ctf_encoder.c @@ -0,0 +1,353 @@ +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2009 Red Hat Inc. + Copyright (C) 2009 Arnaldo Carvalho de Melo <acme@redhat.com> +*/ + +#include "dwarves.h" +#include "libctf.h" +#include "ctf.h" +#include "hash.h" +#include "elf_symtab.h" +#include <inttypes.h> + +static int tag__check_id_drift(const struct tag *tag, + uint32_t core_id, uint32_t ctf_id) +{ + if (ctf_id != core_id) { + fprintf(stderr, "%s: %s id drift, core: %u, libctf: %d\n", + __func__, dwarf_tag_name(tag->tag), core_id, ctf_id); + return -1; + } + return 0; +} + +static int dwarf_to_ctf_type(uint16_t tag) +{ + switch (tag) { + case DW_TAG_const_type: return CTF_TYPE_KIND_CONST; + case DW_TAG_pointer_type: return CTF_TYPE_KIND_PTR; + case DW_TAG_restrict_type: return CTF_TYPE_KIND_RESTRICT; + case DW_TAG_volatile_type: return CTF_TYPE_KIND_VOLATILE; + case DW_TAG_class_type: + case DW_TAG_structure_type: return CTF_TYPE_KIND_STR; + case DW_TAG_union_type: return CTF_TYPE_KIND_UNION; + } + return 0xffff; +} + +static int base_type__encode(struct tag *tag, uint32_t core_id, struct ctf *ctf) +{ + struct base_type *bt = tag__base_type(tag); + uint32_t ctf_id = ctf__add_base_type(ctf, bt->name, bt->bit_size); + + if (tag__check_id_drift(tag, core_id, ctf_id)) + return -1; + + return 0; +} + +static int pointer_type__encode(struct tag *tag, uint32_t core_id, struct ctf *ctf) +{ + uint32_t ctf_id = ctf__add_short_type(ctf, dwarf_to_ctf_type(tag->tag), tag->type, 0); + + if (tag__check_id_drift(tag, core_id, ctf_id)) + return -1; + + return 0; +} + +static int typedef__encode(struct tag *tag, uint32_t core_id, struct ctf *ctf) +{ + uint32_t ctf_id = ctf__add_short_type(ctf, CTF_TYPE_KIND_TYPDEF, tag->type, tag__namespace(tag)->name); + + if (tag__check_id_drift(tag, core_id, ctf_id)) + return -1; + + return 0; +} + +static int fwd_decl__encode(struct tag *tag, uint32_t core_id, struct ctf *ctf) +{ + uint32_t ctf_id = ctf__add_fwd_decl(ctf, tag__namespace(tag)->name); + + if (tag__check_id_drift(tag, core_id, ctf_id)) + return -1; + + return 0; +} + +static int structure_type__encode(struct tag *tag, uint32_t core_id, struct ctf *ctf) +{ + struct type *type = tag__type(tag); + int64_t position; + uint32_t ctf_id = ctf__add_struct(ctf, dwarf_to_ctf_type(tag->tag), + type->namespace.name, type->size, + type->nr_members, &position); + + if (tag__check_id_drift(tag, core_id, ctf_id)) + return -1; + + const bool is_short = type->size < CTF_SHORT_MEMBER_LIMIT; + struct class_member *pos; + type__for_each_data_member(type, pos) { + if (is_short) + ctf__add_short_member(ctf, pos->name, pos->tag.type, + pos->bit_offset, &position); + else + ctf__add_full_member(ctf, pos->name, pos->tag.type, + pos->bit_offset, &position); + } + + return 0; +} + +static uint32_t array_type__nelems(struct tag *tag) +{ + int i; + uint32_t nelem = 1; + struct array_type *array = tag__array_type(tag); + + for (i = array->dimensions - 1; i >= 0; --i) + nelem *= array->nr_entries[i]; + + return nelem; +} + +static int array_type__encode(struct tag *tag, uint32_t core_id, struct ctf *ctf) +{ + const uint32_t nelems = array_type__nelems(tag); + uint32_t ctf_id = ctf__add_array(ctf, tag->type, 0, nelems); + + if (tag__check_id_drift(tag, core_id, ctf_id)) + return -1; + + return 0; +} + +static int subroutine_type__encode(struct tag *tag, uint32_t core_id, struct ctf *ctf) +{ + struct parameter *pos; + int64_t position; + struct ftype *ftype = tag__ftype(tag); + uint32_t ctf_id = ctf__add_function_type(ctf, tag->type, ftype->nr_parms, ftype->unspec_parms, &position); + + if (tag__check_id_drift(tag, core_id, ctf_id)) + return -1; + + ftype__for_each_parameter(ftype, pos) + ctf__add_parameter(ctf, pos->tag.type, &position); + + return 0; +} + +static int enumeration_type__encode(struct tag *tag, uint32_t core_id, struct ctf *ctf) +{ + struct type *etype = tag__type(tag); + int64_t position; + uint32_t ctf_id = ctf__add_enumeration_type(ctf, etype->namespace.name, + etype->size, etype->nr_members, + &position); + + if (tag__check_id_drift(tag, core_id, ctf_id)) + return -1; + + struct enumerator *pos; + type__for_each_enumerator(etype, pos) + ctf__add_enumerator(ctf, pos->name, pos->value, &position); + + return 0; +} + +static void tag__encode_ctf(struct tag *tag, uint32_t core_id, struct ctf *ctf) +{ + switch (tag->tag) { + case DW_TAG_base_type: + base_type__encode(tag, core_id, ctf); + break; + case DW_TAG_const_type: + case DW_TAG_pointer_type: + case DW_TAG_restrict_type: + case DW_TAG_volatile_type: + pointer_type__encode(tag, core_id, ctf); + break; + case DW_TAG_typedef: + typedef__encode(tag, core_id, ctf); + break; + case DW_TAG_structure_type: + case DW_TAG_union_type: + case DW_TAG_class_type: + if (tag__type(tag)->declaration) + fwd_decl__encode(tag, core_id, ctf); + else + structure_type__encode(tag, core_id, ctf); + break; + case DW_TAG_array_type: + array_type__encode(tag, core_id, ctf); + break; + case DW_TAG_subroutine_type: + subroutine_type__encode(tag, core_id, ctf); + break; + case DW_TAG_enumeration_type: + enumeration_type__encode(tag, core_id, ctf); + break; + } +} + +#define HASHADDR__BITS 8 +#define HASHADDR__SIZE (1UL << HASHADDR__BITS) +#define hashaddr__fn(key) hash_64(key, HASHADDR__BITS) + +static struct function *hashaddr__find_function(const struct hlist_head hashtable[], + const uint64_t addr) +{ + struct function *function; + struct hlist_node *pos; + uint16_t bucket = hashaddr__fn(addr); + const struct hlist_head *head = &hashtable[bucket]; + + hlist_for_each_entry(function, pos, head, tool_hnode) { + if (function->lexblock.ip.addr == addr) + return function; + } + + return NULL; +} + +static struct variable *hashaddr__find_variable(const struct hlist_head hashtable[], + const uint64_t addr) +{ + struct variable *variable; + struct hlist_node *pos; + uint16_t bucket = hashaddr__fn(addr); + const struct hlist_head *head = &hashtable[bucket]; + + hlist_for_each_entry(variable, pos, head, tool_hnode) { + if (variable->ip.addr == addr) + return variable; + } + + return NULL; +} + +/* + * FIXME: Its in the DWARF loader, we have to find a better handoff + * mechanizm... + */ +extern struct strings *strings; + +int cu__encode_ctf(struct cu *cu, int verbose) +{ + int err = -1; + struct ctf *ctf = ctf__new(cu->filename, cu->elf); + + if (ctf == NULL) + goto out; + + if (cu__cache_symtab(cu) < 0) + goto out_delete; + + ctf__set_strings(ctf, strings); + + uint32_t id; + struct tag *pos; + cu__for_each_type(cu, id, pos) + tag__encode_ctf(pos, id, ctf); + + struct hlist_head hash_addr[HASHADDR__SIZE]; + + for (id = 0; id < HASHADDR__SIZE; ++id) + INIT_HLIST_HEAD(&hash_addr[id]); + + struct function *function; + cu__for_each_function(cu, id, function) { + uint64_t addr = function->lexblock.ip.addr; + struct hlist_head *head = &hash_addr[hashaddr__fn(addr)]; + hlist_add_head(&function->tool_hnode, head); + } + + uint64_t addr; + GElf_Sym sym; + const char *sym_name; + cu__for_each_cached_symtab_entry(cu, id, sym, sym_name) { + if (ctf__ignore_symtab_function(&sym, sym_name)) + continue; + + addr = elf_sym__value(&sym); + int64_t position; + function = hashaddr__find_function(hash_addr, addr); + if (function == NULL) { + if (verbose) + fprintf(stderr, + "function %4d: %-20s %#" PRIx64 " %5u NOT FOUND!\n", + id, sym_name, addr, + elf_sym__size(&sym)); + err = ctf__add_function(ctf, 0, 0, 0, &position); + if (err != 0) + goto out_err_ctf; + continue; + } + + const struct ftype *ftype = &function->proto; + err = ctf__add_function(ctf, function->proto.tag.type, + ftype->nr_parms, + ftype->unspec_parms, &position); + + if (err != 0) + goto out_err_ctf; + + struct parameter *pos; + ftype__for_each_parameter(ftype, pos) + ctf__add_function_parameter(ctf, pos->tag.type, &position); + } + + for (id = 0; id < HASHADDR__SIZE; ++id) + INIT_HLIST_HEAD(&hash_addr[id]); + + struct variable *var; + cu__for_each_variable(cu, id, pos) { + var = tag__variable(pos); + if (variable__scope(var) != VSCOPE_GLOBAL) + continue; + struct hlist_head *head = &hash_addr[hashaddr__fn(var->ip.addr)]; + hlist_add_head(&var->tool_hnode, head); + } + + cu__for_each_cached_symtab_entry(cu, id, sym, sym_name) { + if (ctf__ignore_symtab_object(&sym, sym_name)) + continue; + addr = elf_sym__value(&sym); + + var = hashaddr__find_variable(hash_addr, addr); + if (var == NULL) { + if (verbose) + fprintf(stderr, + "variable %4d: %-20s %#" PRIx64 " %5u NOT FOUND!\n", + id, sym_name, addr, + elf_sym__size(&sym)); + err = ctf__add_object(ctf, 0); + if (err != 0) + goto out_err_ctf; + continue; + } + + err = ctf__add_object(ctf, var->ip.tag.type); + if (err != 0) + goto out_err_ctf; + } + + ctf__encode(ctf, CTF_FLAGS_COMPR); + + err = 0; +out_delete: + ctf__delete(ctf); +out: + return err; +out_err_ctf: + fprintf(stderr, + "%4d: %-20s %#llx %5u failed encoding, " + "ABORTING!\n", id, sym_name, + (unsigned long long)addr, elf_sym__size(&sym)); + goto out_delete; +} diff --git a/ctf_encoder.h b/ctf_encoder.h new file mode 100644 index 0000000..16e76e2 --- /dev/null +++ b/ctf_encoder.h @@ -0,0 +1,14 @@ +#ifndef _CTF_ENCODER_H_ +#define _CTF_ENCODER_H_ 1 +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2009 Red Hat Inc. + Copyright (C) 2009 Arnaldo Carvalho de Melo <acme@redhat.com> +*/ + +struct cu; + +int cu__encode_ctf(struct cu *cu, int verbose); + +#endif /* _CTF_ENCODER_H_ */ diff --git a/ctf_loader.c b/ctf_loader.c new file mode 100644 index 0000000..9f03f09 --- /dev/null +++ b/ctf_loader.c @@ -0,0 +1,771 @@ +/* ctfdump.c: CTF dumper. + * + * Copyright (C) 2008 David S. Miller <davem@davemloft.net> + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <stddef.h> +#include <malloc.h> +#include <string.h> +#include <limits.h> +#include <libgen.h> +#include <zlib.h> + +#include <gelf.h> + +#include "libctf.h" +#include "ctf.h" +#include "dutil.h" +#include "dwarves.h" + +/* + * FIXME: We should just get the table from the CTF ELF section + * and use it directly + */ +extern struct strings *strings; + +static void *tag__alloc(const size_t size) +{ + struct tag *tag = zalloc(size); + + if (tag != NULL) + tag->top_level = 1; + + return tag; +} + +static int ctf__load_ftype(struct ctf *ctf, struct ftype *proto, uint16_t tag, + uint16_t type, uint16_t vlen, uint16_t *args, long id) +{ + proto->tag.tag = tag; + proto->tag.type = type; + INIT_LIST_HEAD(&proto->parms); + + int i; + for (i = 0; i < vlen; i++) { + uint16_t type = ctf__get16(ctf, &args[i]); + + if (type == 0) + proto->unspec_parms = 1; + else { + struct parameter *p = tag__alloc(sizeof(*p)); + + if (p == NULL) + goto out_free_parameters; + p->tag.tag = DW_TAG_formal_parameter; + p->tag.type = ctf__get16(ctf, &args[i]); + ftype__add_parameter(proto, p); + } + } + + vlen *= sizeof(*args); + + /* Round up to next multiple of 4 to maintain + * 32-bit alignment. + */ + if (vlen & 0x2) + vlen += 0x2; + + if (id < 0) { + uint32_t type_id; + + cu__add_tag(ctf->priv, &proto->tag, &type_id); + } else { + cu__add_tag_with_id(ctf->priv, &proto->tag, id); + } + + return vlen; +out_free_parameters: + ftype__delete(proto, ctf->priv); + return -ENOMEM; +} + +static struct function *function__new(uint16_t **ptr, GElf_Sym *sym, + struct ctf *ctf) +{ + struct function *func = tag__alloc(sizeof(*func)); + + if (func != NULL) { + func->lexblock.ip.addr = elf_sym__value(sym); + func->lexblock.size = elf_sym__size(sym); + func->name = sym->st_name; + func->vtable_entry = -1; + func->external = elf_sym__bind(sym) == STB_GLOBAL; + INIT_LIST_HEAD(&func->vtable_node); + INIT_LIST_HEAD(&func->tool_node); + INIT_LIST_HEAD(&func->lexblock.tags); + + uint16_t val = ctf__get16(ctf, *ptr); + uint16_t tag = CTF_GET_KIND(val); + uint16_t vlen = CTF_GET_VLEN(val); + + ++*ptr; + + if (tag != CTF_TYPE_KIND_FUNC) { + fprintf(stderr, + "%s: Expected function type, got %u\n", + __func__, tag); + goto out_delete; + } + uint16_t type = ctf__get16(ctf, *ptr); + long id = -1; /* FIXME: not needed for funcs... */ + + ++*ptr; + + if (ctf__load_ftype(ctf, &func->proto, DW_TAG_subprogram, + type, vlen, *ptr, id) < 0) + return NULL; + /* + * Round up to next multiple of 4 to maintain 32-bit alignment. + */ + if (vlen & 0x1) + ++vlen; + *ptr += vlen; + } + + return func; +out_delete: + free(func); + return NULL; +} + +static int ctf__load_funcs(struct ctf *ctf) +{ + struct ctf_header *hp = ctf__get_buffer(ctf); + uint16_t *func_ptr = (ctf__get_buffer(ctf) + sizeof(*hp) + + ctf__get32(ctf, &hp->ctf_func_off)); + + GElf_Sym sym; + uint32_t idx; + ctf__for_each_symtab_function(ctf, idx, sym) + if (function__new(&func_ptr, &sym, ctf) == NULL) + return -ENOMEM; + + return 0; +} + +static struct base_type *base_type__new(strings_t name, uint32_t attrs, + uint8_t float_type, size_t size) +{ + struct base_type *bt = tag__alloc(sizeof(*bt)); + + if (bt != NULL) { + bt->name = name; + bt->bit_size = size; + bt->is_signed = attrs & CTF_TYPE_INT_SIGNED; + bt->is_bool = attrs & CTF_TYPE_INT_BOOL; + bt->is_varargs = attrs & CTF_TYPE_INT_VARARGS; + bt->name_has_encoding = false; + bt->float_type = float_type; + } + return bt; +} + +static void type__init(struct type *type, uint16_t tag, + strings_t name, size_t size) +{ + __type__init(type); + INIT_LIST_HEAD(&type->namespace.tags); + type->size = size; + type->namespace.tag.tag = tag; + type->namespace.name = name; + type->namespace.sname = 0; +} + +static struct type *type__new(uint16_t tag, strings_t name, size_t size) +{ + struct type *type = tag__alloc(sizeof(*type)); + + if (type != NULL) + type__init(type, tag, name, size); + + return type; +} + +static struct class *class__new(strings_t name, size_t size) +{ + struct class *class = tag__alloc(sizeof(*class)); + + if (class != NULL) { + type__init(&class->type, DW_TAG_structure_type, name, size); + INIT_LIST_HEAD(&class->vtable); + } + + return class; +} + +static int create_new_base_type(struct ctf *ctf, void *ptr, + struct ctf_full_type *tp, uint32_t id) +{ + uint32_t *enc = ptr; + uint32_t eval = ctf__get32(ctf, enc); + uint32_t attrs = CTF_TYPE_INT_ATTRS(eval); + strings_t name = ctf__get32(ctf, &tp->base.ctf_name); + struct base_type *base = base_type__new(name, attrs, 0, + CTF_TYPE_INT_BITS(eval)); + if (base == NULL) + return -ENOMEM; + + base->tag.tag = DW_TAG_base_type; + cu__add_tag_with_id(ctf->priv, &base->tag, id); + + return sizeof(*enc); +} + +static int create_new_base_type_float(struct ctf *ctf, void *ptr, + struct ctf_full_type *tp, + uint32_t id) +{ + strings_t name = ctf__get32(ctf, &tp->base.ctf_name); + uint32_t *enc = ptr, eval = ctf__get32(ctf, enc); + struct base_type *base = base_type__new(name, 0, eval, + CTF_TYPE_FP_BITS(eval)); + if (base == NULL) + return -ENOMEM; + + base->tag.tag = DW_TAG_base_type; + cu__add_tag_with_id(ctf->priv, &base->tag, id); + + return sizeof(*enc); +} + +static int create_new_array(struct ctf *ctf, void *ptr, uint32_t id) +{ + struct ctf_array *ap = ptr; + struct array_type *array = tag__alloc(sizeof(*array)); + + if (array == NULL) + return -ENOMEM; + + /* FIXME: where to get the number of dimensions? + * it it flattened? */ + array->dimensions = 1; + array->nr_entries = malloc(sizeof(uint32_t)); + + if (array->nr_entries == NULL) { + free(array); + return -ENOMEM; + } + + array->nr_entries[0] = ctf__get32(ctf, &ap->ctf_array_nelems); + array->tag.tag = DW_TAG_array_type; + array->tag.type = ctf__get16(ctf, &ap->ctf_array_type); + + cu__add_tag_with_id(ctf->priv, &array->tag, id); + + return sizeof(*ap); +} + +static int create_new_subroutine_type(struct ctf *ctf, void *ptr, + int vlen, struct ctf_full_type *tp, + uint32_t id) +{ + uint16_t *args = ptr; + unsigned int type = ctf__get16(ctf, &tp->base.ctf_type); + struct ftype *proto = tag__alloc(sizeof(*proto)); + + if (proto == NULL) + return -ENOMEM; + + vlen = ctf__load_ftype(ctf, proto, DW_TAG_subroutine_type, + type, vlen, args, id); + return vlen < 0 ? -ENOMEM : vlen; +} + +static int create_full_members(struct ctf *ctf, void *ptr, + int vlen, struct type *class) +{ + struct ctf_full_member *mp = ptr; + int i; + + for (i = 0; i < vlen; i++) { + struct class_member *member = zalloc(sizeof(*member)); + + if (member == NULL) + return -ENOMEM; + + member->tag.tag = DW_TAG_member; + member->tag.type = ctf__get16(ctf, &mp[i].ctf_member_type); + member->name = ctf__get32(ctf, &mp[i].ctf_member_name); + member->bit_offset = (ctf__get32(ctf, &mp[i].ctf_member_offset_high) << 16) | + ctf__get32(ctf, &mp[i].ctf_member_offset_low); + /* sizes and offsets will be corrected at class__fixup_ctf_bitfields */ + type__add_member(class, member); + } + + return sizeof(*mp); +} + +static int create_short_members(struct ctf *ctf, void *ptr, + int vlen, struct type *class) +{ + struct ctf_short_member *mp = ptr; + int i; + + for (i = 0; i < vlen; i++) { + struct class_member *member = zalloc(sizeof(*member)); + + if (member == NULL) + return -ENOMEM; + + member->tag.tag = DW_TAG_member; + member->tag.type = ctf__get16(ctf, &mp[i].ctf_member_type); + member->name = ctf__get32(ctf, &mp[i].ctf_member_name); + member->bit_offset = ctf__get16(ctf, &mp[i].ctf_member_offset); + /* sizes and offsets will be corrected at class__fixup_ctf_bitfields */ + + type__add_member(class, member); + } + + return sizeof(*mp); +} + +static int create_new_class(struct ctf *ctf, void *ptr, + int vlen, struct ctf_full_type *tp, + uint64_t size, uint32_t id) +{ + int member_size; + strings_t name = ctf__get32(ctf, &tp->base.ctf_name); + struct class *class = class__new(name, size); + + if (size >= CTF_SHORT_MEMBER_LIMIT) { + member_size = create_full_members(ctf, ptr, vlen, &class->type); + } else { + member_size = create_short_members(ctf, ptr, vlen, &class->type); + } + + if (member_size < 0) + goto out_free; + + cu__add_tag_with_id(ctf->priv, &class->type.namespace.tag, id); + + return (vlen * member_size); +out_free: + class__delete(class, ctf->priv); + return -ENOMEM; +} + +static int create_new_union(struct ctf *ctf, void *ptr, + int vlen, struct ctf_full_type *tp, + uint64_t size, uint32_t id) +{ + int member_size; + strings_t name = ctf__get32(ctf, &tp->base.ctf_name); + struct type *un = type__new(DW_TAG_union_type, name, size); + + if (size >= CTF_SHORT_MEMBER_LIMIT) { + member_size = create_full_members(ctf, ptr, vlen, un); + } else { + member_size = create_short_members(ctf, ptr, vlen, un); + } + + if (member_size < 0) + goto out_free; + + cu__add_tag_with_id(ctf->priv, &un->namespace.tag, id); + + return (vlen * member_size); +out_free: + type__delete(un, ctf->priv); + return -ENOMEM; +} + +static struct enumerator *enumerator__new(strings_t name, uint32_t value) +{ + struct enumerator *en = tag__alloc(sizeof(*en)); + + if (en != NULL) { + en->name = name; + en->value = value; + en->tag.tag = DW_TAG_enumerator; + } + + return en; +} + +static int create_new_enumeration(struct ctf *ctf, void *ptr, + int vlen, struct ctf_full_type *tp, + uint16_t size, uint32_t id) +{ + struct ctf_enum *ep = ptr; + uint16_t i; + struct type *enumeration = type__new(DW_TAG_enumeration_type, + ctf__get32(ctf, + &tp->base.ctf_name), + size ?: (sizeof(int) * 8)); + + if (enumeration == NULL) + return -ENOMEM; + + for (i = 0; i < vlen; i++) { + strings_t name = ctf__get32(ctf, &ep[i].ctf_enum_name); + uint32_t value = ctf__get32(ctf, &ep[i].ctf_enum_val); + struct enumerator *enumerator = enumerator__new(name, value); + + if (enumerator == NULL) + goto out_free; + + enumeration__add(enumeration, enumerator); + } + + cu__add_tag_with_id(ctf->priv, &enumeration->namespace.tag, id); + + return (vlen * sizeof(*ep)); +out_free: + enumeration__delete(enumeration, ctf->priv); + return -ENOMEM; +} + +static int create_new_forward_decl(struct ctf *ctf, struct ctf_full_type *tp, + uint64_t size, uint32_t id) +{ + strings_t name = ctf__get32(ctf, &tp->base.ctf_name); + struct class *fwd = class__new(name, size); + + if (fwd == NULL) + return -ENOMEM; + fwd->type.declaration = 1; + cu__add_tag_with_id(ctf->priv, &fwd->type.namespace.tag, id); + return 0; +} + +static int create_new_typedef(struct ctf *ctf, struct ctf_full_type *tp, + uint64_t size, uint32_t id) +{ + strings_t name = ctf__get32(ctf, &tp->base.ctf_name); + unsigned int type_id = ctf__get16(ctf, &tp->base.ctf_type); + struct type *type = type__new(DW_TAG_typedef, name, size); + + if (type == NULL) + return -ENOMEM; + + type->namespace.tag.type = type_id; + cu__add_tag_with_id(ctf->priv, &type->namespace.tag, id); + + return 0; +} + +static int create_new_tag(struct ctf *ctf, int type, + struct ctf_full_type *tp, uint32_t id) +{ + unsigned int type_id = ctf__get16(ctf, &tp->base.ctf_type); + struct tag *tag = zalloc(sizeof(*tag)); + + if (tag == NULL) + return -ENOMEM; + + switch (type) { + case CTF_TYPE_KIND_CONST: tag->tag = DW_TAG_const_type; break; + case CTF_TYPE_KIND_PTR: tag->tag = DW_TAG_pointer_type; break; + case CTF_TYPE_KIND_RESTRICT: tag->tag = DW_TAG_restrict_type; break; + case CTF_TYPE_KIND_VOLATILE: tag->tag = DW_TAG_volatile_type; break; + default: + free(tag); + printf("%s: unknown type %d\n\n", __func__, type); + return 0; + } + + tag->type = type_id; + cu__add_tag_with_id(ctf->priv, tag, id); + + return 0; +} + +static int ctf__load_types(struct ctf *ctf) +{ + void *ctf_buffer = ctf__get_buffer(ctf); + struct ctf_header *hp = ctf_buffer; + void *ctf_contents = ctf_buffer + sizeof(*hp), + *type_section = (ctf_contents + ctf__get32(ctf, &hp->ctf_type_off)), + *strings_section = (ctf_contents + ctf__get32(ctf, &hp->ctf_str_off)); + struct ctf_full_type *type_ptr = type_section, + *end = strings_section; + uint32_t type_index = 0x0001; + + if (hp->ctf_parent_name || hp->ctf_parent_label) + type_index += 0x8000; + + while (type_ptr < end) { + uint16_t val = ctf__get16(ctf, &type_ptr->base.ctf_info); + uint16_t type = CTF_GET_KIND(val); + int vlen = CTF_GET_VLEN(val); + void *ptr = type_ptr; + uint16_t base_size = ctf__get16(ctf, &type_ptr->base.ctf_size); + uint64_t size = base_size; + + if (base_size == 0xffff) { + size = ctf__get32(ctf, &type_ptr->ctf_size_high); + size <<= 32; + size |= ctf__get32(ctf, &type_ptr->ctf_size_low); + ptr += sizeof(struct ctf_full_type); + } else + ptr += sizeof(struct ctf_short_type); + + if (type == CTF_TYPE_KIND_INT) { + vlen = create_new_base_type(ctf, ptr, type_ptr, type_index); + } else if (type == CTF_TYPE_KIND_FLT) { + vlen = create_new_base_type_float(ctf, ptr, type_ptr, type_index); + } else if (type == CTF_TYPE_KIND_ARR) { + vlen = create_new_array(ctf, ptr, type_index); + } else if (type == CTF_TYPE_KIND_FUNC) { + vlen = create_new_subroutine_type(ctf, ptr, vlen, type_ptr, type_index); + } else if (type == CTF_TYPE_KIND_STR) { + vlen = create_new_class(ctf, ptr, + vlen, type_ptr, size, type_index); + } else if (type == CTF_TYPE_KIND_UNION) { + vlen = create_new_union(ctf, ptr, + vlen, type_ptr, size, type_index); + } else if (type == CTF_TYPE_KIND_ENUM) { + vlen = create_new_enumeration(ctf, ptr, vlen, type_ptr, + size, type_index); + } else if (type == CTF_TYPE_KIND_FWD) { + vlen = create_new_forward_decl(ctf, type_ptr, size, type_index); + } else if (type == CTF_TYPE_KIND_TYPDEF) { + vlen = create_new_typedef(ctf, type_ptr, size, type_index); + } else if (type == CTF_TYPE_KIND_VOLATILE || + type == CTF_TYPE_KIND_PTR || + type == CTF_TYPE_KIND_CONST || + type == CTF_TYPE_KIND_RESTRICT) { + vlen = create_new_tag(ctf, type, type_ptr, type_index); + } else if (type == CTF_TYPE_KIND_UNKN) { + cu__table_nullify_type_entry(ctf->priv, type_index); + fprintf(stderr, + "CTF: idx: %d, off: %zd, root: %s Unknown\n", + type_index, ((void *)type_ptr) - type_section, + CTF_ISROOT(val) ? "yes" : "no"); + vlen = 0; + } else + return -EINVAL; + + if (vlen < 0) + return vlen; + + type_ptr = ptr + vlen; + type_index++; + } + return 0; +} + +static struct variable *variable__new(uint16_t type, GElf_Sym *sym, + struct ctf *ctf) +{ + struct variable *var = tag__alloc(sizeof(*var)); + + if (var != NULL) { + var->scope = VSCOPE_GLOBAL; + var->ip.addr = elf_sym__value(sym); + var->name = sym->st_name; + var->external = elf_sym__bind(sym) == STB_GLOBAL; + var->ip.tag.tag = DW_TAG_variable; + var->ip.tag.type = type; + uint32_t id; /* FIXME: not needed for variables... */ + cu__add_tag(ctf->priv, &var->ip.tag, &id); + } + + return var; +} + +static int ctf__load_objects(struct ctf *ctf) +{ + struct ctf_header *hp = ctf__get_buffer(ctf); + uint16_t *objp = (ctf__get_buffer(ctf) + sizeof(*hp) + + ctf__get32(ctf, &hp->ctf_object_off)); + + GElf_Sym sym; + uint32_t idx; + ctf__for_each_symtab_object(ctf, idx, sym) { + const uint16_t type = *objp; + /* + * Discard void objects, probably was an object + * we didn't found DWARF info for when encoding. + */ + if (type && variable__new(type, &sym, ctf) == NULL) + return -ENOMEM; + ++objp; + } + + return 0; +} + +static int ctf__load_sections(struct ctf *ctf) +{ + int err = ctf__load_symtab(ctf); + + if (err != 0) + goto out; + err = ctf__load_funcs(ctf); + if (err == 0) + err = ctf__load_types(ctf); + if (err == 0) + err = ctf__load_objects(ctf); +out: + return err; +} + +static int class__fixup_ctf_bitfields(struct tag *tag, struct cu *cu) +{ + struct class_member *pos; + struct type *tag_type = tag__type(tag); + + type__for_each_data_member(tag_type, pos) { + struct tag *type = tag__strip_typedefs_and_modifiers(&pos->tag, cu); + + if (type == NULL) /* FIXME: C++ CTF... */ + continue; + + pos->bitfield_offset = 0; + pos->bitfield_size = 0; + pos->byte_offset = pos->bit_offset / 8; + + uint16_t type_bit_size; + size_t integral_bit_size; + + switch (type->tag) { + case DW_TAG_enumeration_type: + type_bit_size = tag__type(type)->size; + /* Best we can do to check if this is a packed enum */ + if (is_power_of_2(type_bit_size)) + integral_bit_size = roundup(type_bit_size, 8); + else + integral_bit_size = sizeof(int) * 8; + break; + case DW_TAG_base_type: { + struct base_type *bt = tag__base_type(type); + char name[256]; + type_bit_size = bt->bit_size; + integral_bit_size = base_type__name_to_size(bt, cu); + if (integral_bit_size == 0) + fprintf(stderr, "%s: unknown base type name \"%s\"!\n", + __func__, base_type__name(bt, cu, name, + sizeof(name))); + } + break; + default: + pos->byte_size = tag__size(type, cu); + pos->bit_size = pos->byte_size * 8; + continue; + } + + /* + * XXX: integral_bit_size can be zero if base_type__name_to_size doesn't + * know about the base_type name, so one has to add there when + * such base_type isn't found. pahole will put zero on the + * struct output so it should be easy to spot the name when + * such unlikely thing happens. + */ + pos->byte_size = integral_bit_size / 8; + + if (integral_bit_size == 0 || type_bit_size == integral_bit_size) { + pos->bit_size = integral_bit_size; + continue; + } + + pos->bitfield_offset = pos->bit_offset % integral_bit_size; + pos->bitfield_size = type_bit_size; + pos->bit_size = type_bit_size; + pos->byte_offset = (((pos->bit_offset / integral_bit_size) * + integral_bit_size) / 8); + } + + return 0; +} + +static int cu__fixup_ctf_bitfields(struct cu *cu) +{ + int err = 0; + struct tag *pos; + + list_for_each_entry(pos, &cu->tags, node) + if (tag__is_struct(pos) || tag__is_union(pos)) { + err = class__fixup_ctf_bitfields(pos, cu); + if (err) + break; + } + + return err; +} + +static const char *ctf__function_name(struct function *func, + const struct cu *cu) +{ + struct ctf *ctf = cu->priv; + + return ctf->symtab->symstrs->d_buf + func->name; +} + +static const char *ctf__variable_name(const struct variable *var, + const struct cu *cu) +{ + struct ctf *ctf = cu->priv; + + return ctf->symtab->symstrs->d_buf + var->name; +} + +static void ctf__cu_delete(struct cu *cu) +{ + ctf__delete(cu->priv); + cu->priv = NULL; +} + +static const char *ctf__strings_ptr(const struct cu *cu, strings_t s) +{ + return ctf__string(cu->priv, s); +} + +struct debug_fmt_ops ctf__ops; + +int ctf__load_file(struct cus *cus, struct conf_load *conf, + const char *filename) +{ + int err; + struct ctf *state = ctf__new(filename, NULL); + + if (state == NULL) + return -1; + + struct cu *cu = cu__new(filename, state->wordsize, NULL, 0, filename); + if (cu == NULL) + return -1; + + cu->language = LANG_C; + cu->uses_global_strings = false; + cu->little_endian = state->ehdr.e_ident[EI_DATA] == ELFDATA2LSB; + cu->dfops = &ctf__ops; + cu->priv = state; + state->priv = cu; + if (ctf__load(state) != 0) + return -1; + + err = ctf__load_sections(state); + + if (err != 0) { + cu__delete(cu); + return err; + } + + err = cu__fixup_ctf_bitfields(cu); + /* + * The app stole this cu, possibly deleting it, + * so forget about it + */ + if (conf && conf->steal && conf->steal(cu, conf)) + return 0; + + cus__add(cus, cu); + return err; +} + +struct debug_fmt_ops ctf__ops = { + .name = "ctf", + .function__name = ctf__function_name, + .load_file = ctf__load_file, + .variable__name = ctf__variable_name, + .strings__ptr = ctf__strings_ptr, + .cu__delete = ctf__cu_delete, +}; diff --git a/ctfdwdiff b/ctfdwdiff new file mode 100755 index 0000000..4ed989f --- /dev/null +++ b/ctfdwdiff @@ -0,0 +1,57 @@ +#!/bin/bash + +results_dir=/tmp/ctfdwdiff + +diff_tool() { + local tool=$1 + local dwarf_options=$2 + local ctf_options=$3 + local obj=$4 + + diff=$results_dir/$obj.$tool.diff + ctf=$results_dir/$obj.$tool.ctf.c + dwarf=$results_dir/$obj.$tool.dwarf.c + $tool -F ctf $ctf_options $obj > $ctf + $tool -F dwarf $dwarf_options $obj > $dwarf + diff -up $dwarf $ctf > $diff + if [ -s $diff ] ; then + [ $# -gt 4 ] && vim $diff + exit 0 + else + rm -f $diff $ctf $dwarf + fi +} + +diff_one() { + local obj=$1 + diff_tool "pahole" "--flat_arrays --show_private_classes --fixup_silly_bitfields" " " $obj $2 + diff_tool "pfunct" "-V --no_parm_names" "-V" $obj $2 +} + + +diff_dir() { + find . -type d | \ + while read dir ; do + cd $dir + ls *.o 2> /dev/null | + while read obj ; do + ncus=$(readelf -wi $obj | grep DW_TAG_compile_unit | wc -l) + if [ $ncus -ne 1 ] ; then + continue + fi + echo $obj + pahole -Z $obj + diff_one $obj $1 + done + cd - > /dev/null + done +} + +rm -rf $results_dir +mkdir $results_dir + +if [ $# -lt 2 ] ; then + diff_dir +else + diff_one $* +fi diff --git a/ctracer.c b/ctracer.c new file mode 100644 index 0000000..0098aa0 --- /dev/null +++ b/ctracer.c @@ -0,0 +1,1129 @@ +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2006 Mandriva Conectiva S.A. + Copyright (C) 2006 Arnaldo Carvalho de Melo <acme@mandriva.com> +*/ + +#include <argp.h> +#include <elf.h> +#include <errno.h> +#include <fcntl.h> +#include <gelf.h> +#include <limits.h> +#include <search.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> + +#include "dwarves_reorganize.h" +#include "dwarves_emit.h" +#include "dwarves.h" +#include "dutil.h" +#include "elf_symtab.h" + +/* + * target class name + */ +static char *class_name; + +/* + * List of compilation units being looked for functions with + * pointers to the specified struct. + */ +static struct cus *methods_cus; + +/** + * Mini class, the subset of the traced class that is collected at the probes + */ + +static struct class *mini_class; + +/* + * Directory where to generate source files + */ +static const char *src_dir = "."; + +/* + * Where to print the ctracer_methods.stp file + */ +static FILE *fp_methods; + +/* + * Where to print the ctracer_collector.c file + */ +static FILE *fp_collector; + +/* + * Where to print the ctracer_classes.h file + */ +static FILE *fp_classes; + +/* + * blacklist __init marked functions, i.e. functions that are + * in the ".init.text" ELF section and are thus discarded after + * boot. + */ +static struct strlist *init_blacklist; + +/* + * List of definitions and forward declarations already emitted for + * methods_cus, to avoid duplication. + */ +static struct type_emissions emissions; + +/* + * CU blacklist: if a "blacklist.cu" file is present, don't consider the + * CUs listed. Use a default of blacklist.cu. + */ +static const char *cu_blacklist_filename = "blacklist.cu"; + +static struct strlist *cu_blacklist; + +static struct cu *cu_filter(struct cu *cu) +{ + if (strlist__has_entry(cu_blacklist, cu->name)) + return NULL; + return cu; +} + +/* + * List of probes and kretprobes already emitted, this is a hack to cope with + * name space collisions, a better solution would be to in these cases to use the + * compilation unit name (net/ipv4/tcp.o, for instance) as a prefix when a + * static function has the same name in multiple compilation units (aka object + * files). + */ +static void *probes_emitted; + +struct structure { + struct list_head node; + struct tag *class; + struct cu *cu; +}; + +static struct structure *structure__new(struct tag *class, struct cu *cu) +{ + struct structure *st = malloc(sizeof(*st)); + + if (st != NULL) { + st->class = class; + st->cu = cu; + } + + return st; +} + +/* + * structs that can be casted to the target class, e.g. i.e. that has the target + * class at its first member. + */ +static LIST_HEAD(aliases); + +/* + * structs have pointers to the target class. + */ +static LIST_HEAD(pointers); + +static const char *structure__name(const struct structure *st) +{ + return class__name(tag__class(st->class), st->cu); +} + +static struct structure *structures__find(struct list_head *list, const char *name) +{ + struct structure *pos; + + if (name == NULL) + return NULL; + + list_for_each_entry(pos, list, node) + if (strcmp(structure__name(pos), name) == 0) + return pos; + + return NULL; +} + +static void structures__add(struct list_head *list, struct tag *class, struct cu *cu) +{ + struct structure *str = structure__new(class, cu); + + if (str != NULL) + list_add(&str->node, list); +} + +static int methods__compare(const void *a, const void *b) +{ + return strcmp(a, b); +} + +static int methods__add(void **table, const char *str) +{ + char **s = tsearch(str, table, methods__compare); + + if (s != NULL) { + if (*s == str) { + char *dup = strdup(str); + if (dup != NULL) + *s = dup; + else { + tdelete(str, table, methods__compare); + return -1; + } + } else + return -1; + } else + return -1; + + return 0; +} + +static void method__add(struct cu *cu, struct function *function, uint32_t id) +{ + list_add(&function->tool_node, &cu->tool_list); + function->priv = (void *)(long)id; +} + +/* + * We want just the function tags that have as one of its parameters + * a pointer to the specified "class" (a struct, unions can be added later). + */ +static struct function *function__filter(struct function *function, + struct cu *cu, type_id_t target_type_id) +{ + if (function__inlined(function) || + function->abstract_origin != 0 || + !list_empty(&function->tool_node) || + !ftype__has_parm_of_type(&function->proto, target_type_id, cu) || + strlist__has_entry(init_blacklist, function__name(function, cu))) { + return NULL; + } + + return function; +} + +/* + * Iterate thru all the tags in the compilation unit, looking just for the + * function tags that have as one of its parameters a pointer to + * the specified "class" (struct). + */ +static int cu_find_methods_iterator(struct cu *cu, void *cookie) +{ + type_id_t target_type_id; + uint32_t function_id; + struct function *function; + struct tag *target = cu__find_struct_by_name(cu, cookie, 0, + &target_type_id); + + INIT_LIST_HEAD(&cu->tool_list); + + if (target == NULL) + return 0; + + cu__for_each_function(cu, function_id, function) + if (function__filter(function, cu, target_type_id)) + method__add(cu, function, function_id); + + return 0; +} + +static struct class_member *class_member__bitfield_tail(struct class_member *head, + struct class *class) +{ + struct class_member *tail = head, + *member = list_prepare_entry(head, + class__tags(class), + tag.node); + list_for_each_entry_continue(member, class__tags(class), tag.node) + if (member->byte_offset == head->byte_offset) + tail = member; + else + break; + + return tail; +} + +/* + * Bitfields are removed as one for simplification right now. + */ +static struct class_member *class__remove_member(struct class *class, const struct cu *cu, + struct class_member *member) +{ + size_t size = member->byte_size; + struct class_member *bitfield_tail = NULL; + struct list_head *next; + uint16_t member_hole = member->hole; + + if (member->bitfield_size != 0) { + bitfield_tail = class_member__bitfield_tail(member, class); + member_hole = bitfield_tail->hole; + } + /* + * Is this the first member? + */ + if (member->tag.node.prev == class__tags(class)) { + class->type.size -= size + member_hole; + class__subtract_offsets_from(class, bitfield_tail ?: member, + size + member_hole); + /* + * Is this the last member? + */ + } else if (member->tag.node.next == class__tags(class)) { + if (size + class->padding >= cu->addr_size) { + class->type.size -= size + class->padding; + class->padding = 0; + } else + class->padding += size; + } else { + if (size + member_hole >= cu->addr_size) { + class->type.size -= size + member_hole; + class__subtract_offsets_from(class, + bitfield_tail ?: member, + size + member_hole); + } else { + struct class_member *from_prev = + list_entry(member->tag.node.prev, + struct class_member, + tag.node); + if (from_prev->hole == 0) + class->nr_holes++; + from_prev->hole += size + member_hole; + } + } + if (member_hole != 0) + class->nr_holes--; + + if (bitfield_tail != NULL) { + next = bitfield_tail->tag.node.next; + list_del_range(&member->tag.node, &bitfield_tail->tag.node); + if (bitfield_tail->bit_hole != 0) + class->nr_bit_holes--; + } else { + next = member->tag.node.next; + list_del(&member->tag.node); + } + + return list_entry(next, struct class_member, tag.node); +} + +static size_t class__find_biggest_member_name(const struct class *class, + const struct cu *cu) +{ + struct class_member *pos; + size_t biggest_name_len = 0; + + type__for_each_data_member(&class->type, pos) { + const size_t len = pos->name ? + strlen(class_member__name(pos, cu)) : 0; + + if (len > biggest_name_len) + biggest_name_len = len; + } + + return biggest_name_len; +} + +static void class__emit_class_state_collector(struct class *class, + const struct cu *cu, + struct class *clone) +{ + struct class_member *pos; + int len = class__find_biggest_member_name(clone, cu); + + fprintf(fp_collector, + "void ctracer__class_state(const void *from, void *to)\n" + "{\n" + "\tconst struct %s *obj = from;\n" + "\tstruct %s *mini_obj = to;\n\n", + class__name(class, cu), class__name(clone, cu)); + type__for_each_data_member(&clone->type, pos) + fprintf(fp_collector, "\tmini_obj->%-*s = obj->%s;\n", len, + class_member__name(pos, cu), + class_member__name(pos, cu)); + fputs("}\n\n", fp_collector); +} + +static struct class *class__clone_base_types(const struct tag *tag, + struct cu *cu, + const char *new_class_name) +{ + struct class *class = tag__class(tag); + struct class_member *pos, *next; + struct class *clone = class__clone(class, new_class_name, cu); + + if (clone == NULL) + return NULL; + + type__for_each_data_member_safe(&clone->type, pos, next) { + struct tag *member_type = cu__type(cu, pos->tag.type); + + tag__assert_search_result(member_type); + if (!tag__is_base_type(member_type, cu)) { + next = class__remove_member(clone, cu, pos); + class_member__delete(pos, cu); + } + } + class__fixup_alignment(clone, cu); + class__reorganize(clone, cu, 0, NULL); + return clone; +} + +/** + * Converter to the legacy ostra tables, will be much improved in the future. + */ +static void emit_struct_member_table_entry(FILE *fp, + int field, const char *name, + int traced, const char *hooks) +{ + fprintf(fp, "%u:%s:", field, name); + if (traced) + fprintf(fp, "yes:%%object->%s:u:%s:none\n", name, hooks); + else + fprintf(fp, "no:None:None:%s:dev_null\n", hooks); +} + +/** + * Generates a converter to the ostra lebacy tables format, needef by + * ostra-cg to preprocess the raw data collected from the debugfs/relay + * channel. + */ +static int class__emit_ostra_converter(struct tag *tag, + const struct cu *cu) +{ + struct class *class = tag__class(tag); + struct class_member *pos; + struct type *type = &mini_class->type; + int field = 0, first = 1; + char filename[128]; + char parm_list[1024]; + char *p = parm_list; + size_t n; + size_t plen = sizeof(parm_list); + FILE *fp_fields, *fp_converter; + const char *name = class__name(class, cu); + + snprintf(filename, sizeof(filename), "%s/%s.fields", src_dir, name); + fp_fields = fopen(filename, "w"); + if (fp_fields == NULL) { + fprintf(stderr, "ctracer: couldn't create %s\n", filename); + exit(EXIT_FAILURE); + } + + snprintf(filename, sizeof(filename), "%s/ctracer2ostra.c", src_dir); + + fp_converter = fopen(filename, "w"); + if (fp_converter == NULL) { + fprintf(stderr, "ctracer: couldn't create %s\n", filename); + exit(EXIT_FAILURE); + } + + fputs("#include \"ctracer_classes.h\"\n" + "#include <stdio.h>\n" + "#include <string.h>\n" + "#include \"ctracer_relay.h\"\n\n", fp_converter); + emit_struct_member_table_entry(fp_fields, field++, "action", 0, + "entry,exit"); + emit_struct_member_table_entry(fp_fields, field++, "function_id", 0, + "entry,exit"); + emit_struct_member_table_entry(fp_fields, field++, "object", 1, + "entry,exit"); + + fprintf(fp_converter, "\n" + "int main(void)\n" + "{\n" + "\twhile (1) {\n" + "\t\tstruct trace_entry hdr;\n" + "\t\tstruct ctracer__mini_%s obj;\n" + "\n" + "\t\tif (read(0, &hdr, sizeof(hdr)) != sizeof(hdr))\n" + "\t\t\tbreak;\n" + "\n" + "\t\tfprintf(stdout, \"%%llu %%c:%%llu:%%p\",\n" + "\t\t\thdr.nsec,\n" + "\t\t\thdr.probe_type ? 'o' : 'i',\n" + "\t\t\thdr.function_id,\n" + "\t\t\thdr.object);\n" + "\n" + "\t\tif (read(0, &obj, sizeof(obj)) != sizeof(obj))\n" + "\t\t\tbreak;\n" + "\t\tfprintf(stdout,\n" + "\t\t\t\":", name); + + type__for_each_data_member(type, pos) { + if (first) + first = 0; + else { + fputc(':', fp_converter); + n = snprintf(p, plen, ",\n\t\t\t "); + plen -= n; p += n; + } + fprintf(fp_converter, "%%u"); + n = snprintf(p, plen, "obj.%s", class_member__name(pos, cu)); + plen -= n; p += n; + emit_struct_member_table_entry(fp_fields, field++, + class_member__name(pos, cu), + 1, "entry,exit"); + } + fprintf(fp_converter, + "\\n\",\n\t\t\t %s);\n" + "\t}\n" + "\treturn 0;\n" + "}\n", parm_list); + fclose(fp_fields); + fclose(fp_converter); + return 0; +} + +/* + * We want just the DW_TAG_structure_type tags that have a member that is a pointer + * to the target class. + */ +static struct tag *pointer_filter(struct tag *tag, struct cu *cu, + type_id_t target_type_id) +{ + struct type *type; + struct class_member *pos; + const char *class_name; + + if (!tag__is_struct(tag)) + return NULL; + + type = tag__type(tag); + if (type->nr_members == 0) + return NULL; + + class_name = class__name(tag__class(tag), cu); + if (class_name == NULL || structures__find(&pointers, class_name)) + return NULL; + + type__for_each_member(type, pos) { + struct tag *ctype = cu__type(cu, pos->tag.type); + + tag__assert_search_result(ctype); + if (tag__is_pointer_to(ctype, target_type_id)) + return tag; + } + + return NULL; +} + +/* + * Iterate thru all the tags in the compilation unit, looking for classes + * that have as one member that is a pointer to the target type. + */ +static int cu_find_pointers_iterator(struct cu *cu, void *class_name) +{ + type_id_t target_type_id, id; + struct tag *target = cu__find_struct_by_name(cu, class_name, 0, + &target_type_id), *pos; + + if (target == NULL) + return 0; + + cu__for_each_type(cu, id, pos) + if (pointer_filter(pos, cu, target_type_id)) + structures__add(&pointers, pos, cu); + + return 0; +} + +static void class__find_pointers(const char *class_name) +{ + cus__for_each_cu(methods_cus, cu_find_pointers_iterator, (void *)class_name, cu_filter); +} + +/* + * We want just the DW_TAG_structure_type tags that have as its first member + * a struct of type target. + */ +static struct tag *alias_filter(struct tag *tag, const struct cu *cu, + type_id_t target_type_id) +{ + struct type *type; + struct class_member *first_member; + + if (!tag__is_struct(tag)) + return NULL; + + type = tag__type(tag); + if (type->nr_members == 0) + return NULL; + + first_member = list_first_entry(&type->namespace.tags, + struct class_member, tag.node); + if (first_member->tag.type != target_type_id) + return NULL; + + if (structures__find(&aliases, class__name(tag__class(tag), cu))) + return NULL; + + return tag; +} + +static void class__find_aliases(const char *class_name); + +/* + * Iterate thru all the tags in the compilation unit, looking for classes + * that have as its first member the specified "class" (struct). + */ +static int cu_find_aliases_iterator(struct cu *cu, void *class_name) +{ + type_id_t target_type_id, id; + struct tag *target = cu__find_struct_by_name(cu, class_name, 0, + &target_type_id), *pos; + if (target == NULL) + return 0; + + cu__for_each_type(cu, id, pos) { + if (alias_filter(pos, cu, target_type_id)) { + const char *alias_name = class__name(tag__class(pos), cu); + + structures__add(&aliases, pos, cu); + + /* + * Now find aliases to this alias, e.g.: + * + * struct tcp_sock { + * struct inet_connection_sock { + * struct inet_sock { + * struct sock { + * } + * } + * } + * } + */ + class__find_aliases(alias_name); + } + } + + return 0; +} + +static void class__find_aliases(const char *class_name) +{ + cus__for_each_cu(methods_cus, cu_find_aliases_iterator, (void *)class_name, cu_filter); +} + +static void emit_list_of_types(struct list_head *list, const struct cu *cu) +{ + struct structure *pos; + + list_for_each_entry(pos, list, node) { + struct type *type = tag__type(pos->class); + /* + * Lets look at the other CUs, perhaps we have already + * emmited this one + */ + if (type_emissions__find_definition(&emissions, cu, + structure__name(pos))) { + type->definition_emitted = 1; + continue; + } + type__emit_definitions(pos->class, pos->cu, &emissions, + fp_classes); + type->definition_emitted = 1; + type__emit(pos->class, pos->cu, NULL, NULL, fp_classes); + tag__type(pos->class)->definition_emitted = 1; + fputc('\n', fp_classes); + } +} + +static int class__emit_classes(struct tag *tag, struct cu *cu) +{ + struct class *class = tag__class(tag); + int err = -1; + char mini_class_name[128]; + + snprintf(mini_class_name, sizeof(mini_class_name), "ctracer__mini_%s", + class__name(class, cu)); + + mini_class = class__clone_base_types(tag, cu, mini_class_name); + if (mini_class == NULL) + goto out; + + type__emit_definitions(tag, cu, &emissions, fp_classes); + + type__emit(tag, cu, NULL, NULL, fp_classes); + fputs("\n/* class aliases */\n\n", fp_classes); + + emit_list_of_types(&aliases, cu); + + fputs("\n/* class with pointers */\n\n", fp_classes); + + emit_list_of_types(&pointers, cu); + + class__fprintf(mini_class, cu, fp_classes); + fputs(";\n\n", fp_classes); + class__emit_class_state_collector(class, cu, mini_class); + err = 0; +out: + return err; +} + +/* + * Emit the kprobes routine for one of the selected "methods", later we'll + * put this into the 'kprobes' table, in cu_emit_kprobes_table_iterator. + * + * This marks the function entry, function__emit_kretprobes will emit the + * probe for the function exit. + */ +static int function__emit_probes(struct function *func, uint32_t function_id, + const struct cu *cu, + const type_id_t target_type_id, int probe_type, + const char *member) +{ + struct parameter *pos; + const char *name = function__name(func, cu); + + fprintf(fp_methods, "probe %s%s = kernel.function(\"%s@%s\")%s\n" + "{\n" + "}\n\n" + "probe %s%s\n" + "{\n", name, + probe_type == 0 ? "" : "__return", + name, + cu->name, + probe_type == 0 ? "" : ".return", + name, + probe_type == 0 ? "" : "__return"); + + list_for_each_entry(pos, &func->proto.parms, tag.node) { + struct tag *type = cu__type(cu, pos->tag.type); + + tag__assert_search_result(type); + if (!tag__is_pointer_to(type, target_type_id)) + continue; + + if (member != NULL) + fprintf(fp_methods, "\tif ($%s)\n\t", + parameter__name(pos, cu)); + + fprintf(fp_methods, + "\tctracer__method_hook(%d, %d, $%s%s%s, %d);\n", + probe_type, + function_id, + parameter__name(pos, cu), + member ? "->" : "", member ?: "", + class__size(mini_class)); + break; + } + + fputs("}\n\n", fp_methods); + fflush(fp_methods); + + return 0; +} + +/* + * Iterate thru the list of methods previously collected by + * cu_find_methods_iterator, emitting the probes for function entry. + */ +static int cu_emit_probes_iterator(struct cu *cu, void *cookie) +{ + type_id_t target_type_id; + struct tag *target = cu__find_struct_by_name(cu, cookie, 0, &target_type_id); + struct function *pos; + + /* OK, this type is not present in this compile unit */ + if (target == NULL) + return 0; + + list_for_each_entry(pos, &cu->tool_list, tool_node) { + uint32_t function_id = (long)pos->priv; + + if (methods__add(&probes_emitted, function__name(pos, cu)) != 0) + continue; + function__emit_probes(pos, function_id, cu, target_type_id, 0, NULL); /* entry */ + function__emit_probes(pos, function_id, cu, target_type_id, 1, NULL); /* exit */ + } + + return 0; +} + +/* + * Iterate thru the list of methods previously collected by + * cu_find_methods_iterator, emitting the probes for function entry. + */ +static int cu_emit_pointer_probes_iterator(struct cu *cu, void *cookie) +{ + type_id_t target_type_id, pointer_id; + struct tag *target, *pointer; + struct function *pos_tag; + struct class_member *pos_member; + + /* This CU doesn't have our classes */ + if (list_empty(&cu->tool_list)) + return 0; + + target = cu__find_struct_by_name(cu, class_name, 1, &target_type_id); + pointer = cu__find_struct_by_name(cu, cookie, 0, &pointer_id); + + /* OK, this type is not present in this compile unit */ + if (target == NULL || pointer == NULL) + return 0; + + /* for now just for the first member that is a pointer */ + type__for_each_member(tag__type(pointer), pos_member) { + struct tag *ctype = cu__type(cu, pos_member->tag.type); + + tag__assert_search_result(ctype); + if (tag__is_pointer_to(ctype, target_type_id)) + break; + } + + list_for_each_entry(pos_tag, &cu->tool_list, tool_node) { + uint32_t function_id = (long)pos_tag->priv; + + if (methods__add(&probes_emitted, function__name(pos_tag, cu)) != 0) + continue; + + function__emit_probes(pos_tag, function_id, cu, target_type_id, 0, + class_member__name(pos_member, cu)); /* entry */ + function__emit_probes(pos_tag, function_id, cu, target_type_id, 1, + class_member__name(pos_member, cu)); /* exit */ + } + + return 0; +} + +/* + * Iterate thru the list of methods previously collected by + * cu_find_methods_iterator, creating the functions table that will + * be used by ostra-cg + */ +static int cu_emit_functions_table(struct cu *cu, void *fp) +{ + struct function *pos; + + list_for_each_entry(pos, &cu->tool_list, tool_node) + if (pos->priv != NULL) { + uint32_t function_id = (long)pos->priv; + fprintf(fp, "%d:%s\n", function_id, + function__name(pos, cu)); + pos->priv = NULL; + } + + return 0; +} + +static int elf__open(const char *filename) +{ + int fd = open(filename, O_RDONLY); + + if (fd < 0) + return -1; + + int err = -1; + + if (elf_version(EV_CURRENT) == EV_NONE) { + fprintf(stderr, "%s: cannot set libelf version.\n", __func__); + goto out_close; + } + + Elf *elf = elf_begin(fd, ELF_C_READ_MMAP, NULL); + if (elf == NULL) { + fprintf(stderr, "%s: cannot read %s ELF file.\n", + __func__, filename); + goto out_close; + } + + GElf_Ehdr ehdr; + if (gelf_getehdr(elf, &ehdr) == NULL) { + fprintf(stderr, "%s: cannot get elf header.\n", __func__); + goto out_elf_end; + } + + GElf_Shdr shdr; + size_t init_index; + Elf_Scn *init = elf_section_by_name(elf, &ehdr, &shdr, ".init.text", + &init_index); + if (init == NULL) + goto out_elf_end; + + struct elf_symtab *symtab = elf_symtab__new(".symtab", elf, &ehdr); + if (symtab == NULL) + goto out_elf_end; + + init_blacklist = strlist__new(true); + if (init_blacklist == NULL) + goto out_elf_symtab_delete; + + uint32_t index; + GElf_Sym sym; + elf_symtab__for_each_symbol(symtab, index, sym) { + if (!elf_sym__is_local_function(&sym)) + continue; + if (elf_sym__section(&sym) != init_index) + continue; + err = strlist__add(init_blacklist, elf_sym__name(&sym, symtab)); + if (err == -ENOMEM) { + fprintf(stderr, "failed for %s(%d,%zd)\n", elf_sym__name(&sym, symtab),elf_sym__section(&sym),init_index); + goto out_delete_blacklist; + } + } + + err = 0; +out_elf_symtab_delete: + elf_symtab__delete(symtab); +out_elf_end: + elf_end(elf); +out_close: + close(fd); + return err; +out_delete_blacklist: + strlist__delete(init_blacklist); + goto out_elf_symtab_delete; +} + +/* Name and version of program. */ +ARGP_PROGRAM_VERSION_HOOK_DEF = dwarves_print_version; + +static const struct argp_option ctracer__options[] = { + { + .key = 'd', + .name = "src_dir", + .arg = "SRC_DIR", + .doc = "generate source files in this directory", + }, + { + .key = 'C', + .name = "cu_blacklist", + .arg = "FILE", + .doc = "Blacklist the CUs in FILE", + }, + { + .key = 'D', + .name = "dir", + .arg = "DIR", + .doc = "load files in this directory", + }, + { + .key = 'g', + .name = "glob", + .arg = "GLOB", + .doc = "file mask to load", + }, + { + .key = 'r', + .name = "recursive", + .doc = "recursively load files", + }, + { + .name = NULL, + } +}; + +static const char *dirname, *glob; +static int recursive; + +static error_t ctracer__options_parser(int key, char *arg, + struct argp_state *state __unused) +{ + switch (key) { + case 'd': src_dir = arg; break; + case 'C': cu_blacklist_filename = arg; break; + case 'D': dirname = arg; break; + case 'g': glob = arg; break; + case 'r': recursive = 1; break; + default: return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static const char ctracer__args_doc[] = "FILE CLASS"; + +static struct argp ctracer__argp = { + .options = ctracer__options, + .parser = ctracer__options_parser, + .args_doc = ctracer__args_doc, +}; + +int main(int argc, char *argv[]) +{ + int remaining, err; + struct tag *class; + struct cu *cu; + char *filename; + char functions_filename[PATH_MAX]; + char methods_filename[PATH_MAX]; + char collector_filename[PATH_MAX]; + char classes_filename[PATH_MAX]; + struct structure *pos; + FILE *fp_functions; + int rc = EXIT_FAILURE; + + if (dwarves__init(0)) { + fputs("ctracer: insufficient memory\n", stderr); + goto out; + } + + if (argp_parse(&ctracer__argp, argc, argv, 0, &remaining, NULL) || + remaining < argc) { + switch (argc - remaining) { + case 1: goto failure; + case 2: filename = argv[remaining++]; + class_name = argv[remaining++]; break; + default: goto failure; + } + } else { +failure: + argp_help(&ctracer__argp, stderr, ARGP_HELP_SEE, argv[0]); + goto out; + } + + type_emissions__init(&emissions); + + /* + * Create the methods_cus (Compilation Units) object where we will + * load the objects where we'll look for functions pointers to the + * specified class, i.e. to find its "methods", where we'll insert + * the entry and exit hooks. + */ + methods_cus = cus__new(); + if (methods_cus == NULL) { + fputs("ctracer: insufficient memory\n", stderr); + goto out; + } + + /* + * if --dir/-D was specified, recursively traverse the path looking for + * object files (compilation units) that match the glob specified (*.ko) + * for kernel modules, but could be "*.o" in the future when we support + * uprobes for user space tracing. + */ + if (dirname != NULL && cus__load_dir(methods_cus, NULL, dirname, glob, + recursive) != 0) { + fprintf(stderr, "ctracer: couldn't load DWARF info " + "from %s dir with glob %s\n", + dirname, glob); + goto out; + } + + /* + * If a filename was specified, for instance "vmlinux", load it too. + */ + if (filename != NULL) { + if (elf__open(filename)) { + fprintf(stderr, "ctracer: couldn't load ELF symtab " + "info from %s\n", filename); + goto out; + } + err = cus__load_file(methods_cus, NULL, filename); + if (err != 0) { + cus__print_error_msg("ctracer", methods_cus, filename, err); + goto out; + } + } + + /* + * See if the specified struct exists: + */ + class = cus__find_struct_by_name(methods_cus, &cu, class_name, 0, NULL); + if (class == NULL) { + fprintf(stderr, "ctracer: struct %s not found!\n", class_name); + goto out; + } + + snprintf(functions_filename, sizeof(functions_filename), + "%s/%s.functions", src_dir, class__name(tag__class(class), cu)); + fp_functions = fopen(functions_filename, "w"); + if (fp_functions == NULL) { + fprintf(stderr, "ctracer: couldn't create %s\n", + functions_filename); + goto out; + } + + snprintf(methods_filename, sizeof(methods_filename), + "%s/ctracer_methods.stp", src_dir); + fp_methods = fopen(methods_filename, "w"); + if (fp_methods == NULL) { + fprintf(stderr, "ctracer: couldn't create %s\n", + methods_filename); + goto out; + } + + snprintf(collector_filename, sizeof(collector_filename), + "%s/ctracer_collector.c", src_dir); + fp_collector = fopen(collector_filename, "w"); + if (fp_collector == NULL) { + fprintf(stderr, "ctracer: couldn't create %s\n", + collector_filename); + goto out; + } + + snprintf(classes_filename, sizeof(classes_filename), + "%s/ctracer_classes.h", src_dir); + fp_classes = fopen(classes_filename, "w"); + if (fp_classes == NULL) { + fprintf(stderr, "ctracer: couldn't create %s\n", + classes_filename); + goto out; + } + + fputs("%{\n" + "#include </home/acme/git/pahole/lib/ctracer_relay.h>\n" + "%}\n" + "function ctracer__method_hook(probe_type, func, object, state_len)\n" + "%{\n" + "\tctracer__method_hook(_stp_gettimeofday_ns(), " + "THIS->probe_type, THIS->func, " + "(void *)(long)THIS->object, " + "THIS->state_len);\n" + "%}\n\n", fp_methods); + + fputs("\n#include \"ctracer_classes.h\"\n\n", fp_collector); + class__find_aliases(class_name); + class__find_pointers(class_name); + + class__emit_classes(class, cu); + fputc('\n', fp_collector); + + class__emit_ostra_converter(class, cu); + + cu_blacklist = strlist__new(true); + if (cu_blacklist != NULL) + strlist__load(cu_blacklist, cu_blacklist_filename); + + cus__for_each_cu(methods_cus, cu_find_methods_iterator, + class_name, cu_filter); + cus__for_each_cu(methods_cus, cu_emit_probes_iterator, + class_name, cu_filter); + cus__for_each_cu(methods_cus, cu_emit_functions_table, + fp_functions, cu_filter); + + list_for_each_entry(pos, &aliases, node) { + const char *alias_name = structure__name(pos); + + cus__for_each_cu(methods_cus, cu_find_methods_iterator, + (void *)alias_name, cu_filter); + cus__for_each_cu(methods_cus, cu_emit_probes_iterator, + (void *)alias_name, cu_filter); + cus__for_each_cu(methods_cus, cu_emit_functions_table, + fp_functions, cu_filter); + } + + list_for_each_entry(pos, &pointers, node) { + const char *pointer_name = structure__name(pos); + cus__for_each_cu(methods_cus, cu_find_methods_iterator, + (void *)pointer_name, cu_filter); + cus__for_each_cu(methods_cus, cu_emit_pointer_probes_iterator, + (void *)pointer_name, cu_filter); + cus__for_each_cu(methods_cus, cu_emit_functions_table, fp_functions, + cu_filter); + } + + fclose(fp_methods); + fclose(fp_collector); + fclose(fp_functions); + fclose(fp_classes); + strlist__delete(cu_blacklist); + + rc = EXIT_SUCCESS; +out: + cus__delete(methods_cus); + dwarves__exit(); + return rc; +} diff --git a/dtagnames.c b/dtagnames.c new file mode 100644 index 0000000..0ffcbf7 --- /dev/null +++ b/dtagnames.c @@ -0,0 +1,63 @@ +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2006 Mandriva Conectiva S.A. + Copyright (C) 2006 Arnaldo Carvalho de Melo <acme@mandriva.com> +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <malloc.h> + +#include "dwarves.h" +#include "dutil.h" + +static void print_malloc_stats(void) +{ + struct mallinfo m = mallinfo(); + + fprintf(stderr, "size: %u\n", m.uordblks); +} + +static int class__tag_name(struct tag *tag, struct cu *cu __unused, + void *cookie __unused) +{ + puts(dwarf_tag_name(tag->tag)); + return 0; +} + +static int cu__dump_class_tag_names(struct cu *cu, void *cookie __unused) +{ + cu__for_all_tags(cu, class__tag_name, NULL); + return 0; +} + +static void cus__dump_class_tag_names(struct cus *cus) +{ + cus__for_each_cu(cus, cu__dump_class_tag_names, NULL, NULL); +} + +int main(int argc __unused, char *argv[]) +{ + int err, rc = EXIT_FAILURE; + struct cus *cus = cus__new(); + + if (dwarves__init(0) || cus == NULL) { + fputs("dtagnames: insufficient memory\n", stderr); + goto out; + } + + err = cus__load_files(cus, NULL, argv + 1); + if (err != 0) { + cus__fprintf_load_files_err(cus, "dtagnames", argv + 1, err, stderr); + goto out; + } + + cus__dump_class_tag_names(cus); + print_malloc_stats(); + rc = EXIT_SUCCESS; +out: + cus__delete(cus); + dwarves__exit(); + return rc; +} @@ -0,0 +1,207 @@ +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2007 Arnaldo Carvalho de Melo <acme@redhat.com> +*/ + + +#include "dutil.h" + +#include <ctype.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +void *zalloc(const size_t size) +{ + void *s = malloc(size); + if (s != NULL) + memset(s, 0, size); + return s; +} + +struct str_node *str_node__new(const char *s, bool dupstr) +{ + struct str_node *snode = malloc(sizeof(*snode)); + + if (snode != NULL){ + if (dupstr) { + s = strdup(s); + if (s == NULL) + goto out_delete; + } + snode->s = s; + } + + return snode; + +out_delete: + free(snode); + return NULL; +} + +static void str_node__delete(struct str_node *snode, bool dupstr) +{ + if (dupstr) + free((void *)snode->s); + free(snode); +} + +int __strlist__add(struct strlist *slist, const char *new_entry, void *priv) +{ + struct rb_node **p = &slist->entries.rb_node; + struct rb_node *parent = NULL; + struct str_node *sn; + + while (*p != NULL) { + int rc; + + parent = *p; + sn = rb_entry(parent, struct str_node, rb_node); + rc = strcmp(sn->s, new_entry); + + if (rc > 0) + p = &(*p)->rb_left; + else if (rc < 0) + p = &(*p)->rb_right; + else + return -EEXIST; + } + + sn = str_node__new(new_entry, slist->dupstr); + if (sn == NULL) + return -ENOMEM; + + rb_link_node(&sn->rb_node, parent, p); + rb_insert_color(&sn->rb_node, &slist->entries); + + sn->priv = priv; + + list_add_tail(&sn->node, &slist->list_entries); + + return 0; +} + +int strlist__add(struct strlist *slist, const char *new_entry) +{ + return __strlist__add(slist, new_entry, NULL); +} + +int strlist__load(struct strlist *slist, const char *filename) +{ + char entry[1024]; + int err = -1; + FILE *fp = fopen(filename, "r"); + + if (fp == NULL) + return -1; + + while (fgets(entry, sizeof(entry), fp) != NULL) { + const size_t len = strlen(entry); + + if (len == 0) + continue; + entry[len - 1] = '\0'; + + if (strlist__add(slist, entry) != 0) + goto out; + } + + err = 0; +out: + fclose(fp); + return err; +} + +struct strlist *strlist__new(bool dupstr) +{ + struct strlist *slist = malloc(sizeof(*slist)); + + if (slist != NULL) { + slist->entries = RB_ROOT; + INIT_LIST_HEAD(&slist->list_entries); + slist->dupstr = dupstr; + } + + return slist; +} + +void strlist__delete(struct strlist *slist) +{ + if (slist != NULL) { + struct str_node *pos; + struct rb_node *next = rb_first(&slist->entries); + + while (next) { + pos = rb_entry(next, struct str_node, rb_node); + next = rb_next(&pos->rb_node); + strlist__remove(slist, pos); + } + slist->entries = RB_ROOT; + free(slist); + } +} + +void strlist__remove(struct strlist *slist, struct str_node *sn) +{ + rb_erase(&sn->rb_node, &slist->entries); + list_del_init(&sn->node); + str_node__delete(sn, slist->dupstr); +} + +bool strlist__has_entry(struct strlist *slist, const char *entry) +{ + struct rb_node **p = &slist->entries.rb_node; + struct rb_node *parent = NULL; + + while (*p != NULL) { + struct str_node *sn; + int rc; + + parent = *p; + sn = rb_entry(parent, struct str_node, rb_node); + rc = strcmp(sn->s, entry); + + if (rc > 0) + p = &(*p)->rb_left; + else if (rc < 0) + p = &(*p)->rb_right; + else + return true; + } + + return false; +} + +Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep, + GElf_Shdr *shp, const char *name, size_t *index) +{ + Elf_Scn *sec = NULL; + size_t cnt = 1; + + while ((sec = elf_nextscn(elf, sec)) != NULL) { + char *str; + + gelf_getshdr(sec, shp); + str = elf_strptr(elf, ep->e_shstrndx, shp->sh_name); + if (!strcmp(name, str)) { + if (index) + *index = cnt; + break; + } + ++cnt; + } + + return sec; +} + +char *strlwr(char *s) +{ + int len = strlen(s), i; + + for (i = 0; i < len; ++i) + s[i] = tolower(s[i]); + + return s; +} @@ -0,0 +1,337 @@ +#ifndef _DUTIL_H_ +#define _DUTIL_H_ 1 +/* + SPDX-License-Identifier: GPL-2.0-only + + * Copyright (C) 2007..2009 Arnaldo Carvalho de Melo <acme@redhat.com> + * + * Some functions came from the Linux Kernel sources, copyrighted by a + * cast of dozens, please see the Linux Kernel git history for details. + */ + +#include <stdbool.h> +#include <stddef.h> +#include <string.h> +#include <elf.h> +#include <gelf.h> +#include <asm/bitsperlong.h> +#include "rbtree.h" +#include "list.h" + +#define BITS_PER_LONG __BITS_PER_LONG + +#ifndef __unused +#define __unused __attribute__ ((unused)) +#endif + +#ifndef __pure +#define __pure __attribute__ ((pure)) +#endif + +#define roundup(x,y) ((((x) + ((y) - 1)) / (y)) * (y)) + +static inline __attribute__((const)) bool is_power_of_2(unsigned long n) +{ + return (n != 0 && ((n & (n - 1)) == 0)); +} + +/** + * fls - find last (most-significant) bit set + * @x: the word to search + * + * This is defined the same way as ffs. + * Note fls(0) = 0, fls(1) = 1, fls(0x80000000) = 32. + */ +static __always_inline int fls(int x) +{ + return x ? sizeof(x) * 8 - __builtin_clz(x) : 0; +} + +/** + * fls64 - find last set bit in a 64-bit word + * @x: the word to search + * + * This is defined in a similar way as the libc and compiler builtin + * ffsll, but returns the position of the most significant set bit. + * + * fls64(value) returns 0 if value is 0 or the position of the last + * set bit if value is nonzero. The last (most significant) bit is + * at position 64. + */ +#if BITS_PER_LONG == 32 +static __always_inline int fls64(uint64_t x) +{ + uint32_t h = x >> 32; + if (h) + return fls(h) + 32; + return fls(x); +} +#elif BITS_PER_LONG == 64 +/** + * __fls - find last (most-significant) set bit in a long word + * @word: the word to search + * + * Undefined if no set bit exists, so code should check against 0 first. + */ +static __always_inline unsigned long __fls(unsigned long word) +{ + int num = BITS_PER_LONG - 1; + +#if BITS_PER_LONG == 64 + if (!(word & (~0ul << 32))) { + num -= 32; + word <<= 32; + } +#endif + if (!(word & (~0ul << (BITS_PER_LONG-16)))) { + num -= 16; + word <<= 16; + } + if (!(word & (~0ul << (BITS_PER_LONG-8)))) { + num -= 8; + word <<= 8; + } + if (!(word & (~0ul << (BITS_PER_LONG-4)))) { + num -= 4; + word <<= 4; + } + if (!(word & (~0ul << (BITS_PER_LONG-2)))) { + num -= 2; + word <<= 2; + } + if (!(word & (~0ul << (BITS_PER_LONG-1)))) + num -= 1; + return num; +} + +static __always_inline int fls64(uint64_t x) +{ + if (x == 0) + return 0; + return __fls(x) + 1; +} +#else +#error BITS_PER_LONG not 32 or 64 +#endif + +static inline unsigned fls_long(unsigned long l) +{ + if (sizeof(l) == 4) + return fls(l); + return fls64(l); +} + +/* + * round up to nearest power of two + */ +static inline __attribute__((const)) +unsigned long __roundup_pow_of_two(unsigned long n) +{ + return 1UL << fls_long(n - 1); +} + +/* + * non-constant log of base 2 calculators + * - the arch may override these in asm/bitops.h if they can be implemented + * more efficiently than using fls() and fls64() + * - the arch is not required to handle n==0 if implementing the fallback + */ +static inline __attribute__((const)) +int __ilog2_u32(uint32_t n) +{ + return fls(n) - 1; +} + +static inline __attribute__((const)) +int __ilog2_u64(uint64_t n) +{ + return fls64(n) - 1; +} + +/* + * deal with unrepresentable constant logarithms + */ +extern __attribute__((const)) +int ____ilog2_NaN(void); + +/** + * ilog2 - log of base 2 of 32-bit or a 64-bit unsigned value + * @n - parameter + * + * constant-capable log of base 2 calculation + * - this can be used to initialise global variables from constant data, hence + * the massive ternary operator construction + * + * selects the appropriately-sized optimised version depending on sizeof(n) + */ +#define ilog2(n) \ +( \ + __builtin_constant_p(n) ? ( \ + (n) < 1 ? ____ilog2_NaN() : \ + (n) & (1ULL << 63) ? 63 : \ + (n) & (1ULL << 62) ? 62 : \ + (n) & (1ULL << 61) ? 61 : \ + (n) & (1ULL << 60) ? 60 : \ + (n) & (1ULL << 59) ? 59 : \ + (n) & (1ULL << 58) ? 58 : \ + (n) & (1ULL << 57) ? 57 : \ + (n) & (1ULL << 56) ? 56 : \ + (n) & (1ULL << 55) ? 55 : \ + (n) & (1ULL << 54) ? 54 : \ + (n) & (1ULL << 53) ? 53 : \ + (n) & (1ULL << 52) ? 52 : \ + (n) & (1ULL << 51) ? 51 : \ + (n) & (1ULL << 50) ? 50 : \ + (n) & (1ULL << 49) ? 49 : \ + (n) & (1ULL << 48) ? 48 : \ + (n) & (1ULL << 47) ? 47 : \ + (n) & (1ULL << 46) ? 46 : \ + (n) & (1ULL << 45) ? 45 : \ + (n) & (1ULL << 44) ? 44 : \ + (n) & (1ULL << 43) ? 43 : \ + (n) & (1ULL << 42) ? 42 : \ + (n) & (1ULL << 41) ? 41 : \ + (n) & (1ULL << 40) ? 40 : \ + (n) & (1ULL << 39) ? 39 : \ + (n) & (1ULL << 38) ? 38 : \ + (n) & (1ULL << 37) ? 37 : \ + (n) & (1ULL << 36) ? 36 : \ + (n) & (1ULL << 35) ? 35 : \ + (n) & (1ULL << 34) ? 34 : \ + (n) & (1ULL << 33) ? 33 : \ + (n) & (1ULL << 32) ? 32 : \ + (n) & (1ULL << 31) ? 31 : \ + (n) & (1ULL << 30) ? 30 : \ + (n) & (1ULL << 29) ? 29 : \ + (n) & (1ULL << 28) ? 28 : \ + (n) & (1ULL << 27) ? 27 : \ + (n) & (1ULL << 26) ? 26 : \ + (n) & (1ULL << 25) ? 25 : \ + (n) & (1ULL << 24) ? 24 : \ + (n) & (1ULL << 23) ? 23 : \ + (n) & (1ULL << 22) ? 22 : \ + (n) & (1ULL << 21) ? 21 : \ + (n) & (1ULL << 20) ? 20 : \ + (n) & (1ULL << 19) ? 19 : \ + (n) & (1ULL << 18) ? 18 : \ + (n) & (1ULL << 17) ? 17 : \ + (n) & (1ULL << 16) ? 16 : \ + (n) & (1ULL << 15) ? 15 : \ + (n) & (1ULL << 14) ? 14 : \ + (n) & (1ULL << 13) ? 13 : \ + (n) & (1ULL << 12) ? 12 : \ + (n) & (1ULL << 11) ? 11 : \ + (n) & (1ULL << 10) ? 10 : \ + (n) & (1ULL << 9) ? 9 : \ + (n) & (1ULL << 8) ? 8 : \ + (n) & (1ULL << 7) ? 7 : \ + (n) & (1ULL << 6) ? 6 : \ + (n) & (1ULL << 5) ? 5 : \ + (n) & (1ULL << 4) ? 4 : \ + (n) & (1ULL << 3) ? 3 : \ + (n) & (1ULL << 2) ? 2 : \ + (n) & (1ULL << 1) ? 1 : \ + (n) & (1ULL << 0) ? 0 : \ + ____ilog2_NaN() \ + ) : \ + (sizeof(n) <= 4) ? \ + __ilog2_u32(n) : \ + __ilog2_u64(n) \ + ) + +/** + * roundup_pow_of_two - round the given value up to nearest power of two + * @n - parameter + * + * round the given value up to the nearest power of two + * - the result is undefined when n == 0 + * - this can be used to initialise global variables from constant data + */ +#define roundup_pow_of_two(n) \ +( \ + __builtin_constant_p(n) ? ( \ + (n == 1) ? 1 : \ + (1UL << (ilog2((n) - 1) + 1)) \ + ) : \ + __roundup_pow_of_two(n) \ + ) + +/* We need define two variables, argp_program_version_hook and + argp_program_bug_address, in all programs. argp.h declares these + variables as non-const (which is correct in general). But we can + do better, it is not going to change. So we want to move them into + the .rodata section. Define macros to do the trick. */ +#define ARGP_PROGRAM_VERSION_HOOK_DEF \ + void (*const apvh) (FILE *, struct argp_state *) \ + __asm ("argp_program_version_hook") +#define ARGP_PROGRAM_BUG_ADDRESS_DEF \ + const char *const apba__ __asm ("argp_program_bug_address") + +// Use a list_head so that we keep the original order when iterating in +// the strlist. + +struct str_node { + struct rb_node rb_node; + struct list_head node; + const char *s; + void *priv; +}; + +// list_entries to keep the original order as passed, say, in the command line + +struct strlist { + struct rb_root entries; + struct list_head list_entries; + bool dupstr; +}; + +struct strlist *strlist__new(bool dupstr); +void strlist__delete(struct strlist *slist); + +void strlist__remove(struct strlist *slist, struct str_node *sn); +int strlist__load(struct strlist *slist, const char *filename); +int strlist__add(struct strlist *slist, const char *str); +int __strlist__add(struct strlist *slist, const char *str, void *priv); + +bool strlist__has_entry(struct strlist *slist, const char *entry); + +static inline bool strlist__empty(const struct strlist *slist) +{ + return rb_first(&slist->entries) == NULL; +} + +/** + * strlist__for_each_entry_safe - iterate thru all the strings safe against removal of list entry + * @slist: struct strlist instance to iterate + * @pos: struct str_node iterator + * @n: tmp struct str_node + */ +#define strlist__for_each_entry_safe(slist, pos, n) \ + list_for_each_entry_safe(pos, n, &(slist)->list_entries, node) + +/** + * strstarts - does @str start with @prefix? + * @str: string to examine + * @prefix: prefix to look for. + */ +static inline bool strstarts(const char *str, const char *prefix) +{ + return strncmp(str, prefix, strlen(prefix)) == 0; +} + +void *zalloc(const size_t size); + +Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep, + GElf_Shdr *shp, const char *name, size_t *index); + +#ifndef SHT_GNU_ATTRIBUTES +/* Just a way to check if we're using an old elfutils version */ +static inline int elf_getshdrstrndx(Elf *elf, size_t *dst) +{ + return elf_getshstrndx(elf, dst); +} +#endif + +char *strlwr(char *s); + +#endif /* _DUTIL_H_ */ diff --git a/dwarf_loader.c b/dwarf_loader.c new file mode 100644 index 0000000..4638df7 --- /dev/null +++ b/dwarf_loader.c @@ -0,0 +1,2620 @@ +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2008 Arnaldo Carvalho de Melo <acme@redhat.com> +*/ + +#include <assert.h> +#include <dirent.h> +#include <dwarf.h> +#include <elfutils/libdwfl.h> +#include <errno.h> +#include <fcntl.h> +#include <fnmatch.h> +#include <libelf.h> +#include <obstack.h> +#include <search.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "config.h" +#include "list.h" +#include "dwarves.h" +#include "dutil.h" +#include "pahole_strings.h" +#include "hash.h" + +struct strings *strings; + +#ifndef DW_AT_alignment +#define DW_AT_alignment 0x88 +#endif + +#ifndef DW_AT_GNU_vector +#define DW_AT_GNU_vector 0x2107 +#endif + +#ifndef DW_TAG_GNU_call_site +#define DW_TAG_GNU_call_site 0x4109 +#define DW_TAG_GNU_call_site_parameter 0x410a +#endif + +#define hashtags__fn(key) hash_64(key, HASHTAGS__BITS) + +bool no_bitfield_type_recode = true; + +static void __tag__print_not_supported(uint32_t tag, const char *func) +{ + static bool dwarf_tags_warned[DW_TAG_GNU_call_site_parameter + 64]; + + if (tag < sizeof(dwarf_tags_warned)) { + if (dwarf_tags_warned[tag]) + return; + dwarf_tags_warned[tag] = true; + } + + fprintf(stderr, "%s: tag not supported %#x (%s)!\n", func, + tag, dwarf_tag_name(tag)); +} + +#define tag__print_not_supported(tag) \ + __tag__print_not_supported(tag, __func__) + +struct dwarf_off_ref { + unsigned int from_types : 1; + Dwarf_Off off; +}; + +typedef struct dwarf_off_ref dwarf_off_ref; + +struct dwarf_tag { + struct hlist_node hash_node; + dwarf_off_ref type; + Dwarf_Off id; + union { + dwarf_off_ref abstract_origin; + dwarf_off_ref containing_type; + }; + struct tag *tag; + uint32_t small_id; + strings_t decl_file; + uint16_t decl_line; +}; + +static dwarf_off_ref dwarf_tag__spec(struct dwarf_tag *dtag) +{ + return *(dwarf_off_ref *)(dtag + 1); +} + +static void dwarf_tag__set_spec(struct dwarf_tag *dtag, dwarf_off_ref spec) +{ + *(dwarf_off_ref *)(dtag + 1) = spec; +} + +#define HASHTAGS__BITS 15 +#define HASHTAGS__SIZE (1UL << HASHTAGS__BITS) + +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free + +static void *obstack_zalloc(struct obstack *obstack, size_t size) +{ + void *o = obstack_alloc(obstack, size); + + if (o) + memset(o, 0, size); + return o; +} + +struct dwarf_cu { + struct hlist_head hash_tags[HASHTAGS__SIZE]; + struct hlist_head hash_types[HASHTAGS__SIZE]; + struct obstack obstack; + struct cu *cu; + struct dwarf_cu *type_unit; +}; + +static void dwarf_cu__init(struct dwarf_cu *dcu) +{ + unsigned int i; + for (i = 0; i < HASHTAGS__SIZE; ++i) { + INIT_HLIST_HEAD(&dcu->hash_tags[i]); + INIT_HLIST_HEAD(&dcu->hash_types[i]); + } + obstack_init(&dcu->obstack); + dcu->type_unit = NULL; +} + +static void hashtags__hash(struct hlist_head *hashtable, + struct dwarf_tag *dtag) +{ + struct hlist_head *head = hashtable + hashtags__fn(dtag->id); + hlist_add_head(&dtag->hash_node, head); +} + +static struct dwarf_tag *hashtags__find(const struct hlist_head *hashtable, + const Dwarf_Off id) +{ + if (id == 0) + return NULL; + + struct dwarf_tag *tpos; + struct hlist_node *pos; + uint16_t bucket = hashtags__fn(id); + const struct hlist_head *head = hashtable + bucket; + + hlist_for_each_entry(tpos, pos, head, hash_node) { + if (tpos->id == id) + return tpos; + } + + return NULL; +} + +static void cu__hash(struct cu *cu, struct tag *tag) +{ + struct dwarf_cu *dcu = cu->priv; + struct hlist_head *hashtable = tag__is_tag_type(tag) ? + dcu->hash_types : + dcu->hash_tags; + hashtags__hash(hashtable, tag->priv); +} + +static struct dwarf_tag *dwarf_cu__find_tag_by_ref(const struct dwarf_cu *cu, + const struct dwarf_off_ref *ref) +{ + if (cu == NULL) + return NULL; + if (ref->from_types) { + return NULL; + } + return hashtags__find(cu->hash_tags, ref->off); +} + +static struct dwarf_tag *dwarf_cu__find_type_by_ref(const struct dwarf_cu *dcu, + const struct dwarf_off_ref *ref) +{ + if (dcu == NULL) + return NULL; + if (ref->from_types) { + dcu = dcu->type_unit; + if (dcu == NULL) { + return NULL; + } + } + return hashtags__find(dcu->hash_types, ref->off); +} + +extern struct strings *strings; + +static void *memdup(const void *src, size_t len, struct cu *cu) +{ + void *s = obstack_alloc(&cu->obstack, len); + if (s != NULL) + memcpy(s, src, len); + return s; +} + +/* Number decoding macros. See 7.6 Variable Length Data. */ + +#define get_uleb128_step(var, addr, nth, break) \ + __b = *(addr)++; \ + var |= (uintmax_t) (__b & 0x7f) << (nth * 7); \ + if ((__b & 0x80) == 0) \ + break + +#define get_uleb128_rest_return(var, i, addrp) \ + do { \ + for (; i < 10; ++i) { \ + get_uleb128_step(var, *addrp, i, \ + return var); \ + } \ + /* Other implementations set VALUE to UINT_MAX in this \ + case. So we better do this as well. */ \ + return UINT64_MAX; \ + } while (0) + +static uint64_t __libdw_get_uleb128(uint64_t acc, uint32_t i, + const uint8_t **addrp) +{ + uint8_t __b; + get_uleb128_rest_return (acc, i, addrp); +} + +#define get_uleb128(var, addr) \ + do { \ + uint8_t __b; \ + var = 0; \ + get_uleb128_step(var, addr, 0, break); \ + var = __libdw_get_uleb128 (var, 1, &(addr)); \ + } while (0) + +static uint64_t attr_numeric(Dwarf_Die *die, uint32_t name) +{ + Dwarf_Attribute attr; + uint32_t form; + + if (dwarf_attr(die, name, &attr) == NULL) + return 0; + + form = dwarf_whatform(&attr); + + switch (form) { + case DW_FORM_addr: { + Dwarf_Addr addr; + if (dwarf_formaddr(&attr, &addr) == 0) + return addr; + } + break; + case DW_FORM_data1: + case DW_FORM_data2: + case DW_FORM_data4: + case DW_FORM_data8: + case DW_FORM_sdata: + case DW_FORM_udata: { + Dwarf_Word value; + if (dwarf_formudata(&attr, &value) == 0) + return value; + } + break; + case DW_FORM_flag: + case DW_FORM_flag_present: { + bool value; + if (dwarf_formflag(&attr, &value) == 0) + return value; + } + break; + default: + fprintf(stderr, "DW_AT_<0x%x>=0x%x\n", name, form); + break; + } + + return 0; +} + +static uint64_t dwarf_expr(const uint8_t *expr, uint32_t len __unused) +{ + /* Common case: offset from start of the class */ + if (expr[0] == DW_OP_plus_uconst || + expr[0] == DW_OP_constu) { + uint64_t result; + ++expr; + get_uleb128(result, expr); + return result; + } + + fprintf(stderr, "%s: unhandled %#x DW_OP_ operation\n", + __func__, *expr); + return UINT64_MAX; +} + +static Dwarf_Off attr_offset(Dwarf_Die *die, const uint32_t name) +{ + Dwarf_Attribute attr; + Dwarf_Block block; + + if (dwarf_attr(die, name, &attr) == NULL) + return 0; + + switch (dwarf_whatform(&attr)) { + case DW_FORM_data1: + case DW_FORM_data2: + case DW_FORM_data4: + case DW_FORM_data8: + case DW_FORM_sdata: + case DW_FORM_udata: { + Dwarf_Word value; + if (dwarf_formudata(&attr, &value) == 0) + return value; + break; + } + default: + if (dwarf_formblock(&attr, &block) == 0) + return dwarf_expr(block.data, block.length); + } + + return 0; +} + +static const char *attr_string(Dwarf_Die *die, uint32_t name) +{ + Dwarf_Attribute attr; + if (dwarf_attr(die, name, &attr) != NULL) + return dwarf_formstring(&attr); + return NULL; +} + +static struct dwarf_off_ref attr_type(Dwarf_Die *die, uint32_t attr_name) +{ + Dwarf_Attribute attr; + struct dwarf_off_ref ref; + if (dwarf_attr(die, attr_name, &attr) != NULL) { + Dwarf_Die type_die; + if (dwarf_formref_die(&attr, &type_die) != NULL) { + ref.from_types = attr.form == DW_FORM_ref_sig8; + ref.off = dwarf_dieoffset(&type_die); + return ref; + } + } + memset(&ref, 0, sizeof(ref)); + return ref; +} + +static int attr_location(Dwarf_Die *die, Dwarf_Op **expr, size_t *exprlen) +{ + Dwarf_Attribute attr; + if (dwarf_attr(die, DW_AT_location, &attr) != NULL) { + if (dwarf_getlocation(&attr, expr, exprlen) == 0) + return 0; + } + + return 1; +} + +static void *__tag__alloc(struct dwarf_cu *dcu, size_t size, bool spec) +{ + struct dwarf_tag *dtag = obstack_zalloc(&dcu->obstack, + (sizeof(*dtag) + + (spec ? sizeof(dwarf_off_ref) : 0))); + if (dtag == NULL) + return NULL; + + struct tag *tag = obstack_zalloc(&dcu->cu->obstack, size); + + if (tag == NULL) + return NULL; + + dtag->tag = tag; + tag->priv = dtag; + tag->type = 0; + tag->top_level = 0; + + return tag; +} + +static void *tag__alloc(struct cu *cu, size_t size) +{ + return __tag__alloc(cu->priv, size, false); +} + +static void *tag__alloc_with_spec(struct cu *cu, size_t size) +{ + return __tag__alloc(cu->priv, size, true); +} + +static void tag__init(struct tag *tag, struct cu *cu, Dwarf_Die *die) +{ + struct dwarf_tag *dtag = tag->priv; + + tag->tag = dwarf_tag(die); + + dtag->id = dwarf_dieoffset(die); + + if (tag->tag == DW_TAG_imported_module || + tag->tag == DW_TAG_imported_declaration) + dtag->type = attr_type(die, DW_AT_import); + else + dtag->type = attr_type(die, DW_AT_type); + + dtag->abstract_origin = attr_type(die, DW_AT_abstract_origin); + tag->recursivity_level = 0; + + if (cu->extra_dbg_info) { + int32_t decl_line; + const char *decl_file = dwarf_decl_file(die); + static const char *last_decl_file; + static uint32_t last_decl_file_idx; + + if (decl_file != last_decl_file) { + last_decl_file_idx = strings__add(strings, decl_file); + last_decl_file = decl_file; + } + + dtag->decl_file = last_decl_file_idx; + dwarf_decl_line(die, &decl_line); + dtag->decl_line = decl_line; + } + + INIT_LIST_HEAD(&tag->node); +} + +static struct tag *tag__new(Dwarf_Die *die, struct cu *cu) +{ + struct tag *tag = tag__alloc(cu, sizeof(*tag)); + + if (tag != NULL) + tag__init(tag, cu, die); + + return tag; +} + +static struct ptr_to_member_type *ptr_to_member_type__new(Dwarf_Die *die, + struct cu *cu) +{ + struct ptr_to_member_type *ptr = tag__alloc(cu, sizeof(*ptr)); + + if (ptr != NULL) { + tag__init(&ptr->tag, cu, die); + struct dwarf_tag *dtag = ptr->tag.priv; + dtag->containing_type = attr_type(die, DW_AT_containing_type); + } + + return ptr; +} + +static struct base_type *base_type__new(Dwarf_Die *die, struct cu *cu) +{ + struct base_type *bt = tag__alloc(cu, sizeof(*bt)); + + if (bt != NULL) { + tag__init(&bt->tag, cu, die); + bt->name = strings__add(strings, attr_string(die, DW_AT_name)); + bt->bit_size = attr_numeric(die, DW_AT_byte_size) * 8; + uint64_t encoding = attr_numeric(die, DW_AT_encoding); + bt->is_bool = encoding == DW_ATE_boolean; + bt->is_signed = encoding == DW_ATE_signed; + bt->is_varargs = false; + bt->name_has_encoding = true; + } + + return bt; +} + +static struct array_type *array_type__new(Dwarf_Die *die, struct cu *cu) +{ + struct array_type *at = tag__alloc(cu, sizeof(*at)); + + if (at != NULL) { + tag__init(&at->tag, cu, die); + at->dimensions = 0; + at->nr_entries = NULL; + at->is_vector = dwarf_hasattr(die, DW_AT_GNU_vector); + } + + return at; +} + +static struct string_type *string_type__new(Dwarf_Die *die, struct cu *cu) +{ + struct string_type *st = tag__alloc(cu, sizeof(*st)); + + if (st != NULL) { + tag__init(&st->tag, cu, die); + st->nr_entries = attr_numeric(die, DW_AT_byte_size); + if (st->nr_entries == 0) + st->nr_entries = 1; + } + + return st; +} + +static void namespace__init(struct namespace *namespace, Dwarf_Die *die, + struct cu *cu) +{ + tag__init(&namespace->tag, cu, die); + INIT_LIST_HEAD(&namespace->tags); + namespace->sname = 0; + namespace->name = strings__add(strings, attr_string(die, DW_AT_name)); + namespace->nr_tags = 0; + namespace->shared_tags = 0; +} + +static struct namespace *namespace__new(Dwarf_Die *die, struct cu *cu) +{ + struct namespace *namespace = tag__alloc(cu, sizeof(*namespace)); + + if (namespace != NULL) + namespace__init(namespace, die, cu); + + return namespace; +} + +static void type__init(struct type *type, Dwarf_Die *die, struct cu *cu) +{ + namespace__init(&type->namespace, die, cu); + __type__init(type); + type->size = attr_numeric(die, DW_AT_byte_size); + type->alignment = attr_numeric(die, DW_AT_alignment); + type->declaration = attr_numeric(die, DW_AT_declaration); + dwarf_tag__set_spec(type->namespace.tag.priv, + attr_type(die, DW_AT_specification)); + type->definition_emitted = 0; + type->fwd_decl_emitted = 0; + type->resized = 0; + type->nr_members = 0; + type->nr_static_members = 0; +} + +static struct type *type__new(Dwarf_Die *die, struct cu *cu) +{ + struct type *type = tag__alloc_with_spec(cu, sizeof(*type)); + + if (type != NULL) + type__init(type, die, cu); + + return type; +} + +static struct enumerator *enumerator__new(Dwarf_Die *die, struct cu *cu) +{ + struct enumerator *enumerator = tag__alloc(cu, sizeof(*enumerator)); + + if (enumerator != NULL) { + tag__init(&enumerator->tag, cu, die); + enumerator->name = strings__add(strings, attr_string(die, DW_AT_name)); + enumerator->value = attr_numeric(die, DW_AT_const_value); + } + + return enumerator; +} + +static enum vscope dwarf__location(Dwarf_Die *die, uint64_t *addr, struct location *location) +{ + enum vscope scope = VSCOPE_UNKNOWN; + + if (attr_location(die, &location->expr, &location->exprlen) != 0) + scope = VSCOPE_OPTIMIZED; + else if (location->exprlen != 0) { + Dwarf_Op *expr = location->expr; + switch (expr->atom) { + case DW_OP_addr: + scope = VSCOPE_GLOBAL; + *addr = expr[0].number; + break; + case DW_OP_reg1 ... DW_OP_reg31: + case DW_OP_breg0 ... DW_OP_breg31: + scope = VSCOPE_REGISTER; break; + case DW_OP_fbreg: + scope = VSCOPE_LOCAL; break; + } + } + + return scope; +} + +enum vscope variable__scope(const struct variable *var) +{ + return var->scope; +} + +const char *variable__scope_str(const struct variable *var) +{ + switch (var->scope) { + case VSCOPE_LOCAL: return "local"; + case VSCOPE_GLOBAL: return "global"; + case VSCOPE_REGISTER: return "register"; + case VSCOPE_OPTIMIZED: return "optimized"; + default: break; + }; + + return "unknown"; +} + +static struct variable *variable__new(Dwarf_Die *die, struct cu *cu) +{ + struct variable *var; + bool has_specification; + + has_specification = dwarf_hasattr(die, DW_AT_specification); + if (has_specification) { + var = tag__alloc_with_spec(cu, sizeof(*var)); + } else { + var = tag__alloc(cu, sizeof(*var)); + } + + if (var != NULL) { + tag__init(&var->ip.tag, cu, die); + var->name = strings__add(strings, attr_string(die, DW_AT_name)); + /* variable is visible outside of its enclosing cu */ + var->external = dwarf_hasattr(die, DW_AT_external); + /* non-defining declaration of an object */ + var->declaration = dwarf_hasattr(die, DW_AT_declaration); + var->scope = VSCOPE_UNKNOWN; + var->ip.addr = 0; + if (!var->declaration && cu->has_addr_info) + var->scope = dwarf__location(die, &var->ip.addr, &var->location); + if (has_specification) { + dwarf_tag__set_spec(var->ip.tag.priv, + attr_type(die, DW_AT_specification)); + } + } + + return var; +} + +static int tag__recode_dwarf_bitfield(struct tag *tag, struct cu *cu, uint16_t bit_size) +{ + int id; + type_id_t short_id; + struct tag *recoded; + /* in all the cases the name is at the same offset */ + strings_t name = tag__namespace(tag)->name; + + switch (tag->tag) { + case DW_TAG_typedef: { + const struct dwarf_tag *dtag = tag->priv; + struct dwarf_tag *dtype = dwarf_cu__find_type_by_ref(cu->priv, + &dtag->type); + struct tag *type = dtype->tag; + + id = tag__recode_dwarf_bitfield(type, cu, bit_size); + if (id < 0) + return id; + + struct type *new_typedef = obstack_zalloc(&cu->obstack, + sizeof(*new_typedef)); + if (new_typedef == NULL) + return -ENOMEM; + + recoded = (struct tag *)new_typedef; + recoded->tag = DW_TAG_typedef; + recoded->type = id; + new_typedef->namespace.name = tag__namespace(tag)->name; + } + break; + + case DW_TAG_const_type: + case DW_TAG_volatile_type: { + const struct dwarf_tag *dtag = tag->priv; + struct dwarf_tag *dtype = dwarf_cu__find_type_by_ref(cu->priv, &dtag->type); + struct tag *type = dtype->tag; + + id = tag__recode_dwarf_bitfield(type, cu, bit_size); + if (id == tag->type) + return id; + + recoded = obstack_zalloc(&cu->obstack, sizeof(*recoded)); + if (recoded == NULL) + return -ENOMEM; + + recoded->tag = DW_TAG_volatile_type; + recoded->type = id; + } + break; + + case DW_TAG_base_type: + /* + * Here we must search on the final, core cu, not on + * the dwarf_cu as in dwarf there are no such things + * as base_types of less than 8 bits, etc. + */ + recoded = cu__find_base_type_by_sname_and_size(cu, name, bit_size, &short_id); + if (recoded != NULL) + return short_id; + + struct base_type *new_bt = obstack_zalloc(&cu->obstack, + sizeof(*new_bt)); + if (new_bt == NULL) + return -ENOMEM; + + recoded = (struct tag *)new_bt; + recoded->tag = DW_TAG_base_type; + recoded->top_level = 1; + new_bt->name = name; + new_bt->bit_size = bit_size; + break; + + case DW_TAG_enumeration_type: + /* + * Here we must search on the final, core cu, not on + * the dwarf_cu as in dwarf there are no such things + * as enumeration_types of less than 8 bits, etc. + */ + recoded = cu__find_enumeration_by_sname_and_size(cu, name, bit_size, &short_id); + if (recoded != NULL) + return short_id; + + struct type *alias = tag__type(tag); + struct type *new_enum = obstack_zalloc(&cu->obstack, sizeof(*new_enum)); + if (new_enum == NULL) + return -ENOMEM; + + recoded = (struct tag *)new_enum; + recoded->tag = DW_TAG_enumeration_type; + recoded->top_level = 1; + new_enum->nr_members = alias->nr_members; + /* + * Share the tags + */ + new_enum->namespace.tags.next = &alias->namespace.tags; + new_enum->namespace.shared_tags = 1; + new_enum->namespace.name = name; + new_enum->size = bit_size; + break; + default: + fprintf(stderr, "%s: tag=%s, name=%s, bit_size=%d\n", + __func__, dwarf_tag_name(tag->tag), + strings__ptr(strings, name), bit_size); + return -EINVAL; + } + + uint32_t new_id; + if (cu__add_tag(cu, recoded, &new_id) == 0) + return new_id; + + obstack_free(&cu->obstack, recoded); + return -ENOMEM; +} + +int class_member__dwarf_recode_bitfield(struct class_member *member, + struct cu *cu) +{ + struct dwarf_tag *dtag = member->tag.priv; + struct dwarf_tag *type = dwarf_cu__find_type_by_ref(cu->priv, &dtag->type); + int recoded_type_id; + + if (type == NULL) + return -ENOENT; + + recoded_type_id = tag__recode_dwarf_bitfield(type->tag, cu, member->bitfield_size); + if (recoded_type_id < 0) + return recoded_type_id; + + member->tag.type = recoded_type_id; + return 0; +} + +static struct class_member *class_member__new(Dwarf_Die *die, struct cu *cu, + bool in_union) +{ + struct class_member *member = tag__alloc(cu, sizeof(*member)); + + if (member != NULL) { + tag__init(&member->tag, cu, die); + member->name = strings__add(strings, attr_string(die, DW_AT_name)); + member->is_static = !in_union && !dwarf_hasattr(die, DW_AT_data_member_location); + member->const_value = attr_numeric(die, DW_AT_const_value); + member->alignment = attr_numeric(die, DW_AT_alignment); + member->byte_offset = attr_offset(die, DW_AT_data_member_location); + /* + * Bit offset calculated here is valid only for byte-aligned + * fields. For bitfields on little-endian archs we need to + * adjust them taking into account byte size of the field, + * which might not be yet known. So we'll re-calculate bit + * offset later, in class_member__cache_byte_size. + */ + member->bit_offset = member->byte_offset * 8; + /* + * If DW_AT_byte_size is not present, byte size will be + * determined later in class_member__cache_byte_size using + * base integer/enum type + */ + member->byte_size = attr_numeric(die, DW_AT_byte_size); + member->bitfield_offset = attr_numeric(die, DW_AT_bit_offset); + member->bitfield_size = attr_numeric(die, DW_AT_bit_size); + member->bit_hole = 0; + member->bitfield_end = 0; + member->visited = 0; + member->accessibility = attr_numeric(die, DW_AT_accessibility); + member->virtuality = attr_numeric(die, DW_AT_virtuality); + member->hole = 0; + } + + return member; +} + +static struct parameter *parameter__new(Dwarf_Die *die, struct cu *cu) +{ + struct parameter *parm = tag__alloc(cu, sizeof(*parm)); + + if (parm != NULL) { + tag__init(&parm->tag, cu, die); + parm->name = strings__add(strings, attr_string(die, DW_AT_name)); + } + + return parm; +} + +static struct inline_expansion *inline_expansion__new(Dwarf_Die *die, + struct cu *cu) +{ + struct inline_expansion *exp = tag__alloc(cu, sizeof(*exp)); + + if (exp != NULL) { + struct dwarf_tag *dtag = exp->ip.tag.priv; + + tag__init(&exp->ip.tag, cu, die); + dtag->decl_file = + strings__add(strings, attr_string(die, DW_AT_call_file)); + dtag->decl_line = attr_numeric(die, DW_AT_call_line); + dtag->type = attr_type(die, DW_AT_abstract_origin); + exp->ip.addr = 0; + exp->high_pc = 0; + + if (!cu->has_addr_info) + goto out; + + if (dwarf_lowpc(die, &exp->ip.addr)) + exp->ip.addr = 0; + if (dwarf_lowpc(die, &exp->high_pc)) + exp->high_pc = 0; + + exp->size = exp->high_pc - exp->ip.addr; + if (exp->size == 0) { + Dwarf_Addr base, start; + ptrdiff_t offset = 0; + + while (1) { + offset = dwarf_ranges(die, offset, &base, &start, + &exp->high_pc); + start = (unsigned long)start; + exp->high_pc = (unsigned long)exp->high_pc; + if (offset <= 0) + break; + exp->size += exp->high_pc - start; + if (exp->ip.addr == 0) + exp->ip.addr = start; + } + } + } +out: + return exp; +} + +static struct label *label__new(Dwarf_Die *die, struct cu *cu) +{ + struct label *label = tag__alloc(cu, sizeof(*label)); + + if (label != NULL) { + tag__init(&label->ip.tag, cu, die); + label->name = strings__add(strings, attr_string(die, DW_AT_name)); + if (!cu->has_addr_info || dwarf_lowpc(die, &label->ip.addr)) + label->ip.addr = 0; + } + + return label; +} + +static struct class *class__new(Dwarf_Die *die, struct cu *cu) +{ + struct class *class = tag__alloc_with_spec(cu, sizeof(*class)); + + if (class != NULL) { + type__init(&class->type, die, cu); + INIT_LIST_HEAD(&class->vtable); + class->nr_vtable_entries = + class->nr_holes = + class->nr_bit_holes = + class->padding = + class->bit_padding = 0; + class->priv = NULL; + } + + return class; +} + +static void lexblock__init(struct lexblock *block, struct cu *cu, + Dwarf_Die *die) +{ + Dwarf_Off high_pc; + + if (!cu->has_addr_info || dwarf_lowpc(die, &block->ip.addr)) { + block->ip.addr = 0; + block->size = 0; + } else if (dwarf_highpc(die, &high_pc)) + block->size = 0; + else + block->size = high_pc - block->ip.addr; + + INIT_LIST_HEAD(&block->tags); + + block->size_inline_expansions = + block->nr_inline_expansions = + block->nr_labels = + block->nr_lexblocks = + block->nr_variables = 0; +} + +static struct lexblock *lexblock__new(Dwarf_Die *die, struct cu *cu) +{ + struct lexblock *block = tag__alloc(cu, sizeof(*block)); + + if (block != NULL) { + tag__init(&block->ip.tag, cu, die); + lexblock__init(block, cu, die); + } + + return block; +} + +static void ftype__init(struct ftype *ftype, Dwarf_Die *die, struct cu *cu) +{ + const uint16_t tag = dwarf_tag(die); + assert(tag == DW_TAG_subprogram || tag == DW_TAG_subroutine_type); + + tag__init(&ftype->tag, cu, die); + INIT_LIST_HEAD(&ftype->parms); + ftype->nr_parms = 0; + ftype->unspec_parms = 0; +} + +static struct ftype *ftype__new(Dwarf_Die *die, struct cu *cu) +{ + struct ftype *ftype = tag__alloc(cu, sizeof(*ftype)); + + if (ftype != NULL) + ftype__init(ftype, die, cu); + + return ftype; +} + +static struct function *function__new(Dwarf_Die *die, struct cu *cu) +{ + struct function *func = tag__alloc_with_spec(cu, sizeof(*func)); + + if (func != NULL) { + ftype__init(&func->proto, die, cu); + lexblock__init(&func->lexblock, cu, die); + func->name = strings__add(strings, attr_string(die, DW_AT_name)); + func->linkage_name = strings__add(strings, attr_string(die, DW_AT_MIPS_linkage_name)); + func->inlined = attr_numeric(die, DW_AT_inline); + func->declaration = dwarf_hasattr(die, DW_AT_declaration); + func->external = dwarf_hasattr(die, DW_AT_external); + func->abstract_origin = dwarf_hasattr(die, DW_AT_abstract_origin); + dwarf_tag__set_spec(func->proto.tag.priv, + attr_type(die, DW_AT_specification)); + func->accessibility = attr_numeric(die, DW_AT_accessibility); + func->virtuality = attr_numeric(die, DW_AT_virtuality); + INIT_LIST_HEAD(&func->vtable_node); + INIT_LIST_HEAD(&func->tool_node); + func->vtable_entry = -1; + if (dwarf_hasattr(die, DW_AT_vtable_elem_location)) + func->vtable_entry = attr_offset(die, DW_AT_vtable_elem_location); + func->cu_total_size_inline_expansions = 0; + func->cu_total_nr_inline_expansions = 0; + func->priv = NULL; + } + + return func; +} + +static uint64_t attr_upper_bound(Dwarf_Die *die) +{ + Dwarf_Attribute attr; + + if (dwarf_attr(die, DW_AT_upper_bound, &attr) != NULL) { + Dwarf_Word num; + + if (dwarf_formudata(&attr, &num) == 0) { + return (uintmax_t)num + 1; + } + } else if (dwarf_attr(die, DW_AT_count, &attr) != NULL) { + Dwarf_Word num; + + if (dwarf_formudata(&attr, &num) == 0) { + return (uintmax_t)num; + } + } + + return 0; +} + +static void __cu__tag_not_handled(Dwarf_Die *die, const char *fn) +{ + uint32_t tag = dwarf_tag(die); + + fprintf(stderr, "%s: DW_TAG_%s (%#x) @ <%#llx> not handled!\n", + fn, dwarf_tag_name(tag), tag, + (unsigned long long)dwarf_dieoffset(die)); +} + +static struct tag unsupported_tag; + +#define cu__tag_not_handled(die) __cu__tag_not_handled(die, __FUNCTION__) + +static struct tag *__die__process_tag(Dwarf_Die *die, struct cu *cu, + int toplevel, const char *fn); + +#define die__process_tag(die, cu, toplevel) \ + __die__process_tag(die, cu, toplevel, __FUNCTION__) + +static struct tag *die__create_new_tag(Dwarf_Die *die, struct cu *cu) +{ + struct tag *tag = tag__new(die, cu); + + if (tag != NULL) { + if (dwarf_haschildren(die)) + fprintf(stderr, "%s: %s WITH children!\n", __func__, + dwarf_tag_name(tag->tag)); + } + + return tag; +} + +static struct tag *die__create_new_ptr_to_member_type(Dwarf_Die *die, + struct cu *cu) +{ + struct ptr_to_member_type *ptr = ptr_to_member_type__new(die, cu); + + return ptr ? &ptr->tag : NULL; +} + +static int die__process_class(Dwarf_Die *die, + struct type *class, struct cu *cu); + +static struct tag *die__create_new_class(Dwarf_Die *die, struct cu *cu) +{ + Dwarf_Die child; + struct class *class = class__new(die, cu); + + if (class != NULL && + dwarf_haschildren(die) != 0 && + dwarf_child(die, &child) == 0) { + if (die__process_class(&child, &class->type, cu) != 0) { + class__delete(class, cu); + class = NULL; + } + } + + return class ? &class->type.namespace.tag : NULL; +} + +static int die__process_namespace(Dwarf_Die *die, struct namespace *namespace, + struct cu *cu); + +static struct tag *die__create_new_namespace(Dwarf_Die *die, struct cu *cu) +{ + Dwarf_Die child; + struct namespace *namespace = namespace__new(die, cu); + + if (namespace != NULL && + dwarf_haschildren(die) != 0 && + dwarf_child(die, &child) == 0) { + if (die__process_namespace(&child, namespace, cu) != 0) { + namespace__delete(namespace, cu); + namespace = NULL; + } + } + + return namespace ? &namespace->tag : NULL; +} + +static struct tag *die__create_new_union(Dwarf_Die *die, struct cu *cu) +{ + Dwarf_Die child; + struct type *utype = type__new(die, cu); + + if (utype != NULL && + dwarf_haschildren(die) != 0 && + dwarf_child(die, &child) == 0) { + if (die__process_class(&child, utype, cu) != 0) { + type__delete(utype, cu); + utype = NULL; + } + } + + return utype ? &utype->namespace.tag : NULL; +} + +static struct tag *die__create_new_base_type(Dwarf_Die *die, struct cu *cu) +{ + struct base_type *base = base_type__new(die, cu); + + if (base == NULL) + return NULL; + + if (dwarf_haschildren(die)) + fprintf(stderr, "%s: DW_TAG_base_type WITH children!\n", + __func__); + + return &base->tag; +} + +static struct tag *die__create_new_typedef(Dwarf_Die *die, struct cu *cu) +{ + struct type *tdef = type__new(die, cu); + + if (tdef == NULL) + return NULL; + + if (dwarf_haschildren(die)) { + struct dwarf_tag *dtag = tdef->namespace.tag.priv; + fprintf(stderr, "%s: DW_TAG_typedef %llx WITH children!\n", + __func__, (unsigned long long)dtag->id); + } + + return &tdef->namespace.tag; +} + +static struct tag *die__create_new_array(Dwarf_Die *die, struct cu *cu) +{ + Dwarf_Die child; + /* "64 dimensions will be enough for everybody." acme, 2006 */ + const uint8_t max_dimensions = 64; + uint32_t nr_entries[max_dimensions]; + struct array_type *array = array_type__new(die, cu); + + if (array == NULL) + return NULL; + + if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0) + return &array->tag; + + die = &child; + do { + if (dwarf_tag(die) == DW_TAG_subrange_type) { + nr_entries[array->dimensions++] = attr_upper_bound(die); + if (array->dimensions == max_dimensions) { + fprintf(stderr, "%s: only %u dimensions are " + "supported!\n", + __FUNCTION__, max_dimensions); + break; + } + } else + cu__tag_not_handled(die); + } while (dwarf_siblingof(die, die) == 0); + + array->nr_entries = memdup(nr_entries, + array->dimensions * sizeof(uint32_t), cu); + if (array->nr_entries == NULL) + goto out_free; + + return &array->tag; +out_free: + obstack_free(&cu->obstack, array); + return NULL; +} + +static struct tag *die__create_new_string_type(Dwarf_Die *die, struct cu *cu) +{ + struct string_type *string = string_type__new(die, cu); + + if (string == NULL) + return NULL; + + return &string->tag; +} + +static struct tag *die__create_new_parameter(Dwarf_Die *die, + struct ftype *ftype, + struct lexblock *lexblock, + struct cu *cu) +{ + struct parameter *parm = parameter__new(die, cu); + + if (parm == NULL) + return NULL; + + if (ftype != NULL) + ftype__add_parameter(ftype, parm); + else { + /* + * DW_TAG_formal_parameters on a non DW_TAG_subprogram nor + * DW_TAG_subroutine_type tag happens sometimes, likely due to + * compiler optimizing away a inline expansion (at least this + * was observed in some cases, such as in the Linux kernel + * current_kernel_time function circa 2.6.20-rc5), keep it in + * the lexblock tag list because it can be referenced as an + * DW_AT_abstract_origin in another DW_TAG_formal_parameter. + */ + lexblock__add_tag(lexblock, &parm->tag); + } + + return &parm->tag; +} + +static struct tag *die__create_new_label(Dwarf_Die *die, + struct lexblock *lexblock, + struct cu *cu) +{ + struct label *label = label__new(die, cu); + + if (label == NULL) + return NULL; + + lexblock__add_label(lexblock, label); + return &label->ip.tag; +} + +static struct tag *die__create_new_variable(Dwarf_Die *die, struct cu *cu) +{ + struct variable *var = variable__new(die, cu); + + return var ? &var->ip.tag : NULL; +} + +static struct tag *die__create_new_subroutine_type(Dwarf_Die *die, + struct cu *cu) +{ + Dwarf_Die child; + struct ftype *ftype = ftype__new(die, cu); + struct tag *tag; + + if (ftype == NULL) + return NULL; + + if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0) + goto out; + + die = &child; + do { + uint32_t id; + + switch (dwarf_tag(die)) { + case DW_TAG_subrange_type: // ADA stuff + tag__print_not_supported(dwarf_tag(die)); + continue; + case DW_TAG_formal_parameter: + tag = die__create_new_parameter(die, ftype, NULL, cu); + break; + case DW_TAG_unspecified_parameters: + ftype->unspec_parms = 1; + continue; + default: + tag = die__process_tag(die, cu, 0); + if (tag == NULL) + goto out_delete; + + if (tag == &unsupported_tag) { + tag__print_not_supported(dwarf_tag(die)); + continue; + } + + if (cu__add_tag(cu, tag, &id) < 0) + goto out_delete_tag; + + goto hash; + } + + if (tag == NULL) + goto out_delete; + + if (cu__table_add_tag(cu, tag, &id) < 0) + goto out_delete_tag; +hash: + cu__hash(cu, tag); + struct dwarf_tag *dtag = tag->priv; + dtag->small_id = id; + } while (dwarf_siblingof(die, die) == 0); +out: + return &ftype->tag; +out_delete_tag: + tag__delete(tag, cu); +out_delete: + ftype__delete(ftype, cu); + return NULL; +} + +static struct tag *die__create_new_enumeration(Dwarf_Die *die, struct cu *cu) +{ + Dwarf_Die child; + struct type *enumeration = type__new(die, cu); + + if (enumeration == NULL) + return NULL; + + if (enumeration->size == 0) + enumeration->size = sizeof(int) * 8; + else + enumeration->size *= 8; + + if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0) { + /* Seen on libQtCore.so.4.3.4.debug, + * class QAbstractFileEngineIterator, enum EntryInfoType */ + goto out; + } + + die = &child; + do { + struct enumerator *enumerator; + + if (dwarf_tag(die) != DW_TAG_enumerator) { + cu__tag_not_handled(die); + continue; + } + enumerator = enumerator__new(die, cu); + if (enumerator == NULL) + goto out_delete; + + enumeration__add(enumeration, enumerator); + } while (dwarf_siblingof(die, die) == 0); +out: + return &enumeration->namespace.tag; +out_delete: + enumeration__delete(enumeration, cu); + return NULL; +} + +static int die__process_class(Dwarf_Die *die, struct type *class, + struct cu *cu) +{ + const bool is_union = tag__is_union(&class->namespace.tag); + + do { + switch (dwarf_tag(die)) { + case DW_TAG_subrange_type: // XXX: ADA stuff, its a type tho, will have other entries referencing it... + case DW_TAG_variant_part: // XXX: Rust stuff +#ifdef STB_GNU_UNIQUE + case DW_TAG_GNU_formal_parameter_pack: + case DW_TAG_GNU_template_parameter_pack: + case DW_TAG_GNU_template_template_param: +#endif + case DW_TAG_template_type_parameter: + case DW_TAG_template_value_parameter: + /* + * FIXME: probably we'll have to attach this as a list of + * template parameters to use at class__fprintf time... + * + * See: + * https://gcc.gnu.org/wiki/TemplateParmsDwarf + */ + tag__print_not_supported(dwarf_tag(die)); + continue; + case DW_TAG_inheritance: + case DW_TAG_member: { + struct class_member *member = class_member__new(die, cu, is_union); + + if (member == NULL) + return -ENOMEM; + + if (cu__is_c_plus_plus(cu)) { + uint32_t id; + + if (cu__table_add_tag(cu, &member->tag, &id) < 0) { + class_member__delete(member, cu); + return -ENOMEM; + } + + struct dwarf_tag *dtag = member->tag.priv; + dtag->small_id = id; + } + + type__add_member(class, member); + cu__hash(cu, &member->tag); + } + continue; + default: { + struct tag *tag = die__process_tag(die, cu, 0); + + if (tag == NULL) + return -ENOMEM; + + if (tag == &unsupported_tag) { + tag__print_not_supported(dwarf_tag(die)); + continue; + } + + uint32_t id; + + if (cu__table_add_tag(cu, tag, &id) < 0) { + tag__delete(tag, cu); + return -ENOMEM; + } + + struct dwarf_tag *dtag = tag->priv; + dtag->small_id = id; + + namespace__add_tag(&class->namespace, tag); + cu__hash(cu, tag); + if (tag__is_function(tag)) { + struct function *fself = tag__function(tag); + + if (fself->vtable_entry != -1) + class__add_vtable_entry(type__class(class), fself); + } + continue; + } + } + } while (dwarf_siblingof(die, die) == 0); + + return 0; +} + +static int die__process_namespace(Dwarf_Die *die, struct namespace *namespace, + struct cu *cu) +{ + struct tag *tag; + do { + tag = die__process_tag(die, cu, 0); + if (tag == NULL) + goto out_enomem; + + if (tag == &unsupported_tag) { + tag__print_not_supported(dwarf_tag(die)); + continue; + } + + uint32_t id; + if (cu__table_add_tag(cu, tag, &id) < 0) + goto out_delete_tag; + + struct dwarf_tag *dtag = tag->priv; + dtag->small_id = id; + + namespace__add_tag(namespace, tag); + cu__hash(cu, tag); + } while (dwarf_siblingof(die, die) == 0); + + return 0; +out_delete_tag: + tag__delete(tag, cu); +out_enomem: + return -ENOMEM; +} + +static int die__process_function(Dwarf_Die *die, struct ftype *ftype, + struct lexblock *lexblock, struct cu *cu); + +static int die__create_new_lexblock(Dwarf_Die *die, + struct cu *cu, struct lexblock *father) +{ + struct lexblock *lexblock = lexblock__new(die, cu); + + if (lexblock != NULL) { + if (die__process_function(die, NULL, lexblock, cu) != 0) + goto out_delete; + } + if (father != NULL) + lexblock__add_lexblock(father, lexblock); + return 0; +out_delete: + lexblock__delete(lexblock, cu); + return -ENOMEM; +} + +static struct tag *die__create_new_inline_expansion(Dwarf_Die *die, + struct lexblock *lexblock, + struct cu *cu); + +static int die__process_inline_expansion(Dwarf_Die *die, struct lexblock *lexblock, struct cu *cu) +{ + Dwarf_Die child; + struct tag *tag; + + if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0) + return 0; + + die = &child; + do { + uint32_t id; + + switch (dwarf_tag(die)) { + case DW_TAG_GNU_call_site: + case DW_TAG_GNU_call_site_parameter: + /* + * FIXME: read http://www.dwarfstd.org/ShowIssue.php?issue=100909.2&type=open + * and write proper support. + * + * From a quick read there is not much we can use in + * the existing dwarves tools, so just stop warning the user, + * developers will find these notes if wanting to use in a + * new tool. + */ + continue; + case DW_TAG_lexical_block: + if (die__create_new_lexblock(die, cu, lexblock) != 0) + goto out_enomem; + continue; + case DW_TAG_formal_parameter: + /* + * FIXME: + * So far DW_TAG_inline_routine had just an + * abstract origin, but starting with + * /usr/lib/openoffice.org/basis3.0/program/libdbalx.so + * I realized it really has to be handled as a + * DW_TAG_function... Lets just get the types + * for 1.8, then fix this properly. + * + * cu__tag_not_handled(die); + */ + continue; + case DW_TAG_inlined_subroutine: + tag = die__create_new_inline_expansion(die, lexblock, cu); + break; + case DW_TAG_label: + tag = die__create_new_label(die, lexblock, cu); + break; + default: + tag = die__process_tag(die, cu, 0); + if (tag == NULL) + goto out_enomem; + + if (tag == &unsupported_tag) { + tag__print_not_supported(dwarf_tag(die)); + continue; + } + + if (cu__add_tag(cu, tag, &id) < 0) + goto out_delete_tag; + goto hash; + } + + if (tag == NULL) + goto out_enomem; + + if (cu__table_add_tag(cu, tag, &id) < 0) + goto out_delete_tag; +hash: + cu__hash(cu, tag); + struct dwarf_tag *dtag = tag->priv; + dtag->small_id = id; + } while (dwarf_siblingof(die, die) == 0); + + return 0; +out_delete_tag: + tag__delete(tag, cu); +out_enomem: + return -ENOMEM; +} + +static struct tag *die__create_new_inline_expansion(Dwarf_Die *die, + struct lexblock *lexblock, + struct cu *cu) +{ + struct inline_expansion *exp = inline_expansion__new(die, cu); + + if (exp == NULL) + return NULL; + + if (die__process_inline_expansion(die, lexblock, cu) != 0) { + obstack_free(&cu->obstack, exp); + return NULL; + } + + if (lexblock != NULL) + lexblock__add_inline_expansion(lexblock, exp); + return &exp->ip.tag; +} + +static int die__process_function(Dwarf_Die *die, struct ftype *ftype, + struct lexblock *lexblock, struct cu *cu) +{ + Dwarf_Die child; + struct tag *tag; + + if (!dwarf_haschildren(die) || dwarf_child(die, &child) != 0) + return 0; + + die = &child; + do { + uint32_t id; + + switch (dwarf_tag(die)) { + case DW_TAG_GNU_call_site: + case DW_TAG_GNU_call_site_parameter: + /* + * XXX: read http://www.dwarfstd.org/ShowIssue.php?issue=100909.2&type=open + * and write proper support. + * + * From a quick read there is not much we can use in + * the existing dwarves tools, so just stop warning the user, + * developers will find these notes if wanting to use in a + * new tool. + */ + continue; + case DW_TAG_dwarf_procedure: + /* + * Ignore it, just scope expressions, that we have no use for (so far). + */ + continue; +#ifdef STB_GNU_UNIQUE + case DW_TAG_GNU_formal_parameter_pack: + case DW_TAG_GNU_template_parameter_pack: + case DW_TAG_GNU_template_template_param: +#endif + case DW_TAG_template_type_parameter: + case DW_TAG_template_value_parameter: + /* FIXME: probably we'll have to attach this as a list of + * template parameters to use at class__fprintf time... + * See die__process_class */ + tag__print_not_supported(dwarf_tag(die)); + continue; + case DW_TAG_formal_parameter: + tag = die__create_new_parameter(die, ftype, lexblock, cu); + break; + case DW_TAG_variable: + tag = die__create_new_variable(die, cu); + if (tag == NULL) + goto out_enomem; + lexblock__add_variable(lexblock, tag__variable(tag)); + break; + case DW_TAG_unspecified_parameters: + if (ftype != NULL) + ftype->unspec_parms = 1; + continue; + case DW_TAG_label: + tag = die__create_new_label(die, lexblock, cu); + break; + case DW_TAG_inlined_subroutine: + tag = die__create_new_inline_expansion(die, lexblock, cu); + break; + case DW_TAG_lexical_block: + if (die__create_new_lexblock(die, cu, lexblock) != 0) + goto out_enomem; + continue; + default: + tag = die__process_tag(die, cu, 0); + + if (tag == NULL) + goto out_enomem; + + if (tag == &unsupported_tag) { + tag__print_not_supported(dwarf_tag(die)); + continue; + } + + if (cu__add_tag(cu, tag, &id) < 0) + goto out_delete_tag; + + goto hash; + } + + if (tag == NULL) + goto out_enomem; + + if (cu__table_add_tag(cu, tag, &id) < 0) + goto out_delete_tag; +hash: + cu__hash(cu, tag); + struct dwarf_tag *dtag = tag->priv; + dtag->small_id = id; + } while (dwarf_siblingof(die, die) == 0); + + return 0; +out_delete_tag: + tag__delete(tag, cu); +out_enomem: + return -ENOMEM; +} + +static struct tag *die__create_new_function(Dwarf_Die *die, struct cu *cu) +{ + struct function *function = function__new(die, cu); + + if (function != NULL && + die__process_function(die, &function->proto, + &function->lexblock, cu) != 0) { + function__delete(function, cu); + function = NULL; + } + + return function ? &function->proto.tag : NULL; +} + +static struct tag *__die__process_tag(Dwarf_Die *die, struct cu *cu, + int top_level, const char *fn) +{ + struct tag *tag; + + switch (dwarf_tag(die)) { + case DW_TAG_imported_unit: + return NULL; // We don't support imported units yet, so to avoid segfaults + case DW_TAG_array_type: + tag = die__create_new_array(die, cu); break; + case DW_TAG_string_type: // FORTRAN stuff, looks like an array + tag = die__create_new_string_type(die, cu); break; + case DW_TAG_base_type: + tag = die__create_new_base_type(die, cu); break; + case DW_TAG_const_type: + case DW_TAG_imported_declaration: + case DW_TAG_imported_module: + case DW_TAG_pointer_type: + case DW_TAG_reference_type: + case DW_TAG_restrict_type: + case DW_TAG_unspecified_type: + case DW_TAG_volatile_type: + tag = die__create_new_tag(die, cu); break; + case DW_TAG_ptr_to_member_type: + tag = die__create_new_ptr_to_member_type(die, cu); break; + case DW_TAG_enumeration_type: + tag = die__create_new_enumeration(die, cu); break; + case DW_TAG_namespace: + tag = die__create_new_namespace(die, cu); break; + case DW_TAG_class_type: + case DW_TAG_interface_type: + case DW_TAG_structure_type: + tag = die__create_new_class(die, cu); break; + case DW_TAG_subprogram: + tag = die__create_new_function(die, cu); break; + case DW_TAG_subroutine_type: + tag = die__create_new_subroutine_type(die, cu); break; + case DW_TAG_rvalue_reference_type: + case DW_TAG_typedef: + tag = die__create_new_typedef(die, cu); break; + case DW_TAG_union_type: + tag = die__create_new_union(die, cu); break; + case DW_TAG_variable: + tag = die__create_new_variable(die, cu); break; + default: + __cu__tag_not_handled(die, fn); + /* fall thru */ + case DW_TAG_dwarf_procedure: + /* + * Ignore it, just scope expressions, that we have no use for (so far). + */ + tag = &unsupported_tag; + break; + } + + if (tag != NULL) + tag->top_level = top_level; + + return tag; +} + +static int die__process_unit(Dwarf_Die *die, struct cu *cu) +{ + do { + struct tag *tag = die__process_tag(die, cu, 1); + if (tag == NULL) + return -ENOMEM; + + if (tag == &unsupported_tag) { + // XXX special case DW_TAG_dwarf_procedure, appears when looking at a recent ~/bin/perf + // Investigate later how to properly support this... + if (dwarf_tag(die) != DW_TAG_dwarf_procedure) + tag__print_not_supported(dwarf_tag(die)); + continue; + } + + uint32_t id; + cu__add_tag(cu, tag, &id); + cu__hash(cu, tag); + struct dwarf_tag *dtag = tag->priv; + dtag->small_id = id; + } while (dwarf_siblingof(die, die) == 0); + + return 0; +} + +static void __tag__print_type_not_found(struct tag *tag, const char *func) +{ + struct dwarf_tag *dtag = tag->priv; + fprintf(stderr, "%s: couldn't find %#llx type for %#llx (%s)!\n", func, + (unsigned long long)dtag->type.off, (unsigned long long)dtag->id, + dwarf_tag_name(tag->tag)); +} + +#define tag__print_type_not_found(tag) \ + __tag__print_type_not_found(tag, __func__) + +static void ftype__recode_dwarf_types(struct tag *tag, struct cu *cu); + +static int namespace__recode_dwarf_types(struct tag *tag, struct cu *cu) +{ + struct tag *pos; + struct dwarf_cu *dcu = cu->priv; + struct namespace *ns = tag__namespace(tag); + + namespace__for_each_tag(ns, pos) { + struct dwarf_tag *dtype; + struct dwarf_tag *dpos = pos->priv; + + if (tag__has_namespace(pos)) { + if (namespace__recode_dwarf_types(pos, cu)) + return -1; + continue; + } + + switch (pos->tag) { + case DW_TAG_member: { + struct class_member *member = tag__class_member(pos); + /* + * We may need to recode the type, possibly creating a + * suitably sized new base_type + */ + if (member->bitfield_size != 0 && !no_bitfield_type_recode) { + if (class_member__dwarf_recode_bitfield(member, cu)) + return -1; + continue; + } + } + break; + case DW_TAG_subroutine_type: + case DW_TAG_subprogram: + ftype__recode_dwarf_types(pos, cu); + break; + case DW_TAG_imported_module: + dtype = dwarf_cu__find_tag_by_ref(dcu, &dpos->type); + goto check_type; + /* Can be for both types and non types */ + case DW_TAG_imported_declaration: + dtype = dwarf_cu__find_tag_by_ref(dcu, &dpos->type); + if (dtype != NULL) + goto next; + goto find_type; + } + + if (dpos->type.off == 0) /* void */ + continue; +find_type: + dtype = dwarf_cu__find_type_by_ref(dcu, &dpos->type); +check_type: + if (dtype == NULL) { + tag__print_type_not_found(pos); + continue; + } +next: + pos->type = dtype->small_id; + } + return 0; +} + +static void type__recode_dwarf_specification(struct tag *tag, struct cu *cu) +{ + struct dwarf_tag *dtype; + struct type *t = tag__type(tag); + dwarf_off_ref specification = dwarf_tag__spec(tag->priv); + + if (t->namespace.name != 0 || specification.off == 0) + return; + + dtype = dwarf_cu__find_type_by_ref(cu->priv, &specification); + if (dtype != NULL) + t->namespace.name = tag__namespace(dtype->tag)->name; + else { + struct dwarf_tag *dtag = tag->priv; + + fprintf(stderr, + "%s: couldn't find name for " + "class %#llx, specification=%#llx\n", __func__, + (unsigned long long)dtag->id, + (unsigned long long)specification.off); + } +} + +static void __tag__print_abstract_origin_not_found(struct tag *tag, + const char *func) +{ + struct dwarf_tag *dtag = tag->priv; + fprintf(stderr, + "%s: couldn't find %#llx abstract_origin for %#llx (%s)!\n", + func, (unsigned long long)dtag->abstract_origin.off, + (unsigned long long)dtag->id, + dwarf_tag_name(tag->tag)); +} + +#define tag__print_abstract_origin_not_found(tag ) \ + __tag__print_abstract_origin_not_found(tag, __func__) + +static void ftype__recode_dwarf_types(struct tag *tag, struct cu *cu) +{ + struct parameter *pos; + struct dwarf_cu *dcu = cu->priv; + struct ftype *type = tag__ftype(tag); + + ftype__for_each_parameter(type, pos) { + struct dwarf_tag *dpos = pos->tag.priv; + struct dwarf_tag *dtype; + + if (dpos->type.off == 0) { + if (dpos->abstract_origin.off == 0) { + /* Function without parameters */ + pos->tag.type = 0; + continue; + } + dtype = dwarf_cu__find_tag_by_ref(dcu, &dpos->abstract_origin); + if (dtype == NULL) { + tag__print_abstract_origin_not_found(&pos->tag); + continue; + } + pos->name = tag__parameter(dtype->tag)->name; + pos->tag.type = dtype->tag->type; + continue; + } + + dtype = dwarf_cu__find_type_by_ref(dcu, &dpos->type); + if (dtype == NULL) { + tag__print_type_not_found(&pos->tag); + continue; + } + pos->tag.type = dtype->small_id; + } +} + +static void lexblock__recode_dwarf_types(struct lexblock *tag, struct cu *cu) +{ + struct tag *pos; + struct dwarf_cu *dcu = cu->priv; + + list_for_each_entry(pos, &tag->tags, node) { + struct dwarf_tag *dpos = pos->priv; + struct dwarf_tag *dtype; + + switch (pos->tag) { + case DW_TAG_lexical_block: + lexblock__recode_dwarf_types(tag__lexblock(pos), cu); + continue; + case DW_TAG_inlined_subroutine: + dtype = dwarf_cu__find_tag_by_ref(dcu, &dpos->type); + if (dtype == NULL) { + tag__print_type_not_found(pos); + continue; + } + ftype__recode_dwarf_types(dtype->tag, cu); + continue; + + case DW_TAG_formal_parameter: + if (dpos->type.off != 0) + break; + + struct parameter *fp = tag__parameter(pos); + dtype = dwarf_cu__find_tag_by_ref(dcu, + &dpos->abstract_origin); + if (dtype == NULL) { + tag__print_abstract_origin_not_found(pos); + continue; + } + fp->name = tag__parameter(dtype->tag)->name; + pos->type = dtype->tag->type; + continue; + + case DW_TAG_variable: + if (dpos->type.off != 0) + break; + + struct variable *var = tag__variable(pos); + + if (dpos->abstract_origin.off == 0) { + /* + * DW_TAG_variable completely empty was + * found on libQtGui.so.4.3.4.debug + * <3><d6ea1>: Abbrev Number: 164 (DW_TAG_variable) + */ + continue; + } + + dtype = dwarf_cu__find_tag_by_ref(dcu, + &dpos->abstract_origin); + if (dtype == NULL) { + tag__print_abstract_origin_not_found(pos); + continue; + } + var->name = tag__variable(dtype->tag)->name; + pos->type = dtype->tag->type; + continue; + + case DW_TAG_label: { + struct label *l = tag__label(pos); + + if (dpos->abstract_origin.off == 0) + continue; + + dtype = dwarf_cu__find_tag_by_ref(dcu, &dpos->abstract_origin); + if (dtype != NULL) + l->name = tag__label(dtype->tag)->name; + else + tag__print_abstract_origin_not_found(pos); + } + continue; + } + + dtype = dwarf_cu__find_type_by_ref(dcu, &dpos->type); + if (dtype == NULL) { + tag__print_type_not_found(pos); + continue; + } + pos->type = dtype->small_id; + } +} + +static int tag__recode_dwarf_type(struct tag *tag, struct cu *cu) +{ + struct dwarf_tag *dtag = tag->priv; + struct dwarf_tag *dtype; + + /* Check if this is an already recoded bitfield */ + if (dtag == NULL) + return 0; + + if (tag__is_type(tag)) + type__recode_dwarf_specification(tag, cu); + + if (tag__has_namespace(tag)) + return namespace__recode_dwarf_types(tag, cu); + + switch (tag->tag) { + case DW_TAG_subprogram: { + struct function *fn = tag__function(tag); + + if (fn->name == 0) { + dwarf_off_ref specification = dwarf_tag__spec(dtag); + if (dtag->abstract_origin.off == 0 && + specification.off == 0) { + /* + * Found on libQtGui.so.4.3.4.debug + * <3><1423de>: Abbrev Number: 209 (DW_TAG_subprogram) + * <1423e0> DW_AT_declaration : 1 + */ + return 0; + } + dtype = dwarf_cu__find_tag_by_ref(cu->priv, &dtag->abstract_origin); + if (dtype == NULL) + dtype = dwarf_cu__find_tag_by_ref(cu->priv, &specification); + if (dtype != NULL) + fn->name = tag__function(dtype->tag)->name; + else { + fprintf(stderr, + "%s: couldn't find name for " + "function %#llx, abstract_origin=%#llx," + " specification=%#llx\n", __func__, + (unsigned long long)dtag->id, + (unsigned long long)dtag->abstract_origin.off, + (unsigned long long)specification.off); + } + } + lexblock__recode_dwarf_types(&fn->lexblock, cu); + } + /* Fall thru */ + + case DW_TAG_subroutine_type: + ftype__recode_dwarf_types(tag, cu); + /* Fall thru, for the function return type */ + break; + + case DW_TAG_lexical_block: + lexblock__recode_dwarf_types(tag__lexblock(tag), cu); + return 0; + + case DW_TAG_ptr_to_member_type: { + struct ptr_to_member_type *pt = tag__ptr_to_member_type(tag); + + dtype = dwarf_cu__find_type_by_ref(cu->priv, &dtag->containing_type); + if (dtype != NULL) + pt->containing_type = dtype->small_id; + else { + fprintf(stderr, + "%s: couldn't find type for " + "containing_type %#llx, containing_type=%#llx\n", + __func__, + (unsigned long long)dtag->id, + (unsigned long long)dtag->containing_type.off); + } + } + break; + + case DW_TAG_namespace: + return namespace__recode_dwarf_types(tag, cu); + /* Damn, DW_TAG_inlined_subroutine is an special case + as dwarf_tag->id is in fact an abtract origin, i.e. must be + looked up in the tags_table, not in the types_table. + The others also point to routines, so are in tags_table */ + case DW_TAG_inlined_subroutine: + case DW_TAG_imported_module: + dtype = dwarf_cu__find_tag_by_ref(cu->priv, &dtag->type); + goto check_type; + /* Can be for both types and non types */ + case DW_TAG_imported_declaration: + dtype = dwarf_cu__find_tag_by_ref(cu->priv, &dtag->type); + if (dtype != NULL) + goto out; + goto find_type; + case DW_TAG_variable: { + struct variable *var = tag__variable(tag); + dwarf_off_ref specification = dwarf_tag__spec(dtag); + + if (specification.off) { + dtype = dwarf_cu__find_tag_by_ref(cu->priv, &specification); + if (dtype) + var->spec = tag__variable(dtype->tag); + } + } + + } + + if (dtag->type.off == 0) { + tag->type = 0; /* void */ + return 0; + } + +find_type: + dtype = dwarf_cu__find_type_by_ref(cu->priv, &dtag->type); +check_type: + if (dtype == NULL) { + tag__print_type_not_found(tag); + return 0; + } +out: + tag->type = dtype->small_id; + return 0; +} + +static int cu__recode_dwarf_types_table(struct cu *cu, + struct ptr_table *pt, + uint32_t i) +{ + for (; i < pt->nr_entries; ++i) { + struct tag *tag = pt->entries[i]; + + if (tag != NULL) /* void, see cu__new */ + if (tag__recode_dwarf_type(tag, cu)) + return -1; + } + return 0; +} + +static int cu__recode_dwarf_types(struct cu *cu) +{ + if (cu__recode_dwarf_types_table(cu, &cu->types_table, 1) || + cu__recode_dwarf_types_table(cu, &cu->tags_table, 0) || + cu__recode_dwarf_types_table(cu, &cu->functions_table, 0)) + return -1; + return 0; +} + +static const char *dwarf_tag__decl_file(const struct tag *tag, + const struct cu *cu) +{ + struct dwarf_tag *dtag = tag->priv; + return cu->extra_dbg_info ? + strings__ptr(strings, dtag->decl_file) : NULL; +} + +static uint32_t dwarf_tag__decl_line(const struct tag *tag, + const struct cu *cu) +{ + struct dwarf_tag *dtag = tag->priv; + return cu->extra_dbg_info ? dtag->decl_line : 0; +} + +static unsigned long long dwarf_tag__orig_id(const struct tag *tag, + const struct cu *cu) +{ + struct dwarf_tag *dtag = tag->priv; + return cu->extra_dbg_info ? dtag->id : 0; +} + +static const char *dwarf__strings_ptr(const struct cu *cu __unused, + strings_t s) +{ + return s ? strings__ptr(strings, s) : NULL; +} + +struct debug_fmt_ops dwarf__ops; + +static int die__process(Dwarf_Die *die, struct cu *cu) +{ + Dwarf_Die child; + const uint16_t tag = dwarf_tag(die); + + if (tag == DW_TAG_partial_unit) { + static bool warned; + + if (!warned) { + fprintf(stderr, "WARNING: DW_TAG_partial_unit used, some types will not be considered!\n" + " Probably this was optimized using a tool like 'dwz'\n" + " A future version of pahole will support this.\n"); + warned = true; + } + return 0; // so that other units can be processed + } + + if (tag != DW_TAG_compile_unit && tag != DW_TAG_type_unit) { + fprintf(stderr, "%s: DW_TAG_compile_unit, DW_TAG_type_unit or DW_TAG_partial_unit expected got %s!\n", + __FUNCTION__, dwarf_tag_name(tag)); + return -EINVAL; + } + + cu->language = attr_numeric(die, DW_AT_language); + + if (dwarf_child(die, &child) == 0) { + int err = die__process_unit(&child, cu); + if (err) + return err; + } + + if (dwarf_siblingof(die, die) == 0) + fprintf(stderr, "%s: got %s unexpected tag after " + "DW_TAG_compile_unit!\n", + __FUNCTION__, dwarf_tag_name(tag)); + + return 0; +} + +static int die__process_and_recode(Dwarf_Die *die, struct cu *cu) +{ + int ret = die__process(die, cu); + if (ret != 0) + return ret; + return cu__recode_dwarf_types(cu); +} + +static int class_member__cache_byte_size(struct tag *tag, struct cu *cu, + void *cookie) +{ + struct class_member *member = tag__class_member(tag); + struct conf_load *conf_load = cookie; + + if (tag__is_class_member(tag)) { + if (member->is_static) + return 0; + } else if (tag->tag != DW_TAG_inheritance) { + return 0; + } + + if (member->bitfield_size == 0) { + member->byte_size = tag__size(tag, cu); + member->bit_size = member->byte_size * 8; + return 0; + } + + /* + * Try to figure out byte size, if it's not directly provided in DWARF + */ + if (member->byte_size == 0) { + struct tag *type = tag__strip_typedefs_and_modifiers(&member->tag, cu); + member->byte_size = tag__size(type, cu); + if (member->byte_size == 0) { + int bit_size; + if (tag__is_enumeration(type)) { + bit_size = tag__type(type)->size; + } else { + struct base_type *bt = tag__base_type(type); + bit_size = bt->bit_size ? bt->bit_size : base_type__name_to_size(bt, cu); + } + member->byte_size = (bit_size + 7) / 8 * 8; + } + } + member->bit_size = member->byte_size * 8; + + /* + * XXX: after all the attemps to determine byte size, we might still + * be unsuccessful, because base_type__name_to_size doesn't know about + * the base_type name, so one has to add there when such base_type + * isn't found. pahole will put zero on the struct output so it should + * be easy to spot the name when such unlikely thing happens. + */ + if (member->byte_size == 0) { + member->bitfield_offset = 0; + return 0; + } + + /* + * For little-endian architectures, DWARF data emitted by gcc/clang + * specifies bitfield offset as an offset from the highest-order bit + * of an underlying integral type (e.g., int) to a highest-order bit + * of a bitfield. E.g., for bitfield taking first 5 bits of int-backed + * bitfield, bit offset will be 27 (sizeof(int) - 0 offset - 5 bit + * size), which is very counter-intuitive and isn't a natural + * extension of byte offset, which on little-endian points to + * lowest-order byte. So here we re-adjust bitfield offset to be an + * offset from lowest-order bit of underlying integral type to + * a lowest-order bit of a bitfield. This makes bitfield offset + * a natural extension of byte offset for bitfields and is uniform + * with how big-endian bit offsets work. + */ + if (cu->little_endian) { + member->bitfield_offset = member->bit_size - member->bitfield_offset - member->bitfield_size; + } + member->bit_offset = member->byte_offset * 8 + member->bitfield_offset; + + /* make sure bitfield offset is non-negative */ + if (member->bitfield_offset < 0) { + member->bitfield_offset += member->bit_size; + member->byte_offset -= member->byte_size; + member->bit_offset = member->byte_offset * 8 + member->bitfield_offset; + } + /* align on underlying base type natural alignment boundary */ + member->bitfield_offset += (member->byte_offset % member->byte_size) * 8; + member->byte_offset = member->bit_offset / member->bit_size * member->bit_size / 8; + if (member->bitfield_offset >= member->bit_size) { + member->bitfield_offset -= member->bit_size; + member->byte_offset += member->byte_size; + } + + if (conf_load && conf_load->fixup_silly_bitfields && + member->byte_size == 8 * member->bitfield_size) { + member->bitfield_size = 0; + member->bitfield_offset = 0; + } + + return 0; +} + +static int finalize_cu(struct cus *cus, struct cu *cu, struct dwarf_cu *dcu, + struct conf_load *conf) +{ + base_type_name_to_size_table__init(strings); + cu__for_all_tags(cu, class_member__cache_byte_size, conf); + if (conf && conf->steal) { + return conf->steal(cu, conf); + } + return LSK__KEEPIT; +} + +static int finalize_cu_immediately(struct cus *cus, struct cu *cu, + struct dwarf_cu *dcu, + struct conf_load *conf) +{ + int lsk = finalize_cu(cus, cu, dcu, conf); + switch (lsk) { + case LSK__DELETE: + cu__delete(cu); + break; + case LSK__STOP_LOADING: + break; + case LSK__KEEPIT: + if (!cu->extra_dbg_info) + obstack_free(&dcu->obstack, NULL); + cus__add(cus, cu); + break; + } + return lsk; +} + +static int cus__load_debug_types(struct cus *cus, struct conf_load *conf, + Dwfl_Module *mod, Dwarf *dw, Elf *elf, + const char *filename, + const unsigned char *build_id, + int build_id_len, + struct cu **cup, struct dwarf_cu *dcup) +{ + Dwarf_Off off = 0, noff, type_off; + size_t cuhl; + uint8_t pointer_size, offset_size; + uint64_t signature; + + *cup = NULL; + + while (dwarf_next_unit(dw, off, &noff, &cuhl, NULL, NULL, &pointer_size, + &offset_size, &signature, &type_off) + == 0) { + + if (*cup == NULL) { + struct cu *cu; + + cu = cu__new("", pointer_size, build_id, + build_id_len, filename); + if (cu == NULL) { + return DWARF_CB_ABORT; + } + + cu->uses_global_strings = true; + cu->elf = elf; + cu->dwfl = mod; + cu->extra_dbg_info = conf ? conf->extra_dbg_info : 0; + cu->has_addr_info = conf ? conf->get_addr_info : 0; + + GElf_Ehdr ehdr; + if (gelf_getehdr(elf, &ehdr) == NULL) { + return DWARF_CB_ABORT; + } + cu->little_endian = ehdr.e_ident[EI_DATA] == ELFDATA2LSB; + + dwarf_cu__init(dcup); + dcup->cu = cu; + /* Funny hack. */ + dcup->type_unit = dcup; + cu->priv = dcup; + cu->dfops = &dwarf__ops; + + *cup = cu; + } + + Dwarf_Die die_mem; + Dwarf_Die *cu_die = dwarf_offdie_types(dw, off + cuhl, + &die_mem); + + if (die__process(cu_die, *cup) != 0) + return DWARF_CB_ABORT; + + off = noff; + } + + if (*cup != NULL && cu__recode_dwarf_types(*cup) != 0) + return DWARF_CB_ABORT; + + return 0; +} + +static int cus__load_module(struct cus *cus, struct conf_load *conf, + Dwfl_Module *mod, Dwarf *dw, Elf *elf, + const char *filename) +{ + Dwarf_Off off = 0, noff; + size_t cuhl; + const unsigned char *build_id = NULL; + uint8_t pointer_size, offset_size; + +#ifdef HAVE_DWFL_MODULE_BUILD_ID + GElf_Addr vaddr; + int build_id_len = dwfl_module_build_id(mod, &build_id, &vaddr); +#else + int build_id_len = 0; +#endif + + struct cu *type_cu; + struct dwarf_cu type_dcu; + int type_lsk = LSK__KEEPIT; + + int res = cus__load_debug_types(cus, conf, mod, dw, elf, filename, + build_id, build_id_len, + &type_cu, &type_dcu); + if (res != 0) { + return res; + } + + if (type_cu != NULL) { + type_lsk = finalize_cu(cus, type_cu, &type_dcu, conf); + if (type_lsk == LSK__KEEPIT) { + cus__add(cus, type_cu); + } + } + + while (dwarf_nextcu(dw, off, &noff, &cuhl, NULL, &pointer_size, + &offset_size) == 0) { + Dwarf_Die die_mem; + Dwarf_Die *cu_die = dwarf_offdie(dw, off + cuhl, &die_mem); + + if (cu_die == NULL) + break; + + /* + * DW_AT_name in DW_TAG_compile_unit can be NULL, first + * seen in: + * /usr/libexec/gcc/x86_64-redhat-linux/4.3.2/ecj1.debug + */ + const char *name = attr_string(cu_die, DW_AT_name); + struct cu *cu = cu__new(name ?: "", pointer_size, + build_id, build_id_len, filename); + if (cu == NULL) + return DWARF_CB_ABORT; + cu->uses_global_strings = true; + cu->elf = elf; + cu->dwfl = mod; + cu->extra_dbg_info = conf ? conf->extra_dbg_info : 0; + cu->has_addr_info = conf ? conf->get_addr_info : 0; + + GElf_Ehdr ehdr; + if (gelf_getehdr(elf, &ehdr) == NULL) { + return DWARF_CB_ABORT; + } + cu->little_endian = ehdr.e_ident[EI_DATA] == ELFDATA2LSB; + + struct dwarf_cu dcu; + + dwarf_cu__init(&dcu); + dcu.cu = cu; + dcu.type_unit = type_cu ? &type_dcu : NULL; + cu->priv = &dcu; + cu->dfops = &dwarf__ops; + + if (die__process_and_recode(cu_die, cu) != 0) + return DWARF_CB_ABORT; + + if (finalize_cu_immediately(cus, cu, &dcu, conf) + == LSK__STOP_LOADING) + return DWARF_CB_ABORT; + + off = noff; + } + + if (type_lsk == LSK__DELETE) + cu__delete(type_cu); + + return DWARF_CB_OK; +} + +struct process_dwflmod_parms { + struct cus *cus; + struct conf_load *conf; + const char *filename; + uint32_t nr_dwarf_sections_found; +}; + +static int cus__process_dwflmod(Dwfl_Module *dwflmod, + void **userdata __unused, + const char *name __unused, + Dwarf_Addr base __unused, + void *arg) +{ + struct process_dwflmod_parms *parms = arg; + struct cus *cus = parms->cus; + + GElf_Addr dwflbias; + /* + * Does the relocation and saves the elf for later processing + * by the stealer, such as pahole_stealer, so that it don't + * have to create another Elf instance just to do things like + * reading this ELF file symtab to do CTF encoding of the + * DW_TAG_suprogram tags (functions). + */ + Elf *elf = dwfl_module_getelf(dwflmod, &dwflbias); + + Dwarf_Addr dwbias; + Dwarf *dw = dwfl_module_getdwarf(dwflmod, &dwbias); + + int err = DWARF_CB_OK; + if (dw != NULL) { + ++parms->nr_dwarf_sections_found; + err = cus__load_module(cus, parms->conf, dwflmod, dw, elf, + parms->filename); + } + /* + * XXX We will fall back to try finding other debugging + * formats (CTF), so no point in telling this to the user + * Use for debugging. + * else + * fprintf(stderr, + * "%s: can't get debug context descriptor: %s\n", + * __func__, dwfl_errmsg(-1)); + */ + + return err; +} + +static int cus__process_file(struct cus *cus, struct conf_load *conf, int fd, + const char *filename) +{ + /* Duplicate an fd for dwfl_report_offline to swallow. */ + int dwfl_fd = dup(fd); + + if (dwfl_fd < 0) + return -1; + + /* + * Use libdwfl in a trivial way to open the libdw handle for us. + * This takes care of applying relocations to DWARF data in ET_REL + * files. + */ + + static const Dwfl_Callbacks callbacks = { + .section_address = dwfl_offline_section_address, + .find_debuginfo = dwfl_standard_find_debuginfo, + /* We use this table for core files too. */ + .find_elf = dwfl_build_id_find_elf, + }; + + Dwfl *dwfl = dwfl_begin(&callbacks); + + if (dwfl_report_offline(dwfl, filename, filename, dwfl_fd) == NULL) + return -1; + + dwfl_report_end(dwfl, NULL, NULL); + + struct process_dwflmod_parms parms = { + .cus = cus, + .conf = conf, + .filename = filename, + .nr_dwarf_sections_found = 0, + }; + + /* Process the one or more modules gleaned from this file. */ + dwfl_getmodules(dwfl, cus__process_dwflmod, &parms, 0); + dwfl_end(dwfl); + return parms.nr_dwarf_sections_found ? 0 : -1; +} + +static int dwarf__load_file(struct cus *cus, struct conf_load *conf, + const char *filename) +{ + int fd, err; + + elf_version(EV_CURRENT); + + fd = open(filename, O_RDONLY); + + if (fd == -1) + return -1; + + err = cus__process_file(cus, conf, fd, filename); + close(fd); + + return err; +} + +static int dwarf__init(void) +{ + strings = strings__new(); + return strings != NULL ? 0 : -ENOMEM; +} + +static void dwarf__exit(void) +{ + strings__delete(strings); + strings = NULL; +} + +struct debug_fmt_ops dwarf__ops = { + .name = "dwarf", + .init = dwarf__init, + .exit = dwarf__exit, + .load_file = dwarf__load_file, + .strings__ptr = dwarf__strings_ptr, + .tag__decl_file = dwarf_tag__decl_file, + .tag__decl_line = dwarf_tag__decl_line, + .tag__orig_id = dwarf_tag__orig_id, + .has_alignment_info = true, +}; diff --git a/dwarves.c b/dwarves.c new file mode 100644 index 0000000..a2d871a --- /dev/null +++ b/dwarves.c @@ -0,0 +1,2415 @@ +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2006 Mandriva Conectiva S.A. + Copyright (C) 2006 Arnaldo Carvalho de Melo <acme@mandriva.com> + Copyright (C) 2007 Red Hat Inc. + Copyright (C) 2007 Arnaldo Carvalho de Melo <acme@redhat.com> +*/ + +#include <assert.h> +#include <dirent.h> +#include <dwarf.h> +#include <errno.h> +#include <fcntl.h> +#include <fnmatch.h> +#include <libelf.h> +#include <search.h> +#include <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/utsname.h> + +#include "config.h" +#include "list.h" +#include "dwarves.h" +#include "dutil.h" +#include "pahole_strings.h" +#include <obstack.h> + +#define obstack_chunk_alloc malloc +#define obstack_chunk_free free + +#define min(x, y) ((x) < (y) ? (x) : (y)) + +int tag__is_base_type(const struct tag *tag, const struct cu *cu) +{ + switch (tag->tag) { + case DW_TAG_base_type: + return 1; + + case DW_TAG_typedef: { + const struct tag *type = cu__type(cu, tag->type); + + if (type == NULL) + return 0; + return tag__is_base_type(type, cu); + } + } + return 0; +} + +bool tag__is_array(const struct tag *tag, const struct cu *cu) +{ + switch (tag->tag) { + case DW_TAG_array_type: + return true; + + case DW_TAG_const_type: + case DW_TAG_typedef: { + const struct tag *type = cu__type(cu, tag->type); + + if (type == NULL) + return 0; + return tag__is_array(type, cu); + } + } + return 0; +} + +const char *cu__string(const struct cu *cu, strings_t s) +{ + if (cu->dfops && cu->dfops->strings__ptr) + return cu->dfops->strings__ptr(cu, s); + return NULL; +} + +static inline const char *s(const struct cu *cu, strings_t i) +{ + return cu__string(cu, i); +} + +int __tag__has_type_loop(const struct tag *tag, const struct tag *type, + char *bf, size_t len, FILE *fp, + const char *fn, int line) +{ + char bbf[2048], *abf = bbf; + + if (type == NULL) + return 0; + + if (tag->type == type->type) { + int printed; + + if (bf != NULL) + abf = bf; + else + len = sizeof(bbf); + printed = snprintf(abf, len, "<ERROR(%s:%d): detected type loop: type=%d, tag=%s>", + fn, line, tag->type, dwarf_tag_name(tag->tag)); + if (bf == NULL) + printed = fprintf(fp ?: stderr, "%s\n", abf); + return printed; + } + + return 0; +} + +static void lexblock__delete_tags(struct tag *tag, struct cu *cu) +{ + struct lexblock *block = tag__lexblock(tag); + struct tag *pos, *n; + + list_for_each_entry_safe_reverse(pos, n, &block->tags, node) { + list_del_init(&pos->node); + tag__delete(pos, cu); + } +} + +void lexblock__delete(struct lexblock *block, struct cu *cu) +{ + lexblock__delete_tags(&block->ip.tag, cu); + obstack_free(&cu->obstack, block); +} + +void tag__delete(struct tag *tag, struct cu *cu) +{ + assert(list_empty(&tag->node)); + + switch (tag->tag) { + case DW_TAG_union_type: + type__delete(tag__type(tag), cu); break; + case DW_TAG_class_type: + case DW_TAG_structure_type: + class__delete(tag__class(tag), cu); break; + case DW_TAG_enumeration_type: + enumeration__delete(tag__type(tag), cu); break; + case DW_TAG_subroutine_type: + ftype__delete(tag__ftype(tag), cu); break; + case DW_TAG_subprogram: + function__delete(tag__function(tag), cu); break; + case DW_TAG_lexical_block: + lexblock__delete(tag__lexblock(tag), cu); break; + default: + obstack_free(&cu->obstack, tag); + } +} + +void tag__not_found_die(const char *file, int line, const char *func) +{ + fprintf(stderr, "%s::%s(%d): tag not found, please report to " + "acme@kernel.org\n", file, func, line); + exit(1); +} + +struct tag *tag__follow_typedef(const struct tag *tag, const struct cu *cu) +{ + struct tag *type = cu__type(cu, tag->type); + + if (type != NULL && tag__is_typedef(type)) + return tag__follow_typedef(type, cu); + + return type; +} + +struct tag *tag__strip_typedefs_and_modifiers(const struct tag *tag, const struct cu *cu) +{ + struct tag *type = cu__type(cu, tag->type); + + while (type != NULL && (tag__is_typedef(type) || tag__is_modifier(type))) + type = cu__type(cu, type->type); + + return type; +} + +size_t __tag__id_not_found_fprintf(FILE *fp, type_id_t id, + const char *fn, int line) +{ + return fprintf(fp, "<ERROR(%s:%d): %d not found!>\n", fn, line, id); +} + +static struct base_type_name_to_size { + const char *name; + strings_t sname; + size_t size; +} base_type_name_to_size_table[] = { + { .name = "unsigned", .size = 32, }, + { .name = "signed int", .size = 32, }, + { .name = "unsigned int", .size = 32, }, + { .name = "int", .size = 32, }, + { .name = "short unsigned int", .size = 16, }, + { .name = "signed short", .size = 16, }, + { .name = "unsigned short", .size = 16, }, + { .name = "short int", .size = 16, }, + { .name = "short", .size = 16, }, + { .name = "char", .size = 8, }, + { .name = "signed char", .size = 8, }, + { .name = "unsigned char", .size = 8, }, + { .name = "signed long", .size = 0, }, + { .name = "long int", .size = 0, }, + { .name = "long", .size = 0, }, + { .name = "signed long", .size = 0, }, + { .name = "unsigned long", .size = 0, }, + { .name = "long unsigned int", .size = 0, }, + { .name = "bool", .size = 8, }, + { .name = "_Bool", .size = 8, }, + { .name = "long long unsigned int", .size = 64, }, + { .name = "long long int", .size = 64, }, + { .name = "long long", .size = 64, }, + { .name = "signed long long", .size = 64, }, + { .name = "unsigned long long", .size = 64, }, + { .name = "double", .size = 64, }, + { .name = "double double", .size = 64, }, + { .name = "single float", .size = 32, }, + { .name = "float", .size = 32, }, + { .name = "long double", .size = sizeof(long double) * 8, }, + { .name = "long double long double", .size = sizeof(long double) * 8, }, + { .name = "__int128", .size = 128, }, + { .name = "unsigned __int128", .size = 128, }, + { .name = "__int128 unsigned", .size = 128, }, + { .name = "_Float128", .size = 128, }, + { .name = NULL }, +}; + +void base_type_name_to_size_table__init(struct strings *strings) +{ + int i = 0; + + while (base_type_name_to_size_table[i].name != NULL) { + if (base_type_name_to_size_table[i].sname == 0) + base_type_name_to_size_table[i].sname = + strings__find(strings, + base_type_name_to_size_table[i].name); + ++i; + } +} + +size_t base_type__name_to_size(struct base_type *bt, struct cu *cu) +{ + int i = 0; + char bf[64]; + const char *name, *orig_name; + + if (bt->name_has_encoding) + name = s(cu, bt->name); + else + name = base_type__name(bt, cu, bf, sizeof(bf)); + orig_name = name; +try_again: + while (base_type_name_to_size_table[i].name != NULL) { + if (bt->name_has_encoding) { + if (base_type_name_to_size_table[i].sname == bt->name) { + size_t size; +found: + size = base_type_name_to_size_table[i].size; + + return size ?: ((size_t)cu->addr_size * 8); + } + } else if (strcmp(base_type_name_to_size_table[i].name, + name) == 0) + goto found; + ++i; + } + + if (strstarts(name, "signed ")) { + i = 0; + name += sizeof("signed"); + goto try_again; + } + + fprintf(stderr, "%s: %s %s\n", + __func__, dwarf_tag_name(bt->tag.tag), orig_name); + return 0; +} + +static const char *base_type_fp_type_str[] = { + [BT_FP_SINGLE] = "single", + [BT_FP_DOUBLE] = "double", + [BT_FP_CMPLX] = "complex", + [BT_FP_CMPLX_DBL] = "complex double", + [BT_FP_CMPLX_LDBL] = "complex long double", + [BT_FP_LDBL] = "long double", + [BT_FP_INTVL] = "interval", + [BT_FP_INTVL_DBL] = "interval double", + [BT_FP_INTVL_LDBL] = "interval long double", + [BT_FP_IMGRY] = "imaginary", + [BT_FP_IMGRY_DBL] = "imaginary double", + [BT_FP_IMGRY_LDBL] = "imaginary long double", +}; + +const char *base_type__name(const struct base_type *bt, const struct cu *cu, + char *bf, size_t len) +{ + if (bt->name_has_encoding) + return s(cu, bt->name); + + if (bt->float_type) + snprintf(bf, len, "%s %s", + base_type_fp_type_str[bt->float_type], + s(cu, bt->name)); + else + snprintf(bf, len, "%s%s%s", + bt->is_bool ? "bool " : "", + bt->is_varargs ? "... " : "", + s(cu, bt->name)); + return bf; +} + +void namespace__delete(struct namespace *space, struct cu *cu) +{ + struct tag *pos, *n; + + namespace__for_each_tag_safe_reverse(space, pos, n) { + list_del_init(&pos->node); + + /* Look for nested namespaces */ + if (tag__has_namespace(pos)) + namespace__delete(tag__namespace(pos), cu); + tag__delete(pos, cu); + } + + tag__delete(&space->tag, cu); +} + +void __type__init(struct type *type) +{ + INIT_LIST_HEAD(&type->node); + INIT_LIST_HEAD(&type->type_enum); + type->sizeof_member = NULL; + type->member_prefix = NULL; + type->member_prefix_len = 0; +} + +struct class_member * + type__find_first_biggest_size_base_type_member(struct type *type, + const struct cu *cu) +{ + struct class_member *pos, *result = NULL; + size_t result_size = 0; + + type__for_each_data_member(type, pos) { + if (pos->is_static) + continue; + + struct tag *type = cu__type(cu, pos->tag.type); + size_t member_size = 0, power2; + struct class_member *inner = NULL; + + if (type == NULL) { + tag__id_not_found_fprintf(stderr, pos->tag.type); + continue; + } +reevaluate: + switch (type->tag) { + case DW_TAG_base_type: + member_size = base_type__size(type); + break; + case DW_TAG_pointer_type: + case DW_TAG_reference_type: + member_size = cu->addr_size; + break; + case DW_TAG_class_type: + case DW_TAG_union_type: + case DW_TAG_structure_type: + if (tag__type(type)->nr_members == 0) + continue; + inner = type__find_first_biggest_size_base_type_member(tag__type(type), cu); + member_size = inner->byte_size; + break; + case DW_TAG_array_type: + case DW_TAG_const_type: + case DW_TAG_typedef: + case DW_TAG_rvalue_reference_type: + case DW_TAG_volatile_type: { + struct tag *tag = cu__type(cu, type->type); + if (tag == NULL) { + tag__id_not_found_fprintf(stderr, type->type); + continue; + } + type = tag; + } + goto reevaluate; + case DW_TAG_enumeration_type: + member_size = tag__type(type)->size / 8; + break; + } + + /* long long */ + if (member_size > cu->addr_size) + return pos; + + for (power2 = cu->addr_size; power2 > result_size; power2 /= 2) + if (member_size >= power2) { + if (power2 == cu->addr_size) + return inner ?: pos; + result_size = power2; + result = inner ?: pos; + } + } + + return result; +} + +static void cu__find_class_holes(struct cu *cu) +{ + uint32_t id; + struct class *pos; + + cu__for_each_struct(cu, id, pos) + class__find_holes(pos); +} + +void cus__add(struct cus *cus, struct cu *cu) +{ + cus->nr_entries++; + list_add_tail(&cu->node, &cus->cus); + cu__find_class_holes(cu); +} + +static void ptr_table__init(struct ptr_table *pt) +{ + pt->entries = NULL; + pt->nr_entries = pt->allocated_entries = 0; +} + +static void ptr_table__exit(struct ptr_table *pt) +{ + free(pt->entries); + pt->entries = NULL; +} + +static int ptr_table__add(struct ptr_table *pt, void *ptr, uint32_t *idxp) +{ + const uint32_t nr_entries = pt->nr_entries + 1; + const uint32_t rc = pt->nr_entries; + + if (nr_entries > pt->allocated_entries) { + uint32_t allocated_entries = pt->allocated_entries + 256; + void *entries = realloc(pt->entries, + sizeof(void *) * allocated_entries); + if (entries == NULL) + return -ENOMEM; + + pt->allocated_entries = allocated_entries; + pt->entries = entries; + } + + pt->entries[rc] = ptr; + pt->nr_entries = nr_entries; + *idxp = rc; + return 0; +} + +static int ptr_table__add_with_id(struct ptr_table *pt, void *ptr, + uint32_t id) +{ + /* Assume we won't be fed with the same id more than once */ + if (id >= pt->allocated_entries) { + uint32_t allocated_entries = roundup(id + 1, 256); + void *entries = realloc(pt->entries, + sizeof(void *) * allocated_entries); + if (entries == NULL) + return -ENOMEM; + + /* Zero out the new range */ + memset(entries + pt->allocated_entries * sizeof(void *), 0, + (allocated_entries - pt->allocated_entries) * sizeof(void *)); + + pt->allocated_entries = allocated_entries; + pt->entries = entries; + } + + pt->entries[id] = ptr; + if (id >= pt->nr_entries) + pt->nr_entries = id + 1; + return 0; +} + +static void *ptr_table__entry(const struct ptr_table *pt, uint32_t id) +{ + return id >= pt->nr_entries ? NULL : pt->entries[id]; +} + +static void cu__insert_function(struct cu *cu, struct tag *tag) +{ + struct function *function = tag__function(tag); + struct rb_node **p = &cu->functions.rb_node; + struct rb_node *parent = NULL; + struct function *f; + + while (*p != NULL) { + parent = *p; + f = rb_entry(parent, struct function, rb_node); + if (function->lexblock.ip.addr < f->lexblock.ip.addr) + p = &(*p)->rb_left; + else + p = &(*p)->rb_right; + } + rb_link_node(&function->rb_node, parent, p); + rb_insert_color(&function->rb_node, &cu->functions); +} + +int cu__table_add_tag(struct cu *cu, struct tag *tag, uint32_t *type_id) +{ + struct ptr_table *pt = &cu->tags_table; + + if (tag__is_tag_type(tag)) + pt = &cu->types_table; + else if (tag__is_function(tag)) { + pt = &cu->functions_table; + cu__insert_function(cu, tag); + } + + return ptr_table__add(pt, tag, type_id) ? -ENOMEM : 0; +} + +int cu__table_nullify_type_entry(struct cu *cu, uint32_t id) +{ + return ptr_table__add_with_id(&cu->types_table, NULL, id); +} + +int cu__add_tag(struct cu *cu, struct tag *tag, uint32_t *id) +{ + int err = cu__table_add_tag(cu, tag, id); + + if (err == 0) + list_add_tail(&tag->node, &cu->tags); + + return err; +} + +int cu__table_add_tag_with_id(struct cu *cu, struct tag *tag, uint32_t id) +{ + struct ptr_table *pt = &cu->tags_table; + + if (tag__is_tag_type(tag)) { + pt = &cu->types_table; + } else if (tag__is_function(tag)) { + pt = &cu->functions_table; + cu__insert_function(cu, tag); + } + + return ptr_table__add_with_id(pt, tag, id); +} + +int cu__add_tag_with_id(struct cu *cu, struct tag *tag, uint32_t id) +{ + int err = cu__table_add_tag_with_id(cu, tag, id); + + if (err == 0) + list_add_tail(&tag->node, &cu->tags); + + return err; +} + +struct cu *cu__new(const char *name, uint8_t addr_size, + const unsigned char *build_id, int build_id_len, + const char *filename) +{ + struct cu *cu = malloc(sizeof(*cu) + build_id_len); + + if (cu != NULL) { + uint32_t void_id; + + cu->name = strdup(name); + cu->filename = strdup(filename); + if (cu->name == NULL || cu->filename == NULL) + goto out_free; + + obstack_init(&cu->obstack); + ptr_table__init(&cu->tags_table); + ptr_table__init(&cu->types_table); + ptr_table__init(&cu->functions_table); + /* + * the first entry is historically associated with void, + * so make sure we don't use it + */ + if (ptr_table__add(&cu->types_table, NULL, &void_id) < 0) + goto out_free_name; + + cu->functions = RB_ROOT; + + cu->dfops = NULL; + INIT_LIST_HEAD(&cu->tags); + INIT_LIST_HEAD(&cu->tool_list); + + cu->addr_size = addr_size; + cu->extra_dbg_info = 0; + + cu->nr_inline_expansions = 0; + cu->size_inline_expansions = 0; + cu->nr_structures_changed = 0; + cu->nr_functions_changed = 0; + cu->max_len_changed_item = 0; + cu->function_bytes_added = 0; + cu->function_bytes_removed = 0; + cu->build_id_len = build_id_len; + if (build_id_len > 0) + memcpy(cu->build_id, build_id, build_id_len); + } +out: + return cu; +out_free_name: + free(cu->name); + free(cu->filename); +out_free: + free(cu); + cu = NULL; + goto out; +} + +void cu__delete(struct cu *cu) +{ + ptr_table__exit(&cu->tags_table); + ptr_table__exit(&cu->types_table); + ptr_table__exit(&cu->functions_table); + if (cu->dfops && cu->dfops->cu__delete) + cu->dfops->cu__delete(cu); + obstack_free(&cu->obstack, NULL); + free(cu->filename); + free(cu->name); + free(cu); +} + +bool cu__same_build_id(const struct cu *cu, const struct cu *other) +{ + return cu->build_id_len != 0 && + cu->build_id_len == other->build_id_len && + memcmp(cu->build_id, other->build_id, cu->build_id_len) == 0; +} + +struct tag *cu__function(const struct cu *cu, const uint32_t id) +{ + return cu ? ptr_table__entry(&cu->functions_table, id) : NULL; +} + +struct tag *cu__tag(const struct cu *cu, const uint32_t id) +{ + return cu ? ptr_table__entry(&cu->tags_table, id) : NULL; +} + +struct tag *cu__type(const struct cu *cu, const type_id_t id) +{ + return cu ? ptr_table__entry(&cu->types_table, id) : NULL; +} + +struct tag *cu__find_first_typedef_of_type(const struct cu *cu, + const type_id_t type) +{ + uint32_t id; + struct tag *pos; + + if (cu == NULL || type == 0) + return NULL; + + cu__for_each_type(cu, id, pos) + if (tag__is_typedef(pos) && pos->type == type) + return pos; + + return NULL; +} + +struct tag *cu__find_base_type_by_name(const struct cu *cu, + const char *name, type_id_t *idp) +{ + uint32_t id; + struct tag *pos; + + if (cu == NULL || name == NULL) + return NULL; + + cu__for_each_type(cu, id, pos) { + if (pos->tag != DW_TAG_base_type) + continue; + + const struct base_type *bt = tag__base_type(pos); + char bf[64]; + const char *bname = base_type__name(bt, cu, bf, sizeof(bf)); + if (!bname || strcmp(bname, name) != 0) + continue; + + if (idp != NULL) + *idp = id; + return pos; + } + + return NULL; +} + +struct tag *cu__find_base_type_by_sname_and_size(const struct cu *cu, + strings_t sname, + uint16_t bit_size, + type_id_t *idp) +{ + uint32_t id; + struct tag *pos; + + if (sname == 0) + return NULL; + + cu__for_each_type(cu, id, pos) { + if (pos->tag == DW_TAG_base_type) { + const struct base_type *bt = tag__base_type(pos); + + if (bt->bit_size == bit_size && + bt->name == sname) { + if (idp != NULL) + *idp = id; + return pos; + } + } + } + + return NULL; +} + +struct tag *cu__find_enumeration_by_sname_and_size(const struct cu *cu, + strings_t sname, + uint16_t bit_size, + type_id_t *idp) +{ + uint32_t id; + struct tag *pos; + + if (sname == 0) + return NULL; + + cu__for_each_type(cu, id, pos) { + if (pos->tag == DW_TAG_enumeration_type) { + const struct type *t = tag__type(pos); + + if (t->size == bit_size && + t->namespace.name == sname) { + if (idp != NULL) + *idp = id; + return pos; + } + } + } + + return NULL; +} + +struct tag *cu__find_enumeration_by_name(const struct cu *cu, const char *name, type_id_t *idp) +{ + uint32_t id; + struct tag *pos; + + if (name == NULL) + return NULL; + + cu__for_each_type(cu, id, pos) { + if (pos->tag == DW_TAG_enumeration_type) { + const struct type *type = tag__type(pos); + const char *tname = type__name(type, cu); + + if (tname && strcmp(tname, name) == 0) { + if (idp != NULL) + *idp = id; + return pos; + } + } + } + + return NULL; +} + +struct tag *cu__find_struct_by_sname(const struct cu *cu, strings_t sname, + const int include_decls, type_id_t *idp) +{ + uint32_t id; + struct tag *pos; + + if (sname == 0) + return NULL; + + cu__for_each_type(cu, id, pos) { + struct type *type; + + if (!tag__is_struct(pos)) + continue; + + type = tag__type(pos); + if (type->namespace.name == sname) { + if (!type->declaration) + goto found; + + if (include_decls) + goto found; + } + } + + return NULL; +found: + if (idp != NULL) + *idp = id; + return pos; + +} + +struct tag *cu__find_type_by_name(const struct cu *cu, const char *name, const int include_decls, type_id_t *idp) +{ + if (cu == NULL || name == NULL) + return NULL; + + uint32_t id; + struct tag *pos; + cu__for_each_type(cu, id, pos) { + struct type *type; + + if (!tag__is_type(pos)) + continue; + + type = tag__type(pos); + const char *tname = type__name(type, cu); + if (tname && strcmp(tname, name) == 0) { + if (!type->declaration) + goto found; + + if (include_decls) + goto found; + } + } + + return NULL; +found: + if (idp != NULL) + *idp = id; + return pos; +} + +struct tag *cus__find_type_by_name(const struct cus *cus, struct cu **cu, const char *name, + const int include_decls, type_id_t *id) +{ + struct cu *pos; + + list_for_each_entry(pos, &cus->cus, node) { + struct tag *tag = cu__find_type_by_name(pos, name, include_decls, id); + if (tag != NULL) { + if (cu != NULL) + *cu = pos; + return tag; + } + } + + return NULL; +} + +static struct tag *__cu__find_struct_by_name(const struct cu *cu, const char *name, + const int include_decls, bool unions, type_id_t *idp) +{ + if (cu == NULL || name == NULL) + return NULL; + + uint32_t id; + struct tag *pos; + cu__for_each_type(cu, id, pos) { + struct type *type; + + if (!(tag__is_struct(pos) || (unions && tag__is_union(pos)))) + continue; + + type = tag__type(pos); + const char *tname = type__name(type, cu); + if (tname && strcmp(tname, name) == 0) { + if (!type->declaration) + goto found; + + if (include_decls) + goto found; + } + } + + return NULL; +found: + if (idp != NULL) + *idp = id; + return pos; +} + +struct tag *cu__find_struct_by_name(const struct cu *cu, const char *name, + const int include_decls, type_id_t *idp) +{ + return __cu__find_struct_by_name(cu, name, include_decls, false, idp); +} + +struct tag *cu__find_struct_or_union_by_name(const struct cu *cu, const char *name, + const int include_decls, type_id_t *idp) +{ + return __cu__find_struct_by_name(cu, name, include_decls, true, idp); +} + +static struct tag *__cus__find_struct_by_name(const struct cus *cus, + struct cu **cu, const char *name, + const int include_decls, bool unions, type_id_t *id) +{ + struct cu *pos; + + list_for_each_entry(pos, &cus->cus, node) { + struct tag *tag = __cu__find_struct_by_name(pos, name, include_decls, unions, id); + if (tag != NULL) { + if (cu != NULL) + *cu = pos; + return tag; + } + } + + return NULL; +} + +struct tag *cus__find_struct_by_name(const struct cus *cus, struct cu **cu, const char *name, + const int include_decls, type_id_t *idp) +{ + return __cus__find_struct_by_name(cus, cu, name, include_decls, false, idp); +} + +struct tag *cus__find_struct_or_union_by_name(const struct cus *cus, struct cu **cu, const char *name, + const int include_decls, type_id_t *idp) +{ + return __cus__find_struct_by_name(cus, cu, name, include_decls, true, idp); +} + +struct function *cu__find_function_at_addr(const struct cu *cu, + uint64_t addr) +{ + struct rb_node *n; + + if (cu == NULL) + return NULL; + + n = cu->functions.rb_node; + + while (n) { + struct function *f = rb_entry(n, struct function, rb_node); + + if (addr < f->lexblock.ip.addr) + n = n->rb_left; + else if (addr >= f->lexblock.ip.addr + f->lexblock.size) + n = n->rb_right; + else + return f; + } + + return NULL; + +} + +struct function *cus__find_function_at_addr(const struct cus *cus, + uint64_t addr, struct cu **cu) +{ + struct cu *pos; + + list_for_each_entry(pos, &cus->cus, node) { + struct function *f = cu__find_function_at_addr(pos, addr); + + if (f != NULL) { + if (cu != NULL) + *cu = pos; + return f; + } + } + return NULL; +} + +struct cu *cus__find_cu_by_name(const struct cus *cus, const char *name) +{ + struct cu *pos; + + list_for_each_entry(pos, &cus->cus, node) + if (pos->name && strcmp(pos->name, name) == 0) + return pos; + + return NULL; +} + +struct tag *cu__find_function_by_name(const struct cu *cu, const char *name) +{ + if (cu == NULL || name == NULL) + return NULL; + + uint32_t id; + struct function *pos; + cu__for_each_function(cu, id, pos) { + const char *fname = function__name(pos, cu); + if (fname && strcmp(fname, name) == 0) + return function__tag(pos); + } + + return NULL; +} + +static size_t array_type__nr_entries(const struct array_type *at) +{ + int i; + size_t nr_entries = 1; + + for (i = 0; i < at->dimensions; ++i) + nr_entries *= at->nr_entries[i]; + + return nr_entries; +} + +size_t tag__size(const struct tag *tag, const struct cu *cu) +{ + size_t size; + + switch (tag->tag) { + case DW_TAG_string_type: + return tag__string_type(tag)->nr_entries; + case DW_TAG_member: { + struct class_member *member = tag__class_member(tag); + if (member->is_static) + return 0; + /* Is it cached already? */ + size = member->byte_size; + if (size != 0) + return size; + break; + } + case DW_TAG_pointer_type: + case DW_TAG_reference_type: return cu->addr_size; + case DW_TAG_base_type: return base_type__size(tag); + case DW_TAG_enumeration_type: return tag__type(tag)->size / 8; + } + + if (tag->type == 0) { /* struct class: unions, structs */ + struct type *type = tag__type(tag); + + /* empty base optimization trick */ + if (type->size == 1 && type->nr_members == 0) + size = 0; + else + size = tag__type(tag)->size; + } else { + const struct tag *type = cu__type(cu, tag->type); + + if (type == NULL) { + tag__id_not_found_fprintf(stderr, tag->type); + return -1; + } else if (tag__has_type_loop(tag, type, NULL, 0, NULL)) + return -1; + size = tag__size(type, cu); + } + + if (tag->tag == DW_TAG_array_type) + return size * array_type__nr_entries(tag__array_type(tag)); + + return size; +} + +const char *variable__name(const struct variable *var, const struct cu *cu) +{ + if (cu->dfops && cu->dfops->variable__name) + return cu->dfops->variable__name(var, cu); + return s(cu, var->name); +} + +const char *variable__type_name(const struct variable *var, + const struct cu *cu, + char *bf, size_t len) +{ + const struct tag *tag = cu__type(cu, var->ip.tag.type); + return tag != NULL ? tag__name(tag, cu, bf, len, NULL) : NULL; +} + +void class_member__delete(struct class_member *member, struct cu *cu) +{ + obstack_free(&cu->obstack, member); +} + +static struct class_member *class_member__clone(const struct class_member *from, + struct cu *cu) +{ + struct class_member *member = obstack_alloc(&cu->obstack, sizeof(*member)); + + if (member != NULL) + memcpy(member, from, sizeof(*member)); + + return member; +} + +static void type__delete_class_members(struct type *type, struct cu *cu) +{ + struct class_member *pos, *next; + + type__for_each_tag_safe_reverse(type, pos, next) { + list_del_init(&pos->tag.node); + class_member__delete(pos, cu); + } +} + +void class__delete(struct class *class, struct cu *cu) +{ + if (class->type.namespace.sname != NULL) + free(class->type.namespace.sname); + type__delete_class_members(&class->type, cu); + obstack_free(&cu->obstack, class); +} + +void type__delete(struct type *type, struct cu *cu) +{ + type__delete_class_members(type, cu); + obstack_free(&cu->obstack, type); +} + +static void enumerator__delete(struct enumerator *enumerator, struct cu *cu) +{ + obstack_free(&cu->obstack, enumerator); +} + +void enumeration__delete(struct type *type, struct cu *cu) +{ + struct enumerator *pos, *n; + type__for_each_enumerator_safe_reverse(type, pos, n) { + list_del_init(&pos->tag.node); + enumerator__delete(pos, cu); + } +} + +void class__add_vtable_entry(struct class *class, struct function *vtable_entry) +{ + ++class->nr_vtable_entries; + list_add_tail(&vtable_entry->vtable_node, &class->vtable); +} + +void namespace__add_tag(struct namespace *space, struct tag *tag) +{ + ++space->nr_tags; + list_add_tail(&tag->node, &space->tags); +} + +void type__add_member(struct type *type, struct class_member *member) +{ + if (member->is_static) + ++type->nr_static_members; + else + ++type->nr_members; + namespace__add_tag(&type->namespace, &member->tag); +} + +struct class_member *type__last_member(struct type *type) +{ + struct class_member *pos; + + list_for_each_entry_reverse(pos, &type->namespace.tags, tag.node) + if (pos->tag.tag == DW_TAG_member) + return pos; + return NULL; +} + +static int type__clone_members(struct type *type, const struct type *from, + struct cu *cu) +{ + struct class_member *pos; + + type->nr_members = type->nr_static_members = 0; + INIT_LIST_HEAD(&type->namespace.tags); + + type__for_each_member(from, pos) { + struct class_member *clone = class_member__clone(pos, cu); + + if (clone == NULL) + return -1; + type__add_member(type, clone); + } + + return 0; +} + +struct class *class__clone(const struct class *from, + const char *new_class_name, struct cu *cu) +{ + struct class *class = obstack_alloc(&cu->obstack, sizeof(*class)); + + if (class != NULL) { + memcpy(class, from, sizeof(*class)); + if (new_class_name != NULL) { + class->type.namespace.name = 0; + class->type.namespace.sname = strdup(new_class_name); + if (class->type.namespace.sname == NULL) { + free(class); + return NULL; + } + } + if (type__clone_members(&class->type, &from->type, cu) != 0) { + class__delete(class, cu); + class = NULL; + } + } + + return class; +} + +void enumeration__add(struct type *type, struct enumerator *enumerator) +{ + ++type->nr_members; + namespace__add_tag(&type->namespace, &enumerator->tag); +} + +void lexblock__add_lexblock(struct lexblock *block, struct lexblock *child) +{ + ++block->nr_lexblocks; + list_add_tail(&child->ip.tag.node, &block->tags); +} + +const char *function__name(struct function *func, const struct cu *cu) +{ + if (cu->dfops && cu->dfops->function__name) + return cu->dfops->function__name(func, cu); + return s(cu, func->name); +} + +static void parameter__delete(struct parameter *parm, struct cu *cu) +{ + obstack_free(&cu->obstack, parm); +} + +void ftype__delete(struct ftype *type, struct cu *cu) +{ + struct parameter *pos, *n; + + if (type == NULL) + return; + + ftype__for_each_parameter_safe_reverse(type, pos, n) { + list_del_init(&pos->tag.node); + parameter__delete(pos, cu); + } + obstack_free(&cu->obstack, type); +} + +void function__delete(struct function *func, struct cu *cu) +{ + lexblock__delete_tags(&func->lexblock.ip.tag, cu); + ftype__delete(&func->proto, cu); +} + +int ftype__has_parm_of_type(const struct ftype *ftype, const type_id_t target, + const struct cu *cu) +{ + struct parameter *pos; + + if (ftype->tag.tag == DW_TAG_subprogram) { + struct function *func = (struct function *)ftype; + + if (func->btf) + ftype = tag__ftype(cu__type(cu, ftype->tag.type)); + } + + ftype__for_each_parameter(ftype, pos) { + struct tag *type = cu__type(cu, pos->tag.type); + + if (type != NULL && tag__is_pointer(type)) { + if (type->type == target) + return 1; + } + } + return 0; +} + +void ftype__add_parameter(struct ftype *ftype, struct parameter *parm) +{ + ++ftype->nr_parms; + list_add_tail(&parm->tag.node, &ftype->parms); +} + +void lexblock__add_tag(struct lexblock *block, struct tag *tag) +{ + list_add_tail(&tag->node, &block->tags); +} + +void lexblock__add_inline_expansion(struct lexblock *block, + struct inline_expansion *exp) +{ + ++block->nr_inline_expansions; + block->size_inline_expansions += exp->size; + lexblock__add_tag(block, &exp->ip.tag); +} + +void lexblock__add_variable(struct lexblock *block, struct variable *var) +{ + ++block->nr_variables; + lexblock__add_tag(block, &var->ip.tag); +} + +void lexblock__add_label(struct lexblock *block, struct label *label) +{ + ++block->nr_labels; + lexblock__add_tag(block, &label->ip.tag); +} + +const struct class_member *class__find_bit_hole(const struct class *class, + const struct class_member *trailer, + const uint16_t bit_hole_size) +{ + struct class_member *pos; + const size_t byte_hole_size = bit_hole_size / 8; + + type__for_each_data_member(&class->type, pos) + if (pos == trailer) + break; + else if (pos->hole >= byte_hole_size || + pos->bit_hole >= bit_hole_size) + return pos; + + return NULL; +} + +void class__find_holes(struct class *class) +{ + const struct type *ctype = &class->type; + struct class_member *pos, *last = NULL; + int cur_bitfield_end = ctype->size * 8, cur_bitfield_size = 0; + int bit_holes = 0, byte_holes = 0; + int bit_start, bit_end; + int last_seen_bit = 0; + bool in_bitfield = false; + + if (!tag__is_struct(class__tag(class))) + return; + + if (class->holes_searched) + return; + + class->nr_holes = 0; + class->nr_bit_holes = 0; + + type__for_each_member(ctype, pos) { + /* XXX for now just skip these */ + if (pos->tag.tag == DW_TAG_inheritance && + pos->virtuality == DW_VIRTUALITY_virtual) + continue; + + if (pos->is_static) + continue; + + pos->bit_hole = 0; + pos->hole = 0; + + bit_start = pos->bit_offset; + if (pos->bitfield_size) { + bit_end = bit_start + pos->bitfield_size; + } else { + bit_end = bit_start + pos->byte_size * 8; + } + + bit_holes = 0; + byte_holes = 0; + if (in_bitfield) { + /* check if we have some trailing bitfield bits left */ + int bitfield_end = min(bit_start, cur_bitfield_end); + bit_holes = bitfield_end - last_seen_bit; + last_seen_bit = bitfield_end; + } + if (pos->bitfield_size) { + int aligned_start = pos->byte_offset * 8; + /* we can have some alignment byte padding left, + * but we need to be careful about bitfield spanning + * multiple aligned boundaries */ + if (last_seen_bit < aligned_start && aligned_start <= bit_start) { + byte_holes = pos->byte_offset - last_seen_bit / 8; + last_seen_bit = aligned_start; + } + bit_holes += bit_start - last_seen_bit; + } else { + byte_holes = bit_start/8 - last_seen_bit/8; + } + last_seen_bit = bit_end; + + if (pos->bitfield_size) { + in_bitfield = true; + /* if it's a new bitfield set or same, but with + * bigger-sized type, readjust size and end bit */ + if (bit_end > cur_bitfield_end || pos->bit_size > cur_bitfield_size) { + cur_bitfield_size = pos->bit_size; + cur_bitfield_end = pos->byte_offset * 8 + cur_bitfield_size; + /* + * if current bitfield "borrowed" bits from + * previous bitfield, it will have byte_offset + * of previous bitfield's backing integral + * type, but its end bit will be in a new + * bitfield "area", so we need to adjust + * bitfield end appropriately + */ + if (bit_end > cur_bitfield_end) { + cur_bitfield_end += cur_bitfield_size; + } + } + } else { + in_bitfield = false; + cur_bitfield_size = 0; + cur_bitfield_end = bit_end; + } + + if (last) { + last->hole = byte_holes; + last->bit_hole = bit_holes; + } else { + class->pre_hole = byte_holes; + class->pre_bit_hole = bit_holes; + } + if (bit_holes) + class->nr_bit_holes++; + if (byte_holes) + class->nr_holes++; + + last = pos; + } + + if (in_bitfield) { + int bitfield_end = min(ctype->size * 8, cur_bitfield_end); + class->bit_padding = bitfield_end - last_seen_bit; + last_seen_bit = bitfield_end; + } else { + class->bit_padding = 0; + } + class->padding = ctype->size - last_seen_bit / 8; + + class->holes_searched = true; +} + +static size_t type__natural_alignment(struct type *type, const struct cu *cu); + +static size_t tag__natural_alignment(struct tag *tag, const struct cu *cu) +{ + size_t natural_alignment = 1; + + if (tag == NULL) // Maybe its a non supported type, like DW_TAG_subrange_type, ADA stuff + return natural_alignment; + + if (tag__is_pointer(tag)) { + natural_alignment = cu->addr_size; + } else if (tag->tag == DW_TAG_base_type) { + natural_alignment = base_type__size(tag); + } else if (tag__is_enumeration(tag)) { + natural_alignment = tag__type(tag)->size / 8; + } else if (tag__is_struct(tag) || tag__is_union(tag)) { + natural_alignment = type__natural_alignment(tag__type(tag), cu); + } else if (tag->tag == DW_TAG_array_type) { + tag = tag__strip_typedefs_and_modifiers(tag, cu); + if (tag != NULL) // Maybe its a non supported type, like DW_TAG_subrange_type, ADA stuff + natural_alignment = tag__natural_alignment(tag, cu); + } + + /* + * Cope with zero sized types, like: + * + * struct u64_stats_sync { + * #if BITS_PER_LONG==32 && defined(CONFIG_SMP) + * seqcount_t seq; + * #endif + * }; + * + */ + return natural_alignment ?: 1; +} + +static size_t type__natural_alignment(struct type *type, const struct cu *cu) +{ + struct class_member *member; + + if (type->natural_alignment != 0) + return type->natural_alignment; + + type__for_each_member(type, member) { + /* XXX for now just skip these */ + if (member->tag.tag == DW_TAG_inheritance && + member->virtuality == DW_VIRTUALITY_virtual) + continue; + if (member->is_static) continue; + + struct tag *member_type = tag__strip_typedefs_and_modifiers(&member->tag, cu); + + if (member_type == NULL) // Maybe its a DW_TAG_subrange_type, ADA stuff still not supported + continue; + + size_t member_natural_alignment = tag__natural_alignment(member_type, cu); + + if (type->natural_alignment < member_natural_alignment) + type->natural_alignment = member_natural_alignment; + } + + return type->natural_alignment; +} + +/* + * Sometimes the only indication that a struct is __packed__ is for it to + * appear embedded in another and at an offset that is not natural for it, + * so, in !__packed__ parked struct, check for that and mark the types of + * members at unnatural alignments. + */ +void type__check_structs_at_unnatural_alignments(struct type *type, const struct cu *cu) +{ + struct class_member *member; + + type__for_each_member(type, member) { + struct tag *member_type = tag__strip_typedefs_and_modifiers(&member->tag, cu); + + if (member_type == NULL) { + // just be conservative and ignore + // Found first when a FORTRAN95 DWARF file was processed + // and the DW_TAG_string_type wasn't yet supported + continue; + } + + if (!tag__is_struct(member_type)) + continue; + + size_t natural_alignment = tag__natural_alignment(member_type, cu); + + /* Would this break the natural alignment */ + if ((member->byte_offset % natural_alignment) != 0) { + struct class *cls = tag__class(member_type); + + cls->is_packed = true; + cls->type.packed_attributes_inferred = true; + } + } +} + +bool class__infer_packed_attributes(struct class *cls, const struct cu *cu) +{ + struct type *ctype = &cls->type; + struct class_member *pos; + uint16_t max_natural_alignment = 1; + + if (!tag__is_struct(class__tag(cls))) + return false; + + if (ctype->packed_attributes_inferred) + return cls->is_packed; + + class__find_holes(cls); + + if (cls->padding != 0 || cls->nr_holes != 0) { + type__check_structs_at_unnatural_alignments(ctype, cu); + cls->is_packed = false; + goto out; + } + + type__for_each_member(ctype, pos) { + /* XXX for now just skip these */ + if (pos->tag.tag == DW_TAG_inheritance && + pos->virtuality == DW_VIRTUALITY_virtual) + continue; + + if (pos->is_static) + continue; + + struct tag *member_type = tag__strip_typedefs_and_modifiers(&pos->tag, cu); + size_t natural_alignment = tag__natural_alignment(member_type, cu); + + /* Always aligned: */ + if (natural_alignment == sizeof(char)) + continue; + + if (max_natural_alignment < natural_alignment) + max_natural_alignment = natural_alignment; + + if ((pos->byte_offset % natural_alignment) == 0) + continue; + + cls->is_packed = true; + goto out; + } + + if ((max_natural_alignment != 1 && ctype->alignment == 1) || + (class__size(cls) % max_natural_alignment) != 0) + cls->is_packed = true; + +out: + ctype->packed_attributes_inferred = true; + + return cls->is_packed; +} + +/* + * If structs embedded in unions, nameless or not, have a size which isn't + * isn't a multiple of the union size, then it must be packed, even if + * it has no holes nor padding, as an array of such unions would have the + * natural alignments of non-multiple structs inside it broken. + */ +void union__infer_packed_attributes(struct type *type, const struct cu *cu) +{ + const uint32_t union_size = type->size; + struct class_member *member; + + if (type->packed_attributes_inferred) + return; + + type__for_each_member(type, member) { + struct tag *member_type = tag__strip_typedefs_and_modifiers(&member->tag, cu); + + if (!tag__is_struct(member_type)) + continue; + + size_t natural_alignment = tag__natural_alignment(member_type, cu); + + /* Would this break the natural alignment */ + if ((union_size % natural_alignment) != 0) { + struct class *cls = tag__class(member_type); + + cls->is_packed = true; + cls->type.packed_attributes_inferred = true; + } + } + + type->packed_attributes_inferred = true; +} + +/** class__has_hole_ge - check if class has a hole greater or equal to @size + * @class - class instance + * @size - hole size to check + */ +int class__has_hole_ge(const struct class *class, const uint16_t size) +{ + struct class_member *pos; + + if (class->nr_holes == 0) + return 0; + + type__for_each_data_member(&class->type, pos) + if (pos->hole >= size) + return 1; + + return 0; +} + +struct class_member *type__find_member_by_name(const struct type *type, + const struct cu *cu, + const char *name) +{ + if (name == NULL) + return NULL; + + struct class_member *pos; + type__for_each_data_member(type, pos) { + const char *curr_name = class_member__name(pos, cu); + if (curr_name && strcmp(curr_name, name) == 0) + return pos; + } + + return NULL; +} + +static int strcommon(const char *a, const char *b) +{ + int i = 0; + + while (*a != '\0' && *a == *b) { + ++a; + ++b; + ++i; + } + + return i; +} + +void enumeration__calc_prefix(struct type *enumeration, const struct cu *cu) +{ + if (enumeration->member_prefix) + return; + + const char *previous_name = NULL, *curr_name = ""; + int common_part = INT32_MAX; + struct enumerator *entry; + + type__for_each_enumerator(enumeration, entry) { + const char *curr_name = enumerator__name(entry, cu); + + if (previous_name) { + int curr_common_part = strcommon(curr_name, previous_name); + if (common_part > curr_common_part) + common_part = curr_common_part; + + } + + previous_name = curr_name; + } + + enumeration->member_prefix = strndup(curr_name, common_part); + enumeration->member_prefix_len = common_part == INT32_MAX ? 0 : common_part; +} + +void enumerations__calc_prefix(struct list_head *enumerations) +{ + struct tag_cu_node *pos; + + list_for_each_entry(pos, enumerations, node) + enumeration__calc_prefix(tag__type(pos->tc.tag), pos->tc.cu); +} + +const char *enumeration__prefix(struct type *enumeration, const struct cu *cu) +{ + if (!enumeration->member_prefix) + enumeration__calc_prefix(enumeration, cu); + + return enumeration->member_prefix; +} + +uint16_t enumeration__prefix_len(struct type *enumeration, const struct cu *cu) +{ + if (!enumeration->member_prefix) + enumeration__calc_prefix(enumeration, cu); + + return enumeration->member_prefix_len; +} + + +uint32_t type__nr_members_of_type(const struct type *type, const type_id_t type_id) +{ + struct class_member *pos; + uint32_t nr_members_of_type = 0; + + type__for_each_member(type, pos) + if (pos->tag.type == type_id) + ++nr_members_of_type; + + return nr_members_of_type; +} + +static void lexblock__account_inline_expansions(struct lexblock *block, + const struct cu *cu) +{ + struct tag *pos, *type; + + if (block->nr_inline_expansions == 0) + return; + + list_for_each_entry(pos, &block->tags, node) { + if (pos->tag == DW_TAG_lexical_block) { + lexblock__account_inline_expansions(tag__lexblock(pos), + cu); + continue; + } else if (pos->tag != DW_TAG_inlined_subroutine) + continue; + + type = cu__function(cu, pos->type); + if (type != NULL) { + struct function *ftype = tag__function(type); + + ftype->cu_total_nr_inline_expansions++; + ftype->cu_total_size_inline_expansions += + tag__inline_expansion(pos)->size; + } + + } +} + +void cu__account_inline_expansions(struct cu *cu) +{ + struct tag *pos; + struct function *fpos; + + list_for_each_entry(pos, &cu->tags, node) { + if (!tag__is_function(pos)) + continue; + fpos = tag__function(pos); + lexblock__account_inline_expansions(&fpos->lexblock, cu); + cu->nr_inline_expansions += fpos->lexblock.nr_inline_expansions; + cu->size_inline_expansions += fpos->lexblock.size_inline_expansions; + } +} + +static int list__for_all_tags(struct list_head *list, struct cu *cu, + int (*iterator)(struct tag *tag, + struct cu *cu, void *cookie), + void *cookie) +{ + struct tag *pos, *n; + + if (list_empty(list) || !list->next) + return 0; + + list_for_each_entry_safe_reverse(pos, n, list, node) { + if (tag__has_namespace(pos)) { + struct namespace *space = tag__namespace(pos); + + /* + * See comment in type__for_each_enumerator, the + * enumerators (enum entries) are shared, but the + * enumeration tag must be deleted. + */ + if (!space->shared_tags && + list__for_all_tags(&space->tags, cu, + iterator, cookie)) + return 1; + /* + * vtable functions are already in the class tags list + */ + } else if (tag__is_function(pos)) { + if (list__for_all_tags(&tag__ftype(pos)->parms, + cu, iterator, cookie)) + return 1; + if (list__for_all_tags(&tag__function(pos)->lexblock.tags, + cu, iterator, cookie)) + return 1; + } else if (pos->tag == DW_TAG_subroutine_type) { + if (list__for_all_tags(&tag__ftype(pos)->parms, + cu, iterator, cookie)) + return 1; + } else if (pos->tag == DW_TAG_lexical_block) { + if (list__for_all_tags(&tag__lexblock(pos)->tags, + cu, iterator, cookie)) + return 1; + } + + if (iterator(pos, cu, cookie)) + return 1; + } + return 0; +} + +int cu__for_all_tags(struct cu *cu, + int (*iterator)(struct tag *tag, + struct cu *cu, void *cookie), + void *cookie) +{ + return list__for_all_tags(&cu->tags, cu, iterator, cookie); +} + +void cus__for_each_cu(struct cus *cus, + int (*iterator)(struct cu *cu, void *cookie), + void *cookie, + struct cu *(*filter)(struct cu *cu)) +{ + struct cu *pos; + + list_for_each_entry(pos, &cus->cus, node) { + struct cu *cu = pos; + if (filter != NULL) { + cu = filter(pos); + if (cu == NULL) + continue; + } + if (iterator(cu, cookie)) + break; + } +} + +int cus__load_dir(struct cus *cus, struct conf_load *conf, + const char *dirname, const char *filename_mask, + const int recursive) +{ + struct dirent *entry; + int err = -1; + DIR *dir = opendir(dirname); + + if (dir == NULL) + goto out; + + err = 0; + while ((entry = readdir(dir)) != NULL) { + char pathname[PATH_MAX]; + struct stat st; + + if (strcmp(entry->d_name, ".") == 0 || + strcmp(entry->d_name, "..") == 0) + continue; + + snprintf(pathname, sizeof(pathname), "%.*s/%s", + (int)(sizeof(pathname) - sizeof(entry->d_name) - 1), dirname, entry->d_name); + + err = lstat(pathname, &st); + if (err != 0) + break; + + if (S_ISDIR(st.st_mode)) { + if (!recursive) + continue; + + err = cus__load_dir(cus, conf, pathname, + filename_mask, recursive); + if (err != 0) + break; + } else if (fnmatch(filename_mask, entry->d_name, 0) == 0) { + err = cus__load_file(cus, conf, pathname); + if (err != 0) + break; + } + } + + if (err == -1) + puts(dirname); + closedir(dir); +out: + return err; +} + +/* + * This should really do demand loading of DSOs, STABS anyone? 8-) + */ +extern struct debug_fmt_ops dwarf__ops, ctf__ops, btf_elf__ops; + +static struct debug_fmt_ops *debug_fmt_table[] = { + &dwarf__ops, + &btf_elf__ops, + &ctf__ops, + NULL, +}; + +struct debug_fmt_ops *dwarves__active_loader; + +static int debugging_formats__loader(const char *name) +{ + int i = 0; + while (debug_fmt_table[i] != NULL) { + if (strcmp(debug_fmt_table[i]->name, name) == 0) + return i; + ++i; + } + return -1; +} + +int cus__load_file(struct cus *cus, struct conf_load *conf, + const char *filename) +{ + int i = 0, err = 0; + int loader; + + if (conf && conf->format_path != NULL) { + char *fpath = strdup(conf->format_path); + if (fpath == NULL) + return -ENOMEM; + char *fp = fpath; + while (1) { + char *sep = strchr(fp, ','); + + if (sep != NULL) + *sep = '\0'; + + err = -ENOTSUP; + loader = debugging_formats__loader(fp); + if (loader == -1) + break; + + if (conf->conf_fprintf) + conf->conf_fprintf->has_alignment_info = debug_fmt_table[loader]->has_alignment_info; + + err = 0; + dwarves__active_loader = debug_fmt_table[loader]; + if (debug_fmt_table[loader]->load_file(cus, conf, + filename) == 0) + break; + + err = -EINVAL; + if (sep == NULL) + break; + + fp = sep + 1; + } + free(fpath); + dwarves__active_loader = NULL; + return err; + } + + while (debug_fmt_table[i] != NULL) { + if (conf && conf->conf_fprintf) + conf->conf_fprintf->has_alignment_info = debug_fmt_table[i]->has_alignment_info; + dwarves__active_loader = debug_fmt_table[i]; + if (debug_fmt_table[i]->load_file(cus, conf, filename) == 0) + return 0; + ++i; + } + + dwarves__active_loader = NULL; + return -EINVAL; +} + +#define BUILD_ID_SIZE 20 +#define SBUILD_ID_SIZE (BUILD_ID_SIZE * 2 + 1) + +#define NOTE_ALIGN(sz) (((sz) + 3) & ~3) + +#define NT_GNU_BUILD_ID 3 + +#ifndef min +#define min(x, y) ({ \ + typeof(x) _min1 = (x); \ + typeof(y) _min2 = (y); \ + (void) (&_min1 == &_min2); \ + _min1 < _min2 ? _min1 : _min2; }) +#endif + +/* Force a compilation error if condition is true, but also produce a + result (of value 0 and type size_t), so the expression can be used + e.g. in a structure initializer (or where-ever else comma expressions + aren't permitted). */ +#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); })) + +/* Are two types/vars the same type (ignoring qualifiers)? */ +#ifndef __same_type +# define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b)) +#endif + +/* &a[0] degrades to a pointer: a different type from an array */ +#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0])) + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr)) + +static int sysfs__read_build_id(const char *filename, void *build_id, size_t size) +{ + int fd, err = -1; + + if (size < BUILD_ID_SIZE) + goto out; + + fd = open(filename, O_RDONLY); + if (fd < 0) + goto out; + + while (1) { + char bf[BUFSIZ]; + GElf_Nhdr nhdr; + size_t namesz, descsz; + + if (read(fd, &nhdr, sizeof(nhdr)) != sizeof(nhdr)) + break; + + namesz = NOTE_ALIGN(nhdr.n_namesz); + descsz = NOTE_ALIGN(nhdr.n_descsz); + if (nhdr.n_type == NT_GNU_BUILD_ID && + nhdr.n_namesz == sizeof("GNU")) { + if (read(fd, bf, namesz) != (ssize_t)namesz) + break; + if (memcmp(bf, "GNU", sizeof("GNU")) == 0) { + size_t sz = min(descsz, size); + if (read(fd, build_id, sz) == (ssize_t)sz) { + memset(build_id + sz, 0, size - sz); + err = 0; + break; + } + } else if (read(fd, bf, descsz) != (ssize_t)descsz) + break; + } else { + int n = namesz + descsz; + + if (n > (int)sizeof(bf)) { + n = sizeof(bf); + fprintf(stderr, "%s: truncating reading of build id in sysfs file %s: n_namesz=%u, n_descsz=%u.\n", + __func__, filename, nhdr.n_namesz, nhdr.n_descsz); + } + if (read(fd, bf, n) != n) + break; + } + } + close(fd); +out: + return err; +} + +static int elf_read_build_id(Elf *elf, void *bf, size_t size) +{ + int err = -1; + GElf_Ehdr ehdr; + GElf_Shdr shdr; + Elf_Data *data; + Elf_Scn *sec; + Elf_Kind ek; + void *ptr; + + if (size < BUILD_ID_SIZE) + goto out; + + ek = elf_kind(elf); + if (ek != ELF_K_ELF) + goto out; + + if (gelf_getehdr(elf, &ehdr) == NULL) { + fprintf(stderr, "%s: cannot get elf header.\n", __func__); + goto out; + } + + /* + * Check following sections for notes: + * '.note.gnu.build-id' + * '.notes' + * '.note' (VDSO specific) + */ + do { + sec = elf_section_by_name(elf, &ehdr, &shdr, + ".note.gnu.build-id", NULL); + if (sec) + break; + + sec = elf_section_by_name(elf, &ehdr, &shdr, + ".notes", NULL); + if (sec) + break; + + sec = elf_section_by_name(elf, &ehdr, &shdr, + ".note", NULL); + if (sec) + break; + + return err; + + } while (0); + + data = elf_getdata(sec, NULL); + if (data == NULL) + goto out; + + ptr = data->d_buf; + while (ptr < (data->d_buf + data->d_size)) { + GElf_Nhdr *nhdr = ptr; + size_t namesz = NOTE_ALIGN(nhdr->n_namesz), + descsz = NOTE_ALIGN(nhdr->n_descsz); + const char *name; + + ptr += sizeof(*nhdr); + name = ptr; + ptr += namesz; + if (nhdr->n_type == NT_GNU_BUILD_ID && + nhdr->n_namesz == sizeof("GNU")) { + if (memcmp(name, "GNU", sizeof("GNU")) == 0) { + size_t sz = min(size, descsz); + memcpy(bf, ptr, sz); + memset(bf + sz, 0, size - sz); + err = descsz; + break; + } + } + ptr += descsz; + } + +out: + return err; +} + +static int filename__read_build_id(const char *filename, void *bf, size_t size) +{ + int fd, err = -1; + Elf *elf; + + if (size < BUILD_ID_SIZE) + goto out; + + fd = open(filename, O_RDONLY); + if (fd < 0) + goto out; + + elf = elf_begin(fd, ELF_C_READ, NULL); + if (elf == NULL) { + fprintf(stderr, "%s: cannot read %s ELF file.\n", __func__, filename); + goto out_close; + } + + err = elf_read_build_id(elf, bf, size); + + elf_end(elf); +out_close: + close(fd); +out: + return err; +} + +static int build_id__sprintf(const unsigned char *build_id, int len, char *bf) +{ + char *bid = bf; + const unsigned char *raw = build_id; + int i; + + for (i = 0; i < len; ++i) { + sprintf(bid, "%02x", *raw); + ++raw; + bid += 2; + } + + return (bid - bf) + 1; +} + +static int sysfs__sprintf_build_id(const char *root_dir, char *sbuild_id) +{ + char notes[PATH_MAX]; + unsigned char build_id[BUILD_ID_SIZE]; + int ret; + + if (!root_dir) + root_dir = ""; + + snprintf(notes, sizeof(notes), "%s/sys/kernel/notes", root_dir); + + ret = sysfs__read_build_id(notes, build_id, sizeof(build_id)); + if (ret < 0) + return ret; + + return build_id__sprintf(build_id, sizeof(build_id), sbuild_id); +} + +static int filename__sprintf_build_id(const char *pathname, char *sbuild_id) +{ + unsigned char build_id[BUILD_ID_SIZE]; + int ret; + + ret = filename__read_build_id(pathname, build_id, sizeof(build_id)); + if (ret < 0) + return ret; + else if (ret != sizeof(build_id)) + return -EINVAL; + + return build_id__sprintf(build_id, sizeof(build_id), sbuild_id); +} + +#define zfree(ptr) ({ free(*ptr); *ptr = NULL; }) + +static int vmlinux_path__nr_entries; +static char **vmlinux_path; + +static void vmlinux_path__exit(void) +{ + while (--vmlinux_path__nr_entries >= 0) + zfree(&vmlinux_path[vmlinux_path__nr_entries]); + vmlinux_path__nr_entries = 0; + + zfree(&vmlinux_path); +} + +static const char * const vmlinux_paths[] = { + "vmlinux", + "/boot/vmlinux" +}; + +static const char * const vmlinux_paths_upd[] = { + "/boot/vmlinux-%s", + "/usr/lib/debug/boot/vmlinux-%s", + "/lib/modules/%s/build/vmlinux", + "/usr/lib/debug/lib/modules/%s/vmlinux", + "/usr/lib/debug/boot/vmlinux-%s.debug" +}; + +static int vmlinux_path__add(const char *new_entry) +{ + vmlinux_path[vmlinux_path__nr_entries] = strdup(new_entry); + if (vmlinux_path[vmlinux_path__nr_entries] == NULL) + return -1; + ++vmlinux_path__nr_entries; + + return 0; +} + +static int vmlinux_path__init(void) +{ + struct utsname uts; + char bf[PATH_MAX]; + char *kernel_version; + unsigned int i; + + vmlinux_path = malloc(sizeof(char *) * (ARRAY_SIZE(vmlinux_paths) + + ARRAY_SIZE(vmlinux_paths_upd))); + if (vmlinux_path == NULL) + return -1; + + for (i = 0; i < ARRAY_SIZE(vmlinux_paths); i++) + if (vmlinux_path__add(vmlinux_paths[i]) < 0) + goto out_fail; + + if (uname(&uts) < 0) + goto out_fail; + + kernel_version = uts.release; + + for (i = 0; i < ARRAY_SIZE(vmlinux_paths_upd); i++) { + snprintf(bf, sizeof(bf), vmlinux_paths_upd[i], kernel_version); + if (vmlinux_path__add(bf) < 0) + goto out_fail; + } + + return 0; + +out_fail: + vmlinux_path__exit(); + return -1; +} + +static int cus__load_running_kernel(struct cus *cus, struct conf_load *conf) +{ + int i, err = 0; + char running_sbuild_id[SBUILD_ID_SIZE]; + + if ((!conf || conf->format_path == NULL || strncmp(conf->format_path, "btf", 3) == 0) && + access("/sys/kernel/btf/vmlinux", R_OK) == 0) { + int loader = debugging_formats__loader("btf"); + if (loader == -1) + goto try_elf; + + if (conf && conf->conf_fprintf) + conf->conf_fprintf->has_alignment_info = debug_fmt_table[loader]->has_alignment_info; + + dwarves__active_loader = debug_fmt_table[loader]; + if (debug_fmt_table[loader]->load_file(cus, conf, "/sys/kernel/btf/vmlinux") == 0) + return 0; + dwarves__active_loader = NULL; + } +try_elf: + elf_version(EV_CURRENT); + vmlinux_path__init(); + + sysfs__sprintf_build_id(NULL, running_sbuild_id); + + for (i = 0; i < vmlinux_path__nr_entries; ++i) { + char sbuild_id[SBUILD_ID_SIZE]; + + if (filename__sprintf_build_id(vmlinux_path[i], sbuild_id) > 0 && + strcmp(sbuild_id, running_sbuild_id) == 0) { + err = cus__load_file(cus, conf, vmlinux_path[i]); + break; + } + } + + vmlinux_path__exit(); + + return err; +} + +int cus__load_files(struct cus *cus, struct conf_load *conf, + char *filenames[]) +{ + int i = 0; + + while (filenames[i] != NULL) { + if (cus__load_file(cus, conf, filenames[i])) + return -++i; + ++i; + } + + return i ? 0 : cus__load_running_kernel(cus, conf); +} + +int cus__fprintf_load_files_err(struct cus *cus, const char *tool, char *argv[], int err, FILE *output) +{ + /* errno is not properly preserved in some cases, sigh */ + return fprintf(output, "%s: %s: %s\n", tool, argv[-err - 1], + errno ? strerror(errno) : "No debugging information found"); +} + +struct cus *cus__new(void) +{ + struct cus *cus = malloc(sizeof(*cus)); + + if (cus != NULL) { + cus->nr_entries = 0; + INIT_LIST_HEAD(&cus->cus); + } + + return cus; +} + +void cus__delete(struct cus *cus) +{ + struct cu *pos, *n; + + if (cus == NULL) + return; + + list_for_each_entry_safe(pos, n, &cus->cus, node) { + list_del_init(&pos->node); + cu__delete(pos); + } + + free(cus); +} + +void dwarves__fprintf_init(uint16_t user_cacheline_size); + +int dwarves__init(uint16_t user_cacheline_size) +{ + dwarves__fprintf_init(user_cacheline_size); + + int i = 0; + int err = 0; + + while (debug_fmt_table[i] != NULL) { + if (debug_fmt_table[i]->init) { + err = debug_fmt_table[i]->init(); + if (err) + goto out_fail; + } + ++i; + } + + return 0; +out_fail: + while (i-- != 0) + if (debug_fmt_table[i]->exit) + debug_fmt_table[i]->exit(); + return err; +} + +void dwarves__exit(void) +{ + int i = 0; + + while (debug_fmt_table[i] != NULL) { + if (debug_fmt_table[i]->exit) + debug_fmt_table[i]->exit(); + ++i; + } +} + +struct argp_state; + +void dwarves_print_version(FILE *fp, struct argp_state *state __unused) +{ + fprintf(fp, "v%u.%u\n", DWARVES_MAJOR_VERSION, DWARVES_MINOR_VERSION); +} + +bool print_numeric_version; + +void dwarves_print_numeric_version(FILE *fp) +{ + fprintf(fp, "%u%u\n", DWARVES_MAJOR_VERSION, DWARVES_MINOR_VERSION); +} diff --git a/dwarves.h b/dwarves.h new file mode 100644 index 0000000..24405b7 --- /dev/null +++ b/dwarves.h @@ -0,0 +1,1337 @@ +#ifndef _DWARVES_H_ +#define _DWARVES_H_ 1 +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2006 Mandriva Conectiva S.A. + Copyright (C) 2006..2019 Arnaldo Carvalho de Melo <acme@redhat.com> +*/ + + +#include <stdint.h> +#include <stdio.h> +#include <obstack.h> +#include <dwarf.h> +#include <elfutils/libdwfl.h> +#include <sys/types.h> + +#include "dutil.h" +#include "list.h" +#include "rbtree.h" +#include "pahole_strings.h" + +struct cu; + +enum load_steal_kind { + LSK__KEEPIT, + LSK__DELETE, + LSK__STOP_LOADING, +}; + +/* + * BTF combines all the types into one big CU using btf_dedup(), so for something + * like a allyesconfig vmlinux kernel we can get over 65535 types. + */ +typedef uint32_t type_id_t; + +struct conf_fprintf; + +/** struct conf_load - load configuration + * @extra_dbg_info - keep original debugging format extra info + * (e.g. DWARF's decl_{line,file}, id, etc) + * @fixup_silly_bitfields - Fixup silly things such as "int foo:32;" + * @get_addr_info - wheter to load DW_AT_location and other addr info + */ +struct conf_load { + enum load_steal_kind (*steal)(struct cu *cu, + struct conf_load *conf); + void *cookie; + char *format_path; + bool extra_dbg_info; + bool fixup_silly_bitfields; + bool get_addr_info; + struct conf_fprintf *conf_fprintf; +}; + +/** struct conf_fprintf - hints to the __fprintf routines + * + * @count - Just like 'dd', stop pretty printing input after 'count' records + * @skip - Just like 'dd', skip 'count' records when pretty printing input + * @seek_bytes - Number of bytes to seek, if stdin only from start, when we have --pretty FILE, then from the end as well with negative numbers, + * may be of the form $header.MEMBER_NAME when using with --header. + * @size_bytes - Number of bytes to read, similar to seek_bytes, and when both are in place, first seek seek_bytes then read size_bytes + * @range - data structure field in --header to determine --seek_bytes and --size_bytes, must have 'offset' and 'size' fields + * @flat_arrays - a->foo[10][2] becomes a->foo[20] + * @classes_as_structs - class f becomes struct f, CTF doesn't have a "class" + * @cachelinep - pointer to current cacheline, so that when expanding types we keep track of it, + * needs to be "global", i.e. not set at each recursion. + * @suppress_force_paddings: This makes sense only if the debugging format has struct alignment information, + * So allow for it to be disabled and disable it automatically for things like BTF, + * that don't have such info. + */ +struct conf_fprintf { + const char *prefix; + const char *suffix; + int32_t type_spacing; + int32_t name_spacing; + uint32_t base_offset; + uint32_t count; + uint32_t *cachelinep; + const char *seek_bytes; + const char *size_bytes; + const char *header_type; + const char *range; + uint32_t skip; + uint8_t indent; + uint8_t expand_types:1; + uint8_t expand_pointers:1; + uint8_t rel_offset:1; + uint8_t emit_stats:1; + uint8_t suppress_comments:1; + uint8_t has_alignment_info:1; + uint8_t suppress_aligned_attribute:1; + uint8_t suppress_offset_comment:1; + uint8_t suppress_force_paddings:1; + uint8_t suppress_packed:1; + uint8_t show_decl_info:1; + uint8_t show_only_data_members:1; + uint8_t no_semicolon:1; + uint8_t show_first_biggest_size_base_type_member:1; + uint8_t flat_arrays:1; + uint8_t first_member:1; + uint8_t last_member:1; + uint8_t union_member:1; + uint8_t no_parm_names:1; + uint8_t classes_as_structs:1; + uint8_t hex_fmt:1; + uint8_t strip_inline:1; +}; + +struct cus { + uint32_t nr_entries; + struct list_head cus; +}; + +struct cus *cus__new(void); +void cus__delete(struct cus *cus); + +int cus__load_file(struct cus *cus, struct conf_load *conf, + const char *filename); +int cus__load_files(struct cus *cus, struct conf_load *conf, + char *filenames[]); +int cus__fprintf_load_files_err(struct cus *cus, const char *tool, + char *argv[], int err, FILE *output); +int cus__load_dir(struct cus *cus, struct conf_load *conf, + const char *dirname, const char *filename_mask, + const int recursive); +void cus__add(struct cus *cus, struct cu *cu); +void cus__print_error_msg(const char *progname, const struct cus *cus, + const char *filename, const int err); +struct cu *cus__find_cu_by_name(const struct cus *cus, const char *name); +struct tag *cus__find_struct_by_name(const struct cus *cus, struct cu **cu, + const char *name, const int include_decls, + type_id_t *id); +struct tag *cus__find_struct_or_union_by_name(const struct cus *cus, struct cu **cu, + const char *name, const int include_decls, type_id_t *id); +struct tag *cu__find_type_by_name(const struct cu *cu, const char *name, const int include_decls, type_id_t *idp); +struct tag *cus__find_type_by_name(const struct cus *cus, struct cu **cu, const char *name, + const int include_decls, type_id_t *id); +struct function *cus__find_function_at_addr(const struct cus *cus, + uint64_t addr, struct cu **cu); +void cus__for_each_cu(struct cus *cus, int (*iterator)(struct cu *cu, void *cookie), + void *cookie, + struct cu *(*filter)(struct cu *cu)); + +struct ptr_table { + void **entries; + uint32_t nr_entries; + uint32_t allocated_entries; +}; + +struct function; +struct tag; +struct cu; +struct variable; + +/* Same as DW_LANG, so that we don't have to include dwarf.h in CTF */ +enum dwarf_languages { + LANG_C89 = 0x01, /* ISO C:1989 */ + LANG_C = 0x02, /* C */ + LANG_Ada83 = 0x03, /* ISO Ada:1983 */ + LANG_C_plus_plus = 0x04, /* ISO C++:1998 */ + LANG_Cobol74 = 0x05, /* ISO Cobol:1974 */ + LANG_Cobol85 = 0x06, /* ISO Cobol:1985 */ + LANG_Fortran77 = 0x07, /* ISO FORTRAN 77 */ + LANG_Fortran90 = 0x08, /* ISO Fortran 90 */ + LANG_Pascal83 = 0x09, /* ISO Pascal:1983 */ + LANG_Modula2 = 0x0a, /* ISO Modula-2:1996 */ + LANG_Java = 0x0b, /* Java */ + LANG_C99 = 0x0c, /* ISO C:1999 */ + LANG_Ada95 = 0x0d, /* ISO Ada:1995 */ + LANG_Fortran95 = 0x0e, /* ISO Fortran 95 */ + LANG_PL1 = 0x0f, /* ISO PL/1:1976 */ + LANG_Objc = 0x10, /* Objective-C */ + LANG_ObjC_plus_plus = 0x11, /* Objective-C++ */ + LANG_UPC = 0x12, /* Unified Parallel C */ + LANG_D = 0x13, /* D */ +}; + +/** struct debug_fmt_ops - specific to the underlying debug file format + * + * @function__name - will be called by function__name(), giving a chance to + * formats such as CTF to get this from some other place + * than the global strings table. CTF does this by storing + * GElf_Sym->st_name in function->name, and by using + * function->name as an index into the .strtab ELF section. + * @variable__name - will be called by variable__name(), see @function_name + * cu__delete - called at cu__delete(), to give a chance to formats such as + * CTF to keep the .strstab ELF section available till the cu is + * deleted. See @function__name + */ +struct debug_fmt_ops { + const char *name; + int (*init)(void); + void (*exit)(void); + int (*load_file)(struct cus *cus, + struct conf_load *conf, + const char *filename); + const char *(*tag__decl_file)(const struct tag *tag, + const struct cu *cu); + uint32_t (*tag__decl_line)(const struct tag *tag, + const struct cu *cu); + unsigned long long (*tag__orig_id)(const struct tag *tag, + const struct cu *cu); + void (*tag__free_orig_info)(struct tag *tag, + struct cu *cu); + const char *(*function__name)(struct function *tag, + const struct cu *cu); + const char *(*variable__name)(const struct variable *var, + const struct cu *cu); + const char *(*strings__ptr)(const struct cu *cu, strings_t s); + void (*cu__delete)(struct cu *cu); + bool has_alignment_info; +}; + +extern struct debug_fmt_ops *dwarves__active_loader; + +struct cu { + struct list_head node; + struct list_head tags; + struct list_head tool_list; /* To be used by tools such as ctracer */ + struct ptr_table types_table; + struct ptr_table functions_table; + struct ptr_table tags_table; + struct rb_root functions; + char *name; + char *filename; + void *priv; + struct obstack obstack; + struct debug_fmt_ops *dfops; + Elf *elf; + Dwfl_Module *dwfl; + uint32_t cached_symtab_nr_entries; + uint8_t addr_size; + uint8_t extra_dbg_info:1; + uint8_t has_addr_info:1; + uint8_t uses_global_strings:1; + uint8_t little_endian:1; + uint16_t language; + unsigned long nr_inline_expansions; + size_t size_inline_expansions; + uint32_t nr_functions_changed; + uint32_t nr_structures_changed; + size_t max_len_changed_item; + size_t function_bytes_added; + size_t function_bytes_removed; + int build_id_len; + unsigned char build_id[0]; +}; + +struct cu *cu__new(const char *name, uint8_t addr_size, + const unsigned char *build_id, int build_id_len, + const char *filename); +void cu__delete(struct cu *cu); + +const char *cu__string(const struct cu *cu, strings_t s); + +static inline int cu__cache_symtab(struct cu *cu) +{ + int err = dwfl_module_getsymtab(cu->dwfl); + if (err > 0) + cu->cached_symtab_nr_entries = dwfl_module_getsymtab(cu->dwfl); + return err; +} + +static inline __pure bool cu__is_c_plus_plus(const struct cu *cu) +{ + return cu->language == LANG_C_plus_plus; +} + +/** + * cu__for_each_cached_symtab_entry - iterate thru the cached symtab entries + * @cu: struct cu instance + * @id: uint32_t tag id + * @pos: struct GElf_Sym iterator + * @name: char pointer where the symbol_name will be stored + */ +#define cu__for_each_cached_symtab_entry(cu, id, pos, name) \ + for (id = 1, \ + name = dwfl_module_getsym(cu->dwfl, id, &sym, NULL); \ + id < cu->cached_symtab_nr_entries; \ + ++id, name = dwfl_module_getsym(cu->dwfl, id, &sym, NULL)) + +/** + * cu__for_each_type - iterate thru all the type tags + * @cu: struct cu instance to iterate + * @id: type_id_t id + * @pos: struct tag iterator + * + * See cu__table_nullify_type_entry and users for the reason for + * the NULL test (hint: CTF Unknown types) + */ +#define cu__for_each_type(cu, id, pos) \ + for (id = 1; id < cu->types_table.nr_entries; ++id) \ + if (!(pos = cu->types_table.entries[id])) \ + continue; \ + else + +/** + * cu__for_each_struct - iterate thru all the struct tags + * @cu: struct cu instance to iterate + * @pos: struct class iterator + * @id: type_id_t id + */ +#define cu__for_each_struct(cu, id, pos) \ + for (id = 1; id < cu->types_table.nr_entries; ++id) \ + if (!(pos = tag__class(cu->types_table.entries[id])) || \ + !tag__is_struct(class__tag(pos))) \ + continue; \ + else + +/** + * cu__for_each_struct_or_union - iterate thru all the struct and union tags + * @cu: struct cu instance to iterate + * @pos: struct class iterator + * @id: type_id_t tag id + */ +#define cu__for_each_struct_or_union(cu, id, pos) \ + for (id = 1; id < cu->types_table.nr_entries; ++id) \ + if (!(pos = tag__class(cu->types_table.entries[id])) || \ + !(tag__is_struct(class__tag(pos)) || \ + tag__is_union(class__tag(pos)))) \ + continue; \ + else + +/** + * cu__for_each_function - iterate thru all the function tags + * @cu: struct cu instance to iterate + * @pos: struct function iterator + * @id: uint32_t tag id + */ +#define cu__for_each_function(cu, id, pos) \ + for (id = 0; id < cu->functions_table.nr_entries; ++id) \ + if (!(pos = tag__function(cu->functions_table.entries[id]))) \ + continue; \ + else + +/** + * cu__for_each_variable - iterate thru all the global variable tags + * @cu: struct cu instance to iterate + * @pos: struct tag iterator + * @id: uint32_t tag id + */ +#define cu__for_each_variable(cu, id, pos) \ + for (id = 0; id < cu->tags_table.nr_entries; ++id) \ + if (!(pos = cu->tags_table.entries[id]) || \ + !tag__is_variable(pos)) \ + continue; \ + else + +int cu__add_tag(struct cu *cu, struct tag *tag, uint32_t *id); +int cu__add_tag_with_id(struct cu *cu, struct tag *tag, uint32_t id); +int cu__table_add_tag(struct cu *cu, struct tag *tag, uint32_t *id); +int cu__table_add_tag_with_id(struct cu *cu, struct tag *tag, uint32_t id); +int cu__table_nullify_type_entry(struct cu *cu, uint32_t id); +struct tag *cu__find_base_type_by_name(const struct cu *cu, const char *name, + type_id_t *id); +struct tag *cu__find_base_type_by_sname_and_size(const struct cu *cu, + strings_t name, + uint16_t bit_size, + type_id_t *idp); +struct tag *cu__find_enumeration_by_name(const struct cu *cu, const char *name, type_id_t *idp); +struct tag *cu__find_enumeration_by_sname_and_size(const struct cu *cu, + strings_t sname, + uint16_t bit_size, + type_id_t *idp); +struct tag *cu__find_first_typedef_of_type(const struct cu *cu, + const type_id_t type); +struct tag *cu__find_function_by_name(const struct cu *cu, const char *name); +struct tag *cu__find_struct_by_sname(const struct cu *cu, strings_t sname, + const int include_decls, type_id_t *idp); +struct function *cu__find_function_at_addr(const struct cu *cu, + uint64_t addr); +struct tag *cu__function(const struct cu *cu, const uint32_t id); +struct tag *cu__tag(const struct cu *cu, const uint32_t id); +struct tag *cu__type(const struct cu *cu, const type_id_t id); +struct tag *cu__find_struct_by_name(const struct cu *cu, const char *name, + const int include_decls, type_id_t *id); +struct tag *cu__find_struct_or_union_by_name(const struct cu *cu, const char *name, + const int include_decls, type_id_t *id); +bool cu__same_build_id(const struct cu *cu, const struct cu *other); +void cu__account_inline_expansions(struct cu *cu); +int cu__for_all_tags(struct cu *cu, + int (*iterator)(struct tag *tag, + struct cu *cu, void *cookie), + void *cookie); + +/** struct tag - basic representation of a debug info element + * @priv - extra data, for instance, DWARF offset, id, decl_{file,line} + * @top_level - + */ +struct tag { + struct list_head node; + type_id_t type; + uint16_t tag; + bool visited; + bool top_level; + uint16_t recursivity_level; + void *priv; +}; + +// To use with things like type->type_enum == perf_event_type+perf_user_event_type +struct tag_cu { + struct tag *tag; + struct cu *cu; +}; + +void tag__delete(struct tag *tag, struct cu *cu); + +static inline int tag__is_enumeration(const struct tag *tag) +{ + return tag->tag == DW_TAG_enumeration_type; +} + +static inline int tag__is_namespace(const struct tag *tag) +{ + return tag->tag == DW_TAG_namespace; +} + +static inline int tag__is_struct(const struct tag *tag) +{ + return tag->tag == DW_TAG_structure_type || + tag->tag == DW_TAG_interface_type || + tag->tag == DW_TAG_class_type; +} + +static inline int tag__is_typedef(const struct tag *tag) +{ + return tag->tag == DW_TAG_typedef; +} + +static inline int tag__is_rvalue_reference_type(const struct tag *tag) +{ + return tag->tag == DW_TAG_rvalue_reference_type; +} + +static inline int tag__is_union(const struct tag *tag) +{ + return tag->tag == DW_TAG_union_type; +} + +static inline int tag__is_const(const struct tag *tag) +{ + return tag->tag == DW_TAG_const_type; +} + +static inline int tag__is_pointer(const struct tag *tag) +{ + return tag->tag == DW_TAG_pointer_type; +} + +static inline int tag__is_pointer_to(const struct tag *tag, type_id_t type) +{ + return tag__is_pointer(tag) && tag->type == type; +} + +static inline bool tag__is_variable(const struct tag *tag) +{ + return tag->tag == DW_TAG_variable; +} + +static inline bool tag__is_volatile(const struct tag *tag) +{ + return tag->tag == DW_TAG_volatile_type; +} + +static inline bool tag__is_restrict(const struct tag *tag) +{ + return tag->tag == DW_TAG_restrict_type; +} + +static inline int tag__is_modifier(const struct tag *tag) +{ + return tag__is_const(tag) || + tag__is_volatile(tag) || + tag__is_restrict(tag); +} + +static inline bool tag__has_namespace(const struct tag *tag) +{ + return tag__is_struct(tag) || + tag__is_union(tag) || + tag__is_namespace(tag) || + tag__is_enumeration(tag); +} + +/** + * tag__is_tag_type - is this tag derived from the 'type' class? + * @tag - tag queried + */ +static inline int tag__is_type(const struct tag *tag) +{ + return tag__is_union(tag) || + tag__is_struct(tag) || + tag__is_typedef(tag) || + tag__is_rvalue_reference_type(tag) || + tag__is_enumeration(tag); +} + +/** + * tag__is_tag_type - is this one of the possible types for a tag? + * @tag - tag queried + */ +static inline int tag__is_tag_type(const struct tag *tag) +{ + return tag__is_type(tag) || + tag->tag == DW_TAG_array_type || + tag->tag == DW_TAG_string_type || + tag->tag == DW_TAG_base_type || + tag->tag == DW_TAG_const_type || + tag->tag == DW_TAG_pointer_type || + tag->tag == DW_TAG_rvalue_reference_type || + tag->tag == DW_TAG_ptr_to_member_type || + tag->tag == DW_TAG_reference_type || + tag->tag == DW_TAG_restrict_type || + tag->tag == DW_TAG_subroutine_type || + tag->tag == DW_TAG_unspecified_type || + tag->tag == DW_TAG_volatile_type; +} + +static inline const char *tag__decl_file(const struct tag *tag, + const struct cu *cu) +{ + if (cu->dfops && cu->dfops->tag__decl_file) + return cu->dfops->tag__decl_file(tag, cu); + return NULL; +} + +static inline uint32_t tag__decl_line(const struct tag *tag, + const struct cu *cu) +{ + if (cu->dfops && cu->dfops->tag__decl_line) + return cu->dfops->tag__decl_line(tag, cu); + return 0; +} + +static inline unsigned long long tag__orig_id(const struct tag *tag, + const struct cu *cu) +{ + if (cu->dfops && cu->dfops->tag__orig_id) + return cu->dfops->tag__orig_id(tag, cu); + return 0; +} + +static inline void tag__free_orig_info(struct tag *tag, struct cu *cu) +{ + if (cu->dfops && cu->dfops->tag__free_orig_info) + cu->dfops->tag__free_orig_info(tag, cu); +} + +size_t tag__fprintf_decl_info(const struct tag *tag, + const struct cu *cu, FILE *fp); +size_t tag__fprintf(struct tag *tag, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp); + +const char *tag__name(const struct tag *tag, const struct cu *cu, + char *bf, size_t len, const struct conf_fprintf *conf); +void tag__not_found_die(const char *file, int line, const char *func); + +#define tag__assert_search_result(tag) \ + do { if (!tag) tag__not_found_die(__FILE__,\ + __LINE__, __func__); } while (0) + +size_t tag__size(const struct tag *tag, const struct cu *cu); +size_t tag__nr_cachelines(const struct tag *tag, const struct cu *cu); +struct tag *tag__follow_typedef(const struct tag *tag, const struct cu *cu); +struct tag *tag__strip_typedefs_and_modifiers(const struct tag *tag, const struct cu *cu); + +size_t __tag__id_not_found_fprintf(FILE *fp, type_id_t id, + const char *fn, int line); +#define tag__id_not_found_fprintf(fp, id) \ + __tag__id_not_found_fprintf(fp, id, __func__, __LINE__) + +int __tag__has_type_loop(const struct tag *tag, const struct tag *type, + char *bf, size_t len, FILE *fp, + const char *fn, int line); +#define tag__has_type_loop(tag, type, bf, len, fp) \ + __tag__has_type_loop(tag, type, bf, len, fp, __func__, __LINE__) + +struct ptr_to_member_type { + struct tag tag; + type_id_t containing_type; +}; + +static inline struct ptr_to_member_type * + tag__ptr_to_member_type(const struct tag *tag) +{ + return (struct ptr_to_member_type *)tag; +} + +/** struct namespace - base class for enums, structs, unions, typedefs, etc + * + * @sname - for clones, for instance, where we can't always add a new string + * @tags - class_member, enumerators, etc + * @shared_tags: if this bit is set, don't free the entries in @tags + */ +struct namespace { + struct tag tag; + strings_t name; + uint16_t nr_tags; + uint8_t shared_tags; + char * sname; + struct list_head tags; +}; + +static inline struct namespace *tag__namespace(const struct tag *tag) +{ + return (struct namespace *)tag; +} + +void namespace__delete(struct namespace *nspace, struct cu *cu); + +/** + * namespace__for_each_tag - iterate thru all the tags + * @nspace: struct namespace instance to iterate + * @pos: struct tag iterator + */ +#define namespace__for_each_tag(nspace, pos) \ + list_for_each_entry(pos, &(nspace)->tags, node) + +/** + * namespace__for_each_tag_safe_reverse - safely iterate thru all the tags, in reverse order + * @nspace: struct namespace instance to iterate + * @pos: struct tag iterator + * @n: struct class_member temp iterator + */ +#define namespace__for_each_tag_safe_reverse(nspace, pos, n) \ + list_for_each_entry_safe_reverse(pos, n, &(nspace)->tags, node) + +void namespace__add_tag(struct namespace *nspace, struct tag *tag); + +struct ip_tag { + struct tag tag; + uint64_t addr; +}; + +struct inline_expansion { + struct ip_tag ip; + size_t size; + uint64_t high_pc; +}; + +static inline struct inline_expansion * + tag__inline_expansion(const struct tag *tag) +{ + return (struct inline_expansion *)tag; +} + +struct label { + struct ip_tag ip; + strings_t name; +}; + +static inline struct label *tag__label(const struct tag *tag) +{ + return (struct label *)tag; +} + +static inline const char *label__name(const struct label *label, + const struct cu *cu) +{ + return cu__string(cu, label->name); +} + +enum vscope { + VSCOPE_UNKNOWN, + VSCOPE_LOCAL, + VSCOPE_GLOBAL, + VSCOPE_REGISTER, + VSCOPE_OPTIMIZED +} __attribute__((packed)); + +struct location { + Dwarf_Op *expr; + size_t exprlen; +}; + +struct variable { + struct ip_tag ip; + strings_t name; + uint8_t external:1; + uint8_t declaration:1; + enum vscope scope; + struct location location; + struct hlist_node tool_hnode; + struct variable *spec; +}; + +static inline struct variable *tag__variable(const struct tag *tag) +{ + return (struct variable *)tag; +} + +enum vscope variable__scope(const struct variable *var); +const char *variable__scope_str(const struct variable *var); + +const char *variable__name(const struct variable *var, const struct cu *cu); + +const char *variable__type_name(const struct variable *var, + const struct cu *cu, char *bf, size_t len); + +struct lexblock { + struct ip_tag ip; + struct list_head tags; + uint32_t size; + uint16_t nr_inline_expansions; + uint16_t nr_labels; + uint16_t nr_variables; + uint16_t nr_lexblocks; + uint32_t size_inline_expansions; +}; + +static inline struct lexblock *tag__lexblock(const struct tag *tag) +{ + return (struct lexblock *)tag; +} + +void lexblock__delete(struct lexblock *lexblock, struct cu *cu); + +struct function; + +void lexblock__add_inline_expansion(struct lexblock *lexblock, + struct inline_expansion *exp); +void lexblock__add_label(struct lexblock *lexblock, struct label *label); +void lexblock__add_lexblock(struct lexblock *lexblock, struct lexblock *child); +void lexblock__add_tag(struct lexblock *lexblock, struct tag *tag); +void lexblock__add_variable(struct lexblock *lexblock, struct variable *var); +size_t lexblock__fprintf(const struct lexblock *lexblock, const struct cu *cu, + struct function *function, uint16_t indent, + const struct conf_fprintf *conf, FILE *fp); + +struct parameter { + struct tag tag; + strings_t name; +}; + +static inline struct parameter *tag__parameter(const struct tag *tag) +{ + return (struct parameter *)tag; +} + +static inline const char *parameter__name(const struct parameter *parm, + const struct cu *cu) +{ + return cu__string(cu, parm->name); +} + +/* + * tag.tag can be DW_TAG_subprogram_type or DW_TAG_subroutine_type. + */ +struct ftype { + struct tag tag; + struct list_head parms; + uint16_t nr_parms; + uint8_t unspec_parms; /* just one bit is needed */ +}; + +static inline struct ftype *tag__ftype(const struct tag *tag) +{ + return (struct ftype *)tag; +} + +void ftype__delete(struct ftype *ftype, struct cu *cu); + +/** + * ftype__for_each_parameter - iterate thru all the parameters + * @ftype: struct ftype instance to iterate + * @pos: struct parameter iterator + */ +#define ftype__for_each_parameter(ftype, pos) \ + list_for_each_entry(pos, &(ftype)->parms, tag.node) + +/** + * ftype__for_each_parameter_safe - safely iterate thru all the parameters + * @ftype: struct ftype instance to iterate + * @pos: struct parameter iterator + * @n: struct parameter temp iterator + */ +#define ftype__for_each_parameter_safe(ftype, pos, n) \ + list_for_each_entry_safe(pos, n, &(ftype)->parms, tag.node) + +/** + * ftype__for_each_parameter_safe_reverse - safely iterate thru all the parameters, in reverse order + * @ftype: struct ftype instance to iterate + * @pos: struct parameter iterator + * @n: struct parameter temp iterator + */ +#define ftype__for_each_parameter_safe_reverse(ftype, pos, n) \ + list_for_each_entry_safe_reverse(pos, n, &(ftype)->parms, tag.node) + +void ftype__add_parameter(struct ftype *ftype, struct parameter *parm); +size_t ftype__fprintf(const struct ftype *ftype, const struct cu *cu, + const char *name, const int inlined, + const int is_pointer, const int type_spacing, bool is_prototype, + const struct conf_fprintf *conf, FILE *fp); +size_t ftype__fprintf_parms(const struct ftype *ftype, + const struct cu *cu, int indent, + const struct conf_fprintf *conf, FILE *fp); +int ftype__has_parm_of_type(const struct ftype *ftype, const type_id_t target, + const struct cu *cu); + +struct function { + struct ftype proto; + struct lexblock lexblock; + struct rb_node rb_node; + strings_t name; + strings_t linkage_name; + uint32_t cu_total_size_inline_expansions; + uint16_t cu_total_nr_inline_expansions; + uint8_t inlined:2; + uint8_t abstract_origin:1; + uint8_t external:1; + uint8_t accessibility:2; /* DW_ACCESS_{public,protected,private} */ + uint8_t virtuality:2; /* DW_VIRTUALITY_{none,virtual,pure_virtual} */ + uint8_t declaration:1; + uint8_t btf:1; + int32_t vtable_entry; + struct list_head vtable_node; + /* fields used by tools */ + union { + struct list_head tool_node; + struct hlist_node tool_hnode; + }; + void *priv; +}; + +static inline struct function *tag__function(const struct tag *tag) +{ + return (struct function *)tag; +} + +static inline struct tag *function__tag(const struct function *func) +{ + return (struct tag *)func; +} + +void function__delete(struct function *func, struct cu *cu); + +static __pure inline int tag__is_function(const struct tag *tag) +{ + return tag->tag == DW_TAG_subprogram; +} + +/** + * function__for_each_parameter - iterate thru all the parameters + * @func: struct function instance to iterate + * @pos: struct parameter iterator + */ +#define function__for_each_parameter(func, cu, pos) \ + ftype__for_each_parameter(func->btf ? tag__ftype(cu__type(cu, func->proto.tag.type)) : &func->proto, pos) + +const char *function__name(struct function *func, const struct cu *cu); + +static inline const char *function__linkage_name(const struct function *func, + const struct cu *cu) +{ + return cu__string(cu, func->linkage_name); +} + +size_t function__fprintf_stats(const struct tag *tag_func, + const struct cu *cu, + const struct conf_fprintf *conf, + FILE *fp); +const char *function__prototype(const struct function *func, + const struct cu *cu, char *bf, size_t len); + +static __pure inline uint64_t function__addr(const struct function *func) +{ + return func->lexblock.ip.addr; +} + +static __pure inline uint32_t function__size(const struct function *func) +{ + return func->lexblock.size; +} + +static inline int function__declared_inline(const struct function *func) +{ + return (func->inlined == DW_INL_declared_inlined || + func->inlined == DW_INL_declared_not_inlined); +} + +static inline int function__inlined(const struct function *func) +{ + return (func->inlined == DW_INL_inlined || + func->inlined == DW_INL_declared_inlined); +} + +/* struct class_member - struct, union, class member + * + * @bit_offset - offset in bits from the start of the struct + * @bit_size - cached bit size, can be smaller than the integral type if in a bitfield + * @byte_offset - offset in bytes from the start of the struct + * @byte_size - cached byte size, integral type byte size for bitfields + * @bitfield_offset - offset in the current bitfield + * @bitfield_size - size in the current bitfield + * @bit_hole - If there is a bit hole before the next one (or the end of the struct) + * @bitfield_end - Is this the last entry in a bitfield? + * @alignment - DW_AT_alignement, zero if not present, gcc emits since circa 7.3.1 + * @accessibility - DW_ACCESS_{public,protected,private} + * @virtuality - DW_VIRTUALITY_{none,virtual,pure_virtual} + * @hole - If there is a hole before the next one (or the end of the struct) + */ +struct class_member { + struct tag tag; + strings_t name; + uint32_t bit_offset; + uint32_t bit_size; + uint32_t byte_offset; + size_t byte_size; + int8_t bitfield_offset; + uint8_t bitfield_size; + uint8_t bit_hole; + uint8_t bitfield_end:1; + uint64_t const_value; + uint32_t alignment; + uint8_t visited:1; + uint8_t is_static:1; + uint8_t accessibility:2; + uint8_t virtuality:2; + uint16_t hole; +}; + +void class_member__delete(struct class_member *member, struct cu *cu); + +static inline struct class_member *tag__class_member(const struct tag *tag) +{ + return (struct class_member *)tag; +} + +static inline const char *class_member__name(const struct class_member *member, + const struct cu *cu) +{ + return cu__string(cu, member->name); +} + +static __pure inline int tag__is_class_member(const struct tag *tag) +{ + return tag->tag == DW_TAG_member; +} + +int tag__is_base_type(const struct tag *tag, const struct cu *cu); +bool tag__is_array(const struct tag *tag, const struct cu *cu); + +struct class_member_filter; + +struct tag_cu_node { + struct list_head node; + struct tag_cu tc; +}; + +/** + * struct type - base type for enumerations, structs and unions + * + * @nnr_members: number of non static DW_TAG_member entries + * @nr_static_members: number of static DW_TAG_member entries + * @nr_tags: number of tags + * @alignment: DW_AT_alignement, zero if not present, gcc emits since circa 7.3.1 + * @natural_alignment: For inferring __packed__, normally the widest scalar in it, recursively + * @sizeof_member: Use this to find the size of the record + * @type_member: Use this to select a member from where to get an id on an enum to find a type + * to cast for, needs to be used with the upcoming type_enum. + * @type_enum: enumeration(s) to use together with type_member to find a type to cast + * @member_prefix: the common prefix for all members, say in an enum, this should be calculated on demand + * @member_prefix_len: the lenght of the common prefix for all members + */ +struct type { + struct namespace namespace; + struct list_head node; + uint32_t size; + int32_t size_diff; + uint16_t nr_static_members; + uint16_t nr_members; + uint32_t alignment; + struct class_member *sizeof_member; + struct class_member *type_member; + struct class_member_filter *filter; + struct list_head type_enum; + char *member_prefix; + uint16_t member_prefix_len; + uint16_t max_tag_name_len; + uint16_t natural_alignment; + bool packed_attributes_inferred; + uint8_t declaration; /* only one bit used */ + uint8_t definition_emitted:1; + uint8_t fwd_decl_emitted:1; + uint8_t resized:1; +}; + +void __type__init(struct type *type); + +static inline struct class *type__class(const struct type *type) +{ + return (struct class *)type; +} + +static inline struct tag *type__tag(const struct type *type) +{ + return (struct tag *)type; +} + +void type__delete(struct type *type, struct cu *cu); + +/** + * type__for_each_tag - iterate thru all the tags + * @type: struct type instance to iterate + * @pos: struct tag iterator + */ +#define type__for_each_tag(type, pos) \ + list_for_each_entry(pos, &(type)->namespace.tags, node) + +/** + * type__for_each_enumerator - iterate thru the enumerator entries + * @type: struct type instance to iterate + * @pos: struct enumerator iterator + */ +#define type__for_each_enumerator(type, pos) \ + struct list_head *__type__for_each_enumerator_head = \ + (type)->namespace.shared_tags ? \ + (type)->namespace.tags.next : \ + &(type)->namespace.tags; \ + list_for_each_entry(pos, __type__for_each_enumerator_head, tag.node) + +/** + * type__for_each_enumerator_safe_reverse - safely iterate thru the enumerator entries, in reverse order + * @type: struct type instance to iterate + * @pos: struct enumerator iterator + * @n: struct enumerator temp iterator + */ +#define type__for_each_enumerator_safe_reverse(type, pos, n) \ + if ((type)->namespace.shared_tags) /* Do nothing */ ; else \ + list_for_each_entry_safe_reverse(pos, n, &(type)->namespace.tags, tag.node) + +/** + * type__for_each_member - iterate thru the entries that use space + * (data members and inheritance entries) + * @type: struct type instance to iterate + * @pos: struct class_member iterator + */ +#define type__for_each_member(type, pos) \ + list_for_each_entry(pos, &(type)->namespace.tags, tag.node) \ + if (!(pos->tag.tag == DW_TAG_member || \ + pos->tag.tag == DW_TAG_inheritance)) \ + continue; \ + else + +/** + * type__for_each_data_member - iterate thru the data member entries + * @type: struct type instance to iterate + * @pos: struct class_member iterator + */ +#define type__for_each_data_member(type, pos) \ + list_for_each_entry(pos, &(type)->namespace.tags, tag.node) \ + if (pos->tag.tag != DW_TAG_member) \ + continue; \ + else + +/** + * type__for_each_member_safe - safely iterate thru the entries that use space + * (data members and inheritance entries) + * @type: struct type instance to iterate + * @pos: struct class_member iterator + * @n: struct class_member temp iterator + */ +#define type__for_each_member_safe(type, pos, n) \ + list_for_each_entry_safe(pos, n, &(type)->namespace.tags, tag.node) \ + if (pos->tag.tag != DW_TAG_member) \ + continue; \ + else + +/** + * type__for_each_data_member_safe - safely iterate thru the data member entries + * @type: struct type instance to iterate + * @pos: struct class_member iterator + * @n: struct class_member temp iterator + */ +#define type__for_each_data_member_safe(type, pos, n) \ + list_for_each_entry_safe(pos, n, &(type)->namespace.tags, tag.node) \ + if (pos->tag.tag != DW_TAG_member) \ + continue; \ + else + +/** + * type__for_each_tag_safe_reverse - safely iterate thru all tags in a type, in reverse order + * @type: struct type instance to iterate + * @pos: struct class_member iterator + * @n: struct class_member temp iterator + */ +#define type__for_each_tag_safe_reverse(type, pos, n) \ + list_for_each_entry_safe_reverse(pos, n, &(type)->namespace.tags, tag.node) + +void type__add_member(struct type *type, struct class_member *member); +struct class_member * + type__find_first_biggest_size_base_type_member(struct type *type, + const struct cu *cu); + +struct class_member *type__find_member_by_name(const struct type *type, + const struct cu *cu, + const char *name); +uint32_t type__nr_members_of_type(const struct type *type, const type_id_t oftype); +struct class_member *type__last_member(struct type *type); + +void enumeration__calc_prefix(struct type *type, const struct cu *cu); +const char *enumeration__prefix(struct type *type, const struct cu *cu); +uint16_t enumeration__prefix_len(struct type *type, const struct cu *cu); +int enumeration__max_entry_name_len(struct type *type, const struct cu *cu); + +void enumerations__calc_prefix(struct list_head *enumerations); + +size_t typedef__fprintf(const struct tag *tag_type, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp); + +static inline struct type *tag__type(const struct tag *tag) +{ + return (struct type *)tag; +} + +struct class { + struct type type; + struct list_head vtable; + uint16_t nr_vtable_entries; + uint8_t nr_holes; + uint8_t nr_bit_holes; + uint16_t pre_hole; + uint16_t padding; + uint8_t pre_bit_hole; + uint8_t bit_padding; + bool holes_searched; + bool is_packed; + void *priv; +}; + +static inline struct class *tag__class(const struct tag *tag) +{ + return (struct class *)tag; +} + +static inline struct tag *class__tag(const struct class *cls) +{ + return (struct tag *)cls; +} + +struct class *class__clone(const struct class *from, + const char *new_class_name, struct cu *cu); +void class__delete(struct class *cls, struct cu *cu); + +static inline struct list_head *class__tags(struct class *cls) +{ + return &cls->type.namespace.tags; +} + +static __pure inline const char *namespace__name(const struct namespace *nspace, + const struct cu *cu) +{ + return nspace->sname ?: cu__string(cu, nspace->name); +} + +static __pure inline const char *type__name(const struct type *type, + const struct cu *cu) +{ + return namespace__name(&type->namespace, cu); +} + +static __pure inline const char *class__name(struct class *cls, + const struct cu *cu) +{ + return type__name(&cls->type, cu); +} + +static inline int class__is_struct(const struct class *cls) +{ + return tag__is_struct(&cls->type.namespace.tag); +} + +void class__find_holes(struct class *cls); +int class__has_hole_ge(const struct class *cls, const uint16_t size); + +bool class__infer_packed_attributes(struct class *cls, const struct cu *cu); + +void union__infer_packed_attributes(struct type *type, const struct cu *cu); + +void type__check_structs_at_unnatural_alignments(struct type *type, const struct cu *cu); + +size_t class__fprintf(struct class *cls, const struct cu *cu, FILE *fp); + +void class__add_vtable_entry(struct class *cls, struct function *vtable_entry); +static inline struct class_member * + class__find_member_by_name(const struct class *cls, + const struct cu *cu, const char *name) +{ + return type__find_member_by_name(&cls->type, cu, name); +} + +static inline uint16_t class__nr_members(const struct class *cls) +{ + return cls->type.nr_members; +} + +static inline uint32_t class__size(const struct class *cls) +{ + return cls->type.size; +} + +static inline int class__is_declaration(const struct class *cls) +{ + return cls->type.declaration; +} + +const struct class_member *class__find_bit_hole(const struct class *cls, + const struct class_member *trailer, + const uint16_t bit_hole_size); + +#define class__for_each_member_from(cls, from, pos) \ + pos = list_prepare_entry(from, class__tags(cls), tag.node); \ + list_for_each_entry_from(pos, class__tags(cls), tag.node) \ + if (!tag__is_class_member(&pos->tag)) \ + continue; \ + else + +#define class__for_each_member_safe_from(cls, from, pos, tmp) \ + pos = list_prepare_entry(from, class__tags(cls), tag.node); \ + list_for_each_entry_safe_from(pos, tmp, class__tags(cls), tag.node) \ + if (!tag__is_class_member(&pos->tag)) \ + continue; \ + else + +#define class__for_each_member_continue(cls, from, pos) \ + pos = list_prepare_entry(from, class__tags(cls), tag.node); \ + list_for_each_entry_continue(pos, class__tags(cls), tag.node) \ + if (!tag__is_class_member(&pos->tag)) \ + continue; \ + else + +#define class__for_each_member_reverse(cls, member) \ + list_for_each_entry_reverse(member, class__tags(cls), tag.node) \ + if (member->tag.tag != DW_TAG_member) \ + continue; \ + else + +enum base_type_float_type { + BT_FP_SINGLE = 1, + BT_FP_DOUBLE, + BT_FP_CMPLX, + BT_FP_CMPLX_DBL, + BT_FP_CMPLX_LDBL, + BT_FP_LDBL, + BT_FP_INTVL, + BT_FP_INTVL_DBL, + BT_FP_INTVL_LDBL, + BT_FP_IMGRY, + BT_FP_IMGRY_DBL, + BT_FP_IMGRY_LDBL +}; + +struct base_type { + struct tag tag; + strings_t name; + uint16_t bit_size; + uint8_t name_has_encoding:1; + uint8_t is_signed:1; + uint8_t is_bool:1; + uint8_t is_varargs:1; + uint8_t float_type:4; +}; + +static inline struct base_type *tag__base_type(const struct tag *tag) +{ + return (struct base_type *)tag; +} + +static inline uint16_t base_type__size(const struct tag *tag) +{ + return tag__base_type(tag)->bit_size / 8; +} + +const char *base_type__name(const struct base_type *btype, const struct cu *cu, + char *bf, size_t len); + +void base_type_name_to_size_table__init(struct strings *strings); +size_t base_type__name_to_size(struct base_type *btype, struct cu *cu); + +struct array_type { + struct tag tag; + uint32_t *nr_entries; + uint8_t dimensions; + bool is_vector; +}; + +static inline struct array_type *tag__array_type(const struct tag *tag) +{ + return (struct array_type *)tag; +} + +struct string_type { + struct tag tag; + uint32_t nr_entries; +}; + +static inline struct string_type *tag__string_type(const struct tag *tag) +{ + return (struct string_type *)tag; +} + +struct enumerator { + struct tag tag; + strings_t name; + uint32_t value; + struct tag_cu type_enum; // To cache the type_enum searches +}; + +static inline const char *enumerator__name(const struct enumerator *enumerator, + const struct cu *cu) +{ + return cu__string(cu, enumerator->name); +} + +void enumeration__delete(struct type *type, struct cu *cu); +void enumeration__add(struct type *type, struct enumerator *enumerator); +size_t enumeration__fprintf(const struct tag *tag_enum, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp); + +int dwarves__init(uint16_t user_cacheline_size); +void dwarves__exit(void); + +const char *dwarf_tag_name(const uint32_t tag); + +struct argp_state; + +void dwarves_print_version(FILE *fp, struct argp_state *state); +void dwarves_print_numeric_version(FILE *fp); + +extern bool print_numeric_version;; + +extern bool no_bitfield_type_recode; + +extern const char tabs[]; + +#endif /* _DWARVES_H_ */ diff --git a/dwarves_emit.c b/dwarves_emit.c new file mode 100644 index 0000000..434e339 --- /dev/null +++ b/dwarves_emit.c @@ -0,0 +1,346 @@ +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2006 Mandriva Conectiva S.A. + Copyright (C) 2006 Arnaldo Carvalho de Melo <acme@mandriva.com> + Copyright (C) 2007 Red Hat Inc. + Copyright (C) 2007 Arnaldo Carvalho de Melo <acme@redhat.com> +*/ + +#include <string.h> + +#include "list.h" +#include "dwarves_emit.h" +#include "dwarves.h" + +void type_emissions__init(struct type_emissions *emissions) +{ + INIT_LIST_HEAD(&emissions->definitions); + INIT_LIST_HEAD(&emissions->fwd_decls); +} + +static void type_emissions__add_definition(struct type_emissions *emissions, + struct type *type) +{ + type->definition_emitted = 1; + if (!list_empty(&type->node)) + list_del(&type->node); + list_add_tail(&type->node, &emissions->definitions); +} + +static void type_emissions__add_fwd_decl(struct type_emissions *emissions, + struct type *type) +{ + type->fwd_decl_emitted = 1; + if (list_empty(&type->node)) + list_add_tail(&type->node, &emissions->fwd_decls); +} + +struct type *type_emissions__find_definition(const struct type_emissions *emissions, + const struct cu *cu, + const char *name) +{ + struct type *pos; + + if (name == NULL) + return NULL; + + list_for_each_entry(pos, &emissions->definitions, node) + if (type__name(pos, cu) != NULL && + strcmp(type__name(pos, cu), name) == 0) + return pos; + + return NULL; +} + +static struct type *type_emissions__find_fwd_decl(const struct type_emissions *emissions, + const struct cu *cu, + const char *name) +{ + struct type *pos; + + if (name == NULL) + return NULL; + + list_for_each_entry(pos, &emissions->fwd_decls, node) { + const char *curr_name = type__name(pos, cu); + + if (curr_name && strcmp(curr_name, name) == 0) + return pos; + } + + return NULL; +} + +static int enumeration__emit_definitions(struct tag *tag, struct cu *cu, + struct type_emissions *emissions, + const struct conf_fprintf *conf, + FILE *fp) +{ + struct type *etype = tag__type(tag); + + /* Have we already emitted this in this CU? */ + if (etype->definition_emitted) + return 0; + + /* Ok, lets look at the previous CUs: */ + if (type_emissions__find_definition(emissions, cu, + type__name(etype, cu)) != NULL) { + /* + * Yes, so lets mark it visited on this CU too, + * to speed up the lookup. + */ + etype->definition_emitted = 1; + return 0; + } + + enumeration__fprintf(tag, cu, conf, fp); + fputs(";\n", fp); + type_emissions__add_definition(emissions, etype); + return 1; +} + +static int tag__emit_definitions(struct tag *tag, struct cu *cu, + struct type_emissions *emissions, FILE *fp); + +static int typedef__emit_definitions(struct tag *tdef, struct cu *cu, + struct type_emissions *emissions, FILE *fp) +{ + struct type *def = tag__type(tdef); + struct tag *type, *ptr_type; + + /* Have we already emitted this in this CU? */ + if (def->definition_emitted) + return 0; + + /* Ok, lets look at the previous CUs: */ + if (type_emissions__find_definition(emissions, cu, + type__name(def, cu)) != NULL) { + /* + * Yes, so lets mark it visited on this CU too, + * to speed up the lookup. + */ + def->definition_emitted = 1; + return 0; + } + + type = cu__type(cu, tdef->type); + tag__assert_search_result(type); + + switch (type->tag) { + case DW_TAG_array_type: + tag__emit_definitions(type, cu, emissions, fp); + break; + case DW_TAG_typedef: + typedef__emit_definitions(type, cu, emissions, fp); + break; + case DW_TAG_pointer_type: + ptr_type = cu__type(cu, type->type); + /* void ** can make ptr_type be NULL */ + if (ptr_type == NULL) + break; + if (ptr_type->tag == DW_TAG_typedef) { + typedef__emit_definitions(ptr_type, cu, emissions, fp); + break; + } else if (ptr_type->tag != DW_TAG_subroutine_type) + break; + type = ptr_type; + /* Fall thru */ + case DW_TAG_subroutine_type: + ftype__emit_definitions(tag__ftype(type), cu, emissions, fp); + break; + case DW_TAG_enumeration_type: { + struct type *ctype = tag__type(type); + struct conf_fprintf conf = { + .suffix = NULL, + }; + + if (type__name(ctype, cu) == NULL) { + fputs("typedef ", fp); + conf.suffix = type__name(def, cu); + enumeration__emit_definitions(type, cu, emissions, + &conf, fp); + goto out; + } else + enumeration__emit_definitions(type, cu, emissions, + &conf, fp); + } + break; + case DW_TAG_structure_type: + case DW_TAG_union_type: { + struct type *ctype = tag__type(type); + + if (type__name(ctype, cu) == NULL) { + if (type__emit_definitions(type, cu, emissions, fp)) + type__emit(type, cu, "typedef", + type__name(def, cu), fp); + goto out; + } else if (type__emit_definitions(type, cu, emissions, fp)) + type__emit(type, cu, NULL, NULL, fp); + } + } + + /* + * Recheck if the typedef was emitted, as there are cases, like + * wait_queue_t in the Linux kernel, that is against struct + * __wait_queue, that has a wait_queue_func_t member, a function + * typedef that has as one of its parameters a... wait_queue_t, that + * will thus be emitted before the function typedef, making a no go to + * redefine the typedef after struct __wait_queue. + */ + if (!def->definition_emitted) { + typedef__fprintf(tdef, cu, NULL, fp); + fputs(";\n", fp); + } +out: + type_emissions__add_definition(emissions, def); + return 1; +} + +int type__emit_fwd_decl(struct type *ctype, const struct cu *cu, + struct type_emissions *emissions, FILE *fp) +{ + /* Have we already emitted this in this CU? */ + if (ctype->fwd_decl_emitted) + return 0; + + const char *name = type__name(ctype, cu); + if (name == NULL) + return 0; + + /* Ok, lets look at the previous CUs: */ + if (type_emissions__find_fwd_decl(emissions, cu, name) != NULL) { + /* + * Yes, so lets mark it visited on this CU too, + * to speed up the lookup. + */ + ctype->fwd_decl_emitted = 1; + return 0; + } + + fprintf(fp, "%s %s;\n", + tag__is_union(&ctype->namespace.tag) ? "union" : "struct", + type__name(ctype, cu)); + type_emissions__add_fwd_decl(emissions, ctype); + return 1; +} + +static int tag__emit_definitions(struct tag *tag, struct cu *cu, + struct type_emissions *emissions, FILE *fp) +{ + struct tag *type = cu__type(cu, tag->type); + int pointer = 0; + + if (type == NULL) + return 0; +next_indirection: + switch (type->tag) { + case DW_TAG_pointer_type: + case DW_TAG_reference_type: + pointer = 1; + /* Fall thru */ + case DW_TAG_array_type: + case DW_TAG_const_type: + case DW_TAG_volatile_type: + type = cu__type(cu, type->type); + if (type == NULL) + return 0; + goto next_indirection; + case DW_TAG_typedef: + return typedef__emit_definitions(type, cu, emissions, fp); + case DW_TAG_enumeration_type: + if (type__name(tag__type(type), cu) != NULL) { + struct conf_fprintf conf = { + .suffix = NULL, + }; + return enumeration__emit_definitions(type, cu, emissions, + &conf, fp); + } + break; + case DW_TAG_structure_type: + case DW_TAG_union_type: + if (pointer) { + /* + * Struct defined inline, no name, need to have its + * members types emitted. + */ + if (type__name(tag__type(type), cu) == NULL) + type__emit_definitions(type, cu, emissions, fp); + + return type__emit_fwd_decl(tag__type(type), cu, + emissions, fp); + } + if (type__emit_definitions(type, cu, emissions, fp)) + type__emit(type, cu, NULL, NULL, fp); + return 1; + case DW_TAG_subroutine_type: + return ftype__emit_definitions(tag__ftype(type), cu, + emissions, fp); + } + + return 0; +} + +int ftype__emit_definitions(struct ftype *ftype, struct cu *cu, + struct type_emissions *emissions, FILE *fp) +{ + struct parameter *pos; + /* First check the function return type */ + int printed = tag__emit_definitions(&ftype->tag, cu, emissions, fp); + + /* Then its parameters */ + list_for_each_entry(pos, &ftype->parms, tag.node) + if (tag__emit_definitions(&pos->tag, cu, emissions, fp)) + printed = 1; + + if (printed) + fputc('\n', fp); + return printed; +} + +int type__emit_definitions(struct tag *tag, struct cu *cu, + struct type_emissions *emissions, FILE *fp) +{ + struct type *ctype = tag__type(tag); + struct class_member *pos; + + if (ctype->definition_emitted) + return 0; + + /* Ok, lets look at the previous CUs: */ + if (type_emissions__find_definition(emissions, cu, + type__name(ctype, cu)) != NULL) { + ctype->definition_emitted = 1; + return 0; + } + + if (tag__is_typedef(tag)) + return typedef__emit_definitions(tag, cu, emissions, fp); + + type_emissions__add_definition(emissions, ctype); + + type__check_structs_at_unnatural_alignments(ctype, cu); + + type__for_each_member(ctype, pos) + if (tag__emit_definitions(&pos->tag, cu, emissions, fp)) + fputc('\n', fp); + + return 1; +} + +void type__emit(struct tag *tag, struct cu *cu, + const char *prefix, const char *suffix, FILE *fp) +{ + struct type *ctype = tag__type(tag); + + if (type__name(ctype, cu) != NULL || + suffix != NULL || prefix != NULL) { + struct conf_fprintf conf = { + .prefix = prefix, + .suffix = suffix, + .emit_stats = 1, + }; + tag__fprintf(tag, cu, &conf, fp); + fputc('\n', fp); + } +} diff --git a/dwarves_emit.h b/dwarves_emit.h new file mode 100644 index 0000000..5d8c7ab --- /dev/null +++ b/dwarves_emit.h @@ -0,0 +1,38 @@ +#ifndef _DWARVES_EMIT_H_ +#define _DWARVES_EMIT_H_ 1 +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2006 Mandriva Conectiva S.A. + Copyright (C) 2006 Arnaldo Carvalho de Melo <acme@mandriva.com> + Copyright (C) 2007 Arnaldo Carvalho de Melo <acme@ghostprotocols.net> +*/ + +#include <stdio.h> +#include "list.h" + +struct cu; +struct ftype; +struct tag; +struct type; + +struct type_emissions { + struct list_head definitions; /* struct type entries */ + struct list_head fwd_decls; /* struct class entries */ +}; + +void type_emissions__init(struct type_emissions *temissions); + +int ftype__emit_definitions(struct ftype *ftype, struct cu *cu, + struct type_emissions *emissions, FILE *fp); +int type__emit_definitions(struct tag *tag, struct cu *cu, + struct type_emissions *emissions, FILE *fp); +int type__emit_fwd_decl(struct type *ctype, const struct cu *cu, + struct type_emissions *emissions, FILE *fp); +void type__emit(struct tag *tag_type, struct cu *cu, + const char *prefix, const char *suffix, FILE *fp); +struct type *type_emissions__find_definition(const struct type_emissions *temissions, + const struct cu *cu, + const char *name); + +#endif /* _DWARVES_EMIT_H_ */ diff --git a/dwarves_fprintf.c b/dwarves_fprintf.c new file mode 100644 index 0000000..c96a6fb --- /dev/null +++ b/dwarves_fprintf.c @@ -0,0 +1,1967 @@ +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2006 Mandriva Conectiva S.A. + Copyright (C) 2006 Arnaldo Carvalho de Melo <acme@mandriva.com> + Copyright (C) 2007..2009 Red Hat Inc. + Copyright (C) 2007..2009 Arnaldo Carvalho de Melo <acme@redhat.com> +*/ + +#include <dwarf.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <inttypes.h> +#include <elfutils/version.h> + +#include "config.h" +#include "dwarves.h" + +static const char *dwarf_tag_names[] = { + [DW_TAG_array_type] = "array_type", + [DW_TAG_class_type] = "class_type", + [DW_TAG_entry_point] = "entry_point", + [DW_TAG_enumeration_type] = "enumeration_type", + [DW_TAG_formal_parameter] = "formal_parameter", + [DW_TAG_imported_declaration] = "imported_declaration", + [DW_TAG_label] = "label", + [DW_TAG_lexical_block] = "lexical_block", + [DW_TAG_member] = "member", + [DW_TAG_pointer_type] = "pointer_type", + [DW_TAG_reference_type] = "reference_type", + [DW_TAG_compile_unit] = "compile_unit", + [DW_TAG_string_type] = "string_type", + [DW_TAG_structure_type] = "structure_type", + [DW_TAG_subroutine_type] = "subroutine_type", + [DW_TAG_typedef] = "typedef", + [DW_TAG_union_type] = "union_type", + [DW_TAG_unspecified_parameters] = "unspecified_parameters", + [DW_TAG_variant] = "variant", + [DW_TAG_common_block] = "common_block", + [DW_TAG_common_inclusion] = "common_inclusion", + [DW_TAG_inheritance] = "inheritance", + [DW_TAG_inlined_subroutine] = "inlined_subroutine", + [DW_TAG_module] = "module", + [DW_TAG_ptr_to_member_type] = "ptr_to_member_type", + [DW_TAG_set_type] = "set_type", + [DW_TAG_subrange_type] = "subrange_type", + [DW_TAG_with_stmt] = "with_stmt", + [DW_TAG_access_declaration] = "access_declaration", + [DW_TAG_base_type] = "base_type", + [DW_TAG_catch_block] = "catch_block", + [DW_TAG_const_type] = "const_type", + [DW_TAG_constant] = "constant", + [DW_TAG_enumerator] = "enumerator", + [DW_TAG_file_type] = "file_type", + [DW_TAG_friend] = "friend", + [DW_TAG_namelist] = "namelist", + [DW_TAG_namelist_item] = "namelist_item", + [DW_TAG_packed_type] = "packed_type", + [DW_TAG_subprogram] = "subprogram", + [DW_TAG_template_type_parameter] = "template_type_parameter", + [DW_TAG_template_value_parameter] = "template_value_parameter", + [DW_TAG_thrown_type] = "thrown_type", + [DW_TAG_try_block] = "try_block", + [DW_TAG_variant_part] = "variant_part", + [DW_TAG_variable] = "variable", + [DW_TAG_volatile_type] = "volatile_type", + [DW_TAG_dwarf_procedure] = "dwarf_procedure", + [DW_TAG_restrict_type] = "restrict_type", + [DW_TAG_interface_type] = "interface_type", + [DW_TAG_namespace] = "namespace", + [DW_TAG_imported_module] = "imported_module", + [DW_TAG_unspecified_type] = "unspecified_type", + [DW_TAG_partial_unit] = "partial_unit", + [DW_TAG_imported_unit] = "imported_unit", + [DW_TAG_condition] = "condition", + [DW_TAG_shared_type] = "shared_type", +#ifdef STB_GNU_UNIQUE + [DW_TAG_type_unit] = "type_unit", + [DW_TAG_rvalue_reference_type] = "rvalue_reference_type", +#endif +}; + +static const char *dwarf_gnu_tag_names[] = { + [DW_TAG_MIPS_loop - DW_TAG_MIPS_loop] = "MIPS_loop", + [DW_TAG_format_label - DW_TAG_MIPS_loop] = "format_label", + [DW_TAG_function_template - DW_TAG_MIPS_loop] = "function_template", + [DW_TAG_class_template - DW_TAG_MIPS_loop] = "class_template", +#ifdef STB_GNU_UNIQUE + [DW_TAG_GNU_BINCL - DW_TAG_MIPS_loop] = "GNU_BINCL", + [DW_TAG_GNU_EINCL - DW_TAG_MIPS_loop] = "GNU_EINCL", + [DW_TAG_GNU_template_template_param - DW_TAG_MIPS_loop] = "GNU_template_template_param", + [DW_TAG_GNU_template_parameter_pack - DW_TAG_MIPS_loop] = "GNU_template_parameter_pack", + [DW_TAG_GNU_formal_parameter_pack - DW_TAG_MIPS_loop] = "GNU_formal_parameter_pack", +#endif +#if _ELFUTILS_PREREQ(0, 153) + [DW_TAG_GNU_call_site - DW_TAG_MIPS_loop] = "GNU_call_site", + [DW_TAG_GNU_call_site_parameter - DW_TAG_MIPS_loop] = "GNU_call_site_parameter", +#endif +}; + +const char *dwarf_tag_name(const uint32_t tag) +{ + if (tag >= DW_TAG_array_type && tag <= +#ifdef STB_GNU_UNIQUE + DW_TAG_rvalue_reference_type +#else + DW_TAG_shared_type +#endif + ) + return dwarf_tag_names[tag]; + else if (tag >= DW_TAG_MIPS_loop && tag <= +#if _ELFUTILS_PREREQ(0, 153) + DW_TAG_GNU_call_site_parameter +#elif STB_GNU_UNIQUE + DW_TAG_GNU_formal_parameter_pack +#else + DW_TAG_class_template +#endif + ) + return dwarf_gnu_tag_names[tag - DW_TAG_MIPS_loop]; + return "INVALID"; +} + +static const struct conf_fprintf conf_fprintf__defaults = { + .name_spacing = 23, + .type_spacing = 26, + .emit_stats = 1, +}; + +const char tabs[] = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + +static size_t cacheline_size; + +size_t tag__nr_cachelines(const struct tag *tag, const struct cu *cu) +{ + return (tag__size(tag, cu) + cacheline_size - 1) / cacheline_size; +} + +static const char *tag__accessibility(const struct tag *tag) +{ + int a; + + switch (tag->tag) { + case DW_TAG_inheritance: + case DW_TAG_member: + a = tag__class_member(tag)->accessibility; + break; + case DW_TAG_subprogram: + a = tag__function(tag)->accessibility; + break; + default: + return NULL; + } + + switch (a) { + case DW_ACCESS_public: return "public"; + case DW_ACCESS_private: return "private"; + case DW_ACCESS_protected: return "protected"; + } + + return NULL; +} + +static size_t __tag__id_not_found_snprintf(char *bf, size_t len, uint32_t id, + const char *fn, int line) +{ + return snprintf(bf, len, "<ERROR(%s:%d): %#llx not found!>", fn, line, + (unsigned long long)id); +} + +#define tag__id_not_found_snprintf(bf, len, id) \ + __tag__id_not_found_snprintf(bf, len, id, __func__, __LINE__) + +size_t tag__fprintf_decl_info(const struct tag *tag, + const struct cu *cu, FILE *fp) +{ + return fprintf(fp, "/* <%llx> %s:%u */\n", tag__orig_id(tag, cu), + tag__decl_file(tag, cu), tag__decl_line(tag, cu)); + return 0; +} + +static size_t __class__fprintf(struct class *class, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp); +static size_t type__fprintf(struct tag *type, const struct cu *cu, + const char *name, const struct conf_fprintf *conf, + FILE *fp); + +static size_t array_type__fprintf(const struct tag *tag, + const struct cu *cu, const char *name, + const struct conf_fprintf *conf, + FILE *fp) +{ + struct array_type *at = tag__array_type(tag); + struct tag *type = cu__type(cu, tag->type); + size_t printed; + unsigned long long flat_dimensions = 0; + int i; + + if (type == NULL) + return tag__id_not_found_fprintf(fp, tag->type); + + /* Zero sized arrays? */ + if (at->dimensions >= 1 && at->nr_entries[0] == 0 && tag__is_const(type)) + type = cu__type(cu, type->type); + + printed = type__fprintf(type, cu, name, conf, fp); + for (i = 0; i < at->dimensions; ++i) { + if (conf->flat_arrays || at->is_vector) { + /* + * Seen on the Linux kernel on tun_filter: + * + * __u8 addr[0][ETH_ALEN]; + */ + if (at->nr_entries[i] == 0 && i == 0) + break; + if (!flat_dimensions) + flat_dimensions = at->nr_entries[i]; + else + flat_dimensions *= at->nr_entries[i]; + } else { + bool single_member = conf->last_member && conf->first_member; + + if (at->nr_entries[i] != 0 || !conf->last_member || single_member || conf->union_member) + printed += fprintf(fp, "[%u]", at->nr_entries[i]); + else + printed += fprintf(fp, "[]"); + } + } + + if (at->is_vector) { + type = tag__follow_typedef(tag, cu); + + if (flat_dimensions == 0) + flat_dimensions = 1; + printed += fprintf(fp, " __attribute__ ((__vector_size__ (%llu)))", + flat_dimensions * tag__size(type, cu)); + } else if (conf->flat_arrays) { + bool single_member = conf->last_member && conf->first_member; + + if (flat_dimensions != 0 || !conf->last_member || single_member || conf->union_member) + printed += fprintf(fp, "[%llu]", flat_dimensions); + else + printed += fprintf(fp, "[]"); + } + + return printed; +} + +static size_t string_type__fprintf(const struct tag *tag, + const struct cu *cu, const char *name, + const struct conf_fprintf *conf, + FILE *fp) +{ + struct string_type *st = tag__string_type(tag); + + return fprintf(fp, "string %*s[%u]", conf->type_spacing - 5, name, st->nr_entries); +} + +size_t typedef__fprintf(const struct tag *tag, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp) +{ + struct type *type = tag__type(tag); + const struct conf_fprintf *pconf = conf ?: &conf_fprintf__defaults; + const struct tag *tag_type; + const struct tag *ptr_type; + char bf[512]; + int is_pointer = 0; + size_t printed; + + /* + * Check for void (humm, perhaps we should have a fake void tag instance + * to avoid all these checks? + */ + if (tag->type == 0) + return fprintf(fp, "typedef void %s", type__name(type, cu)); + + tag_type = cu__type(cu, tag->type); + if (tag_type == NULL) { + printed = fprintf(fp, "typedef "); + printed += tag__id_not_found_fprintf(fp, tag->type); + return printed + fprintf(fp, " %s", type__name(type, cu)); + } + + switch (tag_type->tag) { + case DW_TAG_array_type: + printed = fprintf(fp, "typedef "); + return printed + array_type__fprintf(tag_type, cu, + type__name(type, cu), + pconf, fp); + case DW_TAG_pointer_type: + if (tag_type->type == 0) /* void pointer */ + break; + ptr_type = cu__type(cu, tag_type->type); + if (ptr_type == NULL) { + printed = fprintf(fp, "typedef "); + printed += tag__id_not_found_fprintf(fp, tag_type->type); + return printed + fprintf(fp, " *%s", + type__name(type, cu)); + } + if (ptr_type->tag != DW_TAG_subroutine_type) + break; + tag_type = ptr_type; + is_pointer = 1; + /* Fall thru */ + case DW_TAG_subroutine_type: + printed = fprintf(fp, "typedef "); + return printed + ftype__fprintf(tag__ftype(tag_type), cu, + type__name(type, cu), + 0, is_pointer, 0, + true, pconf, fp); + case DW_TAG_class_type: + case DW_TAG_structure_type: { + struct type *ctype = tag__type(tag_type); + + if (type__name(ctype, cu) != NULL) + return fprintf(fp, "typedef struct %s %s", + type__name(ctype, cu), + type__name(type, cu)); + + struct conf_fprintf tconf = *pconf; + + tconf.suffix = type__name(type, cu); + return fprintf(fp, "typedef ") + __class__fprintf(tag__class(tag_type), cu, &tconf, fp); + } + case DW_TAG_enumeration_type: { + struct type *ctype = tag__type(tag_type); + + if (type__name(ctype, cu) != NULL) + return fprintf(fp, "typedef enum %s %s", type__name(ctype, cu), type__name(type, cu)); + + struct conf_fprintf tconf = *pconf; + + tconf.suffix = type__name(type, cu); + return fprintf(fp, "typedef ") + enumeration__fprintf(tag_type, cu, &tconf, fp); + } + } + + return fprintf(fp, "typedef %s %s", + tag__name(tag_type, cu, bf, sizeof(bf), pconf), + type__name(type, cu)); +} + +static size_t imported_declaration__fprintf(const struct tag *tag, + const struct cu *cu, FILE *fp) +{ + char bf[BUFSIZ]; + size_t printed = fprintf(fp, "using ::"); + const struct tag *decl = cu__function(cu, tag->type); + + if (decl == NULL) { + decl = cu__tag(cu, tag->type); + if (decl == NULL) + return printed + tag__id_not_found_fprintf(fp, tag->type); + } + + return printed + fprintf(fp, "%s", tag__name(decl, cu, bf, sizeof(bf), NULL)); +} + +static size_t imported_module__fprintf(const struct tag *tag, + const struct cu *cu, FILE *fp) +{ + const struct tag *module = cu__tag(cu, tag->type); + const char *name = "<IMPORTED MODULE ERROR!>"; + + if (tag__is_namespace(module)) + name = namespace__name(tag__namespace(module), cu); + + return fprintf(fp, "using namespace %s", name); +} + +int enumeration__max_entry_name_len(struct type *type, const struct cu *cu) +{ + if (type->max_tag_name_len) + goto out; + + struct enumerator *pos; + + type__for_each_enumerator(type, pos) { + int len = strlen(enumerator__name(pos, cu)); + + if (type->max_tag_name_len < len) + type->max_tag_name_len = len; + } +out: + return type->max_tag_name_len; +} + +size_t enumeration__fprintf(const struct tag *tag, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp) +{ + struct type *type = tag__type(tag); + struct enumerator *pos; + int max_entry_name_len = enumeration__max_entry_name_len(type, cu); + size_t printed = fprintf(fp, "enum%s%s {\n", + type__name(type, cu) ? " " : "", + type__name(type, cu) ?: ""); + int indent = conf->indent; + + if (indent >= (int)sizeof(tabs)) + indent = sizeof(tabs) - 1; + + type__for_each_enumerator(type, pos) + printed += fprintf(fp, "%.*s\t%-*s = %u,\n", indent, tabs, + max_entry_name_len, enumerator__name(pos, cu), pos->value); + + printed += fprintf(fp, "%.*s}", indent, tabs); + + /* + * XXX: find out how to precisely determine the max size for an + * enumeration, use sizeof(int) for now. + */ + if (type->size / 8 != sizeof(int)) + printed += fprintf(fp, " %s", "__attribute__((__packed__))"); + + if (conf->suffix) + printed += fprintf(fp, " %s", conf->suffix); + + return printed; +} + +static const char *tag__prefix(const struct cu *cu, const uint32_t tag, + const struct conf_fprintf *conf) +{ + switch (tag) { + case DW_TAG_enumeration_type: return "enum "; + case DW_TAG_structure_type: + return (!conf->classes_as_structs && + cu->language == DW_LANG_C_plus_plus) ? "class " : + "struct "; + case DW_TAG_class_type: + return conf->classes_as_structs ? "struct " : "class "; + case DW_TAG_union_type: return "union "; + case DW_TAG_pointer_type: return " *"; + case DW_TAG_reference_type: return " &"; + } + + return ""; +} + +static const char *__tag__name(const struct tag *tag, const struct cu *cu, + char *bf, size_t len, + const struct conf_fprintf *conf); + +static const char *tag__ptr_name(const struct tag *tag, const struct cu *cu, + char *bf, size_t len, const char *ptr_suffix) +{ + if (tag->type == 0) /* No type == void */ + snprintf(bf, len, "void %s", ptr_suffix); + else { + const struct tag *type = cu__type(cu, tag->type); + + if (type == NULL) { + size_t l = tag__id_not_found_snprintf(bf, len, tag->type); + snprintf(bf + l, len - l, " %s", ptr_suffix); + } else if (!tag__has_type_loop(tag, type, bf, len, NULL)) { + char tmpbf[1024]; + const char *const_pointer = ""; + + if (tag__is_const(type)) { + struct tag *next_type = cu__type(cu, type->type); + + if (next_type && tag__is_pointer(next_type)) { + const_pointer = "const "; + type = next_type; + } + } + + snprintf(bf, len, "%s %s%s", + __tag__name(type, cu, + tmpbf, sizeof(tmpbf), NULL), + const_pointer, + ptr_suffix); + } + } + + return bf; +} + +static const char *__tag__name(const struct tag *tag, const struct cu *cu, + char *bf, size_t len, + const struct conf_fprintf *conf) +{ + struct tag *type; + const struct conf_fprintf *pconf = conf ?: &conf_fprintf__defaults; + + if (tag == NULL) + strncpy(bf, "void", len); + else switch (tag->tag) { + case DW_TAG_base_type: { + const struct base_type *bt = tag__base_type(tag); + const char *name = "nameless base type!"; + char bf2[64]; + + if (bt->name) + name = base_type__name(tag__base_type(tag), cu, + bf2, sizeof(bf2)); + + strncpy(bf, name, len); + } + break; + case DW_TAG_subprogram: + strncpy(bf, function__name(tag__function(tag), cu), len); + break; + case DW_TAG_pointer_type: + return tag__ptr_name(tag, cu, bf, len, "*"); + case DW_TAG_reference_type: + return tag__ptr_name(tag, cu, bf, len, "&"); + case DW_TAG_ptr_to_member_type: { + char suffix[512]; + type_id_t id = tag__ptr_to_member_type(tag)->containing_type; + + type = cu__type(cu, id); + if (type != NULL) + snprintf(suffix, sizeof(suffix), "%s::*", + class__name(tag__class(type), cu)); + else { + size_t l = tag__id_not_found_snprintf(suffix, + sizeof(suffix), + id); + snprintf(suffix + l, sizeof(suffix) - l, "::*"); + } + + return tag__ptr_name(tag, cu, bf, len, suffix); + } + case DW_TAG_volatile_type: + case DW_TAG_const_type: + case DW_TAG_restrict_type: + case DW_TAG_unspecified_type: + type = cu__type(cu, tag->type); + if (type == NULL && tag->type != 0) + tag__id_not_found_snprintf(bf, len, tag->type); + else if (!tag__has_type_loop(tag, type, bf, len, NULL)) { + char tmpbf[128]; + const char *prefix = "", *suffix = "", + *type_str = __tag__name(type, cu, tmpbf, + sizeof(tmpbf), + pconf); + switch (tag->tag) { + case DW_TAG_volatile_type: prefix = "volatile "; break; + case DW_TAG_const_type: prefix = "const "; break; + case DW_TAG_restrict_type: suffix = " restrict"; break; + } + snprintf(bf, len, "%s%s%s ", prefix, type_str, suffix); + } + break; + case DW_TAG_array_type: + type = cu__type(cu, tag->type); + if (type == NULL) + tag__id_not_found_snprintf(bf, len, tag->type); + else if (!tag__has_type_loop(tag, type, bf, len, NULL)) + return __tag__name(type, cu, bf, len, pconf); + break; + case DW_TAG_subroutine_type: { + FILE *bfp = fmemopen(bf, len, "w"); + + if (bfp != NULL) { + ftype__fprintf(tag__ftype(tag), cu, NULL, 0, 0, 0, true, pconf, bfp); + fclose(bfp); + } else + snprintf(bf, len, "<ERROR(%s): fmemopen failed!>", + __func__); + } + break; + case DW_TAG_member: + snprintf(bf, len, "%s", class_member__name(tag__class_member(tag), cu)); + break; + case DW_TAG_variable: + snprintf(bf, len, "%s", variable__name(tag__variable(tag), cu)); + break; + default: + snprintf(bf, len, "%s%s", tag__prefix(cu, tag->tag, pconf), + type__name(tag__type(tag), cu) ?: ""); + break; + } + + return bf; +} + +const char *tag__name(const struct tag *tag, const struct cu *cu, + char *bf, size_t len, const struct conf_fprintf *conf) +{ + int printed = 0; + + if (tag == NULL) { + strncpy(bf, "void", len); + return bf; + } + + __tag__name(tag, cu, bf + printed, len - printed, conf); + + return bf; +} + +static const char *variable__prefix(const struct variable *var) +{ + switch (variable__scope(var)) { + case VSCOPE_REGISTER: + return "register "; + case VSCOPE_UNKNOWN: + if (var->external && var->declaration) + return "extern "; + break; + case VSCOPE_GLOBAL: + if (!var->external) + return "static "; + break; + case VSCOPE_LOCAL: + case VSCOPE_OPTIMIZED: + break; + } + return NULL; +} + +static size_t type__fprintf_stats(struct type *type, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp) +{ + size_t printed = fprintf(fp, "\n%.*s/* size: %d, cachelines: %zd, members: %u", + conf->indent, tabs, type->size, + tag__nr_cachelines(type__tag(type), cu), type->nr_members); + + if (type->nr_static_members != 0) + printed += fprintf(fp, ", static members: %u */\n", type->nr_static_members); + else + printed += fprintf(fp, " */\n"); + + return printed; +} + +static size_t union__fprintf(struct type *type, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp); + +static size_t type__fprintf(struct tag *type, const struct cu *cu, + const char *name, const struct conf_fprintf *conf, + FILE *fp) +{ + char tbf[128]; + char namebf[256]; + char namebfptr[258]; + struct type *ctype; + struct tag *type_expanded = NULL; + struct conf_fprintf tconf = { + .type_spacing = conf->type_spacing, + }; + size_t printed = 0; + int expand_types = conf->expand_types; + int suppress_offset_comment = conf->suppress_offset_comment; + + if (type == NULL) + goto out_type_not_found; + + if (conf->expand_pointers) { + int nr_indirections = 0; + + while (tag__is_pointer(type) && type->type != 0) { + struct tag *ttype = cu__type(cu, type->type); + if (ttype == NULL) + goto out_type_not_found; + else { + printed = tag__has_type_loop(type, ttype, + NULL, 0, fp); + if (printed) + return printed; + } + type = ttype; + ++nr_indirections; + } + + if (nr_indirections > 0) { + const size_t len = strlen(name); + if (len + nr_indirections >= sizeof(namebf)) + goto out_type_not_found; + memset(namebf, '*', nr_indirections); + memcpy(namebf + nr_indirections, name, len); + namebf[len + nr_indirections] = '\0'; + name = namebf; + } + + expand_types = nr_indirections; + if (!suppress_offset_comment) + suppress_offset_comment = !!nr_indirections; + + /* Avoid loops */ + if (type->recursivity_level != 0) + expand_types = 0; + ++type->recursivity_level; + type_expanded = type; + } + + if (expand_types) { + int typedef_expanded = 0; + + while (tag__is_typedef(type)) { + struct tag *type_type; + int n; + + ctype = tag__type(type); + if (typedef_expanded) + printed += fprintf(fp, " -> %s", + type__name(ctype, cu)); + else { + printed += fprintf(fp, "/* typedef %s", + type__name(ctype, cu)); + typedef_expanded = 1; + } + type_type = cu__type(cu, type->type); + if (type_type == NULL) + goto out_type_not_found; + n = tag__has_type_loop(type, type_type, NULL, 0, fp); + if (n) + return printed + n; + type = type_type; + } + if (typedef_expanded) + printed += fprintf(fp, " */ "); + } + + tconf = *conf; + + if (tag__is_struct(type) || tag__is_union(type) || + tag__is_enumeration(type)) { +inner_struct: + tconf.prefix = NULL; + tconf.suffix = name; + tconf.emit_stats = 0; + tconf.suppress_offset_comment = suppress_offset_comment; + } + +next_type: + switch (type->tag) { + case DW_TAG_pointer_type: + if (type->type != 0) { + int n; + struct tag *ptype = cu__type(cu, type->type); + if (ptype == NULL) + goto out_type_not_found; + n = tag__has_type_loop(type, ptype, NULL, 0, fp); + if (n) + return printed + n; + if (ptype->tag == DW_TAG_subroutine_type) { + printed += ftype__fprintf(tag__ftype(ptype), + cu, name, 0, 1, + tconf.type_spacing, true, + &tconf, fp); + break; + } + if ((tag__is_struct(ptype) || tag__is_union(ptype) || + tag__is_enumeration(ptype)) && type__name(tag__type(ptype), cu) == NULL) { + if (name == namebfptr) + goto out_type_not_found; + snprintf(namebfptr, sizeof(namebfptr), "* %.*s", (int)sizeof(namebfptr) - 3, name); + tconf.rel_offset = 1; + name = namebfptr; + type = ptype; + tconf.type_spacing -= 8; + goto inner_struct; + } + } + /* Fall Thru */ + default: +print_default: + printed += fprintf(fp, "%-*s %s", tconf.type_spacing, + tag__name(type, cu, tbf, sizeof(tbf), &tconf), + name); + break; + case DW_TAG_subroutine_type: + printed += ftype__fprintf(tag__ftype(type), cu, name, 0, 0, + tconf.type_spacing, true, &tconf, fp); + break; + case DW_TAG_const_type: { + size_t const_printed = fprintf(fp, "%s ", "const"); + tconf.type_spacing -= const_printed; + printed += const_printed; + + struct tag *ttype = cu__type(cu, type->type); + if (ttype) { + type = ttype; + goto next_type; + } + } + goto print_default; + + case DW_TAG_array_type: + printed += array_type__fprintf(type, cu, name, &tconf, fp); + break; + case DW_TAG_string_type: + printed += string_type__fprintf(type, cu, name, &tconf, fp); + break; + case DW_TAG_class_type: + case DW_TAG_structure_type: + ctype = tag__type(type); + + if (type__name(ctype, cu) != NULL && !expand_types) { + printed += fprintf(fp, "%s %-*s %s", + (type->tag == DW_TAG_class_type && + !tconf.classes_as_structs) ? "class" : "struct", + tconf.type_spacing - 7, + type__name(ctype, cu), name); + } else { + struct class *cclass = tag__class(type); + + if (!tconf.suppress_comments) + class__find_holes(cclass); + + tconf.type_spacing -= 8; + printed += __class__fprintf(cclass, cu, &tconf, fp); + } + break; + case DW_TAG_union_type: + ctype = tag__type(type); + + if (type__name(ctype, cu) != NULL && !expand_types) { + printed += fprintf(fp, "union %-*s %s", + tconf.type_spacing - 6, + type__name(ctype, cu), name); + } else { + tconf.type_spacing -= 8; + printed += union__fprintf(ctype, cu, &tconf, fp); + } + break; + case DW_TAG_enumeration_type: + ctype = tag__type(type); + + if (type__name(ctype, cu) != NULL) + printed += fprintf(fp, "enum %-*s %s", + tconf.type_spacing - 5, + type__name(ctype, cu), name); + else + printed += enumeration__fprintf(type, cu, &tconf, fp); + break; + } +out: + if (type_expanded) + --type_expanded->recursivity_level; + + return printed; +out_type_not_found: + printed = fprintf(fp, "%-*s%s> %s", tconf.type_spacing, "<ERROR", + name == namebfptr ? ": pointer to pointer to inner struct/union/enum?" : "", name); + goto out; +} + +static size_t class__fprintf_cacheline_boundary(struct conf_fprintf *conf, + uint32_t offset, + FILE *fp); + +static size_t class_member__fprintf(struct class_member *member, bool union_member, + struct tag *type, const struct cu *cu, + struct conf_fprintf *conf, FILE *fp) +{ + const int size = member->byte_size; + struct conf_fprintf sconf = *conf; + uint32_t offset = member->byte_offset; + size_t printed = 0, printed_cacheline = 0; + const char *cm_name = class_member__name(member, cu), + *name = cm_name; + + if (!sconf.rel_offset) { + offset += sconf.base_offset; + if (!union_member) + sconf.base_offset = offset; + } + + if (member->bitfield_offset < 0) + offset += member->byte_size; + + if (!conf->suppress_comments) + printed_cacheline = class__fprintf_cacheline_boundary(conf, offset, fp); + + if (member->tag.tag == DW_TAG_inheritance) { + name = "<ancestor>"; + printed += fprintf(fp, "/* "); + } + + if (member->is_static) + printed += fprintf(fp, "static "); + + printed += type__fprintf(type, cu, name, &sconf, fp); + + if (member->is_static) { + if (member->const_value != 0) + printed += fprintf(fp, " = %" PRIu64, member->const_value); + } else if (member->bitfield_size != 0) { + printed += fprintf(fp, ":%u", member->bitfield_size); + } + + if (!sconf.suppress_aligned_attribute && member->alignment != 0) + printed += fprintf(fp, " __attribute__((__aligned__(%u)))", member->alignment); + + fputc(';', fp); + ++printed; + + if ((tag__is_union(type) || tag__is_struct(type) || + tag__is_enumeration(type)) && + /* Look if is a type defined inline */ + type__name(tag__type(type), cu) == NULL) { + if (!sconf.suppress_offset_comment) { + /* Check if this is a anonymous union */ + int slen = cm_name ? (int)strlen(cm_name) : -1; + int size_spacing = 5; + + if (tag__is_struct(type) && tag__class(type)->is_packed && !conf->suppress_packed) { + int packed_len = sizeof("__attribute__((__packed__))"); + slen += packed_len; + } + + if (tag__type(type)->alignment != 0 && !conf->suppress_aligned_attribute) { + char bftmp[64]; + int aligned_len = snprintf(bftmp, sizeof(bftmp), " __attribute__((__aligned__(%u)))", tag__type(type)->alignment); + slen += aligned_len; + } + + printed += fprintf(fp, sconf.hex_fmt ? + "%*s/* %#5x" : + "%*s/* %5u", + (sconf.type_spacing + + sconf.name_spacing - slen - 3), + " ", offset); + + if (member->bitfield_size != 0) { + unsigned int bitfield_offset = member->bitfield_offset; + + if (member->bitfield_offset < 0) + bitfield_offset = member->byte_size * 8 + member->bitfield_offset; + + printed += fprintf(fp, sconf.hex_fmt ? ":%#2x" : ":%2u", bitfield_offset); + size_spacing -= 3; + } + + printed += fprintf(fp, sconf.hex_fmt ? " %#*x */" : " %*u */", size_spacing, size); + } + } else { + int spacing = sconf.type_spacing + sconf.name_spacing - printed; + + if (member->tag.tag == DW_TAG_inheritance) { + const size_t p = fprintf(fp, " */"); + printed += p; + spacing -= p; + } + if (!sconf.suppress_offset_comment) { + int size_spacing = 5; + + printed += fprintf(fp, sconf.hex_fmt ? + "%*s/* %#5x" : "%*s/* %5u", + spacing > 0 ? spacing : 0, " ", + offset); + + if (member->bitfield_size != 0) { + unsigned int bitfield_offset = member->bitfield_offset; + + if (member->bitfield_offset < 0) + bitfield_offset = member->byte_size * 8 + member->bitfield_offset; + + printed += fprintf(fp, sconf.hex_fmt ? + ":%#2x" : ":%2u", + bitfield_offset); + size_spacing -= 3; + } + + printed += fprintf(fp, sconf.hex_fmt ? + " %#*x */" : " %*u */", + size_spacing, size); + } + } + return printed + printed_cacheline; +} + +static size_t struct_member__fprintf(struct class_member *member, + struct tag *type, const struct cu *cu, + struct conf_fprintf *conf, FILE *fp) +{ + return class_member__fprintf(member, false, type, cu, conf, fp); +} + +static size_t union_member__fprintf(struct class_member *member, + struct tag *type, const struct cu *cu, + struct conf_fprintf *conf, FILE *fp) +{ + return class_member__fprintf(member, true, type, cu, conf, fp); +} + +static size_t union__fprintf(struct type *type, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp) +{ + struct class_member *pos; + size_t printed = 0; + int indent = conf->indent; + struct conf_fprintf uconf; + uint32_t initial_union_cacheline; + uint32_t cacheline = 0; /* This will only be used if this is the outermost union */ + + if (indent >= (int)sizeof(tabs)) + indent = sizeof(tabs) - 1; + + if (conf->prefix != NULL) + printed += fprintf(fp, "%s ", conf->prefix); + printed += fprintf(fp, "union%s%s {\n", type__name(type, cu) ? " " : "", + type__name(type, cu) ?: ""); + + uconf = *conf; + uconf.indent = indent + 1; + + /* + * If structs embedded in unions, nameless or not, have a size which isn't + * isn't a multiple of the union size, then it must be packed, even if + * it has no holes nor padding, as an array of such unions would have the + * natural alignments of non-multiple structs inside it broken. + */ + union__infer_packed_attributes(type, cu); + + /* + * We may be called directly or from tag__fprintf, so keep sure + * we keep track of the cacheline we're in. + * + * If we're being called from an outer structure, i.e. union within + * struct, class or another union, then this will already have a + * value and we'll continue to use it. + */ + if (uconf.cachelinep == NULL) + uconf.cachelinep = &cacheline; + /* + * Save the cacheline we're in, then, after each union member, get + * back to it. Else we'll end up showing cacheline boundaries in + * just the first of a multi struct union, for instance. + */ + initial_union_cacheline = *uconf.cachelinep; + type__for_each_member(type, pos) { + struct tag *pos_type = cu__type(cu, pos->tag.type); + + if (pos_type == NULL) { + printed += fprintf(fp, "%.*s", uconf.indent, tabs); + printed += tag__id_not_found_fprintf(fp, pos->tag.type); + continue; + } + + uconf.union_member = 1; + printed += fprintf(fp, "%.*s", uconf.indent, tabs); + printed += union_member__fprintf(pos, pos_type, cu, &uconf, fp); + fputc('\n', fp); + ++printed; + *uconf.cachelinep = initial_union_cacheline; + } + + return printed + fprintf(fp, "%.*s}%s%s", indent, tabs, + conf->suffix ? " " : "", conf->suffix ?: ""); +} + +const char *function__prototype(const struct function *func, + const struct cu *cu, char *bf, size_t len) +{ + FILE *bfp = fmemopen(bf, len, "w"); + + if (bfp != NULL) { + ftype__fprintf(&func->proto, cu, NULL, 0, 0, 0, true, + &conf_fprintf__defaults, bfp); + fclose(bfp); + } else + snprintf(bf, len, "<ERROR(%s): fmemopen failed!>", __func__); + + return bf; +} + +size_t ftype__fprintf_parms(const struct ftype *ftype, + const struct cu *cu, int indent, + const struct conf_fprintf *conf, FILE *fp) +{ + struct parameter *pos; + int first_parm = 1; + char sbf[128]; + struct tag *type; + const char *name, *stype; + size_t printed = fprintf(fp, "("); + + ftype__for_each_parameter(ftype, pos) { + if (!first_parm) { + if (indent == 0) + printed += fprintf(fp, ", "); + else + printed += fprintf(fp, ",\n%.*s", + indent, tabs); + } else + first_parm = 0; + name = conf->no_parm_names ? NULL : parameter__name(pos, cu); + type = cu__type(cu, pos->tag.type); + if (type == NULL) { + snprintf(sbf, sizeof(sbf), + "<ERROR: type %d not found>", pos->tag.type); + stype = sbf; + goto print_it; + } + if (tag__is_pointer(type)) { + if (type->type != 0) { + int n; + struct tag *ptype = cu__type(cu, type->type); + if (ptype == NULL) { + printed += + tag__id_not_found_fprintf(fp, type->type); + continue; + } + n = tag__has_type_loop(type, ptype, NULL, 0, fp); + if (n) + return printed + n; + if (ptype->tag == DW_TAG_subroutine_type) { + printed += + ftype__fprintf(tag__ftype(ptype), + cu, name, 0, 1, 0, + true, conf, fp); + continue; + } + } + } else if (type->tag == DW_TAG_subroutine_type) { + printed += ftype__fprintf(tag__ftype(type), cu, name, + true, 0, 0, 0, conf, fp); + continue; + } + stype = tag__name(type, cu, sbf, sizeof(sbf), conf); +print_it: + printed += fprintf(fp, "%s%s%s", stype, name ? " " : "", + name ?: ""); + } + + /* No parameters? */ + if (first_parm) + printed += fprintf(fp, "void)"); + else if (ftype->unspec_parms) + printed += fprintf(fp, ", ...)"); + else + printed += fprintf(fp, ")"); + return printed; +} + +static size_t function__tag_fprintf(const struct tag *tag, const struct cu *cu, + struct function *function, uint16_t indent, + const struct conf_fprintf *conf, FILE *fp) +{ + char bf[512]; + size_t printed = 0, n; + const void *vtag = tag; + int c; + + if (indent >= sizeof(tabs)) + indent = sizeof(tabs) - 1; + c = indent * 8; + + switch (tag->tag) { + case DW_TAG_inlined_subroutine: { + const struct inline_expansion *exp = vtag; + const struct tag *talias = cu__function(cu, exp->ip.tag.type); + struct function *alias = tag__function(talias); + const char *name; + + if (alias == NULL) { + printed += tag__id_not_found_fprintf(fp, exp->ip.tag.type); + break; + } + printed = fprintf(fp, "%.*s", indent, tabs); + name = function__name(alias, cu); + n = fprintf(fp, "%s", name); + size_t namelen = 0; + if (name != NULL) + namelen = strlen(name); + n += ftype__fprintf_parms(&alias->proto, cu, + indent + (namelen + 7) / 8, + conf, fp); + n += fprintf(fp, "; /* size=%zd, low_pc=%#llx */", + exp->size, (unsigned long long)exp->ip.addr); +#if 0 + n = fprintf(fp, "%s(); /* size=%zd, low_pc=%#llx */", + function__name(alias, cu), exp->size, + (unsigned long long)exp->ip.addr); +#endif + c = 69; + printed += n; + } + break; + case DW_TAG_variable: + printed = fprintf(fp, "%.*s", indent, tabs); + n = fprintf(fp, "%s %s; /* scope: %s */", + variable__type_name(vtag, cu, bf, sizeof(bf)), + variable__name(vtag, cu), + variable__scope_str(vtag)); + c += n; + printed += n; + break; + case DW_TAG_label: { + const struct label *label = vtag; + printed = fprintf(fp, "%.*s", indent, tabs); + fputc('\n', fp); + ++printed; + c = fprintf(fp, "%s:", label__name(label, cu)); + printed += c; + } + break; + case DW_TAG_lexical_block: + printed = lexblock__fprintf(vtag, cu, function, indent, + conf, fp); + fputc('\n', fp); + return printed + 1; + default: + printed = fprintf(fp, "%.*s", indent, tabs); + n = fprintf(fp, "%s <%llx>", dwarf_tag_name(tag->tag), + tag__orig_id(tag, cu)); + c += n; + printed += n; + break; + } + return printed + fprintf(fp, "%-*.*s// %5u\n", 70 - c, 70 - c, " ", + tag__decl_line(tag, cu)); +} + +size_t lexblock__fprintf(const struct lexblock *block, const struct cu *cu, + struct function *function, uint16_t indent, + const struct conf_fprintf *conf, FILE *fp) +{ + struct tag *pos; + size_t printed; + + if (indent >= sizeof(tabs)) + indent = sizeof(tabs) - 1; + printed = fprintf(fp, "%.*s{", indent, tabs); + if (block->ip.addr != 0) { + uint64_t offset = block->ip.addr - function->lexblock.ip.addr; + + if (offset == 0) + printed += fprintf(fp, " /* low_pc=%#llx */", + (unsigned long long)block->ip.addr); + else + printed += fprintf(fp, " /* %s+%#llx */", + function__name(function, cu), + (unsigned long long)offset); + } + printed += fprintf(fp, "\n"); + list_for_each_entry(pos, &block->tags, node) + printed += function__tag_fprintf(pos, cu, function, indent + 1, + conf, fp); + printed += fprintf(fp, "%.*s}", indent, tabs); + + if (function->lexblock.ip.addr != block->ip.addr) + printed += fprintf(fp, " /* lexblock size=%d */", block->size); + + return printed; +} + +size_t ftype__fprintf(const struct ftype *ftype, const struct cu *cu, + const char *name, const int inlined, + const int is_pointer, int type_spacing, bool is_prototype, + const struct conf_fprintf *conf, FILE *fp) +{ + struct tag *type = cu__type(cu, ftype->tag.type); + char sbf[128]; + const char *stype = tag__name(type, cu, sbf, sizeof(sbf), conf); + size_t printed = fprintf(fp, "%s%-*s %s%s%s%s", + inlined ? "inline " : "", + type_spacing, stype, + is_prototype ? "(" : "", + is_pointer ? "*" : "", name ?: "", + is_prototype ? ")" : ""); + + return printed + ftype__fprintf_parms(ftype, cu, 0, conf, fp); +} + +static size_t function__fprintf(const struct tag *tag, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp) +{ + struct function *func = tag__function(tag); + struct ftype *ftype = func->btf ? tag__ftype(cu__type(cu, func->proto.tag.type)) : &func->proto; + size_t printed = 0; + bool inlined = !conf->strip_inline && function__declared_inline(func); + + if (func->virtuality == DW_VIRTUALITY_virtual || + func->virtuality == DW_VIRTUALITY_pure_virtual) + printed += fprintf(fp, "virtual "); + + printed += ftype__fprintf(ftype, cu, function__name(func, cu), + inlined, 0, 0, false, conf, fp); + + if (func->virtuality == DW_VIRTUALITY_pure_virtual) + printed += fprintf(fp, " = 0"); + + return printed; +} + +size_t function__fprintf_stats(const struct tag *tag, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp) +{ + struct function *func = tag__function(tag); + size_t printed = lexblock__fprintf(&func->lexblock, cu, func, 0, conf, fp); + + printed += fprintf(fp, "/* size: %d", function__size(func)); + if (func->lexblock.nr_variables > 0) + printed += fprintf(fp, ", variables: %u", + func->lexblock.nr_variables); + if (func->lexblock.nr_labels > 0) + printed += fprintf(fp, ", goto labels: %u", + func->lexblock.nr_labels); + if (func->lexblock.nr_inline_expansions > 0) + printed += fprintf(fp, ", inline expansions: %u (%d bytes)", + func->lexblock.nr_inline_expansions, + func->lexblock.size_inline_expansions); + return printed + fprintf(fp, " */\n"); +} + +static size_t class__fprintf_cacheline_boundary(struct conf_fprintf *conf, + uint32_t offset, + FILE *fp) +{ + int indent = conf->indent; + uint32_t cacheline = offset / cacheline_size; + size_t printed = 0; + + if (cacheline > *conf->cachelinep) { + const uint32_t cacheline_pos = offset % cacheline_size; + const uint32_t cacheline_in_bytes = offset - cacheline_pos; + + if (cacheline_pos == 0) + printed += fprintf(fp, "/* --- cacheline %u boundary " + "(%u bytes) --- */\n", cacheline, + cacheline_in_bytes); + else + printed += fprintf(fp, "/* --- cacheline %u boundary " + "(%u bytes) was %u bytes ago --- " + "*/\n", cacheline, + cacheline_in_bytes, cacheline_pos); + + printed += fprintf(fp, "%.*s", indent, tabs); + + *conf->cachelinep = cacheline; + } + return printed; +} + +static size_t class__vtable_fprintf(struct class *class, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp) +{ + struct function *pos; + size_t printed = 0; + + if (class->nr_vtable_entries == 0) + goto out; + + printed += fprintf(fp, "%.*s/* vtable has %u entries: {\n", + conf->indent, tabs, class->nr_vtable_entries); + + list_for_each_entry(pos, &class->vtable, vtable_node) { + printed += fprintf(fp, "%.*s [%d] = %s(%s), \n", + conf->indent, tabs, pos->vtable_entry, + function__name(pos, cu), + function__linkage_name(pos, cu)); + } + + printed += fprintf(fp, "%.*s} */", conf->indent, tabs); +out: + return printed; +} + +static size_t __class__fprintf(struct class *class, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp) +{ + struct type *type = &class->type; + size_t last_size = 0, size; + uint8_t newline = 0; + uint16_t nr_paddings = 0; + uint16_t nr_forced_alignments = 0, nr_forced_alignment_holes = 0; + uint32_t sum_forced_alignment_holes = 0; + uint32_t sum_bytes = 0, sum_bits = 0; + uint32_t sum_holes = 0; + uint32_t sum_paddings = 0; + uint32_t sum_bit_holes = 0; + uint32_t cacheline = 0; + int size_diff = 0; + int first = 1; + struct class_member *pos, *last = NULL; + struct tag *tag_pos; + const char *current_accessibility = NULL; + struct conf_fprintf cconf = conf ? *conf : conf_fprintf__defaults; + const uint16_t t = type->namespace.tag.tag; + size_t printed = fprintf(fp, "%s%s%s%s%s", + cconf.prefix ?: "", cconf.prefix ? " " : "", + ((cconf.classes_as_structs || + t == DW_TAG_structure_type) ? "struct" : + t == DW_TAG_class_type ? "class" : + "interface"), + type__name(type, cu) ? " " : "", + type__name(type, cu) ?: ""); + int indent = cconf.indent; + + if (indent >= (int)sizeof(tabs)) + indent = sizeof(tabs) - 1; + + if (cconf.cachelinep == NULL) + cconf.cachelinep = &cacheline; + + cconf.indent = indent + 1; + cconf.no_semicolon = 0; + + class__infer_packed_attributes(class, cu); + + /* First look if we have DW_TAG_inheritance */ + type__for_each_tag(type, tag_pos) { + const char *accessibility; + + if (tag_pos->tag != DW_TAG_inheritance) + continue; + + if (first) { + printed += fprintf(fp, " :"); + first = 0; + } else + printed += fprintf(fp, ","); + + pos = tag__class_member(tag_pos); + + if (pos->virtuality == DW_VIRTUALITY_virtual) + printed += fprintf(fp, " virtual"); + + accessibility = tag__accessibility(tag_pos); + if (accessibility != NULL) + printed += fprintf(fp, " %s", accessibility); + + struct tag *pos_type = cu__type(cu, tag_pos->type); + if (pos_type != NULL) + printed += fprintf(fp, " %s", + type__name(tag__type(pos_type), cu)); + else + printed += tag__id_not_found_fprintf(fp, tag_pos->type); + } + + printed += fprintf(fp, " {\n"); + + if (class->pre_bit_hole > 0 && !cconf.suppress_comments) { + if (!newline++) { + fputc('\n', fp); + ++printed; + } + printed += fprintf(fp, "%.*s/* XXX %d bit%s hole, " + "try to pack */\n", cconf.indent, tabs, + class->pre_bit_hole, + class->pre_bit_hole != 1 ? "s" : ""); + sum_bit_holes += class->pre_bit_hole; + } + + if (class->pre_hole > 0 && !cconf.suppress_comments) { + if (!newline++) { + fputc('\n', fp); + ++printed; + } + printed += fprintf(fp, "%.*s/* XXX %d byte%s hole, " + "try to pack */\n", + cconf.indent, tabs, class->pre_hole, + class->pre_hole != 1 ? "s" : ""); + sum_holes += class->pre_hole; + } + + type__for_each_tag(type, tag_pos) { + const char *accessibility = tag__accessibility(tag_pos); + + if (accessibility != NULL && + accessibility != current_accessibility) { + current_accessibility = accessibility; + printed += fprintf(fp, "%.*s%s:\n\n", + cconf.indent - 1, tabs, + accessibility); + } + + if (tag_pos->tag != DW_TAG_member && + tag_pos->tag != DW_TAG_inheritance) { + if (!cconf.show_only_data_members) { + printed += tag__fprintf(tag_pos, cu, &cconf, fp); + printed += fprintf(fp, "\n\n"); + } + continue; + } + pos = tag__class_member(tag_pos); + + if (!cconf.suppress_aligned_attribute && pos->alignment != 0) { + uint32_t forced_alignment_hole = last ? last->hole : class->pre_hole; + + if (forced_alignment_hole != 0) { + ++nr_forced_alignment_holes; + sum_forced_alignment_holes += forced_alignment_hole; + } + ++nr_forced_alignments; + } + /* + * These paranoid checks doesn't make much sense on + * DW_TAG_inheritance, have to understand why virtual public + * ancestors make the offset go backwards... + */ + if (last != NULL && tag_pos->tag == DW_TAG_member && + /* + * kmemcheck bitfield tricks use zero sized arrays as markers + * all over the place. + */ + last_size != 0) { + if (last->bit_hole != 0 && pos->bitfield_size) { + uint8_t bitfield_size = last->bit_hole; + struct tag *pos_type = cu__type(cu, pos->tag.type); + + if (pos_type == NULL) { + printed += fprintf(fp, "%.*s", cconf.indent, tabs); + printed += tag__id_not_found_fprintf(fp, pos->tag.type); + continue; + } + /* + * Now check if this isn't something like 'unsigned :N' with N > 0, + * i.e. _explicitely_ adding a bit hole. + */ + if (last->byte_offset != pos->byte_offset) { + printed += fprintf(fp, "\n%.*s/* Force alignment to the next boundary: */\n", cconf.indent, tabs); + bitfield_size = 0; + } + + printed += fprintf(fp, "%.*s", cconf.indent, tabs); + printed += type__fprintf(pos_type, cu, "", &cconf, fp); + printed += fprintf(fp, ":%u;\n", bitfield_size); + } + + if (pos->byte_offset < last->byte_offset || + (pos->byte_offset == last->byte_offset && + last->bitfield_size == 0 && + /* + * This is just when transitioning from a non-bitfield to + * a bitfield, think about zero sized arrays in the middle + * of a struct. + */ + pos->bitfield_size != 0)) { + if (!cconf.suppress_comments) { + if (!newline++) { + fputc('\n', fp); + ++printed; + } + printed += fprintf(fp, "%.*s/* Bitfield combined" + " with previous fields */\n", + cconf.indent, tabs); + } + } else { + const ssize_t cc_last_size = ((ssize_t)pos->byte_offset - + (ssize_t)last->byte_offset); + + if (cc_last_size > 0 && + (size_t)cc_last_size < last_size) { + if (!cconf.suppress_comments) { + if (!newline++) { + fputc('\n', fp); + ++printed; + } + printed += fprintf(fp, "%.*s/* Bitfield combined" + " with next fields */\n", + cconf.indent, tabs); + } + } + } + } + + if (newline) { + fputc('\n', fp); + newline = 0; + ++printed; + } + + struct tag *pos_type = cu__type(cu, pos->tag.type); + if (pos_type == NULL) { + printed += fprintf(fp, "%.*s", cconf.indent, tabs); + printed += tag__id_not_found_fprintf(fp, pos->tag.type); + continue; + } + + cconf.last_member = list_is_last(&tag_pos->node, &type->namespace.tags); + cconf.first_member = last == NULL; + + size = pos->byte_size; + printed += fprintf(fp, "%.*s", cconf.indent, tabs); + printed += struct_member__fprintf(pos, pos_type, cu, &cconf, fp); + + if (tag__is_struct(pos_type) && !cconf.suppress_comments) { + struct class *tclass = tag__class(pos_type); + uint16_t padding; + /* + * We may not yet have looked for holes and paddings + * in this member's struct type. + */ + class__find_holes(tclass); + class__infer_packed_attributes(tclass, cu); + + padding = tclass->padding; + if (padding > 0) { + ++nr_paddings; + sum_paddings += padding; + if (!newline++) { + fputc('\n', fp); + ++printed; + } + + printed += fprintf(fp, "\n%.*s/* XXX last " + "struct has %d byte%s of " + "padding */", cconf.indent, + tabs, padding, + padding != 1 ? "s" : ""); + } + } + + if (pos->bit_hole != 0 && !cconf.suppress_comments) { + if (!newline++) { + fputc('\n', fp); + ++printed; + } + printed += fprintf(fp, "\n%.*s/* XXX %d bit%s hole, " + "try to pack */", cconf.indent, tabs, + pos->bit_hole, + pos->bit_hole != 1 ? "s" : ""); + sum_bit_holes += pos->bit_hole; + } + + if (pos->hole > 0 && !cconf.suppress_comments) { + if (!newline++) { + fputc('\n', fp); + ++printed; + } + printed += fprintf(fp, "\n%.*s/* XXX %d byte%s hole, " + "try to pack */", + cconf.indent, tabs, pos->hole, + pos->hole != 1 ? "s" : ""); + sum_holes += pos->hole; + } + + fputc('\n', fp); + ++printed; + + /* XXX for now just skip these */ + if (tag_pos->tag == DW_TAG_inheritance) + continue; +#if 0 + /* + * This one was being skipped but caused problems with: + * http://article.gmane.org/gmane.comp.debugging.dwarves/185 + * http://www.spinics.net/lists/dwarves/msg00119.html + */ + if (pos->virtuality == DW_VIRTUALITY_virtual) + continue; +#endif + + if (pos->bitfield_size) { + sum_bits += pos->bitfield_size; + } else { + sum_bytes += pos->byte_size; + } + + if (last == NULL || /* First member */ + /* + * Last member was a zero sized array, typedef, struct, etc + */ + last_size == 0 || + /* + * We moved to a new offset + */ + last->byte_offset != pos->byte_offset) { + last_size = size; + } else if (last->bitfield_size == 0 && pos->bitfield_size != 0) { + /* + * Transitioned from from a non-bitfield to a + * bitfield sharing the same offset + */ + /* + * Compensate by removing the size of the + * last member that is "inside" this new + * member at the same offset. + * + * E.g.: + * struct foo { + * u8 a; / 0 1 / + * int b:1; / 0:23 4 / + * } + */ + last_size = size; + } + + last = pos; + } + + /* + * BTF doesn't have alignment info, for now use this infor from the loader + * to avoid adding the forced bitfield paddings and have btfdiff happy. + */ + if (class->padding != 0 && type->alignment == 0 && cconf.has_alignment_info && + !cconf.suppress_force_paddings && last != NULL) { + tag_pos = cu__type(cu, last->tag.type); + size = tag__size(tag_pos, cu); + + if (is_power_of_2(size) && class->padding > cu->addr_size) { + int added_padding; + int bit_size = size * 8; + + printed += fprintf(fp, "\n%.*s/* Force padding: */\n", cconf.indent, tabs); + + for (added_padding = 0; added_padding < class->padding; added_padding += size) { + printed += fprintf(fp, "%.*s", cconf.indent, tabs); + printed += type__fprintf(tag_pos, cu, "", &cconf, fp); + printed += fprintf(fp, ":%u;\n", bit_size); + } + } + } + + if (!cconf.show_only_data_members) + class__vtable_fprintf(class, cu, &cconf, fp); + + if (!cconf.emit_stats) + goto out; + + printed += type__fprintf_stats(type, cu, &cconf, fp); + + if (sum_holes > 0 || sum_bit_holes > 0) { + if (sum_bytes > 0) { + printed += fprintf(fp, "%.*s/* sum members: %u", + cconf.indent, tabs, sum_bytes); + if (sum_holes > 0) + printed += fprintf(fp, ", holes: %d, sum holes: %u", + class->nr_holes, sum_holes); + printed += fprintf(fp, " */\n"); + } + if (sum_bits > 0) { + printed += fprintf(fp, "%.*s/* sum bitfield members: %u bits", + cconf.indent, tabs, sum_bits); + if (sum_bit_holes > 0) + printed += fprintf(fp, ", bit holes: %d, sum bit holes: %u bits", + class->nr_bit_holes, sum_bit_holes); + else + printed += fprintf(fp, " (%u bytes)", sum_bits / 8); + printed += fprintf(fp, " */\n"); + } + } + if (class->padding > 0) + printed += fprintf(fp, "%.*s/* padding: %u */\n", + cconf.indent, + tabs, class->padding); + if (nr_paddings > 0) + printed += fprintf(fp, "%.*s/* paddings: %u, sum paddings: " + "%u */\n", + cconf.indent, tabs, + nr_paddings, sum_paddings); + if (class->bit_padding > 0) + printed += fprintf(fp, "%.*s/* bit_padding: %u bits */\n", + cconf.indent, tabs, + class->bit_padding); + if (!cconf.suppress_aligned_attribute && nr_forced_alignments != 0) { + printed += fprintf(fp, "%.*s/* forced alignments: %u", + cconf.indent, tabs, + nr_forced_alignments); + if (nr_forced_alignment_holes != 0) { + printed += fprintf(fp, ", forced holes: %u, sum forced holes: %u", + nr_forced_alignment_holes, + sum_forced_alignment_holes); + } + printed += fprintf(fp, " */\n"); + } + cacheline = (cconf.base_offset + type->size) % cacheline_size; + if (cacheline != 0) + printed += fprintf(fp, "%.*s/* last cacheline: %u bytes */\n", + cconf.indent, tabs, + cacheline); + if (cconf.show_first_biggest_size_base_type_member && + type->nr_members != 0) { + struct class_member *m = type__find_first_biggest_size_base_type_member(type, cu); + + printed += fprintf(fp, "%.*s/* first biggest size base type member: %s %u %zd */\n", + cconf.indent, tabs, + class_member__name(m, cu), m->byte_offset, + m->byte_size); + } + + size_diff = type->size * 8 - (sum_bytes * 8 + sum_bits + sum_holes * 8 + sum_bit_holes + + class->padding * 8 + class->bit_padding); + if (size_diff && type->nr_members != 0) + printed += fprintf(fp, "\n%.*s/* BRAIN FART ALERT! %d bytes != " + "%u (member bytes) + %u (member bits) " + "+ %u (byte holes) + %u (bit holes), diff = %d bits */\n", + cconf.indent, tabs, + type->size, sum_bytes, sum_bits, sum_holes, sum_bit_holes, size_diff); +out: + printed += fprintf(fp, "%.*s}", indent, tabs); + + if (class->is_packed && !cconf.suppress_packed) + printed += fprintf(fp, " __attribute__((__packed__))"); + + if (cconf.suffix) + printed += fprintf(fp, " %s", cconf.suffix); + + /* + * A class that was marked packed by class__infer_packed_attributes + * because it has an alignment that is different than its natural + * alignment, should not print the __alignment__ here, just the + * __packed__ attribute. + */ + if (!cconf.suppress_aligned_attribute && type->alignment != 0 && !class->is_packed) + printed += fprintf(fp, " __attribute__((__aligned__(%u)))", type->alignment); + + return printed; +} + +size_t class__fprintf(struct class *class, const struct cu *cu, FILE *fp) +{ + return __class__fprintf(class, cu, NULL, fp); +} + +static size_t variable__fprintf(const struct tag *tag, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp) +{ + const struct variable *var = tag__variable(tag); + const char *name = variable__name(var, cu); + size_t printed = 0; + + if (name != NULL) { + struct tag *type = cu__type(cu, var->ip.tag.type); + if (type != NULL) { + const char *varprefix = variable__prefix(var); + + if (varprefix != NULL) + printed += fprintf(fp, "%s", varprefix); + printed += type__fprintf(type, cu, name, conf, fp); + } + } + return printed; +} + +static size_t namespace__fprintf(const struct tag *tag, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp) +{ + struct namespace *space = tag__namespace(tag); + struct conf_fprintf cconf = *conf; + size_t printed = fprintf(fp, "namespace %s {\n", + namespace__name(space, cu)); + struct tag *pos; + + ++cconf.indent; + cconf.no_semicolon = 0; + + namespace__for_each_tag(space, pos) { + printed += tag__fprintf(pos, cu, &cconf, fp); + printed += fprintf(fp, "\n\n"); + } + + return printed + fprintf(fp, "}"); +} + +size_t tag__fprintf(struct tag *tag, const struct cu *cu, + const struct conf_fprintf *conf, FILE *fp) +{ + size_t printed = 0; + struct conf_fprintf tconf; + const struct conf_fprintf *pconf = conf; + + if (conf == NULL) { + tconf = conf_fprintf__defaults; + pconf = &tconf; + + if (tconf.expand_types) + tconf.name_spacing = 55; + else if (tag__is_union(tag)) + tconf.name_spacing = 21; + } else if (conf->name_spacing == 0 || conf->type_spacing == 0) { + tconf = *conf; + pconf = &tconf; + + if (tconf.name_spacing == 0) { + if (tconf.expand_types) + tconf.name_spacing = 55; + else + tconf.name_spacing = tag__is_union(tag) ? 21 : 23; + } + if (tconf.type_spacing == 0) + tconf.type_spacing = 26; + } + + if (pconf->expand_types) + ++tag->recursivity_level; + + if (pconf->show_decl_info) { + printed += fprintf(fp, "%.*s", pconf->indent, tabs); + printed += fprintf(fp, "/* Used at: %s */\n", cu->name); + printed += fprintf(fp, "%.*s", pconf->indent, tabs); + printed += tag__fprintf_decl_info(tag, cu, fp); + } + printed += fprintf(fp, "%.*s", pconf->indent, tabs); + + switch (tag->tag) { + case DW_TAG_array_type: + printed += array_type__fprintf(tag, cu, "array", pconf, fp); + break; + case DW_TAG_enumeration_type: + printed += enumeration__fprintf(tag, cu, pconf, fp); + break; + case DW_TAG_typedef: + printed += typedef__fprintf(tag, cu, pconf, fp); + break; + case DW_TAG_class_type: + case DW_TAG_interface_type: + case DW_TAG_structure_type: + printed += __class__fprintf(tag__class(tag), cu, pconf, fp); + break; + case DW_TAG_subroutine_type: + printed += ftype__fprintf(tag__ftype(tag), cu, NULL, false, false, 0, true, pconf, fp); + break; + case DW_TAG_namespace: + printed += namespace__fprintf(tag, cu, pconf, fp); + break; + case DW_TAG_subprogram: + printed += function__fprintf(tag, cu, pconf, fp); + break; + case DW_TAG_union_type: + printed += union__fprintf(tag__type(tag), cu, pconf, fp); + break; + case DW_TAG_variable: + printed += variable__fprintf(tag, cu, pconf, fp); + break; + case DW_TAG_imported_declaration: + printed += imported_declaration__fprintf(tag, cu, fp); + break; + case DW_TAG_imported_module: + printed += imported_module__fprintf(tag, cu, fp); + break; + default: + printed += fprintf(fp, "/* %s: %s tag not supported! */", + __func__, dwarf_tag_name(tag->tag)); + break; + } + + if (!pconf->no_semicolon) { + fputc(';', fp); + ++printed; + } + + if (tag__is_function(tag) && !pconf->suppress_comments) { + const struct function *func = tag__function(tag); + + if (func->linkage_name) + printed += fprintf(fp, " /* linkage=%s */", + function__linkage_name(func, cu)); + } + + if (pconf->expand_types) + --tag->recursivity_level; + + return printed; +} + +void cus__print_error_msg(const char *progname, const struct cus *cus, + const char *filename, const int err) +{ + if (err == -EINVAL || (cus != NULL && list_empty(&cus->cus))) + fprintf(stderr, "%s: couldn't load debugging info from %s\n", + progname, filename); + else + fprintf(stderr, "%s: %s\n", progname, strerror(err)); +} + +void dwarves__fprintf_init(uint16_t user_cacheline_size) +{ + if (user_cacheline_size == 0) { + long sys_cacheline_size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); + + if (sys_cacheline_size > 0) + cacheline_size = sys_cacheline_size; + else + cacheline_size = 64; /* Fall back to a sane value */ + } else + cacheline_size = user_cacheline_size; +} diff --git a/dwarves_reorganize.c b/dwarves_reorganize.c new file mode 100644 index 0000000..bae5b6e --- /dev/null +++ b/dwarves_reorganize.c @@ -0,0 +1,848 @@ +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2006 Mandriva Conectiva S.A. + Copyright (C) 2006 Arnaldo Carvalho de Melo <acme@mandriva.com> + Copyright (C) 2007 Red Hat Inc. + Copyright (C) 2007 Arnaldo Carvalho de Melo <acme@redhat.com> +*/ + +#include "list.h" +#include "dwarves_reorganize.h" +#include "dwarves.h" + +static void class__recalc_holes(struct class *class) +{ + class->holes_searched = 0; + class__find_holes(class); +} + +void class__subtract_offsets_from(struct class *class, + struct class_member *from, + const uint16_t size) +{ + struct class_member *member; + + class__for_each_member_continue(class, from, member) { + member->byte_offset -= size; + member->bit_offset -= size * 8; + } + + if (class->padding != 0) { + struct class_member *last_member = + type__last_member(&class->type); + const ssize_t new_padding = (class__size(class) - + (last_member->byte_offset + + last_member->byte_size)); + if (new_padding > 0) + class->padding = new_padding; + else + class->padding = 0; + } +} + +void class__add_offsets_from(struct class *class, struct class_member *from, + const uint16_t size) +{ + struct class_member *member; + + class__for_each_member_continue(class, from, member) { + member->byte_offset += size; + member->bit_offset += size * 8; + } +} + +/* + * XXX: Check this more thoroughly. Right now it is used because I was + * to lazy to do class__remove_member properly, adjusting alignments and + * holes as we go removing fields. Ditto for class__add_offsets_from. + */ +void class__fixup_alignment(struct class *class, const struct cu *cu) +{ + struct class_member *pos, *last_member = NULL; + size_t power2; + + type__for_each_data_member(&class->type, pos) { + if (last_member == NULL && pos->byte_offset != 0) { /* paranoid! */ + class__subtract_offsets_from(class, pos, + (pos->byte_offset - + pos->byte_size)); + pos->byte_offset = 0; + pos->bit_offset = 0; + } else if (last_member != NULL && + last_member->hole >= cu->addr_size) { + size_t dec = (last_member->hole / cu->addr_size) * + cu->addr_size; + + last_member->hole -= dec; + if (last_member->hole == 0) + --class->nr_holes; + pos->byte_offset -= dec; + pos->bit_offset -= dec * 8; + class->type.size -= dec; + class__subtract_offsets_from(class, pos, dec); + } else for (power2 = cu->addr_size; power2 >= 2; power2 /= 2) { + const size_t remainder = pos->byte_offset % power2; + + if (pos->byte_size == power2) { + if (remainder == 0) /* perfectly aligned */ + break; + if (last_member->hole >= remainder) { + last_member->hole -= remainder; + if (last_member->hole == 0) + --class->nr_holes; + pos->byte_offset -= remainder; + pos->bit_offset -= remainder * 8; + class__subtract_offsets_from(class, pos, remainder); + } else { + const size_t inc = power2 - remainder; + + if (last_member->hole == 0) + ++class->nr_holes; + last_member->hole += inc; + pos->byte_offset += inc; + pos->bit_offset += inc * 8; + class->type.size += inc; + class__add_offsets_from(class, pos, inc); + } + } + } + + last_member = pos; + } + + if (last_member != NULL) { + struct class_member *m = + type__find_first_biggest_size_base_type_member(&class->type, cu); + size_t unpadded_size = last_member->byte_offset + last_member->byte_size; + size_t m_size = m->byte_size, remainder; + + /* google for struct zone_padding in the linux kernel for an example */ + if (m_size == 0) + return; + + remainder = unpadded_size % m_size; + if (remainder != 0) { + class->padding = m_size - remainder; + class->type.size = unpadded_size + class->padding; + } + } +} + +static struct class_member * + class__find_next_hole_of_size(struct class *class, + struct class_member *from, size_t size) +{ + struct class_member *bitfield_head = NULL; + struct class_member *member; + + class__for_each_member_continue(class, from, member) { + if (member->bitfield_size != 0) { + if (bitfield_head == NULL) + bitfield_head = member; + } else + bitfield_head = NULL; + if (member->hole != 0) { + if (member->byte_size != 0 && member->byte_size <= size) + return bitfield_head ? : member; + } + } + + return NULL; +} + +static struct class_member * + class__find_last_member_of_size(struct class *class, + struct class_member *to, size_t size) +{ + struct class_member *member; + + class__for_each_member_reverse(class, member) { + if (member->tag.tag != DW_TAG_member) + continue; + + if (member == to) + break; + /* + * Check if this is the first member of a bitfield. It either + * has another member before it that is not part of the current + * bitfield or it is the first member of the struct. + */ + if (member->bitfield_size != 0 && member->byte_offset != 0) { + struct class_member *prev = + list_entry(member->tag.node.prev, + struct class_member, + tag.node); + if (prev->bitfield_size != 0) + continue; + + } + + if (member->byte_size != 0 && member->byte_size <= size) + return member; + } + + return NULL; +} + +static bool class__move_member(struct class *class, struct class_member *dest, + struct class_member *from, const struct cu *cu, + int from_padding, const int verbose, FILE *fp) +{ + const size_t from_size = from->byte_size; + const size_t dest_size = dest->byte_size; + +#ifndef BITFIELD_REORG_ALGORITHMS_ENABLED + /* + * For now refuse to move a bitfield, we need to first fixup some BRAIN FARTs + */ + if (from->bitfield_size != 0) + return false; +#endif + const bool from_was_last = from->tag.node.next == class__tags(class); + struct class_member *tail_from = from; + struct class_member *from_prev = list_entry(from->tag.node.prev, + struct class_member, + tag.node); + uint16_t orig_tail_from_hole = tail_from->hole; + const uint16_t orig_from_offset = from->byte_offset; + /* + * Align 'from' after 'dest': + */ + const uint16_t offset = dest->hole % (from_size > cu->addr_size ? + cu->addr_size : from_size); + /* + * Set new 'from' offset, after 'dest->byte_offset', aligned + */ + const uint16_t new_from_offset = dest->byte_offset + dest_size + offset; + + if (verbose) + fputs("/* Moving", fp); + + if (from->bitfield_size != 0) { + struct class_member *pos, *tmp; + LIST_HEAD(from_list); + + if (verbose) + fprintf(fp, " bitfield('%s' ... ", + class_member__name(from, cu)); + class__for_each_member_safe_from(class, from, pos, tmp) { + /* + * Have we reached the end of the bitfield? + */ + if (pos->byte_offset != orig_from_offset) + break; + tail_from = pos; + orig_tail_from_hole = tail_from->hole; + pos->byte_offset = new_from_offset; + pos->bit_offset = new_from_offset * 8 + pos->bitfield_offset; + list_move_tail(&pos->tag.node, &from_list); + } + list_splice(&from_list, &dest->tag.node); + if (verbose) + fprintf(fp, "'%s')", + class_member__name(tail_from, cu)); + } else { + if (verbose) + fprintf(fp, " '%s'", class_member__name(from, cu)); + /* + * Remove 'from' from the list + */ + list_del(&from->tag.node); + /* + * Add 'from' after 'dest': + */ + __list_add(&from->tag.node, &dest->tag.node, + dest->tag.node.next); + from->byte_offset = new_from_offset; + from->bit_offset = new_from_offset * 8 + from->bitfield_offset; + } + + if (verbose) + fprintf(fp, " from after '%s' to after '%s' */\n", + class_member__name(from_prev, cu), + class_member__name(dest, cu)); + + if (from_padding) { + /* + * Check if we're eliminating the need for padding: + */ + if (orig_from_offset % cu->addr_size == 0) { + /* + * Good, no need for padding anymore: + */ + class->type.size -= from_size + class->padding; + } else { + /* + * No, so just add from_size to the padding: + */ + if (verbose) + fprintf(fp, "/* adding %zd bytes from %s to " + "the padding */\n", + from_size, class_member__name(from, cu)); + } + } else if (from_was_last) { + class->type.size -= from_size + class->padding; + } else { + /* + * See if we are adding a new hole that is bigger than + * sizeof(long), this may have problems with explicit alignment + * made by the programmer, perhaps we need A switch that allows + * us to avoid realignment, just using existing holes but + * keeping the existing alignment, anyway the programmer has to + * check the resulting rerganization before using it, and for + * automatic stuff such as the one that will be used for struct + * "views" in tools such as ctracer we are more interested in + * packing the subset as tightly as possible. + */ + if (orig_tail_from_hole + from_size >= cu->addr_size) { + class->type.size -= cu->addr_size; + class__subtract_offsets_from(class, from_prev, + cu->addr_size); + } + } + + class__recalc_holes(class); + + if (verbose > 1) { + class__fprintf(class, cu, fp); + fputc('\n', fp); + } + + return true; +} + +#ifdef BITFIELD_REORG_ALGORITHMS_ENABLED +static struct class_member * + class__find_next_bit_hole_of_size(struct class *class, + struct class_member *from, + size_t size) +{ + struct class_member *member; + + class__for_each_member_continue(class, from, member) { + if (member->tag.tag != DW_TAG_member) + continue; + if (member->bit_hole != 0 && + member->bitfield_size <= size) + return member; + } +#if 0 + /* + * FIXME: Handle the case where the bit padding is on the same bitfield + * that we're looking, i.e. we can't combine a bitfield with itclass, + * perhaps we should tag bitfields with a sequential, clearly marking + * each of the bitfields in advance, so that all the algoriths that + * have to deal with bitfields, moving them around, demoting, etc, can + * be simplified. + */ + /* + * Now look if the last member is a one member bitfield, + * i.e. if we have bit_padding + */ + if (class->bit_padding != 0) + return type__last_member(&class->type); +#endif + return NULL; +} + +static void class__move_bit_member(struct class *class, const struct cu *cu, + struct class_member *dest, + struct class_member *from, + const int verbose, FILE *fp) +{ + struct class_member *from_prev = list_entry(from->tag.node.prev, + struct class_member, + tag.node); + + if (verbose) + fprintf(fp, "/* Moving '%s:%u' from after '%s' to " + "after '%s:%u' */\n", + class_member__name(from, cu), from->bitfield_size, + class_member__name(from_prev, cu), + class_member__name(dest, cu), dest->bitfield_size); + /* + * Remove 'from' from the list + */ + list_del(&from->tag.node); + /* + * Add from after dest: + */ + __list_add(&from->tag.node, + &dest->tag.node, + dest->tag.node.next); + + /* Check if this was the last entry in the bitfield */ + if (from_prev->bitfield_size == 0) { + size_t from_size = from->byte_size; + /* + * Are we shrinking the struct? + */ + if (from_size + from->hole >= cu->addr_size) { + class->type.size -= from_size + from->hole; + class__subtract_offsets_from(class, from_prev, + from_size + from->hole); + } + } + /* + * Tricky, what are the rules for bitfield layouts on this arch? + * Assume its IA32 + */ + from->bitfield_offset = dest->bitfield_offset + dest->bitfield_size; + /* + * Now both have the same offset: + */ + from->byte_offset = dest->byte_offset; + from->bit_offset = dest->byte_offset * 8 + from->bitfield_offset; + + class__recalc_holes(class); + + if (verbose > 1) { + class__fprintf(class, cu, fp); + fputc('\n', fp); + } +} + +static void class__demote_bitfield_members(struct class *class, + struct class_member *from, + struct class_member *to, + const struct base_type *old_type, + const struct base_type *new_type, + type_id_t new_type_id) +{ + struct class_member *member; + + class__for_each_member_from(class, from, member) { + member->byte_size = new_type->bit_size / 8; + member->tag.type = new_type_id; + if (member == to) + break; + } +} + +static struct tag *cu__find_base_type_of_size(const struct cu *cu, + const size_t size, type_id_t *id) +{ + const char *type_name, *type_name_alt = NULL; + + switch (size) { + case sizeof(unsigned char): + type_name = "unsigned char"; break; + case sizeof(unsigned short int): + type_name = "short unsigned int"; + type_name_alt = "unsigned short"; break; + case sizeof(unsigned int): + type_name = "unsigned int"; + type_name_alt = "unsigned"; break; + case sizeof(unsigned long long): + if (cu->addr_size == 8) { + type_name = "long unsigned int"; + type_name_alt = "unsigned long"; + } else { + type_name = "long long unsigned int"; + type_name_alt = "unsigned long long"; + } + break; + default: + return NULL; + } + + struct tag *ret = cu__find_base_type_by_name(cu, type_name, id); + return ret ?: cu__find_base_type_by_name(cu, type_name_alt, id); +} + +static int class__demote_bitfields(struct class *class, const struct cu *cu, + const int verbose, FILE *fp) +{ + struct class_member *member; + struct class_member *bitfield_head = NULL; + const struct tag *old_type_tag, *new_type_tag; + size_t current_bitfield_size = 0, size, bytes_needed; + int some_was_demoted = 0; + + type__for_each_data_member(&class->type, member) { + /* + * Check if we are moving away from a bitfield + */ + if (member->bitfield_size == 0) { + current_bitfield_size = 0; + bitfield_head = NULL; + } else { + if (bitfield_head == NULL) { + bitfield_head = member; + current_bitfield_size = member->bitfield_size; + } else if (bitfield_head->byte_offset != member->byte_offset) { + /* + * We moved from one bitfield to another, for + * now don't handle this case, just move on to + * the next bitfield, we may well move it to + * another place and then the first bitfield will + * be isolated and will be handled in the next + * pass. + */ + bitfield_head = member; + current_bitfield_size = member->bitfield_size; + } else + current_bitfield_size += member->bitfield_size; + } + + /* + * Have we got to the end of a bitfield with holes? + */ + if (member->bit_hole == 0) + continue; + + size = member->byte_size; + bytes_needed = (current_bitfield_size + 7) / 8; + bytes_needed = roundup_pow_of_two(bytes_needed); + if (bytes_needed == size) + continue; + + type_id_t new_type_id; + old_type_tag = cu__type(cu, member->tag.type); + new_type_tag = cu__find_base_type_of_size(cu, bytes_needed, + &new_type_id); + + if (new_type_tag == NULL) { + fprintf(fp, "/* BRAIN FART ALERT! couldn't find a " + "%zd bytes base type */\n\n", bytes_needed); + continue; + } + if (verbose) { + char old_bf[64], new_bf[64]; + fprintf(fp, "/* Demoting bitfield ('%s' ... '%s') " + "from '%s' to '%s' */\n", + class_member__name(bitfield_head, cu), + class_member__name(member, cu), + base_type__name(tag__base_type(old_type_tag), + cu, old_bf, sizeof(old_bf)), + base_type__name(tag__base_type(new_type_tag), + cu, new_bf, sizeof(new_bf))); + } + + class__demote_bitfield_members(class, + bitfield_head, member, + tag__base_type(old_type_tag), + tag__base_type(new_type_tag), + new_type_id); + class__recalc_holes(class); + some_was_demoted = 1; + + if (verbose > 1) { + class__fprintf(class, cu, fp); + fputc('\n', fp); + } + } + /* + * Now look if we have bit padding, i.e. if the the last member + * is a bitfield and its the sole member in this bitfield, i.e. + * if it wasn't already demoted as part of a bitfield of more than + * one member: + */ + member = type__last_member(&class->type); + if (class->bit_padding != 0 && bitfield_head == member) { + size = member->byte_size; + bytes_needed = (member->bitfield_size + 7) / 8; + if (bytes_needed < size) { + old_type_tag = cu__type(cu, member->tag.type); + type_id_t new_type_id; + new_type_tag = + cu__find_base_type_of_size(cu, bytes_needed, + &new_type_id); + + tag__assert_search_result(old_type_tag); + tag__assert_search_result(new_type_tag); + + if (verbose) { + char old_bf[64], new_bf[64]; + fprintf(fp, "/* Demoting bitfield ('%s') " + "from '%s' to '%s' */\n", + class_member__name(member, cu), + base_type__name(tag__base_type(old_type_tag), + cu, old_bf, sizeof(old_bf)), + base_type__name(tag__base_type(new_type_tag), + cu, new_bf, sizeof(new_bf))); + } + class__demote_bitfield_members(class, + member, member, + tag__base_type(old_type_tag), + tag__base_type(new_type_tag), + new_type_id); + class__recalc_holes(class); + some_was_demoted = 1; + + if (verbose > 1) { + class__fprintf(class, cu, fp); + fputc('\n', fp); + } + } + } + + return some_was_demoted; +} + +static void class__reorganize_bitfields(struct class *class, + const struct cu *cu, + const int verbose, FILE *fp) +{ + struct class_member *member, *brother; +restart: + type__for_each_data_member(&class->type, member) { + /* See if we have a hole after this member */ + if (member->bit_hole != 0) { + /* + * OK, try to find a member that has a bit hole after + * it and that has a size that fits the current hole: + */ + brother = + class__find_next_bit_hole_of_size(class, member, + member->bit_hole); + if (brother != NULL) { + class__move_bit_member(class, cu, + member, brother, + verbose, fp); + goto restart; + } + } + } +} + +static void class__fixup_bitfield_types(struct class *class, + struct class_member *from, + struct class_member *to_before, + type_id_t type) +{ + struct class_member *member; + + class__for_each_member_from(class, from, member) { + if (member == to_before) + break; + member->tag.type = type; + } +} + +/* + * Think about this pahole output a bit: + * + * [filo examples]$ pahole swiss_cheese cheese + * / * <11b> /home/acme/git/pahole/examples/swiss_cheese.c:3 * / + * struct cheese { + * <SNIP> + * int bitfield1:1; / * 64 4 * / + * int bitfield2:1; / * 64 4 * / + * + * / * XXX 14 bits hole, try to pack * / + * / * Bitfield WARNING: DWARF size=4, real size=2 * / + * + * short int d; / * 66 2 * / + * <SNIP> + * + * The compiler (gcc 4.1.1 20070105 (Red Hat 4.1.1-51) in the above example), + * Decided to combine what was declared as an int (4 bytes) bitfield but doesn't + * uses even one byte with the next field, that is a short int (2 bytes), + * without demoting the type of the bitfield to short int (2 bytes), so in terms + * of alignment the real size is 2, not 4, to make things easier for the rest of + * the reorganizing routines we just do the demotion ourselves, fixing up the + * sizes. +*/ +static void class__fixup_member_types(struct class *class, const struct cu *cu, + const uint8_t verbose, FILE *fp) +{ + struct class_member *pos, *bitfield_head = NULL; + uint8_t fixup_was_done = 0; + + type__for_each_data_member(&class->type, pos) { + /* + * Is this bitfield member? + */ + if (pos->bitfield_size != 0) { + /* + * The first entry in a bitfield? + */ + if (bitfield_head == NULL) + bitfield_head = pos; + continue; + } + /* + * OK, not a bitfield member, but have we just passed + * by a bitfield? + */ + if (bitfield_head != NULL) { + const uint16_t real_size = (pos->byte_offset - + bitfield_head->byte_offset); + const size_t size = bitfield_head->byte_size; + /* + * Another case: +struct irq_cfg { + struct irq_pin_list * irq_2_pin; / * 0 8 * / + cpumask_var_t domain; / * 8 16 * / + cpumask_var_t old_domain; / * 24 16 * / + u8 vector; / * 40 1 * / + u8 move_in_progress:1; / * 41: 7 1 * / + u8 remapped:1; / * 41: 6 1 * / + + / * XXX 6 bits hole, try to pack * / + / * XXX 6 bytes hole, try to pack * / + + union { + struct irq_2_iommu irq_2_iommu; / * 16 * / + struct irq_2_irte irq_2_irte; / * 4 * / + }; / * 48 16 * / + / * --- cacheline 1 boundary (64 bytes) --- * / + + * So just fix it up if the byte_size of the bitfield is + * greater than what it really uses. + */ + if (real_size < size) { + type_id_t new_type_id; + struct tag *new_type_tag = + cu__find_base_type_of_size(cu, + real_size, + &new_type_id); + if (new_type_tag == NULL) { + fprintf(stderr, "%s: couldn't find" + " a base_type of %d bytes!\n", + __func__, real_size); + continue; + } + class__fixup_bitfield_types(class, + bitfield_head, pos, + new_type_id); + fixup_was_done = 1; + } + } + bitfield_head = NULL; + } + if (fixup_was_done) { + class__recalc_holes(class); + } + if (verbose && fixup_was_done) { + fprintf(fp, "/* bitfield types were fixed */\n"); + if (verbose > 1) { + class__fprintf(class, cu, fp); + fputc('\n', fp); + } + } +} +#endif // BITFIELD_REORG_ALGORITHMS_ENABLED + +void class__reorganize(struct class *class, const struct cu *cu, + const int verbose, FILE *fp) +{ + struct class_member *member, *brother, *last_member; + size_t alignment_size; + + class__find_holes(class); +#ifdef BITFIELD_REORG_ALGORITHMS_ENABLED + class__fixup_member_types(class, cu, verbose, fp); + while (class__demote_bitfields(class, cu, verbose, fp)) + class__reorganize_bitfields(class, cu, verbose, fp); +#endif + /* Now try to combine holes */ +restart: + alignment_size = 0; + /* + * It can be NULL if this class doesn't have any data members, + * just inheritance entries + */ + last_member = type__last_member(&class->type); + if (last_member == NULL) + return; + + type__for_each_data_member(&class->type, member) { + const size_t aligned_size = member->byte_size + member->hole; + if (aligned_size <= cu->addr_size && + aligned_size > alignment_size) + alignment_size = aligned_size; + } + + if (alignment_size != 0) { + size_t modulo; + uint16_t new_padding; + + if (alignment_size > 1) + alignment_size = roundup(alignment_size, 2); + modulo = (last_member->byte_offset + + last_member->byte_size) % alignment_size; + if (modulo != 0) + new_padding = cu->addr_size - modulo; + else + new_padding = 0; + + if (new_padding != class->padding) { + class->padding = new_padding; + class->type.size = (last_member->byte_offset + + last_member->byte_size + new_padding); + } + } + + type__for_each_data_member(&class->type, member) { + /* See if we have a hole after this member */ + if (member->hole != 0) { + /* + * OK, try to find a member that has a hole after it + * and that has a size that fits the current hole: + */ + brother = class__find_next_hole_of_size(class, member, + member->hole); + if (brother != NULL) { + struct class_member *brother_prev = + list_entry(brother->tag.node.prev, + struct class_member, + tag.node); + /* + * If it the next member, avoid moving it closer, + * it could be a explicit alignment rule, like + * ____cacheline_aligned_in_smp in the Linux + * kernel. + */ + if (brother_prev != member) { + if (class__move_member(class, member, brother, cu, 0, verbose, fp)) + goto restart; + } + } + /* + * OK, but is there padding? If so the last member + * has a hole, if we are not at the last member and + * it has a size that is smaller than the current hole + * we can move it after the current member, reducing + * the padding or eliminating it altogether. + */ + if (class->padding > 0 && + member != last_member && + last_member->byte_size != 0 && + last_member->byte_size <= member->hole) { + if (class__move_member(class, member, last_member, cu, 1, verbose, fp)) + goto restart; + } + } + } + + /* Now try to move members at the tail to after holes */ + if (class->nr_holes == 0) + return; + + type__for_each_data_member(&class->type, member) { + /* See if we have a hole after this member */ + if (member->hole != 0) { + brother = class__find_last_member_of_size(class, member, + member->hole); + if (brother != NULL) { + struct class_member *brother_prev = + list_entry(brother->tag.node.prev, + struct class_member, + tag.node); + /* + * If it the next member, avoid moving it closer, + * it could be a explicit alignment rule, like + * ____cacheline_aligned_in_smp in the Linux + * kernel. + */ + if (brother_prev != member) { + if (class__move_member(class, member, brother, cu, 0, verbose, fp)) + goto restart; + } + } + } + } +} diff --git a/dwarves_reorganize.h b/dwarves_reorganize.h new file mode 100644 index 0000000..5fccf6d --- /dev/null +++ b/dwarves_reorganize.h @@ -0,0 +1,30 @@ +#ifndef _DWARVES_REORGANIZE_H_ +#define _DWARVES_REORGANIZE_H_ 1 +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2006 Mandriva Conectiva S.A. + Copyright (C) 2006 Arnaldo Carvalho de Melo <acme@mandriva.com> + Copyright (C) 2007 Arnaldo Carvalho de Melo <acme@ghostprotocols.net> +*/ + + +#include <stdint.h> +#include <stdio.h> + +struct class; +struct cu; +struct class_member; + +void class__subtract_offsets_from(struct class *cls, struct class_member *from, + const uint16_t size); + +void class__add_offsets_from(struct class *cls, struct class_member *from, + const uint16_t size); + +void class__fixup_alignment(struct class *cls, const struct cu *cu); + +void class__reorganize(struct class *cls, const struct cu *cu, + const int verbose, FILE *fp); + +#endif /* _DWARVES_REORGANIZE_H_ */ diff --git a/elf_symtab.c b/elf_symtab.c new file mode 100644 index 0000000..741990e --- /dev/null +++ b/elf_symtab.c @@ -0,0 +1,68 @@ +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2009 Red Hat Inc. + Copyright (C) 2009 Arnaldo Carvalho de Melo <acme@redhat.com> +*/ + +#include <malloc.h> +#include <stdio.h> +#include <string.h> + +#include "dutil.h" +#include "elf_symtab.h" + +#define HASHSYMS__BITS 8 +#define HASHSYMS__SIZE (1UL << HASHSYMS__BITS) + +struct elf_symtab *elf_symtab__new(const char *name, Elf *elf, GElf_Ehdr *ehdr) +{ + if (name == NULL) + name = ".symtab"; + + GElf_Shdr shdr; + Elf_Scn *sec = elf_section_by_name(elf, ehdr, &shdr, name, NULL); + + if (sec == NULL) + return NULL; + + if (gelf_getshdr(sec, &shdr) == NULL) + return NULL; + + struct elf_symtab *symtab = malloc(sizeof(*symtab)); + if (symtab == NULL) + return NULL; + + symtab->name = strdup(name); + if (symtab->name == NULL) + goto out_delete; + + symtab->syms = elf_getdata(sec, NULL); + if (symtab->syms == NULL) + goto out_free_name; + + sec = elf_getscn(elf, shdr.sh_link); + if (sec == NULL) + goto out_free_name; + + symtab->symstrs = elf_getdata(sec, NULL); + if (symtab->symstrs == NULL) + goto out_free_name; + + symtab->nr_syms = shdr.sh_size / shdr.sh_entsize; + + return symtab; +out_free_name: + free(symtab->name); +out_delete: + free(symtab); + return NULL; +} + +void elf_symtab__delete(struct elf_symtab *symtab) +{ + if (symtab == NULL) + return; + free(symtab->name); + free(symtab); +} diff --git a/elf_symtab.h b/elf_symtab.h new file mode 100644 index 0000000..359add6 --- /dev/null +++ b/elf_symtab.h @@ -0,0 +1,92 @@ +#ifndef _ELF_SYMTAB_H_ +#define _ELF_SYMTAB_H_ 1 +/* + SPDX-License-Identifier: GPL-2.0-only + + Copyright (C) 2009 Red Hat Inc. + Copyright (C) 2009 Arnaldo Carvalho de Melo <acme@redhat.com> +*/ + +#include <stdbool.h> +#include <stdint.h> +#include <gelf.h> +#include <elf.h> + +struct elf_symtab { + uint32_t nr_syms; + Elf_Data *syms; + Elf_Data *symstrs; + char *name; +}; + +struct elf_symtab *elf_symtab__new(const char *name, Elf *elf, GElf_Ehdr *ehdr); +void elf_symtab__delete(struct elf_symtab *symtab); + +static inline uint32_t elf_symtab__nr_symbols(const struct elf_symtab *symtab) +{ + return symtab->nr_syms; +} + +static inline const char *elf_sym__name(const GElf_Sym *sym, + const struct elf_symtab *symtab) +{ + return symtab->symstrs->d_buf + sym->st_name; +} + +static inline uint8_t elf_sym__type(const GElf_Sym *sym) +{ + return GELF_ST_TYPE(sym->st_info); +} + +static inline uint16_t elf_sym__section(const GElf_Sym *sym) +{ + return sym->st_shndx; +} + +static inline uint8_t elf_sym__bind(const GElf_Sym *sym) +{ + return GELF_ST_BIND(sym->st_info); +} + +static inline uint8_t elf_sym__visibility(const GElf_Sym *sym) +{ |