diff --git a/.build.yml b/.build.yml index 477f229..9e89958 100644 --- a/.build.yml +++ b/.build.yml @@ -9,11 +9,11 @@ sources: - https://github.com/jpsamaroo/BPFnative.jl tasks: - setup-julia: | - wget https://julialang-s3.julialang.org/bin/linux/x64/1.6/julia-1.6.2-linux-x86_64.tar.gz - tar xf julia-1.6.2-* - sudo ln -s $PWD/julia-1.6.2/bin/julia /usr/local/bin/julia + wget -q https://julialang-s3.julialang.org/bin/linux/x64/1.7/julia-1.7.1-linux-x86_64.tar.gz + tar xf julia-1.7.1-* + sudo ln -s $PWD/julia-1.7.1/bin/julia /usr/local/bin/julia - build: | cd BPFnative.jl sudo julia -e 'using Pkg; Pkg.update()' >/dev/null 2>&1 - sudo julia --project -e 'using Pkg; Pkg.instantiate()' >/dev/null 2>&1 - sudo julia --project -e 'ENV["BPFNATIVE_ROOT_TESTS"] = "1"; using Pkg; Pkg.test("BPFnative")' + sudo julia --project -e 'ENV["JULIA_PKG_PRECOMPILE_AUTO"]="0"; using Pkg; Pkg.instantiate()' >/dev/null 2>&1 + sudo julia --project -e 'ENV["JULIA_PKG_PRECOMPILE_AUTO"]="0"; ENV["BPFNATIVE_ROOT_TESTS"] = "1"; ENV["JULIA_DEBUG"] = "BPFnative"; using Pkg; Pkg.test("BPFnative")' diff --git a/Manifest.toml b/Manifest.toml index cdf6c51..c40b6e2 100644 --- a/Manifest.toml +++ b/Manifest.toml @@ -17,9 +17,9 @@ version = "1.0.8+0" [[CBinding]] deps = ["Clang_jll", "Libdl", "Markdown", "Scratch"] -git-tree-sha1 = "8d64dcb3b02319e703f453a37351a475b7cf0b4e" +git-tree-sha1 = "9b5b4bb2e6debc86998e1d3862ee60dd3b5b5965" uuid = "d43a6710-96b8-4a2d-833c-c424785e5374" -version = "1.0.6" +version = "1.0.8" [[CEnum]] git-tree-sha1 = "215a9aa4a1f23fbd05b92769fdd62559488d70e9" @@ -27,35 +27,15 @@ uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82" version = "0.4.1" [[Clang_jll]] -deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "libLLVM_jll"] -git-tree-sha1 = "a5923c06de3178dd755f4b9411ea8922a7ae6fb8" +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll", "libLLVM_jll"] +git-tree-sha1 = "8cf7e67e264dedc5d321ec87e78525e958aea057" uuid = "0ee61d77-7f21-5576-8119-9fcc46b10100" -version = "11.0.1+3" - -[[Compat]] -deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"] -git-tree-sha1 = "344f143fa0ec67e47917848795ab19c6a455f32c" -uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "3.32.0" - -[[DataStructures]] -deps = ["Compat", "InteractiveUtils", "OrderedCollections"] -git-tree-sha1 = "4437b64df1e0adccc3e5d1adbc3ac741095e4677" -uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" -version = "0.18.9" +version = "12.0.1+3" [[Dates]] deps = ["Printf"] uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" -[[DelimitedFiles]] -deps = ["Mmap"] -uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" - -[[Distributed]] -deps = ["Random", "Serialization", "Sockets"] -uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" - [[Downloads]] deps = ["ArgTools", "LibCURL", "NetworkOptions"] uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" @@ -72,10 +52,10 @@ uuid = "e2ba6199-217a-4e67-a87a-7c52f15ade04" version = "0.1.6" [[GPUCompiler]] -deps = ["DataStructures", "ExprTools", "InteractiveUtils", "LLVM", "Libdl", "Logging", "TimerOutputs", "UUIDs"] -git-tree-sha1 = "0da0f52fc521ff23b8291e7fda54c61907609f12" +deps = ["ExprTools", "InteractiveUtils", "LLVM", "Libdl", "Logging", "TimerOutputs", "UUIDs"] +git-tree-sha1 = "2cac236070c2c4b36de54ae9146b55ee2c34ac7a" uuid = "61eb1bfa-7361-4325-ad38-22787b887f55" -version = "0.12.6" +version = "0.13.10" [[InteractiveUtils]] deps = ["Markdown"] @@ -89,15 +69,15 @@ version = "1.3.0" [[LLVM]] deps = ["CEnum", "LLVMExtra_jll", "Libdl", "Printf", "Unicode"] -git-tree-sha1 = "733abcbdc67337bb6aaf873c6bebbe1e6440a5df" +git-tree-sha1 = "7cc22e69995e2329cc047a879395b2b74647ab5f" uuid = "929cbde3-209d-540e-8aea-75f648917ca0" -version = "4.1.1" +version = "4.7.0" [[LLVMExtra_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "b36c0677a0549c7d1dc8719899a4133abbfacf7d" +git-tree-sha1 = "c5fc4bef251ecd37685bea1c4068a9cfa41e8b9a" uuid = "dad2f222-ce93-54a1-a47d-0025e8a3acab" -version = "0.0.6+0" +version = "0.0.13+0" [[LibCURL]] deps = ["LibCURL_jll", "MozillaCACerts_jll"] @@ -124,10 +104,6 @@ version = "0.3.0+0" [[Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" -[[LinearAlgebra]] -deps = ["Libdl"] -uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" - [[Logging]] uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" @@ -139,19 +115,17 @@ uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" deps = ["Artifacts", "Libdl"] uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -[[Mmap]] -uuid = "a63ad114-7e13-5084-954f-fe012c677804" - [[MozillaCACerts_jll]] uuid = "14a3606d-f60d-562e-9121-12d972cd8159" [[NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" -[[OrderedCollections]] -git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c" -uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" -version = "1.4.1" +[[ObjectFile]] +deps = ["Reexport", "StructIO"] +git-tree-sha1 = "55ce61d43409b1fb0279d1781bf3b0f22c83ab3b" +uuid = "d8793406-e978-5875-9003-1fc021f44a92" +version = "0.3.7" [[Pkg]] deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] @@ -172,9 +146,14 @@ deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[Random]] -deps = ["Serialization"] +deps = ["SHA", "Serialization"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +[[Reexport]] +git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "1.2.2" + [[SHA]] uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" @@ -187,20 +166,14 @@ version = "1.1.0" [[Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" -[[SharedArrays]] -deps = ["Distributed", "Mmap", "Random", "Serialization"] -uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" - [[Sockets]] uuid = "6462fe0b-24de-5631-8697-dd941f90decc" -[[SparseArrays]] -deps = ["LinearAlgebra", "Random"] -uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" - -[[Statistics]] -deps = ["LinearAlgebra", "SparseArrays"] -uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +[[StructIO]] +deps = ["Test"] +git-tree-sha1 = "010dc73c7146869c042b49adcdb6bf528c12e859" +uuid = "53d494c1-5632-5724-8f4c-31dff12d585f" +version = "0.3.0" [[TOML]] deps = ["Dates"] @@ -216,9 +189,9 @@ uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [[TimerOutputs]] deps = ["ExprTools", "Printf"] -git-tree-sha1 = "209a8326c4f955e2442c07b56029e88bb48299c7" +git-tree-sha1 = "7cb456f358e8f9d102a8b25e8dfedf58fa5689bc" uuid = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" -version = "0.5.12" +version = "0.5.13" [[UUIDs]] deps = ["Random", "SHA"] @@ -227,11 +200,17 @@ uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [[Unicode]] uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +[[VMLinuxBindings]] +deps = ["CBinding"] +git-tree-sha1 = "4f6d71c98437f60e2e8b99bf7b00a8864cf4f0ae" +uuid = "3d5d7239-16b4-4651-96ed-9e800c796499" +version = "0.1.2" + [[XZ_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "9f76853ea2ba894054e24640abfb73d73e5a4cb5" +git-tree-sha1 = "a921669cd9a45c23031fd4eb904f5cc3d20de415" uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800" -version = "5.2.5+0" +version = "5.2.5+2" [[Zlib_jll]] deps = ["Libdl"] diff --git a/Project.toml b/Project.toml index a20ed53..33cc2c1 100644 --- a/Project.toml +++ b/Project.toml @@ -10,20 +10,17 @@ InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" LLVM = "929cbde3-209d-540e-8aea-75f648917ca0" Libbpf_jll = "02f9a84d-9555-5f1a-8c3c-42027521038d" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +ObjectFile = "d8793406-e978-5875-9003-1fc021f44a92" Preferences = "21216c6a-2e73-6563-6e65-726566657250" Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" +VMLinuxBindings = "3d5d7239-16b4-4651-96ed-9e800c796499" [compat] CBinding = "1" -GPUCompiler = "0.12" +GPUCompiler = "0.13" LLVM = "4" Libbpf_jll = "0.3" +ObjectFile = "0.3" Preferences = "1" -julia = "1.6" - -[extras] -Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -UProbes = "fdc92b62-57bd-11e9-1326-e584eb179d13" - -[targets] -test = ["Test", "UProbes"] +VMLinuxBindings = "0.1.2" +julia = "1.7" diff --git a/examples/LICENSE-offwaketime b/examples/LICENSE-offwaketime new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/examples/LICENSE-offwaketime @@ -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. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/examples/offwaketime.jl b/examples/offwaketime.jl new file mode 100644 index 0000000..07c6b1f --- /dev/null +++ b/examples/offwaketime.jl @@ -0,0 +1,254 @@ +#= Copyright (c) 2016 Facebook + + 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. + + Ported to Julia by Julian P Samaroo, 2021 +=# + +ccall(:jl_exit_on_sigint, Cvoid, (Cint,), 0) + +if length(ARGS) > 0 + const out_path = ARGS[1] + const root_pids = parse.(Cint, ARGS[2:end]) +else + const out_path = nothing + const root_pids = [getpid()] +end +_all_pids = () +for root_pid in root_pids + global _all_pids = (_all_pids..., parse.(Int, readdir("/proc/$root_pid/task"))...) +end +const all_pids = _all_pids::NTuple + +using BPFnative +import BPFnative: API, RT, Host, VMLinux +using CBinding + +const MINBLOCK_US = 1 +const TASK_COMM_LEN = 32 + +struct Key + waker::NTuple{TASK_COMM_LEN,UInt8} + target::NTuple{TASK_COMM_LEN,UInt8} + wret::UInt32 + tret::UInt32 + wpid::UInt32 + tpid::UInt32 +end + +const counts = RT.RTMap(;name = "counts", + maptype = API.BPF_MAP_TYPE_HASH, + keytype = Key, + valuetype = UInt64, + maxentries = 10000) + +const start = RT.RTMap(;name = "start", + maptype = API.BPF_MAP_TYPE_HASH, + keytype = UInt32, + valuetype = UInt64, + maxentries = 10000) + +struct WokeBy + name::NTuple{TASK_COMM_LEN,UInt8} + ret::UInt32 + pid::UInt32 +end + +const wokeby = RT.RTMap(;name = "wokeby", + maptype = API.BPF_MAP_TYPE_HASH, + keytype = UInt32, + valuetype = WokeBy, + maxentries = 10000) + +const stackmap = RT.RTMap(;name = "stackmap", + maptype = API.BPF_MAP_TYPE_STACK_TRACE, + keytype = UInt32, + valuetype = NTuple{API.PERF_MAX_STACK_DEPTH,UInt64}, + maxentries = 10000) + +# Syscall-specific data +#const + +function real_pid(ctx) + level = unsafe_load(API.@elemptr(task.nsproxy.pid_ns_for_children.level)) + return unsafe_load(API.@elemptr(task.thread_pid.numbers[level].nr)) +end + +# kprobe/try_to_wake_up +function waker(ctx) + task_ptr = API.get_param(ctx, Val(1)) + task_ptr = reinterpret(API.pointertype(API.task_struct), task_ptr) + woke = RT.ZeroInitRef(WokeBy) + + if (pid = RT.safe_load(task_ptr.pid)) === nothing + return 0 + end + #= + if !(pid in all_pids) + return 0 + end + =# + + RT.get_current_comm(RT.SizedBuffer(API.@elemptr(woke.name), TASK_COMM_LEN)) + sid = RT.get_stackid(ctx, stackmap, RT.BPF_F_FAST_STACK_CMP) + if sid > 0 + unsafe_store!(API.@elemptr(woke.ret), unsafe_trunc(UInt32, sid)) + unsafe_store!(API.@elemptr(woke.pid), unsafe_trunc(UInt32, pid)) + end + + wokeby[pid] = woke[] + return 0 +end + +@inline function update_counts(ctx, pid::UInt32, delta::UInt64) + woke = RT.ZeroInitRef(WokeBy) + key = RT.ZeroInitRef(Key) + + RT.get_current_comm(RT.SizedBuffer(API.@elemptr(key.target), TASK_COMM_LEN)) + tsid = RT.get_stackid(ctx, stackmap, RT.BPF_F_FAST_STACK_CMP) + unsafe_store!(API.@elemptr(key.tret), reinterpret(UInt32, tsid)) + unsafe_store!(API.@elemptr(key.wret), 0) + unsafe_store!(API.@elemptr(key.tpid), pid) + unsafe_store!(API.@elemptr(key.wpid), 0) + + woke = wokeby[pid] + if woke !== nothing + unsafe_store!(API.@elemptr(key.wret), woke.ret) + #RT.memcpy!(API.@elemptr(key.waker), woke.name, TASK_COMM_LEN) + unsafe_store!(API.@elemptr(key.waker), woke.name) + unsafe_store!(API.@elemptr(key.wpid), woke.pid) + delete!(wokeby, pid) + end + + GC.@preserve key begin + _key = Base.unsafe_convert(Ptr{Key}, key) + val = counts[_key] + if val == C_NULL + counts[_key] = 0 + #RT.map_update_elem(counts, _key, _val, UInt64(1)) # TODO: BPF_NOEXIST + val = counts[_key] + if val == C_NULL + return 0 + end + end + counts[_key] = unsafe_load(val) + delta + end + return 0 +end + +# taken from /sys/kernel/debug/tracing/events/sched/sched_switch/format +struct sched_switch_args + pad::UInt64 + prev_comm::NTuple{16,UInt8} + prev_pid::Cint + prev_prio::Cint + prev_state::UInt64 + next_comm::NTuple{16,UInt8} + next_pid::Cint + next_prio::Cint +end +# tracepoint/sched/sched_switch +function oncpu(#=struct sched_switch_args *=#ctx::Ptr{sched_switch_args}) + #= record previous thread sleep time =# + pid = unsafe_load(API.@elemptr(ctx.prev_pid)) + + ts = RT.ktime_get_ns() + start[pid] = ts + #= + if pid in all_pids + end + =# + + #= calculate current thread's delta time =# + pid = RT.get_current_pid_tgid()[1] + tsp = start[pid] + if (tsp === nothing) + #= missed start or filtered =# + return 0 + end + + delta = RT.ktime_get_ns() - tsp + delete!(start, pid) + delta = delta รท 1000 + if (delta < MINBLOCK_US) + return 0 + end + + return update_counts(ctx, pid, delta) +end + +@info "KProbe" +kp = KProbe(waker, "try_to_wake_up"; license="GPL") +API.load(kp) + +@info "Tracepoint" +tp = Tracepoint(oncpu, Tuple{Ptr{sched_switch_args}}, "sched", "sched_switch"; license="GPL", merge_with=[kp.obj]) +API.load(tp) + +host_counts = Host.hostmap(tp.obj, counts) +host_wokeby = Host.hostmap(tp.obj, wokeby) +host_stacks = Host.hostmap(tp.obj, stackmap) + +println("Press enter to exit...") +try + readline() +catch err + if !(err isa InterruptException) + rethrow(err) + end +end + +frames = Tuple{Symbol,UInt64,NTuple{(API.PERF_MAX_STACK_DEPTH*2),UInt64}}[] +proc_names = Dict{Symbol,UInt64}() +for key in keys(host_counts) + if key.wret != 0 && key.tret != 0 + waker_str = String([key.waker...]) + target_str = String([key.target...]) + waker_sym = Symbol(replace(waker_str, '\0'=>"")) + target_sym = Symbol(replace(target_str, '\0'=>"")) + try + #waker_addr = get!(()->rand(UInt64), proc_names, waker_sym) + #target_addr = get!(()->rand(UInt64), proc_names, target_sym) + waker_stack = host_stacks[key.wret] + target_stack = host_stacks[key.tret] + stacks = (reverse(waker_stack)..., target_stack...) + push!(frames, (target_sym, host_counts[key], stacks)) + + #= TODO: Log some extra info + if occursin("julia", waker_str) || occursin("julia", target_str) + println("[$waker_str ($(key.wpid)) | $target_str ($(key.tpid)): $(host_counts[key])]") + println("====WAKER STACK====") + print(Host.stack_to_string(waker_stack; reverse=true)) + println("===================") + print(Host.stack_to_string(target_stack)) + println("====TARGET STACK====") + println() + if in(key.wpid, all_pids) || in(key.tpid, all_pids) + exit() + end + end + =# + catch err + @error exception=err + end + end +end + +API.unload(kp) +API.unload(tp) + +@show length(frames) +data, lidict = Host.stack_to_frames(frames) + +if out_path !== nothing + using Serialization + open(out_path, "w") do io + serialize(io, (data, lidict)) + end +else + using PProf, Profile + + pprof(data, lidict) +end diff --git a/examples/task-runtime.jl b/examples/task-runtime.jl new file mode 100644 index 0000000..3a5e034 --- /dev/null +++ b/examples/task-runtime.jl @@ -0,0 +1,152 @@ +using BPFnative; import BPFnative: RT, API, Host + +#= +This example implements a monitor for some of the USDT probes added in +https://github.com/JuliaLang/julia/pull/43453. It monitors task creation, +running, and pausing for the current process. +=# + +const MAXTASKS = 128 +const child_parent_map = RT.RTMap(;name="child_parent_map", maptype=API.BPF_MAP_TYPE_HASH, keytype=UInt64, valuetype=UInt64, maxentries=MAXTASKS) +const run_map = RT.RTMap(;name="run_map", maptype=API.BPF_MAP_TYPE_HASH, keytype=UInt64, valuetype=Int64, maxentries=MAXTASKS) +const ptls_map = RT.RTMap(;name="ptls_map", maptype=API.BPF_MAP_TYPE_HASH, keytype=UInt64, valuetype=UInt64, maxentries=MAXTASKS) +const state_map = RT.RTMap(;name="state_map", maptype=API.BPF_MAP_TYPE_HASH, keytype=UInt64, valuetype=Int64, maxentries=MAXTASKS) +const process_events_map = RT.RTMap(;name="process_events_map", maptype=API.BPF_MAP_TYPE_ARRAY, keytype=Int32, valuetype=UInt64, maxentries=1) +const gc_state_map = RT.RTMap(;name="gc_state_map", maptype=API.BPF_MAP_TYPE_ARRAY, keytype=Int32, valuetype=Int32, maxentries=1) + +new_task = USDT(getpid(), Base.julia_cmd().exec[1], "julia", "rt__new__task"; license="GPL") do ctx + parent = something(API.get_param(ctx, Val(1)), UInt64(0)) + child = something(API.get_param(ctx, Val(2)), UInt64(0)) + ptls = something(API.get_param(ctx, Val(3)), UInt64(0)) + child_parent_map[child] = parent + run_map[child] = 0 + ptls_map[child] = ptls + state_map[child] = 0 + 0 +end +API.load(new_task) +run_task = USDT(getpid(), Base.julia_cmd().exec[1], "julia", "rt__run__task"; license="GPL", merge_with=[new_task]) do ctx + task = something(API.get_param(ctx, Val(1)), UInt64(0)) + ptls = something(API.get_param(ctx, Val(2)), UInt64(0)) + old_count = something(run_map[task], 0) + run_map[task] = old_count + 1 + ptls_map[task] = ptls + state_map[task] = 1 + 0 +end +@assert run_task isa BPFnative.ProbeSet +println(run_task) +API.load(run_task) +pause_task = USDT(getpid(), Base.julia_cmd().exec[1], "julia", "rt__pause__task"; license="GPL", merge_with=[new_task]) do ctx + task = reinterpret(UInt64, API.get_param(ctx, Val(1))) + ptls = reinterpret(UInt64, API.get_param(ctx, Val(2))) + ptls_map[task] = ptls + state_map[task] = 2 + 0 +end +API.load(pause_task) +start_process_task = USDT(getpid(), Base.julia_cmd().exec[1], "julia", "rt__start__process__events"; license="GPL", merge_with=[new_task]) do ctx + task = something(API.get_param(ctx, Val(1)), UInt64(0)) + process_events_map[1] = task + state_map[task] = 3 + 0 +end +API.load(start_process_task) +finish_process_task = USDT(getpid(), Base.julia_cmd().exec[1], "julia", "rt__finish__process__events"; license="GPL", merge_with=[new_task]) do ctx + task = something(API.get_param(ctx, Val(1)), UInt64(0)) + state_map[task] = 1 + 0 +end +API.load(finish_process_task) + +#= TODO +multiq_state_task = new_task +for (probe, multiq_state) in [ + ("rt__multiq__insert__success", 1), + ] +@eval multiq_state_task = USDT(getpid(), Base.julia_cmd().exec[1], "julia", $probe; license="GPL", merge_with=[multiq_state_task]) do ctx + multiq_state_map[1] = $multiq_state + 0 +end +API.load(multiq_state_task) +end +=# + +gc_state_task = new_task +for (probe, gc_state) in [ + ("gc__begin", 1), + ("gc__stop_the_world", 2), + ("gc__mark__begin", 3), + ("gc__mark__end", 1), + ("gc__sweep__begin", 4), + ("gc__sweep__end", 1), + ("gc__end", 0), + ("gc__finalizer", 5)] + # TODO: Finalizer end? +@eval gc_state_task = USDT(getpid(), Base.julia_cmd().exec[1], "julia", $probe; license="GPL", merge_with=[gc_state_task]) do ctx + gc_state_map[1] = $gc_state + 0 +end +API.load(gc_state_task) +end + +host_child_parent_map = Host.hostmap(new_task, child_parent_map) +host_run_map = Host.hostmap(new_task, run_map) +host_ptls_map = Host.hostmap(new_task, ptls_map) +host_state_map = Host.hostmap(new_task, state_map) +host_process_events_map = Host.hostmap(start_process_task, process_events_map) +host_gc_state_map = Host.hostmap(gc_state_task, gc_state_map) + +f() = Libc.systemsleep(0.01) +@sync for i in 1:100 + Threads.@spawn f() +end + +for task in keys(host_run_map) + parent = try + repr(host_child_parent_map[task]) + catch + "???" + end + t = try + repr(host_run_map[task]) + catch + "???" + end + ptls = try + repr(host_ptls_map[task]) + catch + "???" + end + state = try + x = host_state_map[task] + if x == 0 + "created" + elseif x == 1 + "running" + elseif x == 2 + "paused" + elseif x == 3 + "processing" + end + catch + "???" + end + println("Task $task (parent $parent) (PTLS $ptls) (state $state) ran $t times") +end +ptask = host_process_events_map[1] +println("Last task processing events: $ptask") + +# TODO: This technically should not work, since tasks are paused during GC +gc_state = host_gc_state_map[1] +if gc_state == 1 + println("GC: Running") +elseif gc_state == 2 + println("GC: Stopping the world") +elseif gc_state == 3 + println("GC: Marking") +elseif gc_state == 4 + println("GC: Sweeping") +elseif gc_state == 5 + println("GC: Running finalizers") +end diff --git a/src/BPFnative.jl b/src/BPFnative.jl index 966c368..e3bec39 100644 --- a/src/BPFnative.jl +++ b/src/BPFnative.jl @@ -11,12 +11,11 @@ using Preferences const use_vmlinux = parse(Bool, @load_preference("use_vmlinux", "false")) if use_vmlinux const has_vmlinux = try - @debug "Loading vmlinux..." - vmlinux_load_time = @timed include("vmlinux.jl") - @debug "Loaded vmlinux in $(vmlinux_load_time.time) seconds" + using VMLinuxBindings + c"VMLinuxBindings.struct pt_regs" true catch err - @warn "Failed to load/generate Linux Kernel definitions: $err" + @warn "Failed to load Linux Kernel definitions: $err" false end else @@ -41,21 +40,10 @@ import ..BPFnative: has_vmlinux import ..CBinding import ..CBinding: @c_str, Cptr if has_vmlinux - import ..VMLinux -end -@generated function offsetof(::Type{T}, ::Val{_field}) where {T<:CBinding.Cstruct,_field} - offset = -1 - for field in CBinding.fields(T).parameters - if field.parameters[1] == _field - offset = field.parameters[2].parameters[4] - end - end - @assert offset >= 0 "Failed to find offset of $_field in $T" - :($offset) -end -macro offsetof(T, field) - :($offsetof($T, Val($field))) + import ..VMLinuxBindings + const VMLinux = VMLinuxBindings end +include("utils.jl") include("common.jl") include("libbpf.jl") if Sys.islinux() @@ -69,12 +57,16 @@ import ..API using ..LLVM using ..LLVM.Interop import Core: LLVMPtr +import ..CBinding +import ..CBinding: @c_str, Cptr include("runtime/maps_core.jl") include("runtime/bpfcall.jl") include("runtime/maps.jl") include("runtime/buffers.jl") include("runtime/helpers.jl") include("runtime/intrinsics.jl") +include("runtime/constants.jl") +include("runtime/utils.jl") end # Host API @@ -94,6 +86,7 @@ include("reflection.jl") include("probes.jl") # Useful utilities +include("extra.jl") include("xdp.jl") end # module diff --git a/src/common.jl b/src/common.jl index 520a5e6..50a4a73 100644 --- a/src/common.jl +++ b/src/common.jl @@ -264,17 +264,48 @@ if has_vmlinux const pt_regs = c".VMLinux.struct pt_regs" pointertype(::Type{pt_regs}) = Cptr{pt_regs} +const cpu_user_regs = c".VMLinux.struct cpu_user_regs" +pointertype(::Type{cpu_user_regs}) = Cptr{cpu_user_regs} + const xdp_md = c".VMLinux.struct xdp_md" pointertype(::Type{xdp_md}) = Cptr{xdp_md} const sk_buff = c".VMLinux.struct sk_buff" pointertype(::Type{sk_buff}) = Cptr{sk_buff} +const task_struct = c".VMLinux.struct task_struct" +pointertype(::Type{task_struct}) = Cptr{task_struct} + else ## Perf/Ptrace # TODO: pt_regs for other architectures +if Sys.ARCH == :x86_64 +struct pt_regs + r15::Culong + r14::Culong + r13::Culong + r12::Culong + rbp::Culong + rbx::Culong + r11::Culong + r10::Culong + r9::Culong + r8::Culong + rax::Culong + rcx::Culong + rdx::Culong + rsi::Culong + rdi::Culong + orig_rax::Culong + rip::Culong + cs::Culong + eflags::Culong + rsp::Culong + ss::Culong +end +else struct pt_regs ebx::Clong ecx::Clong @@ -294,7 +325,10 @@ struct pt_regs esp::Clong xss::Cint end +end # x86_64 pointertype(::Type{pt_regs}) = Ptr{pt_regs} +const cpu_user_regs = nothing +pointertype(::Type{cpu_user_regs}) = Ptr{Nothing} ## XDP @@ -347,4 +381,23 @@ struct sk_buff end pointertype(::Type{sk_buff}) = Ptr{sk_buff} +const task_struct = Nothing +pointertype(::Type{task_struct}) = Ptr{task_struct} + +end + +@inline get_param(ctx::Cptr, ::Val{idx}) where idx = + unsafe_load(_get_param(ctx, Val(idx))) +@inline get_param(ctx::Ptr, ::Val{idx}) where idx = + _get_param(unsafe_load(ctx), Val(idx)) + +@static if Sys.ARCH == :x86_64 +function _get_param(ctx, ::Val{idx}) where idx + @assert 1 <= idx <= 5 "Invalid parameter index: $idx" + idx == 1 && return ctx.di + idx == 2 && return ctx.si + idx == 3 && return ctx.dx + idx == 4 && return ctx.cx + idx == 5 && return ctx.r8 +end end diff --git a/src/compiler.jl b/src/compiler.jl index 89a7c32..381517d 100644 --- a/src/compiler.jl +++ b/src/compiler.jl @@ -53,6 +53,7 @@ function bpffunction_compile(@nospecialize(job::CompilerJob)) # compile to BPF method_instance, world = GPUCompiler.emit_julia(job) ir, kernel = GPUCompiler.emit_llvm(job, method_instance; libraries=false) + if format == :llvm return collect.(codeunits.(string.((ir, kernel)))) # TODO: Make more efficient elseif format == :asm @@ -65,15 +66,30 @@ function bpffunction_compile(@nospecialize(job::CompilerJob)) end bpffunction_link(@nospecialize(job::CompilerJob), exe) = exe -function GPUCompiler.finish_module!(job::BPFCompilerJob, mod::LLVM.Module) +function GPUCompiler.optimize_module!(job::BPFCompilerJob, mod::LLVM.Module) + # extra optimizations to kill uses of `gpu_signal_exception` + # GPUCompiler does this slightly too late, causing an LLVM abort due to the + # BPF target not supporting lowering of `llvm.trap` + ModulePassManager() do pm + global_optimizer!(pm) + global_dce!(pm) + strip_dead_prototypes!(pm) + run!(pm, mod) + end + for func in LLVM.functions(mod) + # validate no-throw + if LLVM.name(func) == "gpu_signal_exception" && length(collect(LLVM.uses(func))) > 0 + throw(GPUCompiler.KernelError(job, "eBPF does not support exceptions")) + end + end + mod +end +function GPUCompiler.finish_module!(job::BPFCompilerJob, mod::LLVM.Module, entry::LLVM.Function) params = job.params license = params.license prog_section = params.prog_section for func in LLVM.functions(mod) - if LLVM.name(func) == "gpu_signal_exception" - throw(GPUCompiler.KernelError(job, "eBPF does not support exceptions")) - end # Set entry section for loaders like libbpf LLVM.section!(func, prog_section) end @@ -105,6 +121,8 @@ function GPUCompiler.finish_module!(job::BPFCompilerJob, mod::LLVM.Module) add!(pm, FunctionPass("BPFHeapToStack", heap_to_stack!)) run!(pm, mod) end + + entry end "Validates LLVM contexts of all the things." diff --git a/src/extra.jl b/src/extra.jl new file mode 100644 index 0000000..e790d97 --- /dev/null +++ b/src/extra.jl @@ -0,0 +1,6 @@ +### Extra utility methods + +Host.hostmap(obj::API.Object, map::RT.RTMap{Name,MT,K,V,ME,F}) where {Name,MT,K,V,ME,F} = + Host.hostmap(API.findmap(obj, String(Name)); K=K, V=V) +Host.hostmap(probe::AbstractProbe, map::RT.RTMap{Name,MT,K,V,ME,F}) where {Name,MT,K,V,ME,F} = + Host.hostmap(API.findmap(only(objects(probe)), String(Name)); K=K, V=V) diff --git a/src/host/kallsyms.jl b/src/host/kallsyms.jl index 5cf0990..ee70144 100644 --- a/src/host/kallsyms.jl +++ b/src/host/kallsyms.jl @@ -1,32 +1,60 @@ +const KALLSYMS_CACHE = Ref{Vector{String}}() +const KALLSYMS_REGEX = r"([0-9abcdef]*) ([a-zA-Z]) ([0-9a-zA-Z_\-]*)" function find_ksym(addr::UInt64) start, stop = UInt64(0), addr - rgx = r"([0-9abcdef]*) ([a-zA-Z]) ([0-9a-zA-Z_\-]*)" - last_addr = start - last_kind = "?" - last_name = "" - for line in readlines(open("/proc/kallsyms", "r")) - m = match(rgx, line) + if !isassigned(KALLSYMS_CACHE) + KALLSYMS_CACHE[] = readlines("/proc/kallsyms") + end + L = 1 + R = length(KALLSYMS_CACHE[]) + start_addr = start + kind = "?" + name = "" + while L <= R + M = floor(Int, (L + R) / 2) + m = match(KALLSYMS_REGEX, KALLSYMS_CACHE[][M]) @assert m !== nothing start_addr, kind, name = m.captures start_addr = parse(UInt64, "0x"*start_addr) - if start_addr > stop - return last_addr, last_kind, last_name + if L == R + return start_addr, kind, name elseif start_addr == stop - return addr, kind, name + return start_addr, kind, name + elseif start_addr < stop + L = M + 1 + else + R = M - 1 end - last_addr = addr - last_kind = kind - last_name = name end + return start_addr, kind, name end -function stack_to_string(nt::NTuple{N,UInt64}) where N +function stack_to_string(nt::NTuple{N,UInt64}; reverse=false) where N iob = IOBuffer() - for i in 1:N + order = reverse ? (N:-1:1) : (1:N) + for i in order addr = nt[i] if addr == UInt64(0) - break + continue end println(iob, "$(find_ksym(addr)[3])") end String(take!(iob)) end +function stack_to_frames(nts::Vector{Tuple{Symbol,UInt64,NTuple{N,UInt64}}}) where N + data = UInt64[] + lidict = Dict{UInt64, Vector{Base.StackTraces.StackFrame}}() + for (proc,count,nt) in nts + for i in 1:count + for addr in nt + if addr != 0 + name = find_ksym(addr)[3] + frame = Base.StackTraces.StackFrame(Symbol(name), proc, -1, nothing, true, false, addr) + lidict[addr] = [frame] + push!(data, addr) + end + end + push!(data, 0) + end + end + data, lidict +end diff --git a/src/host/maps.jl b/src/host/maps.jl index 31f2a92..702acaa 100644 --- a/src/host/maps.jl +++ b/src/host/maps.jl @@ -1,6 +1,7 @@ abstract type HostMap{K,V} end abstract type AbstractHashMap{K,V} <: HostMap{K,V} end abstract type AbstractArrayMap{K,V} <: HostMap{K,V} end +abstract type AbstractBufferMap{K,V} <: HostMap{K,V} end function hostmap(map::API.Map; K, V) fd = API.fd(map) @@ -21,6 +22,38 @@ struct ArrayMap{K,V} <: AbstractArrayMap{K,V} end maptype_to_jltype(::Val{API.BPF_MAP_TYPE_ARRAY}) = ArrayMap +# perf buffers + +const perf_buffer = Ptr{Cvoid} +struct PerfBuffer{K,V} <: AbstractBufferMap{K,V} + fd::Cint + buf::perf_buffer +end +PerfBuffer{K,V}(fd) where {K,V} = PerfBuffer{K,V}(fd, 8) +maptype_to_jltype(::Val{API.BPF_MAP_TYPE_PERF_EVENT_ARRAY}) = PerfBuffer +Base.@kwdef struct perf_buffer_opts + sample_cb::Ptr{Cvoid} = C_NULL + lost_cb::Ptr{Cvoid} = C_NULL + ctx::Ptr{Cvoid} = C_NULL +end + +function PerfBuffer{K,V}(map_fd::Cint, page_cnt::Integer, opts::perf_buffer_opts) where {K,V} + opts_ref = Ref{perf_buffer_opts}(opts) + buf = GC.@preserve opts_ref begin + opts_ptr = Base.unsafe_convert(Ptr{perf_buffer_opts}, opts_ref) + ccall((:perf_buffer__new, API.libbpf), perf_buffer, (Cint,Csize_t,Ptr{perf_buffer_opts}), map_fd, Csize_t(page_cnt), opts_ptr) + end + @assert Int(buf) > 0 + PerfBuffer{K,V}(map_fd, buf) +end +function PerfBuffer{K,V}(map_fd::Cint, page_cnt::Integer; sample_cb=C_NULL, lost_cb=C_NULL, ctx=C_NULL) where {K,V} + opts = perf_buffer_opts(sample_cb, lost_cb, ctx) + PerfBuffer{K,V}(map_fd, page_cnt, opts) +end +function poll(buf::PerfBuffer, timeout_ms::Integer) + ccall((:perf_buffer__poll, API.libbpf), Cint, (perf_buffer,Cint), buf.buf, Cint(timeout_ms)) +end + struct map_create_attr map_type::UInt32 key_size::UInt32 @@ -174,6 +207,13 @@ end struct HostMapKeySet{K,V,H<:HostMap{K,V}} map::H end +function Base.length(hk::HostMapKeySet) + ctr = 0 + for key in hk + ctr += 1 + end + ctr +end Base.keys(map::H) where {K,V,H<:HostMap{K,V}} = HostMapKeySet{K,V,H}(map) Base.IteratorSize(::Type{<:HostMapKeySet}) = Base.SizeUnknown() Base.eltype(::Type{HostMapKeySet{K,V,H}}) where {K,V,H} = K diff --git a/src/libbpf.jl b/src/libbpf.jl index 35371d0..41f97f8 100644 --- a/src/libbpf.jl +++ b/src/libbpf.jl @@ -16,10 +16,6 @@ const bpf_map = Ptr{Cvoid} struct Map map::bpf_map end -const perf_buffer = Ptr{Cvoid} -struct PerfBuffer - buf::perf_buffer -end @enum libbpf_print_level begin LIBBPF_WARN @@ -29,6 +25,12 @@ end bpfprintfn(fn) = ccall((:libbpf_set_print, libbpf), Ptr{Cvoid}, (Ptr{Cvoid},), fn) +function libbpf_strerror(ret) + buf = Vector{UInt8}(undef, 64) + ccall((:libbpf_strerror, libbpf), Cint, (Cint, Ptr{UInt8}, Csize_t), ret, buf, 64) + String(buf) +end + Base.@kwdef struct bpf_object_open_opts sz::Csize_t = sizeof(bpf_object_open_opts) object_name::Cstring = C_NULL @@ -137,8 +139,8 @@ function resize!(map::Map, sz::Integer) nothing end function reuse_fd(map::Map, fd::Integer) - ret = ccall((:bpf_map_reuse_fd, libbpf), Cint, (bpf_map,Cint), map[], Cint(fd)) - @assert ret == 0 + ret = ccall((:bpf_map__reuse_fd, libbpf), Cint, (bpf_map,Cint), map[], Cint(fd)) + @assert ret == 0 "$(libbpf_strerror(ret))" nothing end @@ -217,27 +219,6 @@ function attach_tracepoint!(prog::Program, category::String, name::String) Link(link) end -# perf buffers - -Base.@kwdef struct perf_buffer_opts - sample_cb::Ptr{Cvoid} = C_NULL - lost_cb::Ptr{Cvoid} = C_NULL - ctx::Ptr{Cvoid} = C_NULL -end - -function PerfBuffer(map_fd::Cint, page_cnt::Integer, opts::perf_buffer_opts=perf_buffer_opts()) - opts_ref = Ref{perf_buffer_opts}(opts) - buf = GC.@preserve opts_ref begin - opts_ptr = Base.unsafe_convert(Ptr{perf_buffer_opts}, opts_ref) - ccall((:perf_buffer__new, libbpf), perf_buffer, (Cint,Csize_t,Ptr{perf_buffer_opts}), map_fd, Csize_t(page_cnt), opts_ptr) - end - @assert Int(buf) > 0 - PerfBuffer(buf) -end -function poll(buf::PerfBuffer, timeout_ms::Integer) - ccall((:perf_buffer__poll, libbpf), Cint, (perf_buffer,Cint), buf[], Cint(timeout_ms)) -end - ## user-facing API for (T,field) in ((Object,:obj), (Program,:prog), (Map,:map), (Link,:link)) diff --git a/src/probes.jl b/src/probes.jl index f6f6b04..fb81c5a 100644 --- a/src/probes.jl +++ b/src/probes.jl @@ -1,19 +1,61 @@ -export KProbe, UProbe, USDT, Tracepoint, PerfEvent, XDP +export KProbe, USDT, Tracepoint, PerfEvent, XDP using Libdl +using ObjectFile abstract type AbstractProbe end +objects(p::AbstractProbe) = [p.obj] + +"Merges maps from `other` into `obj`." +function merge_maps!(obj, other::AbstractProbe) + for other_obj in objects(other) + merge_maps!(obj, other_obj) + end +end +function merge_maps!(obj, other_obj::API.Object) + for other_map in API.maps(other_obj) + for our_map in API.maps(obj) + if API.name(our_map) == API.name(other_map) + API.reuse_fd(our_map, API.fd(other_map)) + end + end + end +end + +struct ProbeSet <: AbstractProbe + probes::Vector{AbstractProbe} +end +ProbeSet() = ProbeSet(AbstractProbe[]) +objects(p::ProbeSet) = vcat(map(objects, p.probes)...) struct KProbe <: AbstractProbe obj::API.Object kfunc::String retprobe::Bool end -function KProbe(f::Function, kfunc; retprobe=false, kwargs...) - obj = API.Object(bpffunction(f, Tuple{API.pointertype(API.pt_regs)}; kwargs...)) +function KProbe(f::Function, ::Type{T}, kfunc; merge_with=(), retprobe=false, kwargs...) where {T<:Tuple} + obj = API.Object(bpffunction(f, T; kwargs...)) foreach(prog->API.set_kprobe!(prog), API.programs(obj)) + for other in merge_with + merge_maps!(obj, other) + end KProbe(obj, kfunc, retprobe) end +KProbe(f::Function, kfunc; kwargs...) = + KProbe(f, Tuple{API.pointertype(API.pt_regs)}, kfunc; kwargs...) + +const NOTE_ARG_TYPE = @NamedTuple{size::Int, + signed::Bool, + reg::Symbol, + offset::Union{Int,Nothing}} +const NOTE_TYPE = @NamedTuple{owner::String, + location::UInt64, + base::UInt64, + semaphore::UInt64, + provider::String, + func::String, + args::Vector{NOTE_ARG_TYPE}} + struct UProbe#={F<:Function,T}=# <: AbstractProbe obj::API.Object pid::UInt32 @@ -26,51 +68,175 @@ struct UProbe#={F<:Function,T}=# <: AbstractProbe retprobe::Bool end #= -function UProbe(f::Function, method, sig; retprobe=false, kwargs...) - obj = API.Object(bpffunction(f, Tuple{API.pointertype(API.pt_regs)}; kwargs...)) +function UProbe(f::Function, ::Type{T}, method, sig; merge_with=(), retprobe=false, kwargs...) where {T<:Tuple} + obj = API.Object(bpffunction(f, T; kwargs...)) #foreach(prog->API.set_uprobe!(prog), API.programs(obj)) foreach(prog->API.set_kprobe!(prog), API.programs(obj)) + for other in merge_with + merge_maps!(obj, other) + end UProbe(obj, method, sig, retprobe) end -=# +UProbe(f::Function, method, sig; kwargs...) = + UProbe(f, Tuple{API.pointertype(API.pt_regs)}, method, sig; kwargs...) function UProbe(f::Function, binpath, addr; pid=0, retprobe=false, kwargs...) obj = API.Object(bpffunction(f, Tuple{API.pointertype(API.pt_regs)}; kwargs...)) #foreach(prog->API.set_uprobe!(prog), API.programs(obj)) foreach(prog->API.set_kprobe!(prog), API.programs(obj)) UProbe(obj, pid, binpath, addr, retprobe) end +=# struct USDT <: AbstractProbe obj::API.Object pid::UInt32 binpath::String + func::String addr::UInt64 + args::Vector{NOTE_ARG_TYPE} retprobe::Bool end -function USDT(f::Function, pid, binpath, addr::UInt64; retprobe=false, kwargs...) - obj = API.Object(bpffunction(f, Tuple{API.pointertype(API.pt_regs)}; kwargs...)) +primitive type USDTContext{P,T1,T2,T3,T4,T5} Sys.WORD_SIZE end +@inline _context_reg(::Val{T}) where T = T +function infer_reg_type(size, signed) + if size == 1 + signed ? Int8 : UInt8 + elseif size == 2 + signed ? Int16 : UInt16 + elseif size == 4 + signed ? Int32 : UInt32 + elseif size == 8 + signed ? Int64 : UInt64 + else + NTuple{size,UInt8} + end +end +@inline function API.get_param(ctx::USDTContext{P,T1,T2,T3,T4,T5}, ::Val{idx}) where {P,T1,T2,T3,T4,T5,idx} + @assert 1<=idx<=5 + reg = idx == 1 ? T1 : + idx == 2 ? T2 : + idx == 3 ? T3 : + idx == 4 ? T4 : T5 + reg = _context_reg(reg) + ptr = reinterpret(P, ctx) + reg, offset, size, signed = reg + T = infer_reg_type(size, signed) + if offset !== nothing + ptr = unsafe_load(getproperty(ptr, reg)) + ptr = reinterpret(Ptr{T}, ptr) + offset + dest = Ref{T}() + GC.@preserve dest begin + dest_ptr = Base.unsafe_convert(Ptr{T}, dest) + if RT.probe_read_user(dest_ptr, sizeof(T), ptr) == 0 + return dest[] + else + return nothing + end + end + else + return unsafe_load(reinterpret(Ptr{T}, getproperty(ptr, reg))) + end +end +function USDT(f::Function, pid, binpath, note::NOTE_TYPE; merge_with=(), retprobe=false, kwargs...) + func = note.func + addr = note.location + args = note.args + Ts = [Val(length(args) >= i ? (args[i].reg, args[i].offset, args[i].size, args[i].signed) : nothing) for i in 1:5] + P = API.pointertype(API.cpu_user_regs) + C = USDTContext{P,Ts...} + obj = API.Object(bpffunction(f, Tuple{C}; kwargs...)) + for other in merge_with + merge_maps!(obj, other) + end #foreach(prog->API.set_uprobe!(prog), API.programs(obj)) foreach(prog->API.set_kprobe!(prog), API.programs(obj)) - USDT(obj, pid, binpath, addr, retprobe) + USDT(obj, pid, binpath, func, addr, args, retprobe) end -function USDT(f::Function, pid, binpath, func::String; retprobe=false, kwargs...) + +"Reads and returns the STAPSDT notes in the binary file `bin`." +function read_notes(bin) + open(bin) do io + elf = readmeta(io) + sec = only(filter(x->section_name(x)==".note.stapsdt", + collect(Sections(elf)))) + sec_size = section_size(sec) + + notes = NOTE_TYPE[] + seek(sec, 0) + pos = 0 + off = position(io) + while pos < sec_size + # Extract fields + type = read(io, UInt32) + size = read(io, UInt32) + _ = read(io, UInt32) + owner = readuntil(io, '\0') + location = read(io, UInt64) + base = read(io, UInt64) + semaphore = read(io, UInt64) + provider = readuntil(io, '\0') + func = readuntil(io, '\0') + _args = readuntil(io, '\0') + + # Parse arguments + _args = split(_args, ' ') + args = NOTE_ARG_TYPE[] + for arg in _args + m = match(r"([\-0-9]*)@([\-_0-9a-z]*)\(?%([a-z]*[0-9]*)\)?", arg) + if m !== nothing + sz, offset, reg = m.captures + sz = parse(Int, sz) + signed = sz < 0 + sz = abs(sz) + reg = Symbol(reg) + if offset == "" + offset = nothing + else + offset = try + parse(Int, offset) + catch + 0 # TODO: Is this right? This is probably PC-relative? + end + end + push!(args, (;size=sz, signed, reg, offset)) + else + @assert isempty(arg) "Failed to parse argument: $arg" + end + end + + push!(notes, (;owner, location, base, semaphore, provider, func, args)) + + # Seek to next note + pos += size + sizeof(owner)+1 + (4*3) + pos = cld(pos, 4) * 4 + seek(io, off+pos) + end + + notes + end +end +function USDT(f::Function, pid, binpath, provider::String, func::String; retprobe=false, multi=true, kwargs...) + # FIXME: Find libs without bpftrace probes = String(read(`bpftrace -p $pid -l`)) - probe_rgx = Regex("^usdt:/proc/$pid/root(.*):$func\$") - note_rgx = r"Location: ([0-9a-fx]*)," + probe_rgx = Regex("^usdt:/proc/$pid/root(.*):$provider:$func\$") for probe in split(probes, '\n') startswith(probe, "usdt:") || continue - if occursin(Regex("$func\$"), probe) + if occursin(Regex("$provider:$func\$"), probe) m = match(probe_rgx, probe) @assert m !== nothing "Unexpected bpftrace probe format" probe_file = m.captures[1] - notes = String(read(`readelf -n $probe_file`)) - for line in split(notes, '\n') - m = match(note_rgx, line) - if m !== nothing - addr = parse(UInt64, m.captures[1]) - return USDT(f, pid, probe_file, addr; retprobe, kwargs...) + notes = filter(x->x.func==func, read_notes(probe_file)) + if multi + probes = ProbeSet() + for note in filter(x->x.func==func, notes) + probe = USDT(f, pid, probe_file, note; retprobe, kwargs...) + push!(probes.probes, probe) end + return probes + else + @assert length(notes) <= 1 "Multiple probe points found for $func" + note = only(notes) + return USDT(f, pid, probe_file, note; retprobe, kwargs...) end - throw(ArgumentError("Failed to find STAPSDT location in $probe_file")) end end throw(ArgumentError("Failed to find $func in $binpath for process $pid")) @@ -80,27 +246,43 @@ struct Tracepoint <: AbstractProbe category::String name::String end -function Tracepoint(f::Function, category, name; kwargs...) - obj = API.Object(bpffunction(f, Tuple{API.pointertype(API.pt_regs)}; kwargs...)) +function Tracepoint(f::Function, ::Type{T}, category, name; merge_with=(), kwargs...) where {T<:Tuple} + obj = API.Object(bpffunction(f, T; kwargs...)) foreach(prog->API.set_tracepoint!(prog), API.programs(obj)) + for other in merge_with + merge_maps!(obj, other) + end Tracepoint(obj, category, name) end +Tracepoint(f::Function, category, name; kwargs...) = + Tracepoint(f, Tuple{API.pointertype(API.pt_regs)}, category, name; kwargs...) +function Base.show(io::IO, p::ProbeSet) + n = length(p.probes) + println(io, "ProbeSet ($n probes):") + for idx in 1:n + print(io, " ") + show(io, p.probes[idx]) + idx < n && println(io) + end +end Base.show(io::IO, p::KProbe) = print(io, "KProbe ($(p.kfunc))") Base.show(io::IO, p::UProbe) = print(io, "UProbe ($(p.addr) @ $(repr(p.binpath)))") Base.show(io::IO, p::USDT) = - print(io, "USDT (path $(p.binpath), pid $(p.pid))") + print(io, "USDT ($(p.func) @ $(p.binpath) (pid $(p.pid)))") Base.show(io::IO, p::Tracepoint) = print(io, "Tracepoint ($(p.category)/$(p.name))") +API.load(p::ProbeSet) = foreach(API.load, p.probes) function API.load(p::KProbe) API.load(p.obj) - foreach(prog->API.attach_kprobe!(prog, p.retprobe, p.kfunc), API.programs(p.obj)) + foreach(prog->API.attach_kprobe!(prog, p.retprobe, p.kfunc), + API.programs(p.obj)) end +#= function API.load(p::UProbe) - #= binpath = "$(Sys.BINDIR)/julia" # FIXME: Detect julia vs julia-debug ms = Base.method_instances(p.func, p.sig) @assert length(ms) == 1 @@ -109,10 +291,10 @@ function API.load(p::UProbe) addr = faddr - (iaddr - 0x1000) # FIXME: This doesn't work API.load(p.obj) foreach(prog->API.attach_uprobe!(prog, p.retprobe, UInt32(0), binpath, addr), API.programs(p.obj)) - =# API.load(p.obj) foreach(prog->API.attach_uprobe!(prog, p.retprobe, p.pid, p.binpath, p.addr), API.programs(p.obj)) end +=# function API.load(p::USDT) API.load(p.obj) foreach(prog->API.attach_uprobe!(prog, p.retprobe, p.pid, p.binpath, p.addr), @@ -127,3 +309,4 @@ end function API.unload(p::AbstractProbe) API.unload(p.obj) end +API.unload(p::ProbeSet) = foreach(API.unload, p.probes) diff --git a/src/runtime/buffers.jl b/src/runtime/buffers.jl index e459736..676e091 100644 --- a/src/runtime/buffers.jl +++ b/src/runtime/buffers.jl @@ -39,6 +39,7 @@ struct SizedBuffer <: AbstractSizedBuffer length::UInt32 end SizedBuffer(buf::AbstractSizedBuffer) = SizedBuffer(pointer(buf), length(buf)) +SizedBuffer(ptr::Ptr, length) = SizedBuffer(reinterpret(BufPtr, ptr), length) Base.pointer(str::SizedBuffer) = str.ptr Base.length(str::SizedBuffer) = str.length @inline create_buffer(N::Int) = SizedBuffer(create_buffer(Val(N)), N) @@ -92,5 +93,6 @@ struct UnsizedBuffer <: AbstractUnsizedBuffer ptr::BufPtr end UnsizedBuffer(buf::AbstractBuffer) = UnsizedBuffer(pointer(buf)) +UnsizedBuffer(ptr::Ptr) = UnsizedBuffer(reinterpret(BufPtr, ptr)) Base.pointer(str::UnsizedBuffer) = str.ptr Base.length(str::UnsizedBuffer) = missing diff --git a/src/runtime/constants.jl b/src/runtime/constants.jl new file mode 100644 index 0000000..c681570 --- /dev/null +++ b/src/runtime/constants.jl @@ -0,0 +1 @@ +const BPF_F_FAST_STACK_CMP = UInt64(1 << 9) diff --git a/src/runtime/helpers.jl b/src/runtime/helpers.jl index 873c7f1..9b8ccec 100644 --- a/src/runtime/helpers.jl +++ b/src/runtime/helpers.jl @@ -1,7 +1,7 @@ # BPF helpers const ptr_sk_buff = API.pointertype(API.sk_buff) -const ptr_task_struct = Ptr{Cvoid} #API.pointertype(API.task_struct) +const ptr_task_struct = API.pointertype(API.task_struct) bpfconvert(x) = x bpfconvert(x::AbstractBuffer) = pointer(x) @@ -55,18 +55,20 @@ end @inline get_current_comm(buf::AbstractSizedBuffer) = bpfcall(API.get_current_comm, Clong, Tuple{BufPtr, UInt32}, pointer(buf), length(buf)) +@inline perf_event_output(ctx::T, map::M, flags, buf::AbstractSizedBuffer) where {T, M<:RTMap} = + bpfcall(API.perf_event_output, Clong, Tuple{T, M, UInt64, BufPtr, UInt64}, ctx, map, unsafe_trunc(UInt64, flags), pointer(buf), length(buf)) + @inline get_stackid(ctx::T, map::M, flags::Integer) where {T,M<:RTMap} = - bpfcall(API.get_stackid, Clong, Tuple{T, M, UInt64}, ctx, map, unsafe_trunc(UInt64,flags)) -@inline function get_current_task() - res = bpfcall(API.get_current_task, UInt64) - if res > 0 - unsafe_load(reinterpret(ptr_task_struct, res)) - else - nothing - end -end + unsafe_trunc(Int32, bpfcall(API.get_stackid, Clong, Tuple{T, M, UInt64}, ctx, map, unsafe_trunc(UInt64,flags))) +@inline get_current_task() = bpfcall(API.get_current_task, ptr_task_struct) @inline get_stack(ctx::T, buf::AbstractSizedBuffer, flags::UInt64) where {T} = bpfcall(API.get_stack, Clong, Tuple{T, BufPtr, UInt32, UInt64}, ctx, pointer(buf), length(buf), flags) + +@inline probe_read_user(dest::T, size::Integer, unsafe_ptr::T) where {T<:Ptr} = + bpfcall(API.probe_read_user, Clong, Tuple{T, UInt32, T}, dest, unsafe_trunc(UInt32, size), unsafe_ptr) +@inline probe_read_kernel(dest::T, size::Integer, unsafe_ptr::T) where {T<:Ptr} = + bpfcall(API.probe_read_kernel, Clong, Tuple{T, UInt32, T}, dest, unsafe_trunc(UInt32, size), unsafe_ptr) + @inline get_task_stack(ctx::ptr_task_struct, buf::AbstractSizedBuffer, flags::UInt64) where {T} = bpfcall(API.get_task_stack, Clong, Tuple{ptr_task_struct, BufPtr, UInt32, UInt64}, ctx, pointer(buf), length(buf), flags) diff --git a/src/runtime/maps.jl b/src/runtime/maps.jl index cc4791b..87ecb23 100644 --- a/src/runtime/maps.jl +++ b/src/runtime/maps.jl @@ -2,7 +2,7 @@ function map_lookup_elem(map::RTMap{Name,MT,K,V,ME,F}, key::K) where {Name,MT,K, keyref = ZeroInitRef(K, key) GC.@preserve keyref begin keyref_ptr = Base.unsafe_convert(Ptr{K}, keyref) - _map_lookup_elem(map, keyref_ptr) + map_lookup_elem(map, keyref_ptr) end end @inline function map_update_elem(map::RTMap{Name,MT,K,V,ME,F}, key::K, value::V, flags::UInt64) where {Name,MT,K,V,ME,F} @@ -11,19 +11,19 @@ end GC.@preserve keyref valref begin keyref_ptr = Base.unsafe_convert(Ptr{K}, keyref) valref_ptr = Base.unsafe_convert(Ptr{V}, valref) - _map_update_elem(map, keyref_ptr, valref_ptr, flags) + map_update_elem(map, keyref_ptr, valref_ptr, flags) end end function map_delete_elem(map::RTMap{Name,MT,K,V,ME,F}, key::K) where {Name,MT,K,V,ME,F} keyref = ZeroInitRef(K, key) GC.@preserve keyref begin keyref_ptr = Base.unsafe_convert(Ptr{K}, keyref) - _map_delete_elem(map, Base.unsafe_convert(Ptr{K}, keyref)) + map_delete_elem(map, Base.unsafe_convert(Ptr{K}, keyref)) end end # TODO: Use bpfcall -@generated function _map_lookup_elem(map::RTMap{Name,MT,K,V,ME,F}, key::Ptr{K}) where {Name,MT,K,V,ME,F} +@generated function map_lookup_elem(map::RTMap{Name,MT,K,V,ME,F}, key::Ptr{K}) where {Name,MT,K,V,ME,F} Context() do ctx T_keyp = LLVM.PointerType(convert(LLVMType, K; ctx)) T_valp = LLVM.PointerType(convert(LLVMType, V; ctx)) @@ -49,7 +49,7 @@ end call_function(llvm_f, Ptr{V}, Tuple{Ptr{K}}, :key) end end -@generated function _map_update_elem(map::RTMap{Name,MT,K,V,ME,F}, key::Ptr{K}, val::Ptr{V}, flags::UInt64) where {Name,MT,K,V,ME,F} +@generated function map_update_elem(map::RTMap{Name,MT,K,V,ME,F}, key::Ptr{K}, val::Ptr{V}, flags::UInt64) where {Name,MT,K,V,ME,F} Context() do ctx T_cint = convert(LLVMType, Cint; ctx) T_keyp = LLVM.PointerType(convert(LLVMType, K; ctx)) @@ -77,7 +77,7 @@ end call_function(llvm_f, Cint, Tuple{Ptr{K},Ptr{V},UInt64}, :key, :val, :flags) end end -@generated function _map_delete_elem(map::RTMap{Name,MT,K,V,ME,F}, key::Ptr{K}) where {Name,MT,K,V,ME,F} +@generated function map_delete_elem(map::RTMap{Name,MT,K,V,ME,F}, key::Ptr{K}) where {Name,MT,K,V,ME,F} Context() do ctx T_cint = convert(LLVMType, Cint; ctx) T_keyp = LLVM.PointerType(convert(LLVMType, K; ctx)) @@ -127,6 +127,8 @@ end @inline Base.getindex(map::RTMap{Name,MT,K,V,ME,F}, idx) where {Name,MT,K,V,ME,F} = getindex(map, bpfconvert(K, idx)) +@inline Base.getindex(map::RTMap{Name,MT,K,V,ME,F}, idx_ptr::Ptr{K}) where {Name,MT,K,V,ME,F} = + map_lookup_elem(map, idx_ptr) Base.getindex(map::RTMap, ::Nothing) = nothing @inline function Base.getindex(map::AbstractHashMap{Name,MT,K,V,ME,F}, idx::K) where {Name,MT,K,V,ME,F} ptr = map_lookup_elem(map, idx) @@ -151,6 +153,22 @@ end @inline Base.setindex!(map::RTMap{Name,MT,K,V,ME,F}, value, idx) where {Name,MT,K,V,ME,F} = setindex!(map, bpfconvert(V, value), bpfconvert(K, idx)) +@inline function Base.setindex!(map::RTMap{Name,MT,K,V,ME,F}, value, idx_ptr::Ptr{K}) where {Name,MT,K,V,ME,F} + value_ref = ZeroInitRef(V, bpfconvert(V, value)) + GC.@preserve value_ref begin + value_ptr = Base.unsafe_convert(Ptr{V}, value_ref) + map_update_elem(map, idx_ptr, value_ptr, UInt64(0)) + end +end +@inline function Base.setindex!(map::RTMap{Name,MT,K,V,ME,F}, value_ptr::Ptr{V}, idx) where {Name,MT,K,V,ME,F} + idx_ref = ZeroInitRef(K, bpfconvert(K, idx)) + GC.@preserve idx_ref begin + idx_ptr = Base.unsafe_convert(Ptr{K}, idx_ref) + map_update_elem(map, idx_ptr, value_ptr, UInt64(0)) + end +end +@inline Base.setindex!(map::RTMap{Name,MT,K,V,ME,F}, value_ptr::Ptr{V}, idx_ptr::Ptr{K}) where {Name,MT,K,V,ME,F} = + map_update_elem(map, idx_ptr, value_ptr, UInt64(0)) @inline Base.setindex!(map::RTMap, ::Nothing, idx) = nothing @inline Base.setindex!(map::RTMap, value, ::Nothing) = nothing @inline Base.setindex!(map::RTMap, ::Nothing, ::Nothing) = nothing diff --git a/src/runtime/maps_core.jl b/src/runtime/maps_core.jl index b6f468c..11efa9c 100644 --- a/src/runtime/maps_core.jl +++ b/src/runtime/maps_core.jl @@ -1,12 +1,15 @@ abstract type RTMap{Name,MT,K,V,ME,F} end abstract type AbstractHashMap{Name,MT,K,V,ME,F} <: RTMap{Name,MT,K,V,ME,F} end abstract type AbstractArrayMap{Name,MT,K,V,ME,F} <: RTMap{Name,MT,K,V,ME,F} end +abstract type AbstractBufferMap{Name,MT,K,V,ME,F} <: RTMap{Name,MT,K,V,ME,F} end struct HashMap{Name,MT,K,V,ME,F} <: AbstractHashMap{Name,MT,K,V,ME,F} end maptype_to_jltype(::Val{API.BPF_MAP_TYPE_HASH}) = HashMap maptype_to_jltype(::Val{API.BPF_MAP_TYPE_STACK_TRACE}) = HashMap struct ArrayMap{Name,MT,K,V,ME,F} <: AbstractArrayMap{Name,MT,K,V,ME,F} end maptype_to_jltype(::Val{API.BPF_MAP_TYPE_ARRAY}) = ArrayMap +struct PerfArrayMap{Name,MT,K,V,ME,F} <: AbstractBufferMap{Name,MT,K,V,ME,F} end +maptype_to_jltype(::Val{API.BPF_MAP_TYPE_PERF_EVENT_ARRAY}) = PerfArrayMap function RTMap(; name, maptype, keytype, valuetype, maxentries=1, flags=0) jltype = maptype_to_jltype(Val(maptype)) @@ -61,10 +64,55 @@ end end @inline memset!(dest_ptr::LLVMPtr{T,DestAS}, value::UInt8, len::Integer) where {T,DestAS} = memset!(reinterpret(LLVMPtr{UInt8,DestAS}, dest_ptr), value, UInt64(len)) -@inline function ZeroInitRef(T, val) +@inline memset!(dest_ptr::Union{<:Ptr,<:LLVMPtr}, src_ptr::Union{<:Ptr,<:LLVMPtr}, len::Integer) = + memset!(reinterpret(LLVMPtr{UInt8,0}, dest_ptr), reinterpret(LLVMPtr{UInt8,0}, src_ptr), UInt64(len)) + +@inline function _memcpy!(builder, ctx, mod, dest, src, len, volatile) + T_nothing = LLVM.VoidType(ctx) + T_dest = llvmtype(dest) + T_src = llvmtype(src) + T_int8 = convert(LLVMType, UInt8; ctx) + T_int64 = convert(LLVMType, UInt64; ctx) + T_int1 = LLVM.Int1Type(ctx) + + T_intr = LLVM.FunctionType(T_nothing, [T_dest, T_src, T_int64, T_int1]) + intr = LLVM.Function(mod, "llvm.memcpy.p$(Int(addrspace(T_dest)))p$(Int(addrspace(T_src))).i64", T_intr) + call!(builder, intr, [dest, src, len, volatile]) +end +@inline @generated function memcpy!(dest_ptr::LLVMPtr{UInt8,DestAS}, src_ptr::LLVMPtr{UInt8,SrcAS}, len::LT) where {DestAS,SrcAS,LT<:Union{Int64,UInt64}} + Context() do ctx + T_nothing = LLVM.VoidType(ctx) + T_pint8_dest = convert(LLVMType, dest_ptr; ctx) + T_pint8_src = convert(LLVMType, src_ptr; ctx) + T_int8 = convert(LLVMType, UInt8; ctx) + T_int64 = convert(LLVMType, UInt64; ctx) + T_int1 = LLVM.Int1Type(ctx) + + llvm_f, _ = create_function(T_nothing, [T_pint8_dest, T_pint8_src, T_int64]) + mod = LLVM.parent(llvm_f) + Builder(ctx) do builder + entry = BasicBlock(llvm_f, "entry"; ctx) + position!(builder, entry) + + _memcpy!(builder, ctx, mod, parameters(llvm_f)[1], parameters(llvm_f)[2], parameters(llvm_f)[3], ConstantInt(T_int1, 0)) + ret!(builder) + end + call_function(llvm_f, Nothing, Tuple{LLVMPtr{UInt8,DestAS},LLVMPtr{UInt8,SrcAS},LT}, :dest_ptr, :src_ptr, :len) + end +end +@inline memcpy!(dest_ptr::LLVMPtr{T,DestAS}, src_ptr::LLVMPtr{T,SrcAS}, len::Integer) where {T,DestAS,SrcAS} = + memcpy!(reinterpret(LLVMPtr{UInt8,DestAS}, dest_ptr), reinterpret(LLVMPtr{UInt8,SrcAS}, src_ptr), UInt64(len)) +@inline memcpy!(dest_ptr::Union{<:Ptr,<:LLVMPtr}, src_ptr::Union{<:Ptr,<:LLVMPtr}, len::Integer) = + memcpy!(reinterpret(LLVMPtr{UInt8,0}, dest_ptr), reinterpret(LLVMPtr{UInt8,0}, src_ptr), UInt64(len)) + +@inline function ZeroInitRef(T) ref = Ref{T}() ref_llptr = reinterpret(LLVMPtr{T,0}, Base.unsafe_convert(Ptr{T}, ref)) memset!(ref_llptr, UInt8(0), sizeof(T)) + ref +end +@inline function ZeroInitRef(T, val) + ref = ZeroInitRef(T) ref[] = val ref end diff --git a/src/runtime/utils.jl b/src/runtime/utils.jl new file mode 100644 index 0000000..a694db2 --- /dev/null +++ b/src/runtime/utils.jl @@ -0,0 +1,70 @@ +@inline function safe_load(ptr::Ref{T}) where T + dest = Ref{T}() + dest_ptr = Base.unsafe_convert(Ptr{T}, dest) + src_ptr = Base.unsafe_convert(Ptr{T}, ptr) + if probe_read_kernel(dest_ptr, sizeof(T), src_ptr) == 0 + return dest[] + else + return nothing + end +end + +@inline elemptr(ptr::Ptr{T}, ::Val{field}) where {T,field} = + reinterpret(Ptr{API._fieldtype(T, Val(field))}, ptr + API.offsetof(T, Val(field))) +@inline arrptr(ptr::Ptr{T}, idx) where T = + ptr + (sizeof(T)*(idx-1)) +@inline function elemptr(ptr::Cptr{T}, ::Val{field}) where {T,field} + S = API._fieldtype(typeof(ptr), Val(field)) + if S <: Cptr + # unsafe, need to use helper + ref = ZeroInitRef(S) + ref_ptr = Base.unsafe_convert(Ptr{S}, ref) + if probe_read_kernel(ref_ptr, sizeof(S), reinterpret(Ptr{S}, ptr)) != 0 + return nothing + end + return ref[] + else + return getproperty(ptr, field) + end +end +@inline function elemptr(ptr::Cptr{T}) where T + # unsafe, need to use helper + ref = ZeroInitRef(T) + ref_ptr = Base.unsafe_convert(Ptr{T}, ref) + if probe_read_kernel(ref_ptr, sizeof(T), reinterpret(Ptr{T}, ptr)) != 0 + return nothing + end + return ref[] +end +@inline arrptr(ptr::Cptr{T}, idx) where T = + getindex(ptr, idx) +@inline elemptr(::Nothing, field) = nothing +@inline arrptr(::Nothing, field) = nothing +@inline _toptr(ptr::Ptr) = ptr +@inline _toptr(ref::Base.RefValue{T}) where T = Base.unsafe_convert(Ptr{T}, ref) +@inline _toptr(ptr::Cptr) = ptr + +"Returns a pointer to the final element referenced in `ex`, where the +parent object of `ex` is a `Ptr` or `Ref`." +macro elemptr(ex) + @assert Meta.isexpr(ex, :(.)) || Meta.isexpr(ex, :ref) "@elemptr expects a struct field or array access" + rootex = Expr(:block, Expr(:block), nothing) + refex = rootex + while true + if Meta.isexpr(ex, :(.)) + refex.args[2] = :($elemptr(nothing, $(Val(ex.args[2].value)))) + elseif Meta.isexpr(ex, :ref) + if length(ex.args) == 2 + refex.args[2] = :($arrptr(nothing, $(esc(ex.args[2])))) + else + refex.args[2] = :($elemptr(nothing)) + end + else + break + end + refex = refex.args[2] + ex = ex.args[1] + end + refex.args[2] = :($_toptr($(esc(ex)))) + rootex +end diff --git a/src/utils.jl b/src/utils.jl new file mode 100644 index 0000000..5f10445 --- /dev/null +++ b/src/utils.jl @@ -0,0 +1,34 @@ +@generated function offsetof(::Type{T}, ::Val{_field}) where {T<:CBinding.Cstruct,_field} + offset = -1 + for field in CBinding.fields(T).parameters + if field.parameters[1] == _field + offset = field.parameters[2].parameters[4] + break + end + end + @assert offset >= 0 "Failed to find offset of $_field in $T" + :($offset) +end +@generated function offsetof(::Type{T}, ::Val{_field}) where {T,_field} + @assert isstructtype(T) "offsetof is only valid for structs" + offset = -1 + for (idx,field) in enumerate(fieldnames(T)) + if field == _field + offset = fieldoffset(T, idx) + break + end + end + @assert offset >= 0 "Failed to find offset of $_field in $T" + :($offset) +end +"Returns the integer offset of `field` in `T`." +macro offsetof(T, field) + :($offsetof($T, Val($field))) +end + +@generated _fieldtype(::Type{T}, ::Val{field}) where {T,field} = + :($(fieldtype(T, field))) +@generated _fieldtype(::Type{T}, ::Val{field}) where {T<:CBinding.Cstruct,field} = + :($(CBinding.field(T, field).parameters[2])) +@inline _fieldtype(::Type{Cptr{T}}, ::Val{field}) where {T,field} = + _fieldtype(T, Val(field)) diff --git a/src/vmlinux.jl b/src/vmlinux.jl deleted file mode 100644 index abdaa5b..0000000 --- a/src/vmlinux.jl +++ /dev/null @@ -1,16 +0,0 @@ -module VMLinux - using CBinding - - vmlinux_iob = IOBuffer() - run(pipeline(`bpftool btf dump file /sys/kernel/btf/vmlinux format c`; stdout=vmlinux_iob)) - vmlinux_str = String(take!(vmlinux_iob)) - - vmlinux_path = joinpath(@__DIR__, "vmlinux.h") - open(vmlinux_path, "w") do io - write(io, vmlinux_str) - flush(io) - end - - c`-std=c99 -fparse-all-comments -I$(@__DIR__)` - c"#include \"vmlinux.h\"" -end diff --git a/test/Manifest.toml b/test/Manifest.toml new file mode 100644 index 0000000..0753b02 --- /dev/null +++ b/test/Manifest.toml @@ -0,0 +1,166 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.8.0-DEV.760" +manifest_format = "2.0" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.1" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[deps.CEnum]] +git-tree-sha1 = "215a9aa4a1f23fbd05b92769fdd62559488d70e9" +uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82" +version = "0.4.1" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.Downloads]] +deps = ["ArgTools", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.5.1" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[deps.JLLWrappers]] +deps = ["Preferences"] +git-tree-sha1 = "642a199af8b68253517b80bd3bfd17eb4e84df6e" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.3.0" + +[[deps.LLVM]] +deps = ["CEnum", "LLVMExtra_jll", "Libdl", "Printf", "Unicode"] +git-tree-sha1 = "7cc22e69995e2329cc047a879395b2b74647ab5f" +uuid = "929cbde3-209d-540e-8aea-75f648917ca0" +version = "4.7.0" + +[[deps.LLVMExtra_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "c5fc4bef251ecd37685bea1c4068a9cfa41e8b9a" +uuid = "dad2f222-ce93-54a1-a47d-0025e8a3acab" +version = "0.0.13+0" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.3" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "7.73.0+4" + +[[deps.LibGit2]] +deps = ["Base64", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.9.1+2" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.24.0+2" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2020.7.22" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.8.0" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "00cfd92944ca9c760982747e9a1d0d5d86ab1e5a" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.2.2" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.Random]] +deps = ["SHA", "Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.0" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.UProbes]] +deps = ["LLVM", "Libdl"] +git-tree-sha1 = "9636ba99eedd0966d77b7be70dbb1133417cc517" +uuid = "fdc92b62-57bd-11e9-1326-e584eb179d13" +version = "0.1.2" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.12+1" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.41.0+1" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "16.2.1+1" diff --git a/test/Project.toml b/test/Project.toml new file mode 100644 index 0000000..798bc21 --- /dev/null +++ b/test/Project.toml @@ -0,0 +1,6 @@ +[deps] +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +Sockets = "6462fe0b-24de-5631-8697-dd941f90decc" +Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +UProbes = "fdc92b62-57bd-11e9-1326-e584eb179d13" diff --git a/test/runtests.jl b/test/runtests.jl index cfd311a..8387704 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -203,8 +203,45 @@ end end run_root_tests = parse(Bool, get(ENV, "BPFNATIVE_ROOT_TESTS", "0")) if run_root_tests + # FIXME: Test with and without VMLinuxBindings + #@assert BPFnative.has_vmlinux + test_file, test_io = mktemp(;cleanup=true) @info "Running root-only tests" + + # Some useful toplevel defs + const myarrmap = RT.RTMap(;name="myarrmap", + maptype=API.BPF_MAP_TYPE_ARRAY, + keytype=UInt32, + valuetype=UInt32, + maxentries=1) + const myhashmap = RT.RTMap(;name="myhashmap", + maptype=API.BPF_MAP_TYPE_HASH, + keytype=UInt32, + valuetype=UInt32, + maxentries=1) + const mystrmap = RT.RTMap(;name="mystrmap", + maptype=API.BPF_MAP_TYPE_HASH, + keytype=UInt32, + valuetype=NTuple{4,UInt8}, + maxentries=1) + struct PerfBlob + pid::UInt64 + cookie::UInt64 + end + const myperfmap = RT.RTMap(;name="myperfmap", + maptype=API.BPF_MAP_TYPE_PERF_EVENT_ARRAY, + keytype=Cint, + valuetype=UInt32, + maxentries=2) + function perf_sample_cb(ctx::Ptr{Bool}, cpu::Cint, data::Ptr{PerfBlob}, size::UInt32) + data = unsafe_load(data) + @test size >= sizeof(PerfBlob) + @test data.cookie == 0x12345678 + unsafe_store!(ctx, true) + nothing + end + perf_sample_cb_ptr = @eval @cfunction(perf_sample_cb, Nothing, (Ptr{Bool}, Cint, Ptr{PerfBlob}, UInt32)) @testset "probes" begin @testset "kprobe" begin kp = KProbe("ksys_write") do regs @@ -242,33 +279,37 @@ if run_root_tests API.load(p) API.unload(p) end - @testset "usdt" begin - function f(arg) - # FIXME: if @query(:julia, :test, typeof(arg)) - @probe(:julia, :test, arg) - #end - arg - end - f(1) - julia_path = Base.julia_cmd().exec[1] - usdt = USDT(getpid(), julia_path, "julia:test") do ctx + if BPFnative.has_vmlinux + @testset "usdt" begin + function f(arg) + # FIXME: if @query(:julia, :test, typeof(arg)) + @probe(:julia, :test, arg) + #end + arg + end + f(1) + julia_path = Base.julia_cmd().exec[1] usdtmap = RT.RTMap(;name="usdtmap", maptype=API.BPF_MAP_TYPE_HASH, keytype=Int32, valuetype=Int32, maxentries=1) - usdtmap[1] = 1 # TODO: Get 1st (Cint) argument - 0 - end - API.load(usdt) do - f(1) - host_usdtmap = Host.hostmap(API.findmap(usdt.obj, "usdtmap"); K=Int32, V=Int32) - @test host_usdtmap[1] == 1 - host_usdtmap[1] = 2 - @test host_usdtmap[1] == 2 - f(1) - @test host_usdtmap[1] == 1 + usdt = USDT(getpid(), julia_path, "julia", "test") do ctx + usdtmap[1] = 1 # TODO: Get 1st (Cint) argument + 0 + end + API.load(usdt) do + f(1) + host_usdtmap = Host.hostmap(usdt, usdtmap) + @test host_usdtmap[1] == 1 + host_usdtmap[1] = 2 + @test host_usdtmap[1] == 2 + f(1) + @test host_usdtmap[1] == 1 + end end + else + @test_skip "USDT" end # TODO: perf_event # TODO: xdp @@ -298,12 +339,11 @@ if run_root_tests end @testset "map interfacing" begin function kp_func(regs) - mymap = RT.RTMap(;name="mymap", maptype=API.BPF_MAP_TYPE_ARRAY, keytype=UInt32, valuetype=UInt32) - elem = mymap[1] + elem = myarrmap[1] if elem !== nothing - mymap[1] = 42 + myarrmap[1] = 42 else - mymap[1] = 1 + myarrmap[1] = 1 end return 0 end @@ -313,6 +353,49 @@ if run_root_tests hmap = Host.hostmap(map; K=UInt32, V=UInt32) write(test_io, "1"); flush(test_io) @test hmap[1] == 42 + + # Convenience ctor + hmap2 = Host.hostmap(kp.obj, myarrmap) + @test hmap.fd == hmap2.fd + end + end + @testset "map merging" begin + kp1 = KProbe("ksys_write") do regs + myhashmap[1] = 42 + return 0 + end + API.load(kp1) + kp2 = KProbe("ksys_write"; merge_with=(kp1.obj,)) do regs + if myhashmap[1] === nothing + myhashmap[1] = 43 + end + return 0 + end + API.load(kp2) + host_mymap1 = Host.hostmap(kp1.obj, myhashmap) + host_mymap2 = Host.hostmap(kp2.obj, myhashmap) + write(test_io, "1"); flush(test_io) + @test host_mymap1[1] == host_mymap2[1] + API.unload.((kp1, kp2)) + end + @testset "memory intrinsics" begin + kp = KProbe("ksys_write") do regs + buf = RT.@create_string("hi!") + mystrmap[1] = ntuple(x->UInt8(0), 4) + key = Ref{UInt32}(1) + key_ptr = Base.unsafe_convert(Ptr{UInt32}, key) + GC.@preserve key begin + value_ptr = mystrmap[key_ptr] + if reinterpret(UInt64, value_ptr) != 0 + RT.memcpy!(value_ptr, pointer(buf), 4) + end + end + return 0 + end + API.load(kp) do + host_mymap = Host.hostmap(kp.obj, mystrmap) + write(test_io, "1"); flush(test_io) + @test String(reinterpret(UInt8, collect(host_mymap[1]))) == "hi!\0" end end @testset "helpers" begin @@ -481,6 +564,30 @@ if run_root_tests end end # TODO: get_current_comm + @testset "perf_event_output" begin + # From Linux's samples/bpf/trace_output_* + kp = KProbe("ksys_write"; license="GPL") do ctx + buf = RT.create_buffer(sizeof(PerfBlob)) + GC.@preserve buf begin + buf_ptr = reinterpret(Ptr{PerfBlob}, pointer(buf)) + unsafe_store!(buf_ptr, PerfBlob(0x42, 0x12345678)) + end + RT.perf_event_output(ctx, myperfmap, 0, buf) + 0 + end + API.load(kp) do + map = first(API.maps(kp.obj)) + cb_ref = Ref{Bool}(false) + GC.@preserve cb_ref begin + pb = Host.PerfBuffer{Cint,UInt32}(API.fd(map), 8; sample_cb=perf_sample_cb_ptr, ctx=Base.unsafe_convert(Ptr{Bool}, cb_ref)) + for i in 1:10 + Host.poll(pb, 100) + write(test_io, "1"); flush(test_io) + end + @test cb_ref[] + end + end + end @testset "get_stackid" begin # from BCC's stackcount struct StackKey @@ -508,8 +615,30 @@ if run_root_tests @test haskey(counts, key) # TODO: This is potentially racy @test haskey(stacks, key.sid) - @test occursin("ksys_write", Host.stack_to_string(stacks[key.sid])) + @test occursin("sys_write", Host.stack_to_string(stacks[key.sid])) + end + end + end + if BPFnative.has_vmlinux + @testset "struct accessors" begin + kp = KProbe("ksys_write"; license="GPL") do ctx + task = RT.get_current_task() + reinterpret(UInt64, task) == 0 && return 1 + cpu = RT.@elemptr(task.on_cpu[]) + if cpu !== nothing + if cpu == RT.get_smp_processor_id() + myhashmap[1] = cpu + end + end + 0 + end + API.load(kp) do + host_myhashmap = Host.hostmap(kp.obj, myhashmap) + write(test_io, "1"); flush(test_io) + @test haskey(host_myhashmap, 1) end end + else + @test_skip "struct accessors" end end