pax_global_header00006660000000000000000000000064136163321440014515gustar00rootroot0000000000000052 comment=85f9eea624c83443816e37654d0c1c93366a2e66 bpftrace-0.9.4/000077500000000000000000000000001361633214400133155ustar00rootroot00000000000000bpftrace-0.9.4/.clang-format000066400000000000000000000060141361633214400156710ustar00rootroot00000000000000--- Language: Cpp # BasedOnStyle: LLVM AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: Right AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: false AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: true BinPackArguments: false BinPackParameters: false BraceWrapping: # AfterCaseLabel was added in clang9 #AfterCaseLabel: true AfterClass: true AfterControlStatement: true AfterEnum: true AfterFunction: true AfterNamespace: false AfterStruct: true AfterUnion: true AfterExternBlock: true BeforeCatch: true BeforeElse: true IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeColon BreakStringLiterals: true ColumnLimit: 80 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: false DerivePointerAlignment: true DisableFormat: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeBlocks: Preserve IncludeCategories: - Regex: '^"(llvm|llvm-c|clang|clang-c)/' Priority: 2 - Regex: '^(<|"(gtest|gmock|isl|json)/)' Priority: 3 - Regex: '.*' Priority: 1 IncludeIsMainRegex: '(Test)?$' IndentCaseLabels: true IndentPPDirectives: None IndentWidth: 2 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None # Note: These penalties are not only relative to each other, but also to # penalties for more cases hardcoded into clang-format PenaltyBreakAssignment: 100 PenaltyBreakBeforeFirstCallParameter: 75 PenaltyBreakComment: 1 PenaltyBreakFirstLessLess: 50 PenaltyBreakString: 500 PenaltyExcessCharacter: 1000 PenaltyReturnTypeOnItsOwnLine: 1000000 PointerAlignment: Right ReflowComments: true SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 1 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Cpp11 TabWidth: 8 UseTab: Never ... bpftrace-0.9.4/.editorconfig000066400000000000000000000003231361633214400157700ustar00rootroot00000000000000[*] end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 indent_style = space indent_size = 2 [*.py] indent_size = 4 [tools/*.bt] indent_style = tab indent_size = unset bpftrace-0.9.4/.gitattributes000066400000000000000000000000601361633214400162040ustar00rootroot00000000000000*.bt linguist-language=D *.bt linguist-vendored bpftrace-0.9.4/.github/000077500000000000000000000000001361633214400146555ustar00rootroot00000000000000bpftrace-0.9.4/.github/workflows/000077500000000000000000000000001361633214400167125ustar00rootroot00000000000000bpftrace-0.9.4/.github/workflows/embedded.yml000066400000000000000000000072341361633214400211740ustar00rootroot00000000000000name: Embedded Builds on: push jobs: llvm_clang: runs-on: ubuntu-18.04 strategy: matrix: env: - TYPE: Debug NAME: debian_llvm+libclang+glibc2.27 LLVM_VERSION: 8 STATIC_LINKING: ON STATIC_LIBC: OFF EMBED_LLVM: OFF EMBED_CLANG: ON EMBED_LIBCLANG_ONLY: ON EMBED_BCC: OFF EMBED_LIBELF: OFF EMBED_BINUTILS: OFF RUN_ALL_TESTS: 1 RUNTIME_TEST_DISABLE: builtin.cgroup - TYPE: Release NAME: debian_llvm+libclang+glibc2.27 LLVM_VERSION: 8 STATIC_LINKING: ON STATIC_LIBC: OFF EMBED_LLVM: OFF EMBED_CLANG: ON EMBED_LIBCLANG_ONLY: ON EMBED_BCC: OFF EMBED_LIBELF: OFF EMBED_BINUTILS: OFF RUN_ALL_TESTS: 1 RUNTIME_TEST_DISABLE: builtin.cgroup - TYPE: Release NAME: vanilla_llvm+clang+glibc2.27 LLVM_VERSION: 8 STATIC_LINKING: ON STATIC_LIBC: OFF EMBED_LLVM: ON EMBED_CLANG: ON EMBED_LIBCLANG_ONLY: OFF EMBED_BCC: OFF EMBED_LIBELF: OFF EMBED_BINUTILS: OFF RUN_ALL_TESTS: 1 RUNTIME_TEST_DISABLE: builtin.cgroup steps: - uses: actions/checkout@v2 - name: Build docker container run: | docker build -t bpftrace-embedded -f docker/Dockerfile.bionic-glibc docker/ - name: bpftrace embedded build env: ${{ matrix.env }} run: | docker run --privileged -v $(pwd):$(pwd) -w $(pwd) -v /sys/kernel/debug:/sys/kernel/debug:rw -v /lib/modules:/lib/modules:ro -v /usr/src:/usr/src:ro -e STATIC_LINKING=${STATIC_LINKING} -e STATIC_LIBC=${STATIC_LIBC} -e EMBED_LLVM=${EMBED_LLVM} -e EMBED_CLANG=${EMBED_CLANG} -e EMBED_BCC=${EMBED_BCC} -e EMBED_LIBELF=${EMBED_LIBELF} -e EMBED_LIBCLANG_ONLY=${EMBED_LIBCLANG_ONLY} -e EMBED_BINUTILS=${EMBED_BINUTILS} bpftrace-embedded $(pwd)/build-embedded ${TYPE} -j`nproc` - name: Check linked libs env: ${{ matrix.env }} run: | docker run --privileged -v $(pwd):$(pwd) -w $(pwd) -v /sys/kernel/debug:/sys/kernel/debug:rw -v /lib/modules:/lib/modules:ro -v /usr/src:/usr/src:ro -e STATIC_LINKING=${STATIC_LINKING} -e STATIC_LIBC=${STATIC_LIBC} -e EMBED_LLVM=${EMBED_LLVM} -e EMBED_CLANG=${EMBED_CLANG} -e EMBED_BCC=${EMBED_BCC} -e EMBED_LIBELF=${EMBED_LIBELF} -e EMBED_LIBCLANG_ONLY=${EMBED_LIBCLANG_ONLY} -e EMBED_BINUTILS=${EMBED_BINUTILS} --entrypoint /bin/bash bpftrace-embedded -c "[[ -f $(pwd)/build-embedded/src/bpftrace ]] && ! readelf --dynamic $(pwd)/build-embedded/src/bpftrace | grep NEEDED | grep -v 'libm\|libc\|ld-linux\|libpthread\|libdl'" - name: Strip artifacts env: ${{ matrix.env }} if: matrix.env['TYPE'] == 'Release' run: | docker run --privileged -v $(pwd):$(pwd) -w $(pwd) -v /sys/kernel/debug:/sys/kernel/debug:rw -v /lib/modules:/lib/modules:ro -v /usr/src:/usr/src:ro -e STATIC_LINKING=${STATIC_LINKING} -e STATIC_LIBC=${STATIC_LIBC} -e EMBED_LLVM=${EMBED_LLVM} -e EMBED_CLANG=${EMBED_CLANG} -e EMBED_BCC=${EMBED_BCC} -e EMBED_LIBELF=${EMBED_LIBELF} -e EMBED_LIBCLANG_ONLY=${EMBED_LIBCLANG_ONLY} -e EMBED_BINUTILS=${EMBED_BINUTILS} --entrypoint /bin/bash bpftrace-embedded -c "strip --keep-symbol BEGIN_trigger $(pwd)/build-embedded/src/bpftrace" - uses: actions/upload-artifact@v1 with: name: bpftrace-${{ matrix.env['TYPE'] }}-${{ matrix.env['NAME'] }} path: build-embedded/src/bpftrace - uses: actions/upload-artifact@v1 with: name: bpftrace_test-${{ matrix.env['TYPE'] }}-${{ matrix.env['NAME'] }} path: build-embedded/tests/bpftrace_test bpftrace-0.9.4/.gitignore000066400000000000000000000000551361633214400153050ustar00rootroot00000000000000build/ build-*/ tests/runtime/*.pyc .vagrant bpftrace-0.9.4/.travis.yml000066400000000000000000000121151361633214400154260ustar00rootroot00000000000000sudo: required dist: bionic language: # Trick Travis into using bionic (see https://travis-ci.community/t/for-dist-bionic-xenial-vm-is-used-instead-with-language-rust/4487/3) # We're building on Docker, so the actual language doesn't matter. - python services: - docker cache: directories: - $HOME/build/iovisor/bpftrace/build-$TYPE-$BASE/embedded_llvm-prefix/ - $HOME/build/iovisor/bpftrace/build-$TYPE-$BASE/embedded_clang-prefix/ matrix: include: - name: "clang-format" script: - git clang-format master - git diff --exit-code env: LLVM_VERSION=8 BASE=bionic # Certain tests are disabled on our LLVM 5 builds because they are flaky # when bpftrace is compiled with musl libc. They should still be capable # of passing occasionally. # String comparisons and args multiple tracepoints tests are the exception # - they are just broken in debug builds. - name: "Static LLVM 5 Debug" env: LLVM_VERSION=5.0 BASE=alpine TYPE=Debug STATIC_LINKING=ON STATIC_LIBC=ON TEST_ARGS="--gtest_filter=codegen.*:-codegen.call_ntop_char4:codegen.call_ntop_char16:codegen.call_printf:codegen.enum_declaration:codegen.macro_definition:codegen.printf_offsets:codegen.struct_*:codegen.string_equal_comparison:codegen.string_not_equal_comparison:codegen.literal_strncmp:codegen.strncmp:codegen.args_multiple_tracepoints*:codegen.logical_and_or_different_type" - name: "Static LLVM 5 Release" env: LLVM_VERSION=5.0 BASE=alpine TYPE=Release STATIC_LINKING=ON STATIC_LIBC=ON TEST_ARGS="--gtest_filter=codegen.*:-codegen.call_ntop_char4:codegen.call_ntop_char16:codegen.call_printf:codegen.enum_declaration:codegen.macro_definition:codegen.printf_offsets:codegen.struct_*:codegen.args_multiple_tracepoints*:codegen.logical_and_or_different_type" - name: "LLVM 6 Debug" env: LLVM_VERSION=6.0 BASE=bionic TYPE=Debug RUN_ALL_TESTS=1 RUNTIME_TEST_DISABLE=builtin.cgroup,probe.kprobe_offset_fail_size - name: "LLVM 6 Release" env: LLVM_VERSION=6.0 BASE=bionic TYPE=Release RUN_ALL_TESTS=1 RUNTIME_TEST_DISABLE=builtin.cgroup,probe.kprobe_offset_fail_size - name: "LLVM 7 Debug" env: LLVM_VERSION=7 BASE=bionic TYPE=Debug RUN_ALL_TESTS=1 RUNTIME_TEST_DISABLE=builtin.cgroup,probe.kprobe_offset_fail_size - name: "LLVM 7 Release" env: LLVM_VERSION=7 BASE=bionic TYPE=Release RUN_ALL_TESTS=1 RUNTIME_TEST_DISABLE=builtin.cgroup,probe.kprobe_offset_fail_size - name: "LLVM 8 Debug" env: LLVM_VERSION=8 BASE=bionic TYPE=Debug RUN_ALL_TESTS=1 RUNTIME_TEST_DISABLE=builtin.cgroup,probe.kprobe_offset_fail_size - name: "LLVM 8 Release" env: LLVM_VERSION=8 BASE=bionic TYPE=Release RUN_ALL_TESTS=1 RUNTIME_TEST_DISABLE=builtin.cgroup,probe.kprobe_offset_fail_size - name: "Static+glibc-2.27 Debian-linked LLVM 8 Debug" env: LLVM_VERSION=8 BASE=bionic-glibc STATIC_LINKING=ON STATIC_LIBC=OFF EMBED_LLVM=OFF EMBED_CLANG=ON EMBED_LIBCLANG_ONLY=ON TYPE=Debug RUN_ALL_TESTS=1 RUNTIME_TEST_DISABLE=builtin.cgroup,probe.kprobe_offset_fail_size - name: "Static+glibc-2.27 Debian-linked LLVM 8 Release" env: LLVM_VERSION=8 BASE=bionic-glibc STATIC_LINKING=ON STATIC_LIBC=OFF EMBED_LLVM=OFF EMBED_CLANG=ON EMBED_LIBCLANG_ONLY=ON TYPE=Release RUN_ALL_TESTS=1 RUNTIME_TEST_DISABLE=builtin.cgroup,probe.kprobe_offset_fail_size install: - sudo apt-get install linux-headers-$(uname -r) - docker build --build-arg LLVM_VERSION=$LLVM_VERSION -t bpftrace-builder-$BASE-llvm-$LLVM_VERSION -f docker/Dockerfile.$BASE docker/ script: - sudo docker run --privileged --rm -it -v ${PWD}:${PWD} -v /sys/kernel/debug:/sys/kernel/debug:rw -v /lib/modules:/lib/modules:ro -v /usr/src:/usr/src:ro -e WARNINGS_AS_ERRORS=ON -e STATIC_LINKING=$STATIC_LINKING -e STATIC_LIBC=$STATIC_LIBC -e EMBED_LLVM=$EMBED_LLVM -e EMBED_CLANG=$EMBED_CLANG -e EMBED_LIBCLANG_ONLY=$EMBED_LIBCLANG_ONLY -e LLVM_VERSION=$LLVM_VERSION -e RUN_ALL_TESTS=$RUN_ALL_TESTS -e TEST_ARGS=$TEST_ARGS -e RUNTIME_TEST_DISABLE=$RUNTIME_TEST_DISABLE bpftrace-builder-$BASE-llvm-$LLVM_VERSION ${PWD}/build-$TYPE-$BASE $TYPE -j`getconf _NPROCESSORS_ONLN` notifications: irc: channels: # Running the following command generates an encrypted string that only # decrypts properly in iovisor/bpftrace. This prevents forks from # spamming the IRC channel. # # travis encrypt -r "iovisor/bpftrace" "irc.oftc.net#bpftrace" # - secure: "wIAz2oF8z8Kirt0pjAdOT2z9GOm3KtlPfZzdMUHvnDkehT0dIVLcbMvmPOdje/0MJVQXpl3/XguT31wqJ+qhR0el/ZWAxc53ZT7lQRP6v/yKS9AJWO2RYYFMjHldTkdYe5TyKcX3IEY7ixSblkO0eDGyKDC2eYqghSFnclbC83oQVe0qDNewxo4W5gjQjW9Bmly6c7QYxHp/qTYkwP58zOvjMXKupPgk6xjqhI1sShpT5V/1tx6ATq5BPOUWJtIz7VvdrFar1F5r+RuBLz7z4kNhu9ofXfbxOpyuuNIaTPgGBRLa98oHm6xCNNCPouVM+zNXiJLcb5aDcL1DkhueMOYrbOXHp5DAvq58t29zLiksDWY89mo/gp555ZrVibnhj47mIxQ6QFv77I0nJWsk5F11tsVI0zDcBye6nlHJfLqZIPzGdrp4JPpAghLZq2Fgnhqq1gcdax1pHIp4f90XsuPZsf7My24/2oDXnrKMofNIaA4ubzreTDGxhL+xgQK15Tz2XVcjn5MRAup0z6WN+nEWbDF52iQxyKBhU62zXCuvFG62lho3E8lU0e7dQXlFJnG/KY/bW1fhjeC8BLYdejqE/iZkZp2jxqTiAAoTcrTws/AhaXHFNVVTiiVpekvbTbGWEbd8Hffl/+LNmCrjEDrQyCe//QLyy9bjcG+I+Jk=" on_success: change on_failure: always bpftrace-0.9.4/CHANGELOG.md000066400000000000000000001575231361633214400151430ustar00rootroot00000000000000# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased ## [0.9.4] 2020-02-04 ### Highlights - New calls: `signal`, `override`, `strncmp` - Support for attaching to `kprobes` at an offset - Support for struct bitfields ### All Changes #### Added - Add support to attach kprobe to offset (e31e398) by Masanori Misono <m.misono760@gmail.com> - Add `--info` flag (afafbf5) by bas smit <bas@baslab.org> - Mark 'override_return' as unsafe (49cd031) by bas smit <bas@baslab.org> - Implement bpf_override_return (784c64e) by bas smit <bas@baslab.org> - arch: Add support for powerpc64 registers (472f5ed) by Sandipan Das <sandipan@linux.ibm.com> - Add source line information to error messages (46e62c0) by bas smit <bas@baslab.org> - Support octal and hexadecimal escape sequences in string (873d7ba) by Masanori Misono <m.misono760@gmail.com> - Implement `signal` (32bb577) by bas smit <bas@baslab.org> - Make `signal` unsafe (be676b5) by bas smit <bas@baslab.org> - Implement strncmp (a1d0263) by Jay Kamat <jaygkamat@gmail.com> - Add builtin: cpid (cae4dcf) by bas smit <bas@baslab.org> - Allow uprobe offset on quoted attach points (6432609) by bas smit <bas@baslab.org> - Allow string literals as signal specifiers (0230f98) by bas smit <bas@baslab.org> - Implement bitfield support (8822cc2) by Daniel Xu <dxu@dxuuu.xyz> #### Changed - Take first binary match for PATH lookup on uprobe and USDT (ec5c2c3) by Daniel Xu <dxu@dxuuu.xyz> - Infer `uaddr` pointer type from ELF symbol size (59b0659) by bas smit <bas@baslab.org> - Rename `override_return` to `override` (96cb4b5) by bas smit <bas@baslab.org> - Runtime feature testing (17f3c82) by bas smit <bas@baslab.org> - Silenced unsigned/signed comparison warning (75101f9) by Daniel Xu <dxu@dxuuu.xyz> - error message for verification buffer size (41c0ab8) by Gordon Marler <gmarler@bloomberg.net> - Reimplement `elapsed` using a hidden map (2613ea6) by bas smit <bas@baslab.org> - Remove dependency on 'command' shell builtin (3f7a94a) by Adam Jensen <acjensen@gmail.com> - Make parsing fail if lexing fails (d092cb1) by Alastair Robertson <alastair@ajor.co.uk> - Limit increment/decrement to variables (c126441) by bas smit <bas@baslab.org> - Only warn about missing BTF info in debug mode (f84ae5c) by Daniel Xu <dxu@dxuuu.xyz> - Allow uretprobe at an address (f0785b5) by bas smit <bas@baslab.org> - fix uprobe address on short name (f7ed963) by bas smit <bas@baslab.org> - Reverse return value of strncmp (384640e) by Jay Kamat <jaygkamat@gmail.com> - Make strcmp return 0 on match (8d9069c) by bas smit <bas@baslab.org> - Differentiate between regular structs and typedef'd structs (8d34209) by Alastair Robertson <alastair@ajor.co.uk> #### Fixed - Support "." in attach point function argument (c532159) by Daniel Xu <dxu@dxuuu.xyz> - clang_parser: workaround for asm_inline in 5.4+ kernel headers (c30e4dd) by Andreas Gerstmayr <agerstmayr@redhat.com> - Consider signed array (9bb6a8b) by Masanori Misono <m.misono760@gmail.com> - Support anonymous struct/union in BTF::type_of() (36d9914) by Masanori Misono <m.misono760@gmail.com> - Allow resolving binary paths in different mount ns (124e569) by Dale Hamel <dale.hamel@shopify.com> - Avoid useless allocations in strncmp (551664e) by bas smit <bas@baslab.org> - Avoid comparing past string length (b10dc32) by bas smit <bas@baslab.org> - Call llvm.lifetime.end after memcpy if the expression is not a variable (8b2d219) by Masanori Misono <m.misono760@gmail.com> - bug: Strip newlines from log message (361d1fc) by bas smit <bas@baslab.org> - Fix buggy signed binop warnings (e87897c) by Daniel Xu <dxu@dxuuu.xyz> - Reuse `cat` and `system` ID when expanding probes (79aada5) by bas smit <bas@baslab.org> - Remove unneeded `probe_read`s from `strcmp` (43b4e4c) by bas smit <bas@baslab.org> - Fix func variable in uprobe (d864f18) by Masanori Misono <m.misono760@gmail.com> - Add space for the error message about kernel.perf_event_max_stack (de2a7a8) by Kenta Tada <Kenta.Tada@sony.com> - Improve uprobe/usdt visitor error handling and messaging (5005902) by Adam Jensen <acjensen@gmail.com> - Fix some semantic analyser crashes (b11dc75) by Alastair Robertson <alastair@ajor.co.uk> - Fix codegen for modulo operation (fe0ed5a) by Daniel Xu <dxu@dxuuu.xyz> #### Documentation - Document `override_return` (b83b51d) by bas smit <bas@baslab.org> - Add documentation on BTF (6623f25) by Masanori Misono <m.misono760@gmail.com> - docs: limit to 105 chars (91e9dad) by bas smit <bas@baslab.org> - docs: Remove double shebang (da8b10c) by bas smit <bas@baslab.org> - docs: improve readability of code snippets (34a394a) by bas smit <bas@baslab.org> - docs: remove unneeded html elements (06d8662) by bas smit <bas@baslab.org> - Fix typos (e5ad6b9) by Michael Prokop <michael.prokop@synpro.solutions> - One-liner tutorial: Use "struct" when casting (7a5624c) by Alastair Robertson <alastair@ajor.co.uk> - docs: Add centos 7 repo (1b4cb8f) by bas smit <bas@baslab.org> - docs: Fix typo (b38dbd0) by bas smit <bas@baslab.org> - Move debug flags closer to each other in help message (df61049) by Daniel Xu <dxu@dxuuu.xyz> - Add binutils dependency to documentation (c57204c) by Daniel Xu <dxu@dxuuu.xyz> - Add documentation on release procedure (#981) (528fd6e) by Daniel Xu <dxu@dxuuu.xyz> - fix: Minor spelling correction (b3a6aee) by Jason Wohlgemuth <jhwohlgemuth@users.noreply.github.com> - Document `signal` (d5f3c75) by bas smit <bas@baslab.org> - INSTALL.md: Fix TOC link (1ab0a71) by Alastair Robertson <alastair@ajor.co.uk> - Amend sizes in documentation and provide date (ddd10fe) by Dale Hamel <dale.hamel@shopify.com> - Docs: add missing TOC entry (8c1d4e9) by bas smit <bas@baslab.org> - Add the chinese version for one liners tutorial (15a930e) by supersojo <suyanjun218@163.com> #### Internal - Reorganize tests/ directory (193177b) by Daniel Xu <dxu@dxuuu.xyz> - Fix typing issues in `CreateMapUpdateElem` (e86b9bb) by bas smit <bas@baslab.org> - Fix typing issues in `CreateMapLookup` (14af118) by bas smit <bas@baslab.org> - Fix build: Add namespace to BPF_FUNC_override_return (b6de734) by Alastair Robertson <alastair@ajor.co.uk> - Unify vmlinux and BTF location list (1d39776) by Masanori Misono <m.misono760@gmail.com> - Disable probe.kprobe_offset_fail_size runtime test in CI (1497434) by Masanori Misono <m.misono760@gmail.com> - fmt: update formatting in clang_parser.cpp (aefc424) by Andreas Gerstmayr <agerstmayr@redhat.com> - Use constexpr (b59c3a7) by Masanori Misono <m.misono760@gmail.com> - Make use of feature testing (b01f89c) by bas smit <bas@baslab.org> - Import libbpf (132e1ee) by bas smit <bas@baslab.org> - Rename BPFTRACE_BTF_TEST to BPFTRACE_BTF (5bbeb31) by Masanori Misono <m.misono760@gmail.com> - Add test for anonymous struct/union processing using BTF (240f59a) by Masanori Misono <m.misono760@gmail.com> - Switch tests suite to `bcc_foreach_sym` (a251477) by bas smit <bas@baslab.org> - Make resolve_binary_paths include non-executable shared objects in its return. (c3d1095) by Michał Gregorczyk <michalgr@fb.com> - Remove full static builds from travis (4fe9064) by Dale Hamel <dale.hamel@srvthe.net> - Move ast.h definitions into ast.cpp (f0dd0b4) by Daniel Xu <dxu@dxuuu.xyz> - Use subprocess.Popen text mode (47de78b) by Daniel Xu <dxu@dxuuu.xyz> - Fix debian libclang only linking (a9a2f0f) by Dale Hamel <dale.hamel@srvthe.net> - Build static+libc images using github actions (4794aba) by Dale Hamel <dale.hamel@srvthe.net> - Enable static+glibc builds and embedding LLVM deps (b1ae710) by Dale Hamel <dale.hamel@shopify.com> - Create StderrSilencer helper class to redirect stderr (b59b97a) by Daniel Xu <dxu@dxuuu.xyz> - Add missing semicolon (add4117) by Daniel Xu <dxu@dxuuu.xyz> - ast: codegen: Add abstraction for stack pointer offset (d19614d) by Sandipan Das <sandipan@linux.ibm.com> - clang-format: avoid breaking indent in irbuilderbpf.h (5b6d236) by bas smit <bas@baslab.org> - Non-invasive formatting of src/*.h (98328f1) by Alastair Robertson <alastair@ajor.co.uk> - Clang Format: Update line-break penalties (30d5b8d) by Alastair Robertson <alastair@ajor.co.uk> - correct for clang-format check (bb30265) by Gordon Marler <gmarler@bloomberg.net> - Add requested msg prefix (f3327bd) by Gordon Marler <gmarler@bloomberg.net> - add requested changes. (c9453b5) by Gordon Marler <gmarler@bloomberg.net> - Show current log size in msg as starting point (7942b9d) by Gordon Marler <gmarler@bloomberg.net> - Fix CI clang-format (13556f9) by Daniel Xu <dxu@dxuuu.xyz> - Make ninja work with build system (76bb97a) by Daniel Xu <dxu@dxuuu.xyz> - Clang Format: switch/case bracketing style fixes (f4e46b2) by Alastair Robertson <alastair@ajor.co.uk> - Clang Format: Don't wrap braces after namespace (4b26e3f) by Alastair Robertson <alastair@ajor.co.uk> - Add non-literal strncmp test (1c41333) by bas smit <bas@baslab.org> - Rename literal test (4295985) by bas smit <bas@baslab.org> - refactor CreateMapLookupElem (7b7ab95) by bas smit <bas@baslab.org> - Add a semantic and runtime test to test task_struct field accesses (8519550) by Masanori Misono <m.misono760@gmail.com> - Use `struct task_struct` instead of `task_struct` (d39db3a) by Masanori Misono <m.misono760@gmail.com> - BTF leftover for full type rename (5088682) by Jiri Olsa <jolsa@kernel.org> - Create a single is_numeric() function in utils (374ca46) by Alastair Robertson <alastair@ajor.co.uk> - Warn if cmake is less than 3.13 when building with ASAN (ad3b9f3) by Masanori Misono <m.misono760@gmail.com> - Remove unnecessary division (81b7c0a) by Daniel Xu <dxu@dxuuu.xyz> - Add build option, BUILD_ASAN, to turn on address sanitizer (04d015e) by Daniel Xu <dxu@dxuuu.xyz> - Properly indent cmake config (24a7695) by Daniel Xu <dxu@dxuuu.xyz> - Use mocks consistently in codegen tests so they don't require root to run (b261833) by Alastair Robertson <alastair@ajor.co.uk> - Enable -Werror on CI builds (2f0f5db) by Alastair Robertson <alastair@ajor.co.uk> - CMakeLists cleanups (6b8d7ad) by Alastair Robertson <alastair@ajor.co.uk> - Disable deprecated ORCv1 warning in llvm (607b8af) by Daniel Xu <dxu@dxuuu.xyz> - Normalize code (0878020) by Daniel Xu <dxu@dxuuu.xyz> - Pass location to uprobe+offset probe (8c1a355) by bas smit <bas@baslab.org> - Use symbolic constants instead of numeric literal (457aab9) by Daniel Xu <dxu@dxuuu.xyz> - Add clang-format rule to travis CI (3b9e959) by Daniel Xu <dxu@dxuuu.xyz> - Turn off clang-format for specific long lists (bcbfaa0) by Daniel Xu <dxu@dxuuu.xyz> - Add .clang-format file (b04e478) by Daniel Xu <dxu@dxuuu.xyz> - Change reinterpret_cast to static cast and fix formatting (03d2d67) by Alastair Robertson <alastair@ajor.co.uk> - Add PER_CPU detection helper (594fd34) by bas smit <bas@baslab.org> - Store the BPF map type in the map object (2e850c5) by bas smit <bas@baslab.org> - format: align parser (b3680e6) by bas smit <bas@baslab.org> - Make ASSERTs in helper functions fail the parent testcase (ddaa482) by Alastair Robertson <alastair@ajor.co.uk> - Add dependency on testprogs and bpftrace to runtime tests (7870091) by Daniel Xu <dxu@dxuuu.xyz> - Add custom target for testprogs (d799e83) by Daniel Xu <dxu@dxuuu.xyz> - Move testprogs cmake definition before runtime test definitions (6783448) by Daniel Xu <dxu@dxuuu.xyz> - Add tests for resolve_binary_path (8fb727a) by Adam Jensen <acjensen@gmail.com> - Fix tests to run without $PATH (c1c60c2) by Adam Jensen <acjensen@gmail.com> - Add runtime tests for ambiguous wildcard matches (cca9040) by Adam Jensen <acjensen@gmail.com> - Add regression tests for modulo operation (0a1cb65) by Daniel Xu <dxu@dxuuu.xyz> - Don't take reference of a pointer (61ba68a) by Daniel Xu <dxu@dxuuu.xyz> - Silence test suite (8d1f691) by bas smit <bas@baslab.org> - Disable builtin.cgroup runtime test in CI (8277876) by Daniel Xu <dxu@dxuuu.xyz> - Add a RUNTIME_TEST_DISABLE environment to runtime tests (6c984ea) by Daniel Xu <dxu@dxuuu.xyz> - Add script to compare tool codegen between builds (d95a2d1) by bas smit <bas@baslab.org> - Minor btf cleanups (a10479b) by Daniel Xu <dxu@dxuuu.xyz> - Add FieldAnalyser to the clang parser tests (13b06d2) by Jiri Olsa <jolsa@kernel.org> - Iterate only over detected types in BTF::c_def (409d7ad) by Jiri Olsa <jolsa@kernel.org> - Add BPFtrace::btf_set_ to replace global BTF type set (06a09ca) by Jiri Olsa <jolsa@kernel.org> - Add BTF::type_of function (4378e24) by Jiri Olsa <jolsa@kernel.org> - Adding FieldAnalyser class (ec3c621) by Jiri Olsa <jolsa@kernel.org> - Move BTF object into BPFtrace class (fdf3940) by Jiri Olsa <jolsa@kernel.org> - Add runtime test (db81d25) by Daniel Xu <dxu@dxuuu.xyz> - Add clang_parser test (6cae624) by Daniel Xu <dxu@dxuuu.xyz> - Use struct instead of class (fbe3bf6) by Daniel Xu <dxu@dxuuu.xyz> - Make `strncmp` codegen unsigned (af54c9b) by bas smit <bas@baslab.org> - Avoid shift/reduce warnings (3761904) by bas smit <bas@baslab.org> - Treat stackmode as identifier (e018da5) by bas smit <bas@baslab.org> - Define all `call`s in the lexer to avoid redefinition (b8ddf25) by bas smit <bas@baslab.org> - Remove `_` suffix from local variables (34d4654) by bas smit <bas@baslab.org> - Add regression test for #957 (253cfd6) by bas smit <bas@baslab.org> - Fix paths in tests (a8dcb02) by bas smit <bas@baslab.org> - Allow runtime tests to be ran from any directory (9139bed) by bas smit <bas@baslab.org> - Link libiberty during static builds (aa8c7ba) by Daniel Xu <dxu@dxuuu.xyz> - cpid vector -> single (52ff6e3) by bas smit <bas@baslab.org> - 0.9.3 Changelog (f4ea282) by bas smit <bas@baslab.org> - Bump to 0.9.3 (3d1e022) by bas smit <bas@baslab.org> - Add `signal` tests (95cba2b) by bas smit <bas@baslab.org> - Add missing kernel option in INSTALL.md (099d1c9) by Edouard Dausque <git@edouard.dausque.net> - Make printing the LLVM IR from a debugger easier (d534295) by bas smit <bas@baslab.org> - Make `uprobes - list probes by pid` test more quiet (b2a570a) by Daniel Xu <dxu@dxuuu.xyz> - vagrant: add binutils-dev dependency (2e73e04) by Matheus Marchini <mmarchini@netflix.com> - Fix maptype bugs (028c869) by bas smit <bas@baslab.org> - Disable -Winconsistent-missing-override in mock.h (d3cb095) by Masanori Misono <m.misono760@gmail.com> - Disable -Wcast-qual for bpf/btf.h (b308a9c) by Masanori Misono <m.misono760@gmail.com> - Import used headers (979992e) by Masanori Misono <m.misono760@gmail.com> - Fix modernize-deprecated-headers warnings (b09836b) by Masanori Misono <m.misono760@gmail.com> - Fix -Wcast-align (ce45470) by Masanori Misono <m.misono760@gmail.com> - Fix -Wdelete-abstract-non-virtual-dtor (cb78da3) by Masanori Misono <m.misono760@gmail.com> - Fix -Wstring-plus-int (3e52a3d) by Masanori Misono <m.misono760@gmail.com> - Fix -Wunreachable-code-loop-increment (f354911) by Masanori Misono <m.misono760@gmail.com> - Fix -Wbraced-scalar-init (6fc82ed) by Masanori Misono <m.misono760@gmail.com> - Fix -Wmismatched-tags (e29a4f2) by Masanori Misono <m.misono760@gmail.com> - Fix -Wformat-security (cc3ef62) by Masanori Misono <m.misono760@gmail.com> - Fix some compiler warnings (9a85f10) by Daniel Xu <dxu@dxuuu.xyz> ## [0.9.3] 2019-11-22 ### Highlights - Allow attaching to uprobes at an offset - BTF support - integer casts - integer pointer casts ### All Changes #### Added - Add support to cast to a pointer of integer (#942) (8b60006) by Masanori Misono <m.misono760@gmail.com> - Add sargX builtin (9dc6024) by Adam Jensen <acjensen@gmail.com> - Add support to specify symbol with offset to uprobe (33e887f) by Jiri Olsa <jolsa@kernel.org> - add threadsnoop tool (f021967) by Brendan Gregg <bgregg@netflix.com> - add tcpsynbl tool (0cbc301) by Brendan Gregg <bgregg@netflix.com> - add tcplife tool (51d8852) by Brendan Gregg <bgregg@netflix.com> - add swapin tool (c80753b) by Brendan Gregg <bgregg@netflix.com> - add setuids tool (439311a) by Brendan Gregg <bgregg@netflix.com> - add naptime tool (572de59) by Brendan Gregg <bgregg@netflix.com> - add biostacks tool (162bc63) by Brendan Gregg <bgregg@netflix.com> - Add check if uprobe is aligned (e2c65bd) by Jiri Olsa <jolsa@kernel.org> - Support wildcards in probe path (#879) (2a361cc) by Adam Jensen <acjensen@gmail.com> - Add --btf option (ec931fa) by Jiri Olsa <jolsa@kernel.org> - Introduce int casts (ee82e64) by bas smit <bas@baslab.org> - utils: unpack kheaders.tar.xz if necessary (#768) (896fafb) by Matt Mullins <mokomull@gmail.com> - Add support to check for libbpf package (8e0800c) by Jiri Olsa <jolsa@kernel.org> - Add signed types (53cf421) by bas smit <bas@baslab.org> - Add location support to builtins (a79e5a6) by bas smit <bas@baslab.org> - Add location support to calls (c1b2a91) by bas smit <bas@baslab.org> - Add location support to the AST (67c208d) by bas smit <bas@baslab.org> - Highlight bpftrace source files (cfbaa2f) by Paul Chaignon <paul.chaignon@orange.com> - Add travis CI build icon to README.md (50375e2) by Daniel Xu <dxu@dxuuu.xyz> - Add IRC badge to README (a20af57) by Daniel Xu <dxu@dxuuu.xyz> #### Changed - Use the same shebang for all tools (78eb451) by bas smit <bas@baslab.org> - Change exit() to send SIGTERM to child processes (649cc86) by Matheus Marchini <mmarchini@netflix.com> - Make `stats` and `avg` signed (809dc46) by bas smit <bas@baslab.org> - Refactor error printer to make severity level configurable (676a6a7) by bas smit <bas@baslab.org> - Make output line-buffered by default (#894) (78e64ba) by Daniel Xu <dxu@dxuuu.xyz> - cmake: don't use language extensions (like gnu++14) (4ce4afc) by Matheus Marchini <mmarchini@netflix.com> - add file extension on README (545901c) by sangyun-han <sangyun628@gmail.com> - build: don't set -std flag manually (3cbc482) by Matheus Marchini <mat@mmarchini.me> - Don't use random value on stack (b67452b) by Daniel Xu <dxu@dxuuu.xyz> - codegen: ensure logical OR and AND works with non-64-bit integers (69cbd85) by Matheus Marchini <mat@mmarchini.me> - Allow child process to exit on attach_probe failure (#868) (ecf1bc8) by Adam Jensen <acjensen@gmail.com> - json output: Make output more consistent (#874) (9d1269b) by Dan Xu <dxu@dxuuu.xyz> - Do not generate extra load for ++/-- for maps/variables (3f79fad) by Jiri Olsa <jolsa@kernel.org> #### Fixed - semantic_analyser: validate use of calls as map keys (b54c085) by Matheus Marchini <mat@mmarchini.me> - codegen: fix rhs type check for binop (2d87213) by Daniel Xu <dxu@dxuuu.xyz> - Fix map field access (a9acf92) by Jiri Olsa <jolsa@kernel.org> - Correctly parse enums (59d0b0d) by Daniel Xu <dxu@dxuuu.xyz> - Allow build from uncommon bcc installation (9986329) by Jiri Olsa <jolsa@kernel.org> - Fix sigint handling under heavy load (0058d41) by Augusto Caringi <acaringi@redhat.com> - Assign default value to elem_type to avoid undefined behavior. (a0b8722) by Florian Kuebler <kuebler@google.com> - Strip trailing newline from error message (5315eee) by bas smit <bas@baslab.org> - Use strerror to improve `cgroupid` error message (72de290) by bas smit <bas@baslab.org> - Initialize member variable (4dd8bb8) by Daniel Xu <dxu@dxuuu.xyz> - Fix umask build issue (#861) (24de62a) by Michael Würtinger <michael@wuertinger.de> - Handle SIGTERM gracefully (#857) (fb47632) by Dan Xu <dxu@dxuuu.xyz> - json output: suppress output if map is not initialized (348975b) by Andreas Gerstmayr <agerstmayr@redhat.com> - fix 'designated initializers' build errors (#847) (4910e75) by Alek P <alek-p@users.noreply.github.com> - remove invalid 'unused attribute' (9bf8204) by Matheus Marchini <mat@mmarchini.me> #### Documentation - Mention sargX builtin in docs (352e983) by Adam Jensen <acjensen@gmail.com> - Update reference guide (65c97fd) by Jiri Olsa <jolsa@kernel.org> - Docs: fix inconsistent install script option (a65e3d8) by Daniel T. Lee <danieltimlee@gmail.com> - docs: Fix mismatch between code and example (2499437) by bas smit <bas@baslab.org> - fix typo in example text - correct name of script (891021b) by sangyun-han <sangyun628@gmail.com> - Add openSUSE package status link into install.md (#859) (613b42f) by James Wang <jnwang@suse.com> - Fix a typo in reference_guide (e7420eb) by James Wang <jnwang@suse.com> - Ubuntu instructions: add minimum release version (413c1a0) by Peter Sanford <psanford@sanford.io> #### Internal - Add tests for sargX builtin (774a7a6) by Adam Jensen <acjensen@gmail.com> - Add test (0c08b1d) by Daniel Xu <dxu@dxuuu.xyz> - Avoid leaking state between cmake tests (625269f) by bas smit <bas@baslab.org> - Avoid testing for FOUR_ARGS_SIGNATURE on systems without bfd (cd1d231) by bas smit <bas@baslab.org> - Unset `CMAKE_REQUIRED_LIBRARIES` to avoid influencing tests (ab0665b) by bas smit <bas@baslab.org> - Define PACKAGE to make libbfd happy (d165396) by Daniel Xu <dxu@dxuuu.xyz> - Fix 'may be used uninitialized' build warning in bfd-disasm.cpp (ffd203b) by Augusto Caringi <acaringi@redhat.com> - Change "variable.tracepoint arg casts in predicates" runtime test (9aae057) by Jiri Olsa <jolsa@kernel.org> - bfd-disasm: fix LIBBFD_DISASM_FOUR_ARGS_SIGNATURE (7d62627) by Matheus Marchini <mmarchini@netflix.com> - semantic_analyser: fix gcc build error on xenial (0e6014a) by Matheus Marchini <mmarchini@netflix.com> - Prevent forks from notifying the IRC channel (ca93440) by Daniel Xu <dxu@dxuuu.xyz> - Add runtime tests for uprobe offset/address (d9c2bab) by Jiri Olsa <jolsa@kernel.org> - Bypass the uprobe align check in unsafe mode (18b9635) by Jiri Olsa <jolsa@kernel.org> - Adding tests for uprobe offset definitions (d894d0e) by Jiri Olsa <jolsa@kernel.org> - Add BfdDisasm class (8198628) by Jiri Olsa <jolsa@kernel.org> - Add Disasm class (6f7bc6f) by Jiri Olsa <jolsa@kernel.org> - Add support to check for libbfd/libopcodes libraries (542f2b9) by Jiri Olsa <jolsa@kernel.org> - Add resolve_offset_uprobe functions (7be4143) by Jiri Olsa <jolsa@kernel.org> - Add address and func_offset to ast::AttachPoint and Probe classes (893201a) by Jiri Olsa <jolsa@kernel.org> - Fix `sigint under heavy load` runtime test (4f7fd67) by Daniel Xu <dxu@dxuuu.xyz> - Notify irc channel on build failures (83b5684) by Daniel Xu <dxu@dxuuu.xyz> - Add BTF class (43530aa) by Jiri Olsa <jolsa@kernel.org> - Read every BTF type (67dbe3f) by Daniel Xu <dxu@dxuuu.xyz> - Disable codegen.logical_and_or_different_type test in alpine CI (5271e6c) by Daniel Xu <dxu@dxuuu.xyz> - Warn when doing signed division (#910) (fff3b05) by Daniel Xu <dxu@dxuuu.xyz> - Add short option for --btf and update usage (88dbe47) by Daniel Xu <dxu@dxuuu.xyz> - Add BTF tests (47621bb) by Jiri Olsa <jolsa@kernel.org> - Add ClangParser::parse_btf_definitions function (54cf4ab) by Jiri Olsa <jolsa@kernel.org> - Add SizedType::operator!= function (8cb79f9) by Jiri Olsa <jolsa@kernel.org> - Add ClangParserHandler::check_diagnostics function (3e75475) by Jiri Olsa <jolsa@kernel.org> - Add ClangParser::visit_children function (4842ccf) by Jiri Olsa <jolsa@kernel.org> - Add BTF::c_def function (02a2d0d) by Jiri Olsa <jolsa@kernel.org> - Add Expression::resolve string set (0779333) by Jiri Olsa <jolsa@kernel.org> - Add curtask task_struct cast type for field access (80cb0d7) by Jiri Olsa <jolsa@kernel.org> - test: fix watchpoint runtime test flakiness (88fc1b8) by Matheus Marchini <mmarchini@netflix.com> - Disable sign checking for division binop (8084463) by bas smit <bas@baslab.org> - Add ability to test for warnings (b19ebb6) by bas smit <bas@baslab.org> - Revert "Signed types (#834)" (6613a14) by Daniel Xu <dxu@dxuuu.xyz> - Signed types (#834) (446facb) by bas smit <bas@baslab.org> - test: fix flaky 32-bit tp runtime test (c0d94c8) by Matheus Marchini <mat@mmarchini.me> - travis: use bionic and enable runtime tests (57c5a55) by Matheus Marchini <mat@mmarchini.me> - test: print bpftrace script when codegen test fails (b0c4902) by Matheus Marchini <mat@mmarchini.me> - tests: add test for cat with fmt str (#842) (b3143a6) by Matheus Marchini <mat@mmarchini.me> - Fix tests (#844) (fd0ec92) by bas smit <bas@baslab.org> ## [0.9.2] 2019-07-31 ### Highlights - New environment variables (BPFTRACE_NO_USER_SYMBOLS, BPFTRACE_LOG_SIZE) - New probe type: memory `watchpoint` - Support for JSON output ### All Changes #### Added - Add vargs support for cat() builtin (similar to system) (7f1aa7b) by Augusto Caringi <acaringi@redhat.com> - Add memory watchpoint probe type (#790) (854cd4b) by Dan Xu <dxu@dxuuu.xyz> - Add support for Go symbol names to uaddr (#805) (e6eb3dd) by Jason Keene <jasonkeene@gmail.com> - add option for JSON output (5c6f20a) by Andreas Gerstmayr <andreas@gerstmayr.me> - Add $# for number of positional arguments (ec8b61a) by Mark Drayton <mdrayton@gmail.com> - Add BPFTRACE_NO_USER_SYMBOLS environment variable (#800) (41d2c9f) by Dan Xu <dxu@dxuuu.xyz> - Add line numbers to parser error messages (a584752, 2233ea7) by bas smit <bas@baslab.org> - Add new environment variable BPFTRACE_LOG_SIZE (2f7dc75, 7de1e84, 2f7dc75) by Ray Jenkins <ray.jenkins@segment.com> #### Changed - Terminate when map creation fails (6936ca6) by bas smit <bas@baslab.org> - Print more descriptive error message on uprobe stat failure (0737ec8) by Dan Xu <dxu@dxuuu.xyz> - Allow '#' in attach point path (2dfbc93) by Dan Xu <dxu@dxuuu.xyz> - Disable `func`, `retval` and `reg` for tracepoints since tracepoints can't access this information (7bfc0f8) by bas smit <bas@baslab.org> #### Fixed - Skip keys which were removed during iteration on `print` (bfd1c07) by Andreas Gerstmayr <agerstmayr@redhat.com> - Fix exiting prematurely on strace attach (a584752..0e97b2c) by Jay Kamat <jaygkamat@gmail.com> - Fix unused variable warnings (9d07eb5) by Daniel Xu <dxu@dxuuu.xyz> - Fix alignment issues on `ntop` (2006424) by Matheus Marchini <mat@mmarchini.me> - Fix BEGIN being triggered multiple times when bpftrace is run a second time (14bc835) by bas smit <bas@baslab.org> - Fix crash when using $0 (b41d66d) by Alastair Robertson <alastair@ajor.co.uk> - Fix tcp tools printing errors (206b36c) by bas smit <bas@baslab.org> #### Documentation - Update Ubuntu install instructions (4e3ffc3) by Brendan Gregg <bgregg@netflix.com> - Clarify help message for `-o` (d6e9478) by Daniel Xu <dxu@dxuuu.xyz> - `opensnoop.bt` was incorrectly linked to load.bt (d74fae0) by southpawflo <16946610+southpawflo@users.noreply.github.com> - Document multiple attach points for probes (21bc5bf) by Daniel Xu <dxu@dxuuu.xyz> - Fix incorrect reference to the `probe` key (83d473c) by Jeremy Baumont <jeremy.baumont@gmail.com> #### Internal - Fix failing test (086c018) by bas smit <bas@baslab.org> - Collapse bcc symbol resolvers by process executable (63ff8b0) by Daniel Xu <dxu@dxuuu.xyz> - Remove unneeded probe read (7d0aa99) by bas smit <bas@baslab.org> - Fix runtime test parser to not break with commented out tests (#824) (b73c963) by Augusto Mecking Caringi <acaringi@redhat.com> - bpftrace: optimize resolve_kname (#765) (ec5278d) by Matheus Marchini <mat@mmarchini.me> - Resolve symbol names using bcc_elf_foreach_sym (#811) (a2d9298) by Jason Keene <jasonkeene@gmail.com> - Add basic editorconfig for defining style (#775) (5b20829) by Jay Kamat <jaygkamat@gmail.com> - Auto-generate list of includes for codegen tests (e3b8ecd) by Alastair Robertson <alastair@ajor.co.uk> - Do not emit GEP instruction when pushing string literals to stack (#667) (e98530c) by Michał Gregorczyk <michalgr@users.noreply.github.com> - tool style tweaks (8bb0940) by Brendan Gregg <bgregg@netflix.com> - Clean up unused variable (#787) (8627e84) by Dan Xu <dxu@dxuuu.xyz> - Make member variables end with underscores (c76a8e4) by Alastair Robertson <alastair@ajor.co.uk> - Fail in case there's unresolved type in definitions (ecb7a1b, 2239756, a6a4fb3) by Jiri Olsa <jolsa@kernel.org> ## [0.9.1] 2019-06-25 ### Highlights - Introduce compound assignment operators (`+=` and friends) (7f26468) by Matheus Marchini <mat@mmarchini.me> - Add support for arrays and IPv6 for the `ntop` builtin function (c9dd10f) by Matheus Marchini <mat@mmarchini.me> - Add basic support to enums (treat them as constants) (e4cb6ce) by Matheus Marchini <mat@mmarchini.me> - Add macro definition support (8826470,af67b56,14e892b) by Matheus Marchini <mat@mmarchini.me>, Javier Honduvilla Coto <javierhonduco@gmail.com> - Add support for arrays and IPv6 for the `ntop` builtin function (c9dd10f) by Matheus Marchini <mat@mmarchini.me> - Allow comparison of two string variables (7c8e8ed) by williangaspar <williangaspar360@gmail.com> - Add pre and post behavior to ++ and -- operators (f2e1345...9fea147) by Alastair Robertson <alastair@ajor.co.uk> - [**BREAKING CHANGE**] Ban kprobes that cause CPU deadlocks (40cf190) by Javier Honduvilla Coto <javierhonduco@gmail.com> - [**BREAKING CHANGE**] Add unsafe-mode and make default execution mode safe-mode (981c3cf,4ce68cd) by Daniel Xu <dxu@dxuuu.xyz> ### All Changes #### Added - Introduce compound assignment operators (`+=` and friends) (7f26468) by Matheus Marchini <mat@mmarchini.me> - Add KBUILD_MODNAME (a540fba) by Brendan Gregg <bgregg@netflix.com> - Add flags for include paths and files (`--include` and `-I`, respectively) (632652f) by Matheus Marchini <mat@mmarchini.me> - List uprobes with -l (122ef6e) by Matheus Marchini <mat@mmarchini.me> - Add BPFTRACE_MAX_PROBES environment variable (ddb79df) by Matheus Marchini <mat@mmarchini.me> - Add option to redirect trace output to file (462a811) by bas smit <bas@baslab.org> - Add script to check kernel requirements (ac19743) by bas smit <bas@baslab.org> - Add USDT wildcard matching support (82dbe4e...3725edf,648a65a) by Dale Hamel <dale.hamel@srvthe.net> - Add support for arrays and IPv6 for the `ntop` builtin function (c9dd10f,24a463f) by Matheus Marchini <mat@mmarchini.me> - Add 'cat' builtin (ae1cfc9,ef9baf8) by Augusto Caringi <acaringi@redhat.com> - Add array indexing operator [] for one-dimensional, constant arrays (ec664a1) by Dale Hamel <dalehamel@users.noreply.github.com> - Allow dots to truncate fields in `printf` (0f636c9) by Brendan Gregg <bgregg@netflix.com> - Add `BPFTRACE_MAP_KEYS_MAX` environment variable, and increase default map keys limit to 4096 (fab8bf6) by Brendan Gregg <bgregg@netflix.com> - Add support for delimiters in join() statement (eb40386) by Jason Koch <jkoch@netflix.com> - Add basic support to enums (treat them as constants) (e4cb6ce) by Matheus Marchini <mat@mmarchini.me> - Add macro definition support (8826470,af67b56,14e892b) by Matheus Marchini <mat@mmarchini.me>, Javier Honduvilla Coto <javierhonduco@gmail.com> - Add hardware:branch-misses (9631623) by Jason Koch <jkoch@netflix.com> - Allow comparison of two string variables (7c8e8ed) by williangaspar <williangaspar360@gmail.com> #### Changed - Add pre and post behavior to ++ and -- operators (f2e1345...9fea147) by Alastair Robertson <alastair@ajor.co.uk> - Parse negative integer literals correctly (108068f) by Daniel Xu <dxu@dxuuu.xyz> - Tools improvements (9dbee04,a189c36) by Brendan Gregg <bgregg@netflix.com> - USAGE message trim (18d63b0) by Brendan Gregg <bgregg@netflix.com> - Allow `probe` builtin for `BEGIN` and `END` probes (3741efe) by bas smit <bas@baslab.org> - Default -d and -dd output to stdout (ecea569) by Jay Kamat <jaygkamat@gmail.com> - Return with error code if clang finds an error while parsing structs/enums/macros/includes (364849d) by Matheus Marchini <mat@mmarchini.me> - Restore map key validation (7826ee3) by Alastair Robertson <alastair@ajor.co.uk> - Add `/usr/include` to default header search path (32dd14b) by Javier Honduvilla Coto <javierhonduco@gmail.com> - More information in error message when failing to open script file (3b06e5f) by Alastair Robertson <alastair@ajor.co.uk> - [**BREAKING CHANGE**] Add unsafe-mode and make default execution mode safe-mode (981c3cf,4ce68cd) by Daniel Xu <dxu@dxuuu.xyz> - Safety measure for LLVM out of memory issue (6b53e4a) by Brendan Gregg <bgregg@netflix.com> - Allow non-zero lhist min value (51fdb6a) by bas smit <bas@baslab.org> - Improvements in startup speed (5ed8717,1ffb50f) by Matheus Marchini <mat@mmarchini.me> - When using -c, spawn the child process only when the tracing is ready (e442e9d) by Jiri Olsa <jolsa@kernel.org> - Allow more pointers as ints (3abc93e) by Brendan Gregg <bgregg@netflix.com> - Validate that PID (received via `-p`) is an integer (48206ad) by Javier Honduvilla Coto <javierhonduco@gmail.com> - Promote map keys to 64-bit (e06e39d) by Brendan Gregg <bgregg@netflix.com> - Add hint when traced PID is not running (9edb3e1) by Javier Honduvilla Coto <javierhonduco@gmail.com> - Allow pointers in printf, mapkeys, and filters (0202412,280f1c6) by Brendan Gregg <bgregg@netflix.com> - Allow ksym() lookups on function pointers (2139d46) by Brendan Gregg <bgregg@netflix.com> - [**BREAKING CHANGE**] Ban kprobes that cause CPU deadlocks (40cf190) by Javier Honduvilla Coto <javierhonduco@gmail.com> #### Fixed - Workaround for asm goto in Kernel 5+ headers (60263e1) by Matheus Marchini <mat@mmarchini.me> - Properly handle invalid `args` utilization (13c2e2e) by Augusto Caringi <acaringi@redhat.com> - Fix abort caused by lhist with incorrect number of arguments (41036b9) by bas smit <bas@baslab.org> - Fix anonymous struct parsing (ea63e8b) by Alastair Robertson <alastair@ajor.co.uk> - Fix code generation for bitwise and logical not on integer values (f522296) by synth0 <synthkaf@outlook.com> - Fix typo in type mismatch error message (83924f8) by Jay Kamat <jaygkamat@gmail.com> - Fix clearing action for some aggregations (dcd657e) by Javier Honduvilla Coto <javierhonduco@gmail.com> - Fix possible crash if an invalid char is used in search (c4c6894) by Augusto Caringi <acaringi@redhat.com> - Fix headers includes by using -isystem rather than -I (32daaa2) by Javier Honduvilla Coto <javierhonduco@gmail.com> - Fix exit() function bypassing END probe processing #228 (f63e1df,e4c418e,5cce746) by Augusto Caringi <acaringi@redhat.com> - Fix order in which probes fire (a4bf870) by John Gallagher <john.gallagher@delphix.com> - Stop throwing 'failed to initialize usdt context for path' error message (1fa3d3c) by Augusto Caringi <acaringi@redhat.com> - Fix stringification of ntop keys in maps (598050e) by Matheus Marchini <mat@mmarchini.me> - Fix parsing of forward-decl structs inside structs (354c919) by Matheus Marchini <mat@mmarchini.me> - Fix struct definition from headers (4564d55) by Matheus Marchini <mat@mmarchini.me> - Avoid crash if incorrect command line option is used (aa24f29) by Augusto Caringi <acaringi@redhat.com> - Fix clang_parser for LLVM 8+ (80ce138) by Matheus Marchini <mat@mmarchini.me> - Fix semicolon being required in some cases after if statements (13de974) by Matheus Marchini <mat@mmarchini.me> - Throw error message if argN or retval is used with incorrect probe type (b40354c) by Augusto Caringi <acaringi@redhat.com> - Fix USDT listing (`-l`) without a search pattern (af01fac) by Javier Honduvilla Coto <javierhonduco@gmail.com> - Add missing space to error message (e1f5f14) by Alastair Robertson <alastair@ajor.co.uk> - Fix unroll in some cases (mostly when the generated code was large) (702145c) by Matheus Marchini <mat@mmarchini.me> #### Documentation - Added info on clang environment variables (7676530) by Richard Elling <Richard.Elling@RichardElling.com> - Fix snap instructions. (3877e46) by George Slavin <george.r.slavin@gmail.com> - Fix ustack documentation (5eeeb10) by Daniel Xu <dxu@dxuuu.xyz> - Replace stack with kstack (49e01e0) by Javier Honduvilla Coto <javierhonduco@gmail.com> - Fix TOC in the reference guide (05eb170) by Alastair Robertson <alastair@ajor.co.uk> - Fix broken links in docs (c215c61,845f9b6) by Daniel Xu <dxu@dxuuu.xyz> - Fix inaccurate tutorial on listing (a4aeaa5) by Daniel Xu <dxu@dxuuu.xyz> - Add documentation for BEGIN/END probes (81de93a) by Daniel Xu <dxu@dxuuu.xyz> - Update build instructions for Ubuntu (38b9620) by bas smit <bas@baslab.org> - INSTALL.md: update required dependency for usdt (5fc438e) by Zi Shen Lim <zlim.lnx@gmail.com> - Fix ++ and -- text on undefined variables (47ab5cd) by Matheus Marchini <mat@mmarchini.me> - Reference guide small fixes (0d9c1a4) by Augusto Caringi <acaringi@redhat.com> - Add instructions to install on Gentoo (3c23187) by Patrick McLean <chutzpah@gentoo.org> - Add install instructions for Ubuntu snap package (0982bb6) by George Slavin <george.r.slavin@gmail.com> - Fix spelling mistake (a45869f) by George Slavin <george.r.slavin@gmail.com> - Fix 'one liners tutorial': use 'openat' instead of 'open' in examples (0cce55c) by Augusto Caringi <acaringi@redhat.com> - Add contributing section to the README (2a08468) by Alastair Robertson <alastair@ajor.co.uk> - Standardise documentation on the bpftrace name (135a4d3) by Alastair Robertson <alastair@ajor.co.uk> - Update install instructions (505b50a) by Alastair Robertson <alastair@ajor.co.uk> #### Internal - [tests] add missing tests to codegen.cpp (012ebda) by Matheus Marchini <mat@mmarchini.me> - tests: add runtime tests for regression bugs (ee57b6f) by Matheus Marchini <mat@mmarchini.me> - vagrant: add Ubuntu 19.04 box (60e6d0a) by Matheus Marchini <mat@mmarchini.me> - docker: add Fedora 30 (9ccafa0) by Zi Shen Lim <zlim.lnx@gmail.com> - Add Vagrantfile for ubuntu (b221f79) by bas smit <bas@baslab.org> - tests: fix and improve runtime tests (c7b3b2f) by Matheus Marchini <mat@mmarchini.me> - Clean up includes in clang_parser (374c240) by Daniel Xu <dxu@dxuuu.xyz> - Remove double `check_nargs` call (c226c10) by bas smit <bas@baslab.org> - Fix call.system runtime test (3b4f578) by Daniel Xu <dxu@dxuuu.xyz> - Fix call.str runtime test (8afbc22) by Daniel Xu <dxu@dxuuu.xyz> - Fix k[ret]probe_order runtime tests (27a334c) by Daniel Xu <dxu@dxuuu.xyz> - Remove old TODO (5be3752) by Alastair Robertson <alastair@ajor.co.uk> - Add clang_parser::parse_fail test (6fd7aac) by Jiri Olsa <jolsa@kernel.org> - Fix some bugs with positional parameters (13fb175) by Alastair Robertson <alastair@ajor.co.uk> - Fix runtime tests (a05ee59) by bas smit <bas@baslab.org> - Enable multiline matching for runtime test regex (c8763e4) by bas smit <bas@baslab.org> - Add environment var support to runtime tests (543513e) by bas smit <bas@baslab.org> - Disable codegen.printf_offsets test for LLVM5 CI build (ea8a7e4) by Alastair Robertson <alastair@ajor.co.uk> - Fix LLVM 5 tests (938e79b) by Alastair Robertson <alastair@ajor.co.uk> - Refactor find_wildcard_matches() to allow for proper testing (371c7cf) by Alastair Robertson <alastair@ajor.co.uk> - tests: Use Python 3 for integration tests + test fix (#651) (4b0e477) by Javier Honduvilla Coto <javierhonduco@gmail.com> - Add --unsafe to more runtime tests (8b2234a) by Daniel Xu <dxu@dxuuu.xyz> - Fix 'ignoring return value' build warning (bdc9f16) by Augusto Caringi <acaringi@redhat.com> - Fix 'signed overflow' related build warning (0ece2a9) by Augusto Caringi <acaringi@redhat.com> - Fix UnboundLocalError on skipped test (03958cb) by John Gallagher <john.gallagher@delphix.com> - Use getopt_long instead of getopt (d732298) by Daniel Xu <dxu@dxuuu.xyz> - Fix docs typo (05bf095) by bas smit <bas@baslab.org> - check explicitly for systemtap sys/sdt.h and ignore if not present (831633d) by Jason Koch <jkoch@netflix.com> - Suppress build warning in GCC >=8 caused by #474 (71d1cd5) by Augusto Caringi <acaringi@redhat.com> - Remove more tabs (e9594dd) by Alastair Robertson <alastair@ajor.co.uk> - Convert tabs to spaces (585e8b5) by Alastair Robertson <alastair@ajor.co.uk> - Add existence tests for kstack, kstack() and ustack() (954d93d) by Alastair Robertson <alastair@ajor.co.uk> - [tests] more runtime tests enhancements (#586) (249c7a1) by Matheus Marchini <mat@mmarchini.me> - Codegen: Fix assigning non-struct "internal" values to maps (4020a5c) by Alastair Robertson <alastair@ajor.co.uk> - Fix typo on LLVM_REQUESTED_VERSION macro in CMakeLists.txt (82dbe4e) by Quentin Monnet <quentin.monnet@netronome.com> - Fix build warning (a77becb) by Alastair Robertson <alastair@ajor.co.uk> - [tests] allow tests to be skipped if a given condition is not met (59fa32a) by Matheus Marchini <mat@mmarchini.me> - [tests] make other.if_compare_and_print_string less flaky (840bbb3) by Matheus Marchini <mat@mmarchini.me> - Fix compile warnings and mark more functions as const (cfb058d) by Alastair Robertson <alastair@ajor.co.uk> - Misc readability fixes (9581e01) by Fangrui Song <i@maskray.me> - build: unify dockerfiles under a bionic image (445fb61) by Matheus Marchini <mat@mmarchini.me> - [tests] fix and enhance runtime tests (ea5deb9) by Matheus Marchini <mat@mmarchini.me> - [tests] add test script to run tools with -d (4ff113d) by Matheus Marchini <mat@mmarchini.me> - [clang_parser] decouple kernel cflags from the parser method (ad753d5) by Matheus Marchini <mat@mmarchini.me> - Address TODO items related to objdump dependency (382b9b7) by Adam Jensen <acjensen@gmail.com> - Fall back to objdump/grep if bcc is older (fdd02ec) by Adam Jensen <acjensen@gmail.com> - [clang_parser] pass BPFtrace as arg instead of StructMap (a0af75f) by Matheus Marchini <mat@mmarchini.me> - [ast] introduce Identifier type to AST (389d55f) by Matheus Marchini <mat@mmarchini.me> - use CMAKE_SYSTEM_PROCESSOR when selecting whether to include x86_64 or aarch64 sources (0ea7a63) by Michał Gregorczyk <michalgr@fb.com> - Clearify error message for mismatched llvm. (9b77fee) by George Slavin <george.r.slavin@gmail.com> - Add more info to LLVM mismatch error message (1e3b1be) by George Slavin <george.r.slavin@gmail.com> - Allow 0 as kernel version during first attempt to call bcc_prog_load (13499ac) by Michał Gregorczyk <michalgr@fb.com> - Fix bpftrace_VERSION_MINOR in CMakeLists.txt (8 -> 9) (13321eb) by Matheus Marchini <mat@mmarchini.me> - Fix version information when not building inside a git repo (#489) (1f33126) by Augusto Caringi <acaringi@redhat.com> - Do not try to load bpf program with unknown kernel version (2c00b7f) by Michał Gregorczyk <michalgr@fb.com> - Add better checks for llvm version (4fe081e) by George Slavin <george.r.slavin@gmail.com> - Fix deprecated stack warning in builtin_stack test (a1aaed8) by George Slavin <george.r.slavin@gmail.com> - add test for 32-bit tp args (77f7cb7) by Brendan Gregg <bgregg@netflix.com> - tests: add some basic integration tests (e9805af) by Javier Honduvilla Coto <javierhonduco@gmail.com> - Fix and simplify lexer.l (57bae63) by Fangrui Song <i@maskray.me> - Fix 2 clang warnings: -Wmismatched-tags and -Wpessimizing-move (18da040) by Fangrui Song <i@maskray.me> - Revert "Stop linking against bcc-loader-static" (5b6352c) by Alastair Robertson <alastair@ajor.co.uk> - fix typo on BPF_FUNC_get_current_cgroup_id missing message (27371c3) by Jason Koch <jkoch@netflix.com> - propagate HAVE_GET_CURRENT_CGROUP_ID to ast modules (57e30da) by Jason Koch <jkoch@netflix.com> - Add missing include (5763dc2) by Michał Gregorczyk <michalgr@fb.com> - No need for `if` when we're not doing anything (a65ad14) by Alastair Robertson <alastair@ajor.co.uk> - Make indirect* related data static (24d9dd2) by Jiri Olsa <jolsa@kernel.org> - Fix issues, add tests and improve reliability of positional parameters (acec163,f2e1345) by Matheus Marchini <mat@mmarchini.me> ## [0.9.0] 2019-03-16 ### Deprecated - **Deprecate `sym()`**. Use `ksym()` instead (50a66d2) by williangaspar - **Deprecate `stack`**. Use `kstack` instead (e8b99cd) by williangaspar ### Added - List usdt probes with -l (fa7d5f3) by Timothy J Fontaine - Introduce perf formatting for ustack()/kstack() (db910b9) by Matheus Marchini - Add increment and decrement operators (++/--) (c8d8a08, 6aa66a1, 223d8d8, 1f82aaf, 8c5c4ea) by Dale Hamel - Add changelog file to keep track of unreleased changes (d11fb01) by Matheus Marchini - Allow args with multiple tracepoints (a0a905f, 2df50d3, cddae1a) by Brendan Gregg - Add elapsed builtin (0fde181) by Brendan Gregg - Add support to demangle C++ symbols in userspace stack traces (872525c) by Augusto Caringi - allow \r (e7f0584) by Brendan Gregg - Use debuginfo files information when available (1132d42) by Augusto Caringi - Add ustack([int limit])/kstack([int limit]) calls (08da997) by Matheus Marchini - Allow custom provider name in USDT probe definition (361245c, 80d640a, 20ddfed, c3a6ff1) by Dale Hamel - Detect kernel headers even if they are splitted into source/ and build/ directories (4d76385) by Kirill Smelkov - Add support for arm64 (aarch64) (47fa8aa) by Ali Saidi - Allow customizing stdout buffering mode via -b (1663b84) by Hongli Lai (Phusion) - Add support to list tracepoint arguments (#323) (4a048fc) by Augusto Caringi - Add `ksym` as a replacement for `sym` (50a66d2) by williangaspar - Add `kstack` as a replacement for `stack` (e8b99cd, 840712b, f8f7ceb,6ec9a02) by williangaspar - cmake: add BUILD_TESTING support (a56ab12) by Aleksa Sarai - Add --version (61a4650, eab3675) by williangaspar - Add hint to install docs and normalize format (c0084a2) by Daniel Xu - Make bpftrace -l list sofware and hardware types (#44) (acd9a80) by Augusto Caringi - Print program ID when the verbose option is enabled. (8e8258d) by David Calavera ### Changed - Use `struct` when casting on docs and tools (e2ba048) by Brendan Gregg - Allow using the `struct` keyword when casting (df03256) by williangaspar - Make path optional on usdts when attaching to pid (c1c7c83) by Timothy J Fontaine - Resolve binary name from PATH for usdts and uprobes (28f0834) by Matheus Marchini - Use map lookups instead of sequential checks in tcpdrop.bt and tcpretrans.bt (cb0969c) by Slavomir Kaslev - Implicitly declare variables to 0 if used but not defined (a408cc2) by Matheus Marchini - Sort all integer maps by values, ascending (c378f57) by Dale Hamel - Change Ubuntu install to LLVM 6.0 (98353bf) by Brendan Gregg - ignore EFAULT stack IDs (f080bbf) by Brendan Gregg - Usage updates (6de4101) by Brendan Gregg - make map stack indentation 4 chars (c1dd418) by Brendan Gregg - Print error messages on all `abort()` calls (5c2ca5b) by williangaspar - Lesson 9: Replace "stack" to "kstack" (1ac56bd) by CavemanWork - Use structs with semicolons in tools and documentation (85dba93) by Brendan Gregg - Allow semicolon after struct definition (5982c74) by williangaspar - remove unnecessary newlines in -l (bb4a83c) by Brendan Gregg - list sw/hw probes with full names (6f3e1c4) by Brendan Gregg - hist: split negative, zero, and one into separate buckets (48c0afb) by Brendan Gregg - lhist: interval notation tweak (43e7974) by Brendan Gregg - runqlat.bt: remove if semicolon (c10c0dc) by Brendan Gregg - Probe list optimizations and improvements (7f84552) by Augusto Caringi - Link against system installed bcc (#327) (4c3fbad) by Dan Xu - Make semicolon optional after if and unroll blocks (d74d403) by williangaspar - Avoid crashing if mistakenly just '-d' or '-v' is used (f2f6732) by Augusto Caringi - Return cleanly after printing help (1d41717) by Daniel Xu ### Fixed - Make sure we create map keys when we have all the typing information (971bd77) by Matheus Marchini - Fix for new bpf_attach_kprobe signature (080bef8) by Matheus Marchini - Fix string comparison improperly deallocating variables (ffa173a) by williangaspar - Fix probe keys on maps when the map is used more than one time (df81736) by Matheus Marchini - Fix using same variable name on multiple programs (61a14f2) by williangaspar - Fix build on old compilers (644943a, 1b69272) by Kirill Smelkov - Fix build with latest bcc (d64b36a) by williangaspar - Don't throw warning for undefined types in tracepoint structure definition if `args` is not used (f2ebe1a) by Matheus Marchini - Fix for 'redefinition of tracepoint' warning message (baaeade) by Augusto Caringi - Minor fixes in our documentation (0667533) by Matheus Marchini - Fix string comparison (5e114dd, 63acdb6) by williangaspar - Prevent empty trigger functions to be optimized away with -O2 (#218) (9f2069b) by Augusto Caringi - Fix -l behavior with shortcut probe names (2d30e31) by williangaspar - Fix alpine docker build (#372) (2b83b67) by Dan Xu - Fix tracepoint wildcards (946c785) by Brendan Gregg - tests: fix codegen test fot call_hist (342fd6d) by Matheus Marchini - docs: fix trivial typos (3da1980) by Xiaozhou Liu - Fix symbol translation for func, sym, and stack (6276fb5) by Brendan Gregg - Fix wrong package name in Ubuntu Dockerfile (f8e67a9) by xbe - Fix wrong package name in build instructions (8e597de) by Daniel Xu - Fix arguments and error messages for tracepoint shortcut `t` (0eddba7) by williangaspar ### Internal - Fix 'different signedness' warning messages in codegen call_[uk]stack.cpp (cb25318) by Augusto Caringi - Fix 'signedness' warning message in tracepoint_format_parser.cpp (c3e562f) by Augusto Caringi - Stop linking against bcc-loader-static (5fbb7a7) by Daniel Xu - Speeding up runtime tests (60c5d96) by williangaspar - docker: make sure debugfs is mounted (7dcfc47) by Zi Shen Lim - Better coverage for variable_clear() (34fdded) by williangaspar - Add missing space (c65e7c1) by puyuegang - Ignore warnings on code generated by bison (a935942) by Matheus Marchini - Ignore warnings from LLVM headers (b6c4fd6) by Matheus Marchini - Downgrade back to c++14 (f6986d8) by Matheus Marchini - Fix 'parameter not used' warning (2401ab3) by Matheus Marchini - Fix new build warning msg after c++17 was enabled (e4cbe48) by Augusto Caringi - Get rid of cmake CMP0075 policy warning (9b8208a) by Augusto Caringi - Use C++17 instead of C++14 (4b4d5dc) by Alex Birch - Re-enable more build warnings, fix related warnings #316 (8c383dc) by Augusto Caringi - Define `__BPF_TRACING__` before building (required for kernel 4.19+) (e0bf01d) by Kirill Smelkov - Re-enable subset of build warnings and fix some related warnings #316 (f0f56b0) by Augusto Caringi - Cleanup enforce_infinite_rmlimits : removed getrlimit() : Added error description using strerror() (d76465f) by T K Sourab - use the new libbcc API: bcc_{create_map, prog_load} when possible (c03c39f) by Xiaozhou Liu - resources: generate c++ file instead of c file (5e1350b) by Matheus Marchini - docker: disable runtime tests on CI (0667b92) by Matheus Marchini - Hide -inl.h header from interface (10a43d0) by Daniel Xu ## [0.8.0] - 2019-01-06 This is a release to aid packaging. bpftrace has not reached a 1.0 release status yet, as there are still development changes and things to fix. But what is here should be tremendously useful, provided you bear in mind that there will be some changes made to the programming language and command line options between now and a 1.0 release, so any tools or documentation written will become out of date and require changes. If you are anxiously waiting a 1.0 release, please consider contributing so that it can be released sooner. bpftrace-0.9.4/CMakeLists.txt000066400000000000000000000130371361633214400160610ustar00rootroot00000000000000cmake_minimum_required(VERSION 2.8.12) project(bpftrace) # bpftrace version number components. set(bpftrace_VERSION_MAJOR 0) set(bpftrace_VERSION_MINOR 9) set(bpftrace_VERSION_PATCH 4) set(WARNINGS_AS_ERRORS OFF CACHE BOOL "Build with -Werror") set(STATIC_LINKING OFF CACHE BOOL "Build bpftrace as a statically linked executable") set(STATIC_LIBC OFF CACHE BOOL "Attempt to embed libc, only known to work with musl. Has issues with dlopen.") set(EMBED_LLVM OFF CACHE BOOL "Build LLVM static libs as an ExternalProject and link to these instead of system libs.") set(EMBED_CLANG OFF CACHE BOOL "Build Clang static libs as an ExternalProject and link to these instead of system libs.") set(EMBED_LIBCLANG_ONLY OFF CACHE BOOL "Build only libclang.a, and link to system libraries statically") set(LLVM_VERSION "8" CACHE STRING "Embedded LLVM/Clang version to build and link against.") set(BUILD_ASAN OFF CACHE BOOL "Build bpftrace with -fsanitize=address") set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) if(EMBED_LLVM OR EMBED_CLANG) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake/embed) include(embed_helpers) if (NOT STATIC_LINKING) set(CONFIG_ERROR "Dependencies can only be embedded for a static build.\n" "Enable STATIC_LINKING=ON to embed static libs.") message(FATAL_ERROR ${CONFIG_ERROR}) elseif(STATIC_LIBC) message(WARNING "static libc is known to cause problems, consider STATIC_LIBC=OFF. Proceed at your own risk") #iovisor/bpftrace/issues/266 endif() endif() if(EMBED_LIBCLANG_ONLY AND NOT EMBED_CLANG) message(FATAL_ERROR "Cannot set EMBED_LIBCLANG_ONLY without also setting EMBED_CLANG") endif() set (CMAKE_CXX_STANDARD 14) set (CMAKE_CXX_STANDARD_REQUIRED ON) set (CMAKE_CXX_EXTENSIONS OFF) add_compile_options("-Wall") add_compile_options("-Wextra") add_compile_options("-Wundef") add_compile_options("-Wpointer-arith") add_compile_options("-Wcast-align") add_compile_options("-Wwrite-strings") add_compile_options("-Wcast-qual") add_compile_options("-Wswitch-default") #add_compile_options("-Wswitch-enum") #add_compile_options("-Wconversion") add_compile_options("-Wunreachable-code") #add_compile_options("-Wformat=2") add_compile_options("-Wdisabled-optimization") if (WARNINGS_AS_ERRORS) add_compile_options("-Werror") endif() include(CTest) if(STATIC_LINKING) if(STATIC_LIBC) set(CMAKE_EXE_LINKER_FLAGS "-static") elseif(EMBED_LLVM OR EMBED_CLANG) set(EMBEDDED_LINK_FLAGS "-static-libgcc -static-libstdc++ -Wl,--start-group") set(EMBEDDED_LINK_FLAGS "${EMBEDDED_LINK_FLAGS} -Wl,-Bdynamic -lrt") set(EMBEDDED_LINK_FLAGS "${EMBEDDED_LINK_FLAGS} -Wl,-Bdynamic -lpthread") set(EMBEDDED_LINK_FLAGS "${EMBEDDED_LINK_FLAGS} -Wl,-Bdynamic -ldl -lm") set(EMBEDDED_LINK_FLAGS "${EMBEDDED_LINK_FLAGS} -Wl,-Bstatic -lz") endif(STATIC_LIBC) set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") set(CMAKE_LINK_SEARCH_START_STATIC TRUE) set(CMAKE_LINK_SEARCH_END_STATIC TRUE) endif(STATIC_LINKING) set(FIND_LIBRARY_USE_LIB64_PATHS TRUE) find_package(LibBcc REQUIRED) include_directories(SYSTEM ${LIBBCC_INCLUDE_DIRS}) find_package(LibElf REQUIRED) include_directories(SYSTEM ${LIBELF_INCLUDE_DIRS}) find_package(BISON REQUIRED) find_package(FLEX REQUIRED) bison_target(bison_parser src/parser.yy ${CMAKE_BINARY_DIR}/parser.tab.cc VERBOSE) flex_target(flex_lexer src/lexer.l ${CMAKE_BINARY_DIR}/lex.yy.cc) add_flex_bison_dependency(flex_lexer bison_parser) add_library(parser ${BISON_bison_parser_OUTPUTS} ${FLEX_flex_lexer_OUTPUTS}) target_compile_options(parser PRIVATE "-w") target_include_directories(parser PUBLIC src src/ast ${CMAKE_BINARY_DIR}) include(CheckSymbolExists) set(CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE) check_symbol_exists(name_to_handle_at "sys/types.h;sys/stat.h;fcntl.h" HAVE_NAME_TO_HANDLE_AT) set(CMAKE_REQUIRED_DEFINITIONS) if(POLICY CMP0075) cmake_policy(SET CMP0075 NEW) endif() set(CMAKE_REQUIRED_LIBRARIES bcc) get_filename_component(LIBBCC_LIBDIR ${LIBBCC_LIBRARIES} DIRECTORY) set(CMAKE_REQUIRED_LINK_OPTIONS -L${LIBBCC_LIBDIR}) check_symbol_exists(bcc_prog_load "${LIBBCC_INCLUDE_DIRS}/libbpf.h" HAVE_BCC_PROG_LOAD) check_symbol_exists(bcc_create_map "${LIBBCC_INCLUDE_DIRS}/libbpf.h" HAVE_BCC_CREATE_MAP) check_symbol_exists(bcc_elf_foreach_sym "${LIBBCC_INCLUDE_DIRS}/bcc_elf.h" HAVE_BCC_ELF_FOREACH_SYM) set(CMAKE_REQUIRED_LIBRARIES) set(CMAKE_REQUIRED_LINK_OPTIONS) find_package(LibBpf) find_package(LibBfd) find_package(LibOpcodes) if(${LIBBFD_FOUND} AND ${LIBOPCODES_FOUND}) set(HAVE_BFD_DISASM TRUE) endif() include(CheckIncludeFile) check_include_file("sys/sdt.h" HAVE_SYSTEMTAP_SYS_SDT_H) if (EMBED_LLVM) include(embed_llvm) else() # Some users have multiple versions of llvm installed and would like to specify # a specific llvm version. if(${LLVM_REQUESTED_VERSION}) find_package(LLVM ${LLVM_REQUESTED_VERSION} REQUIRED) else() find_package(LLVM REQUIRED) endif() if(${LLVM_VERSION_MAJOR} VERSION_LESS 5) message(SEND_ERROR "Unsupported LLVM version found: ${LLVM_INCLUDE_DIRS}") message(SEND_ERROR "Specify an LLVM major version using LLVM_REQUESTED_VERSION=") endif() include_directories(SYSTEM ${LLVM_INCLUDE_DIRS}) add_definitions(${LLVM_DEFINITIONS}) endif() if(EMBED_CLANG) include(embed_clang) else() find_package(Clang REQUIRED) include_directories(SYSTEM ${CLANG_INCLUDE_DIRS}) endif() add_subdirectory(src/arch) add_subdirectory(src/ast) add_subdirectory(src) if (BUILD_TESTING) add_subdirectory(tests) endif() add_subdirectory(resources) add_subdirectory(tools) add_subdirectory(man) bpftrace-0.9.4/CONTRIBUTING-TOOLS.md000066400000000000000000000134061361633214400164500ustar00rootroot00000000000000# Contributing bpftrace/eBPF tools If you want to contribute tools to bpftrace, please read this checklist first. _(Written by Brendan Gregg. Adapted from the [bcc version](https://github.com/iovisor/bcc/blob/master/CONTRIBUTING-SCRIPTS.md))._ bpftrace tool development checklist: 1. **Research the topic landscape**. Learn the existing tools and metrics (incl. from /proc). Determine what real world problems exist and need solving. We have too many tools and metrics as it is, we don't need more "I guess that's useful" tools, we need more "ah-hah! I couldn't do this before!" tools. Consider asking other developers about your idea. Many of us can be found in IRC, in the #iovisor channel on irc.oftc.net. There's also the iovisor mailing list (see the README.md), and github for issues. 1. **Create a known workload for testing**. This might involving writing a 10 line C program, using a micro-benchmark, or just improvising at the shell. If you don't know how to create a workload, learn! Figuring this out will provide invaluable context and details that you may have otherwise overlooked. Sometimes it's easy, and I'm able to just use dd(1) from /dev/urandom or a disk device to /dev/null. It lets me set the I/O size, count, and provides throughput statistics for cross-checking my tool output. But other times I need a micro-benchmark, or some C. 1. **Write the tool to solve the problem and no more**. Unix philosophy: do one thing and do it well. netstat doesn't have an option to dump packets, tcpdump-style. They are two different tools. 1. **Consider bcc for custom output and options**. Need to really customize your output? Want to support a variety of command line options? It sounds like your tool may be better as a bcc tool, which currently supports these using Python (and other) interfaces [bcc](https://github.com/iovisor/bcc). 1. **Check your tool correctly measures your known workload**. If possible, run a prime number of events (eg, 23) and check that the numbers match. Try other workload variations. 1. **Use other observability tools to perform a cross-check or sanity check**. Eg, imagine you write a PCI bus tool that shows current throughput is 28 Gbytes/sec. How could you sanity test that? Well, what PCI devices are there? Disks and network cards? Measure their throughput (iostat, nicstat, sar), and check if is in the ballpark of 28 Gbytes/sec (which would include PCI frame overheads). Ideally, your numbers match. 1. **Measure the overhead of the tool**. If you are running a micro-benchmark, how much slower is it with the tool running. Is more CPU consumed? Try to determine the worst case: run the micro-benchmark so that CPU headroom is exhausted, and then run the bpftrace tool. Can overhead be lowered? 1. **Test again, and stress test**. You want to discover and fix all the bad things before others hit them. 1. **Consider your own repository**. Your tool does not need to be here! bpftrace makes it very easy to create new tools, perhaps too easy. As the previous items described, it's possible to create tools that print metrics that are incorrect, or cause too high overhead. Tools here will likely be run on production servers as root, at many companies, and we want to err on the side of caution. You can always create your own repository of bpftrace tools, and once they have had some exposure, testing, and bug fixes, consider contributing them here. 1. **Concise, intuitive, self-explanatory output**. The default output should meet the common need concisely. Consider including a startup message that's self-explanatory, eg "Tracing block I/O. Output every 1 seconds. Ctrl-C to end.". Also, try hard to keep the output less than 80 characters wide, especially the default output of the tool. That way, the output not only fits on the smallest reasonable terminal, it also fits well in slide decks, blog posts, articles, and printed material, all of which help education and adoption. Publishers of technical books often have templates they require books to conform to: it may not be an option to shrink or narrow the font to fit your output. 1. **Check style**: Do you have a consistent convention for indentation, variable names, and comment style? You can follow the lead from the other tools. 1. **Write an _example.txt file**. Copy the style in tools/biolatency_example.txt: start with an intro sentence, then have examples, and finish with the USAGE message. Explain everything: the first example should explain what we are seeing, even if this seems obvious. For some people it won't be obvious. Also explain why we are running the tool: what problems it's solving. It can take a long time (hours) to come up with good examples, but it's worth it. These will get copied around (eg, presentations, articles). 1. **Read your example.txt file**. Does this sound too niche or convoluted? Are you spending too much time explaining caveats? These can be hints that perhaps you should fix your tool, or abandon it! I've abandoned many tools at this stage. 1. **Write a man page**. Either ROFF (.8), markdown (.md), or plain text (.txt): so long as it documents the important sections, particularly columns (fields) and caveats. These go under man/man8. See the other examples. Include a section on overhead, and pull no punches. It's better for end users to know about high overhead beforehand, than to discover it the hard way. Also explain caveats. Don't assume those will be obvious to tool users. 1. **Read your man page**. For ROFF: nroff -man filename. Like before, this exercise is like saying something out loud. Does it sound too niche or convoluted? Again, hints that you might need to go back and fix things, or abandon it. 1. **Spell check your documentation**. Use a spell checker like aspell to check your document quality before committing. 1. **Add an entry to README.md**. 1. If you made it this far, pull request! bpftrace-0.9.4/INSTALL.md000066400000000000000000000210121361633214400147410ustar00rootroot00000000000000# bpftrace Install - [Linux Kernel Requirements](#linux-kernel-requirements) - [Package install](#package-install) - [Ubuntu](#ubuntu-packages) - [Fedora](#fedora-package) - [Gentoo](#gentoo-package) - [Debian](#debian-package) - [openSUSE](#openSUSE-package) - [CentOS](#CentOS-package) - [Building bpftrace](#building-bpftrace) - [Ubuntu](#ubuntu) - [Fedora](#fedora) - [Amazon Linux](#amazon-linux) - (*please add sections for other OSes)* - [Using Docker](#using-docker) - [Generic build](#generic-build-process) # Linux Kernel Requirements It is recommended that you are running a Linux 4.9 kernel or higher. Some tools may work on older kernels, but these old kernels are no longer tested. To explain this requirement, these are the kernel versions where major features were added: - 4.1 - kprobes - 4.3 - uprobes - 4.6 - stack traces, count and hist builtins (use PERCPU maps for accuracy and efficiency) - 4.7 - tracepoints - 4.9 - timers/profiling Minor improvements have been added in later kernels, so newer than 4.9 is preferred. Your kernel also needs to be built with the following options: ``` CONFIG_BPF=y CONFIG_BPF_SYSCALL=y CONFIG_BPF_JIT=y CONFIG_HAVE_EBPF_JIT=y CONFIG_BPF_EVENTS=y CONFIG_FTRACE_SYSCALLS=y ``` This can be verified by running the `check_kernel_features` script from the `scripts` directory. # Package install ## Ubuntu packages ``` sudo apt-get install -y bpftrace ``` Should work on Ubuntu 19.04 and later. On Ubuntu 16.04 and later, bpftrace is also available as a snap package (https://snapcraft.io/bpftrace), however, the snap provides extremely limited file permissions so the --devmode option should be specified on installation in order avoid file access issues. ``` sudo snap install --devmode bpftrace sudo snap connect bpftrace:system-trace ``` The snap package also currently has issues with uprobes ([#829](https://github.com/iovisor/bpftrace/issues/829)). ## Fedora package For Fedora 28 (and later), bpftrace is already included in the official repo. Just install the package with dnf. ``` sudo dnf install -y bpftrace ``` ## Gentoo package On Gentoo, bpftrace is included in the official repo. The package can be installed with emerge. ``` sudo emerge -av bpftrace ``` ## Debian package Is available and tracked [here](https://tracker.debian.org/pkg/bpftrace). ## openSUSE package Is available and tracked [here](https://software.opensuse.org/package/bpftrace). ## CentOS package A build maintained by @fbs can be found [here](https://github.com/fbs/el7-bpf-specs/blob/master/README.md#repository). # Building bpftrace bpftrace's build system will download `gtest` at build time. If you don't want that or don't want tests, you can use the `make bpftrace` target. ## Ubuntu Due to the kernel requirements Ubuntu 18.04 or newer is highly recommended. ### 18.04 and 18.10 The versions of `bcc` currently available in Ubuntu 18.04 (Bionic) and 18.10 (Cosmic) do not have all the requirements for building `bpftrace` so building `bcc` first is required. The instructions for building `bcc` can be found [here](https://github.com/iovisor/bcc/blob/master/INSTALL.md#install-and-compile-bcc). The build dependencies listed below are also required for `bcc` so install those first. Make sure `bcc` works by testing some of the shipped tools before proceeding. It might be required to `ldconfig` to update the linker. ### 19.04 and newer The version of `bcc` available in Ubuntu 19.04 (Disco) is new enough so compilation is not required, install with: ``` sudo apt-get install -y libbpfcc-dev ``` ### Building `bpftrace` ``` sudo apt-get update sudo apt-get install -y bison cmake flex g++ git libelf-dev zlib1g-dev libfl-dev systemtap-sdt-dev binutils-dev sudo apt-get install -y llvm-7-dev llvm-7-runtime libclang-7-dev clang-7 git clone https://github.com/iovisor/bpftrace mkdir bpftrace/build; cd bpftrace/build; cmake -DCMAKE_BUILD_TYPE=Release .. make -j8 make install ``` The bpftrace binary will be in installed in /usr/local/bin/bpftrace, and tools in /usr/local/share/bpftrace/tools. You can change the install location using an argument to cmake, where the default is `-DCMAKE_INSTALL_PREFIX=/usr/local`. ## Fedora You'll want the newest kernel possible (see kernel requirements), eg, by using Fedora 28 or newer. ``` sudo dnf install -y bison flex cmake make git gcc-c++ elfutils-libelf-devel zlib-devel llvm-devel clang-devel bcc-devel systemtap-sdt-devel binutils-devel git clone https://github.com/iovisor/bpftrace cd bpftrace mkdir build; cd build; cmake -DCMAKE_BUILD_TYPE=Release .. make -j8 make install ``` The bpftrace binary will be in installed in /usr/local/bin/bpftrace, and tools in /usr/local/share/bpftrace/tools. You can change the install location using an argument to cmake, where the default is `-DCMAKE_INSTALL_PREFIX=/usr/local`. ## Amazon Linux In the future the install should be `yum install bpftrace`. Right now (16-Oct-2018), however, three dependencies need updating in the Amazon Linux repositories (llvm, libtinfo, bison), and bpftrace itself needs to be packaged. The current workaround is to build the three dependencies manually, as well as bpftrace. It's not fun, but it is doable, and will only get better as Amazon updates things. ``` sudo bash builddir=/media/ephemeral0 # change to suit your system: needs about 2 Gbytes free # dependencies yum install git cmake3 gcc64-c++.x86_64 bison flex # llvm cd $builddir wget http://releases.llvm.org/6.0.0/clang+llvm-6.0.0-x86_64-linux-gnu-Fedora27.tar.xz tar xf clang* (cd clang* && sudo cp -R * /usr/local/) cp -p /usr/lib64/llvm6.0/lib/libLLVM-6.0.so /usr/lib64/libLLVM.so # libtinfo.so.6 (comes from ncurses) cd $builddir wget ftp://ftp.gnu.org/gnu/ncurses/ncurses-6.0.tar.gz tar xvf ncurses-6.0.tar.gz cd ncurses-6.0 ./configure --with-shared --with-termlib make -j8 make install # bison cd $builddir wget http://ftp.gnu.org/gnu/bison/bison-3.1.tar.gz tar xf bison* cd bison* ./configure make -j4 make install # bpftrace cd $builddir git clone https://github.com/iovisor/bpftrace cd bpftrace mkdir build; cd build cmake3 .. make -j8 make install echo /usr/local/lib >> /etc/ld.so.conf ldconfig -v ``` The bpftrace binary will be in installed in /usr/local/bin/bpftrace, and tools in /usr/local/share/bpftrace/tools. You may need to add /usr/local/bin to your $PATH. You can also change the install location using an argument to cmake, where the default is `-DCMAKE_INSTALL_PREFIX=/usr/local`. ## Using Docker There are currently problems with bpftrace string comparisons when using the Docker build. The regular build is recommended for now. Building inside a Docker container will produce a statically linked bpftrace executable. `./build.sh` There are some more fine-grained options if you find yourself building bpftrace a lot: - `./build-docker-image.sh` - builds just the `bpftrace-builder` Docker image - `./build-debug.sh` - builds bpftrace with debugging information (requires `./build-docker-image.sh` to have already been run) - `./build-release.sh` - builds bpftrace in a release configuration (requires `./build-docker-image.sh` to have already been run) `./build.sh` is equivalent to `./build-docker-image.sh && ./build-release.sh` ## Generic build process Use specific OS build sections listed earlier if available (Ubuntu, Docker). ### Requirements - A C++ compiler - CMake - Flex - Bison - LLVM & Clang 5.0+ development packages - BCC development package - LibElf - Binutils development package - Kernel requirements described earlier ### Compilation ``` git clone https://github.com/iovisor/bpftrace mkdir -p bpftrace/build cd bpftrace/build cmake -DCMAKE_BUILD_TYPE=Release ../ make ``` By default bpftrace will be built as a dynamically linked executable. If a statically linked executable would be preferred and your system has the required libraries installed, the CMake option `-DSTATIC_LINKING:BOOL=ON` can be used. Building bpftrace using the alpine Docker image below will result in a statically linked executable, and additional flags allow for compiling and statically linking the dependencies of bpftrace, see [the embedded build docs](./docs/embedded_builds.md) for more about this type of build. A debug build of bpftrace can be set up with `cmake -DCMAKE_BUILD_TYPE=Debug ../`. The latest version of Google Test will be downloaded on each build. To speed up builds and only download its source on the first run, use the CMake option `-DOFFLINE_BUILDS:BOOL=ON`. To test that the build works, you can try running the test suite, and a one-liner: ``` ./tests/bpftrace_test ./src/bpftrace -e 'kprobe:do_nanosleep { printf("sleep by %s\n", comm); }' ``` bpftrace-0.9.4/LICENSE000066400000000000000000000261361361633214400143320ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. bpftrace-0.9.4/README.md000066400000000000000000000343571361633214400146100ustar00rootroot00000000000000# bpftrace [![Build Status](https://travis-ci.org/iovisor/bpftrace.svg?branch=master)](https://travis-ci.org/iovisor/bpftrace) [![IRC #bpftrace](https://img.shields.io/badge/IRC-bpftrace-blue.svg)](http://irc.lc/oftc/bpftrace/web@@@) bpftrace is a high-level tracing language for Linux enhanced Berkeley Packet Filter (eBPF) available in recent Linux kernels (4.x). bpftrace uses LLVM as a backend to compile scripts to BPF-bytecode and makes use of [BCC](https://github.com/iovisor/bcc) for interacting with the Linux BPF system, as well as existing Linux tracing capabilities: kernel dynamic tracing (kprobes), user-level dynamic tracing (uprobes), and tracepoints. The bpftrace language is inspired by awk and C, and predecessor tracers such as DTrace and SystemTap. bpftrace was created by [Alastair Robertson](https://github.com/ajor). To learn more about bpftrace, see the [Reference Guide](docs/reference_guide.md) and [One-Liner Tutorial](docs/tutorial_one_liners.md). ## Install For build and install instructions, see [INSTALL.md](INSTALL.md). ## Development For development and testing a [Vagrantfile](Vagrantfile) is available. ## Examples Count system calls using tracepoints: ``` # bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[probe] = count(); }' Attaching 320 probes... ^C ... @[tracepoint:syscalls:sys_enter_access]: 3291 @[tracepoint:syscalls:sys_enter_close]: 3897 @[tracepoint:syscalls:sys_enter_newstat]: 4268 @[tracepoint:syscalls:sys_enter_open]: 4609 @[tracepoint:syscalls:sys_enter_mmap]: 4781 ``` Produce a histogram of time (in nanoseconds) spent in the `read()` system call: ``` // read.bt file tracepoint:syscalls:sys_enter_read { @start[tid] = nsecs; } tracepoint:syscalls:sys_exit_read / @start[tid] / { @times = hist(nsecs - @start[tid]); delete(@start[tid]); } ``` ``` # bpftrace read.bt Attaching 2 probes... ^C @times: [256, 512) 326 |@ | [512, 1k) 7715 |@@@@@@@@@@@@@@@@@@@@@@@@@@ | [1k, 2k) 15306 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [2k, 4k) 609 |@@ | [4k, 8k) 611 |@@ | [8k, 16k) 438 |@ | [16k, 32k) 59 | | [32k, 64k) 36 | | [64k, 128k) 5 | | ``` Print process name and paths for file opens, using kprobes (kernel dynamic tracing) of do_sys_open(): ``` # bpftrace -e 'kprobe:do_sys_open { printf("%s: %s\n", comm, str(arg1)) }' Attaching 1 probe... git: .git/objects/da git: .git/objects/pack git: /etc/localtime systemd-journal: /var/log/journal/72d0774c88dc4943ae3d34ac356125dd DNS Res~ver #15: /etc/hosts ^C ``` CPU profiling, sampling kernel stacks at 99 Hertz: ``` # bpftrace -e 'profile:hz:99 { @[kstack] = count() }' Attaching 1 probe... ^C ... @[ queue_work_on+41 tty_flip_buffer_push+43 pty_write+83 n_tty_write+434 tty_write+444 __vfs_write+55 vfs_write+177 sys_write+85 entry_SYSCALL_64_fastpath+26 ]: 97 @[ cpuidle_enter_state+299 cpuidle_enter+23 call_cpuidle+35 do_idle+394 cpu_startup_entry+113 rest_init+132 start_kernel+1083 x86_64_start_reservations+41 x86_64_start_kernel+323 verify_cpu+0 ]: 150 ``` ## One-Liners The following one-liners demonstrate different capabilities: ``` # Files opened by process bpftrace -e 'tracepoint:syscalls:sys_enter_open { printf("%s %s\n", comm, str(args->filename)); }' # Syscall count by program bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }' # Read bytes by process: bpftrace -e 'tracepoint:syscalls:sys_exit_read /args->ret/ { @[comm] = sum(args->ret); }' # Read size distribution by process: bpftrace -e 'tracepoint:syscalls:sys_exit_read { @[comm] = hist(args->ret); }' # Show per-second syscall rates: bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @ = count(); } interval:s:1 { print(@); clear(@); }' # Trace disk size by process bpftrace -e 'tracepoint:block:block_rq_issue { printf("%d %s %d\n", pid, comm, args->bytes); }' # Count page faults by process bpftrace -e 'software:faults:1 { @[comm] = count(); }' # Count LLC cache misses by process name and PID (uses PMCs): bpftrace -e 'hardware:cache-misses:1000000 { @[comm, pid] = count(); }' # Profile user-level stacks at 99 Hertz, for PID 189: bpftrace -e 'profile:hz:99 /pid == 189/ { @[ustack] = count(); }' # Files opened, for processes in the root cgroup-v2 bpftrace -e 'tracepoint:syscalls:sys_enter_openat /cgroup == cgroupid("/sys/fs/cgroup/unified/mycg")/ { printf("%s\n", str(args->filename)); }' ``` ## Tools bpftrace contains various tools, which also serve as examples of programming in the bpftrace language. - tools/[bashreadline.bt](tools/bashreadline.bt): Print entered bash commands system wide. [Examples](tools/bashreadline_example.txt). - tools/[biolatency.bt](tools/biolatency.bt): Block I/O latency as a histogram. [Examples](tools/biolatency_example.txt). - tools/[biosnoop.bt](tools/biosnoop.bt): Block I/O tracing tool, showing per I/O latency. [Examples](tools/biosnoop_example.txt). - tools/[biostacks.bt](tools/biostacks.bt): Show disk I/O latency with initialization stacks. [Examples](tools/biostacks_example.txt). - tools/[bitesize.bt](tools/bitesize.bt): Show disk I/O size as a histogram. [Examples](tools/bitesize_example.txt). - tools/[capable.bt](tools/capable.bt): Trace security capability checks. [Examples](tools/capable_example.txt). - tools/[cpuwalk.bt](tools/cpuwalk.bt): Sample which CPUs are executing processes. [Examples](tools/cpuwalk_example.txt). - tools/[dcsnoop.bt](tools/dcsnoop.bt): Trace directory entry cache (dcache) lookups. [Examples](tools/dcsnoop_example.txt). - tools/[execsnoop.bt](tools/execsnoop.bt): Trace new processes via exec() syscalls. [Examples](tools/execsnoop_example.txt). - tools/[gethostlatency.bt](tools/gethostlatency.bt): Show latency for getaddrinfo/gethostbyname[2] calls. [Examples](tools/gethostlatency_example.txt). - tools/[killsnoop.bt](tools/killsnoop.bt): Trace signals issued by the kill() syscall. [Examples](tools/killsnoop_example.txt). - tools/[loads.bt](tools/loads.bt): Print load averages. [Examples](tools/loads_example.txt). - tools/[mdflush.bt](tools/mdflush.bt): Trace md flush events. [Examples](tools/mdflush_example.txt). - tools/[naptime.bt](tools/naptime.bt): Show voluntary sleep calls. [Examples](tools/naptime_example.txt). - tools/[opensnoop.bt](tools/opensnoop.bt): Trace open() syscalls showing filenames. [Examples](tools/opensnoop_example.txt). - tools/[oomkill.bt](tools/oomkill.bt): Trace OOM killer. [Examples](tools/oomkill_example.txt). - tools/[pidpersec.bt](tools/pidpersec.bt): Count new processes (via fork). [Examples](tools/pidpersec_example.txt). - tools/[runqlat.bt](tools/runqlat.bt): CPU scheduler run queue latency as a histogram. [Examples](tools/runqlat_example.txt). - tools/[runqlen.bt](tools/runqlen.bt): CPU scheduler run queue length as a histogram. [Examples](tools/runqlen_example.txt). - tools/[setuids.bt](tools/setuids.bt): Trace the setuid syscalls: privilege escalation. [Examples](tools/setuids_example.txt). - tools/[statsnoop.bt](tools/statsnoop.bt): Trace stat() syscalls for general debugging. [Examples](tools/statsnoop_example.txt). - tools/[swapin.bt](tools/swapin.bt): Show swapins by process. [Examples](tools/swapin_example.txt). - tools/[syncsnoop.bt](tools/syncsnoop.bt): Trace sync() variety of syscalls. [Examples](tools/syncsnoop_example.txt). - tools/[syscount.bt](tools/syscount.bt): Count system calls. [Examples](tools/syscount_example.txt). - tools/[tcpaccept.bt](tools/tcpaccept.bt): Trace TCP passive connections (accept()). [Examples](tools/tcpaccept_example.txt). - tools/[tcpconnect.bt](tools/tcpconnect.bt): Trace TCP active connections (connect()). [Examples](tools/tcpconnect_example.txt). - tools/[tcpdrop.bt](tools/tcpdrop.bt): Trace kernel-based TCP packet drops with details. [Examples](tools/tcpdrop_example.txt). - tools/[tcplife.bt](tools/tcplife.bt): Trace TCP session lifespans with connection details. [Examples](tools/tcplife_example.txt). - tools/[tcpretrans.bt](tools/tcpretrans.bt): Trace TCP retransmits. [Examples](tools/tcpretrans_example.txt). - tools/[tcpsynbl.bt](tools/tcpsynbl.bt): Show TCP SYN backlog as a histogram. [Examples](tools/tcpsynbl_example.txt). - tools/[threadsnoop.bt](tools/threadsnoop.bt): List new thread creation. [Examples](tools/threadsnoop_example.txt). - tools/[vfscount.bt](tools/vfscount.bt): Count VFS calls. [Examples](tools/vfscount_example.txt). - tools/[vfsstat.bt](tools/vfsstat.bt): Count some VFS calls, with per-second summaries. [Examples](tools/vfsstat_example.txt). - tools/[writeback.bt](tools/writeback.bt): Trace file system writeback events with details. [Examples](tools/writeback_example.txt). - tools/[xfsdist.bt](tools/xfsdist.bt): Summarize XFS operation latency distribution as a histogram. [Examples](tools/xfsdist_example.txt). For more eBPF observability tools, see [bcc tools](https://github.com/iovisor/bcc#tools). ## Probe types
### kprobes Attach a bpftrace script to a kernel function, to be executed when that function is called: `kprobe:vfs_read { ... }` ### uprobes Attach script to a userland function: `uprobe:/bin/bash:readline { ... }` ### tracepoints Attach script to a statically defined tracepoint in the kernel: `tracepoint:sched:sched_switch { ... }` Tracepoints are guaranteed to be stable between kernel versions, unlike kprobes. ### software Attach script to kernel software events, executing once every provided count or use a default: `software:faults:100` `software:faults:` ### hardware Attach script to hardware events (PMCs), executing once every provided count or use a default: `hardware:cache-references:1000000` `hardware:cache-references:` ### profile Run the script on all CPUs at specified time intervals: `profile:hz:99 { ... }` `profile:s:1 { ... }` `profile:ms:20 { ... }` `profile:us:1500 { ... }` ### interval Run the script once per interval, for printing interval output: `interval:s:1 { ... }` `interval:ms:20 { ... }` ### Multiple attachment points A single probe can be attached to multiple events: `kprobe:vfs_read,kprobe:vfs_write { ... }` ### Wildcards Some probe types allow wildcards to be used when attaching a probe: `uprobe:/bin/bash:read* { ... }` `kprobe:vfs_* { ... }` ### Predicates Define conditions for which a probe should be executed: `kprobe:sys_open / uid == 0 / { ... }` ## Builtins The following variables and functions are available for use in bpftrace scripts: Variables: - `pid` - Process ID (kernel tgid) - `tid` - Thread ID (kernel pid) - `cgroup` - Cgroup ID of the current process - `uid` - User ID - `gid` - Group ID - `nsecs` - Nanosecond timestamp - `elapsed` - Nanosecond timestamp since bpftrace initialization - `cpu` - Processor ID - `comm` - Process name - `stack` - Kernel stack trace - `ustack` - User stack trace - `arg0`, `arg1`, ... etc. - Arguments to the function being traced - `sarg0`, `sarg1`, ... etc. - Arguments to the function being traced (for programs that store arguments on the stack) - `retval` - Return value from function being traced - `func` - Name of the function currently being traced - `probe` - Full name of the probe - `curtask` - Current task_struct as a u64 - `rand` - Random number of type u32 - `$1`, `$2`, ... etc. - Positional parameters to the bpftrace program Functions: - `hist(int n)` - Produce a log2 histogram of values of `n` - `lhist(int n, int min, int max, int step)` - Produce a linear histogram of values of `n` - `count()` - Count the number of times this function is called - `sum(int n)` - Sum this value - `min(int n)` - Record the minimum value seen - `max(int n)` - Record the maximum value seen - `avg(int n)` - Average this value - `stats(int n)` - Return the count, average, and total for this value - `delete(@x)` - Delete the map element passed in as an argument - `str(char *s [, int length])` - Returns the string pointed to by `s` - `printf(char *fmt, ...)` - Print formatted to stdout - `print(@x[, int top [, int div]])` - Print a map, with optional top entry count and divisor - `clear(@x)` - Delete all key/values from a map - `sym(void *p)` - Resolve kernel address - `usym(void *p)` - Resolve user space address - `ntop([int af, ]int|char[4|16] addr)` - Resolve ip address - `kaddr(char *name)` - Resolve kernel symbol name - `uaddr(char *name)` - Resolve user space symbol name - `reg(char *name)` - Returns the value stored in the named register - `join(char *arr[] [, char *delim])` - Prints the string array - `time(char *fmt)` - Print the current time - `cat(char *filename)` - Print file content - `system(char *fmt)` - Execute shell command - `exit()` - Quit bpftrace See the [Reference Guide](docs/reference_guide.md) for more detail. ## Internals
bpftrace employs various techniques for efficiency, minimizing the instrumentation overhead. Summary statistics are stored in kernel BPF maps, which are asynchronously copied from kernel to user-space, only when needed. Other data, and asynchronous actions, are passed from kernel to user-space via the perf output buffer. ## Contributing * Have ideas for new bpftrace tools? [CONTRIBUTING-TOOLS.md](CONTRIBUTING-TOOLS.md) * Bugs reports and feature requests: [Issue Tracker](https://github.com/iovisor/bpftrace/issues) * bpftrace development IRC: #bpftrace at irc.oftc.net ## License Copyright 2019 Alastair Robertson Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. bpftrace-0.9.4/Vagrantfile000066400000000000000000000034111361633214400155010ustar00rootroot00000000000000# -*- mode: ruby -*- # vi: set ft=ruby : # Environment variables: # # SKIP_BCC_BUILD: Set to skip the building bcc from source $ubuntu_18_deps = < { 'image' => 'ubuntu/bionic64', 'scripts' => [ $ubuntu_18_deps, ], }, 'ubuntu-18.10' => { 'image' => 'ubuntu/cosmic64', 'scripts' => [ $ubuntu_18_deps, ], }, 'ubuntu-19.04' => { 'image' => 'ubuntu/disco64', 'scripts' => [ $ubuntu_18_deps, ], }, } boxes.each do | name, params | config.vm.define name do |box| box.vm.box = params['image'] box.vm.provider "virtualbox" do |v| v.memory = 2048 v.cpus = 2 end box.vm.provider :libvirt do |v| v.memory = 2048 v.cpus = 2 end (params['scripts'] || []).each do |script| box.vm.provision :shell, inline: script end unless ENV['SKIP_BCC_BUILD'] box.vm.provision :shell, privileged: false, inline: $build_bcc end config.vm.post_up_message = <<-HEREDOC ####### bpftrace source is available in /vagrant Build command: cmake /vagrant -DCMAKE_INSTALL_PREFIX=/usr/local && make ####### HEREDOC end end end bpftrace-0.9.4/build-debug.sh000077500000000000000000000002511361633214400160350ustar00rootroot00000000000000#!/bin/bash set -e docker run --rm -it -u $(id -u):$(id -g) -v $(pwd):$(pwd) -e STATIC_LINKING=ON -e RUN_TESTS=0 bpftrace-builder-alpine "$(pwd)/build-debug" Debug "$@" bpftrace-0.9.4/build-docker-image.sh000077500000000000000000000001441361633214400172770ustar00rootroot00000000000000#!/bin/bash set -e pushd docker docker build -t bpftrace-builder-alpine -f Dockerfile.alpine . popd bpftrace-0.9.4/build-release.sh000077500000000000000000000002551361633214400163730ustar00rootroot00000000000000#!/bin/bash set -e docker run --rm -it -u $(id -u):$(id -g) -v $(pwd):$(pwd) -e STATIC_LINKING=ON -e RUN_TESTS=0 bpftrace-builder-alpine "$(pwd)/build-release" Release "$@" bpftrace-0.9.4/build.sh000077500000000000000000000001031361633214400147450ustar00rootroot00000000000000#!/bin/bash set -e ./build-docker-image.sh ./build-release.sh "$@" bpftrace-0.9.4/cmake/000077500000000000000000000000001361633214400143755ustar00rootroot00000000000000bpftrace-0.9.4/cmake/FindLibBcc.cmake000066400000000000000000000034501361633214400173200ustar00rootroot00000000000000# - Try to find libbcc # Once done this will define # # LIBBCC_FOUND - system has libbcc # LIBBCC_INCLUDE_DIRS - the libbcc include directory # LIBBCC_LIBRARIES - Link these to use libbcc # LIBBCC_DEFINITIONS - Compiler switches required for using libbcc # LIBBPF_LIBRARY_STATIC - libbpf static library (for static compilation) # LIBBCC_LOADER_LIBRARY_STATIC - libbcc helper static library (for static compilation) if (LIBBCC_LIBRARIES AND LIBBCC_INCLUDE_DIRS) set (LibBcc_FIND_QUIETLY TRUE) endif (LIBBCC_LIBRARIES AND LIBBCC_INCLUDE_DIRS) find_path (LIBBCC_INCLUDE_DIRS NAMES libbpf.h PATHS /usr/include /usr/include/bcc /usr/local/include /usr/local/include/libbcc /usr/local/include/bcc /opt/local/include /opt/local/include/libbcc /sw/include /sw/include/libbcc ENV CPATH) find_library (LIBBCC_LIBRARIES NAMES bcc PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib ENV LIBRARY_PATH ENV LD_LIBRARY_PATH) find_library (LIBBPF_LIBRARY_STATIC NAMES bpf PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib ENV LIBRARY_PATH ENV LD_LIBRARY_PATH) find_library (LIBBCC_LOADER_LIBRARY_STATIC NAMES bcc-loader-static PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib ENV LIBRARY_PATH ENV LD_LIBRARY_PATH) include (FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set LIBBCC_FOUND to TRUE if all listed variables are TRUE FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibBcc "Please install the bcc library package, which is required. Depending on your distro, it may be called bpfcclib or bcclib (Ubuntu), bcc-devel (Fedora), or something else. If unavailable, install bcc from source (github.com/iovisor/bcc)." LIBBCC_LIBRARIES LIBBCC_INCLUDE_DIRS) bpftrace-0.9.4/cmake/FindLibBfd.cmake000066400000000000000000000040171361633214400173240ustar00rootroot00000000000000# - Try to find libbfd # Once done this will define # # LIBBFD_FOUND - system has libbfd # LIBBFD_INCLUDE_DIRS - the libbfd include directory # LIBBFD_LIBRARIES - Link these to use libbfd # LIBBFD_DEFINITIONS - Compiler switches required for using libbfd # LIBIBERTY_LIBRARIES - libiberty static library (for static compilation) #if (LIBBFD_LIBRARIES AND LIBBFD_INCLUDE_DIRS) # set (LibBpf_FIND_QUIETLY TRUE) #endif (LIBBFD_LIBRARIES AND LIBBFD_INCLUDE_DIRS) find_path (LIBBFD_INCLUDE_DIRS NAMES bfd.h PATHS /usr/include /usr/local/include /opt/local/include /sw/include ENV CPATH) find_library (LIBBFD_LIBRARIES NAMES bfd PATHS /lib /usr/lib /usr/local/lib /opt/local/lib /usr/lib/x86_64-linux-gnu/ /sw/lib ENV LIBRARY_PATH ENV LD_LIBRARY_PATH) # libbfd.so is statically linked with libiberty.a but libbfd.a # is not. So if we do a static bpftrace build, we must link in # libiberty.a find_library (LIBIBERTY_LIBRARIES NAMES libiberty.a PATHS /lib /usr/lib /usr/local/lib /opt/local/lib /usr/lib/x86_64-linux-gnu/ /sw/lib ENV LIBRARY_PATH ENV LD_LIBRARY_PATH) include (FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set LIBBFD_FOUND to TRUE if all listed variables are TRUE FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibBfd "Please install the libbfd development package" LIBBFD_LIBRARIES LIBBFD_INCLUDE_DIRS) mark_as_advanced(LIBBFD_INCLUDE_DIRS LIBBFD_LIBRARIES) if(${LIBBFD_FOUND}) SET(CMAKE_REQUIRED_LIBRARIES bfd opcodes) INCLUDE(CheckCXXSourceCompiles) CHECK_CXX_SOURCE_COMPILES(" #include // See comment in bfd-disasm.cpp for why this needs to exist #define PACKAGE \"bpftrace-test\" #include #include int main(void) { bfd *abfd = bfd_openr(NULL, NULL); disassembler(bfd_get_arch(abfd), bfd_big_endian(abfd), bfd_get_mach(abfd), abfd); return 0; }" LIBBFD_DISASM_FOUR_ARGS_SIGNATURE) SET(CMAKE_REQUIRED_LIBRARIES) endif() bpftrace-0.9.4/cmake/FindLibBpf.cmake000066400000000000000000000031171361633214400173400ustar00rootroot00000000000000# - Try to find libbpf # Once done this will define # # LIBBPF_FOUND - system has libbpf # LIBBPF_INCLUDE_DIRS - the libbpf include directory # LIBBPF_LIBRARIES - Link these to use libbpf # LIBBPF_DEFINITIONS - Compiler switches required for using libbpf #if (LIBBPF_LIBRARIES AND LIBBPF_INCLUDE_DIRS) # set (LibBpf_FIND_QUIETLY TRUE) #endif (LIBBPF_LIBRARIES AND LIBBPF_INCLUDE_DIRS) find_path (LIBBPF_INCLUDE_DIRS NAMES bpf/bpf.h bpf/btf.h bpf/libbpf.h PATHS /usr/include /usr/local/include /opt/local/include /sw/include ENV CPATH) find_library (LIBBPF_LIBRARIES NAMES bpf PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib ENV LIBRARY_PATH ENV LD_LIBRARY_PATH) include (FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set LIBBPF_FOUND to TRUE if all listed variables are TRUE FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibBpf "Please install the libbpf development package" LIBBPF_LIBRARIES LIBBPF_INCLUDE_DIRS) mark_as_advanced(LIBBPF_INCLUDE_DIRS LIBBPF_LIBRARIES) # We need btf_dump support, set LIBBPF_BTF_DUMP_FOUND # when it's found. if (LIBBPF_FOUND) include(CheckSymbolExists) # adding also elf for static build check SET(CMAKE_REQUIRED_LIBRARIES ${LIBBPF_LIBRARIES} elf) # libbpf quirk, needs upstream fix SET(CMAKE_REQUIRED_DEFINITIONS -include stdbool.h) check_symbol_exists(btf_dump__new "${LIBBPF_INCLUDE_DIRS}/bpf/btf.h" HAVE_BTF_DUMP) if (HAVE_BTF_DUMP) set(LIBBPF_BTF_DUMP_FOUND TRUE) endif() SET(CMAKE_REQUIRED_DEFINITIONS) SET(CMAKE_REQUIRED_LIBRARIES) endif() bpftrace-0.9.4/cmake/FindLibElf.cmake000066400000000000000000000032101361633214400173310ustar00rootroot00000000000000# - Try to find libelf # Once done this will define # # LIBELF_FOUND - system has libelf # LIBELF_INCLUDE_DIRS - the libelf include directory # LIBELF_LIBRARIES - Link these to use libelf # LIBELF_DEFINITIONS - Compiler switches required for using libelf # # Copyright (c) 2008 Bernhard Walle # # Redistribution and use is allowed according to the terms of the New # BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # if (LIBELF_LIBRARIES AND LIBELF_INCLUDE_DIRS) set (LibElf_FIND_QUIETLY TRUE) endif (LIBELF_LIBRARIES AND LIBELF_INCLUDE_DIRS) find_path (LIBELF_INCLUDE_DIRS NAMES libelf.h PATHS /usr/include /usr/include/libelf /usr/local/include /usr/local/include/libelf /opt/local/include /opt/local/include/libelf /sw/include /sw/include/libelf ENV CPATH) find_library (LIBELF_LIBRARIES NAMES elf PATHS /usr/lib /usr/local/lib /opt/local/lib /usr/lib/x86_64-linux-gnu/ /sw/lib ENV LIBRARY_PATH ENV LD_LIBRARY_PATH) include (FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set LIBELF_FOUND to TRUE if all listed variables are TRUE FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibElf "Please install the libelf development package" LIBELF_LIBRARIES LIBELF_INCLUDE_DIRS) SET(CMAKE_REQUIRED_LIBRARIES elf) INCLUDE(CheckCXXSourceCompiles) CHECK_CXX_SOURCE_COMPILES("#include int main() { Elf *e = (Elf*)0; size_t sz; elf_getshdrstrndx(e, &sz); return 0; }" ELF_GETSHDRSTRNDX) SET(CMAKE_REQUIRED_LIBRARIES) mark_as_advanced(LIBELF_INCLUDE_DIRS LIBELF_LIBRARIES ELF_GETSHDRSTRNDX) bpftrace-0.9.4/cmake/FindLibOpcodes.cmake000066400000000000000000000022101361633214400202160ustar00rootroot00000000000000# - Try to find libbfd # Once done this will define # # LIBOPCODES_FOUND - system has libbfd # LIBOPCODES_INCLUDE_DIRS - the libbfd include directory # LIBOPCODES_LIBRARIES - Link these to use libbfd # LIBOPCODES_DEFINITIONS - Compiler switches required for using libbfd #if (LIBOPCODES_LIBRARIES AND LIBOPCODES_INCLUDE_DIRS) # set (LibBpf_FIND_QUIETLY TRUE) #endif (LIBOPCODES_LIBRARIES AND LIBOPCODES_INCLUDE_DIRS) find_path (LIBOPCODES_INCLUDE_DIRS NAMES dis-asm.h PATHS /usr/include /usr/local/include /opt/local/include /sw/include ENV CPATH) find_library (LIBOPCODES_LIBRARIES NAMES opcodes PATHS /lib /usr/lib /usr/local/lib /opt/local/lib /usr/lib/x86_64-linux-gnu/ /sw/lib ENV LIBRARY_PATH ENV LD_LIBRARY_PATH) include (FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set LIBOPCODES_FOUND to TRUE if all listed variables are TRUE FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibOpcodes "Please install the libopcodes development package" LIBOPCODES_LIBRARIES LIBOPCODES_INCLUDE_DIRS) mark_as_advanced(LIBOPCODES_INCLUDE_DIRS LIBOPCODES_LIBRARIES) bpftrace-0.9.4/cmake/embed/000077500000000000000000000000001361633214400154515ustar00rootroot00000000000000bpftrace-0.9.4/cmake/embed/embed_clang.cmake000066400000000000000000000124341361633214400206770ustar00rootroot00000000000000if(NOT EMBED_CLANG) return() endif() include(embed_helpers) if(CMAKE_BUILD_TYPE STREQUAL "Debug") set(EMBEDDED_BUILD_TYPE "RelWithDebInfo") elseif(CMAKE_BUILD_TYPE STREQUAL "Release") set(EMBEDDED_BUILD_TYPE "MinSizeRel") else() set(EMBEDDED_BUILD_TYPE ${CMAKE_BUILD_TYPE}) endif() if(${LLVM_VERSION} VERSION_GREATER_EQUAL "9") set(LLVM_FULL_VERSION "9.0.1") set(CLANG_DOWNLOAD_URL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_FULL_VERSION}/clang-${LLVM_FULL_VERSION}.src.tar.xz") set(CLANG_URL_CHECKSUM "SHA256=5778512b2e065c204010f88777d44b95250671103e434f9dc7363ab2e3804253") elseif(${LLVM_VERSION} VERSION_GREATER_EQUAL "8") set(LLVM_FULL_VERSION "8.0.1") set(CLANG_DOWNLOAD_URL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_FULL_VERSION}/cfe-${LLVM_FULL_VERSION}.src.tar.xz") set(CLANG_URL_CHECKSUM "SHA256=70effd69f7a8ab249f66b0a68aba8b08af52aa2ab710dfb8a0fba102685b1646") elseif(${LLVM_VERSION} VERSION_GREATER_EQUAL "7") set(LLVM_FULL_VERSION "7.1.0") set(CLANG_DOWNLOAD_URL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_FULL_VERSION}/cfe-${LLVM_FULL_VERSION}.src.tar.xz") set(CLANG_URL_CHECKSUM "SHA256=e97dc472aae52197a4d5e0185eb8f9e04d7575d2dc2b12194ddc768e0f8a846d") else() message(FATAL_ERROR "No supported LLVM version has been specified with LLVM_VERSION (LLVM_VERSION=${LLVM_VERSION}), aborting") endif() ProcessorCount(nproc) set(LIBCLANG_INSTALL_COMMAND "mkdir -p /lib/ && \ cp /lib/libclang.a /lib/libclang.a" ) set(CLANG_INSTALL_COMMAND INSTALL_COMMAND /bin/bash -c "${CMAKE_MAKE_PROGRAM} install -j${nproc} && ${LIBCLANG_INSTALL_COMMAND}" ) if(NOT EMBED_LLVM) # If not linking and building against embedded LLVM, patches may need to # be applied to link with the distribution LLVM. This is handled by a # helper function prepare_clang_patches(patch_command) set(CLANG_PATCH_COMMAND PATCH_COMMAND /bin/bash -c "${patch_command}") endif() if(EMBED_LIBCLANG_ONLY) set(CLANG_LIBRARY_TARGETS clang) set(CLANG_BUILD_COMMAND BUILD_COMMAND /bin/bash -c "${CMAKE_MAKE_PROGRAM} libclang_static -j${nproc}" ) set(CLANG_INSTALL_COMMAND INSTALL_COMMAND /bin/bash -c "${LIBCLANG_INSTALL_COMMAND}") # Include system clang here to deal with the rest of the targets find_package(Clang REQUIRED) include_directories(SYSTEM ${CLANG_INCLUDE_DIRS}) else() set(CLANG_LIBRARY_TARGETS clang clangAST clangAnalysis clangBasic clangDriver clangEdit clangFormat clangFrontend clangIndex clangLex clangParse clangRewrite clangSema clangSerialization clangToolingCore clangToolingInclusions ) endif() # These configure flags are a blending of the Alpine, debian, and gentoo # packages configure flags, customized to reduce build targets as much as # possible set(CLANG_CONFIGURE_FLAGS -Wno-dev -DLLVM_TARGETS_TO_BUILD=BPF -DCMAKE_BUILD_TYPE=${EMBEDDED_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX= -DCMAKE_VERBOSE_MAKEFILE=OFF -DCLANG_VENDOR=bpftrace -DCLANG_BUILD_EXAMPLES=OFF -DCLANG_INCLUDE_DOCS=OFF -DCLANG_INCLUDE_TESTS=OFF -DCLANG_PLUGIN_SUPPORT=ON -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_EH=ON -DLLVM_ENABLE_RTTI=ON -DCLANG_BUILD_TOOLS=OFF ) # If LLVM is being embedded, inform Clang to use its Cmake file instead of system if(EMBED_LLVM) list(APPEND CLANG_CONFIGURE_FLAGS -DLLVM_DIR=${EMBEDDED_LLVM_INSTALL_DIR}/lib/cmake/llvm) endif() set(CLANG_TARGET_LIBS "") foreach(clang_target IN LISTS CLANG_LIBRARY_TARGETS) list(APPEND CLANG_TARGET_LIBS "/lib/lib${clang_target}.a") endforeach(clang_target) ExternalProject_Add(embedded_clang URL "${CLANG_DOWNLOAD_URL}" URL_HASH "${CLANG_URL_CHECKSUM}" CMAKE_ARGS "${CLANG_CONFIGURE_FLAGS}" ${CLANG_PATCH_COMMAND} ${CLANG_BUILD_COMMAND} ${CLANG_INSTALL_COMMAND} BUILD_BYPRODUCTS ${CLANG_TARGET_LIBS} UPDATE_DISCONNECTED 1 DOWNLOAD_NO_PROGRESS 1 ) # If LLVM is also being embedded, build it first if (EMBED_LLVM) ExternalProject_Add_StepDependencies(embedded_clang install embedded_llvm) endif() # Set up library targets and locations ExternalProject_Get_Property(embedded_clang INSTALL_DIR) set(EMBEDDED_CLANG_INSTALL_DIR ${INSTALL_DIR}) set(CLANG_EMBEDDED_CMAKE_TARGETS "") include_directories(SYSTEM ${EMBEDDED_CLANG_INSTALL_DIR}/include) foreach(clang_target IN LISTS CLANG_LIBRARY_TARGETS) # Special handling is needed to not overlap with the library definition from # system cmake files for Clang's "clang" target. if(EMBED_LIBCLANG_ONLY AND ${clang_target} STREQUAL "clang") set(clang_target "embedded-libclang") list(APPEND CLANG_EMBEDDED_CMAKE_TARGETS ${clang_target}) add_library(${clang_target} STATIC IMPORTED) set_property(TARGET ${clang_target} PROPERTY IMPORTED_LOCATION ${EMBEDDED_CLANG_INSTALL_DIR}/lib/libclang.a) add_dependencies(${clang_target} embedded_clang) else() list(APPEND CLANG_EMBEDDED_CMAKE_TARGETS ${clang_target}) add_library(${clang_target} STATIC IMPORTED) set_property(TARGET ${clang_target} PROPERTY IMPORTED_LOCATION ${EMBEDDED_CLANG_INSTALL_DIR}/lib/lib${clang_target}.a) add_dependencies(${clang_target} embedded_clang) endif() endforeach(clang_target) bpftrace-0.9.4/cmake/embed/embed_helpers.cmake000066400000000000000000000075231361633214400212600ustar00rootroot00000000000000include(ExternalProject) include(ProcessorCount) include(embed_patches) # Workaround to remove dynamic libs from library dependencies function(unlink_transitive_dependency targets dep_to_remove) foreach(tgt ${targets}) get_target_property(int_link_libs ${tgt} INTERFACE_LINK_LIBRARIES) if(NOT(int_link_libs MATCHES "-NOTFOUND$")) list(REMOVE_ITEM int_link_libs "${dep_to_remove}") set_target_properties(${tgt} PROPERTIES INTERFACE_LINK_LIBRARIES "${int_link_libs}") endif() endforeach(tgt ${targets}) endfunction(unlink_transitive_dependency) # Detect the distribution bpftrace is being built on function(detect_host_os os_id) file(STRINGS "/etc/os-release" HOST_OS_INFO) foreach(os_info IN LISTS HOST_OS_INFO) if(os_info MATCHES "^ID=") string(REPLACE "ID=" "" HOST_OS_ID ${os_info}) set(${os_id} ${HOST_OS_ID} PARENT_SCOPE) break() endif() endforeach(os_info) endfunction(detect_host_os os_id) function(detect_host_os_family family_id) file(STRINGS "/etc/os-release" HOST_OS_INFO) foreach(os_info IN LISTS HOST_OS_INFO) if(os_info MATCHES "^ID_LIKE=") string(REPLACE "ID_LIKE=" "" HOST_OS_ID_LIKE ${os_info}) set(${family_id} ${HOST_OS_ID_LIKE} PARENT_SCOPE) break() endif() endforeach(os_info) endfunction(detect_host_os_family family_id) # TODO dalehamel # DRY up get_host_triple and get_target_triple by accepting a triple_type arg # For simplicity sake, kept separate for now. function(get_host_triple out) # Get the architecture. set(arch ${CMAKE_HOST_SYSTEM_PROCESSOR}) # Get os and vendor if (${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Linux") set(vendor "generic") set(os "linux") else() message(AUTHOR_WARNING "The host system ${CMAKE_HOST_SYSTEM_NAME} isn't supported") endif() set(triple "${arch}-${vendor}-${os}") set(${out} ${triple} PARENT_SCOPE) message(STATUS "Detected host triple: ${triple}") endfunction() function(get_target_triple out) # Get the architecture. set(arch ${CMAKE_SYSTEM_PROCESSOR}) # Get os and vendor if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(vendor "generic") set(os "linux") elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Android") set(vendor "android") set(os "linux") message(AUTHOR_WARNING "Android build not yet fully implemented.") else() message(AUTHOR_WARNING "The target system ${CMAKE_SYSTEM_NAME} isn't supported") endif() set(triple "${arch}-${vendor}-${os}") set(${out} ${triple} PARENT_SCOPE) message(STATUS "Detected target triple: ${triple}") endfunction() function(fix_llvm_linkflags targetProperty propertyValue) set_target_properties(${target_property} PROPERTIES INTERFACE_LINK_LIBRARIES "${propertyValue}" ) endfunction(fix_llvm_linkflags targetProperty propertyValue) function(prepare_patch_series patchSeries patchPath) message("Writing patch series to ${patchPath}/series ...") file(WRITE "${patchPath}/series" "") foreach(patch_info IN ITEMS ${patchSeries}) file(APPEND "${patchPath}/series" "${patch_info}\n") endforeach(patch_info) endfunction(prepare_patch_series patchSeries patchPath) function(fetch_patches patchName patchPath patchURL patchChecksum stripLevel) if(NOT EXISTS "${patchPath}/${patchName}") message("Downloading ${patchURL}") file(MAKE_DIRECTORY ${patchPath}) file(DOWNLOAD "${patchURL}" "${patchPath}/${patchName}" EXPECTED_HASH SHA256=${patchChecksum}) # Can add to this if ladder to support additional patch formats, tar # probably catches quit a lot... if(patchName MATCHES .*tar.*) execute_process(COMMAND tar -xpf ${patchPath}/${patchName} --strip-components=${stripLevel} -C ${patchPath}) else() message("Patch ${patchName} doesn't appear to a tar achive, assuming it is a plaintext patch") endif() endif() endfunction(fetch_patches patchName patchPatch patchURL patchChecksum) bpftrace-0.9.4/cmake/embed/embed_llvm.cmake000066400000000000000000000114611361633214400205640ustar00rootroot00000000000000if(NOT EMBED_LLVM) return() endif() include(embed_helpers) # TO DO # Set up cross-compilation # https://cmake.org/cmake/help/v3.6/manual/cmake-toolchains.7.html#cross-compiling-using-clang get_host_triple(CHOST) get_target_triple(CBUILD) if(CMAKE_BUILD_TYPE STREQUAL "Debug") # Same as debian, see # https://salsa.debian.org/pkg-llvm-team/llvm-toolchain/blob/8/debian/rules set(EMBEDDED_BUILD_TYPE "RelWithDebInfo") elseif(CMAKE_BUILD_TYPE STREQUAL "Release") set(EMBEDDED_BUILD_TYPE "MinSizeRel") else() set(EMBEDDED_BUILD_TYPE ${CMAKE_BUILD_TYPE}) endif() if(${LLVM_VERSION} VERSION_GREATER_EQUAL "9") set(LLVM_FULL_VERSION "9.0.1") set(LLVM_DOWNLOAD_URL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_FULL_VERSION}/llvm-${LLVM_FULL_VERSION}.src.tar.xz") set(LLVM_URL_CHECKSUM "SHA256=00a1ee1f389f81e9979f3a640a01c431b3021de0d42278f6508391a2f0b81c9a") elseif(${LLVM_VERSION} VERSION_GREATER_EQUAL "8") set(LLVM_FULL_VERSION "8.0.1") set(LLVM_DOWNLOAD_URL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_FULL_VERSION}/llvm-${LLVM_FULL_VERSION}.src.tar.xz") set(LLVM_URL_CHECKSUM "SHA256=44787a6d02f7140f145e2250d56c9f849334e11f9ae379827510ed72f12b75e7") elseif(${LLVM_VERSION} VERSION_GREATER_EQUAL "7") set(LLVM_FULL_VERSION "7.1.0") set(LLVM_DOWNLOAD_URL "https://github.com/llvm/llvm-project/releases/download/llvmorg-${LLVM_FULL_VERSION}/llvm-${LLVM_FULL_VERSION}.src.tar.xz") set(LLVM_URL_CHECKSUM "SHA256=1bcc9b285074ded87b88faaedddb88e6b5d6c331dfcfb57d7f3393dd622b3764") else() message(FATAL_ERROR "No supported LLVM version has been specified with LLVM_VERSION (LLVM_VERSION=${LLVM_VERSION}), aborting") endif() # Default to building almost all targets, + BPF specific ones set(LLVM_LIBRARY_TARGETS LLVMAggressiveInstCombine LLVMAnalysis LLVMAsmParser LLVMAsmPrinter LLVMBinaryFormat LLVMBitReader LLVMBitWriter LLVMBPFAsmParser LLVMBPFAsmPrinter LLVMBPFCodeGen LLVMBPFDesc LLVMBPFDisassembler LLVMBPFInfo LLVMCodeGen LLVMCore LLVMCoroutines LLVMCoverage LLVMDebugInfoCodeView LLVMDebugInfoDWARF LLVMDebugInfoMSF LLVMDebugInfoPDB LLVMDemangle LLVMDlltoolDriver LLVMExecutionEngine LLVMFuzzMutate LLVMGlobalISel LLVMInstCombine LLVMInstrumentation LLVMInterpreter LLVMipo LLVMIRReader LLVMLibDriver LLVMLineEditor LLVMLinker LLVMLTO LLVMMC LLVMMCA LLVMMCDisassembler LLVMMCJIT LLVMMCParser LLVMMIRParser LLVMObjCARCOpts LLVMObject LLVMObjectYAML LLVMOption LLVMOptRemarks LLVMOrcJIT LLVMPasses LLVMProfileData LLVMRuntimeDyld LLVMScalarOpts LLVMSelectionDAG LLVMSymbolize LLVMTableGen LLVMTarget LLVMTextAPI LLVMTransformUtils LLVMVectorize LLVMWindowsManifest LLVMXRay LLVMSupport ) # These build flags are based off of Alpine, Debian and Gentoo packages # optimized for compatibility and reducing build targets set(LLVM_CONFIGURE_FLAGS -Wno-dev -DLLVM_TARGETS_TO_BUILD=BPF -DCMAKE_BUILD_TYPE=${EMBEDDED_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX= -DLLVM_BINUTILS_INCDIR=/usr/include -DLLVM_BUILD_DOCS=OFF -DLLVM_BUILD_EXAMPLES=OFF -DLLVM_BUILD_EXTERNAL_COMPILER_RT=ON -DLLVM_BUILD_LLVM_DYLIB=ON -DLLVM_BUILD_TESTS=OFF -DLLVM_DEFAULT_TARGET_TRIPLE=${CBUILD} -DLLVM_ENABLE_ASSERTIONS=OFF -DLLVM_ENABLE_CXX1Y=ON -DLLVM_ENABLE_FFI=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_LIBCXX=OFF -DLLVM_ENABLE_PIC=ON -DLLVM_ENABLE_LIBPFM=OFF -DLLVM_ENABLE_EH=ON -DLLVM_ENABLE_RTTI=ON -DLLVM_ENABLE_SPHINX=OFF -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=ON -DLLVM_HOST_TRIPLE=${CHOST} -DLLVM_INCLUDE_EXAMPLES=OFF -DLLVM_LINK_LLVM_DYLIB=ON -DLLVM_APPEND_VC_REV=OFF ) set(LLVM_TARGET_LIBS "") foreach(llvm_target IN LISTS LLVM_LIBRARY_TARGETS) list(APPEND LLVM_TARGET_LIBS "/lib/lib${llvm_target}.a") endforeach(llvm_target) ExternalProject_Add(embedded_llvm URL "${LLVM_DOWNLOAD_URL}" URL_HASH "${LLVM_URL_CHECKSUM}" CMAKE_ARGS "${LLVM_CONFIGURE_FLAGS}" BUILD_BYPRODUCTS ${LLVM_TARGET_LIBS} UPDATE_DISCONNECTED 1 DOWNLOAD_NO_PROGRESS 1 ) # Set up build targets and map to embedded paths ExternalProject_Get_Property(embedded_llvm INSTALL_DIR) set(EMBEDDED_LLVM_INSTALL_DIR ${INSTALL_DIR}) set(LLVM_EMBEDDED_CMAKE_TARGETS "") include_directories(SYSTEM ${EMBEDDED_LLVM_INSTALL_DIR}/include) foreach(llvm_target IN LISTS LLVM_LIBRARY_TARGETS) list(APPEND LLVM_EMBEDDED_CMAKE_TARGETS ${llvm_target}) add_library(${llvm_target} STATIC IMPORTED) set_property(TARGET ${llvm_target} PROPERTY IMPORTED_LOCATION ${EMBEDDED_LLVM_INSTALL_DIR}/lib/lib${llvm_target}.a) add_dependencies(${llvm_target} embedded_llvm) endforeach(llvm_target) bpftrace-0.9.4/cmake/embed/embed_patches.cmake000066400000000000000000000067701361633214400212500ustar00rootroot00000000000000# Patch function for clang to be able to link to system LLVM function(prepare_clang_patches patch_command) message("Building embedded Clang against host LLVM, checking compatibiilty...") detect_host_os(HOST_OS_ID) detect_host_os_family(HOST_OS_FAMILY) set(CLANG_PATCH_COMMAND "/bin/true") if(HOST_OS_ID STREQUAL "debian" OR HOST_OS_ID STREQUAL "ubuntu" OR HOST_OS_FAMILY STREQUAL "debian") message("Building on a debian-like system, will apply minimal debian patches to clang sources in order to build.") set(PATCH_NAME "debian-patches.tar.gz") set(PATCH_PATH "${CMAKE_CURRENT_BINARY_DIR}/debian-llvm/") set(DEBIAN_PATCH_SERIES "") list(APPEND DEBIAN_PATCH_SERIES "kfreebsd/clang_lib_Basic_Targets.diff -p2") if(${LLVM_VERSION} VERSION_EQUAL "8" OR ${LLVM_VERSION} VERSION_GREATER "8" ) set(DEBIAN_PATCH_URL_BASE "https://salsa.debian.org/pkg-llvm-team/llvm-toolchain/-/archive/debian/") set(DEBIAN_PATCH_URL_PATH "8_8.0.1-1/llvm-toolchain-debian-8_8.0.1-1.tar.gz?path=debian%2Fpatches") set(DEBIAN_PATCH_URL "${DEBIAN_PATCH_URL_BASE}/${DEBIAN_PATCH_URL_PATH}") set(DEBIAN_PATCH_CHECKSUM 2b845a5de3cc2d49924b632d3e7a2fca53c55151e586528750ace2cb2aae23db) else() message(FATAL_ERROR "No supported LLVM version has been specified with LLVM_VERSION (LLVM_VERSION=${LLVM_VERSION}), aborting") endif() list(LENGTH DEBIAN_PATCH_SERIES NUM_PATCHES) message("${NUM_PATCHES} patches will be applied for Clang ${LLVM_VERSION} on ${HOST_OS_ID}/${HOST_OS_ID_LIKE}") fetch_patches(${PATCH_NAME} ${PATCH_PATH} ${DEBIAN_PATCH_URL} ${DEBIAN_PATCH_CHECKSUM} 3) prepare_patch_series("${DEBIAN_PATCH_SERIES}" ${PATCH_PATH}) # These targets are from LLVMExports.cmake, so may vary by distribution. # in order to avoid fighting with what the LLVM package wants the linker to # do, it is easiest to just override the target link properties # These libraries are missing from the linker line command line # in the upstream package # Adding extra libraries here shouldn't affect the result, as they will be # ignored by the linker if not needed # It matters a lot what linker is being used. GNU toolchain accepts the # -Wl,--start-group option for avoiding circular dependencies in static # libs. Otherwise, with lld or other linkers, and it seems the default # behavior of lld (see) https://reviews.llvm.org/D43786 is to do this # anyays. # # For other linker, the order of static libraries is very significant, an # must be precomputed to find the correct non-circular permutation... set_target_properties(LLVMSupport PROPERTIES INTERFACE_LINK_LIBRARIES "LLVMCoroutines;LLVMCoverage;LLVMDebugInfoDWARF;LLVMDebugInfoPDB;LLVMDemangle;LLVMDlltoolDriver;LLVMFuzzMutate;LLVMInterpreter;LLVMLibDriver;LLVMLineEditor;LLVMLTO;LLVMMCA;LLVMMIRParser;LLVMObjCARCOpts;LLVMObjectYAML;LLVMOption;LLVMOptRemarks;LLVMPasses;LLVMPerfJITEvents;LLVMSymbolize;LLVMTableGen;LLVMTextAPI;LLVMWindowsManifest;LLVMXRay;-Wl,-Bstatic -ltinfo;" ) # Need to omit lpthread here or it will try and link statically, and fail set_target_properties(LLVMCodeGen PROPERTIES INTERFACE_LINK_LIBRARIES "LLVMAnalysis;LLVMBitReader;LLVMBitWriter;LLVMCore;LLVMMC;LLVMProfileData;LLVMScalarOpts;LLVMSupport;LLVMTarget;LLVMTransformUtils" ) set(CLANG_PATCH_COMMAND "(QUILT_PATCHES=${PATCH_PATH} quilt push -a || [[ $? -eq 2 ]])") endif() set(${patch_command} "${CLANG_PATCH_COMMAND}" PARENT_SCOPE) endfunction(prepare_clang_patches patch_command) bpftrace-0.9.4/docker/000077500000000000000000000000001361633214400145645ustar00rootroot00000000000000bpftrace-0.9.4/docker/Dockerfile.alpine000066400000000000000000000021271361633214400200270ustar00rootroot00000000000000FROM alpine:3.8 RUN apk add --update \ bison \ binutils-dev \ build-base \ clang-dev \ clang-static \ curl \ cmake \ elfutils-dev \ flex-dev \ git \ linux-headers \ llvm5-dev \ llvm5-static \ python \ zlib-dev # Put LLVM directories where CMake expects them to be RUN ln -s /usr/lib/cmake/llvm5 /usr/lib/cmake/llvm RUN ln -s /usr/include/llvm5/llvm /usr/include/llvm RUN ln -s /usr/include/llvm5/llvm-c /usr/include/llvm-c # Alpine currently does not have a package for bcc. Until they do, # we'll peg the alpine build to bcc v0.8.0 # # We're building here so docker can cache the build layer WORKDIR / RUN curl -L https://github.com/iovisor/bcc/archive/v0.8.0.tar.gz \ --output /bcc.tar.gz RUN tar xvf /bcc.tar.gz RUN mv bcc-0.8.0 bcc RUN cd /bcc && mkdir build && cd build && cmake .. && make install -j4 && \ cp src/cc/libbcc.a /usr/local/lib64/libbcc.a && \ cp src/cc/libbcc-loader-static.a /usr/local/lib64/libbcc-loader-static.a && \ cp src/cc/libbpf.a /usr/local/lib64/libbpf.a COPY build.sh /build.sh RUN chmod 755 /build.sh ENTRYPOINT ["/bin/sh", "/build.sh"] bpftrace-0.9.4/docker/Dockerfile.bionic000066400000000000000000000024751361633214400200300ustar00rootroot00000000000000FROM ubuntu:bionic ARG LLVM_VERSION ENV LLVM_VERSION=$LLVM_VERSION RUN apt-get update && apt-get install -y curl gnupg &&\ llvmRepository="\n\ deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic main\n\ deb-src http://apt.llvm.org/bionic/ llvm-toolchain-bionic main\n\ deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-${LLVM_VERSION} main\n\ deb-src http://apt.llvm.org/bionic/ llvm-toolchain-bionic-${LLVM_VERSION} main\n" &&\ echo $llvmRepository >> /etc/apt/sources.list && \ curl -L https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 4052245BD4284CDD && \ echo "deb https://repo.iovisor.org/apt/bionic bionic main" | tee /etc/apt/sources.list.d/iovisor.list RUN apt-get update && apt-get install -y \ bison \ binutils-dev \ cmake \ flex \ g++ \ git \ libelf-dev \ zlib1g-dev \ libbcc \ clang-${LLVM_VERSION} \ libclang-${LLVM_VERSION}-dev \ libclang-common-${LLVM_VERSION}-dev \ libclang1-${LLVM_VERSION} \ llvm-${LLVM_VERSION} \ llvm-${LLVM_VERSION}-dev \ llvm-${LLVM_VERSION}-runtime \ libllvm${LLVM_VERSION} \ systemtap-sdt-dev \ python3 COPY build.sh /build.sh RUN chmod 755 /build.sh ENTRYPOINT ["bash", "/build.sh"] bpftrace-0.9.4/docker/Dockerfile.bionic-glibc000066400000000000000000000035741361633214400211070ustar00rootroot00000000000000FROM ubuntu:bionic ARG bcc_ref="v0.12.0" ARG LLVM_VERSION="8" ENV LLVM_VERSION=$LLVM_VERSION # There is no need need for most of this once # https://github.com/iovisor/bcc/pull/2677 merges, as this is all to build # the bcc static dependencies. This would save ~5 minutes of CI time RUN apt-get update && apt-get install -y curl gnupg &&\ llvmRepository="\n\ deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic main\n\ deb-src http://apt.llvm.org/bionic/ llvm-toolchain-bionic main\n\ deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-${LLVM_VERSION} main\n\ deb-src http://apt.llvm.org/bionic/ llvm-toolchain-bionic-${LLVM_VERSION} main\n" &&\ echo $llvmRepository >> /etc/apt/sources.list && \ curl -L https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - RUN apt-get update && apt-get install -y \ bison \ binutils-dev \ cmake \ flex \ g++ \ git \ libelf-dev \ zlib1g-dev \ libiberty-dev \ libbfd-dev \ libedit-dev \ clang-${LLVM_VERSION} \ libclang-${LLVM_VERSION}-dev \ libclang-common-${LLVM_VERSION}-dev \ libclang1-${LLVM_VERSION} \ llvm-${LLVM_VERSION} \ llvm-${LLVM_VERSION}-dev \ llvm-${LLVM_VERSION}-runtime \ libllvm${LLVM_VERSION} \ systemtap-sdt-dev \ python3 \ quilt # Build BCC and install static libs RUN mkdir -p /src && git clone https://github.com/iovisor/bcc /src/bcc RUN cd /src/bcc && git reset --hard $bcc_ref && mkdir build && cd build && \ cmake -DCMAKE_INSTALL_PREFIX=/usr/local ../ && make -j$(nproc) && \ make install && mkdir -p /usr/local/lib && \ cp src/cc/libbcc.a /usr/local/lib/libbcc.a && \ cp src/cc/libbcc-loader-static.a /usr/local/lib/libbcc-loader-static.a && \ cp ./src/cc/libbcc_bpf.a /usr/local/lib/libbpf.a COPY build.sh /build.sh RUN chmod 755 /build.sh ENTRYPOINT ["bash", "/build.sh"] bpftrace-0.9.4/docker/Dockerfile.fedora30000066400000000000000000000005141361633214400201600ustar00rootroot00000000000000FROM fedora:30 RUN dnf install -y \ bison \ binutils-devel \ clang-devel \ cmake \ elfutils-libelf-devel \ flex \ gcc-c++ \ git \ llvm-devel \ make \ zlib-devel \ bcc-devel \ systemtap-sdt-devel COPY build.sh /build.sh RUN chmod 755 /build.sh ENTRYPOINT ["/bin/sh", "/build.sh"] bpftrace-0.9.4/docker/build.sh000066400000000000000000000043671361633214400162310ustar00rootroot00000000000000#!/bin/bash set -e WARNINGS_AS_ERRORS=${WARNINGS_AS_ERRORS:-OFF} STATIC_LINKING=${STATIC_LINKING:-OFF} STATIC_LIBC=${STATIC_LIBC:-OFF} LLVM_VERSION=${LLVM_VERSION:-8} # default llvm to latest version EMBED_LLVM=${EMBED_LLVM:-OFF} EMBED_CLANG=${EMBED_CLANG:-OFF} EMBED_LIBCLANG_ONLY=${EMBED_LIBCLANG_ONLY:-OFF} DEPS_ONLY=${DEPS_ONLY:-OFF} RUN_TESTS=${RUN_TESTS:-1} CI_TIMEOUT=${CI_TIMEOUT:-0} # If running on Travis, we may need several builds incrementally building up # the cache in order to cold-start the build cache within the 50 minute travis # job timeout. The gist is to kill the job safely and save the cache and run # again until the build cache is fully warmed with_timeout() { if [[ $CI_TIMEOUT -gt 0 ]];then set +e [[ -z $CI_TIME_REMAINING ]] && CI_TIME_REMAINING=$CI_TIMEOUT start_time="$(date -u +%s)" timeout $CI_TIME_REMAINING $@ rc=$? end_time="$(date -u +%s)" elapsed="$(($end_time-$start_time))" CI_TIME_REMAINING=$((CI_TIME_REMAINING-elapsed)) echo "{$CI_TIME_REMAINING}s remains for other jobs" if [[ $rc -eq 124 ]];then echo "Exiting early on timeout to upload cache and retry..." echo "This is expected on a cold cache / new LLVM release." echo "Retry the build until it passes, so long as it progresses." echo "see docs/embedded_builds.md for more info" exit 0 elif [[ $rc -ne 0 ]];then exit $rc # preserve set -e behavior on non-timeout fi set -e # resume set -e else $@ fi } # Build bpftrace mkdir -p "$1" cd "$1" cmake -DCMAKE_BUILD_TYPE="$2" -DWARNINGS_AS_ERRORS:BOOL=$WARNINGS_AS_ERRORS \ -DSTATIC_LINKING:BOOL=$STATIC_LINKING -DSTATIC_LIBC:BOOL=$STATIC_LIBC \ -DEMBED_LLVM:BOOL=$EMBED_LLVM -DEMBED_CLANG:BOOL=$EMBED_CLANG \ -DEMBED_LIBCLANG_ONLY:BOOL=$EMBED_LIBCLANG_ONLY \ -DLLVM_VERSION=$LLVM_VERSION ../ shift 2 # It is necessary to build embedded llvm and clang targets first, # so that their headers can be referenced [[ $EMBED_LLVM == "ON" ]] && with_timeout make embedded_llvm "$@" [[ $EMBED_CLANG == "ON" ]] && with_timeout make embedded_clang "$@" [[ $DEPS_ONLY == "ON" ]] && exit 0 make "$@" if [ $RUN_TESTS = 1 ]; then if [ "$RUN_ALL_TESTS" = "1" ]; then ctest -V else ./tests/bpftrace_test $TEST_ARGS; fi fi bpftrace-0.9.4/docs/000077500000000000000000000000001361633214400142455ustar00rootroot00000000000000bpftrace-0.9.4/docs/embedded_builds.md000066400000000000000000000204451361633214400176670ustar00rootroot00000000000000# Embedding dependencies To make bpftrace more portable, it has long supported an alpine-based musl build, which statically compiled bpftrace resulting in no runtime linking required. The drawback to this approach is that LLVM libraries, even when statically compiled, depends on symbols from libdl, and works best and most predictably when dynamically linked to libc. To embed everything except for libc, building LLVM and Clang from source is supported. This allows for linking to arbitrary libc targets dynamically, which may provide the best of both worlds between a purely static and a purely dynamically-linked bpftrace executable. For this reason, there is CMake support in the bpftrace project to build LLVM and Clang from source, as these are the heaviest dependencies of bpftrace. Other library dependencies can be obtained by most package managers reliably. Upstream packages provided by package maintainers can't be depended on to present all of the necessary built artifacts to statically link against LLVM and Clang, despite the project [LLVM catering to this with its own CMake build process](https://llvm.org/docs/CMake.html#embedding-llvm-in-your-project). # Configuration flags To make an semi-static executable that includes everything except libc, the CMake option `STATIC_LIBC:BOOL=OFF` can be added. This should allow for a bpftrace executable that can run on any system with a compatible libc. To assist in this, new configuration flags allow to download, configure, build and embed static libraries for all dependencies of bpftrace, rather than relying on system libraries. To build the necessary static LLVM or Clang libraries, `-DEMBED_LLVM:BOOL=ON` and `-DEMBED_CLANG=ON` can be used respectively. Clang can link to system LLVM as well, but may need to be patched. It is possible to link bpftrace's embedded libraries with system libraries, so linking to a distribution specific LLVM is possible. Many distributions do not include `libclang.a`, using `-DEMBED_LIBCLANG_ONLY=ON` will build only `libclang.a`, allowing for linking to system LLVM and Clang libraries on Debian, as opposed to Vanilla LLVM sources. # Build times To build from scratch on a modern development laptop with an 8-way parallel build, bpftrace and its dependencies can be built in about 30 minutes. Subsequent builds will not have this overhead, once the embedded dependencies are built and cached, and will proceed at the comparable speeds as dynamically linked builds. ## CI environments Internal CI environments with any sort of caching mechanism should be able to just cache any external project's build directories, and the overhead of building on CI shouldn't be any different than other CI jobs. # Embedding clang On the alpine platform, libclang.a is generated by default and a static build is already achieved using this distribution. This libclang dependency is only achievable with custom builds, or by using alpine. To get around this problem, the `embed_clang.cmake` file provides the necessary static libraries for bpftrace to link against, by downloading LLVM from a github tagged release and compiling it directly using custom cmake flags, based on those provided by alpine already. This basically pulls the fairly simple alpine-build "in-tree", using cmake flags based on it, and specifically itemizing the libraries that will be link-time dependencies of bpftrace. In turn, these clang libraries depend heavily on LLVM libraries, so this necessitates having access to LLVM static libraries as well. It is also possible to build only libclang from (mostly) vanilla sources, and use system LLVM and Clang libraries. This is what the `EMBED_LIBCLANG_ONLY` flag provides. # Embedding LLVM It is possible to avoid embedding LLVM by applying distribution specific patches to embedded clang, so that it will be patch-compatible with the LLVM libraries shipped with the target system. This can save time by avoiding building LLVM altogether, but as distributions LLVM libraries may have more dependencies and other bloat than the embedded LLVM, this can result in a larger executable. On Ubuntu, the size of the bpftrace executable is increased from 36MB to 75MB (as of Jan 13 2020). This is still a desirable time-saver where space doesn't matter, such as when doing Debug builds - it can avoid building 15GB of LLVM libraries. ## Distribution patches The LLVM builds maintained by distributions have patches on top of the tagged releases. It may be favorable from time-to-time to pull in these patches. By downloading patches from an external source and writing a custom series file, `quilt` can be used to apply arbitrary patches to embedded sources. The `embed_helpers.cmake` file has the necessary helper functions to download patches, add them to a patch series, and set the patch command to be used with the ExternalProject. These are currently used to patch the embedded libclang build to link to system libraries on Debian. Luckily this is actually pulling patches directly from LLVM's own build repo, which is linked to by https://apt.llvm.org/. You can see the branch associated with the LLVM version. This shows the patch series used to build LLVM 9, etc for Debian. In the case of LLVM 8, to get libclang.a to build only one patch, is needed, not the whole series. The worst possible case in adding support for a new LLVM version is to copy the series file, and set "-p2" or "-p1" depending on if it is LLVM itself or a subproject, and what patch level it was written at. The workflow on a new LLVM release support linking to system libs would be to: * Try it with the existing patch series and maybe it just works * Examine the build failure, and grep for patches affecting these files from those in the patch series on the LLVM 9 / 10 etc branch * Add patches to the series file until there is a minimal one that builds. Most of the time the patches change minor cosmetic things, no major functionality. In this case, the patch makes it from KFreebsd to kFreebsd which breaks a header, but it is easy to fix # Building on CI In order to build successfully in an environment like Travis CI, it is necessary that build jobs complete in less than 50 minutes. Unfortunately, it takes about 50 minutes to build just LLVM dependencies alone on Travis. In order to build a new version of LLVM, or LLVM from a cold cache, the build must be done incrementally. This is done with controlled timeouts, to make the build complete within the 50 minute window. This allows for progress to be saved, and another CI job to pick up where it left off. This is done by setting the `CI_TIMEOUT` environment variable, currently to 40 minutes (to allow time for static bcc to build and cache artifacts to be uploaded). It may take several successive builds to rebuild the LLVM and Clang embedded library dependencies, when a new embedded LLVM target is added or the cache is cleared. Once the cache is warmed, the build times should be comparable to other builds. ## Debug build The debug build is too large to complete successfully on Travis CI. The debug artifacts for LLVM and Clang build process are around 35GB, which simply takes too long to save and restore on travis, resulting in builds being killed due to travis' 10 minute no-output timeout. This makes the incremental approach used to warm the cache for the Release binary impracticable. Debug builds ultimately produce a bpftrace binary that is 1.2GB, and not practical for redistribution. When run locally or on a less restricted CI environment, embedded Debug build should still complete and pass all tests, but it may take 1-2 hours for this to complete from a cold cache. As a way to ensure that debug builds can succeed, the system LLVM may be linked to by setting `EMBED_LLVM=OFF`. # libc options ## glibc This is the default, and currently only tested and supported. bpftrace is built against the glibc from Ubuntu bionic (18.04), which provides 2.27. ## musl (not yet supported) The original static build of bpftrace built against musl statically, but there is no reason why it could not link against musl dynamically. ## bionic (android - not yet supported) It should be possible to link to bionic libc for Android, allowing for bpftrace to run on Android systems with Kernels that support it. ## uclibc (not yet supported) Theoretically no reason this shouldn't work to dynamically link to, may be helpful for supporting bpftrace on embedded environments. bpftrace-0.9.4/docs/internals_development.md000066400000000000000000000675471361633214400212130ustar00rootroot00000000000000# bpftrace Internals This document is for bpftrace internals developers.
# Codegen This is the most difficult part of bpftrace. It involves writing code like this (from ast/codegen_llvm.cpp): ```C++ AllocaInst *buf = b_.CreateAllocaBPF(call.type, "usym"); b_.CreateMemSet(buf, b_.getInt8(0), call.type.size, 1); Value *pid = b_.CreateLShr(b_.CreateGetPidTgid(), 32); Value *addr_offset = b_.CreateGEP(buf, b_.getInt64(0)); Value *pid_offset = b_.CreateGEP(buf, {b_.getInt64(0), b_.getInt64(8)}); call.vargs->front()->accept(*this); b_.CreateStore(expr_, addr_offset); b_.CreateStore(pid, pid_offset); expr_ = buf; ``` These are llvm [Intermediate Representation](https://en.wikipedia.org/wiki/Intermediate_representation) \(IR\) functions that emit an llvm assembly-like language which can be compiled directly to BPF, thanks to llvm's BPF support. If you use bpftrace -d, you'll see this llvm assembly: ```shell bpftrace -d -e 'kprobe:do_nanosleep { printf("%s is sleeping\n", comm); }' ``` Produces: ```ll ... define i64 @"kprobe:do_nanosleep"(i8*) local_unnamed_addr section "s_kprobe:do_nanosleep" { entry: %comm = alloca [64 x i8], align 1 %printf_args = alloca %printf_t, align 8 %1 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, %printf_t* %printf_args, align 8 %2 = getelementptr inbounds [64 x i8], [64 x i8]* %comm, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) call void @llvm.memset.p0i8.i64(i8* nonnull %2, i8 0, i64 64, i32 1, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([64 x i8]* nonnull %comm, i64 64) %3 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1, i64 0 call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull %3, i8* nonnull %2, i64 64, i32 1, i1 false) %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 72) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ... ``` ## References Reference documentation for the codegen_llvm.cpp IR calls: - [llvm::IRBuilderBase Class Reference](https://llvm.org/doxygen/classllvm_1_1IRBuilderBase.html) - [llvm::IRBuilder Class Template Reference](https://llvm.org/doxygen/classllvm_1_1IRBuilder.html) Reference documentation for the llvm assembly: - [LLVM Language Reference Manual](https://llvm.org/docs/LangRef.html) Reference documentation for eBPF kernel helpers: - [Kernel Helpers](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md#helpers) - [`bpf.h`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/bpf.h) Reference for eBPF syscall and data structures (e.g. maps): - [`bpf(2)` man page](http://man7.org/linux/man-pages/man2/bpf.2.html) ## Gotchas If there's one gotcha I would like to mention, it's the use of CreateGEP() (Get Element Pointer). It's needed when dereferencing at an offset in a buffer, and it's tricky to use. ## Verifier BPF programs are submitted to Linux's in-kernel BPF verifier. Read the large comment at the start of the source; it provides a good explanation. Source code: https://github.com/torvalds/linux/blob/master/kernel/bpf/verifier.c Self-tests: https://github.com/torvalds/linux/blob/master/tools/testing/selftests/bpf/test_verifier.c If you see an error, it's often educational to look up the error message inside the source code or tests. The reasoning is easily understood if you work your way backwards from the message. If you find a test which expects your error message: the name of the test may reveal a better explanation for what the error means. You may also find that nearby tests reveal the criteria for success. Reading BPF instructions is difficult, but if you compare "successful tests" against their various "failure tests": you may see patterns and differences. For example, a successful test may perform a bitmask or conditional logic to provide guarantees about what range of inputs is possible. Mostly the verifier is trying to check "are you allowed to read/write this memory?". Usually there's a notion of "if your read begins inside the BPF stack, it needs to end inside the BPF stack as well", or suchlike. For example: if you've stack-allocated a buffer of 64 bytes for writing strings into, and you intend to parameterise "how many bytes might I copy into this buffer": you will need to add some minimum and maximum conditions, to constrain whichever variable is used to determine the length of data copied. BPF load and store instructions may be picky about what locations they're happy to read/write to. For example, probe_read_str() will only permit writes into a PTR_TO_STACK. I've documented some common errors you may encounter when the verifier bounds-checks your program. Most of this was learned during https://github.com/iovisor/bpftrace/pull/241. ### min value is negative ``` R2 min value is negative, either use unsigned or 'var &= const' ``` Probably you are using a variable to determine "how far should I jump, relative to this pointer". You need to prove that your variable is always positive. You could try casting to unsigned integer (my notes say that this did not result in any improvement, but it feels like it's worth another try): ```c++ // where expr_ is the problematic Value* b_.CreateIntCast( expr_, b_.getInt64Ty(), false) ``` Or you could bitmask it such that no negative number is possible: ```c++ b_.CreateAnd( expr_, 0x7fffffffffffffff) // 64-bit number with all bits except the first set to 1 ``` Or you could try [CreateMaxNum()](https://llvm.org/docs/LangRef.html#llvm-maxnum-intrinsic) (my notes say that this segfaulted, but it feels like it's worth another try): ```c++ b_.CreateMaxNum( b_.getInt64(0), expr_, "ensure_positive"), ``` Or you could try using if/else to provide bounds hints (my notes say that this did not result in any improvement, but it feels like it's worth another try): ```c++ // where expr_ is the problematic Value* // allocate a variable in which to store your final result, after comparisons are completed AllocaInst *mycoolvar = b_.CreateAllocaBPF(b_.getInt64Ty(), "mycoolvar"); Function *parent = b_.GetInsertBlock()->getParent(); BasicBlock *positive = BasicBlock::Create(module_->getContext(), "positive", parent); BasicBlock *negative = BasicBlock::Create(module_->getContext(), "negative", parent); BasicBlock *done = BasicBlock::Create(module_->getContext(), "done", parent); b_.CreateCondBr( b_.CreateICmpUGE(expr_, b_.getInt64(0), "if_positive"), positive, negative); // if expr_ is positive, store it into mycoolvar b_.SetInsertPoint(positive); b_.CreateStore(expr_, mycoolvar); b_.CreateBr(done); // if expr_ is negative, store a 0 into mycoolvar (or whatever you want to do) b_.SetInsertPoint(negative); b_.CreateStore(b_.getInt64(0), mycoolvar); b_.CreateBr(done); b_.SetInsertPoint(done); ``` **My favoured approach is to select the result of an unsigned comparison:** ```c++ // largest number we'll allow. choosing arbitrary maximum // since this example just wants to take advantage of the comparison's unsignedness Value *max = b_.getInt64(1024); // integer comparison: unsigned less-than-or-equal-to CmpInst::Predicate P = CmpInst::ICMP_ULE; // check whether expr_ is less-than-or-equal-to maximum Value *Cmp = b_.CreateICmp(P, expr_, max, "str.min.cmp"); // Select will contain expr_ if expr_ is sufficiently low, otherwise it will contain max Value *Select = b_.CreateSelect(Cmp, expr_, max, "str.min.select"); ``` ### unbounded memory access ``` R2 unbounded memory access, use 'var &= const' or 'if (var < const)' ``` You need to prove that you don't jump too far from your pointer. This re-uses techniques from "min value is negative"; you just need to tighten the range even further. How far is too far? You need to [stay below `BPF_MAX_VAR_SIZ`](https://github.com/iovisor/bpftrace/pull/241#issuecomment-440274294), `1ULL << 29`. So, you could bitmask your variable with `(1ULL << 29) - 1` = `0x1FFFFFFF`: ```c++ b_.CreateAnd( expr_, 0x1fffffff) // (1ULL << 29) - 1 ``` ### invalid stack ``` invalid stack type R1 off=-72 access_size=536870911 ``` This means that it's possible for us to jump so far that we'd overflow our stack. Keep re-using techniques from above, and tighten the range even further. But more likely, you have a fundamental problem: perhaps you're trying to allocate a buffer of arbitrary size (determined at runtime), and do arbitrarily-sized writes into it (determined at runtime). If indeed that's what you're trying to do: you'll have to change your architecture. The BPF stack (512 bytes) can only accommodate tiny allocations and jumps. You need to move towards storing your data in BPF maps. Consider this ongoing discussion on how to rearchitect to store stack data in a map: https://github.com/iovisor/bpftrace/issues/305 ### expected=PTR_TO_STACK; actual=PTR_TO_MAP_VALUE ``` R1 type=map_value_or_null expected=fp ``` This was encountered when I invoked `probe_read_str(void *dst, int size, const void *unsafe_ptr)` with a `*dst` that pointed to a BPF map value. It refused; `probe_read_str(3)` will only write into stack-allocated memory. The workaround is probably to write data onto the BPF stack _first_, then transfer from BPF stack into BPF map. If you've a lot of data, then this will take a few trips. ### stack limit exceeded ``` Looks like the BPF stack limit of 512 bytes is exceeded. Please move large on stack variables into BPF per-cpu array map. ``` You're trying to stack-allocate a really big variable. Sadly you'll need to rearchitect; see above. ### call to 'memset' is not supported. A call to built-in function 'memset' is not supported. This occurs when you attempt to zero out a large amount of memory, e.g. 1024 bytes. Probably the real problem is that you stack-allocated a really big variable. It just happens that (at large numbers): you'll get the error about memset _before_ you get the error about the allocation. ## Examples. ### 1. Codegen: Sum We can explore and get the hang of llvm assembly by writing some simple C programs and compiling them using clang. Since llvm assembly maps to llvm IR, I've sometimes prototyped my codegen_llvm.cpp IR this way: writing a C program to produce the llvm assembly, and then manually mapping it back to llvm IR. test.c: ```C int test(int arg_a, int arg_b) { int sum; sum = arg_a + arg_b; return sum; } ``` Compiling into llvm assembly: ``` # /usr/bin/clang-6.0 -cc1 test.c -emit-llvm ``` Produces test.ll: ```ll ; ModuleID = 'test.c' source_filename = "test.c" target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64-pc-linux-gnu" ; Function Attrs: noinline nounwind optnone define i32 @test(i32 %arg_a, i32 %arg_b) #0 { %arg_a.addr = alloca i32, align 4 %arg_b.addr = alloca i32, align 4 %sum = alloca i32, align 4 store i32 %arg_a, i32* %arg_a.addr, align 4 store i32 %arg_b, i32* %arg_b.addr, align 4 %1 = load i32, i32* %arg_a.addr, align 4 %2 = load i32, i32* %arg_b.addr, align 4 %add = add nsw i32 %1, %2 store i32 %add, i32* %sum, align 4 %3 = load i32, i32* %sum, align 4 ret i32 %3 } attributes #0 = { noinline nounwind optnone "correctly-rounded-divide-sqrt-fp-math"="false" "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "no-trapping-math"="false" "stack-protector-buffer-size"="8" "target-features"="+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" } !llvm.module.flags = !{!0} !llvm.ident = !{!1} !0 = !{i32 1, !"wchar_size", i32 4} !1 = !{!"clang version 6.0.1-svn334776-1~exp1~20180726133222.87 (branches/release_60)"} ``` You can imagine now mapping this back, line by line, to IR. Eg: ```ll %arg_a.addr = alloca i32, align 4 %arg_b.addr = alloca i32, align 4 %sum = alloca i32, align 4 ``` Becomes: ```C++ AllocaInst *arg_a_alloc = b_.CreateAllocaBPF(SizedType(Type::integer, 4), "arg_a"); AllocaInst *arg_b_alloc = b_.CreateAllocaBPF(SizedType(Type::integer, 4), "arg_b"); AllocaInst *sum_alloc = b_.CreateAllocaBPF(SizedType(Type::integer, 4), "sum"); ``` And then: ```ll store i32 %arg_a, i32* %arg_a.addr, align 4 store i32 %arg_b, i32* %arg_b.addr, align 4 ``` Becomes: ```C++ Value *arg_a = test->arg_begin()+0; // haven't explained this bit yet Value *arg_b = test->arg_begin()+1; // " " b_.CreateStore(arg_a, arg_a_alloc); b_.CreateStore(arg_b, arg_b_alloc); ``` And then: ```ll %1 = load i32, i32* %arg_a.addr, align 4 %2 = load i32, i32* %arg_b.addr, align 4 %add = add nsw i32 %1, %2 store i32 %add, i32* %sum, align 4 ``` Becomes: ``` Value *arg_a_load = b_.CreateLoad(arg_a_alloc); Value *arg_b_load = b_.CreateLoad(arg_b_alloc); Value *add = b_.CreateAdd(arg_a_load, arg_b_load); b_.CreateStore(add, sum_alloc); ``` Although I'd probably have written that on one line as: ``` b_.CreateStore(b_.CreateAdd(b_.CreateLoad(arg_a_alloc, arg_b_alloc)), sum_alloc); ``` Finally: ```ll %3 = load i32, i32* %sum, align 4 ret i32 %3 ``` Becomes (I'll just do this on one line as well): ``` b_.CreateRet(b_.CreateLoad(sum_alloc)); ``` That's just my mental conversion. I haven't tested this and it may have a bug. But this should be enough to illustrate the idea. ### 2. Codegen: curtask If you need to add support to a BPF kernel function that bpftrace does not yet call, this is a simple example. It adds a `curtask` builtin that calls BPF_FUNC_get_current_task. See [bcc Kernel Versions](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md) for documentation on these BPF functions. The commit is: https://github.com/iovisor/bpftrace/commit/895ea46f2c800e2f283339d0c96b3c8209590498 The diff is as simple as such an addition gets, and shows the different files and locations that need to be updated: ```diff diff --git a/README.md b/README.md index 6d72e2a..9cf4d8b 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,7 @@ Variables: - `arg0`, `arg1`, ... etc. - Arguments to the function being traced - `retval` - Return value from function being traced - `func` - Name of the function currently being traced +- `curtask` - Current task_struct as a u64. Functions: - `hist(int n)` - Produce a log2 histogram of values of `n` diff --git a/src/ast/codegen_llvm.cpp b/src/ast/codegen_llvm.cpp index 27fa477..d3dd1ff 100644 --- a/src/ast/codegen_llvm.cpp +++ b/src/ast/codegen_llvm.cpp @@ -70,6 +70,10 @@ void CodegenLLVM::visit(Builtin &builtin) { expr_ = b_.CreateGetCpuId(); } + else if (builtin.ident == "curtask") + { + expr_ = b_.CreateGetCurrentTask(); + } else if (builtin.ident == "comm") { AllocaInst *buf = b_.CreateAllocaBPF(builtin.type, "comm"); diff --git a/src/ast/irbuilderbpf.cpp b/src/ast/irbuilderbpf.cpp index ccae94c..3ccf1e6 100644 --- a/src/ast/irbuilderbpf.cpp +++ b/src/ast/irbuilderbpf.cpp @@ -307,6 +307,19 @@ CallInst *IRBuilderBPF::CreateGetCpuId() return CreateCall(getcpuid_func, {}, "get_cpu_id"); } +CallInst *IRBuilderBPF::CreateGetCurrentTask() +{ + // u64 bpf_get_current_task(void) + // Return: current task_struct + FunctionType *getcurtask_func_type = FunctionType::get(getInt64Ty(), false); + PointerType *getcurtask_func_ptr_type = PointerType::get(getcurtask_func_type, 0); + Constant *getcurtask_func = ConstantExpr::getCast( + Instruction::IntToPtr, + getInt64(BPF_FUNC_get_current_task), + getcurtask_func_ptr_type); + return CreateCall(getcurtask_func, {}, "get_cur_task"); +} + CallInst *IRBuilderBPF::CreateGetStackId(Value *ctx, bool ustack, size_t limit) { Value *map_ptr = CreateBpfPseudoCall(bpftrace_.stackid_maps_[limit]->mapfd_); diff --git a/src/ast/irbuilderbpf.h b/src/ast/irbuilderbpf.h index 0321e9a..ce2e3b6 100644 --- a/src/ast/irbuilderbpf.h +++ b/src/ast/irbuilderbpf.h @@ -36,6 +36,7 @@ public: CallInst *CreateGetPidTgid(); CallInst *CreateGetUidGid(); CallInst *CreateGetCpuId(); + CallInst *CreateGetCurrentTask(); CallInst *CreateGetStackId(Value *ctx, bool ustack); CallInst *CreateGetJoinMap(Value *ctx); void CreateGetCurrentComm(AllocaInst *buf, size_t size); diff --git a/src/ast/semantic_analyser.cpp b/src/ast/semantic_analyser.cpp index 8eb5744..64c9411 100644 --- a/src/ast/semantic_analyser.cpp +++ b/src/ast/semantic_analyser.cpp @@ -32,6 +32,7 @@ void SemanticAnalyser::visit(Builtin &builtin) builtin.ident == "uid" || builtin.ident == "gid" || builtin.ident == "cpu" || + builtin.ident == "curtask" || builtin.ident == "retval") { builtin.type = SizedType(Type::integer, 8); } diff --git a/src/lexer.l b/src/lexer.l index c5996b6..3bec616 100644 --- a/src/lexer.l +++ b/src/lexer.l @@ -38,7 +38,7 @@ header <(\\.|[_\-\./a-zA-Z0-9])*> {vspace}+ { loc.lines(yyleng); loc.step(); } "//".*$ // Comments -pid|tid|uid|gid|nsecs|cpu|comm|stack|ustack|arg[0-9]|retval|func|name { +pid|tid|uid|gid|nsecs|cpu|comm|stack|ustack|arg[0-9]|retval|func|name|curtask { return Parser::make_BUILTIN(yytext, loc); } {ident} { return Parser::make_IDENT(yytext, loc); } {path} { return Parser::make_PATH(yytext, loc); } diff --git a/tests/codegen.cpp b/tests/codegen.cpp index 38918ca..c00d25f 100644 --- a/tests/codegen.cpp +++ b/tests/codegen.cpp @@ -489,6 +489,42 @@ attributes #1 = { argmemonly nounwind } )EXPECTED"); } +TEST(codegen, builtin_curtask) +{ + test("kprobe:f { @x = curtask }", + +R"EXPECTED(; Function Attrs: nounwind +declare i64 @llvm.bpf.pseudo(i64, i64) #0 + +; Function Attrs: argmemonly nounwind +declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 + +define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f" { +entry: + %"@x_val" = alloca i64, align 8 + %"@x_key" = alloca i64, align 8 + %get_cur_task = tail call i64 inttoptr (i64 35 to i64 ()*)() + %1 = bitcast i64* %"@x_key" to i8* + call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) + store i64 0, i64* %"@x_key", align 8 + %2 = bitcast i64* %"@x_val" to i8* + call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) + store i64 %get_cur_task, i64* %"@x_val", align 8 + %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) + %update_elem = call i64 inttoptr (i64 2 to i64 (i8*, i8*, i8*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) + call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) + call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) + ret i64 0 +} + +; Function Attrs: argmemonly nounwind +declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 + +attributes #0 = { nounwind } +attributes #1 = { argmemonly nounwind } +)EXPECTED"); +} + TEST(codegen, builtin_comm) { test("kprobe:f { @x = comm }", diff --git a/tests/parser.cpp b/tests/parser.cpp index cff201b..49b83be 100644 --- a/tests/parser.cpp +++ b/tests/parser.cpp @@ -29,6 +29,7 @@ TEST(Parser, builtin_variables) test("kprobe:f { gid }", "Program\n kprobe:f\n builtin: gid\n"); test("kprobe:f { nsecs }", "Program\n kprobe:f\n builtin: nsecs\n"); test("kprobe:f { cpu }", "Program\n kprobe:f\n builtin: cpu\n"); + test("kprobe:f { curtask }", "Program\n kprobe:f\n builtin: curtask\n"); test("kprobe:f { comm }", "Program\n kprobe:f\n builtin: comm\n"); test("kprobe:f { stack }", "Program\n kprobe:f\n builtin: stack\n"); test("kprobe:f { ustack }", "Program\n kprobe:f\n builtin: ustack\n"); diff --git a/tests/semantic_analyser.cpp b/tests/semantic_analyser.cpp index d6e26b8..d9b24e2 100644 --- a/tests/semantic_analyser.cpp +++ b/tests/semantic_analyser.cpp @@ -67,6 +67,7 @@ TEST(semantic_analyser, builtin_variables) test("kprobe:f { gid }", 0); test("kprobe:f { nsecs }", 0); test("kprobe:f { cpu }", 0); + test("kprobe:f { curtask }", 0); test("kprobe:f { comm }", 0); test("kprobe:f { stack }", 0); test("kprobe:f { ustack }", 0); ``` ### 3. Codegen: arguments & return value See the implementation of `lhist()` for an example of pulling in arguments. Commit: https://github.com/iovisor/bpftrace/commit/6bdd1198e04392aa468b12357a051816f2cc50e4 You'll also notice that the builtins finish by setting `expr_` to the final result. This is taking the node in the AST and replacing it with the computed expression. Calls don't necessarily do this: for example, `reg()` sets `expr_` since it returns a value, but `printf()` sets `expr_` to `nullptr`, since it does not return a value. ### 4. Codegen: sum(), min(), max(), avg(), stats() These are examples of adding new map functions, and the required components. Since the functions themselves are simple, they are good examples of codegen. They were all added in a single commit: https://github.com/iovisor/bpftrace/commit/0746ff9c048ed503c606b736ad3a78e141c22890 This also shows the bpftrace components that were added to support these: `BPFtrace::print_map_stats()`, `BPFtrace::max_value()`, `BPFtrace::min_value()`. # Probes Probes are reasonably straightforward. We use libbpf/libbcc, both from [bcc](https://github.com/iovisor/bcc), to create the probes via functions such as `bpf_attach_kprobe()`, `bpf_attach_uprobe()`, and `bpf_attach_tracepoint()`. We also use USDT helpers such as `bcc_usdt_enable_probe()` ## 1. Probes: Interval The addition of the `interval` probe type is a simple example of adding a probe, and the components required: https://github.com/iovisor/bpftrace/commit/c1e7b05be917ad6fa23a210d047bf9387745bf32 diff: ```diff diff --git a/README.md b/README.md index b73a6d1..4654f65 100644 --- a/README.md +++ b/README.md @@ -157,8 +157,8 @@ Attach script to a statically defined tracepoint in the kernel: Tracepoints are guaranteed to be stable between kernel versions, unlike kprobes. -### timers -Run the script at specified time intervals: +### profile +Run the script on all CPUs at specified time intervals: `profile:hz:99 { ... }` @@ -168,6 +168,13 @@ Run the script at specified time intervals: `profile:us:1500 { ... }` +### interval +Run the script once per interval, for printing interval output: + +`interval:s:1 { ... }` + +`interval:ms:20 { ... }` + ### Multiple attachment points A single probe can be attached to multiple events: diff --git a/src/ast/semantic_analyser.cpp b/src/ast/semantic_analyser.cpp index a08eaf7..2a79553 100644 --- a/src/ast/semantic_analyser.cpp +++ b/src/ast/semantic_analyser.cpp @@ -478,6 +478,15 @@ void SemanticAnalyser::visit(AttachPoint &ap) else if (ap.freq <= 0) err_ << "profile frequency should be a positive integer" << std::endl; } + else if (ap.provider == "interval") { + if (ap.target == "") + err_ << "interval probe must have unit of time" << std::endl; + else if (ap.target != "ms" && + ap.target != "s") + err_ << ap.target << " is not an accepted unit of time" << std::endl; + if (ap.func != "") + err_ << "interval probe must have an integer frequency" << std::endl; + } else if (ap.provider == "BEGIN" || ap.provider == "END") { if (ap.target != "" || ap.func != "") err_ << "BEGIN/END probes should not have a target" << std::endl; diff --git a/src/attached_probe.cpp b/src/attached_probe.cpp index 598ecdc..991111b 100644 --- a/src/attached_probe.cpp +++ b/src/attached_probe.cpp @@ -36,6 +36,7 @@ bpf_prog_type progtype(ProbeType t) case ProbeType::uretprobe: return BPF_PROG_TYPE_KPROBE; break; case ProbeType::tracepoint: return BPF_PROG_TYPE_TRACEPOINT; break; case ProbeType::profile: return BPF_PROG_TYPE_PERF_EVENT; break; + case ProbeType::interval: return BPF_PROG_TYPE_PERF_EVENT; break; default: abort(); } } @@ -61,6 +62,9 @@ AttachedProbe::AttachedProbe(Probe &probe, std::tuple &fun case ProbeType::profile: attach_profile(); break; + case ProbeType::interval: + attach_interval(); + break; default: abort(); } @@ -93,6 +97,7 @@ AttachedProbe::~AttachedProbe() err = bpf_detach_tracepoint(probe_.path.c_str(), eventname().c_str()); break; case ProbeType::profile: + case ProbeType::interval: break; default: abort(); @@ -279,4 +284,35 @@ void AttachedProbe::attach_profile() } } +void AttachedProbe::attach_interval() +{ + int pid = -1; + int group_fd = -1; + int cpu = 0; + + uint64_t period, freq; + if (probe_.path == "s") + { + period = probe_.freq * 1e9; + freq = 0; + } + else if (probe_.path == "ms") + { + period = probe_.freq * 1e6; + freq = 0; + } + else + { + abort(); + } + + int perf_event_fd = bpf_attach_perf_event(progfd_, PERF_TYPE_SOFTWARE, + PERF_COUNT_SW_CPU_CLOCK, period, freq, pid, cpu, group_fd); + + if (perf_event_fd < 0) + throw std::runtime_error("Error attaching probe: " + probe_.name); + + perf_event_fds_.push_back(perf_event_fd); +} + } // namespace bpftrace diff --git a/src/attached_probe.h b/src/attached_probe.h index 86b610c..97036e3 100644 --- a/src/attached_probe.h +++ b/src/attached_probe.h @@ -27,6 +27,7 @@ private: void attach_uprobe(); void attach_tracepoint(); void attach_profile(); + void attach_interval(); Probe &probe_; std::tuple &func_; diff --git a/src/types.cpp b/src/types.cpp index 6813c72..2abaad6 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -57,6 +57,8 @@ ProbeType probetype(const std::string &type) return ProbeType::tracepoint; else if (type == "profile") return ProbeType::profile; + else if (type == "interval") + return ProbeType::interval; abort(); } diff --git a/src/types.h b/src/types.h index 4c4524a..6c94eac 100644 --- a/src/types.h +++ b/src/types.h @@ -52,6 +52,7 @@ enum class ProbeType uretprobe, tracepoint, profile, + interval, }; std::string typestr(Type t); diff --git a/tests/bpftrace.cpp b/tests/bpftrace.cpp index 3c3b036..50b6538 100644 --- a/tests/bpftrace.cpp +++ b/tests/bpftrace.cpp @@ -59,6 +59,14 @@ void check_profile(Probe &p, const std::string &unit, int freq, const std::strin EXPECT_EQ("profile:" + unit + ":" + std::to_string(freq), p.name); } +void check_interval(Probe &p, const std::string &unit, int freq, const std::string &prog_name) +{ + EXPECT_EQ(ProbeType::interval, p.type); + EXPECT_EQ(freq, p.freq); + EXPECT_EQ(prog_name, p.prog_name); + EXPECT_EQ("interval:" + unit + ":" + std::to_string(freq), p.name); +} + void check_special_probe(Probe &p, const std::string &attach_point, const std::string &prog_name) { EXPECT_EQ(ProbeType::uprobe, p.type); @@ -309,6 +317,22 @@ TEST(bpftrace, add_probes_profile) check_profile(bpftrace.get_probes().at(0), "ms", 997, probe_prog_name); } +TEST(bpftrace, add_probes_interval) +{ + ast::AttachPoint a("interval", "s", 1); + ast::AttachPointList attach_points = { &a }; + ast::Probe probe(&attach_points, nullptr, nullptr); + + StrictMock bpftrace; + + EXPECT_EQ(0, bpftrace.add_probe(probe)); + EXPECT_EQ(1, bpftrace.get_probes().size()); + EXPECT_EQ(0, bpftrace.get_special_probes().size()); + + std::string probe_prog_name = "interval:s:1"; + check_interval(bpftrace.get_probes().at(0), "s", 1, probe_prog_name); +} + std::pair, std::vector> key_value_pair_int(std::vector key, int val) { std::pair, std::vector> pair; diff --git a/tests/parser.cpp b/tests/parser.cpp index 786f3d0..d2db79b 100644 --- a/tests/parser.cpp +++ b/tests/parser.cpp @@ -260,6 +260,14 @@ TEST(Parser, profile_probe) " int: 1\n"); } +TEST(Parser, interval_probe) +{ + test("interval:s:1 { 1 }", + "Program\n" + " interval:s:1\n" + " int: 1\n"); +} + TEST(Parser, multiple_attach_points_kprobe) { test("BEGIN,kprobe:sys_open,uprobe:/bin/sh:foo,tracepoint:syscalls:sys_enter_* { 1 }", ``` bpftrace-0.9.4/docs/reference_guide.md000066400000000000000000002361631361633214400177150ustar00rootroot00000000000000# bpftrace Reference Guide For a reference summary, see the [README.md](../README.md) for the sections on [Probe types](../README.md#probe-types) and [Builtins](../README.md#builtins). This is a work in progress. If something is missing, check the bpftrace source to see if these docs are just out of date. And if you find something, please file an issue or pull request to update these docs. Also, please keep these docs as terse as possible to maintain it's brevity (inspired by the 6-page awk summary from page 106 of [v7vol2b.pdf](https://9p.io/7thEdMan/bswv7.html)). Leave longer examples and discussion to other files in /docs, the /tools/\*\_examples.txt files, or blog posts and other articles. ## Contents - [Terminology](#terminology) - [Usage](#usage) - [1. Hello World](#1-hello-world) - [2. `-e 'program'`: One-Liners](#2--e-program-one-liners) - [3. `filename`: Program Files](#3-filename-program-files) - [4. `-l`: Listing Probes](#4--l-listing-probes) - [5. `-d`: Debug Output](#5--d-debug-output) - [6. `-v`: Verbose Output](#6--v-verbose-output) - [7. Preprocessor Options](#7-preprocessor-options) - [8. Other Options](#8-other-options) - [9. Environment Variables](#9-environment-variables) - [10. Clang Environment Variables](#10-clang-environment-variables) - [Language](#language) - [1. `{...}`: Action Blocks](#1--action-blocks) - [2. `/.../`: Filtering](#2--filtering) - [3. `//`, `/*`: Comments](#3---comments) - [4. `->`: C Struct Navigation](#4---c-struct-navigation) - [5. `struct`: Struct Declaration](#5-struct-struct-declaration) - [6. `? :`: ternary operators](#6---ternary-operators) - [7. `if () {...} else {...}`: if-else statements](#7-if---else--if-else-statements) - [8. `unroll () {...}`: unroll](#8-unroll---unroll) - [9. `++ and --`: increment operators](#9--and----increment-operators) - [10. `[]`: Array access](#10--array-access) - [Probes](#probes) - [1. `kprobe`/`kretprobe`: Dynamic Tracing, Kernel-Level](#1-kprobekretprobe-dynamic-tracing-kernel-level) - [2. `kprobe`/`kretprobe`: Dynamic Tracing, Kernel-Level Arguments](#2-kprobekretprobe-dynamic-tracing-kernel-level-arguments) - [3. `uprobe`/`uretprobe`: Dynamic Tracing, User-Level](#3-uprobeuretprobe-dynamic-tracing-user-level) - [4. `uprobe`/`uretprobe`: Dynamic Tracing, User-Level Arguments](#4-uprobeuretprobe-dynamic-tracing-user-level-arguments) - [5. `tracepoint`: Static Tracing, Kernel-Level](#5-tracepoint-static-tracing-kernel-level) - [6. `tracepoint`: Static Tracing, Kernel-Level Arguments](#6-tracepoint-static-tracing-kernel-level-arguments) - [7. `usdt`: Static Tracing, User-Level](#7-usdt-static-tracing-user-level) - [8. `usdt`: Static Tracing, User-Level Arguments](#8-usdt-static-tracing-user-level-arguments) - [9. `profile`: Timed Sampling Events](#9-profile-timed-sampling-events) - [10. `interval`: Timed Output](#10-interval-timed-output) - [11. `software`: Pre-defined Software Events](#11-software-pre-defined-software-events) - [12. `hardware`: Pre-defined Hardware Events](#12-hardware-pre-defined-hardware-events) - [13. `BEGIN`/`END`: Built-in events](#13-beginend-built-in-events) - [14. `watchpoint`: Memory watchpoints](#14-watchpoint-memory-watchpoints) - [Variables](#variables) - [1. Builtins](#1-builtins) - [2. `@`, `$`: Basic Variables](#2---basic-variables) - [3. `@[]`: Associative Arrays](#3--associative-arrays) - [4. `count()`: Frequency Counting](#4-count-frequency-counting) - [5. `hist()`, `lhist()`: Histograms](#5-hist-lhist-histograms) - [6. `nsecs`: Timestamps and Time Deltas](#6-nsecs-timestamps-and-time-deltas) - [7. `kstack`: Stack Traces, Kernel](#7-kstack-stack-traces-kernel) - [8. `ustack`: Stack Traces, User](#8-ustack-stack-traces-user) - [9. `$1`, ..., `$N`, `$#`: Positional Parameters](#9-1--n--positional-parameters) - [Functions](#functions) - [1. Builtins](#1-builtins-1) - [2. `printf()`: Print Formatted](#2-printf-Printing) - [3. `time()`: Time](#3-time-time) - [4. `join()`: Join](#4-join-join) - [5. `str()`: Strings](#5-str-strings) - [6. `ksym()`: Symbol Resolution, Kernel-Level](#6-ksym-symbol-resolution-kernel-level) - [7. `usym()`: Symbol Resolution, User-Level](#7-usym-symbol-resolution-user-level) - [8. `kaddr()`: Address Resolution, Kernel-Level](#8-kaddr-address-resolution-kernel-level) - [9. `uaddr()`: Address Resolution, User-Level](#9-uaddr-address-resolution-user-level) - [10. `reg()`: Registers](#10-reg-registers) - [11. `system()`: System](#11-system-system) - [12. `exit()`: Exit](#12-exit-exit) - [13. `cgroupid()`: Resolve cgroup ID](#13-cgroupid-resolve-cgroup-id) - [14. `ntop()`: Convert IP address data to text](#14-ntop-convert-ip-address-data-to-text) - [15. `kstack()`: Stack Traces, Kernel](#15-kstack-stack-traces-kernel) - [16. `ustack()`: Stack Traces, User](#16-ustack-stack-traces-user) - [17. `cat()`: Print file content](#17-cat-print-file-content) - [18. `signal()`: Send a signal to the current task](#18-signal-send-a-signal-to-current-task) - [19. `strncmp()`: Compare first n characters of two strings](#19-strncmp-compare-first-n-characters-of-two-strings) - [20. `override()`: Override return value](#20-override-override-return-value) - [Map Functions](#map-functions) - [1. Builtins](#1-builtins-2) - [2. `count()`: Count](#2-count-count) - [3. `sum()`: Sum](#3-sum-sum) - [4. `avg()`: Average](#4-avg-average) - [5. `min()`: Minimum](#5-min-minimum) - [6. `max()`: Maximum](#6-max-maximum) - [7. `stats()`: Stats](#7-stats-stats) - [8. `hist()`: Log2 Histogram](#8-hist-log2-histogram) - [9. `lhist()`: Linear Histogram](#9-lhist-linear-histogram) - [10. `print()`: Print Map](#10-print-print-map) - [Output](#output) - [1. `printf()`: Per-Event Output](#1-printf-per-event-output) - [2. `interval`: Interval Output](#2-interval-interval-output) - [3. `hist()`, `printf()`: Histogram Printing](#3-hist-print-histogram-printing) - [Advanced Tools](#advanced-tools) - [Errors](#errors) # Terminology Term | Description ---- | ----------- BPF | Berkeley Packet Filter: a kernel technology originally developed for optimizing the processing of packet filters (eg, tcpdump expressions) eBPF | Enhanced BPF: a kernel technology that extends BPF so that it can execute more generic programs on any events, such as the bpftrace programs listed below. It makes use of the BPF sandboxed virtual machine environment. Also note that eBPF is often just referred to as BPF. probe | An instrumentation point in software or hardware, that generates events that can execute bpftrace programs. static tracing | Hard-coded instrumentation points in code. Since these are fixed, they may be provided as part of a stable API, and documented. dynamic tracing | Also known as dynamic instrumentation, this is a technology that can instrument any software event, such as function calls and returns, by live modification of instruction text. Target software usually does not need special capabilities to support dynamic tracing, other than a symbol table that bpftrace can read. Since this instruments all software text, it is not considered a stable API, and the target functions may not be documented outside of their source code. tracepoints | A Linux kernel technology for providing static tracing. kprobes | A Linux kernel technology for providing dynamic tracing of kernel functions. uprobes | A Linux kernel technology for providing dynamic tracing of user-level functions. USDT | User Statically-Defined Tracing: static tracing points for user-level software. Some applications support USDT. BPF map | A BPF memory object, which is used by bpftrace to create many higher-level objects. BTF | BPF Type Format: the metadata format which encodes the debug info related to BPF program/map. # Usage Command line usage is summarized by bpftrace without options: ``` # bpftrace USAGE: bpftrace [options] filename bpftrace [options] -e 'program' OPTIONS: -B MODE output buffering mode ('line', 'full', or 'none') -d debug info dry run -dd verbose debug info dry run -b force BTF (BPF type format) processing -e 'program' execute this program -h show this help message -I DIR add the specified DIR to the search path for include files. --include FILE adds an implicit #include which is read before the source file is preprocessed. -l [search] list probes -p PID enable USDT probes on PID -c 'CMD' run CMD and enable USDT probes on resulting process -v verbose messages --version bpftrace version ENVIRONMENT: BPFTRACE_STRLEN [default: 64] bytes on BPF stack per str() BPFTRACE_NO_CPP_DEMANGLE [default: 0] disable C++ symbol demangling BPFTRACE_MAP_KEYS_MAX [default: 4096] max keys in a map BPFTRACE_MAX_PROBES [default: 512] max number of probes bpftrace can attach to BPFTRACE_VMLINUX [default: None] vmlinux path used for kernel symbol resolution BPFTRACE_BTF [default: None] BTF file EXAMPLES: bpftrace -l '*sleep*' list probes containing "sleep" bpftrace -e 'kprobe:do_nanosleep { printf("PID %d sleeping...\n", pid); }' trace processes calling sleep bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }' count syscalls by process name ``` ## 1. Hello World The most basic example of a bpftrace program: ``` # bpftrace -e 'BEGIN { printf("Hello, World!\n"); }' Attaching 1 probe... Hello, World! ^C ``` The syntax to this program will be explained in the [Language](#language) section. In this section, we'll cover tool usage. A program will continue running until Ctrl-C is hit, or an `exit()` function is called. When a program exits, all populated maps are printed: this behavior, and maps, are explained in later sections. ## 2. `-e 'program'`: One-Liners The `-e` option allows a program to be specified, and is a way to construct one-liners: ``` # bpftrace -e 'tracepoint:syscalls:sys_enter_nanosleep { printf("%s is sleeping.\n", comm); }' Attaching 1 probe... iscsid is sleeping. irqbalance is sleeping. iscsid is sleeping. iscsid is sleeping. [...] ``` This example is printing when processes call the nanosleep syscall. Again, the syntax of the program will be explained in the [Language](#language) section. ## 3. `filename`: Program Files Programs saved as files are often called scripts, and can be executed by specifying their file name. We'll often use a `.bt` file extension, short for bpftrace, but the extension is ignored. For example, listing the sleepers.bt file using `cat -n` (which enumerates the output lines): ``` # cat -n sleepers.bt 1 tracepoint:syscalls:sys_enter_nanosleep 2 { 3 printf("%s is sleeping.\n", comm); 4 } ``` Running sleepers.bt: ``` # bpftrace sleepers.bt Attaching 1 probe... iscsid is sleeping. iscsid is sleeping. [...] ``` It can also be made executable to run stand-alone. Start by adding an interpreter line at the top (`#!`) with either the path to your installed bpftrace (/usr/local/bin is the default) or the path to `env` (usually just `/usr/bin/env`) followed by `bpftrace` (so it will find bpftrace in your `$PATH`): ``` 1 #!/usr/local/bin/bpftrace 2 3 tracepoint:syscalls:sys_enter_nanosleep 4 { 5 printf("%s is sleeping.\n", comm); 6 } ``` Then make it executable: ``` # chmod 755 sleepers.bt # ./sleepers.bt Attaching 1 probe... iscsid is sleeping. iscsid is sleeping. [...] ``` ## 4. `-l`: Listing Probes Probes from the tracepoint and kprobe libraries can be listed with `-l`. ``` # bpftrace -l | more tracepoint:xfs:xfs_attr_list_sf tracepoint:xfs:xfs_attr_list_sf_all tracepoint:xfs:xfs_attr_list_leaf tracepoint:xfs:xfs_attr_list_leaf_end [...] # bpftrace -l | wc -l 46260 ``` Other libraries generate probes dynamically, such as uprobe, and require specific ways to determine available probes. See the later [Probes](#probes) sections. Search terms can be added: ``` # bpftrace -l '*nanosleep*' tracepoint:syscalls:sys_enter_clock_nanosleep tracepoint:syscalls:sys_exit_clock_nanosleep tracepoint:syscalls:sys_enter_nanosleep tracepoint:syscalls:sys_exit_nanosleep kprobe:nanosleep_copyout kprobe:hrtimer_nanosleep [...] ``` The `-v` option when listing tracepoints will show their arguments for use from the args builtin. For example: ``` # bpftrace -lv tracepoint:syscalls:sys_enter_open tracepoint:syscalls:sys_enter_open int __syscall_nr; const char * filename; int flags; umode_t mode; ``` ## 5. `-d`: Debug Output The `-d` option produces debug output, and does not run the program. This is mostly useful for debugging issues with bpftrace itself. You can also use `-dd` to produce a more verbose debug output, which will also print unoptimized IR. **If you are an end-user of bpftrace, you should not normally need the `-d` or `-v` options, and you can skip to the [Language](#language) section.** ``` # bpftrace -d -e 'tracepoint:syscalls:sys_enter_nanosleep { printf("%s is sleeping.\n", comm); }' Program tracepoint:syscalls:sys_enter_nanosleep call: printf string: %s is sleeping.\n builtin: comm [...] ``` The output begins with `Program` and then an abstract syntax tree (AST) representation of the program. Continued: ``` [...] %printf_t = type { i64, [16 x i8] } [...] define i64 @"tracepoint:syscalls:sys_enter_nanosleep"(i8*) local_unnamed_addr section "s_tracepoint:syscalls:sys_enter_nanosleep" { entry: %comm = alloca [16 x i8], align 1 %printf_args = alloca %printf_t, align 8 %1 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = getelementptr inbounds [16 x i8], [16 x i8]* %comm, i64 0, i64 0 %3 = bitcast %printf_t* %printf_args to i8* call void @llvm.memset.p0i8.i64(i8* nonnull %3, i8 0, i64 24, i32 8, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) call void @llvm.memset.p0i8.i64(i8* nonnull %2, i8 0, i64 16, i32 1, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm, i64 16) %4 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1, i64 0 call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull %4, i8* nonnull %2, i64 16, i32 1, i1 false) %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i8*, i64, i8*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 24) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 [...] ``` This section shows the llvm intermediate representation (IR) assembly, which is then compiled into BPF. ## 6. `-v`: Verbose Output The `-v` option prints more information about the program as it is run: ``` # bpftrace -v -e 'tracepoint:syscalls:sys_enter_nanosleep { printf("%s is sleeping.\n", comm); }' Attaching 1 probe... Bytecode: 0: (bf) r6 = r1 1: (b7) r1 = 0 2: (7b) *(u64 *)(r10 -24) = r1 3: (7b) *(u64 *)(r10 -32) = r1 4: (7b) *(u64 *)(r10 -40) = r1 5: (7b) *(u64 *)(r10 -8) = r1 6: (7b) *(u64 *)(r10 -16) = r1 7: (bf) r1 = r10 8: (07) r1 += -16 9: (b7) r2 = 16 10: (85) call bpf_get_current_comm#16 11: (79) r1 = *(u64 *)(r10 -16) 12: (7b) *(u64 *)(r10 -32) = r1 13: (79) r1 = *(u64 *)(r10 -8) 14: (7b) *(u64 *)(r10 -24) = r1 15: (18) r7 = 0xffff9044e65f1000 17: (85) call bpf_get_smp_processor_id#8 18: (bf) r4 = r10 19: (07) r4 += -40 20: (bf) r1 = r6 21: (bf) r2 = r7 22: (bf) r3 = r0 23: (b7) r5 = 24 24: (85) call bpf_perf_event_output#25 25: (b7) r0 = 0 26: (95) exit processed 26 insns (limit 131072), stack depth 40 Attaching tracepoint:syscalls:sys_enter_nanosleep Running... iscsid is sleeping. iscsid is sleeping. [...] ``` This includes `Bytecode:` and then the eBPF bytecode after it was compiled from the llvm assembly. ## 7. Preprocessor Options The `-I` option can be used to add directories to the list of directories that bpftrace uses to look for headers. Can be defined multiple times. ``` # cat program.bt #include BEGIN { @ = FOO } # bpftrace program.bt definitions.h:1:10: fatal error: 'foo.h' file not found # /tmp/include foo.h # bpftrace -I /tmp/include program.bt Attaching 1 probe... ``` The `--include` option can be used to include headers by default. Can be defined multiple times. Headers are included in the order they are defined, and they are included before any other include in the program being executed. ``` # bpftrace --include linux/path.h --include linux/dcache.h \ -e 'kprobe:vfs_open { printf("open path: %s\n", str(((path *)arg0)->dentry->d_name.name)); }' Attaching 1 probe... open path: .com.google.Chrome.ASsbu2 open path: .com.google.Chrome.gimc10 open path: .com.google.Chrome.R1234s ``` ## 8. Other Options The `--version` option prints the bpftrace version: ``` # bpftrace --version bpftrace v0.8-90-g585e-dirty ``` ## 9. Environment Variables ### 9.1 `BPFTRACE_STRLEN` Default: 64 Number of bytes allocated on the BPF stack for the string returned by str(). Make this larger if you wish to read bigger strings with str(). Beware that the BPF stack is small (512 bytes), and that you pay the toll again inside printf() (whilst it composes a perf event output buffer). So in practice you can only grow this to about 200 bytes. Support for even larger strings is [being discussed](https://github.com/iovisor/bpftrace/issues/305). ### 9.2 `BPFTRACE_NO_CPP_DEMANGLE` Default: 0 C++ symbol demangling in userspace stack traces is enabled by default. This feature can be turned off by setting the value of this environment variable to `1`. ### 9.3 `BPFTRACE_MAP_KEYS_MAX` Default: 4096 This is the maximum number of keys that can be stored in a map. Increasing the value will consume more memory and increase startup times. There are some cases where you will want to: for example, sampling stack traces, recording timestamps for each page, etc. ### 9.4 `BPFTRACE_MAX_PROBES` Default: 512 This is the maximum number of probes that bpftrace can attach to. Increasing the value will consume more memory, increase startup times and can incur high performance overhead or even freeze or crash the system. ### 9.5 `BPFTRACE_VMLINUX` Default: None This specifies the vmlinux path used for kernel symbol resolution when attaching kprobe to offset. If this value is not given, bpftrace searches vmlinux from pre defined locations. See src/attached_probe.cpp:find_vmlinux() for details. ### 9.6 `BPFTRACE_BTF` Default: None The path to a BTF file. By default, bpftrace searches several locations to find a BTF file. See src/btf.cpp for the details. ## 10. Clang Environment Variables bpftrace parses header files using libclang, the C interface to Clang. Thus environment variables affecting the clang toolchain can be used. For example, if header files are included from a non-default directory, the `CPATH` or `C_INCLUDE_PATH` environment variables can be set to allow clang to locate the files. See clang documentation for more information on these environment variables and their usage. # Language ## 1. `{...}`: Action Blocks Syntax: `probe[,probe,...] /filter/ { action }` A bpftrace program can have multiple action blocks. The filter is optional. Example: ``` # bpftrace -e 'kprobe:do_sys_open { printf("opening: %s\n", str(arg1)); }' Attaching 1 probe... opening: /proc/cpuinfo opening: /proc/stat opening: /proc/diskstats opening: /proc/stat opening: /proc/vmstat [...] ``` This is a one-liner invocation of bpftrace. The probe is `kprobe:do_sys_open`. When that probe "fires" (the instrumentation event occurred) the action will be executed, which consists of a `print()` statement. Explanations of the probe and action are in the sections that follow. ## 2. `/.../`: Filtering Syntax: `/filter/` Filters (also known as predicates) can be added after probe names. The probe still fires, but it will skip the action unless the filter is true. Examples: ``` # bpftrace -e 'kprobe:vfs_read /arg2 < 16/ { printf("small read: %d byte buffer\n", arg2); }' Attaching 1 probe... small read: 8 byte buffer small read: 8 byte buffer small read: 8 byte buffer small read: 8 byte buffer small read: 8 byte buffer small read: 12 byte buffer ^C ``` ``` # bpftrace -e 'kprobe:vfs_read /comm == "bash"/ { printf("read by %s\n", comm); }' Attaching 1 probe... read by bash read by bash read by bash read by bash ^C ``` ## 3. `//`, `/*`: Comments Syntax ``` // single-line comment /* * multi-line comment */ ``` These can be used in bpftrace scripts to document your code. ## 4. `->`: C Struct Navigation tracepoint example: ``` # bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }' Attaching 1 probe... snmpd /proc/diskstats snmpd /proc/stat snmpd /proc/vmstat [...] ``` This is returning the `filename` member from the `args` struct, which for tracepoint probes contains the tracepoint arguments. See the [Static Tracing, Kernel-Level Arguments](#6-tracepoint-static-tracing-kernel-level-arguments) section for the contents of this struct. kprobe example: ``` # cat path.bt #include #include kprobe:vfs_open { printf("open path: %s\n", str(((struct path *)arg0)->dentry->d_name.name)); } # bpftrace path.bt Attaching 1 probe... open path: dev open path: if_inet6 open path: retrans_time_ms [...] ``` This uses dynamic tracing of the `vfs_open()` kernel function, via the short script path.bt. Some kernel headers needed to be included to understand the `path` and `dentry` structs. ## 5. `struct`: Struct Declaration Example: ``` // from fs/namei.c: struct nameidata { struct path path; struct qstr last; // [...] }; ``` You can define your own structs when needed. In some cases, kernel structs are not declared in the kernel headers package, and are declared manually in bpftrace tools (or partial structs are: enough to reach the member to dereference). ## 6. `? :`: ternary operators Example: ``` # bpftrace -e 'tracepoint:syscalls:sys_exit_read { @error[args->ret < 0 ? - args->ret : 0] = count(); }' Attaching 1 probe... ^C @error[11]: 24 @error[0]: 78 ``` ## 7. `if () {...} else {...}`: if-else statements Example: ``` # bpftrace -e 'tracepoint:syscalls:sys_enter_read { @reads = count(); if (args->count > 1024) { @large = count(); } }' Attaching 1 probe... ^C @large: 72 @reads: 80 ``` ## 8. `unroll () {...}`: Unroll Example: ``` # bpftrace -e 'kprobe:do_nanosleep { $i = 1; unroll(5) { printf("i: %d\n", $i); $i = $i + 1; } }' Attaching 1 probe... i: 1 i: 2 i: 3 i: 4 i: 5 ^C ``` ## 9. `++` and `--`: Increment operators `++` and `--` can be used to conveniently increment or decrement counters in maps or variables. Note that maps will be implictly declared and initialized to 0 if not already declared or defined. Scratch variables must be initialized before using these operators. Example - variable: ``` bpftrace -e 'BEGIN { $x = 0; $x++; $x++; printf("x: %d\n", $x); }' Attaching 1 probe... x: 2 ^C ``` Example - map: ``` bpftrace -e 'k:vfs_read { @++ }' Attaching 1 probe... ^C @: 12807 ``` Example - map with key: ``` # bpftrace -e 'k:vfs_read { @[probe]++ }' Attaching 1 probe... ^C @[kprobe:vfs_read]: 13369 ``` ## 10. `[]`: Array Access You may access one-dimensional constant arrays with the array access operator `[]`. Example: ``` # bpftrace -e 'struct MyStruct { int y[4]; } uprobe:./testprogs/array_access:test_struct { $s = (struct MyStruct *) arg0; @x = $s->y[0]; exit(); }' Attaching 1 probe... @x: 1 ``` # Probes - `kprobe` - kernel function start - `kretprobe` - kernel function return - `uprobe` - user-level function start - `uretprobe` - user-level function return - `tracepoint` - kernel static tracepoints - `usdt` - user-level static tracepoints - `profile` - timed sampling - `interval` - timed output - `software` - kernel software events - `hardware` - processor-level events Some probe types allow wildcards to match multiple probes, eg, `kprobe:vfs_*`. You may also specify multiple attach points for an action block using a comma separated list. ## 1. `kprobe`/`kretprobe`: Dynamic Tracing, Kernel-Level Syntax: ``` kprobe:function_name[+offset] kretprobe:function_name ``` These use kprobes (a Linux kernel capability). `kprobe` instruments the beginning of a function's execution, and `kretprobe` instruments the end (its return). Examples: ``` # bpftrace -e 'kprobe:do_nanosleep { printf("sleep by %d\n", tid); }' Attaching 1 probe... sleep by 1396 sleep by 3669 sleep by 1396 sleep by 27662 sleep by 3669 ^C ``` It's also possible to specify offset within the probed function: ``` # gdb -q /usr/lib/debug/boot/vmlinux-`uname -r` --ex 'disassemble do_sys_open' Reading symbols from /usr/lib/debug/boot/vmlinux-5.0.0-32-generic...done. Dump of assembler code for function do_sys_open: 0xffffffff812b2ed0 <+0>: callq 0xffffffff81c01820 <__fentry__> 0xffffffff812b2ed5 <+5>: push %rbp 0xffffffff812b2ed6 <+6>: mov %rsp,%rbp 0xffffffff812b2ed9 <+9>: push %r15 ... # bpftrace -e 'kprobe:do_sys_open+9 { printf("in here\n"); }' Attaching 1 probe... in here ... ``` The address is being checked using vmlinux (with debug symbols) if it's aligned with instruction boundaries and within the function. If it's not, we fail to add it: ``` # bpftrace -e 'kprobe:do_sys_open+1 { printf("in here\n"); }' Attaching 1 probe... Could not add kprobe into middle of instruction: /usr/lib/debug/boot/vmlinux-5.0.0-32-generic:do_sys_open+1 ``` If bpftrace is compiled with `ALLOW_UNSAFE_PROBE` option, you can use --unsafe option to skip the check. In this case, linux kernel still checks instruction alignment. The default vmlinux path can be overridden using the environment variable `BPFTRACE_VMLINUX`. ## 2. `kprobe`/`kretprobe`: Dynamic Tracing, Kernel-Level Arguments Syntax: ``` kprobe: arg0, arg1, ..., argN kretprobe: retval ``` Arguments can be accessed via these variables names. `arg0` is the first argument and can only be accessed with a `kprobe`. `retval` is the return value for the instrumented function, and can only be accessed on `kretprobe`. Examples: ``` # bpftrace -e 'kprobe:do_sys_open { printf("opening: %s\n", str(arg1)); }' Attaching 1 probe... opening: /proc/cpuinfo opening: /proc/stat opening: /proc/diskstats opening: /proc/stat opening: /proc/vmstat [...] ``` ``` # bpftrace -e 'kprobe:do_sys_open { printf("open flags: %d\n", arg2); }' Attaching 1 probe... open flags: 557056 open flags: 32768 open flags: 32768 open flags: 32768 [...] ``` ``` # bpftrace -e 'kretprobe:do_sys_open { printf("returned: %d\n", retval); }' Attaching 1 probe... returned: 8 returned: 21 returned: -2 returned: 21 [...] ``` As an example of struct arguments: ``` # cat path.bt #include #include kprobe:vfs_open { printf("open path: %s\n", str(((struct path *)arg0)->dentry->d_name.name)); } # bpftrace path.bt Attaching 1 probe... open path: dev open path: if_inet6 open path: retrans_time_ms [...] ``` Here arg0 was casted as a (struct path \*), since that is the first argument to vfs_open(). The struct support is the same as bcc, and based on available kernel headers. This means that many, but not all, structs will be available, and you may need to manually define some structs. If the kernel has BTF (BPF Type Format) data, all kernel structs are always available without defining them. For example: ``` # bpftrace -e 'kprobe:vfs_open { printf("open path: %s\n", \ str(((struct path *)arg0)->dentry->d_name.name)); }' Attaching 1 probe... open path: cmdline open path: interrupts [...] ``` Requirements for using BTF: - Linux 4.18+ with `CONFIG_DEBUG_INFO_BTF=y` - Building requires dwarves with pahole v1.13+ - bpftrace v0.9.3+ with BTF support (built with libbpf v0.0.4+) See [kernel documentation](https://www.kernel.org/doc/html/latest/bpf/btf.html) for more information on BTF. ## 3. `uprobe`/`uretprobe`: Dynamic Tracing, User-Level Syntax: ``` uprobe:library_name:function_name[+offset] uprobe:library_name:address uretprobe:library_name:function_name ``` These use uprobes (a Linux kernel capability). `uprobe` instruments the beginning of a user-level function's execution, and `uretprobe` instruments the end (its return). To list available uprobes, you can use any program to list the text segment symbols from a binary, such as `objdump` and `nm`. For example: ``` # objdump -tT /bin/bash | grep readline 00000000007003f8 g DO .bss 0000000000000004 Base rl_readline_state 0000000000499e00 g DF .text 00000000000001c5 Base readline_internal_char 00000000004993d0 g DF .text 0000000000000126 Base readline_internal_setup 000000000046d400 g DF .text 000000000000004b Base posix_readline_initialize 000000000049a520 g DF .text 0000000000000081 Base readline [...] ``` This has listed various functions containing "readline" from /bin/bash. These can be instrumented using `uprobe` and `uretprobe`. Examples: ``` # bpftrace -e 'uretprobe:/bin/bash:readline { printf("read a line\n"); }' Attaching 1 probe... read a line read a line read a line ^C ``` While tracing, this has caught a few executions of the `readline()` function in /bin/bash. This example is continued in the next section. It's also possible to specify uprobe with virtual address, like: ``` # objdump -tT /bin/bash | grep main ... 000000000002ec00 g DF .text 0000000000001868 Base main ... # bpftrace -e 'uprobe:/bin/bash:0x2ec00 { printf("in here\n"); }' Attaching 1 probe... ``` And to specify offset within the probed function: ``` # objdump -d /bin/bash ... 000000000002ec00 : 2ec00: f3 0f 1e fa endbr64 2ec04: 41 57 push %r15 2ec06: 41 56 push %r14 2ec08: 41 55 push %r13 ... # bpftrace -e 'uprobe:/bin/bash:main+4 { printf("in here\n"); }' Attaching 1 probe... ... ``` The address is being checked if it's aligned with instruction boundaries. If it's not, we fail to add it: ``` # bpftrace -e 'uprobe:/bin/bash:main+1 { printf("in here\n"); }' Attaching 1 probe... Could not add uprobe into middle of instruction: /bin/bash:main+1 ``` If bpftrace is compiled with `ALLOW_UNSAFE_PROBE` option, you can use --unsafe option to skip the check: ``` # bpftrace -e 'uprobe:/bin/bash:main+1 { printf("in here\n"); } --unsafe' Attaching 1 probe... Unsafe uprobe in the middle of the instruction: /bin/bash:main+1 ``` ## 4. `uprobe`/`uretprobe`: Dynamic Tracing, User-Level Arguments Syntax: ``` uprobe: arg0, arg1, ..., argN uretprobe: retval ``` Arguments can be accessed via these variables names. `arg0` is the first argument, and can only be accessed with a `uprobe`. `retval` is the return value for the instrumented function, and can only be accessed on `uretprobe`. Examples: ``` # bpftrace -e 'uprobe:/bin/bash:readline { printf("arg0: %d\n", arg0); }' Attaching 1 probe... arg0: 19755784 arg0: 19755016 arg0: 19755784 ^C ``` What does `arg0` of `readline()` in /bin/bash contain? I don't know. I'd need to look at the bash source code to find out what its arguments were. ``` # bpftrace -e 'uprobe:/lib/x86_64-linux-gnu/libc-2.23.so:fopen { printf("fopen: %s\n", str(arg0)); }' Attaching 1 probe... fopen: /proc/filesystems fopen: /usr/share/locale/locale.alias fopen: /proc/self/mountinfo ^C ``` In this case, I know that the first argument of libc `fopen()` is the pathname (see the fopen(3) man page), so I've traced it using a uprobe. Adjust the path to libc to match your system (it may not be libc-2.23.so). A `str()` call is necessary to turn the char * pointer to a string, as explained in a later section. ``` # bpftrace -e 'uretprobe:/bin/bash:readline { printf("readline: \"%s\"\n", str(retval)); }' Attaching 1 probe... readline: "echo hi" readline: "ls -l" readline: "date" readline: "uname -r" ^C ``` Back to the bash `readline()` example: after checking the source code, I saw that the return value was the string read. So I can use a `uretprobe` and the `retval` variable to see the read string. ## 5. `tracepoint`: Static Tracing, Kernel-Level Syntax: `tracepoint:name` These use tracepoints (a Linux kernel capability). ``` # bpftrace -e 'tracepoint:block:block_rq_insert { printf("block I/O created by %d\n", tid); }' Attaching 1 probe... block I/O created by 28922 block I/O created by 3949 block I/O created by 883 block I/O created by 28941 block I/O created by 28941 block I/O created by 28941 [...] ``` ## 6. `tracepoint`: Static Tracing, Kernel-Level Arguments Example: ``` # bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }' Attaching 1 probe... irqbalance /proc/interrupts irqbalance /proc/stat snmpd /proc/diskstats snmpd /proc/stat snmpd /proc/vmstat snmpd /proc/net/dev [...] ``` The available members for each tracepoint can be listed from their /format file in /sys. For example: ``` # cat /sys/kernel/debug/tracing/events/syscalls/sys_enter_open/format name: sys_enter_openat ID: 608 format: field:unsigned short common_type; offset:0; size:2; signed:0; field:unsigned char common_flags; offset:2; size:1; signed:0; field:unsigned char common_preempt_count; offset:3; size:1; signed:0; field:int common_pid; offset:4; size:4; signed:1; field:int __syscall_nr; offset:8; size:4; signed:1; field:int dfd; offset:16; size:8; signed:0; field:const char * filename; offset:24; size:8; signed:0; field:int flags; offset:32; size:8; signed:0; field:umode_t mode; offset:40; size:8; signed:0; print fmt: "dfd: 0x%08lx, filename: 0x%08lx, flags: 0x%08lx, mode: 0x%08lx", ((unsigned long)(REC->dfd)), ((unsigned long)(REC->filename)), ((unsigned long)(REC->flags)), ((unsigned long)(REC->mode)) ``` Apart from the `filename` member, we can also print `flags`, `mode`, and more. After the "common" members listed first, the members are specific to the tracepoint. ## 7. `usdt`: Static Tracing, User-Level Syntax: ``` usdt:binary_path:probe_name usdt:binary_path:[probe_namespace]:probe_name usdt:library_path:probe_name usdt:library_path:[probe_namespace]:probe_name ``` Where the `probe_namespace` is optional, and will default to the basename of the binary or library path. Examples: ``` # bpftrace -e 'usdt:/root/tick:loop { printf("hi\n"); }' Attaching 1 probe... hi hi hi hi hi ^C ``` The basename of a path will be used for the namespace of a probe. If it doesn't match, the probe won't be found. In this example, the function name `loop` is in the namespace `tick`. If we rename the binary to `tock`, it won't be found: ``` mv /root/tick /root/tock bpftrace -e 'usdt:/root/tock:loop { printf("hi\n"); }' Attaching 1 probe... Error finding location for probe: usdt:/root/tock:loop ``` The probe namespace can be manually specified, between the path and probe function name. This allows for the probe to be found, regardless of the name of the binary: ``` bpftrace -e 'usdt:/root/tock:tick:loop { printf("hi\n"); }' ``` ## 8. `usdt`: Static Tracing, User-Level Arguments Examples: ``` # bpftrace -e 'usdt:/root/tick:loop { printf("%s: %d\n", str(arg0), arg1); }' my string: 1 my string: 2 my string: 3 my string: 4 my string: 5 ^C ``` ``` # bpftrace -e 'usdt:/root/tick:loop /arg1 > 2/ { printf("%s: %d\n", str(arg0), arg1); }' my string: 3 my string: 4 my string: 5 my string: 6 ^C ``` ## 9. `profile`: Timed Sampling Events Syntax: ``` profile:hz:rate profile:s:rate profile:ms:rate profile:us:rate ``` These operating using perf_events (a Linux kernel facility), which is also used by the `perf` command). Examples: ``` # bpftrace -e 'profile:hz:99 { @[tid] = count(); }' Attaching 1 probe... ^C @[32586]: 98 @[0]: 579 ``` ## 10. `interval`: Timed Output Syntax: ``` interval:ms:rate interval:s:rate ``` This fires on one CPU only, and can be used for generating per-interval output. Example: ``` # bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @syscalls = count(); } interval:s:1 { print(@syscalls); clear(@syscalls); }' Attaching 2 probes... @syscalls: 1263 @syscalls: 731 @syscalls: 891 @syscalls: 1195 @syscalls: 1154 @syscalls: 1635 @syscalls: 1208 [...] ``` This prints the rate of syscalls per second. ## 11. `software`: Pre-defined Software Events Syntax: ``` software:event_name:count software:event_name: ``` These are the pre-defined software events provided by the Linux kernel, as commonly traced via the perf utility. They are similar to tracepoints, but there is only about a dozen of these, and they are documented in the perf\_event\_open(2) man page. The event names are: - `cpu-clock` or `cpu` - `task-clock` - `page-faults` or `faults` - `context-switches` or `cs` - `cpu-migrations` - `minor-faults` - `major-faults` - `alignment-faults` - `emulation-faults` - `dummy` - `bpf-output` The count is the trigger for the probe, which will fire once for every count events. If the count is not provided, a default is used. Examples: ``` # bpftrace -e 'software:faults:100 { @[comm] = count(); }' Attaching 1 probe... ^C @[ls]: 1 @[pager]: 2 @[locale]: 2 @[preconv]: 2 @[sh]: 3 @[tbl]: 3 @[bash]: 4 @[groff]: 5 @[grotty]: 7 @[sleep]: 9 @[nroff]: 12 @[troff]: 18 @[man]: 97 ``` This roughly counts who is causing page faults, by sampling the process name for every one in one hundred faults. ## 12. `hardware`: Pre-defined Hardware Events Syntax: ``` hardware:event_name:count hardware:event_name: ``` These are the pre-defined hardware events provided by the Linux kernel, as commonly traced by the perf utility. They are implemented using performance monitoring counters (PMCs): hardware resources on the processor. There are about ten of these, and they are documented in the perf\_event\_open(2) man page. The event names are: - `cpu-cycles` or `cycles` - `instructions` - `cache-references` - `cache-misses` - `branch-instructions` or `branches` - `branch-misses` - `bus-cycles` - `frontend-stalls` - `backend-stalls` - `ref-cycles` The count is the trigger for the probe, which will fire once for every count events. If the count is not provided, a default is used. Examples: ``` bpftrace -e 'hardware:cache-misses:1000000 { @[pid] = count(); }' ``` That would fire once for every 1000000 cache misses. This usually indicates the last level cache (LLC). ## 13. `BEGIN`/`END`: Built-in events Syntax: ``` BEGIN END ``` These are special built-in events provided by the bpftrace runtime. `BEGIN` is triggered before all other probes are attached. `END` is triggered after all other probes are detached. ## 14. `watchpoint`: Memory watchpoints **WARNING**: this feature is experimental and may be subject to interface changes. Syntax: ``` watchpoint::hex_address:length:mode ``` These are memory watchpoints provided by the kernel. Whenever a memory address is written to (`w`), read from (`r`), or executed (`x`), the kernel can generate an event. Note that a pid (`-p`) or a command (`-c`) must be provided to bpftrace. Also note you may not monitor for execution while monitoring read or write. Examples: ``` bpftrace -e 'watchpoint::0x10000000:8:rw { printf("hit!\n"); }' -c ~/binary ``` # Variables ## 1. Builtins - `pid` - Process ID (kernel tgid) - `tid` - Thread ID (kernel pid) - `uid` - User ID - `gid` - Group ID - `nsecs` - Nanosecond timestamp - `elapsed` - Nanosecond timestamp since bpftrace initialization - `cpu` - Processor ID - `comm` - Process name - `kstack` - Kernel stack trace - `ustack` - User stack trace - `arg0`, `arg1`, ..., `argN`. - Arguments to the traced function; assumed to be 64 bits wide - `sarg0`, `sarg1`, ..., `sargN`. - Arguments to the traced function (for programs that store arguments on the stack); assumed to be 64 bits wide - `retval` - Return value from traced function - `func` - Name of the traced function - `probe` - Full name of the probe - `curtask` - Current task struct as a u64 - `rand` - Random number as a u32 - `cgroup` - Cgroup ID of the current process - `cpid` - Child pid(u32), only valid with the `-c command` flag - `$1`, `$2`, ..., `$N`, `$#`. - Positional parameters for the bpftrace program Many of these are discussed in other sections (use search). ## 2. `@`, `$`: Basic variables Syntax: ``` @global_name @thread_local_variable_name[tid] $scratch_name ``` bpftrace supports global & per-thread variables (via BPF maps), and scratch variables. Examples: ### 2.1. Global Syntax: `@name` For example, `@start`: ``` # bpftrace -e 'BEGIN { @start = nsecs; } kprobe:do_nanosleep /@start != 0/ { printf("at %d ms: sleep\n", (nsecs - @start) / 1000000); }' Attaching 2 probes... at 437 ms: sleep at 647 ms: sleep at 1098 ms: sleep at 1438 ms: sleep at 1648 ms: sleep ^C @start: 4064438886907216 ``` ### 2.2. Per-Thread: These can be implemented as an associative array keyed on the thread ID. For example, `@start[tid]`: ``` # bpftrace -e 'kprobe:do_nanosleep { @start[tid] = nsecs; } kretprobe:do_nanosleep /@start[tid] != 0/ { printf("slept for %d ms\n", (nsecs - @start[tid]) / 1000000); delete(@start[tid]); }' Attaching 2 probes... slept for 1000 ms slept for 1000 ms slept for 1000 ms slept for 1009 ms slept for 2002 ms [...] ``` ### 2.3. Scratch: Syntax: `$name` For example, `$delta`: ``` # bpftrace -e 'kprobe:do_nanosleep { @start[tid] = nsecs; } kretprobe:do_nanosleep /@start[tid] != 0/ { $delta = nsecs - @start[tid]; printf("slept for %d ms\n", $delta / 1000000); delete(@start[tid]); }' Attaching 2 probes... slept for 1000 ms slept for 1000 ms slept for 1000 ms ``` ## 3. `@[]`: Associative Arrays Syntax: `@associative_array_name[key_name] = value` These are implemented using BPF maps. For example, `@start[tid]`: ``` # bpftrace -e 'kprobe:do_nanosleep { @start[tid] = nsecs; } kretprobe:do_nanosleep /@start[tid] != 0/ { printf("slept for %d ms\n", (nsecs - @start[tid]) / 1000000); delete(@start[tid]); }' Attaching 2 probes... slept for 1000 ms slept for 1000 ms slept for 1000 ms [...] ``` ## 4. `count()`: Frequency Counting This is provided by the count() function: see the [Count](#2-count-count) section. ## 5. `hist()`, `lhist()`: Histograms These are provided by the hist() and lhist() functions. See the [Log2 Histogram](#8-hist-log2-histogram) and [Linear Histogram](#9-lhist-linear-histogram) sections. ## 6. `nsecs`: Timestamps and Time Deltas Syntax: `nsecs` These are implemented using bpf_ktime_get_ns(). Examples: ``` # bpftrace -e 'BEGIN { @start = nsecs; } kprobe:do_nanosleep /@start != 0/ { printf("at %d ms: sleep\n", (nsecs - @start) / 1000000); }' Attaching 2 probes... at 437 ms: sleep at 647 ms: sleep at 1098 ms: sleep at 1438 ms: sleep ^C ``` ## 7. `kstack`: Stack Traces, Kernel Syntax: `kstack` This builtin is an alias to [`kstack()`](#15-kstack-stack-traces-kernel). Examples: ``` # bpftrace -e 'kprobe:ip_output { @[kstack] = count(); }' Attaching 1 probe... [...] @[ ip_output+1 tcp_transmit_skb+1308 tcp_write_xmit+482 tcp_release_cb+225 release_sock+64 tcp_sendmsg+49 sock_sendmsg+48 sock_write_iter+135 __vfs_write+247 vfs_write+179 sys_write+82 entry_SYSCALL_64_fastpath+30 ]: 1708 @[ ip_output+1 tcp_transmit_skb+1308 tcp_write_xmit+482 __tcp_push_pending_frames+45 tcp_sendmsg_locked+2637 tcp_sendmsg+39 sock_sendmsg+48 sock_write_iter+135 __vfs_write+247 vfs_write+179 sys_write+82 entry_SYSCALL_64_fastpath+30 ]: 9048 @[ ip_output+1 tcp_transmit_skb+1308 tcp_write_xmit+482 tcp_tasklet_func+348 tasklet_action+241 __do_softirq+239 irq_exit+174 do_IRQ+74 ret_from_intr+0 cpuidle_enter_state+159 do_idle+389 cpu_startup_entry+111 start_secondary+398 secondary_startup_64+165 ]: 11430 ``` ## 8. `ustack`: Stack Traces, User Syntax: `ustack` This builtin is an alias to [`ustack()`](#16-ustack-stack-traces-user). Examples: ``` # bpftrace -e 'kprobe:do_sys_open /comm == "bash"/ { @[ustack] = count(); }' Attaching 1 probe... ^C @[ __open_nocancel+65 command_word_completion_function+3604 rl_completion_matches+370 bash_default_completion+540 attempt_shell_completion+2092 gen_completion_matches+82 rl_complete_internal+288 rl_complete+145 _rl_dispatch_subseq+647 _rl_dispatch+44 readline_internal_char+479 readline_internal_charloop+22 readline_internal+23 readline+91 yy_readline_get+152 yy_readline_get+429 yy_getc+13 shell_getc+469 read_token+251 yylex+192 yyparse+777 parse_command+126 read_command+207 reader_loop+391 main+2409 __libc_start_main+231 0x61ce258d4c544155 ]: 9 @[ __open_nocancel+65 command_word_completion_function+3604 rl_completion_matches+370 bash_default_completion+540 attempt_shell_completion+2092 gen_completion_matches+82 rl_complete_internal+288 rl_complete+89 _rl_dispatch_subseq+647 _rl_dispatch+44 readline_internal_char+479 readline_internal_charloop+22 readline_internal+23 readline+91 yy_readline_get+152 yy_readline_get+429 yy_getc+13 shell_getc+469 read_token+251 yylex+192 yyparse+777 parse_command+126 read_command+207 reader_loop+391 main+2409 __libc_start_main+231 0x61ce258d4c544155 ]: 18 ``` Note that for this example to work, bash had to be recompiled with frame pointers. ## 9. `$1`, ..., `$N`, `$#`: Positional Parameters Syntax: `$1`, `$2`, ..., `$N`, `$#` These are the positional parameters to the bpftrace program, also referred to as command line arguments. If the parameter is numeric (entirely digits), it can be used as a number. If it is non-numeric, it must be used as a string in the `str()` call. If a parameter is used that was not provided, it will default to zero for numeric context, and "" for string context. `$#` returns the number of positional arguments supplied. This allows scripts to be written that use basic arguments to change their behavior. If you develop a script that requires more complex argument processing, it may be better suited for bcc instead, which supports Python's argparse and completely custom argument processing. One-liner example: ``` # bpftrace -e 'BEGIN { printf("I got %d, %s (%d args)\n", $1, str($2), $#); }' 42 "hello" Attaching 1 probe... I got 42, hello (2 args) ``` Script example, bsize.d: ``` #!/usr/local/bin/bpftrace BEGIN { printf("Tracing block I/O sizes > %d bytes\n", $1); } tracepoint:block:block_rq_issue /args->bytes > $1/ { @ = hist(args->bytes); } ``` When run with a 65536 argument: ``` # ./bsize.bt 65536 Attaching 2 probes... Tracing block I/O sizes > 65536 bytes ^C @: [512K, 1M) 1 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| ``` It has passed the argument in as $1, and used it as a filter. With no arguments, $1 defaults to zero: ``` # ./bsize.bt Attaching 2 probes... Tracing block I/O sizes > 0 bytes ^C @: [4K, 8K) 115 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [8K, 16K) 35 |@@@@@@@@@@@@@@@ | [16K, 32K) 5 |@@ | [32K, 64K) 3 |@ | [64K, 128K) 1 | | [128K, 256K) 0 | | [256K, 512K) 0 | | [512K, 1M) 1 | | ``` # Functions ## 1. Builtins - `printf(char *fmt, ...)` - Print formatted - `time(char *fmt)` - Print formatted time - `join(char *arr[] [, char *delim])` - Print the array - `str(char *s [, int length])` - Returns the string pointed to by s - `ksym(void *p)` - Resolve kernel address - `usym(void *p)` - Resolve user space address - `kaddr(char *name)` - Resolve kernel symbol name - `uaddr(char *name)` - Resolve user-level symbol name - `reg(char *name)` - Returns the value stored in the named register - `system(char *fmt)` - Execute shell command - `exit()` - Quit bpftrace - `cgroupid(char *path)` - Resolve cgroup ID - `kstack([StackMode mode, ][int level])` - Kernel stack trace - `ustack([StackMode mode, ][int level])` - User stack trace - `ntop([int af, ]int|char[4|16] addr)` - Convert IP address data to text - `cat(char *filename)` - Print file content Some of these are asynchronous: the kernel queues the event, but some time later (milliseconds) it is processed in user-space. The asynchronous actions are: `printf()`, `time()`, and `join()`. Both `ksym()` and `usym()`, as well as the variables `kstack` and `ustack`, record addresses synchronously, but then do symbol translation asynchronously. A selection of these are discussed in the following sections. ## 2. `printf()`: Printing Syntax: `printf(fmt, args)` This behaves like printf() from C and other languages, with a limited set of format characters. Example: ``` # bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s called %s\n", comm, str(args->filename)); }' Attaching 1 probe... bash called /bin/ls bash called /usr/bin/man man called /apps/nflx-bash-utils/bin/preconv man called /usr/local/sbin/preconv man called /usr/local/bin/preconv man called /usr/sbin/preconv man called /usr/bin/preconv man called /apps/nflx-bash-utils/bin/tbl [...] ``` ## 3. `time()`: Time Syntax: `time(fmt)` This prints the current time using the format string supported by libc `strftime(3)`. ``` # bpftrace -e 'kprobe:do_nanosleep { time("%H:%M:%S\n"); }' 07:11:03 07:11:09 ^C ``` If a format string is not provided, it defaults to "%H:%M:%S\n". ## 4. `join()`: Join Syntax: `join(char *arr[] [, char *delim])` This joins the array of strings with a space character, and prints it out, separated by delimiters. The default delimiter, if none is provided, is the space character. This current version does not return a string, so it cannot be used as an argument in printf(). Example: ``` # bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv); }' Attaching 1 probe... ls --color=auto man ls preconv -e UTF-8 preconv -e UTF-8 preconv -e UTF-8 preconv -e UTF-8 preconv -e UTF-8 tbl [...] ``` ``` # bpftrace -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv, ","); }' Attaching 1 probe... ls,--color=auto man,ls preconv,-e,UTF-8 preconv,-e,UTF-8 preconv,-e,UTF-8 preconv,-e,UTF-8 preconv,-e,UTF-8 tbl [...] ``` ## 5. `str()`: Strings Syntax: `str(char *s [, int length])` Returns the string pointed to by s. `length` can be used to limit the size of the read, and/or introduce a null-terminator. By default, the string will have size 64 bytes (tuneable using [env var `BPFTRACE_STRLEN`](#7-env-bpftrace_strlen)). Examples: We can take the `args->filename` of `sys_enter_execve` (a `const char *filename`), and read the string to which it points. This string can be provided as an argument to printf(): ``` # bpftrace -e 'tracepoint:syscalls:sys_enter_execve { printf("%s called %s\n", comm, str(args->filename)); }' Attaching 1 probe... bash called /bin/ls bash called /usr/bin/man man called /apps/nflx-bash-utils/bin/preconv man called /usr/local/sbin/preconv man called /usr/local/bin/preconv man called /usr/sbin/preconv man called /usr/bin/preconv man called /apps/nflx-bash-utils/bin/tbl [...] ``` We can trace strings that are displayed in a bash shell. Some length tuning is employed, because: - sys_enter_write()'s `args->buf` does not point to null-terminated strings - we use the length parameter to limit how many bytes to read of the pointed-to string - sys_enter_write()'s `args->buf` contains messages larger than 64 bytes - we increase BPFTRACE_STRLEN to accommodate the large messages ``` # BPFTRACE_STRLEN=200 bpftrace -e 'tracepoint:syscalls:sys_enter_write /pid == 23506/ { printf("<%s>\n", str(args->buf, args->count)); }' # type pwd into terminal 23506

# press enter in terminal 23506 < > ``` ## 6. `ksym()`: Symbol resolution, kernel-level Syntax: `ksym(addr)` Examples: ``` # bpftrace -e 'kprobe:do_nanosleep { printf("%s\n", ksym(reg("ip"))); }' Attaching 1 probe... do_nanosleep do_nanosleep ``` ## 7. `usym()`: Symbol resolution, user-level Syntax: `usym(addr)` Examples: ``` # bpftrace -e 'uprobe:/bin/bash:readline { printf("%s\n", usym(reg("ip"))); }' Attaching 1 probe... readline readline readline ^C ``` ## 8. `kaddr()`: Address resolution, kernel-level Syntax: `kaddr(char *name)` Examples: ``` # bpftrace -e 'BEGIN { printf("%s\n", str(*kaddr("usbcore_name"))); }' Attaching 1 probe... usbcore ^C ``` This is printing the `usbcore_name` string from drivers/usb/core/usb.c: ``` const char *usbcore_name = "usbcore"; ``` ## 9. `uaddr()`: Address resolution, user-level Syntax: - `u64 *uaddr(symbol)` (default) - `u64 *uaddr(symbol)` - `u32 *uaddr(symbol)` - `u16 *uaddr(symbol)` - `u8 *uaddr(symbol)` Supported Probe Types: - u(ret)probes - USDT **Does not work with ASLR, see issue [#75](https://github.com/iovisor/bpftrace/75)** The `uaddr` function returns the address of the specified symbol. This lookup happens during program compilation and cannot be used dynamically. The default return type is `u64*`. If the ELF object size matches a known integer size (1, 2, 4 or 8 bytes) the return type is modified to match the width (`u8*`, `u16*`, `u32*` or `u64*` resp.). As ELF does not contain type info the type is always assumed to be unsigned. Examples: ``` # bpftrace -e 'uprobe:/bin/bash:readline { printf("PS1: %s\n", str(*uaddr("ps1_prompt"))); }' Attaching 1 probe... PS1: \[\e[34;1m\]\u@\h:\w>\[\e[0m\] PS1: \[\e[34;1m\]\u@\h:\w>\[\e[0m\] ^C ``` This is printing the `ps1_prompt` string from /bin/bash, whenever a `readline()` function is executed. ## 10. `reg()`: Registers Syntax: `reg(char *name)` Examples: ``` # bpftrace -e 'kprobe:tcp_sendmsg { @[ksym(reg("ip"))] = count(); }' Attaching 1 probe... ^C @[tcp_sendmsg]: 7 ``` See src/arch/x86_64.cpp for the register name list. ## 11. `system()`: System Syntax: `system(fmt)` This runs the provided command at the shell. For example: ``` # bpftrace --unsafe -e 'kprobe:do_nanosleep { system("ps -p %d\n", pid); }' Attaching 1 probe... PID TTY TIME CMD 1339 ? 00:00:15 iscsid PID TTY TIME CMD 1339 ? 00:00:15 iscsid PID TTY TIME CMD 1518 ? 00:01:07 irqbalance PID TTY TIME CMD 1339 ? 00:00:15 iscsid ^C ``` This can be useful to execute commands or a shell script when an instrumented event happens. Note this is an unsafe function. To use it, bpftrace must be run with `--unsafe`. ## 12. `exit()`: Exit Syntax: `exit()` This exits bpftrace, and can be combined with an interval probe to record statistics for a certain duration. Example: ``` # bpftrace -e 'kprobe:do_sys_open { @opens = count(); } interval:s:1 { exit(); }' Attaching 2 probes... @opens: 119 ``` ## 13. `cgroupid()`: Resolve cgroup ID Syntax: `cgroupid(char *path)` This returns a cgroup ID of a specific cgroup, and can be combined with the `cgroup` builtin to filter the tasks that belong to the specific cgroup, for example: ``` # bpftrace -e 'tracepoint:syscalls:sys_enter_openat /cgroup == cgroupid("/sys/fs/cgroup/unified/mycg")/ { printf("%s\n", str(args->filename)); }': Attaching 1 probe... /etc/ld.so.cache /lib64/libc.so.6 /usr/lib/locale/locale-archive /etc/shadow ^C ``` And in other terminal: ``` # echo $$ > /sys/fs/cgroup/unified/mycg/cgroup.procs # cat /etc/shadow ``` ## 14. `ntop()`: Convert IP address data to text Syntax: `ntop([int af, ]int|char[4|16] addr)` This returns the string representation of an IPv4 or IPv6 address. ntop will infer the address type (IPv4 or IPv6) based on the `addr` type and size. If an integer or `char[4]` is given, ntop assumes IPv4, if a `char[16]` is given, ntop assumes IPv6. You can also pass the address type explicitly as the first parameter. Examples: A simple example of ntop with an ipv4 hex-encoded literal: ``` bpftrace -e 'BEGIN { printf("%s\n", ntop(0x0100007f));}' 127.0.0.1 ^C ``` Same example as before, but passing the address type explicitly to ntop: ``` bpftrace -e '#include BEGIN { printf("%s\n", ntop(AF_INET, 0x0100007f));}' 127.0.0.1 ^C ``` A less trivial example of this usage, tracing tcp state changes, and printing the destination IPv6 address: ``` bpftrace -e 'tracepoint:tcp:tcp_set_state { printf("%s\n", ntop(args->daddr_v6)) }' Attaching 1 probe... ::ffff:216.58.194.164 ::ffff:216.58.194.164 ::ffff:216.58.194.164 ::ffff:216.58.194.164 ::ffff:216.58.194.164 ^C ``` And initiate a connection to this (or any) address in another terminal: ``` curl www.google.com ``` ## 15. `kstack()`: Stack Traces, Kernel Syntax: `kstack([StackMode mode, ][int limit])` These are implemented using BPF stack maps. Examples: ``` # bpftrace -e 'kprobe:ip_output { @[kstack()] = count(); }' Attaching 1 probe... [...] @[ ip_output+1 tcp_transmit_skb+1308 tcp_write_xmit+482 tcp_release_cb+225 release_sock+64 tcp_sendmsg+49 sock_sendmsg+48 sock_write_iter+135 __vfs_write+247 vfs_write+179 sys_write+82 entry_SYSCALL_64_fastpath+30 ]: 1708 @[ ip_output+1 tcp_transmit_skb+1308 tcp_write_xmit+482 __tcp_push_pending_frames+45 tcp_sendmsg_locked+2637 tcp_sendmsg+39 sock_sendmsg+48 sock_write_iter+135 __vfs_write+247 vfs_write+179 sys_write+82 entry_SYSCALL_64_fastpath+30 ]: 9048 @[ ip_output+1 tcp_transmit_skb+1308 tcp_write_xmit+482 tcp_tasklet_func+348 tasklet_action+241 __do_softirq+239 irq_exit+174 do_IRQ+74 ret_from_intr+0 cpuidle_enter_state+159 do_idle+389 cpu_startup_entry+111 start_secondary+398 secondary_startup_64+165 ]: 11430 ``` Sampling only three frames from the stack (limit = 3): ``` # bpftrace -e 'kprobe:ip_output { @[kstack(3)] = count(); }' Attaching 1 probe... [...] @[ ip_output+1 tcp_transmit_skb+1308 tcp_write_xmit+482 ]: 22186 ``` You can also choose a different output format. Available formats are `bpftrace` and `perf`: ``` # bpftrace -e 'kprobe:do_mmap { @[kstack(perf)] = count(); }' Attaching 1 probe... [...] @[ ffffffffb4019501 do_mmap+1 ffffffffb401700a sys_mmap_pgoff+266 ffffffffb3e334eb sys_mmap+27 ffffffffb3e03ae3 do_syscall_64+115 ffffffffb4800081 entry_SYSCALL_64_after_hwframe+61 ]: 22186 ``` It's also possible to use a different output format and limit the number of frames: ``` # bpftrace -e 'kprobe:do_mmap { @[kstack(perf, 3)] = count(); }' Attaching 1 probe... [...] @[ ffffffffb4019501 do_mmap+1 ffffffffb401700a sys_mmap_pgoff+266 ffffffffb3e334eb sys_mmap+27 ]: 22186 ``` ## 16. `ustack()`: Stack Traces, User Syntax: `ustack([StackMode mode, ][int limit])` These are implemented using BPF stack maps. Examples: ``` # bpftrace -e 'kprobe:do_sys_open /comm == "bash"/ { @[ustack()] = count(); }' Attaching 1 probe... ^C @[ __open_nocancel+65 command_word_completion_function+3604 rl_completion_matches+370 bash_default_completion+540 attempt_shell_completion+2092 gen_completion_matches+82 rl_complete_internal+288 rl_complete+145 _rl_dispatch_subseq+647 _rl_dispatch+44 readline_internal_char+479 readline_internal_charloop+22 readline_internal+23 readline+91 yy_readline_get+152 yy_readline_get+429 yy_getc+13 shell_getc+469 read_token+251 yylex+192 yyparse+777 parse_command+126 read_command+207 reader_loop+391 main+2409 __libc_start_main+231 0x61ce258d4c544155 ]: 9 @[ __open_nocancel+65 command_word_completion_function+3604 rl_completion_matches+370 bash_default_completion+540 attempt_shell_completion+2092 gen_completion_matches+82 rl_complete_internal+288 rl_complete+89 _rl_dispatch_subseq+647 _rl_dispatch+44 readline_internal_char+479 readline_internal_charloop+22 readline_internal+23 readline+91 yy_readline_get+152 yy_readline_get+429 yy_getc+13 shell_getc+469 read_token+251 yylex+192 yyparse+777 parse_command+126 read_command+207 reader_loop+391 main+2409 __libc_start_main+231 0x61ce258d4c544155 ]: 18 ``` Sampling only three frames from the stack (limit = 6): ``` # bpftrace -e 'kprobe:do_sys_open /comm == "bash"/ { @[ustack(6)] = count(); }' Attaching 1 probe... ^C @[ __open_nocancel+65 command_word_completion_function+3604 rl_completion_matches+370 bash_default_completion+540 attempt_shell_completion+2092 gen_completion_matches+82 ]: 27 ``` You can also choose a different output format. Available formats are `bpftrace` and `perf`: ``` # bpftrace -e 'uprobe:bash:readline { printf("%s\n", ustack(perf)); }' Attaching 1 probe... 5649feec4090 readline+0 (/home/mmarchini/bash/bash/bash) 5649fee2bfa6 yy_readline_get+451 (/home/mmarchini/bash/bash/bash) 5649fee2bdc6 yy_getc+13 (/home/mmarchini/bash/bash/bash) 5649fee2cd36 shell_getc+469 (/home/mmarchini/bash/bash/bash) 5649fee2e527 read_token+251 (/home/mmarchini/bash/bash/bash) 5649fee2d9e2 yylex+192 (/home/mmarchini/bash/bash/bash) 5649fee286fd yyparse+777 (/home/mmarchini/bash/bash/bash) 5649fee27dd6 parse_command+54 (/home/mmarchini/bash/bash/bash) ``` It's also possible to use a different output format and limit the number of frames: ``` # bpftrace -e 'uprobe:bash:readline { printf("%s\n", ustack(perf, 3)); }' Attaching 1 probe... 5649feec4090 readline+0 (/home/mmarchini/bash/bash/bash) 5649fee2bfa6 yy_readline_get+451 (/home/mmarchini/bash/bash/bash) 5649fee2bdc6 yy_getc+13 (/home/mmarchini/bash/bash/bash) ``` Note that for these examples to work, bash had to be recompiled with frame pointers. ## 17. `cat()`: Print file content Syntax: `cat(filename)` This prints the file content. For example: ``` # bpftrace -e 't:syscalls:sys_enter_execve { printf("%s ", str(args->filename)); cat("/proc/loadavg"); }' Attaching 1 probe... /usr/libexec/grepconf.sh 3.18 2.90 2.94 2/977 30138 /usr/bin/grep 3.18 2.90 2.94 4/978 30139 /usr/bin/flatpak 3.18 2.90 2.94 2/980 30143 /usr/bin/grep 3.18 2.90 2.94 3/977 30144 /usr/bin/sed 3.18 2.90 2.94 7/978 30146 /usr/bin/tclsh 3.18 2.90 2.94 5/978 30150 /usr/bin/manpath 3.18 2.90 2.94 2/978 30152 /bin/ps 3.18 2.90 2.94 2/979 30155 ^C ``` The `cat()` builtin also supports a format string as argument: ``` ./bpftrace -e 'tracepoint:syscalls:sys_enter_sendmsg { printf("%s => ", comm); cat("/proc/%d/cmdline", pid); printf("\n") }' Attaching 1 probe... Gecko_IOThread => /usr/lib64/firefox/firefox Gecko_IOThread => /usr/lib64/firefox/firefox Gecko_IOThread => /usr/lib64/firefox/firefox Gecko_IOThread => /usr/lib64/firefox/firefox Gecko_IOThread => /usr/lib64/firefox/firefox Gecko_IOThread => /usr/lib64/firefox/firefox Gecko_IOThread => /usr/lib64/firefox/firefox ^C ``` ## 18. `signal()`: Send a signal to current task Syntax: - `signal(u32 signal)` - `signal("SIG")` Kernel: 5.3 Supported Probe Types: - k(ret)probes - u(ret)probes - USDT - profile `signal` sends the specified signal to the current task: ``` # bpftrace -e 'kprobe:__x64_sys_execve /comm == "bash"/ { signal(5); }' --unsafe $ ls Trace/breakpoint trap (core dumped) ``` The signal can also be specified using a name, similar to the `kill(1)` command: ``` # bpftrace -e 'k:f { signal("KILL"); }' # bpftrace -e 'k:f { signal("SIGINT"); }' ``` ## 19. `strncmp()`: Compare first n characters of two strings Syntax: `strncmp(char *s1, char *s2, int length)` Return zero if the first `length` characters in `s1` and `s2` are equal, and non-zero otherwise. Examples: ``` bpftrace -e 't:syscalls:sys_enter_* /strncmp("mpv", comm, 3) == 0/ { @[comm, probe] = count() }' Attaching 320 probes... [...] @[mpv/vo, tracepoint:syscalls:sys_enter_rt_sigaction]: 238 @[mpv:gdrv0, tracepoint:syscalls:sys_enter_futex]: 680 @[mpv/ao, tracepoint:syscalls:sys_enter_write]: 1022 @[mpv, tracepoint:syscalls:sys_enter_ioctl]: 2677 @[mpv:cs0, tracepoint:syscalls:sys_enter_ioctl]: 2889 @[mpv/vo, tracepoint:syscalls:sys_enter_read]: 2993 @[mpv/demux, tracepoint:syscalls:sys_enter_futex]: 4745 @[mpv, tracepoint:syscalls:sys_enter_write]: 6936 @[mpv/vo, tracepoint:syscalls:sys_enter_futex]: 7662 @[mpv:cs0, tracepoint:syscalls:sys_enter_futex]: 8127 @[mpv/lua script , tracepoint:syscalls:sys_enter_futex]: 10150 @[mpv/vo, tracepoint:syscalls:sys_enter_poll]: 10241 @[mpv/vo, tracepoint:syscalls:sys_enter_recvmsg]: 15018 @[mpv, tracepoint:syscalls:sys_enter_getpid]: 31178 @[mpv, tracepoint:syscalls:sys_enter_futex]: 403868 ``` ## 20. `override()`: Override return value Syntax: `override(u64 rc)` Kernel: 4.16 Supported Probe Types: kprobes The probed function will not be executed, instead a helper will be executed that will just return `rc`. ``` # bpftrace -e 'k:__x64_sys_getuid /comm == "id"/ { override(2<<21); }' --unsafe -c id uid=4194304 gid=0(root) euid=0(root) groups=0(root) ``` This feature only works on kernels compiled with `CONFIG_BPF_KPROBE_OVERRIDE` and only works on functions tagged `ALLOW_ERROR_INJECTION`. bpftrace does not test whether error injection is allowed for the probed function, instead if will fail to load the program into the kernel: ``` ioctl(PERF_EVENT_IOC_SET_BPF): Invalid argument Error attaching probe: 'kprobe:vfs_read' ``` # Map Functions Maps are special BPF data types that can be used to store counts, statistics, and histograms. They are also used for some variable types as discussed in the previous section, whenever `@` is used: [globals](#21-global), [per thread variables](#22-per-thread), and [associative arrays](#3--associative-arrays). When bpftrace exits, all maps are printed. For example (the `count()` function is covered in the sections that follow): ``` # bpftrace -e 'kprobe:vfs_read { @[comm] = count(); }' Attaching 1 probe... ^C @[systemd]: 6 @[vi]: 7 @[sshd]: 16 @[snmpd]: 321 @[snmp-pass]: 374 ``` The map was printed after the Ctrl-C to end the program. If you use maps that you do not wish to be automatically printed on exit, you can add an END block that clears the maps. For example: ``` END { clear(@start); } ``` ## 1. Builtins - `count()` - Count the number of times this function is called - `sum(int n)` - Sum the value - `avg(int n)` - Average the value - `min(int n)` - Record the minimum value seen - `max(int n)` - Record the maximum value seen - `stats(int n)` - Return the count, average, and total for this value - `hist(int n)` - Produce a log2 histogram of values of n - `lhist(int n, int min, int max, int step)` - Produce a linear histogram of values of n - `delete(@x[key])` - Delete the map element passed in as an argument - `print(@x[, top [, div]])` - Print the map, optionally the top entries only and with a divisor - `clear(@x)` - Delete all keys from the map - `zero(@x)` - Set all map values to zero Some of these are asynchronous: the kernel queues the event, but some time later (milliseconds) it is processed in user-space. The asynchronous actions are: `print()`, `clear()`, and `zero()`. ## 2. `count()`: Count Syntax: `@counter_name[optional_keys] = count()` This is implemented using a BPF map. For example, `@reads`: ``` # bpftrace -e 'kprobe:vfs_read { @reads = count(); }' Attaching 1 probe... ^C @reads: 119 ``` That shows there were 119 calls to vfs_read() while tracing. This next example includes the `comm` variable as a key, so that the value is broken down by each process name. For example, `@reads[comm]`: ``` # bpftrace -e 'kprobe:vfs_read { @reads[comm] = count(); }' Attaching 1 probe... ^C @reads[sleep]: 4 @reads[bash]: 5 @reads[ls]: 7 @reads[snmp-pass]: 8 @reads[snmpd]: 14 @reads[sshd]: 14 ``` ## 3. `sum()`: Sum Syntax: `@counter_name[optional_keys] = sum(value)` This is implemented using a BPF map. For example, `@bytes[comm]`: ``` # bpftrace -e 'kprobe:vfs_read { @bytes[comm] = sum(arg2); }' Attaching 1 probe... ^C @bytes[bash]: 7 @bytes[sleep]: 4160 @bytes[ls]: 6208 @bytes[snmpd]: 20480 @bytes[snmp-pass]: 65536 @bytes[sshd]: 262144 ``` That is summing requested bytes via the vfs_read() kernel function, which is one of two possible entry points for the read syscall. To see actual bytes read: ``` # bpftrace -e 'kretprobe:vfs_read /retval > 0/ { @bytes[comm] = sum(retval); }' Attaching 1 probe... ^C @bytes[bash]: 5 @bytes[sshd]: 1135 @bytes[systemd-journal]: 1699 @bytes[sleep]: 2496 @bytes[ls]: 4583 @bytes[snmpd]: 35549 @bytes[snmp-pass]: 55681 ``` Now a filter is used to ensure the return value was positive before it is used in the sum(). The return value may be negative in cases of error, as is the case with other functions. Remember this whenever using sum() on a retval. ## 4. `avg()`: Average Syntax: `@counter_name[optional_keys] = avg(value)` This is implemented using a BPF map. For example, `@bytes[comm]`: ``` # bpftrace -e 'kprobe:vfs_read { @bytes[comm] = avg(arg2); }' Attaching 1 probe... ^C @bytes[bash]: 1 @bytes[sleep]: 832 @bytes[ls]: 886 @bytes[snmpd]: 1706 @bytes[snmp-pass]: 8192 @bytes[sshd]: 16384 ``` This is averaging the requested read size. ## 5. `min()`: Minimum Syntax: `@counter_name[optional_keys] = min(value)` This is implemented using a BPF map. For example, `@bytes[comm]`: ``` # bpftrace -e 'kprobe:vfs_read { @bytes[comm] = min(arg2); }' Attaching 1 probe... ^C @bytes[bash]: 1 @bytes[systemd-journal]: 8 @bytes[snmpd]: 64 @bytes[ls]: 832 @bytes[sleep]: 832 @bytes[snmp-pass]: 8192 @bytes[sshd]: 16384 ``` This shows the minimum value seen. ## 6. `max()`: Maximum Syntax: `@counter_name[optional_keys] = max(value)` This is implemented using a BPF map. For example, `@bytes[comm]`: ``` # bpftrace -e 'kprobe:vfs_read { @bytes[comm] = max(arg2); }' Attaching 1 probe... ^C @bytes[bash]: 1 @bytes[systemd-journal]: 8 @bytes[sleep]: 832 @bytes[ls]: 1024 @bytes[snmpd]: 4096 @bytes[snmp-pass]: 8192 @bytes[sshd]: 16384 ``` This shows the maximum value seen. ## 7. `stats()`: Stats Syntax: `@counter_name[optional_keys] = stats(value)` This is implemented using a BPF map. For example, `@bytes[comm]`: ``` # bpftrace -e 'kprobe:vfs_read { @bytes[comm] = stats(arg2); }' Attaching 1 probe... ^C @bytes[bash]: count 7, average 1, total 7 @bytes[sleep]: count 5, average 832, total 4160 @bytes[ls]: count 7, average 886, total 6208 @bytes[snmpd]: count 18, average 1706, total 30718 @bytes[snmp-pass]: count 12, average 8192, total 98304 @bytes[sshd]: count 15, average 16384, total 245760 ``` This stats() function returns three statistics: the count of events, the average for the argument value, and the total of the argument value. This is similar to using count(), avg(), and sum(). ## 8. `hist()`: Log2 Histogram Syntax: ``` @histogram_name[optional_key] = hist(value) ``` This is implemented using a BPF map. Examples: ### 8.1. Power-Of-2: ``` # bpftrace -e 'kretprobe:vfs_read { @bytes = hist(retval); }' Attaching 1 probe... ^C @bytes: (..., 0) 117 |@@@@@@@@@@@@ | [0] 5 | | [1] 325 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [2, 4) 6 | | [4, 8) 3 | | [8, 16) 495 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [16, 32) 35 |@@@ | [32, 64) 25 |@@ | [64, 128) 21 |@@ | [128, 256) 1 | | [256, 512) 3 | | [512, 1K) 2 | | [1K, 2K) 1 | | [2K, 4K) 2 | | ``` ### 8.2. Power-Of-2 By Key: ``` # bpftrace -e 'kretprobe:do_sys_open { @bytes[comm] = hist(retval); }' Attaching 1 probe... ^C @bytes[snmp-pass]: [4, 8) 6 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| @bytes[ls]: [2, 4) 9 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| @bytes[snmpd]: [1] 1 |@@@@ | [2, 4) 0 | | [4, 8) 0 | | [8, 16) 4 |@@@@@@@@@@@@@@@@@@ | [16, 32) 11 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| @bytes[irqbalance]: (..., 0) 15 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [0] 0 | | [1] 0 | | [2, 4) 0 | | [4, 8) 21 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| ``` ## 9. `lhist()`: Linear Histogram Syntax: ``` @histogram_name[optional_key] = lhist(value, min, max, step) ``` This is implemented using a BPF map. `min` must be non-negative. Examples: ``` # bpftrace -e 'kretprobe:vfs_read { @bytes = lhist(retval, 0, 10000, 1000); }' Attaching 1 probe... ^C @bytes: [0, 1000) 480 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [1000, 2000) 49 |@@@@@ | [2000, 3000) 12 |@ | [3000, 4000) 39 |@@@@ | [4000, 5000) 267 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | ``` ## 10. `print()`: Print Map Syntax: ```print(@map [, top [, divisor]])``` The `print()` function will print a map, similar to the automatic printing when bpftrace ends. Two optional arguments can be provided: a top number, so that only the top number of entries are printed, and a divisor, which divides the value. A couple of examples will explain their use. As an example of top, tracing `vfs` operations and printing the top 5: ``` # bpftrace -e 'kprobe:vfs_* { @[func] = count(); } END { print(@, 5); clear(@); }' Attaching 54 probes... ^C @[vfs_getattr]: 91 @[vfs_getattr_nosec]: 92 @[vfs_statx_fd]: 135 @[vfs_open]: 188 @[vfs_read]: 405 ``` The final `clear()` is used to prevent printing the map automatically on exit. As an example of divisor, summing total time in vfs_read() by process name as milliseconds: ``` # bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; } kretprobe:vfs_read /@start[tid]/ {@ms[pid] = sum(nsecs - @start[tid]); delete(@start[tid]); } END { print(@ms, 0, 1000000); clear(@ms); clear(@start); }' ``` This one-liner sums the vfs_read() durations as nanoseconds, and then does the division to milliseconds when printing. Without this capability, should one try to divide to milliseconds when summing (eg, `sum((nsecs - @start[tid]) / 1000000)`), the value would often be rounded to zero, and not accumulate as it should. # Output ## 1. `printf()`: Per-Event Output Syntax: `printf(char *format, arguments)` Per-event details can be printed using `print()`. Examples: ``` # bpftrace -e 'kprobe:do_nanosleep { printf("sleep by %d\n", tid); }' Attaching 1 probe... sleep by 3669 sleep by 1396 sleep by 3669 sleep by 1396 [...] ``` ## 2. `interval`: Interval output Syntax: `interval:s:duration_seconds` Examples: ``` # bpftrace -e 'kprobe:do_sys_open { @opens = @opens + 1; } interval:s:1 { printf("opens/sec: %d\n", @opens); @opens = 0; }' Attaching 2 probes... opens/sec: 16 opens/sec: 2 opens/sec: 3 opens/sec: 15 opens/sec: 8 opens/sec: 2 ^C @opens: 2 ``` ## 3. `hist()`, `print()`: Histogram Printing Declared histograms are automatically printed out on program termination. See [5. Histograms](#5-histograms) for declarations. Examples: ``` # bpftrace -e 'kretprobe:vfs_read { @bytes = hist(retval); }' Attaching 1 probe... ^C @bytes: (..., 0) 117 |@@@@@@@@@@@@ | [0] 5 | | [1] 325 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [2, 4) 6 | | [4, 8) 3 | | [8, 16) 495 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [16, 32) 35 |@@@ | [32, 64) 25 |@@ | [64, 128) 21 |@@ | [128, 256) 1 | | [256, 512) 3 | | [512, 1K) 2 | | [1K, 2K) 1 | | [2K, 4K) 2 | | ``` Histograms can also be printed on-demand, using the `print()` function. Eg: ``` # bpftrace -e 'kretprobe:vfs_read { @bytes = hist(retval); } interval:s:1 { print(@bytes); clear(@bytes); }' [...] ``` # Advanced Tools bpftrace can be used to create some powerful one-liners and some simple tools. For complex tools, which may involve command line options, positional parameters, argument processing, and customized output, consider switching to [bcc](https://github.com/iovisor/bcc). bcc provides Python (and other) front-ends, enabling usage of all the other Python libraries (including argparse), as well as a direct control of the kernel BPF program. The down side is that bcc is much more verbose and laborious to program. Together, bpftrace and bcc are complimentary. An expected development path would be exploration with bpftrace one-liners, then and ad hoc scripting with bpftrace, then finally, when needed, advanced tooling with bcc. As an example of bpftrace vs bcc differences, the bpftrace xfsdist.bt tool also exists in bcc as xfsdist.py. Both measure the same functions and produce the same summary of information. However, the bcc version supports various arguments: ``` # ./xfsdist.py -h usage: xfsdist.py [-h] [-T] [-m] [-p PID] [interval] [count] Summarize XFS operation latency positional arguments: interval output interval, in seconds count number of outputs optional arguments: -h, --help show this help message and exit -T, --notimestamp don't include timestamp on interval output -m, --milliseconds output in milliseconds -p PID, --pid PID trace this PID only examples: ./xfsdist # show operation latency as a histogram ./xfsdist -p 181 # trace PID 181 only ./xfsdist 1 10 # print 1 second summaries, 10 times ./xfsdist -m 5 # 5s summaries, milliseconds ``` The bcc version is 131 lines of code. The bpftrace version is 22. # Errors ## 1. Looks like the BPF stack limit of 512 bytes is exceeded BPF programs that operate on many data items may hit this limit. There are a number of things you can try to stay within the limit: 1. Find ways to reduce the size of the data used in the program. Eg, avoid strings if they are unnecessary: use `pid` instead of `comm`. Use fewer map keys. 1. Split your program over multiple probes. 1. Check the status of the BPF stack limit in Linux (it may be increased in the future, maybe as a tuneabe). 1. (advanced): Run -d and examine the LLVM IR, and look for ways to optimize src/ast/codegen_llvm.cpp. ## 2. Kernel headers not found bpftrace requires kernel headers for certain features, which are searched for by default in: ```bash /lib/modules/$(uname -r) ``` The default search directory can be overridden using the environment variable `BPFTRACE_KERNEL_SOURCE`. bpftrace-0.9.4/docs/release_process.md000066400000000000000000000041301361633214400177430ustar00rootroot00000000000000# Release procedure This document describes how to release a new bpftrace version. The "release manager" (RM) can be one or more person. Usually whoever is motivated enough to drive a release. ## Branching model In the usual case, we release directly from master. Reasoning is that bpftrace isn't a huge project yet so complicated branching models and release strategies more get in the way than provide order. If master is really busy or really buggy, the RM can choose to cut a release branch (titled `X.Y.Z_release`) to try and stabilize the code without including work in progress into the release. ## Merging pull requests Please squash + rebase all pull requests (with no merge commit). In other words, there should be one commit in master per pull request. This makes generating changelogs both trivial and precise with the least amount of noise. The exception to this is PRs with complicated changes. If this is the case and the commits are well structured, a rebase + merge (no merge commit) is acceptable. The rule of thumb is the commit titles should make sense in a changelog. ## Semantic versioning We choose to follow semantic versioning. Note that this doesn't matter much for major version < 1 but will matter a lot for >= 1.0.0 releases. See https://semver.org/ . ## Tagging a release You must do 3 things to formally release a version: 1. Update `CHANGELOG.md`. Use the following git command to generate the appropriate data format: ``` git log --oneline ${PREVIOUS_VER}..upstream/master --no-merges --format=" - %s (%h) by %aN <%aE>" ``` where `${PREVIOUS_VER}` is the last release tag, eg `v0.9.3`. Please see previous releases for the final formatting. 1. Update `bpftrace_VERSION_MAJOR`, `bpftrace_VERSION_MINOR`, and `bpftrace_VERSION_PATCH` in `CMakeLists.txt` to the target version. 1. Tag a release. We do this in the github UI by clicking "releases" (on same line as "commits"), then "Draft a new release". The tag version and release title should be the same and in `vX.Y.Z` format. The tag description should be the same as what you added to `CHANGELOG.md`. bpftrace-0.9.4/docs/tutorial_one_liners.md000066400000000000000000000414571361633214400206620ustar00rootroot00000000000000# The bpftrace One-Liner Tutorial This teaches you bpftrace for Linux in 12 easy lessons, where each lesson is a one-liner you can try running. This series of one-liners introduces concepts which are summarized as bullet points. For a full reference to bpftrace, see the [Reference Guide](reference_guide.md) Contributed by Brendan Gregg, Netflix (2018), based on his FreeBSD [DTrace Tutorial](https://wiki.freebsd.org/DTrace/Tutorial). # Lesson 1. Listing Probes ``` bpftrace -l 'tracepoint:syscalls:sys_enter_*' ``` "bpftrace -l" lists all probes, and a search term can be added. - A probe is an instrumentation point for capturing event data. - The supplied search term supports wildcards/globs (`*` and `?`) - "bpftrace -l" can also be piped to grep(1) for full regular expression searching. # Lesson 2. Hello World ``` # bpftrace -e 'BEGIN { printf("hello world\n"); }' Attaching 1 probe... hello world ^C ``` This prints a welcome message. Run it, then hit Ctrl-C to end. - The word `BEGIN` is a special probe that fires at the start of the program (like awk's BEGIN). You can use it to set variables and print headers. - An action can be associated with probes, in { }. This example calls printf() when the probe fires. # Lesson 3. File Opens ``` # bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }' Attaching 1 probe... snmp-pass /proc/cpuinfo snmp-pass /proc/stat snmpd /proc/net/dev snmpd /proc/net/if_inet6 ^C ``` This traces file opens as they happen, and we're printing the process name and pathname. - It begins with the probe `tracepoint:syscalls:sys_enter_openat`: this is the tracepoint probe type (kernel static tracing), and is instrumenting when the `openat()` syscall begins (is entered). Tracepoints are preferred over kprobes (kernel dynamic tracing, introduced in lesson 6), since tracepoints have stable API. Note: In modern Linux systems (glibc >= 2.26) the `open` wrapper always calls the `openat` syscall. - `comm` is a builtin variable that has the current process's name. Other similar builtins include pid and tid. - `args` is a pointer to a struct containing all the tracepoint arguments. This struct is automatically generated by bpftrace based tracepoint information. The members of this struct can be found with: `bpftrace -vl tracepoint:syscalls:sys_enter_openat`. - `args->filename` dereferences the `args` struct and gets the value of the `filename` member. - `str()` turns a pointer into the string it points to. # Lesson 4. Syscall Counts By Process ``` bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }' Attaching 1 probe... ^C @[bpftrace]: 6 @[systemd]: 24 @[snmp-pass]: 96 @[sshd]: 125 ``` This summarizes syscalls by process name, printing a report on Ctrl-C. - @: This denotes a special variable type called a map, which can store and summarize data in different ways. You can add an optional variable name after the @, eg "@num", either to improve readability, or to differentiate between more than one map. - []: The optional brackets allow a key to be set for the map, much like an associative array. - count(): This is a map function – the way it is populated. count() counts the number of times it is called. Since this is saved by comm, the result is a frequency count of system calls by process name. Maps are automatically printed when bpftrace ends (eg, via Ctrl-C). # Lesson 5. Distribution of read() Bytes ``` # bpftrace -e 'tracepoint:syscalls:sys_exit_read /pid == 18644/ { @bytes = hist(args->ret); }' Attaching 1 probe... ^C @bytes: [0, 1] 12 |@@@@@@@@@@@@@@@@@@@@ | [2, 4) 18 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [4, 8) 0 | | [8, 16) 0 | | [16, 32) 0 | | [32, 64) 30 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [64, 128) 19 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [128, 256) 1 |@ ``` This summarizes the return value of the sys_read() kernel function for PID 18644, printing it as a histogram. - /.../: This is a filter (aka predicate), which acts as a filter for the action. The action is only executed if the filtered expression is true, in this case, only for the process ID 18644. Boolean operators are supported ("&&", "||"). - ret: This is the return value of the function. For sys_read(), this is either -1 (error) or the number of bytes successfully read. - @: This is a map similar to the previous lesson, but without any keys ([]) this time, and the name "bytes" which decorates the output. - hist(): This is a map function which summarizes the argument as a power-of-2 histogram. The output shows rows that begin with interval notation, where, for example `[128, 256)` means that the value is: 128 <= value < 256. The next number is the count of occurrences, and then an ASCII histogram is printed to visualize that count. The histogram can be used to study multi-modal distributions. - Other map functions include lhist() (linear hist), count(), sum(), avg(), min(), and max(). # Lesson 6. Kernel Dynamic Tracing of read() Bytes ``` # bpftrace -e 'kretprobe:vfs_read { @bytes = lhist(retval, 0, 2000, 200); }' Attaching 1 probe... ^C @bytes: (...,0] 0 | | [0, 200) 66 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [200, 400) 2 |@ | [400, 600) 3 |@@ | [600, 800) 0 | | [800, 1000) 5 |@@@ | [1000, 1200) 0 | | [1200, 1400) 0 | | [1400, 1600) 0 | | [1600, 1800) 0 | | [1800, 2000) 0 | | [2000,...) 39 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | ``` Summarize read() bytes as a linear histogram, and traced using kernel dynamic tracing. - It begins with the probe `kretprobe:vfs_read`: this is the kretprobe probe type (kernel dynamic tracing of function returns) instrumenting the `vfs_read()` kernel function. There is also the kprobe probe type (shown in the next lesson), to instrument when functions begin execution (are entered). These are powerful probe types, letting you trace tens of thousands of different kernel functions. However, these are "unstable" probe types: since they can trace any kernel function, there is no guarantee that your kprobe/kretprobe will work between kernel versions, as the function names, arguments, return values, and roles may change. Also, since it is tracing the raw kernel, you'll need to browse the kernel source to understand what these probes, arguments, and return values, mean. - lhist(): this is a linear histogram, where the arguments are: value, min, max, step. The first argument (`retval`) of vfs_read() is the return value: the number of bytes read. # Lesson 7. Timing read()s ``` # bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; } kretprobe:vfs_read /@start[tid]/ { @ns[comm] = hist(nsecs - @start[tid]); delete(@start[tid]); }' Attaching 2 probes... [...] @ns[snmp-pass]: [0, 1] 0 | | [2, 4) 0 | | [4, 8) 0 | | [8, 16) 0 | | [16, 32) 0 | | [32, 64) 0 | | [64, 128) 0 | | [128, 256) 0 | | [256, 512) 27 |@@@@@@@@@ | [512, 1k) 125 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [1k, 2k) 22 |@@@@@@@ | [2k, 4k) 1 | | [4k, 8k) 10 |@@@ | [8k, 16k) 1 | | [16k, 32k) 3 |@ | [32k, 64k) 144 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [64k, 128k) 7 |@@ | [128k, 256k) 28 |@@@@@@@@@@ | [256k, 512k) 2 | | [512k, 1M) 3 |@ | [1M, 2M) 1 | | ``` Summarize the time spent in read(), in nanoseconds, as a histogram, by process name. - @start[tid]: This uses the thread ID as a key. There may be many reads in-flight, and we want to store a start timestamp to each. How? We could construct a unique identifier for each read, and use that as the key. But because kernel threads can only be executing one syscall at a time, we can use the thread ID as the unique identifier, as each thread cannot be executing more than one. - nsecs: Nanoseconds since boot. This is a high resolution timestamp counter than can be used to time events. - /@start[tid]/: This filter checks that the start time was seen and recorded. Without this filter, this program may be launched during a read and only catch the end, resulting in a time calculation of now - zero, instead of now - start. - delete(@start[tid]): this frees the variable. # Lesson 8. Count Process-Level Events ``` # bpftrace -e 'tracepoint:sched:sched* { @[probe] = count(); } interval:s:5 { exit(); }' Attaching 25 probes... @[tracepoint:sched:sched_wakeup_new]: 1 @[tracepoint:sched:sched_process_fork]: 1 @[tracepoint:sched:sched_process_exec]: 1 @[tracepoint:sched:sched_process_exit]: 1 @[tracepoint:sched:sched_process_free]: 2 @[tracepoint:sched:sched_process_wait]: 7 @[tracepoint:sched:sched_wake_idle_without_ipi]: 53 @[tracepoint:sched:sched_stat_runtime]: 212 @[tracepoint:sched:sched_wakeup]: 253 @[tracepoint:sched:sched_waking]: 253 @[tracepoint:sched:sched_switch]: 510 ``` Count process-level events for five seconds, printing a summary. - sched: The `sched` probe category has high-level scheduler and process events, such as fork, exec, and context switch. - probe: The full name of the probe. - interval:s:5: This is a probe that fires once every 5 seconds, on one CPU only. It is used for creating script-level intervals or timeouts. - exit(): This exits bpftrace. # Lesson 9. Profile On-CPU Kernel Stacks ``` # bpftrace -e 'profile:hz:99 { @[kstack] = count(); }' Attaching 1 probe... ^C [...] @[ filemap_map_pages+181 __handle_mm_fault+2905 handle_mm_fault+250 __do_page_fault+599 async_page_fault+69 ]: 12 [...] @[ cpuidle_enter_state+164 do_idle+390 cpu_startup_entry+111 start_secondary+423 secondary_startup_64+165 ]: 22122 ``` Profile kernel stacks at 99 Hertz, printing a frequency count. - profile:hz:99: This fires on all CPUs at 99 Hertz. Why 99 and not 100 or 1000? We want frequent enough to catch both the big and small picture of execution, but not too frequent as to perturb performance. 100 Hertz is enough. But we don't want 100 exactly, as sampling may occur in lockstep with other timed activities, hence 99. - kstack: Returns the kernel stack trace. This is used as a key for the map, so that it can be frequency counted. The output of this is ideal to be visualized as a flame graph. There is also `ustack` for the user-level stack trace. # Lesson 10. Scheduler Tracing ``` # bpftrace -e 'tracepoint:sched:sched_switch { @[kstack] = count(); }' ^C [...] @[ __schedule+697 __schedule+697 schedule+50 schedule_timeout+365 xfsaild+274 kthread+248 ret_from_fork+53 ]: 73 @[ __schedule+697 __schedule+697 schedule_idle+40 do_idle+356 cpu_startup_entry+111 start_secondary+423 secondary_startup_64+165 ]: 305 ``` This counts stack traces that led to context switching (off-CPU) events. The above output has been truncated to show the last two only. - sched: The sched category has tracepoints for different kernel CPU scheduler events: sched_switch, sched_wakeup, sched_migrate_task, etc. - sched_switch: This probe fires when a thread leaves CPU. This will be a blocking event: eg, waiting on I/O, a timer, paging/swapping, or a lock. - kstack: A kernel stack trace. - sched_switch fires in thread context, so that the stack refers to the thread who is leaving. As you use other probe types, pay attention to context, as comm, pid, kstack, etc, may not refer to the target of the probe. # Lesson 11. Block I/O Tracing ``` # bpftrace -e 'tracepoint:block:block_rq_issue { @ = hist(args->bytes); }' Attaching 1 probe... ^C @: [0, 1] 1 |@@ | [2, 4) 0 | | [4, 8) 0 | | [8, 16) 0 | | [16, 32) 0 | | [32, 64) 0 | | [64, 128) 0 | | [128, 256) 0 | | [256, 512) 0 | | [512, 1K) 0 | | [1K, 2K) 0 | | [2K, 4K) 0 | | [4K, 8K) 24 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [8K, 16K) 2 |@@@@ | [16K, 32K) 6 |@@@@@@@@@@@@@ | [32K, 64K) 5 |@@@@@@@@@@ | [64K, 128K) 0 | | [128K, 256K) 1 |@@ | ``` Block I/O requests by size in bytes, as a histogram. - tracepoint:block: The block category of tracepoints traces various block I/O (storage) events. - block_rq_issue: This fires when an I/O is issued to the device. - args->bytes: This is a member from the tracepoint block_rq_issue arguments which shows the size in bytes. The context of this probe is important: this fires when the I/O is issued to the device. This often happens in process context, where builtins like comm will show you the process name, but it can also happen from kernel context (eg, readahead) when the pid and comm will not show the application you expect. # Lesson 12. Kernel Struct Tracing ``` # cat path.bt #include #include kprobe:vfs_open { printf("open path: %s\n", str(((struct path *)arg0)->dentry->d_name.name)); } # bpftrace path.bt Attaching 1 probe... open path: dev open path: if_inet6 open path: retrans_time_ms [...] ``` This uses kernel dynamic tracing of the vfs_open() function, which has a (struct path *) as the first argument. - kprobe: As mentioned earlier, this is the kernel dynamic tracing probe type, which traces the entry of kernel functions (use kretprobe to trace their returns). - `arg0` is a builtin variable containing the first probe argument, the meaning of which is defined by the probe type. For `kprobe`, it is the first argument to the function. Other arguments can be accessed as arg1, ..., argN. - `((struct path *)arg0)->dentry->d_name.name`: this casts `arg0` as `struct path *`, then dereferences dentry, etc. - #include: these were necessary to include struct definitions for path and dentry. The kernel struct support is the same as bcc, making use of kernel headers. This means that many structs are available, but not everything, and sometimes it might be necessary to manually include a struct. For an example of this, see the [dcsnoop tool](../tools/dcsnoop.bt), which includes a portion of struct nameidata manually as it wasn't in the available headers. If the kernel has BTF (BPF Type Format) data, all kernel structs are always available. At this point you understand much of bpftrace, and can begin to use and write powerful one-liners. See the [Reference Guide](reference_guide.md) for more capabilities. bpftrace-0.9.4/docs/tutorial_one_liners_chinese.md000066400000000000000000000377121361633214400223570ustar00rootroot00000000000000# bpftrace一行教程 该教程通过12个简单小节帮助你了解bpftrace的使用。每一小节都是一行的命令,你可以立马运行并看到运行效果。该教程系列用来介绍bpftrace的概念。关于bpftrace的完整参考,见[bpftrace完整参考](reference_guide.md)。 该教程贡献者是Brendan Gregg, Netflix (2018), 基于他的DTrace教程系列 [DTrace Tutorial](https://wiki.freebsd.org/DTrace/Tutorial)。 # 1. 列出所有探针 ``` bpftrace -l 'tracepoint:syscalls:sys_enter_*' ``` "bpftrace -l" 列出所有探测点,并且可以添加搜索项。 - 探针是用于捕获事件数据的检测点。 - 提供的搜索词支持通配符如*/? - "bpftrace -l" 也可以通过管道传递给grep,进行完整的正则表达式搜索。 # 2. Hello World ``` # bpftrace -e 'BEGIN { printf("hello world\n"); }' Attaching 1 probe... hello world ^C ``` 打印欢迎消息。 运行后, 按Ctrl-C结束。 - `BEGIN`是一个特殊的探针,在程序开始的执行触发探针执行,你可以使用它设置变量和打印消息头。 - 探针可以关联动作,把动作放到{}中。 # 3. 文件打开 ``` # bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }' Attaching 1 probe... snmp-pass /proc/cpuinfo snmp-pass /proc/stat snmpd /proc/net/dev snmpd /proc/net/if_inet6 ^C ``` 这里我们跟踪文件打开的时候打印进程名和文件名。 - 该命令开始是`tracepoint:syscalls:sys_enter_openat`: 这个是tracepoint探针类型(内核静态跟踪),当进入`openat()`系统调用时执行该探针。相比kprobes探针(内核动态跟踪,在第6节介绍),我们更加喜欢用tracepoints探针,因为tracepoints有稳定的应用程序编程接口。注意:现代linux系统(glibc >= 2.26),`open`总是调用`openat`系统调用。 - `comm`是内建变量,代表当前进程的名字。其它类似的变量还有pid和tid,分别表示进程标识和线程标识。 - `args`是一个指针,指向该tracepoint的参数。这个结构时由bpftrace根据tracepoint信息自动生成的。这个结构的成员可以通过命令`bpftrace -vl tracepoint:syscalls:sys_enter_openat`找到。 - `args->filename`用来获取args的成员变量`filename`的值。 - `str()`用来把字符串指针转换成字符串。 # 4. 进程的系统调用记数统计 ``` bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }' Attaching 1 probe... ^C @[bpftrace]: 6 @[systemd]: 24 @[snmp-pass]: 96 @[sshd]: 125 ``` 按Ctrl-C后打印进程的系统调用计数。 - @: 表示一种特殊的变量类型,称为map,可以以不同的方式来存储和描述数据。你可以在@后添加可选的变量名,增加可读性或由多个map时用来区分不同的map。 - []: 可选的中括号允许设置map的关键字,比较像关联数组 - count(): 这个时一个map函数 - 记录被调用次数。因为调用次数保存在comm的map里,所以结果是进程执行系统调用的计数统计。 在bpftrace结束(如按Ctrl-C)时Maps自动打印出来 # 5. read()分布统计 ``` # bpftrace -e 'tracepoint:syscalls:sys_exit_read /pid == 18644/ { @bytes = hist(args->ret); }' Attaching 1 probe... ^C @bytes: [0, 1] 12 |@@@@@@@@@@@@@@@@@@@@ | [2, 4) 18 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [4, 8) 0 | | [8, 16) 0 | | [16, 32) 0 | | [32, 64) 30 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [64, 128) 19 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [128, 256) 1 |@ ``` 这里跟踪进程号为18644的进程执行内核函数sys_read()的统计,打印出直方图。 - /.../: 这里设置一个过滤条件(条件判断),满足该过滤条件时才执行{}里面的动作。在这个例子中意思时只追踪进程号为18644的进程。过滤条件表达式也支持布尔运算如("&&", "||")。 - ret: 表示函数的返回值。对于sys_read(),-1表示错误,其它则表示成功读取的字节数。 - @: 类似于上节的map,但是这里没有[],使用"bytes"修饰输出。 - hist(): 一个map函数,用来描述直方图的参数。输出行以2次方的间隔开始,如`[128, 256)`表示值大于等于128且小于256。后面跟着位于该区间的个数统计,最后是ascii码表示的直方图。该图可以用来研究它的模式分布。 - 其它的map函数还有lhist(线性直方图),count(),sum(),avg(),min()和max()。 # 6. 内核动态跟踪read()的字节数 ``` # bpftrace -e 'kretprobe:vfs_read { @bytes = lhist(retval, 0, 2000, 200); }' Attaching 1 probe... ^C @bytes: (...,0] 0 | | [0, 200) 66 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [200, 400) 2 |@ | [400, 600) 3 |@@ | [600, 800) 0 | | [800, 1000) 5 |@@@ | [1000, 1200) 0 | | [1200, 1400) 0 | | [1400, 1600) 0 | | [1600, 1800) 0 | | [1800, 2000) 0 | | [2000,...) 39 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | ``` 使用内核动态跟踪技术显示read()返回字节数的直方图。 - `kretprobe:vfs_read`: 这里是kretprobe类型(动态跟踪内核函数返回值)的跟踪`vfs_read`系统调用探针。此外还有kprobe类型的探针用于跟踪内核函数调用。它们是功能强大的探针类型,让我们可以跟踪成千上万的内核函数。然而它们是"不稳定"的探针类型:因为它们可以跟踪任意内核函数,不保证kprobe和kretprobe能够正常工作。由于内核版本的不同,内核函数名,参数,返回值等可能会变化。由于它们用来跟踪底层内核的,你需要浏览内核源代码,理解这些探针的参数、返回值的意义。 - lhist(): 线性直方图函数:参数是值,最小,最大,步进值。第一个参数(`retval`)表示系统调用sys_read()返回值:即成功读取的字节数。 # 7. read()调用的时间 ``` # bpftrace -e 'kprobe:vfs_read { @start[tid] = nsecs; } kretprobe:vfs_read /@start[tid]/ { @ns[comm] = hist(nsecs - @start[tid]); delete(@start[tid]); }' Attaching 2 probes... [...] @ns[snmp-pass]: [0, 1] 0 | | [2, 4) 0 | | [4, 8) 0 | | [8, 16) 0 | | [16, 32) 0 | | [32, 64) 0 | | [64, 128) 0 | | [128, 256) 0 | | [256, 512) 27 |@@@@@@@@@ | [512, 1k) 125 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [1k, 2k) 22 |@@@@@@@ | [2k, 4k) 1 | | [4k, 8k) 10 |@@@ | [8k, 16k) 1 | | [16k, 32k) 3 |@ | [32k, 64k) 144 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [64k, 128k) 7 |@@ | [128k, 256k) 28 |@@@@@@@@@@ | [256k, 512k) 2 | | [512k, 1M) 3 |@ | [1M, 2M) 1 | | ``` 根据进程名,以直方图的形式显示read()调用花费的时间,时间单位为纳秒。 - @start[tid]: This uses the thread ID as a key. There may be many reads in-flight, and we want to store a start timestamp to each. How? We could construct a unique identifier for each read, and use that as the key. But because kernel threads can only be executing one syscall at a time, we can use the thread ID as the unique identifier, as each thread cannot be executing more than one. - nsecs: 自系统启动到现在的纳秒数。这个是一个高精度时间戳追踪可以用追踪时间事件。 - /@start[tid]/: 该过滤条件检查起始时间。如果没有这个过滤条件,则只能探测每次read()调用的结束时间,而不是now-start。 - delete(@start[tid]): 释放变量。. # 8. 统计进程级别的事件 ``` # bpftrace -e 'tracepoint:sched:sched* { @[probe] = count(); } interval:s:5 { exit(); }' Attaching 25 probes... @[tracepoint:sched:sched_wakeup_new]: 1 @[tracepoint:sched:sched_process_fork]: 1 @[tracepoint:sched:sched_process_exec]: 1 @[tracepoint:sched:sched_process_exit]: 1 @[tracepoint:sched:sched_process_free]: 2 @[tracepoint:sched:sched_process_wait]: 7 @[tracepoint:sched:sched_wake_idle_without_ipi]: 53 @[tracepoint:sched:sched_stat_runtime]: 212 @[tracepoint:sched:sched_wakeup]: 253 @[tracepoint:sched:sched_waking]: 253 @[tracepoint:sched:sched_switch]: 510 ``` 这里统计5秒内进程级的事件并打印。 - sched: `sched`探针可以探测调度器的高级事件和进程事件如fork, exec和上下文切换 - probe: 探针的完整名称 - interval:s:5: 探针每5秒仅在一个cpu上触发一次。用来创建脚本级别的间隔或超时时间。 - exit(): 退出bpftrace。 # 9. 分析内核实时函数栈 ``` # bpftrace -e 'profile:hz:99 { @[kstack] = count(); }' Attaching 1 probe... ^C [...] @[ filemap_map_pages+181 __handle_mm_fault+2905 handle_mm_fault+250 __do_page_fault+599 async_page_fault+69 ]: 12 [...] @[ cpuidle_enter_state+164 do_idle+390 cpu_startup_entry+111 start_secondary+423 secondary_startup_64+165 ]: 22122 ``` 以99赫兹的频率分析内核调用栈并打印次数统计。 - profile:hz:99: 这里所有cpu都以99赫兹的频率采样分析内核栈。为什么是99而不是100或者1000?我们想要抓取足够详细的内核执行时内核栈信息,但是频率太大影响性能。100赫兹足够了,但是我们不想用正好100赫兹,因为采样可能发生在lockstep中,所以99赫兹够用了。 - kstack: 返回内核调用栈。这里作为map的关键字,可以跟踪次数。这些输出信息可以使用火焰图可视化。此外`ustack`用来分析用户级堆栈。 # 10. 调度器跟踪 ``` # bpftrace -e 'tracepoint:sched:sched_switch { @[kstack] = count(); }' ^C [...] @[ __schedule+697 __schedule+697 schedule+50 schedule_timeout+365 xfsaild+274 kthread+248 ret_from_fork+53 ]: 73 @[ __schedule+697 __schedule+697 schedule_idle+40 do_idle+356 cpu_startup_entry+111 start_secondary+423 secondary_startup_64+165 ]: 305 ``` 这里统计进程上下文切换次数。以上输出截断了只输出最上面的两个。 - sched: 跟踪调度类别的调度器事件:sched_switch, sched_wakeup, sched_migrate_task等 - sched_switch: 当线程释放cpu资源,当前不运行时触发。这里可能的阻塞事件:如等待I/O,定时器,分页/交换,锁等 - kstack: 内核堆栈跟踪,打印调用栈 - sched_switch在线程切换的时候触发,打印的调用栈是被切换出cpu的那个线程。你可以使用其它的探针,注意这里的进程上下文,可以打印像comm,pidmkstack等信息,但是可能不能引用切换到切线的上下文信息。 # 11. 块级I/O跟踪 ``` # bpftrace -e 'tracepoint:block:block_rq_issue { @ = hist(args->bytes); }' Attaching 1 probe... ^C @: [0, 1] 1 |@@ | [2, 4) 0 | | [4, 8) 0 | | [8, 16) 0 | | [16, 32) 0 | | [32, 64) 0 | | [64, 128) 0 | | [128, 256) 0 | | [256, 512) 0 | | [512, 1K) 0 | | [1K, 2K) 0 | | [2K, 4K) 0 | | [4K, 8K) 24 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [8K, 16K) 2 |@@@@ | [16K, 32K) 6 |@@@@@@@@@@@@@ | [32K, 64K) 5 |@@@@@@@@@@ | [64K, 128K) 0 | | [128K, 256K) 1 |@@ | ``` 以上是块I/O请求字节数的直方图。 - tracepoint:block: 块类别的跟踪点跟踪块级I/O事件。 - block_rq_issue: 当I/O提交到块设备时触发。 - args->bytes: 跟踪点block_rq_issue的参数成员bytes,表示提交I/O请求的字节数。 探针的上下文是非常重要的: 即当I/O提交给块设备后所处的上下文。通常位于进程上下文,通过内核的comm可以得到进程名,但是也可能是内核上下文,(如readahead),这时不能显示预期的进程号和进程名信息。 # 12. 内核结构跟踪 ``` # cat path.bt #include #include kprobe:vfs_open { printf("open path: %s\n", str(((path *)arg0)->dentry->d_name.name)); } # bpftrace path.bt Attaching 1 probe... open path: dev open path: if_inet6 open path: retrans_time_ms [...] ``` 这里使用内核动态跟踪技术跟踪vfs_read()函数,该函数的(struct path *)作为第一个参数。 - kprobe: 如前面所述,这是内核动态跟踪kprobe探针类型,跟踪内核函数的调用(kretprobe探针类型跟踪内核函数返回值)。 - `arg0` 是一个内建变量,表示探针的第一个参数,其含义由探针类型决定。对于`kprobe`类型探针,它表示函数的第一个参数。其它参数使用arg1,...,argN访问。 - `((path *)arg0)->dentry->d_name.name`: 这里`arg0`作为`path *`并引用dentry。 - #include: 包含必要的path和dentry类型声明的头文件。 bfptrace对内核结构跟踪的支持和bcc是一样的,允许使用头文件。这意味着大多数结构是可用的,但是并不是所有的,有时需要手动增加某些结构的声明。例如这个例子,见[dcsnoop tool](../tools/dcsnoop.bt),包含struct nameidate的声明。在将来,bpftrace会使用新的linux BTF支持,到时候所有结构都将可用。 到这里你已经理解了bpstrace的大部分功能,你可以开始使用和编写强大的一行命令。查阅[参考手册](reference_guide.md)更多的功能。 bpftrace-0.9.4/images/000077500000000000000000000000001361633214400145625ustar00rootroot00000000000000bpftrace-0.9.4/images/bpftrace.graffle000066400000000000000000005120061361633214400177040ustar00rootroot00000000000000 ApplicationVersion com.omnigroup.OmniGrafflePro 138.29.0.155790 CreationDate 2016-03-04 01:58:06 +0000 Creator Brendan Gregg GraphDocumentVersion 6 GuidesLocked NO GuidesVisible YES ImageCounter 1 LinksVisible NO MagnetsVisible NO MasterSheets ModificationDate 2018-09-06 19:01:57 +0000 Modifier Brendan Gregg NotesVisible NO OriginVisible NO PageBreaks YES PrintInfo NSBottomMargin float 41 NSHorizonalPagination int 0 NSLeftMargin float 18 NSPaperSize coded BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAx7X05TU2l6ZT1mZn2WgWQCgRgDhg== NSPrintReverseOrientation int 0 NSRightMargin float 18 NSTopMargin float 18 ReadOnly NO Sheets ActiveLayerIndex 0 AutoAdjust BackgroundGraphic Bounds {{0, 0}, {720, 504}} Class SolidGraphic FontInfo Font CourierNewPS-BoldMT Size 16 ID 2 Style shadow Draws NO stroke Draws NO CanvasOrigin {0, 0} CanvasSize {720, 504} ColumnAlign 1 ColumnSpacing 36 DisplayScale 1 0/72 in = 1.0000 in GraphicsList Class LineGraphic ID 218 Points {100.56071, 286.80136} {100.56071, 336.21786} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic ID 217 Points {100.56071, 243.74657} {100.56071, 218.11617} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic ID 212 Points {116.51337, 211.42293} {17.765594, 211.42293} Style stroke HeadArrow 0 TailArrow 0 Bounds {{594.2088, 450.04092}, {79, 16}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue Size 13 ID 210 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs26 \cf0 Timed Events} VerticalPad 0 Wrap NO Bounds {{592.52771, 397.79242}, {98, 40}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPS-BoldMT Size 18 ID 209 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\b\fs36 \cf0 profile:\ interval:} VerticalPad 0 Wrap NO Bounds {{11.168304, 452.83752}, {86, 16}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue Size 13 ID 208 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs26 \cf0 Special Events} VerticalPad 0 Wrap NO Bounds {{11.542926, 401.00977}, {55, 40}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPS-BoldMT Size 18 ID 207 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\b\fs36 \cf0 BEGIN\ END} VerticalPad 0 Wrap NO Bounds {{674.00085, 225.2804}, {26, 17}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPS-BoldMT Size 14 ID 206 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\b\fs28 \cf0 bus} VerticalPad 0 Wrap NO Class LineGraphic ID 205 Points {707.11572, 245.2608} {667.31061, 245.2608} Style stroke HeadArrow 0 TailArrow 0 Width 1.5 Bounds {{594.2088, 59.240166}, {98, 21}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPS-BoldMT Size 18 ID 203 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\b\fs36 \cf0 hardware:} VerticalPad 0 Wrap NO Bounds {{127.97746, 401.00977}, {98, 21}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPS-BoldMT Size 18 ID 202 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\b\fs36 \cf0 software:} VerticalPad 0 Wrap NO Bounds {{137.27701, 60.738724}, {119, 21}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPS-BoldMT Size 18 ID 200 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 2 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr \f0\b\fs36 \cf0 tracepoint:} VerticalPad 0 Wrap NO Class LineGraphic ID 199 Points {257.00269, 358.54868} {268.39191, 330.24179} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{240.63177, 358.47018}, {34, 17}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPS-BoldMT Size 14 ID 198 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\b\fs28 \cf0 scsi} VerticalPad 0 Wrap NO Class LineGraphic ID 197 Points {132.44882, 339.89978} {132.68912, 273.06277} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{130.54706, 340.3559}, {34, 17}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPS-BoldMT Size 14 ID 196 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 2 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr \f0\b\fs28 \cf0 jbd2} VerticalPad 0 Wrap NO Bounds {{485.17822, 484.28946}, {218, 15}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color w 0.697622 Font HelveticaNeue Size 12 ID 138 Shape Rectangle Style shadow Draws NO stroke Draws NO Text Align 2 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;} {\colortbl;\red255\green255\blue255;\red178\green178\blue178;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr \f0\fs24 \cf2 https://github.com/iovisor/bpftrace 2018} VerticalPad 0 Wrap NO Class LineGraphic ID 195 Points {452.44153, 402.19925} {452.26917, 296.48868} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{431.7403, 403.79242}, {101, 48}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color b 0.6 g 0.6 r 0.6 Font CourierNewPS-BoldMT Size 14 ID 194 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\b\fs28 \cf0 page-faults\ minor-faults\ major-faults} VerticalPad 0 Wrap NO Class LineGraphic ID 193 Points {390.05359, 401.50204} {389.88123, 250.79147} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{284.14206, 404.28827}, {110, 32}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color b 0.6 g 0.6 r 0.6 Font CourierNewPS-BoldMT Size 14 ID 192 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 2 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr \f0\b\fs28 \cf0 cpu-clock\ cs migrations} VerticalPad 0 Wrap NO Class LineGraphic ID 191 Points {720.67969, 390.10159} {0, 390.10159} Style stroke HeadArrow 0 TailArrow 0 Class LineGraphic Head ID 44 ID 188 Points {645.28192, 366.33118} {645.28192, 344.8429} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Tail ID 186 Class LineGraphic Head ID 43 ID 187 Points {642.23309, 194.14841} {642.18158, 224.80887} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{615.78192, 366.33118}, {59, 17}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPSMT Size 14 ID 186 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\b\fs28 \cf0 cache-*} VerticalPad 0 Wrap NO Bounds {{592.52771, 110.81422}, {101, 80}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color b 0.6 g 0.6 r 0.6 Font CourierNewPS-BoldMT Size 14 ID 185 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\b\fs28 \cf0 cpu-cycles\ instructions\ branch-*\ frontend-*\ backend-*} VerticalPad 0 Wrap NO Class LineGraphic ID 184 Points {580.85297, 51.678741} {580.85297, 504} Style stroke HeadArrow 0 TailArrow 0 Class LineGraphic ID 183 Points {117.76019, 51.820648} {117.76019, 504} Style stroke HeadArrow 0 TailArrow 0 Class LineGraphic ID 181 Points {459.71552, 89.123932} {412.92816, 215.27336} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic ID 180 Points {348.47974, 339.62717} {366.03448, 296.155} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{11.542926, 61.738724}, {97, 16}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue Size 13 ID 174 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs26 \cf0 Dynamic Tracing} VerticalPad 0 Wrap NO Bounds {{277.97556, 61.738724}, {80, 16}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue Size 13 ID 50 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs26 \cf0 Static Tracing} VerticalPad 0 Wrap NO Bounds {{609.698, 321.87671}, {71.167847, 22.466171}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 44 Shape Rectangle Style fill Color b 0.851391 g 0.851391 r 0.851391 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 DRAM} Bounds {{616.98218, 225.30887}, {50.32843, 40.90387}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 43 Shape Rectangle Style fill Color b 0.851391 g 0.851391 r 0.851391 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 CPU\ 1} Class LineGraphic ID 113 Points {621.03168, 243.74657} {470.22656, 243.74657} Style stroke HeadArrow 0 TailArrow 0 Width 1.5 Bounds {{649.10779, 275.54425}, {48, 31}} Class ShapedGraphic FitText YES Flow Resize ID 70 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Memory\ Bus} VerticalPad 0 Wrap NO Bounds {{495.49713, 202.92044}, {73, 31}} Class ShapedGraphic FitText YES Flow Resize ID 68 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 CPU\ Interconnect} VerticalPad 0 Wrap NO Class LineGraphic ID 137 Points {720.67969, 51.205944} {0, 51.205944} Style stroke HeadArrow 0 TailArrow 0 Width 0.5 Class LineGraphic ID 168 Points {194.18262, 359.07861} {214.97746, 305.54657} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic ID 171 Points {154.87547, 126.52167} {154.11154, 259.46808} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{5.5429268, 142.82158}, {109, 60}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color b 0 g 0 r 0 Font CourierNewPS-BoldMT Size 18 ID 165 Shape Rectangle Style shadow Draws NO stroke Draws NO Text Align 2 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr \f0\b\fs36 \cf0 uprobe:\ uretprobe:\ usdt:} VerticalPad 0 Wrap NO Bounds {{5.5429268, 245.28812}, {109, 40}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color b 0 g 0 r 0 Font CourierNewPS-BoldMT Size 18 ID 163 Shape Rectangle Style shadow Draws NO stroke Draws NO Text Align 2 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr \f0\b\fs36 \cf0 kprobe:\ kretprobe:} VerticalPad 0 Wrap NO Class LineGraphic ID 161 Points {490.20508, 189.45589} {457.79449, 243.1916} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{483.17822, 107.67329}, {76, 80}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color b 0.6 g 0.6 r 0.6 Font CourierNewPS-BoldMT Size 14 ID 160 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\b\fs28 \cf0 sched\ task\ signal\ timer\ workqueue} VerticalPad 0 Wrap NO Class LineGraphic ID 159 Points {491.18097, 339.74957} {454.96002, 324.67889} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic ID 158 Points {478.23856, 282.89941} {449.48291, 283.48987} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{484.61728, 257.14508}, {76, 48}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPSMT Size 14 ID 157 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\b\fs28 \cf0 kmem\ vmscan\ writeback} VerticalPad 0 Wrap NO Bounds {{495.49713, 335.2966}, {26, 17}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPSMT Size 14 ID 155 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\b\fs28 \cf0 irq} VerticalPad 0 Wrap NO Bounds {{175.27701, 358.91217}, {43, 17}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPS-BoldMT Size 14 ID 154 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 2 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr \f0\b\fs28 \cf0 block} VerticalPad 0 Wrap NO Bounds {{145.91074, 104.60594}, {34, 17}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPS-BoldMT Size 14 ID 153 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 2 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr \f0\b\fs28 \cf0 ext4} VerticalPad 0 Wrap NO Bounds {{432.88959, 69.687424}, {68, 17}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPS-BoldMT Size 14 ID 152 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 2 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr \f0\b\fs28 \cf0 syscalls} VerticalPad 0 Wrap NO Bounds {{387.52762, 104.94469}, {34, 17}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPS-BoldMT Size 14 ID 201 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\b\fs28 \cf0 sock} VerticalPad 0 Wrap NO Class LineGraphic ID 142 Points {403.37744, 124.20242} {360.62976, 234.90355} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{338.05746, 340.3559}, {26, 32}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font CourierNewPSMT Size 14 ID 141 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 CourierNewPS-BoldMT;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\b\fs28 \cf0 net\ skb} VerticalPad 0 Wrap NO Bounds {{247.66663, 21.18483}, {186, 26}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Thin Size 21 ID 136 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Thin;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs42 \cf0 bpftrace Probe Types} VerticalPad 0 Wrap NO Bounds {{127.24895, 311.14508}, {342.55994, 22.466171}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 90 Shape Rectangle Style fill Color b 0.989631 g 0.927333 r 0.755614 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Device Drivers} Bounds {{198.2724, 176.34805}, {271.53644, 22.466171}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 94 Shape Rectangle Style fill Color b 0.816575 g 0.817319 r 0.990984 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 System Libraries} Bounds {{240.47348, 115.41571}, {105, 16}} Class ShapedGraphic FitText YES Flow Resize ID 46 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Operating System} VerticalPad 0 Wrap NO Bounds {{378.9624, 266.21274}, {90.846375, 44.932343}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 89 Shape Rectangle Style fill Color b 0.802726 g 0.939072 r 0.998695 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Virtual Memory} Bounds {{378.9624, 221.2804}, {90.846375, 44.932343}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 85 Shape Rectangle Style fill Color b 0.802726 g 0.939072 r 0.998695 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Scheduler\ } Bounds {{277.97556, 288.67892}, {100.98691, 22.466171}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 88 Shape Rectangle Style fill Color b 0.797729 g 0.916695 r 0.851254 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Ethernet} Bounds {{277.97556, 266.21274}, {100.98691, 22.466171}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 87 Shape Rectangle Style fill Color b 0.797729 g 0.916695 r 0.851254 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 IP} Bounds {{277.97556, 243.74657}, {100.98691, 22.466171}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 86 Shape Rectangle Style fill Color b 0.797729 g 0.916695 r 0.851254 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 TCP/UDP} Bounds {{277.97556, 221.2804}, {100.98691, 22.466171}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 84 Shape Rectangle Style fill Color b 0.797729 g 0.916695 r 0.851254 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Sockets} Bounds {{127.24895, 288.67889}, {150.72662, 22.466171}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 81 Shape Rectangle Style fill Color b 0.989599 g 0.860461 r 0.79432 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Block Device Interface} Bounds {{127.24889, 266.21274}, {150.72662, 22.466171}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 82 Shape Rectangle Style fill Color b 0.989599 g 0.860461 r 0.79432 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Volume Manager} Bounds {{127.24895, 243.74657}, {150.72662, 22.466171}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 80 Shape Rectangle Style fill Color b 0.989599 g 0.860461 r 0.79432 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 File Systems} Bounds {{127.24889, 221.2804}, {150.72662, 22.466171}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 79 Shape Rectangle Style fill Color b 0.989599 g 0.860461 r 0.79432 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 VFS} Bounds {{127.24895, 198.81422}, {342.55994, 22.466171}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 83 Shape Rectangle Style fill Color b 0.840392 g 0.984419 r 0.994962 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 System Call Interface} Bounds {{127.24895, 137.62154}, {342.55994, 61.192688}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 78 Shape Rectangle Style fill Color b 0.816575 g 0.817319 r 0.990984 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;\f1\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Applications\ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f1\b \cf0 \ } Class LineGraphic ID 114 Points {642.23309, 266.71274} {642.52075, 336.21786} Style stroke HeadArrow 0 TailArrow 0 Width 1.5 GridInfo HPages 1 KeepToScale Layers Lock NO Name Layer 1 Print YES View YES LayoutInfo Animate NO circoMinDist 18 circoSeparation 0.0 layoutEngine dot neatoSeparation 0.0 twopiSeparation 0.0 Orientation 1 PrintOnePage RowAlign 1 RowSpacing 36 SheetTitle probe types 2018 UniqueID 35 VPages 1 ActiveLayerIndex 0 AutoAdjust BackgroundGraphic Bounds {{0, 0}, {720, 504}} Class SolidGraphic FontInfo Font HelveticaNeue-Medium Size 14 ID 2 Style shadow Draws NO stroke Draws NO CanvasOrigin {0, 0} CanvasSize {720, 504} ColumnAlign 1 ColumnSpacing 36 DisplayScale 1 0/72 in = 1.0000 in GraphicsList Class LineGraphic ID 273 Points {367.90503, 278.2301} {367.90503, 327.57556} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic ID 272 Points {341.46149, 158.34779} {128.52332, 124.22475} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic ID 271 Points {327.12799, 220.11488} {129.4769, 242.09456} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{469.24796, 151.50769}, {65, 12}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 270 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs18 \cf0 process structs} VerticalPad 0 Wrap NO Class LineGraphic Head ID 179 ID 266 Points {402.69992, 177.07678} {402.69992, 192.24722} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Tail ID 264 Bounds {{77.252029, 115.34409}, {49, 12}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 265 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs20 \cf0 structs_} VerticalPad 0 Wrap NO Bounds {{341.64667, 142.57678}, {122.10646, 34}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 264 Shape Rectangle Style fill Color b 0.840392 g 0.984419 r 0.994962 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Clang Parser\ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \fs18 \cf0 clang_parser.*} Bounds {{596.92767, 149.64587}, {45, 17}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 13 ID 263 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs26 \cf0 Events:} VerticalPad 0 Wrap NO Class LineGraphic ID 262 Points {327.71173, 261.40762} {130.06064, 283.3873} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{204.50983, 255.98492}, {57.269684, 12}} Class ShapedGraphic FitText Vertical Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 259 Rotation 353 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs20 \cf0 name_ids_} VerticalPad 0 Class LineGraphic ID 256 Points {328.19995, 198.0903} {128.40494, 153.61374} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{52.51329, 134.49333}, {73, 36}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 255 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs20 \cf0 printf_args_\ stackid_map_\ ...} VerticalPad 0 Wrap NO Bounds {{208.23531, 102.76789}, {73, 12}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 253 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs18 \cf0 bpftrace program} VerticalPad 0 Wrap NO Bounds {{367.90503, 455.75885}, {85, 12}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Italic Size 14 ID 252 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs20 \cf0 AsyncAction::*} VerticalPad 0 Wrap NO Bounds {{266.16663, 455.78265}, {49, 12}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Italic Size 14 ID 251 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs20 \cf0 printf()} VerticalPad 0 Wrap NO Bounds {{483.05966, 288.89569}, {60, 12}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 250 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs18 \cf0 BPF func calls} VerticalPad 0 Wrap NO Bounds {{483.05969, 249.63867}, {58, 23}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 249 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs18 \cf0 AST Nodes to\ LLVM IR calls} VerticalPad 0 Wrap NO Class LineGraphic ID 248 Points {671.17255, 367.70795} {671.17255, 245.32845} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic ID 247 Points {665.60895, 432.4906} {665.60895, 442.52478} Style stroke HeadArrow 0 HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic ID 246 Points {633.69141, 393.59317} {633.69141, 407.73416} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{609.55481, 328.13684}, {50.13974, 18}} Class ShapedGraphic FontInfo Font HelveticaNeue-Medium Size 12 ID 245 Shape Rectangle Style fill Color b 0.880332 g 0.985746 r 0.904703 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs24 \cf0 Verifier} Bounds {{618.401, 285.35843}, {26, 17}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 13 ID 244 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs26 \cf0 BPF} VerticalPad 0 Wrap NO Bounds {{596.92767, 227.44653}, {79, 18}} Class ShapedGraphic FontInfo Font HelveticaNeue-Medium Size 11 ID 243 Shape Rectangle Style fill Color b 0.880332 g 0.985746 r 0.904703 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs22 \cf0 perf_events} Bounds {{596.92767, 173.44653}, {79, 18}} Class ShapedGraphic FontInfo Font HelveticaNeue-Medium Size 11 ID 242 Shape Rectangle Style fill Color b 0.880332 g 0.985746 r 0.904703 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs22 \cf0 tracepoints} Bounds {{597.04163, 209.44653}, {79, 18}} Class ShapedGraphic FontInfo Font HelveticaNeue-Medium Size 11 ID 241 Shape Rectangle Style fill Color b 0.880332 g 0.985746 r 0.904703 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs22 \cf0 uprobes} Bounds {{597.04163, 191.44653}, {79, 18}} Class ShapedGraphic FontInfo Font HelveticaNeue-Medium Size 11 ID 240 Shape Rectangle Style fill Color b 0.880332 g 0.985746 r 0.904703 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs22 \cf0 kprobes} Bounds {{265.43835, 434.62363}, {166, 14}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 11 ID 239 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs22 \cf0 Per-event Output, Async Actions} VerticalPad 0 Wrap NO Bounds {{52.413818, 412.15796}, {67, 12}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 238 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs20 \cf0 print_map()} VerticalPad 0 Wrap NO Bounds {{601.33826, 406.15381}, {63, 18}} Class ShapedGraphic FontInfo Font HelveticaNeue-Medium Size 12 ID 237 Shape Rectangle Style fill Color b 0.880332 g 0.985746 r 0.904703 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs24 \cf0 Maps} Bounds {{54.570164, 438.77698}, {67, 24}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 236 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs20 \cf0 perf_event_\ printer()} VerticalPad 0 Wrap NO Bounds {{302.99918, 402.28421}, {92, 14}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 11 ID 235 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs22 \cf0 Async Summaries} VerticalPad 0 Wrap NO Bounds {{599.61194, 442.52478}, {71.560608, 18}} Class ShapedGraphic FontInfo Font HelveticaNeue-Medium Size 12 ID 234 Shape Rectangle Style fill Color b 0.880332 g 0.985746 r 0.904703 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs24 \cf0 perf buffer} Class LineGraphic ID 231 Points {599.61194, 452.52454} {124.47879, 452.52454} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic ID 230 Points {601.33826, 419.89258} {118.98193, 419.89258} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic ID 229 Points {57.801025, 197.90755} {41.32082, 198.16011} {40.85701, 393.81531} {149.95813, 393.81531} Style stroke HeadArrow 0 TailArrow 0 Width 1.7000000476837158 Bounds {{258.29111, 353.49463}, {235, 12}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 221 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs20 \cf0 bpf_attach_*(), bcc_usdt_enable_probe()} VerticalPad 0 Wrap NO Bounds {{257.2486, 322.3833}, {91, 12}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 220 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs20 \cf0 bpf_load_prog()} VerticalPad 0 Wrap NO Bounds {{185.80527, 301.36035}, {53, 28}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 214 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 bcc\ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \fs18 \cf0 libbpf/libbcc} VerticalPad 0 Wrap NO Bounds {{175.31076, 294.23932}, {74, 87.786255}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 213 Line ID 219 Position 0.15744693577289581 RotationType 0 Shape Rectangle Style fill Color b 0.988235 g 0.968627 r 0.843137 shadow Draws NO Bounds {{350.28506, 327.60806}, {104.45963, 21.979675}} Class ShapedGraphic FontInfo Font HelveticaNeue-Medium Size 13 ID 183 Line ID 219 Offset -0.4654541015625 Position 0.56086909770965576 RotationType 0 Shape Rectangle Style fill Color b 0.918349 g 0.918349 r 0.918349 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 BPF bytecode} Class LineGraphic ID 219 Points {138.07822, 338.13245} {609.55487, 338.13245} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic Head ID 212 ID 218 Points {92.362823, 273.69583} {92.611145, 293.6806} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Tail ID 202 Class LineGraphic ID 217 Points {138.15176, 368.62692} {671.17255, 368.62692} Style stroke HeadArrow 0 HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{48.773739, 294.18057}, {88.77803, 87.786255}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 212 Shape Rectangle Style fill Color b 0.840392 g 0.984419 r 0.994962 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;\f1\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Attached\ Probes \fs28 \ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f1\fs16 \cf0 attached_probes_} Bounds {{183.37308, 191.73579}, {79, 12}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 211 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs20 \cf0 create_maps()} VerticalPad 0 Wrap NO Bounds {{165.10924, 215.05168}, {126.24751, 12}} Class ShapedGraphic FitText Vertical Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 210 Rotation 353 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs20 \cf0 bpftrace_.add_probe()} VerticalPad 0 Bounds {{57.851265, 234.58633}, {68.530945, 38.609528}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 202 Shape Rectangle Style fill Color b 0.840392 g 0.984419 r 0.994962 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;\f1\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Probes\ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f1\fs18 \cf0 probes_} Bounds {{469.24796, 74.576599}, {74, 23}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 200 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs18 \cf0 parse bpftrace\ program into AST} VerticalPad 0 Wrap NO Bounds {{483.05969, 191.73579}, {63, 34}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 199 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs18 \cf0 syntax checks,\ map creation,\ add probes} VerticalPad 0 Wrap NO Bounds {{305.83975, 378.62137}, {97, 12}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 198 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs20 \cf0 bpf_create_map()} VerticalPad 0 Wrap NO Class LineGraphic ID 196 Points {149.95813, 393.77628} {633.72522, 393.77628} Style stroke HeadArrow 0 HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic ID 195 Points {327.44894, 207.44949} {127.90216, 207.71301} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Bounds {{57.851265, 180.0654}, {68.530945, 48}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 194 Shape Rectangle Style fill Color b 0.840392 g 0.984419 r 0.994962 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;\f1\fmodern\fcharset0 Courier;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Maps\ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \fs18 \cf0 maps.*\ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f1 \cf0 maps_} Bounds {{52.157158, 79.419266}, {72, 28}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 193 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 bpftrace\ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \fs18 \cf0 main.*, bpftrace.*} VerticalPad 0 Wrap NO Bounds {{613.19141, 79.419266}, {39, 17}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Medium Size 14 ID 160 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 0 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural \f0\fs26 \cf0 Kernel} VerticalPad 0 Wrap NO Bounds {{211.40228, 76.429428}, {68.530945, 21.979675}} Class ShapedGraphic FontInfo Font HelveticaNeue-Medium Size 14 ID 189 Line ID 190 Position 0.49987009167671204 RotationType 0 Shape Rectangle Style fill Color b 0.918349 g 0.918349 r 0.918349 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 program} Class LineGraphic Head ID 83 ID 190 Points {150.42352, 87.419266} {340.96149, 87.419266} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic ID 188 Points {430.47937, 312.56189} {430.47937, 327.76917} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Class LineGraphic Head ID 181 ID 187 Points {402.69992, 227.24722} {402.69992, 243.26457} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Tail ID 179 Bounds {{377.73087, 110.03512}, {50.13974, 18}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 180 Shape Rectangle Style fill Color b 0.918349 g 0.918349 r 0.918349 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs24 \cf0 AST} Class LineGraphic Head ID 264 ID 92 Points {402.55963, 104.91927} {402.655, 142.07678} Style stroke HeadArrow FilledArrow HeadScale 0.75 LineType 1 TailArrow 0 Width 1.7000000476837158 Tail ID 83 Bounds {{27.851707, 70.806091}, {122.10646, 405.32471}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 186 Shape Rectangle Style fill Color b 0.840392 g 0.984419 r 0.994962 shadow Draws NO Bounds {{589.73248, 275.51532}, {95, 156.49527}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 185 Shape Rectangle Style fill Color b 0.880332 g 0.985746 r 0.904703 shadow Draws NO Bounds {{580.16034, 70.806076}, {113.48351, 405.32474}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 184 Shape Rectangle Style fill Color b 0.880332 g 0.985746 r 0.904703 shadow Draws NO Bounds {{382.19992, 277.76465}, {95, 34}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 182 Shape Rectangle Style fill Color b 0.840392 g 0.984419 r 0.994962 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 IR Builder\ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \fs18 \cf0 ast/irbuilderbpf.*} Bounds {{328.19989, 243.76459}, {149, 34}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 181 Shape Rectangle Style fill Color b 0.840392 g 0.984419 r 0.994962 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Code Generation\ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \fs18 \cf0 ast/codegen_llvm.*} Bounds {{328.19995, 192.74722}, {149, 34}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 179 Shape Rectangle Style fill Color b 0.840392 g 0.984419 r 0.994962 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Semantic Analyzer\ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \fs18 \cf0 ast/semantic_analyser.*} Bounds {{341.46149, 70.419266}, {122.10646, 34}} Class ShapedGraphic FontInfo Font Helvetica-Bold Size 13 ID 83 Shape Rectangle Style fill Color b 0.840392 g 0.984419 r 0.994962 shadow Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Medium;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs26 \cf0 Parser\ \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \fs18 \cf0 driver.*, lexer.l, parser.yy} Bounds {{490.45667, 484.28946}, {218, 15}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color w 0.697622 Font HelveticaNeue Size 12 ID 138 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Align 2 Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue;} {\colortbl;\red255\green255\blue255;\red178\green178\blue178;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qr \f0\fs24 \cf2 https://github.com/iovisor/bpftrace 2018} VerticalPad 0 Wrap NO Class LineGraphic ID 137 Points {720.67969, 51.205944} {0, 51.205944} Style stroke HeadArrow 0 TailArrow 0 Width 0.5 Bounds {{266.16663, 21.18483}, {149, 26}} Class ShapedGraphic FitText YES Flow Resize FontInfo Font HelveticaNeue-Thin Size 21 ID 136 Shape Rectangle Style fill Draws NO shadow Draws NO stroke Draws NO Text Pad 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1404\cocoasubrtf460 \cocoascreenfonts1{\fonttbl\f0\fnil\fcharset0 HelveticaNeue-Thin;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs42 \cf0 bpftrace Internals} VerticalPad 0 Wrap NO GridInfo HPages 1 KeepToScale Layers Lock NO Name Layer 1 Print YES View YES LayoutInfo Animate NO circoMinDist 18 circoSeparation 0.0 layoutEngine dot neatoSeparation 0.0 twopiSeparation 0.0 Orientation 1 PrintOnePage RowAlign 1 RowSpacing 36 SheetTitle internals 2018 UniqueID 54 VPages 1 SmartAlignmentGuidesActive YES SmartDistanceGuidesActive YES UseEntirePage WindowInfo CurrentSheet 1 ExpandedCanvases FitInWindow ListView OutlineWidth 142 RightSidebar ShowRuler Sidebar SidebarWidth 126 VisibleRegion {{-80, 0}, {880.95294, 504.00003}} Zoom 2.1488094329833984 ZoomValues probe types 2018 0.0 1 internals 2018 0.0 1 saveQuickLookFiles YES bpftrace-0.9.4/images/bpftrace_internals_2018.png000066400000000000000000007267131361633214400216270ustar00rootroot00000000000000PNG  IHDRYysRGB pHYsgRiTXtXML:com.adobe.xmp 5 1 2 O@IDATx U??[(Kj%Ֆ$Z/DQJğZ*)QiQ)RI?oΙ{;smg~w9      @9<&ۚv      *@=Ղ)@@@@@ 4 @@@@@H j     I pO      pO` @@@@@'MGC@@@@@RZ0     @ܓ!     S-B@@@@@ iI@@@@@TL!     h     @T @@@@@HZ{t4D@@@@@ U{S     $-@=i:"     *@=Ղ)@@@@@ 4 @@@@@H j     I pO      pO` @@@@@'MGC@@@@@RZ0     @ܓ!     S-B@@@@@ iI@@@@@TL!     h     @T @@@@@HZ{t4D@@@@@ U{S     $-@=i:"     *@=Ղ)@@@@@ 4 @@@@@H j     I pO      pO` @@@@@'MGC@@@@@RZ0     @ܓ!     S-B@@@@@ iI@@@@@TL!     h     @T @@@@@HZ{t4D@@@@@ U{S     $-@=i:"     *@=Ղ)@@@@@ 4 @@@@@H j     I pO      pO` @@@@@'MGC@@@@@RKd @Ȟ{책 ZbO<Ďj6uT[j?VT)kݺub~رc-^ ,h˗߯fgY/g^=  D =Ry@v֭5kرcݱ%s@=,YnV2d;={&)GEa޽^i3ˍ   p`@R)SX=^z6c ۶m϶iӦ1F-k"w?=  !@Jy(@@ (~q٤I裏51l0Ӳo=l93WD}9s@@Gső" @/O @@@< @@(uV+T~#B@@_4A@@@@s!    /K     hrqr  o{뭷l„ wڵkmv'کjW]u]v'g#F5kZZ=6g[d}7vaY…3toР~AЉx tt˗/?tIA-[4J*e7pCP?[N,o޼rM|?{ [k.9r}gf}v;ShѢVD [ / k̪U_~֭[g6m_ύٲeK;#\X='N6l`7o|nݺΧjժamz&O>ecweʔJ*Yǎ㏏zxx_,X}E{Ū/:{{Ŋ?/w/n%K͛_7@@  \t*  Pviv}`ԩvw]β> Hٳ믿v^x+=h>} d/[j׮m+Vtv+WlSLbŊ1LmH?#Ö+@_tC:_/"{׃e=z{G"Er; b?n+Wt ?S6h ?3B .Յ ̝;uGҥKMoB;M Em۶~-]-ٳ{IN[{m>Ejժs^s裏FK)]^ Rz֭mb.Μ{H/ҽFu@Bw=Dv}Mmf[E,~fxh%_Bi=z^Wwa~u\@ntJ*o3 Ve@@}ˣ@@ [ xouH8kʡbN)^P9)^oCOJ߾}cS}?.KzhGݾK)^-NH2*^OFeT-Żΰu]xpݼ6e)}Q9lO>w" #'^)^08ŻSU潠jJӦMݶ )^3fJ񂕮8tU;w4loѢEƍzx=]5jxYc i<ؼ{JrZ)XFǍWJcQ M֭KݬW1cƸT2CX)ZEJ^y啸w奞9sf.U(]MhzJ-JVz&M6mDPϡCfVt"T?/{͌5*jx_gfGV|.H4FDUF}6lˡ߸qηjʼ4( 0/5jcPo RUu@@|p4  ڌW)orp/ZhJ]g)QD?0WUyBr墭ާe+Wt5^R%S~sllVw`$SUj8~iݵA{k_^zn^i(   :4  J*O/EpWc=6}Ih@`%%% l2S\=g۸q&JUT/ Yf/Xԃ;٢ApU]瑕%pjD_g#3^תU˾kի1{=UV .c"S@@ ~pg:zʥfzwX g.F||6mNۦ  @ 3G@\!d7l`߳gϾl޵ݵkWN _z%k֬}nTVʕO>9Mx≮͛M3hIKfn;m+Ye8׃ڵ?Νksqz깁O?DwA}@@Ta# ɞܺukpSٗ^~.}Vf}]])0tBܕ:`ϫmc/3s9m[*+^zP?ߺw4ޗ̶Ů՛]Mk0OBV}C??yfzmzgAie:o\v4j(3w@@lph  .}v&0*w^V kdD'nxb_7kO=t#mנo|~7MO>k /i*^,YbD_JӱcGkٲ-ZȔv  3D@ G ><* 4 3ZHU8㌌Qf͚ Wix.\r_x6yddRp(|蠵E ԩS]l[iCsGbU^[סO>.MLzbo>XUX  r$q*  *0n8۱cG8Ku.ʎtȑV\)o_ZWnxV*}Ff Rp79j(cݻ 'y2eŊl2ꪫš+@.d.ܹӥH}vk5[u빮\~6sXU]?~~c%  @ 3G@\!дiS۵k%ǝQ߾}ӵ:3]j?n刕>ݯ_5yn`[FoQBB)=z[*UlȐ!Id wɔkIT ,Rzh@Dz<؎8C;Dnqזgu͙3tq(&rJM  p p?4@*P|ykӦK2vظ>SxS PÆ ]t=ϡҝV/#F @k9餓\M6\+7|3/V~}E.ȵ5xzoٲ$Ox ⪔g}vc;cG.ЩA_c?MCoA֭mҥ6tЄ,DnqXkܸqBfʔ)n܄:u<@@rdr* OET^Ψh0Xc_@ꡭ.hGZ]̌0f8qbܸqͮAd7O|m5jdO= WZ5m[nG9ӻ+sBr_r% m۶Y۶mmҤIu&'x^9*_DZ^']v颒.(f͚XUX z~衇xJ&@@rrb L7hzrW^yJIIq'o]z.LO sϵӧժUs=-ZlU4 CW0T~q#J unʕ={ @4-&Lp͚5+>Cwٟ}uԓXm͛gyTi޼?ꅅۻ 8\[w",^ؔF=ߕCMtR=M:( ,fͲs9y(?.l޼ΝkJStiw|-[dʻf_&rxzuqns7zB@EGߕ.UT=}BwgP@@r dko@MES0a9mڴ/z/ݴ)R$ x~6m^//Py1Ǥva|ѢES o4˼|)^o4ˣ-Qx/NyA&MUuDz~ftwߝ qL2)^/z/`Bwd| 7"')mj}\p) ,lLRǪm>zxH2V`W_iCq ܶg̘*l>ׄ 3=D?fh>z \yacSz݁p '83=/w\ZoժU3~rwwn;9jc=[n5ҏR}Ѧڵ?p=8@@@vr!y'dFe@@@@@;)eҸ@@@@@'nF @@@@@pOC@@@@@ -@@@@@H#@= @@@@@H\{f@@@@@@ 4$,@@@@@@ q@@@@@4ox4+X      ?^YfY>},%%%V@@@@@@0/~C@;0f@@@@@HH`! Q@@@@@ܣ@@@@@'Ee@@@@@ pR@@@@@ @@@@@.@= K@@@@@HH{B\TF@@@@@ .,E@@@@@ ! qQ@@@@@ܣ@@@@@'Ee@@@@@ pR@@@@@ @@@@@.@= K@@@@@HH{B\TF@@@@@ .,E@@@@@ ! qQ@@@@@ܣ@@@@@'Ee@@@@@ pR@@@@@ @@@@@.@= K@@@@@HH{B\TF@@@@@ .,E@@@@@ ! qQ@@@@@ܣ@@@@@'Ee@@@@@ pR@@@@@ @@@@@.@= K@@@@@HH{B\TF@@@@@ .,E@@@@@ ! qQ@@@@@ܣ@@@@@'Ee@@@@@ pR@@@@@8,TF@@  ]vڕϐSCXv-[6j# q pJ  9]`ȑ֮]~#бcG6lX:5X /۷o?3}"@8#cQ d%K܆>(st [El)ǟ؎;m8(OO|NB r!VPr8v&>tPڥdBA?9Ən馃p9F@l гG{/=ݳS! ^]{}?v/O]O۲ERq6y~v4uΜ9;D ?6w    @ (C'x0Y&gٶԆsMwa[v7<"@@sj   @aUa˘A'V2rMLb           /@=~+j"     S{LV           @L1iX     @㷢&     1 ۶m޽{ۖ-[3o<(P@ G@@8DO kf#F2!pgۭc-@@@H+@= K@r?Ýgi͛7?φǏŋ:g   @2(Ur-b5gv!@fX}m*X0U  Z~?  DiY:[խnÇW*! ]tVzxK/̛7m߾V\i<˗~};Z# !   a?&U>raAO {ΝO?dF &|'# EQPX  Y+p#NwڂOޞÏvl·CIS ppYhBgQ" =36  dnh=7F3#{\d=mF0Z`ְaC0`KrUWY"Esα;ölْ?kԨ,Y ,h+V޽{ێ;vM'2eXZ?|ARVX1~5Hd9s5mJ(ᎩsΦm[o|v)ؕW^iJXD :vhݻwww[^r厗̘12ľ;+ۻ*z~mkҤz.U_n:xW`CL j@@Ȟ>l E=ݻoݔ7?p)hfZ`7oZ7VW^f;3MWۻͲ/a|^.MX ]hoN0R ^ ~g|MN:s<[{]ѭC8q tʔ){nP*^zip~n߾}[n.*7{VZe +(>uTW^XxnÆ ;Ͼ`_MBkGy[vڽ4K믿e˖>+Vؒ%K,W] *>.B4WP^v6mڴiRW2 [{~9{@@@2%c[nC0M6mE5MM7w:oVs}7EhOC{}sovcV:v ]Ǎ*TʟQ\qܢ]rY +]]p56}Ɯn䄂5k_ky TZƷۉ'kNgOx!/Yfpl J*H}խ͛Ev,,'1&^zɔ椓N  kM9}zq/ *7_`ug[S|/Ivg^s=3Eop{oLy뭻2kԨb*|R~ >Kk_| cz;*5M]:Fgkڤ(f^M)oF>3=tW '|]X#Qy^R%_}W6bBE-Z]0ڛ.&l5\Hۻ뮻졇xhʕ5k_o (Eҧ(zhQ ^eʕi9/Egz+zh M"ʍ_Z&zil[?b% 石G@@(xa2n-[m;3m :nyˀY67VBܘ̳efP]W^~׫V?,S~[HvjɢVf. EYgUpuz95won-Qլqq%SO`whA'tl6Ǝ=:r5D@td9c\]*L6mlvQGY]saY4 jdQ: ^J?vS' U -~:_|NkpȢsR U^%"_V+/nu G{y9S@@ՕϹSkժoozFU=J!ZWuN%NGZNx=矧;ڵ.|rg[F}@)c<7 @@b&9}l^C^|QG!ݥx;^Ǭ7jnpV :@K\2KS,Vd]y# OaUD/`SJخP {)xiRD/ZMRU40j^ `y7= tjeqGWyW/kAX?K)xV,lԩկnyDpp'/rZ iKl_#^oޗ   -zGy?e޺%âe9k='^@{ .mG2/UXXdR5e +kpUUv[fKɘ۶Y|HugQ[:Ν `z-)ǻ_]qVvmS|5ʊ/GN(Ҽ^oذz^i֬T>s=7nl[n7x` ۷rp nPVs_6i$z<a "f G :7zˣjժִiS7tsrK=J@r@OY$ݽq=v0up&/(-n}@LЮm3{jxwLʕܟ*+eɭ  @Pg'ݟq?`h6GzS2Û4m5:&zM{Y~=}F}hsfE? ^.y QQh%cVeF@HgϞ6ydwE^ zN+حAG.ݺus\8q5oՏKAwyj4:{]3OnJU3a„ p>}D|eJQAZRۭWHc׍ :tV?O]ĐGN?PRt"ܢ/o1ͻ8;3ά`˖yX佷>o+/1Yg۶_PFU;p V`iGTVSW.H;Pd@@ g( 'r 3Δ? ~&/ 'ͯGչZ僟5{M|q:y[l*{6p@+[65Ek wuu衇$ӌ6@nsrQ`OO3j֭]oe˖ywroFRJf͚gPlY JgzG`>k#Goֽ~3*ʏ^bwwfGbx jԨNҾUZh~3^O{9wCyuTRir[cdIC.!_̀RS5kf88F<+:W s8yD'*F"{ٮDOi/}p/g'  fΜz+*~M7Y˖-ΕQsx!2~QnׄuHwgg]F[gIϝG65o\SMW.w7|gm9UV1qp (x,=R=V nw+FW]t=jlX4_oo[׀_Rq _>hN0 $ p(۩y Og܋X`4z~Arj WW\vr׉Zu?ϹJ~/A{=tng˦,] mpؽywn]Ӥ~cv U)ֹ-֥s{WۃP@gۭ7DX> > Tm sպ=3bF 8`uukzs̱^zym$_@a:i$\ 5@W.]`ik w7haÆ v膴]-b~{5[۷oow}wحV{|.lݻw~mF9P6luTMRhQk۶k>haϪ9cVxU /Ν;ַo_ד9X Cʙh"+WT7iw~,ݱcn̟Rek٤ksAd~vG\)o~`V͛njUvuygOd%oMs>S@/|yp0O,P7aӦ%`?/[E7U{wӜ{^O6Q^9G<3!mD.wߛeM33,C=g]\+h߭aO{ɵ Vذ'ҭͤ?ol{vh72흏9*UI)_| 4(T=ϟNa YyNli6Az-[x⋮wJԯz7o^UV.f`۶m#oǏw#s=n #j~nh5(h߻wo"ֿt1@$(X ^}nZN;vtwwyigƂw} O!Lta}[L줓A^爅 iRoo}OMٮý9ڪmj@. oɦ:5cv}05|mJgUsϯ/ {\|ui>0O2[[v-3@f pL-ۏ>({pV멣/0gzs?z }֞7c@Rl g ԮUͺubTizޛnzA KT wzȽ*5s=.F`Ս.q=U=Wwum/_^OjwǽA]zkk`S~`O`y%{ خ|T2Jw Nm^ v';|$םYQlW:jԨa={l0`֭[zGQ~m/wn]j\y(H*ڇzתUO>qVpNqڴiavꫭp.-.>|G{ c㏛`.i^Tzݝw!vg+9v{LԦkV~u9 @L}Ժf l4k߈(3.:MxmU}folxx4zxUsdG+o9g^|'"Bgt:X=fKtb'xyzG}9/gg6~+vXĐn$/mpRїO mcQ'OxH$&eP8@hK2eLK.{zW^W lWzܸqVv`. uQ*UR{l(rk Ϝ9_T/ tƋ/uQǩ`1ޟ"EبQr+.ީ⋃6C k&X~] ~̘1zWePHT VʙDC/Uʗ/mע/I(;@ `{=$tLu/OPze+aogGɓ^MF• `iN{ڔwM=Z[(=?zӽwɕ&jƾhJwQC\z'JQӮyo̕,{ᵽosgϳ{ p=5wߠ~S4C3mKl( \䲰`*[[]g/ȌmDn3yln#avmUi<Cs`_wJn 2=hWF /ҥy=9ΥPw_G婧_ںKd_8rr} jڬY3&LH};5m!.9tt͸`{^/{+L7gRӿW80֫ߝ^P1?֦mAݥK 5.خac>mJ?SDQ'o_}9 dE\2wsl-' \ޠnӹẫlV~^fl#XGudz=mܸV|ƾv׃#p'ii婽*?utWFY$$s|)RK11Hscat@IP JPh *^YQb־ Vo?fB~-d+eJS.v8Kh .S&zʂ@>QuQ ˹ծ1(zgTʖ-6lpB~ezgUQ| PKɞ~JjϬDR#-ltׇk _   ~ƧR޽v,;iG?96;"U|xNid׸{l|P߉օ֍u]t J6Zb 4Y);"Sd6 @|W\qUTɍ_Z?KݧY2RlW@OƱcF +>x`P ]tKzG8UGw'{n7~L?$+VL3@V خ͚>۔=j֩r߃:E&";B"b@g=pG)y얖i]c* 4VɌm.W/63vCv≩tDu+ܧ5> Iۥ Zo6~Ճ_èpõi~scSj xSM7@b/饅>5cZBWZ,KIӗE֣mw~iꭣÆ NHq6J֘v&@ʘ}5  .;g}9rM>!7z諨7|93R[q _[o(Zh_gZzME? QKo;5QVTm;Sdc+U6NZ/㟌Udy,fƑem`ͮg6ELڱ㷈n߾3׮ nIZd-Z>⵿nlOڈg&MN ~xducf z2871bE7W0Pϥ3f9 ieۏ4w7`m#Rw_4䯿©ʝlQ:~S–,Y.h@ (`Tg϶VZ=aE @"ݰaCSŋUW][Ĕ[lnߴi3L {.B;餓Zjɶn (P.B}۶m]=~oߛAS4?dw^ ,hᄏw0>5jdUݻwo M;o9s]Evcӣv]yNs̑ :wM*>h{:ǻ+Ѿ}{S u~;vt?خZ7zܣ/ۜwet1ƍwf{s5h@,G޿r_dǏヤ=[2&RkqMԳꫯÇcƌq/{W{?>}S0@A[EyT7zT^l87olVr="/ i@@_N712exB .?h Kݹٵ C3g jn<E+3gtǦsWUw鳍\l26]yu1_Tt}/\괠J-slܸKQǠYp  'T^Ht<~.w P? tv]V!~rM`^6]Eӏ s#[k⸗kA T7*[fB~.Qsw|:F.0ifږPh믿~[WyJ׷6qilyqXկGv L9)w>>wBq3;2kBb'u }֪]8C^ϋn^y[JegUF{ &10h+uwةe. R<п5:oFA"RzS}ɮZuu@[ImM_NΠZʕݗH;^|E_*2`?1U\_>\A6{)Go#"1ȭ[npi_G[@ +Vqꁮ֭swDnSu!|宷yϞ=t5lmu[\=@zk.Wj\E q.^z.٣GS^4Wy)(Э:h sѣg Ew-WQ*d걮xY@套^ o[v'ƍ-E }wꡮN禀.{mzoFWMJκZtW+dW@^dwm4Y@On}cζes6 ^fԪNcvI6]ۃ}}=N}׶ſ[۞6ivSÛm7f[z{߻#@=< iﰁzXSJ%dCynWy_y)7djZ F߷׻𠊶H6|h?Q4*U:f|}1D-S}MkfX4ۨPn}J]TءEԶgY&2 d/РPAꥦ/t~DPBחdo@/=:H^굦[;ty5u{Gp[hzX#xu Y%:s](ۮ]€MkVpK~6j(w^uw3<}SLzE0b <+M]mS9_~A3(Sv*Zs_UY)dTtW[?.:$SNG*ڦ6O 6IWݺX᧝yꩧQf֬Y>3RרhТE-&Ol_KiH:m3tVEѧ[[vъF*Ou3ݮ1z4aHy1[ܹH ;k֬e]:' B99A BC si!8ufshhAD3glpvo}_7 Y0DK߃3%" ^~<|U|鯛zD#A7yCAZnX|$~'G"Щg{ٱe@snLLiJ15eɖYswٮ/\tkS._үOc ߷6ܓo [b>", ]31HX˪|Wƶ"M},W[Hf˰PlQ[scQͷSPo۲> @]6mǣ.DxwkCn|*2,>G=H>{TI]w->p$m1z3f.Ur~u;|ybN_P흝3nZ'k zىGjϣGumBV yH n+/="e0cOޱ4s/0V= r4 vFXA}3y 9 , @3ڵ9  %/}{y>a ?XI\:I"iNi$S ר`_zUy=;x=z,S$Hh>IOp66"  #{|;x~.XBR4&**5iD ERUs&K(H^lC)]Z9 :EN<'q$X7*t[X,KTjon9  [uϞ=O ^~v,#Jܹsx) F lٲ\;Ҳy=M:޵ 9?*jγԅ$Wjޏ{hYc{1\:=x&~)Џ<7 oUR= ̱mZ<v)j*VOd[c=7Da9]r"R?zHV<&L_\wy!ը0@,]gÆOU/|S 8={* aÆƍS#0`Hi!N:U%@S\r-4,c6vXlG/^+}r2Q'.~V׳dI/[=O{$6QcfT/o1ޤq-͡MN! ;U2x#}4_M9ܞrE[|8y3@,7o#\KTT֫ wQT]8бp?T=[Ze붽Zn4M;vWٶ}l8Wʖ)"qAemhQ^wQ x}ZXg?LXh5_]o.?~Qc~W{lkw#QQkqp>|X|ǎjC#|R|yh#*$Oϣ]޽ m0]oٲEa QЇG=t5jdyw04i;$[0f4" '8bd= ͫ7 X1cƨ<5߿zpӦ.$H mڴQZF HHB4:C HBk 0?(_P9F5jp,QP9%c&Flrزu&P6m#r: '=HTњcr,P}N}*go EKԓHeA]h>cm\ id/6̑_pw>\%bbXdi* |vs_hdYAsxP֨g{$ Ba=w^/\S,^8^9_x%*6ќ3`p:\1<E>SIAk$]:'U>cpwrp˱H3vl[Ns殔K B]&1o,_rUqR@[8xZDtZLE,{^[*ɗ/ޭ[lԐeAzLk@ɓGi#j/Ρ'I5xrZJk۶mS8q(mvɌ͖I+Wfy4i]%V[l"|2]S]X%nmGVoDG͸ :#FPQxz^9操9RMO 9J/ Z5<5ru,@a;ݏ=iZ,4 >hC%A dPGuD "  Z!A;ޝHHHHB<=Ǎ#"mzVUjXG:;]\ќf6Qn?ˡ -rݾ}8oߌc ʹ#ߑhQ=bxzNKr(;w?gHnLTF .vmi<2jx`Ji4#X@qjڸ jr#zJ_ lvH+5:$+m۶ԫW/FUdIIN'ÇW*I$ F$@$@$@^`lxHHHH@jM) RP?C^c%|HΝ$W}iXZMo>d $4m0a~0V,'cN& nO>?iNhI@l$ڄaS Gu-R:О :!i*"3;MOWes3ihIB37y~">ׇ f ᙿ/+7 5tL9$ Gr6H_?G$@$@$3:}f$@$@]f0>D> !dddƍ;2n98u>r,׏;|oUft~DwTh٪wоLhc.ܿHN_NТnʖ)"ﳗkR# ّWoIA>w(-E$pkec6buwg":W 5 IȢÇ* I\Hy޾}+Wz 9m;' V *un'M3g/I5Ӳm>5^i}NL8A=ܘRҨ}PR%2;uhfݥqc~Whݷi'N׸=YRx @"@{z\?XPBDYIT+=jP^Rď_~n߾'suByd"M[TC]|Mf(c8HHH 4@Tv@HAWMK̐,!;[6;پyW˔I}5$8;%J =D|{76{?83gizX`[ Hjm;-DX'nc   pDaqPEjZrHKTd=()^WVޢc_\#I4mMl]j5~Ti K99{9oJPW>zW΃SJ LP %U?fԳU ^.:#fά>j |Y1K)I'\^c]*ZJ%WkٳF;۷i5m}F/^VQ$cS0qP\=xN5kE$S!Xaկ}Jm%eBjx>wruZ~kԢ4]i$@$@$@$ܞ)y}[?ˌiCm:z8'H]7hGx`ϟ?ku2Jڵ $ @p$`Ԑ2,WA'x= #ZݮU(hv DR%u5Z]6Qɉ>btٻ# #'^p h}~AT X!MwMUXޥ0+Y" T!æb9oܸkv|[2Eu[2q<*"0ӧ/-{?H…F7udDQyDtMDErFC&UZҬ'OYH('  V 6'^Cnܸ#=zsGg֪u-lAA4 'D#"4ـ.ζiP$N`W\)Uʤ1c+J\@IDATԪY^~kUtɞ'OV9Kz<"O @0 `"Ь ]!{ܕd~eܼ_G]*ti!{W*K8^~g^\ıQUk<썛YF &kVNW7o+]a!ܷJΝަ抄c#gUno+Gɱ#{3߀^6 uL.a}u5+w- %{9_xM t7^-Zl{w˄qT?ܼyW%BӓmԶMrAݫ`ҤI)/Vڢ1^#   '(B(AmwkIpm9QRK^̓IHHH^tK!կ-V#CtwRbP"BJ81cd`Nƹah=>x'sB$Ii[|1UF&,AxF"̙ WC"s5[8"q;vL>e|Fy]}R8u1+˗%&SF9#lޣ/-BMr+E܇  2vA3ޕHHHH g nnϔ>!fN @8uꔴhB 7;s899IznݺvGb &HV? S  #J^:3 `p7M :sfa#ak %I7;$f͚%?~%ݻwג2gQ޲e  8C %pS#   &@{fAO+ _ܾ}[5kڴ@F$@$X;C 9#    "@I" 8/J*U$~*m۶3vQV*׮]Ai9rKҤIr*^êމ'ԩٳgKٳgӧc߳g*O8Q_.*U8qU]vIj$UT꼳G޼y#f͚J+V;v[ϯ+V藽=zTuȐ!r9 o[˖-UǏ tҦM+E5$ҧO/)ST\nq\7o.]Z'N,%K͛7U3gTU}bK0`"J,Y2z}7N:$ŊS"EȪUT5˔)#ɓ',;H _ 0ݯ؎HH\P! 6|9?^g}FfdA?~dʔILW~ⅺ;ܹYvQqFeԨQHG}Æ N,dΜYqw^^+g2".PXnݪS*8<.\  ۶m  ƎwiD+W wD->=zP 9rdٰaڴi#ӦM3ߺukS<͛7F5kV9y$IDսw,>}zٲe/_(g976m$˗.]:UGQv̙5„ # ,P6 p=Ϛ3%   &&IHe˖8;&ߗ0pwU0ȭ[Tt6!JÇWX4NqAg;ب_ɥKT;߯yEСb˗U=U+WTs0?8pGE<É?~xl5N[,ѣGWz<ǡCʷod]aeҤI0O,|U’%K < /4 Ms$@$@$@$=pzlK$@$PuTر˗ VD %âkԨTPA9l!y=/d] s8T G!?H&M ݮ];իnGdW KԨQKp/999zh=npyhqD#,kժeq۫ 5nX (!Jݖb4a#j_U#F4_<  @"`N{z- 'J'ME$@$dŋ'9s洸? D0CD7D["Q 1hO]*UJEXϘ1Cɸ ̙3[zxAKlpC~F7h1'f ƍ6˾f}/Dpc yk˛7c}K,*$H@I,[LEx34G;,G~-RHHGN{=g) ݿ?  !- "p5ikm k]ϧc$4ZR&p" Sٍ(pDuzg1Z;PFtO}r6Y?ׁN:L7E$-@ HX>ׯe̘1@%O xHn$n`i6aY|*ؖt׮];΁'   D8V  / *IG"Hi68 !c6=ۺ=eݱׅzbRZ֭cǎzQ@FShmhCǏJ`HӧOb=oY>}zǎ96hw \ZS?ݷeC(g;v=~xH `]"s%{łb+=6`    &@w#d$@$@@Z=zPF$֭[m-+sh#i&ІR/5kqJ 8B[ .(ubpB:HBI}qɗ/HS}ƍ\HZjUAW8!Ν;:u2ٗ={ͺjժIrdJNuzjŎ:fpu`~=)X@˓'ǂɓ'"o>hA#<H]P!doׄYHHH C   !Ppa%5i&۷rnjS,X`5zHNZF"i${KK.J*Ç*^"!wt$e֭ y*A맚!p̻J&MΝ;*(אX;wlܦrѝ,Y25+g{"ETg޽.n۰az `'M 'zƍ]_ @[<3[l L4fN3~x)ZҾ1bZ0=uLӧ MyHHHHHH#/;' hիWsP%O-Z4ɔ)v}<%RNi8߿AompD۲,Y(kwwwr̶E*ʕ+Jw=M4!BUr0'k֬c?r) g;!sm%M=&N\2$h%H}D{xeߨQk׮)-3zrci޼ҡ7`GXBh͎>zHEqkhc6|vHHHHHHHp+9# pHpV[k{{7P8"Glw(QF2d0e8gn{Ws_ |H|!:߫ӧO 41SN =+m6%iηoŏ_@{`=      Wt+NvF$@$@$H "7o1ܧO%5U{'      G'@?!HH_ '!9 o۶$I$0 Vkתd23HJ#      L8v  _իۄHZ:u@Zpg$@$@$@$@$@$@$pGHHHHH @k$vss$aFn,HHHHH-ҷXHHHHH̝;WfΜiiժU*U*ث9O7HHHHHf͚0@,Y"mϟ/...6?Yf2vX"ٷc6a}    `H`8d     ǎׯ[Xb[ٖJ׮]K*UKȑȑ#~z>o<ܹu8iҤ;- ?VHmm |tg @%tRcnEUG eʔ1 +WWԩ߸\jUU3f1BG%xb^q͛W^?_HHHH  ߿u֩d̘qY QU\Y/Z;w"q͛7~m$@$@$@$@#Cc$IHHHH ذaUȑC%K}l߾]={&q4q .ݻW.\8PRĈHHHH<`'$}Us뷒HY$W.gɓgy-DI~WE„nuٷ_ܸ%O9TBhZMe-w%Y}ؘHHHӧOeǎÒ%KVm  SVZI߾}m-&M.]dO f,7Rhms˗]bNJ)[G1xpI^v}q9{9g˚Q.hqw1s:WH>YjT\;c[tܽuP} -[?I%e٪-~iϓ;KdKa2cu.\pgĈM-$$KHʗ+~oXcHHHBUV߯J.-p2(4jH~yNZ%T}jx,eʔesU$ $@$@$@$@$`Ew+ < S/[}+Hj4p$5zd Y ʑŋWZyNƞ1C_:t`Oh=~*poq@>|MJ޽(20~СfM먨+7I=C|}(g{2EdԈ9s:y̞B{!yyl, @H tRc͛7%JօbŊ ah׿ɝ;Q ˖-_~-z{    ^$K*Nm#D #u$my8ć; $gɬÕܨE}B[RvEY`㏪vF5-Z-H"Jm I$OliCL<*>y7MAΝ"O5- ;h9<   $pIrBرuRڵ +O>JE2gOeΝRNI" qㆺ~1U'J(*:    #PV=gTȑYƉ%V:+0dkDi_dM_q/ivs_tHݔ.U|fzr@_:4_wo XDW^w.3 *HH?r)SF./_C7)$-CpӥKg2ϑ :-p (^@o] &Γ3g/'Q7`Έ1ƌ_c`ehYK:ج|58Se8/]pcC$;2[2@ @$.k׮5&_V-U!jԨR|yŋw~zYf7Nnmp׬YSڴifc    IwXx2 \vK-~\pUjn-#Fΐ>jR.( V(Q"[\󏃰a(n߾g}JٳUUO9y7KHFvMs9sI̷\uÌ:s$@$@$@$DYKl4 ǘWݺuΝ;r=}%KI%ane9M4Z^^^    C L*g^Ly1̘1KBy9ݷoBTM_zP޿HP>IrJ?y(3~/Y~6 ʸ s觍-+oe9%z6ïcySRhm;8g/'MwiZcَ0ݯOHHHr$O\)"H 7ے <py4XАɚ Qrj:+cٴU~Ҝq42 ˠ!A.kz*}ckAyJ^ xD˯\Y>~_4i|Lhꅍc=,5GYdHHHHHHHHH dyc3رc t2Ç/Zt7_*KɌiC-e˧H΃CAtĈ\٢ҴI-vEuuvp<67sȲ۹ʡ߬im߷4n"FϔD KvU_#)~XbHNͥ_}kwhWV1/bc%8qbŕ$iD{ 7t'B;y>J8[M&#LԦڌHAI$Pu/ܯOU~ɢ ܿ"qְAu}ɓMN(_Wo)yIb?i\6nmql"9r$Vq7*ixDߔg^XOŋcǎFH"I,Y O*ZF', ܹ#ׯ7Yp Ǐwp$@$@$@$6dʔV? YidΒ={KgrO ele St!Wc6$օλGD~Z .i?^ߧIF1f_VXUyL$@$@?_z|A훚G„ "|68ۻv9 @ @{xL$ pzgW 1M&M^ ?֜ؾw{E#BJ^ס. 2YZj%:t"/[l7nܐׯ_{՝۷o [Z'ٓ3>qԉ   H)H`]_8T̞=;$>2ΉHHHH = %DSZE!3G?dȐAmիWmםϞ=e߾}j3׉9M)SR' e  p=uz]IHHHHLw3 ISrc @Ս'O.ʖ-kqΝ䌿]4_1t7Ҡ (QL$@$@$@-oE$@$@$@$(C@ذar1bЅ <?*w=^Cn>t/\6s]J'>f̘, 0F0`vO$@$@$@!!rj$@O bĈ+W.{GR֫WzN7oUm֭[j۲eEqzoQ$@$@$@~&w?cC    :M0X$ @RX]&ƺwzr#2ݫ6s}HdɒŸ~CF$@$@$7p7"   G `)RlʕӫWl?x.˱cf8|^#+HHB3̙3͛78u/_ ,ϗ ku\ԩL0 XݚIH @H"j3:y=t?lj fW坜wj3۷o^Ŀ}\fzöyf:ų(Q"z<   NI&2h #d˖Mgnou#   p&~† +2eRܹ)"/^jӧg+DKxF$@$@dފ/y>̓>u    Ý ([-f;etY}x&E#wѣj3KH"L$@$@GiӦr#yn  GD$@F vRhQ;თ:q62ϝ;6yħNڦN|UY&  #PF i۶yƮ1 yJ$@$@$@$@s$@ɣ63_zhw Q7nPۦM,'HK,j3w^Ŀz\ݻ-ĈC[K@'4  I K۷vXxHHHHt{HH? NNNjXō={NEE/^~-VJlFC'h$@$@!del9ܱ ۠A3$   Wt+ΐ1e+CMͻAҤN);98 #7n\)^=A… iӧOruDol={&s羜8yN† +ٳeTy[| pم#GNCZԭ]Ieh|-Z[u.|ps*1d/_v˧OTɂ=zTYb\tMR;j߯} ՘ҥK%-q%Jd{h;wT-o؆9H_*1v*WvE-HvҞyA5-YC[<;7g%z''O]Ijת ?qu;G9r$ɐ>&eGcpGwkecHw SD [{Φ#:߾}3WY&i+^#F$@$Ѹq9F$@$@$@$bXzB̴BD._!K9k(/1cFVZLQ2iBM?M~c4)Uh|JU[/Y^:hD?}\讜 ϑ#~h>!-[R !rUy@)ʙ[$v옪 լFn߹'=Q眳[g˚Q.h#Ks&la-9Td'nXw>{Ϫ>޹{_Z!g|fu1-\tMgօJG@\zQv8ٵLIdtNR`n\F 4DUsǶ`WEknA$4H`\ >|nnn:j3ׁpƌ=mЉ7KR۰L$@$@f4y/ Iw;A9j[8qvsip!l$ ~?֝/j(JW}LH}`찗/lml'>jj :f7TXB=l>Sr3xZD|r$Iɜ)h #8qbVdI}֦Nݻw-d, as)NM֓#>]tZh, hVJ$@$@$@$@$]p.|A8Q"zH CrQ[SvH5vk .@j!8cp/WthD#zдbNJm?^Eg~h2vl4;>cIҤs, p~ϟ_mC'ۍ7X}2Cgۺu,'Oܓ#QqƵ޿/u7 Jxm4   t=h{e8u:6mJ !eQࠚR yX 8}'N,iQIm}~#…k2dpcuowy2kr<ц MrٲeSy&W:^&-vaZKlKxH 4ؾ}TRC>k i$@@߾}eȐ!kҜ- _q~g_Q؝䤺!h_V)v}JϚ51VDw/L5J,}SX~L֋߽ٽt2Xji##u'dZMq6Ls=[Ց9FJu5,TMJ֯/^h Io(A*[*U,&OE=^x!u"Gl3":mdn2 7G==4H#   !@sɒ%F;Sײany䙴oXi~c^hCJw 2eJ#K6Q@e'ONܨ(GI5я{uF\@IDATkCͧ.[/=Q2YH !A.kzj|ПߵZ6m#+P})a.58u7[]Celګ v3 閭{IRp^9~l۾O={护G:h;"]ko;YMΜH>|8^%ATڵm$Wݒ3MK/%NwqO>5I)圶2zZJ%՞HB/$IRJY@x'g訉MYߩΝ{*ЌimnTnѼ` LK"?~t$;ι|O4ּs[]Ζ-` kYq̩YH{}=ď[ 6 Bt2eʤk}DuDgx)y,k֬{ڴi}IċOo;$@$@$@$@$@$@ rP08}رӲ{!q$cf` @`,)R\r6zp߾}ۦ{֭[b6lX_NԩSIom      K 8{ب^>q\]9HϞ=<$ <^kC2۲t: >t4H" Dc)^61Ç_|%gϞɾ}btᤧ @P!@{P$sVJ;/v: X`H<ÌuIl DI%}Ϋ둗/_:?wK:?r XL :%*IHHHHHH`ؓ ckc8,5q+WnHm TA:e?Ytp(!3 lij[]mZ?Ki*p + +:^o:?Vu /e6uGoGd>HHHHHHE6o 94i˭{WbGَk vu &*T(I2^ʗ/oSʕ+ܹcSmٲE/:…7tEVV&     p,g$@$@$@L ~D6gzCx8v>}*{Ջ8^z"I,ea„V6 8%@S4<@$@$@$DY˧^x0":V 6k6d?k9$%JdㄇC>UTBx+)n =   [:^3gr{{{˓'OUnC'… zYjM1cr7'9wHHHHHH:܃G J:FRBf/_?yܽ{צ[n OOO*ÇwO$`<4>}&+6ʆ;ڵKI8HXr" r: K1 ),]+o޼uz9_~J'KWX{'    `G`q$@$@$@A Adɒ6ݿߩN<"3DٳG/ֺ_}SxDH 0, wtmۻ#`6uӥٿ.X6I]WVP~G5橿D˩S'][Hʥ $@$@$@$@:?9^ѣsH+d/#seY3Fĉ3}n)cϭL$@$2 @=z^FC';k/©N<4i$>; g'|R؏2cRFy볂o eB6{֔{Z!    ~)H0>zVyqV  ܸq[.^^y5(kެ[fe  ϋ@0a$k֬z^9nt>?Jeʕ֮%VXiĉcS;$`ON2o H 7$ Q/r>JH¹m0}\U<,7c%Ŋ敊J G3]^F)͚Wp ٱs-^S\kI&ב>]J_+رu\zSf(D8 IGV"v[ 3{Y:ݾyrݔ͛7ԉ!CG|ĉoCٶmAj2~6)R$e{CH *M UCEڳdYV}fLIHHH>/t^zJo}ǵ=b(U njv;4:T~ 4ȥ%LP/JF~q_5W^u%Ǐez믿v7Xr;WW2} z['i֢>H7n,~W.^۷Ia]I>sTb<{\%%M~ݧtcŌ.3~ .iU߆ %?sZn޺#؎;ILGj_/eCr uoСrCE$^ؚ3Z/]&32eJcr,Νpw YpHHH>?tqoYdzCKĤJTbĈ__:6vzuDt>}^=zׯUT:cKN{hѢ(jm>>cX i[PB 41=F$@$<DU (눟={ I-7N<=zT/~D{>:UT)R$kUns+6 gc/Wju:m̝!ÔNXZ7a¸ҺOҦu}kLH~R~vmJUpknieޒ;w̺1Ji߱~A`W޽Wߦ0%J'O<]%I$0n){^+}%b=\%yfl$N_z~ZV"Mw)#+mլ$}"F /?ի"vo ME=UXq† # ?{`tn&i"vӿ8w4p~&  , ?!C'H.%fh:i荛ei-XXf70fo<ѣ'?~l?oHn޲Pdߗ.l4a\$`lDXwS*Qi3wUrV9q`5mڼK'zf0iWZGL][ ZH{p mGhImuh^GV^Gc{XG#0% 1,N*8%+=tGYG#%^XrLR@}g;})zBTB[DCgwSIuيwߑz,6Yj& l:1c$E4}b5٭zƪX/]N\|Igz\ F`#R;.18fo.6QO%|c1sڻW[0 "Xnݺ|pS^dA=u܆N&nEiet7:?r=thǿvt6X4|Xiq*c&:Zn/yf3Й?`Y*}X^FyWE F]#j=Ի$KDݷhVG;9i:Q ׉ڸn\JϯhqchKDVGc^FHH=(Q"|w6ܹ#'N匿vK/=z$^^^zv -)Rrƣ̑v-S%Xzuqd֌6ݔKf=m"3+;r p@Gq2,Z$NfjoR'mK*j}Ĩ)Z78ZiB6I~5IT3=6'Odsߡӻ4PU9t⥫! M;f5u.D$f7oϓ'bܣWpyctq >'wEt%Enc A3h@LP\Jr;`?  i$@$@G zJN^>}ԗЉn|R9 Zst!O!BЖ;^GpFaQ"G46|7O6Yj7 .0aFa:شI-ƌD~U3g.}8;gm&͍aO,YYwׁ)ʡn^؛+|MK=EfgԘzv5825fxE*z1Ctj_{i 8&~FW(ڧrF>t[0daUpbD9?1. _'ꤩ”[ {_zqWֈR4R<@jYOr$R&[)c0$$udƒ3C;;](C iɯ:z9ਮ;eC f2L`PsTCƙ0Cy`t.s9:J   …SѲzBooo_x=ugϞՋrG|Xlq $KPy;߃껇ԩ4 z+g8rϬ\Y6lnsfN^ެR2 !NwG,Q-ks =;긆 f Z 9F?'̱vs'UtCYЕh>kg5^}NZ$VVG_Gv49:eGYh7lWwO;Wfw> @0mڴzv/^?uꔺOpM'*uٸqk%W NӥM): ÇVZwm8wlt دV^TfȒ!^GF$/:E4+@312i@SU=H8ӓ'O,kV}C ]uaD W`7 ͜T@y̘dIRVI XQz!cG\_|?]Y^lq~Aw3FիWҮC?w B('{wQ~2?o: Ë|(/OW #Wdǃt`x S|GDHH h*H`)]͠nݺyĻb>]vZ:)S匇N;ZN܁"5Zhp:܍Vt8SHޗ)]D;pHLov\CT4IB}JUۥU:ƾr ƏtRT:hzs}҆ǘZCe~ _'I+W/ffIAe0cEmȘwyH9Z w Tɚ`pJ{XIdIRPw9+UH97I]zҺK~IbEʨSZ~*:隵[e*aF)&;&G2IKVZJAf3XaᚹR軿K>Tӛ{ODj6agO6;>f_84yeCߴQ\gIC\/<8#;nm{ȥ ::Gc$F.@ f- O6M`ѾXu?"I׾q X4jgJY>7H =|nr-gNmVӻhjht]f[o/_:Q]hQl6ݻP S ;v us.٫秭U*IӾ KĤx1&}ݚf;o^DX˱=K9cٵ}fLLyFy\5>bר =ZwR>i$3#e`Wu p{j+Ur:G=uz9"<;3lb\KD-G1I4  K'ҥK(ukUېa:#Gv與C ɐhuT/WkG++XU֮f܀놼^|ȠC qޡC' 51+VAYt!')ݗǑDȟ 0?aܼyGD`}VCsĉ̒k6k Gh?pҼeO]'w,Ҡ~5ܸqC#FI&Gj+oq݄رck'0r|iTB. Hs9=oJ(ZF-mڴυӧWAweԨQtRYzvRpn#;wNup %KD5ʖ۷obŊ`7o֌/:rӜQ>h ;5g(R]a8\G߅AI z^xAٰfaVn0req/[!gϞŹ3Sr)G#[VZ6h+G #oԺϐ_F|e2?iȉr\|_.'P$o@P$EPD 2x;Qx~:ۭ=4YDNF뤩Vgq̯5$fs8r6jXq_|v3gNk:]m!kb8jsFyx,gZ@?Y$Zz`A$Bz}D_s3yHH HDG/"rqQ6mQ-Z&Dtjp >7"A!K6lX7ԨQCG.\PX;8T 9q ɏs.I,Tү]dgJletw:6R{=' JnH3g.hj>aݻ0x>S)-EyjQĹ޷YқM'M'lnȼ͞|şmf]~K.(Dq dX:meq:8^U; g;_pO+_)Ay*"8{kҕՏJ'最/^\WI~U\]I.]-'M$Z5kHǎ6};wjժڹܤI3(sَm8QNg|a: Ns|~ z[nz\͛7༆hoPv 3zSD5y5bNƳ(·F2eʘa_~ Nxίqru?Oo߼EseRr*u R)]uZhg;[wj)gv#LIN=7m)U93=2ϡ)[F7Kɏu߽[0.Cyz1feGe^{pbs$\8~}\FqgB7+6iAv⤹3eL_E ccPvwS v^|a1 Q7T $`~EzXׁTLET ,4  C VXZVC$$"KO8aֶ؆"CX NHĜci5$UʤRvkĻw#U !e]:5ppRD-_yGruD k=g#~s9jH@Giݑ?zRk߶_ZѾ47aI8̙٭pC8r#r H,}A)̟{ mcmn ~ 3_U>eUdɒsc""x " saۿzykm64 >\HpQkې;wڱ?>]e笯g &Z ,SBrϥ+[Rªgϔ=]g|By긟ٴS\w2~cF4\ծbK(ʗSg,L"Gl16ċ#{;א2JK%[iOY%=lXxvlNcSi,y \/tဿr銬]^ٲa0|YgR6 @"@{08OOTT;b L;rJ J"bɘkq{ J9- &w:؜1 [6)׀}DbaipߥꚃrĿ|<Hcɝ;ə3>hHHH0ηN<^R# exd{'hpsʡ+uDDY F[F"V8^F#$<38c*ZQ 6}v\"S nwj{v#ܐAҕ| FD=Mz#yawu7#ACʹ_ܟA3 2[%͸Yd9]w|G IT\]*`Ag!:Izpcݨhok7$[`K,A'F=w ;{:~IՋHիK">0W~`f4A_@%J L fvŋ^vts׉+p?IU([̙6OWD')QHFAerR8u:uՒ 'gLu֍LgQdi3=;j;W9 8fd˙U&N`|sDzs[q:܃|jNS2%n cY*ii ڸ[rA\1nov9u`͚VԋhnvVݐ\UÛlGQ601m7Ԏ Q9bt$b8ӥK(x$@$ @r֨N\ +('4$Y!sU>]șwD9oJj55Ok/aAWrT۩R%Izf`zyDu*i_UtN!:z ƣG#?褊J>yD:#:GJK ^SR@b]8x&>h H3g.ʥt4c<e*UR?#d^xQx1\;$Ia9K簝(|H Kn@aϞ=ӛp92)$Yi_d0#_=>^8q3fj=]|6m֣ǬSDc&;SJeixq sK@]Yy=J!3*WU2}U2hS6n7rJƦ87vŏ+w1vm-9nsI!i'7 1 ADXřN58A d!09'p!HjC49 z7$φyu1zAu2 H`g۫:eϞ=R~}i֬ng, |2;իΝѳgOJ19ߌҨQ#A81~I\Ϭ܌ f8؍2hc_M}pBCJ}y:햼sk'$LAv%̾O?mn#ɨ5Ѩynʓ I%cIރ{oJ;i^:^'Ν?Z  /ۑp @dۉ~ڕx4sDzթ]QGBqI:uv#GaÆ)TjժɈ#T2ZsҨ5 @'Gќ+W?|^KIՑ@0CGڀ(mڴ~n@&N7o>ƏBB5 çOZ.]!?sL}N,#xHHshCp|Ǐ_(t$KpZ8u i_~c*>ZV/:fk_F;^^~1BLW KÃC&Ms gͦ "ə(x$ׂg3z,'  %бJpܺuWl-qR~UWjf5 ]+[\pP)9sfj*}DQ%_Bd: t| "aC5w)_b߻wI#ۑ"a„ҰaCݦVZ: * ykŊQK.Ձ#:tD^qy)V+WNk{xx 6Op.@֭[KD+WdɚxG3 A0+WxkpGp:lG=)Ӥ ˑGZ}2pZ/D,{g[c޲k.J6d͙z8P9f87tn)vRnUɜ$T<|rPʠ) 0:(#0KӦM`鄇Cڕ Q Ђ7֌wF$@$@$$ܴaKaܔJ#qzɚ5Sz4OB$s:M4"ۑٙ! 3.!Og3t顣=b.^sO@uc-)ShG<$Wh;K"_^G_H H:0WԶp!sftiu1uZl8 Xׂ>/7W9a^@@ӵܼ~SoWVN?~JRgJ'GsgsV*3Y\h|w/l*yذ'zi(Ț3V-DY^\} hwUΟt\@`=Ȳ_ `M \pF;wN'~rt߱c^㘎 `#!kmtz\ F QxR/Qe* "$!\ʇ"![!HկVϘ1_U189]gmm_DǭKW5*;Gp:qMOJi]?H:jp:W_;rI*רdnxwho)_X& Rl)4wmvD,Zh똱c}lݸU&,qǑd)I27g.ui!M4|J ⹋2U9$?aҙzV(g=1RDN$g1 AAACHB$B<-;d#<_%#GKuosMz ÇU JׂÉ~ig\ƹ߰cIv}$TP*yJ{.[l^g.݉G%|pgLə?Z`M    E@t>>-IJ|^)*o?d>s |:Fe˖-ٷo=ZjԨ!'6͛7:~RBA|8qf͚2fٿzQS 3Ǐ{K#uMf+ rq C_ I^xg;/{ @L@]4m\K0ƍRfyI$CJ8zp#ƍ)h{q K RH8B [nՋQQ 2hҥK'_qk  HԩszTY$NhSfј1NJ2{r%~Yv#yD6 ' 'unr9gD,WӢEh 3g.csM+}go %3[EC" DҠ~US23%JKIGZ7zϒ%y\n |TtT܁s2h_}o]9˔IUMOЉïxH 0HxÇi:cuuHx"smĈi{  >^@;|*Hd$r ׮YA=~"CM'Dc2Z>"F o-s G#n' Hx@F;n9;a] CQ˗ F gۍx///\O6-ϯGJ$ $W%}V6!/_H8rV=\;۷k(Cu5 O76'={Kj?شܽ6 ;Ǐ2֪FD=f皌F F k)ܳ԰`F1S1}_@jȕ?_YurZ |p$yR YBqGd_$ZݯFr3Fcݻf/_3}Ƚ⚪l) _:}gΜJz/1`?'αILQgHQ3ڌW7r:u:H/{>E_n Fqb-  7 D& Q l/^ׯ_˖-[bT7vr4Ђ+?.$@$@Fv y*h0̗{lJK9:Kltd|=ec1BxYV}dcǽevzYGvD]Ur竤^Wʕ+7t՝;;lJ!o,kKdH.TXJwTz9,u?K'͖5\z]Ij8ޑ />{rRBI5c,U/^#KCBCǒ$N 9rdrX$@$@$@$@C^IH`Z=Hx~/-'Oص|{5HxXG]n 7|oo\7SZ-f/9dڶi߇vW{dq^ic$iШܾ}W;qJfꩢ _:= E-^#7H<2w(S% 5TΓ W',O>RJctywҽ[ $@$@$@$@AݑHH!DWPA/> xC(xDC QC&CqBKҤ |8|`xE"ŋWIė1}^9I=mj_8ͦӫgkb؝;֭৪lcݩ ,i"vG1dY)WoJ4%R65Î~֭\'W,LX5 8٭S& V/ɓ>6C$@$@$@$QQ0$$@$@@4e=x?t<}iwW^3g ])S%'XGr]t*_h0$@$@_~)uTK@Kf   ^ؔqH:UPBp//H>ODC ٳgN]rEf̘TbST<@$@$@$@$@$`!`p^. |hob|S<$H  4hҦM(xyHHHH>/Q/=;|^ͫ N 98(:CK" sȡ֭[wZ^^^_<(otΝ[oO#      <y|μJ   QjJF|R6oެt & xFd&      GHH_QÇԂt`7$@$@$@$@$@$@$ ?!HH Hpo>-x$\uf΢ZiҤ3,'      L ph$@$@As̩6m߹sGnj:l>}^ɣehrE-kP7nVyЫe9"qYldȐZ֯.UcY$pxU$   OAOA$ GK (M6 %JK /o5 $P-]N9_d+8v[fYLFa$H c[  p(۷oDC ޯ(/ Dþ[3kܹv(Q\ GbŊfyUy<xꕜ;wμرcKȑ}n|C&M  *:/;'  bĈ!+W j}V(x///-G/t5 >q6QЂgc,%MVvW Μ9#)R0wҨQ#s$@$@$@$@$ :]:$@$@$^;G3p`6mb={vF$@$@$@$@$@$@$8pHH>_QvQW^uzGQ4$hfS|<@$@$@$@$@$B yF_.E1kzOAOA$  `o۶֭[,F|0alٲec{|6lB$@$@$@$@#Vt)Cɏyq/^Jz-4i 4:܃ЇC9{3n6:?ϚDӧԩS5(xhG=dՐ |6…'/׋m $:f+JHr ϞK|Rݹ'qőF9j{$[C0ܿw_i).\8:܃P×YQ; @NF$r &Hj\͛7M-x///9tFՙG }C Q豜HHHHޏ@ rG%W|z{z]x0DA50OIeˁkQdǖz?;*VtN2gd;}ȑ铧8VXҠi=iآCGOr`A&B#w6iۥ1cX-=ITW9}[ y##QF1k m,[\9' r=}ҩWG[0YX޷Yd$H@xc_C`@;$W7ʗ O?H,TܾGTɤINZf7B:Cȫ   "+V,Z^БQϞ= 6HTsZ7pM$@.۾N`\fuNJGsIYT69zf`!} yXIe~^EC*e:g}fs?i$2ixA=,~x2u$ə:n3bh3e>f@;ѿR:f, [d~ɖ+>pDYVSU6,CB9t{ώeua׽% zx}gڿً~N|ց8ޱGp7p#=~t8 |ZΓ'^ܸqCmf:>#唽{(S@o^gb2@|xQH݈EqޥDA@0$I&M'NpPÇرcZj ,;w+Wf̘5kFͣ˗/ug8 D<J.w ZA@Qչwu~ѢGdzo0T\@0>w@?}ln`ǁU4lO6Mֽo>רo3[NO3&bvwdItcgj\,#'sR OYВ ?ҨzS>83{qĦ.ٓhѬ㮟iϡ"DHKA@A "Y`kެLx-Z4 hǎ[/{A@}נA3fL-E[K$7A@'#)kFp";&DXz TQVv+QwF_z̓< ^s5UZ{Ql5uTgfWRjW7UrY\"7}-\AiҧryA4ti⒝/0=m=e8U)Yں =emP?T]״.-]ܺK g/V8xQf V{(I"Ӱ3%e~%  !<Nv(+&'c̉%4q8ZOqX/X9OXc1ʥlkN4cT5FNM9?ֆ_DomʗS(vXhg;ںV*c.c::޾# /~\?s .4{#bETI0+P2 ߵМ=z4~&rGI훷kJƵ'nJ6QWP~ ^*]:1 `,[0nM*& j a ?P?̗0qBf\Kڱ oSlAB:ȫ!8U1s,GIaqѫj&ƈʔ.lqç"A@{@ҚC <ӖAҚ=ztG?݉݌  @dɨnݺ!uPY߿;v`g̘y彰 6Zʢ&gȉ  $I G:X6 {l0A@-ȑ#M`,˗/yYЅSHRA@:.r2N\ A@,^m2o"_K8ۋu_1P$A@ agZϟ?ۄXZ,xA (]4%J>}1  @ RFCaAKACرt#Jv h]=hJjA@A PB?|0?~.\?C߶C ޚ#F Z‚MN!i И1c851A@A@A =Iz 3g:sBnk9;vw>=sMy@w=/^Qh?R)Kt˔*7o3鯿925K~?ի7?~J嚜\]]m  Dwww Mf8q`jwhY ^Xk@" p9VJA "_^EKa6*p8\A2f9s{ZOgmNYh ?^/|r.Tx D,l޽k;v%,xh/&_@ S0  Eݻ۔!q#G!^qD|r){ڴ)ƍj 倸JYZt>}L7ŏJY=y'u6nkL]OVB0⋕tK]=8ٜƍkq$:9+6kfRͷXA #2eJ&< ,'ONx={f[,L2YHѤKN"(7f{]A@,YЧ>9AR >|#P@Yݫ8-Xw07_vE%a_WgUP=w7sv.c-4c&NZ@W X҅Kf1Cj|&͚6l&(}Byr|A=fϘQ}Z2|Kg]&6;1b% D./\0oAesr}4yeƮ5FN:' D#;fHCG.p $@:-ifJ$s$3pBAne){Z8r2ݫXm!l$kuwMGUJӍYI&R% Rf=hu*;~6V4){Fq2(]pΟ?gdA@ȍ5 ӧOt)Yo߾m۶$/,;@XO,)A@A@(r @6 Z9M |q\*;v+Z .ھuOvÇ_!}뫯f\0 f8e[=7\~w%sNHsgSYf o޼K,Z3gNGϟXre/ X#%J,;wxZg̀ǂѣ_ePM;w*1A@" OtލzQ$A@|`vCE!=eP/sbe>ڽ)RƌilK0-3u߼u`g͚Ѧ]ܟ|ysP~5F;:b @ppuu%l`k-Ǐϟ-ښ$k|ڴi-V-7VZE=vSxA@BE=a'  NG@NXjլ@xXzlҷ~Kf9h;b^Eimw>~c;i$ 0iTɉqߺub JVt_W xÅ8qb1{;vLN#A@"|"ExeyK.6g.Ѐz_>_vTve>? gN@MsS  `qE.}=.Wmڼ0{p"ϟ?'Tكp,Mӂ.+O*/NڛFj?}b#mq6nM$pжQg ;5+V # !5 ~ ͛7Z_(a|B/)CB"㈅<7oޤR„ B !@J>޽[bSժ8- {8~yUe;ڷ|Fz3q^cvC3k%NE戤˝[_e6t? o߾m=-x{,x̀ZѢEsV\A@ĉڵkT~}*TWE=z(5o\ɒ*gf*_A # _+U,E?uzCZ3GсGsGQe{歿k>|P޽{O{5.m+E>.X0/cƌNnKֳH7~._ך|"A@PD UT 5 Z/^[#na2;s̆ $iŠF A@@زe ٳrf6B 8C hy|wTZY\Z ?o8s2Y<޽`CO]tΟb}yޣhO,2~7B=|{Lc\+V4:O5޸! 0cp&I,~w:qD4 ? ŋ*pE={67K뭵~>RSA@A@A@ ZrI-U H8bCO")'Ej5}GC%ݼy&Ob%-:}nˠ^D]fC~CԠQW2ugVϋ ?=zhM+WmG]%Q@ЏVdA}({A@&ƍbŊ3ds *W頚Cʕ+ە' ( [hU0۷FA*Ux"3̀0`,YbƌIٲe֭[ӢEƍr, `Lz9r߽...[ rm;w&777Ztd,YB{ӭ[A2d@zRe .3-0t4hO\dɒQŊY˜?@ e d hce{WNQYJE*C%qyZ=}RVEjҸ>4iRɃ]| 8lswLV~&`/&ljB 1@wdP gj"EhܹԦM& hѢ Y6Ѐ_{LXl0sM‚MNA@jժΘ̒% ;7m7oLeʔaɓ޽{\3=>|@}e=7l٣D"8=yǤSܧ/9 Bj *Q;wb2?:n߾P7Lԣ?0tPv#߁֨Q#-dG?}AKM6ڵkgd.\bĈaM:ԩS  Nϗ/_Ҕ)Shiƌ9)ڌv޹s'?eMōj`ptu> 4=q\vmfгgԤTdfːL(^o޼Ƀ9rPzV\ftU5*N> P #̭eҼyɓ,}ӡC˟a{in;`ժUҧOoQ3gΨ3,um|߁yg_ DI\|֮]V<4(L-`ݳgOO0PJ0 0͞=;35kF"1S&l`HǤ=C;`b Dvtߏ/'S֝Zݙ);XXUdFԸiwsݮC:߾}ǺQߑu;9zdofҦuh׶!5kZ[u6_@IDATҽ)Aij@}`Zrv/^ZpM8N<u֧sh7Z awW9kл$Kn#ǂ@h"}&Mxbyn :jJ/@g͚5d5mLݿ@z<s8!MAYu\%>zYm0w \Ӄ%0àlȇ1$X$.о5ٍ U0P>6o,ֆ-&W,6l+T2Cm|g`0a fy%ރz„ I4?{/fZ;j v"yG%3 xLzϞ52?|0çjdwTLȆQ`=;vd5cwhF4sڵGV\ ^AG|20q 3D?$ZgsvE_ImpB;|r_DzLԵ},9#mB?vjD_9AfVANGy0] N ѦZ ~:tejDL$X2t>  |᧾ᢦT:߇UfO}ݟ?;._~xd3jլ2ll)[UZVuH 8u6L&u*SR`r?T`b:1lwݩ+t^%@y`X.s g`mp,c0gA( >&X ̙3ta5Sk@ L$<KLBW>1PgpA+]'fO><?@;j6 0;~̙<观EӧO<сA8NO05JGa9u԰D翘 , =\HMkܸqw)rS&uN :&{h:H +V\H΀W" dMf5cޤ b4 JO=vL` `E\` Nia!͑6a>*+zuaDķvOgk?gRP_E׷x]Q6}RhҤI6r" n^ՍwPXLi-8v삓Y;E8hlmy;H ֳ lXZ >XXhCp(,t+V,}XI 8dvQ7fdt’5`i?߰c09{l͍60az;t(#PbZb YNsl`paL2 kTD6mx`yX *pi+ 0P_3tYzz 11a; :Z`ǁ3gLDHA 00Lڃ1~c1m'R) 9`ѯT %X* i>cbU-Dm zBX`Lxϑ6iMzAlZ:A}6A +۰l 3 w8HAY 꼲"O=x ?ګ/O{HI%9&^)iIyˋ?_Q{=3FYP\PD ,&,, "pw3:R4,W,Ft俧WjD/'k)^Oٜ$I g?bnc 14,b9+hchmX~ &7Z+f8ᐿgm_b,%[zz%!ip,3Vtشj+a3fֆA/&9l ct`V&:9c;>ÀiB,ẍ́G:[t]  a |GUndH 8][.zC_ࡒY|)Xx.3;AjeX~?`d`fClA1*۶mkO0LZ9f:C m:@*> S`U!V'". $ Zh8QL@l!SP){A {(m߸[;ޖ&J+c҆=ԭMOڱy}'28ym,;ڲn+JJ 'ϛ< Wϣ UđShÚtmbe)ԃ oQɑG_ׁt4i `4|Ŏ:9?o{ ݾB9}tTB)D'd޵mM?ӦxCڰRyj@bu/]4Zv ze$H&: b(@&Dia%ĪJ"Nʛ nࡃ{5D,%&O̘>=B+@r:͞xby-j|{RN51 vlNmN6nnn+p߬OA5=i>fnyܹ! '8V> b`x6刏=8ّVK 9Ο?VPw0OҎiL:aޖU1x&dQ&ځ:/~z^= 2VŎx`͛[m>t"y`c5!wG }LD@2 /݌;!}v͎s?>ԴMc1tYjݰL7U*CyYxvhLh@I'U ,4ls,υԱyf^x!Z/`Q&NΝ:O`\42eHo^fє1у|_mohlcbL_rhכ} Yq9S}0l.I >f4mQ!9+B+7pU"baKJB@G,o  ˓ _b`+ي-2[OOON*&*[&L,ڀ2l4!({cn;gK. lݷoA+^c  Z00Q๶,mZ_l]O[i]{&&Dd F{ln ;Xɺ*0S[rnEjRn=z}i'Wgaoإ7D``#@:`=C_~Ln@ Yk3TNv . :Ð#`ۣ<Nm/^H)v9 AJ߳g5nܘr1|JJѬs]#{P&0dȸ&U\DŽ&XG<;ь{޼ADG:8F"8a=V&u}jHzu`;GsEy h`9Yn5*s{ZN[a-pʃ3e96!]S8Æ4:= tP o `(K:XX¬ tgza 8:}p# fcp Q0I L,`IL,`6N ұ:]t`_a#+\J-k#{A "!wQ8xѤʕJRmoF={v9Be3l˭ZK:yl=_ XPsJ:$w z`=*uەUL.[:odq]N" pmuz"b/GA_:ཾg@< G@F?9r 00! kc7qƾ#v j Y@80oM(R4Xa t8?4뺡 =wL4[_MNx\`;Cg#0jXfi9p-GnmSM;TW: vi}H-;_-3VLvϟIwRKR$rA-r09T/n5+ƾ{5;X7SǞ)Lػs?󗧚)_dr;Fr 8ܝQ r'H |pH) LG7˗ח8&t&<G6vc 1W*1AypkVt-ƆdٲeӡCI0dֆŘ``!/KpkCp"`ta@d dc0`àm#b#($: ,F^ .\%K PÉƗy < K1)Vg9o uAONRp}[`cFZБk ;ɞ=]3`xŏ֩4ui) IF^9z^5*Ӕ |Up7BZV:غG 0kGLg6H!m_"HiH̖-CEτ-$ E?6 YAL?(&8HhC ThCBŞIiV, _?JxIT?ll7SL6AƖe̒|-ʖw]_Wt$&e_r6~&]k%MLR5ޝSz؏?@PђE4pH<{mGHQ |1` f8X޽{l8q+V͐w3 `6c}p0 8| ܂(bI2ꚹu:=mlX cbQ5m/[&{ϳuVZ \&!D9_[@qwڇ&Bdɒʲ$ `[9pDzi |mڞcyԭp5:K̖diPūt]7=I}}gN98oy@)'Q|iU(iOҥ9k?QD wuj]:kGQ QňK6]tNiͿU jOr>ojD/?={'l9=S/*|+|( _4[i5E%M̚>:_XCR/_|y'NWRbi°D 0L8_?@r4t-'[ѪS 9q6f:JlOw fĚrO1r `Q!9-&zp]4{5cRjϛRиYܓ7a\ϕA_Wb1hX8[X~Xm萮\ w\USX0m%+U+6я7رcgAS7;{p֒ZkLZ$pdbE$Yz Ximbz"iDTH^9Yf+29rَn*GJm^^l._ȑ#;,ae !PB)N2pD?ZGC/]}T22m"joΘ-GtƱ>U谧P+ӢLjo{HPkZߎY~ɜW~[v[ga:6h#pj~>ZEɈ.;np,&A"4pw뵞s0_]IA>|`m{h;ap]|8a|6kƍYkߙχ+4KvZ ޵x<̘u<6m/-LzI;v[wYvݿsXMu:p~|VW2iݽuH [0|R_bծUN@!3U\`K a]͗ĉEcG'6ҳ' fCY-]AӅ[9h,FCO<9sy̙5-_۸iM**{Ѿ+ 8 AO!.0'ه 0>}*ڲiGUOYl&/P8mq^JZj=_CXM'-Pn4iZҶ}F@Yi6Z5+qPlQ^8XvI%{jCgNn;7̬x2 ylYޣY8q&< =|o Vtj__6o'-\a;jT)}'=fៃESl֤9d2y`LL:;#fT͌tđ}R°Ϛ߿ pƏ#jݰٱO,3KdtmxbS7g3fӚ7AEHr'6\N7PV}l"ꢆ}q1c4?D Çىp d VZbƽ4BXgӶo>lذ¡hI cRLFw,U[r1hئM_sg%JMz=z4З+Zs[8pFS|rݢ }G-j|/ ײf+Mѷ ,e8u@כV,imݪq^\xv8xҸg}>M#Q2:x5MJvxsPeֽ c ʘ G}ܟ?w43ߓcA@R2\voK3PT G?eBvppjfьm#N[B[g٦u}k\{q3g-M*87W9 VRJnSsz17l[dt:pwQ:uQ:AkYl~9{sbGߍs"$m,wgяΝͦ]Đ[j xT1,stv,-[SϾ-xL  |c2~[He+ׯ^υlvfWCЅ}PG`ˣL⩉3|/ lsqUl~\Orс{PJ ٞ#OvZmXҢ}30kҁ1Vv8]U >ClB ! w'ra;pXdr5cߥ0T%P?&tNӧ CH~FF<͂ a۵m=Nl8hCzQ޼ٹ!| SZֶ \8)͖!1,]5;)nfXRK5C> ͞㷤\LɸD 1)vQj\9 -[#/fo-h5 Ŝ8kV=|2dHM̆x/WR:h7i\ 4zdocAs5o~^bA7؁\J{X t.^Y,-5,nȉ MBlZI~dVUjZTʪصT9[@+v,t긥~?{N2M~Ž۰D  =QR1kwTǏ除.Iu}fȽqzp!AzsI%"DZWovk#A CyN+Wn by(l͛0x*Q<#-GaCzAG X v3ZnK*- jugԙƍm孜XO*R 1YT_1̞Z;%Je}d7"CsoZx-;"Rۤ- =4GEW?N_dƀ9@GRm5j9&kW|L7^qZ๊]s9GVܿvޭbSժ'yXxR927o y bo&&8CH >UR+{bja6+/aFر΁KJXެYWpA)pv_R:#GP cJӛ=Uz^x58ifRK:{VXa`#F4f.yVO7Χ2 s:[fO[J?ʁ^aFyp:tT΀=  8pΜOĄY\~'-YoX&p^i1Vld2tQ1ةi>Їsz6paT%Z8@?5&!dX PZG=PK M1tN`WT1Y;M-w􇵌Q*$a$GZҚW4W@&Mh+5jlY2lrMnݢcDz,ߘ1c Py-8 T@A =?㮳;"؁Z3kpiL찅}≬IeKu8eٌFi| L%X3ƍ;אTm:]pИNHM!xׯ! `jvҸstރ޾6oό{J2f ̶D6J{A@k>  G-+_SS>_j2@X0 -^8W+k1!DX3T\8&y4ph5|9رcS/_RzB} g;Ι5__37M4!6U6jX86=CM0.!kOԞMҸUdAxR Ϟ=9sDdv'KׯO...!A@@{}Yq ?0VtP$ +];0 'PJ^ 1JڰX,w$?:^/l8r]>?X;GE͘_"fy[oM\x|7$g; dp?~];Vg$d5nTj(OkEp>]*Ս9rdW͝Q:ϟ>y`5~l?j\4L4 j/1PUe95_yk>uٳg՟/&X C˗+? UkiL./fL0'wϽ.p=[8vܷϦ;_ +W.Zv@/)W  jR,I+]1A@8c;` 3HaB(QC3%K䥽3g-cwRWWrxʕ%` 阬Y3PiY4|tҥsBJHDF{vfu5^XW'X y61A@A :`15t68$j\FB[r|:l4g @f)2sRja APmɕ)3[lR=VlظϜ6/h'x b~noa5 a΃>#VA+lc$A>čbD5Q,΁u4p==FV5=~Ktu$PO>\T9ݽضȺHsޢE zM8fϞMVUѢEiJ/۷/۷bŊ( 6d6͛74i$ڰaz_Պ)[lԹsg*YNF:uRd{,b ڴi={vB5k4teZ`,eΜ:vHeʔ1vy|r1cLLPfaÆq>|HsڵkS:u8p]m۶U|/rׯ_3ڧNVD#GR)K]Oƍ|?o޼4`5G'#݆3g7yyyѝ;w(cƌ4p@*RV;mz !8C I;X5X@vRW0堪`ϗdNG/}[ϋņAWiTB Z%ӗ.]gdnڶi@Ӧ WvDAY]"j[] #Abd*Ss_xIUʨHJ$!3}.a`sǦ#E2C3/PYX6dמC]g֥>t׆tU-G+* 86Nsv&NΗ wI9 U-`5Wy$KhnT@.͜薛G@/\m.Wrl0m`H{w<}Y.D?%r`u~}ZAyѢE|˗j⦊;EMb'Ncѣ ٯJ%J`7hGg8ŷoNrqZ8Q6Nb17o[iС 4h~'.k֭mڴiԮ];ίL$EQ_J00KF۶m+4n8Nh.]D׮]}zKG!h.& 9WhfRyB An\Kw.Jђ#q `r?zuULLNo9͌KTGo#8 0oӺ>7W& 曰̄q|}x2"u;0+tlG֮Orvz 5k#4h:NK 9^]R}0q%Jvĸ6J[DI2)$!N YGlQqoԤ/{ 4g HBB%0C`T8GZ81d/^3֧W[GP)a<~t\\i09g+M*'VZދ٦M_L]ĢHiȠ.cStEitIN -g:d܃.\`50{&,/pOmyd~T:6GzЮ݇yMUGW/=~1rWX-ouUؾǘ)'OTQ5tux`Yۯ߫/~`yWP=c0α`t6ڡ0ڱnN&MJ:sr3J1$CtjߑѣG߾:ANc-3zh,|>k,^ӬY3 ۷W{>y|J*eȹ}<==VTѓԧOfxwѲe˸ΐw% G1 9rtMyܸq|L\ߊ{JO"W㥵as0 (|" C1ЁkW:ujI'x2g ա:&hʮ#xj|ЀN\^ޟ3XKzѣEM[p\\(Ef kJpg,C-[o1]j3/=qNоp<3쯿b; a٫ @@@;Gmj %&ɃUrX9'KɞJ1Keq䉃Yz?ҤNA.6rK@f% ;?`veZ?,6mG`w_vYv&M bA/("@蠖W`udgl0+6ǟJ7/̮ax/L!V9㓩8#ovfR$wo2<^5u\lUO }zW~.V_|14O[|YxR@I\<僰 BMm#ޯHh/!}?DpV!=vh Yk]3g3-ݒ3gN$C1>Q>TG;בR-0aig pA&C )X1tfӤIcVO(`XBXauص"*ڰB5"|WlvD"^2lLbI+6viaçUAAhvv#F4Wm쩖X Cr53[ȉ#[t xӒYi  ` Enؒ0r>dTy $iqL^~0X<^8LS l0IՕVXDpl&#[r,V7Bu8#`o gv@ 00 4P+qpT[y̶6aZ>PM*6 *e]ToAL_t{Fߗ}A |ňj<HKwPa>pw\~jӺ ﯐n][65oV֩fzɍ`]uܘ嵛61ayjU9*`XY30vjG`*8]]Y€: fHpẀ{^enޱ|4rMA@A@~b+6|XFLl~رc@E1ta)S4_8C:v @IDATy|Ơgx7;DWf=$NvӥKǷoU Жt|"$Pc\BfuZ=$ ]i~h_^@z0wP,E͑#3Z0Gkg9-9Z1bD7ߒc -eK&ѳ'iά?R  WBի7ܵ ŌwbITLd^'B;o޼%AݞM:>Ћ/ҥKZPEYdɒp 1[-ԫW/vi'uVs8F`Vh#СO &TȠƀ~޽yCX1A@0ܝ^r:R fRf@M[H,^>|f(A@A@@޼s ^ȝ;Z |ڶmKgΜ@۷os^bխ[hT505eɒWn>TԩS*TfM|߸wX,!nnn`>|vI#]p ySbEEϡ ԟiӦq׉'X T+/ǍG?NiuY'O<D  q;} `jڶMJ8A*E9tSFҾJ8ZA@A@@8~z*Gbp{'Qn>|`q9nܸfӧOtMfo5Ց9QD8r(3f1cZkuOOOF<T Tҟk]3;?/p=ƥgU>{·ZC=&[\('}?ͻ* N@` oj۸o?}vHA =) w A@A&׮]-[о}ɓ'6ӤH/+:7.qSC'-=˻qC#3U8k&}AϞ0g؞@ݿ&LG UrQwǷA{4qX_"ڂ^sySڢT!(Mq_~8"Eb9} ʂbf͚ūrٲe߯<88֬yORVGt=hCM>d gU+>{n\]ָ$ZL߸qV rL,رcRmi UX׹"|N7nQԨQLB*_q'O ~*"wPҨv xܧPAd-5=}JOyf.炀 G9ՕAK=<!TTm%wt\9s:%շnKͤ╫1Qƌi[q;mٺ6E(')B+=z=h(WJ"E$8z*Q.ɐ!zΟ=iRϲ Ay)c4|J=)"5hUm+,^xTH>r=bŎegnk؁=ztآ ]pgLG{Osg.ɳ$wTɩdc@7ިt!ije?O?QLe\)C]MƞoR)WԡG;ʕ/׌荚oY<~j\sJmF1@_YK!T*x*MW;R/uً4r W@bo'x~R5͞_42 #4Wu60]qjq -Q@mi6gJ~{^՜UA@Pz Z|9}N9jx}-֢E ]6T;wVކP F9<=>=cUVLCWtQ6CO>L0tF;W^ =EF/0=~H6tYhԩԲeKgti ~10bO@d;7c-4Gv Оp@S[l֮߳_0.ݽ_h{aLQy^OǏ~$OX}OkUSϿϸaOopXkZkU&0.^ 7NL:\B\ i].nf쇸u&7E;65Ɂ @!g~omCʁQ^jWOz:W.^M)E;m:vqa]_~9T*oYڽc"E$6~^y\38n\͘8ȑ#͙gYԿ 8Y?F@wDXm /3q.Y4_\yOO}!駱5m[`ѣ[Kx~ݼV/Ŋ棑j9c~Y؛7뷩ZVNm&ͣ…r iЀN4h5ӺII$0~Pt^  `tΜ9ٓ^zM>:s̔_jŋgsx:?Č5ʨZ[neb?u#ERHʕKyf4?S<'e)tsرc=ޯVϞ=#?noҔL^wlߘ=u]Sxq9St2YӚ|1b*U s ~wA<拫yzJ,1g8r2;t(:Ƥ| t:u*V[5lkwP%.oh5ޫړˆIs 9pwg@<ܿj,{6/r%)Z"m%؏=j9q#hE~!? ^&R bnH h*9Ym4H PV-i%ScBHho+}4mG(4w:Vݻ7;wN i4X i2SQ2߰a8o Qijժנ_|yD} l~#SL[qjԨ2H Ȫ͓.^Fv'8A7z9m$5ANNyI9~yJ2e\nݲ.\ŊJ*( !3[n;U*US|kԳdCk Y~f}1R|hvV)QōL]DMugDI.gcLfA= $yi GIz"i_ǃg]!(diT TSxaM,&+@{Xa!?0dLc"Mj2AJN0'NO>WcLfC rh%b)6xz鵊+< udϞ fä=c2ҝpFV^]Mw7OmrxSM&`ҥF+;1 :H輲*xXi1e⹏[Org^:q7x^|Eq3FSUt,gy޾Ò1I;=q=y Gh T0+,5F0`v$H锂f.QQ\g⤉hWMd ~''> /5iн_' _P}ROX藞ÇW__ V1j:~ܺm_͠iPs4a: ƞMkH,jߨVvjdA!@cs%=ZՋfxݝ]_H ҧO ӧOȑ#E{ҤI%tOLڵ|}7e瀮YwZΞk<*]ΝKh qJ֭ի,QTj@kZ|ACdXW*W$w䠣3_-m-O LMghHTKݜf>F͚9Q~w]ʨ8z̷_[}K{àe|O8W* ҽH<8bߏ&!w^b[*SEňwH*? ֆs!Cs#`{͚5 y #+Z ub2м KJW=~ޠcҪzuղHcȗ0d6d]pE}yU{1"vefm/_z=ERZ\<}_zE$6j\꼲A w44m RJٳgB Lz̙/( )ĉQܯTR,l6s}k/ ,Y“gHyQn%d6x Tw?<͖HW 1Bxf?Ƌ% [ް| A~@d #lׯ_tA֫B n LiB|İưM_lˮ.U,3^7`,MVr/}{)ρFx|8l<ɹMj(2>IǎM0rH8JFǏF ͆{^X";ocLB@ZгO|Rw8a^@Ѷ~NZr3eQ++;٩+N S-%N$VCU#0*V}?Æ S#F^xXvş+WJ+Ha  ǏsfCw3 SCߧ { `7J>,&qdɒ9PGrǡi%8b<Q'V" )pFE2<0> 1 QI H\8:VA ocpVn<C΋IД)Sd[{[\$nܸ-[69r <@ 9dӫ gmHCG_M3L*X{ F,߱qƤe/T\էOޟ޼~nE[v=-i޲X?KK皷v康H@l>Mf;uSŒy$ 'clYhպOҤRZ]yóC'hˆm7o4pdKzC@0vÃpA|w@Td ۙp e;32#8\lxEṍΘ16k֌P5^[Tl0xbBp¿!(∶5{lM;L7/?pD=l4V` mbJ UpO9HIXc9Ԫ+kxU$䋶u3GFǃ?*ٌj)- 1Ie0̃tsTRXfw@{|㤖OFa[v]g[8B#=mbBz=oݵI.e\EO(,NrSZؖzu[{G'ꄞ箃xυ-lbVQx@|A oti^;L{ktm6b55m.g 6xpփo6YNyu{4OHXF8;Pva&' x , >!K_Kwo3g;'˯q-/5яČ*X=)kB@k.z-f/I3ԕ9W)as g/#߸&]`ȀsM;8k%MLPxտtBsXy{vJ Vq`^ڴ茜9pw-7[:PS%d[YW$xZio+$&OՌfR\2ʙ"EH#͜n}}|5Io߾ˋӦMi,׶/炀  ~9D:]uǗ;x Y5jDX: oumZba=p9&2C 2F1hǚa6^h{<6PCIuhthd:t3u4wm*cR}5!뗗֫wI-&1`8ضIx#Ft <_j?];Z<W/>vL94`'Oq]ghl4u az]}O9-$9FN<:آy,:Nnޤq dDC^/ۆ5ƆTğC{а!q#Z4/on:}{䉌{cӞc{s~չmGfoPsKq@~.PeS߼uFxlGT w66/ǝ]wy^|>4֘殘eg̐q\OXѿU(*֥ү_&{(W.9#\9iIj7Z F.H{ꛃv݁UiD.\(ޕ5o3Ld,Y"5A8C.ZJ\ &514"1!oB719*Mm#/$iQDVg2zk1|cŊiwBجmٸiu9V i " !d1u> k%Lw1Y3-ZPF%琥3w'''sLk ~c5cǎ2 3 ] AoÆq:̬Is{Dzs6XQI p@0WfwT/U~׮yRᆪ$#OWA2 )ٳ<>]]]#x1: Ё&5u6xeaE4himQՂ xhezu>]xC#N8>et!t[ ZA X!L#~aވYK3>Hz%=}s{,bRr3֭Xf60Pb;7%QbF@wzpPR?t~$孮ttj=*M:[ |A4X̘1s^s8q"{fx9-!> HqHf D>d0.mÉz˗/ yzX= &ٔB-{ۓ^+q}o:Us Jz9W6ߪ eU*۳X^w-CRsҝG랼/S>E}MZ5 ^\JĦS52}ڂIJݍnݸ(g$O HBG@w|J,?w?k^<]TϾ#hNj Xp!уv 7p7ċXHmxX/2NG1QC_d;tQn}qGfQЄ,92א{ oz /ysL~y:rv) ;xHG9! V"61A 8"ox92d`}߿^z/7y^,, B/^ نK/xr*U0A;^RoܸZF=W`G`-'7Hrxcӆ~ce렒iӦ5x]&x>s .]=_x+V,X<ԃ3]  `XUgֆg[~ 7{X_ڴ.s‹ى'^%{ >>z(\rLeM~ !̙3sѐ ؇:Ԭb_f%qpwJA@!{ѢEt547юc\ `jN%h!(dM a2zh ih/^olȋ[nMܥj"-&H (p8pXCl ::! rxh/_oK yoذ!,Xк:?'Ou!{HQFno\! /X^c/B_A@A@A d! x;`^Eg:w$zŊQH so2B !y1cF v*ag䐢e4'N"Օ D𠆶9AϞ=xLPIh0&Oy51+>$b 9ӲeKNT {Ş mhΜ9`Po޼{|>|HǦ]{pds7o)Ygє_:uvJbt H˗Y-?|+IW*o^{qau=n!HtU"kg͚w'NoA:Qy@9u !wp3:<-C&n8҇6h#fͨH~;3/|H`2A,dC]<s-5   xeb'h[2A:}NX0lI0lH}A #q| <_q &uݶm[ dO޽{ tBAN}2Hy(@[)vHlܸ3_HW:<@R1fR 3ƛw9BZ֓25z8`0C=(<ӅLjApfdA1A@B XW"@IDATb2 X!.|89?~q.x2BwCÏ)]C@X@8~ϟH1vH, z˖-cno!CPi޽L,;vIqr99^B\`̙3^''< $ qSILymܹ,YxcXbelA@Ux~@l͛7ʂ;$ d:Hu rD!c4hЀ Fիd=}tjڴA@B!QFQND #!4hFWm -cq ;e j2 YDG=tHЌnhcA ^fH`}qovŞ^{߾}9(+q@ښ%hbǎ́N፯ zwf#M6鬁_v- =zKo &dzH{wA@A@A@ЀQ7nlT{ sqq ۑRW^}rXi>UT|n'SL,-bTͦ=z44o\'$?@iG@S1 dcpZA@A@A@F@w npw@R 0 5jTj a\ѦIҢEt2׭[Dz(wGQmvy{h0 An-^z)/e͚/#n.R    pw@< T/@Cs2/>}3]Z5c4j-^֯_"D@4c rf|K\XFK$IX2*74%]޽KX[EtC-   $B$_w*U !6Zxq֭eɒ@N::HB СCgϞtu3AŖ]뱃&mL6FCA@A@A@L*ɓ-zZ wOOON} asX6t#=ÇO*%Jd4L˟P <<<'֦'u:V=!0Yb  @tUK,z?6XA B;JQ޽G鯋WQ DT.CQJΰx+nPt}d/ `?խ[lOv#ʠ  >5.vu (?a|?=|>D 'Gm-`㛼~=cČa;^xEc9b_w^ q9!|sZ'Td-48>>ymĽLA!"d+ 4_ w^A 7l-bƊIWQ]iKB`QɹzE]<{ FoR)WԡG;@х{@%)zԱEt ى4@#GfmP tx?1p_%YPD~r%kguH[o!-X=Z KZ|nԪS >Ɵ(NJ~ ?|O8(6jِ:Ƹ#su5kt[(sSx͎<̚ek| ҦmQu494ܟ?d;xP*C#Xq5z+PghchSp'AǏ7o~d| ˗:dWȋ2b @P"S~EjWO{qw@v?tYjV;}\KKZѢL;y!RHL #&)W4jpnέ;FHxq'uE*8?tÅG޾Q]k`w.B6B; pË=*-_:Q^~Ҁ}#[d=A#9L2=>Ҳ c^]Ur  8xJ۵zQɣyVa=qdBikuOܲeTn Qs.o։jհ-e͑RIe13^A *P$?ErN[mmzpREs0H5e8w.ΣkIR}iTtQ&!ɲaFou[ )S,j<0TVer=':vlImtd}wp/"=o%n훱V `5JĂXF@} 2=P @EŅڵkG}7   ֣v]ʩaVlYJy+HB9h -߼5N)h撩!BNO4IgJfqF8#,fW}M/} 0f9,H$O]*ҬsX2.z'tqjʼ?E4~ClYfՒ\?sN ,ہAiŒɍk?2py=x_( !GwܾaJ G|#ӵz`ykotkuyM삀 !:;;^dOAA@<ߺaS2,rs\K/E g/{#ܡ^&.7;WV[.<&6Hm굫2|سø5'r3~x[-\ i׺_VJcհ+Uy M_}G@w")aJ8r4-X&MzA- |.r2߇A@p,)l  mp3{#3ϧܧO8Ç5nΐ=W6S7䱇!|s/lgYMoV0x'K>xlj1ղF]vw=e.o'u:'Pٶ)gV-!B;4T-U  ϒku^\ H[{gzא ŋ'b-5A@Zĺ_"D4QTAdkLD,ҡHnOY}m_׫)/Ӆэk7 : V++;.{xuTr4S W.]Qnc2vp#O= HE@wY@w]1Ѿq||b %ۚLג.wn>e^L7>r  8,_[. ]HL{ګ:V Yf a Hì9r렣s!qKA@p NNN/_>:tEH51A@A 8"0sJ> nX+jҌ޼~ֹwW]pzv裈zWi4yTwp/]$%˖LY3@N4Yʜ=]j^K9&e̚_rɄjVUY[`?j(ܼuOz4&{ر80懴oٔfXG@wCpwReD8Zb)ܷ,C?zԮ:}kϧkB  x[:﵌JЂ@I}N4_<kϝ?Ul ?ʰ8qX}Xhכ0q-"T(/oSg(Bt>ݨ&-NRʐ75]r wjaq(@A՛4`ZdH~*m1dt1{4L3U  PvH"E"  kRhmsO?D.iaLR>kt&ۇI/_dRףDL קν;z#y yCy\\Y[O*P$bČ;&`~mY`Q1APTX,bRr3֭Xk)[ қr8QhpwI,+nܸM&Ђkut~-/f'VR/ ̑tԿŋ'ϼ!' @F zL/f *WLHA@+C>VR1(jԨ)[F5iՈݻs\Z)Sd\F|oNv\٥a (&yz-%O%Ik ?<2wj={Fj1N[0I-ş<~ĉM)S1!5Mw9qReF. <_E:Pi@wؚLOT"סv,Gy)Sܹsd   xGA@) ޱ&NHav]m*E'U8fz֜YI~Bǧ=cꬲ*XRm$=g3#F +݋L8:A-)/ bŊj8XLA@@6=.Զkkפ% RwJ<T@@K֓ Eb_d+A@A@#X%Wn]ÌyŜ삀 @Fĕ#az2xAρx;`>.\y{Ksv9BG?oM/U1EIA@A@7"%oA@"PřgL-Dс-IՂ pz>wP`[WRANC'|߳?~nəwj?)20A@A x#;(A@@a` @@@1a}mʻg}ÑSd6mJN+n`t3; fK/A{yW+G u @C՛4~\B$1A@y̸` iSzr ӡ@Za'a2l12 )RD%9A@A@A@A@>~u${0!n޼cQ_8?[듭Rj- ]c?wJʒ yd;tʕ3I>ڜ+Nd;2#њXA^ŋWT|Cwَ|MO8gQ.L˖oF#WohTb d    ,y\GO !p3Ȟ>s3A_)ztvJ?&R%N.]F͚4% КU~ڴa Gu0R֍t` U"+hZss<{})=YG}v>D5@A@A@A@A l#Y51 ł"){a&dúܹK(/]|Ǝͺb6 ^͚1 Ӣd麆W{ʿӒE $IR҅Y#~\u;W:qtM]uܫ=K(GLzJ&ȟJAgpϙ*wZ3>pϗ/;EXc:$b`=tlߘ q&҆cjȅ    @F`KQ'%S! ap5z&˽@Eo9SF-*:> xϙ5‚lǹ\Z#4v]<|Kty}b_d)S:EvWק4krxĹ Ƞ@ Ra=kyS-]<+    az~ F7C]6EG ӭ٬IMoC/]fd'yҤ s?`Owz[Vl1[ɜVk 1HuHXoU{_!L@kμSڢ޽0ͳg/e     i=h x r!מn&V"9JF~KC2رmJ5J.߮-e$>eʯD9sR%A㓙˃0<}ZO-dYx#*ر:>; ^=ZQ$&    Xp%HfG?x ♓8WP(R'Wt]Ɍoann.>|* ۱ϟ3fɒIx#p`UEN#͛\}޷o'ֶF"    Xܱ'9+"m:IAW1:ڛr7c=y+fz~㶷׿^O&q|m*\8qn>uq y~SJJ/^K'QʔI|ċ:ulH Jf4f,z1WӶ}WEooݒOA@7.JI]x>zBSrJ6Ŋ3HHirŌJ Uw>|;-{ΝJ%Q,=[F*_8G8=+(^ ^* >N?Cؒ˅pL& ^@ ,>AG'Ol_m{)Kkp_f5lPg>XfqDI ܏=#v@F瘔ѽ|1bkP %MQN8O 2GA@B6T`FMњ>moא:Ղ1bD;jjmgʔ6H#jÝ;-08h:Kzl+MrhT{!cd,J:} dϞQw tDA@|,պkS=w6KrHTm&ARgX6ӹ`&-^ )HkX/ l]9e}5O2u\A'۷k`ԅT~Gjg=wvEoXr?v>8qbY'HqMA@A "pyʖd;F&P5m>c\13PQodu R_^: R<yo7reA@A@JoͲuv7Bׂr1=c]3mClFn}Zrt欯3p4Anԩ qf\J W'x˵kU4WXȟ;3ZXxIik+Yr\ 6=C?%xoIy=\H*.*{A@A@@B&_2C%2݆oO_NѮX|ϞD5k2Fb8gCElht&+tA7g]I4mb}*@ iKV=j*A@A{غa;|QTO}#E@$ex;Tu[Whެë|[ʟ?%A4wJg-.;=ԍB`7TOto\Z/ǃt*ұg9=B_Gc&'  2i};l7̦92Y ؚEg,kXg KX '7oޡQg+]\>'(t"`ajer i#| R<!*{ !CGx "2m`/bgŃ1|<ׯTti۳eFcŊ%KZjt).!U\Y}U/n}utڕʗ/OQFuynqFڱc .{~'eB pXh%!#eC#J AH @AD9^~-T`.?)75kqV! {%c)H"붧={uAՍvS(86)[\޼Quw5<\>Y~;{2B9ʥK(^JzɃbBS->!#/4z䯺X:[5[[<'?a.uj tD{Ü%cl~ GQUޱ)ֺ}믿RjҤ M<̙Ep{׳ ֋+Ɩ+9sgϞ۴iC#Fn { h…F+=_~M!N! !d;hMU{4~U! B@!`|P5Kzc_WtlYm'Hs+K3ZIacΜ#}B(W]EH+>Dҳ5]iXLm//\1^k]xF彏٘37~ݻ?Vun b9~8TvE9,~woKw] (cQݯy}a{F];uxU(_\W]">[A)YY5n^G`֭:pݾng|WXpRki ǘ"F,Q A/ovL7u*T9FPՌ#`1KiMztk,j|=.IXFׯ|̰YcΡavkV&N<f_ug4j3[$5S8jq6nG+o5kH{ *W(Pŋy uHht4^,-]<ް/J8)]ڴKg,CuOǏi<`C:>cs˭WٛҥKHjV%ܧOsWYѣUVٰuQFQϞ=3 ;|n:~!Q?~m[>~2fHⶹ<~`sY؝(Q"ڼy3]΋%J<3eĂ3ghk.ڽ{ͼϟ?cǎD= ,z-=?#>}<$ٺu+ X~! !vKWđ){lJD@"]:{t)B@ M#RXA$O5gqxU-v#]f{D4mRBlHjb?Hics(xG&/z{=ltf/oUH-T+o.`3q㎱ۙFd;fc.-kbn;J/_zFWpm;v1aԈ^U `vķl8 ؞={TΨ,ßn]g]_d$IXPk777;Jٕb5nwww739!@#;|R7%޺ukh[pF;'ŋYlG/xDaX(v EYl/P1fD?|F~`&*B@"k[ V_z[H;^6wI#! B@kH gm|4G׭Su..zF_q_0ۙPUΞO(>[.-juVRXݹsX3gNk6a3stVJ,ȯԩs8iB%7`)_Ĭ H?GϮoٚ IZTQLX `q̣cǎvکPyz<RżEpw1`^! A@z%͛T4fds۷)vh׫ 6R$ObXT"\$|DCWvR4H4μrͻ/ʋ|jͬn޺k,rCE\i;9s9;g^h*vj>?uIa:2E ϡY7-[!eUrGn"~YÇ@-|ߺu-U`?sNڲe ! Q(+Wf$fxd\pGuAݐ!C2.^MݎRpa!;w-jb Y, ¢oB Qȑ)W\TT)޾-ŋǶ?F3 fHB@#`:1#ܦqftGE<9h 6XwbNG@w4z2G"B@!b $RQ"6$,\ûaݢws{= ?~{QK0Yo7Gqǎ-KGYX{6 p˲͛ƺ+jΊ3ϝH*x(Shm=| iT;V(WswϙuɶS{#ᩭ`q|ܩٹ됱Y c=]V4o_~KE }=-`-I~G:Xn#7*JɌM\JW$S=|36۸r6Oht1VUoClG2ӆ(8\3}l5a4QɶKmQhns% reӦMU+Wd-A\'ҮhtX K.!z4iXІM ,mPtT;"][`,d̶5RyO7l؀&FDXHn7-Zhwu "}„ ~(nj1xEf炷!|}7Cc)]U޼ePjV2dH\u ;oy7GbԢe/~~K{ݺ /_S5mVvjvB0W֘tï;l}8s":u^B~VČs;z 8"{dɒN%dF6#Ht3fyPtmLb8rZjްaC:ڵ}*T(Nrj3ZJ*S GɓHN D{t,v1 5J@<8!ߺ*UJQ|񭛱<fa|Ǭ/=.9s$xcs>VB@!4 H{|dB@! +zϷ/Yd/xfoT"_ ,HHf~~'ĥh4W,*"5P׺U%EeJ[&Gԍt%4zJeQoth*M+?qt=լQH!RBju%߳sEԾn}q<]%x:j׶=}͝e˦ԵK ʗ7yźjq2 WE:4qo6fΜ?#'o1[c=KS_[wDdMנS'6Rhw,=t\W@;lxCy`|1+$mڴnj Iw-tXut

f]c"cu! p?;H h]5N+B@8$>{OSQɭOkur͛7k׮)KvELJdN*C:wrףGO l'5KzݻE/HpzYِM#!|ūOD-ܬfҼUp.)Ӫ䘑"E47: ~c_xŜ^DĬ]֩Dx+~덫uglnoUڶiDnj+8?z6mreK8*sXn?ѓ'(bW432eRھmϟJWޠdI׿2bZq[S*B@oN@w}"s̬|@;^B (OGgw۷-YFbT! B =!1uH lNƴ W*5O_A@lôwQcȨD>}"s,!.\8m駟8… >~N:EK,a{}={ٳg9,O8Wnᮏ ƍgu!#Z@R,ؙjp#ͤB@! B%W7esiӗ-9 Jf G*6eWS-+7w>O}}~n՛[S3iæȽbd/Š֮FUO>s2Ν,o!:ujfƧçАaiߩiF@uwdQH u=uG~FwK>wHnZpa|Kٳܳg2 m^9rd2dUT{$Eեx~A G[jE!֑Ju,udz! B@! ,vwq V)T+J= _ ۶,PQp(UڢԤY7J:9]KeO?1h:dQzKkLv[DC:y 5nT#w>D ufq衵\dRWb;ym%N~=UܜZI්2q|*\(7' /mL;)B@_[hA֭㇆J%KFcǎʕ+*/O>, 6gΜj}CԸqcXVVLV5"Xud|1/-P„ #Ggy ! B@! #ܝH}׬d^ ĥFәh ,[[%~Z8 GcDws3n-SBl~X=5/Z$nԱ)[~T\ǁ(+WnCyZX: ߉ CWl0EQI=E[jѣGU:)ԉMl%K! #ϟS*FE5.ٳgjLnXݹsgwaG?ʔ)c㟕IX*\GX ݻ7_k^(jԨX/&xJVRX:2]/}k˷amQ] ɓ'{~kժłNօB@! N~gwp)SCl׵BbSrd("ͥSfn;Ub7T ]῞%K:MbuRX>^=~w<@iѲx9y=\fT|U?Rh^!C$^Vx޽}q2ĉry-ɓ')ZhMzj3p@R-GXB夸`ذaK=c rם"5|4:Jk\| h۶m놲Jw(ւs1b3DB/?dȘ1#-Zȋɏ@E'1 ̀pUHB@H3'NP][rΟ?5*P(;-[Fʑ#LKrugLо:- wK[ݒ#/^Ylc=JHy)b^.Y4Ժ Z?IE΍{tG?.] WlMB[lÇ-%??ԣG>?K"ۗӇu|i]K&u13rR*Lj,oqISpDʙ;v: >{޾}*"skRMFr|Ѥ Ç审R8v'!a邨nmXQeve"3"f͚E-[ !ЬYӐݨociU/q:ur>;qӮF'{5ӥKIܷXɮ_E:2%OKOڵ< sWxg?nL_ڴy7ܪ7'5PnX?w <ƎMf{8/{˟bK:|*uŌJ,Hv,+X 1<= .60+⨽y Dc/߿-9Fn(S Kد<|PwG` *DmZ£ iӦlۂ:1cr?ƈƁ%<p ֢~Hzx1"l|y251][褣ؼO[!zYxru(an]*U?ܵϻnc}31wuwz@>f1Žz`qhݺuDk_cog=z4ϜDp_ڭ[7Az! Dpw{(G5@/P 'ܾ6JxxG[epGEvs8W6O쵎ht[>CuwTgmGuٶy&l0ޫ)DX'B@8Fӝs,^>E͚5D2x{4~~Μ9,!=w|)uu6O؍/j_9uV;w.a & jJw%/>K+^*R H֥-1"-'a*`ԹS3]x+7hᆪ7n, B B ֬%[ٖUƨTJ@s%;w0C+둜fL2 z;fM0 H>ҠAqY:)uԜCA (sN/^`ˇ{SNj6C'Or_|7?֯_YYca;1^v%"kK<'BoDp7GI]4.I%<͒% .,[t LfŜLUR$D$ zhтΟ?o/P;ze )" QN(ʆPl-9sF_<6.ǗPDwTHoGB IzM:.IP$%2=(~ߘ^,fP(hİ^6/NVI@\X E^jQ`+x;"Z1OkΔ)mܧN[̹Dpw]k[R= hvCF@lvB5v2ǦSʑ#GTw{+Vpm]6 +W{6$njI\KA"Xb;?DNYpÂ;fGBlǃ(0)DpvIB ot_OGo޼:M^T.! 'TEY \0 @͑+DFsdqB~nܴiGQdɸ|a] "V2/X4g$sVT=vYL;1xꪂ/ZDKyqmו>|B3g-q{xVĈ͕Ñ)RTRW "!W^u{`<8o l{X!ׄ)xkCDɧI~B@";R-JHztoi _J?CGK;!  U DsD) <1/X "?wDCG9m4~x-L:|~  5uD{)B'}4yB:~^~~wšJKPOu/\39rƎMGv ĥjUKS.-Ԧ]_rW6&(ϟU[(ۭ4qo\/_"I.^VI'C'Tr[j K'7/Q xofIOUbm9tzΫ,Y"jVB- DvRʙ#3ave0`teJ6%{?y֮FeV-=1v9|?R K%2OiAjZzegqҧKEtPIaaEG#ʼ]=u[BjS %؊c;/MX"H/3Rq;6FKST~$H\ٯo5+ 8#&gF"me.^("k]t;3r *~A3 NXڽ{wJ>='MްaC}91͆82AkFi0C+r}؎|gΜ68&M{B@O@w"p>Z(ԶDL @Xdu  ;vRJaBHG/T:i.HeY_j! !^ *VE52;k ߢ-htu0h%Kdd9I`԰q2V!Cj:l yh 3fL3w%B޽c9{ɈX̟cY7w٤qu#a͛wx,nj=O:~+ofeԳװ3&T?oWV h߹~Ԯ۞zIÇԺm1UrR ܹJDyZ_ h>X`q-V5eΜVْ ISGLiߜ*/7EBS$6 Ŗ{ʢ%=p}lV%Ʈ߰N*θ=^~Ccf1<,9( x ,\M7okr妚ݕF%@GuO^i|-[J%W=R;N̼yh<8DbT Cp_x1'_.Hʚ?~ڻwzw u` f4]vdn,ӧC^>)S䙕8ze  lL;B@!dKwB@K ߿Wa/\::qG:ዞXQh`?SDA͐!? _/e>[QD ft}zdp$0|(ԵKs9CTv˓:urZtmZ7I0JΓwL%>AvA ͢;&/6pUdxџ?I9TVA&ͣuR6 W9VMd؈x/_ɍN[đKRbn}Պ)TW4˔kd,{b;"'믢Cqybu=*esAlпmʗ/;1i|Ƀڵ[ԭN {R%H]}!*ilh7Sӥ;X9eR#XgfrԾoj sg6mb{s\ Jv8QH[H !';7NCdG2PgZBFw\xPJb?v Dd;vS6oޜB/mڴ-[p?={9 hׯ_gʕ++[؟8N>B@`G;`wɮ pw-_]Kӕ8@`u4uؼ8vB ( *:|1]t._lA۷,S#KT;")RDNnvfVuGd.]ݰqw@KD~G\ J"yucYLQ 62GKZmvXyo_v/\e^n]rb!reu?lQ@yUETbQ(|;4lPw2]pz4kc>?~"D߽{ZYWf[\#v2Dv!TAʎ;3>*Ur߳}ǍV׆;9 ke/UT1uV)KzQ>|KH9/E! ?ܝ[urҝB@pML%2=@ DClr+1-HٴywN uX+ a;XgT/ XfIG]4t5_U7%P%|eINpW ?El.lJ ,i":to:S2*QOt~1fMj?e\>EY߬TQHҠ~Lv}Ɗ%RHz*u$F㒆B@`C@w'ւ'VB@`@@`&%o@e; Zg Sթߞ+} o$8h"8@G`oHGl.)R$Qћ9A)vV~(P8e/h4m[7R▇ s?Ceвi̥Z%B'߾} Wɒ%$Wdۜz&-{{Mhܘ~ k<9|Q&OY@{p/yT]~l9+_JT eC;ly"Fg9x'ҲǙ瑾B@! wPwAtmr]c]͋LKDssTAq  ܨH͑=3ݹ{!$L^>)=U,_SYy G*~ӱ,C؞8q|jVW\ERXׯ&a^J4jLeprm!pC\0#{S?S&])yĴ1^J Z c y T92;wpՃO]#R|rÝrLժV/it]SCT1kl=|xN_jEq! Bku_㄀B 1=W2R! @h>u0u1QHץ  <#5F-G_%C ~N|vڂO}޼uWy?./\~TX6o~u)X0'MZEiP 0O-v2s ! B.$ϟ=]|}p!pr)rBbF7GKdKB@8@s:TN%ayʔI:߫D33u3uЄnܸs2wzЋ(R|/Yta*V4#j*9csO,ac>=gJ.!›~iM(~x#f ;sR% ҍ842A"nǭi-57SHjTGR6*sfP p{A٫q^[o=V)M~z߷O{~K{c{tuS~E/NB@! 0npJ`p_b%9"C1*2 1][$L^&IDYo\&!BxË|pڴ)̛;MT>z\SDAWŋڋ{U[u lgr(tG Dy-[sy*Sz>NJdBsY3-cVyN! B@."PߴNjת@R%/_еk×8-K /툈"*[b9B= "ӵp.bzPɸ9S.׮V/ݖ!WYGX趲B@! <Dpw{#ܗ. mܴ*VnYӇQ9U`N 0DuL@YݎŗѫBNLG۷Ǐ.a(QX#ELwzX!B EUWXN&B@! ^8XS%sBQsQmts}w! B@! N~-e$ɀ؝uBЯ_ubҕy|!e`lE2ұB@! B@!`68Jwgtn_\[&tn-t'7AN! B@%0P0bYW dB{vX;9MeLKۏl0a8`֋N~C嗰P*Y$ZL5*#쓮#ELIDV! B@!ls-^WoRɈ0Dpw!.F?pʡntC%B@! C9t4AJte>UByiqZʗx"@TO"9pw2Pvg?lҵp>"htuyq{.= ! KO.\Xv# ! Q"GL1cQ, ):>]G|K{}g%=722"{bPt(@`!0h$2uq`NO쵙4IB2Is!<:U~@`'#B@?м;D"ܽyܿh-&[l?zraB HH<9'3]rIB@@ذou,B@N";}%ɀ؝uׯD4xd1),X !CJ*rI:޾}K+V4F 40]rMjڴ)m_&`|E]u-үpSSn(VXRB@! ]fb^"ω޿J3g-#DܵoۘҧO38+ݻƺ hn^Fy,= ! FK ́"EXԹbmaHay]X MB@!:KwN$ ahg~]t-! B@! B@?ݏ=;I(o*K .xڧw ='/s羗:VdJ7Kd B@! B@!  ?d.N"]FϞgФ 6ZxTݽ>[;?]{[>Tºi |Ȝ9s/WQtI0W>B@! B@!D<] ['M矯MGŋW4z,0q}^3u%#ڵ[ic*htuLwmB@! B@! NX[H;;_%x!?ƌE^uUD<^)QDR.㇏t-ʕ+jEtcB@! B@! l F"U Ө13 62*ZLO Q< "8}ʑ#5mUC~B@! B@! pw2P'tһwQ˯psy19s <ݱ7%OTMu4btBbTgSd = 1>B@! B@! 3 L6KPӧϔ(i~z{{^eْ"[C1mb#uЩKǍGӧ2EVB@! B@! @'}bu{}?zu+"EH*ӱ!&kSt`J{W*^,?_{z'B@! B@! pw1zpw1`wkZM_`kf5pzVȚ]Bywg:rsv>DMmHs#ͤB@! Et#tPTJi֬J_cQg{*Y{Ҥ )jֻ6ûGBI$snq nȇ̊|Â0@"B@Fwi/G"} ,gϞdIGBiԯhSigE L0T`.~^ퟔ~{ç/VGyl޽+6HB@!T7sh-7i֝޼y9sJvbE9| ȁqĤn]ZP(lX},թמg>Gƌ6}CWNQpN! RRoD/oެ/Z(\( `yh@δgrz4u!!(;wH! B Д ]:GRCE^.=t.B@E@IDAT!{{f!;׫Bz q֤~LUo<ʻw/\E]B@! JJR*r=߼yG/Кی{/Q]X n昭 CB@! p@2E(E$FS3ug?2%3:u-_OxGTcܺuOJ'N_:xg3a„V-u! %v:7o>Tԣ_Wm] {tKNTFEn~8Tw3YP"(n9Y`!_U#Y Id,pȆB@!  I5g7lIOӧ/P]i^"-?PlmE}&59RZV6O:OY(/^7N$IcJ XpѼF6ƺw+Hj]RHLݥ! @ض/ڷs?m9ҫ~)_rj^%} K_H1N~'$@]؝[Z{1Tɂ.eʤTP.ڻ(RvBD! B@![UQ.PpG#{ x7[7}_)SzZܸq۾߸e?uq޷bd?U2(B@&@nj޶]]Zu3'ϪUˇ`ѯ])!eܰ v*PN?ne)%ܝXG;{7m UqrҝB@! @H!%Jd ܽestI4^1ӭ[x#|Eѵ^oϷ˔){={A1bxMw)'ȧQ3~I%kרa5o F+Rۼ1bPt(MˈO>ѢEĉSL~ޝG7L,+VLozY{={ŎJ.MpXf =J(~'$Qo)zTjzr]!@ IDI{>u `Ђq?yF=DaÆu i']vHRqUL"B@! J zơ?3֝N:pD.sbl'MN8;w$Ϙ\,YO?e'чo14e@.!{EyafV4dUT im~\7o6rS3!,Y&Me/_/_N"x{-5ocʖ-K ,Pkbr;GSF ozѲe˸ȑ#+#*ݝV^͂A̙Ep 6̢8Il='WɵuC?ݗ, K;n֫[Nj3IB@! @T˓޽Ej<ܹk}Z'޺ϗV̛g,/_k(6\6oLժUS3ꦼ+\rY5o<~ۇ %Vօq%؟b4f喕X 5cKDpw;?r-PԨ_pexB@! @hQ'ȨppSu7׫LcGU?flei@_h%ҍ 'I\]W{Y\1ֵ=Z\ҧOEߴyX>u0Uޒ.\ʯ+~Jժ1Wuؔ@ȶ.xٵka͍mNj׮M߷opRV؝:ujZpʍJ/EK֍[tha/pZO:+R0fAT}EFR$Vq?N˨+^(EU̥oE@w'wVq q.'EWT&hpFg^nBfϞMOOj]j$N=őBy}z:d̘Qm ٳg)RD$Ih0'O\>|<Yx 93gdM<:G {xcoQ޽2bcɓ'z0s)/x2d[q~=elǛ[~`_b5w{u@ͥzj6s|R6o*k^uoeUJ e tT EY'O,v6 b® )/Oɽ/9z;$V 8V?溳S11;w({8"F$@$@a C ’lOMmm3Dk5u{el _B={LGԩS>#$ 6x+yn;<׭|!'O6a~Rd^͛7+^z!0;*0c80aw=_Q]G&aIZ2e$?(7Oa`^vٰ2"n^Փ AyU^#4Õgw2eu}yFvء͝g{fnr@C_H$dТK.2| sZG\wgjM^^^r5U o}O!A֍2Nt9{ű@!H!e\ Nw:     `E{l6m={V`FG6",ݻxSy a„ʛ]u-x#) Hj;:ᅏ𜧐@X 7^\I&J)EKz_m\MwWǐ2V@xK$@$@$@$@$@$a $JHq_Y :>f`*ˉ'|($\{|eq.Wqƨ!rYg? @$@w7?O>qTO$@$@$@$@$Q ܼyGkWTM -[HqŧjuN㊕S)UJ\ \.^+/_ k^uv vhݻe7BݿA۶Z(…ׅ$@$@$@$4z;,p[IHHHH ,qLiѪ֌ɜd~iE?pݍzg,`~s@kaj W1nή!p=?+iIzmeLE[nU[   z_=PD@$@$@$@$@йrf"Eڬ7.Q4)_JʔldE%${L3!&wӌ{əsڹ|5)Km(K'Oɟ)OGNʪg% 8Cwgh9W}B$@$@$@$@$AP)y,Yҫ]ϛ;~Ƞ} !kn)n\CjլhwwI-̜~7o.5W[>SXI$@$@$@`HGI9: =n$@$@$@$@$Z7G"WI4*(Xf߿&K|ު_ԅĻ귲k_\|+'NS&O;bV#fy7߿׾B@<I+_tE5bܒ+oeiݶ<|XoVW]֤7.uݺ`~̉$.\5[ 4lItõq;v[Wj2WŋWe" k\,Yޘ{=|䔪>s8k%NmZGbǎ%#GV=Gc5,ޓ =܃A4;Ί=IHHHH 8^z-%)ȑY 򗊯wY5CʗJMQF+U'N,ɦ^r冬]U6l!kWϔ7oʡ'wGScO>Wq£E*kyT߽w_֬ݪtD^/TձbЛ5C)\2MJN:/ǎֳMvl](SP}u=61Ƣ9Y;(=M4oo䰶"_RaX?(o[0qt4P3[ځAUeنWСJ(ΓZˠ߫gbY|z ҭp=sAԩ b&^5/x>\߲eẊ% .]n    fpw3`'    8qNKYD5hVQH>om U\"mZ7RaEReH-J`{Hww~퉗]1cFW8Ƣ_?gE7Lj7y<@s ̟T爇oW)3_ X= =]L.Ju$@$@$@$@$ ~~Ԓ`>;5{(-OQo0m.HM`-nbnQ @#pUix\e=[9S.ܹڐ {߾}uk eJɓ~cͶPðʋ|R"|joEˬf imw2paliݶ[|ig}&jV3vT3QWg^,yrgM$Ax]/8"HnMaS*C;ԬQPݶMceǡUH&Z 5+I>~O'+}3kɾ@;>u|iq 3xD5 Qzj*9#C-ߨuHlhx@HHHBK)$@$@$@$@$@!@ ac"A\wK4)$IDjKɩS:2;alG8qb[^lUqOy,]P<&su}Oղϟ~mkwEu;6++ uxJ$3eXGyc; 'KoSNV/'7nQ^YW,6ѻWxϚ1BN,]ьݟHUrZVp~ue_w{o[kYzuIHHHhpw1^kw:     @xyy >f`\*UFGBDMM===lk̟2=%|\)R%|4ȑّqtCu @h#@Wz WGo6SfI9Α}FΟ:)o\.$H L7IHHHHHH']\ H"(UȐ QFʖS~օGK6?8-[G-0ͅ5aHHH xԪ⽧I(x8HHHB@&Bs X:wtlm޾U+Ļ`9 N(5*,03~m\\-(6XA$@$@$,K?q B8HHH @Pb ѢG~eɳ2`/1ץKݬ :$o(]ȐS$G<>_3P @&@.Lu$@\y4nV2}Md( [~'Tx@8sv7ޫq2zɖ3xzyIf姥+%j4$yٺHHHHHHH"4ipw3`'W!s[l٥VFٳՋ}XSF^H46RxWUOuJ$@$@$@$@$@$@$@Lkw: J>kב'/\8ogj1*+4hJ1KpƚL$@$@$@$@$@$@$ ϟnL$@@W4i7]h{Ko)g#هHHHHHHH bϙ.Ju$@$`Er7FOqF3֙yؗHHHHHHH |`hGCωI0׮JFDY#.߼mwn$3zGgx% $gi    'Iw: Iw ar-UtFs]!/_o-ՓJϮ;-8c-HHxj [Ed @'@w?Bk'x# @`ۆu{&)Rn_,4Ũ(gOW/_ʔäˠ!F; cu*zrpX  !=ztv|&֑ B!elٟHHHHJ f̘RF 8qi,>M4 36ȓr 4ÀHHHH@PAg19oB uJ(Q^O޼y%sN    ?4;.Ju$@$@$@$@N(Vxyy95SJ,vŋ'N//N#    @|uR# GoX6nؿf5H]8aY9<ԩw̥ '.~}Ny-Z\>uoQ +Uď7T-?]՚ƍ-eJ UbHHHO:kw8+-[V<<<ݻR3B$@$@$@$t4ݑkYc߹sO*y7S%Sƽ ·oSM ^yHA!TѦ]?q㎚m7!4cӜ>>'N>СCnQOHHHH@okw5̡c񖺵͜xX _˪՛eZ#yVuZٷScI^)K{˓'@b9rJ@{ HZx:56|5d A^Uo3ųgjU$g?mF?|hiH)=O)|e2˷d0 9w] M wl'CuVszB7C(4,;[n߶a]Cj. oT]|f3[^b\!ϗ2ooy e, DXQk֭+QDwWy̙3     qVA=_x)KmP<"E$kUO> }r}Hl,!9xE_V >c+ӦucC7F~]N.JKȈa%}&|Iiٺ?~"=OղҧW[ɑ#?>- :[~7FY:vFi/Qqae܄91Kdڔ!OT]߻+[׭ˬ W>t_8sZh 2bMk/IᒥԽp6d'a$r;w1_w|+vlт;7 (dVY{rSC.߿{PRekjA9t:ȸv̙4ѭ @'ƍ-Mu$58OP"|SNĀ] @on=RJnMsrztiҸ^%_˯P_bIq>o1ru A<}jwl4_r\0?m}˜ݮ |Ur.:yX*;T]>:Lid͚A֭%.? ܹ2v,sqBnCw.U+$7=;HUT=aÇ=e^7xȄqrzC zh*/$f;u>L7~|8~*+ ~UBr/`UkH3ZqzY'SzWgL7j"}փZV]Kv<1= ?L&w$NH".N!> ;/[Ϗ*UhW ߛHHHH  0awsg*BD,|Uݗ.]S^؅ 1W:OϤЮWxyyb@}7Ћ2h@'i٢q_"qB=cGN3,z 7n۶i,~g7xW.%i3SǏ-{3)o{ ݐoBѤL]HyFxc9~f7ȯsI̘ 5A~qL8`3l߲@g&KR@.?KBaN>2nxnjK^j1o\"}8|*RE_,QBo\/5v8<e?uJV)jнkpdzlVˠ!ƺ۫O,QB%Ǣ7$@$ZEj9nv= ,(2d0@IDATYA2rHnщ7$@$@$@$@èhmpD>ql`eNZ|q'qcKrŌݗ<޳4sfxƺ.\*wcŊF5TRF}};0<7}v$'Q!nl7:hu/I˗~صáMjX7uJ&>5_??iҘr~1_nkK󘯡y/Y 58?ĕM ?/~TF "`mTpJLCp}nHHHB nTc$̛ v6t/YP,F ƢL";ryA>FK-j?/ fM/cDzۯU|T=ɞ=jH!a,br { *Z۪l^w횕T/~8:pYG<}X +I.i˭ҹsʺz 竘4679Uvv +]z?; ڣUsu#yܼ!ejoz$@$5jH1ThZBZn2v dɢNjÆ U0!.HHHB!qCpwnrCc g,I *6kj} u2KߣoܸcxPb1cVlɣKZH K׍~R%7օ^)xO.'3=a{؄B;yh T ͛UHqV>Wa/V$M'1ήYH~=SN/WCr?T}OSij5{W-_%ɔ-e+Wh$rR| mMaׯ%]t1cƠk.y}o_~jgϞ/5Xl!17`t;who)Ɖ`]:n=>  AgH-^Ͳin2>`80h1/BW9E3<7Aоw_* w;Yd<{D2f\TXYQmksewϧ~C7uQ"񝯚b;IIy lZ)#ƕXdQ?*C&OGQÍKy -Ecj-JVehՌb6J<-FzJ[\ݱ?0ЩZLAbh^7{ d3@^(< YJq-to4V|4 OIT-ְ=y YEK$qRĬe뵣μ>{}\?xΏ>KL*ϝϴp+)Ӥ(Q`z )ZB`׌Oyid`{x8p@*V( r}4V[SkpxR~})UL[@ݴiS|[o…q\{ -9swj'   Jw?x-jHsSRZ5+i1k^CE_Ek`}nVٲel2/_çXݽalGwRAiGVU0Qx1:L/U 䔦Ve$n]f*Oi43h|lظC64WLn%;={ʾ}Լ_}:DTE\koo␷o^78Ѵ7cpp>}zY]a?~vS%hIVktǁ#ңG9z#]هH ֬YC{0r8 @%pu>`d@YekNZ;N㈽M#whH| H,KKLw%Hk,}ZNŏ .](/˗ "ty@/B y+/^kk^kPbycboܸQŠ;$Ն a-_|ҿu8  z]fs@H䋃 6a#gdʕ+hCH˖-'>3:@$lwoߝ`+ ޿{V#Ft;#JN\vv=վ޿3\%@-`Մ7wxNx^y{P'2GUĀ_0t>L pPĎK֯-: u:PYw62~\c>4{~N rEs2`mXjyŲh M5ҭ0=g Yr,}{3(mQmѪzk??\[ʕ-Fa@9dXf qsaEbOx^ 0"#ȗ_~i)S27 ?(;glF]HKNg.s2i$>{lws{>}TW\qHÆ'NX s|R9)l\i| dHH1?V;wVFm۶ 0+^7m7UWb#ĉA^P&xn6 /|pТlβa0v]_„ k`OpAH#g /Z;}IHHHH"]=]>ܩ9E ?b\`}RJ.'0D+VL,nǁ C! L1^f^o=F7z".zh$|2FX <.8L@y # @ogϞ0;cO0(>Vӂߵ_f ^Ugd      qA[Xpw  ˆ{ 1cF6'zJD~:$DS׺=?WUvğGq=N8*< u!< gaFLxoG!]IX~agynU%    {w.TJ$@$ C=zdիW*ʕS1͍0H#Y?~,;vTUUT172B aȑ#mC QK(ڑ]{%VTtid˼0$#.+ŝYVbMՏZn]?qFܹ33ؗHHHH"&nAsnߵ?>)'   Lȑ#k. 7 ^zp*hCJܞ6mJSL)ݺu+WJTUV­ .ɍ6 ~ԨQ0jۋ:8s :towLoٲt$A7o::bosU胡m۶U!Tp;wneFO5jX#«)SF@LdϞ=65;<;D޿oCF I&P;8[ٳgаaC}1u!O|-8 \ `A!      p@!  G@7/[+cxLco}W\g <>hq6LK1dĈ2l0AWāwuر.mϕQ bð/xx`gΜY㮌ᎹyŋW*Pt]8|,?>J*%۷oWoNċϿF=tsɒ%%GF= $@$@$@$@$@$`@${sqKj" ്.-Wola΀%׍z[@W!Y7749>N>-P:$[EZ 9"/Vo78{n!   &0{\ɟh:-\no}.K~Yf̃?sA0/7dظ/o}_[6v۾=}f̃Y>x$UKUG7y1իF; $1 fpw3`' N+H(H$I$gΜ } ghѢy8" -po q- dQ`sAɗ//qQInCt/_VI{] prD46QDN!.LIuy^?oA<gL$@$2IeL'/\VomZEj7iwϟ=]~BUjTqƑ(U(eU=CRqg~&̈́ bĈn2 |44u w:  9&~Dz+4i*̙ԘWo۶mn}#~ .T͈WpE8A8_@:\ݶi&ݻj'N|6fŊY`= ;SӦM0SN)cȑ#[3gfJ{x$ I #WvwM*5*!cUu+7kp]YaBJdIَ5W|W>CzJev]>Mhpw-Pn&Փ @ w ezycCٳ,j؝;wm۶{nQFs+JyC$@$@a:@,%˕ЌﵥLR!9 ~Wѽb 8 T^IF{6SU3VL[ 5@,FN &;VʽS.rMUwi?>?MKKCO7|jWaGȬ)sUyDF_]k$YJˈCw~2o/{_bGw{TQgmp{0`r( b ^vMG78p_{@ܹұcG5ٳgadcZVĨQyO$@$@$`EޛmV$-^G4%9rLsX+ܷljQxaI(cqo;;Q|f?TX8[eƀ{HXo%wo}P<¨SڗZ^?RuzӼwqӁWp Pb x?q. ĕlgĉݍ[Ǐ`)G<<{J'   H:dϞ]%DN4nX~AhȡC܃qo$EUvGL o޼Q?~,M̺01o޼)ɓ'i:<F .ȕ+W$UT9sfQ&xȥ9r}! }{@zF$@3&RxiT!gae6ߪbCӌT孊hZq{HR[E;8آy$ZmPOH0#Q<7W\di !Sz0M9k=|sߧVI!ܷ$]@@׭.wqUK((~PBFʑ#lkV͐%BhfNC$@$@$@#`A?`MnpcbwY֮]k1 Sr'M4ƽ^@݌3ӧ~#潽P=9sTC4m\tIV"౨ lH<כǎN:)?c4MJ*I.]?7c7aA֑ @D'py3zGxW'6yݹO3Svܦ[T[*9y\pYՏ<\j5a<.$ ,PYn-0〡dɒ*in֭Ⱦ}f͚bo<ކh֬m3TԸ~s *!L@zZrKHܿfׯ,z7GXV<16;:9 b[vi~?׊.&qU*R]hfc>#),ϟ@ mUi{.uAb8=O bì(Q"=l 9p]Uݾ}W6.Ǐs?6aE7ԩGή\ɓ'ݮ1u!M1ǍiH  fkazƍH?G.ǏxU`{10l$0j(P .vt9ȭZR^0Ϟ=[֯{}ҤIR`A)_Q^=[}ٲev +VN0@嗲pB5I&_ٱcJ$IԫQC-Z(9۵kqUIk'>Yf ^xwMڵk+]Ǘm۶ѣ*~n:u቞lc``ֽڋ/B}3ӧʕKo8ٳ!$Ņ; IpgVpq, /ǕSr { ͣchSn6T <PHHHNҊ+`{տCpbxX` ۫WZ\u-o%(w~̙#~R =_UbcC}CxuAu>q<_hQ҇+2qD5ѾLR2c eGHH_ ЏCs~رC*Wt0B7^`o3 # ߾} E?I̘1qٟ)S&80QL$@$x6P)#&/rpN9w#Zrxo\I%.<2hQ ~1ߤ~g6lûZW5I3f/##4]46#}?p<@;2\8x\nݺ+%K%<={Y.].<ؕUNch߾r}ɜ)dɒC&ƍ;riI"+N@c_jl?~VDxj²e˨yE hXkC"2ehw;>#E )}0IHMy"2̜4)"I (t#lPƺz 0@#|IpSE tjLzUz5x OuxX4iRe|޴p/wϟC 28B$7=dbalו@ӦMa!f`9^.x#< gϞ+E>]Uj_p8!lO><A8]خ#qj5dzq %..gR A6ۈ+DOסk3`;BċOtO+jJJ(OOőɐ1MC%F,X ν+֊d*܃Jq Oeo޼cJx ylЌZ^k/(5%t隴`D'͓'L4DrȬeUҩc=苬}%ӧ 7+~6YM%{dL#Ft)V4L2pikh^Loq(ĉKnRS ,m1 ͓C[4/&:(?p 4^%++a]$Ipa@$I%s$yY b``EHxEcALn=TexhoMb=p(H8x^ .B@a@ܗ/_A]UnxToC]\ 0=Ŏ[".=>fA8]"E yᡎġ nhӧOU7iҤ hM  Si7WqD2W]Hp+ @𳩜NRlq7x9w-oڗXBQ );B\Z|j}0ȣ;w%л6!y\8WŜ5g wgс7 ܿA#KtF\}2'KDɫ5i6q=ב#'k5eцwh|}Tԯ.732eJ/ժVw: c:7H g9p6m;w6)k54|0lñ>sb{߳k,+]1F|6}yW밈AizǏ]4&zQLx췠>|jڶn6  xK*_T̙3sܓ%8Zp_b=Zy 6m~Gƿ+msx\->>}Zm<>z N:eʙ3Zߺsa(Uc: @L'OoZmetB2)S'.m6ODCIFҢzT,S[_[}̫ݚEz*BMRKy s7 [kEf9G928~̇}M],F aN>o!ٻ7abYP^CnWUּ^0iÆt5v);jw ibjV&e@$@$@$@akt3g!d[l1w9۴(P,2v3+W6vA (?bGC؞={zOU4߾}.Fv_Nܹx޽ۘ@ĵkdÆ g=jF{s`YWHH &F֯)sW|%,CFݟ[VFDŽ0Ufxɯ۵F/4`ZlO.LuܴMU-YLx! w?sO-`f3&*?6 5q$Ϝ9n]H4t kY/kڸL:?~;p\5߾=)ssΛ4 6&|. Lfy*AΦ`bQLܘ A$@$@$@]&c1c8'tD+ RarVk9ȴ If@6&ud߿_mի1= v Ӑnr(Na_|xϝ;W_.dkQ\ 0.ʌ;VS;w={۪W^Bټy  _|C ď!C}ĀC"Om޼YF7k,5;`~zUGD\IH  o@4iF7#T{pڄZqMkc7o)C`X: ƍS mLxr֧Fe}XNY*ZJ5j *D9s@/ZzbO`b6 Ļ*3Ǹ]N$@ѝ@ d?DY?QuS[m<+t!ƍ#& 0m8~qޭqAl }(!#=o޼·'?yw;&u˗(p/2;v|ZG{!׭[װlP&AJ뭷;DuqHHH f|%w?m>3͓sjѬY3ի廵qkek֯ Kx]b[ODܹSEwd?wrem-[7~$^ӻ  4n@2g+kxpW o?Vb\&   eGU]S*UTuիDc;|17w# ]CLf:uz]|YlYEz˖UGL:+ %0cHwmJ=/oi2>6^DPM"4?.2=xWHHHb 2^U\Y\e3Ev!o1 xXȱ?x=wWY+"w+$@$@$@$h)cd@n¸AF6b:9{OYDXh6i_UM$FI)[au]Ș1,d+l[T⍉%Jד_L >n=v34\(9,ȝ;w"< W3?6|1C̙UFz%3g-۷b66wzu:lO4~3I=^*ҷ;a'WHHHH|"D'M1,L$@$@Ns9x4Л@ @ϗhclM+qV稉<.ǎY2J⸭gϮvm %YR",sOs;H~Ya?e4iR؏ߏ;-g +L~!}Z%#A$@$@$@$1BM A#.]Z   @@;+ܸ~Cm5h{Qt0 8X +Wp_xٳgV/_*H )O|9eIHHHB/o>n!  hB }M;F@IDATeЁCѤGѳ%˖)GαWPp zZY @L';vlYnqL@@Ph vHHHHHHHHHHg}FgKf{Ž$@$@$@$@$@$@$@$@$@$]PpgHHHHHHHHHw/sճ:          %@~IHHHHHHHHH @vDf0=\JvHHH"LSN A HBxQB$@$@$@QL/3 Փ @Ȗ-y[.̊,{tHL~Gd l2Iբ<% 8iY.'Mݿ|9v<3ҰAuTcd̘V2dH#>Q]z^RehU5yeϞ}Rp59xH6 :^&L%w5 wp8S^r |Om*P \lkn3ݚMm9}΋r졲O* v!C셀cnQKQjSJ}:˽mނ}/ W$sU;ukgmtWGm9(1yOd/jh*};$[Lj I&LyxO~ċW5#Ւs)YKڕy>|h>M̘ 5G̲f(TmaElۍ${̲f,5Ȁ Mw6-dCܟ4aYƵSAF. xEW/872ay,g2>!7nܔĉClG`?˩Rp9oys˗ödЀL'Λ7|raqs'T;ڵmbnTܼyKar} I2a!g,u,򿌌e¸AJl~|: v^=ګhw'*pGb;vhӧd؎Tkרom5ҥ۩r?UJX+.VoCɔE͒ES HHHHHHHH|!@Z^u#3} 2tRJ ^&}w|כ6z*y/_N_`Px]5msc?#<>e)5 _s6bˌdɒHu_w={&ÊΫ_a;Vfj6;GJcrWh5K7\P۷,Q;vgg$edrɓ(         Lg>던%_Qv'{u0@vqTE2/Ȧ?O?_=(#GMmz=DU-k֌*}*3z]j"Вe D]ޔO? y7J.eCjƄ\}Jj/,v\pQmCζݳ,Z7CY\z}nL*˜3LL:qҗҢuW^+ ^}F QۿOFԫ[U Lw_رӪ3cN|u`1K1]>VbWL:t׷Xx葑~UCM)FO5LR~1 Tæ]jׅV <}]~@$@$@$@$@$@$@$@$@$@avpM9x蘼XjrbeRa)R,z~5D3(xȰ|E]ۦ*]ٛrޞoձ uhc##[׶͋Vo%{wVW^V٠Gn=>P}^F%:I;&IRJ(S&MJ|<]RH|U^@fDyÆe.H|/ ZkO:2i$$ ƗaLFLV۟{.^TG.] | ݮ|0Y\pǏSb=y򤊫5ެ $@$OcÆ `=)XaǕ=۷os6dɒIB3 _1[n͛(eʔ1B/4uT~>IvA9zjD"E +,Q۠(>{D-[ի!Iy'N~M͹Tzu9C-$@$@$@љwnTf̙U6o\2y֜X}.{z'N [CwZt 5hdC[ӫeWې5ǘX鎉EQ i Iu%ؿ@F_gϬ"zR*ƍ#p+6[׷mjLxS<~e:J<r` ?K2>n  طoTb%o_|>vĈ2j(Xj/;wnwm|{uOɟ?WY믿֭[S|GҽA^͛rP:'ԩSKTᅪy^0-;}sܳg:eNdĉNg0TA!>s/Jo|gz.]ȪU3gHL:\9soڴI^|EN$@$@$`Zm-I0ӦMVlV9o &Zo=oXrϖ-bp1%s}ޞ \25YDNKTyd J]VgX|/Rm& ,ѣGG +c֬Y^̘1RR%`VAȺ/_6~G%nݺ52TdO6-Lr(SvmdRYHHHvpidesclufV_uO ~հ`/׮ݐTi_PbS.f Xv K Ejh{ku_ƍqEy3%z\2?s5P@~E8"ڒ4iR5ؕ( YOg0:cӉ{_%HxJ착AYMر Fᅴ$0﷝;wYeB 2}tትe -(ۂI%΂<pNJ7.eK>3WB&Ǎ)2W[!_ٵBY[^ !+YΘ1CC+xʼnG7oEQhΜ9+k_rEc 0QD*S.GO?<ϟ?ߨQ#ɓ'C?WFpu?jSa/+ˮݻLMz'W\ҠA~xb8m۶x#P'ӤIĥ &Oe>U ,*l!ԩ#3f(A*χo\:$RJJ6m>:\-#qa5w 7)!mrqq.ZH`A|N+;*,;u0HV3sMׯC$TF4nX66k`_+}hҥKՓ0~4/m.a%K9@`{kX@s` |&yUk>T%x ˸.x 8ʕ+=0WP!kP?G݋>7nr^VxJO>n<V/_^=3rH5߉    #@=XLJv4W/fKԱI `[oEF^ƒ R~tҩ5B&vU2dPŒ.˗/WuEG!Z>|k֬iV ?6M>S%JZ@})%z|hܹMj:&D@>w aÁ@0 p}0,O3yd?~x=zLM4q[?Dy7rhsR~>7ŵ=Y_?K$2YV35>,_}U%Zx@X 8[lY%(QBYO^Z0,ҲeK%nz0gxoqbn|>Jڵk2demUbE^zɮ]:̙!~'3ֳgO^s|y|y߱c.{̙ 1d\h׮CÆ սWǏWT} څwj2c;O|j~ڴiUԩSwǎjlݺUp0P#}Do+ aPՅA޽{S   <"T1L<s   V7J8Ag Hk֬QY B<7!÷tㅬŪU~ߧOviE?p~hBdf" .]|!KŽQ@#cz /;C={ ĉ* aSk={V1h1 &2!#;Xw}@ :wD{ p}YfJ `wv1Ȁ]fqO8Gux@;!(N2Eq]1@V55W8V|>uiFU'KkI&6a^ "3ds{W?mQ.2gz/!2. &{ף~:qOwUǼƀ`c:>ΗϹ>ƗJ;[i5$i-\S< c\|fdSSkDʔ)4paAد[5Vw#XZOa.hB8'š?}a|(+|}zw㸍HHH qky,ĝ$@$@$@m5k"+R(! 2!u/ RB̆]} x!%:ϯl ZC?]`Df1b+{L|Z NjO:uR Z40`6Zs8HE_#ۄt;# Za  =D>'| db@/uU\<D 0'r6l55xI {Os e3 ["5}Qu{O7Ik2Fp'D='9A:}yl`lw`<\J'NT>6/Lo^Y︧}^zRdJ*)dslqX %} ;OZi#cݏr7o>D :w%'" OMw]dɔ͍y0˧W{;øHHHmh9Ǻ$@$@$ TTAl, Zpt^;D8d#"~@#ȾF@Ā0QY?Di\thd[ٵֺ^@[<큁,|vw39`d=[v&Xm8 z?1>we]氆\}{nu8p@:vTRXN"An6GTܑnu9C] 2LYLPι q|d#p+<ف'G|~CAW vA$@$@$9(R?Փ @@6)΍azv-B`,6*}} fdëv+Q= nK?5EQ4LeW~ȶǹ`X; w圷 Fu8CoןoUoO<b\sk=v/CFf5kѭTDrʹl=6{\5&u5r:Z\קv}_h?]΁ ^zg:h-OXpHH߿/ c{wU6+5ɓһwoL b&~Wz,!psXg Kڴi!𚇏9}4Lਲ਼g̘a!Y4/;fXȚ9{x#" q*[n&]D]k֬Q` 1q ֻwʏ3FeubBUJ1#ģ?P7E`h!>GK߬O Z½K9sFFpu_k 6;uđG2g0ᨯ\u2?pbbK-ZPB+=|.,XWk׮U$^>E`` b4W6;>;u3 k{O@ S&U6 &MT=yb ٍ|axw1߻\XS{QX^zp܃p5E '0S|q˗ F{+;>[x~>om-=C1&4@=~5߲e UB=1}=:uj= CYg6HHH(Ypwqf)Y ,8:tP,8,Ct²p Q ['ل ocذaJDžB:X[̚54b4MA]"?t|<HYe|];CrH! e܅xf*&صy4ΛOo]GV"D@wYw~Dx ml- @%00>VAS%.!m7;2Y!bBJ~ ր 18I!a" D>FXq"291I$))^ ƽ'W >Gy?Ϡڎ(XEW{ϹӗJ;;#CLjW. ߁q = X'!'W#60hnS zy>|{Z\}\b9|>0*^=&<`ㅧ &kP˖-WD{j{׋ [}}z>y+{[z}1ni_D+[#@:!ݛ2'HH?ѭ!yL % Ayh          @#@+l) w:         P|a(0'        F UTfSIi.s! pTsճ:692u\Ǫ>LOsDf=RyZ+ggIHHHܧKu-u6XPi RVb#AƎ[2d`cͬ*(G` 9 @c ߰]?»uȭiv=9SÇcJoO    hBh݈f004~D=7n4]pv26_7=dsdIG>C$@$@$@$@$@$@$@$eH+qĉ|n0 63.'[|4f,G*U,ٽ{*TH*UsDfM6I#l1 @x?^ Ԓ6ZPlZY m;QB~@$@.Rlra{DhX         |(0'         !@ nsHHHHHHHHHH @ Ppy)?Փ  6_f Ց @ Ew?f$@$@$@$@$@$@$@$@$@$ (|! Y @PneràY ( za,          "@ w:         |h)gHHHHHHHHHw/sճ:          %@~IHHHHHHHHH @PpB0fHHHHHHHHHw?_(f0'         !@ w:         mP΂;3mHHHHHHHHHH @ Pp f  6_/ wgu$@$@$@$@$@$@$@$@$@$@J{^6HHHHHHHHHH Ppz:          @ݏ.&         #;ͱ3Aτ W.]j3yA͛rA/J N3}?G (m{D2u뷳UjӺeSтH*[;"lذAݻ' ٳG۷oÇ寿dɒI瞋D$@$@$@$@$@$Y(Gi䋙*X@H ԫ[%( |%_|rҥ@iM ĊKڴnM{so>Vo_|)0F!F[n~s=/\~]Oɟ?LGs7I:JRX1eHHHH"@.*G$;p=/ hr]H|"vs玴kJ~lٲ[oznwtcƌiӦŋwp#}*߶nvܥKՏ3fHܖ    (ǿ#Zw %\!  W/Powdk˗ѣGŋUGɠA>&]tFz5C jrVX|?|]޲e'OT>3Ѽ[nJt?Wvء+V(K,Zjɮ]2s̑3gΘY(s@<&9s>Lp^k@\?xڔ-[69wjXoڴI0GmSΝkݤ޽LCT 5y A`=_ÇA5kes_ x„ jz]nQm9rtg[TA$@$@$@$A!G3ܟ @`87(!b2id CXmذE)a`QhQYfbO z۶m1z6ڵkfRضrJmgϪEh {dc۷o 4*UJe)SFe2F:a~#*?L0l޽PM4ٳg+?'N,lۺu|fyp*PjOڴivt^0qÇ}\U^~}DVmFZC߳gۧLvU%q_zW VZ2pō׺2F ,0HHHHH_/F6?U  >[!_T +AA66u`Vd=# buRgaK$IC#a_}U *(9`g sհ_ꫯL?os@'1B}e &N#2G,yNj [ݏ>(n֬uuF=ԑaC zwVN#f|t7߯18K %    Pp#0'  t*Urus ZpvwC?Ǥ]O>K>6GyL{WUlxw1TTR)_~ ӧcE2#>ȝ&Mٳgn޼΀/ubBWP3q>,|ce%J(6t;hEx+=wu;o:: AψbL$@$@$@J_uy>mauAdrN9t65#+ CyIza'A1n x{ܵ}]3cz^֓6o\QpBew<&ݸqG=<&eE     Nj3YZ Ց N^#oLMGƲ5Kwĉk,o;dڷo<ڝ^JL*ৎIfZ*z N[==%K&ӧOW-~ ɭ}|Ǥ?~\̙ṯ>Xv!gnE    @:/FOG<;K$@$@$P෍LdLf w}WMYlYw QO<){%Kj;|Izf ?w(?w\5& 0(&EϼM6^e\޽"3faÆLʕ+GBG^:Яʕ+UޫW/5I,|-Z!k.ټyGf:& L0֭[K5k({L؊LԊ [>DydkN@vիWUȜG'O,M6C;^/"ISy0`}Z9a ӻv -(ۂI%VOpHHH 8p@yF8?~[츯ʔ)cݏ=RR^z@0`#HHHH/ ڲg$@$@$@$!/V(+ٲe cN_bWgnr\:d/vpԛ|-c}_y޽[.0-&$yws; B/( w/  @P"&ńvDַqF5cjsIkcR[_Γre`~5ascp;,b Pp#af.&  ;-`̙3Uu7_|>?=Y켯ѽ-Y x    Ki6_[kU:          &@ݏ~˪IHHHHHHHHH Pp0fHHHHHHHHHw?^(A$@$@$@$@$@$@$@$@$'fOrm%'Mߓ oܸ%3|K$@J|ӧO˗Gy2      #pjOFė/]V-ygm&pÇjsI!\V|.\3z *$@$@$@$@$@$@AN[q5;c]a8ut%-el>崹FVG$@$@$@$@$@$@$@$@1S3t%3mia<H8pY H`>}zPG8 @ضmY)Rd˙͖f[^e˗HҖ:Yk'wה5XPpJ=3ң{;keu$@!0x8:lԩSG&O*x @hԨܛj,m:e UP{׾]LҶJ\(ܿwe-eJ$@$@$@$@$@$@$@$@$@$@M{@_6HHHHHHHHHH XPp+v 4zaHHHHHHHHH ]cˎܺy~`s bŒ^ Nd@W#        '̳ď&ً ^ۭDh)Wm#        h@+$M4IVu" @=*$@$@$@$@$@$@$@$@$Q>8AR~M$U(GyHHHHHHHHb&-^󹷵*$,(˕b;IHHHHHHHH ̓Sxާ4iI;p嗀         Az欙T1 {mPp6!        &PQm'Wlܢl D+_ iҥا8YG@(aHHHHHHHHH +4jm$/t;H Ppö @4$д{[ _ F܃$@$@$@$@$@$@$@$@$r!J<MZN%n ܃2$@$@$@$@$@$@$@$@$4u!gΚIJ+:(cfgIHHHHHHHH 0mT[ĉИ-SO=對+$L(b[IHHHHHHHH H$ԨS͡7[6l/ ,X :J**U*+fG?pBٳg:uJǏ/ٳg2eH͚5Yo#;{C6nXy;9D$@$@AB}\xQ}\ڵ*(Q`   ݼBr t2q?&dU1ѹ13):_QHHH,.]*7oT[ "k9rO˗/+v,o:{/oe=k.?~$I"'O 2۶mSv@ϟ?/׮]sY;w 2Fp=zܻwO'N,2dP/<.nܸ!&Ⱥiː K G{p{+em$E(GxHHH"1hŊnݺ˖-Wl.]Hڴi%eʔ3gN%˗OMfӬY3eŒ7o^r効]/@>صT>}zjՔu ò%cƌJ.^7oެQhawȑD&;ve1IdĈ*"2B\EZT`W?ToUOӧɓKB7u m&MjիUꅐtL,Y2u)S&z]W_J>~w2j(/yQy ,BwoEN$@$@$|ҦO|fIZʸM$@$@$@$8qB R#x={T7otɡ˕+'WŋgƖ[nɡCV4|-[ִYdglra|Rq[l)hC>d1O* ?PDzlC>A!f_^ƍq#G*UO??UVm:uDd'2x'D}ϿԫWO맄hlCv=q-oѓ]J^z%fBsy8 -һEsxg}utX>?~XoV>&Qݹsچڱc@—{Q$@$@$@$@$M    5kiKlt|SHg̘ޭ?>Hڰ6}D`ĉp .T%vk;QkԨ͑AOug{OٰA:|_~}!_`[WaG} #:tSpm/ .jy s)DE*7BlǓ ,PO%_~Tزer/cN$@$@$@$@QM{T_HHH@EQ.XkaQ~W_Բn@67Ca>5[nUXؔ@0E@GFs3F bŒlٲ"Ȧׁod#/Hr &Hԩʕ+{X?`zj)VY7w}״ꫯv,.\X>S5 V+cL2$@$@$@$@$B+<7  XEC_vM-[fNa1w\e sT/XڵKҧOydcQ:&&Փn7؎ݨ7\Uo CF=^KdsyD(I\k<1H+.]m*qx:C =B_EOur {gOF5:5kTaʅ?/H!Vz߀}fz$# Pp&   krʙ>c`frέ ;vѣ$㏕9ʶnZ XF;&IE&3صU'fͪdbK>ݾ}{WEmS[ NH¾;Ν;4(j`` b;oVҤIcv<4O~$nܸjݛ܋2$@$@$@wc'6Λ L}ŒoBwmzB1-ebUgIHH5'N%cQ>}bҳgO5hɒ%MLnYB>l2J:wm'/\7r:z AݺuS/SLQ¹^#[_מz` ؼ'ڶm I; ^WZJLZF  "5I /.F$@$@$X&K.A SŪyfrӧ+^GloArR[1F#Ed9TVF5Dԏ=c:I$fVnO}-6CE$kEIʀTtGUhQvm? "6B 8⽣>\r bܐOR>f|z/xH$@$@$ %<% [tD,{ZNPO޹D{g=x$'w鶽zG?GSPFAPf$@$@$@$tŪ'xfë+C(b^۷oWID 1\ ow$CER:u(vAGLuW'aLǗ4_`]AP>OJTFx]^=9pAc@O:%{nu~Zǵ˔)=|PnܰN8 6gΜB|ׯ ቉['t1cTO!8{/|'  p@l% ~-p2 'E-3qdɓdOG(W\iITy9`"I7z}@6$Pr*Ii`#m9v$2>j$@$@$@$@"FRIM/n&իѐ QXpZkr9hY2(1;̽IzՕx}[<“C~)WDMŘS+8sYwĀQg"% Ko$KݥiRB))P8\r, p%z&"   F`ҥ*v*UQFz$@$@$@$@A<6_SdϬϟ`6xeC Aý !wΑGC#cϪ4bIʊ7cW1HC>m4f3ǎKj~_]'QGA~E~E6;jo'~.#f0*D$@$@$ $G[jJFXxHHHH.<rKI,[cG{=^Wȅ W]nPkw9l$/i???yHH hӧ~$Jŗ @'P~ 5ʡ}._!{$JϘ(T-YCSO!fF*ʼn" 73ӥ^'׺H̺زdOG~8ì p5~#r5`^ƵER@Nr̸RT}ʌYKKJo˺ŋnңɘ+e[$@$@$@$@$@$@$@@w kՒՂ-wnUDHg>o߼5t 2rpٸg\V+H=y" Pmbԛ.\8ԫ{ZvS[Ә.{m_ p%pw%`Vr/0<7>>z ٵܷ[mZ5b%LCMCo˗e2w*9~j*rHiuIHHHHHH9TiSI\Y崑%twv= W,r7&N("XqĉVu,wN;e&KTm?zH:,qDʋ~l"*s Ɯ#F&@D?Uyrgl2ȭ[ٲf{ɞ=Gxt)p<$_nJ5kz.+6oձ0/U1V ÚelϟR% J( p4~;>Ȑږ{Ν A#,\HC:k"=5DHѤQ-ɑ#>l՛f [ɑ=4_Uqօ}z=t}c Ϝ9˗"E>btiެf"m{eUHHHHHHH d;W/_}e$7xOrXt=8~hayiV!=~*-r`)Si~Sw1vSxm:2w=>7HE(6s5iզk(/ѢEٿ/Bj?KԨQ}6jhr4T9upᢵr~ҬE% g1ցǥe*I+3 &'O'Ol1 ׬Vn#=|D˜Fg͒^-`u̙5C,G]ܹFjf/[I8s,` ݻwXbƨ0;26/X-ϟZr#笐6xYѓEX \V3R tX-$JP `=$U^Јݹ'#Gz?3#{>%gB (gѝ[wX|?:V#&KRH~bcO?jRdas$JJ<߿(1cFU7ׄq?Cke˦yRV%֮jD.)G/EjIذ3#2ghӫK !@~qV޾:/fb՞N%wARfyvej{VrC7aY wLi$?~u<R W>ٸzi <SZh13Ƹ*$eʤvy,_:Ey[ݪzu*ccrެiUw-fY^ccگ .VRIUtlc{&L6}_xԪ;bWxHHHHHHB$-V91cH2}6M^4"nVEK-)cSIK/\)D6W~llcd'RRI=27J޽},[\4eSR3L PARNo$o*Pe&1q\Im!7|BضLF8}MJC 6̾rԩ]Q %k~ x #(G8<ֿo{8i>}Q?|hw߹puoƀ -f}MҰ~5I4!V7|YZ~.~A_~[‡'62<ʗ˙pz>·\MB7{ %Vruc3dxYHHHHHHH ;V,թ"8ITv h(Me TܔWo<4[NUi޶ 1X][Ĉ~eȘsКΝ$2gSf Kתjt#pjIݽ ; 8KY[zxݻTGLY;/^8 byV)RD #W@[Ĉֵxʴr}}XtЗ/=@!\Ę0a<G7r#I(\2ÇR\#o 7q׋[@ gyϚlZttw7ZtiϲrY TiRӿx}5/lQAo5y].a߽{#v~d -!{lMFXjȹS{$YCPOo;YVDR׮};IFc#]#]%4S<e&m; CHjϢWBUV@-{Jp/WJG\zĴ|r=࿰:mxx;z&c?cR9~{m9s L-{=#B ەk+I|v嘂R[CSh~C>qsae͙E,{ ?m4U9#Wj3xu#@gMs.+OΝczt6DWR<7(ҳdIo;j$VoB۷ qXL2fo'#M)$uL˪a߾]c_%'d1$VgW̘1'r|$@$@$@$@$@$@>$p-yOOb\a Oy8!J\.ڸ|rsU{lH xF8q{]M/_u+!kT˖oO>˃U}fȐJJ*lce;qz_lx~_AG#GO˞=GhѼjRfҰA56H:hDiظjQOܾ_0?͒J}rU%#J:L!}*URkd܆:uQNϤz:S'c͕/.i$PpjWf%vmɀAɘ1L_T۩CAVj^(Spn-yu;x3v!Bx)Y$OXҝe/Jp9KRLaSƱyɲ%8鈡޴I-hkarUlOHF#vW$ UۘG<٤FrQv:>k,^0A5蠘BXG2eāҾ@Cޡ\93K U0+hsc*y{vR+wcSJxz}3c       ?!Pf%FAz,Ɲ}mc[Vŋr?k*-SnQp1ϟWXO^$a¸{{[5ds:|`E-4iRȉȍw͛wd_낃UvzvowxZ9b+WmVźXr29c#        N>A/;$Mbogd)S&3bo~ԓgϊ~m+W)T(y:̽eݚ*j0a\4         `M{dWڤB\u)3g-Uc!z Iy/]+2 3W|uNYb<{X&|(+ @0&2A={Y :tR0 $ݲi^cCFBO9VR q$Ks(P OV"rȫlcK"y,窓!gV >4       mDⷺF~M~g+m$| 6?kaYPΓMw;?Id8U®;4v?5v*T(oEF$@$@$@$@$@$@$31bŐ^Ȯ?w vb!i zÇ'x$3{t+#B#e-,HHHHHHB8 $8}^Zy&( i  )Sh$@k  ] ?܃5HHHHHHHHHH.IHHHHHHHHH rF$@$@$@$@$@$@$@$@$@$@@{@g$@$@$@$@$@$@$@$@$@$@wM9#          @=K          G{𻦜 @% @#@=]SΈHHHHHHHHHH PpHHHHHHHHHH )gD$@$@$@$@$@$@$@$@$@$B@HHHHHHHHjjqr$@=u(HHHHHHHH|p($x$H@'KxFB@vA8        '0v(;Ͽ? D8uԯ9)OJe#Z:4eeϮxFIHHHHHHHB41csK j(VFIbŎiu;Ck ܃EIHHHHHHHHH? q$@$@$@$@$@$@$@$@$@$@A p8D          O{F! @ @=\$HHHHHHHHHH GH$@$@$@$@$@$@$@$@$@$PpC$         (k 9D   J`۶m$}.]: &2<?.GcǎɩS$A'Oɝ;d̘1`{_t|E͢L2'N ># ? ٜ8qB.]&Sn]nreeK$@$@$@$dPp2%  G`Сo>/'bŊ2k,3eyq˗/W^IxJ*Wym۶ӧOWիW]Efuާ;ׯ_;v&LӦ|]ϯrQq8P ` `gcѢE2~xu b`H9w   J!eBIHH pv *-k׮lٲy Ħ(?K֭eԨQNףYfҤISlO<ԫWO ( aÆUYFr!Ov} ËK{{V֯XPais̑P6>l؋‰ @#@=]pNHH*7oϟ_Ւ%K)ݻwOƌTl=yd K4?^nܸ!K,(VZ֡QAKXT۷M~u  lݻw+r!Jp$@$@$@$ Ppן'  KoUoQXr'0WǏ=m۳_Vg}{ܧsBX f!CT3Ǘz.\8T|03`dV7oި!Ϝ9SkmoN>}[n#ޙ ;wy#|F[5gOQFDYZ@ܝ5Gvv,O$@$@$@|u97  bŊ-L՞$KV“$I#7`[ݻMV*Wnݺ)AǶlb[>~ŀѣK1$ŝ!kѣ mCt5x^>Srt]y'NX"E߿jժO;y9^ѣ۫$'/ZFzx\}޾}ҥUӦMձ^zcϟ?zBn X#ezC2dЇ+UErL#cj;ZXO2+VLUZA/^mqӦMRD 1 al7jH=}+կ__Ɉxbk/_TL3rlsϞ=j㏧!X=byeQ} c1SNj>|xP G>^#94hZg0Eݻ=ѧl:    lHHH @~bW)B,XP % pBa?zHy M62vX+ܪgϞ)lذar01VB-d.\Xy$a58Um>X!c0x?~\ʖ-+H ѧ^ǡEqUΏÇ:lbP8qFjB1CxݟfNE?|,`=yD#;S1Dg)BuĘٳ6%p0GbGA餩Xxϟk.Վ*\x?/X@)9**TH @AÜQ9zJ@8(M>}|Q p֭Sp䳡50| .^pa{U "X4uz[m    :lHHH @в6p!D3ѬTRxƍ0wچ ={v}wxT:tHy" <\!A!:vCP 'nBpS6W\j7sBm۶)0T-3ecy\@IDAT1x#+/ ,e̘,(_۴iVx'L`?W^k' luҁΌŁ<;w0,h@H$M4J\~;ހAF{ԁ=^?;vl%JpB3g>+xt pxXr|mɈ .I& q~zt ؁5xZ c85kVu]P[s3۶ wpd+NxMwV6la|=a|xDm% v*͛77_ "7DXmZGCz7"!Y@ekזdɒڇN aF„ #pGPA؍s𪆝;wJǾ~4honЏ)RAAEu0x J"%\;bq x#clߣ a|p :l 0{8 "l[}gpFAaٲeS'qpN>-NRǵk&^0܇G00!jۧ<a9rN߾} +VK8*Do|6a1WV]`! a0/ Q}ѢE-3 yqM`=ge 7)߷H!GL_Fjx7gʔVΞlWf 4!O$@Vwv V!'BbhCE07n9DnPVlv|Fa1]緷xc[޳}{J?bY Xuâ 'b,Q^o>M$@$?7{ r^}N=[DzLX1ɲwc˖mlݶW<ֈKZ6')S${%J$!{w̸2cyxٓ $6x^KNy/^`yFn/t=P0g QH*/vxvTXupKGX%K^B9r滣B1̳k޽jvfS6O|4l9A?a,%d([wzHH T)QC?⿝ K]3p^M _.p^9#@Xv:L_rtt9Cc/.G=4C$@$ $C„ }j'پCt(0h_-'Ox{e:d˽ʳb5^tɜt;~s@lj6.ݻ _BgZ9,&N܉q MXT$Ib#߿_%yE~]{5[7cqfXl=+\ܲ VF (9!4O4cc $uparGT=݆:x yAf06<%E0|Ff0OKC]Ձ,YR%7<6ā $@o@w}r^o' X~*F^*IQpWc97Yj/DMPquSǑ CIK'H&iC՗"]OrI[H^Y|T\9EkJDI˧O22foRJic8:U>ƣT26ej9s8[8sӧ-98x\ZX,QPΝ_0'O'Ol1ܓKgm死FȀ'hB2g+Yˢ.IH a1pÆ 2l0+UwbE#dekϚgfu_8>kժeUuRWx5HƊHR+[JҨ9鶽zױ*Aޖc6 &|8~E^=[Fٴa;N _O*UinRxC<X y:,8+t}ҥq^',Ŷ= A_Ӯ]T7XgU}`)gXb*6>{Ɋqa,P;Nv+'   w/SQ@/Wަuٿgh{Xiݶ9'__ɰ!]Z2wXeʤ(2]/WܐlY3ȶ7nliܨ?KoӤIÛxUTJ˲>G-U7s歰km֭>": <'HzDy^yr>K/Cˌ_G47jܩ9HH$&kV!G]rʥ;t蠎#٪vYOo#(Q(oh*U*I6!vI'ƶf0NCtȑŞsEcŊe^yM{ +u *K;ƒ{QFJxGlW{#6"r!r[5֯_?5=DHG,|`۷oWq8 ؆ oxhU⇣cw\,ǎL_8%Mg98(P@_'='4ឬVtD5'@T>߰zx7_߰b iӦ,5$x @[ci>lw-[TIaq^='+-stOfY^oG`9     PpQ+Ŏ0:ur%_tM%Ĥ;Flo=ة2@ 'Z=l<o|( wP/rڡ]Q@Gq,R*t $؎  B"BVhP&̙3*3/xCDІW!D=9!fѢE0H,A ]0 i˖-Sb^hl~={'pM؆v޽-'L`j/bk $!b ?>QHV 6  w6C?EN`3(2+_|BBe̙ uَ6ܞ>}{"Fh'9𖉅m;}ĉJx"qǂ \3ô\gE|6vyb ۢ_< g۷~YHHHH+o/"z>lqec?e icfrMdҮAHxUZeYrZ/ĉ>H;Qu]VAsD/T10{޽p;nxz{ܫ:</Y' uF?P,o8fi>W#F w?wlNGUVDZӹ8iJP&'$m۶S:\I'wʥKg/޾IH hg&>;LuIS=#LV 4\*STm0#oDdF;xO< NLMˆy^ܺʛ˽ӧSQ?c%>g%t}ڇ8}"8PEHH!8qb˿ g9#Bo "5^Y,e;Bxk/qv۶7xc!q>{ΐTVVa?vyiUflgֲrfe,_Qj3gN+={(140te7}ElHHH v9H؉_/U2 ZZPع8Pp!Ga'3!Z|҇O;vLV zo߾+6\#@ 8      Nҹ%ZI"O{^qדJ&1bp g "*$|]F#w-\&o2SlvxyhܸvS>`wNo2C6xU%+6|.|,~9|sz3ftfr!Bx$#?;wV5[TR^~)[&      @ OQ=WVl'ٵm@tEU.*1%~#vX%;<,uTRҹw}UJvu f/Ry&NwÝeU[Ճ. ۤo;alwjxǖ,eRvpGOc~-ь܆vt; |!߫c?ٹuqMGI%C ֱ4˱lؽV=|$]t7zy>,v"@=p]o1dž~~;~1HX vɓgNuYȘ[dZHڵ[2o*`ܽ ]ΗHHHHHHv='Dѵ+ˉ'e.cZ~d*'#>9m}|Ξ:'k5{wb!+TIf-UJ)nG!7  R]=C*%|pdNq矋$c4N +ː1 w_΁HHHHHH+!5jPZwh)%3'ʠC=ڶlܳΜūM=ἆx=@2dI/ߖ?M6Ƶdԑf=qIyz*MJhlgˑEEv[6wIZbr.L Și#U د[A<ҧ]L,{9e-r=iR0aߟr>[O@l>[…m^}7ҰZ9v)[=dsJb ;p# #f5Jz(V|9n JZc-z4S+#Jti׬jfĀQ#G1rDsXQ;f06isڕk84eVݠĎx{M?et1ay^oeǞ^B2| IB0V,, 5GHHHHHHH+N`kkӡ{; C(thuA8qJ"ɂً)wĶbU_TYnLZ>ax#ak@ekU1 ~K]Xf(O>S{)M"orF$ lHHHHHH\J b*m*m,<~p]pEǟ˅}b#ޞU^a˖3v[_8ܼ~KǜbƊi|0?ۥ_˂e+С)Z2 nۼr>$K/Ɉe-.boӶmgėt?2sL @#ڰޝAo83CmUu>$۷4=۰uWk(}Uq,t_riYrJ=#YdŋIA$@$@$@$@$@$ lڴIܤ`1K@8B!͘<%c;|L7Ū-j+Rm`ўs~'M4ǖ6p2f:4g,-V!=~arYU F&NH{~GWG瘍9QXA%+K:Uj>P g+!_Sއj;`q$N<'όGtd a+_GV^ٽɝU xK4^;*sr8L:Ez>'(PB2a (JJ&өzqrj`$Oһ>b(@ K,TO N(J&"HY2lI*Ji:v匤mX,VL5^ + Evn%[v1ҾOV3W; oQ"9搻8<2j},_R޽}'[u[*T+;t¸#؎ZmnN#@=]>&kIԨ`'KxT$y 6El(3W3L8(      @ !'KL\^p`0詿X {uPb#'e!dPH= !̂sXђEdώRR &}^lqӰP: WmP͎9I*d-Vu|;N,5yt2?OM;7;QO z-]0ϏsO|b2$G\5 &]13T~v۴j ;wz[6 3;\$7A:KIrHHHHHHH5+6/*+oK=MԲ~j)Q:*nܳN*DV|,%K ۶Kk5ob;Zk.WB͔5[9[ƏkƜvf$_~$+Wm6V>%ٲe[q#.VP$[ } (3'7Y6vܕF"]jӂmș2LmԩS!)HHHHHH3r@R&vj94q3/%zrUrYCGrpX1iJܺ+.\6DQq-;ԀBSNsá2i$+.u-y$KT_K./$6ydj,#>ӿU7~.ZLbǎ-O<~F"݈ )Kw.]Vmz+af-Z25LO?OݢEӤR*..pZ}c)z?5~4kC 3]Ҳuo#)Y;=S tyrF#c5keΜ6fI/XFn=i cJS'q9cX|f;׏?`2ݺekѪ7M+C_JM{}GsVHA1$ې3IC|yHHHHHHH9mi1 l.\8_&uHyWf.#)-, 0%#G&yČ]Vެ^,G5ϓڵ*DuYQ9w||wI.,Ԓakg{GdҧW[SlG8F|/We֌_ڳݩT<.y@j,oxxVmwJy0۬:thɔ)7?~u<R W>y}Q1 >GH 1[ f6$. ߦ3|wRR)ڹ !؅ n*Nڴ)tqo3dH-bŐ˗F$@$@$@$@$@$@$@$@$x PpFVX~+]wܽkK%{Jp}‰J|\s-]XG/^U7'NWadŋ-=zyj)xh:3{n>v%'LOr=A8re"ADǂ$@$@$@$@$@$@$@@\Y͛d51Ά d:Ƕ[-A,HjϢ5WBm;Dwŋnv"~b@1흳==~x۵[ܽs$@$@$@$@$@$@$ ,۸8ϐ#A{𸎾3?y;udvÇf{ca]֯&x~m | WǏxᝩƲ$@$@$@$@$@$@$@$@$@@ܓ`2gΝ,-/^InCTUY\+T]~۪K/PǑD5zdxٲuӤI!ɬvcU%e$Vey  _xꕤHB4hVwa9xtVIQ$N8r  =]1H={I|9ײnvy䙴oXͧY||Y6mh:'M,[/{U#F?PW!ؑehѼjRfҰA56H:hDiظjQOܾ_ub3u`߰NWBV)eJ E{SBU߻vRԯT+Wogܓ Z֩$1cFWl8qbݻ˗/fRXr(#GRSmsHH<|Pn޼)a7sENiJjn/۷_3gl:e;X|>r䈑mݻwKCh\zUr̩"ޱxЯ~$ @ @=P\D]F2`ɘ1L_T۩CACҹ<.ۃvK:vDz1fLs7Bɓ'cJ.m￿(.݆J2%nܯ&˖L6ʢ\pa\٢ҴI-% +8Pf^^8/\%!HLW^zzS )S&5S)Q{ĉҶͶn*}B I˖-cw4+Cē[˗ x+Ϙ1C*[lqxy2Pȝ;zJB9yp7ouI͚5U2{=ԩS>w~o&M 琨R`A#HĎ[ڷo/&M Rc`IHHHw["!t?fe^BW,c{2ke^){UլQ^zUI&`Pd!t~ s--$IPV;۶|m9߳{k%oظa}ͪ._G 7NL'~Yy?yĩwܩO0)ѣG :tڴic,t3gʏ?mypo㩈?SV^-'1c{x2|HΝ"bP^=]L~1,,z՘1cs$@!C%\p![>~mG(X6bv1t R^3|h7W5kI\Y OF6^9 b/hذwɓ'ڵklٲyKYvz* Iԩc芬<·o߮{d"͚5ZE={e`={v7=ۻw\| ]j׮-h&BV+WNJ:ʿkY^x!ϟ?W}a!H$I<O[k|!sRw6#=zteq^vpZyr4/$ #G+V(\R PہRJ%nR[-h رcu3RJA {ֳgO9|: *A }7N0=zU!mtj*<,Ն&JH jCwKYjUC6y!jALu֭+zRb$; =RPqwӦMFN DVX +V,%"OqB;>8s(ɓӪ_˫!b7ahk׮1bDI>]`YsUuX`¢֜'q}m?{xϞ=[1¸9uJ<`rU}vAf͚Ypʕ˶>Dp 5uTz ׫{2p@A+"ErDw 'w0EP#BBV(:Ӿ(\weWСjFH9rʸ1d- lcK"yd8* G A8;!;J* 17a"ƍU˶mdРAJޯ{3޷^B@؃9²@paa"0I!ƏߪСCˎ;$eJX1<֮ë H(*"EJiRR`Sc Ơ5zxQ^ūWj4q R--STP@=:ksϛ7?߷>]6ԏnokE|{[j/ߗQ5yMzBo<,. M7ݔzDj%8/=y!+V۷oO[صkW\搊5~'Ex{矟3/{t /| S=9jxyimc{/_a}!`ޟqƂ}N;-!zZ*wvD4gŮj=} \43O|H=d$ q$)$ %>s82} Cr&G3zVrѓ$ I" /<袋`8xޖBPD"] ?O ^:0g>'/e؇?u{] I8B! P7(6sGzI'=+"eś07r+,a)8@0CH6^W[Xlڴ)]G 1 xNw;^qEH0“9aDxpx<9t/!!Q7bO-)iʕ_4;oSmc{0~b*;~0wzիfg#U搧‚o*6$ wPL!P祦<O|"#|H%q?akP([ge#,/*OSif׿E댴m۶ZK.̷^E} L9d׌7SpЁI@$ Jҋud[$ #y1+<{7A\t)bNjci#F)kޅMHar艗{nYfMz'O=A@F03j^~]w%v^!b;!$*IDAT' ![x)k>n<6H{<lݜSgdO k*<)S~2P>/ʧ&e- H@*:3K$ Yp"O~`ٲey҂ -o?`wygES( KmbTX*#4 _N0yWb},'V:|y8y{[ ]xؗ MoN89cfx&?l跍hl.qy,"uS}k;C}"o}kO]_S!ʰi^$  H@F26G$0x'bSib52' VEE^rxdv;wL/lznݺoljo78-/r.8RWWEo+L/Y S~vs3ΨÊ+[o5 mBG)^]<$~7U~bǻ E;`?#Hy1u"׭7({U#c^q!mbӧzj>=O~25=Ih ?Tmݖ^Ƌ5{+FJ<^̋i#U9`T 1I<}G?8p`>>&QWp|%=p禲3^EYv饗'vUqI@$ I!d>: }X|(3-L&}?xG}ny@;S_rڈQt'Ǔc|^VG?3> #A$o.կON!zq%/yIя~@«Ov<| !1:$ڟy0߇rHqŧAuP?Nt/p䷾&-6rǎۈ¬W :b0B{p_~9E<:XcX VxVUq{yGRi:C=tړC20 `u+_J~&íx+k/}iJ'8q09ńiF(=λ8x衇3"aqu!IE(6^E?./6P6jo84 H@ Ͽ⌷>&mCuۮ/^p))Dw=DM Sy_5'vF蒀{4KK w^=.Θ&DG׆7*"*2>Oaz$@XF^reuw4![wxυ !$J^Aۘ{IZ`>8( Y2xD2摀$0,a Z^%0΂!eFlM$  L !;ʞ'/fBV&vq '(74V0B ն:O}k[|̈si18~z_a2s>$  H@#">"6# H@`Չk{]O!urJ↸Մ%MFgi^;DziӦ$~WՃVi9 H@$  , f$  H`2 Rx 9眓 mK֚ lذ!Ÿ7Sp'?VS%PM;ⱓ3^jJx^vI@$  H` (:6 H@wFCkK@ଳ*6nؐ$^lɋ^5 J'P]~ (O i4h$  H@B"ྐfþH@$0-[6#FHoV=8O$  H@$O"I, H@$  H@$  H@M@}K@$  H@$  H@@Wܻ"i=$  H@$  H@$0'z$  H@$  H@$  tE@+# H@$  H@$  H@M@}K@$  H@$  H@@Wܻ"i=$  H@$  H@$0'z$  H@$  H@$  tE@+# H@$  H@$  H@M@}K@$  H@$  H@@Wܻ"i=$  H@$  H@$0'z$  H@$  H@$  tE@+# H@$  H@$  H@M@}K@$  H@$  H@@W誢R՛-q-O H`۶8DiJ@$  H@b#e5}wuJ`~c;㱡^ 4 H`r瞓3XG* H@$  H` s)$ A v}ƍ=S<ȘK _qgX$  H@$  TPU ,dK,)N=ԅŁ)$  H@$  H@$  H@ST$  H@$  H@$  H @ H@$  H@$  H@k@$  H@$  H@$ Z$  H@$  H@$  H@5  H@$  H@$  H@耀{B$  H@$  H@$$  H@$  H@$  t@@V! H@$  H@$  H@Ppw H@$  H@$  H@: D$  H@$  H@$  ($  H@$  H@$  H@PpUH@$  H@$  H@]$  H@$  H@$ (w*$  H@$  H@$  H@  H@$  H@$  H@@;h$  H@$  H@$ w׀$  H@$  H@$  H @ H@$  H@$  H@k@$  H@$  H@$ Z$  H@$  H@>Z{şJ,ʼn f|tjan5 C`a [V$  H@$ Aw]q7˖-+:9%LmVva~7^ \;v{wvښ\3/z8x}ݷXzk|m{~gU81G<)C6[n-׿'tRw#6n`N1Dy~]wuLeYzVrrR!xwqǬ4>x=+|(Ξ>q\EGqD>+\PpV) H@$  H@x"z`Uz/Tx@"=O-sY>@Å=أ˲ X5 u"kn~G '4kQ6;T[!"ηPw3L=z5w_#9O$0wrKZs93qyK^jXKnHW+V~ ۔TϘ1K# H@$  H@b$&Cx-UD9D9DxE<5*kԋ膀I~(D#BC{Ŀhr֭[ӡ@4%CCH}s;C-?Vk1[<\6tX07>a+ke\3$cb.Be9Lx:ʟbsȈź#Fe(`z!2!c BVb&sCY ^12\AaE`K}8`6b("5lC4/GaU6Uҙc!vE^ gƺ3BDOG4fnˆ^gaFi9='phy;4!cKq:f:sǎ{ kc~ث9(qfaM^ڪUҡm2"xr~)c~^!49F˭i|XJ:F&CGlka= \pQ/W7B0!H߽bв~g?Y'}AnE5@z*'sK1wg:|9cc-A%k7Lv`Z#=o+GC&f>X1] !=fI}pne~Â'WFeFղH@$  H@$0/ .|pD]v!o̲@Bj$F"߄@0+!p"k׮3>آS?`C`_.S& F=_xNy+k%:" !Ö` !9fᅋQqtʄNM Pb;yY3Ὄwu.bF:c}pPQoƃ!\^n]X":ybl(}a>W}=TMWl>Gxs}_ mB2(WY{E^0ezM?0c0vq/a/a0")1z2&9Igcbm#kMNJ{"-k{S)ܧfqg|i[G>h$  H@$  H`@t/\Gú:G<-jV,)xOH兙w@+.1l rr(0(^X6f"fYuk-3(=[pryZ{C< /au3UsM<y+(6L٨cO,?Č:ybl!=,9×HO!D !5Uss0OF]c>A 8G%yܼyKs=W:h'^x 7$+#xIyDzgm]`pȏ {C-'wx_G]UI#|޴_}dOxa|os0& 1(z.ڨjc'^{m;a'u™n[UmhƔ-{ >*aXU,gwS6'sRXQo,q/ē83{}mJ'P98b_k}Gʳ?1}€k|c+Ӛ. H@$  H@&"B/$7^+=8Ɉ_DY7~aY i۔!?pQw/o7QݓFQw#k=1aqGa>ay!Ca0QP H@$  H@"& w$B'bLnɋ8(HCZxuիW'/KCN(Q6x]"nWz!" h#b!!! _uUHx嫾aWU!!/aZoi:#cD [<>L*uq1 F !! m_>yȆL:l~FA>Ig}züXb%ߩ׬Y!˞68k>p9O߽y=ax) 똿Xoc\cm0?1'|Ø;<יkצ`xYCQzCz37޺uK]6`{}$ܠߩz qX@}< CFeKGG՘H@$  H@$0YGF \Ls ^!5wyC,BAB-[r7BhBMlVV^_C~Ɓ[aD[K'< UNfmT5Cs)9ufKo8~]ug߈m3O jy;Ô;}SsH1u9>O[e]Ć<{N$  H@$  HB1%7#q9C$0{~$  H@$  H@ 5/$;/&$8 F6a#lnY$  ,  h2$  H@$  H`x?,BaR;."Wݎ7& 83fKsEY땀$  H@$  H`l "@^B(^8 $ !^>>H$  H@$  H@$  H`~$qӶJ@$  H@$  H@Ƌxͧ$  H@$  H@$ y">OmV$  H@$  H@Ƌxͧ$  H@$  H@$ y">OmV$  H@$  H@Ƌxͧ$  H@$  H@$ y">OmV$  H@$  H@Ƌxͧ$  H@$  H@$ y"TS6+ H@$  H@$  H@0A$  H@$  H@-*03ٮIENDB`bpftrace-0.9.4/images/bpftrace_probes_2018.png000066400000000000000000007522231361633214400211150ustar00rootroot00000000000000PNG  IHDRYysRGB pHYsgRiTXtXML:com.adobe.xmp 5 1 2 O@IDATx$CAPK)XqB/"IV(,XByy9sggg}{Ivv|;ws+(      P^zޥY@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:wX0     @cA@@@@@:&d@z7|z)7s?vwy{7O?~{tC_cz]߾}3zU]@wΥ-_u7v覽_dEL3Դ"@@Yid, G`nvw)­:n}u믿~߻w.וW^5) 6|󦬋   ЀӇ ( =P7p@wAW^)w_&tR`_w^;eb  @c"+@@(@ ; <֝ww_K/Yf!#Gt/v׺M6٤$Gx~WꫯXb hWZo{r~{3ׯ`ghy_$G;Ez妚j0ʿ:>CYgc_~ɏS{̘1~X)hzSO=_|qb`W.s_~?/-: h~?& eq ,K~aO^nNG};|z K. 3z B/x   Lߖ} x zA|dwނ忎&sdAR׿" Dvs%Kk` "n ZGh,pYMH۪Xmh68=`,`Zv?~??6 HVk4:ӣz(ڪՔ-X1 %X-jG |d -8mV~` c~EV;/c53Ou9r +n5ϓ2 [-R^&R`TaŲ.M8l9AOQj?Te>`dufof)wsh5+>$(ZĈfܭ?v)b2^}>蠃J5Q"K䍬rrRXg\fi|-R]q :6KU0l9>Xި֫j+`lyfKye]y) [JO>~x?B粳TzgV$,Qy}Z)Z)IB֢zlAץ<΁R9  4M{(Y D T:ȌT#֢ X'EfL/JR@˽ׯJw:~(Z[lEzR[oJ T.J483'xb-1/  @ypn(  @_,;+P;wPk-TZwgԺxO_tO>9|jyuFix|ڃ|߹t~egqk8K;P7d=4˚.u?R[ECR0@@N\@@ X:NuvSv"%s==s|{>i["MnfWg;M6٤.U:UuV ,k,Mo^z(U-,R{u)s_qZUIʞUfD@zz9l@ ((UM`:, &պ8lF>>zN :Uj_Ex s1׎e]eV{/fUTԵlBXY2)n)b*z(SE5믿~C﷥/sz~3j<]}վK*a<(s!P+FIǻ+;%JDA@_{l@ngPӬ`|%;emtݥW *_T5R{n_#U_,W vr pJ\;:fPD۬u;JW_պX_ f:nߡ'K^߹|~@F)U󑫕EVQ}.Ku@ /B{S#G黽"wl" @ p#ץj~7/;~ԤnxGJ@c㎮wn-AsK$*kF7lM9T^z饦.zp5}m谔F\v4\w?կ%\;:AJ'ӧOfMu]s|wO>{ܣ>_/b_ ~%/3  r7Ǒ  ЭBmzJ)>ӒqRi>@r7j|{}^ץ:O>?>"JT3]ҏa@@ '@j\GU li?CFW駟|煪ͮ\ʵ qepSݓDžrWkwf{^߹p~FE)c1d>*тy]Z(6l^~e_]ǻf1@@14 R/pU _W> +Vo C?:D+>j}±60;R)e#W^6!^W^߹p~]i|ZO*T>R^^pr7￿u]ݳ>Ta0@@nuӱ  [ϯʆn9 ,ީB75oWixz ~UWuzSpޢ:ׯaܝw[ Գ^:ꨣ| I&^%촑{-;/~ _]QFGyw}x\Ky?c|rUjZ>?fa<  @Q, h/̞Xfj]P+;kvurXOQ HWnVZU-40*,Ve9y]v8qb>hlWmzҢn +dzj˵JtݱtIOd{:uT꫗\G߹r~պBǚS%˧{_9XjSL~rVrZ7,9@@j ^K   Wgg=K.2`_:KΘ135O8ᄌRXulڬbUC-E>^tEe;V]wur-SPp[,Jɡꐶ~r)m> 6.z> +7iV6\O83xݍ7ƍ馛Fmf} y^8 /Gu1p۷oa@@7 tGEY^>MȕW^Y!wqNJ6#MdM7G%'V/*܌`R;sK,_wQ>3k;Yg\SUO>kip'N\be)طiz>h֔Zf=p/SʔZ;-؉~ުjocuJ-%;Sί>J"aÆVtNJץ΁ls[N~5X#  !@A@QOΣ.oƊBVۿjgΟ\ Vt4GAkaպ,_oTjFmYazqUm5ዦGXMȂ~CMO.z/+Y-R;Dx_',7 #K-‘l 3_-(|,gs$ˬb5#{HⷽkFO>kx=$졌߂FF׹7r>+Oj'' [7z`Iшw"\h߰?^-7뮻JRr{/BaK}% R^ş3]C*?0\v,=Uٙ  Px~~D@. Zyjd6c Ǩ`jǛ5@k9ﳥ,u?`{ 8㌑,ߞC-#GrZf+{.؏dkg| _wܱ*;KM:餑Yh4h>3u]#;ܵ[9Ӄ=Tâ;롆~:?Qnɖ|ȡe߉f納dܗFZ=84hP&ƥA%m@=?TR׫z_s=]wtuhfuVnȐ!LA@hY&@@:{AaȚ|ɣ*l5\щ'X1z+TZM=> /q?WjZW:,__)l)@pׂzJ">./ׂ .YH5_CQL4Mm]Tz\yvbԨQE˖33~j=ϖ[M]Z=ஃRmu3RvT4Nhf}v+o$5p޽{PGn99~#M6$ 골ϨZÄϥ @@>K0   PQ@{3PI[PvlѣAqn WYMo{N8gm6NHs܂Y;Y@;R8,(/g-|.zt^}7朵j: ,@C{ZlA}f~a鮌,;]veS3v\}W:{MÏuu)s`ݘ1c?{p8Z/t  "0~!-+E@,r"pg;k;NU-u*&fB@,W!@@@r> 3ܷ@@ 8@@h+{̧l}yv@@p6A@@ _'{.G  ܻ   PG}va7|pw9昣U   LX   Љ\s{W.7n8 +SO=M!  P,@؄1   '|{SOo>w)vM>-  @OY 1" @ c>ϲr-S@z;g2\"G@S`B^pS@@@@@Y4     M DV     w>     4A{Y           M@d     p3     @7U      0_y$@@@@@@~IWlĈcqQձAVW^k    X}7w.M#@],!!Ц'NtK,[qݵ^ۦGn#   P)+^ven=},/0*YZQ7tcƌq_|E+   @n_ܶ@rtZNi     T)@J(fC@@@@@r:LC@@@@@JUB1     a     U p@@@@@('@@@@@@R{P̆     @9t     @ܫb6@@@@@ p/4@@@@@ ^%!     PN{9!     P* @@@@@r0 @@@@@*W l      ^Ni     T)@J(fC@@@@@r:LC@@@@@JUB1     a     U p@@@@@('0YLC@@@@ln >ܽ[3qn/Ǜb1c8>nĉ s1gv Ng}E@@@h |mv;ggdcoңx@RmN;    *|AJ߶Ύ#PJju]n„ EѫW/׷o_ Ygh:#Ən?QMv0ZӢ._|s'x½nr+Rb oK,o&Ci=gNo{ n:":)A)-G_t-X.}Ok< ݐ!Cܖ[nI=O:֭{mN9(tɜ -Pglm=>;ܿ{NgeXׯ_+[73Q{ jbn@pn)p^znKo?? v贀{fisO>č;}NA[ϝ{ՃMߨ_]tta /tIB #sw}N8]P+ t=h:lZwu6`h܊+fi&O>o]3jYaҀsݵ(h1/ ~6~1lܐ5@Zk-%駟^zه >ꪫW_}0 {{r)!dD'7g1k AuF#=yg2,:;Ȟ |O; jkk%,s=Yg?Gv2K|]1{Թ7h̏@@ĉf}g>ݡ*tF/]>}2+]V}Cj{:\i;Q uz~*]toέԇv+J-cO?>NhUCE "永r%'jpO0tl[I>DvQQ:` 0]wu&q(P {4  <#w?OZz+c_ e>>THUP?7jݚkcis9gƨR5>UFQO'8{1_d)tկ|EUVY%Z7U`Q UR?ky)^1Oyjq*Ϭ矻{O+DhvvAluw_N;TyTAz-(*z$ uJ)#6lg+IU:uYYXvU4md_.J]4" Tl_4|ȾaTKx.%)XEvaZqVX|sjO'YcjGJ2q|Nt`\詧`ѱ5Zb5.Dn? gJ[n%mbUI>gZ+?SkShVYjiۣz(T4Y ײSo{M{o]mj\^u^{-eu۽Aʂ /,=}.xqkvd5+Uo - z׻K\Wgw NZEْѽ[ k}!u] tXܒO2$|6lSr9mZ,֮κ=3#K]xu@b G3ZhVn]8bd90.j'Y=-cO2GO۴4AG޽{qV0d=azU0_V+1ƿ69J׿u?pȏ?-~;yS(Pd}RD(nv,w9ꛤkA.j9w]4R7Wzه~8f[gyȑ##k=zgZu*YZ xZuBX_X&ւ{XZ ŨިƲ.M>e%/Zڝ >G@; QW}YWT1\QEJɚn-3KV-jUH).?R"T|z}zhˮj\7۷rgqFrֿRչ5pmGάdZpZLJ;Ş9;_vV#Oͨm;,+X77pZm՜}A fQu)i5|hJc̩Dj*ujЫUN$5:,^ƞ4&=ZFimM7Z):S:MWo~Ufaꛎ38_iUS*;찃馛6׈o^ZDv&axu->=@?#Fx}05yR?rJqaXiN%qv]ozs1X@d|^Y@t _F隬f\'?:}p\|ž : m^8tT'T<}ekw}ꪫ>t}SL]Gck~ZY &y9眪AEtxUnoji7|>=TO2dl*i=o:m쬢qc@V<_N]}nT-}wu}-y>jFe#{1Z4bsﶮʳ"{Sz]wmzϽA)# @%g*4/M{Pt> ø~~#{ŏXIN{}̷.O)Hav;JڧQj!ONyH~ӽZɢ߮]`bTn. [xwI'}1U\unvCQ#<||~ݜ{|+EusDI tÀZkkǦr'5 ~}¨WzU~k6j.["_[`BQ3dtͶc9ƯOM3,SB't< 5C8ַb-f(j[i>IekNG\sM|ZO|iBvcώS8Oڏz5^$qZ/RPP锒oRIu,k8D:4.ffas=V>Yǝ1jI^L m'0:j)ޗzU S{0v{4֩r|tj+vS7֧{yLn洰mN%7eȊ5:ºJڨ f~Dx`3Rs{sk:+*IZ@qdӠ2%Ӫ]w,ع*T75CުO?PQ5r PM&{P{c?$ڝ 7 tsV؂U)#o,ŕE,vf*>^H})q=^}A1- xuUAr>DV`_^o2{X@gK7o谼֧{c. 4sdžs6>'W(E*s Ji]mUϗU$p1^k:*Jˢ;e9XL֗*pײ>L!曇HZ*(Gw&pmaA.֭}(~T{_?jJ20J?`}گmX+9Yc5'|^燀{kVK4ɳi\A(BCViV33=n= .}cw]-ezYK}Wn{n^VukYߓv{rSAkM)St|!t 構rk]JuYEi4O-v7w{`шriz֭f-m< 'Kw7H' twV +^΁հ$wѿdFΑ*}uuСUN$ ?dB-'M2dH5=l/ZtÒAd0^r.-OCohH$LJa >a:(xX@) 囊#\'=_x%A]R/X-sg"gOrSzSz}MO75?0>U[ZaXb&Zl33{XԳ.Yt_{Oƕz5@9{OY4TVJΞѼ@կw΂Eei4j;Mӈ駟wqldNg= Y>5n<9iXGIcr\{֟ړ {;8wPPWMnE鑲نyKOaB5",+JS}e5t}Hz[)TeUk~5-Z 1{  PQ'+fTjd )D㲆*@:ׯJM)VɧK.69ܰ~+Zo꫗`RY 6{jk7T::Y-LS7'Y߽V5-Jhx>M~ǗgRK-/gHRRt7.@g=U'rnTsVr):B5jTP]*)JWG=|WMZ|xd wո*K9Z&PzPJb~}0ZK w8TȱVV']r='[,Pýd]tEO3UM^,ǯ *hk@%ff:g?s0;FCK0ZJt+w-]Ԍ0\Kzg-zG9aVkE&jTZڶi?r/WmTUT#Z]5q:}NUǞRzz^W]Sw~6?CYrn{d wulZmrvWE t˂z![ >H@wbYt~K*vۭ`~s+֯W mT3LՔt wUdLY,|j[\+E=(X6GǤ^.Ί$K/ k5[TfzM`TOAnls7UOu~UQj=El]ve%kdR֣4ty g+uQ9?<ՒΪ]5juU! ʰcHyH f⍾ Yuʪꉧպ.WZ˪r'jbxs=ٚEt] E&ׁSW\qųG4v3؀jwtQmP?ժ>Y ߖ:Qßt~YRo'qS>~v;KU0:=u=Zf첋tHĬVu2Uڟ{}1:RQv gtP*yld+jSZWs{jT@ M~?uVQNe<3"ZxFo>ͯ?vRoDN`O.k=| 8(UչT;dT;+U)P77:Sk~5逛>YOX'[>5f~~5of;|rWD 5ї]>6sծK?CoOkdP+z|[XC]Moᘭů dtM:lhO,ΔyW* Y׿tZR,_p'R *)0t0*j<嚙y^L)_B2+5C`{؆X:ÎX gt=ԃP{aB^;>(Ri\xmy䑙# w\x%a|C) EoA*q~CUSjV=N}RBj??f}iahl҅l(7 @!Mg--3ӓߞKV Jd:ŦtWMѼI{^yþz] ve);S:(;L?9}?2(Hu^u]zgÚ\Am3^c((ԘCҭ¶:ܻY?վroP! @9Ky4qw_jYc½bjy?~*#NJYa?5_`{OoC]b(PAÆ WEPj8]I-Lk7e]6xcXuU d]AX>@ ̤pr!5 @54ouT^NC_) vCV)U@)U ji:ꨢtc|#t?zNi\<)RX/$eMe\@zYέ&'Y\ApO?(",k[Jš[h!RLd83fj=%VrJգΕu/U}_J/7=^֧ =\*] R^w  b<=GL#<:4ԫgqOZijYMQ`TY}}+)rAr<X}Կh>K*订ri]r Hm~ʄt25<j$iK߾}3קjw* ^)qVYk,6V:V͒tӓtQ.WfkΧL3M ީY`u,]*g}$[ڤ^WMW@>S?,jّ J=T'DQZO0-^[eاr%Lry晙ͮU=ҏfPX7(%x@V IR*=kW=O+TS% 67.}Eu.^|E:@ZV iwz wJ>ҵsǐ~*q񘅯2u#z%1Py)P!J_}E㺲BZ9&l+z~ޙrw4ڱUo}uEOբ@5&ZK(*պjf}*8SOKH3:Zx]VSA K4Bޗ{>G͗^WeֳJFWJINP95tԑ~0F]s`=S=+tC5$|Xu ))ʭ[*2k5R0RQsk&~MS]_Utw oa֏ @ +>&Fx/uzOK_jk1xRݧ*ȮRMz5AU( M{UmXJUcM)? j`M] M.O5EՔDLCM=4SOAܻ4jǺ{'CAT]99?[[n?}+UjU[@iOK-\cW_}էyQ0uFFjߙfƓur98 P{wG}QZV'k}ukzj/K7jkݔS9TkHݻFU7,i@{ eRMZSUSBTz^jR@ joPYÕ~&M=oCl"juL^N`Wm3j':NMxT d}`uirY}GѲrkz@KtJN}hG ᩖjp]bcOE5TYGjiŀ "(ֻܗkoj>_NOǛGi|'1"ICE!4hPA+ͫր=\N:~DnLnW-]dMJ}QmnkHͪ=o߸eCr~ Շ1WQ-pS`_]NY_4zhwڂJMz,Y}MΧZ뵦vuYg~` 'WepOnR_k}PRk6kj˦U]Up>|SuVsW4@hdk8jhjA~R-hnI>D 2p.RktK$י5lAsl75{͚[S }?tNaL+] :-:{mbEGiY #nZf{q]qoPn]#`u3{ߪv{)SPY]V#OEL-6Y=R}B~6[F&# -׌0ܯ_?[EVeXPt o N֙~UoTzoWM+^N-:ş}>j_EʖR%O+܆~W*:Fqg +3wX~odFX{t_ý۲h~:uj<v{\tE>GdY-:,g)$|'myqWy念@M~HP'%WkQmQՈדluus D9 fήHÇϜVHkx1=>_<{H;CDr\vꟜ^y_>W-jXj_˷\$JZZ!wYTXTӾ E5%,ڍAVG*ZS }JaҬIN&FQZGQ@zV7~:K@t;7{[pwZKvr9+ZZLG[ XNRYnuf2f}FYa ~ҁw~g:)=ևwe+w:N}ZE=U[{Rpge?}n _࠾]9䓿Yg7[fy)nFGlWmmi=gWlOA2ܦ B+=X@|ut #Qm H Af4+pW33ђlΧuקPXZnX L)*crެj=@Ѓ%u’UoJf!5j:˫d/9^M4'\l||6vtRM( Fx|(MIR.x߬Ik*LCހOPйQ=K-5^&uY2_@[!=p *[vu:SMmh~_Jy +| F(j g:Sh^{GQ?0VZ4Wd**h);n6MV-TQO}=ar+ ~hoz_U7 nVehq1ZYk;zr޻Udgy|Y R'M^׻J:U7ܞtH.jR8kX6^٫&ZM+zȤc?#'׮Ͻj\Zz>O ëZ&X'^.j+O%βYghϮχuF6}6mߓ[oT=yuS<2Gb MӞyZꁬ'벃ogWj\N?ީuUCTŚU4 &龤]Kvud@z@ۥQP,gMP*(}?8sWnw饗:ۜ9#9LUMo5R);9QǛ tF33~ܪ)jG^YZdEt7ja)H(TY,oSi+Pqj_]'4ʳʶ@+t'OsOO)͒EAn=TCvUvHSzOӃeU^tUi!"*+my҅j[[bjLu8lU-TiB3!OJŢJw|JtOd!yW:;{%Lخ^UI!5L:}hRU()Sʴ]]75ҍnT`{ɓ̄.PtW`P7jnڽ LZNjQw 芀{C; @ tA6msnERj[n%d;*Z5颠{2cJ8rq/Z;oX41cu|&')_U* |km֕Fu%#ܻBm"])BlN%(q@@{>&o}A}- 'NNޓq+Zk-<Zgkޫnp5͕ W URWn= E&jثRL( k>TJO?4LY},Rq]Zg{=OsJʥsƾ! Umý.   &櫯1 F'"QZ;ө1W:YP[oOҼJŒ,J8l0"O_-ԁɢ|(\pS;CQRպTqzjlW TQ(?zhFRE)vT]AK8L@[Fu @Wp u:/馛O?]pW_}QZb-fjIA@P_1Gs+Emot#Fp|pUN/kWr'lWt]&GrEĵ?Z_߾}}mT.ZG2u͕W^S4>ݿخZ^R/O/楗^򝷪F:W:KҶs! Й;Sm!m.JU,tl<51?|Ssrx  @(zU~UW9SQ`zѮ1^.ݧJNWTW]vY t0<8k| \icVG5\uQ/,E.%Ն{}ij_!fzy @O'<ǎ PWQK7mk̍ ]%p5׸ۯO^QC*첋Kj/_}0jxUֳjqNyR<#O5,>|{*J*>p@[5\ @O{Op @ s9}TTtQ7 VpuFA@n|{ᮿ,W_uoFfȐ!Ni'VsTjQǥ֡W=W`\Yz;#}g)j~>mot,}-{KĿK\o6 uPE@:E{0@Q@@{ #<ҧ9CCt=HE>6| kijq+~uuvi>vfZ?u]} [}Ȭ _rfv9|3Ygܗ[n90@5ص\QM~ir)l:fc}E/ku0; @'R   .'q`OhS 6-!Z ,vyg?FA=5Bۏ?܅<裏:ޕ=]{n%O:y]jdMSyՆ/UT]jҫ&_gqF/>Y_zun}=! ?>   x[VvFWuS\SmK;OOJM2)(Lþhwj:ge駟:GVVYex8 \r%nV5;h`g}v7n8N\Xbn>X ?3QꫯfyfF(T_n&}X:UG}Qk݇7j)0dS]A}=8}0ϋ/ 蜨A(< e:6Hs\? D^$>1#]^~e$W7ɛz  @sUڢ.J=SK/gS0W^q f*?x`~rYU2Nn@OOk] 䫦UW]UP>W1c_f3vg}v)ys9j`D ~xu}]tEL!`$to )eI@@@kZkV:U kc9M1vivͨJ_1ڟP;^Jr)Yvͦ<=UTQvB5jTWu"U ?I 8lWQ~;2PdQZjLo`\WÕ @w{w=W{=8  t~ꩧ|cǎuSM5O-. Rbe饗8=z_W-P 'x`WPZ[Eiq>FUs1S0]'LJǢT, kJΥ纝{C65g]G@@PM[kt;JRT儯4L{˥W*&={uºkmmf{@@<:@@@T`̘1a-tSO=u@@ yN@@@.Hke]t8  =~W_=~  dY/   @M70 {@@-@@@:E@x[)@@N Il@@@ _CuYݺg@ 6@@@rxѣmNn&3 tPf    $;KՆp  -@   @S[w7\z݀  %@   @.v7n\n:K)@@N l@@@C Wnm@@3w6B@@hرcunnf3 tf[   M:tuN&`@@{I@@c<(?^w,9+EEQNŜsΊYQ f #&<ϜO1=uV]Ml|v;UWWgի k6V4,޽{e͈@kýx" " " " " " !ϻ_|1տשShmr6qOD@D@D@D@D@D"z$'SC " " " m@@6C4#G,bnW˚ {[P1E@D@D@D@D@D@Ev}Ycq^3" " " "Vpo+:@ r2SM5n\vJýR$U@]wz/.kFD@D@D@ڊmE^h믿q_%K(4#" " "po ËG ao^^*-" " " -D@jE@D@D@D@D@D@*O^s|_b=M=qY3" " " "Pkn9(d ME@D@D@9Q{D@j7|b8ůn0=z^x?DJ/ %[oo " ͈7pooWD?x,scu~i{@[[@,pD@D@D@Dý^KD W_}՝{qwhݚk[O?QFņu-qY3" " " "cޮ#"P_c3XGĥÆ sM7]f^wǻW^yeY?#<ҭz؞{n38O5|#s]vq=ꫯBsqb-hfmo6hL3;묳ܺkFD@D@:9-"nf(F /Bo[YuPg{Ͻ 3ܹsh r2ӟ6lN*>#|0;-~< ?2e߿i?(I@hAD<|y/k]s5[n8 裏 /w^/D_qnwvk뮻 8E?SN)pgEy 28t;<'O#GlOp z}j< 2Q\:cOq .j$s5+ Zxꩧܡ^|Eob:u]t_Zjך_?g޽56Zi'5۫ÝQ+r\Y>glԂ4$eO8ov"z_ĉ` z~8U.+NM6ġ%l8&QGw=c%\^X駟@;:eBD@D@j/Gt 4SQ`8n߱^ؖ]vY?.l<w}|eYu]u#Ov磌r&MJw/9Sp_~?>(|[^r :]IN&hixmVwt?*J4㧽E@m6JL?;/ƍscǎ$+_[Kl>o߾7i)}cMhw}E(`]za; #/%?Lf>wI'5vZCΚ*#3FG;ERGg>3_ 7D޽ibz_3fvjVYeh~^ij\ 0!vm²mK< \^mR೜'@ kѨTPD=e,,s=琊IN8<ءQS?ڔD~(ڰ80: xOڣ* i6|y.DvwC-93-cXA xC +^u]!*{%(8V~py\M>226l9zKvy:Pft$>tbҌ3˴3\l罜CY;p>$F?ބ횊@KýȪ^V!@2`h"X{饗J&Hc90?xAuDuQt_xG36D@D@DV_5D# 'r-5m.&r=>J}=wn#r^saN2`>a#<ґ'a?ݞΧv?w0ZO5ga0:Y{ :ҔJ$FB /92I<Y|7Yg JoL(DKtp3tDM0hey&I+?3:N`IHH*R}MD H6 a^8've" 5DY$W USr 2hŖ|%;+_iQwC7׻lI⾍9/̩>mj$`ND,K1K ˛\l޸ߏ2{ǺBۘv%hܦTY=өZԓ2'MZk0J(=[oj$cɆ,RYaN̤3z.YG\fpEsn7 0 E'`>ؚpz@IDATN?AM|o%ε;@HВ$2d;s73ۈ!q)0vygc? ~]cSH Ra;eio: 7a^_kFǮwnԨQ<3e%2s"i2tD2vC?^}JU_ge#P<+3:DDEDK_3w} /r)N0`3]Gm{wC.L=n֭[fs˝kMF#ꫯC9$c(9p7̒fNXƴسso[^zS6,=pO ּ"kzX.n<,gL^R{\iNLDt4;,^KEN2q93Ӓ3O,CYhWb~/"泻ۗ{y/(~Nj@(o= G}tߠA2)Ǻsӯ,".P7RCtOD@D@Dxg\=2Ӗ.{7܂28 |3Mvg>Ș1c<)b0qib#IH"e?|>yMqJb;Α,o$LTTZDl$Z۳g&-@UxcǏ0g3}fgqgm ,=)Rs<@o!*0(wM7h{38sto&I 9)H 7d/̩5/ a-TNƢE%6Po9ڢK s;,Y ]v-}L|fyܹsX3E1ĉì8c<75Vhco*qH>0\CnDo(gMVѮWK%N$i}>i|פӳf1"/M4[>z>4^{q2Ӝψ K.١#" " G{~MZh ىbfyNEuiV̐+'$ZXc1")Ĩ(b/r]wݵzٜiҾb#1iLݕ`-H&!j=4f~AڑHϕW^/6cZ@ƺjpgTyGQ#k=QH=YX^Nfe7xk-^괠HGXXn0})\hugee/BAI P:3\%_OXnN;>dٓ+'H6dϥu0OsH" 1kvI4@p,b'^'ѣ3qWP_K>kh)" " /,?yP,2e2yV;Nl„ kmaԱf|M}Udوi'Z_P%璖Ohn:8F,=C6F(Ώ>hG9&'ϵ8T,pne4Qg%7eפcp:B[ʝ~{lge}F\8mp뭷% C-awև=QRp#,uXB2^h*ڑHϝ`0(몫 9m)(YhÝ:Q8WFDH%Zk:4쟟N4)] HRn@ `rz QO&" " "0,J,:v\w / KCfvgNw/îVo#՜%fu9PE:{Qwld_?ܟ!Yt` 6u6 iƐxK8p}6Bo-2Mr4gc;fR( 1[L\_K*٨͢ei itA:Y< aδcZ!H:X*v4goŝvYc/g iPvi4̂@@ZF8J-Bؽ`j?|5 mCr֜^eXg#Yf,\o!ܶĊ*0c ܝuԄ/,I\r~9aÆg[Cf̂L^*e =wLJ;ED (½," " " 5,K uWVY]Dڋ3}ʪKZHb:sd8H!#.\waꞱֈ@HF MQ+P33ȏ)S$0`T&#6MJ:n8Eir?Τh@'#le\(s n͙썯$q&%Gk$gN3g>ʞr}$%6jg9Ik:O$$$2<.(PsJ5/F`$&<5>O$& w),AQ" Zwq^xe]n2g$#O\HLnY~&C瓘aTe]WdyD[u6e+ן}I&?r:H.or8wFp։Lʮ v9xq;#.de(j$jjjMn!F&#ByW쥵юkFhO?h+waϋIbDAUO^{"cH]*dk+\|l%"жȿVu޽mԎ^Ʉ$,'i)ɉCth^ L$Ejr[r_wy&_f5XOPFs9~l5/-yhU9@]߾}[t0V20IW~裏| 6Y'tNZgΘ:%H^e/i2;1OGC"mӆ$/aCbdI"c#9BhLTFZI;!R z!N/ıfegW*q؂:.oUvC$k|EYKCzﻼU-l^r:5h^:k 7t={vgA9/ I޽{{/ލd$PO^E@"ܫ"PqD ccd>N$a"z}^xREjvj;!C눜Pl>}ᵥ)½-"|p}&PMD$H Ր1"Ѭ|#5"~ 'J߻H-(F'B"NS*ijAMXMĻĈǗRZ-8DΟ<ɔw~oXc:\Mq˶jxUW]5nL|Tvd" " " 5BaÆ1gp&>#z$U|s|ÇWo#ĉ}"Vc?0@"JW^q{ǜ:OA&LpXL*Vu3c9,laJBTyrmXO{r&U޳v2ځ{NgPfuڢE (ijQ,Z)UN@*j4xP2eJYT ' $B Iux(A ܥ^lL+1Hߗ"G?Oà n8ipScm8l_؁z8lJ:O=Tc $-64$)I,)s49p!;p0CD=",R*o0EJwEr85{fضkda=RCgN9?"|۸oKRg`o=.^2iwo֋竩@ <{ :2h.jss]TCd{>8ٗSy7nR(Ƚ:~2H7=Fp$e7G} tu/A:&o<J p.^ֈǑ~Ce^8."Yhq:XCŌPMÝcY_B ,y,"x|g83Q&"  Pp<4B9x6YŞٯ=)HqM7406up)v,JbjKS#ן{y7!`%W}ʴ?kk͂L_[W f-? q~xNmiҁs;YP[p_nSY;ZNYpp@U0}=g~ΤUnwny[s5E5WeUTgbt&8M^;fqdfJbQ_j8E,;q5\bNp:(0M'<3o[%VNa~+blhdOE]t3;l_E#[bRg;3gCTE瞻dHLǒNN]t;+IaɂP蘢ˆܻv5q^$,r36 }2hI6Zpv̖<p_." "P{LRGeI}Nl6d;s93]vgZQLM&m6h&cR%g.]anȑ7|3>HuM'%.MWlt˪+0o%ŁMĊ -Y ߑG鯋{g QD#T:p%OGiPq.gr5>ʝHwi-G5QXsXg mo_m-6:0RtD̵ulFQVjTN=T#vm/nw8y&hFЍ_=02UzgU@^1HD@D@ڞww}-~1p eq482FB%a-mڒW^^68rYdEa >20ݦgXǒ6w~'Nh~sᰖEH 4Q8nOvqpuQ^"R:DCpYO+qԲG 1 jl ߇,9jLݏ.Lq|7?=-OFZC#w2Fǻ#ye"PIrW6&(O?ݝjzD&cHs2{օD%eBDz@ov85JE :;q$A%e駟bidt,ɫnVkP˱SNwY;JrO#s (lN)ϋ e]o^ W ui* ^}gc;$d'pJe͈4or`tax#aeo˼ێde1㝡K.O1&ZW9qTkGvP5N Cc+>|h]!e1C;Dێ36XpIK/u'OvguC V[$Ŝ8ۋ#B¦bmC\K'LШC 1I pq^"7dx^xl\v}w\AZ'_6OxZBc0hР0i UŨ`tA&" "P>*ݻCqۯuvti:6ʟ},%'SyƪQD}Q(IhDRdU@U2i,zcƌv"P9yq7KD-$%t;3cb3;ݔ)S毾!5iꩧۉ' Oto&j]w 5]j|9Im^ D9Dps6w-s 6_:,hرc}' |t;b` ⸌a4iTǐF9s={)u8гL H-}kFȇk I%p ME4asE~}O$[n*{VUio'B:5VD@D@RR)!̜5tV-w*`9rS",ΛYff }l]6T՗ѣߟzlf5ElgLuル9sgguQFd&c>kN}o3p ֻ?7K:~u"dtP,ӵk$M9ɜqi[ne\ ǠEOl/K%f1mߑas r}+!N;mf2b,jd6>CU⊱rHɲ{l,ǹ{$\gx:z뭸6gN2M ǜs2eVՙZ/cQuiE` jGsrLs5rnuVjUyྰQUUy.jt!{FWF%" GUD@:./8qF-@@&d}utGFr3ܹOzmg3Dރ(vs>rMD-x^V> x~7O?kuv(bJBEf9pDNrn(nt{9?lHk@~g6slo.Y?GDc[9 ;Q$T}9hU2kP}Fgd@b.aMT<ǀm>}܃>Yg1Q.hIі K1|>!hY r2"1U"$ȧ5ٓ@&" " Ft/I|D#C;2s ,@,25H A'5iMAB\ I{8N9s0Ω\{8/YЄ)Szp/΢CzH 6;-޲a#ǢsTJ'%nM;&Qc;p6ch湔\ư5r_tbGy;c^Y퉴^{n#oH!Fas$s'R2hES=Jߊu(G :_k"h:vkߞ8OMV+ʘ391X. 5GBRIdKD^qJ0 fI\vǩN',M1#MJKcnrI |8;:)3oݳ‰YRϟD`#B.jh(" " ý>:&"PHZ.Mi^D@*IF p^}U/úw=8Lc;zV?(-M`t,A'G\F /y@©\(ÇqjI,J6j(?ŒbR C|,^,-KF 5B #Gꩧ O! #%=!m0p0Z'*"P@bND6pCc]wEqn K.D*L/`w@cH KEM+H MJi:\O3(o8ۓO>őwү_|e߬-آ`[X #~ᰪ`JN;-eJ-Tl[|A٦"ЖvI&~)Fv^;Rg""!Z&* @BS:'/#2p|l>}ĉ~? .ס_{+s&ҽ{ww@]vuHɨ3 K'ɏ?)]DQtۯX'#j\Ly}@g„ > z62{W8.$U /ЃQOkGj߭N=gqF$]t }]?rHX{> uw~Ce"C^f;a:x[`yuJq=OD" @xI '-R @{I6:}錖Yf!ߞڥ@ hLq;Z}cǎu~i<y͔Ow$yv'|o?~WԈL;Ae]6-Dm {ѣGq!QȪ:+oyqcƌq+R\%@t"=%/qѝr7ʈ,EՉ@I=t v[%v 0*]26It $y/>6L9끇 ,5h)={t|cD#qͤI BG8իWA/ti$}Ch:{=ߑH ?r嫯rs1#j>3JΝ;+~:<@e$ ˄5׿~?"igo쮾m!eM7S,r*," 5F ͣ:曽/&BW2R9iJ'e8[+Ep#)aN:${"8c$T|}Ջ/#UJ|ǮwnԨQ V'D>nVsjm>OH+kncDu]%pTnnr|x DFjUVqm[ao׿Ր+6oS-6^yƲdxmzN[yr'L'HĢ)3i:e̱-3UfZu"t*cĪ92sd)Y֢j 9ꨣ*|4S&l^zzi.]24)Z/lIzCނz^~u#ӧOmeُ?Y:evZp PS:% ʤ gyff:sO_zt\Kh%$,F8:>/1Sg/#]tR}Mw"ܡ ^C " " "PSnF0"Ѵqꪂ)Q駟>3q8nNG68>ΰ̔D￿馛u{YB?͑`\"u͹f5kT| BZnW"{'-Wt}cxׯ_m>wg}v3:3?߭[7]Ls9 7|S NNmNy?) fDBv#^{mשSPΔh^Q9&ip\"^~AN*{Pdz>G Ƀ{:7]L3[ouOt~eD$reD0j ={׸[nMR잋;Lv'DP{yW+5*Pi!ps=UjjgDNۻcu0gzdl9zGg;3k:dczsLW=;cw=3YfN=\CsdD@<믿\CfN&m!Jߤ.i619X&n1~$Pd4#)֌pOrKZ`\B/\S:7 2:s(Gެ4nId}wI2<@fz꩸rdNP :2.@-H{z9/Eչ kУ%bVVyOj&]XF妋-,4Ƿ^$3MN;Ř9#(: qhu]/-7#:!& 7{wyg`dsC7Rsl;Q2%|ƈRG;5G2d,M.|9)Se]q9&-?D#ai+:X!{sp "wuW?Q;L'Hmh{`='kX;<Ljtā? Hpw\'`%R=\;"SK9F :ԗ#)=#'YC=otLM_^@CxhOPZF" " " GN $/veRZ}g[&K$Փ K/;JI/X$BRS$0Keʢ5#`0:L^Hp!=d8Ip (5d! mm!bĆ龚j$c[tE_嬟m|{9@e l>ί^x!E{HZLS[V4h &uVO<H,eUbJHqttފNkoC*,֨kiWFhwIʴ vDD@D@ZWiԕ%lwr2ׯ#UD@ڄDw͏2YeU3]tׄ*3Έ *_|IUQH+/&nO[KON Kr6`\O>_gIXJ+soeHOG<0 C}:QD$"KZ۠e]'N3huMzME@D@*Kmt~>cn9K‘rO;C{؞') YO?(m٦NBd7׈/o DҊQD`¾C}Yf)i[r<ǐz뭋&&6 OW_rW g}˚hD3\hk } 3V" +n B6"/w[o*5v|=9 ~2 G-=JҷqϞaQ x^'XD]m 7 sޥU7!C /i{G A!gp 7n#e=}w*_}&L:pb8Zk-wI'V[ͯ9r/?x4c<):`䧴z1}~/·He5C$]w;szD`ɏgq!x@IDATPܟ =xq3h#M-^w?DPÞx.$}ٿ"N#tgq81~b#ʥ mcdB ȗ_{8.܂~>M2c9#6HJйB`-{âo3=1\#xn)vLD؏~T}VE}t={@usb5[T9 9+Wi{ߓ́fǒs9Emv9X)B e渎1 ҍל9X}5Lm>m.vxFe,>s g19˪:3 HʥKچ>Q\$3=t: J=z/k/%X>Xu,o:΂o D6JjW^&"P@^z70-Z;~[!k-~ZtգG_~8բիOweY+kqJ" C4k-ID4"x Kv.D(o|&8{?De]ܛo&MW%Ld81*Fo%=ȉ2LguVwxЎt{:1=oy#Hr8 ;DOwN~bQPAy0  cvi>i !ȸq%\:,u]P"I&_X\{ :SoGBntΰcG>Y*O~0bt#'^Ռ;6ޛHgm* D3b֣Gw1ǔPlj_'IjjTRRax/>+wXSnw'snke͈T/Gq; O\osqY3#0e7+|2^˶t7p>޽{-M@kIʴg(HXdO R/8AF)N7tv!bQ^~Z_~( BolF?:Jk.ӘNZ{B[y&w{>-jO}=Id^RFTE@=aKa7ME@D@D@!G.nފno?'VkOV3" " 6l>MéJ+Jה:B4}foJC=]tM#:@kkG׮]_[=AD@ڊ@~_VqE@D@D@*C?p>;1Vַn6-<[[ᅬ{B3" " " "Ёށ.NUD.YC֨5" " " o,hƠ-,XB\=fLlꩧv75#" " " |E@ V<XD " " "P6bsNΪeT',uc {ujId" " " "Q ȳQ[Dn=wM&Y<]$qTt[oIB $4pW^-" z׍!" " OGo^[ce4}OZ," oeaOɬ-^ lЭ[`yf*G`/Fp[nfif|4 VO+/?޽{ujq\sUgV@J@E@::ppNXD@D\y-_'5FIRd* 1b=z?+ء={t=\Q뮻Eh@J@2) ͋t8ppNXD@D\3'3/ns*O w+^|]e˚j%?*.Z-" 텀~I˕P;D@ڄ@>]2mrtP&Ntf i;u˚d{X->Dnd ,߽()se_[D@D po4""P;~QZ"O}TNmi55_m2dHnM]y]߾}#rGh9IED@j@V3IRvLD@D 0ee7aQ "Ĵ믾g9*xUU|+K<sEY0 TQ{W;:w[l1?Iu3 oXRy-袮ѽi]>3|3+ov(Sy"JISkdD@D@:wxƒT3#M `IH?鑧vZGɓ'Zȍ92/wE? 3&Lp.lXwᄏ7n\A馛ί駟bnY:?pny9A_~y7h 7tPGN34n3ʟ{nnҤIm3?蠃ܬZ ý"U@PjrjHd:sn^Rr23X_UQ$ѣ{ᇋ}mm<;K/]G>:Y믿]_y׻vorDLۣ>)p '_~9yy8 '|h:pY'|vߡ&2Slt>G!k׮n}}/WJ]c~щ1bRαcǺ[7|V4̀][_wZ?ς0GAԂ4@x( ŧaVS*!IX<⋱6eT|ML[-YzpjvF9"Xb _|7?v|pZSO=emf=3f88䓴o#rx<<$իP'ȿHý$7XpA$'S W]Pێ}H+,<#(0 inV ,@T06pCwo!/6q;DgJ+v_xcq"qJvev),)"q6"CSI .~裏t@dhRO7i^@ٯړh O?/!k[r-Hvw6rWz (ijxQD@D@=Q_w ;[˜y;nú[fZ\st98$%pNIFql+n4q={fiЭ[7wuZQh.(;zg9믿^-] $%pds9}cbqfD~n6s\s1bD::x`[3rJm4^N' {3@`UVqr[e"P)y w%MY#M'0^{_tmc~g}3gif&ok\[֞g황2n_wj+kGEgƛO?ugs4ZokloQMbP4|<8C[,9 /Oϼ=l\}}֗awSvfpDp/L d9(S8Z/B$t~pU{gVsi/7|ӏlM{.0_3Ri߾,NZ^-C@Z['chkGP@^]/.9'}=^n /o?_q컟o⊖.ubZAK.qځ䇿;6̎(ռvۨen ٦qY3%&K7ۼPm"@&M*؇Ġ/w饗?9+mi/^oloýފو}M7uw}w=I:#^bieqO?}!}饗 ɝPt$F#_G1 )YtE(ʄ:3zhSy鉌b-ϩ:RLߕWZ*bNeshIj^ID@j@^HISk456)[|kï6}ߑ^v@>1G¸G6^{m7_"7hytb=k-8|qY3K-Q0ы$BP)>c8jԨb\0օ#>kh{}6v PSg;:C q'|GFA[rNH 37 ꪫ:F@'m){衇:㐊"pC {fi&++8%7rI :ԭ /+58O|W] 7%suy,6`(o>8z'ٳ?rHA{nML䔀-#h {~.W欣s`;: D|֒.o]-v4t]㋯5Cj@L׿EKEZ)|qJ[O@VhmWq \3Vw%u-7V"<ͷUk9G~W@z-,M9=_S@u8Vo&&O~A9cgH-+dk!ھ% "9üMX.MbMмT:.:Mh#3B֩tEweFJW_m$T2ĉWgeb] ?sM|/Ǘ&7d#Pwq[ԧ}# N5GPEr~ c+-zݓ=eYDL5PKTVFjs 's^k72Y/3شEZ?92wdcNdO Tp Tv8v>Myr*kICs ? y\`ȓD#Hq~ҥN8˟<tI *5" 9#Y;G"ʑ]3z';>~ROm}u{|]N>NdOwg]"7f=~(ڏ}n{=y-R^٬ yRF|qt6|'y-uxr7RCE2jI/Pa/_LŌ88y` :Qy/6l;3[o/VL_$orz YKyDPza)e—t;iK/ 2tb}w}mۈ' N,`30^x|'ʖaeB cذaEW(i?p[$${'~o={FU$]NpX5曹,msӗ|96sӆmyHl~m/#' hܥ~VK4XKIS^+yCN1}ƃ,q&o.w mO t}vkVkOX8vEk=j7ߟg]y;(3dCcN;N8`i!6=9u?HEe[F%_7n=`2qI$Qt$5h!:h#[2TgoLj8gϐH Gc3po_Ct8q;P, ?t(/(sꧮЕ֛'r@6ؠI ':p}\-8G"lH -w| #?R{Z>?߿ 3x $(:@E&pC!C<8y_H;$C#F4 !5>a#xa!ZXo(Tk+_~UWU:XwtK~1Wz ͷ ZڎU+v\嵯Qnzp0ܦ!c8 V<0T(aFO}1|ᦽ{i9zv\$Fj|iaGyg;贗DFKmym摍3eyS!S8F;)l1CR)|ŖӤFa;):GvM+K ??=j(EF-[gUܜ]8=3HBՆuߡz}݁>c.N-iRlcƍw]9?)'!WƬFuߞ3.?Su^ou~Oϱmmxl1g;g3x;"*mk<:v9j3GȪrZz)aʏ8Sg;pAF}ysS xYnEeiVUS\D]叮prodƤQrG!=7mm2&7"K DZ7vm]߾}2sd!<!u<?0Py}} +{#:81ځ,kW#?(%_V"Кwǧ -n8-扖'`08RSݑsPOswC9|r7 btwmL e 9I}囻(KQu~6S7gU$X$KOہq&X}݋#':y wHxzZ<_é1d޺bƃZHCVxLqȃ Y 5FY2<h/V0E~=TZOyGDzpNupb\A7?|aڸ{}xx8ʫܷK>t@9QX_uNq?dNzg˝wnߓ?gM?d]m.nX?mB{$Sޙ[5|x^+ʔ1@4)SM2(EDyNMD@R"x뻴vp=s}g=ڿs>{=p /:2y #Nq.Qm2ϙLo+h0˙W\^ŮVuF{J+Hpo',e}Ѧ![~?Hʢ'8TMD|JAXAnEuw ;$[wIޙҴm yC.5;DI1HE_lyo?~ۅGNmnBQHw1A @*DheY~n9Xc7#wX#@5!}:DRXH 4%[#Gg.^ɒ:uMo0oA=_@{P_A'ByNG< 'DS1Q([NjL¶C)A>#y\q2~o> d-[w|i#65Oh5Ey[-ጞxAɄGi>Ski) 'ViZM X+iL#hUնuNXb(ӧM ۂMѿnlkm'iY$oEc3Fsom|OU$-v>65z5}zogdX+Wx-Ѻx &];g/]$ +cC'~G>?ܶu ~ޡϹ~^ԪS_!]seB@F,pdpIEUgdK!=@,A]7 1kH{?sv >e_9^I@G3KE˿"A~^Q4̑ jܽ:6fY],qISY+~Mbf)1&Wv{bbUk<@fCbHX<"H+E3v"b1":v{,$6Lָ'Aoh -i#| ADz)d{z|{҉M CV,A- )|#YGk*ުFȽh6@Zݶř6PڈqH کd K6%kF =sL޾G{ƒ`'z'…ܔOcBv{Ͽ}:Ovs磴Tw%/,6?*c0'KMf{(j:$X3rԐ4h$ENglȥcJ)5]6%4v\8h7TbAtE΁H2 )mA :<$K&F'βxY5U@qZYm?LDNuFm@, JIֆ4 F3N82rz -"conܮG}{*bȩƤEKtZ,Q8t~쏌37u vW#,e#:ZMDF Fs宜E⶚5좚xB9Z tON̮ͧ;3h6T}y,2[˪0-S1z :=fND~6sXǸ.4w׌hukD,Qܮ٘e-'֟5A)"\G]3rVn=fS!@Qd,Nf"’lt\Yf\d;ߐljԨ,fy< 2#<"mMK }Q?Z߾}MT?`{2q9Xxx(V~`E^vߎ;좙Gӎttlui~ c]hyWO38gA $U(ii9sSXи2@GDȜa ߇{Ndae]pA,^׎Aa撥#^nbH +;]QwN=opb/utUcigEfb'EƍTwtfmr.LLYл wL:e!&^}T LL ?ӴH&"!$!zjEƍC:xM`)X$|ѢE^?ֲPD@5t@dRy{sN BC`SO8(L Zk-]Ɏ̌mP^zfjAsmr5'P7)y-s} Jɴa?׏"!#@LƌM>]!3L@(;xZ# t߿IaPinݺJv2O]pρ?;% Zz"ҳބ9.0\V^cc1~!qqc98JBfnEι  sB]`DŽ#ܣ{qzMkbu 3JsN;nףͿڳGجbnbYHaUBͣgPn'b7޿/XƈV9Ւ7oW:-#ɑ/:[--ig ҷI~31y8#腢8?H"w [dN͛7wwɲ  !C+W)S!DQcWvYpt~{pPs.] oOsY]+FS)Jd<=?n(BCCl[⦐xp V64V]"hz8& `m؇@Pm@N橋9%ms~F2nXvm]HffSw}\Ekvww*6=[]O<=W&pO ҫ  |zU?[+xv>u1G m{ߚMoZZEVoщo_~}v#~A s=^y͈&:_ݫq\;N93GԧG{~/LC3 2>A~A [Ӫj&쀎hI ?Xxy,gxMJnT[ԩ'̛8HDa.E.?2mf"Cm$^`e d\jmvjQ4]i!$0ٶm[5p4!p,$6 c=o׮];ЫW/ zZ4hݤl<V!ΑpAMT!okCεw6ml3?Q?"+i!0~\%MzUHj)PAG`,ܳ,5PQ>n˸^Lm Mll)p_b: ڵ7Aeߺ|_H~quʿ=D[}hc}kͻG^(igy#׭_z藷Fӭ{?1N#1YnmZQ|s2UqFdG\v?n @b f n/0)H0K?7Yޑ+&mZ2-C2gd5dp\uMhVzupzDD{w={vS1 ci9A6Z]ȌԓhZwADzye˖5oj)uQmuiOСCYL=G{= vmf.vE3n-ZÎKӦMC˗W'|xKD!Vaa>2Uqg8H_rLc.ESӇ2YG^¨"ym(j6;1)~[g_2n/sU&f] i4_|4-. k^w8,^6Б8qXv^HuZ[c {EhDZ^k_Z#9βx8WiKxkԧ85*n׭[׮Fg8C@Bq׏z4~&]|,IlAyQ~%2\R%qBgN*{]GPrZ휎^SgN'E]Lw7qm/0.anMϼ~*_ZFM5SvG/LV+8DW7INV;#ܭ.B<ٰ#UBu u4*' W  0/oimMFr6Vu?yэ;Y} ^-n^1!iꪬƌQ>'Uс bnu>RM?% 2ֺ~=wUA@A@" {p2 Yr@dyLDX$ j6RD C #&!IKyG/~vn:Y'-vܩ.Fo„ fr-SÍh Q=1ht$kAsRb%MBߌB!m#E؆4A@nTFjuvLBcfLC+\XkkwwDmwl6ҿ3/V &!_2ᩧ9T.]V Zx:̃#: o.VCb ubӬ&!!>хa m|3}naK]^~テwE Uw:O?o1sf"AʶA`u]k@<4@EN/袐CA@A@8O[6fjΨLTzR51ݻw73‘LvFqZo)2?hd'Zf ا_#A}m#B}ĈaT&v1tZnA Hw2IJJ"ܳw9KFYi5c1jը@ޣ\;Sk\nDDVy&"YJg)>̐nߜmӦ myÜ٪v:Uo]tֱIcC<Phdjń}]Zw9_8V_bLy9ӽz*"|_!n4ExZ 8d~t5駅 (nnkRE# VT?:EHe|A@A &B;S[#HQ()t։ʶ %Æ 3E-ؾUk"b }t1$j }R|דǻ:MyIF&(q%ȐN@ 7o뤣eOg*NGrT e3x`u.zƏ5rM~d'jTF6u\ & 2j[lQ{ȌᜠBw#q0mtҲI=o+ @A`#1Hwg2FD#rm,9Jo6Sl=qssG3]P )_ڱ(㑌s}m9O…Kcfi-w~NE- q">9U˫Ȉrk`+u1.0duԎ=<|W8h͊9bC+PW)Z P,1|Zi:'f8m/QSm5+Vj*h.1A@Hx]_~1( wS-W׉ E0̦);QӅu u3j~ ']޾µ$2zh ϡ{Qr*ZUt=S8{MD=?VF^'V#$Fg~۶mfBb13A3QEyЙT?S R4ȋẻpd\֍H$BZ@X~9  q:0)٬A>e~KA@A@bF@ H{*~rM {ry( .^3 ڕ*y벐џ=XU6m̲A@# {1#A IpRA@r<g v Jجn:lZBrJ,xԁT}]62\ĉ%d$ b @=ceQ>}zO;s mYf"jpEQ1A@H?﯐b RÅ ̠Oҵ12bSuaú5Vs2҅#qFy(Z$l,d7NZBbɍun3%KU&et @.A@$)*zŸ! &lŊf=K(o*낀 prrXLgx9XLM xq&MeY/4o%=sO,s=w֮]rf+uqPBf.A@A c1~@)o4hj(IA@H\9;N5U+ FCXMo~XER/eoc:$c:`ISA@A BG(v,XPM2}_ؑq5}嗫:ݳH"5T9 D=G2xA@A˯V֯n*_޼޺,L˺u{-W m6{nUT)/ZСC?C!zgDł;v]vŋ;/16o͛U…:VФ @=ݐe 4PLb 8ҹh!+ @ 0jƌk(!qO?˗{=V(]Z] ,<^z%o.@XSթ։2rH /6VR%K݅cǪ9@IDAT;wW^yʫb3g]-F{Qdݻu-[_qn+J߆ׯ*?Q/6Edm \̙>C3N ^ $Cڸq^7|c\pK_bMo7Q" uV#QBY6$>|;Ñ @c,gA@҃)xv>UlYo]E* Z9DrW\&Mfpn/woHW}wnsopiN1kɵ_Ujd!xpkTzԐ!CL$nD:a;uꤞ{4DFa8ׯ l2uM7O?T>e]8^@"sU7xcƽj֬YZj)[gwU]w]gwꩧf}OK.!c!pLKkA@R )Z\ @G`5j޽ޅkPIG\9O:I5zK!Zif$Fm"R˝\r%ꮻBDR1!;#~熴 *Fŗ믿kOTk8z.޸qcUD )D}F߾}6m6@w2w ĉoc8! QHP77uA [; U޽ՀT׮]UڵUW]edڵkʓ]֜(pvRѮs~rA`u.DR&]pIcA@H5샌.)j  |#8oA=H.9%L.|ʕ3R/t;%z;MT3r-G57nDـ|A:qv xxס6Qb@!J6OdEru,TÅl{7$'8;r{u!E-2c"։dSd4d, B){" d)yҁ䂓t0q|J͟_Q0ULH/ሯX"4Va{$c/XXi#dӎ7N͛U…c*>3ŕ*Zʗ/bZe]f 85F磏>2. pw3bHars/SϪz|dN>L ^[#krǏomdGIKA@q6[rҏ9p.~ѷ֪^]On Hٶmw~@8MX"=!!d{4dv#QzT~N5dKNŋamqDwShC+Pg}\!o>nz+ږ)S_|y5uTӯD.2Vvd4kL_2EAof s饗u :Ӎ~ (P۞-[d̹@*ӎc(6KRSdTVsUXd skݛ( ݭ[7CS*UgsI&ʕr!˜/7#X\ z뭷2p$i"vJ{fu%  YM͎.s:uI'em'ɴ[7A כ ٴk׮tMA ̞=Hw$&5{1#97o^oFۅVr! ']Ztjժ">ȨD38kƱ~2}~wn{ OwyǿY8p 6pA: ܵ_hDYcݙ&LFb5f5!L~?O+VS/Eכر6>})ii(Ή k8j֬4h`u#n;YF@"܃q KpuDN&|r I3BվQÐuY^:^Qzb9 g!?1X,b@* @!TO:4F.vv0@DadZ9VRPdH;oK/7V*T0lcSWTAd3F=hnv;hu'#gu}cǚs}dx 9;Zd&@[{FڵΝ;lBbHXCƇ6h^`6]9I@8-"M{8c.\$81z ٓdP/:R.Em'˂@n5m  p :5ile\9Oո&wFC!!qDu5߿!,ƃ}!JRD^{jРA&%g֮]jI"&L}WBb(JkH) 4c.DBr~thA!af =j*2 .a iFDt -& P!!Ac/XHe'q?@b'Wl{n{}ѐDO7ԅ>4{Xq@ϟ?_UVsA@ #70֭[h}"׈'#Y!Ok=lٲFz.s]8Bb5p;H~ئMs 8Y߾fL|VbC@|7$!׳F!J'nFb@F?,/^ی(  C- 9sg-J]5dDnWe Zɓ'$Ȥ f. JPhHHd=ѱֈ4G Cv#FgzjCC9RݻhBӈh|H\:~cl6Z#"7_|B[@d@=xG3zlwڵlGײַ@y9,32wCd=(p;? &>zZvbh{l}DቴByOH?f1瞇 *EEw1w" ` /hT!A@A@g: ޢt^½&[v&R-9s/hN8! وB%B(t gQc@Bv WZ-fdOvgRJy}\X ]gE*dDʣf͚]g4fDq ib! kHru]gW=ې nS-CLu!_O#Hv5D ^$O͛ÞNT"qZy82 /~$=|^{Fܴ;Rp⅀x* #!Kr^$~ͫ/SHY'N9ϴD dȶ0e(dkoVC8c-[Td=O%5DB2cm$&G8ߣQNOL)ds2j\|΍d+G9 e ~# {Ʊz1i6BG-l\1i*O=HvQp$= X[A@3hN8R07;|\:&35QrVnyDerNNj)b8~  ~̙B #+6GUG:˿[oB E:`lk޼jРf8!`ӦMS522Tpfz *ϜwQ_9?ǰEF` .P ł9gշo_eg,*TPO<9p{l>uTUbE ׁ 5)\C~|RS`Otwr#a K.MS=o_F_dڲeK/Q>}ԅ^6ьzWZ/O5*nTF` {1U\dMBHwZ^k]uXs- [=vNmǫƍ{ 7K1Lp1t>#Ҋ8'Vuba)`D0`ݻ'7],  =KGgwF Zɦx!@$4ӁEb ُH@JG[n 6mڸCᾈ'>}g!Ř p,S<{s7x9R/TBcGDRx.8@RlٲYfr\#C|͊z$s.Hvm&֎ wqp$ВcN:$s/Å^|Y[rǶ9^E'pPeB;Gs~}DoqsҴTyQq0 u$7,F gA@rbmtzX3db  A Cv(\bT".NKGP$0ǒA N zR>!_8qͭ\UԵ޺,t+WyVun޺,ɀղeW_} v~O:A@A ;~ሗ/Dܟȝ~j閦.yFw뮻@۵kHB47n~G0e&.rSȖ'lɒ%jرm۶OMy˖-CP5QhuժUB 2qym/Yo % YYPA !0 {WܡQX,d )jYkU] 4P_lӦM!9pADVA@r&r͐} ڵkUMsYWEQ9#46OI@mٲ< @\RSN 27r;d^!74c<ݻwX:ѣGVZUmۦV\i?; D"HNd ,پ}B>{YS:Ak+ G{ ?O?XA@<9rw yԌŋm_uHqZL O?~rs'~9܌\ @ i)?^Ym{WD,Cj"|@~aլY3Fj#-2uTCBC_~Y-]4O'bJ*cruҤI {\4i,XPɓG Iyf~znOI4G!֭9Jl\{' :34صk!V缐`֭[:~KΜ[njĉ5еkW#q}%:ː7n49k9m8s _ZռysUbESx⟢D}畛]qig'7y3Zϔ$Ⱥ_D+)sMM J9Ku7A@rv?g 6F~o)p%y e G ֬YN(A@A 4~ȅxx&:hjHs&Wͭ!/PL~CZ}s|w+"z!3ٝ?zvМh;-hCjcD134ۈrv.8\_H!Cʹ$ g2ϟʔ)BtQի+pFԨQ#,& &nϚlwVo?>ydj"jBRw/HԄC}r}oH;:A@r_,Ý:@%gaWegBt𐵒ŋ<]yX{fUݐiްaC?W"+ 4DGd޽!]Q#ڞ >{ ńkHn:a͚5D:t(j{۠@'B;ڷo*W~iyԆccիjöD⌖[oe$otp4!+Oukې㏇u@dO2pf%y%̹ ur/r傀 BUŻj57xf ^sz<EϜb9{"ۖ` \hѰݷ~k2 .&)}=煣 Xbs|<|XxO=9ܻ~<dK1vA@r:N' ta+QzGD-n-X"!Cd%e8% R y4!])p@~"S4 hmbH Btv$jժO>Dxk98єG{HYSOE$?8?׏΁/MtQ<8փ>OH ɇ` 2l$+#'{AB#  )*-ϸ/+k]_X&mV2Gp2>lD)-s~}U-eȶH[VjU̱H"}C3;u W\a=$>ۚjeҮd杏.pg Gz_:ÃsF=652⺘ejǏ2w%PBƖlsex|vNݝ,cI38  qpLDS7x8Mi~hC͐q%f~m߳gO~SLQ҆3 st-$UWEƆ,j&kщ^ǹ!5p$@ZdsT\, ޒda@D/8gsn]tQҌŏg-uVFj}UY;d.%!A7:%&  !̜wrmk%W O8A5Ҳbr,֬Yc{իW! ,0Ri]^[ Đtu߇E4XAw'"쑌^l/S!K*x`Hz꣹7o^5zh#un26duF6kIdc,q-i4x^Wnv8j'J%;BNnE.AV}nw9Ο#{FRrK?h"0?|_ MHnB#ܓ{2:A@A '!&6([kMWWi Ů]jͻVN2"b7Q;hi$_\߳gm+A[ݻo]*1OoofYHms 1O=Έ|< rA#6d#\#h,]sr2%K;!JuJ|pTVZ&۳p8p!{v7А1ݻא5k4]B,uƷ_-t5r0LTP@ hh;i- DB#X$3#][׫IيK>ydO r r-l}9رltPdsύ$} Y㗐q;R2ls n ᅦ|"<݂# /K뚤PpNm۶MMF1H֯Z&PB#diK-a8!Y&6$cׄSXt8>@,gt,/ TDod r)^7H[qMÏ#?$=~XƵ' *oA;nb ~ܢV2  "4yBoE%.^(8&̝u_cW2bYŋ7Y6h "&8C=mwIz6qn B(}W^O?[NC*3Wj ${ND'3$WvǛdKfC EÐ8 r~80e]fB]AݺubJ~!{)=>3E4[C!-ze۶mKw (>@d&nDĻ/nT4G!@/~ԩ=WŊդIL_\^z!Aa_ˇ2El P+#I&{Lqzvm!R@l[Eq2,X _n7X )Ho@.AɕKA@22ֱqc( N:x:uuN%]&%!GfIvߟyΐX+F6Ƅu 9 ߕW^EoB!)9ؼyGv8G erI&Y/h oݺ58H1mi&ҼyL!aݝ]֐gv7Y^h"od<䓦(1dbhhu&2Ef͚L)+{G;RRjAjUnυDF O?)rqa,>lajMpFm&L0SX;M;|"c"3w\>!܃qI 40d:7P7SqL;K AY#YJA pܢN픾8X*ciUnvIΟ@\ݞ"zɒ%m33o߾! o߾6@U iD "\F'BCHQsFB7 E^mqF8/8p ɫJ.0VLkժeC)hHĹqdK'd1;kݺuޜ@M3V sk.qű;eEi!ِfň4R9AZL]1DwӖ"b5p""o\g!СC7ooq _Jwp?68!E=?a>A@A@A ҅^rj|BD,1ֵf8E0/ؗ:2sV5;K06mf#Fx#O~A= y jժBjÆ HWk-ȴ!Ս'Օx" FmKp ,XE^XgO?68 owinQ! A†k¹d|m(|t (dd; DpDGcΝbn/:tuLc% IM? h|ԨQ\%kȞw"_(BX' O" feL88رc~\*^?Ns=zx}йsg/){58ڐ](#eC2 NG7|`… ='c>d{=cQ "R45E>T A ؼ}dtr*o3TS]TL5YCx 0w9^Soi+_1Z#_{*p%t&Yh ;TǢ[jݦod8 1)"pW+ $id]kݺq!k80)\N8z1L8pDj} GuØn?BSQrLJ!4| d3({<p[Gȝ-[$}fӖϿ4ý\lR6<O.½ܨ#GG>ޢvl'r8'\\ʜ^ogkBNK Q}i'X#@T%!QA &1pg GAA&H; Gdߐb ʖHf#qD#j})wΒ!"?H@!FJ?QD: א"jC8㚑!JfضDUpc"E†='Xd'# 4!,jPTyʔ)in??o? ~mDzKOGgT{d(p;F?NH@@֐ w9w>+ޟ1 {4pO ҫ ^A$ _)وSUڴUg˷{o!B߫G4cb&$=/%X"Se ,HwHNESܒ|ב\ W3Rv`94dאhjJk\)Z86w>8gwSH>w8,:8/1n$vUzM! SHBGEVeGNW6dHi:U#ixA]u%~t$\9ժR_nsBӈS3B _?UK똨gDN&)?0(/2ESf(뮻L'ƒk$z4s#mv R%o%Ɉ>5q!#9IC G3Q8ɅԒ%K"3sl/F&΁|.|2" {bA psL$ d!?6M UGkDZ+} 4Tl.$"<^I7kQ(Yʗ6]>8ǎ+B= E @"oo؃ %[i30$d(QwժHnQQ~)DQpK.7IओNJ3)Aمh3] Lڗ:ʍCמ'=c]u[p]YrUZC=m'sA !D.]Lu&-DѢe-*5a&כ r BGQFW%j=ܓ&yӧfsPq߾}Mp?ƻKWdWTZEJ *U߈GZjGMLp#ud`h?^&DzeD$ŗ%=?`Yt])5G93wߩ{\MuY=jmU'i]e!KůsIH!ƍ^|+ڸqbB¥b@2!@:EPu R5kT}vh"腴~ZF ǐ6RG衯AVnC<# RC ӦM3)IPWHvӑ9O!Tꦍ=Z1Bmٲs?` c;,7VHPC< ^]zb yB/}5d{ջg11f9Y lZZ2fѣB3sAOTS-By YlwN/m׼/[8ywq ";v~PEsS7?yd:j/WRoN4m[Ri13g@8Pi~)GH,>,\JpיD{PȂ "{y/$t޽!ѷ]gcA N kuv"D%}MK5'K.5Į.SmH0ݺuk̉Fe&=dBzݺu i F讳ёg9q3"۶mrcG=qu1md %<[>)pPXۏm1=A/_>էOu!m9 pY6px߸b;wTj€ raq1A@bE`&MMŜH`uNeڹiUN;d N)^X# {ƹ D\ iV@@xNDʓk_.ypMޏIF&$%hj#ad5D#BT/ԡC<ڍT8e}N]IaJ^J-j!@!2rTձIc/MbԾ&#2OGHccISzWHxlg?Ի^(4 ueۙIg.gv;$ρUY튂߈l_kL<4N˯ꁟ }wd;Yڌ/"M7힟8IҲ5 ^uh|T'+B,aSY7b Kjg䊋gUHbT^xҮ=sj< 2*WN8!Im`CTҮ_*IJY4YI\H*A;Ft⧴w~%:QD Fd{n2b}w2VkTn0yt:wǠAjf7y,.5{ou 9N6.< n;\{pw; www:7nM6U3w6+UyG/G0\ڼB5I{׃vDG{n#FP|a`,޼1,yXk c9;-b'נ^/0UdE/_S=oם% j`Qx`a1%/^npS`L X+ܭf[;W6j,~DO<ߵ"O_ w.>|8=ϺI_?W}~¡ ZV &lFa(c̙ԠAydC]͛^Yju߯%M1cRnL7u}b1 Ӱaèo߾5+pהV膛dL˛O)u^k@Y⯩@Z ۹K6=lL"$E z),/nX"}%u;F O?q[H`׿5Ұ5#זּm?~GsfL 0&`MZ7nfbݔď;svSF(?eX0$|bl_7W޽{f_BA@]7}tի׮z[fL ,hSmسgt?Kb&J>}z*R7M,n?oL(/|VbaZvR1KW|^ qʄ{oDNʔNۇ5pR$ŋ[K M  Ȳ ϜVO,--_P/ZBL*X­ 5Tc?y$tG!s tH)M|(Na,+-JTq)U  ֨IC:uGiבiJצM 鿝ZXxzaA*E$tS||R'Kd>&e b&EL~baL 0&Ogl= pNaʹ[l[5_RTŸ;R%~Ǡ8YfWΝ;ҥ͇ z)R$ZvXVy!t ]tԮ];Z.~|@ 9aNRw7nH7+-\*si&+`t?5^C[@~Z6aIf0\n4ALSK`E ?YÔ bʆ R 1#8+;H<4X({O&̝K8 -sج$\O75UQikyLbokع9"RJ6j,?N)?@ї hz!Һ y 5w00 2@|>'-JZ1L"vAwrƒ\`k$XuL 0&U)JܨrzҢ{޼y~JrM }[(ZngyC aQ+G͛i͚5t)z)͘1C*ᵺjJَc_Z-%9| z@a 7X4hW1buIGU Q|y*%ʴ1<r'&`o_M4gPikn~>H"\XIR\Ժ^=ErfdLw\T0vs3ClE/XDZXk״s}CW:r%1psqhw~aޯM2 KXS\H+\KtiiEtvz#^w,^N>[cw2cn%w ez[a a*_;'!-_f5bmں~Hga 0P FP`L p6 rK?ؓcJKP?PC1 V{Xޔ@m_p!zo}׉*XCI_kذiX'KX NA_;wKK""0M }+L EkW+{:' LKׯbgVjRnH GO 1I#O'  Kcυ[1ą bA؅vA$jU(XM )xKXa@w={F PZ++k<v4/_tU…}{g(•@No}{vK O .pG )I" ^G(uT+FϝK_VbTG ; #/KD5ʔ Z\n .RGZL 0&@`{@v.T/֭#Xg˖2 2d.`pvrrŋ#h:MgW7eZrMj8XB>h9EIȏ{dL  G`eIyk8\6nu(Y (d"SmÚlymj֨.E aP3A"p]Ȣ5X2|vL]@z} 0&9p9S٥%K0C/V([jETsw4zvIK[n6pd\:pcn 0y z ?z{F MWL |Vn٪FM! )XGAyb*>_tIZ*+WN9p BO-ٳEIe; "\ wɜ9E$3 >l-ȅRs䠩 '/߼dER9^.2@)-S0!8' E%G5f5k4' oؠN?>ϟO94=5d-+aM8Q9pK.4iЬYa1B5jT7ns 0&;= = ,K.ɱ=xV=f ;%pXѳg  `L =H,!G`9!(عRҡCnr׭ &`L = .)S%L e ֭.-8;;QA }ϟХv?W `L'UN/=LfI|VzF/z5jdmx9S5[ƍUs}k]`L KYgLn 榕˧P=HQ WY\٢4u@`L yn+E>jJS& -<'@*iֽ\M `!)Ku=D|naݮӧH>9eRƖc7ǚ.ϖ 0&`Hׯ " `L 0&7UEGN*ω' ھjLYٮhpbԨQtMՕo`L 0&`;Xn;ςG@(ЯPx%`L $/t955STO` bOpUM\3&c7fMj9.Pƈ*W*pl;v$dذa A-g&`Lv 0%+!˛{cL 0']^thʔɨp*ω'p]pE5\VE >sq lذ6oެd͚ڴi`L 0xi.|5d=d8s/L (pbL 0P#izYZ?Z$蠑+ 0ϟSNj?DTeaL 0u&rXr'&lMcL 0!6'X*ܭ5t":j'?~e7Nˮp\Ç;w(7 <'`L 0$ w|.<*&Bp%`L GҎ՘ʕ-JWyN?;гg/TÍxCpč7hܸq A1 `L 0xwI,|1=@s7L ;?m`L d /XMfMk֪Y㻈k* %о}{0b7s 0&|`2! ν2&`?  0&B>QǍ*/<6S~3_bESҤ,m߾]>GԲeK`L 0&`XnχG - [[67`6Ot=5F џ<'Oӈ)mTpu=gyVD߾}S 7jR M|tf3f 6 QH80K@oTǿb!@胻`L $16xP:_|+6ҙGrND)b5kz]iN$ cv|=ybŌN|F5Xbp?~FƊJ(ؽ0zV M SJ\vΝERƯaާOԩ+!ݾqzvo:^ܽ-/1kլ`##a;A:wJ*UGV\IwVerM͚5SyNǥ%lO˛61p%v}z쥰LI$=w}?+{4}RbӤ0: ?BX@z!|ϗ=5Pstc ?%N޽=zT6@H'J2dHqܻ`UɄd ׂ?a]zvk*vͰ/G 3c*h5[#0x`zV˖-J.D K*ĉC۷o9sl$f̘4|eڵk͍  h'5YE+V6o,߽{Gp73+0onY&/V 0&`|[Ly^aϟEPGS?V,*ӧK%e5]tUh"`la@Pn=FK 妃V4\P^J>EAW*8q^_,]/\]F>5&O]`.-W*$EԤYwyKԢyB<ڵmH1yn+je+vу7o!T1wnm .\!=|=ĊhxZSՁH$7W/xژ]sذq};1ML .oEXbH<Wޢ-{ѱcg \NBhLmZtTҶu~3y?}uLk;(@@+Wm1x͠*V(NcFRhMO4}5~^ݻ-K`'H &KmPUt@ӊ)U&P;׬nLl˗ijۡx7lךƢ6=p{իK.$IBEM6ѕ+WT 3&`{Wa~0/*| 0&`-me}Y@.(<ϕ2e\:tD6.2ޛmڥJif@I +ycb]JҼf= z$<祥A`ץ8޸~8Jz{TvXs ,2+Y^xVT"s۩FulOjcLU5_aYn7d˚AK +vhRեjAȵ 'ͧoiEC ~}3[Ծ]#; *ǘSОqϙ5B^_dv]jаT=Wt&u U\f^JW=z Ԯ$-;ul&hп6fI/0hRǎS,rS7 Űb#@1?{2㮅U}l5Cor1 AdRZsm>ǂ n.F-"KJ'yjy>sdEA%ōtЁGjղׯ_L 0&[;sY3& W $)Kr Pa}cER!;wRnUVPCy=s9lM֚T\J(Ǵ%qcٙJfMk@\%$IE; }j$)pef!E\ZDIBDaI5d2pv 5[/I$$(K oQK$qr RlKt$7i'_uw +|bNh%9y@%Jd"p{+=^/Bxŋ gg'$v1еc1R-"4jV k5G,nŸ^9pAG`\=rhR:9%n쾀,zj#?!*Yb ?s"XL+MQ:;d`Iݽ{w54?Θ1*"KfY*U5Zǎg̘1-qU&Jޠ9ln _gL ?b Ϸc ,w1|PԆFha)lh<&jmx}LZ"0^a l/wXe% Eۨy3oE a ZHqtªyB?G3pB0.7#=jS*O@q~#6 Y(b>}J}Vt׸$M| ƋoZSgn(|E`ҧOE8S&|{y'h6Io *axZGkIr0#j/'Z4F]#X0OLΝL,7̕7=-h_3pkd,?aܮ<>V7l"*ω%YV{-b5AYpnۻ-*飨4wɚ&-4H#_1kM~wF0a,NYX\3w'|1D} 2ĈdIG)Q~kkVw-KQFVi?]O7^pcɓ,f>saL`ѢEt!uPB)}vԈ… 0TdIٳgTߌ-Yq=3&𝊅 T7lؐp0&p/_x%aaV~rW"@[^?-P+W*x}zV*Q;peqZaTòL鿅E(d185:;vLݤw4X.` RJF/{[4^1R _aA4+U5;5`^{U+T0JhG) uάdDv4iR(.`xD`w,!l/Z`UV@zI#uxʔN2`+ z ei?_?$'^z# v! r> NS w޸nn%[ѣӧOWJ N@H9\˜>}:@jՊ=*)cǎ-\^3}_Y*,L 0&v sÓG@"`ԏ04ȗ7,r~*-,Ҥq ]b#2n!CNaSwdΜuΟ٪E?uߠ!3XpkrY- z+W9 G?YI9; > w=3QUƞĉ%SB@l.Vj(olǽW&\iuҥܴyvs!*V.(Ujn+,)_K[b W&U!`;'nr'`qS5ۋ,*ω!sٱcpaeQxO1Kdoq6mZ{aL4Qg| X+ܭÕ[eL c )];ӕ+>A8nsGݻgjٺ,~i*Yr^k)Esv;LK,L׮ݖc޺m+zPBYQk}0$*lt3+l@.UJIc; ]F:u;'o(WǏ"uTZx2tE˗ӆ Ãʔ)C'OAҽ]h%nJ[Awq͙3fvic3Uƿmu<.'yL !`a( 6LCtN1:U{_uX RS@V hْIdдtEi<}bo׈DlPrX=6U!/#epO0EMc֭.*[my:y jk|3Ktw2:{jk!ra)?x`':ֳҿ&0Ӂ+ vhs!|9AI$.G]oFkS7 C&_ps"yx"rA%wn:E Яoo[i_^z1baRYK UT%9y]壌3(Զm[l/-[RHaՁAyYܹS!IܹsSѩB b\Tٳ'(Qªcƙ@X$ЧOruu RFԁM<==I&ԴiSu4kbWSZ;|f!N( M< aLf' @ƍWlgq͘1C~ Y\:HP7Rt>>gN&64D E͛wt-\o{q)i"-WySw>q 3G*fpOµ{!!xi EVJB?J_J#_G6Y+{cezt \<=3h9SZ\k`tݹ t V[ Wد=&!b*kCみA?y%n5q1gudxPk_ = ^:Z(F(㓊WEl%KQ> j~9m5윔T7Z͔ ].-ڄYuFcǎUyNX 6LZu -Z XLjCKܨy2%~֬A[`Y?dzGȟ?~%߲AazIcǎ˕>Yn).V[LNJJG(Y=0~ݟ-v!Ȃ$NX?"-mq>>ϟ?E t%_K+-M XXE[n˩S1qRQ̘1Cbv+Ѫ]~]Q-H wqı4a" *WbL Ǐ6lfaH"E/߈vEL `lLT.v~-S UVM9`Kq*?av& &9(mk׮M߾}s >33&/q'#F4R | 0@xԩSWbL 0 Lqݺu-oK2` `M86Çɓ'iƍ " ޷;%CfL 03;K/_Wws«<'>=zׯ_wNSVyN0&u (u}DW{oޱc=y$H`am/9;PlemӪu9e3 vD OEWo²a*܉?ѫWo(z(Wyn $7q!0~ɓ)bĈ69f`L ԩS:wL_~5^„ t8B+C2a<<[*T /Ci.\baL *1c^ R+ZL =PsGH؍ pϐ!|lɝPJ 铇~,U9N`L1̝:~IUkU$I;ƤyvO`͊9OlڪUoۼnti [ '( L޼}hDLa9qy]͛ %p+ԤIRxYndž3@CiԨ@Mh$XĹ?!p]ڷo|Ӕ3gNa5ʡϘwVav3PWpG%U5[o0&`KZQB.yVH֍;4u,5.en*oĭw=s4_ktm2#0u1Ջ q7J*ѝ;wD}+a?0ʖ-+]b$o޼,L 4pР}Ej&el]ࠩLxDL 0&t+x4RA5}jm.Ν;ˋ_qae36g@dʔrAUVѣ%FAǵ9v H +$ly`LKSZf3}%kUk5깨<':OӠ j׮A3  ys̡ׯ_Sʕ)F'O*RōWN~BWxO퐎;kǎ״?p8|pJ0 ZtiN>=uդ:mڴuI8%N d}lСʕcǸ>z6 _gFJYdRJv\ RC)y'JHפiҤʔ)C2d7ΗҠZ݀ 33nr,݃mn XJZJ1|Wkc8F oqކ;CaL 0~IGGéJ TAu?]DjY>3& E?|2hQ}0e{x({tY#8qGoߦhѢ/KY&A qrrɓKKpXqM_l~WZ޽{Zjtiy/bĈ5kV,;nϞ=\xqX`"/_uǂѣG4f95k1T/tR_i%. .]DPlw]ݯ_?SU)0u2(LN/$OL h"E ?heE gdjdp7E1?xxx(_`Ubv 0&gwm<%KPkR5|fC`cj?ot*Abe֭[e8 3fP^hѢJQ Kwm۶^qPP'K/^L V/_Nmڴ7RVhꞖ@իWS$IK.1cFb[h!oܹTn]S|e>p%J,ZnMe;e:&NH{͛7bM(5jDգӧ(/.&L )R$]M ˀ 36gB^sݺu 1pQF_K ṾA9VS5.{ٲ'ƣcL 0G'LϿHQ"II2`aL h*m$J=xlp͑2޲e -n߾=M6]&-CÇx$(k7sX_e˦ݒg(a 8@z@َ1jXRJe|Io޼)єM~I4imG_>(Z7(,J?WN3&+D/VDM6T |K8aU`L Z:1s(^VrL ~H)KFJ`%ggg{Ouc1PǢOދ&qzƻb͕u[2 ,ˠ cL 8V;{í +~pK-}Aohώf(h"y'q9[ns]tDi‡@JWUصury޻-~L}>3)Ye_`J˗c y^w'a<Bxr[>R E! `&f(u~݉pɀH.;vK}PCƂ)S$X}߻wAŋR9 "6mZyk!@G~ M ,ˠ rL 0["D kDl߾ױckpzAmS{wnٔRN[>GmT߿rS(BctXR/3fW/?uO;JuNFP? r 83Ьn+Y?rJǏBe*;۔bej׬clCT~iJfHM"g|'Թ(glϝ3L O`wv*XZ9*~52d s6XvT~#h%, ܽ@@5U0䀯OR>}hJ{1pf5%̄ҟ ,P*w9pGE /\h,YnjՊ^/p-cj Cw'l2ki#4;J+RϽҬhڸVl_OS٩EGNw;3]+)abm (J?y;xz ` S4kLCU1&`hNȑh*ω06~AM6Ӗ-[ɓ;wn_]xj׮-}X}sxo7o+*PCʗ//ϩSPGuVyyy\˖P7(,JiSظ }S/ʕ l8fpwγ իZҦWRL6F؇j72k({Pf+ںVW$EK6ײfϑDSЯ^W3-&y'?cEkϞP.RTq 9#(K+Fm; ѻ_qGcF jޠmܽ6_սkץ5hRۿb| 0:I>QwnKWyN@̘1Ν;tQjРu ÕIrʴqF*[,A 2-,۶m+"Ji„ tYS-[*U?~F!˗/lEǏPB.,!X4i[K*B( ˠtzơeGk׮4k,ywЁDarL 8c B)y*Vf׶uf?FU1 !"1]$qxU,}4yi;bljG]#c4o;YKxzP2Pu9=43fIOMQ/qƦ*+#sLԿKǻUʔ[M½F]ַMm;Һ{԰3KE۸<'F k֬R tBw(!ӦMGѩSjժ-Z4͛|ȓ'͛7!Y\`Y&3Ĺ&p&_r=`L +,Œp+]Uןl峼3KWRC~P]/YO5.y&`Np͚=:KxI>{=L{wz&>xD;o\%-r_gi׶|MiC~&:X ϟA;(R42ׯߨCo+g C=6{8/:ݱc64iRG$I{9rD)]\\PnTe&M4!(_~Mׯ˱:\E?\(^t)sm9.(Q.H>LNNNePX6an 0E`ܸqsNH]D` C1&`kG3ފg ʼ|Ν:B92(ޝLe)j¯_?U7QL4nrz;Hs'+gwGC32m8zD*MJ_3h̅4{|z1SCVfSE1C'Ѫw?Z# RƵk!SXsi*㼓שBc 5#=;?=Li"e2jҕ6]JƵZ;,܍#eZl͞N7T״DkTզv=_3pǮ>3ɍnܺYVE*RȷqUNeʔQ>峹X%jAG-/_JѣG)SRԨQMVza1Kwȃcrvv&|wKFSax{GUXʛK6Yk7XRWk+0,ee33`!AՋ/ [7n՘+܍p 0!?EY/-<̥V&itriJjp+!`&*v'VOjϟ> +P^#Lw&fDzJϞ<Ƶ[QM)U|pM(5J=i'SΔ3LN UN"EZɣ4H:~͘?գVV;{]J 5SBY?llAO?iͻ40u`%i=v E."Ʒ7J, ID.9˗|:s2ݾ@{D)RJqT,[֐-G Rh~aAͧ^Ё1G% -}XbQd4r5hQ#ӸUCE{~PN4iкY>(,*4}3&`ʕ+E9B<v Mk+ +3&h,Mإ,Vݱz gP݄G{R 3?~|/JOr-7nٕ֭r_kG"ΩQ%)8r+BQZU9@vMh*;*OR0obJ? 7F)#FH{磿5'D`ijS(8{wHK< }6D)a9݇bwھi͜| GҥeիWԪU+VqcȐ!Ԯ];UG.Aɘ(:uj13&O&]9C0.e,z2J.CVՀޝLjykK({oJ@aW'qJAvM(w1wy,sv1Ke*Sm֬|79X=/[FUiL~q,T_%|ũA:aS7ʢKvߋGiPTJ9.,_)r$Z})%JP'ID {Iw5c WnHeWodn}:ȴcNFp7 ,I V¿U;N,*Q,kũS*yk,lh0qY gt eAkC?"D@$eؘT ipF@cؿ޽ p^\ܼm?m&Y2kY>3&f͚E3g5|D puue"G ?ԃ^-[ի:Hl޼YJ# ZaMX֞ 0`#`lAS-GkVfp+)oFXʵT:8sGY75ax~ݵwnBqsq^ݵHq? ݤi,=o̙.mY֯Z@ׯ7\55lVM#V-Y:ܗ_4Rj8iץ%=yTչ鳮4 6 G?Lʊ9gLklW^㚴`R)9x ؽRmNu Ml*ۨ*9NuoMTlO7N=q\i}O9۫ >ZRl AogUN:j6W=Z}Gz13 7Ō =aRBE'L 0C&H~91Ðɕ+,X|ݣk׮G*Oŋgݹs3Siǧm?\PXAK:dZ=̙Sl%8wF O@R . 6&p<={P׮]S NRN)o޼M>۷t{…iРA>HHkpkQ6nH9r]kiM]cJ %Æ \3w\^~DhҤ -\-`b׮] 7g 1ǎ3߹[luJ罓la{p{xL 8]Wp[̭=Jҙ<]eRDݒ6nkMuqƧUjn\@˄)Qc?^$NX1={%JPL(C 2y+mVZ%(ܹ>8=8[V&J?h0 ct @Zt& Uk[ĎҵgeXqQUF`}CΥ_Bf!#QlL 48!^xUj8ueUP9kF=yxh2HB?|JjGJMi|W$CU,j496^pf3 0Іǚv5M,OD7#Q5Kw!+u:l/FCc~9Bf/۱T$kjmGQ^I|x?IK$FL N>&%vJdgMUknn{=a"<5ۉ>?"'M_b5xK5iY'!oԴm?:|2gr:fh7ca'U _5yqj5Lk6"ئGZS޼u/!X4)6mJ]ӏiԦ zlfrVNmoO2ڑr6w!:`cL 0ҥ\VF-T G4{Rffʔ)jt)I$/_>+OcT D_p+<==Gc%1i0(H.gvx`k;iMpc#' pIDD#?;܃;fLc de{r1bI8|5ݤ1Cq#GB[+^p7?2 Dޝ4"DWYK%T} &3]8{YHD"᨞=zD-N< !',e"J +L8čӞ`@l32\p4eX 3}>r)VPUY@ԥxAK?JrkRE콐9y mIfo28&! 4@IDAT3{ޯC$l(䐱B yse[CbV4b`uJ%ϩFԟyӆO{C}^-澈.XAFP_V]l@0~=UVk֝8JO≎mQhiGܯߟ [nj=iҍj7 5vʍ܃ݺuBWݦc}z`#}:MDzt}A$.3=jӼ^3.cL 0?@9dB]>-[RRCӧO p8Sϰ޸qc*Yb ݻ1Wvw (Q%6Pde HؼJd0xt.ra!@ inՃLRh>O;C-W z^Au|㚭tZ9f+K]=np`U쩷hy),[0r)xGN3g 5Դ^;y|zZ O&u&&q"Y*>M-uZg(A9TNc|uYȵN; *]W:ߺ\ $\y,u\)|.W$e$b&]AwTlQJ) iڨ00.ATbE.Kˌb5HbBrҹUZ S)]iܕEo[(י9cZ$~ڷOv.oSt;FFcӇ)gL2)5_M2R6JYAoOԮ^nN珥S?ڛ]rӴHؘQx"e }lL 0&7tuVZN۞5kV逅>!QӧOPzz{:68`I0ٶ3S Fhݺurn$TH CU n-"#XR>*K(wD"!L[>%U+:EL[V3hhT%-={PN)RdjoG,`~_xDe W6"|EuNZ"by* Ӵu#> {?NB[3)dp/+6z5$\*T񖔪X+hpc 5E[?4׮ܠ>]'|Dvnu:'%Uz@꥗ly4icZ|5V2dJGX$^6 [ W=uZ5H[6 O!݁ZbL< Pdl_K{\3LGS׀$4˷q+V[&٭\+}DUJ[R29,tڣr!Yjvb52Lg.Nx чʇӉ 6gbMY6Jyޟ(y̿_<|Jd͔իcJg!9R!}aφ^se\[\;_e!w%?`L O%'ӦMK :XhѤu턣tVkmxĉW8vq]YS)[FbRګWNСoz1gΜR?`7 LP (wn{pP p1) L?YRƾw|4wHe+[N5ˋ.ǀq4Jhb7&Y'5까 dC{#ߗw'RH}x4IϰAgЈ>`.92ȉ}ԍ4Z4@wnޥBǽK>ڠXklu ދ GG=6XlZp^Xt~LEԒ+gRxl!K8LJhWnV!Sŭ7)K"#F4ݍC'icabo};@HX|9S׸qcѺe1 lY5Okj^zKAGEBO=K&dTp"?xGv݈7gKВU;\"yb;V ufᔏ'/Ɠbc3(;kރ[0&)DM+h4$I:-vAq"={8T\ҤI#uo޼)u!$Mƍvdڴi'6U/uߵƌ#ɮح[w0G!BQ,!np3vw w*2U(_ȻZ' vBEEb>v1%]Q(uZx\ *&PdsY"vȪQەjـҦlWW,شX,ֈTэףZAuj&Wmr._ 0t(sF:B%6%%i7N+ۈϊԷ`y *# ﺍܩmf?]N<9LEæu(~x4ml|l4NTqMj&M]-;/~nk 4t!/3AK D[B~HĻJo^>ڟ!o$aP[Ʉ/uLlT)󏊫-t$|aͰg;Tvmr2LNrBr>gL 0@MŒ'O:^~h,p`$T e0adwmABY=7.-[@yΜ9 Z_|˗ |&OLm۶ OA8lqc sAAu1&|Jʄ9C;vmF-:s 4rb3dCO'K'MAi2RQs~3LIL'%$=WB=pX'IXGp"2ؒQؼmcy@r0~LY2yZ3H ݏu=z4J&\#z^tfk׸0gy]G6ҧƵB$L&4-zTsD4{, ^H"Yk+&THxb~I6q;A}$^غ%5xzb޴q+ ވdžJpa IE𳡴q+&j 6 o!d~\k)a,ω$B:Fϒ){%_lfL@#FЫ2&//8: '| le޽U]pkCB}޽{tev =ztڼyhÛ4i"jͪI1zJĉ*Lrd ƇAmӥ~ut5逇ݜ~2ed~ƒF v70s xtG3eɞGa!@̘1Ry]]C9<Npn!ߒ%48MPkrB7~\+$o޾'HzH {ؓ38# =I<-# P",@SQpW<K6KrBkBz)3&Kƴ_~y({a K&Qq9`L X&p'!r|͡!C]U]MرcjDVJJWP;]-3=izM~dɒQz2ϓ'i0&`3D=q3&>2gLCr;\H) =C-.R(z"lܕԴauJ.䁴6j\Rb#Ն64JxȖCGϤJRhƒG3殠]6cA:7ĽY VN8c!)ǚ3'ƙ?<jB*Ikѭ;EV@GK׫kV:jKDd>lFoJ޸uOlPr0ǥ@.Zd%[lL 0&XVV7dX$H_c}-̘1C1bD2@ ?PeƖhnrK \31&p`L QaÆ Rj-P!fJnUJS9)i;hH+aQD^Nmw^-;#(R! ׁ\ ?I'>(ҵ}A:4X̝;GfzwMY޵CU8B]>Q2DNTp+8LG }0ybY-Q#'NSl=A4xƲKp^ďC6lQj0C4nx`Lv..o߾ 6Pݺu-p+r0JÓĮ6ZQD!Dx1sn 3& &R`L O?'u5;Ȋ‡ GM!_2w`V]yDZ6*ǏE9mlcIչ1{N=7Uw-cT70Zt^{ͅV4w3=iQrѺYm?u!=~~™WCϑ5ɆHzk14o4b|OFMG8L-UʤmLxi_/\o,tacL 0#9sf^:_^Nhu$_"ƍ9R]Pԩ\O'ZBm޽2PXӎ;*HpOtwI 1&:p'|ecL 0&+m[A*F:[7Mor{N+}9ڵKѣEQ,B&eϦV;0kcsM>Z:KtZV^FkDfOLZ#m TFaDc2lY:{;SVFӿXNɒ&:wIelZ@=TG;߿?;wNӧzl$IDk䍩!I:k*Sx۴_3p&B-S w$^8`L 0`JK挢)cҝ{'G){)Fh.pOןGwޢѣR:W<,Ջ'Xl}htc976 ҦNIΩ[+V Zh|`N~y;˗/˗/t!yMF^ *DݺuˍlܼySz`c`„ Nұ1KXRcL Dp7y7`L 0J@4кhĠ.fm1&,(P@jÙ8qb5)dFrRӮ]Df=:[FJl;` ޒQH+`vfpm>gL 0&05}Z'ڰ| U2&B6qJg:իWRӓҤICHd+A͛Gdd,X@߿ɓ''''')y>|xGNcppo0` FsēyV\`L 0F)Ǐo03&eϞ]>L@!w2&I;{!fL 0&B,z*Ƚ!YO$%_?N.\`*(,4*& w;q&%eHv>T4CwIpC+#$ccL t9JdڮE!t<-p^4/ N-ZEJ@("0k,hph<_#9sfER :wo#ܗx边*.gL +`cL 0&`+O )vL 0&!7z=] 1bPhLJw3C$غu̴N )XYɽ~ːr[}ٿݦ^w7"F^ `:߿i2t4%p0KӕK<*6xbg/(jԨ..4+"FH"E M{UX#w߿l߾&M sIaÆzWC fsTqېv{k#{(JT q @0'0zpWP wgC3p}D˖-#2]p!LҨ/|O`ΝuVzQ Pf͌ h ?AׯT3fzl P^Ѿ[}NPbD/`L 0&`L 0&]eSN޽{n9NTR%2H } n* |eއmMZ|s`L 0&`L 0&0% D'w˗/u6LHɊ+v\@nbW?`L 0&`L  cǎQ 1k֬gafp0-ԩ=K_mwno@7nׯ[iL@b|T +W"FUZie[t tj"FD%ʺHFW'L,_𤳧P"y@seڽ}=yx=}(74LMig u(bH :GouQDe3y:@G^ҷo(SrJ:%ʛ {wе+7֣"vX>SZwVȱ7G{ (,\ŎK֛셬N0>+[mjm…o%[}A?T7= 6+uT,Yf `L 0&Ky;v]__͙c AKmfڣDBfZuhJOK ‘&3u֚*U-O`L 0& |4i=xznKh 0%%{rNWpcv#^/6uH$9p)eS[[!FVF3[R ʒE$iJ}Ac9MhѩpЕ,WM<GD!Hܲn08fMF2x|&@R%KL>c4qDT\1mb5~rK޴e]j2=yʤSعI?~wOI%1eQ'#F>|>': {>{N=:֛}"r2nPC9Wӵ޾OoޕÂuarJ8T oT/y6J^xɺ VQb\N|Au9V/8`L 0&@^^^`L `{3W^;v^QLѢEbŊQ)zrUnݢ۷ӥK,Y5jDv5W\yLH<9eϞrE:txYgϞyN>}d9rdOժUylqK1sLK ''';wnj׮Ў5v2X'O&7|$J*԰aCJ"a}lB7nܠիSl٬ր&kgnj"ݜT>#FLOG]3!TtQu9#&.u GS}æՋR&833DJ+4ie:>~цs'jپpejKD3_= !<5nQ_[dtby,C7$]0}6&G hKk صm\ӳ4m<reѩg[RԿGif= &'KWnMd(/ 0&p D_͛7Km3"$dx ĴlْM +M޾}ԩSU) ֭[3ڵkh-e}S#:K,1rT>:$ԻwNG붢MƍuATk1rHjРM:W(׳g5f{ 4p *tgϞn (mL_D}РA"i|0'ˍQF6M|k_|!l `w9J}97ʨuDb[j5,꿉߱Wӻ7^=wAF=v1?8,z*$2x^3?ݿ{>O޾8&OifbKPǎ_nD&&kE]%DiDȑ.ڿh2=H^K)J& VMO׀RuW/]Qډ$4mjn4n [QUWH7!sX1iRgr+AN-,Fˆ:Dnd 桶h᲍C}|Vئa7`L  h8@^94amP819ϟІ!\n4uԡZj M Ν;5Ydŋ9NƌC;w4-%Ju68SyEjv\>]mf[nvm[Cg2V/_:ەLQ<*e8p[|S5zgIVEV c4;4s9QhŢJQHP;t7ai"u1C֪jI]Zצ&JRLA"BYv]ԡU PB go\L̕jsQjqRͽ2e͑2e +kr-nU˼?kjwmP?~Kg-{4B$e&pZ$&|`p1&`L 0NMl2eDYfVZQ8q5W!RP!rss9s@]f 2Dm=)iҤbNtȿh ʕSo:hk ~EŢX96P'1/^\F'Ks̑Nrӊ߿Sݺueic9"D0ׯK' +%j |(-#G\[P_`wk̅ ^@C#$2das9yt?Z0s,}yb'qI/~grz'tt6.Wϵ''û(QdWl~D6{kЇ޷8`B$dm^]> +_Ms&4IsHhK߭ܘЖ}kG҅MMԚE]MG%u1[.LODnM iHБ\;֨e&-4v; ÆM⩊o{aL 0&@3g(>\G4˗/;t9"gp\oذAF=z֯_OΝ_8Çk=zГ'O… rYC-p J-sD+ylh-qĴvZza6mu8!G_-z+Wқ7ohRV:xjZ=CGk {#dQ׶m[Y}I8ǣJoܹsem+R2eMq?X۵6&[&َ6B22ki'G}3wgdžgA߼]/Z :vJʬZ2&6l2~DIzfjѸa=%{j۸ -7IM629qldlߴҤpC$]Ur>o(5m8@2duplV>)[n`<"D8j,=uZ(fHE8 Dߣ~ mrZVm;%?qZtAdYusjg>a6@P2eͨ-{D$Ku.8Mm hZ^4%?zypwZhWD:@;+0+ި/`L 0&@!`>:,0$,cҥ4ar2'D[#zڜɝ$IH4X75! 'ǏՈr؜V;!\/uH Y"r\1D#r^/z~ĉ}v KX"@c#u z |l61HԊyE[Vp# !i*Ȑ!z-d<bxw-7;cޖ +)=ZǪ^?keѡfTXlcr3luH4W*#W,Ft?IA L߄{pڷ](?!t)MspZMex-ׯYrm =W#U\?>lTZ]unFmkklݏ{zNCmd X!p=E>P+8qӨ'6xh|M7D }vᩳ w<V; A.0$N3ڋ/}!ttUݴf:A(=ټ}?yk,ٷlV=J&`L 0&=ޥ#F͂/cH p{ zve&Fa O?}6U͐ٮ,Ekk)]e<;W-hĤETvRD۵>~0v~nX&l B6'LL.˫yDkGO +Ki2R Kg{2C+M|HfHHuc!I$8՟ X,ٽe^z73vL'_| EGc%ji8NϞzʲSxm  Hx>}$XqδÚeȔ6cY׋f-muU.X2&`L RCJ )hki/YppW^]uXá 2"m5wwwZbEyӱG2VŐ9:sʥWȩ :m'OZlJl*TPj;D׫WN*"9# 5`\{ Au3?mݐfOn4s0*sdMkq8&j}:~$)p׳1c7]Rm=ɚ3{.]tNKyйS'T0ց=[[4uFuh#"C8ܻJ*AEQ Q3f k,dc@]JQ~SH:9sڌ'Nwu+9ه:oen~ S%ϧmZ`$hє@nì^^- r2XÆ]ZZX/ɶ'䬍_ ]-\!M{`L 0&@%~cXR0pzr!cb!ɧ6Bf8s+g;u˗tӤICЀݽ{RsY5jpk (ë%;057c;*5pnbl>|۸/{[WZP{oN"|e8h[iˆSNs2%JWXһuсݛep"BU(OlL[:6sSyJ:ᴇaLD7ifs$H`H1!_HOy<H>p;k {CLY3"M OinZ,hiE4*uJݿ1޽}G}$OJST|- 2.'RLfvr8i-\9%,\، ,]~'lC O>譗˘`L 0&Bv=i$z)-YDD2E L- ]s* sSVŖ N,Zh֚pk  رckts?C UQ._8%'mMTu±@@PB!eb$Lb A[}BÓ!h\+HG1bReyTᅄSeRldp?;}IYT%@NYO1~?ul?A^18N)enl8""Mm/fʺ_uqA@s"G7!e<־y7f,p) .Pno\uTb/VҶG#Op2y[Jj޶qchܞ2&`L 0%߫W/M֮]KpB^IC}c 9~Z۷o޵mPڞ={K."縷cMMSӶJ{ܹμҡhѢvK&^_r*"%+R<38wi"ehd\ ~/8G^/ I?K4;$qҤLwn"߾։F'@CHƺsFNFU6R<}xΜ8(◫IݹA Z9T13?}5oVB[zmX6靜?sIu>o\zTjLznc!"}nƍT`A26jsSI+dc7D =l0xϞr p+VL:SL)֯_b264nԨQSʕ+ D7o53gڵ˨ wuuRJ):u<yDmVJ9)S&?>ݼyΞ=+%r>tIo;+t1Ӯs޷=srI_)uZgز;|J$0"ZU!h۽}/^[Zyyиiè[3Dl{kGYz {s榕"#PƵ,JENF}u2J2&`!٘@P!2z'<==mI+kb6ЂGTz޼yuѦ[nt=vtFm?܏=Jٳ6MvJ֭/^lWY{E2۷G;{,ZhԸqcmflC%Sz ̬;KNib#!"φM:вG)Qd>-@_}%8jj76h:nXp՜ieeƵFsMN64qZ[ v?FMYiBkpGQ Ǜv{r}B/Ҏ I邀fshŒVX)nPJkC5wDG9 bSh~ں 6*Dh$+v["f_+7c@Qhs`L 0&h ;w/_J%Y!'{W$e֭kt&Aơv$L.eȐA:en=~xURZ{Qgz͚54>.#dR%~u>\$ϟ_oڴɇN:%KFf"K;v,%LtxH߸S= " ' -[V:!c!-6?3xcZt&ڪmtzWҭS s {.J)/ѫ(I2'rNAH5DWH2q5B)#ٶ~RYX}䡼b qE4ɋ+)C;Ϥ0Zɶd!sK,~{'K`4λ9W/<|Ҥs¡my3yƄ)Q\ y|.ղ5hZG|><Bу'=zTLJrrNAz^[.&lVw峝{ L_˽ _yKm" 'Njٲe)^9`L 0Bz#hŊ6>}zmSc+=rCڡC{G96J.-@0gHHz#_ޱ1cFy@^VL… e4ʗ1cF2=2eDkצe˖/FL 0&`L E" ,(QPĈ&E3v]/_J,IZDjժF訏7.QĐ=]1///ruu5rGʔ)Cʕѣ+MTfMkQ'&`Dޖi5g:"%`z\zU>WN[6L 0&`L X` `G߿WgC/\߽{:v(X߻wt+  17nHJ1! SK.S$dߺut#B~Ν|Ϟ=ԥKkQ'L 0&; n֢W&`IIfA7o wn]O"`ٱa0oB9+ ۼ"0~6+s~Wb]w#$| ĥt_9} bc$P jaT[iq;&x۶m7l|Op /^y7)'aL ?¬<%Y~~qǝ/Wo޽{z됖AѣG4bݨJ2wrG&NɔaS>*hqy<#wc#3`O cƌrsv`L hQC{Rp`VL3N9M 硁}[im{]ijųGRk>aIQ'%H 0 6hЀ֭['G%Ο?_ꫫx+W.u ѣGI-tOXuL yqbӧO?Y̰l2;wVZԦM)L&wG@$ a"Wx3%&Q0ag{A| 0;,]JJ?./ߣ;&l.Q_0"=ZZw^$&cfԹsg}ٓ'OT&LwRΜ9QN6l H`L5۵-[8t- 0Ewk ƣiet .$$N1/;w[J]B-h6kL[L 0&`L 0KRJR*رc"QڵtZ+V,B߿K~:㰤IJc ţuj ׈RO:,nܸ1,Y:u$km1bĠ%JPe=ȵhs&Bawp:u*[>[ +\2.`L 0&`L Tɓ'[=H0g{5WEƍiH0gX~AjCRFu8dVK(Z̭˙-psp3HfH>88pL ܛkQl׶s&`L h'13333333Š1;cȞ133s;{SsݬV{w߷'iH3=TS/G,(& /ܹsOH3`sc=GH5ܱL AZbĈs:vDGV?nDbwZ3-ȓ'\Q.]:|NdF`F_$`F`|>+>&ܓ%KF˖-m۶&oݺe3ȢÇӥK>ok2#0#0#0#;pv4DŽ{1w`F`F`F`F`Lwg}|QPA]A& [o̎G҂w/1F`FHLW^0a,Æ  3l9I&QVhڴi ϟ?o&Mʗ/uڕ,X`߿ *D={QFٵDݻwvhD?>C dȑ# Ǐ^,w^t8vXߨG{ʕ+6H}`w1Ub7J2u1vgעI&I}IC}Ĉ԰aCڻjF/ԯ_?GU8`F`F`F`F&@0Wv5ӧO]-~oܸa Oig AʤhѢUiζk<+VH-[+uV4x(P@J^VQz,vț͛W{Dn/_3fГiR$ ~w}'e`t"eʔZnM+ViSwl(N8қ~cAxͿpy@|J9a!C)wxɆʕ+өSR;5@Vc;{,=x \?CbF7nƍXbi=Gn*th?gwE*u鷉ԾYe盬ՠS}E_pz'ϙ!s&7Ic\;QlҬ6^Ho:0-#޾~cbHw5SNN{ UxY -"hȽeF`Bs΋(  z )b&ۡ ٳgR鎬W^,Dܹs#+q "[ fe]vCm׮$ttt0̙3e=M߇=Ɔ> nxɃL9QA׎ފlW͛ҹx."U=wq&Aa^R?XMsl֬Yd{_IAH`70qOP5 MkQVr8mO4!+cO^OrU嘂k6]BwnW#+ojҲ}HS;T6A6it&%`F`!zhp /pH)SFO~͚5V:$\5A>1v@pW^ 1 gƪJ*vW r*:ѣ^V5,W_~}8q,Uhr3|7 _1Ѳn|%dem2O2KW1/=rgJ(M,.l^>Gwo]hcQly(XV\}&OR V!׋X+X._-#0#6tp' y!W={v;Aۯ > q,X Qww|G@Jܹs]/ХO2%AvM/렏u@Zpnc w'i B H=w4]V ?Ϝ8Ls.'ShY9Q?gS~#[ѩ^ԦsoݧJKN EaKLw#Lyn<׭9vdG-W6$bG׀roC{Ҫ%3 :bČM]JUk7UI67Ҁ-ܩ?m4֓$KE [tzMٔAtqݛeD߽}]t $/D 䎶 mx}(HɊԽH8tӯɱ+oؑ4Bi}pQ 1Jؼn)͛>V':j'~$?~Uo^Ez}"ހ ܋Sh' ,?TUK g^>7…@#Rx tߥЏA1&(pP7B Y$FoXWxy>}]U*4hgt}b٢{:p=Z*Y,A2z|ApFa0. w?҂ eѢyjߞuܴ QR|>x5Y[L֍+ib=[cl?HCYn6[Ч+XT>a;ҍ }H7;G+](SI?D딐cFyutQr%N\DپuonC|#&_ YA02jpwz?JJXоieQ}&wĪ)D*+ձڻM%[\CLjԭ&ZK4\i״&ψU4STRѪv%?_@0#|9$rQ; /{޽7$[\@RC^LTm9:O…},)ju>ƒ̮_Nz D࡮ێ;[7DŽ^cs玑dnv̓ Zݮx. P]ڜm]!^mヲk2Eۑ; OaQRQCd;WwDy oCӱ{h˜V`A? O1ٺa3{_ʼn[/>X*/NaB|^q'GX~_zYy5|StCCm<%\`F {0`B7jԈ B-Z^  I4J$ M0!AKر BQ-1_vmzq:ߖ)S&ŋT[n9sPf͜膀^ݻ-(3w\ ̙3n ;-^z!_q`*z~`2{ZLHx!;u)YvJmtUr U HE^\uU$@GMUM+ch6= _<B c]ѿ4ZX#V>q //lXА̉;.]h7adxOD:&: r;k͑YSe'Q?|rgNŮ)R;u9+T.Bg"nU}:7ݢSWm% /΁5H"Pْhݲ k68'_fHРBq/$Om[ԕm``F`=[n{ ϟO}ɹr ˗̞6 o޼55jAhd\xdUqc'GmFYy'OrpȘ>Xq6@~}F`Fp ~L~i߿3dc9ⰙaÆt g"E$)$IijAƋ?ҥK ^_es3fжm2 ǂrʕ%imSA\]hhCч:u~ďFJF?ϟ?OG%覿}V5kPժUc޺믒PńdupӥKXuB Zx:3` Uwȑ 8G.]C ڵ7 ӧOObŢWɓ'D·АI(exoذ>lș3'{Txqx?" IYe"EYXB?%|z}x[D;{wn%fw ̮-k?WJ`?|T1oݸU:CJ^G&xt"aqHS,eV/~b:u:F8z霱Au,ei|gt9#m0a(G6+TuX~]3xCLUuzxp74M?-qq 8wQVWn>O>QAK |@ۭ_)bJ0.eʐZ:t_|#0W.N睠UGFURժ\.!c}ĉԧOЉjU}K `ez>B*ߎۄ -o|cǶ'G}$z*j ]rwȷ@6 ^зw7Wg01n8r.s1h?~+\YP|y2*Tp /+C _}wCqJP9HkUݷBhm!A&woHC!beC9n0~9rȆ<3xȒ%Tpxo3NC2)D|RT*VH=zzPx֭Ї=<-M)ѣG+Hnl۽{w!: d;@x:#83;!"HFw4Y9[z*Jt;yt1߽I %?ya#aÅ3qzyJ]~䩍f JaC@#ؠZd;9bǵ"5ܺj<}a%N,:^Q3޺5NeFx38nAa^j\M]NFK)AGK=AT@N.HBrІeeZg46c; %}:^RIjP͕EHlA'8l#C]4m|ZO|Vʐ.%ʑɧ͸M=$c>}t>AjU^尚Xw@>t۾};c6Hƒ^*J*%㋙>`xG;2V ^xws$Vߛ w*>O߽{ ҸxVb/pC;ɥa.nj#褸_<3=L^ZhQ/0w\Y^ ->&1lFZq4iF>fO< -q_@Cx;[xٸq:aV{}㌶P;>k:0rE`Ix;Ѓ#! x!! |H.@^U  4s"*LNFܻv|ʜ9yDxE?Jf'X[e@ ]hcJvhKscǑk  _63GC\庖}0O'ӫjJe*˫ym#܆OCFϤ';ULjlL@0N&vk;'&W/q^mk6&ܯF` HԭY޺!/R\M gMJ"+/'H[v0Η$Q|Z47~%ZaXM/SĨn;.\se"t`AC~Ox\2[ ]vԉ //_ڬ sʐĪUe~vmGL08) 7e˖MʷK$~.XfpJ{2p( lnx2<dɒx sBcǤstCL2xߺ ]4}zygE wL"~8Wp^X Q糹>&i fA4<$(|6&d p&w27jj3 }<V-NDb_ .YRr.Ԩ\jr#cET^ ʚ#MW/ҲS[v'Lj=*(kȁt 6"/e \<#Ͽf9nmx޷oVܻ-/ڪgxB]F;# *':Y"aU~-[͜dtC< =~H'(?јa? sQ-4lC󧏓) Q&}gz`\P:fO%?<4'0W6a$8)Q*L1Bcdܯ}G@߿OlۤdA~DY / /AbC}-a)Nh {}ClV9*'OֵUO\#~n[T:珟fR ,&Oo=WZ㬜]> ,o~r-4>V0q24w H OO?ʠ A@^*_GLۏܲj&me6AztM`1A [^3ʘ9^]>jЊ𱲧S1&B z'ЏK>QkWK}2AvMGvcm^Gu8BRneպƩ}ݎFF[?5 `{ne: tU(S-BS^7%{x&~R>kOwt7ȓ_=5翿n`N&x{?~XCFjժ2jk׮R\ gΜ7xC?+V(7uo|!(AS'?&AB:H*hC@לWey0A&};"x$9V Fn;_pLr9rϟ/A0CzE7@lA#Fzcu#Dz~t ֩S~g$6yo| &Gў2qخ];Cd˖-ƹT9x,b]TVM%mVR3p~< =@P`LUV(gc.nj_!ٍ-=xu+-ݱA"_S/r{P2RJGΔ-X /dm^T͏iMqE)Kq|FJ/ǒ+%*]#ݹA?y7o߳KNj.3k/'ʺ^yH~5O 'O_$;vG އnI`:3 K@ ޒ:xJ M oFѨNo={|3t=gΜR AGYU܂PWe0ad>&PtMHʀRzޥl#ПpB)2y@cu[ƙ~UTɎlW@VcHqx뫘P:ٮAjK~oUiBnOjbB0L&dL9s`ִiwo&U:&~q_G o`uu*}& ?~nBm@` 1XBE>rp YxG"EBa†hÉOX1x>昡WmSF9KggɼIbDzтwoQJXBGuKZdz!ILaŶ٠K(wUޓy7#;H)hÛHBAl=R>Ń6T.|f=1ܹ#_t|UTjO'[̎>*bn&1Ak6J,v+VLu"P dYVT7l \=JЮ-Y{f7g"j))[aWFK߰y'Z8{\Գ}}5;O޽}M"a2krY sF`?ARb&t(nr/n,z?jR$W.% .nf/)Vr"'q3heѢEdImAt'H;ѐ)cTb;ڼ}? 5ЪG#'N_3Epϥ疍 1˖hݦRJfC| Ȃ  C(J8Է}q,<==o_~׫2@cxq+CdunݨO4b.]E>9g2HӠ08tթA~رt5#kǎOK#K2@CXV"恀`ʕ#H:@AAxXWԇTOѢE  H</F `/1\+{mܸQ +CRVa٠rᕹRF½֙)/zCJT|G)SH-yԇ d;tᵏX Řp.W2c=3AǎLMz!ˏ>*Q)Ez23#3#G% ѺKq:xתr2姫nڶ= )½M:4v\ }DGVfJKIwק{+; +%*5WYuIit}L/v"Fqu*ϋhꭴxF)}>hGfn>i]ecp׶"_KLj $8HieH-:4 8VZec$6khc*ڰMvA̵Fpb]eeЗr@  6LhTCذahϦTH^#;@ڮ :Vֲi-8?Ŏ]/%c郫(zyG۵Gw,vЯR&}C0lfovn5fh/^F,X7;Ђ ǐ DxC6Dv^d &ݕS[|=| j*-$*@ !dZ ^fMdLJKr6zWrLNJfWJx:eE@gqXߖ&My LWϭq?4{C^\r)Y J+ 6y{+` nn]#AÚUݶ1F`~yMW絫mT([RrsvG)?_%%cbư%BS$OL;@x_yWHмBrnfNJXYVEtU| 6u27n^?}2f|( ŋ'tݎd7n,=zT݀HA+I#GO+U|#: hI&RZjɖ-d1b1F Gȭ^Zߙ3gE"~yc`zC =g9&A, ͚5KHG ڭ_>M6Mz+y%6ݵKʸ~KaQ@d:3?70#0#ɒ?o68gzt}mY5խL"7FF``X)rǺu$fɠ08ʕ+ !)l\d6bKĎ[ MX1UIhǾ@< ĸرJR|G,\d_eʔ a9=@G&SN-?~pG3f̐"lp5&܃q1~ZҴgfʝ&~b_Hzt&FpC~"Rݰw%F h W/'FF`F ?79Q,ZHljԩL;۷o%?6>pF`ccG2;=dF`C ['ڼyԇ1-X@ѣ [L}eUp)5pӳ_fMilň8F`e?|Qlݵ/FeooA#w`F`7Ly& 8\ٲe[nqpa=8^U#DF靶#-4yj7:0; 0A">sF`F x!{^ϢERePל9sJ)Ȝ w.riC=fF`w@cU>0#0A` wf2eѣGO'9#0?t!`#0#tE d=^X#KZ@_>#0#0#0#b`=^z8Kʸ%1A3qF`F 8#p pF ĵI:x s&+U$-76<f͚eVZ"E ؝w^Jw7oҥ 8j;xW;z(>||"=ztjժUP pC9 9cpc@9F "`1B*&}<2fmUlٲ4i?6kݺu&ׯ_OUVSTpAs1XR&\+)#Tǹ#0#U[rKWm `!н{wj۶-իWw yO=bcB?3G=}d C]F`F`'^}GTx#:r|`hѢQf (%Jd컺ׯ*T(W\xʕ+RRo߾/`+V'OҮ]W^aH<F0s1 } bƌISUGe9` _7w`F`Op>Q] ;y҄^cг 1Bx*R0'Mc9=oZjڶ/QMUTEsZק1/=VˊOX1hnԴoӦ`:p$}ՂD~/)rHSDY2UdKҝ{giԶ`zC~#A튴p(o3v\. +$-0c ݻG:u#GHI ܹM[nQ.]ѣFYxëmG ?v 1̛7o}Q5* ::t ՟ǏS R0 uCŋIk֬QFիW%Ѥ P։4G-J3g1d$0a;V䪭~ҦMKիW޽{Kh |һT@0R2:VC?~<رΟ?o ٓG~R4ydy;w}۞={hĈSζ_n= >q͝U=ؿkGtQYnE6^:"Fs(Vw(x4ph6L}4ׯ^u&Ǽѡ/ġw $N?zO=MDUL&iԣC?VDAAO^q7SYעe, BNFY:rA;Q/wڴn$ʙ=]$48'{1TB u!ӣGx+7i҄2d`#YjU#-[6¤n u=s̔.]:u\2(QBzmվ^ƫ}xf%՞92ᜅ iŋ2r+_T)I+ZXV-9 Zmջ$GՓ 3xcv! J2%UPANWcD-UuBSg/:tN_az[Wߦ:ѬICćDŽNT]b(Wk}zھ}e3gN:~>k,YZW.PI& M6)B4=ijvi$ c 4 |`DV: zf}ca~F=9\Q}V#wMzZ4g$afѭ̙3Ըqcy  4H3뵃dW>㱲Çɓ'edly^UbVr-VTVTZ#3`(ܹ߮ 14`9bBPp=_Dʕ+Oko߾ru &t}bM [#5.0D@37#wAVQ/3WkZdL|4-~XonBvۛbqiֵ'ӌI#z lk߄]1@2T^n*sڕ$q$ +7p@~+?u!6Nٮ6 ͘B4c1RhfGЋPl U'ۑD;%Odٺ³lg2|BzXO;b q`z `ΜB'O_Bll3ex}VkְACv:ф2ZǍUQNj> <D"Ap={1jh#)y+qOnhC/:uȏjϝV: $nr3(/Y$_- Dz>}d TJJ,4:d2f(W@d>-W\Rg=qR̘12]' nDTW#e>W1@05i$j<?3$6{ LMRhѢU::cǖ}obC@*w~qKʄKeo/,Jsu[bM(h=RBz6 "0${gyQڲ~>qz;mT(Q3VQV݌S\<=qݣw m?tōa{{:'oݸbTYs,_M{vl֯ C|l_ ;֯Z@O?zѽH<#QA`Ul^~D vnC7ݒ4z'[vK-+uQm%YǻdjԺK6z5ȬQҴ[)SVsyЇ͟"&Oi1WZ8k q#Rx :ts&l9ӂ5<}g̰h2rF4rz6eKx.Gg+il;n-W7JtWܩ?mH$RQ^vtƶt䄉[-4o;zLK2,՟ >YKwPO^ںa]cxI菕jVt]:sC@a=r|`E&-,c Ȑ9 ՟ri+y6y\:k}O" 5k݈gW`RvڻϾg`r?EA}~wmZmB{BƽfҸ4gB>#OA7~=g4˝+QDʗ긿HЉS @~ƙ;*ꊽ\hᄋ<ޯYQ)%Vb>~Ȇ|DۘPֿ# ;|eХUMlWl_<{BkN޿Gx7Yݾa`8e^ ^fm. \ <<G(QEs)R#FJGo;2d>؉c Wnݻ׿*@"VyǏ/W}ЌM!naÆk<5O?y0xyT̍ܿwNvmceKQo^Q6o^,)u%x߸vV/- GoC{RlyfxÛ[4(oc@itq)MӮI%~EI܂Uz֟dNβ_%&Oh[>v Eni)dޜPZ4ߗ MkK [1>L?itͫ$.z.ǹbĊCy гNbfg8Sn!td驏YG?{D'X{*A{Gt)4,"WiRE@z. o\r%|vfvvv滹͛fKDƢc6>rjk 'xtuCߍyI ek(7F$K!ϯ9-hp >XaOnO(;a5Y2{hOjÏ'ͯ1}yTn09yp iҺJg{)8(e8y4rLJA1?G8'+r`!Y8oiC+QY/}.wSS@W;s]x|f?N3'9y q|3яI6=J i RrO4[Wz ]o4%P/邅&KJJ.m{J6=vfˬ}nS 1A֭y.=tbS'̒.Q՚+$QgODׯݐF5[j7&^2K`_xfNɩ}>i;5G9+R$ |%yΒ)du!9S:g}ZnH?yk`!qp\2'̊7W 9dĀ>? Wɔ)r!ڼyTw}}pWL")V,!.Ç $j|XՃ8 X ̊ﲗ4 󤟫x̖<#  #+ɣ)?_["U5c5enYb-4cSh*x1PEY@WZ`3ٹuՎpg3}&A? UpqО:n*[ppXTc^|/;N\$$QRBs1/{I?|])Q'HMT:mybRjgje^G%M*uk5^O1P} 7 ]ܱRq[$NRRq,f jK0Ӭt'K WUE(K+giߣfW3y8c>C~ kA?-WCZPw 2VSQN)?TA@:\|?+s#e_w 4w嫔yJ+ku_=rps@̟;#-l\L*M|j|hJ{XO5ESwsE>i@IDATi#*P_P<'w{mgJ~0" ,qQwժUN' 9sz2c yT92& tI,YE ʕ+ SYۘ1c1B1z;R~<],F"3sIVP;EJEχ*+B |5{ne~uVYL>9XC1nlwT\k~]m2VB3ߵ?f,p,?~4ΟւxVfe^)]ҰiB{!=,>'hP 9rd@N77?Pbw{{pOҏ5_-Cl'4pqZ(F4NF۸AU=)_?ZSl>3!-;|.͏re;:^*]ۿ[ xo<C`?_.G e BvhG/jʾu7QMМ6kQ>O4 -p"(ix7 '*A;ӦM+I&۷,XPrWTIUoV`F9ʕKY۰O2yr8p|'*p ,ء={*( ×KP(hRp>A>M4jRۤIw~и0W@_ZAɗ_~iET=͛7J3mׯ 1cFՏ;wC߻w1eꞢ u[F (U:KU0}@C@Cȵk+p|&0i`CO!@!:@~~ᇂ;L+h]RP! %KLy΋<8Hff1?H7iE*;}]ٰwA.m xw%@y5KTi;_S.J 8|74!~/ldЯ 7)Wteou9Xc̐G?4p1E=PgŔ+  iB9j_/;e~ypQ;~ճ>2c1dG~j.[t'r\sעKT#+z=hXXeaYn6U?zY͞3у{6g:S0RFs=qJ"}={fLVcC\JI|;xLJuTE6}N"WΑu"7?-}oϼ#U WL JcOdPځn}HGubؠfTS"_J*j#[7?{0u*G?00O(@10觉=\$wΪ5[k6U&OM>Ҧ̪(EԩS ֭[ȑ# 4@y;s[n"q 2ddL0WpouA?cy5.{,nWpmgExԩa?P!,0'x{ 8ǎjsV:u{!SAݢE Uv!J؇e壏>| ,kժ^ٍ6q>ks:f,G_*m۶50MFl w V$ 4&WaXLz`m|͠Ad2k,)[4owg} r*N$`:HZr}ß4V-B0n/ɧ4Mb8k@?9guw?f1qfRˣ*}zyteafԥu.N-XW,Pɸ,?w:T soŭS wϾ沓Z][xrtu3E2V-keI5/~_ )IQ:$q$P9cGB"(dm#8gın׬;g}tTٹ6 陁4~`v[½Q?RAG>k7v9ūYGa_(w9>R3y6U7zw X7inz x%p +aݮ  0ĎeʔҥKƍ5!+ $Pn =-[w %K,)qm֕wU z%KJΝfg_Y tڵ2sX䛃 p`/;jժXbҩW^rҥ01rM0+W.7o\]pw3 L$NXwX|L~8Xc`R]'  <̴׈ ^J3A!zUWy`5U?RB,rݟKՃ'ʯ%/\=Om߼֨Fs?/:{6?J\$UXk-5͟w2q'SBa"З}ZS㖣l Xm$Ka:,wp/d/=G|fyB*jֵzU';?Z FDr0,̽-1[\j-'F u֯` lQQ^ZYoSeݨ Y2_~(ϗ{tZ>b Z}PC~KvBoک>KeԐ~JNmu@ȶ!J_K]-6ߑL `#1qt +W::Qτw-|^J={ܮ>}ZBg79 9|cO{ 1lP<ܑ] zG \W7iD)m&ǎS ouE6¸# k}9,.n&=UToƺfY\t]`&`6>۷Y b@w.d`^zo߾Miժ}>V s" ӉpHH~ōϸ:-tΔ%QٓFQ㙲͠Bsz2υX__YW5Js+S| ٳs&Ҥh\U?f|eyD!]۱e\޷ +5%!SPbeaS#ܵ/DL1wVCَCƜMFvYLC]Iힲ1">=3>c:9Y%CFN*$\ ~t؜I„i2JbzE~ :}^<&5HYLO6,± +:,g }EOT9)OHY,/xc%~l\FyY\i1 +K n.>r[dߧm-Xc}B pD0hª'V6 gtw&۰.J|HWvpG X(QB /Ć']6mu)}VI$sqC{ ]֜4yJv4bvW;~)ܺ).7+3p%# ;T7oM Wm58~O=HJW!o؏Ո?׮\/z5j¹zfF>cl}6t&:F%-d Aj&6~<b<サR0kW.vfhӺ XJ{c,z?iAG߳]Ml1L#^?X`/s&Ȱܞ@t{wwy?٥FϧYJ2k}wupmby8dNUm7ju{x }/c,3S/ε#ްfwt"SuiݼtY;w)Z[ Adɓ'n^ݣر AXC=>j(ybS 4\a5 A^e9 XJ$`a3, ?[?+tYy÷FAPBEKKꇯ }<,}LoJ>_`<|p_74v/S;RO +Cѯ42OMtِBI^l^ٷ{QUR(u3lHO[˂ٓ(pL!;y3'4嫸Y1Ν=m\˄EKU2,51a'bMh͒%[.Y=پyUup֛r9s_B|k7gbG'|kA)k.&rZS͝Va{sy፧FM>sʴs۞g̷ތ ~+_-=:} ;hh,W^\yܗg.(5z+ҳNPoڶq{ /ə-y3KTi5C2mO \zIƪU&q{~!$3L߸n1]R; @.ǾzJT]SglR$i"ٶy:79:=3rJIȖ۵iV|ҳΛK &|Y6,FL6Ԫ}3#I_ 9ɼKNG艳m.q@2g),tv(_zpI8E  f=ѢE -R~[\lbݑرcFm6LZ˗;Ϳ i֦L3qʵLNO; ;6ٕfM~`sG{)_^֔hA#=|${ TM|mH+Pw HWM=JV!d4mzuu\&]#pa/hI.2{vZ<.:5,k V73e;:>cVl4jQ4 zspO/Qqܓ3g#}j}}w_s&.K[2׵JXmHplT^0lաa#oj >)R%b>vFfwo'sLlv{yE جu#|fY=KVU7}%g2zOҡk؉No;{4FyQ09%@ 4SQ"?i-S ;H˦͚Kn՝<7 @0(?xAښS]F 6m"Ey_':d,vZ.?rHi߾fW[]l0O< pqN:mFz.]ۏʍW$mJY 7"?;Ob[/mL6<՟3'}Ws_>_u⬟7.ϴiDIlϡv%U *PsFLx P-PrR;vI)|L5_WUl ')!z߹%M'Or=#+%MTkLg/fy>w>xL^KHf2U"odi5gy=Qo\,mԩ^NLf%FÏ6!E ٮx224Yoxnީ*k֬%J캼 k ߲̣AE`[!σ ͛7j|L@x0]e'I&ˮYlc@;E#rj@Ev:=6}.}*(qRW19sI^N؜ 8as%VxXy*{T o1>c:{^/2|]Z}LuԹrQ;d"p_ NH$@DݟB9GLQB03?bl"Hȡq˚SyHH z ̘4˚RNF>mҹG{9vQ + 6{6jt1(=yI3A$@$@ `EuC&@2&@yHA:V+0 \pV5$S 5 XCdν UID x饗$9l_8 o P[޼sT;g#$@$`~E F^%pMiA~n * L tumr|[(  pA; P<prļ X9!gsLGҾyU9}M7c#3$@~OჇ`Nh,2KB x(&HH 8u|V= QPw&?#vG{-a=  9lZ    pwEB h΋@0+)GH|Q֬5>4&'K}%+R~@vwH 9[Ɂ @0?/:(('^tP" !p]9uM}>u$z]I ̚:ZL7)kڗ&_NZkm8~soEfFcwݗGtbh \ًAh:/Wݰʱgb)ɓ > &LF+*ܣ?/NMӪ׮ks<%ݯvìH^$0y`F!@}$$V,S&YJuIQ6޹sǯΐ U Pn;K' _~oŽ ?:+  PnŻc@ԩU?֨-&uԹo:ǪCƕuhˢK*w'ۺCv ̛8ZhǏåvvF>/Cl+R3a sƍA?6h)I.DBW{ D?*ܣ!hO r^o NulG1btIH̟2^Ŋ[*:0aѥnG7]! ?&`~^oFn2H!tP]|sI^UV P6*V$Fvnc=(_y5# X(V3 O oPμ = [|xf^Y. nGcƠXH ы[G_gxe   cfw]*-p9D9_5@ӧird.cd<Hݾ-+Pɑ7`JwX9pΆGHHB ?/zZL@= $` C8h!pI2_ w X6{_pg "MZ7r,?zIz %9*v/b2h۬XH zo{ #* D3{J4w' S *FY[T[&s$ C5M nxRYkzܴGRI.ZH-ԑCF*@^3AV&? 52HH.¬ D-*ܣ/['y@ wx?hxF$Ij M5`H7o!   6z &{bT[&s$k9__׳6E3&|xY$OƀC w$@~Fέ_^Qd͝[:aragi<--v9\  "M H#d$@ `~?H|E3&K<)SAR mzEOO.UkF(Ъ0$@$̘,Ȃȶ]%WΟ3\Qs1G#)]^mF%&HH,߸zEݫYSH ťWҳi]u+H#{&D~16eT̐ *Plp0C$@$𜀽. ~4E% #Y,aK5Z(~_GFyDx7 h[7ɿk>$NuogϞc&%W#mNlc7 42;M;\Z-*GϦW//)"wl9c Ӏ c_fңqmq^t ψмtafo/ܑ#JU}.okeΪg'   pn@bw`EH (_x! E&}y&@i8q pf"*}I۪ #F iS)UKPvmZ'>\̛8Zċ'TUl^JW|GH̘1U^vozR*kd]%`>NFK%~IJu Y8uڵC)lԭ" 8Gt,żp|$GҔ$_q&iopg}F@+ϩț_:A2fˡ='/q~XZϙ!՚0il&,5#LY?V+ @DܻwOΟ?S&C, ?~/_ٲe>#N $@&1`GN=,47V q'K!PB~])Q RA$JA)& sǍݕRxj;n\ݛ uoXBCbQW?rszm:JZ{JYwnݔmkVj/k:q/'17_l&Ecp)H"[|ҨCyT6asgeUٶ5|pj s (QeLc:9νHI'e{ڴi厶jbKHcR!P~}9s;UY*@a ?|8 9?1 Mhd"1:ҭч;7oȺ U:s6vU{aBJX*xY'IA=o;Ȍܾ7ӎ۷GNT<9G78si.~ΐ(맍3:e6[U5_z):kI' elO( LOXNٷo_`OzMv-  Yӌno-n n\7:BsY9y,n%H`}!XCf|"!573N#)){.q$[lfXmy+툹} %'ϥՒfعMj/VBrU@}o$Hؓz.z' {Uq @xlߵkWxx\زe4he 3b  7 HUfa&ǜW G}cUB4:r(۴b` O޾eT"ʯ^ 7_ݛ7H~m2U7j>!p;70njji= dٱRsSwoqgQ_U._Ն1bUV>EчzKx:Hg.5[G 9Fr$@$@$@$`!T[fs$; ;ֽ9X*+7hj]:[7ɿ\]`u2My$Mʨ]:dTٟ )DW̿_O?2ڎ7$QN"c y;cnߐ 1gp&W}nԡPE۟;m,4_z!>ll^/_8u`@KI8ߛc8#F/vG9  nTHgh3ԼFV[`nي͔"5w2THuc 8rT#nb ETӵK~3NP/>k6ʕ 猴}7IR go:;cnߦǾ<ܽ'>?QY'P#`re5AvJHرjcᷩv)ZgH9{Z9w}vi=x}Ff7q6iPު]Q~dGm|O{^vL6Hl,lU5ʹ3`XqqhNy527~f-Tߌ&H&ҹ3JW%_P8zv(+ @ PAp<HYel.|Md48Yr; <_?"31X^Qscrly}{X*hkEiuˍxB_OCYV}3^-ܽ=qpຓ+ 9~ue;5yqM[Q_.LOY$N"3_1KszӖn1)Q>/,9M& 0CnWejcj,l7~|v!   WpwEH9SN˾` ѣG˒%!AzחpKl45K]bKJ!+SK/i3et׿tl]8)4J77ܨ*AJl_kb|=";we$ĸzVº$@$@$@$ TC@nH*9R>mZȏzٗ^zIvgvoC{f)lQ6yf\IEɳZ/Z\/R:{f/gs8~ꇂ5敿zoywoOw'Qi2fφ)Xlt,s'z\)ɷC|?*!#pLqœ>VB!5Bl?&qŗ]ELm&x X@ C' ĊK`\$I$"9fŁ CέF)&2g Xpw5?M+cvl1]V^^Ѿ(VlR.iy4'R!ݪ2fɬ_55eO<⍱iԍOѤ[U<|%H(ZvCꊲ5˛uk|S-D  [,$N3j4k%_O!^+RAsIHH"bYl 4kLƌq]+$+pKpU{z9C`^06UJO'ׯ\Rn\QRcsG$;n*&yfm٧s^}TR)O[ #ZA D%S킥6aԨͶIO`dۀ }l PIeKܿL$e! @(SNɡCB R/j5f*3ft˥^xy%JHҤI#)S_̓'K;J?~fTa ^bwj1[|R_8}mz]6kڠ#) ;" ^ZpG;vlɕ+,YRڴiTn{nͥu?~|~2c xʕedٲe2d*f~le,X 9ԴiS*mG.e>r>$պ|(YM1Gظ\0* RHH ^4OrfEQ[l7:@VVbk9k+œo-& Ȟ]UR1GYw( @+t@qe%^Ū6'ւK|oKeّ I իWW^6mZiܸlݺ5L u1j(5k>5 uaT$q6oF=Yj/>l#1bwT!IL!gŋw-[Jԩ+ H THWJr?pp0aXWX1믿VVM=2pZ8YKԨQ#YxQlo2t!s@$X.ܙ";WVGȞEsTUn,:kYɤQ=y w?#삻d0YTQ3wOe= AäA_MԩS`aڸM 2D-WRa걀HJ K,%Ç##ٸqj&M4?~U TR4E;RL<6,q wq/sl3 X8tɗ/ɓGZjoVGŲ_K`k.ʖU/9@fR& X>gc/D w~Mo{PtTڒ QPu~t3+LDem2 8vڥ͜9S=zd4w+V-]tҦMew 2ŋ_t38pH]aE?5kV֭ٛTRXAg -Aիۜ(ꫯJe޼y-.3=zTUNp?7*0pA `nО:r%Gy$Fe&H Xb.JJa-nmF~ao LM *1"g.?kSdhCL›/Mc/~A&M$F'NTg>}/ڵk+E XnB_j/O>5 g}&c64V\(7A!}ry8Iy(!pGcV8Fw2` "RŃΝ;b&ѭ6B6C;u+~}fEK0[8[/6w ?Ifo4'SHG:~Ag6w ~v*]tQV#G%K @/7|S){¬|24 #7oÂܑݨFHd˖MgnS%gΜ7D)\<<)|_~]l"׮]rѢEt|Kp}("Yׯ__̀W$IJlR<4cN/AƍggϞ Wa-wN?= ,o.J?νc;b-\w=ccL2xnvYaaޯ+[ݻ VͶkN)2 &PN[RWWe/G4hΝ[/nSx] X_dG}ngb T[>sQH/Xr}믿.F /2M ֭[ C5R^%w: Rʭ[K4L I_;cp@IHڵk7N5kTP3a n^IzjA@7|#XM1BYB~I &0~裏jժcH,uqp&%mڴK…;Õ+WTx1+8 Ż6Wr%AU;x@2PӝLxԂxGF!PP!)VTX7 8ZVf7zShQ* q㆔-[/QH `PW$;wѪV@[92+`Y_jԬYS2d _\z՟oF$@8|.O,QxSNٴaTR9̊}y6 e`Qw4ɓ'+1^:@:Bu5G`u7*V`Qdc1c`.JѣG] wxx=ڦmڴ3C$9ȵg͛Wƍ'/^!CV ,0,84h-0O$@F{6 8IGFra[/au b֭[7 lQy¬V*ňA=`n|F9.2?a [F3:DŽW O3^PV.@32,3 ɓGB$@` + ,JX] pk  ?^>ͱGЊ{yE!z |޽{W] 1@*U_ rݐS_w~uٴiTF K$'?p%Wٳ/624?iܸZ:Π0J]J,m۶Օ.)6FzV`eʔ ӧO>=D ,B?c< D'źAɝ3gN5i F8/-]*k 5׫9ۻ1iҤ1K֬YUWsO ! >у;Dp;v(ĂDd-_&G`qK8qf=} 7"[TV  |+gP^`? ( *>{̾$@$sPxܹ<(=LzРAȊ"}*6lzz5٭ czp=I JX )J`ZQ|!.o(P@r/#GhtT{}H?̫tkliîR$Tn}޽Һuk^ZY"_~)fu' WYٳgWnC Yl4lk]ɘ1\LVb"RW໳ta *yeS{ w<mHlEW_Ν;`hnĈ#/ڞsY}TJlV〸XI˔)SdԨQa k~u<,-۷o/Pq$@@;."mܸ1;u,^{M=r;%`COd`I8`u7tа( Hs04[>ϟ_Q"Gdz:7V5k: mŋrc:)ߪmɞʺ$@$@$`9T[s$8?.ׯ7kDy&@)NJ+VZ/=>0iӦ Bvw ~-]w3go߾g8p@>#dk¾ӥW^j>|j,'  PΏ WGWZ &،E6yf+ʔ)#p'p p5yQY2H j; *wsd֬YRX0wtf0L% DǓItfkĉܓ@={| Zk5'|"/R=\D J,i\s  ($giz} Vč׸"́.k޼ڪV& ~HHX^tI߿}_lP$7<QH  D~DWo9<\]k}pƵkd1cFɔ)SW|8zd͚UҦM+x S_A$@$pe@Շ^\9IsoGuF\(!]R%Yh>}Zy$@$@Os1ѝ7o^nh̛6|n֭Հ,X o$O\J.-3g :uwڥ=N} |08/^U{nd̙C Ȟ={Gƹj|߰a :$HD^{&`aHHGRGy  [`8Bw2|3KE@TεXIHsx񜙿$I7+ӧO/ *Ueܹsm&a9_dI{U eҥV>%ǎRKn wkɓ'K~͛Fz^=zTV^-wыsΆFK Xʬ_^>|Zr`7nݻwKԩ%W\0xc۷e޼yE9ry&CСC-]c?)$`uKͥH=M4i۶mF(ۍZpRre9 X/߿/\auȱ=rx6 GQ9*`Ijp] ຟ  P.2CV+Vt8N!R `T ,}K8q1LR` ?n8#8RJ^,\_zyv(4o޽W(p1өS'ѕGVbò.PljGro!sQSiݮD硍:u" dɒI2el ߍ8Pp8J 4P`*:-I\={]VwUHh lH@(BPXU;qℌ;Vw] He0aB8ÇһwoApN<|תUKsbpE#.%|<к]'aٺ;}a PVz_` !ti֭/{D ;2lUTf7 z#~l߾]{^ ӧ+VXѲYXkω7o_|KK.9rhe6K/q xL"ߡ0s=!Cu}()$@$V@;-ܣ.& 8pl۶Mpٿ;Ck4+Q ?O6vT:Fc9+KErٳ;صkWX"E4 Bae^%, 6PX9R>ǹ'"aP btΝ[Yd’\(R^nܸ!UVQǍWY_+WfӪ] XRmٲEA񃾢T8$@ ˶IBDJwX8`F|˗O)Dܷo|ԬYS#Yf5o*Uvc?z/^~y>ք5?~Pq4ɓ'eݺuFETpqAAnm3gy% /Dld N.dG۸qEȑ#wv*:.J{|z_>sb\fd$XH($I t'Ӯ];9IյF`E5+Be;u]9[݆7e63w~#e;DDsfk%"$N@Dh@g ~箖qYW(Av_hǎcQX ~ಞ&I!P~ڽ\\+YS0x=/\o-iBvZ,w2qBB &M"",3j 2Lwq.0474kvR"" jU" eҥKq,fdk׮,ʺkx+B (gΜ[`̃>䳞@bp'E;i%G3|{x 7Z@]XzK7%" (V|QopeI8I$ufK""М4A衇>(,FD@A '?C~\ˆv5\tIZ+ۗ[n9Oy\H #gh]V[\HwaۨnPh :Qq[;(]qQ\+W1Y6TEzL^{fɼZ> 0x`÷3L0!@p/ =[qK.dyP\x~=zYi}ZO((X'|Н .H@ /|'{/p>qacv[5'!C>}K.$yas1kFanV0@뮻L>|-xnfk@cHs'80Er)S -h5{YeU C/p1fM7uI6`3|?cSO57 e11ӎ$ۻwos뭷ƒywG"iIছnv.Bfv<PJCᬔ!?<6gܸqs2gqFVw ; baImAp}xM@Ts\L2 #>pgo۵{]v,?we̻zA_%" 4 W69眦CgPJ 0qD3jԨm }Ĥ%½h""P9|0qGq _2ѻz:yg2ç; +6ߛneP6|s]+g52,cufz>Ti3fhC(Ydȑˆ6sm ¢ZBdh P3X,'g>%O>kmh| 5L* qK3GPc K/\='wk[D@D@D@3G}f|Iu\" {QXDPl$9/M;bpQ1vXsW'O6aX`cԩ΂=iQ0%5`)iI ^dZ2j=m,QJD- 0XaÆjy!Va7:~ǏS|544n裏6sOXL6`ޫWPls :s1-I%DcwnR7ȇ{#^ۤxf!" LX"½RJS )܋" '꫻i>h45fРAExRa6$ٳS0?),9\W.M4)%v y h(E-ªmky[#nok_-E+|k fB}}F.x.ny+N:4ȕH]>H碽" " |P|W}W"(@ 4" %F}ce33 pߢ\@ɀ||}7 nhPb.]e9O[y䑆?IyhݎY{ͷ 3<β(D+vfqTb&]fb@HA0E@D@@1PWCm^`jy H^q*PD I r^gun/GamYϣ!AJLI^p1M-E@D@Dhp/2@`va!tB (ph\\*/"P>} Kl&~U&o~\ x7>JwIi4=@9d\=#0x`β/1c+B*B@PRxsכ)SDi5С%JiذmUò^]w]ßDD@J% Rw> SE@D5 N&?pEdRNE@B=j#Mo;%" " " " " "P |9sZݯ_?믿mv-Ұ?7.h1bH^ -J%i׮с$p 7D }{mky`Y2~{G4""P()Tc}WJڵ/)g}vp_o駟Lm()+S@s@vIxw O`w4 /Բ4[d⫩"PEr%REuT:X4!Yj@tM&;*b+s]wEĊd֊"" " {1VD@D@D HW@!/"{Yr%͟'e[?X T@8_TTũD@Dy <?~fY4JN8}i֫" $ JTY" " " " A@֠s@ A.i>y7^f玶"" es97^ک" " "Ќpoƫ6@ |fСQ[nYfem4'f8zxZD@Dj4R5*XD@D@Df@*BD@D ̘1:(ZJsX{e]fx{V[MGߘ1c?o/.ĉ͓O>irGoj)" " " " "P'p j@-رK nիW-WWu2:'f ;k +P>pS%lX wcJ$vk5j%)" "P{O"" D`Yg-2pr%R8FN&" " O@>" " " " mD`v0/ K3&L`z T2@РT_aOD@D@u=V&:<.r. ,Pr(D@tV?r)SL5&go1bлwoөSh[+"P3g)Sɓ'vڙ%X,YV^N4}W4K/tY[fY6S}E@D@D@D@p= " uH`ĉ'4_uT{`KcǚOQDY/矛Ç{/;hVYeh[+M`ڴiSNjIJyӿsw_~%V APtbƍg.r: qg~_FYfsᇛ?ڗ\>4zA}.H+"^Nh^z" " "Pp+4Q0ϱr!%+܇jPJɶn[W ʼ꫱,RRLjhCD $3G->omCXʣOM~r*_}\ꫯ7l}Ql- O VE@D@D@ꆀusTQj*m5l: `ջ~E\r%u@ճ{ĔT/?h^{،\*s=s9c3feQGPdk00={F,%" " " " " Aq4,Sر0as)sꩧY+XˑKܱ63O<+3~k{9.f(m"pdpT & {C_^5ND8p[c ۛw' /?S'?mJdcƌ1n"+Lk /Rە y饗N;-MywF G۬T.{/MiiOr[믏[g~Ε x*_^½K.9OOΙQE½. " "  L`n&" ME`77pCL^uUjByGkIe;}gh=ӳV%/JۇzE5|Ηq‚(՟|I_SiӦ<A^W;;찃Yjb 7 tV#ڵ3gXւI%G(ˎ;hb WΛvZb?+t3r*nPΗŀ榛nj~+rsH쌓O>$ N*ln+v@hܸmUD@D xE\se6pC]gu|ٳI*D+_tn>SW$ Q| nU~oe֡JްpI?V[men䡺N$.3 \[`@GpL=hwݽ3zh2 X0"T.ODs:@v q{Ō~7xT%h* {<WKve(\`{Q \Y,u^7xѝ " " "POp@ݺu3k|ZK.w1c dT(@q53U( ǻ@êw.B7,uꇥ6^9vXky?V˴ 3!>qgM&#./6 J}x^g^>l_"" " " "Nsԩ9CiTD@D- uY->(v>.,+p\\;)k'FJJ)yw%T>X6^z/3>@uI;@R+)s„ /[| {ǹkʖ6 @ 3pl^p3b;ѻwos 'j}~VT4H )) `TDZ\ʴiGD@LK.9Ws- ALl~5u'֚^€(yܡӖMKA i~mYʭKY'WfCXX76)J ^RǥKAg?Ct[oNp= :fYX}JŗT@= J<DM3eŁ;Mp+if 4] e3u&MўG,ܛZ" " "dWQmKk\'f%%t9r%8vGiFcJU/uQO+2Q}g8, Xp1TK@m@ c6Ю`5 JWCXcKKCcO^/,c=\<OK$ϖ" " " " B@ fj@ kb\)$p}20믿nN>d/*3x≘2f>}̙gbo9ݾ{R#" "P4RMj FU׮];nDXbؾFOt3ʧMMj6HpT htAqo&jj |D;B+NC7s=, Qfz P \=A }Ǡ,UbeW:\ۚ6`mO?(~M#HA C3a0Yo;Jǎ}iRfرik@{λl|k#۱zhpTG?i'M䬶mzd` 0f̘J׭[7SO9/ڠ]ve]R|r(ܽkw7"t> _9u#8n *Ubϫ" KDtE|3k#J^~ᇨ zS+mN@ 6TxֲKʑJ(y9̩ڪjZtiGD@qsO纴M~;39ks=ffv5fMpoV@C{C\F5BDN|.ӝHfK kB0i!3ʭ/>puj׮s%ʲ_\j@7py'\ux|Yn&N^l_bY:G@RjGLW]uYnr[oSN/Bfw6'|!҈#ZN:ɬ:fsrK +`(SN1_~e%,s5Qk 6po&M]' 1pyJ+ lk ؝; 1e;%=f+h3:@.lEGD@D@D|k̨QR c~G(C۷ot<׿[sXZs9زu KM:7pC0¥^jpْ?of\SW^CBIOCX30BȐ!ry׌fk}R>sQD@D@D@D@Dh,R$}YMhuSN5_;kBVw*X ur;BE`G%~}Nimؕ^x!|K/m'WfΜ/|5gt2eyl¶Epou:@)JjGA]SHD ` {>9 IAپ;`cnW./[:O0!:ݷD;rKѣ ` 0o2'l;?0nzG;2R'ݶm^gC@y@`rT9cN:m,NAw}<3YOQd=A⋛Kbヒ)qR)Yc5СC[fHju=Td@tbfuVe87_<>stT<9\N\Y*Q|嬯:N0w^S"ED@D@D@D@D@D Ȳ$" "^k\SMfv}w<[ۆ fV_}ugꫯdQYpͩS'p.dWd+{\tEkumB/삥~GQ>,uY6ex%5Rq*PD@뮻t̘1ùYveͶnkYfӡC3}tC@N( >)WXa3<8;\sM馛ZȼD eܸq ݻw7ߣCgu9>}M64K&OlcW7SN16wk˞{i~3d#0W]ub-Lǎ?,\jYd4?)ܛPE@D@D@D@D@Dhफ़*"Pp2h g~)s(g\~po/`K.1v<ܺ楗^;xNa~W_'x~Cu Q#'Ntn#?NְK^t5YD@D@D@D@D@D@D@D@vygfs1cƌqnek`>J$" _}k?t9O_vܨL{6w)쳏իSƿ[ VZɹ\pA깓;KKϞ= ǎ+ItwϾp3a3i$X1K,s#Ӯ](VDݓRD@D@D@D@D hvM_VVC4_Z:D@D@D@D@D@D@j,kҨb" " " )pO]" " " " " " I@y]T+H;AD@D@D@D@D& Ȳ&/*%" " " "9@es\֬^Z%" " "P.)%" " " " " " F@/Z'(%@SH=ID" " " " " 5A@Dj2y%t%PD@D@D@ {TD@D@D@D@DȕH۱3>$ H$m! @p/YuP-D@D@D@ HE{E@D@D@D@D@ژ," " " " E½hd " " " " "dxikN`#G#Fu^ "P>3W߿@N_~e>h>>m4>O>$Ȼۼ۱}O`رn7@_f'-XFVa402Ço&i"\;\sejZ+" " "Pk3hРMݺu3j _Gb-LΝm@HOҥYoCZ3 pYW2I*7|}.O?msd" " " "PI&M2/|Tu]g9h[+A`f5ֈ{뭷;֊kӯ_? h]D5̰g~Z(MD@D@D@ڈm^(&iL9D@D@D@ZZg(," " " " 5A@ @>lG1k1Z%" " J@ Fj4  4EUD@D@DH@SMh;R{YD@D@D@D@D >h@½. " " " " @@D*oBD@D@DHvuf<d)kr2" " " " " " =hkRE@D@D@D@D@R Ȳ9v0)kj" " " " " # h5 4V[E@D@D H^P-!j%)" " " " " " " " " " F@ Z"# W" @@=½*" " " " ML@DM}Ъ@½.*$" " " " " " ݓRD@D@DH^WIupdAD@D@D H^WGu&& &j))©" " " " "ldlWj9Z-" " J`z-" " " " " "|4|\-=yͻk{=Ӯ];B %\tŬfY c=޽9cBWƍg bƌc~lwqСC14RQ>6'0s0sĶ!" " "PFm?xsꫯn^x|eɓ'G:w/vL:5g1q$†-" " " " " U&袋7ܝe4lMϨk\UQD@D ?W^yl))mfС P ӧOPi*FG@ К@xGͨQomZj:vh{T:uֵ"" "PPfOEUp.*}9ߙ+lFfYg-(І@-mlPKUR]Z { 8f=r5t:"KfҤIQ0lƦ}O?5O>q][n(Oۄ^W>?o-RJq#D@D@D@D@D@D@j'ßDD@D>=O>3ETJjX{f~YjݔORFT_WCr/}Y9EDyX-pZ! ᮳z 6̼k_jOo\{k52eScɽkuYq*䣏>r3f9sf,)~ѷ~{W_u>ȟy3qD3s[Ϝ{C,_ ztM?عUi׮Yve<~0=y'W_}e^~)׿^8 fS>qرc?ܢv\zfH=֖;Mf 2\s{f.+Vo|A3a7`5h&7'ʴ?L<9cCy'oZ|1PKNȷ=쳛W_y\mz ?_MfmZkAOmQ a=_㰙o\?ʡ<=U,*ܳQ́?ϱ_X dmk[鮿AvUBYUW\sȐ!Q*[C{O.L׿,I:taL8}ҋK_%\4#W Xs{-w[ȳҧzYLG뮻 ֱmbf@1-a>@xW2=\AUƺN.ڡs &[@m; .`ފ2s=z|b) s9'e>Gq^"zk3 >ٓU駟B6㎩Xys/H鶱Cp,۷oowaX\0` Jl ^qL}~ź|&u.X1FM?ػ_KvwrQ܇\Kco{;S;'x"V;cԋ{;=k҄rjn"W.MS!~|y܏'b-d$? KP6dBiov',+-J4<Pa#`aUXۇz Xy4hO-yb o]Ÿ}{c?!FŃY>#Zi  M4)j `b/q!:|b l>tZh> kHYmܲPԩ-O{E dkIaA-QA_"6vtNiE/v뭷Mm/Xc5f9O. o|'N|qa7 |,\(:yC }S҇Zk]AkCŤ[n}_srAt~} FWJI+K+C wC?2 w^٦>C#1?lx;@3221s )܉}wFP>`qmň45?-t,Q w@e#l]t;u"r5Hȃ0 F%@PN>dŸDD@D@j@(pF#"d&'nHHe]i1fZ?˩|͋VgϞ2=) era?M%XZB(`l+yq/K.g5XeѶVG}C^}p%;S{Z?.}k,T(Tӵ½f0JZ:F~Kº?>5#AcblK/u>dqfOo)Q@LYz(庾 ˪u:L[X#T]\S: ꞴVU J+ڪ]V#3\&h]̙3[ |OLVV5_Tb7!6#\2OPp3|ˠի;w[!}M,UW]o\b VR?ux0i!XJqp/S{KV~C% qM7dl? 3fȑqS;)+;9mS`~dJLarMiv@AxY_yGu.Z>Cb#Jc%Wp>C/,}1Ms>7/)sX0ˁ5~M 6ZeI1eg:0)iO5:c0Sİv,?~XHH.NZYYh½͵0a |" "P5TxU~ia0+q(}Nu;CukUŠ{RR|֛."}B/KihYop R[P;FyXǃ,|.H5K]Xn)ti5X2Sg<g%U֥cر)ScVᖱA!~*b*xi}e$'b絮`o}S[%WF [+FilǮqEU Wҗ_~ymݎT' c])-(elpYu񔷬luf?4J ,PVST,;c cu5 arW˙'a:VFچᗵL۠;*u ׭r?hϺ-ia~ng/ČxZ^\W,vP&[r6#`elwgc'E.;f*ļkgp/'`ckE\P-6`_Ԯb?VY=餓 =҉@V|>^ 8w9UH\,=n˚WmV?X9|b]gge)O;5* Sx]iӾ&঑Ԕ;SSÔ n>֧`ኟt'kR > &qy?<>q/28ʤSN :bg VLWSrlvL6AnN)#ix^K9s+[ʅVX'K\BX|yr;K[bmOj,c$njbb"L=VT%ęP>3'qOsXnvD#`\O*+LODKZ tt^6MaZ_tXn.ݎ5.~0{Pa!Џ`/~+zgcf3ͭO_%32ϧM.%< ed,Kkd23sίr<|0bVH(4ܬ:0  m%Y߯onxְd2iz19(paiaŚM`#xVՃ:wWt9hT$i~1d.c}TG큃uc,c8zvX e,-b]dq 26CK *K=;f|p&hueY.m'=c洒Y&aYuw6tƺˉ̚eo*#3l:Xw2Y:'c/gvu @;9V&e`fp+_<'쀌6aK?5G +a`Ij _v&mu~6׽e}ygPeg|R Tv!4M>=_bymP*cBE8Jʥay:n)GbGAfU6Mt &b*" 3ubeZ+lEFIxl8#ڇ>\M*\щfJ"pz)WFx>*\O?1Xb@%>,<dUMEpTJXm%o)R:O {'馛F>2Gyd>:Os֚E9CQ.IwgϦpwN:Ŕp('nFIJ$294ee[@&e˛k讀 \93ϤUbGxd[ѷ R"СCd _BQ┕b?6Odk߿9X%Ok) ng[*zfpa3B|&TJ9V廕wxmaoK~oP'ͪpGt{[>:͍!;++_9ٮg Q p(aݺXNK |]L,Dp![H^թ4Vi=zdrY8 +ĤlJi_>蒝kI^6gMJZu>V™T1bD>y3+(<B$)e/^t_}*XDza0΍TϖVE@49 iKR){s)K3b^%u{f]LJ>\3 iyuIS3X}XJ#Jkr|NJA(jNbFrlܤ?^k/7c _pi+e?wuEª?fuС EHn$gtKnоE, g_YuЊ١Is9]ǰ_*.ĺk0M딬`Ц|LXaR߅\/cf? Ԃ=rƘoUn2XY ?cМ<fVuA]P?~Mcq:XͺKySְά\l} ѓCc534LA4=gL +4nW3XHI]*f_< X;peQf{ߺM+>u9?joK-@;+E(y"'^aH*md 7*LYRχ5@|'-}N4 w&a0-=ߕVqQ˃?:ujJQVV7Q!YVxsOj]u`&,4#:1~:!\HfZ1YH-|Y R5a.FPDg,pe}S(Vo; ">lҷ RV[T<] \+VEY<ú`[1܋u9vPzm}>QvPKs )i wX߅esߠPèER]v1]r#ְzH(4Nh#)(~cJ薁MHjRPrP }e4b#aӡt`пB~{9(p") gb&fڷ5y0(ҔiH+ǒLI?W4\fe*+h)VM\|ņ`yqӳmُc4QZY8~Nb-=Mۑtu.:}ZQe۰[o`T{}2m-NGp@kqRS"@a5v+jVcŎxc Qn}CĬ~3֕Jd5W0A;r0Yl?c솀1VamH!lb:Ǝ;_T},(9 s/V6D@G*bAìYR*߳V>ƺ ;VE V]֚'u4sAf*ܳ~gUǯп~ݦE$cɥtvmӟ$x(r *w[n1v6+* '`Ya?ʍU;[Jk;7GݒoIxZO)ĔE~_%9\=w+rKn_yw(l7YQ Diy5s-xX8kE*Mιg5]ln ;WװYs8w5H~?_]dt^J;Hirx6v2.W{TOI>ӜuZ;wmR>#W4ڮ"ci;80)~a0 ej\`:G8'F MZ#Iat)-)la]:S# y폦lQ09Y45)I7SV*_Xq2X3COW lXd0VHhm|R<[B_>b+D!l`)VcaBKp_|)4B/UҔR(wA$M)š-ưL@֝,Y*ժ399ے)Idlkaeu6a&Z8G,)VzK \,ﮤkT.{K+g&f:}(XWH%"OM%-ܩ?}÷dpCo>,r,܉ l}Bn5OJ< -Q;LCp9L·z(<@[Y[C,FU1МŦX:+SBTGÈ,*Bk`X]^,##VߎNm@@&&jcd<U|8 ";xbJr{j/.]hK0C>gq&ۢODbR:o6yM & ZLa5m?xJX6Y&~L-gQoc[]xJZmR;K|;k1ָ03[cYlv.=Hfُr1ciI}0U'`r,Yt^欢.j7гsM>ĺ%G,όfx=m  a Kf0pF }g) f`6>f Wlj }3^օO%Y_oKv|1Ş)Ǥrcm+(HB\^CNrL>|V_Qo2pʔRZؖMyD@J$ \³ۻuɕTE`29V [5W^pӓmRŴ AO#4)&G5S[}?>l+P_}4ᛉB][$;D :1.ߧȄz`n3 T@IDAT?ԏ>1xlbg0 y6…W£|ϼfc]ဪTK=36RٕjK8<@-#,7cl$-EJ}Fmrb;=;B+X~ﷳf*<fH`uN0Ns~ߠAEƏfRΌvs d |6֊$+ r6)ܓJR^l经_>V2_Jr.|P@fۭDRϓ,UVV)*|R'OBaJ]g_bP7 SOee )ܭ3C>~[hYgeYTjr s)ı% jXZ7Q }lO2,-yl[p>}+7| E~5,FڌG&dbM|a``Npo2H7urxNZ?{xN⽕*n ^<54e?AĭW_&޽{%Vi0ƨ!荎=KP5 Bs>AC f 7N6[~U|7W|F bo~wLIe;m Hc81[=6w%U1vc5O/X דo]f Cp/:vt<3~.3X~s1n}" " "TUFcYe()mJ@pY,\I KY&Yyg;*|*y~kK"P͠N/9!>7]a;&ہI a؏XZE=Z퀻`'w vK@s.j2=c]dc iN]DIRPP~c(BAa,#{H֫pG+Ůsoࢬ#rI>IV%I( >`Ic⡠Onv$GLc4ĹEdBlxk-_r|$Z > Wtt b>*%" ag~6 S~abru"6J{ |ɗUβR\*֬|s ~b٣,JƠ\9WYvDTJ >h."miyn$֧=S쬦N1na5|K>iPːY+~Ie/ۺF)ClNZӖ?i՟,39K IɴXˇxgm.zEu=:Uero6sRbkO@Q*@ naT7ncJ%W)Sr‹ nY5OO/ytwJ#"'_gvXpXEL'0[nKBkA :cuZy8"~8zg_Z].*!r3Ohy3).bZyVlM2kl*Ԍh7dgu-₧y\GXeNgS]UFщ`%;]_ĽW?P"[ _nvXG effܟ|s1lxvoo8WxoǺrq|}[CkGL;߇#1o*s]xۙuT' BYA5I?wuj쌭7"vݫiߗ;}U@Ma½*'=Y#>ҟ|I駃G PE{l4(A>pC!?>("A6FP:ķv #xׯO> ?@Ӟ0`[1Ds (`>ƋiҊ@k@f-ENC-a%d*oK%(7Qz3ᕒ(x#xp}u@+"PY5[h'CȝBV>*[DT[^[UmD@jSVcRuD@46UjrzV[WrzYa`j7Vh)MKCf-,sma1Y`ocr;4}QsGE3(.Z" {-] ED@D@D@D@D@Z (1  tfcdfJ2{b~l+_Y+" " " "P%RW (@-+Kor@2e.Z8-m!r#Fl8:@Nr6$_E JW%ZkS$@I8Mȉ%&!b{9P@6齫l_D@D@D@D@L+ ?8[[+[TH;D !x( 'q[ 袋̹k˨jp!DD@D@D@j|uT7hBlK&70,kСC-TEu H^0*%hMlM:@V@ ½/&" " " " " " " " " "P?R~j*" " " " " " " "PcG5޽9cm@ƍg bƌc~AI%:;po:@=z=q pZ@̍7wަSNѶV*C`̙楗^26mi߾YhK?{g.Iu=0 20@pOp!. ,HH !l2K_O>}|yTuնzNշ^{0t0Xʏ7rp ׌LDF~X {,,ͭTw?s=7]~oRD@D@D@D@H@[5kxbi&l T2h߃:(/|y處k6xQl/+qI' 7pCXfevn)ZX};- Z*am \rI{6J{?<:dh5o>,za1,O?믏)Yd 0S7h\h);B0^ =}.,,?OmXhZR* 0 𥨴 q ~FUVY%ꪫ3:љx۹8㌾emroT<w~;[1̶n5裏!e|pw2B`=P c岔Qڝ{'o}5I844x'J8YlGD@D@D@DH|{X +iS(jz30C [_q?CoF<)2y &G'{#wGp83#O^}trsOpꩧ:2X~ӌ{wlIXr%1Fo M6Y d+80p+Rgq|sgxc'  'g]s5[lfiōt&j~{/7qL7 Fsܖ^zG{'2Lˢ}K-Td> 7[+tM,ra1ȷ{c]<@Nxvc Ѿx]1 w@G?ȤS5'\p0xukܨb;b/>X=Wq&@!Cts_ev7Fӊ+^|ź>au֩* QkBH]zQ7|_|_lsSN ~oKW]~G/2׉mx# o}8:}17_fs9g{cX)ΉEOaV1Iֹf/b[>7 fijÇg߭Me PU=f0=&{0vqǘ܄n@ :_WI'ُ٫RzPܼ]z~U*" "e67[n̄Nc2{ye؋Bf.'2؋@vwf'{g+f-)3X7\aSdv[f\CEZg?2,}WП&>eLxcέutn plb/.gz{! /38N ؋sf@6fu?2̯K yow%Ef&Pezٛni1y+26lXwl~Dyxmafep{GfWjaSKZ?#_N9$dYY0-3.h:ym:$2BJ̫<#ݗN8a65jTU~ιMrM5TyY&,M܈92o]d8&Ŕs3oǯy]p,̒ùkk<j]v%C/+TkǗ5i#M̬Fܯ2sZVa7/-gy晧=ܙgSZ&Bf,>{|1j6@HpohU#" %S0!g1,lHq-3"c&0 ъj\.moG-=6BS+ϛb-x%M;*llhlo2᝺]m;&t2B^TR1'm#$p ŝs9 ,P'(SkcWEɞH%x:C=.*ˮm2guV2-|@aaj/kq ^ fmay֮:VC^,B>7CtoTI[&qJZ U_wE`V&dY 4tEp{4בM.pYh%J  k]sloTp'>jbM;ϊM3ds@Խtjɠh)3ORtr\eo"+|U竷lɳ8t"y#gt{Ng2:4:<l\IaʳbLIK"f7KpCUs&e/ x[/4ٰ)ˆHd$ K{Sk2 ~S{oW"  }S|fUx-8,_;Q|xltfq;|+cd;#ZFoܲlCO=hmHqi98qlxrCeeZS[yѝXwEɞH%x5rz. /;B@ћԻ7!!Qar,Nof!=+#”YQ,Em}o*<6"oz"h>PBtGyz<64#Zia;c,,zA<Cٹc񲚦gDM!&wY«7u48E}F@6wkYlX$섶Yx޾t=Xg=7D<{Kf0lMI ݯE%#s(E3th cO3>+yg^1i[;n<2=|ps5W'{#F{wY+vZ0X/ɫ9ꨣUMĊ,/(ŊbyQğ``Uk9^Ρyg9OWlm8Dpw E/DŽ`qyLγM8Ex,Vk<8[GC4~74XD0[mU *6bI7< L ms׋+90`aEn#/vY3qh(1IrZv`n}y^fW[8vW39PƆ:(j3Ǵ撨7Љg!<Оjp { XL/9w:7tcKWvۼsAb6x61nADv{$p?]N/l/=:?1ÇFؕvv%aEx10x!%/p@JMD x*#i| !FW#T !܎8pWឋԨ exo_YCZTl4@6h~6uC4;$t 8p̍=믏Zbѻ;SF(*:8q*C|#!2e^m /<qoyI'@8<=6njg[R#%S cuY'~< b oP6Ic{eI\DbSftR4!ϧ61kO{&^,n{gġ k A8iJF;d~WEh.va= 6^,X3W_}Xi-lbpva(ňNòz2AiӦF{n8 #j_1* A$#dLӁ>#]p|@+ߋ"H5Z}vw6x^%\~@v7w_JId͞\ф@ۄIby!ev+lСcg:Oxv}~(^|!ꫯ"r_{ƈED|#%as-OHWNBq1šý/ǁ?s0]1Ó(7DdH{uᆃUz,⭧%B1ML-SM5UXvecah5x[r3^9EqQ?\)/:I ,*pW'R7|xNx''iD ^QVylya֛=P[n+aK1壏>qɽVCKTq糳s15}i(icCj\W{bOِl2>aZ3?X07D<wW>Dh:+mbR^babFČ̟e+mulYZL־s@{yFH>LdPǰa2=c;lEszL&EdYC60<LpVF&e{Wflg"c'oFJ H3eu}-ͳf/yga2 J4eKΧ ֡T3ϟ L`ӰnIib~~sj_~yCyӲz򞜶uT{ .TqS3gxMi:ZüWt߯Šck\'`ź}r]}m SW6lDf#h2mLo:3묬h4C0N:I+[x_fiVپIYśIO\ 2Rf]-jE<DU$#`!b%\Qyu*JiM|"iCfS&qPvjmEoK#SxatKy&7XMR\(ٓS k7ɯ*w|;Kttai;[I;Eܲ%b*n ik2 ,=YfV-grWp:(TֆZJb 68;OE/.Ua[h6j8Ēc^>7?}n:u\HQrȋ0`^HɊygtФtqg3#m2IMECO9I#*kɯ.Ѧ{ǴN[L~W<_[oՓ.b}}<(mIZ džZ;)Y;eڨ;:\_VoK]|ťm,v}&;\u: eB:EN{キf Ŏݼl}4*XD@ 2|/CJiu^-6{0G0+2캍k`goi-dA˄/N=Ԝy~ѓ;=/]S Y8.wD))I;#vdqNns\,^o:+QF_>zBD4HJ|t)!^^hzdpWch?\FOȒg|0-2wqXWbYQ]YwmHa;cO}?V/9ih/J(ܸb2#D L/&=*@_D Hpa*ND@:I|.Ce~CA,`H1(*,u'[3z%G4b!(^;6)GeKBuw{N) N=غSvyIYS:"V <ޑW?K<&Zjt1 ):,ZkzHwܱaQ0#eKV[-v^V[U9*gN8f&:qu!2=\ Tryy>޶tip=K^v)}Gk1N$apuDw 2 ,L ΄:>,TQ,\8S00UYOUjyWlCu U LC{c"j78)MA+"DVذ`YMIE@{`/1LؙK "Lm1܃͋Srb16u. #aLlN H'5/8!5R[gEx 26awC Zk?8bŦ" " " "% XDMԥrID@D@D@D@D@E!eO=Ƀ o~[3c8㌚CD@D@D@D@D@D@D@D@D@D@^PE@D@D@D@D@D@D@D@D@D@D`t$ }t<:&rPD@D@D@D@D@D@D@D@D@D`t$ }t<:&h-?"U(" " " " " "0ZiȐ!a?Gy$꫑$Lb03CjW,ȑ# 6[p +߷T;E@D@D@D@D@D@Jଳ ?pc֒n$pF Nnᆰ2˔m7tS\ꫯi> ]w]Xmˊ6h i fU"" " " " " " =Cz6)S˧~暞 vy馛6*RD@D@' Y)@/Xs5|k^hzUnɂ-q7oV '۶WÓG}4)oO?=IѹUB`=P cݹZD@D@zbD@D@D@D@D@D@CCmN}TbٿSN݀L@IDAT9e1bD` @w wGp8?s#O^j$Q!" " " " " '_Dc&dB_K/f!5\Z5~_~aL$34S8p`,MN|W^y%|Lm駟“O> A>y,U.p=|Izvs!" "::֪ID@D@D@D@D@ڂQ⤔M7]l2KX~c&뮻K/*ay K.d@/;'$DMJ+%"A1k7|6|({j-/Z׿A40ӆ=3_VO3-jHe]OYdRK駟>y晅Rz+l]w04ӄe]6/;hs9!IWs8kviK/<|?g"MO~r^p:nߴ ގ-"'%^^xauY#_|묳NXl#ށi)" "P}Fy9fP8ED@D@D@D' \tEƮ|LLh eefyʠgugZ~WfmaYN:ivuUglP裏L/G?םgqFj?Ү;㏯jâ.ZQUi492kk%εTkm[* 2,pYO=TU>3,;}_ w(D@D@D@D@D@D{Vs5^L^xX㹎1>&M|Øcf}_2U80VҞbI:L8a1ƨxL%_zpW{px7hNا~qyZ_~c|ƪĊ8#{k 顕Z+m}ݣg%!h:{[o]?8xU/bІ=#kˋ-2;._x|y';nqduڄÇWN^{ {FW^yp뭷IwCL:xzvy%iߎ_u4hP\o=ܓ'#,o~󛼃?믿>uy!Z$ӯ%piŸ|#fQGXÆ ?C019"6pw{7}Ǘzj^~@W:8Kbꫯx7bni847x)'θ )nBT 5waG;.4Yv ?B>?Ź NLD@D@j^@?"0cm1Ji"ȤxLAVlO< ][sGFo~| 'tRUn;oosԎ|vi8 /R8fr,18@=> giW_͋ =+‚;%X"h\n3B4j hksԙ6*-^6aoڋp0πLD@D@j^4DNw&f%_|c`#*&d<ǻロZI='xZɺ}V.稧z%f?'xcuկ~3LD@D@Hp/m" " " " " "   )sWy#n3g3Z W2tUuNx\WGe^hY5,b!%,N>8pF" " "-E@D@D@D@D@D@Dꫯb6[z1̋Ad_?֬q'lxlµ^[U'|j+cSx֓爑 ~gU`ᆱ/HGy:s5ׄM6$r){,oo|ef m= Z<Z@`ԨQQ`fP>^a]vMq:;찊'xbx '&`(#d3r-?p(Ll:s5\3z W/rHM?aÆE6 [o 0p;蠃np Bm**#& N:i 3^NO-o>OywuWr)s=w <ې!Czg9ms~e1?@zyj|MLsdžZ*Bo Oo8.CMX_dE‹/m汃|qxmꫯ!<rUVNGAhK/j)" " _&"0\Ofp8ED@D@D@D' \tEc˙g&f~ivqUl'/]pUr뭓Q3?笳Ϊgb#W;9-/o(oZ i=>|NgV'%\qMѺ y{NlB +s=wq*&IL% 㺲΅BN,S2h1EPH(D@D@D@D@D@D`VX!m駏qN v-[lE`T¸n4kgp_3</zKCxW^9h j._Cf.u]Wy#98=yr& <8z؏=qmnD@(FsXv y㦛n ?9_8p`X~޼nڱƬa.c2kP5f}0tbNC|~5\1LGO0$;P;tX,aŐ9! 3Zh0묳vwtLГsCEzꩧA.vڴ#a){k0;t Bfȑ1_|;õu\z{4`~(BD/ng! ߜj+}4U-E@ZL cZU'" " " " " " s '0,k ^q衇/|wqO?^xᅐFCŃu1Lj]D@D@D@ڂ8 j_N? gpqM" " " "[ZzE@D@D@D@D@D@ eY&0eј쒉N/p'w yͩPCD@D@D@D@D@DuYó>>G O>y@t@K!OD@D@D@D@D@I'WD@D@D`t!2˙q* _.$.gR!" " " " " " " " " "ЫýWrH_ ?xo&eja&ry~ _}l/rʆkzh1 믿Rz^?j@_#pM7}ݷ^o‡~6xXO?)# ~\#FhXp;oߩCwq>jԨ믇+,:n%n_ܨe" " " " " " " " mJ`))?3ՓM6YviڻY6[4hPl$MR:mzu.=Hp=ΣBD@D@D@D@D@D@D@Dr 7o3(KM:cSM5Utqu]'xGc>D@#0$w}j@Mkhn \zꪆ}Waȑaq >{iLO qy{}W#7x#0I,~9 Ry\-xp7W_}5ہxW2K13. fa0|9IL(&qk:EiE@D@D@D@D@D@D@D@$kYg5N'M+#_x# ns1Gc~m&?SL3<3 Wsυ7?Oc & ,R~e*ymzg}r)j|[VN?^{:xҪ?]v%L4DLYt饗>:K^d`ze]6}yO>$߷ꪫ_>BzQc7 *眲%dze?cp=< w\>q̈́}dYgm=&w̝w9vpt 'gwIIOb*a" " " " " " " "N:餰GQu%_|E@Fd_{ù!CDgk sA׏.b;xwg rKp]a` BDZ+C{Ӭa뭷b;ak]pO|z4'<;C8o]Ǫ򗿔VIĈ#-"OBxk ιyذaSN9%>g<1y.ɁQB }y睁KdžX~Nڃ@K<1裏bOwL8Q ! /eóxJeW _2V]D@D@D@D@D@D@D@6(V$u]7,Q$ǣgpx=cc!ݍ(?C@șԈO>h  Z0`cx6s/1;*{ChY{/n?syC.<\:~_D& ,7<|1=!=/ 5CFjtabvOc9&\yfFyyZ6@=EUf\ ,m_ن5M 둉@g B w GTl~W0׿CtGZ~Sc@FNo*+^0`}4Ý^-7f9g+RjGg 8z蘁ըuga~\;WZQ&^FX_Ĝb~0< f3OC}XQzð!pgi0tJ" " " " " " " "RxOv-4hPPKH Qut2CpE#tm_m28^fa+ߛni(P+qyr-<b+e< 2<-#]vi/'L7+/͢6!tDnp Kzm*5z&dMb"l`\NW˸tee+1R&s衇%\2^5^JL+&HYC&n~iGsB.9tbǑ"1%k?>J^z8?n_' -X'a#gyУ꽪t0N;?}QGš@MK mbֹcVQg1.k P+Oq;NiZѐРü^:zH0TS_{d>osNP8Q5r6*j.1Kxx≁^'m23[.zS.)y?6o Ge3:u"3+P, U x#{y /9?!\pAY-F,)n :Mˤ7K|HDYLꪫz(pbl!3 XUtR4j5čr.)3bpFL%х h>8B6bIQ|Y+B'2B:zAfSvvtG3N ;, zk f=Ԉӏ$Ұ}6:QhtILGg)3zi2nuY'OYE0<]hMv?c&aLЍmoКq陉>VL%dy[O`v]weC -FUf1-4NC9$np 3iqN"'j)[X&I;X_^Ƽp馛M.ӘnάW1nwWdL2Iܿ6TvdT0B~dqyG>|6cQ|6c{fq}jiOֻ٧ڭƊ@w .խh|,vr,pLGZ ے!Tʎ;XtsźEiгa4n39׸|_GKsyq\hgڟ< hts,|sf 9VFQ/,,|E?nl*w=kݵNsp(@ Q;ؒ]=e }7Y1-!u9洗.!, "Fh7?La|8ꇝٱ=%I\c6b,0m}1܌AKOE'6l6mVCD@D@D@D@D@D@D@ڟ@YLV2, Q{nS>zc(?:}6!kY gyfu!֣!{tv>=e~Ө3<9O]1m!yҰ=>ѥ.#0F=,sO>^.w}qBv~7j.wg9#!G“D~ wpzF{sS,khc=D eΝxp#K vb!Oj|rLD@D@D@D@D@D@D@FgD` 8axgib{/ۋihֱ,=Pnw&9;RT+"nfO#&'n`w 3۰NjbPB_yWcO?}Yq*ܺy$ $a2 B,Umq![S_fx#3uR+ۊpb16M8eIMD@D@D@D@D@D@D@D@D@4]pcI 0;PEc`ỷ]+4G9S"Dxc~xEfFG%ݨQb:"xI|1DTEt>c%X",qz 1b)-> {Gp 19+!rysxSN;VΎZ*ƒ ˒2?!kz|]bqm0 :ߎ1gmiOMk)" " " " " " " " " 5 t3q6[tE3kDa& xLzُ?X.W^98Rq݆wd&g"^{bwfc^곐5 ,w^UyM,.RleݓM6Yf^un1lo8K yY+fQq<0u8YA|gg-~isL~h+νŖlAqy%2L8oI7:/1=xWzu'mŞϬcv`1M9TOD@D@D@D@D@D@z.߉;-W@624ÝJ&`~䪫1؆4j#矏x_veꫯJ*3n"v܍4x.=N^^4f&<҅ FcFO.'= $3pQꫯj/b`fb$L;p@Ϧ%0en&dV]Dd>Td u]7$!.\[xK pX ? 0&}9-iyͧ^Ot1k>8!k4}_X'O?&x0lzt&5/`^񝎗6sYM_K逢<Ç~Wv@ 0gg=v*@D@'i㗻7^HR"TYz饻3Yx'ܿwf;%~:—ԝyYf)f8gv1&QF.mR;D@D@D@D@D@D@D@D@D@&hjhY)$ & J ƛ)f1D@D@D@D@D@D@D@D@D@D@*>#>2VE@D@D@D@D@D@D@D@D@D@'Y(" " " " " " " " " "|ܛX5I!43V " " " " " " " " " " ~pu" " " " " " " " " " ' U@? d@ Hpo>c " " " " " " " " " "Hp'Y(" " " " " " " " " "|ܛX5I!43V " " " " " " " " " " ~pu" " " " " " " " " " ' U@? d@ Hpo>c " " " " " " " " " "Hp'Y(" " " " " " " " " "|ܛX5I!43V " " " " " " " " " " ~pu" " " " " " " " " " ' U@? d@ Hpo>c " " " " " " " " " "Hp'Y(" " " " " " " " " "|ܛX5I!43V " " " " " " " " " " ~pu" " " " " " " " " " ' U@? d@ Hpo>c " " " " " " " ";ndI AZPj2""W (ka's9gGKɳ>fm0TEG2_> 4(?5RUnnᥗ^ ?~:thec@zm*8Z|ɻYZ? K.d`~9L2Ia5!s?wܰ⋷ \g}q9a SM5Ugq6<ÀeI'-*{' s=w$v5jTovvHpohU#" " " " " " =Mয়~bog !YZh0;ߙZwߍ34ӔVL%aXzzu4ۈ&@zm BߎK}l籯B,fa;z^y9>W7W8w܇^x@S蠘iPt{Ugq0묳V+n/ߡO}t:}"?ӆ!CA{/U/" " " " " " &g!Vjno7S*[G20Xc,ޠ}]CFJ@$8p`+V]."PwtM7Fz{3GH1"sk.e<&, ofGgK-3SsoNFH0RmZNCv;#j@W{FN8aX`I4}@hn_2<_ qE4DuY/i63jvF002n/rx(-z뭘CG:g_:qCޑdISL}mOQp3j@[ \uHZi(ԋըFBwLO4#he(8_ֈDniO׆;]v 6li3yZvN2&J^Ğ^ 5#q2)njxݑ3m&!F8f[&ߋƵי{EzMs)~kZo?O ><^G/b5wy'ޗkGm0駟>ޯ~ܛ<iᅞ^e7W;vP >R[~KM_xq.%&v i<hO("q:|e #IF|Nvsĵ(]ڤ s^[e$;`IDATJvG;3N%v8sM#ۊt)v!" " " " " " "mEv1|R5B5B+ms&DLb$w䅙:^`eVk?b0tiP龮|!:W& Q . {Dj.=/61O7枵+k]I*B+,|kߛ ~E>u~9wϗ+o៑ q~;m9e4x#׺H{yܺʒsvbz) yy%"kq\CcAu6ҕ:NFP7)*룏>^h )ӎgEmNHH q֍HDEhFpūENax#h~-p~j];lOu?wC R˸бY?Za{=ӎGALC>gy_{*цmYpeo Tf &nڵzUwn;{?* DXl=ϱ:glf;<9P@| #xgL!󡭈;i1gH5RvY[}%/meW{=jA8 9#ܓ׻fgv<:F8Ė)?e/xݷI^*{/cah_87w9cM$  H@$  H@'XB!{ =pDb^ľaľxs21Ag,olsO(eƃU|<<tWt7coϸɋ'r劘ݔ+ 0&PvMۄH色Mnl)huɼc/ yQp#bw\ sWDJ¿e<+Ͻ{S~sگcʋ;U'/| v>۷o1kYv޲`pAF#y}\}Z#|޴_N̉L2©dnf걍VKI3ǚ'/_`XM8sЃEoYޣRS)'>Hsyh ;hͳ.^QߤnNoF SRߴ`_fľH= ^zutbC=πsžLl}'eOv|?98ꃽpf}:dFpUETFt+ExDDXSS)Exrx( !.O-a?.{0[ S1o0FF( ވ0_;9c+H6O ϟ?|8<D?016Sm ^'?$pXB $TʗAB$ybeGiȋx#^paD0NY:X啱"6uY/2߫s-)oFS v'6>=lC_$5ṗijc)jqgwҥ`'"~&Y[džRvCY&lڵk-H̝5>wڣ]AA h_0}L'$NOxkZ$  H@+a/޹!xbA+b^x$"-:M˿Bb[36XG[}ZVm0M[ 7aO<{%MugmbNzc02Js)2o{9+džRmdSvX/c^}5_}>7*{hbu` H@$  H@$2K#>&ꌅPȿ-B2 1R0H2eWP{/+_1C0l1 [s^_Ϻ]{s'uӪ{E&uvev,9$=dx٣^i{*}Qy~~-.9I@$  H@$&PLh?R&P$  H C«$  H@$  H@'y %1y1!q\%1 geo K@wܿ$  H@$  L؇+Sc|W^^B Eޓ n$ qgvG͞/M݇$0#_:#L$  H@N^K(E' $p $  H@$  H@/$  H` Ct$  H@$  H@&p+Wܹsg%$  lڤ$  H@$  H@ |>}}( H`A@]3$  H@$  H@$  @@}6! H@$  H@$  H@Pp$  H@$  H@$  H@3PpMH@$  H@$  H@ܵ H@$  H@$  H@ gh$  H@$  H@$ A8>}vj1_Y33g" H@$  H@XNׯ%N//|LvE@}WG;&p~b;$  H@$  H@? HdYشIENDB`bpftrace-0.9.4/man/000077500000000000000000000000001361633214400140705ustar00rootroot00000000000000bpftrace-0.9.4/man/CMakeLists.txt000066400000000000000000000000271361633214400166270ustar00rootroot00000000000000add_subdirectory(man8) bpftrace-0.9.4/man/man8/000077500000000000000000000000001361633214400147335ustar00rootroot00000000000000bpftrace-0.9.4/man/man8/CMakeLists.txt000066400000000000000000000007061361633214400174760ustar00rootroot00000000000000find_program(GZIP gzip) file(GLOB FILES *.8) set(GZFILES "") foreach(FIL ${FILES}) get_filename_component(NAME ${FIL} NAME) add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${NAME}.gz COMMAND ${GZIP} -c ${FIL} > ${CMAKE_CURRENT_BINARY_DIR}/${NAME}.gz DEPENDS ${FIL}) list(APPEND GZFILES "${CMAKE_CURRENT_BINARY_DIR}/${NAME}.gz") endforeach() add_custom_target(man ALL DEPENDS ${GZFILES}) install(FILES ${GZFILES} DESTINATION man/man8) bpftrace-0.9.4/man/man8/bashreadline.8000066400000000000000000000025601361633214400174500ustar00rootroot00000000000000.TH bashreadline 8 "2018-09-06" "USER COMMANDS" .SH NAME bashreadline.bt \- Print bash commands system wide. Uses bpftrace/eBPF. .SH SYNOPSIS .B bashreadline.bt .SH DESCRIPTION bashreadline traces the return of the readline() function using uretprobes, to show the bash commands that were entered interactively, system wide. The entered command may fail: this is just showing what was entered. This program is also a basic example of bpftrace and uretprobes. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace bash commands system wide: # .B bashreadline.bt .SH FIELDS .TP TIME A timestamp on the output, in "HH:MM:SS" format. .TP PID The process ID for bash. .TP COMMAND Entered command. .SH OVERHEAD As the rate of interactive bash commands is expected to be very low (<<100/s), the overhead of this program is expected to be negligible. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO opensnoop(8) bpftrace-0.9.4/man/man8/biolatency.8000066400000000000000000000034121361633214400171550ustar00rootroot00000000000000.TH biolatency 8 "2018-09-13" "USER COMMANDS" .SH NAME biolatency.bt \- Block I/O latency as a histogram. Uses bpftrace/eBPF. .SH SYNOPSIS .B biolatency.bt .SH DESCRIPTION This tool summarizes time (latency) spent in block device I/O (disk I/O) as a power-of-2 histogram. This allows the distribution to be studied, including modes and outliers. There are often two modes, one for device cache hits and one for cache misses, which can be shown by this tool. Latency outliers will also be shown. This tool currently works by dynamic tracing of the blk_account*() kernel functions, which will need updating to match any changes to these functions in future kernels versions. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace block device I/O (disk I/O), and print a latency histogram on Ctrl-C: # .B biolatency.bt .SH FIELDS .TP 1st, 2nd This is a range of latency, in microseconds (shown in "[...)" set notation). .TP 3rd A column showing the count of operations in this range. .TP 4th This is an ASCII histogram representing the count column. .SH OVERHEAD Since block device I/O usually has a relatively low frequency (< 10,000/s), the overhead for this tool is expected to be negligible. For high IOPS storage systems, test and quantify before use. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO biosnoop(1) bpftrace-0.9.4/man/man8/biosnoop.8000066400000000000000000000034241361633214400166570ustar00rootroot00000000000000.TH biosnoop 8 "2018-09-11" "USER COMMANDS" .SH NAME biosnoop.bt \- Block I/O tracing tool, showing per I/O latency. Uses bpftrace/eBPF. .SH SYNOPSIS .B biosnoop.bt .SH DESCRIPTION This is a basic block I/O (disk I/O) tracing tool, showing each I/O event along with the issuing process ID, and the I/O latency. This can be used to investigate disk I/O performance issues. This tool currently works by dynamic tracing of the blk_account*() kernel functions, which will need updating to match any changes to these functions in future kernels versions. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace block I/O events, printing per-line summaries: # .B biosnoop.bt .SH FIELDS .TP TIME Time of the I/O completion, in milliseconds since program start. .TP COMM Issuing process name. This often identifies the issuing application process, but I/O may be initiated from kernel threads only. .TP PID Issuing process ID. This often identifies the issuing application process, but I/O may be initiated from kernel threads only. .TP ARGS Process name and arguments (16 word maximum). .SH OVERHEAD Since block device I/O usually has a relatively low frequency (< 10,000/s), the overhead for this tool is expected to be negligible. For high IOPS storage systems, test and quantify before use. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool provides more fields. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO opensnoop(8) bpftrace-0.9.4/man/man8/biostacks.8000066400000000000000000000036751361633214400170210ustar00rootroot00000000000000.TH biostacks 8 "2019-07-12" "USER COMMANDS" .SH NAME biostacks \- Show disk I/O latency with initialization stacks. Uses bpftrace/eBPF. .SH SYNOPSIS .B biostacks .SH DESCRIPTION This tool shows disk I/O latency histograms for each block I/O initialization path. This can help reveal the reason for different latencies, as some may be created by log flushing, others by application reads, etc. This works by tracing the blk_account_io_start() and the blk_start_request() or blk_mq_start_request() functions using dynamic instrumentation. Linux 5.0 removed the classic I/O scheduler, so the blk_start_request() probe can be removed from the tool (just delete it). This tool may need other maintenance to keep working if these functions change in later kernels. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace disk I/O latency with initialization stacks: # .B biostacks.bt .SH FIELDS .TP 0th An initialization kernel stack trace (shown in "@[...]") is printed before each I/O histogram. .TP 1st, 2nd This is a range of I/O latency, in microseconds (shown in "[...)" set notation). .TP 3rd A column showing the count of I/O in this range. .TP 4th This is an ASCII histogram representing the count column. .SH OVERHEAD The rate of biostacks should be low (bounded by device IOPS), such that the overhead of this tool is expected to be negligible. .SH SOURCE This tool originated from the book "BPF Performance Tools", published by Addison Wesley (2019): .IP http://www.brendangregg.com/bpf-performance-tools-book.html .PP See the book for more documentation on this tool. .PP This version is in the bpftrace repository: .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO biosnoop(8) bpftrace-0.9.4/man/man8/bitesize.8000066400000000000000000000027771361633214400166570ustar00rootroot00000000000000.TH bitesize 8 "2018-09-07" "USER COMMANDS" .SH NAME bitesize.bt \- Show disk I/O size as a histogram. Uses bpftrace/eBPF. .SH SYNOPSIS .B bitesize.bt .SH DESCRIPTION This can be used to characterize the distribution of block device (disk) I/O sizes. To study block device I/O in more detail, see biosnoop(8). This uses the tracepoint:block:block_rq_issue tracepoint, and is a simple example of bpftrace. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace block I/O and summarize as a histogram by process: # .B bitesize.bt .SH FIELDS .TP 0th A process name (shown in "@[...]") is printed before each I/O histogram. .TP 1st, 2nd This is a range of I/O sizes, in Kbytes (shown in "[...)" set notation). .TP 3rd A column showing the count of I/O in this range. .TP 4th This is an ASCII histogram representing the count column. .SH OVERHEAD Since block device I/O usually has a relatively low frequency (< 10,000/s), the overhead for this tool is expected to be low or negligible. For high IOPS storage systems, test and quantify before use. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO biosnoop.bt(8) bpftrace-0.9.4/man/man8/bpftrace.8000066400000000000000000000203101361633214400166060ustar00rootroot00000000000000. .TH "BPFTRACE" "8" "October 2018" . .SH "NAME" \fBbpftrace\fR \- the eBPF tracing language & frontend . .SH "SYNOPSIS" bpftrace [\fIOPTIONS\fR] \fIFILE\fR . .br bpftrace [\fIOPTIONS\fR] \-e \'program code\' . .SH "DESCRIPTION" bpftrace is a high\-level tracing language for Linux enhanced Berkeley Packet Filter (eBPF) available in recent Linux kernels (4\.x)\. . .P bpftrace uses: . .IP "\(bu" 4 \fILLVM\fR as a backend to compile scripts to BPF\-bytecode . .IP "\(bu" 4 \fIBCC\fR for interacting with the Linux BPF system . .IP "" 0 . .P As well as the existing Linux tracing capabilities: . .TS tab(@) allbox; ccc. @kernel@userland static@\fItracepoints@USDT\fR* probes dynamic@\fIkprobes@uprobes\fR .TE . .P . *USDT = user-level statically defined tracing . .P The bpftrace language is inspired by awk and C, and predecessor tracers such as DTrace and SystemTap\. . .P See \fBEXAMPLES\fR and \fBONELINERS\fR if you are impatient\. . .br See \fBPROBE TYPES\fR and \fBBUILTINS (variables/functions)\fR for the bpftrace language elements\. . .SH "OPTIONS" . .TP \fB\-l [searchterm]\fR List probes. . .TP \fB\-e \'PROGRAM\'\fR Execute PROGRAM. . .TP \fB\-p PID\fR Enable USDT probes on PID. Will terminate bpftrace on PID termination. Note this is not a global PID filter on probes. . .TP \fB\-c CMD\fR Helper to run CMD. Equivalent to manually running CMD and then giving passing the PID to -p. This is useful to ensure you've traced at least the duration CMD's execution. . .TP \fB\--unsafe\fR Enable unsafe builtin functions. By default, bpftrace runs in safe mode. Safe mode ensure programs cannot modify system state. Unsafe builtin functions are marked as such in \fBBUILTINS (functions)\fR. . .TP \fB\--btf\fR Force BTF data processing if it's available. By default it's enabled only if the user does not specify any types/includes. . .TP \fB\-v\fR Verbose messages. . .TP \fB\-d\fR Debug info on dry run. . .TP \fB\-dd\fR Verbose debug info on dry run. . .SH "EXAMPLES" . .TP \fBbpftrace \-l \'*sleep*\'\fR List probes containing "sleep". . .TP \fBbpftrace \-e \'kprobe:do_nanosleep { printf("PID %d sleeping\en", pid); }\'\fR Trace processes calling sleep. . .TP \fBbpftrace \-c \'sleep 5\' \-e \'kprobe:do_nanosleep { printf("PID %d sleeping\en", pid); }\'\fR run "sleep 5" in a new process and then trace processes calling sleep. . .TP \fBbpftrace \-e \'tracepoint:raw_syscalls:sys_enter { @[comm]=count(); }\'\fR Count syscalls by process name. . .SH "ONELINERS" For brevity, just the the actual BPF code is shown below\. . .br Usage: \fBbpftrace \-e \'bpf\-code\'\fR . .TP New processes with arguments: \fBtracepoint:syscalls:sys_enter_execve { join(args\->argv); }\fR . .TP Files opened by process: \fBtracepoint:syscalls:sys_enter_open { printf("%s %s\en", comm, str(args\->filename)); }\fR . .TP Syscall count by program: \fBtracepoint:raw_syscalls:sys_enter { @[comm] = count(); }\fR . .TP Syscall count by syscall: \fBtracepoint:syscalls:sys_enter_* { @[probe] = count(); }\fR . .TP Syscall count by process: \fBtracepoint:raw_syscalls:sys_enter { @[pid, comm] = count(); }\fR . .TP Read bytes by process: \fBtracepoint:syscalls:sys_exit_read /args\->ret/ { @[comm] = sum(args\->ret); }\fR . .TP Read size distribution by process: \fBtracepoint:syscalls:sys_exit_read { @[comm] = hist(args\->ret); }\fR . .TP Disk size by process: \fBtracepoint:block:block_rq_issue { printf("%d %s %d\en", pid, comm, args\->bytes); }\fR . .TP Pages paged in by process: \fBsoftware:major\-faults:1 { @[comm] = count(); }\fR . .TP Page faults by process: \fBsoftware:faults:1 { @[comm] = count(); }\fR . .TP Profile user\-level stacks at 99 Hertz, for PID 189: \fBprofile:hz:99 /pid == 189/ { @[ustack] = count(); }\fR . .SH "PROBE TYPES" . .SS "KPROBES" Attach a bpftrace script to a kernel function, to be executed when that function is called: . .P \fBkprobe:vfs_read { \.\.\. }\fR . .SS "UPROBES" Attach script to a userland function: . .P \fBuprobe:/bin/bash:readline { \.\.\. }\fR . .SS "TRACEPOINTS" Attach script to a statically defined tracepoint in the kernel: . .P \fBtracepoint:sched:sched_switch { \.\.\. }\fR . .P Tracepoints are guaranteed to be stable between kernel versions, unlike kprobes\. . .SS "SOFTWARE" Attach script to kernel software events, executing once every provided count or use a default: . .P \fBsoftware:faults:100\fR \fBsoftware:faults:\fR . .SS "HARDWARE" Attach script to hardware events (PMCs), executing once every provided count or use a default: . .P \fBhardware:cache\-references:1000000\fR \fBhardware:cache\-references:\fR . .SS "PROFILE" Run the script on all CPUs at specified time intervals: . .P \fBprofile:hz:99 { \.\.\. }\fR . .P \fBprofile:s:1 { \.\.\. }\fR . .P \fBprofile:ms:20 { \.\.\. }\fR . .P \fBprofile:us:1500 { \.\.\. }\fR . .SS "INTERVAL" Run the script once per interval, for printing interval output: . .P \fBinterval:s:1 { \.\.\. }\fR . .P \fBinterval:ms:20 { \.\.\. }\fR . .SS "MULTIPLE ATTACHMENT POINTS" A single probe can be attached to multiple events: . .P \fBkprobe:vfs_read,kprobe:vfs_write { \.\.\. }\fR . .SS "WILDCARDS" Some probe types allow wildcards to be used when attaching a probe: . .P \fBkprobe:vfs_* { \.\.\. }\fR . .SS "PREDICATES" Define conditions for which a probe should be executed: . .P \fBkprobe:sys_open / uid == 0 / { \.\.\. }\fR . .SH "BUILTINS" The following variables and functions are available for use in bpftrace scripts: . .SS "VARIABLES" . .TP \fBpid\fR Process ID (kernel tgid) . .TP \fBtid\fR Thread ID (kernel pid) . .TP \fBcgroup\fR Cgroup ID of the current process . .TP \fBuid\fR User ID . .TP \fBgid\fR Group ID . .TP \fBnsecs\fR Nanosecond timestamp . .TP \fBcpu\fR Processor ID . .TP \fBcomm\fR Process name . .TP \fBkstack\fR Kernel stack trace . .TP \fBustack\fR User stack trace . .TP \fBarg0\fR, \fBarg1\fR, \.\.\. etc\. Arguments to the function being traced . .TP \fBretval\fR Return value from function being traced . .TP \fBfunc\fR Name of the function currently being traced . .TP \fBprobe\fR Full name of the probe . .TP \fBcurtask\fR Current task_struct as a u64\. . .TP \fBrand\fR Random number of type u32\. . .SS "FUNCTIONS" . .TP \fBhist(int n)\fR Produce a log2 histogram of values of \fBn\fR . .TP \fBlhist(int n, int min, int max, int step)\fR Produce a linear histogram of values of \fBn\fR . .TP \fBcount()\fR Count the number of times this function is called . .TP \fBsum(int n)\fR Sum this value . .TP \fBmin(int n)\fR Record the minimum value seen . .TP \fBmax(int n)\fR Record the maximum value seen . .TP \fBavg(int n)\fR Average this value . .TP \fBstats(int n)\fR Return the count, average, and total for this value . .TP \fBdelete(@x)\fR Delete the map element passed in as an argument . .TP \fBstr(char *s)\fR Returns the string pointed to by \fBs\fR . .TP \fBprintf(char *fmt, \.\.\.)\fR Print formatted to stdout . .TP \fBprint(@x[, int top [, int div]])\fR Print a map, with optional top entry count and divisor . .TP \fBclear(@x)\fR Delete all key/values from a map . .TP \fBsym(void *p)\fR Resolve kernel address . .TP \fBusym(void *p)\fR Resolve user space address . .TP \fBkaddr(char *name)\fR Resolve kernel symbol name . .TP \fBuaddr(char *name)\fR Resolve user space symbol name . .TP \fBreg(char *name)\fR Returns the value stored in the named register . .TP \fBjoin(char *arr[])\fR Prints the string array . .TP \fBtime(char *fmt)\fR Print the current time . .TP \fBcat(char *filename)\fR Print file content . .TP \fBntop([int af, ]int|char[4|16] addr)\fR Convert IP address data to text . .TP \fBsystem(char *fmt)\fR (unsafe) Execute shell command . .TP \fBexit()\fR Quit bpftrace . .TP \fBkstack([StackMode mode, ][int level])\fR Kernel stack trace . .TP \fBustack([StackMode mode, ][int level])\fR User stack trace . .SH "FURTHER READING" The official documentation can be found here: . .br https://github\.com/iovisor/bpftrace/blob/master/docs . .SH "HISTORY" The first official talk by Alastair on bpftrace happened at the Tracing Summit in Edinburgh, Oct 25th 2018\. . .SH "AUTHOR" Created by Alastair Robertson\. . .br Manpage by Stephan Schuberth\. . .SH "SEE ALSO" \fBman \-k bcc\fR, after having installed the \fIbpfcc\-tools\fR package under Ubuntu\. . .SH "CONTRIBUTING" Prior to contributing new tools, read the official checklist at: . .br https://github\.com/iovisor/bpftrace/blob/master/CONTRIBUTING\-TOOLS\.md bpftrace-0.9.4/man/man8/capable.8000066400000000000000000000027001361633214400164120ustar00rootroot00000000000000.TH capable 8 "2018-09-08" "USER COMMANDS" .SH NAME capable.bt \- Trace security capability checks (cap_capable()). .SH SYNOPSIS .B capable.bt .SH DESCRIPTION This traces security capability checks in the kernel, and prints details for each call. This can be useful for general debugging, and also security enforcement: determining a white list of capabilities an application needs. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF, bpftrace. .SH EXAMPLES .TP Trace all capability checks system-wide: # .B capable.bt .SH FIELDS .TP TIME(s) Time of capability check: HH:MM:SS. .TP UID User ID. .TP PID Process ID. .TP COMM Process name. CAP Capability number. NAME Capability name. See capabilities(7) for descriptions. .TP AUDIT Whether this was an audit event. .SH OVERHEAD This adds low-overhead instrumentation to capability checks, which are expected to be low frequency, however, that depends on the application. Test in a lab environment before use. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool provides options to customize the output. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO capabilities(7) bpftrace-0.9.4/man/man8/cpuwalk.8000066400000000000000000000023101361633214400164660ustar00rootroot00000000000000.TH cpuwalk 8 "2018-09-08" "USER COMMANDS" .SH NAME cpuwalk.bt \- Sample which CPUs are executing processes.. Uses bpftrace/eBPF. .SH SYNOPSIS .B cpuwalk.bt .SH DESCRIPTION This tool samples CPUs at 99 Hertz, then prints a histogram showing which CPUs were active. 99 Hertz is used to avoid lockstep sampling that would skew results. This tool can help identify if your application's workload is evenly using the CPUs, or if there is an imbalance problem. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Sample CPUs and print a summary on Ctrl-C: # .B cpuwalk.bt .SH FIELDS .TP 1st, 2nd The CPU is shown in the first field, after the "[". Disregard the second field. .TP 3rd A column showing the number of samples for this CPU. .TP 4th This is an ASCII histogram representing the count column. .SH OVERHEAD This should be negligible. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO mpstat(1) bpftrace-0.9.4/man/man8/dcsnoop.8000066400000000000000000000040151361633214400164710ustar00rootroot00000000000000.TH dcsnoop 8 "2018-09-08" "USER COMMANDS" .SH NAME dcsnoop.bt \- Trace directory entry cache (dcache) lookups. Uses bpftrace/eBPF. .SH SYNOPSIS .B dcsnoop.bt .SH DESCRIPTION By default, this traces every dcache lookup, and shows the process performing the lookup and the filename requested. The output of this tool can be verbose, and is intended for further investigations of dcache performance beyond dcstat(8), which prints per-second summaries. This uses kernel dynamic tracing of the d_lookup() function, and will need and will need updating to match any changes to this function. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bcc. .SH EXAMPLES .TP Trace all dcache lookups: # .B dcsnoop.bt .SH FIELDS .TP TIME(ms) Time of lookup, in milliseconds. .TP PID Process ID. .TP COMM Process name. .TP T Type: R == reference, M == miss. A miss will print two lines, one for the reference, and one for the miss. .TP FILE The file name component that was being looked up. This contains trailing pathname components (after '/'), which will be the subject of subsequent lookups. .SH OVERHEAD File name lookups can be frequent (depending on the workload), and this tool prints a line for each failed lookup, and with \-a, each reference as well. The output may be verbose, and the incurred overhead, while optimized to some extent, may still be from noticeable to significant. This is only really intended for deeper investigations beyond dcstat(8), when absolutely necessary. Measure and quantify the overhead in a test environment before use. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO dcstat(8) bpftrace-0.9.4/man/man8/execsnoop.8000066400000000000000000000034661361633214400170400ustar00rootroot00000000000000.TH execsnoop 8 "2018-09-11" "USER COMMANDS" .SH NAME execsnoop.bt \- Trace new processes via exec() syscalls. Uses bpftrace/eBPF. .SH SYNOPSIS .B execsnoop.bt .SH DESCRIPTION This traces when processes call exec() (execve()). It is handy for identifying new processes created via the usual fork()->exec() sequence. Note that the return value is not currently traced, so the exec() may have failed. This tool is useful for debugging shell scripts, including application startup. It is also useful for identifying a type of performance issue: a flood of short-lived processes, that end quickly and aren't readily visible in top(1). Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace all new processes calling execve(): # .B execsnoop.bt .SH FIELDS .TP TIME Time of the exec() call, in milliseconds since program start. .TP PID Process ID .TP ARGS Process name and arguments (16 word maximum). .SH OVERHEAD This traces the execve() tracepoint and prints output for each event. As the rate of this is generally expected to be low (< 100/s), the overhead is also expected to be negligible. If you have an application that is spawning a high rate of new processes for a reason (large build process), this could cause a small amount of overhead: test and understand overhead before use. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool provides more fields and options to customize the output. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO opensnoop(8) bpftrace-0.9.4/man/man8/gethostlatency.8000066400000000000000000000033341361633214400200640ustar00rootroot00000000000000.TH gethostlatency 8 "2018-09-08" "USER COMMANDS" .SH NAME gethostlatency.bt \- Show latency for getaddrinfo/gethostbyname[2] calls. Uses bpftrace/eBPF. .SH SYNOPSIS .B gethostlatency.bt .SH DESCRIPTION This traces and prints when getaddrinfo(), gethostbyname(), and gethostbyname2() are called, system wide, and shows the responsible PID and command name, latency of the call (duration) in milliseconds, and the host string. This tool can be useful for identifying DNS latency, by identifying which remote host name lookups were slow, and by how much. This tool currently uses dynamic tracing of user-level functions and registers, and may need modifications to match your software and processor architecture. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bcc. .SH EXAMPLES .TP Trace host lookups (getaddrinfo/gethostbyname[2]) system wide: # .B gethostlatency.bt .SH FIELDS .TP TIME Time of the command (HH:MM:SS). .TP PID Process ID of the client performing the call. .TP COMM Process (command) name of the client performing the call. .TP LATms Latency of the call, in milliseconds. .TP HOST Host name string: the target of the lookup. .SH OVERHEAD The rate of lookups should be relatively low, so the overhead is not expected to be a problem. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool provides command line options. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO tcpdump(8) bpftrace-0.9.4/man/man8/killsnoop.8000066400000000000000000000031321361633214400170350ustar00rootroot00000000000000.TH killsnoop 8 "2018-09-07" "USER COMMANDS" .SH NAME killsnoop.bt \- Trace signals issued by the kill() syscall. Uses bpftrace/eBPF. .SH SYNOPSIS .B killsnoop.bt .SH DESCRIPTION killsnoop traces the kill() syscall, to show signals sent via this method. This may be useful to troubleshoot failing applications, where an unknown mechanism is sending signals. This works by tracing the kill() syscall tracepoints. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace all kill() syscalls: # .B killsnoop.bt .SH FIELDS .TP TIME Time of the kill call. .TP PID Source process ID .TP COMM Source process name .TP SIG Signal number. See signal(7). .TP TPID Target process ID .TP RES Result. 0 == success, a negative value (of the error code) for failure. .SH OVERHEAD This traces the kernel kill function and prints output for each event. As the rate of this is generally expected to be low (< 100/s), the overhead is also expected to be negligible. If you have an application that is calling a very high rate of kill()s for some reason, then test and understand overhead before use. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO opensnoop(8) bpftrace-0.9.4/man/man8/loads.8000066400000000000000000000037341361633214400161350ustar00rootroot00000000000000.TH loads 8 "2018-09-10" "USER COMMANDS" .SH NAME loads.bt \- Prints load averages. Uses bpftrace/eBPF. .SH SYNOPSIS .B loads.bt .SH DESCRIPTION These are the same load averages printed by "uptime", but to three decimal places instead of two (not that it really matters). This is really a demonstration of fetching and processing a kernel structure from bpftrace. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Print system load averages every second: # .B loads.bt .SH FIELDS .TP HH:MM:SS Each output line includes time of printing in "HH:MM:SS" format. .TP load averages: These are exponentially-damped moving sum averages of the system loads. Load is a measurement of demand on system resources, which include CPUs and other resources that are accessed with the kernel in an uninterruptible state (TASK_UNINTERRUPTIBLE), which includes types of disk I/O and lock accesses. Linux load averages originally reflected CPU demand only, as it does in other OSes, but this was changed in Linux 0.99.14. This demand measurement reflects not just the utilized resource, but also the queued demand (a saturation measurement). Finally, the three numbers are called the "one-", "five-", and "fifteen-minute" load averages, however these times are constants used in the exponentially-damping equation, and the load averages reflect load beyond these times. Were you expecting an accurate description of load averages in the man page of a bpftrace tool? .SH OVERHEAD Other than bpftrace startup time, negligible. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. .SH REFERENCE For more on load averages, see: .PP http://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO uptime(1) bpftrace-0.9.4/man/man8/mdflush.8000066400000000000000000000032131361633214400164650ustar00rootroot00000000000000.TH mdflush 8 "2018-09-07" "USER COMMANDS" .SH NAME mdflush.bt \- Trace md flush events. Uses bpftrace/eBPF. .SH SYNOPSIS .B mdflush.bt .SH DESCRIPTION This tool traces flush events by md, the Linux multiple device driver (software RAID). The timestamp and md device for the flush are printed. Knowing when these flushes happen can be useful for correlation with unexplained spikes in disk latency. This works by tracing the kernel md_flush_request() function using kernel dynamic tracing, and will need updating to match any changes to this function. Note that the flushes themselves are likely to originate from higher in the I/O stack, such as from the file systems. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace md flush events: # .B mdflush.bt .SH FIELDS .TP TIME Time of the flush event (HH:MM:SS). .TP PID The process ID that was on-CPU when the event was issued. This may identify the cause of the flush (eg, the "sync" command), but will often identify a kernel worker thread that was managing I/O. .TP COMM The command name for the PID. .TP DEVICE The md device name. .SH OVERHEAD Expected to be negligible. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO biosnoop(8) bpftrace-0.9.4/man/man8/naptime.8000066400000000000000000000027731361633214400164720ustar00rootroot00000000000000.TH naptime 8 "2019-07-05" "USER COMMANDS" .SH NAME naptime.bt \- Trace voluntary sleep calls. Uses bpftrace/eBPF. .SH SYNOPSIS .B naptime.bt .SH DESCRIPTION This tool traces application sleeps, and can be used for debugging high latency that may be caused by deliberate sleeps placed in application routines, especially administration scripts. This tool works by tracing the nanosleep(2) syscall using the syscall tracepoints. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace application sleeps via nanosleep(2): # .B naptime.bt .SH FIELDS .TP TIME A timestamp in HH:MM:SS format. .TP PPID Parent process ID. .TP PCOMM Parent process name. .TP PID The sleeping process ID. .TP COMM The sleeping process name. .TP SECONDS The requested duration of the sleep. .SH OVERHEAD nanosleep(2) calls are expected to be low frequency (<< 100/s), so the overhead of this tool is expected to be negligible. .SH SOURCE This tool originated from the book "BPF Performance Tools", published by Addison Wesley (2019): .IP http://www.brendangregg.com/bpf-performance-tools-book.html .PP See the book for more documentation on this tool. .PP This version is in the bpftrace repository: .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO capable(8) bpftrace-0.9.4/man/man8/oomkill.8000066400000000000000000000034141361633214400164740ustar00rootroot00000000000000.TH oomkill 8 "2018-09-07" "USER COMMANDS" .SH NAME oomkill.bt \- Trace OOM killer. Uses bpftrace/eBPF. .SH SYNOPSIS .B oomkill.bt .SH DESCRIPTION This traces the kernel out-of-memory killer, and prints basic details, including the system load averages at the time of the OOM kill. This can provide more context on the system state at the time: was it getting busier or steady, based on the load averages? This tool may also be useful to customize for investigations; for example, by adding other task_struct details at the time of OOM, or by adding other commands to run at the shell. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace OOM kill events: # .B oomkill.bt .SH FIELDS .TP Triggered by ... The process ID and process name of the task that was running when another task was OOM killed. .TP OOM kill of ... The process ID and name of the target process that was OOM killed. .TP loadavg Contents of /proc/loadavg. The first three numbers are 1, 5, and 15 minute load averages (where the average is an exponentially damped moving sum, and those numbers are constants in the equation); then there is the number of running tasks, a slash, and the total number of tasks; and then the last number is the last PID to be created. .SH OVERHEAD Negligible. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO dmesg(1) bpftrace-0.9.4/man/man8/opensnoop.8000066400000000000000000000031001361633214400170360ustar00rootroot00000000000000.TH opensnoop 8 "2018-09-08" "USER COMMANDS" .SH NAME opensnoop.bt \- Trace open() syscalls. Uses bpftrace/eBPF. .SH SYNOPSIS .B opensnoop.bt .SH DESCRIPTION opensnoop traces the open() syscall, showing which processes are attempting to open which files. This can be useful for determining the location of config and log files, or for troubleshooting applications that are failing, specially on startup. This works by tracing the open() syscall tracepoint. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bcc. .SH EXAMPLES .TP Trace all open() syscalls: # .B opensnoop.bt .SH FIELDS PID Process ID .TP TID Thread ID .TP COMM Process name .TP FD File descriptor (if success), or -1 (if failed) .TP ERR Error number (see the system's errno.h) .TP PATH Open path .SH OVERHEAD This traces the open tracepoint and prints output for each event. As the rate of this is generally expected to be low (< 1000/s), the overhead is also expected to be negligible. If you have an application that is calling a high rate of open()s, then test and understand overhead before use. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO statsnoop(8), execsnoop(8) bpftrace-0.9.4/man/man8/pidpersec.8000066400000000000000000000026551361633214400170120ustar00rootroot00000000000000.TH pidpersec 8 "2018-09-06" "USER COMMANDS" .SH NAME pidpersec.bt \- Count new processes (via fork()). Uses bpftrace/eBPF. .SH SYNOPSIS .B pidpersec.bt .SH DESCRIPTION pidpersec shows how many new processes were created each second. There can be performance issues caused by many short-lived processes, which may not be visible in sampling tools like top(1). pidpersec provides one way to investigate this behavior. This works by tracing the tracepoint:sched:sched_process_fork tracepoint. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Count new processes, printing per-second summaries until Ctrl-C is hit: # .B pidpersec.bt .SH FIELDS .TP 1st Count of processes (after "@") .SH OVERHEAD This traces kernel forks, and maintains an in-kernel count which is read asynchronously from user-space. As the rate of this is generally expected to be low (<< 1000/s), the overhead is also expected to be negligible. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO top(1) bpftrace-0.9.4/man/man8/runqlat.8000066400000000000000000000034231361633214400165140ustar00rootroot00000000000000.TH runqlat 8 "2018-09-17" "USER COMMANDS" .SH NAME runqlat.bt \- CPU scheduler run queue latency as a histogram. Uses bpftrace/eBPF. .SH SYNOPSIS .B runqlat.bt .SH DESCRIPTION This traces time spent waiting in the CPU scheduler for a turn on-CPU. This metric is often called run queue latency, or scheduler latency. This tool shows this latency as a power-of-2 histogram in nanoseconds, allowing multimodal distributions to be studied, as well as latency outliers. This tool uses the sched tracepoints. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace CPU run queue latency system wide, printing a histogram on Ctrl-C: # .B runqlat.bt .SH FIELDS .TP 1st, 2nd This is a range of latency, in microseconds (shown in "[...)" set notation). .TP 3rd A column showing the count of scheduler events in this range. .TP 4th This is an ASCII histogram representing the count column. .SH OVERHEAD This traces scheduler functions, which can become very frequent. While eBPF has very low overhead, and this tool uses in-kernel maps for efficiency, the frequency of scheduler events for some workloads may be high enough that the overhead of this tool becomes significant. Measure in a lab environment to quantify the overhead before use. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO runqlen(8), mpstat(1), pidstat(1), uptime(1) bpftrace-0.9.4/man/man8/runqlen.8000066400000000000000000000030731361633214400165130ustar00rootroot00000000000000.TH runqlen 8 "2018-10-07" "USER COMMANDS" .SH NAME runqlen.bt \- CPU scheduler run queue length as a histogram. Uses bpftrace/eBPF. .SH SYNOPSIS .B runqlen.bt .SH DESCRIPTION This program summarizes scheduler queue length as a histogram, and can also show run queue occupancy. It works by sampling the run queue length on all CPUs at 99 Hertz. This tool can be used to identify imbalances, eg, when processes are bound to CPUs causing queueing, or interrupt mappings causing the same. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace CPU run queue length system wide, printing a histogram on Ctrl-C: # .B runqlen.bt .SH FIELDS .TP 1st, 2nd The run queue length is shown in the first field (after "["). .TP 3rd A column showing the count of samples in for that length. .TP 4th This is an ASCII histogram representing the count column. .SH OVERHEAD This samples scheduler structs at 99 Hertz across all CPUs. Relatively, this is a low rate of events, and the overhead of this tool is expected to be near zero. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO runqlat(8), mpstat(1), pidstat(1), uptime(1) bpftrace-0.9.4/man/man8/setuids.8000066400000000000000000000027741361633214400165160ustar00rootroot00000000000000.TH setuids 8 "2019-07-05" "USER COMMANDS" .SH NAME setuids.bt \- Trace setuid family of syscalls. Uses bpftrace/eBPF. .SH SYNOPSIS .B setuids.bt .SH DESCRIPTION This tool traces privilege escalation via setuid syscalls, and can be used for debugging, whitelist creation, and intrusion detection. It works by tracing the setuid(2), setfsuid(2), and retresuid(2) syscalls using the syscall tracepoints. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace setuid syscalls: # .B setuids.bt .SH FIELDS .TP PID The calling process ID. .TP COMM The calling process (thread) name. .TP UID The UID of the caller. .TP SYSCALL The syscall name. .TP ARGS The arguments to the syscall .TP (RET) The return value for the syscall: 0 == success, other numbers indicate an error code. .SH OVERHEAD setuid calls are expected to be low frequency (<< 100/s), so the overhead of this tool is expected to be negligible. .SH SOURCE This tool originated from the book "BPF Performance Tools", published by Addison Wesley (2019): .IP http://www.brendangregg.com/bpf-performance-tools-book.html .PP See the book for more documentation on this tool. .PP This version is in the bpftrace repository: .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO capable(8) bpftrace-0.9.4/man/man8/statsnoop.8000066400000000000000000000033031361633214400170550ustar00rootroot00000000000000.TH statsnoop 8 "2018-09-08" "USER COMMANDS" .SH NAME statsnoop.bt \- Trace stat() syscalls. Uses bpftrace/eBPF. .SH SYNOPSIS .B statsnoop.bt .SH DESCRIPTION statsnoop traces the stat() syscall, showing which processes are attempting to stat which files. This can be useful for determining the location of config and log files, or for troubleshooting applications that are failing, specially on startup. This traces the tracepoints for statfs(), statx(), newstat(), and newlstat(). These aren't the only the stat syscalls: if you are missing activity, you may need to add more variants. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bcc. .SH EXAMPLES .TP Trace all stat() syscalls: # .B statsnoop.bt .SH FIELDS PID Process ID .TP TID Thread ID .TP COMM Process name .TP FD File descriptor (if success), or -1 (if failed) .TP ERR Error number (see the system's errno.h) .TP PATH Stat path .SH OVERHEAD This traces the stat tracepoints and prints output for each event. As the rate of this is generally expected to be low (< 1000/s), the overhead is also expected to be negligible. If you have an application that is calling a high rate of stat()s, then test and understand overhead before use. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO opensnoop(8), execsnoop(8) bpftrace-0.9.4/man/man8/swapin.8000066400000000000000000000030421361633214400163240ustar00rootroot00000000000000.TH swapin 8 "2019-07-05" "USER COMMANDS" .SH NAME swapin \- Count swapins by process. Uses bpftrace/eBPF. .SH SYNOPSIS .B swapin .SH DESCRIPTION This tool counts swapins by process, to show which process is affected by swapping (if swap devices are in use). This can explain a significant source of application latency, if it has began swapping due to memory pressure on the system. This works by tracing the swap_readpage() kernel function using dynamic instrumentation. This tool may need maintenance to keep working if that function changes in later kernels. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Count swapins by process, showing per-second summaries. # .B swapin.bt .SH FIELDS .TP 1st The process name. .TP 2nd The process ID. .TP 3rd The count of swapins during that interval. .SH OVERHEAD The rate of swapins should be low (bounded by swapin device IOPS), such that the overhead of this tool is expected to be negligible. .SH SOURCE This tool originated from the book "BPF Performance Tools", published by Addison Wesley (2019): .IP http://www.brendangregg.com/bpf-performance-tools-book.html .PP See the book for more documentation on this tool. .PP This version is in the bpftrace repository: .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO swapon(8) bpftrace-0.9.4/man/man8/syncsnoop.8000066400000000000000000000030531361633214400170600ustar00rootroot00000000000000.TH syncsnoop 8 "2018-09-06" "USER COMMANDS" .SH NAME syncsnoop.bt \- Trace the sync() variety of syscalls. Uses bpftrace/eBPF. .SH SYNOPSIS .B syncsnoop.bt .SH DESCRIPTION syncsnoop traces calls to sync() syscalls (sync(), fsync(), msync(), etc), which flushes file system cache and buffers to storage devices. These calls can cause performance perturbations, and it can be useful to know if they are happening, when they happen, and how frequently. This works by tracing the sync() variety of syscalls via tracepoints. This program is also a basic example of eBPF/bcc. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace calls to sync() syscalls: # .B syncsnoop.bt .SH FIELDS .TP TIME A timestamp on the output, in "HH:MM:SS" format. .TP PID The process ID that was on-CPU during the event. .TP COMM The process name that was on-CPU during the event. .TP EVENT The tracepoint name for the sync event. .SH OVERHEAD This traces sync syscalls and prints output for each event. As the rate of this is generally expected to be low (<< 100/s), the overhead is also expected to be negligible. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO iostat(1) bpftrace-0.9.4/man/man8/syscount.8000066400000000000000000000044471361633214400167240ustar00rootroot00000000000000.TH syscount 8 "2018-09-06" "USER COMMANDS" .SH NAME syscount.bt \- Count system calls. Uses bpftrace/eBPF. .SH SYNOPSIS .B syscount.bt .SH DESCRIPTION This counts system calls (syscalls), printing a summary of the top ten syscall IDs, and the top ten process names making syscalls. This can be helpful for characterizing the kernel and resource workload, and finding applications who are using syscalls inefficiently. This works by using the tracepoint:raw_syscalls:sys_enter tracepoint. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Count all VFS calls until Ctrl-C is hit: # .B syscount.bt .SH OUTPUT .TP Top 10 syscalls IDs: This shows the syscall ID number (in @syscall[]) followed by a count for this syscall during tracing. To see the syscall name for that ID, you can use "ausyscall --dump", or the bcc version of this tool that does translations. .TP Top 10 processes: This shows the process name (in @process[]) followed by a count of syscalls during tracing. .SH OVERHEAD For most applications, the overhead should be manageable if they perform 1000's or even 10,000's of syscalls per second. For higher rates, the overhead may become considerable. For example, tracing a microbenchmark loop of 4 million calls to geteuid(), slows it down by 2.4x. However, this represents tracing a workload that has a syscall rate of over 4 million syscalls per second per CPU, which should not be typical (in one large cloud production environment, rates of between 10k and 50k are typical, where the application overhead is expected to be closer to 1%). For comparison, strace(1) in its current ptrace-based implementation (which it has had for decades) runs the same geteuid() workload 102x slower (that's one hundred and two times slower). .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc version provides different command line options, and translates the syscall IDs to their syscall names. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO strace(1) bpftrace-0.9.4/man/man8/tcpaccept.8000066400000000000000000000041421361633214400167730ustar00rootroot00000000000000.TH tcpaccept 8 "2018-10-24" "USER COMMANDS" .SH NAME tcpaccept.bt \- Trace TCP passive connections (accept()). Uses bpftrace/eBPF .SH SYNOPSIS .B tcpaccept.bt .SH DESCRIPTION This tool traces passive TCP connections (eg, via an accept() syscall; connect() are active connections). This can be useful for general troubleshooting to see what new connections the local server is accepting. This uses dynamic tracing of the kernel inet_csk_accept() socket function (from tcp_prot.accept), and will need to be modified to match kernel changes. This tool only traces successful TCP accept()s. Connection attempts to closed ports will not be shown (those can be traced via other functions). Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace all passive TCP connections (accept()s): # .B tcpaccept.bt .TP .SH FIELDS .TP TIME(s) Time of the call, in HH:MM:SS format. .TP PID Process ID .TP COMM Process name .TP RADDR Remote IP address. .TP RPORT Remote port. .TP LADDR Local IP address. .TP LPORT Local port .TP BL Current accept backlog vs maximum backlog .SH OVERHEAD This traces the kernel inet_csk_accept function and prints output for each event. The rate of this depends on your server application. If it is a web or proxy server accepting many tens of thousands of connections per second, then the overhead of this tool may be measurable (although, still a lot better than tracing every packet). If it is less than a thousand a second, then the overhead is expected to be negligible. Test and understand this overhead before use. .SH SOURCE This is from bpftrace .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg, adapted for bpftrace by Dale Hamel .SH SEE ALSO tcpconnect(8), funccount(8), tcpdump(8) bpftrace-0.9.4/man/man8/tcpconnect.8000066400000000000000000000035751361633214400171760ustar00rootroot00000000000000.TH tcpconnect 8 "2018-11-24" "USER COMMANDS" .SH NAME tcpconnect.bt \- Trace TCP active connections (connect()). Uses Linux bpftrace/eBPF .SH SYNOPSIS .B tcpconnect.bt .SH DESCRIPTION This tool traces active TCP connections (eg, via a connect() syscall; accept() are passive connections). This can be useful for general troubleshooting to see what connections are initiated by the local server. All connection attempts are traced, even if they ultimately fail. This works by tracing the kernel tcp_v4_connect() and tcp_v6_connect() functions using dynamic tracing, and will need updating to match any changes to these functions. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace all active TCP connections: # .B tcpconnect.bt .TP .SH FIELDS .TP TIME(s) Time of the call, in HH:MM:SS format. .TP PID Process ID .TP COMM Process name .TP SADDR Source IP address. .TP SPORT Source port. .TP DADDR Destination IP address. .TP DPORT Destination port .SH OVERHEAD This traces the kernel tcp_v[46]_connect functions and prints output for each event. As the rate of this is generally expected to be low (< 1000/s), the overhead is also expected to be negligible. If you have an application that is calling a high rate of connects()s, such as a proxy server, then test and understand this overhead before use. .SH SOURCE This is from bpftrace .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg, adapted for bpftrace by Dale Hamel .SH SEE ALSO tcpaccept(8), funccount(8), tcpdump(8) bpftrace-0.9.4/man/man8/tcpdrop.8000066400000000000000000000040671361633214400165060ustar00rootroot00000000000000.TH tcpdrop 8 "2018-11-24" "USER COMMANDS" .SH NAME tcpdrop.bt \- Trace kernel-based TCP packet drops with details. Uses Linux bpftrace/eBPF .SH SYNOPSIS .B tcpdrop.bt .SH DESCRIPTION This tool traces TCP packets or segments that were dropped by the kernel, and shows details from the IP and TCP headers, the socket state, and the kernel stack trace. This is useful for debugging cases of high kernel drops, which can cause timer-based retransmits and performance issues. This tool works using dynamic tracing of the tcp_drop() kernel function, which requires a recent kernel version. This tool is limited to ipv4, and cannot parse tcpflags as bpftrace currently cannot parse socket buffers in the way that bcc can. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace all tcp drops: # .B tcpdrop.bt .TP .SH FIELDS .TP TIME Time of the call, in HH:MM:SS format. .TP PID Process ID that was on-CPU during the drop. This may be unrelated, as drops can occur on the receive interrupt and be unrelated to the PID that was interrupted. .TP COMM Process name .TP SADDR Source IP address. .TP SPORT Source TCP port. .TP DADDR Destination IP address. .TP DPORT Destionation TCP port. .TP STATE TCP session state ("ESTABLISHED", etc). .SH OVERHEAD This traces the kernel tcp_drop() function, which should be low frequency, and therefore the overhead of this tool should be negligible. As always, test and understand this tools overhead for your types of workloads before production use. .SH SOURCE This is from bpftrace .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg, adapted for bpftrace by Dale Hamel .SH SEE ALSO tcplife(8), tcpaccept(8), tcpconnect(8), tcptop(8) bpftrace-0.9.4/man/man8/tcplife.8000066400000000000000000000043771361633214400164650ustar00rootroot00000000000000.TH tcplife 8 "2019-07-03" "USER COMMANDS" .SH NAME tcplife \- Trace TCP session lifespans with connection details. Uses bpftrace/eBPF. .SH SYNOPSIS .B tcplife .SH DESCRIPTION This tool shows the lifespan of TCP sessions that open and close while tracing, and shows the duration and throughput statistics. For efficiency, this tool only instruments TCP state changes, rather than all packets. This tool works by using the sock:inet_sock_set_state tracepoint, which was added in Linux 4.16. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF, bpftrace, and the sock:inet_sock_set_state tracepoint (Linux 4.16+). .SH EXAMPLES .TP Show TCP sessions with details: # .B tcplife.bt .SH FIELDS .TP PID Process ID .TP COMM Process name .TP LADDR Local IP address. .TP DADDR Remote IP address. .TP LPORT Local port. .TP RPORT Remote port. .TP TX_KB Total transmitted Kbytes. .TP RX_KB Total received Kbytes. .TP MS Lifespan of the session, in milliseconds. .SH OVERHEAD This traces the kernel TCP set state function, which should be called much less often than send/receive tracing, and therefore have lower overhead. The overhead of the tool is relative to the rate of new TCP sessions: if this is high, over 10,000 per second, then there may be noticeable overhead just to print out 10k lines of formatted output per second. You can find out the rate of new TCP sessions using "sar \-n TCP 1", and adding the active/s and passive/s columns. As always, test and understand this tools overhead for your types of workloads before production use. .SH SOURCE This tool originated from BCC: .IP https://github.com/iovisor/bcc .PP The BCC version has many command line options for customizing the output. .PP This bpftrace version originated from the book "BPF Performance Tools", published by Addison Wesley (2019): .IP http://www.brendangregg.com/bpf-performance-tools-book.html .PP See the book for more documentation on this tool. .PP This bpftrace version is in the bpftrace repository: .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO tcptop(8) bpftrace-0.9.4/man/man8/tcpretrans.8000066400000000000000000000037531361633214400172210ustar00rootroot00000000000000.TH tcpretrans 8 "2018-11-24" "USER COMMANDS" .SH NAME tcpretrans.bt \- Trace or count TCP retransmits. Uses Linux bpftrace/eBPF .SH SYNOPSIS .B tcpretrans.bt .SH DESCRIPTION This traces TCP retransmits, showing address, port, and TCP state information, and sometimes the PID (although usually not, since retransmits are usually sent by the kernel on timeouts). To keep overhead very low, only the TCP retransmit functions are traced. This does not trace every packet (like tcpdump(8) or a packet sniffer). Optionally, it can count retransmits over a user signalled interval to spot potentially dropping network paths the flows are traversing. This uses dynamic tracing of the kernel tcp_retransmit_skb() and tcp_send_loss_probe() functions, and will need to be updated to match kernel changes to these functions. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bcc. CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace TCP retransmits: # .B tcpretrans.bt .TP .SH FIELDS .TP TIME Time of the call, in HH:MM:SS format. .TP PID Process ID that was on-CPU. This is less useful than it might sound, as it may usually be 0, for the kernel, for timer-based retransmits. .TP LADDR Local IP address. .TP LPORT Local port. .TP RADDR Remote IP address. .TP RPORT Remote port. .TP STATE TCP session state. .SH OVERHEAD Should be negligible: TCP retransmit events should be low (<1000/s), and the low overhead this tool adds to each event should make the cost negligible. .SH SOURCE This is from bpftrace .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg, adapted for bpftrace by Dale Hamel .SH SEE ALSO tcpconnect(8), tcpaccept(8) bpftrace-0.9.4/man/man8/tcpsynbl.8000066400000000000000000000034721361633214400166700ustar00rootroot00000000000000.TH tcpsynbl 8 "2019-07-03" "USER COMMANDS" .SH NAME tcpsynbl \- Show the TCP SYN backlog as a histogram. Uses bpftrace/eBPF. .SH SYNOPSIS .B tcpsynbl .SH DESCRIPTION This tool shows the TCP SYN backlog size during SYN arrival as a histogram. This lets you see how close your applications are to hitting the backlog limit and dropping SYNs (causing performance issues with SYN retransmits), and is a measure of workload saturation. The histogram shown is measured at the time of SYN received, and a separate histogram is shown for each backlog limit. This works by tracing the tcp_v4_syn_recv_sock() and tcp_v6_syn_recv_sock() kernel functions using dynamic instrumentation. Since these functions may change in future kernels, this tool may need maintenance to keep working. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Show the TCP SYN backlog as a histogram. # .B tcpsynbl.bt .SH FIELDS .TP backlog The backlog size when a SYN was received. .TP count The number of times this backlog size was encountered. .TP distribution An ASCII visualization of the count column. .SH OVERHEAD Inbound SYNs should be relatively low compared to packets and other events, so the overhead of this tool is expected to be negligible. .SH SOURCE This tool originated from the book "BPF Performance Tools", published by Addison Wesley (2019): .IP http://www.brendangregg.com/bpf-performance-tools-book.html .PP See the book for more documentation on this tool. .PP This version is in the bpftrace repository: .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO tcptop(8) bpftrace-0.9.4/man/man8/threadsnoop.8000066400000000000000000000031711361633214400173540ustar00rootroot00000000000000.TH threadsnoop 8 "2019-07-02" "USER COMMANDS" .SH NAME threadsnoop.bt \- Trace thread creation via pthread_create(). Uses bpftrace/eBPF. .SH SYNOPSIS .B threadsnoop.bt .SH DESCRIPTION threadsnoop traces calls to pthread_create(), showing this path of thread creation. This can be used for workload characterization and discovery, and is a companion to execsnoop(8) which traces execve(2). This works by tracing the pthread_create() from libpthread.so.0. The path to this library may need adjusting in the tool source to match your system. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace calls pthread_create(): # .B threadsnoop.bt .SH FIELDS .TP TIME(ms) Elapsed time since the tool began tracing (in milliseconds). .TP PID The process ID. .TP COMM The process (thread) name. .TP FUNC The name of the start routine, if the symbol is available, else a hex address for the start routine address. .SH OVERHEAD Thread creation is expected to be low (<< 1000/s), so the overhead of this tool is expected to be negligible. .SH SOURCE This tool originated from the book "BPF Performance Tools", published by Addison Wesley (2019): .IP http://www.brendangregg.com/bpf-performance-tools-book.html .PP See the book for more documentation on this tool. .PP This version is in the bpftrace repository: .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO execsnoop(8) bpftrace-0.9.4/man/man8/vfscount.8000066400000000000000000000032251361633214400166750ustar00rootroot00000000000000.TH vfscount 8 "2018-09-06" "USER COMMANDS" .SH NAME vfscount.bt \- Count VFS calls ("vfs_*"). Uses bpftrace/eBPF. .SH SYNOPSIS .B vfscount.bt .SH DESCRIPTION This counts VFS calls. This can be useful for general workload characterization of these operations. This works by tracing all kernel functions beginning with "vfs_" using dynamic tracing. This may match more functions than you are interested in measuring: Edit the script to customize which functions to trace. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Count all VFS calls until Ctrl-C is hit: # .B vfscount.bt .SH FIELDS .TP 1st Kernel function name (in @[]) .TP 2nd Number of calls while tracing .SH OVERHEAD This traces kernel vfs functions and maintains in-kernel counts, which are asynchronously copied to user-space. While the rate of VFS operations can be very high (>1M/sec), this is a relatively efficient way to trace these events, and so the overhead is expected to be small for normal workloads. Measure in a test environment, and if overheads are an issue, edit the script to reduce the types of vfs functions traced (currently all beginning with "vfs_"). .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO vfsstat.bt(8) bpftrace-0.9.4/man/man8/vfsstat.8000066400000000000000000000034311361633214400165170ustar00rootroot00000000000000.TH vfsstat 8 "2018-09-06" "USER COMMANDS" .SH NAME vfsstat.bt \- Count key VFS calls. Uses bpftrace/eBPF. .SH SYNOPSIS .B vfsstat.bt .SH DESCRIPTION This traces some common VFS calls and prints per-second summaries. This can be useful for general workload characterization, and looking for patterns in operation usage over time. This works by tracing some kernel vfs functions using dynamic tracing, and will need updating to match any changes to these functions. Edit the script to customize which functions are traced. Also see vfscount, which is more easily customized to trace multiple functions. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Count some VFS calls, printing per-second summaries until Ctrl-C is hit: # .B vfsstat.bt .SH FIELDS .TP HH:MM:SS Each output summary is prefixed by the time of printing in "HH:MM:SS" format. .TP 1st Kernel function name (in @[]) .TP 2nd Number of calls while tracing .SH OVERHEAD This traces various kernel vfs functions and maintains in-kernel counts, which are asynchronously copied to user-space. While the rate of VFS operations can be very high (>1M/sec), this is a relatively efficient way to trace these events, and so the overhead is expected to be small for normal workloads. Measure in a test environment. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO vfscount.bt(8) bpftrace-0.9.4/man/man8/writeback.8000066400000000000000000000033121361633214400167760ustar00rootroot00000000000000.TH writeback 8 "2018-09-14" "USER COMMANDS" .SH NAME writeback.bt \- Trace file system writeback events with details. Uses bpftrace/eBPF. .SH SYNOPSIS .B writeback.bt .SH DESCRIPTION This traces when file system dirtied pages are flushed to disk by kernel writeback, and prints details including when the event occurred, and the duration of the event. This can be useful for correlating these times with other performance problems, and if there is a match, it would be a clue that the problem may be caused by writeback. How quickly the kernel does writeback can be tuned: see the kernel docs, eg, vm.dirty_writeback_centisecs. This uses the tracepoint:writeback:writeback_start and tracepoint:writeback:writeback_written tracepoints. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace all writeback events with timestamps and latency details: # .B writeback.bt .SH FIELDS .TP TIME Time that the writeback event completed, in %H:%M:%S format. .TP DEVICE Device name in major:minor number format. .TP PAGES Pages written during writeback. .TP REASON Reason for the writeback event. This may be "background", "vmscan", "sync", "periodic", etc. .TP ms Duration of the writeback event in milliseconds. .SH OVERHEAD Since writeback events are expected to be infrequent (<10/sec), the overhead of this tool is expected to be negligible (near 0%). .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO biosnoop(8) bpftrace-0.9.4/man/man8/xfsdist.8000066400000000000000000000034301361633214400165100ustar00rootroot00000000000000.TH xfsdist 8 "2018-09-08" "USER COMMANDS" .SH NAME xfsdist.bt \- Summarize XFS operation latency. Uses bpftrace/eBPF. .SH SYNOPSIS .B xfsdist.bt .SH DESCRIPTION This tool summarizes time (latency) spent in common XFS file operations: reads, writes, opens, and syncs, and presents it as a power-of-2 histogram. It uses an in-kernel eBPF map to store the histogram for efficiency. Since this works by tracing the xfs_file_operations interface functions, it will need updating to match any changes to these functions. Since this uses BPF, only the root user can use this tool. .SH REQUIREMENTS CONFIG_BPF and bpftrace. .SH EXAMPLES .TP Trace XFS operation time, and print a summary on Ctrl-C: # .B xfsdist.bt .SH FIELDS .TP 0th The operation name (shown in "@[...]") is printed before each I/O histogram. .TP 1st, 2nd This is a range of latency, in microseconds (shown in "[...)" set notation). .TP 3rd A column showing the count of operations in this range. .TP 4th This is an ASCII histogram representing the count column. .SH OVERHEAD This adds low-overhead instrumentation to these XFS operations, including reads and writes from the file system cache. Such reads and writes can be very frequent (depending on the workload; eg, 1M/sec), at which point the overhead of this tool may become noticeable. Measure and quantify before use. .SH SOURCE This is from bpftrace. .IP https://github.com/iovisor/bpftrace .PP Also look in the bpftrace distribution for a companion _examples.txt file containing example usage, output, and commentary for this tool. This is a bpftrace version of the bcc tool of the same name. The bcc tool may provide more options and customizations. .IP https://github.com/iovisor/bcc .SH OS Linux .SH STABILITY Unstable - in development. .SH AUTHOR Brendan Gregg .SH SEE ALSO biolatency(8) bpftrace-0.9.4/resources/000077500000000000000000000000001361633214400153275ustar00rootroot00000000000000bpftrace-0.9.4/resources/CMakeLists.txt000066400000000000000000000012351361633214400200700ustar00rootroot00000000000000add_library(resources headers.cpp) target_include_directories(resources PUBLIC ../src) function(embed_headers output) file(WRITE ${output} "#include \"headers.h\"\n\nnamespace bpftrace {\n") file(GLOB headers *.h) foreach(header ${headers}) get_filename_component(filename ${header} NAME) string(MAKE_C_IDENTIFIER ${filename} varname) file(READ ${header} contents) file(APPEND ${output} "const char ${varname}[] = R\"CONTENTS(${contents})CONTENTS\";\nconst unsigned ${varname}_len = sizeof(${varname});\n") endforeach() file(APPEND ${output} "} // namespace bpftrace") endfunction() embed_headers(${CMAKE_BINARY_DIR}/resources/headers.cpp) bpftrace-0.9.4/resources/__stddef_max_align_t.h000066400000000000000000000033521361633214400216140ustar00rootroot00000000000000/*===---- __stddef_max_align_t.h - Definition of max_align_t for modules ---=== * * Copyright (c) 2014 Chandler Carruth * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * *===-----------------------------------------------------------------------=== */ #ifndef __CLANG_MAX_ALIGN_T_DEFINED #define __CLANG_MAX_ALIGN_T_DEFINED #if defined(_MSC_VER) typedef double max_align_t; #elif defined(__APPLE__) typedef long double max_align_t; #else // Define 'max_align_t' to match the GCC definition. typedef struct { long long __clang_max_align_nonce1 __attribute__((__aligned__(__alignof__(long long)))); long double __clang_max_align_nonce2 __attribute__((__aligned__(__alignof__(long double)))); } max_align_t; #endif #endif bpftrace-0.9.4/resources/clang_workarounds.h000066400000000000000000000016301361633214400212220ustar00rootroot00000000000000#ifndef __CLANG_WORKAROUNDS_H #define __CLANG_WORKAROUNDS_H // linux/types.h is included by default, which will bring // in asm_volatile_goto definition if permitted based on // compiler setup and kernel configs. // // clang does not support "asm volatile goto" yet. // So redefine asm_volatile_goto to some invalid asm code. // We won't execute this code anyway, so we just need to make sure clang is // able to parse our headers. // // From: https://github.com/iovisor/bcc/pull/2133/files #include #ifdef asm_volatile_goto #undef asm_volatile_goto #define asm_volatile_goto(x...) asm volatile("invalid use of asm_volatile_goto") #endif // In Linux 5.4 asm_inline was introduced, but it's not supported by clang. // Redefine it to just asm to enable successful compilation. // // From: https://github.com/iovisor/bcc/pull/2547 #ifdef asm_inline #undef asm_inline #define asm_inline asm #endif #endif bpftrace-0.9.4/resources/float.h000066400000000000000000000121071361633214400166060ustar00rootroot00000000000000/*===---- float.h - Characteristics of floating point types ----------------=== * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * *===-----------------------------------------------------------------------=== */ #ifndef __FLOAT_H #define __FLOAT_H /* If we're on MinGW, fall back to the system's float.h, which might have * additional definitions provided for Windows. * For more details see http://msdn.microsoft.com/en-us/library/y0ybw9fy.aspx * * Also fall back on Darwin to allow additional definitions and * implementation-defined values. */ #if (defined(__APPLE__) || (defined(__MINGW32__) || defined(_MSC_VER))) && \ __STDC_HOSTED__ && __has_include_next() /* Prior to Apple's 10.7 SDK, float.h SDK header used to apply an extra level * of #include_next to keep Metrowerks compilers happy. Avoid this * extra indirection. */ #ifdef __APPLE__ #define _FLOAT_H_ #endif # include_next /* Undefine anything that we'll be redefining below. */ # undef FLT_EVAL_METHOD # undef FLT_ROUNDS # undef FLT_RADIX # undef FLT_MANT_DIG # undef DBL_MANT_DIG # undef LDBL_MANT_DIG # if __STDC_VERSION__ >= 199901L || !defined(__STRICT_ANSI__) # undef DECIMAL_DIG # endif # undef FLT_DIG # undef DBL_DIG # undef LDBL_DIG # undef FLT_MIN_EXP # undef DBL_MIN_EXP # undef LDBL_MIN_EXP # undef FLT_MIN_10_EXP # undef DBL_MIN_10_EXP # undef LDBL_MIN_10_EXP # undef FLT_MAX_EXP # undef DBL_MAX_EXP # undef LDBL_MAX_EXP # undef FLT_MAX_10_EXP # undef DBL_MAX_10_EXP # undef LDBL_MAX_10_EXP # undef FLT_MAX # undef DBL_MAX # undef LDBL_MAX # undef FLT_EPSILON # undef DBL_EPSILON # undef LDBL_EPSILON # undef FLT_MIN # undef DBL_MIN # undef LDBL_MIN # if __STDC_VERSION__ >= 201112L || !defined(__STRICT_ANSI__) # undef FLT_TRUE_MIN # undef DBL_TRUE_MIN # undef LDBL_TRUE_MIN # undef FLT_DECIMAL_DIG # undef DBL_DECIMAL_DIG # undef LDBL_DECIMAL_DIG # endif #endif /* Characteristics of floating point types, C99 5.2.4.2.2 */ #define FLT_EVAL_METHOD __FLT_EVAL_METHOD__ #define FLT_ROUNDS (__builtin_flt_rounds()) #define FLT_RADIX __FLT_RADIX__ #define FLT_MANT_DIG __FLT_MANT_DIG__ #define DBL_MANT_DIG __DBL_MANT_DIG__ #define LDBL_MANT_DIG __LDBL_MANT_DIG__ #if __STDC_VERSION__ >= 199901L || !defined(__STRICT_ANSI__) # define DECIMAL_DIG __DECIMAL_DIG__ #endif #define FLT_DIG __FLT_DIG__ #define DBL_DIG __DBL_DIG__ #define LDBL_DIG __LDBL_DIG__ #define FLT_MIN_EXP __FLT_MIN_EXP__ #define DBL_MIN_EXP __DBL_MIN_EXP__ #define LDBL_MIN_EXP __LDBL_MIN_EXP__ #define FLT_MIN_10_EXP __FLT_MIN_10_EXP__ #define DBL_MIN_10_EXP __DBL_MIN_10_EXP__ #define LDBL_MIN_10_EXP __LDBL_MIN_10_EXP__ #define FLT_MAX_EXP __FLT_MAX_EXP__ #define DBL_MAX_EXP __DBL_MAX_EXP__ #define LDBL_MAX_EXP __LDBL_MAX_EXP__ #define FLT_MAX_10_EXP __FLT_MAX_10_EXP__ #define DBL_MAX_10_EXP __DBL_MAX_10_EXP__ #define LDBL_MAX_10_EXP __LDBL_MAX_10_EXP__ #define FLT_MAX __FLT_MAX__ #define DBL_MAX __DBL_MAX__ #define LDBL_MAX __LDBL_MAX__ #define FLT_EPSILON __FLT_EPSILON__ #define DBL_EPSILON __DBL_EPSILON__ #define LDBL_EPSILON __LDBL_EPSILON__ #define FLT_MIN __FLT_MIN__ #define DBL_MIN __DBL_MIN__ #define LDBL_MIN __LDBL_MIN__ #if __STDC_VERSION__ >= 201112L || !defined(__STRICT_ANSI__) # define FLT_TRUE_MIN __FLT_DENORM_MIN__ # define DBL_TRUE_MIN __DBL_DENORM_MIN__ # define LDBL_TRUE_MIN __LDBL_DENORM_MIN__ # define FLT_DECIMAL_DIG __FLT_DECIMAL_DIG__ # define DBL_DECIMAL_DIG __DBL_DECIMAL_DIG__ # define LDBL_DECIMAL_DIG __LDBL_DECIMAL_DIG__ #endif #ifdef __STDC_WANT_IEC_60559_TYPES_EXT__ # define FLT16_MANT_DIG __FLT16_MANT_DIG__ # define FLT16_DECIMAL_DIG __FLT16_DECIMAL_DIG__ # define FLT16_DIG __FLT16_DIG__ # define FLT16_MIN_EXP __FLT16_MIN_EXP__ # define FLT16_MIN_10_EXP __FLT16_MIN_10_EXP__ # define FLT16_MAX_EXP __FLT16_MAX_EXP__ # define FLT16_MAX_10_EXP __FLT16_MAX_10_EXP__ # define FLT16_MAX __FLT16_MAX__ # define FLT16_EPSILON __FLT16_EPSILON__ # define FLT16_MIN __FLT16_MIN__ # define FLT16_TRUE_MIN __FLT16_TRUE_MIN__ #endif /* __STDC_WANT_IEC_60559_TYPES_EXT__ */ #endif /* __FLOAT_H */ bpftrace-0.9.4/resources/limits.h000066400000000000000000000072261361633214400170100ustar00rootroot00000000000000/*===---- limits.h - Standard header for integer sizes --------------------===*\ * * Copyright (c) 2009 Chris Lattner * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * \*===----------------------------------------------------------------------===*/ #ifndef __CLANG_LIMITS_H #define __CLANG_LIMITS_H /* The system's limits.h may, in turn, try to #include_next GCC's limits.h. Avert this #include_next madness. */ #if defined __GNUC__ && !defined _GCC_LIMITS_H_ #define _GCC_LIMITS_H_ #endif /* System headers include a number of constants from POSIX in . Include it if we're hosted. */ #if __STDC_HOSTED__ && __has_include_next() #include_next #endif /* Many system headers try to "help us out" by defining these. No really, we know how big each datatype is. */ #undef SCHAR_MIN #undef SCHAR_MAX #undef UCHAR_MAX #undef SHRT_MIN #undef SHRT_MAX #undef USHRT_MAX #undef INT_MIN #undef INT_MAX #undef UINT_MAX #undef LONG_MIN #undef LONG_MAX #undef ULONG_MAX #undef CHAR_BIT #undef CHAR_MIN #undef CHAR_MAX /* C90/99 5.2.4.2.1 */ #define SCHAR_MAX __SCHAR_MAX__ #define SHRT_MAX __SHRT_MAX__ #define INT_MAX __INT_MAX__ #define LONG_MAX __LONG_MAX__ #define SCHAR_MIN (-__SCHAR_MAX__-1) #define SHRT_MIN (-__SHRT_MAX__ -1) #define INT_MIN (-__INT_MAX__ -1) #define LONG_MIN (-__LONG_MAX__ -1L) #define UCHAR_MAX (__SCHAR_MAX__*2 +1) #define USHRT_MAX (__SHRT_MAX__ *2 +1) #define UINT_MAX (__INT_MAX__ *2U +1U) #define ULONG_MAX (__LONG_MAX__ *2UL+1UL) #ifndef MB_LEN_MAX #define MB_LEN_MAX 1 #endif #define CHAR_BIT __CHAR_BIT__ #ifdef __CHAR_UNSIGNED__ /* -funsigned-char */ #define CHAR_MIN 0 #define CHAR_MAX UCHAR_MAX #else #define CHAR_MIN SCHAR_MIN #define CHAR_MAX __SCHAR_MAX__ #endif /* C99 5.2.4.2.1: Added long long. C++11 18.3.3.2: same contents as the Standard C Library header . */ #if __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L #undef LLONG_MIN #undef LLONG_MAX #undef ULLONG_MAX #define LLONG_MAX __LONG_LONG_MAX__ #define LLONG_MIN (-__LONG_LONG_MAX__-1LL) #define ULLONG_MAX (__LONG_LONG_MAX__*2ULL+1ULL) #endif /* LONG_LONG_MIN/LONG_LONG_MAX/ULONG_LONG_MAX are a GNU extension. It's too bad that we don't have something like #pragma poison that could be used to deprecate a macro - the code should just use LLONG_MAX and friends. */ #if defined(__GNU_LIBRARY__) ? defined(__USE_GNU) : !defined(__STRICT_ANSI__) #undef LONG_LONG_MIN #undef LONG_LONG_MAX #undef ULONG_LONG_MAX #define LONG_LONG_MAX __LONG_LONG_MAX__ #define LONG_LONG_MIN (-__LONG_LONG_MAX__-1LL) #define ULONG_LONG_MAX (__LONG_LONG_MAX__*2ULL+1ULL) #endif #endif /* __CLANG_LIMITS_H */ bpftrace-0.9.4/resources/stdarg.h000066400000000000000000000037501361633214400167710ustar00rootroot00000000000000/*===---- stdarg.h - Variable argument handling ----------------------------=== * * Copyright (c) 2008 Eli Friedman * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * *===-----------------------------------------------------------------------=== */ #ifndef __STDARG_H #define __STDARG_H #ifndef _VA_LIST typedef __builtin_va_list va_list; #define _VA_LIST #endif #define va_start(ap, param) __builtin_va_start(ap, param) #define va_end(ap) __builtin_va_end(ap) #define va_arg(ap, type) __builtin_va_arg(ap, type) /* GCC always defines __va_copy, but does not define va_copy unless in c99 mode * or -ansi is not specified, since it was not part of C90. */ #define __va_copy(d,s) __builtin_va_copy(d,s) #if __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L || !defined(__STRICT_ANSI__) #define va_copy(dest, src) __builtin_va_copy(dest, src) #endif #ifndef __GNUC_VA_LIST #define __GNUC_VA_LIST 1 typedef __builtin_va_list __gnuc_va_list; #endif #endif /* __STDARG_H */ bpftrace-0.9.4/resources/stddef.h000066400000000000000000000106221361633214400167520ustar00rootroot00000000000000/*===---- stddef.h - Basic type definitions --------------------------------=== * * Copyright (c) 2008 Eli Friedman * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * *===-----------------------------------------------------------------------=== */ #if !defined(__STDDEF_H) || defined(__need_ptrdiff_t) || \ defined(__need_size_t) || defined(__need_wchar_t) || \ defined(__need_NULL) || defined(__need_wint_t) #if !defined(__need_ptrdiff_t) && !defined(__need_size_t) && \ !defined(__need_wchar_t) && !defined(__need_NULL) && \ !defined(__need_wint_t) /* Always define miscellaneous pieces when modules are available. */ #if !__has_feature(modules) #define __STDDEF_H #endif #define __need_ptrdiff_t #define __need_size_t #define __need_wchar_t #define __need_NULL #define __need_STDDEF_H_misc /* __need_wint_t is intentionally not defined here. */ #endif #if defined(__need_ptrdiff_t) #if !defined(_PTRDIFF_T) || __has_feature(modules) /* Always define ptrdiff_t when modules are available. */ #if !__has_feature(modules) #define _PTRDIFF_T #endif typedef __PTRDIFF_TYPE__ ptrdiff_t; #endif #undef __need_ptrdiff_t #endif /* defined(__need_ptrdiff_t) */ #if defined(__need_size_t) #if !defined(_SIZE_T) || __has_feature(modules) /* Always define size_t when modules are available. */ #if !__has_feature(modules) #define _SIZE_T #endif typedef __SIZE_TYPE__ size_t; #endif #undef __need_size_t #endif /*defined(__need_size_t) */ #if defined(__need_STDDEF_H_misc) /* ISO9899:2011 7.20 (C11 Annex K): Define rsize_t if __STDC_WANT_LIB_EXT1__ is * enabled. */ #if (defined(__STDC_WANT_LIB_EXT1__) && __STDC_WANT_LIB_EXT1__ >= 1 && \ !defined(_RSIZE_T)) || __has_feature(modules) /* Always define rsize_t when modules are available. */ #if !__has_feature(modules) #define _RSIZE_T #endif typedef __SIZE_TYPE__ rsize_t; #endif #endif /* defined(__need_STDDEF_H_misc) */ #if defined(__need_wchar_t) #ifndef __cplusplus /* Always define wchar_t when modules are available. */ #if !defined(_WCHAR_T) || __has_feature(modules) #if !__has_feature(modules) #define _WCHAR_T #if defined(_MSC_EXTENSIONS) #define _WCHAR_T_DEFINED #endif #endif typedef __WCHAR_TYPE__ wchar_t; #endif #endif #undef __need_wchar_t #endif /* defined(__need_wchar_t) */ #if defined(__need_NULL) #undef NULL #ifdef __cplusplus # if !defined(__MINGW32__) && !defined(_MSC_VER) # define NULL __null # else # define NULL 0 # endif #else # define NULL ((void*)0) #endif #ifdef __cplusplus #if defined(_MSC_EXTENSIONS) && defined(_NATIVE_NULLPTR_SUPPORTED) namespace std { typedef decltype(nullptr) nullptr_t; } using ::std::nullptr_t; #endif #endif #undef __need_NULL #endif /* defined(__need_NULL) */ #if defined(__need_STDDEF_H_misc) #if __STDC_VERSION__ >= 201112L || __cplusplus >= 201103L #include "__stddef_max_align_t.h" #endif #define offsetof(t, d) __builtin_offsetof(t, d) #undef __need_STDDEF_H_misc #endif /* defined(__need_STDDEF_H_misc) */ /* Some C libraries expect to see a wint_t here. Others (notably MinGW) will use __WINT_TYPE__ directly; accommodate both by requiring __need_wint_t */ #if defined(__need_wint_t) /* Always define wint_t when modules are available. */ #if !defined(_WINT_T) || __has_feature(modules) #if !__has_feature(modules) #define _WINT_T #endif typedef __WINT_TYPE__ wint_t; #endif #undef __need_wint_t #endif /* __need_wint_t */ #endif bpftrace-0.9.4/resources/stdint.h000066400000000000000000000555351361633214400170220ustar00rootroot00000000000000/*===---- stdint.h - Standard header for sized integer types --------------===*\ * * Copyright (c) 2009 Chris Lattner * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. * \*===----------------------------------------------------------------------===*/ #ifndef __CLANG_STDINT_H #define __CLANG_STDINT_H /* If we're hosted, fall back to the system's stdint.h, which might have * additional definitions. */ #if __STDC_HOSTED__ && __has_include_next() // C99 7.18.3 Limits of other integer types // // Footnote 219, 220: C++ implementations should define these macros only when // __STDC_LIMIT_MACROS is defined before is included. // // Footnote 222: C++ implementations should define these macros only when // __STDC_CONSTANT_MACROS is defined before is included. // // C++11 [cstdint.syn]p2: // // The macros defined by are provided unconditionally. In particular, // the symbols __STDC_LIMIT_MACROS and __STDC_CONSTANT_MACROS (mentioned in // footnotes 219, 220, and 222 in the C standard) play no role in C++. // // C11 removed the problematic footnotes. // // Work around this inconsistency by always defining those macros in C++ mode, // so that a C library implementation which follows the C99 standard can be // used in C++. # ifdef __cplusplus # if !defined(__STDC_LIMIT_MACROS) # define __STDC_LIMIT_MACROS # define __STDC_LIMIT_MACROS_DEFINED_BY_CLANG # endif # if !defined(__STDC_CONSTANT_MACROS) # define __STDC_CONSTANT_MACROS # define __STDC_CONSTANT_MACROS_DEFINED_BY_CLANG # endif # endif # include_next # ifdef __STDC_LIMIT_MACROS_DEFINED_BY_CLANG # undef __STDC_LIMIT_MACROS # undef __STDC_LIMIT_MACROS_DEFINED_BY_CLANG # endif # ifdef __STDC_CONSTANT_MACROS_DEFINED_BY_CLANG # undef __STDC_CONSTANT_MACROS # undef __STDC_CONSTANT_MACROS_DEFINED_BY_CLANG # endif #else /* C99 7.18.1.1 Exact-width integer types. * C99 7.18.1.2 Minimum-width integer types. * C99 7.18.1.3 Fastest minimum-width integer types. * * The standard requires that exact-width type be defined for 8-, 16-, 32-, and * 64-bit types if they are implemented. Other exact width types are optional. * This implementation defines an exact-width types for every integer width * that is represented in the standard integer types. * * The standard also requires minimum-width types be defined for 8-, 16-, 32-, * and 64-bit widths regardless of whether there are corresponding exact-width * types. * * To accommodate targets that are missing types that are exactly 8, 16, 32, or * 64 bits wide, this implementation takes an approach of cascading * redefinitions, redefining __int_leastN_t to successively smaller exact-width * types. It is therefore important that the types are defined in order of * descending widths. * * We currently assume that the minimum-width types and the fastest * minimum-width types are the same. This is allowed by the standard, but is * suboptimal. * * In violation of the standard, some targets do not implement a type that is * wide enough to represent all of the required widths (8-, 16-, 32-, 64-bit). * To accommodate these targets, a required minimum-width type is only * defined if there exists an exact-width type of equal or greater width. */ #ifdef __INT64_TYPE__ # ifndef __int8_t_defined /* glibc sys/types.h also defines int64_t*/ typedef __INT64_TYPE__ int64_t; # endif /* __int8_t_defined */ typedef __UINT64_TYPE__ uint64_t; # define __int_least64_t int64_t # define __uint_least64_t uint64_t # define __int_least32_t int64_t # define __uint_least32_t uint64_t # define __int_least16_t int64_t # define __uint_least16_t uint64_t # define __int_least8_t int64_t # define __uint_least8_t uint64_t #endif /* __INT64_TYPE__ */ #ifdef __int_least64_t typedef __int_least64_t int_least64_t; typedef __uint_least64_t uint_least64_t; typedef __int_least64_t int_fast64_t; typedef __uint_least64_t uint_fast64_t; #endif /* __int_least64_t */ #ifdef __INT56_TYPE__ typedef __INT56_TYPE__ int56_t; typedef __UINT56_TYPE__ uint56_t; typedef int56_t int_least56_t; typedef uint56_t uint_least56_t; typedef int56_t int_fast56_t; typedef uint56_t uint_fast56_t; # define __int_least32_t int56_t # define __uint_least32_t uint56_t # define __int_least16_t int56_t # define __uint_least16_t uint56_t # define __int_least8_t int56_t # define __uint_least8_t uint56_t #endif /* __INT56_TYPE__ */ #ifdef __INT48_TYPE__ typedef __INT48_TYPE__ int48_t; typedef __UINT48_TYPE__ uint48_t; typedef int48_t int_least48_t; typedef uint48_t uint_least48_t; typedef int48_t int_fast48_t; typedef uint48_t uint_fast48_t; # define __int_least32_t int48_t # define __uint_least32_t uint48_t # define __int_least16_t int48_t # define __uint_least16_t uint48_t # define __int_least8_t int48_t # define __uint_least8_t uint48_t #endif /* __INT48_TYPE__ */ #ifdef __INT40_TYPE__ typedef __INT40_TYPE__ int40_t; typedef __UINT40_TYPE__ uint40_t; typedef int40_t int_least40_t; typedef uint40_t uint_least40_t; typedef int40_t int_fast40_t; typedef uint40_t uint_fast40_t; # define __int_least32_t int40_t # define __uint_least32_t uint40_t # define __int_least16_t int40_t # define __uint_least16_t uint40_t # define __int_least8_t int40_t # define __uint_least8_t uint40_t #endif /* __INT40_TYPE__ */ #ifdef __INT32_TYPE__ # ifndef __int8_t_defined /* glibc sys/types.h also defines int32_t*/ typedef __INT32_TYPE__ int32_t; # endif /* __int8_t_defined */ # ifndef __uint32_t_defined /* more glibc compatibility */ # define __uint32_t_defined typedef __UINT32_TYPE__ uint32_t; # endif /* __uint32_t_defined */ # define __int_least32_t int32_t # define __uint_least32_t uint32_t # define __int_least16_t int32_t # define __uint_least16_t uint32_t # define __int_least8_t int32_t # define __uint_least8_t uint32_t #endif /* __INT32_TYPE__ */ #ifdef __int_least32_t typedef __int_least32_t int_least32_t; typedef __uint_least32_t uint_least32_t; typedef __int_least32_t int_fast32_t; typedef __uint_least32_t uint_fast32_t; #endif /* __int_least32_t */ #ifdef __INT24_TYPE__ typedef __INT24_TYPE__ int24_t; typedef __UINT24_TYPE__ uint24_t; typedef int24_t int_least24_t; typedef uint24_t uint_least24_t; typedef int24_t int_fast24_t; typedef uint24_t uint_fast24_t; # define __int_least16_t int24_t # define __uint_least16_t uint24_t # define __int_least8_t int24_t # define __uint_least8_t uint24_t #endif /* __INT24_TYPE__ */ #ifdef __INT16_TYPE__ #ifndef __int8_t_defined /* glibc sys/types.h also defines int16_t*/ typedef __INT16_TYPE__ int16_t; #endif /* __int8_t_defined */ typedef __UINT16_TYPE__ uint16_t; # define __int_least16_t int16_t # define __uint_least16_t uint16_t # define __int_least8_t int16_t # define __uint_least8_t uint16_t #endif /* __INT16_TYPE__ */ #ifdef __int_least16_t typedef __int_least16_t int_least16_t; typedef __uint_least16_t uint_least16_t; typedef __int_least16_t int_fast16_t; typedef __uint_least16_t uint_fast16_t; #endif /* __int_least16_t */ #ifdef __INT8_TYPE__ #ifndef __int8_t_defined /* glibc sys/types.h also defines int8_t*/ typedef __INT8_TYPE__ int8_t; #endif /* __int8_t_defined */ typedef __UINT8_TYPE__ uint8_t; # define __int_least8_t int8_t # define __uint_least8_t uint8_t #endif /* __INT8_TYPE__ */ #ifdef __int_least8_t typedef __int_least8_t int_least8_t; typedef __uint_least8_t uint_least8_t; typedef __int_least8_t int_fast8_t; typedef __uint_least8_t uint_fast8_t; #endif /* __int_least8_t */ /* prevent glibc sys/types.h from defining conflicting types */ #ifndef __int8_t_defined # define __int8_t_defined #endif /* __int8_t_defined */ /* C99 7.18.1.4 Integer types capable of holding object pointers. */ #define __stdint_join3(a,b,c) a ## b ## c #ifndef _INTPTR_T #ifndef __intptr_t_defined typedef __INTPTR_TYPE__ intptr_t; #define __intptr_t_defined #define _INTPTR_T #endif #endif #ifndef _UINTPTR_T typedef __UINTPTR_TYPE__ uintptr_t; #define _UINTPTR_T #endif /* C99 7.18.1.5 Greatest-width integer types. */ typedef __INTMAX_TYPE__ intmax_t; typedef __UINTMAX_TYPE__ uintmax_t; /* C99 7.18.4 Macros for minimum-width integer constants. * * The standard requires that integer constant macros be defined for all the * minimum-width types defined above. As 8-, 16-, 32-, and 64-bit minimum-width * types are required, the corresponding integer constant macros are defined * here. This implementation also defines minimum-width types for every other * integer width that the target implements, so corresponding macros are * defined below, too. * * These macros are defined using the same successive-shrinking approach as * the type definitions above. It is likewise important that macros are defined * in order of decending width. * * Note that C++ should not check __STDC_CONSTANT_MACROS here, contrary to the * claims of the C standard (see C++ 18.3.1p2, [cstdint.syn]). */ #define __int_c_join(a, b) a ## b #define __int_c(v, suffix) __int_c_join(v, suffix) #define __uint_c(v, suffix) __int_c_join(v##U, suffix) #ifdef __INT64_TYPE__ # ifdef __INT64_C_SUFFIX__ # define __int64_c_suffix __INT64_C_SUFFIX__ # define __int32_c_suffix __INT64_C_SUFFIX__ # define __int16_c_suffix __INT64_C_SUFFIX__ # define __int8_c_suffix __INT64_C_SUFFIX__ # else # undef __int64_c_suffix # undef __int32_c_suffix # undef __int16_c_suffix # undef __int8_c_suffix # endif /* __INT64_C_SUFFIX__ */ #endif /* __INT64_TYPE__ */ #ifdef __int_least64_t # ifdef __int64_c_suffix # define INT64_C(v) __int_c(v, __int64_c_suffix) # define UINT64_C(v) __uint_c(v, __int64_c_suffix) # else # define INT64_C(v) v # define UINT64_C(v) v ## U # endif /* __int64_c_suffix */ #endif /* __int_least64_t */ #ifdef __INT56_TYPE__ # ifdef __INT56_C_SUFFIX__ # define INT56_C(v) __int_c(v, __INT56_C_SUFFIX__) # define UINT56_C(v) __uint_c(v, __INT56_C_SUFFIX__) # define __int32_c_suffix __INT56_C_SUFFIX__ # define __int16_c_suffix __INT56_C_SUFFIX__ # define __int8_c_suffix __INT56_C_SUFFIX__ # else # define INT56_C(v) v # define UINT56_C(v) v ## U # undef __int32_c_suffix # undef __int16_c_suffix # undef __int8_c_suffix # endif /* __INT56_C_SUFFIX__ */ #endif /* __INT56_TYPE__ */ #ifdef __INT48_TYPE__ # ifdef __INT48_C_SUFFIX__ # define INT48_C(v) __int_c(v, __INT48_C_SUFFIX__) # define UINT48_C(v) __uint_c(v, __INT48_C_SUFFIX__) # define __int32_c_suffix __INT48_C_SUFFIX__ # define __int16_c_suffix __INT48_C_SUFFIX__ # define __int8_c_suffix __INT48_C_SUFFIX__ # else # define INT48_C(v) v # define UINT48_C(v) v ## U # undef __int32_c_suffix # undef __int16_c_suffix # undef __int8_c_suffix # endif /* __INT48_C_SUFFIX__ */ #endif /* __INT48_TYPE__ */ #ifdef __INT40_TYPE__ # ifdef __INT40_C_SUFFIX__ # define INT40_C(v) __int_c(v, __INT40_C_SUFFIX__) # define UINT40_C(v) __uint_c(v, __INT40_C_SUFFIX__) # define __int32_c_suffix __INT40_C_SUFFIX__ # define __int16_c_suffix __INT40_C_SUFFIX__ # define __int8_c_suffix __INT40_C_SUFFIX__ # else # define INT40_C(v) v # define UINT40_C(v) v ## U # undef __int32_c_suffix # undef __int16_c_suffix # undef __int8_c_suffix # endif /* __INT40_C_SUFFIX__ */ #endif /* __INT40_TYPE__ */ #ifdef __INT32_TYPE__ # ifdef __INT32_C_SUFFIX__ # define __int32_c_suffix __INT32_C_SUFFIX__ # define __int16_c_suffix __INT32_C_SUFFIX__ # define __int8_c_suffix __INT32_C_SUFFIX__ #else # undef __int32_c_suffix # undef __int16_c_suffix # undef __int8_c_suffix # endif /* __INT32_C_SUFFIX__ */ #endif /* __INT32_TYPE__ */ #ifdef __int_least32_t # ifdef __int32_c_suffix # define INT32_C(v) __int_c(v, __int32_c_suffix) # define UINT32_C(v) __uint_c(v, __int32_c_suffix) # else # define INT32_C(v) v # define UINT32_C(v) v ## U # endif /* __int32_c_suffix */ #endif /* __int_least32_t */ #ifdef __INT24_TYPE__ # ifdef __INT24_C_SUFFIX__ # define INT24_C(v) __int_c(v, __INT24_C_SUFFIX__) # define UINT24_C(v) __uint_c(v, __INT24_C_SUFFIX__) # define __int16_c_suffix __INT24_C_SUFFIX__ # define __int8_c_suffix __INT24_C_SUFFIX__ # else # define INT24_C(v) v # define UINT24_C(v) v ## U # undef __int16_c_suffix # undef __int8_c_suffix # endif /* __INT24_C_SUFFIX__ */ #endif /* __INT24_TYPE__ */ #ifdef __INT16_TYPE__ # ifdef __INT16_C_SUFFIX__ # define __int16_c_suffix __INT16_C_SUFFIX__ # define __int8_c_suffix __INT16_C_SUFFIX__ #else # undef __int16_c_suffix # undef __int8_c_suffix # endif /* __INT16_C_SUFFIX__ */ #endif /* __INT16_TYPE__ */ #ifdef __int_least16_t # ifdef __int16_c_suffix # define INT16_C(v) __int_c(v, __int16_c_suffix) # define UINT16_C(v) __uint_c(v, __int16_c_suffix) # else # define INT16_C(v) v # define UINT16_C(v) v ## U # endif /* __int16_c_suffix */ #endif /* __int_least16_t */ #ifdef __INT8_TYPE__ # ifdef __INT8_C_SUFFIX__ # define __int8_c_suffix __INT8_C_SUFFIX__ #else # undef __int8_c_suffix # endif /* __INT8_C_SUFFIX__ */ #endif /* __INT8_TYPE__ */ #ifdef __int_least8_t # ifdef __int8_c_suffix # define INT8_C(v) __int_c(v, __int8_c_suffix) # define UINT8_C(v) __uint_c(v, __int8_c_suffix) # else # define INT8_C(v) v # define UINT8_C(v) v ## U # endif /* __int8_c_suffix */ #endif /* __int_least8_t */ /* C99 7.18.2.1 Limits of exact-width integer types. * C99 7.18.2.2 Limits of minimum-width integer types. * C99 7.18.2.3 Limits of fastest minimum-width integer types. * * The presence of limit macros are completely optional in C99. This * implementation defines limits for all of the types (exact- and * minimum-width) that it defines above, using the limits of the minimum-width * type for any types that do not have exact-width representations. * * As in the type definitions, this section takes an approach of * successive-shrinking to determine which limits to use for the standard (8, * 16, 32, 64) bit widths when they don't have exact representations. It is * therefore important that the definitions be kept in order of decending * widths. * * Note that C++ should not check __STDC_LIMIT_MACROS here, contrary to the * claims of the C standard (see C++ 18.3.1p2, [cstdint.syn]). */ #ifdef __INT64_TYPE__ # define INT64_MAX INT64_C( 9223372036854775807) # define INT64_MIN (-INT64_C( 9223372036854775807)-1) # define UINT64_MAX UINT64_C(18446744073709551615) # define __INT_LEAST64_MIN INT64_MIN # define __INT_LEAST64_MAX INT64_MAX # define __UINT_LEAST64_MAX UINT64_MAX # define __INT_LEAST32_MIN INT64_MIN # define __INT_LEAST32_MAX INT64_MAX # define __UINT_LEAST32_MAX UINT64_MAX # define __INT_LEAST16_MIN INT64_MIN # define __INT_LEAST16_MAX INT64_MAX # define __UINT_LEAST16_MAX UINT64_MAX # define __INT_LEAST8_MIN INT64_MIN # define __INT_LEAST8_MAX INT64_MAX # define __UINT_LEAST8_MAX UINT64_MAX #endif /* __INT64_TYPE__ */ #ifdef __INT_LEAST64_MIN # define INT_LEAST64_MIN __INT_LEAST64_MIN # define INT_LEAST64_MAX __INT_LEAST64_MAX # define UINT_LEAST64_MAX __UINT_LEAST64_MAX # define INT_FAST64_MIN __INT_LEAST64_MIN # define INT_FAST64_MAX __INT_LEAST64_MAX # define UINT_FAST64_MAX __UINT_LEAST64_MAX #endif /* __INT_LEAST64_MIN */ #ifdef __INT56_TYPE__ # define INT56_MAX INT56_C(36028797018963967) # define INT56_MIN (-INT56_C(36028797018963967)-1) # define UINT56_MAX UINT56_C(72057594037927935) # define INT_LEAST56_MIN INT56_MIN # define INT_LEAST56_MAX INT56_MAX # define UINT_LEAST56_MAX UINT56_MAX # define INT_FAST56_MIN INT56_MIN # define INT_FAST56_MAX INT56_MAX # define UINT_FAST56_MAX UINT56_MAX # define __INT_LEAST32_MIN INT56_MIN # define __INT_LEAST32_MAX INT56_MAX # define __UINT_LEAST32_MAX UINT56_MAX # define __INT_LEAST16_MIN INT56_MIN # define __INT_LEAST16_MAX INT56_MAX # define __UINT_LEAST16_MAX UINT56_MAX # define __INT_LEAST8_MIN INT56_MIN # define __INT_LEAST8_MAX INT56_MAX # define __UINT_LEAST8_MAX UINT56_MAX #endif /* __INT56_TYPE__ */ #ifdef __INT48_TYPE__ # define INT48_MAX INT48_C(140737488355327) # define INT48_MIN (-INT48_C(140737488355327)-1) # define UINT48_MAX UINT48_C(281474976710655) # define INT_LEAST48_MIN INT48_MIN # define INT_LEAST48_MAX INT48_MAX # define UINT_LEAST48_MAX UINT48_MAX # define INT_FAST48_MIN INT48_MIN # define INT_FAST48_MAX INT48_MAX # define UINT_FAST48_MAX UINT48_MAX # define __INT_LEAST32_MIN INT48_MIN # define __INT_LEAST32_MAX INT48_MAX # define __UINT_LEAST32_MAX UINT48_MAX # define __INT_LEAST16_MIN INT48_MIN # define __INT_LEAST16_MAX INT48_MAX # define __UINT_LEAST16_MAX UINT48_MAX # define __INT_LEAST8_MIN INT48_MIN # define __INT_LEAST8_MAX INT48_MAX # define __UINT_LEAST8_MAX UINT48_MAX #endif /* __INT48_TYPE__ */ #ifdef __INT40_TYPE__ # define INT40_MAX INT40_C(549755813887) # define INT40_MIN (-INT40_C(549755813887)-1) # define UINT40_MAX UINT40_C(1099511627775) # define INT_LEAST40_MIN INT40_MIN # define INT_LEAST40_MAX INT40_MAX # define UINT_LEAST40_MAX UINT40_MAX # define INT_FAST40_MIN INT40_MIN # define INT_FAST40_MAX INT40_MAX # define UINT_FAST40_MAX UINT40_MAX # define __INT_LEAST32_MIN INT40_MIN # define __INT_LEAST32_MAX INT40_MAX # define __UINT_LEAST32_MAX UINT40_MAX # define __INT_LEAST16_MIN INT40_MIN # define __INT_LEAST16_MAX INT40_MAX # define __UINT_LEAST16_MAX UINT40_MAX # define __INT_LEAST8_MIN INT40_MIN # define __INT_LEAST8_MAX INT40_MAX # define __UINT_LEAST8_MAX UINT40_MAX #endif /* __INT40_TYPE__ */ #ifdef __INT32_TYPE__ # define INT32_MAX INT32_C(2147483647) # define INT32_MIN (-INT32_C(2147483647)-1) # define UINT32_MAX UINT32_C(4294967295) # define __INT_LEAST32_MIN INT32_MIN # define __INT_LEAST32_MAX INT32_MAX # define __UINT_LEAST32_MAX UINT32_MAX # define __INT_LEAST16_MIN INT32_MIN # define __INT_LEAST16_MAX INT32_MAX # define __UINT_LEAST16_MAX UINT32_MAX # define __INT_LEAST8_MIN INT32_MIN # define __INT_LEAST8_MAX INT32_MAX # define __UINT_LEAST8_MAX UINT32_MAX #endif /* __INT32_TYPE__ */ #ifdef __INT_LEAST32_MIN # define INT_LEAST32_MIN __INT_LEAST32_MIN # define INT_LEAST32_MAX __INT_LEAST32_MAX # define UINT_LEAST32_MAX __UINT_LEAST32_MAX # define INT_FAST32_MIN __INT_LEAST32_MIN # define INT_FAST32_MAX __INT_LEAST32_MAX # define UINT_FAST32_MAX __UINT_LEAST32_MAX #endif /* __INT_LEAST32_MIN */ #ifdef __INT24_TYPE__ # define INT24_MAX INT24_C(8388607) # define INT24_MIN (-INT24_C(8388607)-1) # define UINT24_MAX UINT24_C(16777215) # define INT_LEAST24_MIN INT24_MIN # define INT_LEAST24_MAX INT24_MAX # define UINT_LEAST24_MAX UINT24_MAX # define INT_FAST24_MIN INT24_MIN # define INT_FAST24_MAX INT24_MAX # define UINT_FAST24_MAX UINT24_MAX # define __INT_LEAST16_MIN INT24_MIN # define __INT_LEAST16_MAX INT24_MAX # define __UINT_LEAST16_MAX UINT24_MAX # define __INT_LEAST8_MIN INT24_MIN # define __INT_LEAST8_MAX INT24_MAX # define __UINT_LEAST8_MAX UINT24_MAX #endif /* __INT24_TYPE__ */ #ifdef __INT16_TYPE__ #define INT16_MAX INT16_C(32767) #define INT16_MIN (-INT16_C(32767)-1) #define UINT16_MAX UINT16_C(65535) # define __INT_LEAST16_MIN INT16_MIN # define __INT_LEAST16_MAX INT16_MAX # define __UINT_LEAST16_MAX UINT16_MAX # define __INT_LEAST8_MIN INT16_MIN # define __INT_LEAST8_MAX INT16_MAX # define __UINT_LEAST8_MAX UINT16_MAX #endif /* __INT16_TYPE__ */ #ifdef __INT_LEAST16_MIN # define INT_LEAST16_MIN __INT_LEAST16_MIN # define INT_LEAST16_MAX __INT_LEAST16_MAX # define UINT_LEAST16_MAX __UINT_LEAST16_MAX # define INT_FAST16_MIN __INT_LEAST16_MIN # define INT_FAST16_MAX __INT_LEAST16_MAX # define UINT_FAST16_MAX __UINT_LEAST16_MAX #endif /* __INT_LEAST16_MIN */ #ifdef __INT8_TYPE__ # define INT8_MAX INT8_C(127) # define INT8_MIN (-INT8_C(127)-1) # define UINT8_MAX UINT8_C(255) # define __INT_LEAST8_MIN INT8_MIN # define __INT_LEAST8_MAX INT8_MAX # define __UINT_LEAST8_MAX UINT8_MAX #endif /* __INT8_TYPE__ */ #ifdef __INT_LEAST8_MIN # define INT_LEAST8_MIN __INT_LEAST8_MIN # define INT_LEAST8_MAX __INT_LEAST8_MAX # define UINT_LEAST8_MAX __UINT_LEAST8_MAX # define INT_FAST8_MIN __INT_LEAST8_MIN # define INT_FAST8_MAX __INT_LEAST8_MAX # define UINT_FAST8_MAX __UINT_LEAST8_MAX #endif /* __INT_LEAST8_MIN */ /* Some utility macros */ #define __INTN_MIN(n) __stdint_join3( INT, n, _MIN) #define __INTN_MAX(n) __stdint_join3( INT, n, _MAX) #define __UINTN_MAX(n) __stdint_join3(UINT, n, _MAX) #define __INTN_C(n, v) __stdint_join3( INT, n, _C(v)) #define __UINTN_C(n, v) __stdint_join3(UINT, n, _C(v)) /* C99 7.18.2.4 Limits of integer types capable of holding object pointers. */ /* C99 7.18.3 Limits of other integer types. */ #define INTPTR_MIN (-__INTPTR_MAX__-1) #define INTPTR_MAX __INTPTR_MAX__ #define UINTPTR_MAX __UINTPTR_MAX__ #define PTRDIFF_MIN (-__PTRDIFF_MAX__-1) #define PTRDIFF_MAX __PTRDIFF_MAX__ #define SIZE_MAX __SIZE_MAX__ /* ISO9899:2011 7.20 (C11 Annex K): Define RSIZE_MAX if __STDC_WANT_LIB_EXT1__ * is enabled. */ #if defined(__STDC_WANT_LIB_EXT1__) && __STDC_WANT_LIB_EXT1__ >= 1 #define RSIZE_MAX (SIZE_MAX >> 1) #endif /* C99 7.18.2.5 Limits of greatest-width integer types. */ #define INTMAX_MIN (-__INTMAX_MAX__-1) #define INTMAX_MAX __INTMAX_MAX__ #define UINTMAX_MAX __UINTMAX_MAX__ /* C99 7.18.3 Limits of other integer types. */ #define SIG_ATOMIC_MIN __INTN_MIN(__SIG_ATOMIC_WIDTH__) #define SIG_ATOMIC_MAX __INTN_MAX(__SIG_ATOMIC_WIDTH__) #ifdef __WINT_UNSIGNED__ # define WINT_MIN __UINTN_C(__WINT_WIDTH__, 0) # define WINT_MAX __UINTN_MAX(__WINT_WIDTH__) #else # define WINT_MIN __INTN_MIN(__WINT_WIDTH__) # define WINT_MAX __INTN_MAX(__WINT_WIDTH__) #endif #ifndef WCHAR_MAX # define WCHAR_MAX __WCHAR_MAX__ #endif #ifndef WCHAR_MIN # if __WCHAR_MAX__ == __INTN_MAX(__WCHAR_WIDTH__) # define WCHAR_MIN __INTN_MIN(__WCHAR_WIDTH__) # else # define WCHAR_MIN __UINTN_C(__WCHAR_WIDTH__, 0) # endif #endif /* 7.18.4.2 Macros for greatest-width integer constants. */ #define INTMAX_C(v) __int_c(v, __INTMAX_C_SUFFIX__) #define UINTMAX_C(v) __int_c(v, __UINTMAX_C_SUFFIX__) #endif /* __STDC_HOSTED__ */ #endif /* __CLANG_STDINT_H */ bpftrace-0.9.4/scripts/000077500000000000000000000000001361633214400150045ustar00rootroot00000000000000bpftrace-0.9.4/scripts/check_kernel_features.sh000077500000000000000000000015331361633214400216600ustar00rootroot00000000000000#!/bin/sh # Report missing kernel features # # Usage: ./check_kernel_features.sh set -e set -u err=0 config='' # Find kernel config for c in "/boot/config-$(uname -r)" "/boot/config" "/proc/config.gz"; do if [ -f "$c" ]; then config="$c" break fi done if [ -z "$config" ]; then echo "Could not find kernel config" >&2 exit 1 fi # Check feature check_opt() { if ! zgrep -qE "^${1}[[:space:]]*=[[:space:]]*[y|Y]" "$config"; then err=1 echo "Required option ${1} not set" >&2 fi } check_opt 'CONFIG_BPF' check_opt 'CONFIG_BPF_EVENTS' check_opt 'CONFIG_BPF_JIT' check_opt 'CONFIG_BPF_SYSCALL' check_opt 'CONFIG_FTRACE_SYSCALLS' check_opt 'CONFIG_HAVE_EBPF_JIT' # Status report if [ $err -eq 0 ]; then echo "All required features present!" else echo "Missing required features" fi exit $err bpftrace-0.9.4/scripts/compare_tool_codegen.sh000077500000000000000000000031721361633214400215150ustar00rootroot00000000000000#!/bin/bash # Compare the IR generated for the shipped # tools between two bpftrace builds # set -o pipefail set -e set -u if [[ "$#" -ne 3 ]]; then echo "Compare IR generated between two bpftrace builds" echo "" echo "USAGE:" echo "$(basename $0) " echo "" echo "EXAMPLE:" echo "$(basename $0) bpftrace bpftrace_master /vagrant/tools" echo "" exit 1 fi TOOLDIR=$3 BPF_A=$(command -v "$1") || ( echo "ERROR: $1 not found"; exit 1 ) BPF_B=$(command -v "$2") || ( echo "ERROR: $2 not found"; exit 1 ) [[ -d "$TOOLDIR" ]] || (echo "tooldir does not appear to be a directory: ${TOOLDIR}"; exit 1) # Set to 1 to only compare result after opt AFTER_OPT=0 if [ $AFTER_OPT -eq 1 ]; then FLAGS="-d" else FLAGS="-dd" fi TMPDIR=$(mktemp -d) [[ $? -ne 0 || -z $TMPDIR ]] && (echo "Failed to create tmp dir"; exit 10) cd $TMPDIR set +e function hash() { file="${1}" sha1sum "${1}" | awk '{print $1}' } function fix_timestamp() { cat $@ | awk '/(add|sub) i64 %get_ns/ { $NF = ""} {print}' } for script in ${TOOLDIR}/*.bt; do s=$(basename ${script/.bt/}) echo "Checking $s" 2>&1 $BPF_A "$FLAGS" "$script" | fix_timestamp > "a_${s}" 2>&1 $BPF_B "$FLAGS" "$script" | fix_timestamp > "b_${s}" if [ $? -ne 0 ]; then echo "###############################" echo "bpftrace failed on script: ${s}" echo "###############################" continue fi if [[ $(hash "a_${s}") != $(hash "b_${s}") ]]; then echo "Change detected for script: ${s}" diff -b -u "a_${s}" "b_${s}" fi done [[ -n ${TMPDIR} ]] && rm -rf "${TMPDIR}" bpftrace-0.9.4/scripts/seccomp.c000066400000000000000000000125011361633214400166000ustar00rootroot00000000000000// Compile with gcc seccomp.c -lseccomp -ggdb -o seccomp #include #include #include #include #include #include #include #include #include void help() { printf("Simulate bpf(2) syscall failures based on the bpf(2) command "); printf("executed\n"); printf("\n"); printf("USAGE:\n"); printf("./seccomp [OPTIONS] -- command [args]"); printf("./seccomp -e map_create:11 -- bpftrace -e ...\n"); printf("./seccomp -e map_create:100 -k prog_load -- ./src/bpftrace -e "); printf("'i:s:1 { @=5 }'\n"); printf("\n"); printf("OPTIONS:\n"); printf("\t-k [NAME] Kill program when bpf is called with this command\n"); printf("\t-e [NAME]:[ERRNO] Set errno to ERRNO program when bpf is called "); printf("with this command\n"); printf("\t-l List known bpf commands\n"); printf("\n"); exit(0); } struct entry { const int value; const char* symbol; const char* name; }; #define ENTRY(symbol, name) \ { symbol, #symbol, name } static const struct entry bpf_commands[] = { ENTRY(BPF_MAP_CREATE, "map_create"), ENTRY(BPF_MAP_LOOKUP_ELEM, "map_lookup_elem"), ENTRY(BPF_MAP_UPDATE_ELEM, "map_update_elem"), ENTRY(BPF_MAP_DELETE_ELEM, "map_delete_elem"), ENTRY(BPF_MAP_GET_NEXT_KEY, "map_get_next_key"), ENTRY(BPF_PROG_LOAD, "prog_load"), ENTRY(BPF_OBJ_PIN, "obj_pin"), ENTRY(BPF_OBJ_GET, "obj_get"), ENTRY(BPF_PROG_ATTACH, "prog_attach"), ENTRY(BPF_PROG_DETACH, "prog_detach"), ENTRY(BPF_PROG_TEST_RUN, "prog_test_run"), ENTRY(BPF_PROG_GET_NEXT_ID, "prog_get_next_id"), ENTRY(BPF_MAP_GET_NEXT_ID, "map_get_next_id"), ENTRY(BPF_PROG_GET_FD_BY_ID, "prog_get_fd_by_id"), ENTRY(BPF_MAP_GET_FD_BY_ID, "map_get_fd_by_id"), ENTRY( BPF_OBJ_GET_INFO_BY_FD, "obj_get_info_by_fd"), ENTRY(BPF_PROG_QUERY, "prog_query"), ENTRY(BPF_RAW_TRACEPOINT_OPEN, "raw_tracepoint_open"), ENTRY(BPF_BTF_LOAD, "btf_load"), ENTRY(BPF_BTF_GET_FD_BY_ID, "btf_get_fd_by_id"), ENTRY(BPF_TASK_FD_QUERY, "task_fd_query"), }; void list(void) { for (int x = 0; x < sizeof(bpf_commands) / sizeof(struct entry); x++) { printf( "name: %s\tflag: %s\n", bpf_commands[x].name, bpf_commands[x].symbol); } exit(0); } // Search the bpf_commands table for an entry for with symbol or name matches // the argument name // // Return the bpf command value or -1 if the lookup failed int lookup_cmd(char* name) { for (int x = 0; x < sizeof(bpf_commands) / sizeof(struct entry); x++) { if (strcmp(name, bpf_commands[x].name) == 0 || strcmp(name, bpf_commands[x].symbol) == 0) { return bpf_commands[x].value; } } return -1; } // Parse the errno string and add the required filter to seccomp int add_errno(scmp_filter_ctx* ctx, char* str) { assert(ctx != NULL); assert(str != NULL); char* substr = strchr(str, ':'); if (substr == NULL) { printf("Expected ERRNO format \"COMMAND:ERRNO\", got: %s\n", str); return -1; } // Copy command to a new string int keysize = substr - str; char* buf = malloc((keysize + 1) * sizeof(char)); assert(buf != NULL); strncpy(buf, str, keysize); // convert ERRNO to positive int int err = atoi(substr + 1); if (err < 0) { err *= -1; } // Find the command ID int command = lookup_cmd(buf); if (command < 0) { printf("Unknown bpf command: %s\n", buf); goto exit; } int rc = seccomp_rule_add(*ctx, SCMP_ACT_ERRNO(err), SCMP_SYS(bpf), 1, SCMP_A0(SCMP_CMP_EQ, command)); if (rc < 0) { printf("Failed to add ERRNO(%d) filter for command: %s(%d): %s\n", err, buf, command, strerror(rc * -1)); } else { printf("Added ERRNO(%d) for command: %s(%d)\n", err, buf, command); } exit: free(buf); } // Parse CLI args, setup seccomp and execve into the command int main(int argc, char** argv) { int index; int c; opterr = 0; scmp_filter_ctx ctx; ctx = seccomp_init(SCMP_ACT_ALLOW); if (ctx == NULL) { printf("Failed to init seccomp\n"); } while ((c = getopt(argc, argv, "k:e:hl")) != -1) { switch (c) { case 'h': help(); break; case 'l': list(); break; case 'k': { int v = lookup_cmd(optarg); if (v >= 0) { int rc = seccomp_rule_add( ctx, SCMP_ACT_KILL, SCMP_SYS(bpf), 1, SCMP_A0(SCMP_CMP_EQ, v)); if (rc < 0) { printf("Failed to add KILL filter for command: %s: %s\n", optarg, strerror(-1 * rc)); } else { printf("Added KILL for command: %s\n", optarg); } } else { printf("Unknown bpf command: %s\n", optarg); } break; } case 'e': add_errno(&ctx, optarg); break; default: printf("Unknown option: %s\n", optarg); break; } } if (argc - optind < 2) { printf("expected command with arguments\n"); goto abort; } seccomp_load(ctx); printf("Executing: "); for (int i = optind; i < argc; i++) { printf("%s ", argv[i]); } printf("\n------------\n\n"); int rc = execve(argv[optind], argv + optind, NULL); printf("Execve failed: %d, %s\n", rc, strerror(errno)); abort: seccomp_release(ctx); } bpftrace-0.9.4/scripts/tracepoint_variable_sized_types.py000066400000000000000000000021771361633214400240240ustar00rootroot00000000000000# This script lists all the types in the kernel's tracepoint format files # which appear with more than one size. This script's output should be # compared to the code in TracepointFormatParser::adjust_integer_types() import glob field_types = {} for format_file in glob.iglob("/sys/kernel/debug/tracing/events/*/*/format"): for line in open(format_file): if not line.startswith("\tfield:"): continue size_section = line.split(";")[2].split(":") if size_section[0] != "\tsize": continue size_val = size_section[1] field_section = line.split(";")[0].split(":") if field_section[0] != "\tfield": continue field_val = field_section[1] if "[" in field_val or "*" in field_val: continue field_type = " ".join(field_val.split()[:-1]) if field_type not in field_types: field_types[field_type] = set() field_types[field_type].add(size_val) for t in sorted(field_types): sizes = field_types[t] if len(sizes) > 1: sizes_str = ",".join(sorted(sizes)) print(f"{t}: {sizes_str}") bpftrace-0.9.4/src/000077500000000000000000000000001361633214400141045ustar00rootroot00000000000000bpftrace-0.9.4/src/CMakeLists.txt000066400000000000000000000101011361633214400166350ustar00rootroot00000000000000if(HAVE_BFD_DISASM) set(BFD_DISASM_SRC bfd-disasm.cpp) endif() add_executable(bpftrace attached_probe.cpp bpffeature.cpp bpftrace.cpp btf.cpp clang_parser.cpp disasm.cpp driver.cpp fake_map.cpp list.cpp main.cpp map.cpp mapkey.cpp output.cpp printf.cpp resolve_cgroupid.cpp signal.cpp struct.cpp tracepoint_format_parser.cpp types.cpp utils.cpp ${BFD_DISASM_SRC} ) if(HAVE_NAME_TO_HANDLE_AT) target_compile_definitions(bpftrace PRIVATE HAVE_NAME_TO_HANDLE_AT=1) endif(HAVE_NAME_TO_HANDLE_AT) if(HAVE_BCC_PROG_LOAD) target_compile_definitions(bpftrace PRIVATE HAVE_BCC_PROG_LOAD) endif(HAVE_BCC_PROG_LOAD) if(HAVE_BCC_CREATE_MAP) target_compile_definitions(bpftrace PRIVATE HAVE_BCC_CREATE_MAP) endif(HAVE_BCC_CREATE_MAP) if(HAVE_BCC_ELF_FOREACH_SYM) target_compile_definitions(bpftrace PRIVATE HAVE_BCC_ELF_FOREACH_SYM) endif(HAVE_BCC_ELF_FOREACH_SYM) if (LIBBPF_BTF_DUMP_FOUND) target_compile_definitions(bpftrace PRIVATE HAVE_LIBBPF_BTF_DUMP) target_include_directories(bpftrace PUBLIC ${LIBBPF_INCLUDE_DIRS}) target_link_libraries(bpftrace ${LIBBPF_LIBRARIES}) endif(LIBBPF_BTF_DUMP_FOUND) if(HAVE_BFD_DISASM) target_compile_definitions(bpftrace PRIVATE HAVE_BFD_DISASM) if(LIBBFD_DISASM_FOUR_ARGS_SIGNATURE) target_compile_definitions(bpftrace PRIVATE LIBBFD_DISASM_FOUR_ARGS_SIGNATURE) endif(LIBBFD_DISASM_FOUR_ARGS_SIGNATURE) if(STATIC_LINKING) add_library(LIBBFD STATIC IMPORTED) set_property(TARGET LIBBFD PROPERTY IMPORTED_LOCATION ${LIBBFD_LIBRARIES}) target_link_libraries(bpftrace LIBBFD) add_library(LIBOPCODES STATIC IMPORTED) set_property(TARGET LIBOPCODES PROPERTY IMPORTED_LOCATION ${LIBOPCODES_LIBRARIES}) target_link_libraries(bpftrace LIBOPCODES) add_library(LIBIBERTY STATIC IMPORTED) set_property(TARGET LIBIBERTY PROPERTY IMPORTED_LOCATION ${LIBIBERTY_LIBRARIES}) target_link_libraries(bpftrace LIBIBERTY) else() target_link_libraries(bpftrace ${LIBBFD_LIBRARIES}) target_link_libraries(bpftrace ${LIBOPCODES_LIBRARIES}) endif(STATIC_LINKING) endif(HAVE_BFD_DISASM) if (ALLOW_UNSAFE_PROBE) target_compile_definitions(bpftrace PRIVATE HAVE_UNSAFE_PROBE) endif(ALLOW_UNSAFE_PROBE) target_link_libraries(bpftrace arch ast parser resources) if(STATIC_LINKING) target_link_libraries(bpftrace ${LIBBCC_LIBRARIES}) target_link_libraries(bpftrace ${LIBBPF_LIBRARY_STATIC}) target_link_libraries(bpftrace ${LIBBCC_LOADER_LIBRARY_STATIC}) add_library(LIBELF STATIC IMPORTED) set_property(TARGET LIBELF PROPERTY IMPORTED_LOCATION ${LIBELF_LIBRARIES}) target_link_libraries(bpftrace LIBELF) if(NOT STATIC_LIBC) set_target_properties(bpftrace PROPERTIES LINK_FLAGS "${EMBEDDED_LINK_FLAGS}") endif() else() target_link_libraries(bpftrace ${LIBBCC_LIBRARIES}) target_link_libraries(bpftrace ${LIBELF_LIBRARIES}) endif(STATIC_LINKING) if (BUILD_ASAN) if(${CMAKE_VERSION} VERSION_LESS "3.13.0") # target_link_options is supported in CMake 3.13 and newer message("Please use CMake 3.13 or newer to enable ASAN") endif() target_compile_definitions(bpftrace PRIVATE BUILD_ASAN) target_compile_options(bpftrace PUBLIC "-fsanitize=address") target_link_options(bpftrace PUBLIC "-fsanitize=address") endif() install(TARGETS bpftrace DESTINATION bin) set(KERNEL_HEADERS_DIR "" CACHE PATH "Hard-code kernel headers directory") if (KERNEL_HEADERS_DIR) MESSAGE(STATUS "Using KERNEL_HEADERS_DIR=${KERNEL_HEADERS_DIR}") target_compile_definitions(bpftrace PUBLIC KERNEL_HEADERS_DIR="${KERNEL_HEADERS_DIR}") endif() execute_process( COMMAND git describe --abbrev=4 --dirty --tags WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE BPFTRACE_VERSION ERROR_VARIABLE GIT_DESCRIBE_ERROR OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE retcode ) # If the build is not done from a git repo, get the version information from # the version variables in main CMakeLists.txt if(NOT "${retcode}" STREQUAL "0") set(BPFTRACE_VERSION "v${bpftrace_VERSION_MAJOR}.${bpftrace_VERSION_MINOR}.${bpftrace_VERSION_PATCH}") endif() add_definitions("-DBPFTRACE_VERSION=\"${BPFTRACE_VERSION}\"") bpftrace-0.9.4/src/act_helpers.h000066400000000000000000000035631361633214400165550ustar00rootroot00000000000000#pragma once // Annoying C type helpers. // // C headers sometimes contain struct ending with a flexible array // member, which is not supported in C++. An example of such a type is // file_handle from fcntl.h (man name_to_handle_at) // // Here are some helper macros helping to ensure if the C++ // counterpart has members of the same type and offset. #include #include namespace act_helpers { template M get_member_type(M T:: *); } #define ACTH_SAME_SIZE(cxxtype, ctype, extra_type) \ (sizeof(cxxtype) == sizeof(ctype) + sizeof(extra_type)) #define ACTH_GET_TYPE_OF(mem) \ decltype(act_helpers::get_member_type(&mem)) #define ACTH_SAME_OFFSET(cxxtype, cxxmem, ctype, cmem) \ (std::is_standard_layout::value && \ std::is_standard_layout::value && \ (offsetof(cxxtype, cxxmem) == offsetof(ctype, cmem))) #define ACTH_SAME_TYPE(cxxtype, cxxmem, ctype, cmem) \ std::is_same::value #define ACTH_ASSERT_SAME_SIZE(cxxtype, ctype, extra_type) \ static_assert(ACTH_SAME_SIZE(cxxtype, ctype, extra_type), \ "assumption that is broken: " #cxxtype " == " #ctype " + " # extra_type) #define ACTH_ASSERT_SAME_OFFSET(cxxtype, cxxmem, ctype, cmem) \ static_assert(ACTH_SAME_OFFSET(cxxtype, cxxmem, ctype, cmem), \ "assumption that is broken: " #cxxtype "::" #cxxmem " is at the same offset as " #ctype "::" #cmem) #define ACTH_ASSERT_SAME_TYPE(cxxtype, cxxmem, ctype, cmem) \ static_assert(ACTH_SAME_TYPE(cxxtype, cxxmem, ctype, cmem), \ "assumption that is broken: " #cxxtype "::" #cxxmem " has the same type as " #ctype "::" #cmem) #define ACTH_ASSERT_SAME_MEMBER(cxxtype, cxxmem, ctype, cmem) \ ACTH_ASSERT_SAME_TYPE(cxxtype, cxxmem, ctype, cmem); \ ACTH_ASSERT_SAME_OFFSET(cxxtype, cxxmem, ctype, cmem) bpftrace-0.9.4/src/arch/000077500000000000000000000000001361633214400150215ustar00rootroot00000000000000bpftrace-0.9.4/src/arch/CMakeLists.txt000066400000000000000000000004041361633214400175570ustar00rootroot00000000000000if(CMAKE_SYSTEM_PROCESSOR STREQUAL "aarch64") add_library(arch aarch64.cpp) elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64" OR CMAKE_SYSTEM_PROCESSOR STREQUAL "ppc64le") add_library(arch ppc64.cpp) else() add_library(arch x86_64.cpp) endif() bpftrace-0.9.4/src/arch/aarch64.cpp000066400000000000000000000022201361633214400167510ustar00rootroot00000000000000#include "arch.h" #include #include namespace bpftrace { namespace arch { // clang-format off static std::array registers = { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31", "sp", "pc", "pstate", }; static std::array arg_registers = { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", }; // clang-format on int offset(std::string reg_name) { auto it = find(registers.begin(), registers.end(), reg_name); if (it == registers.end()) return -1; return distance(registers.begin(), it); } int max_arg() { return arg_registers.size() - 1; } int arg_offset(int arg_num) { return offset(arg_registers.at(arg_num)); } int ret_offset() { return offset("r0"); } int pc_offset() { return offset("pc"); } int sp_offset() { return offset("sp"); } std::string name() { return std::string("aarch64"); } } // namespace arch } // namespace bpftrace bpftrace-0.9.4/src/arch/arch.h000066400000000000000000000004131361633214400161050ustar00rootroot00000000000000#pragma once #include namespace bpftrace { namespace arch { int offset(std::string reg_name); int max_arg(); int arg_offset(int arg_num); int ret_offset(); int pc_offset(); int sp_offset(); std::string name(); } // namespace arch } // namespace bpftrace bpftrace-0.9.4/src/arch/ppc64.cpp000066400000000000000000000025351361633214400164660ustar00rootroot00000000000000#include "arch.h" #include #include namespace bpftrace { namespace arch { // clang-format off static std::array registers = { "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31", "nip", "msr", "orig_gpr3", "ctr", "link", "xer", "ccr", "softe", "trap", "dar", "dsisr", "result", }; static std::array arg_registers = { "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", }; // clang-format on int offset(std::string reg_name) { auto it = find(registers.begin(), registers.end(), reg_name); if (it == registers.end()) return -1; return distance(registers.begin(), it); } int max_arg() { return arg_registers.size() - 1; } int arg_offset(int arg_num) { return offset(arg_registers.at(arg_num)); } int ret_offset() { return offset("r3"); } int pc_offset() { return offset("nip"); } int sp_offset() { return offset("r1"); } std::string name() { #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ return std::string("ppc64le"); #else return std::string("ppc64"); #endif // __BYTE_ORDER__ } } // namespace arch } // namespace bpftrace bpftrace-0.9.4/src/arch/x86_64.cpp000066400000000000000000000020751361633214400164670ustar00rootroot00000000000000#include "arch.h" #include #include namespace bpftrace { namespace arch { // clang-format off static std::array registers = { "r15", "r14", "r13", "r12", "bp", "bx", "r11", "r10", "r9", "r8", "ax", "cx", "dx", "si", "di", "orig_ax", "ip", "cs", "flags", "sp", "ss", "fs_base", "gs_base", "ds", "es", "fs", "gs", }; static std::array arg_registers = { "di", "si", "dx", "cx", "r8", "r9", }; // clang-format on int offset(std::string reg_name) { auto it = find(registers.begin(), registers.end(), reg_name); if (it == registers.end()) return -1; return distance(registers.begin(), it); } int max_arg() { return arg_registers.size() - 1; } int arg_offset(int arg_num) { return offset(arg_registers.at(arg_num)); } int ret_offset() { return offset("ax"); } int pc_offset() { return offset("ip"); } int sp_offset() { return offset("sp"); } std::string name() { return std::string("x86_64"); } } // namespace arch } // namespace bpftrace bpftrace-0.9.4/src/ast/000077500000000000000000000000001361633214400146735ustar00rootroot00000000000000bpftrace-0.9.4/src/ast/CMakeLists.txt000066400000000000000000000032011361633214400174270ustar00rootroot00000000000000add_library(ast ast.cpp codegen_llvm.cpp field_analyser.cpp irbuilderbpf.cpp printer.cpp semantic_analyser.cpp ) target_include_directories(ast PUBLIC ${CMAKE_SOURCE_DIR}/src) target_include_directories(ast PUBLIC ${CMAKE_SOURCE_DIR}/src/ast) target_include_directories(ast PUBLIC ${CMAKE_BINARY_DIR}) target_link_libraries(ast arch) add_dependencies(ast parser) if (STATIC_LINKING) set(clang_libs clangAST clangAnalysis clangBasic clangDriver clangEdit clangFormat clangFrontend clangIndex clangLex clangParse clangRewrite clangSema clangSerialization clangToolingCore) if(EMBED_CLANG) list(APPEND clang_libs clangToolingInclusions) if(EMBED_LIBCLANG_ONLY) unlink_transitive_dependency("${CLANG_EXPORTED_TARGETS}" "LLVM") endif() target_link_libraries(ast ${CLANG_EMBEDDED_CMAKE_TARGETS}) else() list(INSERT clang_libs 0 libclang.a) endif() if(EMBED_LLVM) target_link_libraries(ast ${LLVM_EMBEDDED_CMAKE_TARGETS}) else() llvm_map_components_to_libnames(llvm_libs bpfcodegen ipo irreader mcjit orcjit ${LLVM_TARGETS_TO_BUILD}) target_link_libraries(ast ${clang_libs}) target_link_libraries(ast ${llvm_libs}) endif() else() find_library(found_LLVM LLVM HINTS ${LLVM_LIBRARY_DIRS}) if(found_LLVM) target_link_libraries(ast LLVM) else() llvm_map_components_to_libnames(_llvm_libs bpfcodegen ipo irreader mcjit orcjit ${LLVM_TARGETS_TO_BUILD}) llvm_expand_dependencies(llvm_libs ${_llvm_libs}) target_link_libraries(ast ${llvm_libs}) endif() target_link_libraries(ast libclang) endif() bpftrace-0.9.4/src/ast/ast.cpp000066400000000000000000000304541361633214400161740ustar00rootroot00000000000000#include "ast.h" #include "parser.tab.hh" #include namespace bpftrace { namespace ast { Node::Node() : loc(location()) { } Node::Node(location loc) : loc(loc) { } Expression::Expression() : Node() { } Expression::Expression(location loc) : Node(loc) { } Integer::Integer(long n) : n(n) { is_literal = true; } Integer::Integer(long n, location loc) : Expression(loc), n(n) { is_literal = true; } void Integer::accept(Visitor &v) { v.visit(*this); } String::String(const std::string &str) : str(str) { is_literal = true; } String::String(const std::string &str, location loc) : Expression(loc), str(str) { is_literal = true; } void String::accept(Visitor &v) { v.visit(*this); } StackMode::StackMode(const std::string &mode) : mode(mode) { is_literal = true; } StackMode::StackMode(const std::string &mode, location loc) : Expression(loc), mode(mode) { is_literal = true; } void StackMode::accept(Visitor &v) { v.visit(*this); } Builtin::Builtin(const std::string &ident) : ident(is_deprecated(ident)) { } Builtin::Builtin(const std::string &ident, location loc) : Expression(loc), ident(is_deprecated(ident)) { } void Builtin::accept(Visitor &v) { v.visit(*this); } Identifier::Identifier(const std::string &ident) : ident(ident) { } Identifier::Identifier(const std::string &ident, location loc) : Expression(loc), ident(ident) { } void Identifier::accept(Visitor &v) { v.visit(*this); } PositionalParameter::PositionalParameter(PositionalParameterType ptype, long n) : ptype(ptype), n(n) { } PositionalParameter::PositionalParameter(PositionalParameterType ptype, long n, location loc) : Expression(loc), ptype(ptype), n(n) { } void PositionalParameter::accept(Visitor &v) { v.visit(*this); } Call::Call(const std::string &func) : func(is_deprecated(func)), vargs(nullptr) { } Call::Call(const std::string &func, location loc) : Expression(loc), func(is_deprecated(func)), vargs(nullptr) { } Call::Call(const std::string &func, ExpressionList *vargs) : func(is_deprecated(func)), vargs(vargs) { } Call::Call(const std::string &func, ExpressionList *vargs, location loc) : Expression(loc), func(is_deprecated(func)), vargs(vargs) { } void Call::accept(Visitor &v) { v.visit(*this); } Map::Map(const std::string &ident, location loc) : Expression(loc), ident(ident), vargs(nullptr) { is_map = true; } Map::Map(const std::string &ident, ExpressionList *vargs) : ident(ident), vargs(vargs) { is_map = true; } Map::Map(const std::string &ident, ExpressionList *vargs, location loc) : Expression(loc), ident(ident), vargs(vargs) { is_map = true; for (auto expr : *vargs) { expr->key_for_map = this; } } void Map::accept(Visitor &v) { v.visit(*this); } Variable::Variable(const std::string &ident) : ident(ident) { is_variable = true; } Variable::Variable(const std::string &ident, location loc) : Expression(loc), ident(ident) { is_variable = true; } void Variable::accept(Visitor &v) { v.visit(*this); } Binop::Binop(Expression *left, int op, Expression *right, location loc) : Expression(loc), left(left), right(right), op(op) { } void Binop::accept(Visitor &v) { v.visit(*this); } Unop::Unop(int op, Expression *expr, location loc) : Expression(loc), expr(expr), op(op), is_post_op(false) { } Unop::Unop(int op, Expression *expr, bool is_post_op, location) : Expression(loc), expr(expr), op(op), is_post_op(is_post_op) { } void Unop::accept(Visitor &v) { v.visit(*this); } Ternary::Ternary(Expression *cond, Expression *left, Expression *right) : cond(cond), left(left), right(right) { } Ternary::Ternary(Expression *cond, Expression *left, Expression *right, location loc) : Expression(loc), cond(cond), left(left), right(right) { } void Ternary::accept(Visitor &v) { v.visit(*this); } FieldAccess::FieldAccess(Expression *expr, const std::string &field) : expr(expr), field(field) { } FieldAccess::FieldAccess(Expression *expr, const std::string &field, location loc) : Expression(loc), expr(expr), field(field) { } void FieldAccess::accept(Visitor &v) { v.visit(*this); } ArrayAccess::ArrayAccess(Expression *expr, Expression *indexpr) : expr(expr), indexpr(indexpr) { } ArrayAccess::ArrayAccess(Expression *expr, Expression *indexpr, location loc) : Expression(loc), expr(expr), indexpr(indexpr) { } void ArrayAccess::accept(Visitor &v) { v.visit(*this); } Cast::Cast(const std::string &type, bool is_pointer, Expression *expr) : cast_type(type), is_pointer(is_pointer), expr(expr) { } Cast::Cast(const std::string &type, bool is_pointer, Expression *expr, location loc) : Expression(loc), cast_type(type), is_pointer(is_pointer), expr(expr) { } void Cast::accept(Visitor &v) { v.visit(*this); } Statement::Statement(location loc) : Node(loc) { } ExprStatement::ExprStatement(Expression *expr) : expr(expr) { } ExprStatement::ExprStatement(Expression *expr, location loc) : Statement(loc), expr(expr) { } void ExprStatement::accept(Visitor &v) { v.visit(*this); } AssignMapStatement::AssignMapStatement(Map *map, Expression *expr, location loc) : Statement(loc), map(map), expr(expr) { expr->map = map; }; void AssignMapStatement::accept(Visitor &v) { v.visit(*this); } AssignVarStatement::AssignVarStatement(Variable *var, Expression *expr) : var(var), expr(expr) { expr->var = var; } AssignVarStatement::AssignVarStatement(Variable *var, Expression *expr, location loc) : Statement(loc), var(var), expr(expr) { expr->var = var; } void AssignVarStatement::accept(Visitor &v) { v.visit(*this); } Predicate::Predicate(Expression *expr) : expr(expr) { } Predicate::Predicate(Expression *expr, location loc) : Node(loc), expr(expr) { } void Predicate::accept(Visitor &v) { v.visit(*this); } AttachPoint::AttachPoint(const std::string &provider, location loc) : Node(loc), provider(probetypeName(provider)) { } AttachPoint::AttachPoint(const std::string &provider, const std::string &func, location loc) : Node(loc), provider(probetypeName(provider)), func(func), need_expansion(true) { } AttachPoint::AttachPoint(const std::string &provider, const std::string &target, const std::string &func, bool need_expansion, location loc) : Node(loc), provider(probetypeName(provider)), target(target), func(func), need_expansion(need_expansion) { } AttachPoint::AttachPoint(const std::string &provider, const std::string &target, const std::string &ns, const std::string &func, bool need_expansion, location loc) : Node(loc), provider(probetypeName(provider)), target(target), ns(ns), func(func), need_expansion(need_expansion) { } AttachPoint::AttachPoint(const std::string &provider, const std::string &str, uint64_t val, location loc) : Node(loc), provider(probetypeName(provider)), need_expansion(true) { if (this->provider == "uprobe" || this->provider == "uretprobe") { target = str; address = val; } else if (this->provider == "kprobe") { func = str; func_offset = val; } else { target = str; freq = val; } } AttachPoint::AttachPoint(const std::string &provider, const std::string &target, uint64_t addr, uint64_t len, const std::string &mode, location loc) : Node(loc), provider(probetypeName(provider)), target(target), addr(addr), len(len), mode(mode) { } AttachPoint::AttachPoint(const std::string &provider, const std::string &target, const std::string &func, uint64_t offset, location loc) : Node(loc), provider(probetypeName(provider)), target(target), func(func), need_expansion(true), func_offset(offset) { } void AttachPoint::accept(Visitor &v) { v.visit(*this); } If::If(Expression *cond, StatementList *stmts) : cond(cond), stmts(stmts) { } If::If(Expression *cond, StatementList *stmts, StatementList *else_stmts) : cond(cond), stmts(stmts), else_stmts(else_stmts) { } void If::accept(Visitor &v) { v.visit(*this); } Unroll::Unroll(long int var, StatementList *stmts) : var(var), stmts(stmts) { } void Unroll::accept(Visitor &v) { v.visit(*this); } Probe::Probe(AttachPointList *attach_points, Predicate *pred, StatementList *stmts) : attach_points(attach_points), pred(pred), stmts(stmts) { } void Probe::accept(Visitor &v) { v.visit(*this); } Program::Program(const std::string &c_definitions, ProbeList *probes) : c_definitions(c_definitions), probes(probes) { } void Program::accept(Visitor &v) { v.visit(*this); } std::string opstr(Binop &binop) { switch (binop.op) { case bpftrace::Parser::token::EQ: return "=="; case bpftrace::Parser::token::NE: return "!="; case bpftrace::Parser::token::LE: return "<="; case bpftrace::Parser::token::GE: return ">="; case bpftrace::Parser::token::LT: return "<"; case bpftrace::Parser::token::GT: return ">"; case bpftrace::Parser::token::LAND: return "&&"; case bpftrace::Parser::token::LOR: return "||"; case bpftrace::Parser::token::LEFT: return "<<"; case bpftrace::Parser::token::RIGHT: return ">>"; case bpftrace::Parser::token::PLUS: return "+"; case bpftrace::Parser::token::MINUS: return "-"; case bpftrace::Parser::token::MUL: return "*"; case bpftrace::Parser::token::DIV: return "/"; case bpftrace::Parser::token::MOD: return "%"; case bpftrace::Parser::token::BAND: return "&"; case bpftrace::Parser::token::BOR: return "|"; case bpftrace::Parser::token::BXOR: return "^"; default: std::cerr << "unknown binary operator" << std::endl; abort(); } } std::string opstr(Unop &unop) { switch (unop.op) { case bpftrace::Parser::token::LNOT: return "!"; case bpftrace::Parser::token::BNOT: return "~"; case bpftrace::Parser::token::MINUS: return "-"; case bpftrace::Parser::token::MUL: return "dereference"; case bpftrace::Parser::token::INCREMENT: return "++"; case bpftrace::Parser::token::DECREMENT: return "--"; default: std::cerr << "unknown unary operator" << std::endl; abort(); } } std::string AttachPoint::name(const std::string &attach_point) const { std::string n = provider; if (target != "") n += ":" + target; if (ns != "") n += ":" + ns; if (attach_point != "") { n += ":" + attach_point; if (func_offset != 0) n += "+" + std::to_string(func_offset); } if (address != 0) n += ":" + std::to_string(address); if (freq != 0) n += ":" + std::to_string(freq); return n; } int AttachPoint::index(std::string name) { if (index_.count(name) == 0) return 0; return index_[name]; } void AttachPoint::set_index(std::string name, int index) { index_[name] = index; } std::string Probe::name() const { std::string n; for (auto &attach_point : *attach_points) { if (!n.empty()) n += ','; n += attach_point->provider; if (attach_point->target != "") n += ":" + attach_point->target; if (attach_point->ns != "") n += ":" + attach_point->ns; if (attach_point->func != "") { n += ":" + attach_point->func; if (attach_point->func_offset != 0) n += "+" + std::to_string(attach_point->func_offset); } if (attach_point->address != 0) n += ":" + std::to_string(attach_point->address); if (attach_point->freq != 0) n += ":" + std::to_string(attach_point->freq); } return n; } int Probe::index() { return index_; } void Probe::set_index(int index) { index_ = index; } } // namespace ast } // namespace bpftrace bpftrace-0.9.4/src/ast/ast.h000066400000000000000000000234021361633214400156340ustar00rootroot00000000000000#pragma once #include "location.hh" #include "utils.h" #include #include #include #include "types.h" namespace bpftrace { namespace ast { class Visitor; class Node { public: Node(); Node(location loc); virtual ~Node() = default; virtual void accept(Visitor &v) = 0; location loc; }; class Map; class Variable; class Expression : public Node { public: Expression(); Expression(location loc); SizedType type; Map *key_for_map = nullptr; Map *map = nullptr; // Only set when this expression is assigned to a map Variable *var = nullptr; // Set when this expression is assigned to a variable bool is_literal = false; bool is_variable = false; bool is_map = false; }; using ExpressionList = std::vector; class Integer : public Expression { public: explicit Integer(long n); explicit Integer(long n, location loc); long n; void accept(Visitor &v) override; }; class PositionalParameter : public Expression { public: explicit PositionalParameter(PositionalParameterType ptype, long n); explicit PositionalParameter(PositionalParameterType ptype, long n, location loc); PositionalParameterType ptype; long n; bool is_in_str = false; void accept(Visitor &v) override; }; class String : public Expression { public: explicit String(const std::string &str); explicit String(const std::string &str, location loc); std::string str; void accept(Visitor &v) override; }; class StackMode : public Expression { public: explicit StackMode(const std::string &mode); explicit StackMode(const std::string &mode, location loc); std::string mode; void accept(Visitor &v) override; }; class Identifier : public Expression { public: explicit Identifier(const std::string &ident); explicit Identifier(const std::string &ident, location loc); std::string ident; void accept(Visitor &v) override; }; class Builtin : public Expression { public: explicit Builtin(const std::string &ident); explicit Builtin(const std::string &ident, location loc); std::string ident; int probe_id; void accept(Visitor &v) override; }; class Call : public Expression { public: explicit Call(const std::string &func); explicit Call(const std::string &func, location loc); Call(const std::string &func, ExpressionList *vargs); Call(const std::string &func, ExpressionList *vargs, location loc); std::string func; ExpressionList *vargs; void accept(Visitor &v) override; }; class Map : public Expression { public: explicit Map(const std::string &ident, location loc); Map(const std::string &ident, ExpressionList *vargs); Map(const std::string &ident, ExpressionList *vargs, location loc); std::string ident; ExpressionList *vargs; bool skip_key_validation = false; void accept(Visitor &v) override; }; class Variable : public Expression { public: explicit Variable(const std::string &ident); explicit Variable(const std::string &ident, location loc); std::string ident; void accept(Visitor &v) override; }; class Binop : public Expression { public: Binop(Expression *left, int op, Expression *right, location loc); Expression *left, *right; int op; void accept(Visitor &v) override; }; class Unop : public Expression { public: Unop(int op, Expression *expr, location loc = location()); Unop(int op, Expression *expr, bool is_post_op = false, location loc = location()); Expression *expr; int op; bool is_post_op; void accept(Visitor &v) override; }; class FieldAccess : public Expression { public: FieldAccess(Expression *expr, const std::string &field); FieldAccess(Expression *expr, const std::string &field, location loc); Expression *expr; std::string field; void accept(Visitor &v) override; }; class ArrayAccess : public Expression { public: ArrayAccess(Expression *expr, Expression *indexpr); ArrayAccess(Expression *expr, Expression *indexpr, location loc); Expression *expr; Expression *indexpr; void accept(Visitor &v) override; }; class Cast : public Expression { public: Cast(const std::string &type, bool is_pointer, Expression *expr); Cast(const std::string &type, bool is_pointer, Expression *expr, location loc); std::string cast_type; bool is_pointer; Expression *expr; void accept(Visitor &v) override; }; class Statement : public Node { public: Statement() = default; Statement(location loc); }; using StatementList = std::vector; class ExprStatement : public Statement { public: explicit ExprStatement(Expression *expr); explicit ExprStatement(Expression *expr, location loc); Expression *expr; void accept(Visitor &v) override; }; class AssignMapStatement : public Statement { public: AssignMapStatement(Map *map, Expression *expr, location loc = location()); Map *map; Expression *expr; void accept(Visitor &v) override; }; class AssignVarStatement : public Statement { public: AssignVarStatement(Variable *var, Expression *expr); AssignVarStatement(Variable *var, Expression *expr, location loc); Variable *var; Expression *expr; void accept(Visitor &v) override; }; class If : public Statement { public: If(Expression *cond, StatementList *stmts); If(Expression *cond, StatementList *stmts, StatementList *else_stmts); Expression *cond; StatementList *stmts = nullptr; StatementList *else_stmts = nullptr; void accept(Visitor &v) override; }; class Unroll : public Statement { public: Unroll(long int var, StatementList *stmts); long int var = 0; StatementList *stmts; void accept(Visitor &v) override; }; class Predicate : public Node { public: explicit Predicate(Expression *expr); explicit Predicate(Expression *expr, location loc); Expression *expr; void accept(Visitor &v) override; }; class Ternary : public Expression { public: Ternary(Expression *cond, Expression *left, Expression *right); Ternary(Expression *cond, Expression *left, Expression *right, location loc); Expression *cond, *left, *right; void accept(Visitor &v) override; }; class AttachPoint : public Node { public: explicit AttachPoint(const std::string &provider, location loc = location()); AttachPoint(const std::string &provider, const std::string &func, location loc = location()); AttachPoint(const std::string &provider, const std::string &target, const std::string &func, bool need_expansion, location loc = location()); AttachPoint(const std::string &provider, const std::string &target, const std::string &ns, const std::string &func, bool need_expansion, location loc = location()); AttachPoint(const std::string &provider, const std::string &str, uint64_t val, location loc = location()); AttachPoint(const std::string &provider, const std::string &target, uint64_t addr, uint64_t len, const std::string &mode, location loc = location()); AttachPoint(const std::string &provider, const std::string &target, const std::string &func, uint64_t offset, location loc = location()); std::string provider; std::string target; std::string ns; std::string func; usdt_probe_entry usdt; // resolved USDT entry, used to support arguments with wildcard matches int freq = 0; uint64_t addr = 0; uint64_t len = 0; std::string mode; bool need_expansion = false; uint64_t address = 0; uint64_t func_offset = 0; void accept(Visitor &v) override; std::string name(const std::string &attach_point) const; int index(std::string name); void set_index(std::string name, int index); private: std::map index_; }; using AttachPointList = std::vector; class Probe : public Node { public: Probe(AttachPointList *attach_points, Predicate *pred, StatementList *stmts); AttachPointList *attach_points; Predicate *pred; StatementList *stmts; void accept(Visitor &v) override; std::string name() const; bool need_expansion = false; // must build a BPF program per wildcard match bool need_tp_args_structs = false; // must import struct for tracepoints int index(); void set_index(int index); private: int index_ = 0; }; using ProbeList = std::vector; class Program : public Node { public: Program(const std::string &c_definitions, ProbeList *probes); std::string c_definitions; ProbeList *probes; void accept(Visitor &v) override; }; class Visitor { public: virtual ~Visitor() = default; virtual void visit(Integer &integer) = 0; virtual void visit(PositionalParameter &integer) = 0; virtual void visit(String &string) = 0; virtual void visit(Builtin &builtin) = 0; virtual void visit(Identifier &identifier) = 0; virtual void visit(StackMode &mode) = 0; virtual void visit(Call &call) = 0; virtual void visit(Map &map) = 0; virtual void visit(Variable &var) = 0; virtual void visit(Binop &binop) = 0; virtual void visit(Unop &unop) = 0; virtual void visit(Ternary &ternary) = 0; virtual void visit(FieldAccess &acc) = 0; virtual void visit(ArrayAccess &arr) = 0; virtual void visit(Cast &cast) = 0; virtual void visit(ExprStatement &expr) = 0; virtual void visit(AssignMapStatement &assignment) = 0; virtual void visit(AssignVarStatement &assignment) = 0; virtual void visit(If &if_block) = 0; virtual void visit(Unroll &unroll) = 0; virtual void visit(Predicate &pred) = 0; virtual void visit(AttachPoint &ap) = 0; virtual void visit(Probe &probe) = 0; virtual void visit(Program &program) = 0; }; std::string opstr(Binop &binop); std::string opstr(Unop &unop); } // namespace ast } // namespace bpftrace bpftrace-0.9.4/src/ast/codegen_llvm.cpp000066400000000000000000002074711361633214400200500ustar00rootroot00000000000000#include "codegen_llvm.h" #include "arch/arch.h" #include "ast.h" #include "bpforc.h" #include "parser.tab.hh" #include "tracepoint_format_parser.h" #include "types.h" #include "utils.h" #include #include #include #include #include #include #include #include #include #include namespace bpftrace { namespace ast { void CodegenLLVM::visit(Integer &integer) { expr_ = b_.getInt64(integer.n); } void CodegenLLVM::visit(PositionalParameter ¶m) { switch (param.ptype) { case PositionalParameterType::positional: { std::string pstr = bpftrace_.get_param(param.n, param.is_in_str); if (is_numeric(pstr)) { expr_ = b_.getInt64(std::stoll(pstr)); } else { Constant *const_str = ConstantDataArray::getString(module_->getContext(), pstr, true); AllocaInst *buf = b_.CreateAllocaBPF(ArrayType::get(b_.getInt8Ty(), pstr.length() + 1), "str"); b_.CreateMemSet(buf, b_.getInt8(0), pstr.length() + 1, 1); b_.CreateStore(const_str, buf); expr_ = buf; } } break; case PositionalParameterType::count: expr_ = b_.getInt64(bpftrace_.num_params()); break; default: std::cerr << "unknown parameter type" << std::endl; abort(); break; } } void CodegenLLVM::visit(String &string) { string.str.resize(string.type.size-1); Constant *const_str = ConstantDataArray::getString(module_->getContext(), string.str, true); AllocaInst *buf = b_.CreateAllocaBPF(string.type, "str"); b_.CreateStore(const_str, buf); expr_ = buf; } void CodegenLLVM::visit(Identifier &identifier) { if (bpftrace_.enums_.count(identifier.ident) != 0) { expr_ = b_.getInt64(bpftrace_.enums_[identifier.ident]); } else { std::cerr << "unknown identifier \"" << identifier.ident << "\"" << std::endl; abort(); } } void CodegenLLVM::visit(Builtin &builtin) { if (builtin.ident == "nsecs") { expr_ = b_.CreateGetNs(); } else if (builtin.ident == "elapsed") { AllocaInst *key = b_.CreateAllocaBPF(b_.getInt64Ty(), "elapsed_key"); b_.CreateStore(b_.getInt64(0), key); auto &map = bpftrace_.elapsed_map_; auto type = SizedType(Type::integer, 8); auto start = b_.CreateMapLookupElem(map->mapfd_, key, type); expr_ = b_.CreateSub(b_.CreateGetNs(), start); // start won't be on stack, no need to LifeTimeEnd it b_.CreateLifetimeEnd(key); } else if (builtin.ident == "kstack" || builtin.ident == "ustack") { Value *stackid = b_.CreateGetStackId(ctx_, builtin.ident == "ustack", builtin.type.stack_type); // Kernel stacks should not be differentiated by tid, since the kernel // address space is the same between pids (and when aggregating you *want* // to be able to correlate between pids in most cases). User-space stacks // are special because of ASLR and so we do usym()-style packing. if (builtin.ident == "ustack") { // pack uint64_t with: (uint32_t)stack_id, (uint32_t)pid Value *pidhigh = b_.CreateShl(b_.CreateGetPidTgid(), 32); stackid = b_.CreateOr(stackid, pidhigh); } expr_ = stackid; } else if (builtin.ident == "pid" || builtin.ident == "tid") { Value *pidtgid = b_.CreateGetPidTgid(); if (builtin.ident == "pid") { expr_ = b_.CreateLShr(pidtgid, 32); } else if (builtin.ident == "tid") { expr_ = b_.CreateAnd(pidtgid, 0xffffffff); } } else if (builtin.ident == "cgroup") { expr_ = b_.CreateGetCurrentCgroupId(); } else if (builtin.ident == "uid" || builtin.ident == "gid" || builtin.ident == "username") { Value *uidgid = b_.CreateGetUidGid(); if (builtin.ident == "uid" || builtin.ident == "username") { expr_ = b_.CreateAnd(uidgid, 0xffffffff); } else if (builtin.ident == "gid") { expr_ = b_.CreateLShr(uidgid, 32); } } else if (builtin.ident == "cpu") { expr_ = b_.CreateGetCpuId(); } else if (builtin.ident == "curtask") { expr_ = b_.CreateGetCurrentTask(); } else if (builtin.ident == "rand") { expr_ = b_.CreateGetRandom(); } else if (builtin.ident == "comm") { AllocaInst *buf = b_.CreateAllocaBPF(builtin.type, "comm"); // initializing memory needed for older kernels: b_.CreateMemSet(buf, b_.getInt8(0), builtin.type.size, 1); b_.CreateGetCurrentComm(buf, builtin.type.size); expr_ = buf; } else if ((!builtin.ident.compare(0, 3, "arg") && builtin.ident.size() == 4 && builtin.ident.at(3) >= '0' && builtin.ident.at(3) <= '9') || builtin.ident == "retval" || builtin.ident == "func") { int offset; if (builtin.ident == "retval") offset = arch::ret_offset(); else if (builtin.ident == "func") offset = arch::pc_offset(); else // argX { int arg_num = atoi(builtin.ident.substr(3).c_str()); if (probetype(current_attach_point_->provider) == ProbeType::usdt) { expr_ = b_.CreateUSDTReadArgument(ctx_, current_attach_point_, arg_num, builtin, bpftrace_.pid_); return; } offset = arch::arg_offset(arg_num); } expr_ = b_.CreateLoad( b_.getInt64Ty(), b_.CreateGEP(ctx_, b_.getInt64(offset * sizeof(uintptr_t))), builtin.ident); if (builtin.type.type == Type::usym) { AllocaInst *buf = b_.CreateAllocaBPF(builtin.type, "func"); b_.CreateMemSet(buf, b_.getInt8(0), builtin.type.size, 1); Value *pid = b_.CreateLShr(b_.CreateGetPidTgid(), 32); Value *addr_offset = b_.CreateGEP(buf, b_.getInt64(0)); Value *pid_offset = b_.CreateGEP(buf, {b_.getInt64(0), b_.getInt64(8)}); b_.CreateStore(expr_, addr_offset); b_.CreateStore(pid, pid_offset); expr_ = buf; } } else if (!builtin.ident.compare(0, 4, "sarg") && builtin.ident.size() == 5 && builtin.ident.at(4) >= '0' && builtin.ident.at(4) <= '9') { int sp_offset = arch::sp_offset(); if (sp_offset == -1) { std::cerr << "negative offset for stack pointer" << std::endl; abort(); } int arg_num = atoi(builtin.ident.substr(4).c_str()); Value *sp = b_.CreateLoad( b_.getInt64Ty(), b_.CreateGEP(ctx_, b_.getInt64(sp_offset * sizeof(uintptr_t))), "reg_sp"); AllocaInst *dst = b_.CreateAllocaBPF(builtin.type, builtin.ident); Value *src = b_.CreateAdd(sp, b_.getInt64((arg_num + 1) * sizeof(uintptr_t))); b_.CreateProbeRead(dst, 8, src); expr_ = b_.CreateLoad(dst); b_.CreateLifetimeEnd(dst); } else if (builtin.ident == "probe") { auto begin = bpftrace_.probe_ids_.begin(); auto end = bpftrace_.probe_ids_.end(); auto found = std::find(begin, end, probefull_); if (found == end) { bpftrace_.probe_ids_.push_back(probefull_); builtin.probe_id = bpftrace_.next_probe_id(); } else { builtin.probe_id = std::distance(begin, found); } expr_ = b_.getInt64(builtin.probe_id); } else if (builtin.ident == "args") { expr_ = ctx_; } else if (builtin.ident == "cpid") { pid_t cpid = bpftrace_.child_pid(); if (cpid < 1) { std::cerr << "BUG: Invalid cpid: " << cpid << std::endl; abort(); } expr_ = b_.getInt32(cpid); } else if (builtin.ident == "ctx") { // undocumented builtin: for debugging expr_ = ctx_; } else { std::cerr << "unknown builtin \"" << builtin.ident << "\"" << std::endl; abort(); } } void CodegenLLVM::visit(Call &call) { if (call.func == "count") { Map &map = *call.map; AllocaInst *key = getMapKey(map); Value *oldval = b_.CreateMapLookupElem(map, key); AllocaInst *newval = b_.CreateAllocaBPF(map.type, map.ident + "_val"); b_.CreateStore(b_.CreateAdd(oldval, b_.getInt64(1)), newval); b_.CreateMapUpdateElem(map, key, newval); // oldval can only be an integer so won't be in memory and doesn't need lifetime end b_.CreateLifetimeEnd(key); b_.CreateLifetimeEnd(newval); expr_ = nullptr; } else if (call.func == "sum") { Map &map = *call.map; AllocaInst *key = getMapKey(map); Value *oldval = b_.CreateMapLookupElem(map, key); AllocaInst *newval = b_.CreateAllocaBPF(map.type, map.ident + "_val"); call.vargs->front()->accept(*this); // promote int to 64-bit expr_ = b_.CreateIntCast(expr_, b_.getInt64Ty(), call.vargs->front()->type.is_signed); b_.CreateStore(b_.CreateAdd(expr_, oldval), newval); b_.CreateMapUpdateElem(map, key, newval); // oldval can only be an integer so won't be in memory and doesn't need lifetime end b_.CreateLifetimeEnd(key); b_.CreateLifetimeEnd(newval); expr_ = nullptr; } else if (call.func == "min") { Map &map = *call.map; AllocaInst *key = getMapKey(map); Value *oldval = b_.CreateMapLookupElem(map, key); AllocaInst *newval = b_.CreateAllocaBPF(map.type, map.ident + "_val"); // Store the max of (0xffffffff - val), so that our SGE comparison with uninitialized // elements will always store on the first occurrence. Revent this later when printing. Function *parent = b_.GetInsertBlock()->getParent(); call.vargs->front()->accept(*this); // promote int to 64-bit expr_ = b_.CreateIntCast(expr_, b_.getInt64Ty(), call.vargs->front()->type.is_signed); Value *inverted = b_.CreateSub(b_.getInt64(0xffffffff), expr_); BasicBlock *lt = BasicBlock::Create(module_->getContext(), "min.lt", parent); BasicBlock *ge = BasicBlock::Create(module_->getContext(), "min.ge", parent); b_.CreateCondBr(b_.CreateICmpSGE(inverted, oldval), ge, lt); b_.SetInsertPoint(ge); b_.CreateStore(inverted, newval); b_.CreateMapUpdateElem(map, key, newval); b_.CreateBr(lt); b_.SetInsertPoint(lt); b_.CreateLifetimeEnd(key); b_.CreateLifetimeEnd(newval); expr_ = nullptr; } else if (call.func == "max") { Map &map = *call.map; AllocaInst *key = getMapKey(map); Value *oldval = b_.CreateMapLookupElem(map, key); AllocaInst *newval = b_.CreateAllocaBPF(map.type, map.ident + "_val"); Function *parent = b_.GetInsertBlock()->getParent(); call.vargs->front()->accept(*this); // promote int to 64-bit expr_ = b_.CreateIntCast(expr_, b_.getInt64Ty(), call.vargs->front()->type.is_signed); BasicBlock *lt = BasicBlock::Create(module_->getContext(), "min.lt", parent); BasicBlock *ge = BasicBlock::Create(module_->getContext(), "min.ge", parent); b_.CreateCondBr(b_.CreateICmpSGE(expr_, oldval), ge, lt); b_.SetInsertPoint(ge); b_.CreateStore(expr_, newval); b_.CreateMapUpdateElem(map, key, newval); b_.CreateBr(lt); b_.SetInsertPoint(lt); b_.CreateLifetimeEnd(key); b_.CreateLifetimeEnd(newval); expr_ = nullptr; } else if (call.func == "avg" || call.func == "stats") { // avg stores the count and total in a hist map using indexes 0 and 1 // respectively, and the calculation is made when printing. Map &map = *call.map; AllocaInst *count_key = getHistMapKey(map, b_.getInt64(0)); Value *count_old = b_.CreateMapLookupElem(map, count_key); AllocaInst *count_new = b_.CreateAllocaBPF(map.type, map.ident + "_num"); b_.CreateStore(b_.CreateAdd(count_old, b_.getInt64(1)), count_new); b_.CreateMapUpdateElem(map, count_key, count_new); b_.CreateLifetimeEnd(count_key); b_.CreateLifetimeEnd(count_new); AllocaInst *total_key = getHistMapKey(map, b_.getInt64(1)); Value *total_old = b_.CreateMapLookupElem(map, total_key); AllocaInst *total_new = b_.CreateAllocaBPF(map.type, map.ident + "_val"); call.vargs->front()->accept(*this); // promote int to 64-bit expr_ = b_.CreateIntCast(expr_, b_.getInt64Ty(), call.vargs->front()->type.is_signed); b_.CreateStore(b_.CreateAdd(expr_, total_old), total_new); b_.CreateMapUpdateElem(map, total_key, total_new); b_.CreateLifetimeEnd(total_key); b_.CreateLifetimeEnd(total_new); expr_ = nullptr; } else if (call.func == "hist") { Map &map = *call.map; call.vargs->front()->accept(*this); // promote int to 64-bit expr_ = b_.CreateIntCast(expr_, b_.getInt64Ty(), call.vargs->front()->type.is_signed); Function *log2_func = module_->getFunction("log2"); Value *log2 = b_.CreateCall(log2_func, expr_, "log2"); AllocaInst *key = getHistMapKey(map, log2); Value *oldval = b_.CreateMapLookupElem(map, key); AllocaInst *newval = b_.CreateAllocaBPF(map.type, map.ident + "_val"); b_.CreateStore(b_.CreateAdd(oldval, b_.getInt64(1)), newval); b_.CreateMapUpdateElem(map, key, newval); // oldval can only be an integer so won't be in memory and doesn't need lifetime end b_.CreateLifetimeEnd(key); b_.CreateLifetimeEnd(newval); expr_ = nullptr; } else if (call.func == "lhist") { Map &map = *call.map; call.vargs->front()->accept(*this); Function *linear_func = module_->getFunction("linear"); // prepare arguments Integer &value_arg = static_cast(*call.vargs->at(0)); Integer &min_arg = static_cast(*call.vargs->at(1)); Integer &max_arg = static_cast(*call.vargs->at(2)); Integer &step_arg = static_cast(*call.vargs->at(3)); Value *value, *min, *max, *step; value_arg.accept(*this); value = expr_; min_arg.accept(*this); min = expr_; max_arg.accept(*this); max = expr_; step_arg.accept(*this); step = expr_; // promote int to 64-bit value = b_.CreateIntCast(value, b_.getInt64Ty(), call.vargs->front()->type.is_signed); min = b_.CreateIntCast(min, b_.getInt64Ty(), false); max = b_.CreateIntCast(max, b_.getInt64Ty(), false); step = b_.CreateIntCast(step, b_.getInt64Ty(), false); Value *linear = b_.CreateCall(linear_func, {value, min, max, step} , "linear"); AllocaInst *key = getHistMapKey(map, linear); Value *oldval = b_.CreateMapLookupElem(map, key); AllocaInst *newval = b_.CreateAllocaBPF(map.type, map.ident + "_val"); b_.CreateStore(b_.CreateAdd(oldval, b_.getInt64(1)), newval); b_.CreateMapUpdateElem(map, key, newval); // oldval can only be an integer so won't be in memory and doesn't need lifetime end b_.CreateLifetimeEnd(key); b_.CreateLifetimeEnd(newval); expr_ = nullptr; } else if (call.func == "delete") { auto &arg = *call.vargs->at(0); auto &map = static_cast(arg); AllocaInst *key = getMapKey(map); b_.CreateMapDeleteElem(map, key); b_.CreateLifetimeEnd(key); expr_ = nullptr; } else if (call.func == "str") { AllocaInst *strlen = b_.CreateAllocaBPF(b_.getInt64Ty(), "strlen"); b_.CreateMemSet(strlen, b_.getInt8(0), sizeof(uint64_t), 1); if (call.vargs->size() > 1) { call.vargs->at(1)->accept(*this); Value *proposed_strlen = b_.CreateAdd(expr_, b_.getInt64(1)); // add 1 to accommodate probe_read_str's null byte // largest read we'll allow = our global string buffer size Value *max = b_.getInt64(bpftrace_.strlen_); // integer comparison: unsigned less-than-or-equal-to CmpInst::Predicate P = CmpInst::ICMP_ULE; // check whether proposed_strlen is less-than-or-equal-to maximum Value *Cmp = b_.CreateICmp(P, proposed_strlen, max, "str.min.cmp"); // select proposed_strlen if it's sufficiently low, otherwise choose maximum Value *Select = b_.CreateSelect(Cmp, proposed_strlen, max, "str.min.select"); b_.CreateStore(Select, strlen); } else { b_.CreateStore(b_.getInt64(bpftrace_.strlen_), strlen); } AllocaInst *buf = b_.CreateAllocaBPF(bpftrace_.strlen_, "str"); b_.CreateMemSet(buf, b_.getInt8(0), bpftrace_.strlen_, 1); call.vargs->front()->accept(*this); b_.CreateProbeReadStr(buf, b_.CreateLoad(strlen), expr_); b_.CreateLifetimeEnd(strlen); expr_ = buf; expr_deleter_ = [this,buf]() { b_.CreateLifetimeEnd(buf); }; } else if (call.func == "kaddr") { uint64_t addr; auto &name = static_cast(*call.vargs->at(0)).str; addr = bpftrace_.resolve_kname(name); expr_ = b_.getInt64(addr); } else if (call.func == "uaddr") { auto &name = static_cast(*call.vargs->at(0)).str; struct symbol sym = {}; int err = bpftrace_.resolve_uname(name, &sym, current_attach_point_->target); if (err < 0 || sym.address == 0) throw std::runtime_error("Could not resolve symbol: " + current_attach_point_->target + ":" + name); expr_ = b_.getInt64(sym.address); } else if (call.func == "cgroupid") { uint64_t cgroupid; auto &path = static_cast(*call.vargs->at(0)).str; cgroupid = bpftrace_.resolve_cgroupid(path); expr_ = b_.getInt64(cgroupid); } else if (call.func == "join") { call.vargs->front()->accept(*this); AllocaInst *first = b_.CreateAllocaBPF(SizedType(Type::integer, 8), call.func + "_first"); AllocaInst *second = b_.CreateAllocaBPF(b_.getInt64Ty(), call.func+"_second"); Value *perfdata = b_.CreateGetJoinMap(ctx_); Function *parent = b_.GetInsertBlock()->getParent(); BasicBlock *zero = BasicBlock::Create(module_->getContext(), "joinzero", parent); BasicBlock *notzero = BasicBlock::Create(module_->getContext(), "joinnotzero", parent); b_.CreateCondBr(b_.CreateICmpNE(perfdata, ConstantExpr::getCast(Instruction::IntToPtr, b_.getInt64(0), b_.getInt8PtrTy()), "joinzerocond"), notzero, zero); // arg0 b_.SetInsertPoint(notzero); b_.CreateStore(b_.getInt64(asyncactionint(AsyncAction::join)), perfdata); b_.CreateStore(b_.getInt64(join_id_), b_.CreateGEP(perfdata, b_.getInt64(8))); join_id_++; AllocaInst *arr = b_.CreateAllocaBPF(b_.getInt64Ty(), call.func+"_r0"); b_.CreateProbeRead(arr, 8, expr_); b_.CreateProbeReadStr(b_.CreateAdd(perfdata, b_.getInt64(8+8)), bpftrace_.join_argsize_, b_.CreateLoad(arr)); for (unsigned int i = 1; i < bpftrace_.join_argnum_; i++) { // argi b_.CreateStore(b_.CreateAdd(expr_, b_.getInt64(8 * i)), first); b_.CreateProbeRead(second, 8, b_.CreateLoad(first)); b_.CreateProbeReadStr(b_.CreateAdd(perfdata, b_.getInt64(8 + 8 + i * bpftrace_.join_argsize_)), bpftrace_.join_argsize_, b_.CreateLoad(second)); } // emit b_.CreatePerfEventOutput(ctx_, perfdata, 8 + 8 + bpftrace_.join_argnum_ * bpftrace_.join_argsize_); b_.CreateBr(zero); // done b_.SetInsertPoint(zero); expr_ = nullptr; } else if (call.func == "ksym") { // We want expr_ to just pass through from the child node - don't set it here call.vargs->front()->accept(*this); } else if (call.func == "usym") { // store uint64_t[2] with: [0]: (uint64_t)addr, [1]: (uint64_t)pid AllocaInst *buf = b_.CreateAllocaBPF(call.type, "usym"); b_.CreateMemSet(buf, b_.getInt8(0), call.type.size, 1); Value *pid = b_.CreateLShr(b_.CreateGetPidTgid(), 32); Value *addr_offset = b_.CreateGEP(buf, b_.getInt64(0)); Value *pid_offset = b_.CreateGEP(buf, {b_.getInt64(0), b_.getInt64(8)}); call.vargs->front()->accept(*this); b_.CreateStore(expr_, addr_offset); b_.CreateStore(pid, pid_offset); expr_ = buf; } else if (call.func == "ntop") { // struct { // int af_type; // union { // char[4] inet4; // char[16] inet6; // } // } //} std::vector elements = { b_.getInt64Ty(), // printf ID ArrayType::get(b_.getInt8Ty(), 16) }; StructType *inet_struct = StructType::create(elements, "inet_t", false); AllocaInst *buf = b_.CreateAllocaBPF(inet_struct, "inet"); Value *af_offset = b_.CreateGEP(buf, b_.getInt64(0)); Value *af_type; auto inet = call.vargs->at(0); if (call.vargs->size() == 1) { if (inet->type.type == Type::integer || inet->type.size == 4) { af_type = b_.getInt64(AF_INET); } else { af_type = b_.getInt64(AF_INET6); } } else { inet = call.vargs->at(1); call.vargs->at(0)->accept(*this); af_type = b_.CreateIntCast(expr_, b_.getInt64Ty(), true); } b_.CreateStore(af_type, af_offset); Value *inet_offset = b_.CreateGEP(buf, {b_.getInt32(0), b_.getInt32(1)}); b_.CreateMemSet(inet_offset, b_.getInt8(0), 16, 1); inet->accept(*this); if (inet->type.type == Type::array) { b_.CreateProbeRead(static_cast(inet_offset), inet->type.size, expr_); } else { b_.CreateStore(b_.CreateIntCast(expr_, b_.getInt32Ty(), false), inet_offset); } expr_ = buf; } else if (call.func == "reg") { auto ®_name = static_cast(*call.vargs->at(0)).str; int offset = arch::offset(reg_name); if (offset == -1) { std::cerr << "negative offset on reg() call" << std::endl; abort(); } expr_ = b_.CreateLoad( b_.getInt64Ty(), b_.CreateGEP(ctx_, b_.getInt64(offset * sizeof(uintptr_t))), call.func+"_"+reg_name); } else if (call.func == "printf") { createFormatStringCall(call, printf_id_, bpftrace_.printf_args_, "printf", AsyncAction::printf); } else if (call.func == "system") { createFormatStringCall(call, system_id_, bpftrace_.system_args_, "system", AsyncAction::syscall); } else if (call.func == "cat") { createFormatStringCall(call, cat_id_, bpftrace_.cat_args_, "cat", AsyncAction::cat); } else if (call.func == "exit") { /* * perf event output has: uint64_t asyncaction_id * The asyncaction_id informs user-space that this is not a printf(), but is a * special asynchronous action. The ID maps to exit(). */ ArrayType *perfdata_type = ArrayType::get(b_.getInt8Ty(), sizeof(uint64_t)); AllocaInst *perfdata = b_.CreateAllocaBPF(perfdata_type, "perfdata"); b_.CreateStore(b_.getInt64(asyncactionint(AsyncAction::exit)), perfdata); b_.CreatePerfEventOutput(ctx_, perfdata, sizeof(uint64_t)); b_.CreateLifetimeEnd(perfdata); expr_ = nullptr; } else if (call.func == "print") { /* * perf event output has: uint64_t asyncaction_id, uint64_t top, uint64_t div, string map_ident * The asyncaction_id informs user-space that this is not a printf(), but is a * special asynchronous action. The ID maps to print(). The top argument is either * a value for truncation, or 0 for everything. The div argument divides the output values * by this (eg: for use in nanosecond -> millisecond conversions). * TODO: consider stashing top & div in a printf_args_ like struct, so we don't need to pass * them here via the perfdata output (which is a little more wasteful than need be: I'm using * uint64_t's to avoid "misaligned stack access off" errors when juggling uint32_t's). */ auto &arg = *call.vargs->at(0); auto &map = static_cast(arg); Constant *const_str = ConstantDataArray::getString(module_->getContext(), map.ident, true); AllocaInst *str_buf = b_.CreateAllocaBPF(ArrayType::get(b_.getInt8Ty(), map.ident.length() + 1), "str"); b_.CreateMemSet(str_buf, b_.getInt8(0), map.ident.length() + 1, 1); b_.CreateStore(const_str, str_buf); ArrayType *perfdata_type = ArrayType::get(b_.getInt8Ty(), sizeof(uint64_t) + 2 * sizeof(uint64_t) + map.ident.length() + 1); AllocaInst *perfdata = b_.CreateAllocaBPF(perfdata_type, "perfdata"); // store asyncactionid: b_.CreateStore(b_.getInt64(asyncactionint(AsyncAction::print)), perfdata); // store top: if (call.vargs->size() > 1) { Integer &top_arg = static_cast(*call.vargs->at(1)); Value *top; top_arg.accept(*this); top = expr_; b_.CreateStore(top, b_.CreateGEP(perfdata, {b_.getInt32(0), b_.getInt32(sizeof(uint64_t))})); } else b_.CreateStore(b_.getInt64(0), b_.CreateGEP(perfdata, {b_.getInt64(0), b_.getInt64(sizeof(uint64_t))})); // store top: if (call.vargs->size() > 2) { Integer &div_arg = static_cast(*call.vargs->at(2)); Value *div; div_arg.accept(*this); div = expr_; b_.CreateStore(div, b_.CreateGEP(perfdata, {b_.getInt64(0), b_.getInt64(sizeof(uint64_t) + sizeof(uint64_t))})); } else b_.CreateStore(b_.getInt64(0), b_.CreateGEP(perfdata, {b_.getInt64(0), b_.getInt64(sizeof(uint64_t) + sizeof(uint64_t))})); // store map ident: b_.CREATE_MEMCPY(b_.CreateGEP(perfdata, {b_.getInt64(0), b_.getInt64(sizeof(uint64_t) + 2 * sizeof(uint64_t))}), str_buf, map.ident.length() + 1, 1); b_.CreatePerfEventOutput(ctx_, perfdata, sizeof(uint64_t) + 2 * sizeof(uint64_t) + map.ident.length() + 1); b_.CreateLifetimeEnd(perfdata); expr_ = nullptr; } else if (call.func == "clear" || call.func == "zero") { auto &arg = *call.vargs->at(0); auto &map = static_cast(arg); Constant *const_str = ConstantDataArray::getString(module_->getContext(), map.ident, true); AllocaInst *str_buf = b_.CreateAllocaBPF(ArrayType::get(b_.getInt8Ty(), map.ident.length() + 1), "str"); b_.CreateMemSet(str_buf, b_.getInt8(0), map.ident.length() + 1, 1); b_.CreateStore(const_str, str_buf); ArrayType *perfdata_type = ArrayType::get(b_.getInt8Ty(), sizeof(uint64_t) + map.ident.length() + 1); AllocaInst *perfdata = b_.CreateAllocaBPF(perfdata_type, "perfdata"); if (call.func == "clear") b_.CreateStore(b_.getInt64(asyncactionint(AsyncAction::clear)), perfdata); else b_.CreateStore(b_.getInt64(asyncactionint(AsyncAction::zero)), perfdata); b_.CREATE_MEMCPY(b_.CreateGEP(perfdata, {b_.getInt64(0), b_.getInt64(sizeof(uint64_t))}), str_buf, map.ident.length() + 1, 1); b_.CreatePerfEventOutput(ctx_, perfdata, sizeof(uint64_t) + map.ident.length() + 1); b_.CreateLifetimeEnd(perfdata); expr_ = nullptr; } else if (call.func == "time") { ArrayType *perfdata_type = ArrayType::get(b_.getInt8Ty(), sizeof(uint64_t) * 2); AllocaInst *perfdata = b_.CreateAllocaBPF(perfdata_type, "perfdata"); b_.CreateStore(b_.getInt64(asyncactionint(AsyncAction::time)), perfdata); b_.CreateStore(b_.getInt64(time_id_), b_.CreateGEP(perfdata, {b_.getInt64(0), b_.getInt64(sizeof(uint64_t))})); time_id_++; b_.CreatePerfEventOutput(ctx_, perfdata, sizeof(uint64_t) * 2); b_.CreateLifetimeEnd(perfdata); expr_ = nullptr; } else if (call.func == "kstack" || call.func == "ustack") { Value *stackid = b_.CreateGetStackId(ctx_, call.func == "ustack", call.type.stack_type); // Kernel stacks should not be differentiated by tid, since the kernel // address space is the same between pids (and when aggregating you *want* // to be able to correlate between pids in most cases). User-space stacks // are special because of ASLR and so we do usym()-style packing. if (call.func == "ustack") { // pack uint64_t with: (uint32_t)stack_id, (uint32_t)pid Value *pidhigh = b_.CreateShl(b_.CreateGetPidTgid(), 32); stackid = b_.CreateOr(stackid, pidhigh); } expr_ = stackid; } else if (call.func == "signal") { // int bpf_send_signal(u32 sig) auto &arg = *call.vargs->at(0); if (arg.type.type == Type::string) { auto signame = static_cast(arg).str; int sigid = signal_name_to_num(signame); // Should be caught in semantic analyser if (sigid < 1) { std::cerr << "BUG: Invalid signal ID for \"" << signame << "\""; abort(); } b_.CreateSignal(b_.getInt32(sigid)); return; } arg.accept(*this); if (arg.is_literal) { b_.CreateSignal(b_.getInt32(static_cast(arg).n)); } else { expr_ = b_.CreateIntCast(expr_, b_.getInt32Ty(), arg.type.is_signed); b_.CreateSignal(expr_); } } else if (call.func == "strncmp") { uint64_t size = static_cast(call.vargs->at(2))->n; const auto& left_arg = call.vargs->at(0); const auto& right_arg = call.vargs->at(1); // If one of the strings is fixed, we can avoid storing the // literal in memory by calling a different function. if (right_arg->is_literal) { left_arg->accept(*this); Value *left_string = expr_; const auto& string_literal = static_cast(right_arg)->str; expr_ = b_.CreateStrncmp(left_string, string_literal, size, false); if (!left_arg->is_variable && dyn_cast(left_string)) b_.CreateLifetimeEnd(left_string); } else if (left_arg->is_literal) { right_arg->accept(*this); Value *right_string = expr_; const auto& string_literal = static_cast(left_arg)->str; expr_ = b_.CreateStrncmp(right_string, string_literal, size, false); if (!right_arg->is_variable && dyn_cast(right_string)) b_.CreateLifetimeEnd(right_string); } else { right_arg->accept(*this); Value *right_string = expr_; left_arg->accept(*this); Value *left_string = expr_; expr_ = b_.CreateStrncmp(left_string, right_string, size, false); if (!left_arg->is_variable && dyn_cast(left_string)) b_.CreateLifetimeEnd(left_string); if (!right_arg->is_variable && dyn_cast(right_string)) b_.CreateLifetimeEnd(right_string); } } else if (call.func == "override") { // int bpf_override(struct pt_regs *regs, u64 rc) // returns: 0 auto &arg = *call.vargs->at(0); arg.accept(*this); expr_ = b_.CreateIntCast(expr_, b_.getInt64Ty(), arg.type.is_signed); b_.CreateOverrideReturn(ctx_, expr_); } else { std::cerr << "missing codegen for function \"" << call.func << "\"" << std::endl; abort(); } } void CodegenLLVM::visit(Map &map) { AllocaInst *key = getMapKey(map); expr_ = b_.CreateMapLookupElem(map, key); b_.CreateLifetimeEnd(key); } void CodegenLLVM::visit(Variable &var) { if (!var.type.IsArray()) { expr_ = b_.CreateLoad(variables_[var.ident]); } else { expr_ = variables_[var.ident]; } } void CodegenLLVM::visit(Binop &binop) { // Handle && and || separately so short circuiting works if (binop.op == bpftrace::Parser::token::LAND) { expr_ = createLogicalAnd(binop); return; } else if (binop.op == bpftrace::Parser::token::LOR) { expr_ = createLogicalOr(binop); return; } Type &type = binop.left->type.type; if (type == Type::string) { if (binop.op != bpftrace::Parser::token::EQ && binop.op != bpftrace::Parser::token::NE) { std::cerr << "missing codegen to string operator \"" << opstr(binop) << "\"" << std::endl; abort(); } std::string string_literal(""); // strcmp returns 0 when strings are equal bool inverse = binop.op == bpftrace::Parser::token::EQ; // If one of the strings is fixed, we can avoid storing the // literal in memory by calling a different function. if (binop.right->is_literal) { binop.left->accept(*this); string_literal = static_cast(binop.right)->str; expr_ = b_.CreateStrcmp(expr_, string_literal, inverse); } else if (binop.left->is_literal) { binop.right->accept(*this); string_literal = static_cast(binop.left)->str; expr_ = b_.CreateStrcmp(expr_, string_literal, inverse); } else { binop.right->accept(*this); Value * right_string = expr_; binop.left->accept(*this); Value * left_string = expr_; size_t len = std::min(binop.left->type.size, binop.right->type.size); expr_ = b_.CreateStrncmp(left_string, right_string, len + 1, inverse); } } else { Value *lhs, *rhs; binop.left->accept(*this); lhs = expr_; binop.right->accept(*this); rhs = expr_; bool lsign = binop.left->type.is_signed; bool rsign = binop.right->type.is_signed; bool do_signed = lsign && rsign; // promote int to 64-bit lhs = b_.CreateIntCast(lhs, b_.getInt64Ty(), lsign); rhs = b_.CreateIntCast(rhs, b_.getInt64Ty(), rsign); switch (binop.op) { case bpftrace::Parser::token::EQ: expr_ = b_.CreateICmpEQ (lhs, rhs); break; case bpftrace::Parser::token::NE: expr_ = b_.CreateICmpNE (lhs, rhs); break; case bpftrace::Parser::token::LE: { expr_ = do_signed ? b_.CreateICmpSLE(lhs, rhs) : b_.CreateICmpULE(lhs, rhs); break; } case bpftrace::Parser::token::GE: { expr_ = do_signed ? b_.CreateICmpSGE(lhs, rhs) : b_.CreateICmpUGE(lhs, rhs); break; } case bpftrace::Parser::token::LT: { expr_ = do_signed ? b_.CreateICmpSLT(lhs, rhs) : b_.CreateICmpULT(lhs, rhs); break; } case bpftrace::Parser::token::GT: { expr_ = do_signed ? b_.CreateICmpSGT(lhs, rhs) : b_.CreateICmpUGT(lhs, rhs); break; } case bpftrace::Parser::token::LEFT: expr_ = b_.CreateShl (lhs, rhs); break; case bpftrace::Parser::token::RIGHT: expr_ = b_.CreateLShr (lhs, rhs); break; case bpftrace::Parser::token::PLUS: expr_ = b_.CreateAdd (lhs, rhs); break; case bpftrace::Parser::token::MINUS: expr_ = b_.CreateSub (lhs, rhs); break; case bpftrace::Parser::token::MUL: expr_ = b_.CreateMul (lhs, rhs); break; case bpftrace::Parser::token::DIV: expr_ = b_.CreateUDiv (lhs, rhs); break; case bpftrace::Parser::token::MOD: { // Always do an unsigned modulo operation here even if `do_signed` // is true. bpf instruction set does not support signed division. // We already warn in the semantic analyser that signed modulo can // lead to undefined behavior (because we will treat it as unsigned). expr_ = b_.CreateURem(lhs, rhs); break; } case bpftrace::Parser::token::BAND: expr_ = b_.CreateAnd (lhs, rhs); break; case bpftrace::Parser::token::BOR: expr_ = b_.CreateOr (lhs, rhs); break; case bpftrace::Parser::token::BXOR: expr_ = b_.CreateXor (lhs, rhs); break; case bpftrace::Parser::token::LAND: case bpftrace::Parser::token::LOR: std::cerr << "\"" << opstr(binop) << "\" was handled earlier" << std::endl; abort(); default: std::cerr << "missing codegen (LLVM) to string operator \"" << opstr(binop) << "\"" << std::endl; abort(); } } // Using signed extension will result in -1 which will likely confuse users expr_ = b_.CreateIntCast(expr_, b_.getInt64Ty(), false); } static bool unop_skip_accept(Unop &unop) { if (unop.expr->type.type == Type::integer) { if (unop.op == bpftrace::Parser::token::INCREMENT || unop.op == bpftrace::Parser::token::DECREMENT) return unop.expr->is_map || unop.expr->is_variable; } return false; } void CodegenLLVM::visit(Unop &unop) { if (!unop_skip_accept(unop)) unop.expr->accept(*this); SizedType &type = unop.expr->type; if (type.type == Type::integer) { switch (unop.op) { case bpftrace::Parser::token::LNOT: { Value* zero_value = Constant::getNullValue(expr_->getType()); expr_ = b_.CreateICmpEQ(expr_, zero_value); } break; case bpftrace::Parser::token::BNOT: expr_ = b_.CreateNot(expr_); break; case bpftrace::Parser::token::MINUS: expr_ = b_.CreateNeg(expr_); break; case bpftrace::Parser::token::INCREMENT: case bpftrace::Parser::token::DECREMENT: { bool is_increment = unop.op == bpftrace::Parser::token::INCREMENT; if (unop.expr->is_map) { Map &map = static_cast(*unop.expr); AllocaInst *key = getMapKey(map); Value *oldval = b_.CreateMapLookupElem(map, key); AllocaInst *newval = b_.CreateAllocaBPF(map.type, map.ident + "_newval"); if (is_increment) b_.CreateStore(b_.CreateAdd(oldval, b_.getInt64(1)), newval); else b_.CreateStore(b_.CreateSub(oldval, b_.getInt64(1)), newval); b_.CreateMapUpdateElem(map, key, newval); b_.CreateLifetimeEnd(key); if (unop.is_post_op) expr_ = oldval; else expr_ = b_.CreateLoad(newval); b_.CreateLifetimeEnd(newval); } else if (unop.expr->is_variable) { Variable &var = static_cast(*unop.expr); Value *oldval = b_.CreateLoad(variables_[var.ident]); Value *newval; if (is_increment) newval = b_.CreateAdd(oldval, b_.getInt64(1)); else newval = b_.CreateSub(oldval, b_.getInt64(1)); b_.CreateStore(newval, variables_[var.ident]); if (unop.is_post_op) expr_ = oldval; else expr_ = newval; } else { std::cerr << "invalid expression passed to " << opstr(unop) << std::endl; abort(); } break; } case bpftrace::Parser::token::MUL: { int size = type.size; if (type.is_pointer) { // When dereferencing a 32-bit integer, only read in 32-bits, etc. size = type.pointee_size; } AllocaInst *dst = b_.CreateAllocaBPF(SizedType(type.type, size), "deref"); b_.CreateProbeRead(dst, size, expr_); expr_ = b_.CreateLoad(dst); b_.CreateLifetimeEnd(dst); break; } default: std::cerr << "missing codegen for unary operator " << opstr(unop) << std::endl; abort(); } } else if (type.type == Type::cast) { switch (unop.op) { case bpftrace::Parser::token::MUL: { if (type.is_pointer && unop.type.type == Type::integer) { int size = unop.type.size; AllocaInst *dst = b_.CreateAllocaBPF(unop.type, "deref"); b_.CreateProbeRead(dst, size, expr_); expr_ = b_.CreateLoad(dst); b_.CreateLifetimeEnd(dst); } break; } default: ; // Do nothing } } else { std::cerr << "invalid type (" << type << ") passed to unary operator \"" << opstr(unop) << "\"" << std::endl; abort(); } } void CodegenLLVM::visit(Ternary &ternary) { Function *parent = b_.GetInsertBlock()->getParent(); BasicBlock *left_block = BasicBlock::Create(module_->getContext(), "left", parent); BasicBlock *right_block = BasicBlock::Create(module_->getContext(), "right", parent); BasicBlock *done = BasicBlock::Create(module_->getContext(), "done", parent); // ordering of all the following statements is important Value *result = b_.CreateAllocaBPF(ternary.type, "result"); AllocaInst *buf = b_.CreateAllocaBPF(ternary.type, "buf"); Value *cond; ternary.cond->accept(*this); cond = expr_; b_.CreateCondBr(b_.CreateICmpNE(cond, b_.getInt64(0), "true_cond"), left_block, right_block); if (ternary.type.type == Type::integer) { // fetch selected integer via CreateStore b_.SetInsertPoint(left_block); ternary.left->accept(*this); b_.CreateStore(expr_, result); b_.CreateBr(done); b_.SetInsertPoint(right_block); ternary.right->accept(*this); b_.CreateStore(expr_, result); b_.CreateBr(done); b_.SetInsertPoint(done); expr_ = b_.CreateLoad(result); } else { // copy selected string via CreateMemCpy b_.SetInsertPoint(left_block); ternary.left->accept(*this); b_.CREATE_MEMCPY(buf, expr_, ternary.type.size, 1); if (!ternary.left->is_variable && dyn_cast(expr_)) b_.CreateLifetimeEnd(expr_); b_.CreateBr(done); b_.SetInsertPoint(right_block); ternary.right->accept(*this); b_.CREATE_MEMCPY(buf, expr_, ternary.type.size, 1); if (!ternary.right->is_variable && dyn_cast(expr_)) b_.CreateLifetimeEnd(expr_); b_.CreateBr(done); b_.SetInsertPoint(done); expr_ = buf; } } void CodegenLLVM::visit(FieldAccess &acc) { SizedType &type = acc.expr->type; assert(type.type == Type::cast); acc.expr->accept(*this); std::string cast_type = type.is_tparg ? tracepoint_struct_ : type.cast_type; Struct &cstruct = bpftrace_.structs_[cast_type]; type = SizedType(type); type.size = cstruct.size; type.cast_type = cast_type; auto &field = cstruct.fields[acc.field]; if (type.is_internal) { // The struct we are reading from has already been pulled into // BPF-memory, e.g. by being stored in a map. // Just read from the correct offset of expr_ Value *src = b_.CreateGEP(expr_, {b_.getInt64(0), b_.getInt64(field.offset)}); if (field.type.type == Type::cast) { // TODO This should be do-able without allocating more memory here AllocaInst *dst = b_.CreateAllocaBPF(field.type, "internal_" + type.cast_type + "." + acc.field); b_.CREATE_MEMCPY(dst, src, field.type.size, 1); expr_ = dst; // TODO clean up dst memory? } else if (field.type.type == Type::string) { expr_ = src; } else { expr_ = b_.CreateLoad(b_.GetType(field.type), src); } } else { // The struct we are reading from has not been pulled into BPF-memory, // so expr_ will contain an external pointer to the start of the struct Value *src = b_.CreateAdd(expr_, b_.getInt64(field.offset)); if (field.type.type == Type::cast && !field.type.is_pointer) { // struct X // { // struct Y y; // }; // // We are trying to access an embedded struct, e.g. "x.y" // // Instead of copying the entire struct Y in, we'll just store it as a // pointer internally and dereference later when necessary. expr_ = src; } else if (field.type.type == Type::array) { // For array types, we want to just pass pointer along, // since the offset of the field should be the start of the array. // The pointer will be dereferenced when the array is accessed by a [] // operation expr_ = src; } else if (field.type.type == Type::string) { AllocaInst *dst = b_.CreateAllocaBPF(field.type, type.cast_type + "." + acc.field); b_.CreateProbeRead(dst, field.type.size, src); expr_ = dst; } else if (field.type.type == Type::integer && field.is_bitfield) { AllocaInst *dst = b_.CreateAllocaBPF(field.type, type.cast_type + "." + acc.field); // memset so verifier doesn't complain about reading uninitialized stack b_.CreateMemSet(dst, b_.getInt8(0), field.type.size, 1); b_.CreateProbeRead(dst, field.bitfield.read_bytes, src); Value *raw = b_.CreateLoad(dst); Value *shifted = b_.CreateLShr(raw, field.bitfield.access_rshift); Value *masked = b_.CreateAnd(shifted, field.bitfield.mask); expr_ = masked; b_.CreateLifetimeEnd(dst); } else { AllocaInst *dst = b_.CreateAllocaBPF(field.type, type.cast_type + "." + acc.field); b_.CreateProbeRead(dst, field.type.size, src); expr_ = b_.CreateLoad(dst); b_.CreateLifetimeEnd(dst); } } } void CodegenLLVM::visit(ArrayAccess &arr) { Value *array, *index, *offset; SizedType &type = arr.expr->type; arr.expr->accept(*this); array = expr_; arr.indexpr->accept(*this); // promote int to 64-bit index = b_.CreateIntCast(expr_, b_.getInt64Ty(), arr.expr->type.is_signed); offset = b_.CreateMul(index, b_.getInt64(type.pointee_size)); AllocaInst *dst = b_.CreateAllocaBPF(SizedType(Type::integer, type.pointee_size), "array_access"); Value *src = b_.CreateAdd(array, offset); b_.CreateProbeRead(dst, type.pointee_size, src); expr_ = b_.CreateLoad(dst); b_.CreateLifetimeEnd(dst); } void CodegenLLVM::visit(Cast &cast) { cast.expr->accept(*this); if (cast.type.type == Type::integer) { expr_ = b_.CreateIntCast(expr_, b_.getIntNTy(8 * cast.type.size), cast.type.is_signed, "cast"); } } void CodegenLLVM::visit(ExprStatement &expr) { expr.expr->accept(*this); } void CodegenLLVM::visit(AssignMapStatement &assignment) { Map &map = *assignment.map; assignment.expr->accept(*this); if (!expr_) // Some functions do the assignments themselves return; Value *val, *expr; expr = expr_; AllocaInst *key = getMapKey(map); if (map.type.type == Type::string || assignment.expr->type.type == Type::inet) { val = expr; } else if (map.type.type == Type::cast) { if (assignment.expr->type.is_internal) { val = expr; } else if (assignment.expr->type.is_pointer) { // expr currently contains a pointer to the struct // and that's what we are saving AllocaInst *dst = b_.CreateAllocaBPF(map.type, map.ident + "_ptr"); b_.CreateStore(expr, dst); val = dst; } else { // expr currently contains a pointer to the struct // We now want to read the entire struct in so we can save it AllocaInst *dst = b_.CreateAllocaBPF(map.type, map.ident + "_val"); b_.CreateProbeRead(dst, map.type.size, expr); val = dst; } } else { if (map.type.type == Type::integer) { // Integers are always stored as 64-bit in map values expr = b_.CreateIntCast(expr, b_.getInt64Ty(), map.type.is_signed); } val = b_.CreateAllocaBPF(map.type, map.ident + "_val"); b_.CreateStore(expr, val); } b_.CreateMapUpdateElem(map, key, val); b_.CreateLifetimeEnd(key); if (!assignment.expr->is_variable) b_.CreateLifetimeEnd(val); } void CodegenLLVM::visit(AssignVarStatement &assignment) { Variable &var = *assignment.var; assignment.expr->accept(*this); if (variables_.find(var.ident) == variables_.end()) { AllocaInst *val = b_.CreateAllocaBPFInit(var.type, var.ident); variables_[var.ident] = val; } if (!var.type.IsArray()) { b_.CreateStore(expr_, variables_[var.ident]); } else { b_.CREATE_MEMCPY(variables_[var.ident], expr_, var.type.size, 1); if (!assignment.expr->is_variable && dyn_cast(expr_)) b_.CreateLifetimeEnd(expr_); } } void CodegenLLVM::visit(If &if_block) { Function *parent = b_.GetInsertBlock()->getParent(); BasicBlock *if_true = BasicBlock::Create(module_->getContext(), "if_stmt", parent); BasicBlock *if_false = BasicBlock::Create(module_->getContext(), "else_stmt", parent); if_block.cond->accept(*this); Value *cond = expr_; b_.CreateCondBr(b_.CreateICmpNE(cond, b_.getInt64(0), "true_cond"), if_true, if_false); b_.SetInsertPoint(if_true); for (Statement *stmt : *if_block.stmts) { stmt->accept(*this); } if (if_block.else_stmts) { BasicBlock *done = BasicBlock::Create(module_->getContext(), "done", parent); b_.CreateBr(done); b_.SetInsertPoint(if_false); for (Statement *stmt : *if_block.else_stmts) { stmt->accept(*this); } b_.CreateBr(done); b_.SetInsertPoint(done); } else { b_.CreateBr(if_false); b_.SetInsertPoint(if_false); } } void CodegenLLVM::visit(Unroll &unroll) { for (int i=0; i < unroll.var; i++) { for (Statement *stmt : *unroll.stmts) { stmt->accept(*this); } } } void CodegenLLVM::visit(Predicate &pred) { Function *parent = b_.GetInsertBlock()->getParent(); BasicBlock *pred_false_block = BasicBlock::Create( module_->getContext(), "pred_false", parent); BasicBlock *pred_true_block = BasicBlock::Create( module_->getContext(), "pred_true", parent); pred.expr->accept(*this); // allow unop casts in predicates: expr_ = b_.CreateIntCast(expr_, b_.getInt64Ty(), false); expr_ = b_.CreateICmpEQ(expr_, b_.getInt64(0), "predcond"); b_.CreateCondBr(expr_, pred_false_block, pred_true_block); b_.SetInsertPoint(pred_false_block); b_.CreateRet(ConstantInt::get(module_->getContext(), APInt(64, 0))); b_.SetInsertPoint(pred_true_block); } void CodegenLLVM::visit(AttachPoint &) { // Empty } void CodegenLLVM::visit(Probe &probe) { FunctionType *func_type = FunctionType::get( b_.getInt64Ty(), {b_.getInt8PtrTy()}, // struct pt_regs *ctx false); // Probe has at least one attach point (required by the parser) auto &attach_point = (*probe.attach_points)[0]; // All usdt probes need expansion to be able to read arguments if (probetype(attach_point->provider) == ProbeType::usdt) probe.need_expansion = true; current_attach_point_ = attach_point; /* * Most of the time, we can take a probe like kprobe:do_f* and build a * single BPF program for that, called "s_kprobe:do_f*", and attach it to * each wildcard match. An exception is the "probe" builtin, where we need * to build different BPF programs for each wildcard match that cantains an * ID for the match. Those programs will be called "s_kprobe:do_fcntl" etc. */ if (probe.need_expansion == false) { // build a single BPF program pre-wildcards Function *func = Function::Create(func_type, Function::ExternalLinkage, probe.name(), module_.get()); probe.set_index(getNextIndexForProbe(probe.name())); func->setSection(getSectionNameForProbe(probe.name(), probe.index())); BasicBlock *entry = BasicBlock::Create(module_->getContext(), "entry", func); b_.SetInsertPoint(entry); ctx_ = func->arg_begin(); if (probe.pred) { probe.pred->accept(*this); } variables_.clear(); for (Statement *stmt : *probe.stmts) { stmt->accept(*this); } b_.CreateRet(ConstantInt::get(module_->getContext(), APInt(64, 0))); } else { /* * Build a separate BPF program for each wildcard match. * We begin by saving state that gets changed by the codegen pass, so we * can restore it for the next pass (printf_id_, time_id_). */ int starting_printf_id = printf_id_; int starting_cat_id = cat_id_; int starting_system_id = system_id_; int starting_time_id = time_id_; int starting_join_id = join_id_; for (auto attach_point : *probe.attach_points) { current_attach_point_ = attach_point; std::set matches; if (attach_point->provider == "BEGIN" || attach_point->provider == "END") { matches.insert(attach_point->provider); } else { matches = bpftrace_.find_wildcard_matches(*attach_point); } tracepoint_struct_ = ""; for (auto &match_ : matches) { printf_id_ = starting_printf_id; cat_id_ = starting_cat_id; system_id_ = starting_system_id; time_id_ = starting_time_id; join_id_ = starting_join_id; std::string full_func_id = match_; // USDT probes must specify both a provider and a function name // So we will extract out the provider namespace to get just the function name if (probetype(attach_point->provider) == ProbeType::usdt) { std::string func_id = match_; std::string orig_ns = attach_point->ns; std::string ns = func_id.substr(0, func_id.find(":")); func_id.erase(0, func_id.find(":")+1); // Ensure that the full probe name used is the resolved one for this probe, attach_point->ns = ns; probefull_ = attach_point->name(func_id); // But propagate the originally specified namespace in case it has a wildcard, attach_point->ns = orig_ns; // Set the probe identifier so that we can read arguments later attach_point->usdt = USDTHelper::find(bpftrace_.pid_, attach_point->target, ns, func_id); } else if (attach_point->provider == "BEGIN" || attach_point->provider == "END") { probefull_ = attach_point->provider; } else { probefull_ = attach_point->name(full_func_id); } // tracepoint wildcard expansion, part 3 of 3. Set tracepoint_struct_ for use by args builtin. if (probetype(attach_point->provider) == ProbeType::tracepoint) tracepoint_struct_ = TracepointFormatParser::get_struct_name(attach_point->target, full_func_id); int index = getNextIndexForProbe(probe.name()); attach_point->set_index(full_func_id, index); Function *func = Function::Create(func_type, Function::ExternalLinkage, probefull_, module_.get()); func->setSection(getSectionNameForProbe(probefull_, index)); BasicBlock *entry = BasicBlock::Create(module_->getContext(), "entry", func); b_.SetInsertPoint(entry); // check: do the following 8 lines need to be in the wildcard loop? ctx_ = func->arg_begin(); if (probe.pred) { probe.pred->accept(*this); } variables_.clear(); for (Statement *stmt : *probe.stmts) { stmt->accept(*this); } b_.CreateRet(ConstantInt::get(module_->getContext(), APInt(64, 0))); } } } bpftrace_.add_probe(probe); current_attach_point_ = nullptr; } void CodegenLLVM::visit(Program &program) { for (Probe *probe : *program.probes) probe->accept(*this); } int CodegenLLVM::getNextIndexForProbe(const std::string &probe_name) { if (next_probe_index_.count(probe_name) == 0) next_probe_index_[probe_name] = 1; int index = next_probe_index_[probe_name]; next_probe_index_[probe_name] += 1; return index; } std::string CodegenLLVM::getSectionNameForProbe(const std::string &probe_name, int index) { return "s_" + probe_name + "_" + std::to_string(index); } AllocaInst *CodegenLLVM::getMapKey(Map &map) { AllocaInst *key; if (map.vargs) { // A single value as a map key (e.g., @[comm] = 0;) if (map.vargs->size() == 1) { Expression *expr = map.vargs->at(0); expr->accept(*this); if (expr->type.type == Type::string || expr->type.type == Type::usym || expr->type.type == Type::inet) { // The value is already in the stack; Skip copy key = dyn_cast(expr_); } else { key = b_.CreateAllocaBPF(expr->type.size, map.ident + "_key"); b_.CreateStore( b_.CreateIntCast(expr_, b_.getInt64Ty(), expr->type.is_signed), key); } } else { // Two or more values as a map key (e.g, @[comm, pid] = 1;) size_t size = 0; for (Expression *expr : *map.vargs) { size += expr->type.size; } key = b_.CreateAllocaBPF(size, map.ident + "_key"); int offset = 0; // Construct a map key in the stack for (Expression *expr : *map.vargs) { expr->accept(*this); Value *offset_val = b_.CreateGEP(key, { b_.getInt64(0), b_.getInt64(offset) }); if (expr->type.type == Type::string || expr->type.type == Type::usym || expr->type.type == Type::inet) { b_.CREATE_MEMCPY(offset_val, expr_, expr->type.size, 1); if (!expr->is_variable && dyn_cast(expr_)) b_.CreateLifetimeEnd(expr_); } else { // promote map key to 64-bit: b_.CreateStore( b_.CreateIntCast(expr_, b_.getInt64Ty(), expr->type.is_signed), offset_val); } offset += expr->type.size; } } } else { // No map key (e.g., @ = 1;). Use 0 as a key. key = b_.CreateAllocaBPF(SizedType(Type::integer, 8), map.ident + "_key"); b_.CreateStore(b_.getInt64(0), key); } return key; } AllocaInst *CodegenLLVM::getHistMapKey(Map &map, Value *log2) { AllocaInst *key; if (map.vargs) { size_t size = 8; // Extra space for the bucket value for (Expression *expr : *map.vargs) { size += expr->type.size; } key = b_.CreateAllocaBPF(size, map.ident + "_key"); int offset = 0; for (Expression *expr : *map.vargs) { expr->accept(*this); Value *offset_val = b_.CreateGEP(key, {b_.getInt64(0), b_.getInt64(offset)}); if (expr->type.type == Type::string || expr->type.type == Type::usym || expr->type.type == Type::inet) { b_.CREATE_MEMCPY(offset_val, expr_, expr->type.size, 1); if (!expr->is_variable && dyn_cast(expr_)) b_.CreateLifetimeEnd(expr_); } else b_.CreateStore(expr_, offset_val); offset += expr->type.size; } Value *offset_val = b_.CreateGEP(key, {b_.getInt64(0), b_.getInt64(offset)}); b_.CreateStore(log2, offset_val); } else { key = b_.CreateAllocaBPF(SizedType(Type::integer, 8), map.ident + "_key"); b_.CreateStore(log2, key); } return key; } Value *CodegenLLVM::createLogicalAnd(Binop &binop) { assert(binop.left->type.type == Type::integer); assert(binop.right->type.type == Type::integer); Function *parent = b_.GetInsertBlock()->getParent(); BasicBlock *lhs_true_block = BasicBlock::Create(module_->getContext(), "&&_lhs_true", parent); BasicBlock *true_block = BasicBlock::Create(module_->getContext(), "&&_true", parent); BasicBlock *false_block = BasicBlock::Create(module_->getContext(), "&&_false", parent); BasicBlock *merge_block = BasicBlock::Create(module_->getContext(), "&&_merge", parent); Value *result = b_.CreateAllocaBPF(b_.getInt64Ty(), "&&_result"); Value *lhs; binop.left->accept(*this); lhs = expr_; b_.CreateCondBr(b_.CreateICmpNE(lhs, b_.GetIntSameSize(0, lhs), "lhs_true_cond"), lhs_true_block, false_block); b_.SetInsertPoint(lhs_true_block); Value *rhs; binop.right->accept(*this); rhs = expr_; b_.CreateCondBr(b_.CreateICmpNE(rhs, b_.GetIntSameSize(0, rhs), "rhs_true_cond"), true_block, false_block); b_.SetInsertPoint(true_block); b_.CreateStore(b_.getInt64(1), result); b_.CreateBr(merge_block); b_.SetInsertPoint(false_block); b_.CreateStore(b_.getInt64(0), result); b_.CreateBr(merge_block); b_.SetInsertPoint(merge_block); return b_.CreateLoad(result); } Value *CodegenLLVM::createLogicalOr(Binop &binop) { assert(binop.left->type.type == Type::integer); assert(binop.right->type.type == Type::integer); Function *parent = b_.GetInsertBlock()->getParent(); BasicBlock *lhs_false_block = BasicBlock::Create(module_->getContext(), "||_lhs_false", parent); BasicBlock *false_block = BasicBlock::Create(module_->getContext(), "||_false", parent); BasicBlock *true_block = BasicBlock::Create(module_->getContext(), "||_true", parent); BasicBlock *merge_block = BasicBlock::Create(module_->getContext(), "||_merge", parent); Value *result = b_.CreateAllocaBPF(b_.getInt64Ty(), "||_result"); Value *lhs; binop.left->accept(*this); lhs = expr_; b_.CreateCondBr(b_.CreateICmpNE(lhs, b_.GetIntSameSize(0, lhs), "lhs_true_cond"), true_block, lhs_false_block); b_.SetInsertPoint(lhs_false_block); Value *rhs; binop.right->accept(*this); rhs = expr_; b_.CreateCondBr(b_.CreateICmpNE(rhs, b_.GetIntSameSize(0, rhs), "rhs_true_cond"), true_block, false_block); b_.SetInsertPoint(false_block); b_.CreateStore(b_.getInt64(0), result); b_.CreateBr(merge_block); b_.SetInsertPoint(true_block); b_.CreateStore(b_.getInt64(1), result); b_.CreateBr(merge_block); b_.SetInsertPoint(merge_block); return b_.CreateLoad(result); } void CodegenLLVM::createLog2Function() { // log2() returns a bucket index for the given value. Index 0 is for // values less than 0, index 1 is for 0, and indexes 2 onwards is the // power-of-2 histogram index. // // log2(int n) // { // int result = 0; // int shift; // if (n < 0) return result; // result++; // if (n == 0) return result; // result++; // for (int i = 4; i >= 0; i--) // { // shift = (v >= (1<<(1<> = shift; // result += shift; // } // return result; // } FunctionType *log2_func_type = FunctionType::get(b_.getInt64Ty(), {b_.getInt64Ty()}, false); Function *log2_func = Function::Create(log2_func_type, Function::InternalLinkage, "log2", module_.get()); log2_func->addFnAttr(Attribute::AlwaysInline); log2_func->setSection("helpers"); BasicBlock *entry = BasicBlock::Create(module_->getContext(), "entry", log2_func); b_.SetInsertPoint(entry); // setup n and result registers Value *arg = log2_func->arg_begin(); Value *n_alloc = b_.CreateAllocaBPF(SizedType(Type::integer, 8)); b_.CreateStore(arg, n_alloc); Value *result = b_.CreateAllocaBPF(SizedType(Type::integer, 8)); b_.CreateStore(b_.getInt64(0), result); // test for less than zero BasicBlock *is_less_than_zero = BasicBlock::Create(module_->getContext(), "hist.is_less_than_zero", log2_func); BasicBlock *is_not_less_than_zero = BasicBlock::Create(module_->getContext(), "hist.is_not_less_than_zero", log2_func); b_.CreateCondBr(b_.CreateICmpSLT(b_.CreateLoad(n_alloc), b_.getInt64(0)), is_less_than_zero, is_not_less_than_zero); b_.SetInsertPoint(is_less_than_zero); b_.CreateRet(b_.CreateLoad(result)); b_.SetInsertPoint(is_not_less_than_zero); // test for equal to zero BasicBlock *is_zero = BasicBlock::Create(module_->getContext(), "hist.is_zero", log2_func); BasicBlock *is_not_zero = BasicBlock::Create(module_->getContext(), "hist.is_not_zero", log2_func); b_.CreateCondBr(b_.CreateICmpEQ(b_.CreateLoad(n_alloc), b_.getInt64(0)), is_zero, is_not_zero); b_.SetInsertPoint(is_zero); b_.CreateStore(b_.getInt64(1), result); b_.CreateRet(b_.CreateLoad(result)); b_.SetInsertPoint(is_not_zero); // power-of-2 index, offset by +2 b_.CreateStore(b_.getInt64(2), result); for (int i = 4; i >= 0; i--) { Value *n = b_.CreateLoad(n_alloc); Value *shift = b_.CreateShl(b_.CreateIntCast(b_.CreateICmpSGE(b_.CreateIntCast(n, b_.getInt64Ty(), false), b_.getInt64(1 << (1< max) // return 1 + (max - min) / step; // result = 1 + (value - min) / step; // // return result; // } // inlined function initialization FunctionType *linear_func_type = FunctionType::get(b_.getInt64Ty(), {b_.getInt64Ty(), b_.getInt64Ty(), b_.getInt64Ty(), b_.getInt64Ty()}, false); Function *linear_func = Function::Create(linear_func_type, Function::InternalLinkage, "linear", module_.get()); linear_func->addFnAttr(Attribute::AlwaysInline); linear_func->setSection("helpers"); BasicBlock *entry = BasicBlock::Create(module_->getContext(), "entry", linear_func); b_.SetInsertPoint(entry); // pull in arguments Value *value_alloc = b_.CreateAllocaBPF(SizedType(Type::integer, 8)); Value *min_alloc = b_.CreateAllocaBPF(SizedType(Type::integer, 8)); Value *max_alloc = b_.CreateAllocaBPF(SizedType(Type::integer, 8)); Value *step_alloc = b_.CreateAllocaBPF(SizedType(Type::integer, 8)); Value *result_alloc = b_.CreateAllocaBPF(SizedType(Type::integer, 8)); Value *value = linear_func->arg_begin()+0; Value *min = linear_func->arg_begin()+1; Value *max = linear_func->arg_begin()+2; Value *step = linear_func->arg_begin()+3; b_.CreateStore(value, value_alloc); b_.CreateStore(min, min_alloc); b_.CreateStore(max, max_alloc); b_.CreateStore(step, step_alloc); // algorithm Value *cmp = b_.CreateICmpSLT(b_.CreateLoad(value_alloc), b_.CreateLoad(min_alloc)); BasicBlock *lt_min = BasicBlock::Create(module_->getContext(), "lhist.lt_min", linear_func); BasicBlock *ge_min = BasicBlock::Create(module_->getContext(), "lhist.ge_min", linear_func); b_.CreateCondBr(cmp, lt_min, ge_min); b_.SetInsertPoint(lt_min); b_.CreateRet(b_.getInt64(0)); b_.SetInsertPoint(ge_min); Value *cmp1 = b_.CreateICmpSGT(b_.CreateLoad(value_alloc), b_.CreateLoad(max_alloc)); BasicBlock *le_max = BasicBlock::Create(module_->getContext(), "lhist.le_max", linear_func); BasicBlock *gt_max = BasicBlock::Create(module_->getContext(), "lhist.gt_max", linear_func); b_.CreateCondBr(cmp1, gt_max, le_max); b_.SetInsertPoint(gt_max); Value *div = b_.CreateUDiv(b_.CreateSub(b_.CreateLoad(max_alloc), b_.CreateLoad(min_alloc)), b_.CreateLoad(step_alloc)); b_.CreateStore(b_.CreateAdd(div, b_.getInt64(1)), result_alloc); b_.CreateRet(b_.CreateLoad(result_alloc)); b_.SetInsertPoint(le_max); Value *div3 = b_.CreateUDiv(b_.CreateSub(b_.CreateLoad(value_alloc), b_.CreateLoad(min_alloc)), b_.CreateLoad(step_alloc)); b_.CreateStore(b_.CreateAdd(div3, b_.getInt64(1)), result_alloc); b_.CreateRet(b_.CreateLoad(result_alloc)); } void CodegenLLVM::createFormatStringCall(Call &call, int &id, CallArgs &call_args, const std::string &call_name, AsyncAction async_action) { /* * perf event output has: uint64_t id, vargs * The id maps to bpftrace_.*_args_, and is a way to define the * types and offsets of each of the arguments, and share that between BPF and * user-space for printing. */ std::vector elements = { b_.getInt64Ty() }; // ID auto &args = std::get<1>(call_args.at(id)); for (Field &arg : args) { llvm::Type *ty = b_.GetType(arg.type); elements.push_back(ty); } StructType *fmt_struct = StructType::create(elements, call_name + "_t", false); int struct_size = layout_.getTypeAllocSize(fmt_struct); auto *struct_layout = layout_.getStructLayout(fmt_struct); for (size_t i=0; igetElementOffset(i+1); // +1 for the id field } AllocaInst *fmt_args = b_.CreateAllocaBPF(fmt_struct, call_name + "_args"); b_.CreateMemSet(fmt_args, b_.getInt8(0), struct_size, 1); Value *id_offset = b_.CreateGEP(fmt_args, {b_.getInt32(0), b_.getInt32(0)}); b_.CreateStore(b_.getInt64(id + asyncactionint(async_action)), id_offset); for (size_t i=1; isize(); i++) { Expression &arg = *call.vargs->at(i); expr_deleter_ = nullptr; arg.accept(*this); Value *offset = b_.CreateGEP(fmt_args, {b_.getInt32(0), b_.getInt32(i)}); if (arg.type.IsArray()) { b_.CREATE_MEMCPY(offset, expr_, arg.type.size, 1); if (!arg.is_variable && dyn_cast(expr_)) b_.CreateLifetimeEnd(expr_); } else b_.CreateStore(expr_, offset); if (expr_deleter_) expr_deleter_(); } id++; b_.CreatePerfEventOutput(ctx_, fmt_args, struct_size); b_.CreateLifetimeEnd(fmt_args); expr_ = nullptr; } std::unique_ptr CodegenLLVM::compile(DebugLevel debug, std::ostream &out) { createLog2Function(); createLinearFunction(); root_->accept(*this); LLVMInitializeBPFTargetInfo(); LLVMInitializeBPFTarget(); LLVMInitializeBPFTargetMC(); LLVMInitializeBPFAsmPrinter(); std::string targetTriple = "bpf-pc-linux"; module_->setTargetTriple(targetTriple); std::string error; const Target *target = TargetRegistry::lookupTarget(targetTriple, error); if (!target) throw new std::runtime_error("Could not create LLVM target " + error); TargetOptions opt; auto RM = Reloc::Model(); TargetMachine *targetMachine = target->createTargetMachine(targetTriple, "generic", "", opt, RM); module_->setDataLayout(targetMachine->createDataLayout()); legacy::PassManager PM; PassManagerBuilder PMB; PMB.OptLevel = 3; PM.add(createFunctionInliningPass()); /* * llvm < 4.0 needs * PM.add(createAlwaysInlinerPass()); * llvm >= 4.0 needs * PM.add(createAlwaysInlinerLegacyPass()); * use below 'stable' workaround */ LLVMAddAlwaysInlinerPass(reinterpret_cast(&PM)); PMB.populateModulePassManager(PM); if (debug == DebugLevel::kFullDebug) { raw_os_ostream llvm_ostream(out); llvm_ostream << "Before optimization\n"; llvm_ostream << "-------------------\n\n"; DumpIR(llvm_ostream); } PM.run(*module_.get()); if (debug != DebugLevel::kNone) { raw_os_ostream llvm_ostream(out); if (debug == DebugLevel::kFullDebug) { llvm_ostream << "\nAfter optimization\n"; llvm_ostream << "------------------\n\n"; } DumpIR(llvm_ostream); } auto bpforc = std::make_unique(targetMachine); bpforc->compileModule(move(module_)); return bpforc; } void CodegenLLVM::DumpIR() { raw_os_ostream llvm_ostream(std::cout); DumpIR(llvm_ostream); } void CodegenLLVM::DumpIR(raw_os_ostream &out) { module_->print(out, nullptr, false, true); } } // namespace ast } // namespace bpftrace bpftrace-0.9.4/src/ast/codegen_llvm.h000066400000000000000000000057401361633214400175100ustar00rootroot00000000000000#pragma once #include #include #include "ast.h" #include "bpftrace.h" #include "irbuilderbpf.h" #include "map.h" #include #include #include #include namespace bpftrace { namespace ast { using namespace llvm; using CallArgs = std::vector>>; class CodegenLLVM : public Visitor { public: explicit CodegenLLVM(Node *root, BPFtrace &bpftrace) : root_(root), module_(std::make_unique("bpftrace", context_)), b_(context_, *module_.get(), bpftrace), layout_(module_.get()), bpftrace_(bpftrace) { } void visit(Integer &integer) override; void visit(PositionalParameter ¶m) override; void visit(String &string) override; void visit(Identifier &identifier) override; void visit(Builtin &builtin) override; void visit(StackMode &) override { }; void visit(Call &call) override; void visit(Map &map) override; void visit(Variable &var) override; void visit(Binop &binop) override; void visit(Unop &unop) override; void visit(Ternary &ternary) override; void visit(FieldAccess &acc) override; void visit(ArrayAccess &arr) override; void visit(Cast &cast) override; void visit(ExprStatement &expr) override; void visit(AssignMapStatement &assignment) override; void visit(AssignVarStatement &assignment) override; void visit(If &if_block) override; void visit(Unroll &unroll) override; void visit(Predicate &pred) override; void visit(AttachPoint &ap) override; void visit(Probe &probe) override; void visit(Program &program) override; AllocaInst *getMapKey(Map &map); AllocaInst *getHistMapKey(Map &map, Value *log2); int getNextIndexForProbe(const std::string &probe_name); std::string getSectionNameForProbe(const std::string &probe_name, int index); Value *createLogicalAnd(Binop &binop); Value *createLogicalOr(Binop &binop); void DumpIR(); void DumpIR(llvm::raw_os_ostream &out); void createLog2Function(); void createLinearFunction(); void createFormatStringCall(Call &call, int &id, CallArgs &call_args, const std::string &call_name, AsyncAction async_action); std::unique_ptr compile(DebugLevel debug=DebugLevel::kNone, std::ostream &out=std::cout); private: Node *root_; LLVMContext context_; std::unique_ptr module_; std::unique_ptr ee_; IRBuilderBPF b_; DataLayout layout_; Value *expr_ = nullptr; std::function expr_deleter_; // intentionally empty Value *ctx_; AttachPoint *current_attach_point_ = nullptr; BPFtrace &bpftrace_; std::string probefull_; std::string tracepoint_struct_; std::map next_probe_index_; std::map variables_; int printf_id_ = 0; int time_id_ = 0; int cat_id_ = 0; uint64_t join_id_ = 0; int system_id_ = 0; }; } // namespace ast } // namespace bpftrace bpftrace-0.9.4/src/ast/field_analyser.cpp000066400000000000000000000061651361633214400203700ustar00rootroot00000000000000#include #include #include "field_analyser.h" namespace bpftrace { namespace ast { void FieldAnalyser::visit(Integer &integer __attribute__((unused))) { } void FieldAnalyser::visit(PositionalParameter ¶m __attribute__((unused))) { } void FieldAnalyser::visit(String &string __attribute__((unused))) { } void FieldAnalyser::visit(StackMode &mode __attribute__((unused))) { } void FieldAnalyser::visit(Identifier &identifier __attribute__((unused))) { } void FieldAnalyser::visit(Builtin &builtin) { if (builtin.ident == "curtask") { type_ = "struct task_struct"; bpftrace_.btf_set_.insert(type_); } } void FieldAnalyser::visit(Call &call) { if (call.vargs) { for (Expression *expr : *call.vargs) { expr->accept(*this); } } } void FieldAnalyser::visit(Map &map) { MapKey key; if (map.vargs) { for (Expression *expr : *map.vargs) { expr->accept(*this); } } } void FieldAnalyser::visit(Variable &var __attribute__((unused))) { } void FieldAnalyser::visit(ArrayAccess &arr) { arr.expr->accept(*this); arr.indexpr->accept(*this); } void FieldAnalyser::visit(Binop &binop) { binop.left->accept(*this); binop.right->accept(*this); } void FieldAnalyser::visit(Unop &unop) { unop.expr->accept(*this); } void FieldAnalyser::visit(Ternary &ternary) { ternary.cond->accept(*this); ternary.left->accept(*this); ternary.right->accept(*this); } void FieldAnalyser::visit(If &if_block) { if_block.cond->accept(*this); for (Statement *stmt : *if_block.stmts) { stmt->accept(*this); } if (if_block.else_stmts) { for (Statement *stmt : *if_block.else_stmts) { stmt->accept(*this); } } } void FieldAnalyser::visit(Unroll &unroll) { for (int i=0; i < unroll.var; i++) { for (Statement *stmt : *unroll.stmts) { stmt->accept(*this); } } } void FieldAnalyser::visit(FieldAccess &acc) { acc.expr->accept(*this); if (!type_.empty()) { type_ = bpftrace_.btf_.type_of(type_, acc.field); bpftrace_.btf_set_.insert(type_); } } void FieldAnalyser::visit(Cast &cast) { cast.expr->accept(*this); type_ = cast.cast_type; assert(!type_.empty()); bpftrace_.btf_set_.insert(type_); } void FieldAnalyser::visit(ExprStatement &expr) { expr.expr->accept(*this); } void FieldAnalyser::visit(AssignMapStatement &assignment) { assignment.map->accept(*this); assignment.expr->accept(*this); } void FieldAnalyser::visit(AssignVarStatement &assignment) { assignment.expr->accept(*this); } void FieldAnalyser::visit(Predicate &pred) { pred.expr->accept(*this); } void FieldAnalyser::visit(AttachPoint &ap __attribute__((unused))) { } void FieldAnalyser::visit(Probe &probe) { for (AttachPoint *ap : *probe.attach_points) { ap->accept(*this); } if (probe.pred) { probe.pred->accept(*this); } for (Statement *stmt : *probe.stmts) { stmt->accept(*this); } } void FieldAnalyser::visit(Program &program) { for (Probe *probe : *program.probes) probe->accept(*this); } int FieldAnalyser::analyse() { if (bpftrace_.btf_.has_data()) root_->accept(*this); return 0; } } // namespace ast } // namespace bpftrace bpftrace-0.9.4/src/ast/field_analyser.h000066400000000000000000000026261361633214400200330ustar00rootroot00000000000000#pragma once #include #include #include "ast.h" #include "bpftrace.h" namespace bpftrace { namespace ast { class FieldAnalyser : public Visitor { public: explicit FieldAnalyser(Node *root, BPFtrace &bpftrace) : type_(""), root_(root), bpftrace_(bpftrace) { } void visit(Integer &integer) override; void visit(PositionalParameter ¶m) override; void visit(String &string) override; void visit(StackMode &mode) override; void visit(Identifier &identifier) override; void visit(Builtin &builtin) override; void visit(Call &call) override; void visit(Map &map) override; void visit(Variable &var) override; void visit(Binop &binop) override; void visit(Unop &unop) override; void visit(Ternary &ternary) override; void visit(FieldAccess &acc) override; void visit(ArrayAccess &arr) override; void visit(Cast &cast) override; void visit(ExprStatement &expr) override; void visit(AssignMapStatement &assignment) override; void visit(AssignVarStatement &assignment) override; void visit(If &if_block) override; void visit(Unroll &unroll) override; void visit(Predicate &pred) override; void visit(AttachPoint &ap) override; void visit(Probe &probe) override; void visit(Program &program) override; int analyse(); private: std::string type_; Node *root_; BPFtrace &bpftrace_; }; } // namespace ast } // namespace bpftrace bpftrace-0.9.4/src/ast/irbuilderbpf.cpp000066400000000000000000000552721361633214400200630ustar00rootroot00000000000000#include #include "irbuilderbpf.h" #include "bcc_usdt.h" #include "arch/arch.h" #include "utils.h" #include namespace libbpf { #undef __BPF_FUNC_MAPPER #include "libbpf/bpf.h" } // namespace libbpf namespace bpftrace { namespace ast { IRBuilderBPF::IRBuilderBPF(LLVMContext &context, Module &module, BPFtrace &bpftrace) : IRBuilder<>(context), module_(module), bpftrace_(bpftrace) { // Declare external LLVM function FunctionType *pseudo_func_type = FunctionType::get( getInt64Ty(), {getInt64Ty(), getInt64Ty()}, false); Function::Create( pseudo_func_type, GlobalValue::ExternalLinkage, "llvm.bpf.pseudo", &module_); } AllocaInst *IRBuilderBPF::CreateAllocaBPF(llvm::Type *ty, llvm::Value *arraysize, const std::string &name) { Function *parent = GetInsertBlock()->getParent(); BasicBlock &entry_block = parent->getEntryBlock(); auto ip = saveIP(); if (entry_block.empty()) SetInsertPoint(&entry_block); else SetInsertPoint(&entry_block.front()); AllocaInst *alloca = CreateAlloca(ty, arraysize, name); restoreIP(ip); CreateLifetimeStart(alloca); return alloca; } AllocaInst *IRBuilderBPF::CreateAllocaBPF(llvm::Type *ty, const std::string &name) { return CreateAllocaBPF(ty, nullptr, name); } AllocaInst *IRBuilderBPF::CreateAllocaBPF(const SizedType &stype, const std::string &name) { llvm::Type *ty = GetType(stype); return CreateAllocaBPF(ty, nullptr, name); } AllocaInst *IRBuilderBPF::CreateAllocaBPFInit(const SizedType &stype, const std::string &name) { Function *parent = GetInsertBlock()->getParent(); BasicBlock &entry_block = parent->getEntryBlock(); auto ip = saveIP(); if (entry_block.empty()) SetInsertPoint(&entry_block); else SetInsertPoint(&entry_block.front()); llvm::Type *ty = GetType(stype); AllocaInst *alloca = CreateAllocaBPF(ty, nullptr, name); if (!stype.IsArray()) { CreateStore(getInt64(0), alloca); } else { CreateMemSet(alloca, getInt64(0), stype.size, 1); } restoreIP(ip); CreateLifetimeStart(alloca); return alloca; } AllocaInst *IRBuilderBPF::CreateAllocaBPF(const SizedType &stype, llvm::Value *arraysize, const std::string &name) { llvm::Type *ty = GetType(stype); return CreateAllocaBPF(ty, arraysize, name); } AllocaInst *IRBuilderBPF::CreateAllocaBPF(int bytes, const std::string &name) { llvm::Type *ty = ArrayType::get(getInt8Ty(), bytes); return CreateAllocaBPF(ty, name); } llvm::ConstantInt *IRBuilderBPF::GetIntSameSize(uint64_t C, llvm::Value *expr) { unsigned size = expr->getType()->getIntegerBitWidth(); return getIntN(size, C); } llvm::Type *IRBuilderBPF::GetType(const SizedType &stype) { llvm::Type *ty; if (stype.IsArray()) { ty = ArrayType::get(getInt8Ty(), stype.size); } else { switch (stype.size) { case 16: ty = getInt128Ty(); break; case 8: ty = getInt64Ty(); break; case 4: ty = getInt32Ty(); break; case 2: ty = getInt16Ty(); break; case 1: ty = getInt8Ty(); break; default: std::cerr << stype.size << " is not a valid type size for GetType" << std::endl; abort(); } } return ty; } CallInst *IRBuilderBPF::CreateBpfPseudoCall(int mapfd) { Function *pseudo_func = module_.getFunction("llvm.bpf.pseudo"); return CreateCall(pseudo_func, {getInt64(BPF_PSEUDO_MAP_FD), getInt64(mapfd)}, "pseudo"); } CallInst *IRBuilderBPF::CreateBpfPseudoCall(Map &map) { int mapfd = bpftrace_.maps_[map.ident]->mapfd_; return CreateBpfPseudoCall(mapfd); } CallInst *IRBuilderBPF::createMapLookup(int mapfd, AllocaInst *key) { Value *map = CreateBpfPseudoCall(mapfd); // void *map_lookup_elem(struct bpf_map * map, void * key) // Return: Map value or NULL assert(key->getType()->isPointerTy()); assert(map->getType()->isIntegerTy()); FunctionType *lookup_func_type = FunctionType::get( getInt8PtrTy(), // bpfPseudoCall returns an int64 {getInt64Ty(), key->getType()}, false); PointerType *lookup_func_ptr_type = PointerType::get(lookup_func_type, 0); Constant *lookup_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_map_lookup_elem), lookup_func_ptr_type); return CreateCall(lookup_func, { map, key }, "lookup_elem"); } CallInst *IRBuilderBPF::CreateGetJoinMap(Value *ctx __attribute__((unused))) { AllocaInst *key = CreateAllocaBPF(getInt32Ty(), "key"); CreateStore(getInt32(0), key); CallInst *call = createMapLookup(bpftrace_.join_map_->mapfd_, key); return call; } Value *IRBuilderBPF::CreateMapLookupElem(Map &map, AllocaInst *key) { int mapfd = bpftrace_.maps_[map.ident]->mapfd_; return CreateMapLookupElem(mapfd, key, map.type); } Value *IRBuilderBPF::CreateMapLookupElem(int mapfd, AllocaInst *key, SizedType &type) { CallInst *call = createMapLookup(mapfd, key); // Check if result == 0 Function *parent = GetInsertBlock()->getParent(); BasicBlock *lookup_success_block = BasicBlock::Create(module_.getContext(), "lookup_success", parent); BasicBlock *lookup_failure_block = BasicBlock::Create(module_.getContext(), "lookup_failure", parent); BasicBlock *lookup_merge_block = BasicBlock::Create(module_.getContext(), "lookup_merge", parent); AllocaInst *value = CreateAllocaBPF(type, "lookup_elem_val"); Value *condition = CreateICmpNE( CreateIntCast(call, getInt8PtrTy(), true), ConstantExpr::getCast(Instruction::IntToPtr, getInt64(0), getInt8PtrTy()), "map_lookup_cond"); CreateCondBr(condition, lookup_success_block, lookup_failure_block); bool is_array = (type.type == Type::string || (type.type == Type::cast && !type.is_pointer) || type.type == Type::inet); SetInsertPoint(lookup_success_block); if (is_array) CREATE_MEMCPY(value, call, type.size, 1); else { assert(value->getType()->isPointerTy() && (value->getType()->getElementType() == getInt64Ty())); // createMapLookup returns an u8* auto *cast = CreatePointerCast(call, value->getType(), "cast"); CreateStore(CreateLoad(getInt64Ty(), cast), value); } CreateBr(lookup_merge_block); SetInsertPoint(lookup_failure_block); if (is_array) CreateMemSet(value, getInt8(0), type.size, 1); else CreateStore(getInt64(0), value); CreateBr(lookup_merge_block); SetInsertPoint(lookup_merge_block); if (is_array) return value; return CreateLoad(value); } void IRBuilderBPF::CreateMapUpdateElem(Map &map, AllocaInst *key, Value *val) { Value *mapid = CreateBpfPseudoCall(map); assert(key->getType()->isPointerTy()); assert(mapid->getType()->isIntegerTy()); assert(val->getType()->isPointerTy()); Value *flags = getInt64(0); // int map_update_elem(struct bpf_map * map, void *key, void * value, u64 // flags) Return: 0 on success or negative error FunctionType *update_func_type = FunctionType::get( getInt64Ty(), { getInt64Ty(), key->getType(), val->getType(), getInt64Ty() }, false); PointerType *update_func_ptr_type = PointerType::get(update_func_type, 0); Constant *update_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_map_update_elem), update_func_ptr_type); CreateCall(update_func, { mapid, key, val, flags }, "update_elem"); } void IRBuilderBPF::CreateMapDeleteElem(Map &map, AllocaInst *key) { Value *map_ptr = CreateBpfPseudoCall(map); // int map_delete_elem(&map, &key) // Return: 0 on success or negative error FunctionType *delete_func_type = FunctionType::get( getInt64Ty(), {getInt8PtrTy(), getInt8PtrTy()}, false); PointerType *delete_func_ptr_type = PointerType::get(delete_func_type, 0); Constant *delete_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_map_delete_elem), delete_func_ptr_type); CreateCall(delete_func, {map_ptr, key}, "delete_elem"); } void IRBuilderBPF::CreateProbeRead(AllocaInst *dst, size_t size, Value *src) { // int bpf_probe_read(void *dst, int size, void *src) // Return: 0 on success or negative error FunctionType *proberead_func_type = FunctionType::get( getInt64Ty(), {getInt8PtrTy(), getInt64Ty(), getInt8PtrTy()}, false); PointerType *proberead_func_ptr_type = PointerType::get(proberead_func_type, 0); Constant *proberead_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_probe_read), proberead_func_ptr_type); CreateCall(proberead_func, {dst, getInt64(size), src}, "probe_read"); } CallInst *IRBuilderBPF::CreateProbeReadStr(AllocaInst *dst, size_t size, Value *src) { return CreateProbeReadStr(dst, getInt64(size), src); } CallInst *IRBuilderBPF::CreateProbeReadStr(AllocaInst *dst, llvm::Value *size, Value *src) { // int bpf_probe_read_str(void *dst, int size, const void *unsafe_ptr) FunctionType *probereadstr_func_type = FunctionType::get( getInt64Ty(), {getInt8PtrTy(), getInt64Ty(), getInt8PtrTy()}, false); PointerType *probereadstr_func_ptr_type = PointerType::get(probereadstr_func_type, 0); Constant *probereadstr_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_probe_read_str), probereadstr_func_ptr_type); return CreateCall(probereadstr_func, {dst, size, src}, "probe_read_str"); } CallInst *IRBuilderBPF::CreateProbeReadStr(Value *dst, size_t size, Value *src) { // int bpf_probe_read_str(void *dst, int size, const void *unsafe_ptr) FunctionType *probereadstr_func_type = FunctionType::get( getInt64Ty(), {getInt8PtrTy(), getInt64Ty(), getInt8PtrTy()}, false); PointerType *probereadstr_func_ptr_type = PointerType::get(probereadstr_func_type, 0); Constant *probereadstr_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_probe_read_str), probereadstr_func_ptr_type); return CreateCall(probereadstr_func, {dst, getInt64(size), src}, "map_read_str"); } Value *IRBuilderBPF::CreateUSDTReadArgument(Value *ctx, struct bcc_usdt_argument *argument, Builtin &builtin) { // TODO (mmarchini): Handle base + index * scale addressing. // https://github.com/iovisor/bcc/pull/988 if (argument->valid & BCC_USDT_ARGUMENT_INDEX_REGISTER_NAME) std::cerr << "index register is not handled yet [" << argument->index_register_name << "]" << std::endl; if (argument->valid & BCC_USDT_ARGUMENT_SCALE) std::cerr << "scale is not handled yet [" << argument->scale << "]" << std::endl; if (argument->valid & BCC_USDT_ARGUMENT_DEREF_IDENT) std::cerr << "defer ident is not handled yet [" << argument->deref_ident << "]" << std::endl; if (argument->valid & BCC_USDT_ARGUMENT_CONSTANT) return getInt64(argument->constant); Value *result = nullptr; if (argument->valid & BCC_USDT_ARGUMENT_BASE_REGISTER_NAME) { int offset = 0; offset = arch::offset(argument->base_register_name); Value* reg = CreateGEP(ctx, getInt64(offset * sizeof(uintptr_t)), "load_register"); AllocaInst *dst = CreateAllocaBPF(builtin.type, builtin.ident); CreateProbeRead(dst, builtin.type.size, reg); result = CreateLoad(dst); if (argument->valid & BCC_USDT_ARGUMENT_DEREF_OFFSET) { Value *ptr = CreateAdd( result, getInt64(argument->deref_offset)); CreateProbeRead(dst, builtin.type.size, ptr); result = CreateLoad(dst); } CreateLifetimeEnd(dst); } return result; } Value *IRBuilderBPF::CreateUSDTReadArgument(Value *ctx, AttachPoint *attach_point, int arg_num, Builtin &builtin, int pid) { struct bcc_usdt_argument argument; void *usdt; if (pid) { //FIXME use attach_point->target when iovisor/bcc#2064 is merged usdt = bcc_usdt_new_frompid(pid, nullptr); } else { usdt = bcc_usdt_new_frompath(attach_point->target.c_str()); } if (usdt == nullptr) { std::cerr << "failed to initialize usdt context for probe " << attach_point->target << std::endl; exit(-1); } std::string ns = std::get(attach_point->usdt); std::string func = std::get(attach_point->usdt); if (bcc_usdt_get_argument(usdt, ns.c_str(), func.c_str(), 0, arg_num, &argument) != 0) { std::cerr << "couldn't get argument " << arg_num << " for " << attach_point->target << ":" << attach_point->ns << ":" << attach_point->func << std::endl; exit(-2); } Value *result = CreateUSDTReadArgument(ctx, &argument, builtin); bcc_usdt_close(usdt); return result; } Value *IRBuilderBPF::CreateStrcmp(Value* val, std::string str, bool inverse) { auto cmpAmount = strlen(str.c_str()) + 1; return CreateStrncmp(val, str, cmpAmount, inverse); } Value *IRBuilderBPF::CreateStrncmp(Value* val, std::string str, uint64_t n, bool inverse) { Function *parent = GetInsertBlock()->getParent(); BasicBlock *str_ne = BasicBlock::Create(module_.getContext(), "strcmp.false", parent); AllocaInst *store = CreateAllocaBPF(getInt8Ty(), "strcmp.result"); CreateStore(getInt1(!inverse), store); const char *c_str = str.c_str(); for (size_t i = 0; i < n; i++) { BasicBlock *char_eq = BasicBlock::Create(module_.getContext(), "strcmp.loop", parent); Value *ptr = CreateAdd( val, getInt64(i)); Value *l = CreateLoad(getInt8Ty(), ptr); Value *r = getInt8(c_str[i]); Value *cmp = CreateICmpNE(l, r, "strcmp.cmp"); CreateCondBr(cmp, str_ne, char_eq); SetInsertPoint(char_eq); } CreateStore(getInt1(inverse), store); CreateBr(str_ne); SetInsertPoint(str_ne); Value *result = CreateLoad(store); CreateLifetimeEnd(store); result = CreateIntCast(result, getInt64Ty(), false); return result; } Value *IRBuilderBPF::CreateStrcmp(Value* val1, Value* val2, bool inverse) { return CreateStrncmp(val1, val2, bpftrace_.strlen_, inverse); } Value *IRBuilderBPF::CreateStrncmp(Value* val1, Value* val2, uint64_t n, bool inverse) { /* // This function compares each character of the two string. // It returns true if all are equal and false if any are different // strcmp(String val1, String val2) { for (size_t i = 0; i < n; i++) { if (val1[i] != val2[i]) { return false; } if (val1[i] == NULL) { return true; } } return true; } */ Function *parent = GetInsertBlock()->getParent(); BasicBlock *str_ne = BasicBlock::Create(module_.getContext(), "strcmp.false", parent); AllocaInst *store = CreateAllocaBPF(getInt8Ty(), "strcmp.result"); BasicBlock *done = BasicBlock::Create(module_.getContext(), "strcmp.done", parent); CreateStore(getInt1(!inverse), store); Value *null_byte = getInt8(0); AllocaInst *val_l = CreateAllocaBPF(getInt8Ty(), "strcmp.char_l"); AllocaInst *val_r = CreateAllocaBPF(getInt8Ty(), "strcmp.char_r"); for (size_t i = 0; i < n; i++) { BasicBlock *char_eq = BasicBlock::Create(module_.getContext(), "strcmp.loop", parent); BasicBlock *loop_null_check = BasicBlock::Create(module_.getContext(), "strcmp.loop_null_cmp", parent); Value *ptr1 = CreateAdd(val1, getInt64(i)); CreateProbeRead(val_l, 1, ptr1); Value *l = CreateLoad(getInt8Ty(), val_l); Value *ptr2 = CreateAdd(val2, getInt64(i)); CreateProbeRead(val_r, 1, ptr2); Value *r = CreateLoad(getInt8Ty(), val_r); Value *cmp = CreateICmpNE(l, r, "strcmp.cmp"); CreateCondBr(cmp, str_ne, loop_null_check); SetInsertPoint(loop_null_check); Value *cmp_null = CreateICmpEQ(l, null_byte, "strcmp.cmp_null"); CreateCondBr(cmp_null, done, char_eq); SetInsertPoint(char_eq); } CreateBr(done); SetInsertPoint(done); CreateStore(getInt1(inverse), store); CreateBr(str_ne); SetInsertPoint(str_ne); Value *result = CreateLoad(store); CreateLifetimeEnd(store); CreateLifetimeEnd(val_l); CreateLifetimeEnd(val_r); result = CreateIntCast(result, getInt64Ty(), false); return result; } CallInst *IRBuilderBPF::CreateGetNs() { // u64 ktime_get_ns() // Return: current ktime FunctionType *gettime_func_type = FunctionType::get(getInt64Ty(), false); PointerType *gettime_func_ptr_type = PointerType::get(gettime_func_type, 0); Constant *gettime_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_ktime_get_ns), gettime_func_ptr_type); return CreateCall(gettime_func, {}, "get_ns"); } CallInst *IRBuilderBPF::CreateGetPidTgid() { // u64 bpf_get_current_pid_tgid(void) // Return: current->tgid << 32 | current->pid FunctionType *getpidtgid_func_type = FunctionType::get(getInt64Ty(), false); PointerType *getpidtgid_func_ptr_type = PointerType::get(getpidtgid_func_type, 0); Constant *getpidtgid_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_get_current_pid_tgid), getpidtgid_func_ptr_type); return CreateCall(getpidtgid_func, {}, "get_pid_tgid"); } CallInst *IRBuilderBPF::CreateGetCurrentCgroupId() { // u64 bpf_get_current_cgroup_id(void) // Return: 64-bit cgroup-v2 id FunctionType *getcgroupid_func_type = FunctionType::get(getInt64Ty(), false); PointerType *getcgroupid_func_ptr_type = PointerType::get( getcgroupid_func_type, 0); Constant *getcgroupid_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_get_current_cgroup_id), getcgroupid_func_ptr_type); return CreateCall(getcgroupid_func, {}, "get_cgroup_id"); } CallInst *IRBuilderBPF::CreateGetUidGid() { // u64 bpf_get_current_uid_gid(void) // Return: current_gid << 32 | current_uid FunctionType *getuidgid_func_type = FunctionType::get(getInt64Ty(), false); PointerType *getuidgid_func_ptr_type = PointerType::get(getuidgid_func_type, 0); Constant *getuidgid_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_get_current_uid_gid), getuidgid_func_ptr_type); return CreateCall(getuidgid_func, {}, "get_uid_gid"); } CallInst *IRBuilderBPF::CreateGetCpuId() { // u32 bpf_raw_smp_processor_id(void) // Return: SMP processor ID FunctionType *getcpuid_func_type = FunctionType::get(getInt64Ty(), false); PointerType *getcpuid_func_ptr_type = PointerType::get(getcpuid_func_type, 0); Constant *getcpuid_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_get_smp_processor_id), getcpuid_func_ptr_type); return CreateCall(getcpuid_func, {}, "get_cpu_id"); } CallInst *IRBuilderBPF::CreateGetCurrentTask() { // u64 bpf_get_current_task(void) // Return: current task_struct FunctionType *getcurtask_func_type = FunctionType::get(getInt64Ty(), false); PointerType *getcurtask_func_ptr_type = PointerType::get(getcurtask_func_type, 0); Constant *getcurtask_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_get_current_task), getcurtask_func_ptr_type); return CreateCall(getcurtask_func, {}, "get_cur_task"); } CallInst *IRBuilderBPF::CreateGetRandom() { // u64 bpf_get_prandom_u32(void) // Return: random FunctionType *getrandom_func_type = FunctionType::get(getInt64Ty(), false); PointerType *getrandom_func_ptr_type = PointerType::get(getrandom_func_type, 0); Constant *getrandom_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_get_prandom_u32), getrandom_func_ptr_type); return CreateCall(getrandom_func, {}, "get_random"); } CallInst *IRBuilderBPF::CreateGetStackId(Value *ctx, bool ustack, StackType stack_type) { assert(bpftrace_.stackid_maps_.count(stack_type) == 1); Value *map_ptr = CreateBpfPseudoCall(bpftrace_.stackid_maps_[stack_type]->mapfd_); int flags = 0; if (ustack) flags |= (1<<8); Value *flags_val = getInt64(flags); // int bpf_get_stackid(ctx, map, flags) // Return: >= 0 stackid on success or negative error FunctionType *getstackid_func_type = FunctionType::get( getInt64Ty(), {getInt8PtrTy(), getInt8PtrTy(), getInt64Ty()}, false); PointerType *getstackid_func_ptr_type = PointerType::get(getstackid_func_type, 0); Constant *getstackid_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_get_stackid), getstackid_func_ptr_type); return CreateCall(getstackid_func, {ctx, map_ptr, flags_val}, "get_stackid"); } void IRBuilderBPF::CreateGetCurrentComm(AllocaInst *buf, size_t size) { // int bpf_get_current_comm(char *buf, int size_of_buf) // Return: 0 on success or negative error FunctionType *getcomm_func_type = FunctionType::get( getInt64Ty(), {getInt8PtrTy(), getInt64Ty()}, false); PointerType *getcomm_func_ptr_type = PointerType::get(getcomm_func_type, 0); Constant *getcomm_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_get_current_comm), getcomm_func_ptr_type); CreateCall(getcomm_func, {buf, getInt64(size)}, "get_comm"); } void IRBuilderBPF::CreatePerfEventOutput(Value *ctx, Value *data, size_t size) { Value *map_ptr = CreateBpfPseudoCall(bpftrace_.perf_event_map_->mapfd_); Value *flags_val = CreateGetCpuId(); Value *size_val = getInt64(size); // int bpf_perf_event_output(ctx, map, flags, data, size) FunctionType *perfoutput_func_type = FunctionType::get( getInt64Ty(), { getInt8PtrTy(), getInt64Ty(), getInt64Ty(), cast(data->getType()), getInt64Ty() }, false); PointerType *perfoutput_func_ptr_type = PointerType::get(perfoutput_func_type, 0); Constant *perfoutput_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_perf_event_output), perfoutput_func_ptr_type); CreateCall(perfoutput_func, {ctx, map_ptr, flags_val, data, size_val}, "perf_event_output"); } void IRBuilderBPF::CreateSignal(Value *sig) { // int bpf_send_signal(u32 sig) // Return: 0 or error FunctionType *signal_func_type = FunctionType::get( getInt64Ty(), {getInt32Ty()}, false); PointerType *signal_func_ptr_type = PointerType::get(signal_func_type, 0); Constant *signal_func = ConstantExpr::getCast( Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_send_signal), signal_func_ptr_type); CreateCall(signal_func, {sig}, "signal"); } void IRBuilderBPF::CreateOverrideReturn(Value *ctx, Value *rc) { // int bpf_override_return(struct pt_regs *regs, u64 rc) // Return: 0 FunctionType *override_func_type = FunctionType::get( getInt64Ty(), { getInt8PtrTy(), getInt64Ty() }, false); PointerType *override_func_ptr_type = PointerType::get(override_func_type, 0); Constant *override_func = ConstantExpr::getCast(Instruction::IntToPtr, getInt64(libbpf::BPF_FUNC_override_return), override_func_ptr_type); CreateCall(override_func, { ctx, rc }, "override"); } } // namespace ast } // namespace bpftrace bpftrace-0.9.4/src/ast/irbuilderbpf.h000066400000000000000000000065101361633214400175170ustar00rootroot00000000000000#pragma once #include "ast.h" #include "bcc_usdt.h" #include "bpftrace.h" #include "types.h" #include #include #if LLVM_VERSION_MAJOR >= 5 && LLVM_VERSION_MAJOR < 7 #define CREATE_MEMCPY(dst, src, size, algn) \ CreateMemCpy((dst), (src), (size), (algn)) #elif LLVM_VERSION_MAJOR >= 7 #define CREATE_MEMCPY(dst, src, size, algn) \ CreateMemCpy((dst), (algn), (src), (algn), (size)) #else #error Unsupported LLVM version #endif namespace bpftrace { namespace ast { using namespace llvm; class IRBuilderBPF : public IRBuilder<> { public: IRBuilderBPF(LLVMContext &context, Module &module, BPFtrace &bpftrace); // clang-format off AllocaInst *CreateAllocaBPF(llvm::Type *ty, const std::string &name=""); AllocaInst *CreateAllocaBPF(const SizedType &stype, const std::string &name=""); AllocaInst *CreateAllocaBPFInit(const SizedType &stype, const std::string &name); AllocaInst *CreateAllocaBPF(llvm::Type *ty, llvm::Value *arraysize, const std::string &name=""); AllocaInst *CreateAllocaBPF(const SizedType &stype, llvm::Value *arraysize, const std::string &name=""); AllocaInst *CreateAllocaBPF(int bytes, const std::string &name=""); llvm::Type *GetType(const SizedType &stype); llvm::ConstantInt *GetIntSameSize(uint64_t C, llvm::Value *expr); CallInst *CreateBpfPseudoCall(int mapfd); CallInst *CreateBpfPseudoCall(Map &map); Value *CreateMapLookupElem(Map &map, AllocaInst *key); Value *CreateMapLookupElem(int mapfd, AllocaInst *key, SizedType &type); void CreateMapUpdateElem(Map &map, AllocaInst *key, Value *val); void CreateMapDeleteElem(Map &map, AllocaInst *key); void CreateProbeRead(AllocaInst *dst, size_t size, Value *src); CallInst *CreateProbeReadStr(AllocaInst *dst, llvm::Value *size, Value *src); CallInst *CreateProbeReadStr(AllocaInst *dst, size_t size, Value *src); CallInst *CreateProbeReadStr(Value *dst, size_t size, Value *src); Value *CreateUSDTReadArgument(Value *ctx, AttachPoint *attach_point, int arg_name, Builtin &builtin, int pid); Value *CreateStrcmp(Value* val, std::string str, bool inverse=false); Value *CreateStrcmp(Value* val1, Value* val2, bool inverse=false); Value *CreateStrncmp(Value* val, std::string str, uint64_t n, bool inverse=false); Value *CreateStrncmp(Value* val1, Value* val2, uint64_t n, bool inverse=false); CallInst *CreateGetNs(); CallInst *CreateGetPidTgid(); CallInst *CreateGetCurrentCgroupId(); CallInst *CreateGetUidGid(); CallInst *CreateGetCpuId(); CallInst *CreateGetCurrentTask(); CallInst *CreateGetRandom(); CallInst *CreateGetStackId(Value *ctx, bool ustack, StackType stack_type); CallInst *CreateGetJoinMap(Value *ctx); void CreateGetCurrentComm(AllocaInst *buf, size_t size); void CreatePerfEventOutput(Value *ctx, Value *data, size_t size); void CreateSignal(Value *sig); void CreateOverrideReturn(Value *ctx, Value *rc); private: Module &module_; BPFtrace &bpftrace_; Value *CreateUSDTReadArgument(Value *ctx, struct bcc_usdt_argument *argument, Builtin &builtin); CallInst *createMapLookup(int mapfd, AllocaInst *key); // clang-format on }; } // namespace ast } // namespace bpftrace bpftrace-0.9.4/src/ast/printer.cpp000066400000000000000000000136431361633214400170710ustar00rootroot00000000000000#include #include #include #include "printer.h" #include "ast.h" namespace bpftrace { namespace ast { void Printer::visit(Integer &integer) { std::string indent(depth_, ' '); out_ << indent << "int: " << integer.n << std::endl; } void Printer::visit(PositionalParameter ¶m) { std::string indent(depth_, ' '); switch (param.ptype) { case PositionalParameterType::positional: out_ << indent << "param: $" << param.n << std::endl; break; case PositionalParameterType::count: out_ << indent << "param: $#" << std::endl; break; default: break; } } void Printer::visit(String &string) { std::string indent(depth_, ' '); std::stringstream ss; for (char c: string.str) { // the argument of isprint() must be an unsigned char or EOF int code = static_cast(c); if (std::isprint(code)) { if (c == '\\') ss << "\\\\"; else if (c == '"') ss << "\\\""; else ss << c; } else { if (c == '\n') ss << "\\n"; else if (c == '\t') ss << "\\t"; else if (c == '\r') ss << "\\r"; else ss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << code; } } out_ << indent << "string: " << ss.str() << std::endl; } void Printer::visit(StackMode &mode) { std::string indent(depth_, ' '); out_ << indent << "stack_mode: " << mode.mode << std::endl; } void Printer::visit(Builtin &builtin) { std::string indent(depth_, ' '); out_ << indent << "builtin: " << builtin.ident << std::endl; } void Printer::visit(Identifier &identifier) { std::string indent(depth_, ' '); out_ << indent << "identifier: " << identifier.ident << std::endl; } void Printer::visit(Call &call) { std::string indent(depth_, ' '); out_ << indent << "call: " << call.func << std::endl; ++depth_; if (call.vargs) { for (Expression *expr : *call.vargs) { expr->accept(*this); } } --depth_; } void Printer::visit(Map &map) { std::string indent(depth_, ' '); out_ << indent << "map: " << map.ident << std::endl; ++depth_; if (map.vargs) { for (Expression *expr : *map.vargs) { expr->accept(*this); } } --depth_; } void Printer::visit(Variable &var) { std::string indent(depth_, ' '); out_ << indent << "variable: " << var.ident << std::endl; } void Printer::visit(Binop &binop) { std::string indent(depth_, ' '); out_ << indent << opstr(binop) << std::endl; ++depth_; binop.left->accept(*this); binop.right->accept(*this); --depth_; } void Printer::visit(Unop &unop) { if (!unop.is_post_op) { std::string indent(depth_, ' '); out_ << indent << opstr(unop) << std::endl; ++depth_; unop.expr->accept(*this); --depth_; } if (unop.is_post_op) { std::string indent(depth_+1, ' '); unop.expr->accept(*this); out_ << indent << opstr(unop) << std::endl; } } void Printer::visit(Ternary &ternary) { std::string indent(depth_, ' '); out_ << indent << "?:" << std::endl; ++depth_; ternary.cond->accept(*this); ternary.left->accept(*this); ternary.right->accept(*this); --depth_; } void Printer::visit(FieldAccess &acc) { std::string indent(depth_, ' '); out_ << indent << "." << std::endl; ++depth_; acc.expr->accept(*this); --depth_; out_ << indent << " " << acc.field << std::endl; } void Printer::visit(ArrayAccess &arr) { std::string indent(depth_, ' '); out_ << indent << "[]" << std::endl; ++depth_; arr.expr->accept(*this); arr.indexpr->accept(*this); --depth_; } void Printer::visit(Cast &cast) { std::string indent(depth_, ' '); if (cast.is_pointer) out_ << indent << "(" << cast.cast_type << "*)" << std::endl; else out_ << indent << "(" << cast.cast_type << ")" << std::endl; ++depth_; cast.expr->accept(*this); --depth_; } void Printer::visit(ExprStatement &expr) { expr.expr->accept(*this); } void Printer::visit(AssignMapStatement &assignment) { std::string indent(depth_, ' '); out_ << indent << "=" << std::endl; ++depth_; assignment.map->accept(*this); assignment.expr->accept(*this); --depth_; } void Printer::visit(AssignVarStatement &assignment) { std::string indent(depth_, ' '); out_ << indent << "=" << std::endl; ++depth_; assignment.var->accept(*this); assignment.expr->accept(*this); --depth_; } void Printer::visit(If &if_block) { std::string indent(depth_, ' '); out_ << indent << "if" << std::endl; ++depth_; if_block.cond->accept(*this); ++depth_; out_ << indent << " then" << std::endl; for (Statement *stmt : *if_block.stmts) { stmt->accept(*this); } if (if_block.else_stmts) { out_ << indent << " else" << std::endl; for (Statement *stmt : *if_block.else_stmts) { stmt->accept(*this); } } depth_ -= 2; } void Printer::visit(Unroll &unroll) { std::string indent(depth_, ' '); out_ << indent << "unroll " << unroll.var << std::endl; ++depth_; for (Statement *stmt : *unroll.stmts) { stmt->accept(*this); } --depth_; } void Printer::visit(Predicate &pred) { std::string indent(depth_, ' '); out_ << indent << "pred" << std::endl; ++depth_; pred.expr->accept(*this); --depth_; } void Printer::visit(AttachPoint &ap) { std::string indent(depth_, ' '); out_ << indent << ap.name(ap.func) << std::endl; } void Printer::visit(Probe &probe) { for (AttachPoint *ap : *probe.attach_points) { ap->accept(*this); } ++depth_; if (probe.pred) { probe.pred->accept(*this); } for (Statement *stmt : *probe.stmts) { stmt->accept(*this); } --depth_; } void Printer::visit(Program &program) { if (program.c_definitions.size() > 0) out_ << program.c_definitions << std::endl; std::string indent(depth_, ' '); out_ << indent << "Program" << std::endl; ++depth_; for (Probe *probe : *program.probes) probe->accept(*this); --depth_; } } // namespace ast } // namespace bpftrace bpftrace-0.9.4/src/ast/printer.h000066400000000000000000000023621361633214400165320ustar00rootroot00000000000000#pragma once #include #include "ast.h" namespace bpftrace { namespace ast { class Printer : public Visitor { public: explicit Printer(std::ostream &out) : out_(out) { } void visit(Integer &integer) override; void visit(PositionalParameter ¶m) override; void visit(String &string) override; void visit(StackMode &mode) override; void visit(Identifier &identifier) override; void visit(Builtin &builtin) override; void visit(Call &call) override; void visit(Map &map) override; void visit(Variable &var) override; void visit(Binop &binop) override; void visit(Unop &unop) override; void visit(Ternary &ternary) override; void visit(FieldAccess &acc) override; void visit(ArrayAccess &arr) override; void visit(Cast &cast) override; void visit(ExprStatement &expr) override; void visit(AssignMapStatement &assignment) override; void visit(AssignVarStatement &assignment) override; void visit(If &if_block) override; void visit(Unroll &unroll) override; void visit(Predicate &pred) override; void visit(AttachPoint &ap) override; void visit(Probe &probe) override; void visit(Program &program) override; int depth_ = 0; private: std::ostream &out_; }; } // namespace ast } // namespace bpftrace bpftrace-0.9.4/src/ast/semantic_analyser.cpp000066400000000000000000001737251361633214400211170ustar00rootroot00000000000000#include "semantic_analyser.h" #include "arch/arch.h" #include "ast.h" #include "fake_map.h" #include "list.h" #include "parser.tab.hh" #include "printf.h" #include "tracepoint_format_parser.h" #include "utils.h" #include #include #include #include #include #include "libbpf.h" namespace bpftrace { namespace ast { static const std::map>& getIntcasts() { static const std::map> intcasts = { {"uint8", std::tuple{1, false}}, {"int8", std::tuple{1, true}}, {"uint16", std::tuple{2, false}}, {"int16", std::tuple{2, true}}, {"uint32", std::tuple{4, false}}, {"int32", std::tuple{4, true}}, {"uint64", std::tuple{8, false}}, {"int64", std::tuple{8, true}}, }; return intcasts; } // TODO: (fbs) We should get rid of this macro at some point #define ERR(x, loc) \ { \ std::stringstream errbuf; \ errbuf << x; \ error(errbuf.str(), loc); \ } void SemanticAnalyser::error(const std::string &msg, const location &loc) { bpftrace_.error(err_, loc, msg); } void SemanticAnalyser::warning(const std::string &msg, const location &loc) { bpftrace_.warning(out_, loc, msg); } void SemanticAnalyser::visit(Integer &integer) { integer.type = SizedType(Type::integer, 8, true); } void SemanticAnalyser::visit(PositionalParameter ¶m) { param.type = SizedType(Type::integer, 8, true); switch (param.ptype) { case PositionalParameterType::positional: if (param.n <= 0) ERR("$" << std::to_string(param.n) + " is not a valid parameter", param.loc); if (is_final_pass()) { std::string pstr = bpftrace_.get_param(param.n, param.is_in_str); if (!is_numeric(pstr) && !param.is_in_str) { ERR(param.loc << "$" << param.n << " used numerically << but given \"" << pstr << "\". Try using str($" << param.n << ").", param.loc); } if (is_numeric(pstr) && param.is_in_str) { // This is blocked due to current limitations in our codegen ERR("$" << param.n << " used in str(), but given numeric value: " << pstr << ". Try $" << param.n << " instead of str($" << param.n << ").", param.loc); } } break; case PositionalParameterType::count: if (is_final_pass() && param.is_in_str) { error("use $#, not str($#)", param.loc); } break; default: error("unknown parameter type", param.loc); param.type = SizedType(Type::none, 0); break; } } void SemanticAnalyser::visit(String &string) { if (string.str.size() > STRING_SIZE-1) { ERR("String is too long (over " << STRING_SIZE << " bytes): " << string.str, string.loc); } string.type = SizedType(Type::string, STRING_SIZE); } void SemanticAnalyser::visit(StackMode &mode) { mode.type = SizedType(Type::stack_mode, 0); if (mode.mode == "bpftrace") { mode.type.stack_type.mode = bpftrace::StackMode::bpftrace; } else if (mode.mode == "perf") { mode.type.stack_type.mode = bpftrace::StackMode::perf; } else { mode.type = SizedType(Type::none, 0); error("Unknown stack mode: '" + mode.mode + "'", mode.loc); } } void SemanticAnalyser::visit(Identifier &identifier) { if (bpftrace_.enums_.count(identifier.ident) != 0) { identifier.type = SizedType(Type::integer, 8); } else { identifier.type = SizedType(Type::none, 0); error("Unknown identifier: '" + identifier.ident + "'", identifier.loc); } } void SemanticAnalyser::visit(Builtin &builtin) { if (builtin.ident == "nsecs" || builtin.ident == "elapsed" || builtin.ident == "pid" || builtin.ident == "tid" || builtin.ident == "cgroup" || builtin.ident == "uid" || builtin.ident == "gid" || builtin.ident == "cpu" || builtin.ident == "curtask" || builtin.ident == "rand" || builtin.ident == "ctx") { builtin.type = SizedType(Type::integer, 8, false); if (builtin.ident == "cgroup" && !feature_.has_helper_get_current_cgroup_id()) { error("BPF_FUNC_get_current_cgroup_id is not available for your kernel " "version", builtin.loc); } else if (builtin.ident == "elapsed") { needs_elapsed_map_ = true; } else if (builtin.ident == "curtask") { /* * Retype curtask to its original type: struct task_truct. */ builtin.type.type = Type::cast; builtin.type.cast_type = "struct task_struct"; builtin.type.is_pointer = true; } } else if (builtin.ident == "retval") { for (auto &attach_point : *probe_->attach_points) { ProbeType type = probetype(attach_point->provider); if (type != ProbeType::kretprobe && type != ProbeType::uretprobe) { ERR("The retval builtin can only be used with 'kretprobe' and " << "'uretprobe' probes" << (type == ProbeType::tracepoint ? " (try to use args->ret instead)" : ""), builtin.loc); } } builtin.type = SizedType(Type::integer, 8); } else if (builtin.ident == "kstack") { builtin.type = SizedType(Type::kstack, StackType()); needs_stackid_maps_.insert(builtin.type.stack_type); } else if (builtin.ident == "ustack") { builtin.type = SizedType(Type::ustack, StackType()); needs_stackid_maps_.insert(builtin.type.stack_type); } else if (builtin.ident == "comm") { builtin.type = SizedType(Type::string, COMM_SIZE); } else if (builtin.ident == "func") { for (auto &attach_point : *probe_->attach_points) { ProbeType type = probetype(attach_point->provider); if (type == ProbeType::kprobe || type == ProbeType::kretprobe) builtin.type = SizedType(Type::ksym, 8); else if (type == ProbeType::uprobe || type == ProbeType::uretprobe) builtin.type = SizedType(Type::usym, 16); else ERR("The func builtin can not be used with '" << attach_point->provider << "' probes", builtin.loc); } } else if (!builtin.ident.compare(0, 3, "arg") && builtin.ident.size() == 4 && builtin.ident.at(3) >= '0' && builtin.ident.at(3) <= '9') { for (auto &attach_point : *probe_->attach_points) { ProbeType type = probetype(attach_point->provider); if (type != ProbeType::kprobe && type != ProbeType::uprobe && type != ProbeType::usdt) ERR("The " << builtin.ident << " builtin can only be used with " << "'kprobes', 'uprobes' and 'usdt' probes", builtin.loc); } int arg_num = atoi(builtin.ident.substr(3).c_str()); if (arg_num > arch::max_arg()) error(arch::name() + " doesn't support " + builtin.ident, builtin.loc); builtin.type = SizedType(Type::integer, 8); } else if (!builtin.ident.compare(0, 4, "sarg") && builtin.ident.size() == 5 && builtin.ident.at(4) >= '0' && builtin.ident.at(4) <= '9') { for (auto &attach_point : *probe_->attach_points) { ProbeType type = probetype(attach_point->provider); if (type != ProbeType::kprobe && type != ProbeType::uprobe) error("The " + builtin.ident + " builtin can only be used with " + "'kprobes' and 'uprobes' probes", builtin.loc); if (is_final_pass() && (attach_point->address != 0 || attach_point->func_offset != 0)) { // If sargX values are needed when using an offset, they can be stored in a map // when entering the function and then referenced from an offset-based probe std::string msg = "Using an address offset with the sargX built-in can" "lead to unexpected behavior "; bpftrace_.warning(out_, builtin.loc, msg); } } builtin.type = SizedType(Type::integer, 8); } else if (builtin.ident == "probe") { builtin.type = SizedType(Type::probe, 8); probe_->need_expansion = true; } else if (builtin.ident == "username") { builtin.type = SizedType(Type::username, 8); } else if (builtin.ident == "cpid") { if (! bpftrace_.has_child_cmd()) { error("cpid cannot be used without child command", builtin.loc); } builtin.type = SizedType(Type::integer, 4); } else if (builtin.ident == "args") { probe_->need_expansion = true; for (auto &attach_point : *probe_->attach_points) { ProbeType type = probetype(attach_point->provider); if (type != ProbeType::tracepoint) { error("The args builtin can only be used with tracepoint probes (" + attach_point->provider + " used here)", builtin.loc); continue; } /* * tracepoint wildcard expansion, part 2 of 3. This: * 1. expands the wildcard, then sets args to be the first matched probe. * This is so that enough of the type information is available to * survive the later semantic analyser checks. * 2. sets is_tparg so that codegen does the real type setting after * expansion. */ auto symbol_stream = bpftrace_.get_symbols_from_file( "/sys/kernel/debug/tracing/available_events"); auto matches = bpftrace_.find_wildcard_matches(attach_point->target, attach_point->func, *symbol_stream); if (!matches.empty()) { auto &match = *matches.begin(); std::string tracepoint_struct = TracepointFormatParser::get_struct_name( attach_point->target, match); Struct &cstruct = bpftrace_.structs_[tracepoint_struct]; builtin.type = SizedType(Type::cast, cstruct.size, tracepoint_struct); builtin.type.is_pointer = true; builtin.type.is_tparg = true; } } } else { builtin.type = SizedType(Type::none, 0); error("Unknown builtin variable: '" + builtin.ident + "'", builtin.loc); } } void SemanticAnalyser::visit(Call &call) { // Check for unsafe-ness first. It is likely the most pertinent issue // (and should be at the top) for any function call. if (bpftrace_.safe_mode_ && is_unsafe_func(call.func)) { error(call.func + "() is an unsafe function being used in safe mode", call.loc); } if (call.vargs) { for (Expression *expr : *call.vargs) { expr->accept(*this); } } if (call.func == "hist") { if (!check_assignment(call, true, false, false)) return; check_nargs(call, 1); check_arg(call, Type::integer, 0); call.type = SizedType(Type::hist, 8); } else if (call.func == "lhist") { if (!check_assignment(call, true, false, false)) return; if (check_nargs(call, 4)) { check_arg(call, Type::integer, 0, false); check_arg(call, Type::integer, 1, true); check_arg(call, Type::integer, 2, true); check_arg(call, Type::integer, 3, true); } if (is_final_pass()) { Expression &min_arg = *call.vargs->at(1); Expression &max_arg = *call.vargs->at(2); Expression &step_arg = *call.vargs->at(3); Integer &min = static_cast(min_arg); Integer &max = static_cast(max_arg); Integer &step = static_cast(step_arg); if (step.n <= 0) { ERR("lhist() step must be >= 1 (" << step.n << " provided)", call.loc); } else { int buckets = (max.n - min.n) / step.n; if (buckets > 1000) { ERR("lhist() too many buckets, must be <= 1000 (would need " << buckets << ")", call.loc); } } if (min.n < 0) { ERR("lhist() min must be non-negative (provided min " << min.n << ")", call.loc); } if (min.n > max.n) { ERR("lhist() min must be less than max (provided min " << min.n << " and max ", call.loc); } if ((max.n - min.n) < step.n) { ERR("lhist() step is too large for the given range (provided step " << step.n << " for range " << (max.n - min.n) << ")", call.loc); } // store args for later passing to bpftrace::Map auto search = map_args_.find(call.map->ident); if (search == map_args_.end()) map_args_.insert({call.map->ident, *call.vargs}); } call.type = SizedType(Type::lhist, 8); } else if (call.func == "count") { if (!check_assignment(call, true, false, false)) return; check_nargs(call, 0); call.type = SizedType(Type::count, 8); } else if (call.func == "sum") { bool sign = false; if (!check_assignment(call, true, false, false)) return; if (check_nargs(call, 1)) { sign = call.vargs->at(0)->type.is_signed; } call.type = SizedType(Type::sum, 8, sign); } else if (call.func == "min") { bool sign = false; if (!check_assignment(call, true, false, false)) return; if (check_nargs(call, 1)) { sign = call.vargs->at(0)->type.is_signed; } call.type = SizedType(Type::min, 8, sign); } else if (call.func == "max") { bool sign = false; if (!check_assignment(call, true, false, false)) return; if (check_nargs(call, 1)) { sign = call.vargs->at(0)->type.is_signed; } call.type = SizedType(Type::max, 8, sign); } else if (call.func == "avg") { if (!check_assignment(call, true, false, false)) return; check_nargs(call, 1); call.type = SizedType(Type::avg, 8, true); } else if (call.func == "stats") { if (!check_assignment(call, true, false, false)) return; check_nargs(call, 1); call.type = SizedType(Type::stats, 8, true); } else if (call.func == "delete") { if (!check_assignment(call, false, false, false)) return; if (check_nargs(call, 1)) { auto &arg = *call.vargs->at(0); if (!arg.is_map) error("delete() expects a map to be provided", call.loc); } call.type = SizedType(Type::none, 0); } else if (call.func == "str") { if (check_varargs(call, 1, 2)) { check_arg(call, Type::integer, 0); call.type = SizedType(Type::string, bpftrace_.strlen_); if (is_final_pass() && call.vargs->size() > 1) { check_arg(call, Type::integer, 1, false); } if (auto *param = dynamic_cast(call.vargs->at(0))) { param->is_in_str = true; } } } else if (call.func == "ksym" || call.func == "usym") { if (check_nargs(call, 1)) { // allow symbol lookups on casts (eg, function pointers) auto &arg = *call.vargs->at(0); if (arg.type.type != Type::integer && arg.type.type != Type::cast) error(call.func + "() expects an integer or pointer argument", call.loc); } if (call.func == "ksym") call.type = SizedType(Type::ksym, 8); else if (call.func == "usym") call.type = SizedType(Type::usym, 16); } else if (call.func == "ntop") { if (!check_varargs(call, 1, 2)) return; auto arg = call.vargs->at(0); if (call.vargs->size() == 2) { arg = call.vargs->at(1); check_arg(call, Type::integer, 0); } if (arg->type.type != Type::integer && arg->type.type != Type::array) ERR(call.func << "() expects an integer or array argument, got " << arg->type.type, call.loc); // Kind of: // // struct { // int af_type; // union { // char[4] inet4; // char[16] inet6; // } // } int buffer_size = 24; if (arg->type.type == Type::array) { if (arg->type.elem_type != Type::integer || arg->type.pointee_size != 1 || !(arg->type.size == 4 || arg->type.size == 16)) { error(call.func + "() invalid array", call.loc); } } call.type = SizedType(Type::inet, buffer_size); call.type.is_internal = true; } else if (call.func == "join") { if (!check_assignment(call, false, false, false)) return; check_varargs(call, 1, 2); check_arg(call, Type::integer, 0); call.type = SizedType(Type::none, 0); needs_join_map_ = true; if (is_final_pass()) { if (call.vargs && call.vargs->size() > 1) { if (check_arg(call, Type::string, 1, true)) { auto &join_delim_arg = *call.vargs->at(1); String &join_delim_str = static_cast(join_delim_arg); bpftrace_.join_args_.push_back(join_delim_str.str); } } else { std::string join_delim_default = " "; bpftrace_.join_args_.push_back(join_delim_default); } } } else if (call.func == "reg") { if (check_nargs(call, 1)) { for (auto &attach_point : *probe_->attach_points) { ProbeType type = probetype(attach_point->provider); if (type == ProbeType::tracepoint) { error("The reg function cannot be used with 'tracepoint' probes", call.loc); continue; } } if (check_arg(call, Type::string, 0, true)) { auto &arg = *call.vargs->at(0); auto ®_name = static_cast(arg).str; int offset = arch::offset(reg_name);; if (offset == -1) { ERR("'" << reg_name << "' is not a valid register on this architecture" << " (" << arch::name() << ")", call.loc); } } } call.type = SizedType(Type::integer, 8); } else if (call.func == "kaddr") { if (check_nargs(call, 1)) { check_arg(call, Type::string, 0, true); } call.type = SizedType(Type::integer, 8); } else if (call.func == "uaddr") { if (!check_nargs(call, 1)) return; if (!(check_arg(call, Type::string, 0, true) && check_symbol(call, 0))) return; std::vector sizes; auto &name = static_cast(*call.vargs->at(0)).str; for (auto &ap : *probe_->attach_points) { ProbeType type = probetype(ap->provider); if (type != ProbeType::usdt && type != ProbeType::uretprobe && type != ProbeType::uprobe) { bpftrace_.error( err_, call.loc, "uaddr can only be used with u(ret)probes and usdt probes"); sizes.push_back(0); continue; } struct symbol sym = {}; int err = bpftrace_.resolve_uname(name, &sym, ap->target); if (err < 0 || sym.address == 0) { bpftrace_.error(err_, call.loc, "Could not resolve symbol: " + ap->target + ":" + name); } sizes.push_back(sym.size); } for (size_t i = 1; i < sizes.size(); i++) { if (sizes.at(0) != sizes.at(i)) { std::stringstream msg; msg << "Symbol size mismatch between probes. Symbol \"" << name << "\" has size " << sizes.at(0) << " for probe \"" << probe_->attach_points->at(0)->name("") << "\" but size " << sizes.at(i) << " for probe \"" << probe_->attach_points->at(i)->name("") << "\""; bpftrace_.error(err_, call.loc, msg.str()); } } call.type = SizedType(Type::integer, 8); call.type.is_pointer = true; switch (sizes.at(0)) { case 1: case 2: case 4: call.type.pointee_size = sizes.at(0); break; default: call.type.pointee_size = 8; } } else if (call.func == "cgroupid") { if (check_nargs(call, 1)) { check_arg(call, Type::string, 0, true); } call.type = SizedType(Type::integer, 8); } else if (call.func == "printf" || call.func == "system" || call.func == "cat") { if (!check_assignment(call, false, false, false)) return; if (check_varargs(call, 1, 7)) { check_arg(call, Type::string, 0, true); if (is_final_pass()) { auto &fmt_arg = *call.vargs->at(0); String &fmt = static_cast(fmt_arg); std::vector args; for (auto iter = call.vargs->begin()+1; iter != call.vargs->end(); iter++) { auto ty = (*iter)->type; // Promote to 64-bit if it's not an array type if (!ty.IsArray()) ty.size = 8; args.push_back(Field{ .type = ty, .offset = 0, .is_bitfield = false, .bitfield = Bitfield{ .read_bytes = 0, .access_rshift = 0, .mask = 0, }, }); } std::string msg = verify_format_string(fmt.str, args); if (msg != "") { error(msg, call.loc); } if (call.func == "printf") bpftrace_.printf_args_.emplace_back(fmt.str, args); else if (call.func == "system") bpftrace_.system_args_.emplace_back(fmt.str, args); else bpftrace_.cat_args_.emplace_back(fmt.str, args); } } call.type = SizedType(Type::none, 0); } else if (call.func == "exit") { check_nargs(call, 0); } else if (call.func == "print") { if (!check_assignment(call, false, false, false)) return; if (check_varargs(call, 1, 3)) { auto &arg = *call.vargs->at(0); if (!arg.is_map) error("print() expects a map to be provided", call.loc); else { Map &map = static_cast(arg); map.skip_key_validation = true; if (map.vargs != nullptr) { ERR("The map passed to " << call.func << "() should not be " << "indexed by a key", call.loc); } } if (is_final_pass()) { if (call.vargs->size() > 1) check_arg(call, Type::integer, 1, true); if (call.vargs->size() > 2) check_arg(call, Type::integer, 2, true); } } } else if (call.func == "clear") { if (!check_assignment(call, false, false, false)) return; if (check_nargs(call, 1)) { auto &arg = *call.vargs->at(0); if (!arg.is_map) error("clear() expects a map to be provided", call.loc); else { Map &map = static_cast(arg); map.skip_key_validation = true; if (map.vargs != nullptr) { ERR("The map passed to " << call.func << "() should not be " << "indexed by a key", call.loc); } } } } else if (call.func == "zero") { if (!check_assignment(call, false, false, false)) return; if (check_nargs(call, 1)) { auto &arg = *call.vargs->at(0); if (!arg.is_map) error("zero() expects a map to be provided", call.loc); else { Map &map = static_cast(arg); map.skip_key_validation = true; if (map.vargs != nullptr) { ERR("The map passed to " << call.func << "() should not be " << "indexed by a key", call.loc); } } } } else if (call.func == "time") { if (!check_assignment(call, false, false, false)) return; if (check_varargs(call, 0, 1)) { if (is_final_pass()) { if (call.vargs && call.vargs->size() > 0) { if (check_arg(call, Type::string, 0, true)) { auto &fmt_arg = *call.vargs->at(0); String &fmt = static_cast(fmt_arg); bpftrace_.time_args_.push_back(fmt.str); } } else { std::string fmt_default = "%H:%M:%S\n"; bpftrace_.time_args_.push_back(fmt_default.c_str()); } } } } else if (call.func == "kstack") { check_stack_call(call, Type::kstack); } else if (call.func == "ustack") { check_stack_call(call, Type::ustack); } else if (call.func == "signal") { if (!feature_.has_helper_send_signal()) { error("BPF_FUNC_send_signal not available for your kernel version", call.loc); } if (!check_assignment(call, false, false, false)) { return; } if (!check_varargs(call, 1, 1)) { return; } auto &arg = *call.vargs->at(0); if (arg.type.type == Type::string && arg.is_literal) { auto sig = static_cast(arg).str; if (signal_name_to_num(sig) < 1) { error(sig + " is not a valid signal", call.loc); } } else if(arg.type.type == Type::integer && arg.is_literal) { auto sig = static_cast(arg).n; if (sig < 1 || sig > 64) { error(std::to_string(sig) + " is not a valid signal, allowed range: [1,64]", call.loc); } } else if(arg.type.type != Type::integer) { error("signal only accepts string literals or integers", call.loc); } for (auto &ap : *probe_->attach_points) { ProbeType type = probetype(ap->provider); if (ap->provider == "BEGIN" || ap->provider == "END") { error(call.func + " can not be used with \"" + ap->provider + "\" probes", call.loc); } else if (type == ProbeType::interval || type == ProbeType::software || type == ProbeType::hardware || type == ProbeType::watchpoint) { error(call.func + " can not be used with \"" + ap->provider + "\" probes", call.loc); } } } else if (call.func == "strncmp") { if (check_nargs(call, 3)) { check_arg(call, Type::string, 0); check_arg(call, Type::string, 1); if (check_arg(call, Type::integer, 2, true)){ Integer &size = static_cast(*call.vargs->at(2)); if (size.n < 0) error("Builtin strncmp requires a non-negative size", call.loc); } } call.type = SizedType(Type::integer, 8); } else if (call.func == "override") { if (!feature_.has_helper_override_return()) { error("BPF_FUNC_override_return not available for your kernel version", call.loc); } check_assignment(call, false, false, false); if (check_varargs(call, 1, 1)) { check_arg(call, Type::integer, 0, false); } for (auto &attach_point : *probe_->attach_points) { ProbeType type = probetype(attach_point->provider); if (type != ProbeType::kprobe) { error(call.func + " can only be used with kprobes.", call.loc); } } } else { error("Unknown function: '" + call.func + "'", call.loc); call.type = SizedType(Type::none, 0); } } void SemanticAnalyser::check_stack_call(Call &call, Type type) { call.type = SizedType(type, StackType()); if (check_varargs(call, 0, 2) && is_final_pass()) { StackType stack_type; if (call.vargs) { switch (call.vargs->size()) { case 0: break; case 1: { auto &arg = *call.vargs->at(0); // If we have a single argument it can be either // stack-mode or stack-size if (arg.type.type == Type::stack_mode) { if (check_arg(call, Type::stack_mode, 0, true)) stack_type.mode = static_cast(arg).type.stack_type.mode; } else { if (check_arg(call, Type::integer, 0, true)) stack_type.limit = static_cast(arg).n; } break; } case 2: { if (check_arg(call, Type::stack_mode, 0, true)) { auto &mode_arg = *call.vargs->at(0); stack_type.mode = static_cast(mode_arg).type.stack_type.mode; } if (check_arg(call, Type::integer, 1, true)) { auto &limit_arg = *call.vargs->at(1); stack_type.limit = static_cast(limit_arg).n; } break; } default: error("Invalid number of arguments", call.loc); break; } } if (stack_type.limit > MAX_STACK_SIZE) { ERR(call.func << "([int limit]): limit shouldn't exceed " << MAX_STACK_SIZE << ", " << stack_type.limit << " given", call.loc); } call.type = SizedType(type, stack_type); needs_stackid_maps_.insert(stack_type); } } void SemanticAnalyser::visit(Map &map) { MapKey key; if (map.vargs) { for (unsigned int i = 0; i < map.vargs->size(); i++){ Expression * expr = map.vargs->at(i); expr->accept(*this); // Insert a cast to 64 bits if needed by injecting // a cast into the ast. if (expr->type.type == Type::integer && expr->type.size < 8) { std::string type = expr->type.is_signed ? "int64" : "uint64"; Expression * cast = new ast::Cast(type, false, expr); cast->accept(*this); map.vargs->at(i) = cast; expr = cast; } if (is_final_pass()) { // Skip is_signed when comparing keys to not break existing scripts // which use maps as a lookup table // TODO (fbs): This needs a better solution SizedType keytype = expr->type; keytype.is_signed = false; key.args_.push_back(keytype); } } } if (is_final_pass()) { if (!map.skip_key_validation) { auto search = map_key_.find(map.ident); if (search != map_key_.end()) { if (search->second != key) { ERR("Argument mismatch for " << map.ident << ": " << "trying to access with arguments: " << key.argument_type_list() << " when map expects arguments: " << search->second.argument_type_list(), map.loc); } } else { map_key_.insert({map.ident, key}); } } } auto search_val = map_val_.find(map.ident); if (search_val != map_val_.end()) { map.type = search_val->second; } else { if (is_final_pass()) { error("Undefined map: " + map.ident, map.loc); } map.type = SizedType(Type::none, 0); } } void SemanticAnalyser::visit(Variable &var) { auto search_val = variable_val_.find(var.ident); if (search_val != variable_val_.end()) { var.type = search_val->second; } else { error("Undefined or undeclared variable: " + var.ident, var.loc); var.type = SizedType(Type::none, 0); } } void SemanticAnalyser::visit(ArrayAccess &arr) { arr.expr->accept(*this); arr.indexpr->accept(*this); SizedType &type = arr.expr->type; SizedType &indextype = arr.indexpr->type; if (is_final_pass()) { if (type.type != Type::array) error("The array index operator [] can only be used on arrays.", arr.loc); if (indextype.type == Type::integer && arr.indexpr->is_literal) { Integer *index = static_cast(arr.indexpr); if ((size_t) index->n >= type.size) ERR("the index " << index->n << " is out of bounds for array of size " << type.size, arr.loc); } else { error("The array index operator [] only accepts literal integer indices.", arr.loc); } } arr.type = SizedType(type.elem_type, type.pointee_size, type.is_signed); } void SemanticAnalyser::visit(Binop &binop) { binop.left->accept(*this); binop.right->accept(*this); Type &lhs = binop.left->type.type; Type &rhs = binop.right->type.type; bool lsign = binop.left->type.is_signed; bool rsign = binop.right->type.is_signed; std::stringstream buf; if (is_final_pass()) { if ((lhs != rhs) && // allow integer to cast pointer comparisons (eg, ptr != 0): !(lhs == Type::cast && rhs == Type::integer) && !(lhs == Type::integer && rhs == Type::cast)) { buf << "Type mismatch for '" << opstr(binop) << "': "; buf << "comparing '" << lhs << "' "; buf << "with '" << rhs << "'"; bpftrace_.error(err_, binop.left->loc + binop.right->loc, buf.str()); buf.str({}); } // Follow what C does else if (lhs == Type::integer && rhs == Type::integer) { auto get_int_literal = [](const auto expr) -> long { return static_cast(expr)->n; }; auto left = binop.left; auto right = binop.right; // First check if operand signedness is the same if (lsign != rsign) { // Convert operands to unsigned if it helps make (lsign == rsign) // // For example: // // unsigned int a; // if (a > 10) ...; // // No warning should be emitted as we know that 10 can be // represented as unsigned int if (lsign && !rsign && left->is_literal && get_int_literal(left) >= 0) { left->type.is_signed = lsign = false; } // The reverse (10 < a) should also hold else if (!lsign && rsign && right->is_literal && get_int_literal(right) >= 0) { right->type.is_signed = rsign = false; } else { switch (binop.op) { case bpftrace::Parser::token::EQ: case bpftrace::Parser::token::NE: case bpftrace::Parser::token::LE: case bpftrace::Parser::token::GE: case bpftrace::Parser::token::LT: case bpftrace::Parser::token::GT: buf << "comparison of integers of different signs: '" << binop.left->type << "' and '" << binop.right->type << "'" << " can lead to undefined behavior"; warning(buf.str(), binop.loc); buf.str({}); break; case bpftrace::Parser::token::PLUS: case bpftrace::Parser::token::MINUS: case bpftrace::Parser::token::MUL: case bpftrace::Parser::token::DIV: case bpftrace::Parser::token::MOD: buf << "arithmetic on integers of different signs: '" << left->type << "' and '" << right->type << "'" << " can lead to undefined behavior"; warning(buf.str(), binop.loc); buf.str({}); break; default: break; } } } // Next, warn on any operations that require signed division. // // SDIV is not implemented for bpf. See Documentation/bpf/bpf_design_QA // in kernel sources if (binop.op == bpftrace::Parser::token::DIV || binop.op == bpftrace::Parser::token::MOD) { // Convert operands to unsigned if possible if (lsign && left->is_literal && get_int_literal(left) >= 0) left->type.is_signed = lsign = false; if (rsign && right->is_literal && get_int_literal(right) >= 0) right->type.is_signed = rsign = false; // If they're still signed, we have to warn if (lsign || rsign) { buf << "signed operands for '" << opstr(binop) << "' can lead to undefined behavior " << "(cast to unsigned to silence warning)"; bpftrace_.warning(out_, binop.loc, buf.str()); buf.str({}); } } } else if (!(lhs == Type::integer && rhs == Type::integer) && binop.op != Parser::token::EQ && binop.op != Parser::token::NE) { ERR("The " << opstr(binop) << " operator can not be used on expressions of types " << lhs << ", " << rhs, binop.loc); } } bool is_signed = lsign && rsign; switch (binop.op) { case bpftrace::Parser::token::LEFT: case bpftrace::Parser::token::RIGHT: is_signed = lsign; break; default: break; } binop.type = SizedType(Type::integer, 8, is_signed); } void SemanticAnalyser::visit(Unop &unop) { if (unop.op == Parser::token::INCREMENT || unop.op == Parser::token::DECREMENT) { // Handle ++ and -- before visiting unop.expr, because these // operators should be able to work with undefined maps. if (!unop.expr->is_map && !unop.expr->is_variable) { error("The " + opstr(unop) + " operator must be applied to a map or variable", unop.loc); } if (unop.expr->is_map) { Map &map = static_cast(*unop.expr); assign_map_type(map, SizedType(Type::integer, 8, true)); } } unop.expr->accept(*this); SizedType &type = unop.expr->type; if (is_final_pass() && !(type.type == Type::integer) && !(type.type == Type::cast && unop.op == Parser::token::MUL)) { ERR("The " << opstr(unop) << " operator can not be used on expressions of type '" << type << "'", unop.loc); } if (unop.op == Parser::token::MUL) { if (type.type == Type::cast) { if (type.is_pointer) { int cast_size; auto &intcasts = getIntcasts(); auto k_v = intcasts.find(type.cast_type); if (k_v == intcasts.end() && bpftrace_.structs_.count(type.cast_type) == 0) { ERR("Unknown struct/union: '" << type.cast_type << "'", unop.loc); return; } if (k_v != intcasts.end()) { auto &v = k_v->second; unop.type = SizedType(Type::integer, std::get<0>(v), std::get<1>(v), k_v->first); } else { cast_size = bpftrace_.structs_[type.cast_type].size; unop.type = SizedType(Type::cast, cast_size, type.cast_type); } unop.type.is_tparg = type.is_tparg; } else { ERR("Can not dereference struct/union of type '" << type.cast_type << "'. " << "It is not a pointer.", unop.loc); } } else if (type.type == Type::integer) { unop.type = SizedType(Type::integer, type.size, type.is_signed); } } else if (unop.op == Parser::token::LNOT) { unop.type = SizedType(Type::integer, type.size, false); } else { unop.type = SizedType(Type::integer, 8, type.is_signed); } } void SemanticAnalyser::visit(Ternary &ternary) { ternary.cond->accept(*this); ternary.left->accept(*this); ternary.right->accept(*this); Type &lhs = ternary.left->type.type; Type &rhs = ternary.right->type.type; if (is_final_pass()) { if (lhs != rhs) { ERR("Ternary operator must return the same type: " << "have '" << lhs << "' " << "and '" << rhs << "'", ternary.loc); } } if (lhs == Type::string) ternary.type = SizedType(lhs, STRING_SIZE); else if (lhs == Type::integer) ternary.type = SizedType(lhs, 8, ternary.left->type.is_signed); else { ERR("Ternary return type unsupported " << lhs, ternary.loc); } } void SemanticAnalyser::visit(If &if_block) { if_block.cond->accept(*this); for (Statement *stmt : *if_block.stmts) { stmt->accept(*this); } if (if_block.else_stmts) { for (Statement *stmt : *if_block.else_stmts) { stmt->accept(*this); } } } void SemanticAnalyser::visit(Unroll &unroll) { if (unroll.var > 20) { error("unroll maximum value is 20", location(0)); } else if (unroll.var < 1) { error("unroll minimum value is 1", location(0)); } for (int i=0; i < unroll.var; i++) { for (Statement *stmt : *unroll.stmts) { stmt->accept(*this); } } } void SemanticAnalyser::visit(FieldAccess &acc) { acc.expr->accept(*this); SizedType &type = acc.expr->type; if (type.type != Type::cast) { if (is_final_pass()) { ERR("Can not access field '" << acc.field << "' on expression of type '" << type << "'", acc.loc); } return; } if (type.is_pointer) { ERR("Can not access field '" << acc.field << "' on type '" << type.cast_type << "'. Try dereferencing it first, or using '->'", acc.loc); return; } if (bpftrace_.structs_.count(type.cast_type) == 0) { ERR("Unknown struct/union: '" << type.cast_type << "'", acc.loc); return; } std::map structs; if (type.is_tparg) { for (AttachPoint *attach_point : *probe_->attach_points) { assert(probetype(attach_point->provider) == ProbeType::tracepoint); auto symbol_stream = bpftrace_.get_symbols_from_file( "/sys/kernel/debug/tracing/available_events"); auto matches = bpftrace_.find_wildcard_matches(attach_point->target, attach_point->func, *symbol_stream); for (auto &match : matches) { std::string tracepoint_struct = TracepointFormatParser::get_struct_name(attach_point->target, match); structs[tracepoint_struct] = bpftrace_.structs_[tracepoint_struct].fields; } } } else { structs[type.cast_type] = bpftrace_.structs_[type.cast_type].fields; } for (auto it : structs) { std::string cast_type = it.first; FieldsMap fields = it.second; if (fields.count(acc.field) == 0) { ERR("Struct/union of type '" << cast_type << "' does not contain " << "a field named '" << acc.field << "'", acc.loc); } else { acc.type = fields[acc.field].type; acc.type.is_internal = type.is_internal; } } } void SemanticAnalyser::visit(Cast &cast) { cast.expr->accept(*this); auto &intcasts = getIntcasts(); auto k_v = intcasts.find(cast.cast_type); int cast_size; if (k_v == intcasts.end() && bpftrace_.structs_.count(cast.cast_type) == 0) { ERR("Unknown struct/union: '" << cast.cast_type << "'", cast.loc); return; } if (cast.is_pointer) { cast_size = sizeof(uintptr_t); cast.type = SizedType(Type::cast, cast_size, cast.cast_type); cast.type.is_pointer = cast.is_pointer; return; } if (k_v != intcasts.end()) { auto &v = k_v->second; cast.type = SizedType(Type::integer, std::get<0>(v), std::get<1>(v), k_v->first); auto rhs = cast.expr->type.type; if (! (rhs == Type::integer || rhs == Type::cast)) { ERR("Casts are not supported for type: \"" << rhs << "\"", cast.loc); } return; } cast_size = bpftrace_.structs_[cast.cast_type].size; cast.type = SizedType(Type::cast, cast_size, cast.cast_type); cast.type.is_pointer = cast.is_pointer; } void SemanticAnalyser::visit(ExprStatement &expr) { expr.expr->accept(*this); } void SemanticAnalyser::visit(AssignMapStatement &assignment) { assignment.map->accept(*this); assignment.expr->accept(*this); assign_map_type(*assignment.map, assignment.expr->type); const std::string &map_ident = assignment.map->ident; if (assignment.expr->type.type == Type::cast) { std::string cast_type = assignment.expr->type.cast_type; std::string curr_cast_type = map_val_[map_ident].cast_type; if (curr_cast_type != "" && curr_cast_type != cast_type) { ERR("Type mismatch for " << map_ident << ": " << "trying to assign value of type '" << cast_type << "' when map already contains a value of type '" << curr_cast_type, assignment.loc); } else { map_val_[map_ident].cast_type = cast_type; map_val_[map_ident].is_internal = true; } } } void SemanticAnalyser::visit(AssignVarStatement &assignment) { assignment.expr->accept(*this); std::string var_ident = assignment.var->ident; auto search = variable_val_.find(var_ident); assignment.var->type = assignment.expr->type; if (search != variable_val_.end()) { if (search->second.type == Type::none) { if (is_final_pass()) { error("Undefined variable: " + var_ident, assignment.loc); } else { search->second = assignment.expr->type; } } else if (search->second.type != assignment.expr->type.type) { ERR("Type mismatch for " << var_ident << ": " << "trying to assign value of type '" << assignment.expr->type << "' when variable already contains a value of type '" << search->second, assignment.loc); } } else { // This variable hasn't been seen before variable_val_.insert({var_ident, assignment.expr->type}); assignment.var->type = assignment.expr->type; } if (assignment.expr->type.type == Type::cast) { std::string cast_type = assignment.expr->type.cast_type; std::string curr_cast_type = variable_val_[var_ident].cast_type; if (curr_cast_type != "" && curr_cast_type != cast_type) { ERR("Type mismatch for " << var_ident << ": " << "trying to assign value of type '" << cast_type << "' when variable already contains a value of type '" << curr_cast_type, assignment.loc); } else { variable_val_[var_ident].cast_type = cast_type; } } } void SemanticAnalyser::visit(Predicate &pred) { pred.expr->accept(*this); if (is_final_pass() && ((pred.expr->type.type != Type::integer) && (!(pred.expr->type.is_pointer && pred.expr->type.type == Type::cast)))) { ERR("Invalid type for predicate: " << pred.expr->type.type, pred.loc); } } void SemanticAnalyser::visit(AttachPoint &ap) { ap.provider = probetypeName(ap.provider); if (ap.provider == "kprobe" || ap.provider == "kretprobe") { if (ap.target != "") error("kprobes should not have a target", ap.loc); if (ap.func == "") error("kprobes should be attached to a function", ap.loc); } else if (ap.provider == "uprobe" || ap.provider == "uretprobe") { if (ap.target == "") error(ap.provider + " should have a target", ap.loc); if (ap.func == "" && ap.address == 0) error(ap.provider + " should be attached to a function and/or address", ap.loc); if (ap.provider == "uretprobe" && ap.func_offset != 0) error("uretprobes can not be attached to a function offset", ap.loc); auto paths = resolve_binary_path(ap.target, bpftrace_.pid_); switch (paths.size()) { case 0: error("uprobe target file '" + ap.target + "' does not exist or is not executable", ap.loc); break; case 1: ap.target = paths.front(); break; default: // If we are doing a PATH lookup (ie not glob), we follow shell // behavior and take the first match. if (ap.target.find("*") == std::string::npos) { warning("attaching to uprobe target file '" + paths.front() + "' but matched " + std::to_string(paths.size()) + " binaries", ap.loc); ap.target = paths.front(); } else error("uprobe target file '" + ap.target + "' must refer to a unique binary but matched " + std::to_string(paths.size()), ap.loc); } } else if (ap.provider == "usdt") { if (ap.func == "") error("usdt probe must have a target function or wildcard", ap.loc); if (ap.target != "") { auto paths = resolve_binary_path(ap.target); switch (paths.size()) { case 0: error("usdt target file '" + ap.target + "' does not exist or is not executable", ap.loc); break; case 1: ap.target = paths.front(); break; default: // If we are doing a PATH lookup (ie not glob), we follow shell // behavior and take the first match. if (ap.target.find("*") == std::string::npos) { warning("attaching to usdt target file '" + paths.front() + "' but matched " + std::to_string(paths.size()) + " binaries", ap.loc); ap.target = paths.front(); } else error("usdt target file '" + ap.target + "' must refer to a unique binary but matched " + std::to_string(paths.size()), ap.loc); } } if (bpftrace_.pid_ > 0) { USDTHelper::probes_for_pid(bpftrace_.pid_); } else if (ap.target != "") { USDTHelper::probes_for_path(ap.target); } else { error("usdt probe must specify at least path or pid to probe", ap.loc); } } else if (ap.provider == "tracepoint") { if (ap.target == "" || ap.func == "") error("tracepoint probe must have a target", ap.loc); } else if (ap.provider == "profile") { if (ap.target == "") error("profile probe must have unit of time", ap.loc); else if (ap.target != "hz" && ap.target != "us" && ap.target != "ms" && ap.target != "s") error(ap.target + " is not an accepted unit of time", ap.loc); if (ap.func != "") error("profile probe must have an integer frequency", ap.loc); else if (ap.freq <= 0) error("profile frequency should be a positive integer", ap.loc); } else if (ap.provider == "interval") { if (ap.target == "") error("interval probe must have unit of time", ap.loc); else if (ap.target != "ms" && ap.target != "s") error(ap.target + " is not an accepted unit of time", ap.loc); if (ap.func != "") error("interval probe must have an integer frequency", ap.loc); } else if (ap.provider == "software") { if (ap.target == "") error("software probe must have a software event name", ap.loc); else { bool found = false; for (auto &probeListItem : SW_PROBE_LIST) { if (ap.target == probeListItem.path || (!probeListItem.alias.empty() && ap.target == probeListItem.alias)) { found = true; break; } } if (!found) error(ap.target + " is not a software probe", ap.loc); } if (ap.func != "") error("software probe can only have an integer count", ap.loc); else if (ap.freq < 0) error("software count should be a positive integer", ap.loc); } else if (ap.provider == "watchpoint") { if (!ap.addr) error("watchpoint must be attached to a non-zero address", ap.loc); if (ap.len != 1 && ap.len != 2 && ap.len != 4 && ap.len != 8) error("watchpoint length must be one of (1,2,4,8)", ap.loc); std::sort(ap.mode.begin(), ap.mode.end()); for (const char c : ap.mode) { if (c != 'r' && c != 'w' && c != 'x') error("watchpoint mode must be combination of (r,w,x)", ap.loc); } for (size_t i = 0; i < ap.mode.size() - 1; ++i) { if (ap.mode[i] == ap.mode[i+1]) error("watchpoint modes may not be duplicated", ap.loc); } if (ap.mode == "rx" || ap.mode == "wx" || ap.mode == "rwx") error("watchpoint modes (rx, wx, rwx) not allowed", ap.loc); } else if (ap.provider == "hardware") { if (ap.target == "") error("hardware probe must have a hardware event name", ap.loc); else { bool found = false; for (auto &probeListItem : HW_PROBE_LIST) { if (ap.target == probeListItem.path || (!probeListItem.alias.empty() && ap.target == probeListItem.alias)) { found = true; break; } } if (!found) error(ap.target + " is not a hardware probe", ap.loc); } if (ap.func != "") error("hardware probe can only have an integer count", ap.loc); else if (ap.freq < 0) error("hardware frequency should be a positive integer", ap.loc); } else if (ap.provider == "BEGIN" || ap.provider == "END") { if (ap.target != "" || ap.func != "") error("BEGIN/END probes should not have a target", ap.loc); if (is_final_pass()) { if (ap.provider == "BEGIN") { if (has_begin_probe_) error("More than one BEGIN probe defined", ap.loc); has_begin_probe_ = true; } if (ap.provider == "END") { if (has_end_probe_) error("More than one END probe defined", ap.loc); has_end_probe_ = true; } } } else { ERR("Invalid provider: '" << ap.provider << "'", ap.loc); } } void SemanticAnalyser::visit(Probe &probe) { // Clear out map of variable names - variables should be probe-local variable_val_.clear(); probe_ = &probe; for (AttachPoint *ap : *probe.attach_points) { ap->accept(*this); } if (probe.pred) { probe.pred->accept(*this); } for (Statement *stmt : *probe.stmts) { stmt->accept(*this); } } void SemanticAnalyser::visit(Program &program) { for (Probe *probe : *program.probes) probe->accept(*this); } int SemanticAnalyser::analyse() { // Multiple passes to handle variables being used before they are defined std::string errors; for (pass_ = 1; pass_ <= num_passes_; pass_++) { root_->accept(*this); errors = err_.str(); if (!errors.empty()) { out_ << errors; return pass_; } } return 0; } int SemanticAnalyser::create_maps(bool debug) { uint32_t failed_maps = 0; auto is_invalid_map = [](int a) -> uint8_t { return a < 0 ? 1 : 0; }; for (auto &map_val : map_val_) { std::string map_name = map_val.first; SizedType type = map_val.second; auto search_args = map_key_.find(map_name); if (search_args == map_key_.end()) { out_ << "map key \"" << map_name << "\" not found" << std::endl; abort(); } auto &key = search_args->second; if (debug) bpftrace_.maps_[map_name] = std::make_unique(map_name, type, key); else { if (type.type == Type::lhist) { // store lhist args to the bpftrace::Map auto map_args = map_args_.find(map_name); if (map_args == map_args_.end()) { out_ << "map arg \"" << map_name << "\" not found" << std::endl; abort(); } Expression &min_arg = *map_args->second.at(1); Expression &max_arg = *map_args->second.at(2); Expression &step_arg = *map_args->second.at(3); Integer &min = static_cast(min_arg); Integer &max = static_cast(max_arg); Integer &step = static_cast(step_arg); bpftrace_.maps_[map_name] = std::make_unique(map_name, type, key, min.n, max.n, step.n, bpftrace_.mapmax_); failed_maps += is_invalid_map(bpftrace_.maps_[map_name]->mapfd_); } else bpftrace_.maps_[map_name] = std::make_unique(map_name, type, key, bpftrace_.mapmax_); } } for (StackType stack_type : needs_stackid_maps_) { // The stack type doesn't matter here, so we use kstack to force SizedType // to set stack_size. if (debug) { bpftrace_.stackid_maps_[stack_type] = std::make_unique(SizedType(Type::kstack, stack_type)); } else { bpftrace_.stackid_maps_[stack_type] = std::make_unique(SizedType(Type::kstack, stack_type)); failed_maps += is_invalid_map(bpftrace_.stackid_maps_[stack_type]->mapfd_); } } if (debug) { if (needs_join_map_) { // join uses map storage as we'd like to process data larger than can fit on the BPF stack. std::string map_ident = "join"; SizedType type = SizedType(Type::join, 8 + 8 + bpftrace_.join_argnum_ * bpftrace_.join_argsize_); MapKey key; bpftrace_.join_map_ = std::make_unique(map_ident, type, key); } if (needs_elapsed_map_) { std::string map_ident = "elapsed"; SizedType type = SizedType(Type::integer, 8); MapKey key; bpftrace_.elapsed_map_ = std::make_unique(map_ident, type, key); } bpftrace_.perf_event_map_ = std::make_unique(BPF_MAP_TYPE_PERF_EVENT_ARRAY); } else { if (needs_join_map_) { // join uses map storage as we'd like to process data larger than can fit on the BPF stack. std::string map_ident = "join"; SizedType type = SizedType(Type::join, 8 + 8 + bpftrace_.join_argnum_ * bpftrace_.join_argsize_); MapKey key; bpftrace_.join_map_ = std::make_unique(map_ident, type, key, 1); failed_maps += is_invalid_map(bpftrace_.join_map_->mapfd_); } if (needs_elapsed_map_) { std::string map_ident = "elapsed"; SizedType type = SizedType(Type::integer, 8); MapKey key; bpftrace_.elapsed_map_ = std::make_unique(map_ident, type, key, 1); failed_maps += is_invalid_map(bpftrace_.elapsed_map_->mapfd_); } bpftrace_.perf_event_map_ = std::make_unique(BPF_MAP_TYPE_PERF_EVENT_ARRAY); failed_maps += is_invalid_map(bpftrace_.perf_event_map_->mapfd_); } if (failed_maps > 0) { out_ << "Creation of the required BPF maps has failed." << std::endl; out_ << "Make sure you have all the required permissions and are not"; out_ << " confined (e.g. like" << std::endl; out_ << "snapcraft does). `dmesg` will likely have useful output for"; out_ << " further troubleshooting" << std::endl; } return failed_maps; } bool SemanticAnalyser::is_final_pass() const { return pass_ == num_passes_; } bool SemanticAnalyser::check_assignment(const Call &call, bool want_map, bool want_var, bool want_map_key) { if (want_map && want_var && want_map_key) { if (!call.map && !call.var && !call.key_for_map) { error(call.func + "() should be assigned to a map or a variable, or be " "used as a map key", call.loc); return false; } } else if (want_map && want_var) { if (!call.map && !call.var) { error(call.func + "() should be assigned to a map or a variable", call.loc); return false; } } else if (want_map && want_map_key) { if (!call.map && !call.key_for_map) { error(call.func + "() should be assigned to a map or be used as a map key", call.loc); return false; } } else if (want_var && want_map_key) { if (!call.var && !call.key_for_map) { error(call.func + "() should be assigned to a variable or be used as a map key", call.loc); return false; } } else if (want_map) { if (!call.map) { error(call.func + "() should be assigned to a map", call.loc); return false; } } else if (want_var) { if (!call.var) { error(call.func + "() should be assigned to a variable", call.loc); return false; } } else if (want_map_key) { if (!call.key_for_map) { error(call.func + "() should be used as a map key", call.loc); return false; } } else { if (call.map || call.var || call.key_for_map) { error(call.func + "() should not be used in an assignment or as a map key", call.loc); return false; } } return true; } bool SemanticAnalyser::check_nargs(const Call &call, size_t expected_nargs) { std::stringstream err; std::vector::size_type nargs = 0; if (call.vargs) nargs = call.vargs->size(); if (nargs != expected_nargs) { if (expected_nargs == 0) err << call.func << "() requires no arguments"; else if (expected_nargs == 1) err << call.func << "() requires one argument"; else err << call.func << "() requires " << expected_nargs << " arguments"; err << " (" << nargs << " provided)"; error(err.str(), call.loc); return false; } return true; } bool SemanticAnalyser::check_varargs(const Call &call, size_t min_nargs, size_t max_nargs) { std::vector::size_type nargs = 0; std::stringstream err; if (call.vargs) nargs = call.vargs->size(); if (nargs < min_nargs) { if (min_nargs == 1) err << call.func << "() requires at least one argument"; else err << call.func << "() requires at least " << min_nargs << " arguments"; err << " (" << nargs << " provided)"; error(err.str(), call.loc); return false; } else if (nargs > max_nargs) { if (max_nargs == 0) err << call.func << "() requires no arguments"; else if (max_nargs == 1) err << call.func << "() takes up to one argument"; else err << call.func << "() takes up to " << max_nargs << " arguments"; err << " (" << nargs << " provided)"; error(err.str(), call.loc); return false; } return true; } bool SemanticAnalyser::check_arg(const Call &call, Type type, int arg_num, bool want_literal) { if (!call.vargs) return false; auto &arg = *call.vargs->at(arg_num); if (want_literal && (!arg.is_literal || arg.type.type != type)) { ERR(call.func << "() expects a " << type << " literal" " (" << arg.type.type << " provided)", call.loc); return false; } else if (is_final_pass() && arg.type.type != type) { ERR(call.func << "() only supports " << type << " arguments" << " (" << arg.type.type << " provided)", call.loc); return false; } return true; } bool SemanticAnalyser::check_symbol(const Call &call, int arg_num __attribute__((unused))) { if (!call.vargs) return false; auto &arg = static_cast(*call.vargs->at(0)).str; std::string re = "^[a-zA-Z0-9./_-]+$"; bool is_valid = std::regex_match(arg, std::regex(re)); if (!is_valid) { ERR(call.func << "() expects a string that is a valid symbol (" << re << ") as input" << " (\"" << arg << "\" provided)", call.loc); return false; } return true; } /* * assign_map_type * * Semantic analysis for assigning a value of the provided type * to the given map. */ void SemanticAnalyser::assign_map_type(const Map &map, const SizedType &type) { const std::string &map_ident = map.ident; auto search = map_val_.find(map_ident); if (search != map_val_.end()) { if (search->second.type == Type::none) { if (is_final_pass()) { error("Undefined map: " + map_ident, map.loc); } else { search->second = type; } } else if (search->second.type != type.type) { ERR("Type mismatch for " << map_ident << ": " << "trying to assign value of type '" << type << "' when map already contains a value of type '" << search->second, map.loc); } } else { // This map hasn't been seen before map_val_.insert({map_ident, type}); if (map_val_[map_ident].type == Type::integer) { // Store all integer values as 64-bit in maps, so that there will // be space for any integer to be assigned to the map later map_val_[map_ident].size = 8; } } } } // namespace ast } // namespace bpftrace bpftrace-0.9.4/src/ast/semantic_analyser.h000066400000000000000000000053751361633214400205570ustar00rootroot00000000000000#pragma once #include #include #include #include "ast.h" #include "bpffeature.h" #include "bpftrace.h" #include "map.h" #include "types.h" namespace bpftrace { namespace ast { class SemanticAnalyser : public Visitor { public: explicit SemanticAnalyser(Node *root, BPFtrace &bpftrace, BPFfeature &feature, std::ostream &out = std::cerr) : root_(root), bpftrace_(bpftrace), feature_(feature), out_(out) { } void visit(Integer &integer) override; void visit(PositionalParameter ¶m) override; void visit(String &string) override; void visit(StackMode &mode) override; void visit(Identifier &identifier) override; void visit(Builtin &builtin) override; void visit(Call &call) override; void visit(Map &map) override; void visit(Variable &var) override; void visit(Binop &binop) override; void visit(Unop &unop) override; void visit(Ternary &ternary) override; void visit(FieldAccess &acc) override; void visit(ArrayAccess &arr) override; void visit(Cast &cast) override; void visit(ExprStatement &expr) override; void visit(AssignMapStatement &assignment) override; void visit(AssignVarStatement &assignment) override; void visit(If &if_block) override; void visit(Unroll &unroll) override; void visit(Predicate &pred) override; void visit(AttachPoint &ap) override; void visit(Probe &probe) override; void visit(Program &program) override; int analyse(); int create_maps(bool debug=false); private: Node *root_; BPFtrace &bpftrace_; BPFfeature &feature_; std::ostream &out_; std::ostringstream err_; int pass_; const int num_passes_ = 10; bool is_final_pass() const; bool check_assignment(const Call &call, bool want_map, bool want_var, bool want_map_key); bool check_nargs(const Call &call, size_t expected_nargs); bool check_varargs(const Call &call, size_t min_nargs, size_t max_nargs); bool check_arg(const Call &call, Type type, int arg_num, bool want_literal=false); bool check_symbol(const Call &call, int arg_num); void check_stack_call(Call &call, Type type); void error(const std::string &msg, const location &loc); void warning(const std::string &msg, const location &loc); void assign_map_type(const Map &map, const SizedType &type); Probe *probe_; std::map variable_val_; std::map map_val_; std::map map_key_; std::map map_args_; std::unordered_set needs_stackid_maps_; bool needs_join_map_ = false; bool needs_elapsed_map_ = false; bool has_begin_probe_ = false; bool has_end_probe_ = false; }; } // namespace ast } // namespace bpftrace bpftrace-0.9.4/src/attached_probe.cpp000066400000000000000000000663371361633214400175730ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "attached_probe.h" #include "bpftrace.h" #include "utils.h" #include "bcc_syms.h" #include "bcc_usdt.h" #include "bcc_elf.h" #include "libbpf.h" #include "utils.h" #include "list.h" #include "disasm.h" #include #include namespace bpftrace { /* * Kernel functions that are unsafe to trace are excluded in the Kernel with * `notrace`. However, the ones below are not excluded. */ const std::set banned_kretprobes = { "_raw_spin_lock", "_raw_spin_lock_irqsave", "_raw_spin_unlock_irqrestore", "queued_spin_lock_slowpath", }; bpf_probe_attach_type attachtype(ProbeType t) { switch (t) { case ProbeType::kprobe: return BPF_PROBE_ENTRY; break; case ProbeType::kretprobe: return BPF_PROBE_RETURN; break; case ProbeType::uprobe: return BPF_PROBE_ENTRY; break; case ProbeType::uretprobe: return BPF_PROBE_RETURN; break; case ProbeType::usdt: return BPF_PROBE_ENTRY; break; default: std::cerr << "invalid probe attachtype \"" << probetypeName(t) << "\"" << std::endl; abort(); } } bpf_prog_type progtype(ProbeType t) { switch (t) { case ProbeType::kprobe: return BPF_PROG_TYPE_KPROBE; break; case ProbeType::kretprobe: return BPF_PROG_TYPE_KPROBE; break; case ProbeType::uprobe: return BPF_PROG_TYPE_KPROBE; break; case ProbeType::uretprobe: return BPF_PROG_TYPE_KPROBE; break; case ProbeType::usdt: return BPF_PROG_TYPE_KPROBE; break; case ProbeType::tracepoint: return BPF_PROG_TYPE_TRACEPOINT; break; case ProbeType::profile: return BPF_PROG_TYPE_PERF_EVENT; break; case ProbeType::interval: return BPF_PROG_TYPE_PERF_EVENT; break; case ProbeType::software: return BPF_PROG_TYPE_PERF_EVENT; break; case ProbeType::watchpoint: return BPF_PROG_TYPE_PERF_EVENT; break; case ProbeType::hardware: return BPF_PROG_TYPE_PERF_EVENT; break; default: std::cerr << "program type not found" << std::endl; abort(); } } void check_banned_kretprobes(std::string const& kprobe_name) { if (banned_kretprobes.find(kprobe_name) != banned_kretprobes.end()) { std::cerr << "error: kretprobe:" << kprobe_name << " can't be used as it might lock up your system." << std::endl; exit(1); } } AttachedProbe::AttachedProbe(Probe &probe, std::tuple func, bool safe_mode) : probe_(probe), func_(func) { load_prog(); if (bt_verbose) std::cerr << "Attaching " << probe_.name << std::endl; switch (probe_.type) { case ProbeType::kprobe: attach_kprobe(safe_mode); break; case ProbeType::kretprobe: check_banned_kretprobes(probe_.attach_point); attach_kprobe(safe_mode); break; case ProbeType::uprobe: case ProbeType::uretprobe: attach_uprobe(safe_mode); break; case ProbeType::tracepoint: attach_tracepoint(); break; case ProbeType::profile: attach_profile(); break; case ProbeType::interval: attach_interval(); break; case ProbeType::software: attach_software(); break; case ProbeType::hardware: attach_hardware(); break; default: std::cerr << "invalid attached probe type \"" << probetypeName(probe_.type) << "\"" << std::endl; abort(); } } AttachedProbe::AttachedProbe(Probe &probe, std::tuple func, int pid) : probe_(probe), func_(func) { load_prog(); switch (probe_.type) { case ProbeType::usdt: attach_usdt(pid); break; case ProbeType::watchpoint: attach_watchpoint(pid, probe.mode); break; default: std::cerr << "invalid attached probe type \"" << probetypeName(probe_.type) << "\"" << std::endl; abort(); } } AttachedProbe::~AttachedProbe() { if (progfd_ >= 0) close(progfd_); int err = 0; for (int perf_event_fd : perf_event_fds_) { err = bpf_close_perf_event_fd(perf_event_fd); if (err) std::cerr << "Error closing perf event FDs for probe: " << probe_.name << std::endl; } err = 0; switch (probe_.type) { case ProbeType::kprobe: case ProbeType::kretprobe: err = bpf_detach_kprobe(eventname().c_str()); break; case ProbeType::uprobe: case ProbeType::uretprobe: case ProbeType::usdt: err = bpf_detach_uprobe(eventname().c_str()); break; case ProbeType::tracepoint: err = bpf_detach_tracepoint(probe_.path.c_str(), eventname().c_str()); break; case ProbeType::profile: case ProbeType::interval: case ProbeType::software: case ProbeType::watchpoint: case ProbeType::hardware: break; default: std::cerr << "invalid attached probe type \"" << probetypeName(probe_.type) << "\" at destructor" << std::endl; abort(); } if (err) std::cerr << "Error detaching probe: " << probe_.name << std::endl; } std::string AttachedProbe::eventprefix() const { switch (attachtype(probe_.type)) { case BPF_PROBE_ENTRY: return "p_"; case BPF_PROBE_RETURN: return "r_"; default: std::cerr << "invalid eventprefix" << std::endl; abort(); } } std::string AttachedProbe::eventname() const { std::ostringstream offset_str; std::string index_str = "_" + std::to_string(probe_.index); switch (probe_.type) { case ProbeType::kprobe: case ProbeType::kretprobe: offset_str << std::hex << offset_; return eventprefix() + sanitise(probe_.attach_point) + "_" + offset_str.str() + index_str; case ProbeType::uprobe: case ProbeType::uretprobe: case ProbeType::usdt: offset_str << std::hex << offset_; return eventprefix() + sanitise(probe_.path) + "_" + offset_str.str() + index_str; case ProbeType::tracepoint: return probe_.attach_point; default: std::cerr << "invalid eventname probe \"" << probetypeName(probe_.type) << "\"" << std::endl; abort(); } } std::string AttachedProbe::sanitise(const std::string &str) { /* * Characters such as "." in event names are rejected by the kernel, * so sanitize: */ return std::regex_replace(str, std::regex("[^A-Za-z0-9_]"), "_"); } static int sym_name_cb(const char *symname, uint64_t start, uint64_t size, void *p) { struct symbol *sym = static_cast(p); if (sym->name == symname) { sym->start = start; sym->size = size; return -1; } return 0; } static int sym_address_cb(const char *symname, uint64_t start, uint64_t size, void *p) { struct symbol *sym = static_cast(p); if (sym->address >= start && sym->address < (start + size)) { sym->start = start; sym->size = size; sym->name = symname; return -1; } return 0; } static uint64_t resolve_offset(const std::string &path, const std::string &symbol, uint64_t loc) { bcc_symbol bcc_sym; if (bcc_resolve_symname(path.c_str(), symbol.c_str(), loc, 0, nullptr, &bcc_sym)) throw std::runtime_error("Could not resolve symbol: " + path + ":" + symbol); return bcc_sym.offset; } static void check_alignment(std::string &path, std::string &symbol, uint64_t sym_offset, uint64_t func_offset, bool safe_mode, ProbeType type) { Disasm dasm(path); AlignState aligned = dasm.is_aligned(sym_offset, func_offset); std::string probe_name = probetypeName(type); std::string tmp = path + ":" + symbol + "+" + std::to_string(func_offset); if (AlignState::Ok == aligned) return; // If we did not allow unaligned uprobes in the // compile time, force the safe mode now. #ifndef HAVE_UNSAFE_PROBE safe_mode = true; #endif switch (aligned) { case AlignState::NotAlign: if (safe_mode) throw std::runtime_error("Could not add " + probe_name + " into middle of instruction: " + tmp); else std::cerr << "Unsafe " + probe_name + " in the middle of the instruction: " << tmp << std::endl; break; case AlignState::Fail: if (safe_mode) throw std::runtime_error("Failed to check if " + probe_name + " is in proper place: " + tmp); else std::cerr << "Unchecked " + probe_name + ": " << tmp << std::endl; break; case AlignState::NotSupp: if (safe_mode) throw std::runtime_error("Can't check if " + probe_name + " is in proper place (compiled without " "(k|u)probe offset support): " + tmp); else std::cerr << "Unchecked " + probe_name + " : " << tmp << std::endl; break; default: throw std::runtime_error("Internal error: " + tmp); } } void AttachedProbe::resolve_offset_uprobe(bool safe_mode) { struct bcc_symbol_option option = { }; struct symbol sym = { }; std::string &symbol = probe_.attach_point; uint64_t func_offset = probe_.func_offset; sym.name = ""; option.use_debug_file = 1; option.use_symbol_type = 0xffffffff; if (symbol.empty()) { sym.address = probe_.address; bcc_elf_foreach_sym(probe_.path.c_str(), sym_address_cb, &option, &sym); symbol = sym.name; func_offset = probe_.address - sym.start; if (!sym.start) { std::stringstream ss; ss << "0x" << std::hex << probe_.address; throw std::runtime_error("Could not resolve address: " + probe_.path + ":" + ss.str()); } } else { sym.name = symbol; bcc_elf_foreach_sym(probe_.path.c_str(), sym_name_cb, &option, &sym); if (!sym.start) throw std::runtime_error("Could not resolve symbol: " + probe_.path + ":" + symbol); } if (probe_.type == ProbeType::uretprobe && func_offset != 0) { std::stringstream msg; msg << "uretprobes cannot be attached at function offset. " << "(address resolved to: " << symbol << "+" << func_offset << ")"; throw std::runtime_error(msg.str()); } if (func_offset >= sym.size) { std::stringstream ss; ss << sym.size; throw std::runtime_error("Offset outside the function bounds ('" + symbol + "' size is " + ss.str() + ")"); } uint64_t sym_offset = resolve_offset(probe_.path, probe_.attach_point, probe_.loc); offset_ = sym_offset + func_offset; // If we are not aligned to the start of the symbol, // check if we are on the instruction boundary. if (func_offset == 0) return; check_alignment( probe_.path, symbol, sym_offset, func_offset, safe_mode, probe_.type); } // find vmlinux file containing the given symbol information static std::string find_vmlinux(const struct vmlinux_location *locs, struct symbol &sym) { struct bcc_symbol_option option = {}; option.use_debug_file = 0; option.use_symbol_type = BCC_SYM_ALL_TYPES; struct utsname buf; uname(&buf); for (int i = 0; locs[i].path; i++) { if (locs[i].raw) continue; // This file is for BTF. skip char path[PATH_MAX + 1]; snprintf(path, PATH_MAX, locs[i].path, buf.release); if (access(path, R_OK)) continue; bcc_elf_foreach_sym(path, sym_name_cb, &option, &sym); if (sym.start) { if (bt_verbose) std::cout << "vmlinux: using " << path << std::endl; return path; } } return ""; } void AttachedProbe::resolve_offset_kprobe(bool safe_mode) { struct symbol sym = {}; std::string &symbol = probe_.attach_point; uint64_t func_offset = probe_.func_offset; offset_ = func_offset; #ifndef HAVE_UNSAFE_PROBE safe_mode = true; #endif if (func_offset == 0) return; sym.name = symbol; const struct vmlinux_location *locs = vmlinux_locs; struct vmlinux_location locs_env[] = { { nullptr, true }, { nullptr, false }, }; char *env_path = std::getenv("BPFTRACE_VMLINUX"); if (env_path) { locs_env[0].path = env_path; locs = locs_env; } std::string path = find_vmlinux(locs, sym); if (path.empty()) { if (safe_mode) { std::stringstream buf; buf << "Could not resolve symbol " << symbol << "."; buf << " Use BPFTRACE_VMLINUX env variable to specify vmlinux path."; #ifdef HAVE_UNSAFE_PROBE buf << " Use --unsafe to skip the userspace check."; #else buf << " Compile bpftrace with ALLOW_UNAFE_PROBE option to force skip " "the check."; #endif throw std::runtime_error(buf.str()); } else { // linux kernel checks alignment, but not the function bounds if (bt_verbose) std::cout << "Could not resolve symbol " << symbol << ". Skip offset checking." << std::endl; return; } } if (func_offset >= sym.size) throw std::runtime_error("Offset outside the function bounds ('" + symbol + "' size is " + std::to_string(sym.size) + ")"); uint64_t sym_offset = resolve_offset(path, probe_.attach_point, probe_.loc); check_alignment( path, symbol, sym_offset, func_offset, safe_mode, probe_.type); } /** * Search for LINUX_VERSION_CODE in the vDSO, returning 0 if it can't be found. */ static unsigned _find_version_note(unsigned long base) { auto ehdr = reinterpret_cast(base); for (int i = 0; i < ehdr->e_shnum; i++) { auto shdr = reinterpret_cast( base + ehdr->e_shoff + (i * ehdr->e_shentsize) ); if (shdr->sh_type == SHT_NOTE) { auto ptr = reinterpret_cast(base + shdr->sh_offset); auto end = ptr + shdr->sh_size; while (ptr < end) { auto nhdr = reinterpret_cast(ptr); ptr += sizeof *nhdr; auto name = ptr; ptr += (nhdr->n_namesz + sizeof(ElfW(Word)) - 1) & -sizeof(ElfW(Word)); auto desc = ptr; ptr += (nhdr->n_descsz + sizeof(ElfW(Word)) - 1) & -sizeof(ElfW(Word)); if ((nhdr->n_namesz > 5 && !memcmp(name, "Linux", 5)) && nhdr->n_descsz == 4 && !nhdr->n_type) return *reinterpret_cast(desc); } } } return 0; } /** * Find a LINUX_VERSION_CODE matching the host kernel. The build-time constant * may not match if bpftrace is compiled on a different Linux version than it's * used on, e.g. if built with Docker. */ static unsigned kernel_version(int attempt) { switch (attempt) { case 0: { // Fetch LINUX_VERSION_CODE from the vDSO .note section, falling back on // the build-time constant if unavailable. This always matches the // running kernel, but is not supported on arm32. unsigned code = 0; unsigned long base = getauxval(AT_SYSINFO_EHDR); if (base && !memcmp(reinterpret_cast(base), ELFMAG, 4)) code = _find_version_note(base); if (! code) code = LINUX_VERSION_CODE; return code; } case 1: struct utsname utsname; if (uname(&utsname) < 0) return 0; unsigned x, y, z; if (sscanf(utsname.release, "%u.%u.%u", &x, &y, &z) != 3) return 0; return KERNEL_VERSION(x, y, z); case 2: { // Try to get the definition of LINUX_VERSION_CODE at runtime. std::ifstream linux_version_header{"/usr/include/linux/version.h"}; const std::string content{std::istreambuf_iterator(linux_version_header), std::istreambuf_iterator()}; const std::regex regex{"#define\\s+LINUX_VERSION_CODE\\s+(\\d+)"}; std::smatch match; if (std::regex_search(content.begin(), content.end(), match, regex)) return static_cast(std::stoi(match[1])); return 0; } default: break; } std::cerr << "invalid kernel version" << std::endl; abort(); } void AttachedProbe::load_prog() { uint8_t *insns = std::get<0>(func_); int prog_len = std::get<1>(func_); const char *license = "GPL"; int log_level = 0; char log_buf[probe_.log_size]; char name[STRING_SIZE], *namep; unsigned log_buf_size = sizeof (log_buf); { // Redirect stderr, so we don't get error messages from BCC StderrSilencer silencer; if (bt_debug == DebugLevel::kNone) silencer.silence(); if (bt_debug != DebugLevel::kNone) log_level = 15; if (bt_verbose) log_level = 1; // bpf_prog_load rejects colons in the probe name strncpy(name, probe_.name.c_str(), STRING_SIZE - 1); namep = name; if (strrchr(name, ':') != NULL) namep = strrchr(name, ':') + 1; for (int attempt = 0; attempt < 3; attempt++) { auto version = kernel_version(attempt); if (version == 0 && attempt > 0) { // Recent kernels don't check the version so we should try to call // bcc_prog_load during first iteration even if we failed to determine // the version. We should not do that in subsequent iterations to avoid // zeroing of log_buf on systems with older kernels. continue; } #ifdef HAVE_BCC_PROG_LOAD progfd_ = bcc_prog_load(progtype(probe_.type), namep, #else progfd_ = bpf_prog_load(progtype(probe_.type), namep, #endif reinterpret_cast(insns), prog_len, license, version, log_level, log_buf, log_buf_size); if (progfd_ >= 0) break; } } if (progfd_ < 0) { if (bt_verbose) { std::cerr << std::endl << "Error log: " << std::endl << log_buf << std::endl; if (errno == ENOSPC) { std::stringstream errmsg; errmsg << "Error: Failed to load program, verification log buffer " << "not big enough, try increasing the BPFTRACE_LOG_SIZE " << "environment variable beyond the current value of " << probe_.log_size << " bytes"; throw std::runtime_error(errmsg.str()); } } throw std::runtime_error("Error loading program: " + probe_.name + (bt_verbose ? "" : " (try -v)")); } if (bt_verbose) { struct bpf_prog_info info = {}; uint32_t info_len = sizeof(info); int ret; ret = bpf_obj_get_info(progfd_, &info, &info_len); if (ret == 0) { std::cout << std::endl << "Program ID: " << info.id << std::endl; } std::cout << std::endl << "Bytecode: " << std::endl << log_buf << std::endl; } } // XXX(mmarchini): bcc changed the signature of bpf_attach_kprobe, adding a new // int parameter at the end. Since there's no reliable way to feature-detect // this, we create a function pointer with the long signature and cast // bpf_attach_kprobe to this function pointer. If we're on an older bcc // version, bpf_attach_kprobe call will be augmented with an extra register // being used for the last parameter, even though this register won't be used // inside the function. Since the register won't be used this is kinda safe, // although not ideal. typedef int (*attach_probe_wrapper_signature)(int, enum bpf_probe_attach_type, const char*, const char*, uint64_t, int); void AttachedProbe::attach_kprobe(bool safe_mode) { resolve_offset_kprobe(safe_mode); int perf_event_fd = cast_signature( &bpf_attach_kprobe)(progfd_, attachtype(probe_.type), eventname().c_str(), probe_.attach_point.c_str(), offset_, 0); if (perf_event_fd < 0) { if (probe_.orig_name != probe_.name) { // a wildcard expansion couldn't probe something, just print a warning // as this is normal for some kernel functions (eg, do_debug()) std::cerr << "Warning: could not attach probe " << probe_.name << ", skipping." << std::endl; } else { // an explicit match failed, so fail as the user must have wanted it throw std::runtime_error("Error attaching probe: '" + probe_.name + "'"); } } perf_event_fds_.push_back(perf_event_fd); } void AttachedProbe::attach_uprobe(bool safe_mode) { resolve_offset_uprobe(safe_mode); int perf_event_fd = bpf_attach_uprobe(progfd_, attachtype(probe_.type), eventname().c_str(), probe_.path.c_str(), offset_, probe_.pid); if (perf_event_fd < 0) throw std::runtime_error("Error attaching probe: " + probe_.name); perf_event_fds_.push_back(perf_event_fd); } void AttachedProbe::attach_usdt(int pid) { struct bcc_usdt_location loc = {}; int err; void *ctx; if (pid) { //FIXME when iovisor/bcc#2604 is merged, optionally pass probe_.path ctx = bcc_usdt_new_frompid(pid, nullptr); if (!ctx) throw std::runtime_error("Error initializing context for probe: " + probe_.name + ", for PID: " + std::to_string(pid)); } else { ctx = bcc_usdt_new_frompath(probe_.path.c_str()); if (!ctx) throw std::runtime_error("Error initializing context for probe: " + probe_.name); } // TODO: fn_name may need a unique suffix for each attachment on the same probe: std::string fn_name = "probe_" + probe_.attach_point + "_1"; // see https://github.com/iovisor/bcc/pull/2294 for BCC_USDT_HAS_FULLY_SPECIFIED_PROBE #ifdef BCC_USDT_HAS_FULLY_SPECIFIED_PROBE if (probe_.ns == "") err = bcc_usdt_enable_probe(ctx, probe_.attach_point.c_str(), fn_name.c_str()); else err = bcc_usdt_enable_fully_specified_probe(ctx, probe_.ns.c_str(), probe_.attach_point.c_str(), fn_name.c_str()); #else err = bcc_usdt_enable_probe(ctx, probe_.attach_point.c_str(), fn_name.c_str()); #endif if (err) throw std::runtime_error("Error finding or enabling probe: " + probe_.name); auto u = USDTHelper::find(pid, probe_.path, probe_.ns, probe_.attach_point); probe_.path = std::get(u); err = bcc_usdt_get_location(ctx, probe_.ns.c_str(), probe_.attach_point.c_str(), 0, &loc); if (err) throw std::runtime_error("Error finding location for probe: " + probe_.name); probe_.loc = loc.address; offset_ = resolve_offset(probe_.path, probe_.attach_point, probe_.loc); int perf_event_fd = bpf_attach_uprobe(progfd_, attachtype(probe_.type), eventname().c_str(), probe_.path.c_str(), offset_, pid == 0 ? -1 : pid); if (perf_event_fd < 0) { if (pid) throw std::runtime_error("Error attaching probe: " + probe_.name + ", to PID: " + std::to_string(pid)); else throw std::runtime_error("Error attaching probe: " + probe_.name); } perf_event_fds_.push_back(perf_event_fd); } void AttachedProbe::attach_tracepoint() { int perf_event_fd = bpf_attach_tracepoint(progfd_, probe_.path.c_str(), eventname().c_str()); if (perf_event_fd < 0) throw std::runtime_error("Error attaching probe: " + probe_.name); perf_event_fds_.push_back(perf_event_fd); } void AttachedProbe::attach_profile() { int pid = -1; int group_fd = -1; uint64_t period, freq; if (probe_.path == "hz") { period = 0; freq = probe_.freq; } else if (probe_.path == "s") { period = probe_.freq * 1e9; freq = 0; } else if (probe_.path == "ms") { period = probe_.freq * 1e6; freq = 0; } else if (probe_.path == "us") { period = probe_.freq * 1e3; freq = 0; } else { std::cerr << "invalid profile path \"" << probe_.path << "\"" << std::endl; abort(); } std::vector cpus = get_online_cpus(); for (int cpu : cpus) { int perf_event_fd = bpf_attach_perf_event(progfd_, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_CLOCK, period, freq, pid, cpu, group_fd); if (perf_event_fd < 0) throw std::runtime_error("Error attaching probe: " + probe_.name); perf_event_fds_.push_back(perf_event_fd); } } void AttachedProbe::attach_interval() { int pid = -1; int group_fd = -1; int cpu = 0; uint64_t period; if (probe_.path == "s") { period = probe_.freq * 1e9; } else if (probe_.path == "ms") { period = probe_.freq * 1e6; } else { std::cerr << "invalid interval path \"" << probe_.path << "\"" << std::endl; abort(); } int perf_event_fd = bpf_attach_perf_event(progfd_, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_CLOCK, period, 0, pid, cpu, group_fd); if (perf_event_fd < 0) throw std::runtime_error("Error attaching probe: " + probe_.name); perf_event_fds_.push_back(perf_event_fd); } void AttachedProbe::attach_software() { int pid = -1; int group_fd = -1; uint64_t period = probe_.freq; uint64_t defaultp = 1; uint32_t type = 0; // from linux/perf_event.h, with aliases from perf: for (auto &probeListItem : SW_PROBE_LIST) { if (probe_.path == probeListItem.path || probe_.path == probeListItem.alias) { type = probeListItem.type; defaultp = probeListItem.defaultp; } } if (period == 0) period = defaultp; std::vector cpus = get_online_cpus(); for (int cpu : cpus) { int perf_event_fd = bpf_attach_perf_event(progfd_, PERF_TYPE_SOFTWARE, type, period, 0, pid, cpu, group_fd); if (perf_event_fd < 0) throw std::runtime_error("Error attaching probe: " + probe_.name); perf_event_fds_.push_back(perf_event_fd); } } void AttachedProbe::attach_hardware() { int pid = -1; int group_fd = -1; uint64_t period = probe_.freq; uint64_t defaultp = 1000000; uint32_t type = 0; // from linux/perf_event.h, with aliases from perf: for (auto &probeListItem : HW_PROBE_LIST) { if (probe_.path == probeListItem.path || probe_.path == probeListItem.alias) { type = probeListItem.type; defaultp = probeListItem.defaultp; } } if (period == 0) period = defaultp; std::vector cpus = get_online_cpus(); for (int cpu : cpus) { int perf_event_fd = bpf_attach_perf_event(progfd_, PERF_TYPE_HARDWARE, type, period, 0, pid, cpu, group_fd); if (perf_event_fd < 0) throw std::runtime_error("Error attaching probe: " + probe_.name); perf_event_fds_.push_back(perf_event_fd); } } void AttachedProbe::attach_watchpoint(int pid, const std::string& mode) { if (pid < 1) { throw std::runtime_error("pid not provided for " + probe_.name); } struct perf_event_attr attr = {}; attr.type = PERF_TYPE_BREAKPOINT; attr.size = sizeof(struct perf_event_attr); attr.config = 0; attr.bp_type = HW_BREAKPOINT_EMPTY; for (const char c : mode) { if (c == 'r') attr.bp_type |= HW_BREAKPOINT_R; else if (c == 'w') attr.bp_type |= HW_BREAKPOINT_W; else if (c == 'x') attr.bp_type |= HW_BREAKPOINT_X; } attr.bp_addr = probe_.addr; attr.bp_len = probe_.len; // Generate a notification every 1 event; we care about every event attr.sample_period = 1; int perf_event_fd = bpf_attach_perf_event_raw(progfd_, &attr, pid, -1, -1, 0); if (perf_event_fd < 0) throw std::runtime_error("Error attaching probe: " + probe_.name); perf_event_fds_.push_back(perf_event_fd); } } // namespace bpftrace bpftrace-0.9.4/src/attached_probe.h000066400000000000000000000024011361633214400172160ustar00rootroot00000000000000#pragma once #include #include #include #include "types.h" #include "libbpf.h" namespace bpftrace { bpf_probe_attach_type attachtype(ProbeType t); bpf_prog_type progtype(ProbeType t); class AttachedProbe { public: AttachedProbe(Probe &probe, std::tuple func, bool safe_mode); AttachedProbe(Probe &probe, std::tuple func, int pid); ~AttachedProbe(); AttachedProbe(const AttachedProbe &) = delete; AttachedProbe &operator=(const AttachedProbe &) = delete; private: std::string eventprefix() const; std::string eventname() const; static std::string sanitise(const std::string &str); void resolve_offset_kprobe(bool safe_mode); void resolve_offset_uprobe(bool safe_mode); void load_prog(); void attach_kprobe(bool safe_mode); void attach_uprobe(bool safe_mode); void attach_usdt(int pid); void attach_tracepoint(); void attach_profile(); void attach_interval(); void attach_software(); void attach_hardware(); void attach_watchpoint(int pid, const std::string &mode); Probe &probe_; std::tuple func_; std::vector perf_event_fds_; int progfd_ = -1; uint64_t offset_ = 0; }; } // namespace bpftrace bpftrace-0.9.4/src/bfd-disasm.cpp000066400000000000000000000052731361633214400166300ustar00rootroot00000000000000#include #include #include #include #include #include #include // bfd.h assumes everyone is using autotools and will error out unless // PACKAGE is defined. Some distros patch this check out. #define PACKAGE "bpftrace" #include #include #include "bcc_syms.h" #include "bcc_elf.h" #include "bfd-disasm.h" namespace bpftrace { BfdDisasm::BfdDisasm(std::string &path) : size_(0) { fd_ = open(path.c_str(), O_RDONLY); if (fd_ >= 0) { struct stat st; if (fstat(fd_, &st) == 0) size_ = st.st_size; } } BfdDisasm::~BfdDisasm() { if (fd_ >= 0) close(fd_); } static void get_exec_path(char *tpath, size_t size) { const char *path = "/proc/self/exe"; ssize_t len; len = readlink(path, tpath, size - 1); if (len < 0) len = 0; tpath[len] = 0; } static int fprintf_nop(void *out __attribute__((unused)), const char *fmt __attribute__((unused)), ...) { return 0; } static AlignState is_aligned_buf(void *buf, uint64_t size, uint64_t offset) { disassembler_ftype disassemble; struct disassemble_info info; char tpath[4096]; bfd *bfdf; get_exec_path(tpath, sizeof(tpath)); bfdf = bfd_openr(tpath, NULL); if (bfdf == NULL) return AlignState::Fail; if (!bfd_check_format(bfdf, bfd_object)) { bfd_close(bfdf); return AlignState::Fail; } init_disassemble_info(&info, stdout, fprintf_nop); info.arch = bfd_get_arch(bfdf); info.mach = bfd_get_mach(bfdf); info.buffer = static_cast(buf); info.buffer_length = size; disassemble_init_for_target(&info); #ifdef LIBBFD_DISASM_FOUR_ARGS_SIGNATURE disassemble = disassembler(info.arch, bfd_big_endian(bfdf), info.mach, bfdf); #else disassemble = disassembler(bfdf); #endif uint64_t pc = 0; int count; do { count = disassemble(pc, &info); pc += static_cast(count); if (pc == offset) { bfd_close(bfdf); return AlignState::Ok; } } while (static_cast(count) > 0 && pc < size && pc < offset); bfd_close(bfdf); return AlignState::NotAlign; } AlignState BfdDisasm::is_aligned(uint64_t offset, uint64_t pc) { AlignState aligned = AlignState::Fail; // 100 bytes should be enough to cover next instruction behind pc uint64_t size = std::min(pc + 100, size_); void *buf; if (fd_ < 0) return aligned; buf = malloc(size); if (!buf) { perror("malloc failed"); return aligned; } uint64_t sz; sz = pread(fd_, buf, size, offset); if (sz == size) aligned = is_aligned_buf(buf, size, pc); else perror("pread failed"); free(buf); return aligned; } } // namespace bpftrace bpftrace-0.9.4/src/bfd-disasm.h000066400000000000000000000004241361633214400162660ustar00rootroot00000000000000#pragma once #include "disasm.h" namespace bpftrace { class BfdDisasm : public IDisasm { public: BfdDisasm(std::string &path); ~BfdDisasm(); AlignState is_aligned(uint64_t offset, uint64_t pc); private: int fd_ = -1; uint64_t size_; }; } // namespace bpftrace bpftrace-0.9.4/src/bpffeature.cpp000066400000000000000000000054411361633214400167370ustar00rootroot00000000000000#include #include #include #include #include #include #include "utils.h" namespace libbpf { #undef __BPF_FUNC_MAPPER #include "libbpf/bpf.h" } // namespace libbpf namespace bpftrace { static bool try_load(const char* name, bpf_prog_type prog_type, struct bpf_insn* insns, size_t len) { constexpr int log_size = 40960; char logbuf[log_size] = {}; int loglevel = 0; int ret = 0; StderrSilencer silencer; silencer.silence(); #ifdef HAVE_BCC_PROG_LOAD ret = bcc_prog_load( prog_type, name, insns, len, "GPL", 0, loglevel, logbuf, log_size); #else ret = bpf_prog_load( prog_type, name, insns, len, "GPL", 0, loglevel, logbuf, log_size); #endif if (ret >= 0) close(ret); return ret >= 0; } static bool detect_loop(void) { struct bpf_insn insns[] = { BPF_MOV64_IMM(BPF_REG_0, 0), BPF_ALU64_IMM(BPF_ADD, BPF_REG_0, 1), BPF_JMP_IMM(BPF_JLT, BPF_REG_0, 4, -2), BPF_EXIT_INSN(), }; return try_load("test_loop", BPF_PROG_TYPE_TRACEPOINT, insns, sizeof(insns)); } static bool detect_get_current_cgroup_id(void) { struct bpf_insn insns[] = { BPF_RAW_INSN( BPF_JMP | BPF_CALL, 0, 0, 0, libbpf::BPF_FUNC_get_current_cgroup_id), BPF_EXIT_INSN(), }; return try_load( "test_cgroup_id", BPF_PROG_TYPE_TRACEPOINT, insns, sizeof(insns)); } static bool detect_signal(void) { struct bpf_insn insns[] = { BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, libbpf::BPF_FUNC_send_signal), BPF_EXIT_INSN(), }; return try_load("test_signal", BPF_PROG_TYPE_KPROBE, insns, sizeof(insns)); } static bool detect_override_return(void) { struct bpf_insn insns[] = { BPF_LD_IMM64(BPF_REG_2, 11), BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, libbpf::BPF_FUNC_override_return), BPF_EXIT_INSN(), }; return try_load( "test_override_return", BPF_PROG_TYPE_KPROBE, insns, sizeof(insns)); } BPFfeature::BPFfeature(void) { has_loop_ = detect_loop(); has_signal_ = detect_signal(); has_get_current_cgroup_id_ = detect_get_current_cgroup_id(); has_override_return_ = detect_override_return(); } std::string BPFfeature::report(void) { std::stringstream buf; auto to_str = [](bool f) -> std::string { return f ? "yes" : "no"; }; buf << "Kernel helpers" << std::endl << " get_current_cgroup_id: " << to_str(has_helper_get_current_cgroup_id()) << std::endl << " send_signal: " << to_str(has_helper_send_signal()) << std::endl << " override_return: " << to_str(has_helper_override_return()) << std::endl << std::endl << "Kernel features" << std::endl << " Loop support: " << to_str(has_loop()) << std::endl << std::endl; return buf.str(); } } // namespace bpftrace bpftrace-0.9.4/src/bpffeature.h000066400000000000000000000011071361633214400163770ustar00rootroot00000000000000#pragma once #include namespace bpftrace { class BPFfeature { public: BPFfeature(); bool has_loop(void) { return has_loop_; }; bool has_helper_send_signal(void) { return has_signal_; }; bool has_helper_get_current_cgroup_id(void) { return has_get_current_cgroup_id_; }; bool has_helper_override_return(void) { return has_override_return_; }; std::string report(void); protected: bool has_loop_; /* Helpers */ bool has_signal_; bool has_get_current_cgroup_id_; bool has_override_return_; }; } // namespace bpftrace bpftrace-0.9.4/src/bpforc.h000066400000000000000000000114201361633214400155260ustar00rootroot00000000000000#pragma once #include "llvm/Config/llvm-config.h" #include "llvm/ExecutionEngine/ExecutionEngine.h" #include "llvm/ExecutionEngine/JITSymbol.h" #include "llvm/ExecutionEngine/Orc/CompileUtils.h" #include "llvm/ExecutionEngine/Orc/IRCompileLayer.h" #include "llvm/ExecutionEngine/Orc/LambdaResolver.h" #include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" #include "llvm/ExecutionEngine/SectionMemoryManager.h" #include "llvm/Target/TargetMachine.h" namespace bpftrace { using namespace llvm; using namespace llvm::orc; class MemoryManager : public SectionMemoryManager { public: explicit MemoryManager(std::map> §ions) : sections_(sections) { } uint8_t *allocateCodeSection(uintptr_t Size, unsigned Alignment, unsigned SectionID, StringRef SectionName) override { uint8_t *addr = SectionMemoryManager::allocateCodeSection(Size, Alignment, SectionID, SectionName); sections_[SectionName.str()] = std::make_tuple(addr, Size); return addr; } uint8_t *allocateDataSection(uintptr_t Size, unsigned Alignment, unsigned SectionID, StringRef SectionName, bool isReadOnly) override { uint8_t *addr = SectionMemoryManager::allocateDataSection(Size, Alignment, SectionID, SectionName, isReadOnly); sections_[SectionName.str()] = std::make_tuple(addr, Size); return addr; } private: std::map> §ions_; }; #if LLVM_VERSION_MAJOR >= 5 && LLVM_VERSION_MAJOR < 7 class BpfOrc { private: std::unique_ptr TM; RTDyldObjectLinkingLayer ObjectLayer; IRCompileLayer CompileLayer; public: std::map> sections_; using ModuleHandle = decltype(CompileLayer)::ModuleHandleT; BpfOrc(TargetMachine *TM_) : TM(TM_), ObjectLayer([this]() { return std::make_shared(sections_); }), CompileLayer(ObjectLayer, SimpleCompiler(*TM)) { } void compileModule(std::unique_ptr M) { auto mod = addModule(move(M)); CompileLayer.emitAndFinalize(mod); } ModuleHandle addModule(std::unique_ptr M) { // We don't actually care about resolving symbols from other modules auto Resolver = createLambdaResolver( [](const std::string &) { return JITSymbol(nullptr); }, [](const std::string &) { return JITSymbol(nullptr); }); return cantFail(CompileLayer.addModule(std::move(M), std::move(Resolver))); } }; #elif LLVM_VERSION_MAJOR >= 7 class BpfOrc { private: ExecutionSession ES; std::unique_ptr TM; std::shared_ptr Resolver; #if LLVM_VERSION_MAJOR >= 8 LegacyRTDyldObjectLinkingLayer ObjectLayer; LegacyIRCompileLayer CompileLayer; #else RTDyldObjectLinkingLayer ObjectLayer; IRCompileLayer CompileLayer; #endif public: std::map> sections_; BpfOrc(TargetMachine *TM_) : TM(TM_), Resolver(createLegacyLookupResolver( ES, [](const std::string &Name __attribute__((unused))) -> JITSymbol { return nullptr; }, [](Error Err) { cantFail(std::move(Err), "lookup failed"); })), #if LLVM_VERSION_MAJOR > 8 ObjectLayer(AcknowledgeORCv1Deprecation, ES, [this](VModuleKey) { return LegacyRTDyldObjectLinkingLayer::Resources{ std::make_shared(sections_), Resolver }; }), CompileLayer(AcknowledgeORCv1Deprecation, ObjectLayer, SimpleCompiler(*TM)) { } #elif LLVM_VERSION_MAJOR == 8 ObjectLayer(ES, [this](VModuleKey) { return LegacyRTDyldObjectLinkingLayer::Resources{ std::make_shared(sections_), Resolver }; }), CompileLayer(ObjectLayer, SimpleCompiler(*TM)) { } #else ObjectLayer(ES, [this](VModuleKey) { return RTDyldObjectLinkingLayer::Resources{ std::make_shared(sections_), Resolver }; }), CompileLayer(ObjectLayer, SimpleCompiler(*TM)) { } #endif void compileModule(std::unique_ptr M) { auto K = addModule(move(M)); cantFail(CompileLayer.emitAndFinalize(K)); } VModuleKey addModule(std::unique_ptr M) { auto K = ES.allocateVModule(); cantFail(CompileLayer.addModule(K, std::move(M))); return K; } }; #else #error Unsupported LLVM version #endif } // namespace bpftrace bpftrace-0.9.4/src/bpftrace.cpp000066400000000000000000001534211361633214400164040ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_BCC_ELF_FOREACH_SYM #include #include "bcc_elf.h" #endif #include "bcc_syms.h" #include "perf_reader.h" #include "bpforc.h" #include "bpftrace.h" #include "attached_probe.h" #include "printf.h" #include "triggers.h" #include "resolve_cgroupid.h" #include "utils.h" extern char** environ; namespace bpftrace { DebugLevel bt_debug = DebugLevel::kNone; bool bt_verbose = false; volatile sig_atomic_t BPFtrace::exitsig_recv = false; constexpr char CHILD_EXIT_QUIETLY = '\0'; constexpr char CHILD_GO = 'g'; int format(char * s, size_t n, const char * fmt, std::vector> &args) { int ret = -1; switch(args.size()) { case 0: ret = snprintf(s, n, "%s", fmt); break; case 1: ret = snprintf(s, n, fmt, args.at(0)->value()); break; case 2: ret = snprintf(s, n, fmt, args.at(0)->value(), args.at(1)->value()); break; case 3: ret = snprintf(s, n, fmt, args.at(0)->value(), args.at(1)->value(), args.at(2)->value()); break; case 4: ret = snprintf(s, n, fmt, args.at(0)->value(), args.at(1)->value(), args.at(2)->value(), args.at(3)->value()); break; case 5: ret = snprintf(s, n, fmt, args.at(0)->value(), args.at(1)->value(), args.at(2)->value(), args.at(3)->value(), args.at(4)->value()); break; case 6: ret = snprintf(s, n, fmt, args.at(0)->value(), args.at(1)->value(), args.at(2)->value(), args.at(3)->value(), args.at(4)->value(), args.at(5)->value()); break; default: std::cerr << "format() can only take up to 7 arguments (" << args.size() << ") provided" << std::endl; abort(); } if (ret < 0 && errno != 0) { std::cerr << "format() error occurred: " << std::strerror(errno) << std::endl; abort(); } return ret; } BPFtrace::~BPFtrace() { kill_child(); if (child_pid() != 0) { // We don't care if waitpid returns any errors. We're just trying // to make a best effort here. It's not like we could recover from // an error. int status; waitpid(child_pid(), &status, 0); } for (const auto& pair : exe_sym_) { if (pair.second.second) bcc_free_symcache(pair.second.second, pair.second.first); } if (ksyms_) bcc_free_symcache(ksyms_, -1); } int BPFtrace::add_probe(ast::Probe &p) { for (auto attach_point : *p.attach_points) { if (attach_point->provider == "BEGIN" || attach_point->provider == "END") { Probe probe; probe.path = "/proc/self/exe"; probe.attach_point = attach_point->provider + "_trigger"; probe.type = probetype(attach_point->provider); probe.log_size = log_size_; probe.orig_name = p.name(); probe.name = p.name(); probe.loc = 0; probe.pid = getpid(); probe.index = attach_point->index(probe.name) > 0 ? attach_point->index(probe.name) : p.index(); special_probes_.push_back(probe); continue; } std::vector attach_funcs; bool underspecified_usdt_probe = (probetype(attach_point->provider) == ProbeType::usdt && attach_point->ns.empty()); if (attach_point->need_expansion && (has_wildcard(attach_point->func) || underspecified_usdt_probe)) { std::set matches; try { matches = find_wildcard_matches(*attach_point); } catch (const WildcardException &e) { std::cerr << e.what() << std::endl; return 1; } attach_funcs.insert(attach_funcs.end(), matches.begin(), matches.end()); } else { if (probetype(attach_point->provider) == ProbeType::usdt && !attach_point->ns.empty()) attach_funcs.push_back(attach_point->ns + ":" + attach_point->func); else attach_funcs.push_back(attach_point->func); } for (auto func_ : attach_funcs) { std::string full_func_id = func_; std::string func_id = func_; // USDT probes must specify both a provider and a function name for full id // So we will extract out the provider namespace to get just the function name if (probetype(attach_point->provider) == ProbeType::usdt ) { std::string ns = func_id.substr(0, func_id.find(":")); func_id.erase(0, func_id.find(":")+1); // Set attach_point ns to be a resolved namespace in case of wildcard attach_point->ns = ns; // Set the function name to be a resolved function id in case of wildcard attach_point->func = func_id; } Probe probe; probe.path = attach_point->target; probe.attach_point = func_id; probe.type = probetype(attach_point->provider); probe.log_size = log_size_; probe.orig_name = p.name(); probe.ns = attach_point->ns; probe.name = attach_point->name(func_id); probe.freq = attach_point->freq; probe.address = attach_point->address; probe.func_offset = attach_point->func_offset; probe.loc = 0; probe.index = attach_point->index(full_func_id) > 0 ? attach_point->index(full_func_id) : p.index(); probe.addr = attach_point->addr; probe.len = attach_point->len; probe.mode = attach_point->mode; probes_.push_back(probe); } } return 0; } std::set BPFtrace::find_wildcard_matches( const ast::AttachPoint &attach_point) const { std::unique_ptr symbol_stream; std::string prefix, func; switch (probetype(attach_point.provider)) { case ProbeType::kprobe: case ProbeType::kretprobe: { symbol_stream = get_symbols_from_file( "/sys/kernel/debug/tracing/available_filter_functions"); prefix = ""; func = attach_point.func; break; } case ProbeType::uprobe: case ProbeType::uretprobe: { symbol_stream = std::make_unique( extract_func_symbols_from_path(attach_point.target)); prefix = ""; func = attach_point.func; break; } case ProbeType::tracepoint: { symbol_stream = get_symbols_from_file( "/sys/kernel/debug/tracing/available_events"); prefix = attach_point.target; func = attach_point.func; break; } case ProbeType::usdt: { symbol_stream = get_symbols_from_usdt(pid_, attach_point.target); prefix = ""; if (attach_point.ns == "") func = "*:" + attach_point.func; else func = attach_point.ns + ":" + attach_point.func; break; } default: { throw WildcardException("Wildcard matches aren't available on probe type '" + attach_point.provider + "'"); } } return find_wildcard_matches(prefix, func, *symbol_stream); } /* * Finds all matches of func in the provided input stream. * * If an optional prefix is provided, lines must start with it to count as a * match, but the prefix is stripped from entries in the result set. * Wildcard tokens ("*") are accepted in func. */ std::set BPFtrace::find_wildcard_matches( const std::string &prefix, const std::string &func, std::istream &symbol_stream) const { if (!has_wildcard(func)) return std::set({func}); bool start_wildcard = func[0] == '*'; bool end_wildcard = func[func.length() - 1] == '*'; std::vector tokens = split_string(func, '*'); tokens.erase(std::remove(tokens.begin(), tokens.end(), ""), tokens.end()); std::string line; std::set matches; std::string full_prefix = prefix.empty() ? "" : (prefix + ":"); while (std::getline(symbol_stream, line)) { if (!full_prefix.empty()) { if (line.find(full_prefix, 0) != 0) continue; line = line.substr(full_prefix.length()); } if (!wildcard_match(line, tokens, start_wildcard, end_wildcard)) continue; // skip the ".part.N" kprobe variants, as they can't be traced: if (line.find(".part.") != std::string::npos) continue; matches.insert(line); } return matches; } std::unique_ptr BPFtrace::get_symbols_from_file(const std::string &path) const { auto file = std::make_unique(path); if (file->fail()) { throw std::runtime_error("Could not read symbols from " + path + ": " + strerror(errno)); } return file; } std::unique_ptr BPFtrace::get_symbols_from_usdt( int pid, const std::string &target) const { std::string probes; usdt_probe_list usdt_probes; if (pid > 0) usdt_probes = USDTHelper::probes_for_pid(pid); else usdt_probes = USDTHelper::probes_for_path(target); for (auto const& usdt_probe : usdt_probes) { std::string path = std::get(usdt_probe); std::string provider = std::get(usdt_probe); std::string fname = std::get(usdt_probe); probes += provider + ":" + fname + "\n"; } return std::make_unique(probes); } int BPFtrace::num_probes() const { return special_probes_.size() + probes_.size(); } void BPFtrace::kill_child() { if (child_pid() == 0) return; if (child_running_) { kill(child_pid(), SIGTERM); } else { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-result" // We need to disable this warning for some GCC/libc combinations despite // using the void cast: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425 (void)write(child_start_pipe_, &CHILD_EXIT_QUIETLY, 1); #pragma GCC diagnostic pop close(child_start_pipe_); } } void BPFtrace::request_finalize() { finalize_ = true; attached_probes_.clear(); kill_child(); } void perf_event_printer(void *cb_cookie, void *data, int size __attribute__((unused))) { auto bpftrace = static_cast(cb_cookie); auto printf_id = *static_cast(data); auto arg_data = static_cast(data); int err; // Ignore the remaining events if perf_event_printer is called during finalization // stage (exit() builtin has been called) if (bpftrace->finalize_) return; if (bpftrace->exitsig_recv) { bpftrace->request_finalize(); return; } // async actions if (printf_id == asyncactionint(AsyncAction::exit)) { bpftrace->request_finalize(); return; } else if (printf_id == asyncactionint(AsyncAction::print)) { std::string arg = (const char *)(static_cast(data) + sizeof(uint64_t) + 2 * sizeof(uint64_t)); uint64_t top = (uint64_t) * (static_cast(data) + 1); uint64_t div = (uint64_t) * (static_cast(data) + 2); err = bpftrace->print_map_ident(arg, top, div); if (err) throw std::runtime_error("Could not print map with ident \"" + arg + "\", err=" + std::to_string(err)); return; } else if (printf_id == asyncactionint(AsyncAction::clear)) { std::string arg = (const char *)(arg_data+sizeof(uint64_t)); err = bpftrace->clear_map_ident(arg); if (err) throw std::runtime_error("Could not clear map with ident \"" + arg + "\", err=" + std::to_string(err)); return; } else if (printf_id == asyncactionint(AsyncAction::zero)) { std::string arg = (const char *)(arg_data+sizeof(uint64_t)); err = bpftrace->zero_map_ident(arg); if (err) throw std::runtime_error("Could not zero map with ident \"" + arg + "\", err=" + std::to_string(err)); return; } else if (printf_id == asyncactionint(AsyncAction::time)) { char timestr[STRING_SIZE]; time_t t; struct tm *tmp; t = time(NULL); tmp = localtime(&t); if (tmp == NULL) { std::cerr << "localtime: " << strerror(errno) << std::endl; return; } uint64_t time_id = (uint64_t) * (static_cast(data) + 1); auto fmt = bpftrace->time_args_[time_id].c_str(); if (strftime(timestr, sizeof(timestr), fmt, tmp) == 0) { std::cerr << "strftime returned 0" << std::endl; return; } bpftrace->out_->message(MessageType::time, timestr, false); return; } else if (printf_id == asyncactionint(AsyncAction::join)) { uint64_t join_id = (uint64_t) * (static_cast(data) + 1); auto delim = bpftrace->join_args_[join_id].c_str(); std::stringstream joined; for (unsigned int i = 0; i < bpftrace->join_argnum_; i++) { auto *arg = arg_data + 2*sizeof(uint64_t) + i * bpftrace->join_argsize_; if (arg[0] == 0) break; if (i) joined << delim; joined << arg; } bpftrace->out_->message(MessageType::join, joined.str()); return; } else if ( printf_id >= asyncactionint(AsyncAction::syscall) && printf_id < asyncactionint(AsyncAction::syscall) + RESERVED_IDS_PER_ASYNCACTION) { if (bpftrace->safe_mode_) { std::cerr << "syscall() not allowed in safe mode" << std::endl; abort(); } auto id = printf_id - asyncactionint(AsyncAction::syscall); auto fmt = std::get<0>(bpftrace->system_args_[id]).c_str(); auto args = std::get<1>(bpftrace->system_args_[id]); auto arg_values = bpftrace->get_arg_values(args, arg_data); const int BUFSIZE = 512; char buffer[BUFSIZE]; int size = format(buffer, BUFSIZE, fmt, arg_values); // Return value is required size EXCLUDING null byte if (size >= BUFSIZE) { std::cerr << "syscall() command to long (" << size << " bytes): "; std::cerr << buffer << std::endl; return; } bpftrace->out_->message(MessageType::syscall, exec_system(buffer), false); return; } else if ( printf_id >= asyncactionint(AsyncAction::cat)) { auto id = printf_id - asyncactionint(AsyncAction::cat); auto fmt = std::get<0>(bpftrace->cat_args_[id]).c_str(); auto args = std::get<1>(bpftrace->cat_args_[id]); auto arg_values = bpftrace->get_arg_values(args, arg_data); const int BUFSIZE = 512; char buffer[BUFSIZE]; int size = format(buffer, BUFSIZE, fmt, arg_values); // Return value is required size EXCLUDING null byte if (size >= BUFSIZE) { std::cerr << "cat() command to long (" << size << " bytes): "; std::cerr << buffer << std::endl; return; } std::stringstream buf; cat_file(buffer, bpftrace->cat_bytes_max_, buf); bpftrace->out_->message(MessageType::cat, buf.str(), false); return; } // printf auto fmt = std::get<0>(bpftrace->printf_args_[printf_id]).c_str(); auto args = std::get<1>(bpftrace->printf_args_[printf_id]); auto arg_values = bpftrace->get_arg_values(args, arg_data); // First try with a stack buffer, if that fails use a heap buffer const int BUFSIZE=512; char buffer[BUFSIZE]; int required_size = format(buffer, BUFSIZE, fmt, arg_values); // Return value is required size EXCLUDING null byte if (required_size < BUFSIZE) { bpftrace->out_->message(MessageType::printf, std::string(buffer), false); } else { auto buf = std::make_unique(required_size+1); // if for some reason the size is still wrong the string // will just be silently truncated format(buf.get(), required_size, fmt, arg_values); bpftrace->out_->message(MessageType::printf, std::string(buf.get()), false); } } std::vector> BPFtrace::get_arg_values(const std::vector &args, uint8_t* arg_data) { std::vector> arg_values; for (auto arg : args) { switch (arg.type.type) { case Type::integer: switch (arg.type.size) { case 8: arg_values.push_back( std::make_unique( *reinterpret_cast(arg_data+arg.offset))); break; case 4: arg_values.push_back( std::make_unique( *reinterpret_cast(arg_data+arg.offset))); break; case 2: arg_values.push_back( std::make_unique( *reinterpret_cast(arg_data+arg.offset))); break; case 1: arg_values.push_back( std::make_unique( *reinterpret_cast(arg_data+arg.offset))); break; default: std::cerr << "get_arg_values: invalid integer size. 8, 4, 2 and byte supported. " << arg.type.size << "provided" << std::endl; abort(); } break; case Type::string: arg_values.push_back( std::make_unique( reinterpret_cast(arg_data+arg.offset))); break; case Type::ksym: arg_values.push_back( std::make_unique( resolve_ksym(*reinterpret_cast(arg_data+arg.offset)))); break; case Type::usym: arg_values.push_back( std::make_unique( resolve_usym( *reinterpret_cast(arg_data+arg.offset), *reinterpret_cast(arg_data+arg.offset + 8)))); break; case Type::inet: arg_values.push_back( std::make_unique( resolve_inet( *reinterpret_cast(arg_data+arg.offset), reinterpret_cast(arg_data+arg.offset + 8)))); break; case Type::username: arg_values.push_back( std::make_unique( resolve_uid( *reinterpret_cast(arg_data+arg.offset)))); break; case Type::probe: arg_values.push_back( std::make_unique( resolve_probe( *reinterpret_cast(arg_data+arg.offset)))); break; case Type::kstack: arg_values.push_back( std::make_unique( get_stack( *reinterpret_cast(arg_data+arg.offset), false, arg.type.stack_type, 8))); break; case Type::ustack: arg_values.push_back( std::make_unique( get_stack( *reinterpret_cast(arg_data+arg.offset), true, arg.type.stack_type, 8))); break; case Type::cast: if (arg.type.is_pointer) { arg_values.push_back( std::make_unique( *reinterpret_cast(arg_data+arg.offset))); break; } // fall through default: std::cerr << "invalid argument type" << std::endl; abort(); } } return arg_values; } void BPFtrace::add_param(const std::string ¶m) { params_.emplace_back(param); } std::string BPFtrace::get_param(size_t i, bool is_str) const { if (params_.size() < i) { return is_str ? "" : "0"; } return params_.at(i-1); } size_t BPFtrace::num_params() const { return params_.size(); } void perf_event_lost(void *cb_cookie, uint64_t lost) { auto bpftrace = static_cast(cb_cookie); bpftrace->out_->lost_events(lost); } std::unique_ptr BPFtrace::attach_probe(Probe &probe, const BpfOrc &bpforc) { // use the single-probe program if it exists (as is the case with wildcards // and the name builtin, which must be expanded into separate programs per // probe), else try to find a the program based on the original probe name // that includes wildcards. std::string index_str = "_" + std::to_string(probe.index); auto func = bpforc.sections_.find("s_" + probe.name + index_str); if (func == bpforc.sections_.end()) func = bpforc.sections_.find("s_" + probe.orig_name + index_str); if (func == bpforc.sections_.end()) { if (probe.name != probe.orig_name) std::cerr << "Code not generated for probe: " << probe.name << " from: " << probe.orig_name << std::endl; else std::cerr << "Code not generated for probe: " << probe.name << std::endl; return nullptr; } try { if (probe.type == ProbeType::usdt || probe.type == ProbeType::watchpoint) return std::make_unique(probe, func->second, pid_); else return std::make_unique(probe, func->second, safe_mode_); } catch (std::runtime_error &e) { std::cerr << e.what() << std::endl; } return nullptr; } bool attach_reverse(const Probe &p) { switch(p.type) { case ProbeType::kprobe: case ProbeType::uprobe: case ProbeType::uretprobe: case ProbeType::usdt: case ProbeType::software: return true; case ProbeType::kretprobe: case ProbeType::tracepoint: case ProbeType::profile: case ProbeType::interval: case ProbeType::watchpoint: case ProbeType::hardware: return false; default: abort(); } } int BPFtrace::run(std::unique_ptr bpforc) { auto r_special_probes = special_probes_.rbegin(); for (; r_special_probes != special_probes_.rend(); ++r_special_probes) { auto attached_probe = attach_probe(*r_special_probes, *bpforc.get()); if (attached_probe == nullptr) return -1; special_attached_probes_.push_back(std::move(attached_probe)); } int epollfd = setup_perf_events(); if (epollfd < 0) return epollfd; if (elapsed_map_) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); auto nsec = 1000000000ULL * ts.tv_sec + ts.tv_nsec; uint64_t key = 0; if (bpf_update_elem(elapsed_map_->mapfd_, &key, &nsec, 0) < 0) { perror("Failed to write start time to elapsed map"); return -1; } } BEGIN_trigger(); // The kernel appears to fire some probes in the order that they were // attached and others in reverse order. In order to make sure that blocks // are executed in the same order they were declared, iterate over the probes // twice: in the first pass iterate forward and attach the probes that will // be fired in the same order they were attached, and in the second pass // iterate in reverse and attach the rest. for (auto probes = probes_.begin(); probes != probes_.end(); ++probes) { if (!attach_reverse(*probes)) { auto attached_probe = attach_probe(*probes, *bpforc.get()); if (attached_probe == nullptr) { kill_child(); return -1; } attached_probes_.push_back(std::move(attached_probe)); } } for (auto r_probes = probes_.rbegin(); r_probes != probes_.rend(); ++r_probes) { if (attach_reverse(*r_probes)) { auto attached_probe = attach_probe(*r_probes, *bpforc.get()); if (attached_probe == nullptr) { kill_child(); return -1; } attached_probes_.push_back(std::move(attached_probe)); } } // Kick the child to execute the command. if (has_child_cmd()) { int ret = write(child_start_pipe_, &CHILD_GO, 1); if (ret < 0) { perror("unable to write to 'go' pipe"); return ret; } child_running_ = true; close(child_start_pipe_); } if (bt_verbose) std::cerr << "Running..." << std::endl; poll_perf_events(epollfd); attached_probes_.clear(); // finalize_ and exitsig_recv should be false from now on otherwise // perf_event_printer() can ignore the END_trigger() events. finalize_ = false; exitsig_recv = false; END_trigger(); poll_perf_events(epollfd, true); special_attached_probes_.clear(); return 0; } int BPFtrace::setup_perf_events() { int epollfd = epoll_create1(EPOLL_CLOEXEC); if (epollfd == -1) { std::cerr << "Failed to create epollfd" << std::endl; return -1; } std::vector cpus = get_online_cpus(); online_cpus_ = cpus.size(); for (int cpu : cpus) { int page_cnt = 64; void *reader = bpf_open_perf_buffer(&perf_event_printer, &perf_event_lost, this, -1, cpu, page_cnt); if (reader == nullptr) { std::cerr << "Failed to open perf buffer" << std::endl; return -1; } struct epoll_event ev = {}; ev.events = EPOLLIN; ev.data.ptr = reader; int reader_fd = perf_reader_fd((perf_reader*)reader); bpf_update_elem(perf_event_map_->mapfd_, &cpu, &reader_fd, 0); if (epoll_ctl(epollfd, EPOLL_CTL_ADD, reader_fd, &ev) == -1) { std::cerr << "Failed to add perf reader to epoll" << std::endl; return -1; } } return epollfd; } void BPFtrace::poll_perf_events(int epollfd, bool drain) { auto events = std::vector(online_cpus_); while (true) { int ready = epoll_wait(epollfd, events.data(), online_cpus_, 100); if (ready < 0 && errno == EINTR && !BPFtrace::exitsig_recv) { // We received an interrupt not caused by SIGINT, skip and run again continue; } // Return if either // * epoll_wait has encountered an error (eg signal delivery) // * There's no events left and we've been instructed to drain or // finalization has been requested through exit() builtin. if (ready < 0 || (ready == 0 && (drain || finalize_))) { return; } for (int i=0; i 0 && !is_pid_alive(pid_)) { return; } } return; } int BPFtrace::print_maps() { for(auto &mapmap : maps_) { IMap &map = *mapmap.second.get(); int err; if (map.type_.type == Type::hist || map.type_.type == Type::lhist) err = print_map_hist(map, 0, 0); else if (map.type_.type == Type::avg || map.type_.type == Type::stats) err = print_map_stats(map); else err = print_map(map, 0, 0); if (err) return err; } return 0; } // print a map given an ident string int BPFtrace::print_map_ident(const std::string &ident, uint32_t top, uint32_t div) { int err = 0; for(auto &mapmap : maps_) { IMap &map = *mapmap.second.get(); if (map.name_ == ident) { if (map.type_.type == Type::hist || map.type_.type == Type::lhist) err = print_map_hist(map, top, div); else if (map.type_.type == Type::avg || map.type_.type == Type::stats) err = print_map_stats(map); else err = print_map(map, top, div); return err; } } return -2; } // clear a map (delete all keys) given an ident string int BPFtrace::clear_map_ident(const std::string &ident) { int err = 0; for(auto &mapmap : maps_) { IMap &map = *mapmap.second.get(); if (map.name_ == ident) { err = clear_map(map); return err; } } return -2; } // zero a map (set all keys to zero) given an ident string int BPFtrace::zero_map_ident(const std::string &ident) { int err = 0; for(auto &mapmap : maps_) { IMap &map = *mapmap.second.get(); if (map.name_ == ident) { err = zero_map(map); return err; } } return -2; } // clear a map int BPFtrace::clear_map(IMap &map) { std::vector old_key; try { if (map.type_.type == Type::hist || map.type_.type == Type::lhist || map.type_.type == Type::stats || map.type_.type == Type::avg) // hist maps have 8 extra bytes for the bucket number old_key = find_empty_key(map, map.key_.size() + 8); else old_key = find_empty_key(map, map.key_.size()); } catch (std::runtime_error &e) { std::cerr << "Error getting key for map '" << map.name_ << "': " << e.what() << std::endl; return -2; } auto key(old_key); // snapshot keys, then operate on them std::vector> keys; while (bpf_get_next_key(map.mapfd_, old_key.data(), key.data()) == 0) { keys.push_back(key); old_key = key; } for (auto &key : keys) { int err = bpf_delete_elem(map.mapfd_, key.data()); if (err) { std::cerr << "Error looking up elem: " << err << std::endl; return -1; } } return 0; } // zero a map int BPFtrace::zero_map(IMap &map) { uint32_t nvalues = map.is_per_cpu_type() ? ncpus_ : 1; std::vector old_key; try { if (map.type_.type == Type::hist || map.type_.type == Type::lhist || map.type_.type == Type::stats || map.type_.type == Type::avg) // hist maps have 8 extra bytes for the bucket number old_key = find_empty_key(map, map.key_.size() + 8); else old_key = find_empty_key(map, map.key_.size()); } catch (std::runtime_error &e) { std::cerr << "Error getting key for map '" << map.name_ << "': " << e.what() << std::endl; return -2; } auto key(old_key); // snapshot keys, then operate on them std::vector> keys; while (bpf_get_next_key(map.mapfd_, old_key.data(), key.data()) == 0) { keys.push_back(key); old_key = key; } int value_size = map.type_.size * nvalues; std::vector zero(value_size, 0); for (auto &key : keys) { int err = bpf_update_elem(map.mapfd_, key.data(), zero.data(), BPF_EXIST); if (err) { std::cerr << "Error looking up elem: " << err << std::endl; return -1; } } return 0; } std::string BPFtrace::map_value_to_str(IMap &map, std::vector value, uint32_t div) { uint32_t nvalues = map.is_per_cpu_type() ? ncpus_ : 1; if (map.type_.type == Type::kstack) return get_stack( read_data(value.data()), false, map.type_.stack_type, 8); else if (map.type_.type == Type::ustack) return get_stack( read_data(value.data()), true, map.type_.stack_type, 8); else if (map.type_.type == Type::ksym) return resolve_ksym(read_data(value.data())); else if (map.type_.type == Type::usym) return resolve_usym(read_data(value.data()), read_data(value.data() + 8)); else if (map.type_.type == Type::inet) return resolve_inet(read_data(value.data()), (uint8_t *)(value.data() + 8)); else if (map.type_.type == Type::username) return resolve_uid(read_data(value.data())); else if (map.type_.type == Type::string) return std::string(reinterpret_cast(value.data())); else if (map.type_.type == Type::count) return std::to_string(reduce_value(value, nvalues) / div); else if (map.type_.type == Type::sum || map.type_.type == Type::integer) { if (map.type_.is_signed) return std::to_string(reduce_value(value, nvalues) / div); return std::to_string(reduce_value(value, nvalues) / div); } else if (map.type_.type == Type::min) return std::to_string(min_value(value, nvalues) / div); else if (map.type_.type == Type::max) return std::to_string(max_value(value, nvalues) / div); else if (map.type_.type == Type::probe) return resolve_probe(read_data(value.data())); else return std::to_string(read_data(value.data()) / div); } int BPFtrace::print_map(IMap &map, uint32_t top, uint32_t div) { uint32_t nvalues = map.is_per_cpu_type() ? ncpus_ : 1; std::vector old_key; try { old_key = find_empty_key(map, map.key_.size()); } catch (std::runtime_error &e) { std::cerr << "Error getting key for map '" << map.name_ << "': " << e.what() << std::endl; return -2; } auto key(old_key); std::vector, std::vector>> values_by_key; while (bpf_get_next_key(map.mapfd_, old_key.data(), key.data()) == 0) { int value_size = map.type_.size; value_size *= nvalues; auto value = std::vector(value_size); int err = bpf_lookup_elem(map.mapfd_, key.data(), value.data()); if (err == -1) { // key was removed by the eBPF program during bpf_get_next_key() and bpf_lookup_elem(), // let's skip this key continue; } else if (err) { std::cerr << "Error looking up elem: " << err << std::endl; return -1; } values_by_key.push_back({key, value}); old_key = key; } if (map.type_.type == Type::count || map.type_.type == Type::sum || map.type_.type == Type::integer) { bool is_signed = map.type_.is_signed; std::sort(values_by_key.begin(), values_by_key.end(), [&](auto &a, auto &b) { if (is_signed) return reduce_value(a.second, nvalues) < reduce_value(b.second, nvalues); return reduce_value(a.second, nvalues) < reduce_value(b.second, nvalues); }); } else if (map.type_.type == Type::min) { std::sort(values_by_key.begin(), values_by_key.end(), [&](auto &a, auto &b) { return min_value(a.second, nvalues) < min_value(b.second, nvalues); }); } else if (map.type_.type == Type::max) { std::sort(values_by_key.begin(), values_by_key.end(), [&](auto &a, auto &b) { return max_value(a.second, nvalues) < max_value(b.second, nvalues); }); } else { sort_by_key(map.key_.args_, values_by_key); }; if (div == 0) div = 1; out_->map(*this, map, top, div, values_by_key); return 0; } int BPFtrace::print_map_hist(IMap &map, uint32_t top, uint32_t div) { // A hist-map adds an extra 8 bytes onto the end of its key for storing // the bucket number. // e.g. A map defined as: @x[1, 2] = @hist(3); // would actually be stored with the key: [1, 2, 3] uint32_t nvalues = map.is_per_cpu_type() ? ncpus_ : 1; std::vector old_key; try { old_key = find_empty_key(map, map.key_.size() + 8); } catch (std::runtime_error &e) { std::cerr << "Error getting key for map '" << map.name_ << "': " << e.what() << std::endl; return -2; } auto key(old_key); std::map, std::vector> values_by_key; while (bpf_get_next_key(map.mapfd_, old_key.data(), key.data()) == 0) { auto key_prefix = std::vector(map.key_.size()); int bucket = key.at(map.key_.size()); for (size_t i=0; i(value_size); int err = bpf_lookup_elem(map.mapfd_, key.data(), value.data()); if (err == -1) { // key was removed by the eBPF program during bpf_get_next_key() and bpf_lookup_elem(), // let's skip this key continue; } else if (err) { std::cerr << "Error looking up elem: " << err << std::endl; return -1; } if (values_by_key.find(key_prefix) == values_by_key.end()) { // New key - create a list of buckets for it if (map.type_.type == Type::hist) values_by_key[key_prefix] = std::vector(65); else values_by_key[key_prefix] = std::vector(1002); } values_by_key[key_prefix].at(bucket) = reduce_value(value, nvalues); old_key = key; } // Sort based on sum of counts in all buckets std::vector, uint64_t>> total_counts_by_key; for (auto &map_elem : values_by_key) { int sum = 0; for (size_t i=0; imap_hist(*this, map, top, div, values_by_key, total_counts_by_key); return 0; } int BPFtrace::print_map_stats(IMap &map) { uint32_t nvalues = map.is_per_cpu_type() ? ncpus_ : 1; // stats() and avg() maps add an extra 8 bytes onto the end of their key for // storing the bucket number. std::vector old_key; try { old_key = find_empty_key(map, map.key_.size() + 8); } catch (std::runtime_error &e) { std::cerr << "Error getting key for map '" << map.name_ << "': " << e.what() << std::endl; return -2; } auto key(old_key); std::map, std::vector> values_by_key; while (bpf_get_next_key(map.mapfd_, old_key.data(), key.data()) == 0) { auto key_prefix = std::vector(map.key_.size()); int bucket = key.at(map.key_.size()); for (size_t i=0; i(value_size); int err = bpf_lookup_elem(map.mapfd_, key.data(), value.data()); if (err == -1) { // key was removed by the eBPF program during bpf_get_next_key() and bpf_lookup_elem(), // let's skip this key continue; } else if (err) { std::cerr << "Error looking up elem: " << err << std::endl; return -1; } if (values_by_key.find(key_prefix) == values_by_key.end()) { // New key - create a list of buckets for it values_by_key[key_prefix] = std::vector(2); } values_by_key[key_prefix].at(bucket) = reduce_value(value, nvalues); old_key = key; } // Sort based on sum of counts in all buckets std::vector, int64_t>> total_counts_by_key; for (auto &map_elem : values_by_key) { assert(map_elem.second.size() == 2); int64_t count = map_elem.second.at(0); int64_t total = map_elem.second.at(1); int64_t value = 0; if (count != 0) value = total / count; total_counts_by_key.push_back({map_elem.first, value}); } std::sort(total_counts_by_key.begin(), total_counts_by_key.end(), [&](auto &a, auto &b) { return a.second < b.second; }); out_->map_stats(*this, map, values_by_key, total_counts_by_key); return 0; } pid_t BPFtrace::spawn_child() { static const int maxargs = 256; char* argv[maxargs]; int wait_for_tracing_pipe[2]; auto args = split_string(cmd_, ' '); auto paths = resolve_binary_path(args[0]); // does path lookup on executable switch (paths.size()) { case 0: std::cerr << "path '" << args[0] << "' does not exist or is not executable" << std::endl; return -1; case 1: args[0] = paths.front().c_str(); break; default: std::cerr << "path '" << args[0] << "' must refer to a unique binary but matched " << paths.size() << " binaries" << std::endl; return -1; } if (args.size() >= (maxargs - 1)) { std::cerr << "Too many args passed into spawn_child (" << args.size() << " > " << maxargs - 1 << ")" << std::endl; return -1; } // Convert vector of strings into raw array of C-strings for execve(2) int idx = 0; for (const auto& arg : args) { argv[idx] = const_cast(arg.c_str()); ++idx; } argv[idx] = nullptr; // must be null terminated if (pipe(wait_for_tracing_pipe) < 0) { perror("failed to create 'go' pipe"); return -1; } // Fork and exec pid_t pid = fork(); if (pid == 0) { // Receive SIGTERM if parent dies // // Useful if user doesn't kill the bpftrace process group if (prctl(PR_SET_PDEATHSIG, SIGTERM)) perror("prctl(PR_SET_PDEATHSIG)"); // Closing the parent's end and wait until the // parent tells us to go. Set the child's end // to be closed on exec. close(wait_for_tracing_pipe[1]); fcntl(wait_for_tracing_pipe[0], F_SETFD, FD_CLOEXEC); char bf; int ret = read(wait_for_tracing_pipe[0], &bf, 1); if (ret != 1) { perror("failed to read 'go' pipe"); return -1; } if (bf == CHILD_EXIT_QUIETLY) { close(wait_for_tracing_pipe[0]); exit(0); } if (execve(argv[0], argv, environ)) { auto err = "Failed to execve: " + std::string(argv[0]); perror(err.c_str()); return -1; } } else if (pid > 0) { close(wait_for_tracing_pipe[0]); child_start_pipe_ = wait_for_tracing_pipe[1]; child_pid_ = pid; pid_ = pid; return pid; } else { perror("Failed to fork"); return -1; } return -1; // silence end of control compiler warning } template T BPFtrace::reduce_value(const std::vector &value, int nvalues) { T sum = 0; for (int i=0; i(value.data() + i * sizeof(T)); } return sum; } uint64_t BPFtrace::max_value(const std::vector &value, int nvalues) { uint64_t val, max = 0; for (int i=0; i(value.data() + i * sizeof(uint64_t)); if (val > max) max = val; } return max; } int64_t BPFtrace::min_value(const std::vector &value, int nvalues) { int64_t val, max = 0, retval; for (int i=0; i(value.data() + i * sizeof(int64_t)); if (val > max) max = val; } /* * This is a hack really until the code generation for the min() function * is sorted out. The way it is currently implemented doesn't allow > * 32 bit quantities and also means we have to do gymnastics with the return * value owing to the way it is stored (i.e., 0xffffffff - val). */ if (max == 0) /* If we have applied the zero() function */ retval = max; else if ((0xffffffff - max) <= 0) /* A negative 32 bit value */ retval = 0 - (max - 0xffffffff); else retval = 0xffffffff - max; /* A positive 32 bit value */ return retval; } std::vector BPFtrace::find_empty_key(IMap &map, size_t size) const { if (size == 0) size = 8; auto key = std::vector(size); uint32_t nvalues = map.is_per_cpu_type() ? ncpus_ : 1; int value_size = map.type_.size * nvalues; auto value = std::vector(value_size); if (bpf_lookup_elem(map.mapfd_, key.data(), value.data())) return key; for (auto &elem : key) elem = 0xff; if (bpf_lookup_elem(map.mapfd_, key.data(), value.data())) return key; for (auto &elem : key) elem = 0x55; if (bpf_lookup_elem(map.mapfd_, key.data(), value.data())) return key; throw std::runtime_error("Could not find empty key"); } std::string BPFtrace::get_stack(uint64_t stackidpid, bool ustack, StackType stack_type, int indent) { int32_t stackid = stackidpid & 0xffffffff; int pid = stackidpid >> 32; auto stack_trace = std::vector(stack_type.limit); int err = bpf_lookup_elem(stackid_maps_[stack_type]->mapfd_, &stackid, stack_trace.data()); if (err) { // ignore EFAULT errors: eg, kstack used but no kernel stack if (stackid != -EFAULT) std::cerr << "Error looking up stack id " << stackid << " (pid " << pid << "): " << err << std::endl; return ""; } std::ostringstream stack; std::string padding(indent, ' '); stack << "\n"; for (auto &addr : stack_trace) { if (addr == 0) break; std::string sym; if (!ustack) sym = resolve_ksym(addr, true); else sym = resolve_usym(addr, pid, true, stack_type.mode == StackMode::perf); switch (stack_type.mode) { case StackMode::bpftrace: stack << padding << sym << std::endl; break; case StackMode::perf: stack << "\t" << std::hex << addr << std::dec << " " << sym << std::endl; break; // TODO (mmarchini) enable -Wswitch-enum and disable -Wswitch-default default: abort(); } } return stack.str(); } std::string BPFtrace::resolve_uid(uintptr_t addr) const { std::string file_name = "/etc/passwd"; std::string uid = std::to_string(addr); std::string username = ""; std::ifstream file(file_name); if (file.fail()) { std::cerr << strerror(errno) << ": " << file_name << std::endl; return username; } std::string line; bool found = false; while (std::getline(file, line) && !found) { auto fields = split_string(line, ':'); if (fields[2] == uid) { found = true; username = fields[0]; } } file.close(); return username; } std::string BPFtrace::resolve_ksym(uintptr_t addr, bool show_offset) { struct bcc_symbol ksym; std::ostringstream symbol; if (!ksyms_) ksyms_ = bcc_symcache_new(-1, nullptr); if (bcc_symcache_resolve(ksyms_, addr, &ksym) == 0) { symbol << ksym.name; if (show_offset) symbol << "+" << ksym.offset; } else { symbol << (void*)addr; } return symbol.str(); } uint64_t BPFtrace::resolve_kname(const std::string &name) const { uint64_t addr = 0; std::string file_name = "/proc/kallsyms"; std::ifstream file(file_name); if (file.fail()) { std::cerr << strerror(errno) << ": " << file_name << std::endl; return addr; } std::string line; while (std::getline(file, line) && addr == 0) { auto tokens = split_string(line, ' '); if (name == tokens[2]) { addr = read_address_from_output(line); break; } } file.close(); return addr; } uint64_t BPFtrace::resolve_cgroupid(const std::string &path) const { return bpftrace_linux::resolve_cgroupid(path); } #ifdef HAVE_BCC_ELF_FOREACH_SYM static int sym_resolve_callback(const char *name, uint64_t addr, uint64_t size, void *payload) { struct symbol *sym = (struct symbol *)payload; if (!strcmp(name, sym->name.c_str())) { sym->address = addr; sym->size = size; return -1; } return 0; } #endif int BPFtrace::resolve_uname(const std::string &name, struct symbol *sym, const std::string &path) const { sym->name = name; #ifdef HAVE_BCC_ELF_FOREACH_SYM struct bcc_symbol_option option; memset(&option, 0, sizeof(option)); option.use_symbol_type = (1 << STT_OBJECT); return bcc_elf_foreach_sym(path.c_str(), sym_resolve_callback, &option, sym); #else std::string call_str = std::string("objdump -tT ") + path + " | grep -w " + sym->name; const char *call = call_str.c_str(); auto result = exec_system(call); sym->address = read_address_from_output(result); /* Trying to grab the size from objdump output is not that easy. foreaech_sym has been around for a while, users should switch to that. */ sym->size = 8; return 0; #endif } #ifdef HAVE_BCC_ELF_FOREACH_SYM static int add_symbol(const char *symname, uint64_t /*start*/, uint64_t /*size*/, void *payload) { auto syms = static_cast(payload); *syms << std::string(symname) << std::endl; return 0; } #endif std::string BPFtrace::extract_func_symbols_from_path(const std::string &path) const { #ifdef HAVE_BCC_ELF_FOREACH_SYM struct bcc_symbol_option symbol_option; memset(&symbol_option, 0, sizeof(symbol_option)); symbol_option.use_debug_file = 1; symbol_option.check_debug_file_crc = 1; symbol_option.use_symbol_type = (1 << STT_FUNC) | (1 << STT_GNU_IFUNC); std::ostringstream syms; int err = bcc_elf_foreach_sym(path.c_str(), add_symbol, &symbol_option, &syms); if (err) throw std::runtime_error("Could not list function symbols: " + path); return syms.str(); #else std::string call_str = std::string("objdump -tT ") + path + + " | " + "grep \"F .text\" | grep -oE '[^[:space:]]+$'"; const char *call = call_str.c_str(); return exec_system(call); #endif } uint64_t BPFtrace::read_address_from_output(std::string output) { std::string first_word = output.substr(0, output.find(" ")); return std::stoull(first_word, 0, 16); } static std::string resolve_inetv4(const uint8_t* inet) { char addr_cstr[INET_ADDRSTRLEN]; inet_ntop(AF_INET, inet, addr_cstr, INET_ADDRSTRLEN); return std::string(addr_cstr); } static std::string resolve_inetv6(const uint8_t* inet) { char addr_cstr[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, inet, addr_cstr, INET6_ADDRSTRLEN); return std::string(addr_cstr); } std::string BPFtrace::resolve_inet(int af, const uint8_t* inet) const { std::string addrstr; switch (af) { case AF_INET: addrstr = resolve_inetv4(inet); break; case AF_INET6: addrstr = resolve_inetv6(inet); break; default: std::cerr << "ntop() got unsupported AF type: " << af << std::endl; addrstr = std::string(""); } // TODO(mmarchini): handle inet_ntop errors return addrstr; } std::string BPFtrace::resolve_usym(uintptr_t addr, int pid, bool show_offset, bool show_module) { struct bcc_symbol usym; std::ostringstream symbol; void *psyms = nullptr; struct bcc_symbol_option symopts; memset(&symopts, 0, sizeof(symopts)); symopts.use_debug_file = 1; symopts.check_debug_file_crc = 1; symopts.use_symbol_type = BCC_SYM_ALL_TYPES; if (resolve_user_symbols_) { std::string pid_exe = get_pid_exe(pid); if (exe_sym_.find(pid_exe) == exe_sym_.end()) { // not cached, create new ProcSyms cache psyms = bcc_symcache_new(pid, &symopts); exe_sym_[pid_exe] = std::make_pair(pid, psyms); } else { psyms = exe_sym_[pid_exe].second; } } if (psyms && bcc_symcache_resolve(psyms, addr, &usym) == 0) { if (demangle_cpp_symbols_) symbol << usym.demangle_name; else symbol << usym.name; if (show_offset) symbol << "+" << usym.offset; if (show_module) symbol << " (" << usym.module << ")"; } else { symbol << (void*)addr; if (show_module) symbol << " ([unknown])"; } return symbol.str(); } std::string BPFtrace::resolve_probe(uint64_t probe_id) const { assert(probe_id < probe_ids_.size()); return probe_ids_[probe_id]; } void BPFtrace::sort_by_key(std::vector key_args, std::vector, std::vector>> &values_by_key) { int arg_offset = 0; for (auto arg : key_args) { arg_offset += arg.size; } // Sort the key arguments in reverse order so the results are sorted by // the first argument first, then the second, etc. for (size_t i=key_args.size(); i-- > 0; ) { auto arg = key_args.at(i); arg_offset -= arg.size; if (arg.type == Type::integer) { if (arg.size == 8) { std::stable_sort( values_by_key.begin(), values_by_key.end(), [&](auto &a, auto &b) { auto va = read_data(a.first.data() + arg_offset); auto vb = read_data(b.first.data() + arg_offset); return va < vb; }); } else if (arg.size == 4) { std::stable_sort( values_by_key.begin(), values_by_key.end(), [&](auto &a, auto &b) { auto va = read_data(a.first.data() + arg_offset); auto vb = read_data(b.first.data() + arg_offset); return va < vb; }); } else { std::cerr << "invalid integer argument size. 4 or 8 expected, but " << arg.size << " provided" << std::endl; abort(); } } else if (arg.type == Type::string) { std::stable_sort(values_by_key.begin(), values_by_key.end(), [&](auto &a, auto &b) { return strncmp((const char*)(a.first.data() + arg_offset), (const char*)(b.first.data() + arg_offset), STRING_SIZE) < 0; }); } // Other types don't get sorted } } bool BPFtrace::is_pid_alive(int pid) { char buf[256]; int ret = snprintf(buf, sizeof(buf), "/proc/%d/status", pid); if (ret < 0) { throw std::runtime_error("failed to snprintf"); } // Do a nonblocking wait on the pid just in case it's our child and it // has exited. We don't really care about any errors, we're just trying // to make a best effort. int status; waitpid(pid, &status, WNOHANG); int fd = open(buf, 0, O_RDONLY); if (fd < 0 && errno == ENOENT) { return false; } close(fd); return true; } const std::string BPFtrace::get_source_line(unsigned int n) { // Get the Nth source line. Return an empty string if it doesn't exist std::string buf; std::stringstream ss(src_); for (unsigned int idx = 0; idx <= n; idx++) { std::getline(ss, buf); if (ss.eof() && idx == n) return buf; if (!ss) return ""; } return buf; } void BPFtrace::warning(std::ostream &out, const location &l, const std::string &m) { log_with_location("WARNING", out, l, m); } void BPFtrace::error(std::ostream &out, const location &l, const std::string &m) { log_with_location("ERROR", out, l, m); } void BPFtrace::log_with_location(std::string level, std::ostream &out, const location &l, const std::string &m) { if (filename_ != "") { out << filename_ << ":"; } std::string msg(m); if (! msg.empty() && msg[msg.length() -1 ] == '\n') { msg.erase(msg.length()-1); } // print only the message if location info wasn't set if (l.begin.line == 0) { out << level << ": " << msg << std::endl; return; } if (l.begin.line > l.end.line) { out << "BUG: begin > end: " << l.begin << ":" << l.end << std::endl; out << level << ": " << msg << std::endl; return; } /* For a multi line error only the line range is printed: :-: ERROR: */ if (l.begin.line < l.end.line) { out << l.begin.line << "-" << l.end.line << ": ERROR: " << msg << std::endl; return; } /* For a single line error the format is: ::-: ERROR: E.g. file.bt:1:10-20: error: i:s:1 /1 < "str"/ ~~~~~~~~~~ */ out << l.begin.line << ":" << l.begin.column << "-" << l.end.column; out << ": " << level << ": " << msg << std::endl; std::string srcline = get_source_line(l.begin.line - 1); if (srcline == "") return; // To get consistent printing all tabs will be replaced with 4 spaces for (auto c : srcline) { if (c == '\t') out << " "; else out << c; } out << std::endl; for (unsigned int x = 0; x < srcline.size() && x < (static_cast(l.end.column) - 1); x++) { char marker = (x < (static_cast(l.begin.column) - 1)) ? ' ' : '~'; if (srcline[x] == '\t') { out << std::string(4, marker); } else { out << marker; } } out << std::endl; } } // namespace bpftrace bpftrace-0.9.4/src/bpftrace.h000066400000000000000000000162021361633214400160440ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include "ast.h" #include "attached_probe.h" #include "btf.h" #include "imap.h" #include "output.h" #include "printf.h" #include "struct.h" #include "types.h" #include "utils.h" namespace bpftrace { struct symbol { std::string name; uint64_t start; uint64_t size; uint64_t address; }; class BpfOrc; enum class DebugLevel; // globals extern DebugLevel bt_debug; extern bool bt_verbose; enum class DebugLevel { kNone, kDebug, kFullDebug }; inline DebugLevel operator++(DebugLevel &level, int) { switch (level) { case DebugLevel::kNone: level = DebugLevel::kDebug; break; case DebugLevel::kDebug: level = DebugLevel::kFullDebug; break; case DebugLevel::kFullDebug: // NOTE (mmarchini): should be handled by the caller level = DebugLevel::kNone; break; default: break; } return level; } class WildcardException : public std::exception { public: WildcardException(const std::string &msg) : msg_(msg) {} const char *what() const noexcept override { return msg_.c_str(); } private: std::string msg_; }; class BPFtrace { public: BPFtrace(std::unique_ptr o = std::make_unique(std::cout)) : out_(std::move(o)),ncpus_(get_possible_cpus().size()) { } virtual ~BPFtrace(); virtual int add_probe(ast::Probe &p); int num_probes() const; int run(std::unique_ptr bpforc); int print_maps(); int print_map_ident(const std::string &ident, uint32_t top, uint32_t div); int clear_map_ident(const std::string &ident); int zero_map_ident(const std::string &ident); inline int next_probe_id() { return next_probe_id_++; }; inline void source(std::string filename, std::string source) { src_ = source; filename_ = filename; } inline const std::string &source() { return src_; } std::string get_stack(uint64_t stackidpid, bool ustack, StackType stack_type, int indent=0); std::string resolve_ksym(uintptr_t addr, bool show_offset=false); std::string resolve_usym(uintptr_t addr, int pid, bool show_offset=false, bool show_module=false); std::string resolve_inet(int af, const uint8_t* inet) const; std::string resolve_uid(uintptr_t addr) const; uint64_t resolve_kname(const std::string &name) const; virtual int resolve_uname(const std::string &name, struct symbol *sym, const std::string &path) const; std::string map_value_to_str(IMap &map, std::vector value, uint32_t div); virtual std::string extract_func_symbols_from_path(const std::string &path) const; std::string resolve_probe(uint64_t probe_id) const; uint64_t resolve_cgroupid(const std::string &path) const; std::vector> get_arg_values(const std::vector &args, uint8_t* arg_data); void add_param(const std::string ¶m); std::string get_param(size_t index, bool is_str) const; size_t num_params() const; void request_finalize(); void error(std::ostream &out, const location &l, const std::string &m); void warning(std::ostream &out, const location &l, const std::string &m); void log_with_location(std::string, std::ostream &, const location &, const std::string &); bool has_child_cmd() { return cmd_.size() != 0; } virtual pid_t child_pid() { return child_pid_; }; int spawn_child(); void kill_child(); std::string cmd_; int pid_{0}; bool finalize_ = false; // Global variable checking if an exit signal was received static volatile sig_atomic_t exitsig_recv; std::map> maps_; std::map structs_; std::map macros_; std::map enums_; std::vector>> printf_args_; std::vector>> system_args_; std::vector join_args_; std::vector time_args_; std::vector>> cat_args_; std::unordered_map> stackid_maps_; std::unique_ptr join_map_; std::unique_ptr elapsed_map_; std::unique_ptr perf_event_map_; std::vector probe_ids_; unsigned int join_argnum_; unsigned int join_argsize_; std::unique_ptr out_; uint64_t strlen_ = 64; uint64_t mapmax_ = 4096; size_t cat_bytes_max_ = 10240; uint64_t max_probes_ = 512; uint64_t log_size_ = 409600; bool demangle_cpp_symbols_ = true; bool resolve_user_symbols_ = true; bool safe_mode_ = true; bool force_btf_ = false; static void sort_by_key( std::vector key_args, std::vector, std::vector>> &values_by_key); std::set find_wildcard_matches( const ast::AttachPoint &attach_point) const; std::set find_wildcard_matches( const std::string &prefix, const std::string &func, std::istream &symbol_stream) const; virtual std::unique_ptr get_symbols_from_file( const std::string &path) const; virtual std::unique_ptr get_symbols_from_usdt( int pid, const std::string &target) const; const std::string get_source_line(unsigned int); BTF btf_; std::unordered_set btf_set_; protected: std::vector probes_; std::vector special_probes_; private: std::vector> attached_probes_; std::vector> special_attached_probes_; void* ksyms_{nullptr}; std::map> exe_sym_; // exe -> (pid, cache) int ncpus_; int online_cpus_; std::vector params_; int next_probe_id_ = 0; pid_t child_pid_ = 0; bool child_running_ = false; // true when `CHILD_GO` has been sent (child // execve) int child_start_pipe_ = -1; std::string src_; std::string filename_; std::vector srclines_; std::unique_ptr attach_probe(Probe &probe, const BpfOrc &bpforc); int setup_perf_events(); void poll_perf_events(int epollfd, bool drain = false); int clear_map(IMap &map); int zero_map(IMap &map); int print_map(IMap &map, uint32_t top, uint32_t div); int print_map_hist(IMap &map, uint32_t top, uint32_t div); int print_map_lhist(IMap &map); int print_map_stats(IMap &map); int print_hist(const std::vector &values, uint32_t div) const; int print_lhist(const std::vector &values, int min, int max, int step) const; template static T reduce_value(const std::vector &value, int nvalues); static int64_t min_value(const std::vector &value, int nvalues); static uint64_t max_value(const std::vector &value, int nvalues); static uint64_t read_address_from_output(std::string output); std::vector find_empty_key(IMap &map, size_t size) const; static bool is_pid_alive(int pid); }; } // namespace bpftrace bpftrace-0.9.4/src/btf.cpp000066400000000000000000000152331361633214400153670ustar00rootroot00000000000000#include "btf.h" #include "bpftrace.h" #include "types.h" #include "utils.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_LIBBPF_BTF_DUMP #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wcast-qual" #include #pragma GCC diagnostic pop #include namespace bpftrace { static unsigned char *get_data(const char *file, ssize_t *sizep) { struct stat st; if (stat(file, &st)) return nullptr; FILE *f; f = fopen(file, "rb"); if (!f) return nullptr; unsigned char *data; unsigned int size; size = st.st_size; data = (unsigned char *) malloc(size); if (!data) { fclose(f); return nullptr; } ssize_t ret = fread(data, 1, st.st_size, f); if (ret != st.st_size) { free(data); fclose(f); return nullptr; } fclose(f); *sizep = size; return data; } static struct btf* btf_raw(char *file) { unsigned char *data; ssize_t size; struct btf *btf; data = get_data(file, &size); if (!data) { std::cerr << "BTF: failed to read data from: " << file << std::endl; return nullptr; } btf = btf__new(data, (__u32) size); free(data); return btf; } static int libbpf_print(enum libbpf_print_level level, const char *msg, va_list ap) { fprintf(stderr, "BTF: (%d) ", level); return vfprintf(stderr, msg, ap); } static struct btf *btf_open(const struct vmlinux_location *locs) { struct utsname buf; uname(&buf); for (int i = 0; locs[i].path; i++) { char path[PATH_MAX + 1]; snprintf(path, PATH_MAX, locs[i].path, buf.release); if (access(path, R_OK)) continue; struct btf *btf; if (locs[i].raw) btf = btf_raw(path); else btf = btf__parse_elf(path, nullptr); int err = libbpf_get_error(btf); if (err) { if (bt_verbose) { char err_buf[256]; libbpf_strerror(libbpf_get_error(btf), err_buf, sizeof(err_buf)); std::cerr << "BTF: failed to read data (" << err_buf << ") from: " << path << std::endl; } continue; } if (bt_verbose) { std::cerr << "BTF: using data from " << path << std::endl; } return btf; } return nullptr; } BTF::BTF(void) : btf(nullptr), state(NODATA) { struct vmlinux_location locs_env[] = { { nullptr, true }, { nullptr, false }, }; const struct vmlinux_location *locs = vmlinux_locs; // Try to get BTF file from BPFTRACE_BTF env char *path = std::getenv("BPFTRACE_BTF"); if (path) { locs_env[0].path = path; locs = locs_env; } btf = btf_open(locs); if (btf) { libbpf_set_print(libbpf_print); state = OK; } else if (bt_debug != DebugLevel::kNone) { std::cerr << "BTF: failed to find BTF data " << std::endl; } } BTF::~BTF() { btf__free(btf); } static void dump_printf(void *ctx, const char *fmt, va_list args) { std::string *ret = static_cast(ctx); char *str; if (vasprintf(&str, fmt, args) < 0) return; *ret += str; free(str); } static const char *btf_str(const struct btf *btf, __u32 off) { if (!off) return "(anon)"; return btf__name_by_offset(btf, off) ? : "(invalid)"; } static std::string full_type_str(const struct btf *btf, const struct btf_type *type) { const char *str = btf_str(btf, type->name_off); if (BTF_INFO_KIND(type->info) == BTF_KIND_STRUCT) return std::string("struct ") + str; if (BTF_INFO_KIND(type->info) == BTF_KIND_UNION) return std::string("union ") + str; return str; } static std::string btf_type_str(const std::string& type) { return std::regex_replace(type, std::regex("^(struct )|(union )"), ""); } std::string BTF::c_def(std::unordered_set& set) { std::string ret = std::string(""); struct btf_dump_opts opts = { .ctx = &ret, }; struct btf_dump *dump; char err_buf[256]; int err; dump = btf_dump__new(btf, nullptr, &opts, dump_printf); err = libbpf_get_error(dump); if (err) { libbpf_strerror(err, err_buf, sizeof(err_buf)); std::cerr << "BTF: failed to initialize dump (" << err_buf << ")" << std::endl; return std::string(""); } std::unordered_set myset(set); __s32 id, max = (__s32) btf__get_nr_types(btf); for (id = 1; id <= max && myset.size(); id++) { const struct btf_type *t = btf__type_by_id(btf, id); std::string str = full_type_str(btf, t); auto it = myset.find(str); if (it != myset.end()) { btf_dump__dump_type(dump, id); myset.erase(it); } } btf_dump__free(dump); return ret; } std::string BTF::type_of(const std::string& name, const std::string& field) { __s32 type_id = btf__find_by_name(btf, btf_type_str(name).c_str()); if (type_id < 0) return std::string(""); const struct btf_type *type = btf__type_by_id(btf, type_id); return type_of(type, field); } std::string BTF::type_of(const btf_type *type, const std::string &field) { if (!type || (BTF_INFO_KIND(type->info) != BTF_KIND_STRUCT && BTF_INFO_KIND(type->info) != BTF_KIND_UNION)) return std::string(""); // We need to walk through oaa the struct/union members // and try to find the requested field name. // // More info on struct/union members: // https://www.kernel.org/doc/html/latest/bpf/btf.html#btf-kind-union const struct btf_member *m = reinterpret_cast(type + 1); for (unsigned int i = 0; i < BTF_INFO_VLEN(type->info); i++) { std::string m_name = btf__name_by_offset(btf, m[i].name_off); // anonymous struct/union if (m_name == "") { const struct btf_type *type = btf__type_by_id(btf, m[i].type); std::string type_name = type_of(type, field); if (!type_name.empty()) return type_name; } if (m_name != field) continue; const struct btf_type *f = btf__type_by_id(btf, m[i].type); if (!f) break; // Get rid of all the pointers on the way to the actual type. while (BTF_INFO_KIND(f->info) == BTF_KIND_PTR) { f = btf__type_by_id(btf, f->type); } return full_type_str(btf, f); } return std::string(""); } } // namespace bpftrace #else // HAVE_LIBBPF_BTF_DUMP namespace bpftrace { BTF::BTF() { } BTF::~BTF() { } std::string BTF::c_def(std::unordered_set& set __attribute__((__unused__))) { return std::string(""); } std::string BTF::type_of(const std::string& name __attribute__((__unused__)), const std::string& field __attribute__((__unused__))) { return std::string(""); } } // namespace bpftrace #endif // HAVE_LIBBPF_BTF_DUMP bpftrace-0.9.4/src/btf.h000066400000000000000000000011201361633214400150220ustar00rootroot00000000000000#pragma once #include #include #include struct btf; struct btf_type; namespace bpftrace { class BTF { enum state { NODATA, OK, }; public: BTF(); ~BTF(); bool has_data(void); std::string c_def(std::unordered_set& set); std::string type_of(const std::string& name, const std::string& field); std::string type_of(const btf_type* type, const std::string& field); private: struct btf* btf; enum state state = NODATA; }; inline bool BTF::has_data(void) { return state == OK; } } // namespace bpftrace bpftrace-0.9.4/src/clang_parser.cpp000066400000000000000000000402531361633214400172540ustar00rootroot00000000000000#include #include #include "llvm/Config/llvm-config.h" #include "ast.h" #include "clang_parser.h" #include "types.h" #include "utils.h" #include "headers.h" #include "btf.h" #include "field_analyser.h" namespace bpftrace { static std::string get_clang_string(CXString string) { std::string str = clang_getCString(string); clang_disposeString(string); return str; } /* * is_anonymous * * Determine whether the provided cursor points to an anonymous struct. * * This union is anonymous: * struct { int i; }; * This is not, although it is marked as such in LLVM 8: * struct { int i; } obj; * This is not, and does not actually declare an instance of a struct: * struct X { int i; }; * * The libclang API was changed in LLVM 8 and restored under a different * function in LLVM 9. For LLVM 8 there is no way to properly tell if * a record declaration is anonymous, so we do some hacks here. * * LLVM version differences: * https://reviews.llvm.org/D54996 * https://reviews.llvm.org/D61232 */ static bool is_anonymous(CXCursor c) { #if LLVM_VERSION_MAJOR <= 7 return clang_Cursor_isAnonymous(c); #elif LLVM_VERSION_MAJOR >= 9 return clang_Cursor_isAnonymousRecordDecl(c); #else // LLVM 8 if (!clang_Cursor_isAnonymous(c)) return false; // In LLVM 8, some structs which the above function says are anonymous // are actually not. We iterate through the siblings of our struct // definition to see if there is a field giving it a name. // // struct Parent struct Parent // { { // struct struct // { { // ... ... // } name; }; // int sibling; int sibling; // }; }; // // Children of parent: Children of parent: // Struct: (cursor c) Struct: (cursor c) // Field: (Record)name Field: (int)sibling // Field: (int)sibling // // Record field found after No record field found after // cursor - not anonymous cursor - anonymous auto parent = clang_getCursorSemanticParent(c); if (clang_Cursor_isNull(parent)) return false; struct AnonFinderState { CXCursor struct_to_check; bool is_anon; bool prev_was_definition; } state; state.struct_to_check = c; state.is_anon = true; state.prev_was_definition = false; clang_visitChildren( parent, [](CXCursor c2, CXCursor, CXClientData client_data) { auto state = static_cast(client_data); if (state->prev_was_definition) { // This is the next child after the definition of the struct we're // interested in. If it is a field containing a record, we assume // that it must be the field for our struct, so our struct is not // anonymous. state->prev_was_definition = false; auto kind = clang_getCursorKind(c2); auto type = clang_getCanonicalType(clang_getCursorType(c2)); if (kind == CXCursor_FieldDecl && type.kind == CXType_Record) { state->is_anon = false; return CXChildVisit_Break; } } // We've found the definition of the struct we're interested in if (memcmp(c2.data, state->struct_to_check.data, 3*sizeof(uintptr_t)) == 0) state->prev_was_definition = true; return CXChildVisit_Continue; }, &state); return state.is_anon; #endif } /* * get_named_parent * * Find the parent struct of the field pointed to by the cursor. * Anonymous structs are skipped. */ static CXCursor get_named_parent(CXCursor c) { CXCursor parent = clang_getCursorSemanticParent(c); while (!clang_Cursor_isNull(parent) && is_anonymous(parent)) { parent = clang_getCursorSemanticParent(parent); } return parent; } // @returns true on success, false otherwise static bool getBitfield(CXCursor c, Bitfield &bitfield) { if (!clang_Cursor_isBitField(c)) { return false; } // Algorithm description: // To handle bitfields, we need to give codegen 3 additional pieces // of information: `read_bytes`, `access_rshift`, and `mask`. // // `read_bytes` tells codegen how many bytes to read starting at `Field::offset`. // This information is necessary because we can't always issue, for example, a // 1 byte read, as the bitfield could be the last 4 bits of the struct. Reading // past the end of the struct could cause a page fault. Therefore, we compute the // minimum number of bytes necessary to fully read the bitfield. This will always // keep the read within the bounds of the struct. // // `access_rshift` tells codegen how much to shift the masked value so that the // LSB of the bitfield is the LSB of the interpreted integer. // // `mask` tells codegen how to mask out the surrounding bitfields. size_t bitfield_offset = clang_Cursor_getOffsetOfField(c) % 8; size_t bitfield_bitwidth = clang_getFieldDeclBitWidth(c); bitfield.mask = (1 << bitfield_bitwidth) - 1; bitfield.access_rshift = bitfield_offset; // Round up to nearest byte bitfield.read_bytes = (bitfield_offset + bitfield_bitwidth + 7) / 8; return true; } // NOTE(mmarchini): as suggested in http://clang-developers.42468.n3.nabble.com/Extracting-macro-information-using-libclang-the-C-Interface-to-Clang-td4042648.html#message4042666 static bool translateMacro(CXCursor cursor, std::string &name, std::string &value) { CXToken* tokens = nullptr; unsigned numTokens = 0; CXTranslationUnit transUnit = clang_Cursor_getTranslationUnit(cursor); CXSourceRange srcRange = clang_getCursorExtent(cursor); clang_tokenize(transUnit, srcRange, &tokens, &numTokens); for (unsigned n=0; n= DebugLevel::kDebug) std::cerr << "Input (" << input.size() << "): " << input << std::endl; return false; } } return true; } CXCursor ClangParser::ClangParserHandler::get_translation_unit_cursor() { return clang_getTranslationUnitCursor(translation_unit); } bool ClangParser::visit_children(CXCursor &cursor, BPFtrace &bpftrace) { int err = clang_visitChildren( cursor, [](CXCursor c, CXCursor parent, CXClientData client_data) { if (clang_getCursorKind(c) == CXCursor_MacroDefinition) { std::string macro_name; std::string macro_value; if (translateMacro(c, macro_name, macro_value)) { auto ¯os = static_cast(client_data)->macros_; macros[macro_name] = macro_value; } return CXChildVisit_Recurse; } if (clang_getCursorKind(parent) == CXCursor_EnumDecl) { auto &enums = static_cast(client_data)->enums_; enums[get_clang_string(clang_getCursorSpelling(c))] = clang_getEnumConstantDeclValue(c); return CXChildVisit_Recurse; } if (clang_getCursorKind(parent) != CXCursor_StructDecl && clang_getCursorKind(parent) != CXCursor_UnionDecl) return CXChildVisit_Recurse; if (clang_getCursorKind(c) == CXCursor_FieldDecl) { auto &structs = static_cast(client_data)->structs_; auto named_parent = get_named_parent(c); auto ptype = clang_getCanonicalType(clang_getCursorType(named_parent)); auto ptypestr = get_clang_string(clang_getTypeSpelling(ptype)); auto ptypesize = clang_Type_getSizeOf(ptype); auto ident = get_clang_string(clang_getCursorSpelling(c)); auto offset = clang_Type_getOffsetOf(ptype, ident.c_str()) / 8; auto type = clang_getCanonicalType(clang_getCursorType(c)); Bitfield bitfield; bool is_bitfield = getBitfield(c, bitfield); // Warn if we already have the struct member defined and is // different type and keep the current definition in place. if (structs.count(ptypestr) != 0 && structs[ptypestr].fields.count(ident) != 0 && structs[ptypestr].fields[ident].offset != offset && structs[ptypestr].fields[ident].type != get_sized_type(type) && structs[ptypestr].fields[ident].is_bitfield && is_bitfield && structs[ptypestr].fields[ident].bitfield != bitfield && structs[ptypestr].size != ptypesize) { std::cerr << "type mismatch for " << ptypestr << "::" << ident << std::endl; } else { structs[ptypestr].fields[ident].offset = offset; structs[ptypestr].fields[ident].type = get_sized_type(type); structs[ptypestr].fields[ident].is_bitfield = is_bitfield; structs[ptypestr].fields[ident].bitfield = bitfield; structs[ptypestr].size = ptypesize; } } return CXChildVisit_Recurse; }, &bpftrace); // clang_visitChildren returns a non-zero value if the traversal // was terminated by the visitor returning CXChildVisit_Break. return err == 0; } bool ClangParser::parse_btf_definitions(BPFtrace &bpftrace) { if (!bpftrace.btf_set_.size()) return true; BTF &btf = bpftrace.btf_; if (!btf.has_data()) return true; std::string input = btf.c_def(bpftrace.btf_set_); CXUnsavedFile unsaved_files = { .Filename = "btf.h", .Contents = input.c_str(), .Length = input.size(), }; ClangParserHandler handler; CXErrorCode error = handler.parse_translation_unit( "btf.h", NULL, 0, &unsaved_files, 1, CXTranslationUnit_DetailedPreprocessingRecord); if (error) { if (bt_debug == DebugLevel::kFullDebug) { std::cerr << "Clang error while parsing BTF C definitions: " << error << std::endl; std::cerr << "Input (" << input.size() << "): " << input << std::endl; } return false; } if (!handler.check_diagnostics(input)) return false; CXCursor cursor = handler.get_translation_unit_cursor(); return visit_children(cursor, bpftrace); } bool ClangParser::parse(ast::Program *program, BPFtrace &bpftrace, std::vector extra_flags) { auto input = program->c_definitions; // Add BTF definitions, but do not bail out // in case of error, just notify if ((input.size() == 0 || bpftrace.force_btf_) && !parse_btf_definitions(bpftrace)) std::cerr << "Failed to parse BTF data." << std::endl; if (input.size() == 0) return true; // We occasionally get crashes in libclang otherwise CXUnsavedFile unsaved_files[] = { { .Filename = "definitions.h", .Contents = input.c_str(), .Length = input.size(), }, { .Filename = "/bpftrace/include/__stddef_max_align_t.h", .Contents = __stddef_max_align_t_h, .Length = __stddef_max_align_t_h_len, }, { .Filename = "/bpftrace/include/float.h", .Contents = float_h, .Length = float_h_len, }, { .Filename = "/bpftrace/include/limits.h", .Contents = limits_h, .Length = limits_h_len, }, { .Filename = "/bpftrace/include/stdarg.h", .Contents = stdarg_h, .Length = stdarg_h_len, }, { .Filename = "/bpftrace/include/stddef.h", .Contents = stddef_h, .Length = stddef_h_len, }, { .Filename = "/bpftrace/include/stdint.h", .Contents = stdint_h, .Length = stdint_h_len, }, { .Filename = "/bpftrace/include/" CLANG_WORKAROUNDS_H, .Contents = clang_workarounds_h, .Length = clang_workarounds_h_len, }, }; std::vector args = { "-isystem", "/usr/local/include", "-isystem", "/bpftrace/include", "-isystem", "/usr/include", }; for (auto &flag : extra_flags) { args.push_back(flag.c_str()); } ClangParserHandler handler; CXErrorCode error = handler.parse_translation_unit( "definitions.h", &args[0], args.size(), unsaved_files, sizeof(unsaved_files)/sizeof(CXUnsavedFile), CXTranslationUnit_DetailedPreprocessingRecord); if (error) { if (bt_debug == DebugLevel::kFullDebug) { std::cerr << "Clang error while parsing C definitions: " << error << std::endl; std::cerr << "Input (" << input.size() << "): " << input << std::endl; } return false; } if (!handler.check_diagnostics(input)) return false; CXCursor cursor = handler.get_translation_unit_cursor(); return visit_children(cursor, bpftrace); } } // namespace bpftrace bpftrace-0.9.4/src/clang_parser.h000066400000000000000000000022661361633214400167230ustar00rootroot00000000000000#pragma once #include "bpftrace.h" #include #define CLANG_WORKAROUNDS_H "clang_workarounds.h" namespace bpftrace { namespace ast { class Program; } class ClangParser { public: bool parse(ast::Program *program, BPFtrace &bpftrace, std::vector extra_flags = {}); private: bool visit_children(CXCursor &cursor, BPFtrace &bpftrace); bool parse_btf_definitions(BPFtrace &bpftrace); class ClangParserHandler { public: ClangParserHandler(); ~ClangParserHandler(); CXTranslationUnit get_translation_unit(); CXErrorCode parse_translation_unit(const char *source_filename, const char *const *command_line_args, int num_command_line_args, struct CXUnsavedFile *unsaved_files, unsigned num_unsaved_files, unsigned options); bool check_diagnostics(const std::string &input); CXCursor get_translation_unit_cursor(); private: CXIndex index; CXTranslationUnit translation_unit; }; }; } // namespace bpftrace bpftrace-0.9.4/src/disasm.cpp000066400000000000000000000007561361633214400161000ustar00rootroot00000000000000#include "disasm.h" #include "bfd-disasm.h" namespace bpftrace { class DummyDisasm : public IDisasm { AlignState is_aligned(uint64_t offset __attribute__((unused)), uint64_t pc __attribute__((unused))) override { return AlignState::NotSupp; } }; Disasm::Disasm(std::string &path __attribute__((unused))) { #ifdef HAVE_BFD_DISASM dasm_ = std::make_unique(path); #else dasm_ = std::make_unique(); #endif } } // namespace bpftrace bpftrace-0.9.4/src/disasm.h000066400000000000000000000007551361633214400155440ustar00rootroot00000000000000#pragma once #include #include namespace bpftrace { enum class AlignState { Ok, Fail, NotAlign, NotSupp }; class IDisasm { public: virtual AlignState is_aligned(uint64_t offset, uint64_t pc) = 0; virtual ~IDisasm() = default; }; class Disasm { public: Disasm(std::string &path); AlignState is_aligned(uint64_t offset, uint64_t pc) { return dasm_->is_aligned(offset, pc); } private: std::unique_ptr dasm_; }; } // namespace bpftrace bpftrace-0.9.4/src/driver.cpp000066400000000000000000000023571361633214400161120ustar00rootroot00000000000000#include #include "driver.h" extern void *yy_scan_string(const char *yy_str, yyscan_t yyscanner); extern int yylex_init(yyscan_t *scanner); extern int yylex_destroy (yyscan_t yyscanner); extern bpftrace::location loc; namespace bpftrace { Driver::Driver(BPFtrace &bpftrace, std::ostream &o) : bpftrace_(bpftrace), out_(o) { yylex_init(&scanner_); parser_ = std::make_unique(*this, scanner_); } Driver::~Driver() { yylex_destroy(scanner_); } void Driver::source(std::string filename, std::string script) { bpftrace_.source(filename, script); } // Kept for the test suite int Driver::parse_str(std::string script) { source("stdin", script); return parse(); } int Driver::parse() { // Reset source location info on every pass loc.initialize(); yy_scan_string(bpftrace_.source().c_str(), scanner_); parser_->parse(); // Keep track of errors thrown ourselves, since the result of // parser_->parse() doesn't take scanner errors into account, // only parser errors. return failed_; } void Driver::error(const location &l, const std::string &m) { bpftrace_.error(out_, l, m); failed_ = true; } void Driver::error(const std::string &m) { out_ << m << std::endl; failed_ = true; } } // namespace bpftrace bpftrace-0.9.4/src/driver.h000066400000000000000000000014601361633214400155510ustar00rootroot00000000000000#pragma once #include #include #include "ast.h" #include "bpftrace.h" #include "parser.tab.hh" typedef void* yyscan_t; #define YY_DECL bpftrace::Parser::symbol_type yylex(bpftrace::Driver &driver, yyscan_t yyscanner) YY_DECL; namespace bpftrace { class Driver { public: explicit Driver(BPFtrace &bpftrace, std::ostream &o = std::cerr); ~Driver(); int parse(); int parse_str(std::string script); void source(std::string, std::string); void error(std::ostream &, const location &, const std::string &); void error(const location &l, const std::string &m); void error(const std::string &m); ast::Program *root_; BPFtrace &bpftrace_; private: std::unique_ptr parser_; std::ostream &out_; yyscan_t scanner_; bool failed_ = false; }; } // namespace bpftrace bpftrace-0.9.4/src/fake_map.cpp000066400000000000000000000010051361633214400163470ustar00rootroot00000000000000#include "fake_map.h" namespace bpftrace { int FakeMap::next_mapfd_ = 1; FakeMap::FakeMap(const std::string &name __attribute__((unused)), const SizedType &type __attribute__((unused)), const MapKey &key __attribute__((unused))) { mapfd_ = next_mapfd_++; } FakeMap::FakeMap(const SizedType &type __attribute__((unused))) { mapfd_ = next_mapfd_++; } FakeMap::FakeMap(enum bpf_map_type map_type __attribute__((unused))) { mapfd_ = next_mapfd_++; } } // namespace bpftrace bpftrace-0.9.4/src/fake_map.h000066400000000000000000000004531361633214400160220ustar00rootroot00000000000000#pragma once #include "imap.h" namespace bpftrace { class FakeMap : public IMap { public: FakeMap(const std::string &name, const SizedType &type, const MapKey &key); FakeMap(const SizedType &type); FakeMap(enum bpf_map_type map_type); static int next_mapfd_; }; } // namespace bpftrace bpftrace-0.9.4/src/headers.h000066400000000000000000000012131361633214400156650ustar00rootroot00000000000000#pragma once namespace bpftrace { // These externs are provided by our build system. See resources/CMakeLists.txt extern const char __stddef_max_align_t_h[]; extern const unsigned __stddef_max_align_t_h_len; extern const char float_h[]; extern const unsigned float_h_len; extern const char limits_h[]; extern const unsigned limits_h_len; extern const char stdarg_h[]; extern const unsigned stdarg_h_len; extern const char stddef_h[]; extern const unsigned stddef_h_len; extern const char stdint_h[]; extern const unsigned stdint_h_len; extern const char clang_workarounds_h[]; extern const unsigned clang_workarounds_h_len; } // namespace bpftrace bpftrace-0.9.4/src/imap.h000066400000000000000000000011401361633214400151770ustar00rootroot00000000000000#pragma once #include #include "mapkey.h" #include "types.h" #include "libbpf.h" namespace bpftrace { class IMap { public: virtual ~IMap() { } IMap() { } IMap(const IMap &) = delete; IMap &operator=(const IMap &) = delete; int mapfd_; std::string name_; SizedType type_; MapKey key_; enum bpf_map_type map_type_; bool is_per_cpu_type() { return map_type_ == BPF_MAP_TYPE_PERCPU_HASH || map_type_ == BPF_MAP_TYPE_PERCPU_ARRAY; } // used by lhist(). TODO: move to separate Map object. int lqmin; int lqmax; int lqstep; }; } // namespace bpftrace bpftrace-0.9.4/src/lexer.l000066400000000000000000000227271361633214400154120ustar00rootroot00000000000000%option yylineno nodefault noyywrap noinput %option never-interactive %option reentrant %option stack %{ #include #include "driver.h" #include "utils.h" #include "parser.tab.hh" bpftrace::location loc; static std::string struct_type; static std::string buffer; #define YY_USER_ACTION loc.columns(yyleng); #define yyterminate() return bpftrace::Parser::make_END(loc) using namespace bpftrace; %} ident [_a-zA-Z][_a-zA-Z0-9]* map @{ident}|@ var ${ident} int [0-9]+|0[xX][0-9a-fA-F]+ cint :{int}: hex (x|X)[0-9a-fA-F]{1,2} oct [0-7]{1,3} hspace [ \t] vspace [\n\r] space {hspace}|{vspace} path :(\\.|[_\-\./a-zA-Z0-9#\*])*: builtin arg[0-9]|args|cgroup|comm|cpid|cpu|ctx|curtask|elapsed|func|gid|nsecs|pid|probe|rand|retval|sarg[0-9]|tid|uid|username call avg|cat|cgroupid|clear|count|delete|exit|hist|join|kaddr|ksym|lhist|max|min|ntop|override|print|printf|reg|signal|stats|str|strncmp|sum|sym|system|time|uaddr|usym|zero /* Don't add to this! Use builtin OR call not both */ call_and_builtin stack|kstack|ustack %x STR %x STRUCT %x ENUM %x BRACE %x COMMENT %% {hspace}+ { loc.step(); } {vspace}+ { loc.lines(yyleng); loc.step(); } ^"#!".*$ // executable line "//".*$ // single-line comments "/*" yy_push_state(COMMENT, yyscanner); { "*/" yy_pop_state(yyscanner); [^*\n]+|"*" {} \n loc.lines(1); loc.step(); <> yy_pop_state(yyscanner); driver.error(loc, "end of file during comment"); } bpftrace|perf { return Parser::make_STACK_MODE(yytext, loc); } {builtin} { return Parser::make_BUILTIN(yytext, loc); } {call} { return Parser::make_CALL(yytext, loc); } {call_and_builtin} { return Parser::make_CALL_BUILTIN(yytext, loc); } {int} { return Parser::make_INT(strtoll(yytext, NULL, 0), loc); } {cint} { return Parser::make_CINT(strtoll(yytext+1, NULL, 0), loc); } {path} { return Parser::make_PATH(yytext, loc); } {map} { return Parser::make_MAP(yytext, loc); } {var} { return Parser::make_VAR(yytext, loc); } ":" { return Parser::make_COLON(loc); } ";" { return Parser::make_SEMI(loc); } "{" { return Parser::make_LBRACE(loc); } "}" { return Parser::make_RBRACE(loc); } "[" { return Parser::make_LBRACKET(loc); } "]" { return Parser::make_RBRACKET(loc); } "(" { return Parser::make_LPAREN(loc); } ")" { return Parser::make_RPAREN(loc); } \//{space}*[\/\{] { return Parser::make_ENDPRED(loc); } /* If "/" is followed by "/" or "{", choose ENDPRED, otherwise DIV */ "," { return Parser::make_COMMA(loc); } "=" { return Parser::make_ASSIGN(loc); } "<<=" { return Parser::make_LEFTASSIGN(loc); } ">>=" { return Parser::make_RIGHTASSIGN(loc); } "+=" { return Parser::make_PLUSASSIGN(loc); } "-=" { return Parser::make_MINUSASSIGN(loc); } "*=" { return Parser::make_MULASSIGN(loc); } "/=" { return Parser::make_DIVASSIGN(loc); } "%=" { return Parser::make_MODASSIGN(loc); } "&=" { return Parser::make_BANDASSIGN(loc); } "|=" { return Parser::make_BORASSIGN(loc); } "^=" { return Parser::make_BXORASSIGN(loc); } "==" { return Parser::make_EQ(loc); } "!=" { return Parser::make_NE(loc); } "<=" { return Parser::make_LE(loc); } ">=" { return Parser::make_GE(loc); } "<<" { return Parser::make_LEFT(loc); } ">>" { return Parser::make_RIGHT(loc); } "<" { return Parser::make_LT(loc); } ">" { return Parser::make_GT(loc); } "&&" { return Parser::make_LAND(loc); } "||" { return Parser::make_LOR(loc); } "+" { return Parser::make_PLUS(loc); } "-" { return Parser::make_MINUS(loc); } "++" { return Parser::make_INCREMENT(loc); } "--" { return Parser::make_DECREMENT(loc); } "*" { return Parser::make_MUL(loc); } "/" { return Parser::make_DIV(loc); } "%" { return Parser::make_MOD(loc); } "&" { return Parser::make_BAND(loc); } "|" { return Parser::make_BOR(loc); } "^" { return Parser::make_BXOR(loc); } "!" { return Parser::make_LNOT(loc); } "~" { return Parser::make_BNOT(loc); } "." { return Parser::make_DOT(loc); } "->" { return Parser::make_PTR(loc); } "$"[0-9]+ { return Parser::make_PARAM(yytext, loc); } "$"# { return Parser::make_PARAMCOUNT(loc); } "#"[^!].* { return Parser::make_CPREPROC(yytext, loc); } "if" { return Parser::make_IF(loc); } "else" { return Parser::make_ELSE(loc); } "?" { return Parser::make_QUES(loc); } "unroll" { return Parser::make_UNROLL(loc); } \" { yy_push_state(STR, yyscanner); buffer.clear(); } { \" { yy_pop_state(yyscanner); return Parser::make_STRING(buffer, loc); } [^\\\n\"]+ buffer += yytext; \\n buffer += '\n'; \\t buffer += '\t'; \\r buffer += '\r'; \\\" buffer += '\"'; \\\\ buffer += '\\'; \\{oct} { long value = strtol(yytext+1, NULL, 8); if (value > UCHAR_MAX) driver.error(loc, std::string("octal escape sequence out of range '") + yytext + "'"); buffer += value; } \\{hex} buffer += strtol(yytext+2, NULL, 16); \n driver.error(loc, "unterminated string"); yy_pop_state(yyscanner); loc.lines(1); loc.step(); <> driver.error(loc, "unterminated string"); yy_pop_state(yyscanner); \\. { driver.error(loc, std::string("invalid escape character '") + yytext + "'"); } . driver.error(loc, "invalid character"); yy_pop_state(yyscanner); } struct|union|enum yy_push_state(STRUCT, yyscanner); buffer.clear(); struct_type = yytext; { "*"|")" { if (YY_START == STRUCT) { // Finished parsing the typename of a cast // Put the cast type into a canonical form by trimming // and then inserting a single space. yy_pop_state(yyscanner); unput(yytext[0]); return Parser::make_IDENT(struct_type + " " + trim(buffer), loc); } buffer += yytext[0]; } "{" yy_push_state(BRACE, yyscanner); buffer += '{'; "}"|"};" { buffer += yytext; yy_pop_state(yyscanner); if (YY_START == STRUCT) { // Finished parsing a struct definition // Trimming isn't needed here since the typenames // will go through Clang before we get them back // anyway. yy_pop_state(yyscanner); return Parser::make_STRUCT_DEFN(struct_type + buffer, loc); } } . buffer += yytext[0]; \n buffer += '\n'; loc.lines(1); loc.step(); } {ident} { if (driver.bpftrace_.macros_.count(yytext) != 0) { const char *s = driver.bpftrace_.macros_[yytext].c_str(); int z; // NOTE(mmarchini) workaround for simple recursive // macros. More complex recursive macros (for // example, with operators) will go into an // infinite loop. Yes, we should fix that in the // future. if (strcmp(s, yytext) == 0) { return Parser::make_IDENT(yytext, loc); } else { for (z=strlen(s) - 1; z >= 0; z--) unput(s[z]); } } else { return Parser::make_IDENT(yytext, loc); } } . { driver.error(loc, std::string("invalid character '") + std::string(yytext) + std::string("'")); } %% bpftrace-0.9.4/src/libbpf/000077500000000000000000000000001361633214400153425ustar00rootroot00000000000000bpftrace-0.9.4/src/libbpf/bpf.h000066400000000000000000000057321361633214400162710ustar00rootroot00000000000000// clang-format off #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ FN(map_lookup_elem), \ FN(map_update_elem), \ FN(map_delete_elem), \ FN(probe_read), \ FN(ktime_get_ns), \ FN(trace_printk), \ FN(get_prandom_u32), \ FN(get_smp_processor_id), \ FN(skb_store_bytes), \ FN(l3_csum_replace), \ FN(l4_csum_replace), \ FN(tail_call), \ FN(clone_redirect), \ FN(get_current_pid_tgid), \ FN(get_current_uid_gid), \ FN(get_current_comm), \ FN(get_cgroup_classid), \ FN(skb_vlan_push), \ FN(skb_vlan_pop), \ FN(skb_get_tunnel_key), \ FN(skb_set_tunnel_key), \ FN(perf_event_read), \ FN(redirect), \ FN(get_route_realm), \ FN(perf_event_output), \ FN(skb_load_bytes), \ FN(get_stackid), \ FN(csum_diff), \ FN(skb_get_tunnel_opt), \ FN(skb_set_tunnel_opt), \ FN(skb_change_proto), \ FN(skb_change_type), \ FN(skb_under_cgroup), \ FN(get_hash_recalc), \ FN(get_current_task), \ FN(probe_write_user), \ FN(current_task_under_cgroup), \ FN(skb_change_tail), \ FN(skb_pull_data), \ FN(csum_update), \ FN(set_hash_invalid), \ FN(get_numa_node_id), \ FN(skb_change_head), \ FN(xdp_adjust_head), \ FN(probe_read_str), \ FN(get_socket_cookie), \ FN(get_socket_uid), \ FN(set_hash), \ FN(setsockopt), \ FN(skb_adjust_room), \ FN(redirect_map), \ FN(sk_redirect_map), \ FN(sock_map_update), \ FN(xdp_adjust_meta), \ FN(perf_event_read_value), \ FN(perf_prog_read_value), \ FN(getsockopt), \ FN(override_return), \ FN(sock_ops_cb_flags_set), \ FN(msg_redirect_map), \ FN(msg_apply_bytes), \ FN(msg_cork_bytes), \ FN(msg_pull_data), \ FN(bind), \ FN(xdp_adjust_tail), \ FN(skb_get_xfrm_state), \ FN(get_stack), \ FN(skb_load_bytes_relative), \ FN(fib_lookup), \ FN(sock_hash_update), \ FN(msg_redirect_hash), \ FN(sk_redirect_hash), \ FN(lwt_push_encap), \ FN(lwt_seg6_store_bytes), \ FN(lwt_seg6_adjust_srh), \ FN(lwt_seg6_action), \ FN(rc_repeat), \ FN(rc_keydown), \ FN(skb_cgroup_id), \ FN(get_current_cgroup_id), \ FN(get_local_storage), \ FN(sk_select_reuseport), \ FN(skb_ancestor_cgroup_id), \ FN(sk_lookup_tcp), \ FN(sk_lookup_udp), \ FN(sk_release), \ FN(map_push_elem), \ FN(map_pop_elem), \ FN(map_peek_elem), \ FN(msg_push_data), \ FN(msg_pop_data), \ FN(rc_pointer_rel), \ FN(spin_lock), \ FN(spin_unlock), \ FN(sk_fullsock), \ FN(tcp_sock), \ FN(skb_ecn_set_ce), \ FN(get_listener_sock), \ FN(skc_lookup_tcp), \ FN(tcp_check_syncookie), \ FN(sysctl_get_name), \ FN(sysctl_get_current_value), \ FN(sysctl_get_new_value), \ FN(sysctl_set_new_value), \ FN(strtol), \ FN(strtoul), \ FN(sk_storage_get), \ FN(sk_storage_delete), \ FN(send_signal), /* integer value in 'imm' field of BPF_CALL instruction selects which helper * function eBPF program intends to call */ #define __BPF_ENUM_FN(x) BPF_FUNC_ ## x enum bpf_func_id { __BPF_FUNC_MAPPER(__BPF_ENUM_FN) __BPF_FUNC_MAX_ID, }; #undef __BPF_ENUM_FN // clang-format on bpftrace-0.9.4/src/list.cpp000066400000000000000000000175111361633214400155700ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include "list.h" #include "bpftrace.h" #include "utils.h" namespace bpftrace { const std::string kprobe_path = "/sys/kernel/debug/tracing/available_filter_functions"; const std::string tp_path = "/sys/kernel/debug/tracing/events"; inline bool search_probe(const std::string &probe, const std::regex& re) { try { if (std::regex_search(probe, re)) return false; else return true; } catch(std::regex_error& e) { return true; } } void list_dir(const std::string path, std::vector &files) { // yes, I know about std::filesystem::directory_iterator, but no, it wasn't available DIR *dp; struct dirent *dep; if ((dp = opendir(path.c_str())) == NULL) return; while ((dep = readdir(dp)) != NULL) files.push_back(std::string(dep->d_name)); closedir(dp); } void list_probes_from_list(const std::vector &probes_list, const std::string &probetype, const std::string &search, const std::regex& re) { std::string probe; for (auto &probeListItem : probes_list) { probe = probetype + ":" + probeListItem.path + ":"; if (!search.empty()) { if (search_probe(probe, re)) continue; } std::cout << probe << std::endl; } } void print_tracepoint_args(const std::string &category, const std::string &event) { std::string format_file_path = tp_path + "/" + category + "/" + event + "/format"; std::ifstream format_file(format_file_path.c_str()); std::regex re("^\tfield:.*;$", std::regex::icase | std::regex::grep | std::regex::nosubs | std::regex::optimize); std::string line; if (format_file.fail()) { std::cerr << "ERROR: tracepoint format file not found: " << format_file_path << std::endl; return; } // Skip lines until the first empty line do { getline(format_file, line); } while (line.length() > 0); for (; getline(format_file, line); ) { try { if (std::regex_match(line, re)) { unsigned idx = line.find(":") + 1; line = line.substr(idx); idx = line.find(";") + 1; line = line.substr(0, idx); std::cout << " " << line << std::endl; } } catch(std::regex_error& e) { return; } } } void list_probes(const BPFtrace &bpftrace, const std::string &search_input) { std::string search = search_input; std::string probe_name; std::regex re; std::smatch probe_match; std::regex probe_regex(":.*"); std::regex_search ( search, probe_match, probe_regex ); // replace alias name with full name if (probe_match.size()) { auto pos = probe_match.position(0); probe_name = probetypeName(search.substr(0, probe_match.position(0))); search = probe_name + search.substr(pos, search.length()); } std::string s = "^"; for (char c : search) { if (c == '*') s += ".*"; else if (c == '?') s += '.'; else s += c; } s += '$'; try { re = std::regex(s, std::regex::icase | std::regex::grep | std::regex::nosubs | std::regex::optimize); } catch(std::regex_error& e) { std::cerr << "ERROR: invalid character in search expression." << std::endl; return; } // software list_probes_from_list(SW_PROBE_LIST, "software", search, re); // hardware list_probes_from_list(HW_PROBE_LIST, "hardware", search, re); // uprobe { std::unique_ptr symbol_stream; std::string executable; std::string absolute_exe; bool show_all = false; if (bpftrace.pid_ > 0) { executable = get_pid_exe(bpftrace.pid_); absolute_exe = path_for_pid_mountns(bpftrace.pid_, executable); } else if (probe_name == "uprobe") { executable = search.substr(search.find(":") + 1, search.size()); show_all = executable.find(":") == std::string::npos; executable = executable.substr(0, executable.find(":")); auto paths = resolve_binary_path(executable); switch (paths.size()) { case 0: std::cerr << "uprobe target '" << executable << "' does not exist or is not executable" << std::endl; return; case 1: absolute_exe = paths.front(); break; default: std::cerr << "path '" << executable << "' must refer to a unique binary but matched " << paths.size() << std::endl; return; } } if (!executable.empty()) { symbol_stream = std::make_unique( bpftrace.extract_func_symbols_from_path(absolute_exe)); std::string line; while (std::getline(*symbol_stream, line)) { std::string probe = "uprobe:" + absolute_exe + ":" + line; if (show_all || search.empty() || !search_probe(probe, re)) std::cout << probe << std::endl; } } } // usdt usdt_probe_list usdt_probes; bool usdt_path_list = false; if (bpftrace.pid_ > 0) { // PID takes precedence over path, so path from search expression will be ignored if pid specified usdt_probes = USDTHelper::probes_for_pid(bpftrace.pid_); } else if (probe_name == "usdt") { // If the *full* path is provided as part of the search expression parse it out and use it std::string usdt_path = search.substr(search.find(":")+1, search.size()); usdt_path_list = usdt_path.find(":") == std::string::npos; usdt_path = usdt_path.substr(0, usdt_path.find(":")); auto paths = resolve_binary_path(usdt_path); switch (paths.size()) { case 0: std::cerr << "usdt target '" << usdt_path << "' does not exist or is not executable" << std::endl; return; case 1: usdt_probes = USDTHelper::probes_for_path(paths.front()); break; default: std::cerr << "usdt target '" << usdt_path << "' must refer to a unique binary but matched " << paths.size() << std::endl; return; } } for (auto const& usdt_probe : usdt_probes) { std::string path = std::get(usdt_probe); std::string provider = std::get(usdt_probe); std::string fname = std::get(usdt_probe); std::string probe = "usdt:" + path + ":" + provider + ":" + fname; if (usdt_path_list || search.empty() || !search_probe(probe, re)) std::cout << probe << std::endl; } // tracepoints std::string probe; std::vector cats; list_dir(tp_path, cats); for (const std::string &cat : cats) { if (cat == "." || cat == ".." || cat == "enable" || cat == "filter") continue; std::vector events = std::vector(); list_dir(tp_path + "/" + cat, events); for (const std::string &event : events) { if (event == "." || event == ".." || event == "enable" || event == "filter") continue; probe = "tracepoint:" + cat + ":" + event; if (!search.empty()) { if (search_probe(probe, re)) continue; } std::cout << probe << std::endl; if (bt_verbose) print_tracepoint_args(cat, event); } } // Optimization: If the search expression starts with "t" (tracepoint) there is // no need to search for kprobes. if (search[0] == 't') return; // kprobes std::ifstream file(kprobe_path); if (file.fail()) { std::cerr << strerror(errno) << ": " << kprobe_path << std::endl; return; } std::string line; size_t loc; while (std::getline(file, line)) { loc = line.find_first_of(" "); if (loc == std::string::npos) probe = "kprobe:" + line; else probe = "kprobe:" + line.substr(0, loc); if (!search.empty()) { if (search_probe(probe, re)) continue; } std::cout << probe << std::endl; } } } // namespace bpftrace bpftrace-0.9.4/src/list.h000066400000000000000000000041041361633214400152270ustar00rootroot00000000000000#include #include #include "bpftrace.h" namespace bpftrace { struct ProbeListItem { std::string path; std::string alias; uint32_t type; uint64_t defaultp; }; const std::vector SW_PROBE_LIST = { { "alignment-faults", "", PERF_COUNT_SW_ALIGNMENT_FAULTS, 1 }, { "bpf-output", "", PERF_COUNT_SW_BPF_OUTPUT, 1 }, { "context-switches", "cs", PERF_COUNT_SW_CONTEXT_SWITCHES, 1000 }, { "cpu-clock", "cpu", PERF_COUNT_SW_CPU_CLOCK, 1000000 }, { "cpu-migrations", "", PERF_COUNT_SW_CPU_MIGRATIONS, 1 }, { "dummy", "", PERF_COUNT_SW_DUMMY, 1 }, { "emulation-faults", "", PERF_COUNT_SW_EMULATION_FAULTS, 1 }, { "major-faults", "", PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1 }, { "minor-faults", "", PERF_COUNT_SW_PAGE_FAULTS_MIN, 100 }, { "page-faults", "faults", PERF_COUNT_SW_PAGE_FAULTS, 100 }, { "task-clock", "", PERF_COUNT_SW_TASK_CLOCK, 1 }, }; const std::vector HW_PROBE_LIST = { { "backend-stalls", "", PERF_COUNT_HW_STALLED_CYCLES_BACKEND, 1000000 }, { "branch-instructions", "branches", PERF_COUNT_HW_BRANCH_INSTRUCTIONS, 100000 }, { "branch-misses", "", PERF_COUNT_HW_BRANCH_MISSES, 100000 }, { "bus-cycles", "", PERF_COUNT_HW_BUS_CYCLES, 100000 }, { "cache-misses", "", PERF_COUNT_HW_CACHE_MISSES, 1000000 }, { "cache-references", "", PERF_COUNT_HW_CACHE_REFERENCES, 1000000 }, { "cpu-cycles", "cycles", PERF_COUNT_HW_CPU_CYCLES, 1000000 }, { "frontend-stalls", "", PERF_COUNT_HW_STALLED_CYCLES_FRONTEND, 1000000 }, { "instructions", "", PERF_COUNT_HW_INSTRUCTIONS, 1000000 }, { "ref-cycles", "", PERF_COUNT_HW_REF_CPU_CYCLES, 1000000 } }; void list_probes(const BPFtrace &bpftrace, const std::string &search = ""); } // namespace bpftrace bpftrace-0.9.4/src/main.cpp000066400000000000000000000407401361633214400155410ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include "bpffeature.h" #include "bpforc.h" #include "bpftrace.h" #include "clang_parser.h" #include "codegen_llvm.h" #include "driver.h" #include "field_analyser.h" #include "list.h" #include "output.h" #include "printer.h" #include "semantic_analyser.h" #include "tracepoint_format_parser.h" using namespace bpftrace; namespace { enum class OutputBufferConfig { UNSET = 0, LINE, FULL, NONE, }; } // namespace void usage() { // clang-format off std::cerr << "USAGE:" << std::endl; std::cerr << " bpftrace [options] filename" << std::endl; std::cerr << " bpftrace [options] -e 'program'" << std::endl << std::endl; std::cerr << "OPTIONS:" << std::endl; std::cerr << " -B MODE output buffering mode ('full', 'none')" << std::endl; std::cerr << " -f FORMAT output format ('text', 'json')" << std::endl; std::cerr << " -o file redirect bpftrace output to file" << std::endl; std::cerr << " -d debug info dry run" << std::endl; std::cerr << " -dd verbose debug info dry run" << std::endl; std::cerr << " -b force BTF (BPF type format) processing" << std::endl; std::cerr << " -e 'program' execute this program" << std::endl; std::cerr << " -h, --help show this help message" << std::endl; std::cerr << " -I DIR add the directory to the include search path" << std::endl; std::cerr << " --include FILE add an #include file before preprocessing" << std::endl; std::cerr << " -l [search] list probes" << std::endl; std::cerr << " -p PID enable USDT probes on PID" << std::endl; std::cerr << " -c 'CMD' run CMD and enable USDT probes on resulting process" << std::endl; std::cerr << " --unsafe allow unsafe builtin functions" << std::endl; std::cerr << " -v verbose messages" << std::endl; std::cerr << " --info Print information about kernel BPF support" << std::endl; std::cerr << " -V, --version bpftrace version" << std::endl << std::endl; std::cerr << "ENVIRONMENT:" << std::endl; std::cerr << " BPFTRACE_STRLEN [default: 64] bytes on BPF stack per str()" << std::endl; std::cerr << " BPFTRACE_NO_CPP_DEMANGLE [default: 0] disable C++ symbol demangling" << std::endl; std::cerr << " BPFTRACE_MAP_KEYS_MAX [default: 4096] max keys in a map" << std::endl; std::cerr << " BPFTRACE_CAT_BYTES_MAX [default: 10k] maximum bytes read by cat builtin" << std::endl; std::cerr << " BPFTRACE_MAX_PROBES [default: 512] max number of probes" << std::endl; std::cerr << " BPFTRACE_LOG_SIZE [default: 409600] log size in bytes" << std::endl; std::cerr << " BPFTRACE_NO_USER_SYMBOLS [default: 0] disable user symbol resolution" << std::endl; std::cerr << " BPFTRACE_VMLINUX [default: None] vmlinux path used for kernel symbol resolution" << std::endl; std::cerr << " BPFTRACE_BTF [default: None] BTF file" << std::endl; std::cerr << std::endl; std::cerr << "EXAMPLES:" << std::endl; std::cerr << "bpftrace -l '*sleep*'" << std::endl; std::cerr << " list probes containing \"sleep\"" << std::endl; std::cerr << "bpftrace -e 'kprobe:do_nanosleep { printf(\"PID %d sleeping...\\n\", pid); }'" << std::endl; std::cerr << " trace processes calling sleep" << std::endl; std::cerr << "bpftrace -e 'tracepoint:raw_syscalls:sys_enter { @[comm] = count(); }'" << std::endl; std::cerr << " count syscalls by process name" << std::endl; // clang-format on } static void enforce_infinite_rlimit() { struct rlimit rl = {}; int err; rl.rlim_max = RLIM_INFINITY; rl.rlim_cur = rl.rlim_max; err = setrlimit(RLIMIT_MEMLOCK, &rl); if (err) std::cerr << std::strerror(err)<<": couldn't set RLIMIT_MEMLOCK for " << "bpftrace. If your program is not loading, you can try " << "\"ulimit -l 8192\" to fix the problem" << std::endl; } #ifdef BUILD_ASAN static void cap_memory_limits() { } #else static void cap_memory_limits() { struct rlimit rl = {}; int err; uint64_t memory_limit_bytes = 1 * 1024 * 1024 * 1024; // this is a safety measure for issue #528 "LLVM ERROR: out of memory", // and caps bpftrace memory to 1 Gbyte. This may be removed once the LLVM // issue has been fixed, and this is no longer deemed necessary. rl.rlim_max = memory_limit_bytes; rl.rlim_cur = rl.rlim_max; err = setrlimit(RLIMIT_AS, &rl); err += setrlimit(RLIMIT_RSS, &rl); if (err) std::cerr << std::strerror(err)<<": couldn't set RLIMIT_AS and " << "RLIMIT_RSS for bpftrace (these are a temporary precaution to stop " << "accidental large program loads, and are not required" << std::endl; } #endif // BUILD_ASAN bool is_root() { if (geteuid() != 0) { std::cerr << "ERROR: bpftrace currently only supports running as the root user." << std::endl; return false; } else return true; } int main(int argc, char *argv[]) { int err; char *pid_str = nullptr; char *cmd_str = nullptr; bool listing = false; bool safe_mode = true; bool force_btf = false; std::string script, search, file_name, output_file, output_format; OutputBufferConfig obc = OutputBufferConfig::UNSET; int c; const char* const short_options = "dbB:f:e:hlp:vc:Vo:I:"; option long_options[] = { option{ "help", no_argument, nullptr, 'h' }, option{ "version", no_argument, nullptr, 'V' }, option{ "unsafe", no_argument, nullptr, 'u' }, option{ "btf", no_argument, nullptr, 'b' }, option{ "include", required_argument, nullptr, '#' }, option{ "info", no_argument, nullptr, 2000 }, option{ nullptr, 0, nullptr, 0 }, // Must be last }; std::vector include_dirs; std::vector include_files; while ((c = getopt_long( argc, argv, short_options, long_options, nullptr)) != -1) { switch (c) { case 2000: if (is_root()) { std::cerr << BPFfeature().report(); return 0; } return 1; case 'o': output_file = optarg; break; case 'd': bt_debug++; if (bt_debug == DebugLevel::kNone) { usage(); return 1; } break; case 'v': bt_verbose = true; break; case 'B': if (std::strcmp(optarg, "line") == 0) { obc = OutputBufferConfig::LINE; } else if (std::strcmp(optarg, "full") == 0) { obc = OutputBufferConfig::FULL; } else if (std::strcmp(optarg, "none") == 0) { obc = OutputBufferConfig::NONE; } else { std::cerr << "USAGE: -B must be either 'line', 'full', or 'none'." << std::endl; return 1; } break; case 'f': output_format = optarg; break; case 'e': script = optarg; break; case 'p': pid_str = optarg; break; case 'I': include_dirs.push_back(optarg); break; case '#': include_files.push_back(optarg); break; case 'l': listing = true; break; case 'c': cmd_str = optarg; break; case 'u': safe_mode = false; break; case 'b': force_btf = true; break; case 'h': usage(); return 0; case 'V': std::cout << "bpftrace " << BPFTRACE_VERSION << std::endl; return 0; default: usage(); return 1; } } if (argc == 1) { usage(); return 1; } if (bt_verbose && (bt_debug != DebugLevel::kNone)) { // TODO: allow both std::cerr << "USAGE: Use either -v or -d." << std::endl; return 1; } if (cmd_str && pid_str) { std::cerr << "USAGE: Cannot use both -c and -p." << std::endl; usage(); return 1; } std::ostream * os = &std::cout; std::ofstream outputstream; if (!output_file.empty()) { outputstream.open(output_file); if (outputstream.fail()) { std::cerr << "Failed to open output file: \"" << output_file; std::cerr << "\": " << strerror(errno) << std::endl; return 1; } os = &outputstream; } std::unique_ptr output; if (output_format.empty() || output_format == "text") { output = std::make_unique(*os); } else if (output_format == "json") { output = std::make_unique(*os); } else { std::cerr << "Invalid output format \"" << output_format << "\"" << std::endl; std::cerr << "Valid formats: 'text', 'json'" << std::endl; return 1; } switch (obc) { case OutputBufferConfig::UNSET: case OutputBufferConfig::LINE: std::setvbuf(stdout, NULL, _IOLBF, BUFSIZ); break; case OutputBufferConfig::FULL: std::setvbuf(stdout, NULL, _IOFBF, BUFSIZ); break; case OutputBufferConfig::NONE: std::setvbuf(stdout, NULL, _IONBF, BUFSIZ); break; default: // Should never get here std::abort(); } BPFtrace bpftrace(std::move(output)); Driver driver(bpftrace); bpftrace.safe_mode_ = safe_mode; bpftrace.force_btf_ = force_btf; // PID is currently only used for USDT probes that need enabling. Future work: // - make PID a filter for all probe types: pass to perf_event_open(), etc. // - provide PID in USDT probe specification as a way to override -p. bpftrace.pid_ = 0; if (pid_str) { if (!is_numeric(pid_str)) { std::cerr << "ERROR: pid '" << pid_str << "' is not a valid number." << std::endl; return 1; } bpftrace.pid_ = strtol(pid_str, NULL, 10); } // Listing probes if (listing) { if (!is_root()) return 1; if (optind == argc-1) list_probes(bpftrace, argv[optind]); else if (optind == argc) list_probes(bpftrace, ""); else usage(); return 0; } if (script.empty()) { // Script file if (argv[optind] == nullptr) { std::cerr << "USAGE: filename or -e 'program' required." << std::endl; return 1; } std::string filename(argv[optind]); std::ifstream file(filename); if (file.fail()) { std::cerr << "Error opening file '" << filename << "': "; std::cerr << std::strerror(errno) << std::endl; return -1; } std::stringstream buf; buf << file.rdbuf(); driver.source(filename, buf.str()); err = driver.parse(); if (err) return err; optind++; } else { // Script is provided as a command line argument driver.source("stdin", script); err = driver.parse(); if (err) return err; } if (!is_root()) return 1; ast::FieldAnalyser fields(driver.root_, bpftrace); err = fields.analyse(); if (err) return err; // FIXME (mmarchini): maybe we don't want to always enforce an infinite // rlimit? enforce_infinite_rlimit(); cap_memory_limits(); // positional parameters while (optind < argc) { bpftrace.add_param(argv[optind]); optind++; } // defaults bpftrace.join_argnum_ = 16; bpftrace.join_argsize_ = 1024; if (!get_uint64_env_var("BPFTRACE_STRLEN", bpftrace.strlen_)) return 1; // in practice, the largest buffer I've seen fit into the BPF stack was 240 bytes. // I've set the bar lower, in case your program has a deeper stack than the one from my tests, // in the hope that you'll get this instructive error instead of getting the BPF verifier's error. if (bpftrace.strlen_ > 200) { // the verifier errors you would encounter when attempting larger allocations would be: // >240= // ~1024= std::cerr << "'BPFTRACE_STRLEN' " << bpftrace.strlen_ << " exceeds the current maximum of 200 bytes." << std::endl << "This limitation is because strings are currently stored on the 512 byte BPF stack." << std::endl << "Long strings will be pursued in: https://github.com/iovisor/bpftrace/issues/305" << std::endl; return 1; } if (const char* env_p = std::getenv("BPFTRACE_NO_CPP_DEMANGLE")) { if (std::string(env_p) == "1") bpftrace.demangle_cpp_symbols_ = false; else if (std::string(env_p) == "0") bpftrace.demangle_cpp_symbols_ = true; else { std::cerr << "Env var 'BPFTRACE_NO_CPP_DEMANGLE' did not contain a valid value (0 or 1)." << std::endl; return 1; } } if (!get_uint64_env_var("BPFTRACE_MAP_KEYS_MAX", bpftrace.mapmax_)) return 1; if (!get_uint64_env_var("BPFTRACE_MAX_PROBES", bpftrace.max_probes_)) return 1; if (!get_uint64_env_var("BPFTRACE_LOG_SIZE", bpftrace.log_size_)) return 1; if (const char* env_p = std::getenv("BPFTRACE_CAT_BYTES_MAX")) { uint64_t proposed; std::istringstream stringstream(env_p); if (!(stringstream >> proposed)) { std::cerr << "Env var 'BPFTRACE_CAT_BYTES_MAX' did not contain a valid uint64_t, or was zero-valued." << std::endl; return 1; } bpftrace.cat_bytes_max_ = proposed; } if (const char* env_p = std::getenv("BPFTRACE_NO_USER_SYMBOLS")) { std::string s(env_p); if (s == "1") bpftrace.resolve_user_symbols_ = false; else if (s == "0") bpftrace.resolve_user_symbols_ = true; else { std::cerr << "Env var 'BPFTRACE_NO_USER_SYMBOLS' did not contain a valid value (0 or 1)." << std::endl; return 1; } } if (cmd_str) bpftrace.cmd_ = cmd_str; if (TracepointFormatParser::parse(driver.root_) == false) return 1; if (bt_debug != DebugLevel::kNone) { ast::Printer p(std::cout); driver.root_->accept(p); std::cout << std::endl; } ClangParser clang; std::vector extra_flags; { struct utsname utsname; uname(&utsname); std::string ksrc, kobj; auto kdirs = get_kernel_dirs(utsname); ksrc = std::get<0>(kdirs); kobj = std::get<1>(kdirs); if (ksrc != "") extra_flags = get_kernel_cflags(utsname.machine, ksrc, kobj); } extra_flags.push_back("-include"); extra_flags.push_back(CLANG_WORKAROUNDS_H); for (auto dir : include_dirs) { extra_flags.push_back("-I"); extra_flags.push_back(dir); } for (auto file : include_files) { extra_flags.push_back("-include"); extra_flags.push_back(file); } // NOTE(mmarchini): if there are no C definitions, clang parser won't run to // avoid issues in some versions. Since we're including files in the command // line, we want to force parsing, so we make sure C definitions are not // empty before going to clang parser stage. if (!include_files.empty() && driver.root_->c_definitions.empty()) driver.root_->c_definitions = "#define __BPFTRACE_DUMMY__"; if (!clang.parse(driver.root_, bpftrace, extra_flags)) return 1; err = driver.parse(); if (err) return err; BPFfeature features; ast::SemanticAnalyser semantics(driver.root_, bpftrace, features); err = semantics.analyse(); if (err) return err; if (bpftrace.has_child_cmd() && (bpftrace.spawn_child() < 0)) return 1; err = semantics.create_maps(bt_debug != DebugLevel::kNone); if (err) return err; ast::CodegenLLVM llvm(driver.root_, bpftrace); auto bpforc = llvm.compile(bt_debug); if (bt_debug != DebugLevel::kNone) return 0; // Signal handler that lets us know an exit signal was received. struct sigaction act = {}; act.sa_handler = [](int) { BPFtrace::exitsig_recv = true; }; sigaction(SIGINT, &act, NULL); sigaction(SIGTERM, &act, NULL); uint64_t num_probes = bpftrace.num_probes(); if (num_probes == 0) { std::cout << "No probes to attach" << std::endl; return 1; } else if (num_probes > bpftrace.max_probes_) { std::cerr << "Can't attach to " << num_probes << " probes because it " << "exceeds the current limit of " << bpftrace.max_probes_ << " probes." << std::endl << "You can increase the limit through the BPFTRACE_MAX_PROBES " << "environment variable, but BE CAREFUL since a high number of probes " << "attached can cause your system to crash." << std::endl; return 1; } else bpftrace.out_->attached_probes(num_probes); err = bpftrace.run(move(bpforc)); if (err) return err; // We are now post-processing. If we receive another SIGINT, // handle it normally (exit) act.sa_handler = SIG_DFL; sigaction(SIGINT, &act, NULL); std::cout << "\n\n"; err = bpftrace.print_maps(); if (err) return err; return 0; } bpftrace-0.9.4/src/map.cpp000066400000000000000000000070671361633214400153770ustar00rootroot00000000000000#include #include #include #include #include "libbpf.h" #include "utils.h" #include "bpftrace.h" #include "map.h" namespace bpftrace { int Map::create_map(enum bpf_map_type map_type, const char *name, int key_size, int value_size, int max_entries, int flags) { #ifdef HAVE_BCC_CREATE_MAP return bcc_create_map(map_type, name, key_size, value_size, max_entries, flags); #else return bpf_create_map(map_type, name, key_size, value_size, max_entries, flags); #endif } Map::Map(const std::string &name, const SizedType &type, const MapKey &key, int min, int max, int step, int max_entries) { name_ = name; type_ = type; key_ = key; // for lhist maps: lqmin = min; lqmax = max; lqstep = step; int key_size = key.size(); if (type.type == Type::hist || type.type == Type::lhist || type.type == Type::avg || type.type == Type::stats) key_size += 8; if (key_size == 0) key_size = 8; if ((type.type == Type::hist || type.type == Type::lhist || type.type == Type::count || type.type == Type::sum || type.type == Type::min || type.type == Type::max || type.type == Type::avg || type.type == Type::stats) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0))) { map_type_ = BPF_MAP_TYPE_PERCPU_HASH; } else if (type.type == Type::join) { map_type_ = BPF_MAP_TYPE_PERCPU_ARRAY; max_entries = 1; key_size = 4; } else map_type_ = BPF_MAP_TYPE_HASH; int value_size = type.size; int flags = 0; mapfd_ = create_map(map_type_, name.c_str(), key_size, value_size, max_entries, flags); if (mapfd_ < 0) { std::cerr << "Error creating map: '" << name_ << "': " << strerror(errno) << std::endl; } } Map::Map(const SizedType &type) { #ifdef DEBUG // TODO (mmarchini): replace with DCHECK if (!type.IsStack()) { std::cerr << "Map::Map(SizedType) constructor should be called only with stack types" << std::endl; abort(); } #endif type_ = type; int key_size = 4; int value_size = sizeof(uintptr_t) * type.stack_type.limit; std::string name = "stack"; int max_entries = 4096; int flags = 0; enum bpf_map_type map_type = BPF_MAP_TYPE_STACK_TRACE; mapfd_ = create_map(map_type, name.c_str(), key_size, value_size, max_entries, flags); if (mapfd_ < 0) { std::cerr << "Error creating stack id map" << std::endl; // TODO (mmarchini): Check perf_event_max_stack in the semantic_analyzer std::cerr << "This might have happened because kernel.perf_event_max_stack " << "is smaller than " << type.stack_type.limit << ". Try to tweak this value with " << "sysctl kernel.perf_event_max_stack=" << std::endl; } } Map::Map(enum bpf_map_type map_type) { int key_size, value_size, max_entries, flags; map_type_ = map_type; std::string name; #ifdef DEBUG // TODO (mmarchini): replace with DCHECK if (map_type == BPF_MAP_TYPE_STACK_TRACE) { std::cerr << "Use Map::Map(SizedType) constructor instead" << std::endl; abort(); } #endif if (map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY) { std::vector cpus = get_online_cpus(); name = "printf"; key_size = 4; value_size = 4; max_entries = cpus.size(); flags = 0; } else { std::cerr << "invalid map type" << std::endl; abort(); } mapfd_ = create_map(map_type, name.c_str(), key_size, value_size, max_entries, flags); if (mapfd_ < 0) { std::cerr << "Error creating " << name << " map: " << strerror(errno) << std::endl; } } Map::~Map() { if (mapfd_ >= 0) close(mapfd_); } } // namespace bpftrace bpftrace-0.9.4/src/map.h000066400000000000000000000013361361633214400150350ustar00rootroot00000000000000#pragma once #include "imap.h" namespace bpftrace { class Map : public IMap { public: Map(const std::string &name, const SizedType &type, const MapKey &key, int max_entries) : Map(name, type, key, 0, 0, 0, max_entries){}; Map(const std::string &name, const SizedType &type, const MapKey &key, int min, int max, int step, int max_entries); Map(const SizedType &type); Map(enum bpf_map_type map_type); virtual ~Map() override; int create_map(enum bpf_map_type map_type, const char *name, int key_size, int value_size, int max_entries, int flags); }; } // namespace bpftrace bpftrace-0.9.4/src/mapkey.cpp000066400000000000000000000054761361633214400161120ustar00rootroot00000000000000#include "mapkey.h" #include "bpftrace.h" #include "utils.h" namespace bpftrace { bool MapKey::operator!=(const MapKey &k) const { return args_ != k.args_; } size_t MapKey::size() const { size_t size = 0; for (auto &arg : args_) size += arg.size; return size; } std::string MapKey::argument_type_list() const { std::ostringstream list; list << "["; for (size_t i = 0; i < args_.size(); i++) { if (i) list << ", "; list << args_[i]; } list << "]"; return list.str(); } std::vector MapKey::argument_value_list(BPFtrace &bpftrace, const std::vector &data) const { std::vector list; int offset = 0; for (const SizedType &arg : args_) { list.push_back(argument_value(bpftrace, arg, &data[offset])); offset += arg.size; } return list; } std::string MapKey::argument_value_list_str(BPFtrace &bpftrace, const std::vector &data) const { if (args_.empty()) return ""; return "[" + str_join(argument_value_list(bpftrace, data), ", ") + "]"; } std::string MapKey::argument_value(BPFtrace &bpftrace, const SizedType &arg, const void *data) { auto arg_data = static_cast(data); std::ostringstream ptr; switch (arg.type) { case Type::integer: switch (arg.size) { case 1: return std::to_string(read_data(data)); case 2: return std::to_string(read_data(data)); case 4: return std::to_string(read_data(data)); case 8: return std::to_string(read_data(data)); default: break; } break; case Type::kstack: return bpftrace.get_stack( read_data(data), false, arg.stack_type, 4); case Type::ustack: return bpftrace.get_stack( read_data(data), true, arg.stack_type, 4); case Type::ksym: return bpftrace.resolve_ksym(read_data(data)); case Type::usym: return bpftrace.resolve_usym(read_data(data), read_data(arg_data + 8)); case Type::inet: return bpftrace.resolve_inet(read_data(data), (const uint8_t *)(arg_data + 8)); case Type::username: return bpftrace.resolve_uid(read_data(data)); case Type::probe: return bpftrace.probe_ids_[read_data(data)]; case Type::string: return std::string((const char*)data); case Type::cast: if (arg.is_pointer) { // use case: show me these pointer values ptr << "0x" << std::hex << read_data(data); return ptr.str(); } // fall through default: std::cerr << "invalid mapkey argument type" << std::endl; } abort(); } } // namespace bpftrace bpftrace-0.9.4/src/mapkey.h000066400000000000000000000013431361633214400155440ustar00rootroot00000000000000#pragma once #include #include #include "types.h" namespace bpftrace { class BPFtrace; class MapKey { public: std::vector args_; bool operator!=(const MapKey &k) const; size_t size() const; std::string argument_type_list() const; std::vector argument_value_list( BPFtrace &bpftrace, const std::vector &data) const; std::string argument_value_list_str(BPFtrace &bpftrace, const std::vector &data) const; private: static std::string argument_value(BPFtrace &bpftrace, const SizedType &arg, const void *data); }; } // namespace bpftrace bpftrace-0.9.4/src/output.cpp000066400000000000000000000345221361633214400161560ustar00rootroot00000000000000#include "output.h" #include "bpftrace.h" #include "utils.h" namespace bpftrace { std::ostream& operator<<(std::ostream& out, MessageType type) { switch (type) { case MessageType::map: out << "map"; break; case MessageType::hist: out << "hist"; break; case MessageType::stats: out << "stats"; break; case MessageType::printf: out << "printf"; break; case MessageType::time: out << "time"; break; case MessageType::cat: out << "cat"; break; case MessageType::join: out << "join"; break; case MessageType::syscall: out << "syscall"; break; case MessageType::attached_probes: out << "attached_probes"; break; case MessageType::lost_events: out << "lost_events"; break; default: out << "?"; } return out; } std::string TextOutput::hist_index_label(int power) { char suffix = '\0'; if (power >= 40) { suffix = 'T'; power -= 40; } else if (power >= 30) { suffix = 'G'; power -= 30; } else if (power >= 20) { suffix = 'M'; power -= 20; } else if (power >= 10) { suffix = 'K'; power -= 10; } std::ostringstream label; label << (1< &values, int &min_index, int &max_index, int &max_value) const { min_index = -1; max_index = -1; max_value = 0; for (size_t i = 0; i < values.size(); i++) { int v = values.at(i); if (v > 0) { if (min_index == -1) min_index = i; max_index = i; } if (v > max_value) max_value = v; } } void Output::lhist_prepare(const std::vector &values, int min, int max, int step, int &max_index, int &max_value, int &buckets, int &start_value, int &end_value) const { max_index = -1; max_value = 0; buckets = (max - min) / step; // excluding lt and gt buckets for (size_t i = 0; i < values.size(); i++) { int v = values.at(i); if (v != 0) max_index = i; if (v > max_value) max_value = v; } if (max_index == -1) return; // trim empty values start_value = -1; end_value = 0; for (unsigned int i = 0; i <= static_cast(buckets) + 1; i++) { if (values.at(i) > 0) { if (start_value == -1) { start_value = i; } end_value = i; } } if (start_value == -1) { start_value = 0; } } void TextOutput::map(BPFtrace &bpftrace, IMap &map, uint32_t top, uint32_t div, const std::vector, std::vector>> &values_by_key) const { uint32_t i = 0; size_t total = values_by_key.size(); for (auto &pair : values_by_key) { auto key = pair.first; auto value = pair.second; if (top) { if (total > top && i++ < (total - top)) continue; } out_ << map.name_ << map.key_.argument_value_list_str(bpftrace, key) << ": "; out_ << bpftrace.map_value_to_str(map, value, div); if (map.type_.type != Type::kstack && map.type_.type != Type::ustack && map.type_.type != Type::ksym && map.type_.type != Type::usym && map.type_.type != Type::inet) out_ << std::endl; } if (i == 0) out_ << std::endl; } void TextOutput::hist(const std::vector &values, uint32_t div) const { int min_index, max_index, max_value; hist_prepare(values, min_index, max_index, max_value); if (max_index == -1) return; for (int i = min_index; i <= max_index; i++) { std::ostringstream header; if (i == 0) { header << "(..., 0)"; } else if (i == 1) { header << "[0]"; } else if (i == 2) { header << "[1]"; } else { header << "[" << hist_index_label(i-2); header << ", " << hist_index_label(i-2+1) << ")"; } int max_width = 52; int bar_width = values.at(i)/(float)max_value*max_width; std::string bar(bar_width, '@'); out_ << std::setw(16) << std::left << header.str() << std::setw(8) << std::right << (values.at(i) / div) << " |" << std::setw(max_width) << std::left << bar << "|" << std::endl; } } void TextOutput::lhist(const std::vector &values, int min, int max, int step) const { int max_index, max_value, buckets, start_value, end_value; lhist_prepare(values, min, max, step, max_index, max_value, buckets, start_value, end_value); if (max_index == -1) return; for (int i = start_value; i <= end_value; i++) { int max_width = 52; int bar_width = values.at(i)/(float)max_value*max_width; std::ostringstream header; if (i == 0) { header << "(..., " << lhist_index_label(min) << ")"; } else if (i == (buckets + 1)) { header << "[" << lhist_index_label(max) << ", ...)"; } else { header << "[" << lhist_index_label((i - 1) * step + min); header << ", " << lhist_index_label(i * step + min) << ")"; } std::string bar(bar_width, '@'); out_ << std::setw(16) << std::left << header.str() << std::setw(8) << std::right << values.at(i) << " |" << std::setw(max_width) << std::left << bar << "|" << std::endl; } } void TextOutput::map_hist(BPFtrace &bpftrace, IMap &map, uint32_t top, uint32_t div, const std::map, std::vector> &values_by_key, const std::vector, uint64_t>> &total_counts_by_key) const { uint32_t i = 0; for (auto &key_count : total_counts_by_key) { auto &key = key_count.first; auto &value = values_by_key.at(key); if (top) { if (i++ < (values_by_key.size() - top)) continue; } out_ << map.name_ << map.key_.argument_value_list_str(bpftrace, key) << ": " << std::endl; if (map.type_.type == Type::hist) hist(value, div); else lhist(value, map.lqmin, map.lqmax, map.lqstep); out_ << std::endl; } } void TextOutput::map_stats(BPFtrace &bpftrace, IMap &map, const std::map, std::vector> &values_by_key, const std::vector, int64_t>> &total_counts_by_key) const { for (auto &key_count : total_counts_by_key) { auto &key = key_count.first; auto &value = values_by_key.at(key); out_ << map.name_ << map.key_.argument_value_list_str(bpftrace, key) << ": "; int64_t count = (int64_t)value.at(0); int64_t total = value.at(1); int64_t average = 0; if (count != 0) average = total / count; if (map.type_.type == Type::stats) out_ << "count " << count << ", average " << average << ", total " << total << std::endl; else out_ << average << std::endl; } out_ << std::endl; } void TextOutput::message(MessageType type __attribute__((unused)), const std::string& msg, bool nl) const { out_ << msg; if (nl) out_ << std::endl; } void TextOutput::lost_events(uint64_t lost) const { out_ << "Lost " << lost << " events" << std::endl; } void TextOutput::attached_probes(uint64_t num_probes) const { if (num_probes == 1) out_ << "Attaching " << num_probes << " probe..." << std::endl; else out_ << "Attaching " << num_probes << " probes..." << std::endl; } std::string JsonOutput::json_escape(const std::string &str) const { std::ostringstream escaped; for (const char &c : str) { switch (c) { case '"': escaped << "\\\""; break; case '\\': escaped << "\\\\"; break; case '\n': escaped << "\\n"; break; case '\r': escaped << "\\r"; break; case '\t': escaped << "\\t"; break; default: if ('\x00' <= c && c <= '\x1f') { escaped << "\\u" << std::hex << std::setw(4) << std::setfill('0') << (int)c; } else { escaped << c; } } } return escaped.str(); } void JsonOutput::map(BPFtrace &bpftrace, IMap &map, uint32_t top, uint32_t div, const std::vector, std::vector>> &values_by_key) const { if (values_by_key.empty()) return; out_ << "{\"type\": \"" << MessageType::map << "\", \"data\": {"; out_ << "\"" << json_escape(map.name_) << "\": "; if (map.key_.size() > 0) // check if this map has keys out_ << "{"; uint32_t i = 0; uint32_t j = 0; size_t total = values_by_key.size(); for (auto &pair : values_by_key) { auto key = pair.first; auto value = pair.second; if (top) { if (total > top && j++ < (total - top)) continue; } std::vector args = map.key_.argument_value_list(bpftrace, key); if (i > 0) out_ << ", "; if (args.size() > 0) { out_ << "\"" << json_escape(str_join(args, ",")) << "\": "; } if (map.type_.type == Type::kstack || map.type_.type == Type::ustack || map.type_.type == Type::ksym || map.type_.type == Type::usym || map.type_.type == Type::inet || map.type_.type == Type::username || map.type_.type == Type::string || map.type_.type == Type::probe) { out_ << "\"" << json_escape(bpftrace.map_value_to_str(map, value, div)) << "\""; } else { out_ << bpftrace.map_value_to_str(map, value, div); } i++; } if (map.key_.size() > 0) out_ << "}"; out_ << "}}" << std::endl; } void JsonOutput::hist(const std::vector &values, uint32_t div) const { int min_index, max_index, max_value; hist_prepare(values, min_index, max_index, max_value); if (max_index == -1) return; out_ << "["; for (int i = min_index; i <= max_index; i++) { if (i > min_index) out_ << ", "; out_ << "{"; if (i == 0) { out_ << "\"max\": -1, "; } else if (i == 1) { out_ << "\"min\": 0, \"max\": 0, "; } else if (i == 2) { out_ << "\"min\": 1, \"max\": 1, "; } else { long low = 1 << (i-2); long high = (1 << (i-2+1)) - 1; out_ << "\"min\": " << low << ", \"max\": " << high << ", "; } out_ << "\"count\": " << values.at(i) / div; out_ << "}"; } out_ << "]"; } void JsonOutput::lhist(const std::vector &values, int min, int max, int step) const { int max_index, max_value, buckets, start_value, end_value; lhist_prepare(values, min, max, step, max_index, max_value, buckets, start_value, end_value); if (max_index == -1) return; out_ << "["; for (int i = start_value; i <= end_value; i++) { if (i > start_value) out_ << ", "; out_ << "{"; if (i == 0) { out_ << "\"max\": " << min - 1 << ", "; } else if (i == (buckets + 1)) { out_ << "\"min\": " << max << ", "; } else { long low = (i - 1) * step + min; long high = i * step + min - 1; out_ << "\"min\": " << low << ", \"max\": " << high << ", "; } out_ << "\"count\": " << values.at(i); out_ << "}"; } out_ << "]"; } void JsonOutput::map_hist(BPFtrace &bpftrace, IMap &map, uint32_t top, uint32_t div, const std::map, std::vector> &values_by_key, const std::vector, uint64_t>> &total_counts_by_key) const { if (total_counts_by_key.empty()) return; out_ << "{\"type\": \"" << MessageType::hist << "\", \"data\": {"; out_ << "\"" << json_escape(map.name_) << "\": "; if (map.key_.size() > 0) // check if this map has keys out_ << "{"; uint32_t i = 0; uint32_t j = 0; for (auto &key_count : total_counts_by_key) { auto &key = key_count.first; auto &value = values_by_key.at(key); if (top) { if (j++ < (values_by_key.size() - top)) continue; } std::vector args = map.key_.argument_value_list(bpftrace, key); if (i > 0) out_ << ", "; if (args.size() > 0) { out_ << "\"" << json_escape(str_join(args, ",")) << "\": "; } if (map.type_.type == Type::hist) hist(value, div); else lhist(value, map.lqmin, map.lqmax, map.lqstep); i++; } if (map.key_.size() > 0) out_ << "}"; out_ << "}}" << std::endl; } void JsonOutput::map_stats(BPFtrace &bpftrace, IMap &map, const std::map, std::vector> &values_by_key, const std::vector, int64_t>> &total_counts_by_key) const { if (total_counts_by_key.empty()) return; out_ << "{\"type\": \"" << MessageType::stats << "\", \"data\": {"; out_ << "\"" << json_escape(map.name_) << "\": "; if (map.key_.size() > 0) // check if this map has keys out_ << "{"; uint32_t i = 0; for (auto &key_count : total_counts_by_key) { auto &key = key_count.first; auto &value = values_by_key.at(key); std::vector args = map.key_.argument_value_list(bpftrace, key); if (i > 0) out_ << ", "; if (args.size() > 0) { out_ << " \"" << json_escape(str_join(args, ",")) << "\": "; } uint64_t count = value.at(0); int64_t total = value.at(1); int64_t average = 0; if (count != 0) average = total / count; if (map.type_.type == Type::stats) out_ << "{\"count\": " << count << ", \"average\": " << average << ", \"total\": " << total << "}"; else out_ << average; i++; } if (map.key_.size() > 0) out_ << "}"; out_ << "}}" << std::endl; } void JsonOutput::message(MessageType type, const std::string& msg, bool nl __attribute__((unused))) const { out_ << "{\"type\": \"" << type << "\", \"data\": \"" << json_escape(msg) << "\"}" << std::endl; } void JsonOutput::message(MessageType type, const std::string& field, uint64_t value) const { out_ << "{\"type\": \"" << type << "\", \"data\": " << "{\"" << field << "\": " << value << "}" << "}" << std::endl; } void JsonOutput::lost_events(uint64_t lost) const { message(MessageType::lost_events, "events", lost); } void JsonOutput::attached_probes(uint64_t num_probes) const { message(MessageType::attached_probes, "probes", num_probes); } } // namespace bpftrace bpftrace-0.9.4/src/output.h000066400000000000000000000113611361633214400156170ustar00rootroot00000000000000#pragma once #include #include #include #include #include "imap.h" namespace bpftrace { enum class MessageType { // don't forget to update std::ostream& operator<<(std::ostream& out, MessageType type) in output.cpp map, hist, stats, printf, time, cat, join, syscall, attached_probes, lost_events }; std::ostream& operator<<(std::ostream& out, MessageType type); class Output { public: explicit Output(std::ostream& out = std::cout, std::ostream& err = std::cerr) : out_(out),err_(err) { } Output(const Output &) = delete; Output& operator=(const Output &) = delete; virtual ~Output() = default; virtual std::ostream& outputstream() const { return out_; }; virtual void map(BPFtrace &bpftrace, IMap &map, uint32_t top, uint32_t div, const std::vector, std::vector>> &values_by_key) const = 0; virtual void map_hist(BPFtrace &bpftrace, IMap &map, uint32_t top, uint32_t div, const std::map, std::vector> &values_by_key, const std::vector, uint64_t>> &total_counts_by_key) const = 0; virtual void map_stats(BPFtrace &bpftrace, IMap &map, const std::map, std::vector> &values_by_key, const std::vector, int64_t>> &total_counts_by_key) const = 0; virtual void message(MessageType type, const std::string& msg, bool nl = true) const = 0; virtual void lost_events(uint64_t lost) const = 0; virtual void attached_probes(uint64_t num_probes) const = 0; protected: std::ostream &out_; std::ostream &err_; void hist_prepare(const std::vector &values, int &min_index, int &max_index, int &max_value) const; void lhist_prepare(const std::vector &values, int min, int max, int step, int &max_index, int &max_value, int &buckets, int &start_value, int &end_value) const; }; class TextOutput : public Output { public: explicit TextOutput(std::ostream& out = std::cout, std::ostream& err = std::cerr) : Output(out, err) { } void map(BPFtrace &bpftrace, IMap &map, uint32_t top, uint32_t div, const std::vector, std::vector>> &values_by_key) const override; void map_hist(BPFtrace &bpftrace, IMap &map, uint32_t top, uint32_t div, const std::map, std::vector> &values_by_key, const std::vector, uint64_t>> &total_counts_by_key) const override; void map_stats(BPFtrace &bpftrace, IMap &map, const std::map, std::vector> &values_by_key, const std::vector, int64_t>> &total_counts_by_key) const override; void message(MessageType type, const std::string& msg, bool nl = true) const override; void lost_events(uint64_t lost) const override; void attached_probes(uint64_t num_probes) const override; private: static std::string hist_index_label(int power); static std::string lhist_index_label(int number); void hist(const std::vector &values, uint32_t div) const; void lhist(const std::vector &values, int min, int max, int step) const; }; class JsonOutput : public Output { public: explicit JsonOutput(std::ostream& out = std::cout, std::ostream& err = std::cerr) : Output(out, err) { } void map(BPFtrace &bpftrace, IMap &map, uint32_t top, uint32_t div, const std::vector, std::vector>> &values_by_key) const override; void map_hist(BPFtrace &bpftrace, IMap &map, uint32_t top, uint32_t div, const std::map, std::vector> &values_by_key, const std::vector, uint64_t>> &total_counts_by_key) const override; void map_stats(BPFtrace &bpftrace, IMap &map, const std::map, std::vector> &values_by_key, const std::vector, int64_t>> &total_counts_by_key) const override; void message(MessageType type, const std::string& msg, bool nl = true) const override; void message(MessageType type, const std::string& field, uint64_t value) const; void lost_events(uint64_t lost) const override; void attached_probes(uint64_t num_probes) const override; private: std::string json_escape(const std::string &str) const; void hist(const std::vector &values, uint32_t div) const; void lhist(const std::vector &values, int min, int max, int step) const; }; } // namespace bpftrace bpftrace-0.9.4/src/parser.yy000066400000000000000000000364461361633214400160000ustar00rootroot00000000000000%skeleton "lalr1.cc" %require "3.0.4" %defines %define api.namespace { bpftrace } %define parser_class_name { Parser } %define api.token.constructor %define api.value.type variant %define parse.assert %define parse.error verbose %param { bpftrace::Driver &driver } %param { void *yyscanner } %locations // Forward declarations of classes referenced in the parser %code requires { namespace bpftrace { class Driver; namespace ast { class Node; } // namespace ast } // namespace bpftrace #include "ast.h" } %{ #include #include "driver.h" void yyerror(bpftrace::Driver &driver, const char *s); %} %token END 0 "end of file" COLON ":" SEMI ";" LBRACE "{" RBRACE "}" LBRACKET "[" RBRACKET "]" LPAREN "(" RPAREN ")" QUES "?" ENDPRED "end predicate" COMMA "," PARAMCOUNT "$#" ASSIGN "=" EQ "==" NE "!=" LE "<=" GE ">=" LEFT "<<" RIGHT ">>" LT "<" GT ">" LAND "&&" LOR "||" PLUS "+" INCREMENT "++" LEFTASSIGN "<<=" RIGHTASSIGN ">>=" PLUSASSIGN "+=" MINUSASSIGN "-=" MULASSIGN "*=" DIVASSIGN "/=" MODASSIGN "%=" BANDASSIGN "&=" BORASSIGN "|=" BXORASSIGN "^=" MINUS "-" DECREMENT "--" MUL "*" DIV "/" MOD "%" BAND "&" BOR "|" BXOR "^" LNOT "!" BNOT "~" DOT "." PTR "->" IF "if" ELSE "else" UNROLL "unroll" STRUCT "struct" UNION "union" ; %token BUILTIN "builtin" %token CALL "call" %token CALL_BUILTIN "call_builtin" %token IDENT "identifier" %token PATH "path" %token CPREPROC "preprocessor directive" %token STRUCT_DEFN "struct definition" %token ENUM "enum" %token STRING "string" %token MAP "map" %token VAR "variable" %token PARAM "positional parameter" %token INT "integer" %token CINT "colon surrounded integer" %token STACK_MODE "stack_mode" %type c_definitions %type probes %type probe %type pred %type ternary %type block stmts %type block_stmt stmt semicolon_ended_stmt compound_assignment %type expr %type call %type map %type var %type vargs %type attach_points %type attach_point %type param %type wildcard %type ident %type map_or_var %type pre_post_op %type int %right ASSIGN %left QUES COLON %left LOR %left LAND %left BOR %left BXOR %left BAND %left EQ NE %left LE GE LT GT %left LEFT RIGHT %left PLUS MINUS %left MUL DIV MOD %right LNOT BNOT DEREF CAST %left DOT PTR %start program %% program : c_definitions probes { driver.root_ = new ast::Program($1, $2); } ; c_definitions : CPREPROC c_definitions { $$ = $1 + "\n" + $2; } | STRUCT_DEFN c_definitions { $$ = $1 + ";\n" + $2; } | ENUM c_definitions { $$ = $1 + ";\n" + $2; } | { $$ = std::string(); } ; probes : probes probe { $$ = $1; $1->push_back($2); } | probe { $$ = new ast::ProbeList; $$->push_back($1); } ; probe : attach_points pred block { $$ = new ast::Probe($1, $2, $3); } ; attach_points : attach_points "," attach_point { $$ = $1; $1->push_back($3); } | attach_point { $$ = new ast::AttachPointList; $$->push_back($1); } ; attach_point : ident { $$ = new ast::AttachPoint($1, @$); } | ident ":" wildcard { $$ = new ast::AttachPoint($1, $3, @$); } | ident ":" wildcard PLUS INT { $$ = new ast::AttachPoint($1, $3, $5, @$); } | ident PATH STRING { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3, false, @$); } | ident PATH wildcard { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3, true, @$); } | ident PATH wildcard PLUS INT { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3, (uint64_t) $5, @$); } | ident PATH STRING PLUS INT { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3, (uint64_t) $5, @$); } | ident PATH INT { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3, @$); } | ident PATH INT CINT ident { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3, $4, $5, @$); } | ident PATH STRING ":" STRING { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3, $5, false, @$); } | ident PATH STRING ":" wildcard { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3, $5, true, @$); } | ident PATH wildcard ":" STRING { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3, $5, true, @$); } | ident PATH wildcard ":" wildcard { $$ = new ast::AttachPoint($1, $2.substr(1, $2.size()-2), $3, $5, true, @$); } ; wildcard : wildcard ident { $$ = $1 + $2; } | wildcard MUL { $$ = $1 + "*"; } | wildcard LBRACKET { $$ = $1 + "["; } | wildcard RBRACKET { $$ = $1 + "]"; } | wildcard DOT { $$ = $1 + "."; } | { $$ = ""; } ; pred : DIV expr ENDPRED { $$ = new ast::Predicate($2, @$); } | { $$ = nullptr; } ; ternary : expr QUES expr COLON expr { $$ = new ast::Ternary($1, $3, $5, @$); } ; param : PARAM { $$ = new ast::PositionalParameter(PositionalParameterType::positional, std::stoll($1.substr(1, $1.size()-1)), @$); } | PARAMCOUNT { $$ = new ast::PositionalParameter(PositionalParameterType::count, 0, @$); } ; block : "{" stmts "}" { $$ = $2; } ; semicolon_ended_stmt: stmt ";" { $$ = $1; } ; stmts : semicolon_ended_stmt stmts { $$ = $2; $2->insert($2->begin(), $1); } | block_stmt stmts { $$ = $2; $2->insert($2->begin(), $1); } | stmt { $$ = new ast::StatementList; $$->push_back($1); } | { $$ = new ast::StatementList; } ; block_stmt : IF "(" expr ")" block { $$ = new ast::If($3, $5); } | IF "(" expr ")" block ELSE block { $$ = new ast::If($3, $5, $7); } | UNROLL "(" INT ")" block { $$ = new ast::Unroll($3, $5); } ; stmt : expr { $$ = new ast::ExprStatement($1); } | compound_assignment { $$ = $1; } | map "=" expr { $$ = new ast::AssignMapStatement($1, $3, @2); } | var "=" expr { $$ = new ast::AssignVarStatement($1, $3, @2); } ; compound_assignment : map LEFTASSIGN expr { $$ = new ast::AssignMapStatement($1, new ast::Binop($1, token::LEFT, $3, @2)); } | var LEFTASSIGN expr { $$ = new ast::AssignVarStatement($1, new ast::Binop($1, token::LEFT, $3, @2)); } | map RIGHTASSIGN expr { $$ = new ast::AssignMapStatement($1, new ast::Binop($1, token::RIGHT, $3, @2)); } | var RIGHTASSIGN expr { $$ = new ast::AssignVarStatement($1, new ast::Binop($1, token::RIGHT, $3, @2)); } | map PLUSASSIGN expr { $$ = new ast::AssignMapStatement($1, new ast::Binop($1, token::PLUS, $3, @2)); } | var PLUSASSIGN expr { $$ = new ast::AssignVarStatement($1, new ast::Binop($1, token::PLUS, $3, @2)); } | map MINUSASSIGN expr { $$ = new ast::AssignMapStatement($1, new ast::Binop($1, token::MINUS, $3, @2)); } | var MINUSASSIGN expr { $$ = new ast::AssignVarStatement($1, new ast::Binop($1, token::MINUS, $3, @2)); } | map MULASSIGN expr { $$ = new ast::AssignMapStatement($1, new ast::Binop($1, token::MUL, $3, @2)); } | var MULASSIGN expr { $$ = new ast::AssignVarStatement($1, new ast::Binop($1, token::MUL, $3, @2)); } | map DIVASSIGN expr { $$ = new ast::AssignMapStatement($1, new ast::Binop($1, token::DIV, $3, @2)); } | var DIVASSIGN expr { $$ = new ast::AssignVarStatement($1, new ast::Binop($1, token::DIV, $3, @2)); } | map MODASSIGN expr { $$ = new ast::AssignMapStatement($1, new ast::Binop($1, token::MOD, $3, @2)); } | var MODASSIGN expr { $$ = new ast::AssignVarStatement($1, new ast::Binop($1, token::MOD, $3, @2)); } | map BANDASSIGN expr { $$ = new ast::AssignMapStatement($1, new ast::Binop($1, token::BAND, $3, @2)); } | var BANDASSIGN expr { $$ = new ast::AssignVarStatement($1, new ast::Binop($1, token::BAND, $3, @2)); } | map BORASSIGN expr { $$ = new ast::AssignMapStatement($1, new ast::Binop($1, token::BOR, $3, @2)); } | var BORASSIGN expr { $$ = new ast::AssignVarStatement($1, new ast::Binop($1, token::BOR, $3, @2)); } | map BXORASSIGN expr { $$ = new ast::AssignMapStatement($1, new ast::Binop($1, token::BXOR, $3, @2)); } | var BXORASSIGN expr { $$ = new ast::AssignVarStatement($1, new ast::Binop($1, token::BXOR, $3, @2)); } ; int : MINUS INT { $$ = new ast::Integer(-1 * $2, @$); } | INT { $$ = new ast::Integer($1, @$); } ; expr : int { $$ = $1; } | STRING { $$ = new ast::String($1, @$); } | BUILTIN { $$ = new ast::Builtin($1, @$); } | CALL_BUILTIN { $$ = new ast::Builtin($1, @$); } | IDENT { $$ = new ast::Identifier($1, @$); } | STACK_MODE { $$ = new ast::StackMode($1, @$); } | ternary { $$ = $1; } | param { $$ = $1; } | map_or_var { $$ = $1; } | call { $$ = $1; } | "(" expr ")" { $$ = $2; } | expr EQ expr { $$ = new ast::Binop($1, token::EQ, $3, @2); } | expr NE expr { $$ = new ast::Binop($1, token::NE, $3, @2); } | expr LE expr { $$ = new ast::Binop($1, token::LE, $3, @2); } | expr GE expr { $$ = new ast::Binop($1, token::GE, $3, @2); } | expr LT expr { $$ = new ast::Binop($1, token::LT, $3, @2); } | expr GT expr { $$ = new ast::Binop($1, token::GT, $3, @2); } | expr LAND expr { $$ = new ast::Binop($1, token::LAND, $3, @2); } | expr LOR expr { $$ = new ast::Binop($1, token::LOR, $3, @2); } | expr LEFT expr { $$ = new ast::Binop($1, token::LEFT, $3, @2); } | expr RIGHT expr { $$ = new ast::Binop($1, token::RIGHT, $3, @2); } | expr PLUS expr { $$ = new ast::Binop($1, token::PLUS, $3, @2); } | expr MINUS expr { $$ = new ast::Binop($1, token::MINUS, $3, @2); } | expr MUL expr { $$ = new ast::Binop($1, token::MUL, $3, @2); } | expr DIV expr { $$ = new ast::Binop($1, token::DIV, $3, @2); } | expr MOD expr { $$ = new ast::Binop($1, token::MOD, $3, @2); } | expr BAND expr { $$ = new ast::Binop($1, token::BAND, $3, @2); } | expr BOR expr { $$ = new ast::Binop($1, token::BOR, $3, @2); } | expr BXOR expr { $$ = new ast::Binop($1, token::BXOR, $3, @2); } | LNOT expr { $$ = new ast::Unop(token::LNOT, $2, @1); } | BNOT expr { $$ = new ast::Unop(token::BNOT, $2, @1); } | MINUS expr { $$ = new ast::Unop(token::MINUS, $2, @1); } | MUL expr %prec DEREF { $$ = new ast::Unop(token::MUL, $2, @1); } | expr DOT ident { $$ = new ast::FieldAccess($1, $3, @2); } | expr PTR ident { $$ = new ast::FieldAccess(new ast::Unop(token::MUL, $1, @2), $3, @$); } | expr "[" expr "]" { $$ = new ast::ArrayAccess($1, $3, @2 + @4); } | "(" IDENT ")" expr %prec CAST { $$ = new ast::Cast($2, false, $4, @1 + @3); } | "(" IDENT MUL ")" expr %prec CAST { $$ = new ast::Cast($2, true, $5, @1 + @4); } | pre_post_op { $$ = $1; } ; pre_post_op : map_or_var INCREMENT { $$ = new ast::Unop(token::INCREMENT, $1, true, @2); } | map_or_var DECREMENT { $$ = new ast::Unop(token::DECREMENT, $1, true, @2); } | INCREMENT map_or_var { $$ = new ast::Unop(token::INCREMENT, $2, @1); } | DECREMENT map_or_var { $$ = new ast::Unop(token::DECREMENT, $2, @1); } | ident INCREMENT { error(@1, "The ++ operator must be applied to a map or variable"); YYERROR; } | INCREMENT ident { error(@1, "The ++ operator must be applied to a map or variable"); YYERROR; } | ident DECREMENT { error(@1, "The -- operator must be applied to a map or variable"); YYERROR; } | DECREMENT ident { error(@1, "The -- operator must be applied to a map or variable"); YYERROR; } ; ident : IDENT { $$ = $1; } | BUILTIN { $$ = $1; } | CALL { $$ = $1; } | CALL_BUILTIN { $$ = $1; } | STACK_MODE { $$ = $1; } ; call : CALL "(" ")" { $$ = new ast::Call($1, @$); } | CALL "(" vargs ")" { $$ = new ast::Call($1, $3, @$); } | CALL_BUILTIN "(" ")" { $$ = new ast::Call($1, @$); } | CALL_BUILTIN "(" vargs ")" { $$ = new ast::Call($1, $3, @$); } | IDENT "(" ")" { error(@1, "Unknown function: " + $1); YYERROR; } | IDENT "(" vargs ")" { error(@1, "Unknown function: " + $1); YYERROR; } | BUILTIN "(" ")" { error(@1, "Unknown function: " + $1); YYERROR; } | BUILTIN "(" vargs ")" { error(@1, "Unknown function: " + $1); YYERROR; } | STACK_MODE "(" ")" { error(@1, "Unknown function: " + $1); YYERROR; } | STACK_MODE "(" vargs ")" { error(@1, "Unknown function: " + $1); YYERROR; } ; map : MAP { $$ = new ast::Map($1, @$); } | MAP "[" vargs "]" { $$ = new ast::Map($1, $3, @$); } ; var : VAR { $$ = new ast::Variable($1, @$); } ; map_or_var : var { $$ = $1; } | map { $$ = $1; } ; vargs : vargs "," expr { $$ = $1; $1->push_back($3); } | expr { $$ = new ast::ExpressionList; $$->push_back($1); } ; %% void bpftrace::Parser::error(const location &l, const std::string &m) { driver.error(l, m); } bpftrace-0.9.4/src/printf.cpp000066400000000000000000000050201361633214400161070ustar00rootroot00000000000000#include #include "printf.h" #include "printf_format_types.h" #include "struct.h" namespace bpftrace { std::string verify_format_string(const std::string &fmt, std::vector args) { std::stringstream message; const std::regex re("%-?[0-9]*(\\.[0-9]+)?[a-zA-Z]+"); auto tokens_begin = std::sregex_iterator(fmt.begin(), fmt.end(), re); auto tokens_end = std::sregex_iterator(); auto num_tokens = std::distance(tokens_begin, tokens_end); int num_args = args.size(); if (num_args < num_tokens) { message << "printf: Not enough arguments for format string (" << num_args << " supplied, " << num_tokens << " expected)" << std::endl; return message.str(); } if (num_args > num_tokens) { message << "printf: Too many arguments for format string (" << num_args << " supplied, " << num_tokens << " expected)" << std::endl; return message.str(); } auto token_iter = tokens_begin; for (int i=0; istr()[offset] == '-') offset++; while ((token_iter->str()[offset] >= '0' && token_iter->str()[offset] <= '9') || token_iter->str()[offset] == '.') offset++; const std::string token = token_iter->str().substr(offset); const auto token_type_iter = printf_format_types.find(token); if (token_type_iter == printf_format_types.end()) { message << "printf: Unknown format string token: %" << token << std::endl; return message.str(); } const Type &token_type = token_type_iter->second; if (arg_type != token_type) { message << "printf: %" << token << " specifier expects a value of type " << token_type << " (" << arg_type << " supplied)" << std::endl; return message.str(); } } return ""; } uint64_t PrintableString::value() { return (uint64_t)value_.c_str(); } uint64_t PrintableCString::value() { return (uint64_t)value_; } uint64_t PrintableInt::value() { return value_; } } // namespace bpftrace bpftrace-0.9.4/src/printf.h000066400000000000000000000015211361633214400155560ustar00rootroot00000000000000#pragma once #include #include "ast.h" #include "types.h" namespace bpftrace { struct Field; std::string verify_format_string(const std::string& fmt, std::vector args); class IPrintable { public: virtual ~IPrintable() { }; virtual uint64_t value() = 0; }; class PrintableString : public virtual IPrintable { public: PrintableString(std::string value) : value_(std::move(value)) { } uint64_t value(); private: std::string value_; }; class PrintableCString : public virtual IPrintable { public: PrintableCString(char* value) : value_(value) { } uint64_t value(); private: char* value_; }; class PrintableInt : public virtual IPrintable { public: PrintableInt(uint64_t value) : value_(value) { } uint64_t value(); private: uint64_t value_; }; } // namespace bpftrace bpftrace-0.9.4/src/printf_format_types.h000066400000000000000000000032321361633214400203530ustar00rootroot00000000000000#include #include "types.h" namespace bpftrace { // valid printf length + specifier combinations // it's done like this because as of this writing, C++ doesn't have a builtin way to do // compile time string concatenation. here is the Python 3 code that generates this map: // #!/usr/bin/python3 // lengths = ("", "hh", "h", "l", "ll", "j", "z", "t") // specifiers = ("d", "u", "x", "X", "p") // // print("{\"s\", Type::string},") // print(",\n".join([f"{{\"{l+s}\", Type::integer}}" for l in lengths for s in specifiers])) const std::unordered_map printf_format_types = { {"s", Type::string}, {"c", Type::integer}, {"d", Type::integer}, {"u", Type::integer}, {"x", Type::integer}, {"X", Type::integer}, {"p", Type::integer}, {"hhd", Type::integer}, {"hhu", Type::integer}, {"hhx", Type::integer}, {"hhX", Type::integer}, {"hhp", Type::integer}, {"hd", Type::integer}, {"hu", Type::integer}, {"hx", Type::integer}, {"hX", Type::integer}, {"hp", Type::integer}, {"ld", Type::integer}, {"lu", Type::integer}, {"lx", Type::integer}, {"lX", Type::integer}, {"lp", Type::integer}, {"lld", Type::integer}, {"llu", Type::integer}, {"llx", Type::integer}, {"llX", Type::integer}, {"llp", Type::integer}, {"jd", Type::integer}, {"ju", Type::integer}, {"jx", Type::integer}, {"jX", Type::integer}, {"jp", Type::integer}, {"zd", Type::integer}, {"zu", Type::integer}, {"zx", Type::integer}, {"zX", Type::integer}, {"zp", Type::integer}, {"td", Type::integer}, {"tu", Type::integer}, {"tx", Type::integer}, {"tX", Type::integer}, {"tp", Type::integer} }; } // namespace bpftrace bpftrace-0.9.4/src/resolve_cgroupid.cpp000066400000000000000000000043011361633214400201610ustar00rootroot00000000000000#ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #ifdef HAVE_NAME_TO_HANDLE_AT # include # include #else # include # include #endif #include #include #include #include #include "act_helpers.h" #include "resolve_cgroupid.h" namespace { #ifndef HAVE_NAME_TO_HANDLE_AT struct file_handle { unsigned int handle_bytes; int handle_type; char f_handle[0]; }; int name_to_handle_at(int dirfd, const char *pathname, struct file_handle *handle, int *mount_id, int flags) { return (int)syscall(SYS_name_to_handle_at, dirfd, pathname, handle, mount_id, flags); } #endif // Not embedding file_handle directly in cgid_file_handle, because C++ // has problems with zero-sized array as struct members and // file_handle's f_handle is a zero-sized array. // // Also, not embedding file_handle through the public inheritance, // since checking for member offsets with offsetof within a // non-standard-layout type is conditionally-supported (so compilers // are not required to support it). And we want to do it to make sure // we got the wrapper right. // // Hence open coding the file_handle members directly in // cgid_file_handle and the static asserts following it. struct cgid_file_handle { file_handle *as_file_handle_ptr() { return reinterpret_cast(this); } unsigned int handle_bytes = sizeof(std::uint64_t); int handle_type; std::uint64_t cgid; }; ACTH_ASSERT_SAME_SIZE(cgid_file_handle, file_handle, std::uint64_t); ACTH_ASSERT_SAME_MEMBER(cgid_file_handle, handle_bytes, file_handle, handle_bytes); ACTH_ASSERT_SAME_MEMBER(cgid_file_handle, handle_type, file_handle, handle_type); ACTH_ASSERT_SAME_OFFSET(cgid_file_handle, cgid, file_handle, f_handle); } namespace bpftrace_linux { std::uint64_t resolve_cgroupid(const std::string &path) { cgid_file_handle cfh; int mount_id; int err = name_to_handle_at(AT_FDCWD, path.c_str(), cfh.as_file_handle_ptr(), &mount_id, 0); if (err < 0) { auto emsg = std::strerror(errno); throw std::runtime_error("Failed to get `cgroupid` for path: \"" + path + "\": " + emsg); } return cfh.cgid; } } bpftrace-0.9.4/src/resolve_cgroupid.h000066400000000000000000000002141361633214400176250ustar00rootroot00000000000000#pragma once #include #include namespace bpftrace_linux { std::uint64_t resolve_cgroupid(const std::string &path); } bpftrace-0.9.4/src/signal.cpp000066400000000000000000000023341361633214400160670ustar00rootroot00000000000000#include #include #include #include namespace bpftrace { static std::map signals = { {"SIGABRT", SIGABRT}, {"SIGALRM", SIGALRM}, {"SIGBUS", SIGBUS}, {"SIGCHLD", SIGCHLD}, {"SIGCONT", SIGCONT}, {"SIGFPE", SIGFPE}, {"SIGHUP", SIGHUP}, {"SIGILL", SIGILL}, {"SIGINT", SIGINT}, {"SIGKILL", SIGKILL}, {"SIGPIPE", SIGPIPE}, {"SIGPOLL", SIGPOLL}, {"SIGQUIT", SIGQUIT}, {"SIGSEGV", SIGSEGV}, {"SIGSTOP", SIGSTOP}, {"SIGSYS", SIGSYS}, {"SIGTERM", SIGTERM}, {"SIGTRAP", SIGTRAP}, {"SIGTSTP", SIGTSTP}, {"SIGTTIN", SIGTTIN}, {"SIGTTOU", SIGTTOU}, {"SIGURG", SIGURG}, {"SIGUSR1", SIGUSR1}, {"SIGUSR2", SIGUSR2}, {"SIGVTALRM", SIGVTALRM}, {"SIGWINCH", SIGWINCH}, {"SIGXCPU", SIGXCPU}, {"SIGXFSZ", SIGXFSZ}, }; int signal_name_to_num(std::string &signal) { if (signal.empty()) { return -1; } std::string sig(signal); std::for_each(sig.begin(), sig.end(), [](char & c){ c = ::toupper(c); }); if (sig[0] != 'S') { sig.insert(0, "SIG"); } auto s = signals.find(sig); if (s != signals.end()) return s->second; return -1; } } // namespace bpftrace bpftrace-0.9.4/src/struct.cpp000066400000000000000000000005061361633214400161350ustar00rootroot00000000000000#include "struct.h" namespace bpftrace { bool Bitfield::operator==(const Bitfield &other) const { return read_bytes == other.read_bytes && mask == other.mask && access_rshift == other.access_rshift; } bool Bitfield::operator!=(const Bitfield &other) const { return !(*this == other); } } // namespace bpftrace bpftrace-0.9.4/src/struct.h000066400000000000000000000012441361633214400156020ustar00rootroot00000000000000#pragma once #include "types.h" #include namespace bpftrace { struct Bitfield { bool operator==(const Bitfield &other) const; bool operator!=(const Bitfield &other) const; // Read `read_bytes` bytes starting from this field's offset size_t read_bytes; // Then rshift the resulting value by `access_rshift` to get field value size_t access_rshift; // Then logical AND `mask` to mask out everything but this bitfield size_t mask; }; struct Field { SizedType type; int offset; bool is_bitfield; Bitfield bitfield; }; using FieldsMap = std::map; struct Struct { int size; FieldsMap fields; }; } // namespace bpftrace bpftrace-0.9.4/src/tracepoint_format_parser.cpp000066400000000000000000000147161361633214400217150ustar00rootroot00000000000000#include #include #include #include #include "ast.h" #include "struct.h" #include "tracepoint_format_parser.h" #include "bpftrace.h" namespace bpftrace { std::set TracepointFormatParser::struct_list; bool TracepointFormatParser::parse(ast::Program *program) { std::vector probes_with_tracepoint; for (ast::Probe *probe : *program->probes) for (ast::AttachPoint *ap : *probe->attach_points) if (ap->provider == "tracepoint") { probes_with_tracepoint.push_back(probe); continue; } if (probes_with_tracepoint.empty()) return true; ast::TracepointArgsVisitor n{}; program->c_definitions += "#include \n"; for (ast::Probe *probe : probes_with_tracepoint) { n.analyse(probe); if (!probe->need_tp_args_structs) continue; for (ast::AttachPoint *ap : *probe->attach_points) { if (ap->provider == "tracepoint") { std::string &category = ap->target; std::string &event_name = ap->func; std::string format_file_path = "/sys/kernel/debug/tracing/events/" + category + "/" + event_name + "/format"; glob_t glob_result; if (has_wildcard(event_name)) { // tracepoint wildcard expansion, part 1 of 3. struct definitions. memset(&glob_result, 0, sizeof(glob_result)); int ret = glob(format_file_path.c_str(), 0, NULL, &glob_result); if (ret != 0) { if (ret == GLOB_NOMATCH) { std::cerr << "ERROR: tracepoints not found: " << category << ":" << event_name << std::endl; // helper message: if (category == "syscall") std::cerr << "Did you mean syscalls:" << event_name << "?" << std::endl; if (bt_verbose) { std::cerr << strerror(errno) << ": " << format_file_path << std::endl; } return false; } else { // unexpected error std::cerr << strerror(errno) << std::endl; return false; } } for (size_t i = 0; i < glob_result.gl_pathc; ++i) { std::string filename(glob_result.gl_pathv[i]); std::ifstream format_file(filename); std::string prefix("/sys/kernel/debug/tracing/events/" + category + "/"); std::string real_event = filename.substr(prefix.length(), filename.length() - std::string("/format").length() - prefix.length()); // Check to avoid adding the same struct more than once to definitions std::string struct_name = get_struct_name(category, real_event); if (!TracepointFormatParser::struct_list.count(struct_name)) { program->c_definitions += get_tracepoint_struct(format_file, category, real_event); TracepointFormatParser::struct_list.insert(struct_name); } } globfree(&glob_result); } else { // single tracepoint std::ifstream format_file(format_file_path.c_str()); if (format_file.fail()) { std::cerr << "ERROR: tracepoint not found: " << category << ":" << event_name << std::endl; // helper message: if (category == "syscall") std::cerr << "Did you mean syscalls:" << event_name << "?" << std::endl; if (bt_verbose) { std::cerr << strerror(errno) << ": " << format_file_path << std::endl; } return false; } // Check to avoid adding the same struct more than once to definitions std::string struct_name = get_struct_name(category, event_name); if (TracepointFormatParser::struct_list.insert(struct_name).second) program->c_definitions += get_tracepoint_struct(format_file, category, event_name); } } } } return true; } std::string TracepointFormatParser::get_struct_name(const std::string &category, const std::string &event_name) { return "struct _tracepoint_" + category + "_" + event_name; } std::string TracepointFormatParser::parse_field(const std::string &line) { auto field_pos = line.find("field:"); if (field_pos == std::string::npos) return ""; auto field_semi_pos = line.find(';', field_pos); if (field_semi_pos == std::string::npos) return ""; auto offset_pos = line.find("offset:", field_semi_pos); if (offset_pos == std::string::npos) return ""; auto offset_semi_pos = line.find(';', offset_pos); if (offset_semi_pos == std::string::npos) return ""; auto size_pos = line.find("size:", offset_semi_pos); if (size_pos == std::string::npos) return ""; auto size_semi_pos = line.find(';', size_pos); if (size_semi_pos == std::string::npos) return ""; int size = std::stoi(line.substr(size_pos + 5, size_semi_pos - size_pos - 5)); std::string field = line.substr(field_pos + 6, field_semi_pos - field_pos - 6); auto field_type_end_pos = field.find_last_of("\t "); if (field_type_end_pos == std::string::npos) return ""; std::string field_type = field.substr(0, field_type_end_pos); std::string field_name = field.substr(field_type_end_pos+1); if (field_type.find("__data_loc") != std::string::npos) { field_type = "int"; field_name = "data_loc_" + field_name; } // Only adjust field types for non-arrays if (field_name.find("[") == std::string::npos) field_type = adjust_integer_types(field_type, size); return " " + field_type + " " + field_name + ";\n"; } std::string TracepointFormatParser::adjust_integer_types(const std::string &field_type, int size) { std::string new_type = field_type; // Adjust integer fields to correctly sized types if (size == 8) { if (field_type == "int") new_type = "s64"; if (field_type == "unsigned int" || field_type == "unsigned" || field_type == "u32" || field_type == "pid_t" || field_type == "uid_t" || field_type == "gid_t") new_type = "u64"; } return new_type; } std::string TracepointFormatParser::get_tracepoint_struct(std::istream &format_file, const std::string &category, const std::string &event_name) { std::string format_struct = get_struct_name(category, event_name) + "\n{\n"; for (std::string line; getline(format_file, line); ) { format_struct += parse_field(line); } format_struct += "};\n"; return format_struct; } } // namespace bpftrace bpftrace-0.9.4/src/tracepoint_format_parser.h000066400000000000000000000073101361633214400213520ustar00rootroot00000000000000#pragma once #include #include #include "ast/ast.h" namespace bpftrace { namespace ast { class TracepointArgsVisitor : public Visitor { public: ~TracepointArgsVisitor() override { } void visit(__attribute__((unused)) Integer &integer) override { }; // Leaf void visit(__attribute__((unused)) PositionalParameter &integer) override { }; // Leaf void visit(__attribute__((unused)) String &string) override { }; // Leaf void visit(__attribute__((unused)) StackMode &mode) override { }; // Leaf void visit(__attribute__((unused)) Identifier &identifier) override { }; // Leaf void visit(Builtin &builtin) override { // Leaf if (builtin.ident == "args") probe_->need_tp_args_structs = true; }; // Leaf void visit(Call &call) override { if (call.vargs) { for (Expression *expr : *call.vargs) { expr->accept(*this); } } }; void visit(Map &map) override { if (map.vargs) { for (Expression *expr : *map.vargs) { expr->accept(*this); } } }; void visit(__attribute__((unused)) Variable &var) override { }; // Leaf void visit(Binop &binop) override { binop.left->accept(*this); binop.right->accept(*this); }; void visit(Unop &unop) override { unop.expr->accept(*this); }; void visit(Ternary &ternary) override { ternary.cond->accept(*this); ternary.left->accept(*this); ternary.right->accept(*this); }; void visit(FieldAccess &acc) override { acc.expr->accept(*this); }; void visit(ArrayAccess &acc) override { acc.expr->accept(*this); }; void visit(Cast &cast) override { cast.expr->accept(*this); }; void visit(ExprStatement &expr) override { expr.expr->accept(*this); }; void visit(AssignMapStatement &assignment) override { assignment.map->accept(*this); assignment.expr->accept(*this); }; void visit(AssignVarStatement &assignment) override { assignment.expr->accept(*this); }; void visit(If &if_block) override { if_block.cond->accept(*this); for (Statement *stmt : *if_block.stmts) { stmt->accept(*this); } if (if_block.else_stmts) { for (Statement *stmt : *if_block.else_stmts) { stmt->accept(*this); } } }; void visit(Unroll &unroll) override { for (Statement *stmt : *unroll.stmts) { stmt->accept(*this); } }; void visit(Predicate &pred) override { pred.expr->accept(*this); }; void visit(__attribute__((unused)) AttachPoint &ap) override { }; // Leaf void visit(Probe &probe) override { probe_ = &probe; for (AttachPoint *ap : *probe.attach_points) { ap->accept(*this); } if (probe.pred) { probe.pred->accept(*this); } for (Statement *stmt : *probe.stmts) { stmt->accept(*this); } }; void visit(Program &program) override { for (Probe *probe : *program.probes) probe->accept(*this); }; void analyse(Probe *probe) { probe_ = probe; probe->accept(*this); } private: Probe *probe_; }; } // namespace ast class TracepointFormatParser { public: static bool parse(ast::Program *program); static std::string get_struct_name(const std::string &category, const std::string &event_name); private: static std::string parse_field(const std::string &line); static std::string adjust_integer_types(const std::string &field_type, int size); static std::set struct_list; protected: static std::string get_tracepoint_struct(std::istream &format_file, const std::string &category, const std::string &event_name); }; } // namespace bpftrace bpftrace-0.9.4/src/triggers.h000066400000000000000000000002311361633214400160770ustar00rootroot00000000000000#pragma once extern "C" { void __attribute__((noinline)) BEGIN_trigger() { asm (""); } void __attribute__((noinline)) END_trigger() { asm (""); } } bpftrace-0.9.4/src/types.cpp000066400000000000000000000100701361633214400157520ustar00rootroot00000000000000#include #include #include "types.h" namespace bpftrace { std::ostream &operator<<(std::ostream &os, Type type) { os << typestr(type); return os; } std::ostream &operator<<(std::ostream &os, const SizedType &type) { if (type.type == Type::cast) { os << type.cast_type; } else if (type.type == Type::integer) { os << (type.is_signed ? "" : "unsigned ") << "int" << 8*type.size; } else { os << type.type; } if (type.is_pointer) os << "*"; return os; } bool SizedType::IsEqual(const SizedType &t) const { return type == t.type && size == t.size && is_signed == t.is_signed; } bool SizedType::operator!=(const SizedType &t) const { return !IsEqual(t); } bool SizedType::operator==(const SizedType &t) const { return IsEqual(t); } bool SizedType::IsArray() const { return type == Type::array || type == Type::string || type == Type::usym || type == Type::inet || (type == Type::cast && !is_pointer); } bool SizedType::IsStack() const { return type == Type::ustack || type == Type::kstack; } std::string typestr(Type t) { switch (t) { case Type::none: return "none"; break; case Type::integer: return "integer"; break; case Type::hist: return "hist"; break; case Type::lhist: return "lhist"; break; case Type::count: return "count"; break; case Type::sum: return "sum"; break; case Type::min: return "min"; break; case Type::max: return "max"; break; case Type::avg: return "avg"; break; case Type::stats: return "stats"; break; case Type::kstack: return "kstack"; break; case Type::ustack: return "ustack"; break; case Type::string: return "string"; break; case Type::ksym: return "ksym"; break; case Type::usym: return "usym"; break; case Type::cast: return "cast"; break; case Type::join: return "join"; break; case Type::probe: return "probe"; break; case Type::username: return "username"; break; case Type::inet: return "inet"; break; case Type::stack_mode:return "stack mode";break; case Type::array: return "array"; break; default: std::cerr << "call or probe type not found" << std::endl; abort(); } } ProbeType probetype(const std::string &probeName) { ProbeType retType = ProbeType::invalid; auto v = std::find_if(PROBE_LIST.begin(), PROBE_LIST.end(), [&probeName] (const ProbeItem& p) { return (p.name == probeName || p.abbr == probeName); }); if (v != PROBE_LIST.end()) retType = v->type; return retType; } std::string probetypeName(const std::string &probeName) { std::string res = probeName; auto v = std::find_if(PROBE_LIST.begin(), PROBE_LIST.end(), [&probeName] (const ProbeItem& p) { return (p.name == probeName || p.abbr == probeName); }); if (v != PROBE_LIST.end()) res = v->name; return res; } std::string probetypeName(ProbeType t) { switch (t) { case ProbeType::invalid: return "invalid"; break; case ProbeType::kprobe: return "kprobe"; break; case ProbeType::kretprobe: return "kretprobe"; break; case ProbeType::uprobe: return "uprobe"; break; case ProbeType::uretprobe: return "uretprobe"; break; case ProbeType::usdt: return "usdt"; break; case ProbeType::tracepoint: return "tracepoint"; break; case ProbeType::profile: return "profile"; break; case ProbeType::interval: return "interval"; break; case ProbeType::software: return "software"; break; case ProbeType::hardware: return "hardware"; break; default: std::cerr << "probe type not found" << std::endl; abort(); } } uint64_t asyncactionint(AsyncAction a) { return (uint64_t)a; } } // namespace bpftrace bpftrace-0.9.4/src/types.h000066400000000000000000000111521361633214400154210ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include namespace bpftrace { const int MAX_STACK_SIZE = 1024; const int DEFAULT_STACK_SIZE = 127; const int STRING_SIZE = 64; const int COMM_SIZE = 16; enum class Type { none, integer, hist, lhist, count, sum, min, max, avg, stats, kstack, ustack, string, ksym, usym, cast, join, probe, username, inet, stack_mode, array, }; std::ostream &operator<<(std::ostream &os, Type type); enum class StackMode { bpftrace, perf, }; struct StackType { size_t limit = DEFAULT_STACK_SIZE; StackMode mode = StackMode::bpftrace; bool operator ==(const StackType &obj) const { return limit == obj.limit && mode == obj.mode; } }; struct SizedType { SizedType() : type(Type::none), size(0) { } SizedType(Type type, size_t size_, bool is_signed, const std::string &cast_type = "") : type(type), size(size_), is_signed(is_signed), cast_type(cast_type) { } SizedType(Type type, size_t size_, const std::string &cast_type = "") : type(type), size(size_), cast_type(cast_type) { } SizedType(Type type, StackType stack_type_) : SizedType(type, 8) { stack_type = stack_type_; } Type type; Type elem_type = Type::none; // Array element type if accessing elements of an // array size_t size; StackType stack_type; bool is_signed = false; std::string cast_type; bool is_internal = false; bool is_pointer = false; bool is_tparg = false; size_t pointee_size = 0; bool IsArray() const; bool IsStack() const; bool IsEqual(const SizedType &t) const; bool operator==(const SizedType &t) const; bool operator!=(const SizedType &t) const; }; std::ostream &operator<<(std::ostream &os, const SizedType &type); enum class ProbeType { invalid, kprobe, kretprobe, uprobe, uretprobe, usdt, tracepoint, profile, interval, software, hardware, watchpoint, }; struct ProbeItem { std::string name; std::string abbr; ProbeType type; }; const std::vector PROBE_LIST = { { "kprobe", "k", ProbeType::kprobe }, { "kretprobe", "kr", ProbeType::kretprobe }, { "uprobe", "u", ProbeType::uprobe }, { "uretprobe", "ur", ProbeType::uretprobe }, { "usdt", "U", ProbeType::usdt }, { "BEGIN", "BEGIN", ProbeType::uprobe }, { "END", "END", ProbeType::uprobe }, { "tracepoint", "t", ProbeType::tracepoint }, { "profile", "p", ProbeType::profile }, { "interval", "i", ProbeType::interval }, { "software", "s", ProbeType::software }, { "hardware", "h", ProbeType::hardware }, { "watchpoint", "w", ProbeType::watchpoint }, }; std::string typestr(Type t); ProbeType probetype(const std::string &type); std::string probetypeName(const std::string &type); std::string probetypeName(ProbeType t); struct Probe { ProbeType type; std::string path; // file path if used std::string attach_point; // probe name (last component) std::string orig_name; // original full probe name, // before wildcard expansion std::string name; // full probe name std::string ns; // for USDT probes, if provider namespace not from path uint64_t loc; // for USDT probes uint64_t log_size; int index = 0; int freq; pid_t pid = -1; uint64_t addr = 0; // for watchpoint probes, start of region uint64_t len = 0; // for watchpoint probes, size of region std::string mode; // for watchpoint probes, watch mode (rwx) uint64_t address = 0; uint64_t func_offset = 0; }; const int RESERVED_IDS_PER_ASYNCACTION = 10000; enum class AsyncAction { printf = 0, // printf reserves 0-9999 for printf_ids syscall = 10000, // system reserves 10000-19999 for printf_ids cat = 20000, // cat reserves 20000-29999 for printf_ids exit = 30000, print, clear, zero, time, join, }; uint64_t asyncactionint(AsyncAction a); enum class PositionalParameterType { positional, count }; } // namespace bpftrace namespace std { template <> struct hash { size_t operator()(const bpftrace::StackType &obj) const { switch (obj.mode) { case bpftrace::StackMode::bpftrace: return std::hash()("bpftrace#" + to_string(obj.limit)); case bpftrace::StackMode::perf: return std::hash()("perf#" + to_string(obj.limit)); // TODO (mmarchini): enable -Wswitch-enum and disable -Wswitch-default default: abort(); } } }; } // namespace std bpftrace-0.9.4/src/utils.cpp000066400000000000000000000471701361633214400157610ustar00rootroot00000000000000#include #include #include #include #include #include #include #include #include #include #include #include #include #include "bcc_elf.h" #include "bcc_usdt.h" #include "utils.h" namespace { std::vector read_cpu_range(std::string path) { std::ifstream cpus_range_stream { path }; std::vector cpus; std::string cpu_range; while (std::getline(cpus_range_stream, cpu_range, ',')) { std::size_t rangeop = cpu_range.find('-'); if (rangeop == std::string::npos) { cpus.push_back(std::stoi(cpu_range)); } else { int start = std::stoi(cpu_range.substr(0, rangeop)); int end = std::stoi(cpu_range.substr(rangeop + 1)); for (int i = start; i <= end; i++) cpus.push_back(i); } } return cpus; } std::vector expand_wildcard_path(const std::string& path) { glob_t glob_result; memset(&glob_result, 0, sizeof(glob_result)); if (glob(path.c_str(), GLOB_NOCHECK, nullptr, &glob_result)) { globfree(&glob_result); throw std::runtime_error("glob() failed"); } std::vector matching_paths; for (size_t i = 0; i < glob_result.gl_pathc; ++i) { matching_paths.push_back(std::string(glob_result.gl_pathv[i])); } globfree(&glob_result); return matching_paths; } std::vector expand_wildcard_paths(const std::vector& paths) { std::vector expanded_paths; for (const auto& p : paths) { auto ep = expand_wildcard_path(p); expanded_paths.insert(expanded_paths.end(), ep.begin(), ep.end()); } return expanded_paths; } } // namespace namespace bpftrace { //'borrowed' from libbpf's bpf_core_find_kernel_btf // from Andrii Nakryiko const struct vmlinux_location vmlinux_locs[] = { { "/sys/kernel/btf/vmlinux", true }, { "/boot/vmlinux-%1$s", false }, { "/lib/modules/%1$s/vmlinux-%1$s", false }, { "/lib/modules/%1$s/build/vmlinux", false }, { "/usr/lib/modules/%1$s/kernel/vmlinux", false }, { "/usr/lib/debug/boot/vmlinux-%1$s", false }, { "/usr/lib/debug/boot/vmlinux-%1$s.debug", false }, { "/usr/lib/debug/lib/modules/%1$s/vmlinux", false }, { nullptr, false }, }; static bool provider_cache_loaded = false; // Maps all providers of pid to vector of tracepoints on that provider static std::map usdt_provider_cache; static bool pid_in_different_mountns(int pid); static std::vector resolve_binary_path(const std::string &cmd, const char *env_paths, int pid); static void usdt_probe_each(struct bcc_usdt *usdt_probe) { usdt_provider_cache[usdt_probe->provider].push_back(std::make_tuple(usdt_probe->bin_path, usdt_probe->provider, usdt_probe->name)); } void StderrSilencer::silence() { fflush(stderr); old_stderr_ = dup(STDERR_FILENO); int new_stderr_ = open("/dev/null", O_WRONLY); dup2(new_stderr_, STDERR_FILENO); close(new_stderr_); } StderrSilencer::~StderrSilencer() { if (old_stderr_ != -1) { fflush(stderr); dup2(old_stderr_, STDERR_FILENO); close(old_stderr_); old_stderr_ = -1; } } usdt_probe_entry USDTHelper::find( int pid, const std::string &target, const std::string &provider, const std::string &name) { if (pid > 0) read_probes_for_pid(pid); else read_probes_for_path(target); usdt_probe_list probes = usdt_provider_cache[provider]; auto it = std::find_if(probes.begin(), probes.end(), [&name](const usdt_probe_entry& e) {return std::get(e) == name;}); if (it != probes.end()) { return *it; } else { return std::make_tuple("", "", ""); } } usdt_probe_list USDTHelper::probes_for_provider(const std::string &provider) { usdt_probe_list probes; if(!provider_cache_loaded) { std::cerr << "cannot read probes by provider before providers have been loaded by pid or path." << std::endl; return probes; } read_probes_for_pid(0); return usdt_provider_cache[provider]; } usdt_probe_list USDTHelper::probes_for_pid(int pid) { read_probes_for_pid(pid); usdt_probe_list probes; for (auto const& usdt_probes : usdt_provider_cache) { probes.insert( probes.end(), usdt_probes.second.begin(), usdt_probes.second.end() ); } return probes; } usdt_probe_list USDTHelper::probes_for_path(const std::string &path) { read_probes_for_path(path); usdt_probe_list probes; for (auto const& usdt_probes : usdt_provider_cache) { probes.insert( probes.end(), usdt_probes.second.begin(), usdt_probes.second.end() ); } return probes; } void USDTHelper::read_probes_for_pid(int pid) { if(provider_cache_loaded) return; if (pid > 0) { void *ctx = bcc_usdt_new_frompid(pid, nullptr); if (ctx == nullptr) { std::cerr << "failed to initialize usdt context for pid: " << pid << std::endl; if (kill(pid, 0) == -1 && errno == ESRCH) { std::cerr << "hint: process not running" << std::endl; } return; } bcc_usdt_foreach(ctx, usdt_probe_each); bcc_usdt_close(ctx); provider_cache_loaded = true; } else { std::cerr << "a pid must be specified to list USDT probes by PID" << std::endl; } } void USDTHelper::read_probes_for_path(const std::string &path) { if(provider_cache_loaded) return; void *ctx = bcc_usdt_new_frompath(path.c_str()); if (ctx == nullptr) { std::cerr << "failed to initialize usdt context for path " << path << std::endl; return; } bcc_usdt_foreach(ctx, usdt_probe_each); bcc_usdt_close(ctx); provider_cache_loaded = true; } bool get_uint64_env_var(const std::string &str, uint64_t &dest) { if (const char* env_p = std::getenv(str.c_str())) { std::istringstream stringstream(env_p); if (!(stringstream >> dest)) { std::cerr << "Env var '" << str << "' did not contain a valid uint64_t, or was zero-valued." << std::endl; return false; } } return true; } std::string get_pid_exe(pid_t pid) { char proc_path[512]; char exe_path[4096]; int res; sprintf(proc_path, "/proc/%d/exe", pid); res = readlink(proc_path, exe_path, sizeof(exe_path)); if (res == -1) return ""; if (res >= static_cast(sizeof(exe_path))) { throw std::runtime_error("executable path exceeded maximum supported size of 4096 characters"); } exe_path[res] = '\0'; return std::string(exe_path); } bool has_wildcard(const std::string &str) { return str.find("*") != std::string::npos || (str.find("[") != std::string::npos && str.find("]") != std::string::npos); } std::vector split_string(const std::string &str, char delimiter) { std::vector elems; std::stringstream ss(str); std::string value; while(std::getline(ss, value, delimiter)) { elems.push_back(value); } return elems; } bool wildcard_match(const std::string &str, std::vector &tokens, bool start_wildcard, bool end_wildcard) { size_t next = 0; if (!start_wildcard) if (str.find(tokens[0], next) != next) return false; for (std::string token : tokens) { size_t found = str.find(token, next); if (found == std::string::npos) return false; next = found + token.length(); } if (!end_wildcard) if (str.length() != next) return false; return true; } std::vector get_online_cpus() { return read_cpu_range("/sys/devices/system/cpu/online"); } std::vector get_possible_cpus() { return read_cpu_range("/sys/devices/system/cpu/possible"); } std::vector get_kernel_cflags( const char* uname_machine, const std::string& ksrc, const std::string& kobj) { std::vector cflags; std::string arch = uname_machine; const char *archenv; if (!strncmp(uname_machine, "x86_64", 6)) { arch = "x86"; } else if (uname_machine[0] == 'i' && !strncmp(&uname_machine[2], "86", 2)) { arch = "x86"; } else if (!strncmp(uname_machine, "arm", 3)) { arch = "arm"; } else if (!strncmp(uname_machine, "sa110", 5)) { arch = "arm"; } else if (!strncmp(uname_machine, "s390x", 5)) { arch = "s390"; } else if (!strncmp(uname_machine, "parisc64", 8)) { arch = "parisc"; } else if (!strncmp(uname_machine, "ppc", 3)) { arch = "powerpc"; } else if (!strncmp(uname_machine, "mips", 4)) { arch = "mips"; } else if (!strncmp(uname_machine, "sh", 2)) { arch = "sh"; } else if (!strncmp(uname_machine, "aarch64", 7)) { arch = "arm64"; } // If ARCH env is defined, use it over uname archenv = getenv("ARCH"); if (archenv) arch = std::string(archenv); cflags.push_back("-nostdinc"); cflags.push_back("-isystem"); cflags.push_back("/virtual/lib/clang/include"); // see linux/Makefile for $(LINUXINCLUDE) + $(USERINCLUDE) cflags.push_back("-I" + ksrc + "/arch/"+arch+"/include"); cflags.push_back("-I" + kobj + "/arch/"+arch+"/include/generated"); cflags.push_back("-I" + ksrc + "/include"); cflags.push_back("-I" + kobj + "/include"); cflags.push_back("-I" + ksrc + "/arch/"+arch+"/include/uapi"); cflags.push_back("-I" + kobj + "/arch/"+arch+"/include/generated/uapi"); cflags.push_back("-I" + ksrc + "/include/uapi"); cflags.push_back("-I" + kobj + "/include/generated/uapi"); cflags.push_back("-include"); cflags.push_back(ksrc + "/include/linux/kconfig.h"); cflags.push_back("-D__KERNEL__"); cflags.push_back("-D__BPF_TRACING__"); cflags.push_back("-D__HAVE_BUILTIN_BSWAP16__"); cflags.push_back("-D__HAVE_BUILTIN_BSWAP32__"); cflags.push_back("-D__HAVE_BUILTIN_BSWAP64__"); cflags.push_back("-DKBUILD_MODNAME='\"bpftrace\"'"); // If ARCH env variable is set, pass this along. if (archenv) cflags.push_back("-D__TARGET_ARCH_" + arch); return cflags; } bool is_dir(const std::string& path) { struct stat buf; if (::stat(path.c_str(), &buf) < 0) return false; return S_ISDIR(buf.st_mode); } namespace { struct KernelHeaderTmpDir { KernelHeaderTmpDir(const std::string& prefix) : path{prefix + "XXXXXX"} { if (::mkdtemp(&path[0]) == nullptr) { throw std::runtime_error("creating temporary path for kheaders.tar.xz failed"); } } ~KernelHeaderTmpDir() { if (path.size() > 0) { // move_to either did not succeed or did not run, so clean up after ourselves exec_system(("rm -rf " + path).c_str()); } } void move_to(const std::string& new_path) { int err = ::rename(path.c_str(), new_path.c_str()); if (err == 0) { path = ""; } } std::string path; }; std::string unpack_kheaders_tar_xz(const struct utsname& utsname) { std::string path_prefix{"/tmp"}; if (const char* tmpdir = ::getenv("TMPDIR")) { path_prefix = tmpdir; } path_prefix += "/kheaders-"; std::string shared_path{path_prefix + utsname.release}; struct stat stat_buf; if (::stat(shared_path.c_str(), &stat_buf) == 0) { // already unpacked return shared_path; } if (::stat("/sys/kernel/kheaders.tar.xz", &stat_buf) != 0) { FILE* modprobe = ::popen("modprobe kheaders", "w"); if (modprobe == nullptr || pclose(modprobe) != 0) { return ""; } if (::stat("/sys/kernel/kheaders.tar.xz", &stat_buf) != 0) { return ""; } } KernelHeaderTmpDir tmpdir{path_prefix}; FILE* tar = ::popen(("tar xf /sys/kernel/kheaders.tar.xz -C " + tmpdir.path).c_str(), "w"); if (!tar) { return ""; } int rc = ::pclose(tar); if (rc == 0) { tmpdir.move_to(shared_path); return shared_path; } return ""; } } // namespace // get_kernel_dirs returns {ksrc, kobj} - directories for pristine and // generated kernel sources. // // When the kernel was built in its source tree ksrc == kobj, however when // the kernel was build in a different directory than its source, ksrc != kobj. // // A notable example is Debian, which places pristine kernel headers in // // /lib/modules/`uname -r`/source/ // // and generated kernel headers in // // /lib/modules/`uname -r`/build/ // // {"", ""} is returned if no trace of kernel headers was found at all. // Both ksrc and kobj are guaranteed to be != "", if at least some trace of kernel sources was found. std::tuple get_kernel_dirs(const struct utsname& utsname) { #ifdef KERNEL_HEADERS_DIR return {KERNEL_HEADERS_DIR, KERNEL_HEADERS_DIR}; #endif const char *kpath_env = ::getenv("BPFTRACE_KERNEL_SOURCE"); if (kpath_env) return std::make_tuple(kpath_env, kpath_env); std::string kdir = std::string("/lib/modules/") + utsname.release; auto ksrc = kdir + "/source"; auto kobj = kdir + "/build"; // if one of source/ or build/ is not present - try to use the other one for both. if (!is_dir(ksrc)) { ksrc = ""; } if (!is_dir(kobj)) { kobj = ""; } if (ksrc == "" && kobj == "") { const auto kheaders_tar_xz_path = unpack_kheaders_tar_xz(utsname); if (kheaders_tar_xz_path.size() > 0) { return std::make_tuple(kheaders_tar_xz_path, kheaders_tar_xz_path); } return std::make_tuple("", ""); } if (ksrc == "") { ksrc = kobj; } else if (kobj == "") { kobj = ksrc; } return std::make_tuple(ksrc, kobj); } const std::string &is_deprecated(const std::string &str) { std::vector::iterator item; for (item = DEPRECATED_LIST.begin(); item != DEPRECATED_LIST.end(); item++) { if (str == item->old_name) { if (item->show_warning) { std::cerr << "warning: " << item->old_name << " is deprecated and will be removed in the future. "; std::cerr << "Use " << item->new_name << " instead." << std::endl; item->show_warning = false; } return item->new_name; } } return str; } bool is_unsafe_func(const std::string &func_name) { return std::any_of( UNSAFE_BUILTIN_FUNCS.begin(), UNSAFE_BUILTIN_FUNCS.end(), [&](const auto& cand) { return func_name == cand; }); } std::string exec_system(const char* cmd) { std::array buffer; std::string result; std::shared_ptr pipe(popen(cmd, "r"), pclose); if (!pipe) throw std::runtime_error("popen() failed!"); while (!feof(pipe.get())) { if (fgets(buffer.data(), 128, pipe.get()) != nullptr) result += buffer.data(); } return result; } /* Original resolve_binary_path API defaulting to bpftrace's mount namespace */ std::vector resolve_binary_path(const std::string& cmd) { const char *env_paths = getenv("PATH"); return resolve_binary_path(cmd, env_paths, -1); } /* If a pid is specified, the binary path is taken relative to its own PATH if it is in a different mount namespace. Otherwise, the path is resolved relative to the local PATH env var for bpftrace's own mount namespace if it is set */ std::vector resolve_binary_path(const std::string &cmd, int pid) { std::string env_paths = ""; std::ostringstream pid_environ_path; if (pid > 0 && pid_in_different_mountns(pid)) { pid_environ_path << "/proc/" << pid << "/environ"; std::ifstream environ(pid_environ_path.str()); if (environ) { std::string env_var; std::string pathstr = ("PATH="); while (std::getline(environ, env_var, '\0')) { if (env_var.find(pathstr) != std::string::npos) { env_paths = env_var.substr(pathstr.length()); break; } } } return resolve_binary_path(cmd, env_paths.c_str(), pid); } else { return resolve_binary_path(cmd, getenv("PATH"), pid); } } /* Private interface to resolve_binary_path, used for the exposed variants above, allowing for a PID whose mount namespace should be optionally considered. */ static std::vector resolve_binary_path(const std::string &cmd, const char *env_paths, int pid) { std::vector candidate_paths = { cmd }; if (env_paths != nullptr && cmd.find("/") == std::string::npos) for (const auto& path : split_string(env_paths, ':')) candidate_paths.push_back(path + "/" + cmd); if (cmd.find("*") != std::string::npos) candidate_paths = ::expand_wildcard_paths(candidate_paths); std::vector valid_executable_paths; for (const auto &path : candidate_paths) { std::string rel_path; if (pid > 0 && pid_in_different_mountns(pid)) rel_path = path_for_pid_mountns(pid, path); else rel_path = path; if (bcc_elf_is_exe(rel_path.c_str()) || bcc_elf_is_shared_obj(rel_path.c_str())) valid_executable_paths.push_back(rel_path); } return valid_executable_paths; } std::string path_for_pid_mountns(int pid, const std::string &path) { std::ostringstream pid_relative_path; std::string root = (path.length() >= 1 && path.at(0) == '/') ? "/root" : "/root/"; pid_relative_path << "/proc/" << pid << root << path; return pid_relative_path.str(); } /* Determines if the target process is in a different mount namespace from bpftrace. If a process is in a different mount namespace (eg, container) it is very likely that any references to local paths will not be valid, and that paths need to be made relative to the PID. If an invalid PID is specified or doesn't exist, it returns false. True is only returned if the namespace of the target process could be read and it doesn't match that of bpftrace. If there was an error reading either mount namespace, it will throw an exception */ static bool pid_in_different_mountns(int pid) { struct stat self_stat, target_stat; int self_fd = -1, target_fd = -1; std::stringstream errmsg; char buf[64]; if (pid <= 0) return false; if ((size_t)snprintf(buf, sizeof(buf), "/proc/%d/ns/mnt", pid) >= sizeof(buf)) { errmsg << "Reading mountNS would overflow buffer."; goto error; } self_fd = open("/proc/self/ns/mnt", O_RDONLY); if (self_fd < 0) { errmsg << "open(/proc/self/ns/mnt): " << strerror(errno); goto error; } target_fd = open(buf, O_RDONLY); if (target_fd < 0) { errmsg << "open(/proc//ns/mnt): " << strerror(errno); goto error; } if (fstat(self_fd, &self_stat)) { errmsg << "fstat(self_fd): " << strerror(errno); goto error; } if (fstat(target_fd, &target_stat)) { errmsg << "fstat(target_fd)" << strerror(errno); goto error; } close(self_fd); close(target_fd); return self_stat.st_ino != target_stat.st_ino; error: if (self_fd >= 0) close(self_fd); if (target_fd >= 0) close(target_fd); throw MountNSException("Failed to compare mount ns with PID " + std::to_string(pid) + ". " + "The error was " + errmsg.str()); return false; } void cat_file(const char *filename, size_t max_bytes, std::ostream &out) { std::ifstream file(filename); const size_t BUFSIZE = 4096; if (file.fail()){ std::cerr << "Error opening file '" << filename << "': "; std::cerr << strerror(errno) << std::endl; return; } char buf[BUFSIZE]; size_t bytes_read = 0; // Read the file batches to avoid allocating a potentially // massive buffer. while (bytes_read < max_bytes) { size_t size = std::min(BUFSIZE, max_bytes - bytes_read); file.read(buf, size); out.write(buf, file.gcount()); if (file.eof()) { return; } if (file.fail()) { std::cerr << "Error opening file '" << filename << "': "; std::cerr << strerror(errno) << std::endl; return; } bytes_read += file.gcount(); } } std::string str_join(const std::vector &list, const std::string &delim) { std::string str; bool first = true; for (const auto &elem : list) { if (first) first = false; else str += delim; str += elem; } return str; } bool is_numeric(const std::string &s) { return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit); } } // namespace bpftrace bpftrace-0.9.4/src/utils.h000066400000000000000000000102731361633214400154200ustar00rootroot00000000000000#pragma once #include #include #include #include #include #include #include #include namespace bpftrace { struct vmlinux_location { const char *path; // path with possible "%s" format to be replaced current // release bool raw; // file is either as ELF (false) or raw BTF data (true) }; extern const struct vmlinux_location vmlinux_locs[]; typedef enum _USDT_TUPLE_ORDER_ { USDT_PATH_INDEX, USDT_PROVIDER_INDEX, USDT_FNAME_INDEX } usdt_probe_entry_enum; typedef std::tuple usdt_probe_entry; typedef std::vector usdt_probe_list; class MountNSException : public std::exception { public: MountNSException(const std::string &msg) : msg_(msg) { } const char *what() const noexcept override { return msg_.c_str(); } private: std::string msg_; }; class StderrSilencer { public: StderrSilencer() = default; ~StderrSilencer(); void silence(); private: int old_stderr_ = -1; }; class USDTHelper { public: static usdt_probe_entry find(int pid, const std::string &target, const std::string &provider, const std::string &name); static usdt_probe_list probes_for_provider(const std::string &provider); static usdt_probe_list probes_for_pid(int pid); static usdt_probe_list probes_for_path(const std::string &path); static void read_probes_for_pid(int pid); static void read_probes_for_path(const std::string &path); }; // Hack used to suppress build warning related to #474 template new_signature cast_signature(old_signature func) { #if __GNUC__ >= 8 _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wcast-function-type\"") #endif return reinterpret_cast(func); #if __GNUC__ >= 8 _Pragma("GCC diagnostic pop") #endif } struct DeprecatedName { std::string old_name; std::string new_name; bool show_warning = true; }; static std::vector DEPRECATED_LIST = { { "stack", "kstack"}, { "sym", "ksym"}, }; static std::vector UNSAFE_BUILTIN_FUNCS = { "system", "signal", "override", }; bool get_uint64_env_var(const ::std::string &str, uint64_t &dest); std::string get_pid_exe(pid_t pid); bool has_wildcard(const std::string &str); std::vector split_string(const std::string &str, char delimiter); bool wildcard_match(const std::string &str, std::vector &tokens, bool start_wildcard, bool end_wildcard); std::vector get_online_cpus(); std::vector get_possible_cpus(); bool is_dir(const std::string &path); std::tuple get_kernel_dirs( const struct utsname &utsname); std::vector get_kernel_cflags(const char *uname_machine, const std::string &ksrc, const std::string &kobj); const std::string &is_deprecated(const std::string &str); bool is_unsafe_func(const std::string &func_name); std::string exec_system(const char *cmd); std::vector resolve_binary_path(const std::string &cmd); std::vector resolve_binary_path(const std::string &cmd, int pid); std::string path_for_pid_mountns(int pid, const std::string &path); void cat_file(const char *filename, size_t, std::ostream &); std::string str_join(const std::vector &list, const std::string &delim); bool is_numeric(const std::string &str); // trim from end of string (right) inline std::string &rtrim(std::string &s) { s.erase(s.find_last_not_of(" ") + 1); return s; } // trim from beginning of string (left) inline std::string <rim(std::string &s) { s.erase(0, s.find_first_not_of(" ")); return s; } // trim from both ends of string (right then left) inline std::string &trim(std::string &s) { return ltrim(rtrim(s)); } int signal_name_to_num(std::string &signal); template T read_data(const void *src) { T v; std::memcpy(&v, src, sizeof(v)); return v; } } // namespace bpftrace bpftrace-0.9.4/tests/000077500000000000000000000000001361633214400144575ustar00rootroot00000000000000bpftrace-0.9.4/tests/CMakeLists.txt000066400000000000000000000211301361633214400172140ustar00rootroot00000000000000add_compile_options("-Wno-undef") add_compile_options("-Wno-switch-default") add_compile_options("-Wno-switch-enum") # Combine all codegen tests into a single compilation unit to improve build # performance. https://github.com/iovisor/bpftrace/issues/229 function(generate_codegen_includes output) file(GLOB tests codegen/*.cpp) file(WRITE ${output} "") foreach(test ${tests}) file(APPEND ${output} "#include \"${test}\"\n") endforeach() endfunction() if(HAVE_BFD_DISASM) set(BFD_DISASM_SRC ${CMAKE_SOURCE_DIR}/src/bfd-disasm.cpp) endif() generate_codegen_includes(${CMAKE_BINARY_DIR}/tests/codegen_includes.cpp) add_executable(bpftrace_test ast.cpp bpftrace.cpp clang_parser.cpp main.cpp mocks.cpp parser.cpp probe.cpp semantic_analyser.cpp tracepoint_format_parser.cpp utils.cpp ${CMAKE_BINARY_DIR}/tests/codegen_includes.cpp ${CMAKE_SOURCE_DIR}/src/attached_probe.cpp ${CMAKE_SOURCE_DIR}/src/bpftrace.cpp ${CMAKE_SOURCE_DIR}/src/bpffeature.cpp ${CMAKE_SOURCE_DIR}/src/btf.cpp ${CMAKE_SOURCE_DIR}/src/clang_parser.cpp ${CMAKE_SOURCE_DIR}/src/disasm.cpp ${CMAKE_SOURCE_DIR}/src/driver.cpp ${CMAKE_SOURCE_DIR}/src/fake_map.cpp ${CMAKE_SOURCE_DIR}/src/map.cpp ${CMAKE_SOURCE_DIR}/src/mapkey.cpp ${CMAKE_SOURCE_DIR}/src/output.cpp ${CMAKE_SOURCE_DIR}/src/printf.cpp ${CMAKE_SOURCE_DIR}/src/resolve_cgroupid.cpp ${CMAKE_SOURCE_DIR}/src/signal.cpp ${CMAKE_SOURCE_DIR}/src/struct.cpp ${CMAKE_SOURCE_DIR}/src/tracepoint_format_parser.cpp ${CMAKE_SOURCE_DIR}/src/types.cpp ${CMAKE_SOURCE_DIR}/src/utils.cpp ${BFD_DISASM_SRC} ) if(HAVE_NAME_TO_HANDLE_AT) target_compile_definitions(bpftrace_test PRIVATE HAVE_NAME_TO_HANDLE_AT=1) endif(HAVE_NAME_TO_HANDLE_AT) if(HAVE_BCC_PROG_LOAD) target_compile_definitions(bpftrace_test PRIVATE HAVE_BCC_PROG_LOAD) endif(HAVE_BCC_PROG_LOAD) if(HAVE_BCC_CREATE_MAP) target_compile_definitions(bpftrace_test PRIVATE HAVE_BCC_CREATE_MAP) endif(HAVE_BCC_CREATE_MAP) if (LIBBPF_BTF_DUMP_FOUND) target_compile_definitions(bpftrace_test PRIVATE HAVE_LIBBPF_BTF_DUMP) target_include_directories(bpftrace_test PUBLIC ${LIBBPF_INCLUDE_DIRS}) target_link_libraries(bpftrace_test ${LIBBPF_LIBRARIES}) endif(LIBBPF_BTF_DUMP_FOUND) if(HAVE_BFD_DISASM) target_compile_definitions(bpftrace_test PRIVATE HAVE_BFD_DISASM) if(LIBBFD_DISASM_FOUR_ARGS_SIGNATURE) target_compile_definitions(bpftrace_test PRIVATE LIBBFD_DISASM_FOUR_ARGS_SIGNATURE) endif(LIBBFD_DISASM_FOUR_ARGS_SIGNATURE) if(STATIC_LINKING) add_library(LIBBFD STATIC IMPORTED) set_property(TARGET LIBBFD PROPERTY IMPORTED_LOCATION ${LIBBFD_LIBRARIES}) target_link_libraries(bpftrace_test LIBBFD) add_library(LIBOPCODES STATIC IMPORTED) set_property(TARGET LIBOPCODES PROPERTY IMPORTED_LOCATION ${LIBOPCODES_LIBRARIES}) target_link_libraries(bpftrace_test LIBOPCODES) add_library(LIBIBERTY STATIC IMPORTED) set_property(TARGET LIBIBERTY PROPERTY IMPORTED_LOCATION ${LIBIBERTY_LIBRARIES}) target_link_libraries(bpftrace_test LIBIBERTY) else() target_link_libraries(bpftrace_test ${LIBBFD_LIBRARIES}) target_link_libraries(bpftrace_test ${LIBOPCODES_LIBRARIES}) endif(STATIC_LINKING) endif(HAVE_BFD_DISASM) target_link_libraries(bpftrace_test arch ast parser resources) if (STATIC_LINKING) if(EMBED_LLVM OR EMBED_CLANG) set_target_properties(bpftrace_test PROPERTIES LINK_FLAGS "${EMBEDDED_LINK_FLAGS}") endif() target_link_libraries(bpftrace_test ${LIBBCC_LIBRARIES}) target_link_libraries(bpftrace_test ${LIBBPF_LIBRARY_STATIC}) target_link_libraries(bpftrace_test ${LIBBCC_LOADER_LIBRARY_STATIC}) add_library(LIBELF STATIC IMPORTED) set_property(TARGET LIBELF PROPERTY IMPORTED_LOCATION ${LIBELF_LIBRARIES}) target_link_libraries(bpftrace_test LIBELF) else() target_link_libraries(bpftrace_test ${LIBBCC_LIBRARIES}) target_link_libraries(bpftrace_test ${LIBELF_LIBRARIES}) endif(STATIC_LINKING) find_package(Threads REQUIRED) # Ninja build system needs the byproducts set explicilty so it can # check for missing dependencies. # https://cmake.org/pipermail/cmake/2015-April/060234.html set(gtest_byproducts /googlemock/gtest/libgtest.a /googlemock/gtest/libgtest_main.a /googlemock/libgmock.a ) include(ExternalProject) if (OFFLINE_BUILDS) ExternalProject_Add(gtest-git GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.8.1 STEP_TARGETS build update EXCLUDE_FROM_ALL 1 UPDATE_DISCONNECTED 1 BUILD_BYPRODUCTS ${gtest_byproducts} ) else() ExternalProject_Add(gtest-git GIT_REPOSITORY https://github.com/google/googletest.git GIT_TAG release-1.8.1 STEP_TARGETS build update EXCLUDE_FROM_ALL 1 BUILD_BYPRODUCTS ${gtest_byproducts} ) endif() add_dependencies(bpftrace_test gtest-git-build) ExternalProject_Get_Property(gtest-git source_dir binary_dir) target_include_directories(bpftrace_test PUBLIC ${source_dir}/googletest/include) target_include_directories(bpftrace_test PUBLIC ${source_dir}/googlemock/include) target_link_libraries(bpftrace_test ${binary_dir}/googlemock/gtest/libgtest.a) target_link_libraries(bpftrace_test ${binary_dir}/googlemock/gtest/libgtest_main.a) target_link_libraries(bpftrace_test ${binary_dir}/googlemock/libgmock.a) if(NOT STATIC_LINKING) target_link_libraries(bpftrace_test ${CMAKE_THREAD_LIBS_INIT}) endif(NOT STATIC_LINKING) add_test(NAME bpftrace_test COMMAND bpftrace_test) # Compile all testprograms, one per .c file for runtime testing file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/testprogs/) file(GLOB testprogs testprogs/*.c) set(compiled_testprogs "") foreach(testprog ${testprogs}) get_filename_component(bin_name ${testprog} NAME_WE) add_executable (${bin_name} ${testprog}) if(HAVE_SYSTEMTAP_SYS_SDT_H) target_compile_definitions(${bin_name} PRIVATE HAVE_SYSTEMTAP_SYS_SDT_H) endif(HAVE_SYSTEMTAP_SYS_SDT_H) set_target_properties( ${bin_name} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/testprogs/ COMPILE_FLAGS "-g -O0" LINK_FLAGS "-no-pie") list(APPEND compiled_testprogs ${CMAKE_CURRENT_BINARY_DIR}/testprogs/${bin_name}) endforeach() add_custom_target(testprogs DEPENDS ${compiled_testprogs}) # Similarly compile all test libs file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/testlibs/) file(GLOB testlibs testlibs/*.c) set(compiled_testlibs "") foreach(testlib_source ${testlibs}) get_filename_component(testlib_name ${testlib_source} NAME_WE) add_library(${testlib_name} SHARED ${testlib_source}) set_target_properties(${testlib_name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/testlibs/ COMPILE_FLAGS "-g -O0") # clear the executable bit add_custom_command(TARGET ${testlib_name} POST_BUILD COMMAND chmod -x ${CMAKE_CURRENT_BINARY_DIR}/testlibs/lib${testlib_name}.so) list(APPEND compiled_testlibs ${CMAKE_CURRENT_BINARY_DIR}/testlibs/lib${testlib_name}.so) endforeach() add_custom_target(testlibs DEPENDS ${compiled_testlibs}) configure_file(runtime-tests.sh runtime-tests.sh COPYONLY) file(GLOB runtime_tests runtime/*) list(REMOVE_ITEM runtime_tests ${CMAKE_CURRENT_SOURCE_DIR}/runtime/engine) list(REMOVE_ITEM runtime_tests ${CMAKE_CURRENT_SOURCE_DIR}/runtime/scripts) list(REMOVE_ITEM runtime_tests ${CMAKE_CURRENT_SOURCE_DIR}/runtime/outputs) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/runtime/) foreach(runtime_test ${runtime_tests}) configure_file(${runtime_test} ${CMAKE_CURRENT_BINARY_DIR}/runtime/ COPYONLY) endforeach() file(GLOB runtime_engine_files runtime/engine/*) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/runtime/engine) foreach(runtime_engine_file ${runtime_engine_files}) configure_file(${runtime_engine_file} ${CMAKE_CURRENT_BINARY_DIR}/runtime/engine/ COPYONLY) endforeach() file(GLOB runtime_test_scripts runtime/scripts/*) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/runtime/scripts) foreach(runtime_test_script ${runtime_test_scripts}) configure_file(${runtime_test_script} ${CMAKE_CURRENT_BINARY_DIR}/runtime/scripts/ COPYONLY) endforeach() file(GLOB runtime_test_outputs runtime/outputs/*) file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/runtime/outputs) foreach(runtime_test_output ${runtime_test_outputs}) configure_file(${runtime_test_output} ${CMAKE_CURRENT_BINARY_DIR}/runtime/outputs/ COPYONLY) endforeach() add_custom_target( runtime-tests COMMAND ./runtime-tests.sh DEPENDS ${compiled_testprogs} ${compiled_testlibs} ${CMAKE_BINARY_DIR}/src/bpftrace ) add_test(NAME runtime_test COMMAND ./runtime-tests.sh) configure_file(tools-parsing-test.sh tools-parsing-test.sh COPYONLY) add_custom_target(tools-parsing-test COMMAND ./tools-parsing-test.sh) add_test(NAME tools-parsing-test COMMAND ./tools-parsing-test.sh) bpftrace-0.9.4/tests/README.md000066400000000000000000000023571361633214400157450ustar00rootroot00000000000000# bpftrace Tests There are two test suites in the project. ## Unit tests These tests can be run with the `bpftrace_test` executable. The code generation tests are based on the output of LLVM 5, so may give errors if run with different version. They can be excluded by running: `bpftrace_test --gtest_filter=-codegen*` ## Runtime tests Runtime tests will call the bpftrace executable. * Run: `sudo make runtime-tests` inside your build folder * By default, runtime-tests will look for the executable in the build folder. You can set a value to the environment variable `BPFTRACE_RUNTIME_TEST_EXECUTABLE` to customize it ### Test programs You can add test programs for your runtime tests by placing a `.c` file corresponding to your test program in `tests/testprogs`. You can add test libraries for your runtime tests by placing a `.c` file corresponding to your test library in `tests/testlibs`. The test file `tests/testprogs/my_test.c` will result in an executable that you can call and probe in your runtime test at `./testprogs/my_test` This is intended to be useful for testing uprobes and USDT probes, or using uprobes to verify some other behavior in bpftrace. It can also be used to tightly control what code paths are triggered in the system. bpftrace-0.9.4/tests/ast.cpp000066400000000000000000000074461361633214400157650ustar00rootroot00000000000000#include "gtest/gtest.h" #include "ast.h" namespace bpftrace { namespace test { namespace ast { using bpftrace::ast::AttachPoint; using bpftrace::ast::AttachPointList; using bpftrace::ast::Probe; TEST(ast, probe_name_special) { AttachPoint ap1("BEGIN"); AttachPointList attach_points1 = { &ap1 }; Probe begin(&attach_points1, nullptr, nullptr); EXPECT_EQ(begin.name(), "BEGIN"); AttachPoint ap2("END"); AttachPointList attach_points2 = { &ap2 }; Probe end(&attach_points2, nullptr, nullptr); EXPECT_EQ(end.name(), "END"); } TEST(ast, probe_name_kprobe) { AttachPoint ap1("kprobe", "sys_read"); AttachPointList attach_points1 = { &ap1 }; Probe kprobe1(&attach_points1, nullptr, nullptr); EXPECT_EQ(kprobe1.name(), "kprobe:sys_read"); AttachPoint ap2("kprobe", "sys_write"); AttachPointList attach_points2 = { &ap1, &ap2 }; Probe kprobe2(&attach_points2, nullptr, nullptr); EXPECT_EQ(kprobe2.name(), "kprobe:sys_read,kprobe:sys_write"); AttachPoint ap3("kprobe", "sys_read", (uint64_t)10); AttachPointList attach_points3 = { &ap1, &ap2, &ap3 }; Probe kprobe3(&attach_points3, nullptr, nullptr); EXPECT_EQ(kprobe3.name(), "kprobe:sys_read,kprobe:sys_write,kprobe:sys_read+10"); } TEST(ast, probe_name_uprobe) { AttachPoint ap1("uprobe", "/bin/sh", "readline", true); AttachPointList attach_points1 = { &ap1 }; Probe uprobe1(&attach_points1, nullptr, nullptr); EXPECT_EQ(uprobe1.name(), "uprobe:/bin/sh:readline"); AttachPoint ap2("uprobe", "/bin/sh", "somefunc", true); AttachPointList attach_points2 = { &ap1, &ap2 }; Probe uprobe2(&attach_points2, nullptr, nullptr); EXPECT_EQ(uprobe2.name(), "uprobe:/bin/sh:readline,uprobe:/bin/sh:somefunc"); AttachPoint ap3("uprobe", "/bin/sh", 1000); AttachPointList attach_points3 = { &ap1, &ap2, &ap3 }; Probe uprobe3(&attach_points3, nullptr, nullptr); EXPECT_EQ(uprobe3.name(), "uprobe:/bin/sh:readline,uprobe:/bin/sh:somefunc,uprobe:/bin/sh:1000"); AttachPoint ap4("uprobe", "/bin/sh", "somefunc", (uint64_t) 10); AttachPointList attach_points4 = { &ap1, &ap2, &ap3, &ap4 }; Probe uprobe4(&attach_points4, nullptr, nullptr); EXPECT_EQ(uprobe4.name(), "uprobe:/bin/sh:readline,uprobe:/bin/sh:somefunc,uprobe:/bin/sh:1000,uprobe:/bin/sh:somefunc+10"); AttachPoint ap5("u", "/bin/sh", (uint64_t) 10); AttachPointList attach_points5 = { &ap5 }; Probe uprobe5(&attach_points5, nullptr, nullptr); EXPECT_EQ(uprobe5.name(), "uprobe:/bin/sh:10"); AttachPoint ap6("ur", "/bin/sh", (uint64_t) 10); AttachPointList attach_points6 = { &ap6 }; Probe uprobe6(&attach_points6, nullptr, nullptr); EXPECT_EQ(uprobe6.name(), "uretprobe:/bin/sh:10"); AttachPoint ap7("uretprobe", "/bin/sh", (uint64_t)1000); AttachPointList attach_points7 = { &ap7 }; Probe uprobe7(&attach_points7, nullptr, nullptr); EXPECT_EQ(uprobe7.name(), "uretprobe:/bin/sh:1000"); } TEST(ast, probe_name_usdt) { AttachPoint ap1("usdt", "/bin/sh", "probe1", true); AttachPointList attach_points1 = { &ap1 }; Probe usdt1(&attach_points1, nullptr, nullptr); EXPECT_EQ(usdt1.name(), "usdt:/bin/sh:probe1"); AttachPoint ap2("usdt", "/bin/sh", "probe2", true); AttachPointList attach_points2 = { &ap1, &ap2 }; Probe usdt2(&attach_points2, nullptr, nullptr); EXPECT_EQ(usdt2.name(), "usdt:/bin/sh:probe1,usdt:/bin/sh:probe2"); } TEST(ast, attach_point_name) { AttachPoint ap1("kprobe", "sys_read"); AttachPoint ap2("kprobe", "sys_thisone"); AttachPoint ap3("uprobe", "/bin/sh", "readline", true); AttachPointList attach_points = { &ap1, &ap2, &ap3 }; Probe kprobe(&attach_points, nullptr, nullptr); EXPECT_EQ(ap2.name("sys_thisone"), "kprobe:sys_thisone"); Probe uprobe(&attach_points, nullptr, nullptr); EXPECT_EQ(ap3.name("readline"), "uprobe:/bin/sh:readline"); } } // namespace ast } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/bpftrace.cpp000066400000000000000000000563221361633214400167610ustar00rootroot00000000000000#include #include "gmock/gmock.h" #include "gtest/gtest.h" #include "bpftrace.h" #include "mocks.h" namespace bpftrace { namespace test { namespace bpftrace { using ::testing::ContainerEq; using ::testing::StrictMock; static const std::string kprobe_name(const std::string &attach_point, uint64_t func_offset) { auto str = func_offset ? "+" + std::to_string(func_offset) : ""; return "kprobe:" + attach_point + str; } void check_kprobe(Probe &p, const std::string &attach_point, const std::string &orig_name, uint64_t func_offset = 0) { EXPECT_EQ(ProbeType::kprobe, p.type); EXPECT_EQ(attach_point, p.attach_point); EXPECT_EQ(orig_name, p.orig_name); EXPECT_EQ(kprobe_name(attach_point, func_offset), p.name); EXPECT_EQ(func_offset, p.func_offset); } static const std::string uprobe_name(const std::string &path, const std::string &attach_point, uint64_t address, uint64_t func_offset) { if (attach_point.empty()) { return "uprobe:" + path + ":" + std::to_string(address); } else { auto str = func_offset ? "+" + std::to_string(func_offset) : ""; return "uprobe:" + path + ":" + attach_point + str; } } void check_uprobe(Probe &p, const std::string &path, const std::string &attach_point, const std::string &orig_name, uint64_t address = 0, uint64_t func_offset = 0) { EXPECT_EQ(ProbeType::uprobe, p.type); EXPECT_EQ(attach_point, p.attach_point); EXPECT_EQ(orig_name, p.orig_name); EXPECT_EQ(uprobe_name(path, attach_point, address, func_offset), p.name); EXPECT_EQ(address, p.address); EXPECT_EQ(func_offset, p.func_offset); } void check_usdt(Probe &p, const std::string &path, const std::string &provider, const std::string &attach_point, const std::string &orig_name) { EXPECT_EQ(ProbeType::usdt, p.type); EXPECT_EQ(attach_point, p.attach_point); EXPECT_EQ(orig_name, p.orig_name); EXPECT_EQ("usdt:" + path + ":" + provider + ":" + attach_point, p.name); } void check_tracepoint(Probe &p, const std::string &target, const std::string &func, const std::string &orig_name) { EXPECT_EQ(ProbeType::tracepoint, p.type); EXPECT_EQ(func, p.attach_point); EXPECT_EQ(orig_name, p.orig_name); EXPECT_EQ("tracepoint:" + target + ":" + func, p.name); } void check_profile(Probe &p, const std::string &unit, int freq, const std::string &orig_name) { EXPECT_EQ(ProbeType::profile, p.type); EXPECT_EQ(freq, p.freq); EXPECT_EQ(orig_name, p.orig_name); EXPECT_EQ("profile:" + unit + ":" + std::to_string(freq), p.name); } void check_interval(Probe &p, const std::string &unit, int freq, const std::string &orig_name) { EXPECT_EQ(ProbeType::interval, p.type); EXPECT_EQ(freq, p.freq); EXPECT_EQ(orig_name, p.orig_name); EXPECT_EQ("interval:" + unit + ":" + std::to_string(freq), p.name); } void check_software(Probe &p, const std::string &unit, int freq, const std::string &orig_name) { EXPECT_EQ(ProbeType::software, p.type); EXPECT_EQ(freq, p.freq); EXPECT_EQ(orig_name, p.orig_name); EXPECT_EQ("software:" + unit + ":" + std::to_string(freq), p.name); } void check_hardware(Probe &p, const std::string &unit, int freq, const std::string &orig_name) { EXPECT_EQ(ProbeType::hardware, p.type); EXPECT_EQ(freq, p.freq); EXPECT_EQ(orig_name, p.orig_name); EXPECT_EQ("hardware:" + unit + ":" + std::to_string(freq), p.name); } void check_special_probe(Probe &p, const std::string &attach_point, const std::string &orig_name) { EXPECT_EQ(ProbeType::uprobe, p.type); EXPECT_EQ(attach_point, p.attach_point); EXPECT_EQ(orig_name, p.orig_name); EXPECT_EQ(orig_name, p.name); } TEST(bpftrace, add_begin_probe) { ast::AttachPoint a("BEGIN"); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(0U, bpftrace.get_probes().size()); ASSERT_EQ(1U, bpftrace.get_special_probes().size()); check_special_probe(bpftrace.get_special_probes().at(0), "BEGIN_trigger", "BEGIN"); } TEST(bpftrace, add_end_probe) { ast::AttachPoint a("END"); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(0U, bpftrace.get_probes().size()); ASSERT_EQ(1U, bpftrace.get_special_probes().size()); check_special_probe(bpftrace.get_special_probes().at(0), "END_trigger", "END"); } TEST(bpftrace, add_probes_single) { ast::AttachPoint a("kprobe", "sys_read"); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); check_kprobe(bpftrace.get_probes().at(0), "sys_read", "kprobe:sys_read"); } TEST(bpftrace, add_probes_multiple) { ast::AttachPoint a1("kprobe", "sys_read"); ast::AttachPoint a2("kprobe", "sys_write"); ast::AttachPointList attach_points = { &a1, &a2 }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(2U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); std::string probe_orig_name = "kprobe:sys_read,kprobe:sys_write"; check_kprobe(bpftrace.get_probes().at(0), "sys_read", probe_orig_name); check_kprobe(bpftrace.get_probes().at(1), "sys_write", probe_orig_name); } TEST(bpftrace, add_probes_wildcard) { ast::AttachPoint a1("kprobe", "sys_read"); ast::AttachPoint a2("kprobe", "my_*"); ast::AttachPoint a3("kprobe", "sys_write"); ast::AttachPointList attach_points = { &a1, &a2, &a3 }; ast::Probe probe(&attach_points, nullptr, nullptr); auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace, get_symbols_from_file( "/sys/kernel/debug/tracing/available_filter_functions")) .Times(1); ASSERT_EQ(0, bpftrace->add_probe(probe)); ASSERT_EQ(4U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); std::string probe_orig_name = "kprobe:sys_read,kprobe:my_*,kprobe:sys_write"; check_kprobe(bpftrace->get_probes().at(0), "sys_read", probe_orig_name); check_kprobe(bpftrace->get_probes().at(1), "my_one", probe_orig_name); check_kprobe(bpftrace->get_probes().at(2), "my_two", probe_orig_name); check_kprobe(bpftrace->get_probes().at(3), "sys_write", probe_orig_name); } TEST(bpftrace, add_probes_wildcard_no_matches) { ast::AttachPoint a1("kprobe", "sys_read"); ast::AttachPoint a2("kprobe", "not_here_*"); ast::AttachPoint a3("kprobe", "sys_write"); ast::AttachPointList attach_points = { &a1, &a2, &a3 }; ast::Probe probe(&attach_points, nullptr, nullptr); auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace, get_symbols_from_file( "/sys/kernel/debug/tracing/available_filter_functions")) .Times(1); ASSERT_EQ(0, bpftrace->add_probe(probe)); ASSERT_EQ(2U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); std::string probe_orig_name = "kprobe:sys_read,kprobe:not_here_*,kprobe:sys_write"; check_kprobe(bpftrace->get_probes().at(0), "sys_read", probe_orig_name); check_kprobe(bpftrace->get_probes().at(1), "sys_write", probe_orig_name); } TEST(bpftrace, add_probes_offset) { uint64_t offset = 10; ast::AttachPoint a("kprobe", "sys_read", offset); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); std::string probe_orig_name = "kprobe:sys_read+" + std::to_string(offset); check_kprobe( bpftrace.get_probes().at(0), "sys_read", probe_orig_name, offset); } TEST(bpftrace, add_probes_uprobe) { ast::AttachPoint a("uprobe", "/bin/sh", "foo", true); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); check_uprobe(bpftrace.get_probes().at(0), "/bin/sh", "foo", "uprobe:/bin/sh:foo"); } TEST(bpftrace, add_probes_uprobe_wildcard) { ast::AttachPoint a("uprobe", "/bin/sh", "*open", true); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace, extract_func_symbols_from_path("/bin/sh")).Times(1); ASSERT_EQ(0, bpftrace->add_probe(probe)); ASSERT_EQ(2U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); std::string probe_orig_name = "uprobe:/bin/sh:*open"; check_uprobe(bpftrace->get_probes().at(0), "/bin/sh", "first_open", probe_orig_name); check_uprobe(bpftrace->get_probes().at(1), "/bin/sh", "second_open", probe_orig_name); } TEST(bpftrace, add_probes_uprobe_wildcard_no_matches) { ast::AttachPoint a("uprobe", "/bin/sh", "foo*", true); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace, extract_func_symbols_from_path("/bin/sh")).Times(1); ASSERT_EQ(0, bpftrace->add_probe(probe)); ASSERT_EQ(0U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); } TEST(bpftrace, add_probes_uprobe_string_literal) { ast::AttachPoint a("uprobe", "/bin/sh", "foo*", false); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); check_uprobe(bpftrace.get_probes().at(0), "/bin/sh", "foo*", "uprobe:/bin/sh:foo*"); } TEST(bpftrace, add_probes_uprobe_address) { ast::AttachPoint a("uprobe", "/bin/sh", 1024); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); check_uprobe(bpftrace.get_probes().at(0), "/bin/sh", "", "uprobe:/bin/sh:1024", 1024); } TEST(bpftrace, add_probes_uprobe_string_offset) { ast::AttachPoint a("uprobe", "/bin/sh", "foo", (uint64_t) 10); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); check_uprobe(bpftrace.get_probes().at(0), "/bin/sh", "foo", "uprobe:/bin/sh:foo+10", 0, 10); } TEST(bpftrace, add_probes_usdt) { ast::AttachPoint a("usdt", "/bin/sh", "prov1", "mytp", false); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); check_usdt(bpftrace.get_probes().at(0), "/bin/sh", "prov1", "mytp", "usdt:/bin/sh:prov1:mytp"); } TEST(bpftrace, add_probes_usdt_wildcard) { ast::AttachPoint a("usdt", "/bin/sh", "prov*", "tp*", true); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace, get_symbols_from_usdt(0, "/bin/sh")).Times(1); ASSERT_EQ(0, bpftrace->add_probe(probe)); ASSERT_EQ(3U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); check_usdt(bpftrace->get_probes().at(0), "/bin/sh", "prov1", "tp1", "usdt:/bin/sh:prov1:tp1"); check_usdt(bpftrace->get_probes().at(1), "/bin/sh", "prov1", "tp2", "usdt:/bin/sh:prov1:tp2"); check_usdt(bpftrace->get_probes().at(2), "/bin/sh", "prov2", "tp", "usdt:/bin/sh:prov2:tp"); } TEST(bpftrace, add_probes_usdt_empty_namespace) { ast::AttachPoint a("usdt", "/bin/sh", "", "tp", true); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace, get_symbols_from_usdt(0, "/bin/sh")).Times(1); ASSERT_EQ(0, bpftrace->add_probe(probe)); ASSERT_EQ(2U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); check_usdt(bpftrace->get_probes().at(0), "/bin/sh", "nahprov", "tp", "usdt:/bin/sh:nahprov:tp"); check_usdt(bpftrace->get_probes().at(1), "/bin/sh", "prov2", "tp", "usdt:/bin/sh:prov2:tp"); } TEST(bpftrace, add_probes_tracepoint) { ast::AttachPoint a("tracepoint", "sched", "sched_switch", true); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); std::string probe_orig_name = "tracepoint:sched:sched_switch"; check_tracepoint(bpftrace.get_probes().at(0), "sched", "sched_switch", probe_orig_name); } TEST(bpftrace, add_probes_tracepoint_wildcard) { ast::AttachPoint a("tracepoint", "sched", "sched_*", true); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); auto bpftrace = get_strict_mock_bpftrace(); std::set matches = { "sched_one", "sched_two" }; EXPECT_CALL(*bpftrace, get_symbols_from_file("/sys/kernel/debug/tracing/available_events")) .Times(1); ASSERT_EQ(0, bpftrace->add_probe(probe)); ASSERT_EQ(2U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); std::string probe_orig_name = "tracepoint:sched:sched_*"; check_tracepoint(bpftrace->get_probes().at(0), "sched", "sched_one", probe_orig_name); check_tracepoint(bpftrace->get_probes().at(1), "sched", "sched_two", probe_orig_name); } TEST(bpftrace, add_probes_tracepoint_wildcard_no_matches) { ast::AttachPoint a("tracepoint", "typo", "typo_*", true); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); auto bpftrace = get_strict_mock_bpftrace(); EXPECT_CALL(*bpftrace, get_symbols_from_file("/sys/kernel/debug/tracing/available_events")) .Times(1); ASSERT_EQ(0, bpftrace->add_probe(probe)); ASSERT_EQ(0U, bpftrace->get_probes().size()); ASSERT_EQ(0U, bpftrace->get_special_probes().size()); } TEST(bpftrace, add_probes_profile) { ast::AttachPoint a("profile", "ms", 997); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); std::string probe_orig_name = "profile:ms:997"; check_profile(bpftrace.get_probes().at(0), "ms", 997, probe_orig_name); } TEST(bpftrace, add_probes_interval) { ast::AttachPoint a("interval", "s", 1); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); std::string probe_orig_name = "interval:s:1"; check_interval(bpftrace.get_probes().at(0), "s", 1, probe_orig_name); } TEST(bpftrace, add_probes_software) { ast::AttachPoint a("software", "faults", 1000); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); std::string probe_orig_name = "software:faults:1000"; check_software(bpftrace.get_probes().at(0), "faults", 1000, probe_orig_name); } TEST(bpftrace, add_probes_hardware) { ast::AttachPoint a("hardware", "cache-references", 1000000); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); ASSERT_EQ(1U, bpftrace.get_probes().size()); ASSERT_EQ(0U, bpftrace.get_special_probes().size()); std::string probe_orig_name = "hardware:cache-references:1000000"; check_hardware(bpftrace.get_probes().at(0), "cache-references", 1000000, probe_orig_name); } TEST(bpftrace, invalid_provider) { ast::AttachPoint a("lookatme", "invalid"); ast::AttachPointList attach_points = { &a }; ast::Probe probe(&attach_points, nullptr, nullptr); StrictMock bpftrace; ASSERT_EQ(0, bpftrace.add_probe(probe)); } std::pair, std::vector> key_value_pair_int(std::vector key, int val) { std::pair, std::vector> pair; pair.first = std::vector(sizeof(uint64_t)*key.size()); pair.second = std::vector(sizeof(uint64_t)); uint8_t *key_data = pair.first.data(); uint8_t *val_data = pair.second.data(); for (size_t i=0; i, std::vector> key_value_pair_str(std::vector key, int val) { std::pair, std::vector> pair; pair.first = std::vector(STRING_SIZE*key.size()); pair.second = std::vector(sizeof(uint64_t)); uint8_t *key_data = pair.first.data(); uint8_t *val_data = pair.second.data(); for (size_t i=0; i, std::vector> key_value_pair_int_str(int myint, std::string mystr, int val) { std::pair, std::vector> pair; pair.first = std::vector(sizeof(uint64_t) + STRING_SIZE); pair.second = std::vector(sizeof(uint64_t)); uint8_t *key_data = pair.first.data(); uint8_t *val_data = pair.second.data(); uint64_t k = myint, v = val; std::memcpy(key_data, &k, sizeof(k)); strncpy((char*)key_data + sizeof(uint64_t), mystr.c_str(), STRING_SIZE); std::memcpy(val_data, &v, sizeof(v)); return pair; } TEST(bpftrace, sort_by_key_int) { StrictMock bpftrace; std::vector key_args = { SizedType(Type::integer, 8) }; std::vector, std::vector>> values_by_key = { key_value_pair_int({2}, 12), key_value_pair_int({3}, 11), key_value_pair_int({1}, 10), }; bpftrace.sort_by_key(key_args, values_by_key); std::vector, std::vector>> expected_values = { key_value_pair_int({1}, 10), key_value_pair_int({2}, 12), key_value_pair_int({3}, 11), }; EXPECT_THAT(values_by_key, ContainerEq(expected_values)); } TEST(bpftrace, sort_by_key_int_int) { StrictMock bpftrace; std::vector key_args = { SizedType(Type::integer, 8), SizedType(Type::integer, 8), SizedType(Type::integer, 8), }; std::vector, std::vector>> values_by_key = { key_value_pair_int({5,2,1}, 1), key_value_pair_int({5,3,1}, 2), key_value_pair_int({5,1,1}, 3), key_value_pair_int({2,2,2}, 4), key_value_pair_int({2,3,2}, 5), key_value_pair_int({2,1,2}, 6), }; bpftrace.sort_by_key(key_args, values_by_key); std::vector, std::vector>> expected_values = { key_value_pair_int({2,1,2}, 6), key_value_pair_int({2,2,2}, 4), key_value_pair_int({2,3,2}, 5), key_value_pair_int({5,1,1}, 3), key_value_pair_int({5,2,1}, 1), key_value_pair_int({5,3,1}, 2), }; EXPECT_THAT(values_by_key, ContainerEq(expected_values)); } TEST(bpftrace, sort_by_key_str) { StrictMock bpftrace; std::vector key_args = { SizedType(Type::string, STRING_SIZE), }; std::vector, std::vector>> values_by_key = { key_value_pair_str({"z"}, 1), key_value_pair_str({"a"}, 2), key_value_pair_str({"x"}, 3), key_value_pair_str({"d"}, 4), }; bpftrace.sort_by_key(key_args, values_by_key); std::vector, std::vector>> expected_values = { key_value_pair_str({"a"}, 2), key_value_pair_str({"d"}, 4), key_value_pair_str({"x"}, 3), key_value_pair_str({"z"}, 1), }; EXPECT_THAT(values_by_key, ContainerEq(expected_values)); } TEST(bpftrace, sort_by_key_str_str) { StrictMock bpftrace; std::vector key_args = { SizedType(Type::string, STRING_SIZE), SizedType(Type::string, STRING_SIZE), SizedType(Type::string, STRING_SIZE), }; std::vector, std::vector>> values_by_key = { key_value_pair_str({"z", "a", "l"}, 1), key_value_pair_str({"a", "a", "m"}, 2), key_value_pair_str({"z", "c", "n"}, 3), key_value_pair_str({"a", "c", "o"}, 4), key_value_pair_str({"z", "b", "p"}, 5), key_value_pair_str({"a", "b", "q"}, 6), }; bpftrace.sort_by_key(key_args, values_by_key); std::vector, std::vector>> expected_values = { key_value_pair_str({"a", "a", "m"}, 2), key_value_pair_str({"a", "b", "q"}, 6), key_value_pair_str({"a", "c", "o"}, 4), key_value_pair_str({"z", "a", "l"}, 1), key_value_pair_str({"z", "b", "p"}, 5), key_value_pair_str({"z", "c", "n"}, 3), }; EXPECT_THAT(values_by_key, ContainerEq(expected_values)); } TEST(bpftrace, sort_by_key_int_str) { StrictMock bpftrace; std::vector key_args = { SizedType(Type::integer, 8), SizedType(Type::string, STRING_SIZE), }; std::vector, std::vector>> values_by_key = { key_value_pair_int_str(1, "b", 1), key_value_pair_int_str(2, "b", 2), key_value_pair_int_str(3, "b", 3), key_value_pair_int_str(1, "a", 4), key_value_pair_int_str(2, "a", 5), key_value_pair_int_str(3, "a", 6), }; bpftrace.sort_by_key(key_args, values_by_key); std::vector, std::vector>> expected_values = { key_value_pair_int_str(1, "a", 4), key_value_pair_int_str(1, "b", 1), key_value_pair_int_str(2, "a", 5), key_value_pair_int_str(2, "b", 2), key_value_pair_int_str(3, "a", 6), key_value_pair_int_str(3, "b", 3), }; EXPECT_THAT(values_by_key, ContainerEq(expected_values)); } } // namespace bpftrace } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/clang_parser.cpp000066400000000000000000000653751361633214400176430ustar00rootroot00000000000000#include "gtest/gtest.h" #include "clang_parser.h" #include "driver.h" #include "bpftrace.h" #include "struct.h" #include "field_analyser.h" #include namespace bpftrace { namespace test { namespace clang_parser { using StructMap = std::map; static void parse(const std::string &input, BPFtrace &bpftrace, bool result = true, const std::string& probe = "kprobe:sys_read { 1 }") { auto extended_input = input + probe; Driver driver(bpftrace); ASSERT_EQ(driver.parse_str(extended_input), 0); ast::FieldAnalyser fields(driver.root_, bpftrace); EXPECT_EQ(fields.analyse(), 0); ClangParser clang; ASSERT_EQ(clang.parse(driver.root_, bpftrace), result); } TEST(clang_parser, integers) { BPFtrace bpftrace; parse("struct Foo { int x; int y, z; }", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 1U); ASSERT_EQ(structs.count("struct Foo"), 1U); EXPECT_EQ(structs["struct Foo"].size, 12); ASSERT_EQ(structs["struct Foo"].fields.size(), 3U); ASSERT_EQ(structs["struct Foo"].fields.count("x"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("y"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("z"), 1U); EXPECT_EQ(structs["struct Foo"].fields["x"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["x"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["x"].offset, 0); EXPECT_EQ(structs["struct Foo"].fields["y"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["y"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["y"].offset, 4); EXPECT_EQ(structs["struct Foo"].fields["z"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["z"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["z"].offset, 8); } TEST(clang_parser, c_union) { BPFtrace bpftrace; parse("union Foo { char c; short s; int i; long l; }", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 1U); ASSERT_EQ(structs.count("union Foo"), 1U); EXPECT_EQ(structs["union Foo"].size, 8); ASSERT_EQ(structs["union Foo"].fields.size(), 4U); ASSERT_EQ(structs["union Foo"].fields.count("c"), 1U); ASSERT_EQ(structs["union Foo"].fields.count("s"), 1U); ASSERT_EQ(structs["union Foo"].fields.count("i"), 1U); ASSERT_EQ(structs["union Foo"].fields.count("l"), 1U); EXPECT_EQ(structs["union Foo"].fields["c"].type.type, Type::integer); EXPECT_EQ(structs["union Foo"].fields["c"].type.size, 1U); EXPECT_EQ(structs["union Foo"].fields["c"].offset, 0); EXPECT_EQ(structs["union Foo"].fields["s"].type.type, Type::integer); EXPECT_EQ(structs["union Foo"].fields["s"].type.size, 2U); EXPECT_EQ(structs["union Foo"].fields["s"].offset, 0); EXPECT_EQ(structs["union Foo"].fields["i"].type.type, Type::integer); EXPECT_EQ(structs["union Foo"].fields["i"].type.size, 4U); EXPECT_EQ(structs["union Foo"].fields["i"].offset, 0); EXPECT_EQ(structs["union Foo"].fields["l"].type.type, Type::integer); EXPECT_EQ(structs["union Foo"].fields["l"].type.size, 8U); EXPECT_EQ(structs["union Foo"].fields["l"].offset, 0); } TEST(clang_parser, c_enum) { BPFtrace bpftrace; parse("enum E {NONE}; struct Foo { enum E e; }", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 1U); ASSERT_EQ(structs.count("struct Foo"), 1U); EXPECT_EQ(structs["struct Foo"].size, 4); ASSERT_EQ(structs["struct Foo"].fields.size(), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("e"), 1U); EXPECT_EQ(structs["struct Foo"].fields["e"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["e"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["e"].offset, 0); } TEST(clang_parser, integer_ptr) { BPFtrace bpftrace; parse("struct Foo { int *x; }", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 1U); ASSERT_EQ(structs.count("struct Foo"), 1U); EXPECT_EQ(structs["struct Foo"].size, 8); ASSERT_EQ(structs["struct Foo"].fields.size(), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("x"), 1U); EXPECT_EQ(structs["struct Foo"].fields["x"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["x"].type.size, sizeof(uintptr_t)); EXPECT_EQ(structs["struct Foo"].fields["x"].type.is_pointer, true); EXPECT_EQ(structs["struct Foo"].fields["x"].type.pointee_size, sizeof(int)); EXPECT_EQ(structs["struct Foo"].fields["x"].offset, 0); } TEST(clang_parser, string_ptr) { BPFtrace bpftrace; parse("struct Foo { char *str; }", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 1U); ASSERT_EQ(structs.count("struct Foo"), 1U); EXPECT_EQ(structs["struct Foo"].size, 8); ASSERT_EQ(structs["struct Foo"].fields.size(), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("str"), 1U); EXPECT_EQ(structs["struct Foo"].fields["str"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["str"].type.size, sizeof(uintptr_t)); EXPECT_EQ(structs["struct Foo"].fields["str"].type.is_pointer, true); EXPECT_EQ(structs["struct Foo"].fields["str"].type.pointee_size, 1U); EXPECT_EQ(structs["struct Foo"].fields["str"].offset, 0); } TEST(clang_parser, string_array) { BPFtrace bpftrace; parse("struct Foo { char str[32]; }", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 1U); ASSERT_EQ(structs.count("struct Foo"), 1U); EXPECT_EQ(structs["struct Foo"].size, 32); ASSERT_EQ(structs["struct Foo"].fields.size(), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("str"), 1U); EXPECT_EQ(structs["struct Foo"].fields["str"].type.type, Type::string); EXPECT_EQ(structs["struct Foo"].fields["str"].type.size, 32U); EXPECT_EQ(structs["struct Foo"].fields["str"].offset, 0); } TEST(clang_parser, nested_struct_named) { BPFtrace bpftrace; parse("struct Bar { int x; } struct Foo { struct Bar bar; }", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 2U); ASSERT_EQ(structs.count("struct Foo"), 1U); ASSERT_EQ(structs.count("struct Bar"), 1U); EXPECT_EQ(structs["struct Foo"].size, 4); ASSERT_EQ(structs["struct Foo"].fields.size(), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("bar"), 1U); EXPECT_EQ(structs["struct Foo"].fields["bar"].type.type, Type::cast); EXPECT_EQ(structs["struct Foo"].fields["bar"].type.cast_type, "struct Bar"); EXPECT_EQ(structs["struct Foo"].fields["bar"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["bar"].offset, 0); } TEST(clang_parser, nested_struct_ptr_named) { BPFtrace bpftrace; parse("struct Bar { int x; } struct Foo { struct Bar *bar; }", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 2U); ASSERT_EQ(structs.count("struct Foo"), 1U); ASSERT_EQ(structs.count("struct Bar"), 1U); EXPECT_EQ(structs["struct Foo"].size, 8); ASSERT_EQ(structs["struct Foo"].fields.size(), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("bar"), 1U); EXPECT_EQ(structs["struct Foo"].fields["bar"].type.type, Type::cast); EXPECT_EQ(structs["struct Foo"].fields["bar"].type.cast_type, "struct Bar"); EXPECT_EQ(structs["struct Foo"].fields["bar"].type.size, sizeof(uintptr_t)); EXPECT_EQ(structs["struct Foo"].fields["bar"].type.is_pointer, true); EXPECT_EQ(structs["struct Foo"].fields["bar"].type.pointee_size, 4U); EXPECT_EQ(structs["struct Foo"].fields["bar"].offset, 0); } TEST(clang_parser, nested_struct_no_type) { BPFtrace bpftrace; // bar and baz's struct/union do not have type names, but are not anonymous // since they are called bar and baz parse("struct Foo { struct { int x; } bar; union { int y; } baz; }", bpftrace); std::string bar = "struct Foo::(anonymous at definitions.h:1:14)"; std::string baz = "union Foo::(anonymous at definitions.h:1:37)"; StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 3U); ASSERT_EQ(structs.count("struct Foo"), 1U); ASSERT_EQ(structs.count(bar), 1U); ASSERT_EQ(structs.count(baz), 1U); EXPECT_EQ(structs["struct Foo"].size, 8); ASSERT_EQ(structs["struct Foo"].fields.size(), 2U); ASSERT_EQ(structs["struct Foo"].fields.count("bar"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("baz"), 1U); EXPECT_EQ(structs["struct Foo"].fields["bar"].type.type, Type::cast); EXPECT_EQ(structs["struct Foo"].fields["bar"].type.cast_type, bar); EXPECT_EQ(structs["struct Foo"].fields["bar"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["bar"].offset, 0); EXPECT_EQ(structs["struct Foo"].fields["baz"].type.type, Type::cast); EXPECT_EQ(structs["struct Foo"].fields["baz"].type.cast_type, baz); EXPECT_EQ(structs["struct Foo"].fields["baz"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["baz"].offset, 4); EXPECT_EQ(structs[bar].size, 4); ASSERT_EQ(structs[bar].fields.size(), 1U); ASSERT_EQ(structs[bar].fields.count("x"), 1U); EXPECT_EQ(structs[bar].fields["x"].type.type, Type::integer); EXPECT_EQ(structs[bar].fields["x"].type.size, 4U); EXPECT_EQ(structs[bar].fields["x"].offset, 0); EXPECT_EQ(structs[baz].size, 4); ASSERT_EQ(structs[baz].fields.size(), 1U); ASSERT_EQ(structs[baz].fields.count("y"), 1U); EXPECT_EQ(structs[baz].fields["y"].type.type, Type::integer); EXPECT_EQ(structs[baz].fields["y"].type.size, 4U); EXPECT_EQ(structs[baz].fields["y"].offset, 0); } TEST(clang_parser, nested_struct_unnamed_fields) { BPFtrace bpftrace; parse("struct Foo" "{" " struct { int x; int y; };" // Anonymous struct field " int a;" " struct Bar { int z; };" // Struct definition - not a field of Foo "}", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 2U); ASSERT_EQ(structs.count("struct Foo"), 1U); ASSERT_EQ(structs.count("struct Bar"), 1U); EXPECT_EQ(structs["struct Foo"].size, 12); ASSERT_EQ(structs["struct Foo"].fields.size(), 3U); ASSERT_EQ(structs["struct Foo"].fields.count("x"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("y"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("a"), 1U); EXPECT_EQ(structs["struct Foo"].fields["x"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["x"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["x"].offset, 0); EXPECT_EQ(structs["struct Foo"].fields["y"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["y"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["y"].offset, 4); EXPECT_EQ(structs["struct Foo"].fields["a"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["a"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["a"].offset, 8); EXPECT_EQ(structs["struct Bar"].size, 4); EXPECT_EQ(structs["struct Bar"].fields.size(), 1U); EXPECT_EQ(structs["struct Bar"].fields.count("z"), 1U); EXPECT_EQ(structs["struct Bar"].fields["z"].type.type, Type::integer); EXPECT_EQ(structs["struct Bar"].fields["z"].type.size, 4U); EXPECT_EQ(structs["struct Bar"].fields["z"].offset, 0); } TEST(clang_parser, nested_struct_anon_union_struct) { BPFtrace bpftrace; parse("struct Foo" "{" " union" " {" " long long _xy;" " struct { int x; int y;};" " };" " int a;" " struct { int z; };" "}", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 1U); ASSERT_EQ(structs.count("struct Foo"), 1U); EXPECT_EQ(structs["struct Foo"].size, 16); ASSERT_EQ(structs["struct Foo"].fields.size(), 5U); ASSERT_EQ(structs["struct Foo"].fields.count("_xy"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("x"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("y"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("a"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("z"), 1U); EXPECT_EQ(structs["struct Foo"].fields["_xy"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["_xy"].type.size, 8U); EXPECT_EQ(structs["struct Foo"].fields["_xy"].offset, 0); EXPECT_EQ(structs["struct Foo"].fields["x"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["x"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["x"].offset, 0); EXPECT_EQ(structs["struct Foo"].fields["y"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["y"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["y"].offset, 4); EXPECT_EQ(structs["struct Foo"].fields["a"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["a"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["a"].offset, 8); EXPECT_EQ(structs["struct Foo"].fields["z"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["z"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["z"].offset, 12); } TEST(clang_parser, bitfields) { BPFtrace bpftrace; parse("struct Foo { int a:8, b:8, c:16; }", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 1U); ASSERT_EQ(structs.count("struct Foo"), 1U); EXPECT_EQ(structs["struct Foo"].size, 4); ASSERT_EQ(structs["struct Foo"].fields.size(), 3U); ASSERT_EQ(structs["struct Foo"].fields.count("a"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("b"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("c"), 1U); EXPECT_EQ(structs["struct Foo"].fields["a"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["a"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["a"].offset, 0); EXPECT_TRUE(structs["struct Foo"].fields["a"].is_bitfield); EXPECT_EQ(structs["struct Foo"].fields["a"].bitfield.read_bytes, 0x1U); EXPECT_EQ(structs["struct Foo"].fields["a"].bitfield.access_rshift, 0U); EXPECT_EQ(structs["struct Foo"].fields["a"].bitfield.mask, 0xFFU); EXPECT_EQ(structs["struct Foo"].fields["b"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["b"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["b"].offset, 1); EXPECT_TRUE(structs["struct Foo"].fields["b"].is_bitfield); EXPECT_EQ(structs["struct Foo"].fields["b"].bitfield.read_bytes, 0x1U); EXPECT_EQ(structs["struct Foo"].fields["b"].bitfield.access_rshift, 0U); EXPECT_EQ(structs["struct Foo"].fields["b"].bitfield.mask, 0xFFU); EXPECT_EQ(structs["struct Foo"].fields["c"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["c"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["c"].offset, 2); EXPECT_TRUE(structs["struct Foo"].fields["c"].is_bitfield); EXPECT_EQ(structs["struct Foo"].fields["c"].bitfield.read_bytes, 0x2U); EXPECT_EQ(structs["struct Foo"].fields["c"].bitfield.access_rshift, 0U); EXPECT_EQ(structs["struct Foo"].fields["c"].bitfield.mask, 0xFFFFU); } TEST(clang_parser, bitfields_uneven_fields) { BPFtrace bpftrace; parse("struct Foo { int a:1, b:1, c:3, d:20, e:7; }", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 1U); ASSERT_EQ(structs.count("struct Foo"), 1U); EXPECT_EQ(structs["struct Foo"].size, 4); ASSERT_EQ(structs["struct Foo"].fields.size(), 5U); ASSERT_EQ(structs["struct Foo"].fields.count("a"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("b"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("c"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("d"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("e"), 1U); EXPECT_EQ(structs["struct Foo"].fields["a"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["a"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["a"].offset, 0); EXPECT_TRUE(structs["struct Foo"].fields["a"].is_bitfield); EXPECT_EQ(structs["struct Foo"].fields["a"].bitfield.read_bytes, 1U); EXPECT_EQ(structs["struct Foo"].fields["a"].bitfield.access_rshift, 0U); EXPECT_EQ(structs["struct Foo"].fields["a"].bitfield.mask, 0x1U); EXPECT_EQ(structs["struct Foo"].fields["b"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["b"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["b"].offset, 0); EXPECT_TRUE(structs["struct Foo"].fields["b"].is_bitfield); EXPECT_EQ(structs["struct Foo"].fields["b"].bitfield.read_bytes, 1U); EXPECT_EQ(structs["struct Foo"].fields["b"].bitfield.access_rshift, 1U); EXPECT_EQ(structs["struct Foo"].fields["b"].bitfield.mask, 0x1U); EXPECT_EQ(structs["struct Foo"].fields["c"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["c"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["c"].offset, 0); EXPECT_TRUE(structs["struct Foo"].fields["c"].is_bitfield); EXPECT_EQ(structs["struct Foo"].fields["c"].bitfield.read_bytes, 1U); EXPECT_EQ(structs["struct Foo"].fields["c"].bitfield.access_rshift, 2U); EXPECT_EQ(structs["struct Foo"].fields["c"].bitfield.mask, 0x7U); EXPECT_EQ(structs["struct Foo"].fields["d"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["d"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["d"].offset, 0); EXPECT_TRUE(structs["struct Foo"].fields["d"].is_bitfield); EXPECT_EQ(structs["struct Foo"].fields["d"].bitfield.read_bytes, 4U); EXPECT_EQ(structs["struct Foo"].fields["d"].bitfield.access_rshift, 5U); EXPECT_EQ(structs["struct Foo"].fields["d"].bitfield.mask, 0xFFFFFU); EXPECT_EQ(structs["struct Foo"].fields["e"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["e"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["e"].offset, 3); EXPECT_TRUE(structs["struct Foo"].fields["e"].is_bitfield); EXPECT_EQ(structs["struct Foo"].fields["e"].bitfield.read_bytes, 1U); EXPECT_EQ(structs["struct Foo"].fields["e"].bitfield.access_rshift, 1U); EXPECT_EQ(structs["struct Foo"].fields["e"].bitfield.mask, 0x7FU); } TEST(clang_parser, bitfields_with_padding) { BPFtrace bpftrace; parse("struct Foo { int pad; int a:28, b:4; long int end;}", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 1U); ASSERT_EQ(structs.count("struct Foo"), 1U); EXPECT_EQ(structs["struct Foo"].size, 16); ASSERT_EQ(structs["struct Foo"].fields.size(), 4U); ASSERT_EQ(structs["struct Foo"].fields.count("pad"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("a"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("b"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("end"), 1U); EXPECT_EQ(structs["struct Foo"].fields["a"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["a"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["a"].offset, 4); EXPECT_TRUE(structs["struct Foo"].fields["a"].is_bitfield); EXPECT_EQ(structs["struct Foo"].fields["a"].bitfield.read_bytes, 4U); EXPECT_EQ(structs["struct Foo"].fields["a"].bitfield.access_rshift, 0U); EXPECT_EQ(structs["struct Foo"].fields["a"].bitfield.mask, 0xFFFFFFFU); EXPECT_EQ(structs["struct Foo"].fields["b"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["b"].type.size, 4U); EXPECT_EQ(structs["struct Foo"].fields["b"].offset, 7); EXPECT_TRUE(structs["struct Foo"].fields["b"].is_bitfield); EXPECT_EQ(structs["struct Foo"].fields["b"].bitfield.read_bytes, 1U); EXPECT_EQ(structs["struct Foo"].fields["b"].bitfield.access_rshift, 4U); EXPECT_EQ(structs["struct Foo"].fields["b"].bitfield.mask, 0xFU); } TEST(clang_parser, builtin_headers) { // size_t is definied in stddef.h BPFtrace bpftrace; parse("#include \nstruct Foo { size_t x, y, z; }", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.count("struct Foo"), 1U); EXPECT_EQ(structs["struct Foo"].size, 24); ASSERT_EQ(structs["struct Foo"].fields.size(), 3U); ASSERT_EQ(structs["struct Foo"].fields.count("x"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("y"), 1U); ASSERT_EQ(structs["struct Foo"].fields.count("z"), 1U); EXPECT_EQ(structs["struct Foo"].fields["x"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["x"].type.size, 8U); EXPECT_EQ(structs["struct Foo"].fields["x"].offset, 0); EXPECT_EQ(structs["struct Foo"].fields["y"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["y"].type.size, 8U); EXPECT_EQ(structs["struct Foo"].fields["y"].offset, 8); EXPECT_EQ(structs["struct Foo"].fields["z"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo"].fields["z"].type.size, 8U); EXPECT_EQ(structs["struct Foo"].fields["z"].offset, 16); } TEST(clang_parser, macro_preprocessor) { BPFtrace bpftrace; parse("#define FOO size_t\n k:f { 0 }", bpftrace); parse("#define _UNDERSCORE 314\n k:f { 0 }", bpftrace); auto ¯os = bpftrace.macros_; ASSERT_EQ(macros.count("FOO"), 1U); EXPECT_EQ(macros["FOO"], "size_t"); ASSERT_EQ(macros.count("_UNDERSCORE"), 1U); EXPECT_EQ(macros["_UNDERSCORE"], "314"); } TEST(clang_parser, parse_fail) { BPFtrace bpftrace; parse("struct a { int a; struct b b; };", bpftrace, false); } #ifdef HAVE_LIBBPF_BTF_DUMP #include "data/btf_data.h" class clang_parser_btf : public ::testing::Test { protected: void SetUp() override { char *path = strdup("/tmp/XXXXXX"); if (!path) return; int fd = mkstemp(path); if (fd < 0) { std::remove(path); return; } if (write(fd, btf_data, btf_data_len) != btf_data_len) { close(fd); std::remove(path); return; } close(fd); setenv("BPFTRACE_BTF", path, true); path_ = path; } void TearDown() override { // clear the environment and remove the temp file unsetenv("BPFTRACE_BTF"); if (path_) std::remove(path_); } char *path_ = nullptr; }; TEST_F(clang_parser_btf, btf) { BPFtrace bpftrace; parse("", bpftrace, true, "kprobe:sys_read {\n" " @x1 = (struct Foo1 *) curtask;\n" " @x2 = (struct Foo2 *) curtask;\n" " @x3 = (struct Foo3 *) curtask;\n" "}"); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 3U); ASSERT_EQ(structs.count("struct Foo1"), 1U); ASSERT_EQ(structs.count("struct Foo2"), 1U); ASSERT_EQ(structs.count("struct Foo3"), 1U); EXPECT_EQ(structs["struct Foo1"].size, 16); ASSERT_EQ(structs["struct Foo1"].fields.size(), 3U); ASSERT_EQ(structs["struct Foo1"].fields.count("a"), 1U); ASSERT_EQ(structs["struct Foo1"].fields.count("b"), 1U); ASSERT_EQ(structs["struct Foo1"].fields.count("c"), 1U); EXPECT_EQ(structs["struct Foo1"].fields["a"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo1"].fields["a"].type.size, 4U); EXPECT_EQ(structs["struct Foo1"].fields["a"].offset, 0); EXPECT_EQ(structs["struct Foo1"].fields["b"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo1"].fields["b"].type.size, 1U); EXPECT_EQ(structs["struct Foo1"].fields["b"].offset, 4); EXPECT_EQ(structs["struct Foo1"].fields["c"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo1"].fields["c"].type.size, 8U); EXPECT_EQ(structs["struct Foo1"].fields["c"].offset, 8); EXPECT_EQ(structs["struct Foo2"].size, 24); ASSERT_EQ(structs["struct Foo2"].fields.size(), 3U); ASSERT_EQ(structs["struct Foo2"].fields.count("a"), 1U); ASSERT_EQ(structs["struct Foo2"].fields.count("f"), 1U); ASSERT_EQ(structs["struct Foo2"].fields.count("g"), 1U); EXPECT_EQ(structs["struct Foo2"].fields["a"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo2"].fields["a"].type.size, 4U); EXPECT_EQ(structs["struct Foo2"].fields["a"].offset, 0); EXPECT_EQ(structs["struct Foo2"].fields["f"].type.type, Type::cast); EXPECT_EQ(structs["struct Foo2"].fields["f"].type.size, 16U); EXPECT_EQ(structs["struct Foo2"].fields["f"].offset, 8); EXPECT_EQ(structs["struct Foo2"].fields["g"].type.type, Type::integer); EXPECT_EQ(structs["struct Foo2"].fields["g"].type.size, 1U); EXPECT_EQ(structs["struct Foo2"].fields["g"].offset, 8); EXPECT_EQ(structs["struct Foo3"].size, 16); ASSERT_EQ(structs["struct Foo3"].fields.size(), 2U); ASSERT_EQ(structs["struct Foo3"].fields.count("foo1"), 1U); ASSERT_EQ(structs["struct Foo3"].fields.count("foo2"), 1U); EXPECT_EQ(structs["struct Foo3"].fields["foo1"].type.type, Type::cast); EXPECT_EQ(structs["struct Foo3"].fields["foo1"].type.size, 8U); EXPECT_EQ(structs["struct Foo3"].fields["foo1"].type.is_pointer, true); EXPECT_EQ(structs["struct Foo3"].fields["foo1"].type.cast_type, "struct Foo1"); EXPECT_EQ(structs["struct Foo3"].fields["foo1"].offset, 0); EXPECT_EQ(structs["struct Foo3"].fields["foo2"].type.type, Type::cast); EXPECT_EQ(structs["struct Foo3"].fields["foo2"].type.size, 8U); EXPECT_EQ(structs["struct Foo3"].fields["foo2"].type.is_pointer, true); EXPECT_EQ(structs["struct Foo3"].fields["foo2"].type.cast_type, "struct Foo2"); EXPECT_EQ(structs["struct Foo3"].fields["foo2"].offset, 8); } TEST_F(clang_parser_btf, btf_field_struct) { BPFtrace bpftrace; parse("", bpftrace, true, "kprobe:sys_read {\n" " @x3 = ((struct Foo3 *) curtask)->foo2->g;\n" "}"); /* task_struct->Foo3->Foo2->char */ EXPECT_EQ(bpftrace.btf_set_.size(), 4U); EXPECT_NE(bpftrace.btf_set_.find("struct task_struct"), bpftrace.btf_set_.end()); EXPECT_NE(bpftrace.btf_set_.find("struct Foo3"), bpftrace.btf_set_.end()); EXPECT_NE(bpftrace.btf_set_.find("struct Foo2"), bpftrace.btf_set_.end()); EXPECT_NE(bpftrace.btf_set_.find("char"), bpftrace.btf_set_.end()); } #endif // HAVE_LIBBPF_BTF_DUMP TEST(clang_parser, struct_typedef) { // Make sure we can differentiate between "struct max_align_t {}" and // "typedef struct {} max_align_t" BPFtrace bpftrace; parse("#include <__stddef_max_align_t.h>\n" "struct max_align_t { int x; };", bpftrace); StructMap &structs = bpftrace.structs_; ASSERT_EQ(structs.size(), 2U); ASSERT_EQ(structs.count("struct max_align_t"), 1U); ASSERT_EQ(structs.count("max_align_t"), 1U); // Non-typedef'd struct EXPECT_EQ(structs["struct max_align_t"].size, 4); ASSERT_EQ(structs["struct max_align_t"].fields.size(), 1U); ASSERT_EQ(structs["struct max_align_t"].fields.count("x"), 1U); EXPECT_EQ(structs["struct max_align_t"].fields["x"].type.type, Type::integer); EXPECT_EQ(structs["struct max_align_t"].fields["x"].type.size, 4U); EXPECT_EQ(structs["struct max_align_t"].fields["x"].offset, 0); // typedef'd struct (defined in __stddef_max_align_t.h builtin header) EXPECT_EQ(structs["max_align_t"].size, 32); ASSERT_EQ(structs["max_align_t"].fields.size(), 2U); ASSERT_EQ(structs["max_align_t"].fields.count("__clang_max_align_nonce1"), 1U); ASSERT_EQ(structs["max_align_t"].fields.count("__clang_max_align_nonce2"), 1U); EXPECT_EQ(structs["max_align_t"].fields["__clang_max_align_nonce1"].type.type, Type::integer); EXPECT_EQ(structs["max_align_t"].fields["__clang_max_align_nonce1"].type.size, 8U); EXPECT_EQ(structs["max_align_t"].fields["__clang_max_align_nonce1"].offset, 0); // double are not parsed correctly yet so these fields are junk for now EXPECT_EQ(structs["max_align_t"].fields["__clang_max_align_nonce2"].type.type, Type::none); EXPECT_EQ(structs["max_align_t"].fields["__clang_max_align_nonce2"].type.size, 0U); EXPECT_EQ(structs["max_align_t"].fields["__clang_max_align_nonce2"].offset, 16); } } // namespace clang_parser } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/000077500000000000000000000000001361633214400160635ustar00rootroot00000000000000bpftrace-0.9.4/tests/codegen/args_multiple_tracepoints.cpp000066400000000000000000000221451361633214400240550ustar00rootroot00000000000000#include "common.h" #include "../mocks.h" using ::testing::Return; using ::testing::_; namespace bpftrace { namespace test { namespace codegen { TEST(codegen, args_multiple_tracepoints) { auto bpftrace = get_mock_bpftrace(); test(*bpftrace, "tracepoint:sched:sched_one,tracepoint:sched:sched_two { " "@[args->common_field] = count(); }", #if LLVM_VERSION_MAJOR > 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:sched:sched_one"(i8*) local_unnamed_addr section "s_tracepoint:sched:sched_one_1" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca [8 x i8], align 8 %"struct _tracepoint_sched_sched_one.common_field" = alloca i64, align 8 %1 = add i8* %0, i64 8 %2 = bitcast i64* %"struct _tracepoint_sched_sched_one.common_field" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct _tracepoint_sched_sched_one.common_field", i64 8, i8* %1) %3 = load i64, i64* %"struct _tracepoint_sched_sched_one.common_field", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %4 = getelementptr inbounds [8 x i8], [8 x i8]* %"@_key", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %3, [8 x i8]* %"@_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [8 x i8]*)*)(i64 %pseudo, [8 x i8]* nonnull %"@_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %5 = load i64, i64* %cast, align 8 %phitmp = add i64 %5, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %6 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %lookup_elem_val.0, i64* %"@_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [8 x i8]*, i64*, i64)*)(i64 %pseudo1, [8 x i8]* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:sched:sched_two"(i8*) local_unnamed_addr section "s_tracepoint:sched:sched_two_2" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca [8 x i8], align 8 %"struct _tracepoint_sched_sched_two.common_field" = alloca i64, align 8 %1 = add i8* %0, i64 16 %2 = bitcast i64* %"struct _tracepoint_sched_sched_two.common_field" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct _tracepoint_sched_sched_two.common_field", i64 8, i8* %1) %3 = load i64, i64* %"struct _tracepoint_sched_sched_two.common_field", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %4 = getelementptr inbounds [8 x i8], [8 x i8]* %"@_key", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %3, [8 x i8]* %"@_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [8 x i8]*)*)(i64 %pseudo, [8 x i8]* nonnull %"@_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %5 = load i64, i64* %cast, align 8 %phitmp = add i64 %5, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %6 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %lookup_elem_val.0, i64* %"@_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [8 x i8]*, i64*, i64)*)(i64 %pseudo1, [8 x i8]* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:sched:sched_one"(i8*) local_unnamed_addr section "s_tracepoint:sched:sched_one_1" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca [8 x i8], align 8 %"struct _tracepoint_sched_sched_one.common_field" = alloca i64, align 8 %1 = add i8* %0, i64 8 %2 = bitcast i64* %"struct _tracepoint_sched_sched_one.common_field" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct _tracepoint_sched_sched_one.common_field", i64 8, i8* %1) %3 = load i64, i64* %"struct _tracepoint_sched_sched_one.common_field", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %4 = getelementptr inbounds [8 x i8], [8 x i8]* %"@_key", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %3, [8 x i8]* %"@_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [8 x i8]*)*)(i64 %pseudo, [8 x i8]* nonnull %"@_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %5 = load i64, i64* %cast, align 8 %phitmp = add i64 %5, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %6 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %lookup_elem_val.0, i64* %"@_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [8 x i8]*, i64*, i64)*)(i64 %pseudo1, [8 x i8]* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:sched:sched_two"(i8*) local_unnamed_addr section "s_tracepoint:sched:sched_two_2" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca [8 x i8], align 8 %"struct _tracepoint_sched_sched_two.common_field" = alloca i64, align 8 %1 = add i8* %0, i64 16 %2 = bitcast i64* %"struct _tracepoint_sched_sched_two.common_field" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct _tracepoint_sched_sched_two.common_field", i64 8, i8* %1) %3 = load i64, i64* %"struct _tracepoint_sched_sched_two.common_field", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %4 = getelementptr inbounds [8 x i8], [8 x i8]* %"@_key", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %3, [8 x i8]* %"@_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [8 x i8]*)*)(i64 %pseudo, [8 x i8]* nonnull %"@_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %5 = load i64, i64* %cast, align 8 %phitmp = add i64 %5, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %6 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %lookup_elem_val.0, i64* %"@_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [8 x i8]*, i64*, i64)*)(i64 %pseudo1, [8 x i8]* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/args_multiple_tracepoints_wild.cpp000066400000000000000000000221031361633214400250660ustar00rootroot00000000000000#include "common.h" #include "../mocks.h" using ::testing::Return; using ::testing::_; namespace bpftrace { namespace test { namespace codegen { TEST(codegen, args_multiple_tracepoints_wild) { auto bpftrace = get_mock_bpftrace(); test(*bpftrace, "tracepoint:sched:sched_* { @[args->common_field] = count(); }", #if LLVM_VERSION_MAJOR > 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:sched:sched_one"(i8*) local_unnamed_addr section "s_tracepoint:sched:sched_one_1" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca [8 x i8], align 8 %"struct _tracepoint_sched_sched_one.common_field" = alloca i64, align 8 %1 = add i8* %0, i64 8 %2 = bitcast i64* %"struct _tracepoint_sched_sched_one.common_field" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct _tracepoint_sched_sched_one.common_field", i64 8, i8* %1) %3 = load i64, i64* %"struct _tracepoint_sched_sched_one.common_field", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %4 = getelementptr inbounds [8 x i8], [8 x i8]* %"@_key", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %3, [8 x i8]* %"@_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [8 x i8]*)*)(i64 %pseudo, [8 x i8]* nonnull %"@_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %5 = load i64, i64* %cast, align 8 %phitmp = add i64 %5, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %6 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %lookup_elem_val.0, i64* %"@_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [8 x i8]*, i64*, i64)*)(i64 %pseudo1, [8 x i8]* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:sched:sched_two"(i8*) local_unnamed_addr section "s_tracepoint:sched:sched_two_2" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca [8 x i8], align 8 %"struct _tracepoint_sched_sched_two.common_field" = alloca i64, align 8 %1 = add i8* %0, i64 16 %2 = bitcast i64* %"struct _tracepoint_sched_sched_two.common_field" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct _tracepoint_sched_sched_two.common_field", i64 8, i8* %1) %3 = load i64, i64* %"struct _tracepoint_sched_sched_two.common_field", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %4 = getelementptr inbounds [8 x i8], [8 x i8]* %"@_key", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %3, [8 x i8]* %"@_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [8 x i8]*)*)(i64 %pseudo, [8 x i8]* nonnull %"@_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %5 = load i64, i64* %cast, align 8 %phitmp = add i64 %5, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %6 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %lookup_elem_val.0, i64* %"@_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [8 x i8]*, i64*, i64)*)(i64 %pseudo1, [8 x i8]* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:sched:sched_one"(i8*) local_unnamed_addr section "s_tracepoint:sched:sched_one_1" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca [8 x i8], align 8 %"struct _tracepoint_sched_sched_one.common_field" = alloca i64, align 8 %1 = add i8* %0, i64 8 %2 = bitcast i64* %"struct _tracepoint_sched_sched_one.common_field" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct _tracepoint_sched_sched_one.common_field", i64 8, i8* %1) %3 = load i64, i64* %"struct _tracepoint_sched_sched_one.common_field", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %4 = getelementptr inbounds [8 x i8], [8 x i8]* %"@_key", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %3, [8 x i8]* %"@_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [8 x i8]*)*)(i64 %pseudo, [8 x i8]* nonnull %"@_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %5 = load i64, i64* %cast, align 8 %phitmp = add i64 %5, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %6 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %lookup_elem_val.0, i64* %"@_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [8 x i8]*, i64*, i64)*)(i64 %pseudo1, [8 x i8]* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:sched:sched_two"(i8*) local_unnamed_addr section "s_tracepoint:sched:sched_two_2" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca [8 x i8], align 8 %"struct _tracepoint_sched_sched_two.common_field" = alloca i64, align 8 %1 = add i8* %0, i64 16 %2 = bitcast i64* %"struct _tracepoint_sched_sched_two.common_field" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct _tracepoint_sched_sched_two.common_field", i64 8, i8* %1) %3 = load i64, i64* %"struct _tracepoint_sched_sched_two.common_field", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %4 = getelementptr inbounds [8 x i8], [8 x i8]* %"@_key", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %3, [8 x i8]* %"@_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [8 x i8]*)*)(i64 %pseudo, [8 x i8]* nonnull %"@_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %5 = load i64, i64* %cast, align 8 %phitmp = add i64 %5, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %6 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %lookup_elem_val.0, i64* %"@_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [8 x i8]*, i64*, i64)*)(i64 %pseudo1, [8 x i8]* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/bitshift_left.cpp000066400000000000000000000025301361633214400214150ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, bitshift_left) { test("kprobe:f { @x = 1 << 10; }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 1024, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/bitshift_right.cpp000066400000000000000000000025301361633214400216000ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, bitshift_right) { test("kprobe:f { @x = 1024 >> 9; }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 2, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/bitwise_not.cpp000066400000000000000000000025061361633214400211200ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, bitwise_not) { test("BEGIN { @x = ~10; }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 -11, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_arg.cpp000066400000000000000000000042201361633214400210640ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_arg) { test("kprobe:f { @x = arg0; @y = arg2 }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@y_val" = alloca i64, align 8 %"@y_key" = alloca i64, align 8 %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = getelementptr i8, i8* %0, i64 112 %arg0 = load i64, i8* %1, align 8 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 %arg0, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %4 = getelementptr i8, i8* %0, i64 96 %arg2 = load i64, i8* %4, align 8 %5 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@y_key", align 8 %6 = bitcast i64* %"@y_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %arg2, i64* %"@y_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem2 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@y_key", i64* nonnull %"@y_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_comm.cpp000066400000000000000000000060761361633214400212610ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_comm) { test("kprobe:f { @x = comm }", #if LLVM_VERSION_MAJOR < 7 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %comm = alloca [16 x i8], align 1 %1 = getelementptr inbounds [16 x i8], [16 x i8]* %comm, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i8 0, i64 16, i32 1, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm, i64 16) %2 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [16 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [16 x i8]* nonnull %comm, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %comm = alloca [16 x i8], align 1 %1 = getelementptr inbounds [16 x i8], [16 x i8]* %comm, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %1, i8 0, i64 16, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm, i64 16) %2 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [16 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [16 x i8]* nonnull %comm, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_cpid.cpp000066400000000000000000000030661361633214400212410ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { class MockBPFtraceCpid : public BPFtrace { public: MOCK_METHOD0(child_pid, int(void)); }; TEST(codegen, builtin_cpid) { MockBPFtraceCpid bpftrace; bpftrace.cmd_ = "sleep 1"; ON_CALL(bpftrace, child_pid()) .WillByDefault(Return(1337)); test(bpftrace, "kprobe:f { @ = cpid }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca i64, align 8 %1 = bitcast i64* %"@_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@_key", align 8 %2 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 1337, i64* %"@_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_cpu.cpp000066400000000000000000000026241361633214400211100ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_cpu) { test("kprobe:f { @x = cpu }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 %get_cpu_id, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_ctx.cpp000066400000000000000000000025261361633214400211200ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_ctx) { test("kprobe:f { @x = ctx }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = zext i8* %0 to i64 %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 %2, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_curtask.cpp000066400000000000000000000026411361633214400217740ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_curtask) { test("kprobe:f { @x = curtask }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_ptr" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %get_cur_task = tail call i64 inttoptr (i64 35 to i64 ()*)() %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_ptr" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 %get_cur_task, i64* %"@x_ptr", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_ptr", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_elapsed.cpp000066400000000000000000000044011361633214400217310ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_elapsed) { test("i:s:1 { @ = elapsed; }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"interval:s:1"(i8* nocapture readnone) local_unnamed_addr section "s_interval:s:1_1" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca i64, align 8 %elapsed_key = alloca i64, align 8 %1 = bitcast i64* %elapsed_key to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %elapsed_key, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo, i64* nonnull %elapsed_key) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %2 = load i64, i64* %cast, align 8 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %2, %lookup_success ], [ 0, %entry ] %get_ns = call i64 inttoptr (i64 5 to i64 ()*)() %3 = sub i64 %get_ns, %lookup_elem_val.0 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %4 = bitcast i64* %"@_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@_key", align 8 %5 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 %3, i64* %"@_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_func.cpp000066400000000000000000000062651361633214400212610ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_func) { test("kprobe:f { @x = func }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = getelementptr i8, i8* %0, i64 128 %func = load i64, i8* %1, align 8 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 %func, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } TEST(codegen, builtin_func_uprobe) { test("uprobe:/bin/sh:f { @x = func }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"uprobe:/bin/sh:f"(i8* nocapture readonly) local_unnamed_addr section "s_uprobe:/bin/sh:f_1" { entry: %"@x_val" = alloca [16 x i8], align 8 %"@x_key" = alloca i64, align 8 %func1 = alloca [16 x i8], align 8 %1 = getelementptr i8, i8* %0, i64 128 %func = load i64, i8* %1, align 8 %2 = getelementptr inbounds [16 x i8], [16 x i8]* %func1, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %3 = lshr i64 %get_pid_tgid, 32 %4 = getelementptr inbounds [16 x i8], [16 x i8]* %func1, i64 0, i64 8 store i64 %func, [16 x i8]* %func1, align 8 store i64 %3, i8* %4, align 8 %5 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@x_key", align 8 %6 = getelementptr inbounds [16 x i8], [16 x i8]* %"@x_val", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store [16 x i8]* %func1, [16 x i8]* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [16 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [16 x i8]* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_func_wild.cpp000066400000000000000000000030531361633214400222700ustar00rootroot00000000000000#include "common.h" #include "../mocks.h" namespace bpftrace { namespace test { namespace codegen { using ::testing::Return; TEST(codegen, builtin_func_wild) { auto bpftrace = get_mock_bpftrace(); test(*bpftrace, "kprobe:do_execve* { @x = func }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:do_execve*"(i8* nocapture readonly) local_unnamed_addr section "s_kprobe:do_execve*_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = getelementptr i8, i8* %0, i64 128 %func = load i64, i8* %1, align 8 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 %func, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_kstack.cpp000066400000000000000000000027541361633214400216050ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_kstack) { test("kprobe:f { @x = kstack }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2) %get_stackid = tail call i64 inttoptr (i64 27 to i64 (i8*, i8*, i64)*)(i8* %0, i64 %pseudo, i64 0) %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 %get_stackid, i64* %"@x_val", align 8 %pseudo1 = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_nsecs.cpp000066400000000000000000000026201361633214400214300ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_nsecs) { test("kprobe:f { @x = nsecs }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %get_ns = tail call i64 inttoptr (i64 5 to i64 ()*)() %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 %get_ns, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_pid_tid.cpp000066400000000000000000000042711361633214400217350ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_pid_tid) { test("kprobe:f { @x = pid; @y = tid }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@y_val" = alloca i64, align 8 %"@y_key" = alloca i64, align 8 %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %1 = lshr i64 %get_pid_tgid, 32 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 %1, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %get_pid_tgid1 = call i64 inttoptr (i64 14 to i64 ()*)() %4 = and i64 %get_pid_tgid1, 4294967295 %5 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@y_key", align 8 %6 = bitcast i64* %"@y_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %4, i64* %"@y_val", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem3 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo2, i64* nonnull %"@y_key", i64* nonnull %"@y_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_probe.cpp000066400000000000000000000027301361633214400214260ustar00rootroot00000000000000#include "common.h" #include "../mocks.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_probe) { auto bpftrace = get_mock_bpftrace(); test(*bpftrace, "tracepoint:sched:sched_one { @x = probe }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:sched:sched_one"(i8* nocapture readnone) local_unnamed_addr section "s_tracepoint:sched:sched_one_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_probe_wild.cpp000066400000000000000000000027671361633214400224570ustar00rootroot00000000000000#include "common.h" #include "../mocks.h" namespace bpftrace { namespace test { namespace codegen { using ::testing::Return; TEST(codegen, builtin_probe_wild) { auto bpftrace = get_mock_bpftrace(); test(*bpftrace, "tracepoint:sched:sched_on* { @x = probe }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:sched:sched_one"(i8* nocapture readnone) local_unnamed_addr section "s_tracepoint:sched:sched_one_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_rand.cpp000066400000000000000000000026261361633214400212470ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_rand) { test("kprobe:f { @x = rand }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %get_random = tail call i64 inttoptr (i64 7 to i64 ()*)() %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 %get_random, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_retval.cpp000066400000000000000000000026611361633214400216170ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_retval) { test("kretprobe:f { @x = retval }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kretprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kretprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = getelementptr i8, i8* %0, i64 80 %retval = load i64, i8* %1, align 8 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 %retval, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_sarg.cpp000066400000000000000000000054601361633214400212560ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_sarg) { test("kprobe:f { @x = sarg0; @y = sarg2 }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@y_val" = alloca i64, align 8 %"@y_key" = alloca i64, align 8 %sarg2 = alloca i64, align 8 %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %sarg0 = alloca i64, align 8 %1 = getelementptr i8, i8* %0, i64 152 %reg_sp = load i64, i8* %1, align 8 %2 = bitcast i64* %sarg0 to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %3 = add i64 %reg_sp, 8 %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %sarg0, i64 8, i64 %3) %4 = load i64, i64* %sarg0, align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %5 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@x_key", align 8 %6 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %4, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) %reg_sp1 = load i64, i8* %1, align 8 %7 = bitcast i64* %sarg2 to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) %8 = add i64 %reg_sp1, 24 %probe_read2 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %sarg2, i64 8, i64 %8) %9 = load i64, i64* %sarg2, align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) %10 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %10) store i64 0, i64* %"@y_key", align 8 %11 = bitcast i64* %"@y_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %11) store i64 %9, i64* %"@y_val", align 8 %pseudo3 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem4 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo3, i64* nonnull %"@y_key", i64* nonnull %"@y_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %10) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %11) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_uid_gid.cpp000066400000000000000000000042651361633214400217300ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_uid_gid) { test("kprobe:f { @x = uid; @y = gid }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@y_val" = alloca i64, align 8 %"@y_key" = alloca i64, align 8 %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %get_uid_gid = tail call i64 inttoptr (i64 15 to i64 ()*)() %1 = and i64 %get_uid_gid, 4294967295 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 %1, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %get_uid_gid1 = call i64 inttoptr (i64 15 to i64 ()*)() %4 = lshr i64 %get_uid_gid1, 32 %5 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@y_key", align 8 %6 = bitcast i64* %"@y_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %4, i64* %"@y_val", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem3 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo2, i64* nonnull %"@y_key", i64* nonnull %"@y_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_username.cpp000066400000000000000000000042721361633214400221410ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_username) { test("kprobe:f { @x = username; @y = gid}", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@y_val" = alloca i64, align 8 %"@y_key" = alloca i64, align 8 %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %get_uid_gid = tail call i64 inttoptr (i64 15 to i64 ()*)() %1 = and i64 %get_uid_gid, 4294967295 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 %1, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %get_uid_gid1 = call i64 inttoptr (i64 15 to i64 ()*)() %4 = lshr i64 %get_uid_gid1, 32 %5 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@y_key", align 8 %6 = bitcast i64* %"@y_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %4, i64* %"@y_val", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem3 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo2, i64* nonnull %"@y_key", i64* nonnull %"@y_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/builtin_ustack.cpp000066400000000000000000000031431361633214400216100ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, builtin_ustack) { test("kprobe:f { @x = ustack }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2) %get_stackid = tail call i64 inttoptr (i64 27 to i64 (i8*, i8*, i64)*)(i8* %0, i64 %pseudo, i64 256) %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %1 = shl i64 %get_pid_tgid, 32 %2 = or i64 %1, %get_stackid %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %4 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %2, i64* %"@x_val", align 8 %pseudo1 = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_avg.cpp000066400000000000000000000065731361633214400203520ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_avg) { test("kprobe:f { @x = avg(pid) }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key2" = alloca i64, align 8 %"@x_num" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo, i64* nonnull %"@x_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %2 = load i64, i64* %cast, align 8 %phitmp = add i64 %2, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %3 = bitcast i64* %"@x_num" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 %lookup_elem_val.0, i64* %"@x_num", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@x_key", i64* nonnull %"@x_num", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %4 = bitcast i64* %"@x_key2" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 1, i64* %"@x_key2", align 8 %pseudo3 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem4 = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo3, i64* nonnull %"@x_key2") %map_lookup_cond9 = icmp eq i8* %lookup_elem4, null br i1 %map_lookup_cond9, label %lookup_merge7, label %lookup_success5 lookup_success5: ; preds = %lookup_merge %cast10 = bitcast i8* %lookup_elem4 to i64* %5 = load i64, i64* %cast10, align 8 br label %lookup_merge7 lookup_merge7: ; preds = %lookup_merge, %lookup_success5 %lookup_elem_val8.0 = phi i64 [ %5, %lookup_success5 ], [ 0, %lookup_merge ] %6 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) %get_pid_tgid = call i64 inttoptr (i64 14 to i64 ()*)() %7 = lshr i64 %get_pid_tgid, 32 %8 = add i64 %7, %lookup_elem_val8.0 store i64 %8, i64* %"@x_val", align 8 %pseudo11 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem12 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo11, i64* nonnull %"@x_key2", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_cat.cpp000066400000000000000000000024061361633214400203330ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_cat) { test("kprobe:f { cat(\"/proc/loadavg\"); }", R"EXPECTED(%cat_t = type { i64 } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %cat_args = alloca %cat_t, align 8 %1 = bitcast %cat_t* %cat_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = getelementptr inbounds %cat_t, %cat_t* %cat_args, i64 0, i32 0 store i64 20000, i64* %2, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %cat_t*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %cat_t* nonnull %cat_args, i64 8) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_cgroup.cpp000066400000000000000000000035101361633214400210600ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_cgroup) { test("tracepoint:syscalls:sys_enter_openat /cgroup == 0x100000001/ { @x = " "cgroup }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:syscalls:sys_enter_openat"(i8* nocapture readnone) local_unnamed_addr section "s_tracepoint:syscalls:sys_enter_openat_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %get_cgroup_id = tail call i64 inttoptr (i64 80 to i64 ()*)() %1 = icmp eq i64 %get_cgroup_id, 4294967297 br i1 %1, label %pred_true, label %pred_false pred_false: ; preds = %entry ret i64 0 pred_true: ; preds = %entry %get_cgroup_id1 = tail call i64 inttoptr (i64 80 to i64 ()*)() %2 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 %get_cgroup_id1, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_clear.cpp000066400000000000000000000046621361633214400206600ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_clear) { test("BEGIN { @x = 1; } kprobe:f { clear(@x); }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 1, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %perfdata = alloca [11 x i8], align 8 %1 = getelementptr inbounds [11 x i8], [11 x i8]* %perfdata, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 30002, [11 x i8]* %perfdata, align 8 %str.sroa.0.0..sroa_idx = getelementptr inbounds [11 x i8], [11 x i8]* %perfdata, i64 0, i64 8 store i8 64, i8* %str.sroa.0.0..sroa_idx, align 8 %str.sroa.4.0..sroa_idx = getelementptr inbounds [11 x i8], [11 x i8]* %perfdata, i64 0, i64 9 store i8 120, i8* %str.sroa.4.0..sroa_idx, align 1 %str.sroa.5.0..sroa_idx = getelementptr inbounds [11 x i8], [11 x i8]* %perfdata, i64 0, i64 10 store i8 0, i8* %str.sroa.5.0..sroa_idx, align 2 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, [11 x i8]*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, [11 x i8]* nonnull %perfdata, i64 11) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_count.cpp000066400000000000000000000037341361633214400207210ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_count) { test("kprobe:f { @x = count() }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo, i64* nonnull %"@x_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %2 = load i64, i64* %cast, align 8 %phitmp = add i64 %2, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 %lookup_elem_val.0, i64* %"@x_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_delete.cpp000066400000000000000000000033351361633214400210300ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_delete) { test("kprobe:f { @x = 1; delete(@x) }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key1" = alloca i64, align 8 %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 1, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %3 = bitcast i64* %"@x_key1" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key1", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %delete_elem = call i64 inttoptr (i64 3 to i64 (i8*, i8*)*)(i64 %pseudo2, i64* nonnull %"@x_key1") call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_exit.cpp000066400000000000000000000023151361633214400205340ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_exit) { test("kprobe:f { exit() }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %perfdata = alloca [8 x i8], align 8 %1 = getelementptr inbounds [8 x i8], [8 x i8]* %perfdata, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 30000, [8 x i8]* %perfdata, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, [8 x i8]*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, [8 x i8]* nonnull %perfdata, i64 8) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_hist.cpp000066400000000000000000000060311361633214400205310ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_hist) { test("kprobe:f { @x = hist(pid) }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %1 = lshr i64 %get_pid_tgid, 32 %2 = icmp eq i64 %1, 0 br i1 %2, label %log2.exit, label %hist.is_not_zero.i hist.is_not_zero.i: ; preds = %entry %3 = icmp ugt i64 %get_pid_tgid, 281474976710655 %4 = zext i1 %3 to i64 %5 = shl nuw nsw i64 %4, 4 %6 = lshr i64 %1, %5 %7 = icmp sgt i64 %6, 255 %8 = zext i1 %7 to i64 %9 = shl nuw nsw i64 %8, 3 %10 = lshr i64 %6, %9 %11 = icmp sgt i64 %10, 15 %12 = zext i1 %11 to i64 %13 = shl nuw nsw i64 %12, 2 %14 = lshr i64 %10, %13 %15 = or i64 %5, %9 %16 = or i64 %15, %13 %17 = or i64 %16, 2 %18 = icmp sgt i64 %14, 3 %19 = zext i1 %18 to i64 %20 = shl nuw nsw i64 %19, 1 %21 = lshr i64 %14, %20 %22 = add nuw nsw i64 %20, %17 %23 = icmp sgt i64 %21, 1 %24 = zext i1 %23 to i64 %25 = or i64 %22, %24 br label %log2.exit log2.exit: ; preds = %entry, %hist.is_not_zero.i %log22 = phi i64 [ %25, %hist.is_not_zero.i ], [ 1, %entry ] %26 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %26) store i64 %log22, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo, i64* nonnull %"@x_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %log2.exit %cast = bitcast i8* %lookup_elem to i64* %27 = load i64, i64* %cast, align 8 %phitmp = add i64 %27, 1 br label %lookup_merge lookup_merge: ; preds = %log2.exit, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %log2.exit ] %28 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %28) store i64 %lookup_elem_val.0, i64* %"@x_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %26) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %28) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_kstack.cpp000066400000000000000000000102521361633214400210420ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_kstack) { auto result = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@y_val" = alloca i64, align 8 %"@y_key" = alloca i64, align 8 %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 4) %get_stackid = tail call i64 inttoptr (i64 27 to i64 (i8*, i8*, i64)*)(i8* %0, i64 %pseudo, i64 0) %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 %get_stackid, i64* %"@x_val", align 8 %pseudo1 = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 3) %get_stackid3 = call i64 inttoptr (i64 27 to i64 (i8*, i8*, i64)*)(i8* %0, i64 %pseudo2, i64 0) %3 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@y_key", align 8 %4 = bitcast i64* %"@y_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %get_stackid3, i64* %"@y_val", align 8 %pseudo4 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem5 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo4, i64* nonnull %"@y_key", i64* nonnull %"@y_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; // Mode doesn't directly affect codegen, so the programs below should // generate the same IR test("kprobe:f { @x = kstack(); @y = kstack(6) }", result); test("kprobe:f { @x = kstack(perf); @y = kstack(perf, 6) }", result); test("kprobe:f { @x = kstack(perf); @y = kstack(bpftrace) }", result); } TEST(codegen, call_kstack_mapids) { BPFtrace bpftrace; Driver driver(bpftrace); FakeMap::next_mapfd_ = 1; ASSERT_EQ(driver.parse_str("kprobe:f { @x = kstack(5); @y = kstack(6); @z = kstack(6) }"), 0); ClangParser clang; clang.parse(driver.root_, bpftrace); MockBPFfeature feature; ast::SemanticAnalyser semantics(driver.root_, bpftrace, feature); ASSERT_EQ(semantics.analyse(), 0); ASSERT_EQ(semantics.create_maps(true), 0); ast::CodegenLLVM codegen(driver.root_, bpftrace); codegen.compile(); ASSERT_EQ(FakeMap::next_mapfd_, 7); ASSERT_EQ(bpftrace.stackid_maps_.size(), 2U); StackType stack_type; stack_type.limit = 5; ASSERT_EQ(bpftrace.stackid_maps_.count(stack_type), 1U); stack_type.limit = 6; ASSERT_EQ(bpftrace.stackid_maps_.count(stack_type), 1U); } TEST(codegen, call_kstack_modes_mapids) { BPFtrace bpftrace; Driver driver(bpftrace); FakeMap::next_mapfd_ = 1; ASSERT_EQ(driver.parse_str("kprobe:f { @x = kstack(perf); @y = kstack(bpftrace); @z = kstack() }"), 0); ClangParser clang; clang.parse(driver.root_, bpftrace); MockBPFfeature feature; ast::SemanticAnalyser semantics(driver.root_, bpftrace, feature); ASSERT_EQ(semantics.analyse(), 0); ASSERT_EQ(semantics.create_maps(true), 0); ast::CodegenLLVM codegen(driver.root_, bpftrace); codegen.compile(); ASSERT_EQ(FakeMap::next_mapfd_, 7); ASSERT_EQ(bpftrace.stackid_maps_.size(), 2U); StackType stack_type; stack_type.mode = StackMode::perf; ASSERT_EQ(bpftrace.stackid_maps_.count(stack_type), 1U); stack_type.mode = StackMode::bpftrace; ASSERT_EQ(bpftrace.stackid_maps_.count(stack_type), 1U); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_lhist.cpp000066400000000000000000000044141361633214400207100ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_lhist) { test("kprobe:f { @x = lhist(pid, 0, 100, 1) }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %get_pid_tgid1 = tail call i64 inttoptr (i64 14 to i64 ()*)() %1 = lshr i64 %get_pid_tgid1, 32 %2 = icmp ugt i64 %get_pid_tgid1, 433791696895 %3 = add nuw nsw i64 %1, 1 %linear3 = select i1 %2, i64 101, i64 %3 %4 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %linear3, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo, i64* nonnull %"@x_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %5 = load i64, i64* %cast, align 8 %phitmp = add i64 %5, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %6 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %lookup_elem_val.0, i64* %"@x_val", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo2, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_max.cpp000066400000000000000000000044161361633214400203540ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_max) { test("kprobe:f { @x = max(pid) }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo, i64* nonnull %"@x_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %2 = load i64, i64* %cast, align 8 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %2, %lookup_success ], [ 0, %entry ] %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %get_pid_tgid = call i64 inttoptr (i64 14 to i64 ()*)() %4 = lshr i64 %get_pid_tgid, 32 %5 = icmp slt i64 %4, %lookup_elem_val.0 br i1 %5, label %min.lt, label %min.ge min.lt: ; preds = %lookup_merge, %min.ge call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 min.ge: ; preds = %lookup_merge store i64 %4, i64* %"@x_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) br label %min.lt } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_min.cpp000066400000000000000000000044541361633214400203540ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_min) { test("kprobe:f { @x = min(pid) }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo, i64* nonnull %"@x_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %2 = load i64, i64* %cast, align 8 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %2, %lookup_success ], [ 0, %entry ] %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %get_pid_tgid = call i64 inttoptr (i64 14 to i64 ()*)() %4 = lshr i64 %get_pid_tgid, 32 %5 = xor i64 %4, 4294967295 %6 = icmp slt i64 %5, %lookup_elem_val.0 br i1 %6, label %min.lt, label %min.ge min.lt: ; preds = %lookup_merge, %min.ge call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 min.ge: ; preds = %lookup_merge store i64 %5, i64* %"@x_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) br label %min.lt } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_ntop_char16.cpp000066400000000000000000000121331361633214400217060ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_ntop_char16) { test("struct inet { unsigned char addr[16] } kprobe:f { @x = ntop(((struct " "inet*)0)->addr); }", #if LLVM_VERSION_MAJOR < 6 R"EXPECTED(%inet_t = type { i64, [16 x i8] } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %inet = alloca %inet_t, align 8 %1 = bitcast %inet_t* %inet to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 10, %inet_t* %inet, align 8 %2 = getelementptr inbounds %inet_t, %inet_t* %inet, i64 0, i32 1 %3 = getelementptr inbounds [16 x i8], [16 x i8]* %2, i64 0, i64 0 call void @llvm.memset.p0i8.i64(i8* %3, i8 0, i64 16, i32 8, i1 false) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([16 x i8]* %2, i64 16, i64 0) %4 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, %inet_t*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", %inet_t* nonnull %inet, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #elif LLVM_VERSION_MAJOR > 6 R"EXPECTED(%inet_t = type { i64, [16 x i8] } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %inet = alloca %inet_t, align 8 %1 = bitcast %inet_t* %inet to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 10, %inet_t* %inet, align 8 %2 = getelementptr inbounds %inet_t, %inet_t* %inet, i64 0, i32 1 %3 = getelementptr inbounds [16 x i8], [16 x i8]* %2, i64 0, i64 0 call void @llvm.memset.p0i8.i64(i8* nonnull align 8 %3, i8 0, i64 16, i1 false) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([16 x i8]* nonnull %2, i64 16, i64 0) %4 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, %inet_t*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", %inet_t* nonnull %inet, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(%inet_t = type { i64, [16 x i8] } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %inet = alloca %inet_t, align 8 %1 = bitcast %inet_t* %inet to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 10, %inet_t* %inet, align 8 %2 = getelementptr inbounds %inet_t, %inet_t* %inet, i64 0, i32 1 %3 = getelementptr inbounds [16 x i8], [16 x i8]* %2, i64 0, i64 0 call void @llvm.memset.p0i8.i64(i8* nonnull %3, i8 0, i64 16, i32 8, i1 false) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([16 x i8]* nonnull %2, i64 16, i64 0) %4 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, %inet_t*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", %inet_t* nonnull %inet, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_ntop_char4.cpp000066400000000000000000000121231361633214400216220ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_ntop_char4) { test("struct inet { unsigned char addr[4] } kprobe:f { @x = ntop(((struct " "inet*)0)->addr); }", #if LLVM_VERSION_MAJOR < 6 R"EXPECTED(%inet_t = type { i64, [16 x i8] } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %inet = alloca %inet_t, align 8 %1 = bitcast %inet_t* %inet to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 2, %inet_t* %inet, align 8 %2 = getelementptr inbounds %inet_t, %inet_t* %inet, i64 0, i32 1 %3 = getelementptr inbounds [16 x i8], [16 x i8]* %2, i64 0, i64 0 call void @llvm.memset.p0i8.i64(i8* %3, i8 0, i64 16, i32 8, i1 false) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([16 x i8]* %2, i64 4, i64 0) %4 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, %inet_t*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", %inet_t* nonnull %inet, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #elif LLVM_VERSION_MAJOR > 6 R"EXPECTED(%inet_t = type { i64, [16 x i8] } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %inet = alloca %inet_t, align 8 %1 = bitcast %inet_t* %inet to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 2, %inet_t* %inet, align 8 %2 = getelementptr inbounds %inet_t, %inet_t* %inet, i64 0, i32 1 %3 = getelementptr inbounds [16 x i8], [16 x i8]* %2, i64 0, i64 0 call void @llvm.memset.p0i8.i64(i8* nonnull align 8 %3, i8 0, i64 16, i1 false) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([16 x i8]* nonnull %2, i64 4, i64 0) %4 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, %inet_t*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", %inet_t* nonnull %inet, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(%inet_t = type { i64, [16 x i8] } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %inet = alloca %inet_t, align 8 %1 = bitcast %inet_t* %inet to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 2, %inet_t* %inet, align 8 %2 = getelementptr inbounds %inet_t, %inet_t* %inet, i64 0, i32 1 %3 = getelementptr inbounds [16 x i8], [16 x i8]* %2, i64 0, i64 0 call void @llvm.memset.p0i8.i64(i8* nonnull %3, i8 0, i64 16, i32 8, i1 false) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([16 x i8]* nonnull %2, i64 4, i64 0) %4 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, %inet_t*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", %inet_t* nonnull %inet, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_ntop_key.cpp000066400000000000000000000154421361633214400214200ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_ntop_key) { test("kprobe:f { @x[ntop(2, 0xFFFFFFFF)] = count()}", #if LLVM_VERSION_MAJOR < 6 R"EXPECTED(%inet_t = type { i64, [16 x i8] } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %inet = alloca %inet_t, align 8 %1 = bitcast %inet_t* %inet to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 2, %inet_t* %inet, align 8 %2 = getelementptr inbounds %inet_t, %inet_t* %inet, i64 0, i32 1 %3 = getelementptr inbounds [16 x i8], [16 x i8]* %2, i64 0, i64 0 call void @llvm.memset.p0i8.i64(i8* %3, i8 0, i64 16, i32 8, i1 false) store i32 -1, [16 x i8]* %2, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, %inet_t*)*)(i64 %pseudo, %inet_t* nonnull %inet) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %4 = load i64, i64* %cast, align 8 %phitmp = add i64 %4, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %5 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 %lookup_elem_val.0, i64* %"@x_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, %inet_t*, i64*, i64)*)(i64 %pseudo1, %inet_t* nonnull %inet, i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #elif LLVM_VERSION_MAJOR > 6 R"EXPECTED(%inet_t = type { i64, [16 x i8] } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %inet = alloca %inet_t, align 8 %1 = bitcast %inet_t* %inet to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 2, %inet_t* %inet, align 8 %2 = getelementptr inbounds %inet_t, %inet_t* %inet, i64 0, i32 1 %3 = getelementptr inbounds [16 x i8], [16 x i8]* %2, i64 0, i64 0 call void @llvm.memset.p0i8.i64(i8* nonnull align 8 %3, i8 0, i64 16, i1 false) store i32 -1, [16 x i8]* %2, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, %inet_t*)*)(i64 %pseudo, %inet_t* nonnull %inet) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %4 = load i64, i64* %cast, align 8 %phitmp = add i64 %4, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %5 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 %lookup_elem_val.0, i64* %"@x_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, %inet_t*, i64*, i64)*)(i64 %pseudo1, %inet_t* nonnull %inet, i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(%inet_t = type { i64, [16 x i8] } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %inet = alloca %inet_t, align 8 %1 = bitcast %inet_t* %inet to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 2, %inet_t* %inet, align 8 %2 = getelementptr inbounds %inet_t, %inet_t* %inet, i64 0, i32 1 %3 = getelementptr inbounds [16 x i8], [16 x i8]* %2, i64 0, i64 0 call void @llvm.memset.p0i8.i64(i8* nonnull %3, i8 0, i64 16, i32 8, i1 false) store i32 -1, [16 x i8]* %2, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, %inet_t*)*)(i64 %pseudo, %inet_t* nonnull %inet) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %4 = load i64, i64* %cast, align 8 %phitmp = add i64 %4, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %5 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 %lookup_elem_val.0, i64* %"@x_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, %inet_t*, i64*, i64)*)(i64 %pseudo1, %inet_t* nonnull %inet, i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_override_return.cpp000066400000000000000000000010151361633214400227750ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_override) { test( "kprobe:f { override(arg0); }", R"EXPECTED(define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %1 = getelementptr i8, i8* %0, i64 112 %arg0 = load i64, i8* %1, align 8 %override = tail call i64 inttoptr (i64 58 to i64 (i8*, i64)*)(i8* %0, i64 %arg0) ret i64 0 } )EXPECTED", false); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_override_return_literal.cpp000066400000000000000000000007031361633214400245140ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_override_literal) { test( "kprobe:f { override(-1); }", R"EXPECTED(define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %override = tail call i64 inttoptr (i64 58 to i64 (i8*, i64)*)(i8* %0, i64 -1) ret i64 0 } )EXPECTED", false); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_print.cpp000066400000000000000000000173071361633214400207260ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_print) { test("BEGIN { @x = 1; } kprobe:f { print(@x); }", #if LLVM_VERSION_MAJOR > 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 1, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %perfdata = alloca [27 x i8], align 8 %1 = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 30001, [27 x i8]* %perfdata, align 8 %2 = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 8 %str.sroa.0.0..sroa_idx = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 24 call void @llvm.memset.p0i8.i64(i8* nonnull align 8 %2, i8 0, i64 16, i1 false) store i8 64, i8* %str.sroa.0.0..sroa_idx, align 8 %str.sroa.4.0..sroa_idx = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 25 store i8 120, i8* %str.sroa.4.0..sroa_idx, align 1 %str.sroa.5.0..sroa_idx = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 26 store i8 0, i8* %str.sroa.5.0..sroa_idx, align 2 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, [27 x i8]*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, [27 x i8]* nonnull %perfdata, i64 27) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #elif LLVM_VERSION_MAJOR == 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 1, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %perfdata = alloca [27 x i8], align 8 %1 = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 30001, [27 x i8]* %perfdata, align 8 %2 = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 8 %str.sroa.0.0..sroa_idx = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 24 call void @llvm.memset.p0i8.i64(i8* nonnull %2, i8 0, i64 16, i32 8, i1 false) store i8 64, i8* %str.sroa.0.0..sroa_idx, align 8 %str.sroa.4.0..sroa_idx = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 25 store i8 120, i8* %str.sroa.4.0..sroa_idx, align 1 %str.sroa.5.0..sroa_idx = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 26 store i8 0, i8* %str.sroa.5.0..sroa_idx, align 2 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, [27 x i8]*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, [27 x i8]* nonnull %perfdata, i64 27) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 1, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %perfdata = alloca [27 x i8], align 8 %1 = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 30001, [27 x i8]* %perfdata, align 8 %2 = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 8 %str.sroa.0.0..sroa_idx = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 24 call void @llvm.memset.p0i8.i64(i8* %2, i8 0, i64 16, i32 8, i1 false) store i8 64, i8* %str.sroa.0.0..sroa_idx, align 8 %str.sroa.4.0..sroa_idx = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 25 store i8 120, i8* %str.sroa.4.0..sroa_idx, align 1 %str.sroa.5.0..sroa_idx = getelementptr inbounds [27 x i8], [27 x i8]* %perfdata, i64 0, i64 26 store i8 0, i8* %str.sroa.5.0..sroa_idx, align 2 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, [27 x i8]*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, [27 x i8]* nonnull %perfdata, i64 27) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_printf.cpp000066400000000000000000000110431361633214400210630ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_printf) { test("struct Foo { char c; long l; } kprobe:f { $foo = (struct Foo*)0; printf(\"%c %lu\\n\", $foo->c, $foo->l) }", #if LLVM_VERSION_MAJOR < 7 R"EXPECTED(%printf_t = type { i64, i64, i64 } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %"struct Foo.l" = alloca i64, align 8 %"struct Foo.c" = alloca i8, align 1 %printf_args = alloca %printf_t, align 8 %1 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = bitcast %printf_t* %printf_args to i8* call void @llvm.memset.p0i8.i64(i8* nonnull %2, i8 0, i64 16, i32 8, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %"struct Foo.c") %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %"struct Foo.c", i64 1, i64 0) %3 = load i8, i8* %"struct Foo.c", align 1 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %"struct Foo.c") %4 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1 store i8 %3, i64* %4, align 8 %5 = bitcast i64* %"struct Foo.l" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) %probe_read1 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.l", i64 8, i64 8) %6 = load i64, i64* %"struct Foo.l", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) %7 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 2 store i64 %6, i64* %7, align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 24) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(%printf_t = type { i64, i64, i64 } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %"struct Foo.l" = alloca i64, align 8 %"struct Foo.c" = alloca i8, align 1 %printf_args = alloca %printf_t, align 8 %1 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = bitcast %printf_t* %printf_args to i8* call void @llvm.memset.p0i8.i64(i8* nonnull align 8 %2, i8 0, i64 16, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %"struct Foo.c") %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %"struct Foo.c", i64 1, i64 0) %3 = load i8, i8* %"struct Foo.c", align 1 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %"struct Foo.c") %4 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1 store i8 %3, i64* %4, align 8 %5 = bitcast i64* %"struct Foo.l" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) %probe_read1 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.l", i64 8, i64 8) %6 = load i64, i64* %"struct Foo.l", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) %7 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 2 store i64 %6, i64* %7, align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 24) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_reg.cpp000066400000000000000000000027371361633214400203500ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_reg) // Identical to builtin_func apart from variable names { test("kprobe:f { @x = reg(\"ip\") }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = getelementptr i8, i8* %0, i64 128 %reg_ip = load i64, i8* %1, align 8 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 %reg_ip, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_signal.cpp000066400000000000000000000010201361633214400210300ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_signal) { test("k:f { signal(arg0); }", R"EXPECTED(define i64 @"kprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kprobe:f_1" { entry: %1 = getelementptr i8, i8* %0, i64 112 %arg0 = load i64, i8* %1, align 8 %2 = trunc i64 %arg0 to i32 %signal = tail call i64 inttoptr (i64 109 to i64 (i32)*)(i32 %2) ret i64 0 } )EXPECTED", false); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_signal_literal.cpp000066400000000000000000000006511361633214400225550ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_signal_literal) { test("k:f { signal(8); }", R"EXPECTED(define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %signal = tail call i64 inttoptr (i64 109 to i64 (i32)*)(i32 8) ret i64 0 } )EXPECTED", false); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_signal_string_literal.cpp000066400000000000000000000006721361633214400241460ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_signal_string_literal) { test("k:f { signal(\"SIGKILL\"); }", R"EXPECTED(define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %signal = tail call i64 inttoptr (i64 109 to i64 (i32)*)(i32 9) ret i64 0 } )EXPECTED", false); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_stats.cpp000066400000000000000000000065771361633214400207370ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_stats) { test("kprobe:f { @x = stats(pid) }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key2" = alloca i64, align 8 %"@x_num" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo, i64* nonnull %"@x_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %2 = load i64, i64* %cast, align 8 %phitmp = add i64 %2, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %3 = bitcast i64* %"@x_num" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 %lookup_elem_val.0, i64* %"@x_num", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@x_key", i64* nonnull %"@x_num", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %4 = bitcast i64* %"@x_key2" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 1, i64* %"@x_key2", align 8 %pseudo3 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem4 = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo3, i64* nonnull %"@x_key2") %map_lookup_cond9 = icmp eq i8* %lookup_elem4, null br i1 %map_lookup_cond9, label %lookup_merge7, label %lookup_success5 lookup_success5: ; preds = %lookup_merge %cast10 = bitcast i8* %lookup_elem4 to i64* %5 = load i64, i64* %cast10, align 8 br label %lookup_merge7 lookup_merge7: ; preds = %lookup_merge, %lookup_success5 %lookup_elem_val8.0 = phi i64 [ %5, %lookup_success5 ], [ 0, %lookup_merge ] %6 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) %get_pid_tgid = call i64 inttoptr (i64 14 to i64 ()*)() %7 = lshr i64 %get_pid_tgid, 32 %8 = add i64 %7, %lookup_elem_val8.0 store i64 %8, i64* %"@x_val", align 8 %pseudo11 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem12 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo11, i64* nonnull %"@x_key2", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_str.cpp000066400000000000000000000063751361633214400204050ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_str) { test("kprobe:f { @x = str(arg0) }", #if LLVM_VERSION_MAJOR < 7 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i8 0, i64 64, i32 1, i1 false) %2 = getelementptr i8, i8* %0, i64 112 %arg0 = load i64, i8* %2, align 8 %probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 64, i64 %arg0) %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %1, i8 0, i64 64, i1 false) %2 = getelementptr i8, i8* %0, i64 112 %arg0 = load i64, i8* %2, align 8 %probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 64, i64 %arg0) %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_str_2_expr.cpp000066400000000000000000000072061361633214400216560ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_str_2_expr) { test("kprobe:f { @x = str(arg0, arg1) }", #if LLVM_VERSION_MAJOR < 7 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %1 = getelementptr i8, i8* %0, i64 104 %arg1 = load i64, i8* %1, align 8 %2 = add i64 %arg1, 1 %3 = icmp ult i64 %2, 64 %str.min.select = select i1 %3, i64 %2, i64 64 %4 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) call void @llvm.memset.p0i8.i64(i8* nonnull %4, i8 0, i64 64, i32 1, i1 false) %5 = getelementptr i8, i8* %0, i64 112 %arg0 = load i64, i8* %5, align 8 %probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 %str.min.select, i64 %arg0) %6 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %1 = getelementptr i8, i8* %0, i64 104 %arg1 = load i64, i8* %1, align 8 %2 = add i64 %arg1, 1 %3 = icmp ult i64 %2, 64 %str.min.select = select i1 %3, i64 %2, i64 64 %4 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %4, i8 0, i64 64, i1 false) %5 = getelementptr i8, i8* %0, i64 112 %arg0 = load i64, i8* %5, align 8 %probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 %str.min.select, i64 %arg0) %6 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_str_2_lit.cpp000066400000000000000000000064041361633214400214670ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_str_2_lit) { test("kprobe:f { @x = str(arg0, 6) }", #if LLVM_VERSION_MAJOR < 7 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i8 0, i64 64, i32 1, i1 false) %2 = getelementptr i8, i8* %0, i64 112 %arg0 = load i64, i8* %2, align 8 %probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 7, i64 %arg0) %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %1, i8 0, i64 64, i1 false) %2 = getelementptr i8, i8* %0, i64 112 %arg0 = load i64, i8* %2, align 8 %probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 7, i64 %arg0) %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_sum.cpp000066400000000000000000000040561361633214400203730ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_sum) { test("kprobe:f { @x = sum(pid) }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo, i64* nonnull %"@x_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %2 = load i64, i64* %cast, align 8 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %2, %lookup_success ], [ 0, %entry ] %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %get_pid_tgid = call i64 inttoptr (i64 14 to i64 ()*)() %4 = lshr i64 %get_pid_tgid, 32 %5 = add i64 %4, %lookup_elem_val.0 store i64 %5, i64* %"@x_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_system.cpp000066400000000000000000000026521361633214400211130ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_system) { test(" kprobe:f { system(\"echo %d\", 100) }", R"EXPECTED(%system_t = type { i64, i64 } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %system_args = alloca %system_t, align 8 %1 = bitcast %system_t* %system_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = getelementptr inbounds %system_t, %system_t* %system_args, i64 0, i32 0 store i64 10000, i64* %2, align 8 %3 = getelementptr inbounds %system_t, %system_t* %system_args, i64 0, i32 1 store i64 100, i64* %3, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %system_t*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %system_t* nonnull %system_args, i64 16) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED", false); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_time.cpp000066400000000000000000000025001361633214400205150ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_time) { test("kprobe:f { time(); }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %perfdata = alloca [16 x i8], align 8 %1 = getelementptr inbounds [16 x i8], [16 x i8]* %perfdata, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 30004, [16 x i8]* %perfdata, align 8 %2 = getelementptr inbounds [16 x i8], [16 x i8]* %perfdata, i64 0, i64 8 store i64 0, i8* %2, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, [16 x i8]*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, [16 x i8]* nonnull %perfdata, i64 16) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_ustack.cpp000066400000000000000000000106141361633214400210560ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_ustack) { auto result = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@y_val" = alloca i64, align 8 %"@y_key" = alloca i64, align 8 %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 4) %get_stackid = tail call i64 inttoptr (i64 27 to i64 (i8*, i8*, i64)*)(i8* %0, i64 %pseudo, i64 256) %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %1 = shl i64 %get_pid_tgid, 32 %2 = or i64 %1, %get_stackid %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %4 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %2, i64* %"@x_val", align 8 %pseudo1 = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 3) %get_stackid3 = call i64 inttoptr (i64 27 to i64 (i8*, i8*, i64)*)(i8* %0, i64 %pseudo2, i64 256) %get_pid_tgid4 = call i64 inttoptr (i64 14 to i64 ()*)() %5 = shl i64 %get_pid_tgid4, 32 %6 = or i64 %5, %get_stackid3 %7 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 0, i64* %"@y_key", align 8 %8 = bitcast i64* %"@y_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %8) store i64 %6, i64* %"@y_val", align 8 %pseudo5 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem6 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo5, i64* nonnull %"@y_key", i64* nonnull %"@y_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %8) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; // Mode doesn't directly affect codegen, so both should generate the same // program test("kprobe:f { @x = ustack(); @y = ustack(6) }", result); test("kprobe:f { @x = ustack(perf); @y = ustack(perf, 6) }", result); test("kprobe:f { @x = ustack(perf); @y = ustack(bpftrace) }", result); } TEST(codegen, call_ustack_mapids) { BPFtrace bpftrace; Driver driver(bpftrace); FakeMap::next_mapfd_ = 1; ASSERT_EQ(driver.parse_str("kprobe:f { @x = ustack(5); @y = ustack(6); @z = ustack(6) }"), 0); ClangParser clang; clang.parse(driver.root_, bpftrace); MockBPFfeature feature; ast::SemanticAnalyser semantics(driver.root_, bpftrace, feature); ASSERT_EQ(semantics.analyse(), 0); ASSERT_EQ(semantics.create_maps(true), 0); ast::CodegenLLVM codegen(driver.root_, bpftrace); codegen.compile(); ASSERT_EQ(FakeMap::next_mapfd_, 7); ASSERT_EQ(bpftrace.stackid_maps_.size(), 2U); StackType stack_type; stack_type.limit = 5; ASSERT_EQ(bpftrace.stackid_maps_.count(stack_type), 1U); stack_type.limit = 6; ASSERT_EQ(bpftrace.stackid_maps_.count(stack_type), 1U); } TEST(codegen, call_ustack_modes_mapids) { BPFtrace bpftrace; Driver driver(bpftrace); FakeMap::next_mapfd_ = 1; ASSERT_EQ(driver.parse_str("kprobe:f { @x = ustack(perf); @y = ustack(bpftrace); @z = ustack() }"), 0); ClangParser clang; clang.parse(driver.root_, bpftrace); MockBPFfeature feature; ast::SemanticAnalyser semantics(driver.root_, bpftrace, feature); ASSERT_EQ(semantics.analyse(), 0); ASSERT_EQ(semantics.create_maps(true), 0); ast::CodegenLLVM codegen(driver.root_, bpftrace); codegen.compile(); ASSERT_EQ(FakeMap::next_mapfd_, 7); ASSERT_EQ(bpftrace.stackid_maps_.size(), 2U); StackType stack_type; stack_type.mode = StackMode::perf; ASSERT_EQ(bpftrace.stackid_maps_.count(stack_type), 1U); stack_type.mode = StackMode::bpftrace; ASSERT_EQ(bpftrace.stackid_maps_.count(stack_type), 1U); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_usym_key.cpp000066400000000000000000000043501361633214400214310ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_usym_key) { test("kprobe:f { @x[usym(0)] = count() }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %usym = alloca [16 x i8], align 8 %1 = getelementptr inbounds [16 x i8], [16 x i8]* %usym, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %2 = lshr i64 %get_pid_tgid, 32 %3 = getelementptr inbounds [16 x i8], [16 x i8]* %usym, i64 0, i64 8 store i64 0, [16 x i8]* %usym, align 8 store i64 %2, i8* %3, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [16 x i8]*)*)(i64 %pseudo, [16 x i8]* nonnull %usym) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %4 = load i64, i64* %cast, align 8 %phitmp = add i64 %4, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %5 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 %lookup_elem_val.0, i64* %"@x_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [16 x i8]*, i64*, i64)*)(i64 %pseudo1, [16 x i8]* nonnull %usym, i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/call_zero.cpp000066400000000000000000000046601361633214400205470ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, call_zero) { test("BEGIN { @x = 1; } kprobe:f { zero(@x); }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 1, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %perfdata = alloca [11 x i8], align 8 %1 = getelementptr inbounds [11 x i8], [11 x i8]* %perfdata, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 30003, [11 x i8]* %perfdata, align 8 %str.sroa.0.0..sroa_idx = getelementptr inbounds [11 x i8], [11 x i8]* %perfdata, i64 0, i64 8 store i8 64, i8* %str.sroa.0.0..sroa_idx, align 8 %str.sroa.4.0..sroa_idx = getelementptr inbounds [11 x i8], [11 x i8]* %perfdata, i64 0, i64 9 store i8 120, i8* %str.sroa.4.0..sroa_idx, align 1 %str.sroa.5.0..sroa_idx = getelementptr inbounds [11 x i8], [11 x i8]* %perfdata, i64 0, i64 10 store i8 0, i8* %str.sroa.5.0..sroa_idx, align 2 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, [11 x i8]*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, [11 x i8]* nonnull %perfdata, i64 11) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/common.h000066400000000000000000000031271361633214400175270ustar00rootroot00000000000000#pragma once #include "gtest/gtest.h" #include "gmock/gmock.h" #include "../mocks.h" #include "bpffeature.h" #include "bpforc.h" #include "bpftrace.h" #include "clang_parser.h" #include "codegen_llvm.h" #include "driver.h" #include "fake_map.h" #include "semantic_analyser.h" #include "tracepoint_format_parser.h" namespace bpftrace { namespace test { namespace codegen { const std::string header = R"HEAD(; ModuleID = 'bpftrace' source_filename = "bpftrace" target datalayout = "e-m:e-p:64:64-i64:64-n32:64-S128" target triple = "bpf-pc-linux" )HEAD"; static void test( BPFtrace &bpftrace, const std::string &input, const std::string &expected_output) { Driver driver(bpftrace); FakeMap::next_mapfd_ = 1; ASSERT_EQ(driver.parse_str(input), 0); ClangParser clang; clang.parse(driver.root_, bpftrace); ASSERT_EQ(driver.parse_str(input), 0); MockBPFfeature feature; ast::SemanticAnalyser semantics(driver.root_, bpftrace, feature); ASSERT_EQ(semantics.analyse(), 0); ASSERT_EQ(semantics.create_maps(true), 0); std::stringstream out; ast::CodegenLLVM codegen(driver.root_, bpftrace); codegen.compile(DebugLevel::kDebug, out); std::string full_expected_output = header + expected_output; EXPECT_EQ(full_expected_output, out.str()) << "the following program failed: '" << input << "'"; } static void test( const std::string &input, const std::string &expected_output, bool safe_mode = true) { BPFtrace bpftrace; bpftrace.safe_mode_ = safe_mode; test(bpftrace, input, expected_output); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/comparison_extend.cpp000066400000000000000000000027671361633214400223240ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, comparison_extend) { // Make sure i1 is zero extended test("kprobe:f { @ = 1 < arg0 }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca i64, align 8 %1 = getelementptr i8, i8* %0, i64 112 %arg0 = load i64, i8* %1, align 8 %2 = icmp ugt i64 %arg0, 1 %3 = zext i1 %2 to i64 %4 = bitcast i64* %"@_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@_key", align 8 %5 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 %3, i64* %"@_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/dereference.cpp000066400000000000000000000032251361633214400210400ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, dereference) { test("kprobe:f { @x = *1234 }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %deref = alloca i64, align 8 %1 = bitcast i64* %deref to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %deref, i64 8, i64 1234) %2 = load i64, i64* %deref, align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %4 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %2, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/empty_function.cpp000066400000000000000000000006731361633214400216400ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, empty_function) { test("kprobe:f { 1; }", R"EXPECTED(; Function Attrs: norecurse nounwind readnone define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr #0 section "s_kprobe:f_1" { entry: ret i64 0 } attributes #0 = { norecurse nounwind readnone } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/enum.cpp000066400000000000000000000040001361633214400175250ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, enum_declaration) { test("enum { a = 42, b } k:f { @a = a; @b = b }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@b_val" = alloca i64, align 8 %"@b_key" = alloca i64, align 8 %"@a_val" = alloca i64, align 8 %"@a_key" = alloca i64, align 8 %1 = bitcast i64* %"@a_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@a_key", align 8 %2 = bitcast i64* %"@a_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 42, i64* %"@a_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@a_key", i64* nonnull %"@a_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %3 = bitcast i64* %"@b_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@b_key", align 8 %4 = bitcast i64* %"@b_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 43, i64* %"@b_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem2 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@b_key", i64* nonnull %"@b_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/general.cpp000066400000000000000000000052021361633214400202030ustar00rootroot00000000000000#include "gmock/gmock.h" #include "gtest/gtest.h" #include "bpforc.h" #include "bpftrace.h" #include "clang_parser.h" #include "codegen_llvm.h" #include "driver.h" #include "semantic_analyser.h" #include "common.h" namespace bpftrace { namespace test { namespace codegen { using ::testing::_; class MockBPFtrace : public BPFtrace { public: MOCK_METHOD1(add_probe, int(ast::Probe &p)); }; TEST(codegen, populate_sections) { BPFtrace bpftrace; Driver driver(bpftrace); ASSERT_EQ(driver.parse_str("kprobe:foo { 1 } kprobe:bar { 1 }"), 0); MockBPFfeature feature; ast::SemanticAnalyser semantics(driver.root_, bpftrace, feature); ASSERT_EQ(semantics.analyse(), 0); std::stringstream out; ast::CodegenLLVM codegen(driver.root_, bpftrace); auto bpforc = codegen.compile(); // Check sections are populated EXPECT_EQ(bpforc->sections_.size(), 2U); EXPECT_EQ(bpforc->sections_.count("s_kprobe:foo_1"), 1U); EXPECT_EQ(bpforc->sections_.count("s_kprobe:bar_1"), 1U); } TEST(codegen, printf_offsets) { BPFtrace bpftrace; Driver driver(bpftrace); // TODO (mmarchini): also test printf with a string argument ASSERT_EQ(driver.parse_str("struct Foo { char c; int i; } kprobe:f { $foo = (struct Foo*)0; printf(\"%c %u\\n\", $foo->c, $foo->i) }"), 0); ClangParser clang; clang.parse(driver.root_, bpftrace); MockBPFfeature feature; ast::SemanticAnalyser semantics(driver.root_, bpftrace, feature); ASSERT_EQ(semantics.analyse(), 0); ASSERT_EQ(semantics.create_maps(true), 0); std::stringstream out; ast::CodegenLLVM codegen(driver.root_, bpftrace); auto bpforc = codegen.compile(); EXPECT_EQ(bpftrace.printf_args_.size(), 1U); auto &fmt = std::get<0>(bpftrace.printf_args_[0]); auto &args = std::get<1>(bpftrace.printf_args_[0]); EXPECT_EQ(fmt, "%c %u\n"); EXPECT_EQ(args.size(), 2U); // NOTE (mmarchini) type.size is the original arg size, and it might be // different from the actual size we use to store in memory EXPECT_EQ(args[0].type.type, Type::integer); EXPECT_EQ(args[0].type.size, 8U); EXPECT_EQ(args[0].offset, 8); EXPECT_EQ(args[1].type.type, Type::integer); EXPECT_EQ(args[1].type.size, 8U); EXPECT_EQ(args[1].offset, 16); } TEST(codegen, probe_count) { MockBPFtrace bpftrace; EXPECT_CALL(bpftrace, add_probe(_)).Times(2); Driver driver(bpftrace); ASSERT_EQ(driver.parse_str("kprobe:f { 1; } kprobe:d { 1; }"), 0); MockBPFfeature feature; ast::SemanticAnalyser semantics(driver.root_, bpftrace, feature); ASSERT_EQ(semantics.analyse(), 0); ast::CodegenLLVM codegen(driver.root_, bpftrace); codegen.compile(); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/if_else_printf.cpp000066400000000000000000000045711361633214400215660ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, if_else_printf) { test("kprobe:f { if (pid > 10) { printf(\"hi\\n\"); } else {printf(\"hello\\n\")} }", R"EXPECTED(%printf_t.0 = type { i64 } %printf_t = type { i64 } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %printf_args1 = alloca %printf_t.0, align 8 %printf_args = alloca %printf_t, align 8 %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %1 = icmp ugt i64 %get_pid_tgid, 47244640255 br i1 %1, label %if_stmt, label %else_stmt if_stmt: ; preds = %entry %2 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %3 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0 store i64 0, i64* %3, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 8) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) br label %done else_stmt: ; preds = %entry %4 = bitcast %printf_t.0* %printf_args1 to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) %5 = getelementptr inbounds %printf_t.0, %printf_t.0* %printf_args1, i64 0, i32 0 store i64 1, i64* %5, align 8 %pseudo2 = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id3 = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output4 = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t.0*, i64)*)(i8* %0, i64 %pseudo2, i64 %get_cpu_id3, %printf_t.0* nonnull %printf_args1, i64 8) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) br label %done done: ; preds = %else_stmt, %if_stmt ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/if_else_variable.cpp000066400000000000000000000031431361633214400220430ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, if_else_variable) { test("kprobe:f { if (pid > 10000) { $s = 10 } else { $s = 20 } printf(\"s = %d\", $s) }", R"EXPECTED(%printf_t = type { i64, i64 } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %printf_args = alloca %printf_t, align 8 %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %1 = icmp ugt i64 %get_pid_tgid, 42953967927295 %. = select i1 %1, i64 10, i64 20 %2 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %3 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1 %4 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0 store i64 0, i64* %4, align 8 store i64 %., i64* %3, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 16) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/if_nested_printf.cpp000066400000000000000000000036641361633214400221220ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, if_nested_printf) { test("kprobe:f { if (pid > 10000) { if (pid % 2 == 0) { printf(\"hi\\n\");} } }", R"EXPECTED(%printf_t = type { i64 } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %printf_args = alloca %printf_t, align 8 %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %1 = icmp ugt i64 %get_pid_tgid, 42953967927295 br i1 %1, label %if_stmt, label %else_stmt if_stmt: ; preds = %entry %get_pid_tgid3 = tail call i64 inttoptr (i64 14 to i64 ()*)() %.lobit = and i64 %get_pid_tgid3, 4294967296 %true_cond4 = icmp eq i64 %.lobit, 0 br i1 %true_cond4, label %if_stmt1, label %else_stmt else_stmt: ; preds = %if_stmt, %if_stmt1, %entry ret i64 0 if_stmt1: ; preds = %if_stmt %2 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %3 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0 store i64 0, i64* %3, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 8) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) br label %else_stmt } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/if_printf.cpp000066400000000000000000000035401361633214400205510ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, if_printf) { test("kprobe:f { if (pid > 10000) { printf(\"%d is high\\n\", pid); } }", R"EXPECTED(%printf_t = type { i64, i64 } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %printf_args = alloca %printf_t, align 8 %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %1 = icmp ugt i64 %get_pid_tgid, 42953967927295 br i1 %1, label %if_stmt, label %else_stmt if_stmt: ; preds = %entry %2 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %3 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0 store i64 0, i64* %3, align 8 %get_pid_tgid1 = tail call i64 inttoptr (i64 14 to i64 ()*)() %4 = lshr i64 %get_pid_tgid1, 32 %5 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1 store i64 %4, i64* %5, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 16) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) br label %else_stmt else_stmt: ; preds = %if_stmt, %entry ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/if_variable.cpp000066400000000000000000000057261361633214400210440ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, if_variable) { test("kprobe:f { if (pid > 10000) { $s = 10 } printf(\"s = %d\", $s); }", #if LLVM_VERSION_MAJOR < 6 R"EXPECTED(%printf_t = type { i64, i64 } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %printf_args = alloca %printf_t, align 8 %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %1 = icmp ugt i64 %get_pid_tgid, 42953967927295 %. = select i1 %1, i64 10, i64 0 %2 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %3 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1 %4 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0 store i64 0, i64* %4, align 8 store i64 %., i64* %3, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 16) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(%printf_t = type { i64, i64 } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %printf_args = alloca %printf_t, align 8 %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %1 = icmp ugt i64 %get_pid_tgid, 42953967927295 %spec.select = select i1 %1, i64 10, i64 0 %2 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %3 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1 %4 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0 store i64 0, i64* %4, align 8 store i64 %spec.select, i64* %3, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 16) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/int_propagation.cpp000066400000000000000000000055141361633214400217710ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, int_propagation) { test("kprobe:f { @x = 1234; @y = @x }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@y_val" = alloca i64, align 8 %"@y_key" = alloca i64, align 8 %"@x_key1" = alloca i64, align 8 %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 1234, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %3 = bitcast i64* %"@x_key1" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key1", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo2, i64* nonnull %"@x_key1") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %4 = load i64, i64* %cast, align 8 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %4, %lookup_success ], [ 0, %entry ] call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@y_key", align 8 %6 = bitcast i64* %"@y_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %lookup_elem_val.0, i64* %"@y_val", align 8 %pseudo3 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem4 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo3, i64* nonnull %"@y_key", i64* nonnull %"@y_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/intcast_assign_var.cpp000066400000000000000000000030561361633214400224540ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, intcast_retval) { // Make sure the result is truncated to 32 bit and sign extended to 64 test("kretprobe:f { @=(int32)retval }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kretprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kretprobe:f_1" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca i64, align 8 %1 = getelementptr i8, i8* %0, i64 80 %retval = load i64, i8* %1, align 8 %2 = bitcast i64* %"@_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@_key", align 8 %sext = shl i64 %retval, 32 %3 = ashr exact i64 %sext, 32 %4 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %3, i64* %"@_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/intcast_retval.cpp000066400000000000000000000042171361633214400216150ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, intcast_call) { // Casting should work inside a call test("kretprobe:f { @=sum((int32)retval) }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kretprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kretprobe:f_1" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca i64, align 8 %1 = bitcast i64* %"@_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo, i64* nonnull %"@_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %2 = load i64, i64* %cast, align 8 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %2, %lookup_success ], [ 0, %entry ] %3 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %4 = getelementptr i8, i8* %0, i64 80 %retval = load i64, i8* %4, align 8 %sext = shl i64 %retval, 32 %5 = ashr exact i64 %sext, 32 %6 = add i64 %5, %lookup_elem_val.0 store i64 %6, i64* %"@_val", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo2, i64* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/intptrcast_assign_var.cpp000066400000000000000000000034201361633214400231750ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, intptrcast_assign_var) { test("kretprobe:f { @=*(int8*)(reg(\"bp\")-1) }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kretprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kretprobe:f_1" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca i64, align 8 %deref = alloca i8, align 1 %1 = getelementptr i8, i8* %0, i64 32 %reg_bp = load i64, i8* %1, align 8 %2 = add i64 %reg_bp, -1 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %deref) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %deref, i64 1, i64 %2) %3 = load i8, i8* %deref, align 1 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %deref) %4 = bitcast i64* %"@_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@_key", align 8 %5 = sext i8 %3 to i64 %6 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %5, i64* %"@_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/intptrcast_call.cpp000066400000000000000000000046731361633214400217670ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, intptrcast_call) { // Casting should work inside a call test("kretprobe:f { @=sum(*(int8*)(reg(\"bp\")-1)) }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kretprobe:f"(i8* nocapture readonly) local_unnamed_addr section "s_kretprobe:f_1" { entry: %deref = alloca i8, align 1 %"@_val" = alloca i64, align 8 %"@_key" = alloca i64, align 8 %1 = bitcast i64* %"@_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo, i64* nonnull %"@_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %2 = load i64, i64* %cast, align 8 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %2, %lookup_success ], [ 0, %entry ] %3 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %4 = getelementptr i8, i8* %0, i64 32 %reg_bp = load i64, i8* %4, align 8 %5 = add i64 %reg_bp, -1 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %deref) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %deref, i64 1, i64 %5) %6 = load i8, i8* %deref, align 1 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %deref) %7 = sext i8 %6 to i64 %8 = add i64 %lookup_elem_val.0, %7 store i64 %8, i64* %"@_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/literal_strncmp.cpp000066400000000000000000000152241361633214400217750ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, literal_strncmp) { test("kretprobe:vfs_read /strncmp(comm, \"sshd\", 2)/ { @[comm] = count(); }", #if LLVM_VERSION_MAJOR > 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kretprobe:vfs_read"(i8* nocapture readnone) local_unnamed_addr section "s_kretprobe:vfs_read_1" { entry: %"@_val" = alloca i64, align 8 %comm3 = alloca [16 x i8], align 1 %comm = alloca [16 x i8], align 1 %1 = getelementptr inbounds [16 x i8], [16 x i8]* %comm, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %1, i8 0, i64 16, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm, i64 16) %2 = load i8, [16 x i8]* %comm, align 1 %strcmp.cmp = icmp eq i8 %2, 115 br i1 %strcmp.cmp, label %strcmp.loop, label %pred_true.critedge pred_false: ; preds = %strcmp.loop ret i64 0 pred_true.critedge: ; preds = %entry call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) br label %pred_true pred_true: ; preds = %strcmp.loop, %pred_true.critedge %3 = getelementptr inbounds [16 x i8], [16 x i8]* %comm3, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %3, i8 0, i64 16, i1 false) %get_comm4 = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm3, i64 16) %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [16 x i8]*)*)(i64 %pseudo, [16 x i8]* nonnull %comm3) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success strcmp.loop: ; preds = %entry %4 = add [16 x i8]* %comm, i64 1 %5 = load i8, [16 x i8]* %4, align 1 %strcmp.cmp2 = icmp eq i8 %5, 115 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) br i1 %strcmp.cmp2, label %pred_false, label %pred_true lookup_success: ; preds = %pred_true %cast = bitcast i8* %lookup_elem to i64* %6 = load i64, i64* %cast, align 8 %phitmp = add i64 %6, 1 br label %lookup_merge lookup_merge: ; preds = %pred_true, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %pred_true ] %7 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 %lookup_elem_val.0, i64* %"@_val", align 8 %pseudo5 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [16 x i8]*, i64*, i64)*)(i64 %pseudo5, [16 x i8]* nonnull %comm3, i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kretprobe:vfs_read"(i8* nocapture readnone) local_unnamed_addr section "s_kretprobe:vfs_read_1" { entry: %"@_val" = alloca i64, align 8 %comm3 = alloca [16 x i8], align 1 %comm = alloca [16 x i8], align 1 %1 = getelementptr inbounds [16 x i8], [16 x i8]* %comm, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i8 0, i64 16, i32 1, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm, i64 16) %2 = load i8, [16 x i8]* %comm, align 1 %strcmp.cmp = icmp eq i8 %2, 115 br i1 %strcmp.cmp, label %strcmp.loop, label %pred_true.critedge pred_false: ; preds = %strcmp.loop ret i64 0 pred_true.critedge: ; preds = %entry call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) br label %pred_true pred_true: ; preds = %strcmp.loop, %pred_true.critedge %3 = getelementptr inbounds [16 x i8], [16 x i8]* %comm3, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) call void @llvm.memset.p0i8.i64(i8* nonnull %3, i8 0, i64 16, i32 1, i1 false) %get_comm4 = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm3, i64 16) %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [16 x i8]*)*)(i64 %pseudo, [16 x i8]* nonnull %comm3) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success strcmp.loop: ; preds = %entry %4 = add [16 x i8]* %comm, i64 1 %5 = load i8, [16 x i8]* %4, align 1 %strcmp.cmp2 = icmp eq i8 %5, 115 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) br i1 %strcmp.cmp2, label %pred_false, label %pred_true lookup_success: ; preds = %pred_true %cast = bitcast i8* %lookup_elem to i64* %6 = load i64, i64* %cast, align 8 %phitmp = add i64 %6, 1 br label %lookup_merge lookup_merge: ; preds = %pred_true, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %pred_true ] %7 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 %lookup_elem_val.0, i64* %"@_val", align 8 %pseudo5 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [16 x i8]*, i64*, i64)*)(i64 %pseudo5, [16 x i8]* nonnull %comm3, i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/logical_and.cpp000066400000000000000000000041241361633214400210240ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, logical_and) { test("kprobe:f { @x = pid != 1234 && pid != 1235 }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %.mask = and i64 %get_pid_tgid, -4294967296 %1 = icmp eq i64 %.mask, 5299989643264 br i1 %1, label %"&&_false", label %"&&_lhs_true" "&&_lhs_true": ; preds = %entry %get_pid_tgid1 = tail call i64 inttoptr (i64 14 to i64 ()*)() %.mask2 = and i64 %get_pid_tgid1, -4294967296 %2 = icmp eq i64 %.mask2, 5304284610560 br i1 %2, label %"&&_false", label %"&&_merge" "&&_false": ; preds = %"&&_lhs_true", %entry br label %"&&_merge" "&&_merge": ; preds = %"&&_lhs_true", %"&&_false" %"&&_result.0" = phi i64 [ 0, %"&&_false" ], [ 1, %"&&_lhs_true" ] %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %4 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %"&&_result.0", i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/logical_and_or_different_type.cpp000066400000000000000000000177661361633214400246330ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, logical_and_or_different_type) { test("struct Foo { int m; } BEGIN { $foo = (struct Foo)0; printf(\"%d %d %d %d\", $foo.m && 0, 1 && $foo.m, $foo.m || 0, 0 || $foo.m); }", #if LLVM_VERSION_MAJOR > 6 R"EXPECTED(%printf_t = type { i64, i64, i64, i64, i64 } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @BEGIN(i8*) local_unnamed_addr section "s_BEGIN_1" { entry: %"struct Foo.m16" = alloca i32, align 4 %"struct Foo.m8" = alloca i32, align 4 %"struct Foo.m6" = alloca i32, align 4 %"struct Foo.m" = alloca i32, align 4 %printf_args = alloca %printf_t, align 8 %"$foo" = alloca i32, align 4 %tmpcast = bitcast i32* %"$foo" to [4 x i8]* %1 = bitcast i32* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 4 %1, i64 0, i64 4, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i32, i32 addrspace(64)* null, align 536870912 store i32 %2, i32* %"$foo", align 4 %3 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %4 = bitcast i32* %"struct Foo.m" to i8* %5 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0 store i64 0, i64* %5, align 8 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Foo.m", i64 4, [4 x i8]* nonnull %tmpcast) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) %6 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1 store i64 0, i64* %6, align 8 %7 = bitcast i32* %"struct Foo.m6" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) %probe_read7 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Foo.m6", i64 4, [4 x i8]* nonnull %tmpcast) %8 = load i32, i32* %"struct Foo.m6", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) %rhs_true_cond = icmp ne i32 %8, 0 %"&&_result5.0" = zext i1 %rhs_true_cond to i64 %9 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 2 store i64 %"&&_result5.0", i64* %9, align 8 %10 = bitcast i32* %"struct Foo.m8" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %10) %probe_read9 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Foo.m8", i64 4, [4 x i8]* nonnull %tmpcast) %11 = load i32, i32* %"struct Foo.m8", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %10) %lhs_true_cond10 = icmp ne i32 %11, 0 %"||_result.0" = zext i1 %lhs_true_cond10 to i64 %12 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 3 store i64 %"||_result.0", i64* %12, align 8 %13 = bitcast i32* %"struct Foo.m16" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %13) %probe_read17 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Foo.m16", i64 4, [4 x i8]* nonnull %tmpcast) %14 = load i32, i32* %"struct Foo.m16", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %13) %rhs_true_cond18 = icmp ne i32 %14, 0 %"||_result15.0" = zext i1 %rhs_true_cond18 to i64 %15 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 4 store i64 %"||_result15.0", i64* %15, align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 40) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(%printf_t = type { i64, i64, i64, i64, i64 } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @BEGIN(i8*) local_unnamed_addr section "s_BEGIN_1" { entry: %"struct Foo.m16" = alloca i32, align 4 %"struct Foo.m8" = alloca i32, align 4 %"struct Foo.m6" = alloca i32, align 4 %"struct Foo.m" = alloca i32, align 4 %printf_args = alloca %printf_t, align 8 %"$foo" = alloca i32, align 4 %tmpcast = bitcast i32* %"$foo" to [4 x i8]* %1 = bitcast i32* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i64 0, i64 4, i32 4, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i32, i32 addrspace(64)* null, align 536870912 store i32 %2, i32* %"$foo", align 4 %3 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %4 = bitcast i32* %"struct Foo.m" to i8* %5 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0 store i64 0, i64* %5, align 8 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Foo.m", i64 4, [4 x i8]* nonnull %tmpcast) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) %6 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1 store i64 0, i64* %6, align 8 %7 = bitcast i32* %"struct Foo.m6" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) %probe_read7 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Foo.m6", i64 4, [4 x i8]* nonnull %tmpcast) %8 = load i32, i32* %"struct Foo.m6", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) %rhs_true_cond = icmp ne i32 %8, 0 %"&&_result5.0" = zext i1 %rhs_true_cond to i64 %9 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 2 store i64 %"&&_result5.0", i64* %9, align 8 %10 = bitcast i32* %"struct Foo.m8" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %10) %probe_read9 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Foo.m8", i64 4, [4 x i8]* nonnull %tmpcast) %11 = load i32, i32* %"struct Foo.m8", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %10) %lhs_true_cond10 = icmp ne i32 %11, 0 %"||_result.0" = zext i1 %lhs_true_cond10 to i64 %12 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 3 store i64 %"||_result.0", i64* %12, align 8 %13 = bitcast i32* %"struct Foo.m16" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %13) %probe_read17 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Foo.m16", i64 4, [4 x i8]* nonnull %tmpcast) %14 = load i32, i32* %"struct Foo.m16", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %13) %rhs_true_cond18 = icmp ne i32 %14, 0 %"||_result15.0" = zext i1 %rhs_true_cond18 to i64 %15 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 4 store i64 %"||_result15.0", i64* %15, align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 40) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/logical_not.cpp000066400000000000000000000037411361633214400210660ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, logical_not) { test("BEGIN { @x = !10; @y = !0; }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN_1" { entry: %"@y_val" = alloca i64, align 8 %"@y_key" = alloca i64, align 8 %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %3 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@y_key", align 8 %4 = bitcast i64* %"@y_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 1, i64* %"@y_val", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem2 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo1, i64* nonnull %"@y_key", i64* nonnull %"@y_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/logical_or.cpp000066400000000000000000000041231361633214400207010ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, logical_or) { test("kprobe:f { @x = pid == 1234 || pid == 1235 }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %.mask = and i64 %get_pid_tgid, -4294967296 %1 = icmp eq i64 %.mask, 5299989643264 br i1 %1, label %"||_true", label %"||_lhs_false" "||_lhs_false": ; preds = %entry %get_pid_tgid1 = tail call i64 inttoptr (i64 14 to i64 ()*)() %.mask2 = and i64 %get_pid_tgid1, -4294967296 %2 = icmp eq i64 %.mask2, 5304284610560 br i1 %2, label %"||_true", label %"||_merge" "||_true": ; preds = %"||_lhs_false", %entry br label %"||_merge" "||_merge": ; preds = %"||_lhs_false", %"||_true" %"||_result.0" = phi i64 [ 1, %"||_true" ], [ 0, %"||_lhs_false" ] %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %4 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %"||_result.0", i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/macro_definition.cpp000066400000000000000000000025311361633214400221010ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, macro_definition) { test("#define FOO 100\nk:f { @ = FOO }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca i64, align 8 %1 = bitcast i64* %"@_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@_key", align 8 %2 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 100, i64* %"@_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/map_assign_int.cpp000066400000000000000000000025201361633214400215610ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, map_assign_int) { test("kprobe:f { @x = 1; }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 1, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/map_assign_string.cpp000066400000000000000000000132521361633214400223010ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, map_assign_string) { test("kprobe:f { @x = \"blah\"; }", #if LLVM_VERSION_MAJOR > 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i8 98, i8* %1, align 1 %str.repack1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 1 store i8 108, i8* %str.repack1, align 1 %str.repack2 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 2 store i8 97, i8* %str.repack2, align 1 %str.repack3 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 3 store i8 104, i8* %str.repack3, align 1 %str.repack4 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 4 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %str.repack4, i8 0, i64 60, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #elif LLVM_VERSION_MAJOR == 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i8 98, i8* %1, align 1 %str.repack1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 1 store i8 108, i8* %str.repack1, align 1 %str.repack2 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 2 store i8 97, i8* %str.repack2, align 1 %str.repack3 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 3 store i8 104, i8* %str.repack3, align 1 %str.repack4 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 4 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.memset.p0i8.i64(i8* nonnull %str.repack4, i8 0, i64 60, i32 1, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i8 98, i8* %1, align 1 %str.repack1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 1 store i8 108, i8* %str.repack1, align 1 %str.repack2 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 2 store i8 97, i8* %str.repack2, align 1 %str.repack3 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 3 store i8 104, i8* %str.repack3, align 1 %str.repack4 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 4 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.memset.p0i8.i64(i8* %str.repack4, i8 0, i64 60, i32 1, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/map_increment_decrement.cpp000066400000000000000000000154211361633214400234410ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, map_increment_decrement) { test("BEGIN { @x = 10; @x++; ++@x; @x--; --@x; }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN_1" { entry: %"@x_newval38" = alloca i64, align 8 %"@x_key29" = alloca i64, align 8 %"@x_newval26" = alloca i64, align 8 %"@x_key17" = alloca i64, align 8 %"@x_newval14" = alloca i64, align 8 %"@x_key5" = alloca i64, align 8 %"@x_newval" = alloca i64, align 8 %"@x_key1" = alloca i64, align 8 %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 10, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %3 = bitcast i64* %"@x_key1" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key1", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo2, i64* nonnull %"@x_key1") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %4 = load i64, i64* %cast, align 8 %phitmp = add i64 %4, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] %5 = bitcast i64* %"@x_newval" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 %lookup_elem_val.0, i64* %"@x_newval", align 8 %pseudo3 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem4 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo3, i64* nonnull %"@x_key1", i64* nonnull %"@x_newval", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) %6 = bitcast i64* %"@x_key5" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 0, i64* %"@x_key5", align 8 %pseudo6 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem7 = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo6, i64* nonnull %"@x_key5") %map_lookup_cond12 = icmp eq i8* %lookup_elem7, null br i1 %map_lookup_cond12, label %lookup_merge10, label %lookup_success8 lookup_success8: ; preds = %lookup_merge %cast13 = bitcast i8* %lookup_elem7 to i64* %7 = load i64, i64* %cast13, align 8 %phitmp41 = add i64 %7, 1 br label %lookup_merge10 lookup_merge10: ; preds = %lookup_merge, %lookup_success8 %lookup_elem_val11.0 = phi i64 [ %phitmp41, %lookup_success8 ], [ 1, %lookup_merge ] %8 = bitcast i64* %"@x_newval14" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %8) store i64 %lookup_elem_val11.0, i64* %"@x_newval14", align 8 %pseudo15 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem16 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo15, i64* nonnull %"@x_key5", i64* nonnull %"@x_newval14", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %8) %9 = bitcast i64* %"@x_key17" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %9) store i64 0, i64* %"@x_key17", align 8 %pseudo18 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem19 = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo18, i64* nonnull %"@x_key17") %map_lookup_cond24 = icmp eq i8* %lookup_elem19, null br i1 %map_lookup_cond24, label %lookup_merge22, label %lookup_success20 lookup_success20: ; preds = %lookup_merge10 %cast25 = bitcast i8* %lookup_elem19 to i64* %10 = load i64, i64* %cast25, align 8 %phitmp42 = add i64 %10, -1 br label %lookup_merge22 lookup_merge22: ; preds = %lookup_merge10, %lookup_success20 %lookup_elem_val23.0 = phi i64 [ %phitmp42, %lookup_success20 ], [ -1, %lookup_merge10 ] %11 = bitcast i64* %"@x_newval26" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %11) store i64 %lookup_elem_val23.0, i64* %"@x_newval26", align 8 %pseudo27 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem28 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo27, i64* nonnull %"@x_key17", i64* nonnull %"@x_newval26", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %9) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %11) %12 = bitcast i64* %"@x_key29" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %12) store i64 0, i64* %"@x_key29", align 8 %pseudo30 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem31 = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo30, i64* nonnull %"@x_key29") %map_lookup_cond36 = icmp eq i8* %lookup_elem31, null br i1 %map_lookup_cond36, label %lookup_merge34, label %lookup_success32 lookup_success32: ; preds = %lookup_merge22 %cast37 = bitcast i8* %lookup_elem31 to i64* %13 = load i64, i64* %cast37, align 8 %phitmp43 = add i64 %13, -1 br label %lookup_merge34 lookup_merge34: ; preds = %lookup_merge22, %lookup_success32 %lookup_elem_val35.0 = phi i64 [ %phitmp43, %lookup_success32 ], [ -1, %lookup_merge22 ] %14 = bitcast i64* %"@x_newval38" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %14) store i64 %lookup_elem_val35.0, i64* %"@x_newval38", align 8 %pseudo39 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem40 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo39, i64* nonnull %"@x_key29", i64* nonnull %"@x_newval38", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %12) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %14) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/map_key_int.cpp000066400000000000000000000031431361633214400210670ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, map_key_int) { test("kprobe:f { @x[11,22,33] = 44 }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca [24 x i8], align 8 %1 = getelementptr inbounds [24 x i8], [24 x i8]* %"@x_key", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 11, i8* %1, align 8 %2 = getelementptr inbounds [24 x i8], [24 x i8]* %"@x_key", i64 0, i64 8 store i64 22, i8* %2, align 8 %3 = getelementptr inbounds [24 x i8], [24 x i8]* %"@x_key", i64 0, i64 16 store i64 33, i8* %3, align 8 %4 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 44, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [24 x i8]*, i64*, i64)*)(i64 %pseudo, [24 x i8]* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/map_key_probe.cpp000066400000000000000000000104121361633214400214010ustar00rootroot00000000000000#include "common.h" #include "../mocks.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, map_key_probe) { auto bpftrace = get_mock_bpftrace(); test(*bpftrace, "tracepoint:sched:sched_one,tracepoint:sched:sched_two { @x[probe] = " "@x[probe] + 1 }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:sched:sched_one"(i8* nocapture readnone) local_unnamed_addr section "s_tracepoint:sched:sched_one_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key1" = alloca [8 x i8], align 8 %"@x_key" = alloca [8 x i8], align 8 %1 = getelementptr inbounds [8 x i8], [8 x i8]* %"@x_key", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, [8 x i8]* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [8 x i8]*)*)(i64 %pseudo, [8 x i8]* nonnull %"@x_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %2 = load i64, i64* %cast, align 8 %phitmp = add i64 %2, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %3 = getelementptr inbounds [8 x i8], [8 x i8]* %"@x_key1", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, [8 x i8]* %"@x_key1", align 8 %4 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %lookup_elem_val.0, i64* %"@x_val", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [8 x i8]*, i64*, i64)*)(i64 %pseudo2, [8 x i8]* nonnull %"@x_key1", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:sched:sched_two"(i8* nocapture readnone) local_unnamed_addr section "s_tracepoint:sched:sched_two_2" { entry: %"@x_val" = alloca i64, align 8 %"@x_key1" = alloca [8 x i8], align 8 %"@x_key" = alloca [8 x i8], align 8 %1 = getelementptr inbounds [8 x i8], [8 x i8]* %"@x_key", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 1, [8 x i8]* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [8 x i8]*)*)(i64 %pseudo, [8 x i8]* nonnull %"@x_key") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %2 = load i64, i64* %cast, align 8 %phitmp = add i64 %2, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %3 = getelementptr inbounds [8 x i8], [8 x i8]* %"@x_key1", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 1, [8 x i8]* %"@x_key1", align 8 %4 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %lookup_elem_val.0, i64* %"@x_val", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [8 x i8]*, i64*, i64)*)(i64 %pseudo2, [8 x i8]* nonnull %"@x_key1", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/map_key_string.cpp000066400000000000000000000134371361633214400216120ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, map_key_string) { test("kprobe:f { @x[\"a\", \"b\"] = 44 }", #if LLVM_VERSION_MAJOR > 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca [128 x i8], align 1 %1 = getelementptr inbounds [128 x i8], [128 x i8]* %"@x_key", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i8 97, i8* %1, align 1 %str.sroa.4.0..sroa_idx = getelementptr inbounds [128 x i8], [128 x i8]* %"@x_key", i64 0, i64 1 %str1.sroa.0.0..sroa_idx = getelementptr inbounds [128 x i8], [128 x i8]* %"@x_key", i64 0, i64 64 call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %str.sroa.4.0..sroa_idx, i8 0, i64 63, i1 false) store i8 98, i8* %str1.sroa.0.0..sroa_idx, align 1 %str1.sroa.4.0..sroa_idx = getelementptr inbounds [128 x i8], [128 x i8]* %"@x_key", i64 0, i64 65 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %str1.sroa.4.0..sroa_idx, i8 0, i64 63, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 44, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [128 x i8]*, i64*, i64)*)(i64 %pseudo, [128 x i8]* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #elif LLVM_VERSION_MAJOR == 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca [128 x i8], align 1 %1 = getelementptr inbounds [128 x i8], [128 x i8]* %"@x_key", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i8 97, i8* %1, align 1 %str.sroa.4.0..sroa_idx = getelementptr inbounds [128 x i8], [128 x i8]* %"@x_key", i64 0, i64 1 %str1.sroa.0.0..sroa_idx = getelementptr inbounds [128 x i8], [128 x i8]* %"@x_key", i64 0, i64 64 call void @llvm.memset.p0i8.i64(i8* nonnull %str.sroa.4.0..sroa_idx, i8 0, i64 63, i32 1, i1 false) store i8 98, i8* %str1.sroa.0.0..sroa_idx, align 1 %str1.sroa.4.0..sroa_idx = getelementptr inbounds [128 x i8], [128 x i8]* %"@x_key", i64 0, i64 65 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.memset.p0i8.i64(i8* nonnull %str1.sroa.4.0..sroa_idx, i8 0, i64 63, i32 1, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 44, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [128 x i8]*, i64*, i64)*)(i64 %pseudo, [128 x i8]* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca [128 x i8], align 1 %1 = getelementptr inbounds [128 x i8], [128 x i8]* %"@x_key", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i8 97, i8* %1, align 1 %str.sroa.4.0..sroa_idx = getelementptr inbounds [128 x i8], [128 x i8]* %"@x_key", i64 0, i64 1 %str1.sroa.0.0..sroa_idx = getelementptr inbounds [128 x i8], [128 x i8]* %"@x_key", i64 0, i64 64 call void @llvm.memset.p0i8.i64(i8* %str.sroa.4.0..sroa_idx, i8 0, i64 63, i32 1, i1 false) store i8 98, i8* %str1.sroa.0.0..sroa_idx, align 1 %str1.sroa.4.0..sroa_idx = getelementptr inbounds [128 x i8], [128 x i8]* %"@x_key", i64 0, i64 65 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.memset.p0i8.i64(i8* %str1.sroa.4.0..sroa_idx, i8 0, i64 63, i32 1, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 44, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [128 x i8]*, i64*, i64)*)(i64 %pseudo, [128 x i8]* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/multiple_identical_probes.cpp000066400000000000000000000011721361633214400240110ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, multiple_identical_probes) { test("kprobe:f { 1; } kprobe:f { 1; }", R"EXPECTED(; Function Attrs: norecurse nounwind readnone define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr #0 section "s_kprobe:f_1" { entry: ret i64 0 } ; Function Attrs: norecurse nounwind readnone define i64 @"kprobe:f.1"(i8* nocapture readnone) local_unnamed_addr #0 section "s_kprobe:f_2" { entry: ret i64 0 } attributes #0 = { norecurse nounwind readnone } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/optional_positional_parameter.cpp000066400000000000000000000115071361633214400247210ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, optional_positional_parameter) { test("BEGIN { @x = $1; @y = str($2) }", #if LLVM_VERSION_MAJOR > 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN_1" { entry: %"@y_key" = alloca i64, align 8 %str1 = alloca [1 x i8], align 1 %str = alloca [64 x i8], align 1 %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %3 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %3, i8 0, i64 64, i1 false) %4 = getelementptr inbounds [1 x i8], [1 x i8]* %str1, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i8 0, i8* %4, align 1 %probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 64, [1 x i8]* nonnull %str1) %5 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@y_key", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem3 = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo2, i64* nonnull %"@y_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN_1" { entry: %"@y_key" = alloca i64, align 8 %str1 = alloca [1 x i8], align 1 %str = alloca [64 x i8], align 1 %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %1 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@x_key", align 8 %2 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %3 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) call void @llvm.memset.p0i8.i64(i8* nonnull %3, i8 0, i64 64, i32 1, i1 false) %4 = getelementptr inbounds [1 x i8], [1 x i8]* %str1, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i8 0, i8* %4, align 1 %probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 64, [1 x i8]* nonnull %str1) %5 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@y_key", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem3 = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo2, i64* nonnull %"@y_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/pred_binop.cpp000066400000000000000000000032651361633214400207160ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, pred_binop) { test("kprobe:f / pid == 1234 / { @x = 1 }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %.mask = and i64 %get_pid_tgid, -4294967296 %1 = icmp eq i64 %.mask, 5299989643264 br i1 %1, label %pred_true, label %pred_false pred_false: ; preds = %entry ret i64 0 pred_true: ; preds = %entry %2 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 1, i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/regression.cpp000066400000000000000000000014401361633214400207460ustar00rootroot00000000000000#include "gmock/gmock.h" #include "gtest/gtest.h" #include "bpforc.h" #include "bpftrace.h" #include "clang_parser.h" #include "codegen_llvm.h" #include "driver.h" #include "semantic_analyser.h" #include "common.h" namespace bpftrace { namespace test { namespace codegen { using ::testing::_; TEST(codegen, regression_957) { auto bpftrace = get_mock_bpftrace(); Driver driver(*bpftrace); ASSERT_EQ(driver.parse_str("t:sched:sched_one* { cat(\"%s\", probe); }"), 0); MockBPFfeature feature; ast::SemanticAnalyser semantics(driver.root_, *bpftrace, feature); ASSERT_EQ(semantics.analyse(), 0); ASSERT_EQ(semantics.create_maps(true), 0); ast::CodegenLLVM codegen(driver.root_, *bpftrace); codegen.compile(); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/string_equal_comparison.cpp000066400000000000000000000173661361633214400235330ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, string_equal_comparison) { test("kretprobe:vfs_read /comm == \"sshd\"/ { @[comm] = count(); }", #if LLVM_VERSION_MAJOR > 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kretprobe:vfs_read"(i8* nocapture readnone) local_unnamed_addr section "s_kretprobe:vfs_read_1" { entry: %"@_val" = alloca i64, align 8 %comm9 = alloca [16 x i8], align 1 %comm = alloca [16 x i8], align 1 %1 = getelementptr inbounds [16 x i8], [16 x i8]* %comm, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %1, i8 0, i64 16, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm, i64 16) %2 = load i8, [16 x i8]* %comm, align 1 %strcmp.cmp = icmp eq i8 %2, 115 br i1 %strcmp.cmp, label %strcmp.loop, label %pred_false pred_false: ; preds = %strcmp.loop5, %strcmp.loop3, %strcmp.loop1, %strcmp.loop, %entry ret i64 0 pred_true: ; preds = %strcmp.loop5 %3 = getelementptr inbounds [16 x i8], [16 x i8]* %comm9, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %3, i8 0, i64 16, i1 false) %get_comm10 = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm9, i64 16) %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [16 x i8]*)*)(i64 %pseudo, [16 x i8]* nonnull %comm9) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success strcmp.loop: ; preds = %entry %4 = add [16 x i8]* %comm, i64 1 %5 = load i8, [16 x i8]* %4, align 1 %strcmp.cmp2 = icmp eq i8 %5, 115 br i1 %strcmp.cmp2, label %strcmp.loop1, label %pred_false strcmp.loop1: ; preds = %strcmp.loop %6 = add [16 x i8]* %comm, i64 2 %7 = load i8, [16 x i8]* %6, align 1 %strcmp.cmp4 = icmp eq i8 %7, 104 br i1 %strcmp.cmp4, label %strcmp.loop3, label %pred_false strcmp.loop3: ; preds = %strcmp.loop1 %8 = add [16 x i8]* %comm, i64 3 %9 = load i8, [16 x i8]* %8, align 1 %strcmp.cmp6 = icmp eq i8 %9, 100 br i1 %strcmp.cmp6, label %strcmp.loop5, label %pred_false strcmp.loop5: ; preds = %strcmp.loop3 %10 = add [16 x i8]* %comm, i64 4 %11 = load i8, [16 x i8]* %10, align 1 %strcmp.cmp8 = icmp eq i8 %11, 0 br i1 %strcmp.cmp8, label %pred_true, label %pred_false lookup_success: ; preds = %pred_true %cast = bitcast i8* %lookup_elem to i64* %12 = load i64, i64* %cast, align 8 %phitmp = add i64 %12, 1 br label %lookup_merge lookup_merge: ; preds = %pred_true, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %pred_true ] %13 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %13) store i64 %lookup_elem_val.0, i64* %"@_val", align 8 %pseudo11 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [16 x i8]*, i64*, i64)*)(i64 %pseudo11, [16 x i8]* nonnull %comm9, i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %13) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kretprobe:vfs_read"(i8* nocapture readnone) local_unnamed_addr section "s_kretprobe:vfs_read_1" { entry: %"@_val" = alloca i64, align 8 %comm9 = alloca [16 x i8], align 1 %comm = alloca [16 x i8], align 1 %1 = getelementptr inbounds [16 x i8], [16 x i8]* %comm, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i8 0, i64 16, i32 1, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm, i64 16) %2 = load i8, [16 x i8]* %comm, align 1 %strcmp.cmp = icmp eq i8 %2, 115 br i1 %strcmp.cmp, label %strcmp.loop, label %pred_false pred_false: ; preds = %strcmp.loop5, %strcmp.loop3, %strcmp.loop1, %strcmp.loop, %entry ret i64 0 pred_true: ; preds = %strcmp.loop5 %3 = getelementptr inbounds [16 x i8], [16 x i8]* %comm9, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) call void @llvm.memset.p0i8.i64(i8* nonnull %3, i8 0, i64 16, i32 1, i1 false) %get_comm10 = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm9, i64 16) %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [16 x i8]*)*)(i64 %pseudo, [16 x i8]* nonnull %comm9) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success strcmp.loop: ; preds = %entry %4 = add [16 x i8]* %comm, i64 1 %5 = load i8, [16 x i8]* %4, align 1 %strcmp.cmp2 = icmp eq i8 %5, 115 br i1 %strcmp.cmp2, label %strcmp.loop1, label %pred_false strcmp.loop1: ; preds = %strcmp.loop %6 = add [16 x i8]* %comm, i64 2 %7 = load i8, [16 x i8]* %6, align 1 %strcmp.cmp4 = icmp eq i8 %7, 104 br i1 %strcmp.cmp4, label %strcmp.loop3, label %pred_false strcmp.loop3: ; preds = %strcmp.loop1 %8 = add [16 x i8]* %comm, i64 3 %9 = load i8, [16 x i8]* %8, align 1 %strcmp.cmp6 = icmp eq i8 %9, 100 br i1 %strcmp.cmp6, label %strcmp.loop5, label %pred_false strcmp.loop5: ; preds = %strcmp.loop3 %10 = add [16 x i8]* %comm, i64 4 %11 = load i8, [16 x i8]* %10, align 1 %strcmp.cmp8 = icmp eq i8 %11, 0 br i1 %strcmp.cmp8, label %pred_true, label %pred_false lookup_success: ; preds = %pred_true %cast = bitcast i8* %lookup_elem to i64* %12 = load i64, i64* %cast, align 8 %phitmp = add i64 %12, 1 br label %lookup_merge lookup_merge: ; preds = %pred_true, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %pred_true ] %13 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %13) store i64 %lookup_elem_val.0, i64* %"@_val", align 8 %pseudo11 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [16 x i8]*, i64*, i64)*)(i64 %pseudo11, [16 x i8]* nonnull %comm9, i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %13) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/string_not_equal_comparison.cpp000066400000000000000000000173621361633214400244070ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, string_not_equal_comparison) { test("kretprobe:vfs_read /comm != \"sshd\"/ { @[comm] = count(); }", #if LLVM_VERSION_MAJOR > 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kretprobe:vfs_read"(i8* nocapture readnone) local_unnamed_addr section "s_kretprobe:vfs_read_1" { entry: %"@_val" = alloca i64, align 8 %comm9 = alloca [16 x i8], align 1 %comm = alloca [16 x i8], align 1 %1 = getelementptr inbounds [16 x i8], [16 x i8]* %comm, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %1, i8 0, i64 16, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm, i64 16) %2 = load i8, [16 x i8]* %comm, align 1 %strcmp.cmp = icmp eq i8 %2, 115 br i1 %strcmp.cmp, label %strcmp.loop, label %pred_true pred_false: ; preds = %strcmp.loop5 ret i64 0 pred_true: ; preds = %strcmp.loop5, %strcmp.loop3, %strcmp.loop1, %strcmp.loop, %entry %3 = getelementptr inbounds [16 x i8], [16 x i8]* %comm9, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %3, i8 0, i64 16, i1 false) %get_comm10 = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm9, i64 16) %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [16 x i8]*)*)(i64 %pseudo, [16 x i8]* nonnull %comm9) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success strcmp.loop: ; preds = %entry %4 = add [16 x i8]* %comm, i64 1 %5 = load i8, [16 x i8]* %4, align 1 %strcmp.cmp2 = icmp eq i8 %5, 115 br i1 %strcmp.cmp2, label %strcmp.loop1, label %pred_true strcmp.loop1: ; preds = %strcmp.loop %6 = add [16 x i8]* %comm, i64 2 %7 = load i8, [16 x i8]* %6, align 1 %strcmp.cmp4 = icmp eq i8 %7, 104 br i1 %strcmp.cmp4, label %strcmp.loop3, label %pred_true strcmp.loop3: ; preds = %strcmp.loop1 %8 = add [16 x i8]* %comm, i64 3 %9 = load i8, [16 x i8]* %8, align 1 %strcmp.cmp6 = icmp eq i8 %9, 100 br i1 %strcmp.cmp6, label %strcmp.loop5, label %pred_true strcmp.loop5: ; preds = %strcmp.loop3 %10 = add [16 x i8]* %comm, i64 4 %11 = load i8, [16 x i8]* %10, align 1 %strcmp.cmp8 = icmp eq i8 %11, 0 br i1 %strcmp.cmp8, label %pred_false, label %pred_true lookup_success: ; preds = %pred_true %cast = bitcast i8* %lookup_elem to i64* %12 = load i64, i64* %cast, align 8 %phitmp = add i64 %12, 1 br label %lookup_merge lookup_merge: ; preds = %pred_true, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %pred_true ] %13 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %13) store i64 %lookup_elem_val.0, i64* %"@_val", align 8 %pseudo11 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [16 x i8]*, i64*, i64)*)(i64 %pseudo11, [16 x i8]* nonnull %comm9, i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %13) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kretprobe:vfs_read"(i8* nocapture readnone) local_unnamed_addr section "s_kretprobe:vfs_read_1" { entry: %"@_val" = alloca i64, align 8 %comm9 = alloca [16 x i8], align 1 %comm = alloca [16 x i8], align 1 %1 = getelementptr inbounds [16 x i8], [16 x i8]* %comm, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i8 0, i64 16, i32 1, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm, i64 16) %2 = load i8, [16 x i8]* %comm, align 1 %strcmp.cmp = icmp eq i8 %2, 115 br i1 %strcmp.cmp, label %strcmp.loop, label %pred_true pred_false: ; preds = %strcmp.loop5 ret i64 0 pred_true: ; preds = %strcmp.loop5, %strcmp.loop3, %strcmp.loop1, %strcmp.loop, %entry %3 = getelementptr inbounds [16 x i8], [16 x i8]* %comm9, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) call void @llvm.memset.p0i8.i64(i8* nonnull %3, i8 0, i64 16, i32 1, i1 false) %get_comm10 = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm9, i64 16) %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, [16 x i8]*)*)(i64 %pseudo, [16 x i8]* nonnull %comm9) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success strcmp.loop: ; preds = %entry %4 = add [16 x i8]* %comm, i64 1 %5 = load i8, [16 x i8]* %4, align 1 %strcmp.cmp2 = icmp eq i8 %5, 115 br i1 %strcmp.cmp2, label %strcmp.loop1, label %pred_true strcmp.loop1: ; preds = %strcmp.loop %6 = add [16 x i8]* %comm, i64 2 %7 = load i8, [16 x i8]* %6, align 1 %strcmp.cmp4 = icmp eq i8 %7, 104 br i1 %strcmp.cmp4, label %strcmp.loop3, label %pred_true strcmp.loop3: ; preds = %strcmp.loop1 %8 = add [16 x i8]* %comm, i64 3 %9 = load i8, [16 x i8]* %8, align 1 %strcmp.cmp6 = icmp eq i8 %9, 100 br i1 %strcmp.cmp6, label %strcmp.loop5, label %pred_true strcmp.loop5: ; preds = %strcmp.loop3 %10 = add [16 x i8]* %comm, i64 4 %11 = load i8, [16 x i8]* %10, align 1 %strcmp.cmp8 = icmp eq i8 %11, 0 br i1 %strcmp.cmp8, label %pred_false, label %pred_true lookup_success: ; preds = %pred_true %cast = bitcast i8* %lookup_elem to i64* %12 = load i64, i64* %cast, align 8 %phitmp = add i64 %12, 1 br label %lookup_merge lookup_merge: ; preds = %pred_true, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %pred_true ] %13 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %13) store i64 %lookup_elem_val.0, i64* %"@_val", align 8 %pseudo11 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, [16 x i8]*, i64*, i64)*)(i64 %pseudo11, [16 x i8]* nonnull %comm9, i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %13) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/string_propagation.cpp000066400000000000000000000260361361633214400225070ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, string_propagation) { test("kprobe:f { @x = \"asdf\"; @y = @x }", #if LLVM_VERSION_MAJOR > 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@y_key" = alloca i64, align 8 %lookup_elem_val = alloca [64 x i8], align 1 %"@x_key1" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i8 97, i8* %1, align 1 %str.repack5 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 1 store i8 115, i8* %str.repack5, align 1 %str.repack6 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 2 store i8 100, i8* %str.repack6, align 1 %str.repack7 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 3 store i8 102, i8* %str.repack7, align 1 %str.repack8 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 4 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %str.repack8, i8 0, i64 60, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %3 = bitcast i64* %"@x_key1" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key1", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo2, i64* nonnull %"@x_key1") %4 = getelementptr inbounds [64 x i8], [64 x i8]* %lookup_elem_val, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_failure, label %lookup_success lookup_success: ; preds = %entry call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 1 %4, i8* nonnull align 1 %lookup_elem, i64 64, i1 false) br label %lookup_merge lookup_failure: ; preds = %entry call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %4, i8 0, i64 64, i1 false) br label %lookup_merge lookup_merge: ; preds = %lookup_failure, %lookup_success call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@y_key", align 8 %pseudo3 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem4 = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo3, i64* nonnull %"@y_key", [64 x i8]* nonnull %lookup_elem_val, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture writeonly, i8* nocapture readonly, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #elif LLVM_VERSION_MAJOR == 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@y_key" = alloca i64, align 8 %lookup_elem_val = alloca [64 x i8], align 1 %"@x_key1" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i8 97, i8* %1, align 1 %str.repack5 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 1 store i8 115, i8* %str.repack5, align 1 %str.repack6 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 2 store i8 100, i8* %str.repack6, align 1 %str.repack7 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 3 store i8 102, i8* %str.repack7, align 1 %str.repack8 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 4 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.memset.p0i8.i64(i8* nonnull %str.repack8, i8 0, i64 60, i32 1, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %3 = bitcast i64* %"@x_key1" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key1", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo2, i64* nonnull %"@x_key1") %4 = getelementptr inbounds [64 x i8], [64 x i8]* %lookup_elem_val, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_failure, label %lookup_success lookup_success: ; preds = %entry call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull %4, i8* nonnull %lookup_elem, i64 64, i32 1, i1 false) br label %lookup_merge lookup_failure: ; preds = %entry call void @llvm.memset.p0i8.i64(i8* nonnull %4, i8 0, i64 64, i32 1, i1 false) br label %lookup_merge lookup_merge: ; preds = %lookup_failure, %lookup_success call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@y_key", align 8 %pseudo3 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem4 = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo3, i64* nonnull %"@y_key", [64 x i8]* nonnull %lookup_elem_val, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture writeonly, i8* nocapture readonly, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@y_key" = alloca i64, align 8 %lookup_elem_val = alloca [64 x i8], align 1 %"@x_key1" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i8 97, i8* %1, align 1 %str.repack5 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 1 store i8 115, i8* %str.repack5, align 1 %str.repack6 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 2 store i8 100, i8* %str.repack6, align 1 %str.repack7 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 3 store i8 102, i8* %str.repack7, align 1 %str.repack8 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 4 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.memset.p0i8.i64(i8* %str.repack8, i8 0, i64 60, i32 1, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %3 = bitcast i64* %"@x_key1" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key1", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo2, i64* nonnull %"@x_key1") %4 = getelementptr inbounds [64 x i8], [64 x i8]* %lookup_elem_val, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_failure, label %lookup_success lookup_success: ; preds = %entry call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull %4, i8* nonnull %lookup_elem, i64 64, i32 1, i1 false) br label %lookup_merge lookup_failure: ; preds = %entry call void @llvm.memset.p0i8.i64(i8* nonnull %4, i8 0, i64 64, i32 1, i1 false) br label %lookup_merge lookup_merge: ; preds = %lookup_failure, %lookup_success call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@y_key", align 8 %pseudo3 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem4 = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo3, i64* nonnull %"@y_key", [64 x i8]* nonnull %lookup_elem_val, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture writeonly, i8* nocapture readonly, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/strncmp.cpp000066400000000000000000001037531361633214400202660ustar00rootroot00000000000000#include "../mocks.h" #include "common.h" using ::testing::_; using ::testing::Return; namespace bpftrace { namespace test { namespace codegen { TEST(codegen, strncmp) { auto bpftrace = get_mock_bpftrace(); test(*bpftrace, "t:file:filename /str(args->filename) == comm/ { @=1 }", #if LLVM_VERSION_MAJOR > 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:file:filename"(i8*) local_unnamed_addr section "s_tracepoint:file:filename_1" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca i64, align 8 %strcmp.char_r = alloca i8, align 1 %strcmp.char_l = alloca i8, align 1 %"struct _tracepoint_file_filename.filename" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %comm = alloca [16 x i8], align 1 %1 = getelementptr inbounds [16 x i8], [16 x i8]* %comm, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %1, i8 0, i64 16, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm, i64 16) %2 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %2, i8 0, i64 64, i1 false) %3 = add i8* %0, i64 8 %4 = bitcast i64* %"struct _tracepoint_file_filename.filename" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct _tracepoint_file_filename.filename", i64 8, i8* %3) %5 = load i64, i64* %"struct _tracepoint_file_filename.filename", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) %probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 64, i64 %5) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %strcmp.char_l) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %strcmp.char_r) %probe_read1 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* nonnull %str) %6 = load i8, i8* %strcmp.char_l, align 1 %probe_read2 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* nonnull %comm) %7 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp = icmp eq i8 %6, %7 br i1 %strcmp.cmp, label %strcmp.loop_null_cmp, label %pred_false.critedge pred_false.critedge: ; preds = %entry call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %strcmp.char_l) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %strcmp.char_r) br label %pred_false pred_false: ; preds = %strcmp.false, %pred_false.critedge ret i64 0 pred_true.critedge: ; preds = %strcmp.loop87, %strcmp.loop_null_cmp, %strcmp.loop_null_cmp4, %strcmp.loop_null_cmp10, %strcmp.loop_null_cmp16, %strcmp.loop_null_cmp22, %strcmp.loop_null_cmp28, %strcmp.loop_null_cmp34, %strcmp.loop_null_cmp40, %strcmp.loop_null_cmp46, %strcmp.loop_null_cmp52, %strcmp.loop_null_cmp58, %strcmp.loop_null_cmp64, %strcmp.loop_null_cmp70, %strcmp.loop_null_cmp76, %strcmp.loop_null_cmp82, %strcmp.loop_null_cmp88 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %strcmp.char_l) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %strcmp.char_r) %8 = bitcast i64* %"@_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %8) store i64 0, i64* %"@_key", align 8 %9 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %9) store i64 1, i64* %"@_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %8) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %9) ret i64 0 strcmp.false: ; preds = %strcmp.loop87, %strcmp.loop81, %strcmp.loop75, %strcmp.loop69, %strcmp.loop63, %strcmp.loop57, %strcmp.loop51, %strcmp.loop45, %strcmp.loop39, %strcmp.loop33, %strcmp.loop27, %strcmp.loop21, %strcmp.loop15, %strcmp.loop9, %strcmp.loop3, %strcmp.loop call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %strcmp.char_l) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %strcmp.char_r) br label %pred_false strcmp.loop: ; preds = %strcmp.loop_null_cmp %10 = add [64 x i8]* %str, i64 1 %probe_read5 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %10) %11 = load i8, i8* %strcmp.char_l, align 1 %12 = add [16 x i8]* %comm, i64 1 %probe_read6 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %12) %13 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp7 = icmp eq i8 %11, %13 br i1 %strcmp.cmp7, label %strcmp.loop_null_cmp4, label %strcmp.false strcmp.loop_null_cmp: ; preds = %entry %strcmp.cmp_null = icmp eq i8 %6, 0 br i1 %strcmp.cmp_null, label %pred_true.critedge, label %strcmp.loop strcmp.loop3: ; preds = %strcmp.loop_null_cmp4 %14 = add [64 x i8]* %str, i64 2 %probe_read11 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %14) %15 = load i8, i8* %strcmp.char_l, align 1 %16 = add [16 x i8]* %comm, i64 2 %probe_read12 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %16) %17 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp13 = icmp eq i8 %15, %17 br i1 %strcmp.cmp13, label %strcmp.loop_null_cmp10, label %strcmp.false strcmp.loop_null_cmp4: ; preds = %strcmp.loop %strcmp.cmp_null8 = icmp eq i8 %11, 0 br i1 %strcmp.cmp_null8, label %pred_true.critedge, label %strcmp.loop3 strcmp.loop9: ; preds = %strcmp.loop_null_cmp10 %18 = add [64 x i8]* %str, i64 3 %probe_read17 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %18) %19 = load i8, i8* %strcmp.char_l, align 1 %20 = add [16 x i8]* %comm, i64 3 %probe_read18 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %20) %21 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp19 = icmp eq i8 %19, %21 br i1 %strcmp.cmp19, label %strcmp.loop_null_cmp16, label %strcmp.false strcmp.loop_null_cmp10: ; preds = %strcmp.loop3 %strcmp.cmp_null14 = icmp eq i8 %15, 0 br i1 %strcmp.cmp_null14, label %pred_true.critedge, label %strcmp.loop9 strcmp.loop15: ; preds = %strcmp.loop_null_cmp16 %22 = add [64 x i8]* %str, i64 4 %probe_read23 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %22) %23 = load i8, i8* %strcmp.char_l, align 1 %24 = add [16 x i8]* %comm, i64 4 %probe_read24 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %24) %25 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp25 = icmp eq i8 %23, %25 br i1 %strcmp.cmp25, label %strcmp.loop_null_cmp22, label %strcmp.false strcmp.loop_null_cmp16: ; preds = %strcmp.loop9 %strcmp.cmp_null20 = icmp eq i8 %19, 0 br i1 %strcmp.cmp_null20, label %pred_true.critedge, label %strcmp.loop15 strcmp.loop21: ; preds = %strcmp.loop_null_cmp22 %26 = add [64 x i8]* %str, i64 5 %probe_read29 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %26) %27 = load i8, i8* %strcmp.char_l, align 1 %28 = add [16 x i8]* %comm, i64 5 %probe_read30 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %28) %29 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp31 = icmp eq i8 %27, %29 br i1 %strcmp.cmp31, label %strcmp.loop_null_cmp28, label %strcmp.false strcmp.loop_null_cmp22: ; preds = %strcmp.loop15 %strcmp.cmp_null26 = icmp eq i8 %23, 0 br i1 %strcmp.cmp_null26, label %pred_true.critedge, label %strcmp.loop21 strcmp.loop27: ; preds = %strcmp.loop_null_cmp28 %30 = add [64 x i8]* %str, i64 6 %probe_read35 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %30) %31 = load i8, i8* %strcmp.char_l, align 1 %32 = add [16 x i8]* %comm, i64 6 %probe_read36 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %32) %33 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp37 = icmp eq i8 %31, %33 br i1 %strcmp.cmp37, label %strcmp.loop_null_cmp34, label %strcmp.false strcmp.loop_null_cmp28: ; preds = %strcmp.loop21 %strcmp.cmp_null32 = icmp eq i8 %27, 0 br i1 %strcmp.cmp_null32, label %pred_true.critedge, label %strcmp.loop27 strcmp.loop33: ; preds = %strcmp.loop_null_cmp34 %34 = add [64 x i8]* %str, i64 7 %probe_read41 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %34) %35 = load i8, i8* %strcmp.char_l, align 1 %36 = add [16 x i8]* %comm, i64 7 %probe_read42 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %36) %37 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp43 = icmp eq i8 %35, %37 br i1 %strcmp.cmp43, label %strcmp.loop_null_cmp40, label %strcmp.false strcmp.loop_null_cmp34: ; preds = %strcmp.loop27 %strcmp.cmp_null38 = icmp eq i8 %31, 0 br i1 %strcmp.cmp_null38, label %pred_true.critedge, label %strcmp.loop33 strcmp.loop39: ; preds = %strcmp.loop_null_cmp40 %38 = add [64 x i8]* %str, i64 8 %probe_read47 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %38) %39 = load i8, i8* %strcmp.char_l, align 1 %40 = add [16 x i8]* %comm, i64 8 %probe_read48 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %40) %41 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp49 = icmp eq i8 %39, %41 br i1 %strcmp.cmp49, label %strcmp.loop_null_cmp46, label %strcmp.false strcmp.loop_null_cmp40: ; preds = %strcmp.loop33 %strcmp.cmp_null44 = icmp eq i8 %35, 0 br i1 %strcmp.cmp_null44, label %pred_true.critedge, label %strcmp.loop39 strcmp.loop45: ; preds = %strcmp.loop_null_cmp46 %42 = add [64 x i8]* %str, i64 9 %probe_read53 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %42) %43 = load i8, i8* %strcmp.char_l, align 1 %44 = add [16 x i8]* %comm, i64 9 %probe_read54 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %44) %45 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp55 = icmp eq i8 %43, %45 br i1 %strcmp.cmp55, label %strcmp.loop_null_cmp52, label %strcmp.false strcmp.loop_null_cmp46: ; preds = %strcmp.loop39 %strcmp.cmp_null50 = icmp eq i8 %39, 0 br i1 %strcmp.cmp_null50, label %pred_true.critedge, label %strcmp.loop45 strcmp.loop51: ; preds = %strcmp.loop_null_cmp52 %46 = add [64 x i8]* %str, i64 10 %probe_read59 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %46) %47 = load i8, i8* %strcmp.char_l, align 1 %48 = add [16 x i8]* %comm, i64 10 %probe_read60 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %48) %49 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp61 = icmp eq i8 %47, %49 br i1 %strcmp.cmp61, label %strcmp.loop_null_cmp58, label %strcmp.false strcmp.loop_null_cmp52: ; preds = %strcmp.loop45 %strcmp.cmp_null56 = icmp eq i8 %43, 0 br i1 %strcmp.cmp_null56, label %pred_true.critedge, label %strcmp.loop51 strcmp.loop57: ; preds = %strcmp.loop_null_cmp58 %50 = add [64 x i8]* %str, i64 11 %probe_read65 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %50) %51 = load i8, i8* %strcmp.char_l, align 1 %52 = add [16 x i8]* %comm, i64 11 %probe_read66 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %52) %53 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp67 = icmp eq i8 %51, %53 br i1 %strcmp.cmp67, label %strcmp.loop_null_cmp64, label %strcmp.false strcmp.loop_null_cmp58: ; preds = %strcmp.loop51 %strcmp.cmp_null62 = icmp eq i8 %47, 0 br i1 %strcmp.cmp_null62, label %pred_true.critedge, label %strcmp.loop57 strcmp.loop63: ; preds = %strcmp.loop_null_cmp64 %54 = add [64 x i8]* %str, i64 12 %probe_read71 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %54) %55 = load i8, i8* %strcmp.char_l, align 1 %56 = add [16 x i8]* %comm, i64 12 %probe_read72 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %56) %57 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp73 = icmp eq i8 %55, %57 br i1 %strcmp.cmp73, label %strcmp.loop_null_cmp70, label %strcmp.false strcmp.loop_null_cmp64: ; preds = %strcmp.loop57 %strcmp.cmp_null68 = icmp eq i8 %51, 0 br i1 %strcmp.cmp_null68, label %pred_true.critedge, label %strcmp.loop63 strcmp.loop69: ; preds = %strcmp.loop_null_cmp70 %58 = add [64 x i8]* %str, i64 13 %probe_read77 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %58) %59 = load i8, i8* %strcmp.char_l, align 1 %60 = add [16 x i8]* %comm, i64 13 %probe_read78 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %60) %61 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp79 = icmp eq i8 %59, %61 br i1 %strcmp.cmp79, label %strcmp.loop_null_cmp76, label %strcmp.false strcmp.loop_null_cmp70: ; preds = %strcmp.loop63 %strcmp.cmp_null74 = icmp eq i8 %55, 0 br i1 %strcmp.cmp_null74, label %pred_true.critedge, label %strcmp.loop69 strcmp.loop75: ; preds = %strcmp.loop_null_cmp76 %62 = add [64 x i8]* %str, i64 14 %probe_read83 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %62) %63 = load i8, i8* %strcmp.char_l, align 1 %64 = add [16 x i8]* %comm, i64 14 %probe_read84 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %64) %65 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp85 = icmp eq i8 %63, %65 br i1 %strcmp.cmp85, label %strcmp.loop_null_cmp82, label %strcmp.false strcmp.loop_null_cmp76: ; preds = %strcmp.loop69 %strcmp.cmp_null80 = icmp eq i8 %59, 0 br i1 %strcmp.cmp_null80, label %pred_true.critedge, label %strcmp.loop75 strcmp.loop81: ; preds = %strcmp.loop_null_cmp82 %66 = add [64 x i8]* %str, i64 15 %probe_read89 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %66) %67 = load i8, i8* %strcmp.char_l, align 1 %68 = add [16 x i8]* %comm, i64 15 %probe_read90 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %68) %69 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp91 = icmp eq i8 %67, %69 br i1 %strcmp.cmp91, label %strcmp.loop_null_cmp88, label %strcmp.false strcmp.loop_null_cmp82: ; preds = %strcmp.loop75 %strcmp.cmp_null86 = icmp eq i8 %63, 0 br i1 %strcmp.cmp_null86, label %pred_true.critedge, label %strcmp.loop81 strcmp.loop87: ; preds = %strcmp.loop_null_cmp88 %70 = add [64 x i8]* %str, i64 16 %probe_read95 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %70) %71 = load i8, i8* %strcmp.char_l, align 1 %72 = add [16 x i8]* %comm, i64 16 %probe_read96 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %72) %73 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp97 = icmp eq i8 %71, %73 br i1 %strcmp.cmp97, label %pred_true.critedge, label %strcmp.false strcmp.loop_null_cmp88: ; preds = %strcmp.loop81 %strcmp.cmp_null92 = icmp eq i8 %67, 0 br i1 %strcmp.cmp_null92, label %pred_true.critedge, label %strcmp.loop87 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"tracepoint:file:filename"(i8*) local_unnamed_addr section "s_tracepoint:file:filename_1" { entry: %"@_val" = alloca i64, align 8 %"@_key" = alloca i64, align 8 %strcmp.char_r = alloca i8, align 1 %strcmp.char_l = alloca i8, align 1 %"struct _tracepoint_file_filename.filename" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %comm = alloca [16 x i8], align 1 %1 = getelementptr inbounds [16 x i8], [16 x i8]* %comm, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i8 0, i64 16, i32 1, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm, i64 16) %2 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) call void @llvm.memset.p0i8.i64(i8* nonnull %2, i8 0, i64 64, i32 1, i1 false) %3 = add i8* %0, i64 8 %4 = bitcast i64* %"struct _tracepoint_file_filename.filename" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct _tracepoint_file_filename.filename", i64 8, i8* %3) %5 = load i64, i64* %"struct _tracepoint_file_filename.filename", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) %probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 64, i64 %5) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %strcmp.char_l) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %strcmp.char_r) %probe_read1 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* nonnull %str) %6 = load i8, i8* %strcmp.char_l, align 1 %probe_read2 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* nonnull %comm) %7 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp = icmp eq i8 %6, %7 br i1 %strcmp.cmp, label %strcmp.loop_null_cmp, label %pred_false.critedge pred_false.critedge: ; preds = %entry call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %strcmp.char_l) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %strcmp.char_r) br label %pred_false pred_false: ; preds = %strcmp.false, %pred_false.critedge ret i64 0 pred_true.critedge: ; preds = %strcmp.loop87, %strcmp.loop_null_cmp, %strcmp.loop_null_cmp4, %strcmp.loop_null_cmp10, %strcmp.loop_null_cmp16, %strcmp.loop_null_cmp22, %strcmp.loop_null_cmp28, %strcmp.loop_null_cmp34, %strcmp.loop_null_cmp40, %strcmp.loop_null_cmp46, %strcmp.loop_null_cmp52, %strcmp.loop_null_cmp58, %strcmp.loop_null_cmp64, %strcmp.loop_null_cmp70, %strcmp.loop_null_cmp76, %strcmp.loop_null_cmp82, %strcmp.loop_null_cmp88 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %strcmp.char_l) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %strcmp.char_r) %8 = bitcast i64* %"@_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %8) store i64 0, i64* %"@_key", align 8 %9 = bitcast i64* %"@_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %9) store i64 1, i64* %"@_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@_key", i64* nonnull %"@_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %8) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %9) ret i64 0 strcmp.false: ; preds = %strcmp.loop87, %strcmp.loop81, %strcmp.loop75, %strcmp.loop69, %strcmp.loop63, %strcmp.loop57, %strcmp.loop51, %strcmp.loop45, %strcmp.loop39, %strcmp.loop33, %strcmp.loop27, %strcmp.loop21, %strcmp.loop15, %strcmp.loop9, %strcmp.loop3, %strcmp.loop call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %strcmp.char_l) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %strcmp.char_r) br label %pred_false strcmp.loop: ; preds = %strcmp.loop_null_cmp %10 = add [64 x i8]* %str, i64 1 %probe_read5 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %10) %11 = load i8, i8* %strcmp.char_l, align 1 %12 = add [16 x i8]* %comm, i64 1 %probe_read6 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %12) %13 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp7 = icmp eq i8 %11, %13 br i1 %strcmp.cmp7, label %strcmp.loop_null_cmp4, label %strcmp.false strcmp.loop_null_cmp: ; preds = %entry %strcmp.cmp_null = icmp eq i8 %6, 0 br i1 %strcmp.cmp_null, label %pred_true.critedge, label %strcmp.loop strcmp.loop3: ; preds = %strcmp.loop_null_cmp4 %14 = add [64 x i8]* %str, i64 2 %probe_read11 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %14) %15 = load i8, i8* %strcmp.char_l, align 1 %16 = add [16 x i8]* %comm, i64 2 %probe_read12 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %16) %17 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp13 = icmp eq i8 %15, %17 br i1 %strcmp.cmp13, label %strcmp.loop_null_cmp10, label %strcmp.false strcmp.loop_null_cmp4: ; preds = %strcmp.loop %strcmp.cmp_null8 = icmp eq i8 %11, 0 br i1 %strcmp.cmp_null8, label %pred_true.critedge, label %strcmp.loop3 strcmp.loop9: ; preds = %strcmp.loop_null_cmp10 %18 = add [64 x i8]* %str, i64 3 %probe_read17 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %18) %19 = load i8, i8* %strcmp.char_l, align 1 %20 = add [16 x i8]* %comm, i64 3 %probe_read18 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %20) %21 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp19 = icmp eq i8 %19, %21 br i1 %strcmp.cmp19, label %strcmp.loop_null_cmp16, label %strcmp.false strcmp.loop_null_cmp10: ; preds = %strcmp.loop3 %strcmp.cmp_null14 = icmp eq i8 %15, 0 br i1 %strcmp.cmp_null14, label %pred_true.critedge, label %strcmp.loop9 strcmp.loop15: ; preds = %strcmp.loop_null_cmp16 %22 = add [64 x i8]* %str, i64 4 %probe_read23 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %22) %23 = load i8, i8* %strcmp.char_l, align 1 %24 = add [16 x i8]* %comm, i64 4 %probe_read24 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %24) %25 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp25 = icmp eq i8 %23, %25 br i1 %strcmp.cmp25, label %strcmp.loop_null_cmp22, label %strcmp.false strcmp.loop_null_cmp16: ; preds = %strcmp.loop9 %strcmp.cmp_null20 = icmp eq i8 %19, 0 br i1 %strcmp.cmp_null20, label %pred_true.critedge, label %strcmp.loop15 strcmp.loop21: ; preds = %strcmp.loop_null_cmp22 %26 = add [64 x i8]* %str, i64 5 %probe_read29 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %26) %27 = load i8, i8* %strcmp.char_l, align 1 %28 = add [16 x i8]* %comm, i64 5 %probe_read30 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %28) %29 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp31 = icmp eq i8 %27, %29 br i1 %strcmp.cmp31, label %strcmp.loop_null_cmp28, label %strcmp.false strcmp.loop_null_cmp22: ; preds = %strcmp.loop15 %strcmp.cmp_null26 = icmp eq i8 %23, 0 br i1 %strcmp.cmp_null26, label %pred_true.critedge, label %strcmp.loop21 strcmp.loop27: ; preds = %strcmp.loop_null_cmp28 %30 = add [64 x i8]* %str, i64 6 %probe_read35 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %30) %31 = load i8, i8* %strcmp.char_l, align 1 %32 = add [16 x i8]* %comm, i64 6 %probe_read36 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %32) %33 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp37 = icmp eq i8 %31, %33 br i1 %strcmp.cmp37, label %strcmp.loop_null_cmp34, label %strcmp.false strcmp.loop_null_cmp28: ; preds = %strcmp.loop21 %strcmp.cmp_null32 = icmp eq i8 %27, 0 br i1 %strcmp.cmp_null32, label %pred_true.critedge, label %strcmp.loop27 strcmp.loop33: ; preds = %strcmp.loop_null_cmp34 %34 = add [64 x i8]* %str, i64 7 %probe_read41 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %34) %35 = load i8, i8* %strcmp.char_l, align 1 %36 = add [16 x i8]* %comm, i64 7 %probe_read42 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %36) %37 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp43 = icmp eq i8 %35, %37 br i1 %strcmp.cmp43, label %strcmp.loop_null_cmp40, label %strcmp.false strcmp.loop_null_cmp34: ; preds = %strcmp.loop27 %strcmp.cmp_null38 = icmp eq i8 %31, 0 br i1 %strcmp.cmp_null38, label %pred_true.critedge, label %strcmp.loop33 strcmp.loop39: ; preds = %strcmp.loop_null_cmp40 %38 = add [64 x i8]* %str, i64 8 %probe_read47 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %38) %39 = load i8, i8* %strcmp.char_l, align 1 %40 = add [16 x i8]* %comm, i64 8 %probe_read48 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %40) %41 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp49 = icmp eq i8 %39, %41 br i1 %strcmp.cmp49, label %strcmp.loop_null_cmp46, label %strcmp.false strcmp.loop_null_cmp40: ; preds = %strcmp.loop33 %strcmp.cmp_null44 = icmp eq i8 %35, 0 br i1 %strcmp.cmp_null44, label %pred_true.critedge, label %strcmp.loop39 strcmp.loop45: ; preds = %strcmp.loop_null_cmp46 %42 = add [64 x i8]* %str, i64 9 %probe_read53 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %42) %43 = load i8, i8* %strcmp.char_l, align 1 %44 = add [16 x i8]* %comm, i64 9 %probe_read54 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %44) %45 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp55 = icmp eq i8 %43, %45 br i1 %strcmp.cmp55, label %strcmp.loop_null_cmp52, label %strcmp.false strcmp.loop_null_cmp46: ; preds = %strcmp.loop39 %strcmp.cmp_null50 = icmp eq i8 %39, 0 br i1 %strcmp.cmp_null50, label %pred_true.critedge, label %strcmp.loop45 strcmp.loop51: ; preds = %strcmp.loop_null_cmp52 %46 = add [64 x i8]* %str, i64 10 %probe_read59 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %46) %47 = load i8, i8* %strcmp.char_l, align 1 %48 = add [16 x i8]* %comm, i64 10 %probe_read60 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %48) %49 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp61 = icmp eq i8 %47, %49 br i1 %strcmp.cmp61, label %strcmp.loop_null_cmp58, label %strcmp.false strcmp.loop_null_cmp52: ; preds = %strcmp.loop45 %strcmp.cmp_null56 = icmp eq i8 %43, 0 br i1 %strcmp.cmp_null56, label %pred_true.critedge, label %strcmp.loop51 strcmp.loop57: ; preds = %strcmp.loop_null_cmp58 %50 = add [64 x i8]* %str, i64 11 %probe_read65 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %50) %51 = load i8, i8* %strcmp.char_l, align 1 %52 = add [16 x i8]* %comm, i64 11 %probe_read66 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %52) %53 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp67 = icmp eq i8 %51, %53 br i1 %strcmp.cmp67, label %strcmp.loop_null_cmp64, label %strcmp.false strcmp.loop_null_cmp58: ; preds = %strcmp.loop51 %strcmp.cmp_null62 = icmp eq i8 %47, 0 br i1 %strcmp.cmp_null62, label %pred_true.critedge, label %strcmp.loop57 strcmp.loop63: ; preds = %strcmp.loop_null_cmp64 %54 = add [64 x i8]* %str, i64 12 %probe_read71 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %54) %55 = load i8, i8* %strcmp.char_l, align 1 %56 = add [16 x i8]* %comm, i64 12 %probe_read72 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %56) %57 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp73 = icmp eq i8 %55, %57 br i1 %strcmp.cmp73, label %strcmp.loop_null_cmp70, label %strcmp.false strcmp.loop_null_cmp64: ; preds = %strcmp.loop57 %strcmp.cmp_null68 = icmp eq i8 %51, 0 br i1 %strcmp.cmp_null68, label %pred_true.critedge, label %strcmp.loop63 strcmp.loop69: ; preds = %strcmp.loop_null_cmp70 %58 = add [64 x i8]* %str, i64 13 %probe_read77 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %58) %59 = load i8, i8* %strcmp.char_l, align 1 %60 = add [16 x i8]* %comm, i64 13 %probe_read78 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %60) %61 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp79 = icmp eq i8 %59, %61 br i1 %strcmp.cmp79, label %strcmp.loop_null_cmp76, label %strcmp.false strcmp.loop_null_cmp70: ; preds = %strcmp.loop63 %strcmp.cmp_null74 = icmp eq i8 %55, 0 br i1 %strcmp.cmp_null74, label %pred_true.critedge, label %strcmp.loop69 strcmp.loop75: ; preds = %strcmp.loop_null_cmp76 %62 = add [64 x i8]* %str, i64 14 %probe_read83 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %62) %63 = load i8, i8* %strcmp.char_l, align 1 %64 = add [16 x i8]* %comm, i64 14 %probe_read84 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %64) %65 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp85 = icmp eq i8 %63, %65 br i1 %strcmp.cmp85, label %strcmp.loop_null_cmp82, label %strcmp.false strcmp.loop_null_cmp76: ; preds = %strcmp.loop69 %strcmp.cmp_null80 = icmp eq i8 %59, 0 br i1 %strcmp.cmp_null80, label %pred_true.critedge, label %strcmp.loop75 strcmp.loop81: ; preds = %strcmp.loop_null_cmp82 %66 = add [64 x i8]* %str, i64 15 %probe_read89 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %66) %67 = load i8, i8* %strcmp.char_l, align 1 %68 = add [16 x i8]* %comm, i64 15 %probe_read90 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %68) %69 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp91 = icmp eq i8 %67, %69 br i1 %strcmp.cmp91, label %strcmp.loop_null_cmp88, label %strcmp.false strcmp.loop_null_cmp82: ; preds = %strcmp.loop75 %strcmp.cmp_null86 = icmp eq i8 %63, 0 br i1 %strcmp.cmp_null86, label %pred_true.critedge, label %strcmp.loop81 strcmp.loop87: ; preds = %strcmp.loop_null_cmp88 %70 = add [64 x i8]* %str, i64 16 %probe_read95 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_l, i64 1, [64 x i8]* %70) %71 = load i8, i8* %strcmp.char_l, align 1 %72 = add [16 x i8]* %comm, i64 16 %probe_read96 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %strcmp.char_r, i64 1, [16 x i8]* %72) %73 = load i8, i8* %strcmp.char_r, align 1 %strcmp.cmp97 = icmp eq i8 %71, %73 br i1 %strcmp.cmp97, label %pred_true.critedge, label %strcmp.false strcmp.loop_null_cmp88: ; preds = %strcmp.loop81 %strcmp.cmp_null92 = icmp eq i8 %67, 0 br i1 %strcmp.cmp_null92, label %pred_true.critedge, label %strcmp.loop87 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/struct_char.cpp000066400000000000000000000140041361633214400211070ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, struct_char) { #if LLVM_VERSION_MAJOR > 6 auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo.x" = alloca i8, align 1 %"$foo" = alloca [1 x i8], align 1 %1 = getelementptr inbounds [1 x i8], [1 x i8]* %"$foo", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %1, i64 0, i64 1, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i8, i8 addrspace(64)* null, align 536870912 store i8 %2, i8* %1, align 1 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %"struct Foo.x") %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %"struct Foo.x", i64 1, [1 x i8]* nonnull %"$foo") %3 = load i8, i8* %"struct Foo.x", align 1 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %"struct Foo.x") %4 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@x_key", align 8 %5 = sext i8 %3 to i64 %6 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %5, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #else auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo.x" = alloca i8, align 1 %"$foo" = alloca [1 x i8], align 1 %1 = getelementptr inbounds [1 x i8], [1 x i8]* %"$foo", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i64 0, i64 1, i32 1, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i8, i8 addrspace(64)* null, align 536870912 store i8 %2, i8* %1, align 1 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %"struct Foo.x") %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %"struct Foo.x", i64 1, [1 x i8]* nonnull %"$foo") %3 = load i8, i8* %"struct Foo.x", align 1 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %"struct Foo.x") %4 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@x_key", align 8 %5 = sext i8 %3 to i64 %6 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %5, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #endif test("struct Foo { char x; }" "kprobe:f" "{" " $foo = (struct Foo)0;" " @x = $foo.x;" "}", expected); expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo.x" = alloca i8, align 1 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %"struct Foo.x") %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i8* nonnull %"struct Foo.x", i64 1, i64 0) %1 = load i8, i8* %"struct Foo.x", align 1 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %"struct Foo.x") %2 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %3 = sext i8 %1 to i64 %4 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %3, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; test("struct Foo { char x; }" "kprobe:f" "{" " $foo = (struct Foo*)0;" " @x = $foo->x;" "}", expected); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/struct_integer_ptr.cpp000066400000000000000000000161041361633214400225170ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, struct_integer_ptr) { #if LLVM_VERSION_MAJOR > 6 auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %deref = alloca i32, align 4 %"struct Foo.x" = alloca i64, align 8 %"$foo" = alloca i64, align 8 %tmpcast = bitcast i64* %"$foo" to [8 x i8]* %1 = bitcast i64* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 8 %1, i64 0, i64 8, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i64, i64 addrspace(64)* null, align 536870912 store i64 %2, i64* %"$foo", align 8 %3 = bitcast i64* %"struct Foo.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.x", i64 8, [8 x i8]* nonnull %tmpcast) %4 = load i64, i64* %"struct Foo.x", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i32* %deref to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) %probe_read1 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %deref, i64 4, i64 %4) %6 = load i32, i32* %deref, align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) %7 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 0, i64* %"@x_key", align 8 %8 = zext i32 %6 to i64 %9 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %9) store i64 %8, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %9) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #else auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %deref = alloca i32, align 4 %"struct Foo.x" = alloca i64, align 8 %"$foo" = alloca i64, align 8 %tmpcast = bitcast i64* %"$foo" to [8 x i8]* %1 = bitcast i64* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i64 0, i64 8, i32 8, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i64, i64 addrspace(64)* null, align 536870912 store i64 %2, i64* %"$foo", align 8 %3 = bitcast i64* %"struct Foo.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.x", i64 8, [8 x i8]* nonnull %tmpcast) %4 = load i64, i64* %"struct Foo.x", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i32* %deref to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) %probe_read1 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %deref, i64 4, i64 %4) %6 = load i32, i32* %deref, align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) %7 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 0, i64* %"@x_key", align 8 %8 = zext i32 %6 to i64 %9 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %9) store i64 %8, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %9) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #endif test("struct Foo { int *x; }" "kprobe:f" "{" " $foo = (struct Foo)0;" " @x = *$foo.x;" "}", expected); expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %deref = alloca i32, align 4 %"struct Foo.x" = alloca i64, align 8 %1 = bitcast i64* %"struct Foo.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.x", i64 8, i64 0) %2 = load i64, i64* %"struct Foo.x", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %3 = bitcast i32* %deref to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read1 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %deref, i64 4, i64 %2) %4 = load i32, i32* %deref, align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@x_key", align 8 %6 = zext i32 %4 to i64 %7 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 %6, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; test("struct Foo { int *x; }" "kprobe:f" "{" " $foo = (struct Foo*)0;" " @x = *$foo->x;" "}", expected); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/struct_integers.cpp000066400000000000000000000141451361633214400220200ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, struct_integers) { #if LLVM_VERSION_MAJOR > 6 auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo.x" = alloca i32, align 4 %"$foo" = alloca i32, align 4 %tmpcast = bitcast i32* %"$foo" to [4 x i8]* %1 = bitcast i32* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 4 %1, i64 0, i64 4, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i32, i32 addrspace(64)* null, align 536870912 store i32 %2, i32* %"$foo", align 4 %3 = bitcast i32* %"struct Foo.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Foo.x", i64 4, [4 x i8]* nonnull %tmpcast) %4 = load i32, i32* %"struct Foo.x", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@x_key", align 8 %6 = sext i32 %4 to i64 %7 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 %6, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #else auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo.x" = alloca i32, align 4 %"$foo" = alloca i32, align 4 %tmpcast = bitcast i32* %"$foo" to [4 x i8]* %1 = bitcast i32* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i64 0, i64 4, i32 4, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i32, i32 addrspace(64)* null, align 536870912 store i32 %2, i32* %"$foo", align 4 %3 = bitcast i32* %"struct Foo.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Foo.x", i64 4, [4 x i8]* nonnull %tmpcast) %4 = load i32, i32* %"struct Foo.x", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@x_key", align 8 %6 = sext i32 %4 to i64 %7 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 %6, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #endif test("struct Foo { int x; }" "kprobe:f" "{" " $foo = (struct Foo)0;" " @x = $foo.x;" "}", expected); expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo.x" = alloca i32, align 4 %1 = bitcast i32* %"struct Foo.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Foo.x", i64 4, i64 0) %2 = load i32, i32* %"struct Foo.x", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %4 = sext i32 %2 to i64 %5 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 %4, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; test("struct Foo { int x; }" "kprobe:f" "{" " $foo = (struct Foo*)0;" " @x = $foo->x;" "}", expected); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/struct_long.cpp000066400000000000000000000140251361633214400211340ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, struct_long) { #if LLVM_VERSION_MAJOR > 6 auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo.x" = alloca i64, align 8 %"$foo" = alloca i64, align 8 %tmpcast = bitcast i64* %"$foo" to [8 x i8]* %1 = bitcast i64* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 8 %1, i64 0, i64 8, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i64, i64 addrspace(64)* null, align 536870912 store i64 %2, i64* %"$foo", align 8 %3 = bitcast i64* %"struct Foo.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.x", i64 8, [8 x i8]* nonnull %tmpcast) %4 = load i64, i64* %"struct Foo.x", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@x_key", align 8 %6 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %4, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #else auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo.x" = alloca i64, align 8 %"$foo" = alloca i64, align 8 %tmpcast = bitcast i64* %"$foo" to [8 x i8]* %1 = bitcast i64* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i64 0, i64 8, i32 8, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i64, i64 addrspace(64)* null, align 536870912 store i64 %2, i64* %"$foo", align 8 %3 = bitcast i64* %"struct Foo.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.x", i64 8, [8 x i8]* nonnull %tmpcast) %4 = load i64, i64* %"struct Foo.x", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@x_key", align 8 %6 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %4, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #endif test("struct Foo { long x; }" "kprobe:f" "{" " $foo = (struct Foo)0;" " @x = $foo.x;" "}", expected); expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo.x" = alloca i64, align 8 %1 = bitcast i64* %"struct Foo.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.x", i64 8, i64 0) %2 = load i64, i64* %"struct Foo.x", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %4 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %2, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; test("struct Foo { long x; }" "kprobe:f" "{" " $foo = (struct Foo*)0;" " @x = $foo->x;" "}", expected); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/struct_nested_struct_anon.cpp000066400000000000000000000150741361633214400241030ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, struct_nested_struct_anon) { #if LLVM_VERSION_MAJOR > 6 auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo::(anonymous at definitions.h:1:14).x" = alloca i32, align 4 %"$foo" = alloca i32, align 4 %tmpcast = bitcast i32* %"$foo" to [4 x i8]* %1 = bitcast i32* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 4 %1, i64 0, i64 4, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i32, i32 addrspace(64)* null, align 536870912 store i32 %2, i32* %"$foo", align 4 %3 = bitcast i32* %"struct Foo::(anonymous at definitions.h:1:14).x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Foo::(anonymous at definitions.h:1:14).x", i64 4, [4 x i8]* nonnull %tmpcast) %4 = load i32, i32* %"struct Foo::(anonymous at definitions.h:1:14).x", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@x_key", align 8 %6 = sext i32 %4 to i64 %7 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 %6, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #else auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo::(anonymous at definitions.h:1:14).x" = alloca i32, align 4 %"$foo" = alloca i32, align 4 %tmpcast = bitcast i32* %"$foo" to [4 x i8]* %1 = bitcast i32* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i64 0, i64 4, i32 4, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i32, i32 addrspace(64)* null, align 536870912 store i32 %2, i32* %"$foo", align 4 %3 = bitcast i32* %"struct Foo::(anonymous at definitions.h:1:14).x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Foo::(anonymous at definitions.h:1:14).x", i64 4, [4 x i8]* nonnull %tmpcast) %4 = load i32, i32* %"struct Foo::(anonymous at definitions.h:1:14).x", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@x_key", align 8 %6 = sext i32 %4 to i64 %7 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 %6, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #endif test("struct Foo { struct { int x; } bar; }" "kprobe:f" "{" " $foo = (struct Foo)0;" " @x = $foo.bar.x;" "}", expected); expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo::(anonymous at definitions.h:1:14).x" = alloca i32, align 4 %1 = bitcast i32* %"struct Foo::(anonymous at definitions.h:1:14).x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Foo::(anonymous at definitions.h:1:14).x", i64 4, i64 0) %2 = load i32, i32* %"struct Foo::(anonymous at definitions.h:1:14).x", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %4 = sext i32 %2 to i64 %5 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 %4, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; test("struct Foo { struct { int x; } bar; }" "kprobe:f" "{" " $foo = (struct Foo*)0;" " @x = $foo->bar.x;" "}", expected); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/struct_nested_struct_named.cpp000066400000000000000000000142661361633214400242360ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, struct_nested_struct_named) { #if LLVM_VERSION_MAJOR > 6 auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Bar.x" = alloca i32, align 4 %"$foo" = alloca i32, align 4 %tmpcast = bitcast i32* %"$foo" to [4 x i8]* %1 = bitcast i32* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 4 %1, i64 0, i64 4, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i32, i32 addrspace(64)* null, align 536870912 store i32 %2, i32* %"$foo", align 4 %3 = bitcast i32* %"struct Bar.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Bar.x", i64 4, [4 x i8]* nonnull %tmpcast) %4 = load i32, i32* %"struct Bar.x", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@x_key", align 8 %6 = sext i32 %4 to i64 %7 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 %6, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #else auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Bar.x" = alloca i32, align 4 %"$foo" = alloca i32, align 4 %tmpcast = bitcast i32* %"$foo" to [4 x i8]* %1 = bitcast i32* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i64 0, i64 4, i32 4, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i32, i32 addrspace(64)* null, align 536870912 store i32 %2, i32* %"$foo", align 4 %3 = bitcast i32* %"struct Bar.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Bar.x", i64 4, [4 x i8]* nonnull %tmpcast) %4 = load i32, i32* %"struct Bar.x", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@x_key", align 8 %6 = sext i32 %4 to i64 %7 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 %6, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #endif test("struct Bar { int x; } struct Foo { struct Bar bar; }" "kprobe:f" "{" " $foo = (struct Foo)0;" " @x = $foo.bar.x;" "}", expected); expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Bar.x" = alloca i32, align 4 %1 = bitcast i32* %"struct Bar.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Bar.x", i64 4, i64 0) %2 = load i32, i32* %"struct Bar.x", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %4 = sext i32 %2 to i64 %5 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 %4, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; test("struct Bar { int x; } struct Foo { struct Bar bar; }" "kprobe:f" "{" " $foo = (struct Foo*)0;" " @x = $foo->bar.x;" "}", expected); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/struct_nested_struct_ptr_named.cpp000066400000000000000000000164321361633214400251200ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, struct_nested_struct_ptr_named) { #if LLVM_VERSION_MAJOR > 6 auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Bar.x" = alloca i32, align 4 %"struct Foo.bar" = alloca i64, align 8 %"$foo" = alloca i64, align 8 %tmpcast = bitcast i64* %"$foo" to [8 x i8]* %1 = bitcast i64* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 8 %1, i64 0, i64 8, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i64, i64 addrspace(64)* null, align 536870912 store i64 %2, i64* %"$foo", align 8 %3 = bitcast i64* %"struct Foo.bar" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.bar", i64 8, [8 x i8]* nonnull %tmpcast) %4 = load i64, i64* %"struct Foo.bar", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i32* %"struct Bar.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) %probe_read1 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Bar.x", i64 4, i64 %4) %6 = load i32, i32* %"struct Bar.x", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) %7 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 0, i64* %"@x_key", align 8 %8 = sext i32 %6 to i64 %9 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %9) store i64 %8, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %9) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #else auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Bar.x" = alloca i32, align 4 %"struct Foo.bar" = alloca i64, align 8 %"$foo" = alloca i64, align 8 %tmpcast = bitcast i64* %"$foo" to [8 x i8]* %1 = bitcast i64* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i64 0, i64 8, i32 8, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i64, i64 addrspace(64)* null, align 536870912 store i64 %2, i64* %"$foo", align 8 %3 = bitcast i64* %"struct Foo.bar" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.bar", i64 8, [8 x i8]* nonnull %tmpcast) %4 = load i64, i64* %"struct Foo.bar", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i32* %"struct Bar.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) %probe_read1 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Bar.x", i64 4, i64 %4) %6 = load i32, i32* %"struct Bar.x", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) %7 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 0, i64* %"@x_key", align 8 %8 = sext i32 %6 to i64 %9 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %9) store i64 %8, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %9) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #endif test("struct Bar { int x; } struct Foo { struct Bar *bar; }" "kprobe:f" "{" " $foo = (struct Foo)0;" " @x = $foo.bar->x;" "}", expected); expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Bar.x" = alloca i32, align 4 %"struct Foo.bar" = alloca i64, align 8 %1 = bitcast i64* %"struct Foo.bar" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.bar", i64 8, i64 0) %2 = load i64, i64* %"struct Foo.bar", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %3 = bitcast i32* %"struct Bar.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read1 = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i32* nonnull %"struct Bar.x", i64 4, i64 %2) %4 = load i32, i32* %"struct Bar.x", align 4 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@x_key", align 8 %6 = sext i32 %4 to i64 %7 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 %6, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; test("struct Bar { int x; } struct Foo { struct Bar *bar; }" "kprobe:f" "{" " $foo = (struct Foo*)0;" " @x = $foo->bar->x;" "}", expected); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/struct_save.cpp000066400000000000000000000033111361633214400211270ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, struct_save) { auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@foo_val" = alloca [12 x i8], align 1 %"@foo_key" = alloca i64, align 8 %1 = bitcast i64* %"@foo_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@foo_key", align 8 %2 = getelementptr inbounds [12 x i8], [12 x i8]* %"@foo_val", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([12 x i8]* nonnull %"@foo_val", i64 12, i64 0) %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [12 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@foo_key", [12 x i8]* nonnull %"@foo_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; test("struct Foo { int x, y, z; }" "kprobe:f" "{" " @foo = (struct Foo)0;" "}", expected); test("struct Foo { int x, y, z; }" "kprobe:f" "{" " @foo = *(struct Foo*)0;" "}", expected); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/struct_save_nested.cpp000066400000000000000000000127501361633214400225000ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, struct_save_nested) { auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"@foo_key5" = alloca i64, align 8 %"@bar_key" = alloca i64, align 8 %"internal_struct Foo.bar" = alloca i64, align 8 %tmpcast = bitcast i64* %"internal_struct Foo.bar" to [8 x i8]* %"@foo_key1" = alloca i64, align 8 %"@foo_val" = alloca [16 x i8], align 1 %"@foo_key" = alloca i64, align 8 %1 = bitcast i64* %"@foo_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@foo_key", align 8 %2 = getelementptr inbounds [16 x i8], [16 x i8]* %"@foo_val", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([16 x i8]* nonnull %"@foo_val", i64 16, i64 0) %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [16 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@foo_key", [16 x i8]* nonnull %"@foo_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %3 = bitcast i64* %"@foo_key1" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@foo_key1", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo2, i64* nonnull %"@foo_key1") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %lookup_elem_val.sroa.3.0.lookup_elem.sroa_idx = getelementptr inbounds i8, i8* %lookup_elem, i64 4 %lookup_elem_val.sroa.3.0.lookup_elem.sroa_cast = bitcast i8* %lookup_elem_val.sroa.3.0.lookup_elem.sroa_idx to i64* %lookup_elem_val.sroa.3.0.copyload = load i64, i64* %lookup_elem_val.sroa.3.0.lookup_elem.sroa_cast, align 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.sroa.3.0 = phi i64 [ %lookup_elem_val.sroa.3.0.copyload, %lookup_success ], [ 0, %entry ] call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %4 = bitcast i64* %"internal_struct Foo.bar" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 %lookup_elem_val.sroa.3.0, i64* %"internal_struct Foo.bar", align 8 %5 = bitcast i64* %"@bar_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@bar_key", align 8 %pseudo3 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem4 = call i64 inttoptr (i64 2 to i64 (i64, i64*, [8 x i8]*, i64)*)(i64 %pseudo3, i64* nonnull %"@bar_key", [8 x i8]* nonnull %tmpcast, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) %6 = bitcast i64* %"@foo_key5" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 0, i64* %"@foo_key5", align 8 %pseudo6 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %lookup_elem7 = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo6, i64* nonnull %"@foo_key5") %map_lookup_cond12 = icmp eq i8* %lookup_elem7, null br i1 %map_lookup_cond12, label %lookup_merge10, label %lookup_success8 lookup_success8: ; preds = %lookup_merge %lookup_elem_val11.sroa.3.0.lookup_elem7.sroa_idx = getelementptr inbounds i8, i8* %lookup_elem7, i64 4 %lookup_elem_val11.sroa.3.0.lookup_elem7.sroa_cast = bitcast i8* %lookup_elem_val11.sroa.3.0.lookup_elem7.sroa_idx to i64* %lookup_elem_val11.sroa.3.0.copyload = load i64, i64* %lookup_elem_val11.sroa.3.0.lookup_elem7.sroa_cast, align 1 %sext = shl i64 %lookup_elem_val11.sroa.3.0.copyload, 32 %phitmp17 = ashr exact i64 %sext, 32 br label %lookup_merge10 lookup_merge10: ; preds = %lookup_merge, %lookup_success8 %lookup_elem_val11.sroa.3.0 = phi i64 [ %phitmp17, %lookup_success8 ], [ 0, %lookup_merge ] call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) %7 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 0, i64* %"@x_key", align 8 %8 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %8) store i64 %lookup_elem_val11.sroa.3.0, i64* %"@x_val", align 8 %pseudo14 = call i64 @llvm.bpf.pseudo(i64 1, i64 3) %update_elem15 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo14, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %8) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; test("struct Foo { int m; struct { int x; int y; } bar; int n; }" "kprobe:f" "{" " @foo = (struct Foo)0;" " @bar = @foo.bar;" " @x = @foo.bar.x;" "}", expected); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/struct_save_string.cpp000066400000000000000000000152731361633214400225270ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, struct_save_string) { #if LLVM_VERSION_MAJOR > 6 auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@str_key" = alloca i64, align 8 %lookup_elem_val = alloca [32 x i8], align 1 %"@foo_key1" = alloca i64, align 8 %"@foo_val" = alloca [32 x i8], align 1 %"@foo_key" = alloca i64, align 8 %1 = bitcast i64* %"@foo_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@foo_key", align 8 %2 = getelementptr inbounds [32 x i8], [32 x i8]* %"@foo_val", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([32 x i8]* nonnull %"@foo_val", i64 32, i64 0) %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [32 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@foo_key", [32 x i8]* nonnull %"@foo_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %3 = bitcast i64* %"@foo_key1" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@foo_key1", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo2, i64* nonnull %"@foo_key1") %4 = getelementptr inbounds [32 x i8], [32 x i8]* %lookup_elem_val, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_failure, label %lookup_success lookup_success: ; preds = %entry call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 1 %4, i8* nonnull align 1 %lookup_elem, i64 32, i1 false) br label %lookup_merge lookup_failure: ; preds = %entry call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %4, i8 0, i64 32, i1 false) br label %lookup_merge lookup_merge: ; preds = %lookup_failure, %lookup_success call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@str_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@str_key", align 8 %pseudo3 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem4 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i8*, i64)*)(i64 %pseudo3, i64* nonnull %"@str_key", i8* nonnull %4, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture writeonly, i8* nocapture readonly, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #else auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@str_key" = alloca i64, align 8 %lookup_elem_val = alloca [32 x i8], align 1 %"@foo_key1" = alloca i64, align 8 %"@foo_val" = alloca [32 x i8], align 1 %"@foo_key" = alloca i64, align 8 %1 = bitcast i64* %"@foo_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@foo_key", align 8 %2 = getelementptr inbounds [32 x i8], [32 x i8]* %"@foo_val", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([32 x i8]* nonnull %"@foo_val", i64 32, i64 0) %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [32 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@foo_key", [32 x i8]* nonnull %"@foo_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %3 = bitcast i64* %"@foo_key1" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@foo_key1", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo2, i64* nonnull %"@foo_key1") %4 = getelementptr inbounds [32 x i8], [32 x i8]* %lookup_elem_val, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_failure, label %lookup_success lookup_success: ; preds = %entry call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull %4, i8* nonnull %lookup_elem, i64 32, i32 1, i1 false) br label %lookup_merge lookup_failure: ; preds = %entry call void @llvm.memset.p0i8.i64(i8* nonnull %4, i8 0, i64 32, i32 1, i1 false) br label %lookup_merge lookup_merge: ; preds = %lookup_failure, %lookup_success call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@str_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@str_key", align 8 %pseudo3 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem4 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i8*, i64)*)(i64 %pseudo3, i64* nonnull %"@str_key", i8* nonnull %4, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture writeonly, i8* nocapture readonly, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #endif test("struct Foo { char str[32]; }" "kprobe:f" "{" " @foo = (struct Foo)0;" " @str = @foo.str;" "}", expected); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/struct_semicolon.cpp000066400000000000000000000036701361633214400221710ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, struct_semicolon) { auto expected = R"EXPECTED(%printf_t = type { i64, i64 } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8*) local_unnamed_addr section "s_kprobe:f_1" { entry: %printf_args = alloca %printf_t, align 8 %1 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0 store i64 0, i64* %2, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_stackid = tail call i64 inttoptr (i64 27 to i64 (i8*, i8*, i64)*)(i8* %0, i64 %pseudo, i64 256) %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %3 = shl i64 %get_pid_tgid, 32 %4 = or i64 %3, %get_stackid %5 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1 store i64 %4, i64* %5, align 8 %pseudo1 = tail call i64 @llvm.bpf.pseudo(i64 1, i64 2) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t*, i64)*)(i8* %0, i64 %pseudo1, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 16) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; test("struct Foo { int x, y; char *str; };" "k:f" "{" " printf(\"%s\\n\", ustack);" "}", expected); test("struct Foo { int x, y; char *str; }" "k:f" "{" " printf(\"%s\\n\", ustack);" "}", expected); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/struct_short.cpp000066400000000000000000000141461361633214400213400ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, struct_short) { #if LLVM_VERSION_MAJOR > 6 auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo.x" = alloca i16, align 2 %"$foo" = alloca i16, align 2 %tmpcast = bitcast i16* %"$foo" to [2 x i8]* %1 = bitcast i16* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 2 %1, i64 0, i64 2, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i16, i16 addrspace(64)* null, align 536870912 store i16 %2, i16* %"$foo", align 2 %3 = bitcast i16* %"struct Foo.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i16* nonnull %"struct Foo.x", i64 2, [2 x i8]* nonnull %tmpcast) %4 = load i16, i16* %"struct Foo.x", align 2 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@x_key", align 8 %6 = sext i16 %4 to i64 %7 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 %6, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #else auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo.x" = alloca i16, align 2 %"$foo" = alloca i16, align 2 %tmpcast = bitcast i16* %"$foo" to [2 x i8]* %1 = bitcast i16* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i64 0, i64 2, i32 2, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i16, i16 addrspace(64)* null, align 536870912 store i16 %2, i16* %"$foo", align 2 %3 = bitcast i16* %"struct Foo.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i16* nonnull %"struct Foo.x", i64 2, [2 x i8]* nonnull %tmpcast) %4 = load i16, i16* %"struct Foo.x", align 2 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@x_key", align 8 %6 = sext i16 %4 to i64 %7 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 %6, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #endif test("struct Foo { short x; }" "kprobe:f" "{" " $foo = (struct Foo)0;" " @x = $foo.x;" "}", expected); expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"struct Foo.x" = alloca i16, align 2 %1 = bitcast i16* %"struct Foo.x" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i16* nonnull %"struct Foo.x", i64 2, i64 0) %2 = load i16, i16* %"struct Foo.x", align 2 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %4 = sext i16 %2 to i64 %5 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 %4, i64* %"@x_val", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; test("struct Foo { short x; }" "kprobe:f" "{" " $foo = (struct Foo*)0;" " @x = $foo->x;" "}", expected); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/struct_string_array.cpp000066400000000000000000000135431361633214400227050ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, struct_string_array) { #if LLVM_VERSION_MAJOR > 6 auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@mystr_key" = alloca i64, align 8 %"struct Foo.str" = alloca [32 x i8], align 1 %"$foo" = alloca [32 x i8], align 1 %1 = getelementptr inbounds [32 x i8], [32 x i8]* %"$foo", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %1, i64 0, i64 32, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memcpy.p0i8.p64i8.i64(i8* nonnull align 1 %1, i8 addrspace(64)* align 536870912 null, i64 32, i1 false) %2 = getelementptr inbounds [32 x i8], [32 x i8]* %"struct Foo.str", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([32 x i8]* nonnull %"struct Foo.str", i64 32, [32 x i8]* nonnull %"$foo") %3 = bitcast i64* %"@mystr_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@mystr_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [32 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@mystr_key", [32 x i8]* nonnull %"struct Foo.str", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memcpy.p0i8.p64i8.i64(i8* nocapture writeonly, i8 addrspace(64)* nocapture readonly, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #else auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@mystr_key" = alloca i64, align 8 %"struct Foo.str" = alloca [32 x i8], align 1 %"$foo" = alloca [32 x i8], align 1 %1 = getelementptr inbounds [32 x i8], [32 x i8]* %"$foo", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i64 0, i64 32, i32 1, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memcpy.p0i8.p64i8.i64(i8* nonnull %1, i8 addrspace(64)* null, i64 32, i32 1, i1 false) %2 = getelementptr inbounds [32 x i8], [32 x i8]* %"struct Foo.str", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([32 x i8]* nonnull %"struct Foo.str", i64 32, [32 x i8]* nonnull %"$foo") %3 = bitcast i64* %"@mystr_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@mystr_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [32 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@mystr_key", [32 x i8]* nonnull %"struct Foo.str", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memcpy.p0i8.p64i8.i64(i8* nocapture writeonly, i8 addrspace(64)* nocapture readonly, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #endif test("struct Foo { char str[32]; }" "kprobe:f" "{" " $foo = (struct Foo)0;" " @mystr = $foo.str;" "}", expected); expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@mystr_key" = alloca i64, align 8 %"struct Foo.str" = alloca [32 x i8], align 1 %1 = getelementptr inbounds [32 x i8], [32 x i8]* %"struct Foo.str", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)([32 x i8]* nonnull %"struct Foo.str", i64 32, i64 0) %2 = bitcast i64* %"@mystr_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@mystr_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [32 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@mystr_key", [32 x i8]* nonnull %"struct Foo.str", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; test("struct Foo { char str[32]; }" "kprobe:f" "{" " $foo = (struct Foo*)0;" " @mystr = $foo->str;" "}", expected); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/struct_string_ptr.cpp000066400000000000000000000212361361633214400223720ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, struct_string_ptr) { #if LLVM_VERSION_MAJOR > 6 auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@mystr_key" = alloca i64, align 8 %"struct Foo.str" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %"$foo" = alloca i64, align 8 %tmpcast = bitcast i64* %"$foo" to [8 x i8]* %1 = bitcast i64* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 8 %1, i64 0, i64 8, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i64, i64 addrspace(64)* null, align 536870912 store i64 %2, i64* %"$foo", align 8 %3 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %3, i8 0, i64 64, i1 false) %4 = bitcast i64* %"struct Foo.str" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.str", i64 8, [8 x i8]* nonnull %tmpcast) %5 = load i64, i64* %"struct Foo.str", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) %probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 64, i64 %5) %6 = bitcast i64* %"@mystr_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 0, i64* %"@mystr_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@mystr_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #else auto expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@mystr_key" = alloca i64, align 8 %"struct Foo.str" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %"$foo" = alloca i64, align 8 %tmpcast = bitcast i64* %"$foo" to [8 x i8]* %1 = bitcast i64* %"$foo" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i64 0, i64 8, i32 8, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = load i64, i64 addrspace(64)* null, align 536870912 store i64 %2, i64* %"$foo", align 8 %3 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) call void @llvm.memset.p0i8.i64(i8* nonnull %3, i8 0, i64 64, i32 1, i1 false) %4 = bitcast i64* %"struct Foo.str" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.str", i64 8, [8 x i8]* nonnull %tmpcast) %5 = load i64, i64* %"struct Foo.str", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) %probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 64, i64 %5) %6 = bitcast i64* %"@mystr_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 0, i64* %"@mystr_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@mystr_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #endif test("struct Foo { char *str; }" "kprobe:f" "{" " $foo = (struct Foo)0;" " @mystr = str($foo.str);" "}", expected); #if LLVM_VERSION_MAJOR > 6 expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@mystr_key" = alloca i64, align 8 %"struct Foo.str" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %1, i8 0, i64 64, i1 false) %2 = bitcast i64* %"struct Foo.str" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.str", i64 8, i64 0) %3 = load i64, i64* %"struct Foo.str", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 64, i64 %3) %4 = bitcast i64* %"@mystr_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@mystr_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@mystr_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #else expected = R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@mystr_key" = alloca i64, align 8 %"struct Foo.str" = alloca i64, align 8 %str = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %str, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i8 0, i64 64, i32 1, i1 false) %2 = bitcast i64* %"struct Foo.str" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) %probe_read = call i64 inttoptr (i64 4 to i64 (i8*, i64, i8*)*)(i64* nonnull %"struct Foo.str", i64 8, i64 0) %3 = load i64, i64* %"struct Foo.str", align 8 call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %probe_read_str = call i64 inttoptr (i64 45 to i64 (i8*, i64, i8*)*)([64 x i8]* nonnull %str, i64 64, i64 %3) %4 = bitcast i64* %"@mystr_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@mystr_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@mystr_key", [64 x i8]* nonnull %str, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"; #endif test("struct Foo { char *str; }" "kprobe:f" "{" " $foo = (struct Foo*)0;" " @mystr = str($foo->str);" "}", expected); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/ternary_int.cpp000066400000000000000000000027631361633214400211350ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, ternary_int) { test("kprobe:f { @x = pid < 10000 ? 1 : 2; }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_val" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %1 = icmp ult i64 %get_pid_tgid, 42949672960000 %. = select i1 %1, i64 1, i64 2 %2 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@x_key", align 8 %3 = bitcast i64* %"@x_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 %., i64* %"@x_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", i64* nonnull %"@x_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/ternary_str.cpp000066400000000000000000000160051361633214400211450ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, ternary_str) { test("kprobe:f { @x = pid < 10000 ? \"lo\" : \"hi\"; }", #if LLVM_VERSION_MAJOR > 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %buf = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %2 = icmp ult i64 %get_pid_tgid, 42949672960000 br i1 %2, label %left, label %right left: ; preds = %entry store i8 108, i8* %1, align 1 %str.sroa.4.0..sroa_idx = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 1 store i8 111, i8* %str.sroa.4.0..sroa_idx, align 1 %str.sroa.5.0..sroa_idx = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 2 call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %str.sroa.5.0..sroa_idx, i8 0, i64 61, i1 false) br label %done right: ; preds = %entry store i8 104, i8* %1, align 1 %str1.sroa.4.0..sroa_idx = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 1 store i8 105, i8* %str1.sroa.4.0..sroa_idx, align 1 %str1.sroa.5.0..sroa_idx = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 2 call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %str1.sroa.5.0..sroa_idx, i8 0, i64 61, i1 false) br label %done done: ; preds = %right, %left %3 = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 63 store i8 0, i8* %3, align 1 %4 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %buf, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #elif LLVM_VERSION_MAJOR == 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %buf = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %2 = icmp ult i64 %get_pid_tgid, 42949672960000 br i1 %2, label %left, label %right left: ; preds = %entry store i8 108, i8* %1, align 1 %str.sroa.4.0..sroa_idx = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 1 store i8 111, i8* %str.sroa.4.0..sroa_idx, align 1 %str.sroa.5.0..sroa_idx = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 2 call void @llvm.memset.p0i8.i64(i8* nonnull %str.sroa.5.0..sroa_idx, i8 0, i64 61, i32 1, i1 false) br label %done right: ; preds = %entry store i8 104, i8* %1, align 1 %str1.sroa.4.0..sroa_idx = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 1 store i8 105, i8* %str1.sroa.4.0..sroa_idx, align 1 %str1.sroa.5.0..sroa_idx = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 2 call void @llvm.memset.p0i8.i64(i8* nonnull %str1.sroa.5.0..sroa_idx, i8 0, i64 61, i32 1, i1 false) br label %done done: ; preds = %right, %left %3 = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 63 store i8 0, i8* %3, align 1 %4 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %buf, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@x_key" = alloca i64, align 8 %buf = alloca [64 x i8], align 1 %1 = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %get_pid_tgid = tail call i64 inttoptr (i64 14 to i64 ()*)() %2 = icmp ult i64 %get_pid_tgid, 42949672960000 %.sink128 = select i1 %2, i8 108, i8 104 %.sink = select i1 %2, i8 111, i8 105 store i8 %.sink128, i8* %1, align 1 %str1.sroa.4.0..sroa_idx = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 1 store i8 %.sink, i8* %str1.sroa.4.0..sroa_idx, align 1 %str1.sroa.5.0..sroa_idx = getelementptr inbounds [64 x i8], [64 x i8]* %buf, i64 0, i64 2 %3 = bitcast i64* %"@x_key" to i8* call void @llvm.memset.p0i8.i64(i8* %str1.sroa.5.0..sroa_idx, i8 0, i64 62, i32 1, i1 false) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [64 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [64 x i8]* nonnull %buf, i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/unroll.cpp000066400000000000000000000223741361633214400201120ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, unroll) { test("BEGIN { @i = 0; unroll(5) { @i += 1 } }", R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @BEGIN(i8* nocapture readnone) local_unnamed_addr section "s_BEGIN_1" { entry: %"@i_val56" = alloca i64, align 8 %"@i_key55" = alloca i64, align 8 %"@i_key46" = alloca i64, align 8 %"@i_val43" = alloca i64, align 8 %"@i_key42" = alloca i64, align 8 %"@i_key33" = alloca i64, align 8 %"@i_val30" = alloca i64, align 8 %"@i_key29" = alloca i64, align 8 %"@i_key20" = alloca i64, align 8 %"@i_val17" = alloca i64, align 8 %"@i_key16" = alloca i64, align 8 %"@i_key7" = alloca i64, align 8 %"@i_val4" = alloca i64, align 8 %"@i_key3" = alloca i64, align 8 %"@i_key1" = alloca i64, align 8 %"@i_val" = alloca i64, align 8 %"@i_key" = alloca i64, align 8 %1 = bitcast i64* %"@i_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) store i64 0, i64* %"@i_key", align 8 %2 = bitcast i64* %"@i_val" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) store i64 0, i64* %"@i_val", align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo, i64* nonnull %"@i_key", i64* nonnull %"@i_val", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %3 = bitcast i64* %"@i_key1" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@i_key1", align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo2, i64* nonnull %"@i_key1") %map_lookup_cond = icmp eq i8* %lookup_elem, null br i1 %map_lookup_cond, label %lookup_merge, label %lookup_success lookup_success: ; preds = %entry %cast = bitcast i8* %lookup_elem to i64* %4 = load i64, i64* %cast, align 8 %phitmp = add i64 %4, 1 br label %lookup_merge lookup_merge: ; preds = %entry, %lookup_success %lookup_elem_val.0 = phi i64 [ %phitmp, %lookup_success ], [ 1, %entry ] call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %5 = bitcast i64* %"@i_key3" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %5) store i64 0, i64* %"@i_key3", align 8 %6 = bitcast i64* %"@i_val4" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %6) store i64 %lookup_elem_val.0, i64* %"@i_val4", align 8 %pseudo5 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem6 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo5, i64* nonnull %"@i_key3", i64* nonnull %"@i_val4", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %5) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %6) %7 = bitcast i64* %"@i_key7" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) store i64 0, i64* %"@i_key7", align 8 %pseudo8 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem9 = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo8, i64* nonnull %"@i_key7") %map_lookup_cond14 = icmp eq i8* %lookup_elem9, null br i1 %map_lookup_cond14, label %lookup_merge12, label %lookup_success10 lookup_success10: ; preds = %lookup_merge %cast15 = bitcast i8* %lookup_elem9 to i64* %8 = load i64, i64* %cast15, align 8 %phitmp59 = add i64 %8, 1 br label %lookup_merge12 lookup_merge12: ; preds = %lookup_merge, %lookup_success10 %lookup_elem_val13.0 = phi i64 [ %phitmp59, %lookup_success10 ], [ 1, %lookup_merge ] call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) %9 = bitcast i64* %"@i_key16" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %9) store i64 0, i64* %"@i_key16", align 8 %10 = bitcast i64* %"@i_val17" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %10) store i64 %lookup_elem_val13.0, i64* %"@i_val17", align 8 %pseudo18 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem19 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo18, i64* nonnull %"@i_key16", i64* nonnull %"@i_val17", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %9) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %10) %11 = bitcast i64* %"@i_key20" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %11) store i64 0, i64* %"@i_key20", align 8 %pseudo21 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem22 = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo21, i64* nonnull %"@i_key20") %map_lookup_cond27 = icmp eq i8* %lookup_elem22, null br i1 %map_lookup_cond27, label %lookup_merge25, label %lookup_success23 lookup_success23: ; preds = %lookup_merge12 %cast28 = bitcast i8* %lookup_elem22 to i64* %12 = load i64, i64* %cast28, align 8 %phitmp60 = add i64 %12, 1 br label %lookup_merge25 lookup_merge25: ; preds = %lookup_merge12, %lookup_success23 %lookup_elem_val26.0 = phi i64 [ %phitmp60, %lookup_success23 ], [ 1, %lookup_merge12 ] call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %11) %13 = bitcast i64* %"@i_key29" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %13) store i64 0, i64* %"@i_key29", align 8 %14 = bitcast i64* %"@i_val30" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %14) store i64 %lookup_elem_val26.0, i64* %"@i_val30", align 8 %pseudo31 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem32 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo31, i64* nonnull %"@i_key29", i64* nonnull %"@i_val30", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %13) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %14) %15 = bitcast i64* %"@i_key33" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %15) store i64 0, i64* %"@i_key33", align 8 %pseudo34 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem35 = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo34, i64* nonnull %"@i_key33") %map_lookup_cond40 = icmp eq i8* %lookup_elem35, null br i1 %map_lookup_cond40, label %lookup_merge38, label %lookup_success36 lookup_success36: ; preds = %lookup_merge25 %cast41 = bitcast i8* %lookup_elem35 to i64* %16 = load i64, i64* %cast41, align 8 %phitmp61 = add i64 %16, 1 br label %lookup_merge38 lookup_merge38: ; preds = %lookup_merge25, %lookup_success36 %lookup_elem_val39.0 = phi i64 [ %phitmp61, %lookup_success36 ], [ 1, %lookup_merge25 ] call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %15) %17 = bitcast i64* %"@i_key42" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %17) store i64 0, i64* %"@i_key42", align 8 %18 = bitcast i64* %"@i_val43" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %18) store i64 %lookup_elem_val39.0, i64* %"@i_val43", align 8 %pseudo44 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem45 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo44, i64* nonnull %"@i_key42", i64* nonnull %"@i_val43", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %17) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %18) %19 = bitcast i64* %"@i_key46" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %19) store i64 0, i64* %"@i_key46", align 8 %pseudo47 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %lookup_elem48 = call i8* inttoptr (i64 1 to i8* (i64, i64*)*)(i64 %pseudo47, i64* nonnull %"@i_key46") %map_lookup_cond53 = icmp eq i8* %lookup_elem48, null br i1 %map_lookup_cond53, label %lookup_merge51, label %lookup_success49 lookup_success49: ; preds = %lookup_merge38 %cast54 = bitcast i8* %lookup_elem48 to i64* %20 = load i64, i64* %cast54, align 8 %phitmp62 = add i64 %20, 1 br label %lookup_merge51 lookup_merge51: ; preds = %lookup_merge38, %lookup_success49 %lookup_elem_val52.0 = phi i64 [ %phitmp62, %lookup_success49 ], [ 1, %lookup_merge38 ] call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %19) %21 = bitcast i64* %"@i_key55" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %21) store i64 0, i64* %"@i_key55", align 8 %22 = bitcast i64* %"@i_val56" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %22) store i64 %lookup_elem_val52.0, i64* %"@i_val56", align 8 %pseudo57 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem58 = call i64 inttoptr (i64 2 to i64 (i64, i64*, i64*, i64)*)(i64 %pseudo57, i64* nonnull %"@i_key55", i64* nonnull %"@i_val56", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %21) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %22) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/variable.cpp000066400000000000000000000120471361633214400203600ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, variable) { test("kprobe:f { $var = comm; @x = $var; @y = $var }", #if LLVM_VERSION_MAJOR > 6 R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@y_key" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"$var" = alloca [16 x i8], align 1 %1 = getelementptr inbounds [16 x i8], [16 x i8]* %"$var", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %1, i64 0, i64 16, i1 false) %comm = alloca [16 x i8], align 1 %2 = getelementptr inbounds [16 x i8], [16 x i8]* %comm, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) call void @llvm.memset.p0i8.i64(i8* nonnull align 1 %2, i8 0, i64 16, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm, i64 16) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull align 1 %1, i8* nonnull align 1 %2, i64 16, i1 false) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [16 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [16 x i8]* nonnull %"$var", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %4 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@y_key", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem2 = call i64 inttoptr (i64 2 to i64 (i64, i64*, [16 x i8]*, i64)*)(i64 %pseudo1, i64* nonnull %"@y_key", [16 x i8]* nonnull %"$var", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture writeonly, i8* nocapture readonly, i64, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #else R"EXPECTED(; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @"kprobe:f"(i8* nocapture readnone) local_unnamed_addr section "s_kprobe:f_1" { entry: %"@y_key" = alloca i64, align 8 %"@x_key" = alloca i64, align 8 %"$var" = alloca [16 x i8], align 1 %1 = getelementptr inbounds [16 x i8], [16 x i8]* %"$var", i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memset.p0i8.i64(i8* nonnull %1, i64 0, i64 16, i32 1, i1 false) %comm = alloca [16 x i8], align 1 %2 = getelementptr inbounds [16 x i8], [16 x i8]* %comm, i64 0, i64 0 call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %2) call void @llvm.memset.p0i8.i64(i8* nonnull %2, i8 0, i64 16, i32 1, i1 false) %get_comm = call i64 inttoptr (i64 16 to i64 (i8*, i64)*)([16 x i8]* nonnull %comm, i64 16) call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) call void @llvm.memcpy.p0i8.p0i8.i64(i8* nonnull %1, i8* nonnull %2, i64 16, i32 1, i1 false) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %2) %3 = bitcast i64* %"@x_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %3) store i64 0, i64* %"@x_key", align 8 %pseudo = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %update_elem = call i64 inttoptr (i64 2 to i64 (i64, i64*, [16 x i8]*, i64)*)(i64 %pseudo, i64* nonnull %"@x_key", [16 x i8]* nonnull %"$var", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %3) %4 = bitcast i64* %"@y_key" to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) store i64 0, i64* %"@y_key", align 8 %pseudo1 = call i64 @llvm.bpf.pseudo(i64 1, i64 2) %update_elem2 = call i64 inttoptr (i64 2 to i64 (i64, i64*, [16 x i8]*, i64)*)(i64 %pseudo1, i64* nonnull %"@y_key", [16 x i8]* nonnull %"$var", i64 0) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.memset.p0i8.i64(i8* nocapture writeonly, i8, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.memcpy.p0i8.p0i8.i64(i8* nocapture writeonly, i8* nocapture readonly, i64, i32, i1) #1 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); #endif } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/codegen/variable_increment_decrement.cpp000066400000000000000000000073721361633214400244570ustar00rootroot00000000000000#include "common.h" namespace bpftrace { namespace test { namespace codegen { TEST(codegen, variable_increment_decrement) { test("BEGIN { $x = 10; printf(\"%d\", $x++); printf(\"%d\", ++$x); printf(\"%d\", $x--); printf(\"%d\", --$x); }", R"EXPECTED(%printf_t.2 = type { i64, i64 } %printf_t.1 = type { i64, i64 } %printf_t.0 = type { i64, i64 } %printf_t = type { i64, i64 } ; Function Attrs: nounwind declare i64 @llvm.bpf.pseudo(i64, i64) #0 ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #1 define i64 @BEGIN(i8*) local_unnamed_addr section "s_BEGIN_1" { entry: %printf_args9 = alloca %printf_t.2, align 8 %printf_args5 = alloca %printf_t.1, align 8 %printf_args1 = alloca %printf_t.0, align 8 %printf_args = alloca %printf_t, align 8 %1 = bitcast %printf_t* %printf_args to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %1) %2 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 1 %3 = getelementptr inbounds %printf_t, %printf_t* %printf_args, i64 0, i32 0 store i64 0, i64* %3, align 8 store i64 10, i64* %2, align 8 %pseudo = tail call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id = tail call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t*, i64)*)(i8* %0, i64 %pseudo, i64 %get_cpu_id, %printf_t* nonnull %printf_args, i64 16) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %1) %4 = bitcast %printf_t.0* %printf_args1 to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %4) %5 = getelementptr inbounds %printf_t.0, %printf_t.0* %printf_args1, i64 0, i32 0 store i64 1, i64* %5, align 8 %6 = getelementptr inbounds %printf_t.0, %printf_t.0* %printf_args1, i64 0, i32 1 store i64 12, i64* %6, align 8 %pseudo2 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id3 = call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output4 = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t.0*, i64)*)(i8* %0, i64 %pseudo2, i64 %get_cpu_id3, %printf_t.0* nonnull %printf_args1, i64 16) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %4) %7 = bitcast %printf_t.1* %printf_args5 to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %7) %8 = getelementptr inbounds %printf_t.1, %printf_t.1* %printf_args5, i64 0, i32 0 store i64 2, i64* %8, align 8 %9 = getelementptr inbounds %printf_t.1, %printf_t.1* %printf_args5, i64 0, i32 1 store i64 12, i64* %9, align 8 %pseudo6 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id7 = call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output8 = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t.1*, i64)*)(i8* %0, i64 %pseudo6, i64 %get_cpu_id7, %printf_t.1* nonnull %printf_args5, i64 16) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %7) %10 = bitcast %printf_t.2* %printf_args9 to i8* call void @llvm.lifetime.start.p0i8(i64 -1, i8* nonnull %10) %11 = getelementptr inbounds %printf_t.2, %printf_t.2* %printf_args9, i64 0, i32 0 store i64 3, i64* %11, align 8 %12 = getelementptr inbounds %printf_t.2, %printf_t.2* %printf_args9, i64 0, i32 1 store i64 10, i64* %12, align 8 %pseudo10 = call i64 @llvm.bpf.pseudo(i64 1, i64 1) %get_cpu_id11 = call i64 inttoptr (i64 8 to i64 ()*)() %perf_event_output12 = call i64 inttoptr (i64 25 to i64 (i8*, i64, i64, %printf_t.2*, i64)*)(i8* %0, i64 %pseudo10, i64 %get_cpu_id11, %printf_t.2* nonnull %printf_args9, i64 16) call void @llvm.lifetime.end.p0i8(i64 -1, i8* nonnull %10) ret i64 0 } ; Function Attrs: argmemonly nounwind declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #1 attributes #0 = { nounwind } attributes #1 = { argmemonly nounwind } )EXPECTED"); } } // namespace codegen } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/data/000077500000000000000000000000001361633214400153705ustar00rootroot00000000000000bpftrace-0.9.4/tests/data/btf_data.h000066400000000000000000000052311361633214400173060ustar00rootroot00000000000000#pragma once // The data consists of following source file objects: // // struct Foo1 { // int a; // char b; // long c; // }; // // struct Foo2 { // int a; // union { // struct Foo1 f; // struct { // char g; // }; // }; // }; // // struct Foo3 { // struct Foo1 *foo1; // struct Foo2 *foo2; // }; // generated by: // % cat a.c // [... the above definitions ...] // struct Foo3 f3; // % clang -target bpf -g -c a.c // % llvm-objcopy --dump-section .BTF=a.btf a.o // % hexdump -ve '13/1 "0x%02x, " "\n"' a.btf unsigned char btf_data[] = { 0x9f, 0xeb, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x01, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x01, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x08, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x04, 0x18, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x05, 0x10, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x6f, 0x6f, 0x33, 0x00, 0x66, 0x6f, 0x6f, 0x31, 0x00, 0x66, 0x6f, 0x6f, 0x32, 0x00, 0x46, 0x6f, 0x6f, 0x31, 0x00, 0x61, 0x00, 0x62, 0x00, 0x63, 0x00, 0x69, 0x6e, 0x74, 0x00, 0x63, 0x68, 0x61, 0x72, 0x00, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x69, 0x6e, 0x74, 0x00, 0x46, 0x6f, 0x6f, 0x32, 0x00, 0x66, 0x00, 0x67, 0x00, }; unsigned int btf_data_len = sizeof(btf_data) / sizeof(btf_data[0]); bpftrace-0.9.4/tests/main.cpp000066400000000000000000000007411361633214400161110ustar00rootroot00000000000000#include "gtest/gtest.h" class ThrowListener : public testing::EmptyTestEventListener { void OnTestPartResult(const testing::TestPartResult& result) override { if (result.type() == testing::TestPartResult::kFatalFailure) { throw testing::AssertionException(result); } } }; int main(int argc, char *argv[]) { ::testing::InitGoogleTest(&argc, argv); ::testing::UnitTest::GetInstance()->listeners().Append(new ThrowListener); return RUN_ALL_TESTS(); } bpftrace-0.9.4/tests/mocks.cpp000066400000000000000000000064431361633214400163060ustar00rootroot00000000000000#include "mocks.h" namespace bpftrace { namespace test { using ::testing::_; using ::testing::NiceMock; using ::testing::Return; using ::testing::StrictMock; void setup_mock_bpftrace(MockBPFtrace &bpftrace) { ON_CALL(bpftrace, get_symbols_from_file("/sys/kernel/debug/tracing/available_filter_functions")) .WillByDefault([](const std::string &) { std::string ksyms = "SyS_read\n" "sys_read\n" "sys_write\n" "my_one\n" "my_two\n"; auto myval = std::unique_ptr(new std::istringstream(ksyms)); return myval; }); ON_CALL(bpftrace, get_symbols_from_file("/sys/kernel/debug/tracing/available_events")) .WillByDefault([](const std::string &) { std::string tracepoints = "sched:sched_one\n" "sched:sched_two\n" "sched:foo\n" "notsched:bar\n" "file:filename\n"; return std::unique_ptr(new std::istringstream(tracepoints)); }); std::string usyms = "first_open\n" "second_open\n" "open_as_well\n" "something_else\n"; ON_CALL(bpftrace, extract_func_symbols_from_path(_)) .WillByDefault(Return(usyms)); ON_CALL(bpftrace, get_symbols_from_usdt(_, _)) .WillByDefault([](int, const std::string &) { std::string usdt_syms = "prov1:tp1\n" "prov1:tp2\n" "prov2:tp\n" "prov2:notatp\n" "nahprov:tp\n"; return std::unique_ptr(new std::istringstream(usdt_syms)); }); // Fill in some default tracepoint struct definitions bpftrace.structs_["struct _tracepoint_sched_sched_one"] = Struct { .size = 8, .fields = {{"common_field", Field { .type = SizedType(Type::integer, 8, false), .offset = 8, .is_bitfield = false, .bitfield = {}, }}}, }; bpftrace.structs_["struct _tracepoint_sched_sched_two"] = Struct { .size = 8, .fields = {{"common_field", Field { .type = SizedType(Type::integer, 8, false), .offset = 16, // different offset than sched_one.common_field .is_bitfield = false, .bitfield = {}, }}}, }; auto ptr_type = SizedType(Type::integer, 8, false); ptr_type.is_pointer = true; ptr_type.pointee_size = 1; bpftrace.structs_["struct _tracepoint_file_filename"] = Struct{ .size = 8, .fields = { { "filename", Field{ .type = ptr_type, .offset = 8, .is_bitfield = false, .bitfield = {}, } } }, }; } std::unique_ptr get_mock_bpftrace() { auto bpftrace = std::make_unique>(); setup_mock_bpftrace(*bpftrace); return bpftrace; } std::unique_ptr get_strict_mock_bpftrace() { auto bpftrace = std::make_unique>(); setup_mock_bpftrace(*bpftrace); return bpftrace; } } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/mocks.h000066400000000000000000000031161361633214400157450ustar00rootroot00000000000000#pragma once #include "bpffeature.h" #include "bpftrace.h" #include "gmock/gmock.h" namespace bpftrace { namespace test { class MockBPFtrace : public BPFtrace { public: #pragma GCC diagnostic push #ifdef __clang__ #pragma GCC diagnostic ignored "-Winconsistent-missing-override" #endif MOCK_CONST_METHOD1(get_symbols_from_file, std::unique_ptr(const std::string &path)); MOCK_CONST_METHOD2(get_symbols_from_usdt, std::unique_ptr(int pid, const std::string &target)); MOCK_CONST_METHOD1(extract_func_symbols_from_path, std::string(const std::string &path)); #pragma GCC diagnostic pop std::vector get_probes() { return probes_; } std::vector get_special_probes() { return special_probes_; } int resolve_uname(const std::string &name, struct symbol *sym, const std::string &path) const override { (void)path; sym->name = name; if (name[0] > 'A' && name[0] < 'z') { sym->address = 12345; sym->size = 4; } else { auto fields = split_string(name, '_'); sym->address = std::stoull(fields.at(0)); sym->size = std::stoull(fields.at(1)); } return 0; } }; std::unique_ptr get_mock_bpftrace(); std::unique_ptr get_strict_mock_bpftrace(); class MockBPFfeature : public BPFfeature { public: MockBPFfeature(bool has_features = true) { has_loop_ = has_signal_ = has_get_current_cgroup_id_ = has_override_return_ = has_features; }; }; } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/parser.cpp000066400000000000000000000743541361633214400164740ustar00rootroot00000000000000#include #include "gtest/gtest.h" #include "driver.h" #include "printer.h" namespace bpftrace { namespace test { namespace parser { using Printer = ast::Printer; void test_parse_failure(const std::string &input) { BPFtrace bpftrace; std::stringstream out; Driver driver(bpftrace, out); ASSERT_EQ(driver.parse_str(input), 1); } void test(const std::string &input, const std::string &output) { BPFtrace bpftrace; Driver driver(bpftrace); ASSERT_EQ(driver.parse_str(input), 0); std::ostringstream out; Printer printer(out); driver.root_->accept(printer); EXPECT_EQ(output, out.str()); } TEST(Parser, builtin_variables) { test("kprobe:f { pid }", "Program\n kprobe:f\n builtin: pid\n"); test("kprobe:f { tid }", "Program\n kprobe:f\n builtin: tid\n"); test("kprobe:f { cgroup }", "Program\n kprobe:f\n builtin: cgroup\n"); test("kprobe:f { uid }", "Program\n kprobe:f\n builtin: uid\n"); test("kprobe:f { username }", "Program\n kprobe:f\n builtin: username\n"); test("kprobe:f { gid }", "Program\n kprobe:f\n builtin: gid\n"); test("kprobe:f { nsecs }", "Program\n kprobe:f\n builtin: nsecs\n"); test("kprobe:f { elapsed }", "Program\n kprobe:f\n builtin: elapsed\n"); test("kprobe:f { cpu }", "Program\n kprobe:f\n builtin: cpu\n"); test("kprobe:f { curtask }", "Program\n kprobe:f\n builtin: curtask\n"); test("kprobe:f { rand }", "Program\n kprobe:f\n builtin: rand\n"); test("kprobe:f { ctx }", "Program\n kprobe:f\n builtin: ctx\n"); test("kprobe:f { comm }", "Program\n kprobe:f\n builtin: comm\n"); test("kprobe:f { stack }", "Program\n kprobe:f\n builtin: kstack\n"); test("kprobe:f { kstack }", "Program\n kprobe:f\n builtin: kstack\n"); test("kprobe:f { ustack }", "Program\n kprobe:f\n builtin: ustack\n"); test("kprobe:f { arg0 }", "Program\n kprobe:f\n builtin: arg0\n"); test("kprobe:f { sarg0 }", "Program\n kprobe:f\n builtin: sarg0\n"); test("kprobe:f { retval }", "Program\n kprobe:f\n builtin: retval\n"); test("kprobe:f { func }", "Program\n kprobe:f\n builtin: func\n"); test("kprobe:f { probe }", "Program\n kprobe:f\n builtin: probe\n"); test("kprobe:f { args }", "Program\n kprobe:f\n builtin: args\n"); } TEST(Parser, positional_param) { test("kprobe:f { $1 }", "Program\n kprobe:f\n param: $1\n"); } TEST(Parser, positional_param_count) { test("kprobe:f { $# }", "Program\n kprobe:f\n param: $#\n"); } TEST(Parser, comment) { test("kprobe:f { /*** ***/0; }", "Program\n kprobe:f\n int: 0\n"); } TEST(Parser, map_assign) { test("kprobe:sys_open { @x = 1; }", "Program\n" " kprobe:sys_open\n" " =\n" " map: @x\n" " int: 1\n"); test("kprobe:sys_open { @x = @y; }", "Program\n" " kprobe:sys_open\n" " =\n" " map: @x\n" " map: @y\n"); test("kprobe:sys_open { @x = arg0; }", "Program\n" " kprobe:sys_open\n" " =\n" " map: @x\n" " builtin: arg0\n"); test("kprobe:sys_open { @x = count(); }", "Program\n" " kprobe:sys_open\n" " =\n" " map: @x\n" " call: count\n"); test("kprobe:sys_read { @x = sum(arg2); }", "Program\n" " kprobe:sys_read\n" " =\n" " map: @x\n" " call: sum\n" " builtin: arg2\n"); test("kprobe:sys_read { @x = min(arg2); }", "Program\n" " kprobe:sys_read\n" " =\n" " map: @x\n" " call: min\n" " builtin: arg2\n"); test("kprobe:sys_read { @x = max(arg2); }", "Program\n" " kprobe:sys_read\n" " =\n" " map: @x\n" " call: max\n" " builtin: arg2\n"); test("kprobe:sys_read { @x = avg(arg2); }", "Program\n" " kprobe:sys_read\n" " =\n" " map: @x\n" " call: avg\n" " builtin: arg2\n"); test("kprobe:sys_read { @x = stats(arg2); }", "Program\n" " kprobe:sys_read\n" " =\n" " map: @x\n" " call: stats\n" " builtin: arg2\n"); test("kprobe:sys_open { @x = \"mystring\" }", "Program\n" " kprobe:sys_open\n" " =\n" " map: @x\n" " string: mystring\n"); test("kprobe:sys_open { @x = $myvar; }", "Program\n" " kprobe:sys_open\n" " =\n" " map: @x\n" " variable: $myvar\n"); } TEST(Parser, variable_assign) { test("kprobe:sys_open { $x = 1; }", "Program\n" " kprobe:sys_open\n" " =\n" " variable: $x\n" " int: 1\n"); test("kprobe:sys_open { $x = -1; }", "Program\n" " kprobe:sys_open\n" " =\n" " variable: $x\n" " int: -1\n"); } TEST(semantic_analyser, compound_variable_assignments) { test("kprobe:f { $a = 0; $a <<= 1 }", "Program\n" " kprobe:f\n" " =\n" " variable: $a\n" " int: 0\n" " =\n" " variable: $a\n" " <<\n" " variable: $a\n" " int: 1\n"); test("kprobe:f { $a = 0; $a >>= 1 }", "Program\n" " kprobe:f\n" " =\n" " variable: $a\n" " int: 0\n" " =\n" " variable: $a\n" " >>\n" " variable: $a\n" " int: 1\n"); test("kprobe:f { $a = 0; $a += 1 }", "Program\n" " kprobe:f\n" " =\n" " variable: $a\n" " int: 0\n" " =\n" " variable: $a\n" " +\n" " variable: $a\n" " int: 1\n"); test("kprobe:f { $a = 0; $a -= 1 }", "Program\n" " kprobe:f\n" " =\n" " variable: $a\n" " int: 0\n" " =\n" " variable: $a\n" " -\n" " variable: $a\n" " int: 1\n"); test("kprobe:f { $a = 0; $a *= 1 }", "Program\n" " kprobe:f\n" " =\n" " variable: $a\n" " int: 0\n" " =\n" " variable: $a\n" " *\n" " variable: $a\n" " int: 1\n"); test("kprobe:f { $a = 0; $a /= 1 }", "Program\n" " kprobe:f\n" " =\n" " variable: $a\n" " int: 0\n" " =\n" " variable: $a\n" " /\n" " variable: $a\n" " int: 1\n"); test("kprobe:f { $a = 0; $a %= 1 }", "Program\n" " kprobe:f\n" " =\n" " variable: $a\n" " int: 0\n" " =\n" " variable: $a\n" " %\n" " variable: $a\n" " int: 1\n"); test("kprobe:f { $a = 0; $a &= 1 }", "Program\n" " kprobe:f\n" " =\n" " variable: $a\n" " int: 0\n" " =\n" " variable: $a\n" " &\n" " variable: $a\n" " int: 1\n"); test("kprobe:f { $a = 0; $a |= 1 }", "Program\n" " kprobe:f\n" " =\n" " variable: $a\n" " int: 0\n" " =\n" " variable: $a\n" " |\n" " variable: $a\n" " int: 1\n"); test("kprobe:f { $a = 0; $a ^= 1 }", "Program\n" " kprobe:f\n" " =\n" " variable: $a\n" " int: 0\n" " =\n" " variable: $a\n" " ^\n" " variable: $a\n" " int: 1\n"); } TEST(Parser, compound_map_assignments) { test("kprobe:f { @a <<= 1 }", "Program\n" " kprobe:f\n" " =\n" " map: @a\n" " <<\n" " map: @a\n" " int: 1\n"); test("kprobe:f { @a >>= 1 }", "Program\n" " kprobe:f\n" " =\n" " map: @a\n" " >>\n" " map: @a\n" " int: 1\n"); test("kprobe:f { @a += 1 }", "Program\n" " kprobe:f\n" " =\n" " map: @a\n" " +\n" " map: @a\n" " int: 1\n"); test("kprobe:f { @a -= 1 }", "Program\n" " kprobe:f\n" " =\n" " map: @a\n" " -\n" " map: @a\n" " int: 1\n"); test("kprobe:f { @a *= 1 }", "Program\n" " kprobe:f\n" " =\n" " map: @a\n" " *\n" " map: @a\n" " int: 1\n"); test("kprobe:f { @a /= 1 }", "Program\n" " kprobe:f\n" " =\n" " map: @a\n" " /\n" " map: @a\n" " int: 1\n"); test("kprobe:f { @a %= 1 }", "Program\n" " kprobe:f\n" " =\n" " map: @a\n" " %\n" " map: @a\n" " int: 1\n"); test("kprobe:f { @a &= 1 }", "Program\n" " kprobe:f\n" " =\n" " map: @a\n" " &\n" " map: @a\n" " int: 1\n"); test("kprobe:f { @a |= 1 }", "Program\n" " kprobe:f\n" " =\n" " map: @a\n" " |\n" " map: @a\n" " int: 1\n"); test("kprobe:f { @a ^= 1 }", "Program\n" " kprobe:f\n" " =\n" " map: @a\n" " ^\n" " map: @a\n" " int: 1\n"); } TEST(Parser, integer_sizes) { test("kprobe:do_nanosleep { $x = 0x12345678; }", "Program\n" " kprobe:do_nanosleep\n" " =\n" " variable: $x\n" " int: 305419896\n"); test("kprobe:do_nanosleep { $x = 0x4444444412345678; }", "Program\n" " kprobe:do_nanosleep\n" " =\n" " variable: $x\n" " int: 4919131752149309048\n"); } TEST(Parser, map_key) { test("kprobe:sys_open { @x[0] = 1; @x[0,1,2] = 1; }", "Program\n" " kprobe:sys_open\n" " =\n" " map: @x\n" " int: 0\n" " int: 1\n" " =\n" " map: @x\n" " int: 0\n" " int: 1\n" " int: 2\n" " int: 1\n"); test("kprobe:sys_open { @x[@a] = 1; @x[@a,@b,@c] = 1; }", "Program\n" " kprobe:sys_open\n" " =\n" " map: @x\n" " map: @a\n" " int: 1\n" " =\n" " map: @x\n" " map: @a\n" " map: @b\n" " map: @c\n" " int: 1\n"); test("kprobe:sys_open { @x[pid] = 1; @x[tid,uid,arg9] = 1; }", "Program\n" " kprobe:sys_open\n" " =\n" " map: @x\n" " builtin: pid\n" " int: 1\n" " =\n" " map: @x\n" " builtin: tid\n" " builtin: uid\n" " builtin: arg9\n" " int: 1\n"); } TEST(Parser, predicate) { test("kprobe:sys_open / @x / { 1; }", "Program\n" " kprobe:sys_open\n" " pred\n" " map: @x\n" " int: 1\n"); } TEST(Parser, predicate_containing_division) { test("kprobe:sys_open /100/25/ { 1; }", "Program\n" " kprobe:sys_open\n" " pred\n" " /\n" " int: 100\n" " int: 25\n" " int: 1\n"); } TEST(Parser, expressions) { test("kprobe:sys_open / 1 <= 2 && (9 - 4 != 5*10 || ~0) || comm == \"string\" /\n" "{\n" " 1;\n" "}", "Program\n" " kprobe:sys_open\n" " pred\n" " ||\n" " &&\n" " <=\n" " int: 1\n" " int: 2\n" " ||\n" " !=\n" " -\n" " int: 9\n" " int: 4\n" " *\n" " int: 5\n" " int: 10\n" " ~\n" " int: 0\n" " ==\n" " builtin: comm\n" " string: string\n" " int: 1\n"); } TEST(Parser, variable_post_increment_decrement) { test("kprobe:sys_open { $x++; }", "Program\n" " kprobe:sys_open\n" " variable: $x\n" " ++\n"); test("kprobe:sys_open { ++$x; }", "Program\n" " kprobe:sys_open\n" " ++\n" " variable: $x\n"); test("kprobe:sys_open { $x--; }", "Program\n" " kprobe:sys_open\n" " variable: $x\n" " --\n"); test("kprobe:sys_open { --$x; }", "Program\n" " kprobe:sys_open\n" " --\n" " variable: $x\n"); } TEST(Parser, map_increment_decrement) { test("kprobe:sys_open { @x++; }", "Program\n" " kprobe:sys_open\n" " map: @x\n" " ++\n"); test("kprobe:sys_open { ++@x; }", "Program\n" " kprobe:sys_open\n" " ++\n" " map: @x\n"); test("kprobe:sys_open { @x--; }", "Program\n" " kprobe:sys_open\n" " map: @x\n" " --\n"); test("kprobe:sys_open { --@x; }", "Program\n" " kprobe:sys_open\n" " --\n" " map: @x\n"); } TEST(Parser, bit_shifting) { test("kprobe:do_nanosleep { @x = 1 << 10 }", "Program\n" " kprobe:do_nanosleep\n" " =\n" " map: @x\n" " <<\n" " int: 1\n" " int: 10\n"); test("kprobe:do_nanosleep { @x = 1024 >> 9 }", "Program\n" " kprobe:do_nanosleep\n" " =\n" " map: @x\n" " >>\n" " int: 1024\n" " int: 9\n"); test("kprobe:do_nanosleep / 2 < 1 >> 8 / { $x = 1 }", "Program\n" " kprobe:do_nanosleep\n" " pred\n" " <\n" " int: 2\n" " >>\n" " int: 1\n" " int: 8\n" " =\n" " variable: $x\n" " int: 1\n"); } TEST(Parser, ternary_int) { test("kprobe:sys_open { @x = pid < 10000 ? 1 : 2 }", "Program\n" " kprobe:sys_open\n" " =\n" " map: @x\n" " ?:\n" " <\n" " builtin: pid\n" " int: 10000\n" " int: 1\n" " int: 2\n"); } TEST(Parser, if_block) { test("kprobe:sys_open { if (pid > 10000) { printf(\"%d is high\\n\", pid); } }", "Program\n" " kprobe:sys_open\n" " if\n" " >\n" " builtin: pid\n" " int: 10000\n" " then\n" " call: printf\n" " string: %d is high\\n\n" " builtin: pid\n"); } TEST(Parser, if_stmt_if) { test("kprobe:sys_open { if (pid > 10000) { printf(\"%d is high\\n\", pid); } @pid = pid; if (pid < 1000) { printf(\"%d is low\\n\", pid); } }", "Program\n" " kprobe:sys_open\n" " if\n" " >\n" " builtin: pid\n" " int: 10000\n" " then\n" " call: printf\n" " string: %d is high\\n\n" " builtin: pid\n" " =\n" " map: @pid\n" " builtin: pid\n" " if\n" " <\n" " builtin: pid\n" " int: 1000\n" " then\n" " call: printf\n" " string: %d is low\\n\n" " builtin: pid\n"); } TEST(Parser, if_block_variable) { test("kprobe:sys_open { if (pid > 10000) { $s = 10; } }", "Program\n" " kprobe:sys_open\n" " if\n" " >\n" " builtin: pid\n" " int: 10000\n" " then\n" " =\n" " variable: $s\n" " int: 10\n"); } TEST(Parser, if_else) { test("kprobe:sys_open { if (pid > 10000) { $s = \"a\"; } else { $s= \"b\"; } printf(\"%d is high\\n\", pid, $s); }", "Program\n" " kprobe:sys_open\n" " if\n" " >\n" " builtin: pid\n" " int: 10000\n" " then\n" " =\n" " variable: $s\n" " string: a\n" " else\n" " =\n" " variable: $s\n" " string: b\n" " call: printf\n" " string: %d is high\\n\n" " builtin: pid\n" " variable: $s\n"); } TEST(Parser, unroll) { test("kprobe:sys_open { $i = 0; unroll(5) { printf(\"i: %d\\n\", $i); $i = $i + 1; } }", "Program\n" " kprobe:sys_open\n" " =\n" " variable: $i\n" " int: 0\n" " unroll 5\n" " call: printf\n" " string: i: %d\\n\n" " variable: $i\n" " =\n" " variable: $i\n" " +\n" " variable: $i\n" " int: 1\n"); } TEST(Parser, ternary_str) { test("kprobe:sys_open { @x = pid < 10000 ? \"lo\" : \"high\" }", "Program\n" " kprobe:sys_open\n" " =\n" " map: @x\n" " ?:\n" " <\n" " builtin: pid\n" " int: 10000\n" " string: lo\n" " string: high\n"); } TEST(Parser, ternary_nested) { test("kprobe:sys_open { @x = pid < 10000 ? pid < 5000 ? 1 : 2 : 3 }", "Program\n" " kprobe:sys_open\n" " =\n" " map: @x\n" " ?:\n" " <\n" " builtin: pid\n" " int: 10000\n" " ?:\n" " <\n" " builtin: pid\n" " int: 5000\n" " int: 1\n" " int: 2\n" " int: 3\n"); } TEST(Parser, call) { test("kprobe:sys_open { @x = count(); @y = hist(1,2,3); delete(@x); }", "Program\n" " kprobe:sys_open\n" " =\n" " map: @x\n" " call: count\n" " =\n" " map: @y\n" " call: hist\n" " int: 1\n" " int: 2\n" " int: 3\n" " call: delete\n" " map: @x\n"); } TEST(Parser, call_unknown_function) { test_parse_failure("kprobe:sys_open { myfunc() }"); test_parse_failure("k:f { probe(); }"); } TEST(Parser, call_builtin) { // Builtins should not be usable as function test_parse_failure("k:f { nsecs(); }"); test_parse_failure("k:f { nsecs (); }"); test_parse_failure("k:f { nsecs(\"abc\"); }"); test_parse_failure("k:f { nsecs(123); }"); test_parse_failure("k:f { probe(\"blah\"); }"); test_parse_failure("k:f { probe(); }"); test_parse_failure("k:f { probe(123); }"); } TEST(Parser, call_kaddr) { test("kprobe:f { @ = kaddr(\"avenrun\") }", "Program\n" " kprobe:f\n" " =\n" " map: @\n" " call: kaddr\n" " string: avenrun\n"); } TEST(Parser, multiple_probes) { test("kprobe:sys_open { 1; } kretprobe:sys_open { 2; }", "Program\n" " kprobe:sys_open\n" " int: 1\n" " kretprobe:sys_open\n" " int: 2\n"); } TEST(Parser, uprobe) { test("uprobe:/my/program:func { 1; }", "Program\n" " uprobe:/my/program:func\n" " int: 1\n"); test("uprobe:/my/go/program:\"pkg.func\u2C51\" { 1; }", "Program\n" " uprobe:/my/go/program:pkg.func\u2C51\n" " int: 1\n"); test ("uprobe:/with#hash:asdf { 1 }", "Program\n" " uprobe:/with#hash:asdf\n" " int: 1\n"); } TEST(Parser, usdt) { test("usdt:/my/program:probe { 1; }", "Program\n" " usdt:/my/program:probe\n" " int: 1\n"); } TEST(Parser, usdt_namespaced_probe) { test("usdt:/my/program:namespace:probe { 1; }", "Program\n" " usdt:/my/program:namespace:probe\n" " int: 1\n"); test("usdt:/my/program*:namespace:probe { 1; }", "Program\n" " usdt:/my/program*:namespace:probe\n" " int: 1\n"); test("usdt:/my/*program:namespace:probe { 1; }", "Program\n" " usdt:/my/*program:namespace:probe\n" " int: 1\n"); test("usdt:*my/program*:namespace:probe { 1; }", "Program\n" " usdt:*my/program*:namespace:probe\n" " int: 1\n"); } TEST(Parser, escape_chars) { test("kprobe:sys_open { \"newline\\nand tab\\tcr\\rbackslash\\\\quote\\\"here oct\\1009hex\\x309\" }", "Program\n" " kprobe:sys_open\n" " string: newline\\nand tab\\tcr\\rbackslash\\\\quote\\\"here oct@9hex09\n"); } TEST(Parser, begin_probe) { test("BEGIN { 1 }", "Program\n" " BEGIN\n" " int: 1\n"); } TEST(Parser, tracepoint_probe) { test("tracepoint:sched:sched_switch { 1 }", "Program\n" " tracepoint:sched:sched_switch\n" " int: 1\n"); } TEST(Parser, profile_probe) { test("profile:ms:997 { 1 }", "Program\n" " profile:ms:997\n" " int: 1\n"); } TEST(Parser, interval_probe) { test("interval:s:1 { 1 }", "Program\n" " interval:s:1\n" " int: 1\n"); } TEST(Parser, software_probe) { test("software:faults:1000 { 1 }", "Program\n" " software:faults:1000\n" " int: 1\n"); } TEST(Parser, hardware_probe) { test("hardware:cache-references:1000000 { 1 }", "Program\n" " hardware:cache-references:1000000\n" " int: 1\n"); } TEST(Parser, multiple_attach_points_kprobe) { test("BEGIN,kprobe:sys_open,uprobe:/bin/sh:foo,tracepoint:syscalls:sys_enter_* { 1 }", "Program\n" " BEGIN\n" " kprobe:sys_open\n" " uprobe:/bin/sh:foo\n" " tracepoint:syscalls:sys_enter_*\n" " int: 1\n"); } TEST(Parser, character_class_attach_point) { test("kprobe:[Ss]y[Ss]_read { 1 }", "Program\n" " kprobe:[Ss]y[Ss]_read\n" " int: 1\n"); } TEST(Parser, wildcard_attach_points) { test("kprobe:sys_* { 1 }", "Program\n" " kprobe:sys_*\n" " int: 1\n"); test("kprobe:*blah { 1 }", "Program\n" " kprobe:*blah\n" " int: 1\n"); test("kprobe:sys*blah { 1 }", "Program\n" " kprobe:sys*blah\n" " int: 1\n"); test("kprobe:* { 1 }", "Program\n" " kprobe:*\n" " int: 1\n"); test("kprobe:sys_* { @x = cpu*retval }", "Program\n" " kprobe:sys_*\n" " =\n" " map: @x\n" " *\n" " builtin: cpu\n" " builtin: retval\n"); test("kprobe:sys_* { @x = *arg0 }", "Program\n" " kprobe:sys_*\n" " =\n" " map: @x\n" " dereference\n" " builtin: arg0\n"); } TEST(Parser, wildcard_path) { test("uprobe:/my/program*:* { 1; }", "Program\n" " uprobe:/my/program*:*\n" " int: 1\n"); test("uprobe:/my/program*:func { 1; }", "Program\n" " uprobe:/my/program*:func\n" " int: 1\n"); test("uprobe:*my/program*:func { 1; }", "Program\n" " uprobe:*my/program*:func\n" " int: 1\n"); test("uprobe:/my/program*foo:func { 1; }", "Program\n" " uprobe:/my/program*foo:func\n" " int: 1\n"); test("usdt:/my/program*:* { 1; }", "Program\n" " usdt:/my/program*:*\n" " int: 1\n"); test("usdt:/my/program*:func { 1; }", "Program\n" " usdt:/my/program*:func\n" " int: 1\n"); test("usdt:*my/program*:func { 1; }", "Program\n" " usdt:*my/program*:func\n" " int: 1\n"); test("usdt:/my/program*foo:func { 1; }", "Program\n" " usdt:/my/program*foo:func\n" " int: 1\n"); // Make sure calls or builtins don't cause issues test("usdt:/my/program*avg:func { 1; }", "Program\n" " usdt:/my/program*avg:func\n" " int: 1\n"); test("usdt:/my/program*nsecs:func { 1; }", "Program\n" " usdt:/my/program*nsecs:func\n" " int: 1\n"); } TEST(Parser, dot_in_func) { test("uprobe:/my/go/program:runtime.main.func1 { 1; }", "Program\n" " uprobe:/my/go/program:runtime.main.func1\n" " int: 1\n"); } TEST(Parser, wildcard_func) { test("usdt:/my/program:abc*cd { 1; }", "Program\n" " usdt:/my/program:abc*cd\n" " int: 1\n"); test("usdt:/my/program:abc*c*d { 1; }", "Program\n" " usdt:/my/program:abc*c*d\n" " int: 1\n"); std::string keywords[] = { "arg0", "args", "curtask", "func", "gid" "rand", "uid", "avg", "cat", "exit", "kaddr", "min", "printf", "usym", "kstack", "ustack", "bpftrace", "perf", "uprobe", "kprobe", }; for(auto kw : keywords) { test("usdt:/my/program:"+ kw +"*c*d { 1; }", "Program\n" " usdt:/my/program:"+ kw + "*c*d\n" " int: 1\n"); test("usdt:/my/program:abc*"+ kw +"*c*d { 1; }", "Program\n" " usdt:/my/program:abc*"+ kw + "*c*d\n" " int: 1\n"); } } TEST(Parser, short_map_name) { test("kprobe:sys_read { @ = 1 }", "Program\n" " kprobe:sys_read\n" " =\n" " map: @\n" " int: 1\n"); } TEST(Parser, include) { test("#include \nkprobe:sys_read { @x = 1 }", "#include \n" "\n" "Program\n" " kprobe:sys_read\n" " =\n" " map: @x\n" " int: 1\n"); } TEST(Parser, include_quote) { test("#include \"stdio.h\"\nkprobe:sys_read { @x = 1 }", "#include \"stdio.h\"\n" "\n" "Program\n" " kprobe:sys_read\n" " =\n" " map: @x\n" " int: 1\n"); } TEST(Parser, include_multiple) { test("#include \n#include \"blah\"\n#include \nkprobe:sys_read { @x = 1 }", "#include \n" "#include \"blah\"\n" "#include \n" "\n" "Program\n" " kprobe:sys_read\n" " =\n" " map: @x\n" " int: 1\n"); } TEST(Parser, brackets) { test("kprobe:sys_read { (arg0*arg1) }", "Program\n" " kprobe:sys_read\n" " *\n" " builtin: arg0\n" " builtin: arg1\n"); } TEST(Parser, cast) { test("kprobe:sys_read { (struct mytype)arg0; }", "Program\n" " kprobe:sys_read\n" " (struct mytype)\n" " builtin: arg0\n"); test("kprobe:sys_read { (union mytype)arg0; }", "Program\n" " kprobe:sys_read\n" " (union mytype)\n" " builtin: arg0\n"); } TEST(Parser, cast_ptr) { test("kprobe:sys_read { (struct mytype*)arg0; }", "Program\n" " kprobe:sys_read\n" " (struct mytype*)\n" " builtin: arg0\n"); test("kprobe:sys_read { (union mytype*)arg0; }", "Program\n" " kprobe:sys_read\n" " (union mytype*)\n" " builtin: arg0\n"); } TEST(Parser, cast_typedef) { test("kprobe:sys_read { (mytype)arg0; }", "Program\n" " kprobe:sys_read\n" " (mytype)\n" " builtin: arg0\n"); } TEST(Parser, cast_ptr_typedef) { test("kprobe:sys_read { (mytype*)arg0; }", "Program\n" " kprobe:sys_read\n" " (mytype*)\n" " builtin: arg0\n"); } TEST(Parser, cast_or_expr1) { test("kprobe:sys_read { (mytype)*arg0; }", "Program\n" " kprobe:sys_read\n" " (mytype)\n" " dereference\n" " builtin: arg0\n"); } TEST(Parser, cast_or_expr2) { test("kprobe:sys_read { (arg1)*arg0; }", "Program\n" " kprobe:sys_read\n" " *\n" " builtin: arg1\n" " builtin: arg0\n"); } TEST(Parser, cast_precedence) { test("kprobe:sys_read { (mytype)arg0.field; }", "Program\n" " kprobe:sys_read\n" " (mytype)\n" " .\n" " builtin: arg0\n" " field\n"); test("kprobe:sys_read { (mytype*)arg0->field; }", "Program\n" " kprobe:sys_read\n" " (mytype*)\n" " .\n" " dereference\n" " builtin: arg0\n" " field\n"); test("kprobe:sys_read { (mytype)arg0+123; }", "Program\n" " kprobe:sys_read\n" " +\n" " (mytype)\n" " builtin: arg0\n" " int: 123\n"); } TEST(Parser, dereference_precedence) { test("kprobe:sys_read { *@x+1 }", "Program\n" " kprobe:sys_read\n" " +\n" " dereference\n" " map: @x\n" " int: 1\n"); test("kprobe:sys_read { *@x**@y }", "Program\n" " kprobe:sys_read\n" " *\n" " dereference\n" " map: @x\n" " dereference\n" " map: @y\n"); test("kprobe:sys_read { *@x*@y }", "Program\n" " kprobe:sys_read\n" " *\n" " dereference\n" " map: @x\n" " map: @y\n"); test("kprobe:sys_read { *@x.myfield }", "Program\n" " kprobe:sys_read\n" " dereference\n" " .\n" " map: @x\n" " myfield\n"); } TEST(Parser, field_access) { test("kprobe:sys_read { @x.myfield; }", "Program\n" " kprobe:sys_read\n" " .\n" " map: @x\n" " myfield\n"); test("kprobe:sys_read { @x->myfield; }", "Program\n" " kprobe:sys_read\n" " .\n" " dereference\n" " map: @x\n" " myfield\n"); } TEST(Parser, field_access_builtin) { test("kprobe:sys_read { @x.count; }", "Program\n" " kprobe:sys_read\n" " .\n" " map: @x\n" " count\n"); test("kprobe:sys_read { @x->count; }", "Program\n" " kprobe:sys_read\n" " .\n" " dereference\n" " map: @x\n" " count\n"); } TEST(Parser, array_access) { test("kprobe:sys_read { x[index]; }", "Program\n" " kprobe:sys_read\n" " []\n" " identifier: x\n" " identifier: index\n"); test("kprobe:sys_read { $val = x[index]; }", "Program\n" " kprobe:sys_read\n" " =\n" " variable: $val\n" " []\n" " identifier: x\n" " identifier: index\n"); } TEST(Parser, cstruct) { test("struct Foo { int x, y; char *str; } kprobe:sys_read { 1; }", "struct Foo { int x, y; char *str; };\n" "\n" "Program\n" " kprobe:sys_read\n" " int: 1\n"); } TEST(Parser, cstruct_nested) { test("struct Foo { struct { int x; } bar; } kprobe:sys_read { 1; }", "struct Foo { struct { int x; } bar; };\n" "\n" "Program\n" " kprobe:sys_read\n" " int: 1\n"); } TEST(Parser, unexpected_symbol) { BPFtrace bpftrace; std::stringstream out; Driver driver(bpftrace, out); EXPECT_EQ(driver.parse_str("i:s:1 { < }"), 1); std::string expected = R"(stdin:1:9-10: ERROR: syntax error, unexpected <, expecting } i:s:1 { < } ~ )"; EXPECT_EQ(out.str(), expected); } TEST(Parser, string_with_tab) { BPFtrace bpftrace; std::stringstream out; Driver driver(bpftrace, out); EXPECT_EQ(driver.parse_str("i:s:1\t\t\t$a"), 1); std::string expected = R"(stdin:1:9-11: ERROR: syntax error, unexpected variable, expecting { i:s:1 $a ~~ )"; EXPECT_EQ(out.str(), expected); } TEST(Parser, unterminated_string) { BPFtrace bpftrace; std::stringstream out; Driver driver(bpftrace, out); EXPECT_EQ(driver.parse_str("kprobe:f { \"asdf }"), 1); std::string expected = R"(stdin:1:12-19: ERROR: unterminated string kprobe:f { "asdf } ~~~~~~~ stdin:1:12-19: ERROR: syntax error, unexpected end of file, expecting } kprobe:f { "asdf } ~~~~~~~ )"; EXPECT_EQ(out.str(), expected); } TEST(Parser, uprobe_offset) { test("u:./test:fn+1 {}", "Program\n" " uprobe:./test:fn+1\n"); test("u:./test:fn+0x10 {}", "Program\n" " uprobe:./test:fn+16\n"); test("u:./test:\"fn.abc\"+1 {}", "Program\n" " uprobe:./test:fn.abc+1\n"); test("u:./test:\"fn.abc\"+0x10 {}", "Program\n" " uprobe:./test:fn.abc+16\n"); } TEST(Parser, invalid_increment_decrement) { test_parse_failure("i:s:1 { @=5++}"); test_parse_failure("i:s:1 { @=++5}"); test_parse_failure("i:s:1 { @=5--}"); test_parse_failure("i:s:1 { @=--5}"); test_parse_failure("i:s:1 { @=\"a\"++}"); } } // namespace parser } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/probe.cpp000066400000000000000000000035371361633214400163020ustar00rootroot00000000000000#include "gmock/gmock.h" #include "gtest/gtest.h" #include "bpforc.h" #include "bpftrace.h" #include "clang_parser.h" #include "codegen_llvm.h" #include "driver.h" #include "fake_map.h" #include "mocks.h" #include "semantic_analyser.h" namespace bpftrace { namespace test { namespace probe { using bpftrace::ast::AttachPoint; using bpftrace::ast::AttachPointList; using bpftrace::ast::Probe; void gen_bytecode(const std::string &input, std::stringstream &out) { auto bpftrace = get_mock_bpftrace(); Driver driver(*bpftrace); FakeMap::next_mapfd_ = 1; ASSERT_EQ(driver.parse_str(input), 0); ClangParser clang; clang.parse(driver.root_, *bpftrace); MockBPFfeature feature = MockBPFfeature(); ast::SemanticAnalyser semantics(driver.root_, *bpftrace, feature); ASSERT_EQ(semantics.analyse(), 0); ASSERT_EQ(semantics.create_maps(true), 0); ast::CodegenLLVM codegen(driver.root_, *bpftrace); codegen.compile(DebugLevel::kDebug, out); } void compare_bytecode(const std::string &input1, const std::string &input2) { std::stringstream expected_output1; std::stringstream expected_output2; gen_bytecode(input1, expected_output1); gen_bytecode(input2, expected_output2); EXPECT_EQ(expected_output1.str(), expected_output2.str()); } TEST(probe, short_name) { compare_bytecode("tracepoint:a:b { args }", "t:a:b { args }"); compare_bytecode("kprobe:f { pid }", "k:f { pid }"); compare_bytecode("kretprobe:f { pid }", "kr:f { pid }"); compare_bytecode("uprobe:sh:f { 1 }", "u:sh:f { 1 }"); compare_bytecode("profile:hz:997 { 1 }", "p:hz:997 { 1 }"); compare_bytecode("hardware:cache-references:1000000 { 1 }", "h:cache-references:1000000 { 1 }"); compare_bytecode("software:faults:1000 { 1 }", "s:faults:1000 { 1 }"); compare_bytecode("interval:s:1 { 1 }", "i:s:1 { 1 }"); } } // namespace probe } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/runtime-tests.sh000077500000000000000000000003511361633214400176400ustar00rootroot00000000000000#!/bin/bash set -e; pushd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 BPFTRACE_RUNTIME_TEST_EXECUTABLE=${BPFTRACE_RUNTIME_TEST_EXECUTABLE:-../src/}; export BPFTRACE_RUNTIME_TEST_EXECUTABLE; python3 runtime/engine/main.py $@ bpftrace-0.9.4/tests/runtime/000077500000000000000000000000001361633214400161425ustar00rootroot00000000000000bpftrace-0.9.4/tests/runtime/array000066400000000000000000000015141361633214400172040ustar00rootroot00000000000000NAME array element access - assign to map RUN bpftrace -v -e 'struct MyStruct { int y[4]; } uprobe:./testprogs/array_access:test_struct { $s = (struct MyStruct *) arg0; @x = $s->y[0]; exit(); }' EXPECT @x: 1 TIMEOUT 5 AFTER ./testprogs/array_access NAME array element access - assign to var RUN bpftrace -v -e 'struct MyStruct { int y[4]; } uprobe:./testprogs/array_access:test_struct { $s = (struct MyStruct *) arg0; $x = $s->y[0]; printf("Result: %d\n", $x); exit(); }' EXPECT Result: 1 TIMEOUT 5 AFTER ./testprogs/array_access NAME array element access - out of bounds RUN bpftrace -v -e 'struct MyStruct { int y[4]; } uprobe:./testprogs/array_access:test_struct { $s = (struct MyStruct *) arg0; $x = $s->y[5]; printf("%d\n", $x); exit(); }' EXPECT the index 5 is out of bounds for array of size 4 TIMEOUT 5 AFTER ./testprogs/array_access bpftrace-0.9.4/tests/runtime/banned_probes000066400000000000000000000015531361633214400206720ustar00rootroot00000000000000NAME kretprobe:_raw_spin_lock is banned RUN bpftrace -v -e 'kretprobe:_raw_spin_lock { exit(); }' EXPECT error: kretprobe:_raw_spin_lock can't be used as it might lock up your system. TIMEOUT 1 NAME kretprobe:queued_spin_lock_slowpath is banned RUN bpftrace -v -e 'kretprobe:queued_spin_lock_slowpath { exit(); }' EXPECT error: kretprobe:queued_spin_lock_slowpath can't be used as it might lock up your system. TIMEOUT 1 NAME kretprobe:_raw_spin_unlock_irqrestore is banned RUN bpftrace -v -e 'kretprobe:_raw_spin_unlock_irqrestore { exit(); }' EXPECT error: kretprobe:_raw_spin_unlock_irqrestore can't be used as it might lock up your system. TIMEOUT 1 NAME kretprobe:_raw_spin_lock_irqsave is banned RUN bpftrace -v -e 'kretprobe:_raw_spin_lock_irqsave { exit(); }' EXPECT error: kretprobe:_raw_spin_lock_irqsave can't be used as it might lock up your system. TIMEOUT 1 bpftrace-0.9.4/tests/runtime/basic000066400000000000000000000052241361633214400171510ustar00rootroot00000000000000NAME it shows version RUN bpftrace --version EXPECT bpftrace v TIMEOUT 1 NAME it shows usage with help flag RUN bpftrace -h EXPECT USAGE TIMEOUT 1 NAME it shows usage with bad flag RUN bpftrace -idonotexist EXPECT USAGE TIMEOUT 1 NAME errors on non existent file RUN bpftrace non_existent_file.bt EXPECT Error opening file 'non_existent_file.bt': No such file or directory TIMEOUT 1 NAME it lists kprobes RUN bpftrace -l | grep kprobes EXPECT kprobe TIMEOUT 1 NAME it lists tracepoints RUN bpftrace -l | grep tracepoint EXPECT tracepoint TIMEOUT 1 NAME it lists software events RUN bpftrace -l | grep software EXPECT software TIMEOUT 1 NAME it lists hardware events RUN bpftrace -l | grep hardware EXPECT hardware TIMEOUT 1 NAME it lists kprobes with regex filter RUN bpftrace -l "kprobe:*" EXPECT kprobe: TIMEOUT 1 NAME it lists tracepoints with regex filter RUN bpftrace -l "tracepoint:raw_syscalls:*" EXPECT tracepoint:raw_syscalls:sys_exit TIMEOUT 1 NAME it lists software events with regex filter RUN bpftrace -l "software:*" EXPECT software TIMEOUT 1 NAME it lists hardware events with regex filter RUN bpftrace -l "hardware:*" EXPECT hardware TIMEOUT 1 NAME errors on invalid character in search expression RUN bpftrace -l '\n' EXPECT ERROR: invalid character in search expression TIMEOUT 1 NAME pid fails validation with leading non-number RUN bpftrace -p a1111 EXPECT ERROR: pid 'a1111' is not a valid number. TIMEOUT 1 NAME pid fails validation with non-number in between RUN bpftrace -p 111a1 EXPECT ERROR: pid '111a1' is not a valid number. TIMEOUT 1 NAME pid fails validation with non-numeric argument RUN bpftrace -p not_a_pid EXPECT ERROR: pid 'not_a_pid' is not a valid number. TIMEOUT 1 NAME libraries under /usr/include are in the search path RUN bpftrace -e "$(echo "#include "; echo "BEGIN { exit(); }")" 2>&1 EXPECT ^((?!file not found).)*$ TIMEOUT 1 NAME non existent library include fails RUN bpftrace -e "$(echo "#include "; echo "BEGIN { exit(); }")" 2>&1 EXPECT file not found TIMEOUT 1 NAME defines work RUN bpftrace -e "$(echo '#define _UNDERSCORE 314'; echo 'BEGIN { printf("%d\\n", _UNDERSCORE); exit(); }')" EXPECT 314 TIMEOUT 1 NAME increment/decrement map RUN bpftrace -e 'BEGIN { @x = 10; printf("%d", @x++); printf(" %d", ++@x); printf(" %d", @x--); printf(" %d\n", --@x); delete(@x); exit(); }' EXPECT 10 12 12 10 TIMEOUT 1 NAME increment/decrement variable RUN bpftrace -e 'BEGIN { $x = 10; printf("%d", $x++); printf(" %d", ++$x); printf(" %d", $x--); printf(" %d\n", --$x); exit(); }' EXPECT 10 12 12 10 TIMEOUT 1 NAME spawn child RUN bpftrace -e 'i:ms:500 { printf("%d\n", cpid); }' -c '/bin/sleep 1' EXPECT [0-9]+ TIMEOUT 1bpftrace-0.9.4/tests/runtime/builtin000066400000000000000000000102041361633214400175300ustar00rootroot00000000000000NAME pid RUN bpftrace -v -e 'i:ms:1 { printf("SUCCESS '$test' %d\n", pid); exit(); }' EXPECT SUCCESS pid [0-9][0-9]* TIMEOUT 5 NAME tid RUN bpftrace -v -e 'i:ms:1 { printf("SUCCESS '$test' %d\n", tid); exit(); }' EXPECT SUCCESS tid [0-9][0-9]* TIMEOUT 5 NAME uid RUN bpftrace -v -e 'i:ms:1 { printf("SUCCESS '$test' %d\n", uid); exit(); }' EXPECT SUCCESS uid [0-9][0-9]* TIMEOUT 5 NAME gid RUN bpftrace -v -e 'i:ms:1 { printf("SUCCESS '$test' %d\n", gid); exit(); }' EXPECT SUCCESS gid [0-9][0-9]* TIMEOUT 5 NAME nsecs RUN bpftrace -v -e 'i:ms:1 { printf("SUCCESS '$test' %d\n", nsecs); exit(); }' EXPECT SUCCESS nsecs -?[0-9][0-9]* TIMEOUT 5 NAME elapsed RUN bpftrace -v -e 'i:ms:1 { printf("SUCCESS '$test' %d\n", elapsed); exit(); }' EXPECT SUCCESS elapsed -?[0-9][0-9]* TIMEOUT 5 NAME cpu RUN bpftrace -v -e 'i:ms:1 { printf("SUCCESS '$test' %d\n", cpu); exit(); }' EXPECT SUCCESS cpu -?[0-9][0-9]* TIMEOUT 5 NAME comm RUN bpftrace -v -e 'k:vfs_read { printf("SUCCESS '$test' %s\n", comm); exit(); }' EXPECT SUCCESS comm .* TIMEOUT 5 NAME stack RUN bpftrace -v -e 'k:vfs_read{ printf("SUCCESS '$test' %s\n", stack); exit(); }' EXPECT SUCCESS stack TIMEOUT 5 NAME kstack RUN bpftrace -v -e 'k:vfs_read{ printf("SUCCESS '$test' %s\n", kstack); exit(); }' EXPECT SUCCESS kstack TIMEOUT 5 NAME ustack RUN bpftrace -v -e 'k:vfs_read { printf("SUCCESS '$test' %s\n", ustack); exit(); }' EXPECT SUCCESS ustack TIMEOUT 5 NAME arg RUN bpftrace -v -e 'k:vfs_read { printf("SUCCESS '$test' %d\n", arg0); exit(); }' EXPECT SUCCESS arg -?[0-9][0-9]* TIMEOUT 5 NAME sarg RUN bpftrace -v -e 'uprobe:./testprogs/stack_args:too_many_args { printf("SUCCESS '$test' %d %d\n", sarg0, sarg1); exit(); }' EXPECT SUCCESS sarg 32 64 TIMEOUT 5 AFTER ./testprogs/stack_args NAME retval RUN bpftrace -v -e 'kretprobe:vfs_read { printf("SUCCESS '$test' %d\n", retval); exit(); }' EXPECT SUCCESS retval .* TIMEOUT 5 NAME func RUN bpftrace -v -e 'k:vfs_read { printf("SUCCESS '$test' %s\n", func); exit(); }' EXPECT SUCCESS func .* TIMEOUT 5 NAME func_uprobe RUN bpftrace -v -e 'uprobe:./testprogs/uprobe_negative_retval:function1 { printf("SUCCESS %s\n", func); exit(); }' EXPECT SUCCESS .* AFTER ./testprogs/uprobe_negative_retval TIMEOUT 5 NAME username RUN bpftrace -v -e 'i:ms:1 { printf("SUCCESS '$test' %s\n", username); exit(); }' EXPECT SUCCESS username .* TIMEOUT 5 NAME probe RUN bpftrace -v -e 'k:do_nanosleep { printf("SUCCESS '$test' %s\n", probe); exit(); }' EXPECT SUCCESS probe kprobe:do_nanosleep TIMEOUT 5 AFTER sleep 0.1 NAME begin probe RUN bpftrace -v -e 'BEGIN { printf("%s", probe);exit(); } END{printf("-%s\n", probe); }' EXPECT ^BEGIN-END$ TIMEOUT 5 AFTER sleep 0.1 NAME curtask RUN bpftrace -v -e 'i:ms:1 { printf("SUCCESS '$test' %d\n", curtask); exit(); }' EXPECT SUCCESS curtask -?[0-9][0-9]* TIMEOUT 5 NAME curtask_field RUN bpftrace -v -e 'struct task_struct {int x;} i:ms:1 { printf("SUCCESS '$test' %d\n", curtask->x); exit(); }' EXPECT SUCCESS curtask_field -?[0-9][0-9]* TIMEOUT 5 NAME rand RUN bpftrace -v -e 'i:ms:1 { printf("SUCCESS '$test' %d\n", rand); exit(); }' EXPECT SUCCESS rand -?[0-9][0-9]* TIMEOUT 5 NAME cgroup RUN bpftrace -v -e 'i:ms:1 { printf("SUCCESS '$test' %d\n", cgroup); exit(); }' EXPECT SUCCESS cgroup -?[0-9][0-9]* TIMEOUT 5 MIN_KERNEL 4.18 NAME cat RUN bpftrace -e 'i:ms:1 { cat("/proc/loadavg"); exit(); }' EXPECT ^([0-9]+\.[0-9]+ ?)+.*$ TIMEOUT 5 NAME cat limited output ENV BPFTRACE_CAT_BYTES_MAX=1 RUN bpftrace -e 'i:ms:1 { cat("/proc/loadavg"); exit(); }' EXPECT ^[0-9]$ TIMEOUT 5 NAME cat format str RUN bpftrace -e 'i:ms:1 { $s = "loadavg"; cat("/proc/%s", $s); exit(); }' EXPECT ^([0-9]+\.[0-9]+ ?)+.*$ TIMEOUT 5 NAME log size too small ENV BPFTRACE_LOG_SIZE=1 RUN bpftrace -ve 'BEGIN { if (str($1) == str($2)) { printf("%s\n", str($1)); exit() } }' "hello" "hello" EXPECT ^Error loading program: BEGIN TIMEOUT 5 NAME increase log size RUN bpftrace -ve 'BEGIN { if (str($1) == str($2)) { printf("%s\n", str($1)); exit() } }' "hello" "hello" EXPECT ^Attaching 1 probe TIMEOUT 5 NAME cat "no such file" RUN bpftrace -e 'i:ms:1 { cat("/does/not/exist/file"); exit(); }' EXPECT ^Error opening file '/does/not/exist/file': No such file or directory$ TIMEOUT 5 bpftrace-0.9.4/tests/runtime/call000066400000000000000000000055431361633214400170070ustar00rootroot00000000000000NAME printf RUN bpftrace -v -e 'i:ms:1 { printf("hi!\n"); exit();}' EXPECT hi! TIMEOUT 5 NAME printf_argument RUN bpftrace -v -e 'i:ms:1 { printf("value: %d100\n", 100); exit();}' EXPECT value: 100 TIMEOUT 5 NAME time RUN bpftrace -v -e 'i:ms:1 { time("%H:%M:%S\n"); exit();}' EXPECT [0-9]*:[0-9]*:[0-9]* TIMEOUT 5 NAME time_short RUN bpftrace -v -e 'i:ms:1 { time("%H-%M:%S\n"); exit();}' EXPECT [0-9]*-[0-9]* TIMEOUT 5 NAME join RUN bpftrace --unsafe -v -e 'i:ms:1 { system("echo 'A'"); } kprobe:sys_execve { join(arg1); exit();}' EXPECT A TIMEOUT 5 NAME join_delim RUN bpftrace --unsafe -v -e 'i:ms:1 { system("echo 'A'"); } kprobe:sys_execve { join(arg1, ","); exit();}' EXPECT A TIMEOUT 5 NAME str RUN bpftrace -v -e 't:syscalls:sys_enter_execve { printf("P: %s\n", str(args->filename)); exit();}' AFTER ls EXPECT P: /*. TIMEOUT 5 NAME ksym RUN bpftrace -v -e 'kprobe:do_nanosleep { printf("%s\n", ksym(reg("ip"))); exit();}' EXPECT do_nanosleep TIMEOUT 5 AFTER sleep 0.1 NAME system RUN bpftrace --unsafe -v -e 'i:ms:100 { system("echo 'ok_system'"); exit();}' EXPECT ok_system TIMEOUT 5 NAME count RUN bpftrace -v -e 'i:ms:100 { @ = count(); exit();}' EXPECT @:\s[0-9]+ TIMEOUT 5 NAME sum RUN bpftrace -v -e 'kprobe:vfs_read { @bytes[comm] = sum(arg2); exit();}' EXPECT @.*\[.*\]\:\s[0-9]* TIMEOUT 5 AFTER cat /dev/null NAME avg RUN bpftrace -v -e 'kprobe:vfs_read { @bytes[comm] = avg(arg2); exit();}' EXPECT @.*\[.*\]\:\s[0-9]* TIMEOUT 5 AFTER cat /dev/null NAME min RUN bpftrace -v -e 'kprobe:vfs_read { @bytes[comm] = min(arg2); exit();}' EXPECT @.*\[.*\]\:\s[0-9]* TIMEOUT 5 AFTER cat /dev/null NAME max RUN bpftrace -v -e 'kprobe:vfs_read { @bytes[comm] = max(arg2); exit();}' EXPECT @.*\[.*\]\:\s[0-9]* AFTER cat /dev/null TIMEOUT 5 NAME stats RUN bpftrace -v -e 'kprobe:vfs_read { @bytes[comm] = stats(arg2); exit();}' EXPECT @.*\[.*\]\:\scount\s[0-9]*\,\saverage\s[0-9]*\,\stotal\s[0-9]* TIMEOUT 5 AFTER cat /dev/null NAME hist RUN bpftrace -v -e 'kretprobe:vfs_read { @bytes = hist(retval); exit();}' EXPECT @bytes: *\n[\[(].* AFTER cat /dev/null TIMEOUT 5 NAME lhist RUN bpftrace -v -e 'kretprobe:vfs_read { @bytes = lhist(retval, 0, 10000, 1000); exit()}' EXPECT @bytes: *\n[\[(].* TIMEOUT 5 AFTER cat /dev/null NAME kstack RUN bpftrace -v -e 'k:do_nanosleep { printf("SUCCESS '$test' %s\n%s\n", kstack(), kstack(1)); exit(); }' EXPECT SUCCESS kstack TIMEOUT 5 AFTER sleep 0.1 NAME ustack RUN bpftrace -v -e 'k:do_nanosleep { printf("SUCCESS '$test' %s\n%s\n", ustack(), ustack(1)); exit(); }' EXPECT SUCCESS ustack TIMEOUT 5 AFTER sleep 0.1 NAME cat RUN bpftrace -v -e 'i:ms:1 { cat("/proc/uptime"); exit();}' EXPECT [0-9]*.[0-9]* [0-9]*.[0-9]* TIMEOUT 5 NAME uaddr RUN bpftrace -v -e 'uprobe:testprogs/uprobe_test:function1 { printf("0x%lx -- 0x%lx\n", *uaddr("GLOBAL_A"), *uaddr("GLOBAL_C")); exit(); }' -c ./testprogs/uprobe_test EXPECT 0x55555555 -- 0x33333333 TIMEOUT 5 bpftrace-0.9.4/tests/runtime/engine/000077500000000000000000000000001361633214400174075ustar00rootroot00000000000000bpftrace-0.9.4/tests/runtime/engine/main.py000066400000000000000000000043101361633214400207030ustar00rootroot00000000000000#!/usr/bin/python3 import time from datetime import timedelta import argparse from utils import Utils, ok, fail, warn from parser import TestParser def main(test_filter = None): if not test_filter: test_filter = "*" test_suite = list(TestParser.read_all(test_filter)) total_tests = 0 for fname, suite_tests in test_suite: total_tests += len(suite_tests) failed_tests = [] print(ok("[==========]") + " Running %d tests from %d test cases.\n" % (total_tests, len(test_suite))) start_time = time.time() skipped_tests = [] for fname, tests in test_suite: print(ok("[----------]") + " %d tests from %s" % (len(tests), fname)) for test in tests: status = Utils.run_test(test) if Utils.skipped(status): skipped_tests.append((fname, test, status)) if Utils.failed(status): failed_tests.append("%s.%s" % (fname, test.name)) # TODO(mmarchini) elapsed time per test suite and per test (like gtest) print(ok("[----------]") + " %d tests from %s\n" % (len(tests), fname)) elapsed = time.time() - start_time total_tests -= len(skipped_tests) # TODO(mmarchini) pretty print time print(ok("[==========]") + " %d tests from %d test cases ran. (%s total)" % (total_tests, len(test_suite), elapsed)) print(ok("[ PASSED ]") + " %d tests." % (total_tests - len(failed_tests))) if skipped_tests: print(warn("[ SKIP ]") + " %d tests, listed below:" % len(skipped_tests)) for test_suite, test, status in skipped_tests: print(warn("[ SKIP ]") + " %s.%s (%s)" % (test_suite, test.name, Utils.skip_reason(test, status))) if failed_tests: print(fail("[ FAILED ]") + " %d tests, listed below:" % len(failed_tests)) for failed_test in failed_tests: print(fail("[ FAILED ]") + " %s" % failed_test) if failed_tests: exit(1) if __name__ == "__main__": parser = argparse.ArgumentParser(description='Runtime tests for bpftrace.') parser.add_argument('--filter', dest='tests_filter', help='filter runtime tests') args = parser.parse_args() main(args.tests_filter) bpftrace-0.9.4/tests/runtime/engine/parser.py000066400000000000000000000071311361633214400212570ustar00rootroot00000000000000#!/usr/bin/python3 from fnmatch import fnmatch from collections import namedtuple import os from utils import ERROR_COLOR, NO_COLOR class RequiredFieldError(Exception): pass class UnknownFieldError(Exception): pass TestStruct = namedtuple('TestStruct', 'name run expect timeout before after suite kernel requirement env') class TestParser(object): @staticmethod def read_all(test_filter): try: for root, subdirs, files in os.walk('./runtime'): for ignore_dir in ["engine", "scripts", "outputs"]: if ignore_dir in subdirs: subdirs.remove(ignore_dir) for filename in files: if filename.startswith("."): continue parser = TestParser.read(root + '/' + filename, test_filter) if parser[1]: yield parser except RequiredFieldError as error: print(ERROR_COLOR + str(error) + NO_COLOR) @staticmethod def read(file_name, test_filter): tests = [] test_lines = [] test_suite = file_name.split('/')[-1] with open (file_name, 'r') as file: lines = file.readlines() line_num = 0 for line in lines: line_num += 1 if line.startswith("#"): continue if line != '\n': test_lines.append(line) if line == '\n' or line_num == len(lines): if test_lines: test_struct = TestParser.__read_test_struct(test_lines, test_suite) if fnmatch("%s.%s" % (test_suite, test_struct.name), test_filter): tests.append(test_struct) test_lines = [] return (test_suite, tests) @staticmethod def __read_test_struct(test, test_suite): name = '' run = '' expect = '' timeout = '' before = '' after = '' kernel = '' requirement = '' env = {} for item in test: item_split = item.split() item_name = item_split[0] line = ' '.join(item_split[1:]) if item_name == 'NAME': name = line elif item_name == 'RUN': run = line elif item_name == 'EXPECT': expect = line elif item_name == 'TIMEOUT': timeout = int(line.strip(' ')) elif item_name == 'BEFORE': before = line elif item_name == 'AFTER': after = line elif item_name == 'MIN_KERNEL': kernel = line elif item_name == 'REQUIRES': requirement = line elif item_name == 'ENV': for e in line.split(): k, v = e.split('=') env[k]=v else: raise UnknownFieldError('Field %s is unknown. Suite: %s' % (item_name, test_suite)) if name == '': raise RequiredFieldError('Test NAME is required. Suite: ' + test_suite) elif run == '': raise RequiredFieldError('Test RUN is required. Suite: ' + test_suite) elif expect == '': raise RequiredFieldError('Test EXPECT is required. Suite: ' + test_suite) elif timeout == '': raise RequiredFieldError('Test TIMEOUT is required. Suite: ' + test_suite) return TestStruct(name, run, expect, timeout, before, after, test_suite, kernel, requirement, env) bpftrace-0.9.4/tests/runtime/engine/utils.py000066400000000000000000000122221361633214400211200ustar00rootroot00000000000000#!/usr/bin/python3 import subprocess import signal import os from os import environ, uname, devnull from distutils.version import LooseVersion import re BPF_PATH = environ["BPFTRACE_RUNTIME_TEST_EXECUTABLE"] ATTACH_TIMEOUT = 5 DEFAULT_TIMEOUT = 5 OK_COLOR = '\033[92m' WARN_COLOR = '\033[94m' ERROR_COLOR = '\033[91m' NO_COLOR = '\033[0m' # TODO(mmarchini) only add colors if terminal supports it def colorify(s, color): return "%s%s%s" % (color, s, NO_COLOR) def ok(s): return colorify(s, OK_COLOR) def warn(s): return colorify(s, WARN_COLOR) def fail(s): return colorify(s, ERROR_COLOR) class TimeoutError(Exception): pass class Utils(object): PASS = 0 FAIL = 1 SKIP_KERNEL_VERSION = 2 TIMEOUT = 3 SKIP_REQUIREMENT_UNSATISFIED = 4 SKIP_ENVIRONMENT_DISABLED = 5 @staticmethod def failed(status): return status in [Utils.FAIL, Utils.TIMEOUT] @staticmethod def skipped(status): return status in [ Utils.SKIP_KERNEL_VERSION, Utils.SKIP_REQUIREMENT_UNSATISFIED, Utils.SKIP_ENVIRONMENT_DISABLED, ] @staticmethod def skip_reason(test, status): if status == Utils.SKIP_KERNEL_VERSION: return "min Kernel: %s" % test.kernel elif status == Utils.SKIP_REQUIREMENT_UNSATISFIED: return "unmet condition: '%s'" % test.requirement elif status == Utils.SKIP_ENVIRONMENT_DISABLED: return "disabled by environment variable" else: raise ValueError("Invalid skip reason: %d" % status) @staticmethod def prepare_bpf_call(test): return BPF_PATH + test.run @staticmethod def __handler(signum, frame): raise TimeoutError('TIMEOUT') @staticmethod def run_test(test): current_kernel = LooseVersion(uname()[2]) if test.kernel and LooseVersion(test.kernel) > current_kernel: print(warn("[ SKIP ] ") + "%s.%s" % (test.suite, test.name)) return Utils.SKIP_KERNEL_VERSION full_test_name = test.suite + "." + test.name if full_test_name in os.getenv("RUNTIME_TEST_DISABLE", "").split(","): print(warn("[ SKIP ] ") + "%s.%s" % (test.suite, test.name)) return Utils.SKIP_ENVIRONMENT_DISABLED signal.signal(signal.SIGALRM, Utils.__handler) try: before = None after = None print(ok("[ RUN ] ") + "%s.%s" % (test.suite, test.name)) if test.requirement: with open(devnull, 'w') as dn: if subprocess.call(test.requirement, shell=True, stdout=dn, stderr=dn) != 0: print(warn("[ SKIP ] ") + "%s.%s" % (test.suite, test.name)) return Utils.SKIP_REQUIREMENT_UNSATISFIED if test.before: before = subprocess.Popen(test.before, shell=True, preexec_fn=os.setsid) bpf_call = Utils.prepare_bpf_call(test) env = {'test': test.name} env.update(test.env) p = subprocess.Popen( bpf_call, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, env=env, preexec_fn=os.setsid, universal_newlines=True, bufsize=1 ) signal.alarm(ATTACH_TIMEOUT) output = "" while p.poll() is None: nextline = p.stdout.readline() output += nextline if nextline == "Running...\n": signal.alarm(test.timeout or DEFAULT_TIMEOUT) if not after and test.after: after = subprocess.Popen(test.after, shell=True, preexec_fn=os.setsid) break output += p.communicate()[0] signal.alarm(0) result = re.search(test.expect, output, re.M) except (TimeoutError): # Give it a last chance, the test might have worked but the # bpftrace process might still be alive if p.poll() is None: os.killpg(os.getpgid(p.pid), signal.SIGKILL) output += p.communicate()[0] result = re.search(test.expect, output) if not result: print(fail("[ TIMEOUT ] ") + "%s.%s" % (test.suite, test.name)) print('\tCommand: %s' % bpf_call) print('\tTimeout: %s' % test.timeout) print('\tCurrent output: %s' % output) return Utils.TIMEOUT finally: if before and before.poll() is None: os.killpg(os.getpgid(before.pid), signal.SIGKILL) if after and after.poll() is None: os.killpg(os.getpgid(after.pid), signal.SIGKILL) if result: print(ok("[ OK ] ") + "%s.%s" % (test.suite, test.name)) return Utils.PASS else: print(fail("[ FAILED ] ") + "%s.%s" % (test.suite, test.name)) print('\tCommand: ' + bpf_call) print('\tExpected: ' + test.expect) print('\tFound: ' + output) return Utils.FAIL bpftrace-0.9.4/tests/runtime/file-output000066400000000000000000000017241361633214400203460ustar00rootroot00000000000000NAME printf to file RUN bpftrace -v -e 'i:ms:10 { printf("%s %d\n", "SUCCESS", 1); exit() }' -o /tmp/bpftrace-file-output-test >/dev/null; cat /tmp/bpftrace-file-output-test; rm /tmp/bpftrace-file-output-test EXPECT ^SUCCESS 1$ TIMEOUT 5 NAME cat to file RUN bpftrace -v -e 'i:ms:10 { cat("/proc/loadavg"); exit(); }' -o /tmp/bpftrace-file-output-test >/dev/null; cat /tmp/bpftrace-file-output-test; rm /tmp/bpftrace-file-output-test EXPECT ^([0-9]+\.[0-9]+ )+.*$ TIMEOUT 5 NAME print map to file RUN bpftrace -v -e 'i:ms:10 { @=lhist(50, 0, 100, 10); exit();}' -o /tmp/bpftrace-file-output-test >/dev/null; cat /tmp/bpftrace-file-output-test; rm /tmp/bpftrace-file-output-test EXPECT ^\[50, 60\).*\@+\|$ TIMEOUT 5 NAME system stdout to file RUN bpftrace -v --unsafe -e 'i:ms:10 { system("cat /proc/loadavg"); exit(); }' -o /tmp/bpftrace-file-output-test >/dev/null; cat /tmp/bpftrace-file-output-test; rm /tmp/bpftrace-file-output-test EXPECT ^([0-9]+\.[0-9]+ )+.*$ TIMEOUT 5 bpftrace-0.9.4/tests/runtime/intcast000066400000000000000000000012171361633214400175330ustar00rootroot00000000000000NAME Casting retval should work RUN bpftrace -v -e 'ur:./testprogs/uprobe_negative_retval:main /(int32)retval < 0/ { @[(int32)retval]=count(); exit();}' AFTER ./testprogs/uprobe_negative_retval EXPECT ^@\[-100\]: 1$ TIMEOUT 5 NAME Sum casted retval RUN bpftrace -v -e 'ur:./testprogs/uprobe_negative_retval:function1 { @=stats((int32)retval); exit();} ur:./testprogs/uprobe_negative_retval:main { exit() }' AFTER ./testprogs/uprobe_negative_retval EXPECT ^@: count 221, average -10, total -2210$ TIMEOUT 5 NAME Casting ints RUN bpftrace -v -e 'BEGIN{printf("Values: %d %d\n", (uint8) -10, (uint16) 131087); exit(); }' EXPECT Values: 246 15$ TIMEOUT 5 bpftrace-0.9.4/tests/runtime/intptrcast000066400000000000000000000005451361633214400202640ustar00rootroot00000000000000NAME Sum casted value RUN bpftrace -v -e 'uprobe:./testprogs/intptrcast:fn { @=sum(*(uint16*)(reg("sp")+8)); exit();}' EXPECT ^@: 3258 TIMEOUT 5 AFTER ./testprogs/intptrcast NAME Casting ints RUN bpftrace -v -e 'uprobe:./testprogs/intptrcast:fn { $a=*(uint16*)(reg("sp")+8); printf("%d\n", $a); exit();}' EXPECT 3258 TIMEOUT 5 AFTER ./testprogs/intptrcast bpftrace-0.9.4/tests/runtime/json-output000066400000000000000000000075111361633214400204000ustar00rootroot00000000000000NAME invalid_format RUN bpftrace -f jsonx -e 'BEGIN { @scalar = 5; exit(); }' EXPECT ^Invalid output format "jsonx"$ TIMEOUT 5 NAME scalar RUN bpftrace -f json -e 'BEGIN { @scalar = 5; exit(); }' | grep -v attached_probes | python -c 'import sys,json; print(json.load(sys.stdin) == json.load(open("runtime/outputs/scalar.json")))' EXPECT ^True$ TIMEOUT 5 NAME scalar_str RUN bpftrace -f json -e 'BEGIN { @scalar_str = "a b \n d e"; exit(); }' | grep -v attached_probes | python -c 'import sys,json; print(json.load(sys.stdin) == json.load(open("runtime/outputs/scalar_str.json")))' EXPECT ^True$ TIMEOUT 5 NAME complex RUN bpftrace -f json -e 'BEGIN { @complex[comm,2] = 5; exit(); }' | grep -v attached_probes | python -c 'import sys,json; print(json.load(sys.stdin) == json.load(open("runtime/outputs/complex.json")))' EXPECT ^True$ TIMEOUT 5 NAME map RUN bpftrace -f json -e 'BEGIN { @map["key1"] = 2; @map["key2"] = 3; exit(); }' | grep -v attached_probes | python -c 'import sys,json; print(json.load(sys.stdin) == json.load(open("runtime/outputs/map.json")))' EXPECT ^True$ TIMEOUT 5 NAME histogram RUN bpftrace -f json -e 'BEGIN { @hist = hist(2); @hist = hist(1025); exit(); }' | grep -v attached_probes | python -c 'import sys,json; print(json.load(sys.stdin) == json.load(open("runtime/outputs/hist.json")))' EXPECT ^True$ TIMEOUT 5 NAME multiple histograms RUN bpftrace -f json -e 'BEGIN { @["bpftrace"] = hist(2); @["curl"] = hist(-1); @["curl"] = hist(0); @["curl"] = hist(511); @["curl"] = hist(1024); @["curl"] = hist(1025); exit(); }' | grep -v attached_probes | python -c 'import sys,json; print(json.load(sys.stdin) == json.load(open("runtime/outputs/hist_multiple.json")))' EXPECT ^True$ TIMEOUT 5 NAME linear histogram RUN bpftrace -f json -e 'BEGIN { @h = lhist(2, 0, 100, 10); @h = lhist(50, 0, 100, 10); @h = lhist(1000, 0, 100, 10); exit(); }' | grep -v attached_probes | python -c 'import sys,json; print(json.load(sys.stdin) == json.load(open("runtime/outputs/lhist.json")))' EXPECT ^True$ TIMEOUT 5 NAME multiple linear histograms RUN bpftrace -f json -e 'BEGIN { @stats["bpftrace"] = lhist(2, 0, 100, 10); @stats["curl"] = lhist(50, 0, 100, 10); @stats["bpftrace"] = lhist(1000, 0, 100, 10); exit(); }' | grep -v attached_probes | python -c 'import sys,json; print(json.load(sys.stdin) == json.load(open("runtime/outputs/lhist_multiple.json")))' EXPECT ^True$ TIMEOUT 5 NAME stats RUN bpftrace -f json -e 'BEGIN { @stats = stats(2); @stats = stats(10); exit(); }' | grep -v attached_probes | python -c 'import sys,json; print(json.load(sys.stdin) == json.load(open("runtime/outputs/stats.json")))' EXPECT ^True$ TIMEOUT 5 NAME multiple stats RUN bpftrace -f json -e 'BEGIN { @stats["curl"] = stats(2); @stats["zsh"] = stats(10); exit(); }' | grep -v attached_probes | python -c 'import sys,json; print(json.load(sys.stdin) == json.load(open("runtime/outputs/stats_multiple.json")))' EXPECT ^True$ TIMEOUT 5 NAME printf RUN bpftrace -f json -v -e 'BEGIN { printf("test %d", 5); exit(); }' EXPECT ^{"type": "printf", "data": "test 5"}$ TIMEOUT 5 NAME printf_escaping RUN bpftrace -f json -v -e 'BEGIN { printf("test \r \n \t \\ \" bar"); exit(); }' EXPECT ^{"type": "printf", "data": "test \\r \\n \\t \\\\ \\\" bar"}$ TIMEOUT 5 NAME time RUN bpftrace -f json -v -e 'BEGIN { time(); exit(); }' EXPECT ^{"type": "time", "data": "[0-9]*:[0-9]*:[0-9]*\\n"}$ TIMEOUT 5 NAME syscall RUN bpftrace --unsafe -v -f json -e 'BEGIN { system("echo a b c"); exit(); }' EXPECT ^{"type": "syscall", "data": "a b c\\n"}$ TIMEOUT 5 NAME join_delim RUN bpftrace --unsafe -f json -e 'tracepoint:syscalls:sys_enter_execve { join(args->argv, ","); exit(); }' -c "/bin/echo 'A'" EXPECT ^{"type": "join", "data": "/bin/echo,'A'"} TIMEOUT 5 NAME cat RUN bpftrace -v -f json -e 'BEGIN { cat("/proc/uptime"); exit(); }' EXPECT ^{"type": "cat", "data": "[0-9]*.[0-9]* [0-9]*.[0-9]*\\n"}$ TIMEOUT 5 bpftrace-0.9.4/tests/runtime/other000066400000000000000000000075421361633214400172160ustar00rootroot00000000000000NAME if_gt RUN bpftrace -v -e 'i:ms:1 {$a = 10; if ($a > 2) { $a = 20 } printf("a=%d\n", $a); exit();}' EXPECT a=20 TIMEOUT 5 NAME if_lt RUN bpftrace -v -e 'i:ms:1 {$a = 10; if ($a < 2) { $a = 20 } printf("a=%d\n", $a); exit();}' EXPECT a=10 TIMEOUT 5 NAME ifelse_go_else RUN bpftrace -v -e 'i:ms:1 {$a = ""; if (10 < 2) { $a = "hi" } else {$a = "hello"} printf("a=%s\n", $a); exit();}' EXPECT a=hello TIMEOUT 5 NAME ifelse_go_if RUN bpftrace -v -e 'i:ms:1 {$a = ""; if (10 > 2) { $a = "hi" } else {$a = "hello"} printf("a=%s\n", $a); exit();}' EXPECT a=hi TIMEOUT 5 NAME unroll RUN bpftrace -v -e 'i:ms:1 {$a = 1; unroll (10) { $a = $a + 1; } printf("a=%d\n", $a); exit();}' EXPECT a=11 TIMEOUT 5 NAME unroll_max_value RUN bpftrace -v -e 'i:ms:1 {$a = 1; unroll (30) { $a = $a + 2; } printf("a=%d\n", $a); exit();}' EXPECT unroll maximum value is 20 TIMEOUT 5 NAME unroll_min_value RUN bpftrace -v -e 'i:ms:1 {$a = 1; unroll (0) { $a = $a + 2; } printf("a=%d\n", $a); exit();}' EXPECT unroll minimum value is 1 TIMEOUT 5 NAME if_compare_and_print_string RUN bpftrace -v -e 'BEGIN { if (comm == "bpftrace") { printf("comm: %s\n", comm);} exit();}' EXPECT comm: bpftrace TIMEOUT 5 NAME struct positional string compare - equal returns true RUN bpftrace -v -e 'BEGIN { if (str($1) == str($2)) { printf("I got %s\n", str($1));} exit();}' "hello" "hello" EXPECT I got hello TIMEOUT 5 NAME struct positional string compare - equal returns false RUN bpftrace -v -e 'BEGIN { if (str($1) == str($2)) { printf("I got %s\n", str($1));} else { printf("not equal\n");} exit();}' "hi" "hello" EXPECT not equal TIMEOUT 5 NAME struct positional string compare - not equal RUN bpftrace -v -e 'BEGIN { if (str($1) != str($2)) { printf("I got %s\n", str($1));} else { printf("not equal\n");} exit();}' "hello" "hello" EXPECT not equal TIMEOUT 5 NAME string compare map lookup RUN bpftrace -v -e 't:syscalls:sys_enter_openat /comm == "cat"/ { @[comm] = 1; }' -c "/bin/cat /dev/null" EXPECT @\[cat\]: 1 TIMEOUT 5 NAME struct partial string compare - pass RUN bpftrace -v -e 'BEGIN { if (strncmp(str($1), str($2), 4) == 0) { printf("I got %s\n", str($1));} exit();}' "hhvm" "hhvm-proc" EXPECT I got hhvm TIMEOUT 5 NAME struct partial string compare - pass reverse RUN bpftrace -v -e 'BEGIN { if (strncmp(str($1), str($2), 4) == 0) { printf("I got %s\n", str($1));} exit();}' "hhvm-proc" "hhvm" EXPECT I got hhvm-proc TIMEOUT 5 NAME strncmp function argument RUN bpftrace -v -e 'struct F {char s[8];} u:./testprogs/string_args:print { @=strncmp(((struct F*)arg0)->s, "hello", 5); }' -c ./testprogs/string_args EXPECT @: 0 TIMEOUT 5 NAME optional_positional_int RUN bpftrace -e 'BEGIN { printf("-%d-\n", $1); exit() }' EXPECT -0- TIMEOUT 1 NAME optional_positional_str RUN bpftrace -e 'BEGIN { printf("-%s-\n", str($1)); exit() }' EXPECT -- TIMEOUT 1 NAME positional arg count RUN bpftrace -v -e 'BEGIN { printf("got %d args: %s %d\n", $#, str($1), $2); exit();}' "one" 2 EXPECT got 2 args: one 2 TIMEOUT 5 NAME lhist can be cleared RUN bpftrace -e 'BEGIN{ @[1] = lhist(3,0,10,1); clear(@); exit() }' EXPECT .* TIMEOUT 1 NAME hist can be cleared RUN bpftrace -e 'BEGIN{ @[1] = hist(1); clear(@); exit() }' EXPECT .* TIMEOUT 1 NAME stats can be cleared RUN bpftrace -e 'BEGIN{ @[1] = stats(1); clear(@); exit() }' EXPECT .* TIMEOUT 1 NAME avg can be cleared RUN bpftrace -e 'BEGIN{ @[1] = avg(1); clear(@); exit() }' EXPECT .* TIMEOUT 1 NAME sigint under heavy load RUN bpftrace --unsafe -v -e 'tracepoint:sched:sched_switch { system("echo foo"); } END { printf("end"); }' EXPECT end TIMEOUT 5 AFTER sleep 2; pkill -SIGINT bpftrace NAME bitfield access RUN bpftrace -v -e 'struct Foo { unsigned int a:4, b:8, c:3, d:1, e:16; } uprobe:./testprogs/bitfield_test:func{ $foo = (struct Foo *)arg0; printf("%d %d %d %d %d\n", $foo->a, $foo->b, $foo->c, $foo->d, $foo->e); exit()}' EXPECT 1 2 5 0 65535 TIMEOUT 5 AFTER ./testprogs/bitfield_test bpftrace-0.9.4/tests/runtime/outputs/000077500000000000000000000000001361633214400176655ustar00rootroot00000000000000bpftrace-0.9.4/tests/runtime/outputs/complex.json000066400000000000000000000001021361633214400222200ustar00rootroot00000000000000{"type": "map", "data": { "@complex": { "bpftrace,2": 5} }} bpftrace-0.9.4/tests/runtime/outputs/hist.json000066400000000000000000000007331361633214400215320ustar00rootroot00000000000000{"type": "hist", "data": { "@hist": [ {"min": 2, "max": 3, "count": 1}, {"min": 4, "max": 7, "count": 0}, {"min": 8, "max": 15, "count": 0}, {"min": 16, "max": 31, "count": 0}, {"min": 32, "max": 63, "count": 0}, {"min": 64, "max": 127, "count": 0}, {"min": 128, "max": 255, "count": 0}, {"min": 256, "max": 511, "count": 0}, {"min": 512, "max": 1023, "count": 0}, {"min": 1024, "max": 2047, "count": 1} ] }} bpftrace-0.9.4/tests/runtime/outputs/hist_multiple.json000066400000000000000000000012211361633214400234360ustar00rootroot00000000000000{"type": "hist", "data": { "@": { "bpftrace": [ {"min": 2, "max": 3, "count": 1} ], "curl": [ {"max": -1, "count": 1}, {"min": 0, "max": 0, "count": 1}, {"min": 1, "max": 1, "count": 0}, {"min": 2, "max": 3, "count": 0}, {"min": 4, "max": 7, "count": 0}, {"min": 8, "max": 15, "count": 0}, {"min": 16, "max": 31, "count": 0}, {"min": 32, "max": 63, "count": 0}, {"min": 64, "max": 127, "count": 0}, {"min": 128, "max": 255, "count": 0}, {"min": 256, "max": 511, "count": 1}, {"min": 512, "max": 1023, "count": 0}, {"min": 1024, "max": 2047, "count": 2} ]} }} bpftrace-0.9.4/tests/runtime/outputs/lhist.json000066400000000000000000000007571361633214400217140ustar00rootroot00000000000000{"type": "hist", "data": { "@h": [ {"min": 0, "max": 9, "count": 1}, {"min": 10, "max": 19, "count": 0}, {"min": 20, "max": 29, "count": 0}, {"min": 30, "max": 39, "count": 0}, {"min": 40, "max": 49, "count": 0}, {"min": 50, "max": 59, "count": 1}, {"min": 60, "max": 69, "count": 0}, {"min": 70, "max": 79, "count": 0}, {"min": 80, "max": 89, "count": 0}, {"min": 90, "max": 99, "count": 0}, {"min": 100, "count": 1} ] }} bpftrace-0.9.4/tests/runtime/outputs/lhist_multiple.json000066400000000000000000000011001361633214400236060ustar00rootroot00000000000000{"type": "hist", "data": { "@stats": { "curl": [ {"min": 50, "max": 59, "count": 1} ], "bpftrace": [ {"min": 0, "max": 9, "count": 1}, {"min": 10, "max": 19, "count": 0}, {"min": 20, "max": 29, "count": 0}, {"min": 30, "max": 39, "count": 0}, {"min": 40, "max": 49, "count": 0}, {"min": 50, "max": 59, "count": 0}, {"min": 60, "max": 69, "count": 0}, {"min": 70, "max": 79, "count": 0}, {"min": 80, "max": 89, "count": 0}, {"min": 90, "max": 99, "count": 0}, {"min": 100, "count": 1} ]} }} bpftrace-0.9.4/tests/runtime/outputs/map.json000066400000000000000000000001071361633214400213330ustar00rootroot00000000000000{"type": "map", "data": { "@map": { "key1": 2, "key2": 3} }} bpftrace-0.9.4/tests/runtime/outputs/scalar.json000066400000000000000000000000541361633214400220240ustar00rootroot00000000000000{"type": "map", "data": { "@scalar": 5 }} bpftrace-0.9.4/tests/runtime/outputs/scalar_str.json000066400000000000000000000000731361633214400227150ustar00rootroot00000000000000{"type": "map", "data": { "@scalar_str": "a b \n d e" }} bpftrace-0.9.4/tests/runtime/outputs/stats.json000066400000000000000000000001231361633214400217120ustar00rootroot00000000000000{"type": "stats", "data": { "@stats": {"count": 2, "average": 6, "total": 12} }} bpftrace-0.9.4/tests/runtime/outputs/stats_multiple.json000066400000000000000000000002261361633214400236310ustar00rootroot00000000000000{"type": "stats", "data": { "@stats": { "curl": {"count": 1, "average": 2, "total": 2}, "zsh": {"count": 1, "average": 10, "total": 10}} }} bpftrace-0.9.4/tests/runtime/probe000066400000000000000000000120051361633214400171720ustar00rootroot00000000000000NAME kprobe RUN bpftrace -v -e 'kprobe:vfs_read { printf("SUCCESS '$test' %d\n", pid); exit(); }' EXPECT SUCCESS kprobe [0-9][0-9]* TIMEOUT 5 NAME kprobe_short_name RUN bpftrace -v -e 'k:vfs_read { printf("SUCCESS '$test' %d\n", pid); exit(); }' EXPECT SUCCESS kprobe_short_name [0-9][0-9]* TIMEOUT 5 NAME kprobe_target RUN bpftrace -v -e 'kprobe:syscalls:sys_exit_nanosleep { printf("SUCCESS '$test' %d\n", pid); exit(); }' EXPECT kprobes should not have a target TIMEOUT 5 NAME kprobe_order RUN bpftrace -v runtime/scripts/kprobe_order.bt EXPECT first second TIMEOUT 5 AFTER /bin/bash -c "sleep 1e-6"; /bin/bash -c "sleep 1e-6" NAME kprobe_offset RUN bpftrace -v -e 'kprobe:vfs_read+0 { printf("SUCCESS '$test' %d\n", pid); exit(); }' EXPECT SUCCESS kprobe_offset [0-9][0-9]* TIMEOUT 5 NAME kprobe_offset_fail_size RUN bpftrace -v -e 'kprobe:vfs_read+1000000 { printf("SUCCESS '$test' %d\n", pid); exit(); }' EXPECT Offset outside the function bounds \('vfs_read' size is* TIMEOUT 5 NAME kretprobe RUN bpftrace -v -e 'kretprobe:vfs_read { printf("SUCCESS '$test' %d\n", pid); exit(); }' EXPECT SUCCESS kretprobe [0-9][0-9]* TIMEOUT 5 NAME kretprobe_short_name RUN bpftrace -v -e 'kr:vfs_read { printf("SUCCESS '$test' %d\n", pid); exit(); }' EXPECT SUCCESS kretprobe_short_name [0-9][0-9]* TIMEOUT 5 NAME kretprobe_target RUN bpftrace -v -e 'kretprobe:syscalls:sys_exit_nanosleep { printf("SUCCESS '$test' %d\n", pid); exit(); }' EXPECT kprobes should not have a target TIMEOUT 5 NAME kretprobe_order RUN bpftrace -v runtime/scripts/kretprobe_order.bt EXPECT first second TIMEOUT 5 AFTER /bin/bash -c "sleep 1e-6"; /bin/bash -c "sleep 1e-6" NAME uprobe RUN bpftrace -v -e 'uprobe:/bin/bash:echo_builtin { printf("arg0: %d\n", arg0); exit();}' EXPECT arg0: [0-9]* TIMEOUT 5 AFTER /bin/bash -c "echo lala" NAME uprobe_offset RUN bpftrace -v -e 'uprobe:/bin/bash:echo_builtin+0 { printf("arg0: %d\n", arg0); exit();}' EXPECT arg0: [0-9]* TIMEOUT 5 AFTER /bin/bash -c "echo lala" NAME uprobe_offset RUN bpftrace -v -e 'uprobe:/bin/bash:"echo_builtin"+0 { printf("arg0: %d\n", arg0); exit();}' EXPECT arg0: [0-9]* TIMEOUT 5 AFTER /bin/bash -c "echo lala" NAME uprobe_offset_fail_size RUN bpftrace -e 'uprobe:/bin/bash:echo_builtin+1000000 { printf("arg0: %d\n", arg0); exit();}' EXPECT Offset outside the function bounds \('echo_builtin' size is* TIMEOUT 5 NAME uprobe_address_fail_resolve RUN bpftrace -e 'uprobe:/bin/bash:10 { printf("arg0: %d\n", arg0); exit();}' EXPECT Could not resolve address: /bin/bash:0xa TIMEOUT 5 NAME uprobe_order RUN bpftrace -v runtime/scripts/uprobe_order.bt EXPECT first second TIMEOUT 5 AFTER /bin/bash -c "echo lala"; /bin/bash -c "echo lala" NAME uretprobe RUN bpftrace -v -e 'uretprobe:/bin/bash:echo_builtin { printf("readline: %d\n", retval); exit();}' EXPECT readline: [0-9]* TIMEOUT 5 AFTER /bin/bash -c "echo lala" NAME uretprobe_order RUN bpftrace -v runtime/scripts/uretprobe_order.bt EXPECT first second TIMEOUT 5 AFTER /bin/bash -c "echo lala"; /bin/bash -c "echo lala" NAME tracepoint RUN bpftrace -v -e 'tracepoint:syscalls:sys_exit_nanosleep { printf("SUCCESS '$test' %d\n", gid); exit(); }' EXPECT SUCCESS tracepoint [0-9][0-9]* AFTER sleep 0.1 TIMEOUT 5 NAME tracepoint_short_name RUN bpftrace -v -e 't:syscalls:sys_exit_nanosleep { printf("SUCCESS '$test' %d\n", gid); exit(); }' EXPECT SUCCESS tracepoint_short_name [0-9][0-9]* AFTER sleep 0.1 TIMEOUT 5 NAME tracepoint_order RUN bpftrace -v runtime/scripts/tracepoint_order.bt EXPECT first second TIMEOUT 5 AFTER sleep 0.1; sleep 0.1 NAME profile RUN bpftrace -v -e 'profile:hz:599 { @[tid] = count(); exit();}' EXPECT \@\[[0-9]*\]\:\s[0-9] TIMEOUT 5 NAME profile_short_name RUN bpftrace -v -e 'p:hz:599 { @[tid] = count(); exit();}' EXPECT \@\[[0-9]*\]\:\s[0-9] TIMEOUT 5 NAME profile_order RUN bpftrace -v runtime/scripts/profile_order.bt EXPECT first second TIMEOUT 5 NAME interval RUN bpftrace -v -e 't:raw_syscalls:sys_enter { @syscalls = count(); } interval:ms:1{ print(@syscalls); clear(@syscalls); exit();}' EXPECT @syscalls\:\s[0-9]* TIMEOUT 5 NAME interval_short_name RUN bpftrace -v -e 't:raw_syscalls:sys_enter { @syscalls = count(); } i:ms:1{ print(@syscalls); clear(@syscalls); exit();}' EXPECT @syscalls\:\s[0-9]* TIMEOUT 5 NAME interval_order RUN bpftrace -v runtime/scripts/interval_order.bt EXPECT first second TIMEOUT 5 NAME software RUN bpftrace -v -e 'software:faults:1 { @[comm] = count(); exit();}' EXPECT @\[.*\]\:\s[0-9]* TIMEOUT 5 NAME software_order RUN bpftrace -v runtime/scripts/software_order.bt EXPECT first second TIMEOUT 5 NAME hardware REQUIRES ls /sys/devices/cpu/events/cache-misses RUN bpftrace -v -e 'hardware:cache-misses:10 { @[pid] = count(); exit(); }' EXPECT @\[.*\]\:\s[0-9]* TIMEOUT 5 NAME hardware_order REQUIRES ls /sys/devices/cpu/events/cache-misses RUN bpftrace -v runtime/scripts/hardware_order.bt EXPECT first second TIMEOUT 5 NAME BEGIN RUN bpftrace -v -e 'BEGIN { printf("Hello\n"); exit();}' EXPECT Hello TIMEOUT 2 NAME END_processing_after_exit RUN bpftrace -v -e "interval:s:1 { exit(); } END { printf("end"); }" EXPECT end TIMEOUT 2 bpftrace-0.9.4/tests/runtime/regression000066400000000000000000000022651361633214400202520ustar00rootroot00000000000000# https://github.com/iovisor/bpftrace/pull/566#issuecomment-487295113 NAME codegen_struct_save_nested RUN bpftrace -e 'struct Foo { int m; struct { int x; int y; } bar; int n; } BEGIN { @foo = (struct Foo)0; @bar = @foo.bar; @x = @foo.bar.x; printf("done\n"); exit(); }' EXPECT done TIMEOUT 1 NAME logical_and_or_different_sizes RUN bpftrace -v -e 'struct Foo { int m; } uprobe:./testprogs/simple_struct:func { $foo = (struct Foo*)arg0; printf("%d %d %d %d %d\n", $foo->m, $foo->m && 0, 1 && $foo->m, $foo->m || 0, 0 || $foo->m); exit(); }' AFTER ./testprogs/simple_struct EXPECT 2 0 1 1 1 TIMEOUT 5 # https://github.com/iovisor/bpftrace/issues/759 # Update test once https://github.com/iovisor/bpftrace/pull/781 lands # NAME ntop_map_printf # RUN bpftrace -v -e 'union ip { char v4[4]; char v6[16]; } uprobe:./testprogs/ntop:test_ntop { @a = ntop(2, ((union ip*)arg0)->v4); printf("v4: %s; ", @a); @b = ntop(10, ((union ip*)arg1)->v6); exit() } END { printf("v6: %s", @b); clear(@a); clear(@b) }' # AFTER ./testprogs/ntop # EXPECT v4: 127.0.0.1; v6: ffee:ffee:ddcc:ddcc:bbaa:bbaa:c0a8:1 # TIMEOUT 1 NAME modulo_operator RUN bpftrace -v -e 'BEGIN { @x = 4; printf("%d\n", @x % 2) }' EXPECT 0 TIMEOUT 5 bpftrace-0.9.4/tests/runtime/scripts/000077500000000000000000000000001361633214400176315ustar00rootroot00000000000000bpftrace-0.9.4/tests/runtime/scripts/hardware_order.bt000066400000000000000000000006611361633214400231530ustar00rootroot00000000000000hardware:cache-misses:10 { @probe1_ready = 1; if (@go == 1) { printf("first"); } } hardware:cache-misses:10 { @probe2_ready = 1; if (@go == 1) { printf(" second\n"); exit(); } } hardware:cache-misses:10 { // Make sure all probes are attached before letting them print anything // so that the output we get is all from the same event. if (@probe1_ready == 1 && @probe2_ready == 1) { @go = 1; } } bpftrace-0.9.4/tests/runtime/scripts/interval_order.bt000066400000000000000000000006201361633214400231750ustar00rootroot00000000000000interval:ms:1 { @probe1_ready = 1; if (@go == 1) { printf("first"); } } interval:ms:1 { @probe2_ready = 1; if (@go == 1) { printf(" second\n"); exit(); } } interval:ms:1 { // Make sure all probes are attached before letting them print anything // so that the output we get is all from the same event. if (@probe1_ready == 1 && @probe2_ready == 1) { @go = 1; } } bpftrace-0.9.4/tests/runtime/scripts/kprobe_order.bt000066400000000000000000000006611361633214400226400ustar00rootroot00000000000000kprobe:hrtimer_nanosleep { @probe1_ready = 1; if (@go == 1) { printf("first"); } } kprobe:hrtimer_nanosleep { @probe2_ready = 1; if (@go == 1) { printf(" second\n"); exit(); } } kprobe:hrtimer_nanosleep { // Make sure all probes are attached before letting them print anything // so that the output we get is all from the same event. if (@probe1_ready == 1 && @probe2_ready == 1) { @go = 1; } } bpftrace-0.9.4/tests/runtime/scripts/kretprobe_order.bt000066400000000000000000000006721361633214400233550ustar00rootroot00000000000000kretprobe:hrtimer_nanosleep { @probe1_ready = 1; if (@go == 1) { printf("first"); } } kretprobe:hrtimer_nanosleep { @probe2_ready = 1; if (@go == 1) { printf(" second\n"); exit(); } } kretprobe:hrtimer_nanosleep { // Make sure all probes are attached before letting them print anything // so that the output we get is all from the same event. if (@probe1_ready == 1 && @probe2_ready == 1) { @go = 1; } } bpftrace-0.9.4/tests/runtime/scripts/profile_order.bt000066400000000000000000000006501361633214400230140ustar00rootroot00000000000000profile:hz:99 { @probe1_ready = 1; if (@go == 1 && cpu == 0) { printf("first"); } } profile:hz:99 { @probe2_ready = 1; if (@go == 1 && cpu == 0) { printf(" second\n"); exit(); } } profile:hz:99 { // Make sure all probes are attached before letting them print anything // so that the output we get is all from the same event. if (@probe1_ready == 1 && @probe2_ready == 1) { @go = 1; } } bpftrace-0.9.4/tests/runtime/scripts/software_order.bt000066400000000000000000000006341361633214400232100ustar00rootroot00000000000000software:faults:1 { @probe1_ready = 1; if (@go == 1) { printf("first"); } } software:faults:1 { @probe2_ready = 1; if (@go == 1) { printf(" second\n"); exit(); } } software:faults:1 { // Make sure all probes are attached before letting them print anything // so that the output we get is all from the same event. if (@probe1_ready == 1 && @probe2_ready == 1) { @go = 1; } } bpftrace-0.9.4/tests/runtime/scripts/tracepoint_order.bt000066400000000000000000000007331361633214400235260ustar00rootroot00000000000000tracepoint:syscalls:sys_exit_nanosleep { @probe1_ready = 1; if (@go == 1) { printf("first"); } } tracepoint:syscalls:sys_exit_nanosleep { @probe2_ready = 1; if (@go == 1) { printf(" second\n"); exit(); } } tracepoint:syscalls:sys_exit_nanosleep { // Make sure all probes are attached before letting them print anything // so that the output we get is all from the same event. if (@probe1_ready == 1 && @probe2_ready == 1) { @go = 1; } } bpftrace-0.9.4/tests/runtime/scripts/uprobe_order.bt000066400000000000000000000007001361633214400226440ustar00rootroot00000000000000uprobe:/bin/bash:echo_builtin { @probe1_ready = 1; if (@go == 1) { printf("first"); } } uprobe:/bin/bash:echo_builtin { @probe2_ready = 1; if (@go == 1) { printf(" second\n"); exit(); } } uprobe:/bin/bash:echo_builtin { // Make sure all probes are attached before letting them print anything // so that the output we get is all from the same event. if (@probe1_ready == 1 && @probe2_ready == 1) { @go = 1; } } bpftrace-0.9.4/tests/runtime/scripts/uretprobe_order.bt000066400000000000000000000007111361633214400233610ustar00rootroot00000000000000uretprobe:/bin/bash:echo_builtin { @probe1_ready = 1; if (@go == 1) { printf("first"); } } uretprobe:/bin/bash:echo_builtin { @probe2_ready = 1; if (@go == 1) { printf(" second\n"); exit(); } } uretprobe:/bin/bash:echo_builtin { // Make sure all probes are attached before letting them print anything // so that the output we get is all from the same event. if (@probe1_ready == 1 && @probe2_ready == 1) { @go = 1; } } bpftrace-0.9.4/tests/runtime/sigint000066400000000000000000000004711361633214400173640ustar00rootroot00000000000000NAME strace no quit RUN bpftrace -v -e 'i:s:1 { printf("%s %d\n", "SUCCESS", 1); exit() }' & (sleep 0.5 && strace -p $! -o /dev/null) EXPECT SUCCESS 1 TIMEOUT 3 NAME sigint quit RUN bpftrace -v -e 'END { printf("%s %d", "SUCCESS", 1) }' & (sleep 1 && /usr/bin/env kill -s SIGINT $!) EXPECT ^SUCCESS 1$ TIMEOUT 2 bpftrace-0.9.4/tests/runtime/signed_ints000066400000000000000000000014101361633214400203670ustar00rootroot00000000000000NAME stats with negative values RUN bpftrace -e 'BEGIN { @=stats(-10); @=stats(-5); @=stats(5); exit() }' EXPECT ^@:\scount\s3,\saverage\s-3+,\stotal\s-10$ TIMEOUT 5 NAME avg with negative values RUN bpftrace -e 'BEGIN { @=avg(-30); @=avg(-10); exit(); }' EXPECT ^@:\s-20$ TIMEOUT 5 NAME negative map value RUN bpftrace -e 'BEGIN { @ = -11; exit(); }' EXPECT @: -11$ TIMEOUT 1 NAME sum negative maps RUN bpftrace -e 'BEGIN { @ = -11; @+=@; exit() }' EXPECT @: -22$ TIMEOUT 1 NAME Comparison should print as 0 or 1 RUN bpftrace -e 'struct x { uint64_t x; }; BEGIN { $a = ((struct x)0).x; printf("%d %d\n", $a > -1, $a < 1); exit(); }' EXPECT ^0 1$ TIMEOUT 1 NAME sum with negative value RUN bpftrace -e 'BEGIN { @=sum(10); @=sum(-20); exit(); }' EXPECT ^@: -10$ TIMEOUT 1 bpftrace-0.9.4/tests/runtime/uprobe000066400000000000000000000040351361633214400173630ustar00rootroot00000000000000NAME "uprobes - list probes by file" RUN bpftrace -l 'uprobe:./testprogs/uprobe_test' EXPECT uprobe:./testprogs/uprobe_test:function1 TIMEOUT 5 NAME "uprobes - list probes by file with wildcarded filter" RUN bpftrace -l 'uprobe:./testprogs/uprobe_test:func*' EXPECT uprobe:./testprogs/uprobe_test:function1 TIMEOUT 5 NAME "uprobes - list probes with ambiguous wildcarded file fails" RUN bpftrace -l 'uprobe:./testprogs/u*:*' EXPECT must refer to a unique binary TIMEOUT 5 NAME "uprobes - list probes by file with wildcarded file and filter" RUN bpftrace -l 'uprobe:./testprogs/uprobe_test*:func*' EXPECT uprobe:./testprogs/uprobe_test:function1 TIMEOUT 5 NAME "uprobes - list probes by file with specific filter" RUN bpftrace -l 'uprobe:./testprogs/uprobe_test:function1' EXPECT uprobe:./testprogs/uprobe_test:function1 TIMEOUT 5 NAME "uprobes - list probes by pid" RUN bpftrace -l -p $(pidof uprobe_test) | grep -e '^uprobe' EXPECT uprobe:.*/testprogs/uprobe_test:function1 TIMEOUT 5 BEFORE ./testprogs/uprobe_test NAME "uprobes - list probes by pid, uprobes only" RUN bpftrace -l 'uprobe:*' -p $(pidof uprobe_test) EXPECT uprobe:.*/testprogs/uprobe_test:function1 TIMEOUT 5 BEFORE ./testprogs/uprobe_test NAME "uprobes - list probes by pid in separate mount namespace" RUN bpftrace -l -p $(pidof uprobe_test) | grep -e '^uprobe' EXPECT uprobe:.*/bpftrace-unshare-mountns-test/uprobe_test:function1 TIMEOUT 5 BEFORE ./testprogs/mountns_wrapper uprobe_test NAME "uprobes - attach to probe for executable in separate mount namespace" RUN bpftrace -e 'uprobe:/tmp/bpftrace-unshare-mountns-test/uprobe_test:function1 { printf("here\n" ); exit(); }' -p $(pidof uprobe_test) EXPECT Attaching 1 probe... TIMEOUT 5 BEFORE ./testprogs/mountns_wrapper uprobe_test NAME "uprobes - list probes in non-executable library" RUN bpftrace -l 'uprobe:./testlibs/libsimple.so:fun' EXPECT uprobe:./testlibs/libsimple.so:fun TIMEOUT 5 NAME "uprobes - probe function in non-executable library" RUN bpftrace -e 'uprobe:./testlibs/libsimple.so:fun {}' EXPECT Attaching 1 probe... TIMEOUT 5 bpftrace-0.9.4/tests/runtime/usdt000066400000000000000000000136461361633214400170560ustar00rootroot00000000000000NAME "usdt probes - list probes by file" RUN bpftrace -l 'usdt:./testprogs/usdt_test' EXPECT usdt:./testprogs/usdt_test:tracetest:testprobe TIMEOUT 5 REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - list probes by pid" RUN bpftrace -l 'usdt:*' -p $(pidof usdt_test) EXPECT usdt_test:tracetest:testprobe TIMEOUT 5 BEFORE ./testprogs/usdt_test REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - filter probes by file on provider" RUN bpftrace -l 'usdt:./testprogs/usdt_test:tracetest2:*' EXPECT usdt:./testprogs/usdt_test:tracetest2:testprobe2 TIMEOUT 5 REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - filter probes by pid on provider" RUN bpftrace -l 'usdt:*:tracetest2:*' -p $(pidof usdt_test) EXPECT usdt_test:tracetest2:testprobe2 TIMEOUT 5 BEFORE ./testprogs/usdt_test REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - filter probes by wildcard file and wildcard probe name" RUN bpftrace -l 'usdt:./testprogs/usdt_test*:tracetest:test*' EXPECT usdt:./testprogs/usdt_test:tracetest:testprobe2 TIMEOUT 5 REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - filter probes by file and wildcard probe name" RUN bpftrace -l 'usdt:./testprogs/usdt_test:tracetest:test*' EXPECT usdt:./testprogs/usdt_test:tracetest:testprobe2 TIMEOUT 5 REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - filter probes by pid and wildcard probe name" RUN bpftrace -l 'usdt:*:tracetest:test*' -p $(pidof usdt_test) EXPECT usdt_test:tracetest:testprobe2 TIMEOUT 5 BEFORE ./testprogs/usdt_test REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - attach to fully specified probe by file" RUN bpftrace -e 'usdt:./testprogs/usdt_test:tracetest:testprobe { printf("here\n" ); exit(); }' EXPECT here TIMEOUT 5 BEFORE ./testprogs/usdt_test REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - attach to fully specified probe by pid" RUN bpftrace -e 'usdt::tracetest:testprobe { printf("here\n" ); exit(); }' -p $(pidof usdt_test) EXPECT here TIMEOUT 5 BEFORE ./testprogs/usdt_test REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - all probes by wildcard and file" RUN bpftrace -e 'usdt:./testprogs/usdt_test:* { printf("here\n" ); exit(); }' EXPECT Attaching 3 probes... TIMEOUT 5 BEFORE ./testprogs/usdt_test REQUIRES ./testprogs/usdt_test should_not_skip # TODO(mmarchini): re-enable this test # This test has two problems: it relies on the latest version of bcc and it # assumes USDTs are coming only from the binary. On Ubuntu, glibc is built with # USDT support, which means it will attach to 43 probes instead of 3. Before # re-enabling this test, we should: # - Skip if bcc doesn't support multiple probes with the same name # - Fix https://github.com/iovisor/bpftrace/issues/565#issuecomment-496731112 # and https://github.com/iovisor/bpftrace/issues/688 NAME "usdt probes - all probes by wildcard and pid" RUN bpftrace -e 'usdt:* { printf("here\n" ); exit(); }' -p $(pidof usdt_test) EXPECT Attaching 3 probes... TIMEOUT 5 BEFORE ./testprogs/usdt_test REQUIRES bash -c "exit 1" # REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - attach to probe by wildcard and file" RUN bpftrace -e 'usdt:./testprogs/usdt_test::*probe2 { printf("here\n" ); exit(); }' EXPECT Attaching 2 probes... TIMEOUT 5 BEFORE ./testprogs/usdt_test REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - attach to probes by wildcard file" RUN bpftrace -e 'usdt:./testprogs/usdt_test*::* { printf("here\n" ); exit(); }' EXPECT Attaching 3 probes... TIMEOUT 5 BEFORE ./testprogs/usdt_test REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - attach to probe with ambiguous wildcard file fails" RUN bpftrace -e 'usdt:./testprogs/u*::* { printf("here\n" ); exit(); }' EXPECT must refer to a unique binary TIMEOUT 5 NAME "usdt probes - attach to probe on multiple providers by wildcard and pid" RUN bpftrace -e 'usdt:::*probe2 { printf("here\n" ); exit(); }' -p $(pidof usdt_test) EXPECT Attaching 2 probes... TIMEOUT 5 BEFORE ./testprogs/usdt_test REQUIRES ./testprogs/usdt_test should_not_skip # TODO(mmarchini): re-enable this test # This test relies on the latest version of bcc. Before re-enabling this test, # we should: # - Skip if bcc doesn't support multiple probes with the same name NAME "usdt probes - attach to probe with args by file" RUN bpftrace -e 'usdt:./testprogs/usdt_test:* { printf("%s\n", str(arg1) ); exit(); }' EXPECT Hello world TIMEOUT 5 BEFORE ./testprogs/usdt_test REQUIRES bash -c "exit 1" # REQUIRES ./testprogs/usdt_test should_not_skip # TODO(mmarchini): re-enable this test # This test relies on the latest version of bcc. Before re-enabling this test, # we should: # - Skip if bcc doesn't support multiple probes with the same name NAME "usdt probes - attach to probe with args by pid" RUN bpftrace -e 'usdt:./testprogs/usdt_test:* { printf("%s\n", str(arg1) ); exit(); }' -p $(pidof usdt_test) EXPECT Hello world TIMEOUT 5 BEFORE ./testprogs/usdt_test REQUIRES bash -c "exit 1" # REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - attach to probe with probe builtin and args by file" RUN bpftrace -e 'usdt:./testprogs/usdt_test:* { printf("%lld %s\n", arg0, probe ); exit(); }' EXPECT usdt:./testprogs/usdt_test:tracetest:testprobe TIMEOUT 5 BEFORE ./testprogs/usdt_test REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - attach to probe with probe builtin and args by pid" RUN bpftrace -e 'usdt:./testprogs/usdt_test:* { printf("%lld %s\n", arg0, probe ); exit(); }' -p $(pidof usdt_test) EXPECT usdt_test:tracetest:testprobe TIMEOUT 5 BEFORE ./testprogs/usdt_test REQUIRES ./testprogs/usdt_test should_not_skip NAME "usdt probes - attach to probe with semaphore" RUN bpftrace -e 'usdt::tracetest:testprobe { printf("%s\n", str(arg1) ); exit(); }' -p $(pidof usdt_semaphore_test) EXPECT tracetest_testprobe_semaphore: 1 TIMEOUT 5 BEFORE ./testprogs/usdt_semaphore_test REQUIRES ./testprogs/usdt_semaphore_test should_not_skip bpftrace-0.9.4/tests/runtime/variable000066400000000000000000000026621361633214400176600ustar00rootroot00000000000000NAME global_int RUN bpftrace -v -e 'i:ms:1 {@a = 10; printf("%d\n", @a); exit();}' EXPECT \@a: 10 TIMEOUT 5 NAME global_string RUN bpftrace -v -e 'i:ms:1 {@a = "hi"; printf("%s\\n", @a); exit();}' EXPECT @a: hi TIMEOUT 5 NAME local_int RUN bpftrace -v -e 'i:ms:1 {$a = 10; printf("a=%d\n", $a); exit();}' EXPECT a=10 TIMEOUT 5 NAME local_string RUN bpftrace -v -e 'i:ms:1 {$a = "hi"; printf("a=%s\n", $a); exit();}' EXPECT a=hi TIMEOUT 5 NAME global_associative_arrays RUN bpftrace -v -e 'k:vfs_read { @start[tid] = nsecs; } kretprobe:vfs_read /@start[tid] != 0/ { printf("slept for %d ms\n", (nsecs - @start[tid]) / 1000000); delete(@start[tid]); exit();}' EXPECT slept for [0-9]+ ms TIMEOUT 5 AFTER cat /dev/null NAME scratch RUN bpftrace -v -e 'k:vfs_read { @start[tid] = nsecs; } kretprobe:vfs_read /@start[tid] != 0/ { $delta = nsecs - @start[tid]; printf("slept for %d ms\n", $delta / 1000000); delete(@start[tid]); exit(); }' EXPECT slept for [0-9]* ms TIMEOUT 5 AFTER cat /dev/null NAME 32-bit tracepoint arg RUN bpftrace -v -e 'tracepoint:random:random_read { $i = args->got_bits; printf("bits: %d\n", $i); if ($i == 24) { exit() } }' EXPECT bits: 24 TIMEOUT 5 AFTER dd if=/dev/random bs=3 count=1 NAME tracepoint arg casts in predicates RUN bpftrace -v -e 'tracepoint:syscalls:sys_enter_wait4 /args->ru/ { @ru[tid] = args->ru; } tracepoint:syscalls:sys_exit_wait4 /@ru[tid]/ { @++; exit(); }' -c ./testprogs/wait4_ru EXPECT @: 1 TIMEOUT 5 bpftrace-0.9.4/tests/runtime/watchpoint000066400000000000000000000002401361633214400202410ustar00rootroot00000000000000NAME watchpoint - absolute address RUN bpftrace -v -e 'watchpoint::0x10000000:8:w { printf("hit!\n"); exit() }' -c ./testprogs/watchpoint EXPECT hit! TIMEOUT 5 bpftrace-0.9.4/tests/semantic_analyser.cpp000066400000000000000000001356541361633214400207020ustar00rootroot00000000000000#include "semantic_analyser.h" #include "bpffeature.h" #include "bpftrace.h" #include "clang_parser.h" #include "driver.h" #include "mocks.h" #include "gmock/gmock.h" #include "gtest/gtest.h" namespace bpftrace { namespace test { namespace semantic_analyser { using ::testing::_; using ::testing::HasSubstr; void test_for_warning( BPFtrace &bpftrace, const std::string &input, const std::string &warning, bool invert = false, bool safe_mode = true) { Driver driver(bpftrace); bpftrace.safe_mode_ = safe_mode; ASSERT_EQ(driver.parse_str(input), 0); ClangParser clang; clang.parse(driver.root_, bpftrace); ASSERT_EQ(driver.parse_str(input), 0); MockBPFfeature feature; std::stringstream out; ast::SemanticAnalyser semantics(driver.root_, bpftrace, feature, out); semantics.analyse(); if (invert) EXPECT_THAT(out.str(), Not(HasSubstr(warning))); else EXPECT_THAT(out.str(), HasSubstr(warning)); } void test_for_warning( const std::string &input, const std::string &warning, bool invert = false, bool safe_mode = true) { auto bpftrace = get_mock_bpftrace(); test_for_warning(*bpftrace, input, warning, invert, safe_mode); } void test(BPFtrace &bpftrace, BPFfeature &feature, Driver &driver, const std::string &input, int expected_result = 0, bool safe_mode = true) { bpftrace.safe_mode_ = safe_mode; ASSERT_EQ(driver.parse_str(input), 0); ClangParser clang; clang.parse(driver.root_, bpftrace); ASSERT_EQ(driver.parse_str(input), 0); std::stringstream out; ast::SemanticAnalyser semantics(driver.root_, bpftrace, feature, out); std::stringstream msg; msg << "\nInput:\n" << input << "\n\nOutput:\n"; EXPECT_EQ(expected_result, semantics.analyse()) << msg.str() + out.str(); } void test(BPFtrace &bpftrace, const std::string &input, int expected_result=0, bool safe_mode = true) { Driver driver(bpftrace); MockBPFfeature feature = MockBPFfeature(); test(bpftrace, feature, driver, input, expected_result, safe_mode); } void test(Driver &driver, const std::string &input, int expected_result=0, bool safe_mode = true) { auto bpftrace = get_mock_bpftrace(); MockBPFfeature feature = MockBPFfeature(); test(*bpftrace, feature, driver, input, expected_result, safe_mode); } void test(BPFfeature &feature, const std::string &input, int expected_result = 0, bool safe_mode = true) { auto bpftrace = get_mock_bpftrace(); Driver driver(*bpftrace); test(*bpftrace, feature, driver, input, expected_result, safe_mode); } void test(const std::string &input, int expected_result=0, bool safe_mode = true) { MockBPFfeature feature = MockBPFfeature(); test(feature, input, expected_result, safe_mode); } TEST(semantic_analyser, builtin_variables) { // Just check that each builtin variable exists. test("kprobe:f { pid }", 0); test("kprobe:f { tid }", 0); test("kprobe:f { cgroup }", 0); test("kprobe:f { uid }", 0); test("kprobe:f { username }", 0); test("kprobe:f { gid }", 0); test("kprobe:f { nsecs }", 0); test("kprobe:f { elapsed }", 0); test("kprobe:f { cpu }", 0); test("kprobe:f { curtask }", 0); test("kprobe:f { rand }", 0); test("kprobe:f { ctx }", 0); test("kprobe:f { comm }", 0); test("kprobe:f { stack }", 0); test("kprobe:f { kstack }", 0); test("kprobe:f { ustack }", 0); test("kprobe:f { arg0 }", 0); test("kprobe:f { sarg0 }", 0); test("kretprobe:f { retval }", 0); test("kprobe:f { func }", 0); test("uprobe:/bin/sh:f { func }", 0); test("kprobe:f { probe }", 0); test("tracepoint:a:b { args }", 0); test("kprobe:f { fake }", 1); MockBPFfeature feature(false); test(feature, "k:f { cgroup }", 1); } TEST(semantic_analyser, builtin_cpid) { auto bpftrace = get_mock_bpftrace(); test(*bpftrace, "i:ms:100 { printf(\"%d\\n\", cpid); }", 1, false); test(*bpftrace, "i:ms:100 { @=cpid }", 1, false); test(*bpftrace, "i:ms:100 { $a=cpid }", 1, false); bpftrace->cmd_ = "sleep 1"; test(*bpftrace, "i:ms:100 { printf(\"%d\\n\", cpid); }", 0, false); test(*bpftrace, "i:ms:100 { @=cpid }", 0, false); test(*bpftrace, "i:ms:100 { $a=cpid }", 0, false); } TEST(semantic_analyser, builtin_functions) { // Just check that each function exists. // Each function should also get its own test case for more thorough testing test("kprobe:f { @x = hist(123) }", 0); test("kprobe:f { @x = lhist(123, 0, 123, 1) }", 0); test("kprobe:f { @x = count() }", 0); test("kprobe:f { @x = sum(pid) }", 0); test("kprobe:f { @x = min(pid) }", 0); test("kprobe:f { @x = max(pid) }", 0); test("kprobe:f { @x = avg(pid) }", 0); test("kprobe:f { @x = stats(pid) }", 0); test("kprobe:f { @x = 1; delete(@x) }", 0); test("kprobe:f { @x = 1; print(@x) }", 0); test("kprobe:f { @x = 1; clear(@x) }", 0); test("kprobe:f { @x = 1; zero(@x) }", 0); test("kprobe:f { time() }", 0); test("kprobe:f { exit() }", 0); test("kprobe:f { str(0xffff) }", 0); test("kprobe:f { printf(\"hello\\n\") }", 0); test("kprobe:f { system(\"ls\\n\") }", 0, false /* safe_node */); test("kprobe:f { join(0) }", 0); test("kprobe:f { sym(0xffff) }", 0); test("kprobe:f { ksym(0xffff) }", 0); test("kprobe:f { usym(0xffff) }", 0); test("kprobe:f { kaddr(\"sym\") }", 0); test("kprobe:f { ntop(0xffff) }", 0); test("kprobe:f { ntop(2, 0xffff) }", 0); test("kprobe:f { reg(\"ip\") }", 0); test("kprobe:f { kstack(1) }", 0); test("kprobe:f { ustack(1) }", 0); test("kprobe:f { cat(\"/proc/uptime\") }", 0); test("uprobe:/bin/bash:main { uaddr(\"glob_asciirange\") }", 0); } TEST(semantic_analyser, undefined_map) { test("kprobe:f / @mymap == 123 / { @mymap = 0 }", 0); test("kprobe:f / @mymap == 123 / { 456; }", 10); test("kprobe:f / @mymap1 == 1234 / { 1234; @mymap1 = @mymap2; }", 10); } TEST(semantic_analyser, consistent_map_values) { test("kprobe:f { @x = 0; @x = 1; }", 0); test("kprobe:f { @x = 0; @x = \"a\"; }", 1); } TEST(semantic_analyser, consistent_map_keys) { test("kprobe:f { @x = 0; @x; }", 0); test("kprobe:f { @x[1] = 0; @x[2]; }", 0); test("kprobe:f { @x = 0; @x[1]; }", 10); test("kprobe:f { @x[1] = 0; @x; }", 10); test("kprobe:f { @x[1,2] = 0; @x[3,4]; }", 0); test("kprobe:f { @x[1,2] = 0; @x[3]; }", 10); test("kprobe:f { @x[1] = 0; @x[2,3]; }", 10); test("kprobe:f { @x[1,\"a\",kstack] = 0; @x[2,\"b\", kstack]; }", 0); test("kprobe:f { @x[1,\"a\",kstack] = 0; @x[\"b\", 2, kstack]; }", 10); } TEST(semantic_analyser, predicate_expressions) { test("kprobe:f / 999 / { 123 }", 0); test("kprobe:f / \"str\" / { 123 }", 10); test("kprobe:f / kstack / { 123 }", 10); test("kprobe:f / @mymap / { @mymap = \"str\" }", 10); } TEST(semantic_analyser, ternary_experssions) { test("kprobe:f { @x = pid < 10000 ? 1 : 2 }", 0); test("kprobe:f { @x = pid < 10000 ? \"lo\" : \"high\" }", 0); test("kprobe:f { @x = pid < 10000 ? 1 : \"high\" }", 10); test("kprobe:f { @x = pid < 10000 ? \"lo\" : 2 }", 10); } TEST(semantic_analyser, mismatched_call_types) { test("kprobe:f { @x = 1; @x = count(); }", 1); test("kprobe:f { @x = count(); @x = sum(pid); }", 1); test("kprobe:f { @x = 1; @x = hist(0); }", 1); } TEST(semantic_analyser, compound_left) { test("kprobe:f { $a <<= 0 }", 1); test("kprobe:f { $a = 0; $a <<= 1 }", 0); test("kprobe:f { @a <<= 1 }", 0); } TEST(semantic_analyser, compound_right) { test("kprobe:f { $a >>= 0 }", 1); test("kprobe:f { $a = 0; $a >>= 1 }", 0); test("kprobe:f { @a >>= 1 }", 0); } TEST(semantic_analyser, compound_plus) { test("kprobe:f { $a += 0 }", 1); test("kprobe:f { $a = 0; $a += 1 }", 0); test("kprobe:f { @a += 1 }", 0); } TEST(semantic_analyser, compound_minus) { test("kprobe:f { $a -= 0 }", 1); test("kprobe:f { $a = 0; $a -= 1 }", 0); test("kprobe:f { @a -= 1 }", 0); } TEST(semantic_analyser, compound_mul) { test("kprobe:f { $a *= 0 }", 1); test("kprobe:f { $a = 0; $a *= 1 }", 0); test("kprobe:f { @a *= 1 }", 0); } TEST(semantic_analyser, compound_div) { test("kprobe:f { $a /= 0 }", 1); test("kprobe:f { $a = 0; $a /= 1 }", 0); test("kprobe:f { @a /= 1 }", 0); } TEST(semantic_analyser, compound_mod) { test("kprobe:f { $a %= 0 }", 1); test("kprobe:f { $a = 0; $a %= 1 }", 0); test("kprobe:f { @a %= 1 }", 0); } TEST(semantic_analyser, compound_band) { test("kprobe:f { $a &= 0 }", 1); test("kprobe:f { $a = 0; $a &= 1 }", 0); test("kprobe:f { @a &= 1 }", 0); } TEST(semantic_analyser, compound_bor) { test("kprobe:f { $a |= 0 }", 1); test("kprobe:f { $a = 0; $a |= 1 }", 0); test("kprobe:f { @a |= 1 }", 0); } TEST(semantic_analyser, compound_bxor) { test("kprobe:f { $a ^= 0 }", 1); test("kprobe:f { $a = 0; $a ^= 1 }", 0); test("kprobe:f { @a ^= 1 }", 0); } TEST(semantic_analyser, call_hist) { test("kprobe:f { @x = hist(1); }", 0); test("kprobe:f { @x = hist(); }", 1); test("kprobe:f { hist(1); }", 1); test("kprobe:f { $x = hist(1); }", 1); test("kprobe:f { @x[hist(1)] = 1; }", 1); } TEST(semantic_analyser, call_lhist) { test("kprobe:f { @ = lhist(5, 0, 10, 1); }", 0); test("kprobe:f { @ = lhist(5, 0, 10); }", 1); test("kprobe:f { @ = lhist(5, 0); }", 1); test("kprobe:f { @ = lhist(5); }", 1); test("kprobe:f { @ = lhist(); }", 1); test("kprobe:f { @ = lhist(5, 0, 10, 1, 2); }", 1); test("kprobe:f { lhist(-10, -10, 10, 1); }", 1); test("kprobe:f { @ = lhist(-10, -10, 10, 1); }", 10); // must be positive test("kprobe:f { $x = lhist(); }", 1); test("kprobe:f { @[lhist()] = 1; }", 1); } TEST(semantic_analyser, call_count) { test("kprobe:f { @x = count(); }", 0); test("kprobe:f { @x = count(1); }", 1); test("kprobe:f { count(); }", 1); test("kprobe:f { $x = count(); }", 1); test("kprobe:f { @[count()] = 1; }", 1); } TEST(semantic_analyser, call_sum) { test("kprobe:f { @x = sum(123); }", 0); test("kprobe:f { @x = sum(); }", 1); test("kprobe:f { @x = sum(123, 456); }", 1); test("kprobe:f { sum(123); }", 1); test("kprobe:f { $x = sum(123); }", 1); test("kprobe:f { @[sum(123)] = 1; }", 1); } TEST(semantic_analyser, call_min) { test("kprobe:f { @x = min(123); }", 0); test("kprobe:f { @x = min(); }", 1); test("kprobe:f { min(123); }", 1); test("kprobe:f { $x = min(123); }", 1); test("kprobe:f { @[min(123)] = 1; }", 1); } TEST(semantic_analyser, call_max) { test("kprobe:f { @x = max(123); }", 0); test("kprobe:f { @x = max(); }", 1); test("kprobe:f { max(123); }", 1); test("kprobe:f { $x = max(123); }", 1); test("kprobe:f { @[max(123)] = 1; }", 1); } TEST(semantic_analyser, call_avg) { test("kprobe:f { @x = avg(123); }", 0); test("kprobe:f { @x = avg(); }", 1); test("kprobe:f { avg(123); }", 1); test("kprobe:f { $x = avg(123); }", 1); test("kprobe:f { @[avg(123)] = 1; }", 1); } TEST(semantic_analyser, call_stats) { test("kprobe:f { @x = stats(123); }", 0); test("kprobe:f { @x = stats(); }", 1); test("kprobe:f { stats(123); }", 1); test("kprobe:f { $x = stats(123); }", 1); test("kprobe:f { @[stats(123)] = 1; }", 1); } TEST(semantic_analyser, call_delete) { test("kprobe:f { @x = 1; delete(@x); }", 0); test("kprobe:f { delete(1); }", 1); test("kprobe:f { delete(); }", 1); test("kprobe:f { @y = delete(@x); }", 1); test("kprobe:f { $y = delete(@x); }", 1); test("kprobe:f { @[delete(@x)] = 1; }", 1); } TEST(semantic_analyser, call_exit) { test("kprobe:f { exit(); }", 0); test("kprobe:f { exit(1); }", 1); test("kprobe:f { @a = exit(1); }", 1); test("kprobe:f { $a = exit(1); }", 1); test("kprobe:f { @[exit(1)] = 1; }", 1); } TEST(semantic_analyser, call_print) { test("kprobe:f { @x = count(); print(@x); }", 0); test("kprobe:f { @x = count(); print(@x, 5); }", 0); test("kprobe:f { @x = count(); print(@x, 5, 10); }", 0); test("kprobe:f { @x = count(); print(@x, 5, 10, 1); }", 1); test("kprobe:f { @x = count(); @x = print(); }", 1); test("kprobe:f { print(@x); @x[1,2] = count(); }", 0); test("kprobe:f { @x[1,2] = count(); print(@x); }", 0); test("kprobe:f { @x[1,2] = count(); print(@x[3,4]); }", 1); test("kprobe:f { @x = count(); @ = print(@x); }", 1); test("kprobe:f { @x = count(); $y = print(@x); }", 1); test("kprobe:f { @x = count(); @[print(@x)] = 1; }", 1); } TEST(semantic_analyser, call_clear) { test("kprobe:f { @x = count(); clear(@x); }", 0); test("kprobe:f { @x = count(); clear(@x, 1); }", 1); test("kprobe:f { @x = count(); @x = clear(); }", 1); test("kprobe:f { clear(@x); @x[1,2] = count(); }", 0); test("kprobe:f { @x[1,2] = count(); clear(@x); }", 0); test("kprobe:f { @x[1,2] = count(); clear(@x[3,4]); }", 1); test("kprobe:f { @x = count(); @ = clear(@x); }", 1); test("kprobe:f { @x = count(); $y = clear(@x); }", 1); test("kprobe:f { @x = count(); @[clear(@x)] = 1; }", 1); } TEST(semantic_analyser, call_zero) { test("kprobe:f { @x = count(); zero(@x); }", 0); test("kprobe:f { @x = count(); zero(@x, 1); }", 1); test("kprobe:f { @x = count(); @x = zero(); }", 1); test("kprobe:f { zero(@x); @x[1,2] = count(); }", 0); test("kprobe:f { @x[1,2] = count(); zero(@x); }", 0); test("kprobe:f { @x[1,2] = count(); zero(@x[3,4]); }", 1); test("kprobe:f { @x = count(); @ = zero(@x); }", 1); test("kprobe:f { @x = count(); $y = zero(@x); }", 1); test("kprobe:f { @x = count(); @[zero(@x)] = 1; }", 1); } TEST(semantic_analyser, call_time) { test("kprobe:f { time(); }", 0); test("kprobe:f { time(\"%M:%S\"); }", 0); test("kprobe:f { time(\"%M:%S\", 1); }", 1); test("kprobe:f { @x = time(); }", 1); test("kprobe:f { $x = time(); }", 1); test("kprobe:f { @[time()] = 1; }", 1); test("kprobe:f { time(1); }", 10); test("kprobe:f { $x = \"str\"; time($x); }", 10); } TEST(semantic_analyser, call_str) { test("kprobe:f { str(arg0); }", 0); test("kprobe:f { @x = str(arg0); }", 0); test("kprobe:f { str(); }", 1); test("kprobe:f { str(\"hello\"); }", 10); } TEST(semantic_analyser, call_str_2_lit) { test("kprobe:f { str(arg0, 3); }", 0); test("kprobe:f { @x = str(arg0, 3); }", 0); test("kprobe:f { str(arg0, \"hello\"); }", 10); } TEST(semantic_analyser, call_str_2_expr) { test("kprobe:f { str(arg0, arg1); }", 0); test("kprobe:f { @x = str(arg0, arg1); }", 0); } TEST(semantic_analyser, call_sym) { test("kprobe:f { ksym(arg0); }", 0); test("kprobe:f { @x = ksym(arg0); }", 0); test("kprobe:f { ksym(); }", 1); test("kprobe:f { ksym(\"hello\"); }", 1); test("kprobe:f { sym(arg0); }", 0); test("kprobe:f { @x = sym(arg0); }", 0); test("kprobe:f { sym(); }", 1); test("kprobe:f { sym(\"hello\"); }", 1); } TEST(semantic_analyser, call_usym) { test("kprobe:f { usym(arg0); }", 0); test("kprobe:f { @x = usym(arg0); }", 0); test("kprobe:f { usym(); }", 1); test("kprobe:f { usym(\"hello\"); }", 1); } TEST(semantic_analyser, call_ntop) { std::string structs = "struct inet { unsigned char ipv4[4]; unsigned char ipv6[16]; unsigned char invalid[10]; } "; test("kprobe:f { ntop(2, arg0); }", 0); test("kprobe:f { ntop(arg0); }", 0); test(structs + "kprobe:f { ntop(10, ((struct inet*)0)->ipv4); }", 0); test(structs + "kprobe:f { ntop(10, ((struct inet*)0)->ipv6); }", 0); test(structs + "kprobe:f { ntop(((struct inet*)0)->ipv4); }", 0); test(structs + "kprobe:f { ntop(((struct inet*)0)->ipv6); }", 0); test("kprobe:f { @x = ntop(2, arg0); }", 0); test("kprobe:f { @x = ntop(arg0); }", 0); test("kprobe:f { @x = ntop(2, 0xFFFF); }", 0); test("kprobe:f { @x = ntop(0xFFFF); }", 0); test(structs + "kprobe:f { @x = ntop(((struct inet*)0)->ipv4); }", 0); test(structs + "kprobe:f { @x = ntop(((struct inet*)0)->ipv6); }", 0); test("kprobe:f { ntop(); }", 1); test("kprobe:f { ntop(2, \"hello\"); }", 1); test("kprobe:f { ntop(\"hello\"); }", 1); test(structs + "kprobe:f { ntop(((struct inet*)0)->invalid); }", 1); } TEST(semantic_analyser, call_kaddr) { test("kprobe:f { kaddr(\"avenrun\"); }", 0); test("kprobe:f { @x = kaddr(\"avenrun\"); }", 0); test("kprobe:f { kaddr(); }", 1); test("kprobe:f { kaddr(123); }", 1); } TEST(semantic_analyser, call_uaddr) { test("u:/bin/bash:main { uaddr(\"github.com/golang/glog.severityName\"); }", 0); test("uprobe:/bin/bash:main { uaddr(\"glob_asciirange\"); }", 0); test("u:/bin/bash:main,u:/bin/bash:readline { uaddr(\"glob_asciirange\"); }", 0); test("uprobe:/bin/bash:main { @x = uaddr(\"glob_asciirange\"); }", 0); test("uprobe:/bin/bash:main { uaddr(); }", 1); test("uprobe:/bin/bash:main { uaddr(123); }", 1); test("uprobe:/bin/bash:main { uaddr(\"?\"); }", 1); test("uprobe:/bin/bash:main { $str = \"glob_asciirange\"; uaddr($str); }", 1); test("uprobe:/bin/bash:main { @str = \"glob_asciirange\"; uaddr(@str); }", 1); test("k:f { uaddr(\"A\"); }", 1); test("i:s:1 { uaddr(\"A\"); }", 1); // The C struct parser should set the is_signed flag on signed types BPFtrace bpftrace; Driver driver(bpftrace); std::string prog = "uprobe:/bin/bash:main {" "$a = uaddr(\"12345_1\");" "$b = uaddr(\"12345_2\");" "$c = uaddr(\"12345_4\");" "$d = uaddr(\"12345_8\");" "$e = uaddr(\"12345_5\");" "$f = uaddr(\"12345_33\");" "}"; test(driver, prog, 0); std::vector sizes = { 1, 2, 4, 8, 8, 8 }; for (size_t i = 0; i < sizes.size(); i++) { auto v = static_cast( driver.root_->probes->at(0)->stmts->at(i)); EXPECT_EQ(SizedType(Type::integer, 8, false), v->var->type); EXPECT_EQ(true, v->var->type.is_pointer); EXPECT_EQ((unsigned long int)sizes.at(i), v->var->type.pointee_size); } } TEST(semantic_analyser, call_reg) { test("kprobe:f { reg(\"ip\"); }", 0); test("kprobe:f { @x = reg(\"ip\"); }", 0); test("kprobe:f { reg(\"blah\"); }", 1); test("kprobe:f { reg(); }", 1); test("kprobe:f { reg(123); }", 1); } TEST(semantic_analyser, call_func) { test("kprobe:f { @[func] = count(); }", 0); test("kprobe:f { printf(\"%s\", func); }", 0); test("uprobe:/bin/sh:f { @[func] = count(); }", 0); test("uprobe:/bin/sh:f { printf(\"%s\", func); }", 0); } TEST(semantic_analyser, call_probe) { test("kprobe:f { @[probe] = count(); }", 0); test("kprobe:f { printf(\"%s\", probe); }", 0); } TEST(semantic_analyser, call_cat) { test("kprobe:f { cat(\"/proc/loadavg\"); }", 0); test("kprobe:f { cat(\"/proc/%d/cmdline\", 1); }", 0); test("kprobe:f { cat(); }", 1); test("kprobe:f { cat(123); }", 1); test("kprobe:f { @x = cat(\"/proc/loadavg\"); }", 1); test("kprobe:f { $x = cat(\"/proc/loadavg\"); }", 1); test("kprobe:f { @[cat(\"/proc/loadavg\")] = 1; }", 1); } TEST(semantic_analyser, call_stack) { test("kprobe:f { kstack() }", 0); test("kprobe:f { ustack() }", 0); test("kprobe:f { kstack(bpftrace) }", 0); test("kprobe:f { ustack(bpftrace) }", 0); test("kprobe:f { kstack(perf) }", 0); test("kprobe:f { ustack(perf) }", 0); test("kprobe:f { kstack(3) }", 0); test("kprobe:f { ustack(3) }", 0); test("kprobe:f { kstack(perf, 3) }", 0); test("kprobe:f { ustack(perf, 3) }", 0); // Wrong arguments test("kprobe:f { kstack(3, perf) }", 10); test("kprobe:f { ustack(3, perf) }", 10); test("kprobe:f { kstack(perf, 3, 4) }", 1); test("kprobe:f { ustack(perf, 3, 4) }", 1); test("kprobe:f { kstack(bob) }", 1); test("kprobe:f { ustack(bob) }", 1); test("kprobe:f { kstack(\"str\") }", 10); test("kprobe:f { ustack(\"str\") }", 10); test("kprobe:f { kstack(perf, \"str\") }", 10); test("kprobe:f { ustack(perf, \"str\") }", 10); test("kprobe:f { kstack(\"str\", 3) }", 10); test("kprobe:f { ustack(\"str\", 3) }", 10); // Non-literals test("kprobe:f { @x = perf; kstack(@x) }", 10); test("kprobe:f { @x = perf; ustack(@x) }", 10); test("kprobe:f { @x = perf; kstack(@x, 3) }", 10); test("kprobe:f { @x = perf; ustack(@x, 3) }", 10); test("kprobe:f { @x = 3; kstack(@x) }", 10); test("kprobe:f { @x = 3; ustack(@x) }", 10); test("kprobe:f { @x = 3; kstack(perf, @x) }", 10); test("kprobe:f { @x = 3; ustack(perf, @x) }", 10); } TEST(semantic_analyser, map_reassignment) { test("kprobe:f { @x = 1; @x = 2; }", 0); test("kprobe:f { @x = 1; @x = \"foo\"; }", 1); } TEST(semantic_analyser, variable_reassignment) { test("kprobe:f { $x = 1; $x = 2; }", 0); test("kprobe:f { $x = 1; $x = \"foo\"; }", 1); } TEST(semantic_analyser, map_use_before_assign) { test("kprobe:f { @x = @y; @y = 2; }", 0); } TEST(semantic_analyser, variable_use_before_assign) { test("kprobe:f { @x = $y; $y = 2; }", 1); } TEST(semantic_analyser, maps_are_global) { test("kprobe:f { @x = 1 } kprobe:g { @y = @x }", 0); test("kprobe:f { @x = 1 } kprobe:g { @x = \"abc\" }", 1); } TEST(semantic_analyser, variables_are_local) { test("kprobe:f { $x = 1 } kprobe:g { $x = \"abc\"; }", 0); test("kprobe:f { $x = 1 } kprobe:g { @y = $x }", 1); } TEST(semantic_analyser, array_access) { test("kprobe:f { $s = arg0; @x = $s->y[0];}", 10); test("kprobe:f { $s = 0; @x = $s->y[0];}", 10); test("struct MyStruct { int y[4]; } kprobe:f { $s = (struct MyStruct *) " "arg0; @x = $s->y[5];}", 10); test("struct MyStruct { int y[4]; } kprobe:f { $s = (struct MyStruct *) " "arg0; @x = $s->y[-1];}", 10); test("struct MyStruct { int y[4]; } kprobe:f { $s = (struct MyStruct *) " "arg0; @x = $s->y[\"0\"];}", 10); test("struct MyStruct { int y[4]; } kprobe:f { $s = (struct MyStruct *) " "arg0; $idx = 0; @x = $s->y[$idx];}", 10); BPFtrace bpftrace; Driver driver(bpftrace); test(driver, "struct MyStruct { int y[4]; } kprobe:f { $s = (struct MyStruct *) " "arg0; @x = $s->y[0];}", 0); auto assignment = static_cast( driver.root_->probes->at(0)->stmts->at(1)); EXPECT_EQ(SizedType(Type::integer, 8, true), assignment->map->type); } TEST(semantic_analyser, variable_type) { BPFtrace bpftrace; Driver driver(bpftrace); test(driver, "kprobe:f { $x = 1 }", 0); SizedType st(Type::integer, 8, true); auto assignment = static_cast(driver.root_->probes->at(0)->stmts->at(0)); EXPECT_EQ(st, assignment->var->type); } TEST(semantic_analyser, unroll) { test("kprobe:f { $i = 0; unroll(5) { printf(\"i: %d\\n\", $i); $i = $i + 1; } }", 0); test("kprobe:f { $i = 0; unroll(21) { printf(\"i: %d\\n\", $i); $i = $i + 1; } }", 1); test("kprobe:f { $i = 0; unroll(0) { printf(\"i: %d\\n\", $i); $i = $i + 1; } }", 1); } TEST(semantic_analyser, map_integer_sizes) { BPFtrace bpftrace; Driver driver(bpftrace); std::string structs = "struct type1 { int x; }"; test(driver, structs + "kprobe:f { $x = ((struct type1)0).x; @x = $x; }", 0); auto var_assignment = static_cast(driver.root_->probes->at(0)->stmts->at(0)); auto map_assignment = static_cast(driver.root_->probes->at(0)->stmts->at(1)); EXPECT_EQ(SizedType(Type::integer, 4, true), var_assignment->var->type); EXPECT_EQ(SizedType(Type::integer, 8, true), map_assignment->map->type); } TEST(semantic_analyser, unop_dereference) { test("kprobe:f { *0; }", 0); test("struct X { int n; } kprobe:f { $x = (struct X*)0; *$x; }", 0); test("struct X { int n; } kprobe:f { $x = (struct X)0; *$x; }", 1); test("kprobe:f { *\"0\"; }", 10); } TEST(semantic_analyser, unop_not) { test("kprobe:f { ~0; }", 0); test("struct X { int n; } kprobe:f { $x = (struct X*)0; ~$x; }", 10); test("struct X { int n; } kprobe:f { $x = (struct X)0; ~$x; }", 10); test("kprobe:f { ~\"0\"; }", 10); } TEST(semantic_analyser, unop_increment_decrement) { test("kprobe:f { $x = 0; $x++; }", 0); test("kprobe:f { $x = 0; $x--; }", 0); test("kprobe:f { $x = 0; ++$x; }", 0); test("kprobe:f { $x = 0; --$x; }", 0); test("kprobe:f { @x++; }", 0); test("kprobe:f { @x--; }", 0); test("kprobe:f { ++@x; }", 0); test("kprobe:f { --@x; }", 0); test("kprobe:f { $x++; }", 1); test("kprobe:f { @x = \"a\"; @x++; }", 1); test("kprobe:f { $x = \"a\"; $x++; }", 10); } TEST(semantic_analyser, printf) { test("kprobe:f { printf(\"hi\") }", 0); test("kprobe:f { printf(1234) }", 1); test("kprobe:f { printf() }", 1); test("kprobe:f { $fmt = \"mystring\"; printf($fmt) }", 1); test("kprobe:f { printf(\"%s\", comm) }", 0); test("kprobe:f { printf(\"%-16s\", comm) }", 0); test("kprobe:f { printf(\"%-10.10s\", comm) }", 0); test("kprobe:f { printf(\"%A\", comm) }", 10); test("kprobe:f { @x = printf(\"hi\") }", 1); test("kprobe:f { $x = printf(\"hi\") }", 1); } TEST(semantic_analyser, system) { test("kprobe:f { system(\"ls\") }", 0, false /* safe_mode */); test("kprobe:f { system(1234) }", 1, false /* safe_mode */); test("kprobe:f { system() }", 1, false /* safe_mode */); test("kprobe:f { $fmt = \"mystring\"; system($fmt) }", 1, false /* safe_mode */); } TEST(semantic_analyser, printf_format_int) { test("kprobe:f { printf(\"int: %d\", 1234) }", 0); test("kprobe:f { printf(\"int: %d\", pid) }", 0); test("kprobe:f { @x = 123; printf(\"int: %d\", @x) }", 0); test("kprobe:f { $x = 123; printf(\"int: %d\", $x) }", 0); test("kprobe:f { printf(\"int: %u\", 1234) }", 0); test("kprobe:f { printf(\"int: %x\", 1234) }", 0); test("kprobe:f { printf(\"int: %X\", 1234) }", 0); } TEST(semantic_analyser, printf_format_int_with_length) { test("kprobe:f { printf(\"int: %d\", 1234) }", 0); test("kprobe:f { printf(\"int: %u\", 1234) }", 0); test("kprobe:f { printf(\"int: %x\", 1234) }", 0); test("kprobe:f { printf(\"int: %X\", 1234) }", 0); test("kprobe:f { printf(\"int: %p\", 1234) }", 0); test("kprobe:f { printf(\"int: %hhd\", 1234) }", 0); test("kprobe:f { printf(\"int: %hhu\", 1234) }", 0); test("kprobe:f { printf(\"int: %hhx\", 1234) }", 0); test("kprobe:f { printf(\"int: %hhX\", 1234) }", 0); test("kprobe:f { printf(\"int: %hhp\", 1234) }", 0); test("kprobe:f { printf(\"int: %hd\", 1234) }", 0); test("kprobe:f { printf(\"int: %hu\", 1234) }", 0); test("kprobe:f { printf(\"int: %hx\", 1234) }", 0); test("kprobe:f { printf(\"int: %hX\", 1234) }", 0); test("kprobe:f { printf(\"int: %hp\", 1234) }", 0); test("kprobe:f { printf(\"int: %ld\", 1234) }", 0); test("kprobe:f { printf(\"int: %lu\", 1234) }", 0); test("kprobe:f { printf(\"int: %lx\", 1234) }", 0); test("kprobe:f { printf(\"int: %lX\", 1234) }", 0); test("kprobe:f { printf(\"int: %lp\", 1234) }", 0); test("kprobe:f { printf(\"int: %lld\", 1234) }", 0); test("kprobe:f { printf(\"int: %llu\", 1234) }", 0); test("kprobe:f { printf(\"int: %llx\", 1234) }", 0); test("kprobe:f { printf(\"int: %llX\", 1234) }", 0); test("kprobe:f { printf(\"int: %llp\", 1234) }", 0); test("kprobe:f { printf(\"int: %jd\", 1234) }", 0); test("kprobe:f { printf(\"int: %ju\", 1234) }", 0); test("kprobe:f { printf(\"int: %jx\", 1234) }", 0); test("kprobe:f { printf(\"int: %jX\", 1234) }", 0); test("kprobe:f { printf(\"int: %jp\", 1234) }", 0); test("kprobe:f { printf(\"int: %zd\", 1234) }", 0); test("kprobe:f { printf(\"int: %zu\", 1234) }", 0); test("kprobe:f { printf(\"int: %zx\", 1234) }", 0); test("kprobe:f { printf(\"int: %zX\", 1234) }", 0); test("kprobe:f { printf(\"int: %zp\", 1234) }", 0); test("kprobe:f { printf(\"int: %td\", 1234) }", 0); test("kprobe:f { printf(\"int: %tu\", 1234) }", 0); test("kprobe:f { printf(\"int: %tx\", 1234) }", 0); test("kprobe:f { printf(\"int: %tX\", 1234) }", 0); test("kprobe:f { printf(\"int: %tp\", 1234) }", 0); } TEST(semantic_analyser, printf_format_string) { test("kprobe:f { printf(\"str: %s\", \"mystr\") }", 0); test("kprobe:f { printf(\"str: %s\", comm) }", 0); test("kprobe:f { printf(\"str: %s\", str(arg0)) }", 0); test("kprobe:f { @x = \"hi\"; printf(\"str: %s\", @x) }", 0); test("kprobe:f { $x = \"hi\"; printf(\"str: %s\", $x) }", 0); } TEST(semantic_analyser, printf_bad_format_string) { test("kprobe:f { printf(\"%d\", \"mystr\") }", 10); test("kprobe:f { printf(\"%d\", str(arg0)) }", 10); test("kprobe:f { printf(\"%s\", 1234) }", 10); test("kprobe:f { printf(\"%s\", arg0) }", 10); } TEST(semantic_analyser, printf_format_multi) { test("kprobe:f { printf(\"%d %d %s\", 1, 2, \"mystr\") }", 0); test("kprobe:f { printf(\"%d %s %d\", 1, 2, \"mystr\") }", 10); } TEST(semantic_analyser, join) { test("kprobe:f { join(arg0) }", 0); test("kprobe:f { printf(\"%s\", join(arg0)) }", 10); test("kprobe:f { join() }", 1); test("kprobe:f { $fmt = \"mystring\"; join($fmt) }", 10); test("kprobe:f { @x = join(arg0) }", 1); test("kprobe:f { $x = join(arg0) }", 1); } TEST(semantic_analyser, join_delimiter) { test("kprobe:f { join(arg0, \",\") }", 0); test("kprobe:f { printf(\"%s\", join(arg0, \",\")) }", 10); test("kprobe:f { $fmt = \"mystring\"; join($fmt, \",\") }", 10); test("kprobe:f { @x = join(arg0, \",\") }", 1); test("kprobe:f { $x = join(arg0, \",\") }", 1); test("kprobe:f { join(arg0, 3) }", 10); } TEST(semantic_analyser, kprobe) { test("kprobe:f { 1 }", 0); test("kprobe:path:f { 1 }", 1); test("kprobe { 1 }", 1); test("kretprobe:f { 1 }", 0); test("kretprobe:path:f { 1 }", 1); test("kretprobe { 1 }", 1); } TEST(semantic_analyser, uprobe) { test("uprobe:/bin/sh:f { 1 }", 0); test("u:/bin/sh:f { 1 }", 0); test("uprobe:/bin/sh:0x10 { 1 }", 0); test("u:/bin/sh:0x10 { 1 }", 0); test("uprobe:/bin/sh:f+0x10 { 1 }", 0); test("u:/bin/sh:f+0x10 { 1 }", 0); test("uprobe:sh:f { 1 }", 0); test("uprobe:/notexistfile:f { 1 }", 1); test("uprobe:notexistfile:f { 1 }", 1); test("uprobe:f { 1 }", 1); test("uprobe { 1 }", 1); test("uretprobe:/bin/sh:f { 1 }", 0); test("ur:/bin/sh:f { 1 }", 0); test("uretprobe:sh:f { 1 }", 0); test("ur:sh:f { 1 }", 0); test("uretprobe:/bin/sh:0x10 { 1 }", 0); test("ur:/bin/sh:0x10 { 1 }", 0); test("uretprobe:/bin/sh:f+0x10 { 1 }", 1); test("ur:/bin/sh:f+0x10 { 1 }", 1); test("uretprobe:/notexistfile:f { 1 }", 1); test("uretprobe:notexistfile:f { 1 }", 1); test("uretprobe:f { 1 }", 1); test("uretprobe { 1 }", 1); } TEST(semantic_analyser, usdt) { test("usdt:/bin/sh:probe { 1 }", 0); test("usdt:sh:probe { 1 }", 0); test("usdt:/bin/sh:namespace:probe { 1 }", 0); test("usdt:/notexistfile:probe { 1 }", 1); test("usdt:notexistfile:probe { 1 }", 1); test("usdt { 1 }", 1); } TEST(semantic_analyser, begin_end_probes) { test("BEGIN { 1 }", 0); test("BEGIN:f { 1 }", 1); test("BEGIN:path:f { 1 }", 1); test("BEGIN { 1 } BEGIN { 2 }", 10); test("END { 1 }", 0); test("END:f { 1 }", 1); test("END:path:f { 1 }", 1); test("END { 1 } END { 2 }", 10); } TEST(semantic_analyser, tracepoint) { test("tracepoint:category:event { 1 }", 0); test("tracepoint:f { 1 }", 1); test("tracepoint { 1 }", 1); } TEST(semantic_analyser, watchpoint) { test("watchpoint::0x1234:8:rw { 1 }", 0); test("watchpoint:/dev/null:0x1234:8:rw { 1 }", 0); test("watchpoint::0x1234:9:rw { 1 }", 1); test("watchpoint::0x1234:8:rwx { 1 }", 1); test("watchpoint::0x1234:8:rx { 1 }", 1); test("watchpoint::0x1234:8:b { 1 }", 1); test("watchpoint::0x1234:8:rww { 1 }", 1); test("watchpoint::0x0:8:rww { 1 }", 1); } TEST(semantic_analyser, args_builtin_wrong_use) { test("BEGIN { args->foo }", 1); test("END { args->foo }", 1); test("kprobe:f { args->foo }", 1); test("kretprobe:f { args->foo }", 1); test("uprobe:f { args->foo }", 1); test("uretprobe:f { args->foo }", 1); test("profile:f { args->foo }", 1); test("usdt:sh:probe { args->foo }", 1); test("profile:ms:100 { args->foo }", 1); test("hardware:cache-references:1000000 { args->foo }", 1); test("software:faults:1000 { args->foo }", 1); test("interval:s:1 { args->foo }", 1); } TEST(semantic_analyser, profile) { test("profile:hz:997 { 1 }", 0); test("profile:s:10 { 1 }", 0); test("profile:ms:100 { 1 }", 0); test("profile:us:100 { 1 }", 0); test("profile:ms:nan { 1 }", 1); test("profile:unit:100 { 1 }", 1); test("profile:f { 1 }", 1); test("profile { 1 }", 1); } TEST(semantic_analyser, variable_cast_types) { std::string structs = "struct type1 { int field; } struct type2 { int field; }"; test(structs + "kprobe:f { $x = (struct type1)cpu; $x = (struct type1)cpu; }", 0); test(structs + "kprobe:f { $x = (struct type1)cpu; $x = (struct type2)cpu; }", 1); } TEST(semantic_analyser, map_cast_types) { std::string structs = "struct type1 { int field; } struct type2 { int field; }"; test(structs + "kprobe:f { @x = (struct type1)cpu; @x = (struct type1)cpu; }", 0); test(structs + "kprobe:f { @x = (struct type1)cpu; @x = (struct type2)cpu; }", 1); } TEST(semantic_analyser, variable_casts_are_local) { std::string structs = "struct type1 { int field; } struct type2 { int field; }"; test(structs + "kprobe:f { $x = (struct type1)cpu } kprobe:g { $x = (struct type2)cpu; }", 0); } TEST(semantic_analyser, map_casts_are_global) { std::string structs = "struct type1 { int field; } struct type2 { int field; }"; test(structs + "kprobe:f { @x = (struct type1)cpu } kprobe:g { @x = (struct type2)cpu; }", 1); } TEST(semantic_analyser, cast_unknown_type) { test("kprobe:f { (struct faketype)cpu }", 1); } TEST(semantic_analyser, field_access) { std::string structs = "struct type1 { int field; }"; test(structs + "kprobe:f { ((struct type1)cpu).field }", 0); test(structs + "kprobe:f { $x = (struct type1)cpu; $x.field }", 0); test(structs + "kprobe:f { @x = (struct type1)cpu; @x.field }", 0); test("struct task_struct {int x;} kprobe:f { curtask->x }", 0); } TEST(semantic_analyser, field_access_wrong_field) { std::string structs = "struct type1 { int field; }"; test(structs + "kprobe:f { ((struct type1)cpu).blah }", 1); test(structs + "kprobe:f { $x = (struct type1)cpu; $x.blah }", 1); test(structs + "kprobe:f { @x = (struct type1)cpu; @x.blah }", 1); } TEST(semantic_analyser, field_access_wrong_expr) { std::string structs = "struct type1 { int field; }"; test(structs + "kprobe:f { 1234->field }", 10); } TEST(semantic_analyser, field_access_types) { std::string structs = "struct type1 { int field; char mystr[8]; }" "struct type2 { int field; }"; test(structs + "kprobe:f { ((struct type1)0).field == 123 }", 0); test(structs + "kprobe:f { ((struct type1)0).field == \"abc\" }", 10); test(structs + "kprobe:f { ((struct type1)0).mystr == \"abc\" }", 0); test(structs + "kprobe:f { ((struct type1)0).mystr == 123 }", 10); test(structs + "kprobe:f { ((struct type1)0).field == ((struct type2)0).field }", 0); test(structs + "kprobe:f { ((struct type1)0).mystr == ((struct type2)0).field }", 10); } TEST(semantic_analyser, field_access_pointer) { std::string structs = "struct type1 { int field; }"; test(structs + "kprobe:f { ((struct type1*)0)->field }", 0); test(structs + "kprobe:f { ((struct type1*)0).field }", 1); test(structs + "kprobe:f { *((struct type1*)0) }", 0); } TEST(semantic_analyser, field_access_sub_struct) { std::string structs = "struct type2 { int field; } " "struct type1 { struct type2 *type2ptr; struct type2 type2; }"; test(structs + "kprobe:f { ((struct type1)0).type2ptr->field }", 0); test(structs + "kprobe:f { ((struct type1)0).type2.field }", 0); test(structs + "kprobe:f { $x = (struct type2)0; $x = ((struct type1)0).type2 }", 0); test(structs + "kprobe:f { $x = (struct type2*)0; $x = ((struct type1)0).type2ptr }", 0); test(structs + "kprobe:f { $x = (struct type1)0; $x = ((struct type1)0).type2 }", 1); test(structs + "kprobe:f { $x = (struct type1*)0; $x = ((struct type1)0).type2ptr }", 1); } TEST(semantic_analyser, field_access_is_internal) { BPFtrace bpftrace; Driver driver(bpftrace); std::string structs = "struct type1 { int x; }"; test(driver, structs + "kprobe:f { $x = ((struct type1)0).x }", 0); auto var_assignment1 = static_cast(driver.root_->probes->at(0)->stmts->at(0)); EXPECT_EQ(false, var_assignment1->var->type.is_internal); test(driver, structs + "kprobe:f { @type1 = (struct type1)0; $x = @type1.x }", 0); auto map_assignment = static_cast(driver.root_->probes->at(0)->stmts->at(0)); auto var_assignment2 = static_cast(driver.root_->probes->at(0)->stmts->at(1)); EXPECT_EQ(true, map_assignment->map->type.is_internal); EXPECT_EQ(true, var_assignment2->var->type.is_internal); } TEST(semantic_analyser, probe_short_name) { test("t:a:b { args }", 0); test("k:f { pid }", 0); test("kr:f { pid }", 0); test("u:sh:f { 1 }", 0); test("ur:sh:f { 1 }", 0); test("p:hz:997 { 1 }", 0); test("h:cache-references:1000000 { 1 }", 0); test("s:faults:1000 { 1 }", 0); test("i:s:1 { 1 }", 0); } TEST(semantic_analyser, positional_parameters) { BPFtrace bpftrace; bpftrace.add_param("123"); bpftrace.add_param("hello"); test(bpftrace, "kprobe:f { printf(\"%d\", $0); }", 1); test(bpftrace, "kprobe:f { printf(\"%s\", str($0)); }", 1); test(bpftrace, "kprobe:f { printf(\"%d\", $1); }", 0); test(bpftrace, "kprobe:f { printf(\"%s\", str($1)); }", 10); test(bpftrace, "kprobe:f { printf(\"%s\", str($2)); }", 0); test(bpftrace, "kprobe:f { printf(\"%d\", $2); }", 10); // Parameters are not required to exist to be used: test(bpftrace, "kprobe:f { printf(\"%s\", str($3)); }", 0); test(bpftrace, "kprobe:f { printf(\"%d\", $3); }", 0); test(bpftrace, "kprobe:f { printf(\"%d\", $#); }", 0); test(bpftrace, "kprobe:f { printf(\"%s\", str($#)); }", 10); } TEST(semantic_analyser, macros) { test("#define A 1\nkprobe:f { printf(\"%d\", A); }", 0); test("#define A A\nkprobe:f { printf(\"%d\", A); }", 1); test("enum { A = 1 }\n#define A A\nkprobe:f { printf(\"%d\", A); }", 0); } TEST(semantic_analyser, enums) { test("enum { a = 1, b } kprobe:f { printf(\"%d\", a); }", 0); } TEST(semantic_analyser, signed_int_comparison_warnings) { bool invert = true; std::string cmp_sign = "comparison of integers of different signs"; test_for_warning("kretprobe:f /-1 < retval/ {}", cmp_sign); test_for_warning("kretprobe:f /-1 > retval/ {}", cmp_sign); test_for_warning("kretprobe:f /-1 >= retval/ {}", cmp_sign); test_for_warning("kretprobe:f /-1 <= retval/ {}", cmp_sign); test_for_warning("kretprobe:f /-1 != retval/ {}", cmp_sign); test_for_warning("kretprobe:f /-1 == retval/ {}", cmp_sign); test_for_warning("kretprobe:f /retval > -1/ {}", cmp_sign); test_for_warning("kretprobe:f /retval < -1/ {}", cmp_sign); // These should not trigger a warning test_for_warning("kretprobe:f /1 < retval/ {}", cmp_sign, invert); test_for_warning("kretprobe:f /1 > retval/ {}", cmp_sign, invert); test_for_warning("kretprobe:f /1 >= retval/ {}", cmp_sign, invert); test_for_warning("kretprobe:f /1 <= retval/ {}", cmp_sign, invert); test_for_warning("kretprobe:f /1 != retval/ {}", cmp_sign, invert); test_for_warning("kretprobe:f /1 == retval/ {}", cmp_sign, invert); test_for_warning("kretprobe:f /retval > 1/ {}", cmp_sign, invert); test_for_warning("kretprobe:f /retval < 1/ {}", cmp_sign, invert); } TEST(semantic_analyser, signed_int_arithmetic_warnings) { // Test type warnings for arithmetic bool invert = true; std::string msg = "arithmetic on integers of different signs"; test_for_warning("kprobe:f { @ = -1 - arg0 }", msg); test_for_warning("kprobe:f { @ = -1 + arg0 }", msg); test_for_warning("kprobe:f { @ = -1 * arg0 }", msg); test_for_warning("kprobe:f { @ = -1 / arg0 }", msg); test_for_warning("kprobe:f { @ = arg0 + 1 }", msg, invert); test_for_warning("kprobe:f { @ = arg0 - 1 }", msg, invert); test_for_warning("kprobe:f { @ = arg0 * 1 }", msg, invert); test_for_warning("kprobe:f { @ = arg0 / 1 }", msg, invert); } TEST(semantic_analyser, signed_int_division_warnings) { bool invert = true; std::string msg = "signed operands"; test_for_warning("kprobe:f { @ = -1 / 1 }", msg); test_for_warning("kprobe:f { @ = 1 / -1 }", msg); // These should not trigger a warning test_for_warning("kprobe:f { @ = 1 / 1 }", msg, invert); test_for_warning("kprobe:f { @ = -(1 / 1) }", msg, invert); } TEST(semantic_analyser, signed_int_modulo_warnings) { bool invert = true; std::string msg = "signed operands"; test_for_warning("kprobe:f { @ = -1 % 1 }", msg); test_for_warning("kprobe:f { @ = 1 % -1 }", msg); // These should not trigger a warning test_for_warning("kprobe:f { @ = 1 % 1 }", msg, invert); test_for_warning("kprobe:f { @ = -(1 % 1) }", msg, invert); } TEST(semantic_analyser, map_as_lookup_table) { // Initializing a map should not lead to usage issues test("BEGIN { @[0] = \"abc\"; @[1] = \"def\" } kretprobe:f { printf(\"%s\\n\", @[retval])}"); } TEST(semantic_analyser, cast_sign) { // The C struct parser should set the is_signed flag on signed types BPFtrace bpftrace; Driver driver(bpftrace); std::string prog = "struct t { int s; unsigned int us; long l; unsigned long ul }; " "kprobe:f { " " $t = ((struct t *)0xFF);" " $s = $t->s; $us = $t->us; $l = $t->l; $lu = $t->ul; }"; test(driver, prog, 0); auto s = static_cast(driver.root_->probes->at(0)->stmts->at(1)); auto us = static_cast(driver.root_->probes->at(0)->stmts->at(2)); auto l = static_cast(driver.root_->probes->at(0)->stmts->at(3)); auto ul = static_cast(driver.root_->probes->at(0)->stmts->at(4)); EXPECT_EQ(SizedType(Type::integer, 4, true), s->var->type); EXPECT_EQ(SizedType(Type::integer, 4, false), us->var->type); EXPECT_EQ(SizedType(Type::integer, 8, true), l->var->type); EXPECT_EQ(SizedType(Type::integer, 8, false), ul->var->type); } TEST(semantic_analyser, binop_sign) { // Make sure types are correct std::string prog_pre = "struct t { long l; unsigned long ul }; " "kprobe:f { " " $t = ((struct t *)0xFF); "; std::string operators[] = { "==", "!=", "<", "<=", ">", ">=", "+", "-", "/", "*"}; for(std::string op : operators) { BPFtrace bpftrace; Driver driver(bpftrace); std::string prog = prog_pre + "$varA = $t->l " + op + " $t->l; " "$varB = $t->ul " + op + " $t->l; " "$varC = $t->ul " + op + " $t->ul;" "}"; test(driver, prog, 0); auto varA = static_cast(driver.root_->probes->at(0)->stmts->at(1)); EXPECT_EQ(SizedType(Type::integer, 8, true), varA->var->type); auto varB = static_cast(driver.root_->probes->at(0)->stmts->at(2)); EXPECT_EQ(SizedType(Type::integer, 8, false), varB->var->type); auto varC = static_cast(driver.root_->probes->at(0)->stmts->at(3)); EXPECT_EQ(SizedType(Type::integer, 8, false), varC->var->type); } } TEST(semantic_analyser, int_cast_types) { test("kretprobe:f { @ = (int8)retval }", 0); test("kretprobe:f { @ = (int16)retval }", 0); test("kretprobe:f { @ = (int32)retval }", 0); test("kretprobe:f { @ = (int64)retval }", 0); test("kretprobe:f { @ = (uint8)retval }", 0); test("kretprobe:f { @ = (uint16)retval }", 0); test("kretprobe:f { @ = (uint32)retval }", 0); test("kretprobe:f { @ = (uint64)retval }", 0); test("kretprobe:f { @ = (int2)retval }", 1); test("kretprobe:f { @ = (uint2)retval }", 1); } TEST(semantic_analyser, int_cast_usage) { test("kretprobe:f /(int32) retval < 0 / {}", 0); test("kprobe:f /(int32) arg0 < 0 / {}", 0); test("kprobe:f { @=sum((int32)arg0) }", 0); test("kprobe:f { @=avg((int32)arg0) }", 0); test("kprobe:f { @=avg((int32)arg0) }", 0); test("kprobe:f { @=avg((int32)\"abc\") }", 1); } TEST(semantic_analyser, intptr_cast_types) { test("kretprobe:f { @ = *(int8*)retval }", 0); test("kretprobe:f { @ = *(int16*)retval }", 0); test("kretprobe:f { @ = *(int32*)retval }", 0); test("kretprobe:f { @ = *(int64*)retval }", 0); test("kretprobe:f { @ = *(uint8*)retval }", 0); test("kretprobe:f { @ = *(uint16*)retval }", 0); test("kretprobe:f { @ = *(uint32*)retval }", 0); test("kretprobe:f { @ = *(uint64*)retval }", 0); test("kretprobe:f { @ = *(int2*)retval }", 1); test("kretprobe:f { @ = *(uint2*)retval }", 1); } TEST(semantic_analyser, intptr_cast_usage) { test("kretprobe:f /(*(int32*) retval) < 0 / {}", 0); test("kprobe:f /(*(int32*) arg0) < 0 / {}", 0); test("kprobe:f { @=sum(*(int32*)arg0) }", 0); test("kprobe:f { @=avg(*(int32*)arg0) }", 0); test("kprobe:f { @=avg(*(int32*)arg0) }", 0); // This is OK (@ = 0x636261) test("kprobe:f { @=avg(*(int32*)\"abc\") }", 0); test("kprobe:f { @=avg(*(int32*)123) }", 0); } TEST(semantic_analyser, signal) { // int literals test("k:f { signal(1); }", 0, false); test("kr:f { signal(1); }", 0, false); test("u:/bin/sh:f { signal(11); }", 0, false); test("ur:/bin/sh:f { signal(11); }", 0, false); test("p:hz:1 { signal(1); }", 0, false); // vars test("k:f { @=1; signal(@); }", 0, false); test("k:f { @=1; signal((int32)arg0); }", 0, false); // String test("k:f { signal(\"KILL\"); }", 0, false); test("k:f { signal(\"SIGKILL\"); }", 0, false); // Not allowed for: test("hardware:pcm:1000 { signal(1); }", 1, false); test("software:pcm:1000 { signal(1); }", 1, false); test("BEGIN { signal(1); }", 1, false); test("END { signal(1); }", 1, false); test("i:s:1 { signal(1); }", 1, false); // invalid signals test("k:f { signal(0); }", 1, false); test("k:f { signal(-100); }", 1, false); test("k:f { signal(100); }", 1, false); test("k:f { signal(\"SIGABC\"); }", 1, false); test("k:f { signal(\"ABC\"); }", 1, false); // Missing kernel support MockBPFfeature feature(false); test(feature, "k:f { signal(1) }", 1, false); test(feature, "k:f { signal(\"KILL\"); }", 1, false); } TEST(semantic_analyser, strncmp) { // Test strncmp builtin test("i:s:1 { $a = \"bar\"; strncmp(\"foo\", $a, 1) }", 0); test("i:s:1 { strncmp(\"foo\", \"bar\", 1) }", 0); test("i:s:1 { strncmp(1) }", 1); test("i:s:1 { strncmp(1,1,1) }", 10); test("i:s:1 { strncmp(\"a\",1,1) }", 10); test("i:s:1 { strncmp(\"a\",\"a\",-1) }", 1); test("i:s:1 { strncmp(\"a\",\"a\",\"foo\") }", 1); } TEST(semantic_analyser, override) { // literals test("k:f { override(-1); }", 0, false); // variables test("k:f { override(arg0); }", 0, false); // Probe types test("kr:f { override(-1); }", 1, false); test("u:f { override(-1); }", 1, false); test("t:syscalls:sys_enter_openat { override(-1); }", 1, false); test("i:s:1 { override(-1); }", 1, false); test("p:hz:1 { override(-1); }", 1, false); } TEST(semantic_analyser, struct_member_keywords) { std::string keywords[] = { "arg0", "args", "curtask", "func", "gid" "rand", "uid", "avg", "cat", "exit", "kaddr", "min", "printf", "usym", "kstack", "ustack", "bpftrace", "perf", "uprobe", "kprobe", }; for(auto kw : keywords) { test("struct S{ int " + kw + ";}; k:f { ((struct S*)arg0)->" + kw + "}", 0); test("struct S{ int " + kw + ";}; k:f { ((struct S)arg0)." + kw + "}", 0); } } TEST(semantic_analyser, builtin_args) { auto bpftrace = get_mock_bpftrace(); test(*bpftrace, "t:sched:sched_one { args->common_field }", 0); test(*bpftrace, "t:sched:sched_two { args->common_field }", 0); test(*bpftrace, "t:sched:sched_one," "t:sched:sched_two { args->common_field }", 0); test(*bpftrace, "t:sched:sched_* { args->common_field }", 0); test(*bpftrace, "t:sched:sched_one { args->not_a_field }", 1); } } // namespace semantic_analyser } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/testlibs/000077500000000000000000000000001361633214400163105ustar00rootroot00000000000000bpftrace-0.9.4/tests/testlibs/simple.c000066400000000000000000000000721361633214400177440ustar00rootroot00000000000000__attribute__((__visibility__("default"))) void fun() { } bpftrace-0.9.4/tests/testprogs/000077500000000000000000000000001361633214400165115ustar00rootroot00000000000000bpftrace-0.9.4/tests/testprogs/array_access.c000066400000000000000000000003621361633214400213150ustar00rootroot00000000000000typedef struct MY_STRUCT{ int x[4]; }mystruct; void test_struct(mystruct *s __attribute__((unused))) { } int main(int argc __attribute__((unused)), char ** argv __attribute__((unused))) { mystruct s; s.x[0] = 1; test_struct(&s); } bpftrace-0.9.4/tests/testprogs/bitfield_test.c000066400000000000000000000005121361633214400214740ustar00rootroot00000000000000struct Foo { unsigned int a:4, b:8, c:3, d:1, e:16; }; __attribute__((noinline)) unsigned int func(struct Foo *foo) { return foo->b; } int main() { struct Foo foo; foo.a = 1; foo.b = 2; foo.c = 5; foo.d = 0; foo.e = 65535; func(&foo); return 0; } bpftrace-0.9.4/tests/testprogs/intptrcast.c000066400000000000000000000003541361633214400210520ustar00rootroot00000000000000// a is on the stack int fn(short rdi, short rsi, short rdx, short rcx, short r8, short r9, short a) { return rdi + rsi + rdx + rcx + r8 + r9 + a; } int main() { fn(0x123, 0x456, 0x789, 0xabc, 0xdef, 0xfed, 0xcba); return 0; } bpftrace-0.9.4/tests/testprogs/mountns_wrapper.c000066400000000000000000000045771361633214400221350ustar00rootroot00000000000000#ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #define errExit(msg) \ do \ { \ perror(msg); \ exit(EXIT_FAILURE); \ } while (0) /* Run another simple test program in a different mount namespace. usage: mountns_wrapper some_testprog The test program directory is bind-mounted into a path that is private to its mount namespace. bpftrace will run from the caller's mount namespace, before the unshare. This will cause bpftrace to not be able to see the path within its own mount namespace. To access the path, bpftrace must use /proc/PID/root, to see the mount namespace from the target PID's perspective. This is useful for both uprobe and USDT tests, to ensure that bpftrace can target processes running in containers, such as docker. LIMITATIONS: doesn't pass arguments to test program, as this hasn't been necessary yet. */ int main(int argc, char *argv[]) { const char *private_mount = "/tmp/bpftrace-unshare-mountns-test"; char dpath[PATH_MAX]; char exe[PATH_MAX]; if (argc != 2) errExit("Must specify test program as only argument."); // Enter a new mount namespace if (unshare(CLONE_NEWNS) != 0) errExit("Failed to unshare"); // Recursively set the mount namespace to private, so caller can't see if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) != 0) errExit("Failed to make mount private"); // make a tempdir and bind mount containing testprog folder to it if (mkdir(private_mount, 0770) != 0 && (errno != EEXIST)) errExit("Failed to make private mount dir"); int idx = readlink("/proc/self/exe", dpath, sizeof(dpath) - 1); dpath[idx] = '\0'; char *dname = dirname(dpath); if (mount(dname, private_mount, NULL, MS_BIND, NULL) != 0) errExit("Failed to set up private bind mount"); snprintf(exe, PATH_MAX, "%s/%s", private_mount, argv[1]); char *args[] = { exe, NULL }; return execvp(args[0], args); } bpftrace-0.9.4/tests/testprogs/simple_struct.c000066400000000000000000000002221361633214400215460ustar00rootroot00000000000000struct Foo { int m; }; int func(struct Foo *foo) { return foo->m; } int main() { struct Foo foo; foo.m = 2; func(&foo); return 0; } bpftrace-0.9.4/tests/testprogs/stack_args.c000066400000000000000000000004111361633214400207720ustar00rootroot00000000000000#define ARG(x) int (x) __attribute((unused)) // Declare enough args that some are placed on the stack void too_many_args(ARG(a), ARG(b), ARG(c), ARG(d), ARG(e), ARG(f), ARG(g), ARG(h)) {} int main(void) { too_many_args(0, 1, 2, 4, 8, 16, 32, 64); return 0; } bpftrace-0.9.4/tests/testprogs/string_args.c000066400000000000000000000002161361633214400211760ustar00rootroot00000000000000void print(char * a, char * b) { (void) a; (void) b; } int main(void) { char sa[] = "hello"; char sb[] = "world"; print(sa, sb); } bpftrace-0.9.4/tests/testprogs/uprobe_negative_retval.c000066400000000000000000000003651361633214400234140ustar00rootroot00000000000000#include __attribute__ ((noinline)) int function1(int x) { return x; } int main(int argc __attribute__((unused)), char **argv __attribute__((unused))) { for (int x = -120; x <= 100; x++) { function1(x); } return -100; } bpftrace-0.9.4/tests/testprogs/uprobe_test.c000066400000000000000000000004351361633214400212120ustar00rootroot00000000000000#include int GLOBAL_A = 0x55555555; int GLOBAL_B = 0x88888888; int GLOBAL_C = 0x33333333; char GLOBAL_D = 8; int function1() { return 0; } int main(int argc __attribute__((unused)), char **argv __attribute__((unused))) { usleep(1000000); function1(); return 0; } bpftrace-0.9.4/tests/testprogs/usdt_semaphore_test.c000066400000000000000000000016621361633214400227430ustar00rootroot00000000000000#define _SDT_HAS_SEMAPHORES 1 #ifdef HAVE_SYSTEMTAP_SYS_SDT_H #include #else #define DTRACE_PROBE2(a, b, c, d) (void)0 #endif #include #include #include __extension__ unsigned short tracetest_testprobe_semaphore __attribute__ ((unused)) __attribute__ ((section (".probes"))) __attribute__ ((visibility ("hidden"))); static long myclock() { char buffer[100]; struct timeval tv; gettimeofday(&tv, NULL); sprintf(buffer, "tracetest_testprobe_semaphore: %d\n", tracetest_testprobe_semaphore); DTRACE_PROBE2(tracetest, testprobe, tv.tv_sec, buffer); return tv.tv_sec; } int main(int argc, char **argv __attribute__((unused))) { if (argc > 1) // If we don't have Systemtap headers, we should skip USDT tests. Returning 1 can be used as validation in the REQUIRE #ifndef HAVE_SYSTEMTAP_SYS_SDT_H return 1; #else return 0; #endif while (1) { myclock(); } return 0; } bpftrace-0.9.4/tests/testprogs/usdt_test.c000066400000000000000000000014211361633214400206710ustar00rootroot00000000000000#ifdef HAVE_SYSTEMTAP_SYS_SDT_H #include #else #define DTRACE_PROBE2(a, b, c, d) (void)0 #endif #include #include #include static long myclock() { struct timeval tv; gettimeofday(&tv, NULL); DTRACE_PROBE2(tracetest, testprobe, tv.tv_sec, "Hello world"); DTRACE_PROBE2(tracetest, testprobe2, tv.tv_sec, "Hello world2"); DTRACE_PROBE2(tracetest2, testprobe2, tv.tv_sec, "Hello world3"); return tv.tv_sec; } int main(int argc, char **argv __attribute__((unused))) { if (argc > 1) // If we don't have Systemtap headers, we should skip USDT tests. Returning 1 can be used as validation in the REQUIRE #ifndef HAVE_SYSTEMTAP_SYS_SDT_H return 1; #else return 0; #endif while (1) { myclock(); } return 0; } bpftrace-0.9.4/tests/testprogs/wait4_ru.c000066400000000000000000000004661361633214400204210ustar00rootroot00000000000000#include #include #include #include #include #include #include int main(void) { struct rusage rusage; pid_t pid; pid = fork(); if (pid == 0) { exit(0); } wait4(pid, NULL, 0, &rusage); return 0; } bpftrace-0.9.4/tests/testprogs/watchpoint.c000066400000000000000000000006061361633214400210370ustar00rootroot00000000000000#include #include #include #include int main() { volatile void* addr = mmap( (void*)0x10000000, 2 << 20, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if ((long)addr < 0) { perror("mmap"); return 1; } uint8_t i = 0; while (1) { *((volatile uint8_t*)addr) = i++; } } bpftrace-0.9.4/tests/tools-parsing-test.sh000077500000000000000000000007161361633214400206000ustar00rootroot00000000000000#!/bin/bash set +e; DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" BPFTRACE_EXECUTABLE=${BPFTRACE_EXECUTABLE:-$DIR/../src/bpftrace}; EXIT_STATUS=0; # TODO(mmarchini) get path from cmake for f in $(ls ../../tools/*.bt); do if $BPFTRACE_EXECUTABLE --unsafe -d $f 2>/dev/null >/dev/null; then echo "$f passed" else echo "$f failed"; $BPFTRACE_EXECUTABLE --unsafe -d $f; EXIT_STATUS=1; fi done exit $EXIT_STATUS bpftrace-0.9.4/tests/tracepoint_format_parser.cpp000066400000000000000000000106071361633214400222630ustar00rootroot00000000000000#include "gtest/gtest.h" #include "tracepoint_format_parser.h" namespace bpftrace { namespace test { namespace tracepoint_format_parser { class MockTracepointFormatParser : public TracepointFormatParser { public: static std::string get_tracepoint_struct_public(std::istream &format_file, const std::string &category, const std::string &event_name) { return get_tracepoint_struct(format_file, category, event_name); } }; TEST(tracepoint_format_parser, tracepoint_struct) { std::string input = "name: sys_enter_read\n" "ID: 650\n" "format:\n" " field:unsigned short common_type; offset:0; size:2; signed:0;\n" " field:unsigned char common_flags; offset:2; size:1; signed:0;\n" " field:unsigned char common_preempt_count; offset:3; size:1; signed:0;\n" " field:int common_pid; offset:4; size:4; signed:1;\n" "\n" " field:int __syscall_nr; offset:8; size:4; signed:1;\n" " field:unsigned int fd; offset:16; size:8; signed:0;\n" " field:char * buf; offset:24; size:8; signed:0;\n" " field:size_t count; offset:32; size:8; signed:0;\n" "\n" "print fmt: \"fd: 0x%08lx, buf: 0x%08lx, count: 0x%08lx\", ((unsigned long)(REC->fd)), ((unsigned long)(REC->buf)), ((unsigned long)(REC->count))\n"; std::string expected = "struct _tracepoint_syscalls_sys_enter_read\n" "{\n" " unsigned short common_type;\n" " unsigned char common_flags;\n" " unsigned char common_preempt_count;\n" " int common_pid;\n" " int __syscall_nr;\n" " u64 fd;\n" " char * buf;\n" " size_t count;\n" "};\n"; std::istringstream format_file(input); std::string result = MockTracepointFormatParser::get_tracepoint_struct_public(format_file, "syscalls", "sys_enter_read"); EXPECT_EQ(expected, result); } TEST(tracepoint_format_parser, array) { std::string input = " field:char char_array[8]; offset:0; size:8; signed:1;\n" " field:int int_array[2]; offset:8; size:8; signed:1;\n"; std::string expected = "struct _tracepoint_syscalls_sys_enter_read\n" "{\n" " char char_array[8];\n" " int int_array[2];\n" "};\n"; std::istringstream format_file(input); std::string result = MockTracepointFormatParser::get_tracepoint_struct_public(format_file, "syscalls", "sys_enter_read"); EXPECT_EQ(expected, result); } TEST(tracepoint_format_parser, data_loc) { std::string input = " field:__data_loc char[] msg; offset:8; size:4; signed:1;"; std::string expected = "struct _tracepoint_syscalls_sys_enter_read\n" "{\n" " int data_loc_msg;\n" "};\n"; std::istringstream format_file(input); std::string result = MockTracepointFormatParser::get_tracepoint_struct_public(format_file, "syscalls", "sys_enter_read"); EXPECT_EQ(expected, result); } TEST(tracepoint_format_parser, adjust_integer_types) { std::string input = " field:int arr[8]; offset:0; size:32; signed:1;\n" " field:int int_a; offset:0; size:4; signed:1;\n" " field:int int_b; offset:0; size:8; signed:1;\n" " field:u32 u32_a; offset:0; size:4; signed:0;\n" " field:u32 u32_b; offset:0; size:8; signed:0;\n" " field:unsigned int uint_a; offset:0; size:4; signed:0;\n" " field:unsigned int uint_b; offset:0; size:8; signed:0;\n" " field:unsigned unsigned_a; offset:0; size:4; signed:0;\n" " field:unsigned unsigned_b; offset:0; size:8; signed:0;\n" " field:uid_t uid_a; offset:0; size:4; signed:0;\n" " field:uid_t uid_b; offset:0; size:8; signed:0;\n" " field:gid_t gid_a; offset:0; size:4; signed:0;\n" " field:gid_t gid_b; offset:0; size:8; signed:0;\n" " field:pid_t pid_a; offset:0; size:4; signed:1;\n" " field:pid_t pid_b; offset:0; size:8; signed:0;\n"; std::string expected = "struct _tracepoint_syscalls_sys_enter_read\n" "{\n" " int arr[8];\n" " int int_a;\n" " s64 int_b;\n" " u32 u32_a;\n" " u64 u32_b;\n" " unsigned int uint_a;\n" " u64 uint_b;\n" " unsigned unsigned_a;\n" " u64 unsigned_b;\n" " uid_t uid_a;\n" " u64 uid_b;\n" " gid_t gid_a;\n" " u64 gid_b;\n" " pid_t pid_a;\n" " u64 pid_b;\n" "};\n"; std::istringstream format_file(input); std::string result = MockTracepointFormatParser::get_tracepoint_struct_public(format_file, "syscalls", "sys_enter_read"); EXPECT_EQ(expected, result); } } // namespace tracepoint_format_parser } // namespace test } // namespace bpftrace bpftrace-0.9.4/tests/utils.cpp000066400000000000000000000123431361633214400163260ustar00rootroot00000000000000#include "utils.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include #include #include #include namespace bpftrace { namespace test { namespace utils { TEST(utils, split_string) { std::vector tokens_empty = {}; std::vector tokens_one_empty = {""}; std::vector tokens_two_empty = {"", ""}; std::vector tokens_f = {"", "f"}; std::vector tokens_foo_bar = {"foo", "bar"}; std::vector tokens_empty_foo_bar = {"", "foo", "bar"}; std::vector tokens_empty_foo_empty_bar = {"", "foo", "", "bar"}; std::vector tokens_empty_foo_bar_biz = {"", "foo", "bar", "biz"}; EXPECT_EQ(split_string("", '-'), tokens_empty); EXPECT_EQ(split_string("-", '-'), tokens_one_empty); EXPECT_EQ(split_string("--", '-'), tokens_two_empty); EXPECT_EQ(split_string("-f-", '-'), tokens_f); EXPECT_EQ(split_string("-foo-bar-", '-'), tokens_empty_foo_bar); EXPECT_EQ(split_string("-foo--bar-", '-'), tokens_empty_foo_empty_bar); EXPECT_EQ(split_string("-foo-bar-biz-", '-'), tokens_empty_foo_bar_biz); EXPECT_EQ(split_string("-foo-bar", '-'), tokens_empty_foo_bar); EXPECT_EQ(split_string("foo-bar-", '-'), tokens_foo_bar); EXPECT_EQ(split_string("foo-bar", '-'), tokens_foo_bar); } TEST(utils, wildcard_match) { std::vector tokens_not = {"not"}; std::vector tokens_bar = {"bar"}; std::vector tokens_bar_not = {"bar", "not"}; std::vector tokens_foo = {"foo"}; std::vector tokens_biz = {"biz"}; std::vector tokens_foo_biz = {"foo", "biz"}; // start: true, end: true EXPECT_EQ(wildcard_match("foobarbiz", tokens_not, true, true), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_bar, true, true), true); EXPECT_EQ(wildcard_match("foobarbiz", tokens_bar_not, true, true), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_foo, true, true), true); EXPECT_EQ(wildcard_match("foobarbiz", tokens_biz, true, true), true); EXPECT_EQ(wildcard_match("foobarbiz", tokens_foo_biz, true, true), true); // start: false, end: true EXPECT_EQ(wildcard_match("foobarbiz", tokens_not, false, true), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_bar, false, true), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_bar_not, false, true), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_foo, false, true), true); EXPECT_EQ(wildcard_match("foobarbiz", tokens_biz, false, true), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_foo_biz, false, true), true); // start: true, end: false EXPECT_EQ(wildcard_match("foobarbiz", tokens_not, true, false), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_bar, true, false), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_bar_not, true, false), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_foo, true, false), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_biz, true, false), true); EXPECT_EQ(wildcard_match("foobarbiz", tokens_foo_biz, true, false), true); // start: false, end: false EXPECT_EQ(wildcard_match("foobarbiz", tokens_not, false, false), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_bar, false, false), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_bar_not, false, false), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_foo, false, false), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_biz, false, false), false); EXPECT_EQ(wildcard_match("foobarbiz", tokens_foo_biz, false, false), true); } static void symlink_test_binary(const std::string& destination) { if (symlink("/proc/self/exe", destination.c_str())) { throw std::runtime_error("Couldn't symlink /proc/self/exe to " + destination + ": " + strerror(errno)); } } TEST(utils, resolve_binary_path) { std::string path = "/tmp/bpftrace-test-utils-XXXXXX"; if (::mkdtemp(&path[0]) == nullptr) { throw std::runtime_error("creating temporary path for tests failed"); } // We need real elf executables, linking test binary allows us to do that // without additional dependencies. symlink_test_binary(path + "/executable"); symlink_test_binary(path + "/executable2"); int fd; fd = open((path + "/nonexecutable").c_str(), O_CREAT, S_IRUSR); close(fd); fd = open((path + "/nonexecutable2").c_str(), O_CREAT, S_IRUSR); close(fd); std::vector paths_empty = {}; std::vector paths_one_executable = {path + "/executable"}; std::vector paths_all_executables = {path + "/executable", path + "/executable2"}; EXPECT_EQ(resolve_binary_path(path + "/does/not/exist"), paths_empty); EXPECT_EQ(resolve_binary_path(path + "/does/not/exist*"), paths_empty); EXPECT_EQ(resolve_binary_path(path + "/nonexecutable"), paths_empty); EXPECT_EQ(resolve_binary_path(path + "/nonexecutable*"), paths_empty); EXPECT_EQ(resolve_binary_path(path + "/executable"), paths_one_executable); EXPECT_EQ(resolve_binary_path(path + "/executable*"), paths_all_executables); EXPECT_EQ(resolve_binary_path(path + "/*executable*"), paths_all_executables); exec_system(("rm -rf " + path).c_str()); } } // namespace utils } // namespace test } // namespace bpftrace bpftrace-0.9.4/tools/000077500000000000000000000000001361633214400144555ustar00rootroot00000000000000bpftrace-0.9.4/tools/CMakeLists.txt000066400000000000000000000003701361633214400172150ustar00rootroot00000000000000file(GLOB BT_FILES *.bt) file(GLOB TXT_FILES *.txt) list(REMOVE_ITEM TXT_FILES ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt) install(FILES ${BT_FILES} DESTINATION share/bpftrace/tools) install(FILES ${TXT_FILES} DESTINATION share/bpftrace/tools/doc) bpftrace-0.9.4/tools/bashreadline.bt000077500000000000000000000012721361633214400174320ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * bashreadline Print entered bash commands from all running shells. * For Linux, uses bpftrace and eBPF. * * This works by tracing the readline() function using a uretprobe (uprobes). * * USAGE: bashreadline.bt * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 06-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Tracing bash commands... Hit Ctrl-C to end.\n"); printf("%-9s %-6s %s\n", "TIME", "PID", "COMMAND"); } uretprobe:/bin/bash:readline { time("%H:%M:%S "); printf("%-6d %s\n", pid, str(retval)); } bpftrace-0.9.4/tools/bashreadline_example.txt000066400000000000000000000013221361633214400213500ustar00rootroot00000000000000Demonstrations of bashreadline, the Linux bpftrace/eBPF version. This prints bash commands from all running bash shells on the system. For example: # ./bashreadline.bt Attaching 2 probes... Tracing bash commands... Hit Ctrl-C to end. TIME PID COMMAND 06:40:06 5526 df -h 06:40:09 5526 ls -l 06:40:18 5526 echo hello bpftrace 06:40:42 5526 echooo this is a failed command, but we can see it anyway ^C The entered command may fail. This is just showing what command lines were entered interactively for bash to process. It works by tracing the return of the readline() function using uprobes (specifically a uretprobe). There is another version of this tool in bcc: https://github.com/iovisor/bcc bpftrace-0.9.4/tools/biolatency.bt000077500000000000000000000011331361633214400171360ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * biolatency.bt Block I/O latency as a histogram. * For Linux, uses bpftrace, eBPF. * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 13-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Tracing block device I/O... Hit Ctrl-C to end.\n"); } kprobe:blk_account_io_start { @start[arg0] = nsecs; } kprobe:blk_account_io_done /@start[arg0]/ { @usecs = hist((nsecs - @start[arg0]) / 1000); delete(@start[arg0]); } END { clear(@start); } bpftrace-0.9.4/tools/biolatency_example.txt000066400000000000000000000031561361633214400210670ustar00rootroot00000000000000Demonstrations of biolatency, the Linux BPF/bpftrace version. This traces block I/O, and shows latency as a power-of-2 histogram. For example: # ./biolatency.bt Attaching 3 probes... Tracing block device I/O... Hit Ctrl-C to end. ^C @usecs: [256, 512) 2 | | [512, 1K) 10 |@ | [1K, 2K) 426 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [2K, 4K) 230 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [4K, 8K) 9 |@ | [8K, 16K) 128 |@@@@@@@@@@@@@@@ | [16K, 32K) 68 |@@@@@@@@ | [32K, 64K) 0 | | [64K, 128K) 0 | | [128K, 256K) 10 |@ | While tracing, this shows that 426 block I/O had a latency of between 1K and 2K usecs (1024 and 2048 microseconds), which is between 1 and 2 milliseconds. There are also two modes visible, one between 1 and 2 milliseconds, and another between 8 and 16 milliseconds: this sounds like cache hits and cache misses. There were also 10 I/O with latency 128 to 256 ms: outliers. Other tools and instrumentation, like biosnoop.bt, can shed more light on those outliers. There is another version of this tool in bcc: https://github.com/iovisor/bcc The bcc version provides options to customize the output. bpftrace-0.9.4/tools/biosnoop.bt000077500000000000000000000015701361633214400166420ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * biosnoop.bt Block I/O tracing tool, showing per I/O latency. * For Linux, uses bpftrace, eBPF. * * TODO: switch to block tracepoints. Add device, offset, and size columns. * * This is a bpftrace version of the bcc tool of the same name. * * 15-Nov-2017 Brendan Gregg Created this. */ BEGIN { printf("%-12s %-16s %-6s %7s\n", "TIME(ms)", "COMM", "PID", "LAT(ms)"); } kprobe:blk_account_io_start { @start[arg0] = nsecs; @iopid[arg0] = pid; @iocomm[arg0] = comm; } kprobe:blk_account_io_done /@start[arg0] != 0 && @iopid[arg0] != 0 && @iocomm[arg0] != ""/ { $now = nsecs; printf("%-12u %-16s %-6d %7d\n", elapsed / 1000000, @iocomm[arg0], @iopid[arg0], ($now - @start[arg0]) / 1000000); delete(@start[arg0]); delete(@iopid[arg0]); delete(@iocomm[arg0]); } END { clear(@start); clear(@iopid); clear(@iocomm); } bpftrace-0.9.4/tools/biosnoop_example.txt000066400000000000000000000032221361633214400205600ustar00rootroot00000000000000Demonstrations of biosnoop, the Linux BPF/bpftrace version. This traces block I/O, and shows the issuing process (at least, the process that was on-CPU at the time of queue insert) and the latency of the I/O: # ./biosnoop.bt Attaching 4 probes... TIME(ms) COMM PID LAT(ms) 611 bash 4179 10 611 cksum 4179 0 627 cksum 4179 15 641 cksum 4179 13 644 cksum 4179 3 658 cksum 4179 13 673 cksum 4179 14 686 cksum 4179 13 701 cksum 4179 14 710 cksum 4179 8 717 cksum 4179 6 728 cksum 4179 10 735 cksum 4179 6 751 cksum 4179 10 758 cksum 4179 17 783 cksum 4179 12 796 cksum 4179 25 802 cksum 4179 32 [...] This output shows the cksum process was issuing block I/O, which were completing with around 12 milliseconds of latency. Each block I/O event is printed out, with a completion time as the first column, measured from program start. An example of some background flushing: # ./biosnoop.bt Attaching 4 probes... TIME(ms) COMM PID LAT(ms) 2966 jbd2/nvme0n1-8 615 0 2967 jbd2/nvme0n1-8 615 0 [...] There is another version of this tool in bcc: https://github.com/iovisor/bcc The bcc version provides more fields. bpftrace-0.9.4/tools/biostacks.bt000077500000000000000000000015641361633214400167770ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * biostacks - Show disk I/O latency with initialization stacks. * * See BPF Performance Tools, Chapter 9, for an explanation of this tool. * * Copyright (c) 2019 Brendan Gregg. * Licensed under the Apache License, Version 2.0 (the "License"). * This was originally created for the BPF Performance Tools book * published by Addison Wesley. ISBN-13: 9780136554820 * When copying or porting, include this comment. * * 19-Mar-2019 Brendan Gregg Created this. */ BEGIN { printf("Tracing block I/O with init stacks. Hit Ctrl-C to end.\n"); } kprobe:blk_account_io_start { @reqstack[arg0] = kstack; @reqts[arg0] = nsecs; } kprobe:blk_start_request, kprobe:blk_mq_start_request /@reqts[arg0]/ { @usecs[@reqstack[arg0]] = hist(nsecs - @reqts[arg0]); delete(@reqstack[arg0]); delete(@reqts[arg0]); } END { clear(@reqstack); clear(@reqts); } bpftrace-0.9.4/tools/biostacks_example.txt000066400000000000000000000035721361633214400207220ustar00rootroot00000000000000Demonstrations of biostacks, the Linux BCC/eBPF version. This tool shows block I/O latency as a histogram, with the kernel stack trace that initiated the I/O. This can help explain disk I/O that is not directly requested by applications (eg, metadata reads on writes, resilvering, etc). For example: # ./biostacks.bt Attaching 5 probes... Tracing block I/O with init stacks. Hit Ctrl-C to end. ^C @usecs[ blk_account_io_start+1 blk_mq_make_request+1102 generic_make_request+292 submit_bio+115 _xfs_buf_ioapply+798 xfs_buf_submit+101 xlog_bdstrat+43 xlog_sync+705 xlog_state_release_iclog+108 _xfs_log_force+542 xfs_log_force+44 xfsaild+428 kthread+289 ret_from_fork+53 ]: [64K, 128K) 1 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [...] @usecs[ blk_account_io_start+1 blk_mq_make_request+707 generic_make_request+292 submit_bio+115 xfs_add_to_ioend+455 xfs_do_writepage+758 write_cache_pages+524 xfs_vm_writepages+190 do_writepages+75 __writeback_single_inode+69 writeback_sb_inodes+481 __writeback_inodes_wb+103 wb_writeback+625 wb_workfn+384 process_one_work+478 worker_thread+50 kthread+289 ret_from_fork+53 ]: [8K, 16K) 560 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [16K, 32K) 218 |@@@@@@@@@@@@@@@@@@@@ | [32K, 64K) 26 |@@ | [64K, 128K) 2 | | [128K, 256K) 53 |@@@@ | [256K, 512K) 60 |@@@@@ | This output shows the most frequent stack was XFS writeback, with latencies between 8 and 512 microseconds. The other stack included here shows an XFS log sync. bpftrace-0.9.4/tools/bitesize.bt000077500000000000000000000010711361633214400166240ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * bitesize Show disk I/O size as a histogram. * For Linux, uses bpftrace and eBPF. * * USAGE: bitesize.bt * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 07-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Tracing block device I/O... Hit Ctrl-C to end.\n"); } tracepoint:block:block_rq_issue { @[args->comm] = hist(args->bytes); } END { printf("\nI/O size (bytes) histograms by process name:"); } bpftrace-0.9.4/tools/bitesize_example.txt000066400000000000000000000056731361633214400205620ustar00rootroot00000000000000Demonstrations of bitesize, the Linux bpftrace/eBPF version. This traces disk I/O via the block I/O interface, and prints a summary of I/O sizes as histograms for each process name. For example: # ./bitesize.bt Attaching 3 probes... Tracing block device I/O... Hit Ctrl-C to end. ^C I/O size (bytes) histograms by process name: @[cleanup]: [4K, 8K) 2 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| @[postdrop]: [4K, 8K) 2 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| @[jps]: [4K, 8K) 1 |@@@@@@@@@@@@@@@@@@@@@@@@@@ | [8K, 16K) 0 | | [16K, 32K) 0 | | [32K, 64K) 2 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| @[kworker/2:1H]: [0] 3 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [1] 0 | | [2, 4) 0 | | [4, 8) 0 | | [8, 16) 0 | | [16, 32) 0 | | [32, 64) 0 | | [64, 128) 0 | | [128, 256) 0 | | [256, 512) 0 | | [512, 1K) 0 | | [1K, 2K) 0 | | [2K, 4K) 0 | | [4K, 8K) 0 | | [8K, 16K) 0 | | [16K, 32K) 0 | | [32K, 64K) 0 | | [64K, 128K) 1 |@@@@@@@@@@@@@@@@@ | @[jbd2/nvme0n1-8]: [4K, 8K) 3 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [8K, 16K) 0 | | [16K, 32K) 0 | | [32K, 64K) 2 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [64K, 128K) 1 |@@@@@@@@@@@@@@@@@ | @[dd]: [16K, 32K) 921 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| The most active process while tracing was "dd", which issues 921 I/O between 16 Kbytes and 32 Kbytes in size. There is another version of this tool in bcc: https://github.com/iovisor/bcc bpftrace-0.9.4/tools/capable.bt000077500000000000000000000034571361633214400164070ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * capable Trace security capabilitiy checks (cap_capable()). * For Linux, uses bpftrace and eBPF. * * USAGE: capable.bt * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 08-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Tracing cap_capable syscalls... Hit Ctrl-C to end.\n"); printf("%-9s %-6s %-6s %-16s %-4s %-20s AUDIT\n", "TIME", "UID", "PID", "COMM", "CAP", "NAME"); @cap[0] = "CAP_CHOWN"; @cap[1] = "CAP_DAC_OVERRIDE"; @cap[2] = "CAP_DAC_READ_SEARCH"; @cap[3] = "CAP_FOWNER"; @cap[4] = "CAP_FSETID"; @cap[5] = "CAP_KILL"; @cap[6] = "CAP_SETGID"; @cap[7] = "CAP_SETUID"; @cap[8] = "CAP_SETPCAP"; @cap[9] = "CAP_LINUX_IMMUTABLE"; @cap[10] = "CAP_NET_BIND_SERVICE"; @cap[11] = "CAP_NET_BROADCAST"; @cap[12] = "CAP_NET_ADMIN"; @cap[13] = "CAP_NET_RAW"; @cap[14] = "CAP_IPC_LOCK"; @cap[15] = "CAP_IPC_OWNER"; @cap[16] = "CAP_SYS_MODULE"; @cap[17] = "CAP_SYS_RAWIO"; @cap[18] = "CAP_SYS_CHROOT"; @cap[19] = "CAP_SYS_PTRACE"; @cap[20] = "CAP_SYS_PACCT"; @cap[21] = "CAP_SYS_ADMIN"; @cap[22] = "CAP_SYS_BOOT"; @cap[23] = "CAP_SYS_NICE"; @cap[24] = "CAP_SYS_RESOURCE"; @cap[25] = "CAP_SYS_TIME"; @cap[26] = "CAP_SYS_TTY_CONFIG"; @cap[27] = "CAP_MKNOD"; @cap[28] = "CAP_LEASE"; @cap[29] = "CAP_AUDIT_WRITE"; @cap[30] = "CAP_AUDIT_CONTROL"; @cap[31] = "CAP_SETFCAP"; @cap[32] = "CAP_MAC_OVERRIDE"; @cap[33] = "CAP_MAC_ADMIN"; @cap[34] = "CAP_SYSLOG"; @cap[35] = "CAP_WAKE_ALARM"; @cap[36] = "CAP_BLOCK_SUSPEND"; @cap[37] = "CAP_AUDIT_READ"; } kprobe:cap_capable { $cap = arg2; $audit = arg3; time("%H:%M:%S "); printf("%-6d %-6d %-16s %-4d %-20s %d\n", uid, pid, comm, $cap, @cap[$cap], $audit); } END { clear(@cap); } bpftrace-0.9.4/tools/capable_example.txt000066400000000000000000000051431361633214400203230ustar00rootroot00000000000000Demonstrations of capable, the Linux bpftrace/eBPF version. capable traces calls to the kernel cap_capable() function, which does security capability checks, and prints details for each call. For example: # ./capable.bt TIME UID PID COMM CAP NAME AUDIT 22:11:23 114 2676 snmpd 12 CAP_NET_ADMIN 1 22:11:23 0 6990 run 24 CAP_SYS_RESOURCE 1 22:11:23 0 7003 chmod 3 CAP_FOWNER 1 22:11:23 0 7003 chmod 4 CAP_FSETID 1 22:11:23 0 7005 chmod 4 CAP_FSETID 1 22:11:23 0 7005 chmod 4 CAP_FSETID 1 22:11:23 0 7006 chown 4 CAP_FSETID 1 22:11:23 0 7006 chown 4 CAP_FSETID 1 22:11:23 0 6990 setuidgid 6 CAP_SETGID 1 22:11:23 0 6990 setuidgid 6 CAP_SETGID 1 22:11:23 0 6990 setuidgid 7 CAP_SETUID 1 22:11:24 0 7013 run 24 CAP_SYS_RESOURCE 1 22:11:24 0 7026 chmod 3 CAP_FOWNER 1 22:11:24 0 7026 chmod 4 CAP_FSETID 1 22:11:24 0 7028 chmod 4 CAP_FSETID 1 22:11:24 0 7028 chmod 4 CAP_FSETID 1 22:11:24 0 7029 chown 4 CAP_FSETID 1 22:11:24 0 7029 chown 4 CAP_FSETID 1 22:11:24 0 7013 setuidgid 6 CAP_SETGID 1 22:11:24 0 7013 setuidgid 6 CAP_SETGID 1 22:11:24 0 7013 setuidgid 7 CAP_SETUID 1 22:11:25 0 7036 run 24 CAP_SYS_RESOURCE 1 22:11:25 0 7049 chmod 3 CAP_FOWNER 1 22:11:25 0 7049 chmod 4 CAP_FSETID 1 22:11:25 0 7051 chmod 4 CAP_FSETID 1 22:11:25 0 7051 chmod 4 CAP_FSETID 1 [...] This can be useful for general debugging, and also security enforcement: determining a whitelist of capabilities an application needs. The output above includes various capability checks: snmpd checking CAP_NET_ADMIN, run checking CAP_SYS_RESOURCES, then some short-lived processes checking CAP_FOWNER, CAP_FSETID, etc. To see what each of these capabilities does, check the capabilities(7) man page and the kernel source. There is another version of this tool in bcc: https://github.com/iovisor/bcc The bcc version provides options to customize the output. bpftrace-0.9.4/tools/cpuwalk.bt000077500000000000000000000007611361633214400164610ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * cpuwalk Sample which CPUs are executing processes. * For Linux, uses bpftrace and eBPF. * * USAGE: cpuwalk.bt * * This is a bpftrace version of the DTraceToolkit tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 08-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Sampling CPU at 99hz... Hit Ctrl-C to end.\n"); } profile:hz:99 /pid/ { @cpu = lhist(cpu, 0, 1000, 1); } bpftrace-0.9.4/tools/cpuwalk_example.txt000066400000000000000000000114671361633214400204100ustar00rootroot00000000000000Demonstrations of cpuwalk, the Linux bpftrace/eBPF version. cpuwalk samples which CPUs processes are running on, and prints a summary histogram. For example, here is a Linux kernel build on a 36-CPU server: # ./cpuwalk.bt Attaching 2 probes... Sampling CPU at 99hz... Hit Ctrl-C to end. ^C @cpu: [0, 1) 130 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [1, 2) 137 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [2, 3) 99 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [3, 4) 99 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [4, 5) 82 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [5, 6) 34 |@@@@@@@@@@@@ | [6, 7) 67 |@@@@@@@@@@@@@@@@@@@@@@@@ | [7, 8) 41 |@@@@@@@@@@@@@@@ | [8, 9) 97 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [9, 10) 140 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [10, 11) 105 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [11, 12) 77 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [12, 13) 39 |@@@@@@@@@@@@@@ | [13, 14) 58 |@@@@@@@@@@@@@@@@@@@@@ | [14, 15) 64 |@@@@@@@@@@@@@@@@@@@@@@@ | [15, 16) 57 |@@@@@@@@@@@@@@@@@@@@@ | [16, 17) 99 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [17, 18) 56 |@@@@@@@@@@@@@@@@@@@@ | [18, 19) 44 |@@@@@@@@@@@@@@@@ | [19, 20) 80 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [20, 21) 64 |@@@@@@@@@@@@@@@@@@@@@@@ | [21, 22) 59 |@@@@@@@@@@@@@@@@@@@@@ | [22, 23) 88 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [23, 24) 84 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [24, 25) 29 |@@@@@@@@@@ | [25, 26) 48 |@@@@@@@@@@@@@@@@@ | [26, 27) 62 |@@@@@@@@@@@@@@@@@@@@@@@ | [27, 28) 66 |@@@@@@@@@@@@@@@@@@@@@@@@ | [28, 29) 57 |@@@@@@@@@@@@@@@@@@@@@ | [29, 30) 59 |@@@@@@@@@@@@@@@@@@@@@ | [30, 31) 56 |@@@@@@@@@@@@@@@@@@@@ | [31, 32) 23 |@@@@@@@@ | [32, 33) 90 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [33, 34) 62 |@@@@@@@@@@@@@@@@@@@@@@@ | [34, 35) 39 |@@@@@@@@@@@@@@ | [35, 36) 68 |@@@@@@@@@@@@@@@@@@@@@@@@@ | This shows that all 36 CPUs were active, with some busier than others. Compare that output to the following workload from an application: # ./cpuwalk.bt Attaching 2 probes... Sampling CPU at 99hz... Hit Ctrl-C to end. ^C @cpu: [6, 7) 243 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [7, 8) 0 | | [8, 9) 0 | | [9, 10) 0 | | [10, 11) 0 | | [11, 12) 0 | | [12, 13) 0 | | [13, 14) 0 | | [14, 15) 0 | | [15, 16) 0 | | [16, 17) 0 | | [17, 18) 0 | | [18, 19) 0 | | [19, 20) 0 | | [20, 21) 1 | | In this case, only a single CPU (6) is really active doing work. Only a single sample was taken of another CPU (20) running a process. If the workload was supposed to be making use of multiple CPUs, it isn't, and that can be investigated (application's configuration, number of threads, CPU binding, etc). bpftrace-0.9.4/tools/dcsnoop.bt000077500000000000000000000022641361633214400164600ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * dcsnoop Trace directory entry cache (dcache) lookups. * For Linux, uses bpftrace and eBPF. * * This uses kernel dynamic tracing of kernel functions, lookup_fast() and * d_lookup(), which will need to be modified to match kernel changes. See * code comments. * * USAGE: dcsnoop.bt * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 08-Sep-2018 Brendan Gregg Created this. */ #include #include // from fs/namei.c: struct nameidata { struct path path; struct qstr last; // [...] }; BEGIN { printf("Tracing dcache lookups... Hit Ctrl-C to end.\n"); printf("%-8s %-6s %-16s %1s %s\n", "TIME", "PID", "COMM", "T", "FILE"); } // comment out this block to avoid showing hits: kprobe:lookup_fast { $nd = (struct nameidata *)arg0; printf("%-8d %-6d %-16s R %s\n", elapsed / 1000000, pid, comm, str($nd->last.name)); } kprobe:d_lookup { $name = (struct qstr *)arg1; @fname[tid] = $name->name; } kretprobe:d_lookup /@fname[tid]/ { printf("%-8d %-6d %-16s M %s\n", elapsed / 1000000, pid, comm, str(@fname[tid])); delete(@fname[tid]); } bpftrace-0.9.4/tools/dcsnoop_example.txt000066400000000000000000000110041361633214400203720ustar00rootroot00000000000000Demonstrations of dcsnoop, the Linux bpftrace/eBPF version. dcsnoop traces directory entry cache (dcache) lookups, and can be used for further investigation beyond dcstat(8). The output is likely verbose, as dcache lookups are likely frequent. For example: # ./dcsnoop.bt Attaching 4 probes... Tracing dcache lookups... Hit Ctrl-C to end. TIME PID COMM T FILE 427 1518 irqbalance R proc/interrupts 427 1518 irqbalance R interrupts 427 1518 irqbalance R proc/stat 427 1518 irqbalance R stat 483 2440 snmp-pass R proc/cpuinfo 483 2440 snmp-pass R cpuinfo 486 2440 snmp-pass R proc/stat 486 2440 snmp-pass R stat 834 1744 snmpd R proc/net/dev 834 1744 snmpd R net/dev 834 1744 snmpd R self/net 834 1744 snmpd R 1744 834 1744 snmpd R net 834 1744 snmpd R dev 834 1744 snmpd R proc/net/if_inet6 834 1744 snmpd R net/if_inet6 834 1744 snmpd R self/net 834 1744 snmpd R 1744 834 1744 snmpd R net 834 1744 snmpd R if_inet6 835 1744 snmpd R sys/class/net/docker0/device/vendor 835 1744 snmpd R class/net/docker0/device/vendor 835 1744 snmpd R net/docker0/device/vendor 835 1744 snmpd R docker0/device/vendor 835 1744 snmpd R devices/virtual/net/docker0 835 1744 snmpd R virtual/net/docker0 835 1744 snmpd R net/docker0 835 1744 snmpd R docker0 835 1744 snmpd R device/vendor 835 1744 snmpd R proc/sys/net/ipv4/neigh/docker0/retrans_time_ms 835 1744 snmpd R sys/net/ipv4/neigh/docker0/retrans_time_ms 835 1744 snmpd R net/ipv4/neigh/docker0/retrans_time_ms 835 1744 snmpd R ipv4/neigh/docker0/retrans_time_ms 835 1744 snmpd R neigh/docker0/retrans_time_ms 835 1744 snmpd R docker0/retrans_time_ms 835 1744 snmpd R retrans_time_ms 835 1744 snmpd R proc/sys/net/ipv6/neigh/docker0/retrans_time_ms 835 1744 snmpd R sys/net/ipv6/neigh/docker0/retrans_time_ms 835 1744 snmpd R net/ipv6/neigh/docker0/retrans_time_ms 835 1744 snmpd R ipv6/neigh/docker0/retrans_time_ms 835 1744 snmpd R neigh/docker0/retrans_time_ms 835 1744 snmpd R docker0/retrans_time_ms 835 1744 snmpd R retrans_time_ms 835 1744 snmpd R proc/sys/net/ipv6/conf/docker0/forwarding 835 1744 snmpd R sys/net/ipv6/conf/docker0/forwarding 835 1744 snmpd R net/ipv6/conf/docker0/forwarding 835 1744 snmpd R ipv6/conf/docker0/forwarding 835 1744 snmpd R conf/docker0/forwarding [...] 5154 934 cksum R usr/bin/basename 5154 934 cksum R bin/basename 5154 934 cksum R basename 5154 934 cksum R usr/bin/bashbug 5154 934 cksum R bin/bashbug 5154 934 cksum R bashbug 5154 934 cksum M bashbug 5155 934 cksum R usr/bin/batch 5155 934 cksum R bin/batch 5155 934 cksum R batch 5155 934 cksum M batch 5155 934 cksum R usr/bin/bc 5155 934 cksum R bin/bc 5155 934 cksum R bc 5155 934 cksum M bc 5169 934 cksum R usr/bin/bdftopcf 5169 934 cksum R bin/bdftopcf 5169 934 cksum R bdftopcf 5169 934 cksum M bdftopcf 5173 934 cksum R usr/bin/bdftruncate 5173 934 cksum R bin/bdftruncate 5173 934 cksum R bdftruncate 5173 934 cksum M bdftruncate The way the dcache is currently implemented, each component of a path is checked in turn. The first line, showing "proc/interrupts" from irqbalance, will be a lookup for "proc" in a directory (that isn't shown here). If it finds "proc", it will then lookup "interrupts" inside net. The script is easily modifiable to only show misses, reducing the volume of the output. Or use the bcc version of this tool, which only shows misses by default: https://github.com/iovisor/bcc bpftrace-0.9.4/tools/execsnoop.bt000077500000000000000000000014141361633214400170120ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * execsnoop.bt Trace new processes via exec() syscalls. * For Linux, uses bpftrace and eBPF. * * This traces when processes call exec(). It is handy for identifying new * processes created via the usual fork()->exec() sequence. Note that the * return value is not currently traced, so the exec() may have failed. * * TODO: switch to tracepoints args. Support more args. Include retval. * * This is a bpftrace version of the bcc tool of the same name. * * 15-Nov-2017 Brendan Gregg Created this. * 11-Sep-2018 " " Switched to use join(). */ BEGIN { printf("%-10s %-5s %s\n", "TIME(ms)", "PID", "ARGS"); } tracepoint:syscalls:sys_enter_execve { printf("%-10u %-5d ", elapsed / 1000000, pid); join(args->argv); } bpftrace-0.9.4/tools/execsnoop_example.txt000066400000000000000000000032371361633214400207410ustar00rootroot00000000000000Demonstrations of execsnoop, the Linux BPF/bpftrace version. Tracing all new process execution (via exec()): # ./execsnoop.bt Attaching 3 probes... TIME(ms) PID ARGS 2460 3466 ls --color=auto -lh execsnoop.bt execsnoop.bt.0 execsnoop.bt.1 3996 3467 man ls 4005 3473 preconv -e UTF-8 4005 3473 preconv -e UTF-8 4005 3473 preconv -e UTF-8 4005 3473 preconv -e UTF-8 4005 3473 preconv -e UTF-8 4005 3474 tbl 4005 3474 tbl 4005 3474 tbl 4005 3474 tbl 4005 3474 tbl 4005 3476 nroff -mandoc -rLL=193n -rLT=193n -Tutf8 4005 3476 nroff -mandoc -rLL=193n -rLT=193n -Tutf8 4005 3476 nroff -mandoc -rLL=193n -rLT=193n -Tutf8 4005 3476 nroff -mandoc -rLL=193n -rLT=193n -Tutf8 4005 3476 nroff -mandoc -rLL=193n -rLT=193n -Tutf8 4006 3479 pager -rLL=193n 4006 3479 pager -rLL=193n 4006 3479 pager -rLL=193n 4006 3479 pager -rLL=193n 4006 3479 pager -rLL=193n 4007 3481 locale charmap 4008 3482 groff -mtty-char -Tutf8 -mandoc -rLL=193n -rLT=193n 4009 3483 troff -mtty-char -mandoc -rLL=193n -rLT=193n -Tutf8 The output begins by showing an "ls" command, and then the process execution to serve "man ls". The same exec arguments appear multiple times: in this case they are failing as the $PATH variable is walked, until one finally succeeds. This tool can be used to discover unwanted short-lived processes that may be causing performance issues such as latency perturbations. There is another version of this tool in bcc: https://github.com/iovisor/bcc The bcc version provides more fields and command line options. bpftrace-0.9.4/tools/gethostlatency.bt000077500000000000000000000025541361633214400200520ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * gethostlatency Trace getaddrinfo/gethostbyname[2] calls. * For Linux, uses bpftrace and eBPF. * * This can be useful for identifying DNS latency, by identifying which * remote host name lookups were slow, and by how much. * * This uses dynamic tracing of user-level functions and registers, and may # need modifications to match your software and processor architecture. * * USAGE: gethostlatency.bt * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 08-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Tracing getaddr/gethost calls... Hit Ctrl-C to end.\n"); printf("%-9s %-6s %-16s %6s %s\n", "TIME", "PID", "COMM", "LATms", "HOST"); } uprobe:/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo, uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethostbyname, uprobe:/lib/x86_64-linux-gnu/libc.so.6:gethostbyname2 { @start[tid] = nsecs; @name[tid] = arg0; } uretprobe:/lib/x86_64-linux-gnu/libc.so.6:getaddrinfo, uretprobe:/lib/x86_64-linux-gnu/libc.so.6:gethostbyname, uretprobe:/lib/x86_64-linux-gnu/libc.so.6:gethostbyname2 /@start[tid]/ { $latms = (nsecs - @start[tid]) / 1000000; time("%H:%M:%S "); printf("%-6d %-16s %6d %s\n", pid, comm, $latms, str(@name[tid])); delete(@start[tid]); delete(@name[tid]); } bpftrace-0.9.4/tools/gethostlatency_example.txt000066400000000000000000000016331361633214400217710ustar00rootroot00000000000000Demonstrations of gethostlatency, the Linux bpftrace/eBPF version. This traces host name lookup calls (getaddrinfo(), gethostbyname(), and gethostbyname2()), and shows the PID and command performing the lookup, the latency (duration) of the call in milliseconds, and the host string: # ./gethostlatency.bt Attaching 7 probes... Tracing getaddr/gethost calls... Hit Ctrl-C to end. TIME PID COMM LATms HOST 02:52:05 19105 curl 81 www.netflix.com 02:52:12 19111 curl 17 www.netflix.com 02:52:19 19116 curl 9 www.facebook.com 02:52:23 19118 curl 3 www.facebook.com In this example, the first call to lookup "www.netflix.com" took 81 ms, and the second took 17 ms (sounds like some caching). There is another version of this tool in bcc: https://github.com/iovisor/bcc The bcc version provides options to customize the output. bpftrace-0.9.4/tools/killsnoop.bt000077500000000000000000000015431361633214400170240ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * killsnoop Trace signals issued by the kill() syscall. * For Linux, uses bpftrace and eBPF. * * USAGE: killsnoop.bt * * Also a basic example of bpftrace. * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 07-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Tracing kill() signals... Hit Ctrl-C to end.\n"); printf("%-9s %-6s %-16s %-4s %-6s %s\n", "TIME", "PID", "COMM", "SIG", "TPID", "RESULT"); } tracepoint:syscalls:sys_enter_kill { @tpid[tid] = args->pid; @tsig[tid] = args->sig; } tracepoint:syscalls:sys_exit_kill /@tpid[tid]/ { time("%H:%M:%S "); printf("%-6d %-16s %-4d %-6d %d\n", pid, comm, @tsig[tid], @tpid[tid], args->ret); delete(@tpid[tid]); delete(@tsig[tid]); } bpftrace-0.9.4/tools/killsnoop_example.txt000066400000000000000000000014451361633214400207470ustar00rootroot00000000000000Demonstrations of killsnoop, the Linux bpftrace/eBPF version. This traces signals sent via the kill() syscall. For example: # ./killsnoop.bt Attaching 3 probes... Tracing kill() signals... Hit Ctrl-C to end. TIME PID COMM SIG TPID RESULT 00:09:37 22485 bash 2 23856 0 00:09:40 22485 bash 2 23856 -3 00:09:31 22485 bash 15 23814 -3 The first line showed a SIGINT (2) sent from PID 22485 (a bash shell) to PID 23856. The result, 0, means success. The next line shows the same signal sent, which resulted in -3, a failure (likely because the target process no longer existed). There is another version of this tool in bcc: https://github.com/iovisor/bcc The bcc version provides command line options to customize the output. bpftrace-0.9.4/tools/loads.bt000077500000000000000000000021471361633214400161150ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * loads Prints load averages. * For Linux, uses bpftrace and eBPF. * * These are the same load averages printed by "uptime", but to three decimal * places instead of two (not that it really matters). This is really a * demonstration of fetching and processing a kernel structure from bpftrace. * * USAGE: loads.bt * * This is a bpftrace version of a DTraceToolkit tool. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 10-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Reading load averages... Hit Ctrl-C to end.\n"); } interval:s:1 { /* * See fs/proc/loadavg.c and include/linux/sched/loadavg.h for the * following calculations. */ $avenrun = kaddr("avenrun"); $load1 = *$avenrun; $load5 = *($avenrun + 8); $load15 = *($avenrun + 16); time("%H:%M:%S "); printf("load averages: %d.%03d %d.%03d %d.%03d\n", ($load1 >> 11), (($load1 & ((1 << 11) - 1)) * 1000) >> 11, ($load5 >> 11), (($load5 & ((1 << 11) - 1)) * 1000) >> 11, ($load15 >> 11), (($load15 & ((1 << 11) - 1)) * 1000) >> 11 ); } bpftrace-0.9.4/tools/loads_example.txt000066400000000000000000000015401361633214400200330ustar00rootroot00000000000000Demonstrations of loads, the Linux bpftrace/eBPF version. This is a simple tool that prints the system load averages, to three decimal places each (not that it really matters), as a demonstration of fetching kernel structures from bpftrace: # ./loads.bt Attaching 2 probes... Reading load averages... Hit Ctrl-C to end. 21:29:17 load averages: 2.091 2.048 1.947 21:29:18 load averages: 2.091 2.048 1.947 21:29:19 load averages: 2.091 2.048 1.947 21:29:20 load averages: 2.091 2.048 1.947 21:29:21 load averages: 2.164 2.064 1.953 21:29:22 load averages: 2.164 2.064 1.953 21:29:23 load averages: 2.164 2.064 1.953 ^C These are the same load averages printed by uptime: # uptime 21:29:24 up 2 days, 18:57, 3 users, load average: 2.16, 2.06, 1.95 For more on load averages, see my post: http://www.brendangregg.com/blog/2017-08-08/linux-load-averages.html bpftrace-0.9.4/tools/mdflush.bt000077500000000000000000000012251361633214400164510ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * mdflush Trace md flush events. * For Linux, uses bpftrace and eBPF. * * USAGE: mdflush.bt * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 08-Sep-2018 Brendan Gregg Created this. */ #include #include BEGIN { printf("Tracing md flush events... Hit Ctrl-C to end.\n"); printf("%-8s %-6s %-16s %s", "TIME", "PID", "COMM", "DEVICE"); } kprobe:md_flush_request { time("%H:%M:%S "); printf("%-6d %-16s %s\n", pid, comm, ((struct bio *)arg1)->bi_disk->disk_name); } bpftrace-0.9.4/tools/mdflush_example.txt000066400000000000000000000035121361633214400203740ustar00rootroot00000000000000Demonstrations of mdflush, the Linux bpftrace/eBPF version. The mdflush tool traces flushes at the md driver level, and prints details including the time of the flush: # ./mdflush.bt Tracing md flush requests... Hit Ctrl-C to end. TIME PID COMM DEVICE 03:13:49 16770 sync md0 03:14:08 16864 sync md0 03:14:49 496 kworker/1:0H md0 03:14:49 488 xfsaild/md0 md0 03:14:54 488 xfsaild/md0 md0 03:15:00 488 xfsaild/md0 md0 03:15:02 85 kswapd0 md0 03:15:02 488 xfsaild/md0 md0 03:15:05 488 xfsaild/md0 md0 03:15:08 488 xfsaild/md0 md0 03:15:10 488 xfsaild/md0 md0 03:15:11 488 xfsaild/md0 md0 03:15:11 488 xfsaild/md0 md0 03:15:11 488 xfsaild/md0 md0 03:15:11 488 xfsaild/md0 md0 03:15:11 488 xfsaild/md0 md0 03:15:12 488 xfsaild/md0 md0 03:15:13 488 xfsaild/md0 md0 03:15:15 488 xfsaild/md0 md0 03:15:19 496 kworker/1:0H md0 03:15:49 496 kworker/1:0H md0 03:15:55 18840 sync md0 03:16:49 496 kworker/1:0H md0 03:17:19 496 kworker/1:0H md0 03:20:19 496 kworker/1:0H md0 03:21:19 496 kworker/1:0H md0 03:21:49 496 kworker/1:0H md0 03:25:19 496 kworker/1:0H md0 [...] This can be useful for correlation with latency outliers or spikes in disk latency, as measured using another tool (eg, system monitoring). If spikes in disk latency often coincide with md flush events, then it would make flushing a target for tuning. Note that the flush events are likely to originate from higher in the I/O stack, such as from file systems. This traces md processing them, and the timestamp corresponds with when md began to issue the flush to disks. There is another version of this tool in bcc: https://github.com/iovisor/bcc bpftrace-0.9.4/tools/naptime.bt000077500000000000000000000017521361633214400164510ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * naptime - Show voluntary sleep calls. * * See BPF Performance Tools, Chapter 13, for an explanation of this tool. * * Copyright (c) 2019 Brendan Gregg. * Licensed under the Apache License, Version 2.0 (the "License"). * This was originally created for the BPF Performance Tools book * published by Addison Wesley. ISBN-13: 9780136554820 * When copying or porting, include this comment. * * 16-Feb-2019 Brendan Gregg Created this. */ #include #include BEGIN { printf("Tracing sleeps. Hit Ctrl-C to end.\n"); printf("%-8s %-6s %-16s %-6s %-16s %s\n", "TIME", "PPID", "PCOMM", "PID", "COMM", "SECONDS"); } tracepoint:syscalls:sys_enter_nanosleep /args->rqtp->tv_sec + args->rqtp->tv_nsec/ { $task = (struct task_struct *)curtask; time("%H:%M:%S "); printf("%-6d %-16s %-6d %-16s %d.%03d\n", $task->real_parent->pid, $task->real_parent->comm, pid, comm, args->rqtp->tv_sec, args->rqtp->tv_nsec / 1000000); } bpftrace-0.9.4/tools/naptime_example.txt000066400000000000000000000015141361633214400203670ustar00rootroot00000000000000Demonstrations of naptime, the Linux bpftrace/eBPF version. Tracing application sleeps via the nanosleep(2) syscall: # ./naptime.bt Attaching 2 probes... Tracing sleeps. Hit Ctrl-C to end. TIME PCOMM PPID COMM PID SECONDS 15:50:00 1 systemd 1319 mysqld 1.000 15:50:01 4388 bash 25250 sleep 5.000 15:50:01 1 systemd 1319 mysqld 1.000 15:50:01 1 systemd 1180 cron 60.000 15:50:01 1 systemd 1180 cron 60.000 15:50:02 1 systemd 1319 mysqld 1.000 [...] The output shows mysqld performing a one second sleep every second (likely a daemon thread), a sleep(1) command sleeping for five seconds and called by bash, and cron threads sleeping for 60 seconds. bpftrace-0.9.4/tools/oomkill.bt000077500000000000000000000022151361633214400164550ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * oomkill Trace OOM killer. * For Linux, uses bpftrace and eBPF. * * This traces the kernel out-of-memory killer, and prints basic details, * including the system load averages. This can provide more context on the * system state at the time of OOM: was it getting busier or steady, based * on the load averages? This tool may also be useful to customize for * investigations; for example, by adding other task_struct details at the * time of the OOM, or other commands in the system() call. * * This currently works by using kernel dynamic tracing of oom_kill_process(). * * USAGE: oomkill.bt * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 07-Sep-2018 Brendan Gregg Created this. */ #include BEGIN { printf("Tracing oom_kill_process()... Hit Ctrl-C to end.\n"); } kprobe:oom_kill_process { $oc = (struct oom_control *)arg1; time("%H:%M:%S "); printf("Triggered by PID %d (\"%s\"), ", pid, comm); printf("OOM kill of PID %d (\"%s\"), %d pages, loadavg: ", $oc->chosen->pid, $oc->chosen->comm, $oc->totalpages); cat("/proc/loadavg"); } bpftrace-0.9.4/tools/oomkill_example.txt000066400000000000000000000032041361633214400203760ustar00rootroot00000000000000Demonstrations of oomkill, the Linux bpftrace/eBPF version. oomkill is a simple program that traces the Linux out-of-memory (OOM) killer, and shows basic details on one line per OOM kill: # ./oomkill.bt Tracing oom_kill_process()... Ctrl-C to end. 21:03:39 Triggered by PID 3297 ("ntpd"), OOM kill of PID 22516 ("perl"), 3850642 pages, loadavg: 0.99 0.39 0.30 3/282 22724 21:03:48 Triggered by PID 22517 ("perl"), OOM kill of PID 22517 ("perl"), 3850642 pages, loadavg: 0.99 0.41 0.30 2/282 22932 The first line shows that PID 22516, with process name "perl", was OOM killed when it reached 3850642 pages (usually 4 Kbytes per page). This OOM kill happened to be triggered by PID 3297, process name "ntpd", doing some memory allocation. The system log (dmesg) shows pages of details and system context about an OOM kill. What it currently lacks, however, is context on how the system had been changing over time. I've seen OOM kills where I wanted to know if the system was at steady state at the time, or if there had been a recent increase in workload that triggered the OOM event. oomkill provides some context: at the end of the line is the load average information from /proc/loadavg. For both of the oomkills here, we can see that the system was getting busier at the time (a higher 1 minute "average" of 0.99, compared to the 15 minute "average" of 0.30). oomkill can also be the basis of other tools and customizations. For example, you can edit it to include other task_struct details from the target PID at the time of the OOM kill, or to run other commands from the shell. There is another version of this tool in bcc: https://github.com/iovisor/bcc bpftrace-0.9.4/tools/opensnoop.bt000077500000000000000000000016711361633214400170340ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * opensnoop Trace open() syscalls. * For Linux, uses bpftrace and eBPF. * * Also a basic example of bpftrace. * * USAGE: opensnoop.bt * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 08-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Tracing open syscalls... Hit Ctrl-C to end.\n"); printf("%-6s %-16s %4s %3s %s\n", "PID", "COMM", "FD", "ERR", "PATH"); } tracepoint:syscalls:sys_enter_open, tracepoint:syscalls:sys_enter_openat { @filename[tid] = args->filename; } tracepoint:syscalls:sys_exit_open, tracepoint:syscalls:sys_exit_openat /@filename[tid]/ { $ret = args->ret; $fd = $ret > 0 ? $ret : -1; $errno = $ret > 0 ? 0 : - $ret; printf("%-6d %-16s %4d %3d %s\n", pid, comm, $fd, $errno, str(@filename[tid])); delete(@filename[tid]); } END { clear(@filename); } bpftrace-0.9.4/tools/opensnoop_example.txt000066400000000000000000000047401361633214400207560ustar00rootroot00000000000000Demonstrations of opensnoop, the Linux bpftrace/eBPF version. opensnoop traces the open() syscall system-wide, and prints various details. Example output: # ./opensnoop.bt Attaching 3 probes... Tracing open syscalls... Hit Ctrl-C to end. PID COMM FD ERR PATH 2440 snmp-pass 4 0 /proc/cpuinfo 2440 snmp-pass 4 0 /proc/stat 25706 ls 3 0 /etc/ld.so.cache 25706 ls 3 0 /lib/x86_64-linux-gnu/libselinux.so.1 25706 ls 3 0 /lib/x86_64-linux-gnu/libc.so.6 25706 ls 3 0 /lib/x86_64-linux-gnu/libpcre.so.3 25706 ls 3 0 /lib/x86_64-linux-gnu/libdl.so.2 25706 ls 3 0 /lib/x86_64-linux-gnu/libpthread.so.0 25706 ls 3 0 /proc/filesystems 25706 ls 3 0 /usr/lib/locale/locale-archive 25706 ls 3 0 . 1744 snmpd 8 0 /proc/net/dev 1744 snmpd 21 0 /proc/net/if_inet6 1744 snmpd 21 0 /sys/class/net/eth0/device/vendor 1744 snmpd 21 0 /sys/class/net/eth0/device/device 1744 snmpd 21 0 /proc/sys/net/ipv4/neigh/eth0/retrans_time_ms 1744 snmpd 21 0 /proc/sys/net/ipv6/neigh/eth0/retrans_time_ms 1744 snmpd 21 0 /proc/sys/net/ipv6/conf/eth0/forwarding 1744 snmpd 21 0 /proc/sys/net/ipv6/neigh/eth0/base_reachable_time_ms 1744 snmpd -1 2 /sys/class/net/lo/device/vendor 1744 snmpd 21 0 /proc/sys/net/ipv4/neigh/lo/retrans_time_ms 1744 snmpd 21 0 /proc/sys/net/ipv6/neigh/lo/retrans_time_ms 1744 snmpd 21 0 /proc/sys/net/ipv6/conf/lo/forwarding 1744 snmpd 21 0 /proc/sys/net/ipv6/neigh/lo/base_reachable_time_ms 2440 snmp-pass 4 0 /proc/cpuinfo 2440 snmp-pass 4 0 /proc/stat 22884 pickup 12 0 maildrop 2440 snmp-pass 4 0 /proc/cpuinfo 2440 snmp-pass 4 0 /proc/stat While tracing, at "ls" command was launched: the libraries it uses can be seen as they were opened. Also, the snmpd process opened various /proc and /sys files (reading metrics). was starting up: a new process). opensnoop can be useful for discovering configuration and log files, if used during application startup. There is another version of this tool in bcc: https://github.com/iovisor/bcc The bcc version provides command line options to customize the output. bpftrace-0.9.4/tools/pidpersec.bt000077500000000000000000000011641361633214400167670ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * pidpersec Count new procesess (via fork). * For Linux, uses bpftrace and eBPF. * * Written as a basic example of counting on an event. * * USAGE: pidpersec.bt * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 06-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Tracing new processes... Hit Ctrl-C to end.\n"); } tracepoint:sched:sched_process_fork { @ = count(); } interval:s:1 { time("%H:%M:%S PIDs/sec: "); print(@); clear(@); } END { clear(@); } bpftrace-0.9.4/tools/pidpersec_example.txt000066400000000000000000000027401361633214400207120ustar00rootroot00000000000000Demonstrations of pidpersec, the Linux bpftrace/eBPF version. Tracing new procesess: # ./pidpersec.bt Attaching 4 probes... Tracing new processes... Hit Ctrl-C to end. 22:29:50 PIDs/sec: @: 121 22:29:51 PIDs/sec: @: 120 22:29:52 PIDs/sec: @: 122 22:29:53 PIDs/sec: @: 124 22:29:54 PIDs/sec: @: 123 22:29:55 PIDs/sec: @: 121 22:29:56 PIDs/sec: @: 121 22:29:57 PIDs/sec: @: 121 22:29:58 PIDs/sec: @: 49 22:29:59 PIDs/sec: 22:30:00 PIDs/sec: 22:30:01 PIDs/sec: 22:30:02 PIDs/sec: ^C The output begins by showing a rate of new procesess over 120 per second. That then ends at time 22:29:59, and for the next few seconds there are zero new processes per second. The following example shows a Linux build launched at 6:33:40, on a 36 CPU server, with make -j36: # ./pidpersec.bt Attaching 4 probes... Tracing new processes... Hit Ctrl-C to end. 06:33:38 PIDs/sec: 06:33:39 PIDs/sec: 06:33:40 PIDs/sec: @: 2314 06:33:41 PIDs/sec: @: 2517 06:33:42 PIDs/sec: @: 1345 06:33:43 PIDs/sec: @: 1752 06:33:44 PIDs/sec: @: 1744 06:33:45 PIDs/sec: @: 1549 06:33:46 PIDs/sec: @: 1643 06:33:47 PIDs/sec: @: 1487 06:33:48 PIDs/sec: @: 1534 06:33:49 PIDs/sec: @: 1279 06:33:50 PIDs/sec: @: 1392 06:33:51 PIDs/sec: @: 1556 06:33:52 PIDs/sec: @: 1580 06:33:53 PIDs/sec: @: 1944 A Linux kernel build involves launched many thousands of short-lived processes, which can be seen in the above output: a rate of over 1,000 processes per second. There is another version of this tool in bcc: https://github.com/iovisor/bcc bpftrace-0.9.4/tools/runqlat.bt000077500000000000000000000014311361633214400164740ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * runqlat.bt CPU scheduler run queue latency as a histogram. * For Linux, uses bpftrace, eBPF. * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 17-Sep-2018 Brendan Gregg Created this. */ #include BEGIN { printf("Tracing CPU scheduler... Hit Ctrl-C to end.\n"); } tracepoint:sched:sched_wakeup, tracepoint:sched:sched_wakeup_new { @qtime[args->pid] = nsecs; } tracepoint:sched:sched_switch { if (args->prev_state == TASK_RUNNING) { @qtime[args->prev_pid] = nsecs; } $ns = @qtime[args->next_pid]; if ($ns) { @usecs = hist((nsecs - $ns) / 1000); } delete(@qtime[args->next_pid]); } END { clear(@qtime); } bpftrace-0.9.4/tools/runqlat_example.txt000066400000000000000000000206671361633214400204320ustar00rootroot00000000000000Demonstrations of runqlat, the Linux BPF/bpftrace version. This traces time spent waiting in the CPU scheduler for a turn on-CPU. This metric is often called run queue latency, or scheduler latency. This tool shows this latency as a power-of-2 histogram in nanoseconds. For example: # ./runqlat.bt Attaching 5 probes... Tracing CPU scheduler... Hit Ctrl-C to end. ^C @usecs: [0] 1 | | [1] 11 |@@ | [2, 4) 16 |@@@ | [4, 8) 43 |@@@@@@@@@@ | [8, 16) 134 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [16, 32) 220 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [32, 64) 117 |@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [64, 128) 84 |@@@@@@@@@@@@@@@@@@@ | [128, 256) 10 |@@ | [256, 512) 2 | | [512, 1K) 5 |@ | [1K, 2K) 5 |@ | [2K, 4K) 5 |@ | [4K, 8K) 4 | | [8K, 16K) 1 | | [16K, 32K) 2 | | [32K, 64K) 0 | | [64K, 128K) 1 | | [128K, 256K) 0 | | [256K, 512K) 0 | | [512K, 1M) 1 | | This is an idle system where most of the time we are waiting for less than 128 microseconds, shown by the mode above. As an example of reading the output, the above histogram shows 220 scheduling events with a run queue latency of between 16 and 32 microseconds. The output also shows an outlier taking between 0.5 and 1 seconds: ??? XXX likely work was scheduled behind another higher priority task, and had to wait briefly. The kernel decides whether it is worth migrating such work to an idle CPU, or leaving it wait its turn on its current CPU run queue where the CPU caches should be hotter. I'll now add a single-threaded CPU bound workload to this system, and bind it on one CPU: # ./runqlat.bt Attaching 5 probes... Tracing CPU scheduler... Hit Ctrl-C to end. ^C @usecs: [1] 6 |@@@ | [2, 4) 26 |@@@@@@@@@@@@@ | [4, 8) 97 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [8, 16) 72 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [16, 32) 17 |@@@@@@@@@ | [32, 64) 19 |@@@@@@@@@@ | [64, 128) 20 |@@@@@@@@@@ | [128, 256) 3 |@ | [256, 512) 0 | | [512, 1K) 0 | | [1K, 2K) 1 | | [2K, 4K) 1 | | [4K, 8K) 4 |@@ | [8K, 16K) 3 |@ | [16K, 32K) 0 | | [32K, 64K) 0 | | [64K, 128K) 0 | | [128K, 256K) 1 | | [256K, 512K) 0 | | [512K, 1M) 0 | | [1M, 2M) 1 | | That didn't make much difference. Now I'll add a second single-threaded CPU workload, and bind it to the same CPU, causing contention: # ./runqlat.bt Attaching 5 probes... Tracing CPU scheduler... Hit Ctrl-C to end. ^C @usecs: [0] 1 | | [1] 8 |@@@ | [2, 4) 28 |@@@@@@@@@@@@ | [4, 8) 95 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [8, 16) 120 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [16, 32) 22 |@@@@@@@@@ | [32, 64) 10 |@@@@ | [64, 128) 7 |@@@ | [128, 256) 3 |@ | [256, 512) 1 | | [512, 1K) 0 | | [1K, 2K) 0 | | [2K, 4K) 2 | | [4K, 8K) 4 |@ | [8K, 16K) 107 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [16K, 32K) 0 | | [32K, 64K) 0 | | [64K, 128K) 0 | | [128K, 256K) 0 | | [256K, 512K) 1 | | There's now a second mode between 8 and 16 milliseconds, as each thread must wait its turn on the one CPU. Now I'l run 10 CPU-bound throuds on one CPU: # ./runqlat.bt Attaching 5 probes... Tracing CPU scheduler... Hit Ctrl-C to end. ^C @usecs: [0] 2 | | [1] 10 |@ | [2, 4) 38 |@@@@ | [4, 8) 63 |@@@@@@ | [8, 16) 106 |@@@@@@@@@@@ | [16, 32) 28 |@@@ | [32, 64) 13 |@ | [64, 128) 15 |@ | [128, 256) 2 | | [256, 512) 2 | | [512, 1K) 1 | | [1K, 2K) 1 | | [2K, 4K) 2 | | [4K, 8K) 4 | | [8K, 16K) 3 | | [16K, 32K) 0 | | [32K, 64K) 478 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [64K, 128K) 1 | | [128K, 256K) 0 | | [256K, 512K) 0 | | [512K, 1M) 0 | | [1M, 2M) 1 | | This shows that most of the time threads need to wait their turn, with the largest mode between 32 and 64 milliseconds. There is another version of this tool in bcc: https://github.com/iovisor/bcc The bcc version provides options to customize the output. bpftrace-0.9.4/tools/runqlen.bt000077500000000000000000000020341361633214400164720ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * runqlen.bt CPU scheduler run queue length as a histogram. * For Linux, uses bpftrace, eBPF. * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 07-Oct-2018 Brendan Gregg Created this. */ #include // Until BTF is available, we'll need to declare some of this struct manually, // since it isn't available to be #included. This will need maintenance to match // your kernel version. It is from kernel/sched/sched.h: struct cfs_rq_partial { struct load_weight load; unsigned long runnable_weight; unsigned int nr_running; unsigned int h_nr_running; }; BEGIN { printf("Sampling run queue length at 99 Hertz... Hit Ctrl-C to end.\n"); } profile:hz:99 { $task = (struct task_struct *)curtask; $my_q = (struct cfs_rq_partial *)$task->se.cfs_rq; $len = $my_q->nr_running; $len = $len > 0 ? $len - 1 : 0; // subtract currently running task @runqlen = lhist($len, 0, 100, 1); } bpftrace-0.9.4/tools/runqlen_example.txt000066400000000000000000000017241361633214400204210ustar00rootroot00000000000000Demonstrations of runqlen, the Linux BPF/bpftrace version. This tool samples the length of the CPU scheduler run queues, showing these sampled lengths as a histogram. This can be used to characterize demand for CPU resources. For example: # ./runqlen.bt Attaching 2 probes... Sampling run queue length at 99 Hertz... Hit Ctrl-C to end. ^C @runqlen: [0, 1) 1967 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [1, 2) 0 | | [2, 3) 0 | | [3, 4) 306 |@@@@@@@@ | This output shows that the run queue length was usually zero, except for some samples where it was 3. This was caused by binding 4 CPU bound threads to a single CPUs. There is another version of this tool in bcc: https://github.com/iovisor/bcc The bcc version provides options to customize the output. bpftrace-0.9.4/tools/setuids.bt000077500000000000000000000034161361633214400164730ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * setuids - Trace the setuid syscalls: privilege escalation. * * See BPF Performance Tools, Chapter 11, for an explanation of this tool. * * Copyright (c) 2019 Brendan Gregg. * Licensed under the Apache License, Version 2.0 (the "License"). * This was originally created for the BPF Performance Tools book * published by Addison Wesley. ISBN-13: 9780136554820 * When copying or porting, include this comment. * * 26-Feb-2019 Brendan Gregg Created this. */ BEGIN { printf("Tracing setuid(2) family syscalls. Hit Ctrl-C to end.\n"); printf("%-8s %-6s %-16s %-6s %-9s %s\n", "TIME", "PID", "COMM", "UID", "SYSCALL", "ARGS (RET)"); } tracepoint:syscalls:sys_enter_setuid, tracepoint:syscalls:sys_enter_setfsuid { @uid[tid] = uid; @setuid[tid] = args->uid; @seen[tid] = 1; } tracepoint:syscalls:sys_enter_setresuid { @uid[tid] = uid; @ruid[tid] = args->ruid; @euid[tid] = args->euid; @suid[tid] = args->suid; @seen[tid] = 1; } tracepoint:syscalls:sys_exit_setuid /@seen[tid]/ { time("%H:%M:%S "); printf("%-6d %-16s %-6d setuid uid=%d (%d)\n", pid, comm, @uid[tid], @setuid[tid], args->ret); delete(@seen[tid]); delete(@uid[tid]); delete(@setuid[tid]); } tracepoint:syscalls:sys_exit_setfsuid /@seen[tid]/ { time("%H:%M:%S "); printf("%-6d %-16s %-6d setfsuid uid=%d (prevuid=%d)\n", pid, comm, @uid[tid], @setuid[tid], args->ret); delete(@seen[tid]); delete(@uid[tid]); delete(@setuid[tid]); } tracepoint:syscalls:sys_exit_setresuid /@seen[tid]/ { time("%H:%M:%S "); printf("%-6d %-16s %-6d setresuid ", pid, comm, @uid[tid]); printf("ruid=%d euid=%d suid=%d (%d)\n", @ruid[tid], @euid[tid], @suid[tid], args->ret); delete(@seen[tid]); delete(@uid[tid]); delete(@ruid[tid]); delete(@euid[tid]); delete(@suid[tid]); } bpftrace-0.9.4/tools/setuids_example.txt000066400000000000000000000046111361633214400204130ustar00rootroot00000000000000Demonstrations of setuids, the Linux bpftrace/eBPF version. This tool traces privilege escalation via setuid syscalls (setuid(2), setfsuid(2), retresuid(2)). For example, here are the setuid calls during an ssh login: # ./setuids.bt Attaching 7 probes... Tracing setuid(2) family syscalls. Hit Ctrl-C to end. TIME PID COMM UID SYSCALL ARGS (RET) 14:28:22 21785 ssh 1000 setresuid ruid=-1 euid=1000 suid=-1 (0) 14:28:22 21787 sshd 0 setresuid ruid=122 euid=122 suid=122 (0) 14:28:22 21787 sshd 122 setuid uid=0 (-1) 14:28:22 21787 sshd 122 setresuid ruid=-1 euid=0 suid=-1 (-1) 14:28:24 21786 sshd 0 setresuid ruid=-1 euid=1000 suid=-1 (0) 14:28:24 21786 sshd 0 setresuid ruid=-1 euid=0 suid=-1 (0) 14:28:24 21786 sshd 0 setresuid ruid=-1 euid=1000 suid=-1 (0) 14:28:24 21786 sshd 0 setresuid ruid=-1 euid=0 suid=-1 (0) 14:28:24 21786 sshd 0 setfsuid uid=1000 (prevuid=0) 14:28:24 21786 sshd 0 setfsuid uid=1000 (prevuid=1000) 14:28:24 21786 sshd 0 setfsuid uid=0 (prevuid=1000) 14:28:24 21786 sshd 0 setfsuid uid=0 (prevuid=0) 14:28:24 21786 sshd 0 setfsuid uid=1000 (prevuid=0) 14:28:24 21786 sshd 0 setfsuid uid=1000 (prevuid=1000) 14:28:24 21786 sshd 0 setfsuid uid=0 (prevuid=1000) 14:28:24 21786 sshd 0 setfsuid uid=0 (prevuid=0) 14:28:24 21786 sshd 0 setfsuid uid=1000 (prevuid=0) 14:28:24 21786 sshd 0 setfsuid uid=1000 (prevuid=1000) 14:28:24 21786 sshd 0 setfsuid uid=0 (prevuid=1000) 14:28:24 21786 sshd 0 setfsuid uid=0 (prevuid=0) 14:28:24 21851 sshd 0 setresuid ruid=1000 euid=1000 suid=1000 (0) 14:28:24 21851 sshd 1000 setuid uid=0 (-1) 14:28:24 21851 sshd 1000 setresuid ruid=-1 euid=0 suid=-1 (-1) Why does sshd make so many calls? I don't know! Nevertheless, this shows what this tool can do: it shows the caller details (PID, COMM, and UID), the syscall (SYSCALL), and the syscall arguments (ARGS) and return value (RET). You can modify this tool to print user stack traces for each call, which will show the code path in sshd (provided it is compiled with frame pointers). bpftrace-0.9.4/tools/statsnoop.bt000077500000000000000000000024221361633214400170410ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * statsnoop Trace stat() syscalls. * For Linux, uses bpftrace and eBPF. * * This traces the traecepoints for statfs(), statx(), newstat(), and * newlstat(). These aren't the only the stat syscalls: if you are missing * activity, you may need to add more variants. * * Also a basic example of bpftrace. * * USAGE: statsnoop.bt * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 08-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Tracing stat syscalls... Hit Ctrl-C to end.\n"); printf("%-6s %-16s %3s %s\n", "PID", "COMM", "ERR", "PATH"); } tracepoint:syscalls:sys_enter_statfs { @filename[tid] = args->pathname; } tracepoint:syscalls:sys_enter_statx, tracepoint:syscalls:sys_enter_newstat, tracepoint:syscalls:sys_enter_newlstat { @filename[tid] = args->filename; } tracepoint:syscalls:sys_exit_statfs, tracepoint:syscalls:sys_exit_statx, tracepoint:syscalls:sys_exit_newstat, tracepoint:syscalls:sys_exit_newlstat /@filename[tid]/ { $ret = args->ret; $errno = $ret >= 0 ? 0 : - $ret; printf("%-6d %-16s %3d %s\n", pid, comm, $errno, str(@filename[tid])); delete(@filename[tid]); } END { clear(@filename); } bpftrace-0.9.4/tools/statsnoop_example.txt000066400000000000000000000052621361633214400207700ustar00rootroot00000000000000Demonstrations of statsnoop, the Linux bpftrace/eBPF version. statsnoop traces different stat() syscalls system-wide, and prints details. Example output: # ./statsnoop.bt Attaching 9 probes... Tracing stat syscalls... Hit Ctrl-C to end. PID COMM ERR PATH 27835 bash 0 . 27835 bash 2 /usr/local/sbin/iconfig 27835 bash 2 /usr/local/bin/iconfig 27835 bash 2 /usr/sbin/iconfig 27835 bash 2 /usr/bin/iconfig 27835 bash 2 /sbin/iconfig 27835 bash 2 /bin/iconfig 27835 bash 2 /usr/games/iconfig 27835 bash 2 /usr/local/games/iconfig 27835 bash 2 /snap/bin/iconfig 27835 bash 2 /apps/python/bin/iconfig 30573 command-not-fou 2 /usr/bin/Modules/Setup 30573 command-not-fou 2 /usr/bin/lib/python3.5/os.py 30573 command-not-fou 2 /usr/bin/lib/python3.5/os.pyc 30573 command-not-fou 0 /usr/lib/python3.5/os.py 30573 command-not-fou 2 /usr/bin/pybuilddir.txt 30573 command-not-fou 2 /usr/bin/lib/python3.5/lib-dynload 30573 command-not-fou 0 /usr/lib/python3.5/lib-dynload 30573 command-not-fou 2 /usr/lib/python35.zip 30573 command-not-fou 0 /usr/lib 30573 command-not-fou 2 /usr/lib/python35.zip 30573 command-not-fou 0 /usr/lib/python3.5/ 30573 command-not-fou 0 /usr/lib/python3.5/ 30573 command-not-fou 0 /usr/lib/python3.5/ 30573 command-not-fou 2 /usr/lib/python3.5/encodings/__init__.cpython-35m-x86_64-linux- 30573 command-not-fou 2 /usr/lib/python3.5/encodings/__init__.abi3.so 30573 command-not-fou 2 /usr/lib/python3.5/encodings/__init__.so 30573 command-not-fou 0 /usr/lib/python3.5/encodings/__init__.py 30573 command-not-fou 0 /usr/lib/python3.5/encodings/__init__.py This output has caught me mistyping a command in another shell, "iconfig" instead of "ifconfig". The first several lines show the bash shell searching the $PATH (why is games in my $PATH??), and failing to find it (ERR == 2 is file not found). Then, a "command-not-found" program executes (the name is truncated to 16 characters in the COMM field, including the NULL), which begins the process of searching for and suggesting a package. ie, this: # iconfig The program 'iconfig' is currently not installed. You can install it by typing: apt install ipmiutil statsnoop can be used for general debugging, to see what file information has been requested, and whether those files exist. It can be used as a companion to opensnoop, which shows what files were actually opened. There is another version of this tool in bcc: https://github.com/iovisor/bcc The bcc version provides options to customize the output. bpftrace-0.9.4/tools/swapin.bt000077500000000000000000000011301361633214400163030ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * swapin - Show swapins by process. * * See BPF Performance Tools, Chapter 7, for an explanation of this tool. * * Copyright (c) 2019 Brendan Gregg. * Licensed under the Apache License, Version 2.0 (the "License"). * This was originally created for the BPF Performance Tools book * published by Addison Wesley. ISBN-13: 9780136554820 * When copying or porting, include this comment. * * 26-Jan-2019 Brendan Gregg Created this. */ kprobe:swap_readpage { @[comm, pid] = count(); } interval:s:1 { time(); print(@); clear(@); } bpftrace-0.9.4/tools/swapin_example.txt000066400000000000000000000010451361633214400202320ustar00rootroot00000000000000Demonstrations of swapin, the Linux BCC/eBPF version. This tool counts swapins by process, to show which process is affected by swapping. For example: # ./swapin.bt Attaching 2 probes... 13:36:59 13:37:00 @[chrome, 4536]: 10809 @[gnome-shell, 2239]: 12410 13:37:01 @[chrome, 4536]: 3826 13:37:02 @[cron, 1180]: 23 @[gnome-shell, 2239]: 2462 13:37:03 @[gnome-shell, 1444]: 4 @[gnome-shell, 2239]: 3420 13:37:04 13:37:05 [...] While tracing, this showed that PID 2239 (gnome-shell) and PID 4536 (chrome) suffered over ten thousand swapins. bpftrace-0.9.4/tools/syncsnoop.bt000077500000000000000000000015061361633214400170440ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * syncsnoop Trace sync() variety of syscalls. * For Linux, uses bpftrace and eBPF. * * Also a basic example of bpftrace. * * USAGE: syncsnoop.bt * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 06-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Tracing sync syscalls... Hit Ctrl-C to end.\n"); printf("%-9s %-6s %-16s %s\n", "TIME", "PID", "COMM", "EVENT"); } tracepoint:syscalls:sys_enter_sync, tracepoint:syscalls:sys_enter_syncfs, tracepoint:syscalls:sys_enter_fsync, tracepoint:syscalls:sys_enter_fdatasync, tracepoint:syscalls:sys_enter_sync_file_range, tracepoint:syscalls:sys_enter_msync { time("%H:%M:%S "); printf("%-6d %-16s %s\n", pid, comm, probe); } bpftrace-0.9.4/tools/syncsnoop_example.txt000066400000000000000000000010351361633214400207630ustar00rootroot00000000000000Demonstrations of syncsnoop, the Linux bpftrace/eBPF version. Tracing file system sync events: # ./syncsnoop.bt Attaching 7 probes... Tracing sync syscalls... Hit Ctrl-C to end. TIME PID COMM EVENT 02:02:17 27933 sync tracepoint:syscalls:sys_enter_sync 02:03:43 27936 sync tracepoint:syscalls:sys_enter_sync The output shows calls to the sync() syscall (traced via its tracepoint), along with various details. There is another version of this tool in bcc: https://github.com/iovisor/bcc bpftrace-0.9.4/tools/syscount.bt000077500000000000000000000015521361633214400167010ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * syscount.bt Count system callls. * For Linux, uses bpftrace, eBPF. * * This is a bpftrace version of the bcc tool of the same name. * The bcc versions translates syscall IDs to their names, and this version * currently does not. Syscall IDs can be listed by "ausyscall --dump". * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 13-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Counting syscalls... Hit Ctrl-C to end.\n"); // ausyscall --dump | awk 'NR > 1 { printf("\t@sysname[%d] = \"%s\";\n", $1, $2); }' } tracepoint:raw_syscalls:sys_enter { @syscall[args->id] = count(); @process[comm] = count(); } END { printf("\nTop 10 syscalls IDs:\n"); print(@syscall, 10); clear(@syscall); printf("\nTop 10 processes:\n"); print(@process, 10); clear(@process); } bpftrace-0.9.4/tools/syscount_example.txt000066400000000000000000000021701361633214400206200ustar00rootroot00000000000000Demonstrations of syscount, the Linux bpftrace/eBPF version. syscount counts system calls, and prints summaries of the top ten syscall IDs, and the top ten process names making syscalls. For example: # ./syscount.bt Attaching 3 probes... Counting syscalls... Hit Ctrl-C to end. ^C Top 10 syscalls IDs: @syscall[6]: 36862 @syscall[21]: 42189 @syscall[13]: 44532 @syscall[12]: 58456 @syscall[9]: 82113 @syscall[8]: 95575 @syscall[5]: 147658 @syscall[3]: 163269 @syscall[2]: 270801 @syscall[4]: 326333 Top 10 processes: @process[rm]: 14360 @process[tail]: 16011 @process[objtool]: 20767 @process[fixdep]: 28489 @process[as]: 48982 @process[gcc]: 90652 @process[command-not-fou]: 172874 @process[sh]: 270515 @process[cc1]: 482888 @process[make]: 1404065 The above output was traced during a Linux kernel build, and the process name with the most syscalls was "make" with 1,404,065 syscalls while tracing. The highest syscall ID was 4, which is stat(). There is another version of this tool in bcc: https://github.com/iovisor/bcc The bcc version provides different command line options, and translates the syscall IDs to their syscall names. bpftrace-0.9.4/tools/tcpaccept.bt000077500000000000000000000032721361633214400167610ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * tcpaccept.bt Trace TCP accept()s * For Linux, uses bpftrace and eBPF. * * USAGE: tcpaccept.bt * * This is a bpftrace version of the bcc tool of the same name. * * This uses dynamic tracing of the kernel inet_csk_accept() socket function * (from tcp_prot.accept), and will need to be modified to match kernel changes. * Copyright (c) 2018 Dale Hamel. * Licensed under the Apache License, Version 2.0 (the "License") * 23-Nov-2018 Dale Hamel created this. */ #include #include BEGIN { printf("Tracing TCP accepts. Hit Ctrl-C to end.\n"); printf("%-8s %-6s %-14s ", "TIME", "PID", "COMM"); printf("%-39s %-5s %-39s %-5s %s\n", "RADDR", "RPORT", "LADDR", "LPORT", "BL"); } kretprobe:inet_csk_accept { $sk = (struct sock *)retval; $inet_family = $sk->__sk_common.skc_family; if ($inet_family == AF_INET || $inet_family == AF_INET6) { // initialize variable type: $daddr = ntop(0); $saddr = ntop(0); if ($inet_family == AF_INET) { $daddr = ntop($sk->__sk_common.skc_daddr); $saddr = ntop($sk->__sk_common.skc_rcv_saddr); } else { $daddr = ntop( $sk->__sk_common.skc_v6_daddr.in6_u.u6_addr8); $saddr = ntop( $sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr8); } $lport = $sk->__sk_common.skc_num; $dport = $sk->__sk_common.skc_dport; $qlen = $sk->sk_ack_backlog; $qmax = $sk->sk_max_ack_backlog; // Destination port is big endian, it must be flipped $dport = ($dport >> 8) | (($dport << 8) & 0x00FF00); time("%H:%M:%S "); printf("%-6d %-14s ", pid, comm); printf("%-39s %-5d %-39s %-5d ", $daddr, $dport, $saddr, $lport); printf("%d/%d\n", $qlen, $qmax); } } bpftrace-0.9.4/tools/tcpaccept_example.txt000066400000000000000000000025271361633214400207050ustar00rootroot00000000000000Demonstrations of tcpaccept, the Linux bpftrace/eBPF version. This tool traces the kernel function accepting TCP socket connections (eg, a passive connection via accept(); not connect()). Some example output (IP addresses changed to protect the innocent): # ./tcpaccept.bt Tracing tcp accepts. Hit Ctrl-C to end. TIME PID COMM RADDR RPORT LADDR LPORT BL 00:34:19 3949061 nginx 10.228.22.228 44226 10.229.20.169 8080 0/128 00:34:19 3951399 ruby 127.0.0.1 52422 127.0.0.1 8000 0/128 00:34:19 3949062 nginx 10.228.23.128 35408 10.229.20.169 8080 0/128 This output shows three connections, an IPv4 connections to PID 1463622, a "redis-server" process listening on port 6379, and one IPv6 connection to a "thread.rb" process listening on port 8000. The remote address and port are also printed, and the accept queue current size as well as maximum size are shown. The overhead of this tool should be negligible, since it is only tracing the kernel function performing accept. It is not tracing every packet and then filtering. This tool only traces successful TCP accept()s. Connection attempts to closed ports will not be shown (those can be traced via other functions). There is another version of this tool in bcc: https://github.com/iovisor/bcc USAGE message: # ./tcpaccept.bt bpftrace-0.9.4/tools/tcpconnect.bt000077500000000000000000000031211361633214400171440ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * tcpconnect.bt Trace TCP connect()s. * For Linux, uses bpftrace and eBPF. * * USAGE: tcpconnect.bt * * This is a bpftrace version of the bcc tool of the same name. * It is limited to ipv4 addresses. * * All connection attempts are traced, even if they ultimately fail. * * This uses dynamic tracing of kernel functions, and will need to be updated * to match kernel changes. * * Copyright (c) 2018 Dale Hamel. * Licensed under the Apache License, Version 2.0 (the "License") * * 23-Nov-2018 Dale Hamel created this. */ #include #include BEGIN { printf("Tracing tcp connections. Hit Ctrl-C to end.\n"); printf("%-8s %-8s %-16s ", "TIME", "PID", "COMM"); printf("%-39s %-6s %-39s %-6s\n", "SADDR", "SPORT", "DADDR", "DPORT"); } kprobe:tcp_connect { $sk = ((struct sock *) arg0); $inet_family = $sk->__sk_common.skc_family; if ($inet_family == AF_INET || $inet_family == AF_INET6) { if ($inet_family == AF_INET) { $daddr = ntop($sk->__sk_common.skc_daddr); $saddr = ntop($sk->__sk_common.skc_rcv_saddr); } else { $daddr = ntop($sk->__sk_common.skc_v6_daddr.in6_u.u6_addr8); $saddr = ntop($sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr8); } $lport = $sk->__sk_common.skc_num; $dport = $sk->__sk_common.skc_dport; // Destination port is big endian, it must be flipped $dport = ($dport >> 8) | (($dport << 8) & 0x00FF00); time("%H:%M:%S "); printf("%-8d %-16s ", pid, comm); printf("%-39s %-6d %-39s %-6d\n", $saddr, $lport, $daddr, $dport); } } bpftrace-0.9.4/tools/tcpconnect_example.txt000066400000000000000000000021011361633214400210630ustar00rootroot00000000000000Demonstrations of tcpconnect, the Linux bpftrace/eBPF version. This tool traces the kernel function performing active TCP connections (eg, via a connect() syscall; accept() are passive connections). Some example output (IP addresses changed to protect the innocent): # ./tcpconnect.bt TIME PID COMM SADDR SPORT DADDR DPORT 00:36:45 1798396 agent 127.0.0.1 5001 10.229.20.82 56114 00:36:45 1798396 curl 127.0.0.1 10255 10.229.20.82 56606 00:36:45 3949059 nginx 127.0.0.1 8000 127.0.0.1 37780 This output shows three connections, one from a "agent" process, one from "curl", and one from "redis-cli". The output details shows the IP version, source address, source socket port, destination address, and destination port. This traces attempted connections: these may have failed. The overhead of this tool should be negligible, since it is only tracing the kernel functions performing connect. It is not tracing every packet and then filtering. USAGE message: # ./tcpconnect.bt bpftrace-0.9.4/tools/tcpdrop.bt000077500000000000000000000042461361633214400164700ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * tcpdrop.bt Trace TCP kernel-dropped packets/segments. * For Linux, uses bpftrace and eBPF. * * USAGE: tcpdrop.bt * * This is a bpftrace version of the bcc tool of the same name. * It is limited to ipv4 addresses, and cannot show tcp flags. * * This provides information such as packet details, socket state, and kernel * stack trace for packets/segments that were dropped via tcp_drop(). * Copyright (c) 2018 Dale Hamel. * Licensed under the Apache License, Version 2.0 (the "License") * 23-Nov-2018 Dale Hamel created this. */ #include #include BEGIN { printf("Tracing tcp drops. Hit Ctrl-C to end.\n"); printf("%-8s %-8s %-16s %-21s %-21s %-8s\n", "TIME", "PID", "COMM", "SADDR:SPORT", "DADDR:DPORT", "STATE"); // See https://github.com/torvalds/linux/blob/master/include/net/tcp_states.h @tcp_states[1] = "ESTABLISHED"; @tcp_states[2] = "SYN_SENT"; @tcp_states[3] = "SYN_RECV"; @tcp_states[4] = "FIN_WAIT1"; @tcp_states[5] = "FIN_WAIT2"; @tcp_states[6] = "TIME_WAIT"; @tcp_states[7] = "CLOSE"; @tcp_states[8] = "CLOSE_WAIT"; @tcp_states[9] = "LAST_ACK"; @tcp_states[10] = "LISTEN"; @tcp_states[11] = "CLOSING"; @tcp_states[12] = "NEW_SYN_RECV"; } kprobe:tcp_drop { $sk = ((struct sock *) arg0); $inet_family = $sk->__sk_common.skc_family; if ($inet_family == AF_INET || $inet_family == AF_INET6) { if ($inet_family == AF_INET) { $daddr = ntop($sk->__sk_common.skc_daddr); $saddr = ntop($sk->__sk_common.skc_rcv_saddr); } else { $daddr = ntop($sk->__sk_common.skc_v6_daddr.in6_u.u6_addr8); $saddr = ntop($sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr8); } $lport = $sk->__sk_common.skc_num; $dport = $sk->__sk_common.skc_dport; // Destination port is big endian, it must be flipped $dport = ($dport >> 8) | (($dport << 8) & 0x00FF00); $state = $sk->__sk_common.skc_state; $statestr = @tcp_states[$state]; time("%H:%M:%S "); printf("%-8d %-16s ", pid, comm); printf("%39s:%-6d %39s:%-6d %-10s\n", $saddr, $lport, $daddr, $dport, $statestr); printf("%s\n", kstack); } } END { clear(@tcp_states); } bpftrace-0.9.4/tools/tcpdrop_example.txt000066400000000000000000000023501361633214400204040ustar00rootroot00000000000000Demonstrations of tcpdrop, the Linux bpftrace/eBPF version. tcpdrop prints details of TCP packets or segments that were dropped by the kernel, including the kernel stack trace that led to the drop: # ./tcpdrop.bt TIME PID COMM SADDR:SPORT DADDR:DPORT STATE 00:39:21 0 swapper/2 10.231.244.31:3306 10.229.20.82:50552 ESTABLISHE tcp_drop+0x1 tcp_v4_do_rcv+0x135 tcp_v4_rcv+0x9c7 ip_local_deliver_finish+0x62 ip_local_deliver+0x6f ip_rcv_finish+0x129 ip_rcv+0x28f __netif_receive_skb_core+0x432 __netif_receive_skb+0x18 netif_receive_skb_internal+0x37 napi_gro_receive+0xc5 ena_clean_rx_irq+0x3c3 ena_io_poll+0x33f net_rx_action+0x140 __softirqentry_text_start+0xdf irq_exit+0xb6 do_IRQ+0x82 ret_from_intr+0x0 native_safe_halt+0x6 default_idle+0x20 arch_cpu_idle+0x15 default_idle_call+0x23 do_idle+0x17f cpu_startup_entry+0x73 rest_init+0xae start_kernel+0x4dc x86_64_start_reservations+0x24 x86_64_start_kernel+0x74 secondary_startup_64+0xa5 [...] The last column shows the state of the TCP session. This tool is useful for debugging high rates of drops, which can cause the remote end to do timer-based retransmits, hurting performance. USAGE: # ./tcpdrop.bt bpftrace-0.9.4/tools/tcplife.bt000077500000000000000000000053011361633214400164340ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * tcplife - Trace TCP session lifespans with connection details. * * See BPF Performance Tools, Chapter 10, for an explanation of this tool. * * Copyright (c) 2019 Brendan Gregg. * Licensed under the Apache License, Version 2.0 (the "License"). * This was originally created for the BPF Performance Tools book * published by Addison Wesley. ISBN-13: 9780136554820 * When copying or porting, include this comment. * * 17-Apr-2019 Brendan Gregg Created this. */ #include #include #include #include BEGIN { printf("%-5s %-10s %-15s %-5s %-15s %-5s ", "PID", "COMM", "LADDR", "LPORT", "RADDR", "RPORT"); printf("%5s %5s %s\n", "TX_KB", "RX_KB", "MS"); } kprobe:tcp_set_state { $sk = (struct sock *)arg0; $newstate = arg1; /* * This tool includes PID and comm context. From TCP this is best * effort, and may be wrong in some situations. It does this: * - record timestamp on any state < TCP_FIN_WAIT1 * note some state transitions may not be present via this kprobe * - cache task context on: * TCP_SYN_SENT: tracing from client * TCP_LAST_ACK: client-closed from server * - do output on TCP_CLOSE: * fetch task context if cached, or use current task */ // record first timestamp seen for this socket if ($newstate < TCP_FIN_WAIT1 && @birth[$sk] == 0) { @birth[$sk] = nsecs; } // record PID & comm on SYN_SENT if ($newstate == TCP_SYN_SENT || $newstate == TCP_LAST_ACK) { @skpid[$sk] = pid; @skcomm[$sk] = comm; } // session ended: calculate lifespan and print if ($newstate == TCP_CLOSE && @birth[$sk]) { $delta_ms = (nsecs - @birth[$sk]) / 1000000; $lport = $sk->__sk_common.skc_num; $dport = $sk->__sk_common.skc_dport; $dport = ($dport >> 8) | (($dport << 8) & 0xff00); $tp = (struct tcp_sock *)$sk; $pid = @skpid[$sk]; $comm = @skcomm[$sk]; if ($comm == "") { // not cached, use current task $pid = pid; $comm = comm; } $family = $sk->__sk_common.skc_family; $saddr = ntop(0); $daddr = ntop(0); if ($family == AF_INET) { $saddr = ntop(AF_INET, $sk->__sk_common.skc_rcv_saddr); $daddr = ntop(AF_INET, $sk->__sk_common.skc_daddr); } else { // AF_INET6 $saddr = ntop(AF_INET6, $sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr8); $daddr = ntop(AF_INET6, $sk->__sk_common.skc_v6_daddr.in6_u.u6_addr8); } printf("%-5d %-10.10s %-15s %-5d %-15s %-6d ", $pid, $comm, $saddr, $lport, $daddr, $dport); printf("%5d %5d %d\n", $tp->bytes_acked / 1024, $tp->bytes_received / 1024, $delta_ms); delete(@birth[$sk]); delete(@skpid[$sk]); delete(@skcomm[$sk]); } } END { clear(@birth); clear(@skpid); clear(@skcomm); } bpftrace-0.9.4/tools/tcplife_example.txt000066400000000000000000000030751361633214400203640ustar00rootroot00000000000000Demonstrations of tcplife, the Linux bpftrace/eBPF version. This tool shows the lifespan of TCP sessions, including througphut statistics, and for efficiency only instruments TCP state changes (rather than all packets). For example: # ./tcplife.bt PID COMM LADDR LPORT RADDR RPORT TX_KB RX_KB MS 20976 ssh 127.0.0.1 56766 127.0.0.1 22 6 10584 3059 20977 sshd 127.0.0.1 22 127.0.0.1 56766 10584 6 3059 14519 monitord 127.0.0.1 44832 127.0.0.1 44444 0 0 0 4496 Chrome_IOT 7f00:6:5ea7::a00:0 42846 0:0:bb01:: 443 0 3 12441 4496 Chrome_IOT 7f00:6:5aa7::a00:0 42842 0:0:bb01:: 443 0 3 12436 4496 Chrome_IOT 7f00:6:62a7::a00:0 42850 0:0:bb01:: 443 0 3 12436 4496 Chrome_IOT 7f00:6:5ca7::a00:0 42844 0:0:bb01:: 443 0 3 12442 4496 Chrome_IOT 7f00:6:60a7::a00:0 42848 0:0:bb01:: 443 0 3 12436 4496 Chrome_IOT 10.0.0.65 33342 54.241.2.241 443 0 3 10717 4496 Chrome_IOT 10.0.0.65 33350 54.241.2.241 443 0 3 10711 4496 Chrome_IOT 10.0.0.65 33352 54.241.2.241 443 0 3 10712 14519 monitord 127.0.0.1 44832 127.0.0.1 44444 0 0 0 The output begins with a localhost ssh connection, so both endpoints can be seen: the ssh process (PID 20976) which received 10584 Kbytes, and the sshd process (PID 20977) which transmitted 10584 Kbytes. This session lasted 3059 milliseconds. Other sessions can also be seen, including IPv6 connections. bpftrace-0.9.4/tools/tcpretrans.bt000077500000000000000000000041101361633214400171700ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * tcpretrans.bt Trace or count TCP retransmits * For Linux, uses bpftrace and eBPF. * * USAGE: tcpretrans.bt * * This is a bpftrace version of the bcc tool of the same name. * It is limited to ipv4 addresses, and doesn't support tracking TLPs. * * This uses dynamic tracing of kernel functions, and will need to be updated * to match kernel changes. * * Copyright (c) 2018 Dale Hamel. * Licensed under the Apache License, Version 2.0 (the "License") * * 23-Nov-2018 Dale Hamel created this. */ #include #include BEGIN { printf("Tracing tcp retransmits. Hit Ctrl-C to end.\n"); printf("%-8s %-8s %20s %21s %6s\n", "TIME", "PID", "LADDR:LPORT", "RADDR:RPORT", "STATE"); // See include/net/tcp_states.h: @tcp_states[1] = "ESTABLISHED"; @tcp_states[2] = "SYN_SENT"; @tcp_states[3] = "SYN_RECV"; @tcp_states[4] = "FIN_WAIT1"; @tcp_states[5] = "FIN_WAIT2"; @tcp_states[6] = "TIME_WAIT"; @tcp_states[7] = "CLOSE"; @tcp_states[8] = "CLOSE_WAIT"; @tcp_states[9] = "LAST_ACK"; @tcp_states[10] = "LISTEN"; @tcp_states[11] = "CLOSING"; @tcp_states[12] = "NEW_SYN_RECV"; } kprobe:tcp_retransmit_skb { $sk = (struct sock *)arg0; $inet_family = $sk->__sk_common.skc_family; if ($inet_family == AF_INET || $inet_family == AF_INET6) { // initialize variable type: $daddr = ntop(0); $saddr = ntop(0); if ($inet_family == AF_INET) { $daddr = ntop($sk->__sk_common.skc_daddr); $saddr = ntop($sk->__sk_common.skc_rcv_saddr); } else { $daddr = ntop( $sk->__sk_common.skc_v6_daddr.in6_u.u6_addr8); $saddr = ntop( $sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr8); } $lport = $sk->__sk_common.skc_num; $dport = $sk->__sk_common.skc_dport; // Destination port is big endian, it must be flipped $dport = ($dport >> 8) | (($dport << 8) & 0x00FF00); $state = $sk->__sk_common.skc_state; $statestr = @tcp_states[$state]; time("%H:%M:%S "); printf("%-8d %14s:%-6d %14s:%-6d %6s\n", pid, $saddr, $lport, $daddr, $dport, $statestr); } } END { clear(@tcp_states); } bpftrace-0.9.4/tools/tcpretrans_example.txt000066400000000000000000000021721361633214400211200ustar00rootroot00000000000000Demonstrations of tcpretrans, the Linux bpftrace/eBPF version. This tool traces the kernel TCP retransmit function to show details of these retransmits. For example: # ./tcpretrans.bt TIME PID LADDR:LPORT RADDR:RPORT STATE 00:43:54 0 10.229.20.82:46654 153.2.224.76:443 SYN_SENT 00:43:55 0 10.232.0.49:57678 10.229.20.99:24231 SYN_SENT 00:43:57 100 10.229.20.175:54224 10.201.76.122:443 ESTABLISHED [...] This output shows three TCP retransmits, the first two were for an IPv4 connection from 10.153.223.157 port 22 to 69.53.245.40 port 34619. The TCP state was "ESTABLISHED" at the time of the retransmit. The on-CPU PID at the time of the retransmit is printed, in this case 0 (the kernel, which will be the case most of the time). Retransmits are usually a sign of poor network health, and this tool is useful for their investigation. Unlike using tcpdump, this tool has very low overhead, as it only traces the retransmit function. It also prints additional kernel details: the state of the TCP session at the time of the retransmit. USAGE message: # ./tcpretrans.bt bpftrace-0.9.4/tools/tcpsynbl.bt000077500000000000000000000016411361633214400166470ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * tcpsynbl - Show TCP SYN backlog as a histogram. * * See BPF Performance Tools, Chapter 10, for an explanation of this tool. * * Copyright (c) 2019 Brendan Gregg. * Licensed under the Apache License, Version 2.0 (the "License"). * This was originally created for the BPF Performance Tools book * published by Addison Wesley. ISBN-13: 9780136554820 * When copying or porting, include this comment. * * 19-Apr-2019 Brendan Gregg Created this. */ #include BEGIN { printf("Tracing SYN backlog size. Ctrl-C to end.\n"); } kprobe:tcp_v4_syn_recv_sock, kprobe:tcp_v6_syn_recv_sock { $sock = (struct sock *)arg0; @backlog[$sock->sk_max_ack_backlog & 0xffffffff] = hist($sock->sk_ack_backlog); if ($sock->sk_ack_backlog > $sock->sk_max_ack_backlog) { time("%H:%M:%S dropping a SYN.\n"); } } END { printf("\n@backlog[backlog limit]: histogram of backlog size\n"); } bpftrace-0.9.4/tools/tcpsynbl_example.txt000066400000000000000000000016541361633214400205750ustar00rootroot00000000000000Demonstrations of tcpsynbl, the Linux bpftrace/eBPF version. This tool shows the TCP SYN backlog size during SYN arrival as a histogram. This lets you see how close your applications are to hitting the backlog limit and dropping SYNs (causing performance issues with SYN retransmits). For example: # ./tcpsynbl.bt Attaching 4 probes... Tracing SYN backlog size. Ctrl-C to end. ^C @backlog[backlog limit]: histogram of backlog size @backlog[500]: [0] 2266 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [1] 3 | | [2, 4) 1 | | This output shows that for the backlog limit of 500, there were 2266 SYN arrivals where the backlog was zero, three where the backlog was one, and one where the backlog was either two or three. This indicates that we are nowhere near this limit. bpftrace-0.9.4/tools/threadsnoop.bt000077500000000000000000000013011361633214400173300ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * threadsnoop - List new thread creation. * * See BPF Performance Tools, Chapter 13, for an explanation of this tool. * * Copyright (c) 2019 Brendan Gregg. * Licensed under the Apache License, Version 2.0 (the "License"). * This was originally created for the BPF Performance Tools book * published by Addison Wesley. ISBN-13: 9780136554820 * When copying or porting, include this comment. * * 15-Feb-2019 Brendan Gregg Created this. */ BEGIN { printf("%-10s %-6s %-16s %s\n", "TIME(ms)", "PID", "COMM", "FUNC"); } uprobe:/lib/x86_64-linux-gnu/libpthread.so.0:pthread_create { printf("%-10u %-6d %-16s %s\n", elapsed / 1000000, pid, comm, usym(arg2)); } bpftrace-0.9.4/tools/threadsnoop_example.txt000066400000000000000000000021121361633214400212530ustar00rootroot00000000000000Demonstrations of threadsnoop, the Linux bpftrace/eBPF version. Tracing new threads via phtread_create(): # ./threadsnoop.bt Attaching 2 probes... TIME(ms) PID COMM FUNC 1938 28549 dockerd threadentry 1939 28549 dockerd threadentry 1939 28549 dockerd threadentry 1940 28549 dockerd threadentry 1949 28549 dockerd threadentry 1958 28549 dockerd threadentry 1939 28549 dockerd threadentry 1950 28549 dockerd threadentry 2013 28579 docker-containe 0x562f30f2e710 2036 28549 dockerd threadentry 2083 28579 docker-containe 0x562f30f2e710 2116 629 systemd-journal 0x7fb7114955c0 2116 629 systemd-journal 0x7fb7114955c0 [...] The output shows a dockerd process creating several threads with the start routine threadentry(), and docker-containe (truncated) and systemd-journal also starting threads: in their cases, the function had no symbol information available, so their addresses are printed in hex. bpftrace-0.9.4/tools/vfscount.bt000077500000000000000000000010031361633214400166500ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * vfscount Count VFS calls ("vfs_*"). * For Linux, uses bpftrace and eBPF. * * Written as a basic example of counting kernel functions. * * USAGE: vfscount.bt * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 06-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Tracing VFS calls... Hit Ctrl-C to end.\n"); } kprobe:vfs_* { @[func] = count(); } bpftrace-0.9.4/tools/vfscount_example.txt000066400000000000000000000022601361633214400206000ustar00rootroot00000000000000Demonstrations of vfscount, the Linux bpftrace/eBPF version. Tracing all VFS calls: # ./vfscount.bt Attaching 54 probes... cannot attach kprobe, Invalid argument Warning: could not attach probe kprobe:vfs_dedupe_get_page.isra.21, skipping. Tracing VFS calls... Hit Ctrl-C to end. ^C @[vfs_fsync_range]: 4 @[vfs_readlink]: 14 @[vfs_statfs]: 56 @[vfs_lock_file]: 60 @[vfs_write]: 276 @[vfs_statx]: 328 @[vfs_statx_fd]: 394 @[vfs_open]: 541 @[vfs_getattr]: 595 @[vfs_getattr_nosec]: 597 @[vfs_read]: 1113 While tracing, the vfs_read() call was the most frequent, occurring 1,113 times. VFS is the Virtual File System: a kernel abstraction for file systems and other resources that expose a file system interface. Much of VFS maps directly to the syscall interface. Tracing VFS calls gives you a high level breakdown of the kernel workload, and starting points for further investigation. Notet that a warning was printed: "Warning: could not attach probe kprobe:vfs_dedupe_get_page.isra.21": these are not currently instrumentable by bpftrace/kprobes, so a warning is printed to let you know that they will be missed. There is another version of this tool in bcc: https://github.com/iovisor/bcc bpftrace-0.9.4/tools/vfsstat.bt000077500000000000000000000013211361633214400164760ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * vfsstat Count some VFS calls, with per-second summaries. * For Linux, uses bpftrace and eBPF. * * Written as a basic example of counting multiple events and printing a * per-second summary. * * USAGE: vfsstat.bt * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 06-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Tracing key VFS calls... Hit Ctrl-C to end.\n"); } kprobe:vfs_read*, kprobe:vfs_write*, kprobe:vfs_fsync, kprobe:vfs_open, kprobe:vfs_create { @[func] = count(); } interval:s:1 { time(); print(@); clear(@); } END { clear(@); } bpftrace-0.9.4/tools/vfsstat_example.txt000066400000000000000000000016411361633214400204250ustar00rootroot00000000000000Demonstrations of vfsstat, the Linux bpftrace/eBPF version. This traces some common VFS calls (see the script for the list) and prints per-second summaries. # ./vfsstat.bt Attaching 8 probes... Tracing key VFS calls... Hit Ctrl-C to end. 21:30:38 @[vfs_write]: 1274 @[vfs_open]: 8675 @[vfs_read]: 11515 21:30:39 @[vfs_write]: 1155 @[vfs_open]: 8077 @[vfs_read]: 10398 21:30:40 @[vfs_write]: 1222 @[vfs_open]: 8554 @[vfs_read]: 11011 21:30:41 @[vfs_write]: 1230 @[vfs_open]: 8605 @[vfs_read]: 11077 21:30:42 @[vfs_write]: 1229 @[vfs_open]: 8591 @[vfs_read]: 11061 ^C Each second, a timestamp is printed ("HH:MM:SS") followed by common VFS functions and the number of calls for that second. While tracing, the vfs_read() kernel function was most frequent, occurring over 10,000 times per second. There is another version of this tool in bcc: https://github.com/iovisor/bcc The bcc version provides command line options. bpftrace-0.9.4/tools/writeback.bt000077500000000000000000000032501361633214400167620ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * writeback Trace file system writeback events with details. * For Linux, uses bpftrace and eBPF. * * This traces when file system dirtied pages are flushed to disk by kernel * writeback, and prints details including when the event occurred, and the * duration of the event. This can be useful for correlating these times with * other performance problems, and if there is a match, it would be a clue * that the problem may be caused by writeback. How quickly the kernel does * writeback can be tuned: see the kernel docs, eg, * vm.dirty_writeback_centisecs. * * USAGE: writeback.bt * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 14-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Tracing writeback... Hit Ctrl-C to end.\n"); printf("%-9s %-8s %-8s %-16s %s\n", "TIME", "DEVICE", "PAGES", "REASON", "ms"); // see /sys/kernel/debug/tracing/events/writeback/writeback_start/format @reason[0] = "background"; @reason[1] = "vmscan"; @reason[2] = "sync"; @reason[3] = "periodic"; @reason[4] = "laptop_timer"; @reason[5] = "free_more_memory"; @reason[6] = "fs_free_space"; @reason[7] = "forker_thread"; } tracepoint:writeback:writeback_start { @start[args->sb_dev] = nsecs; } tracepoint:writeback:writeback_written { $sb_dev = args->sb_dev; $s = @start[$sb_dev]; delete(@start[$sb_dev]); $lat = $s ? (nsecs - $s) / 1000 : 0; time("%H:%M:%S "); printf("%-8s %-8d %-16s %d.%03d\n", args->name, args->nr_pages & 0xffff, // TODO: explain these bitmasks @reason[args->reason & 0xffffffff], $lat / 1000, $lat % 1000); } END { clear(@reason); clear(@start); } bpftrace-0.9.4/tools/writeback_example.txt000066400000000000000000000036521361633214400207120ustar00rootroot00000000000000Demonstrations of writeback, the Linux bpftrace/eBPF version. This tool traces when the kernel writeback procedure is writing dirtied pages to disk, and shows details such as the time, device numbers, reason for the write back, and the duration. For example: # ./writeback.bt Attaching 4 probes... Tracing writeback... Hit Ctrl-C to end. TIME DEVICE PAGES REASON ms 23:28:47 259:1 15791 periodic 0.005 23:28:48 259:0 15792 periodic 0.004 23:28:52 259:1 15784 periodic 0.003 23:28:53 259:0 18682 periodic 0.003 23:28:55 259:0 41970 background 326.663 23:28:56 259:0 18418 background 332.689 23:28:56 259:0 60402 background 362.446 23:28:57 259:1 18230 periodic 0.005 23:28:57 259:1 65492 background 3.343 23:28:57 259:1 65492 background 0.002 23:28:58 259:0 36850 background 0.000 23:28:58 259:0 13298 background 597.198 23:28:58 259:0 55282 background 322.050 23:28:59 259:0 31730 background 336.031 23:28:59 259:0 8178 background 357.119 23:29:01 259:0 50162 background 1803.146 23:29:02 259:0 27634 background 1311.876 23:29:03 259:0 6130 background 331.599 23:29:03 259:0 50162 background 293.968 23:29:03 259:0 28658 background 284.946 23:29:03 259:0 7154 background 286.572 [...] By looking a the timestamps and latency, it can be seen that the system was not spending much time in writeback until 23:28:55, when "background" writeback began, taking over 300 milliseconds per flush. If timestamps of heavy writeback coincide with times when applications suffered performance issues, that would be a clue that they are correlated and there is contention for the disk devices. There are various ways to tune this: eg, vm.dirty_writeback_centisecs. bpftrace-0.9.4/tools/xfsdist.bt000077500000000000000000000017141361633214400164760ustar00rootroot00000000000000#!/usr/bin/env bpftrace /* * xfsdist Summarize XFS operation latency. * For Linux, uses bpftrace and eBPF. * * This traces four common file system calls: read, write, open, and fsync. * It can be customized to trace more if desired. * * USAGE: xfsdist.bt * * This is a bpftrace version of the bcc tool of the same name. * * Copyright 2018 Netflix, Inc. * Licensed under the Apache License, Version 2.0 (the "License") * * 08-Sep-2018 Brendan Gregg Created this. */ BEGIN { printf("Tracing XFS operation latency... Hit Ctrl-C to end.\n"); } kprobe:xfs_file_read_iter, kprobe:xfs_file_write_iter, kprobe:xfs_file_open, kprobe:xfs_file_fsync { @start[tid] = nsecs; @name[tid] = func; } kretprobe:xfs_file_read_iter, kretprobe:xfs_file_write_iter, kretprobe:xfs_file_open, kretprobe:xfs_file_fsync /@start[tid]/ { @us[@name[tid]] = hist((nsecs - @start[tid]) / 1000); delete(@start[tid]); delete(@name[tid]); } END { clear(@start); clear(@name); } bpftrace-0.9.4/tools/xfsdist_example.txt000066400000000000000000000065331361633214400204240ustar00rootroot00000000000000Demonstrations of xfsdist, the Linux bpftrace/eBPF version. xfsdist traces XFS reads, writes, opens, and fsyncs, and summarizes their latency as a power-of-2 histogram. For example: # xfsdist.bt Attaching 9 probes... Tracing XFS operation latency... Hit Ctrl-C to end. ^C @us[xfs_file_write_iter]: [8, 16) 1 |@@@@@@@@@@@@@@@@@@@@@@@@@@ | [16, 32) 2 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| @us[xfs_file_read_iter]: [1] 724 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [2, 4) 137 |@@@@@@@@@ | [4, 8) 143 |@@@@@@@@@@ | [8, 16) 37 |@@ | [16, 32) 11 | | [32, 64) 22 |@ | [64, 128) 7 | | [128, 256) 0 | | [256, 512) 485 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ | [512, 1K) 149 |@@@@@@@@@@ | [1K, 2K) 98 |@@@@@@@ | [2K, 4K) 85 |@@@@@@ | [4K, 8K) 27 |@ | [8K, 16K) 29 |@@ | [16K, 32K) 25 |@ | [32K, 64K) 1 | | [64K, 128K) 0 | | [128K, 256K) 6 | | @us[xfs_file_open]: [1] 1819 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@| [2, 4) 272 |@@@@@@@ | [4, 8) 0 | | [8, 16) 9 | | [16, 32) 7 | | This output shows a bi-modal distribution for read latency, with a faster mode of 724 reads that took between 0 and 1 microseconds, and a slower mode of over 485 reads that took between 256 and 512 microseconds. It's likely that the faster mode was a hit from the in-memory file system cache, and the slower mode is a read from a storage device (disk). This "latency" is measured from when the operation was issued from the VFS interface to the file system, to when it completed. This spans everything: block device I/O (disk I/O), file system CPU cycles, file system locks, run queue latency, etc. This is a better measure of the latency suffered by applications reading from the file system than measuring this down at the block device interface. Note that this only traces the common file system operations previously listed: other file system operations (eg, inode operations including getattr()) are not traced. There is another version of this tool in bcc: https://github.com/iovisor/bcc The bcc version provides command line options to customize the output.